From 715346a7e73f8a2739351c6517b32a54e26de2b3 Mon Sep 17 00:00:00 2001 From: Domen Grabec Date: Tue, 3 Feb 2026 11:29:38 +0100 Subject: [PATCH 01/36] initial commit --- .../interfaces/morpho/IMorphoV2Adapter.sol | 9 +++ .../contracts/interfaces/morpho/IVaultV2.sol | 8 ++ .../contracts/strategies/MorphoV2Strategy.sol | 73 +++++++++++++++++++ 3 files changed, 90 insertions(+) create mode 100644 contracts/contracts/interfaces/morpho/IMorphoV2Adapter.sol create mode 100644 contracts/contracts/interfaces/morpho/IVaultV2.sol create mode 100644 contracts/contracts/strategies/MorphoV2Strategy.sol diff --git a/contracts/contracts/interfaces/morpho/IMorphoV2Adapter.sol b/contracts/contracts/interfaces/morpho/IMorphoV2Adapter.sol new file mode 100644 index 0000000000..6aeedce3d9 --- /dev/null +++ b/contracts/contracts/interfaces/morpho/IMorphoV2Adapter.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +interface IMorphoV2Adapter { + // address of the underlying vault + function morphoVaultV1() external view returns (address); + // address of the parent Morpho V2 vault + function parentVault() external view returns (address); +} \ No newline at end of file diff --git a/contracts/contracts/interfaces/morpho/IVaultV2.sol b/contracts/contracts/interfaces/morpho/IVaultV2.sol new file mode 100644 index 0000000000..579b994c44 --- /dev/null +++ b/contracts/contracts/interfaces/morpho/IVaultV2.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import { IERC4626 } from "../../../lib/openzeppelin/interfaces/IERC4626.sol"; + +interface IVaultV2 is IERC4626 { + function liquidityAdapter() external view returns (address); +} \ No newline at end of file diff --git a/contracts/contracts/strategies/MorphoV2Strategy.sol b/contracts/contracts/strategies/MorphoV2Strategy.sol new file mode 100644 index 0000000000..9130d56a4a --- /dev/null +++ b/contracts/contracts/strategies/MorphoV2Strategy.sol @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +/** + * @title Generalized 4626 Strategy when asset is Tether USD (USDT) + * @notice Investment strategy for ERC-4626 Tokenized Vaults for the USDT asset. + * @author Origin Protocol Inc + */ +import { Generalized4626Strategy } from "./Generalized4626Strategy.sol"; +import { IVaultV2 } from "../interfaces/morpho/IVaultV2.sol"; +import { IERC4626 } from "../../lib/openzeppelin/interfaces/IERC4626.sol"; +import { IMorphoV2Adapter } from "../interfaces/morpho/IMorphoV2Adapter.sol"; + +contract MorphoV2Strategy is Generalized4626Strategy { + /** + * @param _baseConfig Base strategy config with Morpho V2 Vault and + * vaultAddress (OToken Vault contract), eg VaultProxy or OETHVaultProxy + * @param _assetToken Address of the ERC-4626 asset token. e.g. USDC + */ + constructor(BaseStrategyConfig memory _baseConfig, address _assetToken) + Generalized4626Strategy(_baseConfig, _assetToken) + {} + + /** + * @dev Remove all the liquidity that is available in the Morpho V2 vault + * The particular behaviour of the Morpho V2 vault is that it can hold + * multiple Morpho V1 vaults as adapters but only one liquidity adapter. + * The immediate available funds on the Morpho V2 vault are therfore any + * any liquid assets residing on the Vault V2 contract and the maxWithdraw + * amount that the Morpho V1 contract can supply. + * + */ + function withdrawAll() + external + virtual + override + onlyVaultOrGovernor + nonReentrant + { + uint256 availableAssetLiquidity = _maxWithdraw(); + + if (availableAssetLiquidity > 0) { + IVaultV2(platformAddress).withdraw( + availableAssetLiquidity, + vaultAddress, + address(this) + ); + } + + emit Withdrawal( + address(assetToken), + address(shareToken), + availableAssetLiquidity + ); + } + + function maxWithdraw() external view returns (uint256) { + return _maxWithdraw(); + } + + function _maxWithdraw() internal view returns (uint256 availableAssetLiquidity) { + availableAssetLiquidity = assetToken.balanceOf(platformAddress); + + address liquidityAdapter = IVaultV2(platformAddress).liquidityAdapter(); + if (liquidityAdapter != address(0)) { + // adapter representing one Morpho V1 vault + address underlyingVault = IMorphoV2Adapter(liquidityAdapter).morphoVaultV1(); + availableAssetLiquidity += IERC4626(underlyingVault).maxWithdraw(liquidityAdapter); + } + } + + +} From cfe1e1606902a9ced97205add8539193e03d2a70 Mon Sep 17 00:00:00 2001 From: Domen Grabec Date: Tue, 3 Feb 2026 11:51:30 +0100 Subject: [PATCH 02/36] add deploy file --- .../168_upgrade_ousd_morpho_v2_strategy.js | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 contracts/deploy/mainnet/168_upgrade_ousd_morpho_v2_strategy.js diff --git a/contracts/deploy/mainnet/168_upgrade_ousd_morpho_v2_strategy.js b/contracts/deploy/mainnet/168_upgrade_ousd_morpho_v2_strategy.js new file mode 100644 index 0000000000..648a69249c --- /dev/null +++ b/contracts/deploy/mainnet/168_upgrade_ousd_morpho_v2_strategy.js @@ -0,0 +1,37 @@ +const addresses = require("../../utils/addresses"); +const { deploymentWithGovernanceProposal } = require("../../utils/deploy"); + +module.exports = deploymentWithGovernanceProposal( + { + deployName: "168_upgrade_ousd_morpho_v2_strategy", + forceDeploy: false, + reduceQueueTime: true, + deployerIsProposer: false, + proposalId: "", + }, + async ({ deployWithConfirmation }) => { + const cVaultProxy = await ethers.getContract("VaultProxy"); + const cOUSDMorphoV2StrategyProxy = await ethers.getContract( + "OUSDMorphoV2StrategyProxy" + ); + + const dMorphoV2StrategyImpl = await deployWithConfirmation( + "MorphoV2Strategy", + [ + [addresses.mainnet.MorphoOUSDv2Vault, cVaultProxy.address], + addresses.mainnet.USDC, + ] + ); + + return { + name: "Upgrade OUSD Morpho V2 strategy implementation", + actions: [ + { + contract: cOUSDMorphoV2StrategyProxy, + signature: "upgradeTo(address)", + args: [dMorphoV2StrategyImpl.address], + }, + ], + }; + } +); From 7bdb55ec0212a770ac0326dd94569c97597767c4 Mon Sep 17 00:00:00 2001 From: Domen Grabec Date: Wed, 4 Feb 2026 14:14:41 +0100 Subject: [PATCH 03/36] account for owner liquidity and available liquidity --- .../strategies/Generalized4626Strategy.sol | 2 +- .../contracts/strategies/MorphoV2Strategy.sol | 14 +++++++---- .../ousd-v2-morpho.mainnet.fork-test.js | 24 +++++++------------ 3 files changed, 18 insertions(+), 22 deletions(-) diff --git a/contracts/contracts/strategies/Generalized4626Strategy.sol b/contracts/contracts/strategies/Generalized4626Strategy.sol index 0695a6fc0c..ff8aea0c64 100644 --- a/contracts/contracts/strategies/Generalized4626Strategy.sol +++ b/contracts/contracts/strategies/Generalized4626Strategy.sol @@ -154,7 +154,7 @@ contract Generalized4626Strategy is InitializableAbstractStrategy { * @return balance Total value of the asset in the platform */ function checkBalance(address _asset) - external + public view virtual override diff --git a/contracts/contracts/strategies/MorphoV2Strategy.sol b/contracts/contracts/strategies/MorphoV2Strategy.sol index 9130d56a4a..cebbb9dcb0 100644 --- a/contracts/contracts/strategies/MorphoV2Strategy.sol +++ b/contracts/contracts/strategies/MorphoV2Strategy.sol @@ -10,6 +10,7 @@ import { Generalized4626Strategy } from "./Generalized4626Strategy.sol"; import { IVaultV2 } from "../interfaces/morpho/IVaultV2.sol"; import { IERC4626 } from "../../lib/openzeppelin/interfaces/IERC4626.sol"; import { IMorphoV2Adapter } from "../interfaces/morpho/IMorphoV2Adapter.sol"; +import { Math } from "@openzeppelin/contracts/utils/math/Math.sol"; contract MorphoV2Strategy is Generalized4626Strategy { /** @@ -37,11 +38,14 @@ contract MorphoV2Strategy is Generalized4626Strategy { onlyVaultOrGovernor nonReentrant { - uint256 availableAssetLiquidity = _maxWithdraw(); + uint256 availableMorphoVault = _maxWithdraw(); - if (availableAssetLiquidity > 0) { - IVaultV2(platformAddress).withdraw( - availableAssetLiquidity, + uint256 strategyAssetBalance = checkBalance(address(assetToken)); + uint256 balanceToWithdraw = Math.min(availableMorphoVault, strategyAssetBalance); + + if (balanceToWithdraw > 0) { + IVaultV2(platformAddress).withdraw( + balanceToWithdraw, vaultAddress, address(this) ); @@ -50,7 +54,7 @@ contract MorphoV2Strategy is Generalized4626Strategy { emit Withdrawal( address(assetToken), address(shareToken), - availableAssetLiquidity + balanceToWithdraw ); } diff --git a/contracts/test/strategies/ousd-v2-morpho.mainnet.fork-test.js b/contracts/test/strategies/ousd-v2-morpho.mainnet.fork-test.js index d7c8b9d780..41c92199e7 100644 --- a/contracts/test/strategies/ousd-v2-morpho.mainnet.fork-test.js +++ b/contracts/test/strategies/ousd-v2-morpho.mainnet.fork-test.js @@ -2,7 +2,7 @@ const { expect } = require("chai"); const { formatUnits, parseUnits } = require("ethers/lib/utils"); const addresses = require("../../utils/addresses"); -const { canWithdrawAllFromMorphoOUSD } = require("../../utils/morpho"); +const { morphoWithdrawShortfall } = require("../../utils/morpho"); const { getMerklRewards } = require("../../tasks/merkl"); const { units, isCI } = require("../helpers"); @@ -222,15 +222,17 @@ describe("ForkTest: Yearn's Morpho OUSD v2 Strategy", function () { const strategyVaultShares = await morphoOUSDv2Vault.balanceOf( morphoOUSDv2Strategy.address ); + const withdrawAllShortfall = await morphoWithdrawShortfall(); const usdcWithdrawAmountExpected = await morphoOUSDv2Vault.convertToAssets(strategyVaultShares); expect(usdcWithdrawAmountExpected).to.be.gte(minBalance.sub(1)); - + // amount expected minus the shortfall due to not enough liquidity in the Morpho OUSD v1 Vault + const usdcWithdrawAmountAvailable = usdcWithdrawAmountExpected.sub(withdrawAllShortfall) log( - `Expected to withdraw ${formatUnits( + `Wanted to withdraw ${formatUnits( usdcWithdrawAmountExpected, 6 - )} USDC` + )} USDC, adjusted for shortfall of ${formatUnits(withdrawAllShortfall, 6)} USDC totals to ${formatUnits(usdcWithdrawAmountAvailable, 6)} USDC` ); const ousdSupplyBefore = await ousd.totalSupply(); @@ -238,11 +240,6 @@ describe("ForkTest: Yearn's Morpho OUSD v2 Strategy", function () { log("Before withdraw all from strategy"); - const withdrawAllAllowed = await canWithdrawAllFromMorphoOUSD(); - - // If there is not enough liquidity in the Morpho OUSD v1 Vault, skip the withdrawAll test - if (withdrawAllAllowed === false) return; - // Now try to withdraw all the WETH from the strategy const tx = await morphoOUSDv2Strategy.connect(vaultSigner).withdrawAll(); @@ -254,7 +251,7 @@ describe("ForkTest: Yearn's Morpho OUSD v2 Strategy", function () { morphoOUSDv2Vault.address, (amount) => expect(amount).approxEqualTolerance( - usdcWithdrawAmountExpected, + usdcWithdrawAmountAvailable, 0.01, "Withdrawal amount" ), @@ -268,7 +265,7 @@ describe("ForkTest: Yearn's Morpho OUSD v2 Strategy", function () { // Check the USDC amount in the vault increases expect(await usdc.balanceOf(vault.address)).to.approxEqualTolerance( - vaultUSDCBalanceBefore.add(usdcWithdrawAmountExpected), + vaultUSDCBalanceBefore.add(usdcWithdrawAmountAvailable), 0.01 ); }); @@ -335,11 +332,6 @@ describe("ForkTest: Yearn's Morpho OUSD v2 Strategy", function () { it("Only vault and governor can withdraw all USDC from the strategy", async function () { const { morphoOUSDv2Strategy, strategist, timelock, josh } = fixture; - const withdrawAllAllowed = await canWithdrawAllFromMorphoOUSD(); - - // If there is not enough liquidity in the Morpho OUSD v1 Vault, skip the withdrawAll test - if (withdrawAllAllowed === false) return; - for (const signer of [strategist, josh]) { const tx = morphoOUSDv2Strategy.connect(signer).withdrawAll(); From 451bc7bb567fb8b3593cdf09e6bccddb2f5cc227 Mon Sep 17 00:00:00 2001 From: Domen Grabec Date: Thu, 5 Feb 2026 13:11:07 +0100 Subject: [PATCH 04/36] prettier --- .../interfaces/morpho/IMorphoV2Adapter.sol | 7 +++-- .../contracts/interfaces/morpho/IVaultV2.sol | 2 +- .../contracts/strategies/MorphoV2Strategy.sol | 30 ++++++++++++------- .../ousd-v2-morpho.mainnet.fork-test.js | 8 +++-- 4 files changed, 30 insertions(+), 17 deletions(-) diff --git a/contracts/contracts/interfaces/morpho/IMorphoV2Adapter.sol b/contracts/contracts/interfaces/morpho/IMorphoV2Adapter.sol index 6aeedce3d9..313d6c5b35 100644 --- a/contracts/contracts/interfaces/morpho/IMorphoV2Adapter.sol +++ b/contracts/contracts/interfaces/morpho/IMorphoV2Adapter.sol @@ -2,8 +2,9 @@ pragma solidity ^0.8.0; interface IMorphoV2Adapter { - // address of the underlying vault + // address of the underlying vault function morphoVaultV1() external view returns (address); - // address of the parent Morpho V2 vault + + // address of the parent Morpho V2 vault function parentVault() external view returns (address); -} \ No newline at end of file +} diff --git a/contracts/contracts/interfaces/morpho/IVaultV2.sol b/contracts/contracts/interfaces/morpho/IVaultV2.sol index 579b994c44..354e6e15aa 100644 --- a/contracts/contracts/interfaces/morpho/IVaultV2.sol +++ b/contracts/contracts/interfaces/morpho/IVaultV2.sol @@ -5,4 +5,4 @@ import { IERC4626 } from "../../../lib/openzeppelin/interfaces/IERC4626.sol"; interface IVaultV2 is IERC4626 { function liquidityAdapter() external view returns (address); -} \ No newline at end of file +} diff --git a/contracts/contracts/strategies/MorphoV2Strategy.sol b/contracts/contracts/strategies/MorphoV2Strategy.sol index cebbb9dcb0..bda946c64f 100644 --- a/contracts/contracts/strategies/MorphoV2Strategy.sol +++ b/contracts/contracts/strategies/MorphoV2Strategy.sol @@ -14,19 +14,19 @@ import { Math } from "@openzeppelin/contracts/utils/math/Math.sol"; contract MorphoV2Strategy is Generalized4626Strategy { /** - * @param _baseConfig Base strategy config with Morpho V2 Vault and + * @param _baseConfig Base strategy config with Morpho V2 Vault and * vaultAddress (OToken Vault contract), eg VaultProxy or OETHVaultProxy * @param _assetToken Address of the ERC-4626 asset token. e.g. USDC */ constructor(BaseStrategyConfig memory _baseConfig, address _assetToken) Generalized4626Strategy(_baseConfig, _assetToken) {} - + /** * @dev Remove all the liquidity that is available in the Morpho V2 vault - * The particular behaviour of the Morpho V2 vault is that it can hold + * The particular behaviour of the Morpho V2 vault is that it can hold * multiple Morpho V1 vaults as adapters but only one liquidity adapter. - * The immediate available funds on the Morpho V2 vault are therfore any + * The immediate available funds on the Morpho V2 vault are therfore any * any liquid assets residing on the Vault V2 contract and the maxWithdraw * amount that the Morpho V1 contract can supply. * @@ -41,8 +41,11 @@ contract MorphoV2Strategy is Generalized4626Strategy { uint256 availableMorphoVault = _maxWithdraw(); uint256 strategyAssetBalance = checkBalance(address(assetToken)); - uint256 balanceToWithdraw = Math.min(availableMorphoVault, strategyAssetBalance); - + uint256 balanceToWithdraw = Math.min( + availableMorphoVault, + strategyAssetBalance + ); + if (balanceToWithdraw > 0) { IVaultV2(platformAddress).withdraw( balanceToWithdraw, @@ -62,16 +65,21 @@ contract MorphoV2Strategy is Generalized4626Strategy { return _maxWithdraw(); } - function _maxWithdraw() internal view returns (uint256 availableAssetLiquidity) { + function _maxWithdraw() + internal + view + returns (uint256 availableAssetLiquidity) + { availableAssetLiquidity = assetToken.balanceOf(platformAddress); address liquidityAdapter = IVaultV2(platformAddress).liquidityAdapter(); if (liquidityAdapter != address(0)) { // adapter representing one Morpho V1 vault - address underlyingVault = IMorphoV2Adapter(liquidityAdapter).morphoVaultV1(); - availableAssetLiquidity += IERC4626(underlyingVault).maxWithdraw(liquidityAdapter); + address underlyingVault = IMorphoV2Adapter(liquidityAdapter) + .morphoVaultV1(); + availableAssetLiquidity += IERC4626(underlyingVault).maxWithdraw( + liquidityAdapter + ); } } - - } diff --git a/contracts/test/strategies/ousd-v2-morpho.mainnet.fork-test.js b/contracts/test/strategies/ousd-v2-morpho.mainnet.fork-test.js index 41c92199e7..3add96409a 100644 --- a/contracts/test/strategies/ousd-v2-morpho.mainnet.fork-test.js +++ b/contracts/test/strategies/ousd-v2-morpho.mainnet.fork-test.js @@ -227,12 +227,16 @@ describe("ForkTest: Yearn's Morpho OUSD v2 Strategy", function () { await morphoOUSDv2Vault.convertToAssets(strategyVaultShares); expect(usdcWithdrawAmountExpected).to.be.gte(minBalance.sub(1)); // amount expected minus the shortfall due to not enough liquidity in the Morpho OUSD v1 Vault - const usdcWithdrawAmountAvailable = usdcWithdrawAmountExpected.sub(withdrawAllShortfall) + const usdcWithdrawAmountAvailable = + usdcWithdrawAmountExpected.sub(withdrawAllShortfall); log( `Wanted to withdraw ${formatUnits( usdcWithdrawAmountExpected, 6 - )} USDC, adjusted for shortfall of ${formatUnits(withdrawAllShortfall, 6)} USDC totals to ${formatUnits(usdcWithdrawAmountAvailable, 6)} USDC` + )} USDC, adjusted for shortfall of ${formatUnits( + withdrawAllShortfall, + 6 + )} USDC totals to ${formatUnits(usdcWithdrawAmountAvailable, 6)} USDC` ); const ousdSupplyBefore = await ousd.totalSupply(); From 1f943d8b72bbb19a146b0b6aa24bbf0a4d8b6c6a Mon Sep 17 00:00:00 2001 From: Domen Grabec Date: Fri, 6 Feb 2026 13:18:43 +0100 Subject: [PATCH 05/36] add comment --- contracts/contracts/strategies/MorphoV2Strategy.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contracts/contracts/strategies/MorphoV2Strategy.sol b/contracts/contracts/strategies/MorphoV2Strategy.sol index bda946c64f..3bd8426c56 100644 --- a/contracts/contracts/strategies/MorphoV2Strategy.sol +++ b/contracts/contracts/strategies/MorphoV2Strategy.sol @@ -23,13 +23,14 @@ contract MorphoV2Strategy is Generalized4626Strategy { {} /** + * @notice Remove all the liquidity that is available in the Morpho V2 vault. + Which might not be all of the liquidity owned by the strategy. * @dev Remove all the liquidity that is available in the Morpho V2 vault * The particular behaviour of the Morpho V2 vault is that it can hold * multiple Morpho V1 vaults as adapters but only one liquidity adapter. * The immediate available funds on the Morpho V2 vault are therfore any * any liquid assets residing on the Vault V2 contract and the maxWithdraw * amount that the Morpho V1 contract can supply. - * */ function withdrawAll() external From 9ee8188968cce5fe838aa6d291a7893ab643699e Mon Sep 17 00:00:00 2001 From: Domen Grabec Date: Tue, 10 Feb 2026 15:39:07 +0100 Subject: [PATCH 06/36] add support to redeem only what is available from 4626 Vault --- .../contracts/strategies/Generalized4626Strategy.sol | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/contracts/contracts/strategies/Generalized4626Strategy.sol b/contracts/contracts/strategies/Generalized4626Strategy.sol index ff8aea0c64..10882e3af9 100644 --- a/contracts/contracts/strategies/Generalized4626Strategy.sol +++ b/contracts/contracts/strategies/Generalized4626Strategy.sol @@ -4,6 +4,9 @@ pragma solidity ^0.8.0; /** * @title Generalized 4626 Strategy * @notice Investment strategy for ERC-4626 Tokenized Vaults + * @dev This strategy should not be used for the Morpho V2 Vaults as those are not + * completley ERC-4626 compliant - they don't implement the maxWithdraw() and + * maxRedeem() functions and rather return 0 when any of them is called. * @author Origin Protocol Inc */ import { IERC4626 } from "../../lib/openzeppelin/interfaces/IERC4626.sol"; @@ -132,11 +135,13 @@ contract Generalized4626Strategy is InitializableAbstractStrategy { onlyVaultOrGovernor nonReentrant { - uint256 shareBalance = shareToken.balanceOf(address(this)); + // @dev Don't use for Morpho V2 Vaults as below line will return 0 + uint256 sharesToRedeem = IERC4626(platformAddress).maxRedeem(address(this)); + uint256 assetAmount = 0; - if (shareBalance > 0) { + if (sharesToRedeem > 0) { assetAmount = IERC4626(platformAddress).redeem( - shareBalance, + sharesToRedeem, vaultAddress, address(this) ); From 6e2d3295035fe6bd76003d0cf07b13eb6d052b32 Mon Sep 17 00:00:00 2001 From: Domen Grabec Date: Tue, 10 Feb 2026 19:19:44 +0100 Subject: [PATCH 07/36] enforce the expected liquidity adpater when estimating the additional possible liquidity in the withdrawAll --- .../contracts/strategies/MorphoV2Strategy.sol | 27 +++++++++++++++---- .../168_upgrade_ousd_morpho_v2_strategy.js | 1 + contracts/utils/addresses.js | 3 ++- contracts/utils/morpho.js | 4 +-- 4 files changed, 27 insertions(+), 8 deletions(-) diff --git a/contracts/contracts/strategies/MorphoV2Strategy.sol b/contracts/contracts/strategies/MorphoV2Strategy.sol index 3bd8426c56..e9017400a8 100644 --- a/contracts/contracts/strategies/MorphoV2Strategy.sol +++ b/contracts/contracts/strategies/MorphoV2Strategy.sol @@ -2,8 +2,8 @@ pragma solidity ^0.8.0; /** - * @title Generalized 4626 Strategy when asset is Tether USD (USDT) - * @notice Investment strategy for ERC-4626 Tokenized Vaults for the USDT asset. + * @title Generalized 4626 Strategy when the underlying platform is Morpho V2 + * @notice Investment strategy for ERC-4626 Tokenized Vaults for the Morpho V2 platform. * @author Origin Protocol Inc */ import { Generalized4626Strategy } from "./Generalized4626Strategy.sol"; @@ -13,14 +13,26 @@ import { IMorphoV2Adapter } from "../interfaces/morpho/IMorphoV2Adapter.sol"; import { Math } from "@openzeppelin/contracts/utils/math/Math.sol"; contract MorphoV2Strategy is Generalized4626Strategy { + + IMorphoV2Adapter public immutable expectedAdapter; + /** * @param _baseConfig Base strategy config with Morpho V2 Vault and * vaultAddress (OToken Vault contract), eg VaultProxy or OETHVaultProxy * @param _assetToken Address of the ERC-4626 asset token. e.g. USDC + * @param _expectedAdapter The Morpho V1 marketplace adapter address that is expected to offer + * additional liquidity for the Morpho V2 vault. The nature of the Morpho V2 vaults is that the + * adapter can be switched for another (type of) adapter. Which could point to Morpho V2 vault, + * or future V2 Market not yet introduced. The `withdrawAll` function of this strategy only supports + * Morpho V1 vault as the underlying liquidity source. For that reason the expected adapter must be + * confirmed to be a Morpho V1 adapter. */ - constructor(BaseStrategyConfig memory _baseConfig, address _assetToken) + constructor(BaseStrategyConfig memory _baseConfig, address _assetToken, address _expectedAdapter) Generalized4626Strategy(_baseConfig, _assetToken) - {} + { + require(_expectedAdapter != address(0), "Expected adapter must be set"); + expectedAdapter = IMorphoV2Adapter(_expectedAdapter); + } /** * @notice Remove all the liquidity that is available in the Morpho V2 vault. @@ -74,7 +86,12 @@ contract MorphoV2Strategy is Generalized4626Strategy { availableAssetLiquidity = assetToken.balanceOf(platformAddress); address liquidityAdapter = IVaultV2(platformAddress).liquidityAdapter(); - if (liquidityAdapter != address(0)) { + // This strategy can only safely calculate and withdraw additional liquidity when + // the liquidity adapter is set to the expected Morpho V1 Vault adapter. If that configuration + // changes the additional available liquidity can not be calculated anymore. + if ( + liquidityAdapter == address(expectedAdapter) + ) { // adapter representing one Morpho V1 vault address underlyingVault = IMorphoV2Adapter(liquidityAdapter) .morphoVaultV1(); diff --git a/contracts/deploy/mainnet/168_upgrade_ousd_morpho_v2_strategy.js b/contracts/deploy/mainnet/168_upgrade_ousd_morpho_v2_strategy.js index 648a69249c..124c1a21e1 100644 --- a/contracts/deploy/mainnet/168_upgrade_ousd_morpho_v2_strategy.js +++ b/contracts/deploy/mainnet/168_upgrade_ousd_morpho_v2_strategy.js @@ -20,6 +20,7 @@ module.exports = deploymentWithGovernanceProposal( [ [addresses.mainnet.MorphoOUSDv2Vault, cVaultProxy.address], addresses.mainnet.USDC, + addresses.mainnet.MorphoOUSDv2Adapter, ] ); diff --git a/contracts/utils/addresses.js b/contracts/utils/addresses.js index 6d058d6ef5..f23560fcc3 100644 --- a/contracts/utils/addresses.js +++ b/contracts/utils/addresses.js @@ -211,7 +211,8 @@ addresses.mainnet.MorphoOUSDv2StrategyProxy = "0x3643cafA6eF3dd7Fcc2ADaD1cabf708075AFFf6e"; addresses.mainnet.MorphoOUSDv1Vault = "0x5B8b9FA8e4145eE06025F642cAdB1B47e5F39F04"; -addresses.mainnet.MorphoOUSDv2Adaptor = +// Morpho V1 Vualt adapter used by the Morpho V2 Vault +addresses.mainnet.MorphoOUSDv2Adapter = "0xD8F093dCE8504F10Ac798A978eF9E0C230B2f5fF"; addresses.mainnet.MorphoOUSDv2Vault = "0xFB154c729A16802c4ad1E8f7FF539a8b9f49c960"; diff --git a/contracts/utils/morpho.js b/contracts/utils/morpho.js index 93483db8e1..6e8d76408b 100644 --- a/contracts/utils/morpho.js +++ b/contracts/utils/morpho.js @@ -25,7 +25,7 @@ async function morphoWithdrawShortfall() { ); const maxWithdrawal = await morphoOUSDv1Vault.maxWithdraw( - addresses.mainnet.MorphoOUSDv2Adaptor + addresses.mainnet.MorphoOUSDv2Adapter ); // check all funds can be withdrawn from the Morpho OUSD v2 Strategy @@ -74,7 +74,7 @@ async function snapMorpho({ block }) { ); const maxWithdrawal = await morphoOUSDv1Vault.maxWithdraw( - addresses.mainnet.MorphoOUSDv2Adaptor, + addresses.mainnet.MorphoOUSDv2Adapter, { blockTag } ); From ed8c49aa30622a3c3d530cebc0b3d808e99e598e Mon Sep 17 00:00:00 2001 From: Shah <10547529+shahthepro@users.noreply.github.com> Date: Tue, 10 Feb 2026 08:58:56 +0400 Subject: [PATCH 08/36] Cross-chain Strategy (#2715) * some scaffolding * add basic necessities for unit tests * checkpoint * Fix compiling issues * Add fork test scaffolding * Fix stuffs * Prettify and change salt * Add auto-verification * Fix checkBalance * Make CCTPHookWrapper more resilient * refactor message version and type checks * add some comments * add comment * fix compile errors * Change addresses * Cross chain changes (#2718) * fix deploy files * minor rename * add calls to Morpho Vault into a try catch * refactor hook wrapper * don't revert if withdraw from underlying fails * use checkBalance for deposit/withdrawal acknowledgment * update message in remote strategy * remove unneeded functions * Fix compilation issues * Fix deployment files a bit * Fix Message relayer * Clean up master strategy * Fix deployment file name * move around stuff * Fix CCTP Integrator * clean up fork * Fix race condition (#2720) * Fix race condition * Transfer everything on wtihdrawal * Move destination domain one step above * Cleanup code * decode payloads in fork tests * Add library for message handling * More changes * Add comments and prettify * WIP Unit test setup (#2722) * add cross chain unit test basic files * add basic unit test setup * add header encoding * more tests * Add more fork tests * Add token transfer tests * WIP Unit tests for OUSD Simplified strategy (#2724) * more unit test integration * more tying up ends * fix bug * cleanup * add full round-trip test * cleanup * Fix approve all and prettify --------- Co-authored-by: Shahul Hameed <10547529+shahthepro@users.noreply.github.com> * Fix master fork tests * linter * add direct withdrawal paths and additional checks * Fix Remote strategy tests * Update comments and clean up code * Fix comment * Fix failing unit test * fix: withdraw only if balance is lower than requested amount * Document crosschain strategy * Update deployment file numbers * adjust the charts * change the function visibility to pure * fix: create2 proxy without using deployer address * fix: impersonate a single deployer on fork * deploy script bug fix * Store create2 proxy addresses * fix: await * more logging * fix opts * Fix env for deploy action * Change writeFileSync to writeFile * add log * Add more logs * fix callback * Add empty file * Cleanup logs * withdraw funds according to the spec * Address Code Review comments (#2732) * Address Code Review comments * Fix initialize method * Fix initialize method * fix remote strat initialize method * Revert 999 * fix comments * add a test that uncovers a withdrawal error (#2733) * remove transferType * correct spelling * rename baseToken to usdcToken * improve error message * simplify code * fix: min withdraw amount is 1e6 * add validations for config * fix: require a min deposit amount of 1e6 * fix: withdrawAll caps withdraw amount * Move around constants * Move decode message header function * fix fork tests * prettify * adjust some comments * have consistent event names * fix: remove redundant check * simplify stuff * adjust comment * fix: variable name * Add TokensBridged and MessageTransmitted events * Add finality threshold checks * add comment regarding fast transfers * Add analytics info * Change error message * Set 1 USDC as min allowed value for deposits * Change comment * Change comment * Update max transfer amount comment * Set nonce(0) as processed during initialization * Use Strategizable for strategist functionality (#2740) * use Strategizable * Add comment --------- Co-authored-by: Shahul Hameed <10547529+shahthepro@users.noreply.github.com> * set vault address to zero (#2742) * remove unnecessary comments * Add comment * Add nonReentrant for deposit and withdraw methods * Add more checks in constructor * Fix withdrawAll * Update deploy numbers * simplify _withdraw (#2741) * address verification (#2749) * add address verification * remove operator check * Fix merge issue * small adjustement * fix unit test * Bumped the deploy script numbers * remove unused parameter (#2754) * Add unit tests (#2751) * fix unit test * add more unit tests * add more unit tests * prettier * add some more unit tests * add thorough unit test support * Default to Timelock governance * lint * Fix VaultAddress in deployment scripts * Add events for nonce updates (#2755) * Sparrow dom/cctp defender action (#2770) * Add unit tests (#2751) * fix unit test * add more unit tests * add more unit tests * prettier * add some more unit tests * add thorough unit test support * add comment * create a defender task and cleanup * small change * add the ability for the defender relay action to store already processed transactions * update gitignore * prettier * put into a better place * ... * add dry run option, also fix issues with cross chain providers * read cctp domain ids from config * make api a constant * remove finality checks * add custom per chain block lookback * readme change * move all configuration out of cross-chain source file, to more easily support multiple networks and relaying directions * clear the testing addresses * prettier * add test address * add the option not to initialize the implementation contract * Prettify * add comment (#2787) * ignore messages that are too old (#2786) * [OUSD-09] Check burnToken in relay method (#2782) * [OUSD-09] Check burnToken in relay method * Fix: Check burn token is usdc on remote chain * Add tests * Make min transfer amount a constant (#2780) * [OUSD-15] Emit event when withdrawAll is skipped (#2781) --------- Co-authored-by: Domen Grabec Co-authored-by: Nicholas Addison --- .gitignore | 1 + contracts/.solhintignore | 3 +- contracts/README.md | 7 +- contracts/abi/createx.json | 24 + .../contracts/governance/Strategizable.sol | 2 +- contracts/contracts/interfaces/cctp/ICCTP.sol | 75 ++ .../contracts/mocks/MockERC4626Vault.sol | 166 ++++ .../crosschain/CCTPMessageTransmitterMock.sol | 305 +++++++ .../CCTPMessageTransmitterMock2.sol | 91 ++ .../crosschain/CCTPTokenMessengerMock.sol | 119 +++ ...InitializeGovernedUpgradeabilityProxy2.sol | 22 + contracts/contracts/proxies/Proxies.sol | 1 + .../create2/CrossChainStrategyProxy.sol | 23 + .../strategies/Generalized4626Strategy.sol | 9 + .../crosschain/AbstractCCTPIntegrator.sol | 642 ++++++++++++++ .../crosschain/CrossChainMasterStrategy.sol | 369 ++++++++ .../crosschain/CrossChainRemoteStrategy.sol | 407 +++++++++ .../crosschain/CrossChainStrategyHelper.sol | 261 ++++++ .../crosschain/crosschain-strategy.md | 744 ++++++++++++++++ contracts/contracts/utils/BytesHelper.sol | 110 +++ .../utils/InitializableAbstractStrategy.sol | 2 +- .../base/040_crosschain_strategy_proxies.js | 21 + .../deploy/base/041_crosschain_strategy.js | 53 ++ contracts/deploy/deployActions.js | 341 ++++++- contracts/deploy/mainnet/000_mock.js | 21 + contracts/deploy/mainnet/001_core.js | 4 + .../165_crosschain_strategy_proxies.js | 25 + .../deploy/mainnet/166_crosschain_strategy.js | 59 ++ .../deployments/base/create2Proxies.json | 1 + .../deployments/mainnet/create2Proxies.json | 1 + .../defender-actions/crossChainRelay.js | 72 ++ .../defender-actions/rollup.config.cjs | 1 + contracts/tasks/crossChain.js | 213 +++++ contracts/tasks/defender.js | 1 + contracts/tasks/tasks.js | 73 ++ contracts/test/_fixture-base.js | 62 +- contracts/test/_fixture.js | 100 +++ .../crosschain/_crosschain-helpers.js | 276 ++++++ .../crosschain/cross-chain-strategy.js | 829 ++++++++++++++++++ ...chain-master-strategy.mainnet.fork-test.js | 550 ++++++++++++ ...osschain-remote-strategy.base.fork-test.js | 286 ++++++ contracts/utils/addresses.js | 17 + contracts/utils/cctp.js | 34 + contracts/utils/defender.js | 45 + contracts/utils/deploy.js | 5 + 45 files changed, 6465 insertions(+), 8 deletions(-) create mode 100644 contracts/contracts/interfaces/cctp/ICCTP.sol create mode 100644 contracts/contracts/mocks/MockERC4626Vault.sol create mode 100644 contracts/contracts/mocks/crosschain/CCTPMessageTransmitterMock.sol create mode 100644 contracts/contracts/mocks/crosschain/CCTPMessageTransmitterMock2.sol create mode 100644 contracts/contracts/mocks/crosschain/CCTPTokenMessengerMock.sol create mode 100644 contracts/contracts/proxies/InitializeGovernedUpgradeabilityProxy2.sol create mode 100644 contracts/contracts/proxies/create2/CrossChainStrategyProxy.sol create mode 100644 contracts/contracts/strategies/crosschain/AbstractCCTPIntegrator.sol create mode 100644 contracts/contracts/strategies/crosschain/CrossChainMasterStrategy.sol create mode 100644 contracts/contracts/strategies/crosschain/CrossChainRemoteStrategy.sol create mode 100644 contracts/contracts/strategies/crosschain/CrossChainStrategyHelper.sol create mode 100644 contracts/contracts/strategies/crosschain/crosschain-strategy.md create mode 100644 contracts/contracts/utils/BytesHelper.sol create mode 100644 contracts/deploy/base/040_crosschain_strategy_proxies.js create mode 100644 contracts/deploy/base/041_crosschain_strategy.js create mode 100644 contracts/deploy/mainnet/165_crosschain_strategy_proxies.js create mode 100644 contracts/deploy/mainnet/166_crosschain_strategy.js create mode 100644 contracts/deployments/base/create2Proxies.json create mode 100644 contracts/deployments/mainnet/create2Proxies.json create mode 100644 contracts/scripts/defender-actions/crossChainRelay.js create mode 100644 contracts/tasks/crossChain.js create mode 100644 contracts/test/strategies/crosschain/_crosschain-helpers.js create mode 100644 contracts/test/strategies/crosschain/cross-chain-strategy.js create mode 100644 contracts/test/strategies/crosschain/crosschain-master-strategy.mainnet.fork-test.js create mode 100644 contracts/test/strategies/crosschain/crosschain-remote-strategy.base.fork-test.js create mode 100644 contracts/utils/cctp.js create mode 100644 contracts/utils/defender.js diff --git a/.gitignore b/.gitignore index 847bb491de..127e456554 100644 --- a/.gitignore +++ b/.gitignore @@ -63,6 +63,7 @@ contracts/dist/ contracts/.localKeyValueStorage contracts/.localKeyValueStorage.mainnet contracts/.localKeyValueStorage.holesky +contracts/.localKeyValueStorage.base contracts/scripts/defender-actions/dist/ contracts/lib/defender-actions/dist/ diff --git a/contracts/.solhintignore b/contracts/.solhintignore index ffbcf74d3a..988f3bc831 100644 --- a/contracts/.solhintignore +++ b/contracts/.solhintignore @@ -1,2 +1,3 @@ node_modules -contracts/interfaces/morpho/Types.sol \ No newline at end of file +contracts/interfaces/morpho/Types.sol +contracts/mocks/**/*.sol \ No newline at end of file diff --git a/contracts/README.md b/contracts/README.md index 389dddc2b0..c7d9b283e0 100644 --- a/contracts/README.md +++ b/contracts/README.md @@ -370,7 +370,12 @@ pnpm hardhat updateAction --id f92ea662-fc34-433b-8beb-b34e9ab74685 --file sonic pnpm hardhat updateAction --id b1d831f1-29d4-4943-bb2e-8e625b76e82c --file claimBribes pnpm hardhat updateAction --id 6567d7c6-7ec7-44bd-b95b-470dd1ff780b --file manageBribeOnSonic pnpm hardhat updateAction --id 6a633bb0-aff8-4b37-aaae-b4c6f244ed87 --file managePassThrough -pnpm hardhat updateAction --id 076c59e4-4150-42c7-9ba0-9962069ac353 --file manageBribes +pnpm hardhat updateAction --id 076c59e4-4150-42c7-9ba0-9962069ac353 --file manageBribes +# These are Base -> Mainnet & Mainnet -> Base actions +# they share the codebase. The direction of relaying attestations is defined by the +# network of the relayer that is attached to the action +pnpm hardhat updateAction --id bb43e5da-f936-4185-84da-253394583665 --file crossChainRelay +pnpm hardhat updateAction --id e571409b-5399-48e4-bfb2-50b7af9903aa --file crossChainRelay ``` `rollup` can be installed globally to avoid the `npx` prefix. diff --git a/contracts/abi/createx.json b/contracts/abi/createx.json index 9e30b0e694..84904ff09a 100644 --- a/contracts/abi/createx.json +++ b/contracts/abi/createx.json @@ -23,6 +23,30 @@ "stateMutability": "payable", "type": "function" }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "salt", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "initCode", + "type": "bytes" + } + ], + "name": "deployCreate3", + "outputs": [ + { + "internalType": "address", + "name": "newContract", + "type": "address" + } + ], + "stateMutability": "payable", + "type": "function" + }, { "inputs": [ { diff --git a/contracts/contracts/governance/Strategizable.sol b/contracts/contracts/governance/Strategizable.sol index 62a3c0c028..4d823d6d1a 100644 --- a/contracts/contracts/governance/Strategizable.sol +++ b/contracts/contracts/governance/Strategizable.sol @@ -15,7 +15,7 @@ contract Strategizable is Governable { /** * @dev Verifies that the caller is either Governor or Strategist. */ - modifier onlyGovernorOrStrategist() { + modifier onlyGovernorOrStrategist() virtual { require( msg.sender == strategistAddr || isGovernor(), "Caller is not the Strategist or Governor" diff --git a/contracts/contracts/interfaces/cctp/ICCTP.sol b/contracts/contracts/interfaces/cctp/ICCTP.sol new file mode 100644 index 0000000000..639b0ee307 --- /dev/null +++ b/contracts/contracts/interfaces/cctp/ICCTP.sol @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +interface ICCTPTokenMessenger { + function depositForBurn( + uint256 amount, + uint32 destinationDomain, + bytes32 mintRecipient, + address burnToken, + bytes32 destinationCaller, + uint256 maxFee, + uint32 minFinalityThreshold + ) external; + + function depositForBurnWithHook( + uint256 amount, + uint32 destinationDomain, + bytes32 mintRecipient, + address burnToken, + bytes32 destinationCaller, + uint256 maxFee, + uint32 minFinalityThreshold, + bytes memory hookData + ) external; + + function getMinFeeAmount(uint256 amount) external view returns (uint256); +} + +interface ICCTPMessageTransmitter { + function sendMessage( + uint32 destinationDomain, + bytes32 recipient, + bytes32 destinationCaller, + uint32 minFinalityThreshold, + bytes memory messageBody + ) external; + + function receiveMessage(bytes calldata message, bytes calldata attestation) + external + returns (bool); +} + +interface IMessageHandlerV2 { + /** + * @notice Handles an incoming finalized message from an IReceiverV2 + * @dev Finalized messages have finality threshold values greater than or equal to 2000 + * @param sourceDomain The source domain of the message + * @param sender The sender of the message + * @param finalityThresholdExecuted the finality threshold at which the message was attested to + * @param messageBody The raw bytes of the message body + * @return success True, if successful; false, if not. + */ + function handleReceiveFinalizedMessage( + uint32 sourceDomain, + bytes32 sender, + uint32 finalityThresholdExecuted, + bytes calldata messageBody + ) external returns (bool); + + /** + * @notice Handles an incoming unfinalized message from an IReceiverV2 + * @dev Unfinalized messages have finality threshold values less than 2000 + * @param sourceDomain The source domain of the message + * @param sender The sender of the message + * @param finalityThresholdExecuted The finality threshold at which the message was attested to + * @param messageBody The raw bytes of the message body + * @return success True, if successful; false, if not. + */ + function handleReceiveUnfinalizedMessage( + uint32 sourceDomain, + bytes32 sender, + uint32 finalityThresholdExecuted, + bytes calldata messageBody + ) external returns (bool); +} diff --git a/contracts/contracts/mocks/MockERC4626Vault.sol b/contracts/contracts/mocks/MockERC4626Vault.sol new file mode 100644 index 0000000000..02b4672c2d --- /dev/null +++ b/contracts/contracts/mocks/MockERC4626Vault.sol @@ -0,0 +1,166 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { IERC4626 } from "../../lib/openzeppelin/interfaces/IERC4626.sol"; +import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; + +contract MockERC4626Vault is IERC4626, ERC20 { + using SafeERC20 for IERC20; + + address public asset; + uint8 public constant DECIMALS = 18; + + constructor(address _asset) ERC20("Mock Vault Share", "MVS") { + asset = _asset; + } + + // ERC20 totalSupply is inherited + + // ERC20 balanceOf is inherited + + function deposit(uint256 assets, address receiver) + public + override + returns (uint256 shares) + { + shares = previewDeposit(assets); + IERC20(asset).safeTransferFrom(msg.sender, address(this), assets); + _mint(receiver, shares); + return shares; + } + + function mint(uint256 shares, address receiver) + public + override + returns (uint256 assets) + { + assets = previewMint(shares); + IERC20(asset).safeTransferFrom(msg.sender, address(this), assets); + _mint(receiver, shares); + return assets; + } + + function withdraw( + uint256 assets, + address receiver, + address owner + ) public override returns (uint256 shares) { + shares = previewWithdraw(assets); + if (msg.sender != owner) { + // No approval check for mock + } + _burn(owner, shares); + IERC20(asset).safeTransfer(receiver, assets); + return shares; + } + + function redeem( + uint256 shares, + address receiver, + address owner + ) public override returns (uint256 assets) { + assets = previewRedeem(shares); + if (msg.sender != owner) { + // No approval check for mock + } + _burn(owner, shares); + IERC20(asset).safeTransfer(receiver, assets); + return assets; + } + + function totalAssets() public view override returns (uint256) { + return IERC20(asset).balanceOf(address(this)); + } + + function convertToShares(uint256 assets) + public + view + override + returns (uint256 shares) + { + uint256 supply = totalSupply(); // Use ERC20 totalSupply + return + supply == 0 || assets == 0 + ? assets + : (assets * supply) / totalAssets(); + } + + function convertToAssets(uint256 shares) + public + view + override + returns (uint256 assets) + { + uint256 supply = totalSupply(); // Use ERC20 totalSupply + return supply == 0 ? shares : (shares * totalAssets()) / supply; + } + + function maxDeposit(address receiver) + public + view + override + returns (uint256) + { + return type(uint256).max; + } + + function maxMint(address receiver) public view override returns (uint256) { + return type(uint256).max; + } + + function maxWithdraw(address owner) public view override returns (uint256) { + return convertToAssets(balanceOf(owner)); + } + + function maxRedeem(address owner) public view override returns (uint256) { + return balanceOf(owner); + } + + function previewDeposit(uint256 assets) + public + view + override + returns (uint256 shares) + { + return convertToShares(assets); + } + + function previewMint(uint256 shares) + public + view + override + returns (uint256 assets) + { + return convertToAssets(shares); + } + + function previewWithdraw(uint256 assets) + public + view + override + returns (uint256 shares) + { + return convertToShares(assets); + } + + function previewRedeem(uint256 shares) + public + view + override + returns (uint256 assets) + { + return convertToAssets(shares); + } + + function _mint(address account, uint256 amount) internal override { + super._mint(account, amount); + } + + function _burn(address account, uint256 amount) internal override { + super._burn(account, amount); + } + + // Inherited from ERC20 +} diff --git a/contracts/contracts/mocks/crosschain/CCTPMessageTransmitterMock.sol b/contracts/contracts/mocks/crosschain/CCTPMessageTransmitterMock.sol new file mode 100644 index 0000000000..a48667f974 --- /dev/null +++ b/contracts/contracts/mocks/crosschain/CCTPMessageTransmitterMock.sol @@ -0,0 +1,305 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import { ICCTPMessageTransmitter } from "../../interfaces/cctp/ICCTP.sol"; +import { IERC20 } from "../../utils/InitializableAbstractStrategy.sol"; +import { BytesHelper } from "../../utils/BytesHelper.sol"; +import { AbstractCCTPIntegrator } from "../../strategies/crosschain/AbstractCCTPIntegrator.sol"; + +/** + * @title Mock conctract simulating the functionality of the CCTPTokenMessenger contract + * for the porposes of unit testing. + * @author Origin Protocol Inc + */ + +contract CCTPMessageTransmitterMock is ICCTPMessageTransmitter { + using BytesHelper for bytes; + + IERC20 public usdc; + uint256 public nonce = 0; + // Sender index in the burn message v2 + // Ref: https://github.com/circlefin/evm-cctp-contracts/blob/master/src/messages/v2/BurnMessageV2.sol + uint8 constant BURN_MESSAGE_V2_MESSAGE_SENDER_INDEX = 100; + uint8 constant BURN_MESSAGE_V2_HOOK_DATA_INDEX = 228; + + uint16 public messageFinality = 2000; + address public messageSender; + uint32 public sourceDomain = 4294967295; // 0xFFFFFFFF + + // Full message with header + struct Message { + uint32 version; + uint32 sourceDomain; + uint32 destinationDomain; + bytes32 recipient; + bytes32 messageHeaderRecipient; + bytes32 sender; + bytes32 destinationCaller; + uint32 minFinalityThreshold; + bool isTokenTransfer; + uint256 tokenAmount; + bytes messageBody; + } + + Message[] public messages; + // map of encoded messages to the corresponding message structs + mapping(bytes32 => Message) public encodedMessages; + + constructor(address _usdc) { + usdc = IERC20(_usdc); + } + + // @dev for the porposes of unit tests queues the message to be mock-sent using + // the cctp bridge. + function sendMessage( + uint32 destinationDomain, + bytes32 recipient, + bytes32 destinationCaller, + uint32 minFinalityThreshold, + bytes memory messageBody + ) external virtual override { + bytes32 nonceHash = keccak256(abi.encodePacked(nonce)); + nonce++; + + // If destination is mainnet, source is base and vice versa + uint32 sourceDomain = destinationDomain == 0 ? 6 : 0; + + Message memory message = Message({ + version: 1, + sourceDomain: sourceDomain, + destinationDomain: destinationDomain, + recipient: recipient, + messageHeaderRecipient: recipient, + sender: bytes32(uint256(uint160(msg.sender))), + destinationCaller: destinationCaller, + minFinalityThreshold: minFinalityThreshold, + isTokenTransfer: false, + tokenAmount: 0, + messageBody: messageBody + }); + + messages.push(message); + } + + // @dev for the porposes of unit tests queues the USDC burn/mint to be executed + // using the cctp bridge. + function sendTokenTransferMessage( + uint32 destinationDomain, + bytes32 recipient, + bytes32 destinationCaller, + uint32 minFinalityThreshold, + uint256 tokenAmount, + bytes memory messageBody + ) external { + bytes32 nonceHash = keccak256(abi.encodePacked(nonce)); + nonce++; + + // If destination is mainnet, source is base and vice versa + uint32 sourceDomain = destinationDomain == 0 ? 6 : 0; + + Message memory message = Message({ + version: 1, + sourceDomain: sourceDomain, + destinationDomain: destinationDomain, + recipient: recipient, + messageHeaderRecipient: recipient, + sender: bytes32(uint256(uint160(msg.sender))), + destinationCaller: destinationCaller, + minFinalityThreshold: minFinalityThreshold, + isTokenTransfer: true, + tokenAmount: tokenAmount, + messageBody: messageBody + }); + + messages.push(message); + } + + function receiveMessage(bytes memory message, bytes memory attestation) + public + virtual + override + returns (bool) + { + Message memory storedMsg = encodedMessages[keccak256(message)]; + AbstractCCTPIntegrator recipient = AbstractCCTPIntegrator( + address(uint160(uint256(storedMsg.recipient))) + ); + + bytes32 sender = storedMsg.sender; + bytes memory messageBody = storedMsg.messageBody; + + // Credit USDC in this step as it is done in the live cctp contracts + if (storedMsg.isTokenTransfer) { + usdc.transfer(address(recipient), storedMsg.tokenAmount); + // override the sender with the one stored in the Burn message as the sender int he + // message header is the TokenMessenger. + sender = bytes32( + uint256( + uint160( + storedMsg.messageBody.extractAddress( + BURN_MESSAGE_V2_MESSAGE_SENDER_INDEX + ) + ) + ) + ); + messageBody = storedMsg.messageBody.extractSlice( + BURN_MESSAGE_V2_HOOK_DATA_INDEX, + storedMsg.messageBody.length + ); + } else { + bytes32 overrideSenderBytes = bytes32( + uint256(uint160(messageSender)) + ); + if (messageFinality >= 2000) { + recipient.handleReceiveFinalizedMessage( + sourceDomain == 4294967295 + ? storedMsg.sourceDomain + : sourceDomain, + messageSender == address(0) ? sender : overrideSenderBytes, + messageFinality, + messageBody + ); + } else { + recipient.handleReceiveUnfinalizedMessage( + sourceDomain == 4294967295 + ? storedMsg.sourceDomain + : sourceDomain, + messageSender == address(0) ? sender : overrideSenderBytes, + messageFinality, + messageBody + ); + } + } + + return true; + } + + function addMessage(Message memory storedMsg) external { + messages.push(storedMsg); + } + + function _encodeMessageHeader( + uint32 version, + uint32 sourceDomain, + bytes32 sender, + bytes32 recipient, + bytes memory messageBody + ) internal pure returns (bytes memory) { + bytes memory header = abi.encodePacked( + version, // 0-3 + sourceDomain, // 4-7 + bytes32(0), // 8-39 destinationDomain + bytes4(0), // 40-43 nonce + sender, // 44-75 sender + recipient, // 76-107 recipient + bytes32(0), // other stuff + bytes8(0) // other stuff + ); + return abi.encodePacked(header, messageBody); + } + + function _removeFront() internal returns (Message memory) { + require(messages.length > 0, "No messages"); + Message memory removed = messages[0]; + // Shift array + for (uint256 i = 0; i < messages.length - 1; i++) { + messages[i] = messages[i + 1]; + } + messages.pop(); + return removed; + } + + function _processMessage(Message memory storedMsg) internal { + bytes memory encodedMessage = _encodeMessageHeader( + storedMsg.version, + storedMsg.sourceDomain, + storedMsg.sender, + storedMsg.messageHeaderRecipient, + storedMsg.messageBody + ); + + encodedMessages[keccak256(encodedMessage)] = storedMsg; + + address recipient = address(uint160(uint256(storedMsg.recipient))); + + AbstractCCTPIntegrator(recipient).relay(encodedMessage, bytes("")); + } + + function _removeBack() internal returns (Message memory) { + require(messages.length > 0, "No messages"); + Message memory last = messages[messages.length - 1]; + messages.pop(); + return last; + } + + function messagesInQueue() external view returns (uint256) { + return messages.length; + } + + function processFrontOverrideHeader(bytes4 customHeader) external { + Message memory storedMsg = _removeFront(); + + bytes memory modifiedBody = abi.encodePacked( + customHeader, + storedMsg.messageBody.extractSlice(4, storedMsg.messageBody.length) + ); + + storedMsg.messageBody = modifiedBody; + + _processMessage(storedMsg); + } + + function processFrontOverrideVersion(uint32 customVersion) external { + Message memory storedMsg = _removeFront(); + storedMsg.version = customVersion; + _processMessage(storedMsg); + } + + function processFrontOverrideSender(address customSender) external { + Message memory storedMsg = _removeFront(); + storedMsg.sender = bytes32(uint256(uint160(customSender))); + _processMessage(storedMsg); + } + + function processFrontOverrideRecipient(address customRecipient) external { + Message memory storedMsg = _removeFront(); + storedMsg.messageHeaderRecipient = bytes32( + uint256(uint160(customRecipient)) + ); + _processMessage(storedMsg); + } + + function processFrontOverrideMessageBody(bytes memory customMessageBody) + external + { + Message memory storedMsg = _removeFront(); + storedMsg.messageBody = customMessageBody; + _processMessage(storedMsg); + } + + function processFront() external { + Message memory storedMsg = _removeFront(); + _processMessage(storedMsg); + } + + function processBack() external { + Message memory storedMsg = _removeBack(); + _processMessage(storedMsg); + } + + function getMessagesLength() external view returns (uint256) { + return messages.length; + } + + function overrideMessageFinality(uint16 _finality) external { + messageFinality = _finality; + } + + function overrideSender(address _sender) external { + messageSender = _sender; + } + + function overrideSourceDomain(uint32 _sourceDomain) external { + sourceDomain = _sourceDomain; + } +} diff --git a/contracts/contracts/mocks/crosschain/CCTPMessageTransmitterMock2.sol b/contracts/contracts/mocks/crosschain/CCTPMessageTransmitterMock2.sol new file mode 100644 index 0000000000..a44d5d3fbe --- /dev/null +++ b/contracts/contracts/mocks/crosschain/CCTPMessageTransmitterMock2.sol @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import { IMessageHandlerV2 } from "../../interfaces/cctp/ICCTP.sol"; +import { BytesHelper } from "../../utils/BytesHelper.sol"; +import { CCTPMessageTransmitterMock } from "./CCTPMessageTransmitterMock.sol"; + +uint8 constant SOURCE_DOMAIN_INDEX = 4; +uint8 constant RECIPIENT_INDEX = 76; +uint8 constant SENDER_INDEX = 44; +uint8 constant MESSAGE_BODY_INDEX = 148; + +/** + * @title Mock conctract simulating the functionality of the CCTPTokenMessenger contract + * for the porposes of unit testing. + * @author Origin Protocol Inc + */ + +contract CCTPMessageTransmitterMock2 is CCTPMessageTransmitterMock { + using BytesHelper for bytes; + + address public cctpTokenMessenger; + + event MessageReceivedInMockTransmitter(bytes message); + event MessageSent(bytes message); + + constructor(address _usdc) CCTPMessageTransmitterMock(_usdc) {} + + function setCCTPTokenMessenger(address _cctpTokenMessenger) external { + cctpTokenMessenger = _cctpTokenMessenger; + } + + function sendMessage( + uint32 destinationDomain, + bytes32 recipient, + bytes32 destinationCaller, + uint32 minFinalityThreshold, + bytes memory messageBody + ) external virtual override { + bytes memory message = abi.encodePacked( + uint32(1), // version + uint32(destinationDomain == 0 ? 6 : 0), // source domain + uint32(destinationDomain), // destination domain + uint256(0), + bytes32(uint256(uint160(msg.sender))), // sender + recipient, // recipient + destinationCaller, // destination caller + minFinalityThreshold, // min finality threshold + uint32(0), + messageBody // message body + ); + emit MessageSent(message); + } + + function receiveMessage(bytes memory message, bytes memory attestation) + public + virtual + override + returns (bool) + { + uint32 sourceDomain = message.extractUint32(SOURCE_DOMAIN_INDEX); + address recipient = message.extractAddress(RECIPIENT_INDEX); + address sender = message.extractAddress(SENDER_INDEX); + + bytes memory messageBody = message.extractSlice( + MESSAGE_BODY_INDEX, + message.length + ); + + bool isBurnMessage = recipient == cctpTokenMessenger; + + if (isBurnMessage) { + // recipient = messageBody.extractAddress(BURN_MESSAGE_V2_RECIPIENT_INDEX); + // This step won't mint USDC, transfer it to the recipient address + // in your tests + } else { + IMessageHandlerV2(recipient).handleReceiveFinalizedMessage( + sourceDomain, + bytes32(uint256(uint160(sender))), + 2000, + messageBody + ); + } + + // This step won't mint USDC, transfer it to the recipient address + // in your tests + emit MessageReceivedInMockTransmitter(message); + + return true; + } +} diff --git a/contracts/contracts/mocks/crosschain/CCTPTokenMessengerMock.sol b/contracts/contracts/mocks/crosschain/CCTPTokenMessengerMock.sol new file mode 100644 index 0000000000..e33cc9c0d1 --- /dev/null +++ b/contracts/contracts/mocks/crosschain/CCTPTokenMessengerMock.sol @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import { ICCTPTokenMessenger } from "../../interfaces/cctp/ICCTP.sol"; +import { CCTPMessageTransmitterMock } from "./CCTPMessageTransmitterMock.sol"; +import { IERC20 } from "../../utils/InitializableAbstractStrategy.sol"; + +/** + * @title Mock conctract simulating the functionality of the CCTPTokenMessenger contract + * for the porposes of unit testing. + * @author Origin Protocol Inc + */ + +contract CCTPTokenMessengerMock is ICCTPTokenMessenger { + IERC20 public usdc; + CCTPMessageTransmitterMock public cctpMessageTransmitterMock; + + constructor(address _usdc, address _cctpMessageTransmitterMock) { + usdc = IERC20(_usdc); + cctpMessageTransmitterMock = CCTPMessageTransmitterMock( + _cctpMessageTransmitterMock + ); + } + + function depositForBurn( + uint256 amount, + uint32 destinationDomain, + bytes32 mintRecipient, + address burnToken, + bytes32 destinationCaller, + uint256 maxFee, + uint32 minFinalityThreshold + ) external override { + revert("Not implemented"); + } + + /** + * @dev mocks the depositForBurnWithHook function by sending the USDC to the CCTPMessageTransmitterMock + * called by the AbstractCCTPIntegrator contract. + */ + function depositForBurnWithHook( + uint256 amount, + uint32 destinationDomain, + bytes32 mintRecipient, + address burnToken, + bytes32 destinationCaller, + uint256 maxFee, + uint32 minFinalityThreshold, + bytes memory hookData + ) external override { + require(burnToken == address(usdc), "Invalid burn token"); + + usdc.transferFrom(msg.sender, address(this), maxFee); + uint256 destinationAmount = amount - maxFee; + usdc.transferFrom( + msg.sender, + address(cctpMessageTransmitterMock), + destinationAmount + ); + + bytes memory burnMessage = _encodeBurnMessageV2( + mintRecipient, + amount, + msg.sender, + maxFee, + maxFee, + hookData + ); + + cctpMessageTransmitterMock.sendTokenTransferMessage( + destinationDomain, + mintRecipient, + destinationCaller, + minFinalityThreshold, + destinationAmount, + burnMessage + ); + } + + function _encodeBurnMessageV2( + bytes32 mintRecipient, + uint256 amount, + address messageSender, + uint256 maxFee, + uint256 feeExecuted, + bytes memory hookData + ) internal view returns (bytes memory) { + bytes32 burnTokenBytes32 = bytes32( + abi.encodePacked(bytes12(0), bytes20(uint160(address(usdc)))) + ); + bytes32 messageSenderBytes32 = bytes32( + abi.encodePacked(bytes12(0), bytes20(uint160(messageSender))) + ); + bytes32 expirationBlock = bytes32(0); + + // Ref: https://developers.circle.com/cctp/technical-guide#message-body + return + abi.encodePacked( + uint32(1), // 0-3: version + burnTokenBytes32, // 4-35: burnToken (bytes32 left-padded address) + mintRecipient, // 36-67: mintRecipient (bytes32 left-padded address) + amount, // 68-99: uint256 amount + messageSenderBytes32, // 100-131: messageSender (bytes32 left-padded address) + maxFee, // 132-163: uint256 maxFee + feeExecuted, // 164-195: uint256 feeExecuted + expirationBlock, // 196-227: bytes32 expirationBlock + hookData // 228+: dynamic hookData + ); + } + + function getMinFeeAmount(uint256 amount) + external + view + override + returns (uint256) + { + return 0; + } +} diff --git a/contracts/contracts/proxies/InitializeGovernedUpgradeabilityProxy2.sol b/contracts/contracts/proxies/InitializeGovernedUpgradeabilityProxy2.sol new file mode 100644 index 0000000000..250acbe782 --- /dev/null +++ b/contracts/contracts/proxies/InitializeGovernedUpgradeabilityProxy2.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import { InitializeGovernedUpgradeabilityProxy } from "./InitializeGovernedUpgradeabilityProxy.sol"; + +/** + * @title BaseGovernedUpgradeabilityProxy2 + * @dev This is the same as InitializeGovernedUpgradeabilityProxy except that the + * governor is defined in the constructor. + * @author Origin Protocol Inc + */ +contract InitializeGovernedUpgradeabilityProxy2 is + InitializeGovernedUpgradeabilityProxy +{ + /** + * This is used when the msg.sender can not be the governor. E.g. when the proxy is + * deployed via CreateX + */ + constructor(address governor) InitializeGovernedUpgradeabilityProxy() { + _setGovernor(governor); + } +} diff --git a/contracts/contracts/proxies/Proxies.sol b/contracts/contracts/proxies/Proxies.sol index a6055cb95a..03227c25a8 100644 --- a/contracts/contracts/proxies/Proxies.sol +++ b/contracts/contracts/proxies/Proxies.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.0; import { InitializeGovernedUpgradeabilityProxy } from "./InitializeGovernedUpgradeabilityProxy.sol"; +import { InitializeGovernedUpgradeabilityProxy2 } from "./InitializeGovernedUpgradeabilityProxy2.sol"; /** * @notice OUSDProxy delegates calls to an OUSD implementation diff --git a/contracts/contracts/proxies/create2/CrossChainStrategyProxy.sol b/contracts/contracts/proxies/create2/CrossChainStrategyProxy.sol new file mode 100644 index 0000000000..a5feec929b --- /dev/null +++ b/contracts/contracts/proxies/create2/CrossChainStrategyProxy.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import { InitializeGovernedUpgradeabilityProxy2 } from "../InitializeGovernedUpgradeabilityProxy2.sol"; + +// ******************************************************** +// ******************************************************** +// IMPORTANT: DO NOT CHANGE ANYTHING IN THIS FILE. +// Any changes to this file (even whitespaces) will +// affect the create2 address of the proxy +// ******************************************************** +// ******************************************************** + +/** + * @notice CrossChainStrategyProxy delegates calls to a + * CrossChainMasterStrategy or CrossChainRemoteStrategy + * implementation contract. + */ +contract CrossChainStrategyProxy is InitializeGovernedUpgradeabilityProxy2 { + constructor(address governor) + InitializeGovernedUpgradeabilityProxy2(governor) + {} +} diff --git a/contracts/contracts/strategies/Generalized4626Strategy.sol b/contracts/contracts/strategies/Generalized4626Strategy.sol index 10882e3af9..b4793db7de 100644 --- a/contracts/contracts/strategies/Generalized4626Strategy.sol +++ b/contracts/contracts/strategies/Generalized4626Strategy.sol @@ -67,6 +67,7 @@ contract Generalized4626Strategy is InitializableAbstractStrategy { */ function deposit(address _asset, uint256 _amount) external + virtual override onlyVault nonReentrant @@ -109,6 +110,14 @@ contract Generalized4626Strategy is InitializableAbstractStrategy { address _asset, uint256 _amount ) external virtual override onlyVault nonReentrant { + _withdraw(_recipient, _asset, _amount); + } + + function _withdraw( + address _recipient, + address _asset, + uint256 _amount + ) internal virtual { require(_amount > 0, "Must withdraw something"); require(_recipient != address(0), "Must specify recipient"); require(_asset == address(assetToken), "Unexpected asset address"); diff --git a/contracts/contracts/strategies/crosschain/AbstractCCTPIntegrator.sol b/contracts/contracts/strategies/crosschain/AbstractCCTPIntegrator.sol new file mode 100644 index 0000000000..a3af1ffa90 --- /dev/null +++ b/contracts/contracts/strategies/crosschain/AbstractCCTPIntegrator.sol @@ -0,0 +1,642 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +/** + * @title AbstractCCTPIntegrator + * @author Origin Protocol Inc + * + * @dev Abstract contract that contains all the logic used to integrate with CCTP. + */ + +import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import { IERC20 } from "../../utils/InitializableAbstractStrategy.sol"; + +import { ICCTPTokenMessenger, ICCTPMessageTransmitter, IMessageHandlerV2 } from "../../interfaces/cctp/ICCTP.sol"; + +import { CrossChainStrategyHelper } from "./CrossChainStrategyHelper.sol"; +import { Governable } from "../../governance/Governable.sol"; +import { BytesHelper } from "../../utils/BytesHelper.sol"; +import "../../utils/Helpers.sol"; + +abstract contract AbstractCCTPIntegrator is Governable, IMessageHandlerV2 { + using SafeERC20 for IERC20; + + using BytesHelper for bytes; + using CrossChainStrategyHelper for bytes; + + event LastTransferNonceUpdated(uint64 lastTransferNonce); + event NonceProcessed(uint64 nonce); + + event CCTPMinFinalityThresholdSet(uint16 minFinalityThreshold); + event CCTPFeePremiumBpsSet(uint16 feePremiumBps); + event OperatorChanged(address operator); + event TokensBridged( + uint32 destinationDomain, + address peerStrategy, + address tokenAddress, + uint256 tokenAmount, + uint256 maxFee, + uint32 minFinalityThreshold, + bytes hookData + ); + event MessageTransmitted( + uint32 destinationDomain, + address peerStrategy, + uint32 minFinalityThreshold, + bytes message + ); + + // Message body V2 fields + // Ref: https://developers.circle.com/cctp/technical-guide#message-body + // Ref: https://github.com/circlefin/evm-cctp-contracts/blob/master/src/messages/v2/BurnMessageV2.sol + uint8 private constant BURN_MESSAGE_V2_VERSION_INDEX = 0; + uint8 private constant BURN_MESSAGE_V2_BURN_TOKEN_INDEX = 4; + uint8 private constant BURN_MESSAGE_V2_RECIPIENT_INDEX = 36; + uint8 private constant BURN_MESSAGE_V2_AMOUNT_INDEX = 68; + uint8 private constant BURN_MESSAGE_V2_MESSAGE_SENDER_INDEX = 100; + uint8 private constant BURN_MESSAGE_V2_FEE_EXECUTED_INDEX = 164; + uint8 private constant BURN_MESSAGE_V2_HOOK_DATA_INDEX = 228; + + /** + * @notice Max transfer threshold imposed by the CCTP + * Ref: https://developers.circle.com/cctp/evm-smart-contracts#depositforburn + * @dev 10M USDC limit applies to both standard and fast transfer modes. The fast transfer mode has + * an additional limitation that is not present on-chain and Circle may alter that amount off-chain + * at their preference. The amount available for fast transfer can be queried here: + * https://iris-api.circle.com/v2/fastBurn/USDC/allowance . + * If a fast transfer token transaction has been issued and there is not enough allowance for it + * the off-chain Iris component will re-attempt the transaction and if it fails it will fallback + * to a standard transfer. Reference section 4.3 in the whitepaper: + * https://6778953.fs1.hubspotusercontent-na1.net/hubfs/6778953/PDFs/Whitepapers/CCTPV2_White_Paper.pdf + */ + uint256 public constant MAX_TRANSFER_AMOUNT = 10_000_000 * 10**6; // 10M USDC + + /// @notice Minimum transfer amount to avoid zero or dust transfers + uint256 public constant MIN_TRANSFER_AMOUNT = 10**6; + + // CCTP contracts + // This implementation assumes that remote and local chains have these contracts + // deployed on the same addresses. + /// @notice CCTP message transmitter contract + ICCTPMessageTransmitter public immutable cctpMessageTransmitter; + /// @notice CCTP token messenger contract + ICCTPTokenMessenger public immutable cctpTokenMessenger; + + /// @notice USDC address on local chain + address public immutable usdcToken; + + /// @notice USDC address on remote chain + address public immutable peerUsdcToken; + + /// @notice Domain ID of the chain from which messages are accepted + uint32 public immutable peerDomainID; + + /// @notice Strategy address on other chain + address public immutable peerStrategy; + + /** + * @notice Minimum finality threshold + * Can be 1000 (safe, after 1 epoch) or 2000 (finalized, after 2 epochs). + * Ref: https://developers.circle.com/cctp/technical-guide#finality-thresholds + * @dev When configuring the contract for fast transfer we should check the available + * allowance of USDC that can be bridged using fast mode: + * wget https://iris-api.circle.com/v2/fastBurn/USDC/allowance + */ + uint16 public minFinalityThreshold; + + /// @notice Fee premium in basis points + uint16 public feePremiumBps; + + /// @notice Nonce of the last known deposit or withdrawal + uint64 public lastTransferNonce; + + /// @notice Operator address: Can relay CCTP messages + address public operator; + + /// @notice Mapping of processed nonces + mapping(uint64 => bool) private nonceProcessed; + + // For future use + uint256[48] private __gap; + + modifier onlyCCTPMessageTransmitter() { + require( + msg.sender == address(cctpMessageTransmitter), + "Caller is not CCTP transmitter" + ); + _; + } + + modifier onlyOperator() { + require(msg.sender == operator, "Caller is not the Operator"); + _; + } + + /** + * @notice Configuration for CCTP integration + * @param cctpTokenMessenger Address of the CCTP token messenger contract + * @param cctpMessageTransmitter Address of the CCTP message transmitter contract + * @param peerDomainID Domain ID of the chain from which messages are accepted. + * 0 for Ethereum, 6 for Base, etc. + * Ref: https://developers.circle.com/cctp/cctp-supported-blockchains + * @param peerStrategy Address of the master or remote strategy on the other chain + * @param usdcToken USDC address on local chain + */ + struct CCTPIntegrationConfig { + address cctpTokenMessenger; + address cctpMessageTransmitter; + uint32 peerDomainID; + address peerStrategy; + address usdcToken; + address peerUsdcToken; + } + + constructor(CCTPIntegrationConfig memory _config) { + require(_config.usdcToken != address(0), "Invalid USDC address"); + require( + _config.peerUsdcToken != address(0), + "Invalid peer USDC address" + ); + require( + _config.cctpTokenMessenger != address(0), + "Invalid CCTP config" + ); + require( + _config.cctpMessageTransmitter != address(0), + "Invalid CCTP config" + ); + require( + _config.peerStrategy != address(0), + "Invalid peer strategy address" + ); + + cctpMessageTransmitter = ICCTPMessageTransmitter( + _config.cctpMessageTransmitter + ); + cctpTokenMessenger = ICCTPTokenMessenger(_config.cctpTokenMessenger); + + // Domain ID of the chain from which messages are accepted + peerDomainID = _config.peerDomainID; + + // Strategy address on other chain, should + // always be same as the proxy of this strategy + peerStrategy = _config.peerStrategy; + + // USDC address on local chain + usdcToken = _config.usdcToken; + + // Just a sanity check to ensure the base token is USDC + uint256 _usdcTokenDecimals = Helpers.getDecimals(_config.usdcToken); + string memory _usdcTokenSymbol = Helpers.getSymbol(_config.usdcToken); + require(_usdcTokenDecimals == 6, "Base token decimals must be 6"); + require( + keccak256(abi.encodePacked(_usdcTokenSymbol)) == + keccak256(abi.encodePacked("USDC")), + "Token symbol must be USDC" + ); + + // USDC address on remote chain + peerUsdcToken = _config.peerUsdcToken; + } + + /** + * @dev Initialize the implementation contract + * @param _operator Operator address + * @param _minFinalityThreshold Minimum finality threshold + * @param _feePremiumBps Fee premium in basis points + */ + function _initialize( + address _operator, + uint16 _minFinalityThreshold, + uint16 _feePremiumBps + ) internal { + _setOperator(_operator); + _setMinFinalityThreshold(_minFinalityThreshold); + _setFeePremiumBps(_feePremiumBps); + + // Nonce starts at 1, so assume nonce 0 as processed. + // NOTE: This will cause the deposit/withdraw to fail if the + // strategy is not initialized properly (which is expected). + nonceProcessed[0] = true; + } + + /*************************************** + Settings + ****************************************/ + /** + * @dev Set the operator address + * @param _operator Operator address + */ + function setOperator(address _operator) external onlyGovernor { + _setOperator(_operator); + } + + /** + * @dev Set the operator address + * @param _operator Operator address + */ + function _setOperator(address _operator) internal { + operator = _operator; + emit OperatorChanged(_operator); + } + + /** + * @dev Set the minimum finality threshold at which + * the message is considered to be finalized to relay. + * Only accepts a value of 1000 (Safe, after 1 epoch) or + * 2000 (Finalized, after 2 epochs). + * @param _minFinalityThreshold Minimum finality threshold + */ + function setMinFinalityThreshold(uint16 _minFinalityThreshold) + external + onlyGovernor + { + _setMinFinalityThreshold(_minFinalityThreshold); + } + + /** + * @dev Set the minimum finality threshold + * @param _minFinalityThreshold Minimum finality threshold + */ + function _setMinFinalityThreshold(uint16 _minFinalityThreshold) internal { + // 1000 for fast transfer and 2000 for standard transfer + require( + _minFinalityThreshold == 1000 || _minFinalityThreshold == 2000, + "Invalid threshold" + ); + + minFinalityThreshold = _minFinalityThreshold; + emit CCTPMinFinalityThresholdSet(_minFinalityThreshold); + } + + /** + * @dev Set the fee premium in basis points. + * Cannot be higher than 30% (3000 basis points). + * @param _feePremiumBps Fee premium in basis points + */ + function setFeePremiumBps(uint16 _feePremiumBps) external onlyGovernor { + _setFeePremiumBps(_feePremiumBps); + } + + /** + * @dev Set the fee premium in basis points + * Cannot be higher than 30% (3000 basis points). + * Ref: https://developers.circle.com/cctp/technical-guide#fees + * @param _feePremiumBps Fee premium in basis points + */ + function _setFeePremiumBps(uint16 _feePremiumBps) internal { + require(_feePremiumBps <= 3000, "Fee premium too high"); // 30% + + feePremiumBps = _feePremiumBps; + emit CCTPFeePremiumBpsSet(_feePremiumBps); + } + + /*************************************** + CCTP message handling + ****************************************/ + + /** + * @dev Handles a finalized CCTP message + * @param sourceDomain Source domain of the message + * @param sender Sender of the message + * @param finalityThresholdExecuted Fidelity threshold executed + * @param messageBody Message body + */ + function handleReceiveFinalizedMessage( + uint32 sourceDomain, + bytes32 sender, + uint32 finalityThresholdExecuted, + bytes memory messageBody + ) external override onlyCCTPMessageTransmitter returns (bool) { + // Make sure the finality threshold at execution is at least 2000 + require( + finalityThresholdExecuted >= 2000, + "Finality threshold too low" + ); + + return _handleReceivedMessage(sourceDomain, sender, messageBody); + } + + /** + * @dev Handles an unfinalized but safe CCTP message + * @param sourceDomain Source domain of the message + * @param sender Sender of the message + * @param finalityThresholdExecuted Fidelity threshold executed + * @param messageBody Message body + */ + function handleReceiveUnfinalizedMessage( + uint32 sourceDomain, + bytes32 sender, + uint32 finalityThresholdExecuted, + bytes memory messageBody + ) external override onlyCCTPMessageTransmitter returns (bool) { + // Make sure the contract is configured to handle unfinalized messages + require( + minFinalityThreshold == 1000, + "Unfinalized messages are not supported" + ); + // Make sure the finality threshold at execution is at least 1000 + require( + finalityThresholdExecuted >= 1000, + "Finality threshold too low" + ); + + return _handleReceivedMessage(sourceDomain, sender, messageBody); + } + + /** + * @dev Handles a CCTP message + * @param sourceDomain Source domain of the message + * @param sender Sender of the message + * @param messageBody Message body + */ + function _handleReceivedMessage( + uint32 sourceDomain, + bytes32 sender, + bytes memory messageBody + ) internal returns (bool) { + require(sourceDomain == peerDomainID, "Unknown Source Domain"); + + // Extract address from bytes32 (CCTP stores addresses as right-padded bytes32) + address senderAddress = address(uint160(uint256(sender))); + require(senderAddress == peerStrategy, "Unknown Sender"); + + _onMessageReceived(messageBody); + + return true; + } + + /** + * @dev Sends tokens to the peer strategy using CCTP Token Messenger + * @param tokenAmount Amount of tokens to send + * @param hookData Hook data + */ + function _sendTokens(uint256 tokenAmount, bytes memory hookData) + internal + virtual + { + // CCTP has a maximum transfer amount of 10M USDC per tx + require(tokenAmount <= MAX_TRANSFER_AMOUNT, "Token amount too high"); + + // Approve only what needs to be transferred + IERC20(usdcToken).safeApprove(address(cctpTokenMessenger), tokenAmount); + + // Compute the max fee to be paid. + // Ref: https://developers.circle.com/cctp/evm-smart-contracts#getminfeeamount + // The right way to compute fees would be to use CCTP's getMinFeeAmount function. + // The issue is that the getMinFeeAmount is not present on v2.0 contracts, but is on + // v2.1. Some of CCTP's deployed contracts are v2.0, some are v2.1. + // We will only be using standard transfers and fee on those is 0 for now. If they + // ever start implementing fee for standard transfers or if we decide to use fast + // trasnfer, we can use feePremiumBps as a workaround. + uint256 maxFee = feePremiumBps > 0 + ? (tokenAmount * feePremiumBps) / 10000 + : 0; + + // Send tokens to the peer strategy using CCTP Token Messenger + cctpTokenMessenger.depositForBurnWithHook( + tokenAmount, + peerDomainID, + bytes32(uint256(uint160(peerStrategy))), + address(usdcToken), + bytes32(uint256(uint160(peerStrategy))), + maxFee, + uint32(minFinalityThreshold), + hookData + ); + + emit TokensBridged( + peerDomainID, + peerStrategy, + usdcToken, + tokenAmount, + maxFee, + uint32(minFinalityThreshold), + hookData + ); + } + + /** + * @dev Sends a message to the peer strategy using CCTP Message Transmitter + * @param message Payload of the message to send + */ + function _sendMessage(bytes memory message) internal virtual { + cctpMessageTransmitter.sendMessage( + peerDomainID, + bytes32(uint256(uint160(peerStrategy))), + bytes32(uint256(uint160(peerStrategy))), + uint32(minFinalityThreshold), + message + ); + + emit MessageTransmitted( + peerDomainID, + peerStrategy, + uint32(minFinalityThreshold), + message + ); + } + + /** + * @dev Receives a message from the peer strategy on the other chain, + * does some basic checks and relays it to the local MessageTransmitterV2. + * If the message is a burn message, it will also handle the hook data + * and call the _onTokenReceived function. + * @param message Payload of the message to send + * @param attestation Attestation of the message + */ + function relay(bytes memory message, bytes memory attestation) + external + onlyOperator + { + ( + uint32 version, + uint32 sourceDomainID, + address sender, + address recipient, + bytes memory messageBody + ) = message.decodeMessageHeader(); + + // Ensure that it's a CCTP message + require( + version == CrossChainStrategyHelper.CCTP_MESSAGE_VERSION, + "Invalid CCTP message version" + ); + + // Ensure that the source domain is the peer domain + require(sourceDomainID == peerDomainID, "Unknown Source Domain"); + + // Ensure message body version + version = messageBody.extractUint32(BURN_MESSAGE_V2_VERSION_INDEX); + + // NOTE: There's a possibility that the CCTP Token Messenger might + // send other types of messages in future, not just the burn message. + // If it ever comes to that, this shouldn't cause us any problems + // because it has to still go through the followign checks: + // - version check + // - message body length check + // - sender and recipient (which should be in the same slots and same as address(this)) + // - hook data handling (which will revert even if all the above checks pass) + bool isBurnMessageV1 = sender == address(cctpTokenMessenger); + + if (isBurnMessageV1) { + // Handle burn message + require( + version == 1 && + messageBody.length >= BURN_MESSAGE_V2_HOOK_DATA_INDEX, + "Invalid burn message" + ); + + // Ensure the burn token is USDC + address burnToken = messageBody.extractAddress( + BURN_MESSAGE_V2_BURN_TOKEN_INDEX + ); + require(burnToken == peerUsdcToken, "Invalid burn token"); + + // Address of caller of depositForBurn (or depositForBurnWithCaller) on source domain + sender = messageBody.extractAddress( + BURN_MESSAGE_V2_MESSAGE_SENDER_INDEX + ); + + recipient = messageBody.extractAddress( + BURN_MESSAGE_V2_RECIPIENT_INDEX + ); + } else { + // We handle only Burn message or our custom messagee + require( + version == CrossChainStrategyHelper.ORIGIN_MESSAGE_VERSION, + "Unsupported message version" + ); + } + + // Ensure the recipient is this contract + // Both sender and recipient should be deployed to same address on both chains. + require(address(this) == recipient, "Unexpected recipient address"); + require(sender == peerStrategy, "Incorrect sender/recipient address"); + + // Relay the message + // This step also mints USDC and transfers it to the recipient wallet + bool relaySuccess = cctpMessageTransmitter.receiveMessage( + message, + attestation + ); + require(relaySuccess, "Receive message failed"); + + if (isBurnMessageV1) { + // Extract the hook data from the message body + bytes memory hookData = messageBody.extractSlice( + BURN_MESSAGE_V2_HOOK_DATA_INDEX, + messageBody.length + ); + + // Extract the token amount from the message body + uint256 tokenAmount = messageBody.extractUint256( + BURN_MESSAGE_V2_AMOUNT_INDEX + ); + + // Extract the fee executed from the message body + uint256 feeExecuted = messageBody.extractUint256( + BURN_MESSAGE_V2_FEE_EXECUTED_INDEX + ); + + // Call the _onTokenReceived function + _onTokenReceived(tokenAmount - feeExecuted, feeExecuted, hookData); + } + } + + /*************************************** + Message utils + ****************************************/ + + /*************************************** + Nonce Handling + ****************************************/ + /** + * @dev Checks if the last known transfer is pending. + * Nonce starts at 1, so 0 is disregarded. + * @return True if a transfer is pending, false otherwise + */ + function isTransferPending() public view returns (bool) { + return !nonceProcessed[lastTransferNonce]; + } + + /** + * @dev Checks if a given nonce is processed. + * Nonce starts at 1, so 0 is disregarded. + * @param nonce Nonce to check + * @return True if the nonce is processed, false otherwise + */ + function isNonceProcessed(uint64 nonce) public view returns (bool) { + return nonceProcessed[nonce]; + } + + /** + * @dev Marks a given nonce as processed. + * Can only mark nonce as processed once. New nonce should + * always be greater than the last known nonce. Also updates + * the last known nonce. + * @param nonce Nonce to mark as processed + */ + function _markNonceAsProcessed(uint64 nonce) internal { + uint64 lastNonce = lastTransferNonce; + + // Can only mark latest nonce as processed + // Master strategy when receiving a message from the remote strategy + // will have lastNone == nonce, as the nonce is increase at the start + // of deposit / withdrawal flow. + // Remote strategy will have lastNonce < nonce, as a new nonce initiated + // from master will be greater than the last one. + require(nonce >= lastNonce, "Nonce too low"); + // Can only mark nonce as processed once + require(!nonceProcessed[nonce], "Nonce already processed"); + + nonceProcessed[nonce] = true; + emit NonceProcessed(nonce); + + if (nonce != lastNonce) { + // Update last known nonce + lastTransferNonce = nonce; + emit LastTransferNonceUpdated(nonce); + } + } + + /** + * @dev Gets the next nonce to use. + * Nonce starts at 1, so 0 is disregarded. + * Reverts if last nonce hasn't been processed yet. + * @return Next nonce + */ + function _getNextNonce() internal returns (uint64) { + uint64 nonce = lastTransferNonce; + + require(nonceProcessed[nonce], "Pending token transfer"); + + nonce = nonce + 1; + lastTransferNonce = nonce; + emit LastTransferNonceUpdated(nonce); + + return nonce; + } + + /*************************************** + Inheritence overrides + ****************************************/ + + /** + * @dev Called when the USDC is received from the CCTP + * @param tokenAmount The actual amount of USDC received (amount sent - fee executed) + * @param feeExecuted The fee executed + * @param payload The payload of the message (hook data) + */ + function _onTokenReceived( + uint256 tokenAmount, + uint256 feeExecuted, + bytes memory payload + ) internal virtual; + + /** + * @dev Called when the message is received + * @param payload The payload of the message + */ + function _onMessageReceived(bytes memory payload) internal virtual; +} diff --git a/contracts/contracts/strategies/crosschain/CrossChainMasterStrategy.sol b/contracts/contracts/strategies/crosschain/CrossChainMasterStrategy.sol new file mode 100644 index 0000000000..f886bd58ad --- /dev/null +++ b/contracts/contracts/strategies/crosschain/CrossChainMasterStrategy.sol @@ -0,0 +1,369 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +/** + * @title OUSD Yearn V3 Master Strategy - the Mainnet part + * @author Origin Protocol Inc + * + * @dev This strategy can only perform 1 deposit or withdrawal at a time. For that + * reason it shouldn't be configured as an asset default strategy. + */ + +import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import { IERC20, InitializableAbstractStrategy } from "../../utils/InitializableAbstractStrategy.sol"; +import { AbstractCCTPIntegrator } from "./AbstractCCTPIntegrator.sol"; +import { CrossChainStrategyHelper } from "./CrossChainStrategyHelper.sol"; + +contract CrossChainMasterStrategy is + AbstractCCTPIntegrator, + InitializableAbstractStrategy +{ + using SafeERC20 for IERC20; + using CrossChainStrategyHelper for bytes; + + /** + * @notice Remote strategy balance + * @dev The remote balance is cached and might not reflect the actual + * real-time balance of the remote strategy. + */ + uint256 public remoteStrategyBalance; + + /// @notice Amount that's bridged due to a pending Deposit process + /// but with no acknowledgement from the remote strategy yet + uint256 public pendingAmount; + + uint256 internal constant MAX_BALANCE_CHECK_AGE = 1 days; + + event RemoteStrategyBalanceUpdated(uint256 balance); + event WithdrawRequested(address indexed asset, uint256 amount); + event WithdrawAllSkipped(); + event BalanceCheckIgnored(uint64 nonce, uint256 timestamp, bool isTooOld); + + /** + * @param _stratConfig The platform and OToken vault addresses + */ + constructor( + BaseStrategyConfig memory _stratConfig, + CCTPIntegrationConfig memory _cctpConfig + ) + InitializableAbstractStrategy(_stratConfig) + AbstractCCTPIntegrator(_cctpConfig) + { + require( + _stratConfig.platformAddress == address(0), + "Invalid platform address" + ); + require( + _stratConfig.vaultAddress != address(0), + "Invalid Vault address" + ); + } + + /** + * @dev Initialize the strategy implementation + * @param _operator Address of the operator + * @param _minFinalityThreshold Minimum finality threshold + * @param _feePremiumBps Fee premium in basis points + */ + function initialize( + address _operator, + uint16 _minFinalityThreshold, + uint16 _feePremiumBps + ) external virtual onlyGovernor initializer { + _initialize(_operator, _minFinalityThreshold, _feePremiumBps); + + address[] memory rewardTokens = new address[](0); + address[] memory assets = new address[](0); + address[] memory pTokens = new address[](0); + + InitializableAbstractStrategy._initialize( + rewardTokens, + assets, + pTokens + ); + } + + /// @inheritdoc InitializableAbstractStrategy + function deposit(address _asset, uint256 _amount) + external + override + onlyVault + nonReentrant + { + _deposit(_asset, _amount); + } + + /// @inheritdoc InitializableAbstractStrategy + function depositAll() external override onlyVault nonReentrant { + uint256 balance = IERC20(usdcToken).balanceOf(address(this)); + // Deposit if balance is greater than 1 USDC + if (balance >= MIN_TRANSFER_AMOUNT) { + _deposit(usdcToken, balance); + } + } + + /// @inheritdoc InitializableAbstractStrategy + function withdraw( + address _recipient, + address _asset, + uint256 _amount + ) external override onlyVault nonReentrant { + require(_recipient == vaultAddress, "Only Vault can withdraw"); + _withdraw(_asset, _amount); + } + + /// @inheritdoc InitializableAbstractStrategy + function withdrawAll() external override onlyVaultOrGovernor nonReentrant { + if (isTransferPending()) { + // Do nothing if there is a pending transfer + // Note: We never want withdrawAll to fail, so + // emit an event to indicate that the withdrawal was skipped + emit WithdrawAllSkipped(); + return; + } + + // Withdraw everything in Remote strategy + uint256 _remoteBalance = remoteStrategyBalance; + if (_remoteBalance < MIN_TRANSFER_AMOUNT) { + // Do nothing if there is less than 1 USDC in the Remote strategy + return; + } + + _withdraw( + usdcToken, + _remoteBalance > MAX_TRANSFER_AMOUNT + ? MAX_TRANSFER_AMOUNT + : _remoteBalance + ); + } + + /** + * @notice Check the balance of the strategy that includes + * the balance of the asset on this contract, + * the amount of the asset being bridged, + * and the balance reported by the Remote strategy. + * @param _asset Address of the asset to check + * @return balance Total balance of the asset + */ + function checkBalance(address _asset) + public + view + override + returns (uint256 balance) + { + require(_asset == usdcToken, "Unsupported asset"); + + // USDC balance on this contract + // + USDC being bridged + // + USDC cached in the corresponding Remote part of this contract + return + IERC20(usdcToken).balanceOf(address(this)) + + pendingAmount + + remoteStrategyBalance; + } + + /// @inheritdoc InitializableAbstractStrategy + function supportsAsset(address _asset) public view override returns (bool) { + return _asset == usdcToken; + } + + /// @inheritdoc InitializableAbstractStrategy + function safeApproveAllTokens() + external + override + onlyGovernor + nonReentrant + {} + + /// @inheritdoc InitializableAbstractStrategy + function _abstractSetPToken(address, address) internal override {} + + /// @inheritdoc InitializableAbstractStrategy + function collectRewardTokens() + external + override + onlyHarvester + nonReentrant + {} + + /// @inheritdoc AbstractCCTPIntegrator + function _onMessageReceived(bytes memory payload) internal override { + if ( + payload.getMessageType() == + CrossChainStrategyHelper.BALANCE_CHECK_MESSAGE + ) { + // Received when Remote strategy checks the balance + _processBalanceCheckMessage(payload); + return; + } + + revert("Unknown message type"); + } + + /// @inheritdoc AbstractCCTPIntegrator + function _onTokenReceived( + uint256 tokenAmount, + // solhint-disable-next-line no-unused-vars + uint256 feeExecuted, + bytes memory payload + ) internal override { + uint64 _nonce = lastTransferNonce; + + // Should be expecting an acknowledgement + require(!isNonceProcessed(_nonce), "Nonce already processed"); + + // Now relay to the regular flow + // NOTE: Calling _onMessageReceived would mean that we are bypassing a + // few checks that the regular flow does (like sourceDomainID check + // and sender check in `handleReceiveFinalizedMessage`). However, + // CCTPMessageRelayer relays the message first (which will go through + // all the checks) and not update balance and then finally calls this + // `_onTokenReceived` which will update the balance. + // So, if any of the checks fail during the first no-balance-update flow, + // this won't happen either, since the tx would revert. + _onMessageReceived(payload); + + // Send any tokens in the contract to the Vault + uint256 usdcBalance = IERC20(usdcToken).balanceOf(address(this)); + // Should always have enough tokens + require(usdcBalance >= tokenAmount, "Insufficient balance"); + // Transfer all tokens to the Vault to not leave any dust + IERC20(usdcToken).safeTransfer(vaultAddress, usdcBalance); + + // Emit withdrawal amount + emit Withdrawal(usdcToken, usdcToken, usdcBalance); + } + + /** + * @dev Bridge and deposit asset into the remote strategy + * @param _asset Address of the asset to deposit + * @param depositAmount Amount of the asset to deposit + */ + function _deposit(address _asset, uint256 depositAmount) internal virtual { + require(_asset == usdcToken, "Unsupported asset"); + require(pendingAmount == 0, "Unexpected pending amount"); + // Deposit at least 1 USDC + require( + depositAmount >= MIN_TRANSFER_AMOUNT, + "Deposit amount too small" + ); + require( + depositAmount <= MAX_TRANSFER_AMOUNT, + "Deposit amount too high" + ); + + // Get the next nonce + // Note: reverts if a transfer is pending + uint64 nonce = _getNextNonce(); + + // Set pending amount + pendingAmount = depositAmount; + + // Build deposit message payload + bytes memory message = CrossChainStrategyHelper.encodeDepositMessage( + nonce, + depositAmount + ); + + // Send deposit message to the remote strategy + _sendTokens(depositAmount, message); + + // Emit deposit event + emit Deposit(_asset, _asset, depositAmount); + } + + /** + * @dev Send a withdraw request to the remote strategy + * @param _asset Address of the asset to withdraw + * @param _amount Amount of the asset to withdraw + */ + function _withdraw(address _asset, uint256 _amount) internal virtual { + require(_asset == usdcToken, "Unsupported asset"); + // Withdraw at least 1 USDC + require(_amount >= MIN_TRANSFER_AMOUNT, "Withdraw amount too small"); + require( + _amount <= remoteStrategyBalance, + "Withdraw amount exceeds remote strategy balance" + ); + require( + _amount <= MAX_TRANSFER_AMOUNT, + "Withdraw amount exceeds max transfer amount" + ); + + // Get the next nonce + // Note: reverts if a transfer is pending + uint64 nonce = _getNextNonce(); + + // Build and send withdrawal message with payload + bytes memory message = CrossChainStrategyHelper.encodeWithdrawMessage( + nonce, + _amount + ); + _sendMessage(message); + + // Emit WithdrawRequested event here, + // Withdraw will be emitted in _onTokenReceived + emit WithdrawRequested(usdcToken, _amount); + } + + /** + * @dev Process balance check: + * - Confirms a deposit to the remote strategy + * - Skips balance update if there's a pending withdrawal + * - Updates the remote strategy balance + * @param message The message containing the nonce and balance + */ + function _processBalanceCheckMessage(bytes memory message) + internal + virtual + { + // Decode the message + // When transferConfirmation is true, it means that the message is a result of a deposit or a withdrawal + // process. + ( + uint64 nonce, + uint256 balance, + bool transferConfirmation, + uint256 timestamp + ) = message.decodeBalanceCheckMessage(); + // Get the last cached nonce + uint64 _lastCachedNonce = lastTransferNonce; + + if (nonce != _lastCachedNonce) { + // If nonce is not the last cached nonce, it is an outdated message + // Ignore it + return; + } + + // A received message nonce not yet processed indicates there is a + // deposit or withdrawal in progress. + bool transferInProgress = isTransferPending(); + + if (transferInProgress) { + if (transferConfirmation) { + // Apply the effects of the deposit / withdrawal completion + _markNonceAsProcessed(nonce); + pendingAmount = 0; + } else { + // A balanceCheck arrived that is not part of the deposit / withdrawal process + // that has been generated on the Remote contract after the deposit / withdrawal which is + // still pending. This can happen when the CCTP bridge delivers the messages out of order. + // Ignore it, since the pending deposit / withdrawal must first be cofirmed. + emit BalanceCheckIgnored(nonce, timestamp, false); + return; + } + } else { + if (block.timestamp > timestamp + MAX_BALANCE_CHECK_AGE) { + // Balance check is too old, ignore it + emit BalanceCheckIgnored(nonce, timestamp, true); + return; + } + } + + // At this point update the strategy balance the balanceCheck message is either: + // - a confirmation of a deposit / withdrawal + // - a message that updates balances when no deposit / withdrawal is in progress + remoteStrategyBalance = balance; + emit RemoteStrategyBalanceUpdated(balance); + } +} diff --git a/contracts/contracts/strategies/crosschain/CrossChainRemoteStrategy.sol b/contracts/contracts/strategies/crosschain/CrossChainRemoteStrategy.sol new file mode 100644 index 0000000000..6902c66ade --- /dev/null +++ b/contracts/contracts/strategies/crosschain/CrossChainRemoteStrategy.sol @@ -0,0 +1,407 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +/** + * @title CrossChainRemoteStrategy + * @author Origin Protocol Inc + * + * @dev Part of the cross-chain strategy that lives on the remote chain. + * Handles deposits and withdrawals from the master strategy on peer chain + * and locally deposits the funds to a 4626 compatible vault. + */ + +import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import { IERC20 } from "../../utils/InitializableAbstractStrategy.sol"; +import { IERC4626 } from "../../../lib/openzeppelin/interfaces/IERC4626.sol"; +import { Generalized4626Strategy } from "../Generalized4626Strategy.sol"; +import { AbstractCCTPIntegrator } from "./AbstractCCTPIntegrator.sol"; +import { CrossChainStrategyHelper } from "./CrossChainStrategyHelper.sol"; +import { InitializableAbstractStrategy } from "../../utils/InitializableAbstractStrategy.sol"; +import { Strategizable } from "../../governance/Strategizable.sol"; + +contract CrossChainRemoteStrategy is + AbstractCCTPIntegrator, + Generalized4626Strategy, + Strategizable +{ + using SafeERC20 for IERC20; + using CrossChainStrategyHelper for bytes; + + event DepositUnderlyingFailed(string reason); + event WithdrawalFailed(uint256 amountRequested, uint256 amountAvailable); + event WithdrawUnderlyingFailed(string reason); + + modifier onlyOperatorOrStrategistOrGovernor() { + require( + msg.sender == operator || + msg.sender == strategistAddr || + isGovernor(), + "Caller is not the Operator, Strategist or the Governor" + ); + _; + } + + modifier onlyGovernorOrStrategist() + override(InitializableAbstractStrategy, Strategizable) { + require( + msg.sender == strategistAddr || isGovernor(), + "Caller is not the Strategist or Governor" + ); + _; + } + + constructor( + BaseStrategyConfig memory _baseConfig, + CCTPIntegrationConfig memory _cctpConfig + ) + AbstractCCTPIntegrator(_cctpConfig) + Generalized4626Strategy(_baseConfig, _cctpConfig.usdcToken) + { + require(usdcToken == address(assetToken), "Token mismatch"); + require( + _baseConfig.platformAddress != address(0), + "Invalid platform address" + ); + // Vault address must always be address(0) for the remote strategy + require( + _baseConfig.vaultAddress == address(0), + "Invalid vault address" + ); + } + + /** + * @dev Initialize the strategy implementation + * @param _strategist Address of the strategist + * @param _operator Address of the operator + * @param _minFinalityThreshold Minimum finality threshold + * @param _feePremiumBps Fee premium in basis points + */ + function initialize( + address _strategist, + address _operator, + uint16 _minFinalityThreshold, + uint16 _feePremiumBps + ) external virtual onlyGovernor initializer { + _initialize(_operator, _minFinalityThreshold, _feePremiumBps); + _setStrategistAddr(_strategist); + + address[] memory rewardTokens = new address[](0); + address[] memory assets = new address[](1); + address[] memory pTokens = new address[](1); + + assets[0] = address(usdcToken); + pTokens[0] = address(platformAddress); + + InitializableAbstractStrategy._initialize( + rewardTokens, + assets, + pTokens + ); + } + + /// @inheritdoc Generalized4626Strategy + function deposit(address _asset, uint256 _amount) + external + virtual + override + onlyGovernorOrStrategist + nonReentrant + { + _deposit(_asset, _amount); + } + + /// @inheritdoc Generalized4626Strategy + function depositAll() + external + virtual + override + onlyGovernorOrStrategist + nonReentrant + { + _deposit(usdcToken, IERC20(usdcToken).balanceOf(address(this))); + } + + /// @inheritdoc Generalized4626Strategy + /// @dev Interface requires a recipient, but for compatibility it must be address(this). + function withdraw( + address _recipient, + address _asset, + uint256 _amount + ) external virtual override onlyGovernorOrStrategist nonReentrant { + _withdraw(_recipient, _asset, _amount); + } + + /// @inheritdoc Generalized4626Strategy + function withdrawAll() + external + virtual + override + onlyGovernorOrStrategist + nonReentrant + { + IERC4626 platform = IERC4626(platformAddress); + _withdraw( + address(this), + usdcToken, + platform.previewRedeem(platform.balanceOf(address(this))) + ); + } + + /// @inheritdoc AbstractCCTPIntegrator + function _onMessageReceived(bytes memory payload) internal override { + uint32 messageType = payload.getMessageType(); + if (messageType == CrossChainStrategyHelper.DEPOSIT_MESSAGE) { + // Received when Master strategy sends tokens to the remote strategy + // Do nothing because we receive acknowledgement with token transfer, + // so _onTokenReceived will handle it + } else if (messageType == CrossChainStrategyHelper.WITHDRAW_MESSAGE) { + // Received when Master strategy requests a withdrawal + _processWithdrawMessage(payload); + } else { + revert("Unknown message type"); + } + } + + /** + * @dev Process deposit message from peer strategy + * @param tokenAmount Amount of tokens received + * @param feeExecuted Fee executed + * @param payload Payload of the message + */ + function _processDepositMessage( + // solhint-disable-next-line no-unused-vars + uint256 tokenAmount, + // solhint-disable-next-line no-unused-vars + uint256 feeExecuted, + bytes memory payload + ) internal virtual { + (uint64 nonce, ) = payload.decodeDepositMessage(); + + // Replay protection is part of the _markNonceAsProcessed function + _markNonceAsProcessed(nonce); + + // Deposit everything we got, not just what was bridged + uint256 balance = IERC20(usdcToken).balanceOf(address(this)); + + // Underlying call to deposit funds can fail. It mustn't affect the overall + // flow as confirmation message should still be sent. + if (balance >= MIN_TRANSFER_AMOUNT) { + _deposit(usdcToken, balance); + } + + // Send balance check message to the peer strategy + bytes memory message = CrossChainStrategyHelper + .encodeBalanceCheckMessage( + lastTransferNonce, + checkBalance(usdcToken), + true, + block.timestamp + ); + _sendMessage(message); + } + + /** + * @dev Deposit assets by converting them to shares + * @param _asset Address of asset to deposit + * @param _amount Amount of asset to deposit + */ + function _deposit(address _asset, uint256 _amount) internal override { + // By design, this function should not revert. Otherwise, it'd + // not be able to process messages and might freeze the contracts + // state. However these two require statements would never fail + // in every function invoking this. The same kind of checks should + // be enforced in all the calling functions for these two and any + // other require statements added to this function. + require(_amount > 0, "Must deposit something"); + require(_asset == address(usdcToken), "Unexpected asset address"); + + // This call can fail, and the failure doesn't need to bubble up to the _processDepositMessage function + // as the flow is not affected by the failure. + + try IERC4626(platformAddress).deposit(_amount, address(this)) { + emit Deposit(_asset, address(shareToken), _amount); + } catch Error(string memory reason) { + emit DepositUnderlyingFailed( + string(abi.encodePacked("Deposit failed: ", reason)) + ); + } catch (bytes memory lowLevelData) { + emit DepositUnderlyingFailed( + string( + abi.encodePacked( + "Deposit failed: low-level call failed with data ", + lowLevelData + ) + ) + ); + } + } + + /** + * @dev Process withdrawal message from peer strategy + * @param payload Payload of the message + */ + function _processWithdrawMessage(bytes memory payload) internal virtual { + (uint64 nonce, uint256 withdrawAmount) = payload + .decodeWithdrawMessage(); + + // Replay protection is part of the _markNonceAsProcessed function + _markNonceAsProcessed(nonce); + + uint256 usdcBalance = IERC20(usdcToken).balanceOf(address(this)); + + if (usdcBalance < withdrawAmount) { + // Withdraw the missing funds from the remote strategy. This call can fail and + // the failure doesn't bubble up to the _processWithdrawMessage function + _withdraw(address(this), usdcToken, withdrawAmount - usdcBalance); + + // Update the possible increase in the balance on the contract. + usdcBalance = IERC20(usdcToken).balanceOf(address(this)); + } + + // Check balance after withdrawal + uint256 strategyBalance = checkBalance(usdcToken); + + // If there are some tokens to be sent AND the balance is sufficient + // to satisfy the withdrawal request then send the funds to the peer strategy. + // In case a direct withdraw(All) has previously been called + // there is a possibility of USDC funds remaining on the contract. + // A separate withdraw to extract or deposit to the Morpho vault needs to be + // initiated from the peer Master strategy to utilise USDC funds. + if ( + withdrawAmount >= MIN_TRANSFER_AMOUNT && + usdcBalance >= withdrawAmount + ) { + // The new balance on the contract needs to have USDC subtracted from it as + // that will be withdrawn in the next step + bytes memory message = CrossChainStrategyHelper + .encodeBalanceCheckMessage( + lastTransferNonce, + strategyBalance - withdrawAmount, + true, + block.timestamp + ); + _sendTokens(withdrawAmount, message); + } else { + // Contract either: + // - only has small dust amount of USDC + // - doesn't have sufficient funds to satisfy the withdrawal request + // In both cases send the balance update message to the peer strategy. + bytes memory message = CrossChainStrategyHelper + .encodeBalanceCheckMessage( + lastTransferNonce, + strategyBalance, + true, + block.timestamp + ); + _sendMessage(message); + emit WithdrawalFailed(withdrawAmount, usdcBalance); + } + } + + /** + * @dev Withdraw asset by burning shares + * @param _recipient Address to receive withdrawn asset + * @param _asset Address of asset to withdraw + * @param _amount Amount of asset to withdraw + */ + function _withdraw( + address _recipient, + address _asset, + uint256 _amount + ) internal override { + require(_amount > 0, "Must withdraw something"); + require(_recipient == address(this), "Invalid recipient"); + require(_asset == address(usdcToken), "Unexpected asset address"); + + // This call can fail, and the failure doesn't need to bubble up to the _processWithdrawMessage function + // as the flow is not affected by the failure. + try + // slither-disable-next-line unused-return + IERC4626(platformAddress).withdraw( + _amount, + address(this), + address(this) + ) + { + emit Withdrawal(_asset, address(shareToken), _amount); + } catch Error(string memory reason) { + emit WithdrawUnderlyingFailed( + string(abi.encodePacked("Withdrawal failed: ", reason)) + ); + } catch (bytes memory lowLevelData) { + emit WithdrawUnderlyingFailed( + string( + abi.encodePacked( + "Withdrawal failed: low-level call failed with data ", + lowLevelData + ) + ) + ); + } + } + + /** + * @dev Process token received message from peer strategy + * @param tokenAmount Amount of tokens received + * @param feeExecuted Fee executed + * @param payload Payload of the message + */ + function _onTokenReceived( + uint256 tokenAmount, + uint256 feeExecuted, + bytes memory payload + ) internal override { + uint32 messageType = payload.getMessageType(); + + require( + messageType == CrossChainStrategyHelper.DEPOSIT_MESSAGE, + "Invalid message type" + ); + + _processDepositMessage(tokenAmount, feeExecuted, payload); + } + + /** + * @dev Send balance update message to the peer strategy + */ + function sendBalanceUpdate() + external + virtual + onlyOperatorOrStrategistOrGovernor + { + uint256 balance = checkBalance(usdcToken); + bytes memory message = CrossChainStrategyHelper + .encodeBalanceCheckMessage( + lastTransferNonce, + balance, + false, + block.timestamp + ); + _sendMessage(message); + } + + /** + * @notice Get the total asset value held in the platform and contract + * @param _asset Address of the asset + * @return balance Total value of the asset in the platform and contract + */ + function checkBalance(address _asset) + public + view + override + returns (uint256) + { + require(_asset == usdcToken, "Unexpected asset address"); + /** + * Balance of USDC on the contract is counted towards the total balance, since a deposit + * to the Morpho V2 might fail and the USDC might remain on this contract as a result of a + * bridged transfer. + */ + uint256 balanceOnContract = IERC20(usdcToken).balanceOf(address(this)); + + IERC4626 platform = IERC4626(platformAddress); + return + platform.previewRedeem(platform.balanceOf(address(this))) + + balanceOnContract; + } +} diff --git a/contracts/contracts/strategies/crosschain/CrossChainStrategyHelper.sol b/contracts/contracts/strategies/crosschain/CrossChainStrategyHelper.sol new file mode 100644 index 0000000000..3a4b86d0c0 --- /dev/null +++ b/contracts/contracts/strategies/crosschain/CrossChainStrategyHelper.sol @@ -0,0 +1,261 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +/** + * @title CrossChainStrategyHelper + * @author Origin Protocol Inc + * @dev This library is used to encode and decode the messages for the cross-chain strategy. + * It is used to ensure that the messages are valid and to get the message version and type. + */ + +import { BytesHelper } from "../../utils/BytesHelper.sol"; + +library CrossChainStrategyHelper { + using BytesHelper for bytes; + + uint32 public constant DEPOSIT_MESSAGE = 1; + uint32 public constant WITHDRAW_MESSAGE = 2; + uint32 public constant BALANCE_CHECK_MESSAGE = 3; + + uint32 public constant CCTP_MESSAGE_VERSION = 1; + uint32 public constant ORIGIN_MESSAGE_VERSION = 1010; + + // CCTP Message Header fields + // Ref: https://developers.circle.com/cctp/technical-guide#message-header + uint8 private constant VERSION_INDEX = 0; + uint8 private constant SOURCE_DOMAIN_INDEX = 4; + uint8 private constant SENDER_INDEX = 44; + uint8 private constant RECIPIENT_INDEX = 76; + uint8 private constant MESSAGE_BODY_INDEX = 148; + + /** + * @dev Get the message version from the message. + * It should always be 4 bytes long, + * starting from the 0th index. + * @param message The message to get the version from + * @return The message version + */ + function getMessageVersion(bytes memory message) + internal + pure + returns (uint32) + { + // uint32 bytes 0 to 4 is Origin message version + // uint32 bytes 4 to 8 is Message type + return message.extractUint32(0); + } + + /** + * @dev Get the message type from the message. + * It should always be 4 bytes long, + * starting from the 4th index. + * @param message The message to get the type from + * @return The message type + */ + function getMessageType(bytes memory message) + internal + pure + returns (uint32) + { + // uint32 bytes 0 to 4 is Origin message version + // uint32 bytes 4 to 8 is Message type + return message.extractUint32(4); + } + + /** + * @dev Verify the message version and type. + * The message version should be the same as the Origin message version, + * and the message type should be the same as the expected message type. + * @param _message The message to verify + * @param _type The expected message type + */ + function verifyMessageVersionAndType(bytes memory _message, uint32 _type) + internal + pure + { + require( + getMessageVersion(_message) == ORIGIN_MESSAGE_VERSION, + "Invalid Origin Message Version" + ); + require(getMessageType(_message) == _type, "Invalid Message type"); + } + + /** + * @dev Get the message payload from the message. + * The payload starts at the 8th byte. + * @param message The message to get the payload from + * @return The message payload + */ + function getMessagePayload(bytes memory message) + internal + pure + returns (bytes memory) + { + // uint32 bytes 0 to 4 is Origin message version + // uint32 bytes 4 to 8 is Message type + // Payload starts at byte 8 + return message.extractSlice(8, message.length); + } + + /** + * @dev Encode the deposit message. + * The message version and type are always encoded in the message. + * @param nonce The nonce of the deposit + * @param depositAmount The amount of the deposit + * @return The encoded deposit message + */ + function encodeDepositMessage(uint64 nonce, uint256 depositAmount) + internal + pure + returns (bytes memory) + { + return + abi.encodePacked( + ORIGIN_MESSAGE_VERSION, + DEPOSIT_MESSAGE, + abi.encode(nonce, depositAmount) + ); + } + + /** + * @dev Decode the deposit message. + * The message version and type are verified in the message. + * @param message The message to decode + * @return The nonce and the amount of the deposit + */ + function decodeDepositMessage(bytes memory message) + internal + pure + returns (uint64, uint256) + { + verifyMessageVersionAndType(message, DEPOSIT_MESSAGE); + + (uint64 nonce, uint256 depositAmount) = abi.decode( + getMessagePayload(message), + (uint64, uint256) + ); + return (nonce, depositAmount); + } + + /** + * @dev Encode the withdrawal message. + * The message version and type are always encoded in the message. + * @param nonce The nonce of the withdrawal + * @param withdrawAmount The amount of the withdrawal + * @return The encoded withdrawal message + */ + function encodeWithdrawMessage(uint64 nonce, uint256 withdrawAmount) + internal + pure + returns (bytes memory) + { + return + abi.encodePacked( + ORIGIN_MESSAGE_VERSION, + WITHDRAW_MESSAGE, + abi.encode(nonce, withdrawAmount) + ); + } + + /** + * @dev Decode the withdrawal message. + * The message version and type are verified in the message. + * @param message The message to decode + * @return The nonce and the amount of the withdrawal + */ + function decodeWithdrawMessage(bytes memory message) + internal + pure + returns (uint64, uint256) + { + verifyMessageVersionAndType(message, WITHDRAW_MESSAGE); + + (uint64 nonce, uint256 withdrawAmount) = abi.decode( + getMessagePayload(message), + (uint64, uint256) + ); + return (nonce, withdrawAmount); + } + + /** + * @dev Encode the balance check message. + * The message version and type are always encoded in the message. + * @param nonce The nonce of the balance check + * @param balance The balance to check + * @param transferConfirmation Indicates if the message is a transfer confirmation. This is true + * when the message is a result of a deposit or a withdrawal. + * @return The encoded balance check message + */ + function encodeBalanceCheckMessage( + uint64 nonce, + uint256 balance, + bool transferConfirmation, + uint256 timestamp + ) internal pure returns (bytes memory) { + return + abi.encodePacked( + ORIGIN_MESSAGE_VERSION, + BALANCE_CHECK_MESSAGE, + abi.encode(nonce, balance, transferConfirmation, timestamp) + ); + } + + /** + * @dev Decode the balance check message. + * The message version and type are verified in the message. + * @param message The message to decode + * @return The nonce, the balance and indicates if the message is a transfer confirmation + */ + function decodeBalanceCheckMessage(bytes memory message) + internal + pure + returns ( + uint64, + uint256, + bool, + uint256 + ) + { + verifyMessageVersionAndType(message, BALANCE_CHECK_MESSAGE); + + ( + uint64 nonce, + uint256 balance, + bool transferConfirmation, + uint256 timestamp + ) = abi.decode( + getMessagePayload(message), + (uint64, uint256, bool, uint256) + ); + return (nonce, balance, transferConfirmation, timestamp); + } + + /** + * @dev Decode the CCTP message header + * @param message Message to decode + * @return version Version of the message + * @return sourceDomainID Source domain ID + * @return sender Sender of the message + * @return recipient Recipient of the message + * @return messageBody Message body + */ + function decodeMessageHeader(bytes memory message) + internal + pure + returns ( + uint32 version, + uint32 sourceDomainID, + address sender, + address recipient, + bytes memory messageBody + ) + { + version = message.extractUint32(VERSION_INDEX); + sourceDomainID = message.extractUint32(SOURCE_DOMAIN_INDEX); + // Address of MessageTransmitterV2 caller on source domain + sender = message.extractAddress(SENDER_INDEX); + // Address to handle message body on destination domain + recipient = message.extractAddress(RECIPIENT_INDEX); + messageBody = message.extractSlice(MESSAGE_BODY_INDEX, message.length); + } +} diff --git a/contracts/contracts/strategies/crosschain/crosschain-strategy.md b/contracts/contracts/strategies/crosschain/crosschain-strategy.md new file mode 100644 index 0000000000..ab1afed9d9 --- /dev/null +++ b/contracts/contracts/strategies/crosschain/crosschain-strategy.md @@ -0,0 +1,744 @@ +# Cross-Chain Strategy Documentation + +## Overview + +The Cross-Chain Strategy enables OUSD Vault to deploy funds across multiple EVM chains using Circle's Cross-Chain Transfer Protocol (CCTP). The strategy consists of two main contracts: + +- **CrossChainMasterStrategy**: Deployed on Ethereum (same chain as OUSD Vault), acts as the primary strategy interface +- **CrossChainRemoteStrategy**: Deployed on a remote EVM chain (e.g., Base), manages funds in a 4626-compatible vault + +### Key Design Decisions + +- **Single Pending Transfer**: Only one deposit or withdrawal can be in-flight at a time to simplify state management and prevent race conditions +- **Nonce-Based Ordering**: All transfers use incrementing nonces to ensure proper sequencing and prevent replay attacks +- **CCTP Integration**: Uses Circle's CCTP for secure cross-chain token transfers and message passing + +--- + +## Architecture + +### High-Level Flow + +```mermaid +graph TB + subgraph Ethereum["Ethereum Chain"] + Vault[OUSD Vault] + Master[CrossChainMasterStrategy] + CCTPTokenMessenger1[CCTP Token Messenger] + CCTPMessageTransmitter1[CCTP Message Transmitter] + end + + subgraph Remote["Remote Chain (Base)"] + RemoteStrategy[CrossChainRemoteStrategy] + Vault4626[4626 Vault] + CCTPTokenMessenger2[CCTP Token Messenger] + CCTPMessageTransmitter2[CCTP Message Transmitter] + end + + Vault -->|deposit/withdraw| Master + Master -->|bridge USDC + messages| CCTPTokenMessenger1 + Master -->|send messages| CCTPMessageTransmitter1 + + CCTPTokenMessenger1 -.->|CCTP Bridge| CCTPTokenMessenger2 + CCTPMessageTransmitter1 -.->|CCTP Bridge| CCTPMessageTransmitter2 + + CCTPTokenMessenger2 -->|mint USDC| RemoteStrategy + CCTPMessageTransmitter2 -->|deliver messages| RemoteStrategy + + RemoteStrategy -->|deposit/withdraw| Vault4626 + RemoteStrategy -->|send balance updates| CCTPMessageTransmitter2 + + style Ethereum fill:#e1f5ff + style Remote fill:#fff4e1 + style Master fill:#c8e6c9 + style RemoteStrategy fill:#c8e6c9 + style Vault fill:#ffccbc + style Vault4626 fill:#ffccbc +``` + +### Contract Inheritance + +```mermaid +classDiagram + class Governable { + <> + +governor: address + +onlyGovernor() + } + + class AbstractCCTPIntegrator { + <> + +cctpMessageTransmitter: ICCTPMessageTransmitter + +cctpTokenMessenger: ICCTPTokenMessenger + +usdcToken: address + +peerDomainID: uint32 + +peerStrategy: address + +lastTransferNonce: uint64 + +operator: address + +_sendTokens(amount, hookData) + +_sendMessage(message) + +relay(message, attestation) + +_getNextNonce() uint64 + +_markNonceAsProcessed(nonce) + +_onTokenReceived()* void + +_onMessageReceived()* void + } + + class InitializableAbstractStrategy { + <> + +vaultAddress: address + +deposit(asset, amount) + +withdraw(recipient, asset, amount) + +checkBalance(asset) uint256 + } + + class Generalized4626Strategy { + <> + +platformAddress: address + +assetToken: address + +shareToken: address + +_deposit(asset, amount) + +_withdraw(recipient, asset, amount) + } + + class CrossChainMasterStrategy { + +remoteStrategyBalance: uint256 + +pendingAmount: uint256 + +transferTypeByNonce: mapping + +deposit(asset, amount) + +withdraw(recipient, asset, amount) + +checkBalance(asset) uint256 + +_processBalanceCheckMessage(message) + } + + class CrossChainRemoteStrategy { + +strategistAddr: address + +deposit(asset, amount) + +withdraw(recipient, asset, amount) + +checkBalance(asset) uint256 + +sendBalanceUpdate() + +_processDepositMessage(tokenAmount, fee, payload) + +_processWithdrawMessage(payload) + } + + Governable <|-- AbstractCCTPIntegrator + AbstractCCTPIntegrator <|-- CrossChainMasterStrategy + AbstractCCTPIntegrator <|-- CrossChainRemoteStrategy + InitializableAbstractStrategy <|-- CrossChainMasterStrategy + Generalized4626Strategy <|-- CrossChainRemoteStrategy + + note for AbstractCCTPIntegrator "_onTokenReceived() and\n_onMessageReceived() are\nabstract functions" + note for CrossChainMasterStrategy "Deployed on Ethereum\nInterfaces with OUSD Vault" + note for CrossChainRemoteStrategy "Deployed on Remote Chain\nManages 4626 Vault" +``` + +--- + +## Contracts and Libraries + +### AbstractCCTPIntegrator + +**Purpose**: Base contract providing CCTP integration functionality shared by both Master and Remote strategies. + +**Key Responsibilities**: +- CCTP message handling (`handleReceiveFinalizedMessage`, `handleReceiveUnfinalizedMessage`) +- Token bridging via CCTP Token Messenger (`_sendTokens`) +- Message sending via CCTP Message Transmitter (`_sendMessage`) +- Message relaying by operators (`relay`) +- Nonce management for transfer ordering +- Security checks (domain validation, sender validation) + +**Key State Variables**: +- `cctpMessageTransmitter`: CCTP Message Transmitter contract +- `cctpTokenMessenger`: CCTP Token Messenger contract +- `usdcToken`: USDC address on local chain +- `peerDomainID`: Domain ID of the peer chain +- `peerStrategy`: Address of the strategy on peer chain +- `minFinalityThreshold`: Minimum finality threshold (1000 or 2000) +- `feePremiumBps`: Fee premium in basis points (max 3000) +- `lastTransferNonce`: Last known transfer nonce +- `nonceProcessed`: Mapping of processed nonces +- `operator`: Address authorized to relay messages + +**Key Functions**: +- `_sendTokens(uint256 tokenAmount, bytes memory hookData)`: Bridges USDC via CCTP with hook data +- `_sendMessage(bytes memory message)`: Sends a message via CCTP +- `relay(bytes memory message, bytes memory attestation)`: Relays a finalized CCTP message (operator-only) +- `_getNextNonce()`: Gets and increments the next nonce +- `_markNonceAsProcessed(uint64 nonce)`: Marks a nonce as processed +- `isTransferPending()`: Checks if there's a pending transfer +- `isNonceProcessed(uint64 nonce)`: Checks if a nonce has been processed + +**Abstract Functions** (implemented by child contracts): +- `_onTokenReceived(uint256 tokenAmount, uint256 feeExecuted, bytes memory payload)`: Called when USDC is received via CCTP +- `_onMessageReceived(bytes memory payload)`: Called when a message is received + +### CrossChainMasterStrategy + +**Purpose**: Strategy deployed on Ethereum that interfaces with OUSD Vault and coordinates with Remote strategy. + +**Key Responsibilities**: +- Receiving deposits from OUSD Vault +- Initiating withdrawals requested by OUSD Vault +- Tracking remote strategy balance +- Managing pending transfer state +- Processing balance check messages from Remote strategy + +**Key State Variables**: +- `remoteStrategyBalance`: Cached balance of funds in Remote strategy +- `pendingAmount`: Amount bridged but not yet confirmed received +- `transferTypeByNonce`: Mapping of nonce to transfer type (Deposit/Withdrawal) + +**Key Functions**: +- `deposit(address _asset, uint256 _amount)`: Called by Vault to deposit funds +- `withdraw(address _recipient, address _asset, uint256 _amount)`: Called by Vault to withdraw funds +- `checkBalance(address _asset)`: Returns total balance (local + pending + remote) +- `_deposit(address _asset, uint256 depositAmount)`: Internal deposit handler +- `_withdraw(address _asset, address _recipient, uint256 _amount)`: Internal withdrawal handler +- `_processBalanceCheckMessage(bytes memory message)`: Processes balance check from Remote + +**Deposit Flow**: +1. Validate no pending transfer exists +2. Get next nonce and mark as Deposit type +3. Set `pendingAmount` +4. Bridge USDC via CCTP with deposit message in hook data +5. Wait for balance check message to confirm + +**Withdrawal Flow**: +1. Validate no pending transfer exists +2. Validate sufficient remote balance +3. Get next nonce and mark as Withdrawal type +4. Send withdrawal message via CCTP +5. Wait for tokens to be bridged back with balance check in hook data + +**Balance Check Processing**: +- Validates nonce matches `lastTransferNonce` +- Updates `remoteStrategyBalance` +- If pending deposit: marks nonce as processed, resets `pendingAmount` +- If pending withdrawal: skips balance update (handled in `_onTokenReceived`) + +### CrossChainRemoteStrategy + +**Purpose**: Strategy deployed on remote chain that manages funds in a 4626 vault and responds to Master strategy commands. + +**Key Responsibilities**: +- Receiving bridged USDC from Master strategy +- Depositing to 4626 vault +- Withdrawing from 4626 vault +- Sending balance check messages to Master strategy +- Managing strategist permissions + +**Key State Variables**: +- `strategistAddr`: Address of strategist (for compatibility with Generalized4626Strategy) + +**Key Functions**: +- `deposit(address _asset, uint256 _amount)`: Deposits to 4626 vault (governor/strategist only) +- `withdraw(address _recipient, address _asset, uint256 _amount)`: Withdraws from 4626 vault (governor/strategist only) +- `checkBalance(address _asset)`: Returns total balance (4626 vault + contract balance) +- `sendBalanceUpdate()`: Manually sends balance check message (operator/strategist/governor) +- `_processDepositMessage(uint256 tokenAmount, uint256 feeExecuted, bytes memory payload)`: Handles deposit message +- `_processWithdrawMessage(bytes memory payload)`: Handles withdrawal message +- `_deposit(address _asset, uint256 _amount)`: Internal deposit to 4626 vault (with error handling) +- `_withdraw(address _recipient, address _asset, uint256 _amount)`: Internal withdrawal from 4626 vault (with error handling) + +**Deposit Message Handling**: +1. Decode nonce and amount from payload +2. Verify nonce not already processed +3. Mark nonce as processed +4. Deposit all USDC balance to 4626 vault (may fail silently) +5. Send balance check message with updated balance + +**Withdrawal Message Handling**: +1. Decode nonce and amount from payload +2. Verify nonce not already processed +3. Mark nonce as processed +4. Withdraw from 4626 vault (may fail silently) +5. Bridge USDC back to Master (if balance > 1e6) with balance check in hook data +6. If balance <= 1e6, send balance check message only + +### CrossChainStrategyHelper + +**Purpose**: Library for encoding and decoding cross-chain messages. + +**Message Constants**: +- `DEPOSIT_MESSAGE = 1` +- `WITHDRAW_MESSAGE = 2` +- `BALANCE_CHECK_MESSAGE = 3` +- `CCTP_MESSAGE_VERSION = 1` +- `ORIGIN_MESSAGE_VERSION = 1010` + +**Message Format**: +``` +[0-4 bytes]: Origin Message Version (1010) +[4-8 bytes]: Message Type (1, 2, or 3) +[8+ bytes]: Message Payload (ABI-encoded) +``` + +**Key Functions**: +- `encodeDepositMessage(uint64 nonce, uint256 depositAmount)`: Encodes deposit message +- `decodeDepositMessage(bytes memory message)`: Decodes deposit message +- `encodeWithdrawMessage(uint64 nonce, uint256 withdrawAmount)`: Encodes withdrawal message +- `decodeWithdrawMessage(bytes memory message)`: Decodes withdrawal message +- `encodeBalanceCheckMessage(uint64 nonce, uint256 balance)`: Encodes balance check message +- `decodeBalanceCheckMessage(bytes memory message)`: Decodes balance check message +- `getMessageVersion(bytes memory message)`: Extracts message version +- `getMessageType(bytes memory message)`: Extracts message type +- `verifyMessageVersionAndType(bytes memory _message, uint32 _type)`: Validates message format + +**Message Payloads**: +- **Deposit**: `abi.encode(nonce, depositAmount)` +- **Withdraw**: `abi.encode(nonce, withdrawAmount)` +- **Balance Check**: `abi.encode(nonce, balance)` + +### BytesHelper + +**Purpose**: Utility library for extracting typed data from byte arrays. + +**Key Functions**: +- `extractSlice(bytes memory data, uint256 start, uint256 end)`: Extracts a byte slice +- `extractUint32(bytes memory data, uint256 start)`: Extracts uint32 at offset +- `extractUint256(bytes memory data, uint256 start)`: Extracts uint256 at offset +- `extractAddress(bytes memory data, uint256 start)`: Extracts address at offset (32-byte padded) + +**Usage**: Used by `CrossChainStrategyHelper` and `AbstractCCTPIntegrator` to parse CCTP message headers and bodies. + +--- + +## Message Protocol + +### CCTP Message Structure + +CCTP messages have a header and body: + +**Header**: +Ref: https://developers.circle.com/cctp/technical-guide#message-header + +**Message Body for Burn Messages (V2)**: +Ref: https://developers.circle.com/cctp/technical-guide#message-body +Ref: https://github.com/circlefin/evm-cctp-contracts/blob/master/src/messages/v2/BurnMessageV2.sol + +### Origin's Custom Message Body + +All Origin messages follow this format: +``` +[0-4 bytes]: ORIGIN_MESSAGE_VERSION (1010) +[4-8 bytes]: MESSAGE_TYPE (1, 2, or 3) +[8+ bytes]: Payload (ABI-encoded) +``` + +### Message Types + +#### 1. Deposit Message + +**Sent By**: Master Strategy +**Sent Via**: CCTP Token Messenger (as hook data) +**Contains**: +- Nonce (uint64) +- Deposit Amount (uint256) + +**Encoding**: `abi.encodePacked(ORIGIN_MESSAGE_VERSION, DEPOSIT_MESSAGE, abi.encode(nonce, depositAmount))` + +**Flow**: +1. Master bridges USDC with deposit message as hook data +2. Remote receives USDC and hook data via `_onTokenReceived` +3. Remote deposits to 4626 vault +4. Remote sends balance check message + +#### 2. Withdraw Message + +**Sent By**: Master Strategy +**Sent Via**: CCTP Message Transmitter +**Contains**: +- Nonce (uint64) +- Withdraw Amount (uint256) + +**Encoding**: `abi.encodePacked(ORIGIN_MESSAGE_VERSION, WITHDRAW_MESSAGE, abi.encode(nonce, withdrawAmount))` + +**Flow**: +1. Master sends withdrawal message +2. Remote receives via `_onMessageReceived` +3. Remote withdraws from 4626 vault +4. Remote bridges USDC back with balance check as hook data +5. Master receives USDC and processes balance check + +#### 3. Balance Check Message + +**Sent By**: Remote Strategy +**Sent Via**: CCTP Message Transmitter (or as hook data in burn message) +**Contains**: +- Nonce (uint64) +- Balance (uint256) + +**Encoding**: `abi.encodePacked(ORIGIN_MESSAGE_VERSION, BALANCE_CHECK_MESSAGE, abi.encode(nonce, balance))` + +**Flow**: +1. Remote sends balance check after deposit/withdrawal +2. Master receives and validates nonce +3. Master updates `remoteStrategyBalance` +4. If nonce matches pending transfer, marks as processed + +--- + +## Communication Flows + +### Deposit Flow + +```mermaid +sequenceDiagram + participant Vault as OUSD Vault + participant Master as CrossChainMasterStrategy
(Ethereum) + participant CCTP1 as CCTP Token Messenger
(Ethereum) + participant Bridge as CCTP Bridge + participant CCTP2 as CCTP Token Messenger
(Base) + participant Operator as Operator + participant Remote as CrossChainRemoteStrategy
(Base) + participant Vault4626 as 4626 Vault + participant CCTPMsg1 as CCTP Message Transmitter
(Base) + participant CCTPMsg2 as CCTP Message Transmitter
(Ethereum) + + Vault->>Master: deposit(USDC, amount) + Note over Master: Validates: no pending transfer,
amount > 0, amount <= MAX + Note over Master: Increments nonce, sets pendingAmount,
marks as Deposit type + Master->>CCTP1: depositForBurnWithHook(amount, hookData) + Note over Master: hookData = DepositMessage(nonce, amount) + Master-->>Master: Deposit event emitted + + CCTP1->>Bridge: Burn USDC + Send message + Bridge->>CCTP2: Message with attestation + + Operator->>Remote: relay(message, attestation) + Note over Remote: Validates message:
domain, sender, version + Remote->>CCTPMsg1: receiveMessage(message, attestation) + Note over CCTPMsg1: Detects burn message,
forwards to TokenMessenger + CCTPMsg1->>CCTP2: Process burn message + CCTP2->>Remote: Mint USDC (amount - fee) + Remote->>Remote: _onTokenReceived(tokenAmount, fee, hookData) + Note over Remote: Decodes DepositMessage(nonce, amount) + Note over Remote: Marks nonce as processed + Remote->>Vault4626: deposit(USDC balance) + Note over Remote: May fail silently, emits DepositFailed if fails + Remote->>Remote: Calculate new balance + Remote->>CCTPMsg1: sendMessage(BalanceCheckMessage) + Note over Remote: BalanceCheckMessage(nonce, balance) + + CCTPMsg1->>Bridge: Message with attestation + Bridge->>CCTPMsg2: Message with attestation + + Operator->>Master: relay(message, attestation) + Note over Master: Validates message + Master->>CCTPMsg2: receiveMessage(message, attestation) + Note over CCTPMsg2: Not a burn message,
forwards to handleReceiveFinalizedMessage + CCTPMsg2->>Master: handleReceiveFinalizedMessage(...) + Master->>Master: _onMessageReceived(payload) + Note over Master: Processes BalanceCheckMessage + Note over Master: Validates nonce matches lastTransferNonce + Note over Master: Marks nonce as processed + Note over Master: Resets pendingAmount = 0 + Note over Master: Updates remoteStrategyBalance +``` + +### Withdrawal Flow + +```mermaid +sequenceDiagram + participant Vault as OUSD Vault + participant Master as CrossChainMasterStrategy
(Ethereum) + participant CCTPMsg1 as CCTP Message Transmitter
(Ethereum) + participant Bridge as CCTP Bridge + participant CCTPMsg2 as CCTP Message Transmitter
(Base) + participant Operator as Operator + participant Remote as CrossChainRemoteStrategy
(Base) + participant Vault4626 as 4626 Vault + participant CCTP1 as CCTP Token Messenger
(Base) + participant CCTP2 as CCTP Token Messenger
(Ethereum) + + Vault->>Master: withdraw(vault, USDC, amount) + Note over Master: Validates: no pending transfer,
amount > 0, amount <= remoteBalance,
amount <= MAX_TRANSFER_AMOUNT + Note over Master: Increments nonce,
marks as Withdrawal type + Master->>CCTPMsg1: sendMessage(WithdrawMessage) + Note over Master: WithdrawMessage(nonce, amount) + Master-->>Master: WithdrawRequested event emitted + + CCTPMsg1->>Bridge: Message with attestation + Bridge->>CCTPMsg2: Message with attestation + + Operator->>Remote: relay(message, attestation) + Note over Remote: Validates message + Remote->>CCTPMsg2: receiveMessage(message, attestation) + Note over CCTPMsg2: Not a burn message,
forwards to handleReceiveFinalizedMessage + CCTPMsg2->>Remote: handleReceiveFinalizedMessage(...) + Remote->>Remote: _onMessageReceived(payload) + Note over Remote: Decodes WithdrawMessage(nonce, amount) + Note over Remote: Marks nonce as processed + Remote->>Vault4626: withdraw(amount) + Note over Remote: May fail silently, emits WithdrawFailed if fails + Remote->>Remote: Calculate new balance + + alt USDC balance > 1e6 + Remote->>CCTP1: depositForBurnWithHook(usdcBalance, hookData) + Note over Remote: hookData = BalanceCheckMessage(nonce, balance) + else USDC balance <= 1e6 + Remote->>CCTPMsg2: sendMessage(BalanceCheckMessage) + Note over Remote: BalanceCheckMessage(nonce, balance) + end + + CCTP1->>Bridge: Burn USDC + Send message + Bridge->>CCTP2: Message with attestation + + Operator->>Master: relay(message, attestation) + Note over Master: Validates message + Master->>CCTPMsg2: receiveMessage(message, attestation) + Note over CCTPMsg2: Detects burn message,
forwards to TokenMessenger + CCTPMsg2->>CCTP2: Process burn message + CCTP2->>Master: Mint USDC (amount - fee) + CCTPMsg2->>Master: _onTokenReceived(tokenAmount, fee, hookData) + Note over Master: Validates nonce matches lastTransferNonce + Note over Master: Validates transfer type is Withdrawal + Note over Master: Marks nonce as processed + Master->>Master: _onMessageReceived(payload) + Note over Master: Processes BalanceCheckMessage + Note over Master: Updates remoteStrategyBalance + Master->>Vault: Transfer all USDC + Master-->>Master: Withdrawal event emitted +``` + +### Balance Update Flow (Manual) + +```mermaid +sequenceDiagram + participant Caller as Operator/Strategist/
Governor + participant Remote as CrossChainRemoteStrategy
(Base) + participant Vault4626 as 4626 Vault + participant CCTPMsg1 as CCTP Message Transmitter
(Base) + participant Bridge as CCTP Bridge + participant CCTPMsg2 as CCTP Message Transmitter
(Ethereum) + participant Operator as Operator + participant Master as CrossChainMasterStrategy
(Ethereum) + + Caller->>Remote: sendBalanceUpdate() + Note over Remote: Calculates current balance:
4626 vault + contract balance + Remote->>Vault4626: previewRedeem(shares) + Vault4626-->>Remote: Asset value + Remote->>Remote: balance = vaultValue + contractBalance + Remote->>CCTPMsg1: sendMessage(BalanceCheckMessage) + Note over Remote: BalanceCheckMessage(lastTransferNonce, balance) + + CCTPMsg1->>Bridge: Message with attestation + Bridge->>CCTPMsg2: Message with attestation + + Operator->>Master: relay(message, attestation) + Note over Master: Validates message + Master->>CCTPMsg2: receiveMessage(message, attestation) + Note over CCTPMsg2: Not a burn message,
forwards to handleReceiveFinalizedMessage + CCTPMsg2->>Master: handleReceiveFinalizedMessage(...) + Master->>Master: _onMessageReceived(payload) + Note over Master: Processes BalanceCheckMessage + + alt nonce matches lastTransferNonce + alt no pending transfer + Note over Master: Updates remoteStrategyBalance + else pending deposit + Note over Master: Marks nonce as processed + Note over Master: Resets pendingAmount = 0 + Note over Master: Updates remoteStrategyBalance + else pending withdrawal + Note over Master: Ignores (handled by _onTokenReceived) + end + else nonce doesn't match + Note over Master: Ignores message (out of order) + end +``` + +--- + +## Nonce Management + +### Nonce Lifecycle + +1. **Initialization**: Nonces start at 0 (but 0 is disregarded, first nonce is 1) +2. **Increment**: `_getNextNonce()` increments `lastTransferNonce` and returns new value +3. **Processing**: `_markNonceAsProcessed(nonce)` marks nonce as processed +4. **Validation**: `isNonceProcessed(nonce)` checks if nonce has been processed + +### Nonce Rules + +- Nonces must be strictly increasing +- A nonce can only be marked as processed once +- Only the latest nonce can be marked as processed (nonce >= lastTransferNonce) +- New transfers cannot start if last nonce hasn't been processed + +### Replay Protection + +- Each message includes a nonce +- Nonces are checked before processing +- Once processed, a nonce cannot be processed again +- Out-of-order messages with non-matching nonces are ignored + +--- + +## State Management + +### Master Strategy State + +**Local State**: +- `IERC20(usdcToken).balanceOf(address(this))`: USDC held locally +- `pendingAmount`: USDC bridged but not confirmed +- `remoteStrategyBalance`: Cached balance in Remote strategy + +**Total Balance**: `localBalance + pendingAmount + remoteStrategyBalance` + +**Transfer State**: +- `lastTransferNonce`: Last known nonce +- `transferTypeByNonce`: Type of each transfer (Deposit/Withdrawal) +- `nonceProcessed`: Which nonces have been processed + +### Remote Strategy State + +**Local State**: +- `IERC20(usdcToken).balanceOf(address(this))`: USDC held locally +- `IERC4626(platformAddress).balanceOf(address(this))`: Shares in 4626 vault + +**Total Balance**: `contractBalance + previewRedeem(shares)` + +**Transfer State**: +- `lastTransferNonce`: Last known nonce +- `nonceProcessed`: Which nonces have been processed + +--- + +## Error Handling and Edge Cases + +### Deposit Failures + +**Remote Strategy Deposit Failure**: +- If 4626 vault deposit fails, Remote emits `DepositFailed` event +- Balance check message is still sent (includes undeposited USDC) +- Master strategy updates balance correctly +- Funds remain on Remote contract until manual deposit by the Guardian + +### Withdrawal Failures + +**Remote Strategy Withdrawal Failure**: +- If 4626 vault withdrawal fails, Remote emits `WithdrawFailed` event +- Balance check message is still sent (with original balance) +- Master strategy updates balance correctly +- No tokens are bridged back (or minimal dust if balance <= 1e6) +- Guardian will have to manually call the public `withdraw` method later to process the withdrawal and then call the `relay` method the WithdrawMessage again + +### Message Ordering + +**Out-of-Order Messages**: +- Balance check messages with non-matching nonces are ignored +- Master strategy only processes balance checks for `lastTransferNonce` +- Older messages are safely discarded + +**Race Conditions**: +- Single pending transfer design prevents most race conditions +- Withdrawal balance checks are ignored if withdrawal is pending (handled by `_onTokenReceived`) + +### Nonce Edge Cases + +**Nonce Too Low**: +- `_markNonceAsProcessed` reverts if nonce < lastTransferNonce +- Prevents replay attacks with old nonces + +**Nonce Already Processed**: +- `_markNonceAsProcessed` reverts if nonce already processed +- Prevents duplicate processing + +**Pending Transfer**: +- `_getNextNonce` reverts if last nonce not processed +- Prevents starting new transfer while one is pending + +### CCTP Limitations + +**Max Transfer Amount**: +- CCTP limits transfers to 10M USDC per transaction +- Both strategies enforce `MAX_TRANSFER_AMOUNT = 10_000_000 * 10**6` + +**Finality Thresholds**: +- Supports 1000 (safe, 1 epoch) or 2000 (finalized, 2 epochs) +- Configurable via `setMinFinalityThreshold` +- Unfinalized messages only supported if threshold is 1000 + +**Fee Handling**: +- Fee premium configurable up to 30% (3000 bps) +- Fees are deducted from bridged amount +- Remote strategy receives `tokenAmount - feeExecuted` + +### Operator Requirements + +**Message Relaying**: +- Only `operator` can call `relay()` +- Operator must provide valid CCTP attestation +- Operator is responsible for monitoring and relaying finalized messages + +**Security**: +- Messages are validated for domain, sender, and recipient +- Only messages from `peerStrategy` are accepted +- Only messages to `address(this)` are processed + +--- + +## Other Notes + +### Proxies +- Both strategies use Create2 to deploy their proxy to the same address on all networks + +### Initialization + +Both strategies require initialization: +- **Master**: `initialize(operator, minFinalityThreshold, feePremiumBps)` +- **Remote**: `initialize(strategist, operator, minFinalityThreshold, feePremiumBps)` + +### Governance + +- Both strategies inherit from `Governable` +- Governor can upgrade implementation, update operator, finality threshold, fee premium +- Remote strategy governor can update strategist address + +## Analytics & Monitoring + +### Useful Contract Methods and Variables +- `MasterStrategy.checkBalance(address)`: Returns the sum of balance held locally in the master contract, balance reported by the remote strategy and any tokens that are being bridged and are yet to be acknowledged by the rremote strategy +- `MasterStrategy.pendingAmount`: Returns the amount that is being bridged from Master to Remote strategy. Once it's received on Remote strategy and it sends back an acknowledgement, it'll set back to zero. +- `MasterStrategy.remoteStrategyBalacne`: Last reported balance of Remote strategy +- `RemoteStrategy.checkBalance(address)`: Returns the balance held by the remote strategy as well as the amount it has deposited into the underlying 4626 vault +- `RemoteStrategy.platformAddress`: Returns the underlying 4626 Vault to which remote strategy deposits funds to. + + +### Contract Events +The following events need to be monitored from the contracts and an alert be sent to any of the channels as they happen: + + ``` + event CCTPMinFinalityThresholdSet(uint16 minFinalityThreshold); + event CCTPFeePremiumBpsSet(uint16 feePremiumBps); + event OperatorChanged(address operator); + + event TokensBridged( + uint32 destinationDomain, + address peerStrategy, + address tokenAddress, + uint256 tokenAmount, + uint256 maxFee, + uint32 minFinalityThreshold, + bytes hookData + ); + + event MessageTransmitted( + uint32 destinationDomain, + address peerStrategy, + uint32 minFinalityThreshold, + bytes message + ); + + event DepositUnderlyingFailed(string reason); + event WithdrawalFailed(uint256 amountRequested, uint256 amountAvailable); + event WithdrawUnderlyingFailed(string reason); + event StrategistUpdated(address _address); + + event RemoteStrategyBalanceUpdated(uint256 balance); + event WithdrawRequested(address indexed asset, uint256 amount); + ``` + +Out of these, `DepositUnderlyingFailed`, `WithdrawalFailed` and `WithdrawUnderlyingFailed` are of higher importance as they require manual intervention by Guardian when they get emitted. \ No newline at end of file diff --git a/contracts/contracts/utils/BytesHelper.sol b/contracts/contracts/utils/BytesHelper.sol new file mode 100644 index 0000000000..75a0fa1875 --- /dev/null +++ b/contracts/contracts/utils/BytesHelper.sol @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +uint256 constant UINT32_LENGTH = 4; +uint256 constant UINT64_LENGTH = 8; +uint256 constant UINT256_LENGTH = 32; +// Address is 20 bytes, but we expect the data to be padded with 0s to 32 bytes +uint256 constant ADDRESS_LENGTH = 32; + +library BytesHelper { + /** + * @dev Extract a slice from bytes memory + * @param data The bytes memory to slice + * @param start The start index (inclusive) + * @param end The end index (exclusive) + * @return result A new bytes memory containing the slice + */ + function extractSlice( + bytes memory data, + uint256 start, + uint256 end + ) internal pure returns (bytes memory) { + require(end >= start, "Invalid slice range"); + require(end <= data.length, "Slice end exceeds data length"); + + uint256 length = end - start; + bytes memory result = new bytes(length); + + // Simple byte-by-byte copy + for (uint256 i = 0; i < length; i++) { + result[i] = data[start + i]; + } + + return result; + } + + /** + * @dev Decode a uint32 from a bytes memory + * @param data The bytes memory to decode + * @return uint32 The decoded uint32 + */ + function decodeUint32(bytes memory data) internal pure returns (uint32) { + require(data.length == 4, "Invalid data length"); + return uint32(uint256(bytes32(data)) >> 224); + } + + /** + * @dev Extract a uint32 from a bytes memory + * @param data The bytes memory to extract from + * @param start The start index (inclusive) + * @return uint32 The extracted uint32 + */ + function extractUint32(bytes memory data, uint256 start) + internal + pure + returns (uint32) + { + return decodeUint32(extractSlice(data, start, start + UINT32_LENGTH)); + } + + /** + * @dev Decode an address from a bytes memory. + * Expects the data to be padded with 0s to 32 bytes. + * @param data The bytes memory to decode + * @return address The decoded address + */ + function decodeAddress(bytes memory data) internal pure returns (address) { + // We expect the data to be padded with 0s, so length is 32 not 20 + require(data.length == 32, "Invalid data length"); + return abi.decode(data, (address)); + } + + /** + * @dev Extract an address from a bytes memory + * @param data The bytes memory to extract from + * @param start The start index (inclusive) + * @return address The extracted address + */ + function extractAddress(bytes memory data, uint256 start) + internal + pure + returns (address) + { + return decodeAddress(extractSlice(data, start, start + ADDRESS_LENGTH)); + } + + /** + * @dev Decode a uint256 from a bytes memory + * @param data The bytes memory to decode + * @return uint256 The decoded uint256 + */ + function decodeUint256(bytes memory data) internal pure returns (uint256) { + require(data.length == 32, "Invalid data length"); + return abi.decode(data, (uint256)); + } + + /** + * @dev Extract a uint256 from a bytes memory + * @param data The bytes memory to extract from + * @param start The start index (inclusive) + * @return uint256 The extracted uint256 + */ + function extractUint256(bytes memory data, uint256 start) + internal + pure + returns (uint256) + { + return decodeUint256(extractSlice(data, start, start + UINT256_LENGTH)); + } +} diff --git a/contracts/contracts/utils/InitializableAbstractStrategy.sol b/contracts/contracts/utils/InitializableAbstractStrategy.sol index 890da82e57..dbb4adb097 100644 --- a/contracts/contracts/utils/InitializableAbstractStrategy.sol +++ b/contracts/contracts/utils/InitializableAbstractStrategy.sol @@ -81,7 +81,7 @@ abstract contract InitializableAbstractStrategy is Initializable, Governable { /** * @dev Verifies that the caller is the Governor or Strategist. */ - modifier onlyGovernorOrStrategist() { + modifier onlyGovernorOrStrategist() virtual { require( isGovernor() || msg.sender == IVault(vaultAddress).strategistAddr(), "Caller is not the Strategist or Governor" diff --git a/contracts/deploy/base/040_crosschain_strategy_proxies.js b/contracts/deploy/base/040_crosschain_strategy_proxies.js new file mode 100644 index 0000000000..c722c21980 --- /dev/null +++ b/contracts/deploy/base/040_crosschain_strategy_proxies.js @@ -0,0 +1,21 @@ +const { deployOnBase } = require("../../utils/deploy-l2"); +const { deployProxyWithCreateX } = require("../deployActions"); + +module.exports = deployOnBase( + { + deployName: "040_crosschain_strategy_proxies", + }, + async () => { + // the salt needs to match the salt on the base chain deploying the other part of the strategy + const salt = "Morpho V2 Crosschain Strategy 1"; + const proxyAddress = await deployProxyWithCreateX( + salt, + "CrossChainStrategyProxy" + ); + console.log(`CrossChainStrategyProxy address: ${proxyAddress}`); + + return { + actions: [], + }; + } +); diff --git a/contracts/deploy/base/041_crosschain_strategy.js b/contracts/deploy/base/041_crosschain_strategy.js new file mode 100644 index 0000000000..042b84fb9f --- /dev/null +++ b/contracts/deploy/base/041_crosschain_strategy.js @@ -0,0 +1,53 @@ +const { deployOnBase } = require("../../utils/deploy-l2"); +const addresses = require("../../utils/addresses"); +const { + deployCrossChainRemoteStrategyImpl, + getCreate2ProxyAddress, +} = require("../deployActions"); +const { cctpDomainIds } = require("../../utils/cctp"); + +module.exports = deployOnBase( + { + deployName: "041_crosschain_strategy", + }, + async () => { + const crossChainStrategyProxyAddress = await getCreate2ProxyAddress( + "CrossChainStrategyProxy" + ); + console.log( + `CrossChainStrategyProxy address: ${crossChainStrategyProxyAddress}` + ); + + const implAddress = await deployCrossChainRemoteStrategyImpl( + "0xbeeF010f9cb27031ad51e3333f9aF9C6B1228183", // 4626 Vault + crossChainStrategyProxyAddress, + cctpDomainIds.Ethereum, + crossChainStrategyProxyAddress, + addresses.base.USDC, + addresses.mainnet.USDC, + "CrossChainRemoteStrategy", + addresses.CCTPTokenMessengerV2, + addresses.CCTPMessageTransmitterV2, + addresses.base.timelock + ); + console.log(`CrossChainRemoteStrategyImpl address: ${implAddress}`); + + const cCrossChainRemoteStrategy = await ethers.getContractAt( + "CrossChainRemoteStrategy", + crossChainStrategyProxyAddress + ); + console.log( + `CrossChainRemoteStrategy address: ${cCrossChainRemoteStrategy.address}` + ); + + return { + actions: [ + { + contract: cCrossChainRemoteStrategy, + signature: "safeApproveAllTokens()", + args: [], + }, + ], + }; + } +); diff --git a/contracts/deploy/deployActions.js b/contracts/deploy/deployActions.js index 94692f1a98..6807da0f6e 100644 --- a/contracts/deploy/deployActions.js +++ b/contracts/deploy/deployActions.js @@ -1,4 +1,6 @@ const hre = require("hardhat"); +const fs = require("fs"); +const path = require("path"); const { setStorageAt } = require("@nomicfoundation/hardhat-network-helpers"); const { getNetworkName } = require("../utils/hardhat-helpers"); const { parseUnits } = require("ethers/lib/utils.js"); @@ -13,17 +15,26 @@ const { isSonicOrFork, isTest, isFork, + isForkTest, + isCI, isPlume, isHoodi, isHoodiOrFork, } = require("../test/helpers.js"); -const { deployWithConfirmation, withConfirmation } = require("../utils/deploy"); +const { + deployWithConfirmation, + verifyContractOnEtherscan, + withConfirmation, + encodeSaltForCreateX, +} = require("../utils/deploy"); const { metapoolLPCRVPid } = require("../utils/constants"); const { replaceContractAt } = require("../utils/hardhat"); const { resolveContract } = require("../utils/resolvers"); const { impersonateAccount, getSigner } = require("../utils/signers"); const { getDefenderSigner } = require("../utils/signersNoHardhat"); const { getTxOpts } = require("../utils/tx"); +const createxAbi = require("../abi/createx.json"); + const { beaconChainGenesisTimeHoodi, beaconChainGenesisTimeMainnet, @@ -1682,6 +1693,328 @@ const deploySonicSwapXAMOStrategyImplementation = async () => { return cSonicSwapXAMOStrategy; }; +const getCreate2ProxiesFilePath = async () => { + const networkName = + isFork || isForkTest || isCI ? "localhost" : await getNetworkName(); + return path.resolve( + __dirname, + `./../deployments/${networkName}/create2Proxies.json` + ); +}; + +const storeCreate2ProxyAddress = async (proxyName, proxyAddress) => { + const filePath = await getCreate2ProxiesFilePath(); + + // Ensure the directory exists before writing the file + const dirPath = path.dirname(filePath); + if (!fs.existsSync(dirPath)) { + fs.mkdirSync(dirPath, { recursive: true }); + } + + let existingContents = {}; + if (fs.existsSync(filePath)) { + existingContents = JSON.parse(fs.readFileSync(filePath, "utf8")); + } + + await new Promise((resolve, reject) => { + fs.writeFile( + filePath, + JSON.stringify( + { + ...existingContents, + [proxyName]: proxyAddress, + }, + undefined, + 2 + ), + (err) => { + if (err) { + console.log("Err:", err); + reject(err); + return; + } + console.log( + `Stored create2 proxy address for ${proxyName} at ${filePath}` + ); + resolve(); + } + ); + }); +}; + +const getCreate2ProxyAddress = async (proxyName) => { + const filePath = await getCreate2ProxiesFilePath(); + if (!fs.existsSync(filePath)) { + throw new Error(`Create2 proxies file not found at ${filePath}`); + } + const contents = JSON.parse(fs.readFileSync(filePath, "utf8")); + if (!contents[proxyName]) { + throw new Error(`Proxy ${proxyName} not found in ${filePath}`); + } + return contents[proxyName]; +}; + +// deploys an instance of InitializeGovernedUpgradeabilityProxy where address is defined by salt +const deployProxyWithCreateX = async ( + salt, + proxyName, + verifyContract = false, + contractPath = null +) => { + const { deployerAddr } = await getNamedAccounts(); + + const sDeployer = await ethers.provider.getSigner(deployerAddr); + + // Basically hex of "originprotocol" padded to 20 bytes to mimic an address + const addrForSalt = "0x0000000000006f726967696e70726f746f636f6c"; + // NOTE: We always use fixed address to compute the salt for the proxy. + // It makes the address predictable, easier to verify and easier to use + // with CI and local fork testing. + log( + `Deploying ${proxyName} with salt: ${salt} and fixed address: ${addrForSalt}` + ); + + const cCreateX = await ethers.getContractAt(createxAbi, addresses.createX); + const factoryEncodedSalt = encodeSaltForCreateX(addrForSalt, false, salt); + + const getFactoryBytecode = async () => { + // No deployment needed—get factory directly from artifacts + const ProxyContract = await ethers.getContractFactory(proxyName); + const encodedArgs = ProxyContract.interface.encodeDeploy([deployerAddr]); + return ethers.utils.hexConcat([ProxyContract.bytecode, encodedArgs]); + }; + + const txResponse = await withConfirmation( + cCreateX + .connect(sDeployer) + .deployCreate2(factoryEncodedSalt, await getFactoryBytecode()) + ); + + // // // Create3ProxyContractCreation + // const create3ContractCreationTopic = + // "0x2feea65dd4e9f9cbd86b74b7734210c59a1b2981b5b137bd0ee3e208200c9067"; + const contractCreationTopic = + "0xb8fda7e00c6b06a2b54e58521bc5894fee35f1090e5a3bb6390bfe2b98b497f7"; + + // const topicToUse = isCreate3 ? create3ContractCreationTopic : contractCreationTopic; + const txReceipt = await txResponse.wait(); + const proxyAddress = ethers.utils.getAddress( + `0x${txReceipt.events + .find((event) => event.topics[0] === contractCreationTopic) + .topics[1].slice(26)}` + ); + + log(`Deployed ${proxyName} at ${proxyAddress}`); + + await storeCreate2ProxyAddress(proxyName, proxyAddress); + + // Verify contract on Etherscan if requested and on a live network + // Can be enabled via parameter or VERIFY_CONTRACTS environment variable + const shouldVerify = + verifyContract || process.env.VERIFY_CONTRACTS === "true"; + if (shouldVerify && !isTest && !isFork && proxyAddress) { + // Constructor args for the proxy are [deployerAddr] + const constructorArgs = [deployerAddr]; + await verifyContractOnEtherscan( + proxyName, + proxyAddress, + constructorArgs, + proxyName, + contractPath + ); + } + + return proxyAddress; +}; + +// deploys and initializes the CrossChain master strategy +const deployCrossChainMasterStrategyImpl = async ( + proxyAddress, + targetDomainId, + remoteStrategyAddress, + baseToken, + peerBaseToken, + vaultAddress, + implementationName = "CrossChainMasterStrategy", + skipInitialize = false, + tokenMessengerAddress = addresses.CCTPTokenMessengerV2, + messageTransmitterAddress = addresses.CCTPMessageTransmitterV2, + governor = addresses.mainnet.Timelock +) => { + const { deployerAddr, multichainStrategistAddr } = await getNamedAccounts(); + const sDeployer = await ethers.provider.getSigner(deployerAddr); + log(`Deploying CrossChainMasterStrategyImpl as deployer ${deployerAddr}`); + + const cCrossChainStrategyProxy = await ethers.getContractAt( + "CrossChainStrategyProxy", + proxyAddress + ); + + await deployWithConfirmation(implementationName, [ + [ + addresses.zero, // platform address + vaultAddress, // vault address + ], + [ + tokenMessengerAddress, + messageTransmitterAddress, + targetDomainId, + remoteStrategyAddress, + baseToken, + peerBaseToken, + ], + ]); + const dCrossChainMasterStrategy = await ethers.getContract( + implementationName + ); + + if (!skipInitialize) { + const initData = dCrossChainMasterStrategy.interface.encodeFunctionData( + "initialize(address,uint16,uint16)", + [multichainStrategistAddr, 2000, 0] + ); + + // Init the proxy to point at the implementation, set the governor, and call initialize + const initFunction = "initialize(address,address,bytes)"; + await withConfirmation( + cCrossChainStrategyProxy.connect(sDeployer)[initFunction]( + dCrossChainMasterStrategy.address, + governor, // governor + initData, // data for delegate call to the initialize function on the strategy + await getTxOpts() + ) + ); + } + + return dCrossChainMasterStrategy.address; +}; + +// deploys and initializes the CrossChain remote strategy +const deployCrossChainRemoteStrategyImpl = async ( + platformAddress, // underlying 4626 vault address + proxyAddress, + targetDomainId, + remoteStrategyAddress, + baseToken, + peerBaseToken, + implementationName = "CrossChainRemoteStrategy", + tokenMessengerAddress = addresses.CCTPTokenMessengerV2, + messageTransmitterAddress = addresses.CCTPMessageTransmitterV2, + governor = addresses.base.timelock, + initialize = true +) => { + const { deployerAddr, multichainStrategistAddr } = await getNamedAccounts(); + const sDeployer = await ethers.provider.getSigner(deployerAddr); + log(`Deploying CrossChainRemoteStrategyImpl as deployer ${deployerAddr}`); + + const cCrossChainStrategyProxy = await ethers.getContractAt( + "CrossChainStrategyProxy", + proxyAddress + ); + + await deployWithConfirmation(implementationName, [ + [ + platformAddress, + addresses.zero, // There is no vault on the remote strategy + ], + [ + tokenMessengerAddress, + messageTransmitterAddress, + targetDomainId, + remoteStrategyAddress, + baseToken, + peerBaseToken, + ], + ]); + const dCrossChainRemoteStrategy = await ethers.getContract( + implementationName + ); + + if (initialize) { + const initData = dCrossChainRemoteStrategy.interface.encodeFunctionData( + "initialize(address,address,uint16,uint16)", + [multichainStrategistAddr, multichainStrategistAddr, 2000, 0] + ); + + // Init the proxy to point at the implementation, set the governor, and call initialize + const initFunction = "initialize(address,address,bytes)"; + await withConfirmation( + cCrossChainStrategyProxy.connect(sDeployer)[initFunction]( + dCrossChainRemoteStrategy.address, + governor, // governor + //initData, // data for delegate call to the initialize function on the strategy + initData, + await getTxOpts() + ) + ); + } + + return dCrossChainRemoteStrategy.address; +}; + +// deploy the corss chain Master / Remote strategy pair for unit testing +const deployCrossChainUnitTestStrategy = async (usdcAddress) => { + const { deployerAddr, governorAddr } = await getNamedAccounts(); + // const sDeployer = await ethers.provider.getSigner(deployerAddr); + const sGovernor = await ethers.provider.getSigner(governorAddr); + const dMasterProxy = await deployWithConfirmation( + "CrossChainMasterStrategyProxy", + [deployerAddr], + "CrossChainStrategyProxy" + ); + const dRemoteProxy = await deployWithConfirmation( + "CrossChainRemoteStrategyProxy", + [deployerAddr], + "CrossChainStrategyProxy" + ); + + const cVaultProxy = await ethers.getContract("VaultProxy"); + const messageTransmitter = await ethers.getContract( + "CCTPMessageTransmitterMock" + ); + const tokenMessenger = await ethers.getContract("CCTPTokenMessengerMock"); + const c4626Vault = await ethers.getContract("MockERC4626Vault"); + + await deployCrossChainMasterStrategyImpl( + dMasterProxy.address, + 6, // Base domain id + // unit tests differ from mainnet where remote strategy has a different address + dRemoteProxy.address, + usdcAddress, + usdcAddress, // Assume both are same on unit tests + cVaultProxy.address, + "CrossChainMasterStrategy", + false, + tokenMessenger.address, + messageTransmitter.address, + governorAddr + ); + + await deployCrossChainRemoteStrategyImpl( + c4626Vault.address, + dRemoteProxy.address, + 0, // Ethereum domain id + dMasterProxy.address, + usdcAddress, + usdcAddress, // Assume both are same on unit tests + "CrossChainRemoteStrategy", + tokenMessenger.address, + messageTransmitter.address, + governorAddr + ); + + const cCrossChainRemoteStrategy = await ethers.getContractAt( + "CrossChainRemoteStrategy", + dRemoteProxy.address + ); + await withConfirmation( + cCrossChainRemoteStrategy.connect(sGovernor).safeApproveAllTokens() + ); + // await withConfirmation( + // messageTransmitter.connect(sDeployer).setCCTPTokenMessenger(tokenMessenger.address) + // ); +}; + module.exports = { deployOracles, deployCore, @@ -1719,4 +2052,10 @@ module.exports = { deployPlumeMockRoosterAMOStrategyImplementation, getPlumeContracts, deploySonicSwapXAMOStrategyImplementation, + deployProxyWithCreateX, + deployCrossChainMasterStrategyImpl, + deployCrossChainRemoteStrategyImpl, + deployCrossChainUnitTestStrategy, + + getCreate2ProxyAddress, }; diff --git a/contracts/deploy/mainnet/000_mock.js b/contracts/deploy/mainnet/000_mock.js index f2a598e705..7a5f2d9976 100644 --- a/contracts/deploy/mainnet/000_mock.js +++ b/contracts/deploy/mainnet/000_mock.js @@ -28,6 +28,7 @@ const { const deployMocks = async ({ getNamedAccounts, deployments }) => { const { deploy } = deployments; const { deployerAddr, governorAddr } = await getNamedAccounts(); + // const sDeployer = await ethers.provider.getSigner(deployerAddr); console.log("Running 000_mock deployment..."); console.log("Deployer address", deployerAddr); @@ -447,6 +448,26 @@ const deployMocks = async ({ getNamedAccounts, deployments }) => { const mockBeaconRoots = await ethers.getContract("MockBeaconRoots"); await replaceContractAt(addresses.mainnet.beaconRoots, mockBeaconRoots); + await deploy("CCTPMessageTransmitterMock", { + from: deployerAddr, + args: [usdc.address], + }); + const messageTransmitter = await ethers.getContract( + "CCTPMessageTransmitterMock" + ); + await deploy("CCTPTokenMessengerMock", { + from: deployerAddr, + args: [usdc.address, messageTransmitter.address], + }); + await deploy("MockERC4626Vault", { + from: deployerAddr, + args: [usdc.address], + }); + // const tokenMessenger = await ethers.getContract("CCTPTokenMessengerMock"); + // await messageTransmitter + // .connect(sDeployer) + // .setCCTPTokenMessenger(tokenMessenger.address); + console.log("000_mock deploy done."); return true; diff --git a/contracts/deploy/mainnet/001_core.js b/contracts/deploy/mainnet/001_core.js index 024146b8b9..37cc3fc746 100644 --- a/contracts/deploy/mainnet/001_core.js +++ b/contracts/deploy/mainnet/001_core.js @@ -21,10 +21,13 @@ const { deployWOeth, deployOETHSwapper, deployOUSDSwapper, + deployCrossChainUnitTestStrategy, } = require("../deployActions"); const main = async () => { console.log("Running 001_core deployment..."); + const usdc = await ethers.getContract("MockUSDC"); + await deployOracles(); await deployCore(); await deployCurveMetapoolMocks(); @@ -48,6 +51,7 @@ const main = async () => { await deployWOeth(); await deployOETHSwapper(); await deployOUSDSwapper(); + await deployCrossChainUnitTestStrategy(usdc.address); console.log("001_core deploy done."); return true; }; diff --git a/contracts/deploy/mainnet/165_crosschain_strategy_proxies.js b/contracts/deploy/mainnet/165_crosschain_strategy_proxies.js new file mode 100644 index 0000000000..a9e4fedb75 --- /dev/null +++ b/contracts/deploy/mainnet/165_crosschain_strategy_proxies.js @@ -0,0 +1,25 @@ +const { deploymentWithGovernanceProposal } = require("../../utils/deploy"); +const { deployProxyWithCreateX } = require("../deployActions"); + +module.exports = deploymentWithGovernanceProposal( + { + deployName: "165_crosschain_strategy_proxies", + forceDeploy: false, + reduceQueueTime: true, + deployerIsProposer: false, + proposalId: "", + }, + async () => { + // the salt needs to match the salt on the base chain deploying the other part of the strategy + const salt = "Morpho V2 Crosschain Strategy 1"; + const proxyAddress = await deployProxyWithCreateX( + salt, + "CrossChainStrategyProxy" + ); + console.log(`CrossChainStrategyProxy address: ${proxyAddress}`); + + return { + actions: [], + }; + } +); diff --git a/contracts/deploy/mainnet/166_crosschain_strategy.js b/contracts/deploy/mainnet/166_crosschain_strategy.js new file mode 100644 index 0000000000..8b11a023fd --- /dev/null +++ b/contracts/deploy/mainnet/166_crosschain_strategy.js @@ -0,0 +1,59 @@ +const { deploymentWithGovernanceProposal } = require("../../utils/deploy"); +const addresses = require("../../utils/addresses"); +const { cctpDomainIds } = require("../../utils/cctp"); +const { + deployCrossChainMasterStrategyImpl, + getCreate2ProxyAddress, +} = require("../deployActions"); + +module.exports = deploymentWithGovernanceProposal( + { + deployName: "166_crosschain_strategy", + forceDeploy: false, + reduceQueueTime: true, + deployerIsProposer: false, + proposalId: "", + }, + async () => { + const crossChainStrategyProxyAddress = await getCreate2ProxyAddress( + "CrossChainStrategyProxy" + ); + const cProxy = await ethers.getContractAt( + "CrossChainStrategyProxy", + crossChainStrategyProxyAddress + ); + console.log(`CrossChainStrategyProxy address: ${cProxy.address}`); + + const cVaultProxy = await ethers.getContract("VaultProxy"); + + const implAddress = await deployCrossChainMasterStrategyImpl( + crossChainStrategyProxyAddress, + cctpDomainIds.Base, + // Same address for both master and remote strategy + crossChainStrategyProxyAddress, + addresses.mainnet.USDC, + addresses.base.USDC, + cVaultProxy.address, + "CrossChainMasterStrategy", + false, + addresses.CCTPTokenMessengerV2, + addresses.CCTPMessageTransmitterV2, + addresses.mainnet.Timelock + ); + console.log(`CrossChainMasterStrategyImpl address: ${implAddress}`); + + const cCrossChainMasterStrategy = await ethers.getContractAt( + "CrossChainMasterStrategy", + crossChainStrategyProxyAddress + ); + console.log( + `CrossChainMasterStrategy address: ${cCrossChainMasterStrategy.address}` + ); + + // TODO: Set reward tokens to Morpho + + return { + actions: [], + }; + } +); diff --git a/contracts/deployments/base/create2Proxies.json b/contracts/deployments/base/create2Proxies.json new file mode 100644 index 0000000000..9e26dfeeb6 --- /dev/null +++ b/contracts/deployments/base/create2Proxies.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/contracts/deployments/mainnet/create2Proxies.json b/contracts/deployments/mainnet/create2Proxies.json new file mode 100644 index 0000000000..9e26dfeeb6 --- /dev/null +++ b/contracts/deployments/mainnet/create2Proxies.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/contracts/scripts/defender-actions/crossChainRelay.js b/contracts/scripts/defender-actions/crossChainRelay.js new file mode 100644 index 0000000000..121e48f48f --- /dev/null +++ b/contracts/scripts/defender-actions/crossChainRelay.js @@ -0,0 +1,72 @@ +const { ethers } = require("ethers"); +const { Defender } = require("@openzeppelin/defender-sdk"); +const { processCctpBridgeTransactions } = require("../../tasks/crossChain"); +const { getNetworkName } = require("../../utils/hardhat-helpers"); +const { configuration } = require("../../utils/cctp"); + +// Entrypoint for the Defender Action +const handler = async (event) => { + console.log( + `DEBUG env var in handler before being set: "${process.env.DEBUG}"` + ); + + // Initialize defender relayer provider and signer + const client = new Defender(event); + // Chain ID of the target contract relayer signer + const provider = client.relaySigner.getProvider({ ethersVersion: "v5" }); + const { chainId } = await provider.getNetwork(); + let sourceProvider; + const signer = await client.relaySigner.getSigner(provider, { + speed: "fastest", + ethersVersion: "v5", + }); + + // destinatino chain is mainnet, source chain is base + if (chainId === 1) { + if (!event.secrets.BASE_PROVIDER_URL) { + throw new Error("BASE_PROVIDER_URL env var required"); + } + sourceProvider = new ethers.providers.JsonRpcProvider( + event.secrets.BASE_PROVIDER_URL + ); + } + // destination chain is base, source chain is mainnet + else if (chainId === 8453) { + if (!event.secrets.PROVIDER_URL) { + throw new Error("PROVIDER_URL env var required"); + } + sourceProvider = new ethers.providers.JsonRpcProvider( + event.secrets.PROVIDER_URL + ); + } else { + throw new Error(`Unsupported chain id: ${chainId}`); + } + + const networkName = await getNetworkName(sourceProvider); + const isMainnet = networkName === "mainnet"; + const isBase = networkName === "base"; + + let config; + if (isMainnet) { + config = configuration.mainnetBaseMorpho.mainnet; + } else if (isBase) { + config = configuration.mainnetBaseMorpho.base; + } else { + throw new Error(`Unsupported network name: ${networkName}`); + } + + await processCctpBridgeTransactions({ + destinationChainSigner: signer, + sourceChainProvider: sourceProvider, + store: client.keyValueStore, + networkName, + blockLookback: config.blockLookback, + cctpDestinationDomainId: config.cctpDestinationDomainId, + cctpSourceDomainId: config.cctpSourceDomainId, + cctpIntegrationContractAddress: config.cctpIntegrationContractAddress, + cctpIntegrationContractAddressDestination: + config.cctpIntegrationContractAddressDestination, + }); +}; + +module.exports = { handler }; diff --git a/contracts/scripts/defender-actions/rollup.config.cjs b/contracts/scripts/defender-actions/rollup.config.cjs index edf0cc11f5..5f54fb6395 100644 --- a/contracts/scripts/defender-actions/rollup.config.cjs +++ b/contracts/scripts/defender-actions/rollup.config.cjs @@ -40,6 +40,7 @@ const actions = [ "sonicRequestWithdrawal", "sonicClaimWithdrawals", "claimBribes", + "crossChainRelay", "manageBribes" ]; diff --git a/contracts/tasks/crossChain.js b/contracts/tasks/crossChain.js new file mode 100644 index 0000000000..8b76650141 --- /dev/null +++ b/contracts/tasks/crossChain.js @@ -0,0 +1,213 @@ +//const { KeyValueStoreClient } = require("@openzeppelin/defender-sdk"); +const ethers = require("ethers"); +const { logTxDetails } = require("../utils/txLogger"); +const { api: cctpApi } = require("../utils/cctp"); + +const cctpOperationsConfig = async ({ + destinationChainSigner, + sourceChainProvider, + networkName, + cctpIntegrationContractAddress, + cctpIntegrationContractAddressDestination, +}) => { + const cctpIntegratorAbi = [ + "event TokensBridged(uint32 peerDomainID,address peerStrategy,address usdcToken,uint256 tokenAmount,uint256 maxFee,uint32 minFinalityThreshold,bytes hookData)", + "event MessageTransmitted(uint32 peerDomainID,address peerStrategy,uint32 minFinalityThreshold,bytes message)", + "function relay(bytes message, bytes attestation) external", + ]; + + const cctpIntegrationContractSource = new ethers.Contract( + cctpIntegrationContractAddress, + cctpIntegratorAbi, + sourceChainProvider + ); + const cctpIntegrationContractDestination = new ethers.Contract( + cctpIntegrationContractAddressDestination, + cctpIntegratorAbi, + destinationChainSigner + ); + + return { + networkName, + cctpIntegrationContractSource, + cctpIntegrationContractDestination, + }; +}; + +const fetchAttestation = async ({ transactionHash, cctpChainId }) => { + console.log( + `Fetching attestation for transaction hash: ${transactionHash} on cctp chain id: ${cctpChainId}` + ); + const response = await fetch( + `${cctpApi}/v2/messages/${cctpChainId}?transactionHash=${transactionHash}` + ); + if (!response.ok) { + throw new Error( + `Error fetching attestation code: ${ + response.status + } error: ${await response.text()}` + ); + } + const resultJson = await response.json(); + + if (resultJson.messages.length !== 1) { + throw new Error( + `Expected 1 attestation, got ${resultJson.messages.length}` + ); + } + + const message = resultJson.messages[0]; + const status = message.status; + if (status !== "complete") { + throw new Error(`Attestation is not complete, status: ${status}`); + } + + return { + attestation: message.attestation, + message: message.message, + status: "ok", + }; +}; + +// TokensBridged & MessageTransmitted are the 2 events that are emitted when a transaction is published to the CCTP contract +// One transaction containing such message can at most only contain one of these events +const fetchTxHashesFromCctpTransactions = async ({ + config, + blockLookback, + overrideBlock, + sourceChainProvider, +} = {}) => { + let resolvedFromBlock, resolvedToBlock; + if (overrideBlock) { + resolvedFromBlock = overrideBlock; + resolvedToBlock = overrideBlock; + } else { + const latestBlock = await sourceChainProvider.getBlockNumber(); + resolvedFromBlock = Math.max(latestBlock - blockLookback, 0); + resolvedToBlock = latestBlock; + } + + const cctpIntegrationContractSource = config.cctpIntegrationContractSource; + + const tokensBridgedTopic = + cctpIntegrationContractSource.interface.getEventTopic("TokensBridged"); + const messageTransmittedTopic = + cctpIntegrationContractSource.interface.getEventTopic("MessageTransmitted"); + + console.log( + `Fetching event logs from block ${resolvedFromBlock} to block ${resolvedToBlock}` + ); + const [eventLogsTokenBridged, eventLogsMessageTransmitted] = + await Promise.all([ + sourceChainProvider.getLogs({ + address: cctpIntegrationContractSource.address, + fromBlock: resolvedFromBlock, + toBlock: resolvedToBlock, + topics: [tokensBridgedTopic], + }), + sourceChainProvider.getLogs({ + address: cctpIntegrationContractSource.address, + fromBlock: resolvedFromBlock, + toBlock: resolvedToBlock, + topics: [messageTransmittedTopic], + }), + ]); + + // There should be no duplicates in the event logs, but still deduplicate to be safe + const possiblyDuplicatedTxHashes = [ + ...eventLogsTokenBridged, + ...eventLogsMessageTransmitted, + ].map((log) => log.transactionHash); + const allTxHashes = Array.from(new Set([...possiblyDuplicatedTxHashes])); + + console.log(`Found ${allTxHashes.length} transactions that emitted messages`); + return { allTxHashes }; +}; + +const processCctpBridgeTransactions = async ({ + block = undefined, + dryrun = false, + destinationChainSigner, + sourceChainProvider, + store, + networkName, + blockLookback, + cctpDestinationDomainId, + cctpSourceDomainId, + cctpIntegrationContractAddress, + cctpIntegrationContractAddressDestination, +}) => { + const config = await cctpOperationsConfig({ + destinationChainSigner, + sourceChainProvider, + networkName, + cctpIntegrationContractAddress, + cctpIntegrationContractAddressDestination, + }); + console.log( + `Fetching cctp messages posted on ${config.networkName} network.${ + block ? ` Only for block: ${block}` : " Looking at most recent blocks" + }` + ); + + const { allTxHashes } = await fetchTxHashesFromCctpTransactions({ + config, + overrideBlock: block, + sourceChainProvider, + blockLookback, + }); + for (const txHash of allTxHashes) { + const storeKey = `cctp_message_${txHash}`; + const storedValue = await store.get(storeKey); + + if (storedValue === "processed") { + console.log( + `Transaction with hash: ${txHash} has already been processed. Skipping...` + ); + continue; + } + + const { attestation, message, status } = await fetchAttestation({ + transactionHash: txHash, + cctpChainId: cctpSourceDomainId, + }); + if (status !== "ok") { + console.log( + `Attestation from tx hash: ${txHash} on cctp chain id: ${config.cctpSourceDomainId} is not attested yet, status: ${status}. Skipping...` + ); + } + + console.log( + `Attempting to relay attestation with tx hash: ${txHash} and message: ${message} to cctp chain id: ${cctpDestinationDomainId}` + ); + + if (dryrun) { + console.log( + `Dryrun: Would have relayed attestation with tx hash: ${txHash} to cctp chain id: ${cctpDestinationDomainId}` + ); + continue; + } + + const relayTx = await config.cctpIntegrationContractDestination.relay( + message, + attestation + ); + console.log( + `Relay transaction with hash ${relayTx.hash} sent to cctp chain id: ${cctpDestinationDomainId}` + ); + const receipt = await logTxDetails(relayTx, "CCTP relay"); + + // Final verification + if (receipt.status === 1) { + console.log("SUCCESS: Transaction executed successfully!"); + await store.put(storeKey, "processed"); + } else { + console.log("FAILURE: Transaction reverted!"); + throw new Error(`Transaction reverted - status: ${receipt.status}`); + } + } +}; + +module.exports = { + processCctpBridgeTransactions, +}; diff --git a/contracts/tasks/defender.js b/contracts/tasks/defender.js index 80d06f5b4d..5b929b4cb2 100644 --- a/contracts/tasks/defender.js +++ b/contracts/tasks/defender.js @@ -70,4 +70,5 @@ const updateAction = async ({ id, file }) => { module.exports = { setActionVars, updateAction, + getClient, }; diff --git a/contracts/tasks/tasks.js b/contracts/tasks/tasks.js index 2b82d83cfc..5b4daab063 100644 --- a/contracts/tasks/tasks.js +++ b/contracts/tasks/tasks.js @@ -145,6 +145,10 @@ const { } = require("./beaconTesting"); const { claimMerklRewards } = require("./merkl"); +const { processCctpBridgeTransactions } = require("./crossChain"); +const { keyValueStoreLocalClient } = require("../utils/defender"); +const { configuration } = require("../utils/cctp"); + const log = require("../utils/logger")("tasks"); // Environment tasks. @@ -1279,6 +1283,75 @@ task("stakeValidators").setAction(async (_, __, runSuper) => { return runSuper(); }); +/** + * This function relays the messages between mainnet and base networks. + * + * IMPORTANT!!! + * If possible please use the defender action and not local execution. The defender action stores into the cloud + * key-value store the transaction hashes that have already been relayed. Relaying the transaction via this task + * will make the defender relayer continuously fail relaying the transaction that has already been processed. + * If the action is ran every ~12 hours and looks back for ~1 day worth of blocks it might fail to run 2-3 times and + * then skip some pending transactions that would need relaying. + */ +task( + "relayCCTPMessage", + "Fetches CCTP attested Messages via Circle Gateway API and relays it to the integrator contract" +) + .addOptionalParam( + "block", + "Override the block number at which the message emission transaction happened", + undefined, + types.int + ) + .addOptionalParam( + "dryrun", + "Do not call verifyBalances on the strategy contract. Just log the params including the proofs", + false, + types.boolean + ) + .setAction(async (taskArgs) => { + const networkName = await getNetworkName(); + const storeFilePath = require("path").join( + __dirname, + "..", + `.localKeyValueStorage.${networkName}` + ); + + // This action only works with the Defender Relayer signer + const signer = await getDefenderSigner(); + const store = keyValueStoreLocalClient({ _storePath: storeFilePath }); + + const isMainnet = networkName === "mainnet"; + const isBase = networkName === "base"; + + let config; + if (isMainnet) { + config = configuration.mainnetBaseMorpho.mainnet; + } else if (isBase) { + config = configuration.mainnetBaseMorpho.base; + } else { + throw new Error(`Unsupported network name: ${networkName}`); + } + + await processCctpBridgeTransactions({ + ...taskArgs, + destinationChainSigner: signer, + sourceChainProvider: ethers.provider, + store, + networkName, + blockLookback: config.blockLookback, + cctpDestinationDomainId: config.cctpDestinationDomainId, + cctpSourceDomainId: config.cctpSourceDomainId, + cctpIntegrationContractAddress: config.cctpIntegrationContractAddress, + cctpIntegrationContractAddressDestination: + config.cctpIntegrationContractAddressDestination, + }); + }); + +task("relayCCTPMessage").setAction(async (_, __, runSuper) => { + return runSuper(); +}); + subtask("exitValidator", "Starts the exit process from a validator") .addParam( "pubkey", diff --git a/contracts/test/_fixture-base.js b/contracts/test/_fixture-base.js index 028e10d850..88a18314a5 100644 --- a/contracts/test/_fixture-base.js +++ b/contracts/test/_fixture-base.js @@ -1,13 +1,14 @@ const hre = require("hardhat"); const { ethers } = hre; const mocha = require("mocha"); -const { isFork, isBaseFork, oethUnits } = require("./helpers"); +const { isFork, isBaseFork, oethUnits, usdcUnits } = require("./helpers"); const { impersonateAndFund, impersonateAccount } = require("../utils/signers"); const { nodeRevert, nodeSnapshot } = require("./_fixture"); const { deployWithConfirmation } = require("../utils/deploy"); const addresses = require("../utils/addresses"); const erc20Abi = require("./abi/erc20.json"); const hhHelpers = require("@nomicfoundation/hardhat-network-helpers"); +const { getCreate2ProxyAddress } = require("../deploy/deployActions"); const log = require("../utils/logger")("test:fixtures-base"); @@ -150,11 +151,12 @@ const defaultFixture = async () => { ); // WETH - let weth, aero; + let weth, aero, usdc; if (isFork) { weth = await ethers.getContractAt("IWETH9", addresses.base.WETH); aero = await ethers.getContractAt(erc20Abi, addresses.base.AERO); + usdc = await ethers.getContractAt(erc20Abi, addresses.base.USDC); } else { weth = await ethers.getContract("MockWETH"); aero = await ethers.getContract("MockAero"); @@ -275,8 +277,9 @@ const defaultFixture = async () => { aerodromeAmoStrategy, curveAMOStrategy, - // WETH + // Tokens weth, + usdc, // Signers governor, @@ -335,6 +338,58 @@ const bridgeHelperModuleFixture = deployments.createFixture(async () => { }; }); +const crossChainFixture = deployments.createFixture(async () => { + const fixture = await defaultBaseFixture(); + const crossChainStrategyProxyAddress = await getCreate2ProxyAddress( + "CrossChainStrategyProxy" + ); + const crossChainRemoteStrategy = await ethers.getContractAt( + "CrossChainRemoteStrategy", + crossChainStrategyProxyAddress + ); + + await deployWithConfirmation("CCTPMessageTransmitterMock2", [ + fixture.usdc.address, + ]); + const mockMessageTransmitter = await ethers.getContract( + "CCTPMessageTransmitterMock2" + ); + await deployWithConfirmation("CCTPTokenMessengerMock", [ + fixture.usdc.address, + mockMessageTransmitter.address, + ]); + const mockTokenMessenger = await ethers.getContract("CCTPTokenMessengerMock"); + await mockMessageTransmitter.setCCTPTokenMessenger( + addresses.CCTPTokenMessengerV2 + ); + + const usdcMinter = await impersonateAndFund( + "0x2230393EDAD0299b7E7B59F20AA856cD1bEd52e1" + ); + const usdcContract = await ethers.getContractAt( + [ + "function mint(address to, uint256 amount) external", + "function configureMinter(address minter, uint256 minterAmount) external", + ], + addresses.base.USDC + ); + + await usdcContract + .connect(usdcMinter) + .configureMinter(fixture.rafael.address, usdcUnits("100000000")); + + await usdcContract + .connect(fixture.rafael) + .mint(fixture.rafael.address, usdcUnits("1000000")); + + return { + ...fixture, + crossChainRemoteStrategy, + mockMessageTransmitter, + mockTokenMessenger, + }; +}); + mocha.after(async () => { if (snapshotId) { await nodeRevert(snapshotId); @@ -347,4 +402,5 @@ module.exports = { MINTER_ROLE, BURNER_ROLE, bridgeHelperModuleFixture, + crossChainFixture, }; diff --git a/contracts/test/_fixture.js b/contracts/test/_fixture.js index 8173f8e4f8..592c16bf1f 100644 --- a/contracts/test/_fixture.js +++ b/contracts/test/_fixture.js @@ -24,6 +24,7 @@ const { getOracleAddresses, oethUnits, ousdUnits, + usdcUnits, units, isTest, isFork, @@ -31,6 +32,7 @@ const { isHoleskyFork, } = require("./helpers"); const { hardhatSetBalance, setERC20TokenBalance } = require("./_fund"); +const { getCreate2ProxyAddress } = require("../deploy/deployActions"); const usdsAbi = require("./abi/usds.json").abi; const usdtAbi = require("./abi/usdt.json").abi; @@ -2610,6 +2612,62 @@ async function instantRebaseVaultFixture() { return fixture; } +// Unit test cross chain fixture where both contracts are deployed on the same chain for the +// purposes of unit testing +async function crossChainFixtureUnit() { + const fixture = await defaultFixture(); + const { governor, vault } = fixture; + + const crossChainMasterStrategyProxy = await ethers.getContract( + "CrossChainMasterStrategyProxy" + ); + const crossChainRemoteStrategyProxy = await ethers.getContract( + "CrossChainRemoteStrategyProxy" + ); + + const cCrossChainMasterStrategy = await ethers.getContractAt( + "CrossChainMasterStrategy", + crossChainMasterStrategyProxy.address + ); + + const cCrossChainRemoteStrategy = await ethers.getContractAt( + "CrossChainRemoteStrategy", + crossChainRemoteStrategyProxy.address + ); + + await vault + .connect(governor) + .approveStrategy(cCrossChainMasterStrategy.address); + + const messageTransmitter = await ethers.getContract( + "CCTPMessageTransmitterMock" + ); + const tokenMessenger = await ethers.getContract("CCTPTokenMessengerMock"); + + // In unit test environment it is not the off-chain defender action that calls the "relay" + // to relay the messages but rather the message transmitter. + await cCrossChainMasterStrategy + .connect(governor) + .setOperator(messageTransmitter.address); + await cCrossChainRemoteStrategy + .connect(governor) + .setOperator(messageTransmitter.address); + + const morphoVault = await ethers.getContract("MockERC4626Vault"); + + // Impersonate the OUSD Vault + fixture.vaultSigner = await impersonateAndFund(vault.address); + + return { + ...fixture, + crossChainMasterStrategy: cCrossChainMasterStrategy, + crossChainRemoteStrategy: cCrossChainRemoteStrategy, + messageTransmitter: messageTransmitter, + tokenMessenger: tokenMessenger, + morphoVault: morphoVault, + }; +} + /** * Configure a reborn hack attack */ @@ -2943,6 +3001,46 @@ async function enableExecutionLayerGeneralPurposeRequests() { }; } +async function crossChainFixture() { + const fixture = await defaultFixture(); + + const crossChainStrategyProxyAddress = await getCreate2ProxyAddress( + "CrossChainStrategyProxy" + ); + const cCrossChainMasterStrategy = await ethers.getContractAt( + "CrossChainMasterStrategy", + crossChainStrategyProxyAddress + ); + + await deployWithConfirmation("CCTPMessageTransmitterMock2", [ + fixture.usdc.address, + ]); + const mockMessageTransmitter = await ethers.getContract( + "CCTPMessageTransmitterMock2" + ); + await deployWithConfirmation("CCTPTokenMessengerMock", [ + fixture.usdc.address, + mockMessageTransmitter.address, + ]); + const mockTokenMessenger = await ethers.getContract("CCTPTokenMessengerMock"); + await mockMessageTransmitter.setCCTPTokenMessenger( + addresses.CCTPTokenMessengerV2 + ); + + await setERC20TokenBalance( + fixture.matt.address, + fixture.usdc, + usdcUnits("1000000") + ); + + return { + ...fixture, + crossChainMasterStrategy: cCrossChainMasterStrategy, + mockMessageTransmitter: mockMessageTransmitter, + mockTokenMessenger: mockTokenMessenger, + }; +} + /** * A fixture is a setup function that is run only the first time it's invoked. On subsequent invocations, * Hardhat will reset the state of the network to what it was at the point after the fixture was initially executed. @@ -3036,4 +3134,6 @@ module.exports = { bridgeHelperModuleFixture, beaconChainFixture, claimRewardsModuleFixture, + crossChainFixtureUnit, + crossChainFixture, }; diff --git a/contracts/test/strategies/crosschain/_crosschain-helpers.js b/contracts/test/strategies/crosschain/_crosschain-helpers.js new file mode 100644 index 0000000000..b9d5afbaca --- /dev/null +++ b/contracts/test/strategies/crosschain/_crosschain-helpers.js @@ -0,0 +1,276 @@ +const { expect } = require("chai"); + +const addresses = require("../../../utils/addresses"); +const { replaceContractAt } = require("../../../utils/hardhat"); +const { setStorageAt } = require("@nomicfoundation/hardhat-network-helpers"); + +const DEPOSIT_FOR_BURN_EVENT_TOPIC = + "0x0c8c1cbdc5190613ebd485511d4e2812cfa45eecb79d845893331fedad5130a5"; +const MESSAGE_SENT_EVENT_TOPIC = + "0x8c5261668696ce22758910d05bab8f186d6eb247ceac2af2e82c7dc17669b036"; + +const emptyByte = "0000"; +const empty2Bytes = emptyByte.repeat(2); +const empty4Bytes = emptyByte.repeat(4); +const empty16Bytes = empty4Bytes.repeat(4); +const empty18Bytes = `${empty2Bytes}${empty16Bytes}`; +const empty20Bytes = empty4Bytes.repeat(5); + +const REMOTE_STRATEGY_BALANCE_SLOT = 207; + +const decodeDepositForBurnEvent = (event) => { + const [ + amount, + mintRecipient, + destinationDomain, + destinationTokenMessenger, + destinationCaller, + maxFee, + hookData, + ] = ethers.utils.defaultAbiCoder.decode( + ["uint256", "address", "uint32", "address", "address", "uint256", "bytes"], + event.data + ); + + const [burnToken] = ethers.utils.defaultAbiCoder.decode( + ["address"], + event.topics[1] + ); + const [depositer] = ethers.utils.defaultAbiCoder.decode( + ["address"], + event.topics[2] + ); + const [minFinalityThreshold] = ethers.utils.defaultAbiCoder.decode( + ["uint256"], + event.topics[3] + ); + + return { + amount, + mintRecipient, + destinationDomain, + destinationTokenMessenger, + destinationCaller, + maxFee, + hookData, + burnToken, + depositer, + minFinalityThreshold, + }; +}; + +const decodeMessageSentEvent = (event) => { + const evData = event.data.slice(130); // ignore first two slots along with 0x prefix + + const version = ethers.BigNumber.from(`0x${evData.slice(0, 8)}`); + const sourceDomain = ethers.BigNumber.from(`0x${evData.slice(8, 16)}`); + const desinationDomain = ethers.BigNumber.from(`0x${evData.slice(16, 24)}`); + // Ignore empty nonce from 24 to 88 + const [sender, recipient, destinationCaller] = + ethers.utils.defaultAbiCoder.decode( + ["address", "address", "address"], + `0x${evData.slice(88, 280)}` + ); + const minFinalityThreshold = ethers.BigNumber.from( + `0x${evData.slice(280, 288)}` + ); + // Ignore empty threshold from 288 to 296 + const endIndex = evData.endsWith("00000000") + ? evData.length - 8 + : evData.length; + const payload = `0x${evData.slice(296, endIndex)}`; + + return { + version, + sourceDomain, + desinationDomain, + sender, + recipient, + destinationCaller, + minFinalityThreshold, + payload, + }; +}; + +const encodeDepositMessageBody = (nonce, amount) => { + const encodedPayload = ethers.utils.defaultAbiCoder.encode( + ["uint64", "uint256"], + [nonce, amount] + ); + return `0x000003f200000001${encodedPayload.slice(2)}`; +}; + +const encodeWithdrawMessageBody = (nonce, amount) => { + const encodedPayload = ethers.utils.defaultAbiCoder.encode( + ["uint64", "uint256"], + [nonce, amount] + ); + return `0x000003f200000002${encodedPayload.slice(2)}`; +}; + +const decodeDepositOrWithdrawMessage = (message) => { + message = message.slice(2); // Ignore 0x prefix + + const originMessageVersion = ethers.BigNumber.from( + `0x${message.slice(0, 8)}` + ); + const messageType = ethers.BigNumber.from(`0x${message.slice(8, 16)}`); + expect(originMessageVersion).to.eq(1010); + + const [nonce, amount] = ethers.utils.defaultAbiCoder.decode( + ["uint64", "uint256"], + `0x${message.slice(16)}` + ); + + return { + messageType, + nonce, + amount, + }; +}; + +const encodeCCTPMessage = ( + sourceDomain, + sender, + recipient, + messageBody, + version = 1 +) => { + const versionStr = version.toString(16).padStart(8, "0"); + const sourceDomainStr = sourceDomain.toString(16).padStart(8, "0"); + const senderStr = sender.replace("0x", "").toLowerCase().padStart(64, "0"); + const recipientStr = recipient + .replace("0x", "") + .toLowerCase() + .padStart(64, "0"); + const messageBodyStr = messageBody.slice(2); + return `0x${versionStr}${sourceDomainStr}${empty18Bytes}${senderStr}${recipientStr}${empty20Bytes}${messageBodyStr}`; +}; + +const encodeBurnMessageBody = ( + sender, + recipient, + burnToken, + amount, + hookData +) => { + const senderEncoded = ethers.utils.defaultAbiCoder + .encode(["address"], [sender]) + .slice(2); + const recipientEncoded = ethers.utils.defaultAbiCoder + .encode(["address"], [recipient]) + .slice(2); + const burnTokenEncoded = ethers.utils.defaultAbiCoder + .encode(["address"], [burnToken]) + .slice(2); + const amountEncoded = ethers.utils.defaultAbiCoder + .encode(["uint256"], [amount]) + .slice(2); + const encodedHookData = hookData.slice(2); + return `0x00000001${burnTokenEncoded}${recipientEncoded}${amountEncoded}${senderEncoded}${empty16Bytes.repeat( + 3 + )}${encodedHookData}`; +}; +const decodeBurnMessageBody = (message) => { + message = message.slice(2); // Ignore 0x prefix + + const version = ethers.BigNumber.from(`0x${message.slice(0, 8)}`); + expect(version).to.eq(1); + const [burnToken, recipient, amount, sender] = + ethers.utils.defaultAbiCoder.decode( + ["address", "address", "uint256", "address"], + `0x${message.slice(8, 264)}` + ); + + const hookData = `0x${message.slice(456)}`; // Ignore 0x prefix and following 96 bytes + return { version, burnToken, recipient, amount, sender, hookData }; +}; + +const encodeBalanceCheckMessageBody = ( + nonce, + balance, + transferConfirmation, + timestamp +) => { + const resolvedTimestamp = timestamp ?? Math.floor(Date.now() / 1000); + const encodedPayload = ethers.utils.defaultAbiCoder.encode( + ["uint64", "uint256", "bool", "uint256"], + [nonce, balance, transferConfirmation, resolvedTimestamp] + ); + + // const version = 1010; // ORIGIN_MESSAGE_VERSION + // const messageType = 3; // BALANCE_CHECK_MESSAGE + return `0x000003f200000003${encodedPayload.slice(2)}`; +}; + +const decodeBalanceCheckMessageBody = (message) => { + message = message.slice(2); // Ignore 0x prefix + const version = ethers.BigNumber.from(`0x${message.slice(0, 8)}`); + const messageType = ethers.BigNumber.from(`0x${message.slice(8, 16)}`); + expect(version).to.eq(1010); + expect(messageType).to.eq(3); + const [nonce, balance, transferConfirmation, timestamp] = + ethers.utils.defaultAbiCoder.decode( + ["uint64", "uint256", "bool", "uint256"], + `0x${message.slice(16)}` + ); + return { + version, + messageType, + nonce, + balance, + transferConfirmation, + timestamp, + }; +}; + +const replaceMessageTransmitter = async () => { + const mockMessageTransmitter = await ethers.getContract( + "CCTPMessageTransmitterMock2" + ); + await replaceContractAt( + addresses.CCTPMessageTransmitterV2, + mockMessageTransmitter + ); + const replacedTransmitter = await ethers.getContractAt( + "CCTPMessageTransmitterMock2", + addresses.CCTPMessageTransmitterV2 + ); + await replacedTransmitter.setCCTPTokenMessenger( + addresses.CCTPTokenMessengerV2 + ); + + return replacedTransmitter; +}; + +const setRemoteStrategyBalance = async (strategy, balance) => { + await setStorageAt( + strategy.address, + `0x${REMOTE_STRATEGY_BALANCE_SLOT.toString(16)}`, + balance.toHexString() + ); +}; + +module.exports = { + DEPOSIT_FOR_BURN_EVENT_TOPIC, + MESSAGE_SENT_EVENT_TOPIC, + emptyByte, + empty2Bytes, + empty4Bytes, + empty16Bytes, + empty18Bytes, + empty20Bytes, + REMOTE_STRATEGY_BALANCE_SLOT, + setRemoteStrategyBalance, + decodeDepositForBurnEvent, + decodeMessageSentEvent, + decodeDepositOrWithdrawMessage, + encodeCCTPMessage, + encodeDepositMessageBody, + encodeWithdrawMessageBody, + encodeBurnMessageBody, + decodeBurnMessageBody, + encodeBalanceCheckMessageBody, + decodeBalanceCheckMessageBody, + replaceMessageTransmitter, +}; diff --git a/contracts/test/strategies/crosschain/cross-chain-strategy.js b/contracts/test/strategies/crosschain/cross-chain-strategy.js new file mode 100644 index 0000000000..6815d238b2 --- /dev/null +++ b/contracts/test/strategies/crosschain/cross-chain-strategy.js @@ -0,0 +1,829 @@ +const { expect } = require("chai"); +const { isCI, ousdUnits } = require("../../helpers"); +const { + createFixtureLoader, + crossChainFixtureUnit, +} = require("../../_fixture"); +const { setERC20TokenBalance } = require("../../_fund"); +const { units, usdcUnits } = require("../../helpers"); +const { impersonateAndFund } = require("../../../utils/signers"); +const { encodeBalanceCheckMessageBody } = require("./_crosschain-helpers"); + +const loadFixture = createFixtureLoader(crossChainFixtureUnit); +const DAY_IN_SECONDS = 86400; + +describe("ForkTest: CrossChainRemoteStrategy", function () { + this.timeout(0); + + // Retry up to 3 times on CI + this.retries(isCI ? 3 : 0); + + let fixture, + josh, + governor, + usdc, + crossChainRemoteStrategy, + crossChainMasterStrategy, + vault, + initialVaultValue; + beforeEach(async () => { + fixture = await loadFixture(); + josh = fixture.josh; + governor = fixture.governor; + usdc = fixture.usdc; + crossChainRemoteStrategy = fixture.crossChainRemoteStrategy; + crossChainMasterStrategy = fixture.crossChainMasterStrategy; + vault = fixture.vault; + initialVaultValue = await vault.totalValue(); + }); + + const mint = async (amount) => { + await usdc.connect(josh).approve(vault.address, await units(amount, usdc)); + await vault.connect(josh).mint(usdc.address, await units(amount, usdc), 0); + }; + + const depositToMasterStrategy = async (amount) => { + await vault + .connect(governor) + .depositToStrategy( + crossChainMasterStrategy.address, + [usdc.address], + [await units(amount, usdc)] + ); + }; + + // Even though remote strategy has funds withdrawn the message initiates on master strategy + const withdrawFromRemoteStrategy = (amount) => { + return vault + .connect(governor) + .withdrawFromStrategy( + crossChainMasterStrategy.address, + [usdc.address], + [usdcUnits(amount, usdc)] + ); + }; + + const withdrawAllFromRemoteStrategy = () => { + return vault + .connect(governor) + .withdrawAllFromStrategy(crossChainMasterStrategy.address); + }; + + // Withdraws from the remote strategy directly, without going through the master strategy + const directWithdrawFromRemoteStrategy = async (amount) => { + await crossChainRemoteStrategy + .connect(governor) + .withdraw( + crossChainRemoteStrategy.address, + usdc.address, + await units(amount, usdc) + ); + }; + + // Withdraws all the remote strategy directly, without going through the master strategy + const directWithdrawAllFromRemoteStrategy = async () => { + await crossChainRemoteStrategy.connect(governor).withdrawAll(); + }; + + const sendBalanceUpdateToMaster = async () => { + await crossChainRemoteStrategy.connect(governor).sendBalanceUpdate(); + }; + + // Checks the diff in the total expected value in the vault + // (plus accompanying strategy value) + const assertVaultTotalValue = async (amountExpected) => { + const amountToCompare = + typeof amountExpected === "string" + ? ousdUnits(amountExpected) + : amountExpected; + + await expect((await vault.totalValue()).sub(initialVaultValue)).to.eq( + amountToCompare + ); + }; + + const mintToMasterDepositToRemote = async (amount) => { + const { messageTransmitter, morphoVault } = fixture; + const amountBn = await units(amount, usdc); + + await mint(amount); + const vaultDiffAfterMint = (await vault.totalValue()).sub( + initialVaultValue + ); + + const remoteBalanceBefore = await crossChainRemoteStrategy.checkBalance( + usdc.address + ); + const remoteBalanceRecByMasterBefore = + await crossChainMasterStrategy.remoteStrategyBalance(); + const messagesinQueueBefore = await messageTransmitter.messagesInQueue(); + await assertVaultTotalValue(vaultDiffAfterMint); + + await depositToMasterStrategy(amount); + await expect(await messageTransmitter.messagesInQueue()).to.eq( + messagesinQueueBefore + 1 + ); + await assertVaultTotalValue(vaultDiffAfterMint); + + // Simulate off chain component processing deposit message + await expect(messageTransmitter.processFront()) + .to.emit(crossChainRemoteStrategy, "Deposit") + .withArgs(usdc.address, morphoVault.address, amountBn); + + await assertVaultTotalValue(vaultDiffAfterMint); + // 1 message is processed, another one (checkBalance) has entered the queue + await expect(await messageTransmitter.messagesInQueue()).to.eq( + messagesinQueueBefore + 1 + ); + await expect( + await morphoVault.balanceOf(crossChainRemoteStrategy.address) + ).to.eq(remoteBalanceBefore.add(amountBn)); + + // Simulate off chain component processing checkBalance message + await expect(messageTransmitter.processFront()) + .to.emit(crossChainMasterStrategy, "RemoteStrategyBalanceUpdated") + .withArgs(remoteBalanceBefore.add(amountBn)); + + await expect(await messageTransmitter.messagesInQueue()).to.eq( + messagesinQueueBefore + ); + await assertVaultTotalValue(vaultDiffAfterMint); + await expect(await crossChainMasterStrategy.remoteStrategyBalance()).to.eq( + remoteBalanceRecByMasterBefore.add(amountBn) + ); + }; + + const withdrawFromRemoteToVault = async (amount, expectWithdrawalEvent) => { + const { messageTransmitter, morphoVault } = fixture; + const amountBn = await units(amount, usdc); + const remoteBalanceBefore = await crossChainRemoteStrategy.checkBalance( + usdc.address + ); + const remoteBalanceRecByMasterBefore = + await crossChainMasterStrategy.remoteStrategyBalance(); + + const messagesinQueueBefore = await messageTransmitter.messagesInQueue(); + + await withdrawFromRemoteStrategy(amount); + await expect(await messageTransmitter.messagesInQueue()).to.eq( + messagesinQueueBefore + 1 + ); + + if (expectWithdrawalEvent) { + await expect(messageTransmitter.processFront()) + .to.emit(crossChainRemoteStrategy, "Withdrawal") + .withArgs(usdc.address, morphoVault.address, amountBn) + .to.emit(crossChainRemoteStrategy, "TokensBridged"); + } else { + await expect(messageTransmitter.processFront()).to.emit( + crossChainRemoteStrategy, + "TokensBridged" + ); + } + + await expect(await messageTransmitter.messagesInQueue()).to.eq( + messagesinQueueBefore + 1 + ); + + // master strategy still has the old value fo the remote strategy balance + await expect(await crossChainMasterStrategy.remoteStrategyBalance()).to.eq( + remoteBalanceRecByMasterBefore + ); + + const remoteBalanceAfter = remoteBalanceBefore - amountBn; + + await expect( + await crossChainRemoteStrategy.checkBalance(usdc.address) + ).to.eq(remoteBalanceAfter); + + // Simulate off chain component processing checkBalance message + await expect(messageTransmitter.processFront()) + .to.emit(crossChainMasterStrategy, "RemoteStrategyBalanceUpdated") + .withArgs(remoteBalanceAfter); + + await expect(await crossChainMasterStrategy.remoteStrategyBalance()).to.eq( + remoteBalanceAfter + ); + }; + + it("Should mint USDC to master strategy, transfer to remote and update balance", async function () { + const { morphoVault } = fixture; + await assertVaultTotalValue("0"); + await expect(await morphoVault.totalAssets()).to.eq(await units("0", usdc)); + + await mintToMasterDepositToRemote("1000"); + await assertVaultTotalValue("1000"); + + await expect(await morphoVault.totalAssets()).to.eq( + await units("1000", usdc) + ); + }); + + it("Should be able to withdraw from the remote strategy", async function () { + const { morphoVault } = fixture; + await mintToMasterDepositToRemote("1000"); + await assertVaultTotalValue("1000"); + + await expect(await morphoVault.totalAssets()).to.eq( + await units("1000", usdc) + ); + await withdrawFromRemoteToVault("500", true); + await assertVaultTotalValue("1000"); + }); + + it("Should be able to direct withdraw from the remote strategy directly and collect to master", async function () { + const { morphoVault } = fixture; + await mintToMasterDepositToRemote("1000"); + await assertVaultTotalValue("1000"); + + await expect(await morphoVault.totalAssets()).to.eq( + await units("1000", usdc) + ); + await directWithdrawFromRemoteStrategy("500"); + await assertVaultTotalValue("1000"); + + // 500 has been withdrawn from the Morpho vault but still remains on the + // remote strategy + await expect( + await crossChainRemoteStrategy.checkBalance(usdc.address) + ).to.eq(await units("1000", usdc)); + + // Next withdraw should not withdraw any additional funds from Morpho and just send + // 450 USDC to the master. + await withdrawFromRemoteToVault("450", false); + + await assertVaultTotalValue("1000"); + // The remote strategy should have 500 USDC in Morpho vault and 50 USDC on the contract + await expect( + await crossChainRemoteStrategy.checkBalance(usdc.address) + ).to.eq(await units("550", usdc)); + await expect(await usdc.balanceOf(crossChainRemoteStrategy.address)).to.eq( + await units("50", usdc) + ); + }); + + it("Should be able to direct withdraw from the remote strategy directly and withdrawing More from Morpho when collecting to the master", async function () { + const { morphoVault } = fixture; + await mintToMasterDepositToRemote("1000"); + await assertVaultTotalValue("1000"); + + await expect(await morphoVault.totalAssets()).to.eq( + await units("1000", usdc) + ); + await directWithdrawFromRemoteStrategy("500"); + await assertVaultTotalValue("1000"); + + // 500 has been withdrawn from the Morpho vault but still remains on the + // remote strategy + await expect( + await crossChainRemoteStrategy.checkBalance(usdc.address) + ).to.eq(await units("1000", usdc)); + + // Next withdraw should withdraw 50 additional funds and send them with existing + // 500 USDC to the master. + await withdrawFromRemoteToVault("550", false); + + await assertVaultTotalValue("1000"); + // The remote strategy should have 500 USDC in Morpho vault and 50 USDC on the contract + await expect( + await crossChainRemoteStrategy.checkBalance(usdc.address) + ).to.eq(await units("450", usdc)); + await expect(await usdc.balanceOf(crossChainRemoteStrategy.address)).to.eq( + await units("0", usdc) + ); + }); + + it("Should fail when a withdrawal too large is requested", async function () { + const { morphoVault } = fixture; + await mintToMasterDepositToRemote("1000"); + await assertVaultTotalValue("1000"); + + await expect(await morphoVault.totalAssets()).to.eq( + await units("1000", usdc) + ); + + // Master strategy should prevent withdrawing more than is available in the remote strategy + await expect(withdrawFromRemoteStrategy("1001")).to.be.revertedWith( + "Withdraw amount exceeds remote strategy balance" + ); + + await assertVaultTotalValue("1000"); + }); + + it("Should be able to direct withdraw all from the remote strategy directly and collect to master", async function () { + const { morphoVault, messageTransmitter } = fixture; + await mintToMasterDepositToRemote("1000"); + await assertVaultTotalValue("1000"); + + await expect(await morphoVault.totalAssets()).to.eq( + await units("1000", usdc) + ); + await directWithdrawAllFromRemoteStrategy(); + await assertVaultTotalValue("1000"); + + // All has been withdrawn from the Morpho vault but still remains on the + // remote strategy + await expect( + await crossChainRemoteStrategy.checkBalance(usdc.address) + ).to.eq(await units("1000", usdc)); + + await withdrawFromRemoteStrategy("1000"); + await expect(messageTransmitter.processFront()).not.to.emit( + crossChainRemoteStrategy, + "WithdrawUnderlyingFailed" + ); + await expect(messageTransmitter.processFront()) + .to.emit(crossChainMasterStrategy, "RemoteStrategyBalanceUpdated") + .withArgs(await units("0", usdc)); + + await assertVaultTotalValue("1000"); + await expect( + await crossChainRemoteStrategy.checkBalance(usdc.address) + ).to.eq(await units("0", usdc)); + }); + + it("Should fail when a withdrawal too large is requested on the remote strategy", async function () { + const { messageTransmitter } = fixture; + const remoteStrategySigner = await impersonateAndFund( + crossChainRemoteStrategy.address + ); + + await mintToMasterDepositToRemote("1000"); + await assertVaultTotalValue("1000"); + + await directWithdrawFromRemoteStrategy("10"); + + // Trick the remote strategy into thinking it has 10 USDC more than it actually does + await usdc + .connect(remoteStrategySigner) + .transfer(vault.address, await units("10", usdc)); + // Vault has 10 USDC more & Master strategy still thinks it has 1000 USDC + await assertVaultTotalValue("1010"); + + // This step should fail because the remote strategy no longer holds 1000 USDC + await withdrawFromRemoteStrategy("1000"); + + // Process on remote strategy + await expect(messageTransmitter.processFront()) + .to.emit(crossChainRemoteStrategy, "WithdrawalFailed") + .withArgs(await units("1000", usdc), await units("0", usdc)); + + // Process on master strategy + // This event doesn't get triggerred as the master strategy considers the balance check update + // as a race condition, and is exoecting an "on TokenReceived " to be called instead + + // which also causes the master strategy not to update the balance of the remote strategy + await expect(messageTransmitter.processFront()) + .to.emit(crossChainMasterStrategy, "RemoteStrategyBalanceUpdated") + .withArgs(await units("990", usdc)); + + await expect(await messageTransmitter.messagesInQueue()).to.eq(0); + + await expect( + await crossChainRemoteStrategy.checkBalance(usdc.address) + ).to.eq(await units("990", usdc)); + + await expect( + await crossChainMasterStrategy.checkBalance(usdc.address) + ).to.eq(await units("990", usdc)); + }); + + it("Should be able to process withdrawal & checkBalance on Remote strategy and in reverse order on master strategy", async function () { + const { messageTransmitter } = fixture; + + await mintToMasterDepositToRemote("1000"); + + await withdrawFromRemoteStrategy("300"); + + // Process on remote strategy + await expect(messageTransmitter.processFront()); + // This sends a second balanceUpdate message to the CCTP bridge + await sendBalanceUpdateToMaster(); + + await expect(await messageTransmitter.messagesInQueue()).to.eq(2); + + // first process the standalone balanceCheck message - meaning we process messages out of order + // this message should be ignored on Master + await expect(messageTransmitter.processBack()).to.not.emit( + crossChainMasterStrategy, + "RemoteStrategyBalanceUpdated" + ); + + // Second balance update message is part of the deposit / withdrawal process and should be processed + await expect(messageTransmitter.processFront()) + .to.emit(crossChainMasterStrategy, "RemoteStrategyBalanceUpdated") + .withArgs(await units("700", usdc)); + + await expect(await messageTransmitter.messagesInQueue()).to.eq(0); + + await expect( + await crossChainRemoteStrategy.checkBalance(usdc.address) + ).to.eq(await units("700", usdc)); + + await expect( + await crossChainMasterStrategy.checkBalance(usdc.address) + ).to.eq(await units("700", usdc)); + + await assertVaultTotalValue("1000"); + }); + + it("Should emit a BalanceCheckIgnored event if balance update message is too old", async function () { + const { messageTransmitter, crossChainMasterStrategy } = fixture; + await sendBalanceUpdateToMaster(); + const latestBlock = await ethers.provider.getBlock("latest"); + const staleTimestamp = latestBlock.timestamp - DAY_IN_SECONDS - 1; + await expect( + messageTransmitter.processFrontOverrideMessageBody( + encodeBalanceCheckMessageBody("0", "1000", true, staleTimestamp) + ) + ) + .to.emit(crossChainMasterStrategy, "BalanceCheckIgnored") + .withArgs(0, staleTimestamp, true); + + await expect(await messageTransmitter.messagesInQueue()).to.eq(0); + }); + + it("Should emit a RemoteStrategyBalanceUpdated event if balance update message is just in time", async function () { + const { messageTransmitter, crossChainMasterStrategy } = fixture; + await sendBalanceUpdateToMaster(); + const latestBlock = await ethers.provider.getBlock("latest"); + const staleTimestamp = latestBlock.timestamp - DAY_IN_SECONDS + 10; + await expect( + messageTransmitter.processFrontOverrideMessageBody( + encodeBalanceCheckMessageBody("0", "1000", true, staleTimestamp) + ) + ).to.emit(crossChainMasterStrategy, "RemoteStrategyBalanceUpdated"); + + await expect(await messageTransmitter.messagesInQueue()).to.eq(0); + }); + + it("Should fail on deposit if a previous one has not completed", async function () { + await mint("100"); + await depositToMasterStrategy("50"); + + await expect(depositToMasterStrategy("50")).to.be.revertedWith( + "Unexpected pending amount" + ); + }); + + it("Should fail to withdraw if a previous deposit has not completed", async function () { + await mintToMasterDepositToRemote("40"); + await mint("50"); + await depositToMasterStrategy("50"); + + await expect(withdrawFromRemoteStrategy("40")).to.be.revertedWith( + "Pending token transfer" + ); + }); + + it("Should fail on deposit if a previous withdrawal has not completed", async function () { + await mintToMasterDepositToRemote("230"); + await withdrawFromRemoteStrategy("50"); + + await mint("30"); + await expect(depositToMasterStrategy("30")).to.be.revertedWith( + "Pending token transfer" + ); + }); + + it("Should fail to withdraw if a previous withdrawal has not completed", async function () { + await mintToMasterDepositToRemote("230"); + await withdrawFromRemoteStrategy("50"); + + await expect(withdrawFromRemoteStrategy("40")).to.be.revertedWith( + "Pending token transfer" + ); + }); + + it("Should fail to deposit non usdc asset", async function () { + const { ousd, vault, vaultSigner, josh, crossChainMasterStrategy } = + fixture; + await mint("10"); + await ousd.connect(josh).transfer(vault.address, await units("10", ousd)); + await expect( + crossChainMasterStrategy + .connect(vaultSigner) + .deposit(ousd.address, await units("10", ousd)) + ).to.be.revertedWith("Unsupported asset"); + }); + + it("Should not deposit less than 1 USDC using normal depositAll approach", async function () { + await mint("1"); + // DepositAll function doesn't call _deposit when amount is less than 1 USDC + await expect( + vault + .connect(governor) + .depositToStrategy( + crossChainMasterStrategy.address, + [usdc.address], + [await units("0.5", usdc)] + ) + ).not.to.emit(crossChainMasterStrategy, "Deposit"); + }); + + it("Should revert when depositing less than 1 USDC", async function () { + const { usdc, vaultSigner, crossChainMasterStrategy } = fixture; + await mint("10"); + await expect( + crossChainMasterStrategy + .connect(vaultSigner) + .deposit(usdc.address, await units("0.5", usdc)) + ).to.be.revertedWith("Deposit amount too small"); + }); + + it("Should not calling withdrawAll if one withdraw is pending", async function () { + await mintToMasterDepositToRemote("10"); + await withdrawFromRemoteStrategy("5"); + + // 1 withdraw is pending, the withdrawAll won't issue another withdraw, but wont fail as well + await expect(withdrawAllFromRemoteStrategy()).to.not.emit( + crossChainMasterStrategy, + "WithdrawRequested" + ); + }); + + it("Should not calling withdrawAll when to little balance is on the remote strategy", async function () { + await mintToMasterDepositToRemote("10"); + await withdrawFromRemoteToVault("9.5", true); + + await expect( + await crossChainRemoteStrategy.checkBalance(usdc.address) + ).to.eq(usdcUnits("0.5")); + + // Remote only has 0.5 USDC left, the withdrawAll won't issue another withdraw, but wont fail as well + await expect(withdrawAllFromRemoteStrategy()).to.not.emit( + crossChainMasterStrategy, + "WithdrawRequested" + ); + }); + + it("Should revert if withdrawal amount is too small", async function () { + await mintToMasterDepositToRemote("10"); + + await expect(withdrawFromRemoteStrategy("0.9")).to.be.revertedWith( + "Withdraw amount too small" + ); + }); + + it("Should revert if withdrawal exceeds remote strategy balance", async function () { + await mintToMasterDepositToRemote("10"); + + await expect(withdrawFromRemoteStrategy("11")).to.be.revertedWith( + "Withdraw amount exceeds remote strategy balance" + ); + }); + + it("Should revert if withdrawal exceeds max transfer amount", async function () { + await setERC20TokenBalance(josh.address, usdc, "100000000"); + await mintToMasterDepositToRemote("9000000"); + await mintToMasterDepositToRemote("9000000"); + + await expect(withdrawFromRemoteStrategy("10000001")).to.be.revertedWith( + "Withdraw amount exceeds max transfer amount" + ); + }); + + it("Should revert if balance update on the remote strategy is not called by operator or governor or strategist", async function () { + await mintToMasterDepositToRemote("10"); + await expect( + crossChainRemoteStrategy.connect(josh).sendBalanceUpdate() + ).to.be.revertedWith( + "Caller is not the Operator, Strategist or the Governor" + ); + }); + + it("Should revert if deposit on the remote strategy is not called by the governor or strategist", async function () { + await mintToMasterDepositToRemote("10"); + await expect( + crossChainRemoteStrategy + .connect(josh) + .deposit(usdc.address, await units("10", usdc)) + ).to.be.revertedWith("Caller is not the Strategist or Governor"); + }); + + it("Should revert if depositAll on the remote strategy is not called by the governor or strategist", async function () { + await mintToMasterDepositToRemote("10"); + await expect( + crossChainRemoteStrategy.connect(josh).depositAll() + ).to.be.revertedWith("Caller is not the Strategist or Governor"); + }); + + it("Should revert if withdraw on the remote strategy is not called by the governor or strategist", async function () { + await mintToMasterDepositToRemote("10"); + await expect( + crossChainRemoteStrategy + .connect(josh) + .withdraw(vault.address, usdc.address, await units("10", usdc)) + ).to.be.revertedWith("Caller is not the Strategist or Governor"); + }); + + it("Should revert if withdrawAll on the remote strategy is not called by the governor or strategist", async function () { + await mintToMasterDepositToRemote("10"); + await expect( + crossChainRemoteStrategy.connect(josh).withdrawAll() + ).to.be.revertedWith("Caller is not the Strategist or Governor"); + }); + + it("Should revert if depositing 0 amount", async function () { + await mintToMasterDepositToRemote("10"); + await expect( + crossChainRemoteStrategy + .connect(governor) + .deposit(usdc.address, await units("0", usdc)) + ).to.be.revertedWith("Must deposit something"); + }); + + it("Should revert if not depositing USDC", async function () { + const { ousd } = fixture; + await mintToMasterDepositToRemote("10"); + await expect( + crossChainRemoteStrategy + .connect(governor) + .deposit(ousd.address, await units("10", ousd)) + ).to.be.revertedWith("Unexpected asset address"); + }); + + it("Check balance on the remote strategy should revert when not passing USDC address", async function () { + const { ousd } = fixture; + await mintToMasterDepositToRemote("10"); + await expect( + crossChainRemoteStrategy.checkBalance(ousd.address) + ).to.be.revertedWith("Unexpected asset address"); + }); + + it("Check balance on the remote strategy should revert when not passing USDC address", async function () { + const { messageTransmitter } = fixture; + + await mintToMasterDepositToRemote("1000"); + + await withdrawFromRemoteStrategy("300"); + + // Process on remote strategy + await expect( + messageTransmitter.processFrontOverrideHeader("0x00000001") + ).to.be.revertedWith("Unsupported message version"); + }); + + it("Should revert if setMinFinalityThreshold does not equal 1000 or 2000", async function () { + await expect( + crossChainMasterStrategy.connect(governor).setMinFinalityThreshold(1001) + ).to.be.revertedWith("Invalid threshold"); + + await expect( + crossChainMasterStrategy.connect(governor).setMinFinalityThreshold(2001) + ).to.be.revertedWith("Invalid threshold"); + }); + + it("Should set min finality threshold to 1000", async function () { + await crossChainMasterStrategy + .connect(governor) + .setMinFinalityThreshold(1000); + await expect(await crossChainMasterStrategy.minFinalityThreshold()).to.eq( + 1000 + ); + }); + + it("Should set min finality threshold to 2000", async function () { + await crossChainMasterStrategy + .connect(governor) + .setMinFinalityThreshold(2000); + await expect(await crossChainMasterStrategy.minFinalityThreshold()).to.eq( + 2000 + ); + }); + + it("Should set fee premium to 1000 bps successfully", async function () { + const initialFeeBps = await crossChainMasterStrategy.feePremiumBps(); + expect(initialFeeBps).to.equal(0); // Default is 0 + + await expect( + crossChainMasterStrategy.connect(governor).setFeePremiumBps(1000) + ) + .to.emit(crossChainMasterStrategy, "CCTPFeePremiumBpsSet") + .withArgs(1000); + + // Verify state updated + expect(await crossChainMasterStrategy.feePremiumBps()).to.equal(1000); + }); + + it("Should revert when setting fee premium >3000 bps", async function () { + await expect( + crossChainMasterStrategy.connect(governor).setFeePremiumBps(3001) + ).to.be.revertedWith("Fee premium too high"); + }); + + it("Should revert if sender of the message is not correct", async function () { + const { messageTransmitter } = fixture; + + await mintToMasterDepositToRemote("1000"); + + await withdrawFromRemoteStrategy("300"); + + await messageTransmitter.connect(josh).overrideSender(josh.address); + // Process on remote strategy + await expect(messageTransmitter.processFront()).to.be.revertedWith( + "Unknown Sender" + ); + }); + + it("Should revert if unfinalized messages are not supported", async function () { + const { messageTransmitter } = fixture; + + await mintToMasterDepositToRemote("1000"); + + await withdrawFromRemoteStrategy("300"); + + await messageTransmitter.connect(josh).overrideMessageFinality(1000); + // Process on remote strategy + await expect(messageTransmitter.processFront()).to.be.revertedWith( + "Unfinalized messages are not supported" + ); + }); + + it("Should accept unfinalized messages if min finality threshold is set to 1000", async function () { + const { messageTransmitter } = fixture; + + await mintToMasterDepositToRemote("1000"); + + await withdrawFromRemoteStrategy("300"); + + await crossChainRemoteStrategy + .connect(governor) + .setMinFinalityThreshold(1000); + + await messageTransmitter.connect(josh).overrideMessageFinality(1000); + // Process on remote strategy + await messageTransmitter.processFront(); + }); + + it("Should revert is message finality is below 1000", async function () { + const { messageTransmitter } = fixture; + + await mintToMasterDepositToRemote("1000"); + + await withdrawFromRemoteStrategy("300"); + + await crossChainRemoteStrategy + .connect(governor) + .setMinFinalityThreshold(1000); + + await messageTransmitter.connect(josh).overrideMessageFinality(999); + // Process on remote strategy + await expect(messageTransmitter.processFront()).to.be.revertedWith( + "Finality threshold too low" + ); + }); + + it("Should revert if the source domain is not correct", async function () { + const { messageTransmitter } = fixture; + + await mintToMasterDepositToRemote("1000"); + + await withdrawFromRemoteStrategy("300"); + + await messageTransmitter.connect(josh).overrideSourceDomain(444); + // Process on remote strategy + await expect(messageTransmitter.processFront()).to.be.revertedWith( + "Unknown Source Domain" + ); + }); + + it("Should revert if incorrect cctp message version is used", async function () { + const { messageTransmitter } = fixture; + + await mintToMasterDepositToRemote("1000"); + + await withdrawFromRemoteStrategy("300"); + + // Process on remote strategy + await expect( + messageTransmitter.processFrontOverrideVersion(2) + ).to.be.revertedWith("Invalid CCTP message version"); + }); + + it("Should revert if incorrect sender is used in the message header", async function () { + const { messageTransmitter } = fixture; + + await mintToMasterDepositToRemote("1000"); + + await withdrawFromRemoteStrategy("300"); + + // Process on remote strategy + await expect( + messageTransmitter.processFrontOverrideSender(josh.address) + ).to.be.revertedWith("Incorrect sender/recipient address"); + }); + + it("Should revert if incorrect sender is used in the message header", async function () { + const { messageTransmitter } = fixture; + + await mintToMasterDepositToRemote("1000"); + + await withdrawFromRemoteStrategy("300"); + + // Process on remote strategy + await expect( + messageTransmitter.processFrontOverrideRecipient(josh.address) + ).to.be.revertedWith("Unexpected recipient address"); + }); +}); diff --git a/contracts/test/strategies/crosschain/crosschain-master-strategy.mainnet.fork-test.js b/contracts/test/strategies/crosschain/crosschain-master-strategy.mainnet.fork-test.js new file mode 100644 index 0000000000..5cb3eb150a --- /dev/null +++ b/contracts/test/strategies/crosschain/crosschain-master-strategy.mainnet.fork-test.js @@ -0,0 +1,550 @@ +const { expect } = require("chai"); + +const { usdcUnits, isCI } = require("../../helpers"); +const { createFixtureLoader, crossChainFixture } = require("../../_fixture"); +const { impersonateAndFund } = require("../../../utils/signers"); +const addresses = require("../../../utils/addresses"); +const loadFixture = createFixtureLoader(crossChainFixture); +const { + DEPOSIT_FOR_BURN_EVENT_TOPIC, + MESSAGE_SENT_EVENT_TOPIC, + setRemoteStrategyBalance, + decodeDepositForBurnEvent, + decodeMessageSentEvent, + decodeDepositOrWithdrawMessage, + encodeCCTPMessage, + encodeBurnMessageBody, + encodeBalanceCheckMessageBody, + replaceMessageTransmitter, +} = require("./_crosschain-helpers"); + +describe("ForkTest: CrossChainMasterStrategy", function () { + this.timeout(0); + + // Retry up to 3 times on CI + this.retries(isCI ? 3 : 0); + + let fixture; + beforeEach(async () => { + fixture = await loadFixture(); + }); + + describe("Message sending", function () { + it("Should initiate bridging of deposited USDC", async function () { + const { matt, crossChainMasterStrategy, usdc } = fixture; + + if (await crossChainMasterStrategy.isTransferPending()) { + // Skip if there's a pending transfer + console.log( + "Skipping deposit fork test because there's a pending transfer" + ); + return; + } + + const vaultAddr = await crossChainMasterStrategy.vaultAddress(); + + const impersonatedVault = await impersonateAndFund(vaultAddr); + + // Let the strategy hold some USDC + await usdc + .connect(matt) + .transfer(crossChainMasterStrategy.address, usdcUnits("1000")); + + const usdcBalanceBefore = await usdc.balanceOf( + crossChainMasterStrategy.address + ); + const strategyBalanceBefore = await crossChainMasterStrategy.checkBalance( + usdc.address + ); + + // Simulate deposit call + const tx = await crossChainMasterStrategy + .connect(impersonatedVault) + .deposit(usdc.address, usdcUnits("1000")); + + const usdcBalanceAfter = await usdc.balanceOf( + crossChainMasterStrategy.address + ); + expect(usdcBalanceAfter).to.eq(usdcBalanceBefore.sub(usdcUnits("1000"))); + + const strategyBalanceAfter = await crossChainMasterStrategy.checkBalance( + usdc.address + ); + expect(strategyBalanceAfter).to.eq(strategyBalanceBefore); + + expect(await crossChainMasterStrategy.pendingAmount()).to.eq( + usdcUnits("1000") + ); + + // Check for message sent event + const receipt = await tx.wait(); + const depositForBurnEvent = receipt.events.find((e) => + e.topics.includes(DEPOSIT_FOR_BURN_EVENT_TOPIC) + ); + const burnEventData = decodeDepositForBurnEvent(depositForBurnEvent); + + expect(burnEventData.amount).to.eq(usdcUnits("1000")); + expect(burnEventData.mintRecipient.toLowerCase()).to.eq( + crossChainMasterStrategy.address.toLowerCase() + ); + expect(burnEventData.destinationDomain).to.eq(6); + expect(burnEventData.destinationTokenMessenger.toLowerCase()).to.eq( + addresses.CCTPTokenMessengerV2.toLowerCase() + ); + expect(burnEventData.destinationCaller.toLowerCase()).to.eq( + crossChainMasterStrategy.address.toLowerCase() + ); + expect(burnEventData.maxFee).to.eq(0); + expect(burnEventData.burnToken).to.eq(usdc.address); + + expect(burnEventData.depositer.toLowerCase()).to.eq( + crossChainMasterStrategy.address.toLowerCase() + ); + expect(burnEventData.minFinalityThreshold).to.eq(2000); + expect(burnEventData.burnToken.toLowerCase()).to.eq( + usdc.address.toLowerCase() + ); + + // Decode and verify payload + const { messageType, nonce, amount } = decodeDepositOrWithdrawMessage( + burnEventData.hookData + ); + expect(messageType).to.eq(1); + expect(nonce).to.eq(1); + expect(amount).to.eq(usdcUnits("1000")); + }); + + it("Should request withdrawal", async function () { + const { crossChainMasterStrategy, usdc } = fixture; + + if (await crossChainMasterStrategy.isTransferPending()) { + // Skip if there's a pending transfer + console.log( + "Skipping deposit fork test because there's a pending transfer" + ); + return; + } + + const vaultAddr = await crossChainMasterStrategy.vaultAddress(); + const impersonatedVault = await impersonateAndFund(vaultAddr); + + // set an arbitrary remote strategy balance + await setRemoteStrategyBalance( + crossChainMasterStrategy, + usdcUnits("1000") + ); + + const tx = await crossChainMasterStrategy + .connect(impersonatedVault) + .withdraw(vaultAddr, usdc.address, usdcUnits("1000")); + const receipt = await tx.wait(); + const messageSentEvent = receipt.events.find((e) => + e.topics.includes(MESSAGE_SENT_EVENT_TOPIC) + ); + + const { + version, + sourceDomain, + desinationDomain, + sender, + recipient, + destinationCaller, + minFinalityThreshold, + payload, + } = decodeMessageSentEvent(messageSentEvent); + + expect(version).to.eq(1); + expect(sourceDomain).to.eq(0); + expect(desinationDomain).to.eq(6); + expect(sender.toLowerCase()).to.eq( + crossChainMasterStrategy.address.toLowerCase() + ); + expect(recipient.toLowerCase()).to.eq( + crossChainMasterStrategy.address.toLowerCase() + ); + expect(destinationCaller.toLowerCase()).to.eq( + crossChainMasterStrategy.address.toLowerCase() + ); + expect(minFinalityThreshold).to.eq(2000); + + // Decode and verify payload + const { messageType, nonce, amount } = + decodeDepositOrWithdrawMessage(payload); + expect(messageType).to.eq(2); + expect(nonce).to.eq(1); + expect(amount).to.eq(usdcUnits("1000")); + }); + }); + + describe("Message receiving", function () { + it("Should handle balance check message", async function () { + const { crossChainMasterStrategy, strategist } = fixture; + + if (await crossChainMasterStrategy.isTransferPending()) { + // Skip if there's a pending transfer + console.log( + "Skipping balance check message fork test because there's a pending transfer" + ); + return; + } + + const lastNonce = ( + await crossChainMasterStrategy.lastTransferNonce() + ).toNumber(); + + // Replace transmitter to mock transmitter + await replaceMessageTransmitter(); + + // Build check balance payload + const balancePayload = encodeBalanceCheckMessageBody( + lastNonce, + usdcUnits("12345"), + false + ); + const message = encodeCCTPMessage( + 6, + crossChainMasterStrategy.address, + crossChainMasterStrategy.address, + balancePayload + ); + + // Relay the message with fake attestation + await crossChainMasterStrategy.connect(strategist).relay(message, "0x"); + + const remoteStrategyBalance = + await crossChainMasterStrategy.remoteStrategyBalance(); + expect(remoteStrategyBalance).to.eq(usdcUnits("12345")); + }); + + it("Should handle balance check message for a pending deposit", async function () { + const { crossChainMasterStrategy, strategist, usdc, matt } = fixture; + + if (await crossChainMasterStrategy.isTransferPending()) { + // Skip if there's a pending transfer + console.log( + "Skipping balance check message fork test because there's a pending transfer" + ); + return; + } + + // Do a pre-deposit + const vaultAddr = await crossChainMasterStrategy.vaultAddress(); + + const impersonatedVault = await impersonateAndFund(vaultAddr); + + // Let the strategy hold some USDC + await usdc + .connect(matt) + .transfer(crossChainMasterStrategy.address, usdcUnits("1000")); + + // Simulate deposit call + await crossChainMasterStrategy + .connect(impersonatedVault) + .deposit(usdc.address, usdcUnits("1000")); + + const lastNonce = ( + await crossChainMasterStrategy.lastTransferNonce() + ).toNumber(); + + // Replace transmitter to mock transmitter + await replaceMessageTransmitter(); + + // Build check balance payload + const payload = encodeBalanceCheckMessageBody( + lastNonce, + usdcUnits("10000"), + true // deposit confirmation + ); + const message = encodeCCTPMessage( + 6, + crossChainMasterStrategy.address, + crossChainMasterStrategy.address, + payload + ); + + // Relay the message with fake attestation + await crossChainMasterStrategy.connect(strategist).relay(message, "0x"); + + const remoteStrategyBalance = + await crossChainMasterStrategy.remoteStrategyBalance(); + // We did a deposit of 1000 USDC but had the remote strategy report 10k for the test. + expect(remoteStrategyBalance).to.eq(usdcUnits("10000")); + + expect(await crossChainMasterStrategy.pendingAmount()).to.eq( + usdcUnits("0") + ); + }); + + it("Should accept tokens for a pending withdrawal", async function () { + const { crossChainMasterStrategy, strategist, matt, usdc } = fixture; + + if (await crossChainMasterStrategy.isTransferPending()) { + // Skip if there's a pending transfer + console.log( + "Skipping balance check message fork test because there's a pending transfer" + ); + return; + } + + const vaultAddr = await crossChainMasterStrategy.vaultAddress(); + const impersonatedVault = await impersonateAndFund(vaultAddr); + + // set an arbitrary remote strategy balance + await setRemoteStrategyBalance( + crossChainMasterStrategy, + usdcUnits("123456") + ); + + // Simulate withdrawal call + await crossChainMasterStrategy + .connect(impersonatedVault) + .withdraw(vaultAddr, usdc.address, usdcUnits("1000")); + + const lastNonce = ( + await crossChainMasterStrategy.lastTransferNonce() + ).toNumber(); + + // Replace transmitter to mock transmitter + await replaceMessageTransmitter(); + + // Build check balance payload + const balancePayload = encodeBalanceCheckMessageBody( + lastNonce, + usdcUnits("12345"), + true // withdrawal confirmation + ); + const burnPayload = encodeBurnMessageBody( + crossChainMasterStrategy.address, + crossChainMasterStrategy.address, + addresses.base.USDC, + usdcUnits("2342"), + balancePayload + ); + const message = encodeCCTPMessage( + 6, + addresses.CCTPTokenMessengerV2, + addresses.CCTPTokenMessengerV2, + burnPayload + ); + + // transfer some USDC to master strategy + await usdc + .connect(matt) + .transfer(crossChainMasterStrategy.address, usdcUnits("2342")); + + // Relay the message with fake attestation + await crossChainMasterStrategy.connect(strategist).relay(message, "0x"); + + const remoteStrategyBalance = + await crossChainMasterStrategy.remoteStrategyBalance(); + expect(remoteStrategyBalance).to.eq(usdcUnits("12345")); + }); + + it("Should ignore balance check message for a pending withdrawal", async function () { + const { crossChainMasterStrategy, strategist, usdc } = fixture; + + if (await crossChainMasterStrategy.isTransferPending()) { + // Skip if there's a pending transfer + console.log( + "Skipping balance check message fork test because there's a pending transfer" + ); + return; + } + + const vaultAddr = await crossChainMasterStrategy.vaultAddress(); + const impersonatedVault = await impersonateAndFund(vaultAddr); + + // set an arbitrary remote strategy balance + await setRemoteStrategyBalance( + crossChainMasterStrategy, + usdcUnits("1000") + ); + + const remoteStrategyBalanceBefore = + await crossChainMasterStrategy.remoteStrategyBalance(); + + // Simulate withdrawal call + await crossChainMasterStrategy + .connect(impersonatedVault) + .withdraw(vaultAddr, usdc.address, usdcUnits("1000")); + + const lastNonce = ( + await crossChainMasterStrategy.lastTransferNonce() + ).toNumber(); + + // Replace transmitter to mock transmitter + await replaceMessageTransmitter(); + + // Build check balance payload + const payload = encodeBalanceCheckMessageBody( + lastNonce, + usdcUnits("10000"), + false + ); + const message = encodeCCTPMessage( + 6, + crossChainMasterStrategy.address, + crossChainMasterStrategy.address, + payload + ); + + // Relay the message with fake attestation + await crossChainMasterStrategy.connect(strategist).relay(message, "0x"); + + // Should've ignore the message + const remoteStrategyBalance = + await crossChainMasterStrategy.remoteStrategyBalance(); + expect(remoteStrategyBalance).to.eq(remoteStrategyBalanceBefore); + }); + + it("Should ignore balance check message with older nonce", async function () { + const { crossChainMasterStrategy, strategist, matt, usdc } = fixture; + + if (await crossChainMasterStrategy.isTransferPending()) { + // Skip if there's a pending transfer + console.log( + "Skipping balance check message fork test because there's a pending transfer" + ); + return; + } + + const lastNonce = ( + await crossChainMasterStrategy.lastTransferNonce() + ).toNumber(); + + // Do a pre-deposit + const vaultAddr = await crossChainMasterStrategy.vaultAddress(); + + const impersonatedVault = await impersonateAndFund(vaultAddr); + + // Let the strategy hold some USDC + await usdc + .connect(matt) + .transfer(crossChainMasterStrategy.address, usdcUnits("1000")); + + // Simulate deposit call + await crossChainMasterStrategy + .connect(impersonatedVault) + .deposit(usdc.address, usdcUnits("1000")); + + const remoteStrategyBalanceBefore = + await crossChainMasterStrategy.remoteStrategyBalance(); + + // Replace transmitter to mock transmitter + await replaceMessageTransmitter(); + + // Build check balance payload + const payload = encodeBalanceCheckMessageBody( + lastNonce, + usdcUnits("123244"), + false // deposit confirmation + ); + const message = encodeCCTPMessage( + 6, + crossChainMasterStrategy.address, + crossChainMasterStrategy.address, + payload + ); + + // Relay the message with fake attestation + await crossChainMasterStrategy.connect(strategist).relay(message, "0x"); + + const remoteStrategyBalance = + await crossChainMasterStrategy.remoteStrategyBalance(); + expect(remoteStrategyBalance).to.eq(remoteStrategyBalanceBefore); + }); + + it("Should ignore if nonce is higher", async function () { + const { crossChainMasterStrategy, strategist } = fixture; + + if (await crossChainMasterStrategy.isTransferPending()) { + // Skip if there's a pending transfer + console.log( + "Skipping balance check message fork test because there's a pending transfer" + ); + return; + } + + const lastNonce = ( + await crossChainMasterStrategy.lastTransferNonce() + ).toNumber(); + + // Replace transmitter to mock transmitter + await replaceMessageTransmitter(); + + const remoteStrategyBalanceBefore = + await crossChainMasterStrategy.remoteStrategyBalance(); + + // Build check balance payload + const payload = encodeBalanceCheckMessageBody( + lastNonce + 2, + usdcUnits("123244"), + false + ); + const message = encodeCCTPMessage( + 6, + crossChainMasterStrategy.address, + crossChainMasterStrategy.address, + payload + ); + + // Relay the message with fake attestation + await crossChainMasterStrategy.connect(strategist).relay(message, "0x"); + const remoteStrategyBalanceAfter = + await crossChainMasterStrategy.remoteStrategyBalance(); + expect(remoteStrategyBalanceAfter).to.eq(remoteStrategyBalanceBefore); + }); + + it("Should revert if the burn token is not peer USDC", async function () { + const { crossChainMasterStrategy, strategist } = fixture; + + if (await crossChainMasterStrategy.isTransferPending()) { + // Skip if there's a pending transfer + console.log( + "Skipping balance check message fork test because there's a pending transfer" + ); + return; + } + + // set an arbitrary remote strategy balance + await setRemoteStrategyBalance( + crossChainMasterStrategy, + usdcUnits("123456") + ); + + const lastNonce = ( + await crossChainMasterStrategy.lastTransferNonce() + ).toNumber(); + + // Replace transmitter to mock transmitter + await replaceMessageTransmitter(); + + // Build check balance payload + const balancePayload = encodeBalanceCheckMessageBody( + lastNonce, + usdcUnits("12345"), + true // withdrawal confirmation + ); + const burnPayload = encodeBurnMessageBody( + crossChainMasterStrategy.address, + crossChainMasterStrategy.address, + addresses.mainnet.WETH, // Not peer USDC + usdcUnits("2342"), + balancePayload + ); + const message = encodeCCTPMessage( + 6, + addresses.CCTPTokenMessengerV2, + addresses.CCTPTokenMessengerV2, + burnPayload + ); + + // Relay the message with fake attestation + const tx = crossChainMasterStrategy + .connect(strategist) + .relay(message, "0x"); + + await expect(tx).to.be.revertedWith("Invalid burn token"); + }); + }); +}); diff --git a/contracts/test/strategies/crosschain/crosschain-remote-strategy.base.fork-test.js b/contracts/test/strategies/crosschain/crosschain-remote-strategy.base.fork-test.js new file mode 100644 index 0000000000..ea8c2dfac2 --- /dev/null +++ b/contracts/test/strategies/crosschain/crosschain-remote-strategy.base.fork-test.js @@ -0,0 +1,286 @@ +const { expect } = require("chai"); + +const { isCI, usdcUnits } = require("../../helpers"); +const { createFixtureLoader } = require("../../_fixture"); +const { crossChainFixture } = require("../../_fixture-base"); +const { + MESSAGE_SENT_EVENT_TOPIC, + decodeMessageSentEvent, + decodeBalanceCheckMessageBody, + replaceMessageTransmitter, + encodeBurnMessageBody, + decodeBurnMessageBody, + encodeCCTPMessage, + encodeDepositMessageBody, + encodeWithdrawMessageBody, +} = require("./_crosschain-helpers"); +const addresses = require("../../../utils/addresses"); + +const loadFixture = createFixtureLoader(crossChainFixture); + +describe("ForkTest: CrossChainRemoteStrategy", function () { + this.timeout(0); + + // Retry up to 3 times on CI + this.retries(isCI ? 3 : 0); + + let fixture; + beforeEach(async () => { + fixture = await loadFixture(); + }); + + const verifyBalanceCheckMessage = ( + messageSentEvent, + expectedNonce, + expectedBalance, + transferAmount = "0" + ) => { + const { crossChainRemoteStrategy, usdc } = fixture; + const { + version, + sourceDomain, + desinationDomain, + sender, + recipient, + destinationCaller, + minFinalityThreshold, + payload, + } = decodeMessageSentEvent(messageSentEvent); + + expect(version).to.eq(1); + expect(sourceDomain).to.eq(6); + expect(desinationDomain).to.eq(0); + expect(destinationCaller.toLowerCase()).to.eq( + crossChainRemoteStrategy.address.toLowerCase() + ); + expect(minFinalityThreshold).to.eq(2000); + + let balanceCheckPayload = payload; + + const isBurnMessage = + sender.toLowerCase() == addresses.CCTPTokenMessengerV2.toLowerCase(); + if (isBurnMessage) { + // Verify burn message + const { burnToken, recipient, amount, sender, hookData } = + decodeBurnMessageBody(payload); + expect(burnToken.toLowerCase()).to.eq(usdc.address.toLowerCase()); + expect(recipient.toLowerCase()).to.eq( + crossChainRemoteStrategy.address.toLowerCase() + ); + expect(amount).to.eq(transferAmount); + expect(sender.toLowerCase()).to.eq( + crossChainRemoteStrategy.address.toLowerCase() + ); + balanceCheckPayload = hookData; + } else { + // Ensure sender and recipient are the strategy address + expect(sender.toLowerCase()).to.eq( + crossChainRemoteStrategy.address.toLowerCase() + ); + expect(recipient.toLowerCase()).to.eq( + crossChainRemoteStrategy.address.toLowerCase() + ); + } + + const { + version: balanceCheckVersion, + messageType, + nonce, + balance, + } = decodeBalanceCheckMessageBody(balanceCheckPayload); + + expect(balanceCheckVersion).to.eq(1010); + expect(messageType).to.eq(3); + expect(nonce).to.eq(expectedNonce); + expect(balance).to.approxEqual(expectedBalance); + }; + + it("Should send a balance update message", async function () { + const { crossChainRemoteStrategy, strategist, rafael, usdc } = fixture; + // Send some USDC to the remote strategy + await usdc + .connect(rafael) + .transfer(crossChainRemoteStrategy.address, usdcUnits("1234")); + + const balanceBefore = await crossChainRemoteStrategy.checkBalance( + usdc.address + ); + const nonceBefore = await crossChainRemoteStrategy.lastTransferNonce(); + + const tx = await crossChainRemoteStrategy + .connect(strategist) + .sendBalanceUpdate(); + const receipt = await tx.wait(); + const messageSentEvent = receipt.events.find((e) => + e.topics.includes(MESSAGE_SENT_EVENT_TOPIC) + ); + + verifyBalanceCheckMessage( + messageSentEvent, + nonceBefore.toNumber(), + balanceBefore + ); + }); + + it("Should handle deposits", async function () { + const { crossChainRemoteStrategy, strategist, rafael, usdc } = fixture; + + // snapshot state + const balanceBefore = await crossChainRemoteStrategy.checkBalance( + usdc.address + ); + const nonceBefore = await crossChainRemoteStrategy.lastTransferNonce(); + + const depositAmount = usdcUnits("1234.56"); + + // Replace transmitter to mock transmitter + await replaceMessageTransmitter(); + + const nextNonce = nonceBefore.toNumber() + 1; + + // Build deposit message + const depositPayload = encodeDepositMessageBody(nextNonce, depositAmount); + const burnPayload = encodeBurnMessageBody( + crossChainRemoteStrategy.address, + crossChainRemoteStrategy.address, + addresses.mainnet.USDC, + depositAmount, + depositPayload + ); + const message = encodeCCTPMessage( + 0, + addresses.CCTPTokenMessengerV2, + addresses.CCTPTokenMessengerV2, + burnPayload + ); + + // Simulate token transfer + await usdc + .connect(rafael) + .transfer(crossChainRemoteStrategy.address, depositAmount); + + // Relay the message + const tx = await crossChainRemoteStrategy + .connect(strategist) + .relay(message, "0x"); + + // Check if it sent the check balance message + const receipt = await tx.wait(); + const messageSentEvent = receipt.events.find((e) => + e.topics.includes(MESSAGE_SENT_EVENT_TOPIC) + ); + + // Verify the balance check message + const expectedBalance = balanceBefore.add(depositAmount); + verifyBalanceCheckMessage(messageSentEvent, nextNonce, expectedBalance); + + const nonceAfter = await crossChainRemoteStrategy.lastTransferNonce(); + expect(nonceAfter).to.eq(nextNonce); + + const balanceAfter = await crossChainRemoteStrategy.checkBalance( + usdc.address + ); + expect(balanceAfter).to.approxEqual(expectedBalance); + }); + + it("Should handle withdrawals", async function () { + const { crossChainRemoteStrategy, strategist, rafael, usdc } = fixture; + + const withdrawalAmount = usdcUnits("1234.56"); + + // Make sure the strategy has enough balance + const depositAmount = withdrawalAmount.mul(2); + await usdc + .connect(rafael) + .transfer(crossChainRemoteStrategy.address, depositAmount); + await crossChainRemoteStrategy + .connect(strategist) + .deposit(usdc.address, depositAmount); + + // snapshot state + const balanceBefore = await crossChainRemoteStrategy.checkBalance( + usdc.address + ); + const nonceBefore = await crossChainRemoteStrategy.lastTransferNonce(); + const nextNonce = nonceBefore.toNumber() + 1; + + // Build withdrawal message + const withdrawalPayload = encodeWithdrawMessageBody( + nextNonce, + withdrawalAmount + ); + const message = encodeCCTPMessage( + 0, + crossChainRemoteStrategy.address, + crossChainRemoteStrategy.address, + withdrawalPayload + ); + + // Replace transmitter to mock transmitter + await replaceMessageTransmitter(); + + // Relay the message + const tx = await crossChainRemoteStrategy + .connect(strategist) + .relay(message, "0x"); + + // Check if it sent the check balance message + const receipt = await tx.wait(); + const messageSentEvent = receipt.events.find((e) => + e.topics.includes(MESSAGE_SENT_EVENT_TOPIC) + ); + + // Verify the balance check message + const expectedBalance = balanceBefore.sub(withdrawalAmount); + verifyBalanceCheckMessage( + messageSentEvent, + nextNonce, + expectedBalance, + withdrawalAmount + ); + + const nonceAfter = await crossChainRemoteStrategy.lastTransferNonce(); + expect(nonceAfter).to.eq(nextNonce); + + const balanceAfter = await crossChainRemoteStrategy.checkBalance( + usdc.address + ); + expect(balanceAfter).to.approxEqual(expectedBalance); + }); + + it("Should revert if the burn token is not peer USDC", async function () { + const { crossChainRemoteStrategy, strategist } = fixture; + + const nonceBefore = await crossChainRemoteStrategy.lastTransferNonce(); + + const depositAmount = usdcUnits("1234.56"); + + // Replace transmitter to mock transmitter + await replaceMessageTransmitter(); + + const nextNonce = nonceBefore.toNumber() + 1; + + // Build deposit message + const depositPayload = encodeDepositMessageBody(nextNonce, depositAmount); + const burnPayload = encodeBurnMessageBody( + crossChainRemoteStrategy.address, + crossChainRemoteStrategy.address, + addresses.base.WETH, // Not peer USDC + depositAmount, + depositPayload + ); + const message = encodeCCTPMessage( + 0, + addresses.CCTPTokenMessengerV2, + addresses.CCTPTokenMessengerV2, + burnPayload + ); + + // Relay the message + const tx = crossChainRemoteStrategy + .connect(strategist) + .relay(message, "0x"); + + await expect(tx).to.be.revertedWith("Invalid burn token"); + }); +}); diff --git a/contracts/utils/addresses.js b/contracts/utils/addresses.js index f23560fcc3..e1250dabd1 100644 --- a/contracts/utils/addresses.js +++ b/contracts/utils/addresses.js @@ -10,6 +10,11 @@ addresses.multichainBuybackOperator = "0xBB077E716A5f1F1B63ed5244eBFf5214E50fec8c"; addresses.votemarket = "0x8c2c5A295450DDFf4CB360cA73FCCC12243D14D9"; +// CCTP contracts (uses same addresses on all chains) +addresses.CCTPTokenMessengerV2 = "0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d"; +addresses.CCTPMessageTransmitterV2 = + "0x81D40F21F12A8F0E3252Bccb954D722d4c464B64"; + addresses.mainnet = {}; addresses.base = {}; addresses.sonic = {}; @@ -464,6 +469,10 @@ addresses.base.CCIPRouter = "0x881e3A65B4d4a04dD529061dd0071cf975F58bCD"; addresses.base.MerklDistributor = "0x8BB4C975Ff3c250e0ceEA271728547f3802B36Fd"; +addresses.base.USDC = "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913"; + +addresses.base.MorphoOusdV2Vault = "0x2Ba14b2e1E7D2189D3550b708DFCA01f899f33c1"; + // Sonic addresses.sonic.wS = "0x039e2fB66102314Ce7b64Ce5Ce3E5183bc94aD38"; addresses.sonic.WETH = "0x309C92261178fA0CF748A855e90Ae73FDb79EBc7"; @@ -697,4 +706,12 @@ addresses.hoodi.beaconChainDepositContract = addresses.hoodi.defenderRelayer = "0x419B6BdAE482f41b8B194515749F3A2Da26d583b"; addresses.hoodi.mockBeaconRoots = "0xdCfcAE4A084AA843eE446f400B23aA7B6340484b"; +// Crosschain Strategy +// TODO delete: master - remote test address: 0x1743658b284a843b47f555343dbb628d46d0c254 +addresses.base.CrossChainRemoteStrategy = "TODO"; +addresses.mainnet.CrossChainMasterStrategy = "TODO"; +// CCTP Circle Contract addresses: https://developers.circle.com/cctp/references/contract-addresses +addresses.CCTPTokenMessengerV2 = "0x28b5a0e9c621a5badaa536219b3a228c8168cf5d"; +addresses.CCTPMessageTransmitterV2 = + "0x81D40F21F12A8F0E3252Bccb954D722d4c464B64"; module.exports = addresses; diff --git a/contracts/utils/cctp.js b/contracts/utils/cctp.js new file mode 100644 index 0000000000..890c206233 --- /dev/null +++ b/contracts/utils/cctp.js @@ -0,0 +1,34 @@ +const addresses = require("./addresses"); + +const cctpDomainIds = { + Ethereum: 0, + Base: 6, +}; +const api = "https://iris-api.circle.com"; +const configuration = { + mainnetBaseMorpho: { + mainnet: { + cctpDestinationDomainId: cctpDomainIds.Base, + cctpSourceDomainId: cctpDomainIds.Ethereum, + cctpIntegrationContractAddress: + addresses.mainnet.CrossChainMasterStrategy, + cctpIntegrationContractAddressDestination: + addresses.base.CrossChainRemoteStrategy, + blockLookback: 14600, // a bit over 2 days in block time on mainnet + }, + base: { + cctpDestinationDomainId: cctpDomainIds.Ethereum, + cctpSourceDomainId: cctpDomainIds.Base, + cctpIntegrationContractAddress: addresses.base.CrossChainRemoteStrategy, + cctpIntegrationContractAddressDestination: + addresses.mainnet.CrossChainMasterStrategy, + blockLookback: 87600, // a bit over 2 days in block time on base + }, + }, +}; + +module.exports = { + cctpDomainIds, + api, + configuration, +}; diff --git a/contracts/utils/defender.js b/contracts/utils/defender.js new file mode 100644 index 0000000000..52f131b8b2 --- /dev/null +++ b/contracts/utils/defender.js @@ -0,0 +1,45 @@ +const fs = require("fs"); +const path = require("path"); + +const keyValueStoreLocalClient = ({ _storePath }) => ({ + storePath: _storePath, + + async get(key) { + return this.getStore()[key]; + }, + + async put(key, value) { + this.updateStore((store) => { + store[key] = value; + }); + }, + + async del(key) { + this.updateStore((store) => { + delete store[key]; + }); + }, + + getStore() { + try { + if (!fs.existsSync(this.storePath)) { + return {}; + } + const contents = fs.readFileSync(this.storePath, "utf8"); + return contents ? JSON.parse(contents) : {}; + } catch (error) { + return {}; + } + }, + + updateStore(updater) { + const store = this.getStore(); + updater(store); + fs.mkdirSync(path.dirname(this.storePath), { recursive: true }); + fs.writeFileSync(this.storePath, JSON.stringify(store, null, 2)); + }, +}); + +module.exports = { + keyValueStoreLocalClient, +}; diff --git a/contracts/utils/deploy.js b/contracts/utils/deploy.js index bd75e0bba0..8461aa5165 100644 --- a/contracts/utils/deploy.js +++ b/contracts/utils/deploy.js @@ -328,6 +328,11 @@ const _verifyProxyInitializedWithCorrectGovernor = (transactionData) => { return; } + if (isMainnet || isBase || isFork || isBaseFork) { + // TODO: Skip verification for Fork for now + return; + } + const initProxyGovernor = ( "0x" + transactionData.slice(10 + 64 + 24, 10 + 64 + 64) ).toLowerCase(); From 18f49da5d0849888dc69d3dfae2a66ae17180292 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment?= <55331875+clement-ux@users.noreply.github.com> Date: Tue, 10 Feb 2026 12:12:54 +0100 Subject: [PATCH 09/36] [Vault] Simplify VaultCore. (#2714) * Refactor OETHVaultCore and VaultCore to simplify asset handling and remove WETH dependencies * Add constructors to MockVault and MockVaultCoreInstantRebase for initialization * Refactor OETHVaultCore and VaultCore to implement async withdrawal functionality * Refactor OETHVaultCore and VaultCore to remove unused functions and simplify total value calculations * Remove unused addWithdrawalQueueLiquidity function from OETHVaultCore and clean up comments in VaultCore * Refactor OETHVaultCore and VaultCore to remove unused imports and functions, simplifying the codebase. * Remove unused pricing functions and related imports in VaultCore to simplify the codebase. * Update constructor parameter in OETHVaultCore to use _weth for clarity * Add OUSDVaultCore contract and implement redeem functionality with access control * Refactor OETHVaultAdmin and VaultAdmin contracts to simplify code and improve clarity by removing deprecated functions and updating asset handling methods. * Refactor vault admin contracts to remove constructors and simplify initialization by using address(0) for backing asset. * Refactor vault contracts to enforce backing asset restrictions and remove deprecated asset handling * Refactor VaultAdmin and VaultStorage contracts to simplify default strategy handling for backing asset * Refactor AbstractHarvester and related contracts to integrate oracle address, simplifying price retrieval and enhancing clarity * Revert changes on Harvester * remove old code * Refactor vault admin contracts to include backing asset address in constructors for improved clarity and functionality * Refactor OETH Vault tests to simplify strategy handling and improve error messages * adjust decimal logic in VaultStorage * wip fix test * Refactor scaling logic in VaultCore to use scaleBy function for improved clarity and consistency * Refactor Compound strategy tests to simplify logic and improve clarity by removing unused variables and consolidating test cases * Refactor Vault tests to simplify logic by removing unused variables and consolidating test cases * Refactor Vault tests to simplify code by removing unused variables and consolidating test cases * Refactor Vault redeem tests to simplify logic by removing unused variables and consolidating test cases * fix buyback fixture * Refactor Reborner contract and tests to use USDC instead of USDS, update mint and redeem amounts for consistency * Refactor compoundingStakingSSVStrategyFixture to simplify default strategy setting * Refactor Convex strategy tests to use USDC instead of USDS and simplify asset handling * Refactor MockVaultCoreInstantRebase constructor to accept backing asset address, update test fixtures to use USDC instead of USDS, and simplify Dripper and VaultValueChecker tests by removing unused variables and consolidating logic. * fix remaining unit test * fix base setup * fix sonic setup * Add slither-disable comments for reentrancy checks in mint functions * Remove collateral swaps test file to streamline testing suite * Add OUSDVaultAdmin contract and deployment script for OUSD Vault upgrade * fix vault fork test * prettier * fix more fork tests * lint * fix more tests * fix sonic fork test * lint + prettier * skip plume test * fix sonic fork test * fix sonic fork test * remove .only * remove old fix * fix decimal conversion * refactor: change contracts to abstract for VaultAdmin, VaultCore, VaultInitializer, and VaultStorage * fix test * WIP add async withdraw test for OUSD * Add lot of test for async withdraw * adjust comment * refactor: remove setDripper function from IVault interface * fix: update redeem function to use correct amount parameter * feat: add deprecated weth asset index to OETHVaultCore contract * increase ousd vault upgrade number * fix: add slither disable comment for constable-states in OETHVaultCore contract * fix: correct drip duration initialization in VaultInitializer contract * feat: deprecate calculateRedeemOutputs and introduce calculateRedeemOutput in VaultCore contract * refactor: rename backingAsset to asset across contracts for consistency * refactor: change visibility of deprecated asset-related mappings to private * fix: improve error message for unsupported asset decimals in VaultStorage constructor * fix: update error message for unsupported asset in minting process * refactor: update comments to clarify AMO strategy references in mint and redeem functions * refactor: standardize terminology by replacing 'backing asset' with 'asset' in contracts and tests * refactor: simplify redeem output calculation by removing deprecated function * refactor: streamline redeem output calculation by consolidating return values * refactor: improve clarity in comments regarding AMO strategy in mint and redeem functions * refactor: mark isSupportedAsset function as deprecated * [Vault] Simplify `mint()` (#2735) * refactor: remove deprecated asset structure and related mappings from VaultStorage * refactor: remove deprecated mint function parameters and related tests * refactor: remove unused parameters from _mint and mint functions * refactor: remove unnecessary blank lines in test files * refactor: change internal mappings to private for better encapsulation * Clement/simplify ousd get rid of old stuff (#2736) * refactor: remove deprecated asset structure and related mappings from VaultStorage * refactor: remove deprecated mint function parameters and related tests * refactor: remove unused parameters from _mint and mint functions * refactor: remove unnecessary blank lines in test files * refactor: change internal mappings to private for better encapsulation * refactor: simplify asset mappings by changing from mapping to single uint256 * [Vault] Remove `redeem()` and simplify inheritance. (#2737) * Remove redeem functionality and related events from vault contracts * Refactor vault contracts to inherit from VaultCore, removing deprecated OETHVaultCore references and relocating legacy code for consistency. * Fix tests after redeem has been removed (#2738) * Remove calculateRedeemOutputs view function from Vault * Removed redeem, fund and yield Hardhat task * FIxed unit tests * Fixed fork tests * Fix Base fork tests * Fixed Base Curve AMO fork tests * Removed redeemFeeBps from Sonic fork tests * Fix Base and Sonic unit tests * Prettier * Remove hardhat fund from node script * Fix OUSD AMO fork tests * Fix AMO fork tests * Remove unused gap variable from OUSDVaultCore contract --------- Co-authored-by: Nick Addison * Removed Beacon consolidation fork tests * Add OETH vault upgrade deployment script with governance proposal * Remove deprecated redeem tests from OETH vault and bridge helper test files * lint * Add vault upgrade deployment script and skip deprecated tests in bridge helper * prettier * Add deployment script for OSonic vault upgrade with governance actions * Refactor OETH and OSonic VaultAdmin contracts to inherit from VaultAdmin * New gov prop of deploy script 163 * Simplify withdrawal amount calculations in VaultCore contract * fix bug * [Vault] Merge VaultAdmin and VaultCore (#2743) * Refactor vault contracts to remove deprecated implementations and simplify admin structure * Refactor vault contracts to unify admin structure and simplify deployment scripts * move zapper to dedicated folder * Refactor vault upgrade scripts to unify implementation names and simplify deployment actions * Updated hot deploy with new Vault contracts * Update Vault contract diagrams * Add OSVault contract and update deployment script references --------- Co-authored-by: Nicholas Addison * Removed commented out IOracle lines from Harvester * Changed the OUSD default strategy to the Morpho OUSD v2 strategy * Rename OSonicVault to OSVault in deployment script * Refactor rebase and withdrawal queue liquidity calls in VaultAdmin contract * Add slither disable comments for reentrancy checks in VaultAdmin contract * Removed dependency on OracleRouter from the OUSD and SuperOETH Vaults * Bumped the deploy script numbers * prevent removal of strategies that still have balance after the withdrawal (#2767) * [OUSD-16] Miscellaneous General Comments (#2788) * replace interface to fetch decimals * add comment * add link to readme * removed TODO * rename oUSD to oToken * add function for reverse compatibility and mark deprecated * rename * refactor: update setRebaseRateMax function to clarify APR parameters and improve documentation (#2785) * Remove constant MAX_PRICE_STALENESS from BridgedWOETHStrategy contract (#2784) * remove unused _totalValueInVault function to simplify VaultCore contract (#2779) * [OUSD-13] `removeStrategy()` Does Not Reset Mint Whitelist Flag. (#2778) * Reset mintWhiteList flag when removing strategy. * Add test to reset mint whitelist flag when removing a strategy * [OUSD-11] Missing Zero Amount Validation In `requestWithdrawal()`. (#2777) * Add validation for withdrawal amount in requestWithdrawal function * Fail to allow withdrawal of zero amount in requestWithdrawal function * Add asset support check in approveStrategy function (#2776) * Fix Vault tests (#2790) * Fix base unit tests * Fix Ehereum unit tests * Remove redundant fork tests * fix approval * Fix CI --------- Co-authored-by: Nick Addison Co-authored-by: Domen Grabec Co-authored-by: Shah <10547529+shahthepro@users.noreply.github.com> --- contracts/README.md | 5 +- .../contracts/harvest/AbstractHarvester.sol | 6 +- contracts/contracts/interfaces/IVault.sol | 99 +- .../mocks/MockEvilReentrantContract.sol | 5 +- contracts/contracts/mocks/MockNonRebasing.sol | 2 +- contracts/contracts/mocks/MockOETHVault.sol | 15 +- .../contracts/mocks/MockOETHVaultAdmin.sol | 8 +- .../contracts/mocks/MockRebornMinter.sol | 4 +- contracts/contracts/mocks/MockStrategy.sol | 12 +- contracts/contracts/mocks/MockVault.sol | 13 +- .../mocks/MockVaultCoreInstantRebase.sol | 2 + .../strategies/BridgedWOETHStrategy.sol | 11 +- contracts/contracts/vault/OETHBaseVault.sol | 12 + .../contracts/vault/OETHBaseVaultAdmin.sol | 12 - .../contracts/vault/OETHBaseVaultCore.sol | 38 - contracts/contracts/vault/OETHPlumeVault.sol | 29 + .../contracts/vault/OETHPlumeVaultCore.sol | 47 - contracts/contracts/vault/OETHVault.sol | 8 +- contracts/contracts/vault/OETHVaultAdmin.sol | 144 -- contracts/contracts/vault/OETHVaultCore.sol | 524 ----- contracts/contracts/vault/OSVault.sol | 12 + .../contracts/vault/OSonicVaultAdmin.sol | 43 - contracts/contracts/vault/OSonicVaultCore.sol | 21 - contracts/contracts/vault/OUSDVault.sol | 12 + contracts/contracts/vault/README.md | 54 +- contracts/contracts/vault/Vault.sol | 12 - contracts/contracts/vault/VaultAdmin.sol | 493 +--- contracts/contracts/vault/VaultCore.sol | 939 +++----- .../contracts/vault/VaultInitializer.sol | 21 +- contracts/contracts/vault/VaultStorage.sol | 115 +- .../AbstractOTokenZapper.sol | 0 .../{vault => zapper}/OETHBaseZapper.sol | 0 .../{vault => zapper}/OETHZapper.sol | 0 .../{vault => zapper}/OSonicZapper.sol | 0 contracts/deploy/base/000_mock.js | 30 +- contracts/deploy/base/040_vault_upgrade.js | 79 + contracts/deploy/deployActions.js | 199 +- contracts/deploy/mainnet/000_mock.js | 2 + contracts/deploy/mainnet/001_core.js | 2 +- .../deploy/mainnet/167_ousd_vault_upgrade.js | 58 + .../deploy/mainnet/168_oeth_vault_upgrade.js | 54 + contracts/deploy/plume/002_core.js | 9 +- contracts/deploy/sonic/000_mock.js | 45 +- contracts/deploy/sonic/001_vault_and_token.js | 38 +- contracts/deploy/sonic/026_vault_upgrade.js | 56 + contracts/docs/OETHBaseVaultAdminSquashed.svg | 167 -- contracts/docs/OETHBaseVaultCoreSquashed.svg | 173 -- contracts/docs/OETHBaseVaultStorage.svg | 382 ---- contracts/docs/OETHVaultAdminSquashed.svg | 167 -- contracts/docs/OETHVaultCoreSquashed.svg | 173 -- contracts/docs/OETHVaultStorage.svg | 359 --- contracts/docs/OSonicVaultAdminSquashed.svg | 167 -- contracts/docs/OSonicVaultCoreSquashed.svg | 173 -- contracts/docs/OSonicVaultStorage.svg | 359 --- contracts/docs/VaultCoreSquashed.svg | 174 -- contracts/docs/VaultHierarchy.svg | 261 +-- ...ultAdminSquashed.svg => VaultSquashed.svg} | 322 +-- contracts/docs/VaultStorage.svg | 598 +++-- contracts/docs/generate.sh | 22 +- contracts/docs/plantuml/baseContracts.png | Bin 63241 -> 58803 bytes contracts/docs/plantuml/baseContracts.puml | 6 +- contracts/docs/plantuml/ousdContracts.png | Bin 30678 -> 26210 bytes contracts/docs/plantuml/ousdContracts.puml | 11 +- contracts/node.sh | 2 - contracts/package.json | 4 +- contracts/scripts/governor/propose.js | 138 -- contracts/smoke/mintRedeemTest.js | 206 -- contracts/tasks/account.js | 170 -- contracts/tasks/tasks.js | 47 +- contracts/tasks/vault.js | 67 - contracts/test/_fixture-base.js | 18 +- contracts/test/_fixture-plume.js | 13 +- contracts/test/_fixture-sonic.js | 9 +- contracts/test/_fixture.js | 180 +- contracts/test/_hot-deploy.js | 86 +- contracts/test/_metastrategies-fixtures.js | 13 +- .../beaconConsolidation.mainnet.fork-test.js | 35 - contracts/test/behaviour/harvester.js | 14 +- .../test/behaviour/sfcStakingStrategy.js | 19 +- contracts/test/behaviour/ssvStrategy.js | 9 +- .../oethb-timelock.base.fork-test.js | 9 +- contracts/test/hacks/reborn.js | 21 +- contracts/test/hacks/reentrant.js | 22 - contracts/test/oracle/oracle.js | 90 - .../poolBooster.sonic.fork-test.js | 2 +- .../bridge-helper.base.fork-test.js | 10 +- .../bridge-helper.mainnet.fork-test.js | 27 +- .../bridge-helper.plume.fork-test.js | 2 +- .../base/aerodrome-amo.base.fork-test.js | 15 +- .../test/strategies/compoundingSSVStaking.js | 18 + contracts/test/strategies/convex.js | 65 +- .../curve-amo-ousd.mainnet.fork-test.js | 1 + contracts/test/strategies/dripper.js | 13 +- .../sonic/swapx-amo.sonic.fork-test.js | 6 +- .../test/strategies/vault-value-checker.js | 81 +- contracts/test/token/ousd.js | 48 +- contracts/test/token/woeth.js | 2 +- contracts/test/token/wousd.js | 26 +- .../collateral-swaps.mainnet.fork-test.js | 332 --- contracts/test/vault/compound.js | 541 +---- contracts/test/vault/deposit.js | 4 +- contracts/test/vault/exchangeRate.js | 262 --- .../test/vault/harvester.mainnet.fork-test.js | 7 - contracts/test/vault/index.js | 380 +--- contracts/test/vault/oeth-vault.js | 346 +-- .../vault/oeth-vault.mainnet.fork-test.js | 219 +- .../test/vault/oethb-vault.base.fork-test.js | 86 +- contracts/test/vault/oethb-vault.base.js | 80 +- contracts/test/vault/oneinch-swapper.js | 524 ----- contracts/test/vault/os-vault.sonic.js | 7 - contracts/test/vault/rebase.js | 44 +- contracts/test/vault/redeem.js | 2024 +++++++++++++---- contracts/test/vault/upgrade.js | 27 - .../test/vault/vault.mainnet.fork-test.js | 86 +- contracts/test/vault/vault.sonic.fork-test.js | 5 - contracts/test/vault/z_mockvault.js | 5 +- 116 files changed, 3890 insertions(+), 9381 deletions(-) create mode 100644 contracts/contracts/vault/OETHBaseVault.sol delete mode 100644 contracts/contracts/vault/OETHBaseVaultAdmin.sol delete mode 100644 contracts/contracts/vault/OETHBaseVaultCore.sol create mode 100644 contracts/contracts/vault/OETHPlumeVault.sol delete mode 100644 contracts/contracts/vault/OETHPlumeVaultCore.sol delete mode 100644 contracts/contracts/vault/OETHVaultAdmin.sol delete mode 100644 contracts/contracts/vault/OETHVaultCore.sol create mode 100644 contracts/contracts/vault/OSVault.sol delete mode 100644 contracts/contracts/vault/OSonicVaultAdmin.sol delete mode 100644 contracts/contracts/vault/OSonicVaultCore.sol create mode 100644 contracts/contracts/vault/OUSDVault.sol delete mode 100644 contracts/contracts/vault/Vault.sol rename contracts/contracts/{vault => zapper}/AbstractOTokenZapper.sol (100%) rename contracts/contracts/{vault => zapper}/OETHBaseZapper.sol (100%) rename contracts/contracts/{vault => zapper}/OETHZapper.sol (100%) rename contracts/contracts/{vault => zapper}/OSonicZapper.sol (100%) create mode 100644 contracts/deploy/base/040_vault_upgrade.js create mode 100644 contracts/deploy/mainnet/167_ousd_vault_upgrade.js create mode 100644 contracts/deploy/mainnet/168_oeth_vault_upgrade.js create mode 100644 contracts/deploy/sonic/026_vault_upgrade.js delete mode 100644 contracts/docs/OETHBaseVaultAdminSquashed.svg delete mode 100644 contracts/docs/OETHBaseVaultCoreSquashed.svg delete mode 100644 contracts/docs/OETHBaseVaultStorage.svg delete mode 100644 contracts/docs/OETHVaultAdminSquashed.svg delete mode 100644 contracts/docs/OETHVaultCoreSquashed.svg delete mode 100644 contracts/docs/OETHVaultStorage.svg delete mode 100644 contracts/docs/OSonicVaultAdminSquashed.svg delete mode 100644 contracts/docs/OSonicVaultCoreSquashed.svg delete mode 100644 contracts/docs/OSonicVaultStorage.svg delete mode 100644 contracts/docs/VaultCoreSquashed.svg rename contracts/docs/{VaultAdminSquashed.svg => VaultSquashed.svg} (70%) delete mode 100644 contracts/smoke/mintRedeemTest.js delete mode 100644 contracts/test/beacon/beaconConsolidation.mainnet.fork-test.js delete mode 100644 contracts/test/hacks/reentrant.js delete mode 100644 contracts/test/oracle/oracle.js delete mode 100644 contracts/test/vault/collateral-swaps.mainnet.fork-test.js delete mode 100644 contracts/test/vault/exchangeRate.js delete mode 100644 contracts/test/vault/oneinch-swapper.js delete mode 100644 contracts/test/vault/upgrade.js diff --git a/contracts/README.md b/contracts/README.md index c7d9b283e0..220c8e0cb6 100644 --- a/contracts/README.md +++ b/contracts/README.md @@ -125,11 +125,10 @@ You can enable the "hot deploy" mode when doing fork testing development. The mo To enable Hot Deploys set the HOT_DEPLOY variable in the contracts/.env file. Enable various modes using comma separated flags to direct which contracts need source updated (in the node runtime): - strategy -> strategy contract associated to fixture -- vaultCore -> vaultCore or oethVaultCore depending on the nature of the fixture -- vaultAdmin -> vaultAdmin or oethVaultAdmin depending on the nature of the fixture +- vault -> OUSDVault or OETHVault depending on the nature of the fixture - harvester -> harvester or oethHarvester (not yet supported) -example: HOT_DEPLOY=strategy,vaultCore,vaultAdmin,harvester +example: HOT_DEPLOY=strategy,vault,harvester #### Supporting new fixtures / contracts diff --git a/contracts/contracts/harvest/AbstractHarvester.sol b/contracts/contracts/harvest/AbstractHarvester.sol index 99471bdad7..287bbc03e2 100644 --- a/contracts/contracts/harvest/AbstractHarvester.sol +++ b/contracts/contracts/harvest/AbstractHarvester.sol @@ -198,7 +198,6 @@ abstract contract AbstractHarvester is Governable { // Revert if feed does not exist // slither-disable-next-line unused-return - IOracle(IVault(vaultAddress).priceProvider()).price(_tokenAddress); IERC20 token = IERC20(_tokenAddress); // if changing token swap provider cancel existing allowance @@ -443,10 +442,11 @@ abstract contract AbstractHarvester is Governable { _harvest(_strategyAddr); IStrategy strategy = IStrategy(_strategyAddr); address[] memory rewardTokens = strategy.getRewardTokenAddresses(); - IOracle priceProvider = IOracle(IVault(vaultAddress).priceProvider()); uint256 len = rewardTokens.length; for (uint256 i = 0; i < len; ++i) { - _swap(rewardTokens[i], _rewardTo, priceProvider); + // This harvester contract is not used anymore. Keeping the code + // for passing test deployment. Safe to use address(0x1) as oracle. + _swap(rewardTokens[i], _rewardTo, IOracle(address(0x1))); } } diff --git a/contracts/contracts/interfaces/IVault.sol b/contracts/contracts/interfaces/IVault.sol index 0b0bc98e93..c517cf82b0 100644 --- a/contracts/contracts/interfaces/IVault.sol +++ b/contracts/contracts/interfaces/IVault.sol @@ -6,8 +6,6 @@ import { VaultStorage } from "../vault/VaultStorage.sol"; interface IVault { // slither-disable-start constable-states - event AssetSupported(address _asset); - event AssetDefaultStrategyUpdated(address _asset, address _strategy); event AssetAllocated(address _asset, address _strategy, uint256 _amount); event StrategyApproved(address _addr); event StrategyRemoved(address _addr); @@ -15,11 +13,10 @@ interface IVault { event Redeem(address _addr, uint256 _value); event CapitalPaused(); event CapitalUnpaused(); + event DefaultStrategyUpdated(address _strategy); event RebasePaused(); event RebaseUnpaused(); event VaultBufferUpdated(uint256 _vaultBuffer); - event RedeemFeeUpdated(uint256 _redeemFeeBps); - event PriceProviderUpdated(address _priceProvider); event AllocateThresholdUpdated(uint256 _threshold); event RebaseThresholdUpdated(uint256 _threshold); event StrategistUpdated(address _address); @@ -27,18 +24,10 @@ interface IVault { event YieldDistribution(address _to, uint256 _yield, uint256 _fee); event TrusteeFeeBpsChanged(uint256 _basis); event TrusteeAddressChanged(address _address); - event SwapperChanged(address _address); - event SwapAllowedUndervalueChanged(uint256 _basis); - event SwapSlippageChanged(address _asset, uint256 _basis); - event Swapped( - address indexed _fromAsset, - address indexed _toAsset, - uint256 _fromAssetAmount, - uint256 _toAssetAmount - ); event StrategyAddedToMintWhitelist(address indexed strategy); event StrategyRemovedFromMintWhitelist(address indexed strategy); - event DripperChanged(address indexed _dripper); + event RebasePerSecondMaxChanged(uint256 rebaseRatePerSecond); + event DripDurationChanged(uint256 dripDuration); event WithdrawalRequested( address indexed _withdrawer, uint256 indexed _requestId, @@ -51,6 +40,7 @@ interface IVault { uint256 _amount ); event WithdrawalClaimable(uint256 _claimable, uint256 _newClaimable); + event WithdrawalClaimDelayUpdated(uint256 _newDelay); // Governable.sol function transferGovernance(address _newGovernor) external; @@ -59,17 +49,7 @@ interface IVault { function governor() external view returns (address); - function ADMIN_IMPLEMENTATION() external view returns (address); - // VaultAdmin.sol - function setPriceProvider(address _priceProvider) external; - - function priceProvider() external view returns (address); - - function setRedeemFeeBps(uint256 _redeemFeeBps) external; - - function redeemFeeBps() external view returns (uint256); - function setVaultBuffer(uint256 _vaultBuffer) external; function vaultBuffer() external view returns (uint256); @@ -98,28 +78,13 @@ interface IVault { function trusteeFeeBps() external view returns (uint256); - function ousdMetaStrategy() external view returns (address); - - function setSwapper(address _swapperAddr) external; - - function setSwapAllowedUndervalue(uint16 _percentageBps) external; - - function setOracleSlippage(address _asset, uint16 _allowedOracleSlippageBps) - external; - - function supportAsset(address _asset, uint8 _unitConversion) external; - function approveStrategy(address _addr) external; function removeStrategy(address _addr) external; - function setAssetDefaultStrategy(address _asset, address _strategy) - external; + function setDefaultStrategy(address _strategy) external; - function assetDefaultStrategies(address _asset) - external - view - returns (address); + function defaultStrategy() external view returns (address); function pauseRebase() external; @@ -135,10 +100,6 @@ interface IVault { function transferToken(address _asset, uint256 _amount) external; - function priceUnitMint(address asset) external view returns (uint256); - - function priceUnitRedeem(address asset) external view returns (uint256); - function withdrawAllFromStrategy(address _strategyAddr) external; function withdrawAllFromStrategies() external; @@ -172,67 +133,38 @@ interface IVault { function rebase() external; - function swapCollateral( - address fromAsset, - address toAsset, - uint256 fromAssetAmount, - uint256 minToAssetAmount, - bytes calldata data - ) external returns (uint256 toAssetAmount); - function totalValue() external view returns (uint256 value); function checkBalance(address _asset) external view returns (uint256); + /// @notice Deprecated: use calculateRedeemOutput function calculateRedeemOutputs(uint256 _amount) external view returns (uint256[] memory); - function getAssetCount() external view returns (uint256); - - function getAssetConfig(address _asset) + function calculateRedeemOutput(uint256 _amount) external view - returns (VaultStorage.Asset memory config); + returns (uint256); + + function getAssetCount() external view returns (uint256); function getAllAssets() external view returns (address[] memory); function getStrategyCount() external view returns (uint256); - function swapper() external view returns (address); - - function allowedSwapUndervalue() external view returns (uint256); - function getAllStrategies() external view returns (address[] memory); + /// @notice Deprecated. function isSupportedAsset(address _asset) external view returns (bool); - function netOusdMintForStrategyThreshold() external view returns (uint256); - - function setOusdMetaStrategy(address _ousdMetaStrategy) external; - - function setNetOusdMintForStrategyThreshold(uint256 _threshold) external; - - function netOusdMintedForStrategy() external view returns (int256); - - function setDripper(address _dripper) external; - function dripper() external view returns (address); - function weth() external view returns (address); - - function cacheWETHAssetIndex() external; - - function wethAssetIndex() external view returns (uint256); + function asset() external view returns (address); - function initialize(address, address) external; + function initialize(address) external; - function setAdminImpl(address) external; - - function removeAsset(address _asset) external; - - // These are OETH specific functions function addWithdrawalQueueLiquidity() external; function requestWithdrawal(uint256 _amount) @@ -257,7 +189,6 @@ interface IVault { view returns (VaultStorage.WithdrawalRequest memory); - // OETHb specific functions function addStrategyToMintWhitelist(address strategyAddr) external; function removeStrategyFromMintWhitelist(address strategyAddr) external; @@ -285,5 +216,7 @@ interface IVault { function previewYield() external view returns (uint256 yield); + function weth() external view returns (address); + // slither-disable-end constable-states } diff --git a/contracts/contracts/mocks/MockEvilReentrantContract.sol b/contracts/contracts/mocks/MockEvilReentrantContract.sol index 47fee94c19..fea2c81bb1 100644 --- a/contracts/contracts/mocks/MockEvilReentrantContract.sol +++ b/contracts/contracts/mocks/MockEvilReentrantContract.sol @@ -19,6 +19,7 @@ contract MockEvilReentrantContract { IVault public immutable oethVault; address public immutable poolAddress; bytes32 public immutable balancerPoolId; + address public immutable priceProvider; constructor( address _balancerVault, @@ -37,7 +38,6 @@ contract MockEvilReentrantContract { } function doEvilStuff() public { - address priceProvider = oethVault.priceProvider(); uint256 rethPrice = IOracle(priceProvider).price(address(reth)); // 1. Join pool @@ -99,9 +99,6 @@ contract MockEvilReentrantContract { virtual returns (uint256 bptExpected) { - // Get the oracle from the OETH Vault - address priceProvider = oethVault.priceProvider(); - for (uint256 i = 0; i < _assets.length; ++i) { uint256 strategyAssetMarketPrice = IOracle(priceProvider).price( _assets[i] diff --git a/contracts/contracts/mocks/MockNonRebasing.sol b/contracts/contracts/mocks/MockNonRebasing.sol index 61bbe79c6e..b3ad870347 100644 --- a/contracts/contracts/mocks/MockNonRebasing.sol +++ b/contracts/contracts/mocks/MockNonRebasing.sol @@ -47,7 +47,7 @@ contract MockNonRebasing { } function redeemOusd(address _vaultContract, uint256 _amount) public { - IVault(_vaultContract).redeem(_amount, 0); + IVault(_vaultContract).requestWithdrawal(_amount); } function approveFor( diff --git a/contracts/contracts/mocks/MockOETHVault.sol b/contracts/contracts/mocks/MockOETHVault.sol index 9e34d8430f..7b72c90189 100644 --- a/contracts/contracts/mocks/MockOETHVault.sol +++ b/contracts/contracts/mocks/MockOETHVault.sol @@ -1,25 +1,18 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.0; -import { OETHVaultCore } from "../vault/OETHVaultCore.sol"; +import { OETHVault } from "../vault/OETHVault.sol"; import { StableMath } from "../utils/StableMath.sol"; import "../utils/Helpers.sol"; -contract MockOETHVault is OETHVaultCore { +contract MockOETHVault is OETHVault { using StableMath for uint256; - constructor(address _weth) OETHVaultCore(_weth) { + constructor(address _weth) OETHVault(_weth) { _setGovernor(msg.sender); } function supportAsset(address asset) external { - assets[asset] = Asset({ - isSupported: true, - unitConversion: UnitConversion(0), - decimals: 18, - allowedOracleSlippageBps: 0 - }); - - allAssets.push(asset); + require(asset == asset, "Only asset supported"); } } diff --git a/contracts/contracts/mocks/MockOETHVaultAdmin.sol b/contracts/contracts/mocks/MockOETHVaultAdmin.sol index a2664212b5..43c9689bd8 100644 --- a/contracts/contracts/mocks/MockOETHVaultAdmin.sol +++ b/contracts/contracts/mocks/MockOETHVaultAdmin.sol @@ -1,10 +1,10 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.0; -import { OETHVaultAdmin } from "../vault/OETHVaultAdmin.sol"; +import { OETHVault } from "../vault/OETHVault.sol"; -contract MockOETHVaultAdmin is OETHVaultAdmin { - constructor(address _weth) OETHVaultAdmin(_weth) {} +contract MockOETHVault is OETHVault { + constructor(address _weth) OETHVault(_weth) {} // fetches the WETH amount in outstanding withdrawals function outstandingWithdrawalsAmount() @@ -19,6 +19,6 @@ contract MockOETHVaultAdmin is OETHVaultAdmin { } function wethAvailable() external view returns (uint256) { - return _wethAvailable(); + return _assetAvailable(); } } diff --git a/contracts/contracts/mocks/MockRebornMinter.sol b/contracts/contracts/mocks/MockRebornMinter.sol index b0c812ca56..aa7394b7d1 100644 --- a/contracts/contracts/mocks/MockRebornMinter.sol +++ b/contracts/contracts/mocks/MockRebornMinter.sol @@ -97,8 +97,8 @@ contract Reborner { log("We are attempting to mint.."); address asset = sanctum.asset(); address vault = sanctum.vault(); - IERC20(asset).approve(vault, 1e18); - IVault(vault).mint(asset, 1e18, 0); + IERC20(asset).approve(vault, 1e6); + IVault(vault).mint(asset, 1e6, 0); log("We are now minting.."); } diff --git a/contracts/contracts/mocks/MockStrategy.sol b/contracts/contracts/mocks/MockStrategy.sol index 95c8bf7a30..df3eb5b636 100644 --- a/contracts/contracts/mocks/MockStrategy.sol +++ b/contracts/contracts/mocks/MockStrategy.sol @@ -9,7 +9,11 @@ contract MockStrategy { address public withdrawAllAsset; address public withdrawAllRecipient; - constructor() {} + bool public shouldSupportAsset; + + constructor() { + shouldSupportAsset = true; + } function deposit(address asset, uint256 amount) external {} @@ -39,7 +43,11 @@ contract MockStrategy { } function supportsAsset(address) external view returns (bool) { - return true; + return shouldSupportAsset; + } + + function setShouldSupportAsset(bool _shouldSupportAsset) external { + shouldSupportAsset = _shouldSupportAsset; } function collectRewardTokens() external {} diff --git a/contracts/contracts/mocks/MockVault.sol b/contracts/contracts/mocks/MockVault.sol index 717c1f31f3..fb084b6883 100644 --- a/contracts/contracts/mocks/MockVault.sol +++ b/contracts/contracts/mocks/MockVault.sol @@ -1,16 +1,17 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.0; -import { VaultCore } from "../vault/VaultCore.sol"; +import { VaultAdmin } from "../vault/VaultAdmin.sol"; import { StableMath } from "../utils/StableMath.sol"; -import { VaultInitializer } from "../vault/VaultInitializer.sol"; import "../utils/Helpers.sol"; -contract MockVault is VaultCore { +contract MockVault is VaultAdmin { using StableMath for uint256; uint256 storedTotalValue; + constructor(address _asset) VaultAdmin(_asset) {} + function setTotalValue(uint256 _value) public { storedTotalValue = _value; } @@ -31,15 +32,11 @@ contract MockVault is VaultCore { { // Avoids rounding errors by returning the total value // in a single currency - if (allAssets[0] == _asset) { + if (asset == _asset) { uint256 decimals = Helpers.getDecimals(_asset); return storedTotalValue.scaleBy(decimals, 18); } else { return 0; } } - - function setMaxSupplyDiff(uint256 _maxSupplyDiff) external onlyGovernor { - maxSupplyDiff = _maxSupplyDiff; - } } diff --git a/contracts/contracts/mocks/MockVaultCoreInstantRebase.sol b/contracts/contracts/mocks/MockVaultCoreInstantRebase.sol index 7043497725..e10e66670b 100644 --- a/contracts/contracts/mocks/MockVaultCoreInstantRebase.sol +++ b/contracts/contracts/mocks/MockVaultCoreInstantRebase.sol @@ -4,6 +4,8 @@ pragma solidity ^0.8.0; import { VaultCore } from "../vault/VaultCore.sol"; contract MockVaultCoreInstantRebase is VaultCore { + constructor(address _asset) VaultCore(_asset) {} + function _nextYield(uint256 supply, uint256 vaultValue) internal view diff --git a/contracts/contracts/strategies/BridgedWOETHStrategy.sol b/contracts/contracts/strategies/BridgedWOETHStrategy.sol index 1f602318d0..37c029acb6 100644 --- a/contracts/contracts/strategies/BridgedWOETHStrategy.sol +++ b/contracts/contracts/strategies/BridgedWOETHStrategy.sol @@ -21,8 +21,7 @@ contract BridgedWOETHStrategy is InitializableAbstractStrategy { IWETH9 public immutable weth; IERC20 public immutable bridgedWOETH; IERC20 public immutable oethb; - - uint256 public constant MAX_PRICE_STALENESS = 2 days; + IOracle public immutable oracle; uint128 public lastOraclePrice; uint128 public maxPriceDiffBps; @@ -31,11 +30,13 @@ contract BridgedWOETHStrategy is InitializableAbstractStrategy { BaseStrategyConfig memory _stratConfig, address _weth, address _bridgedWOETH, - address _oethb + address _oethb, + address _oracle ) InitializableAbstractStrategy(_stratConfig) { weth = IWETH9(_weth); bridgedWOETH = IERC20(_bridgedWOETH); oethb = IERC20(_oethb); + oracle = IOracle(_oracle); } function initialize(uint128 _maxPriceDiffBps) @@ -100,8 +101,7 @@ contract BridgedWOETHStrategy is InitializableAbstractStrategy { */ function _updateWOETHOraclePrice() internal returns (uint256) { // WETH price per unit of bridged wOETH - uint256 oraclePrice = IOracle(IVault(vaultAddress).priceProvider()) - .price(address(bridgedWOETH)); + uint256 oraclePrice = oracle.price(address(bridgedWOETH)); // 1 wOETH > 1 WETH, always require(oraclePrice > 1 ether, "Invalid wOETH value"); @@ -265,6 +265,7 @@ contract BridgedWOETHStrategy is InitializableAbstractStrategy { _asset != address(bridgedWOETH) && _asset != address(weth), "Cannot transfer supported asset" ); + // Use SafeERC20 only for rescuing unknown assets; core tokens are standard. IERC20(_asset).safeTransfer(governor(), _amount); } diff --git a/contracts/contracts/vault/OETHBaseVault.sol b/contracts/contracts/vault/OETHBaseVault.sol new file mode 100644 index 0000000000..16eebe01f5 --- /dev/null +++ b/contracts/contracts/vault/OETHBaseVault.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import { VaultAdmin } from "./VaultAdmin.sol"; + +/** + * @title OETH Base VaultAdmin Contract + * @author Origin Protocol Inc + */ +contract OETHBaseVault is VaultAdmin { + constructor(address _weth) VaultAdmin(_weth) {} +} diff --git a/contracts/contracts/vault/OETHBaseVaultAdmin.sol b/contracts/contracts/vault/OETHBaseVaultAdmin.sol deleted file mode 100644 index d29d863ac3..0000000000 --- a/contracts/contracts/vault/OETHBaseVaultAdmin.sol +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -import { OETHVaultAdmin } from "./OETHVaultAdmin.sol"; - -/** - * @title OETH Base VaultAdmin Contract - * @author Origin Protocol Inc - */ -contract OETHBaseVaultAdmin is OETHVaultAdmin { - constructor(address _weth) OETHVaultAdmin(_weth) {} -} diff --git a/contracts/contracts/vault/OETHBaseVaultCore.sol b/contracts/contracts/vault/OETHBaseVaultCore.sol deleted file mode 100644 index cfa0b6a6f7..0000000000 --- a/contracts/contracts/vault/OETHBaseVaultCore.sol +++ /dev/null @@ -1,38 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -import { StableMath } from "../utils/StableMath.sol"; -import { OETHVaultCore } from "./OETHVaultCore.sol"; - -import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { IStrategy } from "../interfaces/IStrategy.sol"; - -/** - * @title OETH Base VaultCore Contract - * @author Origin Protocol Inc - */ -contract OETHBaseVaultCore is OETHVaultCore { - using SafeERC20 for IERC20; - using StableMath for uint256; - - constructor(address _weth) OETHVaultCore(_weth) {} - - // @inheritdoc OETHVaultCore - function _redeem(uint256 _amount, uint256 _minimumUnitAmount) - internal - virtual - override - { - // Only Strategist or Governor can redeem using the Vault for now. - // We don't have the onlyGovernorOrStrategist modifier on VaultCore. - // Since we won't be using that modifier anywhere in the VaultCore as well, - // the check has been added inline instead of moving it to VaultStorage. - require( - msg.sender == strategistAddr || isGovernor(), - "Caller is not the Strategist or Governor" - ); - - super._redeem(_amount, _minimumUnitAmount); - } -} diff --git a/contracts/contracts/vault/OETHPlumeVault.sol b/contracts/contracts/vault/OETHPlumeVault.sol new file mode 100644 index 0000000000..5a4e8e81a8 --- /dev/null +++ b/contracts/contracts/vault/OETHPlumeVault.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import { VaultAdmin } from "./VaultAdmin.sol"; + +/** + * @title OETH Plume VaultAdmin Contract + * @author Origin Protocol Inc + */ +contract OETHPlumeVault is VaultAdmin { + constructor(address _weth) VaultAdmin(_weth) {} + + // @inheritdoc VaultAdmin + function _mint( + address, + uint256 _amount, + uint256 + ) internal virtual { + // Only Strategist or Governor can mint using the Vault for now. + // This allows the strateigst to fund the Vault with WETH when + // removing liquidi from wOETH strategy. + require( + msg.sender == strategistAddr || isGovernor(), + "Caller is not the Strategist or Governor" + ); + + super._mint(_amount); + } +} diff --git a/contracts/contracts/vault/OETHPlumeVaultCore.sol b/contracts/contracts/vault/OETHPlumeVaultCore.sol deleted file mode 100644 index 28404a4c84..0000000000 --- a/contracts/contracts/vault/OETHPlumeVaultCore.sol +++ /dev/null @@ -1,47 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -import { OETHVaultCore } from "./OETHVaultCore.sol"; - -/** - * @title OETH Plume VaultCore Contract - * @author Origin Protocol Inc - */ -contract OETHPlumeVaultCore is OETHVaultCore { - constructor(address _weth) OETHVaultCore(_weth) {} - - // @inheritdoc OETHVaultCore - function _mint( - address _asset, - uint256 _amount, - uint256 _minimumOusdAmount - ) internal virtual override { - // Only Strategist or Governor can mint using the Vault for now. - // This allows the strateigst to fund the Vault with WETH when - // removing liquidi from wOETH strategy. - require( - msg.sender == strategistAddr || isGovernor(), - "Caller is not the Strategist or Governor" - ); - - super._mint(_asset, _amount, _minimumOusdAmount); - } - - // @inheritdoc OETHVaultCore - function _redeem(uint256 _amount, uint256 _minimumUnitAmount) - internal - virtual - override - { - // Only Strategist or Governor can redeem using the Vault for now. - // We don't have the onlyGovernorOrStrategist modifier on VaultCore. - // Since we won't be using that modifier anywhere in the VaultCore as well, - // the check has been added inline instead of moving it to VaultStorage. - require( - msg.sender == strategistAddr || isGovernor(), - "Caller is not the Strategist or Governor" - ); - - super._redeem(_amount, _minimumUnitAmount); - } -} diff --git a/contracts/contracts/vault/OETHVault.sol b/contracts/contracts/vault/OETHVault.sol index 0ec1690ad5..eb7d3b9f55 100644 --- a/contracts/contracts/vault/OETHVault.sol +++ b/contracts/contracts/vault/OETHVault.sol @@ -1,12 +1,12 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.0; -import { Vault } from "./Vault.sol"; +import { VaultAdmin } from "./VaultAdmin.sol"; /** - * @title OETH Vault Contract + * @title OETH VaultAdmin Contract * @author Origin Protocol Inc */ -contract OETHVault is Vault { - +contract OETHVault is VaultAdmin { + constructor(address _weth) VaultAdmin(_weth) {} } diff --git a/contracts/contracts/vault/OETHVaultAdmin.sol b/contracts/contracts/vault/OETHVaultAdmin.sol deleted file mode 100644 index 080dec140b..0000000000 --- a/contracts/contracts/vault/OETHVaultAdmin.sol +++ /dev/null @@ -1,144 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; - -import { IStrategy } from "../interfaces/IStrategy.sol"; -import { IVault } from "../interfaces/IVault.sol"; -import { VaultAdmin } from "./VaultAdmin.sol"; - -/** - * @title OETH VaultAdmin Contract - * @author Origin Protocol Inc - */ -contract OETHVaultAdmin is VaultAdmin { - using SafeERC20 for IERC20; - - address public immutable weth; - - constructor(address _weth) { - weth = _weth; - } - - /** - * @notice Adds a strategy to the mint whitelist. - * Reverts if strategy isn't approved on Vault. - * @param strategyAddr Strategy address - */ - function addStrategyToMintWhitelist(address strategyAddr) - external - onlyGovernor - { - require(strategies[strategyAddr].isSupported, "Strategy not approved"); - - require( - !isMintWhitelistedStrategy[strategyAddr], - "Already whitelisted" - ); - - isMintWhitelistedStrategy[strategyAddr] = true; - - emit StrategyAddedToMintWhitelist(strategyAddr); - } - - /** - * @notice Removes a strategy from the mint whitelist. - * @param strategyAddr Strategy address - */ - function removeStrategyFromMintWhitelist(address strategyAddr) - external - onlyGovernor - { - // Intentionally skipping `strategies.isSupported` check since - // we may wanna remove an address even after removing the strategy - - require(isMintWhitelistedStrategy[strategyAddr], "Not whitelisted"); - - isMintWhitelistedStrategy[strategyAddr] = false; - - emit StrategyRemovedFromMintWhitelist(strategyAddr); - } - - /// @dev Simplified version of the deposit function as WETH is the only supported asset. - function _depositToStrategy( - address _strategyToAddress, - address[] calldata _assets, - uint256[] calldata _amounts - ) internal override { - require( - strategies[_strategyToAddress].isSupported, - "Invalid to Strategy" - ); - require( - _assets.length == 1 && _amounts.length == 1 && _assets[0] == weth, - "Only WETH is supported" - ); - - // Check the there is enough WETH to transfer once the WETH reserved for the withdrawal queue is accounted for - require(_amounts[0] <= _wethAvailable(), "Not enough WETH available"); - - // Send required amount of funds to the strategy - IERC20(weth).safeTransfer(_strategyToAddress, _amounts[0]); - - // Deposit all the funds that have been sent to the strategy - IStrategy(_strategyToAddress).depositAll(); - } - - function _withdrawFromStrategy( - address _recipient, - address _strategyFromAddress, - address[] calldata _assets, - uint256[] calldata _amounts - ) internal override { - super._withdrawFromStrategy( - _recipient, - _strategyFromAddress, - _assets, - _amounts - ); - - IVault(address(this)).addWithdrawalQueueLiquidity(); - } - - function _withdrawAllFromStrategy(address _strategyAddr) internal override { - super._withdrawAllFromStrategy(_strategyAddr); - - IVault(address(this)).addWithdrawalQueueLiquidity(); - } - - function _withdrawAllFromStrategies() internal override { - super._withdrawAllFromStrategies(); - - IVault(address(this)).addWithdrawalQueueLiquidity(); - } - - /// @dev Calculate how much WETH in the vault is not reserved for the withdrawal queue. - // That is, it is available to be redeemed or deposited into a strategy. - function _wethAvailable() internal view returns (uint256 wethAvailable) { - WithdrawalQueueMetadata memory queue = withdrawalQueueMetadata; - - // The amount of WETH that is still to be claimed in the withdrawal queue - uint256 outstandingWithdrawals = queue.queued - queue.claimed; - - // The amount of sitting in WETH in the vault - uint256 wethBalance = IERC20(weth).balanceOf(address(this)); - - // If there is not enough WETH in the vault to cover the outstanding withdrawals - if (wethBalance <= outstandingWithdrawals) { - return 0; - } - - return wethBalance - outstandingWithdrawals; - } - - function _swapCollateral( - address, - address, - uint256, - uint256, - bytes calldata - ) internal pure override returns (uint256) { - revert("Collateral swap not supported"); - } -} diff --git a/contracts/contracts/vault/OETHVaultCore.sol b/contracts/contracts/vault/OETHVaultCore.sol deleted file mode 100644 index cbc59a8eee..0000000000 --- a/contracts/contracts/vault/OETHVaultCore.sol +++ /dev/null @@ -1,524 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; - -import { StableMath } from "../utils/StableMath.sol"; -import { VaultCore } from "./VaultCore.sol"; -import { IStrategy } from "../interfaces/IStrategy.sol"; - -/** - * @title OETH VaultCore Contract - * @author Origin Protocol Inc - */ -contract OETHVaultCore is VaultCore { - using SafeERC20 for IERC20; - using StableMath for uint256; - - address public immutable weth; - uint256 public wethAssetIndex; - - // For future use (because OETHBaseVaultCore inherits from this) - uint256[50] private __gap; - - constructor(address _weth) { - weth = _weth; - } - - /** - * @dev Caches WETH's index in `allAssets` variable. - * Reduces gas usage by redeem by caching that. - */ - function cacheWETHAssetIndex() external onlyGovernor { - uint256 assetCount = allAssets.length; - for (uint256 i; i < assetCount; ++i) { - if (allAssets[i] == weth) { - wethAssetIndex = i; - break; - } - } - - require(allAssets[wethAssetIndex] == weth, "Invalid WETH Asset Index"); - } - - // @inheritdoc VaultCore - function mintForStrategy(uint256 amount) - external - override - whenNotCapitalPaused - { - require( - strategies[msg.sender].isSupported == true, - "Unsupported strategy" - ); - require( - isMintWhitelistedStrategy[msg.sender] == true, - "Not whitelisted strategy" - ); - - emit Mint(msg.sender, amount); - - // Mint matching amount of OTokens - oUSD.mint(msg.sender, amount); - } - - // @inheritdoc VaultCore - function burnForStrategy(uint256 amount) - external - override - whenNotCapitalPaused - { - require( - strategies[msg.sender].isSupported == true, - "Unsupported strategy" - ); - require( - isMintWhitelistedStrategy[msg.sender] == true, - "Not whitelisted strategy" - ); - - emit Redeem(msg.sender, amount); - - // Burn OTokens - oUSD.burn(msg.sender, amount); - } - - // @inheritdoc VaultCore - // slither-disable-start reentrancy-no-eth - function _mint( - address _asset, - uint256 _amount, - uint256 _minimumOusdAmount - ) internal virtual override { - require(_asset == weth, "Unsupported asset for minting"); - require(_amount > 0, "Amount must be greater than 0"); - require( - _amount >= _minimumOusdAmount, - "Mint amount lower than minimum" - ); - - emit Mint(msg.sender, _amount); - - // Rebase must happen before any transfers occur. - if (!rebasePaused && _amount >= rebaseThreshold) { - _rebase(); - } - - // Mint oTokens - oUSD.mint(msg.sender, _amount); - - // Transfer the deposited coins to the vault - IERC20(_asset).safeTransferFrom(msg.sender, address(this), _amount); - - // Give priority to the withdrawal queue for the new WETH liquidity - _addWithdrawalQueueLiquidity(); - - // Auto-allocate if necessary - if (_amount >= autoAllocateThreshold) { - _allocate(); - } - } - - // slither-disable-end reentrancy-no-eth - - // @inheritdoc VaultCore - function _calculateRedeemOutputs(uint256 _amount) - internal - view - virtual - override - returns (uint256[] memory outputs) - { - // Overrides `VaultCore._calculateRedeemOutputs` to redeem with only - // WETH instead of LST-mix. Doesn't change the function signature - // for backward compatibility - - // Calculate redeem fee - if (redeemFeeBps > 0) { - uint256 redeemFee = _amount.mulTruncateScale(redeemFeeBps, 1e4); - _amount = _amount - redeemFee; - } - - // Ensure that the WETH index is cached - uint256 _wethAssetIndex = wethAssetIndex; - require( - allAssets[_wethAssetIndex] == weth, - "WETH Asset index not cached" - ); - - outputs = new uint256[](allAssets.length); - outputs[_wethAssetIndex] = _amount; - } - - // @inheritdoc VaultCore - function _redeem(uint256 _amount, uint256 _minimumUnitAmount) - internal - virtual - override - { - // Override `VaultCore._redeem` to simplify it. Gets rid of oracle - // usage and looping through all assets for LST-mix redeem. Instead - // does a simple WETH-only redeem. - emit Redeem(msg.sender, _amount); - - if (_amount == 0) { - return; - } - - // Amount excluding fees - // No fee for the strategist or the governor, makes it easier to do operations - uint256 amountMinusFee = (msg.sender == strategistAddr || isGovernor()) - ? _amount - : _calculateRedeemOutputs(_amount)[wethAssetIndex]; - - require( - amountMinusFee >= _minimumUnitAmount, - "Redeem amount lower than minimum" - ); - - // Is there enough WETH in the Vault available after accounting for the withdrawal queue - require(_wethAvailable() >= amountMinusFee, "Liquidity error"); - - // Transfer WETH minus the fee to the redeemer - IERC20(weth).safeTransfer(msg.sender, amountMinusFee); - - // Burn OETH from user (including fees) - oUSD.burn(msg.sender, _amount); - - // Prevent insolvency - _postRedeem(_amount); - } - - /** - * @notice Request an asynchronous withdrawal of WETH in exchange for OETH. - * The OETH is burned on request and the WETH is transferred to the withdrawer on claim. - * This request can be claimed once the withdrawal queue's `claimable` amount - * is greater than or equal this request's `queued` amount. - * There is a minimum of 10 minutes before a request can be claimed. After that, the request just needs - * enough WETH liquidity in the Vault to satisfy all the outstanding requests to that point in the queue. - * OETH is converted to WETH at 1:1. - * @param _amount Amount of OETH to burn. - * @return requestId Unique ID for the withdrawal request - * @return queued Cumulative total of all WETH queued including already claimed requests. - */ - function requestWithdrawal(uint256 _amount) - external - virtual - whenNotCapitalPaused - nonReentrant - returns (uint256 requestId, uint256 queued) - { - require(withdrawalClaimDelay > 0, "Async withdrawals not enabled"); - - // The check that the requester has enough OETH is done in to later burn call - - requestId = withdrawalQueueMetadata.nextWithdrawalIndex; - queued = withdrawalQueueMetadata.queued + _amount; - - // Store the next withdrawal request - withdrawalQueueMetadata.nextWithdrawalIndex = SafeCast.toUint128( - requestId + 1 - ); - // Store the updated queued amount which reserves WETH in the withdrawal queue - // and reduces the vault's total assets - withdrawalQueueMetadata.queued = SafeCast.toUint128(queued); - // Store the user's withdrawal request - withdrawalRequests[requestId] = WithdrawalRequest({ - withdrawer: msg.sender, - claimed: false, - timestamp: uint40(block.timestamp), - amount: SafeCast.toUint128(_amount), - queued: SafeCast.toUint128(queued) - }); - - // Burn the user's OETH - oUSD.burn(msg.sender, _amount); - - // Prevent withdrawal if the vault is solvent by more than the allowed percentage - _postRedeem(_amount); - - emit WithdrawalRequested(msg.sender, requestId, _amount, queued); - } - - // slither-disable-start reentrancy-no-eth - /** - * @notice Claim a previously requested withdrawal once it is claimable. - * This request can be claimed once the withdrawal queue's `claimable` amount - * is greater than or equal this request's `queued` amount and 10 minutes has passed. - * If the requests is not claimable, the transaction will revert with `Queue pending liquidity`. - * If the request is not older than 10 minutes, the transaction will revert with `Claim delay not met`. - * OETH is converted to WETH at 1:1. - * @param _requestId Unique ID for the withdrawal request - * @return amount Amount of WETH transferred to the withdrawer - */ - function claimWithdrawal(uint256 _requestId) - external - virtual - whenNotCapitalPaused - nonReentrant - returns (uint256 amount) - { - // Try and get more liquidity if there is not enough available - if ( - withdrawalRequests[_requestId].queued > - withdrawalQueueMetadata.claimable - ) { - // Add any WETH to the withdrawal queue - // this needs to remain here as: - // - Vault can be funded and `addWithdrawalQueueLiquidity` is not externally called - // - funds can be withdrawn from a strategy - // - // Those funds need to be added to withdrawal queue liquidity - _addWithdrawalQueueLiquidity(); - } - - amount = _claimWithdrawal(_requestId); - - // transfer WETH from the vault to the withdrawer - IERC20(weth).safeTransfer(msg.sender, amount); - - // Prevent insolvency - _postRedeem(amount); - } - - // slither-disable-end reentrancy-no-eth - - /** - * @notice Claim a previously requested withdrawals once they are claimable. - * This requests can be claimed once the withdrawal queue's `claimable` amount - * is greater than or equal each request's `queued` amount and 10 minutes has passed. - * If one of the requests is not claimable, the whole transaction will revert with `Queue pending liquidity`. - * If one of the requests is not older than 10 minutes, - * the whole transaction will revert with `Claim delay not met`. - * @param _requestIds Unique ID of each withdrawal request - * @return amounts Amount of WETH received for each request - * @return totalAmount Total amount of WETH transferred to the withdrawer - */ - function claimWithdrawals(uint256[] calldata _requestIds) - external - virtual - whenNotCapitalPaused - nonReentrant - returns (uint256[] memory amounts, uint256 totalAmount) - { - // Add any WETH to the withdrawal queue - // this needs to remain here as: - // - Vault can be funded and `addWithdrawalQueueLiquidity` is not externally called - // - funds can be withdrawn from a strategy - // - // Those funds need to be added to withdrawal queue liquidity - _addWithdrawalQueueLiquidity(); - - amounts = new uint256[](_requestIds.length); - for (uint256 i; i < _requestIds.length; ++i) { - amounts[i] = _claimWithdrawal(_requestIds[i]); - totalAmount += amounts[i]; - } - - // transfer all the claimed WETH from the vault to the withdrawer - IERC20(weth).safeTransfer(msg.sender, totalAmount); - - // Prevent insolvency - _postRedeem(totalAmount); - } - - function _claimWithdrawal(uint256 requestId) - internal - returns (uint256 amount) - { - require(withdrawalClaimDelay > 0, "Async withdrawals not enabled"); - - // Load the structs from storage into memory - WithdrawalRequest memory request = withdrawalRequests[requestId]; - WithdrawalQueueMetadata memory queue = withdrawalQueueMetadata; - - require( - request.timestamp + withdrawalClaimDelay <= block.timestamp, - "Claim delay not met" - ); - // If there isn't enough reserved liquidity in the queue to claim - require(request.queued <= queue.claimable, "Queue pending liquidity"); - require(request.withdrawer == msg.sender, "Not requester"); - require(request.claimed == false, "Already claimed"); - - // Store the request as claimed - withdrawalRequests[requestId].claimed = true; - // Store the updated claimed amount - withdrawalQueueMetadata.claimed = queue.claimed + request.amount; - - emit WithdrawalClaimed(msg.sender, requestId, request.amount); - - return request.amount; - } - - /// @notice Adds WETH to the withdrawal queue if there is a funding shortfall. - /// @dev is called from the Native Staking strategy when validator withdrawals are processed. - /// It also called before any WETH is allocated to a strategy. - function addWithdrawalQueueLiquidity() external { - _addWithdrawalQueueLiquidity(); - } - - /// @dev Adds WETH to the withdrawal queue if there is a funding shortfall. - /// This assumes 1 WETH equal 1 OETH. - function _addWithdrawalQueueLiquidity() - internal - returns (uint256 addedClaimable) - { - WithdrawalQueueMetadata memory queue = withdrawalQueueMetadata; - - // Check if the claimable WETH is less than the queued amount - uint256 queueShortfall = queue.queued - queue.claimable; - - // No need to do anything is the withdrawal queue is full funded - if (queueShortfall == 0) { - return 0; - } - - uint256 wethBalance = IERC20(weth).balanceOf(address(this)); - - // Of the claimable withdrawal requests, how much is unclaimed? - // That is, the amount of WETH that is currently allocated for the withdrawal queue - uint256 allocatedWeth = queue.claimable - queue.claimed; - - // If there is no unallocated WETH then there is nothing to add to the queue - if (wethBalance <= allocatedWeth) { - return 0; - } - - uint256 unallocatedWeth = wethBalance - allocatedWeth; - - // the new claimable amount is the smaller of the queue shortfall or unallocated weth - addedClaimable = queueShortfall < unallocatedWeth - ? queueShortfall - : unallocatedWeth; - uint256 newClaimable = queue.claimable + addedClaimable; - - // Store the new claimable amount back to storage - withdrawalQueueMetadata.claimable = SafeCast.toUint128(newClaimable); - - // emit a WithdrawalClaimable event - emit WithdrawalClaimable(newClaimable, addedClaimable); - } - - /*************************************** - View Functions - ****************************************/ - - /// @dev Calculate how much WETH in the vault is not reserved for the withdrawal queue. - // That is, it is available to be redeemed or deposited into a strategy. - function _wethAvailable() internal view returns (uint256 wethAvailable) { - WithdrawalQueueMetadata memory queue = withdrawalQueueMetadata; - - // The amount of WETH that is still to be claimed in the withdrawal queue - uint256 outstandingWithdrawals = queue.queued - queue.claimed; - - // The amount of sitting in WETH in the vault - uint256 wethBalance = IERC20(weth).balanceOf(address(this)); - - // If there is not enough WETH in the vault to cover the outstanding withdrawals - if (wethBalance <= outstandingWithdrawals) { - return 0; - } - - return wethBalance - outstandingWithdrawals; - } - - /// @dev Get the balance of an asset held in Vault and all strategies - /// less any WETH that is reserved for the withdrawal queue. - /// WETH is the only asset that can return a non-zero balance. - /// All other assets will return 0 even if there is some dust amounts left in the Vault. - /// For example, there is 1 wei left of stETH in the OETH Vault but will return 0 in this function. - /// - /// If there is not enough WETH in the vault and all strategies to cover all outstanding - /// withdrawal requests then return a WETH balance of 0 - function _checkBalance(address _asset) - internal - view - override - returns (uint256 balance) - { - if (_asset != weth) { - return 0; - } - - // Get the WETH in the vault and the strategies - balance = super._checkBalance(_asset); - - WithdrawalQueueMetadata memory queue = withdrawalQueueMetadata; - - // If the vault becomes insolvent enough that the total value in the vault and all strategies - // is less than the outstanding withdrawals. - // For example, there was a mass slashing event and most users request a withdrawal. - if (balance + queue.claimed < queue.queued) { - return 0; - } - - // Need to remove WETH that is reserved for the withdrawal queue - return balance + queue.claimed - queue.queued; - } - - /** - * @notice Allocate unallocated funds on Vault to strategies. - **/ - function allocate() external override whenNotCapitalPaused nonReentrant { - // Add any unallocated WETH to the withdrawal queue first - _addWithdrawalQueueLiquidity(); - - _allocate(); - } - - /// @dev Allocate WETH to the default WETH strategy if there is excess to the Vault buffer. - /// This is called from either `mint` or `allocate` and assumes `_addWithdrawalQueueLiquidity` - /// has been called before this function. - function _allocate() internal override { - // No need to do anything if no default strategy for WETH - address depositStrategyAddr = assetDefaultStrategies[weth]; - if (depositStrategyAddr == address(0)) return; - - uint256 wethAvailableInVault = _wethAvailable(); - // No need to do anything if there isn't any WETH in the vault to allocate - if (wethAvailableInVault == 0) return; - - // Calculate the target buffer for the vault using the total supply - uint256 totalSupply = oUSD.totalSupply(); - uint256 targetBuffer = totalSupply.mulTruncate(vaultBuffer); - - // If available WETH in the Vault is below or equal the target buffer then there's nothing to allocate - if (wethAvailableInVault <= targetBuffer) return; - - // The amount of assets to allocate to the default strategy - uint256 allocateAmount = wethAvailableInVault - targetBuffer; - - IStrategy strategy = IStrategy(depositStrategyAddr); - // Transfer WETH to the strategy and call the strategy's deposit function - IERC20(weth).safeTransfer(address(strategy), allocateAmount); - strategy.deposit(weth, allocateAmount); - - emit AssetAllocated(weth, depositStrategyAddr, allocateAmount); - } - - /// @dev The total value of all WETH held by the vault and all its strategies - /// less any WETH that is reserved for the withdrawal queue. - /// - // If there is not enough WETH in the vault and all strategies to cover all outstanding - // withdrawal requests then return a total value of 0. - function _totalValue() internal view override returns (uint256 value) { - // As WETH is the only asset, just return the WETH balance - return _checkBalance(weth); - } - - /// @dev Only WETH is supported in the OETH Vault so return the WETH balance only - /// Any ETH balances in the Vault will be ignored. - /// Amounts from previously supported vault assets will also be ignored. - /// For example, there is 1 wei left of stETH in the OETH Vault but is will be ignored. - function _totalValueInVault() - internal - view - override - returns (uint256 value) - { - value = IERC20(weth).balanceOf(address(this)); - } -} diff --git a/contracts/contracts/vault/OSVault.sol b/contracts/contracts/vault/OSVault.sol new file mode 100644 index 0000000000..c965871034 --- /dev/null +++ b/contracts/contracts/vault/OSVault.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import { VaultAdmin } from "./VaultAdmin.sol"; + +/** + * @title Origin Sonic VaultAdmin contract on Sonic + * @author Origin Protocol Inc + */ +contract OSVault is VaultAdmin { + constructor(address _wS) VaultAdmin(_wS) {} +} diff --git a/contracts/contracts/vault/OSonicVaultAdmin.sol b/contracts/contracts/vault/OSonicVaultAdmin.sol deleted file mode 100644 index 445ba03550..0000000000 --- a/contracts/contracts/vault/OSonicVaultAdmin.sol +++ /dev/null @@ -1,43 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -import { OETHVaultAdmin } from "./OETHVaultAdmin.sol"; - -/** - * @title Origin Sonic VaultAdmin contract on Sonic - * @author Origin Protocol Inc - */ -contract OSonicVaultAdmin is OETHVaultAdmin { - /// @param _wS Sonic's Wrapped S token - constructor(address _wS) OETHVaultAdmin(_wS) {} - - /*************************************** - Asset Config - ****************************************/ - - /** - * @notice Add a supported asset to the contract, i.e. one that can be to mint OTokens. - * @dev Overridden to remove price provider integration - * @param _asset Address of asset - * @param _unitConversion 0 decimals, 1 exchange rate - */ - function supportAsset(address _asset, uint8 _unitConversion) - external - override - onlyGovernor - { - require(!assets[_asset].isSupported, "Asset already supported"); - - assets[_asset] = Asset({ - isSupported: true, - unitConversion: UnitConversion(_unitConversion), - decimals: 0, // will be overridden in _cacheDecimals - allowedOracleSlippageBps: 0 // 0% by default - }); - - _cacheDecimals(_asset); - allAssets.push(_asset); - - emit AssetSupported(_asset); - } -} diff --git a/contracts/contracts/vault/OSonicVaultCore.sol b/contracts/contracts/vault/OSonicVaultCore.sol deleted file mode 100644 index f35b7b5cd5..0000000000 --- a/contracts/contracts/vault/OSonicVaultCore.sol +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -import { OETHVaultCore } from "./OETHVaultCore.sol"; - -/** - * @title Origin Sonic VaultCore contract on Sonic - * @author Origin Protocol Inc - */ -contract OSonicVaultCore is OETHVaultCore { - /// @param _wS Sonic's Wrapped S token - constructor(address _wS) OETHVaultCore(_wS) {} - - /** - * @notice Instant redeem is not supported on Sonic. - * Use the asynchronous `requestWithdrawal` a `claimWithdrawal` instead. - */ - function _redeem(uint256, uint256) internal override { - revert("unsupported function"); - } -} diff --git a/contracts/contracts/vault/OUSDVault.sol b/contracts/contracts/vault/OUSDVault.sol new file mode 100644 index 0000000000..a800636b91 --- /dev/null +++ b/contracts/contracts/vault/OUSDVault.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import { VaultAdmin } from "./VaultAdmin.sol"; + +/** + * @title OUSD VaultAdmin Contract + * @author Origin Protocol Inc + */ +contract OUSDVault is VaultAdmin { + constructor(address _usdc) VaultAdmin(_usdc) {} +} diff --git a/contracts/contracts/vault/README.md b/contracts/contracts/vault/README.md index 651a49f2d6..868f4d7b2a 100644 --- a/contracts/contracts/vault/README.md +++ b/contracts/contracts/vault/README.md @@ -2,60 +2,14 @@ ## Hierarchy -![Vault Core Hierarchy](../../docs/VaultHierarchy.svg) +![Vault Hierarchy](../../docs/VaultHierarchy.svg) -## OUSD Vault +## Vault -## Vault Core Squashed +## Vault Squashed -![Vault Core Squashed](../../docs/VaultCoreSquashed.svg) +![Vault Squashed](../../docs/VaultSquashed.svg) ### Storage ![Vault Storage](../../docs/VaultStorage.svg) - -## Vault Admin Squashed - -![Vault Admin Squashed](../../docs/VaultAdminSquashed.svg) - -## OETH Vault - -## Vault Core Squashed - -![OETH Vault Core Squashed](../../docs/OETHVaultCoreSquashed.svg) - -## Vault Admin Squashed - -![OETH Vault Admin Squashed](../../docs/OETHVaultAdminSquashed.svg) - -### Storage - -![OETH Vault Storage](../../docs/OETHVaultStorage.svg) - -## Base OETH Vault - -## Vault Core Squashed - -![OETH Vault Core Squashed](../../docs/OETHBaseVaultCoreSquashed.svg) - -## Vault Admin Squashed - -![OETH Vault Admin Squashed](../../docs/OETHBaseVaultAdminSquashed.svg) - -### Storage - -![OETH Vault Storage](../../docs/OETHBaseVaultStorage.svg) - -## Origin Sonic (OS) Vault - -## Vault Core Squashed - -![Sonic Vault Core Squashed](../../docs/OSonicVaultCoreSquashed.svg) - -## Vault Admin Squashed - -![Sonic Vault Admin Squashed](../../docs/OSonicVaultAdminSquashed.svg) - -### Storage - -![Sonic Vault Storage](../../docs/OSonicVaultStorage.svg) \ No newline at end of file diff --git a/contracts/contracts/vault/Vault.sol b/contracts/contracts/vault/Vault.sol deleted file mode 100644 index 7753403040..0000000000 --- a/contracts/contracts/vault/Vault.sol +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -/** - * @title OUSD VaultInitializer Contract - * @notice The VaultInitializer sets up the initial contract. - * @author Origin Protocol Inc - */ -import { VaultInitializer } from "./VaultInitializer.sol"; -import { VaultAdmin } from "./VaultAdmin.sol"; - -contract Vault is VaultInitializer, VaultAdmin {} diff --git a/contracts/contracts/vault/VaultAdmin.sol b/contracts/contracts/vault/VaultAdmin.sol index fc402d97e4..fb3a579289 100644 --- a/contracts/contracts/vault/VaultAdmin.sol +++ b/contracts/contracts/vault/VaultAdmin.sol @@ -9,15 +9,13 @@ pragma solidity ^0.8.0; import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import { IOracle } from "../interfaces/IOracle.sol"; -import { ISwapper } from "../interfaces/ISwapper.sol"; import { IVault } from "../interfaces/IVault.sol"; import { StableMath } from "../utils/StableMath.sol"; import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; -import "./VaultStorage.sol"; +import "./VaultCore.sol"; -contract VaultAdmin is VaultStorage { +abstract contract VaultAdmin is VaultCore { using SafeERC20 for IERC20; using StableMath for uint256; using SafeCast for uint256; @@ -33,32 +31,14 @@ contract VaultAdmin is VaultStorage { _; } + constructor(address _asset) VaultCore(_asset) {} + /*************************************** Configuration ****************************************/ - - /** - * @notice Set address of price provider. - * @param _priceProvider Address of price provider - */ - function setPriceProvider(address _priceProvider) external onlyGovernor { - priceProvider = _priceProvider; - emit PriceProviderUpdated(_priceProvider); - } - /** - * @notice Set a fee in basis points to be charged for a redeem. - * @param _redeemFeeBps Basis point fee to be charged - */ - function setRedeemFeeBps(uint256 _redeemFeeBps) external onlyGovernor { - require(_redeemFeeBps <= 1000, "Redeem fee should not be over 10%"); - redeemFeeBps = _redeemFeeBps; - emit RedeemFeeUpdated(_redeemFeeBps); - } - - /** - * @notice Set a buffer of assets to keep in the Vault to handle most - * redemptions without needing to spend gas unwinding assets from a Strategy. + * @notice Set a buffer of asset to keep in the Vault to handle most + * redemptions without needing to spend gas unwinding asset from a Strategy. * @param _vaultBuffer Percentage using 18 decimals. 100% = 1e18. */ function setVaultBuffer(uint256 _vaultBuffer) @@ -103,57 +83,26 @@ contract VaultAdmin is VaultStorage { } /** - * @notice Set the default Strategy for an asset, i.e. the one which the asset - will be automatically allocated to and withdrawn from - * @param _asset Address of the asset + * @notice Set the default Strategy for asset, i.e. the one which + * the asset will be automatically allocated to and withdrawn from * @param _strategy Address of the Strategy */ - function setAssetDefaultStrategy(address _asset, address _strategy) + function setDefaultStrategy(address _strategy) external onlyGovernorOrStrategist { - emit AssetDefaultStrategyUpdated(_asset, _strategy); + emit DefaultStrategyUpdated(_strategy); // If its a zero address being passed for the strategy we are removing // the default strategy if (_strategy != address(0)) { // Make sure the strategy meets some criteria require(strategies[_strategy].isSupported, "Strategy not approved"); - IStrategy strategy = IStrategy(_strategy); - require(assets[_asset].isSupported, "Asset is not supported"); require( - strategy.supportsAsset(_asset), + IStrategy(_strategy).supportsAsset(asset), "Asset not supported by Strategy" ); } - assetDefaultStrategies[_asset] = _strategy; - } - - /** - * @notice Set maximum amount of OTokens that can at any point be minted and deployed - * to strategy (used only by ConvexOUSDMetaStrategy for now). - * @param _threshold OToken amount with 18 fixed decimals. - */ - function setNetOusdMintForStrategyThreshold(uint256 _threshold) - external - onlyGovernor - { - /** - * Because `netOusdMintedForStrategy` check in vault core works both ways - * (positive and negative) the actual impact of the amount of OToken minted - * could be double the threshold. E.g.: - * - contract has threshold set to 100 - * - state of netOusdMinted is -90 - * - in effect it can mint 190 OToken and still be within limits - * - * We are somewhat mitigating this behaviour by resetting the netOusdMinted - * counter whenever new threshold is set. So it can only move one threshold - * amount in each direction. This also enables us to reduce the threshold - * amount and not have problems with current netOusdMinted being near - * limits on either side. - */ - netOusdMintedForStrategy = 0; - netOusdMintForStrategyThreshold = _threshold; - emit NetOusdMintForStrategyThresholdChanged(_threshold); + defaultStrategy = _strategy; } /** @@ -170,24 +119,27 @@ contract VaultAdmin is VaultStorage { emit WithdrawalClaimDelayUpdated(_delay); } + // slither-disable-start reentrancy-no-eth /** * @notice Set a yield streaming max rate. This spreads yield over - * time if it is above the max rate. - * @param yearlyApr in 1e18 notation. 3 * 1e18 = 3% APR + * time if it is above the max rate. This is a per rebase APR which + * due to compounding differs from the yearly APR. Governance should + * consider this fact when picking a desired APR + * @param apr in 1e18 notation. 3 * 1e18 = 3% APR */ - function setRebaseRateMax(uint256 yearlyApr) - external - onlyGovernorOrStrategist - { + function setRebaseRateMax(uint256 apr) external onlyGovernorOrStrategist { // The old yield will be at the old rate - IVault(address(this)).rebase(); + _rebase(); // Change the rate - uint256 newPerSecond = yearlyApr / 100 / 365 days; + uint256 newPerSecond = apr / 100 / 365 days; require(newPerSecond <= MAX_REBASE_PER_SECOND, "Rate too high"); rebasePerSecondMax = newPerSecond.toUint64(); emit RebasePerSecondMaxChanged(newPerSecond); } + // slither-disable-end reentrancy-no-eth + + // slither-disable-start reentrancy-no-eth /** * @notice Set the drip duration period * @param _dripDuration Time in seconds to target a constant yield rate @@ -197,271 +149,12 @@ contract VaultAdmin is VaultStorage { onlyGovernorOrStrategist { // The old yield will be at the old rate - IVault(address(this)).rebase(); + _rebase(); dripDuration = _dripDuration.toUint64(); emit DripDurationChanged(_dripDuration); } - /*************************************** - Swaps - ****************************************/ - - /** - * @notice Strategist swaps collateral assets sitting in the vault. - * @param _fromAsset The token address of the asset being sold by the vault. - * @param _toAsset The token address of the asset being purchased by the vault. - * @param _fromAssetAmount The amount of assets being sold by the vault. - * @param _minToAssetAmount The minimum amount of assets to be purchased. - * @param _data implementation specific data. eg 1Inch swap data - * @return toAssetAmount The amount of toAssets that was received from the swap - */ - function swapCollateral( - address _fromAsset, - address _toAsset, - uint256 _fromAssetAmount, - uint256 _minToAssetAmount, - bytes calldata _data - ) - external - nonReentrant - onlyGovernorOrStrategist - returns (uint256 toAssetAmount) - { - toAssetAmount = _swapCollateral( - _fromAsset, - _toAsset, - _fromAssetAmount, - _minToAssetAmount, - _data - ); - } - - function _swapCollateral( - address _fromAsset, - address _toAsset, - uint256 _fromAssetAmount, - uint256 _minToAssetAmount, - bytes calldata _data - ) internal virtual returns (uint256 toAssetAmount) { - // Check fromAsset and toAsset are valid - Asset memory fromAssetConfig = assets[_fromAsset]; - Asset memory toAssetConfig = assets[_toAsset]; - require(fromAssetConfig.isSupported, "From asset is not supported"); - require(toAssetConfig.isSupported, "To asset is not supported"); - - // Load swap config into memory to avoid separate SLOADs - SwapConfig memory config = swapConfig; - - // Scope a new block to remove toAssetBalBefore from the scope of swapCollateral. - // This avoids a stack too deep error. - { - uint256 toAssetBalBefore = IERC20(_toAsset).balanceOf( - address(this) - ); - - // Transfer from assets to the swapper contract - IERC20(_fromAsset).safeTransfer(config.swapper, _fromAssetAmount); - - // Call to the Swapper contract to do the actual swap - // The -1 is required for stETH which sometimes transfers 1 wei less than what was specified. - // slither-disable-next-line unused-return - ISwapper(config.swapper).swap( - _fromAsset, - _toAsset, - _fromAssetAmount - 1, - _minToAssetAmount, - _data - ); - - // Compute the change in asset balance held by the Vault - toAssetAmount = - IERC20(_toAsset).balanceOf(address(this)) - - toAssetBalBefore; - } - - // Check the to assets returned is above slippage amount specified by the strategist - require( - toAssetAmount >= _minToAssetAmount, - "Strategist slippage limit" - ); - - // Scope a new block to remove minOracleToAssetAmount from the scope of swapCollateral. - // This avoids a stack too deep error. - { - // Check the slippage against the Oracle in case the strategist made a mistake or has become malicious. - // to asset amount = from asset amount * from asset price / to asset price - uint256 minOracleToAssetAmount = (_fromAssetAmount * - (1e4 - fromAssetConfig.allowedOracleSlippageBps) * - IOracle(priceProvider).price(_fromAsset)) / - (IOracle(priceProvider).price(_toAsset) * - (1e4 + toAssetConfig.allowedOracleSlippageBps)); - - // Scale both sides up to 18 decimals to compare - require( - toAssetAmount.scaleBy(18, toAssetConfig.decimals) >= - minOracleToAssetAmount.scaleBy( - 18, - fromAssetConfig.decimals - ), - "Oracle slippage limit exceeded" - ); - } - - // Check the vault's total value hasn't gone below the OToken total supply - // by more than the allowed percentage. - require( - IVault(address(this)).totalValue() >= - (oUSD.totalSupply() * ((1e4 - config.allowedUndervalueBps))) / - 1e4, - "Allowed value < supply" - ); - - emit Swapped(_fromAsset, _toAsset, _fromAssetAmount, toAssetAmount); - } - - /*************************************** - Swap Config - ****************************************/ - - /** - * @notice Set the contract the performs swaps of collateral assets. - * @param _swapperAddr Address of the Swapper contract that implements the ISwapper interface. - */ - function setSwapper(address _swapperAddr) external onlyGovernor { - swapConfig.swapper = _swapperAddr; - emit SwapperChanged(_swapperAddr); - } - - /// @notice Contract that swaps the vault's collateral assets - function swapper() external view returns (address swapper_) { - swapper_ = swapConfig.swapper; - } - - /** - * @notice Set max allowed percentage the vault total value can drop below the OToken total supply in basis points - * when executing collateral swaps. - * @param _basis Percentage in basis points. eg 100 == 1% - */ - function setSwapAllowedUndervalue(uint16 _basis) external onlyGovernor { - require(_basis < 10001, "Invalid basis points"); - swapConfig.allowedUndervalueBps = _basis; - emit SwapAllowedUndervalueChanged(_basis); - } - - /** - * @notice Max allowed percentage the vault total value can drop below the OToken total supply in basis points - * when executing a collateral swap. - * For example 100 == 1% - * @return value Percentage in basis points. - */ - function allowedSwapUndervalue() external view returns (uint256 value) { - value = swapConfig.allowedUndervalueBps; - } - - /** - * @notice Set the allowed slippage from the Oracle price for collateral asset swaps. - * @param _asset Address of the asset token. - * @param _allowedOracleSlippageBps allowed slippage from Oracle in basis points. eg 20 = 0.2%. Max 10%. - */ - function setOracleSlippage(address _asset, uint16 _allowedOracleSlippageBps) - external - onlyGovernor - { - require(assets[_asset].isSupported, "Asset not supported"); - require(_allowedOracleSlippageBps < 1000, "Slippage too high"); - - assets[_asset].allowedOracleSlippageBps = _allowedOracleSlippageBps; - - emit SwapSlippageChanged(_asset, _allowedOracleSlippageBps); - } - - /*************************************** - Asset Config - ****************************************/ - - /** - * @notice Add a supported asset to the contract, i.e. one that can be - * to mint OTokens. - * @param _asset Address of asset - */ - function supportAsset(address _asset, uint8 _unitConversion) - external - virtual - onlyGovernor - { - require(!assets[_asset].isSupported, "Asset already supported"); - - assets[_asset] = Asset({ - isSupported: true, - unitConversion: UnitConversion(_unitConversion), - decimals: 0, // will be overridden in _cacheDecimals - allowedOracleSlippageBps: 0 // 0% by default - }); - - _cacheDecimals(_asset); - allAssets.push(_asset); - - // Verify that our oracle supports the asset - // slither-disable-next-line unused-return - IOracle(priceProvider).price(_asset); - - emit AssetSupported(_asset); - } - - /** - * @notice Remove a supported asset from the Vault - * @param _asset Address of asset - */ - function removeAsset(address _asset) external onlyGovernor { - require(assets[_asset].isSupported, "Asset not supported"); - - // 1e13 for 18 decimals. And 10 for 6 decimals - uint256 maxDustBalance = uint256(1e13).scaleBy( - assets[_asset].decimals, - 18 - ); - - require( - IVault(address(this)).checkBalance(_asset) <= maxDustBalance, - "Vault still holds asset" - ); - - uint256 assetsCount = allAssets.length; - uint256 assetIndex = assetsCount; // initialize at invalid index - for (uint256 i = 0; i < assetsCount; ++i) { - if (allAssets[i] == _asset) { - assetIndex = i; - break; - } - } - - // Note: If asset is not found in `allAssets`, the following line - // will revert with an out-of-bound error. However, there's no - // reason why an asset would have `Asset.isSupported = true` but - // not exist in `allAssets`. - - // Update allAssets array - allAssets[assetIndex] = allAssets[assetsCount - 1]; - allAssets.pop(); - - // Reset default strategy - assetDefaultStrategies[_asset] = address(0); - emit AssetDefaultStrategyUpdated(_asset, address(0)); - - // Remove asset from storage - delete assets[_asset]; - - emit AssetRemoved(_asset); - } - - /** - * @notice Cache decimals on OracleRouter for a particular asset. This action - * is required before that asset's price can be accessed. - * @param _asset Address of asset token - */ - function cacheDecimals(address _asset) external onlyGovernor { - _cacheDecimals(_asset); - } + // slither-disable-end reentrancy-no-eth /*************************************** Strategy Config @@ -473,6 +166,10 @@ contract VaultAdmin is VaultStorage { */ function approveStrategy(address _addr) external onlyGovernor { require(!strategies[_addr].isSupported, "Strategy already approved"); + require( + IStrategy(_addr).supportsAsset(asset), + "Asset not supported by Strategy" + ); strategies[_addr] = Strategy({ isSupported: true, _deprecated: 0 }); allStrategies.push(_addr); emit StrategyApproved(_addr); @@ -485,14 +182,7 @@ contract VaultAdmin is VaultStorage { function removeStrategy(address _addr) external onlyGovernor { require(strategies[_addr].isSupported, "Strategy not approved"); - - uint256 assetCount = allAssets.length; - for (uint256 i = 0; i < assetCount; ++i) { - require( - assetDefaultStrategies[allAssets[i]] != _addr, - "Strategy is default for an asset" - ); - } + require(defaultStrategy != _addr, "Strategy is default for asset"); // Initialize strategyIndex with out of bounds result so function will // revert if no valid index found @@ -511,22 +201,73 @@ contract VaultAdmin is VaultStorage { // Mark the strategy as not supported strategies[_addr].isSupported = false; + isMintWhitelistedStrategy[_addr] = false; - // Withdraw all assets + // Withdraw all asset IStrategy strategy = IStrategy(_addr); strategy.withdrawAll(); + // 1e13 for 18 decimals. And 1e1(10) for 6 decimals + uint256 maxDustBalance = uint256(1e13).scaleBy(assetDecimals, 18); + + /* + * Some strategies are not able to withdraw all of their funds in a synchronous call. + * Prevent the possible accidental removal of such strategies before their funds are withdrawn. + */ + require( + strategy.checkBalance(asset) < maxDustBalance, + "Strategy has funds" + ); emit StrategyRemoved(_addr); } } + /** + * @notice Adds a strategy to the mint whitelist. + * Reverts if strategy isn't approved on Vault. + * @param strategyAddr Strategy address + */ + function addStrategyToMintWhitelist(address strategyAddr) + external + onlyGovernor + { + require(strategies[strategyAddr].isSupported, "Strategy not approved"); + + require( + !isMintWhitelistedStrategy[strategyAddr], + "Already whitelisted" + ); + + isMintWhitelistedStrategy[strategyAddr] = true; + + emit StrategyAddedToMintWhitelist(strategyAddr); + } + + /** + * @notice Removes a strategy from the mint whitelist. + * @param strategyAddr Strategy address + */ + function removeStrategyFromMintWhitelist(address strategyAddr) + external + onlyGovernor + { + // Intentionally skipping `strategies.isSupported` check since + // we may wanna remove an address even after removing the strategy + + require(isMintWhitelistedStrategy[strategyAddr], "Not whitelisted"); + + isMintWhitelistedStrategy[strategyAddr] = false; + + emit StrategyRemovedFromMintWhitelist(strategyAddr); + } + /*************************************** Strategies ****************************************/ /** - * @notice Deposit multiple assets from the vault into the strategy. - * @param _strategyToAddress Address of the Strategy to deposit assets into. + * @notice Deposit multiple asset from the vault into the strategy. + * @param _strategyToAddress Address of the Strategy to deposit asset into. * @param _assets Array of asset address that will be deposited into the strategy. * @param _amounts Array of amounts of each corresponding asset to deposit. */ @@ -547,26 +288,28 @@ contract VaultAdmin is VaultStorage { strategies[_strategyToAddress].isSupported, "Invalid to Strategy" ); - require(_assets.length == _amounts.length, "Parameter length mismatch"); + require( + _assets.length == 1 && _amounts.length == 1 && _assets[0] == asset, + "Only asset is supported" + ); - uint256 assetCount = _assets.length; - for (uint256 i = 0; i < assetCount; ++i) { - address assetAddr = _assets[i]; - require( - IStrategy(_strategyToAddress).supportsAsset(assetAddr), - "Asset unsupported" - ); - // Send required amount of funds to the strategy - IERC20(assetAddr).safeTransfer(_strategyToAddress, _amounts[i]); - } + // Check the there is enough asset to transfer once the backing + // asset reserved for the withdrawal queue is accounted for + require( + _amounts[0] <= _assetAvailable(), + "Not enough assets available" + ); + + // Send required amount of funds to the strategy + IERC20(asset).safeTransfer(_strategyToAddress, _amounts[0]); // Deposit all the funds that have been sent to the strategy IStrategy(_strategyToAddress).depositAll(); } /** - * @notice Withdraw multiple assets from the strategy to the vault. - * @param _strategyFromAddress Address of the Strategy to withdraw assets from. + * @notice Withdraw multiple asset from the strategy to the vault. + * @param _strategyFromAddress Address of the Strategy to withdraw asset from. * @param _assets Array of asset address that will be withdrawn from the strategy. * @param _amounts Array of amounts of each corresponding asset to withdraw. */ @@ -607,11 +350,13 @@ contract VaultAdmin is VaultStorage { _amounts[i] ); } + + _addWithdrawalQueueLiquidity(); } /** * @notice Sets the maximum allowable difference between - * total supply and backing assets' value. + * total supply and asset' value. */ function setMaxSupplyDiff(uint256 _maxSupplyDiff) external onlyGovernor { maxSupplyDiff = _maxSupplyDiff; @@ -637,18 +382,6 @@ contract VaultAdmin is VaultStorage { emit TrusteeFeeBpsChanged(_basis); } - /** - * @notice Set OToken Metapool strategy - * @param _ousdMetaStrategy Address of OToken metapool strategy - */ - function setOusdMetaStrategy(address _ousdMetaStrategy) - external - onlyGovernor - { - ousdMetaStrategy = _ousdMetaStrategy; - emit OusdMetaStrategyUpdated(_ousdMetaStrategy); - } - /*************************************** Pause ****************************************/ @@ -699,7 +432,7 @@ contract VaultAdmin is VaultStorage { external onlyGovernor { - require(!assets[_asset].isSupported, "Only unsupported assets"); + require(asset != _asset, "Only unsupported asset"); IERC20(_asset).safeTransfer(governor(), _amount); } @@ -708,7 +441,7 @@ contract VaultAdmin is VaultStorage { ****************************************/ /** - * @notice Withdraws all assets from the strategy and sends assets to the Vault. + * @notice Withdraws all asset from the strategy and sends asset to the Vault. * @param _strategyAddr Strategy address. */ function withdrawAllFromStrategy(address _strategyAddr) @@ -725,10 +458,11 @@ contract VaultAdmin is VaultStorage { ); IStrategy strategy = IStrategy(_strategyAddr); strategy.withdrawAll(); + _addWithdrawalQueueLiquidity(); } /** - * @notice Withdraws all assets from all the strategies and sends assets to the Vault. + * @notice Withdraws all asset from all the strategies and sends asset to the Vault. */ function withdrawAllFromStrategies() external onlyGovernorOrStrategist { _withdrawAllFromStrategies(); @@ -739,19 +473,6 @@ contract VaultAdmin is VaultStorage { for (uint256 i = 0; i < stratCount; ++i) { IStrategy(allStrategies[i]).withdrawAll(); } - } - - /*************************************** - Utils - ****************************************/ - - function _cacheDecimals(address token) internal { - Asset storage tokenAsset = assets[token]; - if (tokenAsset.decimals != 0) { - return; - } - uint8 decimals = IBasicToken(token).decimals(); - require(decimals >= 6 && decimals <= 18, "Unexpected precision"); - tokenAsset.decimals = decimals; + _addWithdrawalQueueLiquidity(); } } diff --git a/contracts/contracts/vault/VaultCore.sol b/contracts/contracts/vault/VaultCore.sol index ef1abed76a..72bbcc9dcb 100644 --- a/contracts/contracts/vault/VaultCore.sol +++ b/contracts/contracts/vault/VaultCore.sol @@ -3,27 +3,24 @@ pragma solidity ^0.8.0; /** * @title OToken VaultCore contract - * @notice The Vault contract stores assets. On a deposit, OTokens will be minted + * @notice The Vault contract stores asset. On a deposit, OTokens will be minted and sent to the depositor. On a withdrawal, OTokens will be burned and - assets will be sent to the withdrawer. The Vault accepts deposits of + asset will be sent to the withdrawer. The Vault accepts deposits of interest from yield bearing strategies which will modify the supply of OTokens. * @author Origin Protocol Inc */ import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; import { StableMath } from "../utils/StableMath.sol"; -import { IOracle } from "../interfaces/IOracle.sol"; -import { IGetExchangeRateToken } from "../interfaces/IGetExchangeRateToken.sol"; import "./VaultInitializer.sol"; -contract VaultCore is VaultInitializer { +abstract contract VaultCore is VaultInitializer { using SafeERC20 for IERC20; using StableMath for uint256; - /// @dev max signed int - uint256 internal constant MAX_INT = uint256(type(int256).max); /** * @dev Verifies that the rebasing is not paused. @@ -41,81 +38,74 @@ contract VaultCore is VaultInitializer { _; } - /** - * @dev Verifies that the caller is the AMO strategy. - */ - modifier onlyOusdMetaStrategy() { - require( - msg.sender == ousdMetaStrategy, - "Caller is not the OUSD meta strategy" - ); - _; - } + constructor(address _asset) VaultInitializer(_asset) {} + //////////////////////////////////////////////////// + /// MINT / BURN /// + //////////////////////////////////////////////////// /** * @notice Deposit a supported asset and mint OTokens. - * @param _asset Address of the asset being deposited + * @dev Deprecated: use `mint(uint256 _amount)` instead. + * @dev Deprecated: param _asset Address of the asset being deposited * @param _amount Amount of the asset being deposited - * @param _minimumOusdAmount Minimum OTokens to mint + * @dev Deprecated: param _minimumOusdAmount Minimum OTokens to mint */ function mint( - address _asset, + address, uint256 _amount, - uint256 _minimumOusdAmount + uint256 ) external whenNotCapitalPaused nonReentrant { - _mint(_asset, _amount, _minimumOusdAmount); + _mint(_amount); + } + + /** + * @notice Deposit a supported asset and mint OTokens. + * @param _amount Amount of the asset being deposited + */ + function mint(uint256 _amount) external whenNotCapitalPaused nonReentrant { + _mint(_amount); } + // slither-disable-start reentrancy-no-eth /** * @dev Deposit a supported asset and mint OTokens. - * @param _asset Address of the asset being deposited * @param _amount Amount of the asset being deposited - * @param _minimumOusdAmount Minimum OTokens to mint */ - function _mint( - address _asset, - uint256 _amount, - uint256 _minimumOusdAmount - ) internal virtual { - require(assets[_asset].isSupported, "Asset is not supported"); + function _mint(uint256 _amount) internal virtual { require(_amount > 0, "Amount must be greater than 0"); - uint256 units = _toUnits(_amount, _asset); - uint256 unitPrice = _toUnitPrice(_asset, true); - uint256 priceAdjustedDeposit = (units * unitPrice) / 1e18; - - if (_minimumOusdAmount > 0) { - require( - priceAdjustedDeposit >= _minimumOusdAmount, - "Mint amount lower than minimum" - ); - } + // Scale amount to 18 decimals + uint256 scaledAmount = _amount.scaleBy(18, assetDecimals); - emit Mint(msg.sender, priceAdjustedDeposit); + emit Mint(msg.sender, scaledAmount); // Rebase must happen before any transfers occur. - if (priceAdjustedDeposit >= rebaseThreshold && !rebasePaused) { + if (!rebasePaused && scaledAmount >= rebaseThreshold) { _rebase(); } - // Mint matching amount of OTokens - oUSD.mint(msg.sender, priceAdjustedDeposit); + // Mint oTokens + oToken.mint(msg.sender, scaledAmount); - // Transfer the deposited coins to the vault - IERC20 asset = IERC20(_asset); - asset.safeTransferFrom(msg.sender, address(this), _amount); + IERC20(asset).safeTransferFrom(msg.sender, address(this), _amount); + + // Give priority to the withdrawal queue for the new asset liquidity + _addWithdrawalQueueLiquidity(); - if (priceAdjustedDeposit >= autoAllocateThreshold) { + // Auto-allocate if necessary + if (scaledAmount >= autoAllocateThreshold) { _allocate(); } } + // slither-disable-end reentrancy-no-eth + /** - * @notice Mint OTokens for a Metapool Strategy - * @param _amount Amount of the asset being deposited + * @notice Mint OTokens for an allowed Strategy + * @param _amount Amount of OToken to mint * * Notice: can't use `nonReentrant` modifier since the `mint` function can - * call `allocate`, and that can trigger `ConvexOUSDMetaStrategy` to call this function + * call `allocate`, and that can trigger an AMO strategy to call this function * while the execution of the `mint` has not yet completed -> causing a `nonReentrant` collision. * * Also important to understand is that this is a limitation imposed by the test suite. @@ -127,94 +117,232 @@ contract VaultCore is VaultInitializer { external virtual whenNotCapitalPaused - onlyOusdMetaStrategy { - require(_amount < MAX_INT, "Amount too high"); + require( + strategies[msg.sender].isSupported == true, + "Unsupported strategy" + ); + require( + isMintWhitelistedStrategy[msg.sender] == true, + "Not whitelisted strategy" + ); emit Mint(msg.sender, _amount); + // Mint matching amount of OTokens + oToken.mint(msg.sender, _amount); + } - // safe to cast because of the require check at the beginning of the function - netOusdMintedForStrategy += int256(_amount); - + /** + * @notice Burn OTokens for an allowed Strategy + * @param _amount Amount of OToken to burn + * + * @dev Notice: can't use `nonReentrant` modifier since the `redeem` function could + * require withdrawal on an AMO strategy and that one can call `burnForStrategy` + * while the execution of the `redeem` has not yet completed -> causing a `nonReentrant` collision. + * + * Also important to understand is that this is a limitation imposed by the test suite. + * Production / mainnet contracts should never be configured in a way where mint/redeem functions + * that are moving funds between the Vault and end user wallets can influence strategies + * utilizing this function. + */ + function burnForStrategy(uint256 _amount) + external + virtual + whenNotCapitalPaused + { require( - abs(netOusdMintedForStrategy) < netOusdMintForStrategyThreshold, - "Minted ousd surpassed netOusdMintForStrategyThreshold." + strategies[msg.sender].isSupported == true, + "Unsupported strategy" + ); + require( + isMintWhitelistedStrategy[msg.sender] == true, + "Not whitelisted strategy" ); - // Mint matching amount of OTokens - oUSD.mint(msg.sender, _amount); - } + emit Redeem(msg.sender, _amount); - // In memoriam + // Burn OTokens + oToken.burn(msg.sender, _amount); + } + //////////////////////////////////////////////////// + /// ASYNC WITHDRAWALS /// + //////////////////////////////////////////////////// /** - * @notice Withdraw a supported asset and burn OTokens. - * @param _amount Amount of OTokens to burn - * @param _minimumUnitAmount Minimum stablecoin units to receive in return + * @notice Request an asynchronous withdrawal of asset in exchange for OToken. + * The OToken is burned on request and the asset is transferred to the withdrawer on claim. + * This request can be claimed once the withdrawal queue's `claimable` amount + * is greater than or equal this request's `queued` amount. + * There is a minimum of 10 minutes before a request can be claimed. After that, the request just needs + * enough asset liquidity in the Vault to satisfy all the outstanding requests to that point in the queue. + * OToken is converted to asset at 1:1. + * @param _amount Amount of OToken to burn. + * @return requestId Unique ID for the withdrawal request + * @return queued Cumulative total of all asset queued including already claimed requests. */ - function redeem(uint256 _amount, uint256 _minimumUnitAmount) + function requestWithdrawal(uint256 _amount) external + virtual whenNotCapitalPaused nonReentrant + returns (uint256 requestId, uint256 queued) { - _redeem(_amount, _minimumUnitAmount); + require(_amount > 0, "Amount must be greater than 0"); + require(withdrawalClaimDelay > 0, "Async withdrawals not enabled"); + + // The check that the requester has enough OToken is done in to later burn call + + requestId = withdrawalQueueMetadata.nextWithdrawalIndex; + queued = + withdrawalQueueMetadata.queued + + _amount.scaleBy(assetDecimals, 18); + + // Store the next withdrawal request + withdrawalQueueMetadata.nextWithdrawalIndex = SafeCast.toUint128( + requestId + 1 + ); + // Store the updated queued amount which reserves asset in the withdrawal queue + // and reduces the vault's total asset + withdrawalQueueMetadata.queued = SafeCast.toUint128(queued); + // Store the user's withdrawal request + // `queued` is in asset decimals, while `amount` is in OToken decimals (18) + withdrawalRequests[requestId] = WithdrawalRequest({ + withdrawer: msg.sender, + claimed: false, + timestamp: uint40(block.timestamp), + amount: SafeCast.toUint128(_amount), + queued: SafeCast.toUint128(queued) + }); + + // Burn the user's OToken + oToken.burn(msg.sender, _amount); + + // Prevent withdrawal if the vault is solvent by more than the allowed percentage + _postRedeem(_amount); + + emit WithdrawalRequested(msg.sender, requestId, _amount, queued); } + // slither-disable-start reentrancy-no-eth /** - * @notice Withdraw a supported asset and burn OTokens. - * @param _amount Amount of OTokens to burn - * @param _minimumUnitAmount Minimum stablecoin units to receive in return + * @notice Claim a previously requested withdrawal once it is claimable. + * This request can be claimed once the withdrawal queue's `claimable` amount + * is greater than or equal this request's `queued` amount and 10 minutes has passed. + * If the requests is not claimable, the transaction will revert with `Queue pending liquidity`. + * If the request is not older than 10 minutes, the transaction will revert with `Claim delay not met`. + * OToken is converted to asset at 1:1. + * @param _requestId Unique ID for the withdrawal request + * @return amount Amount of asset transferred to the withdrawer */ - function _redeem(uint256 _amount, uint256 _minimumUnitAmount) - internal + function claimWithdrawal(uint256 _requestId) + external virtual + whenNotCapitalPaused + nonReentrant + returns (uint256 amount) { - // Calculate redemption outputs - uint256[] memory outputs = _calculateRedeemOutputs(_amount); + // Try and get more liquidity if there is not enough available + if ( + withdrawalRequests[_requestId].queued > + withdrawalQueueMetadata.claimable + ) { + // Add any asset to the withdrawal queue + // this needs to remain here as: + // - Vault can be funded and `addWithdrawalQueueLiquidity` is not externally called + // - funds can be withdrawn from a strategy + // + // Those funds need to be added to withdrawal queue liquidity + _addWithdrawalQueueLiquidity(); + } - emit Redeem(msg.sender, _amount); + // Scale amount to asset decimals + amount = _claimWithdrawal(_requestId); - // Send outputs - uint256 assetCount = allAssets.length; - for (uint256 i = 0; i < assetCount; ++i) { - if (outputs[i] == 0) continue; - - address assetAddr = allAssets[i]; - - if (IERC20(assetAddr).balanceOf(address(this)) >= outputs[i]) { - // Use Vault funds first if sufficient - IERC20(assetAddr).safeTransfer(msg.sender, outputs[i]); - } else { - address strategyAddr = assetDefaultStrategies[assetAddr]; - if (strategyAddr != address(0)) { - // Nothing in Vault, but something in Strategy, send from there - IStrategy strategy = IStrategy(strategyAddr); - strategy.withdraw(msg.sender, assetAddr, outputs[i]); - } else { - // Cant find funds anywhere - revert("Liquidity error"); - } - } + // transfer asset from the vault to the withdrawer + IERC20(asset).safeTransfer(msg.sender, amount); + + // Prevent insolvency + _postRedeem(amount.scaleBy(18, assetDecimals)); + } + + // slither-disable-end reentrancy-no-eth + /** + * @notice Claim a previously requested withdrawals once they are claimable. + * This requests can be claimed once the withdrawal queue's `claimable` amount + * is greater than or equal each request's `queued` amount and 10 minutes has passed. + * If one of the requests is not claimable, the whole transaction will revert with `Queue pending liquidity`. + * If one of the requests is not older than 10 minutes, + * the whole transaction will revert with `Claim delay not met`. + * @param _requestIds Unique ID of each withdrawal request + * @return amounts Amount of asset received for each request + * @return totalAmount Total amount of asset transferred to the withdrawer + */ + function claimWithdrawals(uint256[] calldata _requestIds) + external + virtual + whenNotCapitalPaused + nonReentrant + returns (uint256[] memory amounts, uint256 totalAmount) + { + // Add any asset to the withdrawal queue + // this needs to remain here as: + // - Vault can be funded and `addWithdrawalQueueLiquidity` is not externally called + // - funds can be withdrawn from a strategy + // + // Those funds need to be added to withdrawal queue liquidity + _addWithdrawalQueueLiquidity(); + + amounts = new uint256[](_requestIds.length); + for (uint256 i; i < _requestIds.length; ++i) { + // Scale all amounts to asset decimals, thus totalAmount is also in asset decimals + amounts[i] = _claimWithdrawal(_requestIds[i]); + totalAmount += amounts[i]; } - if (_minimumUnitAmount > 0) { - uint256 unitTotal = 0; - for (uint256 i = 0; i < outputs.length; ++i) { - unitTotal += _toUnits(outputs[i], allAssets[i]); - } - require( - unitTotal >= _minimumUnitAmount, - "Redeem amount lower than minimum" + // transfer all the claimed asset from the vault to the withdrawer + IERC20(asset).safeTransfer(msg.sender, totalAmount); + + // Prevent insolvency + _postRedeem(totalAmount.scaleBy(18, assetDecimals)); + + return (amounts, totalAmount); + } + + function _claimWithdrawal(uint256 requestId) + internal + returns (uint256 amount) + { + require(withdrawalClaimDelay > 0, "Async withdrawals not enabled"); + + // Load the structs from storage into memory + WithdrawalRequest memory request = withdrawalRequests[requestId]; + WithdrawalQueueMetadata memory queue = withdrawalQueueMetadata; + + require( + request.timestamp + withdrawalClaimDelay <= block.timestamp, + "Claim delay not met" + ); + // If there isn't enough reserved liquidity in the queue to claim + require(request.queued <= queue.claimable, "Queue pending liquidity"); + require(request.withdrawer == msg.sender, "Not requester"); + require(request.claimed == false, "Already claimed"); + + // Store the request as claimed + withdrawalRequests[requestId].claimed = true; + // Store the updated claimed amount + withdrawalQueueMetadata.claimed = + queue.claimed + + SafeCast.toUint128( + StableMath.scaleBy(request.amount, assetDecimals, 18) ); - } - oUSD.burn(msg.sender, _amount); + emit WithdrawalClaimed(msg.sender, requestId, request.amount); - _postRedeem(_amount); + return StableMath.scaleBy(request.amount, assetDecimals, 18); } function _postRedeem(uint256 _amount) internal { - // Until we can prove that we won't affect the prices of our assets + // Until we can prove that we won't affect the prices of our asset // by withdrawing them, this should be here. // It's possible that a strategy was off on its asset total, perhaps // a reward token sold for more or for less than anticipated. @@ -225,16 +353,16 @@ contract VaultCore is VaultInitializer { totalUnits = _totalValue(); } - // Check that the OTokens are backed by enough assets + // Check that the OTokens are backed by enough asset if (maxSupplyDiff > 0) { - // If there are more outstanding withdrawal requests than assets in the vault and strategies - // then the available assets will be negative and totalUnits will be rounded up to zero. + // If there are more outstanding withdrawal requests than asset in the vault and strategies + // then the available asset will be negative and totalUnits will be rounded up to zero. // As we don't know the exact shortfall amount, we will reject all redeem and withdrawals require(totalUnits > 0, "Too many outstanding requests"); // Allow a max difference of maxSupplyDiff% between - // backing assets value and OUSD total supply - uint256 diff = oUSD.totalSupply().divPrecisely(totalUnits); + // asset value and OUSD total supply + uint256 diff = oToken.totalSupply().divPrecisely(totalUnits); require( (diff > 1e18 ? diff - 1e18 : 1e18 - diff) <= maxSupplyDiff, "Backing supply liquidity error" @@ -242,118 +370,55 @@ contract VaultCore is VaultInitializer { } } - /** - * @notice Burn OTokens for Metapool Strategy - * @param _amount Amount of OUSD to burn - * - * @dev Notice: can't use `nonReentrant` modifier since the `redeem` function could - * require withdrawal on `ConvexOUSDMetaStrategy` and that one can call `burnForStrategy` - * while the execution of the `redeem` has not yet completed -> causing a `nonReentrant` collision. - * - * Also important to understand is that this is a limitation imposed by the test suite. - * Production / mainnet contracts should never be configured in a way where mint/redeem functions - * that are moving funds between the Vault and end user wallets can influence strategies - * utilizing this function. - */ - function burnForStrategy(uint256 _amount) - external - virtual - whenNotCapitalPaused - onlyOusdMetaStrategy - { - require(_amount < MAX_INT, "Amount too high"); - - emit Redeem(msg.sender, _amount); - - // safe to cast because of the require check at the beginning of the function - netOusdMintedForStrategy -= int256(_amount); - - require( - abs(netOusdMintedForStrategy) < netOusdMintForStrategyThreshold, - "Attempting to burn too much OUSD." - ); - - // Burn OTokens - oUSD.burn(msg.sender, _amount); - } - /** * @notice Allocate unallocated funds on Vault to strategies. - **/ + */ function allocate() external virtual whenNotCapitalPaused nonReentrant { + // Add any unallocated asset to the withdrawal queue first + _addWithdrawalQueueLiquidity(); + _allocate(); } /** - * @dev Allocate unallocated funds on Vault to strategies. - **/ + * @dev Allocate asset (eg. WETH or USDC) to the default asset strategy + * if there is excess to the Vault buffer. + * This is called from either `mint` or `allocate` and assumes `_addWithdrawalQueueLiquidity` + * has been called before this function. + */ function _allocate() internal virtual { - uint256 vaultValue = _totalValueInVault(); - // Nothing in vault to allocate - if (vaultValue == 0) return; - uint256 strategiesValue = _totalValueInStrategies(); - // We have a method that does the same as this, gas optimisation - uint256 calculatedTotalValue = vaultValue + strategiesValue; - - // We want to maintain a buffer on the Vault so calculate a percentage - // modifier to multiply each amount being allocated by to enforce the - // vault buffer - uint256 vaultBufferModifier; - if (strategiesValue == 0) { - // Nothing in Strategies, allocate 100% minus the vault buffer to - // strategies - vaultBufferModifier = uint256(1e18) - vaultBuffer; - } else { - vaultBufferModifier = - (vaultBuffer * calculatedTotalValue) / - vaultValue; - if (1e18 > vaultBufferModifier) { - // E.g. 1e18 - (1e17 * 10e18)/5e18 = 8e17 - // (5e18 * 8e17) / 1e18 = 4e18 allocated from Vault - vaultBufferModifier = uint256(1e18) - vaultBufferModifier; - } else { - // We need to let the buffer fill - return; - } - } - if (vaultBufferModifier == 0) return; - - // Iterate over all assets in the Vault and allocate to the appropriate - // strategy - uint256 assetCount = allAssets.length; - for (uint256 i = 0; i < assetCount; ++i) { - IERC20 asset = IERC20(allAssets[i]); - uint256 assetBalance = asset.balanceOf(address(this)); - // No balance, nothing to do here - if (assetBalance == 0) continue; - - // Multiply the balance by the vault buffer modifier and truncate - // to the scale of the asset decimals - uint256 allocateAmount = assetBalance.mulTruncate( - vaultBufferModifier - ); + // No need to do anything if no default strategy for asset + address depositStrategyAddr = defaultStrategy; + if (depositStrategyAddr == address(0)) return; + + uint256 assetAvailableInVault = _assetAvailable(); + // No need to do anything if there isn't any asset in the vault to allocate + if (assetAvailableInVault == 0) return; + + // Calculate the target buffer for the vault using the total supply + uint256 totalSupply = oToken.totalSupply(); + // Scaled to asset decimals + uint256 targetBuffer = totalSupply.mulTruncate(vaultBuffer).scaleBy( + assetDecimals, + 18 + ); - address depositStrategyAddr = assetDefaultStrategies[ - address(asset) - ]; - - if (depositStrategyAddr != address(0) && allocateAmount > 0) { - IStrategy strategy = IStrategy(depositStrategyAddr); - // Transfer asset to Strategy and call deposit method to - // mint or take required action - asset.safeTransfer(address(strategy), allocateAmount); - strategy.deposit(address(asset), allocateAmount); - emit AssetAllocated( - address(asset), - depositStrategyAddr, - allocateAmount - ); - } - } + // If available asset in the Vault is below or equal the target buffer then there's nothing to allocate + if (assetAvailableInVault <= targetBuffer) return; + + // The amount of asset to allocate to the default strategy + uint256 allocateAmount = assetAvailableInVault - targetBuffer; + + IStrategy strategy = IStrategy(depositStrategyAddr); + // Transfer asset to the strategy and call the strategy's deposit function + IERC20(asset).safeTransfer(address(strategy), allocateAmount); + strategy.deposit(asset, allocateAmount); + + emit AssetAllocated(asset, depositStrategyAddr, allocateAmount); } /** - * @notice Calculate the total value of assets held by the Vault and all + * @notice Calculate the total value of asset held by the Vault and all * strategies and update the supply of OTokens. */ function rebase() external virtual nonReentrant { @@ -361,13 +426,13 @@ contract VaultCore is VaultInitializer { } /** - * @dev Calculate the total value of assets held by the Vault and all + * @dev Calculate the total value of asset held by the Vault and all * strategies and update the supply of OTokens, optionally sending a * portion of the yield to the trustee. * @return totalUnits Total balance of Vault in units */ function _rebase() internal whenNotRebasePaused returns (uint256) { - uint256 supply = oUSD.totalSupply(); + uint256 supply = oToken.totalSupply(); uint256 vaultValue = _totalValue(); // If no supply yet, do not rebase if (supply == 0) { @@ -392,15 +457,15 @@ contract VaultCore is VaultInitializer { fee = (yield * trusteeFeeBps) / 1e4; if (fee > 0) { require(fee < yield, "Fee must not be greater than yield"); - oUSD.mint(_trusteeAddress, fee); + oToken.mint(_trusteeAddress, fee); } } emit YieldDistribution(_trusteeAddress, yield, fee); // Only ratchet OToken supply upwards // Final check uses latest totalSupply - if (newSupply > oUSD.totalSupply()) { - oUSD.changeSupply(newSupply); + if (newSupply > oToken.totalSupply()) { + oToken.changeSupply(newSupply); } return vaultValue; } @@ -411,17 +476,22 @@ contract VaultCore is VaultInitializer { * @return yield amount of expected yield */ function previewYield() external view returns (uint256 yield) { - (yield, ) = _nextYield(oUSD.totalSupply(), _totalValue()); + (yield, ) = _nextYield(oToken.totalSupply(), _totalValue()); return yield; } + /** + * @dev Calculates the amount that would rebase at next rebase. + * See this Readme for detailed explanation: + * contracts/contracts/vault/README - Yield Limits.md + */ function _nextYield(uint256 supply, uint256 vaultValue) internal view virtual returns (uint256 yield, uint256 targetRate) { - uint256 nonRebasing = oUSD.nonRebasingSupply(); + uint256 nonRebasing = oToken.nonRebasingSupply(); uint256 rebasing = supply - nonRebasing; uint256 elapsed = block.timestamp - lastRebase; targetRate = rebasePerSecondTarget; @@ -461,7 +531,7 @@ contract VaultCore is VaultInitializer { } /** - * @notice Determine the total value of assets held by the vault and its + * @notice Determine the total value of asset held by the vault and its * strategies. * @return value Total value in USD/ETH (1e18) */ @@ -470,66 +540,17 @@ contract VaultCore is VaultInitializer { } /** - * @dev Internal Calculate the total value of the assets held by the - * vault and its strategies. + * @dev Internal Calculate the total value of the asset held by the + * vault and its strategies. + * @dev The total value of all WETH held by the vault and all its strategies + * less any WETH that is reserved for the withdrawal queue. + * If there is not enough WETH in the vault and all strategies to cover + * all outstanding withdrawal requests then return a total value of 0. * @return value Total value in USD/ETH (1e18) */ function _totalValue() internal view virtual returns (uint256 value) { - return _totalValueInVault() + _totalValueInStrategies(); - } - - /** - * @dev Internal to calculate total value of all assets held in Vault. - * @return value Total value in USD/ETH (1e18) - */ - function _totalValueInVault() - internal - view - virtual - returns (uint256 value) - { - uint256 assetCount = allAssets.length; - for (uint256 y; y < assetCount; ++y) { - address assetAddr = allAssets[y]; - uint256 balance = IERC20(assetAddr).balanceOf(address(this)); - if (balance > 0) { - value += _toUnits(balance, assetAddr); - } - } - } - - /** - * @dev Internal to calculate total value of all assets held in Strategies. - * @return value Total value in USD/ETH (1e18) - */ - function _totalValueInStrategies() internal view returns (uint256 value) { - uint256 stratCount = allStrategies.length; - for (uint256 i = 0; i < stratCount; ++i) { - value = value + _totalValueInStrategy(allStrategies[i]); - } - } - - /** - * @dev Internal to calculate total value of all assets held by strategy. - * @param _strategyAddr Address of the strategy - * @return value Total value in USD/ETH (1e18) - */ - function _totalValueInStrategy(address _strategyAddr) - internal - view - returns (uint256 value) - { - IStrategy strategy = IStrategy(_strategyAddr); - uint256 assetCount = allAssets.length; - for (uint256 y; y < assetCount; ++y) { - address assetAddr = allAssets[y]; - if (strategy.supportsAsset(assetAddr)) { - uint256 balance = strategy.checkBalance(assetAddr); - if (balance > 0) { - value += _toUnits(balance, assetAddr); - } - } - } + // As asset is the only asset, just return the asset balance + value = _checkBalance(asset).scaleBy(18, assetDecimals); } /** @@ -543,6 +564,15 @@ contract VaultCore is VaultInitializer { /** * @notice Get the balance of an asset held in Vault and all strategies. + * @dev Get the balance of an asset held in Vault and all strategies + * less any asset that is reserved for the withdrawal queue. + * BaseAsset is the only asset that can return a non-zero balance. + * All other asset will return 0 even if there is some dust amounts left in the Vault. + * For example, there is 1 wei left of stETH (or USDC) in the OETH (or OUSD) Vault but + * will return 0 in this function. + * + * If there is not enough asset in the vault and all strategies to cover all outstanding + * withdrawal requests then return a asset balance of 0 * @param _asset Address of asset * @return balance Balance of asset in decimals of asset */ @@ -552,6 +582,9 @@ contract VaultCore is VaultInitializer { virtual returns (uint256 balance) { + if (_asset != asset) return 0; + + // Get the asset in the vault and the strategies IERC20 asset = IERC20(_asset); balance = asset.balanceOf(address(this)); uint256 stratCount = allStrategies.length; @@ -561,272 +594,108 @@ contract VaultCore is VaultInitializer { balance = balance + strategy.checkBalance(_asset); } } + + WithdrawalQueueMetadata memory queue = withdrawalQueueMetadata; + + // If the vault becomes insolvent enough that the total value in the vault and all strategies + // is less than the outstanding withdrawals. + // For example, there was a mass slashing event and most users request a withdrawal. + if (balance + queue.claimed < queue.queued) { + return 0; + } + + // Need to remove asset that is reserved for the withdrawal queue + return balance + queue.claimed - queue.queued; } /** - * @notice Calculate the outputs for a redeem function, i.e. the mix of - * coins that will be returned + * @notice Adds WETH to the withdrawal queue if there is a funding shortfall. + * @dev is called from the Native Staking strategy when validator withdrawals are processed. + * It also called before any WETH is allocated to a strategy. */ - function calculateRedeemOutputs(uint256 _amount) - external - view - returns (uint256[] memory) - { - return _calculateRedeemOutputs(_amount); + function addWithdrawalQueueLiquidity() external { + _addWithdrawalQueueLiquidity(); } /** - * @dev Calculate the outputs for a redeem function, i.e. the mix of - * coins that will be returned. - * @return outputs Array of amounts respective to the supported assets + * @dev Adds asset (eg. WETH or USDC) to the withdrawal queue if there is a funding shortfall. + * This assumes 1 asset equal 1 corresponding OToken. */ - function _calculateRedeemOutputs(uint256 _amount) + function _addWithdrawalQueueLiquidity() internal - view - virtual - returns (uint256[] memory outputs) + returns (uint256 addedClaimable) { - // We always give out coins in proportion to how many we have, - // Now if all coins were the same value, this math would easy, - // just take the percentage of each coin, and multiply by the - // value to be given out. But if coins are worth more than $1, - // then we would end up handing out too many coins. We need to - // adjust by the total value of coins. - // - // To do this, we total up the value of our coins, by their - // percentages. Then divide what we would otherwise give out by - // this number. - // - // Let say we have 100 DAI at $1.06 and 200 USDT at $1.00. - // So for every 1 DAI we give out, we'll be handing out 2 USDT - // Our total output ratio is: 33% * 1.06 + 66% * 1.00 = 1.02 - // - // So when calculating the output, we take the percentage of - // each coin, times the desired output value, divided by the - // totalOutputRatio. - // - // For example, withdrawing: 30 OUSD: - // DAI 33% * 30 / 1.02 = 9.80 DAI - // USDT = 66 % * 30 / 1.02 = 19.60 USDT - // - // Checking these numbers: - // 9.80 DAI * 1.06 = $10.40 - // 19.60 USDT * 1.00 = $19.60 - // - // And so the user gets $10.40 + $19.60 = $30 worth of value. + WithdrawalQueueMetadata memory queue = withdrawalQueueMetadata; - uint256 assetCount = allAssets.length; - uint256[] memory assetUnits = new uint256[](assetCount); - uint256[] memory assetBalances = new uint256[](assetCount); - outputs = new uint256[](assetCount); + // Check if the claimable asset is less than the queued amount + uint256 queueShortfall = queue.queued - queue.claimable; - // Calculate redeem fee - if (redeemFeeBps > 0) { - uint256 redeemFee = _amount.mulTruncateScale(redeemFeeBps, 1e4); - _amount = _amount - redeemFee; + // No need to do anything is the withdrawal queue is full funded + if (queueShortfall == 0) { + return 0; } - // Calculate assets balances and decimals once, - // for a large gas savings. - uint256 totalUnits = 0; - for (uint256 i = 0; i < assetCount; ++i) { - address assetAddr = allAssets[i]; - uint256 balance = _checkBalance(assetAddr); - assetBalances[i] = balance; - assetUnits[i] = _toUnits(balance, assetAddr); - totalUnits = totalUnits + assetUnits[i]; - } - // Calculate totalOutputRatio - uint256 totalOutputRatio = 0; - for (uint256 i = 0; i < assetCount; ++i) { - uint256 unitPrice = _toUnitPrice(allAssets[i], false); - uint256 ratio = (assetUnits[i] * unitPrice) / totalUnits; - totalOutputRatio = totalOutputRatio + ratio; - } - // Calculate final outputs - uint256 factor = _amount.divPrecisely(totalOutputRatio); - for (uint256 i = 0; i < assetCount; ++i) { - outputs[i] = (assetBalances[i] * factor) / totalUnits; - } - } + uint256 assetBalance = IERC20(asset).balanceOf(address(this)); - /*************************************** - Pricing - ****************************************/ + // Of the claimable withdrawal requests, how much is unclaimed? + // That is, the amount of asset that is currently allocated for the withdrawal queue + uint256 allocatedBaseAsset = queue.claimable - queue.claimed; - /** - * @notice Returns the total price in 18 digit units for a given asset. - * Never goes above 1, since that is how we price mints. - * @param asset address of the asset - * @return price uint256: unit (USD / ETH) price for 1 unit of the asset, in 18 decimal fixed - */ - function priceUnitMint(address asset) - external - view - returns (uint256 price) - { - /* need to supply 1 asset unit in asset's decimals and can not just hard-code - * to 1e18 and ignore calling `_toUnits` since we need to consider assets - * with the exchange rate - */ - uint256 units = _toUnits( - uint256(1e18).scaleBy(_getDecimals(asset), 18), - asset - ); - price = (_toUnitPrice(asset, true) * units) / 1e18; - } + // If there is no unallocated asset then there is nothing to add to the queue + if (assetBalance <= allocatedBaseAsset) { + return 0; + } - /** - * @notice Returns the total price in 18 digit unit for a given asset. - * Never goes below 1, since that is how we price redeems - * @param asset Address of the asset - * @return price uint256: unit (USD / ETH) price for 1 unit of the asset, in 18 decimal fixed - */ - function priceUnitRedeem(address asset) - external - view - returns (uint256 price) - { - /* need to supply 1 asset unit in asset's decimals and can not just hard-code - * to 1e18 and ignore calling `_toUnits` since we need to consider assets - * with the exchange rate - */ - uint256 units = _toUnits( - uint256(1e18).scaleBy(_getDecimals(asset), 18), - asset - ); - price = (_toUnitPrice(asset, false) * units) / 1e18; - } + uint256 unallocatedBaseAsset = assetBalance - allocatedBaseAsset; + // the new claimable amount is the smaller of the queue shortfall or unallocated asset + addedClaimable = queueShortfall < unallocatedBaseAsset + ? queueShortfall + : unallocatedBaseAsset; + uint256 newClaimable = queue.claimable + addedClaimable; - /*************************************** - Utils - ****************************************/ + // Store the new claimable amount back to storage + withdrawalQueueMetadata.claimable = SafeCast.toUint128(newClaimable); - /** - * @dev Convert a quantity of a token into 1e18 fixed decimal "units" - * in the underlying base (USD/ETH) used by the vault. - * Price is not taken into account, only quantity. - * - * Examples of this conversion: - * - * - 1e18 DAI becomes 1e18 units (same decimals) - * - 1e6 USDC becomes 1e18 units (decimal conversion) - * - 1e18 rETH becomes 1.2e18 units (exchange rate conversion) - * - * @param _raw Quantity of asset - * @param _asset Core Asset address - * @return value 1e18 normalized quantity of units - */ - function _toUnits(uint256 _raw, address _asset) - internal - view - returns (uint256) - { - UnitConversion conversion = assets[_asset].unitConversion; - if (conversion == UnitConversion.DECIMALS) { - return _raw.scaleBy(18, _getDecimals(_asset)); - } else if (conversion == UnitConversion.GETEXCHANGERATE) { - uint256 exchangeRate = IGetExchangeRateToken(_asset) - .getExchangeRate(); - return (_raw * exchangeRate) / 1e18; - } else { - revert("Unsupported conversion type"); - } + // emit a WithdrawalClaimable event + emit WithdrawalClaimable(newClaimable, addedClaimable); } /** - * @dev Returns asset's unit price accounting for different asset types - * and takes into account the context in which that price exists - - * - mint or redeem. - * - * Note: since we are returning the price of the unit and not the one of the - * asset (see comment above how 1 rETH exchanges for 1.2 units) we need - * to make the Oracle price adjustment as well since we are pricing the - * units and not the assets. - * - * The price also snaps to a "full unit price" in case a mint or redeem - * action would be unfavourable to the protocol. - * + * @dev Calculate how much asset (eg. WETH or USDC) in the vault is not reserved for the withdrawal queue. + * That is, it is available to be redeemed or deposited into a strategy. */ - function _toUnitPrice(address _asset, bool isMint) - internal - view - returns (uint256 price) - { - UnitConversion conversion = assets[_asset].unitConversion; - price = IOracle(priceProvider).price(_asset); - - if (conversion == UnitConversion.GETEXCHANGERATE) { - uint256 exchangeRate = IGetExchangeRateToken(_asset) - .getExchangeRate(); - price = (price * 1e18) / exchangeRate; - } else if (conversion != UnitConversion.DECIMALS) { - revert("Unsupported conversion type"); - } + function _assetAvailable() internal view returns (uint256 assetAvailable) { + WithdrawalQueueMetadata memory queue = withdrawalQueueMetadata; - /* At this stage the price is already adjusted to the unit - * so the price checks are agnostic to underlying asset being - * pegged to a USD or to an ETH or having a custom exchange rate. - */ - require(price <= MAX_UNIT_PRICE_DRIFT, "Vault: Price exceeds max"); - require(price >= MIN_UNIT_PRICE_DRIFT, "Vault: Price under min"); - - if (isMint) { - /* Never price a normalized unit price for more than one - * unit of OETH/OUSD when minting. - */ - if (price > 1e18) { - price = 1e18; - } - require(price >= MINT_MINIMUM_UNIT_PRICE, "Asset price below peg"); - } else { - /* Never give out more than 1 normalized unit amount of assets - * for one unit of OETH/OUSD when redeeming. - */ - if (price < 1e18) { - price = 1e18; - } - } - } + // The amount of asset that is still to be claimed in the withdrawal queue + uint256 outstandingWithdrawals = queue.queued - queue.claimed; - /** - * @dev Get the number of decimals of a token asset - * @param _asset Address of the asset - * @return decimals number of decimals - */ - function _getDecimals(address _asset) - internal - view - returns (uint256 decimals) - { - decimals = assets[_asset].decimals; - require(decimals > 0, "Decimals not cached"); - } + // The amount of sitting in asset in the vault + uint256 assetBalance = IERC20(asset).balanceOf(address(this)); + // If there is not enough asset in the vault to cover the outstanding withdrawals + if (assetBalance <= outstandingWithdrawals) return 0; - /** - * @notice Return the number of assets supported by the Vault. - */ - function getAssetCount() public view returns (uint256) { - return allAssets.length; + return assetBalance - outstandingWithdrawals; } + /*************************************** + Utils + ****************************************/ + /** - * @notice Gets the vault configuration of a supported asset. - * @param _asset Address of the token asset + * @notice Return the number of asset supported by the Vault. */ - function getAssetConfig(address _asset) - public - view - returns (Asset memory config) - { - config = assets[_asset]; + function getAssetCount() public view returns (uint256) { + return 1; } /** * @notice Return all vault asset addresses in order */ function getAllAssets() external view returns (address[] memory) { - return allAssets; + address[] memory a = new address[](1); + a[0] = asset; + return a; } /** @@ -849,59 +718,7 @@ contract VaultCore is VaultInitializer { * @return true if supported */ function isSupportedAsset(address _asset) external view returns (bool) { - return assets[_asset].isSupported; - } - - function ADMIN_IMPLEMENTATION() external view returns (address adminImpl) { - bytes32 slot = adminImplPosition; - // solhint-disable-next-line no-inline-assembly - assembly { - adminImpl := sload(slot) - } - } - - /** - * @dev Falldown to the admin implementation - * @notice This is a catch all for all functions not declared in core - */ - // solhint-disable-next-line no-complex-fallback - fallback() external { - bytes32 slot = adminImplPosition; - // solhint-disable-next-line no-inline-assembly - assembly { - // Copy msg.data. We take full control of memory in this inline assembly - // block because it will not return to Solidity code. We overwrite the - // Solidity scratch pad at memory position 0. - calldatacopy(0, 0, calldatasize()) - - // Call the implementation. - // out and outsize are 0 because we don't know the size yet. - let result := delegatecall( - gas(), - sload(slot), - 0, - calldatasize(), - 0, - 0 - ) - - // Copy the returned data. - returndatacopy(0, 0, returndatasize()) - - switch result - // delegatecall returns 0 on error. - case 0 { - revert(0, returndatasize()) - } - default { - return(0, returndatasize()) - } - } - } - - function abs(int256 x) private pure returns (uint256) { - require(x < int256(MAX_INT), "Amount too high"); - return x >= 0 ? uint256(x) : uint256(-x); + return asset == _asset; } function _min(uint256 a, uint256 b) internal pure returns (uint256) { diff --git a/contracts/contracts/vault/VaultInitializer.sol b/contracts/contracts/vault/VaultInitializer.sol index 692a3d1e83..da85904281 100644 --- a/contracts/contracts/vault/VaultInitializer.sol +++ b/contracts/contracts/vault/VaultInitializer.sol @@ -9,24 +9,17 @@ pragma solidity ^0.8.0; import "./VaultStorage.sol"; -contract VaultInitializer is VaultStorage { - function initialize(address _priceProvider, address _oToken) - external - onlyGovernor - initializer - { - require(_priceProvider != address(0), "PriceProvider address is zero"); - require(_oToken != address(0), "oToken address is zero"); +abstract contract VaultInitializer is VaultStorage { + constructor(address _asset) VaultStorage(_asset) {} - oUSD = OUSD(_oToken); + function initialize(address _oToken) external onlyGovernor initializer { + require(_oToken != address(0), "oToken address is zero"); - priceProvider = _priceProvider; + oToken = OUSD(_oToken); rebasePaused = false; capitalPaused = true; - // Initial redeem fee of 0 basis points - redeemFeeBps = 0; // Initial Vault buffer of 0% vaultBuffer = 0; // Initial allocate threshold of 25,000 OUSD @@ -35,7 +28,7 @@ contract VaultInitializer is VaultStorage { rebaseThreshold = 1000e18; // Initialize all strategies allStrategies = new address[](0); - // Start with drip duration disabled - dripDuration = 1; + // Start with drip duration: 7 days + dripDuration = 604800; } } diff --git a/contracts/contracts/vault/VaultStorage.sol b/contracts/contracts/vault/VaultStorage.sol index 53ef34a936..6cb13457ba 100644 --- a/contracts/contracts/vault/VaultStorage.sol +++ b/contracts/contracts/vault/VaultStorage.sol @@ -12,17 +12,15 @@ import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.s import { Address } from "@openzeppelin/contracts/utils/Address.sol"; import { IStrategy } from "../interfaces/IStrategy.sol"; +import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import { Governable } from "../governance/Governable.sol"; import { OUSD } from "../token/OUSD.sol"; import { Initializable } from "../utils/Initializable.sol"; import "../utils/Helpers.sol"; -contract VaultStorage is Initializable, Governable { +abstract contract VaultStorage is Initializable, Governable { using SafeERC20 for IERC20; - event AssetSupported(address _asset); - event AssetRemoved(address _asset); - event AssetDefaultStrategyUpdated(address _asset, address _strategy); event AssetAllocated(address _asset, address _strategy, uint256 _amount); event StrategyApproved(address _addr); event StrategyRemoved(address _addr); @@ -30,12 +28,10 @@ contract VaultStorage is Initializable, Governable { event Redeem(address _addr, uint256 _value); event CapitalPaused(); event CapitalUnpaused(); + event DefaultStrategyUpdated(address _strategy); event RebasePaused(); event RebaseUnpaused(); event VaultBufferUpdated(uint256 _vaultBuffer); - event OusdMetaStrategyUpdated(address _ousdMetaStrategy); - event RedeemFeeUpdated(uint256 _redeemFeeBps); - event PriceProviderUpdated(address _priceProvider); event AllocateThresholdUpdated(uint256 _threshold); event RebaseThresholdUpdated(uint256 _threshold); event StrategistUpdated(address _address); @@ -43,16 +39,6 @@ contract VaultStorage is Initializable, Governable { event YieldDistribution(address _to, uint256 _yield, uint256 _fee); event TrusteeFeeBpsChanged(uint256 _basis); event TrusteeAddressChanged(address _address); - event NetOusdMintForStrategyThresholdChanged(uint256 _threshold); - event SwapperChanged(address _address); - event SwapAllowedUndervalueChanged(uint256 _basis); - event SwapSlippageChanged(address _asset, uint256 _basis); - event Swapped( - address indexed _fromAsset, - address indexed _toAsset, - uint256 _fromAssetAmount, - uint256 _toAssetAmount - ); event StrategyAddedToMintWhitelist(address indexed strategy); event StrategyRemovedFromMintWhitelist(address indexed strategy); event RebasePerSecondMaxChanged(uint256 rebaseRatePerSecond); @@ -77,27 +63,10 @@ contract VaultStorage is Initializable, Governable { // slither-disable-start uninitialized-state // slither-disable-start constable-states - // Assets supported by the Vault, i.e. Stablecoins - enum UnitConversion { - DECIMALS, - GETEXCHANGERATE - } - // Changed to fit into a single storage slot so the decimals needs to be recached - struct Asset { - // Note: OETHVaultCore doesn't use `isSupported` when minting, - // redeeming or checking balance of assets. - bool isSupported; - UnitConversion unitConversion; - uint8 decimals; - // Max allowed slippage from the Oracle price when swapping collateral assets in basis points. - // For example 40 == 0.4% slippage - uint16 allowedOracleSlippageBps; - } - /// @dev mapping of supported vault assets to their configuration - mapping(address => Asset) internal assets; + uint256 private _deprecated_assets; /// @dev list of all assets supported by the vault. - address[] internal allAssets; + address[] private _deprecated_allAssets; // Strategies approved for use by the Vault struct Strategy { @@ -110,14 +79,14 @@ contract VaultStorage is Initializable, Governable { address[] internal allStrategies; /// @notice Address of the Oracle price provider contract - address public priceProvider; + address private _deprecated_priceProvider; /// @notice pause rebasing if true bool public rebasePaused; /// @notice pause operations that change the OToken supply. /// eg mint, redeem, allocate, mint/burn for strategy bool public capitalPaused; /// @notice Redemption fee in basis points. eg 50 = 0.5% - uint256 public redeemFeeBps; + uint256 private _deprecated_redeemFeeBps; /// @notice Percentage of assets to keep in Vault to handle (most) withdrawals. 100% = 1e18. uint256 public vaultBuffer; /// @notice OToken mints over this amount automatically allocate funds. 18 decimals. @@ -126,12 +95,7 @@ contract VaultStorage is Initializable, Governable { uint256 public rebaseThreshold; /// @dev Address of the OToken token. eg OUSD or OETH. - OUSD public oUSD; - - /// @dev Storage slot for the address of the VaultAdmin contract that is delegated to - // keccak256("OUSD.vault.governor.admin.impl"); - bytes32 public constant adminImplPosition = - 0xa2bd3d3cf188a41358c8b401076eb59066b09dec5775650c0de4c55187d17bd9; + OUSD public oToken; /// @dev Address of the contract responsible for post rebase syncs with AMMs address private _deprecated_rebaseHooksAddr = address(0); @@ -144,7 +108,7 @@ contract VaultStorage is Initializable, Governable { /// @notice Mapping of asset address to the Strategy that they should automatically // be allocated to - mapping(address => address) public assetDefaultStrategies; + uint256 private _deprecated_assetDefaultStrategies; /// @notice Max difference between total supply and total value of assets. 18 decimals. uint256 public maxSupplyDiff; @@ -158,31 +122,17 @@ contract VaultStorage is Initializable, Governable { /// @dev Deprecated: Tokens that should be swapped for stablecoins address[] private _deprecated_swapTokens; - uint256 constant MINT_MINIMUM_UNIT_PRICE = 0.998e18; - /// @notice Metapool strategy that is allowed to mint/burn OTokens without changing collateral - address public ousdMetaStrategy; + address private _deprecated_ousdMetaStrategy; /// @notice How much OTokens are currently minted by the strategy - int256 public netOusdMintedForStrategy; + int256 private _deprecated_netOusdMintedForStrategy; /// @notice How much net total OTokens are allowed to be minted by all strategies - uint256 public netOusdMintForStrategyThreshold; - - uint256 constant MIN_UNIT_PRICE_DRIFT = 0.7e18; - uint256 constant MAX_UNIT_PRICE_DRIFT = 1.3e18; - - /// @notice Collateral swap configuration. - /// @dev is packed into a single storage slot to save gas. - struct SwapConfig { - // Contract that swaps the vault's collateral assets - address swapper; - // Max allowed percentage the total value can drop below the total supply in basis points. - // For example 100 == 1% - uint16 allowedUndervalueBps; - } - SwapConfig internal swapConfig = SwapConfig(address(0), 0); + uint256 private _deprecated_netOusdMintForStrategyThreshold; + + uint256 private _deprecated_swapConfig; // List of strategies that can mint oTokens directly // Used in OETHBaseVaultCore @@ -249,25 +199,32 @@ contract VaultStorage is Initializable, Governable { uint256 internal constant MAX_REBASE_PER_SECOND = uint256(0.05 ether) / 1 days; + /// @notice Default strategy for asset + address public defaultStrategy; + // For future use - uint256[43] private __gap; + uint256[42] private __gap; + + /// @notice Index of WETH asset in allAssets array + /// Legacy OETHVaultCore code, relocated here for vault consistency. + uint256 private _deprecated_wethAssetIndex; + + /// @dev Address of the asset (eg. WETH or USDC) + address public immutable asset; + uint8 internal immutable assetDecimals; // slither-disable-end constable-states // slither-disable-end uninitialized-state - /** - * @notice set the implementation for the admin, this needs to be in a base class else we cannot set it - * @param newImpl address of the implementation - */ - function setAdminImpl(address newImpl) external onlyGovernor { - require( - Address.isContract(newImpl), - "new implementation is not a contract" - ); - bytes32 position = adminImplPosition; - // solhint-disable-next-line no-inline-assembly - assembly { - sstore(position, newImpl) - } + constructor(address _asset) { + uint8 _decimals = IERC20Metadata(_asset).decimals(); + require(_decimals <= 18, "invalid asset decimals"); + asset = _asset; + assetDecimals = _decimals; + } + + /// @notice Deprecated: use `oToken()` instead. + function oUSD() external view returns (OUSD) { + return oToken; } } diff --git a/contracts/contracts/vault/AbstractOTokenZapper.sol b/contracts/contracts/zapper/AbstractOTokenZapper.sol similarity index 100% rename from contracts/contracts/vault/AbstractOTokenZapper.sol rename to contracts/contracts/zapper/AbstractOTokenZapper.sol diff --git a/contracts/contracts/vault/OETHBaseZapper.sol b/contracts/contracts/zapper/OETHBaseZapper.sol similarity index 100% rename from contracts/contracts/vault/OETHBaseZapper.sol rename to contracts/contracts/zapper/OETHBaseZapper.sol diff --git a/contracts/contracts/vault/OETHZapper.sol b/contracts/contracts/zapper/OETHZapper.sol similarity index 100% rename from contracts/contracts/vault/OETHZapper.sol rename to contracts/contracts/zapper/OETHZapper.sol diff --git a/contracts/contracts/vault/OSonicZapper.sol b/contracts/contracts/zapper/OSonicZapper.sol similarity index 100% rename from contracts/contracts/vault/OSonicZapper.sol rename to contracts/contracts/zapper/OSonicZapper.sol diff --git a/contracts/deploy/base/000_mock.js b/contracts/deploy/base/000_mock.js index d06adc7ab5..8cf6c1b249 100644 --- a/contracts/deploy/base/000_mock.js +++ b/contracts/deploy/base/000_mock.js @@ -6,6 +6,8 @@ const addresses = require("../../utils/addresses"); const deployMocks = async () => { await deployWithConfirmation("MockWETH", []); await deployWithConfirmation("MockAero", []); + + await deployWithConfirmation("MockStrategy", []); }; const deployWOETH = async () => { @@ -22,11 +24,11 @@ const deployWOETH = async () => { // Initialize the proxy // prettier-ignore await cWOETHProxy["initialize(address,address,bytes)"]( - cWOETHImpl.address, - governorAddr, - "0x", - await getTxOpts() - ); + cWOETHImpl.address, + governorAddr, + "0x", + await getTxOpts() + ); // Initialize implementation const cWOETH = await ethers.getContractAt( @@ -83,11 +85,7 @@ const deployCore = async () => { const dwOETHb = await deployWithConfirmation("WOETHBase", [ cOETHbProxy.address, // Base token ]); - const dOETHbVault = await deployWithConfirmation("OETHVault"); - const dOETHbVaultCore = await deployWithConfirmation("OETHBaseVaultCore", [ - cWETH.address, - ]); - const dOETHbVaultAdmin = await deployWithConfirmation("OETHBaseVaultAdmin", [ + const dOETHbVaultAdmin = await deployWithConfirmation("OETHBaseVault", [ cWETH.address, ]); @@ -98,7 +96,6 @@ const deployCore = async () => { "IVault", cOETHbVaultProxy.address ); - const cOracleRouter = await ethers.getContract("MockOracleRouter"); // Init OETHb const resolution = ethers.utils.parseUnits("1", 27); @@ -119,16 +116,15 @@ const deployCore = async () => { // Init OETHbVault const initDataOETHbVault = cOETHbVault.interface.encodeFunctionData( - "initialize(address,address)", + "initialize(address)", [ - cOracleRouter.address, // OracleRouter cOETHbProxy.address, // OETHb ] ); // prettier-ignore await cOETHbVaultProxy .connect(sDeployer)["initialize(address,address,bytes)"]( - dOETHbVault.address, + dOETHbVaultAdmin.address, governorAddr, initDataOETHbVault ); @@ -146,10 +142,8 @@ const deployCore = async () => { initDatawOETHb ) - await cOETHbVaultProxy.connect(sGovernor).upgradeTo(dOETHbVaultCore.address); - await cOETHbVault.connect(sGovernor).setAdminImpl(dOETHbVaultAdmin.address); + await cOETHbVaultProxy.connect(sGovernor).upgradeTo(dOETHbVaultAdmin.address); - await cOETHbVault.connect(sGovernor).supportAsset(cWETH.address, 0); await cOETHbVault.connect(sGovernor).unpauseCapital(); }; @@ -171,11 +165,13 @@ const deployBridgedWOETHStrategy = async () => { await deployWithConfirmation("BridgedWOETHStrategyProxy"); const cStrategyProxy = await ethers.getContract("BridgedWOETHStrategyProxy"); + const cOracleRouter = await ethers.getContract("MockOracleRouter"); const dStrategyImpl = await deployWithConfirmation("BridgedWOETHStrategy", [ [addresses.zero, cOETHbVaultProxy.address], cWETH.address, cWOETHProxy.address, cOETHbProxy.address, + cOracleRouter.address, ]); const cStrategy = await ethers.getContractAt( "BridgedWOETHStrategy", diff --git a/contracts/deploy/base/040_vault_upgrade.js b/contracts/deploy/base/040_vault_upgrade.js new file mode 100644 index 0000000000..b44242adb2 --- /dev/null +++ b/contracts/deploy/base/040_vault_upgrade.js @@ -0,0 +1,79 @@ +const { deployOnBase } = require("../../utils/deploy-l2"); +const { deployWithConfirmation } = require("../../utils/deploy"); +const addresses = require("../../utils/addresses"); + +module.exports = deployOnBase( + { + deployName: "040_vault_upgrade", + //proposalId: "", + }, + async ({ ethers }) => { + // 1. Deploy OETHBaseVault implementations + const dOETHbVault = await deployWithConfirmation( + "OETHBaseVault", + [addresses.base.WETH], + "OETHBaseVault", + true + ); + + // 2. Connect to the OETHBase Vault as its governor via the proxy + const cOETHbVaultProxy = await ethers.getContract("OETHBaseVaultProxy"); + console.log("OETHb Vault Proxy Address:", cOETHbVaultProxy.address); + const cOETHbVault = await ethers.getContractAt( + "IVault", + cOETHbVaultProxy.address + ); + + // 3. Connect to the Aerodrome AMO + const defaultStrategy = await ethers.getContract( + "AerodromeAMOStrategyProxy" + ); + + // 4. Connect to Bridged WOETH Strategy + const bridgedWOETHStrategy = await ethers.getContract( + "BridgedWOETHStrategyProxy" + ); + + // 5. Connect to oracle router + const cOracleRouter = await ethers.getContract("OETHBaseOracleRouter"); + + // 6. Connect to OETHb Proxy + const cOETHbtProxy = await ethers.getContract("OETHBaseProxy"); + + // 7. Deploy new Bridged WOETH Strategy implementation (with oracle as immutable) + const dStrategyImpl = await deployWithConfirmation("BridgedWOETHStrategy", [ + [addresses.zero, cOETHbVaultProxy.address], + addresses.base.WETH, + addresses.base.BridgedWOETH, + cOETHbtProxy.address, + cOracleRouter.address, + ]); + + // ---------------- + // Governance Actions + // ---------------- + return { + name: "Upgrade OETHBaseVault to new single Vault implementations", + actions: [ + // 1. Upgrade Bridged WOETH Strategy implementation + { + contract: bridgedWOETHStrategy, + signature: "upgradeTo(address)", + args: [dStrategyImpl.address], + }, + // 2. Upgrade OETHBaseVaultProxy to new implementation + { + contract: cOETHbVaultProxy, + signature: "upgradeTo(address)", + args: [dOETHbVault.address], + }, + // 3. Set Aerodrome AMO as default strategy + { + contract: cOETHbVault, + signature: "setDefaultStrategy(address)", + args: [defaultStrategy.address], + }, + ], + }; + } +); diff --git a/contracts/deploy/deployActions.js b/contracts/deploy/deployActions.js index 6807da0f6e..7203da9f6a 100644 --- a/contracts/deploy/deployActions.js +++ b/contracts/deploy/deployActions.js @@ -294,7 +294,6 @@ const deployConvexOUSDMetaStrategy = async () => { * Configure Vault by adding supported assets and Strategies. */ const configureVault = async () => { - const assetAddresses = await getAssetAddresses(deployments); const { governorAddr, strategistAddr } = await getNamedAccounts(); // Signers const sGovernor = await ethers.provider.getSigner(governorAddr); @@ -305,19 +304,6 @@ const configureVault = async () => { await ethers.getContract("VaultProxy") ).address ); - // Set up supported assets for Vault - await withConfirmation( - cVault.connect(sGovernor).supportAsset(assetAddresses.USDS, 0) - ); - log("Added USDS asset to Vault"); - await withConfirmation( - cVault.connect(sGovernor).supportAsset(assetAddresses.USDT, 0) - ); - log("Added USDT asset to Vault"); - await withConfirmation( - cVault.connect(sGovernor).supportAsset(assetAddresses.USDC, 0) - ); - log("Added USDC asset to Vault"); // Unpause deposits await withConfirmation(cVault.connect(sGovernor).unpauseCapital()); log("Unpaused deposits on Vault"); @@ -325,13 +311,17 @@ const configureVault = async () => { await withConfirmation( cVault.connect(sGovernor).setStrategistAddr(strategistAddr) ); + + // Set withdrawal claim delay to 10m + await withConfirmation( + cVault.connect(sGovernor).setWithdrawalClaimDelay(10 * 60) + ); }; /** * Configure OETH Vault by adding supported assets and Strategies. */ -const configureOETHVault = async (isSimpleOETH) => { - const assetAddresses = await getAssetAddresses(deployments); +const configureOETHVault = async () => { let { governorAddr, deployerAddr, strategistAddr } = await getNamedAccounts(); // Signers let sGovernor = await ethers.provider.getSigner(governorAddr); @@ -349,14 +339,6 @@ const configureOETHVault = async (isSimpleOETH) => { sGovernor = sDeployer; } - // Set up supported assets for Vault - const { WETH, RETH, stETH, frxETH } = assetAddresses; - const assets = isSimpleOETH ? [WETH] : [WETH, RETH, stETH, frxETH]; - for (const asset of assets) { - await withConfirmation(cVault.connect(sGovernor).supportAsset(asset, 0)); - } - log("Added assets to OETH Vault"); - // Unpause deposits await withConfirmation(cVault.connect(sGovernor).unpauseCapital()); log("Unpaused deposits on OETH Vault"); @@ -365,12 +347,6 @@ const configureOETHVault = async (isSimpleOETH) => { cVault.connect(sGovernor).setStrategistAddr(strategistAddr) ); - // Cache WETH asset address - await withConfirmation(cVault.connect(sGovernor).cacheWETHAssetIndex()); - - // Redeem fee to 0 - await withConfirmation(cVault.connect(sGovernor).setRedeemFeeBps(0)); - // Allocate threshold await withConfirmation( cVault @@ -519,29 +495,47 @@ const configureStrategies = async (harvesterProxy, oethHarvesterProxy) => { // Signers const sGovernor = await ethers.provider.getSigner(governorAddr); - const compoundProxy = await ethers.getContract("CompoundStrategyProxy"); - const compound = await ethers.getContractAt( - "CompoundStrategy", - compoundProxy.address - ); - await withConfirmation( - compound.connect(sGovernor).setHarvesterAddress(harvesterProxy.address) - ); + // Configure Compound Strategy if deployed + const compoundDeployment = await hre.deployments + .get("CompoundStrategyProxy") + .catch(() => null); + if (compoundDeployment) { + const compound = await ethers.getContractAt( + "CompoundStrategy", + compoundDeployment.address + ); + await withConfirmation( + compound.connect(sGovernor).setHarvesterAddress(harvesterProxy.address) + ); + } - const aaveProxy = await ethers.getContract("AaveStrategyProxy"); - const aave = await ethers.getContractAt("AaveStrategy", aaveProxy.address); - await withConfirmation( - aave.connect(sGovernor).setHarvesterAddress(harvesterProxy.address) - ); + // Configure Aave Strategy if deployed + const aaveDeployment = await hre.deployments + .get("AaveStrategyProxy") + .catch(() => null); + if (aaveDeployment) { + const aave = await ethers.getContractAt( + "AaveStrategy", + aaveDeployment.address + ); + await withConfirmation( + aave.connect(sGovernor).setHarvesterAddress(harvesterProxy.address) + ); + } - const convexProxy = await ethers.getContract("ConvexStrategyProxy"); - const convex = await ethers.getContractAt( - "ConvexStrategy", - convexProxy.address - ); - await withConfirmation( - convex.connect(sGovernor).setHarvesterAddress(harvesterProxy.address) - ); + // Configure Convex Strategy if deployed + const convexDeployment = await hre.deployments + .get("ConvexStrategyProxy") + .catch(() => null); + if (convexDeployment) { + const convex = await ethers.getContractAt( + "ConvexStrategy", + convexDeployment.address + ); + await withConfirmation( + convex.connect(sGovernor).setHarvesterAddress(harvesterProxy.address) + ); + } const nativeStakingSSVStrategyProxy = await ethers.getContract( "NativeStakingSSVStrategyProxy" @@ -1091,11 +1085,7 @@ const deployOETHCore = async () => { // Main contracts const dOETH = await deployWithConfirmation("OETH"); - const dOETHVault = await deployWithConfirmation("OETHVault"); - const dOETHVaultCore = await deployWithConfirmation("OETHVaultCore", [ - assetAddresses.WETH, - ]); - const dOETHVaultAdmin = await deployWithConfirmation("OETHVaultAdmin", [ + const dOETHVault = await deployWithConfirmation("OETHVault", [ assetAddresses.WETH, ]); @@ -1103,10 +1093,6 @@ const deployOETHCore = async () => { const cOETHProxy = await ethers.getContract("OETHProxy"); const cOETHVaultProxy = await ethers.getContract("OETHVaultProxy"); const cOETH = await ethers.getContractAt("OETH", cOETHProxy.address); - - const oracleRouterContractName = - isMainnet || isHoodiOrFork ? "OETHOracleRouter" : "OracleRouter"; - const cOETHOracleRouter = await ethers.getContract(oracleRouterContractName); const cOETHVault = await ethers.getContractAt( "IVault", cOETHVaultProxy.address @@ -1136,24 +1122,15 @@ const deployOETHCore = async () => { await withConfirmation( cOETHVault .connect(sGovernor) - .initialize( - cOETHOracleRouter.address, - cOETHProxy.address, - await getTxOpts() - ) + .initialize(cOETHProxy.address, await getTxOpts()) ); log("Initialized OETHVault"); await withConfirmation( - cOETHVaultProxy.connect(sGovernor).upgradeTo(dOETHVaultCore.address) + cOETHVaultProxy.connect(sGovernor).upgradeTo(dOETHVault.address) ); log("Upgraded VaultCore implementation"); - await withConfirmation( - cOETHVault.connect(sGovernor).setAdminImpl(dOETHVaultAdmin.address) - ); - - log("Initialized VaultAdmin implementation"); // Initialize OETH /* Set the original resolution to 27 decimals. We used to have it set to 18 * decimals at launch and then migrated to 27. Having it set to 27 it will @@ -1178,16 +1155,19 @@ const deployOETHCore = async () => { }; const deployOUSDCore = async () => { - const { governorAddr } = await hre.getNamedAccounts(); + const { governorAddr, deployerAddr } = await hre.getNamedAccounts(); + const assetAddresses = await getAssetAddresses(deployments); log(`Using asset addresses: ${JSON.stringify(assetAddresses, null, 2)}`); // Signers const sGovernor = await ethers.provider.getSigner(governorAddr); + const sDeployer = await ethers.provider.getSigner(deployerAddr); // Proxies await deployWithConfirmation("OUSDProxy"); await deployWithConfirmation("VaultProxy"); + log("Deployed OUSD Token and OUSD Vault proxies"); // Main contracts let dOUSD; @@ -1196,17 +1176,20 @@ const deployOUSDCore = async () => { } else { dOUSD = await deployWithConfirmation("OUSD"); } - const dVault = await deployWithConfirmation("Vault"); - const dVaultCore = await deployWithConfirmation("VaultCore"); - const dVaultAdmin = await deployWithConfirmation("VaultAdmin"); + + // Deploy Vault implementations + const dVaultAdmin = await deployWithConfirmation("OUSDVault", [ + assetAddresses.USDC, + ]); + log("Deployed OUSD Vault implementations (Core, Admin)"); // Get contract instances const cOUSDProxy = await ethers.getContract("OUSDProxy"); const cVaultProxy = await ethers.getContract("VaultProxy"); const cOUSD = await ethers.getContractAt("OUSD", cOUSDProxy.address); - const cOracleRouter = await ethers.getContract("OracleRouter"); const cVault = await ethers.getContractAt("IVault", cVaultProxy.address); + // Initialize OUSD Token Proxy await withConfirmation( cOUSDProxy["initialize(address,address,bytes)"]( dOUSD.address, @@ -1214,34 +1197,25 @@ const deployOUSDCore = async () => { [] ) ); - log("Initialized OUSDProxy"); + log("Initialized OUSD Token Proxy"); - // Need to call the initializer on the Vault then upgraded it to the actual - // VaultCore implementation + // Initialize OUSD Vault Proxy with Vault Core implementation + // prettier-ignore await withConfirmation( - cVaultProxy["initialize(address,address,bytes)"]( - dVault.address, + cVaultProxy.connect(sDeployer)["initialize(address,address,bytes)"]( + dVaultAdmin.address, governorAddr, - [] + [], + await getTxOpts() ) ); + log("Initialized OUSD Vault Proxy"); + // Initialize OUSD Vault Core await withConfirmation( - cVault - .connect(sGovernor) - .initialize(cOracleRouter.address, cOUSDProxy.address) - ); - log("Initialized Vault"); - - await withConfirmation( - cVaultProxy.connect(sGovernor).upgradeTo(dVaultCore.address) + cVault.connect(sGovernor).initialize(cOUSDProxy.address) ); - log("Upgraded VaultCore implementation"); - - await withConfirmation( - cVault.connect(sGovernor).setAdminImpl(dVaultAdmin.address) - ); - log("Initialized VaultAdmin implementation"); + log("Initialized OUSD Vault Core"); // Initialize OUSD /* Set the original resolution to 27 decimals. We used to have it set to 18 @@ -1263,7 +1237,7 @@ const deployOUSDCore = async () => { await withConfirmation( cOUSD.connect(sGovernor).initialize(cVaultProxy.address, resolution) ); - log("Initialized OUSD"); + log("Initialized OUSD Token"); await withConfirmation( cVault @@ -1497,17 +1471,11 @@ const deployWOeth = async () => { }; const deployOETHSwapper = async () => { - const { deployerAddr, governorAddr } = await getNamedAccounts(); + const { deployerAddr } = await getNamedAccounts(); const sDeployer = await ethers.provider.getSigner(deployerAddr); - const sGovernor = await ethers.provider.getSigner(governorAddr); const assetAddresses = await getAssetAddresses(deployments); - const vaultProxy = await ethers.getContract("OETHVaultProxy"); - const vault = await ethers.getContractAt("IVault", vaultProxy.address); - - const mockSwapper = await ethers.getContract("MockSwapper"); - await deployWithConfirmation("Swapper1InchV5"); const cSwapper = await ethers.getContract("Swapper1InchV5"); @@ -1519,27 +1487,13 @@ const deployOETHSwapper = async () => { assetAddresses.WETH, assetAddresses.frxETH, ]); - - await vault.connect(sGovernor).setSwapper(mockSwapper.address); - await vault.connect(sGovernor).setSwapAllowedUndervalue(100); - - await vault.connect(sGovernor).setOracleSlippage(assetAddresses.RETH, 200); - await vault.connect(sGovernor).setOracleSlippage(assetAddresses.stETH, 70); - await vault.connect(sGovernor).setOracleSlippage(assetAddresses.WETH, 20); - await vault.connect(sGovernor).setOracleSlippage(assetAddresses.frxETH, 20); }; const deployOUSDSwapper = async () => { - const { deployerAddr, governorAddr } = await getNamedAccounts(); + const { deployerAddr } = await getNamedAccounts(); const sDeployer = await ethers.provider.getSigner(deployerAddr); - const sGovernor = await ethers.provider.getSigner(governorAddr); const assetAddresses = await getAssetAddresses(deployments); - - const vaultProxy = await ethers.getContract("VaultProxy"); - const vault = await ethers.getContractAt("IVault", vaultProxy.address); - - const mockSwapper = await ethers.getContract("MockSwapper"); // Assumes deployOETHSwapper has already been run const cSwapper = await ethers.getContract("Swapper1InchV5"); @@ -1550,13 +1504,6 @@ const deployOUSDSwapper = async () => { assetAddresses.USDC, assetAddresses.USDT, ]); - - await vault.connect(sGovernor).setSwapper(mockSwapper.address); - await vault.connect(sGovernor).setSwapAllowedUndervalue(100); - - await vault.connect(sGovernor).setOracleSlippage(assetAddresses.USDS, 50); - await vault.connect(sGovernor).setOracleSlippage(assetAddresses.USDC, 50); - await vault.connect(sGovernor).setOracleSlippage(assetAddresses.USDT, 50); }; const deployBaseAerodromeAMOStrategyImplementation = async () => { diff --git a/contracts/deploy/mainnet/000_mock.js b/contracts/deploy/mainnet/000_mock.js index 7a5f2d9976..166253a416 100644 --- a/contracts/deploy/mainnet/000_mock.js +++ b/contracts/deploy/mainnet/000_mock.js @@ -134,6 +134,7 @@ const deployMocks = async ({ getNamedAccounts, deployments }) => { // Deploy a mock Vault with additional functions for tests await deploy("MockVault", { + args: [(await ethers.getContract("MockUSDC")).address], from: governorAddr, }); @@ -448,6 +449,7 @@ const deployMocks = async ({ getNamedAccounts, deployments }) => { const mockBeaconRoots = await ethers.getContract("MockBeaconRoots"); await replaceContractAt(addresses.mainnet.beaconRoots, mockBeaconRoots); + await deploy("MockStrategy", { from: deployerAddr }); await deploy("CCTPMessageTransmitterMock", { from: deployerAddr, args: [usdc.address], diff --git a/contracts/deploy/mainnet/001_core.js b/contracts/deploy/mainnet/001_core.js index 37cc3fc746..7d6dc08841 100644 --- a/contracts/deploy/mainnet/001_core.js +++ b/contracts/deploy/mainnet/001_core.js @@ -42,7 +42,7 @@ const main = async () => { oethDripper ); await configureVault(); - await configureOETHVault(false); + await configureOETHVault(); await configureStrategies(harvesterProxy, oethHarvesterProxy); await deployBuyback(); await deployUniswapV3Pool(); diff --git a/contracts/deploy/mainnet/167_ousd_vault_upgrade.js b/contracts/deploy/mainnet/167_ousd_vault_upgrade.js new file mode 100644 index 0000000000..62c39a76fe --- /dev/null +++ b/contracts/deploy/mainnet/167_ousd_vault_upgrade.js @@ -0,0 +1,58 @@ +const addresses = require("../../utils/addresses"); +const { deploymentWithGovernanceProposal } = require("../../utils/deploy"); + +module.exports = deploymentWithGovernanceProposal( + { + deployName: "167_ousd_vault_upgrade", + forceDeploy: false, + //forceSkip: true, + reduceQueueTime: true, + deployerIsProposer: false, + proposalId: "", + }, + async ({ deployWithConfirmation }) => { + // Deployer Actions + // ---------------- + + // 1. Deploy new OUSD Vault Core and Admin implementations + const dVault = await deployWithConfirmation("OUSDVault", [ + addresses.mainnet.USDC, + ]); + + // 2. Connect to the OUSD Vault as its governor via the proxy + const cVaultProxy = await ethers.getContract("VaultProxy"); + const cVault = await ethers.getContractAt("IVault", cVaultProxy.address); + + // 3. Connect to the Morpho OUSD v2 Strategy Proxy + const cOUSDAMO = await ethers.getContract("OUSDCurveAMOProxy"); + const cMorphoStrategy = await ethers.getContract( + "OUSDMorphoV2StrategyProxy" + ); + + // Governance Actions + // ---------------- + return { + name: "Upgrade OUSD Vault to new Core and Admin implementations", + actions: [ + // 1. Upgrade the OUSD Vault proxy to the new core vault implementation + { + contract: cVaultProxy, + signature: "upgradeTo(address)", + args: [dVault.address], + }, + // 2. Add OUSD/USDC AMO to mint whitelist + { + contract: cVault, + signature: "addStrategyToMintWhitelist(address)", + args: [cOUSDAMO.address], + }, + // 3. Set Morpho OUSD v2 Strategy as default strategy + { + contract: cVault, + signature: "setDefaultStrategy(address)", + args: [cMorphoStrategy.address], + }, + ], + }; + } +); diff --git a/contracts/deploy/mainnet/168_oeth_vault_upgrade.js b/contracts/deploy/mainnet/168_oeth_vault_upgrade.js new file mode 100644 index 0000000000..b6c3a18ef0 --- /dev/null +++ b/contracts/deploy/mainnet/168_oeth_vault_upgrade.js @@ -0,0 +1,54 @@ +const addresses = require("../../utils/addresses"); +const { deploymentWithGovernanceProposal } = require("../../utils/deploy"); + +module.exports = deploymentWithGovernanceProposal( + { + deployName: "168_oeth_vault_upgrade", + forceDeploy: false, + //forceSkip: true, + reduceQueueTime: true, + deployerIsProposer: false, + proposalId: "", + }, + async ({ deployWithConfirmation }) => { + // Deployer Actions + // ---------------- + + // 1. Deploy new OETH Vault Core and Admin implementations + const dVaultAdmin = await deployWithConfirmation( + "OETHVault", + [addresses.mainnet.WETH], + undefined, + true + ); + + // 2. Connect to the OETH Vault as its governor via the proxy + const cVaultProxy = await ethers.getContract("OETHVaultProxy"); + const cVault = await ethers.getContractAt("IVault", cVaultProxy.address); + + // 3. Connect to the Compounding Staking Strategy Proxy to set it as default strategy + const defaultStrategy = await ethers.getContract( + "CompoundingStakingSSVStrategyProxy" + ); + + // Governance Actions + // ---------------- + return { + name: "Upgrade OETH Vault to new Core and Admin implementations", + actions: [ + // 1. Upgrade the OETH Vault proxy to the new core vault implementation + { + contract: cVaultProxy, + signature: "upgradeTo(address)", + args: [dVaultAdmin.address], + }, + // 2. Set OETH/WETH AMO as default strategy + { + contract: cVault, + signature: "setDefaultStrategy(address)", + args: [defaultStrategy.address], + }, + ], + }; + } +); diff --git a/contracts/deploy/plume/002_core.js b/contracts/deploy/plume/002_core.js index ba6eae2b9a..c090140a6c 100644 --- a/contracts/deploy/plume/002_core.js +++ b/contracts/deploy/plume/002_core.js @@ -53,11 +53,10 @@ module.exports = deployOnPlume( const dOETHpVaultCore = await deployWithConfirmation("OETHBaseVaultCore", [ addresses.plume.WETH, ]); - const dOETHpVaultAdmin = await deployWithConfirmation( - "OETHBaseVaultAdmin", - [addresses.plume.WETH] - ); - console.log("OETHBaseVaultAdmin deployed at", dOETHpVaultAdmin.address); + const dOETHpVaultAdmin = await deployWithConfirmation("OETHBaseVault", [ + addresses.plume.WETH, + ]); + console.log("OETHBaseVault deployed at", dOETHpVaultAdmin.address); // Get contract instances const cOETHp = await ethers.getContractAt("OETHPlume", cOETHpProxy.address); const cwOETHp = await ethers.getContractAt( diff --git a/contracts/deploy/sonic/000_mock.js b/contracts/deploy/sonic/000_mock.js index 72f53dde49..ea325b8a41 100644 --- a/contracts/deploy/sonic/000_mock.js +++ b/contracts/deploy/sonic/000_mock.js @@ -46,13 +46,8 @@ const deployCore = async () => { const dWOSonic = await deployWithConfirmation("WOSonic", [ cOSonicProxy.address, // Base token ]); - const dOSonicVaultCore = await deployWithConfirmation("OSonicVaultCore", [ - cWS.address, - ]); - const dOSonicVaultAdmin = await deployWithConfirmation("OSonicVaultAdmin", [ - cWS.address, - ]); + const dOSonicVault = await deployWithConfirmation("OSVault", [cWS.address]); // Get contract instances const cOSonic = await ethers.getContractAt("OSonic", cOSonicProxy.address); @@ -61,7 +56,6 @@ const deployCore = async () => { "IVault", cOSonicVaultProxy.address ); - const cOracleRouter = await ethers.getContract("MockOracleRouter"); // Init OSonic const resolution = ethers.utils.parseUnits("1", 27); @@ -82,16 +76,15 @@ const deployCore = async () => { // Init OSonicVault const initDataOSonicVault = cOSonicVault.interface.encodeFunctionData( - "initialize(address,address)", + "initialize(address)", [ - cOracleRouter.address, // OracleRouter cOSonicProxy.address, // OSonic ] ); // prettier-ignore await cOSonicVaultProxy .connect(sDeployer)["initialize(address,address,bytes)"]( - dOSonicVaultCore.address, + dOSonicVault.address, governorAddr, initDataOSonicVault ); @@ -109,12 +102,8 @@ const deployCore = async () => { initDataWOSonic ) - await cOSonicVaultProxy - .connect(sGovernor) - .upgradeTo(dOSonicVaultCore.address); - await cOSonicVault.connect(sGovernor).setAdminImpl(dOSonicVaultAdmin.address); + await cOSonicVaultProxy.connect(sGovernor).upgradeTo(dOSonicVault.address); - await cOSonicVault.connect(sGovernor).supportAsset(cWS.address, 0); await cOSonicVault.connect(sGovernor).unpauseCapital(); // Set withdrawal claim delay to 1 day await cOSonicVault.connect(sGovernor).setWithdrawalClaimDelay(86400); @@ -163,13 +152,13 @@ const deployStakingStrategy = async () => { cSonicStakingStrategy.interface.encodeFunctionData("initialize()", []); // prettier-ignore await withConfirmation( - cSonicStakingStrategyProxy - .connect(sDeployer)["initialize(address,address,bytes)"]( - dSonicStakingStrategy.address, - governorAddr, - initSonicStakingStrategy - ) - ); + cSonicStakingStrategyProxy + .connect(sDeployer)["initialize(address,address,bytes)"]( + dSonicStakingStrategy.address, + governorAddr, + initSonicStakingStrategy + ) + ); }; const deployDripper = async () => { @@ -191,12 +180,12 @@ const deployDripper = async () => { // prettier-ignore await withConfirmation( cOSonicDripperProxy - .connect(sDeployer)["initialize(address,address,bytes)"]( - dFixedRateDripper.address, - governorAddr, - "0x" - ) - ); + .connect(sDeployer)["initialize(address,address,bytes)"]( + dFixedRateDripper.address, + governorAddr, + "0x" + ) + ); }; const main = async () => { diff --git a/contracts/deploy/sonic/001_vault_and_token.js b/contracts/deploy/sonic/001_vault_and_token.js index 0ecb106f98..73b4ec9c81 100644 --- a/contracts/deploy/sonic/001_vault_and_token.js +++ b/contracts/deploy/sonic/001_vault_and_token.js @@ -49,10 +49,10 @@ module.exports = deployOnSonic( ]); console.log(`Deployed Vault Core to ${dOSonicVaultCore.address}`); - const dOSonicVaultAdmin = await deployWithConfirmation("OSonicVaultAdmin", [ + const dOSonicVault = await deployWithConfirmation("OSonicVault", [ cWS.address, ]); - console.log(`Deployed Vault Admin to ${dOSonicVaultAdmin.address}`); + console.log(`Deployed Vault Admin to ${dOSonicVault.address}`); // Get contract instances const cOSonic = await ethers.getContractAt("OSonic", cOSonicProxy.address); @@ -77,11 +77,11 @@ module.exports = deployOnSonic( // prettier-ignore await cOSonicProxy - .connect(sDeployer)["initialize(address,address,bytes)"]( - dOSonic.address, - addresses.sonic.timelock, - initDataOSonic - ); + .connect(sDeployer)["initialize(address,address,bytes)"]( + dOSonic.address, + addresses.sonic.timelock, + initDataOSonic + ); console.log("Initialized Origin S"); // Init OSonicVault @@ -94,11 +94,11 @@ module.exports = deployOnSonic( ); // prettier-ignore await cOSonicVaultProxy - .connect(sDeployer)["initialize(address,address,bytes)"]( - dOSonicVaultCore.address, - addresses.sonic.timelock, - initDataOSonicVault - ); + .connect(sDeployer)["initialize(address,address,bytes)"]( + dOSonicVaultCore.address, + addresses.sonic.timelock, + initDataOSonicVault + ); console.log("Initialized Origin S Vault"); // Init WOSonic @@ -108,11 +108,11 @@ module.exports = deployOnSonic( ); // prettier-ignore await cWOSonicProxy - .connect(sDeployer)["initialize(address,address,bytes)"]( - dWOSonic.address, - addresses.sonic.timelock, - initDataWOSonic - ) + .connect(sDeployer)["initialize(address,address,bytes)"]( + dWOSonic.address, + addresses.sonic.timelock, + initDataWOSonic + ) console.log("Initialized Wrapper Origin S"); // Deploy the Dripper @@ -133,7 +133,7 @@ module.exports = deployOnSonic( // Init the Dripper proxy // prettier-ignore await withConfirmation( - cOSonicDripperProxy + cOSonicDripperProxy .connect(sDeployer)["initialize(address,address,bytes)"]( dFixedRateDripper.address, addresses.sonic.timelock, @@ -170,7 +170,7 @@ module.exports = deployOnSonic( { contract: cOSonicVault, signature: "setAdminImpl(address)", - args: [dOSonicVaultAdmin.address], + args: [dOSonicVault.address], }, // 3. Support wrapped S { diff --git a/contracts/deploy/sonic/026_vault_upgrade.js b/contracts/deploy/sonic/026_vault_upgrade.js new file mode 100644 index 0000000000..30725e2e3a --- /dev/null +++ b/contracts/deploy/sonic/026_vault_upgrade.js @@ -0,0 +1,56 @@ +const { deployOnSonic } = require("../../utils/deploy-l2"); +const { deployWithConfirmation } = require("../../utils/deploy"); +const addresses = require("../../utils/addresses"); + +module.exports = deployOnSonic( + { + deployName: "026_vault_upgrade", + //proposalId: "", + }, + async ({ ethers }) => { + // 1. Deploy new OSonicVault implementations + const dOSonicVault = await deployWithConfirmation( + "OSVault", + [addresses.sonic.wS], + "OSVault", + true + ); + + // 2. Connect to the OSonic Vault as its governor via the proxy + const cOSonicVaultProxy = await ethers.getContract("OSonicVaultProxy"); + const cOSonicVault = await ethers.getContractAt( + "IVault", + cOSonicVaultProxy.address + ); + + // 3. Connect to Sonic Staking Strategy + const sonicStakingStrategy = await ethers.getContract( + "SonicStakingStrategyProxy" + ); + console.log( + "Sonic Staking Strategy Address:", + sonicStakingStrategy.address + ); + + // ---------------- + // Governance Actions + // ---------------- + return { + name: "Upgrade OSonicVault to new single Vault implementations", + actions: [ + // 1. Upgrade OSonicVaultProxy to new implementation + { + contract: cOSonicVaultProxy, + signature: "upgradeTo(address)", + args: [dOSonicVault.address], + }, + // 2. Set Sonic Staking Strategy as default strategy + { + contract: cOSonicVault, + signature: "setDefaultStrategy(address)", + args: [sonicStakingStrategy.address], + }, + ], + }; + } +); diff --git a/contracts/docs/OETHBaseVaultAdminSquashed.svg b/contracts/docs/OETHBaseVaultAdminSquashed.svg deleted file mode 100644 index a4f23c6a14..0000000000 --- a/contracts/docs/OETHBaseVaultAdminSquashed.svg +++ /dev/null @@ -1,167 +0,0 @@ - - - - - - -UmlClassDiagram - - - -282 - -OETHBaseVaultAdmin -../contracts/vault/OETHBaseVaultAdmin.sol - -Private: -   initialized: bool <<Initializable>> -   initializing: bool <<Initializable>> -   ______gap: uint256[50] <<Initializable>> -   governorPosition: bytes32 <<Governable>> -   pendingGovernorPosition: bytes32 <<Governable>> -   reentryStatusPosition: bytes32 <<Governable>> -   _deprecated_rebaseHooksAddr: address <<VaultStorage>> -   _deprecated_uniswapAddr: address <<VaultStorage>> -   _deprecated_swapTokens: address[] <<VaultStorage>> -   __gap: uint256[44] <<VaultStorage>> -Internal: -   assets: mapping(address=>Asset) <<VaultStorage>> -   allAssets: address[] <<VaultStorage>> -   strategies: mapping(address=>Strategy) <<VaultStorage>> -   allStrategies: address[] <<VaultStorage>> -   oUSD: OUSD <<VaultStorage>> -   swapConfig: SwapConfig <<VaultStorage>> -Public: -   _NOT_ENTERED: uint256 <<Governable>> -   _ENTERED: uint256 <<Governable>> -   priceProvider: address <<VaultStorage>> -   rebasePaused: bool <<VaultStorage>> -   capitalPaused: bool <<VaultStorage>> -   redeemFeeBps: uint256 <<VaultStorage>> -   vaultBuffer: uint256 <<VaultStorage>> -   autoAllocateThreshold: uint256 <<VaultStorage>> -   rebaseThreshold: uint256 <<VaultStorage>> -   adminImplPosition: bytes32 <<VaultStorage>> -   strategistAddr: address <<VaultStorage>> -   assetDefaultStrategies: mapping(address=>address) <<VaultStorage>> -   maxSupplyDiff: uint256 <<VaultStorage>> -   trusteeAddress: address <<VaultStorage>> -   trusteeFeeBps: uint256 <<VaultStorage>> -   MINT_MINIMUM_UNIT_PRICE: uint256 <<VaultStorage>> -   ousdMetaStrategy: address <<VaultStorage>> -   netOusdMintedForStrategy: int256 <<VaultStorage>> -   netOusdMintForStrategyThreshold: uint256 <<VaultStorage>> -   MIN_UNIT_PRICE_DRIFT: uint256 <<VaultStorage>> -   MAX_UNIT_PRICE_DRIFT: uint256 <<VaultStorage>> -   isMintWhitelistedStrategy: mapping(address=>bool) <<VaultStorage>> -   dripper: address <<VaultStorage>> -   withdrawalQueueMetadata: WithdrawalQueueMetadata <<VaultStorage>> -   withdrawalRequests: mapping(uint256=>WithdrawalRequest) <<VaultStorage>> -   withdrawalClaimDelay: uint256 <<VaultStorage>> -   weth: address <<OETHVaultAdmin>> - -Internal: -    _governor(): (governorOut: address) <<Governable>> -    _pendingGovernor(): (pendingGovernor: address) <<Governable>> -    _setGovernor(newGovernor: address) <<Governable>> -    _setPendingGovernor(newGovernor: address) <<Governable>> -    _changeGovernor(_newGovernor: address) <<Governable>> -    _swapCollateral(address, address, uint256, uint256, bytes): uint256 <<OETHVaultAdmin>> -    _depositToStrategy(_strategyToAddress: address, _assets: address[], _amounts: uint256[]) <<OETHVaultAdmin>> -    _withdrawFromStrategy(_recipient: address, _strategyFromAddress: address, _assets: address[], _amounts: uint256[]) <<OETHVaultAdmin>> -    _withdrawAllFromStrategy(_strategyAddr: address) <<OETHVaultAdmin>> -    _withdrawAllFromStrategies() <<OETHVaultAdmin>> -    _cacheDecimals(token: address) <<VaultAdmin>> -    _wethAvailable(): (wethAvailable: uint256) <<OETHVaultAdmin>> -External: -    transferGovernance(_newGovernor: address) <<onlyGovernor>> <<Governable>> -    claimGovernance() <<Governable>> -    setAdminImpl(newImpl: address) <<onlyGovernor>> <<VaultStorage>> -    setPriceProvider(_priceProvider: address) <<onlyGovernor>> <<VaultAdmin>> -    setRedeemFeeBps(_redeemFeeBps: uint256) <<onlyGovernor>> <<VaultAdmin>> -    setVaultBuffer(_vaultBuffer: uint256) <<onlyGovernorOrStrategist>> <<VaultAdmin>> -    setAutoAllocateThreshold(_threshold: uint256) <<onlyGovernor>> <<VaultAdmin>> -    setRebaseThreshold(_threshold: uint256) <<onlyGovernor>> <<VaultAdmin>> -    setStrategistAddr(_address: address) <<onlyGovernor>> <<VaultAdmin>> -    setAssetDefaultStrategy(_asset: address, _strategy: address) <<onlyGovernorOrStrategist>> <<VaultAdmin>> -    setNetOusdMintForStrategyThreshold(_threshold: uint256) <<onlyGovernor>> <<VaultAdmin>> -    setDripper(_dripper: address) <<onlyGovernor>> <<VaultAdmin>> -    setWithdrawalClaimDelay(_delay: uint256) <<onlyGovernor>> <<VaultAdmin>> -    swapCollateral(_fromAsset: address, _toAsset: address, _fromAssetAmount: uint256, _minToAssetAmount: uint256, _data: bytes): (toAssetAmount: uint256) <<nonReentrant, onlyGovernorOrStrategist>> <<VaultAdmin>> -    setSwapper(_swapperAddr: address) <<onlyGovernor>> <<VaultAdmin>> -    swapper(): (swapper_: address) <<VaultAdmin>> -    setSwapAllowedUndervalue(_basis: uint16) <<onlyGovernor>> <<VaultAdmin>> -    allowedSwapUndervalue(): (value: uint256) <<VaultAdmin>> -    setOracleSlippage(_asset: address, _allowedOracleSlippageBps: uint16) <<onlyGovernor>> <<VaultAdmin>> -    supportAsset(_asset: address, _unitConversion: uint8) <<onlyGovernor>> <<VaultAdmin>> -    removeAsset(_asset: address) <<onlyGovernor>> <<VaultAdmin>> -    cacheDecimals(_asset: address) <<onlyGovernor>> <<VaultAdmin>> -    approveStrategy(_addr: address) <<onlyGovernor>> <<VaultAdmin>> -    removeStrategy(_addr: address) <<onlyGovernor>> <<VaultAdmin>> -    depositToStrategy(_strategyToAddress: address, _assets: address[], _amounts: uint256[]) <<onlyGovernorOrStrategist, nonReentrant>> <<VaultAdmin>> -    withdrawFromStrategy(_strategyFromAddress: address, _assets: address[], _amounts: uint256[]) <<onlyGovernorOrStrategist, nonReentrant>> <<VaultAdmin>> -    setMaxSupplyDiff(_maxSupplyDiff: uint256) <<onlyGovernor>> <<VaultAdmin>> -    setTrusteeAddress(_address: address) <<onlyGovernor>> <<VaultAdmin>> -    setTrusteeFeeBps(_basis: uint256) <<onlyGovernor>> <<VaultAdmin>> -    setOusdMetaStrategy(_ousdMetaStrategy: address) <<onlyGovernor>> <<VaultAdmin>> -    pauseRebase() <<onlyGovernorOrStrategist>> <<VaultAdmin>> -    unpauseRebase() <<onlyGovernorOrStrategist>> <<VaultAdmin>> -    pauseCapital() <<onlyGovernorOrStrategist>> <<VaultAdmin>> -    unpauseCapital() <<onlyGovernorOrStrategist>> <<VaultAdmin>> -    transferToken(_asset: address, _amount: uint256) <<onlyGovernor>> <<VaultAdmin>> -    withdrawAllFromStrategy(_strategyAddr: address) <<onlyGovernorOrStrategist>> <<VaultAdmin>> -    withdrawAllFromStrategies() <<onlyGovernorOrStrategist>> <<VaultAdmin>> -    addStrategyToMintWhitelist(strategyAddr: address) <<onlyGovernor>> <<OETHVaultAdmin>> -    removeStrategyFromMintWhitelist(strategyAddr: address) <<onlyGovernor>> <<OETHVaultAdmin>> -Public: -    <<event>> PendingGovernorshipTransfer(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> GovernorshipTransferred(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> AssetSupported(_asset: address) <<VaultStorage>> -    <<event>> AssetRemoved(_asset: address) <<VaultStorage>> -    <<event>> AssetDefaultStrategyUpdated(_asset: address, _strategy: address) <<VaultStorage>> -    <<event>> AssetAllocated(_asset: address, _strategy: address, _amount: uint256) <<VaultStorage>> -    <<event>> StrategyApproved(_addr: address) <<VaultStorage>> -    <<event>> StrategyRemoved(_addr: address) <<VaultStorage>> -    <<event>> Mint(_addr: address, _value: uint256) <<VaultStorage>> -    <<event>> Redeem(_addr: address, _value: uint256) <<VaultStorage>> -    <<event>> CapitalPaused() <<VaultStorage>> -    <<event>> CapitalUnpaused() <<VaultStorage>> -    <<event>> RebasePaused() <<VaultStorage>> -    <<event>> RebaseUnpaused() <<VaultStorage>> -    <<event>> VaultBufferUpdated(_vaultBuffer: uint256) <<VaultStorage>> -    <<event>> OusdMetaStrategyUpdated(_ousdMetaStrategy: address) <<VaultStorage>> -    <<event>> RedeemFeeUpdated(_redeemFeeBps: uint256) <<VaultStorage>> -    <<event>> PriceProviderUpdated(_priceProvider: address) <<VaultStorage>> -    <<event>> AllocateThresholdUpdated(_threshold: uint256) <<VaultStorage>> -    <<event>> RebaseThresholdUpdated(_threshold: uint256) <<VaultStorage>> -    <<event>> StrategistUpdated(_address: address) <<VaultStorage>> -    <<event>> MaxSupplyDiffChanged(maxSupplyDiff: uint256) <<VaultStorage>> -    <<event>> YieldDistribution(_to: address, _yield: uint256, _fee: uint256) <<VaultStorage>> -    <<event>> TrusteeFeeBpsChanged(_basis: uint256) <<VaultStorage>> -    <<event>> TrusteeAddressChanged(_address: address) <<VaultStorage>> -    <<event>> NetOusdMintForStrategyThresholdChanged(_threshold: uint256) <<VaultStorage>> -    <<event>> SwapperChanged(_address: address) <<VaultStorage>> -    <<event>> SwapAllowedUndervalueChanged(_basis: uint256) <<VaultStorage>> -    <<event>> SwapSlippageChanged(_asset: address, _basis: uint256) <<VaultStorage>> -    <<event>> Swapped(_fromAsset: address, _toAsset: address, _fromAssetAmount: uint256, _toAssetAmount: uint256) <<VaultStorage>> -    <<event>> StrategyAddedToMintWhitelist(strategy: address) <<VaultStorage>> -    <<event>> StrategyRemovedFromMintWhitelist(strategy: address) <<VaultStorage>> -    <<event>> DripperChanged(_dripper: address) <<VaultStorage>> -    <<event>> WithdrawalRequested(_withdrawer: address, _requestId: uint256, _amount: uint256, _queued: uint256) <<VaultStorage>> -    <<event>> WithdrawalClaimed(_withdrawer: address, _requestId: uint256, _amount: uint256) <<VaultStorage>> -    <<event>> WithdrawalClaimable(_claimable: uint256, _newClaimable: uint256) <<VaultStorage>> -    <<event>> WithdrawalClaimDelayUpdated(_newDelay: uint256) <<VaultStorage>> -    <<modifier>> initializer() <<Initializable>> -    <<modifier>> onlyGovernor() <<Governable>> -    <<modifier>> nonReentrant() <<Governable>> -    <<modifier>> onlyGovernorOrStrategist() <<VaultAdmin>> -    constructor() <<Governable>> -    governor(): address <<Governable>> -    isGovernor(): bool <<Governable>> -    constructor(_weth: address) <<OETHBaseVaultAdmin>> - - - diff --git a/contracts/docs/OETHBaseVaultCoreSquashed.svg b/contracts/docs/OETHBaseVaultCoreSquashed.svg deleted file mode 100644 index 194fb658f8..0000000000 --- a/contracts/docs/OETHBaseVaultCoreSquashed.svg +++ /dev/null @@ -1,173 +0,0 @@ - - - - - - -UmlClassDiagram - - - -283 - -OETHBaseVaultCore -../contracts/vault/OETHBaseVaultCore.sol - -Private: -   initialized: bool <<Initializable>> -   initializing: bool <<Initializable>> -   ______gap: uint256[50] <<Initializable>> -   governorPosition: bytes32 <<Governable>> -   pendingGovernorPosition: bytes32 <<Governable>> -   reentryStatusPosition: bytes32 <<Governable>> -   _deprecated_rebaseHooksAddr: address <<VaultStorage>> -   _deprecated_uniswapAddr: address <<VaultStorage>> -   _deprecated_swapTokens: address[] <<VaultStorage>> -   __gap: uint256[44] <<VaultStorage>> -   __gap: uint256[50] <<OETHVaultCore>> -Internal: -   assets: mapping(address=>Asset) <<VaultStorage>> -   allAssets: address[] <<VaultStorage>> -   strategies: mapping(address=>Strategy) <<VaultStorage>> -   allStrategies: address[] <<VaultStorage>> -   oUSD: OUSD <<VaultStorage>> -   swapConfig: SwapConfig <<VaultStorage>> -   MAX_INT: uint256 <<VaultCore>> -Public: -   _NOT_ENTERED: uint256 <<Governable>> -   _ENTERED: uint256 <<Governable>> -   priceProvider: address <<VaultStorage>> -   rebasePaused: bool <<VaultStorage>> -   capitalPaused: bool <<VaultStorage>> -   redeemFeeBps: uint256 <<VaultStorage>> -   vaultBuffer: uint256 <<VaultStorage>> -   autoAllocateThreshold: uint256 <<VaultStorage>> -   rebaseThreshold: uint256 <<VaultStorage>> -   adminImplPosition: bytes32 <<VaultStorage>> -   strategistAddr: address <<VaultStorage>> -   assetDefaultStrategies: mapping(address=>address) <<VaultStorage>> -   maxSupplyDiff: uint256 <<VaultStorage>> -   trusteeAddress: address <<VaultStorage>> -   trusteeFeeBps: uint256 <<VaultStorage>> -   MINT_MINIMUM_UNIT_PRICE: uint256 <<VaultStorage>> -   ousdMetaStrategy: address <<VaultStorage>> -   netOusdMintedForStrategy: int256 <<VaultStorage>> -   netOusdMintForStrategyThreshold: uint256 <<VaultStorage>> -   MIN_UNIT_PRICE_DRIFT: uint256 <<VaultStorage>> -   MAX_UNIT_PRICE_DRIFT: uint256 <<VaultStorage>> -   isMintWhitelistedStrategy: mapping(address=>bool) <<VaultStorage>> -   dripper: address <<VaultStorage>> -   withdrawalQueueMetadata: WithdrawalQueueMetadata <<VaultStorage>> -   withdrawalRequests: mapping(uint256=>WithdrawalRequest) <<VaultStorage>> -   withdrawalClaimDelay: uint256 <<VaultStorage>> -   weth: address <<OETHVaultCore>> -   wethAssetIndex: uint256 <<OETHVaultCore>> - -Private: -    abs(x: int256): uint256 <<VaultCore>> -Internal: -    _governor(): (governorOut: address) <<Governable>> -    _pendingGovernor(): (pendingGovernor: address) <<Governable>> -    _setGovernor(newGovernor: address) <<Governable>> -    _setPendingGovernor(newGovernor: address) <<Governable>> -    _changeGovernor(_newGovernor: address) <<Governable>> -    _mint(_asset: address, _amount: uint256, _minimumOusdAmount: uint256) <<OETHVaultCore>> -    _redeem(_amount: uint256, _minimumUnitAmount: uint256) <<OETHBaseVaultCore>> -    _postRedeem(_amount: uint256) <<VaultCore>> -    _allocate() <<OETHVaultCore>> -    _rebase(): uint256 <<whenNotRebasePaused>> <<VaultCore>> -    _totalValue(): (value: uint256) <<OETHVaultCore>> -    _totalValueInVault(): (value: uint256) <<OETHVaultCore>> -    _totalValueInStrategies(): (value: uint256) <<VaultCore>> -    _totalValueInStrategy(_strategyAddr: address): (value: uint256) <<VaultCore>> -    _checkBalance(_asset: address): (balance: uint256) <<OETHVaultCore>> -    _calculateRedeemOutputs(_amount: uint256): (outputs: uint256[]) <<OETHVaultCore>> -    _toUnits(_raw: uint256, _asset: address): uint256 <<VaultCore>> -    _toUnitPrice(_asset: address, isMint: bool): (price: uint256) <<VaultCore>> -    _getDecimals(_asset: address): (decimals: uint256) <<VaultCore>> -    _claimWithdrawal(requestId: uint256): (amount: uint256) <<OETHVaultCore>> -    _addWithdrawalQueueLiquidity(): (addedClaimable: uint256) <<OETHVaultCore>> -    _wethAvailable(): (wethAvailable: uint256) <<OETHVaultCore>> -External: -    transferGovernance(_newGovernor: address) <<onlyGovernor>> <<Governable>> -    claimGovernance() <<Governable>> -    setAdminImpl(newImpl: address) <<onlyGovernor>> <<VaultStorage>> -    initialize(_priceProvider: address, _oToken: address) <<onlyGovernor, initializer>> <<VaultInitializer>> -    mint(_asset: address, _amount: uint256, _minimumOusdAmount: uint256) <<whenNotCapitalPaused, nonReentrant>> <<VaultCore>> -    mintForStrategy(amount: uint256) <<whenNotCapitalPaused>> <<OETHVaultCore>> -    redeem(_amount: uint256, _minimumUnitAmount: uint256) <<whenNotCapitalPaused, nonReentrant>> <<VaultCore>> -    burnForStrategy(amount: uint256) <<whenNotCapitalPaused>> <<OETHVaultCore>> -    redeemAll(_minimumUnitAmount: uint256) <<whenNotCapitalPaused, nonReentrant>> <<VaultCore>> -    allocate() <<whenNotCapitalPaused, nonReentrant>> <<OETHVaultCore>> -    rebase() <<nonReentrant>> <<VaultCore>> -    totalValue(): (value: uint256) <<VaultCore>> -    checkBalance(_asset: address): uint256 <<VaultCore>> -    calculateRedeemOutputs(_amount: uint256): uint256[] <<VaultCore>> -    priceUnitMint(asset: address): (price: uint256) <<VaultCore>> -    priceUnitRedeem(asset: address): (price: uint256) <<VaultCore>> -    getAllAssets(): address[] <<VaultCore>> -    getStrategyCount(): uint256 <<VaultCore>> -    getAllStrategies(): address[] <<VaultCore>> -    isSupportedAsset(_asset: address): bool <<VaultCore>> -    null() <<VaultCore>> -    cacheWETHAssetIndex() <<onlyGovernor>> <<OETHVaultCore>> -    requestWithdrawal(_amount: uint256): (requestId: uint256, queued: uint256) <<whenNotCapitalPaused, nonReentrant>> <<OETHVaultCore>> -    claimWithdrawal(_requestId: uint256): (amount: uint256) <<whenNotCapitalPaused, nonReentrant>> <<OETHVaultCore>> -    claimWithdrawals(_requestIds: uint256[]): (amounts: uint256[], totalAmount: uint256) <<whenNotCapitalPaused, nonReentrant>> <<OETHVaultCore>> -    addWithdrawalQueueLiquidity() <<OETHVaultCore>> -Public: -    <<event>> PendingGovernorshipTransfer(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> GovernorshipTransferred(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> AssetSupported(_asset: address) <<VaultStorage>> -    <<event>> AssetRemoved(_asset: address) <<VaultStorage>> -    <<event>> AssetDefaultStrategyUpdated(_asset: address, _strategy: address) <<VaultStorage>> -    <<event>> AssetAllocated(_asset: address, _strategy: address, _amount: uint256) <<VaultStorage>> -    <<event>> StrategyApproved(_addr: address) <<VaultStorage>> -    <<event>> StrategyRemoved(_addr: address) <<VaultStorage>> -    <<event>> Mint(_addr: address, _value: uint256) <<VaultStorage>> -    <<event>> Redeem(_addr: address, _value: uint256) <<VaultStorage>> -    <<event>> CapitalPaused() <<VaultStorage>> -    <<event>> CapitalUnpaused() <<VaultStorage>> -    <<event>> RebasePaused() <<VaultStorage>> -    <<event>> RebaseUnpaused() <<VaultStorage>> -    <<event>> VaultBufferUpdated(_vaultBuffer: uint256) <<VaultStorage>> -    <<event>> OusdMetaStrategyUpdated(_ousdMetaStrategy: address) <<VaultStorage>> -    <<event>> RedeemFeeUpdated(_redeemFeeBps: uint256) <<VaultStorage>> -    <<event>> PriceProviderUpdated(_priceProvider: address) <<VaultStorage>> -    <<event>> AllocateThresholdUpdated(_threshold: uint256) <<VaultStorage>> -    <<event>> RebaseThresholdUpdated(_threshold: uint256) <<VaultStorage>> -    <<event>> StrategistUpdated(_address: address) <<VaultStorage>> -    <<event>> MaxSupplyDiffChanged(maxSupplyDiff: uint256) <<VaultStorage>> -    <<event>> YieldDistribution(_to: address, _yield: uint256, _fee: uint256) <<VaultStorage>> -    <<event>> TrusteeFeeBpsChanged(_basis: uint256) <<VaultStorage>> -    <<event>> TrusteeAddressChanged(_address: address) <<VaultStorage>> -    <<event>> NetOusdMintForStrategyThresholdChanged(_threshold: uint256) <<VaultStorage>> -    <<event>> SwapperChanged(_address: address) <<VaultStorage>> -    <<event>> SwapAllowedUndervalueChanged(_basis: uint256) <<VaultStorage>> -    <<event>> SwapSlippageChanged(_asset: address, _basis: uint256) <<VaultStorage>> -    <<event>> Swapped(_fromAsset: address, _toAsset: address, _fromAssetAmount: uint256, _toAssetAmount: uint256) <<VaultStorage>> -    <<event>> StrategyAddedToMintWhitelist(strategy: address) <<VaultStorage>> -    <<event>> StrategyRemovedFromMintWhitelist(strategy: address) <<VaultStorage>> -    <<event>> DripperChanged(_dripper: address) <<VaultStorage>> -    <<event>> WithdrawalRequested(_withdrawer: address, _requestId: uint256, _amount: uint256, _queued: uint256) <<VaultStorage>> -    <<event>> WithdrawalClaimed(_withdrawer: address, _requestId: uint256, _amount: uint256) <<VaultStorage>> -    <<event>> WithdrawalClaimable(_claimable: uint256, _newClaimable: uint256) <<VaultStorage>> -    <<event>> WithdrawalClaimDelayUpdated(_newDelay: uint256) <<VaultStorage>> -    <<modifier>> initializer() <<Initializable>> -    <<modifier>> onlyGovernor() <<Governable>> -    <<modifier>> nonReentrant() <<Governable>> -    <<modifier>> whenNotRebasePaused() <<VaultCore>> -    <<modifier>> whenNotCapitalPaused() <<VaultCore>> -    <<modifier>> onlyOusdMetaStrategy() <<VaultCore>> -    constructor() <<Governable>> -    governor(): address <<Governable>> -    isGovernor(): bool <<Governable>> -    getAssetCount(): uint256 <<VaultCore>> -    getAssetConfig(_asset: address): (config: Asset) <<VaultCore>> -    constructor(_weth: address) <<OETHBaseVaultCore>> - - - diff --git a/contracts/docs/OETHBaseVaultStorage.svg b/contracts/docs/OETHBaseVaultStorage.svg deleted file mode 100644 index f768dd006b..0000000000 --- a/contracts/docs/OETHBaseVaultStorage.svg +++ /dev/null @@ -1,382 +0,0 @@ - - - - - - -StorageDiagram - - - -9 - -OETHBaseVaultCore <<Contract>> - -slot - -0 - -1-50 - -51 - -52 - -53 - -54 - -55 - -56 - -57 - -58 - -59 - -60 - -61 - -62 - -63 - -64 - -65 - -66 - -67 - -68 - -69 - -70 - -71 - -72 - -73 - -74 - -75-76 - -77 - -78 - -79-122 - -123 - -124-173 - -type: <inherited contract>.variable (bytes) - -unallocated (30) - -bool: Initializable.initializing (1) - -bool: Initializable.initialized (1) - -uint256[50]: Initializable.______gap (1600) - -mapping(address=>Asset): VaultStorage.assets (32) - -address[]: VaultStorage.allAssets (32) - -mapping(address=>Strategy): VaultStorage.strategies (32) - -address[]: VaultStorage.allStrategies (32) - -unallocated (10) - -bool: VaultStorage.capitalPaused (1) - -bool: VaultStorage.rebasePaused (1) - -address: VaultStorage.priceProvider (20) - -uint256: VaultStorage.redeemFeeBps (32) - -uint256: VaultStorage.vaultBuffer (32) - -uint256: VaultStorage.autoAllocateThreshold (32) - -uint256: VaultStorage.rebaseThreshold (32) - -unallocated (12) - -OUSD: VaultStorage.oUSD (20) - -unallocated (12) - -address: VaultStorage._deprecated_rebaseHooksAddr (20) - -unallocated (12) - -address: VaultStorage._deprecated_uniswapAddr (20) - -unallocated (12) - -address: VaultStorage.strategistAddr (20) - -mapping(address=>address): VaultStorage.assetDefaultStrategies (32) - -uint256: VaultStorage.maxSupplyDiff (32) - -unallocated (12) - -address: VaultStorage.trusteeAddress (20) - -uint256: VaultStorage.trusteeFeeBps (32) - -address[]: VaultStorage._deprecated_swapTokens (32) - -unallocated (12) - -address: VaultStorage.ousdMetaStrategy (20) - -int256: VaultStorage.netOusdMintedForStrategy (32) - -uint256: VaultStorage.netOusdMintForStrategyThreshold (32) - -SwapConfig: VaultStorage.swapConfig (32) - -mapping(address=>bool): VaultStorage.isMintWhitelistedStrategy (32) - -unallocated (12) - -address: VaultStorage.dripper (20) - -WithdrawalQueueMetadata: VaultStorage.withdrawalQueueMetadata (64) - -mapping(uint256=>WithdrawalRequest): VaultStorage.withdrawalRequests (32) - -uint256: VaultStorage.withdrawalClaimDelay (32) - -uint256[44]: VaultStorage.__gap (1408) - -uint256: OETHVaultCore.wethAssetIndex (32) - -uint256[50]: OETHVaultCore.__gap (1600) - - - -1 - -Asset <<Struct>> - -offset - -0 - -type: variable (bytes) - -unallocated (27) - -uint16: allowedOracleSlippageBps (2) - -uint8: decimals (1) - -UnitConversion: unitConversion (1) - -bool: isSupported (1) - - - -9:8->1 - - - - - -2 - -address[]: allAssets <<Array>> -0x46bddb1178e94d7f2892ff5f366840eb658911794f2c3a44c450aa2c505186c1 - -offset - -0 - -type: variable (bytes) - -unallocated (12) - -address (20) - - - -9:10->2 - - - - - -3 - -Strategy <<Struct>> - -offset - -0 - -1 - -type: variable (bytes) - -unallocated (31) - -bool: isSupported (1) - -uint256: _deprecated (32) - - - -9:13->3 - - - - - -4 - -address[]: allStrategies <<Array>> -0x4a11f94e20a93c79f6ec743a1954ec4fc2c08429ae2122118bf234b2185c81b8 - -offset - -0 - -type: variable (bytes) - -unallocated (12) - -address (20) - - - -9:15->4 - - - - - -5 - -address[]: _deprecated_swapTokens <<Array>> -0x9b22d3d61959b4d3528b1d8ba932c96fbe302b36a1aad1d95cab54f9e0a135ea - -offset - -0 - -type: variable (bytes) - -unallocated (12) - -address (20) - - - -9:32->5 - - - - - -6 - -SwapConfig <<Struct>> - -slot - -72 - -type: variable (bytes) - -unallocated (10) - -uint16: allowedUndervalueBps (2) - -address: swapper (20) - - - -9:38->6 - - - - - -7 - -WithdrawalQueueMetadata <<Struct>> - -slot - -75 - -76 - -type: variable (bytes) - -uint128: claimable (16) - -uint128: queued (16) - -uint128: nextWithdrawalIndex (16) - -uint128: claimed (16) - - - -9:45->7 - - - - - -8 - -WithdrawalRequest <<Struct>> - -offset - -0 - -1 - -type: variable (bytes) - -unallocated (6) - -uint40: timestamp (5) - -bool: claimed (1) - -address: withdrawer (20) - -uint128: queued (16) - -uint128: amount (16) - - - -9:51->8 - - - - - diff --git a/contracts/docs/OETHVaultAdminSquashed.svg b/contracts/docs/OETHVaultAdminSquashed.svg deleted file mode 100644 index 42ba2da7ee..0000000000 --- a/contracts/docs/OETHVaultAdminSquashed.svg +++ /dev/null @@ -1,167 +0,0 @@ - - - - - - -UmlClassDiagram - - - -286 - -OETHVaultAdmin -../contracts/vault/OETHVaultAdmin.sol - -Private: -   initialized: bool <<Initializable>> -   initializing: bool <<Initializable>> -   ______gap: uint256[50] <<Initializable>> -   governorPosition: bytes32 <<Governable>> -   pendingGovernorPosition: bytes32 <<Governable>> -   reentryStatusPosition: bytes32 <<Governable>> -   _deprecated_rebaseHooksAddr: address <<VaultStorage>> -   _deprecated_uniswapAddr: address <<VaultStorage>> -   _deprecated_swapTokens: address[] <<VaultStorage>> -   __gap: uint256[44] <<VaultStorage>> -Internal: -   assets: mapping(address=>Asset) <<VaultStorage>> -   allAssets: address[] <<VaultStorage>> -   strategies: mapping(address=>Strategy) <<VaultStorage>> -   allStrategies: address[] <<VaultStorage>> -   oUSD: OUSD <<VaultStorage>> -   swapConfig: SwapConfig <<VaultStorage>> -Public: -   _NOT_ENTERED: uint256 <<Governable>> -   _ENTERED: uint256 <<Governable>> -   priceProvider: address <<VaultStorage>> -   rebasePaused: bool <<VaultStorage>> -   capitalPaused: bool <<VaultStorage>> -   redeemFeeBps: uint256 <<VaultStorage>> -   vaultBuffer: uint256 <<VaultStorage>> -   autoAllocateThreshold: uint256 <<VaultStorage>> -   rebaseThreshold: uint256 <<VaultStorage>> -   adminImplPosition: bytes32 <<VaultStorage>> -   strategistAddr: address <<VaultStorage>> -   assetDefaultStrategies: mapping(address=>address) <<VaultStorage>> -   maxSupplyDiff: uint256 <<VaultStorage>> -   trusteeAddress: address <<VaultStorage>> -   trusteeFeeBps: uint256 <<VaultStorage>> -   MINT_MINIMUM_UNIT_PRICE: uint256 <<VaultStorage>> -   ousdMetaStrategy: address <<VaultStorage>> -   netOusdMintedForStrategy: int256 <<VaultStorage>> -   netOusdMintForStrategyThreshold: uint256 <<VaultStorage>> -   MIN_UNIT_PRICE_DRIFT: uint256 <<VaultStorage>> -   MAX_UNIT_PRICE_DRIFT: uint256 <<VaultStorage>> -   isMintWhitelistedStrategy: mapping(address=>bool) <<VaultStorage>> -   dripper: address <<VaultStorage>> -   withdrawalQueueMetadata: WithdrawalQueueMetadata <<VaultStorage>> -   withdrawalRequests: mapping(uint256=>WithdrawalRequest) <<VaultStorage>> -   withdrawalClaimDelay: uint256 <<VaultStorage>> -   weth: address <<OETHVaultAdmin>> - -Internal: -    _governor(): (governorOut: address) <<Governable>> -    _pendingGovernor(): (pendingGovernor: address) <<Governable>> -    _setGovernor(newGovernor: address) <<Governable>> -    _setPendingGovernor(newGovernor: address) <<Governable>> -    _changeGovernor(_newGovernor: address) <<Governable>> -    _swapCollateral(address, address, uint256, uint256, bytes): uint256 <<OETHVaultAdmin>> -    _depositToStrategy(_strategyToAddress: address, _assets: address[], _amounts: uint256[]) <<OETHVaultAdmin>> -    _withdrawFromStrategy(_recipient: address, _strategyFromAddress: address, _assets: address[], _amounts: uint256[]) <<OETHVaultAdmin>> -    _withdrawAllFromStrategy(_strategyAddr: address) <<OETHVaultAdmin>> -    _withdrawAllFromStrategies() <<OETHVaultAdmin>> -    _cacheDecimals(token: address) <<VaultAdmin>> -    _wethAvailable(): (wethAvailable: uint256) <<OETHVaultAdmin>> -External: -    transferGovernance(_newGovernor: address) <<onlyGovernor>> <<Governable>> -    claimGovernance() <<Governable>> -    setAdminImpl(newImpl: address) <<onlyGovernor>> <<VaultStorage>> -    setPriceProvider(_priceProvider: address) <<onlyGovernor>> <<VaultAdmin>> -    setRedeemFeeBps(_redeemFeeBps: uint256) <<onlyGovernor>> <<VaultAdmin>> -    setVaultBuffer(_vaultBuffer: uint256) <<onlyGovernorOrStrategist>> <<VaultAdmin>> -    setAutoAllocateThreshold(_threshold: uint256) <<onlyGovernor>> <<VaultAdmin>> -    setRebaseThreshold(_threshold: uint256) <<onlyGovernor>> <<VaultAdmin>> -    setStrategistAddr(_address: address) <<onlyGovernor>> <<VaultAdmin>> -    setAssetDefaultStrategy(_asset: address, _strategy: address) <<onlyGovernorOrStrategist>> <<VaultAdmin>> -    setNetOusdMintForStrategyThreshold(_threshold: uint256) <<onlyGovernor>> <<VaultAdmin>> -    setDripper(_dripper: address) <<onlyGovernor>> <<VaultAdmin>> -    setWithdrawalClaimDelay(_delay: uint256) <<onlyGovernor>> <<VaultAdmin>> -    swapCollateral(_fromAsset: address, _toAsset: address, _fromAssetAmount: uint256, _minToAssetAmount: uint256, _data: bytes): (toAssetAmount: uint256) <<nonReentrant, onlyGovernorOrStrategist>> <<VaultAdmin>> -    setSwapper(_swapperAddr: address) <<onlyGovernor>> <<VaultAdmin>> -    swapper(): (swapper_: address) <<VaultAdmin>> -    setSwapAllowedUndervalue(_basis: uint16) <<onlyGovernor>> <<VaultAdmin>> -    allowedSwapUndervalue(): (value: uint256) <<VaultAdmin>> -    setOracleSlippage(_asset: address, _allowedOracleSlippageBps: uint16) <<onlyGovernor>> <<VaultAdmin>> -    supportAsset(_asset: address, _unitConversion: uint8) <<onlyGovernor>> <<VaultAdmin>> -    removeAsset(_asset: address) <<onlyGovernor>> <<VaultAdmin>> -    cacheDecimals(_asset: address) <<onlyGovernor>> <<VaultAdmin>> -    approveStrategy(_addr: address) <<onlyGovernor>> <<VaultAdmin>> -    removeStrategy(_addr: address) <<onlyGovernor>> <<VaultAdmin>> -    depositToStrategy(_strategyToAddress: address, _assets: address[], _amounts: uint256[]) <<onlyGovernorOrStrategist, nonReentrant>> <<VaultAdmin>> -    withdrawFromStrategy(_strategyFromAddress: address, _assets: address[], _amounts: uint256[]) <<onlyGovernorOrStrategist, nonReentrant>> <<VaultAdmin>> -    setMaxSupplyDiff(_maxSupplyDiff: uint256) <<onlyGovernor>> <<VaultAdmin>> -    setTrusteeAddress(_address: address) <<onlyGovernor>> <<VaultAdmin>> -    setTrusteeFeeBps(_basis: uint256) <<onlyGovernor>> <<VaultAdmin>> -    setOusdMetaStrategy(_ousdMetaStrategy: address) <<onlyGovernor>> <<VaultAdmin>> -    pauseRebase() <<onlyGovernorOrStrategist>> <<VaultAdmin>> -    unpauseRebase() <<onlyGovernorOrStrategist>> <<VaultAdmin>> -    pauseCapital() <<onlyGovernorOrStrategist>> <<VaultAdmin>> -    unpauseCapital() <<onlyGovernorOrStrategist>> <<VaultAdmin>> -    transferToken(_asset: address, _amount: uint256) <<onlyGovernor>> <<VaultAdmin>> -    withdrawAllFromStrategy(_strategyAddr: address) <<onlyGovernorOrStrategist>> <<VaultAdmin>> -    withdrawAllFromStrategies() <<onlyGovernorOrStrategist>> <<VaultAdmin>> -    addStrategyToMintWhitelist(strategyAddr: address) <<onlyGovernor>> <<OETHVaultAdmin>> -    removeStrategyFromMintWhitelist(strategyAddr: address) <<onlyGovernor>> <<OETHVaultAdmin>> -Public: -    <<event>> PendingGovernorshipTransfer(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> GovernorshipTransferred(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> AssetSupported(_asset: address) <<VaultStorage>> -    <<event>> AssetRemoved(_asset: address) <<VaultStorage>> -    <<event>> AssetDefaultStrategyUpdated(_asset: address, _strategy: address) <<VaultStorage>> -    <<event>> AssetAllocated(_asset: address, _strategy: address, _amount: uint256) <<VaultStorage>> -    <<event>> StrategyApproved(_addr: address) <<VaultStorage>> -    <<event>> StrategyRemoved(_addr: address) <<VaultStorage>> -    <<event>> Mint(_addr: address, _value: uint256) <<VaultStorage>> -    <<event>> Redeem(_addr: address, _value: uint256) <<VaultStorage>> -    <<event>> CapitalPaused() <<VaultStorage>> -    <<event>> CapitalUnpaused() <<VaultStorage>> -    <<event>> RebasePaused() <<VaultStorage>> -    <<event>> RebaseUnpaused() <<VaultStorage>> -    <<event>> VaultBufferUpdated(_vaultBuffer: uint256) <<VaultStorage>> -    <<event>> OusdMetaStrategyUpdated(_ousdMetaStrategy: address) <<VaultStorage>> -    <<event>> RedeemFeeUpdated(_redeemFeeBps: uint256) <<VaultStorage>> -    <<event>> PriceProviderUpdated(_priceProvider: address) <<VaultStorage>> -    <<event>> AllocateThresholdUpdated(_threshold: uint256) <<VaultStorage>> -    <<event>> RebaseThresholdUpdated(_threshold: uint256) <<VaultStorage>> -    <<event>> StrategistUpdated(_address: address) <<VaultStorage>> -    <<event>> MaxSupplyDiffChanged(maxSupplyDiff: uint256) <<VaultStorage>> -    <<event>> YieldDistribution(_to: address, _yield: uint256, _fee: uint256) <<VaultStorage>> -    <<event>> TrusteeFeeBpsChanged(_basis: uint256) <<VaultStorage>> -    <<event>> TrusteeAddressChanged(_address: address) <<VaultStorage>> -    <<event>> NetOusdMintForStrategyThresholdChanged(_threshold: uint256) <<VaultStorage>> -    <<event>> SwapperChanged(_address: address) <<VaultStorage>> -    <<event>> SwapAllowedUndervalueChanged(_basis: uint256) <<VaultStorage>> -    <<event>> SwapSlippageChanged(_asset: address, _basis: uint256) <<VaultStorage>> -    <<event>> Swapped(_fromAsset: address, _toAsset: address, _fromAssetAmount: uint256, _toAssetAmount: uint256) <<VaultStorage>> -    <<event>> StrategyAddedToMintWhitelist(strategy: address) <<VaultStorage>> -    <<event>> StrategyRemovedFromMintWhitelist(strategy: address) <<VaultStorage>> -    <<event>> DripperChanged(_dripper: address) <<VaultStorage>> -    <<event>> WithdrawalRequested(_withdrawer: address, _requestId: uint256, _amount: uint256, _queued: uint256) <<VaultStorage>> -    <<event>> WithdrawalClaimed(_withdrawer: address, _requestId: uint256, _amount: uint256) <<VaultStorage>> -    <<event>> WithdrawalClaimable(_claimable: uint256, _newClaimable: uint256) <<VaultStorage>> -    <<event>> WithdrawalClaimDelayUpdated(_newDelay: uint256) <<VaultStorage>> -    <<modifier>> initializer() <<Initializable>> -    <<modifier>> onlyGovernor() <<Governable>> -    <<modifier>> nonReentrant() <<Governable>> -    <<modifier>> onlyGovernorOrStrategist() <<VaultAdmin>> -    constructor() <<Governable>> -    governor(): address <<Governable>> -    isGovernor(): bool <<Governable>> -    constructor(_weth: address) <<OETHVaultAdmin>> - - - diff --git a/contracts/docs/OETHVaultCoreSquashed.svg b/contracts/docs/OETHVaultCoreSquashed.svg deleted file mode 100644 index 4bb8488671..0000000000 --- a/contracts/docs/OETHVaultCoreSquashed.svg +++ /dev/null @@ -1,173 +0,0 @@ - - - - - - -UmlClassDiagram - - - -287 - -OETHVaultCore -../contracts/vault/OETHVaultCore.sol - -Private: -   initialized: bool <<Initializable>> -   initializing: bool <<Initializable>> -   ______gap: uint256[50] <<Initializable>> -   governorPosition: bytes32 <<Governable>> -   pendingGovernorPosition: bytes32 <<Governable>> -   reentryStatusPosition: bytes32 <<Governable>> -   _deprecated_rebaseHooksAddr: address <<VaultStorage>> -   _deprecated_uniswapAddr: address <<VaultStorage>> -   _deprecated_swapTokens: address[] <<VaultStorage>> -   __gap: uint256[44] <<VaultStorage>> -   __gap: uint256[50] <<OETHVaultCore>> -Internal: -   assets: mapping(address=>Asset) <<VaultStorage>> -   allAssets: address[] <<VaultStorage>> -   strategies: mapping(address=>Strategy) <<VaultStorage>> -   allStrategies: address[] <<VaultStorage>> -   oUSD: OUSD <<VaultStorage>> -   swapConfig: SwapConfig <<VaultStorage>> -   MAX_INT: uint256 <<VaultCore>> -Public: -   _NOT_ENTERED: uint256 <<Governable>> -   _ENTERED: uint256 <<Governable>> -   priceProvider: address <<VaultStorage>> -   rebasePaused: bool <<VaultStorage>> -   capitalPaused: bool <<VaultStorage>> -   redeemFeeBps: uint256 <<VaultStorage>> -   vaultBuffer: uint256 <<VaultStorage>> -   autoAllocateThreshold: uint256 <<VaultStorage>> -   rebaseThreshold: uint256 <<VaultStorage>> -   adminImplPosition: bytes32 <<VaultStorage>> -   strategistAddr: address <<VaultStorage>> -   assetDefaultStrategies: mapping(address=>address) <<VaultStorage>> -   maxSupplyDiff: uint256 <<VaultStorage>> -   trusteeAddress: address <<VaultStorage>> -   trusteeFeeBps: uint256 <<VaultStorage>> -   MINT_MINIMUM_UNIT_PRICE: uint256 <<VaultStorage>> -   ousdMetaStrategy: address <<VaultStorage>> -   netOusdMintedForStrategy: int256 <<VaultStorage>> -   netOusdMintForStrategyThreshold: uint256 <<VaultStorage>> -   MIN_UNIT_PRICE_DRIFT: uint256 <<VaultStorage>> -   MAX_UNIT_PRICE_DRIFT: uint256 <<VaultStorage>> -   isMintWhitelistedStrategy: mapping(address=>bool) <<VaultStorage>> -   dripper: address <<VaultStorage>> -   withdrawalQueueMetadata: WithdrawalQueueMetadata <<VaultStorage>> -   withdrawalRequests: mapping(uint256=>WithdrawalRequest) <<VaultStorage>> -   withdrawalClaimDelay: uint256 <<VaultStorage>> -   weth: address <<OETHVaultCore>> -   wethAssetIndex: uint256 <<OETHVaultCore>> - -Private: -    abs(x: int256): uint256 <<VaultCore>> -Internal: -    _governor(): (governorOut: address) <<Governable>> -    _pendingGovernor(): (pendingGovernor: address) <<Governable>> -    _setGovernor(newGovernor: address) <<Governable>> -    _setPendingGovernor(newGovernor: address) <<Governable>> -    _changeGovernor(_newGovernor: address) <<Governable>> -    _mint(_asset: address, _amount: uint256, _minimumOusdAmount: uint256) <<OETHVaultCore>> -    _redeem(_amount: uint256, _minimumUnitAmount: uint256) <<OETHVaultCore>> -    _postRedeem(_amount: uint256) <<VaultCore>> -    _allocate() <<OETHVaultCore>> -    _rebase(): uint256 <<whenNotRebasePaused>> <<VaultCore>> -    _totalValue(): (value: uint256) <<OETHVaultCore>> -    _totalValueInVault(): (value: uint256) <<OETHVaultCore>> -    _totalValueInStrategies(): (value: uint256) <<VaultCore>> -    _totalValueInStrategy(_strategyAddr: address): (value: uint256) <<VaultCore>> -    _checkBalance(_asset: address): (balance: uint256) <<OETHVaultCore>> -    _calculateRedeemOutputs(_amount: uint256): (outputs: uint256[]) <<OETHVaultCore>> -    _toUnits(_raw: uint256, _asset: address): uint256 <<VaultCore>> -    _toUnitPrice(_asset: address, isMint: bool): (price: uint256) <<VaultCore>> -    _getDecimals(_asset: address): (decimals: uint256) <<VaultCore>> -    _claimWithdrawal(requestId: uint256): (amount: uint256) <<OETHVaultCore>> -    _addWithdrawalQueueLiquidity(): (addedClaimable: uint256) <<OETHVaultCore>> -    _wethAvailable(): (wethAvailable: uint256) <<OETHVaultCore>> -External: -    transferGovernance(_newGovernor: address) <<onlyGovernor>> <<Governable>> -    claimGovernance() <<Governable>> -    setAdminImpl(newImpl: address) <<onlyGovernor>> <<VaultStorage>> -    initialize(_priceProvider: address, _oToken: address) <<onlyGovernor, initializer>> <<VaultInitializer>> -    mint(_asset: address, _amount: uint256, _minimumOusdAmount: uint256) <<whenNotCapitalPaused, nonReentrant>> <<VaultCore>> -    mintForStrategy(amount: uint256) <<whenNotCapitalPaused>> <<OETHVaultCore>> -    redeem(_amount: uint256, _minimumUnitAmount: uint256) <<whenNotCapitalPaused, nonReentrant>> <<VaultCore>> -    burnForStrategy(amount: uint256) <<whenNotCapitalPaused>> <<OETHVaultCore>> -    redeemAll(_minimumUnitAmount: uint256) <<whenNotCapitalPaused, nonReentrant>> <<VaultCore>> -    allocate() <<whenNotCapitalPaused, nonReentrant>> <<OETHVaultCore>> -    rebase() <<nonReentrant>> <<VaultCore>> -    totalValue(): (value: uint256) <<VaultCore>> -    checkBalance(_asset: address): uint256 <<VaultCore>> -    calculateRedeemOutputs(_amount: uint256): uint256[] <<VaultCore>> -    priceUnitMint(asset: address): (price: uint256) <<VaultCore>> -    priceUnitRedeem(asset: address): (price: uint256) <<VaultCore>> -    getAllAssets(): address[] <<VaultCore>> -    getStrategyCount(): uint256 <<VaultCore>> -    getAllStrategies(): address[] <<VaultCore>> -    isSupportedAsset(_asset: address): bool <<VaultCore>> -    null() <<VaultCore>> -    cacheWETHAssetIndex() <<onlyGovernor>> <<OETHVaultCore>> -    requestWithdrawal(_amount: uint256): (requestId: uint256, queued: uint256) <<whenNotCapitalPaused, nonReentrant>> <<OETHVaultCore>> -    claimWithdrawal(_requestId: uint256): (amount: uint256) <<whenNotCapitalPaused, nonReentrant>> <<OETHVaultCore>> -    claimWithdrawals(_requestIds: uint256[]): (amounts: uint256[], totalAmount: uint256) <<whenNotCapitalPaused, nonReentrant>> <<OETHVaultCore>> -    addWithdrawalQueueLiquidity() <<OETHVaultCore>> -Public: -    <<event>> PendingGovernorshipTransfer(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> GovernorshipTransferred(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> AssetSupported(_asset: address) <<VaultStorage>> -    <<event>> AssetRemoved(_asset: address) <<VaultStorage>> -    <<event>> AssetDefaultStrategyUpdated(_asset: address, _strategy: address) <<VaultStorage>> -    <<event>> AssetAllocated(_asset: address, _strategy: address, _amount: uint256) <<VaultStorage>> -    <<event>> StrategyApproved(_addr: address) <<VaultStorage>> -    <<event>> StrategyRemoved(_addr: address) <<VaultStorage>> -    <<event>> Mint(_addr: address, _value: uint256) <<VaultStorage>> -    <<event>> Redeem(_addr: address, _value: uint256) <<VaultStorage>> -    <<event>> CapitalPaused() <<VaultStorage>> -    <<event>> CapitalUnpaused() <<VaultStorage>> -    <<event>> RebasePaused() <<VaultStorage>> -    <<event>> RebaseUnpaused() <<VaultStorage>> -    <<event>> VaultBufferUpdated(_vaultBuffer: uint256) <<VaultStorage>> -    <<event>> OusdMetaStrategyUpdated(_ousdMetaStrategy: address) <<VaultStorage>> -    <<event>> RedeemFeeUpdated(_redeemFeeBps: uint256) <<VaultStorage>> -    <<event>> PriceProviderUpdated(_priceProvider: address) <<VaultStorage>> -    <<event>> AllocateThresholdUpdated(_threshold: uint256) <<VaultStorage>> -    <<event>> RebaseThresholdUpdated(_threshold: uint256) <<VaultStorage>> -    <<event>> StrategistUpdated(_address: address) <<VaultStorage>> -    <<event>> MaxSupplyDiffChanged(maxSupplyDiff: uint256) <<VaultStorage>> -    <<event>> YieldDistribution(_to: address, _yield: uint256, _fee: uint256) <<VaultStorage>> -    <<event>> TrusteeFeeBpsChanged(_basis: uint256) <<VaultStorage>> -    <<event>> TrusteeAddressChanged(_address: address) <<VaultStorage>> -    <<event>> NetOusdMintForStrategyThresholdChanged(_threshold: uint256) <<VaultStorage>> -    <<event>> SwapperChanged(_address: address) <<VaultStorage>> -    <<event>> SwapAllowedUndervalueChanged(_basis: uint256) <<VaultStorage>> -    <<event>> SwapSlippageChanged(_asset: address, _basis: uint256) <<VaultStorage>> -    <<event>> Swapped(_fromAsset: address, _toAsset: address, _fromAssetAmount: uint256, _toAssetAmount: uint256) <<VaultStorage>> -    <<event>> StrategyAddedToMintWhitelist(strategy: address) <<VaultStorage>> -    <<event>> StrategyRemovedFromMintWhitelist(strategy: address) <<VaultStorage>> -    <<event>> DripperChanged(_dripper: address) <<VaultStorage>> -    <<event>> WithdrawalRequested(_withdrawer: address, _requestId: uint256, _amount: uint256, _queued: uint256) <<VaultStorage>> -    <<event>> WithdrawalClaimed(_withdrawer: address, _requestId: uint256, _amount: uint256) <<VaultStorage>> -    <<event>> WithdrawalClaimable(_claimable: uint256, _newClaimable: uint256) <<VaultStorage>> -    <<event>> WithdrawalClaimDelayUpdated(_newDelay: uint256) <<VaultStorage>> -    <<modifier>> initializer() <<Initializable>> -    <<modifier>> onlyGovernor() <<Governable>> -    <<modifier>> nonReentrant() <<Governable>> -    <<modifier>> whenNotRebasePaused() <<VaultCore>> -    <<modifier>> whenNotCapitalPaused() <<VaultCore>> -    <<modifier>> onlyOusdMetaStrategy() <<VaultCore>> -    constructor() <<Governable>> -    governor(): address <<Governable>> -    isGovernor(): bool <<Governable>> -    getAssetCount(): uint256 <<VaultCore>> -    getAssetConfig(_asset: address): (config: Asset) <<VaultCore>> -    constructor(_weth: address) <<OETHVaultCore>> - - - diff --git a/contracts/docs/OETHVaultStorage.svg b/contracts/docs/OETHVaultStorage.svg deleted file mode 100644 index e425f8712d..0000000000 --- a/contracts/docs/OETHVaultStorage.svg +++ /dev/null @@ -1,359 +0,0 @@ - - - - - - -StorageDiagram - - - -8 - -OETHVaultCore <<Contract>> - -slot - -0 - -1-50 - -51 - -52 - -53 - -54 - -55 - -56 - -57 - -58 - -59 - -60 - -61 - -62 - -63 - -64 - -65 - -66 - -67 - -68 - -69 - -70 - -71 - -72 - -73 - -74 - -75-76 - -77 - -78 - -79-122 - -123 - -124-173 - -type: <inherited contract>.variable (bytes) - -unallocated (30) - -bool: Initializable.initializing (1) - -bool: Initializable.initialized (1) - -uint256[50]: Initializable.______gap (1600) - -mapping(address=>Asset): VaultStorage.assets (32) - -address[]: VaultStorage.allAssets (32) - -mapping(address=>Strategy): VaultStorage.strategies (32) - -address[]: VaultStorage.allStrategies (32) - -unallocated (10) - -bool: VaultStorage.capitalPaused (1) - -bool: VaultStorage.rebasePaused (1) - -address: VaultStorage.priceProvider (20) - -uint256: VaultStorage.redeemFeeBps (32) - -uint256: VaultStorage.vaultBuffer (32) - -uint256: VaultStorage.autoAllocateThreshold (32) - -uint256: VaultStorage.rebaseThreshold (32) - -unallocated (12) - -OUSD: VaultStorage.oUSD (20) - -unallocated (12) - -address: VaultStorage._deprecated_rebaseHooksAddr (20) - -unallocated (12) - -address: VaultStorage._deprecated_uniswapAddr (20) - -unallocated (12) - -address: VaultStorage.strategistAddr (20) - -mapping(address=>address): VaultStorage.assetDefaultStrategies (32) - -uint256: VaultStorage.maxSupplyDiff (32) - -unallocated (12) - -address: VaultStorage.trusteeAddress (20) - -uint256: VaultStorage.trusteeFeeBps (32) - -address[]: VaultStorage._deprecated_swapTokens (32) - -unallocated (12) - -address: VaultStorage.ousdMetaStrategy (20) - -int256: VaultStorage.netOusdMintedForStrategy (32) - -uint256: VaultStorage.netOusdMintForStrategyThreshold (32) - -SwapConfig: VaultStorage.swapConfig (32) - -mapping(address=>bool): VaultStorage.isMintWhitelistedStrategy (32) - -unallocated (12) - -address: VaultStorage.dripper (20) - -WithdrawalQueueMetadata: VaultStorage.withdrawalQueueMetadata (64) - -mapping(uint256=>WithdrawalRequest): VaultStorage.withdrawalRequests (32) - -uint256: VaultStorage.withdrawalClaimDelay (32) - -uint256[44]: VaultStorage.__gap (1408) - -uint256: wethAssetIndex (32) - -uint256[50]: __gap (1600) - - - -1 - -Asset <<Struct>> - -offset - -0 - -type: variable (bytes) - -unallocated (27) - -uint16: allowedOracleSlippageBps (2) - -uint8: decimals (1) - -UnitConversion: unitConversion (1) - -bool: isSupported (1) - - - -8:8->1 - - - - - -2 - -address[]: allAssets <<Array>> -0x46bddb1178e94d7f2892ff5f366840eb658911794f2c3a44c450aa2c505186c1 - -offset - -0 - -type: variable (bytes) - -unallocated (12) - -address (20) - - - -8:10->2 - - - - - -3 - -Strategy <<Struct>> - -offset - -0 - -1 - -type: variable (bytes) - -unallocated (31) - -bool: isSupported (1) - -uint256: _deprecated (32) - - - -8:13->3 - - - - - -4 - -address[]: allStrategies <<Array>> -0x4a11f94e20a93c79f6ec743a1954ec4fc2c08429ae2122118bf234b2185c81b8 - -offset - -0 - -type: variable (bytes) - -unallocated (12) - -address (20) - - - -8:15->4 - - - - - -5 - -SwapConfig <<Struct>> - -slot - -72 - -type: variable (bytes) - -unallocated (10) - -uint16: allowedUndervalueBps (2) - -address: swapper (20) - - - -8:37->5 - - - - - -6 - -WithdrawalQueueMetadata <<Struct>> - -slot - -75 - -76 - -type: variable (bytes) - -uint128: claimable (16) - -uint128: queued (16) - -uint128: nextWithdrawalIndex (16) - -uint128: claimed (16) - - - -8:44->6 - - - - - -7 - -WithdrawalRequest <<Struct>> - -offset - -0 - -1 - -type: variable (bytes) - -unallocated (6) - -uint40: timestamp (5) - -bool: claimed (1) - -address: withdrawer (20) - -uint128: queued (16) - -uint128: amount (16) - - - -8:50->7 - - - - - diff --git a/contracts/docs/OSonicVaultAdminSquashed.svg b/contracts/docs/OSonicVaultAdminSquashed.svg deleted file mode 100644 index ba51485b4e..0000000000 --- a/contracts/docs/OSonicVaultAdminSquashed.svg +++ /dev/null @@ -1,167 +0,0 @@ - - - - - - -UmlClassDiagram - - - -289 - -OSonicVaultAdmin -../contracts/vault/OSonicVaultAdmin.sol - -Private: -   initialized: bool <<Initializable>> -   initializing: bool <<Initializable>> -   ______gap: uint256[50] <<Initializable>> -   governorPosition: bytes32 <<Governable>> -   pendingGovernorPosition: bytes32 <<Governable>> -   reentryStatusPosition: bytes32 <<Governable>> -   _deprecated_rebaseHooksAddr: address <<VaultStorage>> -   _deprecated_uniswapAddr: address <<VaultStorage>> -   _deprecated_swapTokens: address[] <<VaultStorage>> -   __gap: uint256[44] <<VaultStorage>> -Internal: -   assets: mapping(address=>Asset) <<VaultStorage>> -   allAssets: address[] <<VaultStorage>> -   strategies: mapping(address=>Strategy) <<VaultStorage>> -   allStrategies: address[] <<VaultStorage>> -   oUSD: OUSD <<VaultStorage>> -   swapConfig: SwapConfig <<VaultStorage>> -Public: -   _NOT_ENTERED: uint256 <<Governable>> -   _ENTERED: uint256 <<Governable>> -   priceProvider: address <<VaultStorage>> -   rebasePaused: bool <<VaultStorage>> -   capitalPaused: bool <<VaultStorage>> -   redeemFeeBps: uint256 <<VaultStorage>> -   vaultBuffer: uint256 <<VaultStorage>> -   autoAllocateThreshold: uint256 <<VaultStorage>> -   rebaseThreshold: uint256 <<VaultStorage>> -   adminImplPosition: bytes32 <<VaultStorage>> -   strategistAddr: address <<VaultStorage>> -   assetDefaultStrategies: mapping(address=>address) <<VaultStorage>> -   maxSupplyDiff: uint256 <<VaultStorage>> -   trusteeAddress: address <<VaultStorage>> -   trusteeFeeBps: uint256 <<VaultStorage>> -   MINT_MINIMUM_UNIT_PRICE: uint256 <<VaultStorage>> -   ousdMetaStrategy: address <<VaultStorage>> -   netOusdMintedForStrategy: int256 <<VaultStorage>> -   netOusdMintForStrategyThreshold: uint256 <<VaultStorage>> -   MIN_UNIT_PRICE_DRIFT: uint256 <<VaultStorage>> -   MAX_UNIT_PRICE_DRIFT: uint256 <<VaultStorage>> -   isMintWhitelistedStrategy: mapping(address=>bool) <<VaultStorage>> -   dripper: address <<VaultStorage>> -   withdrawalQueueMetadata: WithdrawalQueueMetadata <<VaultStorage>> -   withdrawalRequests: mapping(uint256=>WithdrawalRequest) <<VaultStorage>> -   withdrawalClaimDelay: uint256 <<VaultStorage>> -   weth: address <<OETHVaultAdmin>> - -Internal: -    _governor(): (governorOut: address) <<Governable>> -    _pendingGovernor(): (pendingGovernor: address) <<Governable>> -    _setGovernor(newGovernor: address) <<Governable>> -    _setPendingGovernor(newGovernor: address) <<Governable>> -    _changeGovernor(_newGovernor: address) <<Governable>> -    _swapCollateral(address, address, uint256, uint256, bytes): uint256 <<OETHVaultAdmin>> -    _depositToStrategy(_strategyToAddress: address, _assets: address[], _amounts: uint256[]) <<OETHVaultAdmin>> -    _withdrawFromStrategy(_recipient: address, _strategyFromAddress: address, _assets: address[], _amounts: uint256[]) <<OETHVaultAdmin>> -    _withdrawAllFromStrategy(_strategyAddr: address) <<OETHVaultAdmin>> -    _withdrawAllFromStrategies() <<OETHVaultAdmin>> -    _cacheDecimals(token: address) <<VaultAdmin>> -    _wethAvailable(): (wethAvailable: uint256) <<OETHVaultAdmin>> -External: -    transferGovernance(_newGovernor: address) <<onlyGovernor>> <<Governable>> -    claimGovernance() <<Governable>> -    setAdminImpl(newImpl: address) <<onlyGovernor>> <<VaultStorage>> -    setPriceProvider(_priceProvider: address) <<onlyGovernor>> <<VaultAdmin>> -    setRedeemFeeBps(_redeemFeeBps: uint256) <<onlyGovernor>> <<VaultAdmin>> -    setVaultBuffer(_vaultBuffer: uint256) <<onlyGovernorOrStrategist>> <<VaultAdmin>> -    setAutoAllocateThreshold(_threshold: uint256) <<onlyGovernor>> <<VaultAdmin>> -    setRebaseThreshold(_threshold: uint256) <<onlyGovernor>> <<VaultAdmin>> -    setStrategistAddr(_address: address) <<onlyGovernor>> <<VaultAdmin>> -    setAssetDefaultStrategy(_asset: address, _strategy: address) <<onlyGovernorOrStrategist>> <<VaultAdmin>> -    setNetOusdMintForStrategyThreshold(_threshold: uint256) <<onlyGovernor>> <<VaultAdmin>> -    setDripper(_dripper: address) <<onlyGovernor>> <<VaultAdmin>> -    setWithdrawalClaimDelay(_delay: uint256) <<onlyGovernor>> <<VaultAdmin>> -    swapCollateral(_fromAsset: address, _toAsset: address, _fromAssetAmount: uint256, _minToAssetAmount: uint256, _data: bytes): (toAssetAmount: uint256) <<nonReentrant, onlyGovernorOrStrategist>> <<VaultAdmin>> -    setSwapper(_swapperAddr: address) <<onlyGovernor>> <<VaultAdmin>> -    swapper(): (swapper_: address) <<VaultAdmin>> -    setSwapAllowedUndervalue(_basis: uint16) <<onlyGovernor>> <<VaultAdmin>> -    allowedSwapUndervalue(): (value: uint256) <<VaultAdmin>> -    setOracleSlippage(_asset: address, _allowedOracleSlippageBps: uint16) <<onlyGovernor>> <<VaultAdmin>> -    supportAsset(_asset: address, _unitConversion: uint8) <<onlyGovernor>> <<OSonicVaultAdmin>> -    removeAsset(_asset: address) <<onlyGovernor>> <<VaultAdmin>> -    cacheDecimals(_asset: address) <<onlyGovernor>> <<VaultAdmin>> -    approveStrategy(_addr: address) <<onlyGovernor>> <<VaultAdmin>> -    removeStrategy(_addr: address) <<onlyGovernor>> <<VaultAdmin>> -    depositToStrategy(_strategyToAddress: address, _assets: address[], _amounts: uint256[]) <<onlyGovernorOrStrategist, nonReentrant>> <<VaultAdmin>> -    withdrawFromStrategy(_strategyFromAddress: address, _assets: address[], _amounts: uint256[]) <<onlyGovernorOrStrategist, nonReentrant>> <<VaultAdmin>> -    setMaxSupplyDiff(_maxSupplyDiff: uint256) <<onlyGovernor>> <<VaultAdmin>> -    setTrusteeAddress(_address: address) <<onlyGovernor>> <<VaultAdmin>> -    setTrusteeFeeBps(_basis: uint256) <<onlyGovernor>> <<VaultAdmin>> -    setOusdMetaStrategy(_ousdMetaStrategy: address) <<onlyGovernor>> <<VaultAdmin>> -    pauseRebase() <<onlyGovernorOrStrategist>> <<VaultAdmin>> -    unpauseRebase() <<onlyGovernorOrStrategist>> <<VaultAdmin>> -    pauseCapital() <<onlyGovernorOrStrategist>> <<VaultAdmin>> -    unpauseCapital() <<onlyGovernorOrStrategist>> <<VaultAdmin>> -    transferToken(_asset: address, _amount: uint256) <<onlyGovernor>> <<VaultAdmin>> -    withdrawAllFromStrategy(_strategyAddr: address) <<onlyGovernorOrStrategist>> <<VaultAdmin>> -    withdrawAllFromStrategies() <<onlyGovernorOrStrategist>> <<VaultAdmin>> -    addStrategyToMintWhitelist(strategyAddr: address) <<onlyGovernor>> <<OETHVaultAdmin>> -    removeStrategyFromMintWhitelist(strategyAddr: address) <<onlyGovernor>> <<OETHVaultAdmin>> -Public: -    <<event>> PendingGovernorshipTransfer(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> GovernorshipTransferred(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> AssetSupported(_asset: address) <<VaultStorage>> -    <<event>> AssetRemoved(_asset: address) <<VaultStorage>> -    <<event>> AssetDefaultStrategyUpdated(_asset: address, _strategy: address) <<VaultStorage>> -    <<event>> AssetAllocated(_asset: address, _strategy: address, _amount: uint256) <<VaultStorage>> -    <<event>> StrategyApproved(_addr: address) <<VaultStorage>> -    <<event>> StrategyRemoved(_addr: address) <<VaultStorage>> -    <<event>> Mint(_addr: address, _value: uint256) <<VaultStorage>> -    <<event>> Redeem(_addr: address, _value: uint256) <<VaultStorage>> -    <<event>> CapitalPaused() <<VaultStorage>> -    <<event>> CapitalUnpaused() <<VaultStorage>> -    <<event>> RebasePaused() <<VaultStorage>> -    <<event>> RebaseUnpaused() <<VaultStorage>> -    <<event>> VaultBufferUpdated(_vaultBuffer: uint256) <<VaultStorage>> -    <<event>> OusdMetaStrategyUpdated(_ousdMetaStrategy: address) <<VaultStorage>> -    <<event>> RedeemFeeUpdated(_redeemFeeBps: uint256) <<VaultStorage>> -    <<event>> PriceProviderUpdated(_priceProvider: address) <<VaultStorage>> -    <<event>> AllocateThresholdUpdated(_threshold: uint256) <<VaultStorage>> -    <<event>> RebaseThresholdUpdated(_threshold: uint256) <<VaultStorage>> -    <<event>> StrategistUpdated(_address: address) <<VaultStorage>> -    <<event>> MaxSupplyDiffChanged(maxSupplyDiff: uint256) <<VaultStorage>> -    <<event>> YieldDistribution(_to: address, _yield: uint256, _fee: uint256) <<VaultStorage>> -    <<event>> TrusteeFeeBpsChanged(_basis: uint256) <<VaultStorage>> -    <<event>> TrusteeAddressChanged(_address: address) <<VaultStorage>> -    <<event>> NetOusdMintForStrategyThresholdChanged(_threshold: uint256) <<VaultStorage>> -    <<event>> SwapperChanged(_address: address) <<VaultStorage>> -    <<event>> SwapAllowedUndervalueChanged(_basis: uint256) <<VaultStorage>> -    <<event>> SwapSlippageChanged(_asset: address, _basis: uint256) <<VaultStorage>> -    <<event>> Swapped(_fromAsset: address, _toAsset: address, _fromAssetAmount: uint256, _toAssetAmount: uint256) <<VaultStorage>> -    <<event>> StrategyAddedToMintWhitelist(strategy: address) <<VaultStorage>> -    <<event>> StrategyRemovedFromMintWhitelist(strategy: address) <<VaultStorage>> -    <<event>> DripperChanged(_dripper: address) <<VaultStorage>> -    <<event>> WithdrawalRequested(_withdrawer: address, _requestId: uint256, _amount: uint256, _queued: uint256) <<VaultStorage>> -    <<event>> WithdrawalClaimed(_withdrawer: address, _requestId: uint256, _amount: uint256) <<VaultStorage>> -    <<event>> WithdrawalClaimable(_claimable: uint256, _newClaimable: uint256) <<VaultStorage>> -    <<event>> WithdrawalClaimDelayUpdated(_newDelay: uint256) <<VaultStorage>> -    <<modifier>> initializer() <<Initializable>> -    <<modifier>> onlyGovernor() <<Governable>> -    <<modifier>> nonReentrant() <<Governable>> -    <<modifier>> onlyGovernorOrStrategist() <<VaultAdmin>> -    constructor() <<Governable>> -    governor(): address <<Governable>> -    isGovernor(): bool <<Governable>> -    constructor(_wS: address) <<OSonicVaultAdmin>> - - - diff --git a/contracts/docs/OSonicVaultCoreSquashed.svg b/contracts/docs/OSonicVaultCoreSquashed.svg deleted file mode 100644 index 9d467a3367..0000000000 --- a/contracts/docs/OSonicVaultCoreSquashed.svg +++ /dev/null @@ -1,173 +0,0 @@ - - - - - - -UmlClassDiagram - - - -290 - -OSonicVaultCore -../contracts/vault/OSonicVaultCore.sol - -Private: -   initialized: bool <<Initializable>> -   initializing: bool <<Initializable>> -   ______gap: uint256[50] <<Initializable>> -   governorPosition: bytes32 <<Governable>> -   pendingGovernorPosition: bytes32 <<Governable>> -   reentryStatusPosition: bytes32 <<Governable>> -   _deprecated_rebaseHooksAddr: address <<VaultStorage>> -   _deprecated_uniswapAddr: address <<VaultStorage>> -   _deprecated_swapTokens: address[] <<VaultStorage>> -   __gap: uint256[44] <<VaultStorage>> -   __gap: uint256[50] <<OETHVaultCore>> -Internal: -   assets: mapping(address=>Asset) <<VaultStorage>> -   allAssets: address[] <<VaultStorage>> -   strategies: mapping(address=>Strategy) <<VaultStorage>> -   allStrategies: address[] <<VaultStorage>> -   oUSD: OUSD <<VaultStorage>> -   swapConfig: SwapConfig <<VaultStorage>> -   MAX_INT: uint256 <<VaultCore>> -Public: -   _NOT_ENTERED: uint256 <<Governable>> -   _ENTERED: uint256 <<Governable>> -   priceProvider: address <<VaultStorage>> -   rebasePaused: bool <<VaultStorage>> -   capitalPaused: bool <<VaultStorage>> -   redeemFeeBps: uint256 <<VaultStorage>> -   vaultBuffer: uint256 <<VaultStorage>> -   autoAllocateThreshold: uint256 <<VaultStorage>> -   rebaseThreshold: uint256 <<VaultStorage>> -   adminImplPosition: bytes32 <<VaultStorage>> -   strategistAddr: address <<VaultStorage>> -   assetDefaultStrategies: mapping(address=>address) <<VaultStorage>> -   maxSupplyDiff: uint256 <<VaultStorage>> -   trusteeAddress: address <<VaultStorage>> -   trusteeFeeBps: uint256 <<VaultStorage>> -   MINT_MINIMUM_UNIT_PRICE: uint256 <<VaultStorage>> -   ousdMetaStrategy: address <<VaultStorage>> -   netOusdMintedForStrategy: int256 <<VaultStorage>> -   netOusdMintForStrategyThreshold: uint256 <<VaultStorage>> -   MIN_UNIT_PRICE_DRIFT: uint256 <<VaultStorage>> -   MAX_UNIT_PRICE_DRIFT: uint256 <<VaultStorage>> -   isMintWhitelistedStrategy: mapping(address=>bool) <<VaultStorage>> -   dripper: address <<VaultStorage>> -   withdrawalQueueMetadata: WithdrawalQueueMetadata <<VaultStorage>> -   withdrawalRequests: mapping(uint256=>WithdrawalRequest) <<VaultStorage>> -   withdrawalClaimDelay: uint256 <<VaultStorage>> -   weth: address <<OETHVaultCore>> -   wethAssetIndex: uint256 <<OETHVaultCore>> - -Private: -    abs(x: int256): uint256 <<VaultCore>> -Internal: -    _governor(): (governorOut: address) <<Governable>> -    _pendingGovernor(): (pendingGovernor: address) <<Governable>> -    _setGovernor(newGovernor: address) <<Governable>> -    _setPendingGovernor(newGovernor: address) <<Governable>> -    _changeGovernor(_newGovernor: address) <<Governable>> -    _mint(_asset: address, _amount: uint256, _minimumOusdAmount: uint256) <<OETHVaultCore>> -    _redeem(_amount: uint256, _minimumUnitAmount: uint256) <<OETHVaultCore>> -    _postRedeem(_amount: uint256) <<VaultCore>> -    _allocate() <<OETHVaultCore>> -    _rebase(): uint256 <<whenNotRebasePaused>> <<VaultCore>> -    _totalValue(): (value: uint256) <<OETHVaultCore>> -    _totalValueInVault(): (value: uint256) <<OETHVaultCore>> -    _totalValueInStrategies(): (value: uint256) <<VaultCore>> -    _totalValueInStrategy(_strategyAddr: address): (value: uint256) <<VaultCore>> -    _checkBalance(_asset: address): (balance: uint256) <<OETHVaultCore>> -    _calculateRedeemOutputs(_amount: uint256): (outputs: uint256[]) <<OETHVaultCore>> -    _toUnits(_raw: uint256, _asset: address): uint256 <<VaultCore>> -    _toUnitPrice(_asset: address, isMint: bool): (price: uint256) <<VaultCore>> -    _getDecimals(_asset: address): (decimals: uint256) <<VaultCore>> -    _claimWithdrawal(requestId: uint256): (amount: uint256) <<OETHVaultCore>> -    _addWithdrawalQueueLiquidity(): (addedClaimable: uint256) <<OETHVaultCore>> -    _wethAvailable(): (wethAvailable: uint256) <<OETHVaultCore>> -External: -    transferGovernance(_newGovernor: address) <<onlyGovernor>> <<Governable>> -    claimGovernance() <<Governable>> -    setAdminImpl(newImpl: address) <<onlyGovernor>> <<VaultStorage>> -    initialize(_priceProvider: address, _oToken: address) <<onlyGovernor, initializer>> <<VaultInitializer>> -    mint(_asset: address, _amount: uint256, _minimumOusdAmount: uint256) <<whenNotCapitalPaused, nonReentrant>> <<VaultCore>> -    mintForStrategy(amount: uint256) <<whenNotCapitalPaused>> <<OETHVaultCore>> -    redeem(uint256, uint256) <<OSonicVaultCore>> -    burnForStrategy(amount: uint256) <<whenNotCapitalPaused>> <<OETHVaultCore>> -    redeemAll(_minimumUnitAmount: uint256) <<whenNotCapitalPaused, nonReentrant>> <<VaultCore>> -    allocate() <<whenNotCapitalPaused, nonReentrant>> <<OETHVaultCore>> -    rebase() <<nonReentrant>> <<VaultCore>> -    totalValue(): (value: uint256) <<VaultCore>> -    checkBalance(_asset: address): uint256 <<VaultCore>> -    calculateRedeemOutputs(_amount: uint256): uint256[] <<VaultCore>> -    priceUnitMint(asset: address): (price: uint256) <<VaultCore>> -    priceUnitRedeem(asset: address): (price: uint256) <<VaultCore>> -    getAllAssets(): address[] <<VaultCore>> -    getStrategyCount(): uint256 <<VaultCore>> -    getAllStrategies(): address[] <<VaultCore>> -    isSupportedAsset(_asset: address): bool <<VaultCore>> -    null() <<VaultCore>> -    cacheWETHAssetIndex() <<onlyGovernor>> <<OETHVaultCore>> -    requestWithdrawal(_amount: uint256): (requestId: uint256, queued: uint256) <<whenNotCapitalPaused, nonReentrant>> <<OETHVaultCore>> -    claimWithdrawal(_requestId: uint256): (amount: uint256) <<whenNotCapitalPaused, nonReentrant>> <<OETHVaultCore>> -    claimWithdrawals(_requestIds: uint256[]): (amounts: uint256[], totalAmount: uint256) <<whenNotCapitalPaused, nonReentrant>> <<OETHVaultCore>> -    addWithdrawalQueueLiquidity() <<OETHVaultCore>> -Public: -    <<event>> PendingGovernorshipTransfer(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> GovernorshipTransferred(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> AssetSupported(_asset: address) <<VaultStorage>> -    <<event>> AssetRemoved(_asset: address) <<VaultStorage>> -    <<event>> AssetDefaultStrategyUpdated(_asset: address, _strategy: address) <<VaultStorage>> -    <<event>> AssetAllocated(_asset: address, _strategy: address, _amount: uint256) <<VaultStorage>> -    <<event>> StrategyApproved(_addr: address) <<VaultStorage>> -    <<event>> StrategyRemoved(_addr: address) <<VaultStorage>> -    <<event>> Mint(_addr: address, _value: uint256) <<VaultStorage>> -    <<event>> Redeem(_addr: address, _value: uint256) <<VaultStorage>> -    <<event>> CapitalPaused() <<VaultStorage>> -    <<event>> CapitalUnpaused() <<VaultStorage>> -    <<event>> RebasePaused() <<VaultStorage>> -    <<event>> RebaseUnpaused() <<VaultStorage>> -    <<event>> VaultBufferUpdated(_vaultBuffer: uint256) <<VaultStorage>> -    <<event>> OusdMetaStrategyUpdated(_ousdMetaStrategy: address) <<VaultStorage>> -    <<event>> RedeemFeeUpdated(_redeemFeeBps: uint256) <<VaultStorage>> -    <<event>> PriceProviderUpdated(_priceProvider: address) <<VaultStorage>> -    <<event>> AllocateThresholdUpdated(_threshold: uint256) <<VaultStorage>> -    <<event>> RebaseThresholdUpdated(_threshold: uint256) <<VaultStorage>> -    <<event>> StrategistUpdated(_address: address) <<VaultStorage>> -    <<event>> MaxSupplyDiffChanged(maxSupplyDiff: uint256) <<VaultStorage>> -    <<event>> YieldDistribution(_to: address, _yield: uint256, _fee: uint256) <<VaultStorage>> -    <<event>> TrusteeFeeBpsChanged(_basis: uint256) <<VaultStorage>> -    <<event>> TrusteeAddressChanged(_address: address) <<VaultStorage>> -    <<event>> NetOusdMintForStrategyThresholdChanged(_threshold: uint256) <<VaultStorage>> -    <<event>> SwapperChanged(_address: address) <<VaultStorage>> -    <<event>> SwapAllowedUndervalueChanged(_basis: uint256) <<VaultStorage>> -    <<event>> SwapSlippageChanged(_asset: address, _basis: uint256) <<VaultStorage>> -    <<event>> Swapped(_fromAsset: address, _toAsset: address, _fromAssetAmount: uint256, _toAssetAmount: uint256) <<VaultStorage>> -    <<event>> StrategyAddedToMintWhitelist(strategy: address) <<VaultStorage>> -    <<event>> StrategyRemovedFromMintWhitelist(strategy: address) <<VaultStorage>> -    <<event>> DripperChanged(_dripper: address) <<VaultStorage>> -    <<event>> WithdrawalRequested(_withdrawer: address, _requestId: uint256, _amount: uint256, _queued: uint256) <<VaultStorage>> -    <<event>> WithdrawalClaimed(_withdrawer: address, _requestId: uint256, _amount: uint256) <<VaultStorage>> -    <<event>> WithdrawalClaimable(_claimable: uint256, _newClaimable: uint256) <<VaultStorage>> -    <<event>> WithdrawalClaimDelayUpdated(_newDelay: uint256) <<VaultStorage>> -    <<modifier>> initializer() <<Initializable>> -    <<modifier>> onlyGovernor() <<Governable>> -    <<modifier>> nonReentrant() <<Governable>> -    <<modifier>> whenNotRebasePaused() <<VaultCore>> -    <<modifier>> whenNotCapitalPaused() <<VaultCore>> -    <<modifier>> onlyOusdMetaStrategy() <<VaultCore>> -    constructor() <<Governable>> -    governor(): address <<Governable>> -    isGovernor(): bool <<Governable>> -    getAssetCount(): uint256 <<VaultCore>> -    getAssetConfig(_asset: address): (config: Asset) <<VaultCore>> -    constructor(_wS: address) <<OSonicVaultCore>> - - - diff --git a/contracts/docs/OSonicVaultStorage.svg b/contracts/docs/OSonicVaultStorage.svg deleted file mode 100644 index 1f9243ad89..0000000000 --- a/contracts/docs/OSonicVaultStorage.svg +++ /dev/null @@ -1,359 +0,0 @@ - - - - - - -StorageDiagram - - - -8 - -OSonicVaultCore <<Contract>> - -slot - -0 - -1-50 - -51 - -52 - -53 - -54 - -55 - -56 - -57 - -58 - -59 - -60 - -61 - -62 - -63 - -64 - -65 - -66 - -67 - -68 - -69 - -70 - -71 - -72 - -73 - -74 - -75-76 - -77 - -78 - -79-122 - -123 - -124-173 - -type: <inherited contract>.variable (bytes) - -unallocated (30) - -bool: Initializable.initializing (1) - -bool: Initializable.initialized (1) - -uint256[50]: Initializable.______gap (1600) - -mapping(address=>Asset): VaultStorage.assets (32) - -address[]: VaultStorage.allAssets (32) - -mapping(address=>Strategy): VaultStorage.strategies (32) - -address[]: VaultStorage.allStrategies (32) - -unallocated (10) - -bool: VaultStorage.capitalPaused (1) - -bool: VaultStorage.rebasePaused (1) - -address: VaultStorage.priceProvider (20) - -uint256: VaultStorage.redeemFeeBps (32) - -uint256: VaultStorage.vaultBuffer (32) - -uint256: VaultStorage.autoAllocateThreshold (32) - -uint256: VaultStorage.rebaseThreshold (32) - -unallocated (12) - -OUSD: VaultStorage.oUSD (20) - -unallocated (12) - -address: VaultStorage._deprecated_rebaseHooksAddr (20) - -unallocated (12) - -address: VaultStorage._deprecated_uniswapAddr (20) - -unallocated (12) - -address: VaultStorage.strategistAddr (20) - -mapping(address=>address): VaultStorage.assetDefaultStrategies (32) - -uint256: VaultStorage.maxSupplyDiff (32) - -unallocated (12) - -address: VaultStorage.trusteeAddress (20) - -uint256: VaultStorage.trusteeFeeBps (32) - -address[]: VaultStorage._deprecated_swapTokens (32) - -unallocated (12) - -address: VaultStorage.ousdMetaStrategy (20) - -int256: VaultStorage.netOusdMintedForStrategy (32) - -uint256: VaultStorage.netOusdMintForStrategyThreshold (32) - -SwapConfig: VaultStorage.swapConfig (32) - -mapping(address=>bool): VaultStorage.isMintWhitelistedStrategy (32) - -unallocated (12) - -address: VaultStorage.dripper (20) - -WithdrawalQueueMetadata: VaultStorage.withdrawalQueueMetadata (64) - -mapping(uint256=>WithdrawalRequest): VaultStorage.withdrawalRequests (32) - -uint256: VaultStorage.withdrawalClaimDelay (32) - -uint256[44]: VaultStorage.__gap (1408) - -uint256: OETHVaultCore.wethAssetIndex (32) - -uint256[50]: OETHVaultCore.__gap (1600) - - - -1 - -Asset <<Struct>> - -offset - -0 - -type: variable (bytes) - -unallocated (27) - -uint16: allowedOracleSlippageBps (2) - -uint8: decimals (1) - -UnitConversion: unitConversion (1) - -bool: isSupported (1) - - - -8:8->1 - - - - - -2 - -address[]: allAssets <<Array>> -0x46bddb1178e94d7f2892ff5f366840eb658911794f2c3a44c450aa2c505186c1 - -offset - -0 - -type: variable (bytes) - -unallocated (12) - -address (20) - - - -8:10->2 - - - - - -3 - -Strategy <<Struct>> - -offset - -0 - -1 - -type: variable (bytes) - -unallocated (31) - -bool: isSupported (1) - -uint256: _deprecated (32) - - - -8:13->3 - - - - - -4 - -address[]: allStrategies <<Array>> -0x4a11f94e20a93c79f6ec743a1954ec4fc2c08429ae2122118bf234b2185c81b8 - -offset - -0 - -type: variable (bytes) - -unallocated (12) - -address (20) - - - -8:15->4 - - - - - -5 - -SwapConfig <<Struct>> - -slot - -72 - -type: variable (bytes) - -unallocated (10) - -uint16: allowedUndervalueBps (2) - -address: swapper (20) - - - -8:37->5 - - - - - -6 - -WithdrawalQueueMetadata <<Struct>> - -slot - -75 - -76 - -type: variable (bytes) - -uint128: claimable (16) - -uint128: queued (16) - -uint128: nextWithdrawalIndex (16) - -uint128: claimed (16) - - - -8:44->6 - - - - - -7 - -WithdrawalRequest <<Struct>> - -offset - -0 - -1 - -type: variable (bytes) - -unallocated (6) - -uint40: timestamp (5) - -bool: claimed (1) - -address: withdrawer (20) - -uint128: queued (16) - -uint128: amount (16) - - - -8:50->7 - - - - - diff --git a/contracts/docs/VaultCoreSquashed.svg b/contracts/docs/VaultCoreSquashed.svg deleted file mode 100644 index e0accdf3c5..0000000000 --- a/contracts/docs/VaultCoreSquashed.svg +++ /dev/null @@ -1,174 +0,0 @@ - - - - - - -UmlClassDiagram - - - -302 - -VaultCore -../contracts/vault/VaultCore.sol - -Private: -   initialized: bool <<Initializable>> -   initializing: bool <<Initializable>> -   ______gap: uint256[50] <<Initializable>> -   governorPosition: bytes32 <<Governable>> -   pendingGovernorPosition: bytes32 <<Governable>> -   reentryStatusPosition: bytes32 <<Governable>> -   _deprecated_rebaseHooksAddr: address <<VaultStorage>> -   _deprecated_uniswapAddr: address <<VaultStorage>> -   _deprecated_swapTokens: address[] <<VaultStorage>> -   __gap: uint256[43] <<VaultStorage>> -Internal: -   assets: mapping(address=>Asset) <<VaultStorage>> -   allAssets: address[] <<VaultStorage>> -   allStrategies: address[] <<VaultStorage>> -   swapConfig: SwapConfig <<VaultStorage>> -   MAX_REBASE: uint256 <<VaultStorage>> -   MAX_REBASE_PER_SECOND: uint256 <<VaultStorage>> -   MAX_INT: uint256 <<VaultCore>> -Public: -   _NOT_ENTERED: uint256 <<Governable>> -   _ENTERED: uint256 <<Governable>> -   strategies: mapping(address=>Strategy) <<VaultStorage>> -   priceProvider: address <<VaultStorage>> -   rebasePaused: bool <<VaultStorage>> -   capitalPaused: bool <<VaultStorage>> -   redeemFeeBps: uint256 <<VaultStorage>> -   vaultBuffer: uint256 <<VaultStorage>> -   autoAllocateThreshold: uint256 <<VaultStorage>> -   rebaseThreshold: uint256 <<VaultStorage>> -   oUSD: OUSD <<VaultStorage>> -   adminImplPosition: bytes32 <<VaultStorage>> -   strategistAddr: address <<VaultStorage>> -   assetDefaultStrategies: mapping(address=>address) <<VaultStorage>> -   maxSupplyDiff: uint256 <<VaultStorage>> -   trusteeAddress: address <<VaultStorage>> -   trusteeFeeBps: uint256 <<VaultStorage>> -   MINT_MINIMUM_UNIT_PRICE: uint256 <<VaultStorage>> -   ousdMetaStrategy: address <<VaultStorage>> -   netOusdMintedForStrategy: int256 <<VaultStorage>> -   netOusdMintForStrategyThreshold: uint256 <<VaultStorage>> -   MIN_UNIT_PRICE_DRIFT: uint256 <<VaultStorage>> -   MAX_UNIT_PRICE_DRIFT: uint256 <<VaultStorage>> -   isMintWhitelistedStrategy: mapping(address=>bool) <<VaultStorage>> -   dripper: address <<VaultStorage>> -   withdrawalQueueMetadata: WithdrawalQueueMetadata <<VaultStorage>> -   withdrawalRequests: mapping(uint256=>WithdrawalRequest) <<VaultStorage>> -   withdrawalClaimDelay: uint256 <<VaultStorage>> -   lastRebase: uint64 <<VaultStorage>> -   dripDuration: uint64 <<VaultStorage>> -   rebasePerSecondMax: uint64 <<VaultStorage>> -   rebasePerSecondTarget: uint64 <<VaultStorage>> - -Private: -    abs(x: int256): uint256 <<VaultCore>> -Internal: -    _governor(): (governorOut: address) <<Governable>> -    _pendingGovernor(): (pendingGovernor: address) <<Governable>> -    _setGovernor(newGovernor: address) <<Governable>> -    _setPendingGovernor(newGovernor: address) <<Governable>> -    _changeGovernor(_newGovernor: address) <<Governable>> -    _mint(_asset: address, _amount: uint256, _minimumOusdAmount: uint256) <<VaultCore>> -    _redeem(_amount: uint256, _minimumUnitAmount: uint256) <<VaultCore>> -    _postRedeem(_amount: uint256) <<VaultCore>> -    _allocate() <<VaultCore>> -    _rebase(): uint256 <<whenNotRebasePaused>> <<VaultCore>> -    _nextYield(supply: uint256, vaultValue: uint256, nonRebasing: uint256): (yield: uint256, targetRate: uint256) <<VaultCore>> -    _totalValue(): (value: uint256) <<VaultCore>> -    _totalValueInVault(): (value: uint256) <<VaultCore>> -    _totalValueInStrategies(): (value: uint256) <<VaultCore>> -    _totalValueInStrategy(_strategyAddr: address): (value: uint256) <<VaultCore>> -    _checkBalance(_asset: address): (balance: uint256) <<VaultCore>> -    _calculateRedeemOutputs(_amount: uint256): (outputs: uint256[]) <<VaultCore>> -    _toUnits(_raw: uint256, _asset: address): uint256 <<VaultCore>> -    _toUnitPrice(_asset: address, isMint: bool): (price: uint256) <<VaultCore>> -    _getDecimals(_asset: address): (decimals: uint256) <<VaultCore>> -    _min(a: uint256, b: uint256): uint256 <<VaultCore>> -    _max(a: uint256, b: uint256): uint256 <<VaultCore>> -External: -    transferGovernance(_newGovernor: address) <<onlyGovernor>> <<Governable>> -    claimGovernance() <<Governable>> -    setAdminImpl(newImpl: address) <<onlyGovernor>> <<VaultStorage>> -    initialize(_priceProvider: address, _oToken: address) <<onlyGovernor, initializer>> <<VaultInitializer>> -    mint(_asset: address, _amount: uint256, _minimumOusdAmount: uint256) <<whenNotCapitalPaused, nonReentrant>> <<VaultCore>> -    mintForStrategy(_amount: uint256) <<whenNotCapitalPaused, onlyOusdMetaStrategy>> <<VaultCore>> -    redeem(_amount: uint256, _minimumUnitAmount: uint256) <<whenNotCapitalPaused, nonReentrant>> <<VaultCore>> -    burnForStrategy(_amount: uint256) <<whenNotCapitalPaused, onlyOusdMetaStrategy>> <<VaultCore>> -    redeemAll(_minimumUnitAmount: uint256) <<whenNotCapitalPaused, nonReentrant>> <<VaultCore>> -    allocate() <<whenNotCapitalPaused, nonReentrant>> <<VaultCore>> -    rebase() <<nonReentrant>> <<VaultCore>> -    previewYield(): (yield: uint256) <<VaultCore>> -    totalValue(): (value: uint256) <<VaultCore>> -    checkBalance(_asset: address): uint256 <<VaultCore>> -    calculateRedeemOutputs(_amount: uint256): uint256[] <<VaultCore>> -    priceUnitMint(asset: address): (price: uint256) <<VaultCore>> -    priceUnitRedeem(asset: address): (price: uint256) <<VaultCore>> -    getAllAssets(): address[] <<VaultCore>> -    getStrategyCount(): uint256 <<VaultCore>> -    getAllStrategies(): address[] <<VaultCore>> -    isSupportedAsset(_asset: address): bool <<VaultCore>> -    ADMIN_IMPLEMENTATION(): (adminImpl: address) <<VaultCore>> -    null() <<VaultCore>> -Public: -    <<event>> PendingGovernorshipTransfer(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> GovernorshipTransferred(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> AssetSupported(_asset: address) <<VaultStorage>> -    <<event>> AssetRemoved(_asset: address) <<VaultStorage>> -    <<event>> AssetDefaultStrategyUpdated(_asset: address, _strategy: address) <<VaultStorage>> -    <<event>> AssetAllocated(_asset: address, _strategy: address, _amount: uint256) <<VaultStorage>> -    <<event>> StrategyApproved(_addr: address) <<VaultStorage>> -    <<event>> StrategyRemoved(_addr: address) <<VaultStorage>> -    <<event>> Mint(_addr: address, _value: uint256) <<VaultStorage>> -    <<event>> Redeem(_addr: address, _value: uint256) <<VaultStorage>> -    <<event>> CapitalPaused() <<VaultStorage>> -    <<event>> CapitalUnpaused() <<VaultStorage>> -    <<event>> RebasePaused() <<VaultStorage>> -    <<event>> RebaseUnpaused() <<VaultStorage>> -    <<event>> VaultBufferUpdated(_vaultBuffer: uint256) <<VaultStorage>> -    <<event>> OusdMetaStrategyUpdated(_ousdMetaStrategy: address) <<VaultStorage>> -    <<event>> RedeemFeeUpdated(_redeemFeeBps: uint256) <<VaultStorage>> -    <<event>> PriceProviderUpdated(_priceProvider: address) <<VaultStorage>> -    <<event>> AllocateThresholdUpdated(_threshold: uint256) <<VaultStorage>> -    <<event>> RebaseThresholdUpdated(_threshold: uint256) <<VaultStorage>> -    <<event>> StrategistUpdated(_address: address) <<VaultStorage>> -    <<event>> MaxSupplyDiffChanged(maxSupplyDiff: uint256) <<VaultStorage>> -    <<event>> YieldDistribution(_to: address, _yield: uint256, _fee: uint256) <<VaultStorage>> -    <<event>> TrusteeFeeBpsChanged(_basis: uint256) <<VaultStorage>> -    <<event>> TrusteeAddressChanged(_address: address) <<VaultStorage>> -    <<event>> NetOusdMintForStrategyThresholdChanged(_threshold: uint256) <<VaultStorage>> -    <<event>> SwapperChanged(_address: address) <<VaultStorage>> -    <<event>> SwapAllowedUndervalueChanged(_basis: uint256) <<VaultStorage>> -    <<event>> SwapSlippageChanged(_asset: address, _basis: uint256) <<VaultStorage>> -    <<event>> Swapped(_fromAsset: address, _toAsset: address, _fromAssetAmount: uint256, _toAssetAmount: uint256) <<VaultStorage>> -    <<event>> StrategyAddedToMintWhitelist(strategy: address) <<VaultStorage>> -    <<event>> StrategyRemovedFromMintWhitelist(strategy: address) <<VaultStorage>> -    <<event>> DripperChanged(_dripper: address) <<VaultStorage>> -    <<event>> RebasePerSecondMaxChanged(rebaseRatePerSecond: uint256) <<VaultStorage>> -    <<event>> DripDurationChanged(dripDuration: uint256) <<VaultStorage>> -    <<event>> WithdrawalRequested(_withdrawer: address, _requestId: uint256, _amount: uint256, _queued: uint256) <<VaultStorage>> -    <<event>> WithdrawalClaimed(_withdrawer: address, _requestId: uint256, _amount: uint256) <<VaultStorage>> -    <<event>> WithdrawalClaimable(_claimable: uint256, _newClaimable: uint256) <<VaultStorage>> -    <<event>> WithdrawalClaimDelayUpdated(_newDelay: uint256) <<VaultStorage>> -    <<modifier>> initializer() <<Initializable>> -    <<modifier>> onlyGovernor() <<Governable>> -    <<modifier>> nonReentrant() <<Governable>> -    <<modifier>> whenNotRebasePaused() <<VaultCore>> -    <<modifier>> whenNotCapitalPaused() <<VaultCore>> -    <<modifier>> onlyOusdMetaStrategy() <<VaultCore>> -    constructor() <<Governable>> -    governor(): address <<Governable>> -    isGovernor(): bool <<Governable>> -    getAssetCount(): uint256 <<VaultCore>> -    getAssetConfig(_asset: address): (config: Asset) <<VaultCore>> - - - diff --git a/contracts/docs/VaultHierarchy.svg b/contracts/docs/VaultHierarchy.svg index 2954412d6e..d7d69bf0d9 100644 --- a/contracts/docs/VaultHierarchy.svg +++ b/contracts/docs/VaultHierarchy.svg @@ -4,180 +4,159 @@ - - + + UmlClassDiagram - - + + -21 - -Governable -../contracts/governance/Governable.sol +44 + +<<Abstract>> +Governable +../contracts/governance/Governable.sol - + -261 - -OUSD -../contracts/token/OUSD.sol +307 + +OUSD +../contracts/token/OUSD.sol - + -261->21 - - +307->44 + + - + -277 - -<<Abstract>> -Initializable -../contracts/utils/Initializable.sol +324 + +<<Abstract>> +Initializable +../contracts/utils/Initializable.sol - + -282 - -OETHBaseVaultAdmin -../contracts/vault/OETHBaseVaultAdmin.sol +330 + +OETHBaseVault +../contracts/vault/OETHBaseVault.sol - - -286 - -OETHVaultAdmin -../contracts/vault/OETHVaultAdmin.sol + + +335 + +<<Abstract>> +VaultAdmin +../contracts/vault/VaultAdmin.sol - + -282->286 - - +330->335 + + - + -283 - -OETHBaseVaultCore -../contracts/vault/OETHBaseVaultCore.sol +332 + +OETHVault +../contracts/vault/OETHVault.sol - - -287 - -OETHVaultCore -../contracts/vault/OETHVaultCore.sol - - + -283->287 - - +332->335 + + - - -293 - -VaultAdmin -../contracts/vault/VaultAdmin.sol + + +333 + +OSonicVault +../contracts/vault/OSonicVault.sol - + -286->293 - - +333->335 + + - - -294 - -VaultCore -../contracts/vault/VaultCore.sol + + +334 + +OUSDVault +../contracts/vault/OUSDVault.sol - + -287->294 - - +334->335 + + - - -289 - -OSonicVaultAdmin -../contracts/vault/OSonicVaultAdmin.sol + + +336 + +<<Abstract>> +VaultCore +../contracts/vault/VaultCore.sol - + -289->286 - - +335->336 + + - - -290 - -OSonicVaultCore -../contracts/vault/OSonicVaultCore.sol + + +337 + +<<Abstract>> +VaultInitializer +../contracts/vault/VaultInitializer.sol - + -290->287 - - - - - -296 - -VaultStorage -../contracts/vault/VaultStorage.sol - - +336->337 + + + + + +338 + +<<Abstract>> +VaultStorage +../contracts/vault/VaultStorage.sol + + -293->296 - - - - - -295 - -VaultInitializer -../contracts/vault/VaultInitializer.sol - - - -294->295 - - +337->338 + + - + -295->296 - - - - - -296->21 - - - - - -296->261 - - - - +338->44 + + + + -296->277 - - +338->307 + + + + + +338->324 + + diff --git a/contracts/docs/VaultAdminSquashed.svg b/contracts/docs/VaultSquashed.svg similarity index 70% rename from contracts/docs/VaultAdminSquashed.svg rename to contracts/docs/VaultSquashed.svg index 221eefa703..06204eb0e2 100644 --- a/contracts/docs/VaultAdminSquashed.svg +++ b/contracts/docs/VaultSquashed.svg @@ -4,169 +4,177 @@ - - + + UmlClassDiagram - - + + -301 - -VaultAdmin -../contracts/vault/VaultAdmin.sol - -Private: -   initialized: bool <<Initializable>> -   initializing: bool <<Initializable>> -   ______gap: uint256[50] <<Initializable>> -   governorPosition: bytes32 <<Governable>> -   pendingGovernorPosition: bytes32 <<Governable>> -   reentryStatusPosition: bytes32 <<Governable>> -   _deprecated_rebaseHooksAddr: address <<VaultStorage>> -   _deprecated_uniswapAddr: address <<VaultStorage>> -   _deprecated_swapTokens: address[] <<VaultStorage>> -   __gap: uint256[43] <<VaultStorage>> -Internal: -   assets: mapping(address=>Asset) <<VaultStorage>> -   allAssets: address[] <<VaultStorage>> -   allStrategies: address[] <<VaultStorage>> -   swapConfig: SwapConfig <<VaultStorage>> +334 + +OUSDVault +../contracts/vault/OUSDVault.sol + +Private: +   initialized: bool <<Initializable>> +   initializing: bool <<Initializable>> +   ______gap: uint256[50] <<Initializable>> +   governorPosition: bytes32 <<Governable>> +   pendingGovernorPosition: bytes32 <<Governable>> +   reentryStatusPosition: bytes32 <<Governable>> +   _deprecated_assets: uint256 <<VaultStorage>> +   _deprecated_allAssets: address[] <<VaultStorage>> +   _deprecated_priceProvider: address <<VaultStorage>> +   _deprecated_redeemFeeBps: uint256 <<VaultStorage>> +   _deprecated_rebaseHooksAddr: address <<VaultStorage>> +   _deprecated_uniswapAddr: address <<VaultStorage>> +   _deprecated_assetDefaultStrategies: uint256 <<VaultStorage>> +   _deprecated_swapTokens: address[] <<VaultStorage>> +   _deprecated_ousdMetaStrategy: address <<VaultStorage>> +   _deprecated_netOusdMintedForStrategy: int256 <<VaultStorage>> +   _deprecated_netOusdMintForStrategyThreshold: uint256 <<VaultStorage>> +   _deprecated_swapConfig: uint256 <<VaultStorage>> +   _deprecated_dripper: address <<VaultStorage>> +   __gap: uint256[42] <<VaultStorage>> +   _deprecated_wethAssetIndex: uint256 <<VaultStorage>> +Internal: +   allStrategies: address[] <<VaultStorage>>   MAX_REBASE: uint256 <<VaultStorage>>   MAX_REBASE_PER_SECOND: uint256 <<VaultStorage>> -Public: -   _NOT_ENTERED: uint256 <<Governable>> -   _ENTERED: uint256 <<Governable>> -   strategies: mapping(address=>Strategy) <<VaultStorage>> -   priceProvider: address <<VaultStorage>> +   assetDecimals: uint8 <<VaultStorage>> +Public: +   _NOT_ENTERED: uint256 <<Governable>> +   _ENTERED: uint256 <<Governable>> +   strategies: mapping(address=>Strategy) <<VaultStorage>>   rebasePaused: bool <<VaultStorage>>   capitalPaused: bool <<VaultStorage>> -   redeemFeeBps: uint256 <<VaultStorage>> -   vaultBuffer: uint256 <<VaultStorage>> -   autoAllocateThreshold: uint256 <<VaultStorage>> -   rebaseThreshold: uint256 <<VaultStorage>> -   oUSD: OUSD <<VaultStorage>> -   adminImplPosition: bytes32 <<VaultStorage>> -   strategistAddr: address <<VaultStorage>> -   assetDefaultStrategies: mapping(address=>address) <<VaultStorage>> -   maxSupplyDiff: uint256 <<VaultStorage>> -   trusteeAddress: address <<VaultStorage>> -   trusteeFeeBps: uint256 <<VaultStorage>> -   MINT_MINIMUM_UNIT_PRICE: uint256 <<VaultStorage>> -   ousdMetaStrategy: address <<VaultStorage>> -   netOusdMintedForStrategy: int256 <<VaultStorage>> -   netOusdMintForStrategyThreshold: uint256 <<VaultStorage>> -   MIN_UNIT_PRICE_DRIFT: uint256 <<VaultStorage>> -   MAX_UNIT_PRICE_DRIFT: uint256 <<VaultStorage>> -   isMintWhitelistedStrategy: mapping(address=>bool) <<VaultStorage>> -   dripper: address <<VaultStorage>> -   withdrawalQueueMetadata: WithdrawalQueueMetadata <<VaultStorage>> -   withdrawalRequests: mapping(uint256=>WithdrawalRequest) <<VaultStorage>> -   withdrawalClaimDelay: uint256 <<VaultStorage>> -   lastRebase: uint64 <<VaultStorage>> -   dripDuration: uint64 <<VaultStorage>> -   rebasePerSecondMax: uint64 <<VaultStorage>> -   rebasePerSecondTarget: uint64 <<VaultStorage>> - -Internal: -    _governor(): (governorOut: address) <<Governable>> -    _pendingGovernor(): (pendingGovernor: address) <<Governable>> -    _setGovernor(newGovernor: address) <<Governable>> -    _setPendingGovernor(newGovernor: address) <<Governable>> -    _changeGovernor(_newGovernor: address) <<Governable>> -    _swapCollateral(_fromAsset: address, _toAsset: address, _fromAssetAmount: uint256, _minToAssetAmount: uint256, _data: bytes): (toAssetAmount: uint256) <<VaultAdmin>> -    _depositToStrategy(_strategyToAddress: address, _assets: address[], _amounts: uint256[]) <<VaultAdmin>> -    _withdrawFromStrategy(_recipient: address, _strategyFromAddress: address, _assets: address[], _amounts: uint256[]) <<VaultAdmin>> -    _withdrawAllFromStrategy(_strategyAddr: address) <<VaultAdmin>> -    _withdrawAllFromStrategies() <<VaultAdmin>> -    _cacheDecimals(token: address) <<VaultAdmin>> -External: -    transferGovernance(_newGovernor: address) <<onlyGovernor>> <<Governable>> -    claimGovernance() <<Governable>> -    setAdminImpl(newImpl: address) <<onlyGovernor>> <<VaultStorage>> -    setPriceProvider(_priceProvider: address) <<onlyGovernor>> <<VaultAdmin>> -    setRedeemFeeBps(_redeemFeeBps: uint256) <<onlyGovernor>> <<VaultAdmin>> -    setVaultBuffer(_vaultBuffer: uint256) <<onlyGovernorOrStrategist>> <<VaultAdmin>> -    setAutoAllocateThreshold(_threshold: uint256) <<onlyGovernor>> <<VaultAdmin>> -    setRebaseThreshold(_threshold: uint256) <<onlyGovernor>> <<VaultAdmin>> -    setStrategistAddr(_address: address) <<onlyGovernor>> <<VaultAdmin>> -    setAssetDefaultStrategy(_asset: address, _strategy: address) <<onlyGovernorOrStrategist>> <<VaultAdmin>> -    setNetOusdMintForStrategyThreshold(_threshold: uint256) <<onlyGovernor>> <<VaultAdmin>> -    setDripper(_dripper: address) <<onlyGovernor>> <<VaultAdmin>> -    setWithdrawalClaimDelay(_delay: uint256) <<onlyGovernor>> <<VaultAdmin>> -    setRebaseRateMax(yearlyApr: uint256) <<onlyGovernorOrStrategist>> <<VaultAdmin>> -    setDripDuration(_dripDuration: uint256) <<onlyGovernorOrStrategist>> <<VaultAdmin>> -    swapCollateral(_fromAsset: address, _toAsset: address, _fromAssetAmount: uint256, _minToAssetAmount: uint256, _data: bytes): (toAssetAmount: uint256) <<nonReentrant, onlyGovernorOrStrategist>> <<VaultAdmin>> -    setSwapper(_swapperAddr: address) <<onlyGovernor>> <<VaultAdmin>> -    swapper(): (swapper_: address) <<VaultAdmin>> -    setSwapAllowedUndervalue(_basis: uint16) <<onlyGovernor>> <<VaultAdmin>> -    allowedSwapUndervalue(): (value: uint256) <<VaultAdmin>> -    setOracleSlippage(_asset: address, _allowedOracleSlippageBps: uint16) <<onlyGovernor>> <<VaultAdmin>> -    supportAsset(_asset: address, _unitConversion: uint8) <<onlyGovernor>> <<VaultAdmin>> -    removeAsset(_asset: address) <<onlyGovernor>> <<VaultAdmin>> -    cacheDecimals(_asset: address) <<onlyGovernor>> <<VaultAdmin>> -    approveStrategy(_addr: address) <<onlyGovernor>> <<VaultAdmin>> -    removeStrategy(_addr: address) <<onlyGovernor>> <<VaultAdmin>> -    depositToStrategy(_strategyToAddress: address, _assets: address[], _amounts: uint256[]) <<onlyGovernorOrStrategist, nonReentrant>> <<VaultAdmin>> -    withdrawFromStrategy(_strategyFromAddress: address, _assets: address[], _amounts: uint256[]) <<onlyGovernorOrStrategist, nonReentrant>> <<VaultAdmin>> -    setMaxSupplyDiff(_maxSupplyDiff: uint256) <<onlyGovernor>> <<VaultAdmin>> -    setTrusteeAddress(_address: address) <<onlyGovernor>> <<VaultAdmin>> -    setTrusteeFeeBps(_basis: uint256) <<onlyGovernor>> <<VaultAdmin>> -    setOusdMetaStrategy(_ousdMetaStrategy: address) <<onlyGovernor>> <<VaultAdmin>> -    pauseRebase() <<onlyGovernorOrStrategist>> <<VaultAdmin>> -    unpauseRebase() <<onlyGovernorOrStrategist>> <<VaultAdmin>> -    pauseCapital() <<onlyGovernorOrStrategist>> <<VaultAdmin>> -    unpauseCapital() <<onlyGovernorOrStrategist>> <<VaultAdmin>> -    transferToken(_asset: address, _amount: uint256) <<onlyGovernor>> <<VaultAdmin>> -    withdrawAllFromStrategy(_strategyAddr: address) <<onlyGovernorOrStrategist>> <<VaultAdmin>> -    withdrawAllFromStrategies() <<onlyGovernorOrStrategist>> <<VaultAdmin>> -Public: -    <<event>> PendingGovernorshipTransfer(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> GovernorshipTransferred(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> AssetSupported(_asset: address) <<VaultStorage>> -    <<event>> AssetRemoved(_asset: address) <<VaultStorage>> -    <<event>> AssetDefaultStrategyUpdated(_asset: address, _strategy: address) <<VaultStorage>> -    <<event>> AssetAllocated(_asset: address, _strategy: address, _amount: uint256) <<VaultStorage>> -    <<event>> StrategyApproved(_addr: address) <<VaultStorage>> -    <<event>> StrategyRemoved(_addr: address) <<VaultStorage>> -    <<event>> Mint(_addr: address, _value: uint256) <<VaultStorage>> -    <<event>> Redeem(_addr: address, _value: uint256) <<VaultStorage>> -    <<event>> CapitalPaused() <<VaultStorage>> -    <<event>> CapitalUnpaused() <<VaultStorage>> -    <<event>> RebasePaused() <<VaultStorage>> -    <<event>> RebaseUnpaused() <<VaultStorage>> -    <<event>> VaultBufferUpdated(_vaultBuffer: uint256) <<VaultStorage>> -    <<event>> OusdMetaStrategyUpdated(_ousdMetaStrategy: address) <<VaultStorage>> -    <<event>> RedeemFeeUpdated(_redeemFeeBps: uint256) <<VaultStorage>> -    <<event>> PriceProviderUpdated(_priceProvider: address) <<VaultStorage>> -    <<event>> AllocateThresholdUpdated(_threshold: uint256) <<VaultStorage>> -    <<event>> RebaseThresholdUpdated(_threshold: uint256) <<VaultStorage>> -    <<event>> StrategistUpdated(_address: address) <<VaultStorage>> -    <<event>> MaxSupplyDiffChanged(maxSupplyDiff: uint256) <<VaultStorage>> -    <<event>> YieldDistribution(_to: address, _yield: uint256, _fee: uint256) <<VaultStorage>> -    <<event>> TrusteeFeeBpsChanged(_basis: uint256) <<VaultStorage>> -    <<event>> TrusteeAddressChanged(_address: address) <<VaultStorage>> -    <<event>> NetOusdMintForStrategyThresholdChanged(_threshold: uint256) <<VaultStorage>> -    <<event>> SwapperChanged(_address: address) <<VaultStorage>> -    <<event>> SwapAllowedUndervalueChanged(_basis: uint256) <<VaultStorage>> -    <<event>> SwapSlippageChanged(_asset: address, _basis: uint256) <<VaultStorage>> -    <<event>> Swapped(_fromAsset: address, _toAsset: address, _fromAssetAmount: uint256, _toAssetAmount: uint256) <<VaultStorage>> -    <<event>> StrategyAddedToMintWhitelist(strategy: address) <<VaultStorage>> -    <<event>> StrategyRemovedFromMintWhitelist(strategy: address) <<VaultStorage>> -    <<event>> DripperChanged(_dripper: address) <<VaultStorage>> -    <<event>> RebasePerSecondMaxChanged(rebaseRatePerSecond: uint256) <<VaultStorage>> -    <<event>> DripDurationChanged(dripDuration: uint256) <<VaultStorage>> -    <<event>> WithdrawalRequested(_withdrawer: address, _requestId: uint256, _amount: uint256, _queued: uint256) <<VaultStorage>> -    <<event>> WithdrawalClaimed(_withdrawer: address, _requestId: uint256, _amount: uint256) <<VaultStorage>> -    <<event>> WithdrawalClaimable(_claimable: uint256, _newClaimable: uint256) <<VaultStorage>> -    <<event>> WithdrawalClaimDelayUpdated(_newDelay: uint256) <<VaultStorage>> -    <<modifier>> initializer() <<Initializable>> -    <<modifier>> onlyGovernor() <<Governable>> -    <<modifier>> nonReentrant() <<Governable>> -    <<modifier>> onlyGovernorOrStrategist() <<VaultAdmin>> -    constructor() <<Governable>> -    governor(): address <<Governable>> -    isGovernor(): bool <<Governable>> +   vaultBuffer: uint256 <<VaultStorage>> +   autoAllocateThreshold: uint256 <<VaultStorage>> +   rebaseThreshold: uint256 <<VaultStorage>> +   oUSD: OUSD <<VaultStorage>> +   strategistAddr: address <<VaultStorage>> +   maxSupplyDiff: uint256 <<VaultStorage>> +   trusteeAddress: address <<VaultStorage>> +   trusteeFeeBps: uint256 <<VaultStorage>> +   isMintWhitelistedStrategy: mapping(address=>bool) <<VaultStorage>> +   withdrawalQueueMetadata: WithdrawalQueueMetadata <<VaultStorage>> +   withdrawalRequests: mapping(uint256=>WithdrawalRequest) <<VaultStorage>> +   withdrawalClaimDelay: uint256 <<VaultStorage>> +   lastRebase: uint64 <<VaultStorage>> +   dripDuration: uint64 <<VaultStorage>> +   rebasePerSecondMax: uint64 <<VaultStorage>> +   rebasePerSecondTarget: uint64 <<VaultStorage>> +   defaultStrategy: address <<VaultStorage>> +   asset: address <<VaultStorage>> + +Internal: +    _governor(): (governorOut: address) <<Governable>> +    _pendingGovernor(): (pendingGovernor: address) <<Governable>> +    _setGovernor(newGovernor: address) <<Governable>> +    _setPendingGovernor(newGovernor: address) <<Governable>> +    _changeGovernor(_newGovernor: address) <<Governable>> +    _mint(_amount: uint256) <<VaultCore>> +    _claimWithdrawal(requestId: uint256): (amount: uint256) <<VaultCore>> +    _postRedeem(_amount: uint256) <<VaultCore>> +    _allocate() <<VaultCore>> +    _rebase(): uint256 <<whenNotRebasePaused>> <<VaultCore>> +    _nextYield(supply: uint256, vaultValue: uint256): (yield: uint256, targetRate: uint256) <<VaultCore>> +    _totalValue(): (value: uint256) <<VaultCore>> +    _totalValueInVault(): (value: uint256) <<VaultCore>> +    _checkBalance(_asset: address): (balance: uint256) <<VaultCore>> +    _addWithdrawalQueueLiquidity(): (addedClaimable: uint256) <<VaultCore>> +    _assetAvailable(): (assetAvailable: uint256) <<VaultCore>> +    _min(a: uint256, b: uint256): uint256 <<VaultCore>> +    _max(a: uint256, b: uint256): uint256 <<VaultCore>> +    _depositToStrategy(_strategyToAddress: address, _assets: address[], _amounts: uint256[]) <<VaultAdmin>> +    _withdrawFromStrategy(_recipient: address, _strategyFromAddress: address, _assets: address[], _amounts: uint256[]) <<VaultAdmin>> +    _withdrawAllFromStrategy(_strategyAddr: address) <<VaultAdmin>> +    _withdrawAllFromStrategies() <<VaultAdmin>> +External: +    transferGovernance(_newGovernor: address) <<onlyGovernor>> <<Governable>> +    claimGovernance() <<Governable>> +    initialize(_oToken: address) <<onlyGovernor, initializer>> <<VaultInitializer>> +    mint(address, _amount: uint256, uint256) <<whenNotCapitalPaused, nonReentrant>> <<VaultCore>> +    mint(_amount: uint256) <<whenNotCapitalPaused, nonReentrant>> <<VaultCore>> +    mintForStrategy(_amount: uint256) <<whenNotCapitalPaused>> <<VaultCore>> +    burnForStrategy(_amount: uint256) <<whenNotCapitalPaused>> <<VaultCore>> +    requestWithdrawal(_amount: uint256): (requestId: uint256, queued: uint256) <<whenNotCapitalPaused, nonReentrant>> <<VaultCore>> +    claimWithdrawal(_requestId: uint256): (amount: uint256) <<whenNotCapitalPaused, nonReentrant>> <<VaultCore>> +    claimWithdrawals(_requestIds: uint256[]): (amounts: uint256[], totalAmount: uint256) <<whenNotCapitalPaused, nonReentrant>> <<VaultCore>> +    allocate() <<whenNotCapitalPaused, nonReentrant>> <<VaultCore>> +    rebase() <<nonReentrant>> <<VaultCore>> +    previewYield(): (yield: uint256) <<VaultCore>> +    totalValue(): (value: uint256) <<VaultCore>> +    checkBalance(_asset: address): uint256 <<VaultCore>> +    addWithdrawalQueueLiquidity() <<VaultCore>> +    getAllAssets(): address[] <<VaultCore>> +    getStrategyCount(): uint256 <<VaultCore>> +    getAllStrategies(): address[] <<VaultCore>> +    isSupportedAsset(_asset: address): bool <<VaultCore>> +    setVaultBuffer(_vaultBuffer: uint256) <<onlyGovernorOrStrategist>> <<VaultAdmin>> +    setAutoAllocateThreshold(_threshold: uint256) <<onlyGovernor>> <<VaultAdmin>> +    setRebaseThreshold(_threshold: uint256) <<onlyGovernor>> <<VaultAdmin>> +    setStrategistAddr(_address: address) <<onlyGovernor>> <<VaultAdmin>> +    setDefaultStrategy(_strategy: address) <<onlyGovernorOrStrategist>> <<VaultAdmin>> +    setWithdrawalClaimDelay(_delay: uint256) <<onlyGovernor>> <<VaultAdmin>> +    setRebaseRateMax(yearlyApr: uint256) <<onlyGovernorOrStrategist>> <<VaultAdmin>> +    setDripDuration(_dripDuration: uint256) <<onlyGovernorOrStrategist>> <<VaultAdmin>> +    approveStrategy(_addr: address) <<onlyGovernor>> <<VaultAdmin>> +    removeStrategy(_addr: address) <<onlyGovernor>> <<VaultAdmin>> +    addStrategyToMintWhitelist(strategyAddr: address) <<onlyGovernor>> <<VaultAdmin>> +    removeStrategyFromMintWhitelist(strategyAddr: address) <<onlyGovernor>> <<VaultAdmin>> +    depositToStrategy(_strategyToAddress: address, _assets: address[], _amounts: uint256[]) <<onlyGovernorOrStrategist, nonReentrant>> <<VaultAdmin>> +    withdrawFromStrategy(_strategyFromAddress: address, _assets: address[], _amounts: uint256[]) <<onlyGovernorOrStrategist, nonReentrant>> <<VaultAdmin>> +    setMaxSupplyDiff(_maxSupplyDiff: uint256) <<onlyGovernor>> <<VaultAdmin>> +    setTrusteeAddress(_address: address) <<onlyGovernor>> <<VaultAdmin>> +    setTrusteeFeeBps(_basis: uint256) <<onlyGovernor>> <<VaultAdmin>> +    pauseRebase() <<onlyGovernorOrStrategist>> <<VaultAdmin>> +    unpauseRebase() <<onlyGovernorOrStrategist>> <<VaultAdmin>> +    pauseCapital() <<onlyGovernorOrStrategist>> <<VaultAdmin>> +    unpauseCapital() <<onlyGovernorOrStrategist>> <<VaultAdmin>> +    transferToken(_asset: address, _amount: uint256) <<onlyGovernor>> <<VaultAdmin>> +    withdrawAllFromStrategy(_strategyAddr: address) <<onlyGovernorOrStrategist>> <<VaultAdmin>> +    withdrawAllFromStrategies() <<onlyGovernorOrStrategist>> <<VaultAdmin>> +Public: +    <<event>> PendingGovernorshipTransfer(previousGovernor: address, newGovernor: address) <<Governable>> +    <<event>> GovernorshipTransferred(previousGovernor: address, newGovernor: address) <<Governable>> +    <<event>> AssetAllocated(_asset: address, _strategy: address, _amount: uint256) <<VaultStorage>> +    <<event>> StrategyApproved(_addr: address) <<VaultStorage>> +    <<event>> StrategyRemoved(_addr: address) <<VaultStorage>> +    <<event>> Mint(_addr: address, _value: uint256) <<VaultStorage>> +    <<event>> Redeem(_addr: address, _value: uint256) <<VaultStorage>> +    <<event>> CapitalPaused() <<VaultStorage>> +    <<event>> CapitalUnpaused() <<VaultStorage>> +    <<event>> DefaultStrategyUpdated(_strategy: address) <<VaultStorage>> +    <<event>> RebasePaused() <<VaultStorage>> +    <<event>> RebaseUnpaused() <<VaultStorage>> +    <<event>> VaultBufferUpdated(_vaultBuffer: uint256) <<VaultStorage>> +    <<event>> AllocateThresholdUpdated(_threshold: uint256) <<VaultStorage>> +    <<event>> RebaseThresholdUpdated(_threshold: uint256) <<VaultStorage>> +    <<event>> StrategistUpdated(_address: address) <<VaultStorage>> +    <<event>> MaxSupplyDiffChanged(maxSupplyDiff: uint256) <<VaultStorage>> +    <<event>> YieldDistribution(_to: address, _yield: uint256, _fee: uint256) <<VaultStorage>> +    <<event>> TrusteeFeeBpsChanged(_basis: uint256) <<VaultStorage>> +    <<event>> TrusteeAddressChanged(_address: address) <<VaultStorage>> +    <<event>> StrategyAddedToMintWhitelist(strategy: address) <<VaultStorage>> +    <<event>> StrategyRemovedFromMintWhitelist(strategy: address) <<VaultStorage>> +    <<event>> RebasePerSecondMaxChanged(rebaseRatePerSecond: uint256) <<VaultStorage>> +    <<event>> DripDurationChanged(dripDuration: uint256) <<VaultStorage>> +    <<event>> WithdrawalRequested(_withdrawer: address, _requestId: uint256, _amount: uint256, _queued: uint256) <<VaultStorage>> +    <<event>> WithdrawalClaimed(_withdrawer: address, _requestId: uint256, _amount: uint256) <<VaultStorage>> +    <<event>> WithdrawalClaimable(_claimable: uint256, _newClaimable: uint256) <<VaultStorage>> +    <<event>> WithdrawalClaimDelayUpdated(_newDelay: uint256) <<VaultStorage>> +    <<modifier>> initializer() <<Initializable>> +    <<modifier>> onlyGovernor() <<Governable>> +    <<modifier>> nonReentrant() <<Governable>> +    <<modifier>> whenNotRebasePaused() <<VaultCore>> +    <<modifier>> whenNotCapitalPaused() <<VaultCore>> +    <<modifier>> onlyGovernorOrStrategist() <<VaultAdmin>> +    governor(): address <<Governable>> +    isGovernor(): bool <<Governable>> +    constructor(_usdc: address) <<OUSDVault>> +    getAssetCount(): uint256 <<VaultCore>> diff --git a/contracts/docs/VaultStorage.svg b/contracts/docs/VaultStorage.svg index 0e415f8939..876c965fa2 100644 --- a/contracts/docs/VaultStorage.svg +++ b/contracts/docs/VaultStorage.svg @@ -4,358 +4,316 @@ - - + + StorageDiagram - - + + -8 - -VaultCore <<Contract>> - -slot - -0 - -1-50 - -51 - -52 - -53 - -54 - -55 - -56 - -57 - -58 - -59 - -60 - -61 - -62 - -63 - -64 - -65 - -66 - -67 - -68 - -69 - -70 - -71 - -72 - -73 - -74 - -75-76 - -77 - -78 - -79 - -80-122 - -type: <inherited contract>.variable (bytes) - -unallocated (30) - -bool: Initializable.initializing (1) - -bool: Initializable.initialized (1) - -uint256[50]: Initializable.______gap (1600) - -mapping(address=>Asset): VaultStorage.assets (32) - -address[]: VaultStorage.allAssets (32) - -mapping(address=>Strategy): VaultStorage.strategies (32) - -address[]: VaultStorage.allStrategies (32) - -unallocated (10) - -bool: VaultStorage.capitalPaused (1) - -bool: VaultStorage.rebasePaused (1) - -address: VaultStorage.priceProvider (20) - -uint256: VaultStorage.redeemFeeBps (32) - -uint256: VaultStorage.vaultBuffer (32) - -uint256: VaultStorage.autoAllocateThreshold (32) - -uint256: VaultStorage.rebaseThreshold (32) - -unallocated (12) - -OUSD: VaultStorage.oUSD (20) - -unallocated (12) - -address: VaultStorage._deprecated_rebaseHooksAddr (20) - -unallocated (12) - -address: VaultStorage._deprecated_uniswapAddr (20) - -unallocated (12) - -address: VaultStorage.strategistAddr (20) - -mapping(address=>address): VaultStorage.assetDefaultStrategies (32) - -uint256: VaultStorage.maxSupplyDiff (32) - -unallocated (12) - -address: VaultStorage.trusteeAddress (20) - -uint256: VaultStorage.trusteeFeeBps (32) - -address[]: VaultStorage._deprecated_swapTokens (32) - -unallocated (12) - -address: VaultStorage.ousdMetaStrategy (20) - -int256: VaultStorage.netOusdMintedForStrategy (32) - -uint256: VaultStorage.netOusdMintForStrategyThreshold (32) - -SwapConfig: VaultStorage.swapConfig (32) - -mapping(address=>bool): VaultStorage.isMintWhitelistedStrategy (32) - -unallocated (12) - -address: VaultStorage.dripper (20) - -WithdrawalQueueMetadata: VaultStorage.withdrawalQueueMetadata (64) - -mapping(uint256=>WithdrawalRequest): VaultStorage.withdrawalRequests (32) - -uint256: VaultStorage.withdrawalClaimDelay (32) - -uint64: VaultStorage.rebasePerSecondTarget (8) - -uint64: VaultStorage.rebasePerSecondMax (8) - -uint64: VaultStorage.dripDuration (8) - -uint64: VaultStorage.lastRebase (8) - -uint256[43]: VaultStorage.__gap (1376) +6 + +OUSDVault <<Contract>> + +slot + +0 + +1-50 + +51 + +52 + +53 + +54 + +55 + +56 + +57 + +58 + +59 + +60 + +61 + +62 + +63 + +64 + +65 + +66 + +67 + +68 + +69 + +70 + +71 + +72 + +73 + +74 + +75-76 + +77 + +78 + +79 + +80 + +81-122 + +123 + +type: <inherited contract>.variable (bytes) + +unallocated (30) + +bool: Initializable.initializing (1) + +bool: Initializable.initialized (1) + +uint256[50]: Initializable.______gap (1600) + +uint256: VaultStorage._deprecated_assets (32) + +address[]: VaultStorage._deprecated_allAssets (32) + +mapping(address=>Strategy): VaultStorage.strategies (32) + +address[]: VaultStorage.allStrategies (32) + +unallocated (10) + +bool: VaultStorage.capitalPaused (1) + +bool: VaultStorage.rebasePaused (1) + +address: VaultStorage._deprecated_priceProvider (20) + +uint256: VaultStorage._deprecated_redeemFeeBps (32) + +uint256: VaultStorage.vaultBuffer (32) + +uint256: VaultStorage.autoAllocateThreshold (32) + +uint256: VaultStorage.rebaseThreshold (32) + +unallocated (12) + +OUSD: VaultStorage.oUSD (20) + +unallocated (12) + +address: VaultStorage._deprecated_rebaseHooksAddr (20) + +unallocated (12) + +address: VaultStorage._deprecated_uniswapAddr (20) + +unallocated (12) + +address: VaultStorage.strategistAddr (20) + +uint256: VaultStorage._deprecated_assetDefaultStrategies (32) + +uint256: VaultStorage.maxSupplyDiff (32) + +unallocated (12) + +address: VaultStorage.trusteeAddress (20) + +uint256: VaultStorage.trusteeFeeBps (32) + +address[]: VaultStorage._deprecated_swapTokens (32) + +unallocated (12) + +address: VaultStorage._deprecated_ousdMetaStrategy (20) + +int256: VaultStorage._deprecated_netOusdMintedForStrategy (32) + +uint256: VaultStorage._deprecated_netOusdMintForStrategyThreshold (32) + +uint256: VaultStorage._deprecated_swapConfig (32) + +mapping(address=>bool): VaultStorage.isMintWhitelistedStrategy (32) + +unallocated (12) + +address: VaultStorage._deprecated_dripper (20) + +WithdrawalQueueMetadata: VaultStorage.withdrawalQueueMetadata (64) + +mapping(uint256=>WithdrawalRequest): VaultStorage.withdrawalRequests (32) + +uint256: VaultStorage.withdrawalClaimDelay (32) + +uint64: VaultStorage.rebasePerSecondTarget (8) + +uint64: VaultStorage.rebasePerSecondMax (8) + +uint64: VaultStorage.dripDuration (8) + +uint64: VaultStorage.lastRebase (8) + +unallocated (12) + +address: VaultStorage.defaultStrategy (20) + +uint256[42]: VaultStorage.__gap (1344) + +uint256: VaultStorage._deprecated_wethAssetIndex (32) 1 - -Asset <<Struct>> - -offset - -0 - -type: variable (bytes) - -unallocated (27) - -uint16: allowedOracleSlippageBps (2) - -uint8: decimals (1) - -UnitConversion: unitConversion (1) - -bool: isSupported (1) + +address[]: _deprecated_allAssets <<Array>> +0x46bddb1178e94d7f2892ff5f366840eb658911794f2c3a44c450aa2c505186c1 + +offset + +0 + +type: variable (bytes) + +unallocated (12) + +address (20) - + -8:8->1 - - +6:6->1 + + 2 - -address[]: allAssets <<Array>> -0x46bddb1178e94d7f2892ff5f366840eb658911794f2c3a44c450aa2c505186c1 - -offset - -0 - -type: variable (bytes) - -unallocated (12) - -address (20) + +Strategy <<Struct>> + +offset + +0 + +1 + +type: variable (bytes) + +unallocated (31) + +bool: isSupported (1) + +uint256: _deprecated (32) - + -8:10->2 - - +6:9->2 + + 3 - -Strategy <<Struct>> - -offset - -0 - -1 - -type: variable (bytes) - -unallocated (31) - -bool: isSupported (1) - -uint256: _deprecated (32) + +address[]: allStrategies <<Array>> +0x4a11f94e20a93c79f6ec743a1954ec4fc2c08429ae2122118bf234b2185c81b8 + +offset + +0 + +type: variable (bytes) + +unallocated (12) + +address (20) - + -8:13->3 - - +6:11->3 + + 4 - -address[]: allStrategies <<Array>> -0x4a11f94e20a93c79f6ec743a1954ec4fc2c08429ae2122118bf234b2185c81b8 - -offset - -0 - -type: variable (bytes) - -unallocated (12) - -address (20) + +WithdrawalQueueMetadata <<Struct>> + +slot + +75 + +76 + +type: variable (bytes) + +uint128: claimable (16) + +uint128: queued (16) + +uint128: nextWithdrawalIndex (16) + +uint128: claimed (16) - + -8:15->4 - - +6:38->4 + + 5 - -SwapConfig <<Struct>> - -slot - -72 - -type: variable (bytes) - -unallocated (10) - -uint16: allowedUndervalueBps (2) - -address: swapper (20) + +WithdrawalRequest <<Struct>> + +offset + +0 + +1 + +type: variable (bytes) + +unallocated (6) + +uint40: timestamp (5) + +bool: claimed (1) + +address: withdrawer (20) + +uint128: queued (16) + +uint128: amount (16) - + -8:37->5 - - - - - -6 - -WithdrawalQueueMetadata <<Struct>> - -slot - -75 - -76 - -type: variable (bytes) - -uint128: claimable (16) - -uint128: queued (16) - -uint128: nextWithdrawalIndex (16) - -uint128: claimed (16) - - - -8:44->6 - - - - - -7 - -WithdrawalRequest <<Struct>> - -offset - -0 - -1 - -type: variable (bytes) - -unallocated (6) - -uint40: timestamp (5) - -bool: claimed (1) - -address: withdrawer (20) - -uint128: queued (16) - -uint128: amount (16) - - - -8:50->7 - - +6:44->5 + + diff --git a/contracts/docs/generate.sh b/contracts/docs/generate.sh index f32d8a7601..5af8c5867b 100644 --- a/contracts/docs/generate.sh +++ b/contracts/docs/generate.sh @@ -147,23 +147,19 @@ sol2uml storage .. -c WOSonic -o WOSonicStorage.svg --hideExpand ______gap # contracts/vault -sol2uml .. -v -hv -hf -he -hs -hl -hi -b OETHBaseVaultCore,OETHBaseVaultAdmin,OSonicVaultCore,OSonicVaultAdmin -o VaultHierarchy.svg +sol2uml .. -v -hv -hf -he -hs -hl -hi -i prettier-plugin-solidity -b OUSDVault,OETHVault,OETHBaseVault,OSonicVault -o VaultHierarchy.svg -sol2uml .. -s -d 0 -b VaultCore -o VaultCoreSquashed.svg -sol2uml .. -s -d 0 -b VaultAdmin -o VaultAdminSquashed.svg -sol2uml storage .. -c VaultCore -o VaultStorage.svg --hideExpand __gap,______gap,_deprecated_swapTokens +sol2uml .. -s -d 0 -i prettier-plugin-solidity -b OUSDVault -o VaultSquashed.svg +sol2uml storage .. -i prettier-plugin-solidity -c OUSDVault -o VaultStorage.svg --hideExpand __gap,______gap,_deprecated_swapTokens -sol2uml .. -s -d 0 -b OETHVaultCore -o OETHVaultCoreSquashed.svg -sol2uml .. -s -d 0 -b OETHVaultAdmin -o OETHVaultAdminSquashed.svg -sol2uml storage .. -c OETHVaultCore -o OETHVaultStorage.svg --hideExpand __gap,______gap,_deprecated_swapTokens +# sol2uml .. -s -d 0 -i prettier-plugin-solidity -b OETHVault -o OETHVaultSquashed.svg +# sol2uml storage .. -i prettier-plugin-solidity -c OETHVault -o OETHVaultStorage.svg --hideExpand __gap,______gap,_deprecated_swapTokens -sol2uml .. -s -d 0 -b OETHBaseVaultCore -o OETHBaseVaultCoreSquashed.svg -sol2uml .. -s -d 0 -b OETHBaseVaultAdmin -o OETHBaseVaultAdminSquashed.svg -sol2uml storage .. -c OETHBaseVaultCore -o OETHBaseVaultStorage.svg --hideExpand __gap,______gap +# sol2uml .. -s -d 0 -i prettier-plugin-solidity -b OETHBaseVault -o OETHBaseVaultSquashed.svg +# sol2uml storage .. -i prettier-plugin-solidity -c OETHBaseVault -o OETHBaseVaultStorage.svg --hideExpand __gap,______gap -sol2uml .. -s -d 0 -b OSonicVaultCore -o OSonicVaultCoreSquashed.svg -sol2uml .. -s -d 0 -b OSonicVaultAdmin -o OSonicVaultAdminSquashed.svg -sol2uml storage .. -c OSonicVaultCore -o OSonicVaultStorage.svg --hideExpand __gap,______gap,_deprecated_swapTokens +# sol2uml .. -s -d 0 -i prettier-plugin-solidity -b OSonicVault -o OSonicVaultSquashed.svg +# sol2uml storage .. -i prettier-plugin-solidity -c OSonicVault -o OSonicVaultStorage.svg --hideExpand __gap,______gap,_deprecated_swapTokens # contracts/poolBooster # call stack size exceeded diff --git a/contracts/docs/plantuml/baseContracts.png b/contracts/docs/plantuml/baseContracts.png index 79bdd9924660ded5dcb2897628a485b21d4659aa..c83d6fa1bf3f8677c9588ccc7521c6e1d6fb3bf7 100644 GIT binary patch literal 58803 zcmbrmby!qg-!{x87AoBxL#IPGQbU8Zw31SybfeNRba!`2hcGA%IdpeOcXxdouIsv= z`#HY%JKjG&4?Tw2+r9Q$zc_!f&PCu`MJd!5L@$t#kWgi$B~*}*ke>tpo<4sBT$!}y zd<}ds+DpE(H?sNcVr61#k0fRC$;9@9y@@e}p$o-Fd;8CJ{A_HWtv-CRcd)i%HL|gG z?CT~6h8QtdduRXeb)>(5ah#KPH0PD3r7=9`n+-!#al%>o(nw4(wG!Ri&aI3s9(JYBfTmzRCyxs$Zd6ZbJJNhyk(ZW8!F$ zcrLIe_!~+%W%`{+Tb8KTL%fV^SSA|w%h(^xb&HQOyHV{=UOZ|>){*b@m2N+!W59}4 zOHpw_k49F6V1Qf+i!<<<@%1z4dem$geSh`yPD=!T#c`;8<6}8^uc<+iO)%<Y(4SXS=GPQN1*_WiD8;^PVO3rQt1fqf$rsdLwoH#?@l5LIn;1<#yur3sPeBdt zP=%Nn(dcrI*Yaer;xm_hg?t-%zq8KR2Td7Dw>KbH)SRcDbA-pj;u?E>Jm~jLksVuq zHg>yvED{tqRNdgrkTlg2eJA#=(|<7yg)wgQ^IxQrCdQ$`@}AtLxs~yq>04{u@MoEG zd0t}1t>#XDBiw|?{uW*Oe7B!};CZI$l}z#qfM z4N=8l&(ceEl@6{HB z`{BcfI95Hzt+FsOAtHSITF0F!A-B`U z(wWJi&*Z)|k-LKq^1IV%n-w>?$;rvlm*;z4cRE3NETkbs$^DZGE5{yI}8 zigvH@C{(ZPyo21USf{pBHiof%d}pG3>1wapVBI5yBsF6`ZHT=o*U!PXx8u+kla&!T-itRxaMPM7R1 zJtw1~p;2(x!7$s8gak;CL=xJmhDI*T9OsWlYBA0vd;rI`kFgjn+?lCA0w!|}Ji7OD zPgr|6UDSIIcBQzL-!F1^y*XM`<(08k>!DPn4)(geuDyGf5;TTR%ncpuXlt9SR@SYy zz)gvQ(+dmVZk09nb#=MioQz{*VXboqc_<C9$Dp26AsxMoo3nY% z{I}mUhEO}Xj5)G?Mmnf$2 z4{B_vo4R$!F)%Pxn)X_Z@3v3uL!VPHty9eq4BSE$1s)5O|vy$R~UC;748(O7pe3{Nfw0VSIwo53e+~DmNXv% zCfQ)v7F1(BJ%pRYZB2xSXL{n}J4VS@dmPC#?_N1@$@h8T*K$|XM*rsCUbD@-uukE~ zra`Q*^6}FR!QLbugQ|}a~mDqn~Sxyll}sA zl-Rw_^C5gz-Ef<4Q_4kWv;w2++EKI$kxG>0&I^Ct@Z{UAwby2xXvUr?y&@BgzwJJT z6{r4q3%&&2xZ(ncPvhgwNU>Jbt^!OH9UYwzH`^oIRL9 zzrmiNgqni`#$;eHuOxf{ubYkB8JMKOd4LxO0gC_FW+@{@KuZ8f;g}Zs)-JD&j zQ(j3fyIS-Y3(Hmt<|)#=gMo}vJx;Zzv@^tfMQ+bq58GMw8mQSs$1~&T6qCc*?oP_Q zHkw6C1bASmMP4Q+K^CGYUmI@T&r*VAp6tJ9q_*k$3T*@k0V0Iht< z3@_R_*x8p4yQ1jKc?xMxmXx8D=IXu&9r9~c8h4dQNZ?1m2HQSf9&exv?jzm2%}v`l zI5@B!KG_;y>rb0+_PYNw`I}VSx4smfK!kwt!yo_4E|^*J&s zYNedq^E~6=5_l8BwClXoad*bNnslnkjfZ8J?aILOV#Owy0;|X{IyzbcG4FX*GBdo1 zL{q<(EfaN$8y*>%$p(pC8!32i3FJj4e+wMSGnx*5M%bq)wR1$HRD68U;CH&*uma*%TDc&^#FdW z;8vb!rJy}T4x~*#?iIY=VcC2(=XOZl5lUJ)(_lVKJEfMVNIJQ-4;YGiscynp zIK^hO=#4~p<{1f6{2o&-7a#q%~9{Z(v3S+Ww)mI zA1l4}^*ryrtz58t2BZL=?ZKPlW%B@tv*~2{p%SkyFO%@u`owOHmB^L6AI?=+IlKLa z{7hPc-+uiy5F2i;&XkgQm7gYe(>A)D?H*Tn-du8+_PndM7|mDCQX2%Xo{SrmsTD*v zu2=R8|8&2wC44zkYh%ELCT;)?=t~hm=oA8YRia(J%#Nbk)s`<8$BKRvw!i;bn;gK> zt)|AtRM$-gm-I0{2gZy6%-fN=?q|j!L|m@hB(O5zy%Y6NkyCt2yhOitjea(=v9Yn}H}+w> zfsqTl^Ky;iGHK}kcrV(YDrD{tgiH(Mb%P6f|HL3n5F~$43~3bok0#(O#31 zlF~whEG22M*)FBIqA_!ZnjEX^FYj2okpl5TA8zX@`2_YAAV9x$8Ym>+AVhxpD&2|2 z<7^;9{L+8D@nESVOiqthH9~3ZIlt0DpJ_E?kHyfYxx?+>sK(IM*B6Uf{FuvTll=w< z1}qJTuNE%PtE6qpWaT*R;J>nqR7%>o$ANGVj(&tA8-M$tQh%yj$!^ z9i*Le2rA^lD<<%wC{q7`_;*T3(P%)>%yq1C*c!9t<^1*vtcYql@sPXH3)3m3CX0A^ z&ci&WYi*PwvuP=wD!#bk0wOPqKakb_|IU5y^Wy)VGXLMR>U3&DP79ao^%Y{}x7*s! zuhD@dd*QL6n$a%gU~`_H-jU@Y7smkQv6y>hVm-`}0&8rle}ToA!g2LuA8Qa5=vuG_g%-a#_wr?O1r%II&5& zn2##9`QwC^-o$kx0WkyKE(X4*|3e*t0g_^QnTpGih9fYC-vkNv;i>bY-*ky0bq&Rt z-Bo>6u&S4-$9AwFyJF(Z%=XGldZd(%mSxq%4TVp(uJ))Qm zVvWbu>-=iE=co_W#~Ua~mQM+mwOhamog|3-U`D`D42P-jLNLlhy9(PoH(2XPPiy1lDM3DV9l+LfP)&E{s$?s2Ipc4|NUFXdv^-@3t3pL4> zUM26FY#r7^G4_=p#)}ZIU37RWv>cXv6@y$9nDf(R*moY@YANX(WVrOilka2DI<_J2 zTMt4(y_pq;)laKO1AU}dNmg9KL5}O}@j}oUrdyIT4&Qc)bE1cWj!&L11ys|Fc$uWH z-X16PBYGHrw3vxeef9ljNnEs$#}xhV5mUA^&ld2=U$bq;sMqgzBOGXfSz366#aBS1 zsA)Vp@u#kdAUXFHB?ic!ZOzlfiSMAP1a!U9l9)APRJFy(i1DCE!{u0$J-V6N5zDIe zfiSmt(=41`=(&@YHQG%1rwu>L1-4NqGdaz}%nCQ2A1N%ylk^bORZ=kx3f=B#e zQdvWnWZ?HyYc-IRe{RfF`E=NJcZA@>i=7pNNGI?e{u#q|e27jWfh$(Fh*dC)kcC;~ zmkWhv9)YcTnF2255xnk+Y-v`M$o_IgKP-pje1wV0w&5K_z&!L;j~GCr1N-}!&crZYlzf-#lX z@kZt}V{L8`#URkl?*O}sag?#a>&JNuUJ0KiuYA?jWiWjEN5L*tg;uoaZNydh zCg%c-X4lMB+1WWD&g)Nhgt7Ed?GM1(dv743XST7{e}fd*HkzF#aqVE@bC6Nsy{V@P zA`B&LD|ZQAhG2!J^f7fMEF*5Qqt`5%AznecWwtEazhj6|KPY>S(H-nwsiCM_FwjEd7r zt`ch$X4j!28#^STmiH}RV|bwUSXS6_*oXdB6N0~UEzN1w?dC}I@ww*rf3U@$zO}`9 z?#iL#ok!n|1(r)mtf;DU(`5w$gRINzxOrxNi5xzwSY>-7gC^#B`@#udy^vY|x(5TM zFrdE7QAg_(d~TO{M1e`ro4B1!3$Dby7cK^%C?nK-tYVOa@w*XC73SLQ-A*b)6D8{=f`NRxK!EbZYYQ%ifX7QUI z0|_kljeu{$ZemPN(nut#6mD7)Q^#3JvUQjv$mmM$#NIncKSr|nEo8HAvt@N0J|SKH z*}rc8XHqaGY~bx{LA*E6xBDK;zYL9&^@J8tcgcfX=ZyVRK{1QrrhH=?Ij^qCZ^QHT z4CJ+Y`uhUHFKBhlGrsZAMG^?A%cS7i4Gh{Ai$gIAo`N4?fN*dEA|xG8P?U>#*N)^* z%?Cp(#o|}q#j&LcGU<1|&PHxJf0oy#UDNSHo%PkrrA93?FOAf0KK)RK08FY;z)cL7 z!&gSaND{u#;wL@=Gp22CiszQo5$+w?6HUt>3m=heZ6a=$_|9T<|6edo^16c`{yf40 zkUqG=E5o3FWDc5Y{Z3rO!(y)OFTz8UWsN>-mbeYGTATFnx2l7%+9A)*p;G)ZPF6Fo zMb0r(CX)kn6|@XQLiP8m6Zjb|U3SzlS(Y)(kDxw@K+9B93+gRUdR}NeKAlWG}ACr%oOTgv@Ij@huHau$5(s(rOC<%lLVM-zyNMH zqAo5*+TFhHUEvBmMU;wV=@gpQCb2clH57q{G=$EIOF$=R93 z>m!f;k4RvO|L0gS0k->(*0R_cj_<}=z0<0XK!L%s{unomk$(kpZSE(ZR?T<`?$Ji+ zBE(Q}!Ia$;5Yt#AUFq!eq?W4jytiPLtEi6&ywT=|-u;Di<|Xzh1-7bSc3>u6vX{wf z#c+N70(KxD=#S9~bvRj>Q>rt0vCf)nvk*1|+LEs!A|C~C-Xy_j^}mIc>gs!kUZ${O z!uN#v<_s;NgKcBWJKdn)n6YZe7K_uusBo0=C3q9FN94Hwwgn;Qcn_lE3MqcwdweG{ zUi0NoZr(XDu*?@d!G;1!j%DY5JIm)&-3WqG8~Wq899mgnhw7F3K;3tC(IV16OR0{k z-2(ryJvAR&+Bh$FM4Hqx`EjtnC->l{aHM6Gl#_QbF-T?wcao14fYEJr^}(%*BYm2+Bap7dXHW-J=Y zCGMac5=(h{ZNTr`P?z;W1apr)zFGL=O^|Wx5!VtHS&^WuW}0JxAZ(r%ToJ?wVbn+) zQ1zAyf7%u2VP0qk(W>I)Vdb>r{BF*(Nrv||s^cZ#e0=H-+sNrJiNOlZiFlESAzaqB+ySv#4~_|t z9VJTs@pxWpmljg0LIDX-^CtK!Kv^qh0-G5_T&(^o?v^-;81tr`@J~uq^Rmg3@=b+3 z`S0fB<$SfD?F2%wv~f0(U*AGVFTaGHop;os|9S__FBU9@;t}#rVhBqjl&511$HIK~ zKmnNWPKE!7Q*3d<0^KnHz_`0Rz|Q4tyrMd+M=cn;sSvkY*E{K_WYg-f8a^pvpkeHW^o-o1-QK0eO+(Su>P5FVE#w^}!J6D-3~tDaS-XDa#@6pbO^D=55d}_5E_*w)zn$he zn&WO{a`&=15MRXZTTf*u6{Ix!>5Y48mq)k3^KTxMkdL>@_t@|AeGfDZ?^GX7kK(_2 z{JH;6UzpXD!4_8A&xYGc3JCo@vWtm+kpSB+a;|?UIu9C-Wn!yV6sAf>zQ*5O7(}rT zbff#C@k@-yTu+aT-Rq^3jVO?WS0+YYhI4d0$)byG@ipX)sAL38i@nx(uF*AAlTDd%O|C5#l z9oy^?kkP`Qx7EYxnV2kr?#pp6aY1|C12gH#aR~ zYs)9!PEar@kD^W3t`XM}GWjB7IsW4)>;1 zg26lX3cNynK2dDaO!6{_Vr!Iu!crYSd(ycbEe`{e!IVnZ_qhkh_(N|pTBu%LUJir7 zZf|cD5;-Yp4i63(^q4ASpt=mG{G@UNVOAEHqZNzMt6e4tY$wwyn8|ba5=t%*mTf$YNK18y$|WvGFHWMgDLigYWe*JV?;k zzn4^gru7AInF)l! zy)>ZRtK%;&%_E(ahW?0o{|aJ(NH+cbR>y^-?;ZLXl6asJ?Cui_Jl3V7@bz)a9;J>E z_zRkst`C9gi(Ep^*GloH9M8hQQ!o#==^ASpXk%kzH)VTgZ?GcbgMY?gC>LYNb}J$~ zhI@ZClpY-&t$~g6c~6=5;%|(}z+3{~c&I#cyDzjq*Fm{tpWQYEZ>R3rm;UO^z=$!F zjk%9PvwovT1Dan>rjm2z#w`9>sakQdi3wk8zK{+`iY&%)bEIId$qnIem@SKu#m<-k zrWN7pFi454NXV zs@%|3V`_w2gl%VQ2I2CA~pmAjG z4cRrvzFr5?ToM&1R5glEo8gKOGOG@yCL|>M>#x6ZCTt#qZF~OECzK(08|7n3_l^=_ zMhcF@FCMwwzUZL9rnWApdof0*9k?!g8|meQ*>1G@KU0zK4ywJo^Ava8|MiF~A@-mv zhd`*RAsjok5A%kOe$YX^kRfW3AkE(@u#{?E59Quf(&2_`BZREheBLX=UvA)rZRb?1 zr#SH@BCQ`Y5(dIA8UhS`ta8xe=Z4EyW$k?S*;Af#F#6va4HJIkS$eernB3%KRbM)` zzT-8mX6_gC1Qy9HA2ZvRyZilEDUS^wZ__{|6Bmb0&DmH~qk88$qtM@NG?qImrbrmm zq&=$CRrw_Yu5g|H1cC&B#ln;mf-qmO_da?{0%ja~6<4F=W}cA9Z1EEpe~>@#HarAN z@tYPa!-D-M;&~~I`xNE*6F`!z?fC02*PW(#bBSkY6C3kXB zC3Pd7(n+eCklFCRiDq`-?da=QDqltJuTH$3Q=(<2kPn0j{~9ToIFl z?PslA+F@b?Q&&pj%Csi!8HU#g;U~#`*{SEk-wEz#14PdS3`4bSLe^l;og5Pj_68pl zZBU8brkM!S1t9&jM~Z7T2Oi6ZhH9G^Vl0R@k@JJ{FCz4$`bVs-@jIO*$+`V~JjnNT z>|bkP4~wEwxR;gH5EVDN93atq0q1Z%F(u6Vnl*j8XMA%%9FCz{q)$#xD%*$!3ehiQ z!O^>y#~{&=gsdSqC_B?nA)BX$K0uqOj}20W_r(v9spw`dXTe@#r>!8N(EvzE>qXr# zpDc~#EH^AnRvFsO{GlMEo_`0-7iU75M7bvHS@~WKE-AUbvM#5RM&!U76PNPOkgSxD zKJ^*XPpIzXxKa-fjv7^0#K(pZbR9giRE?Rzb|C&@P%IyPIU~?$e%1k~%N_HtfCz*~ z*o55+d7M1_EOxnf7ANmgm~(TIfcf)9A^Ias7|Vcm;X4AQkhx1TPoVDL(3-e2{hz$2 zlK|vK$6W0ymLg>iJf&7twLz=xwBpN;>U^89up#KU*kVIsLnqL%?auLFAYTczqFqp~ z-(*;Y+UDDCN_jR?NsaQoNBtQ{5Tn(iQbad`nKjglU#L#Yx~vG|ySm&BYx_g3FYWBz zA;dhX{`4P}1Sm7KA@MmZ54@#KOO8uyq5=UWR*xj++Aw2@~>6M_**ytwV7_0n|}H z0`oxS$@|r{*y4buWY3h2NeW&i7AgB1xL(Ee_5wW$3dk>YcxStEhsGI?z*ybn7=Ovi zrHa*d@TGnbr|I-z`0Y|X!_;l*hnohY8B_Uy3QYlHIn+4uXt5wJKS| zCNtU>=443xF()*wy!s`+;O#Du7uai0EgoBo`(?-kOSI++ZcjEx1&G+_L5o9OKF#a+ zl&HkUkuSJSIE%XQajW?BjFX5eh#ap3m@szO9DV~oi-|iV)oM~NGO8IBU$t3u617IY zdsFt_gaWIYYSdy<6IwjSZ3ykA{ZKGgu2C631v2#NF4V?@^HnQsh3T2 z^B-NsVSy0J9k!7d_#&!~6zJhM`y*uG$X&;;_5AjQJxSjM=GweRNuO0~D^LmXR@o@x zSl6FZP@uyWOqnLWl!UE6LF?P!Usw)`H8ghU;kZy3*66k^8+4yK2*c|f+e@y3_(i@W z6)h1VFBvP*Cao+XQHhbFjwrD5RG_|%3QN53xtI+NTsUFSawH@zv75%os*rzI=wL;N z)4+2{0*l=HSn@Z@B^%s}8u3+Oa%_mLrQ3;d_HcwO3aQHsa_knpE(E!-*tHYspUo%~ z7h)67&hw(f9MFrvVuu3J<2&X08)EVtNujdr9KN)US!xI3^4P)4b31o3!T}?P$JH&hs1dpomfBj*>l0 zeb}|CoBpG-1L>XDj&a%O=lL=IF>&Hze6WeaKR_=5lQ_?F`4-o3*R(Zv)!IdjlejIw z^Ah`|QvhB6p^b?)1v&pIX_|r1z^2N0?uG ztA-M?tEy5-deq*n0wwC15BQ#`4g@db-{Y}aq4?B?2s3>%mT+t(JRK;ce^p57_LrW( zkkyDnNyBH1{GH$9+4wgdcD&a85!)8-$o{;K9af`U_u3r6DmLn7JvEb*D&x>R&bXYb z=dm#_o2LPwKL_4Oze^45487+8SCBZk|8Y}{XA)Q7NoI^7sNU{zG5NT-fbZV<<8{== z%aKkEU@zNP_?ZWfReRXzx=FBOH|cJ3>6!jDKLMFf1lC4CgliGMQ4Pypr2>zltg)b= z|E_=O99=duB!_UG`@xrxkZ7<#CCVLwFFYRA!m|Z7zcNp^NTw-CEiY>Zdhl)r6HTl!qYsNM$inzppB>H~BO-~Ci)Z;-L_d`5i-dQQ>qRh<2>c?ksd~Ix;APXB&Sx{l*?}!cgTm`v$5ZQvPM|vVvaK6!Hq4&}Llj6(Ok#_g>Dc7dLwQ zH!qPsdx9$xhbQwwGLqox!Kz9Fe##Z6bTl+H6ciLJEWM?g2gk>7Cp;u1-Wr0PW>(k7 zN57oMKedS)otKkZJvB5A0cc^re*FSSdTs_lCj$uyjSWIizsmv{92^98@SDCYK?rqU z{@tAh`npRMz#com6fiY2i|bvvmom@;KSd#24d#+6vEh$G)W1UWFezKNokkRpUnq`T zzr+9~b#-@lcXcsTB7kxM*s?X&X1)ELbry0YH9s4%_E|me4m!hVF>04i49g0NuN%;= zX>Sq?OUgb>Nv_D({j}%%ifo7NAfICDjGItz;@@7an$o|$+U5Y6%da2jEWq{rx%3ZP z3h;bR*#V?Q?Dw6NY8jayO2Ldx9R#MdU*3VKnu_Lq@)D*U6Y{1=Yu{L1Nb$|+4EZNN zDO42S9i}4JuJP9^G1W6^5l9fK_*VPwHZvpprmLQR<*+m1$z+BGJ=>B#tKcFvJl|Ni z!5$?BSPvQl!~#8JQnqM|1)ZUN(eE!qCSL2IgQUjmW67La@jh*(-kP4c5-)RNn+j?h zEj@E}=(6!=@U!4OPMTsb8j}m>QW5EFuUi3w0iRQ~pi_z$ z$KB57myvqO`3EBJ0EQO!df)J!#{P*fdKhA6*6Q>@oaIxr(9l%(IIe zgX+AG*ET+dD9%_p65Gyc@QF)17w1n0q>cFWEXDOVwI(@wS=DjMsI=zJaO1m1y>xnR zLiY! zcV2~ookYU;_JF3Jc;~n50DHogq-*{kSPJvMO4zC=ZEF|N?XrZOIFhid$HG2wERWP! z)vqL1ERip*ko^`Mw+eQ=eix+Q7l3`z_$sbcMSW(a3-t)7;_AB?N6#n6qD`oPkd|!` z)kj<6hL+mL&gohM?D##lUlT8GzlXXc*HF%W*bXthYbbk9upzgBjv6Ecu~7uz~feOexMmM>`DR)t_e%$&&*(4eJ4w}@`9oQ~+ZGCFiOQEenEZ9-s5eG3MtGJ*IaX5N^Z=x#As5Kix0O)mkJ=8_)FZ;C_j!=pQR6sQ?fc-<&(iG{B7DY83X zi)4Vs3~zWU+o9hWMFO7NB9+sXPTrtHw!HE?(J+_*cf<79jD6DiTUgMH12wutQ9QFL_JBoTgc32HL^U+T-VrP-E&cq(iw4`37cjO5-0&u7itN|Jsot{7Ys;Dr zVurAreq1p2{7f6H)$d~xDD>NZfY4&psELAyYI#_wTD+|X#i~(;G0W^ogRiw{46ubz zk^ZeN0-)KUb*j6hPt)*Lxh;PnX%IhaneBpp3I6Q9u?%xqbb=Ge7#nW&^vJtw0dIzP za)X0}`VewX#AM}!9d!VAkU?w4L~N&g$Ebq$^MrWdV1ypq-#z4Ba|7!}8NwU!+w5kU ztyF{CKo+TxRL>2|-N_+*5Tk+)gSu!NQ22`yFhqnB;JR}7_IXU_5}#=jG68L8D@hMA z40o2U-rf)>l(`Z<<}1eIy~m7%^w}9ab;`{acHbEeE){pVxUHAO05OV_FQ3M152TQP zu@9bF5~EATvTjn(9gB;{%xjDu=OSA(S$z!j;5N`OVfrL>lw{hyj2tHiEj;G4b^}N? zRS8Om$in@rW8P(5L%GaL%RwD5?3Y+BX&c0^K5SDn73pIQ$J*IMB@p1Am>##$FQN*g z6iigwe0kJMq+X5+mwpyPZAz~n8t-&Pz*dLO{9P-;lZv%nf51$aQB*@f-C z+9$&TXlkp{_mS0^bHKP7t1n>W8Uz|MRDb zrZ9aC3P`6r>OCs~9~*RIyDebC>ojFR(rhSK2&uGP{a@LQJzd zRWaRV8`UQi#YJ{4M(Caw_37Xj>aJv^wzB_S*0_;oc2ao`5Y5p(5o-9>5B(i18J~b{ z=Q58PW$u)tmuB+p@a`-Ad!P)}6f=^qaGtK~%Gkv=R3kDrHC0R%U%9xkCZ`D5oy~6ZHB?0dizo+Fs}2 z7;u#WQYvF_f1In3^cEfX3bd~GK}kP;ydD7%{+8`U=`Wh1$-*B1A$JaL}H#d{4I~a%&&z?Ntt0tiQ4Q!PIybu2l60N7>*j*N^9C6n#xhA?pX4Rj^~5ROIL(b17IZcYop zDS!0n(Yk??b0xqXJ`V;Y3hx|UAI$2gwj6(pp5u$4u3^QYb_No&fQXlo2?>E@!2ZXB z>VbDWa0c%6ajzq!XsU#Sgx%!rf3M@Pv$MON?e6q3kUVg3z>mI0Fp$g&n)CW?J-o*R zz!^!JiRTv>D!jmu09%21V`C!=5+Wuv@lmq#8Wk-JPoF-`B(qvPTtTSm<=4;XQUwED@4r$&zDg0awYO)I-G2+fTsYqX$dc(~J#`LS8j6Z& zo1${L$|%4su&oyISAv9ygoKS%29gue?^5@Z_{(V#;NA+k9s}BxQ2$4_Gl@_rG(DX{ zB1`-shkQJf0@z4JCX;4qI^#cj{-p5f)2F-J8-TR^7WW?;*mv^u6lVPEA4$Ez{Po%6 zVWGT$irZAF?-u~udH^gWLX{`}qIScwkM z(ENnuksK2<3h)u@>Ughb0d2p5es%c%{d=k2&(%X4RoWUwS=kl?l81*rvRjT7M@Qp~ zdxinB1$_5DYe*x0xVIhw+`z>(sTm?9FEyV%0{GIvD4ikuwXBp~U0wNr`~jWt@_d^4 zkqm+ww`|OH=E%hrm-hGd_4V|?$s3j_{&t<22;jI!c1{l8xRAk5N7Ng_zgPe5Px?_d zGZRzrRP)S??wYFPbdb40V*z4zzH6lF z57!e5iys^FTjOQur2O^(DgV53YH#@agZoA>MWv<97pT%<1p^dUXS{)I8SJ$eV+Utv zdDC-0K8U}0^XvBJ>VW47umJF*HaaGzBH-l3P!Pb{2FjKd37)jX#LbnJm7Sd?t4VgV zo~HYi0^Gw;Y&ymB^Yd_mlZ|0IDyo*i-A1$i)Knt*R6!TOpzh&uzIFoOt2RyOJg7T4 zzTR$)`_qi`!mn?NI4&s>5u#_siEn|~%Ix`}lcxei{g@cvX7jELbnmgpcEE8cF22_HGZyN$yO>X}TxxBK!-wU>$ERPQUzuSFS9;=`KhO zxe6H?9ySlDt>s|^{FMTtkq>staz3#cr+2by{=;0Wy$CM}lB(GLVj2(uW?##*&pJPM zuE?ZSNF0zWVQ_m`rLH7&=+>UF)texO>gMtUgl#d`c;Rt2JDw@|l8udRji>x)!otEp(8&iX zQoyz|323|+Ho388{esuZ0>l%jI3XD2bj8~V$KT?QqE}sxYk|%11VCiK$&|vZJMG#} z)`HFVuo4ha6ren1km;Z~IH9C_-TI?K%W}|$gpFB*VvH2KNEM#1bsen$irv`oYTJ`CvR*$0+d^z@ZH-^N#R6$Ve1SEkqS?1IKVVn^~*E2mbN*_b2(lp=ChSJ_HJZc(9!| zfR|{2TE1hg&tXjD7I`47lvmy>v4`ECebiFE7Y(pgZ=3a=t|YXo9#HjWb=D!-ZCgcBRC*MD1}vN(|60hFDu$ct5(% zg2&?Qnr+{6{fGHflK)oOs__@wGX+c!u~*Lu*}U_De+stE^tzKx)f;(50jnBB(|cO! zAK})Pw&a;RK=9Z=pqhYB@1Bf$zq2xH%P(c+6^GB=R0w5rd(V)^R;Z<^R@n1CBd~1=^#dCa{@hUQ*mhUb zF%Elvn!m+>72pgDK_1vgOaT$Wv==`Ayxc9eDed#TO4(qhxXA8@#GOQ&nH_kiLzMeH znfYG2LWs$0NDSWmy%j_KpTU4VqR4+ARF*+-lpXDP zG6H>0WLn$GXJA`(ux;gkO%IC!@AYEld-rq zKdafeNX&{s!d^>k$T7Gc`P^NHVKMhH zN?x<_2(h?r)+Zn?0b^cq_ zgGM&3Mtoc4lb=kLYhmA#Yay&1WNV>0(abqs`HINOCAk?k8!_%T-6`-RGOYrecic1Gg2L z2l6H0^ChVN7^(E{+sCoo#7NFb63SAP{`-lHH~*zKphqkHe>d3(MZS|P>B~3CUB2`G z^K^YVK~wl|ku4fBT#2i>KMVMNB|EMMvP-t7__rB0H7a-(7t54RPMlsZ#C2BGvNlmR zsGB;XmpXy{)&Dp{gn33yo~-)H;`4j<<)6OInNOq_8j~5;=!3tK$j$O;c9hBwe^dzC z(r)2YhaQ^DAi61GI|`+Tzf|US;4CJVt)^6lQCNm5wGkhN90DS|gCnN@)!!~7BFY&J#J=2#^81pQw&}e%YUWe=+0OAsYW($6GfmUi3`FlA`Afz86-)2 zFC%^m@_8*xrlAUuT#QlsrrV^i51{|uGQY772LjEdVw{uz1`qn~frT=8E#!_$3jPp#&__%mo^*XjnR zzUn_r!;1m- zJ7h-3=brk_*GP0{TLVfXrTsT=fTkoSh>;2Xh!PGo6 zgoOH&`HZg2Kte((Xa}dKFyN3Jxb1mh0W+fUG1v|Cj}w{4anJImg{$Fdof?=isn{J) z{uk*GJ~Z3NzCx_e{gPPrDvIOT&;#wznVNJm$q0R11UN(voW>J;Inm%?sVqVU9Ig-V zKqKO$2fF_vBJ&!6J;Bw4q-?2$Wh%RvYXbj-GX7acZ{z!r_wD)spsqXwy57kBr)G0U zTNJ4t!PE8UcFCX1WYx>}=AR-CtXNZFAL$G3G*!SB+a1GJgXO}174rhfQ(qep!67+g zjwVR8n^yFylU&Fjn}+;6d0dr);pZE_|K=!?K%lmEc4ym@!zDUA1_lN;HhY<;xP(!$ zv8yX9qrmx9-~jrQr%!3Ash5|Raqc;R@fMT%!i(5u@*0lo9cey)QU=~ZJ6lYMKK+9& z>%4YH&|M@m)#mI@c7&rI2%B zBzb~-{u=$e>UZNQ2^aSLUCc6vNHY=drSiMfi!^|;_448(q9eRF z9E;0(s%kn#B&M9KAU}bBL3b}*-ncv zSUK&AZ|wd=(|uej#<#Go(PLf34jN+YfT=zzFDQ_^9U53jxk*SX)7te~!YL zJ|SyGuzB<5$lzG>EirTBJc5 z47wXh=@gKVl#p(uB?Ku6kq$xP8ynB>oaa2(`(58Z-^)KDd#|fwn1MM_Hqk$IqszhaZaza&uHHlTV?my2_x#mD`!EBUkBs?o#63|`gd^Bm>jKw>O~ zPw|q1Ek~l?J5;m+h3W~ESD^pt8j$zmQ~%FDp*&jVd&lJ|<&aQpU}OjuR{0>J!k@Ww zqh4pPB*Tlq`lb#ljfGkzk~mU^1KEdJj-LNUC~evPodwc+E&=JS`WjUP1&*a=@beUv zPb8tN-Kr*K?UT9?0YQxyyj%T$K1oz<`>#)+EJ3HsbKlvIgu$q>)IyH+e1Z#4kVb#J zXJh{)UbmPkvPd4atNR6jDxHrWc8&#p(I;zL73w!!gTEMp^Gv`{>mRW=HLvGaic7(i zzjfvNg|7@v{8H0@!4m!TF1IwN1OrUfvDVls8cfsqz_=ey`-_%nzLEMDEzw&AnSHP6 z8VbNt=~z~4oK*KN;_B297j}*;((P1kDn!1I?q1sxAjJZs102(qj(VgK|3OvK@@i{; z^mQ4MT%TCn`~1quzH^^r(z^+!+lw*S26l0uUnx(_=hMjF8dg_}4M=r!;hiv$_2Eq( zYQ*tSzU}(z&2u3k=a-dP6jGLzbM)FnnLm{}QXQGJqY8HDG+ZWBxMF&XWBTF2mAL=) z;NR(^6%=i+$v$Jr>gq@ZqOJ=ak4R02Z>PA;4u^G3CE4>*-RH6HW_e?Y z0vtS>CrN2@O21cKGfub2<@O1Mb@&@0E3Ku_>J#p>e|qa2TwuK{+!o0Fl^p>Ik`yMd z?^@;lK7u%j+K#hY<$)ybmM#SqK@qM@0#OBt*Hzie+5sa4E)43eXOt2!lvTGc5|<0+ zGph+&@@dd>J{#A9lkqBM!*>Z^AC{l}3w8$40@50Uh^+YO{1E#@s+oYkh1J+fmHoeW z>ZpSHvf88IIiuQs=^M$MZmPO|;5?})3{WQh) zlj{r!c_*DioVPKl7pQ}a6|v>Ll=lvA#Y`JU_w}hN{e7q=8AE}6nA*5apk}0Hu0?PT zw$jlTqXdWTU|$7XtJ#?V+9=x;GkhA$BHwS+C&l1!6f-U_ohIcJzH>)`)TjV?VP#)K ztV&kkl+IIKvt=1QhLYRmx5&&GynGq9-qC~VcM@NAJzhWeU0wREpnr=trbEU>l{WAD z{_-H|NPZb43W$LN=iwJtj=@XyA4-z?RD9nL@fZ5w*gv94i9hb2DihOz}m@%A?{!M%yp_e8!GhBi^b;LJPeytoD9izUL+66&k&NyXJrs zMhajV-tNptf0MvC=VGTa(Au%e;r#BtuO|u9olv1OZS&}D@{I4-=1!}Ty@ZROm(z-O zxfCIN&67rA9(aBt90Us%#!txk=5>}|6@Ja{UMM9~docQWUp=dd*$b9fHPP+l+|H{IRLfl8t8E(mCYQvQrpRx~GatPum+Y+y zA%{9?0V&1X5`bT8X&cFH=2%?w{Cg|@5{f&M-4d~kF&hgPzAoBPaTskHUJsDI1f^)5 zUxcK)$R770%YRuGrAOT37peKFtY7a{WYWe`$gQq@B@3{+SLXE}A+rjzD^%&DCU#Qn z1VmZ46%oB$B}sz{f5jL|5*WFPX1x7KENgpqhC_93R2*keTQRIi;3=h>+(>>#H zady!&uHeJ$CVPC!iFlJ^=Z{YsnUazby_WFko}zhH+MpbB%yCwvbKQ_w@9?9pXcqhE zD*D6i&oMvpGUbE^NKmH0lvMEZhxIv*GMsptK}oWAemvRIi6ff+6uIFhB$_~XPi;(@ zSn;91UE>lL9w+OsuTF9~cpA5Ia*pIV=A$1`vKD%)0`69%9M>LIT8z@`-1JF?BDsfg zY6{oFtjFYLnDDhCdd$tAjV`KencDF`upglmqH+7<=34!yJaTV9#Y9OgxLLtyR0M^j zlhW{tbSkyMY>%GOO5T{={Uu(+jvwFPqvjZu`WExO`iDpkzw&JDJ5a51K6ehEq7CmpD`?$%y}qcB-)OFvjGx!V@7SSuzrnj(Et-zQl-qCf=`4 z3$SHpOxt!FjRzii-}pUI>YUVH@T78Bq)0QjNQhh2_iq5qkDQ@!*W*6ib6upM^QfcwKe_9} zq#<4kWFs(k)iGw@Sz;0IZ2`Pl63*s1-V84`;mDazbBsL0oA)bG%iMvr-+XVS&B zY>QN()$E-ky>fD7OW#Ef-%R$!#HOUskdiJQib9oxkirTW(U4@Trwr`+@U%IB+k7tX zGqBsJs;NDdLD@4Ife~-WPnM8H`d!)W%fNy*tPXr>NK#hl%kr87( zNg%FPc37Cwd|OCHMn+UrlnUK>BoC`KzGWGbc2HtP#q(?!l~xoMo-8+qZ@l5~L86jL zC5F=ZvP2o-;o(5OWNK`jru{F2o!DPRTU%RMSy@+?nrA6YXd8=XDK9VYH-eeYy8iIi z$imkWLvt6nGw?b=3Dtv-s4NQ@?Cqo(uqe!fz* z`^sPz#2pX_L^>NDgwa?9*TcsB=j2+ak&JPW8q9b6_xD!-R^S$7D1 z+DgrF=K0`ynp@q7&Unt56JuEex%|=UgY9M*J*s>iMn^yZ77eab30I!kr9(7{E!S%@8jop zuskpie0RA3FMzM$!GsW#mjl}&tJ&Pt^skqU8pr=0TTwH_vxta@l<^yj0F&_a@48SY zz$-Tg83iB-8{5^77uLE$sCbVKHfXP1i%(1pI$xTX%@GL+kck}w(kDYP5oW6G?d?a! z`rjbG4GWci|9o!D00Mvva2N&W9DFqwSGg@SCkNmgQc_ZBKT+Xe^w2>rh?iWsE|vNz zTy{kEiwYzzQU2urVX%w+u(zc{D1po8j`}4V8>^`*PT=RdNP|*Fjrxp?j2ACm%F4=0 zV|}Ui0frB_iuIWWTEJDjq^~x1ZQ9)aKVy-nv1s^3O|2FzRjS6phEXXeJV8EXnu|bRCVPgmS`!|v-HhOe)bS%T_I6L#% z#iGOKVsxUSqWACLzk8PeWBU~q!0(7jNu}PM0U_S`P`|Iks)kZ<(!<_}r+OtQ0mYvc z&##e_`%ws@R-&-l^TbtEm534pt+cF+fu0`Z$YKBU8Oh&WO#(ZSz#H?gEZ75I7VH)P zF@Pk}e}4ebYdQ{&{-Gh_dkd&36p4X0L)Dp7hUD>77no5HBiOwY5U<={|Mqi!e&~~n{cZzl zmGGp~J`V%<1&icTYPpQ8tk%b88o-w(BrGiGxavKC125*Kp%&pSPPE49g~dgPIo)Al zS&5LQSsBU+D=scJy)ZpJ-7kDVy^4w2Y$W?@_hf<)e z15v`a&2;;AaEpwGtS{&~8hB9&7sUMN2DT(Jcw;khw6-N@u3fG5erPdiEmKyjGvL*H zVaHI;S&;;j<>F}=JXS3-CPvDj9l)d8izucO&nE6fVhu)`jyNW^my2Ad^sw@bUtNGCOi%8S)d$cr92E`bjF#ECwRgk)fe`w{Dp< zd0znrR@fV0P-jv%ups(K^zTTv$~EPLJWew5sI-i0{dT?eU!)@-We+GsJ>8X+2?7S- zT#Ag0B(T9nUCH?WnV3htVsucvKvi{y#PC${vs9H0nz`hj6INk24-I0S)GPE)ytv^e z|3v}@ZgsQVFUn6lxJ9h-)c(x}6tiWFe|xdGD#1}ItLxbK<6dMblgCQulV@ddK*T|@ z`f?q4blLOnL5BbeGw`uq`k_{vL8dJ2w$J?r-+nrvtwN307h58G%f|;dMFq<&nE&-`E3JLcCJ*`Dv|s1+ShbEPsEikCXlbZrb)TN9{n8%b`AxVcMAZ7v zX~zqpRfMkF+2)h zHch0~63@~g4|Of4_GA+rllT!}do_Ba8HlvdqD^O1@!VHQ39B{U3o*w2;z@EjC2Njj z67d86hW`w$cK~yCup1#FBpyq4U;n#vyQzL9*YS&o7Y|q!v|GYXSELy{u#AH*0C|=O zepy!k!hdD@H#hY)uX$dD(ChzP<7#YfDsRa5hf@EO@BdRavQ@FVIeAW*b%B1DA^d*l zr;mc7b2G2z$@~RytE>b5#Os5;o?CqP+wbPJ!Q(qc+{XZc`)ARxMXZZ7++yF@y!#JN z*0{6IVd>e$#lwk$OZL)_>Z39u7P{9NocpiYc6IA%#t2!avGHL&_|f4QGYffpe?m%D z5^iyPC+Rge5uxd}#|Q69zWcbZxz z5VW3kEz^@z{xi!55}_ObrJo$&(&Wx0e#TY@r#OeVc(|BcXc>S$aW)Mmpo5LurAr&G6k@t`HtDPi;IiP-fGFvaK%Blhbv*Sqh5kHZzo?)Sb9-;dzI=vKq!86$wt6_nf%|}SzC3O0K)z?+gB~z z)sU8MkOkyOPl*``y7z6iZI7hf7Icg;w_jeKo~kMEIdx~lXX|D#E%o}cVRFc|ZoEh^ zvwus|XW$$7!EI!GXZQA7-0D(r)U;ul=o=+-*_)#plfv)1YA+^HJR?Q3%(kUiJw(% zWOexnnYqt2z-7xkw6OFMmB2TzsQ*%#;iy>nGvSvB3nCVivf;De=;jFd`iGTYW~YNy z2He&NI+_w602=8#fE{9u?ef$(6o)*7}#)Xsg}# zw92SwFeOGY^*r5wUDSysV8Ga>Em!N4uWr1^-EF6eJJzO>e17eNn0Zgt7I!in^#<4T z!gg{(*FSg?6Mp=r3l*eq#;Jm*+pXP4JOCGyKd5(s=bZu`XHZh5{ zP%juCT~q#ec|!@<3`c5<7IofG0BQpc;A-K`ODf*|I&-4cuBIYS>pdS{ERLHG%!@zR zC;ua&5qQKqLY~p)d5o#r{RLB@rK1?*j4!1&{_OR?Igm&#LTwsFoKPG{RQwOtY)hP( z&hj5Mt9Rlo!p0B>Rawv1%=yAcQW9L18I1AE^sJ51RAU-Wj5kz>X zFqv{}AdvvvPWLbdmC*A;q&PN37*8hIsoES5sIzXIFBFOTy^|6;u~Or%i9V>mTJFmCG;wEt65_R4q9Ya)1h+03osZ%vg~R z982gLPJG*ivY3n>NdmiNd%r#itIxOB$9{b3uIF-PBQ}8(p?z>;=E+BGYCUN|_2OQq ztW~_i6~WQm+P*imQ7b_a;~Ct^KUY8ToCh8O#WIOuK;Yvw4LY&rBQ@4NbI#(KgS^2W zUdz^}Vx_ssv`5H=A>Tjp!(8_VC4+P0b0ltYJbktN)nLUZ9Uq^+cbWhAkdDSpk$q@X!BwMQ<)x0A z%^5|##anccHtIv{rPjoSAT#atsL-3gsRWDvU~3rHyhV=-}WD|FR@%hx~u#*IuTWPD*Ke5Fq7#(i1Q6EBuXiWS|lfEItx{VxWRx12sW zRig?5KTMs5S=6b;550>1w&sUj$cisu_q2|E~-(Mv_)Y`IU zt4u!ccIjF}pTXiY{C9e-%fQ}v0(?}GF^_8!H3u=@C`*_QKQ4cu_PcGFkwxp6AkZO) z!n(bOro2eL+u=&Xnd!#E#&o_x(JAU@e4nh9RS2b&qnTMFBuN+oS{TfP-sFCs^bdT{ z%0{WeK#9Z3DmUyODEIo{f}A*g&8=JV^uD>ZW-^kS`weUCwQE+VGtvsK2~SNZV(fn$ zi0p0ds=reka%j-g+tVOVMrJNiEO5v$RZn(D8sm;cVSF20s8QN_uY`a3n}LQ$rbj17 zt4u&(Ktw~0woJcgK?KD>SyU?e1lFS>ij|}6IiJ$wGS(uLE;;vc-m)o-SK_6H1___Y1g4c6afc` zJBF5%S=l??**1xC7L0~aU*YA6V&^mY-Rqrtn6Yb1ajldEKLsvl9sa6r3)|l1=|7GV z-KXvtc~^FSTa(PPPEhZTb7T*vQT=ZhcE8K1wb!2-cPA4q zv@TyBPZwT_>Krid_aiLyoR_6yl=0YIt6b15NLWfVvmh-E5l&6hD{15Om8Snf{QE}U z%eZ2_cW$pPvd_~;wl3#YFp%j}_Up^*PyzSTrx|7q?z6WW^86+zGtn(F#|#~>NM+nt zT2kHV5Ls<0sTeoR#%})1<(?Cyph$1pY$V7zOJkjN4RLXF#UYqq_>ys{Gif_5=ziAm z#+!3051A1bB3p61u})ey_4rG6zf>{Ec_nH}|g4-IkYVMP-@Q-NaQp-nvccac`!q zeFJlSh@XLZ4ZupAftTJp3I~S>D^tD1n=fY!^;M+;T_+^0S+|HW1gK_7bS{Ce$97`##0pN1jwr@s@oAF(|0P>M6Cl{ln&#PB- zA_DD)8whibr%N={w^Kp`$v%7-=0VW?9fj)QbS~Gudl1`#$dXMxorWXl>S=y#$2(-I zSu+FS@)dW22=(&M-!2D4oT6d-`bYe;25!%1TXZ(vGjc!g(ISmx)~@jX>RWh5HE^*| zopX?wvN^nwAbnr{nu3H@Q(1<~db>f1ybG?>yoJAt22n<7i1~hx&Jc0j`<^IsOaXZX z1xw&+s;l#W0Cx*kUSQzBX9JDTZp&xtnZLY#Onx#GwQ^=e7k-7Wcp5_bfW~qCi#3p| zQ>@^pKJlkm5df)IXF;ejsP~mSTXVv^_r1A3uB8evhWy6!^~=3`_wGf#CqH?9w8?fY zAKG1^_2@`pJi>l2f`@{zMonUA>v07ln=~vK5*p z%@XPq6cn%3Rd){<`!gg|_X~*)UZrjiTsJ|vg|2d(>G!H+**vA$ebowQ)J<^$Y~{Hz za$*Z80@y;|1l1ICo%A!$yohQGs8Kty?QC(%^rCVVk0ciM`64C#aLDU6r&Zo6!%5sK ze#jd!(?Hh@vOC)4)-&}kJ3k=Wt5sz)3o&A4LvMABXTcK=bM5>gJ*4lpM7MYMl*rE~ zc65-_o$ZggHI3(~8p+|S^zr&)lY@fV<@_6lW`n8*!@jBwESB-3k%Ft;X~(jC)B=?3kszrNaE51hYokKT3!Afyzpe z4_?S8B)qsL_JSp?6DQ0(+v1KMT{m|HqEuWnoZ(95WflTeO}mwMU9xz5@AN^nB(@Bm6>-8J$*CT_Aly% z?i<@$#*Y4sM4T_Zkj4`%*B6U53um_9>xstDz+Pz06~GkOIXJL>^xEIwKavT@ zn6k>q+1a`C=Fd7F4DFZR`r$Ih;ca(j5#tVYSwUXk5XbeZRygeRrK}i5DAp!p21KYH z-_%YI?sJNarJpzo+3xo5nX==Hp6I&HIju}2m?bgO>x7rr-A}OyPIlXD@3g2g%z0(o zx%@$gZ%dFbqUDWz9@1#3z&Ho^pIkBG2ezel^YJw zZR04i)~!oX>uT5Cwuv*U^1FMRS4KbV14Iz1K8j?VpDieAybZG_V-V)--Mu(JNZKIp z<;{JhWV8Q0z%`^gk&Q(?MB6=l*Z&5qCQg8S_Hn59)Mt$b3UeucLfA-8rS|vt3j?9C zgM_5y#R&?!2f^VF_k}JZ7!eNgx9(5~znc+u+#8)P6N}G)Nv1r}t$|5~g(HZ`V%Ny# zTD8n>D@l6v@^J1rQBuYQ> zWZj?*KLWTs9|Vi>SQ|1TbJ(Xf11VMl%(Iy&;);Ij9Wi7E%-+Eao=-V+7$etU@bE35 z3ZNp2d;feA?liv9kdu{lg)Kc`yG~{03ehWXs>nHa31_TKIee-M!79Pa6>|P22-g-F zrHA)~KUX=lZVb}WDc~?MQttY@Qck)atBm_*gMcnFA#Pn;UW1S?Cnu+tmKN#O#bPFz zi*RP}AY^hm(HH3vFG8oBLyj9i`&f5Qvd7UQjtmo4uC!&bdB&v_2AmpuPnjXSvD98* z57EXwbeEo3lU^ET*;+3Pf|ZTw43X39xQxL-V&*N{I6@0)=08#aYtd2PIpcR-;yLRcr;P1I3IKo4&B z?3@246 zS3pfw?w0ub0>@N`zU#sK66KQ%SCHP`I}o!Bw|SLo#0l2S#~)uAJYM1K1`Uu)8R}I0 z%*VEQ`Ho^qhg5dyw3)CY`l;md?ZR(jC#yov#C|u@0u<}2OH#R#Po-5}a@*hO?9}@h zI2H4F1f45*iN?cU4eBsCU2Cpl&bi-6kscw+*#Gv}cNde3StD2y&`o-Aj!A zDlUuynw?U+9P`t0RE?b{ip$5*5`C)Ml=*?Yh#ZG`vMD2FMF)|n^`I3XRWG!a*PtCs za<6$pFM|qOJqr%ccMXX!#tl{Yz#*UTA*uFTU0rUp*k#uif0}x~hjEXt&qth^6}l=^ zjnOO)A>``J>Ec5vRst_4JmXyJDLM1*&op&xa}L#JxY9-6&yJ*5Z?9OtaUJ zEd6#=_$(Nvgi48oe`%B1tAAKtegAIws}zT$jZZwsReUpYG%t%N2(&YV%zE8D=ye8q zaHr^^%MqVuZz|Z@Em4@1FC-8PFi0kKnF1xm1GoCm16q>~bDX#<^9wvv!fMmjSD8Fp zZ$h*U~THSo>MQYk``+G7xtXYXBY$tVl3=M1_74R$(X zhe|Op4~S$vtKS<0Qw!MryvdJy)%Sd$U<>aOi8mc)|8XB8#t_$x#teBlAmg7cfmD|* z>f=-nZVKBbyNptCEN6zi^hY=xuElYMWuFNq;PRINfrNAJV!^wa^I_GT@0X2zy?1_H z>*E#Xys<0kqHJ<&!G`>mzNdEmMEB|H1cPwEeQ})|`jnZ^giLk9L@R6tP5Dt$cUO^9 zJPCg)H|5t2r_3FTs~4)Dn-(K88e-nv^onkp4`C#2Yz(tvkkHjihFn zQqd;gP$mm<{y~BB8;6gCmRWm}{;5|9IbA|lGx#Dus|9H(mW@umGf)h0f(^}}JdX6{ zqpcECC*%w~iAXf~VrkyGv?j4TZ+EbAG==l%)VRAdQo4@Gkyd%#?epQIh^55QbNN`t zJ`tPVIg*@4f2t)RO!oBn4jFPz>YXaRU~XW?_my+{+PXNyxl@5uCK z*hw1d>sm0idYZ_|lArqnm&9ud;WHl$TM^I@s>p>CvRb8^Ec`960LheA01_4;-Mebduo`5%{F^O_g&v8sR5 zD-pUKe@gg#bz2a*$ZBKjuM*FArZp9nTDl+lfgGe}U`UUTUDeKXItPc~<*L(Kk%3I^*TG$2CQMfJA0deyq};3cq=9 z>eFvec=8~={BoG`k1_XGf4d~>bg2P2( zkCb_<+{q-ZG4}m?1Gfb}ca(K2X|=IOPyLB0Mdf4Er;*%Nd0mD`uVJB&a+kvh4tI-5 zD7(p@5*h`uH(mCvYjnZ7foNdp18uJELq&R6BIXcCBa)aS8@6z1nLE|Kvm~hekpAp3 zp=o9|p8JorYW>jIsiK1XE^KzI7Gx$)c{ZD!PM+*pAZ4vHD`)y*V&zj0I_@^&;jEQt zj&_pd58`_%6OA9p;ib1MJGC5&+z_?M7|y)umS0q5k##xp3-Na00(&GjU)c+fb$v1U z!TI#t2<_(Tfbv;+5gqZ0trDJcH76I8#@Ns8SCwee|1NZqAY*300wd40ZbGIbqCz(^ zRG9UED~(Ejo{Wz(c*ossz^{OcirdhbZs!ZBQ{k`}k*j!p%v38?)MH;rP3_yW*Ev7c z3NH7xkHTou8n^p%QRNLygu3e{mR4}+lqpBfh}0y*zn%noglNLuOi3i;@Q`N7cOWo= zM;c>*_Mf|&uf3-Bc`vix=~fXwxfgfD`fGQlIwIWqiONvFywr6^3^{z72Zy7MRdO-) zBv+;eoi*y|{t5w{AXku?!UKi!hZE?X9W64FuL)!|r#`A;$Sz+w%q|I1Fc;FK(P?YE zk5~{uCK~BQFJpD~FNo~dyp~q>O!ftvRw_wYz4#I?j@v7gg#f1mXk9gO24UN=T};%% zDj1Rz$_i<&Ct0LYIC^Qwn|<+lM>)~vPwoHq_k)s`Pmh|<7n9+ysh!P^aGfh!!Dmck zT1aeT2RC>oIUwQ#%k`rWH?39jj5iJ2wI5W{j@gyKVK#Xa;r2Tg7>drKru2VkI@|G< ztN!b5>7Q$VhNEovY_@-H$hMF;s#kTe$ZoARYUiD1QW(TjKT6qWIrxl>p_->sw^ky4 z?uo;tFy(!@N&ApyA%Ut*Hqn1xfS9DDWGVqGjz6L5VvtJZ>6D&)dcjg_^`u>`G5-Kv69`X)rz34@eD&PD>89PiBUrC`u7~^4_(82=gS7kTOOQ~# zkgio`!t9N0xRu~|E9ElYo;02M0HvHBEFF^`-AuWp#W6>FqKMApLq+AMPdNbJ21h13 z+7i_>X0YTS|42oO#s|%IV9C)qn?kVBl7QM9Y{HimvxrM*eLzMIcBWXcO$O>3)SCw8 zcZ8g~yAbw;f4wQdq@byD`zLBd^yqx-GaHQlB2`?+f9z6m_ldvPHJbj$S%w_HI)lO> zV532y!j$!{u+Oy?S?k&%eYk z$`9Z|fWC>Gz37q%2CSB@uCC0t&3nKstD@(ijD_}+RbEoE1>ExdYzji?nOF-jKhIle zoLn>nnwQl#74Uq}Sbf&ko|B-T2S*zZ0aHl8@REx3fBkm8l;?l@=eOXXo}Ql7uOmN) z^VCpK$V0T2TvuXR+AwjrfpQjwkn?7VVSE0!r@%kx;_6C9Mm9$dnRm384^nGI?n-h% zWTZ=%Lr6%7_4{||F-b{G{Dv0%Wq?>k;{vDoqKck6=f@c6`3pQQ{_*f$;FGSGK+FL> zE#ulJbFtk*s0ax;4cl;zqt93Ad$sfSLO^2TBw%T}4d+R3Apj(zudfd`Z^Fkuzdj`y zuwmN;7I_`MXMom_Q&MXG_b0$Bk(5Nu03*DC7B^F?@NutYn6%7dHovi{d^>KxeE*ulRi^?(nMfdj6!Qb+slj&V66C1rhS$+X4b zuAty#YwM+w4;Nancv;bG>R%0R?d>T%e8|1NK>_n|Tvb0FMMJ7vo*JrP!Ms>~>?k=Z z@>P_Tzt7GBHZRbC8nsBRVun{!hc789DcNNNj_WP9JqLVLbY*4b0NV|Clh8LFfkYyK zO^EfmQ;fb2KeEL?H<$T%{d}dwh&lD2o0w#;_^SZ)6dH!=qlBJu3?6E z2cRl|VlrdL4`fI)kljDuv+xWxMj;{hw%}{bm^Z;426!JZ;2>1c{8dQN-?*at6Xv2} z*x1@a_22^)GlJ{ag<(abfm!GR8X@Mbi{JjR39CwsJfBv7{Fs7dr-7qFx#JmL8V=PQtscrGK%q5xRj>ao9YmxYrRpnJHu zz5V^|VV7WrkI^!23J3_Wu*eqpLoA>bsG4PzO6(Tc4}8`VkKvyaIBV7`i}ol-g@F1D??51L-~)8S{;=FjlM!9u>OoF26!@ z%FzfpfB*3VaX&cS*KniE?vhY}8$l z>Kb4CCEs6+@PAMpnVhV^n*;inYD<9trJ2UQt>hWevv;9`}iGKNfx0S1iFt}YwPlr+~>=h zjd{5bfYWAV!{o6?D>EzWv*X`ee7&F$sUsK&qo|}LCBsR1tf#3X{OJ(BJIezME&{x~ zaqLqdzx~w;7?o;|YJpaV6mXPKSD+zE`E-wzAyScbwxvZf={d|K6x9I zz|qA)s!PFsQ<9O%J!;n~G^`j9^+`$>LFeJ_F6TesK!1_O~%h)fFs11=f6?pJ^&1L(B;`S^A>M}zypHzz6}*@}zH zDftSTuD7=rHm&??fHBJ-5z*2X>o!hI2;91b(2r!4{mDvczsFGlEs;%4m!R*u|NZZo zl8&pxvT|~+i!qu|U5&w+PXXHwn#%^uV}drzKz2snWJFM4Qz0`S$jJB?6#RslSU!JV z|9kH07Wz-<_&hvAas>V7vsVBw9p3&W4Q88kUz{I!%QV^Z9AhHw_ime5SWGr}c`ZKe zN#yPC>4M`5q@_?QP`RpPvsIaj!n*bhYoJA3E%c#`2Hw1my2z>Vr2> zK9LPAWo>P3qj!EJS`Y3v`{D0#`-BDtrhlXn^+fd%=HMvaXgKqPHtW!Fn8@>vg0lXqJ;vZJ0^%5dgw z$X`JkVL{XSo)GO*qCVo}Fm@5DyO43@%4?H;3lL5kb*43s4Tw z(gF?3JGJjJVgdQZ9Wb5b6ck$f!Fsx!^IJ#tBwMwAEXy)33uAn+>gMiub|2c2QlmBe z!QDN9$_y{|mW~$GnVG-Q`?UiS_E<_v{jXo6Pn|-aiEt;=v#2CtMnUP7DX-!9!lW#b zW*6j^2$Z6kyS(OhW^FJgrft=J>Q)tLJ|SSpO2OE^NbR5 zhbR7RtTBHJ2sE0T8SdQ!Do&}6?rw<%TQttWh_(ifYFD{}P+5iRnT0cQ4C0S%Be4VT z3}r_Rd%bs?J6B%&4-`v>kH^~{0eE& z;a89CZ4cej z(wGYOW_}FxuTjz6?$J&6i7TX!_+pswX4DqDY3pVpYxXpa*ZUTFlCP~+cYQ?GFRh&L zx*YwIQtifjk1SRHJ2}GzC~$#O2kfnp1Gbj-cB+{Tak+*0`8RZp7g}|dNqoqt~z+dpnBfU;f&o2(RtlA*2aneL^>CTB6txagc%|pN4v`d7!ab-1t*G` z8LRRd7!V^&$ILC?nGZd=$Ht}2-4-@Go6PpM|%e!OdHCGl%e zKA}9ppr@{^^sDZocAs!&$yI(C)wQrs8Mp@NnhAtKgyw$9$=~oUW#LtoKm}WPIKf}$ zh5W|?J4!F&GVpPK&2rp&gy(XMHc&Z@BF>Y$&++w5yDSHyx-)PmXdY8K|^xWEV6;^gHW>+ZIg{^Y_f z49p%>REY^Z76O8TVbuJQcA(;JDJDH`@H~O$;c(#T(fC3giVYObzz?WRpk0ix-~Tl^ zR&8&pt&hV`?;jcpMK)f^0*=7t@#)EDFso68Q(gCxPX!wY6U%&Rmp7_IOMu2!4wVx; zUdIQ_faK}gcvFsRkty%IPUEwqA|oOHoxp32N)v;oR8bTYL({^$?qX(^ zTV3s9WR$ta{pisng~ai*ey06TEJ8QZLDAW!m^9*8y)T-gm|FBk2|hM*4@F;ac_HD zHE;k9{Fo8Z*w|QTTz*X<%4A@D;enAcbXk9RxV;W$uMIsteekIuu;em8aaaS-OH}f& zOC=c8vgZ+oaa=j-5TuC6-rbln(4@)$=Y9yiH-X3gPE%W3+vm>@;)a;*X@p%R+1Pcf zl-+x}yED2yl$F1{ynw;%)$EH)kg@#(HX9gyZMz}CT#Xr*9XxBQs|R*4l3;Div%(tY zPR*i|++;kY?Veja2JW#nN&_!2vqncpGg-j+9lePF>n`gOkV?|*VcI1}%_4Wdf5w3G z(HEDhFNq(xPKl7}7f8s+T;1HLv>d3_AOaUO_xR}d?;9xp$z1-yEcLdnUMV-v>o>^S zI`o?WdWfO;24$mg)UQFc(vv4Q;<5FDVvvlQSHaQ)tEXpdY%Dxwzox|uxG%ZujJwGW zNfS%ch@j&&!Ja*0KkK1nFqWuj1Na>hSHZxMbR9R~d3HMs@0*L{y@bb~!sf-;m6Y4_ zPj9GfdudWa?&jz6R|Xp07duiTfj|4@J||`xV`loi%dZr&$Pl_H5a&d+DVT0 zioLRq;Rw{$(eVO9$Z2DmG`Yj`Xoaby3mkwBJ*aS#l>Y@0fX9y?!`ZzACIEPSP&((K z&3OctnVBn8zks8ZF8$Q_Iw=eoeAE5SO;F%q*s1~4<=fQMvaZjy^Rv>#IyU=#OvPEe z;Pg;?Hr`-WmQV?Md^^{l-)t#Xi?>ItZaA*??n}@@Y+O4_Wn=3D4O#4zwLvhgcu?}4 z==*x#t&XYschGw$^Y=@Ss*H4$u*ch|M0ctDd>)>Xw{$#i%>@!sRa=ph-JyG?eQy&J zrKO|-jAg=}KYtFzO|9+FbD@TLDAMT8Zp1FI*E9|eM%q0a&K&^zr{pd8W|q3u9LAvp z8Njr>WxLQ35i12{ak$ic;UVYqKysgY z(D1z&;oMIHgu%;V$*{5CxY~CtLN$6Xl%9p*q7EsI7bh)TjF-I>4r%)n>h*cB5~?qa z(T!O2ILhnxmuqs}i-JJo8$={s??i{3ky_l(kf}em(U$%Z5rG5uB>LZX#ZG{ICad8QO6O&(hN&`kV9Aa)KSpUqlf>J*<%0vJXJJYlgnwrD-?Zv$c{h zU!>~Oa!Y9SUocsGN=B(+zoy>DXi7v|C!IT8Ttac8nSY30i6EkNJKTzl8p{<~@A)2< z$Zm5-rM74gfQ~;VgEC{UKW^&C_0~h3YPVk620>eFKG{MQPac1WY{f@Xc!YN_Om7?o z%mfww)*acd@l@+=&Gu^9lihBIjN?yY^`TkmDkKY?w1%j9afy+hj$WlhMfgW^Nvt!{ z!XS}qdLHvm`TFGytD?U-IS2)U6^4WHEY;H-Z6Y{q|HY9*LDZ?x^lPv-CiysVIFs~XQkoi=S_vV%0&%%? z`MWJpwF&DMdpxt~WSQQ}10MbE0?MC}+BmsN&JJOa+%AaU`1$zIk)e2B)U#QRbIUi1 zQF$#QP|sgM`vqeXQB%LSxF!Jy*j7)0L~z$aQ8nzf}soYo<@C+zVlb9wwY_W zqM9BidRhgHPe4ZHwYwa_r9be=We3IT3JxPUiLb?;^@0vfOtbn{GnAv`^?Nhz zEBF@V%k+w)Qf0BXwbGfuCc6bZ;=OMbS)L%5dEd)(e9Z3fYK}yGfw3foTl*#u7Rdnp#?O zQ&StieG`VzS`7p%@Q@3l@}w~(*j9z_2e;Z)!QReu_g8Tw&j6P&3di#6hgmy9hq982 zihBDcyj}s%W4_)5PD5YRL(7XS2%+c{$c2%kpdZa+!8Y-u^<-|y97?#t>8jY&+n#X^+#HSbC0ZNN|w9rQ&6%H}=yrg*%s+bB4~oXN+`^ zpnwh7zZ4B6NfO+4=~{BpmnSQW)n9|_H1Hl=-@#odLCw+CSNgFz+njOw(p^5K7S^dR zSup6*uBmu%N<5Uyl9R`Qf*Goo6$|#{a7{Czw^n?x7!Xmwg!wz3FppCAy{FnoYO_pR z`An5G(mM!W+UcQN2lbI}f&{uEsxMi4_48=p$x_@*@IO(>H_oEW^>#O#wOYAE6-swF zetnPdzSU=mpbl`^220=MqhJzc^Rf9NMf`c%S(Zyk%zGvmI9}|6Y5b|Pr-z%D_Z?KH zkBIVloqqE1xin7Hdvdt+4$e&h$JI<-DT^OaO3gepHl{fi3YGykvIT2m8yvgyx!@kL zfSf#By>$Ts${B$~4AuLuvjZ^3wO=!@Vi*0!K}uSfe@h)LToL`G%9~}{z{qA|LXDo7 zEh>zb=}vMFee8^Q|4N{iCB)aqq3S>XM_? z&Z#Bcu#vu*`9J&UHM*_9=g$dgMgDvtk^&IQQO6)549G7#F*UCcZ}hy+d`_if&Cgz z5#1N-!Y^FQqlqP7k{Y#^HxtqOimT1FgO!%k>-Bs^dG~Uk?R-=5J2Lc-Gn@;Q-wmj= zTA;I6U%w2uLe5;z%mZmd68NSafQ zF}Jj2K@HpYba!-I!o&MmQIXY6Eoc`ccGESDkWMU!1>VY+vgjB5nf2q)7eHWSUM4|& zy+wQcXaOfKpnqHEfcx+dM;1=cehL>3SthOigKj@_(v_|^wgY9D(W=}H`CaV%*8_Iv zcY_DEu<{GNO7&utJ!NlGSHIX3v{qeu_}$~N^PRY#$!x{@cb0FL~0a>y0=fl zwsVzmInTN=cgy=lW%WWq%u40vy`;P1%v^rSb+2BVWCkxvZMNn5=IPnUSwDIz8(|m8 zmAp@vtaS6t>J8*Az)vEwEGb zOU?Vk=)=_M%9k%+0{nerNQxomi3B{M(xT97 zX@yCihB)_T&_&YoiX8|)9cLM1(-PaE8To`4t9-!S7`J@;o}g-7$h}5Oe)H@;5ey0R zV>Ye$*sQrEqvz*>ZEbCI0p^&0?R$}0{QOrXC--KmhOFuiRRgsrhaFZnzmnfwwiNq2 zmnN@b8T_gF;nPuu@DFI^TL_w9fi@X$9oJ%OdwX(n5)%A1$sp*B9C2IGcFnfo`4vhB|gk9NM!f$euZcUibE%076< zL2YH)@!(ZFm4Jf2+jAOv66bH)2|aI?1`5n49wcq82x?*%3|AWWH-^2dL+)xiIR4Sz zy{Ktio+s)VCG^jxvadQ+7X8;imw}OP`&`F(t0j-Ll~q`H59$H{(6{nRVn0JCqg!I2 z=Q<{x1I!`?1ukzij2(mQ(Jz!4(t%l7%woLlZ#3YH_{cKjwYmI@SCr~s=h0Amt@M`1 zsIt2WrTji!Gb`TnR1!4SC?u$D2M0}UnEQ*O&)v!6C+)FvAJi>+uXEae6pcj*8ou6J z9kMaLjGHVJvVIPgN!u?hZ~4Mxg_ZV=j+BZQuQW@7c4&kD!ddXvq>*r-}W=Gtbsh z<2?lM7e-dt+S@~di{I|DPIMNLYtc-LRJDnEIRW%B{KY4{hzkNe)ikcCC#Re&sC_0s(VKCS@& zZM7TPKg+b^KyJqT7N)Pn+Ylp8TH4ysb8F_I5-KpOV2z+LzU?_})fJdqka3;q`~w$F z`@6ftj^#!!!or77NLW=}{Q-CMTH3>--5du)d ztt|%e#~MK6ov+=X#0riYI@;W!=wN+2w1o+@1zhAivfI4B3Kkqwuy7s3Q+EvY z2JMmCj!m%}tURJOWgb_>?iTkaM z2?BKknZ)4;C=&AZ$`@kTv|7Hx(66x=w$Of%6yRUJ{74t07ek^HG9dnSGC@(wp;38_ zjK6LA(wZSz%Kv!tD>@E}oxO8%(y*J3$3%J(<+1eawxHn9)KuyVE5O>bL;A}sY5(vu zGCpfju>atLDO{LL&u@?~qXsM{^0x_8ORoFk<5$6H+$`PVq^JB(H2100ewv9G_= zdsFzc;RSKbo!-Ui>1Pjqpii}>eXv!j;+KoNb}Q>mWmiwn6cLw<2@MqVEljLc;o&`S z(dH`a>g#>}uI7`S!w0+wMBsX#c-bS(>lpLV>Xmx)0;2b&r312AzM6i}%zS0=#U02Z z82+JQ1vxS>aAyBAKKHvK%5VlL`i-!rZRs;JNO*#M5}GSBi)AzB8t-i@6_rk?YgN9X z{Emcb2}gUi9C_e^3JdG@UhMri3N<`1VBd|yDW1c^?EUB0Gd3b7nyXhK;W+U6$S-fe zWZYa_($5UYuv_xXS{El-ZO2fT^;LDnP9KDv$Ozu!gs;LXsHKcDu5zAAIh}cmk@6M< z4G^hADRllX6d34P8q9{-6FjrFMT!clZ!Fxi=BBq(OBv4>wq^&Dj?!?YAisjEt8Vc? ziT&m?!!M6ZnlV3}54g_!4cRcnI<|Q3ZwT48A8%_ywV~Z~OHwxW z-Q!fd`&*4!nj6Y$`YLgPEV&ffLCkAr0*-XOLn0vU7}(#_`5wBr$y=m)-;b7@^PtyE ziJ-hC zJ!os(<1WBfdZpy@;?o<u%!J2lOOea7Gy#@LbST{?JT&%4OwgVW?OB% zc~`=fqhDsv(XiH62hyy+OHh7z%a$bb4JFsp?Xt$fvOqhOaO5IeyCg^76_g~%fT?Tt0)C++FacZHF;e5fxyl>A4g%gWyL*Fa0}ZWke^Bh&lS*D_{veVSsf#_!Pub-_!@;AR7< z)BRZh;f0-l8c?Z2i6+d$^UhDh_l#Nnemfgr|H0$gV_v z_dojnVIjhV%OT4MvY^pm5|*tbPs0PN4QO@vZzoDUJ-+7Uw?|#jYDhql$@u+ohk0a; zo#MN2@j^MTe7%)i8(+1%vnO#o3wM^XWniE(25#0X&w-dqJGYEU+oGYa$R18~?QUUb zMn+65UF8C20%DuP))*&VyFpfbe0+TqAZG4U`tn>Y~SCU1-S+-1;x!*musKwvRmF@VX3XHjjDG9fdLqvK0k$B>}wcRIZWT( zV&~^ChdE(}uT3q?K*FT-@-2*(DwF~=LRnp1k<%e43KD?N=8>|@#4}I*cha>p$#J(B z?=$${;7mlyPZcE$uEG|$z9t*mc=-Gvc^TO_9c{&;9gQL6@o*s*&9WASq%2Ss%kKbhoS-T;y6RD3vq9o}baPU4 zFEy7O(M5f)kKNWdPQX-jymvfQyZi}ZjKhj@xsb~EEmc3oEpkbv!Lm)kn<7DG9W-Z& zR}211T&g~p{WB`A_9SanTJe+HDwAVFyAy@WtA~>Rnu~{E92oddchSz;Y6B6OI~@OT zwjCu$M@PTk-{=xOR)6p&Rzmio_cWB`mt6Y!3+GHqzeY0TzO?NsQ2@MAL_{P8MS>#! z+R}ms>nVApX-$>EGkhV|Qnnm=l-ymOusdN8+%_>e2{l%Ls#G2&2#uiZ(;y^9&rxxo z6Y7_LC>(K!=+*C`@MAM670Z3--?7W$!KyHGB3BUE@ND9e@qOA{iUUEt{-F00hQ>~v z+ukLEvx50H7HaFigia7^Gz`1+<9FcHmm1w9?jJd|Mm(B`HtTvb{6n-LN$I=kDyraP z^0@`x_*v_smIuG%Ji9huD-+tkg^+J38892HcAkpnTBrt?ImT|DI5~4E?awh6!;qxwOUS?S=RTJ5w)IciO|_YJY3t) zT?dQ*5?P6-0M*&*JH^SHF|#KJ0lC*#1d`Q|NU!yY(uxW)T;~=u`8|klZ2a-#fx(Up zwi^g@`L;lp3ss7evx)udh-DmUCE`pw79wHGW{vE$6a|AEK2M)EY} zN(TGOqd*L!PfkKcCh+K&w7v^uuEnHAMNKcX%`&{*3;K(Wi4^8|vf*HEUT!zU?w!j* z^u@*~I~@+J#f_Vt{W5fIz|vCXQc7F{ZO-+BAFyU<_1r_NP5{Y0wEy+DysS*VvJbe_ zj@L0V)KD{m24_AJp12(B7?O8DkfWIi9qxnP==;7U&dCOG4BNW>8dra6gblHw7&n+B^-tHO6Dk@+EPc zqn8DWWN{5L;{;q&{_5QxsHI41>Q4UJpd)0mF_^?3yR;)E@;4fpqjYz@KqpTi#3K}c zwss8FDicPq(Y>*;0h0%F*C#)zCyQ*ttNf~){q34um~_HVU3xlluVjJb6J6`0v-YndYF{A{TJ}#nbW%(`M5C^}OW0Jh5IE@<)d>%!B#9f8 zdGT_v(H@g%%e$d0IbSubZfeGvv2FAk2yIT85VGtyc7VXqM)1NAb{VPb@S!^DVaLxO z^AMx_L#uT&e`@t6Hol$Z9UGiyf74`{SjZXvTP$U4SF~j&-!-LUQXGrEj7_I=m|ODX)z1ONMUc(+*%3Z`PF$PXI37^&53)0Lt zt8n}M?U9Bz(9?xXtxED-pLukMGuf3OX;(=}Nk-;E$Y<@=MFj?@Sy_4>_g90~B2zAz zXIY86Z5H&U9<9~|)3*taGmKu+Dg`Nd(GLeGPG4sM3CERyWI4{ihw?&U2{b>F+bI_u zG+#$>+7g*9R#Y!+Rs5zA&(cr28|_APD&ieJ__6lMNM#WKzz+z8eDykIhRfopN8Qrw z_MVEX4&RU{ccQc+PcllBe_xlxi7&koU1!+L3dYn$!$Vyvu` zW{o!85PoLf2k*EMjU@+0f8PT&`joI6lApeqG0MNTSDVPX<)`AEG+dfHx7vJlm)gyP zuvRHt{~q`Xzs(goQ@ACr`45*p=?G+gFZQPRaB+tTKm1e=6>-q+SH+atU*LO~6DL?1 z)IacecSXuFV=G3Hnz2i>jP=`f-U0A6X+Pu*iH!8}^2(K%IV6OmwXm?Lsj0dCG44`m zl=u_-PXB|5+oyiJJ}jsnqJ(eP#9Jnc)p-+F6~zt6j&PT-SvVf%dnes%3DOQE3(r!l z6TuIkzeM^$|Ne19(Ok(DDJ2yMo)2{5l@Bg~gG#vb@*Rnu%Iu0qN8N)zH+qf%zkM}V zF%?JmqeDJgG3q-vl8{dC+nfL=B{YT~C@rn52Dn~3iPzM`Uu$k|W_`g2>71Mb0%WiP z-3#g;l`Y}U9}lwESa9&R<0W-`<%`@7<&P4uQ&h=$L?8XOS|gWH*zJb!3{R7S^WNMT z-IZM4KiAo=B(%A;F5053mc$l3)?yjQu zcbLvPu|c2z+NV;frPS<|ck1V+p15BzgePr+5A}VrJytueRK;|XdIc6QTKfMZi?KlA zT2mBgC*$i|mzv6;|5+yNlsJ=X>JN0G1Bq9oUvsNBif*&D1OW(p`@&?aRPARjhdFAB zv0p!U)~uv)nr7`vH2uhq=)`q$DVL;_{#PAU*gYPqfrpx1x6JTfE4Sqnapp1$vhnnA1-hv9iCRoZ#a#>z4_DX|_4aq@J|vi=_WAz)^0;qOu7Cv!@^n%JeaaWuVO2amdOc{?pOuZu02-xEOonOTWJqdk zqV8)+nogLFVuoSVK4neOk2XQi;kCV4=aI68Bmdpx0Uy zDrBl>Vj}skDV@5)5=hwop1wSbGp4mG#$_AfVwt#KS$j)wP1`jt z#r%f-gum9%LCIju8-!zRmfgUM(gF7SVF3JrXvA>gzWelN?6SAOYl8$TipX&iH8eJc zf=&wfWeR*i>c4#Z=JfhGXoJ>%)<3)hsnyTi?Vz2VF_)En{@~>u)!KMbw}n14h~2$s zg@cRxb}e?#2s&F3wrl1_pNtSB==8jG35{|kdt#Y0?2D-@oD6s*luUlRL%Oct&?;H( zpMHCWdU^uKa+iY1xRPUHvbqH{^Z;RlaA?DyKK~|VBHy=hv5~iqH|fCweFF*ejSk-&LLHG+`YTp9|Q92S5Q)g#&A!$)4!&V2fL)(l% z7DI8%fHlY5+#HMopkrf_yqhHC2zuK&;)R6;OdNu~2T?CF(O_r+CzN+F+iTTR_l+Bwvaf5+*DL!@PAY9w}+c`@fE8o9B+d*50rgZEYmTah6-X`W4C^&9t3N`jd}eV_u_mk2i_kr zgMumTDk%cBc((Ihea0Ms>&07zrQLu!E(tXPxMp~c)1G|jkUZTtPe(s!l~tfx$x;S^ zE~^wTFTmfc&}iTGTF=4;+mFC~1C%@4{LPy;@y|4>3IOZ;#Mqf3SWtUU54tsRml=7( z!k8hW&hznfAkHf*D|Lq0nG~+h$+_RooeUm|q@51Z)2^fD09}64R^!q7 z?P&(cjF&o*adIkQ>>Pl{E9o*Mh~#L;ff^kgWY$oWcj|Qda?dj(cQ9)|efr+~i;0kz zj}YF0rRRZA-GUUa;06+oP+LZgBZl|_h1ZiA#Wu;ChRu2;gHEI6uM{zI1feq#J*CqNJLYJ_#FzY)k8!v zEF>rM@y3vK{;(u6^UJ4)BaM=|W$x<}dfIwyx@_C^F|R3MGWPVs z1ztWCcw}hl=)&O^T+pyJlc)c+zHUU8Jpon|Q`f@8|I;6sp!rFocZZi$M^ftS zxZK9An3;n^wW87cc%Rp>IEDX3<}(9G31?wpxqbUnotM0NHmm`lkPj#Tha3|s7gSe} z*uf`*7FAMG;-T%BXrdz@R}z>cAvNA7u(N|Q1-)E?tq81*m&79}`%xgB5_x+*4dFTx zEV-aM`1$i^1$J`BdC)yE;w^b5xOYGR59Z3`w(~!<`~u3Gn#@{lYkn@u*U;#P78E#GFEOOEiHV5;L8Fd=d}j;|ou;&@Hbt}zIUNP&pWibx&+n$e{gsZDZs9;T zZkm)=@%}uhhrRXkKeYe`M(iDcP(OU&E`rl$kN^Bx*5CgOw3Yx>zz5oL8aMcY+#6K> zte4S^B=~TZufn*B`!5=J;5X=ps%cHaRN+7WkN$)6<9|m0!i00nD;&IZr8O|mi`UQ> zJO%;fC%n0`q8J)n?|98|%uLba4d)eLmcXY^K42;hxfrm##TS!>bpdRJ3>#x86bvIU7j$q?``?iZfwm9!Bui`SOjeA% z2&m8gnTgL|gaE$lfHBZLyhDyx+J8(T_4p6QzqjX)!SyD@`NNJr)$`{A&Im3z2A_=< z+K-L?-)?B%K?2;TbQMk&u96A+;&LSoJ^lL9k~bt_Zi5SwB3M_K< z^S^)hg4PfmC>{#ZU&5A(`uHid6=56%=0DgD1Ebt$&nX;Z7>*iz%&_!wv9d}Z1VGgT z!h_;+fcs#J){sa3ai3dS3ZC|ZqY0P-CDuTZ9)Gw^_@zsi;BgEJ!N-E{b&qzKYA{4d zfl6_!Uua4sEf>MSz#xtfPbQBCR3$HNAkwtOJXe)|cR)EcmkgJ!)3!eY`|?*Cyen4} zP8i^{2C)Qa#2@p7TwMGOtZz>Uv6K9Lm~EoS@EvBih#=*Rj8b4KMgazhy58N&M22UK zj{{JvNtUK<1ohuSPg1qRsB3?Q!cKfq`}jCbtC_{>&-&XE5_ITG-9&Z(#4jxGLm<=! zLQa4aBRl%tyC2}Fvhx20ixALgW*6Wd1Vl+k9=ZFtd3u6XDINJi07K>8g9nQX3pYZe z3JWls3^1PXClo6k3u@@ofq&B9=7_Q?COG@_-_%mGz~ES zIb8-_aa>cr+dWWBgIzR7pVkwW?NgV6i!do8XB+xW0)_Ncm}8=|m(&io1BM=b|Nec6 z0*n+=&_4StzS_eIAT{957^4$<-+OI4@+v)j#9MhOfQ%IxV?s#KGlml5<8>VzID`Aa zN^!2TBbWzF6Zrh&rQB3dtRRxN(+)#O68fMvjCCFg3=H)1^8*}x@`JGydfTF6gpNcN zcz3vOx>ZTQeLJ%#quiHBRVd9~xY27HqfN?T!NDYcd;l}s037%ow6b6UnM3B#zP|vR z37SuJgu0_D`($7&ix^37Xg(m31F(h_PN0HG|M^@8=qXj zc0m3X?-Qj0<3>p?^@EuN$VC7LM>E)i0Qs%+Jt%un3Nt0Z7vtjLk$vUvd$FNE9<6ee zT>K6{c=Y#%bd9rXKh*zC1fHhGCsw>bUGl&J8ZPKUU?16{L#K6p7A4|kmjeA1tP87a zYfIHDFe3__(B}p@4iHtZ_Gf;5hKUP9UJjrzyY~$Q1mN|21WE?97vPrjSVb@&w?Pe> z^3yd!y$qw|m96+v1N6FK5eQY-gm!y`r}|po$B}taCn^ZvMGZfE@WA(Itvcr5S92hR z(c@=T&c9G*2_S^g{FZvUb5o2GmC%1tF@iSTR!p{7QGo%H8{~SYKGbbpo=o`fH4R~8 zVNuYggY$wVrX^1wJQlL*6;PvBl$N61uB>PWW9HYspY=Zef0}XK6>{u%Kq)~TFTo5xxxKjG8V*AU&M#pB+9(chh`N(SnW+g{p0?ZrU%n)hkYWI@1*bmG zom9#9DUaLEWB~nzo{_B)?2P7ZVf_#MU@q8BHPz1>0-?B8eF$byI#1n0O5ZGTQZ6^Hz#K}PjeP3*R2V-_M>BCl24BxgW|{i zOWIA%M40?t_BezN^hhAenR|J(yP`O<2`AEL1`f3yo4X0%{)P4RXm85@tm9^CU(Ve9 zyjMkq2Cq_zr@VjD@xq|A*9xKJ{qh}|88k-YRgR#LsjsgGLQ!p7_$B3ZGW2nJg*A3N z<7g+uPzv}>*9t#0WCX(tR|ldJ&?9Mm%eK}~jvj!uiG5>r_0D(q^xVRaHOhUhPar2? z>$CCG152=_a)zP<3SgmRLwn4@Cjd=1Y-sMg@ek9f%ispWjSG%rfJdbQJ!~lPmbW%A zy<~d?hhjI5l4QdkeFGl&i0Yx?%yYELBIg@0fmq}u`kS09XWFO3H$JuD4gU?A(A@_?f8-)r%af7t!s zg$?Lmd>;jqgov1ci~)A2IyQC)p1xGd;kPr&{i;q+lrnxVA;YQr-Krk{K?nTe%Y)&6 zl526P_73VUI`08}e2cd3& z(XpwyBbfc#_K6u_eB0pHPK{9*xdc%s{OAutUwLpWO0f7?a~h&502QGQr5boEE6(xV zt-QFn_&AyC;^Vrb^-nvx6&0*+ORf?lCaloU3VI2GlmLpDdVbK~#T08;5-cgZKoTc3 zfACTbWV%7|2^(tbXUN3Z!BF!d*+6t=tzfl)i4(R1_*(SR+KFwsL2x);pu^FBSw}fv zf(>nV{9q(eijDf-cVY$aq1kjmKmhn4Pv^P))0fJY#V+uEcEo|#J8z)(!7RQ%Hatns ztiLv;kU;?kp$lpLLNS!*?|*yyxBvM8S8JGp3a@O&2^$2Hpx-)pYJI0apwB{M9rmL$ z5V4`l#&6ybACX*&WH^NZxKN3re<4N$^1S?``@bI2*ofd4jp(dD=s3<-UiUl9$57^& zY4;~f_z1DGE`|&L{8479TDxuoqAUQ(e8=>_+{L7j2w4EVQ=ojkHX%PIe|i+QtCPs~ z@3Y(o3U#^d?N{KZ+eI@RC)K4+cDl#2{0V?%JFJFwC@46XZTo8CDJD!pY!#3&1t?K& z#q5a{TVsPa$VsQjBEwj`y={&`@a z3=D%?ROmX;_0X8-xDhBi%iafCc;^>%>BTK0_kLQDM`_7&3wK39^5?hU2%5u`DvhU; zI*a+TRWCr=r=JE(8^8h?{)155_L(w(u0WK?7Rs&}+x)qr-v{Bew6rk^04&CgHu(9# z9nWJn_CFDnNE;s;Ln#~k1EVLIr;RbKt->_n%^ZSx{&k5B6_MP1z zlJ}d3XACh6u#C$-Z13su8}l#tU3Y5dkUp1v;6g%eZ2Z)nT(TM}Lb(W)-hM)AfxN+x zYFNsezsw3J zJOB?2wWH2&c9bOVGC-Tz+4E=BUVk-pbe0$xykjW>?*voGP?;$oE2UeA5{7eJ*|b#R zGO&BcG21$5JNOdEGUAQUKK^hSS*Cz0ubdX&6>H18e2hSH-qbuwH~U3n+K}Z{eBgX~ z;78Hx+PhS@>a$)8YUm^1GZVvaqds7E9X%RN_51d-7Y)JoN}Uy5M0`Ah&@d8?kAgpb{0Mh7SSWv$pfB|L zxPY97D-?zdsbU`NB`*g90tz@|V`4x^e4X6M4G_7434K=e&bh_@%qZc~HmMH8g0VYA`k;nx|DkD!!x9Ff3(2$U}2)a;{j7VJ^IkClWng*?{txzR8*YCzv@k>)~ z-bcF523=eng%<}(+(7aY)G+MvJd`0JA)iOVT$ER4nl9eTucE9R92uF5P+~+NW}z7% zHxY0KfzbAEC{tz=tWz`_{=8+jCsVk$g=|$)pKiuwz`7>NuNlzX&hi=2rp3XoqB*E}QP;#BFgy6ZzZ+8w*SVYUhy%Yl1H?|Y1-&AVuNl?F10k@i zs&5{_mJ-BtW2Qe>;K`WB zIoy-_8b4`*GUsn>yOYHFY~t=VWe zYI?82LwX2i?V;keV(OnD)c3n$`>z?>ui4R(Bv6V%W7z#Z{%QSZ^_2Q z#0+mzvi0BI+Vb`D1Cpme+~Zf%g)E$>hpD$-zIYK7+>Q5ZV?)Y!{|~sJ0oNa>kU0nk z9WkjjjFp8pP;J`z3jw9xE%hfzbi`OQnEi)_hnad|^sWkztS#J}S|~C(I6{p^M%C^M zhh)vMv@RAH^|6=?!eR+JnbfC;9rm9RAi!o?IsK{FWh7xG_V(`R?Pff|KgHEQWgRVe z%y`Up@AEtdjmHI5U66a3$iH|YiNw0Asu~&^x`M-FXJe!BcYUE5gs==GaBLakyYMQ{ zUSD_mB2j;KdNkIHE4xMW8nTIqLk9$ika1mF;)oZJ|I?(<{GsZWZaEeblon4xx6JVP z*Ryz|Czq;z5KvMnM8(u)QA%76OKWRGVobbYlxsLxpy6J%g3V?#E5+}xURUZscLn-r z$D8oXDPx%Jeo+|vP9HdjaXv<6ur4A{U#}tpNeXW^FT%?J(vu2|%dw{b)qlpN7#tm4 z`u!VjwuoXgD|8#aZs|j}Je%N&{J-Ve3$|z#>3fo zoT}Z~V9pc7$xw#^_B8N3%GI`B#IhD%5B+khD-!R)lEB(*1N5ATQiH&fPlD^;NT)KS zCNm2+8&Vw;0XTwTl%edUk)ffJ-%@9r^gN&C?%chLToZGddGXeSIlp3IrF<`-AKnM3 zOY>)Rbaj39a|IM73^ii_=ni4s35kN|EWR19prC-%!MbZ+K%VV?l_EZUNn!M(+?vSs<<$1?-uRmw!Axx4nPxlEJQ;gP zuxS8(2~g87j3?F8EDSd!k5>7$W1$_Lp-Q(!cab%I;#!)SphjE1hI%ypJkzn+*Kd8> zWCxDyQ%=qeC>SIq>2(OTv1GUYh$6|w;R-?2g(Vqp?pwG*UQISoO90b$?JQ%qx4k{! zS>{qV6o1el3rI@)uyS)#Fs449xzwWbx8@h=p%-*&Dm>5oTcoiR`?}0%BN?CzE>FTc zJcXSjeJGt@^6{Uv$jDw8hFe)`;?Mh>5qG=Y`tIF`cjUzcILKzli%B*yh_*^R9xzxs zmQqfNbmAhIEpWb?irCrPSB_k4xUPnkwmZ8~ZavZ*{LOq^v`?zm^ZO#{vJEYihSD7{B)JG=8id! znfGPN-Jp=rP-|Fp_@5znn=pwWzzU2EU2V(XSvDesk(50|G(rT1cimwcfP({&hW7Rx zA2v;Em%}xcl`oCq)!+JTJSg1jZw?<0P>CN)q}d#WTG@Wv^DY4#M4WD5|ItQcBu-p( zG#}_?3FyA1O7-5a>5Z@J`c2t7p#`*zAUSt}dCM|=uqYD3jc_ic<-lxV?^9X?eoDzI zlH^|PJ^qUb`&-{B!yXu2L2`1(+`gX;27}pZ-C`AU4}Qu^bRv&13`KOr)1tpVkHS{h zMQILyFy_Ahk@|oA%bX^>>u0*;g?skUMweX71d>JbxBFZ>Nv6{)8p5svYWumbtfHc$ zl^fv(_~(x}**vM$!;uso*uI7GIRp_rWea?8H(Q;<9vru|n>O#8&5c1=$9sEU_`MQ~ z`d?qi=I_@s#KF)48lTP+V{A%ggM6xk((-@*&ZNbR$2AmAVjTxT^q~~qqP59S6q*2! z!>SxHjr-0SnhY!?X`>CPlqb1C4liXX{4fEM0Lh7is2+TPUYN*SRTLp}66(_I|8pD9 zfA;*Ror=((%*c}gZso#cUFGA9(OvVA9`x0NzlTr4L68SVS?627A3|42yIBw$Q~8ir zS6dtJW-jMOKg2>oph=TSOL5!$a@1#29#^EqN)ftz$omSj-E%;ees~=nB64{6M66`O z+!b8$0KhEO_Z)dO9=%tOe-sGP7_gC*#zkb&8a$M5=b`TeWpGOrG^~iT3YnA|x^&27 zd;RLw;i9sa7ndOr>a^9wl#~!^C5wY_61=4w>F}`2WHA2cU-S`rynYIh835{E>3xdQ z)2E?fhZ(_#keF6^zo@3>F`yX3w>F`Ixb?8A$lCd;FrP*H8#oJqG`*moTgrl=Wq{NW zkddw3T}z&s`(I2f(%Z^@b9o)5nel8f@eEAf(QdFW-HLg+Sg?NIfod`D8b7LHYbrP8 z$ufZCLT(i6thmio=;`q%Hx%j6_~9f?R4!XBH-G<{)w`cazL+EO$%TdcvQ@>>CXI6)FR12qgoyEP7;#<@EHiVro!gpWv%S_+!ta=JdQOZdQ?6=Gu-s=Sfh>XfKnZCb!p{rWw?nb{~@fP!~3z0TF~P zInN6j(T=u)o8`%kWO2X{LBgGuLMLJejZ18V&7Y*^IIBV zp>)Z*UJJL$W+U`&$6LYe32e&gWk_mT1LDHXt^9IWOvI#+^G^r*um}dJi1Nja&7-dj z{yQMDHgfX8rgSM>s=99;1GlXD;QoR&@2OTdfP$9u;P5abwP_=kLw9Dmy(7uFfo+(F<^q;- zw5fHcOc&Ml08pD-TUi0X7B>;qFDy8ZDJ#kFWRdZ(*oWJyNhzghuGYReNYaC`8+rh3 z6EX^ly#tAKVON_7l1?fK-lwo<2cjVFX(F^K=%HG5C3U8_s$I=NXD@moHsj&5pJDO2 zp2X|;&EbhJSWp52FbrB5ucoG^MskFx^S2KmBE@CBdCX_R`Bp7Nl_aw-k=XTpx_S?= z!~nVD!g>mR6K3^cO`hkEJg_)8Io*fmRv@b?lUvH?FDEHU^>ATx(8KWC`r(=>EP@lh z^VU4eLR=3%nn36meJsl_L3VWNNgzpJ6x&tqQOwaWTQ}G5vmhccWl~GrhkBUO%#x|* zJuIANkCY`>O=n_fopszZ32y5VS~V>~Z5?#m`Wx*bMe!g|AC76a98!Tbv)JZ*WKjYC2!YjE*Md zlgtxhr^(*UvC1Q515%ZQy!Z^ zRjYE9=)2q*_1d$dFxecn?nC)&Q0k>mA(QA~`NfL87khe(N_K8;^*a}JjP4%L z408d?#>Evb!Zgn8ZF4sL^TvE*Uh#?*|E zlBH5?nJRQ?HUHN_G7I5?bI`9vrR0Fqb1&`bsocQrbWp9 zPoJJdMjb< zR_Nkb81)+tMIv`=kCQUNU0M(Xa+?eLfkcu@Dk`K?H6Z(|fy}ui2_l_N)d}!^ zU}7RBo#1Rz8j(mUDZfS9lLNSr#AkDYG~YC`XA3ST)66g_&xQ{5g-cJ853o=H#M?RSb2Cn zF{!DIAVUI+0v5SR)=ElGyB@_n?2cLG<>hH<(v@#Xqg9lYM7-5oW>k3WoSk7}m_WwW z3BW5U+3G<5TAO9d6ylkmQj@MFPpbhUMOr*MgeYw~JSdkJO%@F7=M;PepS z6N2P97M9lG%1$({SimNtk3Mw_MQH^y?^#&(fkVWC>2^JQq>gIulafNGprEgTF-YaH zCRxW@x5!;*_JMYVj`C4%n%H#5MX8FrBWd8dq4GPlMuP}YHXd?Pme#@D7D*F><*281 z^ph7iE3Ql_#R>I6-m3A)WTdGob&_XZ^3PjPrUH)nZP5-5eNR z=t9wXGawBn2A8@c^q)YgS0((_z=jhfvTiL?NwV6i9EL9iq3a$^xRB!*Glr7nS3H); zKhnHncmKqvrhd3A1?oj(<0+uL(d$mF(Y0&Woa;7`do8Z9cTfideqTd_Y;4U$2Htyr zkx4#;vg`0iiycnpPu8yRJg?D^h6_m2NIejpf)a=U%0l24Hl1Bv`M6Y%PmeYnb0oA! zQE?nNSXg!YGod@qfV05rZ>N*kX&YD4K0XSdn14+!Y^Y2j56)~T!q$PQJDNKCIW=X> zDMuq{rwyToTNyRCv)F{fP2s}3mKOEI2rv}lPWt9E8q`LtS}$+-ak*ra%51>_D%|D{RB$$f0|JE_;6E zLY6O-d!a0@i`zJaw2KkGPxvTDv*^ngH3tq`pl6;I{uA^qaYsYp@C(B!F*$h^aCJ}s zx3#tgcZ?wKmb<_AK=-Ojtz^!dp18PduPb;p>t(^OnF%2W1E8V2gh+^q+1c)u8b=8& z1-9N*Rz85(+CLuwCnyJJTUuJ0|H+Cmx)Z4<4SUkp6R)AAbrnZWsThT#<|3!V3|J`r zO8+*}#GR!~7(y2)ZAyR1rU<|Dncyt>(bpGXN*p#4Z~U~wWDpndBNxZ1Uma!RLGCnG zYHC+bkQAyBMT=8__fC?(IHhT>aGL7Do8*5Yze$ZLkd~|xJ;~xV_dTwYK+8jtR#WppUSKvo1S^w_gyb0pNQCnW3N9w%@eFt*X0H_C-w^^Tc1T8Bs3rS$ ztAdX4sEuAjr|kp;9GC1oa|a50fB!KF3m4ZKlpefiRgul@98K+-6bRJmr-~r+n4-Tm z%H|{GP7NoxWsZF*o}yJ+NT5puU4s zK)vhquzMpOHiod@!k@wmgP~{%&p;4h>1bh_Mu&gBBEtlMts=Iq>B|Z}F(>7{0 zbT(#`@#C~Rfw?r4Su6|Q7=&-ie%adfqttYSeT2fVO7VxctC?gaIU4rMn~xD$0LBCD zQ#*fb2WT&1`M+ zp(*it9Rq+{&m0%XDjUIMJ29Ji3wA#QgBQwa#hqvwH6qt|8z3WMDcSYW@~;bN4C0eb zc_H8?OP>O?YUa!9y8LCU-W-=1>zoW~!8@XlfY|ddCJuW=8WvVo%rPbt=ld5O=mEhY z5IwmwO(Pl^78XN8+;#9@>~=tG?DM|zkHoHcZeWd42^dyMFoJ$2fZdbW6fnjmIyPi> zX=&+a_s_$9M*76Bsz6w35o2n~n~^>Hkq!evV_KB2 zq_(j3uLVf+OKWk`B0x?~^yK+YETM|O(+jwM$UTW!c!NG2SDpdKg6P%!Ybn&ZPnPOdJjfFW*^8p2~kj@{B~ zx&xy$?~(nOnu7GcX#fE2M@q6IPi`2#nZ5w{Dkk}}cNh05!%*qxPuO^31#bAnifwZ{ zqts*6vw4%Oac!1SEd^dJ&2Z`*T9H1cv1|GuAnTvAk8)J*;54eE^dt~hWcViJG!eu= zIeB5L@A(`~i>u`M9#sDrHkAHhAbbC806u-8Txo-cGf)#W*0M&mt}hqxLxX!YfW$N~ zNcQGgwh}h(BM%P{f&9Oqpoc<4NThN;)cB%v&sPC|hLvS!6Bx$c1Dw4#m6 zvc6mK_CwE6^Y<&z9m#9OTBu)Zy`$ig71H)X!jX;Lo@5|wdG6*e*1qXaj->c*N(u#bee@#Yg`N?J!%a&$D4fB|Hhzf&lOn9dUP&?>uz&MjR;={^-lexZR6?T5OnK!KFjEJt_UK$1{9{44~ zgJ%BA;-GI2=z=yEtw0I$)Hbmg)DADp#YTJzZ_hIo!$cxSQzU#mwY6h9Rf2@AJH2Oi ze#NYa8K(56T+F;2SJ-MY=KAHQz)eHturdajX3wJPVi!Z-w7{@=wAGIL+Q0woA$PuH;1E zFwKU4a$EOK_cV`>+PZza@}imiGJ5Bwt(;026hgB3{z~-B`D2jyL=L-% zbst>u{^YH9w)55i^U%e0x2wQu<}{ZCXvk@@n(r-OOS1fc4%^~=QEkAv%~iFfRl9)) z*}T|T2^>uZPGIFh+MhOv!Anq*6YK^KoPzt^pzHt{*VI`Eob>#ky75<6!2I30E(en= q^}(Z)-A9u)f*O=!1R465|1&RQO+9fxgn=8h-rCdE&t;ucLK6Ulh|Tidu;7#i85Kn$%6p}O{l2Cwv7UYXe2+t~3kGuv3`TG=~TS}^Hb zTRQf2lL14Fm@2B;|9cPqw6;TX1YTMkqX{tX) z)b?**{~4vUe@&q4$C~O(=PwfRtcbmc__?~-SMi~usHNAh#wlz}YagGbKCq$i!q)oP z9rPCaS09h;TVtQ%xHO-Ta5202fr1WuoGj+Ile$vUH)6t0sI&VJ^CLpI{s`X>+|xdN zlWMl;2;9?Jf-JIU2sq7?_|^xk*Z9=PuATA7HT8$fm4->GFbHq&kEgcv;E|1Kvq0gp zcv+?8pF^zN?+v-B{bJn_*$^|K! zd?rBMMYzr@A&QOaRVEti6tMN+wxm(Nt99Bl{8 zGZak{)0Dp`+wpH=nY~Qp)Gqln{xU`r4V36mf~M5fFX16=NSlp~sYDU#(rZ zPX5hoe!z8ZwEXMER+D8rTJeR$l@|nPm8=z0y)q>{BtiL1_ArsG^fwG)?rv!YQq+|N zR$4n))(tYd0XgKWW;?c%hqt`W4HzSzjZdYQ}G@19+qA8B{&4Dz}vSwjAcn zl4_z07zx^bLbcs8!m_oGgdne5AS#)&qrjrnsf$v;$fB{JvMq^|v#z2-S{!}o8n8mmcQ)||Q5 zly;_IS0C({)$I9ik2YGzeh6*a-0bBZ>7K}=*R;}+khENvI#uj$-^lDYy!bXduV1gy zGPn2~C(4YB{CT7*i?k)tcB!n}&p*6TViOh9qdPUtXc<=bgjJZll%XL*-({<>ww}8w z8sRZ4VUm$?^m~87HamD`b+L87d5R%e${qhP5zL0I$>RDa4B5kxwRnOsrr)H+8DQwX z#JE5+NJ4QB68`9pg7O*V!+Rk`=c%ovC#p)L=e~p?Jmw+XX`#6E0TO+AJgGbiGCT%a ziD+-|eOu!)zq7Ny!NBJb6}5flo0;3}K*vCDHI|i|i+~M3%@{H{^&~T&cRwaNUVWSL z=R{z0e{yNcyu^*UYJUkp~ zXBQ0DX@ngi*N2&Pni8%1j>$|v;)jonC?-IT5rY}(m1byIpf|^6XR8U;A!LFeZ0u6q z7N3cUiE(Y$?G7?e`=OFyZ*)R|>(iy_YU>!wuwt!xWk&eT<(gnytYuoW(JcWs_b)Fm zy?m&p;~Nh9_^4oQg4d_|OYOl?3@T&V)pl!ryUjNqO)IbUzaNHd@eI|_`=NoH-w~Wn zTQ@g)T)_sJb$_8T5y z&@hBkb#-^sAK!4dU67&xT>Y%Sj(yf0MhRJle){yuUwglcCMB~#u}IzFWM@|J3OSsU z;sKj?KN*ZZKic%XxjKOvyI-HJnVOo~yEUCnduI0LtJc{3nZEf}mcA|GibWw21P4fr#nm z%Y&Xr=me+d=i!SLro+@sOfptCXKN{IeMy@uD@x@?z1LS4z%GuSLtnou`(n_S$g|N- z=5d;)SqrTQd-|$+2B3+Q*WP(OD~4Js_G5uEBMh&LF-ImT98I&?!~ODf-)YW;Mk~av zKr)7@LS-tdEey&bAmDMlHHksYxiwwATTs$u_7OjU%epGG)@(F?@E8^26y0 zua2iTMhgIukyWu6E1U%6&(bK?nl#)+lF#L^m+P=70o->P?lsROwn(qu;ACZd3H?EoF~Dpl5l?k4YIMavW9z1GAQNSUthY^ z27=_kFyvzljH8p29DOl3h_M>$#oN!yt|vRvSX)1S{2xIpa$U#JXq!vIe#s28m}k zO?SUmBcW4S|M20OJZ0zJy?dxir6z;vCx_yak{Q7UILXP$6-o5EQx&Gbjt1H$!=gU9 zL#8Bk8l2_k?t`T*qop~&4^~pkB+|}H3Y;!{Yv(fg2(@HE3K*(+=!!bKL5z_AndC6*7$ALj3td?ut&7qPIsk5aV*N+_lU*RRST0mKQ(WEQ!;u zg59XyT9k!{uuMl>9>=ZNI!X8Itjx@TIY7m`M(Z3lyB#qt1$UO04W?9^+*~HThAT`J z)U$#KVls>~8=^(wODjDw!(s<)mU#`0je=*(xZA^$0>N`07nX7U>^!{GI$$$kTgG)f zuiA;NJ1IR+|DY}9hg!ad@_|P!nl#J%hEo9{5%00<2SZ$Yq8Ls9>!C`xb+FQNfMd_7 z4tCgx)VvyRzBY9G2BR7$Xj#oE)RwdSHCPbfcI4OedYPmmOkmM%Ge@OFo6CCca-*PR zVbhE1jer49)w2b7J6-x zEjUR@Nx4#tL?Qv>b-X*@tW=;}Rw`tbBIw!V_UZKWbihtaz_8kKT1FuJl#MBFi$Ru*xdV==%_uF9xNjXAPs`FvdfGO#cvYM&UZRFzRRX-j(zdD&W<>2Gf zXbOxJS$k&nv1EBlZ{jj?C3v7r6jzQM=@%Nz0@#J$cGxK#|Pvgz?#X%up7}ckL45SJ@uq?6JZ0et4TxO~O%!*+i`Y|F* zQwh#KKp6dq>n=F1%O97f@Wg7e%-eCaSWBbo-At2vy?mBLg*W@) zDQd7J6Zpq!&YKd|MwmmO?wR)hWhLXBSa z>wsBvAL@fC`pt*4F-Ql1h1^`9G;cV~`>hPCk>gtnQiBK+Uq( zPhgY9;fF4Vt8zpLM++m9kG@~Nd?`bht27&B(yBAKqza=MI?hRkNxZ~&{~&O4X~$PE zve@qK=CM5dH)8TBSV#Jl_y7d(hkqkblcRM=> zBwz%*3L)vj=BA|V{d1M3nKZQay+19Z+V27PK6|>qG@2s^1Rm>opAOS^n=Xn;XsFWM zqM3DMJasi9&E}ijVHyJGh>T>g>yZCzC9}iNL0}WxOvh1Gc!d&qn(GZ8bNJE1&u@mV zN)i$Qe=Yb}1K`Xu-4BGz)a#sflS4z_)i<4QFqC1e%PQulroQ3`fD7pcNm}jE^)Pg6 z9aM&qD4Hat36aWERhW-k0j65VZQmRAS^G+S`o-H%D6^Fnb1lAD#3_0h%&Mw%$OU2In$_e(k<%snWI~E>PPt~&y81lKa3!jsP_U3=5 z`FBYuZCyY_vu-+7WvRhq6aVf#Q4Xq|Jqi%)px{Lf=g2Z^*6by_?yOIgQs#xfY508ux0 zFi0t-S*_um>-OGVu#E_d$jKCxdvX_m4^U<{w{>mmEx-9QDIZz#s$t51;Kj7>wm7zv z1HqaH-EZxG0(3Am9`7V8cle_{8MSr|1*UO;sqWXuf`&_-u~@A$ISz*`bLe-Zq(Wp4 zIJcPxIp47#W5-;-wX~R1M!J3`R3^nEmi2Sk*Bi0$*rirkQDZo<`!<}|t9QjxxJo$Ws7 z-C^T1h(|uYNx5Q|E}5J0CCqu5Q z9^a;g6*_pJk)>*PmPQ`UBT+59|ec~$2WFV-X)`2M3UjOf?l#mZ1 zId(A{!8EFReK+m1#r`XZm<3k$p-3{7%nc%k8G~G&6l8t`&$8xJJTiDrVa488a1P zmE3prYqt|L`}5n`$qh07{W}o_3k%;-{uxA0E_8b)eFvXm0$=dL)iOqa*+1RyXcJ4i zJFEg<@ILajOYQ6>Y}*pY%-wdv+IaYP&bd76P*ya?EY4YHBX)IFhtLd2aUfS)?Of=q zuLOQ*1azfgGB=$HqXH+<(8b{rztC;HUfoi?6uLFZELNjaX`?6@Q^_l?Ci;C@K!`R+MJx zl{9FtdHd!RMb>(SKB3QVqq1$0koz!W-sWvrVD%jzP>`MbL&!>gjBVVU$$(@}-yAsA z4yb!~gkEKZdz?h`g4%VcrXy0}*yFdv>zr`u^FXIwcl9`_B25=7*?g^(>A0HSbt)h# z*S?Mukq)O}oN3onEF!2TKb!BtuKRb2(xK%je%#J%wwVr-j_HP#Ji9_jCO5I{_c7X} z$_N+AxCqT{L2Z|%b{H}z*BuhC%*Iy6Ql~JSm7h;C*UO0Xw5$rWSW0f>16qFky^O>ooH%Z=JhCc% z>PU3-Wwv9s3REq*oq5&Muq0!a%pbM)w3F$9^la?Eoc}U3unyQgD+Y>tyf+DI%@!OO z@xP}>N3BGhf8Xri*i6eZcv^i;50*__?fby2`6oN$X;iCZN*u2fu08&la&eay#X$pt zS6m8RWPA%p?{|LZIXV|5kNQhoR3217rV^?CqXGxb!RoYEWtCk8q}B9!1^vyG8!+ez zrG!YfZGUDiR}J&=*mxw7Wmm;HIF*Ed3wtLl$tE~PR@*p3;#`sk2d2mxr0T638xo7K z`?<>uxR>Q2e>|G&=}X__#>;Y(fken@MtdrxZaz!!Zacux9K4Fp^G#i#N$~+nOJ0|x z`3+drGN*WnBL#c=u`Zu{jlxZ>&eL+jTVWocgh>6#Y5mzRhc!UigLgs&M?nY#@fDEv zEr*yqbk`_^2c5lpLafV1wCkV`-l$?{ePz#v6W;5^LV*JFdI*Mga+O;?c&|@ZZq&AF z8Fg(&us^wy&u_lsVPz9vu2v}6{*7$(+o*R@iT{O2Yq-|p;;vOO6gu<);v z@RrRmPFB|u;u+D@N^4$lxMftrl|0IP}0-p zMG<~!b|X|5VINZNutT^4PFiX+_64P(5hEW1R*O67%-_}G_3At2REQyNoO`EY71cIN z|LfA>!WnC#6fLc7!4Q4JuHaG?m!)L(VeM@-zWJpU5gqPNZjK0S*muC3Aag5luBfDC zlLqaiG!Xr%FP)$ejM|rFNlSV?hUd65)3V}-FN3A<1H7<25$m~J={1j)=Mx0PhGr`z ziNZ1E&IChq3WBkQ;&Di#jQ71hpxOkG8pMc<-#g=*8GN17YdWHDt|F+vxcHdlCwA3x z`y;u<5VK2G(b)p9c^A_P`mXc-WEuqV6BT@aAVXg2`hizW30>dE+5-#03qhr-(3p>l z&7lH#Q3bSc+Ol-~(#xPcIS9#x3{UuVYRP=0C-caAK?d^>+8*RLVJVHGLa~Oz#Kqac z!nasVYb`RfACEarm{u+2sP!_M4;4+nfoey4I08GjkOLIayDM6R)`1Yty%@(sQR4l{ z(NS!+)}-Lp%c4Xq(wW-kNq}MY@2Y=>5T%g9yg(K}2m+>Wdw=5!=07FnZ_cdAWXBxT z5ihIPJDSq_!>PR^7=jgXugoyL1E0mC^aK2jCL}>T=k(-P!y{T9u>5qiNgQ{g>2XJj zw8qdh0>^A68)kqVmn0x@o*Y^7!%;g8$MeI;Zj`8-ql3dd&86GHHo1|-V#&<^xEgQ$ z_W$m`1Pl1;>5c4M=#L(#u-dsSsN%EAI>alrIkQW}B;&}y44IjyUe1opU3+|;aWhlW z&|m?taizUMj98^!TIL+1)T~`Ma2D}Jt<4v>C`1Ou_iA(=f>f8cu&G(T&qpF6ve2U3 zu*~}t@M^zbb9;szXOf#$xvu{!I6vrEd~cq|c+!V?59kOYY4ENvqzl+6ouTaNe%CZN z)+tAsE4BKo`^Tqm+Kl3>7OSDGfck5}MkahcxkvC$L*qba_KV*`533KDdom5X)hLYu zN)ON4ZazP8k(SVjgB~8>|Dh2@SSj3e>%zYi%V7@x;*NkgEvR&fB0H}uc5$tYH^A!l z8y!W`c8NzjssBpRko0z)9kZx(w?)$0ds3Wed8TwT;e}q3bEi`RJ>{9G|2YW*Z&SoWq3} zPFUd|njZJ4z#FD)1+h40$@H=zj8lhn5z+e${O2s;jPl4~v2;!tQ}Y%j$W^;5*ki#D zCf)kgII4Nbz}!VkmVi=1Dc|lh?j=2-W3>`r|1|>=T;3^q!7o^YiM`eZ*@$okckd}! zBWjpk)cCG-vOz6pCA2&qmG3V=V2{t$CA^)pAikp|&OKYWt?OrHIBgM6dJruUch^W! zKs4d5Vz_7mfk#v-WPp-ukP`Xn8|WTF?hEco!||sG1c&??ey(phi99||T` zQt`F<$CcpP_eY4yahHos!}#-YD2a=-&avz~7dTE#^vTqi9! zVId#me0~U6OUl;(S?}SR9A3OnDq@(cS$D2Gig+Rw?kA}e$3h4r=9C&146h~s^kn`nEsR1ddc05gGldbZjaPI4Y(m$5SgiM3n4|@ z<9w##86YHOEVI>#cc%$tI9zNryz^Z`a0?n0jI^c`eNfm)Ly;+nsquIHGr&kgy^6XR zoS7JfqiDWq!Bo~1@@*P~pp-2rJR#|jtjX=dadF3C6=?4>4XZ^TI%;KcOx;yV|v=&L;QWk`}5;j9BHS`x?Skv3s^}IW&X@xA$0nbkHdkQT*MF z*)`U2OI(Oq@%`b%8HkvtcBu^fW2 zj$0F@av5U&{{C5mce4|UYKWD+?VMS35iyQp=x0uN=18PDyOR?mf{>$g;>(bYv#{t? zEKehU9iN}SI-S(>1OGf^g*l41wemS3eW;6S%A70ve71H(8t38HWBp+(E+&IC-WI?i ztHVP{>W&b~=x+@ehya|@Pv#;y|*D#bGk?Z_KY_rzX z;jYWY+4B*<{W7hh*rNTsjghIW>WJ6(2m?n5J<{;Nf>{UKv9qiyr^x6-R-s8rqFUK= zLe>bztH7x(N5@510_pCb6U&_f>*i8xGMZ-8JDeA#S`CcZ{7n3fXPy-9DeLA&a-F{8 zwpM6@%zDP$cOt-(4q91WZf$QzefY4#VzSJ3seM^&`9s$D2|_6bJ&ozcFK4EL7=yL? zdHk5xWO|Sjz#o5Pu2fx87uHt;70^s6Jntp2NS6!S_v{?SQO|;n*uZx8G;pG&LqNMV8lSB=RvK4(dk)`B0wH(}ykvZ7q-!pD+>}7 zhf>`3kdhNe8< z;AB%tA&YF@3bVWtFP~FdM%gheffx>L2MvvlA52;3or;Gqx$*hptHYnd$X$`*CDW5X z`wz9fq#3@0)(vT^3*_o6&O&#_Er0L!j68q#cVjZrv*gi9RPNjMlQirO_9gM%mZ4zF z>+5Q$?(i9YB7Q-MPtPm$gEbkhtc-`GH@Q~C=&-PM!c(l*-loW8tB5Ys*1_-$IANvF z35Qm*o#Yy=j1BmK$k|+PYxL`Sn6{51=afv__c*L8Je7hr-A02Vf+9AO+gLf^`DP*e z8UtgOaX2Mhlh1BdDn-D3*_;)J8c^6QQ(dDLzpa^-moUZi**=5#?_Sjd-Xl$Os7Wni zznAADDj~IWS$c+A;HMs6okSZf#ylI|0V^=Fxm|&x>oP6J=0Wr~B9d79eHjln^FjzJ z5oY{vUyRsS$QiaTl;JVC>MUfGBHe6P4bXy3=S&b@XYM& z^Ye28ePSGBCrbnnN;9ok8MAJ~QmSX{nkRK(NiEtnzrc}Cu9#T_qyx*D6|CQ&2z2tfy@8LjuL#9Lpo1ItJSk+6A=<$<% z(|fcHAbgW1sSBFH3QUtK=Nt^m!Jc7fRR}iC$eV~Cwy16GB{;~(u!na}4sF{ta@Q@c zgun(znop;AvLUn-gl{X{%ld@G_^l}IKfw&(36YPvqd}8`I;#bb65kKYfXiIS`M!N^ zP|tnB(ev6S@h4u|snS=F5d3Kt1mWqUm|9zU@4E*4DZ5T~Z!l_D{nl>0r91h>QSNhG zv*-^C+~4Sg(|{-mfel#f0oX$l@&7(r_#mDrSct7TG>K(cn{mr4K8uXI?^D3XOL`&r zZDjQtfo<~&Oh4cj_bC4TFmEc6)-T>?;o8aVEW-f;0PoR4*l0ODqBOPP0pRXjhdLx$ z^T)9&|Dkn`9roZI%*no=R}wXE{~0H$33A*Yqn3KdU}??nYuzFU=OO!J9c4{mozMkt zddC8$s=#0!D!_E4d+{(H(`Il++@OFS=3nci3gVA6*+-rS$r-_zyMDcc-^M0NNW|#< zz8JNE5A54dOtNEgdiM<5_|lB>9)<8F6e*Tzm+luR2Ei1~bY-@K_SA49+|+2GFIjgZ z#t+-2H03L_Lk3>o%hyg2@r<(3O@#nQ`UPRwvSyd1(b|s^d0k}g$gjjHeii0p({)4` zNz!+6A%A(f9P;X(?!El}=9c zpXbs@&F6Z9NBCJ%-*8cNV}gpYn9c9NaOP*o$4_7nfoa7tXHIf6TN0;!4=k*x?~4(Q zN^aK$R^4m(STE53iA@g`Zc*c$aPg;0tOSSY-?w;8h47m7Xp_oG=z+7fPEBW+6Q*j5 z6%e}+td1D@pu!Xzyz0c0&z%2wkI5vqG5{Nx-ynVsy+q zV>5~t{)cNQ;IE*rokp^Ql+{XNzoU-H0L1#QS%=L%^LU(>BN*VJ;fx_HJ2SQx))C4n z$rTzP4}m(SRMJ}@tq~>jm+|T_gSAtGgbt0w0(a+4L-WSQ7i4FT8e{GU?^Fq7-Ee!n z+IAbSa9?`j0S(g8l$WJ+HJbn*-R{svk~!P4>4*13q^waF{spA{mHvGcMq;!DWgxL$ zrk{Uh<Y76-F|W+8k%oQ4F?br3`XwUbPC=8rxe2pH#xON!S@dt zM@Y@3J&`@7y^%87Yq%|#bOi??76hZK2&*F?Xgy^@Jo)Ka7*o!o>Yq%dWFB1z*b~lk zP?SupLZh8!y8xpSy$gu%lB09@{I) z6gsp}jLv8m7=7rIE*sWapFI|VB-J1z3qAbQ7RA9l&8YgqA*|e+Mz`3P9!9OtJT?Y& z)+BY7Wr#r_lJG(GTLP$iOvl93cEIuFq$eiH(5pTnD+5B@1pT65Df!ia}SCTl&FZOpW~7ECjX)DgTGYJDcI%PxHoM`S6_*pw<9VCg6OYQ+exU50{! zC8;m|m2})QNE+u-c*Yn5C$iIBe&GJ_v<4vJfF;Fi22GNr%p6<&S?~39WcFJ;2GNnJ zfAc^4+^=r;UX7GB*p+M=FG;SvmZl>vrKZR`wt2LoK-4u^wO0lVI4)V8GD&`ikQ=M) zM`h`_^LE!8{+zpQ|1P49E`j+VjC`6=l0jo(*i#EhuV84 zN?cU6l^VJCu5PgL)Sk8RB_#_hP;Add&8RQxnJnf*V;REylXfsLdVw0%-pO${(`|u^ z78N{1obr2VwBztFHWNZxVb}~(%I(ZZ#WM2q9WQ+#?V>q3dL>{uwMSS?tDx7l&c?_( zPE0C2O;ski^&rqW?seCxw%Bh2cQscwOHV?`$>-O4}i{clC6+ zjqhx*Ac>@<&z@6*NLkutYKz(J*l5mXQ&4glP2IZiD6AxSE^-}T$=bfU7dS=RK9xwr z`q;#=g$k?K>FSkCp$%qt8EeoLT9QEGh=`#@^)*Hcr}sZrvim z;5mA4{X$mbptYmp;{2SNiYf}B1mI1)=HH;8*r0-WLz!S93*^Y+t2ZeG?aT)Y0OmsD zy{PEf*%@#qj7ECj=H?e(!@Rtf=U^4kmF~}K z)lin8b7C{+F;XkZMNHL!bue^0-AD_K-lnZv?-z~+3+QCu%_dyJ$;K~(m2NIDkA(2u zqMVHVmOkhG(tne-10VlQ+NSuarGANO7&rUnDCKaw^C$?peX5&#zk0^;>~b|-h7%2e zh=8Tna`8LteYi@=6IqpNN^hDS_fKIH$DW2Ct75Tu%oMj#XPQKnD=4Cai^NC`7x{_o z$~+W++NgrWc?6|f_exm#LHdUxWo1h3K0zreWUh&3lN(VKP`<(}gqWEJw}~Z87s=AK zu4fAw6!82W>e_6${7sd$qQBx{82+5deEM|Ak)VvWy3nd34+531{Vw9ol|V7pjI-lL zGXDB3uFi1j2~#ZZPuEge=v|#`!3@UEA8SmTL6hs2l0}MWvcN*CEmjUppa7}syL4}G zTkYn_`TBOytlDg?(vN!2gZ0*2yLHz*m%5heWWy8C2;!(|;>zQ`325Mn^xbDzd+`L5 z(Bb7leM0J^hVx6l2J_vHY{>|E4wH6Sgd2LTL5#^%K!R+pv$6GRvt-F{LaTi>`*Ov% zbrRkIoJc>b#MI#;hFa@TD08Ivy}UWaK>GK(g$zQ{%M6Ovv%BSQP`ac?;{lD!%*q1L zU<)ch;V>1lnuc94wt%)5>ykeI;PQ9ibxxUis}zf4ld7jw9NG4sO146$S7wMk@vnL} z-qIJn6RrK9V9=fxAMh{u@tMYC3L@?bfvLc6wYgTEnC3X6-!NnMuwQe!>_KyG`e+<> zdU4MBP*i~*AMyT@%cl7mUKv!$W17Rod#^dhDeu7GJ3t7f(RZBSWXdrTf+p}OUV=MZ z-DyRuQA}NI=lB@6Q7q0#xit2qDAl{P_#wxjG2}OFsO%H+&w-|cTEJ##!1j&e2UZlP zrrRtpSV{T|R;>?+ii+y&;zFm!=3etlKmfq3eW_BiHSie&u$?P z>#~JmNE*Sz>=KSf0Nt{UsGXS*;XiH&R?rhz&;lwD1wehQT4zTvvUPg>r)xU<-MLcj(`KRJ1k~1KVfmLF3H7c$MBPZo^{6<_FR|5BH(};w zY|o>j(Z0HTFPzALJfaWU*Q5o!p&H-~t7V^YI5_0kB~Td<4`4V3GQJ|@PCsaCH;p5Qq^D1)bie`q zi>tUW`e{L{zx}l9&}= zTpHa&`5b{545@Z;S$>0UtNifc!NJ7YqX9~ZFIf8uamg9=f{WVBW}E1S+%8$$#%$nk z{&Cpd`3=R6Z(_ZPUJ#ZEi(+4TamSK_v^#{^R`xHQ)}tmpQR=4(b55glG6<@GP@+Iu z`zJY(tQ#|d=phRmXB=;BL|Gx2KD&4^ng$(AZ=xG?M7x2}=jF9kTg`2uzwEMupt*dm$UV{e9ZQ}a2Q zY>IjEa6$aKw_{PC!o;m!Uc~Z#R9JPr$+At-js_Q2ay00r$H=1wvkARo(> z-&SuI!eIu905Ig)rObKYMdbwU!XO2ZJk&l9x?!y49^^#N<1B6UEI@WvS$!>=GyG@- z?GVwSeSm!Sl^yhZfq)|c-(lH2W32u@etRtj1} z4oOa8U)=5C`Bt`h%;E`*2=2}FX<;hKAmnUAZ8Ri0O%G#^7lRp(myazgz9c}w`;tu-vbbPeY3FeVqL{s02qMD8U%pLK#nyH zB=NPPf?ZIs8Hu!41o#Pj$HwBYq1f8T^dvkY0w6SrmzMMHegY1_WdZ^p7_BzY0Rh-n z_dmZL{e9;!EHRM;56=rmE4H|_)D@iQ{_8pL5)krJF)#!suhiDo<_75f0XW;#)PyYI zo3Su3cr9RiD+{&MVAtaY*b)6kT8&Ly>crXC@1ek}V5l0v=)v1m!0o5|cNE?t-R{6^ z08enJ*w}`XS}5+IGXMaTC@(M1{Tyk#_5=Qx5ub!nJ?sxZFTI!m_G*BVkcNf^E$S1E zf#~DQ+x`#O^5Sb68aU9Kar7yNm|PwYxQ7VajEIX{-`$17zWV|@md`YDD^S4grOnL* zK)(JJ3f-a14_0%7FMuzB1+h_3QqJ;t-H8PFel##F8Y*f!=uM67l9;0-=Z27Mp4=1Q zQ83gN>&F5IIzZVh|6T^mV|3sykYCZx%*i>~nq=eUeW`TsPIa(R_;vl7n{NR5k;DH> z&ktOmP-3lG=%fEWQ!8RNf-+|6M0GcGEuKse4;kGem zfb!EA=<~!R@b9hoii?Ze6fA^JPEM8^_ls#-+&z)n6HLtI=I%b5X$@6YR_;#*Q&Ldu z)dH*lMy9-VQ`(Ii2i0`n^g3Ew7jSPi1APDNt?TjD>8bO0QwIQs2Sf^;kZED*wo?cA z4i^`<+<9LwFu3ouPP*hbP>H#|zBUj)EesUb)Fhz?7IwQxF)^PbP5n$f87ImZW}T|q zD*%oQ$oy$)Am**91)xRl{=vc05)#m2R-kVK-;($r)_nP;zqj+|R?@!m@bL6GKn_=X zB_$r*2G1MZBWzi2EH^ht>dw=<}vq`ZX0Q_E+V zn2yD+wE)O z!gK9W8%V&7`@WzsppJmBK9v<0_shvK_QfOxdT@>=^x=b0p!q~e1Nv0l2T|H+HCqQD z>48p)BtEA_ea2dA^nW-}ENf+D6~m+@&I~YMoE8o=0CB6(B##woD3o$pPAzY4x-R^< z=avmjk_t)9H83=+G#M1Glk16Q1e!Cp-+K!JDh$AN6F4pF^xB>p2aVn~NCAkG$P5TP zTsIVSgc#HZFxfJSP@cX%YM{QtYqRhJ*z>(Jf_Pv>!n?Po01Sm)@54Pq<&nGd^Zw_C zIoa8qRx_$cX#gY~=Hdc$xV)jJrey+)etWu_rpqSX zV-lD(`@TZyZrvtR83jyTr`{2R%#ej5Lon1z-2xsMn6Vw356lQ)ihj>N=do@vA`~Bv z@fl>Z9^0TvOs`Osp|`XvA^3vDe`;f*%Cai8;F&EkU1gbWX}>dv5_M5n<-* zaziOhOD+Q$dYL-S9?K$(>J>G>{;OA6tOGxqEyRF$0?BH6XI>YrphPNujerAuFBLxQ zm--jqN$D1g`xa-Iy(iJZP$L{19H1Ce?9+B|a1a28Ln?s@kev=kv^$b=IcW$1sRkQS zl0LlQUk|Z8y4%B8$oy($H&^u=2w}^=b7zmKET^kR`-X4z3AU^bi$unl5WjkP;V0=C z%gJfYa1Q=~UVs@8Ow(HD%wopb>=aROP#ASl`_r_~sPsBk^(cJ(lY{Q2`Qr8SQiGV9 zQ$A$pqY0pcz~WuHXSLOAz*B&ZTXF~g1p$ig4pMAjSTU$yex1D6C>F4DLYJ(#Br}*+ z?Q9ZU_L8Izy#+*2x#91hcF7qGwD(lS5NvSa&jC#}cyskv(%6pTQW}gQ9+da*x3x_V zP#VAHG|wXzk2KgS9pQgfyD8HMMJTOZtzT zOpvS8B&|vcFO9a;igzrQ%Tku$`z*fJ0M%jb+PC&~&QviMK$UL0!DF!8fA%PV2z#f#hvu0*0-s;(=1@?k1kG%{ zS>Jz)8Ca>evLvIp+9@8(bSDd}GXMWVV^>)8q9Tse3mQ1OYM)AR1d*xTT4z|K&P(2; zdPQsgpE!2cTsw}^Z{~aIndS)K7SC6@1A7Tw90m*qxjOCB>v;8iYRbc6o1+fXzVV%K zO&5RzF%g`xmO;zCFRZ*x4pa}j*jRP{{MfZySc7!YBBKB%_=4G;uogv(wC{Bg{l z%Mi`+n3-bl;IN+A2J}Sa+pXaQiZXItId;u^d}R=)y-(LyH|>e$#i&L3;jQXc}u&0iwvQToU&gUCmVBzu0(K{d~yC1hC{rLgwK$z)K*90rg(^Y zSrD3I2I5~x@i5OAJqeisJUIrd>JAF9V~3}@iw-*H9Z#n zGeDYk>F2D-S(5rn{^eT*zCZcy(Df2em|OQX1u`^4i?yM52VS$k@I^G;(cQ{xfiow@ z%h%WW%-AaWm8^PAQQ`TrF#P#{Son0B?c~3xSylUAQ3Y2W4AwKtnYd9q3)&LU$_^U= zsF7EhgscQlsOyxOm2YU8eRsMn6Y}x@>^9i$TMLg~S(`3_JGAkNyDwN1Q?M&oYF|x4 zLvH&#-3&v4ZjqDQPVe;c`nLZU zK-i#LGJt&gUugr|5m$aZH#CnBO_>2e^$y_L3+ycN;TmK45#mc9(c=oL4vOJ$yN@>f zE07w9<1Y@w^kLSGTjt)PvhQRuC63|xf5EMkJ7IR~|0AuGXyQWXr`rjRPz6L7@cUmm z2EMt2vSM>gpCLO%|0BM)#mfKi2&iXy{YNl>W6ghe6_e;DD;&E|JOh;f z3ba)T1ZV|FIsLZ`g+4ro+31qNnRF3#gQ%XM#RcCi(>Xa*xiO2luC!*E`9yw5VKL<9 z{ogqa7zS~3q4+BfPt*njRBa&~9xze>fSKwpE7wfdyzo(BC`GRq+YmTzN7KW^x<~zD zq7)kV;-JN{jFC6jtIKSw86QwVZ9%J2a4y zOzCWYQmfKmx!~IUm{}X4}(03I@kayTznj4(l!+hb_ zk*orBHF!`mW==uCU+}&OaCOxqM+PkEidl7g7V8VJ$f&4F!=C5@m6EB$w$v6N$ArMU zh49_RT3N}9O$Plhz#ULm_@5an#Nsa1+zDd;L4*6K5ayGKyp&ue__P;jRyP|GIY#nF z!S72%B$3q0!D5m`naMEpHCRGg`snb`d?Z&+PcQvyJ{XAld2b^zIyyS<5z2>UWimiR zNMi3_lNhOEHlDPE^}#l5{T*+8*vrY1|G^yksW!!MwR`sT5X}0AUoSg<`v-LhnGS2= zIsS1CoFsx?a^%Z_0Kf?|VtfhcBs-W9`i9!p(XqC=I#mG3Q)m9T_)d=Oh6N{$RYk6< zTHd?q!iivHg32$xCQODtuOPuW0*&X{5*>`htI<;o{Q=SbZZ+qXya+ zftu&e(vk?Lh>VEdNTFJ}aDAAW_)saWh>p%Y0i(K?j}NB$UX8{>0Ok>=Xx*teOklqW?;G&ki`RbyjfVk~ya z)hkSF|NQb!cl!V&Fz7_$G!FS!`%?r>eo8n!F`(kh0symxj$8U0cG)q_)nwHI-78Bs za`EJhK+^wvRCDfhY|nFBf`8)-iH2P{DJd!70H^qYzJffAn8Ru&7WzuWY&g4@t)D+a zcAjz^-9WT9jPr=7r3cdd=fsEMcqO744D*UHzjINK^Q6Rzfv(%*&2ib6LjY(B{rm_> z@I!q*Wsc<8Nff(b!_s+MOfQ6wGW0Z`K z0^*aBlbe86)1aVdjSqrQkL$Hx6=XJZ;|P^=x;^ucsge!vy>#b|o9hjGvA!V}D)imq zuhs}d(|E>j$M>R2{J&`X?|7>JH+~#fDk-BW^C&Yrl6^wPvG*2=WMpK8LsSS6*<@yK z*;!H9duCJi%;?zUd!19S_viDweSd%ce*NKfdvVTlJjV67uIqlk?ic>J&q&C9F3&xl zpi_3Ms@zUjYXkfGQhqSIXxFzz#`pd60ME9Z&Pa8t5!0%gm|xu)vn}g+{HG*-CfDML zAtM)&Xhb#o~$M#(U97>g)Ypn9hM+;D=IkJx~myL>~!RN~}PWGU^ zzr$@`AR>|wx?N_73(ZPN#8yz%FH!1C;P9Ykz^+;^g`{gXCYt_Ne6s`3|HYM%O#RE1 zut@60aU~Ket2M7(bp29>04kN+Dogf;`(jZYpL{a^!zvNepOF1!k&kJYu~m7AB_?4b zb~%aQ-1*y4mmk+)V(jr2Pa-pBO=1-?pW$zGQ}$S&rJe5cZ!m9UdUje!;c9%Vrt6!W z7B+8SIFQp6oa&DK^3dcY?IlPWck&|;B3H)rg2~oO9x|ZS3#gPAle90@5RUGJvx@k$ zVCf94kEPLG#u6~t5KbXkiIGJKUG4yyp*FKa{>6ra5Dt%p@uYC_a(AO_v= z8dzG`@K8ezaQQ^Rv@+-O%C^6Di>|*Jb$i7BDDO+Xdz~3}o07ydj)z&l>C7A!ar+jN z7#?$X-uBL_WlzF4Vwfks0Lyvrq>)f^g&`X|IT*JBZODcGJKR!H-+#yaZhzlXy}z<- zMf$vPNQK~F%N#>}cM}sVLUj!SWDD(B8xbn)mz9t8uMK!0)o@SQz+%cnUtxe_K^sy03kjyyCH;hVZ=n zU}XJ+ug{i^!~gt!S&P0j(NDw5Px5CvumrHJ^TV8$-#J3+G_ZGgYKQEN?TRBP37#k5 zlMNQiZu3B9tAxxmi{cOr65d%mQ|+Atm(-!J)*=D(F9_qly*@=1gNmEY2#J@vlfY$A zz<|D7roXJZ*aZv6YNsb<*3H_TfIajW?^kw#61O~U=6++CS|YdHH?u;5bnHfXvAi78 zvDc2H3bY9JbBruDncU@#MaNsEh~Ex-ShDn$t5cw{gdkX6yK0*H7+Z8=%dnt)T+ubu z={}{lO)4BLj6x5tcypP->#Xmw3qVwtzIS#!~`MMO+Q9B0=f)R~4UcpTfn}(29 z!(i$5D<<;+TTS8GX}+~QZ~k4Cfi3S)YP`|?QN8ZCicZnNd!p`chf~f^o(#VvFz8)s zKZ}UTG%R<)SI4gBI>oI8F(Fag1flt{lj6!_{_-DCjF~9QksZQO{NX@xZ`S3vXHI0U zxK=O;ca{_X@EO^1RhjXl7&~5>vgUIeUeUPpG7CpZNXMcdKn8{))aTCuN=m1hDx7~y zr=e@hdsfUe1EHw%l~|@~LfbISr*Zur6tX#643d{cANKJSy8-h`4XFlBF3O z>xuK>_~`%l{g%#MiS!NfKdH6hFV>C_&2D{Bx@sZU{%!n$@aS(eE~K8C%|W9L(Vd6& zjH=(L#e%IeTV(lO&$>kz&Ugzs#dn<5{7yxDQe}&nr<8$$LGTW7&y&<%vwXC*VMOt% zzxhw#{goKgl0FI5sPyu+jFRPs1kW|(-%zIbr(4h!laGO0P`-Izir&m_ZSjSeAwt-Q z|6YXQ5Ni#d#uw$MuP<`3A+xMse^o=XJI&C~mb?gmBrDy)=MXB}1SP4~$3$_F_v zs|Z89Y9nQ+aKkae^;L~=RP0)dKeg+x5=a{GD-YiD)R zmpgwpyRxCoRZkmlx}hXFN-Wj5pW?VFWTa7gQ#X{c>Qg}r)Lat*i^0Lkwe_nt^;|4( zLlM2aAk~JGj7|1;@4o>{oi!%2P3)JD1PS-!1fCh@{lvXfM=4k~!fmJj!$zPK-+Xuh zd$YtiH_h$BQu7Dv+tb(hmr5AOinBHITWx2V3NHNF+2qum#^iTqX#6A>nYpQNbi3-p zxB&OEY}+fWcxpblIvBe^$xW zgee*BGygNs(TJ?uwhTut!K;f;eiQCzAQ|WICrao1ii93f?%d}K+BF;VG#^f=e)#~w z&IXkKXCv=ai(SdDSVVpqAbw0a>>ko}ZrG;cxpAfis|)H4_t}50M5g0b^Yw*9B>K^X z4X(*6ZV?&u5fYK-Qc)@M>l1yVlm_EsI2`@v<$zV>>tZK^Lz?G*(-J=>;5(Qu1d_*Z z*G$>+yL5|hs4{b34)-N`e5B=g#PiPGrN-a=Wy_t`+In_^?>cSz`Og{@$KTYPc&~9^ zH$vDc<-Wg<@L#W}GaUsBW2K+o%g>Wf;xOdx|3R)D5&*eI`~8Pp6QVdUnfIhfZn)f% z5UCClR#kKrNz9>IR_&*XBP1Mlu(f*^+x&YhBHR9I;qfF1xyi?VjJZ?&oYc?LGM68B zQt>>y|5Ayik@rc%i1b^^4jnQr_KFUpgHyB5)|$D`e;3uYH%+H(3-HXRouXpLcs7XAA>VX=+ej6m${fj^)d&YBBu!(Fh zY)OcL&AM_{>eKd*mq$ywXzf9=iN|ym7T}SKDaNcFMVT~`v)@6oUMMUq1h7z6R@PMW z1z-*Dx^oR{DnIc+DUS)L?g)YG5wHdT?J|;pQxqS`ql6tOWi;G>yX|iDmkw3F0(*d_ z(+hTOD^Sl=^KC+=iUmA&7HPTX^81}b1eF_}^UWPpP;gw8|t z4i&$Rf@_)Wq{olCmk`kIiUWeO^PQeRxzV=uK2k6o5fXKZfFKMx_Q@{AYBWC;(No(c zdhBjD>pE!Qe5W-95C$6IXK_5Ha?;XgJjk#J!pu7hy=dKN+XdBOZui}%z4W1}MI5hd zYduqr#S!Z4YhTsW)isfu8yhz^Lf^`oGzHZ>Tcz}%K7kFgAfwdG2VjKh1Q#LB&|6sV-;Z}b;=C5^dTgg1nEF~<~IhzAW;d> zMk+I?Af@Wq|LV&io!$Q7MGEA(5b~_MlIKCY4G@)uB4Y4;aCue|l%-|1RcFQ1pC^yx zTj{icp`wOqm$-NDW)7(Jw9IyQt7r(|;eGN$yd@kBoRK3$r>22EQ}0{z7_tQ;3z>fHyqx^&&O3RpIIJLL9;~ z77z44kgy@EVNAdB5#S*4Q6Vf4hCWp{3DDN|@84n1!)Owdo%xE+SW97z_{q9J0M1KI zc;J7qAT|8MVz1}!>FDSbI^qG419z{J@_X~<91)S^G0Xx?nDh{|X0SWcO75rVAH-OFq>&oO%6np;{IU$je(p|eiMHJXFICbh&c#Bh5 zXeeNiw}v5^10Ps^7ZV&>+VZPiy<^>Kz8muAL>^SCF({lK~s@c2(ni;9a4c7Fk%iaO)g*);6J06Dbg z6NmgZV`7@&ys$d}Kzwxrn@zVHUXAd0Plr=k(QR0nmUl#QI{Lw zeUL=@fWSQ`^?An&oDwn$AlKwGY>C|@cw)mBfP)~1du+VAQ-2|2Y@!(l0^_}|Z)m7+ z$ffz~BjMdLfwcqc%5naOH1@sWwH_F6Of^h51v~$~s1{U!f;U_Kp7(9=D)tgdk6>RA zl8_|uT3mWXCHNW9W7ZF{Y2Mza+--4pmwN|5E+{jClGb@mI|$F4xE|^0y-!S}WpX)z zec7Ah`M~ayd&&tEiG!WB;O}?;4z>aa3z5eI=>hu`w?g9MSJ&1&LCNs%2;kwJgHekn zft3soLGBkE+;kfr2)mHx01*E2<%_tuICd(m|1Lh~dBBXJJ`Eu7Z+IT~%`|_R2PbXB zA3uI9CMKq#am6r)!b=vre$l7KUg3Bt$Dck4X!tx?Rz;(Q?uz3sUM16tzx87T9Uc}o zJwHExMYR^UM<8n%4w8=d;7Y)3xC^l&E(xRN2dM=TlE(S7xDAkmItTbKJeaWauX+?A zZfxm)TZwp+K>>t5n#o^xj6=DrxefgRKzh9DK$ zG51XHg(x2%nI;y+hG!0dQP#7Cv`qac8~6=;Ea=grM>@{5K|w(zVAT~BrH=j%MmaqL zL;byAz~|>WVl#Fcv2rmm&)Q7%^eMW_4f4Y9-#trAm=jHt=pv|QaeG62z zh+!9iB?Zv+-jP@^5u7##SE z+SvSpL0F`E6%5=GKIluehPZv^64(tQZ&4#VHl zo7}zNkodi$$%PBMcHqW>Eu$s1lB0M%L#X@v-%1Ky^`QvfP{&EdO611(h5*&#y_(qj z5oUQ~UBpTbd=RG9XGbXzV0F!=#h_Yk-t4FaCnF-Bv`bue+!Tj4`n zpBIpNk~Lqfr2>p@2`3H8^c>x72(uhx5yFs>=Ba9VeRF;I|3&oU>!>nO!dJKQT9&UQ z{uVh6J+_i!NuOM(pis;=IyxG3L+NE;HKKtz{{J_G`-h6cdtSm2oMGVqp3=hRL0TGaxuCx;uG>*%^1V*5&NIf7=1AQPDBSfm@DM=o{` zz(y<{HlDAl{y3{?N;KltCOD@0Rw(%%<1fpaqayoMZQ-<>(uSqa%Ab5oZArOJF4txs zG)ZHx4WKbo!H*<4ZrV+svd^zWo^;duGV=5D-?(w&@1yHO^1$&AKVP)E=n&O;G0t4k z&ODS`?}ah1my6=F|Na)l^zVnIC^DAQ8yrk& zZkEB4U$Ltf!vB+x!7YWojK785AUID<>;@g3F=)OIt^PsJ!((_KEp^B8TlQ5Lfpk&i zUo9AWZL_vLG4ldyw}Qu5F&L9K8XEUocAve8t|TgnYpfT_;@4RuvD;B0DJMprOzRxT z(FSthq7m+p(s+t|v!|kwpRCX)PFYA7IX|5FSLEe|Rg#CgM`I4lqH-a`WFq!t`pqQ+ zt(Ns|tt=BX9r>F-?C-Ah6Pwz4%^6RS^_U3Q=Js`83CLVvnH*&~jSS@{r=&kgt0rQ< zgOaLT*sNKJf|4PzOP4SElF|lggFm>S`eiHgSm5W+58la<7Sn`fksASuq;BU2dzdy! zzb60R!YyQFJ?R{a)+Iv;YpV;hS4lx4k1N=WM?VQs*{-`3J1u8H;jo-M0Fk=jor#OAAirCeqoPL@Or z9cP6a{u$;(TCUJ1cs7Nfq~+U+sE)FJa6Tg{It zO3;##eV?zjy~T(M-muv_cb^Ip(k3Dz^+Dmsf^mrpcjR>M{-OPhNuGC!St`RJFt(yo zxjjyKw!{y?s|45STrMhSdY8M}z7~R&b&*BaKFlBae^h@Bq#EJbnKM_HvqaApov4uO za;tR{_)&_kD_qk#--vvCDM0B6p7EJqw(;OWSq8Og0UK9ONvX{weGN}DRj!_T$zmgc zMovSTTAFVe)M}?Mc6u4bQ7nfvwB7MqrX2gXO`8poBkm!z-~RIWXIDYo2(jHU@^t5V zO#kZ(CRirFHM3=B9B28f{_Lhzy5L{8`sn4J1g*2w z*U47zW7t};Xl|&r-u76KkHmt3wxRJ7s{yi?&1?INZ2#F_;R@ttwJHB9-?(H7+37xd zujPCH!2@TI$^H4lq<5pH==xqdM*4piPwTmJ@Sldv|L|PPC`!|t-xJ5yqR&ak)SGQQ zVF^vR_xEN|k&Bn^#zUGViD-$K>4@=g|IN9p5h0EsMR@3|q)j5<{?kKd{0v);BgfVq z3jdH8#Oav#enk1_|I5(0V1T6WA=6j9_m8FD(;eWpn|*nt?{c~7`Hgm{W!kk>-O4Ns zc3GlMT%K(sJ-C+d2g-;iu<6F>zv)Kqz#fN&%NZ4H{MH5IVuao_yBFk1O{)&*e`!eS z4!a7Ik$iAAkb7iO|AKI8_z^;~Y{PqY5pCMNKlw*lP=H~4Zg0}1^x(P3nAK0d&!)qd z-6dXSnF;xikEDz}(kv{3hZg;k18Gd#8(X+1pG6rP8|&zhFTuh_wkikD4Bgzk8o@VS zKjCR{KO@SN^odin&~ndR^#U`TaE)s5|AcE;(7}9LsR$9Dj@Os=R0>qz(7r>smQHsz zpPFT%@ww#x^&@KcLGnE*bt;9=<$X>Uu8HaM#STJ^M6uNIMr#_Iwx;npbLBxce46Vi zzHPkAvUeX$smpm#+dcK2`(8W?X~q0bzeP4nf9aOU3%eIw7?jeHS>Kt2Y!C2)u~8!S zfNKV|HG3~O3yWTI+`4rk>@C)$;mYP!u5-fy_X8AVR3H|@f<90*ex*LQ(k@a>A1^B5iXVjwAo7d8?EOmhDu744V*Ix zF=sUhA%Ae|8Wmn$(N<x}k(HgS9!(|Rn)BO|U%q~Kwej;H^%=$VaP3>0Kmf>+VVB7Cu zU&T+~=qjmNM3!>~?}j4hSf-DQQ;p0XzR-W)n?mziQMM_5eQqaYxzD2Zu6?g;CyY1$zzC%q#4$i4=BoMazrX|pTG*7iywr$U~1K8><| zLS@HGAbk?aYIf^mtz%197m5)Nk6k&~D#yPr!r16_^hbrEp*K-R^M@UYMVE-<>9lCC z{hFvgoNAxix;d-tK~c^lzH-34Hrp*3*FNhm)I21ma(O{@@5RS=X>IRrNI)J5Tc2{i zFXeHtKM7LzMQ*Ok1J5bX8AavV_A*KTzh>t7?`PFieOJ*={RHow zA(JUCcivBD!F7Ga3F$p9{W2o(TR)0mnNCone*#iScPNROfJm(qh}bw~Ss+e{-AE7O zU<>guY%;(?t~>d6vz<)8NJdtM-m=72UZ7Lunh@*mmc25(Q0K#V2LGVV@9^Y5hYY>L zLn;yWv53q-r2Y@pNM8JbUAhqCAJou5Pe-_#Fu!Y5+4#A5;}yCI5COdxvzEFZ$SCV- z>PCjkY~dl%C0CB_1h+Vw(?xpLX_Y*Uf)&2hf6dpX?ezVFH;o((Q#B*un*;=%k&(Vi z53?xCB@GRp-h+EN_hOjdv`xo11p4uap8ps5TBu(m<^DtL>bi0F=M3FHy?)l$k z0eAezj^C3tzag144N=j_rY19}0f2)7LaQ`Xc(b60(Iu|VE6PN( zb9BawCwjV76?9X!G9LCh?kwGw)EQkD{=~C%wd7Ck$2`LYV#sL-0cUp>zwb3Qw$s*h{OI0tq6X8Idq4R; zMjK$4F|5q%e0<;A+Ss_bNHtHu!4-%x-?FPfDUER$ZpC)AG04ijY*zMu%dsOn;`kDu z^>yMU#rR#tv4kgj=PkCQUFHM%8jg3J8`7-#EB54S)&u8E2$|W6f_lE9!rZU3R@udc_!k8<G&L{%9=sXc$y=u)>nv^YIIy&Yf}vYP}i1(dYp<%&-lb%xR)&R$!IQk zeNR|GjBgI*1!IH_Yc+tA5H3o^h^}V-2^amr-JNpD>m@ehhFv{4 zUhRJ&Yn@edM+Y`1^Y7fo)6*(2d~5dyPEs6i8}D2xVrz92vIE7qiPU_-KbTt@SHfU5 z`|+m-*;a_60t9>{_p4ocIyyL@)C4=aXEo@0dwUm-wPAT(m6DY7E-!cX@VE;{+QP+5 zc7Q~{0;M3>!{C_IxaT8bEQu}wa{?v?E~SgjQxX$PA$6F}p4XLaZg0nIYjg2 zx&?hMqNBPEy*tl1N<98T<%??C!;jFVWbfKfE(8 z1rEaiqU|?s-aH$jLMZ6Ei6yv!w}&%2F~I%%{QMw2BO`?s_j$Z`;PPy2Znpl7*sxy5 z^~;wo@jx>N1-_H;u5ei*{}8Ala&+Xyz2QyKe4|j z-q3I*1%a2@e%+D{`?mR#($dgNhG^?S3n;xMU&7AisS?1g)<#Ot!!NI6Jsz=Ou^Uj* z1w~!~|9cM#Qqo{-KK_3rAs{TAaw!Rw>CGw$8Onx$YjD267b)x)dl@yra!0860GQ@r zXIJ5IB!qiT?5!Rdo0!zp)LfLK!oGe|?pab&6r+Y9+zs#}u;RF3Y!-3f?e6Ql{7vZu zZWy~mBO~X2{Yr?Be+zCLHm@Sa#L9Xuh3)Tv;T|{!;1Duwy&=32Or01L6nBD$2xkuV z&o2~QTwHMb22{EJy>&Fj?Z?Lj$Pb;&!*zn0$vnUF#es8iW?s;!JcoIQC7}4 zc3+>M#?3Vh^vXzS+xR%m@iA^mgi{+C8EL$Vf%3|J+$w|X!{hQS#A9!ZeRAB(!hc}r z%NQCWMkZiU!L_>WH)mv_b`)2Nh+T!y$>hU_53q6By#WG+Bp%Mrs%j6oLqDN&c;)gSZckzYrn?!#TII5|@w= zgd6f1PwIgjNF^vKDUny4NkT_MN*uoly`9+_Ca=7U5aO^yxPwkS@A&)tsiGriLl2VY;=%s(VM)wqy*`=eB zloGulPu(g>YklEA7$>A51lmF{@xpen*n-YQfPbaNLC30Od+cy6gfNE2#x5@`41ow9 zT;sL3_os@AQtMGL#pkC7|9%7r%K=<`Y(Ew=64Mi*&JLS6hhj5%rUE?N!?rPUa(4Fg zsPd#j_P!eWP-R}mV~>OMmamTwKus?Yx|}c)9QtJp{FYGa8;n50JNXHnsQdb%7XSId zHXums*mV#x%k!WPuQz3)#w}6A^-}2xyzB6;3sX~Sjzu9*W2->|^BwR2E|H3Y7$@^w z(wQ$b>~T=>J2#ho`{FOK5+P|~o_}P50dG*bDn|VE-m9Jzl<(`;tU2&0N9~2P zm+|V7w`7)b%=6phh0EpcGe1AS%K_%xo}QW6tTB+H^=28QTdB^KlHz^5k3^qkrNl-@ z8rPxSRB{y^>?5oN&Fo$5IXc*hm<4zSOazoe9(I_#cZus2q5&7~R5l$TH;&p0F+5Oe z3ZE`o=mGo`iV$aDt(lngeSG-Mum&A3;<{PyfBD{7b$3vRfJxz;;YXLVHgOk{V=uz~ zjycgi^w%4}Qz_pZQ}v6bgmuJOk&^1{(9@3;tVa3D3}96E!)-7WIA? z0dz)s9PUD+FQ~>{8?QpeT;sg2Q}xVROso!;PQT1Qxql>eDJ@I zauQ<>mAgQBh3U&{I0^5#3sv*qs`Po69nnly!`LuVjg1|~i}ErYtw zP8;x*f#eu=pHOejQ!b4VGv)gLG506sq@dag%1%URvh zl671@lP+Ku90dUKv5k&mW2qq_sl{p`76B7cKFP@fAlGf zT0^2-L4jztdUq9YO+#t?x(VAmLQeSxlDH?}C$f*+loz>NR3vx*qbtM7TKaB!`9XSPAgWwuQWz??rCpSH_G}xl zTAvHI%{rmu4@n+E(Eib%!@Z@T-cu3G+|W>oGGYwsuVHmTvB08g#;-4V&ev*SkPbGQ zXkn9%eUKoiB)9*>XFUR9M|~9l79W0ve0}yv#7gQ?%1Ufu2GBPxTtpI=}2HQSd zIf%|Fg$a#W;CB9XUvFW&il3qvzJ$1NLF_z~UuZ}5SIPI}H3o!S1!dMJuo3`!7V!LY z2yN$5m%R}&1Zt#XO$ax3Ufyt|OP%4!GThE_XH(uquDC))&YAkP9 z>tUb=XfS2Pd8Vr;f+gME-2ux<<4Xl{i-4eDLqo#{J@`gg(AZ^(48OVH^Hlmj`(?c) zm$}OJlFdgI8UCn+=D|6NEV_vmpvMIU0|T0b_I-sWs8;b(U{K#^n7FbP`(~o+O9wycq8FH~3+>z1p#EVVhq%bmhV&IrNO^nv;C~DY@Bg9UgupWtB|HBAph^zy7SU|6H`R7rMYL(>WH=86(_VqCenfeef!3XY$8v zl+m?3p^xuapd=R*vlmn=3ZanxDw|exAwqXdY?&^rvGNL0=4(X6tr`lhB|Zp)u97I$ zZ;}ekFGfiPl=-w`sM}X%A!(If`;9|E?sL(|jw`oIT-=^zYNkckcxZUuQq7$8d|%I3 zH*LupCR*jGaA?EGj;bAgq{8Lp`<8Ole6X(VbdnqH(p*|A5bp$Z)_7`GN<}B ze2quvt1oPP5IJMre)97y8RhBICiYpIkP@WykdOH_?NkD*RhO~EbdY)?VnuKNqY-xNu!D;f5v<0Rbp8y1i;t$=9 z#J-tcU_)h(1K&mFQCB_9v*ZIB}&ueB7gdD6wflz?kom7hMfM{`T))i}%tfhu+;cSajRl+u%B_4HZr|htRVy3k$n}A%#|X1=!i}T4>kj6nml!s>)k9ul zO>Hst2SJ7{2yd0*g~BCzC3sSIX0e(M=4^XmB}?!Rkjw8W!0O0~ePv~BO-dsq*~Xyn zC}b+9=K~&<>Suc)+z7zE{)v;a#v|bSzea0SNcF4Wa9zBJ^oORRk22SK;MC6)1A55) z^B*B3hAjOfzNI)|8ubN10bRIOad9y;W+Wpg7l2c-)d&bFn6rgu?98yj{%WpjCas0k zLsQR(W@aemWO!Yw-V|`~)lNX&T63++NTR|Z)VNVYZ1|d(3Yw+frlUKFTQX$0C1Y{a zboeGxt;x{1PR1tI8;CF9J8EDSz#|5OzXk`Om>AxkoRw9sUT`>5GWdUby1I~AjOAgP ze20KmrHPS1vB|A=RCj&=>Gz}8!4-Kp(u{w4R5ml;+#ut;tof8&rlTJh!v6QxpBgZPExdnD&_4SZ*h{ zY5HYWt{S+0pcbxQy$NAeU^X|^%C)iEyYZ577g$Zf5C!>Zyqiu7Ej4{_NMgb%?>;xW z>QOvG8DK5y>+82>Y2}T)IDwyP49!j*pKew33x*;< zheAe=M$kUh`UDWVpbsInH5yvaZT;P-F6=)&Zu312)ru9`$duO8iETgX{qC7-);x3m z_9q{U{wIg_ks1l74AAEl8kFXLcgutx5YK-S`^~qhi#5kIE;8vAL^0mD<0(xUjjpVu z-GO<6cE$>#^6qtzC&?0A`r<|K=z;Vr1*jaynZ1r5buHhV_l~O#yViwEDW8pf&kPKK z{yD!6BO_#%I+ZPzVpA7V)(jpB@5FLYQ#uG=E*D(As-*>~;@pE7*JPunDv zAJVHSRB*ypia;q}_7ReswFHELJ*JsD2z6?hpmmeh0nyLUFzvP+;ocbm z{prZ{C&nzjS^s-@z(u_OyUv-{8{T|E4UVrB%6guXt62!HoaN_fFm17xB6;g$Kw~Z3 zx9A?3aq-Kc`jXQlX$hvk63|f9`?K&-`c7dVZvT-Ot%yx3ma&~}H*aRZx(?XWsXpP0NsGc7fBeg^4n)|2X--(@ zyB3Z_|JNPZn)ENzP?J8xJa$5f@~mu-R63*L#pb?og&D-~#&ETj;lD@ep?3i0AQctW zTWNgc*XeVGX0V>3W$dPGu8Lw->bsIOdjqi%-Rsuq(7mJ=m(~DQTcMEm!yf`A)moU^EpDo!m={0bk$`zO~3))fljl~z}b|Q zza|Dd(1q`?Zwory5LZ4!Ryxcpt&Ws71yV?~0cSIGMopCKZ@{^q08vet+rWVgVsg!Yaz@_h-IpkLU5=TQN+BC# zuiT?M{`P}E*4I!@m~)7QKjf|@>qZdPk7wnWaG56Vr7xm$V$vls4L#Gtvv~gK!}``1 z(r&8WZLOx5CT`|< zbJVaD;rh9L`es_08cUu)`)TP@zwLKf3`xcdxGoI^_`FXFbiWY)@Bk|Y4*qDfj*|jy>Cr{*Ku&pgd5}Un+ z92c%fI665+NXlVXP>a2Bm&Q3HcxN8k~kQ|69=$G{nzK>W`c6taL{^+m0$Ku=yH<183Ae zUsh6FW4Cg=Mt3os&HvDwwDdO>&g^UKLr-I24i3c(($a}`a0?)-663K%6cjd+{02K$ z59?I~$8EcmW}0qgHG-JT3vDVw>tx@JcBd6O1_B4Zw!QJ1-8<@Dj}INA_Fp1J{oO)c z3H$~pW}9yNH_QdtKOB*i1E{u1%z5!!h6;EJK@vWTLX0mL1fzqe$08ccds)e@Tq2ns zv^oq6J%bc}NxHDWKHq((`wJ4)jJ~qvS=|<1?)YTQha!%PxW;PID=Lw^l`AOXB$ax_ z>2NFd8D!!9rB^Z1_?@6xmA`!MSB%C)S=FaJnW&!ow&H*Ek;otBK( zZ8>Az#tmGrQztMp(uOIeA(U1**S^#D1<_w+A`YFfmh$)-wX|`PRs~JSC>3}VxviC^sQW~$h zwFjj1h4d*rsi-Aoj+#648S~^O26V7 z=mPvbkr@_33J>z;`=q|PHuvGxK7SuoBOUO=+Me!P_rZ%OG-KCje#c^Wn1slBdg?3u zUp%tGfz(ZsNS=R#*-N2cHcgLtZWy%Axv09L$SG>|-sbc+#aFJf&}03Z4%$NeKf6!I zCZjTbSI}e{(%VqHFCI=pQ0$wl$wwCjC=quBP30|%K2F(mIbi^5n*ewZqcti+cI4c7 z>3e?5A#YF{5&kOQIc6j{_<#=U-R9bRsLXVuwzP1n$+L&X39)`)YcAm|H|g^>&;8wH z(c+uCpIL2%cJXatG^B$}k`WY@kEatg@=)sEOda&9%ikeXm-=I4eS+r9NvRy-Ns~!j zvr_>kmN#zO;JCsgHm~kW^Rtg*NcZVcxr^?iM(RXQH!6i{;sdv{UZ_@D)C)Oc+9QAK ztavK3e4#VA=nB(CVGnT~B(P0`hR!{NPTFi*xi>4V=zwSP*59Ao3>o^hPc>Pz9y6K1 z8}|Co@d1H@My35(4tko^_O-EufkgoUEK7bqupsQl&N>=UHy zn;)pE?nTFRx0F20AnngxIrmycy#}Y7PN%f^eO1!?D~5z$&>owRt>5RQ>Uu);^37+& z(h)Uj#{1Hj1dZ}X0h57I{1wDM6xF}`y)(Bt*Sq-Mdf!f=hhAdv{tB}u=H@n&zTf7A zVje0a@ngQA^VL|+^|#kN-Uy2fWHN5w592Ufx%c@u>14#H)k3QMaq25h#T@14;tNae zOC5=u-#@5-Ixel^=c*M@KMs2RndFjOvV?LOvXWtIS9p>Bf`;g8{6|<`2 z`sru}PJTuc%_mb; zvZ%I@y>LRTyLR(zllIiX>P9E~uux1Jsa=DTr~aSF#Q0{VI!}RYi%~r^(bU8BrJB2+ zzDYnSI!|g(-(-(Xa)FnP3E22x6uqaWO(&cZmAA%CynBygsz1oGt;XwO-Ul3|IzAea zr|2#4Hf5G_Skf>jf1gskJx)#G&!d!J^Pr)JN@(+}d#H!SWtOOZ5BeG#w*4$PBuMB) zttwr<`pS1sq?(~5j0j?=$G173e?m0>Y~Ew~oQ2$FO<0iK<&qu)eik%7N`&_3TT25_ z@kDd=>Q)E;52g1rYKInF(FUk^nGBbkD%m!rOC6d|S^4zvar^Ih!33$RO{-(|Nn)0WDd6MC%FC6;lwXmJzhLm|C zn&0aN75;wPvt55OzR9^xC^x7S(sBeW zBGs9#g)if*UTAZ3^T(nhy7Sx`KR}5F+;>ZxW#ET&r_u&yoWm2q<)=eSpjB^l#{J~K4q1{r8ZX+vegFOp5>ixrR@aH? zqU2;{Rmakvt&T9T!6Q=gS&`HD21)M$#zFZu9csux7*oh)JxQZTD^E``mXDD?^nF$! ztyTd7O76m_RFMFs>*(vFBqeWZ%gA9`*$SYXvTuAmp7Yz`mduxN{ZVg2-tf3&lr-HM z1tEpm&DOXL0T0Yhd5SQX0Poz1AqKSp%XO*V+6*;`0G6KL{fQweuMMdN>1$-r-cQ{j zasi3tBvg8_NfSweiQTeRjXq#vN_2p#(&vFLXPC(2h3*e;s@_de3px5l)*b}WA^=0` zIj~pk#U_hlT!xa-j;kYgMoj10W7Iz9MaM=YYZy~!~1J=qCd4__JkGQCZM9m zv+;MbLPLAK20kGKos6B-d)Ke$!3w1Pmncei*uCZK|9m;G9Ag8Rn*b@LX8ik96h(jf zb~JUeZ` zNOx=u`NlUB*26^&_qO~H`G-dxT++dTk`uQt@pP($3r^b|rdoVV4Z5oqt7uJNsJWBJ z!iP2-UcC5qQCnslFSy|%qZQY(cC}8}#=hGMzp=CSyS&@-|i2T7D zglwKLdaD`pjkQg?z_7HaXmfk}d&O614Y4$kqa4E{rKbpV0g0GegrJavwp`IE=4&3e zY-=%v=g+|xb3Q@dn*pX1m>%3Nl&&f+h>?mRRV5d zO*^0bVWwa3|7?Pg6je$-gI~4`d1u%;t|S$J9bW3nOqzK9)5e_94hX@w5VHUIZwAa7%#3R@|^o)$Nka?Hlhl4yI(3g54gd{p4 z!DGIYhW@nN)1m(bE7f$>_Oo&T6{n|i%)YaeA-nwdZLryBNV}B$zkYR=FgUICd2Re& zJiSa7zF5g>D*2sT2-WF9ujYK>Ib5wWsX?0=%3 zPTFy(G!udB64*%EjB#$CJ|&ZQf>=JhF0U_ zz^#iInYq6?v}ezriAb2MsASRJugNvMW9J-i;K;<<O7{ z&-(g0K&Fu3_O%DHEwfUbOw&Ki#dCi#!9crvMu zj&!X+Sz7-2%{L-|3rzvXlt`qUeQo@H#KAsMY^S2|(xMad=a!tedVbo24*b_8fKz)# zlGV1;STak?dR5YiX{o2#b?imSk^G*fo=ge#yBcW@kBy8^N;xDVKMTXd-z_w5HD_Jk zu~dTtRa302tTO9>IU*t=(tq2Dd>9M90o&n@ z^+57X6?x3w*TIwVzDOpHBA?k5B(-H=Wm(&*N&wF!JtqO8-z2>|#Pye0t zGh$$^A!#)H@ge?jL_~z9oZOoaZMeKIrg>)+D5fh}gMP7|b5@+GsPM?oT_)3n1wChO zr*%q{P**D|$@K;KqpvhNs0MtQhIVhQiB>Yyfs+M46pbFcDm)0pZIkzi@d}3wa5_@2 zl_(gCds4{grYeHvWsHDo;1DhBJb>+&-#2~~HpK`s;9*(?kfro7>jdPrva&Li@T%Jd zEa=mt8A5vXc?HbA)7C|siE3H23p?lWV2D!?;wLL zY)>i_nJ~1zy43!O9fAIC=9d7Xy9H&8Ei%boR}_#804xW;$XTt+;TWZGfckfAM@_kQ z$ZReae1R3gd9^!N_qMQb739nM(xIY#q~vi2W!lrXly?}Oa~EYT^cOb;Z3Mm$tHV`$R;z{o2*2#Mam8t*W<}5K-9`q#!#t23PGG;SAs`8)tP5ig~Gpcflm`Np(#bcW$rbaq}tGhi`{7${*}va*8vcGG^9c3P=Afh)v-OK5LTqapDm^n3E`Z?T@?Tut9hcwlf`f?!!}va1 zLdpnIf9VbPoPR0po}>P%HXsYs77JtJ+2!T;4$h2tU+cU?xWG7o`}-Gy*TFg-A%#n8 zW|;ygRf##EPx=?3)NXBUogOIQfH)7K$abw3NOfskrJlE%U(Qw7^O>mld3h*Knr%m* z@Gsq-D$C)%*UyVyVu0{nP(HiZM`K(40DFBC`|uhrS?(>VHuHLtX&3;21Tnb}-&YKO z(if`_vSMFkLaMP#DW?E$C8eP7y~sFl^_2%yK1+lQ8oVL=0I_(xxZRny{u{DUYhJ-{ zBd4XMRRRI{5A5cmkFRec+Wp~TV-3^-A&Y2ChFcWT-H*q&278;WO-;t2!E!f-hlgiF zh=7nVZ1f4hc7Ff763H2l#L=qr$(-Q3HpsMrHnjk!2!_GzTR+9AIL1y(M^wbVP-bpxTL+$fHcf*2^M!vX z`in#;(91r)X87^W5`;DuL#}DeAbN%l*BkE`=tiVB}d-`Q_dPbkk{#-(A@YImkNd4x{aTQ;?pzb$CANc`~5 zyXit1mLVv(Av0pfz~1HDV^R2zZQ>x7$vBaot3YRzG4T)18GQT z_@3C&AKttyuq3UI%4J>dx21A#mUSTD+^hQ~)_>xZ7=2Yh!qkp*p7`es|8zWWS-Kl} zM_(p$zd*Zmhp1@aBXxqB)T@niQz{z^t!SINwDELdY30KfO!QjhZ#KQ?Pdj#66~rfz z;0!`5ihPVSZObRuvAiGei&n4Ct}eabmG^Pb=q46vD#&u&U0U$|)h~E9hM+Q(cX!QE z7|(%wYgT)Td(5r%UQ$xv_okPhA`Z@_TCk<^vrXjZFZ70qTs9wjt0|aB5&qSFfbFJS zkGlaf!$&uk*8{&W+MhQbR2s`0iL-Bs2ZF|Baga0h{j+9y$Jhl1=crJ~6H(sn=US5g zwVnG*-sX2 zZ{4GQ@#4>!2JVyQ{A*-Gyz}{nU(SZyAn}ypbDH}5+CBd$fw0;%1B(Yx0WLy!5HoRt zo1p_KZ5^F2S|A{kVzvAw6X#?AxLH*Iw^}|kE3jzd)p-Cd%|I z#*)=0a@B~{C@O&p=k+vi@yiam?KwLwq02WWh4t2^T+U$Cd^V-f#Wi`Q&qbIAfb5klnVGSPrEcH?}#LGB)b6Ch<}6i z+O@}kF$UFQpnyOs@Y$ER&S@#KgpHluqBrRQ)ENMSy87}gp`)`hx38~nxmn-KchBt~ z=4tkoT57}>Z!CXv0o-oCy`!Te7&aWOAj5kFh)ov9v+TbtAo(j zn3D9==5M5`MSllsGqZ2Z$xGiQi}{vW4l8J*c#Y1tFvak^ibN37yAb$%giBPVIRMZ* zI==&Hqgm*yqh55$zM|)lfrF#tvYp%BYKw>p57Y*(+9eah=S#6sMR$PZ1ttXH`Yb4` z_cLMxJiw{;TR@RE1e+vVvUZ?c@FJX&lU+d@SG3vC3Ps zaO1C|r3JuKYnT(Y#{f~jd+&x;gM%5gYLcZ$`Au61X@P2^#DW;t4PbbXu_*AB$ePC^ z&l}IJm3sV3S)LmWNp%|H1`YA=jJeUm*CEsev1)qG90T3Z6H^ z1?>SITPoA+MII|7<0}Q0WzU)OE&R#)^zm0sd*)Va61@&8jpzYO@NbJ74%m}`3de=s z$=3m(YM$N1#KMvZO@c^)73OWrSDwe-Z7XQeU+t${$zZ61>2<3t#7$%fj zAMKwbdWP$Kjj))GsgZz#O~OEFf{`i32Tju5)#F%#}%t2Q2sAcS(#Q0lo4t$+5X>wE2Q0&owmIOepCTOpY1woGfS%wTxVwP{|ejJ z1A3qRsXK%hOFICt=KN#Y){J!n|#axXa~E_Pw~6Y5?%g$ ze{ThEmfP>IQxh0c0mO@_0O{2{WF_f#08e(eD*U$=oK!UlP&0!ZDls-zn|i}&j{kh==U_3?0~8A9tCMMi&hNwqI)B9^%C}k&#;hr$u600py~pq5`blj$QE$;uvTu zRoPv6?!@*zgh4bsTyFF)(g&%*#2h@iZvI&VN)xj=7z#h2;sc@%xI;Yb53G!p(?I5z&H38VL8>FJUS> zH^IGjlCOEHonobic$-BrA!u@ojMZCvgzg2bv_ar=)7~2uYi1isXW|9#)X$?8eAwfpYpcxjn&kaw#~pcxS9`)mt{#^8bGZ{NK6vAp~!0FToYjon@`VEcIWn6q_w4`_WVDk_76 zjKBvwaY7ZJfIyV#Il)OJhC6XWDCGp}nb&V&zK$tf$m<_5yx0;BV|K`|pg;|4ZtS>6fUETR&yea_TV|9s3wysyqsfDGbjNoMho zO2UZ~5#a=MXk_TfUtGmc)*tikkTHX1oeCK(?FI9rMTDPl!NWIKR_cKi3h#=g~xtNnvzA8n~>Rj|m`d z6P2<%eqJXC9nv+*3B>>S@dHFP2Tmhz20Bo(Dk^U?Gg+{-ae(9tkxZ4Al@(JzUV_I7 z+uPefcuhi~@Xd3y|L0evJUsS*uGj0Rb_&R%Au`Bnb<%AJ7xJ|R>~1Xw+G!rG)QMK0 zVadhcE#=k4e~EuIw&yYrVA$|Q#ucR1BhYpA#C_vWuJibg?~!M~x@gh@`w;HYumaFS z8%RY&>agw^O43&*kvUQ#Ffumodtwe(1$TGAfP^Q;#Q3uVF~bmMJ{jH}mhmsL-=guM zN}s~=1;UqzcK{(&Re+UO7CeD9Vt``}+3hw8nb%vBKs@RqxF$wGOw1x+@x5B`z2S#) zYE}GHT6HIM3j5~9Mi2-hj-NexBm_~;j*f5zl3rdeuzwv55+T=ensf!-x2%|~=5M}= z)&Yk*WX1;~uN5uG>pDC?n00b?w%MWt8eTAT%`A=Pxc(_xG%ZN8%!>Nd9b`&OL}5Ze zP>c5JRVMTzL@gQ7H#MY!s;jG4_9IDD=&Z#XkEYifI(tAxEsVx#p_iVQmz3NvA2kLR z2{3jk?ClqWR`8HTSgiXF9G*8Q^w!9ek{|*eSx|$y`H=uw&L1Qo6Uc-HnL$)egD#O~ zY9I8LPClXuuavKNF?x*ASaMyjx!j4J5tw)C*l7(vPgohVzP`SyO@#b@`u*h?%O>B$ z;&*CK9tB7|)02rz#YE6v*oNQ{R*pPM<=_J!X!xn)ciPL0f(fdw7V@Zsjhuvu;J;eQ z@>+qUi;nVkSwnW%dH*4Ot96OKvN)-|^y@kxGK5u2leU68`fYnymist%%ocH|ttM$RZo{enj*^*{5l$324sMo7w<tXJJGcEa>c;3z5nka1iLG`4FnWrv=257TQXmM70;np-xli5afySsNG|jAIO4 z-$a)`x_(HBye}363uw_;zNx2hR>AYUJh@Bq@r{u^P`jXr zPpcL+M*ZPD7fcSMlf1kpS%+CUb0_%}*T1~n;$B@brk<1l#Pq2u(Mk6)Z4UghGe#_p zRm2z?=_^{vk=vYZiay($DkCZ`-nhLw-G@~xgiTOtJAqOiJRYj<|2;xYjuLoUzAN(T zKetIyD4S+4X`q!gG&HPGk|P!`>t%o3YKo0m;kwRbuQ=!7bB6>^tr9bxEV-UfV+oFX z6NbC@#v0OQIC7sQQod?wiz$!{h}|0FlKz_W-SWG$@KvsA7YRP|rbpo)-P^{aVVC^~ zr6ni#nV4!nA-R3}XyF|HuaRg{ES-(7adCA4-x_Cx7^?fYv>W2YO=Uf9^iyJP7e=Gc z(*Dj;cxIpzUj^Es5wdrk-d1TZ<^4Vgao4QcAPIX29qu$9ULMW)p8JHG$C(~aJ>Vc# zvE(;nkZ$gESI|GXck|c;N`A@K|N1!Oz{rC0gt@^E-Waxokx02XmjYE>{Kb*m%%11qw2`xntE6BbA!L%XjBI1hF`ha>NWDXgGlyqdf5k1epDkpWoHz?welKmg~P z5B-y)=Yedtm|_BTFa^b*>4ngasS>;Vi6F|_0)o1)_Eu@$|Af?+GYHp0Y!fLS&@}a* zHk`Cs4mGvzJ*NUjhIU30-Vw0I-gR{%4Y7bnhpRo?*cxd$EygmhHn9!!((Ev*a&Jn& z`@J(;5}BI?cHI&2&H0-4GlO&GH-oXkEawUfXGsqFFI-pqOWl%^LmAf5P>)r8mFp%EH5~_2vW8m!EIhaTEziTl_S{eRc7*| z7!Z1yy_Fywxv+VmPBqt{#V%j4Vyo_Jm|iT$pQ3f0hYaTIU&BQ}V6J?&Z6MFO0^OF( zQ+~@X?u(v*F_XexXEJkJn2Ggco}z*lYbl%4f}#Q&Sz~a&;~x@|-%t1H)TZD41%;yt zUz2%4xhi)Jsw;G<2=gsge27uy9l5WTokN2y{Q3G zw#|Vbi7NS9lks~97&bfdC)CPEHe4#KrM0Y6NqQ9CM12l<%BZRvIrea-R}&O+%5KGX zo4Q>g=@d!V!~A(fBCl=N_FHe%Mh>X8lW+qF^9Wqz7F$F|0*;g9ms`_Ky=-F-#ZNgd z4vX#=Mevz%&U56V@z$dc|Nm(c+9l+Znc}zyhPtdDO`>jraDe)L>6PP0&%O;2x%^Fd z-p7x4$F#U*#`Ejq%z(d6MEKzUhdV*(K8{%nDl^jF-=Ap!i0~12!ea(Squ!oq53~|Q z`~fcDJxJLU#@Tk5ME$2tqg$cFyuc-tJ22h8KsWF~zxbW`1?&7EmaT#6a--2IBXY8>RoU@)`{ocP; z6(uNCe&!Lrdcy$?mR(8O|8O#Qu_l1Yz)Mt_I7q)jzCR}$LubY zcpmFvXGWt=pX%~^!@`z3p_Y68uDNa&6}bv@I@p~>TiDZF1MH%5XrA@V`943+HC*w{PxHOETH1f==>C+@ygEkG!DGe9>qTzs8L9`V;&OzpZ}v`!$$b zo^DO3_EtA(t-QQD9=Eyb93*XMwM#Gw<7H*9qDzw>4}FWOh`!vdBg1jgp^fCzXl}pV zRVRk{sqCk!PD^8k8?5Z??LqG8$1y%(Z)$Ci1Eb88m#dr9HN~fr0ljJDBGnPJ*+wd; zf}!l5F2R$sKGyv2dwYNPGy67I`(G4ZE?5%WcGm&a|!3BBFLM&|81Gyek(9t`ei~}%K>nW`J{&P<-a(>wHy}( z-qKWk>}6EF11Totew7FL29HG`6?j~Wof(S|WA4^%VZVv3eK#>4sL#%C)~sk_Zpho7 z+|WNX&r;ZUA&xl`b?L^l?Y{i;U5~vYqfJ=?5Jb9ZvV6F7h$zby%J%u#;>zt*>W|g) zEP<4!0kgi1&qL43%S>2y+^h8az{oIzzIfJrrkcgUEu$=Iu6=5=({-odmS3h>eUB5bcjw*Oz zv@SZ8x>AT~`5;6f^n!Hmj6-au_!Rf(sn^}I*{ZgEeesmER|4I>eeKT~{Gqzxp)It} zJJ0PuSZk-6S^weGPr(9rpT;E~=AQ_lqMozDbN{2(RwKmVpkcNV%^^~U>6xdL92c71 zB6iXV4M8az7)p94bPuSoBxo>hTFXqz6U7xLDdni^@Of1uuTrgAS5yip2-{k}N}ZWC zvWb~NNv_7+rAork&~=rR;*pV}Q!)kpqA70lYh*UJ z_`+Qr5~sf!c?hVETkQw~ag!$P&PA#Y*yc$AOQHRh^P5zjQrzD)f>1szc05>!#bUcC z3EKj&VFSZ0mL=!EGqYqTH?{U%c=R1W3nz1i{l0n6Sq-75`~SL8{?wK1=8$icynt$X zvaNbxB*w7&&-~u=mhUsho7-M&n}A%u6m}R`i#c)GrxDH=jp`tD>bXgY);{DBNlQOK zOKvPQ_Us+VE;h;i{6Rc+juBtJoo+A1qF%}^Y{OpW-}kP7Ceis*L9|`Hvp!cRea_w^ zyyzYn6=5T{cCA#Tf>MzB0={qZy2@0-@WbkVWMZ?))<85YHNIyZqfjI07pkpmn7c#3Z0zFPTqLyc$btQ>WW3qhrFcB{ot=c z(fI5{>Ym)ISbO4x425b=B9GLWtM!Hta{oM<47n#07#f=Tv!)sxZA`Li7# zdEF#b#2vteDW@sU({L2Ka*J$6p;*IX_TonV5s&kjWzfpAT&?AB`NGT*P?Jrx6TMAX zXUCzf_s30M(N1fun0-=sPWP6mj0^#qgez0=MuX@T7U0@}JQAXgRsVLQRS9g%ev+y6 zkKf_|Ob%qpCcO2wa!L89W&tU9-t6Ad?H1zt%ztiD{f=E8i@3-CPcGsK3xn2kgzVbd zzZ-jOhY?(3>orPlJz(V)m4wQ`0FXcz^e=fq8A;E?BqM$Q_-W5`(lDjp{n8VqOkym! z?m>tRp9OcF1XxW2dJaaa5!<`4c!177cJat<{s~^F09*-X=HeoP)`7>*J$0WM?oj@F zBp?U}tp1}q&#QGd^^Ql`J||1oUBqvbylwhPQW;uRf0uCXH$)q^wUhFmf@*o#mgdojVU z#9N*b*V5162WJ_A>>8AW7nj8V>5&e?NC3reQvPL$_}!^q>&g@HDR#06mo%i=y!_Jt zbaaSieozb6VJOHKrvKBQA)8zrq*dP(GIt;62puXNTwGeFm%Tvu6{a)VAI0`%G%k8z z)@e&UFeLdiMHe?Ydd)*xQ_J5v(bC}|tw{tmN!}pi)nR3pem27=7Rhq3&d2$`=0J6^ zBuG!e8iJ8i-q4uJo;>@WlcnI z5WXP|U648_#yrz2euk4Ajpsl;pU){7_OQ@PiATcGV#FUjz=%(S^)<@Wwf>BG7JN`A zxq3~{J?h>D%u5&L=7RFdZFJ;%T7lW%1HIn@LB#+1-<_J>Edb!;oDB(}0Aj$Aj;{jO zj4q1FOLLbK!l$2&J>a#DwHo_25h5?L>u#E&Eqa5=a z^9}#a{$kDKEyHC zwh{k3c72IO;a>}*%)Y;NKVNgDZ5gdHY*PA3+^fl*3!=NLLLpT`OXOD(t<~3<(Sm<| zE-{q~RX=$&`ig3@v6tP$ToTJ4fx$l*F1i2Ear*|H{-~WbFXj2stg6%jszMfvw*0@c@*^7cmHD5dvwS{{PwTw z1Kg+Re9Y$zyEgBCW1WmU!9|eF_WdfvEX2y2Oz*q}@8D|4N<*Vx|6i#{7KnoNp1uB% z$aF~g3@LyR6lQ@&80c9Bd|^b%Y~i_}7=qs<>|(q~2Q8ldpfemStV_o%yMsQ9tHB}B z_t}_L`-3)Jo2bQ|4aNoF|NbWZ4#*j^)WMc}9U4@}B9eGKDuIO7Da4y;+#gL|t(`gj z?@$;D2XLbS^O9|#%7JSL{JZ--74=xW=HYol>rjT1q zPP6&q#@fZy?<4)RNPyHI3*-t}c{(?Pq!X~_H-C4=huQj&gw30MbVLa-8` zA#;M&1r%Z%(HZ`?uY{1MS!p{3-P)(LbiiI9%~v!#eUsFdHe93Tv};8z!`w&jI zbgY|B|BJXGOgBbSlQ1cl^3a-R|LQ&-D9hfH?`!b_$V$dwEhd?RmQp+GLR)ZZaoK79 zN6p=~YWeHlHpG^*j2}?TL(WqV|A_LdvRZ64oI)2ZWPLS~5PN+=s|L&O`Tl;))So|e zP*6fFsZuRA&CGWIrD~Qoa(jzvGl-TsW14vOZGpdJDj#1&Nd$CEy*Y{h z7g~T(gUqZX);ohyX$Wf8NbTk*ts_syU{kg4IrTeVfZOeCWD+(u8nYObgvBK!pyZu` z0&A#t3LQ{+V^_FGY+;fXDRUYb0d64aGtwOG4Cwemrg-lB#fu73QuVfE$j<=TBO!W$ z^RQm?#S>!Jr-{55dV@C3?+44#Lt7i<-T)HU1HHTJ_lS*DF^*Q>f-_s?cU1L=Mp+kc z=ZRD5A~~MoA>tyW1Q2OhX^ruk8V8YXwamuDr+Lira%`mzVJ^v|tbC3(B({SBju-8q zczK=>|LDmwjKGkKu}hyN6LH|g7&BjzL2;YCa?vn>R_U=~q<8ox%b1E%e5zcr6?Zph zwY1V$WrB4AK+A}2miQta6Blv3%B7sgNS@Z1dQR^M<&I4GC^oHRXu)W)i~FN2zOx3Y z;EM?I9y7^~Kkq_y^wV~XrLUzue%q{QJtc7vkIOT<($ClvwXG^uyd`7aWN>he@nr{# zl>poz8hwZBB4|LCj7)8K4T=bV=BM}q+l`u@E1R*-U-qujHucq_1bokM4vrg3bC&-d z$nJSTtvF}D-J)IUGsjlAOjtRRt?yQ=d6&5@7&5SpWphhGo3w7@>Sx+C;`eUK4cNB- z!FlMhTuvnGsrlg0>@66XJS54<;SQcVf*tqD91SO1Z@eSX(9`aJA#;(JVs zg!+~joy3Dr-g5FM`x?&TE35E+>AW1J`*i6Omkqg{06z~uR}_W7?}mY=ZA1pXIs_=9sE^8&7CTrxhe> zEI)qoW@(|j8J8wx?vWNb_dPXOX27L-mA- zo;yqUFu#D`c8MJI#Dhe$#nl2&Qe|*>>Bk{es;cv|SzD9!RYi*10*cquEHJCD%HVbSy|ddM%_G!uWpb$phS+iBR>mtUWQ$cGHM@r|{0NDY7_Dl^fV_K)jNW zC@U$|FN&e>-PA}JV{P<@R_|0MH$DjZDDM&B|>~fBd)05apwm$ac0c4I}P8h*qH4w#ndlDsjs_&1DFrb zDX`I@*>BuinBTc7 z=0h3Ru})0W*WkptUe{SyH;G_uqe>LnkRHkNOf)&?^aQl;)58kv`~|Z=`BKHo8IExD7Pld!Bsrf__3uJ zOYcjfXa-aPCjXzQCoV1lr@M#4il0wLk%*%EU1i#IyaG?;uQM)^*qwnIe&-qZus60* zBx;yZq0#x;1wyTaF;FJr=a1OAfBY|aOMO$MnwTui4H;eCF<^>B6T>#o!t6Ojql`^V z0PEKM_~wTYC7kZ%)r~m7_!n7X1%U!zH*3~tun%j;@?wW}Dd;oZQ_x%(Y>3Mp10r=w*yeIr|9wr$1*(KzW z;XF_iug>;Grpfya9yrA2#>UH(l(#tid;uoZ9Ic0ZSlJk$X-`vAQW#7mcHoOY$vu6o zPk-Y^$kDW1>WBw2jJSaTeR0azV@)X*dSPMIlhbpjH~lV&9yGr=jrT*+Bkjs&>n%1w zD-aSQ>S4t%VHxO&0oX-2?uVUevYJ$5v?jhul&@^~UCi|OIPepUnRH{{)K16?I|s}ELg=R8X}3q&6r z9J24N01O$aHPatN#lyhFR zKG5GTiCZ|_uHfqj6ms|(yNmb_HzwZt#u-Y3LcuLxVs zG!=9PtG5A#7$b;G)M^E)5Q9TaaqTuCnZ1k$O`I13Y?1MZF z)d)aO0Hy8#rU_(?$O|YrH7}8oeU_F==meD}WfUmZo-_|R>b(hlM>+oUTjCZL7FJfA zcN;({a0`SLU^Or7NIkI`EitdDGaIYm`E$-?m@f8c{WOyRxB_TlxUEg0{MDD*0vbzE zV2lzI58+(!dI?2>v@`fqy7W_r%Gaamf49K&LxTpn3;xOPkrTrR8QIU#(%d`Jy&xF| z%@INOSta2%v@RGaHYK(-UB7?foEQ*nl+HovbUW{c3ln0N=x5&o9a$Bbv2GC6O%mU{ zTpxVb`PcShZh_}&6J-elu_?SZD=A=&Itg6Tlaj8nv&%?|zQmiR{JZj{8P19;w*Fdp z7Qp0TswY4?7sTsgxWrX-b%UnB*fi!neF6f@X!Cj)%wAJA#-`~llPm>^AtMmzAANZI zf{fV;%-*c3o#webUeK^Ld8v-534T=aGO(X+f_cB^w0J#%neXY3`wOAXVLn-y#wN{b z*TNT(xBkA_bhOZ5gIl)C72$pxYabQ6o{s5 zZw~7t+cwf|Dgb-^`DuT*zf-yZrcwqRnwpxLqhpD$h-)-f9BuD_FqBLa+)Mx^8#2BO zlxR3AA#@_$v>B+vPO{-a#uX6BXw`Wb#ic_*Nl6LK0ESVh7BHRgyOKCm@a-CCWgRlk z)vONKB`U>RzS$Vg^~Ur~DLJ9ViMl#J8zx)*c!1n*`YGYY!a(8?WDrWBi}bb^=zA5e zfNlf;xM)BnBF?tfZMvDL@F@U_0iFGCCQK2U?@8c+Y}pV{3{qaSwzt2fN%5_Jy&wFjmruFv9B;f8r*D^<^JioIL+_LcQLaHWR zN7Hr{;N?{_Xv!OeTT+AW&ur2?!9d@jt;ul1)G31gx5~F3`?lhzyq&KulU5%`m_iH6JYS z-E}U;)cM!Qi-3KY|Ho~>=CmBlae|(~dwU)w-5eVEjSbL&8{Ad$z66Gpj*ffIRJYcH zT7C=kvK#YjftkU#s;Rmmuzh}?DnPg>%5^qzn+uG23vm$<;LlwQxOE!c zP8o7B{U4nySFu34n2PmlQhWAy+yzV*>{oZ#1TzIy>g;GOjyX{aKmHo*}z`P~M1I>^J@&%yyt z<>%()Y_x#mN|o`%0N9$}O3c?`tJ#cvI9c`9L2c72R~K*k7l;}94};c4+%40KdeNB# z0B)eU4I=QMqY5$< z2rF^n^bD;!`WyQ&uplnq5(Op1*EJ9lPnEjm+|aGKhP_A|(sGc2*r7$`$_Pzj;wT9; zgr|()x2mw4In4xSsXgxmpVb)6RDee&#lZR#c63cHnlK=qwnfKI3UxCiaBJFsp1)GATFr@7& zEtLY)M!qQVl3gNS0|CEt&s=mKU-uqV(jZ0&`DyPDYUmsINCsc=xBUvV4d^BSWEtIE z^BLr2BHaXf8RJ&o9Y4*@51u9=Awd-mtwsP0QVURPh(B*FRDEkuvR|b+%^dU|2vEU1 zC)4KJ-2G-SDB%zIL$e&xopg-zl29y zF|6xR#~Wj|5$qUbL5;uEM2~z*rK-PE-SHBzvGDTp z!eMKIfEa`*aAQf0ADKt*rldo4*z)Xa|r$W zRSeD-I>blHHg;A4ZD#QIvlOfvta=0s6BCliUF06h@`eAC74VEAo`o|%V>NVbg(K>@ zogAnZ&3ygQe_i?zv+Ko}_7pObcAqIz1OW|iW)uqWUGUk-%-9H}beRY+sU4()K+ECH zA>6g3+iW$L&0{&XE_u(HFUW)aLC=zE3PtV#8_4<3I(XX_RQvBpilCcm9N{Xs528|1 ztw0-Begyu^#|Wrl<>Atxw-7-k7~`cDJCOx+rgk6TytOod_f>&YK1VBPf9BODNKqgl zND6Ca|@%G!>95sQ;{y0#mTB&aR(`D`7yy3#%}asx;9() zPQ3Tb-eR`+SGcK@g_)HD_Op`E!0TxXtdahs58JTDOb>}zj+azxI2D4+huJFWV3+~V zCeX(ibQ*jJbgKypWqqg`!VIV_6dhfi^|#x6*$X*^v6{6{CmX;u1t!qQr}ds4z+3$Z z5~B^DKMPomB-N0KwY|glxB}O=HDKfkIa;$X7;+eMRD0~KefV$_WU?X220_cO)i0Wp znV6ZUI6$$3SuXmTWk!+gef^h4fjh6R;CTW!zoO!f2j$i|5&)o}#x%e1=UczG3D8A(y}UXX)epKD4{-Yy-h&0vl;7HD zAN)Y)xY(Kxy_nSUbp`qP2dic4@2*`4ZhrBEEUO2+zR2zZ^?J-{S`dUiO=A)j+@nZIOru5s!% zIm&C#u0q+I`H~Wj_LQgLo(1sbigj>~LuZ%Q@{ zk6m*zFo2K`BWvUFMwAh?mVxEhzG5szjD6;|twf!rE-rqi7E}(tGlX8jyNLfhHd^o4jNT(ejQ7plLhtqAfNXJdtKoJN8PEK+UgyN zuX`X*f=TcwQpa)rHi8`4wFU&>b_RiLVS!Dv@_{Q==+)(QxbHHHfs|8l>s%l<=M2b` z3wBX+s6m1N0)~Z!g#bGy4TJVVdk_LQW0D9@m?jpJ&?QB zEEgpqDcPQTb1H-?7wbI?`8fnX&({oJpwPhACjaOWhU-p1>wv+6Buq!hWBta1fAz)| zQ0s!V;nu4*%g40Gt;`;e=eyhd)zzguopOb241}&Nq9iX=bswm96BJeGl*;qj>oL;A zFaH7rjsm1r(5DA_S1rE4deiZMZh!g`2uGw3Fb0d})(Jt-vA^<}cye-*^#$HV+NX2H zm@>&`(m0@!s@O0n2*9TLw?v{@Xy`M*SiO8CoW0o$#uIO}3X&Bn|@RzcrvUm7vZNvf6^!5=dzMd{|w9sn+db z*A>#LX-EbqW0u)IpJmgpA`S(rH@AUmSu61uIC}1U4u(Yzzy*L-2UHwM4>@%!l=6|z zMOL}LTnt#I5W^4wSI=axNI4(q2PzP93vd58oqO-aNxA5oC!L5Ntwrg3YE|0LLYs*% z9vmeNR9$|THl^kvHl0l~pS8G2PRl$a9ppMgMacoT(lMriqc0e}Zfc7fexU7Yfof7` z9USU;2v0 zhE8OGg0_~oABLmw#a=ShcQ-vpm7S^itnA(`J`6c1K#u}t>x~D_hby}|M|E`?qVExdU3 zZd#4MHZ6a!j>E6>g;44pVEhbjBFKV*n)+*oJk140IOrX7Bg+o8oZTn`Bkpb*|B6_+ zj4(@ei{rB!_{tcb{^7$1U@|KAuS~qSRn#Gc`{fs8>_p}-y}-|pF5+&yf`qevEK@6$ z7_h@Yp@ahr3F;JuV^55`c>77gzpQBVRhd4}?xUB!C~&7gtq`(mpNW3*{+Ja{vicQ(Sa|^p?zlNAS3*)zkGPPAdO4{ zyDAh=UYY#wdyFJupka##?$WR+Bs?MgsnDIePh!$X+w^mQn2f$5S2G$eTml0E?0@ZB zh8#W1APltj{%vowCNgtuN-=K4YLDGJewiK=NdmfvEJz6)hYpm+Y~qO~zcMJlrn1Z%Yl<0Msq&&uYhRc3IJ`sg&U}@xDJ~D4qF23?N|D702&VLeH z2>FB5te)OOvWWkr67NC+h-@&BHK+@LjYmUi2z*wG5EY^XgKC?!bv9-o)!rDwM@SD0QTh;bz|9JGBnRCB_~&`&<TGfIQ%|RexF= z)NY5R{6p9THyy0^OUwilO4PB;L@Qc z3JK-V9a>}fR%DPf1K17Ne`n5~1<{AQ_wN1sW?Kg}zy>HQ8-I_4+QbvcF}9vHfrcX7 z2ykrOw7Uj73bHUcDXA>=T#An+(9UGEkj_{*npVTSsMYXgs1$W>my&%{frlKaa8p5< z!Y7aSPO#&)Q5O-voNSk3`OFY zXZaa)+(==ibS6h)kCmlP#@1ka`Lo>(b5OoZNM#N5cvIGwB0;#S-H3+5O32{pzVDgM zc0a$4S-fnD@rQ5K9$*afdn$u8m*p=mDzaLZ630T9(MO^I2gyTUdI6G3W{%g`gk-`{@(ssSgOOR0Va%z`$Q4NzF+-@X8DK!@fP3+tF|i`gZRh=M+n zP!Xg*3EWc(I%vAbdE>@K!qsJH89@=*`A(3mZGCn1K`f$pDWE7VjeqeX1xfP&scyT6 z(w9{3+U--moTv;}sLRd>sl2?*;1He}-ieclA+~5?diZ7Jee)liw~wtfAqj*E1-%z- z76v%c;5pygf(H#2Q~Lg8sT13%t(s6O$=G#;Nq73FFhH_|^^Qz|-!L16dhs}vy-La; z6QZCC25n$4e{fw*4ROgYk|t)e4)NS!sC#%o^zENv4bqG_p*24%Kj`q;Yc}QHcj{rj zu9jp7<`!se2&{*fSPT+WVSlDafqZc4P;Xo{XqI7>lzO2I1J!*$Yh#Wer`~PAQ0%AT zgxgzN>q#B^!Dc}nq~$HVd0Q8B@UbgdyGb5=CW@DCVB@^LuRL7uh~u3&OATe60CJ8G zqA*y{I2;UtS)jnle&xk1%Fr!RR5IAX z6K(jOtw%dG)FhZR|Is$wZDGapmPoFIDvIn6tPGPqo$HRbEb4~BUm>l0+SnPQujLb- zfy4TqJ50ih<5hoQ=_Oc(lOCL|Mo#pojGaZJ(puhwotszrJ)wLJF^8e885(I|l$l(h zQIHVLc;V*#Ab)?7f z`V}gbCEd7C~9 zu>z}UsQ*JK+Nbkl6D|k*Z80*DoLUTjoF9HU|3dPmY#8xe*-2tEI7e1izn~x?9;9Ix zA-{8)J|40fiUKZFAPU|89oe<{n5iksp`U%JzWykPw>-Bf7s9M!-Mu{|y@Dvge|x(l z!X2Q-_`hS>q&ymnGS2D?W6=D~OCI2xZ}Q2>%a@kE5Ht0k1>?K6R<{irGft6BP+A9a z{0|)~iv>*Z6(8GhO3OD$S3y6%1@nbLF*D@0tE zx=k<%N*-X}8EKA0+SVptPOZIor=FDg;vRH~#nwVh|4qXpj?bKdMkb!<85EUzX*pb8 ze)b0r`+ZfR>*tAT>^W_UDz#tA#t=gbMlj|h&XAW*aV=Uzu&c|V^33cxnXyKz6(uf~ zzNmN+FX(!#q>FLzLy=#;3@J7B&lU?vjU7%Sn?3OVY3jPD#D1eq?BZ2QivupdXt^BFh-)%cR%{hciw-T zvvs;n3s^v-Z<@&(Ix};0_^SnM_@&um3EWz9YgEtl}?Z|Q`69R z2nAu32%@Ah7IO~4qErVV8M%!VGYm^z$ieH!*hV`3e!g& zS(C;u+nnwYT=#8qc`Om0vtK7X1$dzz%8L0Hf=$tRFN_;6sHv`Tn!JWQ|5R)2(CxhS zNBjnhwd&Kf0PuCeSm|MPgL5c?&B;0(&9o9Kfu>_13(-!^6Cz1cnmsX0vv0IOby^w?smLF2v!_245g&H|K*ZIX`!%P*1=MgWb0 zt?!et@b`cLDNTX3h4;vrlp&$BVF!8t+30Jf*R-gbj0eh(6qn4DKNSRIi(c_XHw)F# zgMpftc_B3kQQ0D>{W-6XK#X<9#TE<-@^Ll^ly&(~o)WRLVw0C8H=pIkj+UWWC0>iMA>A!~7G3?yWau|N;bk99d z^KfAG>++*d8O76ZZfa86?M=ne?G#lCyghWg{L* z9U5)VcU$71GH`hOu>D}9*_fzDu6Tej5aWca($)wHZUam*2)4@bF+#-A@p0NVb8x>t zH}yfZ9Ml4OFaw5EY$&=|xNgMU>S(op){&xXu|B4pOX?nzQb zjtMo@^PAtw2d91{Mz_i=g>SCRT`pa9#uZs6M4+F&}F!P3+%sIJwTuXbr%Id76 zk(%UIGFLBq4%YbP^XJo$p$tuaNqOaoqQoVZfyrXZ^K*cIDbBYf8W2i3xeed#Xi;Ae ztmQBQm4V$NE7u-~!`b#`8aLAxde0a&CfFkx`stb5)Ups4spOM1 zuqwZ6th`_0(k#1c7AVLvqwdLmgKq@+ccueBRsrGdrz@%%Dyv1=f( z0Db-Ld(ktE*=#lH?T4^-m$^*I3-Q{3lSq)QzFh(rrjKb~Rvvl{wCd-NWQDv2FaXB9 z4(ln5?`f^Vpn_7xi0_)IwW4>lcMrKR z6j(kT;vymqQ1Sf-VA>w#2Gy)RtePAP@z`tVwbxc`>=bwv+80Jwcg2hT29btzLgOi! z9b2Ad>WLw!evZW_xeA!8DPX;F{}adxAVV~f^b_b=gsZsqHl~x+QmHz25f@xH_96mH zQPKEiqw{4_KFZJSr`=V7z3x|p5edn%FAGrWbcBMN+?rn%DrV^{^^5e{CVNy@Zl;nM z!(08#?)OTAEmen7KT%hgdS(bTjjoC5H-rufc`(1aT3M|UT4#g5w9A~aCqp1!4yn#p z?%X*6MV{RcBMK=6P;ZXR9Ijf?>m{)+8MF?hPrF#nu$98HL3IYx-BhE=9!tK2rCG*~l?;6LhJ%Uz zwhx@bac(r|w1&`j=SxfuECC$>&mnN3S%Ns$JzZy#&ZeXst8*I!)_8~s6Le9yn7BC1 z;0Vx1Id$vq^HaPDwIt9NR@d?eiLzYO$#A%Z?_R-QG^-6%2pSaJTR1R_b7tBL+SI z_{NQ`h3AA1&+;n!y1deNCqK5Nzf6~OD)B$TbrW(0vu;5BDUvV=OL3SKx4mhyb}RnK z|EAtnp@`YP#CgOE+kovz%M!V^;_ov>0TuXtun*Q^uWdPz-#jOQKW_;{$HZ=pb5YU1 zx*d!O(MrQ-Vs89tKF9!C;taxIeGgETBf2`myh zP~7g@fSxt8kc+DEIgGOi-TRLH9%vPy(sseE?J#Ih0Ysu&L;m6GNyQkP0Nj2LAQ$sY zRZh@JL;wWMeCn7SOA%v#ZP#z$?W*y=#2acFBIz4(q4iUFL1I7t{n`O~nNpVKPb0ycd zYh=GNR%O=b8oScWz2i@|-+?a*hb1iU%M_fS?J}X~la+5mpY} zxbXkY50`0%_%@#cjm)T6m;u7l<4=MZYr{*^8)7@c**}+dYJl zwQX7$M)jSmw68e-0QT=yUr9=Q6=a{PmF{A|DTJ#}o86AMAI>l$66hm&K}E#{r3A;| z&(`$&1zupSMs~U9dv|l#asJ42|5mSvA!i=2o-;z;7A0Eb%t&EU)Sm_LUGB9H@23_g ztl1j6g3jD}{aBqAEJiXA)K;`UcGw{dH9cKcM&`{O{HW$Hq4G__MW;S;^@FIkJ$LZr zhi3Knb*uEvkSq(xGsleCIAv(|_xYcVH!Z=PGIcLehbp#i8a>LA@XyEA3$&i62zFNFGLFk%6vVz)5@b!QL*>n)z1o(f+`pxLGgytcyrwNLGp>AJE?$xat4!Sm3U8Su9v-FgqOQ4Fl$72t?3+S}0v z28zXtnLD^l{$vo>rqQi@FPY$o{wQrA8=0GzM;UEXfoopEvLLY~CSCxd2m?#R1&X7< zo>f}9zVk>%IZ>x3&0XEn+2PxVretWpPjv6$-1Bd99~nCD^8UxiM2s}iWP|}ALNC_WpfVBNJ&X~dwaK-bPNov zY3Ug1U|<(h{_foIThn%bl}frq+xn}AW^<^UWK>Oe=PS~ycRKoVf&}8<$&7nZxAC&8 zXosbLk*ct_38pnAn*bgfDn|o}H+YOoe0Jal2tnN>0u_35KfJzMY9j%Axn?A&}RU#9G!=j7nT*47&D@; z)@$_ay1X2UyKq)!Jt&D4=h>v|s^bnoRj)do=whks=qHdm1?r$gnYknU&}iPP)kPYK z#XL2!Oo#$^P$3t38ujSb$QzTL{3InMeJ-DHiT#+-2`YwjXFOcL0sAKmJo<=9j4r>*!hsi zW`I{KC|kXsNoQ}Ey>AXAS`@Du!Cu*`1pv2Z^-m!v#xj(F0I oethv woeth ..> oeth oeth <.> oethv ' oethv <.> drip -oethv ..> oracle +bridgeStrat .> oracle oracle ..> chain -oethv <...> bridgeStrat -oethv <...> aeroStrat +oethv <..> bridgeStrat +oethv <..> aeroStrat bridgeStrat ..> bridged aeroStrat ...> pm diff --git a/contracts/docs/plantuml/ousdContracts.png b/contracts/docs/plantuml/ousdContracts.png index e58e9cae3ee08ed03d0cc549aab2d6432b3b7287..f4cf7a2584d4f752cede709657256e06e0279c94 100644 GIT binary patch literal 26210 zcmb@uWn9%?(=Uv&6_iH0L%O?5Qo2D}QjqROHcE#GNJ*Epbc0B@lyrA@*IC>Db)D-z z=RW6io)^yx<5z3Vnwd3gX1+5!L|IW9Y*;+ zA9`m=ZD$iZdk80ql_^6X}F z3cKA7uT;h#3}(pROr~n*tD+^Vt)Db`tR$|6%4%dkBM;4oWMIowTM%()Ae1c1j>z~2 z;UD99w6oe_P`HXHd+F-xytI;wc|0c_o~m5^VI5uLft5g`xYNXagWP;+^hh!97t8TY zT?hGZ(du2YP(fh^f{3rGw}SWjDA!pYk-d-9toLpc_9d2oW*pfkWx^X$4X}q7wsTBA zJtO!{u==F)HLKFgXK@>3nj~SWNiBuueZ&=yaOGPr(6_m0XWySCuQz|5%KD*~zU#M= z7n{`~R~kQ({^n+EQq`5L*doq7o&8Vctx;7Y8fR%zh;BC~L$#WSd+ST~R!jP#ld4$y zkMd*7e(xwy7~HD3RLAW~XNYIU4S08DKB}V?_k1__>2&#OZ**C{eE4*>IUyXKeHhpH z;N7vS>cidZCjDWN=FADALG}zio7*R`v`*B#v#vKy_vtrfLh;D!wE;8ar-6g%8>hmp z&g;dk?E=|%W_7eTpTk~BA+q(wnKr!I!XZ8um3v+YWiTiCS~mT@1%9c>^;dYj{PR=_ zt<|?XTCeR{)DxbW#RLT;3*?eXDCqtALqpc*_>7sBW&Oj6W=v#U6UhKp#qpH~jET zeWqr#rooP;j;F5gLg;LDOLtDcNu!CcDh1Q;6*@#r@}<8PoESO;B90Du)Cz8(qa*+8 zQcRTf-(w6Ko(!=VG`jCiSK;I1Pfbnz>0;6TVYEA0mYA5xtXA!p ztytQ4EbHlc-4TX6p1`x<9b;UgS4ZLJdosY2%x$An@4PjbBCz0lV}pNny;GX^kYmyn z>2tXhG*N2k=26Vja_hLz?9&&|Vpwk4^QTyMYeeMsFaXV``Btb(+GT4bC)r6NttX;q zT|hug^hf;m#eyGAXdIoI)Vr~2n;EwSpNZw7A3s?6!{4p3``k!xs~2iA&4myPxMnB# z-JhgY3OOvdFL#6$@(>Xa6f}L)%#n*v<#Spe5_zz8j{{`wm35IcY~?09TVTv09m}>{ zZx*>GU*(dAYnJHccYcmwc_UioVj&+(uMz4RO>XUu_HeWCK;m;c zY(9__$E5oe*=D&)rV%0rYA5&JSh`aedd;HA3X379jX}#a0KX9CW5qTIDOtyeMwR~oz)G!>>I54R_lqq)O0lrvCa zLs{A9&jrwTBRsFx;?4M`YV6Xuo7M6Zv8FP_5p15(Xv+jdyJr-lsrF=~sjGKJ5NhQq z65C9T64Uvq(ovniE|-vySjBEt=Veg;E;6Oce9POmKpxv!Wu@%+*B8{JpZhJQ zJm33-F5$-J#WL%Aj(t~YSF_>nmbMx!{lzDcNHtTa#d1AWtjlwaCc7bgvs*#CEBb>c zv^TGRt$sdls=`9;Jz$^fYa-W^bP^pb`UbyOW*U@`})(rR!S3AXz1qrW1zw%ne*+1bBNKNS{lDgD7S1ZE%^?W z%g&#iG_3s8$*T7gX};H6VJywxRdb%-%~sna_o;m&z3orra1>x-ia^b($uz=1d&!c{kFjR;7-EY}4$wU$=oQ_vJVbhbxaLc;fk_WikrwJl(k zT$}yRb|(5XkAba-@HMD&l&l_7u9~|R>qjs0@!uFqOB43tA5ZBa`kKooS)$*dX}cpo z=S`-4GsZskg-Y(cH@Zl)8@KR=|L*!MD=kYp%GD$EBp{}W+28$aTSFfvIP=n1UTru% ziYfdW)Z?`dCfCvo4|~AaO>E7%EyVGifqgPYpbXncGW1u2SMz*iqN&KlceT#SeC$Y zC7UFa#6AROy|3UqKTZOBS$}OZk6m@ZqFg+4&%N7Ye5&sRWKSdx_1)$q8>Au#sQi4- zr!2}F@4!(p-3DW4we_3nDx=oN7I;2j^OvXazkDfiAM`uaIEm}Ml$UPhyCc7c3qLDC zY@>pN7mbxnXjWcfyYQ9np9KyYW2!9%zgY$@n)Q9{aZ{W*C6&B~&$2lSWobJ7axR?m zqEhO|e!kK-Uh){1{i`v8467MQJ-u&#H7-U;IC{pe=?rI&BF1pwDKIO$8tbDgoV5=i2LzTTUuiT{+Lnj?$N zpcy;!aUDv=%f+R^9xq>UdFkFGJDsb4+Ct9B=?qLAFd}%s7BHF^dLMmC@&$&P#QRW; zoy2T~TBFC%_sXoTE6_OiyVM2d`Tm@o$ku#t0kFI=8~f8$a!nLMiq6iQb8X^iLZ%7n zT&d^JhxLKvzE;tf;q9l--Zry9AU6RcBO|#=X}&WyHC48AN%lpG2GB;m)A6S$1TUpA zME^f~ivMTF5jdRxM-OwA8G@D?&uTg^3wc1@?%hL&c;G_&l=SHo??V0ZLJ6G`Y$Al12ZLFW6Zwlm*p0=ZnGTDE|z(q67ljkLInO;z~|$-@2YCf z1hm}EROfo=DkmNhmzUyCrKwwJas@4uBF_xS@StR5llf~)Xv2c9#%vi4?&mPxwA5{~ zh4ZZMValNeOXeFNzO>EyEVA~-VP?ucwNaIEo?(5@nW)IN$U}1W1trPU%gBePFh?5l zfNd_wL|uZOJdZsW7~DQFxgIf>u=AnBEkx0cma#-xqjH;?#9jW1PdJt5sW2_Q1}>$A zLmxbyqi0D|rGuRQFs=M4@qpJv6Z853jtWvcl3{4@{qm&?ATnK4@^JD~rWtM-qrw<6 z&d{wKa+FyDNyFEjgur5nG?nE;rA%!Hhl4iR?AtmDeL=54o-YdR#g&|C&p}kz~%RWS-Zk69hUrw+QUzh32ckae&Sko0AZ4WUANVL#WIqGEA zZkgLsYhG9ld78d3jO0$e5gq&9rO>(O$uQ6hBhY^moy~~REsII$XS(1uhy$a|?UwYr zO2ZuFB#tnR*BtJrEMjZ9MjSG)oqj`9xMbw@+qQnU+IX!`7Yc=;ZvKuBQHoXK?X7ki z^LFkML&y4wDC{lyvT1;@P>J4za6fIJ&7VOXgG$cnV!B>_t$Pti_9AJ2qdKZqi>b0A z;gFZ-{jH5c!Qo_Xe%Oh=|a~gsp-8{ckf@GY_&o(r>DL2 zN2Y@X?UOn2WK2LsGACahvttq-w>ZAQw{!T01CMKzwRRe%-~^ZDG9lwZZt3393I3-9 zFljw%^|Y`&tuSZxV$VWh{}_h7;rh&i$5K3NN|l5!h6X>LwkKuFDW>jiG{&JIy5qrvK=>Z!Ihln%DSxi4p8?#1kC_(-h9aa_e8u9Fv=9 z)lc+X2DrUG*d6luS;5iGT;h#1(`PMkfYlKi67RddMbd6u4)vK*AOL}==#QY3oMIUZ z$P1}>5+mpB+svLcp&*9h7mhx4BRmd;Ww%pTio(YkYrR7Q7TBb%rdQmSh3?RcH$0Yq znMC6Hm0LI!mwALZ*D2-?ZTMVe-`-__HDjKEy0t;MU!R8kWFJjA{_i%Et5WMH=Vt%; zEO#%=={zjg^N01k%I-~|&1CM9wXE7|Ij1e-{3TBEPb#^vlr&!cQyFLMDd`P(N-(1U~&K`YarjoDA;~BJ-^M zp#aH*b|HDx=V!L$=2V2F)k~a8L$Cial(eE4x64r!S3(Y}R;fzaYMbpshL?$E5Xg>r z?}lJ}-l!-EX7k2lvE5JXZe&~_NEZNoHGi&ig1JA zRJEm!onCK4U+F&9qQl0b&Er_M%$-FkP905?=T9TWe#p|U!z~XP-g7|+O$$qJtcE$$ z3y%$JGh@x~6z9IhM9O&~Z3-U=#LWV^zjJDqwvBFlw@N(aZb;U?L0e1ss^8A!(lJX+ z;mH-HMY$7_4JAZ8e+GGfw@uaDX~^noQUYH;pCkWc%wr-e%@I2E6G1X(+jy1no>V^c zTk3lj8)fPY5X(t3ph~SDoSPqbin5~b`u{#LIJc*^80ZlVd}irh^d3S(v__df$x9=L zXgkKQMO=|hMAPgLA!4TFq9pG=`(fYe#h*V;M0Hn1 zCZ*?$iP5Gf@wi?S@KFE7MwU>qx;M6J`l>e1dG85l2+%|*|FrZ(zexL?#><(slB33n z1W`k@9FkW3#CgVmi#S-@D(vI@8YCA&j4WC_eWXv^CP+pzNM|}s`?ni+@{UpV|7F()cmBPqU8(hzRF>L%nJ4M83d6M#BOjED@M_MPWmJ&yH@MbJXTHfn zS}rNwqGP)CbS|OT&@~ET(-8Pp;bHWQlfoL>xR&ExCccMXF#A7g(Nd(o1)CH>0bL`Y zeT?fHD)DT!14+hkY}Ne+V7`u?jbk=lm%J^bHQ z*7&qHS!JYjeC*=m_@Bqas-$k?{6A9)jlJ(waCB2ztA5Px^*U+~W8BI~sit?6Jm$>8 zr8#B*hfgLLdBdF8aW7|djb3{C#~D$)e1S+3dBS#rRT?k)ZrBPm$4m9U94rp5)8050 zE9pPQS;?dNzY?6qElHs0mWgnTM4t3MV&X4*p z#vPO<)D{7vAWRKD4G03)Vj=;B1Je7Di$Q~;n&_XzlERh!()vgt@tR& zi2hkl?_uqetCJ&K{7?1GkuHgXJFhh=Q0uJ6!wF}Pb&G1tYvlQuwHzl*h6HjuH3|n5 z`nMi#kXrR0l5DlYZ4M*hiB^_AEQ*S<&Yp^j+Rwk*mP}p!dLHn4qhD#3FXpxxzTC=d zp@cE@td#qyFhZ##ZGIyO!ft&W(IGt-2y(-x&!T1YOH>&-4>8DVC zqmOW}>?a!B&8I_lXMLUiIQ2EZF^P70H=!%iR!fGG# z9X~J;;pp~=sTC2er|r>t(Keokra{n?cjq|gk5xV+VSl!LeQ+pK<%sRBI+V(S*A;{5 z4tqG#Mtr>HjJOs}L_`KBpdhh)yP=H2gd zQQY1abKjKWN#y#?TP2?Ni}K z{d_d(0C%HPpeODyT*z-8`YqR*^fpq+@q3Ac28kroV&ZZv*4~}~e;RJ@6X%mKlDoJd zdd=^>6<&b@$^_nM-hF#$+Y+)I5Eh}Iqv>0-(Hu?+Lr#jy^~HEXT4Pr26`=(a7-g@o zlfCUR@~FEilBpaw+|Tvce3%-ZvpdE;gE8FyqtVvFii@}eBWto zY62EZ)?2}372AWGK(_&R9kcVEiO|&IyIVsiYM<;T}l}XF(0bf~RbL z5sc1cc!*MnFCj5z(%GBBu{DO5Uzoc1W{}q(#f(Y83n2dCl2|=><%-^Tv7=WqyY+)x z$X)z;!>RcrPAiox{lI9x;o2epJiQox>G}Fr(H3y^ziVk(v&l(_+4C7tL2O!K>Q#i7 zk<Rk`t`P#tH{_?*R@?rPhzzE6m`6-z}`iz0Ovy!-}YaA#9?L?7g~+ zoK2un8002=xy>_Tk!oxGV0{>C+F8@g-idY1bjxY{?<%=br~OZH9`z01P(gW``dDtO zy&NUai3c=aej-<^#SqPz@2siS3RUMw=z3~@YnYm{)neCI((Bs05jyj3ult9fzD#mb zhh)e)eAm%d_Lm%Y7YZ)3UzvL9y7i+-s4hkDg$jBU+AdC3Ul*4cGB@ly(hL(Fd!G|e zI|uhUV_!L&hrEkIfcjv!ny)mSVC9Fo+17C&Yex#FdrJ^qYO-n3)%=zoUQgV3c+c>J zpYbmKvA#@vQpfMel(a*`4Sj@`m51wTZ1`PB1tdyupstPZi7W*1TmzDfVyxsF^>9%x zrlWDSVU1(SYdq^gcDEXrX(@0wUPdG|drJ0GO-Gb+(i&ZWJbd%@WYQUt)+i$digdUD zT6UBIyC~DwECeXV2O~}iJY?fif|tK+M<*`7Q8Gz|&sYWn)qtrq(Qia&4y7-r+`7bAmN3ENZe!+f*j-@x8ThTl>$=JSMk$f&DgmWaS)|F6fw-)rHZMaz&~ z)J*$9=+0Z%yHBAftD@5>7OiK%#KX>yd8kn>3D}MwjM60R4Wykdqb5?4-|u}8*) z;~{FXjKubrG+mQR(HtI@zrN&5x!M0>Gl39#;@Hm3l_B>x>U*PYt0Nh|TXp`|^rR9$ zDv*q}a;!IKdLMn%hvsL$9M<%7J@_3JWjPzTB(UUQPDGsht5Tk?WGpJFt&uW6brC^f z@wM|EDLO~m8jeP}cK)A(a3k@dUo}#W4aKwj-0j0y_P0FeJP(H)_Cm-IO1M_xU?U6M zpSr@%_9wqDX4%euJAV4|QRmG7&8YUZ4R3TYuVf+q?gz^%9pj-VYcx|887Uqn3q002 z1@FDfhIPV~DyPsf8dQ#G{dNfmCp4_+<%W8(u`hg4CfZ=?wHtuQeTp>OnDu=;Sl2p_ zs(g;Vp`tQY-ZyH6gs0vJETyXNc?h+ zA!y1@ns^>CvXDbOI9>!Vz)`V4tW}E7qq-mzH}ib6diJ<Fz_;7uFsSR8$?#ro`$=~L&nB|-3Ltsu$$3<)W#WnTdYG4MHYYasIny3a>)1RO&cqs?dnOBys{pT~p? z@6isGIk0mTW*mGBueY0gvfG+go9$}vVkykxCt@`^Ly|pk6dyj>II$O2x#4k8GLTM(oI@mh^P9xwaMZ*h>oE_tJt))qX%lU$8;W2$%S6o?biD-_j>)+KrL z3c~S@g59peFE}6;rl$Q#Tu71xAYv!_<;B-H-+0QQWT{3hpu8zO#-=o#jIsMfc;)U{ zD;*sj1_lN$vz};l;A8mKz?HXjBgWr~J{`CUvoOa|JV6<%K?#_FObR_`Ip>(DARCE*dZENyu(>(Q=s}og+^pAb4 z_~x0Y`(CGhT7}27Q_kBG7UEI6N~!k!S|p@Hfg~FBJl>NpWX|F$zmvU0TyYuD| z^x|N_P<;=Ljg1X#icLi7b0G(bL{)hTN_DsX>L^~{KTZTz4aalM7w@GKB|N=({thasjz0l}C)u&=F2U|Nd1@8h&uO-RECOhUHbZycEBX zygT86qX`I`KUA7;eorAF8?`w!k+rAtkCDKE6_Ph^zgSN<;dAPlkDaqT%e@)LS!Rr3fHbi91djT0XF z>oc8obF{f&3c|vksURv*S6&RFN5PI9N7U{tHLIlML!1byJEPWey`1jCONXyN%r^Pv zjpL_;O7DW$$uspN*-qZ1)|+z+lzjK;ydi>Wl^PJ^<3EFl`-d9sh~gxkwxk|ON2Z-M z-mr`K>hIoueDJg_Nm^1UI4`f*a1YbRxsX4dy-Q?2YzqV5<3K}Nz#e_`=8b`)h<5Xf zV|K`m?GJ6Vu|d_0!&n^m+fN{`??Z0oTaxI@?FJI3@9Y59bpxtBXiNU4T5$`iZRccf zo4l{sd+8ytv+YxHAKxpnPal8>0ut`8#Ox<288F6vyZ>w|wqQJI*?0^k@0P(fL<8CxH20cC@!==jJX8VMThIt>WjJg9wxy7!b?*+begpPwA7B+6Rd+fI#$z z7A*(Is8;RbD1axwqD6?ng9KgeX9?wZv$bsV0n(CZ$O{AyAfGF0zeM-)u7ZON~f|N@RK%C)QNk#yc7Tf}=9!`p9p28A+#|UN?7B@tG5A86r0?9yL zk{l6$8vx*dUA|8da6@Hz1W=Lhh**S#0BORDzB(GqCd_!;yb3f!Byjc z;WOAIB&c1jSISmaR?g0q3zID%9SgyYpjJqV{qhBR@fq#Pa1YpTklfUK_3E+bDxmH$ zG)xLq3vEURfK(~~P4ydGQEXZP$n>c7^TjrRqi-$_E^fO1SyW?mOLTPf@bIuZHmr8( zQ^EU*5`fSPQd2+8$|DEMe4g9_s{*-10vMC}B*m_jlvHO&hkwi8w~Ik>5tB|O;$PKR zApk5$i23g#GgVYoF>~p}6OtZ1YL(0a)gUM&f_Mjy5If~Q zd|0IT4Or0j>(}Ee6j1A9c6Ge=X#NT&V*BWP0A2&7^uOB0Wii;DYv9p&1;8joX7&hD z5n)P7N;uNnU!RbG<}R|p)~Jke7~k)A2tX|$n0LDiVB8fAnI%IE*z)p#XvPb*R)qdL z=tiVn@47er2?_V#^yew1MTUo;PUH!Aox}8HYXMo96ITE8y-=$(@YAQOwU_$k`=HdM z#&#~W)SCS7Dk(HC_UDFD1UCH6S&nd5hV-8Qm18iM@9ia}bmZmeixd$*VNOo0^S{Ij zr?UB0@b)IhspQm0{{#I(Mb1|5K-k9Kw<^lqObT?Y{bZb@E4ol1R!i| zyw>Vn=>s}U_;4d}3|b`yO;+YuYGsYySLHkle)96j!kM}s#VkRln=>>j%FTd1pq40> z(ysX=441{~fc2*q2@I*Dp%VKFN-!dbcx;IzJ+JI_*!?viX+mDreh>HQFo2{v4-Agk zmzkBdhGp=}!QI_`G*_XijwN#U^!jW^4$^mp+Z9RNmiG}h*f=P;mOKJ2)5_9Q;`i2^IC+d?FH*Ymo^E zb;|&4T>v||(VH=aXY=;zWIC;0%(A`-=qqlZVwx~90f8(TWv9oU0;o&NwVkWStaaHQ zvs`Evx@*JZe5dT_cnnJGjxcPjKn2P4^N+6XhUVtxKy?j(0^$0b_a}(uH~=GI;1#se z8BwTFs3{{O1IXJleA2oM?8~LSM;uYO%tMVcVlF?##^f1xcMyi4+3Q!QU*d!bNRmjE zsO*2uQq6mKy})J60@XtzwF^R&q~Ev}BIsfs7guuH&T8Ap$0G_mEJJLRso-h-z42Pt z@=CC19!}?!%@qPC=5>$6l%9S5{MjEK;meU6;oZ#zf;2EJ|4+2HbZoZHsb`mJSJtT(6byl#yod-G zU@UW`r=Cy8kAg+Gfa%2?{QU}w<=4H~y?W6eOQSGaHjf1H%JBt7W-jip-@SVW9JX6) zK+xN2!CUeq>EP*zj=mNFiX9$Yp}?cgMt$S0M`g4c$f@l%SjTyS47LJ zjb(&R#J|s&hqq8abzm2SYR>9CDy1w(z%S4Pq&M{<^xP-5yLf}w@#IaH!QLB?{yg6wa`!sbQz^w4 zH|%grr*M~iY?-6=gpZVl*|wx+PMhq(5CXl!Xq7t;3mtF&iYB>6V0TDB4kt$xcg50{|PEzUH22iPBSEQR6~r{b|;C+_&pC%DOb<^%SHeR7U%WyCKwGbu;M0xd^%gF)1yZD1zuVH-5i&H^D;|>VF=Fr;;x8kofZa9SNlWC#B1SL=@{E0c7Z> z=%HF^`M7w7hxG7!2(BdAWKDTc+P|N%YH)zO%+k*o%v$82xu=jD?eoLY*F0c8B0R)q zE>%m4?B7N3TJ$Mi)~0+>->5+bxf3<-?k7`-QcK3S%hhKX6^eq@(*0BGG9K4DU7{vr zlUY-3q|67V_5rsZeJ;i%lBY-41W7zs_#J8Sv#gH$8{0an`&XXb5 z`-%DgL8Y+^aN|q+gFh*F<^YbS>mc9a9ZC(FRw?IBw zw_q;GPnp0f>!STCuOv+BewRkY@|qcr{lI{$2!$bjoy=>CU1)5 zM?!*MA9|BYS!!RyLz|pNP(TE6VC+0tMBsWZ^~f}Q>4JZpD*Pi0B@)JjgB&W4vQionD&3itM%;S?pWI$GV8uw z^>P$-IJ!ReTJxWmS(Au;=)Eo{{;D|)Yx%z@q7m2y zAVIeDk2HDV&;erI$U7Nb=ph`fnDOGmdnv2%zDC8~zbZ3u5lE zT5gf5CDrlZRBwBBdCkT9HMnwzG0j(8>#2BV8p+!(lb+M|Nqx6J@Mle!qb#RB#p5P7 zh)a~NJ1DASQ0Ff`g*LHzxgY&q+PNX*qA^~eHfQ)HyoS;1HWPiygo3o}s|J)WR1hAk zy~{*Us5BFcc7E;gCLw+I(?3e9)Hj*YH>YsoU1(m&dRyB^j@M z-y{^vj7{Gn{;}h39t>*D+T{F<#;j|==-v9f)iF}jo>o=>C>eY!t%)D~sK)C-eFjje zbbB79PpR#?B3Pdo6p6#oDPJSX`nG-rZh>hJ__}(aB(BbBL(kWfpPG#=IuZfM0f>zo zjV^7Ya!Bz{ms@j8UJl7aL=9ohl0*7*)3_Bwj9%5-9X?x&cM~&Mt)MVuBJ|sSSk4`& zfqVP*t(ce?aG}bs&OpvcvTLGP_u_PG^gaOOVDw%uoSP?3N>fZodTz--%;tTcOUx9|#^qHg}r7 zRJ+*XasM>;F>BUXH@C=S?nmvOb(1S;uk;98rC3*=@DV6DK^5@|)JIWnrB-mC^AZ9hrvzLU>r@edqE7;XdQdnI zoMkM7^Mr~B!t^_Y?^?>)oOp4Ev4WnKytFUmh!%F8s5*MW>ruWWx zVZ6wF0T%UrZ}vM%US3{qZf*vKLe}ywIgt~;QC|}xwtUy?)fV5oZ7JVC6yc7osZ`H(V=&@D1L@)aSpk6AvW1+f{kmwDQRo0*rB zlT)eM-rPKQ^`Nk{S*QIWW1{hk$tpF6(3+~_$cbk%Nv5jHdB^<)3W&@Y@LK)My8shX z<=2af7)!fJ@cj11%wEPecJcmwAQwY zM0ND=flEYAHhD5wXq}4f9>xI6l0hI|2vB%OhqYf4tbq}@ra!oW!G~-0aIwk~GH@%w z$u;lc@0t;_7iBI%X28>AU7bKw^o{CzTE_QAJ26vA==|MyWr_^1dH7-v zgGcGUFuaR{8L*6jw*;*avRCwC7Jr-sH&yk&+x%DHh97hHKfp~O*}s6BGXUI(zRZtA z0Kkn(+CRVzwHCW2&o^#Sd+RTctp3Z`8Uc7&(;2b<#%i4Yh1E=Bez>L@j>FnY0-%VKm50I6m5}>n%}qW2ML|HS1zooQJTGH7_ZsrXrgiMfC-AMG3_3^rhNDKmP_`ky zp5AKVZd+mqtzeKn=^%|jy`<}UrunFm6Ed>gD;aK7p%IZ16q76oDm@;SRFE}3bW_L0 z(QiD7LK)&BBHs^rw5ZOLIcq!9M+J^tui9L?Vw0*29C9NJ>*|8C6MM&sZvT~p0=0=l zJ(7a-ZqJYtXjk9{K_vP`JDfe=w)ZVN%y&bD9PqN#BQIC0h@zul&_rl*xsoW_;?-Te z@7!-f6VUWmizgf5lE9-*hzbn8MS+XY8cdmR1Rmic+}^R&Atofb*vgvs+bo}>hLqxp z7gHe16XVSK#$?piz-Q}dtyg$(srKQeit#WX5`aUx=+O|WfL0O zDg_2v#o89nA1yWruSNK(fH(yig8P%Qfd^82aCPfZdO-rwE&68~_A`o^jFbaTcg{Az zh!@VNwxn>16E>5fH6hcm_@)-I`n-Fei2roOM34IY39C(a4XN86+{SI4;yKB`iM0;* zctF6*zZf!)ahluNUAxL2g?5>5Bt^h@bNDgqb|KT4tGP}8%`V+1YuA)w6rdsZ;YRT! zQz_zO-sc!g>}JV9Tb@HF8(nj0=-wly8YXQ^tL7&CAwS@|9G-*X1EMzTN+goxl=s}P zX2s!uYMIeeep+K|&FBo6fki!Ph}C@G=W}7G)kY7cmza0|#<)t8g2?`#=8Y5#>jyK> zpq*mHMjqoM<4mWj99^OSuXe?R+)Dq?bW$YO>)G) zrcooKmPPv|A(_O>Zf4Z8;vhH@A zj;WKdleaiS$w;kV+b(UcQzvX^JQDAX?e#$)Z=}HfcaXcEt9J=ISEQhz0Gay<;NBwP zGQ+`;4~j|t1eCrS(*DlXjTy>@6jnQnNSo%a3XQqO>(rr%zEeKP!Awml=*cwo#k>dE z+pu$UV-u4G*F9OD(OQQU6hdxO3k!HD(H~0WlR>Y>y!Y>to4;gOq@faT)sH323>*ap}JTAzS0<9u^iylEUXsXmqm8Wz4dgQ%#W z5GO%7dVIXr2OtOFNvf!*V2zgO3)t1RfwscYfmzVX2A~N%4)cDH$p-G>s*oD;Bm}4! zqHxF};AfFxODe6@Y8)GYO#tV~+nfOYP-Qjlx#*A3xFie+k*(CPe5c~-$~|WwB^4M> zgOqab*WT5Y<$?#`DwLEK%+?L)1cf$F{fA(w4{~yHSvJ>lV{-EHr-3)K^)3u^$itd< zG#T$nT3Md~_{iG;lsJGqbz1uri0^_uCFgNzT-N*X>+!D{{2~FW!OV=hfCM0>2NeLt zYHKIxWVN{N5vY`Q-z+2Bt%EExV9=@3V?z}1jtB;RV|i|AXt=^bozUFC@8VQ`m*;<< zw2XtkSbGkD{D?_XmTs@S3L^YZ*E#Ut#6V75Tv<3SI+~ou__8vRhz~l90q$QVfKEUh z42%rL^D})jbMwHUAg>;Da~S5OtJCCJr4@0@6buv!v};D%J%@;YwxeZWVBqA8y}#~S z(Q9;%CfNXC!(S-l>O*HFaq{QSi%Lqdv9VbA_#rF+jr&})(;ZDYLYX5Xr0(kK3J?>v z5L7U_otBP{4)D7lgeJB|^8oN2bpYP}zqB_2+Wjg0h@ZU81zpZJ{x?W~zFRQ>wxOV* z`Rx?fJl==~O>l%bIIe5G*xo*X?V+HZ#{sB_|M^&jZWL=PrB4mLJ6#$nR=cwv+a?jEgltq1}7R((^1ed+-)_7_}a zRRdfE=+TzslE&YMwduv9IqV!9UKjfje(HdfI<=jNQc@0c zOm*{9MgGagP*+FC5^U^$)BWF;@!8D^*Z{l*9Z}ehG@zV^2gG}f>e@BIP)Z0cQR^uI z2M5Pry4m9@oD%QvZt}9T7YDHPEG;dCyw1m%9G}|>19JGXbptT_DjKxFCNNZ0{j!g; zQUzwtmJztR?2TgpSRUx~?RxJJ&urK%Um9yrw1gD&!HD)ZHWpSk_f2Pe`_B&lnJUk- z?aj@1bv}F3T%Jp(xTR0Pg1Egx5DUUO0Q_3q^t4VA{DMLl4%3%OA*XfZCVNKV$6!%A zKshBof)H~>11l1SShENU2@#h*Mhr|BN%On+p!l_Lf6k+S3vj}a!5_gVOrHK5gDFlV zlejNlh_iTupJs5E1Z%jfbKX*=c-8lnDFhcR512|)He(6pNL4kpBj4wlK+N4de9oJ{ zftW{c1ij9!L6a%1`JbXUpuz%tjgO5@#U_RYk`*rCR{#ijZ1rkv^`TD;6k7o>f%UusUEVBp%Q7|818NRX$Z;)&1AM!u zuvJh}P`tzfUG*S{Zm6u}Yh439nj&TP;q!;(k~e!fAc#n-119ccG`5y+naHvYa;=KT_Z34~82j%NptcrJtz zEQiPHMg=8=FH0ZocKGD2qDsE{l}@gav&Na2BhkR?VM>YY2K#kFxhWO;H>J3=dev{Y zFIx38Cd=Ma)ub@o?%<$85e81?XH^|Hu9kzCDG_j9r-wg+Ah1F?Y}1k9TD!G?mDj6X zT69}33CYwHSINvq{CCGq<+~Bh?1wLDO`@RM3-9VVh+*`DVuvK-k}^j{%Z`}JHlV>t zEHXzy$dU5CO7b2foWwie-E{JQ-nAJ+e?T9gl-TQBURxlJK1&WGX>&K3XD&PapdCy9 zRB5?B`<8AfbY}4B;A))lkpY`s-incn$pFK0D&A2yHLQPzcM(0@8|EwJzU>?}UVdJ- zr9W&FEaM$v6Ehi6hq~z*zv&#LYEG|?U0ff6K%(Xw8MP7Nj}|IB-qHO=PyFTo8;ZK2 zfkv0YyhRth2Ib0Kimy2#Nv+F?ej!U`-?skW_!IB{hCj8$xG2%T{IAW5KS=*4>;8Mk zVm#3vTOm8IpPJ8MzFMQhqAr!e-PDP}aB~!9-5HUzt?iXY4!Syd&rL`(Ecd z=RY&oHSe>1p6C7C&wbw?3H&8P`G(WDTx;UEa6|2xDmB2~G?rboip$yQxY5MsuCN#R z-Y_w1cVx#j^YjrsLPd0w0mv3cDUDyFUAY5X4Puo5?_%7`|LI-S@^81~xdtizKO8Le z_D0TkWH3^C1n=U3lPc)nQ^}r`JuRpWvfI%rq&_?uR&c9SVKA(?84z9CX`Ddek>?k^ z5bkmGG(=fN{uyba@6MK|j_(RC2iN%as^k-rCq|S>!T=@o{OP(Sh{u-THaa275K76S z4)Y9XOd5M505OMV^*lhZoySbfl@4Pr^t*oqlXUX&dQv2O21>wo120d=2aisgB1tIR zPpL!I&bI*|js-L|z*nmw!P|4jp6pu&YNF&&V7CirI}A2M@|#DTh#*xg-IS z>5I@Xy4uwrxyZE{9u5oC-?32Bjah9*?c|#H?;g26PM6P@i%YJWg`H&>T`e0O1HFOa7b~;AQEJ;$=*7rYKC3AVRo$)Xli)6s< z6yyFl%99=E*TF4L>AzMj2j}NeI!|wf*DH;rHJ^F5kP1S^Z#JzZx9= z&+5qCQU|BdRy~{EGrObu2Wx32;9xvEMCx9u{N?(ze77rs=-(HB^b@%4BM8I+^Tzof z1VQ$*n|E~lMvFy7PWWC;&}pFN4%Fmg^v1Sgj-K=t+0a{_bVp}Af=d>hwoKO=Debj4 zl}61xZV5$&I{9-BNz;rKv-Xs;$wJKvw%vZcDa`ufjsC5W^yN_;V4|Lv)cUDgvnq^; zm>aSzW2R0YWY-tlJFy#euC&dbk0B5Yc zFlV~bT|fo=k<%o<;61C+`^P&5dL4bfD}Nu4>)xd7amuU2F5N26`!rji!te>8pB~BSveBL5(nB5uMpL9N`3I`>$Mz{aj~Jpr zL=`qYwp@y@msi_%VUIK}yrJA=VYNbGad<@Ry&#@uADlcZP~Ovei{KYjsDA#t^=;O0 z*Y%V;+LO|@>EK;8y^dvHn2^DRVqfn2MZNW1K&NcE(yKOvc<1*T&2nwKml9Erw1 z+EoDt56s~e#We>aHEi^&`->Nwa8sX9`f~9GD8a|HeY{g?1(Y(vtI9b%te_q(=e&^MW}%5t`7{sk`8r*HGruY zdQPPW#q6mn%|3R0g{?<=%AWStO36Cs7Rl@=>jLRevPB(jtWbVMCRqQu8iOIytf6+7mDiMox;^d*ULa*Tp&}(J~TR%a80Z-Z0O8HesUci|m%G`Pys@0LQIxlv8!0 zJD6rbbnQ;&{$r&aBOWbjGseq_Dw-im6#zRWglm{IjLeH5ZYWU4UMa-LurLGk7F|CQ z_l|_=72Po4usFzQ7m-kWW0=11kIvvwn0B7V+wZoJB-o*Jty^t;z$V+CBKlygt)AC` zRl_Z5^BRSKx3o=K79}kXd6mh)dVEn4br8H_PVyb~No46suJ;VF!1a_)Pqc~BAAo2KNr6I zs5Wx)X*mjFxxhZ&)fUaC&pe(#X*h+K`FerFhpU6Xqz-c2yO&^1E&K<9td#5}DNL%@ z$lNtnqAl&z!-|BO5a+6#&N(w(KD^TqE+h}eY7i!OL^Obewb;@^2(#^2@dh2?j?$^QN5i%&Ej-AmP-#}mym1Y3XkBebDZCzEUF@XAON zt;g%E(!{k;NafnTXQS?Gc{ww5t%bY8IE(vGaDr=^3p&TQIb@Zn$#llAXu}Cw_w5(5 zcVipXzrTQETXU-caYg|)FuQG7Jqd8__4fx|D4F~B8~l-h^Pvp2^Mnbdymm72Jcq$YPMcIe|OA%DO$E)HpaysH@Q`!;mbM?RiEj()vz_- zpALKHNE9x`XJHTrn1lmb`>?x45(BJ>te*=}-pwHg0UpG8bhUpiBL>KAeT33?Uv(H8 z%G-eHT#^GG@uWAowVM9jed-ZQLc@l8M-){i8HKbBT!MvuV7X6KvOPOY9VlO zPT+)-^>$Cr6v|y7zJN4Q-sTeXc!|xNQb6%>HNfO9&ULFxC%g0qYw*ivuv-<#8Ds&1sB(9DtXX*C) zm$6Ep1ad>}DW|dUNven@nK^wmD!?CC-d(VA1VY7@X6!ZE1D7PWl&v!M!#vV^eKDo2IiS};J zRqV2n14g!?lOyD}7+3sKK{Q0x<4yw2E9pBhY=7ZP|s9B8iOQ@r;4X(s!jf^D*L z+7!53cA2%Lr1Gb@^!BvEUsWb2{YnHMN%OL9W%3kx#G(_nN{#Qz@fYb*(I7s^J%u!u zj(e-U`W*FvBDvg5&Nb(w(5G=zW;=;Hbw+0!l(C3!Q`_#g)E{4|kZPE!TD*Q!8?xxW zs&Dozb%aQ@U1u`{_2jr!7oeVivsl8}wQpo3_#z$P4hX3#9c)}T&?^rVt|eIp&Rr6k zDDpm9kV4@B;9;BOrvwY*X(VlxuhIPBAFgu?2v5l{!&|ONSzpnT*X$eSs-21(2uDZ4l~vC(eKJRzPj4C5 zXKG&+23Z(R{A8QPZy*2cR)8+fX*aq&TMBGGP?PvFzIZx)6KXuAUXh_ z%!8Fx>DX)F-rukf`R@99_rLK_X++2?8&bX9G_U0^g-?*5p;9+!dQjT{?kF4CRC0e* zBUJ7+=PZD?)sKR;_WvPV#lcESGVS38Pfu4-kzhAg< z^RBL_hrGz%uSKf1wd$=n$8faS=TaG=yLX``>ih!qKyj36mJREl7kRaV{lqNJC-gJV zLgNEVrq^`CZUM%{*wm+1WPRoD(+^fspM85@v=AS^eP_SF#Hyiz8Wxc4Npc1WYFMjm zK6$?As{ZvH^}ge>tCQ!C5682mB1KZ|&iXgEMv>mpAv(b?48k#zb^ZA&J8A;5x~;OC z8%&Y#T%`=CZN%&5`?%I^%F^IxFP_DM`(^U7A^u$GZd9p?T*%_8d?sr@A4Nt>XV3c*fdxIF zBVjWFiM(VhT@L&}{tIKS{gx!d%wHh3B`Q1wAvyzU^LahrTA;gIr?W9l-B?~$jm+zQ z!t&myt=np@U&gl(KFxt{i99!?;#@P)fFmHRPK4^L?a$I`-Sev{i>|Plo2%iZE|d4X zQ{%0?$fx}QMH}d}`|PUW>HSrzRVl#UH~6M7-en2)9d&I!sCu1Y@s{YO)~#6rJ_9mk zK=t!Q{oGj|Kya3mQ8sr7fw9BX;8=z;w);{k0J+Y$3hC08POlWZUu1&mluPpQeDt(adi}zmC9f{YFn$?~emT2@- z`Sbu&&#N8Zz;&x!ne!?$LAB_9IqMmAz?r+nbMX5ZT`Mc2&QuM)g}rEQl?G5hh(COt zY30`MlfF$A5+`I9i9{qzsujAPF{vREQAzh7O@4)y4_lN6#aC4>xaDZ4v>DYI=d)BJ z{uX4~jlT;OWne!Xb_aWQ;=cAW;z>a~tpFS?5210Zvlon+*$ZS4EUqwlFK|-RFYwDt z(tIA~1SzA}9i9ECmLU|gY?ZHfGV2-ALBHzHd&uj@y$pvBq6SjP zo$RN|_6*K-nWbC#m7la4+0Ama`?Zn_y(o2z+bM;=rt}|5AEaz_K7uEn4_omUW2vQh zjmt|gT6fJ9C;Q7y(!91Gs-G!BXqB&@V(yX%)ANid2A<3KCE;Z6as4Q5Cs4?UJ7I>$Q&Z> zbTR#g`|Gv`-Q3-#$&-njFE&wlKR?E`ay4P>1puc z=d&eG>-NdMbi`)BiN4G@aG}DxVA!Psf;26WB)2B1jrRI;jz++&_X`fRN*zZ-f^5IvXfQopTa}_tGX!x3ZD095+uLk`J zl{6pEuk<5rHOw_G;q~XhbY4?BCiXPA|3)Xf%8D#a+t`4JJ@5;+^|#XL$h`s*CjJn* z1RvS5doMg4%~FRpxhyT^`bfc85<*k}w@-jXCswB&cxs6%#{K}2{JQ25LzoNX9!|q#k;G8Z;jlX~|VuEK%t&*ULz6Hn++CgM1 z!CUKhJo$Wb&~((Y^m_uJquc=Btb<7QM#CXnqQUrQ1LJM0J!Ff0WthpSfbXcFb(LKu zC=K2NbQjCB=*T-Ro5Qm#O$`Q(Zqs{61h$;DFY-BS^hbt0Ybt*y-5tg?J3thfIsLej zOrBlfvA(j;yM^AKXr_u|^GJ9PCjc8iej#={X>S5i(z+=Q{0b@)R?qs`{R%VTg-xE* z`)183n}s6n@-PZ8)R!Uj5Z#8Z?-X%3b)DnkkzK2Zp3K>+h|#pVYnRGCX(pynBdQeE zYWiHS$GT$|7_;IdxUBcW$*^|UW|~ZiVPpK{^x#3R*Nfo45f#qzyMl$)X-Chm0J)z{ zM%42>IjS(YI30iy-!4=$BgKVHqMmN1yhFTc8s_cqb>{5pFp5BF&hy{tIUVee+y9vK zcf#hHk3LC>`CTEOUB#Hrd5f1V8n0I9By%mt78Vz_%SVTvdYLP1&Kr!C(BOZe>)Yf9 z9T+OhoELh>*DbHki9LI6|4HW=yp_UhL40rmdCb&j1~vORottAruH}ECw- zI2nx(D@NGyzB?uAO37)x1_tbutg(FIbCq}W4=s$l4Y!1G*kWSuyjQ0xvhUaiW7WH- zo~syr(nP<1rwVg@SOrw53-}n(x5LZk|H(Yxm^o=Tx;2>6IoB5zN4Ej!OQKDIA{y!$ z+9goJIQsb2EVY7)ue@3){P`?%U4}`8JL;NQ{DBwWstFjeI6iiWbghgZ9=z$du=-c1 zYcn0DUV&{Fh@zBu6uKU#zc9@YNj4G*-tFsC&U!>vy+_LNN}oUhpaZcmrO+55XKJ<9Mi)EvKigZB<6E zYP<>qADt$SlfQ2}DoCR9mIv2N&@`Va3kdA=ejB9M7lTy1rgtG86=47WUA-J7Y2LQ( zY9Zq{7)IWz;Fy$T>)M>!y=d|`kyOQZJ)`Sg@L^hI`3%P*OG@u5_+?l`NIGHthbV)Bl33l z^QvPz>hR!0X?;_}zqip~rA70iom=HjGrHa0 z?%<7ja%Zm3A*_$0c_6JfHV!IdRv3!S&}z6)>&0zz9gIM$dY7q`>#WTMsg!RGR(UOW z02eFv1&xk&)^DgbUd~lI*sfOa(nSOb0U!-fTGe?6nK7cjy<3wD zMv;RoZCmR1lU(}E98c0^Y1792j2#)9INQ6Xb2uc}hJX`_t~rMg!_mJ=|6>`t3CrN- zXje&biz7u??bL3elKn7eLbV&d3LiV+=m|f(;a=Y`DM{FiwLEpuz1tviQ#7J2 zWn5COk?{37Vp8W*<#)Ds!(=X7Z@dxF<%QH}NAHafyd0DembrPM@mVOaq~dkd9h;(yBbWKi_n}_Eq}f z>tE#ayE;Pw+``|DN3@#X6?q*|{~-ldVwR_+1zq1A;6dBP)-c*-$H>e-5p;(Vkm)w# zru^8Cx3@*KKf;5a00B_9K!f+Kd^yG6{T95d!^H8MF?izpmH`1XVO5`I=DLos0&gUR z)HN^e(P0(Mrvz5G63h=u3CiKzv1p{?Ptu!al>i3zLA^8%9y!*1d1}( zTL@-4B2G&Fi4(O86XsyLB})GkTr-UyD$Fv|E>I5>gJyhB-N?rNNf5iWgo>OF?6|Jg zFzA!>(C^gkH}ojU+J+V^TufoN!Z0WZ=^Ae5FguhoRLx>npt@!XWI=kM<*~^*PlIPW zaj`iFq)tk=&Zsv6|En9yo1a-lEsT|XVS?7d?&K3$aSzQh0B}z!?SAR!KbfMZc?|rCAAuhvh3$an|?r*iHD253|NPPK>O_M>q;Q1k1 zYLuKPXpS+=e>GwE#lTIWwr&EP-!RcVN-osA1iM(Z}l)hOIhSsT-xQR6|0{` zw0Jagd!d=tv+Q11&}iEdc+5G@O_#(53ZlVZ1j)<)mGm15cD*hg>!M3qKSOGc{8e8V zGhs8YLy`Hl@L`Aj#t=I~Leofxo`FaJ5IPNi1M<@U*CZ|~U|pU#cz83D6qYHjRR)u+ z<g zQvIFuYgii7Uwx(Yo~6dfadw&ImLE50v_8LwXGmJ&xlUOfqwLha|A{n@#Dx$h0mnYH zFi;EYRyiU+DWlbNv2-m-7e5^>NI&w%N>ft4II_4Xi_r1|>l^4 zUn>+QDMX*QZ1jmm8La5G_0;NZ%0njJt7B2ioqnxLq2@J>7V9znw_dx(!R5`i{Xs_$ zW4#v{2Du@cSxD5;pTtl|mG?g2eg}nIXlA6c{9prch$vaa=Vu?UqS99G^B6XGSFDeg zpdSm8T2S9>_{~tfY8YnlI~GuW$;j7C&zXD-zap(=TtLZ6qHlq^__pRUFj8eewIu#; zq_x(NguhrC;jhx{JWB6bsDru?Bua%LKEL~lzm}F)-y@P z{=C|9{$3N4RLl=j>(Mlp*o2k3AQN(%b`Ea5sZK6ZoIN>(~ zX4Y89I<=jcoY$y6R=m2aE2iX{(|6(VMi*K1u^oFV2V3!eE0!b5+U9324c_u>3&ZqY zMhauFT%B_kt(U;WmgJA&P9i8rD$BlzDY7?KbEz%UB6ESk$h8sAvMt_$ljtKS%~^p> zpPH0G+!cl~74OF*e9uP_AFb;~&UdEDK*pi(AG*-z+n3&s7AnSghz#QEWM#o8zXuq? zSeliPgdGaRDznt*PO&AHH-r&34|@+#S#C|$4%VC29Z4J)=vOZ+4NM?($`M;jag!O) z;6=R51V$9q_=o#KGwcN%6sX-g`kzb^0F*%lc8MS(J!hE+j+y`dVuJv1mSEj4tPiqf6JP?8dg^w1(TbPPxh-7Vd4_V|CE z=RNOxe&^dcU&OuHd#|n1`|T@eqP8XE-#1y5N?UK<4kwFL#`fy<)@z@5ag zC#%2@PLG$bJuIDFd>yTAJWv#^!Pai(9@bWmEqoup_3&_Um*C@baWn^ecse=qS~@#< z4fQbqpAfRseeLn@`zWZucYHGUbU@BC&j?!%Dkw+gmIz-wd71KwurpMFD3u7qZ_B!} z*oww~(y#)gIdMOKdc_>Ck^n17Sx(wGZprc$R3=N|jADL4szdamKgn@e%rOAdu|1{W z{R2#rRmP)t<}*z4yFqzpb#+2f?IYr*cjUF&x_~Bun`(1(DU%^(NQ}V{2 zw;gk?z5TP?uV|1<)nxDZ#E5XxImIVeV7$iIxwiROs3IdGtCv=_PG{1;o#n{~_-o1y zBaW0u5nscC=j5sN&+Lt2``{m9{`ih|8%L<=ZIgB%Mv9U2S=p!WKOFEZ%ru;s+n)+w zA21Jnsl>6wmOrjf!u#?~9&68Wmp=6tIab%*5Us5x%SW{n@r?Ye=7j^<;I;$y>zUp= zinOb`6=Q|uvY4iKwAOv-VGwZJB!h=+?M7ldt+$Kd$N3TJlk$1n&qJBQq=)!+!6v*% zG6hUYs4rmJtkb+YjW5Fx@XTMFE0JW&JmqIId+%h+vYxJpCw=IFku@wdS`#C%*4SU9 zuP!Zar*iz!B1!w<84)5o5Mm=m$hhL)(tCQdjbotMI=pF$#>iebf^zh>-9zpUg)T0g z!aGgB9|gseU0MFQuFuSF7S?OMiJ6`>_l)CclGv|bpKx;G+ll9vd`|vaVr4pjTKqy; zOHELvYkZ_eQ#X@mK%9uIn%8ryI&ay=Br?({xxV~Kv$^iMzu9ka&LGv9_)pZKKlidb zOVj>)wUg7fIkmyf2c`}!sy7559zWuYMF*pzKPQA@Jtss5zaT_^4gjYOHNMy@ZrJjQQ51CY;kuRd;8Lf3@1Ljf=N?<9u5u; zc6P(w>_mU%cmi6>VBC*CEQm9OB}a`EfFhf!B2t z!&lp76{fyxDW^M=6-M>EjEwJPu6h`PJ@RrYtQH#8JXgA7BynEKCefAgxVnG%3G&}Q zUkGMD8ULy!_jVkZ$oag##s)V70|Tc*c%@<69&60%(vrD{MO)DAla0Olg`z!i_jTRx z#s)2KoSmKbdhGOC{7$X=Q*V#@`D|p@>KB6imm}Esp)2_L)#0$hhC;L8+f!f&=bi%Q=k0In(rU6G&drAyPP*R=Py151bOmqc+Jf888VqaRjv6_)c-~wb zvZ|))mg+{h(fcf-R_9dN3{p#|Afh887|W8Przg#VrA0*a#~Q4trTv5^lz*4C1TmM9 zk)In$@`smb)_573Vdwrs}s0}b#2?-6<0&vT%%{c zhT~kL@MVDw7x#WWTZjAI&E?6?_%|c(sUPN(6=st`Q5|6f8d(x4L9Oap5_DoN&*;Zw zyLS1Mc*0sAPr&P(jHgmy00;tEan&2KXj3uW9oWf>BGGGwOmZ(4@F^ zAL%9MN;@XQ_&pQ4qVHO3YbzroV?B6Iz#lC_6Ac45JUl$tB3a!07rMb7)OBT*HHKDP z;PKOhB5V5HCGZ6-}#!8eCteoS*}jUty1do@#>Sxz-nG0dBgW}pNDKN&}TLIpM(FdT%I7sF+*YQaeo?LvDy{G z_dxF1dguGY9tH6viHq3M#1;D-f$cx5Gdso?m5OgE#`KfbAMBONAU%7 z0`^U$lIgh)=a$aF?+j3;2BHjsnX!0Uo7_C1+=3L~2aDh39P*@b}P7;d|| ze!Uz>K}Fl0gqYat-Gq(xf@2 zn_FD%@EOt(^frt3u%_>r7(hWcuyqSG)#Xp5|ubX zxDQh$oB-Z>lsv!Gigji~QnIE;F--=cp zlI~F~)Hhiv)}#< zviGg3-J5HgBnQt!UFFW^i37jBI5i98Xb=y&@sSV$G`ZpWawDfdY!&c&6XgBF!xaYA zY;=Y#ezhFWTkr4uFZU<<`&BJWxcasb4T5xgGqpRxcgGCEIoT3NMiF9$J;s4_jFR~t zt+1&|OFRGRD(h6}CE#fK{nckroS)rIesMY_F$+A^+w;uO|2hoHcMjYdV>S3@N0Ip( zp<{9lgDI62e-1j-kiFFfn>BgG;3v3#6A2+6x7pHv+rOh*R)@z_QqVBL*h>gCz-_s^ z+F9MwoTZlVc;YJrsBq#Oh4-9i+7(y{P%N3iD;T*kpyQ36JAF>ec6QM8&!3+>S60yU z@Tftjb%HZKXsoY4zqvg2-bWz)z~!3&K<({s1Fz2lFP6f3)spyjVU{fcmsQgh8pGKq zt-UA#yOlk#B8ZqP(~>3NG*4u6UsaeU%Fu{7##20#O00jm5!Kn*IU-`vW7XA9n-6ih zxp18+bYLc}1J4;|tM0d5MVxs4`t@s*n%;MnsG2OJFs{%UbdQ07OZiMZhDNjqd$Qbs zgp%^fJx3o2M=%h5iEGCxWv+Jx0kPaJuafo;w`^qlTjeJrT`kfEn+xr?5ZTiHyvEQ! zi|rvv>>8*1uU-BdOV8Uq}**xQj@PQ6l?ZHS}BG}^P_kQ$f5FiE#nC8mVBAPzr|-uQxQnEu4& zm6a0l`2_Zs(aFKWeP;xTNBCC$rcqC)0j-`&DQtwvxL9JWK25K$Fk#Z?s^MG~o~y|TzMCF* z&ULmuD;AaR+J@wmmvgJOOm%$&tnf#np)ad~vnP-jyl2T;<27l79Wt;)e|EzF#z&kyq7HmhEZ=VkdTU95XsG^iJjCVR%%1I|vYU7U!TbbK_-H@o>0J_VTBD zlJ+IFpQ|@a)-#?I%taCIvQa82T?IYKY7I^N%Dd_tF&6}4N#*0)-r;0eq4{o>Cu*)G z?-o2&(sIImZKW%v^)BX_>5cJ$WFERDWoKCMc0o?cU4Ois$E$TvJm2Q2Lant(@zY%x zUhqDrbLNeD?}ad+p?rLSk@P;cLB*haH-VBy=jbmeaX6n~|FHP%Ry z2WGB3VAg6Y@i`(JVtmzT-NR&k6>EW$JkWg!nP$^Fo-nz0wa^VuY{Kjrpviz?VfoM5&b{!I7V(5=+0PD*07NlFlX{@g8t`+4j+3ja0L+134-ZbJS{ zb>j&MT>sCarrW?)NL_2_od>WjCe{c^s_c(-aM>vd4`pzCvBuf@JnjGmU=#3LsGUEP zWfC|1If?x67ss3-_ghI6q`(atlRcv5_7uI3rt;D5f8BT>&4>jsqJRH;UA$qsk?o;k zauw0UejNRnkOV1N9llQr$4VC%G>wn3{t48v`OnHRgPl|D^A%b#CYE@&R-IfbHr`D-HT10W|QmKmq@-owbZ zA!s8u?*N-D{U1VLk{U(5JD8h96}kBy-zu9NO4PA2zoeE zXGe>txIqJ2<*HRe4io&Zi&m>i^g=o=D*~#w2HN8R=kq{x@RR(Q7k0XWtaeDh=N7x^ zW$L#s^;X9kZ8qO4PwJymc`HZ#Z@l<{_L~Mwqs2;?3;)|>$D6;6aTI_Q+T&qe;MMjD zF^(AQrTUyoXBWkITvz!&tmyr$5k6!w1_(Nj<AC#0dEZqN+E)uL)d89*8FV1ksF@9QZzgC`!_sElummcS88;1Y_qpiVmYsNNw!EO4yV$1Z9?8by0BOY&uGxv-<5N#i98~dS5+oLqZ zoEV{oqU{3p1a})RCXW9Gm9c{B4zxPE93~=gBsG+Ly*G-qPPpv}n^R%`)`@|QU zIrm!-gcr!RgyhJ6qIPHy>fO+g-&mUbA&y#pcr_8$TjFjNbd z$20AYwanRv_`5sclsdHb45}D=>*eri<$!%40OMmVvpuQrozRwEi)uRzKJ76jl)P(C zcNd||@iC*^=pRv(KoqP_gsCRBrC!vM0sGee{8@DA15vspNYQ9DETw3{#Hi@tN!_zv zN1@0sh5J`=E1xk0+|9Y$?!SoS(oba9dQb#sb|3{e7Xn%06Bv&T?A<4;odIHUSaxJE zsP*#DxpO-|viD8netf`!RSLv5co|=xn2OE8^!G{;E}w=7A9RZ}s8Q>ryf)&7i*Mo8 zMeupu=h|4bSE}b-PeM~0CBb!Xr;NF$`c5^MOgkcJC$e8Yyo&X>p)_19@^?Ajq(J=M z%DL=j*}wUM_lwn6;UvfS%6NQVxu{*NNZ#e{y+*ma&RM!lYnr92_8Phc9hlhYItD{D zD+Ho2TD|!y=%4WL?lv^G1$Rf0s%u2s`#gAHPI4ZtbJrHif3sJ@q*!a~xz*E;Cl-Lq z;{tcO`UKNp5K&e${`p;q>T>~mY=i<2G)MR0&PU&FTRh5sj1ROFFWBriS^*8)a?`?d zQh^S|kua-N% zYj%dTqLeTNQy!RHSC{x)6)llU$bU0F{epqvrVH|WquY}i9m^^?AlRtPb&P1fX`&QJ z&_JAPxWFSC{>CNh`CP2L4b8hSiD9xV?18<40FAfapQI9V89CD0c8`5rp^u50|x79qpIh7B~k?P zWWH;LWlz@#f*=MZRc{#R5j&|AmaS zSO|2aca$@vbQm6@@}Efch|2n$Z$^qmVNubycb(D>av|v*Z4h^n8lGs-)f5`(#t$-b z+A@g@+LlK6XmRB`c8jyyZ(&^jG#2sK1g<*&PFpt(Yq_2 zteBWa&(wc@h?pvjRydd^FrUaA*nt44dBNF^Z}s@N6+`93(9;Sf7!#_|dzJV{^n z2Hbju6Nf<~oUhblQrO32p^>02KHIcXLr5i{RkNpGO2A2;sHTxNO3XcoGo}wd!`TjS zb!xqPxxFCm9`+M71yRDk4?5oObxbHhqa^RHieU@16@XvGtS@q}TJ*=K*|VhOe(F0b zIS{9QN4=);NeO13{YOP_bTk33dhz__6UR$AzMH;$__ok$2?+tWmZ?SdP3)Yzr+qpr zSjIEa)_tr&CzUMyhn3-%Oaa-z!ar|^bwsG^Ntax^DK7sQeh0-$*6H?6X=V$zy`||P z=cnkxM+%%a8zOKtPr^r)_W+-=%rVaYTSNlt7=LG(^yvkxb31ujQDN z#+tZgjNy-$4R^*^vPw}bSp;-~QHM2exCp4&X!K)mQna8ia(=I{xn|~inNjv6v5#^{ zkg2RD!0cmX1{b3mG*_8kv5;bz#8zu#rLe=B%-uta#EmHbH4iJf(G}?nnfahguDVhE zT~WcTgo3N_Iu|%hgKrvh`q$vf0%?S;T9+UG8XncB{uAaJM7g$}?^?!%C!70jacnmiH9M|^g}Ek0Ffugy#d2p_y`5_{eCc? z6=Q)4u`NkNl(L`l4qigAC;FWGJzT(2&z6o zhE13EmUO0{r`oMOACkQ>_@Y6l?Fcq3)m$HbZC#XI^zAYRy(NPT zX!;R**k}8>^uP_hu&CzqWX6w$G-a({c1GZfp2W@EZi28kRw=t7HMIs;n)_)LRz7;A zF+L;TCHls$^reTCLH&PKE@+Tar76)Gh*>X@x34L|bFiKk;n2e36t!9;#E) zixz4%G*mBcdqcZFafq|H7lDJj@AXZB)+Z;L&#j)vRq(`4rshshF01SIv6GVo1N9$5 zZx>l;W_|@YIO*Efs@2#v7tA~)p4pW!-@C8V(QLR}r2dJy$h8v4maATBDJFY{PWZt@ zD-5%Ys&K>JmJqwZ3!x=+h2=><@+wr_+$a45Iu07!l-6Nejul!NPjdpT$n^M3SN4gt z1vT~!)^FOF3dh<8z-TFZ`DRbdU)P4bk34-oGvgtXOekeTo}*rB7(>ya>A;rS%c^cY z!mZ)l@gR7nk5zQy(Z^IysWh(8hg@1s_T$56Ab8#Y+Ym{hHiBph{h;95_RMa z*cb2$R}=IMU71-fwtQ)ZbeJB0zaZs6cOU}pMGKq|$3v}?kzMScUM z$N>RFL4izOvmzj=3XhUDRI{()wmx90wMpkbIo1K2ZQLGV>OwUJb1yM={ga1C@6GPt zX6NmUoniB3xDl^mjj3fr{A zod-;7XSrAy8kft?v>un06oh?$CDI=SDPj~p543tWx5WSrTTM8{o(Yc|!(n6A9inQ} z&HX52zi#*V4Ksl&x6w3f|ENETbMq0;Y{<66Q%8FJWfG?oc!!zpFHZK2YO2}X^eVz! zW6qrm(MM}o$bDGr0#&<;G@X#p?_Kh&{(4JxT9+hbt1#dBVBDO^&YBQ&$Yut5Nw z^z-*8k=T1}d%QmQp3DVKEz0w0e3f6iJ(|TL%s8j&HKN=wCd8S3XxS&NGi{42Meg>% z+L33bTlH1H_2TcWr#cOVN?FN#tpCp3J|20dmgKfHn-}aNyxdE1zRY@T-Pf9S2^o9^=Fqks z47IVx46?)Gs| zVQlqDj0T@-3zPUgWFKjT+<(oyUf1596a3(B_J*wA#KwOhcl|06hMS2Cei0Ak_(um)+{%&@ z#g=@r!v$v!eDl29B5t`j-}U9xOt(M3Pcn&y0Jt*|5s}Y0qYK>lsAZOrZDqpDCoxW< zBy()>8W%22mkz-v6q`h(I>Jq-BH|wuk;0ZT;Zq>*qd-gu`&P{?;YD}JR*5CeCsgv`(d(m zs9fB^S_8lLZygITb(V$jyTearzQ6G%l*gHOl(Q=AM7R&D({SOua{1E*OgYJpTr+`7 z_ZK6rakva1T&EdBDJbIVWd>eOxjSr3n0@2e;KX|C*QQRXzF-Ha=TYV^H6l6HMnM6C z-VMgs(5of9^OS8LheVEHvDg&=+pz_29$_;09YF=sZl=;gbsVwY0C5cMZM!}O7wXHb z(YGSpmAJ?J00rcXp=U{U#?!Vq#f!mU7*!hYd4hx6faZtw-;?FW1%-clCBnE(j-k^7 zeOSk050cJNWV(_eu|#6i523!xoe`04!yz9&yaTjY1Wf!xB=ePxLLv+y$XR`FITSaQ z|NL7mN_S72`Mwf<)!-VlAAap}srMdV77q;4Ee2>}sTep>@8xg(mTt%3geF5{aNl`~Rer)NUU*-KK9njj zL*onewunUlY)xXd3~fS0=tXu6MajQ5tB1w?E)ty!;Sv3_caaT>rAI4jJ=}2=h;eTc zIBkm~O5#3c!DKZh16BMk#$xtIxxdUSa6YB%S9;->|N2PV6^@RYm$sH4kgrA!o)t?z zTeJc!z$@6cV$@P`?Svxc!|GfFoq0lxsf@lf~ZnJC^_M&Xans-1L}wD(C| z#5A7#G>jVeOgx`7ev4aeWdt7B%nwe(oCz}Og%|i%Dc>h$B|rdP#v{npqV?s_W8Jw#Eu`LkEOXOPFJPE4Io1TgBEZqTiKT^#NU zHB&)M8s4C13sLpH99~yOUe(?MrUmxt8KJ;j3)}}kHZ$jNbzsnnxm3BkCgpBzY^0^6 ztUaC#@tg(-A1-j;q@fqib>*mGK1%zTphpM~e{iTgS^Blr3ZqLkEl<-@N7%;d>LPzA zvdRi?Bqk<)lp*Ro-zkXu9s>9xP@};Ush@<%F#jUH-xY4V#l|A+Nhw{@lUQtW;1850 ztu(+d4DsltD9!sGGxL}>Q;MP@KAvR9cXrAn!j2NEaYcSXyx*!CTfLQ4G_&95)3YoKf;8c6~@S!^9;62&* z{D=j7BntbG zD+3a#a-3N1iXtH)ab-XlQ-%RM-lb`5Olt}-*v=hasnk&Ok5CR>TxgMY= zCx?fw8T`5{KHS&mHhhqslq48%;nF;Z_V+$E9v(AgI4sv@zfi5&50+z?E32_k)b*i2vRq6Dm4m zOQi=k3%RS`OAAfM%X(o&RBs1YaMQsDYpAPtYXFF$&7~c{D&sM% z`Ez_c+vM#G!0h3Q_%eF}yu9B5dY4BQaR#LuAZHt2E-tPzfSYub#WwBQIRgAn{OI91 zKu9pdHHHCok=`)G8?Ck7VT2H5w z%~2~NQPJWOonm?^pON-34e<{Gf`YY83WxF0u`d?cth9g~l=eSkxk3QM4SfHEt*xzZ zhP8>wEPdx&@USPcUP{%|vk4sosn3os7{;&1|#5k3>5x}F- zonhM!W~>2by|xB$j@SjHwy@ez- z$lA9V9FYjFd-r)=ONYn~Q=>JwXc_G=M^wgKJmmUpPv`pgCg1ZUT;G|;vUK67fEhq^ z`WVzf0WQ_(Y_{k7b1luy-O*HXHf&Dl=;-hGfLLfJCvnSYcF_g{&b4Oy^Zh^AL+9E8Ub<6b_!ojd z-K8DNB}o=ed5<#td7BX_wlC@5ka*6s9j>}oZpVN5(O7vZ?0Roe>5&G#pn-0|GWab+ zS8V`RSj?}cXTTyZUl|@Xf3DWr_0z!3cxJdP%Dy0SF^*bphmJ0L=Mn} zmE$c5{4D%S|FkGdL1_^l zLNS#5A4aH%h)}Ek8HSu==A3XilT6qlD$Ch@=qpvUmQ(s52d<=BM@~~84SSo;(Zrm2 zl6(%4ip?+2Aq2!+tW=t7-(+6qNZeV&jKk2|M!Rgl^A#8z9x+W=-TP&1nN>9$!o~DT zV&+rBv8psE@sE33C6M-L4T&ibmG`UE*4p0KhuV13-yUIQD z+{h}om<|%lWLRJ>dH)yiz)FoI!;<$$3N`VkAV%99a#SEN?VrEN>?d1W2nZ^?+Z7U)H2f9?u%9#~E5}4@ ziMJm%#gq5ozK}&?3GlJY9Tgs+bz8&{(fDrv)YS2Ba4N=V>wTe}(HMZ11I3Ges4o=p zpNlGA=u9FAJ*I(>PS0U*fj0mLnxVggVXCiwnsDyx5ZUPd6SCn>0o}41 z?09J_gj5H#V#eov4ty$E*Hyy+;ovdDQ0}MY_pE)C!-BF$vy~eQ+UfykiLaYYq+Hd? zr<}j~j*4`=({!m;gM@MY^L0hM2>GUyf=}h^cz)kuvrP4KH9L!~PwT9UxGnqhRP^ZM zSwNJWUM26E9&HN|&fX0@l$znnc>%WEyFv&r{b`Rk2KXFe`KUmJhEAAetoLB*>6U25 z+%a|Sg|Fr{_V{2%$NR#A9EABJ0 z6vAj8)M)u%OB;F>V$ z7ej=&SO|GzAYDLkt7E=6u2U(F7oqZ`i;tRvg_yZ&QP+V=P8)w7cIkycwezR zUEh2!eix(tt{m%hFfGIV-ol#bQ#a4F`gD3X!8V#Iv`sH{YkgRCq+IIBuev`Y+z5OE{<7wk>+R*00aC^B5Oxuy@D1L#HiS5@!M@ScQf#C-w*$4#r}_+fsUkvIL)<-^25aln zIRLH67;58Gxo#~D?la)Zp7)XeTf_8N$FzjMRLI_vyyGCs3|=Gw=5UJzR38|FQaJg; zJ)z2ud!jSG45OKunZPm3xGiXITSLRz!qT!t>aTwA_5i#o(pL9O+f3Ov+{53SE@+&+ zH+xr3YZPf}HP^T8bbfhyb@&t1!v>`pj%*+DxOFQENlVZ5^c4FZuWt43HWI)3X)Ec5QJHj`OKu?ape;=O}ZShofPW5>cw3)6&x7 z<44+p@6YyTlLlXNtHNR108zFuEKISwh$;DvMz|cAiTM7OqLO;dOI>3By|?)wUvL(rrcq zCC>%pV&d+9K2ta?k<9EmhC2nH(TYt>WsD)@^A%zQ27yik{@X4Ncnyp14!Q;W<|^q| z--_y-boxTa*S48ouY>S-g4cS^kM64_cPjb#nlgVF@lQXjX#a4FGOq~-XGt-#bKdF2&mQGSzZ}o>xEx?5bD2;%9 zuX>;z;jV(?+7Vmq^*z!|Y`laT^tN@0v4M8sF+xu@r~eHcf)nG$degFS1xzM$kVi2W z5;@b0TbqD@03ZX^^Gn6eI=Z!ih&Ta#70)+~>%k@stqTPnhax>f)Bz06Z8v0XBBS5k z7~ywuRI#PUU>wn3mDVxzzQCtSWemOFM__Si0sJC0CCI;Uy2tGcV z{YA6kmU4sB=2pD$@lt9u6(~O;Lp33yykY&x?Mt^*UH3hH)fLf}mX;`M&!VCtmA9<` zIhgFm*>lI?)4G)uvj(pyO9#O@E$6oI`n%4!eIzacz`0~nW={kKlcM1OgEQsYpkk4lK_0IGJTmVX&d$d}E2j_t z(K_-!fVZGP0C;nH)6IFd!}DLj8}J%3QhbL?dA0OJH|^Xpr85_Eq0X}p*!CE&twc@A zbMegstTC+>vHOZG^FW0^Fk{a|FuR#dsrvVaf5ndQxLtQWlI${MG0PVFO84=4tj($P zeI)A~(w!l*l;dYoKpm#G!JvvzxJ#lxE4A@22m{B1BJYS~uLY@^AWzM}Oj4J*>y5Xo zq#7cYS_-LjaT)-qh326g0JRwNpz`QBWH{r9&bgJSPCL#~YjE5$LHt9J+P< z43V-;=0^4nvZ$0Bji-F==-#@-`qzj2)PuV7+(KzSIh@k&IMVVFH7QJ1t=x*X5S zSstj=5HVRtSG49s)xxsX2BwFfHhlf8@{_uDiX*%31BZgP<%bx^VE*v~dg6YsV+VNw z`d$Z-H5;EyDw)O|?X1;*kF&7Ms6()l&YMTMPiN*z~@|$El;nuifiDe&)DZ)WqxgH*F}A zJHsKmgE_F_4|JI=bmb%zFaP7B5a_UHEQME2q#E2L_VgMPR~vOo`Xwu_=1FatIri~1?CpT&p?@NmQ3?@gvTS^ z?O>DH27_8^%whl}#!=8-fwXFikKdcF&*VALmOTId>Bs)Z^uEa)!M3MgjDW_uDg^i7 zQIa*krs%%O*^5nv;}!pD&B7u9(a;^_K~@j}B>3fc%Dk64?08|Y0hYnn*09m;Y(~Y< zg0t`FQl0rxx(~dz2k)wkrH-;WLSI4w2B8^s-~9$~d_tlqGS*b^KiH{_9+Y51L(8hy z=?a%N9k{b%u~2S|ZTyU%vqoke*MK{&!(`q{JshDGrwkX%%j$Y-DUnw8vF3|JuTHB4 zm_r5MQzw#YdB0}d`k^~~j5?{PBZ$Y^^djY6RSUm?+Ui#MyvyTKxq2gmuGfeh&9fKh z%WtqdiUjiH`^GGuuCz5k-pvR(gE5b(ioyZ@YRXSIbw~18ZVwAE-!$G7OuG-puJQnK zcJpxAgag`WfDNV}AiZ0;2xNdgl)a5?5M~2~-DV6*-QiWcjai1Kd>AL=i@x!wCQJPZ z5GS6Qh)Ya)SR>856GhskdTOu(n57Nyh7DMqBjC$!duM_CW1TbVF|PPf7Lwa82s{ zgT!I1ULOnaei~O14#jqWvV_?N@vnsk)dtL?oPWL+G#lJKO0wY+LcXV(Gy7fAnZzFO zJn&v<2}A0X&?x|omg_rM@;h<5KDFF?HlFy2$&>c4aDd@eE#ITjTj0w;Gh>?eoZZI9 zx!C$O3?(MVl;*1_ztXtBCo-!iGFw{WjvagUyD~u!iLv3Qm4~v5fEcx;WasP$MaTuW z{do+YTE1FdC&O`2%b+`Ru;YGe?mEEz{k?8&hIxFb*QK)Rh9PyYkph~hXz>$f32Q11 z=i`-hq-L`)r#j)jaqQpf!d5lTc-G$)^V`w#spT!oKoO$m)!b$BvdLmepw4swAne zgZA^Bq(mcbWP}xcx9|F!Jfu${e11eG3+>HN!rJj~ZZt zB_u?}OKs>BW&_PFHqoL(>F$3BtWS5NblYG`rLl4IE6RWGMY|2>bCby=SIf8g<1chI zQX7#s`BcW?Nc)((`Xtbazy_Fu&0gKmpwN}}KRypt6bqGRh~`R7F8^D!ZyoEq%At1~ zzy_x#ta`WDc0*T!j6j-JRZbDEsZhJq>+QINI%$;Z>2pYtD11}(Z=Bmg{}%!@COG+; z>ex~B4NDg4Fe|>QVy^lA?V+M~3aMSEH{D>AP}v23&E-jH(I^L;ke-Vua!B^ygcg}}R*PwRvh8#>J+ zfOI_K85^@3@vlw!#2IN50q}NoLLEO!bvtd{o{jz{b0dpilF3+}CynC#`7;0^?#K@3 zwblzZB3C{g2$JeEo%C}46H=Y3klIR*-0TuON^bGIvG(21wzFuIM$~_dmw$B53=0o3 zHXXcYZT4a_L1Tn!TjP_N8>($wlP?wRQ`*s>J$0yeYDgb)o-uCrsRRmb->~(rW{T9a zR1n5ZUf)`H4}k6&ps#Omco?(v=U|mbCwGi*owUI}U#~0B3&Z}lXlcDuK_-gg9sw?@ zb`fM|4v8QCB8SN|_YM^eXeClHFgG@>Kxn>;yWTm-9;i@kI@|D7ar-I-HArO=bXgxr|0p7Wilz@`Om+Z zPW4T$@9Fl0!{m3nC8bbXN5@@Q0<*M~6zm1}nj#^8uOBNm(D3?!b9cVQ|IM4+z_yqA z`sD8(0{&#t=2L{@Fi_+r(a6usd*oiYOWNqMl_(^bl$5l^Hy!GhriAymE*qf*iO=K} zmk|{d6a-RBK>>lc*8tjq4%PO;_S_yHW}Qx+2w(cg4br=oGXH0lRL2)yxCuEQME`)(5j#5gyZ(U>k z;Q)M9QzHblSfBg|fe`ANlYgaiPz8Z_a{6OqV(@TrYYw{PfPovJ>qA*lB|E@>pLc1R zn$G_)@7TT0Mg>p?rNbbBnsWy9WjY^f?V7+aBV%rXWBI_ubtc5=nGM zMfPLZl>vRAM5Lr2wa|h2zlU^<0DA#IaB(?kfauvZ)zqSyjWI5_0FFTTD1f%alVyp! zV=!{)6&4j)G02U+u`xirAh^PX1c1htAC9c7tk8u$!oO~O%*FoxO5WGo%g9B44j`(=Iwd6~e@6C@U>~6T3{pOX z^$&oV!1S%Ht$lrc&9?z2?{hNdFuK>J6(sEHxHKZTI+I8xLlN(SdRIpRE5pa{h7bZ{ z{{$Jant51Y0Uq8BoUpgzk)40+mXw^8l?9-kp_t$o>g7{YQ$Qa)63b$Qu>m7oUS5iu zOwj`&;bSiL@dy9|X1OLy06CD z>|l?}Na77x8LfI{WhLMceg=MZc}dpyL{LhK7J2_MtQo*GLkSqNGc(KASJ&4? zoabpRQvkt#;O6Aw>Ytsxo~d~h1_6Pn5g2XM_Tk!Kvkf_-=bbwS~nvjqH*vdmuI*=Py4F0DvH}}qP zBCt#|pb?y}S5dC6uKK{3E^z*q_a8r^0}eBl$4CVnOD`?K!~|?rE;bzKtF2{52LPOj z>1ta59NJl08Z83RXlQ5vyTYj;>9y<9sN@Rn2bHL%@ty*xS8I_ZUnyWeWJM<@9>BEI z4o9*+K=PR_6)F(AS02O5UlaQC_Ow%USOB{d|M=acDK#;1*}|-^T~f{dpDw`?D4>v! z&-Grq0ktz=7@!3!)MMM9KPdLkTlP|2vTT!=^39hPl5qJRQ>HC_XWJYZlvaT?W+x@j}V4XCSu3>4|WGyCXf#Y&}|A%tk z@FP~iB?;U8!KVNuLUzvU%y5}mUtB@@O;WH#Z~X%#-eD8ac^MfQUfJHx_MVnBM$ySE zLQk5AK!%7bP*+lg3x2~Gk0dz<+?&)W*Ggn)VL+rLqXOtSA<2J{Jn*S?7Vn?B&KdQMorW&~!Kg?Y3e#|s43%u(032we^5l~wJMU>1p9aT8|JaI&h zm!C_^Glhs9=ztZfSI*%9rZ?<~g2?YpQY+#HN=Ww@ji;k6;u@;(UNLY}AQyQHCOugA zP@`nDlouXo##c^r)cuvfVq?wxl#{OgHS6O<^HG5BfTdp{X(*YPnE3bz?gbp%AMYp-d2M*5r;SvyPGnxXx5{-d!jA00P62=`GbpA@YkN*n ziDYVp6&q}Ck~_^3e|pv(rpk`}^nT{coD1qqeX`@FqyTV;S_}A)&~mJ8ubB^buYML3 zzK)^PDU({@jdCUkU7n94g%rLT<0ksWvp}X)jP}TBC?xlCc=gI{0%V_gzk%S|kb{K< zz4m)@9j{evfvR-!F2$G%&D6mYv`NA==GyAUgz#jCu@f=+;jf7mHwT}tH)UNn$IZE( zCeu#4aHs^xQ{jSbMG-&TZgYR%_h3QTwWd1lBQ)Q(!k(txz(VDUBuib_*wczu0bRc; zGQh0c&idoNvF+uchgELx^AvX7rhy0H{L{yQ8C^``ZmDb#Pzv#YXNwcNdOHFs;dvH; zWj((8`}+DdW6?BFBPpp-%FK6)%hp4{gBbQXD42_vznPbu#yX^At^2iVx`TxLsMmE3ut^(CId{#iNcta-oOcl}zw*UdS1^dVgX zYwmk1EiAtWjJrAKlK`~XE6g&qOmitFI3K- zV5k+--CRiuu3t(HsfMQwExV?9OVT%tOso@FGB|@EE}AfImpJXtG22gu-C> z*w((}HTz+TyMt{QL1H&D^UHY?k;sg8U1pyLO#=3QWNt4v_j;O$6n|nE-{|db0$5)R z%&>6Ow)7Z^%UzA3dwtKI7ihk)hTHwqlK8R>&wAPUmoemL1D3BdzQFem{bP2*`f&^F zMU#!Orp8;f!z~sM*hCdOd&4G;f2a3arWKRxyUhzdmAv|}AZTNK5i^Eg9Y@`7s-TH5 zQLn?snDp|U6qf}Z+wbaptj@0fB`j$p)uIFnoQ)OB0^dPIW>(|mrT1rq1BeubCf0gp zQcMz*)~lTt@Ru87L5W}64{rVFDKYS!1t(96!6TVW&&0$eAV7T9rfKBu!*@~na;zhn z)SBRp56Bb8Q4FQL$R*~KpW|GAqer#7WNp7Owp8lovu7IN{O!ax!gM4vM?3dY2SA{* zf&b#=?fp2d4yUXgR?kJR5(MZIf`gX?!ErUfS>?pVVFMU1A;e}u{r(+I09*5GX}JTy zE_(*p*(LC#0c?!Cla~%+S;i~l8)KyaO0R%J_tFdmn4#FF>hk=cFyIe0yjtiNG*;ko z{)Gnq`xBNC1;xs+1og^_jDo_CRtmeZ=*y_3Ab;SBR^-gg=m0OKU$jTJgsh6x$-0KAr*#m! zhJ)&W4sQ4EQG=EvY}xINu9l&`0wpzP0{TM;-N5e-*>kXe_HC~XK;h;3MP2P}dJ%{T zYq7`Fk>@>mO3K5fWFu}Y40E7m*|H)0&F)N*p#ns*Z*WmRa#V@|zqURdMCZfAE4VZN zW|MI|(1*2mCZ9kez%5Ts>x-qT-kxtpUu^(XVp7N+?#xf-FIYT@lZ(7ppE^5zGjg{;@=b)C=99@3D}NsWmpiBpn@->M--tuiS!5kL%1X!Oe}S{G7)-H@?%y8CIio zbqe4wAwtWQTaZ<*Y1$0>WL)5#KU<-Po!H^sQmGYz0<73uOXIKlyVLa&+Z&h=fkygm zxY{$N359%4V|>@f0BE!j9sH6BW|{Ga9YQ&12$y?j2yhy^QRQ0$3|jTIr` zlc-NrF2hIP^deE<75ZZ=jrf&j7W0twAY!DiB{4xixBdyx;zpyKI-m@T>UA~!j+M@) zdsvX(EhYqllCHfecri6M*@_!Mt zx@uI;gSMEB%G{rPhkg#q66T;z39qkn6(8TqQYVfo5W^*BbFz6dX?QZCl3_&7>cp3m7C0Q;aXuXRJH1G;>8*c+_H}d!HXSF||`{=D5rzyBfRVgQrkz6N6NswtimB-n(aJ}>)m6Ag-7(kpgtJ$<_Bw~#RM*>5PpQ4Ym!d%)$LS?xy7Ib zhh)~-7Xxc_FA;zj6aeN4;(lNc$@U}EZDG?xLXAZ!% zr=%P$cqZ^%z%}0T6UTht1{hoSHyL?(G-TxNp`jn8a1h>MrQJ)SY2DM%VpSYjmn727 zshoKCx4QoxY16d4vzkn+9Ty}Wqnu=?AL|1_+e?|UnuM4bKkWxL4&nT*h|>#KVmpLO z7qxByb)wwaJ5cJ@UHNrp2a)IIe>f1|a@qDR4?(EII)A%g^KLFy_l_i??#NAAQ>s(i zy&3W*J#4Vja;z|9VKX&M)(2vqk_G6-$PXVnyx|~EiHFZv)X7a^Eq=0hIQuS9wKEbQ zr94_E5OH)NELg!Jh1M*dg+ZAKh7e*!>0n7|QI_FDTb>Z2Er&Xw2lUN=9An0;m$fH4LIF)&TM$YY4DW;)sF({1u}sNTHBv1BH5yjjal^AP>OD zj6Q|zEiVV04#UWE?vpwo$6`-PBn;B6opL|{Bw@$iAT1E(zJW&m!4p_f8b$C0vVnU5 zEe7ihAah|31nB6$L;`y!KKmIuhJZVhW{TOlT*C)$#+r91VIy*WRsNaJT;E3J> zr2ofI{v#g6!}zo`;^)spsp!G#!DeY^$0||(GVOkR^sfPY6QHwMx$j#a1w2w9!&9c; z67`RKO!|`^pc8|fO}Ba)pswQKc}~IyrW;)fP{hsd$JBd|{uMdAFg2gY3GMwlD6$2D z@(*fNb&*E%NcO?km8bseCLV?S#p>d6uhtG-mGz8WY@_EGHFEh2^-=(KQ54 zfVe>Xou8Y#c*~RB)YQa>vjvFxY)7)h!8$#9_|Q%pb`!basH6DI4oI1McwFAzT#*6_ zFY{6uo0|)+uzY@gx<|NA8+gAjqXJ~C!J5=D0qrEkVCbQbQAh*~dQKbDwYDG4pz=&d zgj%&WOup*#BAMmrFoZeC_8e`lJdy}DO=Q*uBz2L$mmUJBd%VzgtKXx;X#((>Lh%S( zX`yD2Xa@!dz`r{M?4RDO3*C|JXL@HIR03Z`V7%5=-=o3fZ0`n80N$& zj)4KQej+ZPQt3`sJ}E4%CdzI#pUq{@ol9M$p%Oq=*+K zO!2$gQPL#Ui$^3+GOd% zFgQ;)f1Vj!6D^L&s}|DpH-?A09e29qg+(=sjh4m|AwU4A*-T?0ug?K+NPz8e#P@^S zVG2(IlSmdk-DOpB?BRaI#gJBsoTXyEMYL%khK!W8 z%4saG#jbazMjA|d8Vti@Tp?;dSY*MaW3_AA@GT!5-sZ%fMG6Z^S9B50b~o&A&(bqw zqw*)FRUXbFmo(3oW*C{1{E#{*S=&OF6%`sizO-5%sQKLn>eCCWE>LH z|NjugYcw+6+^uqne$>-Ab(_62I<6u=tttO_Q*J?U6uy1uT{)`Sd6V;cUdf7XxaABG zOSvfiZ(^w|<^N4Ar3NFG0yHZcOZTlV`S7{(Ew3M3Ia>tb`K-Dg|KIFVxdi_YcB!Lf zv5$)V2sP9qS;H(VDL#CEhYD)Im*y5DnKa@^`jHZk4;uKMij8kw3;K8#DT8|DBf&XX zGU%FM&hvfJ%@a>kU(cd$z|R5*pUfHDo{{n@o(OB}i~2V7IbiLg%s4gL?;E{P#NxjR zC>4FZg6M8!gts%$>IKdRz+XGAo%+H)Jgj(uDECYtq=J}j2%&|&F3bgXhU5av1TlGl zOb`YyEE9xuuU;_d<~^(sgif&9=iLl|HQoKSbzi?h7k{$aejO==cLph zxkyJU2(Xq5FsVJZ*~%jmmT5%*h~OhD{jtwn4q8&fB8RP*#*A~xq`b`S$F_7df}YZh zbIj>H)PP^%XOkK9-2%yfJu2bQ>0z#_-~FGKdxsH|5##AAJ&g&f)&L@yIFE6e*IKRU zs1<|PJt|z6eqn(VB#x|o*B_GX#w)n5l@#b^1T68W4HNt`$kL!P-V0)dux$VcE+D9O zqYfs>m3XRExlds;#%?3ho8cuD96NhVhOR^{o9Tm9^Ws+u4<)@CCIH77rWa;@(p|6; zwrrKFVgQ-3D%=JWL(E!Ri|iEvrAE$hWeV321P1wu`0v#QMe}X8B`bUHzv>*toc8X` zJ)z{zGb`7_AtyJ`JMt2Djh{?cOyVTwZ&$H zty+3dLiOMUAfb}%pYor04>MArkb=oc?zpD6&(oS6MN70ykpkTZN8DT$xg|H;J3#+e zpCac;^l&zLVpJxl32sY`kbOXH8tZ=z*+u531XfGUoc+ZXOO`ny36K9F^m#}iP#}Z! zTsb;3vX?s3YENLOt$&o_A2zCo|0vTjV;d|8hp7~(n*Ue12H2%g4R>ph+W=Jc`fABb z@2f(N%0`Ew{M4&0ZFL#6OzGCp|YAidrs+_Wa4;J|KXh?(EJbYl&s-j-YK0G>TWRvzCTOG z71k{|yo7Eg8LGf3cS4%U$>=FYSJ+M{QdX-vqSs(dP?DvcIO98^Q*hp2(f%GH-%=O= z#p#iIh~JZ~8`J+aGj%F2qTT+}ZOx~3s6okL{&5{RFEiW*P7!aD|P z9I1VT9g}9xFLCKdMYJW+{1}vX#3PCS!XlK%s<_CR1P+rj4R)~0V z0-(_?QYLq7;tt%@j}>eWM_VG|$l5;{uIDtcKoop_?1uPQ$|3~Af2E_TRq!43a-rQ(^tJsk}rnap!c9}p8np+}$z zh)1Bw$hV3TRolJ!;s;$eIrb##)vtlZND8s?^3bF7K+S%oYQMi0>gTQgg`QI5Psl9Y z6&fGX{Ufu+NB{ZIHP!BU$SIf#4)ws)+f;?>7$a7~wEUo04N_(pTTt1UuU0`kR$F9I z3MN_6IP3X>D78b7gy|_!FVmH>-<5;P?dwbhZN}j_$tO=#YxFM?P+8pPykK+r(Qoja z=~wSNu27bfmZCQ^jGD4vE}K7K>KYA9&t!=9Q<=*>C`M0Xk|UleakEjjd@*vjxZ;J@ zefbMDI3rq%n`@wPZ}Pc!d>sNIfd>;>MX7Tap?j#|R4+6B$GJyOLXe{X8u^c)ukJ6D z>{8#r6m`;76>{|F>nc?2{8{7qt2+u--=hnJ%iT5i&Fht)v!<31X4e)|PAS(3iS>Jsm_9{zFdAk=?WXqpu3(HS4&*ISHfG|5@&L=uEoYWDQFvT6j5Tgsm29e4cEIJ! zan_!G+h(`1SbZ(0L0NajL&;tug_^-Y28m4hwE1*C^?UyCoe7^4Kn&*Z$lfg|IH`tzmpw zwQ~_I#9$WF6P+D-oiq}!=-!}V;qwc35W@9rCK?(d#K2iDvePa%MtHkJB^XrGz8ohO z&3=R3SPO(I3%!iO&QrEl`$r3$kZlGdm3(H^Lc*^DB?Td3R`JZuA3%{Ki?bmv*mQ+ITVEPBw6m#WE)ak_u_`XNpG}3H^wn$+x_*o{s)56hJ1q%Ix}4!`UJT>) zsttQ_nfUGuCe6alTwjKEj*s4jIlBAy3~JD-@sPoUJT$<94%yTv;*uPP4&Mge?5|SS znGvEx>pa=-`nTmb>_4SO4d-_*GP>v%rpf7>2SG1MGvxq?GOdyA9FfPkjRWp zsbM>bcG=S1XzL7Ly^2#E6?S1}M+@`8vygQrJk&1%kS z_es6^pI{{Z4tRGvUc!Li^PG1!IgDEVw`a;&mj%)qpCg1Dex^( z)}lRZ($-)nCWd8m?Kp@Vzz0cJbx%*YKU`({MwT}5WW0Lj-4G3Z$;eGp3ArBEuN%Qs zmpW0HF0oXWU=m*UEDY-({g(ZKBZGf1znhA~`tLNxcmW;JRq~yHR$6~?%9E3FH5a6* z8;YFD8jgXbDxxBqJ>tDcwG50X(NK;vclDqZ9Yc#7v{C&e zyE;)xcKAZiMd1<{EGH+Mgmp?Mz(e%Zh8&zj)|soIgt4cq1;fL#rZ6HMP(KWN3-|%k{=o1(h8%{!r$2n7=j_Utfr|WY;Yg5wlVq zAo4n!Aw@t*2$`a8)8KDJmJCf0Aqp;8d$+4reEfbfW^5o(CvG|QDsl~(?G#Et&61LC zMu%)T>RUg^_tcGtFKrH-DHg=&!~O6dmlwz?>UDd;_K#oJDQzN3WTUXUs!mY-48ybI z($PeOx!2td3-90>g?CYTP=iBYMEO~l5{DZm39&Ae*5m0i=zyyumiMGk`2$IjdK#cV z(iedRiuc1Js29(};2*ELGZaiCsM-um@Mm=xMv|jdV8`u_i{AB|mkfTG$TYv=PSF>OQeJrw6+3&UM-`fRf4j}MlE7bzmc{C#=JRoXf!;+0 zui}f08#-|QnMJOY1R2`t@3Y*8x9IxiY_ zAwFd5@~{v%VhC?En1={cmGV`s(p5s=Tc-JZF+{RLmZT~gEtKy`M@TI-EuIw>43mV4 zzajNXKWAQEIIMA)w#nry!0wl|SE1?1)KNFSDJFW_U*)~;1^pP6(ay4Y^U%$6>ESV~ z5gv~`OsC>~sVLPvU9JO}ccG$~0#+t?!l2xbKNH03W&Lt#&q5?y6mL0X(fc@8mby2u zV?##+FNmtOBP}hRvT|R1SU+|<4?TnPfisw2n7pW*z^A8ie9vi!(`CzNd@uGHg-LY# z^U)Uf0U_D-ST3@=i?Mp5m}7+qnk}SvI?kcm$5=m}@Gjc#gc4tXn%E_IGMJ7r&DkcH zqJ%A-T}IS^1Kt_*;il4e#0(zt{EaE-=x8?N^)_Hl+vF?()tZm*d?rn~mAH?NLDb@K z+iN`@J42w`E=%h7o->T9@IP@^$upY?9mlv^a$eh_`af^#?XgM`da5qk=EVG)AA4ef zvnLQ}0X{+C--D;)zdtA7Em$cZpCbK$f0=Zx*fkQr_Fysv9f$^4VbK8QTJaF_a;o%4 z`riq-PP>Ak0e^jAOj%c)LL*l`mITGZSO=T zocuDX9;G<&O4$zXaSlv0a{AQP7G`7ArPk)tU7V51#caY%)-C&kbG)7|t|sd`LwbKw zub|hrJf?qk2Gi4Qza`J_^q*D2J2e~epVpB~FVCJa?IG-qK;KM8t|#j&mDc=rSH144 zdnC7%F4q)wV3Odj0!PuLY0_jKZl4m%82NBKox^TLwyiOr`|#8YrC6v9S>?cNaC>vMc_IOy5Q~d|&w0eeb|Yyi#gFc@ zWflHPN@s)VoGdY`n>HV(|AC45eSdUK_@w8mi%*adYt;F-OD(?`cXf$H2FvUId19!G zGycRO_#;bX>INGsT~l(c;=)+EcT6x-r zURZCf9vYSLMRm;1L#VcSFI(u|W6W7KHkqS%@$K9DG%t|b9!^|)MBU$0m?Av*Y?!MC z|FctFG&G7U$Z(dvDH+8UsVZZwq%Zk~4?=woZhwh8GF1lI4+@6ejod=E4;3ntMst|q(;^YE~g zhz=dDslf818#oEJ>7lLRn0LsAczc`g%Q8(4+V8%MFLtc^^!+B}3C|dD2!zP?K_5O} zlv|WDTu^c2v+I~3HPZNSeOz3JM8VBr8Wl!>k`_*Gpt0VzDcZ0l(RFwAXXxSqvIGXN zT;>~tS#Y4e=0Av1+CN8&Z^#enf>(ohdgsP;7nvoNfM{Guz^T!*m8SHCSsp@Z-O~YSL>rMM9%0 z6>m%rC=`uipr0=XSftF?*Pbyq_x^URh%fT#vo9m!apCLqpD9vv zt8~{TR5oiqJUvdHx?AFIgGN<+j7kW;@0UE6UN<^S>CJkXPSEAA;+(#QeXdRZ(}k!j z^@7W(zGnL|WybD$tsuywrm&L}7ARh*mq>leo4Km;-B6OJtMDV{4XS+!A6NRrqr-MP z>^(C!sYQeGGSQOh<`~0NjSb}LCrwb_V+*W&hkmkMeDlO8$8qA@prrkF#YDlSUv{_a zCeDfunCO6l3e)3n%fdqIJro^CM_jhtKK*-TMWmiNnT7=c&xn%cOHUK5)|H$Q;veC` zRr{k|Oi$@p;EB-;Ba%M&`N0xV<2X5d*yX~hta?HpLbAHFl6Y&P8kouxaZ{w17(!~v zzv;5Xd{uBe+0Js5_}sW(b3l{w`emvfN7$^h%vN;r(tuc@OdkijaHlHt>sIPlXN(9o z)M&;K|0aCY_rYfya}v(Gik7nt`Ff{rj8Yzi8=7h_aOSLhQpVPm85X~{`3!1&lP6E| zvP_4VGAEm&rgmeK8^0KN^E-6Uhflht zaX-)c>T8SmWu(Fl!%3OEEHS%EQ!=euax3&fLB~X%cp8T)-q+`qaC8`5E<(CDhw+W= z7jUejQ5m_~r**pv&yGsyqfu-T-HwBVj@rh{aZ~GtS7`!8@7>MNe-i&%qifn$iuTtW1GVr{&HGeSpuaFMk zo%0Oh%GJ*j3T2z)HYG#GUpw0MJoG7S4i=kBtq;v-aP9Aa{vr3Tv7^X5Ske!L%Uw59 z3gl;K(sXd{etl??rE09ygkLHb!IzKP#rTRIw~MS@&Vk-eiRUhSTY4%oBj?Sjwr|Hg zgYf$&v|#SIM{5GmYrO4ZJ=%oVxX)U^#MIG(uVB2=@$<1vyU)Vb6>G zlbTj1DVd?JEab;-<}B!?lig4o@O=NL@7!e$$T$%D7=j`03mnwtg2=j5in`A$KhkTZ zQ(6^@)XLZFII>^SH)(c5H=gf4O3PAdbPKsl??dYIKV z&->-7&6U=jI)GwEv2s6v!wL5vX8Wu~D1XR%Qnm&yoAs5d-3s}z*2>R;k^}5PL*Ur@ zs>X{=1RXf_)6k~&)79wk8)By-kL5!%k?!G^2Rn z4s?Fic8x#vGjf<0=f`jD=Q!tjlf!W>9BcAK#-suH1+D}hqr`H}Mfwfksh?&sih*fE z!Zc^0BcDP4LrTf#P`JU-Mf8*o+@ZQl#e`8ueq_Mf*^g?+qFp>WYt_Bwd13DwUvnZ`KWz5$aGvBd#QxV%`g#30CTT3eT`o!LLHWaXW( zb!++tECePU8|>_Zwf>L_{Iun%MclY1%SJ^h)$PeB^v(#QLa|L}YW+3tE+>^__vln7 z4%e^d$`yAb7TFfXHbIWV4ntQy4%#VRbm_^kRsZCZw#V=F#J)|@v2}ZC)>ur^2pi4D zWnmJvVhE-z_8h(~vDbb2&{0thKBW8^|7JHS^hj|AkZ=!0Aw_A2-y6A5upnirU z%vpV54wZGGB>aQkC2#Q@52B~JLP;r)Obo(s+M&is&l~y5A3v59G?L`m9rUM4eXCa5 zMAbC4=EgzKPsBiks-UtTrc<5h`S`>n0Ge<~08f3hV21HSo&!tt3rX`uUsT%Bz~@_d zwMw2>_kJBU)i!y<4mpI-ipc^AI){wSp9Ak4*O43q(UoDeoMY5)r7SwuinbfDCi2_D zS&OK(pPBxX>a;j;haVJT7-UE@xCF2NWlTLrn)RECM;b1yHxArqmmGNRmVZ=uF{B}n z-r`wX%|38h+a|D5t^o66cBpKqCUQ$;oH?Coov3;x`rUBkk@iYFnZxruHz(~qYHg2-A1PK#RGm;dgjX<^8ni*GH%Y(Z~04diI{z9kUnhIu&pLs%7PKLUV#(AeRxu| zJ8d@=x5|rRaUNWo$rbI5H*$4;%LoljSG+_j$r{G zuVisEM>H|!^t+uvjX{DU@gduWxHr;GniB7&RoAOu2rJl?7pxDJrP6Iff*?QezR_|b z{}|Nz9(GZ5>(u5T=O-P1J)K^QbrK*dPIM9NlMv;BT%IrbqkDUV!;1qhi*36C`yfvm zHa+#)gyp+a3u=e=h5A9Y?}5UUP|h(@{8gCG>_YR2X%BAqi<)6BbI>R|_2MrY^sAYi zrIlGPA)!a1TLt1mJX#%i<%^9PA6^c7p5aGltRZnOR+kEOXFgzd@QH7k>Y{|HXDTg( zkW%t|WZh}e6Ok%!mVNFuzC*2JAlVibpsQ@s5h3L^ zt#=AU7Rsr*X+k48q(n>xR|@A@aE02gh{g!7oH+_e7EvqEiWa?$2kGzok&OjUG%MPu zKAz2jZQP9fhT;MCj9!~hW&`yc|Ej8i=CO(9BWhDMSf^8EwMp3l1}L_@4QF4bf(#uy zw~-{%^1Nx=%U7OsV6t|m8Qvo(3(VDDoAvK2 z%zpjzJM0B^HK2U7)@=;!q;J&L ateY2a|4><> #DeepSkyB rewards: CRV } -' Oracle -object "OracleRouter" as oracle <> #DeepSkyBlue { -pairs: - USDC/USD -} - checker ..> ousd checker ..> vault wousd ..> ousd ousd <.> vault -vault ..> oracle ' Strategies -vault <...> musd -vault <...> curveAmoStrat +vault <..> musd +vault <..> curveAmoStrat @enduml \ No newline at end of file diff --git a/contracts/node.sh b/contracts/node.sh index 52166b041a..84b8fc293c 100755 --- a/contracts/node.sh +++ b/contracts/node.sh @@ -91,8 +91,6 @@ main() done printf "\n" echo "🟢 Node initialized" - - FORK_NETWORK_NAME=$FORK_NETWORK_NAME FORK=true npx hardhat fund --amount 100000 --network localhost --accountsfromenv true & # wait for subprocesses to finish for job in `jobs -p` diff --git a/contracts/package.json b/contracts/package.json index 99dff7e25c..ffcbf28364 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -31,8 +31,8 @@ "lint:js": "eslint \"test/**/*.js\" \"tasks/**/*.js\" \"deploy/**/*.js\"", "lint:sol": "solhint \"contracts/**/*.sol\"", "prettier": "yarn run prettier:js && yarn run prettier:sol", - "prettier:check": "prettier -c \"*.js\" \"deploy/**/*.js\" \"scripts/**/*.js\" \"smoke/**/*.js\" \"scripts/**/*.js\" \"tasks/**/*.js\" \"test/**/*.js\" \"utils/**/*.js\"", - "prettier:js": "prettier --write \"*.js\" \"deploy/**/*.js\" \"scripts/**/*.js\" \"smoke/**/*.js\" \"scripts/**/*.js\" \"tasks/**/*.js\" \"test/**/*.js\" \"utils/**/*.js\"", + "prettier:check": "prettier -c \"*.js\" \"deploy/**/*.js\" \"scripts/**/*.js\" \"scripts/**/*.js\" \"tasks/**/*.js\" \"test/**/*.js\" \"utils/**/*.js\"", + "prettier:js": "prettier --write \"*.js\" \"deploy/**/*.js\" \"scripts/**/*.js\" \"scripts/**/*.js\" \"tasks/**/*.js\" \"test/**/*.js\" \"utils/**/*.js\"", "prettier:sol": "prettier --write --plugin=prettier-plugin-solidity \"contracts/**/*.sol\"", "test": "rm -rf deployments/hardhat && IS_TEST=true npx hardhat test", "test:base": "rm -rf deployments/hardhat && UNIT_TESTS_NETWORK=base IS_TEST=true npx hardhat test", diff --git a/contracts/scripts/governor/propose.js b/contracts/scripts/governor/propose.js index 6be3eb9c38..8bfdddbd50 100644 --- a/contracts/scripts/governor/propose.js +++ b/contracts/scripts/governor/propose.js @@ -348,46 +348,6 @@ async function proposeUpgradeOusdArgs() { return { args, description }; } -// Returns the argument to use for sending a proposal to upgrade VaultCore. -async function proposeUpgradeVaultCoreArgs(config) { - const vaultProxy = await ethers.getContract("VaultProxy"); - - const args = await proposeArgs([ - { - contract: vaultProxy, - signature: "upgradeTo(address)", - args: [config.address], - }, - ]); - const description = "Upgrade VaultCore"; - return { args, description }; -} - -async function proposeUpgradeVaultCoreAndAdminArgs() { - const cVaultProxy = await ethers.getContract("VaultProxy"); - const cVaultCoreProxy = await ethers.getContractAt( - "VaultCore", - cVaultProxy.address - ); - const cVaultCore = await ethers.getContract("VaultCore"); - const cVaultAdmin = await ethers.getContract("VaultAdmin"); - - const args = await proposeArgs([ - { - contract: cVaultProxy, - signature: "upgradeTo(address)", - args: [cVaultCore.address], - }, - { - contract: cVaultCoreProxy, - signature: "setAdminImpl(address)", - args: [cVaultAdmin.address], - }, - ]); - const description = "Vault Core and Admin upgrade"; - return { args, description }; -} - // Returns the arguments to use for sending a proposal call to upgrade to a new MicOracle. // See migration 11_new_mix_oracle for reference. async function proposeUpgradeOracleArgs() { @@ -656,95 +616,6 @@ async function proposeProp14Args() { return { args, description }; } -// Args to send a proposal to: -// - upgrade Vault Core and Admin -// - set the Aave reward token address to zero address -// - set the liquidation thresholds on strategies (except Aave since it does not have a reward token) -async function proposeProp17Args() { - const cVaultProxy = await ethers.getContract("VaultProxy"); - const cVaultCoreProxy = await ethers.getContractAt( - "VaultCore", - cVaultProxy.address - ); - const cVaultCore = await ethers.getContract("VaultCore"); - const cVaultAdmin = await ethers.getContract("VaultAdmin"); - - const cAaveStrategyProxy = await ethers.getContract("AaveStrategyProxy"); - const cAaveStrategy = await ethers.getContractAt( - "AaveStrategy", - cAaveStrategyProxy.address - ); - - const cCompoundStrategyProxy = await ethers.getContract( - "CompoundStrategyProxy" - ); - const cCompoundStrategy = await ethers.getContractAt( - "CompoundStrategy", - cCompoundStrategyProxy.address - ); - - const cCurveUSDCStrategyProxy = await ethers.getContract( - "CurveUSDCStrategyProxy" - ); - const cCurveUSDCStrategy = await ethers.getContractAt( - "ThreePoolStrategy", - cCurveUSDCStrategyProxy.address - ); - - const cCurveUSDTStrategyProxy = await ethers.getContract( - "CurveUSDTStrategyProxy" - ); - const cCurveUSDTStrategy = await ethers.getContractAt( - "ThreePoolStrategy", - cCurveUSDTStrategyProxy.address - ); - - const args = await proposeArgs([ - { - contract: cVaultProxy, - signature: "upgradeTo(address)", - args: [cVaultCore.address], - }, - { - contract: cVaultCoreProxy, - signature: "setAdminImpl(address)", - args: [cVaultAdmin.address], - }, - { - contract: cAaveStrategy, - signature: "setRewardTokenAddress(address)", - args: [addresses.zero], - }, - { - contract: cCompoundStrategy, - signature: "setPTokenAddress(address,address)", - args: [addresses.mainnet.USDC, addresses.mainnet.cUSDC], - }, - { - contract: cCompoundStrategy, - signature: "setPTokenAddress(address,address)", - args: [addresses.mainnet.USDT, addresses.mainnet.cUSDT], - }, - { - contract: cCompoundStrategy, - signature: "setRewardLiquidationThreshold(uint256)", - args: [utils.parseUnits("1", 18)], // 1 COMP with precision 18 - }, - { - contract: cCurveUSDCStrategy, - signature: "setRewardLiquidationThreshold(uint256)", - args: [utils.parseUnits("200", 18)], // 200 CRV with precision 18 - }, - { - contract: cCurveUSDTStrategy, - signature: "setRewardLiquidationThreshold(uint256)", - args: [utils.parseUnits("200", 18)], // 200 CRV with precision 18 - }, - ]); - const description = "Prop 16"; - return { args, description }; -} - async function proposeSetRewardLiquidationThresholdArgs() { const cCompoundStrategyProxy = await ethers.getContract( "CompoundStrategyProxy" @@ -985,12 +856,6 @@ async function main(config) { } else if (config.upgradeOusd) { console.log("upgradeOusd proposal"); argsMethod = proposeUpgradeOusdArgs; - } else if (config.upgradeVaultCore) { - console.log("upgradeVaultCore proposal"); - argsMethod = proposeUpgradeVaultCoreArgs; - } else if (config.upgradeVaultCoreAndAdmin) { - console.log("upgradeVaultCoreAndAdmin proposal"); - argsMethod = proposeUpgradeVaultCoreAndAdminArgs; } else if (config.upgradeOracle) { console.log("upgradeOracle proposal"); argsMethod = proposeUpgradeOracleArgs; @@ -1018,9 +883,6 @@ async function main(config) { } else if (config.prop14) { console.log("prop14 proposal"); argsMethod = proposeProp14Args; - } else if (config.prop17) { - console.log("prop17 proposal"); - argsMethod = proposeProp17Args; } else if (config.pauseCapital) { console.log("pauseCapital"); argsMethod = proposePauseCapitalArgs; diff --git a/contracts/smoke/mintRedeemTest.js b/contracts/smoke/mintRedeemTest.js deleted file mode 100644 index 65d3683080..0000000000 --- a/contracts/smoke/mintRedeemTest.js +++ /dev/null @@ -1,206 +0,0 @@ -const { fund, mint } = require("../tasks/account"); -const { - usdtUnits, - ousdUnits, - usdcUnits, - daiUnits, - ousdUnitsFormat, - isWithinTolerance, -} = require("../test/helpers"); -const addresses = require("../utils/addresses"); -const erc20Abi = require("../test/abi/erc20.json"); - -let utils, BigNumber, usdt, dai, usdc, ousd, vault, signer, signer2; - -async function fundAccount4(hre) { - await fund( - { - num: 1, - amount: "3000", - }, - hre - ); -} - -const getUsdtBalance = async () => { - return await usdt.connect(signer).balanceOf(signer.address); -}; - -const getDaiBalance = async () => { - return await dai.connect(signer).balanceOf(signer.address); -}; - -const getUsdcBalance = async () => { - return await usdc.connect(signer).balanceOf(signer.address); -}; - -const getOusdBalance = async (signer) => { - return await ousd.connect(signer).balanceOf(signer.address); -}; - -const assertExpectedOusd = (bigNumber, bigNumberExpected, tolerance = 0.03) => { - if (!isWithinTolerance(bigNumber, bigNumberExpected, 0.03)) { - throw new Error( - `Unexpected OUSD value. Expected ${ousdUnitsFormat( - bigNumberExpected - )} with the tolerance of ${tolerance}. Received: ${ousdUnitsFormat( - bigNumber - )}` - ); - } -}; - -const assertExpectedStablecoins = ( - usdtBn, - daiBn, - usdcBn, - unitsExpected, - tolerance = 0.03 -) => { - // adjust decimals of all stablecoins to 18 so they are easier to compare - const adjustedUsdt = usdtBn.mul(BigNumber.from("1000000000000")); - const adjustedUsdc = usdcBn.mul(BigNumber.from("1000000000000")); - const allStablecoins = adjustedUsdt.add(adjustedUsdc).add(daiBn); - const stableCoinsExpected = utils.parseUnits(unitsExpected, 18); - - if (!isWithinTolerance(allStablecoins, stableCoinsExpected, 0.03)) { - throw new Error( - `Unexpected value. Expected to receive total stablecoin units ${ousdUnitsFormat( - stableCoinsExpected - )} with the tolerance of ${tolerance}. Received: ${ousdUnitsFormat( - allStablecoins - )}` - ); - } -}; - -async function setup(hre) { - utils = hre.ethers.utils; - BigNumber = hre.ethers.BigNumber; - ousd = await hre.ethers.getContractAt("OUSD", addresses.mainnet.OUSDProxy); - usdt = await hre.ethers.getContractAt(erc20Abi, addresses.mainnet.USDT); - dai = await hre.ethers.getContractAt(erc20Abi, addresses.mainnet.DAI); - usdc = await hre.ethers.getContractAt(erc20Abi, addresses.mainnet.USDC); - vault = await ethers.getContractAt("IVault", addresses.mainnet.VaultProxy); - signer = (await hre.ethers.getSigners())[4]; - signer2 = (await hre.ethers.getSigners())[5]; - - await fundAccount4(hre); -} - -async function beforeDeploy(hre) { - // fund stablecoins to the 4th account in signers - await setup(hre); - - const usdtBeforeMint = await getUsdtBalance(); - const ousdBeforeMint = await getOusdBalance(signer); - const usdtToMint = "1100"; - await mint( - { - num: 1, - amount: usdtToMint, - }, - hre - ); - - const usdtAfterMint = await getUsdtBalance(); - const ousdAfterMint = await getOusdBalance(signer); - - const expectedUsdt = usdtBeforeMint.sub(usdtUnits(usdtToMint)); - if (!usdtAfterMint.eq(expectedUsdt)) { - throw new Error( - `Incorrect usdt value. Got ${usdtAfterMint.toString()} expected: ${expectedUsdt.toString()}` - ); - } - - const expectedOusd = ousdBeforeMint.add(ousdUnits(usdtToMint)); - assertExpectedOusd(ousdAfterMint, expectedOusd); - - return { - ousdBeforeMint, - ousdAfterMint, - }; -} - -const testMint = async (hre, beforeDeployData) => { - const ousdBeforeMint = await getOusdBalance(signer); - await mint( - { - num: 1, - amount: "500", - }, - hre - ); - - const ousdAfterMint = await getOusdBalance(signer); - - if (!beforeDeployData.ousdAfterMint.eq(ousdBeforeMint)) { - throw new Error( - `Deploy changed the amount of ousd in user's account from ${ousdUnitsFormat( - beforeDeployData.ousdAfterMint - )} to ${ousdUnitsFormat(ousdBeforeMint)}` - ); - } - - return ousdAfterMint; -}; - -const testRedeem = async (ousdAfterMint) => { - const usdtBeforeRedeem = await getUsdtBalance(); - const daiBeforeRedeem = await getDaiBalance(); - const usdcBeforeRedeem = await getUsdcBalance(); - - const unitsToRedeem = "800"; - const ousdToRedeem = ousdUnits(unitsToRedeem); - await vault.connect(signer).redeem(ousdToRedeem, ousdUnits("770")); - - const ousdAfterRedeem = await getOusdBalance(signer); - const usdtAfterRedeem = await getUsdtBalance(); - const daiAfterRedeem = await getDaiBalance(); - const usdcAfterRedeem = await getUsdcBalance(); - - const expectedOusd = ousdAfterMint.sub(ousdToRedeem); - assertExpectedOusd(ousdAfterRedeem, expectedOusd, 0.0); - - assertExpectedStablecoins( - usdtAfterRedeem.sub(usdtBeforeRedeem), - daiAfterRedeem.sub(daiBeforeRedeem), - usdcAfterRedeem.sub(usdcBeforeRedeem), - "800" - ); -}; - -const testTransfer = async () => { - const ousdSenderBeforeSend = await getOusdBalance(signer); - const ousdReceiverBeforeSend = await getOusdBalance(signer2); - const ousdToTransfer = "245.5"; - - await ousd - .connect(signer) - .transfer(signer2.address, ousdUnits(ousdToTransfer)); - - const ousdSenderAfterSend = await getOusdBalance(signer); - const ousdReceiverAfterSend = await getOusdBalance(signer2); - - assertExpectedOusd( - ousdSenderAfterSend, - ousdSenderBeforeSend.sub(ousdUnits(ousdToTransfer)), - 0.0 - ); - assertExpectedOusd( - ousdReceiverAfterSend, - ousdReceiverBeforeSend.add(ousdUnits(ousdToTransfer)), - 0.0 - ); -}; - -async function afterDeploy(hre, beforeDeployData) { - const ousdAfterMint = await testMint(hre, beforeDeployData); - await testRedeem(ousdAfterMint); - await testTransfer(); -} - -module.exports = { - beforeDeploy, - afterDeploy, -}; diff --git a/contracts/tasks/account.js b/contracts/tasks/account.js index 5d211cbb2e..9f146cc7db 100644 --- a/contracts/tasks/account.js +++ b/contracts/tasks/account.js @@ -1,19 +1,3 @@ -// USDT has its own ABI because of non standard returns -const usdtAbi = require("../test/abi/usdt.json").abi; -const usdsAbi = require("../test/abi/erc20.json"); -const tusdAbi = require("../test/abi/erc20.json"); -const usdcAbi = require("../test/abi/erc20.json"); -const { hardhatSetBalance, setERC20TokenBalance } = require("../test/_fund"); - -// By default we use 10 test accounts. -const defaultNumAccounts = 10; - -// The first 4 hardhat accounts are reserved for use as the deployer, governor, etc... -const defaultAccountIndex = 4; - -// By default, fund each test account with 10k worth of each stable coin. -const defaultFundAmount = 10000; - /** * Prints test accounts. */ @@ -40,160 +24,6 @@ async function accounts(taskArguments, hre, privateKeys) { } } -/** - * Funds test accounts on local or fork with USDS, USDT, USDC and TUSD. - */ -async function fund(taskArguments, hre) { - const addresses = require("../utils/addresses"); - const { isFork, isLocalhost } = require("../test/helpers"); - - if (!isFork && !isLocalhost) { - throw new Error("Task can only be used on local or fork"); - } - - if (hre.network.config.chainId !== 1) { - // Skip funding if it's not mainnet - return; - } - - if (!process.env.ACCOUNTS_TO_FUND) { - // No need to fund accounts if no accounts to fund - return; - } - - let usdt, usds, tusd, usdc; - if (isFork) { - usdt = await hre.ethers.getContractAt(usdtAbi, addresses.mainnet.USDT); - usds = await hre.ethers.getContractAt(usdsAbi, addresses.mainnet.USDS); - tusd = await hre.ethers.getContractAt(tusdAbi, addresses.mainnet.TUSD); - usdc = await hre.ethers.getContractAt(usdcAbi, addresses.mainnet.USDC); - } else { - usdt = await hre.ethers.getContract("MockUSDT"); - usds = await hre.ethers.getContract("MockUSDS"); - tusd = await hre.ethers.getContract("MockTUSD"); - usdc = await hre.ethers.getContract("MockUSDC"); - } - - const signers = await hre.ethers.getSigners(); - - let accountsToFund; - let signersToFund; - - if (taskArguments.accountsfromenv) { - if (!isFork) { - throw new Error("accountsfromenv param only works in fork mode"); - } - accountsToFund = process.env.ACCOUNTS_TO_FUND.split(","); - } else { - const numAccounts = Number(taskArguments.num) || defaultNumAccounts; - const accountIndex = Number(taskArguments.account) || defaultAccountIndex; - - signersToFund = signers.splice(accountIndex, numAccounts); - accountsToFund = signersToFund.map((signer) => signer.address); - } - - const fundAmount = taskArguments.amount || defaultFundAmount; - - console.log(`USDS: ${usds.address}`); - console.log(`USDC: ${usdc.address}`); - console.log(`USDT: ${usdt.address}`); - console.log(`TUSD: ${tusd.address}`); - - const contractDataList = [ - { - name: "eth", - token: null, - }, - { - name: "usds", - token: usds, - }, - { - name: "usdc", - token: usdc, - }, - { - name: "usdt", - token: usdt, - }, - ]; - - for (let i = 0; i < accountsToFund.length; i++) { - const currentAccount = accountsToFund[i]; - await Promise.all( - contractDataList.map(async (contractData) => { - const { token, name } = contractData; - const usedFundAmount = token !== null ? fundAmount : "1000000"; - - if (!token) { - await hardhatSetBalance(currentAccount, usedFundAmount); - } else { - await setERC20TokenBalance( - currentAccount, - token, - usedFundAmount, - hre - ); - } - - console.log( - `Funded ${currentAccount} with ${usedFundAmount} ${name.toUpperCase()}` - ); - }) - ); - } -} - -// Sends OUSD to a destination address. -async function transfer(taskArguments) { - const { - ousdUnits, - ousdUnitsFormat, - isFork, - isLocalHost, - } = require("../test/helpers"); - - if (!isFork && !isLocalHost) { - throw new Error("Task can only be used on local or fork"); - } - - const ousdProxy = await ethers.getContract("OUSDProxy"); - const ousd = await ethers.getContractAt("OUSD", ousdProxy.address); - - const index = Number(taskArguments.index); - const amount = taskArguments.amount; - const to = taskArguments.to; - - const signers = await hre.ethers.getSigners(); - const signer = signers[index]; - - // Print balances prior to the transfer - console.log("\nOUSD balances prior transfer"); - console.log( - `${signer.address}: ${ousdUnitsFormat( - await ousd.balanceOf(signer.address) - )} OUSD` - ); - console.log(`${to}: ${ousdUnitsFormat(await ousd.balanceOf(to))} OUSD`); - - // Send OUSD. - console.log( - `\nTransferring ${amount} OUSD from ${signer.address} to ${to}...` - ); - await ousd.connect(signer).transfer(to, ousdUnits(amount)); - - // Print balances after to the transfer - console.log("\nOUSD balances after transfer"); - console.log( - `${signer.address}: ${ousdUnitsFormat( - await ousd.balanceOf(signer.address) - )} OUSD` - ); - console.log(`${to}: ${ousdUnitsFormat(await ousd.balanceOf(to))} OUSD`); -} - module.exports = { accounts, - fund, - transfer, }; diff --git a/contracts/tasks/tasks.js b/contracts/tasks/tasks.js index 5b4daab063..8275c3513d 100644 --- a/contracts/tasks/tasks.js +++ b/contracts/tasks/tasks.js @@ -1,5 +1,4 @@ const { subtask, task, types } = require("hardhat/config"); -const { fund } = require("./account"); const { debug } = require("./debug"); const { env } = require("./env"); const { setActionVars, updateAction } = require("./defender"); @@ -51,14 +50,12 @@ const { depositToStrategy, mint, rebase, - redeem, requestWithdrawal, claimWithdrawal, snapVault, withdrawFromStrategy, withdrawAllFromStrategy, withdrawAllFromStrategies, - yieldTask, } = require("./vault"); const { checkDelta, getDelta, takeSnapshot } = require("./valueChecker"); const { @@ -154,17 +151,6 @@ const log = require("../utils/logger")("tasks"); // Environment tasks. task("env", "Check env vars are properly set for a Mainnet deployment", env); -// Account tasks. -task("fund", "Fund accounts on local or fork") - .addOptionalParam("num", "Number of accounts to fund") - .addOptionalParam("index", "Account start index") - .addOptionalParam("amount", "Stable coin amount to fund each account with") - .addOptionalParam( - "accountsfromenv", - "Fund accounts from the .env file instead of mnemonic" - ) - .setAction(fund); - // Debug tasks. task("debug", "Print info about contracts and their configs", debug); @@ -172,7 +158,7 @@ task("debug", "Print info about contracts and their configs", debug); subtask("allowance", "Get the token allowance an owner has given to a spender") .addParam( "symbol", - "Symbol of the token. eg OETH, WETH, USDT or OGV", + "Symbol of the token. eg OETH, WETH, USDC or OGV", undefined, types.string ) @@ -198,7 +184,7 @@ task("allowance").setAction(async (_, __, runSuper) => { subtask("balance", "Get the token balance of an account or contract") .addParam( "symbol", - "Symbol of the token. eg OETH, WETH, USDT or OGV", + "Symbol of the token. eg OETH, WETH, USDC or OGV", undefined, types.string ) @@ -220,7 +206,7 @@ task("balance").setAction(async (_, __, runSuper) => { subtask("approve", "Approve an account or contract to spend tokens") .addParam( "symbol", - "Symbol of the token. eg OETH, WETH, USDT or OGV", + "Symbol of the token. eg OETH, WETH, USDC or OGV", undefined, types.string ) @@ -244,7 +230,7 @@ task("approve").setAction(async (_, __, runSuper) => { subtask("transfer", "Transfer tokens to an account or contract") .addParam( "symbol", - "Symbol of the token. eg OETH, WETH, USDT or OGV", + "Symbol of the token. eg OETH, WETH, USDC or OGV", undefined, types.string ) @@ -258,7 +244,7 @@ task("transfer").setAction(async (_, __, runSuper) => { subtask("transferFrom", "Transfer tokens from an account or contract") .addParam( "symbol", - "Symbol of the token. eg OETH, WETH, USDT or OGV", + "Symbol of the token. eg OETH, WETH, USDC or OGV", undefined, types.string ) @@ -359,12 +345,10 @@ task("rebase").setAction(async (_, __, runSuper) => { return runSuper(); }); -task("yield", "Artificially generate yield on the OUSD Vault", yieldTask); - subtask("mint", "Mint OTokens from the Vault using collateral assets") .addOptionalParam( "asset", - "Symbol of the collateral asset to deposit. eg WETH, wS, USDT, DAI or USDC", + "Symbol of the collateral asset to deposit. eg WETH, wS or USDC", undefined, types.string ) @@ -392,25 +376,6 @@ task("mint").setAction(async (_, __, runSuper) => { return runSuper(); }); -subtask("redeem", "Redeem OTokens for collateral assets from the Vault") - .addParam("amount", "Amount of OTokens to burn", undefined, types.float) - .addOptionalParam( - "symbol", - "Symbol of the OToken. eg OETH or OUSD", - "OETH", - types.string - ) - .addOptionalParam( - "min", - "Minimum amount of collateral to receive", - 0, - types.float - ) - .setAction(redeem); -task("redeem").setAction(async (_, __, runSuper) => { - return runSuper(); -}); - subtask( "depositToStrategy", "Deposits vault collateral assets to a vault strategy" diff --git a/contracts/tasks/vault.js b/contracts/tasks/vault.js index a8a3d6be4c..540cb0bd09 100644 --- a/contracts/tasks/vault.js +++ b/contracts/tasks/vault.js @@ -1,7 +1,6 @@ const { formatUnits, parseUnits } = require("ethers/lib/utils"); const { getBlock } = require("./block"); -const addresses = require("../utils/addresses"); const { resolveAsset } = require("../utils/resolvers"); const { getSigner } = require("../utils/signers"); const { logTxDetails } = require("../utils/txLogger"); @@ -198,57 +197,6 @@ async function rebase({ symbol }, hre) { await logTxDetails(tx, "rebase"); } -/** - * Artificially generate yield on the vault by sending it USDT. - */ -async function yieldTask(_, hre) { - const usdtAbi = require("../test/abi/usdt.json").abi; - const { - ousdUnitsFormat, - usdtUnits, - usdtUnitsFormat, - isFork, - isLocalhost, - } = require("../test/helpers"); - if (!isFork && !isLocalhost) { - throw new Error("Task can only be used on local or fork"); - } - - let richSigner, usdt; - if (isFork) { - await hre.network.provider.request({ - method: "hardhat_impersonateAccount", - params: [addresses.mainnet.Binance], - }); - richSigner = await hre.ethers.provider.getSigner(addresses.mainnet.Binance); - usdt = await hre.ethers.getContractAt(usdtAbi, addresses.mainnet.USDT); - } else { - const signers = await hre.ethers.getSigners(); - richSigner = signers; - usdt = await hre.ethers.getContract("MockUSDT"); - } - - const { vault, oToken } = await getContracts(hre, "OUSD"); - - log("Sending yield to vault"); - let usdtBalance = await usdt.balanceOf(vault.address); - log("USDT vault balance", usdtUnitsFormat(usdtBalance)); - let vaultValue = await vault.totalValue(); - log("Vault value", ousdUnitsFormat(vaultValue)); - let supply = await oToken.totalSupply(); - log("OUSD supply", ousdUnitsFormat(supply)); - - // Transfer 100k USDT to the vault. - await usdt.connect(richSigner).transfer(vault.address, usdtUnits("100000")); - - usdtBalance = await usdt.balanceOf(vault.address); - log("USDT vault balance", usdtUnitsFormat(usdtBalance)); - vaultValue = await vault.totalValue(); - log("Vault value", ousdUnitsFormat(vaultValue)); - supply = await oToken.totalSupply(); - log("OUSD supply", ousdUnitsFormat(supply)); -} - /** * Call the Vault's admin pauseCapital method. */ @@ -312,19 +260,6 @@ async function mint({ amount, asset, symbol, min, approve }, hre) { await logTxDetails(tx, "mint"); } -async function redeem({ amount, min, symbol }, hre) { - const signer = await getSigner(); - - const { vault } = await getContracts(hre, symbol); - - const oTokenUnits = parseUnits(amount.toString()); - const minUnits = parseUnits(min.toString()); - - log(`About to redeem ${amount} ${symbol}`); - const tx = await vault.connect(signer).redeem(oTokenUnits, minUnits); - await logTxDetails(tx, "redeem"); -} - async function resolveStrategyAddress(strategy, hre) { let strategyAddr = strategy; if (!strategy.match(ethereumAddress)) { @@ -476,12 +411,10 @@ module.exports = { depositToStrategy, mint, rebase, - redeem, requestWithdrawal, claimWithdrawal, snapVault, withdrawFromStrategy, withdrawAllFromStrategy, withdrawAllFromStrategies, - yieldTask, }; diff --git a/contracts/test/_fixture-base.js b/contracts/test/_fixture-base.js index 88a18314a5..c17e0180cf 100644 --- a/contracts/test/_fixture-base.js +++ b/contracts/test/_fixture-base.js @@ -28,18 +28,7 @@ let snapshotId; const baseFixtureWithMockedVaultAdminConfig = async () => { const fixture = await defaultFixture(); - - const cOETHVaultProxy = await ethers.getContract("OETHBaseVaultProxy"); - const cOETHVaultAdmin = await ethers.getContractAt( - "IVault", - cOETHVaultProxy.address - ); - await deployWithConfirmation("MockOETHVaultAdmin", [fixture.weth.address]); - - const mockVaultAdmin = await ethers.getContract("MockOETHVaultAdmin"); - await cOETHVaultAdmin - .connect(fixture.governor) - .setAdminImpl(mockVaultAdmin.address); + await deployWithConfirmation("MockOETHVault", [fixture.weth.address]); fixture.oethbVault = await ethers.getContractAt( "IMockVault", @@ -150,6 +139,10 @@ const defaultFixture = async () => { isFork ? "OETHBaseOracleRouter" : "MockOracleRouter" ); + const mockStrategy = isFork + ? undefined + : await ethers.getContract("MockStrategy"); + // WETH let weth, aero, usdc; @@ -276,6 +269,7 @@ const defaultFixture = async () => { // Strategies aerodromeAmoStrategy, curveAMOStrategy, + mockStrategy, // Tokens weth, diff --git a/contracts/test/_fixture-plume.js b/contracts/test/_fixture-plume.js index 7b2ba1f552..91581ff216 100644 --- a/contracts/test/_fixture-plume.js +++ b/contracts/test/_fixture-plume.js @@ -21,18 +21,7 @@ let snapshotId; const baseFixtureWithMockedVaultAdminConfig = async () => { const fixture = await defaultFixture(); - - const cOETHVaultProxy = await ethers.getContract("OETHPlumeVaultProxy"); - const cOETHVaultAdmin = await ethers.getContractAt( - "IVault", - cOETHVaultProxy.address - ); - await deployWithConfirmation("MockOETHVaultAdmin", [fixture.weth.address]); - - const mockVaultAdmin = await ethers.getContract("MockOETHVaultAdmin"); - await cOETHVaultAdmin - .connect(fixture.governor) - .setAdminImpl(mockVaultAdmin.address); + await deployWithConfirmation("MockOETHVault", [fixture.weth.address]); fixture.oethpVault = await ethers.getContractAt( "IMockVault", diff --git a/contracts/test/_fixture-sonic.js b/contracts/test/_fixture-sonic.js index 2dfef95c27..0d320dc748 100644 --- a/contracts/test/_fixture-sonic.js +++ b/contracts/test/_fixture-sonic.js @@ -237,8 +237,7 @@ async function swapXAMOFixture( ) { const fixture = await defaultSonicFixture(); - const { oSonic, oSonicVault, rafael, nick, strategist, timelock, wS } = - fixture; + const { oSonic, oSonicVault, rafael, nick, strategist, wS } = fixture; let swapXAMOStrategy, swapXPool, swapXGauge, swpx; @@ -264,10 +263,6 @@ async function swapXAMOFixture( swpx = await resolveAsset("SWPx"); } - await oSonicVault - .connect(timelock) - .setAssetDefaultStrategy(wS.address, addresses.zero); - // mint some OS using wS if configured if (config?.wsMintAmount > 0) { const wsAmount = parseUnits(config.wsMintAmount.toString()); @@ -279,7 +274,7 @@ async function swapXAMOFixture( const wsBalance = await wS.balanceOf(oSonicVault.address); const queue = await oSonicVault.withdrawalQueueMetadata(); const available = wsBalance.add(queue.claimed).sub(queue.queued); - const mintAmount = wsAmount.sub(available); + const mintAmount = wsAmount.sub(available).mul(10); if (mintAmount.gt(0)) { // Approve the Vault to transfer wS diff --git a/contracts/test/_fixture.js b/contracts/test/_fixture.js index 592c16bf1f..68f8196d01 100644 --- a/contracts/test/_fixture.js +++ b/contracts/test/_fixture.js @@ -20,7 +20,6 @@ const { deployWithConfirmation } = require("../utils/deploy"); const { replaceContractAt } = require("../utils/hardhat"); const { getAssetAddresses, - usdsUnits, getOracleAddresses, oethUnits, ousdUnits, @@ -218,7 +217,7 @@ const simpleOETHFixture = deployments.createFixture(async () => { }; }); -const getVaultAndTokenConracts = async () => { +const getVaultAndTokenContracts = async () => { const ousdProxy = await ethers.getContract("OUSDProxy"); const vaultProxy = await ethers.getContract("VaultProxy"); @@ -276,9 +275,9 @@ const createAccountTypes = async ({ vault, ousd, ousdUnlocked, deploy }) => { if (!isFork) { await fundAccounts(); - const usds = await ethers.getContract("MockUSDS"); - await usds.connect(matt).approve(vault.address, usdsUnits("1000")); - await vault.connect(matt).mint(usds.address, usdsUnits("1000"), 0); + const usdc = await ethers.getContract("MockUSDC"); + await usdc.connect(matt).approve(vault.address, usdcUnits("1000")); + await vault.connect(matt).mint(usdc.address, usdcUnits("1000"), 0); } const createAccount = async () => { @@ -452,10 +451,10 @@ const createAccountTypes = async ({ vault, ousd, ousdUnlocked, deploy }) => { rebase_delegate_target_1.address ); + // Allow matt to burn OUSD + await vault.connect(governor).setStrategistAddr(matt.address); // matt burn remaining OUSD - await vault - .connect(matt) - .redeem(ousd.balanceOf(matt.address), ousdUnits("0")); + await vault.connect(matt).requestWithdrawal(ousd.balanceOf(matt.address)); return { // StdRebasing account type: @@ -523,21 +522,23 @@ const loadTokenTransferFixture = deployments.createFixture(async () => { const { governorAddr, multichainStrategistAddr, timelockAddr } = await getNamedAccounts(); - const vaultAndTokenConracts = await getVaultAndTokenConracts(); + const vaultAndTokenContracts = await getVaultAndTokenContracts(); const signers = await hre.ethers.getSigners(); let governor = signers[1]; let strategist = signers[0]; + log("Creating account types..."); const accountTypes = await createAccountTypes({ - ousd: vaultAndTokenConracts.ousd, - ousdUnlocked: vaultAndTokenConracts.ousdUnlocked, - vault: vaultAndTokenConracts.vault, + ousd: vaultAndTokenContracts.ousd, + ousdUnlocked: vaultAndTokenContracts.ousdUnlocked, + vault: vaultAndTokenContracts.vault, deploy: deployments.deploy, }); + log("Account types created."); return { - ...vaultAndTokenConracts, + ...vaultAndTokenContracts, ...accountTypes, governorAddr, strategistAddr: multichainStrategistAddr, @@ -567,7 +568,7 @@ const defaultFixture = deployments.createFixture(async () => { const { governorAddr, multichainStrategistAddr, timelockAddr } = await getNamedAccounts(); - const vaultAndTokenConracts = await getVaultAndTokenConracts(); + const vaultAndTokenConracts = await getVaultAndTokenContracts(); const harvesterProxy = await ethers.getContract("HarvesterProxy"); const harvester = await ethers.getContractAt( @@ -730,6 +731,10 @@ const defaultFixture = deployments.createFixture(async () => { addresses.mainnet.curve.OETH_WETH.gauge ); + const mockStrategy = isFork + ? undefined + : await ethers.getContract("MockStrategy"); + let usdt, usds, tusd, @@ -1019,15 +1024,8 @@ const defaultFixture = deployments.createFixture(async () => { } if (!isFork) { - const assetAddresses = await getAssetAddresses(deployments); - const sGovernor = await ethers.provider.getSigner(governorAddr); - // Add TUSD in fixture, it is disabled by default in deployment - await vaultAndTokenConracts.vault - .connect(sGovernor) - .supportAsset(assetAddresses.TUSD, 0); - // Enable capital movement await vaultAndTokenConracts.vault.connect(sGovernor).unpauseCapital(); } @@ -1061,12 +1059,12 @@ const defaultFixture = deployments.createFixture(async () => { // Matt and Josh each have $100 OUSD & 100 OETH for (const user of [matt, josh]) { - await usds + await usdc .connect(user) - .approve(vaultAndTokenConracts.vault.address, usdsUnits("100")); + .approve(vaultAndTokenConracts.vault.address, usdcUnits("100")); await vaultAndTokenConracts.vault .connect(user) - .mint(usds.address, usdsUnits("100"), 0); + .mint(usdc.address, usdcUnits("100"), 0); // Fund WETH contract await hardhatSetBalance(user.address, "50000"); @@ -1182,6 +1180,7 @@ const defaultFixture = deployments.createFixture(async () => { oethMorphoAaveStrategy, convexEthMetaStrategy, oethDripper, + oethFixedRateDripperProxy, oethHarvester, oethZapper, swapper, @@ -1196,6 +1195,8 @@ const defaultFixture = deployments.createFixture(async () => { poolBoosterCentralRegistry, poolBoosterMerklFactory, merklDistributor, + + mockStrategy, }; }); @@ -1469,29 +1470,10 @@ async function compoundVaultFixture() { await fixture.compoundStrategy .connect(sGovernor) .setPTokenAddress(assetAddresses.USDT, assetAddresses.cUSDT); - await fixture.vault - .connect(sGovernor) - .setAssetDefaultStrategy( - fixture.usdt.address, - fixture.compoundStrategy.address - ); // Add USDC await fixture.compoundStrategy .connect(sGovernor) .setPTokenAddress(assetAddresses.USDC, assetAddresses.cUSDC); - await fixture.vault - .connect(sGovernor) - .setAssetDefaultStrategy( - fixture.usdc.address, - fixture.compoundStrategy.address - ); - // Add allocation mapping for USDS - await fixture.vault - .connect(sGovernor) - .setAssetDefaultStrategy( - fixture.usds.address, - fixture.compoundStrategy.address - ); return fixture; } @@ -1515,16 +1497,7 @@ async function convexVaultFixture() { await fixture.vault .connect(sGovernor) - .setAssetDefaultStrategy( - fixture.usdt.address, - fixture.convexStrategy.address - ); - await fixture.vault - .connect(sGovernor) - .setAssetDefaultStrategy( - fixture.usdc.address, - fixture.convexStrategy.address - ); + .setDefaultStrategy(fixture.convexStrategy.address); return fixture; } @@ -1543,10 +1516,10 @@ async function balancerREthFixture(config = { defaultStrategy: true }) { if (config.defaultStrategy) { await oethVault .connect(timelock) - .setAssetDefaultStrategy(reth.address, balancerREthStrategy.address); + .setDefaultStrategy(reth.address, balancerREthStrategy.address); await oethVault .connect(timelock) - .setAssetDefaultStrategy(weth.address, balancerREthStrategy.address); + .setDefaultStrategy(weth.address, balancerREthStrategy.address); } fixture.rEthBPT = await ethers.getContractAt( @@ -1950,24 +1923,15 @@ async function morphoCompoundFixture() { if (isFork) { await fixture.vault .connect(timelock) - .setAssetDefaultStrategy( - fixture.usdt.address, - fixture.morphoCompoundStrategy.address - ); + .setDefaultStrategy(fixture.morphoCompoundStrategy.address); await fixture.vault .connect(timelock) - .setAssetDefaultStrategy( - fixture.usdc.address, - fixture.morphoCompoundStrategy.address - ); + .setDefaultStrategy(fixture.morphoCompoundStrategy.address); await fixture.vault .connect(timelock) - .setAssetDefaultStrategy( - fixture.dai.address, - fixture.morphoCompoundStrategy.address - ); + .setDefaultStrategy(fixture.morphoCompoundStrategy.address); } else { throw new Error( "Morpho strategy only supported in forked test environment" @@ -1988,10 +1952,7 @@ async function aaveFixture() { if (isFork) { await fixture.vault .connect(timelock) - .setAssetDefaultStrategy( - fixture.usdt.address, - fixture.aaveStrategy.address - ); + .setDefaultStrategy(fixture.aaveStrategy.address); } else { throw new Error( "Aave strategy supported for USDT in forked test environment" @@ -2011,19 +1972,12 @@ async function morphoAaveFixture() { if (isFork) { // The supply of DAI and USDT has been paused for Morpho Aave V2 so no default strategy - await fixture.vault - .connect(timelock) - .setAssetDefaultStrategy(fixture.dai.address, addresses.zero); - await fixture.vault - .connect(timelock) - .setAssetDefaultStrategy(fixture.usdt.address, addresses.zero); + await fixture.vault.connect(timelock).setDefaultStrategy(addresses.zero); + await fixture.vault.connect(timelock).setDefaultStrategy(addresses.zero); await fixture.vault .connect(timelock) - .setAssetDefaultStrategy( - fixture.usdc.address, - fixture.morphoAaveStrategy.address - ); + .setDefaultStrategy(fixture.morphoAaveStrategy.address); } else { throw new Error( "Morpho strategy only supported in forked test environment" @@ -2040,11 +1994,11 @@ async function oethMorphoAaveFixture() { const fixture = await oethDefaultFixture(); if (isFork) { - const { oethVault, timelock, weth, oethMorphoAaveStrategy } = fixture; + const { oethVault, timelock, oethMorphoAaveStrategy } = fixture; await oethVault .connect(timelock) - .setAssetDefaultStrategy(weth.address, oethMorphoAaveStrategy.address); + .setDefaultStrategy(oethMorphoAaveStrategy.address); } else { throw new Error( "Morpho strategy only supported in forked test environment" @@ -2086,7 +2040,7 @@ async function nativeStakingSSVStrategyFixture() { } else { fixture.ssvNetwork = await ethers.getContract("MockSSVNetwork"); const { governorAddr } = await getNamedAccounts(); - const { oethVault, weth, nativeStakingSSVStrategy } = fixture; + const { oethVault, nativeStakingSSVStrategy } = fixture; const sGovernor = await ethers.provider.getSigner(governorAddr); // Approve Strategy @@ -2100,7 +2054,7 @@ async function nativeStakingSSVStrategyFixture() { // Set as default await oethVault .connect(sGovernor) - .setAssetDefaultStrategy(weth.address, nativeStakingSSVStrategy.address); + .setDefaultStrategy(nativeStakingSSVStrategy.address); await nativeStakingSSVStrategy .connect(sGovernor) @@ -2173,7 +2127,7 @@ async function compoundingStakingSSVStrategyFixture() { } else { fixture.ssvNetwork = await ethers.getContract("MockSSVNetwork"); const { governorAddr, registratorAddr } = await getNamedAccounts(); - const { oethVault, weth } = fixture; + const { oethVault } = fixture; const sGovernor = await ethers.provider.getSigner(governorAddr); const sRegistrator = await ethers.provider.getSigner(registratorAddr); @@ -2185,10 +2139,7 @@ async function compoundingStakingSSVStrategyFixture() { // Set as default await oethVault .connect(sGovernor) - .setAssetDefaultStrategy( - weth.address, - compoundingStakingSSVStrategy.address - ); + .setDefaultStrategy(compoundingStakingSSVStrategy.address); await compoundingStakingSSVStrategy .connect(sGovernor) @@ -2296,17 +2247,11 @@ async function convexGeneralizedMetaForkedFixture( await fixture.vault .connect(sGovernor) - .setAssetDefaultStrategy( - fixture.usdt.address, - fixture.metaStrategy.address - ); + .setDefaultStrategy(fixture.metaStrategy.address); await fixture.vault .connect(sGovernor) - .setAssetDefaultStrategy( - fixture.usdc.address, - fixture.metaStrategy.address - ); + .setDefaultStrategy(fixture.metaStrategy.address); return fixture; } @@ -2372,9 +2317,7 @@ async function convexOETHMetaVaultFixture( .connect(timelock) .setNetOusdMintForStrategyThreshold(parseUnits("500", 21)); - await oethVault - .connect(timelock) - .setAssetDefaultStrategy(weth.address, addresses.zero); + await oethVault.connect(timelock).setDefaultStrategy(addresses.zero); // Impersonate the OETH Vault fixture.oethVaultSigner = await impersonateAndFund(oethVault.address); @@ -2591,15 +2534,27 @@ async function hackedVaultFixture() { /** * Instant rebase vault, for testing systems external to the vault */ -async function instantRebaseVaultFixture() { +async function instantRebaseVaultFixture(tokenName) { const fixture = await defaultFixture(); const { deploy } = deployments; const { governorAddr } = await getNamedAccounts(); const sGovernor = await ethers.provider.getSigner(governorAddr); + // Default to "usdc" if tokenName not provided + const name = tokenName ? tokenName.toLowerCase() : "usdc"; + let deployTokenAddress; + if (name === "usdc") { + deployTokenAddress = fixture.usdc.address; + } else if (name === "weth") { + deployTokenAddress = fixture.weth.address; + } else { + throw new Error(`Unsupported token name: ${name}`); + } + await deploy("MockVaultCoreInstantRebase", { from: governorAddr, + args: [deployTokenAddress], }); const instantRebase = await ethers.getContract("MockVaultCoreInstantRebase"); @@ -2681,7 +2636,7 @@ async function rebornFixture() { await deploy("Sanctum", { from: governorAddr, - args: [assetAddresses.USDS, vault.address], + args: [assetAddresses.USDC, vault.address], }); const sanctum = await ethers.getContract("Sanctum"); @@ -2718,7 +2673,7 @@ async function rebornFixture() { async function buybackFixture() { const fixture = await defaultFixture(); - const { ousd, oeth, oethVault, vault, weth, usds, josh, governor, timelock } = + const { ousd, oeth, oethVault, vault, weth, usdc, josh, governor, timelock } = fixture; const ousdBuybackProxy = await ethers.getContract("BuybackProxy"); @@ -2761,15 +2716,15 @@ async function buybackFixture() { // Load with funds to test swaps await setERC20TokenBalance(josh.address, weth, "10000"); - await setERC20TokenBalance(josh.address, usds, "10000"); + await setERC20TokenBalance(josh.address, usdc, "10000"); await weth.connect(josh).approve(oethVault.address, oethUnits("10000")); - await usds.connect(josh).approve(vault.address, ousdUnits("10000")); + await usdc.connect(josh).approve(vault.address, usdcUnits("10000")); // Mint & transfer oToken await oethVault.connect(josh).mint(weth.address, oethUnits("1.23"), "0"); await oeth.connect(josh).transfer(oethBuyback.address, oethUnits("1.1")); - await vault.connect(josh).mint(usds.address, oethUnits("1231"), "0"); + await vault.connect(josh).mint(usdc.address, usdcUnits("1231"), "0"); await ousd.connect(josh).transfer(ousdBuyback.address, oethUnits("1100")); await setERC20TokenBalance(armBuyback.address, weth, "100"); @@ -2782,9 +2737,9 @@ async function buybackFixture() { fixture.cvxLocker = await ethers.getContract("MockCVXLocker"); // Mint some OUSD - await usds.connect(josh).mint(ousdUnits("3000")); - await usds.connect(josh).approve(vault.address, ousdUnits("3000")); - await vault.connect(josh).mint(usds.address, ousdUnits("3000"), "0"); + await usdc.connect(josh).mint(usdcUnits("3000")); + await usdc.connect(josh).approve(vault.address, usdcUnits("3000")); + await vault.connect(josh).mint(usdc.address, usdcUnits("3000"), "0"); // Mint some OETH await weth.connect(josh).mint(oethUnits("3")); @@ -2817,7 +2772,6 @@ async function harvesterFixture() { vault, governor, harvester, - usdc, aaveStrategy, comp, aaveToken, @@ -2833,9 +2787,7 @@ async function harvesterFixture() { .setSupportedStrategy(aaveStrategy.address, true); // Add direct allocation of USDC to Aave - await vault - .connect(governor) - .setAssetDefaultStrategy(usdc.address, aaveStrategy.address); + await vault.connect(governor).setDefaultStrategy(aaveStrategy.address); // Let strategies hold some reward tokens await comp diff --git a/contracts/test/_hot-deploy.js b/contracts/test/_hot-deploy.js index 403b0bdd35..59e7c94151 100644 --- a/contracts/test/_hot-deploy.js +++ b/contracts/test/_hot-deploy.js @@ -12,7 +12,6 @@ const { oethPoolLpPID, } = require("../utils/constants"); const { replaceContractAt } = require("../utils/hardhat"); -const { impersonateAndFund } = require("../utils/signers"); const log = require("../utils/logger")("test:fixtures:hot-deploy"); @@ -125,14 +124,13 @@ async function hotDeployOption( const { isOethFixture } = config; const deployStrat = hotDeployOptions.includes("strategy"); - const deployVaultCore = hotDeployOptions.includes("vaultCore"); - const deployVaultAdmin = hotDeployOptions.includes("vaultAdmin"); + const deployVault = hotDeployOptions.includes("vault"); const deployHarvester = hotDeployOptions.includes("harvester"); const deployOracleRouter = hotDeployOptions.includes("oracleRouter"); const deployBuyback = hotDeployOptions.includes("buyback"); log(`Running fixture hot deployment w/ config; isOethFixture:${isOethFixture} strategy:${!!deployStrat} - vaultCore:${!!deployVaultCore} vaultAdmin:${!!deployVaultAdmin} harvester:${!!deployHarvester}`); + vault:${!!deployVault} harvester:${!!deployHarvester}`); if (deployStrat) { if (fixtureName === "balancerREthFixture") { @@ -162,13 +160,8 @@ async function hotDeployOption( } } - if (deployVaultCore || deployVaultAdmin) { - await hotDeployVaultAdmin( - fixture, - deployVaultAdmin, - deployVaultCore, - isOethFixture - ); + if (deployVault) { + await hotDeployVault(fixture, isOethFixture); } if (deployHarvester) { await hotDeployHarvester(fixture, isOethFixture); @@ -182,66 +175,33 @@ async function hotDeployOption( } } -async function hotDeployVaultAdmin( - fixture, - deployVaultAdmin, - deployVaultCore, - isOeth -) { +async function hotDeployVault(fixture, isOeth) { const { deploy } = deployments; const vaultProxyName = `${isOeth ? "OETH" : ""}VaultProxy`; - const vaultCoreName = `${isOeth ? "OETH" : ""}VaultCore`; - const vaultAdminName = `${isOeth ? "OETH" : ""}VaultAdmin`; - const vaultVariableName = `${isOeth ? "oethVault" : "vault"}`; + const vaultName = `${isOeth ? "OETH" : "OUSD"}Vault`; const cVaultProxy = await ethers.getContract(vaultProxyName); - if (deployVaultAdmin) { - log(`Deploying new ${vaultAdminName} implementation`); - - // deploy this contract that exposes internal function - await deploy(vaultAdminName, { - from: addresses.mainnet.Timelock, // doesn't matter which address deploys it - contract: vaultAdminName, - args: isOeth ? [fixture.weth.address] : [], - }); - - const implementation = await ethers.getContract(vaultAdminName); - const cVault = await ethers.getContractAt( - vaultCoreName, - cVaultProxy.address - ); - // TODO: this might be faster by replacing bytecode of existing implementation contract - const signerTimelock = await impersonateAndFund(addresses.mainnet.Timelock); - await cVault.connect(signerTimelock).setAdminImpl(implementation.address); - - fixture[vaultVariableName] = await ethers.getContractAt( - "IVault", - cVaultProxy.address - ); - } - if (deployVaultCore) { - log(`Deploying new ${vaultCoreName} implementation`); - // deploy this contract that exposes internal function - await deploy(vaultCoreName, { - from: addresses.mainnet.Timelock, // doesn't matter which address deploys it - contract: vaultCoreName, - args: isOeth ? [fixture.weth.address] : [], - }); - const implementation = await ethers.getContract(vaultCoreName); + log(`Deploying new ${vaultName} implementation`); + // deploy this contract that exposes internal function + await deploy(vaultName, { + from: addresses.mainnet.Timelock, // doesn't matter which address deploys it + contract: vaultName, + args: isOeth ? [fixture.weth.address] : [], + }); + const implementation = await ethers.getContract(vaultName); - const cVault = await ethers.getContractAt( - "InitializeGovernedUpgradeabilityProxy", - cVaultProxy.address - ); - const liveImplContractAddress = await cVault.implementation(); + const cVault = await ethers.getContractAt( + "InitializeGovernedUpgradeabilityProxy", + cVaultProxy.address + ); + const liveImplContractAddress = await cVault.implementation(); - log( - `Replacing implementation at ${liveImplContractAddress} with the fresh bytecode` - ); + log( + `Replacing implementation at ${liveImplContractAddress} with the fresh bytecode` + ); - await replaceContractAt(liveImplContractAddress, implementation); - } + await replaceContractAt(liveImplContractAddress, implementation); } async function hotDeployHarvester(fixture, forOETH) { diff --git a/contracts/test/_metastrategies-fixtures.js b/contracts/test/_metastrategies-fixtures.js index 119a0860c1..2f1bf7bb85 100644 --- a/contracts/test/_metastrategies-fixtures.js +++ b/contracts/test/_metastrategies-fixtures.js @@ -21,17 +21,10 @@ async function withDefaultOUSDMetapoolStrategiesSet() { const { vault, timelock, dai, usdt, usdc, OUSDmetaStrategy, daniel } = fixture; - await vault - .connect(timelock) - .setAssetDefaultStrategy(dai.address, OUSDmetaStrategy.address); + await vault.connect(timelock).setDefaultStrategy(OUSDmetaStrategy.address); - await vault - .connect(timelock) - .setAssetDefaultStrategy(usdt.address, OUSDmetaStrategy.address); - - await vault - .connect(timelock) - .setAssetDefaultStrategy(usdc.address, OUSDmetaStrategy.address); + await vault.connect(timelock).setDefaultStrategy(OUSDmetaStrategy.address); + await vault.connect(timelock).setDefaultStrategy(OUSDmetaStrategy.address); fixture.cvxRewardPool = await ethers.getContractAt( "IRewardStaking", diff --git a/contracts/test/beacon/beaconConsolidation.mainnet.fork-test.js b/contracts/test/beacon/beaconConsolidation.mainnet.fork-test.js deleted file mode 100644 index fe218604fb..0000000000 --- a/contracts/test/beacon/beaconConsolidation.mainnet.fork-test.js +++ /dev/null @@ -1,35 +0,0 @@ -const { expect } = require("chai"); -const { createFixtureLoader, beaconChainFixture } = require("../_fixture"); - -const loadFixture = createFixtureLoader(beaconChainFixture); - -describe("ForkTest: Beacon Consolidation", function () { - this.timeout(0); - - let fixture; - beforeEach(async () => { - fixture = await loadFixture(); - }); - - it("Should get consolidation fee", async () => { - const { beaconConsolidation } = fixture; - - const fee = await beaconConsolidation.fee(); - expect(fee).to.be.gt(0); - expect(fee).to.be.lt(10); - }); - - it("Should request consolidation of validators", async () => { - const { beaconConsolidation, beaconConsolidationReplaced } = fixture; - - // These are two sweeping validators - const source = - "0xa31b5e5d655a06d849a36e5b03f1b9e647f911f38857c2a263973fba90f61b528173fb7a7cddd63dbe7e6604e7d61c87"; - const target = - "0xa258246e1217568a751670447879b7af5d6df585c59a15ebf0380f276069eadb11f30dea77cfb7357447dc24517be560"; - await beaconConsolidation.request(source, target); - - expect(await beaconConsolidationReplaced.lastSource()).to.equal(source); - expect(await beaconConsolidationReplaced.lastTarget()).to.equal(target); - }); -}); diff --git a/contracts/test/behaviour/harvester.js b/contracts/test/behaviour/harvester.js index dee9c505ac..12c080e32d 100644 --- a/contracts/test/behaviour/harvester.js +++ b/contracts/test/behaviour/harvester.js @@ -39,7 +39,7 @@ const { MAX_UINT256 } = require("../../utils/constants"); })); */ const shouldBehaveLikeHarvester = (context) => { - describe("Harvest behaviour", () => { + describe.skip("Harvest behaviour", () => { async function _checkBalancesPostHarvesting(harvestFn, strategies) { const { harvester } = context(); @@ -94,7 +94,7 @@ const shouldBehaveLikeHarvester = (context) => { }); }); - describe("RewardTokenConfig", () => { + describe.skip("RewardTokenConfig", () => { it("Should only allow valid Uniswap V2 path", async () => { const { harvester, crv, usdt, governor, uniswapRouter } = context(); @@ -409,8 +409,8 @@ const shouldBehaveLikeHarvester = (context) => { ).to.be.revertedWith("InvalidHarvestRewardBps"); }); - it("Should revert for unsupported tokens", async () => { - const { harvester, ousd, governor, uniswapRouter } = context(); + it.skip("Should revert for unsupported tokens", async () => { + const { harvester, governor, uniswapRouter, usdc } = context(); const config = { allowedSlippageBps: 133, @@ -424,7 +424,7 @@ const shouldBehaveLikeHarvester = (context) => { await expect( harvester .connect(governor) - .setRewardTokenConfig(ousd.address, config, []) + .setRewardTokenConfig(usdc.address, config, []) ).to.be.revertedWith("Asset not available"); }); @@ -448,7 +448,7 @@ const shouldBehaveLikeHarvester = (context) => { }); }); - describe("Swap", () => { + describe.skip("Swap", () => { async function _swapWithRouter(swapRouterConfig, swapData) { const { harvester, @@ -900,7 +900,7 @@ const shouldBehaveLikeHarvester = (context) => { }); }); - describe("Admin function", () => { + describe.skip("Admin function", () => { it("Should only allow governor to change RewardProceedsAddress", async () => { const { harvester, governor, daniel, strategist } = context(); diff --git a/contracts/test/behaviour/sfcStakingStrategy.js b/contracts/test/behaviour/sfcStakingStrategy.js index 4f2eaaa083..e2cd87e80d 100644 --- a/contracts/test/behaviour/sfcStakingStrategy.js +++ b/contracts/test/behaviour/sfcStakingStrategy.js @@ -31,13 +31,8 @@ const MIN_WITHDRAWAL_EPOCH_ADVANCE = 4; const shouldBehaveLikeASFCStakingStrategy = (context) => { describe("Initial setup", function () { it("Should verify the initial state", async () => { - const { - sonicStakingStrategy, - addresses, - oSonicVault, - testValidatorIds, - wS, - } = await context(); + const { sonicStakingStrategy, addresses, oSonicVault, testValidatorIds } = + await context(); expect(await sonicStakingStrategy.wrappedSonic()).to.equal( addresses.wS, "Incorrect wrapped sonic address set" @@ -77,16 +72,6 @@ const shouldBehaveLikeASFCStakingStrategy = (context) => { expect( (await sonicStakingStrategy.getRewardTokenAddresses()).length ).to.equal(0, "Incorrectly configured Reward Token Addresses"); - - expect(await oSonicVault.priceProvider()).to.not.equal( - AddressZero, - "Price provider address not set" - ); - - expect(await oSonicVault.priceUnitMint(wS.address)).to.equal( - oethUnits("1"), - "not expected PriceUnitMint" - ); }); }); diff --git a/contracts/test/behaviour/ssvStrategy.js b/contracts/test/behaviour/ssvStrategy.js index a7aa22b2ed..10a8e92dac 100644 --- a/contracts/test/behaviour/ssvStrategy.js +++ b/contracts/test/behaviour/ssvStrategy.js @@ -670,12 +670,12 @@ const shouldBehaveLikeAnSsvStrategy = (context) => { josh, nativeStakingSSVStrategy, nativeStakingFeeAccumulator, - oethFixedRateDripper, + oethFixedRateDripperProxy, weth, validatorRegistrator, } = await context(); const dripperWethBefore = await weth.balanceOf( - oethFixedRateDripper.address + oethFixedRateDripperProxy.address ); const strategyBalanceBefore = await nativeStakingSSVStrategy.checkBalance( weth.address @@ -712,15 +712,14 @@ const shouldBehaveLikeAnSsvStrategy = (context) => { nativeStakingSSVStrategy.address, weth.address, executionRewards.add(consensusRewards), - oethFixedRateDripper.address + oethFixedRateDripperProxy.address ); - // check balances after expect( await nativeStakingSSVStrategy.checkBalance(weth.address) ).to.equal(strategyBalanceBefore, "checkBalance should not increase"); - expect(await weth.balanceOf(oethFixedRateDripper.address)).to.equal( + expect(await weth.balanceOf(oethFixedRateDripperProxy.address)).to.equal( dripperWethBefore.add(executionRewards).add(consensusRewards), "Vault WETH balance should increase" ); diff --git a/contracts/test/governance/oethb-timelock.base.fork-test.js b/contracts/test/governance/oethb-timelock.base.fork-test.js index 279e23db98..d54e410a34 100644 --- a/contracts/test/governance/oethb-timelock.base.fork-test.js +++ b/contracts/test/governance/oethb-timelock.base.fork-test.js @@ -1,8 +1,8 @@ const { createFixtureLoader } = require("../_fixture"); const { defaultBaseFixture } = require("../_fixture-base"); const { expect } = require("chai"); -const addresses = require("../../utils/addresses"); const { advanceTime, advanceBlocks } = require("../helpers"); +const { parseUnits } = require("ethers/lib/utils"); const baseFixture = createFixtureLoader(defaultBaseFixture); @@ -15,9 +15,10 @@ describe("ForkTest: OETHb Timelock", function () { it("Multisig can propose and execute on Timelock", async () => { const { guardian, timelock, oethbVault } = fixture; + const newBufferValue = parseUnits("0.1", 18); const calldata = oethbVault.interface.encodeFunctionData( - "setDripper(address)", - [addresses.dead] + "setVaultBuffer(uint256)", + [newBufferValue] ); const args = [ @@ -41,6 +42,6 @@ describe("ForkTest: OETHb Timelock", function () { await timelock.connect(guardian).executeBatch(...args); - expect(await oethbVault.dripper()).to.eq(addresses.dead); + expect(await oethbVault.vaultBuffer()).to.eq(newBufferValue); }); }); diff --git a/contracts/test/hacks/reborn.js b/contracts/test/hacks/reborn.js index b7a420dee0..07875c93fd 100644 --- a/contracts/test/hacks/reborn.js +++ b/contracts/test/hacks/reborn.js @@ -1,7 +1,7 @@ const { expect } = require("chai"); const { createFixtureLoader, rebornFixture } = require("../_fixture"); -const { isFork, usdsUnits, ousdUnits } = require("../helpers"); +const { isFork, usdcUnits, ousdUnits } = require("../helpers"); describe("Reborn Attack Protection", function () { if (isFork) { @@ -15,9 +15,9 @@ describe("Reborn Attack Protection", function () { fixture = await loadFixture(); }); it("Should correctly do accounting when reborn calls mint as different types of addresses", async function () { - const { usds, ousd, matt, rebornAddress, reborner, deployAndCall } = + const { usdc, ousd, matt, rebornAddress, reborner, deployAndCall } = fixture; - await usds.connect(matt).transfer(rebornAddress, usdsUnits("4")); + await usdc.connect(matt).transfer(rebornAddress, usdcUnits("4")); // call mint and self destruct (since account.code.length = 0) in constructor this is done // as an EOA from OUSD.sol's point of view await deployAndCall({ shouldAttack: true, shouldDestruct: true }); @@ -31,10 +31,11 @@ describe("Reborn Attack Protection", function () { expect(await ousd.nonRebasingSupply()).to.equal(ousdUnits("2")); }); - it("Should correctly do accounting when reborn calls burn as different types of addresses", async function () { - const { usds, ousd, matt, reborner, rebornAddress, deployAndCall } = + // Skipped as instant redeem is no longer supported for ousd + it.skip("Should correctly do accounting when reborn calls burn as different types of addresses", async function () { + const { usdc, ousd, matt, reborner, rebornAddress, deployAndCall } = fixture; - await usds.connect(matt).transfer(reborner.address, usdsUnits("4")); + await usdc.connect(matt).transfer(reborner.address, usdcUnits("4")); // call mint and self destruct (since account.code.length = 0) in constructor this is done // as an EOA from OUSD.sol's point of view await deployAndCall({ shouldAttack: true, shouldDestruct: true }); @@ -50,9 +51,9 @@ describe("Reborn Attack Protection", function () { }); it("Should correctly do accounting when reborn calls transfer as different types of addresses", async function () { - const { usds, ousd, matt, reborner, rebornAddress, deployAndCall } = + const { usdc, ousd, matt, reborner, rebornAddress, deployAndCall } = fixture; - await usds.connect(matt).transfer(reborner.address, usdsUnits("4")); + await usdc.connect(matt).transfer(reborner.address, usdcUnits("4")); // call mint and self destruct (since account.code.length = 0) in constructor this is done // as an EOA from OUSD.sol's point of view await deployAndCall({ shouldAttack: true, shouldDestruct: true }); @@ -74,10 +75,10 @@ describe("Reborn Attack Protection", function () { }); it("Should have correct balance even after recreating", async function () { - const { usds, matt, reborner, deployAndCall, ousd } = fixture; + const { usdc, matt, reborner, deployAndCall, ousd } = fixture; // Mint one OUSD and self-destruct - await usds.connect(matt).transfer(reborner.address, usdsUnits("4")); + await usdc.connect(matt).transfer(reborner.address, usdcUnits("4")); await deployAndCall({ shouldAttack: true, shouldDestruct: true }); await expect(reborner).to.have.a.balanceOf("1", ousd); diff --git a/contracts/test/hacks/reentrant.js b/contracts/test/hacks/reentrant.js deleted file mode 100644 index 6892d52886..0000000000 --- a/contracts/test/hacks/reentrant.js +++ /dev/null @@ -1,22 +0,0 @@ -const { expect } = require("chai"); - -const { createFixtureLoader, hackedVaultFixture } = require("../_fixture"); -const { isFork } = require("../helpers"); - -describe("Reentry Attack Protection", function () { - if (isFork) { - this.timeout(0); - } - - describe("Vault", function () { - const loadFixture = createFixtureLoader(hackedVaultFixture); - it("Should not allow malicious coin to reentrant call vault function", async function () { - const { evilDAI, vault } = await loadFixture(); - - // to see this fail just comment out the require in the nonReentrant() in Governable.sol - await expect(vault.mint(evilDAI.address, 10, 0)).to.be.revertedWith( - "Reentrant call" - ); - }); - }); -}); diff --git a/contracts/test/oracle/oracle.js b/contracts/test/oracle/oracle.js deleted file mode 100644 index 943f5b3723..0000000000 --- a/contracts/test/oracle/oracle.js +++ /dev/null @@ -1,90 +0,0 @@ -const { expect } = require("chai"); - -const { loadDefaultFixture } = require("../_fixture"); -const { ousdUnits, setOracleTokenPriceUsd } = require("../helpers"); - -/* - * Because the oracle code is so tightly intergrated into the vault, - * the actual tests for the core oracle features are just a part of the vault tests. - */ - -describe("Oracle", async () => { - let fixture; - beforeEach(async () => { - fixture = await loadDefaultFixture(); - }); - describe("Oracle read methods for DAPP", () => { - it("should read the mint price", async () => { - const { vault, usdt } = fixture; - const tests = [ - ["0.998", "0.998"], - ["1.00", "1.00"], - ["1.05", "1.00"], - ]; - for (const test of tests) { - const [actual, expectedRead] = test; - await setOracleTokenPriceUsd("USDT", actual); - expect(await vault.priceUnitMint(usdt.address)).to.equal( - ousdUnits(expectedRead) - ); - } - }); - - it("should fail below peg on the mint price", async () => { - const { vault, usdt } = fixture; - const prices = ["0.85", "0.997"]; - for (const price of prices) { - await setOracleTokenPriceUsd("USDT", price); - await expect(vault.priceUnitMint(usdt.address)).to.be.revertedWith( - "Asset price below peg" - ); - } - }); - - it("should read the redeem price", async () => { - const { vault, usdt } = fixture; - const tests = [ - ["0.80", "1.00"], - ["1.00", "1.00"], - ["1.05", "1.05"], - ]; - for (const test of tests) { - const [actual, expectedRead] = test; - await setOracleTokenPriceUsd("USDT", actual); - expect(await vault.priceUnitRedeem(usdt.address)).to.equal( - ousdUnits(expectedRead) - ); - } - }); - }); - - describe("Min/Max Drift", async () => { - const tests = [ - ["0.10", "Oracle: Price under min"], - ["0.699", "Oracle: Price under min"], - ["0.70"], - ["0.98"], - ["1.00"], - ["1.04"], - ["1.30"], - ["1.31", "Oracle: Price exceeds max"], - ["6.00", "Oracle: Price exceeds max"], - ]; - - for (const test of tests) { - const [price, expectedRevert] = test; - const revertLabel = expectedRevert ? "revert" : "not revert"; - const label = `Should ${revertLabel} because of drift at $${price}`; - it(label, async () => { - const { vault, usdt } = fixture; - await setOracleTokenPriceUsd("USDT", price); - if (expectedRevert) { - const tx = vault.priceUnitRedeem(usdt.address); - await expect(tx).to.be.revertedWith(expectedRevert); - } else { - await vault.priceUnitRedeem(usdt.address); - } - }); - } - }); -}); diff --git a/contracts/test/poolBooster/poolBooster.sonic.fork-test.js b/contracts/test/poolBooster/poolBooster.sonic.fork-test.js index 8257d9991f..c10119e858 100644 --- a/contracts/test/poolBooster/poolBooster.sonic.fork-test.js +++ b/contracts/test/poolBooster/poolBooster.sonic.fork-test.js @@ -646,7 +646,7 @@ describe("ForkTest: Pool Booster", function () { ], "PoolBoosterFactorySwapxSingle" ) - ).to.be.revertedWith("Invalid oSonic address"); + ).to.be.revertedWith("Invalid oToken address"); }); it("Can not deploy a factory with zero governor address", async () => { diff --git a/contracts/test/safe-modules/bridge-helper.base.fork-test.js b/contracts/test/safe-modules/bridge-helper.base.fork-test.js index d46b46a609..dd13824c32 100644 --- a/contracts/test/safe-modules/bridge-helper.base.fork-test.js +++ b/contracts/test/safe-modules/bridge-helper.base.fork-test.js @@ -41,7 +41,7 @@ describe("ForkTest: Bridge Helper Safe Module (Base)", function () { .bridgeWETHToEthereum(oethUnits("1")); }); - it("Should deposit wOETH for OETHb and redeem it for WETH", async () => { + it.skip("Should deposit wOETH for OETHb and redeem it for WETH", async () => { const { nick, _mintWETH, @@ -56,9 +56,9 @@ describe("ForkTest: Bridge Helper Safe Module (Base)", function () { } = fixture; // Make sure Vault has some WETH - _mintWETH(nick, "1"); - await weth.connect(nick).approve(oethbVault.address, oethUnits("1")); - await oethbVault.connect(nick).mint(weth.address, oethUnits("1"), "0"); + _mintWETH(nick, "10000"); + await weth.connect(nick).approve(oethbVault.address, oethUnits("10000")); + await oethbVault.connect(nick).mint(weth.address, oethUnits("10000"), "0"); // Update oracle price await woethStrategy.updateWOETHOraclePrice(); @@ -111,7 +111,7 @@ describe("ForkTest: Bridge Helper Safe Module (Base)", function () { ); }); - it("Should mint OETHb with WETH and redeem it for wOETH", async () => { + it.skip("Should mint OETHb with WETH and redeem it for wOETH", async () => { const { _mintWETH, oethbVault, diff --git a/contracts/test/safe-modules/bridge-helper.mainnet.fork-test.js b/contracts/test/safe-modules/bridge-helper.mainnet.fork-test.js index 7c4fb77fe3..e66bc9a134 100644 --- a/contracts/test/safe-modules/bridge-helper.mainnet.fork-test.js +++ b/contracts/test/safe-modules/bridge-helper.mainnet.fork-test.js @@ -5,7 +5,7 @@ const { const { oethUnits } = require("../helpers"); const { expect } = require("chai"); const addresses = require("../../utils/addresses"); -const { impersonateAndFund } = require("../../utils/signers"); +//const { impersonateAndFund } = require("../../utils/signers"); const mainnetFixture = createFixtureLoader(bridgeHelperModuleFixture); @@ -178,27 +178,19 @@ describe("ForkTest: Bridge Helper Safe Module (Ethereum)", function () { ); }); + // Redeem has been deprecated for now, so skipping this test + /* it("Should unwrap WOETH and redeem it to WETH", async () => { - const { - woeth, - weth, - josh, - timelock, - safeSigner, - bridgeHelperModule, - oethVault, - } = fixture; + const { woeth, weth, josh, safeSigner, bridgeHelperModule, oethVault } = + fixture; await oethVault.connect(josh).rebase(); // Do a huge yield deposit to fund the Vault - await oethVault - .connect(timelock) - .setAssetDefaultStrategy(weth.address, addresses.zero); - await impersonateAndFund(josh.address, "3000"); - await weth.connect(josh).deposit({ value: oethUnits("2500") }); - await weth.connect(josh).approve(oethVault.address, oethUnits("2500")); - await oethVault.connect(josh).mint(weth.address, oethUnits("2000"), "0"); + await impersonateAndFund(josh.address, "10000"); + await weth.connect(josh).deposit({ value: oethUnits("9500") }); + await weth.connect(josh).approve(oethVault.address, oethUnits("9500")); + await oethVault.connect(josh).mint(weth.address, oethUnits("9000"), "0"); const woethAmount = oethUnits("1"); @@ -220,4 +212,5 @@ describe("ForkTest: Bridge Helper Safe Module (Ethereum)", function () { ); expect(woethBalanceAfter).to.eq(woethBalanceBefore.sub(woethAmount)); }); + */ }); diff --git a/contracts/test/safe-modules/bridge-helper.plume.fork-test.js b/contracts/test/safe-modules/bridge-helper.plume.fork-test.js index 3e1c58122c..41f5c35fd0 100644 --- a/contracts/test/safe-modules/bridge-helper.plume.fork-test.js +++ b/contracts/test/safe-modules/bridge-helper.plume.fork-test.js @@ -230,7 +230,7 @@ describe("ForkTest: Bridge Helper Safe Module (Plume)", function () { ); }); - it("Should mint OETHp with WETH and redeem it for wOETH", async () => { + it.skip("Should mint OETHp with WETH and redeem it for wOETH", async () => { const { _mintWETH, oethpVault, diff --git a/contracts/test/strategies/base/aerodrome-amo.base.fork-test.js b/contracts/test/strategies/base/aerodrome-amo.base.fork-test.js index dcadeb0b2e..a78a97bcde 100644 --- a/contracts/test/strategies/base/aerodrome-amo.base.fork-test.js +++ b/contracts/test/strategies/base/aerodrome-amo.base.fork-test.js @@ -260,6 +260,7 @@ describe("ForkTest: Aerodrome AMO Strategy (Base)", async function () { governor, strategist, rafael, + nick, aeroSwapRouter, aeroNftManager, harvester, @@ -275,6 +276,7 @@ describe("ForkTest: Aerodrome AMO Strategy (Base)", async function () { governor = fixture.governor; strategist = fixture.strategist; rafael = fixture.rafael; + nick = fixture.nick; aeroSwapRouter = fixture.aeroSwapRouter; aeroNftManager = fixture.aeroNftManager; oethbVaultSigner = await impersonateAndFund(oethbVault.address); @@ -1276,6 +1278,7 @@ describe("ForkTest: Aerodrome AMO Strategy (Base)", async function () { await mintAndDepositToStrategy({ amount: oethUnits("5"), returnTransaction: true, + depositALotBefore: false, }); const { value, direction } = await quoteAmountToSwapBeforeRebalance({ @@ -1403,7 +1406,7 @@ describe("ForkTest: Aerodrome AMO Strategy (Base)", async function () { const user = userOverride || rafael; amount = amount || oethUnits("5"); - const balance = weth.balanceOf(user.address); + const balance = await weth.balanceOf(user.address); if (balance < amount) { await setERC20TokenBalance(user.address, weth, amount + balance, hre); } @@ -1418,11 +1421,19 @@ describe("ForkTest: Aerodrome AMO Strategy (Base)", async function () { userOverride, amount, returnTransaction, + depositALotBefore = false, } = {}) => { const user = userOverride || rafael; amount = amount || oethUnits("5"); + // Deposit a lot of WETH into the vault + if (depositALotBefore) { + const _amount = oethUnits("5000"); + await setERC20TokenBalance(nick.address, weth, _amount, hre); + await weth.connect(nick).approve(oethbVault.address, _amount); + await oethbVault.connect(nick).mint(weth.address, _amount, _amount); + } - const balance = weth.balanceOf(user.address); + const balance = await weth.balanceOf(user.address); if (balance < amount) { await setERC20TokenBalance(user.address, weth, amount + balance, hre); } diff --git a/contracts/test/strategies/compoundingSSVStaking.js b/contracts/test/strategies/compoundingSSVStaking.js index 24e9a6982b..3a95d88a81 100644 --- a/contracts/test/strategies/compoundingSSVStaking.js +++ b/contracts/test/strategies/compoundingSSVStaking.js @@ -1568,6 +1568,24 @@ describe("Unit test: Compounding SSV Staking Strategy", function () { ).state ).to.equal(2, "Validator state not 2 (STAKED)"); }); + + it("Should fail removing a strategy with funds", async () => { + const { compoundingStakingSSVStrategy, oethVault, governor } = fixture; + + await stakeValidators(0, 1); + if ( + (await oethVault.defaultStrategy()) === + compoundingStakingSSVStrategy.address + ) { + await oethVault.connect(governor).setDefaultStrategy(zero); + } + + await expect( + oethVault + .connect(governor) + .removeStrategy(compoundingStakingSSVStrategy.address) + ).to.be.revertedWith("Strategy has funds"); + }); }); describe("Verify deposits", () => { diff --git a/contracts/test/strategies/convex.js b/contracts/test/strategies/convex.js index 640d87f10d..9f1da88d7d 100644 --- a/contracts/test/strategies/convex.js +++ b/contracts/test/strategies/convex.js @@ -2,7 +2,7 @@ const { expect } = require("chai"); const { createFixtureLoader, convexVaultFixture } = require("../_fixture"); const { - usdsUnits, + usdcUnits, ousdUnits, units, expectApproxSupply, @@ -14,32 +14,29 @@ describe("Convex Strategy", function () { this.timeout(0); } - let anna, - ousd, + let ousd, vault, governor, + strategist, crv, threePoolToken, convexStrategy, cvxBooster, - usdt, - usdc, - usds; + usdc; const mint = async (amount, asset) => { - await asset.connect(anna).mint(await units(amount, asset)); + await asset.connect(strategist).mint(await units(amount, asset)); await asset - .connect(anna) + .connect(strategist) .approve(vault.address, await units(amount, asset)); return await vault - .connect(anna) + .connect(strategist) .mint(asset.address, await units(amount, asset), 0); }; const loadFixture = createFixtureLoader(convexVaultFixture); beforeEach(async function () { const fixture = await loadFixture(); - anna = fixture.anna; vault = fixture.vault; ousd = fixture.ousd; governor = fixture.governor; @@ -47,61 +44,39 @@ describe("Convex Strategy", function () { threePoolToken = fixture.threePoolToken; convexStrategy = fixture.convexStrategy; cvxBooster = fixture.cvxBooster; - usdt = fixture.usdt; usdc = fixture.usdc; - usds = fixture.usds; + strategist = fixture.strategist; }); describe("Mint", function () { - it("Should stake USDT in Curve gauge via 3pool", async function () { - await expectApproxSupply(ousd, ousdUnits("200")); - await mint("30000.00", usdt); - await expectApproxSupply(ousd, ousdUnits("30200")); - await expect(anna).to.have.a.balanceOf("30000", ousd); - await expect(cvxBooster).has.an.approxBalanceOf("30000", threePoolToken); - }); - it("Should stake USDC in Curve gauge via 3pool", async function () { await expectApproxSupply(ousd, ousdUnits("200")); await mint("50000.00", usdc); await expectApproxSupply(ousd, ousdUnits("50200")); - await expect(anna).to.have.a.balanceOf("50000", ousd); - await expect(cvxBooster).has.an.approxBalanceOf("50000", threePoolToken); - }); - - it("Should use a minimum LP token amount when depositing USDT into 3pool", async function () { - await expect(mint("29000", usdt)).to.be.revertedWith( - "Slippage ruined your day" - ); - }); - - it("Should use a minimum LP token amount when depositing USDC into 3pool", async function () { - await expect(mint("29000", usdc)).to.be.revertedWith( - "Slippage ruined your day" - ); + await expect(strategist).to.have.a.balanceOf("50000", ousd); + await expect(cvxBooster).has.an.approxBalanceOf("50200", threePoolToken); }); }); describe("Redeem", function () { it("Should be able to unstake from gauge and return USDT", async function () { await expectApproxSupply(ousd, ousdUnits("200")); - await mint("10000.00", usds); await mint("10000.00", usdc); - await mint("10000.00", usdt); - await vault.connect(anna).redeem(ousdUnits("20000"), 0); - await expectApproxSupply(ousd, ousdUnits("10200")); + await vault.connect(governor).set; + await vault.connect(strategist).requestWithdrawal(ousdUnits("10000")); + await expectApproxSupply(ousd, ousdUnits("200")); }); }); describe("Utilities", function () { it("Should allow transfer of arbitrary token by Governor", async () => { - await usds.connect(anna).approve(vault.address, usdsUnits("8.0")); - await vault.connect(anna).mint(usds.address, usdsUnits("8.0"), 0); - // Anna sends her OUSD directly to Strategy + await usdc.connect(strategist).approve(vault.address, usdcUnits("8.0")); + await vault.connect(strategist).mint(usdc.address, usdcUnits("8.0"), 0); + // Strategist sends her OUSD directly to Strategy await ousd - .connect(anna) + .connect(strategist) .transfer(convexStrategy.address, ousdUnits("8.0")); - // Anna asks Governor for help + // Strategist asks Governor for help await convexStrategy .connect(governor) .transferToken(ousd.address, ousdUnits("8.0")); @@ -109,10 +84,10 @@ describe("Convex Strategy", function () { }); it("Should not allow transfer of arbitrary token by non-Governor", async () => { - // Naughty Anna + // Naughty Strategist await expect( convexStrategy - .connect(anna) + .connect(strategist) .transferToken(ousd.address, ousdUnits("8.0")) ).to.be.revertedWith("Caller is not the Governor"); }); diff --git a/contracts/test/strategies/curve-amo-ousd.mainnet.fork-test.js b/contracts/test/strategies/curve-amo-ousd.mainnet.fork-test.js index bb332e9f5b..f713fb8453 100644 --- a/contracts/test/strategies/curve-amo-ousd.mainnet.fork-test.js +++ b/contracts/test/strategies/curve-amo-ousd.mainnet.fork-test.js @@ -233,6 +233,7 @@ describe("Curve AMO OUSD strategy", function () { )} usdc to the pool` ); + await setERC20TokenBalance(nick.address, usdc, attackerusdcAmount, hre); await usdc.connect(nick).approve(curvePool.address, attackerusdcAmount); // Attacker adds a lot of usdc into the pool // prettier-ignore diff --git a/contracts/test/strategies/dripper.js b/contracts/test/strategies/dripper.js index 9584574918..9a6209c713 100644 --- a/contracts/test/strategies/dripper.js +++ b/contracts/test/strategies/dripper.js @@ -7,7 +7,7 @@ const { const { usdtUnits, advanceTime } = require("../helpers"); describe("Dripper", async () => { - let dripper, usdt, vault, ousd, governor, josh; + let dripper, usdt, vault, governor, josh; const loadFixture = createFixtureLoader(instantRebaseVaultFixture); beforeEach(async () => { @@ -15,7 +15,6 @@ describe("Dripper", async () => { dripper = fixture.dripper; usdt = fixture.usdt; vault = fixture.vault; - ousd = fixture.ousd; governor = fixture.governor; josh = fixture.josh; @@ -81,16 +80,6 @@ describe("Dripper", async () => { await expectApproxCollectOf(expected, dripper.collect); }); }); - describe("collectAndRebase()", async () => { - it("transfers funds to the vault and rebases", async () => { - const beforeRct = await ousd.rebasingCreditsPerToken(); - await dripper.connect(governor).setDripDuration("20000"); - await advanceTime(1000); - await expectApproxCollectOf("50", dripper.collectAndRebase); - const afterRct = await ousd.rebasingCreditsPerToken(); - expect(afterRct).to.be.lt(beforeRct); - }); - }); describe("Drip math", async () => { it("gives all funds if collect is after the duration end", async () => { await dripper.connect(governor).setDripDuration("20000"); diff --git a/contracts/test/strategies/sonic/swapx-amo.sonic.fork-test.js b/contracts/test/strategies/sonic/swapx-amo.sonic.fork-test.js index f6bb3b7760..0945b59fe2 100644 --- a/contracts/test/strategies/sonic/swapx-amo.sonic.fork-test.js +++ b/contracts/test/strategies/sonic/swapx-amo.sonic.fork-test.js @@ -112,7 +112,7 @@ describe("Sonic ForkTest: SwapX AMO Strategy", function () { describe("with wS in the vault", () => { const loadFixture = createFixtureLoader(swapXAMOFixture, { - wsMintAmount: 5000, + wsMintAmount: 5000000, depositToStrategy: false, balancePool: true, }); @@ -514,7 +514,7 @@ describe("Sonic ForkTest: SwapX AMO Strategy", function () { swapXAMOStrategy, wS, } = fixture; - const withdrawAmount = parseUnits("2000"); + const withdrawAmount = parseUnits("200"); const dataBeforeWithdraw = await snapData(); logSnapData( @@ -893,7 +893,7 @@ describe("Sonic ForkTest: SwapX AMO Strategy", function () { describe("with an insolvent vault", () => { const loadFixture = createFixtureLoader(swapXAMOFixture, { - wsMintAmount: 50000000, + wsMintAmount: 5000000, depositToStrategy: false, }); beforeEach(async () => { diff --git a/contracts/test/strategies/vault-value-checker.js b/contracts/test/strategies/vault-value-checker.js index 52f6a50531..5b6ecbab18 100644 --- a/contracts/test/strategies/vault-value-checker.js +++ b/contracts/test/strategies/vault-value-checker.js @@ -2,16 +2,17 @@ const { expect } = require("chai"); const { loadDefaultFixture } = require("../_fixture"); const { impersonateAndFund } = require("../../utils/signers"); +const { usdcUnits, ousdUnits } = require("../helpers"); describe("Check vault value", () => { - let vault, ousd, matt, usds, checker, vaultSigner; + let vault, ousd, matt, usdc, checker, vaultSigner; beforeEach(async () => { const fixture = await loadDefaultFixture(); vault = fixture.vault; ousd = fixture.ousd; matt = fixture.matt; - usds = fixture.usds; + usdc = fixture.usdc; checker = await ethers.getContract("VaultValueChecker"); vaultSigner = await ethers.getSigner(vault.address); await impersonateAndFund(vaultSigner.address); @@ -26,10 +27,10 @@ describe("Check vault value", () => { // Alter value if (vaultChange > 0) { - await usds.mintTo(vault.address, vaultChange); + await usdc.mintTo(vault.address, vaultChange); } else if (vaultChange < 0) { // transfer amount out of the vault - await usds + await usdc .connect(vaultSigner) .transfer(matt.address, vaultChange * -1, { gasPrice: 0 }); } @@ -77,58 +78,58 @@ describe("Check vault value", () => { it( "should succeed if vault gain was inside the allowed band", testChange({ - vaultChange: 200, - expectedProfit: 0, - profitVariance: 100, - supplyChange: 200, - expectedVaultChange: 200, - vaultChangeVariance: 100, + vaultChange: usdcUnits("2"), // In USDC, 6 decimals + expectedProfit: ousdUnits("0"), + profitVariance: ousdUnits("100"), + supplyChange: ousdUnits("2"), // In OUSD, 18 decimals + expectedVaultChange: ousdUnits("2"), + vaultChangeVariance: ousdUnits("100"), }) ); it( "should revert if vault gain less than allowed", testChange({ - vaultChange: 50, - expectedProfit: 125, - profitVariance: 25, - supplyChange: 2, - expectedVaultChange: 1, - vaultChangeVariance: 1, + vaultChange: usdcUnits("50"), + expectedProfit: ousdUnits("125"), + profitVariance: ousdUnits("25"), + supplyChange: ousdUnits("2"), + expectedVaultChange: ousdUnits("1"), + vaultChangeVariance: ousdUnits("1"), expectedRevert: "Profit too low", }) ); it( "should revert if vault gain more than allowed", testChange({ - vaultChange: 550, - expectedProfit: 500, - profitVariance: 50, - supplyChange: 2, - expectedVaultChange: 1, - vaultChangeVariance: 1, + vaultChange: usdcUnits("550"), + expectedProfit: ousdUnits("500"), + profitVariance: ousdUnits("50"), + supplyChange: ousdUnits("2"), + expectedVaultChange: ousdUnits("1"), + vaultChangeVariance: ousdUnits("1"), expectedRevert: "Vault value change too high", }) ); it( "should succeed if vault loss was inside the allowed band", testChange({ - vaultChange: -200, - expectedProfit: -200, - profitVariance: 100, - supplyChange: 0, - expectedVaultChange: -200, - vaultChangeVariance: 0, + vaultChange: usdcUnits("200").mul(-1), + expectedProfit: ousdUnits("200").mul(-1), + profitVariance: ousdUnits("100"), + supplyChange: ousdUnits("0"), + expectedVaultChange: ousdUnits("200").mul(-1), + vaultChangeVariance: ousdUnits("0"), }) ); it( "should revert if vault loss under allowed band", testChange({ - vaultChange: -400, - expectedProfit: -400, - profitVariance: 40, - supplyChange: 0, - expectedVaultChange: 0, - vaultChangeVariance: 100, + vaultChange: usdcUnits("40").mul(-1), + expectedProfit: ousdUnits("40").mul(-1), + profitVariance: ousdUnits("4"), + supplyChange: ousdUnits("0"), + expectedVaultChange: ousdUnits("0"), + vaultChangeVariance: ousdUnits("10"), expectedRevert: "Vault value change too low", }) ); @@ -136,12 +137,12 @@ describe("Check vault value", () => { it( "should revert if vault loss over allowed band", testChange({ - vaultChange: 100, - expectedProfit: 100, - profitVariance: 100, - supplyChange: 0, - expectedVaultChange: 0, - vaultChangeVariance: 50, + vaultChange: usdcUnits("100"), + expectedProfit: ousdUnits("100"), + profitVariance: ousdUnits("100"), + supplyChange: ousdUnits("0"), + expectedVaultChange: ousdUnits("0"), + vaultChangeVariance: ousdUnits("50"), expectedRevert: "Vault value change too high", }) ); diff --git a/contracts/test/token/ousd.js b/contracts/test/token/ousd.js index 6edf446b55..8ac1708298 100644 --- a/contracts/test/token/ousd.js +++ b/contracts/test/token/ousd.js @@ -6,7 +6,7 @@ const { } = require("../_fixture"); const { utils, BigNumber } = require("ethers"); -const { usdsUnits, ousdUnits, usdcUnits, isFork } = require("../helpers"); +const { ousdUnits, usdcUnits, isFork } = require("../helpers"); const zeroAddress = "0x0000000000000000000000000000000000000000"; @@ -718,23 +718,23 @@ describe("Token", function () { }); it("Should mint correct amounts on non-rebasing account without previously set creditsPerToken", async () => { - let { ousd, usds, vault, josh, mockNonRebasing } = fixture; + let { ousd, usdc, vault, josh, mockNonRebasing } = fixture; // Give contract 100 USDS from Josh - await usds + await usdc .connect(josh) - .transfer(mockNonRebasing.address, usdsUnits("100")); + .transfer(mockNonRebasing.address, usdcUnits("100")); await expect(mockNonRebasing).has.a.balanceOf("0", ousd); const totalSupplyBefore = await ousd.totalSupply(); await mockNonRebasing.approveFor( - usds.address, + usdc.address, vault.address, - usdsUnits("100") + usdcUnits("100") ); const tx = await mockNonRebasing.mintOusd( vault.address, - usds.address, - usdsUnits("50") + usdc.address, + usdcUnits("50") ); await expect(tx) .to.emit(ousd, "AccountRebasingDisabled") @@ -759,22 +759,22 @@ describe("Token", function () { }); it("Should mint correct amounts on non-rebasing account with previously set creditsPerToken", async () => { - let { ousd, usds, vault, matt, usdc, josh, mockNonRebasing } = fixture; + let { ousd, usdc, vault, matt, josh, mockNonRebasing } = fixture; // Give contract 100 USDS from Josh - await usds + await usdc .connect(josh) - .transfer(mockNonRebasing.address, usdsUnits("100")); + .transfer(mockNonRebasing.address, usdcUnits("100")); await expect(mockNonRebasing).has.a.balanceOf("0", ousd); const totalSupplyBefore = await ousd.totalSupply(); await mockNonRebasing.approveFor( - usds.address, + usdc.address, vault.address, - usdsUnits("100") + usdcUnits("100") ); await mockNonRebasing.mintOusd( vault.address, - usds.address, - usdsUnits("50") + usdc.address, + usdcUnits("50") ); expect(await ousd.totalSupply()).to.equal( totalSupplyBefore.add(ousdUnits("50")) @@ -793,8 +793,8 @@ describe("Token", function () { // Mint again await mockNonRebasing.mintOusd( vault.address, - usds.address, - usdsUnits("50") + usdc.address, + usdcUnits("50") ); expect(await ousd.totalSupply()).to.equal( // Note 200 additional from simulated yield @@ -817,22 +817,22 @@ describe("Token", function () { }); it("Should burn the correct amount for non-rebasing account", async () => { - let { ousd, usds, vault, matt, usdc, josh, mockNonRebasing } = fixture; + let { ousd, usdc, vault, matt, josh, mockNonRebasing } = fixture; // Give contract 100 USDS from Josh - await usds + await usdc .connect(josh) - .transfer(mockNonRebasing.address, usdsUnits("100")); + .transfer(mockNonRebasing.address, usdcUnits("100")); await expect(mockNonRebasing).has.a.balanceOf("0", ousd); const totalSupplyBefore = await ousd.totalSupply(); await mockNonRebasing.approveFor( - usds.address, + usdc.address, vault.address, - usdsUnits("100") + usdcUnits("100") ); await mockNonRebasing.mintOusd( vault.address, - usds.address, - usdsUnits("50") + usdc.address, + usdcUnits("50") ); await expect(await ousd.totalSupply()).to.equal( totalSupplyBefore.add(ousdUnits("50")) diff --git a/contracts/test/token/woeth.js b/contracts/test/token/woeth.js index aa33d78f86..b4447d8726 100644 --- a/contracts/test/token/woeth.js +++ b/contracts/test/token/woeth.js @@ -11,7 +11,7 @@ describe("WOETH", function () { if (isFork) { this.timeout(0); } - const loadFixture = createFixtureLoader(instantRebaseVaultFixture); + const loadFixture = createFixtureLoader(instantRebaseVaultFixture, "weth"); let oeth, weth, woeth, oethVault, usds, matt, josh, governor; beforeEach(async () => { diff --git a/contracts/test/token/wousd.js b/contracts/test/token/wousd.js index c2f7ba0fa1..fab72b4d40 100644 --- a/contracts/test/token/wousd.js +++ b/contracts/test/token/wousd.js @@ -4,13 +4,13 @@ const { createFixtureLoader, instantRebaseVaultFixture, } = require("../_fixture"); -const { ousdUnits, usdsUnits, isFork } = require("../helpers"); +const { ousdUnits, usdcUnits, isFork } = require("../helpers"); describe("WOUSD", function () { if (isFork) { this.timeout(0); } - let ousd, wousd, vault, usds, matt, josh, governor; + let ousd, wousd, vault, usdc, matt, josh, governor; const loadFixture = createFixtureLoader(instantRebaseVaultFixture); beforeEach(async () => { @@ -18,7 +18,7 @@ describe("WOUSD", function () { ousd = fixture.ousd; wousd = fixture.wousd; vault = fixture.vault; - usds = fixture.usds; + usdc = fixture.usdc; matt = fixture.matt; josh = fixture.josh; governor = fixture.governor; @@ -34,13 +34,15 @@ describe("WOUSD", function () { await increaseOUSDSupplyAndRebase(await ousd.totalSupply()); }); - const increaseOUSDSupplyAndRebase = async (usdsAmount) => { - await usds.connect(matt).transfer(vault.address, usdsAmount); + const increaseOUSDSupplyAndRebase = async (usdcAmount) => { + await usdc.mintTo(matt.address, usdcAmount.div(1e12)); + await usdc.connect(matt).transfer(vault.address, usdcAmount.div(1e12)); await vault.rebase(); }; describe("Funds in, Funds out", async () => { it("should deposit at the correct ratio", async () => { + console.log((await wousd.balanceOf(josh.address)).toString()); await wousd.connect(josh).deposit(ousdUnits("50"), josh.address); await expect(josh).to.have.a.balanceOf("75", wousd); await expect(josh).to.have.a.balanceOf("50", ousd); @@ -70,7 +72,7 @@ describe("WOUSD", function () { describe("Collects Rebase", async () => { it("should increase with an OUSD rebase", async () => { await expect(wousd).to.have.approxBalanceOf("100", ousd); - await usds.connect(josh).transfer(vault.address, usdsUnits("200")); + await usdc.connect(josh).transfer(vault.address, usdcUnits("200")); await vault.rebase(); await expect(wousd).to.have.approxBalanceOf("150", ousd); }); @@ -86,12 +88,12 @@ describe("WOUSD", function () { describe("Token recovery", async () => { it("should allow a governor to recover tokens", async () => { - await usds.connect(matt).transfer(wousd.address, usdsUnits("2")); - await expect(wousd).to.have.a.balanceOf("2", usds); - await expect(governor).to.have.a.balanceOf("1000", usds); - await wousd.connect(governor).transferToken(usds.address, usdsUnits("2")); - await expect(wousd).to.have.a.balanceOf("0", usds); - await expect(governor).to.have.a.balanceOf("1002", usds); + await usdc.connect(matt).transfer(wousd.address, usdcUnits("2")); + await expect(wousd).to.have.a.balanceOf("2", usdc); + await expect(governor).to.have.a.balanceOf("1000", usdc); + await wousd.connect(governor).transferToken(usdc.address, usdcUnits("2")); + await expect(wousd).to.have.a.balanceOf("0", usdc); + await expect(governor).to.have.a.balanceOf("1002", usdc); }); it("should not allow a governor to collect OUSD", async () => { await expect( diff --git a/contracts/test/vault/collateral-swaps.mainnet.fork-test.js b/contracts/test/vault/collateral-swaps.mainnet.fork-test.js deleted file mode 100644 index cd9a769814..0000000000 --- a/contracts/test/vault/collateral-swaps.mainnet.fork-test.js +++ /dev/null @@ -1,332 +0,0 @@ -const { expect } = require("chai"); -const { parseUnits, formatUnits } = require("ethers/lib/utils"); - -const { - createFixtureLoader, - defaultFixture, - ousdCollateralSwapFixture, -} = require("../_fixture"); -const { getIInchSwapData, recodeSwapData } = require("../../utils/1Inch"); -const { decimalsFor, isCI } = require("../helpers"); -const { resolveAsset } = require("../../utils/resolvers"); - -const log = require("../../utils/logger")("test:fork:swaps"); - -describe.skip("ForkTest: OUSD Vault", function () { - this.timeout(0); - - // Retry up to 3 times on CI - this.retries(isCI ? 3 : 0); - - let fixture; - - describe("post deployment", () => { - const loadFixture = createFixtureLoader(defaultFixture); - beforeEach(async () => { - fixture = await loadFixture(); - }); - - it("should have swapper set", async () => { - const { vault, swapper } = fixture; - - expect(await vault.swapper()).to.equal(swapper.address); - }); - it("assets should have allowed slippage", async () => { - const { vault, usds, usdc, usdt } = fixture; - - const assets = [usds, usdc, usdt]; - const expectedDecimals = [18, 6, 6]; - const expectedConversions = [0, 0, 0]; - const expectedSlippage = [25, 25, 25]; - - for (let i = 0; i < assets.length; i++) { - const config = await vault.getAssetConfig(assets[i].address); - - expect(config.decimals, `decimals ${i}`).to.equal(expectedDecimals[i]); - expect(config.isSupported, `isSupported ${i}`).to.be.true; - expect(config.unitConversion, `unitConversion ${i}`).to.be.equal( - expectedConversions[i] - ); - expect( - config.allowedOracleSlippageBps, - `allowedOracleSlippageBps ${i}` - ).to.equal(expectedSlippage[i]); - } - }); - }); - - describe("Collateral swaps (Happy paths)", async () => { - const loadFixture = createFixtureLoader(ousdCollateralSwapFixture); - beforeEach(async () => { - fixture = await loadFixture(); - }); - - const tests = [ - { - from: "USDS", - to: "USDT", - fromAmount: 1000000, - minToAssetAmount: 990000, - }, - { - from: "USDS", - to: "USDC", - fromAmount: 1000000, - minToAssetAmount: 999900, - slippage: 0.1, // Max 1Inch slippage - }, - { - from: "USDT", - to: "USDS", - fromAmount: 1000000, - minToAssetAmount: 998000, - }, - { - from: "USDT", - to: "USDC", - fromAmount: 1000000, - minToAssetAmount: 998000, - }, - { - from: "USDC", - to: "USDS", - fromAmount: 1000000, - minToAssetAmount: 999900, - slippage: 0.05, // Max 1Inch slippage - }, - { - from: "USDC", - to: "USDT", - fromAmount: 1000000, - minToAssetAmount: "990000", - slippage: 0.02, - approxFromBalance: true, - }, - ]; - for (const test of tests) { - it(`should be able to swap ${test.fromAmount} ${test.from} for a min of ${ - test.minToAssetAmount - } ${test.to} using ${test.protocols || "all"} protocols`, async () => { - const fromAsset = await resolveAsset(test.from); - const toAsset = await resolveAsset(test.to); - await assertSwap( - { - ...test, - fromAsset, - toAsset, - vault: fixture.vault, - }, - fixture - ); - }); - } - }); - - describe("Collateral swaps (Unhappy paths)", async () => { - const loadFixture = createFixtureLoader(ousdCollateralSwapFixture); - beforeEach(async () => { - fixture = await loadFixture(); - }); - - const tests = [ - { - error: "", - from: "USDS", - to: "USDC", - fromAmount: 100, - minToAssetAmount: 105, - }, - { - error: "From asset is not supported", - from: "WETH", - to: "USDT", - fromAmount: 20, - minToAssetAmount: 1, - }, - { - error: "To asset is not supported", - from: "USDS", - to: "WETH", - fromAmount: 20, - minToAssetAmount: 1, - }, - { - error: "Usds/insufficient-balance", - from: "USDS", - to: "USDC", - fromAmount: 30000000, - minToAssetAmount: 29000000, - }, - { - error: "SafeERC20: low-level call failed", - from: "USDT", - to: "USDC", - fromAmount: 50000000, - minToAssetAmount: 49900000, - }, - { - error: "ERC20: transfer amount exceeds balance", - from: "USDC", - to: "USDS", - fromAmount: 30000000, - minToAssetAmount: 29900000, - }, - ]; - - for (const test of tests) { - it(`should fail to swap ${test.fromAmount} ${test.from} for ${ - test.to - } using ${test.protocols || "all"} protocols: error ${ - test.error - }`, async () => { - const fromAsset = await resolveAsset(test.from); - const toAsset = await resolveAsset(test.to); - await assertFailedSwap( - { - ...test, - fromAsset, - toAsset, - vault: fixture.vault, - }, - fixture - ); - }); - } - }); -}); -const assertSwap = async ( - { - fromAsset, - toAsset, - fromAmount, - minToAssetAmount, - slippage, - protocols, - approxFromBalance, - vault, - }, - fixture -) => { - const { strategist, swapper } = fixture; - - const fromAssetDecimals = await decimalsFor(fromAsset); - fromAmount = await parseUnits(fromAmount.toString(), fromAssetDecimals); - const toAssetDecimals = await decimalsFor(toAsset); - minToAssetAmount = await parseUnits( - minToAssetAmount.toString(), - toAssetDecimals - ); - - const apiEncodedData = await getIInchSwapData({ - vault: vault, - fromAsset, - toAsset, - fromAmount, - slippage, - protocols, - }); - - // re-encode the 1Inch tx.data from their swap API to the executer data - const swapData = await recodeSwapData(apiEncodedData); - - const fromBalanceBefore = await fromAsset.balanceOf(vault.address); - log( - `from asset balance before ${formatUnits( - fromBalanceBefore, - fromAssetDecimals - )}` - ); - const toBalanceBefore = await toAsset.balanceOf(vault.address); - - const tx = vault - .connect(strategist) - .swapCollateral( - fromAsset.address, - toAsset.address, - fromAmount, - minToAssetAmount, - swapData - ); - - // Asset events - await expect(tx).to.emit(vault, "Swapped").withNamedArgs({ - _fromAsset: fromAsset.address, - _toAsset: toAsset.address, - _fromAssetAmount: fromAmount, - }); - await expect(tx) - .to.emit(fromAsset, "Transfer") - .withArgs(vault.address, swapper.address, fromAmount); - - // Asset balances - const fromBalanceAfter = await fromAsset.balanceOf(vault.address); - if (approxFromBalance) { - expect( - fromBalanceBefore.sub(fromBalanceAfter), - "from asset approx bal" - ).to.approxEqualTolerance(fromAmount, 0.01); - } else { - expect(fromBalanceBefore.sub(fromBalanceAfter), "from asset bal").to.eq( - fromAmount - ); - } - const toBalanceAfter = await toAsset.balanceOf(vault.address); - log( - `to assets purchased ${formatUnits( - toBalanceAfter.sub(toBalanceBefore), - toAssetDecimals - )}` - ); - const toAmount = toBalanceAfter.sub(toBalanceBefore); - expect(toAmount, "to asset bal").to.gt(minToAssetAmount); - log( - `swapped ${formatUnits(fromAmount, fromAssetDecimals)} for ${formatUnits( - toAmount, - toAssetDecimals - )}` - ); -}; -const assertFailedSwap = async ( - { - fromAsset, - toAsset, - fromAmount, - minToAssetAmount, - slippage, - protocols, - error, - vault, - }, - fixture -) => { - const { strategist } = fixture; - - const fromAssetDecimals = await decimalsFor(fromAsset); - fromAmount = await parseUnits(fromAmount.toString(), fromAssetDecimals); - const toAssetDecimals = await decimalsFor(toAsset); - minToAssetAmount = parseUnits(minToAssetAmount.toString(), toAssetDecimals); - - const apiEncodedData = await getIInchSwapData({ - vault, - fromAsset, - toAsset, - fromAmount, - slippage, - protocols, - }); - - // re-encode the 1Inch tx.data from their swap API to the executer data - const swapData = await recodeSwapData(apiEncodedData); - - const tx = vault - .connect(strategist) - .swapCollateral( - fromAsset.address, - toAsset.address, - fromAmount, - minToAssetAmount, - swapData - ); - - await expect(tx).to.be.revertedWith(error); -}; diff --git a/contracts/test/vault/compound.js b/contracts/test/vault/compound.js index ccebaca644..cdbb3cb342 100644 --- a/contracts/test/vault/compound.js +++ b/contracts/test/vault/compound.js @@ -8,15 +8,15 @@ const { ousdUnits, usdsUnits, usdcUnits, - usdtUnits, - tusdUnits, setOracleTokenPriceUsd, isFork, expectApproxSupply, } = require("../helpers"); -const addresses = require("../../utils/addresses"); +const { + increase, +} = require("@nomicfoundation/hardhat-network-helpers/dist/src/helpers/time"); -describe("Vault with Compound strategy", function () { +describe.skip("Vault with Compound strategy", function () { if (isFork) { this.timeout(0); } @@ -40,12 +40,12 @@ describe("Vault with Compound strategy", function () { }); it("Governor can call setPTokenAddress", async () => { - const { usds, ousd, matt, compoundStrategy } = fixture; + const { usdc, ousd, matt, compoundStrategy } = fixture; await expect( compoundStrategy .connect(matt) - .setPTokenAddress(ousd.address, usds.address) + .setPTokenAddress(ousd.address, usdc.address) ).to.be.revertedWith("Caller is not the Governor"); }); @@ -58,181 +58,105 @@ describe("Vault with Compound strategy", function () { }); it("Should allocate unallocated assets", async () => { - const { anna, governor, usds, usdc, usdt, tusd, vault, compoundStrategy } = - fixture; - - await usds.connect(anna).transfer(vault.address, usdsUnits("100")); - await usdc.connect(anna).transfer(vault.address, usdcUnits("200")); - await usdt.connect(anna).transfer(vault.address, usdtUnits("300")); - - await tusd.connect(anna).mint(ousdUnits("1000.0")); - await tusd.connect(anna).transfer(vault.address, tusdUnits("400")); + const { anna, governor, usdc, vault, compoundStrategy } = fixture; + await vault.connect(governor).setDefaultStrategy(compoundStrategy.address); + await usdc.connect(anna).transfer(vault.address, usdcUnits("100")); await expect(vault.connect(governor).allocate()) .to.emit(vault, "AssetAllocated") - .withArgs(usds.address, compoundStrategy.address, usdsUnits("300")) - .to.emit(vault, "AssetAllocated") - .withArgs(usdc.address, compoundStrategy.address, usdcUnits("200")) - .to.emit(vault, "AssetAllocated") - .withArgs(usdt.address, compoundStrategy.address, usdcUnits("300")); - /* - TODO: There does not appear to be any support for .withoutArgs to verify - that this event doesn't get emitted. - .to.emit(vault, "AssetAllocated") - .withoutArgs(usdt.address, compoundStrategy.address, tusdUnits("400")); - */ + .withArgs(usdc.address, compoundStrategy.address, usdcUnits("300")); - // Note compoundVaultFixture sets up with 200 USDS already in the Strategy + // Note compoundVaultFixture sets up with 200 USDC already in the Strategy // 200 + 100 = 300 - await expect( - await compoundStrategy.checkBalance(usds.address) - ).to.approxEqual(usdsUnits("300")); await expect( await compoundStrategy.checkBalance(usdc.address) - ).to.approxEqual(usdcUnits("200")); - await expect( - await compoundStrategy.checkBalance(usdt.address) - ).to.approxEqual(usdtUnits("300")); - - // Strategy doesn't support TUSD - // Vault balance for TUSD should remain unchanged - expect(await tusd.balanceOf(vault.address)).to.equal(tusdUnits("400")); + ).to.approxEqual(usdcUnits("300")); }); it("Should correctly handle a deposit of USDC (6 decimals)", async function () { const { anna, ousd, usdc, vault } = fixture; await expect(anna).has.a.balanceOf("0", ousd); - // The mint process maxes out at a 1.0 price - await setOracleTokenPriceUsd("USDC", "1.25"); await usdc.connect(anna).approve(vault.address, usdcUnits("50")); - await vault.connect(anna).mint(usdc.address, usdcUnits("50"), 0); + await vault + .connect(anna) + .mint(usdc.address, usdcUnits("50"), ousdUnits("50")); await expect(anna).has.a.balanceOf("50", ousd); }); it("Should allow withdrawals", async () => { - const { anna, compoundStrategy, ousd, usdc, vault, governor } = fixture; - - await expect(anna).has.a.balanceOf("1000.00", usdc); - await usdc.connect(anna).approve(vault.address, usdcUnits("50.0")); - await vault.connect(anna).mint(usdc.address, usdcUnits("50.0"), 0); - await expect(anna).has.a.balanceOf("50.00", ousd); - - await vault.connect(governor).allocate(); - - // Verify the deposit went to Compound - expect(await compoundStrategy.checkBalance(usdc.address)).to.approxEqual( - usdcUnits("50.0") - ); - - // Note Anna will have slightly less than 50 due to deposit to Compound - // according to the MockCToken implementation - await ousd.connect(anna).approve(vault.address, ousdUnits("40.0")); - await vault.connect(anna).redeem(ousdUnits("40.0"), 0); + const { strategist, ousd, usdc, vault } = fixture; - await expect(anna).has.an.approxBalanceOf("10", ousd); - // Vault has 200 USDS and 50 USDC, 50/250 * 40 USDC will come back - await expect(anna).has.an.approxBalanceOf("958", usdc); - }); - - it("Should calculate the balance correctly with USDS in strategy", async () => { - const { usds, vault, josh, compoundStrategy, governor } = fixture; - - expect(await vault.totalValue()).to.approxEqual( - utils.parseUnits("200", 18) - ); - - // Josh deposits USDS, 18 decimals - await usds.connect(josh).approve(vault.address, usdsUnits("22.0")); - await vault.connect(josh).mint(usds.address, usdsUnits("22.0"), 0); - - await vault.connect(governor).allocate(); + await expect(strategist).has.a.balanceOf("1000.00", usdc); + await usdc.connect(strategist).approve(vault.address, usdcUnits("50.0")); + await vault.connect(strategist).mint(usdc.address, usdcUnits("50.0"), 0); + await expect(strategist).has.a.balanceOf("50.00", ousd); - // Josh had 1000 USDS but used 100 USDS to mint OUSD in the fixture - await expect(josh).has.an.approxBalanceOf("878.0", usds, "Josh has less"); + await ousd.connect(strategist).approve(vault.address, ousdUnits("40.0")); + await vault.connect(strategist).requestWithdrawal(ousdUnits("40.0")); + await increase(60 * 10); // Advance 10 minutes + await vault.connect(strategist).claimWithdrawal(0); // Assumes request ID is 0 - // Verify the deposit went to Compound (as well as existing Vault assets) - expect(await compoundStrategy.checkBalance(usds.address)).to.approxEqual( - usdsUnits("222") - ); - - expect(await vault.totalValue()).to.approxEqual( - utils.parseUnits("222", 18) - ); + await expect(strategist).has.an.balanceOf("10", ousd); + await expect(strategist).has.an.balanceOf("990.0", usdc); }); it("Should calculate the balance correctly with USDC in strategy", async () => { - const { usdc, vault, matt, compoundStrategy, governor } = fixture; + const { usdc, vault, josh, compoundStrategy, governor } = fixture; expect(await vault.totalValue()).to.approxEqual( utils.parseUnits("200", 18) ); - // Matt deposits USDC, 6 decimals - await usdc.connect(matt).approve(vault.address, usdcUnits("8.0")); - await vault.connect(matt).mint(usdc.address, usdcUnits("8.0"), 0); + // Josh deposits USDC, 6 decimals + await usdc.connect(josh).approve(vault.address, usdcUnits("22.0")); + await vault.connect(josh).mint(usdc.address, usdcUnits("22.0"), 0); + await vault.connect(governor).setDefaultStrategy(compoundStrategy.address); await vault.connect(governor).allocate(); - // Verify the deposit went to Compound - await expect(matt).has.an.approxBalanceOf("992.0", usdc, "Matt has less"); + // Josh had 1000 USDC but used 100 USDC to mint OUSD in the fixture + await expect(josh).has.an.approxBalanceOf("878.0", usdc, "Josh has less"); + // Verify the deposit went to Compound (as well as existing Vault assets) expect(await compoundStrategy.checkBalance(usdc.address)).to.approxEqual( - usdcUnits("8.0") + usdcUnits("222") ); expect(await vault.totalValue()).to.approxEqual( - utils.parseUnits("208", 18) + utils.parseUnits("222", 18) ); }); - it("Should calculate the balance correct with TUSD in Vault and USDS, USDC, USDT in Compound strategy", async () => { - const { - tusd, - usdc, - usds, - usdt, - vault, - matt, - josh, - anna, - governor, - compoundStrategy, - } = fixture; + it("Should calculate the balance correct with USDC in Vault and USDC in Compound strategy", async () => { + const { usdc, vault, matt, anna, governor, compoundStrategy } = fixture; expect(await vault.totalValue()).to.approxEqual( utils.parseUnits("200", 18) ); - // Josh deposits USDS, 18 decimals - await usds.connect(josh).approve(vault.address, usdsUnits("22.0")); - await vault.connect(josh).mint(usds.address, usdsUnits("22.0"), 0); + await vault.connect(governor).setDefaultStrategy(compoundStrategy.address); await vault.connect(governor).allocate(); // Existing 200 also ends up in strategy due to allocate call - expect(await compoundStrategy.checkBalance(usds.address)).to.approxEqual( - usdsUnits("222") + expect(await compoundStrategy.checkBalance(usdc.address)).to.approxEqual( + usdcUnits("200") ); // Matt deposits USDC, 6 decimals await usdc.connect(matt).approve(vault.address, usdcUnits("8.0")); await vault.connect(matt).mint(usdc.address, usdcUnits("8.0"), 0); await vault.connect(governor).allocate(); expect(await compoundStrategy.checkBalance(usdc.address)).to.approxEqual( - usdcUnits("8.0") + usdcUnits("208.0") ); - // Anna deposits USDT, 6 decimals - await usdt.connect(anna).approve(vault.address, usdtUnits("10.0")); - await vault.connect(anna).mint(usdt.address, usdtUnits("10.0"), 0); - await vault.connect(governor).allocate(); - expect(await compoundStrategy.checkBalance(usdt.address)).to.approxEqual( - usdtUnits("10.0") + // Anna deposits USDC that will stay in the Vault, 6 decimals + await usdc.connect(anna).approve(vault.address, usdcUnits("10.0")); + await vault.connect(anna).mint(usdc.address, usdcUnits("10.0"), 0); + expect(await usdc.balanceOf(vault.address)).to.approxEqual( + usdcUnits("10.0") ); - // Matt deposits TUSD, 18 decimals - await tusd.connect(matt).mint(ousdUnits("100.0")); - await tusd.connect(matt).approve(vault.address, tusdUnits("9.0")); - await vault.connect(matt).mint(tusd.address, tusdUnits("9.0"), 0); expect(await vault.totalValue()).to.approxEqual( - utils.parseUnits("249", 18) + utils.parseUnits("218", 18) ); }); @@ -265,8 +189,7 @@ describe("Vault with Compound strategy", function () { }); it("Should correctly withdrawAll all assets in Compound strategy", async () => { - const { usdc, vault, matt, josh, usds, compoundStrategy, governor } = - fixture; + const { usdc, vault, matt, compoundStrategy, governor } = fixture; expect(await vault.totalValue()).to.approxEqual( utils.parseUnits("200", 18) @@ -276,112 +199,61 @@ describe("Vault with Compound strategy", function () { await usdc.connect(matt).approve(vault.address, usdcUnits("8.0")); await vault.connect(matt).mint(usdc.address, usdcUnits("8.0"), 0); + await vault.connect(governor).setDefaultStrategy(compoundStrategy.address); await vault.connect(governor).allocate(); expect(await compoundStrategy.checkBalance(usdc.address)).to.approxEqual( - usdcUnits("8") + usdcUnits("208.0") ); - expect(await vault.totalValue()).to.approxEqual( utils.parseUnits("208", 18) ); - - await usds.connect(josh).approve(vault.address, usdsUnits("22.0")); - await vault.connect(josh).mint(usds.address, usdsUnits("22.0"), 0); - - await vault.connect(governor).allocate(); - - expect(await compoundStrategy.checkBalance(usds.address)).to.approxEqual( - usdsUnits("222") - ); - - expect(await vault.totalValue()).to.approxEqual( - utils.parseUnits("230", 18) - ); - await compoundStrategy.connect(governor).withdrawAll(); // There should be no USDS or USDC left in compound strategy expect(await compoundStrategy.checkBalance(usdc.address)).to.equal(0); - expect(await compoundStrategy.checkBalance(usds.address)).to.equal(0); // Vault value should remain the same because the liquidattion sent the // assets back to the vault expect(await vault.totalValue()).to.approxEqual( - utils.parseUnits("230", 18) + utils.parseUnits("208", 18) ); }); it("Should withdrawAll assets in Strategy and return them to Vault on removal", async () => { - const { - usdt, - usdc, - comp, - vault, - matt, - josh, - usds, - harvester, - compoundStrategy, - governor, - } = fixture; + const { usdc, vault, matt, compoundStrategy, governor } = fixture; expect(await vault.totalValue()).to.approxEqual( utils.parseUnits("200", 18) ); - const mockUniswapRouter = await ethers.getContract("MockUniswapRouter"); - - await harvester.connect(governor).setRewardTokenConfig( - comp.address, // reward token - { - allowedSlippageBps: 300, - harvestRewardBps: 0, - swapPlatformAddr: mockUniswapRouter.address, - doSwapRewardToken: true, - swapPlatform: 0, - liquidationLimit: 0, - }, - utils.defaultAbiCoder.encode( - ["address[]"], - [[comp.address, usdt.address]] - ) - ); // Matt deposits USDC, 6 decimals await usdc.connect(matt).approve(vault.address, usdcUnits("8.0")); await vault.connect(matt).mint(usdc.address, usdcUnits("8.0"), 0); + await vault.connect(governor).setDefaultStrategy(compoundStrategy.address); await vault.connect(governor).allocate(); expect(await compoundStrategy.checkBalance(usdc.address)).to.approxEqual( - usdcUnits("8.0") + usdcUnits("208.0") ); - await usds.connect(josh).approve(vault.address, usdsUnits("22.0")); - await vault.connect(josh).mint(usds.address, usdsUnits("22.0"), 0); expect(await vault.totalValue()).to.approxEqual( - utils.parseUnits("230", 18) + utils.parseUnits("208", 18) ); - await expect(await vault.getStrategyCount()).to.equal(1); + expect(await vault.getStrategyCount()).to.equal(1); await vault .connect(governor) - .setAssetDefaultStrategy(usdt.address, addresses.zero); - await vault - .connect(governor) - .setAssetDefaultStrategy(usdc.address, addresses.zero); - await vault - .connect(governor) - .setAssetDefaultStrategy(usds.address, addresses.zero); + .setDefaultStrategy("0x0000000000000000000000000000000000000000"); await vault.connect(governor).removeStrategy(compoundStrategy.address); - await expect(await vault.getStrategyCount()).to.equal(0); - + expect(await vault.getStrategyCount()).to.equal(0); // Vault value should remain the same because the liquidattion sent the // assets back to the vault expect(await vault.totalValue()).to.approxEqual( - utils.parseUnits("230", 18) + utils.parseUnits("208", 18) ); // Should be able to add Strategy back. Proves the struct in the mapping @@ -390,24 +262,22 @@ describe("Vault with Compound strategy", function () { }); it("Should not alter balances after an asset price change", async () => { - let { ousd, vault, matt, usdc, usds } = fixture; + let { ousd, vault, matt, usdc } = fixture; await usdc.connect(matt).approve(vault.address, usdcUnits("200")); await vault.connect(matt).mint(usdc.address, usdcUnits("200"), 0); - await usds.connect(matt).approve(vault.address, usdsUnits("200")); - await vault.connect(matt).mint(usds.address, usdsUnits("200"), 0); // 200 OUSD was already minted in the fixture, 100 each for Matt and Josh - await expectApproxSupply(ousd, ousdUnits("600.0")); - // 100 + 200 + 200 - await expect(matt).has.an.approxBalanceOf("500", ousd, "Initial"); + await expectApproxSupply(ousd, ousdUnits("400.0")); + // 100 + 200 = 300 + await expect(matt).has.an.approxBalanceOf("300", ousd, "Initial"); await setOracleTokenPriceUsd("USDC", "1.30"); await vault.rebase(); - await expectApproxSupply(ousd, ousdUnits("600.0")); + await expectApproxSupply(ousd, ousdUnits("400.0")); await expect(matt).has.an.approxBalanceOf( - "500.00", + "300.00", ousd, "After some assets double" ); @@ -415,68 +285,14 @@ describe("Vault with Compound strategy", function () { await setOracleTokenPriceUsd("USDC", "1.00"); await vault.rebase(); - await expectApproxSupply(ousd, ousdUnits("600.0")); + await expectApproxSupply(ousd, ousdUnits("400.0")); await expect(matt).has.an.approxBalanceOf( - "500", + "300.00", ousd, "After assets go back" ); }); - it("Should handle non-standard token deposits", async () => { - let { ousd, vault, matt, nonStandardToken, oracleRouter, governor } = - fixture; - - await oracleRouter.cacheDecimals(nonStandardToken.address); - if (nonStandardToken) { - await vault.connect(governor).supportAsset(nonStandardToken.address, 0); - } - - await setOracleTokenPriceUsd("NonStandardToken", "1.00"); - - await nonStandardToken - .connect(matt) - .approve(vault.address, usdtUnits("10000")); - - // Try to mint more than balance, to check failure state - try { - await vault - .connect(matt) - .mint(nonStandardToken.address, usdtUnits("1200"), 0); - } catch (err) { - expect( - /reverted with reason string 'SafeERC20: ERC20 operation did not succeed/gi.test( - err.message - ) - ).to.be.true; - } finally { - // Make sure nothing got affected - await expectApproxSupply(ousd, ousdUnits("200.0")); - await expect(matt).has.an.approxBalanceOf("100", ousd); - await expect(matt).has.an.approxBalanceOf("1000", nonStandardToken); - } - - // Try minting with a valid balance of tokens - await vault - .connect(matt) - .mint(nonStandardToken.address, usdtUnits("100"), 0); - await expect(matt).has.an.approxBalanceOf("900", nonStandardToken); - - await expectApproxSupply(ousd, ousdUnits("300.0")); - await expect(matt).has.an.approxBalanceOf("200", ousd, "Initial"); - await vault.rebase(); - await expect(matt).has.an.approxBalanceOf("200", ousd, "After null rebase"); - await setOracleTokenPriceUsd("NonStandardToken", "1.40"); - await vault.rebase(); - - await expectApproxSupply(ousd, ousdUnits("300.0")); - await expect(matt).has.an.approxBalanceOf( - "200.00", - ousd, - "After some assets double" - ); - }); - it("Should never allocate anything when Vault buffer is 1e18 (100%)", async () => { const { usds, vault, governor, compoundStrategy } = fixture; @@ -490,74 +306,26 @@ describe("Vault with Compound strategy", function () { await expect(await compoundStrategy.checkBalance(usds.address)).to.equal(0); }); - it("Should allocate correctly with USDS when Vault buffer is 1e17 (10%)", async () => { - const { usds, vault, governor, compoundStrategy } = await loadFixture( + it("Should allocate correctly with USDC when Vault buffer is 1e17 (10%)", async () => { + const { usdc, vault, governor, compoundStrategy } = await loadFixture( compoundVaultFixture ); - await expect(await vault.getStrategyCount()).to.equal(1); + expect(await vault.getStrategyCount()).to.equal(1); // Set a Vault buffer and allocate await vault.connect(governor).setVaultBuffer(utils.parseUnits("1", 17)); + await vault.connect(governor).setDefaultStrategy(compoundStrategy.address); await vault.allocate(); // Verify 80% went to Compound await expect( - await compoundStrategy.checkBalance(usds.address) - ).to.approxEqual(ousdUnits("180")); + await compoundStrategy.checkBalance(usdc.address) + ).to.approxEqual(usdcUnits("180")); // Remaining 20 should be in Vault await expect(await vault.totalValue()).to.approxEqual(ousdUnits("200")); }); - it("Should allocate correctly with USDS, USDT, USDC when Vault Buffer is 1e17 (10%)", async () => { - const { - usds, - usdc, - usdt, - matt, - josh, - vault, - anna, - governor, - compoundStrategy, - } = fixture; - - expect(await vault.totalValue()).to.approxEqual( - utils.parseUnits("200", 18) - ); - - // Josh deposits USDS, 18 decimals - await usds.connect(josh).approve(vault.address, usdsUnits("22.0")); - await vault.connect(josh).mint(usds.address, usdsUnits("22.0"), 0); - // Matt deposits USDC, 6 decimals - await usdc.connect(matt).approve(vault.address, usdcUnits("8.0")); - await vault.connect(matt).mint(usdc.address, usdcUnits("8.0"), 0); - // Anna deposits USDT, 6 decimals - await usdt.connect(anna).approve(vault.address, usdtUnits("20.0")); - await vault.connect(anna).mint(usdt.address, usdtUnits("20.0"), 0); - - // Set a Vault buffer and allocate - await vault.connect(governor).setVaultBuffer(utils.parseUnits("1", 17)); - await vault.allocate(); - - // Verify 80% went to Compound - await expect( - await compoundStrategy.checkBalance(usds.address) - ).to.approxEqual(usdsUnits("199.8")); - - await expect( - await compoundStrategy.checkBalance(usdc.address) - ).to.approxEqual(usdcUnits("7.2")); - - await expect( - await compoundStrategy.checkBalance(usdt.address) - ).to.approxEqual(usdtUnits("18")); - - expect(await vault.totalValue()).to.approxEqual( - utils.parseUnits("250", 18) - ); - }); - it("Should allow transfer of arbitrary token by Governor", async () => { const { vault, compoundStrategy, ousd, usdc, matt, governor } = fixture; @@ -587,143 +355,37 @@ describe("Vault with Compound strategy", function () { }); it("Should have correct balances on consecutive mint and redeem", async () => { - const { ousd, vault, usdc, usds, anna, matt, josh } = fixture; - - const usersWithBalances = [ - [anna, 0], - [matt, 100], - [josh, 100], - ]; + const { ousd, vault, usdc, anna, matt, josh, governor } = fixture; - const assetsWithUnits = [ - [usds, usdsUnits], - [usdc, usdcUnits], + const testCases = [ + { user: anna, start: 0 }, + { user: matt, start: 100 }, + { user: josh, start: 100 }, ]; - - for (const [user, startBalance] of usersWithBalances) { - for (const [asset, units] of assetsWithUnits) { - for (const amount of [5.09, 10.32, 20.99, 100.01]) { - await asset - .connect(user) - .approve(vault.address, await units(amount.toString())); - await vault - .connect(user) - .mint(asset.address, await units(amount.toString()), 0); - await expect(user).has.an.approxBalanceOf( - (startBalance + amount).toString(), - ousd - ); - await vault.connect(user).redeem(ousdUnits(amount.toString()), 0); - await expect(user).has.an.approxBalanceOf( - startBalance.toString(), - ousd - ); - } + const amounts = [5.09, 10.32, 20.99, 100.01]; + + for (const { user, start } of testCases) { + for (const amount of amounts) { + const mintAmount = usdcUnits(amount.toString()); + await usdc.connect(user).approve(vault.address, mintAmount); + await vault.connect(user).mint(usdc.address, mintAmount, 0); + await expect(user).has.an.approxBalanceOf( + (start + amount).toString(), + ousd + ); + await vault.connect(governor).setStrategistAddr(user.address); + await vault + .connect(user) + .requestWithdrawal(ousdUnits(amount.toString())); + await expect(user).has.an.approxBalanceOf(start.toString(), ousd); } } }); - it("Should collect reward tokens and swap via Uniswap", async () => { - const { anna, vault, harvester, governor, compoundStrategy, comp, usdt } = - fixture; - - const mockUniswapRouter = await ethers.getContract("MockUniswapRouter"); - - const compAmount = utils.parseUnits("100", 18); - await comp.connect(governor).mint(compAmount); - await comp.connect(governor).transfer(compoundStrategy.address, compAmount); - - await harvester.connect(governor).setRewardTokenConfig( - comp.address, // reward token - { - allowedSlippageBps: 0, - harvestRewardBps: 100, - swapPlatformAddr: mockUniswapRouter.address, - doSwapRewardToken: true, - swapPlatform: 0, - liquidationLimit: 0, - }, - utils.defaultAbiCoder.encode( - ["address[]"], - [[comp.address, usdt.address]] - ) - ); - - // Make sure Vault has 0 USDT balance - await expect(vault).has.a.balanceOf("0", usdt); - - // Make sure the Strategy has COMP balance - expect(await comp.balanceOf(await governor.getAddress())).to.be.equal("0"); - expect(await comp.balanceOf(compoundStrategy.address)).to.be.equal( - compAmount - ); - - const balanceBeforeAnna = await usdt.balanceOf(anna.address); - - // prettier-ignore - await harvester - .connect(anna)["harvestAndSwap(address)"](compoundStrategy.address); - - const balanceAfterAnna = await usdt.balanceOf(anna.address); - - // Make sure Vault has 100 USDT balance (the Uniswap mock converts at 1:1) - await expect(vault).has.a.balanceOf("99", usdt); - - // No COMP in Harvester or Compound strategy - await expect(harvester).has.a.balanceOf("0", comp); - expect(await comp.balanceOf(compoundStrategy.address)).to.be.equal("0"); - expect(balanceAfterAnna - balanceBeforeAnna).to.be.equal( - utils.parseUnits("1", 6) - ); - }); - - it("Should not swap if slippage is too high", async () => { - const { josh, vault, harvester, governor, compoundStrategy, comp, usdt } = - fixture; - - const mockUniswapRouter = await ethers.getContract("MockUniswapRouter"); - - // Mock router gives 1:1, if we set this to something high there will be - // too much slippage - await setOracleTokenPriceUsd("COMP", "1.3"); - - const compAmount = utils.parseUnits("100", 18); - await comp.connect(governor).mint(compAmount); - await comp.connect(governor).transfer(compoundStrategy.address, compAmount); - await mockUniswapRouter.setSlippage(utils.parseEther("0.75")); - - await harvester.connect(governor).setRewardTokenConfig( - comp.address, // reward token - { - allowedSlippageBps: 0, - harvestRewardBps: 100, - swapPlatformAddr: mockUniswapRouter.address, - doSwapRewardToken: true, - swapPlatform: 0, - liquidationLimit: 0, - }, - utils.defaultAbiCoder.encode( - ["address[]"], - [[comp.address, usdt.address]] - ) - ); - // Make sure Vault has 0 USDT balance - await expect(vault).has.a.balanceOf("0", usdt); - - // Make sure the Strategy has COMP balance - expect(await comp.balanceOf(await governor.getAddress())).to.be.equal("0"); - expect(await comp.balanceOf(compoundStrategy.address)).to.be.equal( - compAmount - ); - - // prettier-ignore - await expect(harvester - .connect(josh)["harvestAndSwap(address)"](compoundStrategy.address)).to.be.revertedWith("Slippage error"); - }); - const mintDoesAllocate = async (amount) => { - const { anna, vault, usdc, governor } = fixture; + const { anna, vault, usdc, governor, compoundStrategy } = fixture; + await vault.connect(governor).setDefaultStrategy(compoundStrategy.address); await vault.connect(governor).setVaultBuffer(0); await vault.allocate(); await usdc.connect(anna).mint(usdcUnits(amount)); @@ -743,8 +405,9 @@ describe("Vault with Compound strategy", function () { }); it("Alloc with both threshold and buffer", async () => { - const { anna, vault, usdc, usds, governor } = fixture; + const { anna, vault, usdc, usds, governor, compoundStrategy } = fixture; + await vault.connect(governor).setDefaultStrategy(compoundStrategy.address); await vault.allocate(); await vault.connect(governor).setVaultBuffer(utils.parseUnits("1", 17)); await vault.connect(governor).setAutoAllocateThreshold(ousdUnits("3")); @@ -758,11 +421,9 @@ describe("Vault with Compound strategy", function () { // 5 should be below the 10% vault buffer (4/204 * 100 = 1.96%) // All funds should remain in vault - await expect(await usdc.balanceOf(vault.address)).to.equal( - usdcUnits(amount) - ); + expect(await usdc.balanceOf(vault.address)).to.equal(usdcUnits(amount)); // USDS was allocated before the vault buffer was set - await expect(await usds.balanceOf(vault.address)).to.equal(usdsUnits("0")); + expect(await usds.balanceOf(vault.address)).to.equal(usdsUnits("0")); // Use an amount above the vault buffer size that will trigger an allocate const allocAmount = "5000"; diff --git a/contracts/test/vault/deposit.js b/contracts/test/vault/deposit.js index 20c868be39..e76c29f63b 100644 --- a/contracts/test/vault/deposit.js +++ b/contracts/test/vault/deposit.js @@ -1,6 +1,6 @@ const { expect } = require("chai"); -const { createFixtureLoader, compoundVaultFixture } = require("../_fixture"); +const { createFixtureLoader, defaultFixture } = require("../_fixture"); const { usdcUnits, isFork } = require("../helpers"); describe("Vault deposit pausing", function () { @@ -8,7 +8,7 @@ describe("Vault deposit pausing", function () { this.timeout(0); } let fixture; - const loadFixture = createFixtureLoader(compoundVaultFixture); + const loadFixture = createFixtureLoader(defaultFixture); beforeEach(async () => { fixture = await loadFixture(); }); diff --git a/contracts/test/vault/exchangeRate.js b/contracts/test/vault/exchangeRate.js deleted file mode 100644 index 037d3d1725..0000000000 --- a/contracts/test/vault/exchangeRate.js +++ /dev/null @@ -1,262 +0,0 @@ -const { expect } = require("chai"); - -const { loadDefaultFixture } = require("../_fixture"); -const { - ousdUnits, - usdsUnits, - advanceTime, - setOracleTokenPriceUsd, - isFork, -} = require("../helpers"); - -describe("Vault Redeem", function () { - if (isFork) { - this.timeout(0); - } - - let fixture; - beforeEach(async function () { - fixture = await loadDefaultFixture(); - const { vault, reth, governor } = fixture; - await vault.connect(governor).supportAsset(reth.address, 1); - await setOracleTokenPriceUsd("RETHETH", "1.2"); - }); - - it("Should mint at a positive exchange rate", async () => { - const { ousd, vault, reth, anna } = fixture; - - await reth.connect(anna).mint(usdsUnits("4.0")); - await reth.connect(anna).approve(vault.address, usdsUnits("4.0")); - await vault.connect(anna).mint(reth.address, usdsUnits("4.0"), 0); - await expect(anna).has.a.balanceOf("4.80", ousd); - }); - - it("Should mint less at low oracle, positive exchange rate", async () => { - const { ousd, vault, reth, anna } = fixture; - - await setOracleTokenPriceUsd("RETHETH", "1.199"); - await reth.connect(anna).mint(usdsUnits("4.0")); - await reth.connect(anna).approve(vault.address, usdsUnits("4.0")); - await vault.connect(anna).mint(reth.address, usdsUnits("4.0"), 0); - await expect(anna).has.a.approxBalanceOf("4.796", ousd); - }); - - it("Should revert mint at too low oracle, positive exchange rate", async () => { - const { vault, reth, anna } = fixture; - - await setOracleTokenPriceUsd("RETHETH", "1.00"); - await reth.connect(anna).mint(usdsUnits("4.0")); - await reth.connect(anna).approve(vault.address, usdsUnits("4.0")); - const tx = vault.connect(anna).mint(reth.address, usdsUnits("4.0"), 0); - await expect(tx).to.be.revertedWith("Asset price below peg"); - }); - - it("Should mint same at high oracle, positive exchange rate", async () => { - const { ousd, vault, reth, anna } = fixture; - - await setOracleTokenPriceUsd("RETHETH", "1.2"); - await reth.connect(anna).mint(usdsUnits("4.0")); - await reth.connect(anna).approve(vault.address, usdsUnits("4.0")); - await vault.connect(anna).mint(reth.address, usdsUnits("4.0"), 0); - await expect(anna).has.a.balanceOf("4.80", ousd); - }); - - it("Should rebase at a positive exchange rate", async () => { - const { ousd, vault, reth, anna } = fixture; - await vault.rebase(); - - const beforeGift = await ousd.totalSupply(); - - await reth.connect(anna).mint(usdsUnits("3.0")); - await reth.connect(anna).transfer(vault.address, usdsUnits("3.0")); - - await advanceTime(7 * 24 * 60 * 60); - await vault.rebase(); - - const afterGift = await ousd.totalSupply(); - expect(afterGift.sub(beforeGift)).to.approxEqualTolerance( - ousdUnits("3.6"), - 0.1, - "afterGift" - ); - - await setOracleTokenPriceUsd("RETHETH", "1.4"); - await reth.setExchangeRate(usdsUnits("1.4")); - await advanceTime(7 * 24 * 60 * 60); - await vault.rebase(); - const afterExchangeUp = await ousd.totalSupply(); - - expect(afterExchangeUp.sub(afterGift)).to.approxEqualTolerance( - ousdUnits("0.6"), - 0.1, - "afterExchangeUp" - ); - }); - - it("Should redeem at the expected rate", async () => { - const { ousd, vault, usds, reth, anna } = fixture; - - await setOracleTokenPriceUsd("RETHETH", "2.0"); - await reth.setExchangeRate(usdsUnits("2.0")); - - await reth.connect(anna).mint(usdsUnits("100.0")); - await reth.connect(anna).approve(vault.address, usdsUnits("100.0")); - await vault.connect(anna).mint(reth.address, usdsUnits("100.0"), 0); - await expect(anna).has.a.balanceOf("200", ousd, "post mint"); - await vault.rebase(); - await expect(anna).has.a.balanceOf("200", ousd, "post rebase"); - - await vault.connect(anna).redeem(usdsUnits("200.0"), 0); - await expect(anna).has.a.balanceOf("50", reth, "RETH"); - await expect(anna).has.a.balanceOf("1100", usds, "USDC"); - }); - - it("Should redeem less at a high oracle", async () => { - const { ousd, vault, usds, reth, anna } = fixture; - - await setOracleTokenPriceUsd("RETHETH", "2.0"); - await reth.setExchangeRate(usdsUnits("2.0")); - - await reth.connect(anna).mint(usdsUnits("100.0")); - await reth.connect(anna).approve(vault.address, usdsUnits("100.0")); - await vault.connect(anna).mint(reth.address, usdsUnits("100.0"), 0); - await expect(anna).has.a.balanceOf("200", ousd, "post mint"); - await vault.rebase(); - await expect(anna).has.a.balanceOf("200", ousd, "post rebase"); - - // Contains 100 rETH, (200 units) and 200 USDS (200 units) - // After Oracles $600 + $200 = $800 - // - // Redeeming $200 == 1/4 vault - // 25rETH and 50 USDS - - await setOracleTokenPriceUsd("RETHETH", "6.0"); - await reth.setExchangeRate(usdsUnits("6.0")); - await vault.connect(anna).redeem(usdsUnits("200.0"), 0); - await expect(anna).has.a.balanceOf("25", reth, "RETH"); - await expect(anna).has.a.balanceOf("1050", usds, "USDC"); - }); - - it("Should redeem same at a low oracle", async () => { - const { ousd, vault, usds, reth, anna } = fixture; - - await setOracleTokenPriceUsd("RETHETH", "2.0"); - await reth.setExchangeRate(usdsUnits("2.0")); - - await reth.connect(anna).mint(usdsUnits("100.0")); - await reth.connect(anna).approve(vault.address, usdsUnits("100.0")); - await vault.connect(anna).mint(reth.address, usdsUnits("100.0"), 0); - await expect(anna).has.a.balanceOf("200", ousd, "post mint"); - await vault.rebase(); - await expect(anna).has.a.balanceOf("200", ousd, "post rebase"); - - // Contains 100 rETH, (200 units) and 200 USDS (200 units) - // After Oracles $154 + $200 = $354 - // - // But since the exchange rate is still 2.0 the RETH unit price - // is snapped back to 2.0 when redeeming. Making the calculation: - // After Oracles $200 + $200 = $400 - // - // And redeeming 200 is 50% of the vault = 50 RETH & 100 USDS - - await setOracleTokenPriceUsd("RETHETH", "1.54"); - await vault.connect(anna).redeem(usdsUnits("200.0"), 0); - await expect(anna).has.a.balanceOf("50", reth, "RETH"); - await expect(anna).has.a.balanceOf("1100", usds, "USDC"); - }); - - it("Should redeem same at a low oracle v2", async () => { - const { ousd, vault, usds, reth, anna } = fixture; - - await setOracleTokenPriceUsd("RETHETH", "2.0"); - await reth.setExchangeRate(usdsUnits("2.0")); - - await reth.connect(anna).mint(usdsUnits("100.0")); - await reth.connect(anna).approve(vault.address, usdsUnits("100.0")); - await vault.connect(anna).mint(reth.address, usdsUnits("100.0"), 0); - await expect(anna).has.a.balanceOf("200", ousd, "post mint"); - await vault.rebase(); - await expect(anna).has.a.balanceOf("200", ousd, "post rebase"); - - // Contains 100 rETH, (200 units) and 200 USDS (200 units) - // After Oracles $100 + $200 = $300 - // - // Redeeming $150 == 1/2 vault - // 50rETH and 100 USDS - - await setOracleTokenPriceUsd("RETHETH", "1.0"); - await reth.setExchangeRate(usdsUnits("1.0")); - - await vault.connect(anna).redeem(usdsUnits("150.0"), 0); - await expect(anna).has.a.approxBalanceOf("50", reth, "RETH"); - await expect(anna).has.a.approxBalanceOf("1100", usds, "USDC"); - }); - - it("Should handle an exchange rate reedem attack", async () => { - const { ousd, vault, reth, anna, matt, governor } = fixture; - - await setOracleTokenPriceUsd("RETHETH", "2.0"); - await reth.setExchangeRate(usdsUnits("2.0")); - - // Old holder with RETH - await reth.connect(matt).mint(usdsUnits("500.0")); - await reth.connect(matt).approve(vault.address, usdsUnits("500.0")); - await vault.connect(matt).mint(reth.address, usdsUnits("500.0"), 0); - - // Attacker Mints before exchange change - await reth.connect(anna).mint(usdsUnits("500.0")); - await reth.connect(anna).approve(vault.address, usdsUnits("500.0")); - await vault.connect(anna).mint(reth.address, usdsUnits("500.0"), 0); - await expect(anna).has.a.balanceOf("1000", ousd, "post mint"); - - await setOracleTokenPriceUsd("RETHETH", "1.0"); - await reth.setExchangeRate(usdsUnits("1.0")); - - // console.log("----"); - // console.log((await vault.totalValue()).toString() / 1e18); - // console.log((await ousd.totalSupply()).toString() / 1e18); - - // Attacker redeems after exchange change - await vault.connect(governor).setMaxSupplyDiff(usdsUnits("0.9")); - await expect( - vault.connect(anna).redeem(usdsUnits("1000.0"), 0) - ).to.be.revertedWith("Backing supply liquidity error"); - - // console.log((await vault.totalValue()).toString() / 1e18); - // console.log((await ousd.totalSupply()).toString() / 1e18); - }); - - it("Should handle an exchange rate reedem attack, delayed oracle", async () => { - const { ousd, vault, reth, anna, matt, governor } = fixture; - - await setOracleTokenPriceUsd("RETHETH", "2.0"); - await reth.setExchangeRate(usdsUnits("2.0")); - - // Old holder with RETH - await reth.connect(matt).mint(usdsUnits("500.0")); - await reth.connect(matt).approve(vault.address, usdsUnits("500.0")); - await vault.connect(matt).mint(reth.address, usdsUnits("500.0"), 0); - - // Attacker Mints before exchange change - await reth.connect(anna).mint(usdsUnits("500.0")); - await reth.connect(anna).approve(vault.address, usdsUnits("500.0")); - await vault.connect(anna).mint(reth.address, usdsUnits("500.0"), 0); - await expect(anna).has.a.balanceOf("1000", ousd, "post mint"); - - await setOracleTokenPriceUsd("RETHETH", "1.3"); - await reth.setExchangeRate(usdsUnits("1.0")); - - // console.log("----"); - // console.log((await vault.totalValue()).toString() / 1e18); - // console.log((await ousd.totalSupply()).toString() / 1e18); - - // Attacker redeems after exchange change - await vault.connect(governor).setMaxSupplyDiff(usdsUnits("0.9")); - await expect( - vault.connect(anna).redeem(usdsUnits("1000.0"), 0) - ).to.be.revertedWith("Backing supply liquidity error"); - - // console.log((await vault.totalValue()).toString() / 1e18); - // console.log((await ousd.totalSupply()).toString() / 1e18); - }); -}); diff --git a/contracts/test/vault/harvester.mainnet.fork-test.js b/contracts/test/vault/harvester.mainnet.fork-test.js index ef6818f804..1b6d0887c5 100644 --- a/contracts/test/vault/harvester.mainnet.fork-test.js +++ b/contracts/test/vault/harvester.mainnet.fork-test.js @@ -3,7 +3,6 @@ const { utils, BigNumber } = require("ethers"); const { createFixtureLoader, harvesterFixture } = require("./../_fixture"); const { isCI, oethUnits } = require("./../helpers"); -const { hotDeployOption } = require("../_hot-deploy"); const addresses = require("../../utils/addresses"); const { setERC20TokenBalance } = require("../_fund"); const { parseUnits } = require("ethers").utils; @@ -19,12 +18,6 @@ describe("ForkTest: Harvester", function () { let fixture; beforeEach(async () => { fixture = await loadFixture(); - await hotDeployOption(fixture, null, { - isOethFixture: true, - }); - await hotDeployOption(fixture, null, { - isOethFixture: false, - }); }); // Skipping this since we switched to simple harvester diff --git a/contracts/test/vault/index.js b/contracts/test/vault/index.js index 8c0728ef9e..e0b0ea44a4 100644 --- a/contracts/test/vault/index.js +++ b/contracts/test/vault/index.js @@ -1,18 +1,8 @@ const { expect } = require("chai"); -const hre = require("hardhat"); const { utils } = require("ethers"); const { loadDefaultFixture } = require("../_fixture"); -const { - ousdUnits, - usdsUnits, - usdcUnits, - usdtUnits, - tusdUnits, - setOracleTokenPriceUsd, - getOracleAddresses, - isFork, -} = require("../helpers"); +const { ousdUnits, usdsUnits, usdcUnits, isFork } = require("../helpers"); describe("Vault", function () { if (isFork) { @@ -21,52 +11,16 @@ describe("Vault", function () { let fixture; beforeEach(async () => { fixture = await loadDefaultFixture(); + await fixture.compoundStrategy + .connect(fixture.governor) + .setPTokenAddress(fixture.usdc.address, fixture.cusdc.address); }); it("Should support an asset", async () => { - const { vault, oracleRouter, ousd, governor } = fixture; - - const oracleAddresses = await getOracleAddresses(hre.deployments); - const origAssetCount = await vault.connect(governor).getAssetCount(); - expect(await vault.isSupportedAsset(ousd.address)).to.be.false; - - /* Mock oracle feeds report 0 for updatedAt data point. Set - * maxStaleness to 100 years from epoch to make the Oracle - * feeds valid - */ - const maxStaleness = 24 * 60 * 60 * 365 * 100; - - await oracleRouter.setFeed( - ousd.address, - oracleAddresses.chainlink.USDS_USD, - maxStaleness - ); - await oracleRouter.cacheDecimals(ousd.address); - await expect(vault.connect(governor).supportAsset(ousd.address, 0)).to.emit( - vault, - "AssetSupported" - ); - expect(await vault.getAssetCount()).to.equal(origAssetCount.add(1)); - const assets = await vault.connect(governor).getAllAssets(); - expect(assets.length).to.equal(origAssetCount.add(1)); - expect(await vault["checkBalance(address)"](ousd.address)).to.equal(0); - expect(await vault.isSupportedAsset(ousd.address)).to.be.true; - }); - - it("Should revert when adding an asset that is already supported", async function () { - const { vault, usdt, governor } = fixture; + const { vault, usdc, usds } = fixture; - expect(await vault.isSupportedAsset(usdt.address)).to.be.true; - await expect( - vault.connect(governor).supportAsset(usdt.address, 0) - ).to.be.revertedWith("Asset already supported"); - }); - - it("Should revert when attempting to support an asset and not governor", async function () { - const { vault, usdt } = fixture; - await expect(vault.supportAsset(usdt.address, 0)).to.be.revertedWith( - "Caller is not the Governor" - ); + expect(await vault.isSupportedAsset(usds.address)).to.be.false; + expect(await vault.isSupportedAsset(usdc.address)).to.be.true; }); it("Should revert when adding a strategy that is already approved", async function () { @@ -87,111 +41,22 @@ describe("Vault", function () { }); it("Should correctly ratio deposited currencies of differing decimals", async function () { - const { ousd, vault, usdc, usds, matt } = fixture; - + const { ousd, vault, usdc, matt } = fixture; await expect(matt).has.a.balanceOf("100.00", ousd); // Matt deposits USDC, 6 decimals await usdc.connect(matt).approve(vault.address, usdcUnits("2.0")); await vault.connect(matt).mint(usdc.address, usdcUnits("2.0"), 0); await expect(matt).has.a.balanceOf("102.00", ousd); - - // Matt deposits USDS, 18 decimals - await usds.connect(matt).approve(vault.address, usdsUnits("4.0")); - await vault.connect(matt).mint(usds.address, usdsUnits("4.0"), 0); - await expect(matt).has.a.balanceOf("106.00", ousd); - }); - - it("Should correctly handle a deposit of USDS (18 decimals)", async function () { - const { ousd, vault, usds, anna } = fixture; - - await expect(anna).has.a.balanceOf("0.00", ousd); - // We limit to paying to $1 OUSD for for one stable coin, - // so this will deposit at a rate of $1. - await setOracleTokenPriceUsd("USDS", "1.30"); - await usds.connect(anna).approve(vault.address, usdsUnits("3.0")); - await vault.connect(anna).mint(usds.address, usdsUnits("3.0"), 0); - await expect(anna).has.a.balanceOf("3.00", ousd); }); it("Should correctly handle a deposit of USDC (6 decimals)", async function () { const { ousd, vault, usdc, anna } = fixture; await expect(anna).has.a.balanceOf("0.00", ousd); - await setOracleTokenPriceUsd("USDC", "0.998"); await usdc.connect(anna).approve(vault.address, usdcUnits("50.0")); await vault.connect(anna).mint(usdc.address, usdcUnits("50.0"), 0); - await expect(anna).has.a.balanceOf("49.90", ousd); - }); - - it("Should not allow a below peg deposit", async function () { - const { ousd, vault, usdc, anna } = fixture; - - await expect(anna).has.a.balanceOf("0.00", ousd); - await setOracleTokenPriceUsd("USDC", "0.95"); - await usdc.connect(anna).approve(vault.address, usdcUnits("50.0")); - await expect( - vault.connect(anna).mint(usdc.address, usdcUnits("50.0"), 0) - ).to.be.revertedWith("Asset price below peg"); - }); - - it("Should correctly handle a deposit failure of Non-Standard ERC20 Token", async function () { - const { ousd, vault, anna, nonStandardToken, oracleRouter, governor } = - fixture; - - await oracleRouter.cacheDecimals(nonStandardToken.address); - await vault.connect(governor).supportAsset(nonStandardToken.address, 0); - await expect(anna).has.a.balanceOf("1000.00", nonStandardToken); - await setOracleTokenPriceUsd("NonStandardToken", "1.30"); - await nonStandardToken - .connect(anna) - .approve(vault.address, usdtUnits("1500.0")); - - // Anna has a balance of 1000 tokens and she is trying to - // transfer 1500 tokens. The contract doesn't throw but - // fails silently, so Anna's OUSD balance should be zero. - try { - await vault - .connect(anna) - .mint(nonStandardToken.address, usdtUnits("1500.0"), 0); - } catch (err) { - expect( - /reverted with reason string 'SafeERC20: ERC20 operation did not succeed/gi.test( - err.message - ) - ).to.be.true; - } finally { - // Make sure nothing got affected - await expect(anna).has.a.balanceOf("0.00", ousd); - await expect(anna).has.a.balanceOf("1000.00", nonStandardToken); - } - }); - - it("Should correctly handle a deposit of Non-Standard ERC20 Token", async function () { - const { ousd, vault, anna, nonStandardToken, oracleRouter, governor } = - fixture; - - await oracleRouter.cacheDecimals(nonStandardToken.address); - await vault.connect(governor).supportAsset(nonStandardToken.address, 0); - - await expect(anna).has.a.balanceOf("1000.00", nonStandardToken); - await setOracleTokenPriceUsd("NonStandardToken", "1.00"); - - await nonStandardToken - .connect(anna) - .approve(vault.address, usdtUnits("100.0")); - await vault - .connect(anna) - .mint(nonStandardToken.address, usdtUnits("100.0"), 0); - await expect(anna).has.a.balanceOf("100.00", ousd); - await expect(anna).has.a.balanceOf("900.00", nonStandardToken); - }); - - it("Should calculate the balance correctly with USDS", async () => { - const { vault } = fixture; - - // Vault already has USDS from default ficture - expect(await vault.totalValue()).to.equal(utils.parseUnits("200", 18)); + await expect(anna).has.a.balanceOf("50.00", ousd); }); it("Should calculate the balance correctly with USDC", async () => { @@ -204,46 +69,6 @@ describe("Vault", function () { expect(await vault.totalValue()).to.equal(utils.parseUnits("202", 18)); }); - it("Should calculate the balance correctly with USDT", async () => { - const { vault, usdt, matt } = fixture; - - // Matt deposits USDT, 6 decimals - await usdt.connect(matt).approve(vault.address, usdtUnits("5.0")); - await vault.connect(matt).mint(usdt.address, usdtUnits("5.0"), 0); - // Fixture loads 200 USDS, so result should be 205 - expect(await vault.totalValue()).to.equal(utils.parseUnits("205", 18)); - }); - - it("Should calculate the balance correctly with TUSD", async () => { - const { vault, tusd, matt } = fixture; - - await tusd.connect(matt).mint(ousdUnits("100.0")); - - // Matt deposits TUSD, 18 decimals - await tusd.connect(matt).approve(vault.address, tusdUnits("9.0")); - await vault.connect(matt).mint(tusd.address, tusdUnits("9.0"), 0); - // Fixture loads 200 USDS, so result should be 209 - expect(await vault.totalValue()).to.equal(utils.parseUnits("209", 18)); - }); - - it("Should calculate the balance correctly with USDS, USDC, USDT, TUSD", async () => { - const { vault, usdc, usdt, tusd, matt } = fixture; - - await tusd.connect(matt).mint(ousdUnits("100.0")); - - // Matt deposits USDC, 6 decimals - await usdc.connect(matt).approve(vault.address, usdcUnits("8.0")); - await vault.connect(matt).mint(usdc.address, usdcUnits("8.0"), 0); - // Matt deposits USDT, 6 decimals - await usdt.connect(matt).approve(vault.address, usdtUnits("20.0")); - await vault.connect(matt).mint(usdt.address, usdtUnits("20.0"), 0); - // Matt deposits TUSD, 18 decimals - await tusd.connect(matt).approve(vault.address, tusdUnits("9.0")); - await vault.connect(matt).mint(tusd.address, tusdUnits("9.0"), 0); - // Fixture loads 200 USDS, so result should be 237 - expect(await vault.totalValue()).to.equal(utils.parseUnits("237", 18)); - }); - it("Should allow transfer of arbitrary token by Governor", async () => { const { vault, ousd, usdc, matt, governor } = fixture; @@ -274,14 +99,13 @@ describe("Vault", function () { // Governor cannot move USDC because it is a supported token. await expect( vault.connect(governor).transferToken(usdc.address, ousdUnits("8.0")) - ).to.be.revertedWith("Only unsupported assets"); + ).to.be.revertedWith("Only unsupported asset"); }); it("Should allow Governor to add Strategy", async () => { - const { vault, governor, ousd } = fixture; + const { vault, governor, mockStrategy } = fixture; - // Pretend OUSD is a strategy and add its address - await vault.connect(governor).approveStrategy(ousd.address); + await vault.connect(governor).approveStrategy(mockStrategy.address); }); it("Should revert when removing a Strategy that has not been added", async () => { @@ -305,20 +129,6 @@ describe("Vault", function () { await expect(matt).has.a.balanceOf("100.00", ousd); }); - it("Should revert mint if minMintAmount check fails", async () => { - const { vault, matt, ousd, usds, usdt } = fixture; - - await usdt.connect(matt).approve(vault.address, usdtUnits("50.0")); - await usds.connect(matt).approve(vault.address, usdsUnits("25.0")); - - await expect( - vault.connect(matt).mint(usdt.address, usdtUnits("50"), usdsUnits("100")) - ).to.be.revertedWith("Mint amount lower than minimum"); - - await expect(matt).has.a.balanceOf("100.00", ousd); - expect(await ousd.totalSupply()).to.eq(ousdUnits("200.0")); - }); - it("Should allow transfer of arbitrary token by Governor", async () => { const { vault, ousd, usdc, matt, governor } = fixture; @@ -370,61 +180,57 @@ describe("Vault", function () { }); it("Should allow the Governor to call withdraw and then deposit", async () => { - const { vault, governor, usds, josh, compoundStrategy } = fixture; + const { vault, governor, usdc, josh, compoundStrategy } = fixture; await vault.connect(governor).approveStrategy(compoundStrategy.address); - // Send all USDS to Compound - await vault - .connect(governor) - .setAssetDefaultStrategy(usds.address, compoundStrategy.address); - await usds.connect(josh).approve(vault.address, usdsUnits("200")); - await vault.connect(josh).mint(usds.address, usdsUnits("200"), 0); + // Send all USDC to Compound + await vault.connect(governor).setDefaultStrategy(compoundStrategy.address); + await usdc.connect(josh).approve(vault.address, usdcUnits("200")); + await vault.connect(josh).mint(usdc.address, usdcUnits("200"), 0); await vault.connect(governor).allocate(); await vault .connect(governor) .withdrawFromStrategy( compoundStrategy.address, - [usds.address], - [usdsUnits("200")] + [usdc.address], + [usdcUnits("200")] ); await vault .connect(governor) .depositToStrategy( compoundStrategy.address, - [usds.address], - [usdsUnits("200")] + [usdc.address], + [usdcUnits("200")] ); }); it("Should allow the Strategist to call withdrawFromStrategy and then depositToStrategy", async () => { - const { vault, governor, usds, josh, strategist, compoundStrategy } = + const { vault, governor, usdc, josh, strategist, compoundStrategy } = fixture; await vault.connect(governor).approveStrategy(compoundStrategy.address); - // Send all USDS to Compound - await vault - .connect(governor) - .setAssetDefaultStrategy(usds.address, compoundStrategy.address); - await usds.connect(josh).approve(vault.address, usdsUnits("200")); - await vault.connect(josh).mint(usds.address, usdsUnits("200"), 0); + // Send all USDC to Compound + await vault.connect(governor).setDefaultStrategy(compoundStrategy.address); + await usdc.connect(josh).approve(vault.address, usdcUnits("200")); + await vault.connect(josh).mint(usdc.address, usdcUnits("200"), 0); await vault.connect(governor).allocate(); await vault .connect(strategist) .withdrawFromStrategy( compoundStrategy.address, - [usds.address], - [usdsUnits("200")] + [usdc.address], + [usdcUnits("200")] ); await vault .connect(strategist) .depositToStrategy( compoundStrategy.address, - [usds.address], - [usdsUnits("200")] + [usdc.address], + [usdcUnits("200")] ); }); @@ -449,35 +255,14 @@ describe("Vault", function () { }); it("Should withdrawFromStrategy the correct amount for multiple assests and redeploy them using depositToStrategy", async () => { - const { - vault, - governor, - usds, - usdc, - cusdc, - josh, - strategist, - compoundStrategy, - } = fixture; + const { vault, governor, usdc, josh, strategist, compoundStrategy } = + fixture; await vault.connect(governor).approveStrategy(compoundStrategy.address); - // Send all USDS to Compound - await vault - .connect(governor) - .setAssetDefaultStrategy(usds.address, compoundStrategy.address); - - // Add USDC - await compoundStrategy - .connect(governor) - .setPTokenAddress(usdc.address, cusdc.address); // Send all USDC to Compound - await vault - .connect(governor) - .setAssetDefaultStrategy(usdc.address, compoundStrategy.address); + await vault.connect(governor).setDefaultStrategy(compoundStrategy.address); - await usds.connect(josh).approve(vault.address, usdsUnits("200")); - await vault.connect(josh).mint(usds.address, usdsUnits("200"), 0); await usdc.connect(josh).approve(vault.address, usdcUnits("90")); await vault.connect(josh).mint(usdc.address, usdcUnits("90"), 0); await vault.connect(governor).allocate(); @@ -486,15 +271,11 @@ describe("Vault", function () { .connect(strategist) .withdrawFromStrategy( compoundStrategy.address, - [usds.address, usdc.address], - [usdsUnits("50"), usdcUnits("90")] + [usdc.address], + [usdcUnits("90")] ); // correct balances at the end - const expectedVaultUsdsBalance = usdsUnits("50"); - expect(await usds.balanceOf(vault.address)).to.equal( - expectedVaultUsdsBalance - ); const expectedVaultUsdcBalance = usdcUnits("90"); expect(await usdc.balanceOf(vault.address)).to.equal( expectedVaultUsdcBalance @@ -504,12 +285,11 @@ describe("Vault", function () { .connect(strategist) .depositToStrategy( compoundStrategy.address, - [usds.address, usdc.address], - [usdsUnits("50"), usdcUnits("90")] + [usdc.address], + [usdcUnits("90")] ); // correct balances after depositing back - expect(await usds.balanceOf(vault.address)).to.equal(usdsUnits("0")); expect(await usdc.balanceOf(vault.address)).to.equal(usdcUnits("0")); }); @@ -544,19 +324,17 @@ describe("Vault", function () { }); it("Should only allow Governor and Strategist to call withdrawAllFromStrategy", async () => { - const { vault, governor, strategist, compoundStrategy, matt, josh, usds } = + const { vault, governor, strategist, compoundStrategy, matt, josh, usdc } = fixture; await vault.connect(governor).approveStrategy(compoundStrategy.address); - // Get the vault's initial USDS balance. - const vaultUsdsBalance = await usds.balanceOf(vault.address); + // Get the vault's initial USDC balance. + const vaultUsdcBalance = await usdc.balanceOf(vault.address); - // Mint and allocate USDS to Compound. - await vault - .connect(governor) - .setAssetDefaultStrategy(usds.address, compoundStrategy.address); - await usds.connect(josh).approve(vault.address, usdsUnits("200")); - await vault.connect(josh).mint(usds.address, usdsUnits("200"), 0); + // Mint and allocate USDC to Compound. + await vault.connect(governor).setDefaultStrategy(compoundStrategy.address); + await usdc.connect(josh).approve(vault.address, usdcUnits("200")); + await vault.connect(josh).mint(usdc.address, usdcUnits("200"), 0); await vault.connect(governor).allocate(); // Call to withdrawAll by the governor should go thru. @@ -564,9 +342,9 @@ describe("Vault", function () { .connect(governor) .withdrawAllFromStrategy(compoundStrategy.address); - // All the USDS should have been moved back to the vault. - const expectedVaultUsdsBalance = vaultUsdsBalance.add(usdsUnits("200")); - await expect(await usds.balanceOf(vault.address)).to.equal( + // All the USDC should have been moved back to the vault. + const expectedVaultUsdsBalance = vaultUsdcBalance.add(usdcUnits("200")); + await expect(await usdc.balanceOf(vault.address)).to.equal( expectedVaultUsdsBalance ); @@ -580,68 +358,4 @@ describe("Vault", function () { vault.connect(matt).withdrawAllFromStrategy(compoundStrategy.address) ).to.be.revertedWith("Caller is not the Strategist or Governor"); }); - - it("Should only allow metastrategy to mint oTokens and revert when threshold is reached.", async () => { - const { vault, ousd, governor, anna, josh } = fixture; - - await vault - .connect(governor) - .setNetOusdMintForStrategyThreshold(ousdUnits("10")); - // Approve anna address as an address allowed to mint OUSD without backing - await vault.connect(governor).setOusdMetaStrategy(anna.address); - - await expect( - vault.connect(anna).mintForStrategy(ousdUnits("11")) - ).to.be.revertedWith( - "Minted ousd surpassed netOusdMintForStrategyThreshold." - ); - - await expect( - vault.connect(josh).mintForStrategy(ousdUnits("9")) - ).to.be.revertedWith("Caller is not the OUSD meta strategy"); - - await vault.connect(anna).mintForStrategy(ousdUnits("9")); - - await expect(await ousd.balanceOf(anna.address)).to.equal(ousdUnits("9")); - }); - - it("Should reset netOusdMintedForStrategy when new threshold is set", async () => { - const { vault, governor, anna } = fixture; - - await vault - .connect(governor) - .setNetOusdMintForStrategyThreshold(ousdUnits("10")); - - // Approve anna address as an address allowed to mint OUSD without backing - await vault.connect(governor).setOusdMetaStrategy(anna.address); - await vault.connect(anna).mintForStrategy(ousdUnits("9")); - - // netOusdMintedForStrategy should be equal to amount minted - await expect(await vault.netOusdMintedForStrategy()).to.equal( - ousdUnits("9") - ); - - await vault - .connect(governor) - .setNetOusdMintForStrategyThreshold(ousdUnits("10")); - - // netOusdMintedForStrategy should be reset back to 0 - await expect(await vault.netOusdMintedForStrategy()).to.equal( - ousdUnits("0") - ); - }); - it("Should re-cache decimals", async () => { - const { vault, governor, usdc } = fixture; - - const beforeAssetConfig = await vault.getAssetConfig(usdc.address); - expect(beforeAssetConfig.decimals).to.equal(6); - - // cacheDecimals is not on IVault so we need to use the admin contract - const vaultAdmin = await ethers.getContractAt("VaultAdmin", vault.address); - - await vaultAdmin.connect(governor).cacheDecimals(usdc.address); - - const afterAssetConfig = await vault.getAssetConfig(usdc.address); - expect(afterAssetConfig.decimals).to.equal(6); - }); }); diff --git a/contracts/test/vault/oeth-vault.js b/contracts/test/vault/oeth-vault.js index a9056f88ac..f27fa02b90 100644 --- a/contracts/test/vault/oeth-vault.js +++ b/contracts/test/vault/oeth-vault.js @@ -1,5 +1,4 @@ const { expect } = require("chai"); -const hre = require("hardhat"); const { createFixtureLoader, oethDefaultFixture } = require("../_fixture"); const { parseUnits } = require("ethers/lib/utils"); @@ -115,20 +114,6 @@ describe("OETH Vault", function () { ); }); - it("Fail to mint with any other asset", async () => { - const { oethVault, frxETH, stETH, reth, josh } = fixture; - - const amount = parseUnits("1", 18); - const minOeth = parseUnits("0.8", 18); - - for (const asset of [frxETH, stETH, reth]) { - await asset.connect(josh).approve(oethVault.address, amount); - const tx = oethVault.connect(josh).mint(asset.address, amount, minOeth); - - await expect(tx).to.be.revertedWith("Unsupported asset for minting"); - } - }); - it("Fail to mint if amount is zero", async () => { const { oethVault, weth, josh } = fixture; @@ -155,7 +140,7 @@ describe("OETH Vault", function () { await oethVault.connect(governor).approveStrategy(mockStrategy.address); await oethVault .connect(governor) - .setAssetDefaultStrategy(weth.address, mockStrategy.address); + .setDefaultStrategy(mockStrategy.address); const fixtureWithUser = { ...fixture, user: domen }; const dataBefore = await snapData(fixtureWithUser); @@ -189,38 +174,8 @@ describe("OETH Vault", function () { }); }); - describe("Redeem", () => { - it("Should return only WETH in redeem calculations", async () => { - const { oethVault, weth } = fixture; - - const outputs = await oethVault.calculateRedeemOutputs( - oethUnits("1234.43") - ); - - const assets = await oethVault.getAllAssets(); - - expect(assets.length).to.equal(outputs.length); - - for (let i = 0; i < assets.length; i++) { - expect(outputs[i]).to.equal( - assets[i] == weth.address ? oethUnits("1234.43") : "0" - ); - } - }); - - it("Fail to calculateRedeemOutputs if WETH index isn't cached", async () => { - const { frxETH, weth } = fixture; - - await deployWithConfirmation("MockOETHVault", [weth.address]); - const mockVault = await hre.ethers.getContract("MockOETHVault"); - - await mockVault.supportAsset(frxETH.address); - - const tx = mockVault.calculateRedeemOutputs(oethUnits("12343")); - await expect(tx).to.be.revertedWith("WETH Asset index not cached"); - }); - - it("Should update total supply correctly without redeem fee", async () => { + describe("async withdrawal", () => { + it("Should update total supply correctly", async () => { const { oethVault, oeth, weth, daniel } = fixture; await oethVault.connect(daniel).mint(weth.address, oethUnits("10"), "0"); @@ -228,7 +183,9 @@ describe("OETH Vault", function () { const vaultBalanceBefore = await weth.balanceOf(oethVault.address); const supplyBefore = await oeth.totalSupply(); - await oethVault.connect(daniel).redeem(oethUnits("10"), "0"); + await oethVault.connect(daniel).requestWithdrawal(oethUnits("10")); + await advanceTime(10 * 60); // 10 minutes + await oethVault.connect(daniel).claimWithdrawal(0); const userBalanceAfter = await weth.balanceOf(daniel.address); const vaultBalanceAfter = await weth.balanceOf(oethVault.address); @@ -240,42 +197,14 @@ describe("OETH Vault", function () { expect(supplyBefore.sub(supplyAfter)).to.eq(oethUnits("10")); }); - it("Should update total supply correctly with redeem fee", async () => { - const { oethVault, oeth, weth, daniel } = fixture; - await oethVault.connect(daniel).mint(weth.address, oethUnits("10"), "0"); - - await oethVault - .connect(await impersonateAndFund(await oethVault.governor())) - .setRedeemFeeBps(100); - - const userBalanceBefore = await weth.balanceOf(daniel.address); - const vaultBalanceBefore = await weth.balanceOf(oethVault.address); - const supplyBefore = await oeth.totalSupply(); - - await oethVault.connect(daniel).redeem(oethUnits("10"), "0"); - - const userBalanceAfter = await weth.balanceOf(daniel.address); - const vaultBalanceAfter = await weth.balanceOf(oethVault.address); - const supplyAfter = await oeth.totalSupply(); - - // Make sure the total supply went down - expect(userBalanceAfter.sub(userBalanceBefore)).to.eq( - oethUnits("10").sub(oethUnits("0.1")) - ); - expect(vaultBalanceBefore.sub(vaultBalanceAfter)).to.eq( - oethUnits("10").sub(oethUnits("0.1")) - ); - expect(supplyBefore.sub(supplyAfter)).to.eq(oethUnits("10")); - }); - - it("Fail to redeem if not enough liquidity available in the vault", async () => { + it("Fail to claim if not enough liquidity available in the vault", async () => { const { oethVault, weth, domen, governor } = fixture; const mockStrategy = await deployWithConfirmation("MockStrategy"); await oethVault.connect(governor).approveStrategy(mockStrategy.address); await oethVault .connect(governor) - .setAssetDefaultStrategy(weth.address, mockStrategy.address); + .setDefaultStrategy(mockStrategy.address); // Mint some WETH await weth.connect(domen).approve(oethVault.address, oethUnits("10000")); @@ -285,197 +214,131 @@ describe("OETH Vault", function () { await oethVault.connect(domen).mint(weth.address, oethUnits("1.23"), "0"); // Withdraw something more than what the Vault holds - const tx = oethVault.connect(domen).redeem(oethUnits("12.55"), "0"); + await oethVault.connect(domen).requestWithdrawal(oethUnits("12.55")); + await advanceTime(10 * 60); // 10 minutes - await expect(tx).to.revertedWith("Liquidity error"); + const tx = oethVault.connect(domen).claimWithdrawal(0); + await expect(tx).to.revertedWith("Queue pending liquidity"); }); - it("Should redeem zero amount without revert", async () => { + it("Fail to request withdrawal of zero amount", async () => { const { oethVault, daniel } = fixture; - await oethVault.connect(daniel).redeem(0, 0); + const tx = oethVault.connect(daniel).requestWithdrawal(0); + await expect(tx).to.be.revertedWith("Amount must be greater than 0"); }); - it("Fail to redeem if not enough liquidity", async () => { - const { oethVault, daniel } = fixture; - const tx = oethVault - .connect(daniel) - .redeem(oethUnits("1023232323232"), "0"); - await expect(tx).to.be.revertedWith("Liquidity error"); - }); - it("Should allow every user to redeem", async () => { + it("Should allow every user to withdraw", async () => { const { oethVault, weth, daniel } = fixture; await oethVault.connect(daniel).mint(weth.address, oethUnits("10"), "0"); - await oethVault.connect(daniel).redeem(oethUnits("10"), oethUnits("0")); + await oethVault.connect(daniel).requestWithdrawal(oethUnits("10")); + await advanceTime(10 * 60); // 10 minutes + await oethVault.connect(daniel).claimWithdrawal(0); await expect(await weth.balanceOf(oethVault.address)).to.equal(0); }); }); describe("Config", () => { - it("Should allow caching WETH index", async () => { - const { oethVault, weth, governor } = fixture; - - await oethVault.connect(governor).cacheWETHAssetIndex(); - - const index = (await oethVault.wethAssetIndex()).toNumber(); - - const assets = await oethVault.getAllAssets(); - - expect(assets[index]).to.equal(weth.address); - }); - - it("Fail to allow anyone other than Governor to change cached index", async () => { - const { oethVault, strategist } = fixture; - - const tx = oethVault.connect(strategist).cacheWETHAssetIndex(); - await expect(tx).to.be.revertedWith("Caller is not the Governor"); - }); - - it("Fail to cacheWETHAssetIndex if WETH is not a supported asset", async () => { - const { frxETH, weth } = fixture; - const { deployerAddr } = await hre.getNamedAccounts(); - const sDeployer = hre.ethers.provider.getSigner(deployerAddr); - - await deployWithConfirmation("MockOETHVault", [weth.address]); - const mockVault = await hre.ethers.getContract("MockOETHVault"); - - await mockVault.supportAsset(frxETH.address); - - const tx = mockVault.connect(sDeployer).cacheWETHAssetIndex(); - await expect(tx).to.be.revertedWith("Invalid WETH Asset Index"); - }); - it("Should return all strategies", async () => { // Mostly to increase coverage - const { oethVault, weth, governor } = fixture; + const { oethVault, governor, mockStrategy } = fixture; // Empty list await expect((await oethVault.getAllStrategies()).length).to.equal(0); // Add a strategy - await oethVault.connect(governor).approveStrategy(weth.address); + await oethVault.connect(governor).approveStrategy(mockStrategy.address); // Check the strategy list await expect(await oethVault.getAllStrategies()).to.deep.equal([ - weth.address, + mockStrategy.address, ]); }); - }); - - describe("Remove Asset", () => { - it("Should allow removing a single asset", async () => { - const { oethVault, frxETH, governor } = fixture; - - const vaultAdmin = await ethers.getContractAt( - "OETHVaultAdmin", - oethVault.address - ); - const assetCount = (await oethVault.getAssetCount()).toNumber(); - - const tx = await oethVault.connect(governor).removeAsset(frxETH.address); - await expect(tx) - .to.emit(vaultAdmin, "AssetRemoved") - .withArgs(frxETH.address); - await expect(tx) - .to.emit(vaultAdmin, "AssetDefaultStrategyUpdated") - .withArgs(frxETH.address, addresses.zero); + it("Should reset mint whitelist flag when removing a strategy", async () => { + const { oethVault, weth, governor } = fixture; - expect(await oethVault.isSupportedAsset(frxETH.address)).to.be.false; - expect(await oethVault.checkBalance(frxETH.address)).to.equal(0); - expect(await oethVault.assetDefaultStrategies(frxETH.address)).to.equal( - addresses.zero + // Deploy a mock strategy that can be removed + const dMockStrategy = await deployWithConfirmation("MockStrategy"); + const mockStrategy = await ethers.getContractAt( + "MockStrategy", + dMockStrategy.address ); - const allAssets = await oethVault.getAllAssets(); - expect(allAssets.length).to.equal(assetCount - 1); - - expect(allAssets).to.not.contain(frxETH.address); - - const config = await oethVault.getAssetConfig(frxETH.address); - expect(config.isSupported).to.be.false; - }); - - it("Should only allow governance to remove assets", async () => { - const { oethVault, weth, strategist, josh } = fixture; - - for (const signer of [strategist, josh]) { - let tx = oethVault.connect(signer).removeAsset(weth.address); - await expect(tx).to.be.revertedWith("Caller is not the Governor"); - - tx = oethVault.connect(signer).removeAsset(weth.address); - await expect(tx).to.be.revertedWith("Caller is not the Governor"); - } - }); - - it("Fail to remove asset if asset is not supported", async () => { - const { oethVault, usds, governor } = fixture; - const tx = oethVault.connect(governor).removeAsset(usds.address); - - await expect(tx).to.be.revertedWith("Asset not supported"); - }); - - it("Fail to remove asset if vault still holds the asset", async () => { - const { oethVault, weth, governor, daniel } = fixture; + // Set up the mock strategy to support withdrawAll + await mockStrategy.setWithdrawAll(weth.address, oethVault.address); - await oethVault.connect(daniel).mint(weth.address, oethUnits("1"), "0"); - - const tx = oethVault.connect(governor).removeAsset(weth.address); - - await expect(tx).to.be.revertedWith("Vault still holds asset"); - }); + // Approve the strategy + await oethVault.connect(governor).approveStrategy(mockStrategy.address); - it("Fail to revert for smaller dust", async () => { - const { oethVault, weth, governor, daniel } = fixture; + // Add strategy to mint whitelist + await oethVault + .connect(governor) + .addStrategyToMintWhitelist(mockStrategy.address); - await oethVault.connect(daniel).mint(weth.address, "500000000000", "0"); + // Verify it's whitelisted + expect(await oethVault.isMintWhitelistedStrategy(mockStrategy.address)).to + .be.true; - const tx = oethVault.connect(governor).removeAsset(weth.address); + // Remove the strategy + await oethVault.connect(governor).removeStrategy(mockStrategy.address); - await expect(tx).to.not.be.revertedWith("Vault still holds asset"); + // Verify the mint whitelist flag is reset + expect(await oethVault.isMintWhitelistedStrategy(mockStrategy.address)).to + .be.false; }); + }); + describe("Remove Asset", () => { it("Should allow strategy to burnForStrategy", async () => { - const { oethVault, oeth, weth, governor, daniel } = fixture; + const { oethVault, oeth, weth, governor, mockStrategy } = fixture; - await oethVault.connect(governor).approveStrategy(daniel.address); - await oethVault + await weth .connect(governor) - .addStrategyToMintWhitelist(daniel.address); - - // First increase netOusdMintForStrategyThreshold + .transfer(mockStrategy.address, oethUnits("10")); + await oethVault.connect(governor).approveStrategy(mockStrategy.address); await oethVault .connect(governor) - .setNetOusdMintForStrategyThreshold(oethUnits("100")); + .addStrategyToMintWhitelist(mockStrategy.address); + + const strategySigner = await impersonateAndFund(mockStrategy.address); + + await weth + .connect(strategySigner) + .approve(oethVault.address, oethUnits("10")); // Then mint for strategy - await oethVault.connect(daniel).mint(weth.address, oethUnits("10"), "0"); + await oethVault + .connect(strategySigner) + .mint(weth.address, oethUnits("10"), "0"); - await expect(await oeth.balanceOf(daniel.address)).to.equal( + await expect(await oeth.balanceOf(mockStrategy.address)).to.equal( oethUnits("10") ); // Then burn for strategy - await oethVault.connect(daniel).burnForStrategy(oethUnits("10")); + await oethVault.connect(strategySigner).burnForStrategy(oethUnits("10")); - await expect(await oeth.balanceOf(daniel.address)).to.equal( + await expect(await oeth.balanceOf(mockStrategy.address)).to.equal( oethUnits("0") ); }); it("Fail when burnForStrategy because amount > int256 ", async () => { - const { oethVault, governor, daniel } = fixture; + const { oethVault, governor, mockStrategy } = fixture; - await oethVault.connect(governor).approveStrategy(daniel.address); + await oethVault.connect(governor).approveStrategy(mockStrategy.address); await oethVault .connect(governor) - .addStrategyToMintWhitelist(daniel.address); + .addStrategyToMintWhitelist(mockStrategy.address); + + const strategySigner = await impersonateAndFund(mockStrategy.address); const tx = oethVault - .connect(daniel) + .connect(strategySigner) .burnForStrategy(parseUnits("10", 76)); await expect(tx).to.be.revertedWith( @@ -484,26 +347,26 @@ describe("OETH Vault", function () { }); it("Governor should remove strategy from mint whitelist", async () => { - const { oethVault, governor, daniel } = fixture; + const { oethVault, governor, mockStrategy } = fixture; - await oethVault.connect(governor).approveStrategy(daniel.address); + await oethVault.connect(governor).approveStrategy(mockStrategy.address); await oethVault .connect(governor) - .addStrategyToMintWhitelist(daniel.address); + .addStrategyToMintWhitelist(mockStrategy.address); - expect(await oethVault.isMintWhitelistedStrategy(daniel.address)).to.be - .true; + expect(await oethVault.isMintWhitelistedStrategy(mockStrategy.address)).to + .be.true; const tx = await oethVault .connect(governor) - .removeStrategyFromMintWhitelist(daniel.address); + .removeStrategyFromMintWhitelist(mockStrategy.address); expect(tx) .to.emit(oethVault, "StrategyRemovedFromMintWhitelist") - .withArgs(daniel.address); + .withArgs(mockStrategy.address); - expect(await oethVault.isMintWhitelistedStrategy(daniel.address)).to.be - .false; + expect(await oethVault.isMintWhitelistedStrategy(mockStrategy.address)).to + .be.false; }); }); @@ -534,7 +397,7 @@ describe("OETH Vault", function () { .approveStrategy(mockStrategy.address); await oethVault .connect(await impersonateAndFund(await oethVault.governor())) - .setAssetDefaultStrategy(weth.address, mockStrategy.address); + .setDefaultStrategy(mockStrategy.address); // Mint will allocate all to default strategy bc no buffer, no threshold await oethVault.connect(daniel).mint(weth.address, oethUnits("10"), "0"); @@ -575,14 +438,14 @@ describe("OETH Vault", function () { let mockStrategy; beforeEach(async () => { // Deploy default strategy - const { oethVault, weth } = fixture; + const { oethVault } = fixture; mockStrategy = await deployWithConfirmation("MockStrategy"); await oethVault .connect(await impersonateAndFund(await oethVault.governor())) .approveStrategy(mockStrategy.address); await oethVault .connect(await impersonateAndFund(await oethVault.governor())) - .setAssetDefaultStrategy(weth.address, mockStrategy.address); + .setDefaultStrategy(mockStrategy.address); }); it("buffer is 0%, 0 WETH in queue", async () => { const { oethVault, daniel, weth } = fixture; @@ -745,34 +608,11 @@ describe("OETH Vault", function () { fixtureWithUser ); }); - it("Should request withdrawal of zero amount", async () => { + it("Fail to request withdrawal of zero amount", async () => { const { oethVault, josh } = fixture; - const fixtureWithUser = { ...fixture, user: josh }; - await oethVault.connect(josh).requestWithdrawal(firstRequestAmount); - const dataBefore = await snapData(fixtureWithUser); - - const tx = await oethVault.connect(josh).requestWithdrawal(0); - await expect(tx) - .to.emit(oethVault, "WithdrawalRequested") - .withArgs(josh.address, 1, 0, firstRequestAmount); - - await assertChangedData( - dataBefore, - { - oethTotalSupply: 0, - oethTotalValue: 0, - vaultCheckBalance: 0, - userOeth: 0, - userWeth: 0, - vaultWeth: 0, - queued: 0, - claimable: 0, - claimed: 0, - nextWithdrawalIndex: 1, - }, - fixtureWithUser - ); + const tx = oethVault.connect(josh).requestWithdrawal(0); + await expect(tx).to.be.revertedWith("Amount must be greater than 0"); }); it("Should request first and second withdrawals with no WETH in the Vault", async () => { const { oethVault, governor, josh, matt, weth } = fixture; @@ -1119,7 +959,7 @@ describe("OETH Vault", function () { [weth.address], [depositAmount] ); - await expect(tx).to.be.revertedWith("Not enough WETH available"); + await expect(tx).to.be.revertedWith("Not enough assets available"); }); it("Fail to deposit allocated WETH during allocate", async () => { const { oethVault, governor, weth } = fixture; @@ -1127,7 +967,7 @@ describe("OETH Vault", function () { // Set mock strategy as default strategy await oethVault .connect(governor) - .setAssetDefaultStrategy(weth.address, mockStrategy.address); + .setDefaultStrategy(mockStrategy.address); // and buffer to 10% await oethVault.connect(governor).setVaultBuffer(oethUnits("0.1")); @@ -1647,7 +1487,7 @@ describe("OETH Vault", function () { [oethUnits("1")] ); - await expect(tx).to.be.revertedWith("Not enough WETH available"); + await expect(tx).to.be.revertedWith("Not enough assets available"); }); it("Fail to allocate any WETH to the default strategy", async () => { const { oethVault, domen } = fixture; @@ -1690,7 +1530,7 @@ describe("OETH Vault", function () { [oethUnits("1.1")] ); - await expect(tx).to.be.revertedWith("Not enough WETH available"); + await expect(tx).to.be.revertedWith("Not enough assets available"); }); it("Fail to allocate any WETH to the default strategy", async () => { const { oethVault, domen } = fixture; @@ -1733,14 +1573,14 @@ describe("OETH Vault", function () { [oethUnits("5")] ); - await expect(tx).to.be.revertedWith("Not enough WETH available"); + await expect(tx).to.be.revertedWith("Not enough assets available"); }); it("Should allocate 3 WETH to the default strategy", async () => { const { oethVault, governor, domen, weth } = fixture; await oethVault .connect(governor) - .setAssetDefaultStrategy(weth.address, mockStrategy.address); + .setDefaultStrategy(mockStrategy.address); const vaultBalance = await weth.balanceOf(oethVault.address); const stratBalance = await weth.balanceOf(mockStrategy.address); @@ -1965,7 +1805,7 @@ describe("OETH Vault", function () { await oethVault.connect(governor).approveStrategy(mockStrategy.address); await oethVault .connect(governor) - .setAssetDefaultStrategy(weth.address, mockStrategy.address); + .setDefaultStrategy(mockStrategy.address); // Mint 60 OETH to three users await oethVault @@ -2125,7 +1965,7 @@ describe("OETH Vault", function () { await oethVault.connect(governor).approveStrategy(mockStrategy.address); await oethVault .connect(governor) - .setAssetDefaultStrategy(weth.address, mockStrategy.address); + .setDefaultStrategy(mockStrategy.address); // Mint 100 OETH to three users await oethVault diff --git a/contracts/test/vault/oeth-vault.mainnet.fork-test.js b/contracts/test/vault/oeth-vault.mainnet.fork-test.js index e797a5492d..4e144a48b3 100644 --- a/contracts/test/vault/oeth-vault.mainnet.fork-test.js +++ b/contracts/test/vault/oeth-vault.mainnet.fork-test.js @@ -49,14 +49,6 @@ describe("ForkTest: OETH Vault", function () { ); } }); - - it("Should have correct WETH asset index cached", async () => { - const { oethVault, weth } = fixture; - const index = await oethVault.wethAssetIndex(); - const assets = await oethVault.getAllAssets(); - - expect(assets[index]).to.equal(weth.address); - }); }); describe("user operations", () => { @@ -105,15 +97,7 @@ describe("ForkTest: OETH Vault", function () { }); it("should mint with WETH and allocate to strategy", async () => { - const { oethVault, nativeStakingSSVStrategy, weth, josh, strategist } = - fixture; - - oethVault - .connect(strategist) - .setAssetDefaultStrategy( - weth.address, - nativeStakingSSVStrategy.address - ); + const { oethVault, weth, josh } = fixture; const amount = parseUnits("11", 18); const minOeth = parseUnits("8", 18); @@ -131,139 +115,26 @@ describe("ForkTest: OETH Vault", function () { .withArgs(josh.address, amount); }); - it("should not mint with any other asset", async () => { - const { oethVault, frxETH, stETH, reth, josh } = fixture; + it("should mint when specifying any other assets", async () => { + const { oethVault, frxETH, stETH, reth, weth, josh } = fixture; const amount = parseUnits("1", 18); const minOeth = parseUnits("0.8", 18); for (const asset of [frxETH, stETH, reth]) { - await asset.connect(josh).approve(oethVault.address, amount); - const tx = oethVault.connect(josh).mint(asset.address, amount, minOeth); - - await expect(tx).to.be.revertedWith("Unsupported asset for minting"); - } - }); - - it("should have redeem fee", async () => { - const { oethVault } = fixture; - - expect(await oethVault.redeemFeeBps()).to.equal(1000); - }); - - it("should return only WETH in redeem calculations", async () => { - const { oethVault } = fixture; - - const output = await oethVault.calculateRedeemOutputs(oethUnits("123")); - const index = await oethVault.wethAssetIndex(); - - expect(output[index]).to.equal(oethUnits("123").mul("9000").div("10000")); - - output.map((x, i) => { - if (i !== index.toNumber()) { - expect(x).to.equal("0"); - } - }); - }); - - it("should allow strategist to redeem without fee", async () => { - const { oethVault, strategist, matt, weth, oeth } = fixture; - - const sGovernor = await impersonateAndFund(addresses.mainnet.Timelock); - // make sure to not trigger rebase on redeem - await oethVault.connect(sGovernor).setRebaseThreshold(oethUnits("11")); - - // Send a heap of WETH to the vault so it can be redeemed - await weth.connect(matt).transfer(oethVault.address, oethUnits("1000")); - await weth.connect(matt).transfer(strategist.address, oethUnits("100")); - - const amount = oethUnits("10"); - - await weth.connect(strategist).approve(oethVault.address, amount); - - // Mint 1:1 - await oethVault.connect(strategist).mint(weth.address, amount, amount); - - const oethBalanceBefore = await oeth.balanceOf(strategist.address); - const wethBalanceBefore = await weth.balanceOf(strategist.address); - - // Redeem 1:1 instantly - await oethVault.connect(strategist).redeem(amount, amount); - - const oethBalanceAfter = await oeth.balanceOf(strategist.address); - const wethBalanceAfter = await weth.balanceOf(strategist.address); - - expect(oethBalanceAfter).to.equal(oethBalanceBefore.sub(amount)); - expect(wethBalanceAfter).to.equal(wethBalanceBefore.add(amount)); - }); - - it("should enforce fee on other users for instant redeem", async () => { - const { oethVault, josh, matt, weth, oeth } = fixture; - - // Send a heap of WETH to the vault so it can be redeemed - await weth.connect(matt).transfer(oethVault.address, oethUnits("1000")); - - const amount = oethUnits("10"); - const expectedWETH = amount.mul("9000").div("10000"); - - await weth.connect(josh).approve(oethVault.address, amount); - - // Mint 1:1 - await oethVault.connect(josh).mint(weth.address, amount, amount); - - const oethBalanceBefore = await oeth.balanceOf(josh.address); - const wethBalanceBefore = await weth.balanceOf(josh.address); + const wethBefore = await weth.balanceOf(josh.address); + const assetBefore = await asset.balanceOf(josh.address); - // Redeem 1:1 instantly - await oethVault.connect(josh).redeem(amount, expectedWETH); + // Mints with WETH even though other assets are specified + await oethVault.connect(josh).mint(asset.address, amount, minOeth); - const oethBalanceAfter = await oeth.balanceOf(josh.address); - const wethBalanceAfter = await weth.balanceOf(josh.address); - - expect(oethBalanceAfter).to.equal(oethBalanceBefore.sub(amount)); - expect(wethBalanceAfter).to.equal(wethBalanceBefore.add(expectedWETH)); - }); - - it("should partially redeem 10 OETH", async () => { - const { domen, oeth, oethVault, weth } = fixture; - - expect(await oeth.balanceOf(oethWhaleAddress)).to.gt(10); - - const redeemAmount = parseUnits("10", 18); - const minEth = parseUnits("9", 18); - - // Calculate how much to mint based on the WETH in the vault, - // the withdrawal queue, and the WETH to be redeemed - const wethBalance = await weth.balanceOf(oethVault.address); - const queue = await oethVault.withdrawalQueueMetadata(); - const available = wethBalance.add(queue.claimed).sub(queue.queued); - const mintAmount = redeemAmount.sub(available); - if (mintAmount.gt(0)) { - await weth.connect(domen).transfer(oethVault.address, mintAmount); + expect(await weth.balanceOf(josh.address)).to.eq( + wethBefore.sub(amount) + ); + expect(await asset.balanceOf(josh.address)).to.eq(assetBefore); } - - const tx = await oethVault - .connect(oethWhaleSigner) - .redeem(redeemAmount, minEth); - - await logTxDetails(tx, "redeem"); - - await expect(tx) - .to.emit(oethVault, "Redeem") - .withNamedArgs({ _addr: oethWhaleAddress }); }); - it.skip("should not do full redeem by OETH whale", async () => { - const { oeth, oethVault } = fixture; - - const oethWhaleBalance = await oeth.balanceOf(oethWhaleAddress); - expect(oethWhaleBalance, "no longer an OETH whale").to.gt( - parseUnits("1000", 18) - ); - - const tx = oethVault.connect(oethWhaleSigner).redeem(oethWhaleBalance, 0); - await expect(tx).to.revertedWith("Liquidity error"); - }); it("should request a withdraw by OETH whale", async () => { const { oeth, oethVault } = fixture; @@ -287,10 +158,10 @@ describe("ForkTest: OETH Vault", function () { }); }); it("should claim withdraw by a OETH whale", async () => { - const { domen, oeth, oethVault, weth } = fixture; + const { domen, oeth, oethVault, weth, matt } = fixture; let oethWhaleBalance = await oeth.balanceOf(oethWhaleAddress); - + await depositDiffInWeth(fixture, matt); // Calculate how much to mint based on the WETH in the vault, // the withdrawal queue, and the WETH to be withdrawn const wethBalance = await weth.balanceOf(oethVault.address); @@ -326,27 +197,30 @@ describe("ForkTest: OETH Vault", function () { .withArgs(oethWhaleAddress, requestId, oethWhaleBalance); }); it("OETH whale can redeem after withdraw from all strategies", async () => { - const { oeth, oethVault, timelock } = fixture; + const { oeth, oethVault, timelock, matt } = fixture; const oethWhaleBalance = await oeth.balanceOf(oethWhaleAddress); log(`OETH whale balance: ${formatUnits(oethWhaleBalance)}`); expect(oethWhaleBalance, "no longer an OETH whale").to.gt( parseUnits("1000", 18) ); + await depositDiffInWeth(fixture, matt); await oethVault.connect(timelock).withdrawAllFromStrategies(); - const tx = await oethVault + const { nextWithdrawalIndex: requestId } = + await oethVault.withdrawalQueueMetadata(); + + await oethVault .connect(oethWhaleSigner) - .redeem(oethWhaleBalance, 0); - await expect(tx) - .to.emit(oethVault, "Redeem") - .withNamedArgs({ _addr: oethWhaleAddress }); + .requestWithdrawal(oethWhaleBalance); + await advanceTime(delayPeriod); // Advance in time to ensure time delay between request and claim. + await await oethVault.connect(oethWhaleSigner).claimWithdrawal(requestId); }); it("Vault should have the right WETH address", async () => { const { oethVault } = fixture; - expect((await oethVault.weth()).toLowerCase()).to.equal( + expect((await oethVault.asset()).toLowerCase()).to.equal( addresses.mainnet.WETH.toLowerCase() ); }); @@ -362,30 +236,25 @@ describe("ForkTest: OETH Vault", function () { }); }); - // We have migrated to simplified Harvester and this is no longer relevant - // shouldHaveRewardTokensConfigured(() => ({ - // vault: fixture.oethVault, - // harvester: fixture.oethHarvester, - // ignoreTokens: [fixture.weth.address.toLowerCase()], - // expectedConfigs: { - // [fixture.cvx.address]: { - // allowedSlippageBps: 300, - // harvestRewardBps: 200, - // swapPlatformAddr: "0xB576491F1E6e5E62f1d8F26062Ee822B40B0E0d4", - // doSwapRewardToken: true, - // swapPlatform: 3, - // liquidationLimit: oethUnits("2500"), - // curvePoolIndices: [1, 0], - // }, - // [fixture.crv.address]: { - // allowedSlippageBps: 300, - // harvestRewardBps: 200, - // swapPlatformAddr: "0x4eBdF703948ddCEA3B11f675B4D1Fba9d2414A14", - // doSwapRewardToken: true, - // swapPlatform: 3, - // liquidationLimit: oethUnits("4000"), - // curvePoolIndices: [2, 1], - // }, - // }, - // })); + /** + * Checks the difference between withdrawalQueueMetadata[0] and [2] + * and deposits this diff in WETH. + * @param {Object} fixture + * @param {Object} depositor signer to perform the deposit + */ + async function depositDiffInWeth(fixture, depositor) { + const { oethVault, weth } = fixture; + + // Get withdrawalQueueMetadata[0] and [2] + const metadata = await oethVault.withdrawalQueueMetadata(); + const queue = metadata.queued; + const claimed = metadata.claimed; + + if (queue > claimed) { + const diff = queue.sub(claimed).mul(110).div(100); + await weth.connect(depositor).approve(oethVault.address, diff); + return await oethVault.connect(depositor).mint(weth.address, diff, 0); + } + return null; + } }); diff --git a/contracts/test/vault/oethb-vault.base.fork-test.js b/contracts/test/vault/oethb-vault.base.fork-test.js index 2dc81d2782..8232b5c937 100644 --- a/contracts/test/vault/oethb-vault.base.fork-test.js +++ b/contracts/test/vault/oethb-vault.base.fork-test.js @@ -51,83 +51,6 @@ describe("ForkTest: OETHb Vault", function () { 0.1 ); }); - - it("Should allow only Strategist to redeem", async () => { - const { strategist, oethbVault, oethb, weth, rafael } = fixture; - - // Add WETH liquidity to allow redeem - await weth - .connect(rafael) - .transfer(oethbVault.address, oethUnits("3000")); - - await oethbVault.rebase(); - await _mint(strategist); - - const vaultBalanceBefore = await weth.balanceOf(oethbVault.address); - const userBalanceBefore = await oethb.balanceOf(strategist.address); - const totalSupplyBefore = await oethb.totalSupply(); - - await oethbVault.connect(strategist).redeem(oethUnits("1"), "0"); - - const vaultBalanceAfter = await weth.balanceOf(oethbVault.address); - const userBalanceAfter = await oethb.balanceOf(strategist.address); - const totalSupplyAfter = await oethb.totalSupply(); - - expect(totalSupplyAfter).to.approxEqualTolerance( - totalSupplyBefore.sub(oethUnits("1")) - ); - expect(userBalanceAfter).to.approxEqualTolerance( - userBalanceBefore.sub(oethUnits("1")) - ); - expect(vaultBalanceAfter).to.approxEqualTolerance( - vaultBalanceBefore.sub(oethUnits("1")) - ); - }); - - it("Should allow only Governor to redeem", async () => { - const { governor, oethbVault, oethb, weth, rafael } = fixture; - - // Add WETH liquidity to allow redeem - await weth - .connect(rafael) - .transfer(oethbVault.address, oethUnits("3000")); - - await oethbVault.rebase(); - await _mint(governor); - - const vaultBalanceBefore = await weth.balanceOf(oethbVault.address); - const userBalanceBefore = await oethb.balanceOf(governor.address); - const totalSupplyBefore = await oethb.totalSupply(); - - await oethbVault.connect(governor).redeem(oethUnits("1"), "0"); - - const vaultBalanceAfter = await weth.balanceOf(oethbVault.address); - const userBalanceAfter = await oethb.balanceOf(governor.address); - const totalSupplyAfter = await oethb.totalSupply(); - - expect(totalSupplyAfter).to.approxEqualTolerance( - totalSupplyBefore.sub(oethUnits("1")) - ); - expect(userBalanceAfter).to.approxEqualTolerance( - userBalanceBefore.sub(oethUnits("1")) - ); - expect(vaultBalanceAfter).to.approxEqualTolerance( - vaultBalanceBefore.sub(oethUnits("1")) - ); - }); - - it("No one else can redeem", async () => { - const { rafael, nick, oethbVault } = fixture; - - await oethbVault.rebase(); - - for (const signer of [rafael, nick]) { - await _mint(signer); - await expect( - oethbVault.connect(signer).redeem(oethUnits("1"), "0") - ).to.be.revertedWith("Caller is not the Strategist or Governor"); - } - }); }); describe("Async withdrawals", function () { @@ -137,7 +60,10 @@ describe("ForkTest: OETHb Vault", function () { // Add WETH liquidity to allow withdrawal await weth .connect(rafael) - .transfer(oethbVault.address, oethUnits("3000")); + .approve(oethbVault.address, oethUnits("10000")); + await oethbVault + .connect(rafael) + .mint(weth.address, oethUnits("10000"), 0); const delayPeriod = await oethbVault.withdrawalClaimDelay(); @@ -238,7 +164,7 @@ describe("ForkTest: OETHb Vault", function () { }); }); - describe("Mint Whitelist", function () { + describe.skip("Mint Whitelist", function () { it("Should allow a strategy to be added to the whitelist", async () => { const { oethbVault, governor } = fixture; @@ -321,7 +247,7 @@ describe("ForkTest: OETHb Vault", function () { }); }); - describe("Mint & Burn For Strategy", function () { + describe.skip("Mint & Burn For Strategy", function () { let strategySigner, mockStrategy; beforeEach(async () => { diff --git a/contracts/test/vault/oethb-vault.base.js b/contracts/test/vault/oethb-vault.base.js index 75c55aa396..f36fd1d9c5 100644 --- a/contracts/test/vault/oethb-vault.base.js +++ b/contracts/test/vault/oethb-vault.base.js @@ -1,10 +1,8 @@ const { createFixtureLoader } = require("../_fixture"); const { defaultBaseFixture } = require("../_fixture-base"); const { expect } = require("chai"); -const addresses = require("../../utils/addresses"); const { impersonateAndFund } = require("../../utils/signers"); const { oethUnits } = require("../helpers"); -const { deployWithConfirmation } = require("../../utils/deploy"); const baseFixture = createFixtureLoader(defaultBaseFixture); @@ -17,96 +15,85 @@ describe("OETHb Vault", function () { }); it("Should allow a strategy to be added to the whitelist", async () => { - const { oethbVault, governor } = fixture; + const { oethbVault, governor, mockStrategy } = fixture; // Pretend addresses.dead is a strategy - await oethbVault.connect(governor).approveStrategy(addresses.dead); + await oethbVault.connect(governor).approveStrategy(mockStrategy.address); const tx = oethbVault .connect(governor) - .addStrategyToMintWhitelist(addresses.dead); + .addStrategyToMintWhitelist(mockStrategy.address); await expect(tx).to.emit(oethbVault, "StrategyAddedToMintWhitelist"); - expect(await oethbVault.isMintWhitelistedStrategy(addresses.dead)).to.be - .true; + expect(await oethbVault.isMintWhitelistedStrategy(mockStrategy.address)) + .to.be.true; }); it("Should allow a strategy to be removed from the whitelist", async () => { - const { oethbVault, governor } = fixture; + const { oethbVault, governor, mockStrategy } = fixture; // Pretend addresses.dead is a strategy - await oethbVault.connect(governor).approveStrategy(addresses.dead); + await oethbVault.connect(governor).approveStrategy(mockStrategy.address); await oethbVault .connect(governor) - .addStrategyToMintWhitelist(addresses.dead); + .addStrategyToMintWhitelist(mockStrategy.address); // Remove it const tx = oethbVault .connect(governor) - .removeStrategyFromMintWhitelist(addresses.dead); + .removeStrategyFromMintWhitelist(mockStrategy.address); await expect(tx).to.emit(oethbVault, "StrategyRemovedFromMintWhitelist"); - expect(await oethbVault.isMintWhitelistedStrategy(addresses.dead)).to.be - .false; + expect(await oethbVault.isMintWhitelistedStrategy(mockStrategy.address)) + .to.be.false; }); it("Should not allow non-governor to add to whitelist", async () => { - const { oethbVault, rafael } = fixture; + const { oethbVault, rafael, mockStrategy } = fixture; const tx = oethbVault .connect(rafael) - .addStrategyToMintWhitelist(addresses.dead); + .addStrategyToMintWhitelist(mockStrategy.address); await expect(tx).to.be.revertedWith("Caller is not the Governor"); }); it("Should not allow non-governor to remove from whitelist", async () => { - const { oethbVault, rafael } = fixture; + const { oethbVault, rafael, mockStrategy } = fixture; const tx = oethbVault .connect(rafael) - .removeStrategyFromMintWhitelist(addresses.dead); + .removeStrategyFromMintWhitelist(mockStrategy.address); await expect(tx).to.be.revertedWith("Caller is not the Governor"); }); it("Should not allow adding unapproved strategy", async () => { - const { oethbVault, governor } = fixture; + const { oethbVault, governor, mockStrategy } = fixture; const tx = oethbVault .connect(governor) - .addStrategyToMintWhitelist(addresses.dead); + .addStrategyToMintWhitelist(mockStrategy.address); await expect(tx).to.be.revertedWith("Strategy not approved"); }); it("Should not whitelist if already whitelisted", async () => { - const { oethbVault, governor } = fixture; + const { oethbVault, governor, mockStrategy } = fixture; - await oethbVault.connect(governor).approveStrategy(addresses.dead); + await oethbVault.connect(governor).approveStrategy(mockStrategy.address); await oethbVault .connect(governor) - .addStrategyToMintWhitelist(addresses.dead); + .addStrategyToMintWhitelist(mockStrategy.address); const tx = oethbVault .connect(governor) - .addStrategyToMintWhitelist(addresses.dead); + .addStrategyToMintWhitelist(mockStrategy.address); await expect(tx).to.be.revertedWith("Already whitelisted"); }); it("Should revert when removing unwhitelisted strategy", async () => { - const { oethbVault, governor } = fixture; + const { oethbVault, governor, mockStrategy } = fixture; const tx = oethbVault .connect(governor) - .removeStrategyFromMintWhitelist(addresses.dead); + .removeStrategyFromMintWhitelist(mockStrategy.address); await expect(tx).to.be.revertedWith("Not whitelisted"); }); - - describe("Disabled functions", function () { - it("Should not support redeem", async () => { - const { oethbVault, nick } = fixture; - - const tx = oethbVault.connect(nick).redeem(1, 0); - await expect(tx).to.be.revertedWith( - "Caller is not the Strategist or Governor" - ); - }); - }); }); describe("Mint & Burn For Strategy", function () { @@ -115,8 +102,7 @@ describe("OETHb Vault", function () { beforeEach(async () => { fixture = await baseFixture(); const { oethbVault, governor } = fixture; - - mockStrategy = await deployWithConfirmation("MockStrategy"); + mockStrategy = fixture.mockStrategy; await oethbVault.connect(governor).approveStrategy(mockStrategy.address); await oethbVault @@ -163,28 +149,26 @@ describe("OETHb Vault", function () { it("Should not allow a non-white listed strategy to mint", async () => { const { oethbVault, governor } = fixture; - // Pretend addresses.dead is a strategy - await oethbVault.connect(governor).approveStrategy(addresses.dead); + await oethbVault + .connect(governor) + .removeStrategyFromMintWhitelist(mockStrategy.address); const amount = oethUnits("1"); - const tx = oethbVault - .connect(await impersonateAndFund(addresses.dead)) - .mintForStrategy(amount); + const tx = oethbVault.connect(strategySigner).mintForStrategy(amount); await expect(tx).to.be.revertedWith("Not whitelisted strategy"); }); it("Should not allow a non-white listed strategy to burn", async () => { const { oethbVault, governor } = fixture; - // Pretend addresses.dead is a strategy - await oethbVault.connect(governor).approveStrategy(addresses.dead); + await oethbVault + .connect(governor) + .removeStrategyFromMintWhitelist(mockStrategy.address); const amount = oethUnits("1"); - const tx = oethbVault - .connect(await impersonateAndFund(addresses.dead)) - .burnForStrategy(amount); + const tx = oethbVault.connect(strategySigner).burnForStrategy(amount); await expect(tx).to.be.revertedWith("Not whitelisted strategy"); }); }); diff --git a/contracts/test/vault/oneinch-swapper.js b/contracts/test/vault/oneinch-swapper.js deleted file mode 100644 index f8ae6466d6..0000000000 --- a/contracts/test/vault/oneinch-swapper.js +++ /dev/null @@ -1,524 +0,0 @@ -const { expect } = require("chai"); -const { utils, BigNumber } = require("ethers"); - -const { units, usdsUnits, usdtUnits } = require("../helpers"); -const { - createFixtureLoader, - oethCollateralSwapFixture, - ousdCollateralSwapFixture, - oeth1InchSwapperFixture, -} = require("../_fixture"); -const { - SWAP_SELECTOR, - UNISWAP_SELECTOR, - UNISWAPV3_SELECTOR, -} = require("../../utils/1Inch"); -const { impersonateAndFund } = require("../../utils/signers"); - -const log = require("../../utils/logger")("test:oeth:swapper"); - -describe("1Inch Swapper", () => { - describe("No OETH Collateral Swaps", () => { - let fixture; - const loadFixture = createFixtureLoader(oethCollateralSwapFixture); - beforeEach(async () => { - fixture = await loadFixture(); - }); - - it("Should revert stETH to WETH swap", async () => { - const { weth, stETH, oethVault, strategist } = fixture; - const fromAmount = utils.parseEther("100"); - const toAmount = utils.parseEther("100"); - - // Call swap method - const tx = oethVault - .connect(strategist) - .swapCollateral(stETH.address, weth.address, fromAmount, toAmount, []); - - await expect(tx).to.be.revertedWith("Collateral swap not supported"); - }); - - it("Should revert stETH to WETH swap", async () => { - const { stETH, weth, oethVault, strategist } = fixture; - const fromAmount = utils.parseEther("100"); - const toAmount = utils.parseEther("100"); - - // Call swap method - const tx = oethVault - .connect(strategist) - .swapCollateral(stETH.address, weth.address, fromAmount, toAmount, []); - - await expect(tx).to.be.revertedWith("Collateral swap not supported"); - }); - }); - describe("OUSD Collateral Swaps", () => { - let fixture; - const loadFixture = createFixtureLoader(ousdCollateralSwapFixture); - beforeEach(async () => { - fixture = await loadFixture(); - }); - - it("Should allow Governor to set slippage for assets", async () => { - const { usds, governor, vault } = fixture; - - const tx = vault.connect(governor).setOracleSlippage(usds.address, 123); - await expect(tx) - .to.emit(vault, "SwapSlippageChanged") - .withArgs(usds.address, 123); - }); - - it("Should not allow Governor to set slippage for unsupported assets", async () => { - const { governor, vault, weth } = fixture; - - const tx = vault.connect(governor).setOracleSlippage(weth.address, 123); - await expect(tx).to.be.revertedWith("Asset not supported"); - }); - - it("Should not allow anyone else to set slippage for assets", async () => { - const { usds, strategist, josh, vault } = fixture; - - for (const user of [strategist, josh]) { - const tx = vault.connect(user).setOracleSlippage(usds.address, 123); - await expect(tx).to.be.revertedWith("Caller is not the Governor"); - } - }); - - it("Should not allow Governor to set slippage above 10%", async () => { - const { usds, governor, vault } = fixture; - - const tx = vault.connect(governor).setOracleSlippage(usds.address, 1100); - await expect(tx).to.be.revertedWith("Slippage too high"); - }); - - it("Should allow to change Swapper address", async () => { - const { governor, vault, weth } = fixture; - - // Pretend WETH is swapper address - const tx = vault.connect(governor).setSwapper(weth.address); - - await expect(tx).to.emit(vault, "SwapperChanged").withArgs(weth.address); - - expect(await vault.swapper()).to.equal(weth.address); - }); - - it("Should not allow anyone else to set swapper address", async () => { - const { strategist, josh, vault, weth } = fixture; - - for (const user of [strategist, josh]) { - const tx = vault.connect(user).setSwapper(weth.address); - await expect(tx).to.be.revertedWith("Caller is not the Governor"); - } - }); - - it("Should allow the governor to change allowed swap undervalue", async () => { - const { governor, vault } = fixture; - - const tx = vault.connect(governor).setSwapAllowedUndervalue(10); - - await expect(tx) - .to.emit(vault, "SwapAllowedUndervalueChanged") - .withArgs(10); - - expect(await vault.allowedSwapUndervalue()).to.equal(10); - }); - - it("Should not allow anyone else to set allowed swap undervalue", async () => { - const { strategist, josh, vault } = fixture; - - for (const user of [strategist, josh]) { - const tx = vault.connect(user).setSwapAllowedUndervalue(10); - await expect(tx).to.be.revertedWith("Caller is not the Governor"); - } - }); - - it("Should allow the governor to set allowed swap undervalue to 100%", async () => { - const { governor, vault } = fixture; - - const hundredPercent = 10000; - const tx = vault - .connect(governor) - .setSwapAllowedUndervalue(hundredPercent); - - await expect(tx) - .to.emit(vault, "SwapAllowedUndervalueChanged") - .withArgs(hundredPercent); - - expect(await vault.allowedSwapUndervalue()).to.equal(hundredPercent); - }); - - it("Should not allow setting undervalue percentage over 100%", async () => { - const { governor, vault } = fixture; - - const tx = vault.connect(governor).setSwapAllowedUndervalue(10001); - - await expect(tx).to.be.revertedWith("Invalid basis points"); - }); - - it("Should allow to swap tokens", async () => { - const { usds, usdc, usdt, vault, strategist } = fixture; - - for (const fromAsset of [usds, usdc, usdt]) { - for (const toAsset of [usds, usdc, usdt]) { - if (fromAsset.address === toAsset.address) continue; - const fromAmount = await units("20", fromAsset); - const toAmount = await units("21", toAsset); - log( - `swapping 20 ${await fromAsset.symbol()} to ${await toAsset.symbol()}` - ); - expect(await fromAsset.balanceOf(vault.address)).to.gte(fromAmount); - - // Call swap method - const tx = await vault - .connect(strategist) - .swapCollateral( - fromAsset.address, - toAsset.address, - fromAmount, - toAmount, - [] - ); - - expect(tx) - .to.emit(vault, "Swapped") - .withArgs(fromAsset.address, toAsset.address, fromAmount, toAmount); - } - } - }); - - it("Should revert swap if received less tokens than strategist desired", async () => { - const { usds, usdt, vault, strategist, mockSwapper } = fixture; - - // Mock to return lower than slippage next time - await mockSwapper.connect(strategist).setNextOutAmount(usdsUnits("18")); - - const fromAmount = usdtUnits("20"); - const toAmount = usdsUnits("20"); - - // Call swap method - const tx = vault - .connect(strategist) - .swapCollateral(usdt.address, usds.address, fromAmount, toAmount, []); - - await expect(tx).to.be.revertedWith("Strategist slippage limit"); - }); - - it("Should revert swap if received less tokens than Oracle slippage", async () => { - const { usds, usdt, vault, strategist } = fixture; - - const fromAmount = usdtUnits("20"); - const toAmount = usdsUnits("16"); - - // Call swap method - const tx = vault - .connect(strategist) - .swapCollateral(usdt.address, usds.address, fromAmount, toAmount, []); - - await expect(tx).to.be.revertedWith("Oracle slippage limit exceeded"); - }); - - it("Should revert swap if value is under supply", async () => { - const { usds, usdt, oeth, vault, governor, strategist, mockSwapper } = - fixture; - - // Mock to return lower than slippage next time - await mockSwapper - .connect(strategist) - .setNextOutAmount(utils.parseEther("180")); - // increase the allowed Oracle slippage per asset to 9.99% - await vault.connect(governor).setOracleSlippage(usds.address, 999); - await vault.connect(governor).setOracleSlippage(usdt.address, 999); - - const fromAmount = usdtUnits("200"); - const toAmount = usdsUnits("170"); - - log(`total supply: ${await oeth.totalSupply()}`); - log(`total value : ${await vault.totalValue()}`); - - // Call swap method - const tx = vault - .connect(strategist) - .swapCollateral(usdt.address, usds.address, fromAmount, toAmount, []); - - await expect(tx).to.be.revertedWith("Allowed value < supply"); - - log(`total supply: ${await oeth.totalSupply()}`); - log(`total value : ${await vault.totalValue()}`); - }); - - it("Should allow swap if value is under supply by less than the allowed percentage", async () => { - const { usds, usdt, oeth, vault, governor, strategist, mockSwapper } = - fixture; - - // Mock to return lower than slippage next time - await mockSwapper.connect(strategist).setNextOutAmount(usdsUnits("19")); - // increase the allowed Oracle slippage per asset to 9.99% - await vault.connect(governor).setOracleSlippage(usds.address, 999); - await vault.connect(governor).setOracleSlippage(usdt.address, 999); - - const fromAmount = usdtUnits("20"); - const toAmount = usdsUnits("17"); - - log(`total supply: ${await oeth.totalSupply()}`); - log(`total value : ${await vault.totalValue()}`); - - // Call swap method - const tx = await vault - .connect(strategist) - .swapCollateral(usdt.address, usds.address, fromAmount, toAmount, []); - - await expect(tx).to.emit(vault, "Swapped"); - - log(`total supply: ${await oeth.totalSupply()}`); - log(`total value : ${await vault.totalValue()}`); - }); - - it("Should revert if fromAsset is not supported", async () => { - const { usds, weth, vault, strategist } = fixture; - const fromAmount = utils.parseEther("100"); - const toAmount = usdsUnits("100"); - - // Call swap method - const tx = vault - .connect(strategist) - .swapCollateral(weth.address, usds.address, fromAmount, toAmount, []); - - await expect(tx).to.be.revertedWith("From asset is not supported"); - }); - - it("Should revert if toAsset is not supported", async () => { - const { weth, usds, vault, strategist } = fixture; - const fromAmount = usdsUnits("100"); - const toAmount = utils.parseEther("100"); - - // Call swap method - const tx = vault - .connect(strategist) - .swapCollateral(usds.address, weth.address, fromAmount, toAmount, []); - - await expect(tx).to.be.revertedWith("To asset is not supported"); - }); - - it("Should swap if capital is paused", async () => { - const { usds, usdt, vault, strategist } = fixture; - const fromAmount = usdsUnits("100"); - const toAmount = usdtUnits("100"); - - // Fund Vault with some assets - const vaultSigner = await impersonateAndFund(vault.address); - await usds.connect(vaultSigner).mint(fromAmount); - - await vault.connect(strategist).pauseCapital(); - - // Call swap method - const tx = await vault - .connect(strategist) - .swapCollateral(usds.address, usdt.address, fromAmount, toAmount, []); - - expect(tx).to.emit(vault, "Swapped"); - }); - - it("Should revert if not called by Governor or Strategist", async () => { - const { usds, usdt, vault, josh } = fixture; - const fromAmount = usdsUnits("100"); - const toAmount = usdtUnits("100"); - - // Call swap method - const tx = vault - .connect(josh) - .swapCollateral(usds.address, usdt.address, fromAmount, toAmount, []); - - await expect(tx).to.be.revertedWith( - "Caller is not the Strategist or Governor" - ); - }); - }); - - describe.skip("1inch Swapper", () => { - let fixture; - const loadFixture = createFixtureLoader(oeth1InchSwapperFixture); - beforeEach(async () => { - fixture = await loadFixture(); - }); - - it("Should swap assets using 1inch router", async () => { - const { swapper1Inch, strategist, weth, frxETH, mock1InchSwapRouter } = - fixture; - - const deadAddr = "0x1111111111222222222233333333334444444444"; - - const data = utils.defaultAbiCoder.encode( - ["bytes4", "address", "bytes"], - [utils.arrayify(SWAP_SELECTOR), deadAddr, utils.arrayify("0xdead")] - ); - - const fromAmount = utils.parseEther("100"); - const toAmount = utils.parseEther("100"); - - await weth - .connect(strategist) - .mintTo(swapper1Inch.address, fromAmount.mul(2)); - await frxETH - .connect(strategist) - .mintTo(mock1InchSwapRouter.address, toAmount.mul(2)); - - const tx = swapper1Inch - .connect(strategist) - .swap(weth.address, frxETH.address, fromAmount, toAmount, data); - - await expect(tx) - .to.emit(mock1InchSwapRouter, "MockSwapDesc") - .withArgs( - weth.address, - frxETH.address, - deadAddr, - strategist.address, - fromAmount, - toAmount, - 4 - ); - - await expect(tx).to.emit( - mock1InchSwapRouter, - "MockSwap" - // ).withArgs( - // deadAddr, - // ['0', 'x'], - // utils.arrayify("0xdead") - ); - - const r = await (await tx).wait(); - expect(r.logs[3].data).to.equal( - "0x00000000000000000000000011111111112222222222333333333344444444440000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002dead000000000000000000000000000000000000000000000000000000000000" - ); - }); - - it("Should swap assets using Uniswap executor", async () => { - const { swapper1Inch, strategist, weth, frxETH, mock1InchSwapRouter } = - fixture; - - const data = utils.defaultAbiCoder.encode( - ["bytes4", "uint256[]"], - [ - utils.arrayify(UNISWAP_SELECTOR), - [BigNumber.from("123"), BigNumber.from("456")], - ] - ); - - const fromAmount = utils.parseEther("100"); - const toAmount = utils.parseEther("100"); - - await weth - .connect(strategist) - .mintTo(swapper1Inch.address, fromAmount.mul(2)); - await frxETH - .connect(strategist) - .mintTo(swapper1Inch.address, toAmount.mul(2)); - - const tx = swapper1Inch - .connect(strategist) - .swap(weth.address, frxETH.address, fromAmount, toAmount, data); - - await expect(tx) - .to.emit(mock1InchSwapRouter, "MockUnoswapTo") - .withArgs(strategist.address, weth.address, fromAmount, toAmount, [ - BigNumber.from("123"), - BigNumber.from("456"), - ]); - }); - - it("Should swap assets using Uniswap V3 executor", async () => { - const { swapper1Inch, strategist, weth, frxETH, mock1InchSwapRouter } = - fixture; - - const data = utils.defaultAbiCoder.encode( - ["bytes4", "uint256[]"], - [ - utils.arrayify(UNISWAPV3_SELECTOR), - [BigNumber.from("123"), BigNumber.from("456")], - ] - ); - - const fromAmount = utils.parseEther("100"); - const toAmount = utils.parseEther("100"); - - await weth - .connect(strategist) - .mintTo(swapper1Inch.address, fromAmount.mul(2)); - await frxETH - .connect(strategist) - .mintTo(swapper1Inch.address, toAmount.mul(2)); - - const tx = swapper1Inch - .connect(strategist) - .swap(weth.address, frxETH.address, fromAmount, toAmount, data); - - await expect(tx) - .to.emit(mock1InchSwapRouter, "MockUniswapV3SwapTo") - .withArgs(strategist.address, fromAmount, toAmount, [ - BigNumber.from("123"), - BigNumber.from("456"), - ]); - }); - - it("Should revert swap if fromAsset is insufficient ", async () => { - const { swapper1Inch, strategist, weth, frxETH } = fixture; - - const deadAddr = "0x1111111111222222222233333333334444444444"; - - const data = utils.defaultAbiCoder.encode( - ["bytes4", "address", "bytes"], - [utils.arrayify(SWAP_SELECTOR), deadAddr, utils.arrayify("0xdead")] - ); - - const fromAmount = utils.parseEther("100"); - const toAmount = utils.parseEther("100"); - - await frxETH - .connect(strategist) - .mintTo(swapper1Inch.address, toAmount.mul(2)); - - const tx = swapper1Inch - .connect(strategist) - .swap(weth.address, frxETH.address, fromAmount, toAmount, data); - - await expect(tx).to.be.revertedWith( - "ERC20: transfer amount exceeds balance" - ); - }); - - it("Should revert swap if router allowance is insufficient ", async () => { - const { swapper1Inch, strategist, weth, frxETH, mock1InchSwapRouter } = - fixture; - - const deadAddr = "0x1111111111222222222233333333334444444444"; - - const data = utils.defaultAbiCoder.encode( - ["bytes4", "address", "bytes"], - [utils.arrayify(SWAP_SELECTOR), deadAddr, utils.arrayify("0xdead")] - ); - - const fromAmount = utils.parseEther("100"); - const toAmount = utils.parseEther("100"); - - await weth - .connect(strategist) - .mintTo(swapper1Inch.address, toAmount.mul(2)); - await frxETH - .connect(strategist) - .mintTo(swapper1Inch.address, toAmount.mul(2)); - - // Reset allowance - await weth - .connect(await impersonateAndFund(swapper1Inch.address)) - .approve(mock1InchSwapRouter.address, 0); - - const tx = swapper1Inch - .connect(strategist) - .swap(weth.address, frxETH.address, fromAmount, toAmount, data); - - await expect(tx).to.be.revertedWith( - "ERC20: transfer amount exceeds allowance" - ); - }); - }); -}); diff --git a/contracts/test/vault/os-vault.sonic.js b/contracts/test/vault/os-vault.sonic.js index 37cd772451..3214c3c7b8 100644 --- a/contracts/test/vault/os-vault.sonic.js +++ b/contracts/test/vault/os-vault.sonic.js @@ -289,13 +289,6 @@ describe("Origin S Vault", function () { fixtureWithUser ); }); - - it("Should not support redeem", async () => { - const { oSonicVault, wS, nick } = fixture; - - const tx = oSonicVault.connect(nick).redeem(wS.address, 1); - await expect(tx).to.be.revertedWith("unsupported function"); - }); }); describe("Administer Sonic Staking Strategy", function () { diff --git a/contracts/test/vault/rebase.js b/contracts/test/vault/rebase.js index 67fe996539..c2aff4e085 100644 --- a/contracts/test/vault/rebase.js +++ b/contracts/test/vault/rebase.js @@ -3,11 +3,7 @@ const { expect } = require("chai"); const { loadDefaultFixture } = require("../_fixture"); const { ousdUnits, - usdsUnits, usdcUnits, - usdtUnits, - tusdUnits, - getOracleAddress, setOracleTokenPriceUsd, expectApproxSupply, } = require("../helpers"); @@ -169,24 +165,16 @@ describe("Vault rebase", () => { }); it("Should not allocate unallocated assets when no Strategy configured", async () => { - const { anna, governor, usds, usdc, usdt, tusd, vault } = fixture; + const { anna, governor, usdc, vault } = fixture; - await usds.connect(anna).transfer(vault.address, usdsUnits("100")); - await usdc.connect(anna).transfer(vault.address, usdcUnits("200")); - await usdt.connect(anna).transfer(vault.address, usdtUnits("300")); - await tusd.connect(anna).mintTo(vault.address, tusdUnits("400")); + await usdc.connect(anna).transfer(vault.address, usdcUnits("100")); expect(await vault.getStrategyCount()).to.equal(0); await vault.connect(governor).allocate(); - // All assets should still remain in Vault - - // Note defaultFixture sets up with 200 USDS already in the Strategy + // Note defaultFixture sets up with 200 USDC already in the Strategy // 200 + 100 = 300 - expect(await usds.balanceOf(vault.address)).to.equal(usdsUnits("300")); - expect(await usdc.balanceOf(vault.address)).to.equal(usdcUnits("200")); - expect(await usdt.balanceOf(vault.address)).to.equal(usdtUnits("300")); - expect(await tusd.balanceOf(vault.address)).to.equal(tusdUnits("400")); + expect(await usdc.balanceOf(vault.address)).to.equal(usdcUnits("300")); }); it("Should correctly handle a deposit of USDC (6 decimals)", async function () { @@ -199,24 +187,6 @@ describe("Vault rebase", () => { await vault.connect(anna).mint(usdc.address, usdcUnits("50"), 0); await expect(anna).has.a.balanceOf("50", ousd); }); - - it("Should allow priceProvider to be changed", async function () { - const { anna, governor, vault } = fixture; - - const oracle = await getOracleAddress(deployments); - await expect(await vault.priceProvider()).to.be.equal(oracle); - const annaAddress = await anna.getAddress(); - await vault.connect(governor).setPriceProvider(annaAddress); - await expect(await vault.priceProvider()).to.be.equal(annaAddress); - - // Only governor should be able to set it - await expect( - vault.connect(anna).setPriceProvider(oracle) - ).to.be.revertedWith("Caller is not the Governor"); - - await vault.connect(governor).setPriceProvider(oracle); - await expect(await vault.priceProvider()).to.be.equal(oracle); - }); }); describe("Vault yield accrual to OGN", async () => { @@ -230,7 +200,7 @@ describe("Vault rebase", () => { const { _yield, basis, expectedFee } = options; it(`should collect on rebase a ${expectedFee} fee from ${_yield} yield at ${basis}bp `, async function () { - const { matt, governor, ousd, usdt, vault, mockNonRebasing } = fixture; + const { matt, governor, ousd, usdc, vault, mockNonRebasing } = fixture; const trustee = mockNonRebasing; // Setup trustee on vault @@ -239,8 +209,8 @@ describe("Vault rebase", () => { await expect(trustee).has.a.balanceOf("0", ousd); // Create yield for the vault - await usdt.connect(matt).mint(usdcUnits(_yield)); - await usdt.connect(matt).transfer(vault.address, usdcUnits(_yield)); + await usdc.connect(matt).mint(usdcUnits(_yield)); + await usdc.connect(matt).transfer(vault.address, usdcUnits(_yield)); // Do rebase const supplyBefore = await ousd.totalSupply(); await vault.rebase(); diff --git a/contracts/test/vault/redeem.js b/contracts/test/vault/redeem.js index 5715cc9f51..60cbb2647e 100644 --- a/contracts/test/vault/redeem.js +++ b/contracts/test/vault/redeem.js @@ -1,19 +1,11 @@ const { expect } = require("chai"); -const { BigNumber } = require("ethers"); - const { loadDefaultFixture } = require("../_fixture"); -const { - ousdUnits, - usdsUnits, - usdcUnits, - usdtUnits, - setOracleTokenPriceUsd, - isFork, - expectApproxSupply, -} = require("../helpers"); - -describe("Vault Redeem", function () { +const { ousdUnits, usdcUnits, isFork, advanceTime } = require("../helpers"); +const { impersonateAndFund } = require("../../utils/signers"); +const { deployWithConfirmation } = require("../../utils/deploy"); + +describe("OUSD Vault Withdrawals", function () { if (isFork) { this.timeout(0); } @@ -23,420 +15,1692 @@ describe("Vault Redeem", function () { fixture = await loadDefaultFixture(); }); - it("Should allow a redeem", async () => { - const { ousd, vault, usdc, anna, usds } = fixture; - - await expect(anna).has.a.balanceOf("1000.00", usdc); - await expect(anna).has.a.balanceOf("1000.00", usds); - await usdc.connect(anna).approve(vault.address, usdcUnits("50.0")); - await vault.connect(anna).mint(usdc.address, usdcUnits("50.0"), 0); - await expect(anna).has.a.balanceOf("50.00", ousd); - await vault.connect(anna).redeem(ousdUnits("50.0"), 0); - await expect(anna).has.a.balanceOf("0.00", ousd); - // Redeem outputs will be 50/250 * 50 USDC and 200/250 * 50 USDS from fixture - await expect(anna).has.a.balanceOf("960.00", usdc); - await expect(anna).has.a.balanceOf("1040.00", usds); - expect(await ousd.totalSupply()).to.eq(ousdUnits("200.0")); - }); + const snapData = async (fixture) => { + const { ousd, vault, usdc, user } = fixture; + + const ousdTotalSupply = await ousd.totalSupply(); + const ousdTotalValue = await vault.totalValue(); + const vaultCheckBalance = await vault.checkBalance(usdc.address); + const userOusd = await ousd.balanceOf(user.address); + const userUsdc = await usdc.balanceOf(user.address); + const vaultUsdc = await usdc.balanceOf(vault.address); + const queue = await vault.withdrawalQueueMetadata(); + + return { + ousdTotalSupply, + ousdTotalValue, + vaultCheckBalance, + userOusd, + userUsdc, + vaultUsdc, + queue, + }; + }; + + const assertChangedData = async (dataBefore, delta, fixture) => { + const { ousd, vault, usdc, user } = fixture; - it("Should allow a redeem over the rebase threshold", async () => { - const { ousd, vault, usdc, anna, matt, usds } = fixture; + expect(await ousd.totalSupply(), "OUSD Total Supply").to.equal( + dataBefore.ousdTotalSupply.add(delta.ousdTotalSupply) + ); + expect(await vault.totalValue(), "Vault Total Value").to.equal( + dataBefore.ousdTotalValue.add(delta.ousdTotalValue) + ); + expect( + await vault.checkBalance(usdc.address), + "Vault Check Balance of USDC" + ).to.equal(dataBefore.vaultCheckBalance.add(delta.vaultCheckBalance)); + expect(await ousd.balanceOf(user.address), "user's OUSD balance").to.equal( + dataBefore.userOusd.add(delta.userOusd) + ); + expect(await usdc.balanceOf(user.address), "user's USDC balance").to.equal( + dataBefore.userUsdc.add(delta.userUsdc) + ); + expect(await usdc.balanceOf(vault.address), "Vault USDC balance").to.equal( + dataBefore.vaultUsdc.add(delta.vaultUsdc) + ); - await expect(anna).has.a.balanceOf("1000.00", usdc); - await expect(anna).has.a.balanceOf("1000.00", usds); + const queueAfter = await vault.withdrawalQueueMetadata(); + expect(queueAfter.queued, "Queued").to.equal( + dataBefore.queue.queued.add(delta.queued) + ); + expect(queueAfter.claimable, "Claimable").to.equal( + dataBefore.queue.claimable.add(delta.claimable) + ); + expect(queueAfter.claimed, "Claimed").to.equal( + dataBefore.queue.claimed.add(delta.claimed) + ); + expect(queueAfter.nextWithdrawalIndex, "nextWithdrawalIndex").to.equal( + dataBefore.queue.nextWithdrawalIndex.add(delta.nextWithdrawalIndex) + ); + }; + + describe("Withdrawal Queue", function () { + const delayPeriod = 10 * 60; // 10 minutes + beforeEach(async () => { + const { vault, josh, matt } = fixture; + + // In the fixture Matt and Josh mint 100 OUSD + // We should redeem that first to have only the 60 OUSD from USDC minting + await vault.connect(josh).requestWithdrawal(ousdUnits("100")); + await vault.connect(matt).requestWithdrawal(ousdUnits("100")); + + await advanceTime(delayPeriod); // 10 minutes + + await vault.connect(josh).claimWithdrawal(0); + await vault.connect(matt).claimWithdrawal(1); + }); + describe("with all 60 USDC in the vault", () => { + beforeEach(async () => { + const { vault, usdc, daniel, josh, matt } = fixture; + + // Fund three users with USDC + await usdc.mintTo(daniel.address, usdcUnits("10")); + await usdc.mintTo(josh.address, usdcUnits("20")); + await usdc.mintTo(matt.address, usdcUnits("30")); + + // Approve vault to spend USDC + await usdc.connect(daniel).approve(vault.address, usdcUnits("10")); + await usdc.connect(josh).approve(vault.address, usdcUnits("20")); + await usdc.connect(matt).approve(vault.address, usdcUnits("30")); + + // Mint some OUSD to three users + await vault.connect(daniel).mint(usdc.address, usdcUnits("10"), "0"); + await vault.connect(josh).mint(usdc.address, usdcUnits("20"), "0"); + await vault.connect(matt).mint(usdc.address, usdcUnits("30"), "0"); + + // Set max supply diff to 3% to allow withdrawals + await vault + .connect(await impersonateAndFund(await vault.governor())) + .setMaxSupplyDiff(ousdUnits("0.03")); + }); + const firstRequestAmountOUSD = ousdUnits("5"); + const firstRequestAmountUSDC = usdcUnits("5"); + const secondRequestAmountOUSD = ousdUnits("18"); + const secondRequestAmountUSDC = usdcUnits("18"); + + // Positive Test + it("Should request first withdrawal by Daniel", async () => { + const { vault, daniel } = fixture; + const fixtureWithUser = { ...fixture, user: daniel }; + const dataBefore = await snapData(fixtureWithUser); + + const tx = await vault + .connect(daniel) + .requestWithdrawal(firstRequestAmountOUSD); + + const queuedAmount = usdcUnits("205"); // 100 + 100 + 5 + + await expect(tx) + .to.emit(vault, "WithdrawalRequested") + .withArgs(daniel.address, 2, firstRequestAmountOUSD, queuedAmount); + + await assertChangedData( + dataBefore, + { + ousdTotalSupply: firstRequestAmountOUSD.mul(-1), + ousdTotalValue: firstRequestAmountOUSD.mul(-1), + vaultCheckBalance: firstRequestAmountUSDC.mul(-1), + userOusd: firstRequestAmountOUSD.mul(-1), + userUsdc: 0, + vaultUsdc: 0, + queued: firstRequestAmountUSDC, + claimable: 0, + claimed: 0, + nextWithdrawalIndex: 1, + }, + fixtureWithUser + ); + }); + it("Should revert withdrawal of zero amount", async () => { + const { vault, josh } = fixture; + const tx = vault.connect(josh).requestWithdrawal(0); + await expect(tx).to.be.revertedWith("Amount must be greater than 0"); + }); + it("Should request first and second withdrawals with no USDC in the Vault", async () => { + const { vault, governor, josh, matt, usdc } = fixture; + const fixtureWithUser = { ...fixture, user: josh }; + + const mockStrategy = await deployWithConfirmation("MockStrategy"); + await vault.connect(governor).approveStrategy(mockStrategy.address); + + // Deposit all 10 + 20 + 30 = 60 USDC to strategy + await vault + .connect(governor) + .depositToStrategy( + mockStrategy.address, + [usdc.address], + [usdcUnits("60")] + ); - await expect(anna).has.a.balanceOf("0.00", ousd); - await expect(matt).has.a.balanceOf("100.00", ousd); + const dataBefore = await snapData(fixtureWithUser); + + await vault.connect(josh).requestWithdrawal(firstRequestAmountOUSD); + const tx = await vault + .connect(matt) + .requestWithdrawal(secondRequestAmountOUSD); + + const queuedAmount = usdcUnits("223"); // 100 + 100 + 5 + 18 + + await expect(tx) + .to.emit(vault, "WithdrawalRequested") + .withArgs(matt.address, 3, secondRequestAmountOUSD, queuedAmount); + + await assertChangedData( + dataBefore, + { + ousdTotalSupply: firstRequestAmountOUSD + .add(secondRequestAmountOUSD) + .mul(-1), + ousdTotalValue: firstRequestAmountOUSD + .add(secondRequestAmountOUSD) + .mul(-1), + vaultCheckBalance: firstRequestAmountUSDC + .add(secondRequestAmountUSDC) + .mul(-1), + userOusd: firstRequestAmountOUSD.mul(-1), + userUsdc: 0, + vaultUsdc: 0, + queued: firstRequestAmountUSDC.add(secondRequestAmountUSDC), + claimable: 0, + claimed: 0, + nextWithdrawalIndex: 2, + }, + fixtureWithUser + ); + }); + it("Should request second withdrawal by matt", async () => { + const { vault, daniel, matt } = fixture; + const fixtureWithUser = { ...fixture, user: matt }; + await vault.connect(daniel).requestWithdrawal(firstRequestAmountOUSD); + const dataBefore = await snapData(fixtureWithUser); + + const tx = await vault + .connect(matt) + .requestWithdrawal(secondRequestAmountOUSD); + + const queuedAmount = usdcUnits("223"); // 100 + 100 + 5 + 18 + + await expect(tx) + .to.emit(vault, "WithdrawalRequested") + .withArgs(matt.address, 3, secondRequestAmountOUSD, queuedAmount); + + await assertChangedData( + dataBefore, + { + ousdTotalSupply: secondRequestAmountOUSD.mul(-1), + ousdTotalValue: secondRequestAmountOUSD.mul(-1), + vaultCheckBalance: secondRequestAmountUSDC.mul(-1), + userOusd: secondRequestAmountOUSD.mul(-1), + userUsdc: 0, + vaultUsdc: 0, + queued: secondRequestAmountUSDC, + claimable: 0, + claimed: 0, + nextWithdrawalIndex: 1, + }, + fixtureWithUser + ); + }); + it("Should add claimable liquidity to the withdrawal queue", async () => { + const { vault, daniel, josh } = fixture; + const fixtureWithUser = { ...fixture, user: josh }; + await vault.connect(daniel).requestWithdrawal(firstRequestAmountOUSD); + await vault.connect(josh).requestWithdrawal(secondRequestAmountOUSD); + const dataBefore = await snapData(fixtureWithUser); + + const tx = await vault.connect(josh).addWithdrawalQueueLiquidity(); + + const claimableAmount = usdcUnits("223"); // 100 + 100 + 5 + 18 + + await expect(tx) + .to.emit(vault, "WithdrawalClaimable") + .withArgs( + claimableAmount, + firstRequestAmountUSDC.add(secondRequestAmountUSDC) + ); - // Anna mints OUSD with USDC - await usdc.connect(anna).approve(vault.address, usdcUnits("1000.00")); - await vault.connect(anna).mint(usdc.address, usdcUnits("1000.00"), 0); - await expect(anna).has.a.balanceOf("1000.00", ousd); - await expect(matt).has.a.balanceOf("100.00", ousd); + await assertChangedData( + dataBefore, + { + ousdTotalSupply: 0, + ousdTotalValue: 0, + vaultCheckBalance: 0, + userOusd: 0, + userUsdc: 0, + vaultUsdc: 0, + queued: 0, + claimable: firstRequestAmountUSDC.add(secondRequestAmountUSDC), + claimed: 0, + nextWithdrawalIndex: 0, + }, + fixtureWithUser + ); + }); + it("Should claim second request with enough liquidity", async () => { + const { vault, daniel, josh } = fixture; + const fixtureWithUser = { ...fixture, user: josh }; + await vault.connect(daniel).requestWithdrawal(firstRequestAmountOUSD); + await vault.connect(josh).requestWithdrawal(secondRequestAmountOUSD); + const requestId = 3; // ids start at 0 so the fourth request is at index 3. Two in set setup and two here. + const dataBefore = await snapData(fixtureWithUser); + + await advanceTime(delayPeriod); // Advance in time to ensure time delay between request and claim. + + const tx = await vault.connect(josh).claimWithdrawal(requestId); + + const claimableAmount = usdcUnits("223"); // 100 + 100 + 5 + 18 + + await expect(tx) + .to.emit(vault, "WithdrawalClaimed") + .withArgs(josh.address, requestId, secondRequestAmountOUSD); + await expect(tx) + .to.emit(vault, "WithdrawalClaimable") + .withArgs( + claimableAmount, + firstRequestAmountUSDC.add(secondRequestAmountUSDC) + ); - // Anna mints OUSD with USDS - await usds.connect(anna).approve(vault.address, usdsUnits("1000.00")); - await vault.connect(anna).mint(usds.address, usdsUnits("1000.00"), 0); - await expect(anna).has.a.balanceOf("2000.00", ousd); - await expect(matt).has.a.balanceOf("100.00", ousd); + await assertChangedData( + dataBefore, + { + ousdTotalSupply: 0, + ousdTotalValue: 0, + vaultCheckBalance: 0, + userOusd: 0, + userUsdc: secondRequestAmountUSDC, + vaultUsdc: secondRequestAmountUSDC.mul(-1), + queued: 0, + claimable: firstRequestAmountUSDC.add(secondRequestAmountUSDC), + claimed: secondRequestAmountUSDC, + nextWithdrawalIndex: 0, + }, + fixtureWithUser + ); + }); + it("Should claim multiple requests with enough liquidity", async () => { + const { vault, matt } = fixture; + const fixtureWithUser = { ...fixture, user: matt }; + await vault.connect(matt).requestWithdrawal(firstRequestAmountOUSD); + await vault.connect(matt).requestWithdrawal(secondRequestAmountOUSD); + const dataBefore = await snapData(fixtureWithUser); + + await advanceTime(delayPeriod); // Advance in time to ensure time delay between request and claim. + + const tx = await vault.connect(matt).claimWithdrawals([2, 3]); + + const claimableAmount = usdcUnits("223"); // 100 + 100 + 5 + 18 + + await expect(tx) + .to.emit(vault, "WithdrawalClaimed") + .withArgs(matt.address, 2, firstRequestAmountOUSD); + await expect(tx) + .to.emit(vault, "WithdrawalClaimed") + .withArgs(matt.address, 3, secondRequestAmountOUSD); + await expect(tx) + .to.emit(vault, "WithdrawalClaimable") + .withArgs( + claimableAmount, + firstRequestAmountUSDC.add(secondRequestAmountUSDC) + ); - // Rebase should do nothing - await vault.rebase(); - await expect(anna).has.a.balanceOf("2000.00", ousd); - await expect(matt).has.a.balanceOf("100.00", ousd); + await assertChangedData( + dataBefore, + { + ousdTotalSupply: 0, + ousdTotalValue: 0, + vaultCheckBalance: 0, + userOusd: 0, + userUsdc: firstRequestAmountUSDC.add(secondRequestAmountUSDC), + vaultUsdc: firstRequestAmountUSDC + .add(secondRequestAmountUSDC) + .mul(-1), + queued: 0, + claimable: firstRequestAmountUSDC.add(secondRequestAmountUSDC), + claimed: firstRequestAmountUSDC.add(secondRequestAmountUSDC), + nextWithdrawalIndex: 0, + }, + fixtureWithUser + ); + }); + it("Should claim single big request as a whale", async () => { + const { vault, ousd, matt } = fixture; + + const ousdBalanceBefore = await ousd.balanceOf(matt.address); + const totalValueBefore = await vault.totalValue(); + + await vault.connect(matt).requestWithdrawal(ousdUnits("30")); + + const ousdBalanceAfter = await ousd.balanceOf(matt.address); + const totalValueAfter = await vault.totalValue(); + await expect(ousdBalanceBefore).to.equal(ousdUnits("30")); + await expect(ousdBalanceAfter).to.equal(ousdUnits("0")); + await expect(totalValueBefore.sub(totalValueAfter)).to.equal( + ousdUnits("30") + ); + + const ousdTotalSupply = await ousd.totalSupply(); + await advanceTime(delayPeriod); // Advance in time to ensure time delay between request and claim. + const tx = await vault.connect(matt).claimWithdrawal(2); // Claim withdrawal for 50% of the supply + + await expect(tx) + .to.emit(vault, "WithdrawalClaimed") + .withArgs(matt.address, 2, ousdUnits("30")); + + await expect(ousdTotalSupply).to.equal(await ousd.totalSupply()); + await expect(totalValueAfter).to.equal(await vault.totalValue()); + }); + + // Negative tests + it("Fail to claim request because of not enough time passed", async () => { + const { vault, daniel } = fixture; + + // Daniel requests 5 OUSD to be withdrawn + await vault.connect(daniel).requestWithdrawal(firstRequestAmountOUSD); + const requestId = 2; + + // Daniel claimWithdraw request in the same block as the request + const tx = vault.connect(daniel).claimWithdrawal(requestId); + + await expect(tx).to.revertedWith("Claim delay not met"); + }); + it("Fail to request withdrawal because of solvency check too high", async () => { + const { vault, daniel, usdc } = fixture; + + await usdc.mintTo(daniel.address, ousdUnits("10")); + await usdc.connect(daniel).transfer(vault.address, ousdUnits("10")); + + const tx = vault + .connect(daniel) + .requestWithdrawal(firstRequestAmountOUSD); + + await expect(tx).to.revertedWith("Backing supply liquidity error"); + }); + it("Fail to claim request because of solvency check too high", async () => { + const { vault, daniel, usdc } = fixture; + + // Request withdrawal of 5 OUSD + await vault.connect(daniel).requestWithdrawal(firstRequestAmountOUSD); + + // Transfer 10 USDC to the vault + await usdc.mintTo(daniel.address, ousdUnits("10")); + await usdc.connect(daniel).transfer(vault.address, ousdUnits("10")); + + await advanceTime(delayPeriod); // Advance in time to ensure time delay between request and claim. + + // Claim the withdrawal + const tx = vault.connect(daniel).claimWithdrawal(2); + + await expect(tx).to.revertedWith("Backing supply liquidity error"); + }); + it("Fail multiple claim requests because of solvency check too high", async () => { + const { vault, matt, usdc } = fixture; + + // Request withdrawal of 5 OUSD + await vault.connect(matt).requestWithdrawal(firstRequestAmountOUSD); + await vault.connect(matt).requestWithdrawal(secondRequestAmountOUSD); + + // Transfer 10 USDC to the vault + await usdc.mintTo(matt.address, ousdUnits("10")); + await usdc.connect(matt).transfer(vault.address, ousdUnits("10")); + + await advanceTime(delayPeriod); // Advance in time to ensure time delay between request and claim. + + // Claim the withdrawal + const tx = vault.connect(matt).claimWithdrawals([2, 3]); + + await expect(tx).to.revertedWith("Backing supply liquidity error"); + }); + it("Fail request withdrawal because of solvency check too low", async () => { + const { vault, daniel, usdc } = fixture; + + // Simulate a loss of funds from the vault + await usdc + .connect(await impersonateAndFund(vault.address)) + .transfer(daniel.address, usdcUnits("10")); + + const tx = vault + .connect(daniel) + .requestWithdrawal(firstRequestAmountOUSD); - // Anna redeems over the rebase threshold - await vault.connect(anna).redeem(ousdUnits("1500.0"), 0); - await expect(anna).has.a.approxBalanceOf("500.00", ousd); - await expect(matt).has.a.approxBalanceOf("100.00", ousd); + await expect(tx).to.revertedWith("Backing supply liquidity error"); + }); - // Redeem outputs will be 1000/2200 * 1500 USDC and 1200/2200 * 1500 USDS from fixture - await expect(anna).has.an.approxBalanceOf("681.8181", usdc); - await expect(anna).has.a.approxBalanceOf("818.1818", usds); + describe("when deposit 15 USDC to a strategy, leaving 60 - 15 = 45 USDC in the vault; request withdrawal of 5 + 18 = 23 OUSD, leaving 45 - 23 = 22 USDC unallocated", () => { + let mockStrategy; + beforeEach(async () => { + const { vault, usdc, governor, daniel, josh } = fixture; - await expectApproxSupply(ousd, ousdUnits("700.0")); - }); + const dMockStrategy = await deployWithConfirmation("MockStrategy"); + mockStrategy = await ethers.getContractAt( + "MockStrategy", + dMockStrategy.address + ); + await mockStrategy.setWithdrawAll(usdc.address, vault.address); + await vault.connect(governor).approveStrategy(mockStrategy.address); - it("Changing an asset price affects a redeem", async () => { - const { ousd, vault, usds, matt } = fixture; + // Deposit 15 USDC of 10 + 20 + 30 = 60 USDC to strategy + // This leave 60 - 15 = 45 USDC in the vault + await vault + .connect(governor) + .depositToStrategy( + mockStrategy.address, + [usdc.address], + [usdcUnits("15")] + ); + // Request withdrawal of 5 + 18 = 23 OUSD + // This leave 45 - 23 = 22 USDC unallocated to the withdrawal queue + await vault.connect(daniel).requestWithdrawal(firstRequestAmountOUSD); + await vault.connect(josh).requestWithdrawal(secondRequestAmountOUSD); + }); + it("Fail to deposit allocated USDC to a strategy", async () => { + const { vault, usdc, governor } = fixture; + + // USDC in the vault = 60 - 15 = 45 USDC + // unallocated USDC in the Vault = 45 - 23 = 22 USDC + // 23 USDC to deposit > the 22 USDC available so it should revert + const depositAmount = usdcUnits("23"); + const tx = vault + .connect(governor) + .depositToStrategy( + mockStrategy.address, + [usdc.address], + [depositAmount] + ); + await expect(tx).to.be.revertedWith("Not enough assets available"); + }); + it("Fail to deposit allocated USDC during allocate", async () => { + const { vault, governor, usdc } = fixture; - await expectApproxSupply(ousd, ousdUnits("200")); - await expect(matt).has.a.balanceOf("100.00", ousd); - await expect(matt).has.a.balanceOf("900.00", usds); + // Set mock strategy as default strategy + await vault + .connect(governor) + .setDefaultStrategy(mockStrategy.address); - await setOracleTokenPriceUsd("USDS", "1.25"); - await vault.rebase(); + // and buffer to 10% + await vault.connect(governor).setVaultBuffer(ousdUnits("0.1")); - await vault.connect(matt).redeem(ousdUnits("2.0"), 0); - await expectApproxSupply(ousd, ousdUnits("198")); - // Amount of USDS collected is affected by redeem oracles - await expect(matt).has.a.approxBalanceOf("901.60", usds); - }); + // USDC in strategy = 15 USDC + // USDC in the vault = 60 - 15 = 45 USDC + // Unallocated USDC in the vault = 45 - 23 = 22 USDC - it("Should allow redeems of non-standard tokens", async () => { - const { ousd, vault, anna, governor, oracleRouter, nonStandardToken } = - fixture; + await vault.connect(governor).allocate(); - await oracleRouter.cacheDecimals(nonStandardToken.address); - await vault.connect(governor).supportAsset(nonStandardToken.address, 0); + expect(await usdc.balanceOf(mockStrategy.address)).to.approxEqual( + // 60 - 23 = 37 Unreserved USDC + // 90% of 37 = 33.3 USDC for allocation + usdcUnits("33.3"), + "Strategy has the reserved USDC" + ); - await setOracleTokenPriceUsd("NonStandardToken", "1.00"); + expect(await usdc.balanceOf(vault.address)).to.approxEqual( + // 10% of 37 = 3.7 USDC for Vault buffer + // + 23 reserved USDC + usdcUnits("23").add(usdcUnits("3.7")), + "Vault doesn't have enough USDC" + ); + }); + it("Should deposit unallocated USDC to a strategy", async () => { + const { vault, usdc, governor } = fixture; - await expect(anna).has.a.balanceOf("1000.00", nonStandardToken); + // USDC in the vault = 60 - 15 = 45 USDC + // unallocated USDC in the Vault = 45 - 23 = 22 USDC + const depositAmount = usdcUnits("22"); + await vault + .connect(governor) + .depositToStrategy( + mockStrategy.address, + [usdc.address], + [depositAmount] + ); + }); + it("Should claim first request with enough liquidity", async () => { + const { vault, daniel } = fixture; + const fixtureWithUser = { ...fixture, user: daniel }; + const dataBefore = await snapData(fixtureWithUser); + + await advanceTime(delayPeriod); // Advance in time to ensure time delay between request and claim. + + const requestId = 2; + const tx = await vault.connect(daniel).claimWithdrawal(requestId); + + await expect(tx) + .to.emit(vault, "WithdrawalClaimed") + .withArgs(daniel.address, requestId, firstRequestAmountOUSD); + + await assertChangedData( + dataBefore, + { + ousdTotalSupply: 0, + ousdTotalValue: 0, + vaultCheckBalance: 0, + userOusd: 0, + userUsdc: firstRequestAmountUSDC, + vaultUsdc: firstRequestAmountUSDC.mul(-1), + queued: 0, + claimable: firstRequestAmountUSDC.add(secondRequestAmountUSDC), + claimed: firstRequestAmountUSDC, + nextWithdrawalIndex: 0, + }, + fixtureWithUser + ); + }); + it("Should claim a new request with enough USDC liquidity", async () => { + const { vault, matt } = fixture; + const fixtureWithUser = { ...fixture, user: matt }; + + // Set the claimable amount to the queued amount + await vault.addWithdrawalQueueLiquidity(); + + // USDC in the vault = 60 - 15 = 45 USDC + // unallocated USDC in the Vault = 45 - 23 = 22 USDC + // Matt request all unallocated USDC to be withdrawn + const requestAmount = ousdUnits("22"); + await vault.connect(matt).requestWithdrawal(requestAmount); + + const dataBefore = await snapData(fixtureWithUser); + + await advanceTime(delayPeriod); // Advance in time to ensure time delay between request and claim. + + const requestId = 4; + const tx = await vault.connect(matt).claimWithdrawal(requestId); + + await expect(tx) + .to.emit(vault, "WithdrawalClaimed") + .withArgs(matt.address, requestId, requestAmount); + + await assertChangedData( + dataBefore, + { + ousdTotalSupply: 0, + ousdTotalValue: 0, + vaultCheckBalance: 0, + userOusd: 0, + userUsdc: requestAmount.div(1e12), // USDC has 6 decimals + vaultUsdc: requestAmount.mul(-1).div(1e12), + queued: 0, + claimable: requestAmount.div(1e12), + claimed: requestAmount.div(1e12), + nextWithdrawalIndex: 0, + }, + fixtureWithUser + ); + }); + it("Fail to claim a new request with NOT enough USDC liquidity", async () => { + const { vault, matt } = fixture; - // Mint 100 OUSD for 100 tokens - await nonStandardToken - .connect(anna) - .approve(vault.address, usdtUnits("100.0")); - await vault - .connect(anna) - .mint(nonStandardToken.address, usdtUnits("100.0"), 0); - await expect(anna).has.a.balanceOf("100.00", ousd); + // Matt request 23 OUSD to be withdrawn when only 22 USDC is unallocated to existing requests + const requestAmount = ousdUnits("23"); + await vault.connect(matt).requestWithdrawal(requestAmount); - // Redeem 100 tokens for 100 OUSD - await vault.connect(anna).redeem(ousdUnits("100.0"), 0); - await expect(anna).has.a.balanceOf("0.00", ousd); - // 66.66 would have come back as USDS because there is 100 NST and 200 USDS - await expect(anna).has.an.approxBalanceOf("933.33", nonStandardToken); - }); + await advanceTime(delayPeriod); // Advance in time to ensure time delay between request and claim. - it("Should have a default redeem fee of 0", async () => { - const { vault } = fixture; + const tx = vault.connect(matt).claimWithdrawal(4); + await expect(tx).to.be.revertedWith("Queue pending liquidity"); + }); + it("Should claim a new request after withdraw from strategy adds enough liquidity", async () => { + const { vault, daniel, matt, strategist, usdc } = fixture; - await expect(await vault.redeemFeeBps()).to.equal("0"); - }); + // Set the claimable amount to the queued amount + await vault.addWithdrawalQueueLiquidity(); - it("Should charge a redeem fee if redeem fee set", async () => { - const { ousd, vault, usdc, anna, governor } = fixture; - - // 1000 basis points = 10% - await vault.connect(governor).setRedeemFeeBps(1000); - await expect(anna).has.a.balanceOf("1000.00", usdc); - await usdc.connect(anna).approve(vault.address, usdcUnits("50.0")); - await vault.connect(anna).mint(usdc.address, usdcUnits("50.0"), 0); - await expect(anna).has.a.balanceOf("50.00", ousd); - await vault.connect(anna).redeem(ousdUnits("50.0"), 0); - await expect(anna).has.a.balanceOf("0.00", ousd); - // 45 after redeem fee - // USDC is 50/250 of total assets, so balance should be 950 + 50/250 * 45 = 959 - await expect(anna).has.a.balanceOf("959.00", usdc); - }); + // Matt requests all 30 OUSD to be withdrawn which is currently 8 USDC short + const requestAmount = ousdUnits("30"); + await vault.connect(matt).requestWithdrawal(requestAmount); - it("Should revert redeem if balance is insufficient", async () => { - const { ousd, vault, usdc, anna } = fixture; + const fixtureWithUser = { ...fixture, user: daniel }; + const dataBeforeMint = await snapData(fixtureWithUser); - // Mint some OUSD tokens - await expect(anna).has.a.balanceOf("1000.00", usdc); - await usdc.connect(anna).approve(vault.address, usdcUnits("50.0")); - await vault.connect(anna).mint(usdc.address, usdcUnits("50.0"), 0); - await expect(anna).has.a.balanceOf("50.00", ousd); + // USDC in the vault = 60 - 15 = 45 USDC + // unallocated USDC in the Vault = 45 - 23 = 22 USDC + // Add another 8 USDC so the unallocated USDC is 22 + 8 = 30 USDC + const withdrawAmount = usdcUnits("8"); + await vault + .connect(strategist) + .withdrawFromStrategy( + mockStrategy.address, + [usdc.address], + [withdrawAmount] + ); - // Try to withdraw more than balance - await expect( - vault.connect(anna).redeem(ousdUnits("100.0"), 0) - ).to.be.revertedWith("Transfer amount exceeds balance"); - }); + await assertChangedData( + dataBeforeMint, + { + ousdTotalSupply: 0, + ousdTotalValue: 0, + vaultCheckBalance: 0, + userOusd: 0, + userUsdc: 0, + vaultUsdc: withdrawAmount, + queued: 0, + claimable: requestAmount.div(1e12), + claimed: 0, + nextWithdrawalIndex: 0, + }, + fixtureWithUser + ); - it("Should only allow Governor to set a redeem fee", async () => { - const { vault, anna } = fixture; + await advanceTime(delayPeriod); // Advance in time to ensure time delay between request and claim. - await expect(vault.connect(anna).setRedeemFeeBps(100)).to.be.revertedWith( - "Caller is not the Governor" - ); - }); + await vault.connect(matt).claimWithdrawal(4); + }); + it("Should claim a new request after withdrawAllFromStrategy adds enough liquidity", async () => { + const { vault, daniel, matt, strategist, usdc } = fixture; - it("Should redeem entire OUSD balance", async () => { - const { ousd, vault, usdc, usds, anna } = fixture; + // Set the claimable amount to the queued amount + await vault.addWithdrawalQueueLiquidity(); - await expect(anna).has.a.balanceOf("1000.00", usdc); + // Matt requests all 30 OUSD to be withdrawn which is currently 8 USDC short + const requestAmount = ousdUnits("30"); + await vault.connect(matt).requestWithdrawal(requestAmount); - // Mint 100 OUSD tokens using USDC - await usdc.connect(anna).approve(vault.address, usdcUnits("100.0")); - await vault.connect(anna).mint(usdc.address, usdcUnits("100.0"), 0); - await expect(anna).has.a.balanceOf("100.00", ousd); + const fixtureWithUser = { ...fixture, user: daniel }; + const dataBeforeMint = await snapData(fixtureWithUser); + const strategyBalanceBefore = await usdc.balanceOf( + mockStrategy.address + ); - // Mint 150 OUSD tokens using USDS - await usds.connect(anna).approve(vault.address, usdsUnits("150.0")); - await vault.connect(anna).mint(usds.address, usdsUnits("150.0"), 0); - await expect(anna).has.a.balanceOf("250.00", ousd); + // USDC in the vault = 60 - 15 = 45 USDC + // unallocated USDC in the Vault = 45 - 23 = 22 USDC + // Add another 8 USDC so the unallocated USDC is 22 + 8 = 30 USDC + await vault + .connect(strategist) + .withdrawAllFromStrategy(mockStrategy.address); + + await assertChangedData( + dataBeforeMint, + { + ousdTotalSupply: 0, + ousdTotalValue: 0, + vaultCheckBalance: 0, + userOusd: 0, + userUsdc: 0, + vaultUsdc: strategyBalanceBefore, + queued: 0, + claimable: requestAmount.div(1e12), + claimed: 0, + nextWithdrawalIndex: 0, + }, + fixtureWithUser + ); - // Withdraw all - await vault.connect(anna).redeem(ousd.balanceOf(anna.address), 0); + await advanceTime(delayPeriod); // Advance in time to ensure time delay between request and claim. - // 100 USDC and 350 USDS in contract - // (1000-100) + 100/450 * 250 USDC - // (1000-150) + 350/450 * 250 USDS - await expect(anna).has.an.approxBalanceOf("955.55", usdc); - await expect(anna).has.an.approxBalanceOf("1044.44", usds); - }); + await vault.connect(matt).claimWithdrawal(4); + }); + it("Should claim a new request after withdrawAll from strategies adds enough liquidity", async () => { + const { vault, daniel, matt, strategist, usdc } = fixture; - it("Should redeem entire OUSD balance, with a higher oracle price", async () => { - const { ousd, vault, usdc, usds, anna, governor } = fixture; - - await expect(anna).has.a.balanceOf("1000.00", usdc); - - // Mint 100 OUSD tokens using USDC - await usdc.connect(anna).approve(vault.address, usdcUnits("100.0")); - await vault.connect(anna).mint(usdc.address, usdcUnits("100.0"), 0); - await expect(anna).has.a.balanceOf("100.00", ousd); - - // Mint 150 OUSD tokens using USDS - await usds.connect(anna).approve(vault.address, usdsUnits("150.0")); - await vault.connect(anna).mint(usds.address, usdsUnits("150.0"), 0); - await expect(anna).has.a.balanceOf("250.00", ousd); - - await setOracleTokenPriceUsd("USDC", "1.30"); - await setOracleTokenPriceUsd("USDS", "1.20"); - await vault.connect(governor).rebase(); - - // Anna's balance does not change with the rebase - await expect(anna).has.an.approxBalanceOf("250.00", ousd); - - // Withdraw all - await vault.connect(anna).redeem(ousd.balanceOf(anna.address), 0); - - // OUSD to Withdraw 250 - // Total Vault Coins 450 - // USDC Percentage 100 / 450 = 0.222222222222222 - // USDT Percentage 350 / 450 = 0.777777777777778 - // USDC Value Percentage 0.222222222222222 * 1.3 = 0.288888888888889 - // USDT Value Percentage 0.777777777777778 * 1.2 = 0.933333333333333 - // Output to Dollar Ratio 1.22222222222222 - // USDC Output 250 * 0.222222222222222 / 1.22222222222222 = 45.4545454545454 - // USDT Output 250 * 0.777777777777778 / 1.22222222222222 = 159.090909090909 - // Expected USDC 900 + 45.4545454545454 = 945.454545454545 - // Expected USDT 850 + 159.090909090909 = 1009.09090909091 - await expect(anna).has.an.approxBalanceOf( - "945.4545", - usdc, - "USDC has wrong balance" - ); - await expect(anna).has.an.approxBalanceOf( - "1009.09", - usds, - "USDS has wrong balance" - ); - }); + // Set the claimable amount to the queued amount + await vault.addWithdrawalQueueLiquidity(); - it("Should redeem entire OUSD balance, with a lower oracle price", async () => { - const { ousd, vault, usdc, usds, anna, governor } = fixture; - - await expect(anna).has.a.balanceOf("1000.00", usdc); - - // Mint 100 OUSD tokens using USDC - await usdc.connect(anna).approve(vault.address, usdcUnits("100.0")); - await vault.connect(anna).mint(usdc.address, usdcUnits("100.0"), 0); - await expect(anna).has.a.balanceOf("100.00", ousd); - - // Mint 150 OUSD tokens using USDS - await usds.connect(anna).approve(vault.address, usdsUnits("150.0")); - await vault.connect(anna).mint(usds.address, usdsUnits("150.0"), 0); - await expect(anna).has.a.balanceOf("250.00", ousd); - - await setOracleTokenPriceUsd("USDC", "0.90"); - await setOracleTokenPriceUsd("USDS", "0.80"); - await vault.connect(governor).rebase(); - - // Anna's share of OUSD is unaffected - await expect(anna).has.an.approxBalanceOf("250.00", ousd); - - // Withdraw all - await ousd.connect(anna).approve(vault.address, ousdUnits("500")); - await vault.connect(anna).redeem(ousd.balanceOf(anna.address), 0); - - // OUSD to Withdraw 250 - // Total Vault Coins 450 - // USDC Percentage 100 / 450 = 0.2222 - // USDS Percentage 350 / 450 = 0.7778 - // USDC Value Percentage 0.2222 * 1 = 0.2222 - // USDS Value Percentage 0.7778 * 1 = 0.7778 - // Output to Dollar Ratio 1.0000 - // USDC Output 250 * 0.2222 / 1.0000 = 55.5556 - // USDS Output 250 * 0.7778 / 1.0000 = 194.4444 - // Expected USDC 900 + 55.5556 = 955.5556 - // Expected USDS 850 + 194.4444 = 1044.4444 - await expect(anna).has.an.approxBalanceOf( - "955.5556", - usdc, - "USDC has wrong balance" - ); - await expect(anna).has.an.approxBalanceOf( - "1044.44", - usds, - "USDS has wrong balance" - ); - }); + // Matt requests all 30 OUSD to be withdrawn which is currently 8 USDC short + const requestAmount = ousdUnits("30"); + await vault.connect(matt).requestWithdrawal(requestAmount); - it("Should have correct balances on consecutive mint and redeem", async () => { - const { ousd, vault, usdc, usds, anna, matt, josh } = fixture; - - const usersWithBalances = [ - [anna, 0], - [matt, 100], - [josh, 100], - ]; - - const assetsWithUnits = [ - [usds, usdsUnits], - [usdc, usdcUnits], - ]; - - for (const [user, startBalance] of usersWithBalances) { - for (const [asset, units] of assetsWithUnits) { - for (const amount of [5.09, 10.32, 20.99, 100.01]) { - await asset - .connect(user) - .approve(vault.address, await units(amount.toString())); - await vault - .connect(user) - .mint(asset.address, await units(amount.toString()), 0); - await expect(user).has.an.approxBalanceOf( - (startBalance + amount).toString(), - ousd + const fixtureWithUser = { ...fixture, user: daniel }; + const dataBeforeMint = await snapData(fixtureWithUser); + const strategyBalanceBefore = await usdc.balanceOf( + mockStrategy.address ); - await vault.connect(user).redeem(ousdUnits(amount.toString()), 0); - await expect(user).has.an.approxBalanceOf( - startBalance.toString(), - ousd + + // USDC in the vault = 60 - 15 = 45 USDC + // unallocated USDC in the Vault = 45 - 23 = 22 USDC + // Add another 8 USDC so the unallocated USDC is 22 + 8 = 30 USDC + await vault.connect(strategist).withdrawAllFromStrategies(); + + await assertChangedData( + dataBeforeMint, + { + ousdTotalSupply: 0, + ousdTotalValue: 0, + vaultCheckBalance: 0, + userOusd: 0, + userUsdc: 0, + vaultUsdc: strategyBalanceBefore, + queued: 0, + claimable: requestAmount.div(1e12), + claimed: 0, + nextWithdrawalIndex: 0, + }, + fixtureWithUser ); - } - } - } - }); - it("Should have correct balances on consecutive mint and redeem with varying oracle prices", async () => { - const { ousd, vault, usdt, usdc, matt, josh } = fixture; + await advanceTime(delayPeriod); // Advance in time to ensure time delay between request and claim. + + await vault.connect(matt).claimWithdrawal(4); + }); + it("Fail to claim a new request after mint with NOT enough liquidity", async () => { + const { vault, daniel, matt, usdc } = fixture; + + // Matt requests all 30 OUSD to be withdrawn which is not enough liquidity + const requestAmount = ousdUnits("30"); + await vault.connect(matt).requestWithdrawal(requestAmount); + + // USDC in the vault = 60 - 15 = 45 USDC + // unallocated USDC in the Vault = 45 - 23 = 22 USDC + // Add another 6 USDC so the unallocated USDC is 22 + 6 = 28 USDC + await usdc.mintTo(daniel.address, ousdUnits("6").div(1e12)); + await usdc + .connect(daniel) + .approve(vault.address, ousdUnits("6").div(1e12)); + await vault.connect(daniel).mint(usdc.address, usdcUnits("6"), 0); + + await advanceTime(delayPeriod); // Advance in time to ensure time delay between request and claim. + + const tx = vault.connect(matt).claimWithdrawal(4); + await expect(tx).to.be.revertedWith("Queue pending liquidity"); + }); + it("Should claim a new request after mint adds enough liquidity", async () => { + const { vault, daniel, matt, usdc } = fixture; + + // Set the claimable amount to the queued amount + await vault.addWithdrawalQueueLiquidity(); + + // Matt requests all 30 OUSD to be withdrawn which is currently 8 USDC short + const requestAmount = ousdUnits("30"); + await vault.connect(matt).requestWithdrawal(requestAmount); + + const fixtureWithUser = { ...fixture, user: daniel }; + const dataBeforeMint = await snapData(fixtureWithUser); + + // USDC in the vault = 60 - 15 = 45 USDC + // unallocated USDC in the Vault = 45 - 23 = 22 USDC + // Add another 8 USDC so the unallocated USDC is 22 + 8 = 30 USDC + const mintAmount = ousdUnits("8"); + await usdc + .connect(daniel) + .approve(vault.address, mintAmount.div(1e12)); + await vault + .connect(daniel) + .mint(usdc.address, mintAmount.div(1e12), 0); + + await assertChangedData( + dataBeforeMint, + { + ousdTotalSupply: mintAmount, + ousdTotalValue: mintAmount, + vaultCheckBalance: mintAmount.div(1e12), + userOusd: mintAmount, + userUsdc: mintAmount.mul(-1).div(1e12), + vaultUsdc: mintAmount.div(1e12), + queued: 0, + claimable: requestAmount.div(1e12), + claimed: 0, + nextWithdrawalIndex: 0, + }, + fixtureWithUser + ); - const users = [matt, josh]; - const assetsWithUnits = [ - [usdt, usdtUnits], - [usdc, usdcUnits], - ]; - const prices = [0.998, 1.02, 1.09]; - const amounts = [5.09, 10.32, 20.99, 100.01]; + await advanceTime(delayPeriod); // Advance in time to ensure time delay between request and claim. + + await vault.connect(matt).claimWithdrawal(4); + }); + }); + + describe("Fail when", () => { + it("request doesn't have enough OUSD", async () => { + const { vault, josh } = fixture; + const fixtureWithUser = { ...fixture, user: josh }; + const dataBefore = await snapData(fixtureWithUser); + + const tx = vault + .connect(josh) + .requestWithdrawal(dataBefore.userOusd.add(1)); + + await expect(tx).to.revertedWith("Transfer amount exceeds balance"); + }); + it("capital is paused", async () => { + const { vault, governor, josh } = fixture; + + await vault.connect(governor).pauseCapital(); + + const tx = vault + .connect(josh) + .requestWithdrawal(firstRequestAmountOUSD); + + await expect(tx).to.be.revertedWith("Capital paused"); + }); + }); + }); + describe("with 1% vault buffer, 30 USDC in the queue, 15 USDC in the vault, 85 USDC in the strategy, 5 USDC already claimed", () => { + let mockStrategy; + beforeEach(async () => { + const { governor, vault, usdc, daniel, domen, josh, matt } = fixture; + // Mint USDC to users + await usdc.mintTo(daniel.address, usdcUnits("15")); + await usdc.mintTo(josh.address, usdcUnits("20")); + await usdc.mintTo(matt.address, usdcUnits("30")); + await usdc.mintTo(domen.address, usdcUnits("40")); + + // Approve USDC to Vault + await usdc.connect(daniel).approve(vault.address, usdcUnits("15")); + await usdc.connect(josh).approve(vault.address, usdcUnits("20")); + await usdc.connect(matt).approve(vault.address, usdcUnits("30")); + await usdc.connect(domen).approve(vault.address, usdcUnits("40")); + + // Mint 105 OUSD to four users + await vault.connect(daniel).mint(usdc.address, usdcUnits("15"), "0"); + await vault.connect(josh).mint(usdc.address, usdcUnits("20"), "0"); + await vault.connect(matt).mint(usdc.address, usdcUnits("30"), "0"); + await vault.connect(domen).mint(usdc.address, usdcUnits("40"), "0"); + await vault + .connect(await impersonateAndFund(await vault.governor())) + .setMaxSupplyDiff(ousdUnits("0.03")); + + // Request and claim 2 + 3 = 5 USDC from Vault + await vault.connect(daniel).requestWithdrawal(ousdUnits("2")); + await vault.connect(josh).requestWithdrawal(ousdUnits("3")); + await advanceTime(delayPeriod); // Advance in time to ensure time delay between request and claim. + await vault.connect(daniel).claimWithdrawal(2); + await vault.connect(josh).claimWithdrawal(3); + + // Deploy a mock strategy + mockStrategy = await deployWithConfirmation("MockStrategy"); + await vault.connect(governor).approveStrategy(mockStrategy.address); + + // Deposit 85 USDC to strategy + await vault + .connect(governor) + .depositToStrategy( + mockStrategy.address, + [usdc.address], + [usdcUnits("85")] + ); - const getUserOusdBalance = async (user) => { - const bn = await ousd.balanceOf(await user.getAddress()); - return parseFloat(bn.toString() / 1e12 / 1e6); - }; + // Set vault buffer to 1% + await vault.connect(governor).setVaultBuffer(ousdUnits("0.01")); + + // Have 4 + 12 + 16 = 32 USDC outstanding requests + // So a total supply of 100 - 32 = 68 OUSD + await vault.connect(daniel).requestWithdrawal(ousdUnits("4")); + await vault.connect(josh).requestWithdrawal(ousdUnits("12")); + await vault.connect(matt).requestWithdrawal(ousdUnits("16")); + + await vault.connect(josh).addWithdrawalQueueLiquidity(); + }); + describe("Fail to claim", () => { + it("a previously claimed withdrawal", async () => { + const { vault, daniel } = fixture; + + const tx = vault.connect(daniel).claimWithdrawal(2); + + await expect(tx).to.be.revertedWith("Already claimed"); + }); + it("the first withdrawal with wrong withdrawer", async () => { + const { vault, matt } = fixture; + + // Advance in time to ensure time delay between request and claim. + await advanceTime(delayPeriod); + + const tx = vault.connect(matt).claimWithdrawal(2); + + await expect(tx).to.be.revertedWith("Not requester"); + }); + it("the first withdrawal request in the queue before 30 minutes", async () => { + const { vault, daniel } = fixture; + + const tx = vault.connect(daniel).claimWithdrawal(4); + + await expect(tx).to.be.revertedWith("Claim delay not met"); + }); + }); + describe("when waited 30 minutes", () => { + beforeEach(async () => { + // Advance in time to ensure time delay between request and claim. + await advanceTime(delayPeriod); + }); + it("Fail to claim the first withdrawal with wrong withdrawer", async () => { + const { vault, matt } = fixture; + + const tx = vault.connect(matt).claimWithdrawal(4); + + await expect(tx).to.be.revertedWith("Not requester"); + }); + it("Should claim the first withdrawal request in the queue after 30 minutes", async () => { + const { vault, daniel } = fixture; + const fixtureWithUser = { ...fixture, user: daniel }; + const dataBefore = await snapData(fixtureWithUser); + + const requestId = 4; + const tx = await vault.connect(daniel).claimWithdrawal(requestId); + + await expect(tx) + .to.emit(vault, "WithdrawalClaimed") + .withArgs(daniel.address, requestId, ousdUnits("4")); + + await assertChangedData( + dataBefore, + { + ousdTotalSupply: 0, + ousdTotalValue: 0, + vaultCheckBalance: 0, + userOusd: 0, + userUsdc: usdcUnits("4"), + vaultUsdc: usdcUnits("4").mul(-1), + queued: 0, + claimable: 0, + claimed: usdcUnits("4"), + nextWithdrawalIndex: 0, + }, + fixtureWithUser + ); + }); + it("Fail to claim the second withdrawal request in the queue after 30 minutes", async () => { + const { vault, josh } = fixture; + + const tx = vault.connect(josh).claimWithdrawal(5); + + await expect(tx).to.be.revertedWith("Queue pending liquidity"); + }); + it("Fail to claim the last (3rd) withdrawal request in the queue", async () => { + const { vault, matt } = fixture; + + const tx = vault.connect(matt).claimWithdrawal(6); + + await expect(tx).to.be.revertedWith("Queue pending liquidity"); + }); + }); + describe("when mint covers exactly outstanding requests (32 - 15 = 17 OUSD)", () => { + beforeEach(async () => { + const { vault, daniel, usdc } = fixture; + await usdc.mintTo(daniel.address, usdcUnits("17")); + await usdc.connect(daniel).approve(vault.address, usdcUnits("17")); + await vault.connect(daniel).mint(usdc.address, usdcUnits("17"), "0"); + + // Advance in time to ensure time delay between request and claim. + await advanceTime(delayPeriod); + }); + it("Should claim the 2nd and 3rd withdrawal requests in the queue", async () => { + const { vault, daniel, josh } = fixture; + const fixtureWithUser = { ...fixture, user: daniel }; + const dataBefore = await snapData(fixtureWithUser); + + const tx1 = await vault.connect(daniel).claimWithdrawal(4); + + await expect(tx1) + .to.emit(vault, "WithdrawalClaimed") + .withArgs(daniel.address, 4, ousdUnits("4")); + + const tx2 = await vault.connect(josh).claimWithdrawal(5); + + await expect(tx2) + .to.emit(vault, "WithdrawalClaimed") + .withArgs(josh.address, 5, ousdUnits("12")); + + await assertChangedData( + dataBefore, + { + ousdTotalSupply: 0, + ousdTotalValue: 0, + vaultCheckBalance: 0, + userOusd: 0, + userUsdc: usdcUnits("4"), + vaultUsdc: usdcUnits("16").mul(-1), + queued: 0, + claimable: 0, + claimed: usdcUnits("16"), + nextWithdrawalIndex: 0, + }, + fixtureWithUser + ); + }); + it("Fail to deposit 1 USDC to a strategy", async () => { + const { vault, usdc, governor } = fixture; + + const tx = vault + .connect(governor) + .depositToStrategy( + mockStrategy.address, + [usdc.address], + [usdcUnits("1")] + ); - for (const user of users) { - for (const [asset, units] of assetsWithUnits) { - for (const price of prices) { - await setOracleTokenPriceUsd(await asset.symbol(), price.toString()); - // Manually call rebase because not triggered by mint - await vault.rebase(); - // Rebase could have changed user balance - // as there could have been yield from different - // oracle prices on redeems during a previous loop. - let userBalance = await getUserOusdBalance(user); - for (const amount of amounts) { - const ousdToReceive = amount * Math.min(price, 1); - await expect(user).has.an.approxBalanceOf( - userBalance.toString(), - ousd + await expect(tx).to.be.revertedWith("Not enough assets available"); + }); + it("Fail to allocate any USDC to the default strategy", async () => { + const { vault, domen } = fixture; + + const tx = await vault.connect(domen).allocate(); + + await expect(tx).to.not.emit(vault, "AssetAllocated"); + }); + }); + describe("when mint covers exactly outstanding requests and vault buffer (17 + 1 USDC)", () => { + beforeEach(async () => { + const { vault, daniel, usdc } = fixture; + await usdc.mintTo(daniel.address, usdcUnits("18")); + await usdc.connect(daniel).approve(vault.address, usdcUnits("18")); + await vault.connect(daniel).mint(usdc.address, usdcUnits("18"), "0"); + }); + it("Should deposit 1 USDC to a strategy which is the vault buffer", async () => { + const { vault, usdc, governor } = fixture; + + const tx = await vault + .connect(governor) + .depositToStrategy( + mockStrategy.address, + [usdc.address], + [usdcUnits("1")] ); - await asset - .connect(user) - .approve(vault.address, await units(amount.toString())); - await vault - .connect(user) - .mint(asset.address, await units(amount.toString()), 0); - await expect(user).has.an.approxBalanceOf( - (userBalance + ousdToReceive).toString(), - ousd + + expect(tx) + .to.emit(usdc, "Transfer") + .withArgs(vault.address, mockStrategy.address, usdcUnits("1")); + }); + it("Fail to deposit 1.1 USDC to the default strategy", async () => { + const { vault, usdc, governor } = fixture; + + const tx = vault + .connect(governor) + .depositToStrategy( + mockStrategy.address, + [usdc.address], + [usdcUnits("1.1")] ); - await vault - .connect(user) - .redeem(ousdUnits(ousdToReceive.toString()), 0); - await expect(user).has.an.approxBalanceOf( - userBalance.toString(), - ousd + + await expect(tx).to.be.revertedWith("Not enough assets available"); + }); + it("Fail to allocate any USDC to the default strategy", async () => { + const { vault, domen } = fixture; + + const tx = await vault.connect(domen).allocate(); + + await expect(tx).to.not.emit(vault, "AssetAllocated"); + }); + }); + describe("when mint more than covers outstanding requests and vault buffer (17 + 1 + 3 = 21 OUSD)", () => { + beforeEach(async () => { + const { vault, daniel, usdc } = fixture; + await usdc.mintTo(daniel.address, usdcUnits("21")); + await usdc.connect(daniel).approve(vault.address, usdcUnits("21")); + await vault.connect(daniel).mint(usdc.address, usdcUnits("21"), "0"); + }); + it("Should deposit 4 USDC to a strategy", async () => { + const { vault, usdc, governor } = fixture; + + const tx = await vault + .connect(governor) + .depositToStrategy( + mockStrategy.address, + [usdc.address], + [usdcUnits("4")] ); - } - } - } - } - }); - it("Should correctly handle redeem without a rebase and then full redeem", async function () { - const { ousd, vault, usdc, anna } = fixture; - await expect(anna).has.a.balanceOf("0.00", ousd); - await usdc.connect(anna).mint(usdcUnits("3000.0")); - await usdc.connect(anna).approve(vault.address, usdcUnits("3000.0")); - await vault.connect(anna).mint(usdc.address, usdcUnits("3000.0"), 0); - await expect(anna).has.a.balanceOf("3000.00", ousd); - - //peturb the oracle a slight bit. - await setOracleTokenPriceUsd("USDC", "1.000001"); - //redeem without rebasing (not over threshold) - await vault.connect(anna).redeem(ousdUnits("200.00"), 0); - //redeem with rebasing (over threshold) - await vault.connect(anna).redeem(ousd.balanceOf(anna.address), 0); - - await expect(anna).has.a.balanceOf("0.00", ousd); - }); + expect(tx) + .to.emit(usdc, "Transfer") + .withArgs(vault.address, mockStrategy.address, usdcUnits("4")); + }); + it("Fail to deposit 5 USDC to the default strategy", async () => { + const { vault, usdc, governor } = fixture; + + const tx = vault + .connect(governor) + .depositToStrategy( + mockStrategy.address, + [usdc.address], + [usdcUnits("5")] + ); - it("Should respect minimum unit amount argument in redeem", async () => { - const { ousd, vault, usdc, anna, usds } = fixture; - - await expect(anna).has.a.balanceOf("1000.00", usdc); - await expect(anna).has.a.balanceOf("1000.00", usds); - await usdc.connect(anna).approve(vault.address, usdcUnits("100.0")); - await vault.connect(anna).mint(usdc.address, usdcUnits("50.0"), 0); - await expect(anna).has.a.balanceOf("50.00", ousd); - await vault.connect(anna).redeem(ousdUnits("50.0"), ousdUnits("50")); - await vault.connect(anna).mint(usdc.address, usdcUnits("50.0"), 0); - await expect( - vault.connect(anna).redeem(ousdUnits("50.0"), ousdUnits("51")) - ).to.be.revertedWith("Redeem amount lower than minimum"); - }); + await expect(tx).to.be.revertedWith("Not enough assets available"); + }); + it("Should allocate 3 USDC to the default strategy", async () => { + const { vault, governor, domen, usdc } = fixture; + + await vault + .connect(governor) + .setDefaultStrategy(mockStrategy.address); + + const vaultBalance = await usdc.balanceOf(vault.address); + const stratBalance = await usdc.balanceOf(mockStrategy.address); + + const tx = await vault.connect(domen).allocate(); + + // total supply is 68 starting + 21 minted = 89 OUSD + // Vault buffer is 1% of 89 = 0.89 USDC + // USDC transfer amount = 4 USDC available in vault - 0.89 USDC buffer = 3.11 USDC + await expect(tx) + .to.emit(vault, "AssetAllocated") + .withArgs(usdc.address, mockStrategy.address, usdcUnits("3.11")); + + expect(await usdc.balanceOf(vault.address)).to.eq( + vaultBalance.sub(usdcUnits("3.11")) + ); + + expect(await usdc.balanceOf(mockStrategy.address)).to.eq( + stratBalance.add(usdcUnits("3.11")) + ); + }); + }); + }); + describe("with 40 USDC in the queue, 10 USDC in the vault, 30 USDC already claimed", () => { + beforeEach(async () => { + const { vault, usdc, daniel, josh, matt } = fixture; + + // Mint USDC to users + await usdc.mintTo(daniel.address, usdcUnits("10")); + await usdc.mintTo(josh.address, usdcUnits("20")); + await usdc.mintTo(matt.address, usdcUnits("10")); + + // Approve USDC to Vault + await usdc.connect(daniel).approve(vault.address, usdcUnits("10")); + await usdc.connect(josh).approve(vault.address, usdcUnits("20")); + await usdc.connect(matt).approve(vault.address, usdcUnits("10")); + + // Mint 60 OUSD to three users + await vault.connect(daniel).mint(usdc.address, usdcUnits("10"), "0"); + await vault.connect(josh).mint(usdc.address, usdcUnits("20"), "0"); + await vault.connect(matt).mint(usdc.address, usdcUnits("10"), "0"); + + // Request and claim 10 USDC from Vault + await vault.connect(daniel).requestWithdrawal(ousdUnits("10")); + await vault.connect(josh).requestWithdrawal(ousdUnits("20")); + await advanceTime(delayPeriod); // Advance in time to ensure time delay between request and claim. + + // Claim 10 + 20 = 30 USDC from Vault + await vault.connect(daniel).claimWithdrawal(2); + await vault.connect(josh).claimWithdrawal(3); + }); + it("Should allow the last user to request the remaining 10 USDC", async () => { + const { vault, matt } = fixture; + const fixtureWithUser = { ...fixture, user: matt }; + const dataBefore = await snapData(fixtureWithUser); + + const tx = await vault.connect(matt).requestWithdrawal(ousdUnits("10")); + + const queuedAmount = usdcUnits("240"); // 110 + 100 + 10 + 20 + 10 + await expect(tx) + .to.emit(vault, "WithdrawalRequested") + .withArgs(matt.address, 4, ousdUnits("10"), queuedAmount); + + await assertChangedData( + dataBefore, + { + ousdTotalSupply: ousdUnits("10").mul(-1), + ousdTotalValue: ousdUnits("10").mul(-1), + vaultCheckBalance: usdcUnits("10").mul(-1), + userOusd: ousdUnits("10").mul(-1), + userUsdc: 0, + vaultUsdc: 0, + queued: usdcUnits("10").mul(1), + claimable: 0, + claimed: 0, + nextWithdrawalIndex: 1, + }, + fixtureWithUser + ); + }); + it("Should allow the last user to claim the request of 10 USDC", async () => { + const { vault, matt } = fixture; + const fixtureWithUser = { ...fixture, user: matt }; + await vault.connect(matt).requestWithdrawal(ousdUnits("10")); + await advanceTime(delayPeriod); // Advance in time to ensure time delay between request and claim. + + const dataBefore = await snapData(fixtureWithUser); + + const tx = await vault.connect(matt).claimWithdrawal(4); + + await expect(tx) + .to.emit(vault, "WithdrawalClaimed") + .withArgs(matt.address, 4, ousdUnits("10")); + + await assertChangedData( + dataBefore, + { + ousdTotalSupply: 0, + ousdTotalValue: 0, + vaultCheckBalance: 0, + userOusd: 0, + userUsdc: usdcUnits("10"), + vaultUsdc: usdcUnits("10").mul(-1), + queued: 0, + claimable: usdcUnits("10"), + claimed: usdcUnits("10"), + nextWithdrawalIndex: 0, + }, + fixtureWithUser + ); + + expect(await vault.totalValue()).to.equal(0); + }); + }); + describe("with 40 USDC in the queue, 100 USDC in the vault, 0 USDC in the strategy", () => { + beforeEach(async () => { + const { vault, usdc, daniel, josh, matt } = fixture; + + // Mint USDC to users + await usdc.mintTo(daniel.address, usdcUnits("10")); + await usdc.mintTo(josh.address, usdcUnits("20")); + await usdc.mintTo(matt.address, usdcUnits("70")); + + // Approve USDC to Vault + await usdc.connect(daniel).approve(vault.address, usdcUnits("10")); + await usdc.connect(josh).approve(vault.address, usdcUnits("20")); + await usdc.connect(matt).approve(vault.address, usdcUnits("70")); + + // Mint 100 OUSD to three users + await vault.connect(daniel).mint(usdc.address, usdcUnits("10"), "0"); + await vault.connect(josh).mint(usdc.address, usdcUnits("20"), "0"); + await vault.connect(matt).mint(usdc.address, usdcUnits("70"), "0"); + + // Request 40 USDC from Vault + await vault.connect(matt).requestWithdrawal(ousdUnits("40")); + await advanceTime(delayPeriod); // Advance in time to ensure time delay between request and claim. + }); + it("Should allow user to claim the request of 40 USDC", async () => { + const { vault, matt } = fixture; + const fixtureWithUser = { ...fixture, user: matt }; + const dataBefore = await snapData(fixtureWithUser); + + const tx = await vault.connect(matt).claimWithdrawal(2); + + await expect(tx) + .to.emit(vault, "WithdrawalClaimed") + .withArgs(matt.address, 2, ousdUnits("40")); + + await assertChangedData( + dataBefore, + { + ousdTotalSupply: 0, + ousdTotalValue: 0, + vaultCheckBalance: 0, + userOusd: 0, + userUsdc: usdcUnits("40"), + vaultUsdc: usdcUnits("40").mul(-1), + queued: 0, + claimable: usdcUnits("40"), + claimed: usdcUnits("40"), + nextWithdrawalIndex: 0, + }, + fixtureWithUser + ); + }); + it("Should allow user to perform a new request and claim a smaller than the USDC available", async () => { + const { vault, josh } = fixture; + + await vault.connect(josh).requestWithdrawal(ousdUnits("20")); + await advanceTime(delayPeriod); // Advance in time to ensure time delay between request and claim. + + const tx = await vault.connect(josh).claimWithdrawal(3); + + await expect(tx).to.emit(vault, "WithdrawalClaimed"); + }); + it("Should allow user to perform a new request and claim exactly the USDC available", async () => { + const { vault, ousd, josh, matt, daniel } = fixture; + await vault.connect(matt).claimWithdrawal(2); + // All user give OUSD to another user + await ousd.connect(josh).transfer(matt.address, ousdUnits("20")); + await ousd.connect(daniel).transfer(matt.address, ousdUnits("10")); + + const fixtureWithUser = { ...fixture, user: matt }; + + // Matt request the remaining 60 OUSD to be withdrawn + await vault.connect(matt).requestWithdrawal(ousdUnits("60")); + await advanceTime(delayPeriod); // Advance in time to ensure time delay between request and claim. + + const dataBefore = await snapData(fixtureWithUser); + + const tx = await vault.connect(matt).claimWithdrawal(3); + + await expect(tx) + .to.emit(vault, "WithdrawalClaimed") + .withArgs(matt.address, 3, ousdUnits("60")); + + await assertChangedData( + dataBefore, + { + ousdTotalSupply: 0, + ousdTotalValue: 0, + vaultCheckBalance: 0, + userOusd: 0, + userUsdc: usdcUnits("60"), + vaultUsdc: usdcUnits("60").mul(-1), + queued: 0, + claimable: usdcUnits("60"), + claimed: usdcUnits("60"), + nextWithdrawalIndex: 0, + }, + fixtureWithUser + ); + }); + it("Shouldn't allow user to perform a new request and claim more than the USDC available", async () => { + const { vault, ousd, usdc, josh, matt, daniel, governor } = fixture; + await vault.connect(matt).claimWithdrawal(2); + // All user give OUSD to another user + await ousd.connect(josh).transfer(matt.address, ousdUnits("20")); + await ousd.connect(daniel).transfer(matt.address, ousdUnits("10")); + + // Matt request more than the remaining 60 OUSD to be withdrawn + await vault.connect(matt).requestWithdrawal(ousdUnits("60")); + await advanceTime(delayPeriod); // Advance in time to ensure time delay between request and claim. + await usdc + .connect(await impersonateAndFund(vault.address)) + .transfer(governor.address, usdcUnits("50")); // Vault loses 50 USDC + + const tx = vault.connect(matt).claimWithdrawal(3); + await expect(tx).to.be.revertedWith("Queue pending liquidity"); + }); + }); + describe("with 40 USDC in the queue, 15 USDC in the vault, 44 USDC in the strategy, vault insolvent by 5% => Slash 1 ether (1/20 = 5%), 19 USDC total value", () => { + beforeEach(async () => { + const { governor, vault, usdc, daniel, josh, matt, strategist } = + fixture; + // Deploy a mock strategy + const mockStrategy = await deployWithConfirmation("MockStrategy"); + await vault.connect(governor).approveStrategy(mockStrategy.address); + await vault.connect(governor).setDefaultStrategy(mockStrategy.address); + + // Mint USDC to users + await usdc.mintTo(daniel.address, usdcUnits("10")); + await usdc.mintTo(josh.address, usdcUnits("20")); + await usdc.mintTo(matt.address, usdcUnits("30")); + + // Approve USDC to Vault + await usdc.connect(daniel).approve(vault.address, usdcUnits("10")); + await usdc.connect(josh).approve(vault.address, usdcUnits("20")); + await usdc.connect(matt).approve(vault.address, usdcUnits("30")); + + // Mint 60 OUSD to three users + await vault.connect(daniel).mint(usdc.address, usdcUnits("10"), "0"); + await vault.connect(josh).mint(usdc.address, usdcUnits("20"), "0"); + await vault.connect(matt).mint(usdc.address, usdcUnits("30"), "0"); + + await vault.allocate(); + // Request and claim 10 + 20 + 10 = 40 USDC from Vault + await vault.connect(daniel).requestWithdrawal(ousdUnits("10")); + await vault.connect(josh).requestWithdrawal(ousdUnits("20")); + await vault.connect(matt).requestWithdrawal(ousdUnits("10")); + await advanceTime(delayPeriod); // Advance in time to ensure time delay between request and claim. + + // Simulate slash event of 1 ethers + await usdc + .connect(await impersonateAndFund(mockStrategy.address)) + .transfer(governor.address, usdcUnits("1")); + + // Strategist sends 15 USDC to the vault + await vault + .connect(strategist) + .withdrawFromStrategy( + mockStrategy.address, + [usdc.address], + [usdcUnits("15")] + ); + + await vault.connect(josh).addWithdrawalQueueLiquidity(); + }); + it("Should allow first user to claim the request of 10 USDC", async () => { + const { vault, daniel } = fixture; + const fixtureWithUser = { ...fixture, user: daniel }; + const dataBefore = await snapData(fixtureWithUser); + + const tx = await vault.connect(daniel).claimWithdrawal(2); + + expect(tx) + .to.emit(vault, "WithdrawalClaimed") + .withArgs(daniel.address, 2, ousdUnits("10")); + + await assertChangedData( + dataBefore, + { + ousdTotalSupply: 0, + ousdTotalValue: 0, + vaultCheckBalance: 0, + userOusd: 0, + userUsdc: usdcUnits("10"), + vaultUsdc: usdcUnits("10").mul(-1), + queued: 0, + claimable: 0, + claimed: usdcUnits("10"), + nextWithdrawalIndex: 0, + }, + fixtureWithUser + ); + }); + it("Fail to allow second user to claim the request of 20 USDC, due to liquidity", async () => { + const { vault, josh } = fixture; + + const tx = vault.connect(josh).claimWithdrawal(3); + + await expect(tx).to.be.revertedWith("Queue pending liquidity"); + }); + it("Should allow a user to create a new request with solvency check off", async () => { + // maxSupplyDiff is set to 0 so no insolvency check + const { vault, matt } = fixture; + const fixtureWithUser = { ...fixture, user: matt }; + const dataBefore = await snapData(fixtureWithUser); + + const tx = vault.connect(matt).requestWithdrawal(ousdUnits("10")); + + expect(tx) + .to.emit(vault, "WithdrawalRequested") + .withArgs(matt.address, 5, ousdUnits("10"), ousdUnits("50")); + + await assertChangedData( + dataBefore, + { + ousdTotalSupply: ousdUnits("10").mul(-1), + ousdTotalValue: ousdUnits("10").mul(-1), + vaultCheckBalance: usdcUnits("10").mul(-1), + userOusd: ousdUnits("10").mul(-1), + userUsdc: 0, + vaultUsdc: 0, + queued: usdcUnits("10").mul(1), + claimable: 0, + claimed: 0, + nextWithdrawalIndex: 1, + }, + fixtureWithUser + ); + }); + describe("with solvency check at 3%", () => { + beforeEach(async () => { + const { vault } = fixture; + // Turn on insolvency check with 3% buffer + await vault + .connect(await impersonateAndFund(await vault.governor())) + .setMaxSupplyDiff(ousdUnits("0.03")); + }); + it("Fail to allow user to create a new request due to insolvency check", async () => { + const { vault, matt } = fixture; + + const tx = vault.connect(matt).requestWithdrawal(ousdUnits("1")); + + await expect(tx).to.be.revertedWith("Backing supply liquidity error"); + }); + it("Fail to allow first user to claim a withdrawal due to insolvency check", async () => { + const { vault, daniel } = fixture; + + await advanceTime(delayPeriod); + + const tx = vault.connect(daniel).claimWithdrawal(2); + + await expect(tx).to.be.revertedWith("Backing supply liquidity error"); + }); + }); + describe("with solvency check at 10%", () => { + beforeEach(async () => { + const { vault } = fixture; + // Turn on insolvency check with 10% buffer + await vault + .connect(await impersonateAndFund(await vault.governor())) + .setMaxSupplyDiff(ousdUnits("0.1")); + }); + it("Should allow user to create a new request", async () => { + const { vault, matt } = fixture; + + const tx = await vault + .connect(matt) + .requestWithdrawal(ousdUnits("1")); + + expect(tx) + .to.emit(vault, "WithdrawalRequested") + .withArgs(matt.address, 3, ousdUnits("1"), ousdUnits("41")); + }); + it("Should allow first user to claim the request of 10 USDC", async () => { + const { vault, daniel } = fixture; + + const tx = await vault.connect(daniel).claimWithdrawal(2); + + expect(tx) + .to.emit(vault, "WithdrawalClaimed") + .withArgs(daniel.address, 2, ousdUnits("10")); + }); + }); + }); + describe("with 99 USDC in the queue, 40 USDC in the vault, total supply 1, 1% insolvency buffer", () => { + let mockStrategy; + beforeEach(async () => { + const { governor, vault, usdc, daniel, josh, matt, strategist } = + fixture; + // Deploy a mock strategy + mockStrategy = await deployWithConfirmation("MockStrategy"); + await vault.connect(governor).approveStrategy(mockStrategy.address); + await vault.connect(governor).setDefaultStrategy(mockStrategy.address); + + // Mint USDC to users + await usdc.mintTo(daniel.address, usdcUnits("20")); + await usdc.mintTo(josh.address, usdcUnits("30")); + await usdc.mintTo(matt.address, usdcUnits("50")); + + // Approve USDC to Vault + await usdc.connect(daniel).approve(vault.address, usdcUnits("20")); + await usdc.connect(josh).approve(vault.address, usdcUnits("30")); + await usdc.connect(matt).approve(vault.address, usdcUnits("50")); + + // Mint 100 OUSD to three users + await vault.connect(daniel).mint(usdc.address, usdcUnits("20"), "0"); + await vault.connect(josh).mint(usdc.address, usdcUnits("30"), "0"); + await vault.connect(matt).mint(usdc.address, usdcUnits("50"), "0"); + + await vault.allocate(); + + // Request and claim 20 + 30 + 49 = 99 USDC from Vault + await vault.connect(daniel).requestWithdrawal(ousdUnits("20")); + await vault.connect(josh).requestWithdrawal(ousdUnits("30")); + await vault.connect(matt).requestWithdrawal(ousdUnits("49")); + + await advanceTime(delayPeriod); // Advance in time to ensure time delay between request and claim. + + // Strategist sends 40 USDC to the vault + await vault + .connect(strategist) + .withdrawFromStrategy( + mockStrategy.address, + [usdc.address], + [usdcUnits("40")] + ); - it("Should calculate redeem outputs", async () => { - const { vault, anna, usdc, ousd } = fixture; - - // OUSD total supply is 200 backed by 200 USDS - await expect( - await vault.calculateRedeemOutputs(ousdUnits("50")) - ).to.deep.equal([ - usdsUnits("50"), // USDS - BigNumber.from(0), // USDT - BigNumber.from(0), // USDC - BigNumber.from(0), // TUSD - ]); - - // Mint an additional 600 USDC, so OUSD is backed by 600 USDC and 200 USDS - // meaning 1/4 of any redeem should come from USDS and 2/3 from USDC - await usdc.connect(anna).approve(vault.address, usdcUnits("600")); - await vault.connect(anna).mint(usdc.address, usdcUnits("600"), 0); - await expect(anna).has.a.balanceOf("600", ousd); - await expect( - await vault.calculateRedeemOutputs(ousdUnits("100")) - ).to.deep.equal([ - usdsUnits("25"), // USDS - BigNumber.from(0), // USDT - usdcUnits("75"), // USDC - BigNumber.from(0), // TUSD - ]); + await vault.connect(josh).addWithdrawalQueueLiquidity(); + + // Turn on insolvency check with 10% buffer + await vault + .connect(await impersonateAndFund(await vault.governor())) + .setMaxSupplyDiff(ousdUnits("0.01")); + }); + describe("with 2 ether slashed leaving 100 - 40 - 2 = 58 USDC in the strategy", () => { + beforeEach(async () => { + const { usdc, governor } = fixture; + + // Simulate slash event of 2 ethers + await usdc + .connect(await impersonateAndFund(mockStrategy.address)) + .transfer(governor.address, usdcUnits("2")); + }); + it("Should have total value of zero", async () => { + // 100 from mints - 99 outstanding withdrawals - 2 from slashing = -1 value which is rounder up to zero + expect(await fixture.vault.totalValue()).to.equal(0); + }); + it("Should have check balance of zero", async () => { + const { vault, usdc } = fixture; + // 100 from mints - 99 outstanding withdrawals - 2 from slashing = -1 value which is rounder up to zero + expect(await vault.checkBalance(usdc.address)).to.equal(0); + }); + it("Fail to allow user to create a new request due to too many outstanding requests", async () => { + const { vault, matt } = fixture; + + const tx = vault.connect(matt).requestWithdrawal(ousdUnits("1")); + + await expect(tx).to.be.revertedWith("Too many outstanding requests"); + }); + it("Fail to allow first user to claim a withdrawal due to too many outstanding requests", async () => { + const { vault, daniel } = fixture; + + await advanceTime(delayPeriod); + + const tx = vault.connect(daniel).claimWithdrawal(2); + + await expect(tx).to.be.revertedWith("Too many outstanding requests"); + }); + }); + describe("with 1 ether slashed leaving 100 - 40 - 1 = 59 USDC in the strategy", () => { + beforeEach(async () => { + const { usdc, governor } = fixture; + + // Simulate slash event of 1 ethers + await usdc + .connect(await impersonateAndFund(mockStrategy.address)) + .transfer(governor.address, usdcUnits("1")); + }); + it("Should have total value of zero", async () => { + // 100 from mints - 99 outstanding withdrawals - 1 from slashing = 0 value + expect(await fixture.vault.totalValue()).to.equal(0); + }); + it("Fail to allow user to create a new request due to too many outstanding requests", async () => { + const { vault, matt } = fixture; + + const tx = vault.connect(matt).requestWithdrawal(ousdUnits("1")); + + await expect(tx).to.be.revertedWith("Too many outstanding requests"); + }); + it("Fail to allow first user to claim a withdrawal due to too many outstanding requests", async () => { + const { vault, daniel } = fixture; + + await advanceTime(delayPeriod); + + const tx = vault.connect(daniel).claimWithdrawal(2); + + await expect(tx).to.be.revertedWith("Too many outstanding requests"); + }); + }); + describe("with 0.02 ether slashed leaving 100 - 40 - 0.02 = 59.98 USDC in the strategy", () => { + beforeEach(async () => { + const { usdc, governor } = fixture; + + // Simulate slash event of 0.001 ethers + await usdc + .connect(await impersonateAndFund(mockStrategy.address)) + .transfer(governor.address, usdcUnits("0.02")); + }); + it("Should have total value of zero", async () => { + // 100 from mints - 99 outstanding withdrawals - 0.001 from slashing = 0.999 total value + expect(await fixture.vault.totalValue()).to.equal(ousdUnits("0.98")); + }); + it("Fail to allow user to create a new 1 USDC request due to too many outstanding requests", async () => { + const { vault, matt } = fixture; + + const tx = vault.connect(matt).requestWithdrawal(ousdUnits("1")); + + await expect(tx).to.be.revertedWith("Too many outstanding requests"); + }); + + it("Fail to allow user to create a new 0.01 USDC request due to insolvency check", async () => { + const { vault, matt } = fixture; + + const tx = vault.connect(matt).requestWithdrawal(ousdUnits("0.01")); + + await expect(tx).to.be.revertedWith("Backing supply liquidity error"); + }); + it("Fail to allow first user to claim a withdrawal due to insolvency check", async () => { + const { vault, daniel } = fixture; + + await advanceTime(delayPeriod); + + const tx = vault.connect(daniel).claimWithdrawal(2); + + // diff = 1 total supply / 0.98 assets = 1.020408163265306122 which is > 1 maxSupplyDiff + await expect(tx).to.be.revertedWith("Backing supply liquidity error"); + }); + }); + }); }); }); diff --git a/contracts/test/vault/upgrade.js b/contracts/test/vault/upgrade.js deleted file mode 100644 index 8044bc26b3..0000000000 --- a/contracts/test/vault/upgrade.js +++ /dev/null @@ -1,27 +0,0 @@ -const { expect } = require("chai"); - -const { loadDefaultFixture } = require("../_fixture"); - -describe("VaultAdmin Upgrades", async function () { - let ousd, vault, governor; - - beforeEach(async function () { - const fixture = await loadDefaultFixture(); - vault = fixture.vault; - ousd = fixture.ousd; - governor = fixture.governor; - }); - - it("should upgrade to a new admin implementation", async function () { - const newVaultImpl = ousd.address; // ;) - await vault.connect(governor).setAdminImpl(newVaultImpl); - expect(await vault.ADMIN_IMPLEMENTATION()).to.eq(newVaultImpl); - }); - - it("should not upgrade to a non-contract admin implementation", async function () { - const blankImpl = "0x4000000000000000000000000000000000000004"; - await expect( - vault.connect(governor).setAdminImpl(blankImpl) - ).to.be.revertedWith("new implementation is not a contract"); - }); -}); diff --git a/contracts/test/vault/vault.mainnet.fork-test.js b/contracts/test/vault/vault.mainnet.fork-test.js index 0b30da267b..afeaccf5a8 100644 --- a/contracts/test/vault/vault.mainnet.fork-test.js +++ b/contracts/test/vault/vault.mainnet.fork-test.js @@ -1,5 +1,4 @@ const { expect } = require("chai"); -const { utils } = require("ethers"); const addresses = require("../../utils/addresses"); const { loadDefaultFixture } = require("./../_fixture"); @@ -9,16 +8,12 @@ const { differenceInStrategyBalance, differenceInErc20TokenBalances, isCI, - decimalsFor, } = require("./../helpers"); const { canWithdrawAllFromMorphoOUSD } = require("../../utils/morpho"); const { impersonateAndFund } = require("../../utils/signers"); const { shouldHaveRewardTokensConfigured, } = require("./../behaviour/reward-tokens.fork"); -const { formatUnits } = require("ethers/lib/utils"); - -const log = require("../../utils/logger")("test:fork:ousd:vault"); /** * Regarding hardcoded addresses: @@ -76,11 +71,13 @@ describe("ForkTest: Vault", function () { ); }); - it("Should have the correct OUSD MetaStrategy address set", async () => { + it("Should have the OUSD/USDC AMO mint whitelist", async () => { const { vault } = fixture; - expect(await vault.ousdMetaStrategy()).to.equal( - addresses.mainnet.CurveOUSDAMOStrategy - ); + expect( + await vault.isMintWhitelistedStrategy( + addresses.mainnet.CurveOUSDAMOStrategy + ) + ).to.be.true; }); it("Should have supported assets", async () => { @@ -110,7 +107,7 @@ describe("ForkTest: Vault", function () { expect(await vault.capitalPaused()).to.be.false; }); - it("Should allow to mint and redeem w/ USDC", async () => { + it("Should allow to mint w/ USDC", async () => { const { ousd, vault, josh, usdc } = fixture; const balancePreMint = await ousd .connect(josh) @@ -123,37 +120,6 @@ describe("ForkTest: Vault", function () { const balanceDiff = balancePostMint.sub(balancePreMint); expect(balanceDiff).to.approxEqualTolerance(ousdUnits("500"), 1); - - await vault.connect(josh).redeem(balanceDiff, 0); - - const balancePostRedeem = await ousd - .connect(josh) - .balanceOf(josh.getAddress()); - expect(balancePreMint).to.approxEqualTolerance(balancePostRedeem, 1); - }); - - it("Should calculate and return redeem outputs", async () => { - const { vault } = fixture; - const outputs = await vault.calculateRedeemOutputs(ousdUnits("100")); - expect(outputs).to.have.length(1); - const assets = await vault.getAllAssets(); - - const values = await Promise.all( - outputs.map(async (output, index) => { - const asset = await ethers.getContractAt( - "MintableERC20", - assets[index] - ); - return parseFloat( - formatUnits(output.toString(), await decimalsFor(asset)) - ); - }) - ); - - expect(ousdUnits(values[0].toString())).to.approxEqualTolerance( - ousdUnits("100"), - 0.5 - ); }); it("should withdraw from and deposit to strategy", async () => { @@ -220,34 +186,6 @@ describe("ForkTest: Vault", function () { }); }); - describe("Oracle", () => { - it("Should have correct Price Oracle address set", async () => { - const { vault } = fixture; - expect(await vault.priceProvider()).to.equal( - "0x36CFB852d3b84afB3909BCf4ea0dbe8C82eE1C3c" - ); - }); - - it("Should return a price for minting with USDC", async () => { - const { vault, usdc } = fixture; - const price = await vault.priceUnitMint(usdc.address); - - log(`Price for minting with USDC: ${utils.formatEther(price, 6)}`); - - expect(price).to.be.lte(utils.parseEther("1")); - expect(price).to.be.gt(utils.parseEther("0.999")); - }); - - it("Should return a price for redeem with USDC", async () => { - const { vault, usdc } = fixture; - const price = await vault.priceUnitRedeem(usdc.address); - - log(`Price for redeeming with USDC: ${utils.formatEther(price, 6)}`); - - expect(price).to.be.gte(utils.parseEther("1")); - }); - }); - describe("Assets & Strategies", () => { it("Should NOT have any unknown assets", async () => { const { vault } = fixture; @@ -292,12 +230,12 @@ describe("ForkTest: Vault", function () { } }); - it("Should have correct default strategy set for USDC", async () => { - const { vault, usdc } = fixture; + it("Should have correct default strategy", async () => { + const { vault } = fixture; - expect([ - "0x3643cafA6eF3dd7Fcc2ADaD1cabf708075AFFf6e", // Morpho OUSD v2 Strategy - ]).to.include(await vault.assetDefaultStrategies(usdc.address)); + expect(await vault.defaultStrategy()).to.equal( + "0x3643cafA6eF3dd7Fcc2ADaD1cabf708075AFFf6e" // Morpho OUSD v2 Strategy + ); }); it("Should be able to withdraw from all strategies", async () => { diff --git a/contracts/test/vault/vault.sonic.fork-test.js b/contracts/test/vault/vault.sonic.fork-test.js index f1deaef205..526f1e1273 100644 --- a/contracts/test/vault/vault.sonic.fork-test.js +++ b/contracts/test/vault/vault.sonic.fork-test.js @@ -79,11 +79,6 @@ describe("ForkTest: Sonic Vault", function () { addresses.multichainBuybackOperator ); }); - - it("Should have redeem fee set to 0.1%", async () => { - const { oSonicVault } = fixture; - expect(await oSonicVault.redeemFeeBps()).to.equal(BigNumber.from("10")); - }); }); describe("Rebase", () => { diff --git a/contracts/test/vault/z_mockvault.js b/contracts/test/vault/z_mockvault.js index bc28471a85..7a21a21206 100644 --- a/contracts/test/vault/z_mockvault.js +++ b/contracts/test/vault/z_mockvault.js @@ -50,10 +50,7 @@ describe("Vault mock with rebase", async () => { const promise = expect( mockVault .connect(matt) - .redeem( - utils.parseUnits(`${redeemAmount}`, 18), - utils.parseUnits(`${redeemAmount}`, 18) - ) + .requestWithdrawal(utils.parseUnits(`${redeemAmount}`, 18)) ); if (revertMessage) { From 373a646ee66abaa7071d88ee76029954cb831312 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment?= <55331875+clement-ux@users.noreply.github.com> Date: Tue, 10 Feb 2026 13:17:08 +0100 Subject: [PATCH 10/36] [CurvePB] Migrate to new CurvePB. (#2768) * Deploy 166 * Add deployment timestamp * Implement CurvePoolBooster migration script for new version deployment * Update proposalId in CurvePoolBooster migration script --- .../deploy/mainnet/167_curve_pb_migration.js | 113 ++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 contracts/deploy/mainnet/167_curve_pb_migration.js diff --git a/contracts/deploy/mainnet/167_curve_pb_migration.js b/contracts/deploy/mainnet/167_curve_pb_migration.js new file mode 100644 index 0000000000..fc07a964e2 --- /dev/null +++ b/contracts/deploy/mainnet/167_curve_pb_migration.js @@ -0,0 +1,113 @@ +const addresses = require("../../utils/addresses"); +const { deploymentWithGovernanceProposal } = require("../../utils/deploy"); + +const pools = [ + { + name: "TriOGN-OETH", + reward: "0x856c4Efb76C1D1AE02e20CEB03A2A6a08b0b8dC3", + gauge: "0x92d956C1F89a2c71efEEB4Bac45d02016bdD2408", + pool: "0xB8ac7ce449Ed72FF61dE8043d67B507f9F523Fa2", + salt: "0xB6073788e5302122F4DfB6C5aD53a1EAC9cb0289000000000000000000000004", + oldPB: "0x7B5e7aDEBC2da89912BffE55c86675CeCE59803E", + newPB: "0xFc87E0ABe3592945Ad7587F99161dBb340faa767", + }, + { + name: "TriOGN-OUSD", + reward: "0x2A8e1E676Ec238d8A992307B495b45B3fEAa5e86", + gauge: "0x92d956C1F89a2c71efEEB4Bac45d02016bdD2408", + pool: "0xB8ac7ce449Ed72FF61dE8043d67B507f9F523Fa2", + salt: "0xB6073788e5302122F4DfB6C5aD53a1EAC9cb0289000000000000000000000005", + oldPB: "0x514447A1Ef103f3cF4B0fE92A947F071239f2809", + newPB: "0x028C6f98C20094367F7b048F0aFA1E11ce0A8DBd", + }, + { + name: "OETH/LidoARM", + reward: "0x856c4Efb76C1D1AE02e20CEB03A2A6a08b0b8dC3", + gauge: "0xFcaDecfc207e3329a38821E5B04f88029cc4fa62", + pool: "0x95753095F15870ACC0cB0Ed224478ea61aeb0b8e", + salt: "0xB6073788e5302122F4DfB6C5aD53a1EAC9cb0289000000000000000000000006", + oldPB: "0xb61e201bd3c864431ec3d3df0ed1ecf38b63cc8b", + newPB: "0x1A43D2F1bb24aC262D1d7ac05D16823E526FcA32", + }, + { + name: "OUSD/frxUSD", + reward: "0x2A8e1E676Ec238d8A992307B495b45B3fEAa5e86", + gauge: "0x928961d0C4F6E8C683bd5695527D060f18d7d60b", + pool: "0x68d03Ed49800e92D7Aa8aB171424007e55Fd1F49", + salt: "0xB6073788e5302122F4DfB6C5aD53a1EAC9cb0289000000000000000000000007", + oldPB: "0x02260e04f7851FbF09dd9a4fFd1880568410F77e", + newPB: "0xc835BcA1378acb32C522f3831b8dba161a763FBE", + }, +]; + +module.exports = deploymentWithGovernanceProposal( + { + deployName: "167_curve_pb_migration", + forceDeploy: false, + reduceQueueTime: true, + deployerIsProposer: false, + proposalId: + "70336422361048948609898488791231866822675120244859766659168608925215046418279", + }, + async () => { + const cCurvePoolBoosterFactory = await ethers.getContractAt( + "CurvePoolBoosterFactory", + "0xB6073788e5302122F4DfB6C5aD53a1EAC9cb0289" + ); + + const actions = []; + + for (const pool of pools) { + const cOldPoolBooster = await ethers.getContractAt( + "CurvePoolBoosterPlain", + pool.oldPB + ); + + const cRewardToken = await ethers.getContractAt("OUSD", pool.reward); + console.log("Reward token address:", cRewardToken.address); + + // Action 1: Create new CurvePoolBoosterPlain + actions.push({ + contract: cCurvePoolBoosterFactory, + signature: + "createCurvePoolBoosterPlain(address,address,address,uint16,address,address,bytes32,address)", + args: [ + pool.reward, + pool.gauge, + addresses.multichainStrategist, + 0, + addresses.mainnet.CampaignRemoteManager, + addresses.votemarket, + pool.salt, + pool.newPB, + ], + }); + + // Action 2: Undelegate yield from pool to old pool booster + actions.push({ + contract: cRewardToken, + signature: "undelegateYield(address)", + args: [pool.pool], + }); + + // Action 3: YieldForward to pool to new pool booster + actions.push({ + contract: cRewardToken, + signature: "delegateYield(address,address)", + args: [pool.pool, pool.newPB], + }); + + // Action 4: Rescue tokens from old pool booster to new one + actions.push({ + contract: cOldPoolBooster, + signature: "rescueToken(address,address)", + args: [pool.reward, pool.newPB], + }); + } + + return { + name: "Migrate token rewards from CurvePoolBooster to new version", + actions, + }; + } +); From 717f819d4e6c8bac2aa2a2b10a4f0aaf6b170bb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment?= <55331875+clement-ux@users.noreply.github.com> Date: Tue, 10 Feb 2026 14:44:53 +0100 Subject: [PATCH 11/36] Reorder deployment script number (#2791) --- ...n_strategy_proxies.js => 168_crosschain_strategy_proxies.js} | 2 +- .../{166_crosschain_strategy.js => 169_crosschain_strategy.js} | 2 +- .../{167_curve_pb_migration.js => 170_curve_pb_migration.js} | 2 +- .../{167_ousd_vault_upgrade.js => 171_ousd_vault_upgrade.js} | 2 +- .../{168_oeth_vault_upgrade.js => 172_oeth_vault_upgrade.js} | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) rename contracts/deploy/mainnet/{165_crosschain_strategy_proxies.js => 168_crosschain_strategy_proxies.js} (93%) rename contracts/deploy/mainnet/{166_crosschain_strategy.js => 169_crosschain_strategy.js} (97%) rename contracts/deploy/mainnet/{167_curve_pb_migration.js => 170_curve_pb_migration.js} (98%) rename contracts/deploy/mainnet/{167_ousd_vault_upgrade.js => 171_ousd_vault_upgrade.js} (97%) rename contracts/deploy/mainnet/{168_oeth_vault_upgrade.js => 172_oeth_vault_upgrade.js} (97%) diff --git a/contracts/deploy/mainnet/165_crosschain_strategy_proxies.js b/contracts/deploy/mainnet/168_crosschain_strategy_proxies.js similarity index 93% rename from contracts/deploy/mainnet/165_crosschain_strategy_proxies.js rename to contracts/deploy/mainnet/168_crosschain_strategy_proxies.js index a9e4fedb75..a33b711cf0 100644 --- a/contracts/deploy/mainnet/165_crosschain_strategy_proxies.js +++ b/contracts/deploy/mainnet/168_crosschain_strategy_proxies.js @@ -3,7 +3,7 @@ const { deployProxyWithCreateX } = require("../deployActions"); module.exports = deploymentWithGovernanceProposal( { - deployName: "165_crosschain_strategy_proxies", + deployName: "168_crosschain_strategy_proxies", forceDeploy: false, reduceQueueTime: true, deployerIsProposer: false, diff --git a/contracts/deploy/mainnet/166_crosschain_strategy.js b/contracts/deploy/mainnet/169_crosschain_strategy.js similarity index 97% rename from contracts/deploy/mainnet/166_crosschain_strategy.js rename to contracts/deploy/mainnet/169_crosschain_strategy.js index 8b11a023fd..388e82ba4a 100644 --- a/contracts/deploy/mainnet/166_crosschain_strategy.js +++ b/contracts/deploy/mainnet/169_crosschain_strategy.js @@ -8,7 +8,7 @@ const { module.exports = deploymentWithGovernanceProposal( { - deployName: "166_crosschain_strategy", + deployName: "169_crosschain_strategy", forceDeploy: false, reduceQueueTime: true, deployerIsProposer: false, diff --git a/contracts/deploy/mainnet/167_curve_pb_migration.js b/contracts/deploy/mainnet/170_curve_pb_migration.js similarity index 98% rename from contracts/deploy/mainnet/167_curve_pb_migration.js rename to contracts/deploy/mainnet/170_curve_pb_migration.js index fc07a964e2..3444cbd0c4 100644 --- a/contracts/deploy/mainnet/167_curve_pb_migration.js +++ b/contracts/deploy/mainnet/170_curve_pb_migration.js @@ -42,7 +42,7 @@ const pools = [ module.exports = deploymentWithGovernanceProposal( { - deployName: "167_curve_pb_migration", + deployName: "170_curve_pb_migration", forceDeploy: false, reduceQueueTime: true, deployerIsProposer: false, diff --git a/contracts/deploy/mainnet/167_ousd_vault_upgrade.js b/contracts/deploy/mainnet/171_ousd_vault_upgrade.js similarity index 97% rename from contracts/deploy/mainnet/167_ousd_vault_upgrade.js rename to contracts/deploy/mainnet/171_ousd_vault_upgrade.js index 62c39a76fe..e105c66fbf 100644 --- a/contracts/deploy/mainnet/167_ousd_vault_upgrade.js +++ b/contracts/deploy/mainnet/171_ousd_vault_upgrade.js @@ -3,7 +3,7 @@ const { deploymentWithGovernanceProposal } = require("../../utils/deploy"); module.exports = deploymentWithGovernanceProposal( { - deployName: "167_ousd_vault_upgrade", + deployName: "171_ousd_vault_upgrade", forceDeploy: false, //forceSkip: true, reduceQueueTime: true, diff --git a/contracts/deploy/mainnet/168_oeth_vault_upgrade.js b/contracts/deploy/mainnet/172_oeth_vault_upgrade.js similarity index 97% rename from contracts/deploy/mainnet/168_oeth_vault_upgrade.js rename to contracts/deploy/mainnet/172_oeth_vault_upgrade.js index b6c3a18ef0..89ddb5a97e 100644 --- a/contracts/deploy/mainnet/168_oeth_vault_upgrade.js +++ b/contracts/deploy/mainnet/172_oeth_vault_upgrade.js @@ -3,7 +3,7 @@ const { deploymentWithGovernanceProposal } = require("../../utils/deploy"); module.exports = deploymentWithGovernanceProposal( { - deployName: "168_oeth_vault_upgrade", + deployName: "172_oeth_vault_upgrade", forceDeploy: false, //forceSkip: true, reduceQueueTime: true, From 2618d7d005e130218ee4bfc3975e47778840b306 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment?= <55331875+clement-ux@users.noreply.github.com> Date: Wed, 11 Feb 2026 14:54:15 +0100 Subject: [PATCH 12/36] [CurvePB] Improve CurvePoolBoosterBribesModule (#2789) * [CurvePB] Enhance pool reward calculation by adding gauge and LP token details * [CurvePB] Improve CurvePoolBoosterBribesModule - Use single `manageCampaign` call instead of 3 separate calls - Make bridge fee configurable instead of hardcoded - Send ETH from Safe via execTransactionFromModule instead of requiring pool boosters to hold ETH - Add NatSpec documentation * [CurvePB] Make manageBribes fully configurable per pool Add totalRewardAmounts and extraDuration parameters to the parameterized manageBribes overload, allowing per-pool control over reward amounts and campaign duration. The no-arg version retains the previous defaults (use all rewards, +1 period, no maxRewardPerVote update). Remove redundant onlyOperator modifier from internal _manageBribes. Update NatSpec. * [CurvePB] Rename POOLS to pools Follow Solidity naming convention: all-caps is reserved for constants and immutables. Rename mutable storage array to lowercase. Prefix function parameters with underscore to avoid shadowing. * [CurvePB] Revert when removing a pool that does not exist _removePoolBoosterAddress previously did a silent no-op when the address was not found. Now it reverts with "Pool not found" to prevent masking bugs in the caller. * [CurvePB] Add duplicate check and use calldata for pool lists Refactor _addPoolBoosterAddress to accept a single address and revert with "Pool already added" if it already exists, preventing double processing and double bridge fees. Switch addPoolBoosterAddress parameter to calldata for gas efficiency, consistent with removePoolBoosterAddress. * [CurvePB] Make additionalGasLimit configurable Replace the hardcoded 1000000 gas limit with a storage variable that can be set at construction and updated via setAdditionalGasLimit. Add corresponding event. * [CurvePB] Use calldata for manageBribes parameters Switch the parameterized manageBribes overload from memory to calldata for gas efficiency, avoiding unnecessary memory copies. * [CurvePB] Add additionalGasLimit to deployment script Pass the new constructor parameter (1000000) to match the updated CurvePoolBoosterBribesModule constructor signature. * [CurvePB] Fix lint: shorten inline comment exceeding max line length * [CurvePB] Update poolBooster task for new manageBribes signature Update the ABI and manageBribes call to match the new contract interface. Use the no-arg manageBribes() when skipRewardPerVote is true, and the 3-param overload with default totalRewardAmounts (max) and extraDuration (1) otherwise. * [CurvePB] Add zero address check in _addPoolBoosterAddress * [CurvePB] Add max value check for bridge fee * [CurvePB] Add max value check for additional gas limit * [CurvePB] Rename length to pbCount in _manageBribes * [CurvePB] Rename pools to poolBoosters to avoid confusion with AMM pools * Reorder deployment number --- .../CurvePoolBoosterBribesModule.sol | 288 ++++++++++++------ .../mainnet/173_improve_curve_pb_module.js | 44 +++ contracts/tasks/poolBooster.js | 72 ++++- 3 files changed, 289 insertions(+), 115 deletions(-) create mode 100644 contracts/deploy/mainnet/173_improve_curve_pb_module.js diff --git a/contracts/contracts/automation/CurvePoolBoosterBribesModule.sol b/contracts/contracts/automation/CurvePoolBoosterBribesModule.sol index 8bb2919862..ced22d9e2a 100644 --- a/contracts/contracts/automation/CurvePoolBoosterBribesModule.sol +++ b/contracts/contracts/automation/CurvePoolBoosterBribesModule.sol @@ -4,151 +4,241 @@ pragma solidity ^0.8.0; import { AbstractSafeModule } from "./AbstractSafeModule.sol"; interface ICurvePoolBooster { - function manageTotalRewardAmount( - uint256 bridgeFee, + function manageCampaign( + uint256 totalRewardAmount, + uint8 numberOfPeriods, + uint256 maxRewardPerVote, uint256 additionalGasLimit - ) external; - - function manageNumberOfPeriods( - uint8 extraNumberOfPeriods, - uint256 bridgeFee, - uint256 additionalGasLimit - ) external; - - function manageRewardPerVote( - uint256 newMaxRewardPerVote, - uint256 bridgeFee, - uint256 additionalGasLimit - ) external; + ) external payable; } +/// @title CurvePoolBoosterBribesModule +/// @author Origin Protocol +/// @notice Gnosis Safe module that automates the management of VotemarketV2 bribe campaigns +/// across multiple CurvePoolBooster contracts. It instructs the Safe to call `manageCampaign` +/// on each registered pool booster, forwarding ETH from the Safe's balance to cover +/// bridge fees. Campaign parameters (reward amount, duration, reward rate) can be +/// configured per pool or left to sensible defaults. contract CurvePoolBoosterBribesModule is AbstractSafeModule { - address[] public POOLS; + //////////////////////////////////////////////////// + /// --- Storage + //////////////////////////////////////////////////// + + /// @notice List of CurvePoolBooster addresses managed by this module + address[] public poolBoosters; + + /// @notice ETH amount sent per pool booster to cover the L1 -> L2 bridge fee + uint256 public bridgeFee; + + /// @notice Gas limit passed to manageCampaign for cross-chain execution + uint256 public additionalGasLimit; + + //////////////////////////////////////////////////// + /// --- Events + //////////////////////////////////////////////////// + event BridgeFeeUpdated(uint256 newFee); + event AdditionalGasLimitUpdated(uint256 newGasLimit); event PoolBoosterAddressAdded(address pool); event PoolBoosterAddressRemoved(address pool); + //////////////////////////////////////////////////// + /// --- Constructor + //////////////////////////////////////////////////// + + /// @param _safeContract Address of the Gnosis Safe this module is attached to + /// @param _operator Address authorized to call operator-restricted functions + /// @param _poolBoosters Initial list of CurvePoolBooster addresses to manage + /// @param _bridgeFee ETH amount to send per pool booster for bridge fees + /// @param _additionalGasLimit Gas limit for cross-chain execution in manageCampaign constructor( address _safeContract, address _operator, - address[] memory _pools + address[] memory _poolBoosters, + uint256 _bridgeFee, + uint256 _additionalGasLimit ) AbstractSafeModule(_safeContract) { _grantRole(OPERATOR_ROLE, _operator); - _addPoolBoosterAddress(_pools); + for (uint256 i = 0; i < _poolBoosters.length; i++) { + _addPoolBoosterAddress(_poolBoosters[i]); + } + _setBridgeFee(_bridgeFee); + _setAdditionalGasLimit(_additionalGasLimit); } - function addPoolBoosterAddress(address[] memory pools) + //////////////////////////////////////////////////// + /// --- External Mutative Functions + //////////////////////////////////////////////////// + + /// @notice Add new CurvePoolBooster addresses to the managed list + /// @param _poolBoosters Addresses to add + function addPoolBoosterAddress(address[] calldata _poolBoosters) external onlyOperator { - _addPoolBoosterAddress(pools); - } - - function _addPoolBoosterAddress(address[] memory pools) internal { - for (uint256 i = 0; i < pools.length; i++) { - POOLS.push(pools[i]); - emit PoolBoosterAddressAdded(pools[i]); + for (uint256 i = 0; i < _poolBoosters.length; i++) { + _addPoolBoosterAddress(_poolBoosters[i]); } } - function removePoolBoosterAddress(address[] calldata pools) + /// @notice Remove CurvePoolBooster addresses from the managed list + /// @param _poolBoosters Addresses to remove + function removePoolBoosterAddress(address[] calldata _poolBoosters) external onlyOperator { - for (uint256 i = 0; i < pools.length; i++) { - _removePoolBoosterAddress(pools[i]); + for (uint256 i = 0; i < _poolBoosters.length; i++) { + _removePoolBoosterAddress(_poolBoosters[i]); } } - function _removePoolBoosterAddress(address pool) internal { - uint256 length = POOLS.length; - for (uint256 i = 0; i < length; i++) { - if (POOLS[i] == pool) { - POOLS[i] = POOLS[length - 1]; - POOLS.pop(); - emit PoolBoosterAddressRemoved(pool); - break; - } - } + /// @notice Update the ETH bridge fee sent per pool booster + /// @param newFee New bridge fee amount in wei + function setBridgeFee(uint256 newFee) external onlyOperator { + _setBridgeFee(newFee); + } + + /// @notice Update the additional gas limit for cross-chain execution + /// @param newGasLimit New gas limit value + function setAdditionalGasLimit(uint256 newGasLimit) external onlyOperator { + _setAdditionalGasLimit(newGasLimit); } + /// @notice Default entry point to manage bribe campaigns for all registered pool boosters. + /// Applies the same behavior to every pool: + /// - totalRewardAmount = type(uint256).max → use all available reward tokens + /// - numberOfPeriods = 1 → extend by one period (week) + /// - maxRewardPerVote = 0 → no update + /// @dev Calls `manageCampaign` on each pool booster via the Safe. The Safe must hold + /// enough ETH to cover `bridgeFee * poolBoosters.length`. function manageBribes() external onlyOperator { - uint256[] memory rewardsPerVote = new uint256[](POOLS.length); - _manageBribes(rewardsPerVote); + uint256[] memory totalRewardAmounts = new uint256[]( + poolBoosters.length + ); + uint8[] memory extraDuration = new uint8[](poolBoosters.length); + uint256[] memory rewardsPerVote = new uint256[](poolBoosters.length); + for (uint256 i = 0; i < poolBoosters.length; i++) { + totalRewardAmounts[i] = type(uint256).max; // use all available rewards + extraDuration[i] = 1; // extend by 1 period (week) + rewardsPerVote[i] = 0; // no update to maxRewardPerVote + } + _manageBribes(totalRewardAmounts, extraDuration, rewardsPerVote); } - function manageBribes(uint256[] memory rewardsPerVote) - external - onlyOperator - { - require(POOLS.length == rewardsPerVote.length, "Length mismatch"); - _manageBribes(rewardsPerVote); + /// @notice Fully configurable entry point to manage bribe campaigns. Allows setting + /// reward amounts, durations, and reward rates individually for each pool. + /// Each array must have the same length as the poolBoosters array. + /// @param totalRewardAmounts Total reward amount per pool (0 = no update, type(uint256).max = use all available) + /// @param extraDuration Number of periods to extend per pool (0 = no update, 1 = +1 week) + /// @param rewardsPerVote Max reward per vote per pool (0 = no update) + function manageBribes( + uint256[] calldata totalRewardAmounts, + uint8[] calldata extraDuration, + uint256[] calldata rewardsPerVote + ) external onlyOperator { + require( + poolBoosters.length == totalRewardAmounts.length, + "Length mismatch" + ); + require(poolBoosters.length == extraDuration.length, "Length mismatch"); + require( + poolBoosters.length == rewardsPerVote.length, + "Length mismatch" + ); + _manageBribes(totalRewardAmounts, extraDuration, rewardsPerVote); } - function _manageBribes(uint256[] memory rewardsPerVote) - internal - onlyOperator - { - uint256 length = POOLS.length; - for (uint256 i = 0; i < length; i++) { - address poolBoosterAddress = POOLS[i]; + //////////////////////////////////////////////////// + /// --- External View Functions + //////////////////////////////////////////////////// - // PoolBooster need to have a balance of at least 0.003 ether to operate - // 0.001 ether are used for the bridge fee - require( - poolBoosterAddress.balance > 0.003 ether, - "Insufficient balance for bribes" - ); + /// @notice Get the full list of managed CurvePoolBooster addresses + /// @return Array of pool booster addresses + function getPoolBoosters() external view returns (address[] memory) { + return poolBoosters; + } - require( - safeContract.execTransactionFromModule( - poolBoosterAddress, - 0, // Value - abi.encodeWithSelector( - ICurvePoolBooster.manageNumberOfPeriods.selector, - 1, // extraNumberOfPeriods - 0.001 ether, // bridgeFee - 1000000 // additionalGasLimit - ), - 0 - ), - "Manage number of periods failed" - ); + //////////////////////////////////////////////////// + /// --- Internal Functions + //////////////////////////////////////////////////// + + /// @notice Internal logic to add a single pool booster address + /// @dev Reverts if the address is already in the poolBoosters array + /// @param _pool Address to append to the poolBoosters array + function _addPoolBoosterAddress(address _pool) internal { + require(_pool != address(0), "Zero address"); + for (uint256 j = 0; j < poolBoosters.length; j++) { + require(poolBoosters[j] != _pool, "Pool already added"); + } + poolBoosters.push(_pool); + emit PoolBoosterAddressAdded(_pool); + } - require( - safeContract.execTransactionFromModule( - poolBoosterAddress, - 0, // Value - abi.encodeWithSelector( - ICurvePoolBooster.manageTotalRewardAmount.selector, - 0.001 ether, // bridgeFee - 1000000 // additionalGasLimit - ), - 0 - ), - "Manage total reward failed" - ); + /// @notice Internal logic to remove a pool booster address + /// @dev Swaps the target with the last element and pops to avoid gaps + /// @param pool Address to remove from the poolBoosters array + function _removePoolBoosterAddress(address pool) internal { + uint256 length = poolBoosters.length; + for (uint256 i = 0; i < length; i++) { + if (poolBoosters[i] == pool) { + poolBoosters[i] = poolBoosters[length - 1]; + poolBoosters.pop(); + emit PoolBoosterAddressRemoved(pool); + return; + } + } + revert("Pool not found"); + } + + /// @notice Internal logic to set the bridge fee + /// @param newFee New bridge fee amount in wei + function _setBridgeFee(uint256 newFee) internal { + require(newFee <= 0.01 ether, "Bridge fee too high"); + bridgeFee = newFee; + emit BridgeFeeUpdated(newFee); + } - // Skip setting reward per vote if it's zero - if (rewardsPerVote[i] == 0) continue; + /// @notice Internal logic to set the additional gas limit + /// @param newGasLimit New gas limit value + function _setAdditionalGasLimit(uint256 newGasLimit) internal { + require(newGasLimit <= 10_000_000, "Gas limit too high"); + additionalGasLimit = newGasLimit; + emit AdditionalGasLimitUpdated(newGasLimit); + } + + /// @notice Internal logic to manage bribe campaigns for all registered pool boosters + /// @dev Iterates over all pool boosters and instructs the Safe to call `manageCampaign` + /// on each one, sending `bridgeFee` ETH from the Safe's balance per call. + /// @param totalRewardAmounts Total reward amount per pool (0 = no update, type(uint256).max = use all available) + /// @param extraDuration Number of periods to extend per pool (0 = no update) + /// @param rewardsPerVote Max reward per vote per pool (0 = no update) + function _manageBribes( + uint256[] memory totalRewardAmounts, + uint8[] memory extraDuration, + uint256[] memory rewardsPerVote + ) internal { + uint256 pbCount = poolBoosters.length; + require( + address(safeContract).balance >= bridgeFee * pbCount, + "Not enough ETH for bridge fees" + ); + for (uint256 i = 0; i < pbCount; i++) { + address poolBoosterAddress = poolBoosters[i]; require( safeContract.execTransactionFromModule( poolBoosterAddress, - 0, // Value + bridgeFee, // ETH value to cover bridge fee abi.encodeWithSelector( - ICurvePoolBooster.manageRewardPerVote.selector, - rewardsPerVote[i], // newMaxRewardPerVote - 0.001 ether, // bridgeFee - 1000000 // additionalGasLimit + ICurvePoolBooster.manageCampaign.selector, + totalRewardAmounts[i], // 0 = no update, max = use all + extraDuration[i], // numberOfPeriods, 0 = no update, 1 = +1 period (week) + rewardsPerVote[i], // maxRewardPerVote, 0 = no update + additionalGasLimit ), 0 ), - "Set reward per vote failed" + "Manage campaign failed" ); } } - - function getPools() external view returns (address[] memory) { - return POOLS; - } } diff --git a/contracts/deploy/mainnet/173_improve_curve_pb_module.js b/contracts/deploy/mainnet/173_improve_curve_pb_module.js new file mode 100644 index 0000000000..02960b8691 --- /dev/null +++ b/contracts/deploy/mainnet/173_improve_curve_pb_module.js @@ -0,0 +1,44 @@ +const addresses = require("../../utils/addresses"); +const { deploymentWithGovernanceProposal } = require("../../utils/deploy"); + +module.exports = deploymentWithGovernanceProposal( + { + deployName: "173_improve_curve_pb_module", + forceDeploy: false, + reduceQueueTime: true, + deployerIsProposer: false, + proposalId: "", + }, + async ({ deployWithConfirmation }) => { + const safeAddress = addresses.multichainStrategist; + + const moduleName = `CurvePoolBoosterBribesModule`; + await deployWithConfirmation( + moduleName, + [ + safeAddress, + // Defender Relayer + addresses.mainnet.validatorRegistrator, + [ + "0xFc87E0ABe3592945Ad7587F99161dBb340faa767", + "0x1A43D2F1bb24aC262D1d7ac05D16823E526FcA32", + "0x028C6f98C20094367F7b048F0aFA1E11ce0A8DBd", + "0xc835BcA1378acb32C522f3831b8dba161a763FBE", + ], + ethers.utils.parseEther("0.001"), // Bridge fee + 1000000, // Additional gas limit for cross-chain execution + ], + "CurvePoolBoosterBribesModule" + ); + const cCurvePoolBoosterBribesModule = await ethers.getContract(moduleName); + + console.log( + `${moduleName} (for ${safeAddress}) deployed to`, + cCurvePoolBoosterBribesModule.address + ); + + return { + actions: [], + }; + } +); diff --git a/contracts/tasks/poolBooster.js b/contracts/tasks/poolBooster.js index a1ce93a658..4b66d66c7a 100644 --- a/contracts/tasks/poolBooster.js +++ b/contracts/tasks/poolBooster.js @@ -23,14 +23,20 @@ const SECONDS_PER_WEEK = 60 * 60 * 24 * 7; // Minimal ABIs const bribesModuleAbi = [ - "function getPools() external view returns (address[])", - "function manageBribes(uint256[] memory rewardsPerVote) external", + "function getPoolBoosters() external view returns (address[])", + "function manageBribes() external", + "function manageBribes(uint256[] totalRewardAmounts, uint8[] extraDuration, uint256[] rewardsPerVote) external", ]; const poolBoosterAbi = [ "function rewardToken() external view returns (address)", + "function gauge() external view returns (address)", ]; +const gaugeAbi = ["function lp_token() external view returns (address)"]; + +const lpTokenAbi = ["function name() external view returns (string)"]; + const gaugeControllerAbi = [ "function get_total_weight() external view returns (uint256)", ]; @@ -233,14 +239,28 @@ async function calculateRewardsPerVote(provider, options = {}) { // Fetch pools from BribesModule const bribesModule = new Contract(BRIBES_MODULE, bribesModuleAbi, provider); - const pools = await bribesModule.getPools(); - output(`Found ${pools.length} pools in BribesModule`); + const pools = await bribesModule.getPoolBoosters(); + output(`Found ${pools.length} pool boosters in BribesModule`); if (pools.length === 0) { - output("No pools registered, nothing to calculate"); + output("No pool boosters registered, nothing to calculate"); return { pools: [], rewardsPerVote: [] }; } + // Display gauge names for each pool + for (let i = 0; i < pools.length; i++) { + const poolAddress = pools[i]; + if (poolAddress === addresses.zero) continue; + + const poolBooster = new Contract(poolAddress, poolBoosterAbi, provider); + const gaugeAddress = await poolBooster.gauge(); + const gauge = new Contract(gaugeAddress, gaugeAbi, provider); + const lpTokenAddress = await gauge.lp_token(); + const lpToken = new Contract(lpTokenAddress, lpTokenAbi, provider); + const lpTokenName = await lpToken.name(); + output(` Pool ${i + 1} (${poolAddress}) - LP: ${lpTokenName}`); + } + // If skipping, return array of zeros if (skipRewardPerVote) { output(`Mode: Skip RewardPerVote (array of zeros)\n`); @@ -283,10 +303,17 @@ async function calculateRewardsPerVote(provider, options = {}) { for (let i = 0; i < pools.length; i++) { const poolAddress = pools[i]; - output(`\nPool ${i + 1}: ${poolAddress}`); - - // Get reward token for this pool + // Get reward token and LP token name for this pool const poolBooster = new Contract(poolAddress, poolBoosterAbi, provider); + const gaugeAddress = await poolBooster.gauge(); + const gauge = new Contract(gaugeAddress, gaugeAbi, provider); + const lpTokenAddress = await gauge.lp_token(); + const lpToken = new Contract(lpTokenAddress, lpTokenAbi, provider); + const lpTokenName = await lpToken.name(); + output(`\nPool Booster ${i + 1}:\t ${poolAddress}`); + output(`Gauge:\t\t ${gaugeAddress}`); + output(`Pool:\t\t ${lpTokenAddress} (${lpTokenName})`); + const rewardToken = await poolBooster.rewardToken(); try { @@ -365,26 +392,39 @@ async function manageBribes({ signer ); - const { rewardsPerVote } = await calculateRewardsPerVote(provider, { + const { pools, rewardsPerVote } = await calculateRewardsPerVote(provider, { targetEfficiency, skipRewardPerVote, log, }); if (rewardsPerVote.length === 0) { - log("No pools registered in BribesModule, nothing to do"); + log("No pool boosters registered in BribesModule, nothing to do"); return; } // Call manageBribes on the SafeModule log(`\n--- Calling manageBribes on BribesModule ---`); - log( - `Rewards per vote: [${rewardsPerVote - .map((r) => formatUnits(r, 18)) - .join(", ")}]` - ); - const tx = await bribesModuleContract.manageBribes(rewardsPerVote); + let tx; + if (skipRewardPerVote) { + // Use the no-arg version (defaults: all rewards, +1 period, no reward rate update) + log("Using default parameters (no-arg manageBribes)"); + tx = await bribesModuleContract["manageBribes()"](); + } else { + // Build default arrays for totalRewardAmounts and extraDuration + const totalRewardAmounts = pools.map(() => ethers.constants.MaxUint256); + const extraDuration = pools.map(() => 1); + + log( + `Rewards per vote: [${rewardsPerVote + .map((r) => formatUnits(r, 18)) + .join(", ")}]` + ); + tx = await bribesModuleContract[ + "manageBribes(uint256[],uint8[],uint256[])" + ](totalRewardAmounts, extraDuration, rewardsPerVote); + } const receipt = await logTxDetails(tx, "manageBribes"); // Final verification From d3fd3e3257860b13a4d10897153d90a45a4572c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment?= <55331875+clement-ux@users.noreply.github.com> Date: Wed, 11 Feb 2026 15:39:53 +0100 Subject: [PATCH 13/36] [CurvePB] Deploy CurvePBBribeModule for Safe. (#2794) * Deploy 173 * prettier + lint --- .../mainnet/173_improve_curve_pb_module.js | 3 +- .../deployments/mainnet/.migrations.json | 3 +- .../mainnet/CurvePoolBoosterBribesModule.json | 395 ++++++++++++++---- .../ccf1518a549ca89ba906f9e99681e2d8.json | 72 ++++ .../mainnet/CurvePoolBoosterBribesModule.json | 20 +- 5 files changed, 416 insertions(+), 77 deletions(-) create mode 100644 contracts/deployments/mainnet/solcInputs/ccf1518a549ca89ba906f9e99681e2d8.json diff --git a/contracts/deploy/mainnet/173_improve_curve_pb_module.js b/contracts/deploy/mainnet/173_improve_curve_pb_module.js index 02960b8691..9f142f9c13 100644 --- a/contracts/deploy/mainnet/173_improve_curve_pb_module.js +++ b/contracts/deploy/mainnet/173_improve_curve_pb_module.js @@ -28,7 +28,8 @@ module.exports = deploymentWithGovernanceProposal( ethers.utils.parseEther("0.001"), // Bridge fee 1000000, // Additional gas limit for cross-chain execution ], - "CurvePoolBoosterBribesModule" + "CurvePoolBoosterBribesModule", + true ); const cCurvePoolBoosterBribesModule = await ethers.getContract(moduleName); diff --git a/contracts/deployments/mainnet/.migrations.json b/contracts/deployments/mainnet/.migrations.json index 9676f8bd25..5701802f4c 100644 --- a/contracts/deployments/mainnet/.migrations.json +++ b/contracts/deployments/mainnet/.migrations.json @@ -60,5 +60,6 @@ "163_increase_oeth_redeem_fee": 1768182515, "164_fix_curve_pb_module": 1769002767, "165_improve_curve_pb_module": 1769002768, - "166_curve_pool_booster_factory": 1769174027 + "166_curve_pool_booster_factory": 1769174027, + "173_improve_curve_pb_module": 1770818413 } \ No newline at end of file diff --git a/contracts/deployments/mainnet/CurvePoolBoosterBribesModule.json b/contracts/deployments/mainnet/CurvePoolBoosterBribesModule.json index a9fc992752..3fd1acfeac 100644 --- a/contracts/deployments/mainnet/CurvePoolBoosterBribesModule.json +++ b/contracts/deployments/mainnet/CurvePoolBoosterBribesModule.json @@ -1,5 +1,5 @@ { - "address": "0xfA01FE78bBD1ade8064C34FE537BA2E2670B2198", + "address": "0x82447F7C3eF0a628B0c614A3eA0898a5bb7c18fe", "abi": [ { "inputs": [ @@ -15,13 +15,49 @@ }, { "internalType": "address[]", - "name": "_pools", + "name": "_poolBoosters", "type": "address[]" + }, + { + "internalType": "uint256", + "name": "_bridgeFee", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_additionalGasLimit", + "type": "uint256" } ], "stateMutability": "nonpayable", "type": "constructor" }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "newGasLimit", + "type": "uint256" + } + ], + "name": "AdditionalGasLimitUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "newFee", + "type": "uint256" + } + ], + "name": "BridgeFeeUpdated", + "type": "event" + }, { "anonymous": false, "inputs": [ @@ -152,38 +188,45 @@ { "inputs": [ { - "internalType": "uint256", - "name": "", - "type": "uint256" + "internalType": "address[]", + "name": "_poolBoosters", + "type": "address[]" } ], - "name": "POOLS", + "name": "addPoolBoosterAddress", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "additionalGasLimit", "outputs": [ { - "internalType": "address", + "internalType": "uint256", "name": "", - "type": "address" + "type": "uint256" } ], "stateMutability": "view", "type": "function" }, { - "inputs": [ + "inputs": [], + "name": "bridgeFee", + "outputs": [ { - "internalType": "address[]", - "name": "pools", - "type": "address[]" + "internalType": "uint256", + "name": "", + "type": "uint256" } ], - "name": "addPoolBoosterAddress", - "outputs": [], - "stateMutability": "nonpayable", + "stateMutability": "view", "type": "function" }, { "inputs": [], - "name": "getPools", + "name": "getPoolBoosters", "outputs": [ { "internalType": "address[]", @@ -298,8 +341,25 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "manageBribes", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ + { + "internalType": "uint256[]", + "name": "totalRewardAmounts", + "type": "uint256[]" + }, + { + "internalType": "uint8[]", + "name": "extraDuration", + "type": "uint8[]" + }, { "internalType": "uint256[]", "name": "rewardsPerVote", @@ -312,17 +372,29 @@ "type": "function" }, { - "inputs": [], - "name": "manageBribes", - "outputs": [], - "stateMutability": "nonpayable", + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "poolBoosters", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "address[]", - "name": "pools", + "name": "_poolBoosters", "type": "address[]" } ], @@ -380,6 +452,32 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "newGasLimit", + "type": "uint256" + } + ], + "name": "setAdditionalGasLimit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "newFee", + "type": "uint256" + } + ], + "name": "setBridgeFee", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { @@ -422,22 +520,22 @@ "type": "receive" } ], - "transactionHash": "0x8109c85361f6fbab0f565d4dfea6580acea8c0a5c394252039346d2209473b3e", + "transactionHash": "0xdb8d6615983f86446f383050e109b04f0254d9873becfae39b5a8b73710edadc", "receipt": { "to": null, "from": "0x074105fdD39e982B2ffE749A193c942db1046AB9", - "contractAddress": "0xfA01FE78bBD1ade8064C34FE537BA2E2670B2198", - "transactionIndex": 12, - "gasUsed": "1870482", - "logsBloom": "0x00000004000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020002000000000000800000000000000000008000020000000000400000000800000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000004080000000000000000000000000000000018000001000100000000000000000000000000000000000000000000000000000400500000000000020000000000000000000000000800000000000200002000000020000000000000000", - "blockHash": "0x9f8224f5aaf809c5d1e74df8debcbefcb352bdb687e48f82e07e9c1e7590f913", - "transactionHash": "0x8109c85361f6fbab0f565d4dfea6580acea8c0a5c394252039346d2209473b3e", + "contractAddress": "0x82447F7C3eF0a628B0c614A3eA0898a5bb7c18fe", + "transactionIndex": 26, + "gasUsed": "2088147", + "logsBloom": "0x000000040000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200020000000000009000000000000000000080000200000000004000008008000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000800000000000000000000000000000000180000010011000040000001800000000000000000000004000000000000400004005000000000000200000000000000000000000008000000000000000000000000a0000000000000000", + "blockHash": "0x3720dcf45fd450d5ffdbd4981ace52f42e054a175bf73bb826a311e297ba4a0a", + "transactionHash": "0xdb8d6615983f86446f383050e109b04f0254d9873becfae39b5a8b73710edadc", "logs": [ { - "transactionIndex": 12, - "blockNumber": 24283517, - "transactionHash": "0x8109c85361f6fbab0f565d4dfea6580acea8c0a5c394252039346d2209473b3e", - "address": "0xfA01FE78bBD1ade8064C34FE537BA2E2670B2198", + "transactionIndex": 26, + "blockNumber": 24433984, + "transactionHash": "0xdb8d6615983f86446f383050e109b04f0254d9873becfae39b5a8b73710edadc", + "address": "0x82447F7C3eF0a628B0c614A3eA0898a5bb7c18fe", "topics": [ "0x2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d", "0x0000000000000000000000000000000000000000000000000000000000000000", @@ -445,14 +543,14 @@ "0x000000000000000000000000074105fdd39e982b2ffe749a193c942db1046ab9" ], "data": "0x", - "logIndex": 59, - "blockHash": "0x9f8224f5aaf809c5d1e74df8debcbefcb352bdb687e48f82e07e9c1e7590f913" + "logIndex": 243, + "blockHash": "0x3720dcf45fd450d5ffdbd4981ace52f42e054a175bf73bb826a311e297ba4a0a" }, { - "transactionIndex": 12, - "blockNumber": 24283517, - "transactionHash": "0x8109c85361f6fbab0f565d4dfea6580acea8c0a5c394252039346d2209473b3e", - "address": "0xfA01FE78bBD1ade8064C34FE537BA2E2670B2198", + "transactionIndex": 26, + "blockNumber": 24433984, + "transactionHash": "0xdb8d6615983f86446f383050e109b04f0254d9873becfae39b5a8b73710edadc", + "address": "0x82447F7C3eF0a628B0c614A3eA0898a5bb7c18fe", "topics": [ "0x2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d", "0x97667070c54ef182b0f5858b034beac1b6f3089aa2d3188bb1e8929f4fa9b929", @@ -460,14 +558,14 @@ "0x000000000000000000000000074105fdd39e982b2ffe749a193c942db1046ab9" ], "data": "0x", - "logIndex": 60, - "blockHash": "0x9f8224f5aaf809c5d1e74df8debcbefcb352bdb687e48f82e07e9c1e7590f913" + "logIndex": 244, + "blockHash": "0x3720dcf45fd450d5ffdbd4981ace52f42e054a175bf73bb826a311e297ba4a0a" }, { - "transactionIndex": 12, - "blockNumber": 24283517, - "transactionHash": "0x8109c85361f6fbab0f565d4dfea6580acea8c0a5c394252039346d2209473b3e", - "address": "0xfA01FE78bBD1ade8064C34FE537BA2E2670B2198", + "transactionIndex": 26, + "blockNumber": 24433984, + "transactionHash": "0xdb8d6615983f86446f383050e109b04f0254d9873becfae39b5a8b73710edadc", + "address": "0x82447F7C3eF0a628B0c614A3eA0898a5bb7c18fe", "topics": [ "0x2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d", "0x97667070c54ef182b0f5858b034beac1b6f3089aa2d3188bb1e8929f4fa9b929", @@ -475,36 +573,84 @@ "0x000000000000000000000000074105fdd39e982b2ffe749a193c942db1046ab9" ], "data": "0x", - "logIndex": 61, - "blockHash": "0x9f8224f5aaf809c5d1e74df8debcbefcb352bdb687e48f82e07e9c1e7590f913" + "logIndex": 245, + "blockHash": "0x3720dcf45fd450d5ffdbd4981ace52f42e054a175bf73bb826a311e297ba4a0a" + }, + { + "transactionIndex": 26, + "blockNumber": 24433984, + "transactionHash": "0xdb8d6615983f86446f383050e109b04f0254d9873becfae39b5a8b73710edadc", + "address": "0x82447F7C3eF0a628B0c614A3eA0898a5bb7c18fe", + "topics": [ + "0x73fbf341ebd14259e9d6b52784447ca2c15aa93c480c93927fd8135aefff7747" + ], + "data": "0x000000000000000000000000fc87e0abe3592945ad7587f99161dbb340faa767", + "logIndex": 246, + "blockHash": "0x3720dcf45fd450d5ffdbd4981ace52f42e054a175bf73bb826a311e297ba4a0a" + }, + { + "transactionIndex": 26, + "blockNumber": 24433984, + "transactionHash": "0xdb8d6615983f86446f383050e109b04f0254d9873becfae39b5a8b73710edadc", + "address": "0x82447F7C3eF0a628B0c614A3eA0898a5bb7c18fe", + "topics": [ + "0x73fbf341ebd14259e9d6b52784447ca2c15aa93c480c93927fd8135aefff7747" + ], + "data": "0x0000000000000000000000001a43d2f1bb24ac262d1d7ac05d16823e526fca32", + "logIndex": 247, + "blockHash": "0x3720dcf45fd450d5ffdbd4981ace52f42e054a175bf73bb826a311e297ba4a0a" }, { - "transactionIndex": 12, - "blockNumber": 24283517, - "transactionHash": "0x8109c85361f6fbab0f565d4dfea6580acea8c0a5c394252039346d2209473b3e", - "address": "0xfA01FE78bBD1ade8064C34FE537BA2E2670B2198", + "transactionIndex": 26, + "blockNumber": 24433984, + "transactionHash": "0xdb8d6615983f86446f383050e109b04f0254d9873becfae39b5a8b73710edadc", + "address": "0x82447F7C3eF0a628B0c614A3eA0898a5bb7c18fe", "topics": [ "0x73fbf341ebd14259e9d6b52784447ca2c15aa93c480c93927fd8135aefff7747" ], - "data": "0x0000000000000000000000007b5e7adebc2da89912bffe55c86675cece59803e", - "logIndex": 62, - "blockHash": "0x9f8224f5aaf809c5d1e74df8debcbefcb352bdb687e48f82e07e9c1e7590f913" + "data": "0x000000000000000000000000028c6f98c20094367f7b048f0afa1e11ce0a8dbd", + "logIndex": 248, + "blockHash": "0x3720dcf45fd450d5ffdbd4981ace52f42e054a175bf73bb826a311e297ba4a0a" }, { - "transactionIndex": 12, - "blockNumber": 24283517, - "transactionHash": "0x8109c85361f6fbab0f565d4dfea6580acea8c0a5c394252039346d2209473b3e", - "address": "0xfA01FE78bBD1ade8064C34FE537BA2E2670B2198", + "transactionIndex": 26, + "blockNumber": 24433984, + "transactionHash": "0xdb8d6615983f86446f383050e109b04f0254d9873becfae39b5a8b73710edadc", + "address": "0x82447F7C3eF0a628B0c614A3eA0898a5bb7c18fe", "topics": [ "0x73fbf341ebd14259e9d6b52784447ca2c15aa93c480c93927fd8135aefff7747" ], - "data": "0x000000000000000000000000514447a1ef103f3cf4b0fe92a947f071239f2809", - "logIndex": 63, - "blockHash": "0x9f8224f5aaf809c5d1e74df8debcbefcb352bdb687e48f82e07e9c1e7590f913" + "data": "0x000000000000000000000000c835bca1378acb32c522f3831b8dba161a763fbe", + "logIndex": 249, + "blockHash": "0x3720dcf45fd450d5ffdbd4981ace52f42e054a175bf73bb826a311e297ba4a0a" + }, + { + "transactionIndex": 26, + "blockNumber": 24433984, + "transactionHash": "0xdb8d6615983f86446f383050e109b04f0254d9873becfae39b5a8b73710edadc", + "address": "0x82447F7C3eF0a628B0c614A3eA0898a5bb7c18fe", + "topics": [ + "0x42dfb00d085d601e55327921154ae76c1b24270b026c5a0c51caee18eb4c401f" + ], + "data": "0x00000000000000000000000000000000000000000000000000038d7ea4c68000", + "logIndex": 250, + "blockHash": "0x3720dcf45fd450d5ffdbd4981ace52f42e054a175bf73bb826a311e297ba4a0a" + }, + { + "transactionIndex": 26, + "blockNumber": 24433984, + "transactionHash": "0xdb8d6615983f86446f383050e109b04f0254d9873becfae39b5a8b73710edadc", + "address": "0x82447F7C3eF0a628B0c614A3eA0898a5bb7c18fe", + "topics": [ + "0xfe19406854a66706284c7b3480e4c2e37dc42dc468e1211d7a1abcaf721a6f00" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000f4240", + "logIndex": 251, + "blockHash": "0x3720dcf45fd450d5ffdbd4981ace52f42e054a175bf73bb826a311e297ba4a0a" } ], - "blockNumber": 24283517, - "cumulativeGasUsed": "4277347", + "blockNumber": 24433984, + "cumulativeGasUsed": "9136462", "status": 1, "byzantium": true }, @@ -512,17 +658,22 @@ "0x4FF1b9D9ba8558F5EAfCec096318eA0d8b541971", "0x4b91827516f79d6F6a1F292eD99671663b09169a", [ - "0x7B5e7aDEBC2da89912BffE55c86675CeCE59803E", - "0x514447A1Ef103f3cF4B0fE92A947F071239f2809" - ] + "0xFc87E0ABe3592945Ad7587F99161dBb340faa767", + "0x1A43D2F1bb24aC262D1d7ac05D16823E526FcA32", + "0x028C6f98C20094367F7b048F0aFA1E11ce0A8DBd", + "0xc835BcA1378acb32C522f3831b8dba161a763FBE" + ], + "1000000000000000", + 1000000 ], - "numDeployments": 2, - "solcInputHash": "cf850427439840d5b5cae044570ed114", - "metadata": "{\"compiler\":{\"version\":\"0.8.28+commit.7893614a\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_safeContract\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_operator\",\"type\":\"address\"},{\"internalType\":\"address[]\",\"name\":\"_pools\",\"type\":\"address[]\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"pool\",\"type\":\"address\"}],\"name\":\"PoolBoosterAddressAdded\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"pool\",\"type\":\"address\"}],\"name\":\"PoolBoosterAddressRemoved\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"previousAdminRole\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"newAdminRole\",\"type\":\"bytes32\"}],\"name\":\"RoleAdminChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"}],\"name\":\"RoleGranted\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"}],\"name\":\"RoleRevoked\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"DEFAULT_ADMIN_ROLE\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"OPERATOR_ROLE\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"POOLS\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"pools\",\"type\":\"address[]\"}],\"name\":\"addPoolBoosterAddress\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getPools\",\"outputs\":[{\"internalType\":\"address[]\",\"name\":\"\",\"type\":\"address[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"}],\"name\":\"getRoleAdmin\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"index\",\"type\":\"uint256\"}],\"name\":\"getRoleMember\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"}],\"name\":\"getRoleMemberCount\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"grantRole\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"hasRole\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256[]\",\"name\":\"rewardsPerVote\",\"type\":\"uint256[]\"}],\"name\":\"manageBribes\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"manageBribes\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"pools\",\"type\":\"address[]\"}],\"name\":\"removePoolBoosterAddress\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"renounceRole\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"revokeRole\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"safeContract\",\"outputs\":[{\"internalType\":\"contract ISafe\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes4\",\"name\":\"interfaceId\",\"type\":\"bytes4\"}],\"name\":\"supportsInterface\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"transferTokens\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"stateMutability\":\"payable\",\"type\":\"receive\"}],\"devdoc\":{\"events\":{\"RoleAdminChanged(bytes32,bytes32,bytes32)\":{\"details\":\"Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole` `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite {RoleAdminChanged} not being emitted signaling this. _Available since v3.1._\"},\"RoleGranted(bytes32,address,address)\":{\"details\":\"Emitted when `account` is granted `role`. `sender` is the account that originated the contract call, an admin role bearer except when using {AccessControl-_setupRole}.\"},\"RoleRevoked(bytes32,address,address)\":{\"details\":\"Emitted when `account` is revoked `role`. `sender` is the account that originated the contract call: - if using `revokeRole`, it is the admin role bearer - if using `renounceRole`, it is the role bearer (i.e. `account`)\"}},\"kind\":\"dev\",\"methods\":{\"getRoleAdmin(bytes32)\":{\"details\":\"Returns the admin role that controls `role`. See {grantRole} and {revokeRole}. To change a role's admin, use {_setRoleAdmin}.\"},\"getRoleMember(bytes32,uint256)\":{\"details\":\"Returns one of the accounts that have `role`. `index` must be a value between 0 and {getRoleMemberCount}, non-inclusive. Role bearers are not sorted in any particular way, and their ordering may change at any point. WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure you perform all queries on the same block. See the following https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post] for more information.\"},\"getRoleMemberCount(bytes32)\":{\"details\":\"Returns the number of accounts that have `role`. Can be used together with {getRoleMember} to enumerate all bearers of a role.\"},\"grantRole(bytes32,address)\":{\"details\":\"Grants `role` to `account`. If `account` had not been already granted `role`, emits a {RoleGranted} event. Requirements: - the caller must have ``role``'s admin role.\"},\"hasRole(bytes32,address)\":{\"details\":\"Returns `true` if `account` has been granted `role`.\"},\"renounceRole(bytes32,address)\":{\"details\":\"Revokes `role` from the calling account. Roles are often managed via {grantRole} and {revokeRole}: this function's purpose is to provide a mechanism for accounts to lose their privileges if they are compromised (such as when a trusted device is misplaced). If the calling account had been revoked `role`, emits a {RoleRevoked} event. Requirements: - the caller must be `account`.\"},\"revokeRole(bytes32,address)\":{\"details\":\"Revokes `role` from `account`. If `account` had been granted `role`, emits a {RoleRevoked} event. Requirements: - the caller must have ``role``'s admin role.\"},\"supportsInterface(bytes4)\":{\"details\":\"See {IERC165-supportsInterface}.\"},\"transferTokens(address,uint256)\":{\"details\":\"Helps recovering any tokens accidentally sent to this module.\",\"params\":{\"amount\":\"Amount to transfer. 0 to transfer all balance.\",\"token\":\"Token to transfer. 0x0 to transfer Native token.\"}}},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"contracts/automation/CurvePoolBoosterBribesModule.sol\":\"CurvePoolBoosterBribesModule\"},\"evmVersion\":\"paris\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\",\"useLiteralContent\":true},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[]},\"sources\":{\"@openzeppelin/contracts/access/AccessControl.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (access/AccessControl.sol)\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"./IAccessControl.sol\\\";\\nimport \\\"../utils/Context.sol\\\";\\nimport \\\"../utils/Strings.sol\\\";\\nimport \\\"../utils/introspection/ERC165.sol\\\";\\n\\n/**\\n * @dev Contract module that allows children to implement role-based access\\n * control mechanisms. This is a lightweight version that doesn't allow enumerating role\\n * members except through off-chain means by accessing the contract event logs. Some\\n * applications may benefit from on-chain enumerability, for those cases see\\n * {AccessControlEnumerable}.\\n *\\n * Roles are referred to by their `bytes32` identifier. These should be exposed\\n * in the external API and be unique. The best way to achieve this is by\\n * using `public constant` hash digests:\\n *\\n * ```\\n * bytes32 public constant MY_ROLE = keccak256(\\\"MY_ROLE\\\");\\n * ```\\n *\\n * Roles can be used to represent a set of permissions. To restrict access to a\\n * function call, use {hasRole}:\\n *\\n * ```\\n * function foo() public {\\n * require(hasRole(MY_ROLE, msg.sender));\\n * ...\\n * }\\n * ```\\n *\\n * Roles can be granted and revoked dynamically via the {grantRole} and\\n * {revokeRole} functions. Each role has an associated admin role, and only\\n * accounts that have a role's admin role can call {grantRole} and {revokeRole}.\\n *\\n * By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means\\n * that only accounts with this role will be able to grant or revoke other\\n * roles. More complex role relationships can be created by using\\n * {_setRoleAdmin}.\\n *\\n * WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to\\n * grant and revoke this role. Extra precautions should be taken to secure\\n * accounts that have been granted it.\\n */\\nabstract contract AccessControl is Context, IAccessControl, ERC165 {\\n struct RoleData {\\n mapping(address => bool) members;\\n bytes32 adminRole;\\n }\\n\\n mapping(bytes32 => RoleData) private _roles;\\n\\n bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;\\n\\n /**\\n * @dev Modifier that checks that an account has a specific role. Reverts\\n * with a standardized message including the required role.\\n *\\n * The format of the revert reason is given by the following regular expression:\\n *\\n * /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/\\n *\\n * _Available since v4.1._\\n */\\n modifier onlyRole(bytes32 role) {\\n _checkRole(role, _msgSender());\\n _;\\n }\\n\\n /**\\n * @dev See {IERC165-supportsInterface}.\\n */\\n function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {\\n return interfaceId == type(IAccessControl).interfaceId || super.supportsInterface(interfaceId);\\n }\\n\\n /**\\n * @dev Returns `true` if `account` has been granted `role`.\\n */\\n function hasRole(bytes32 role, address account) public view override returns (bool) {\\n return _roles[role].members[account];\\n }\\n\\n /**\\n * @dev Revert with a standard message if `account` is missing `role`.\\n *\\n * The format of the revert reason is given by the following regular expression:\\n *\\n * /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/\\n */\\n function _checkRole(bytes32 role, address account) internal view {\\n if (!hasRole(role, account)) {\\n revert(\\n string(\\n abi.encodePacked(\\n \\\"AccessControl: account \\\",\\n Strings.toHexString(uint160(account), 20),\\n \\\" is missing role \\\",\\n Strings.toHexString(uint256(role), 32)\\n )\\n )\\n );\\n }\\n }\\n\\n /**\\n * @dev Returns the admin role that controls `role`. See {grantRole} and\\n * {revokeRole}.\\n *\\n * To change a role's admin, use {_setRoleAdmin}.\\n */\\n function getRoleAdmin(bytes32 role) public view override returns (bytes32) {\\n return _roles[role].adminRole;\\n }\\n\\n /**\\n * @dev Grants `role` to `account`.\\n *\\n * If `account` had not been already granted `role`, emits a {RoleGranted}\\n * event.\\n *\\n * Requirements:\\n *\\n * - the caller must have ``role``'s admin role.\\n */\\n function grantRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {\\n _grantRole(role, account);\\n }\\n\\n /**\\n * @dev Revokes `role` from `account`.\\n *\\n * If `account` had been granted `role`, emits a {RoleRevoked} event.\\n *\\n * Requirements:\\n *\\n * - the caller must have ``role``'s admin role.\\n */\\n function revokeRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {\\n _revokeRole(role, account);\\n }\\n\\n /**\\n * @dev Revokes `role` from the calling account.\\n *\\n * Roles are often managed via {grantRole} and {revokeRole}: this function's\\n * purpose is to provide a mechanism for accounts to lose their privileges\\n * if they are compromised (such as when a trusted device is misplaced).\\n *\\n * If the calling account had been revoked `role`, emits a {RoleRevoked}\\n * event.\\n *\\n * Requirements:\\n *\\n * - the caller must be `account`.\\n */\\n function renounceRole(bytes32 role, address account) public virtual override {\\n require(account == _msgSender(), \\\"AccessControl: can only renounce roles for self\\\");\\n\\n _revokeRole(role, account);\\n }\\n\\n /**\\n * @dev Grants `role` to `account`.\\n *\\n * If `account` had not been already granted `role`, emits a {RoleGranted}\\n * event. Note that unlike {grantRole}, this function doesn't perform any\\n * checks on the calling account.\\n *\\n * [WARNING]\\n * ====\\n * This function should only be called from the constructor when setting\\n * up the initial roles for the system.\\n *\\n * Using this function in any other way is effectively circumventing the admin\\n * system imposed by {AccessControl}.\\n * ====\\n *\\n * NOTE: This function is deprecated in favor of {_grantRole}.\\n */\\n function _setupRole(bytes32 role, address account) internal virtual {\\n _grantRole(role, account);\\n }\\n\\n /**\\n * @dev Sets `adminRole` as ``role``'s admin role.\\n *\\n * Emits a {RoleAdminChanged} event.\\n */\\n function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {\\n bytes32 previousAdminRole = getRoleAdmin(role);\\n _roles[role].adminRole = adminRole;\\n emit RoleAdminChanged(role, previousAdminRole, adminRole);\\n }\\n\\n /**\\n * @dev Grants `role` to `account`.\\n *\\n * Internal function without access restriction.\\n */\\n function _grantRole(bytes32 role, address account) internal virtual {\\n if (!hasRole(role, account)) {\\n _roles[role].members[account] = true;\\n emit RoleGranted(role, account, _msgSender());\\n }\\n }\\n\\n /**\\n * @dev Revokes `role` from `account`.\\n *\\n * Internal function without access restriction.\\n */\\n function _revokeRole(bytes32 role, address account) internal virtual {\\n if (hasRole(role, account)) {\\n _roles[role].members[account] = false;\\n emit RoleRevoked(role, account, _msgSender());\\n }\\n }\\n}\\n\",\"keccak256\":\"0xb9a137b317dc4806805f2259686186c0c053c32d80fe9c15ecdbf2eb1cf52849\",\"license\":\"MIT\"},\"@openzeppelin/contracts/access/AccessControlEnumerable.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (access/AccessControlEnumerable.sol)\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"./IAccessControlEnumerable.sol\\\";\\nimport \\\"./AccessControl.sol\\\";\\nimport \\\"../utils/structs/EnumerableSet.sol\\\";\\n\\n/**\\n * @dev Extension of {AccessControl} that allows enumerating the members of each role.\\n */\\nabstract contract AccessControlEnumerable is IAccessControlEnumerable, AccessControl {\\n using EnumerableSet for EnumerableSet.AddressSet;\\n\\n mapping(bytes32 => EnumerableSet.AddressSet) private _roleMembers;\\n\\n /**\\n * @dev See {IERC165-supportsInterface}.\\n */\\n function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {\\n return interfaceId == type(IAccessControlEnumerable).interfaceId || super.supportsInterface(interfaceId);\\n }\\n\\n /**\\n * @dev Returns one of the accounts that have `role`. `index` must be a\\n * value between 0 and {getRoleMemberCount}, non-inclusive.\\n *\\n * Role bearers are not sorted in any particular way, and their ordering may\\n * change at any point.\\n *\\n * WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure\\n * you perform all queries on the same block. See the following\\n * https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post]\\n * for more information.\\n */\\n function getRoleMember(bytes32 role, uint256 index) public view override returns (address) {\\n return _roleMembers[role].at(index);\\n }\\n\\n /**\\n * @dev Returns the number of accounts that have `role`. Can be used\\n * together with {getRoleMember} to enumerate all bearers of a role.\\n */\\n function getRoleMemberCount(bytes32 role) public view override returns (uint256) {\\n return _roleMembers[role].length();\\n }\\n\\n /**\\n * @dev Overload {_grantRole} to track enumerable memberships\\n */\\n function _grantRole(bytes32 role, address account) internal virtual override {\\n super._grantRole(role, account);\\n _roleMembers[role].add(account);\\n }\\n\\n /**\\n * @dev Overload {_revokeRole} to track enumerable memberships\\n */\\n function _revokeRole(bytes32 role, address account) internal virtual override {\\n super._revokeRole(role, account);\\n _roleMembers[role].remove(account);\\n }\\n}\\n\",\"keccak256\":\"0x1304796e9cdc64294735b4222849a240363b2aff374bb58b7c728f8dc0f4aa75\",\"license\":\"MIT\"},\"@openzeppelin/contracts/access/IAccessControl.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (access/IAccessControl.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev External interface of AccessControl declared to support ERC165 detection.\\n */\\ninterface IAccessControl {\\n /**\\n * @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`\\n *\\n * `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite\\n * {RoleAdminChanged} not being emitted signaling this.\\n *\\n * _Available since v3.1._\\n */\\n event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);\\n\\n /**\\n * @dev Emitted when `account` is granted `role`.\\n *\\n * `sender` is the account that originated the contract call, an admin role\\n * bearer except when using {AccessControl-_setupRole}.\\n */\\n event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);\\n\\n /**\\n * @dev Emitted when `account` is revoked `role`.\\n *\\n * `sender` is the account that originated the contract call:\\n * - if using `revokeRole`, it is the admin role bearer\\n * - if using `renounceRole`, it is the role bearer (i.e. `account`)\\n */\\n event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);\\n\\n /**\\n * @dev Returns `true` if `account` has been granted `role`.\\n */\\n function hasRole(bytes32 role, address account) external view returns (bool);\\n\\n /**\\n * @dev Returns the admin role that controls `role`. See {grantRole} and\\n * {revokeRole}.\\n *\\n * To change a role's admin, use {AccessControl-_setRoleAdmin}.\\n */\\n function getRoleAdmin(bytes32 role) external view returns (bytes32);\\n\\n /**\\n * @dev Grants `role` to `account`.\\n *\\n * If `account` had not been already granted `role`, emits a {RoleGranted}\\n * event.\\n *\\n * Requirements:\\n *\\n * - the caller must have ``role``'s admin role.\\n */\\n function grantRole(bytes32 role, address account) external;\\n\\n /**\\n * @dev Revokes `role` from `account`.\\n *\\n * If `account` had been granted `role`, emits a {RoleRevoked} event.\\n *\\n * Requirements:\\n *\\n * - the caller must have ``role``'s admin role.\\n */\\n function revokeRole(bytes32 role, address account) external;\\n\\n /**\\n * @dev Revokes `role` from the calling account.\\n *\\n * Roles are often managed via {grantRole} and {revokeRole}: this function's\\n * purpose is to provide a mechanism for accounts to lose their privileges\\n * if they are compromised (such as when a trusted device is misplaced).\\n *\\n * If the calling account had been granted `role`, emits a {RoleRevoked}\\n * event.\\n *\\n * Requirements:\\n *\\n * - the caller must be `account`.\\n */\\n function renounceRole(bytes32 role, address account) external;\\n}\\n\",\"keccak256\":\"0x59ce320a585d7e1f163cd70390a0ef2ff9cec832e2aa544293a00692465a7a57\",\"license\":\"MIT\"},\"@openzeppelin/contracts/access/IAccessControlEnumerable.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (access/IAccessControlEnumerable.sol)\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"./IAccessControl.sol\\\";\\n\\n/**\\n * @dev External interface of AccessControlEnumerable declared to support ERC165 detection.\\n */\\ninterface IAccessControlEnumerable is IAccessControl {\\n /**\\n * @dev Returns one of the accounts that have `role`. `index` must be a\\n * value between 0 and {getRoleMemberCount}, non-inclusive.\\n *\\n * Role bearers are not sorted in any particular way, and their ordering may\\n * change at any point.\\n *\\n * WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure\\n * you perform all queries on the same block. See the following\\n * https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post]\\n * for more information.\\n */\\n function getRoleMember(bytes32 role, uint256 index) external view returns (address);\\n\\n /**\\n * @dev Returns the number of accounts that have `role`. Can be used\\n * together with {getRoleMember} to enumerate all bearers of a role.\\n */\\n function getRoleMemberCount(bytes32 role) external view returns (uint256);\\n}\\n\",\"keccak256\":\"0xba4459ab871dfa300f5212c6c30178b63898c03533a1ede28436f11546626676\",\"license\":\"MIT\"},\"@openzeppelin/contracts/token/ERC20/IERC20.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (token/ERC20/IERC20.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev Interface of the ERC20 standard as defined in the EIP.\\n */\\ninterface IERC20 {\\n /**\\n * @dev Returns the amount of tokens in existence.\\n */\\n function totalSupply() external view returns (uint256);\\n\\n /**\\n * @dev Returns the amount of tokens owned by `account`.\\n */\\n function balanceOf(address account) external view returns (uint256);\\n\\n /**\\n * @dev Moves `amount` tokens from the caller's account to `recipient`.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * Emits a {Transfer} event.\\n */\\n function transfer(address recipient, uint256 amount) external returns (bool);\\n\\n /**\\n * @dev Returns the remaining number of tokens that `spender` will be\\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\\n * zero by default.\\n *\\n * This value changes when {approve} or {transferFrom} are called.\\n */\\n function allowance(address owner, address spender) external view returns (uint256);\\n\\n /**\\n * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\\n * that someone may use both the old and the new allowance by unfortunate\\n * transaction ordering. One possible solution to mitigate this race\\n * condition is to first reduce the spender's allowance to 0 and set the\\n * desired value afterwards:\\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\\n *\\n * Emits an {Approval} event.\\n */\\n function approve(address spender, uint256 amount) external returns (bool);\\n\\n /**\\n * @dev Moves `amount` tokens from `sender` to `recipient` using the\\n * allowance mechanism. `amount` is then deducted from the caller's\\n * allowance.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * Emits a {Transfer} event.\\n */\\n function transferFrom(\\n address sender,\\n address recipient,\\n uint256 amount\\n ) external returns (bool);\\n\\n /**\\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\\n * another (`to`).\\n *\\n * Note that `value` may be zero.\\n */\\n event Transfer(address indexed from, address indexed to, uint256 value);\\n\\n /**\\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\\n * a call to {approve}. `value` is the new allowance.\\n */\\n event Approval(address indexed owner, address indexed spender, uint256 value);\\n}\\n\",\"keccak256\":\"0x61437cb513a887a1bbad006e7b1c8b414478427d33de47c5600af3c748f108da\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/Context.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev Provides information about the current execution context, including the\\n * sender of the transaction and its data. While these are generally available\\n * via msg.sender and msg.data, they should not be accessed in such a direct\\n * manner, since when dealing with meta-transactions the account sending and\\n * paying for execution may not be the actual sender (as far as an application\\n * is concerned).\\n *\\n * This contract is only required for intermediate, library-like contracts.\\n */\\nabstract contract Context {\\n function _msgSender() internal view virtual returns (address) {\\n return msg.sender;\\n }\\n\\n function _msgData() internal view virtual returns (bytes calldata) {\\n return msg.data;\\n }\\n}\\n\",\"keccak256\":\"0xe2e337e6dde9ef6b680e07338c493ebea1b5fd09b43424112868e9cc1706bca7\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/Strings.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (utils/Strings.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev String operations.\\n */\\nlibrary Strings {\\n bytes16 private constant _HEX_SYMBOLS = \\\"0123456789abcdef\\\";\\n\\n /**\\n * @dev Converts a `uint256` to its ASCII `string` decimal representation.\\n */\\n function toString(uint256 value) internal pure returns (string memory) {\\n // Inspired by OraclizeAPI's implementation - MIT licence\\n // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol\\n\\n if (value == 0) {\\n return \\\"0\\\";\\n }\\n uint256 temp = value;\\n uint256 digits;\\n while (temp != 0) {\\n digits++;\\n temp /= 10;\\n }\\n bytes memory buffer = new bytes(digits);\\n while (value != 0) {\\n digits -= 1;\\n buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));\\n value /= 10;\\n }\\n return string(buffer);\\n }\\n\\n /**\\n * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.\\n */\\n function toHexString(uint256 value) internal pure returns (string memory) {\\n if (value == 0) {\\n return \\\"0x00\\\";\\n }\\n uint256 temp = value;\\n uint256 length = 0;\\n while (temp != 0) {\\n length++;\\n temp >>= 8;\\n }\\n return toHexString(value, length);\\n }\\n\\n /**\\n * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.\\n */\\n function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {\\n bytes memory buffer = new bytes(2 * length + 2);\\n buffer[0] = \\\"0\\\";\\n buffer[1] = \\\"x\\\";\\n for (uint256 i = 2 * length + 1; i > 1; --i) {\\n buffer[i] = _HEX_SYMBOLS[value & 0xf];\\n value >>= 4;\\n }\\n require(value == 0, \\\"Strings: hex length insufficient\\\");\\n return string(buffer);\\n }\\n}\\n\",\"keccak256\":\"0x32c202bd28995dd20c4347b7c6467a6d3241c74c8ad3edcbb610cd9205916c45\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/introspection/ERC165.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol)\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"./IERC165.sol\\\";\\n\\n/**\\n * @dev Implementation of the {IERC165} interface.\\n *\\n * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check\\n * for the additional interface id that will be supported. For example:\\n *\\n * ```solidity\\n * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {\\n * return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);\\n * }\\n * ```\\n *\\n * Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation.\\n */\\nabstract contract ERC165 is IERC165 {\\n /**\\n * @dev See {IERC165-supportsInterface}.\\n */\\n function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {\\n return interfaceId == type(IERC165).interfaceId;\\n }\\n}\\n\",\"keccak256\":\"0xd10975de010d89fd1c78dc5e8a9a7e7f496198085c151648f20cba166b32582b\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/introspection/IERC165.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev Interface of the ERC165 standard, as defined in the\\n * https://eips.ethereum.org/EIPS/eip-165[EIP].\\n *\\n * Implementers can declare support of contract interfaces, which can then be\\n * queried by others ({ERC165Checker}).\\n *\\n * For an implementation, see {ERC165}.\\n */\\ninterface IERC165 {\\n /**\\n * @dev Returns true if this contract implements the interface defined by\\n * `interfaceId`. See the corresponding\\n * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]\\n * to learn more about how these ids are created.\\n *\\n * This function call must use less than 30 000 gas.\\n */\\n function supportsInterface(bytes4 interfaceId) external view returns (bool);\\n}\\n\",\"keccak256\":\"0x447a5f3ddc18419d41ff92b3773fb86471b1db25773e07f877f548918a185bf1\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/structs/EnumerableSet.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (utils/structs/EnumerableSet.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev Library for managing\\n * https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive\\n * types.\\n *\\n * Sets have the following properties:\\n *\\n * - Elements are added, removed, and checked for existence in constant time\\n * (O(1)).\\n * - Elements are enumerated in O(n). No guarantees are made on the ordering.\\n *\\n * ```\\n * contract Example {\\n * // Add the library methods\\n * using EnumerableSet for EnumerableSet.AddressSet;\\n *\\n * // Declare a set state variable\\n * EnumerableSet.AddressSet private mySet;\\n * }\\n * ```\\n *\\n * As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)\\n * and `uint256` (`UintSet`) are supported.\\n */\\nlibrary EnumerableSet {\\n // To implement this library for multiple types with as little code\\n // repetition as possible, we write it in terms of a generic Set type with\\n // bytes32 values.\\n // The Set implementation uses private functions, and user-facing\\n // implementations (such as AddressSet) are just wrappers around the\\n // underlying Set.\\n // This means that we can only create new EnumerableSets for types that fit\\n // in bytes32.\\n\\n struct Set {\\n // Storage of set values\\n bytes32[] _values;\\n // Position of the value in the `values` array, plus 1 because index 0\\n // means a value is not in the set.\\n mapping(bytes32 => uint256) _indexes;\\n }\\n\\n /**\\n * @dev Add a value to a set. O(1).\\n *\\n * Returns true if the value was added to the set, that is if it was not\\n * already present.\\n */\\n function _add(Set storage set, bytes32 value) private returns (bool) {\\n if (!_contains(set, value)) {\\n set._values.push(value);\\n // The value is stored at length-1, but we add 1 to all indexes\\n // and use 0 as a sentinel value\\n set._indexes[value] = set._values.length;\\n return true;\\n } else {\\n return false;\\n }\\n }\\n\\n /**\\n * @dev Removes a value from a set. O(1).\\n *\\n * Returns true if the value was removed from the set, that is if it was\\n * present.\\n */\\n function _remove(Set storage set, bytes32 value) private returns (bool) {\\n // We read and store the value's index to prevent multiple reads from the same storage slot\\n uint256 valueIndex = set._indexes[value];\\n\\n if (valueIndex != 0) {\\n // Equivalent to contains(set, value)\\n // To delete an element from the _values array in O(1), we swap the element to delete with the last one in\\n // the array, and then remove the last element (sometimes called as 'swap and pop').\\n // This modifies the order of the array, as noted in {at}.\\n\\n uint256 toDeleteIndex = valueIndex - 1;\\n uint256 lastIndex = set._values.length - 1;\\n\\n if (lastIndex != toDeleteIndex) {\\n bytes32 lastvalue = set._values[lastIndex];\\n\\n // Move the last value to the index where the value to delete is\\n set._values[toDeleteIndex] = lastvalue;\\n // Update the index for the moved value\\n set._indexes[lastvalue] = valueIndex; // Replace lastvalue's index to valueIndex\\n }\\n\\n // Delete the slot where the moved value was stored\\n set._values.pop();\\n\\n // Delete the index for the deleted slot\\n delete set._indexes[value];\\n\\n return true;\\n } else {\\n return false;\\n }\\n }\\n\\n /**\\n * @dev Returns true if the value is in the set. O(1).\\n */\\n function _contains(Set storage set, bytes32 value) private view returns (bool) {\\n return set._indexes[value] != 0;\\n }\\n\\n /**\\n * @dev Returns the number of values on the set. O(1).\\n */\\n function _length(Set storage set) private view returns (uint256) {\\n return set._values.length;\\n }\\n\\n /**\\n * @dev Returns the value stored at position `index` in the set. O(1).\\n *\\n * Note that there are no guarantees on the ordering of values inside the\\n * array, and it may change when more values are added or removed.\\n *\\n * Requirements:\\n *\\n * - `index` must be strictly less than {length}.\\n */\\n function _at(Set storage set, uint256 index) private view returns (bytes32) {\\n return set._values[index];\\n }\\n\\n /**\\n * @dev Return the entire set in an array\\n *\\n * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed\\n * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that\\n * this function has an unbounded cost, and using it as part of a state-changing function may render the function\\n * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.\\n */\\n function _values(Set storage set) private view returns (bytes32[] memory) {\\n return set._values;\\n }\\n\\n // Bytes32Set\\n\\n struct Bytes32Set {\\n Set _inner;\\n }\\n\\n /**\\n * @dev Add a value to a set. O(1).\\n *\\n * Returns true if the value was added to the set, that is if it was not\\n * already present.\\n */\\n function add(Bytes32Set storage set, bytes32 value) internal returns (bool) {\\n return _add(set._inner, value);\\n }\\n\\n /**\\n * @dev Removes a value from a set. O(1).\\n *\\n * Returns true if the value was removed from the set, that is if it was\\n * present.\\n */\\n function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) {\\n return _remove(set._inner, value);\\n }\\n\\n /**\\n * @dev Returns true if the value is in the set. O(1).\\n */\\n function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {\\n return _contains(set._inner, value);\\n }\\n\\n /**\\n * @dev Returns the number of values in the set. O(1).\\n */\\n function length(Bytes32Set storage set) internal view returns (uint256) {\\n return _length(set._inner);\\n }\\n\\n /**\\n * @dev Returns the value stored at position `index` in the set. O(1).\\n *\\n * Note that there are no guarantees on the ordering of values inside the\\n * array, and it may change when more values are added or removed.\\n *\\n * Requirements:\\n *\\n * - `index` must be strictly less than {length}.\\n */\\n function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {\\n return _at(set._inner, index);\\n }\\n\\n /**\\n * @dev Return the entire set in an array\\n *\\n * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed\\n * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that\\n * this function has an unbounded cost, and using it as part of a state-changing function may render the function\\n * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.\\n */\\n function values(Bytes32Set storage set) internal view returns (bytes32[] memory) {\\n return _values(set._inner);\\n }\\n\\n // AddressSet\\n\\n struct AddressSet {\\n Set _inner;\\n }\\n\\n /**\\n * @dev Add a value to a set. O(1).\\n *\\n * Returns true if the value was added to the set, that is if it was not\\n * already present.\\n */\\n function add(AddressSet storage set, address value) internal returns (bool) {\\n return _add(set._inner, bytes32(uint256(uint160(value))));\\n }\\n\\n /**\\n * @dev Removes a value from a set. O(1).\\n *\\n * Returns true if the value was removed from the set, that is if it was\\n * present.\\n */\\n function remove(AddressSet storage set, address value) internal returns (bool) {\\n return _remove(set._inner, bytes32(uint256(uint160(value))));\\n }\\n\\n /**\\n * @dev Returns true if the value is in the set. O(1).\\n */\\n function contains(AddressSet storage set, address value) internal view returns (bool) {\\n return _contains(set._inner, bytes32(uint256(uint160(value))));\\n }\\n\\n /**\\n * @dev Returns the number of values in the set. O(1).\\n */\\n function length(AddressSet storage set) internal view returns (uint256) {\\n return _length(set._inner);\\n }\\n\\n /**\\n * @dev Returns the value stored at position `index` in the set. O(1).\\n *\\n * Note that there are no guarantees on the ordering of values inside the\\n * array, and it may change when more values are added or removed.\\n *\\n * Requirements:\\n *\\n * - `index` must be strictly less than {length}.\\n */\\n function at(AddressSet storage set, uint256 index) internal view returns (address) {\\n return address(uint160(uint256(_at(set._inner, index))));\\n }\\n\\n /**\\n * @dev Return the entire set in an array\\n *\\n * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed\\n * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that\\n * this function has an unbounded cost, and using it as part of a state-changing function may render the function\\n * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.\\n */\\n function values(AddressSet storage set) internal view returns (address[] memory) {\\n bytes32[] memory store = _values(set._inner);\\n address[] memory result;\\n\\n assembly {\\n result := store\\n }\\n\\n return result;\\n }\\n\\n // UintSet\\n\\n struct UintSet {\\n Set _inner;\\n }\\n\\n /**\\n * @dev Add a value to a set. O(1).\\n *\\n * Returns true if the value was added to the set, that is if it was not\\n * already present.\\n */\\n function add(UintSet storage set, uint256 value) internal returns (bool) {\\n return _add(set._inner, bytes32(value));\\n }\\n\\n /**\\n * @dev Removes a value from a set. O(1).\\n *\\n * Returns true if the value was removed from the set, that is if it was\\n * present.\\n */\\n function remove(UintSet storage set, uint256 value) internal returns (bool) {\\n return _remove(set._inner, bytes32(value));\\n }\\n\\n /**\\n * @dev Returns true if the value is in the set. O(1).\\n */\\n function contains(UintSet storage set, uint256 value) internal view returns (bool) {\\n return _contains(set._inner, bytes32(value));\\n }\\n\\n /**\\n * @dev Returns the number of values on the set. O(1).\\n */\\n function length(UintSet storage set) internal view returns (uint256) {\\n return _length(set._inner);\\n }\\n\\n /**\\n * @dev Returns the value stored at position `index` in the set. O(1).\\n *\\n * Note that there are no guarantees on the ordering of values inside the\\n * array, and it may change when more values are added or removed.\\n *\\n * Requirements:\\n *\\n * - `index` must be strictly less than {length}.\\n */\\n function at(UintSet storage set, uint256 index) internal view returns (uint256) {\\n return uint256(_at(set._inner, index));\\n }\\n\\n /**\\n * @dev Return the entire set in an array\\n *\\n * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed\\n * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that\\n * this function has an unbounded cost, and using it as part of a state-changing function may render the function\\n * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.\\n */\\n function values(UintSet storage set) internal view returns (uint256[] memory) {\\n bytes32[] memory store = _values(set._inner);\\n uint256[] memory result;\\n\\n assembly {\\n result := store\\n }\\n\\n return result;\\n }\\n}\\n\",\"keccak256\":\"0x9772845c886f87a3aab315f8d6b68aa599027c20f441b131cd4afaf65b588900\",\"license\":\"MIT\"},\"contracts/automation/AbstractSafeModule.sol\":{\"content\":\"// SPDX-License-Identifier: BUSL-1.1\\npragma solidity ^0.8.0;\\n\\nimport { AccessControlEnumerable } from \\\"@openzeppelin/contracts/access/AccessControlEnumerable.sol\\\";\\nimport { IERC20 } from \\\"@openzeppelin/contracts/token/ERC20/IERC20.sol\\\";\\nimport { ISafe } from \\\"../interfaces/ISafe.sol\\\";\\n\\nabstract contract AbstractSafeModule is AccessControlEnumerable {\\n ISafe public immutable safeContract;\\n\\n bytes32 public constant OPERATOR_ROLE = keccak256(\\\"OPERATOR_ROLE\\\");\\n\\n modifier onlySafe() {\\n require(\\n msg.sender == address(safeContract),\\n \\\"Caller is not the safe contract\\\"\\n );\\n _;\\n }\\n\\n modifier onlyOperator() {\\n require(\\n hasRole(OPERATOR_ROLE, msg.sender),\\n \\\"Caller is not an operator\\\"\\n );\\n _;\\n }\\n\\n constructor(address _safeContract) {\\n safeContract = ISafe(_safeContract);\\n _grantRole(DEFAULT_ADMIN_ROLE, address(safeContract));\\n _grantRole(OPERATOR_ROLE, address(safeContract));\\n }\\n\\n /**\\n * @dev Helps recovering any tokens accidentally sent to this module.\\n * @param token Token to transfer. 0x0 to transfer Native token.\\n * @param amount Amount to transfer. 0 to transfer all balance.\\n */\\n function transferTokens(address token, uint256 amount) external onlySafe {\\n if (address(token) == address(0)) {\\n // Move ETH\\n amount = amount > 0 ? amount : address(this).balance;\\n payable(address(safeContract)).transfer(amount);\\n return;\\n }\\n\\n // Move all balance if amount set to 0\\n amount = amount > 0 ? amount : IERC20(token).balanceOf(address(this));\\n\\n // Transfer to Safe contract\\n // slither-disable-next-line unchecked-transfer unused-return\\n IERC20(token).transfer(address(safeContract), amount);\\n }\\n\\n receive() external payable {\\n // Accept ETH to pay for bridge fees\\n }\\n}\\n\",\"keccak256\":\"0x02f5cebee3ef21afb1e5dafe15a9160017dde1b7361653e6285f884124d15af5\",\"license\":\"BUSL-1.1\"},\"contracts/automation/CurvePoolBoosterBribesModule.sol\":{\"content\":\"// SPDX-License-Identifier: BUSL-1.1\\npragma solidity ^0.8.0;\\n\\nimport { AbstractSafeModule } from \\\"./AbstractSafeModule.sol\\\";\\n\\ninterface ICurvePoolBooster {\\n function manageTotalRewardAmount(\\n uint256 bridgeFee,\\n uint256 additionalGasLimit\\n ) external;\\n\\n function manageNumberOfPeriods(\\n uint8 extraNumberOfPeriods,\\n uint256 bridgeFee,\\n uint256 additionalGasLimit\\n ) external;\\n\\n function manageRewardPerVote(\\n uint256 newMaxRewardPerVote,\\n uint256 bridgeFee,\\n uint256 additionalGasLimit\\n ) external;\\n}\\n\\ncontract CurvePoolBoosterBribesModule is AbstractSafeModule {\\n address[] public POOLS;\\n\\n event PoolBoosterAddressAdded(address pool);\\n event PoolBoosterAddressRemoved(address pool);\\n\\n constructor(\\n address _safeContract,\\n address _operator,\\n address[] memory _pools\\n ) AbstractSafeModule(_safeContract) {\\n _grantRole(OPERATOR_ROLE, _operator);\\n _addPoolBoosterAddress(_pools);\\n }\\n\\n function addPoolBoosterAddress(address[] memory pools)\\n external\\n onlyOperator\\n {\\n _addPoolBoosterAddress(pools);\\n }\\n\\n function _addPoolBoosterAddress(address[] memory pools) internal {\\n for (uint256 i = 0; i < pools.length; i++) {\\n POOLS.push(pools[i]);\\n emit PoolBoosterAddressAdded(pools[i]);\\n }\\n }\\n\\n function removePoolBoosterAddress(address[] calldata pools)\\n external\\n onlyOperator\\n {\\n for (uint256 i = 0; i < pools.length; i++) {\\n _removePoolBoosterAddress(pools[i]);\\n }\\n }\\n\\n function _removePoolBoosterAddress(address pool) internal {\\n uint256 length = POOLS.length;\\n for (uint256 i = 0; i < length; i++) {\\n if (POOLS[i] == pool) {\\n POOLS[i] = POOLS[length - 1];\\n POOLS.pop();\\n emit PoolBoosterAddressRemoved(pool);\\n break;\\n }\\n }\\n }\\n\\n function manageBribes() external onlyOperator {\\n uint256[] memory rewardsPerVote = new uint256[](POOLS.length);\\n _manageBribes(rewardsPerVote);\\n }\\n\\n function manageBribes(uint256[] memory rewardsPerVote)\\n external\\n onlyOperator\\n {\\n require(POOLS.length == rewardsPerVote.length, \\\"Length mismatch\\\");\\n _manageBribes(rewardsPerVote);\\n }\\n\\n function _manageBribes(uint256[] memory rewardsPerVote)\\n internal\\n onlyOperator\\n {\\n uint256 length = POOLS.length;\\n for (uint256 i = 0; i < length; i++) {\\n address poolBoosterAddress = POOLS[i];\\n\\n // PoolBooster need to have a balance of at least 0.003 ether to operate\\n // 0.001 ether are used for the bridge fee\\n require(\\n poolBoosterAddress.balance > 0.003 ether,\\n \\\"Insufficient balance for bribes\\\"\\n );\\n\\n require(\\n safeContract.execTransactionFromModule(\\n poolBoosterAddress,\\n 0, // Value\\n abi.encodeWithSelector(\\n ICurvePoolBooster.manageNumberOfPeriods.selector,\\n 1, // extraNumberOfPeriods\\n 0.001 ether, // bridgeFee\\n 1000000 // additionalGasLimit\\n ),\\n 0\\n ),\\n \\\"Manage number of periods failed\\\"\\n );\\n\\n require(\\n safeContract.execTransactionFromModule(\\n poolBoosterAddress,\\n 0, // Value\\n abi.encodeWithSelector(\\n ICurvePoolBooster.manageTotalRewardAmount.selector,\\n 0.001 ether, // bridgeFee\\n 1000000 // additionalGasLimit\\n ),\\n 0\\n ),\\n \\\"Manage total reward failed\\\"\\n );\\n\\n // Skip setting reward per vote if it's zero\\n if (rewardsPerVote[i] == 0) continue;\\n require(\\n safeContract.execTransactionFromModule(\\n poolBoosterAddress,\\n 0, // Value\\n abi.encodeWithSelector(\\n ICurvePoolBooster.manageRewardPerVote.selector,\\n rewardsPerVote[i], // newMaxRewardPerVote\\n 0.001 ether, // bridgeFee\\n 1000000 // additionalGasLimit\\n ),\\n 0\\n ),\\n \\\"Set reward per vote failed\\\"\\n );\\n }\\n }\\n\\n function getPools() external view returns (address[] memory) {\\n return POOLS;\\n }\\n}\\n\",\"keccak256\":\"0x5b1b3c480b738953bf9a012ad8fbbcf468bd7163fb923dbf8ccf2b980edf9f06\",\"license\":\"BUSL-1.1\"},\"contracts/interfaces/ISafe.sol\":{\"content\":\"// SPDX-License-Identifier: BUSL-1.1\\npragma solidity ^0.8.0;\\n\\ninterface ISafe {\\n function execTransactionFromModule(\\n address,\\n uint256,\\n bytes memory,\\n uint8\\n ) external returns (bool);\\n}\\n\",\"keccak256\":\"0x6d5fb3512c4fab418222023fb1b482891906eae8d2bda9d1eb2ef3d3c7653dee\",\"license\":\"BUSL-1.1\"}},\"version\":1}", - "bytecode": "0x60a060405234801561001057600080fd5b50604051611ef2380380611ef283398101604081905261002f916102c3565b6001600160a01b0383166080819052839061004c90600090610096565b61006c600080516020611ed283398151915260805161009660201b60201c565b50610085600080516020611ed283398151915283610096565b61008e816100bd565b5050506103cc565b6100a08282610186565b60008281526001602052604090206100b89082610224565b505050565b60005b81518110156101825760028282815181106100dd576100dd6103b6565b60209081029190910181015182546001810184556000938452919092200180546001600160a01b0319166001600160a01b0390921691909117905581517f73fbf341ebd14259e9d6b52784447ca2c15aa93c480c93927fd8135aefff77479083908390811061014e5761014e6103b6565b602002602001015160405161017291906001600160a01b0391909116815260200190565b60405180910390a16001016100c0565b5050565b6000828152602081815260408083206001600160a01b038516845290915290205460ff16610182576000828152602081815260408083206001600160a01b03851684529091529020805460ff191660011790556101e03390565b6001600160a01b0316816001600160a01b0316837f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a45050565b6000610239836001600160a01b038416610242565b90505b92915050565b60008181526001830160205260408120546102895750815460018181018455600084815260208082209093018490558454848252828601909352604090209190915561023c565b50600061023c565b80516001600160a01b03811681146102a857600080fd5b919050565b634e487b7160e01b600052604160045260246000fd5b6000806000606084860312156102d857600080fd5b6102e184610291565b92506102ef60208501610291565b60408501519092506001600160401b0381111561030b57600080fd5b8401601f8101861361031c57600080fd5b80516001600160401b03811115610335576103356102ad565b604051600582901b90603f8201601f191681016001600160401b0381118282101715610363576103636102ad565b60405291825260208184018101929081018984111561038157600080fd5b6020850194505b838510156103a75761039985610291565b815260209485019401610388565b50809450505050509250925092565b634e487b7160e01b600052603260045260246000fd5b608051611ac161041160003960008181610386015281816106e5015281816107800152818161085701528181610aa901528181610bd20152610ccd0152611ac16000f3fe60806040526004361061010d5760003560e01c8063a217fddf11610095578063ca15c87311610064578063ca15c873146102f2578063d547741f14610312578063eec8e96714610332578063f5b541a614610352578063f9081ba21461037457600080fd5b8063a217fddf1461027d578063a8423c0814610292578063ab384df0146102b2578063bec3fa17146102d257600080fd5b80632f2ff15d116100dc5780632f2ff15d146101c357806336568abe146101e3578063673a2a1f146102035780639010d07c1461022557806391d148541461025d57600080fd5b806301ffc9a7146101195780630b6bad741461014e57806316d5fd0e14610170578063248a9ca31461018557600080fd5b3661011457005b600080fd5b34801561012557600080fd5b50610139610134366004611502565b6103a8565b60405190151581526020015b60405180910390f35b34801561015a57600080fd5b5061016e610169366004611597565b6103d3565b005b34801561017c57600080fd5b5061016e610460565b34801561019157600080fd5b506101b56101a0366004611632565b60009081526020819052604090206001015490565b604051908152602001610145565b3480156101cf57600080fd5b5061016e6101de366004611667565b6104e7565b3480156101ef57600080fd5b5061016e6101fe366004611667565b610512565b34801561020f57600080fd5b50610218610590565b6040516101459190611693565b34801561023157600080fd5b506102456102403660046116df565b6105f2565b6040516001600160a01b039091168152602001610145565b34801561026957600080fd5b50610139610278366004611667565b610611565b34801561028957600080fd5b506101b5600081565b34801561029e57600080fd5b506102456102ad366004611632565b61063a565b3480156102be57600080fd5b5061016e6102cd366004611701565b610664565b3480156102de57600080fd5b5061016e6102ed366004611778565b6106da565b3480156102fe57600080fd5b506101b561030d366004611632565b6108d7565b34801561031e57600080fd5b5061016e61032d366004611667565b6108ee565b34801561033e57600080fd5b5061016e61034d3660046117a2565b610914565b34801561035e57600080fd5b506101b5600080516020611a6c83398151915281565b34801561038057600080fd5b506102457f000000000000000000000000000000000000000000000000000000000000000081565b60006001600160e01b03198216635a05180f60e01b14806103cd57506103cd82610951565b92915050565b6103eb600080516020611a6c83398151915233610611565b6104105760405162461bcd60e51b815260040161040790611835565b60405180910390fd5b8051600254146104545760405162461bcd60e51b815260206004820152600f60248201526e098cadccee8d040dad2e6dac2e8c6d608b1b6044820152606401610407565b61045d81610986565b50565b610478600080516020611a6c83398151915233610611565b6104945760405162461bcd60e51b815260040161040790611835565b60025460009067ffffffffffffffff8111156104b2576104b261152c565b6040519080825280602002602001820160405280156104db578160200160208202803683370190505b50905061045d81610986565b6000828152602081905260409020600101546105038133610e29565b61050d8383610e8d565b505050565b6001600160a01b03811633146105825760405162461bcd60e51b815260206004820152602f60248201527f416363657373436f6e74726f6c3a2063616e206f6e6c792072656e6f756e636560448201526e103937b632b9903337b91039b2b63360891b6064820152608401610407565b61058c8282610eaf565b5050565b606060028054806020026020016040519081016040528092919081815260200182805480156105e857602002820191906000526020600020905b81546001600160a01b031681526001909101906020018083116105ca575b5050505050905090565b600082815260016020526040812061060a9083610ed1565b9392505050565b6000918252602082815260408084206001600160a01b0393909316845291905290205460ff1690565b6002818154811061064a57600080fd5b6000918252602090912001546001600160a01b0316905081565b61067c600080516020611a6c83398151915233610611565b6106985760405162461bcd60e51b815260040161040790611835565b60005b8181101561050d576106d28383838181106106b8576106b861186c565b90506020020160208101906106cd9190611882565b610edd565b60010161069b565b336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146107525760405162461bcd60e51b815260206004820152601f60248201527f43616c6c6572206973206e6f7420746865207361666520636f6e7472616374006044820152606401610407565b6001600160a01b0382166107c9576000811161076e5747610770565b805b6040519091506001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169082156108fc029083906000818181858888f1935050505015801561050d573d6000803e3d6000fd5b6000811161083e576040516370a0823160e01b81523060048201526001600160a01b038316906370a0823190602401602060405180830381865afa158015610815573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610839919061189d565b610840565b805b60405163a9059cbb60e01b81526001600160a01b037f000000000000000000000000000000000000000000000000000000000000000081166004830152602482018390529192509083169063a9059cbb906044016020604051808303816000875af11580156108b3573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061050d91906118b6565b60008181526001602052604081206103cd90611018565b60008281526020819052604090206001015461090a8133610e29565b61050d8383610eaf565b61092c600080516020611a6c83398151915233610611565b6109485760405162461bcd60e51b815260040161040790611835565b61045d81611022565b60006001600160e01b03198216637965db0b60e01b14806103cd57506301ffc9a760e01b6001600160e01b03198316146103cd565b61099e600080516020611a6c83398151915233610611565b6109ba5760405162461bcd60e51b815260040161040790611835565b60025460005b8181101561050d576000600282815481106109dd576109dd61186c565b6000918252602090912001546001600160a01b03169050660aa87bee538000813111610a4b5760405162461bcd60e51b815260206004820152601f60248201527f496e73756666696369656e742062616c616e636520666f7220627269626573006044820152606401610407565b604080516001602482015266038d7ea4c680006044820152620f424060648083019190915282518083039091018152608490910182526020810180516001600160e01b031663e9a0214360e01b179052905163468721a760e01b81527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169163468721a791610aec918591600091908290600401611928565b6020604051808303816000875af1158015610b0b573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b2f91906118b6565b610b7b5760405162461bcd60e51b815260206004820152601f60248201527f4d616e616765206e756d626572206f6620706572696f6473206661696c6564006044820152606401610407565b6040805166038d7ea4c680006024820152620f424060448083019190915282518083039091018152606490910182526020810180516001600160e01b031663381cf00160e11b179052905163468721a760e01b81527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169163468721a791610c15918591600091908290600401611928565b6020604051808303816000875af1158015610c34573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610c5891906118b6565b610ca45760405162461bcd60e51b815260206004820152601a60248201527f4d616e61676520746f74616c20726577617264206661696c65640000000000006044820152606401610407565b838281518110610cb657610cb661186c565b6020026020010151600003610ccb5750610e21565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663468721a7826000630aaef85460e01b888781518110610d1757610d1761186c565b6020908102919091010151604051602481019190915266038d7ea4c680006044820152620f4240606482015260840160408051601f198184030181529181526020820180516001600160e01b03166001600160e01b03199485161790525160e086901b9092168252610d90939291600090600401611928565b6020604051808303816000875af1158015610daf573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610dd391906118b6565b610e1f5760405162461bcd60e51b815260206004820152601a60248201527f536574207265776172642070657220766f7465206661696c65640000000000006044820152606401610407565b505b6001016109c0565b610e338282610611565b61058c57610e4b816001600160a01b031660146110e7565b610e568360206110e7565b604051602001610e67929190611963565b60408051601f198184030181529082905262461bcd60e51b8252610407916004016119d8565b610e978282611283565b600082815260016020526040902061050d9082611307565b610eb9828261131c565b600082815260016020526040902061050d9082611381565b600061060a8383611396565b60025460005b8181101561050d57826001600160a01b031660028281548110610f0857610f0861186c565b6000918252602090912001546001600160a01b031603611010576002610f2f600184611a01565b81548110610f3f57610f3f61186c565b600091825260209091200154600280546001600160a01b039092169183908110610f6b57610f6b61186c565b9060005260206000200160006101000a8154816001600160a01b0302191690836001600160a01b031602179055506002805480610faa57610faa611a14565b6000828152602090819020600019908301810180546001600160a01b03191690559091019091556040516001600160a01b03851681527f5acf3d4990bcb8de36e8b743ef49e57f65600af9d09b4a50da91c2061b8aca79910160405180910390a1505050565b600101610ee3565b60006103cd825490565b60005b815181101561058c5760028282815181106110425761104261186c565b60209081029190910181015182546001810184556000938452919092200180546001600160a01b0319166001600160a01b0390921691909117905581517f73fbf341ebd14259e9d6b52784447ca2c15aa93c480c93927fd8135aefff7747908390839081106110b3576110b361186c565b60200260200101516040516110d791906001600160a01b0391909116815260200190565b60405180910390a1600101611025565b606060006110f6836002611a2a565b611101906002611a41565b67ffffffffffffffff8111156111195761111961152c565b6040519080825280601f01601f191660200182016040528015611143576020820181803683370190505b509050600360fc1b8160008151811061115e5761115e61186c565b60200101906001600160f81b031916908160001a905350600f60fb1b8160018151811061118d5761118d61186c565b60200101906001600160f81b031916908160001a90535060006111b1846002611a2a565b6111bc906001611a41565b90505b6001811115611234576f181899199a1a9b1b9c1cb0b131b232b360811b85600f16601081106111f0576111f061186c565b1a60f81b8282815181106112065761120661186c565b60200101906001600160f81b031916908160001a90535060049490941c9361122d81611a54565b90506111bf565b50831561060a5760405162461bcd60e51b815260206004820181905260248201527f537472696e67733a20686578206c656e67746820696e73756666696369656e746044820152606401610407565b61128d8282610611565b61058c576000828152602081815260408083206001600160a01b03851684529091529020805460ff191660011790556112c33390565b6001600160a01b0316816001600160a01b0316837f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a45050565b600061060a836001600160a01b0384166113c0565b6113268282610611565b1561058c576000828152602081815260408083206001600160a01b0385168085529252808320805460ff1916905551339285917ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b9190a45050565b600061060a836001600160a01b03841661140f565b60008260000182815481106113ad576113ad61186c565b9060005260206000200154905092915050565b6000818152600183016020526040812054611407575081546001818101845560008481526020808220909301849055845484825282860190935260409020919091556103cd565b5060006103cd565b600081815260018301602052604081205480156114f8576000611433600183611a01565b855490915060009061144790600190611a01565b90508181146114ac5760008660000182815481106114675761146761186c565b906000526020600020015490508087600001848154811061148a5761148a61186c565b6000918252602080832090910192909255918252600188019052604090208390555b85548690806114bd576114bd611a14565b6001900381819060005260206000200160009055905585600101600086815260200190815260200160002060009055600193505050506103cd565b60009150506103cd565b60006020828403121561151457600080fd5b81356001600160e01b03198116811461060a57600080fd5b634e487b7160e01b600052604160045260246000fd5b604051601f8201601f1916810167ffffffffffffffff8111828210171561156b5761156b61152c565b604052919050565b600067ffffffffffffffff82111561158d5761158d61152c565b5060051b60200190565b6000602082840312156115a957600080fd5b813567ffffffffffffffff8111156115c057600080fd5b8201601f810184136115d157600080fd5b80356115e46115df82611573565b611542565b8082825260208201915060208360051b85010192508683111561160657600080fd5b6020840193505b8284101561162857833582526020938401939091019061160d565b9695505050505050565b60006020828403121561164457600080fd5b5035919050565b80356001600160a01b038116811461166257600080fd5b919050565b6000806040838503121561167a57600080fd5b8235915061168a6020840161164b565b90509250929050565b602080825282518282018190526000918401906040840190835b818110156116d45783516001600160a01b03168352602093840193909201916001016116ad565b509095945050505050565b600080604083850312156116f257600080fd5b50508035926020909101359150565b6000806020838503121561171457600080fd5b823567ffffffffffffffff81111561172b57600080fd5b8301601f8101851361173c57600080fd5b803567ffffffffffffffff81111561175357600080fd5b8560208260051b840101111561176857600080fd5b6020919091019590945092505050565b6000806040838503121561178b57600080fd5b6117948361164b565b946020939093013593505050565b6000602082840312156117b457600080fd5b813567ffffffffffffffff8111156117cb57600080fd5b8201601f810184136117dc57600080fd5b80356117ea6115df82611573565b8082825260208201915060208360051b85010192508683111561180c57600080fd5b6020840193505b82841015611628576118248461164b565b825260209384019390910190611813565b60208082526019908201527f43616c6c6572206973206e6f7420616e206f70657261746f7200000000000000604082015260600190565b634e487b7160e01b600052603260045260246000fd5b60006020828403121561189457600080fd5b61060a8261164b565b6000602082840312156118af57600080fd5b5051919050565b6000602082840312156118c857600080fd5b8151801515811461060a57600080fd5b60005b838110156118f35781810151838201526020016118db565b50506000910152565b600081518084526119148160208601602086016118d8565b601f01601f19169290920160200192915050565b60018060a01b038516815283602082015260806040820152600061194f60808301856118fc565b905060ff8316606083015295945050505050565b7f416363657373436f6e74726f6c3a206163636f756e742000000000000000000081526000835161199b8160178501602088016118d8565b7001034b99036b4b9b9b4b733903937b6329607d1b60179184019182015283516119cc8160288401602088016118d8565b01602801949350505050565b60208152600061060a60208301846118fc565b634e487b7160e01b600052601160045260246000fd5b818103818111156103cd576103cd6119eb565b634e487b7160e01b600052603160045260246000fd5b80820281158282048414176103cd576103cd6119eb565b808201808211156103cd576103cd6119eb565b600081611a6357611a636119eb565b50600019019056fe97667070c54ef182b0f5858b034beac1b6f3089aa2d3188bb1e8929f4fa9b929a264697066735822122067eec552b6b04e37cdcca087924ebb7d255e8f65fd27d6c6f41ad2e42a145d5064736f6c634300081c003397667070c54ef182b0f5858b034beac1b6f3089aa2d3188bb1e8929f4fa9b929", - "deployedBytecode": "0x60806040526004361061010d5760003560e01c8063a217fddf11610095578063ca15c87311610064578063ca15c873146102f2578063d547741f14610312578063eec8e96714610332578063f5b541a614610352578063f9081ba21461037457600080fd5b8063a217fddf1461027d578063a8423c0814610292578063ab384df0146102b2578063bec3fa17146102d257600080fd5b80632f2ff15d116100dc5780632f2ff15d146101c357806336568abe146101e3578063673a2a1f146102035780639010d07c1461022557806391d148541461025d57600080fd5b806301ffc9a7146101195780630b6bad741461014e57806316d5fd0e14610170578063248a9ca31461018557600080fd5b3661011457005b600080fd5b34801561012557600080fd5b50610139610134366004611502565b6103a8565b60405190151581526020015b60405180910390f35b34801561015a57600080fd5b5061016e610169366004611597565b6103d3565b005b34801561017c57600080fd5b5061016e610460565b34801561019157600080fd5b506101b56101a0366004611632565b60009081526020819052604090206001015490565b604051908152602001610145565b3480156101cf57600080fd5b5061016e6101de366004611667565b6104e7565b3480156101ef57600080fd5b5061016e6101fe366004611667565b610512565b34801561020f57600080fd5b50610218610590565b6040516101459190611693565b34801561023157600080fd5b506102456102403660046116df565b6105f2565b6040516001600160a01b039091168152602001610145565b34801561026957600080fd5b50610139610278366004611667565b610611565b34801561028957600080fd5b506101b5600081565b34801561029e57600080fd5b506102456102ad366004611632565b61063a565b3480156102be57600080fd5b5061016e6102cd366004611701565b610664565b3480156102de57600080fd5b5061016e6102ed366004611778565b6106da565b3480156102fe57600080fd5b506101b561030d366004611632565b6108d7565b34801561031e57600080fd5b5061016e61032d366004611667565b6108ee565b34801561033e57600080fd5b5061016e61034d3660046117a2565b610914565b34801561035e57600080fd5b506101b5600080516020611a6c83398151915281565b34801561038057600080fd5b506102457f000000000000000000000000000000000000000000000000000000000000000081565b60006001600160e01b03198216635a05180f60e01b14806103cd57506103cd82610951565b92915050565b6103eb600080516020611a6c83398151915233610611565b6104105760405162461bcd60e51b815260040161040790611835565b60405180910390fd5b8051600254146104545760405162461bcd60e51b815260206004820152600f60248201526e098cadccee8d040dad2e6dac2e8c6d608b1b6044820152606401610407565b61045d81610986565b50565b610478600080516020611a6c83398151915233610611565b6104945760405162461bcd60e51b815260040161040790611835565b60025460009067ffffffffffffffff8111156104b2576104b261152c565b6040519080825280602002602001820160405280156104db578160200160208202803683370190505b50905061045d81610986565b6000828152602081905260409020600101546105038133610e29565b61050d8383610e8d565b505050565b6001600160a01b03811633146105825760405162461bcd60e51b815260206004820152602f60248201527f416363657373436f6e74726f6c3a2063616e206f6e6c792072656e6f756e636560448201526e103937b632b9903337b91039b2b63360891b6064820152608401610407565b61058c8282610eaf565b5050565b606060028054806020026020016040519081016040528092919081815260200182805480156105e857602002820191906000526020600020905b81546001600160a01b031681526001909101906020018083116105ca575b5050505050905090565b600082815260016020526040812061060a9083610ed1565b9392505050565b6000918252602082815260408084206001600160a01b0393909316845291905290205460ff1690565b6002818154811061064a57600080fd5b6000918252602090912001546001600160a01b0316905081565b61067c600080516020611a6c83398151915233610611565b6106985760405162461bcd60e51b815260040161040790611835565b60005b8181101561050d576106d28383838181106106b8576106b861186c565b90506020020160208101906106cd9190611882565b610edd565b60010161069b565b336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146107525760405162461bcd60e51b815260206004820152601f60248201527f43616c6c6572206973206e6f7420746865207361666520636f6e7472616374006044820152606401610407565b6001600160a01b0382166107c9576000811161076e5747610770565b805b6040519091506001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169082156108fc029083906000818181858888f1935050505015801561050d573d6000803e3d6000fd5b6000811161083e576040516370a0823160e01b81523060048201526001600160a01b038316906370a0823190602401602060405180830381865afa158015610815573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610839919061189d565b610840565b805b60405163a9059cbb60e01b81526001600160a01b037f000000000000000000000000000000000000000000000000000000000000000081166004830152602482018390529192509083169063a9059cbb906044016020604051808303816000875af11580156108b3573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061050d91906118b6565b60008181526001602052604081206103cd90611018565b60008281526020819052604090206001015461090a8133610e29565b61050d8383610eaf565b61092c600080516020611a6c83398151915233610611565b6109485760405162461bcd60e51b815260040161040790611835565b61045d81611022565b60006001600160e01b03198216637965db0b60e01b14806103cd57506301ffc9a760e01b6001600160e01b03198316146103cd565b61099e600080516020611a6c83398151915233610611565b6109ba5760405162461bcd60e51b815260040161040790611835565b60025460005b8181101561050d576000600282815481106109dd576109dd61186c565b6000918252602090912001546001600160a01b03169050660aa87bee538000813111610a4b5760405162461bcd60e51b815260206004820152601f60248201527f496e73756666696369656e742062616c616e636520666f7220627269626573006044820152606401610407565b604080516001602482015266038d7ea4c680006044820152620f424060648083019190915282518083039091018152608490910182526020810180516001600160e01b031663e9a0214360e01b179052905163468721a760e01b81527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169163468721a791610aec918591600091908290600401611928565b6020604051808303816000875af1158015610b0b573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b2f91906118b6565b610b7b5760405162461bcd60e51b815260206004820152601f60248201527f4d616e616765206e756d626572206f6620706572696f6473206661696c6564006044820152606401610407565b6040805166038d7ea4c680006024820152620f424060448083019190915282518083039091018152606490910182526020810180516001600160e01b031663381cf00160e11b179052905163468721a760e01b81527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169163468721a791610c15918591600091908290600401611928565b6020604051808303816000875af1158015610c34573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610c5891906118b6565b610ca45760405162461bcd60e51b815260206004820152601a60248201527f4d616e61676520746f74616c20726577617264206661696c65640000000000006044820152606401610407565b838281518110610cb657610cb661186c565b6020026020010151600003610ccb5750610e21565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663468721a7826000630aaef85460e01b888781518110610d1757610d1761186c565b6020908102919091010151604051602481019190915266038d7ea4c680006044820152620f4240606482015260840160408051601f198184030181529181526020820180516001600160e01b03166001600160e01b03199485161790525160e086901b9092168252610d90939291600090600401611928565b6020604051808303816000875af1158015610daf573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610dd391906118b6565b610e1f5760405162461bcd60e51b815260206004820152601a60248201527f536574207265776172642070657220766f7465206661696c65640000000000006044820152606401610407565b505b6001016109c0565b610e338282610611565b61058c57610e4b816001600160a01b031660146110e7565b610e568360206110e7565b604051602001610e67929190611963565b60408051601f198184030181529082905262461bcd60e51b8252610407916004016119d8565b610e978282611283565b600082815260016020526040902061050d9082611307565b610eb9828261131c565b600082815260016020526040902061050d9082611381565b600061060a8383611396565b60025460005b8181101561050d57826001600160a01b031660028281548110610f0857610f0861186c565b6000918252602090912001546001600160a01b031603611010576002610f2f600184611a01565b81548110610f3f57610f3f61186c565b600091825260209091200154600280546001600160a01b039092169183908110610f6b57610f6b61186c565b9060005260206000200160006101000a8154816001600160a01b0302191690836001600160a01b031602179055506002805480610faa57610faa611a14565b6000828152602090819020600019908301810180546001600160a01b03191690559091019091556040516001600160a01b03851681527f5acf3d4990bcb8de36e8b743ef49e57f65600af9d09b4a50da91c2061b8aca79910160405180910390a1505050565b600101610ee3565b60006103cd825490565b60005b815181101561058c5760028282815181106110425761104261186c565b60209081029190910181015182546001810184556000938452919092200180546001600160a01b0319166001600160a01b0390921691909117905581517f73fbf341ebd14259e9d6b52784447ca2c15aa93c480c93927fd8135aefff7747908390839081106110b3576110b361186c565b60200260200101516040516110d791906001600160a01b0391909116815260200190565b60405180910390a1600101611025565b606060006110f6836002611a2a565b611101906002611a41565b67ffffffffffffffff8111156111195761111961152c565b6040519080825280601f01601f191660200182016040528015611143576020820181803683370190505b509050600360fc1b8160008151811061115e5761115e61186c565b60200101906001600160f81b031916908160001a905350600f60fb1b8160018151811061118d5761118d61186c565b60200101906001600160f81b031916908160001a90535060006111b1846002611a2a565b6111bc906001611a41565b90505b6001811115611234576f181899199a1a9b1b9c1cb0b131b232b360811b85600f16601081106111f0576111f061186c565b1a60f81b8282815181106112065761120661186c565b60200101906001600160f81b031916908160001a90535060049490941c9361122d81611a54565b90506111bf565b50831561060a5760405162461bcd60e51b815260206004820181905260248201527f537472696e67733a20686578206c656e67746820696e73756666696369656e746044820152606401610407565b61128d8282610611565b61058c576000828152602081815260408083206001600160a01b03851684529091529020805460ff191660011790556112c33390565b6001600160a01b0316816001600160a01b0316837f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a45050565b600061060a836001600160a01b0384166113c0565b6113268282610611565b1561058c576000828152602081815260408083206001600160a01b0385168085529252808320805460ff1916905551339285917ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b9190a45050565b600061060a836001600160a01b03841661140f565b60008260000182815481106113ad576113ad61186c565b9060005260206000200154905092915050565b6000818152600183016020526040812054611407575081546001818101845560008481526020808220909301849055845484825282860190935260409020919091556103cd565b5060006103cd565b600081815260018301602052604081205480156114f8576000611433600183611a01565b855490915060009061144790600190611a01565b90508181146114ac5760008660000182815481106114675761146761186c565b906000526020600020015490508087600001848154811061148a5761148a61186c565b6000918252602080832090910192909255918252600188019052604090208390555b85548690806114bd576114bd611a14565b6001900381819060005260206000200160009055905585600101600086815260200190815260200160002060009055600193505050506103cd565b60009150506103cd565b60006020828403121561151457600080fd5b81356001600160e01b03198116811461060a57600080fd5b634e487b7160e01b600052604160045260246000fd5b604051601f8201601f1916810167ffffffffffffffff8111828210171561156b5761156b61152c565b604052919050565b600067ffffffffffffffff82111561158d5761158d61152c565b5060051b60200190565b6000602082840312156115a957600080fd5b813567ffffffffffffffff8111156115c057600080fd5b8201601f810184136115d157600080fd5b80356115e46115df82611573565b611542565b8082825260208201915060208360051b85010192508683111561160657600080fd5b6020840193505b8284101561162857833582526020938401939091019061160d565b9695505050505050565b60006020828403121561164457600080fd5b5035919050565b80356001600160a01b038116811461166257600080fd5b919050565b6000806040838503121561167a57600080fd5b8235915061168a6020840161164b565b90509250929050565b602080825282518282018190526000918401906040840190835b818110156116d45783516001600160a01b03168352602093840193909201916001016116ad565b509095945050505050565b600080604083850312156116f257600080fd5b50508035926020909101359150565b6000806020838503121561171457600080fd5b823567ffffffffffffffff81111561172b57600080fd5b8301601f8101851361173c57600080fd5b803567ffffffffffffffff81111561175357600080fd5b8560208260051b840101111561176857600080fd5b6020919091019590945092505050565b6000806040838503121561178b57600080fd5b6117948361164b565b946020939093013593505050565b6000602082840312156117b457600080fd5b813567ffffffffffffffff8111156117cb57600080fd5b8201601f810184136117dc57600080fd5b80356117ea6115df82611573565b8082825260208201915060208360051b85010192508683111561180c57600080fd5b6020840193505b82841015611628576118248461164b565b825260209384019390910190611813565b60208082526019908201527f43616c6c6572206973206e6f7420616e206f70657261746f7200000000000000604082015260600190565b634e487b7160e01b600052603260045260246000fd5b60006020828403121561189457600080fd5b61060a8261164b565b6000602082840312156118af57600080fd5b5051919050565b6000602082840312156118c857600080fd5b8151801515811461060a57600080fd5b60005b838110156118f35781810151838201526020016118db565b50506000910152565b600081518084526119148160208601602086016118d8565b601f01601f19169290920160200192915050565b60018060a01b038516815283602082015260806040820152600061194f60808301856118fc565b905060ff8316606083015295945050505050565b7f416363657373436f6e74726f6c3a206163636f756e742000000000000000000081526000835161199b8160178501602088016118d8565b7001034b99036b4b9b9b4b733903937b6329607d1b60179184019182015283516119cc8160288401602088016118d8565b01602801949350505050565b60208152600061060a60208301846118fc565b634e487b7160e01b600052601160045260246000fd5b818103818111156103cd576103cd6119eb565b634e487b7160e01b600052603160045260246000fd5b80820281158282048414176103cd576103cd6119eb565b808201808211156103cd576103cd6119eb565b600081611a6357611a636119eb565b50600019019056fe97667070c54ef182b0f5858b034beac1b6f3089aa2d3188bb1e8929f4fa9b929a264697066735822122067eec552b6b04e37cdcca087924ebb7d255e8f65fd27d6c6f41ad2e42a145d5064736f6c634300081c0033", + "numDeployments": 3, + "solcInputHash": "ccf1518a549ca89ba906f9e99681e2d8", + "metadata": "{\"compiler\":{\"version\":\"0.8.28+commit.7893614a\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_safeContract\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_operator\",\"type\":\"address\"},{\"internalType\":\"address[]\",\"name\":\"_poolBoosters\",\"type\":\"address[]\"},{\"internalType\":\"uint256\",\"name\":\"_bridgeFee\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_additionalGasLimit\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"newGasLimit\",\"type\":\"uint256\"}],\"name\":\"AdditionalGasLimitUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"newFee\",\"type\":\"uint256\"}],\"name\":\"BridgeFeeUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"pool\",\"type\":\"address\"}],\"name\":\"PoolBoosterAddressAdded\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"pool\",\"type\":\"address\"}],\"name\":\"PoolBoosterAddressRemoved\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"previousAdminRole\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"newAdminRole\",\"type\":\"bytes32\"}],\"name\":\"RoleAdminChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"}],\"name\":\"RoleGranted\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"}],\"name\":\"RoleRevoked\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"DEFAULT_ADMIN_ROLE\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"OPERATOR_ROLE\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"_poolBoosters\",\"type\":\"address[]\"}],\"name\":\"addPoolBoosterAddress\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"additionalGasLimit\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"bridgeFee\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getPoolBoosters\",\"outputs\":[{\"internalType\":\"address[]\",\"name\":\"\",\"type\":\"address[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"}],\"name\":\"getRoleAdmin\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"index\",\"type\":\"uint256\"}],\"name\":\"getRoleMember\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"}],\"name\":\"getRoleMemberCount\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"grantRole\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"hasRole\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"manageBribes\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256[]\",\"name\":\"totalRewardAmounts\",\"type\":\"uint256[]\"},{\"internalType\":\"uint8[]\",\"name\":\"extraDuration\",\"type\":\"uint8[]\"},{\"internalType\":\"uint256[]\",\"name\":\"rewardsPerVote\",\"type\":\"uint256[]\"}],\"name\":\"manageBribes\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"poolBoosters\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"_poolBoosters\",\"type\":\"address[]\"}],\"name\":\"removePoolBoosterAddress\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"renounceRole\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"revokeRole\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"safeContract\",\"outputs\":[{\"internalType\":\"contract ISafe\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"newGasLimit\",\"type\":\"uint256\"}],\"name\":\"setAdditionalGasLimit\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"newFee\",\"type\":\"uint256\"}],\"name\":\"setBridgeFee\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes4\",\"name\":\"interfaceId\",\"type\":\"bytes4\"}],\"name\":\"supportsInterface\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"transferTokens\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"stateMutability\":\"payable\",\"type\":\"receive\"}],\"devdoc\":{\"author\":\"Origin Protocol\",\"events\":{\"RoleAdminChanged(bytes32,bytes32,bytes32)\":{\"details\":\"Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole` `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite {RoleAdminChanged} not being emitted signaling this. _Available since v3.1._\"},\"RoleGranted(bytes32,address,address)\":{\"details\":\"Emitted when `account` is granted `role`. `sender` is the account that originated the contract call, an admin role bearer except when using {AccessControl-_setupRole}.\"},\"RoleRevoked(bytes32,address,address)\":{\"details\":\"Emitted when `account` is revoked `role`. `sender` is the account that originated the contract call: - if using `revokeRole`, it is the admin role bearer - if using `renounceRole`, it is the role bearer (i.e. `account`)\"}},\"kind\":\"dev\",\"methods\":{\"addPoolBoosterAddress(address[])\":{\"params\":{\"_poolBoosters\":\"Addresses to add\"}},\"constructor\":{\"params\":{\"_additionalGasLimit\":\"Gas limit for cross-chain execution in manageCampaign\",\"_bridgeFee\":\"ETH amount to send per pool booster for bridge fees\",\"_operator\":\"Address authorized to call operator-restricted functions\",\"_poolBoosters\":\"Initial list of CurvePoolBooster addresses to manage\",\"_safeContract\":\"Address of the Gnosis Safe this module is attached to\"}},\"getPoolBoosters()\":{\"returns\":{\"_0\":\"Array of pool booster addresses\"}},\"getRoleAdmin(bytes32)\":{\"details\":\"Returns the admin role that controls `role`. See {grantRole} and {revokeRole}. To change a role's admin, use {_setRoleAdmin}.\"},\"getRoleMember(bytes32,uint256)\":{\"details\":\"Returns one of the accounts that have `role`. `index` must be a value between 0 and {getRoleMemberCount}, non-inclusive. Role bearers are not sorted in any particular way, and their ordering may change at any point. WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure you perform all queries on the same block. See the following https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post] for more information.\"},\"getRoleMemberCount(bytes32)\":{\"details\":\"Returns the number of accounts that have `role`. Can be used together with {getRoleMember} to enumerate all bearers of a role.\"},\"grantRole(bytes32,address)\":{\"details\":\"Grants `role` to `account`. If `account` had not been already granted `role`, emits a {RoleGranted} event. Requirements: - the caller must have ``role``'s admin role.\"},\"hasRole(bytes32,address)\":{\"details\":\"Returns `true` if `account` has been granted `role`.\"},\"manageBribes()\":{\"details\":\"Calls `manageCampaign` on each pool booster via the Safe. The Safe must hold enough ETH to cover `bridgeFee * poolBoosters.length`.\"},\"manageBribes(uint256[],uint8[],uint256[])\":{\"params\":{\"extraDuration\":\"Number of periods to extend per pool (0 = no update, 1 = +1 week)\",\"rewardsPerVote\":\"Max reward per vote per pool (0 = no update)\",\"totalRewardAmounts\":\"Total reward amount per pool (0 = no update, type(uint256).max = use all available)\"}},\"removePoolBoosterAddress(address[])\":{\"params\":{\"_poolBoosters\":\"Addresses to remove\"}},\"renounceRole(bytes32,address)\":{\"details\":\"Revokes `role` from the calling account. Roles are often managed via {grantRole} and {revokeRole}: this function's purpose is to provide a mechanism for accounts to lose their privileges if they are compromised (such as when a trusted device is misplaced). If the calling account had been revoked `role`, emits a {RoleRevoked} event. Requirements: - the caller must be `account`.\"},\"revokeRole(bytes32,address)\":{\"details\":\"Revokes `role` from `account`. If `account` had been granted `role`, emits a {RoleRevoked} event. Requirements: - the caller must have ``role``'s admin role.\"},\"setAdditionalGasLimit(uint256)\":{\"params\":{\"newGasLimit\":\"New gas limit value\"}},\"setBridgeFee(uint256)\":{\"params\":{\"newFee\":\"New bridge fee amount in wei\"}},\"supportsInterface(bytes4)\":{\"details\":\"See {IERC165-supportsInterface}.\"},\"transferTokens(address,uint256)\":{\"details\":\"Helps recovering any tokens accidentally sent to this module.\",\"params\":{\"amount\":\"Amount to transfer. 0 to transfer all balance.\",\"token\":\"Token to transfer. 0x0 to transfer Native token.\"}}},\"title\":\"CurvePoolBoosterBribesModule\",\"version\":1},\"userdoc\":{\"events\":{\"BridgeFeeUpdated(uint256)\":{\"notice\":\"--- Events\"}},\"kind\":\"user\",\"methods\":{\"addPoolBoosterAddress(address[])\":{\"notice\":\"Add new CurvePoolBooster addresses to the managed list\"},\"additionalGasLimit()\":{\"notice\":\"Gas limit passed to manageCampaign for cross-chain execution\"},\"bridgeFee()\":{\"notice\":\"ETH amount sent per pool booster to cover the L1 -> L2 bridge fee\"},\"getPoolBoosters()\":{\"notice\":\"Get the full list of managed CurvePoolBooster addresses\"},\"manageBribes()\":{\"notice\":\"Default entry point to manage bribe campaigns for all registered pool boosters. Applies the same behavior to every pool: - totalRewardAmount = type(uint256).max \\u2192 use all available reward tokens - numberOfPeriods = 1 \\u2192 extend by one period (week) - maxRewardPerVote = 0 \\u2192 no update\"},\"manageBribes(uint256[],uint8[],uint256[])\":{\"notice\":\"Fully configurable entry point to manage bribe campaigns. Allows setting reward amounts, durations, and reward rates individually for each pool. Each array must have the same length as the poolBoosters array.\"},\"poolBoosters(uint256)\":{\"notice\":\"List of CurvePoolBooster addresses managed by this module\"},\"removePoolBoosterAddress(address[])\":{\"notice\":\"Remove CurvePoolBooster addresses from the managed list\"},\"setAdditionalGasLimit(uint256)\":{\"notice\":\"Update the additional gas limit for cross-chain execution\"},\"setBridgeFee(uint256)\":{\"notice\":\"Update the ETH bridge fee sent per pool booster\"}},\"notice\":\"Gnosis Safe module that automates the management of VotemarketV2 bribe campaigns across multiple CurvePoolBooster contracts. It instructs the Safe to call `manageCampaign` on each registered pool booster, forwarding ETH from the Safe's balance to cover bridge fees. Campaign parameters (reward amount, duration, reward rate) can be configured per pool or left to sensible defaults.\",\"version\":1}},\"settings\":{\"compilationTarget\":{\"contracts/automation/CurvePoolBoosterBribesModule.sol\":\"CurvePoolBoosterBribesModule\"},\"evmVersion\":\"paris\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\",\"useLiteralContent\":true},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[]},\"sources\":{\"@openzeppelin/contracts/access/AccessControl.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (access/AccessControl.sol)\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"./IAccessControl.sol\\\";\\nimport \\\"../utils/Context.sol\\\";\\nimport \\\"../utils/Strings.sol\\\";\\nimport \\\"../utils/introspection/ERC165.sol\\\";\\n\\n/**\\n * @dev Contract module that allows children to implement role-based access\\n * control mechanisms. This is a lightweight version that doesn't allow enumerating role\\n * members except through off-chain means by accessing the contract event logs. Some\\n * applications may benefit from on-chain enumerability, for those cases see\\n * {AccessControlEnumerable}.\\n *\\n * Roles are referred to by their `bytes32` identifier. These should be exposed\\n * in the external API and be unique. The best way to achieve this is by\\n * using `public constant` hash digests:\\n *\\n * ```\\n * bytes32 public constant MY_ROLE = keccak256(\\\"MY_ROLE\\\");\\n * ```\\n *\\n * Roles can be used to represent a set of permissions. To restrict access to a\\n * function call, use {hasRole}:\\n *\\n * ```\\n * function foo() public {\\n * require(hasRole(MY_ROLE, msg.sender));\\n * ...\\n * }\\n * ```\\n *\\n * Roles can be granted and revoked dynamically via the {grantRole} and\\n * {revokeRole} functions. Each role has an associated admin role, and only\\n * accounts that have a role's admin role can call {grantRole} and {revokeRole}.\\n *\\n * By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means\\n * that only accounts with this role will be able to grant or revoke other\\n * roles. More complex role relationships can be created by using\\n * {_setRoleAdmin}.\\n *\\n * WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to\\n * grant and revoke this role. Extra precautions should be taken to secure\\n * accounts that have been granted it.\\n */\\nabstract contract AccessControl is Context, IAccessControl, ERC165 {\\n struct RoleData {\\n mapping(address => bool) members;\\n bytes32 adminRole;\\n }\\n\\n mapping(bytes32 => RoleData) private _roles;\\n\\n bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;\\n\\n /**\\n * @dev Modifier that checks that an account has a specific role. Reverts\\n * with a standardized message including the required role.\\n *\\n * The format of the revert reason is given by the following regular expression:\\n *\\n * /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/\\n *\\n * _Available since v4.1._\\n */\\n modifier onlyRole(bytes32 role) {\\n _checkRole(role, _msgSender());\\n _;\\n }\\n\\n /**\\n * @dev See {IERC165-supportsInterface}.\\n */\\n function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {\\n return interfaceId == type(IAccessControl).interfaceId || super.supportsInterface(interfaceId);\\n }\\n\\n /**\\n * @dev Returns `true` if `account` has been granted `role`.\\n */\\n function hasRole(bytes32 role, address account) public view override returns (bool) {\\n return _roles[role].members[account];\\n }\\n\\n /**\\n * @dev Revert with a standard message if `account` is missing `role`.\\n *\\n * The format of the revert reason is given by the following regular expression:\\n *\\n * /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/\\n */\\n function _checkRole(bytes32 role, address account) internal view {\\n if (!hasRole(role, account)) {\\n revert(\\n string(\\n abi.encodePacked(\\n \\\"AccessControl: account \\\",\\n Strings.toHexString(uint160(account), 20),\\n \\\" is missing role \\\",\\n Strings.toHexString(uint256(role), 32)\\n )\\n )\\n );\\n }\\n }\\n\\n /**\\n * @dev Returns the admin role that controls `role`. See {grantRole} and\\n * {revokeRole}.\\n *\\n * To change a role's admin, use {_setRoleAdmin}.\\n */\\n function getRoleAdmin(bytes32 role) public view override returns (bytes32) {\\n return _roles[role].adminRole;\\n }\\n\\n /**\\n * @dev Grants `role` to `account`.\\n *\\n * If `account` had not been already granted `role`, emits a {RoleGranted}\\n * event.\\n *\\n * Requirements:\\n *\\n * - the caller must have ``role``'s admin role.\\n */\\n function grantRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {\\n _grantRole(role, account);\\n }\\n\\n /**\\n * @dev Revokes `role` from `account`.\\n *\\n * If `account` had been granted `role`, emits a {RoleRevoked} event.\\n *\\n * Requirements:\\n *\\n * - the caller must have ``role``'s admin role.\\n */\\n function revokeRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {\\n _revokeRole(role, account);\\n }\\n\\n /**\\n * @dev Revokes `role` from the calling account.\\n *\\n * Roles are often managed via {grantRole} and {revokeRole}: this function's\\n * purpose is to provide a mechanism for accounts to lose their privileges\\n * if they are compromised (such as when a trusted device is misplaced).\\n *\\n * If the calling account had been revoked `role`, emits a {RoleRevoked}\\n * event.\\n *\\n * Requirements:\\n *\\n * - the caller must be `account`.\\n */\\n function renounceRole(bytes32 role, address account) public virtual override {\\n require(account == _msgSender(), \\\"AccessControl: can only renounce roles for self\\\");\\n\\n _revokeRole(role, account);\\n }\\n\\n /**\\n * @dev Grants `role` to `account`.\\n *\\n * If `account` had not been already granted `role`, emits a {RoleGranted}\\n * event. Note that unlike {grantRole}, this function doesn't perform any\\n * checks on the calling account.\\n *\\n * [WARNING]\\n * ====\\n * This function should only be called from the constructor when setting\\n * up the initial roles for the system.\\n *\\n * Using this function in any other way is effectively circumventing the admin\\n * system imposed by {AccessControl}.\\n * ====\\n *\\n * NOTE: This function is deprecated in favor of {_grantRole}.\\n */\\n function _setupRole(bytes32 role, address account) internal virtual {\\n _grantRole(role, account);\\n }\\n\\n /**\\n * @dev Sets `adminRole` as ``role``'s admin role.\\n *\\n * Emits a {RoleAdminChanged} event.\\n */\\n function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {\\n bytes32 previousAdminRole = getRoleAdmin(role);\\n _roles[role].adminRole = adminRole;\\n emit RoleAdminChanged(role, previousAdminRole, adminRole);\\n }\\n\\n /**\\n * @dev Grants `role` to `account`.\\n *\\n * Internal function without access restriction.\\n */\\n function _grantRole(bytes32 role, address account) internal virtual {\\n if (!hasRole(role, account)) {\\n _roles[role].members[account] = true;\\n emit RoleGranted(role, account, _msgSender());\\n }\\n }\\n\\n /**\\n * @dev Revokes `role` from `account`.\\n *\\n * Internal function without access restriction.\\n */\\n function _revokeRole(bytes32 role, address account) internal virtual {\\n if (hasRole(role, account)) {\\n _roles[role].members[account] = false;\\n emit RoleRevoked(role, account, _msgSender());\\n }\\n }\\n}\\n\",\"keccak256\":\"0xb9a137b317dc4806805f2259686186c0c053c32d80fe9c15ecdbf2eb1cf52849\",\"license\":\"MIT\"},\"@openzeppelin/contracts/access/AccessControlEnumerable.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (access/AccessControlEnumerable.sol)\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"./IAccessControlEnumerable.sol\\\";\\nimport \\\"./AccessControl.sol\\\";\\nimport \\\"../utils/structs/EnumerableSet.sol\\\";\\n\\n/**\\n * @dev Extension of {AccessControl} that allows enumerating the members of each role.\\n */\\nabstract contract AccessControlEnumerable is IAccessControlEnumerable, AccessControl {\\n using EnumerableSet for EnumerableSet.AddressSet;\\n\\n mapping(bytes32 => EnumerableSet.AddressSet) private _roleMembers;\\n\\n /**\\n * @dev See {IERC165-supportsInterface}.\\n */\\n function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {\\n return interfaceId == type(IAccessControlEnumerable).interfaceId || super.supportsInterface(interfaceId);\\n }\\n\\n /**\\n * @dev Returns one of the accounts that have `role`. `index` must be a\\n * value between 0 and {getRoleMemberCount}, non-inclusive.\\n *\\n * Role bearers are not sorted in any particular way, and their ordering may\\n * change at any point.\\n *\\n * WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure\\n * you perform all queries on the same block. See the following\\n * https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post]\\n * for more information.\\n */\\n function getRoleMember(bytes32 role, uint256 index) public view override returns (address) {\\n return _roleMembers[role].at(index);\\n }\\n\\n /**\\n * @dev Returns the number of accounts that have `role`. Can be used\\n * together with {getRoleMember} to enumerate all bearers of a role.\\n */\\n function getRoleMemberCount(bytes32 role) public view override returns (uint256) {\\n return _roleMembers[role].length();\\n }\\n\\n /**\\n * @dev Overload {_grantRole} to track enumerable memberships\\n */\\n function _grantRole(bytes32 role, address account) internal virtual override {\\n super._grantRole(role, account);\\n _roleMembers[role].add(account);\\n }\\n\\n /**\\n * @dev Overload {_revokeRole} to track enumerable memberships\\n */\\n function _revokeRole(bytes32 role, address account) internal virtual override {\\n super._revokeRole(role, account);\\n _roleMembers[role].remove(account);\\n }\\n}\\n\",\"keccak256\":\"0x1304796e9cdc64294735b4222849a240363b2aff374bb58b7c728f8dc0f4aa75\",\"license\":\"MIT\"},\"@openzeppelin/contracts/access/IAccessControl.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (access/IAccessControl.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev External interface of AccessControl declared to support ERC165 detection.\\n */\\ninterface IAccessControl {\\n /**\\n * @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`\\n *\\n * `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite\\n * {RoleAdminChanged} not being emitted signaling this.\\n *\\n * _Available since v3.1._\\n */\\n event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);\\n\\n /**\\n * @dev Emitted when `account` is granted `role`.\\n *\\n * `sender` is the account that originated the contract call, an admin role\\n * bearer except when using {AccessControl-_setupRole}.\\n */\\n event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);\\n\\n /**\\n * @dev Emitted when `account` is revoked `role`.\\n *\\n * `sender` is the account that originated the contract call:\\n * - if using `revokeRole`, it is the admin role bearer\\n * - if using `renounceRole`, it is the role bearer (i.e. `account`)\\n */\\n event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);\\n\\n /**\\n * @dev Returns `true` if `account` has been granted `role`.\\n */\\n function hasRole(bytes32 role, address account) external view returns (bool);\\n\\n /**\\n * @dev Returns the admin role that controls `role`. See {grantRole} and\\n * {revokeRole}.\\n *\\n * To change a role's admin, use {AccessControl-_setRoleAdmin}.\\n */\\n function getRoleAdmin(bytes32 role) external view returns (bytes32);\\n\\n /**\\n * @dev Grants `role` to `account`.\\n *\\n * If `account` had not been already granted `role`, emits a {RoleGranted}\\n * event.\\n *\\n * Requirements:\\n *\\n * - the caller must have ``role``'s admin role.\\n */\\n function grantRole(bytes32 role, address account) external;\\n\\n /**\\n * @dev Revokes `role` from `account`.\\n *\\n * If `account` had been granted `role`, emits a {RoleRevoked} event.\\n *\\n * Requirements:\\n *\\n * - the caller must have ``role``'s admin role.\\n */\\n function revokeRole(bytes32 role, address account) external;\\n\\n /**\\n * @dev Revokes `role` from the calling account.\\n *\\n * Roles are often managed via {grantRole} and {revokeRole}: this function's\\n * purpose is to provide a mechanism for accounts to lose their privileges\\n * if they are compromised (such as when a trusted device is misplaced).\\n *\\n * If the calling account had been granted `role`, emits a {RoleRevoked}\\n * event.\\n *\\n * Requirements:\\n *\\n * - the caller must be `account`.\\n */\\n function renounceRole(bytes32 role, address account) external;\\n}\\n\",\"keccak256\":\"0x59ce320a585d7e1f163cd70390a0ef2ff9cec832e2aa544293a00692465a7a57\",\"license\":\"MIT\"},\"@openzeppelin/contracts/access/IAccessControlEnumerable.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (access/IAccessControlEnumerable.sol)\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"./IAccessControl.sol\\\";\\n\\n/**\\n * @dev External interface of AccessControlEnumerable declared to support ERC165 detection.\\n */\\ninterface IAccessControlEnumerable is IAccessControl {\\n /**\\n * @dev Returns one of the accounts that have `role`. `index` must be a\\n * value between 0 and {getRoleMemberCount}, non-inclusive.\\n *\\n * Role bearers are not sorted in any particular way, and their ordering may\\n * change at any point.\\n *\\n * WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure\\n * you perform all queries on the same block. See the following\\n * https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post]\\n * for more information.\\n */\\n function getRoleMember(bytes32 role, uint256 index) external view returns (address);\\n\\n /**\\n * @dev Returns the number of accounts that have `role`. Can be used\\n * together with {getRoleMember} to enumerate all bearers of a role.\\n */\\n function getRoleMemberCount(bytes32 role) external view returns (uint256);\\n}\\n\",\"keccak256\":\"0xba4459ab871dfa300f5212c6c30178b63898c03533a1ede28436f11546626676\",\"license\":\"MIT\"},\"@openzeppelin/contracts/token/ERC20/IERC20.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (token/ERC20/IERC20.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev Interface of the ERC20 standard as defined in the EIP.\\n */\\ninterface IERC20 {\\n /**\\n * @dev Returns the amount of tokens in existence.\\n */\\n function totalSupply() external view returns (uint256);\\n\\n /**\\n * @dev Returns the amount of tokens owned by `account`.\\n */\\n function balanceOf(address account) external view returns (uint256);\\n\\n /**\\n * @dev Moves `amount` tokens from the caller's account to `recipient`.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * Emits a {Transfer} event.\\n */\\n function transfer(address recipient, uint256 amount) external returns (bool);\\n\\n /**\\n * @dev Returns the remaining number of tokens that `spender` will be\\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\\n * zero by default.\\n *\\n * This value changes when {approve} or {transferFrom} are called.\\n */\\n function allowance(address owner, address spender) external view returns (uint256);\\n\\n /**\\n * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\\n * that someone may use both the old and the new allowance by unfortunate\\n * transaction ordering. One possible solution to mitigate this race\\n * condition is to first reduce the spender's allowance to 0 and set the\\n * desired value afterwards:\\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\\n *\\n * Emits an {Approval} event.\\n */\\n function approve(address spender, uint256 amount) external returns (bool);\\n\\n /**\\n * @dev Moves `amount` tokens from `sender` to `recipient` using the\\n * allowance mechanism. `amount` is then deducted from the caller's\\n * allowance.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * Emits a {Transfer} event.\\n */\\n function transferFrom(\\n address sender,\\n address recipient,\\n uint256 amount\\n ) external returns (bool);\\n\\n /**\\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\\n * another (`to`).\\n *\\n * Note that `value` may be zero.\\n */\\n event Transfer(address indexed from, address indexed to, uint256 value);\\n\\n /**\\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\\n * a call to {approve}. `value` is the new allowance.\\n */\\n event Approval(address indexed owner, address indexed spender, uint256 value);\\n}\\n\",\"keccak256\":\"0x61437cb513a887a1bbad006e7b1c8b414478427d33de47c5600af3c748f108da\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/Context.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev Provides information about the current execution context, including the\\n * sender of the transaction and its data. While these are generally available\\n * via msg.sender and msg.data, they should not be accessed in such a direct\\n * manner, since when dealing with meta-transactions the account sending and\\n * paying for execution may not be the actual sender (as far as an application\\n * is concerned).\\n *\\n * This contract is only required for intermediate, library-like contracts.\\n */\\nabstract contract Context {\\n function _msgSender() internal view virtual returns (address) {\\n return msg.sender;\\n }\\n\\n function _msgData() internal view virtual returns (bytes calldata) {\\n return msg.data;\\n }\\n}\\n\",\"keccak256\":\"0xe2e337e6dde9ef6b680e07338c493ebea1b5fd09b43424112868e9cc1706bca7\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/Strings.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (utils/Strings.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev String operations.\\n */\\nlibrary Strings {\\n bytes16 private constant _HEX_SYMBOLS = \\\"0123456789abcdef\\\";\\n\\n /**\\n * @dev Converts a `uint256` to its ASCII `string` decimal representation.\\n */\\n function toString(uint256 value) internal pure returns (string memory) {\\n // Inspired by OraclizeAPI's implementation - MIT licence\\n // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol\\n\\n if (value == 0) {\\n return \\\"0\\\";\\n }\\n uint256 temp = value;\\n uint256 digits;\\n while (temp != 0) {\\n digits++;\\n temp /= 10;\\n }\\n bytes memory buffer = new bytes(digits);\\n while (value != 0) {\\n digits -= 1;\\n buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));\\n value /= 10;\\n }\\n return string(buffer);\\n }\\n\\n /**\\n * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.\\n */\\n function toHexString(uint256 value) internal pure returns (string memory) {\\n if (value == 0) {\\n return \\\"0x00\\\";\\n }\\n uint256 temp = value;\\n uint256 length = 0;\\n while (temp != 0) {\\n length++;\\n temp >>= 8;\\n }\\n return toHexString(value, length);\\n }\\n\\n /**\\n * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.\\n */\\n function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {\\n bytes memory buffer = new bytes(2 * length + 2);\\n buffer[0] = \\\"0\\\";\\n buffer[1] = \\\"x\\\";\\n for (uint256 i = 2 * length + 1; i > 1; --i) {\\n buffer[i] = _HEX_SYMBOLS[value & 0xf];\\n value >>= 4;\\n }\\n require(value == 0, \\\"Strings: hex length insufficient\\\");\\n return string(buffer);\\n }\\n}\\n\",\"keccak256\":\"0x32c202bd28995dd20c4347b7c6467a6d3241c74c8ad3edcbb610cd9205916c45\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/introspection/ERC165.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol)\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"./IERC165.sol\\\";\\n\\n/**\\n * @dev Implementation of the {IERC165} interface.\\n *\\n * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check\\n * for the additional interface id that will be supported. For example:\\n *\\n * ```solidity\\n * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {\\n * return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);\\n * }\\n * ```\\n *\\n * Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation.\\n */\\nabstract contract ERC165 is IERC165 {\\n /**\\n * @dev See {IERC165-supportsInterface}.\\n */\\n function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {\\n return interfaceId == type(IERC165).interfaceId;\\n }\\n}\\n\",\"keccak256\":\"0xd10975de010d89fd1c78dc5e8a9a7e7f496198085c151648f20cba166b32582b\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/introspection/IERC165.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev Interface of the ERC165 standard, as defined in the\\n * https://eips.ethereum.org/EIPS/eip-165[EIP].\\n *\\n * Implementers can declare support of contract interfaces, which can then be\\n * queried by others ({ERC165Checker}).\\n *\\n * For an implementation, see {ERC165}.\\n */\\ninterface IERC165 {\\n /**\\n * @dev Returns true if this contract implements the interface defined by\\n * `interfaceId`. See the corresponding\\n * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]\\n * to learn more about how these ids are created.\\n *\\n * This function call must use less than 30 000 gas.\\n */\\n function supportsInterface(bytes4 interfaceId) external view returns (bool);\\n}\\n\",\"keccak256\":\"0x447a5f3ddc18419d41ff92b3773fb86471b1db25773e07f877f548918a185bf1\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/structs/EnumerableSet.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (utils/structs/EnumerableSet.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev Library for managing\\n * https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive\\n * types.\\n *\\n * Sets have the following properties:\\n *\\n * - Elements are added, removed, and checked for existence in constant time\\n * (O(1)).\\n * - Elements are enumerated in O(n). No guarantees are made on the ordering.\\n *\\n * ```\\n * contract Example {\\n * // Add the library methods\\n * using EnumerableSet for EnumerableSet.AddressSet;\\n *\\n * // Declare a set state variable\\n * EnumerableSet.AddressSet private mySet;\\n * }\\n * ```\\n *\\n * As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)\\n * and `uint256` (`UintSet`) are supported.\\n */\\nlibrary EnumerableSet {\\n // To implement this library for multiple types with as little code\\n // repetition as possible, we write it in terms of a generic Set type with\\n // bytes32 values.\\n // The Set implementation uses private functions, and user-facing\\n // implementations (such as AddressSet) are just wrappers around the\\n // underlying Set.\\n // This means that we can only create new EnumerableSets for types that fit\\n // in bytes32.\\n\\n struct Set {\\n // Storage of set values\\n bytes32[] _values;\\n // Position of the value in the `values` array, plus 1 because index 0\\n // means a value is not in the set.\\n mapping(bytes32 => uint256) _indexes;\\n }\\n\\n /**\\n * @dev Add a value to a set. O(1).\\n *\\n * Returns true if the value was added to the set, that is if it was not\\n * already present.\\n */\\n function _add(Set storage set, bytes32 value) private returns (bool) {\\n if (!_contains(set, value)) {\\n set._values.push(value);\\n // The value is stored at length-1, but we add 1 to all indexes\\n // and use 0 as a sentinel value\\n set._indexes[value] = set._values.length;\\n return true;\\n } else {\\n return false;\\n }\\n }\\n\\n /**\\n * @dev Removes a value from a set. O(1).\\n *\\n * Returns true if the value was removed from the set, that is if it was\\n * present.\\n */\\n function _remove(Set storage set, bytes32 value) private returns (bool) {\\n // We read and store the value's index to prevent multiple reads from the same storage slot\\n uint256 valueIndex = set._indexes[value];\\n\\n if (valueIndex != 0) {\\n // Equivalent to contains(set, value)\\n // To delete an element from the _values array in O(1), we swap the element to delete with the last one in\\n // the array, and then remove the last element (sometimes called as 'swap and pop').\\n // This modifies the order of the array, as noted in {at}.\\n\\n uint256 toDeleteIndex = valueIndex - 1;\\n uint256 lastIndex = set._values.length - 1;\\n\\n if (lastIndex != toDeleteIndex) {\\n bytes32 lastvalue = set._values[lastIndex];\\n\\n // Move the last value to the index where the value to delete is\\n set._values[toDeleteIndex] = lastvalue;\\n // Update the index for the moved value\\n set._indexes[lastvalue] = valueIndex; // Replace lastvalue's index to valueIndex\\n }\\n\\n // Delete the slot where the moved value was stored\\n set._values.pop();\\n\\n // Delete the index for the deleted slot\\n delete set._indexes[value];\\n\\n return true;\\n } else {\\n return false;\\n }\\n }\\n\\n /**\\n * @dev Returns true if the value is in the set. O(1).\\n */\\n function _contains(Set storage set, bytes32 value) private view returns (bool) {\\n return set._indexes[value] != 0;\\n }\\n\\n /**\\n * @dev Returns the number of values on the set. O(1).\\n */\\n function _length(Set storage set) private view returns (uint256) {\\n return set._values.length;\\n }\\n\\n /**\\n * @dev Returns the value stored at position `index` in the set. O(1).\\n *\\n * Note that there are no guarantees on the ordering of values inside the\\n * array, and it may change when more values are added or removed.\\n *\\n * Requirements:\\n *\\n * - `index` must be strictly less than {length}.\\n */\\n function _at(Set storage set, uint256 index) private view returns (bytes32) {\\n return set._values[index];\\n }\\n\\n /**\\n * @dev Return the entire set in an array\\n *\\n * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed\\n * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that\\n * this function has an unbounded cost, and using it as part of a state-changing function may render the function\\n * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.\\n */\\n function _values(Set storage set) private view returns (bytes32[] memory) {\\n return set._values;\\n }\\n\\n // Bytes32Set\\n\\n struct Bytes32Set {\\n Set _inner;\\n }\\n\\n /**\\n * @dev Add a value to a set. O(1).\\n *\\n * Returns true if the value was added to the set, that is if it was not\\n * already present.\\n */\\n function add(Bytes32Set storage set, bytes32 value) internal returns (bool) {\\n return _add(set._inner, value);\\n }\\n\\n /**\\n * @dev Removes a value from a set. O(1).\\n *\\n * Returns true if the value was removed from the set, that is if it was\\n * present.\\n */\\n function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) {\\n return _remove(set._inner, value);\\n }\\n\\n /**\\n * @dev Returns true if the value is in the set. O(1).\\n */\\n function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {\\n return _contains(set._inner, value);\\n }\\n\\n /**\\n * @dev Returns the number of values in the set. O(1).\\n */\\n function length(Bytes32Set storage set) internal view returns (uint256) {\\n return _length(set._inner);\\n }\\n\\n /**\\n * @dev Returns the value stored at position `index` in the set. O(1).\\n *\\n * Note that there are no guarantees on the ordering of values inside the\\n * array, and it may change when more values are added or removed.\\n *\\n * Requirements:\\n *\\n * - `index` must be strictly less than {length}.\\n */\\n function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {\\n return _at(set._inner, index);\\n }\\n\\n /**\\n * @dev Return the entire set in an array\\n *\\n * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed\\n * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that\\n * this function has an unbounded cost, and using it as part of a state-changing function may render the function\\n * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.\\n */\\n function values(Bytes32Set storage set) internal view returns (bytes32[] memory) {\\n return _values(set._inner);\\n }\\n\\n // AddressSet\\n\\n struct AddressSet {\\n Set _inner;\\n }\\n\\n /**\\n * @dev Add a value to a set. O(1).\\n *\\n * Returns true if the value was added to the set, that is if it was not\\n * already present.\\n */\\n function add(AddressSet storage set, address value) internal returns (bool) {\\n return _add(set._inner, bytes32(uint256(uint160(value))));\\n }\\n\\n /**\\n * @dev Removes a value from a set. O(1).\\n *\\n * Returns true if the value was removed from the set, that is if it was\\n * present.\\n */\\n function remove(AddressSet storage set, address value) internal returns (bool) {\\n return _remove(set._inner, bytes32(uint256(uint160(value))));\\n }\\n\\n /**\\n * @dev Returns true if the value is in the set. O(1).\\n */\\n function contains(AddressSet storage set, address value) internal view returns (bool) {\\n return _contains(set._inner, bytes32(uint256(uint160(value))));\\n }\\n\\n /**\\n * @dev Returns the number of values in the set. O(1).\\n */\\n function length(AddressSet storage set) internal view returns (uint256) {\\n return _length(set._inner);\\n }\\n\\n /**\\n * @dev Returns the value stored at position `index` in the set. O(1).\\n *\\n * Note that there are no guarantees on the ordering of values inside the\\n * array, and it may change when more values are added or removed.\\n *\\n * Requirements:\\n *\\n * - `index` must be strictly less than {length}.\\n */\\n function at(AddressSet storage set, uint256 index) internal view returns (address) {\\n return address(uint160(uint256(_at(set._inner, index))));\\n }\\n\\n /**\\n * @dev Return the entire set in an array\\n *\\n * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed\\n * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that\\n * this function has an unbounded cost, and using it as part of a state-changing function may render the function\\n * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.\\n */\\n function values(AddressSet storage set) internal view returns (address[] memory) {\\n bytes32[] memory store = _values(set._inner);\\n address[] memory result;\\n\\n assembly {\\n result := store\\n }\\n\\n return result;\\n }\\n\\n // UintSet\\n\\n struct UintSet {\\n Set _inner;\\n }\\n\\n /**\\n * @dev Add a value to a set. O(1).\\n *\\n * Returns true if the value was added to the set, that is if it was not\\n * already present.\\n */\\n function add(UintSet storage set, uint256 value) internal returns (bool) {\\n return _add(set._inner, bytes32(value));\\n }\\n\\n /**\\n * @dev Removes a value from a set. O(1).\\n *\\n * Returns true if the value was removed from the set, that is if it was\\n * present.\\n */\\n function remove(UintSet storage set, uint256 value) internal returns (bool) {\\n return _remove(set._inner, bytes32(value));\\n }\\n\\n /**\\n * @dev Returns true if the value is in the set. O(1).\\n */\\n function contains(UintSet storage set, uint256 value) internal view returns (bool) {\\n return _contains(set._inner, bytes32(value));\\n }\\n\\n /**\\n * @dev Returns the number of values on the set. O(1).\\n */\\n function length(UintSet storage set) internal view returns (uint256) {\\n return _length(set._inner);\\n }\\n\\n /**\\n * @dev Returns the value stored at position `index` in the set. O(1).\\n *\\n * Note that there are no guarantees on the ordering of values inside the\\n * array, and it may change when more values are added or removed.\\n *\\n * Requirements:\\n *\\n * - `index` must be strictly less than {length}.\\n */\\n function at(UintSet storage set, uint256 index) internal view returns (uint256) {\\n return uint256(_at(set._inner, index));\\n }\\n\\n /**\\n * @dev Return the entire set in an array\\n *\\n * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed\\n * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that\\n * this function has an unbounded cost, and using it as part of a state-changing function may render the function\\n * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.\\n */\\n function values(UintSet storage set) internal view returns (uint256[] memory) {\\n bytes32[] memory store = _values(set._inner);\\n uint256[] memory result;\\n\\n assembly {\\n result := store\\n }\\n\\n return result;\\n }\\n}\\n\",\"keccak256\":\"0x9772845c886f87a3aab315f8d6b68aa599027c20f441b131cd4afaf65b588900\",\"license\":\"MIT\"},\"contracts/automation/AbstractSafeModule.sol\":{\"content\":\"// SPDX-License-Identifier: BUSL-1.1\\npragma solidity ^0.8.0;\\n\\nimport { AccessControlEnumerable } from \\\"@openzeppelin/contracts/access/AccessControlEnumerable.sol\\\";\\nimport { IERC20 } from \\\"@openzeppelin/contracts/token/ERC20/IERC20.sol\\\";\\nimport { ISafe } from \\\"../interfaces/ISafe.sol\\\";\\n\\nabstract contract AbstractSafeModule is AccessControlEnumerable {\\n ISafe public immutable safeContract;\\n\\n bytes32 public constant OPERATOR_ROLE = keccak256(\\\"OPERATOR_ROLE\\\");\\n\\n modifier onlySafe() {\\n require(\\n msg.sender == address(safeContract),\\n \\\"Caller is not the safe contract\\\"\\n );\\n _;\\n }\\n\\n modifier onlyOperator() {\\n require(\\n hasRole(OPERATOR_ROLE, msg.sender),\\n \\\"Caller is not an operator\\\"\\n );\\n _;\\n }\\n\\n constructor(address _safeContract) {\\n safeContract = ISafe(_safeContract);\\n _grantRole(DEFAULT_ADMIN_ROLE, address(safeContract));\\n _grantRole(OPERATOR_ROLE, address(safeContract));\\n }\\n\\n /**\\n * @dev Helps recovering any tokens accidentally sent to this module.\\n * @param token Token to transfer. 0x0 to transfer Native token.\\n * @param amount Amount to transfer. 0 to transfer all balance.\\n */\\n function transferTokens(address token, uint256 amount) external onlySafe {\\n if (address(token) == address(0)) {\\n // Move ETH\\n amount = amount > 0 ? amount : address(this).balance;\\n payable(address(safeContract)).transfer(amount);\\n return;\\n }\\n\\n // Move all balance if amount set to 0\\n amount = amount > 0 ? amount : IERC20(token).balanceOf(address(this));\\n\\n // Transfer to Safe contract\\n // slither-disable-next-line unchecked-transfer unused-return\\n IERC20(token).transfer(address(safeContract), amount);\\n }\\n\\n receive() external payable {\\n // Accept ETH to pay for bridge fees\\n }\\n}\\n\",\"keccak256\":\"0x02f5cebee3ef21afb1e5dafe15a9160017dde1b7361653e6285f884124d15af5\",\"license\":\"BUSL-1.1\"},\"contracts/automation/CurvePoolBoosterBribesModule.sol\":{\"content\":\"// SPDX-License-Identifier: BUSL-1.1\\npragma solidity ^0.8.0;\\n\\nimport { AbstractSafeModule } from \\\"./AbstractSafeModule.sol\\\";\\n\\ninterface ICurvePoolBooster {\\n function manageCampaign(\\n uint256 totalRewardAmount,\\n uint8 numberOfPeriods,\\n uint256 maxRewardPerVote,\\n uint256 additionalGasLimit\\n ) external payable;\\n}\\n\\n/// @title CurvePoolBoosterBribesModule\\n/// @author Origin Protocol\\n/// @notice Gnosis Safe module that automates the management of VotemarketV2 bribe campaigns\\n/// across multiple CurvePoolBooster contracts. It instructs the Safe to call `manageCampaign`\\n/// on each registered pool booster, forwarding ETH from the Safe's balance to cover\\n/// bridge fees. Campaign parameters (reward amount, duration, reward rate) can be\\n/// configured per pool or left to sensible defaults.\\ncontract CurvePoolBoosterBribesModule is AbstractSafeModule {\\n ////////////////////////////////////////////////////\\n /// --- Storage\\n ////////////////////////////////////////////////////\\n\\n /// @notice List of CurvePoolBooster addresses managed by this module\\n address[] public poolBoosters;\\n\\n /// @notice ETH amount sent per pool booster to cover the L1 -> L2 bridge fee\\n uint256 public bridgeFee;\\n\\n /// @notice Gas limit passed to manageCampaign for cross-chain execution\\n uint256 public additionalGasLimit;\\n\\n ////////////////////////////////////////////////////\\n /// --- Events\\n ////////////////////////////////////////////////////\\n\\n event BridgeFeeUpdated(uint256 newFee);\\n event AdditionalGasLimitUpdated(uint256 newGasLimit);\\n event PoolBoosterAddressAdded(address pool);\\n event PoolBoosterAddressRemoved(address pool);\\n\\n ////////////////////////////////////////////////////\\n /// --- Constructor\\n ////////////////////////////////////////////////////\\n\\n /// @param _safeContract Address of the Gnosis Safe this module is attached to\\n /// @param _operator Address authorized to call operator-restricted functions\\n /// @param _poolBoosters Initial list of CurvePoolBooster addresses to manage\\n /// @param _bridgeFee ETH amount to send per pool booster for bridge fees\\n /// @param _additionalGasLimit Gas limit for cross-chain execution in manageCampaign\\n constructor(\\n address _safeContract,\\n address _operator,\\n address[] memory _poolBoosters,\\n uint256 _bridgeFee,\\n uint256 _additionalGasLimit\\n ) AbstractSafeModule(_safeContract) {\\n _grantRole(OPERATOR_ROLE, _operator);\\n for (uint256 i = 0; i < _poolBoosters.length; i++) {\\n _addPoolBoosterAddress(_poolBoosters[i]);\\n }\\n _setBridgeFee(_bridgeFee);\\n _setAdditionalGasLimit(_additionalGasLimit);\\n }\\n\\n ////////////////////////////////////////////////////\\n /// --- External Mutative Functions\\n ////////////////////////////////////////////////////\\n\\n /// @notice Add new CurvePoolBooster addresses to the managed list\\n /// @param _poolBoosters Addresses to add\\n function addPoolBoosterAddress(address[] calldata _poolBoosters)\\n external\\n onlyOperator\\n {\\n for (uint256 i = 0; i < _poolBoosters.length; i++) {\\n _addPoolBoosterAddress(_poolBoosters[i]);\\n }\\n }\\n\\n /// @notice Remove CurvePoolBooster addresses from the managed list\\n /// @param _poolBoosters Addresses to remove\\n function removePoolBoosterAddress(address[] calldata _poolBoosters)\\n external\\n onlyOperator\\n {\\n for (uint256 i = 0; i < _poolBoosters.length; i++) {\\n _removePoolBoosterAddress(_poolBoosters[i]);\\n }\\n }\\n\\n /// @notice Update the ETH bridge fee sent per pool booster\\n /// @param newFee New bridge fee amount in wei\\n function setBridgeFee(uint256 newFee) external onlyOperator {\\n _setBridgeFee(newFee);\\n }\\n\\n /// @notice Update the additional gas limit for cross-chain execution\\n /// @param newGasLimit New gas limit value\\n function setAdditionalGasLimit(uint256 newGasLimit) external onlyOperator {\\n _setAdditionalGasLimit(newGasLimit);\\n }\\n\\n /// @notice Default entry point to manage bribe campaigns for all registered pool boosters.\\n /// Applies the same behavior to every pool:\\n /// - totalRewardAmount = type(uint256).max \\u2192 use all available reward tokens\\n /// - numberOfPeriods = 1 \\u2192 extend by one period (week)\\n /// - maxRewardPerVote = 0 \\u2192 no update\\n /// @dev Calls `manageCampaign` on each pool booster via the Safe. The Safe must hold\\n /// enough ETH to cover `bridgeFee * poolBoosters.length`.\\n function manageBribes() external onlyOperator {\\n uint256[] memory totalRewardAmounts = new uint256[](\\n poolBoosters.length\\n );\\n uint8[] memory extraDuration = new uint8[](poolBoosters.length);\\n uint256[] memory rewardsPerVote = new uint256[](poolBoosters.length);\\n for (uint256 i = 0; i < poolBoosters.length; i++) {\\n totalRewardAmounts[i] = type(uint256).max; // use all available rewards\\n extraDuration[i] = 1; // extend by 1 period (week)\\n rewardsPerVote[i] = 0; // no update to maxRewardPerVote\\n }\\n _manageBribes(totalRewardAmounts, extraDuration, rewardsPerVote);\\n }\\n\\n /// @notice Fully configurable entry point to manage bribe campaigns. Allows setting\\n /// reward amounts, durations, and reward rates individually for each pool.\\n /// Each array must have the same length as the poolBoosters array.\\n /// @param totalRewardAmounts Total reward amount per pool (0 = no update, type(uint256).max = use all available)\\n /// @param extraDuration Number of periods to extend per pool (0 = no update, 1 = +1 week)\\n /// @param rewardsPerVote Max reward per vote per pool (0 = no update)\\n function manageBribes(\\n uint256[] calldata totalRewardAmounts,\\n uint8[] calldata extraDuration,\\n uint256[] calldata rewardsPerVote\\n ) external onlyOperator {\\n require(\\n poolBoosters.length == totalRewardAmounts.length,\\n \\\"Length mismatch\\\"\\n );\\n require(poolBoosters.length == extraDuration.length, \\\"Length mismatch\\\");\\n require(\\n poolBoosters.length == rewardsPerVote.length,\\n \\\"Length mismatch\\\"\\n );\\n _manageBribes(totalRewardAmounts, extraDuration, rewardsPerVote);\\n }\\n\\n ////////////////////////////////////////////////////\\n /// --- External View Functions\\n ////////////////////////////////////////////////////\\n\\n /// @notice Get the full list of managed CurvePoolBooster addresses\\n /// @return Array of pool booster addresses\\n function getPoolBoosters() external view returns (address[] memory) {\\n return poolBoosters;\\n }\\n\\n ////////////////////////////////////////////////////\\n /// --- Internal Functions\\n ////////////////////////////////////////////////////\\n\\n /// @notice Internal logic to add a single pool booster address\\n /// @dev Reverts if the address is already in the poolBoosters array\\n /// @param _pool Address to append to the poolBoosters array\\n function _addPoolBoosterAddress(address _pool) internal {\\n require(_pool != address(0), \\\"Zero address\\\");\\n for (uint256 j = 0; j < poolBoosters.length; j++) {\\n require(poolBoosters[j] != _pool, \\\"Pool already added\\\");\\n }\\n poolBoosters.push(_pool);\\n emit PoolBoosterAddressAdded(_pool);\\n }\\n\\n /// @notice Internal logic to remove a pool booster address\\n /// @dev Swaps the target with the last element and pops to avoid gaps\\n /// @param pool Address to remove from the poolBoosters array\\n function _removePoolBoosterAddress(address pool) internal {\\n uint256 length = poolBoosters.length;\\n for (uint256 i = 0; i < length; i++) {\\n if (poolBoosters[i] == pool) {\\n poolBoosters[i] = poolBoosters[length - 1];\\n poolBoosters.pop();\\n emit PoolBoosterAddressRemoved(pool);\\n return;\\n }\\n }\\n revert(\\\"Pool not found\\\");\\n }\\n\\n /// @notice Internal logic to set the bridge fee\\n /// @param newFee New bridge fee amount in wei\\n function _setBridgeFee(uint256 newFee) internal {\\n require(newFee <= 0.01 ether, \\\"Bridge fee too high\\\");\\n bridgeFee = newFee;\\n emit BridgeFeeUpdated(newFee);\\n }\\n\\n /// @notice Internal logic to set the additional gas limit\\n /// @param newGasLimit New gas limit value\\n function _setAdditionalGasLimit(uint256 newGasLimit) internal {\\n require(newGasLimit <= 10_000_000, \\\"Gas limit too high\\\");\\n additionalGasLimit = newGasLimit;\\n emit AdditionalGasLimitUpdated(newGasLimit);\\n }\\n\\n /// @notice Internal logic to manage bribe campaigns for all registered pool boosters\\n /// @dev Iterates over all pool boosters and instructs the Safe to call `manageCampaign`\\n /// on each one, sending `bridgeFee` ETH from the Safe's balance per call.\\n /// @param totalRewardAmounts Total reward amount per pool (0 = no update, type(uint256).max = use all available)\\n /// @param extraDuration Number of periods to extend per pool (0 = no update)\\n /// @param rewardsPerVote Max reward per vote per pool (0 = no update)\\n function _manageBribes(\\n uint256[] memory totalRewardAmounts,\\n uint8[] memory extraDuration,\\n uint256[] memory rewardsPerVote\\n ) internal {\\n uint256 pbCount = poolBoosters.length;\\n require(\\n address(safeContract).balance >= bridgeFee * pbCount,\\n \\\"Not enough ETH for bridge fees\\\"\\n );\\n for (uint256 i = 0; i < pbCount; i++) {\\n address poolBoosterAddress = poolBoosters[i];\\n require(\\n safeContract.execTransactionFromModule(\\n poolBoosterAddress,\\n bridgeFee, // ETH value to cover bridge fee\\n abi.encodeWithSelector(\\n ICurvePoolBooster.manageCampaign.selector,\\n totalRewardAmounts[i], // 0 = no update, max = use all\\n extraDuration[i], // numberOfPeriods, 0 = no update, 1 = +1 period (week)\\n rewardsPerVote[i], // maxRewardPerVote, 0 = no update\\n additionalGasLimit\\n ),\\n 0\\n ),\\n \\\"Manage campaign failed\\\"\\n );\\n }\\n }\\n}\\n\",\"keccak256\":\"0xd2b0be49c1cc55e5af773290e27ccc406c37e96545ef3e0e79b71f0afadb089a\",\"license\":\"BUSL-1.1\"},\"contracts/interfaces/ISafe.sol\":{\"content\":\"// SPDX-License-Identifier: BUSL-1.1\\npragma solidity ^0.8.0;\\n\\ninterface ISafe {\\n function execTransactionFromModule(\\n address,\\n uint256,\\n bytes memory,\\n uint8\\n ) external returns (bool);\\n}\\n\",\"keccak256\":\"0x6d5fb3512c4fab418222023fb1b482891906eae8d2bda9d1eb2ef3d3c7653dee\",\"license\":\"BUSL-1.1\"}},\"version\":1}", + "bytecode": "0x60a060405234801561001057600080fd5b506040516122e03803806122e083398101604081905261002f916104a5565b6001600160a01b0385166080819052859061004c906000906100de565b61006c6000805160206122c08339815191526080516100de60201b60201c565b506100856000805160206122c0833981519152856100de565b60005b83518110156100c1576100b98482815181106100a6576100a66105a9565b602002602001015161010560201b60201c565b600101610088565b506100cb8261025e565b6100d4816102ea565b50505050506105bf565b6100e88282610367565b60008281526001602052604090206101009082610406565b505050565b6001600160a01b03811661014f5760405162461bcd60e51b815260206004820152600c60248201526b5a65726f206164647265737360a01b60448201526064015b60405180910390fd5b60005b6002548110156101d857816001600160a01b031660028281548110610179576101796105a9565b6000918252602090912001546001600160a01b0316036101d05760405162461bcd60e51b8152602060048201526012602482015271141bdbdb08185b1c9958591e48185919195960721b6044820152606401610146565b600101610152565b50600280546001810182556000919091527f405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace0180546001600160a01b0319166001600160a01b0383169081179091556040519081527f73fbf341ebd14259e9d6b52784447ca2c15aa93c480c93927fd8135aefff7747906020015b60405180910390a150565b662386f26fc100008111156102b55760405162461bcd60e51b815260206004820152601360248201527f4272696467652066656520746f6f2068696768000000000000000000000000006044820152606401610146565b60038190556040518181527f42dfb00d085d601e55327921154ae76c1b24270b026c5a0c51caee18eb4c401f90602001610253565b629896808111156103325760405162461bcd60e51b815260206004820152601260248201527108ec2e640d8d2dad2e840e8dede40d0d2ced60731b6044820152606401610146565b60048190556040518181527ffe19406854a66706284c7b3480e4c2e37dc42dc468e1211d7a1abcaf721a6f0090602001610253565b6000828152602081815260408083206001600160a01b038516845290915290205460ff16610402576000828152602081815260408083206001600160a01b03851684529091529020805460ff191660011790556103c13390565b6001600160a01b0316816001600160a01b0316837f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a45b5050565b600061041b836001600160a01b038416610424565b90505b92915050565b600081815260018301602052604081205461046b5750815460018181018455600084815260208082209093018490558454848252828601909352604090209190915561041e565b50600061041e565b80516001600160a01b038116811461048a57600080fd5b919050565b634e487b7160e01b600052604160045260246000fd5b600080600080600060a086880312156104bd57600080fd5b6104c686610473565b94506104d460208701610473565b60408701519094506001600160401b038111156104f057600080fd5b8601601f8101881361050157600080fd5b80516001600160401b0381111561051a5761051a61048f565b604051600582901b90603f8201601f191681016001600160401b03811182821017156105485761054861048f565b60405291825260208184018101929081018b84111561056657600080fd5b6020850194505b8385101561058c5761057e85610473565b81526020948501940161056d565b5060608a01516080909a0151989b979a5098979695505050505050565b634e487b7160e01b600052603260045260246000fd5b608051611cc36105fd6000396000818161041e015281816109c901528181610a6401528181610b3b01528181610d3a0152610df30152611cc36000f3fe6080604052600436106101395760003560e01c80639010d07c116100ab578063bec3fa171161006f578063bec3fa171461036a578063ca15c8731461038a578063d547741f146103aa578063eec8e967146103ca578063f5b541a6146103ea578063f9081ba21461040c57600080fd5b80639010d07c146102d557806391d14854146102f5578063998cdf8314610315578063a217fddf14610335578063ab384df01461034a57600080fd5b8063248a9ca3116100fd578063248a9ca31461020b57806328d10eae146102495780632f2ff15d1461025f57806336568abe1461027f57806382b12dd71461029f578063868a7505146102b557600080fd5b806301ffc9a7146101455780630ac2b2981461017a578063110c1a411461019c57806316d5fd0e146101d45780631a765057146101e957600080fd5b3661014057005b600080fd5b34801561015157600080fd5b506101656101603660046117a1565b610440565b60405190151581526020015b60405180910390f35b34801561018657600080fd5b5061019a6101953660046117cb565b61046b565b005b3480156101a857600080fd5b506101bc6101b73660046117cb565b6104b4565b6040516001600160a01b039091168152602001610171565b3480156101e057600080fd5b5061019a6104de565b3480156101f557600080fd5b506101fe610680565b60405161017191906117e4565b34801561021757600080fd5b5061023b6102263660046117cb565b60009081526020819052604090206001015490565b604051908152602001610171565b34801561025557600080fd5b5061023b60045481565b34801561026b57600080fd5b5061019a61027a36600461184c565b6106e2565b34801561028b57600080fd5b5061019a61029a36600461184c565b610708565b3480156102ab57600080fd5b5061023b60035481565b3480156102c157600080fd5b5061019a6102d03660046118c4565b610786565b3480156102e157600080fd5b506101bc6102f036600461196a565b6108c3565b34801561030157600080fd5b5061016561031036600461184c565b6108e2565b34801561032157600080fd5b5061019a6103303660046117cb565b61090b565b34801561034157600080fd5b5061023b600081565b34801561035657600080fd5b5061019a61036536600461198c565b610948565b34801561037657600080fd5b5061019a6103853660046119ce565b6109be565b34801561039657600080fd5b5061023b6103a53660046117cb565b610bbb565b3480156103b657600080fd5b5061019a6103c536600461184c565b610bd2565b3480156103d657600080fd5b5061019a6103e536600461198c565b610bf8565b3480156103f657600080fd5b5061023b600080516020611c6e83398151915281565b34801561041857600080fd5b506101bc7f000000000000000000000000000000000000000000000000000000000000000081565b60006001600160e01b03198216635a05180f60e01b1480610465575061046582610c6e565b92915050565b610483600080516020611c6e833981519152336108e2565b6104a85760405162461bcd60e51b815260040161049f906119f8565b60405180910390fd5b6104b181610ca3565b50565b600281815481106104c457600080fd5b6000918252602090912001546001600160a01b0316905081565b6104f6600080516020611c6e833981519152336108e2565b6105125760405162461bcd60e51b815260040161049f906119f8565b60025460009067ffffffffffffffff81111561053057610530611a2f565b604051908082528060200260200182016040528015610559578160200160208202803683370190505b5060025490915060009067ffffffffffffffff81111561057b5761057b611a2f565b6040519080825280602002602001820160405280156105a4578160200160208202803683370190505b5060025490915060009067ffffffffffffffff8111156105c6576105c6611a2f565b6040519080825280602002602001820160405280156105ef578160200160208202803683370190505b50905060005b60025481101561066f5760001984828151811061061457610614611a45565b602002602001018181525050600183828151811061063457610634611a45565b602002602001019060ff16908160ff1681525050600082828151811061065c5761065c611a45565b60209081029190910101526001016105f5565b5061067b838383610d27565b505050565b606060028054806020026020016040519081016040528092919081815260200182805480156106d857602002820191906000526020600020905b81546001600160a01b031681526001909101906020018083116106ba575b5050505050905090565b6000828152602081905260409020600101546106fe8133610f84565b61067b8383610fe8565b6001600160a01b03811633146107785760405162461bcd60e51b815260206004820152602f60248201527f416363657373436f6e74726f6c3a2063616e206f6e6c792072656e6f756e636560448201526e103937b632b9903337b91039b2b63360891b606482015260840161049f565b610782828261100a565b5050565b61079e600080516020611c6e833981519152336108e2565b6107ba5760405162461bcd60e51b815260040161049f906119f8565b60025485146107db5760405162461bcd60e51b815260040161049f90611a5b565b60025483146107fc5760405162461bcd60e51b815260040161049f90611a5b565b600254811461081d5760405162461bcd60e51b815260040161049f90611a5b565b6108bb86868080602002602001604051908101604052809392919081815260200183836020028082843760009201919091525050604080516020808a0282810182019093528982529093508992508891829185019084908082843760009201919091525050604080516020808902828101820190935288825290935088925087918291850190849080828437600092019190915250610d2792505050565b505050505050565b60008281526001602052604081206108db908361102c565b9392505050565b6000918252602082815260408084206001600160a01b0393909316845291905290205460ff1690565b610923600080516020611c6e833981519152336108e2565b61093f5760405162461bcd60e51b815260040161049f906119f8565b6104b181611038565b610960600080516020611c6e833981519152336108e2565b61097c5760405162461bcd60e51b815260040161049f906119f8565b60005b8181101561067b576109b683838381811061099c5761099c611a45565b90506020020160208101906109b19190611a84565b6110ba565b60010161097f565b336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614610a365760405162461bcd60e51b815260206004820152601f60248201527f43616c6c6572206973206e6f7420746865207361666520636f6e747261637400604482015260640161049f565b6001600160a01b038216610aad5760008111610a525747610a54565b805b6040519091506001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169082156108fc029083906000818181858888f1935050505015801561067b573d6000803e3d6000fd5b60008111610b22576040516370a0823160e01b81523060048201526001600160a01b038316906370a0823190602401602060405180830381865afa158015610af9573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b1d9190611a9f565b610b24565b805b60405163a9059cbb60e01b81526001600160a01b037f000000000000000000000000000000000000000000000000000000000000000081166004830152602482018390529192509083169063a9059cbb906044016020604051808303816000875af1158015610b97573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061067b9190611ab8565b60008181526001602052604081206104659061122f565b600082815260208190526040902060010154610bee8133610f84565b61067b838361100a565b610c10600080516020611c6e833981519152336108e2565b610c2c5760405162461bcd60e51b815260040161049f906119f8565b60005b8181101561067b57610c66838383818110610c4c57610c4c611a45565b9050602002016020810190610c619190611a84565b611239565b600101610c2f565b60006001600160e01b03198216637965db0b60e01b148061046557506301ffc9a760e01b6001600160e01b0319831614610465565b62989680811115610ceb5760405162461bcd60e51b815260206004820152601260248201527108ec2e640d8d2dad2e840e8dede40d0d2ced60731b604482015260640161049f565b60048190556040518181527ffe19406854a66706284c7b3480e4c2e37dc42dc468e1211d7a1abcaf721a6f00906020015b60405180910390a150565b600254600354610d38908290611af0565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316311015610db15760405162461bcd60e51b815260206004820152601e60248201527f4e6f7420656e6f7567682045544820666f722062726964676520666565730000604482015260640161049f565b60005b81811015610f7d57600060028281548110610dd157610dd1611a45565b9060005260206000200160009054906101000a90046001600160a01b031690507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663468721a782600354636dbb501160e01b8a8781518110610e3e57610e3e611a45565b60200260200101518a8881518110610e5857610e58611a45565b60200260200101518a8981518110610e7257610e72611a45565b6020908102919091010151600454604051602481019490945260ff90921660448401526064830152608482015260a40160408051601f198184030181529181526020820180516001600160e01b03166001600160e01b03199485161790525160e086901b9092168252610eec939291600090600401611b57565b6020604051808303816000875af1158015610f0b573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f2f9190611ab8565b610f745760405162461bcd60e51b815260206004820152601660248201527513585b9859d94818d85b5c185a59db8819985a5b195960521b604482015260640161049f565b50600101610db4565b5050505050565b610f8e82826108e2565b61078257610fa6816001600160a01b03166014611386565b610fb1836020611386565b604051602001610fc2929190611b92565b60408051601f198184030181529082905262461bcd60e51b825261049f91600401611c07565b610ff28282611522565b600082815260016020526040902061067b90826115a6565b61101482826115bb565b600082815260016020526040902061067b9082611620565b60006108db8383611635565b662386f26fc100008111156110855760405162461bcd60e51b8152602060048201526013602482015272084e4d2c8ceca40cccaca40e8dede40d0d2ced606b1b604482015260640161049f565b60038190556040518181527f42dfb00d085d601e55327921154ae76c1b24270b026c5a0c51caee18eb4c401f90602001610d1c565b60025460005b818110156111f557826001600160a01b0316600282815481106110e5576110e5611a45565b6000918252602090912001546001600160a01b0316036111ed57600261110c600184611c1a565b8154811061111c5761111c611a45565b600091825260209091200154600280546001600160a01b03909216918390811061114857611148611a45565b9060005260206000200160006101000a8154816001600160a01b0302191690836001600160a01b03160217905550600280548061118757611187611c2d565b6000828152602090819020600019908301810180546001600160a01b03191690559091019091556040516001600160a01b03851681527f5acf3d4990bcb8de36e8b743ef49e57f65600af9d09b4a50da91c2061b8aca79910160405180910390a1505050565b6001016110c0565b5060405162461bcd60e51b815260206004820152600e60248201526d141bdbdb081b9bdd08199bdd5b9960921b604482015260640161049f565b6000610465825490565b6001600160a01b03811661127e5760405162461bcd60e51b815260206004820152600c60248201526b5a65726f206164647265737360a01b604482015260640161049f565b60005b60025481101561130757816001600160a01b0316600282815481106112a8576112a8611a45565b6000918252602090912001546001600160a01b0316036112ff5760405162461bcd60e51b8152602060048201526012602482015271141bdbdb08185b1c9958591e48185919195960721b604482015260640161049f565b600101611281565b50600280546001810182556000919091527f405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace0180546001600160a01b0319166001600160a01b0383169081179091556040519081527f73fbf341ebd14259e9d6b52784447ca2c15aa93c480c93927fd8135aefff774790602001610d1c565b60606000611395836002611af0565b6113a0906002611c43565b67ffffffffffffffff8111156113b8576113b8611a2f565b6040519080825280601f01601f1916602001820160405280156113e2576020820181803683370190505b509050600360fc1b816000815181106113fd576113fd611a45565b60200101906001600160f81b031916908160001a905350600f60fb1b8160018151811061142c5761142c611a45565b60200101906001600160f81b031916908160001a9053506000611450846002611af0565b61145b906001611c43565b90505b60018111156114d3576f181899199a1a9b1b9c1cb0b131b232b360811b85600f166010811061148f5761148f611a45565b1a60f81b8282815181106114a5576114a5611a45565b60200101906001600160f81b031916908160001a90535060049490941c936114cc81611c56565b905061145e565b5083156108db5760405162461bcd60e51b815260206004820181905260248201527f537472696e67733a20686578206c656e67746820696e73756666696369656e74604482015260640161049f565b61152c82826108e2565b610782576000828152602081815260408083206001600160a01b03851684529091529020805460ff191660011790556115623390565b6001600160a01b0316816001600160a01b0316837f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a45050565b60006108db836001600160a01b03841661165f565b6115c582826108e2565b15610782576000828152602081815260408083206001600160a01b0385168085529252808320805460ff1916905551339285917ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b9190a45050565b60006108db836001600160a01b0384166116ae565b600082600001828154811061164c5761164c611a45565b9060005260206000200154905092915050565b60008181526001830160205260408120546116a657508154600181810184556000848152602080822090930184905584548482528286019093526040902091909155610465565b506000610465565b600081815260018301602052604081205480156117975760006116d2600183611c1a565b85549091506000906116e690600190611c1a565b905081811461174b57600086600001828154811061170657611706611a45565b906000526020600020015490508087600001848154811061172957611729611a45565b6000918252602080832090910192909255918252600188019052604090208390555b855486908061175c5761175c611c2d565b600190038181906000526020600020016000905590558560010160008681526020019081526020016000206000905560019350505050610465565b6000915050610465565b6000602082840312156117b357600080fd5b81356001600160e01b0319811681146108db57600080fd5b6000602082840312156117dd57600080fd5b5035919050565b602080825282518282018190526000918401906040840190835b818110156118255783516001600160a01b03168352602093840193909201916001016117fe565b509095945050505050565b80356001600160a01b038116811461184757600080fd5b919050565b6000806040838503121561185f57600080fd5b8235915061186f60208401611830565b90509250929050565b60008083601f84011261188a57600080fd5b50813567ffffffffffffffff8111156118a257600080fd5b6020830191508360208260051b85010111156118bd57600080fd5b9250929050565b600080600080600080606087890312156118dd57600080fd5b863567ffffffffffffffff8111156118f457600080fd5b61190089828a01611878565b909750955050602087013567ffffffffffffffff81111561192057600080fd5b61192c89828a01611878565b909550935050604087013567ffffffffffffffff81111561194c57600080fd5b61195889828a01611878565b979a9699509497509295939492505050565b6000806040838503121561197d57600080fd5b50508035926020909101359150565b6000806020838503121561199f57600080fd5b823567ffffffffffffffff8111156119b657600080fd5b6119c285828601611878565b90969095509350505050565b600080604083850312156119e157600080fd5b6119ea83611830565b946020939093013593505050565b60208082526019908201527f43616c6c6572206973206e6f7420616e206f70657261746f7200000000000000604082015260600190565b634e487b7160e01b600052604160045260246000fd5b634e487b7160e01b600052603260045260246000fd5b6020808252600f908201526e098cadccee8d040dad2e6dac2e8c6d608b1b604082015260600190565b600060208284031215611a9657600080fd5b6108db82611830565b600060208284031215611ab157600080fd5b5051919050565b600060208284031215611aca57600080fd5b815180151581146108db57600080fd5b634e487b7160e01b600052601160045260246000fd5b808202811582820484141761046557610465611ada565b60005b83811015611b22578181015183820152602001611b0a565b50506000910152565b60008151808452611b43816020860160208601611b07565b601f01601f19169290920160200192915050565b60018060a01b0385168152836020820152608060408201526000611b7e6080830185611b2b565b905060ff8316606083015295945050505050565b7f416363657373436f6e74726f6c3a206163636f756e7420000000000000000000815260008351611bca816017850160208801611b07565b7001034b99036b4b9b9b4b733903937b6329607d1b6017918401918201528351611bfb816028840160208801611b07565b01602801949350505050565b6020815260006108db6020830184611b2b565b8181038181111561046557610465611ada565b634e487b7160e01b600052603160045260246000fd5b8082018082111561046557610465611ada565b600081611c6557611c65611ada565b50600019019056fe97667070c54ef182b0f5858b034beac1b6f3089aa2d3188bb1e8929f4fa9b929a26469706673582212203e1672159d94f5308aa8105ef2dbcb72da0c06cf121b1d771d3a8db94152ebe764736f6c634300081c003397667070c54ef182b0f5858b034beac1b6f3089aa2d3188bb1e8929f4fa9b929", + "deployedBytecode": "0x6080604052600436106101395760003560e01c80639010d07c116100ab578063bec3fa171161006f578063bec3fa171461036a578063ca15c8731461038a578063d547741f146103aa578063eec8e967146103ca578063f5b541a6146103ea578063f9081ba21461040c57600080fd5b80639010d07c146102d557806391d14854146102f5578063998cdf8314610315578063a217fddf14610335578063ab384df01461034a57600080fd5b8063248a9ca3116100fd578063248a9ca31461020b57806328d10eae146102495780632f2ff15d1461025f57806336568abe1461027f57806382b12dd71461029f578063868a7505146102b557600080fd5b806301ffc9a7146101455780630ac2b2981461017a578063110c1a411461019c57806316d5fd0e146101d45780631a765057146101e957600080fd5b3661014057005b600080fd5b34801561015157600080fd5b506101656101603660046117a1565b610440565b60405190151581526020015b60405180910390f35b34801561018657600080fd5b5061019a6101953660046117cb565b61046b565b005b3480156101a857600080fd5b506101bc6101b73660046117cb565b6104b4565b6040516001600160a01b039091168152602001610171565b3480156101e057600080fd5b5061019a6104de565b3480156101f557600080fd5b506101fe610680565b60405161017191906117e4565b34801561021757600080fd5b5061023b6102263660046117cb565b60009081526020819052604090206001015490565b604051908152602001610171565b34801561025557600080fd5b5061023b60045481565b34801561026b57600080fd5b5061019a61027a36600461184c565b6106e2565b34801561028b57600080fd5b5061019a61029a36600461184c565b610708565b3480156102ab57600080fd5b5061023b60035481565b3480156102c157600080fd5b5061019a6102d03660046118c4565b610786565b3480156102e157600080fd5b506101bc6102f036600461196a565b6108c3565b34801561030157600080fd5b5061016561031036600461184c565b6108e2565b34801561032157600080fd5b5061019a6103303660046117cb565b61090b565b34801561034157600080fd5b5061023b600081565b34801561035657600080fd5b5061019a61036536600461198c565b610948565b34801561037657600080fd5b5061019a6103853660046119ce565b6109be565b34801561039657600080fd5b5061023b6103a53660046117cb565b610bbb565b3480156103b657600080fd5b5061019a6103c536600461184c565b610bd2565b3480156103d657600080fd5b5061019a6103e536600461198c565b610bf8565b3480156103f657600080fd5b5061023b600080516020611c6e83398151915281565b34801561041857600080fd5b506101bc7f000000000000000000000000000000000000000000000000000000000000000081565b60006001600160e01b03198216635a05180f60e01b1480610465575061046582610c6e565b92915050565b610483600080516020611c6e833981519152336108e2565b6104a85760405162461bcd60e51b815260040161049f906119f8565b60405180910390fd5b6104b181610ca3565b50565b600281815481106104c457600080fd5b6000918252602090912001546001600160a01b0316905081565b6104f6600080516020611c6e833981519152336108e2565b6105125760405162461bcd60e51b815260040161049f906119f8565b60025460009067ffffffffffffffff81111561053057610530611a2f565b604051908082528060200260200182016040528015610559578160200160208202803683370190505b5060025490915060009067ffffffffffffffff81111561057b5761057b611a2f565b6040519080825280602002602001820160405280156105a4578160200160208202803683370190505b5060025490915060009067ffffffffffffffff8111156105c6576105c6611a2f565b6040519080825280602002602001820160405280156105ef578160200160208202803683370190505b50905060005b60025481101561066f5760001984828151811061061457610614611a45565b602002602001018181525050600183828151811061063457610634611a45565b602002602001019060ff16908160ff1681525050600082828151811061065c5761065c611a45565b60209081029190910101526001016105f5565b5061067b838383610d27565b505050565b606060028054806020026020016040519081016040528092919081815260200182805480156106d857602002820191906000526020600020905b81546001600160a01b031681526001909101906020018083116106ba575b5050505050905090565b6000828152602081905260409020600101546106fe8133610f84565b61067b8383610fe8565b6001600160a01b03811633146107785760405162461bcd60e51b815260206004820152602f60248201527f416363657373436f6e74726f6c3a2063616e206f6e6c792072656e6f756e636560448201526e103937b632b9903337b91039b2b63360891b606482015260840161049f565b610782828261100a565b5050565b61079e600080516020611c6e833981519152336108e2565b6107ba5760405162461bcd60e51b815260040161049f906119f8565b60025485146107db5760405162461bcd60e51b815260040161049f90611a5b565b60025483146107fc5760405162461bcd60e51b815260040161049f90611a5b565b600254811461081d5760405162461bcd60e51b815260040161049f90611a5b565b6108bb86868080602002602001604051908101604052809392919081815260200183836020028082843760009201919091525050604080516020808a0282810182019093528982529093508992508891829185019084908082843760009201919091525050604080516020808902828101820190935288825290935088925087918291850190849080828437600092019190915250610d2792505050565b505050505050565b60008281526001602052604081206108db908361102c565b9392505050565b6000918252602082815260408084206001600160a01b0393909316845291905290205460ff1690565b610923600080516020611c6e833981519152336108e2565b61093f5760405162461bcd60e51b815260040161049f906119f8565b6104b181611038565b610960600080516020611c6e833981519152336108e2565b61097c5760405162461bcd60e51b815260040161049f906119f8565b60005b8181101561067b576109b683838381811061099c5761099c611a45565b90506020020160208101906109b19190611a84565b6110ba565b60010161097f565b336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614610a365760405162461bcd60e51b815260206004820152601f60248201527f43616c6c6572206973206e6f7420746865207361666520636f6e747261637400604482015260640161049f565b6001600160a01b038216610aad5760008111610a525747610a54565b805b6040519091506001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169082156108fc029083906000818181858888f1935050505015801561067b573d6000803e3d6000fd5b60008111610b22576040516370a0823160e01b81523060048201526001600160a01b038316906370a0823190602401602060405180830381865afa158015610af9573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b1d9190611a9f565b610b24565b805b60405163a9059cbb60e01b81526001600160a01b037f000000000000000000000000000000000000000000000000000000000000000081166004830152602482018390529192509083169063a9059cbb906044016020604051808303816000875af1158015610b97573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061067b9190611ab8565b60008181526001602052604081206104659061122f565b600082815260208190526040902060010154610bee8133610f84565b61067b838361100a565b610c10600080516020611c6e833981519152336108e2565b610c2c5760405162461bcd60e51b815260040161049f906119f8565b60005b8181101561067b57610c66838383818110610c4c57610c4c611a45565b9050602002016020810190610c619190611a84565b611239565b600101610c2f565b60006001600160e01b03198216637965db0b60e01b148061046557506301ffc9a760e01b6001600160e01b0319831614610465565b62989680811115610ceb5760405162461bcd60e51b815260206004820152601260248201527108ec2e640d8d2dad2e840e8dede40d0d2ced60731b604482015260640161049f565b60048190556040518181527ffe19406854a66706284c7b3480e4c2e37dc42dc468e1211d7a1abcaf721a6f00906020015b60405180910390a150565b600254600354610d38908290611af0565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316311015610db15760405162461bcd60e51b815260206004820152601e60248201527f4e6f7420656e6f7567682045544820666f722062726964676520666565730000604482015260640161049f565b60005b81811015610f7d57600060028281548110610dd157610dd1611a45565b9060005260206000200160009054906101000a90046001600160a01b031690507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663468721a782600354636dbb501160e01b8a8781518110610e3e57610e3e611a45565b60200260200101518a8881518110610e5857610e58611a45565b60200260200101518a8981518110610e7257610e72611a45565b6020908102919091010151600454604051602481019490945260ff90921660448401526064830152608482015260a40160408051601f198184030181529181526020820180516001600160e01b03166001600160e01b03199485161790525160e086901b9092168252610eec939291600090600401611b57565b6020604051808303816000875af1158015610f0b573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f2f9190611ab8565b610f745760405162461bcd60e51b815260206004820152601660248201527513585b9859d94818d85b5c185a59db8819985a5b195960521b604482015260640161049f565b50600101610db4565b5050505050565b610f8e82826108e2565b61078257610fa6816001600160a01b03166014611386565b610fb1836020611386565b604051602001610fc2929190611b92565b60408051601f198184030181529082905262461bcd60e51b825261049f91600401611c07565b610ff28282611522565b600082815260016020526040902061067b90826115a6565b61101482826115bb565b600082815260016020526040902061067b9082611620565b60006108db8383611635565b662386f26fc100008111156110855760405162461bcd60e51b8152602060048201526013602482015272084e4d2c8ceca40cccaca40e8dede40d0d2ced606b1b604482015260640161049f565b60038190556040518181527f42dfb00d085d601e55327921154ae76c1b24270b026c5a0c51caee18eb4c401f90602001610d1c565b60025460005b818110156111f557826001600160a01b0316600282815481106110e5576110e5611a45565b6000918252602090912001546001600160a01b0316036111ed57600261110c600184611c1a565b8154811061111c5761111c611a45565b600091825260209091200154600280546001600160a01b03909216918390811061114857611148611a45565b9060005260206000200160006101000a8154816001600160a01b0302191690836001600160a01b03160217905550600280548061118757611187611c2d565b6000828152602090819020600019908301810180546001600160a01b03191690559091019091556040516001600160a01b03851681527f5acf3d4990bcb8de36e8b743ef49e57f65600af9d09b4a50da91c2061b8aca79910160405180910390a1505050565b6001016110c0565b5060405162461bcd60e51b815260206004820152600e60248201526d141bdbdb081b9bdd08199bdd5b9960921b604482015260640161049f565b6000610465825490565b6001600160a01b03811661127e5760405162461bcd60e51b815260206004820152600c60248201526b5a65726f206164647265737360a01b604482015260640161049f565b60005b60025481101561130757816001600160a01b0316600282815481106112a8576112a8611a45565b6000918252602090912001546001600160a01b0316036112ff5760405162461bcd60e51b8152602060048201526012602482015271141bdbdb08185b1c9958591e48185919195960721b604482015260640161049f565b600101611281565b50600280546001810182556000919091527f405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace0180546001600160a01b0319166001600160a01b0383169081179091556040519081527f73fbf341ebd14259e9d6b52784447ca2c15aa93c480c93927fd8135aefff774790602001610d1c565b60606000611395836002611af0565b6113a0906002611c43565b67ffffffffffffffff8111156113b8576113b8611a2f565b6040519080825280601f01601f1916602001820160405280156113e2576020820181803683370190505b509050600360fc1b816000815181106113fd576113fd611a45565b60200101906001600160f81b031916908160001a905350600f60fb1b8160018151811061142c5761142c611a45565b60200101906001600160f81b031916908160001a9053506000611450846002611af0565b61145b906001611c43565b90505b60018111156114d3576f181899199a1a9b1b9c1cb0b131b232b360811b85600f166010811061148f5761148f611a45565b1a60f81b8282815181106114a5576114a5611a45565b60200101906001600160f81b031916908160001a90535060049490941c936114cc81611c56565b905061145e565b5083156108db5760405162461bcd60e51b815260206004820181905260248201527f537472696e67733a20686578206c656e67746820696e73756666696369656e74604482015260640161049f565b61152c82826108e2565b610782576000828152602081815260408083206001600160a01b03851684529091529020805460ff191660011790556115623390565b6001600160a01b0316816001600160a01b0316837f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a45050565b60006108db836001600160a01b03841661165f565b6115c582826108e2565b15610782576000828152602081815260408083206001600160a01b0385168085529252808320805460ff1916905551339285917ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b9190a45050565b60006108db836001600160a01b0384166116ae565b600082600001828154811061164c5761164c611a45565b9060005260206000200154905092915050565b60008181526001830160205260408120546116a657508154600181810184556000848152602080822090930184905584548482528286019093526040902091909155610465565b506000610465565b600081815260018301602052604081205480156117975760006116d2600183611c1a565b85549091506000906116e690600190611c1a565b905081811461174b57600086600001828154811061170657611706611a45565b906000526020600020015490508087600001848154811061172957611729611a45565b6000918252602080832090910192909255918252600188019052604090208390555b855486908061175c5761175c611c2d565b600190038181906000526020600020016000905590558560010160008681526020019081526020016000206000905560019350505050610465565b6000915050610465565b6000602082840312156117b357600080fd5b81356001600160e01b0319811681146108db57600080fd5b6000602082840312156117dd57600080fd5b5035919050565b602080825282518282018190526000918401906040840190835b818110156118255783516001600160a01b03168352602093840193909201916001016117fe565b509095945050505050565b80356001600160a01b038116811461184757600080fd5b919050565b6000806040838503121561185f57600080fd5b8235915061186f60208401611830565b90509250929050565b60008083601f84011261188a57600080fd5b50813567ffffffffffffffff8111156118a257600080fd5b6020830191508360208260051b85010111156118bd57600080fd5b9250929050565b600080600080600080606087890312156118dd57600080fd5b863567ffffffffffffffff8111156118f457600080fd5b61190089828a01611878565b909750955050602087013567ffffffffffffffff81111561192057600080fd5b61192c89828a01611878565b909550935050604087013567ffffffffffffffff81111561194c57600080fd5b61195889828a01611878565b979a9699509497509295939492505050565b6000806040838503121561197d57600080fd5b50508035926020909101359150565b6000806020838503121561199f57600080fd5b823567ffffffffffffffff8111156119b657600080fd5b6119c285828601611878565b90969095509350505050565b600080604083850312156119e157600080fd5b6119ea83611830565b946020939093013593505050565b60208082526019908201527f43616c6c6572206973206e6f7420616e206f70657261746f7200000000000000604082015260600190565b634e487b7160e01b600052604160045260246000fd5b634e487b7160e01b600052603260045260246000fd5b6020808252600f908201526e098cadccee8d040dad2e6dac2e8c6d608b1b604082015260600190565b600060208284031215611a9657600080fd5b6108db82611830565b600060208284031215611ab157600080fd5b5051919050565b600060208284031215611aca57600080fd5b815180151581146108db57600080fd5b634e487b7160e01b600052601160045260246000fd5b808202811582820484141761046557610465611ada565b60005b83811015611b22578181015183820152602001611b0a565b50506000910152565b60008151808452611b43816020860160208601611b07565b601f01601f19169290920160200192915050565b60018060a01b0385168152836020820152608060408201526000611b7e6080830185611b2b565b905060ff8316606083015295945050505050565b7f416363657373436f6e74726f6c3a206163636f756e7420000000000000000000815260008351611bca816017850160208801611b07565b7001034b99036b4b9b9b4b733903937b6329607d1b6017918401918201528351611bfb816028840160208801611b07565b01602801949350505050565b6020815260006108db6020830184611b2b565b8181038181111561046557610465611ada565b634e487b7160e01b600052603160045260246000fd5b8082018082111561046557610465611ada565b600081611c6557611c65611ada565b50600019019056fe97667070c54ef182b0f5858b034beac1b6f3089aa2d3188bb1e8929f4fa9b929a26469706673582212203e1672159d94f5308aa8105ef2dbcb72da0c06cf121b1d771d3a8db94152ebe764736f6c634300081c0033", "libraries": {}, "devdoc": { + "author": "Origin Protocol", "events": { "RoleAdminChanged(bytes32,bytes32,bytes32)": { "details": "Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole` `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite {RoleAdminChanged} not being emitted signaling this. _Available since v3.1._" @@ -536,6 +687,25 @@ }, "kind": "dev", "methods": { + "addPoolBoosterAddress(address[])": { + "params": { + "_poolBoosters": "Addresses to add" + } + }, + "constructor": { + "params": { + "_additionalGasLimit": "Gas limit for cross-chain execution in manageCampaign", + "_bridgeFee": "ETH amount to send per pool booster for bridge fees", + "_operator": "Address authorized to call operator-restricted functions", + "_poolBoosters": "Initial list of CurvePoolBooster addresses to manage", + "_safeContract": "Address of the Gnosis Safe this module is attached to" + } + }, + "getPoolBoosters()": { + "returns": { + "_0": "Array of pool booster addresses" + } + }, "getRoleAdmin(bytes32)": { "details": "Returns the admin role that controls `role`. See {grantRole} and {revokeRole}. To change a role's admin, use {_setRoleAdmin}." }, @@ -551,12 +721,37 @@ "hasRole(bytes32,address)": { "details": "Returns `true` if `account` has been granted `role`." }, + "manageBribes()": { + "details": "Calls `manageCampaign` on each pool booster via the Safe. The Safe must hold enough ETH to cover `bridgeFee * poolBoosters.length`." + }, + "manageBribes(uint256[],uint8[],uint256[])": { + "params": { + "extraDuration": "Number of periods to extend per pool (0 = no update, 1 = +1 week)", + "rewardsPerVote": "Max reward per vote per pool (0 = no update)", + "totalRewardAmounts": "Total reward amount per pool (0 = no update, type(uint256).max = use all available)" + } + }, + "removePoolBoosterAddress(address[])": { + "params": { + "_poolBoosters": "Addresses to remove" + } + }, "renounceRole(bytes32,address)": { "details": "Revokes `role` from the calling account. Roles are often managed via {grantRole} and {revokeRole}: this function's purpose is to provide a mechanism for accounts to lose their privileges if they are compromised (such as when a trusted device is misplaced). If the calling account had been revoked `role`, emits a {RoleRevoked} event. Requirements: - the caller must be `account`." }, "revokeRole(bytes32,address)": { "details": "Revokes `role` from `account`. If `account` had been granted `role`, emits a {RoleRevoked} event. Requirements: - the caller must have ``role``'s admin role." }, + "setAdditionalGasLimit(uint256)": { + "params": { + "newGasLimit": "New gas limit value" + } + }, + "setBridgeFee(uint256)": { + "params": { + "newFee": "New bridge fee amount in wei" + } + }, "supportsInterface(bytes4)": { "details": "See {IERC165-supportsInterface}." }, @@ -568,11 +763,49 @@ } } }, + "title": "CurvePoolBoosterBribesModule", "version": 1 }, "userdoc": { + "events": { + "BridgeFeeUpdated(uint256)": { + "notice": "--- Events" + } + }, "kind": "user", - "methods": {}, + "methods": { + "addPoolBoosterAddress(address[])": { + "notice": "Add new CurvePoolBooster addresses to the managed list" + }, + "additionalGasLimit()": { + "notice": "Gas limit passed to manageCampaign for cross-chain execution" + }, + "bridgeFee()": { + "notice": "ETH amount sent per pool booster to cover the L1 -> L2 bridge fee" + }, + "getPoolBoosters()": { + "notice": "Get the full list of managed CurvePoolBooster addresses" + }, + "manageBribes()": { + "notice": "Default entry point to manage bribe campaigns for all registered pool boosters. Applies the same behavior to every pool: - totalRewardAmount = type(uint256).max → use all available reward tokens - numberOfPeriods = 1 → extend by one period (week) - maxRewardPerVote = 0 → no update" + }, + "manageBribes(uint256[],uint8[],uint256[])": { + "notice": "Fully configurable entry point to manage bribe campaigns. Allows setting reward amounts, durations, and reward rates individually for each pool. Each array must have the same length as the poolBoosters array." + }, + "poolBoosters(uint256)": { + "notice": "List of CurvePoolBooster addresses managed by this module" + }, + "removePoolBoosterAddress(address[])": { + "notice": "Remove CurvePoolBooster addresses from the managed list" + }, + "setAdditionalGasLimit(uint256)": { + "notice": "Update the additional gas limit for cross-chain execution" + }, + "setBridgeFee(uint256)": { + "notice": "Update the ETH bridge fee sent per pool booster" + } + }, + "notice": "Gnosis Safe module that automates the management of VotemarketV2 bribe campaigns across multiple CurvePoolBooster contracts. It instructs the Safe to call `manageCampaign` on each registered pool booster, forwarding ETH from the Safe's balance to cover bridge fees. Campaign parameters (reward amount, duration, reward rate) can be configured per pool or left to sensible defaults.", "version": 1 }, "storageLayout": { @@ -594,12 +827,28 @@ "type": "t_mapping(t_bytes32,t_struct(AddressSet)1182_storage)" }, { - "astId": 1658, + "astId": 1646, "contract": "contracts/automation/CurvePoolBoosterBribesModule.sol:CurvePoolBoosterBribesModule", - "label": "POOLS", + "label": "poolBoosters", "offset": 0, "slot": "2", "type": "t_array(t_address)dyn_storage" + }, + { + "astId": 1649, + "contract": "contracts/automation/CurvePoolBoosterBribesModule.sol:CurvePoolBoosterBribesModule", + "label": "bridgeFee", + "offset": 0, + "slot": "3", + "type": "t_uint256" + }, + { + "astId": 1652, + "contract": "contracts/automation/CurvePoolBoosterBribesModule.sol:CurvePoolBoosterBribesModule", + "label": "additionalGasLimit", + "offset": 0, + "slot": "4", + "type": "t_uint256" } ], "types": { diff --git a/contracts/deployments/mainnet/solcInputs/ccf1518a549ca89ba906f9e99681e2d8.json b/contracts/deployments/mainnet/solcInputs/ccf1518a549ca89ba906f9e99681e2d8.json new file mode 100644 index 0000000000..51f3f46687 --- /dev/null +++ b/contracts/deployments/mainnet/solcInputs/ccf1518a549ca89ba906f9e99681e2d8.json @@ -0,0 +1,72 @@ +{ + "language": "Solidity", + "sources": { + "@openzeppelin/contracts/access/AccessControl.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (access/AccessControl.sol)\n\npragma solidity ^0.8.0;\n\nimport \"./IAccessControl.sol\";\nimport \"../utils/Context.sol\";\nimport \"../utils/Strings.sol\";\nimport \"../utils/introspection/ERC165.sol\";\n\n/**\n * @dev Contract module that allows children to implement role-based access\n * control mechanisms. This is a lightweight version that doesn't allow enumerating role\n * members except through off-chain means by accessing the contract event logs. Some\n * applications may benefit from on-chain enumerability, for those cases see\n * {AccessControlEnumerable}.\n *\n * Roles are referred to by their `bytes32` identifier. These should be exposed\n * in the external API and be unique. The best way to achieve this is by\n * using `public constant` hash digests:\n *\n * ```\n * bytes32 public constant MY_ROLE = keccak256(\"MY_ROLE\");\n * ```\n *\n * Roles can be used to represent a set of permissions. To restrict access to a\n * function call, use {hasRole}:\n *\n * ```\n * function foo() public {\n * require(hasRole(MY_ROLE, msg.sender));\n * ...\n * }\n * ```\n *\n * Roles can be granted and revoked dynamically via the {grantRole} and\n * {revokeRole} functions. Each role has an associated admin role, and only\n * accounts that have a role's admin role can call {grantRole} and {revokeRole}.\n *\n * By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means\n * that only accounts with this role will be able to grant or revoke other\n * roles. More complex role relationships can be created by using\n * {_setRoleAdmin}.\n *\n * WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to\n * grant and revoke this role. Extra precautions should be taken to secure\n * accounts that have been granted it.\n */\nabstract contract AccessControl is Context, IAccessControl, ERC165 {\n struct RoleData {\n mapping(address => bool) members;\n bytes32 adminRole;\n }\n\n mapping(bytes32 => RoleData) private _roles;\n\n bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;\n\n /**\n * @dev Modifier that checks that an account has a specific role. Reverts\n * with a standardized message including the required role.\n *\n * The format of the revert reason is given by the following regular expression:\n *\n * /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/\n *\n * _Available since v4.1._\n */\n modifier onlyRole(bytes32 role) {\n _checkRole(role, _msgSender());\n _;\n }\n\n /**\n * @dev See {IERC165-supportsInterface}.\n */\n function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {\n return interfaceId == type(IAccessControl).interfaceId || super.supportsInterface(interfaceId);\n }\n\n /**\n * @dev Returns `true` if `account` has been granted `role`.\n */\n function hasRole(bytes32 role, address account) public view override returns (bool) {\n return _roles[role].members[account];\n }\n\n /**\n * @dev Revert with a standard message if `account` is missing `role`.\n *\n * The format of the revert reason is given by the following regular expression:\n *\n * /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/\n */\n function _checkRole(bytes32 role, address account) internal view {\n if (!hasRole(role, account)) {\n revert(\n string(\n abi.encodePacked(\n \"AccessControl: account \",\n Strings.toHexString(uint160(account), 20),\n \" is missing role \",\n Strings.toHexString(uint256(role), 32)\n )\n )\n );\n }\n }\n\n /**\n * @dev Returns the admin role that controls `role`. See {grantRole} and\n * {revokeRole}.\n *\n * To change a role's admin, use {_setRoleAdmin}.\n */\n function getRoleAdmin(bytes32 role) public view override returns (bytes32) {\n return _roles[role].adminRole;\n }\n\n /**\n * @dev Grants `role` to `account`.\n *\n * If `account` had not been already granted `role`, emits a {RoleGranted}\n * event.\n *\n * Requirements:\n *\n * - the caller must have ``role``'s admin role.\n */\n function grantRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {\n _grantRole(role, account);\n }\n\n /**\n * @dev Revokes `role` from `account`.\n *\n * If `account` had been granted `role`, emits a {RoleRevoked} event.\n *\n * Requirements:\n *\n * - the caller must have ``role``'s admin role.\n */\n function revokeRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {\n _revokeRole(role, account);\n }\n\n /**\n * @dev Revokes `role` from the calling account.\n *\n * Roles are often managed via {grantRole} and {revokeRole}: this function's\n * purpose is to provide a mechanism for accounts to lose their privileges\n * if they are compromised (such as when a trusted device is misplaced).\n *\n * If the calling account had been revoked `role`, emits a {RoleRevoked}\n * event.\n *\n * Requirements:\n *\n * - the caller must be `account`.\n */\n function renounceRole(bytes32 role, address account) public virtual override {\n require(account == _msgSender(), \"AccessControl: can only renounce roles for self\");\n\n _revokeRole(role, account);\n }\n\n /**\n * @dev Grants `role` to `account`.\n *\n * If `account` had not been already granted `role`, emits a {RoleGranted}\n * event. Note that unlike {grantRole}, this function doesn't perform any\n * checks on the calling account.\n *\n * [WARNING]\n * ====\n * This function should only be called from the constructor when setting\n * up the initial roles for the system.\n *\n * Using this function in any other way is effectively circumventing the admin\n * system imposed by {AccessControl}.\n * ====\n *\n * NOTE: This function is deprecated in favor of {_grantRole}.\n */\n function _setupRole(bytes32 role, address account) internal virtual {\n _grantRole(role, account);\n }\n\n /**\n * @dev Sets `adminRole` as ``role``'s admin role.\n *\n * Emits a {RoleAdminChanged} event.\n */\n function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {\n bytes32 previousAdminRole = getRoleAdmin(role);\n _roles[role].adminRole = adminRole;\n emit RoleAdminChanged(role, previousAdminRole, adminRole);\n }\n\n /**\n * @dev Grants `role` to `account`.\n *\n * Internal function without access restriction.\n */\n function _grantRole(bytes32 role, address account) internal virtual {\n if (!hasRole(role, account)) {\n _roles[role].members[account] = true;\n emit RoleGranted(role, account, _msgSender());\n }\n }\n\n /**\n * @dev Revokes `role` from `account`.\n *\n * Internal function without access restriction.\n */\n function _revokeRole(bytes32 role, address account) internal virtual {\n if (hasRole(role, account)) {\n _roles[role].members[account] = false;\n emit RoleRevoked(role, account, _msgSender());\n }\n }\n}\n" + }, + "@openzeppelin/contracts/access/AccessControlEnumerable.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (access/AccessControlEnumerable.sol)\n\npragma solidity ^0.8.0;\n\nimport \"./IAccessControlEnumerable.sol\";\nimport \"./AccessControl.sol\";\nimport \"../utils/structs/EnumerableSet.sol\";\n\n/**\n * @dev Extension of {AccessControl} that allows enumerating the members of each role.\n */\nabstract contract AccessControlEnumerable is IAccessControlEnumerable, AccessControl {\n using EnumerableSet for EnumerableSet.AddressSet;\n\n mapping(bytes32 => EnumerableSet.AddressSet) private _roleMembers;\n\n /**\n * @dev See {IERC165-supportsInterface}.\n */\n function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {\n return interfaceId == type(IAccessControlEnumerable).interfaceId || super.supportsInterface(interfaceId);\n }\n\n /**\n * @dev Returns one of the accounts that have `role`. `index` must be a\n * value between 0 and {getRoleMemberCount}, non-inclusive.\n *\n * Role bearers are not sorted in any particular way, and their ordering may\n * change at any point.\n *\n * WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure\n * you perform all queries on the same block. See the following\n * https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post]\n * for more information.\n */\n function getRoleMember(bytes32 role, uint256 index) public view override returns (address) {\n return _roleMembers[role].at(index);\n }\n\n /**\n * @dev Returns the number of accounts that have `role`. Can be used\n * together with {getRoleMember} to enumerate all bearers of a role.\n */\n function getRoleMemberCount(bytes32 role) public view override returns (uint256) {\n return _roleMembers[role].length();\n }\n\n /**\n * @dev Overload {_grantRole} to track enumerable memberships\n */\n function _grantRole(bytes32 role, address account) internal virtual override {\n super._grantRole(role, account);\n _roleMembers[role].add(account);\n }\n\n /**\n * @dev Overload {_revokeRole} to track enumerable memberships\n */\n function _revokeRole(bytes32 role, address account) internal virtual override {\n super._revokeRole(role, account);\n _roleMembers[role].remove(account);\n }\n}\n" + }, + "@openzeppelin/contracts/access/IAccessControl.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (access/IAccessControl.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev External interface of AccessControl declared to support ERC165 detection.\n */\ninterface IAccessControl {\n /**\n * @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`\n *\n * `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite\n * {RoleAdminChanged} not being emitted signaling this.\n *\n * _Available since v3.1._\n */\n event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);\n\n /**\n * @dev Emitted when `account` is granted `role`.\n *\n * `sender` is the account that originated the contract call, an admin role\n * bearer except when using {AccessControl-_setupRole}.\n */\n event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);\n\n /**\n * @dev Emitted when `account` is revoked `role`.\n *\n * `sender` is the account that originated the contract call:\n * - if using `revokeRole`, it is the admin role bearer\n * - if using `renounceRole`, it is the role bearer (i.e. `account`)\n */\n event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);\n\n /**\n * @dev Returns `true` if `account` has been granted `role`.\n */\n function hasRole(bytes32 role, address account) external view returns (bool);\n\n /**\n * @dev Returns the admin role that controls `role`. See {grantRole} and\n * {revokeRole}.\n *\n * To change a role's admin, use {AccessControl-_setRoleAdmin}.\n */\n function getRoleAdmin(bytes32 role) external view returns (bytes32);\n\n /**\n * @dev Grants `role` to `account`.\n *\n * If `account` had not been already granted `role`, emits a {RoleGranted}\n * event.\n *\n * Requirements:\n *\n * - the caller must have ``role``'s admin role.\n */\n function grantRole(bytes32 role, address account) external;\n\n /**\n * @dev Revokes `role` from `account`.\n *\n * If `account` had been granted `role`, emits a {RoleRevoked} event.\n *\n * Requirements:\n *\n * - the caller must have ``role``'s admin role.\n */\n function revokeRole(bytes32 role, address account) external;\n\n /**\n * @dev Revokes `role` from the calling account.\n *\n * Roles are often managed via {grantRole} and {revokeRole}: this function's\n * purpose is to provide a mechanism for accounts to lose their privileges\n * if they are compromised (such as when a trusted device is misplaced).\n *\n * If the calling account had been granted `role`, emits a {RoleRevoked}\n * event.\n *\n * Requirements:\n *\n * - the caller must be `account`.\n */\n function renounceRole(bytes32 role, address account) external;\n}\n" + }, + "@openzeppelin/contracts/access/IAccessControlEnumerable.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (access/IAccessControlEnumerable.sol)\n\npragma solidity ^0.8.0;\n\nimport \"./IAccessControl.sol\";\n\n/**\n * @dev External interface of AccessControlEnumerable declared to support ERC165 detection.\n */\ninterface IAccessControlEnumerable is IAccessControl {\n /**\n * @dev Returns one of the accounts that have `role`. `index` must be a\n * value between 0 and {getRoleMemberCount}, non-inclusive.\n *\n * Role bearers are not sorted in any particular way, and their ordering may\n * change at any point.\n *\n * WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure\n * you perform all queries on the same block. See the following\n * https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post]\n * for more information.\n */\n function getRoleMember(bytes32 role, uint256 index) external view returns (address);\n\n /**\n * @dev Returns the number of accounts that have `role`. Can be used\n * together with {getRoleMember} to enumerate all bearers of a role.\n */\n function getRoleMemberCount(bytes32 role) external view returns (uint256);\n}\n" + }, + "@openzeppelin/contracts/token/ERC20/IERC20.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (token/ERC20/IERC20.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Interface of the ERC20 standard as defined in the EIP.\n */\ninterface IERC20 {\n /**\n * @dev Returns the amount of tokens in existence.\n */\n function totalSupply() external view returns (uint256);\n\n /**\n * @dev Returns the amount of tokens owned by `account`.\n */\n function balanceOf(address account) external view returns (uint256);\n\n /**\n * @dev Moves `amount` tokens from the caller's account to `recipient`.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transfer(address recipient, uint256 amount) external returns (bool);\n\n /**\n * @dev Returns the remaining number of tokens that `spender` will be\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\n * zero by default.\n *\n * This value changes when {approve} or {transferFrom} are called.\n */\n function allowance(address owner, address spender) external view returns (uint256);\n\n /**\n * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\n * that someone may use both the old and the new allowance by unfortunate\n * transaction ordering. One possible solution to mitigate this race\n * condition is to first reduce the spender's allowance to 0 and set the\n * desired value afterwards:\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\n *\n * Emits an {Approval} event.\n */\n function approve(address spender, uint256 amount) external returns (bool);\n\n /**\n * @dev Moves `amount` tokens from `sender` to `recipient` using the\n * allowance mechanism. `amount` is then deducted from the caller's\n * allowance.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transferFrom(\n address sender,\n address recipient,\n uint256 amount\n ) external returns (bool);\n\n /**\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\n * another (`to`).\n *\n * Note that `value` may be zero.\n */\n event Transfer(address indexed from, address indexed to, uint256 value);\n\n /**\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\n * a call to {approve}. `value` is the new allowance.\n */\n event Approval(address indexed owner, address indexed spender, uint256 value);\n}\n" + }, + "@openzeppelin/contracts/utils/Context.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Provides information about the current execution context, including the\n * sender of the transaction and its data. While these are generally available\n * via msg.sender and msg.data, they should not be accessed in such a direct\n * manner, since when dealing with meta-transactions the account sending and\n * paying for execution may not be the actual sender (as far as an application\n * is concerned).\n *\n * This contract is only required for intermediate, library-like contracts.\n */\nabstract contract Context {\n function _msgSender() internal view virtual returns (address) {\n return msg.sender;\n }\n\n function _msgData() internal view virtual returns (bytes calldata) {\n return msg.data;\n }\n}\n" + }, + "@openzeppelin/contracts/utils/introspection/ERC165.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol)\n\npragma solidity ^0.8.0;\n\nimport \"./IERC165.sol\";\n\n/**\n * @dev Implementation of the {IERC165} interface.\n *\n * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check\n * for the additional interface id that will be supported. For example:\n *\n * ```solidity\n * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {\n * return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);\n * }\n * ```\n *\n * Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation.\n */\nabstract contract ERC165 is IERC165 {\n /**\n * @dev See {IERC165-supportsInterface}.\n */\n function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {\n return interfaceId == type(IERC165).interfaceId;\n }\n}\n" + }, + "@openzeppelin/contracts/utils/introspection/IERC165.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Interface of the ERC165 standard, as defined in the\n * https://eips.ethereum.org/EIPS/eip-165[EIP].\n *\n * Implementers can declare support of contract interfaces, which can then be\n * queried by others ({ERC165Checker}).\n *\n * For an implementation, see {ERC165}.\n */\ninterface IERC165 {\n /**\n * @dev Returns true if this contract implements the interface defined by\n * `interfaceId`. See the corresponding\n * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]\n * to learn more about how these ids are created.\n *\n * This function call must use less than 30 000 gas.\n */\n function supportsInterface(bytes4 interfaceId) external view returns (bool);\n}\n" + }, + "@openzeppelin/contracts/utils/Strings.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (utils/Strings.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev String operations.\n */\nlibrary Strings {\n bytes16 private constant _HEX_SYMBOLS = \"0123456789abcdef\";\n\n /**\n * @dev Converts a `uint256` to its ASCII `string` decimal representation.\n */\n function toString(uint256 value) internal pure returns (string memory) {\n // Inspired by OraclizeAPI's implementation - MIT licence\n // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol\n\n if (value == 0) {\n return \"0\";\n }\n uint256 temp = value;\n uint256 digits;\n while (temp != 0) {\n digits++;\n temp /= 10;\n }\n bytes memory buffer = new bytes(digits);\n while (value != 0) {\n digits -= 1;\n buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));\n value /= 10;\n }\n return string(buffer);\n }\n\n /**\n * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.\n */\n function toHexString(uint256 value) internal pure returns (string memory) {\n if (value == 0) {\n return \"0x00\";\n }\n uint256 temp = value;\n uint256 length = 0;\n while (temp != 0) {\n length++;\n temp >>= 8;\n }\n return toHexString(value, length);\n }\n\n /**\n * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.\n */\n function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {\n bytes memory buffer = new bytes(2 * length + 2);\n buffer[0] = \"0\";\n buffer[1] = \"x\";\n for (uint256 i = 2 * length + 1; i > 1; --i) {\n buffer[i] = _HEX_SYMBOLS[value & 0xf];\n value >>= 4;\n }\n require(value == 0, \"Strings: hex length insufficient\");\n return string(buffer);\n }\n}\n" + }, + "@openzeppelin/contracts/utils/structs/EnumerableSet.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (utils/structs/EnumerableSet.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Library for managing\n * https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive\n * types.\n *\n * Sets have the following properties:\n *\n * - Elements are added, removed, and checked for existence in constant time\n * (O(1)).\n * - Elements are enumerated in O(n). No guarantees are made on the ordering.\n *\n * ```\n * contract Example {\n * // Add the library methods\n * using EnumerableSet for EnumerableSet.AddressSet;\n *\n * // Declare a set state variable\n * EnumerableSet.AddressSet private mySet;\n * }\n * ```\n *\n * As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)\n * and `uint256` (`UintSet`) are supported.\n */\nlibrary EnumerableSet {\n // To implement this library for multiple types with as little code\n // repetition as possible, we write it in terms of a generic Set type with\n // bytes32 values.\n // The Set implementation uses private functions, and user-facing\n // implementations (such as AddressSet) are just wrappers around the\n // underlying Set.\n // This means that we can only create new EnumerableSets for types that fit\n // in bytes32.\n\n struct Set {\n // Storage of set values\n bytes32[] _values;\n // Position of the value in the `values` array, plus 1 because index 0\n // means a value is not in the set.\n mapping(bytes32 => uint256) _indexes;\n }\n\n /**\n * @dev Add a value to a set. O(1).\n *\n * Returns true if the value was added to the set, that is if it was not\n * already present.\n */\n function _add(Set storage set, bytes32 value) private returns (bool) {\n if (!_contains(set, value)) {\n set._values.push(value);\n // The value is stored at length-1, but we add 1 to all indexes\n // and use 0 as a sentinel value\n set._indexes[value] = set._values.length;\n return true;\n } else {\n return false;\n }\n }\n\n /**\n * @dev Removes a value from a set. O(1).\n *\n * Returns true if the value was removed from the set, that is if it was\n * present.\n */\n function _remove(Set storage set, bytes32 value) private returns (bool) {\n // We read and store the value's index to prevent multiple reads from the same storage slot\n uint256 valueIndex = set._indexes[value];\n\n if (valueIndex != 0) {\n // Equivalent to contains(set, value)\n // To delete an element from the _values array in O(1), we swap the element to delete with the last one in\n // the array, and then remove the last element (sometimes called as 'swap and pop').\n // This modifies the order of the array, as noted in {at}.\n\n uint256 toDeleteIndex = valueIndex - 1;\n uint256 lastIndex = set._values.length - 1;\n\n if (lastIndex != toDeleteIndex) {\n bytes32 lastvalue = set._values[lastIndex];\n\n // Move the last value to the index where the value to delete is\n set._values[toDeleteIndex] = lastvalue;\n // Update the index for the moved value\n set._indexes[lastvalue] = valueIndex; // Replace lastvalue's index to valueIndex\n }\n\n // Delete the slot where the moved value was stored\n set._values.pop();\n\n // Delete the index for the deleted slot\n delete set._indexes[value];\n\n return true;\n } else {\n return false;\n }\n }\n\n /**\n * @dev Returns true if the value is in the set. O(1).\n */\n function _contains(Set storage set, bytes32 value) private view returns (bool) {\n return set._indexes[value] != 0;\n }\n\n /**\n * @dev Returns the number of values on the set. O(1).\n */\n function _length(Set storage set) private view returns (uint256) {\n return set._values.length;\n }\n\n /**\n * @dev Returns the value stored at position `index` in the set. O(1).\n *\n * Note that there are no guarantees on the ordering of values inside the\n * array, and it may change when more values are added or removed.\n *\n * Requirements:\n *\n * - `index` must be strictly less than {length}.\n */\n function _at(Set storage set, uint256 index) private view returns (bytes32) {\n return set._values[index];\n }\n\n /**\n * @dev Return the entire set in an array\n *\n * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed\n * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that\n * this function has an unbounded cost, and using it as part of a state-changing function may render the function\n * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.\n */\n function _values(Set storage set) private view returns (bytes32[] memory) {\n return set._values;\n }\n\n // Bytes32Set\n\n struct Bytes32Set {\n Set _inner;\n }\n\n /**\n * @dev Add a value to a set. O(1).\n *\n * Returns true if the value was added to the set, that is if it was not\n * already present.\n */\n function add(Bytes32Set storage set, bytes32 value) internal returns (bool) {\n return _add(set._inner, value);\n }\n\n /**\n * @dev Removes a value from a set. O(1).\n *\n * Returns true if the value was removed from the set, that is if it was\n * present.\n */\n function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) {\n return _remove(set._inner, value);\n }\n\n /**\n * @dev Returns true if the value is in the set. O(1).\n */\n function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {\n return _contains(set._inner, value);\n }\n\n /**\n * @dev Returns the number of values in the set. O(1).\n */\n function length(Bytes32Set storage set) internal view returns (uint256) {\n return _length(set._inner);\n }\n\n /**\n * @dev Returns the value stored at position `index` in the set. O(1).\n *\n * Note that there are no guarantees on the ordering of values inside the\n * array, and it may change when more values are added or removed.\n *\n * Requirements:\n *\n * - `index` must be strictly less than {length}.\n */\n function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {\n return _at(set._inner, index);\n }\n\n /**\n * @dev Return the entire set in an array\n *\n * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed\n * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that\n * this function has an unbounded cost, and using it as part of a state-changing function may render the function\n * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.\n */\n function values(Bytes32Set storage set) internal view returns (bytes32[] memory) {\n return _values(set._inner);\n }\n\n // AddressSet\n\n struct AddressSet {\n Set _inner;\n }\n\n /**\n * @dev Add a value to a set. O(1).\n *\n * Returns true if the value was added to the set, that is if it was not\n * already present.\n */\n function add(AddressSet storage set, address value) internal returns (bool) {\n return _add(set._inner, bytes32(uint256(uint160(value))));\n }\n\n /**\n * @dev Removes a value from a set. O(1).\n *\n * Returns true if the value was removed from the set, that is if it was\n * present.\n */\n function remove(AddressSet storage set, address value) internal returns (bool) {\n return _remove(set._inner, bytes32(uint256(uint160(value))));\n }\n\n /**\n * @dev Returns true if the value is in the set. O(1).\n */\n function contains(AddressSet storage set, address value) internal view returns (bool) {\n return _contains(set._inner, bytes32(uint256(uint160(value))));\n }\n\n /**\n * @dev Returns the number of values in the set. O(1).\n */\n function length(AddressSet storage set) internal view returns (uint256) {\n return _length(set._inner);\n }\n\n /**\n * @dev Returns the value stored at position `index` in the set. O(1).\n *\n * Note that there are no guarantees on the ordering of values inside the\n * array, and it may change when more values are added or removed.\n *\n * Requirements:\n *\n * - `index` must be strictly less than {length}.\n */\n function at(AddressSet storage set, uint256 index) internal view returns (address) {\n return address(uint160(uint256(_at(set._inner, index))));\n }\n\n /**\n * @dev Return the entire set in an array\n *\n * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed\n * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that\n * this function has an unbounded cost, and using it as part of a state-changing function may render the function\n * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.\n */\n function values(AddressSet storage set) internal view returns (address[] memory) {\n bytes32[] memory store = _values(set._inner);\n address[] memory result;\n\n assembly {\n result := store\n }\n\n return result;\n }\n\n // UintSet\n\n struct UintSet {\n Set _inner;\n }\n\n /**\n * @dev Add a value to a set. O(1).\n *\n * Returns true if the value was added to the set, that is if it was not\n * already present.\n */\n function add(UintSet storage set, uint256 value) internal returns (bool) {\n return _add(set._inner, bytes32(value));\n }\n\n /**\n * @dev Removes a value from a set. O(1).\n *\n * Returns true if the value was removed from the set, that is if it was\n * present.\n */\n function remove(UintSet storage set, uint256 value) internal returns (bool) {\n return _remove(set._inner, bytes32(value));\n }\n\n /**\n * @dev Returns true if the value is in the set. O(1).\n */\n function contains(UintSet storage set, uint256 value) internal view returns (bool) {\n return _contains(set._inner, bytes32(value));\n }\n\n /**\n * @dev Returns the number of values on the set. O(1).\n */\n function length(UintSet storage set) internal view returns (uint256) {\n return _length(set._inner);\n }\n\n /**\n * @dev Returns the value stored at position `index` in the set. O(1).\n *\n * Note that there are no guarantees on the ordering of values inside the\n * array, and it may change when more values are added or removed.\n *\n * Requirements:\n *\n * - `index` must be strictly less than {length}.\n */\n function at(UintSet storage set, uint256 index) internal view returns (uint256) {\n return uint256(_at(set._inner, index));\n }\n\n /**\n * @dev Return the entire set in an array\n *\n * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed\n * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that\n * this function has an unbounded cost, and using it as part of a state-changing function may render the function\n * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.\n */\n function values(UintSet storage set) internal view returns (uint256[] memory) {\n bytes32[] memory store = _values(set._inner);\n uint256[] memory result;\n\n assembly {\n result := store\n }\n\n return result;\n }\n}\n" + }, + "contracts/automation/AbstractSafeModule.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { AccessControlEnumerable } from \"@openzeppelin/contracts/access/AccessControlEnumerable.sol\";\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport { ISafe } from \"../interfaces/ISafe.sol\";\n\nabstract contract AbstractSafeModule is AccessControlEnumerable {\n ISafe public immutable safeContract;\n\n bytes32 public constant OPERATOR_ROLE = keccak256(\"OPERATOR_ROLE\");\n\n modifier onlySafe() {\n require(\n msg.sender == address(safeContract),\n \"Caller is not the safe contract\"\n );\n _;\n }\n\n modifier onlyOperator() {\n require(\n hasRole(OPERATOR_ROLE, msg.sender),\n \"Caller is not an operator\"\n );\n _;\n }\n\n constructor(address _safeContract) {\n safeContract = ISafe(_safeContract);\n _grantRole(DEFAULT_ADMIN_ROLE, address(safeContract));\n _grantRole(OPERATOR_ROLE, address(safeContract));\n }\n\n /**\n * @dev Helps recovering any tokens accidentally sent to this module.\n * @param token Token to transfer. 0x0 to transfer Native token.\n * @param amount Amount to transfer. 0 to transfer all balance.\n */\n function transferTokens(address token, uint256 amount) external onlySafe {\n if (address(token) == address(0)) {\n // Move ETH\n amount = amount > 0 ? amount : address(this).balance;\n payable(address(safeContract)).transfer(amount);\n return;\n }\n\n // Move all balance if amount set to 0\n amount = amount > 0 ? amount : IERC20(token).balanceOf(address(this));\n\n // Transfer to Safe contract\n // slither-disable-next-line unchecked-transfer unused-return\n IERC20(token).transfer(address(safeContract), amount);\n }\n\n receive() external payable {\n // Accept ETH to pay for bridge fees\n }\n}\n" + }, + "contracts/automation/CurvePoolBoosterBribesModule.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { AbstractSafeModule } from \"./AbstractSafeModule.sol\";\n\ninterface ICurvePoolBooster {\n function manageCampaign(\n uint256 totalRewardAmount,\n uint8 numberOfPeriods,\n uint256 maxRewardPerVote,\n uint256 additionalGasLimit\n ) external payable;\n}\n\n/// @title CurvePoolBoosterBribesModule\n/// @author Origin Protocol\n/// @notice Gnosis Safe module that automates the management of VotemarketV2 bribe campaigns\n/// across multiple CurvePoolBooster contracts. It instructs the Safe to call `manageCampaign`\n/// on each registered pool booster, forwarding ETH from the Safe's balance to cover\n/// bridge fees. Campaign parameters (reward amount, duration, reward rate) can be\n/// configured per pool or left to sensible defaults.\ncontract CurvePoolBoosterBribesModule is AbstractSafeModule {\n ////////////////////////////////////////////////////\n /// --- Storage\n ////////////////////////////////////////////////////\n\n /// @notice List of CurvePoolBooster addresses managed by this module\n address[] public poolBoosters;\n\n /// @notice ETH amount sent per pool booster to cover the L1 -> L2 bridge fee\n uint256 public bridgeFee;\n\n /// @notice Gas limit passed to manageCampaign for cross-chain execution\n uint256 public additionalGasLimit;\n\n ////////////////////////////////////////////////////\n /// --- Events\n ////////////////////////////////////////////////////\n\n event BridgeFeeUpdated(uint256 newFee);\n event AdditionalGasLimitUpdated(uint256 newGasLimit);\n event PoolBoosterAddressAdded(address pool);\n event PoolBoosterAddressRemoved(address pool);\n\n ////////////////////////////////////////////////////\n /// --- Constructor\n ////////////////////////////////////////////////////\n\n /// @param _safeContract Address of the Gnosis Safe this module is attached to\n /// @param _operator Address authorized to call operator-restricted functions\n /// @param _poolBoosters Initial list of CurvePoolBooster addresses to manage\n /// @param _bridgeFee ETH amount to send per pool booster for bridge fees\n /// @param _additionalGasLimit Gas limit for cross-chain execution in manageCampaign\n constructor(\n address _safeContract,\n address _operator,\n address[] memory _poolBoosters,\n uint256 _bridgeFee,\n uint256 _additionalGasLimit\n ) AbstractSafeModule(_safeContract) {\n _grantRole(OPERATOR_ROLE, _operator);\n for (uint256 i = 0; i < _poolBoosters.length; i++) {\n _addPoolBoosterAddress(_poolBoosters[i]);\n }\n _setBridgeFee(_bridgeFee);\n _setAdditionalGasLimit(_additionalGasLimit);\n }\n\n ////////////////////////////////////////////////////\n /// --- External Mutative Functions\n ////////////////////////////////////////////////////\n\n /// @notice Add new CurvePoolBooster addresses to the managed list\n /// @param _poolBoosters Addresses to add\n function addPoolBoosterAddress(address[] calldata _poolBoosters)\n external\n onlyOperator\n {\n for (uint256 i = 0; i < _poolBoosters.length; i++) {\n _addPoolBoosterAddress(_poolBoosters[i]);\n }\n }\n\n /// @notice Remove CurvePoolBooster addresses from the managed list\n /// @param _poolBoosters Addresses to remove\n function removePoolBoosterAddress(address[] calldata _poolBoosters)\n external\n onlyOperator\n {\n for (uint256 i = 0; i < _poolBoosters.length; i++) {\n _removePoolBoosterAddress(_poolBoosters[i]);\n }\n }\n\n /// @notice Update the ETH bridge fee sent per pool booster\n /// @param newFee New bridge fee amount in wei\n function setBridgeFee(uint256 newFee) external onlyOperator {\n _setBridgeFee(newFee);\n }\n\n /// @notice Update the additional gas limit for cross-chain execution\n /// @param newGasLimit New gas limit value\n function setAdditionalGasLimit(uint256 newGasLimit) external onlyOperator {\n _setAdditionalGasLimit(newGasLimit);\n }\n\n /// @notice Default entry point to manage bribe campaigns for all registered pool boosters.\n /// Applies the same behavior to every pool:\n /// - totalRewardAmount = type(uint256).max → use all available reward tokens\n /// - numberOfPeriods = 1 → extend by one period (week)\n /// - maxRewardPerVote = 0 → no update\n /// @dev Calls `manageCampaign` on each pool booster via the Safe. The Safe must hold\n /// enough ETH to cover `bridgeFee * poolBoosters.length`.\n function manageBribes() external onlyOperator {\n uint256[] memory totalRewardAmounts = new uint256[](\n poolBoosters.length\n );\n uint8[] memory extraDuration = new uint8[](poolBoosters.length);\n uint256[] memory rewardsPerVote = new uint256[](poolBoosters.length);\n for (uint256 i = 0; i < poolBoosters.length; i++) {\n totalRewardAmounts[i] = type(uint256).max; // use all available rewards\n extraDuration[i] = 1; // extend by 1 period (week)\n rewardsPerVote[i] = 0; // no update to maxRewardPerVote\n }\n _manageBribes(totalRewardAmounts, extraDuration, rewardsPerVote);\n }\n\n /// @notice Fully configurable entry point to manage bribe campaigns. Allows setting\n /// reward amounts, durations, and reward rates individually for each pool.\n /// Each array must have the same length as the poolBoosters array.\n /// @param totalRewardAmounts Total reward amount per pool (0 = no update, type(uint256).max = use all available)\n /// @param extraDuration Number of periods to extend per pool (0 = no update, 1 = +1 week)\n /// @param rewardsPerVote Max reward per vote per pool (0 = no update)\n function manageBribes(\n uint256[] calldata totalRewardAmounts,\n uint8[] calldata extraDuration,\n uint256[] calldata rewardsPerVote\n ) external onlyOperator {\n require(\n poolBoosters.length == totalRewardAmounts.length,\n \"Length mismatch\"\n );\n require(poolBoosters.length == extraDuration.length, \"Length mismatch\");\n require(\n poolBoosters.length == rewardsPerVote.length,\n \"Length mismatch\"\n );\n _manageBribes(totalRewardAmounts, extraDuration, rewardsPerVote);\n }\n\n ////////////////////////////////////////////////////\n /// --- External View Functions\n ////////////////////////////////////////////////////\n\n /// @notice Get the full list of managed CurvePoolBooster addresses\n /// @return Array of pool booster addresses\n function getPoolBoosters() external view returns (address[] memory) {\n return poolBoosters;\n }\n\n ////////////////////////////////////////////////////\n /// --- Internal Functions\n ////////////////////////////////////////////////////\n\n /// @notice Internal logic to add a single pool booster address\n /// @dev Reverts if the address is already in the poolBoosters array\n /// @param _pool Address to append to the poolBoosters array\n function _addPoolBoosterAddress(address _pool) internal {\n require(_pool != address(0), \"Zero address\");\n for (uint256 j = 0; j < poolBoosters.length; j++) {\n require(poolBoosters[j] != _pool, \"Pool already added\");\n }\n poolBoosters.push(_pool);\n emit PoolBoosterAddressAdded(_pool);\n }\n\n /// @notice Internal logic to remove a pool booster address\n /// @dev Swaps the target with the last element and pops to avoid gaps\n /// @param pool Address to remove from the poolBoosters array\n function _removePoolBoosterAddress(address pool) internal {\n uint256 length = poolBoosters.length;\n for (uint256 i = 0; i < length; i++) {\n if (poolBoosters[i] == pool) {\n poolBoosters[i] = poolBoosters[length - 1];\n poolBoosters.pop();\n emit PoolBoosterAddressRemoved(pool);\n return;\n }\n }\n revert(\"Pool not found\");\n }\n\n /// @notice Internal logic to set the bridge fee\n /// @param newFee New bridge fee amount in wei\n function _setBridgeFee(uint256 newFee) internal {\n require(newFee <= 0.01 ether, \"Bridge fee too high\");\n bridgeFee = newFee;\n emit BridgeFeeUpdated(newFee);\n }\n\n /// @notice Internal logic to set the additional gas limit\n /// @param newGasLimit New gas limit value\n function _setAdditionalGasLimit(uint256 newGasLimit) internal {\n require(newGasLimit <= 10_000_000, \"Gas limit too high\");\n additionalGasLimit = newGasLimit;\n emit AdditionalGasLimitUpdated(newGasLimit);\n }\n\n /// @notice Internal logic to manage bribe campaigns for all registered pool boosters\n /// @dev Iterates over all pool boosters and instructs the Safe to call `manageCampaign`\n /// on each one, sending `bridgeFee` ETH from the Safe's balance per call.\n /// @param totalRewardAmounts Total reward amount per pool (0 = no update, type(uint256).max = use all available)\n /// @param extraDuration Number of periods to extend per pool (0 = no update)\n /// @param rewardsPerVote Max reward per vote per pool (0 = no update)\n function _manageBribes(\n uint256[] memory totalRewardAmounts,\n uint8[] memory extraDuration,\n uint256[] memory rewardsPerVote\n ) internal {\n uint256 pbCount = poolBoosters.length;\n require(\n address(safeContract).balance >= bridgeFee * pbCount,\n \"Not enough ETH for bridge fees\"\n );\n for (uint256 i = 0; i < pbCount; i++) {\n address poolBoosterAddress = poolBoosters[i];\n require(\n safeContract.execTransactionFromModule(\n poolBoosterAddress,\n bridgeFee, // ETH value to cover bridge fee\n abi.encodeWithSelector(\n ICurvePoolBooster.manageCampaign.selector,\n totalRewardAmounts[i], // 0 = no update, max = use all\n extraDuration[i], // numberOfPeriods, 0 = no update, 1 = +1 period (week)\n rewardsPerVote[i], // maxRewardPerVote, 0 = no update\n additionalGasLimit\n ),\n 0\n ),\n \"Manage campaign failed\"\n );\n }\n }\n}\n" + }, + "contracts/interfaces/ISafe.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\ninterface ISafe {\n function execTransactionFromModule(\n address,\n uint256,\n bytes memory,\n uint8\n ) external returns (bool);\n}\n" + } + }, + "settings": { + "optimizer": { + "enabled": true, + "runs": 200 + }, + "evmVersion": "paris", + "outputSelection": { + "*": { + "*": [ + "abi", + "evm.bytecode", + "evm.deployedBytecode", + "evm.methodIdentifiers", + "metadata", + "devdoc", + "userdoc", + "storageLayout", + "evm.gasEstimates" + ], + "": [ + "ast" + ] + } + }, + "metadata": { + "useLiteralContent": true + } + } +} \ No newline at end of file diff --git a/contracts/storageLayout/mainnet/CurvePoolBoosterBribesModule.json b/contracts/storageLayout/mainnet/CurvePoolBoosterBribesModule.json index 1197d7d296..44d11e71f1 100644 --- a/contracts/storageLayout/mainnet/CurvePoolBoosterBribesModule.json +++ b/contracts/storageLayout/mainnet/CurvePoolBoosterBribesModule.json @@ -18,12 +18,28 @@ "src": "@openzeppelin/contracts/access/AccessControlEnumerable.sol:16" }, { - "label": "POOLS", + "label": "poolBoosters", "offset": 0, "slot": "2", "type": "t_array(t_address)dyn_storage", "contract": "CurvePoolBoosterBribesModule", - "src": "contracts/automation/CurvePoolBoosterBribesModule.sol:26" + "src": "contracts/automation/CurvePoolBoosterBribesModule.sol:28" + }, + { + "label": "bridgeFee", + "offset": 0, + "slot": "3", + "type": "t_uint256", + "contract": "CurvePoolBoosterBribesModule", + "src": "contracts/automation/CurvePoolBoosterBribesModule.sol:31" + }, + { + "label": "additionalGasLimit", + "offset": 0, + "slot": "4", + "type": "t_uint256", + "contract": "CurvePoolBoosterBribesModule", + "src": "contracts/automation/CurvePoolBoosterBribesModule.sol:34" } ], "types": { From bf7bd0e43e9e64a020b5e444306226ceaf9c4f91 Mon Sep 17 00:00:00 2001 From: Shah <10547529+shahthepro@users.noreply.github.com> Date: Thu, 12 Feb 2026 14:11:27 +0400 Subject: [PATCH 14/36] [Ethereum] [Base] Deploy Crosschain Strategy (#2793) * [Ethereum] [Base] Deploy Crosschain Strategy * Regenerate governance files --- ....js => 041_crosschain_strategy_proxies.js} | 2 +- ...strategy.js => 042_crosschain_strategy.js} | 9 +- .../deploy/mainnet/169_crosschain_strategy.js | 12 +- contracts/deployments/base/.migrations.json | 4 +- .../base/CrossChainRemoteStrategy.json | 1789 +++++++++++++++++ .../deployments/base/create2Proxies.json | 4 +- .../042_crosschain_strategy.execute.json | 52 + .../042_crosschain_strategy.schedule.json | 57 + .../00a8ab5ddcdb46bc9891a2222be9eb31.json | 621 ++++++ .../deployments/mainnet/.migrations.json | 2 + .../mainnet/CrossChainMasterStrategy.json | 1674 +++++++++++++++ .../deployments/mainnet/create2Proxies.json | 4 +- .../00a8ab5ddcdb46bc9891a2222be9eb31.json | 621 ++++++ .../base/CrossChainRemoteStrategy.json | 240 +++ .../mainnet/CrossChainMasterStrategy.json | 216 ++ 15 files changed, 5298 insertions(+), 9 deletions(-) rename contracts/deploy/base/{040_crosschain_strategy_proxies.js => 041_crosschain_strategy_proxies.js} (91%) rename contracts/deploy/base/{041_crosschain_strategy.js => 042_crosschain_strategy.js} (84%) create mode 100644 contracts/deployments/base/CrossChainRemoteStrategy.json create mode 100644 contracts/deployments/base/operations/042_crosschain_strategy.execute.json create mode 100644 contracts/deployments/base/operations/042_crosschain_strategy.schedule.json create mode 100644 contracts/deployments/base/solcInputs/00a8ab5ddcdb46bc9891a2222be9eb31.json create mode 100644 contracts/deployments/mainnet/CrossChainMasterStrategy.json create mode 100644 contracts/deployments/mainnet/solcInputs/00a8ab5ddcdb46bc9891a2222be9eb31.json create mode 100644 contracts/storageLayout/base/CrossChainRemoteStrategy.json create mode 100644 contracts/storageLayout/mainnet/CrossChainMasterStrategy.json diff --git a/contracts/deploy/base/040_crosschain_strategy_proxies.js b/contracts/deploy/base/041_crosschain_strategy_proxies.js similarity index 91% rename from contracts/deploy/base/040_crosschain_strategy_proxies.js rename to contracts/deploy/base/041_crosschain_strategy_proxies.js index c722c21980..b85938cbf2 100644 --- a/contracts/deploy/base/040_crosschain_strategy_proxies.js +++ b/contracts/deploy/base/041_crosschain_strategy_proxies.js @@ -3,7 +3,7 @@ const { deployProxyWithCreateX } = require("../deployActions"); module.exports = deployOnBase( { - deployName: "040_crosschain_strategy_proxies", + deployName: "041_crosschain_strategy_proxies", }, async () => { // the salt needs to match the salt on the base chain deploying the other part of the strategy diff --git a/contracts/deploy/base/041_crosschain_strategy.js b/contracts/deploy/base/042_crosschain_strategy.js similarity index 84% rename from contracts/deploy/base/041_crosschain_strategy.js rename to contracts/deploy/base/042_crosschain_strategy.js index 042b84fb9f..3f76398525 100644 --- a/contracts/deploy/base/041_crosschain_strategy.js +++ b/contracts/deploy/base/042_crosschain_strategy.js @@ -8,7 +8,7 @@ const { cctpDomainIds } = require("../../utils/cctp"); module.exports = deployOnBase( { - deployName: "041_crosschain_strategy", + deployName: "042_crosschain_strategy", }, async () => { const crossChainStrategyProxyAddress = await getCreate2ProxyAddress( @@ -19,7 +19,7 @@ module.exports = deployOnBase( ); const implAddress = await deployCrossChainRemoteStrategyImpl( - "0xbeeF010f9cb27031ad51e3333f9aF9C6B1228183", // 4626 Vault + addresses.base.MorphoOusdV2Vault, // 4626 Vault crossChainStrategyProxyAddress, cctpDomainIds.Ethereum, crossChainStrategyProxyAddress, @@ -47,6 +47,11 @@ module.exports = deployOnBase( signature: "safeApproveAllTokens()", args: [], }, + { + contract: cCrossChainRemoteStrategy, + signature: "setHarvesterAddress(address)", + args: [addresses.multichainStrategist], + }, ], }; } diff --git a/contracts/deploy/mainnet/169_crosschain_strategy.js b/contracts/deploy/mainnet/169_crosschain_strategy.js index 388e82ba4a..d2a1cd1984 100644 --- a/contracts/deploy/mainnet/169_crosschain_strategy.js +++ b/contracts/deploy/mainnet/169_crosschain_strategy.js @@ -25,6 +25,7 @@ module.exports = deploymentWithGovernanceProposal( console.log(`CrossChainStrategyProxy address: ${cProxy.address}`); const cVaultProxy = await ethers.getContract("VaultProxy"); + const cVault = await ethers.getContractAt("IVault", cVaultProxy.address); const implAddress = await deployCrossChainMasterStrategyImpl( crossChainStrategyProxyAddress, @@ -50,10 +51,15 @@ module.exports = deploymentWithGovernanceProposal( `CrossChainMasterStrategy address: ${cCrossChainMasterStrategy.address}` ); - // TODO: Set reward tokens to Morpho - return { - actions: [], + name: "Add Morpho V2 Crosschain Strategy to OUSD Vault", + actions: [ + { + contract: cVault, + signature: "approveStrategy(address)", + args: [crossChainStrategyProxyAddress], + }, + ], }; } ); diff --git a/contracts/deployments/base/.migrations.json b/contracts/deployments/base/.migrations.json index ac6ab2402e..8474d4765b 100644 --- a/contracts/deployments/base/.migrations.json +++ b/contracts/deployments/base/.migrations.json @@ -36,5 +36,7 @@ "036_oethb_upgrade_EIP7702": 1751453912, "037_deploy_harvester": 1752034150, "038_vault_upgrade": 1752579215, - "039_pool_booster_factory": 1756293140 + "039_pool_booster_factory": 1756293140, + "041_crosschain_strategy_proxies": 1770737618, + "042_crosschain_strategy": 1770807435 } \ No newline at end of file diff --git a/contracts/deployments/base/CrossChainRemoteStrategy.json b/contracts/deployments/base/CrossChainRemoteStrategy.json new file mode 100644 index 0000000000..4316962618 --- /dev/null +++ b/contracts/deployments/base/CrossChainRemoteStrategy.json @@ -0,0 +1,1789 @@ +{ + "address": "0x5F81a5a22375ebCC2075b162D23a5b16a1e7A92d", + "abi": [ + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "platformAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "vaultAddress", + "type": "address" + } + ], + "internalType": "struct InitializableAbstractStrategy.BaseStrategyConfig", + "name": "_baseConfig", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "address", + "name": "cctpTokenMessenger", + "type": "address" + }, + { + "internalType": "address", + "name": "cctpMessageTransmitter", + "type": "address" + }, + { + "internalType": "uint32", + "name": "peerDomainID", + "type": "uint32" + }, + { + "internalType": "address", + "name": "peerStrategy", + "type": "address" + }, + { + "internalType": "address", + "name": "usdcToken", + "type": "address" + }, + { + "internalType": "address", + "name": "peerUsdcToken", + "type": "address" + } + ], + "internalType": "struct AbstractCCTPIntegrator.CCTPIntegrationConfig", + "name": "_cctpConfig", + "type": "tuple" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint16", + "name": "feePremiumBps", + "type": "uint16" + } + ], + "name": "CCTPFeePremiumBpsSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint16", + "name": "minFinalityThreshold", + "type": "uint16" + } + ], + "name": "CCTPMinFinalityThresholdSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "ClaimedRewards", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "_asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "_pToken", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "Deposit", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "string", + "name": "reason", + "type": "string" + } + ], + "name": "DepositUnderlyingFailed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousGovernor", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newGovernor", + "type": "address" + } + ], + "name": "GovernorshipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "_oldHarvesterAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "_newHarvesterAddress", + "type": "address" + } + ], + "name": "HarvesterAddressesUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint64", + "name": "lastTransferNonce", + "type": "uint64" + } + ], + "name": "LastTransferNonceUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint32", + "name": "destinationDomain", + "type": "uint32" + }, + { + "indexed": false, + "internalType": "address", + "name": "peerStrategy", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint32", + "name": "minFinalityThreshold", + "type": "uint32" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "message", + "type": "bytes" + } + ], + "name": "MessageTransmitted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint64", + "name": "nonce", + "type": "uint64" + } + ], + "name": "NonceProcessed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "name": "OperatorChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "_asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "_pToken", + "type": "address" + } + ], + "name": "PTokenAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "_asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "_pToken", + "type": "address" + } + ], + "name": "PTokenRemoved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousGovernor", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newGovernor", + "type": "address" + } + ], + "name": "PendingGovernorshipTransfer", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address[]", + "name": "_oldAddresses", + "type": "address[]" + }, + { + "indexed": false, + "internalType": "address[]", + "name": "_newAddresses", + "type": "address[]" + } + ], + "name": "RewardTokenAddressesUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "rewardToken", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "RewardTokenCollected", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "_address", + "type": "address" + } + ], + "name": "StrategistUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint32", + "name": "destinationDomain", + "type": "uint32" + }, + { + "indexed": false, + "internalType": "address", + "name": "peerStrategy", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "tokenAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "maxFee", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint32", + "name": "minFinalityThreshold", + "type": "uint32" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "hookData", + "type": "bytes" + } + ], + "name": "TokensBridged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "string", + "name": "reason", + "type": "string" + } + ], + "name": "WithdrawUnderlyingFailed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "_asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "_pToken", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "Withdrawal", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "amountRequested", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amountAvailable", + "type": "uint256" + } + ], + "name": "WithdrawalFailed", + "type": "event" + }, + { + "inputs": [], + "name": "MAX_TRANSFER_AMOUNT", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MIN_TRANSFER_AMOUNT", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "assetToPToken", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "assetToken", + "outputs": [ + { + "internalType": "contract IERC20", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "cctpMessageTransmitter", + "outputs": [ + { + "internalType": "contract ICCTPMessageTransmitter", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "cctpTokenMessenger", + "outputs": [ + { + "internalType": "contract ICCTPTokenMessenger", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_asset", + "type": "address" + } + ], + "name": "checkBalance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "claimGovernance", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "collectRewardTokens", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_asset", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "deposit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "depositAll", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "feePremiumBps", + "outputs": [ + { + "internalType": "uint16", + "name": "", + "type": "uint16" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getRewardTokenAddresses", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "governor", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "sourceDomain", + "type": "uint32" + }, + { + "internalType": "bytes32", + "name": "sender", + "type": "bytes32" + }, + { + "internalType": "uint32", + "name": "finalityThresholdExecuted", + "type": "uint32" + }, + { + "internalType": "bytes", + "name": "messageBody", + "type": "bytes" + } + ], + "name": "handleReceiveFinalizedMessage", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "sourceDomain", + "type": "uint32" + }, + { + "internalType": "bytes32", + "name": "sender", + "type": "bytes32" + }, + { + "internalType": "uint32", + "name": "finalityThresholdExecuted", + "type": "uint32" + }, + { + "internalType": "bytes", + "name": "messageBody", + "type": "bytes" + } + ], + "name": "handleReceiveUnfinalizedMessage", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "harvesterAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_strategist", + "type": "address" + }, + { + "internalType": "address", + "name": "_operator", + "type": "address" + }, + { + "internalType": "uint16", + "name": "_minFinalityThreshold", + "type": "uint16" + }, + { + "internalType": "uint16", + "name": "_feePremiumBps", + "type": "uint16" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "isGovernor", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "nonce", + "type": "uint64" + } + ], + "name": "isNonceProcessed", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "isTransferPending", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "lastTransferNonce", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "bytes32[]", + "name": "proof", + "type": "bytes32[]" + } + ], + "name": "merkleClaim", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "merkleDistributor", + "outputs": [ + { + "internalType": "contract IDistributor", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "minFinalityThreshold", + "outputs": [ + { + "internalType": "uint16", + "name": "", + "type": "uint16" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "operator", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "peerDomainID", + "outputs": [ + { + "internalType": "uint32", + "name": "", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "peerStrategy", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "peerUsdcToken", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "platformAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "message", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "attestation", + "type": "bytes" + } + ], + "name": "relay", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "removePToken", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "rewardTokenAddresses", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "safeApproveAllTokens", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "sendBalanceUpdate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint16", + "name": "_feePremiumBps", + "type": "uint16" + } + ], + "name": "setFeePremiumBps", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_harvesterAddress", + "type": "address" + } + ], + "name": "setHarvesterAddress", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint16", + "name": "_minFinalityThreshold", + "type": "uint16" + } + ], + "name": "setMinFinalityThreshold", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_operator", + "type": "address" + } + ], + "name": "setOperator", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "setPTokenAddress", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "_rewardTokenAddresses", + "type": "address[]" + } + ], + "name": "setRewardTokenAddresses", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_address", + "type": "address" + } + ], + "name": "setStrategistAddr", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "shareToken", + "outputs": [ + { + "internalType": "contract IERC20", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "strategistAddr", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_asset", + "type": "address" + } + ], + "name": "supportsAsset", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_newGovernor", + "type": "address" + } + ], + "name": "transferGovernance", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_asset", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "transferToken", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "usdcToken", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "vaultAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_recipient", + "type": "address" + }, + { + "internalType": "address", + "name": "_asset", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "withdraw", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "withdrawAll", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "transactionHash": "0xf13e92fc9595d4cc5bb89ab82a2eea1ba9f5b3d2f607cbadce42bb77021d6c9c", + "receipt": { + "to": null, + "from": "0x58890A9cB27586E83Cb51d2d26bbE18a1a647245", + "contractAddress": "0x5F81a5a22375ebCC2075b162D23a5b16a1e7A92d", + "transactionIndex": 192, + "gasUsed": "4435260", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x9605b0b017bf2b3c188e18e008567a6c90953a43546329882b5fca93e13ad8b4", + "transactionHash": "0xf13e92fc9595d4cc5bb89ab82a2eea1ba9f5b3d2f607cbadce42bb77021d6c9c", + "logs": [], + "blockNumber": 41974179, + "cumulativeGasUsed": "39029825", + "status": 1, + "byzantium": true + }, + "args": [ + [ + "0x2Ba14b2e1E7D2189D3550b708DFCA01f899f33c1", + "0x0000000000000000000000000000000000000000" + ], + [ + "0x28b5a0e9c621a5badaa536219b3a228c8168cf5d", + "0x81D40F21F12A8F0E3252Bccb954D722d4c464B64", + 0, + "0xB1d624fc40824683e2bFBEfd19eB208DbBE00866", + "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", + "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" + ] + ], + "numDeployments": 1, + "solcInputHash": "00a8ab5ddcdb46bc9891a2222be9eb31", + "metadata": "{\"compiler\":{\"version\":\"0.8.28+commit.7893614a\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"platformAddress\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"vaultAddress\",\"type\":\"address\"}],\"internalType\":\"struct InitializableAbstractStrategy.BaseStrategyConfig\",\"name\":\"_baseConfig\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"cctpTokenMessenger\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"cctpMessageTransmitter\",\"type\":\"address\"},{\"internalType\":\"uint32\",\"name\":\"peerDomainID\",\"type\":\"uint32\"},{\"internalType\":\"address\",\"name\":\"peerStrategy\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"usdcToken\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"peerUsdcToken\",\"type\":\"address\"}],\"internalType\":\"struct AbstractCCTPIntegrator.CCTPIntegrationConfig\",\"name\":\"_cctpConfig\",\"type\":\"tuple\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint16\",\"name\":\"feePremiumBps\",\"type\":\"uint16\"}],\"name\":\"CCTPFeePremiumBpsSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint16\",\"name\":\"minFinalityThreshold\",\"type\":\"uint16\"}],\"name\":\"CCTPMinFinalityThresholdSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"ClaimedRewards\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"_asset\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"_pToken\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"_amount\",\"type\":\"uint256\"}],\"name\":\"Deposit\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"string\",\"name\":\"reason\",\"type\":\"string\"}],\"name\":\"DepositUnderlyingFailed\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"previousGovernor\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newGovernor\",\"type\":\"address\"}],\"name\":\"GovernorshipTransferred\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"_oldHarvesterAddress\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"_newHarvesterAddress\",\"type\":\"address\"}],\"name\":\"HarvesterAddressesUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"lastTransferNonce\",\"type\":\"uint64\"}],\"name\":\"LastTransferNonceUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"destinationDomain\",\"type\":\"uint32\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"peerStrategy\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"minFinalityThreshold\",\"type\":\"uint32\"},{\"indexed\":false,\"internalType\":\"bytes\",\"name\":\"message\",\"type\":\"bytes\"}],\"name\":\"MessageTransmitted\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"nonce\",\"type\":\"uint64\"}],\"name\":\"NonceProcessed\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"operator\",\"type\":\"address\"}],\"name\":\"OperatorChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"_asset\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"_pToken\",\"type\":\"address\"}],\"name\":\"PTokenAdded\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"_asset\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"_pToken\",\"type\":\"address\"}],\"name\":\"PTokenRemoved\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"previousGovernor\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newGovernor\",\"type\":\"address\"}],\"name\":\"PendingGovernorshipTransfer\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address[]\",\"name\":\"_oldAddresses\",\"type\":\"address[]\"},{\"indexed\":false,\"internalType\":\"address[]\",\"name\":\"_newAddresses\",\"type\":\"address[]\"}],\"name\":\"RewardTokenAddressesUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"rewardToken\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"RewardTokenCollected\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"_address\",\"type\":\"address\"}],\"name\":\"StrategistUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"destinationDomain\",\"type\":\"uint32\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"peerStrategy\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"tokenAddress\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"tokenAmount\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"maxFee\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"minFinalityThreshold\",\"type\":\"uint32\"},{\"indexed\":false,\"internalType\":\"bytes\",\"name\":\"hookData\",\"type\":\"bytes\"}],\"name\":\"TokensBridged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"string\",\"name\":\"reason\",\"type\":\"string\"}],\"name\":\"WithdrawUnderlyingFailed\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"_asset\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"_pToken\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"_amount\",\"type\":\"uint256\"}],\"name\":\"Withdrawal\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amountRequested\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amountAvailable\",\"type\":\"uint256\"}],\"name\":\"WithdrawalFailed\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"MAX_TRANSFER_AMOUNT\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"MIN_TRANSFER_AMOUNT\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"assetToPToken\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"assetToken\",\"outputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"cctpMessageTransmitter\",\"outputs\":[{\"internalType\":\"contract ICCTPMessageTransmitter\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"cctpTokenMessenger\",\"outputs\":[{\"internalType\":\"contract ICCTPTokenMessenger\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_asset\",\"type\":\"address\"}],\"name\":\"checkBalance\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"claimGovernance\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"collectRewardTokens\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_asset\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_amount\",\"type\":\"uint256\"}],\"name\":\"deposit\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"depositAll\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"feePremiumBps\",\"outputs\":[{\"internalType\":\"uint16\",\"name\":\"\",\"type\":\"uint16\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getRewardTokenAddresses\",\"outputs\":[{\"internalType\":\"address[]\",\"name\":\"\",\"type\":\"address[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"governor\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"sourceDomain\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"sender\",\"type\":\"bytes32\"},{\"internalType\":\"uint32\",\"name\":\"finalityThresholdExecuted\",\"type\":\"uint32\"},{\"internalType\":\"bytes\",\"name\":\"messageBody\",\"type\":\"bytes\"}],\"name\":\"handleReceiveFinalizedMessage\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"sourceDomain\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"sender\",\"type\":\"bytes32\"},{\"internalType\":\"uint32\",\"name\":\"finalityThresholdExecuted\",\"type\":\"uint32\"},{\"internalType\":\"bytes\",\"name\":\"messageBody\",\"type\":\"bytes\"}],\"name\":\"handleReceiveUnfinalizedMessage\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"harvesterAddress\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"initialize\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_strategist\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_operator\",\"type\":\"address\"},{\"internalType\":\"uint16\",\"name\":\"_minFinalityThreshold\",\"type\":\"uint16\"},{\"internalType\":\"uint16\",\"name\":\"_feePremiumBps\",\"type\":\"uint16\"}],\"name\":\"initialize\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"isGovernor\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"nonce\",\"type\":\"uint64\"}],\"name\":\"isNonceProcessed\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"isTransferPending\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"lastTransferNonce\",\"outputs\":[{\"internalType\":\"uint64\",\"name\":\"\",\"type\":\"uint64\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"internalType\":\"bytes32[]\",\"name\":\"proof\",\"type\":\"bytes32[]\"}],\"name\":\"merkleClaim\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"merkleDistributor\",\"outputs\":[{\"internalType\":\"contract IDistributor\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"minFinalityThreshold\",\"outputs\":[{\"internalType\":\"uint16\",\"name\":\"\",\"type\":\"uint16\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"operator\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"peerDomainID\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"peerStrategy\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"peerUsdcToken\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"platformAddress\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"message\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"attestation\",\"type\":\"bytes\"}],\"name\":\"relay\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"removePToken\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"rewardTokenAddresses\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"safeApproveAllTokens\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"sendBalanceUpdate\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint16\",\"name\":\"_feePremiumBps\",\"type\":\"uint16\"}],\"name\":\"setFeePremiumBps\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_harvesterAddress\",\"type\":\"address\"}],\"name\":\"setHarvesterAddress\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint16\",\"name\":\"_minFinalityThreshold\",\"type\":\"uint16\"}],\"name\":\"setMinFinalityThreshold\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_operator\",\"type\":\"address\"}],\"name\":\"setOperator\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"setPTokenAddress\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"_rewardTokenAddresses\",\"type\":\"address[]\"}],\"name\":\"setRewardTokenAddresses\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_address\",\"type\":\"address\"}],\"name\":\"setStrategistAddr\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"shareToken\",\"outputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"strategistAddr\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_asset\",\"type\":\"address\"}],\"name\":\"supportsAsset\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_newGovernor\",\"type\":\"address\"}],\"name\":\"transferGovernance\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_asset\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_amount\",\"type\":\"uint256\"}],\"name\":\"transferToken\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"usdcToken\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"vaultAddress\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_recipient\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_asset\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_amount\",\"type\":\"uint256\"}],\"name\":\"withdraw\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"withdrawAll\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{\"checkBalance(address)\":{\"params\":{\"_asset\":\"Address of the asset\"},\"returns\":{\"_0\":\"balance Total value of the asset in the platform and contract\"}},\"deposit(address,uint256)\":{\"details\":\"Deposit assets by converting them to shares\",\"params\":{\"_amount\":\"Amount of asset to deposit\",\"_asset\":\"Address of asset to deposit\"}},\"depositAll()\":{\"details\":\"Deposit the entire balance of assetToken to gain shareToken\"},\"getRewardTokenAddresses()\":{\"returns\":{\"_0\":\"address[] the reward token addresses.\"}},\"handleReceiveFinalizedMessage(uint32,bytes32,uint32,bytes)\":{\"details\":\"Handles a finalized CCTP message\",\"params\":{\"finalityThresholdExecuted\":\"Fidelity threshold executed\",\"messageBody\":\"Message body\",\"sender\":\"Sender of the message\",\"sourceDomain\":\"Source domain of the message\"}},\"handleReceiveUnfinalizedMessage(uint32,bytes32,uint32,bytes)\":{\"details\":\"Handles an unfinalized but safe CCTP message\",\"params\":{\"finalityThresholdExecuted\":\"Fidelity threshold executed\",\"messageBody\":\"Message body\",\"sender\":\"Sender of the message\",\"sourceDomain\":\"Source domain of the message\"}},\"initialize(address,address,uint16,uint16)\":{\"details\":\"Initialize the strategy implementation\",\"params\":{\"_feePremiumBps\":\"Fee premium in basis points\",\"_minFinalityThreshold\":\"Minimum finality threshold\",\"_operator\":\"Address of the operator\",\"_strategist\":\"Address of the strategist\"}},\"isNonceProcessed(uint64)\":{\"details\":\"Checks if a given nonce is processed. Nonce starts at 1, so 0 is disregarded.\",\"params\":{\"nonce\":\"Nonce to check\"},\"returns\":{\"_0\":\"True if the nonce is processed, false otherwise\"}},\"isTransferPending()\":{\"details\":\"Checks if the last known transfer is pending. Nonce starts at 1, so 0 is disregarded.\",\"returns\":{\"_0\":\"True if a transfer is pending, false otherwise\"}},\"merkleClaim(address,uint256,bytes32[])\":{\"params\":{\"amount\":\"The amount of tokens to claim.\",\"proof\":\"The Merkle proof to validate the claim.\",\"token\":\"The address of the token to claim.\"}},\"relay(bytes,bytes)\":{\"details\":\"Receives a message from the peer strategy on the other chain, does some basic checks and relays it to the local MessageTransmitterV2. If the message is a burn message, it will also handle the hook data and call the _onTokenReceived function.\",\"params\":{\"attestation\":\"Attestation of the message\",\"message\":\"Payload of the message to send\"}},\"removePToken(uint256)\":{\"details\":\"If the ERC-4626 Tokenized Vault needed to be changed, a new contract would need to be deployed and the proxy updated.\"},\"sendBalanceUpdate()\":{\"details\":\"Send balance update message to the peer strategy\"},\"setFeePremiumBps(uint16)\":{\"details\":\"Set the fee premium in basis points. Cannot be higher than 30% (3000 basis points).\",\"params\":{\"_feePremiumBps\":\"Fee premium in basis points\"}},\"setHarvesterAddress(address)\":{\"params\":{\"_harvesterAddress\":\"Address of the harvester contract.\"}},\"setMinFinalityThreshold(uint16)\":{\"details\":\"Set the minimum finality threshold at which the message is considered to be finalized to relay. Only accepts a value of 1000 (Safe, after 1 epoch) or 2000 (Finalized, after 2 epochs).\",\"params\":{\"_minFinalityThreshold\":\"Minimum finality threshold\"}},\"setOperator(address)\":{\"details\":\"Set the operator address\",\"params\":{\"_operator\":\"Operator address\"}},\"setPTokenAddress(address,address)\":{\"details\":\"If the ERC-4626 Tokenized Vault needed to be changed, a new contract would need to be deployed and the proxy updated.\"},\"setRewardTokenAddresses(address[])\":{\"params\":{\"_rewardTokenAddresses\":\"Array of reward token addresses\"}},\"setStrategistAddr(address)\":{\"details\":\"Set address of Strategist\",\"params\":{\"_address\":\"Address of Strategist\"}},\"supportsAsset(address)\":{\"details\":\"Returns bool indicating whether asset is supported by strategy\",\"params\":{\"_asset\":\"Address of the asset\"}},\"transferGovernance(address)\":{\"params\":{\"_newGovernor\":\"Address of the new Governor\"}},\"transferToken(address,uint256)\":{\"params\":{\"_amount\":\"Amount of the asset to transfer\",\"_asset\":\"Address for the asset\"}},\"withdraw(address,address,uint256)\":{\"details\":\"Interface requires a recipient, but for compatibility it must be address(this).\",\"params\":{\"_amount\":\"Amount of asset to withdraw\",\"_asset\":\"Address of asset to withdraw\",\"_recipient\":\"Address to receive withdrawn asset\"}},\"withdrawAll()\":{\"details\":\"Remove all assets from platform and send them to Vault contract.\"}},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{\"MAX_TRANSFER_AMOUNT()\":{\"notice\":\"Max transfer threshold imposed by the CCTP Ref: https://developers.circle.com/cctp/evm-smart-contracts#depositforburn\"},\"MIN_TRANSFER_AMOUNT()\":{\"notice\":\"Minimum transfer amount to avoid zero or dust transfers\"},\"assetToPToken(address)\":{\"notice\":\"asset => pToken (Platform Specific Token Address)\"},\"cctpMessageTransmitter()\":{\"notice\":\"CCTP message transmitter contract\"},\"cctpTokenMessenger()\":{\"notice\":\"CCTP token messenger contract\"},\"checkBalance(address)\":{\"notice\":\"Get the total asset value held in the platform and contract\"},\"claimGovernance()\":{\"notice\":\"Claim Governance of the contract to a new account (`newGovernor`). Can only be called by the new Governor.\"},\"collectRewardTokens()\":{\"notice\":\"Collect accumulated reward token and send to Vault.\"},\"feePremiumBps()\":{\"notice\":\"Fee premium in basis points\"},\"getRewardTokenAddresses()\":{\"notice\":\"Get the reward token addresses.\"},\"governor()\":{\"notice\":\"Returns the address of the current Governor.\"},\"harvesterAddress()\":{\"notice\":\"Address of the Harvester contract allowed to collect reward tokens\"},\"isGovernor()\":{\"notice\":\"Returns true if the caller is the current Governor.\"},\"lastTransferNonce()\":{\"notice\":\"Nonce of the last known deposit or withdrawal\"},\"merkleClaim(address,uint256,bytes32[])\":{\"notice\":\"Claim tokens from the Merkle Distributor\"},\"merkleDistributor()\":{\"notice\":\"The address of the Merkle Distributor contract.\"},\"minFinalityThreshold()\":{\"notice\":\"Minimum finality threshold Can be 1000 (safe, after 1 epoch) or 2000 (finalized, after 2 epochs). Ref: https://developers.circle.com/cctp/technical-guide#finality-thresholds\"},\"operator()\":{\"notice\":\"Operator address: Can relay CCTP messages\"},\"peerDomainID()\":{\"notice\":\"Domain ID of the chain from which messages are accepted\"},\"peerStrategy()\":{\"notice\":\"Strategy address on other chain\"},\"peerUsdcToken()\":{\"notice\":\"USDC address on remote chain\"},\"platformAddress()\":{\"notice\":\"Address of the underlying platform\"},\"removePToken(uint256)\":{\"notice\":\"is not supported for this strategy as the asset and ERC-4626 Tokenized Vault are set at deploy time.\"},\"rewardTokenAddresses(uint256)\":{\"notice\":\"Address of the reward tokens. eg CRV, BAL, CVX, AURA\"},\"safeApproveAllTokens()\":{\"notice\":\"Governor approves the ERC-4626 Tokenized Vault to spend the asset.\"},\"setHarvesterAddress(address)\":{\"notice\":\"Set the Harvester contract that can collect rewards.\"},\"setPTokenAddress(address,address)\":{\"notice\":\"is not supported for this strategy as the asset and ERC-4626 Tokenized Vault are set at deploy time.\"},\"setRewardTokenAddresses(address[])\":{\"notice\":\"Set the reward token addresses. Any old addresses will be overwritten.\"},\"transferGovernance(address)\":{\"notice\":\"Transfers Governance of the contract to a new account (`newGovernor`). Can only be called by the current Governor. Must be claimed for this to complete\"},\"transferToken(address,uint256)\":{\"notice\":\"Transfer token to governor. Intended for recovering tokens stuck in strategy contracts, i.e. mistaken sends.\"},\"usdcToken()\":{\"notice\":\"USDC address on local chain\"},\"vaultAddress()\":{\"notice\":\"Address of the OToken vault\"}},\"version\":1}},\"settings\":{\"compilationTarget\":{\"contracts/strategies/crosschain/CrossChainRemoteStrategy.sol\":\"CrossChainRemoteStrategy\"},\"evmVersion\":\"paris\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\",\"useLiteralContent\":true},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[]},\"sources\":{\"@openzeppelin/contracts/token/ERC20/IERC20.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (token/ERC20/IERC20.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev Interface of the ERC20 standard as defined in the EIP.\\n */\\ninterface IERC20 {\\n /**\\n * @dev Returns the amount of tokens in existence.\\n */\\n function totalSupply() external view returns (uint256);\\n\\n /**\\n * @dev Returns the amount of tokens owned by `account`.\\n */\\n function balanceOf(address account) external view returns (uint256);\\n\\n /**\\n * @dev Moves `amount` tokens from the caller's account to `recipient`.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * Emits a {Transfer} event.\\n */\\n function transfer(address recipient, uint256 amount) external returns (bool);\\n\\n /**\\n * @dev Returns the remaining number of tokens that `spender` will be\\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\\n * zero by default.\\n *\\n * This value changes when {approve} or {transferFrom} are called.\\n */\\n function allowance(address owner, address spender) external view returns (uint256);\\n\\n /**\\n * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\\n * that someone may use both the old and the new allowance by unfortunate\\n * transaction ordering. One possible solution to mitigate this race\\n * condition is to first reduce the spender's allowance to 0 and set the\\n * desired value afterwards:\\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\\n *\\n * Emits an {Approval} event.\\n */\\n function approve(address spender, uint256 amount) external returns (bool);\\n\\n /**\\n * @dev Moves `amount` tokens from `sender` to `recipient` using the\\n * allowance mechanism. `amount` is then deducted from the caller's\\n * allowance.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * Emits a {Transfer} event.\\n */\\n function transferFrom(\\n address sender,\\n address recipient,\\n uint256 amount\\n ) external returns (bool);\\n\\n /**\\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\\n * another (`to`).\\n *\\n * Note that `value` may be zero.\\n */\\n event Transfer(address indexed from, address indexed to, uint256 value);\\n\\n /**\\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\\n * a call to {approve}. `value` is the new allowance.\\n */\\n event Approval(address indexed owner, address indexed spender, uint256 value);\\n}\\n\",\"keccak256\":\"0x61437cb513a887a1bbad006e7b1c8b414478427d33de47c5600af3c748f108da\",\"license\":\"MIT\"},\"@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"../IERC20.sol\\\";\\n\\n/**\\n * @dev Interface for the optional metadata functions from the ERC20 standard.\\n *\\n * _Available since v4.1._\\n */\\ninterface IERC20Metadata is IERC20 {\\n /**\\n * @dev Returns the name of the token.\\n */\\n function name() external view returns (string memory);\\n\\n /**\\n * @dev Returns the symbol of the token.\\n */\\n function symbol() external view returns (string memory);\\n\\n /**\\n * @dev Returns the decimals places of the token.\\n */\\n function decimals() external view returns (uint8);\\n}\\n\",\"keccak256\":\"0x8de418a5503946cabe331f35fe242d3201a73f67f77aaeb7110acb1f30423aca\",\"license\":\"MIT\"},\"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (token/ERC20/utils/SafeERC20.sol)\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"../IERC20.sol\\\";\\nimport \\\"../../../utils/Address.sol\\\";\\n\\n/**\\n * @title SafeERC20\\n * @dev Wrappers around ERC20 operations that throw on failure (when the token\\n * contract returns false). Tokens that return no value (and instead revert or\\n * throw on failure) are also supported, non-reverting calls are assumed to be\\n * successful.\\n * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,\\n * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.\\n */\\nlibrary SafeERC20 {\\n using Address for address;\\n\\n function safeTransfer(\\n IERC20 token,\\n address to,\\n uint256 value\\n ) internal {\\n _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));\\n }\\n\\n function safeTransferFrom(\\n IERC20 token,\\n address from,\\n address to,\\n uint256 value\\n ) internal {\\n _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));\\n }\\n\\n /**\\n * @dev Deprecated. This function has issues similar to the ones found in\\n * {IERC20-approve}, and its usage is discouraged.\\n *\\n * Whenever possible, use {safeIncreaseAllowance} and\\n * {safeDecreaseAllowance} instead.\\n */\\n function safeApprove(\\n IERC20 token,\\n address spender,\\n uint256 value\\n ) internal {\\n // safeApprove should only be called when setting an initial allowance,\\n // or when resetting it to zero. To increase and decrease it, use\\n // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'\\n require(\\n (value == 0) || (token.allowance(address(this), spender) == 0),\\n \\\"SafeERC20: approve from non-zero to non-zero allowance\\\"\\n );\\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));\\n }\\n\\n function safeIncreaseAllowance(\\n IERC20 token,\\n address spender,\\n uint256 value\\n ) internal {\\n uint256 newAllowance = token.allowance(address(this), spender) + value;\\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));\\n }\\n\\n function safeDecreaseAllowance(\\n IERC20 token,\\n address spender,\\n uint256 value\\n ) internal {\\n unchecked {\\n uint256 oldAllowance = token.allowance(address(this), spender);\\n require(oldAllowance >= value, \\\"SafeERC20: decreased allowance below zero\\\");\\n uint256 newAllowance = oldAllowance - value;\\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));\\n }\\n }\\n\\n /**\\n * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement\\n * on the return value: the return value is optional (but if data is returned, it must not be false).\\n * @param token The token targeted by the call.\\n * @param data The call data (encoded using abi.encode or one of its variants).\\n */\\n function _callOptionalReturn(IERC20 token, bytes memory data) private {\\n // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since\\n // we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that\\n // the target address contains contract code and also asserts for success in the low-level call.\\n\\n bytes memory returndata = address(token).functionCall(data, \\\"SafeERC20: low-level call failed\\\");\\n if (returndata.length > 0) {\\n // Return data is optional\\n require(abi.decode(returndata, (bool)), \\\"SafeERC20: ERC20 operation did not succeed\\\");\\n }\\n }\\n}\\n\",\"keccak256\":\"0xc3d946432c0ddbb1f846a0d3985be71299df331b91d06732152117f62f0be2b5\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/Address.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (utils/Address.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev Collection of functions related to the address type\\n */\\nlibrary Address {\\n /**\\n * @dev Returns true if `account` is a contract.\\n *\\n * [IMPORTANT]\\n * ====\\n * It is unsafe to assume that an address for which this function returns\\n * false is an externally-owned account (EOA) and not a contract.\\n *\\n * Among others, `isContract` will return false for the following\\n * types of addresses:\\n *\\n * - an externally-owned account\\n * - a contract in construction\\n * - an address where a contract will be created\\n * - an address where a contract lived, but was destroyed\\n * ====\\n */\\n function isContract(address account) internal view returns (bool) {\\n // This method relies on extcodesize, which returns 0 for contracts in\\n // construction, since the code is only stored at the end of the\\n // constructor execution.\\n\\n uint256 size;\\n assembly {\\n size := extcodesize(account)\\n }\\n return size > 0;\\n }\\n\\n /**\\n * @dev Replacement for Solidity's `transfer`: sends `amount` wei to\\n * `recipient`, forwarding all available gas and reverting on errors.\\n *\\n * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost\\n * of certain opcodes, possibly making contracts go over the 2300 gas limit\\n * imposed by `transfer`, making them unable to receive funds via\\n * `transfer`. {sendValue} removes this limitation.\\n *\\n * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].\\n *\\n * IMPORTANT: because control is transferred to `recipient`, care must be\\n * taken to not create reentrancy vulnerabilities. Consider using\\n * {ReentrancyGuard} or the\\n * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].\\n */\\n function sendValue(address payable recipient, uint256 amount) internal {\\n require(address(this).balance >= amount, \\\"Address: insufficient balance\\\");\\n\\n (bool success, ) = recipient.call{value: amount}(\\\"\\\");\\n require(success, \\\"Address: unable to send value, recipient may have reverted\\\");\\n }\\n\\n /**\\n * @dev Performs a Solidity function call using a low level `call`. A\\n * plain `call` is an unsafe replacement for a function call: use this\\n * function instead.\\n *\\n * If `target` reverts with a revert reason, it is bubbled up by this\\n * function (like regular Solidity function calls).\\n *\\n * Returns the raw returned data. To convert to the expected return value,\\n * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].\\n *\\n * Requirements:\\n *\\n * - `target` must be a contract.\\n * - calling `target` with `data` must not revert.\\n *\\n * _Available since v3.1._\\n */\\n function functionCall(address target, bytes memory data) internal returns (bytes memory) {\\n return functionCall(target, data, \\\"Address: low-level call failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with\\n * `errorMessage` as a fallback revert reason when `target` reverts.\\n *\\n * _Available since v3.1._\\n */\\n function functionCall(\\n address target,\\n bytes memory data,\\n string memory errorMessage\\n ) internal returns (bytes memory) {\\n return functionCallWithValue(target, data, 0, errorMessage);\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\\n * but also transferring `value` wei to `target`.\\n *\\n * Requirements:\\n *\\n * - the calling contract must have an ETH balance of at least `value`.\\n * - the called Solidity function must be `payable`.\\n *\\n * _Available since v3.1._\\n */\\n function functionCallWithValue(\\n address target,\\n bytes memory data,\\n uint256 value\\n ) internal returns (bytes memory) {\\n return functionCallWithValue(target, data, value, \\\"Address: low-level call with value failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but\\n * with `errorMessage` as a fallback revert reason when `target` reverts.\\n *\\n * _Available since v3.1._\\n */\\n function functionCallWithValue(\\n address target,\\n bytes memory data,\\n uint256 value,\\n string memory errorMessage\\n ) internal returns (bytes memory) {\\n require(address(this).balance >= value, \\\"Address: insufficient balance for call\\\");\\n require(isContract(target), \\\"Address: call to non-contract\\\");\\n\\n (bool success, bytes memory returndata) = target.call{value: value}(data);\\n return verifyCallResult(success, returndata, errorMessage);\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\\n * but performing a static call.\\n *\\n * _Available since v3.3._\\n */\\n function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {\\n return functionStaticCall(target, data, \\\"Address: low-level static call failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\\n * but performing a static call.\\n *\\n * _Available since v3.3._\\n */\\n function functionStaticCall(\\n address target,\\n bytes memory data,\\n string memory errorMessage\\n ) internal view returns (bytes memory) {\\n require(isContract(target), \\\"Address: static call to non-contract\\\");\\n\\n (bool success, bytes memory returndata) = target.staticcall(data);\\n return verifyCallResult(success, returndata, errorMessage);\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\\n * but performing a delegate call.\\n *\\n * _Available since v3.4._\\n */\\n function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {\\n return functionDelegateCall(target, data, \\\"Address: low-level delegate call failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\\n * but performing a delegate call.\\n *\\n * _Available since v3.4._\\n */\\n function functionDelegateCall(\\n address target,\\n bytes memory data,\\n string memory errorMessage\\n ) internal returns (bytes memory) {\\n require(isContract(target), \\\"Address: delegate call to non-contract\\\");\\n\\n (bool success, bytes memory returndata) = target.delegatecall(data);\\n return verifyCallResult(success, returndata, errorMessage);\\n }\\n\\n /**\\n * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the\\n * revert reason using the provided one.\\n *\\n * _Available since v4.3._\\n */\\n function verifyCallResult(\\n bool success,\\n bytes memory returndata,\\n string memory errorMessage\\n ) internal pure returns (bytes memory) {\\n if (success) {\\n return returndata;\\n } else {\\n // Look for revert reason and bubble it up if present\\n if (returndata.length > 0) {\\n // The easiest way to bubble the revert reason is using memory via assembly\\n\\n assembly {\\n let returndata_size := mload(returndata)\\n revert(add(32, returndata), returndata_size)\\n }\\n } else {\\n revert(errorMessage);\\n }\\n }\\n }\\n}\\n\",\"keccak256\":\"0x51b758a8815ecc9596c66c37d56b1d33883a444631a3f916b9fe65cb863ef7c4\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/math/SafeCast.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (utils/math/SafeCast.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev Wrappers over Solidity's uintXX/intXX casting operators with added overflow\\n * checks.\\n *\\n * Downcasting from uint256/int256 in Solidity does not revert on overflow. This can\\n * easily result in undesired exploitation or bugs, since developers usually\\n * assume that overflows raise errors. `SafeCast` restores this intuition by\\n * reverting the transaction when such an operation overflows.\\n *\\n * Using this library instead of the unchecked operations eliminates an entire\\n * class of bugs, so it's recommended to use it always.\\n *\\n * Can be combined with {SafeMath} and {SignedSafeMath} to extend it to smaller types, by performing\\n * all math on `uint256` and `int256` and then downcasting.\\n */\\nlibrary SafeCast {\\n /**\\n * @dev Returns the downcasted uint224 from uint256, reverting on\\n * overflow (when the input is greater than largest uint224).\\n *\\n * Counterpart to Solidity's `uint224` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 224 bits\\n */\\n function toUint224(uint256 value) internal pure returns (uint224) {\\n require(value <= type(uint224).max, \\\"SafeCast: value doesn't fit in 224 bits\\\");\\n return uint224(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted uint128 from uint256, reverting on\\n * overflow (when the input is greater than largest uint128).\\n *\\n * Counterpart to Solidity's `uint128` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 128 bits\\n */\\n function toUint128(uint256 value) internal pure returns (uint128) {\\n require(value <= type(uint128).max, \\\"SafeCast: value doesn't fit in 128 bits\\\");\\n return uint128(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted uint96 from uint256, reverting on\\n * overflow (when the input is greater than largest uint96).\\n *\\n * Counterpart to Solidity's `uint96` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 96 bits\\n */\\n function toUint96(uint256 value) internal pure returns (uint96) {\\n require(value <= type(uint96).max, \\\"SafeCast: value doesn't fit in 96 bits\\\");\\n return uint96(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted uint64 from uint256, reverting on\\n * overflow (when the input is greater than largest uint64).\\n *\\n * Counterpart to Solidity's `uint64` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 64 bits\\n */\\n function toUint64(uint256 value) internal pure returns (uint64) {\\n require(value <= type(uint64).max, \\\"SafeCast: value doesn't fit in 64 bits\\\");\\n return uint64(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted uint32 from uint256, reverting on\\n * overflow (when the input is greater than largest uint32).\\n *\\n * Counterpart to Solidity's `uint32` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 32 bits\\n */\\n function toUint32(uint256 value) internal pure returns (uint32) {\\n require(value <= type(uint32).max, \\\"SafeCast: value doesn't fit in 32 bits\\\");\\n return uint32(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted uint16 from uint256, reverting on\\n * overflow (when the input is greater than largest uint16).\\n *\\n * Counterpart to Solidity's `uint16` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 16 bits\\n */\\n function toUint16(uint256 value) internal pure returns (uint16) {\\n require(value <= type(uint16).max, \\\"SafeCast: value doesn't fit in 16 bits\\\");\\n return uint16(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted uint8 from uint256, reverting on\\n * overflow (when the input is greater than largest uint8).\\n *\\n * Counterpart to Solidity's `uint8` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 8 bits.\\n */\\n function toUint8(uint256 value) internal pure returns (uint8) {\\n require(value <= type(uint8).max, \\\"SafeCast: value doesn't fit in 8 bits\\\");\\n return uint8(value);\\n }\\n\\n /**\\n * @dev Converts a signed int256 into an unsigned uint256.\\n *\\n * Requirements:\\n *\\n * - input must be greater than or equal to 0.\\n */\\n function toUint256(int256 value) internal pure returns (uint256) {\\n require(value >= 0, \\\"SafeCast: value must be positive\\\");\\n return uint256(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted int128 from int256, reverting on\\n * overflow (when the input is less than smallest int128 or\\n * greater than largest int128).\\n *\\n * Counterpart to Solidity's `int128` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 128 bits\\n *\\n * _Available since v3.1._\\n */\\n function toInt128(int256 value) internal pure returns (int128) {\\n require(value >= type(int128).min && value <= type(int128).max, \\\"SafeCast: value doesn't fit in 128 bits\\\");\\n return int128(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted int64 from int256, reverting on\\n * overflow (when the input is less than smallest int64 or\\n * greater than largest int64).\\n *\\n * Counterpart to Solidity's `int64` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 64 bits\\n *\\n * _Available since v3.1._\\n */\\n function toInt64(int256 value) internal pure returns (int64) {\\n require(value >= type(int64).min && value <= type(int64).max, \\\"SafeCast: value doesn't fit in 64 bits\\\");\\n return int64(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted int32 from int256, reverting on\\n * overflow (when the input is less than smallest int32 or\\n * greater than largest int32).\\n *\\n * Counterpart to Solidity's `int32` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 32 bits\\n *\\n * _Available since v3.1._\\n */\\n function toInt32(int256 value) internal pure returns (int32) {\\n require(value >= type(int32).min && value <= type(int32).max, \\\"SafeCast: value doesn't fit in 32 bits\\\");\\n return int32(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted int16 from int256, reverting on\\n * overflow (when the input is less than smallest int16 or\\n * greater than largest int16).\\n *\\n * Counterpart to Solidity's `int16` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 16 bits\\n *\\n * _Available since v3.1._\\n */\\n function toInt16(int256 value) internal pure returns (int16) {\\n require(value >= type(int16).min && value <= type(int16).max, \\\"SafeCast: value doesn't fit in 16 bits\\\");\\n return int16(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted int8 from int256, reverting on\\n * overflow (when the input is less than smallest int8 or\\n * greater than largest int8).\\n *\\n * Counterpart to Solidity's `int8` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 8 bits.\\n *\\n * _Available since v3.1._\\n */\\n function toInt8(int256 value) internal pure returns (int8) {\\n require(value >= type(int8).min && value <= type(int8).max, \\\"SafeCast: value doesn't fit in 8 bits\\\");\\n return int8(value);\\n }\\n\\n /**\\n * @dev Converts an unsigned uint256 into a signed int256.\\n *\\n * Requirements:\\n *\\n * - input must be less than or equal to maxInt256.\\n */\\n function toInt256(uint256 value) internal pure returns (int256) {\\n // Note: Unsafe cast below is okay because `type(int256).max` is guaranteed to be positive\\n require(value <= uint256(type(int256).max), \\\"SafeCast: value doesn't fit in an int256\\\");\\n return int256(value);\\n }\\n}\\n\",\"keccak256\":\"0x5c6caab697d302ad7eb59c234a4d2dbc965c1bae87709bd2850060b7695b28c7\",\"license\":\"MIT\"},\"contracts/governance/Governable.sol\":{\"content\":\"// SPDX-License-Identifier: BUSL-1.1\\npragma solidity ^0.8.0;\\n\\n/**\\n * @title Base for contracts that are managed by the Origin Protocol's Governor.\\n * @dev Copy of the openzeppelin Ownable.sol contract with nomenclature change\\n * from owner to governor and renounce methods removed. Does not use\\n * Context.sol like Ownable.sol does for simplification.\\n * @author Origin Protocol Inc\\n */\\nabstract contract Governable {\\n // Storage position of the owner and pendingOwner of the contract\\n // keccak256(\\\"OUSD.governor\\\");\\n bytes32 private constant governorPosition =\\n 0x7bea13895fa79d2831e0a9e28edede30099005a50d652d8957cf8a607ee6ca4a;\\n\\n // keccak256(\\\"OUSD.pending.governor\\\");\\n bytes32 private constant pendingGovernorPosition =\\n 0x44c4d30b2eaad5130ad70c3ba6972730566f3e6359ab83e800d905c61b1c51db;\\n\\n // keccak256(\\\"OUSD.reentry.status\\\");\\n bytes32 private constant reentryStatusPosition =\\n 0x53bf423e48ed90e97d02ab0ebab13b2a235a6bfbe9c321847d5c175333ac4535;\\n\\n // See OpenZeppelin ReentrancyGuard implementation\\n uint256 constant _NOT_ENTERED = 1;\\n uint256 constant _ENTERED = 2;\\n\\n event PendingGovernorshipTransfer(\\n address indexed previousGovernor,\\n address indexed newGovernor\\n );\\n\\n event GovernorshipTransferred(\\n address indexed previousGovernor,\\n address indexed newGovernor\\n );\\n\\n /**\\n * @notice Returns the address of the current Governor.\\n */\\n function governor() public view returns (address) {\\n return _governor();\\n }\\n\\n /**\\n * @dev Returns the address of the current Governor.\\n */\\n function _governor() internal view returns (address governorOut) {\\n bytes32 position = governorPosition;\\n // solhint-disable-next-line no-inline-assembly\\n assembly {\\n governorOut := sload(position)\\n }\\n }\\n\\n /**\\n * @dev Returns the address of the pending Governor.\\n */\\n function _pendingGovernor()\\n internal\\n view\\n returns (address pendingGovernor)\\n {\\n bytes32 position = pendingGovernorPosition;\\n // solhint-disable-next-line no-inline-assembly\\n assembly {\\n pendingGovernor := sload(position)\\n }\\n }\\n\\n /**\\n * @dev Throws if called by any account other than the Governor.\\n */\\n modifier onlyGovernor() {\\n require(isGovernor(), \\\"Caller is not the Governor\\\");\\n _;\\n }\\n\\n /**\\n * @notice Returns true if the caller is the current Governor.\\n */\\n function isGovernor() public view returns (bool) {\\n return msg.sender == _governor();\\n }\\n\\n function _setGovernor(address newGovernor) internal {\\n emit GovernorshipTransferred(_governor(), newGovernor);\\n\\n bytes32 position = governorPosition;\\n // solhint-disable-next-line no-inline-assembly\\n assembly {\\n sstore(position, newGovernor)\\n }\\n }\\n\\n /**\\n * @dev Prevents a contract from calling itself, directly or indirectly.\\n * Calling a `nonReentrant` function from another `nonReentrant`\\n * function is not supported. It is possible to prevent this from happening\\n * by making the `nonReentrant` function external, and make it call a\\n * `private` function that does the actual work.\\n */\\n modifier nonReentrant() {\\n bytes32 position = reentryStatusPosition;\\n uint256 _reentry_status;\\n // solhint-disable-next-line no-inline-assembly\\n assembly {\\n _reentry_status := sload(position)\\n }\\n\\n // On the first call to nonReentrant, _notEntered will be true\\n require(_reentry_status != _ENTERED, \\\"Reentrant call\\\");\\n\\n // Any calls to nonReentrant after this point will fail\\n // solhint-disable-next-line no-inline-assembly\\n assembly {\\n sstore(position, _ENTERED)\\n }\\n\\n _;\\n\\n // By storing the original value once again, a refund is triggered (see\\n // https://eips.ethereum.org/EIPS/eip-2200)\\n // solhint-disable-next-line no-inline-assembly\\n assembly {\\n sstore(position, _NOT_ENTERED)\\n }\\n }\\n\\n function _setPendingGovernor(address newGovernor) internal {\\n bytes32 position = pendingGovernorPosition;\\n // solhint-disable-next-line no-inline-assembly\\n assembly {\\n sstore(position, newGovernor)\\n }\\n }\\n\\n /**\\n * @notice Transfers Governance of the contract to a new account (`newGovernor`).\\n * Can only be called by the current Governor. Must be claimed for this to complete\\n * @param _newGovernor Address of the new Governor\\n */\\n function transferGovernance(address _newGovernor) external onlyGovernor {\\n _setPendingGovernor(_newGovernor);\\n emit PendingGovernorshipTransfer(_governor(), _newGovernor);\\n }\\n\\n /**\\n * @notice Claim Governance of the contract to a new account (`newGovernor`).\\n * Can only be called by the new Governor.\\n */\\n function claimGovernance() external {\\n require(\\n msg.sender == _pendingGovernor(),\\n \\\"Only the pending Governor can complete the claim\\\"\\n );\\n _changeGovernor(msg.sender);\\n }\\n\\n /**\\n * @dev Change Governance of the contract to a new account (`newGovernor`).\\n * @param _newGovernor Address of the new Governor\\n */\\n function _changeGovernor(address _newGovernor) internal {\\n require(_newGovernor != address(0), \\\"New Governor is address(0)\\\");\\n _setGovernor(_newGovernor);\\n }\\n}\\n\",\"keccak256\":\"0xf32f873c8bfbacf2e5f01d0cf37bc7f54fbd5aa656e95c8a599114229946f107\",\"license\":\"BUSL-1.1\"},\"contracts/governance/Strategizable.sol\":{\"content\":\"// SPDX-License-Identifier: BUSL-1.1\\npragma solidity ^0.8.0;\\n\\nimport { Governable } from \\\"./Governable.sol\\\";\\n\\ncontract Strategizable is Governable {\\n event StrategistUpdated(address _address);\\n\\n // Address of strategist\\n address public strategistAddr;\\n\\n // For future use\\n uint256[50] private __gap;\\n\\n /**\\n * @dev Verifies that the caller is either Governor or Strategist.\\n */\\n modifier onlyGovernorOrStrategist() virtual {\\n require(\\n msg.sender == strategistAddr || isGovernor(),\\n \\\"Caller is not the Strategist or Governor\\\"\\n );\\n _;\\n }\\n\\n /**\\n * @dev Set address of Strategist\\n * @param _address Address of Strategist\\n */\\n function setStrategistAddr(address _address) external onlyGovernor {\\n _setStrategistAddr(_address);\\n }\\n\\n /**\\n * @dev Set address of Strategist\\n * @param _address Address of Strategist\\n */\\n function _setStrategistAddr(address _address) internal {\\n strategistAddr = _address;\\n emit StrategistUpdated(_address);\\n }\\n}\\n\",\"keccak256\":\"0x9989400db5353221a725dd76a8b18c27e5c0b24c4732f3bb1e9324640a53290b\",\"license\":\"BUSL-1.1\"},\"contracts/interfaces/IBasicToken.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.0;\\n\\ninterface IBasicToken {\\n function symbol() external view returns (string memory);\\n\\n function decimals() external view returns (uint8);\\n}\\n\",\"keccak256\":\"0xa562062698aa12572123b36dfd2072f1a39e44fed2031cc19c2c9fd522f96ec2\",\"license\":\"MIT\"},\"contracts/interfaces/IMerkl.sol\":{\"content\":\"// SPDX-License-Identifier: BUSL-1.1\\npragma solidity ^0.8.0;\\n\\ninterface IDistributor {\\n event Claimed(address indexed user, address indexed token, uint256 amount);\\n\\n function claim(\\n address[] calldata users,\\n address[] calldata tokens,\\n uint256[] calldata amounts,\\n bytes32[][] calldata proofs\\n ) external;\\n}\\n\",\"keccak256\":\"0xdf9acbbc3e7c00135f5428b8a2cc8a5a31e3d095bb27cd93a58c546a0b606697\",\"license\":\"BUSL-1.1\"},\"contracts/interfaces/IStrategy.sol\":{\"content\":\"// SPDX-License-Identifier: BUSL-1.1\\npragma solidity ^0.8.0;\\n\\n/**\\n * @title Platform interface to integrate with lending platform like Compound, AAVE etc.\\n */\\ninterface IStrategy {\\n /**\\n * @dev Deposit the given asset to platform\\n * @param _asset asset address\\n * @param _amount Amount to deposit\\n */\\n function deposit(address _asset, uint256 _amount) external;\\n\\n /**\\n * @dev Deposit the entire balance of all supported assets in the Strategy\\n * to the platform\\n */\\n function depositAll() external;\\n\\n /**\\n * @dev Withdraw given asset from Lending platform\\n */\\n function withdraw(\\n address _recipient,\\n address _asset,\\n uint256 _amount\\n ) external;\\n\\n /**\\n * @dev Liquidate all assets in strategy and return them to Vault.\\n */\\n function withdrawAll() external;\\n\\n /**\\n * @dev Returns the current balance of the given asset.\\n */\\n function checkBalance(address _asset)\\n external\\n view\\n returns (uint256 balance);\\n\\n /**\\n * @dev Returns bool indicating whether strategy supports asset.\\n */\\n function supportsAsset(address _asset) external view returns (bool);\\n\\n /**\\n * @dev Collect reward tokens from the Strategy.\\n */\\n function collectRewardTokens() external;\\n\\n /**\\n * @dev The address array of the reward tokens for the Strategy.\\n */\\n function getRewardTokenAddresses() external view returns (address[] memory);\\n\\n function harvesterAddress() external view returns (address);\\n\\n function transferToken(address token, uint256 amount) external;\\n\\n function setRewardTokenAddresses(address[] calldata _rewardTokenAddresses)\\n external;\\n}\\n\",\"keccak256\":\"0x79ca47defb3b5a56bba13f14c440838152fd1c1aa640476154516a16da4da8ba\",\"license\":\"BUSL-1.1\"},\"contracts/interfaces/IVault.sol\":{\"content\":\"// SPDX-License-Identifier: BUSL-1.1\\npragma solidity ^0.8.0;\\n\\nimport { VaultStorage } from \\\"../vault/VaultStorage.sol\\\";\\n\\ninterface IVault {\\n // slither-disable-start constable-states\\n\\n event AssetAllocated(address _asset, address _strategy, uint256 _amount);\\n event StrategyApproved(address _addr);\\n event StrategyRemoved(address _addr);\\n event Mint(address _addr, uint256 _value);\\n event Redeem(address _addr, uint256 _value);\\n event CapitalPaused();\\n event CapitalUnpaused();\\n event DefaultStrategyUpdated(address _strategy);\\n event RebasePaused();\\n event RebaseUnpaused();\\n event VaultBufferUpdated(uint256 _vaultBuffer);\\n event AllocateThresholdUpdated(uint256 _threshold);\\n event RebaseThresholdUpdated(uint256 _threshold);\\n event StrategistUpdated(address _address);\\n event MaxSupplyDiffChanged(uint256 maxSupplyDiff);\\n event YieldDistribution(address _to, uint256 _yield, uint256 _fee);\\n event TrusteeFeeBpsChanged(uint256 _basis);\\n event TrusteeAddressChanged(address _address);\\n event StrategyAddedToMintWhitelist(address indexed strategy);\\n event StrategyRemovedFromMintWhitelist(address indexed strategy);\\n event RebasePerSecondMaxChanged(uint256 rebaseRatePerSecond);\\n event DripDurationChanged(uint256 dripDuration);\\n event WithdrawalRequested(\\n address indexed _withdrawer,\\n uint256 indexed _requestId,\\n uint256 _amount,\\n uint256 _queued\\n );\\n event WithdrawalClaimed(\\n address indexed _withdrawer,\\n uint256 indexed _requestId,\\n uint256 _amount\\n );\\n event WithdrawalClaimable(uint256 _claimable, uint256 _newClaimable);\\n event WithdrawalClaimDelayUpdated(uint256 _newDelay);\\n\\n // Governable.sol\\n function transferGovernance(address _newGovernor) external;\\n\\n function claimGovernance() external;\\n\\n function governor() external view returns (address);\\n\\n // VaultAdmin.sol\\n function setVaultBuffer(uint256 _vaultBuffer) external;\\n\\n function vaultBuffer() external view returns (uint256);\\n\\n function setAutoAllocateThreshold(uint256 _threshold) external;\\n\\n function autoAllocateThreshold() external view returns (uint256);\\n\\n function setRebaseThreshold(uint256 _threshold) external;\\n\\n function rebaseThreshold() external view returns (uint256);\\n\\n function setStrategistAddr(address _address) external;\\n\\n function strategistAddr() external view returns (address);\\n\\n function setMaxSupplyDiff(uint256 _maxSupplyDiff) external;\\n\\n function maxSupplyDiff() external view returns (uint256);\\n\\n function setTrusteeAddress(address _address) external;\\n\\n function trusteeAddress() external view returns (address);\\n\\n function setTrusteeFeeBps(uint256 _basis) external;\\n\\n function trusteeFeeBps() external view returns (uint256);\\n\\n function approveStrategy(address _addr) external;\\n\\n function removeStrategy(address _addr) external;\\n\\n function setDefaultStrategy(address _strategy) external;\\n\\n function defaultStrategy() external view returns (address);\\n\\n function pauseRebase() external;\\n\\n function unpauseRebase() external;\\n\\n function rebasePaused() external view returns (bool);\\n\\n function pauseCapital() external;\\n\\n function unpauseCapital() external;\\n\\n function capitalPaused() external view returns (bool);\\n\\n function transferToken(address _asset, uint256 _amount) external;\\n\\n function withdrawAllFromStrategy(address _strategyAddr) external;\\n\\n function withdrawAllFromStrategies() external;\\n\\n function withdrawFromStrategy(\\n address _strategyFromAddress,\\n address[] calldata _assets,\\n uint256[] calldata _amounts\\n ) external;\\n\\n function depositToStrategy(\\n address _strategyToAddress,\\n address[] calldata _assets,\\n uint256[] calldata _amounts\\n ) external;\\n\\n // VaultCore.sol\\n function mint(\\n address _asset,\\n uint256 _amount,\\n uint256 _minimumOusdAmount\\n ) external;\\n\\n function mintForStrategy(uint256 _amount) external;\\n\\n function redeem(uint256 _amount, uint256 _minimumUnitAmount) external;\\n\\n function burnForStrategy(uint256 _amount) external;\\n\\n function allocate() external;\\n\\n function rebase() external;\\n\\n function totalValue() external view returns (uint256 value);\\n\\n function checkBalance(address _asset) external view returns (uint256);\\n\\n /// @notice Deprecated: use calculateRedeemOutput\\n function calculateRedeemOutputs(uint256 _amount)\\n external\\n view\\n returns (uint256[] memory);\\n\\n function calculateRedeemOutput(uint256 _amount)\\n external\\n view\\n returns (uint256);\\n\\n function getAssetCount() external view returns (uint256);\\n\\n function getAllAssets() external view returns (address[] memory);\\n\\n function getStrategyCount() external view returns (uint256);\\n\\n function getAllStrategies() external view returns (address[] memory);\\n\\n /// @notice Deprecated.\\n function isSupportedAsset(address _asset) external view returns (bool);\\n\\n function dripper() external view returns (address);\\n\\n function asset() external view returns (address);\\n\\n function initialize(address) external;\\n\\n function addWithdrawalQueueLiquidity() external;\\n\\n function requestWithdrawal(uint256 _amount)\\n external\\n returns (uint256 requestId, uint256 queued);\\n\\n function claimWithdrawal(uint256 requestId)\\n external\\n returns (uint256 amount);\\n\\n function claimWithdrawals(uint256[] memory requestIds)\\n external\\n returns (uint256[] memory amounts, uint256 totalAmount);\\n\\n function withdrawalQueueMetadata()\\n external\\n view\\n returns (VaultStorage.WithdrawalQueueMetadata memory);\\n\\n function withdrawalRequests(uint256 requestId)\\n external\\n view\\n returns (VaultStorage.WithdrawalRequest memory);\\n\\n function addStrategyToMintWhitelist(address strategyAddr) external;\\n\\n function removeStrategyFromMintWhitelist(address strategyAddr) external;\\n\\n function isMintWhitelistedStrategy(address strategyAddr)\\n external\\n view\\n returns (bool);\\n\\n function withdrawalClaimDelay() external view returns (uint256);\\n\\n function setWithdrawalClaimDelay(uint256 newDelay) external;\\n\\n function lastRebase() external view returns (uint64);\\n\\n function dripDuration() external view returns (uint64);\\n\\n function setDripDuration(uint256 _dripDuration) external;\\n\\n function rebasePerSecondMax() external view returns (uint64);\\n\\n function setRebaseRateMax(uint256 yearlyApr) external;\\n\\n function rebasePerSecondTarget() external view returns (uint64);\\n\\n function previewYield() external view returns (uint256 yield);\\n\\n function weth() external view returns (address);\\n\\n // slither-disable-end constable-states\\n}\\n\",\"keccak256\":\"0x61e2ad6f41abac69275ba86e51d9d3cd95dfd6fc6b81960cf75b1e06adb6251d\",\"license\":\"BUSL-1.1\"},\"contracts/interfaces/cctp/ICCTP.sol\":{\"content\":\"// SPDX-License-Identifier: BUSL-1.1\\npragma solidity ^0.8.0;\\n\\ninterface ICCTPTokenMessenger {\\n function depositForBurn(\\n uint256 amount,\\n uint32 destinationDomain,\\n bytes32 mintRecipient,\\n address burnToken,\\n bytes32 destinationCaller,\\n uint256 maxFee,\\n uint32 minFinalityThreshold\\n ) external;\\n\\n function depositForBurnWithHook(\\n uint256 amount,\\n uint32 destinationDomain,\\n bytes32 mintRecipient,\\n address burnToken,\\n bytes32 destinationCaller,\\n uint256 maxFee,\\n uint32 minFinalityThreshold,\\n bytes memory hookData\\n ) external;\\n\\n function getMinFeeAmount(uint256 amount) external view returns (uint256);\\n}\\n\\ninterface ICCTPMessageTransmitter {\\n function sendMessage(\\n uint32 destinationDomain,\\n bytes32 recipient,\\n bytes32 destinationCaller,\\n uint32 minFinalityThreshold,\\n bytes memory messageBody\\n ) external;\\n\\n function receiveMessage(bytes calldata message, bytes calldata attestation)\\n external\\n returns (bool);\\n}\\n\\ninterface IMessageHandlerV2 {\\n /**\\n * @notice Handles an incoming finalized message from an IReceiverV2\\n * @dev Finalized messages have finality threshold values greater than or equal to 2000\\n * @param sourceDomain The source domain of the message\\n * @param sender The sender of the message\\n * @param finalityThresholdExecuted the finality threshold at which the message was attested to\\n * @param messageBody The raw bytes of the message body\\n * @return success True, if successful; false, if not.\\n */\\n function handleReceiveFinalizedMessage(\\n uint32 sourceDomain,\\n bytes32 sender,\\n uint32 finalityThresholdExecuted,\\n bytes calldata messageBody\\n ) external returns (bool);\\n\\n /**\\n * @notice Handles an incoming unfinalized message from an IReceiverV2\\n * @dev Unfinalized messages have finality threshold values less than 2000\\n * @param sourceDomain The source domain of the message\\n * @param sender The sender of the message\\n * @param finalityThresholdExecuted The finality threshold at which the message was attested to\\n * @param messageBody The raw bytes of the message body\\n * @return success True, if successful; false, if not.\\n */\\n function handleReceiveUnfinalizedMessage(\\n uint32 sourceDomain,\\n bytes32 sender,\\n uint32 finalityThresholdExecuted,\\n bytes calldata messageBody\\n ) external returns (bool);\\n}\\n\",\"keccak256\":\"0x67c60863105b4a694bc6edc77777560120262fd4f883a5bbece53f0c70d88e7b\",\"license\":\"BUSL-1.1\"},\"contracts/strategies/Generalized4626Strategy.sol\":{\"content\":\"// SPDX-License-Identifier: BUSL-1.1\\npragma solidity ^0.8.0;\\n\\n/**\\n * @title Generalized 4626 Strategy\\n * @notice Investment strategy for ERC-4626 Tokenized Vaults\\n * @author Origin Protocol Inc\\n */\\nimport { IERC4626 } from \\\"../../lib/openzeppelin/interfaces/IERC4626.sol\\\";\\nimport { IERC20, InitializableAbstractStrategy } from \\\"../utils/InitializableAbstractStrategy.sol\\\";\\nimport { IDistributor } from \\\"../interfaces/IMerkl.sol\\\";\\n\\ncontract Generalized4626Strategy is InitializableAbstractStrategy {\\n /// @notice The address of the Merkle Distributor contract.\\n IDistributor public constant merkleDistributor =\\n IDistributor(0x3Ef3D8bA38EBe18DB133cEc108f4D14CE00Dd9Ae);\\n\\n /// @dev Replaced with an immutable variable\\n // slither-disable-next-line constable-states\\n address private _deprecate_shareToken;\\n /// @dev Replaced with an immutable variable\\n // slither-disable-next-line constable-states\\n address private _deprecate_assetToken;\\n\\n IERC20 public immutable shareToken;\\n IERC20 public immutable assetToken;\\n\\n // For future use\\n uint256[50] private __gap;\\n\\n event ClaimedRewards(address indexed token, uint256 amount);\\n\\n /**\\n * @param _baseConfig Base strategy config with platformAddress (ERC-4626 Vault contract), eg sfrxETH or sDAI,\\n * and vaultAddress (OToken Vault contract), eg VaultProxy or OETHVaultProxy\\n * @param _assetToken Address of the ERC-4626 asset token. eg frxETH or DAI\\n */\\n constructor(BaseStrategyConfig memory _baseConfig, address _assetToken)\\n InitializableAbstractStrategy(_baseConfig)\\n {\\n shareToken = IERC20(_baseConfig.platformAddress);\\n assetToken = IERC20(_assetToken);\\n }\\n\\n function initialize() external virtual onlyGovernor initializer {\\n address[] memory rewardTokens = new address[](0);\\n address[] memory assets = new address[](1);\\n address[] memory pTokens = new address[](1);\\n\\n assets[0] = address(assetToken);\\n pTokens[0] = address(platformAddress);\\n\\n InitializableAbstractStrategy._initialize(\\n rewardTokens,\\n assets,\\n pTokens\\n );\\n }\\n\\n /**\\n * @dev Deposit assets by converting them to shares\\n * @param _asset Address of asset to deposit\\n * @param _amount Amount of asset to deposit\\n */\\n function deposit(address _asset, uint256 _amount)\\n external\\n virtual\\n override\\n onlyVault\\n nonReentrant\\n {\\n _deposit(_asset, _amount);\\n }\\n\\n /**\\n * @dev Deposit assets by converting them to shares\\n * @param _asset Address of asset to deposit\\n * @param _amount Amount of asset to deposit\\n */\\n function _deposit(address _asset, uint256 _amount) internal virtual {\\n require(_amount > 0, \\\"Must deposit something\\\");\\n require(_asset == address(assetToken), \\\"Unexpected asset address\\\");\\n\\n // slither-disable-next-line unused-return\\n IERC4626(platformAddress).deposit(_amount, address(this));\\n emit Deposit(_asset, address(shareToken), _amount);\\n }\\n\\n /**\\n * @dev Deposit the entire balance of assetToken to gain shareToken\\n */\\n function depositAll() external virtual override onlyVault nonReentrant {\\n uint256 balance = assetToken.balanceOf(address(this));\\n if (balance > 0) {\\n _deposit(address(assetToken), balance);\\n }\\n }\\n\\n /**\\n * @dev Withdraw asset by burning shares\\n * @param _recipient Address to receive withdrawn asset\\n * @param _asset Address of asset to withdraw\\n * @param _amount Amount of asset to withdraw\\n */\\n function withdraw(\\n address _recipient,\\n address _asset,\\n uint256 _amount\\n ) external virtual override onlyVault nonReentrant {\\n _withdraw(_recipient, _asset, _amount);\\n }\\n\\n function _withdraw(\\n address _recipient,\\n address _asset,\\n uint256 _amount\\n ) internal virtual {\\n require(_amount > 0, \\\"Must withdraw something\\\");\\n require(_recipient != address(0), \\\"Must specify recipient\\\");\\n require(_asset == address(assetToken), \\\"Unexpected asset address\\\");\\n\\n // slither-disable-next-line unused-return\\n IERC4626(platformAddress).withdraw(_amount, _recipient, address(this));\\n emit Withdrawal(_asset, address(shareToken), _amount);\\n }\\n\\n /**\\n * @dev Internal method to respond to the addition of new asset / share tokens\\n */\\n function _abstractSetPToken(address, address) internal virtual override {\\n _approveBase();\\n }\\n\\n /**\\n * @dev Remove all assets from platform and send them to Vault contract.\\n */\\n function withdrawAll()\\n external\\n virtual\\n override\\n onlyVaultOrGovernor\\n nonReentrant\\n {\\n uint256 shareBalance = shareToken.balanceOf(address(this));\\n uint256 assetAmount = 0;\\n if (shareBalance > 0) {\\n assetAmount = IERC4626(platformAddress).redeem(\\n shareBalance,\\n vaultAddress,\\n address(this)\\n );\\n emit Withdrawal(\\n address(assetToken),\\n address(shareToken),\\n assetAmount\\n );\\n }\\n }\\n\\n /**\\n * @notice Get the total asset value held in the platform\\n * @param _asset Address of the asset\\n * @return balance Total value of the asset in the platform\\n */\\n function checkBalance(address _asset)\\n public\\n view\\n virtual\\n override\\n returns (uint256 balance)\\n {\\n require(_asset == address(assetToken), \\\"Unexpected asset address\\\");\\n /* We are intentionally not counting the amount of assetToken parked on the\\n * contract toward the checkBalance. The deposit and withdraw functions\\n * should not result in assetToken being unused and owned by this strategy\\n * contract.\\n */\\n IERC4626 platform = IERC4626(platformAddress);\\n return platform.previewRedeem(platform.balanceOf(address(this)));\\n }\\n\\n /**\\n * @notice Governor approves the ERC-4626 Tokenized Vault to spend the asset.\\n */\\n function safeApproveAllTokens() external override onlyGovernor {\\n _approveBase();\\n }\\n\\n function _approveBase() internal virtual {\\n // Approval the asset to be transferred to the ERC-4626 Tokenized Vault.\\n // Used by the ERC-4626 deposit() and mint() functions\\n // slither-disable-next-line unused-return\\n assetToken.approve(platformAddress, type(uint256).max);\\n }\\n\\n /**\\n * @dev Returns bool indicating whether asset is supported by strategy\\n * @param _asset Address of the asset\\n */\\n function supportsAsset(address _asset)\\n public\\n view\\n virtual\\n override\\n returns (bool)\\n {\\n return _asset == address(assetToken);\\n }\\n\\n /**\\n * @notice is not supported for this strategy as the asset and\\n * ERC-4626 Tokenized Vault are set at deploy time.\\n * @dev If the ERC-4626 Tokenized Vault needed to be changed, a new\\n * contract would need to be deployed and the proxy updated.\\n */\\n function setPTokenAddress(address, address) external override onlyGovernor {\\n revert(\\\"unsupported function\\\");\\n }\\n\\n /**\\n * @notice is not supported for this strategy as the asset and\\n * ERC-4626 Tokenized Vault are set at deploy time.\\n * @dev If the ERC-4626 Tokenized Vault needed to be changed, a new\\n * contract would need to be deployed and the proxy updated.\\n */\\n function removePToken(uint256) external override onlyGovernor {\\n revert(\\\"unsupported function\\\");\\n }\\n\\n /// @notice Claim tokens from the Merkle Distributor\\n /// @param token The address of the token to claim.\\n /// @param amount The amount of tokens to claim.\\n /// @param proof The Merkle proof to validate the claim.\\n function merkleClaim(\\n address token,\\n uint256 amount,\\n bytes32[] calldata proof\\n ) external {\\n address[] memory users = new address[](1);\\n users[0] = address(this);\\n\\n address[] memory tokens = new address[](1);\\n tokens[0] = token;\\n\\n uint256[] memory amounts = new uint256[](1);\\n amounts[0] = amount;\\n\\n bytes32[][] memory proofs = new bytes32[][](1);\\n proofs[0] = proof;\\n\\n merkleDistributor.claim(users, tokens, amounts, proofs);\\n\\n emit ClaimedRewards(token, amount);\\n }\\n}\\n\",\"keccak256\":\"0xf03f2e6ad89700985bfa22a6bafd30eccee765fd6cad97e272f983085ff9c7a0\",\"license\":\"BUSL-1.1\"},\"contracts/strategies/crosschain/AbstractCCTPIntegrator.sol\":{\"content\":\"// SPDX-License-Identifier: BUSL-1.1\\npragma solidity ^0.8.0;\\n\\n/**\\n * @title AbstractCCTPIntegrator\\n * @author Origin Protocol Inc\\n *\\n * @dev Abstract contract that contains all the logic used to integrate with CCTP.\\n */\\n\\nimport { SafeERC20 } from \\\"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\\\";\\nimport { IERC20 } from \\\"../../utils/InitializableAbstractStrategy.sol\\\";\\n\\nimport { ICCTPTokenMessenger, ICCTPMessageTransmitter, IMessageHandlerV2 } from \\\"../../interfaces/cctp/ICCTP.sol\\\";\\n\\nimport { CrossChainStrategyHelper } from \\\"./CrossChainStrategyHelper.sol\\\";\\nimport { Governable } from \\\"../../governance/Governable.sol\\\";\\nimport { BytesHelper } from \\\"../../utils/BytesHelper.sol\\\";\\nimport \\\"../../utils/Helpers.sol\\\";\\n\\nabstract contract AbstractCCTPIntegrator is Governable, IMessageHandlerV2 {\\n using SafeERC20 for IERC20;\\n\\n using BytesHelper for bytes;\\n using CrossChainStrategyHelper for bytes;\\n\\n event LastTransferNonceUpdated(uint64 lastTransferNonce);\\n event NonceProcessed(uint64 nonce);\\n\\n event CCTPMinFinalityThresholdSet(uint16 minFinalityThreshold);\\n event CCTPFeePremiumBpsSet(uint16 feePremiumBps);\\n event OperatorChanged(address operator);\\n event TokensBridged(\\n uint32 destinationDomain,\\n address peerStrategy,\\n address tokenAddress,\\n uint256 tokenAmount,\\n uint256 maxFee,\\n uint32 minFinalityThreshold,\\n bytes hookData\\n );\\n event MessageTransmitted(\\n uint32 destinationDomain,\\n address peerStrategy,\\n uint32 minFinalityThreshold,\\n bytes message\\n );\\n\\n // Message body V2 fields\\n // Ref: https://developers.circle.com/cctp/technical-guide#message-body\\n // Ref: https://github.com/circlefin/evm-cctp-contracts/blob/master/src/messages/v2/BurnMessageV2.sol\\n uint8 private constant BURN_MESSAGE_V2_VERSION_INDEX = 0;\\n uint8 private constant BURN_MESSAGE_V2_BURN_TOKEN_INDEX = 4;\\n uint8 private constant BURN_MESSAGE_V2_RECIPIENT_INDEX = 36;\\n uint8 private constant BURN_MESSAGE_V2_AMOUNT_INDEX = 68;\\n uint8 private constant BURN_MESSAGE_V2_MESSAGE_SENDER_INDEX = 100;\\n uint8 private constant BURN_MESSAGE_V2_FEE_EXECUTED_INDEX = 164;\\n uint8 private constant BURN_MESSAGE_V2_HOOK_DATA_INDEX = 228;\\n\\n /**\\n * @notice Max transfer threshold imposed by the CCTP\\n * Ref: https://developers.circle.com/cctp/evm-smart-contracts#depositforburn\\n * @dev 10M USDC limit applies to both standard and fast transfer modes. The fast transfer mode has\\n * an additional limitation that is not present on-chain and Circle may alter that amount off-chain\\n * at their preference. The amount available for fast transfer can be queried here:\\n * https://iris-api.circle.com/v2/fastBurn/USDC/allowance .\\n * If a fast transfer token transaction has been issued and there is not enough allowance for it\\n * the off-chain Iris component will re-attempt the transaction and if it fails it will fallback\\n * to a standard transfer. Reference section 4.3 in the whitepaper:\\n * https://6778953.fs1.hubspotusercontent-na1.net/hubfs/6778953/PDFs/Whitepapers/CCTPV2_White_Paper.pdf\\n */\\n uint256 public constant MAX_TRANSFER_AMOUNT = 10_000_000 * 10**6; // 10M USDC\\n\\n /// @notice Minimum transfer amount to avoid zero or dust transfers\\n uint256 public constant MIN_TRANSFER_AMOUNT = 10**6;\\n\\n // CCTP contracts\\n // This implementation assumes that remote and local chains have these contracts\\n // deployed on the same addresses.\\n /// @notice CCTP message transmitter contract\\n ICCTPMessageTransmitter public immutable cctpMessageTransmitter;\\n /// @notice CCTP token messenger contract\\n ICCTPTokenMessenger public immutable cctpTokenMessenger;\\n\\n /// @notice USDC address on local chain\\n address public immutable usdcToken;\\n\\n /// @notice USDC address on remote chain\\n address public immutable peerUsdcToken;\\n\\n /// @notice Domain ID of the chain from which messages are accepted\\n uint32 public immutable peerDomainID;\\n\\n /// @notice Strategy address on other chain\\n address public immutable peerStrategy;\\n\\n /**\\n * @notice Minimum finality threshold\\n * Can be 1000 (safe, after 1 epoch) or 2000 (finalized, after 2 epochs).\\n * Ref: https://developers.circle.com/cctp/technical-guide#finality-thresholds\\n * @dev When configuring the contract for fast transfer we should check the available\\n * allowance of USDC that can be bridged using fast mode:\\n * wget https://iris-api.circle.com/v2/fastBurn/USDC/allowance\\n */\\n uint16 public minFinalityThreshold;\\n\\n /// @notice Fee premium in basis points\\n uint16 public feePremiumBps;\\n\\n /// @notice Nonce of the last known deposit or withdrawal\\n uint64 public lastTransferNonce;\\n\\n /// @notice Operator address: Can relay CCTP messages\\n address public operator;\\n\\n /// @notice Mapping of processed nonces\\n mapping(uint64 => bool) private nonceProcessed;\\n\\n // For future use\\n uint256[48] private __gap;\\n\\n modifier onlyCCTPMessageTransmitter() {\\n require(\\n msg.sender == address(cctpMessageTransmitter),\\n \\\"Caller is not CCTP transmitter\\\"\\n );\\n _;\\n }\\n\\n modifier onlyOperator() {\\n require(msg.sender == operator, \\\"Caller is not the Operator\\\");\\n _;\\n }\\n\\n /**\\n * @notice Configuration for CCTP integration\\n * @param cctpTokenMessenger Address of the CCTP token messenger contract\\n * @param cctpMessageTransmitter Address of the CCTP message transmitter contract\\n * @param peerDomainID Domain ID of the chain from which messages are accepted.\\n * 0 for Ethereum, 6 for Base, etc.\\n * Ref: https://developers.circle.com/cctp/cctp-supported-blockchains\\n * @param peerStrategy Address of the master or remote strategy on the other chain\\n * @param usdcToken USDC address on local chain\\n */\\n struct CCTPIntegrationConfig {\\n address cctpTokenMessenger;\\n address cctpMessageTransmitter;\\n uint32 peerDomainID;\\n address peerStrategy;\\n address usdcToken;\\n address peerUsdcToken;\\n }\\n\\n constructor(CCTPIntegrationConfig memory _config) {\\n require(_config.usdcToken != address(0), \\\"Invalid USDC address\\\");\\n require(\\n _config.peerUsdcToken != address(0),\\n \\\"Invalid peer USDC address\\\"\\n );\\n require(\\n _config.cctpTokenMessenger != address(0),\\n \\\"Invalid CCTP config\\\"\\n );\\n require(\\n _config.cctpMessageTransmitter != address(0),\\n \\\"Invalid CCTP config\\\"\\n );\\n require(\\n _config.peerStrategy != address(0),\\n \\\"Invalid peer strategy address\\\"\\n );\\n\\n cctpMessageTransmitter = ICCTPMessageTransmitter(\\n _config.cctpMessageTransmitter\\n );\\n cctpTokenMessenger = ICCTPTokenMessenger(_config.cctpTokenMessenger);\\n\\n // Domain ID of the chain from which messages are accepted\\n peerDomainID = _config.peerDomainID;\\n\\n // Strategy address on other chain, should\\n // always be same as the proxy of this strategy\\n peerStrategy = _config.peerStrategy;\\n\\n // USDC address on local chain\\n usdcToken = _config.usdcToken;\\n\\n // Just a sanity check to ensure the base token is USDC\\n uint256 _usdcTokenDecimals = Helpers.getDecimals(_config.usdcToken);\\n string memory _usdcTokenSymbol = Helpers.getSymbol(_config.usdcToken);\\n require(_usdcTokenDecimals == 6, \\\"Base token decimals must be 6\\\");\\n require(\\n keccak256(abi.encodePacked(_usdcTokenSymbol)) ==\\n keccak256(abi.encodePacked(\\\"USDC\\\")),\\n \\\"Token symbol must be USDC\\\"\\n );\\n\\n // USDC address on remote chain\\n peerUsdcToken = _config.peerUsdcToken;\\n }\\n\\n /**\\n * @dev Initialize the implementation contract\\n * @param _operator Operator address\\n * @param _minFinalityThreshold Minimum finality threshold\\n * @param _feePremiumBps Fee premium in basis points\\n */\\n function _initialize(\\n address _operator,\\n uint16 _minFinalityThreshold,\\n uint16 _feePremiumBps\\n ) internal {\\n _setOperator(_operator);\\n _setMinFinalityThreshold(_minFinalityThreshold);\\n _setFeePremiumBps(_feePremiumBps);\\n\\n // Nonce starts at 1, so assume nonce 0 as processed.\\n // NOTE: This will cause the deposit/withdraw to fail if the\\n // strategy is not initialized properly (which is expected).\\n nonceProcessed[0] = true;\\n }\\n\\n /***************************************\\n Settings\\n ****************************************/\\n /**\\n * @dev Set the operator address\\n * @param _operator Operator address\\n */\\n function setOperator(address _operator) external onlyGovernor {\\n _setOperator(_operator);\\n }\\n\\n /**\\n * @dev Set the operator address\\n * @param _operator Operator address\\n */\\n function _setOperator(address _operator) internal {\\n operator = _operator;\\n emit OperatorChanged(_operator);\\n }\\n\\n /**\\n * @dev Set the minimum finality threshold at which\\n * the message is considered to be finalized to relay.\\n * Only accepts a value of 1000 (Safe, after 1 epoch) or\\n * 2000 (Finalized, after 2 epochs).\\n * @param _minFinalityThreshold Minimum finality threshold\\n */\\n function setMinFinalityThreshold(uint16 _minFinalityThreshold)\\n external\\n onlyGovernor\\n {\\n _setMinFinalityThreshold(_minFinalityThreshold);\\n }\\n\\n /**\\n * @dev Set the minimum finality threshold\\n * @param _minFinalityThreshold Minimum finality threshold\\n */\\n function _setMinFinalityThreshold(uint16 _minFinalityThreshold) internal {\\n // 1000 for fast transfer and 2000 for standard transfer\\n require(\\n _minFinalityThreshold == 1000 || _minFinalityThreshold == 2000,\\n \\\"Invalid threshold\\\"\\n );\\n\\n minFinalityThreshold = _minFinalityThreshold;\\n emit CCTPMinFinalityThresholdSet(_minFinalityThreshold);\\n }\\n\\n /**\\n * @dev Set the fee premium in basis points.\\n * Cannot be higher than 30% (3000 basis points).\\n * @param _feePremiumBps Fee premium in basis points\\n */\\n function setFeePremiumBps(uint16 _feePremiumBps) external onlyGovernor {\\n _setFeePremiumBps(_feePremiumBps);\\n }\\n\\n /**\\n * @dev Set the fee premium in basis points\\n * Cannot be higher than 30% (3000 basis points).\\n * Ref: https://developers.circle.com/cctp/technical-guide#fees\\n * @param _feePremiumBps Fee premium in basis points\\n */\\n function _setFeePremiumBps(uint16 _feePremiumBps) internal {\\n require(_feePremiumBps <= 3000, \\\"Fee premium too high\\\"); // 30%\\n\\n feePremiumBps = _feePremiumBps;\\n emit CCTPFeePremiumBpsSet(_feePremiumBps);\\n }\\n\\n /***************************************\\n CCTP message handling\\n ****************************************/\\n\\n /**\\n * @dev Handles a finalized CCTP message\\n * @param sourceDomain Source domain of the message\\n * @param sender Sender of the message\\n * @param finalityThresholdExecuted Fidelity threshold executed\\n * @param messageBody Message body\\n */\\n function handleReceiveFinalizedMessage(\\n uint32 sourceDomain,\\n bytes32 sender,\\n uint32 finalityThresholdExecuted,\\n bytes memory messageBody\\n ) external override onlyCCTPMessageTransmitter returns (bool) {\\n // Make sure the finality threshold at execution is at least 2000\\n require(\\n finalityThresholdExecuted >= 2000,\\n \\\"Finality threshold too low\\\"\\n );\\n\\n return _handleReceivedMessage(sourceDomain, sender, messageBody);\\n }\\n\\n /**\\n * @dev Handles an unfinalized but safe CCTP message\\n * @param sourceDomain Source domain of the message\\n * @param sender Sender of the message\\n * @param finalityThresholdExecuted Fidelity threshold executed\\n * @param messageBody Message body\\n */\\n function handleReceiveUnfinalizedMessage(\\n uint32 sourceDomain,\\n bytes32 sender,\\n uint32 finalityThresholdExecuted,\\n bytes memory messageBody\\n ) external override onlyCCTPMessageTransmitter returns (bool) {\\n // Make sure the contract is configured to handle unfinalized messages\\n require(\\n minFinalityThreshold == 1000,\\n \\\"Unfinalized messages are not supported\\\"\\n );\\n // Make sure the finality threshold at execution is at least 1000\\n require(\\n finalityThresholdExecuted >= 1000,\\n \\\"Finality threshold too low\\\"\\n );\\n\\n return _handleReceivedMessage(sourceDomain, sender, messageBody);\\n }\\n\\n /**\\n * @dev Handles a CCTP message\\n * @param sourceDomain Source domain of the message\\n * @param sender Sender of the message\\n * @param messageBody Message body\\n */\\n function _handleReceivedMessage(\\n uint32 sourceDomain,\\n bytes32 sender,\\n bytes memory messageBody\\n ) internal returns (bool) {\\n require(sourceDomain == peerDomainID, \\\"Unknown Source Domain\\\");\\n\\n // Extract address from bytes32 (CCTP stores addresses as right-padded bytes32)\\n address senderAddress = address(uint160(uint256(sender)));\\n require(senderAddress == peerStrategy, \\\"Unknown Sender\\\");\\n\\n _onMessageReceived(messageBody);\\n\\n return true;\\n }\\n\\n /**\\n * @dev Sends tokens to the peer strategy using CCTP Token Messenger\\n * @param tokenAmount Amount of tokens to send\\n * @param hookData Hook data\\n */\\n function _sendTokens(uint256 tokenAmount, bytes memory hookData)\\n internal\\n virtual\\n {\\n // CCTP has a maximum transfer amount of 10M USDC per tx\\n require(tokenAmount <= MAX_TRANSFER_AMOUNT, \\\"Token amount too high\\\");\\n\\n // Approve only what needs to be transferred\\n IERC20(usdcToken).safeApprove(address(cctpTokenMessenger), tokenAmount);\\n\\n // Compute the max fee to be paid.\\n // Ref: https://developers.circle.com/cctp/evm-smart-contracts#getminfeeamount\\n // The right way to compute fees would be to use CCTP's getMinFeeAmount function.\\n // The issue is that the getMinFeeAmount is not present on v2.0 contracts, but is on\\n // v2.1. Some of CCTP's deployed contracts are v2.0, some are v2.1.\\n // We will only be using standard transfers and fee on those is 0 for now. If they\\n // ever start implementing fee for standard transfers or if we decide to use fast\\n // trasnfer, we can use feePremiumBps as a workaround.\\n uint256 maxFee = feePremiumBps > 0\\n ? (tokenAmount * feePremiumBps) / 10000\\n : 0;\\n\\n // Send tokens to the peer strategy using CCTP Token Messenger\\n cctpTokenMessenger.depositForBurnWithHook(\\n tokenAmount,\\n peerDomainID,\\n bytes32(uint256(uint160(peerStrategy))),\\n address(usdcToken),\\n bytes32(uint256(uint160(peerStrategy))),\\n maxFee,\\n uint32(minFinalityThreshold),\\n hookData\\n );\\n\\n emit TokensBridged(\\n peerDomainID,\\n peerStrategy,\\n usdcToken,\\n tokenAmount,\\n maxFee,\\n uint32(minFinalityThreshold),\\n hookData\\n );\\n }\\n\\n /**\\n * @dev Sends a message to the peer strategy using CCTP Message Transmitter\\n * @param message Payload of the message to send\\n */\\n function _sendMessage(bytes memory message) internal virtual {\\n cctpMessageTransmitter.sendMessage(\\n peerDomainID,\\n bytes32(uint256(uint160(peerStrategy))),\\n bytes32(uint256(uint160(peerStrategy))),\\n uint32(minFinalityThreshold),\\n message\\n );\\n\\n emit MessageTransmitted(\\n peerDomainID,\\n peerStrategy,\\n uint32(minFinalityThreshold),\\n message\\n );\\n }\\n\\n /**\\n * @dev Receives a message from the peer strategy on the other chain,\\n * does some basic checks and relays it to the local MessageTransmitterV2.\\n * If the message is a burn message, it will also handle the hook data\\n * and call the _onTokenReceived function.\\n * @param message Payload of the message to send\\n * @param attestation Attestation of the message\\n */\\n function relay(bytes memory message, bytes memory attestation)\\n external\\n onlyOperator\\n {\\n (\\n uint32 version,\\n uint32 sourceDomainID,\\n address sender,\\n address recipient,\\n bytes memory messageBody\\n ) = message.decodeMessageHeader();\\n\\n // Ensure that it's a CCTP message\\n require(\\n version == CrossChainStrategyHelper.CCTP_MESSAGE_VERSION,\\n \\\"Invalid CCTP message version\\\"\\n );\\n\\n // Ensure that the source domain is the peer domain\\n require(sourceDomainID == peerDomainID, \\\"Unknown Source Domain\\\");\\n\\n // Ensure message body version\\n version = messageBody.extractUint32(BURN_MESSAGE_V2_VERSION_INDEX);\\n\\n // NOTE: There's a possibility that the CCTP Token Messenger might\\n // send other types of messages in future, not just the burn message.\\n // If it ever comes to that, this shouldn't cause us any problems\\n // because it has to still go through the followign checks:\\n // - version check\\n // - message body length check\\n // - sender and recipient (which should be in the same slots and same as address(this))\\n // - hook data handling (which will revert even if all the above checks pass)\\n bool isBurnMessageV1 = sender == address(cctpTokenMessenger);\\n\\n if (isBurnMessageV1) {\\n // Handle burn message\\n require(\\n version == 1 &&\\n messageBody.length >= BURN_MESSAGE_V2_HOOK_DATA_INDEX,\\n \\\"Invalid burn message\\\"\\n );\\n\\n // Ensure the burn token is USDC\\n address burnToken = messageBody.extractAddress(\\n BURN_MESSAGE_V2_BURN_TOKEN_INDEX\\n );\\n require(burnToken == peerUsdcToken, \\\"Invalid burn token\\\");\\n\\n // Address of caller of depositForBurn (or depositForBurnWithCaller) on source domain\\n sender = messageBody.extractAddress(\\n BURN_MESSAGE_V2_MESSAGE_SENDER_INDEX\\n );\\n\\n recipient = messageBody.extractAddress(\\n BURN_MESSAGE_V2_RECIPIENT_INDEX\\n );\\n } else {\\n // We handle only Burn message or our custom messagee\\n require(\\n version == CrossChainStrategyHelper.ORIGIN_MESSAGE_VERSION,\\n \\\"Unsupported message version\\\"\\n );\\n }\\n\\n // Ensure the recipient is this contract\\n // Both sender and recipient should be deployed to same address on both chains.\\n require(address(this) == recipient, \\\"Unexpected recipient address\\\");\\n require(sender == peerStrategy, \\\"Incorrect sender/recipient address\\\");\\n\\n // Relay the message\\n // This step also mints USDC and transfers it to the recipient wallet\\n bool relaySuccess = cctpMessageTransmitter.receiveMessage(\\n message,\\n attestation\\n );\\n require(relaySuccess, \\\"Receive message failed\\\");\\n\\n if (isBurnMessageV1) {\\n // Extract the hook data from the message body\\n bytes memory hookData = messageBody.extractSlice(\\n BURN_MESSAGE_V2_HOOK_DATA_INDEX,\\n messageBody.length\\n );\\n\\n // Extract the token amount from the message body\\n uint256 tokenAmount = messageBody.extractUint256(\\n BURN_MESSAGE_V2_AMOUNT_INDEX\\n );\\n\\n // Extract the fee executed from the message body\\n uint256 feeExecuted = messageBody.extractUint256(\\n BURN_MESSAGE_V2_FEE_EXECUTED_INDEX\\n );\\n\\n // Call the _onTokenReceived function\\n _onTokenReceived(tokenAmount - feeExecuted, feeExecuted, hookData);\\n }\\n }\\n\\n /***************************************\\n Message utils\\n ****************************************/\\n\\n /***************************************\\n Nonce Handling\\n ****************************************/\\n /**\\n * @dev Checks if the last known transfer is pending.\\n * Nonce starts at 1, so 0 is disregarded.\\n * @return True if a transfer is pending, false otherwise\\n */\\n function isTransferPending() public view returns (bool) {\\n return !nonceProcessed[lastTransferNonce];\\n }\\n\\n /**\\n * @dev Checks if a given nonce is processed.\\n * Nonce starts at 1, so 0 is disregarded.\\n * @param nonce Nonce to check\\n * @return True if the nonce is processed, false otherwise\\n */\\n function isNonceProcessed(uint64 nonce) public view returns (bool) {\\n return nonceProcessed[nonce];\\n }\\n\\n /**\\n * @dev Marks a given nonce as processed.\\n * Can only mark nonce as processed once. New nonce should\\n * always be greater than the last known nonce. Also updates\\n * the last known nonce.\\n * @param nonce Nonce to mark as processed\\n */\\n function _markNonceAsProcessed(uint64 nonce) internal {\\n uint64 lastNonce = lastTransferNonce;\\n\\n // Can only mark latest nonce as processed\\n // Master strategy when receiving a message from the remote strategy\\n // will have lastNone == nonce, as the nonce is increase at the start\\n // of deposit / withdrawal flow.\\n // Remote strategy will have lastNonce < nonce, as a new nonce initiated\\n // from master will be greater than the last one.\\n require(nonce >= lastNonce, \\\"Nonce too low\\\");\\n // Can only mark nonce as processed once\\n require(!nonceProcessed[nonce], \\\"Nonce already processed\\\");\\n\\n nonceProcessed[nonce] = true;\\n emit NonceProcessed(nonce);\\n\\n if (nonce != lastNonce) {\\n // Update last known nonce\\n lastTransferNonce = nonce;\\n emit LastTransferNonceUpdated(nonce);\\n }\\n }\\n\\n /**\\n * @dev Gets the next nonce to use.\\n * Nonce starts at 1, so 0 is disregarded.\\n * Reverts if last nonce hasn't been processed yet.\\n * @return Next nonce\\n */\\n function _getNextNonce() internal returns (uint64) {\\n uint64 nonce = lastTransferNonce;\\n\\n require(nonceProcessed[nonce], \\\"Pending token transfer\\\");\\n\\n nonce = nonce + 1;\\n lastTransferNonce = nonce;\\n emit LastTransferNonceUpdated(nonce);\\n\\n return nonce;\\n }\\n\\n /***************************************\\n Inheritence overrides\\n ****************************************/\\n\\n /**\\n * @dev Called when the USDC is received from the CCTP\\n * @param tokenAmount The actual amount of USDC received (amount sent - fee executed)\\n * @param feeExecuted The fee executed\\n * @param payload The payload of the message (hook data)\\n */\\n function _onTokenReceived(\\n uint256 tokenAmount,\\n uint256 feeExecuted,\\n bytes memory payload\\n ) internal virtual;\\n\\n /**\\n * @dev Called when the message is received\\n * @param payload The payload of the message\\n */\\n function _onMessageReceived(bytes memory payload) internal virtual;\\n}\\n\",\"keccak256\":\"0x25f8398d7225b832a4d633e7478743272de704293210a2edf13c156bd1c6a9f0\",\"license\":\"BUSL-1.1\"},\"contracts/strategies/crosschain/CrossChainRemoteStrategy.sol\":{\"content\":\"// SPDX-License-Identifier: BUSL-1.1\\npragma solidity ^0.8.0;\\n\\n/**\\n * @title CrossChainRemoteStrategy\\n * @author Origin Protocol Inc\\n *\\n * @dev Part of the cross-chain strategy that lives on the remote chain.\\n * Handles deposits and withdrawals from the master strategy on peer chain\\n * and locally deposits the funds to a 4626 compatible vault.\\n */\\n\\nimport { SafeERC20 } from \\\"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\\\";\\nimport { IERC20 } from \\\"../../utils/InitializableAbstractStrategy.sol\\\";\\nimport { IERC4626 } from \\\"../../../lib/openzeppelin/interfaces/IERC4626.sol\\\";\\nimport { Generalized4626Strategy } from \\\"../Generalized4626Strategy.sol\\\";\\nimport { AbstractCCTPIntegrator } from \\\"./AbstractCCTPIntegrator.sol\\\";\\nimport { CrossChainStrategyHelper } from \\\"./CrossChainStrategyHelper.sol\\\";\\nimport { InitializableAbstractStrategy } from \\\"../../utils/InitializableAbstractStrategy.sol\\\";\\nimport { Strategizable } from \\\"../../governance/Strategizable.sol\\\";\\n\\ncontract CrossChainRemoteStrategy is\\n AbstractCCTPIntegrator,\\n Generalized4626Strategy,\\n Strategizable\\n{\\n using SafeERC20 for IERC20;\\n using CrossChainStrategyHelper for bytes;\\n\\n event DepositUnderlyingFailed(string reason);\\n event WithdrawalFailed(uint256 amountRequested, uint256 amountAvailable);\\n event WithdrawUnderlyingFailed(string reason);\\n\\n modifier onlyOperatorOrStrategistOrGovernor() {\\n require(\\n msg.sender == operator ||\\n msg.sender == strategistAddr ||\\n isGovernor(),\\n \\\"Caller is not the Operator, Strategist or the Governor\\\"\\n );\\n _;\\n }\\n\\n modifier onlyGovernorOrStrategist()\\n override(InitializableAbstractStrategy, Strategizable) {\\n require(\\n msg.sender == strategistAddr || isGovernor(),\\n \\\"Caller is not the Strategist or Governor\\\"\\n );\\n _;\\n }\\n\\n constructor(\\n BaseStrategyConfig memory _baseConfig,\\n CCTPIntegrationConfig memory _cctpConfig\\n )\\n AbstractCCTPIntegrator(_cctpConfig)\\n Generalized4626Strategy(_baseConfig, _cctpConfig.usdcToken)\\n {\\n require(usdcToken == address(assetToken), \\\"Token mismatch\\\");\\n require(\\n _baseConfig.platformAddress != address(0),\\n \\\"Invalid platform address\\\"\\n );\\n // Vault address must always be address(0) for the remote strategy\\n require(\\n _baseConfig.vaultAddress == address(0),\\n \\\"Invalid vault address\\\"\\n );\\n }\\n\\n /**\\n * @dev Initialize the strategy implementation\\n * @param _strategist Address of the strategist\\n * @param _operator Address of the operator\\n * @param _minFinalityThreshold Minimum finality threshold\\n * @param _feePremiumBps Fee premium in basis points\\n */\\n function initialize(\\n address _strategist,\\n address _operator,\\n uint16 _minFinalityThreshold,\\n uint16 _feePremiumBps\\n ) external virtual onlyGovernor initializer {\\n _initialize(_operator, _minFinalityThreshold, _feePremiumBps);\\n _setStrategistAddr(_strategist);\\n\\n address[] memory rewardTokens = new address[](0);\\n address[] memory assets = new address[](1);\\n address[] memory pTokens = new address[](1);\\n\\n assets[0] = address(usdcToken);\\n pTokens[0] = address(platformAddress);\\n\\n InitializableAbstractStrategy._initialize(\\n rewardTokens,\\n assets,\\n pTokens\\n );\\n }\\n\\n /// @inheritdoc Generalized4626Strategy\\n function deposit(address _asset, uint256 _amount)\\n external\\n virtual\\n override\\n onlyGovernorOrStrategist\\n nonReentrant\\n {\\n _deposit(_asset, _amount);\\n }\\n\\n /// @inheritdoc Generalized4626Strategy\\n function depositAll()\\n external\\n virtual\\n override\\n onlyGovernorOrStrategist\\n nonReentrant\\n {\\n _deposit(usdcToken, IERC20(usdcToken).balanceOf(address(this)));\\n }\\n\\n /// @inheritdoc Generalized4626Strategy\\n /// @dev Interface requires a recipient, but for compatibility it must be address(this).\\n function withdraw(\\n address _recipient,\\n address _asset,\\n uint256 _amount\\n ) external virtual override onlyGovernorOrStrategist nonReentrant {\\n _withdraw(_recipient, _asset, _amount);\\n }\\n\\n /// @inheritdoc Generalized4626Strategy\\n function withdrawAll()\\n external\\n virtual\\n override\\n onlyGovernorOrStrategist\\n nonReentrant\\n {\\n IERC4626 platform = IERC4626(platformAddress);\\n _withdraw(\\n address(this),\\n usdcToken,\\n platform.previewRedeem(platform.balanceOf(address(this)))\\n );\\n }\\n\\n /// @inheritdoc AbstractCCTPIntegrator\\n function _onMessageReceived(bytes memory payload) internal override {\\n uint32 messageType = payload.getMessageType();\\n if (messageType == CrossChainStrategyHelper.DEPOSIT_MESSAGE) {\\n // Received when Master strategy sends tokens to the remote strategy\\n // Do nothing because we receive acknowledgement with token transfer,\\n // so _onTokenReceived will handle it\\n } else if (messageType == CrossChainStrategyHelper.WITHDRAW_MESSAGE) {\\n // Received when Master strategy requests a withdrawal\\n _processWithdrawMessage(payload);\\n } else {\\n revert(\\\"Unknown message type\\\");\\n }\\n }\\n\\n /**\\n * @dev Process deposit message from peer strategy\\n * @param tokenAmount Amount of tokens received\\n * @param feeExecuted Fee executed\\n * @param payload Payload of the message\\n */\\n function _processDepositMessage(\\n // solhint-disable-next-line no-unused-vars\\n uint256 tokenAmount,\\n // solhint-disable-next-line no-unused-vars\\n uint256 feeExecuted,\\n bytes memory payload\\n ) internal virtual {\\n (uint64 nonce, ) = payload.decodeDepositMessage();\\n\\n // Replay protection is part of the _markNonceAsProcessed function\\n _markNonceAsProcessed(nonce);\\n\\n // Deposit everything we got, not just what was bridged\\n uint256 balance = IERC20(usdcToken).balanceOf(address(this));\\n\\n // Underlying call to deposit funds can fail. It mustn't affect the overall\\n // flow as confirmation message should still be sent.\\n if (balance >= MIN_TRANSFER_AMOUNT) {\\n _deposit(usdcToken, balance);\\n }\\n\\n // Send balance check message to the peer strategy\\n bytes memory message = CrossChainStrategyHelper\\n .encodeBalanceCheckMessage(\\n lastTransferNonce,\\n checkBalance(usdcToken),\\n true,\\n block.timestamp\\n );\\n _sendMessage(message);\\n }\\n\\n /**\\n * @dev Deposit assets by converting them to shares\\n * @param _asset Address of asset to deposit\\n * @param _amount Amount of asset to deposit\\n */\\n function _deposit(address _asset, uint256 _amount) internal override {\\n // By design, this function should not revert. Otherwise, it'd\\n // not be able to process messages and might freeze the contracts\\n // state. However these two require statements would never fail\\n // in every function invoking this. The same kind of checks should\\n // be enforced in all the calling functions for these two and any\\n // other require statements added to this function.\\n require(_amount > 0, \\\"Must deposit something\\\");\\n require(_asset == address(usdcToken), \\\"Unexpected asset address\\\");\\n\\n // This call can fail, and the failure doesn't need to bubble up to the _processDepositMessage function\\n // as the flow is not affected by the failure.\\n\\n try IERC4626(platformAddress).deposit(_amount, address(this)) {\\n emit Deposit(_asset, address(shareToken), _amount);\\n } catch Error(string memory reason) {\\n emit DepositUnderlyingFailed(\\n string(abi.encodePacked(\\\"Deposit failed: \\\", reason))\\n );\\n } catch (bytes memory lowLevelData) {\\n emit DepositUnderlyingFailed(\\n string(\\n abi.encodePacked(\\n \\\"Deposit failed: low-level call failed with data \\\",\\n lowLevelData\\n )\\n )\\n );\\n }\\n }\\n\\n /**\\n * @dev Process withdrawal message from peer strategy\\n * @param payload Payload of the message\\n */\\n function _processWithdrawMessage(bytes memory payload) internal virtual {\\n (uint64 nonce, uint256 withdrawAmount) = payload\\n .decodeWithdrawMessage();\\n\\n // Replay protection is part of the _markNonceAsProcessed function\\n _markNonceAsProcessed(nonce);\\n\\n uint256 usdcBalance = IERC20(usdcToken).balanceOf(address(this));\\n\\n if (usdcBalance < withdrawAmount) {\\n // Withdraw the missing funds from the remote strategy. This call can fail and\\n // the failure doesn't bubble up to the _processWithdrawMessage function\\n _withdraw(address(this), usdcToken, withdrawAmount - usdcBalance);\\n\\n // Update the possible increase in the balance on the contract.\\n usdcBalance = IERC20(usdcToken).balanceOf(address(this));\\n }\\n\\n // Check balance after withdrawal\\n uint256 strategyBalance = checkBalance(usdcToken);\\n\\n // If there are some tokens to be sent AND the balance is sufficient\\n // to satisfy the withdrawal request then send the funds to the peer strategy.\\n // In case a direct withdraw(All) has previously been called\\n // there is a possibility of USDC funds remaining on the contract.\\n // A separate withdraw to extract or deposit to the Morpho vault needs to be\\n // initiated from the peer Master strategy to utilise USDC funds.\\n if (\\n withdrawAmount >= MIN_TRANSFER_AMOUNT &&\\n usdcBalance >= withdrawAmount\\n ) {\\n // The new balance on the contract needs to have USDC subtracted from it as\\n // that will be withdrawn in the next step\\n bytes memory message = CrossChainStrategyHelper\\n .encodeBalanceCheckMessage(\\n lastTransferNonce,\\n strategyBalance - withdrawAmount,\\n true,\\n block.timestamp\\n );\\n _sendTokens(withdrawAmount, message);\\n } else {\\n // Contract either:\\n // - only has small dust amount of USDC\\n // - doesn't have sufficient funds to satisfy the withdrawal request\\n // In both cases send the balance update message to the peer strategy.\\n bytes memory message = CrossChainStrategyHelper\\n .encodeBalanceCheckMessage(\\n lastTransferNonce,\\n strategyBalance,\\n true,\\n block.timestamp\\n );\\n _sendMessage(message);\\n emit WithdrawalFailed(withdrawAmount, usdcBalance);\\n }\\n }\\n\\n /**\\n * @dev Withdraw asset by burning shares\\n * @param _recipient Address to receive withdrawn asset\\n * @param _asset Address of asset to withdraw\\n * @param _amount Amount of asset to withdraw\\n */\\n function _withdraw(\\n address _recipient,\\n address _asset,\\n uint256 _amount\\n ) internal override {\\n require(_amount > 0, \\\"Must withdraw something\\\");\\n require(_recipient == address(this), \\\"Invalid recipient\\\");\\n require(_asset == address(usdcToken), \\\"Unexpected asset address\\\");\\n\\n // This call can fail, and the failure doesn't need to bubble up to the _processWithdrawMessage function\\n // as the flow is not affected by the failure.\\n try\\n // slither-disable-next-line unused-return\\n IERC4626(platformAddress).withdraw(\\n _amount,\\n address(this),\\n address(this)\\n )\\n {\\n emit Withdrawal(_asset, address(shareToken), _amount);\\n } catch Error(string memory reason) {\\n emit WithdrawUnderlyingFailed(\\n string(abi.encodePacked(\\\"Withdrawal failed: \\\", reason))\\n );\\n } catch (bytes memory lowLevelData) {\\n emit WithdrawUnderlyingFailed(\\n string(\\n abi.encodePacked(\\n \\\"Withdrawal failed: low-level call failed with data \\\",\\n lowLevelData\\n )\\n )\\n );\\n }\\n }\\n\\n /**\\n * @dev Process token received message from peer strategy\\n * @param tokenAmount Amount of tokens received\\n * @param feeExecuted Fee executed\\n * @param payload Payload of the message\\n */\\n function _onTokenReceived(\\n uint256 tokenAmount,\\n uint256 feeExecuted,\\n bytes memory payload\\n ) internal override {\\n uint32 messageType = payload.getMessageType();\\n\\n require(\\n messageType == CrossChainStrategyHelper.DEPOSIT_MESSAGE,\\n \\\"Invalid message type\\\"\\n );\\n\\n _processDepositMessage(tokenAmount, feeExecuted, payload);\\n }\\n\\n /**\\n * @dev Send balance update message to the peer strategy\\n */\\n function sendBalanceUpdate()\\n external\\n virtual\\n onlyOperatorOrStrategistOrGovernor\\n {\\n uint256 balance = checkBalance(usdcToken);\\n bytes memory message = CrossChainStrategyHelper\\n .encodeBalanceCheckMessage(\\n lastTransferNonce,\\n balance,\\n false,\\n block.timestamp\\n );\\n _sendMessage(message);\\n }\\n\\n /**\\n * @notice Get the total asset value held in the platform and contract\\n * @param _asset Address of the asset\\n * @return balance Total value of the asset in the platform and contract\\n */\\n function checkBalance(address _asset)\\n public\\n view\\n override\\n returns (uint256)\\n {\\n require(_asset == usdcToken, \\\"Unexpected asset address\\\");\\n /**\\n * Balance of USDC on the contract is counted towards the total balance, since a deposit\\n * to the Morpho V2 might fail and the USDC might remain on this contract as a result of a\\n * bridged transfer.\\n */\\n uint256 balanceOnContract = IERC20(usdcToken).balanceOf(address(this));\\n\\n IERC4626 platform = IERC4626(platformAddress);\\n return\\n platform.previewRedeem(platform.balanceOf(address(this))) +\\n balanceOnContract;\\n }\\n}\\n\",\"keccak256\":\"0x2a216504cf878c7d92ef2f0f3f697ca2e75f3f8c077f6d6b8720bb99281c8c6c\",\"license\":\"BUSL-1.1\"},\"contracts/strategies/crosschain/CrossChainStrategyHelper.sol\":{\"content\":\"// SPDX-License-Identifier: BUSL-1.1\\npragma solidity ^0.8.0;\\n\\n/**\\n * @title CrossChainStrategyHelper\\n * @author Origin Protocol Inc\\n * @dev This library is used to encode and decode the messages for the cross-chain strategy.\\n * It is used to ensure that the messages are valid and to get the message version and type.\\n */\\n\\nimport { BytesHelper } from \\\"../../utils/BytesHelper.sol\\\";\\n\\nlibrary CrossChainStrategyHelper {\\n using BytesHelper for bytes;\\n\\n uint32 public constant DEPOSIT_MESSAGE = 1;\\n uint32 public constant WITHDRAW_MESSAGE = 2;\\n uint32 public constant BALANCE_CHECK_MESSAGE = 3;\\n\\n uint32 public constant CCTP_MESSAGE_VERSION = 1;\\n uint32 public constant ORIGIN_MESSAGE_VERSION = 1010;\\n\\n // CCTP Message Header fields\\n // Ref: https://developers.circle.com/cctp/technical-guide#message-header\\n uint8 private constant VERSION_INDEX = 0;\\n uint8 private constant SOURCE_DOMAIN_INDEX = 4;\\n uint8 private constant SENDER_INDEX = 44;\\n uint8 private constant RECIPIENT_INDEX = 76;\\n uint8 private constant MESSAGE_BODY_INDEX = 148;\\n\\n /**\\n * @dev Get the message version from the message.\\n * It should always be 4 bytes long,\\n * starting from the 0th index.\\n * @param message The message to get the version from\\n * @return The message version\\n */\\n function getMessageVersion(bytes memory message)\\n internal\\n pure\\n returns (uint32)\\n {\\n // uint32 bytes 0 to 4 is Origin message version\\n // uint32 bytes 4 to 8 is Message type\\n return message.extractUint32(0);\\n }\\n\\n /**\\n * @dev Get the message type from the message.\\n * It should always be 4 bytes long,\\n * starting from the 4th index.\\n * @param message The message to get the type from\\n * @return The message type\\n */\\n function getMessageType(bytes memory message)\\n internal\\n pure\\n returns (uint32)\\n {\\n // uint32 bytes 0 to 4 is Origin message version\\n // uint32 bytes 4 to 8 is Message type\\n return message.extractUint32(4);\\n }\\n\\n /**\\n * @dev Verify the message version and type.\\n * The message version should be the same as the Origin message version,\\n * and the message type should be the same as the expected message type.\\n * @param _message The message to verify\\n * @param _type The expected message type\\n */\\n function verifyMessageVersionAndType(bytes memory _message, uint32 _type)\\n internal\\n pure\\n {\\n require(\\n getMessageVersion(_message) == ORIGIN_MESSAGE_VERSION,\\n \\\"Invalid Origin Message Version\\\"\\n );\\n require(getMessageType(_message) == _type, \\\"Invalid Message type\\\");\\n }\\n\\n /**\\n * @dev Get the message payload from the message.\\n * The payload starts at the 8th byte.\\n * @param message The message to get the payload from\\n * @return The message payload\\n */\\n function getMessagePayload(bytes memory message)\\n internal\\n pure\\n returns (bytes memory)\\n {\\n // uint32 bytes 0 to 4 is Origin message version\\n // uint32 bytes 4 to 8 is Message type\\n // Payload starts at byte 8\\n return message.extractSlice(8, message.length);\\n }\\n\\n /**\\n * @dev Encode the deposit message.\\n * The message version and type are always encoded in the message.\\n * @param nonce The nonce of the deposit\\n * @param depositAmount The amount of the deposit\\n * @return The encoded deposit message\\n */\\n function encodeDepositMessage(uint64 nonce, uint256 depositAmount)\\n internal\\n pure\\n returns (bytes memory)\\n {\\n return\\n abi.encodePacked(\\n ORIGIN_MESSAGE_VERSION,\\n DEPOSIT_MESSAGE,\\n abi.encode(nonce, depositAmount)\\n );\\n }\\n\\n /**\\n * @dev Decode the deposit message.\\n * The message version and type are verified in the message.\\n * @param message The message to decode\\n * @return The nonce and the amount of the deposit\\n */\\n function decodeDepositMessage(bytes memory message)\\n internal\\n pure\\n returns (uint64, uint256)\\n {\\n verifyMessageVersionAndType(message, DEPOSIT_MESSAGE);\\n\\n (uint64 nonce, uint256 depositAmount) = abi.decode(\\n getMessagePayload(message),\\n (uint64, uint256)\\n );\\n return (nonce, depositAmount);\\n }\\n\\n /**\\n * @dev Encode the withdrawal message.\\n * The message version and type are always encoded in the message.\\n * @param nonce The nonce of the withdrawal\\n * @param withdrawAmount The amount of the withdrawal\\n * @return The encoded withdrawal message\\n */\\n function encodeWithdrawMessage(uint64 nonce, uint256 withdrawAmount)\\n internal\\n pure\\n returns (bytes memory)\\n {\\n return\\n abi.encodePacked(\\n ORIGIN_MESSAGE_VERSION,\\n WITHDRAW_MESSAGE,\\n abi.encode(nonce, withdrawAmount)\\n );\\n }\\n\\n /**\\n * @dev Decode the withdrawal message.\\n * The message version and type are verified in the message.\\n * @param message The message to decode\\n * @return The nonce and the amount of the withdrawal\\n */\\n function decodeWithdrawMessage(bytes memory message)\\n internal\\n pure\\n returns (uint64, uint256)\\n {\\n verifyMessageVersionAndType(message, WITHDRAW_MESSAGE);\\n\\n (uint64 nonce, uint256 withdrawAmount) = abi.decode(\\n getMessagePayload(message),\\n (uint64, uint256)\\n );\\n return (nonce, withdrawAmount);\\n }\\n\\n /**\\n * @dev Encode the balance check message.\\n * The message version and type are always encoded in the message.\\n * @param nonce The nonce of the balance check\\n * @param balance The balance to check\\n * @param transferConfirmation Indicates if the message is a transfer confirmation. This is true\\n * when the message is a result of a deposit or a withdrawal.\\n * @return The encoded balance check message\\n */\\n function encodeBalanceCheckMessage(\\n uint64 nonce,\\n uint256 balance,\\n bool transferConfirmation,\\n uint256 timestamp\\n ) internal pure returns (bytes memory) {\\n return\\n abi.encodePacked(\\n ORIGIN_MESSAGE_VERSION,\\n BALANCE_CHECK_MESSAGE,\\n abi.encode(nonce, balance, transferConfirmation, timestamp)\\n );\\n }\\n\\n /**\\n * @dev Decode the balance check message.\\n * The message version and type are verified in the message.\\n * @param message The message to decode\\n * @return The nonce, the balance and indicates if the message is a transfer confirmation\\n */\\n function decodeBalanceCheckMessage(bytes memory message)\\n internal\\n pure\\n returns (\\n uint64,\\n uint256,\\n bool,\\n uint256\\n )\\n {\\n verifyMessageVersionAndType(message, BALANCE_CHECK_MESSAGE);\\n\\n (\\n uint64 nonce,\\n uint256 balance,\\n bool transferConfirmation,\\n uint256 timestamp\\n ) = abi.decode(\\n getMessagePayload(message),\\n (uint64, uint256, bool, uint256)\\n );\\n return (nonce, balance, transferConfirmation, timestamp);\\n }\\n\\n /**\\n * @dev Decode the CCTP message header\\n * @param message Message to decode\\n * @return version Version of the message\\n * @return sourceDomainID Source domain ID\\n * @return sender Sender of the message\\n * @return recipient Recipient of the message\\n * @return messageBody Message body\\n */\\n function decodeMessageHeader(bytes memory message)\\n internal\\n pure\\n returns (\\n uint32 version,\\n uint32 sourceDomainID,\\n address sender,\\n address recipient,\\n bytes memory messageBody\\n )\\n {\\n version = message.extractUint32(VERSION_INDEX);\\n sourceDomainID = message.extractUint32(SOURCE_DOMAIN_INDEX);\\n // Address of MessageTransmitterV2 caller on source domain\\n sender = message.extractAddress(SENDER_INDEX);\\n // Address to handle message body on destination domain\\n recipient = message.extractAddress(RECIPIENT_INDEX);\\n messageBody = message.extractSlice(MESSAGE_BODY_INDEX, message.length);\\n }\\n}\\n\",\"keccak256\":\"0xbe640419f622ed08d59c10f3ce206a015c524eaa2543c8882ff55a0cea4c6297\",\"license\":\"BUSL-1.1\"},\"contracts/token/OUSD.sol\":{\"content\":\"// SPDX-License-Identifier: BUSL-1.1\\npragma solidity ^0.8.0;\\n\\n/**\\n * @title OUSD Token Contract\\n * @dev ERC20 compatible contract for OUSD\\n * @dev Implements an elastic supply\\n * @author Origin Protocol Inc\\n */\\nimport { IVault } from \\\"../interfaces/IVault.sol\\\";\\nimport { Governable } from \\\"../governance/Governable.sol\\\";\\nimport { SafeCast } from \\\"@openzeppelin/contracts/utils/math/SafeCast.sol\\\";\\n\\ncontract OUSD is Governable {\\n using SafeCast for int256;\\n using SafeCast for uint256;\\n\\n /// @dev Event triggered when the supply changes\\n /// @param totalSupply Updated token total supply\\n /// @param rebasingCredits Updated token rebasing credits\\n /// @param rebasingCreditsPerToken Updated token rebasing credits per token\\n event TotalSupplyUpdatedHighres(\\n uint256 totalSupply,\\n uint256 rebasingCredits,\\n uint256 rebasingCreditsPerToken\\n );\\n /// @dev Event triggered when an account opts in for rebasing\\n /// @param account Address of the account\\n event AccountRebasingEnabled(address account);\\n /// @dev Event triggered when an account opts out of rebasing\\n /// @param account Address of the account\\n event AccountRebasingDisabled(address account);\\n /// @dev Emitted when `value` tokens are moved from one account `from` to\\n /// another `to`.\\n /// @param from Address of the account tokens are moved from\\n /// @param to Address of the account tokens are moved to\\n /// @param value Amount of tokens transferred\\n event Transfer(address indexed from, address indexed to, uint256 value);\\n /// @dev Emitted when the allowance of a `spender` for an `owner` is set by\\n /// a call to {approve}. `value` is the new allowance.\\n /// @param owner Address of the owner approving allowance\\n /// @param spender Address of the spender allowance is granted to\\n /// @param value Amount of tokens spender can transfer\\n event Approval(\\n address indexed owner,\\n address indexed spender,\\n uint256 value\\n );\\n /// @dev Yield resulting from {changeSupply} that a `source` account would\\n /// receive is directed to `target` account.\\n /// @param source Address of the source forwarding the yield\\n /// @param target Address of the target receiving the yield\\n event YieldDelegated(address source, address target);\\n /// @dev Yield delegation from `source` account to the `target` account is\\n /// suspended.\\n /// @param source Address of the source suspending yield forwarding\\n /// @param target Address of the target no longer receiving yield from `source`\\n /// account\\n event YieldUndelegated(address source, address target);\\n\\n enum RebaseOptions {\\n NotSet,\\n StdNonRebasing,\\n StdRebasing,\\n YieldDelegationSource,\\n YieldDelegationTarget\\n }\\n\\n uint256[154] private _gap; // Slots to align with deployed contract\\n uint256 private constant MAX_SUPPLY = type(uint128).max;\\n /// @dev The amount of tokens in existence\\n uint256 public totalSupply;\\n mapping(address => mapping(address => uint256)) private allowances;\\n /// @dev The vault with privileges to execute {mint}, {burn}\\n /// and {changeSupply}\\n address public vaultAddress;\\n mapping(address => uint256) internal creditBalances;\\n // the 2 storage variables below need trailing underscores to not name collide with public functions\\n uint256 private rebasingCredits_; // Sum of all rebasing credits (creditBalances for rebasing accounts)\\n uint256 private rebasingCreditsPerToken_;\\n /// @dev The amount of tokens that are not rebasing - receiving yield\\n uint256 public nonRebasingSupply;\\n mapping(address => uint256) internal alternativeCreditsPerToken;\\n /// @dev A map of all addresses and their respective RebaseOptions\\n mapping(address => RebaseOptions) public rebaseState;\\n mapping(address => uint256) private __deprecated_isUpgraded;\\n /// @dev A map of addresses that have yields forwarded to. This is an\\n /// inverse mapping of {yieldFrom}\\n /// Key Account forwarding yield\\n /// Value Account receiving yield\\n mapping(address => address) public yieldTo;\\n /// @dev A map of addresses that are receiving the yield. This is an\\n /// inverse mapping of {yieldTo}\\n /// Key Account receiving yield\\n /// Value Account forwarding yield\\n mapping(address => address) public yieldFrom;\\n\\n uint256 private constant RESOLUTION_INCREASE = 1e9;\\n uint256[34] private __gap; // including below gap totals up to 200\\n\\n /// @dev Verifies that the caller is the Governor or Strategist.\\n modifier onlyGovernorOrStrategist() {\\n require(\\n isGovernor() || msg.sender == IVault(vaultAddress).strategistAddr(),\\n \\\"Caller is not the Strategist or Governor\\\"\\n );\\n _;\\n }\\n\\n /// @dev Initializes the contract and sets necessary variables.\\n /// @param _vaultAddress Address of the vault contract\\n /// @param _initialCreditsPerToken The starting rebasing credits per token.\\n function initialize(address _vaultAddress, uint256 _initialCreditsPerToken)\\n external\\n onlyGovernor\\n {\\n require(_vaultAddress != address(0), \\\"Zero vault address\\\");\\n require(vaultAddress == address(0), \\\"Already initialized\\\");\\n\\n rebasingCreditsPerToken_ = _initialCreditsPerToken;\\n vaultAddress = _vaultAddress;\\n }\\n\\n /// @dev Returns the symbol of the token, a shorter version\\n /// of the name.\\n function symbol() external pure virtual returns (string memory) {\\n return \\\"OUSD\\\";\\n }\\n\\n /// @dev Returns the name of the token.\\n function name() external pure virtual returns (string memory) {\\n return \\\"Origin Dollar\\\";\\n }\\n\\n /// @dev Returns the number of decimals used to get its user representation.\\n function decimals() external pure virtual returns (uint8) {\\n return 18;\\n }\\n\\n /**\\n * @dev Verifies that the caller is the Vault contract\\n */\\n modifier onlyVault() {\\n require(vaultAddress == msg.sender, \\\"Caller is not the Vault\\\");\\n _;\\n }\\n\\n /**\\n * @return High resolution rebasingCreditsPerToken\\n */\\n function rebasingCreditsPerTokenHighres() external view returns (uint256) {\\n return rebasingCreditsPerToken_;\\n }\\n\\n /**\\n * @return Low resolution rebasingCreditsPerToken\\n */\\n function rebasingCreditsPerToken() external view returns (uint256) {\\n return rebasingCreditsPerToken_ / RESOLUTION_INCREASE;\\n }\\n\\n /**\\n * @return High resolution total number of rebasing credits\\n */\\n function rebasingCreditsHighres() external view returns (uint256) {\\n return rebasingCredits_;\\n }\\n\\n /**\\n * @return Low resolution total number of rebasing credits\\n */\\n function rebasingCredits() external view returns (uint256) {\\n return rebasingCredits_ / RESOLUTION_INCREASE;\\n }\\n\\n /**\\n * @notice Gets the balance of the specified address.\\n * @param _account Address to query the balance of.\\n * @return A uint256 representing the amount of base units owned by the\\n * specified address.\\n */\\n function balanceOf(address _account) public view returns (uint256) {\\n RebaseOptions state = rebaseState[_account];\\n if (state == RebaseOptions.YieldDelegationSource) {\\n // Saves a slot read when transferring to or from a yield delegating source\\n // since we know creditBalances equals the balance.\\n return creditBalances[_account];\\n }\\n uint256 baseBalance = (creditBalances[_account] * 1e18) /\\n _creditsPerToken(_account);\\n if (state == RebaseOptions.YieldDelegationTarget) {\\n // creditBalances of yieldFrom accounts equals token balances\\n return baseBalance - creditBalances[yieldFrom[_account]];\\n }\\n return baseBalance;\\n }\\n\\n /**\\n * @notice Gets the credits balance of the specified address.\\n * @dev Backwards compatible with old low res credits per token.\\n * @param _account The address to query the balance of.\\n * @return (uint256, uint256) Credit balance and credits per token of the\\n * address\\n */\\n function creditsBalanceOf(address _account)\\n external\\n view\\n returns (uint256, uint256)\\n {\\n uint256 cpt = _creditsPerToken(_account);\\n if (cpt == 1e27) {\\n // For a period before the resolution upgrade, we created all new\\n // contract accounts at high resolution. Since they are not changing\\n // as a result of this upgrade, we will return their true values\\n return (creditBalances[_account], cpt);\\n } else {\\n return (\\n creditBalances[_account] / RESOLUTION_INCREASE,\\n cpt / RESOLUTION_INCREASE\\n );\\n }\\n }\\n\\n /**\\n * @notice Gets the credits balance of the specified address.\\n * @param _account The address to query the balance of.\\n * @return (uint256, uint256, bool) Credit balance, credits per token of the\\n * address, and isUpgraded\\n */\\n function creditsBalanceOfHighres(address _account)\\n external\\n view\\n returns (\\n uint256,\\n uint256,\\n bool\\n )\\n {\\n return (\\n creditBalances[_account],\\n _creditsPerToken(_account),\\n true // all accounts have their resolution \\\"upgraded\\\"\\n );\\n }\\n\\n // Backwards compatible view\\n function nonRebasingCreditsPerToken(address _account)\\n external\\n view\\n returns (uint256)\\n {\\n return alternativeCreditsPerToken[_account];\\n }\\n\\n /**\\n * @notice Transfer tokens to a specified address.\\n * @param _to the address to transfer to.\\n * @param _value the amount to be transferred.\\n * @return true on success.\\n */\\n function transfer(address _to, uint256 _value) external returns (bool) {\\n require(_to != address(0), \\\"Transfer to zero address\\\");\\n\\n _executeTransfer(msg.sender, _to, _value);\\n\\n emit Transfer(msg.sender, _to, _value);\\n return true;\\n }\\n\\n /**\\n * @notice Transfer tokens from one address to another.\\n * @param _from The address you want to send tokens from.\\n * @param _to The address you want to transfer to.\\n * @param _value The amount of tokens to be transferred.\\n * @return true on success.\\n */\\n function transferFrom(\\n address _from,\\n address _to,\\n uint256 _value\\n ) external returns (bool) {\\n require(_to != address(0), \\\"Transfer to zero address\\\");\\n uint256 userAllowance = allowances[_from][msg.sender];\\n require(_value <= userAllowance, \\\"Allowance exceeded\\\");\\n\\n unchecked {\\n allowances[_from][msg.sender] = userAllowance - _value;\\n }\\n\\n _executeTransfer(_from, _to, _value);\\n\\n emit Transfer(_from, _to, _value);\\n return true;\\n }\\n\\n function _executeTransfer(\\n address _from,\\n address _to,\\n uint256 _value\\n ) internal {\\n (\\n int256 fromRebasingCreditsDiff,\\n int256 fromNonRebasingSupplyDiff\\n ) = _adjustAccount(_from, -_value.toInt256());\\n (\\n int256 toRebasingCreditsDiff,\\n int256 toNonRebasingSupplyDiff\\n ) = _adjustAccount(_to, _value.toInt256());\\n\\n _adjustGlobals(\\n fromRebasingCreditsDiff + toRebasingCreditsDiff,\\n fromNonRebasingSupplyDiff + toNonRebasingSupplyDiff\\n );\\n }\\n\\n function _adjustAccount(address _account, int256 _balanceChange)\\n internal\\n returns (int256 rebasingCreditsDiff, int256 nonRebasingSupplyDiff)\\n {\\n RebaseOptions state = rebaseState[_account];\\n int256 currentBalance = balanceOf(_account).toInt256();\\n if (currentBalance + _balanceChange < 0) {\\n revert(\\\"Transfer amount exceeds balance\\\");\\n }\\n uint256 newBalance = (currentBalance + _balanceChange).toUint256();\\n\\n if (state == RebaseOptions.YieldDelegationSource) {\\n address target = yieldTo[_account];\\n uint256 targetOldBalance = balanceOf(target);\\n uint256 targetNewCredits = _balanceToRebasingCredits(\\n targetOldBalance + newBalance\\n );\\n rebasingCreditsDiff =\\n targetNewCredits.toInt256() -\\n creditBalances[target].toInt256();\\n\\n creditBalances[_account] = newBalance;\\n creditBalances[target] = targetNewCredits;\\n } else if (state == RebaseOptions.YieldDelegationTarget) {\\n uint256 newCredits = _balanceToRebasingCredits(\\n newBalance + creditBalances[yieldFrom[_account]]\\n );\\n rebasingCreditsDiff =\\n newCredits.toInt256() -\\n creditBalances[_account].toInt256();\\n creditBalances[_account] = newCredits;\\n } else {\\n _autoMigrate(_account);\\n uint256 alternativeCreditsPerTokenMem = alternativeCreditsPerToken[\\n _account\\n ];\\n if (alternativeCreditsPerTokenMem > 0) {\\n nonRebasingSupplyDiff = _balanceChange;\\n if (alternativeCreditsPerTokenMem != 1e18) {\\n alternativeCreditsPerToken[_account] = 1e18;\\n }\\n creditBalances[_account] = newBalance;\\n } else {\\n uint256 newCredits = _balanceToRebasingCredits(newBalance);\\n rebasingCreditsDiff =\\n newCredits.toInt256() -\\n creditBalances[_account].toInt256();\\n creditBalances[_account] = newCredits;\\n }\\n }\\n }\\n\\n function _adjustGlobals(\\n int256 _rebasingCreditsDiff,\\n int256 _nonRebasingSupplyDiff\\n ) internal {\\n if (_rebasingCreditsDiff != 0) {\\n rebasingCredits_ = (rebasingCredits_.toInt256() +\\n _rebasingCreditsDiff).toUint256();\\n }\\n if (_nonRebasingSupplyDiff != 0) {\\n nonRebasingSupply = (nonRebasingSupply.toInt256() +\\n _nonRebasingSupplyDiff).toUint256();\\n }\\n }\\n\\n /**\\n * @notice Function to check the amount of tokens that _owner has allowed\\n * to `_spender`.\\n * @param _owner The address which owns the funds.\\n * @param _spender The address which will spend the funds.\\n * @return The number of tokens still available for the _spender.\\n */\\n function allowance(address _owner, address _spender)\\n external\\n view\\n returns (uint256)\\n {\\n return allowances[_owner][_spender];\\n }\\n\\n /**\\n * @notice Approve the passed address to spend the specified amount of\\n * tokens on behalf of msg.sender.\\n * @param _spender The address which will spend the funds.\\n * @param _value The amount of tokens to be spent.\\n * @return true on success.\\n */\\n function approve(address _spender, uint256 _value) external returns (bool) {\\n allowances[msg.sender][_spender] = _value;\\n emit Approval(msg.sender, _spender, _value);\\n return true;\\n }\\n\\n /**\\n * @notice Creates `_amount` tokens and assigns them to `_account`,\\n * increasing the total supply.\\n */\\n function mint(address _account, uint256 _amount) external onlyVault {\\n require(_account != address(0), \\\"Mint to the zero address\\\");\\n\\n // Account\\n (\\n int256 toRebasingCreditsDiff,\\n int256 toNonRebasingSupplyDiff\\n ) = _adjustAccount(_account, _amount.toInt256());\\n // Globals\\n _adjustGlobals(toRebasingCreditsDiff, toNonRebasingSupplyDiff);\\n totalSupply = totalSupply + _amount;\\n\\n require(totalSupply < MAX_SUPPLY, \\\"Max supply\\\");\\n emit Transfer(address(0), _account, _amount);\\n }\\n\\n /**\\n * @notice Destroys `_amount` tokens from `_account`,\\n * reducing the total supply.\\n */\\n function burn(address _account, uint256 _amount) external onlyVault {\\n require(_account != address(0), \\\"Burn from the zero address\\\");\\n if (_amount == 0) {\\n return;\\n }\\n\\n // Account\\n (\\n int256 toRebasingCreditsDiff,\\n int256 toNonRebasingSupplyDiff\\n ) = _adjustAccount(_account, -_amount.toInt256());\\n // Globals\\n _adjustGlobals(toRebasingCreditsDiff, toNonRebasingSupplyDiff);\\n totalSupply = totalSupply - _amount;\\n\\n emit Transfer(_account, address(0), _amount);\\n }\\n\\n /**\\n * @dev Get the credits per token for an account. Returns a fixed amount\\n * if the account is non-rebasing.\\n * @param _account Address of the account.\\n */\\n function _creditsPerToken(address _account)\\n internal\\n view\\n returns (uint256)\\n {\\n uint256 alternativeCreditsPerTokenMem = alternativeCreditsPerToken[\\n _account\\n ];\\n if (alternativeCreditsPerTokenMem != 0) {\\n return alternativeCreditsPerTokenMem;\\n } else {\\n return rebasingCreditsPerToken_;\\n }\\n }\\n\\n /**\\n * @dev Auto migrate contracts to be non rebasing,\\n * unless they have opted into yield.\\n * @param _account Address of the account.\\n */\\n function _autoMigrate(address _account) internal {\\n uint256 codeLen = _account.code.length;\\n bool isEOA = (codeLen == 0) ||\\n (codeLen == 23 && bytes3(_account.code) == 0xef0100);\\n // In previous code versions, contracts would not have had their\\n // rebaseState[_account] set to RebaseOptions.NonRebasing when migrated\\n // therefore we check the actual accounting used on the account as well.\\n if (\\n (!isEOA) &&\\n rebaseState[_account] == RebaseOptions.NotSet &&\\n alternativeCreditsPerToken[_account] == 0\\n ) {\\n _rebaseOptOut(_account);\\n }\\n }\\n\\n /**\\n * @dev Calculates credits from contract's global rebasingCreditsPerToken_, and\\n * also balance that corresponds to those credits. The latter is important\\n * when adjusting the contract's global nonRebasingSupply to circumvent any\\n * possible rounding errors.\\n *\\n * @param _balance Balance of the account.\\n */\\n function _balanceToRebasingCredits(uint256 _balance)\\n internal\\n view\\n returns (uint256 rebasingCredits)\\n {\\n // Rounds up, because we need to ensure that accounts always have\\n // at least the balance that they should have.\\n // Note this should always be used on an absolute account value,\\n // not on a possibly negative diff, because then the rounding would be wrong.\\n return ((_balance) * rebasingCreditsPerToken_ + 1e18 - 1) / 1e18;\\n }\\n\\n /**\\n * @notice The calling account will start receiving yield after a successful call.\\n * @param _account Address of the account.\\n */\\n function governanceRebaseOptIn(address _account) external onlyGovernor {\\n require(_account != address(0), \\\"Zero address not allowed\\\");\\n _rebaseOptIn(_account);\\n }\\n\\n /**\\n * @notice The calling account will start receiving yield after a successful call.\\n */\\n function rebaseOptIn() external {\\n _rebaseOptIn(msg.sender);\\n }\\n\\n function _rebaseOptIn(address _account) internal {\\n uint256 balance = balanceOf(_account);\\n\\n // prettier-ignore\\n require(\\n alternativeCreditsPerToken[_account] > 0 ||\\n // Accounts may explicitly `rebaseOptIn` regardless of\\n // accounting if they have a 0 balance.\\n creditBalances[_account] == 0\\n ,\\n \\\"Account must be non-rebasing\\\"\\n );\\n RebaseOptions state = rebaseState[_account];\\n // prettier-ignore\\n require(\\n state == RebaseOptions.StdNonRebasing ||\\n state == RebaseOptions.NotSet,\\n \\\"Only standard non-rebasing accounts can opt in\\\"\\n );\\n\\n uint256 newCredits = _balanceToRebasingCredits(balance);\\n\\n // Account\\n rebaseState[_account] = RebaseOptions.StdRebasing;\\n alternativeCreditsPerToken[_account] = 0;\\n creditBalances[_account] = newCredits;\\n // Globals\\n _adjustGlobals(newCredits.toInt256(), -balance.toInt256());\\n\\n emit AccountRebasingEnabled(_account);\\n }\\n\\n /**\\n * @notice The calling account will no longer receive yield\\n */\\n function rebaseOptOut() external {\\n _rebaseOptOut(msg.sender);\\n }\\n\\n function _rebaseOptOut(address _account) internal {\\n require(\\n alternativeCreditsPerToken[_account] == 0,\\n \\\"Account must be rebasing\\\"\\n );\\n RebaseOptions state = rebaseState[_account];\\n require(\\n state == RebaseOptions.StdRebasing || state == RebaseOptions.NotSet,\\n \\\"Only standard rebasing accounts can opt out\\\"\\n );\\n\\n uint256 oldCredits = creditBalances[_account];\\n uint256 balance = balanceOf(_account);\\n\\n // Account\\n rebaseState[_account] = RebaseOptions.StdNonRebasing;\\n alternativeCreditsPerToken[_account] = 1e18;\\n creditBalances[_account] = balance;\\n // Globals\\n _adjustGlobals(-oldCredits.toInt256(), balance.toInt256());\\n\\n emit AccountRebasingDisabled(_account);\\n }\\n\\n /**\\n * @notice Distribute yield to users. This changes the exchange rate\\n * between \\\"credits\\\" and OUSD tokens to change rebasing user's balances.\\n * @param _newTotalSupply New total supply of OUSD.\\n */\\n function changeSupply(uint256 _newTotalSupply) external onlyVault {\\n require(totalSupply > 0, \\\"Cannot increase 0 supply\\\");\\n\\n if (totalSupply == _newTotalSupply) {\\n emit TotalSupplyUpdatedHighres(\\n totalSupply,\\n rebasingCredits_,\\n rebasingCreditsPerToken_\\n );\\n return;\\n }\\n\\n totalSupply = _newTotalSupply > MAX_SUPPLY\\n ? MAX_SUPPLY\\n : _newTotalSupply;\\n\\n uint256 rebasingSupply = totalSupply - nonRebasingSupply;\\n // round up in the favour of the protocol\\n rebasingCreditsPerToken_ =\\n (rebasingCredits_ * 1e18 + rebasingSupply - 1) /\\n rebasingSupply;\\n\\n require(rebasingCreditsPerToken_ > 0, \\\"Invalid change in supply\\\");\\n\\n emit TotalSupplyUpdatedHighres(\\n totalSupply,\\n rebasingCredits_,\\n rebasingCreditsPerToken_\\n );\\n }\\n\\n /*\\n * @notice Send the yield from one account to another account.\\n * Each account keeps its own balances.\\n */\\n function delegateYield(address _from, address _to)\\n external\\n onlyGovernorOrStrategist\\n {\\n require(_from != address(0), \\\"Zero from address not allowed\\\");\\n require(_to != address(0), \\\"Zero to address not allowed\\\");\\n\\n require(_from != _to, \\\"Cannot delegate to self\\\");\\n require(\\n yieldFrom[_to] == address(0) &&\\n yieldTo[_to] == address(0) &&\\n yieldFrom[_from] == address(0) &&\\n yieldTo[_from] == address(0),\\n \\\"Blocked by existing yield delegation\\\"\\n );\\n RebaseOptions stateFrom = rebaseState[_from];\\n RebaseOptions stateTo = rebaseState[_to];\\n\\n require(\\n stateFrom == RebaseOptions.NotSet ||\\n stateFrom == RebaseOptions.StdNonRebasing ||\\n stateFrom == RebaseOptions.StdRebasing,\\n \\\"Invalid rebaseState from\\\"\\n );\\n\\n require(\\n stateTo == RebaseOptions.NotSet ||\\n stateTo == RebaseOptions.StdNonRebasing ||\\n stateTo == RebaseOptions.StdRebasing,\\n \\\"Invalid rebaseState to\\\"\\n );\\n\\n if (alternativeCreditsPerToken[_from] == 0) {\\n _rebaseOptOut(_from);\\n }\\n if (alternativeCreditsPerToken[_to] > 0) {\\n _rebaseOptIn(_to);\\n }\\n\\n uint256 fromBalance = balanceOf(_from);\\n uint256 toBalance = balanceOf(_to);\\n uint256 oldToCredits = creditBalances[_to];\\n uint256 newToCredits = _balanceToRebasingCredits(\\n fromBalance + toBalance\\n );\\n\\n // Set up the bidirectional links\\n yieldTo[_from] = _to;\\n yieldFrom[_to] = _from;\\n\\n // Local\\n rebaseState[_from] = RebaseOptions.YieldDelegationSource;\\n alternativeCreditsPerToken[_from] = 1e18;\\n creditBalances[_from] = fromBalance;\\n rebaseState[_to] = RebaseOptions.YieldDelegationTarget;\\n creditBalances[_to] = newToCredits;\\n\\n // Global\\n int256 creditsChange = newToCredits.toInt256() -\\n oldToCredits.toInt256();\\n _adjustGlobals(creditsChange, -(fromBalance).toInt256());\\n emit YieldDelegated(_from, _to);\\n }\\n\\n /*\\n * @notice Stop sending the yield from one account to another account.\\n */\\n function undelegateYield(address _from) external onlyGovernorOrStrategist {\\n // Require a delegation, which will also ensure a valid delegation\\n require(yieldTo[_from] != address(0), \\\"Zero address not allowed\\\");\\n\\n address to = yieldTo[_from];\\n uint256 fromBalance = balanceOf(_from);\\n uint256 toBalance = balanceOf(to);\\n uint256 oldToCredits = creditBalances[to];\\n uint256 newToCredits = _balanceToRebasingCredits(toBalance);\\n\\n // Remove the bidirectional links\\n yieldFrom[to] = address(0);\\n yieldTo[_from] = address(0);\\n\\n // Local\\n rebaseState[_from] = RebaseOptions.StdNonRebasing;\\n // alternativeCreditsPerToken[from] already 1e18 from `delegateYield()`\\n creditBalances[_from] = fromBalance;\\n rebaseState[to] = RebaseOptions.StdRebasing;\\n // alternativeCreditsPerToken[to] already 0 from `delegateYield()`\\n creditBalances[to] = newToCredits;\\n\\n // Global\\n int256 creditsChange = newToCredits.toInt256() -\\n oldToCredits.toInt256();\\n _adjustGlobals(creditsChange, fromBalance.toInt256());\\n emit YieldUndelegated(_from, to);\\n }\\n}\\n\",\"keccak256\":\"0x73439bef6569f5adf6f5ce2cb54a5f0d3109d4819457532236e172a7091980a9\",\"license\":\"BUSL-1.1\"},\"contracts/utils/BytesHelper.sol\":{\"content\":\"// SPDX-License-Identifier: BUSL-1.1\\npragma solidity ^0.8.0;\\n\\nuint256 constant UINT32_LENGTH = 4;\\nuint256 constant UINT64_LENGTH = 8;\\nuint256 constant UINT256_LENGTH = 32;\\n// Address is 20 bytes, but we expect the data to be padded with 0s to 32 bytes\\nuint256 constant ADDRESS_LENGTH = 32;\\n\\nlibrary BytesHelper {\\n /**\\n * @dev Extract a slice from bytes memory\\n * @param data The bytes memory to slice\\n * @param start The start index (inclusive)\\n * @param end The end index (exclusive)\\n * @return result A new bytes memory containing the slice\\n */\\n function extractSlice(\\n bytes memory data,\\n uint256 start,\\n uint256 end\\n ) internal pure returns (bytes memory) {\\n require(end >= start, \\\"Invalid slice range\\\");\\n require(end <= data.length, \\\"Slice end exceeds data length\\\");\\n\\n uint256 length = end - start;\\n bytes memory result = new bytes(length);\\n\\n // Simple byte-by-byte copy\\n for (uint256 i = 0; i < length; i++) {\\n result[i] = data[start + i];\\n }\\n\\n return result;\\n }\\n\\n /**\\n * @dev Decode a uint32 from a bytes memory\\n * @param data The bytes memory to decode\\n * @return uint32 The decoded uint32\\n */\\n function decodeUint32(bytes memory data) internal pure returns (uint32) {\\n require(data.length == 4, \\\"Invalid data length\\\");\\n return uint32(uint256(bytes32(data)) >> 224);\\n }\\n\\n /**\\n * @dev Extract a uint32 from a bytes memory\\n * @param data The bytes memory to extract from\\n * @param start The start index (inclusive)\\n * @return uint32 The extracted uint32\\n */\\n function extractUint32(bytes memory data, uint256 start)\\n internal\\n pure\\n returns (uint32)\\n {\\n return decodeUint32(extractSlice(data, start, start + UINT32_LENGTH));\\n }\\n\\n /**\\n * @dev Decode an address from a bytes memory.\\n * Expects the data to be padded with 0s to 32 bytes.\\n * @param data The bytes memory to decode\\n * @return address The decoded address\\n */\\n function decodeAddress(bytes memory data) internal pure returns (address) {\\n // We expect the data to be padded with 0s, so length is 32 not 20\\n require(data.length == 32, \\\"Invalid data length\\\");\\n return abi.decode(data, (address));\\n }\\n\\n /**\\n * @dev Extract an address from a bytes memory\\n * @param data The bytes memory to extract from\\n * @param start The start index (inclusive)\\n * @return address The extracted address\\n */\\n function extractAddress(bytes memory data, uint256 start)\\n internal\\n pure\\n returns (address)\\n {\\n return decodeAddress(extractSlice(data, start, start + ADDRESS_LENGTH));\\n }\\n\\n /**\\n * @dev Decode a uint256 from a bytes memory\\n * @param data The bytes memory to decode\\n * @return uint256 The decoded uint256\\n */\\n function decodeUint256(bytes memory data) internal pure returns (uint256) {\\n require(data.length == 32, \\\"Invalid data length\\\");\\n return abi.decode(data, (uint256));\\n }\\n\\n /**\\n * @dev Extract a uint256 from a bytes memory\\n * @param data The bytes memory to extract from\\n * @param start The start index (inclusive)\\n * @return uint256 The extracted uint256\\n */\\n function extractUint256(bytes memory data, uint256 start)\\n internal\\n pure\\n returns (uint256)\\n {\\n return decodeUint256(extractSlice(data, start, start + UINT256_LENGTH));\\n }\\n}\\n\",\"keccak256\":\"0x471aff77f4b6750abd8ed018fa3e89707f045ae377c73b225d413d62c1e9b28f\",\"license\":\"BUSL-1.1\"},\"contracts/utils/Helpers.sol\":{\"content\":\"// SPDX-License-Identifier: BUSL-1.1\\npragma solidity ^0.8.0;\\n\\nimport { IBasicToken } from \\\"../interfaces/IBasicToken.sol\\\";\\n\\nlibrary Helpers {\\n /**\\n * @notice Fetch the `symbol()` from an ERC20 token\\n * @dev Grabs the `symbol()` from a contract\\n * @param _token Address of the ERC20 token\\n * @return string Symbol of the ERC20 token\\n */\\n function getSymbol(address _token) internal view returns (string memory) {\\n string memory symbol = IBasicToken(_token).symbol();\\n return symbol;\\n }\\n\\n /**\\n * @notice Fetch the `decimals()` from an ERC20 token\\n * @dev Grabs the `decimals()` from a contract and fails if\\n * the decimal value does not live within a certain range\\n * @param _token Address of the ERC20 token\\n * @return uint256 Decimals of the ERC20 token\\n */\\n function getDecimals(address _token) internal view returns (uint256) {\\n uint256 decimals = IBasicToken(_token).decimals();\\n require(\\n decimals >= 4 && decimals <= 18,\\n \\\"Token must have sufficient decimal places\\\"\\n );\\n\\n return decimals;\\n }\\n}\\n\",\"keccak256\":\"0x4366f8d90b34c1eef8bbaaf369b1e5cd59f04027bb3c111f208eaee65bbc0346\",\"license\":\"BUSL-1.1\"},\"contracts/utils/Initializable.sol\":{\"content\":\"// SPDX-License-Identifier: BUSL-1.1\\npragma solidity ^0.8.0;\\n\\n/**\\n * @title Base contract any contracts that need to initialize state after deployment.\\n * @author Origin Protocol Inc\\n */\\nabstract contract Initializable {\\n /**\\n * @dev Indicates that the contract has been initialized.\\n */\\n bool private initialized;\\n\\n /**\\n * @dev Indicates that the contract is in the process of being initialized.\\n */\\n bool private initializing;\\n\\n /**\\n * @dev Modifier to protect an initializer function from being invoked twice.\\n */\\n modifier initializer() {\\n require(\\n initializing || !initialized,\\n \\\"Initializable: contract is already initialized\\\"\\n );\\n\\n bool isTopLevelCall = !initializing;\\n if (isTopLevelCall) {\\n initializing = true;\\n initialized = true;\\n }\\n\\n _;\\n\\n if (isTopLevelCall) {\\n initializing = false;\\n }\\n }\\n\\n uint256[50] private ______gap;\\n}\\n\",\"keccak256\":\"0x50d39ebf38a3d3111f2b77a6c75ece1d4ae731552fec4697ab16fcf6c0d4d5e8\",\"license\":\"BUSL-1.1\"},\"contracts/utils/InitializableAbstractStrategy.sol\":{\"content\":\"// SPDX-License-Identifier: BUSL-1.1\\npragma solidity ^0.8.0;\\n\\n/**\\n * @title Base contract for vault strategies.\\n * @author Origin Protocol Inc\\n */\\nimport { IERC20 } from \\\"@openzeppelin/contracts/token/ERC20/IERC20.sol\\\";\\nimport { SafeERC20 } from \\\"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\\\";\\n\\nimport { Initializable } from \\\"../utils/Initializable.sol\\\";\\nimport { Governable } from \\\"../governance/Governable.sol\\\";\\nimport { IVault } from \\\"../interfaces/IVault.sol\\\";\\n\\nabstract contract InitializableAbstractStrategy is Initializable, Governable {\\n using SafeERC20 for IERC20;\\n\\n event PTokenAdded(address indexed _asset, address _pToken);\\n event PTokenRemoved(address indexed _asset, address _pToken);\\n event Deposit(address indexed _asset, address _pToken, uint256 _amount);\\n event Withdrawal(address indexed _asset, address _pToken, uint256 _amount);\\n event RewardTokenCollected(\\n address recipient,\\n address rewardToken,\\n uint256 amount\\n );\\n event RewardTokenAddressesUpdated(\\n address[] _oldAddresses,\\n address[] _newAddresses\\n );\\n event HarvesterAddressesUpdated(\\n address _oldHarvesterAddress,\\n address _newHarvesterAddress\\n );\\n\\n /// @notice Address of the underlying platform\\n address public immutable platformAddress;\\n /// @notice Address of the OToken vault\\n address public immutable vaultAddress;\\n\\n /// @dev Replaced with an immutable variable\\n // slither-disable-next-line constable-states\\n address private _deprecated_platformAddress;\\n\\n /// @dev Replaced with an immutable\\n // slither-disable-next-line constable-states\\n address private _deprecated_vaultAddress;\\n\\n /// @notice asset => pToken (Platform Specific Token Address)\\n mapping(address => address) public assetToPToken;\\n\\n /// @notice Full list of all assets supported by the strategy\\n address[] internal assetsMapped;\\n\\n // Deprecated: Reward token address\\n // slither-disable-next-line constable-states\\n address private _deprecated_rewardTokenAddress;\\n\\n // Deprecated: now resides in Harvester's rewardTokenConfigs\\n // slither-disable-next-line constable-states\\n uint256 private _deprecated_rewardLiquidationThreshold;\\n\\n /// @notice Address of the Harvester contract allowed to collect reward tokens\\n address public harvesterAddress;\\n\\n /// @notice Address of the reward tokens. eg CRV, BAL, CVX, AURA\\n address[] public rewardTokenAddresses;\\n\\n /* Reserved for future expansion. Used to be 100 storage slots\\n * and has decreased to accommodate:\\n * - harvesterAddress\\n * - rewardTokenAddresses\\n */\\n int256[98] private _reserved;\\n\\n struct BaseStrategyConfig {\\n address platformAddress; // Address of the underlying platform\\n address vaultAddress; // Address of the OToken's Vault\\n }\\n\\n /**\\n * @dev Verifies that the caller is the Governor or Strategist.\\n */\\n modifier onlyGovernorOrStrategist() virtual {\\n require(\\n isGovernor() || msg.sender == IVault(vaultAddress).strategistAddr(),\\n \\\"Caller is not the Strategist or Governor\\\"\\n );\\n _;\\n }\\n\\n /**\\n * @param _config The platform and OToken vault addresses\\n */\\n constructor(BaseStrategyConfig memory _config) {\\n platformAddress = _config.platformAddress;\\n vaultAddress = _config.vaultAddress;\\n }\\n\\n /**\\n * @dev Internal initialize function, to set up initial internal state\\n * @param _rewardTokenAddresses Address of reward token for platform\\n * @param _assets Addresses of initial supported assets\\n * @param _pTokens Platform Token corresponding addresses\\n */\\n function _initialize(\\n address[] memory _rewardTokenAddresses,\\n address[] memory _assets,\\n address[] memory _pTokens\\n ) internal {\\n rewardTokenAddresses = _rewardTokenAddresses;\\n\\n uint256 assetCount = _assets.length;\\n require(assetCount == _pTokens.length, \\\"Invalid input arrays\\\");\\n for (uint256 i = 0; i < assetCount; ++i) {\\n _setPTokenAddress(_assets[i], _pTokens[i]);\\n }\\n }\\n\\n /**\\n * @notice Collect accumulated reward token and send to Vault.\\n */\\n function collectRewardTokens() external virtual onlyHarvester nonReentrant {\\n _collectRewardTokens();\\n }\\n\\n /**\\n * @dev Default implementation that transfers reward tokens to the Harvester\\n * Implementing strategies need to add custom logic to collect the rewards.\\n */\\n function _collectRewardTokens() internal virtual {\\n uint256 rewardTokenCount = rewardTokenAddresses.length;\\n for (uint256 i = 0; i < rewardTokenCount; ++i) {\\n IERC20 rewardToken = IERC20(rewardTokenAddresses[i]);\\n uint256 balance = rewardToken.balanceOf(address(this));\\n if (balance > 0) {\\n emit RewardTokenCollected(\\n harvesterAddress,\\n address(rewardToken),\\n balance\\n );\\n rewardToken.safeTransfer(harvesterAddress, balance);\\n }\\n }\\n }\\n\\n /**\\n * @dev Verifies that the caller is the Vault.\\n */\\n modifier onlyVault() {\\n require(msg.sender == vaultAddress, \\\"Caller is not the Vault\\\");\\n _;\\n }\\n\\n /**\\n * @dev Verifies that the caller is the Harvester.\\n */\\n modifier onlyHarvester() {\\n require(msg.sender == harvesterAddress, \\\"Caller is not the Harvester\\\");\\n _;\\n }\\n\\n /**\\n * @dev Verifies that the caller is the Vault or Governor.\\n */\\n modifier onlyVaultOrGovernor() {\\n require(\\n msg.sender == vaultAddress || msg.sender == governor(),\\n \\\"Caller is not the Vault or Governor\\\"\\n );\\n _;\\n }\\n\\n /**\\n * @dev Verifies that the caller is the Vault, Governor, or Strategist.\\n */\\n modifier onlyVaultOrGovernorOrStrategist() {\\n require(\\n msg.sender == vaultAddress ||\\n msg.sender == governor() ||\\n msg.sender == IVault(vaultAddress).strategistAddr(),\\n \\\"Caller is not the Vault, Governor, or Strategist\\\"\\n );\\n _;\\n }\\n\\n /**\\n * @notice Set the reward token addresses. Any old addresses will be overwritten.\\n * @param _rewardTokenAddresses Array of reward token addresses\\n */\\n function setRewardTokenAddresses(address[] calldata _rewardTokenAddresses)\\n external\\n onlyGovernor\\n {\\n uint256 rewardTokenCount = _rewardTokenAddresses.length;\\n for (uint256 i = 0; i < rewardTokenCount; ++i) {\\n require(\\n _rewardTokenAddresses[i] != address(0),\\n \\\"Can not set an empty address as a reward token\\\"\\n );\\n }\\n\\n emit RewardTokenAddressesUpdated(\\n rewardTokenAddresses,\\n _rewardTokenAddresses\\n );\\n rewardTokenAddresses = _rewardTokenAddresses;\\n }\\n\\n /**\\n * @notice Get the reward token addresses.\\n * @return address[] the reward token addresses.\\n */\\n function getRewardTokenAddresses()\\n external\\n view\\n returns (address[] memory)\\n {\\n return rewardTokenAddresses;\\n }\\n\\n /**\\n * @notice Provide support for asset by passing its pToken address.\\n * This method can only be called by the system Governor\\n * @param _asset Address for the asset\\n * @param _pToken Address for the corresponding platform token\\n */\\n function setPTokenAddress(address _asset, address _pToken)\\n external\\n virtual\\n onlyGovernor\\n {\\n _setPTokenAddress(_asset, _pToken);\\n }\\n\\n /**\\n * @notice Remove a supported asset by passing its index.\\n * This method can only be called by the system Governor\\n * @param _assetIndex Index of the asset to be removed\\n */\\n function removePToken(uint256 _assetIndex) external virtual onlyGovernor {\\n require(_assetIndex < assetsMapped.length, \\\"Invalid index\\\");\\n address asset = assetsMapped[_assetIndex];\\n address pToken = assetToPToken[asset];\\n\\n if (_assetIndex < assetsMapped.length - 1) {\\n assetsMapped[_assetIndex] = assetsMapped[assetsMapped.length - 1];\\n }\\n assetsMapped.pop();\\n assetToPToken[asset] = address(0);\\n\\n emit PTokenRemoved(asset, pToken);\\n }\\n\\n /**\\n * @notice Provide support for asset by passing its pToken address.\\n * Add to internal mappings and execute the platform specific,\\n * abstract method `_abstractSetPToken`\\n * @param _asset Address for the asset\\n * @param _pToken Address for the corresponding platform token\\n */\\n function _setPTokenAddress(address _asset, address _pToken) internal {\\n require(assetToPToken[_asset] == address(0), \\\"pToken already set\\\");\\n require(\\n _asset != address(0) && _pToken != address(0),\\n \\\"Invalid addresses\\\"\\n );\\n\\n assetToPToken[_asset] = _pToken;\\n assetsMapped.push(_asset);\\n\\n emit PTokenAdded(_asset, _pToken);\\n\\n _abstractSetPToken(_asset, _pToken);\\n }\\n\\n /**\\n * @notice Transfer token to governor. Intended for recovering tokens stuck in\\n * strategy contracts, i.e. mistaken sends.\\n * @param _asset Address for the asset\\n * @param _amount Amount of the asset to transfer\\n */\\n function transferToken(address _asset, uint256 _amount)\\n public\\n virtual\\n onlyGovernor\\n {\\n require(!supportsAsset(_asset), \\\"Cannot transfer supported asset\\\");\\n IERC20(_asset).safeTransfer(governor(), _amount);\\n }\\n\\n /**\\n * @notice Set the Harvester contract that can collect rewards.\\n * @param _harvesterAddress Address of the harvester contract.\\n */\\n function setHarvesterAddress(address _harvesterAddress)\\n external\\n onlyGovernor\\n {\\n emit HarvesterAddressesUpdated(harvesterAddress, _harvesterAddress);\\n harvesterAddress = _harvesterAddress;\\n }\\n\\n /***************************************\\n Abstract\\n ****************************************/\\n\\n function _abstractSetPToken(address _asset, address _pToken)\\n internal\\n virtual;\\n\\n function safeApproveAllTokens() external virtual;\\n\\n /**\\n * @notice Deposit an amount of assets into the platform\\n * @param _asset Address for the asset\\n * @param _amount Units of asset to deposit\\n */\\n function deposit(address _asset, uint256 _amount) external virtual;\\n\\n /**\\n * @notice Deposit all supported assets in this strategy contract to the platform\\n */\\n function depositAll() external virtual;\\n\\n /**\\n * @notice Withdraw an `amount` of assets from the platform and\\n * send to the `_recipient`.\\n * @param _recipient Address to which the asset should be sent\\n * @param _asset Address of the asset\\n * @param _amount Units of asset to withdraw\\n */\\n function withdraw(\\n address _recipient,\\n address _asset,\\n uint256 _amount\\n ) external virtual;\\n\\n /**\\n * @notice Withdraw all supported assets from platform and\\n * sends to the OToken's Vault.\\n */\\n function withdrawAll() external virtual;\\n\\n /**\\n * @notice Get the total asset value held in the platform.\\n * This includes any interest that was generated since depositing.\\n * @param _asset Address of the asset\\n * @return balance Total value of the asset in the platform\\n */\\n function checkBalance(address _asset)\\n external\\n view\\n virtual\\n returns (uint256 balance);\\n\\n /**\\n * @notice Check if an asset is supported.\\n * @param _asset Address of the asset\\n * @return bool Whether asset is supported\\n */\\n function supportsAsset(address _asset) public view virtual returns (bool);\\n}\\n\",\"keccak256\":\"0x6e99dee31bac1365445ef297fead7e055eb7993cebce078e36b09474b2e0c035\",\"license\":\"BUSL-1.1\"},\"contracts/vault/VaultStorage.sol\":{\"content\":\"// SPDX-License-Identifier: BUSL-1.1\\npragma solidity ^0.8.0;\\n\\n/**\\n * @title OToken VaultStorage contract\\n * @notice The VaultStorage contract defines the storage for the Vault contracts\\n * @author Origin Protocol Inc\\n */\\n\\nimport { IERC20 } from \\\"@openzeppelin/contracts/token/ERC20/IERC20.sol\\\";\\nimport { SafeERC20 } from \\\"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\\\";\\nimport { Address } from \\\"@openzeppelin/contracts/utils/Address.sol\\\";\\n\\nimport { IStrategy } from \\\"../interfaces/IStrategy.sol\\\";\\nimport { IERC20Metadata } from \\\"@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol\\\";\\nimport { Governable } from \\\"../governance/Governable.sol\\\";\\nimport { OUSD } from \\\"../token/OUSD.sol\\\";\\nimport { Initializable } from \\\"../utils/Initializable.sol\\\";\\nimport \\\"../utils/Helpers.sol\\\";\\n\\nabstract contract VaultStorage is Initializable, Governable {\\n using SafeERC20 for IERC20;\\n\\n event AssetAllocated(address _asset, address _strategy, uint256 _amount);\\n event StrategyApproved(address _addr);\\n event StrategyRemoved(address _addr);\\n event Mint(address _addr, uint256 _value);\\n event Redeem(address _addr, uint256 _value);\\n event CapitalPaused();\\n event CapitalUnpaused();\\n event DefaultStrategyUpdated(address _strategy);\\n event RebasePaused();\\n event RebaseUnpaused();\\n event VaultBufferUpdated(uint256 _vaultBuffer);\\n event AllocateThresholdUpdated(uint256 _threshold);\\n event RebaseThresholdUpdated(uint256 _threshold);\\n event StrategistUpdated(address _address);\\n event MaxSupplyDiffChanged(uint256 maxSupplyDiff);\\n event YieldDistribution(address _to, uint256 _yield, uint256 _fee);\\n event TrusteeFeeBpsChanged(uint256 _basis);\\n event TrusteeAddressChanged(address _address);\\n event StrategyAddedToMintWhitelist(address indexed strategy);\\n event StrategyRemovedFromMintWhitelist(address indexed strategy);\\n event RebasePerSecondMaxChanged(uint256 rebaseRatePerSecond);\\n event DripDurationChanged(uint256 dripDuration);\\n event WithdrawalRequested(\\n address indexed _withdrawer,\\n uint256 indexed _requestId,\\n uint256 _amount,\\n uint256 _queued\\n );\\n event WithdrawalClaimed(\\n address indexed _withdrawer,\\n uint256 indexed _requestId,\\n uint256 _amount\\n );\\n event WithdrawalClaimable(uint256 _claimable, uint256 _newClaimable);\\n event WithdrawalClaimDelayUpdated(uint256 _newDelay);\\n\\n // Since we are proxy, all state should be uninitalized.\\n // Since this storage contract does not have logic directly on it\\n // we should not be checking for to see if these variables can be constant.\\n // slither-disable-start uninitialized-state\\n // slither-disable-start constable-states\\n\\n /// @dev mapping of supported vault assets to their configuration\\n uint256 private _deprecated_assets;\\n /// @dev list of all assets supported by the vault.\\n address[] private _deprecated_allAssets;\\n\\n // Strategies approved for use by the Vault\\n struct Strategy {\\n bool isSupported;\\n uint256 _deprecated; // Deprecated storage slot\\n }\\n /// @dev mapping of strategy contracts to their configuration\\n mapping(address => Strategy) public strategies;\\n /// @dev list of all vault strategies\\n address[] internal allStrategies;\\n\\n /// @notice Address of the Oracle price provider contract\\n address private _deprecated_priceProvider;\\n /// @notice pause rebasing if true\\n bool public rebasePaused;\\n /// @notice pause operations that change the OToken supply.\\n /// eg mint, redeem, allocate, mint/burn for strategy\\n bool public capitalPaused;\\n /// @notice Redemption fee in basis points. eg 50 = 0.5%\\n uint256 private _deprecated_redeemFeeBps;\\n /// @notice Percentage of assets to keep in Vault to handle (most) withdrawals. 100% = 1e18.\\n uint256 public vaultBuffer;\\n /// @notice OToken mints over this amount automatically allocate funds. 18 decimals.\\n uint256 public autoAllocateThreshold;\\n /// @notice OToken mints over this amount automatically rebase. 18 decimals.\\n uint256 public rebaseThreshold;\\n\\n /// @dev Address of the OToken token. eg OUSD or OETH.\\n OUSD public oToken;\\n\\n /// @dev Address of the contract responsible for post rebase syncs with AMMs\\n address private _deprecated_rebaseHooksAddr = address(0);\\n\\n /// @dev Deprecated: Address of Uniswap\\n address private _deprecated_uniswapAddr = address(0);\\n\\n /// @notice Address of the Strategist\\n address public strategistAddr = address(0);\\n\\n /// @notice Mapping of asset address to the Strategy that they should automatically\\n // be allocated to\\n uint256 private _deprecated_assetDefaultStrategies;\\n\\n /// @notice Max difference between total supply and total value of assets. 18 decimals.\\n uint256 public maxSupplyDiff;\\n\\n /// @notice Trustee contract that can collect a percentage of yield\\n address public trusteeAddress;\\n\\n /// @notice Amount of yield collected in basis points. eg 2000 = 20%\\n uint256 public trusteeFeeBps;\\n\\n /// @dev Deprecated: Tokens that should be swapped for stablecoins\\n address[] private _deprecated_swapTokens;\\n\\n /// @notice Metapool strategy that is allowed to mint/burn OTokens without changing collateral\\n\\n address private _deprecated_ousdMetaStrategy;\\n\\n /// @notice How much OTokens are currently minted by the strategy\\n int256 private _deprecated_netOusdMintedForStrategy;\\n\\n /// @notice How much net total OTokens are allowed to be minted by all strategies\\n uint256 private _deprecated_netOusdMintForStrategyThreshold;\\n\\n uint256 private _deprecated_swapConfig;\\n\\n // List of strategies that can mint oTokens directly\\n // Used in OETHBaseVaultCore\\n mapping(address => bool) public isMintWhitelistedStrategy;\\n\\n /// @notice Address of the Dripper contract that streams harvested rewards to the Vault\\n /// @dev The vault is proxied so needs to be set with setDripper against the proxy contract.\\n address private _deprecated_dripper;\\n\\n /// Withdrawal Queue Storage /////\\n\\n struct WithdrawalQueueMetadata {\\n // cumulative total of all withdrawal requests included the ones that have already been claimed\\n uint128 queued;\\n // cumulative total of all the requests that can be claimed including the ones that have already been claimed\\n uint128 claimable;\\n // total of all the requests that have been claimed\\n uint128 claimed;\\n // index of the next withdrawal request starting at 0\\n uint128 nextWithdrawalIndex;\\n }\\n\\n /// @notice Global metadata for the withdrawal queue including:\\n /// queued - cumulative total of all withdrawal requests included the ones that have already been claimed\\n /// claimable - cumulative total of all the requests that can be claimed including the ones already claimed\\n /// claimed - total of all the requests that have been claimed\\n /// nextWithdrawalIndex - index of the next withdrawal request starting at 0\\n WithdrawalQueueMetadata public withdrawalQueueMetadata;\\n\\n struct WithdrawalRequest {\\n address withdrawer;\\n bool claimed;\\n uint40 timestamp; // timestamp of the withdrawal request\\n // Amount of oTokens to redeem. eg OETH\\n uint128 amount;\\n // cumulative total of all withdrawal requests including this one.\\n // this request can be claimed when this queued amount is less than or equal to the queue's claimable amount.\\n uint128 queued;\\n }\\n\\n /// @notice Mapping of withdrawal request indices to the user withdrawal request data\\n mapping(uint256 => WithdrawalRequest) public withdrawalRequests;\\n\\n /// @notice Sets a minimum delay that is required to elapse between\\n /// requesting async withdrawals and claiming the request.\\n /// When set to 0 async withdrawals are disabled.\\n uint256 public withdrawalClaimDelay;\\n\\n /// @notice Time in seconds that the vault last rebased yield.\\n uint64 public lastRebase;\\n\\n /// @notice Automatic rebase yield calculations. In seconds. Set to 0 or 1 to disable.\\n uint64 public dripDuration;\\n\\n /// @notice max rebase percentage per second\\n /// Can be used to set maximum yield of the protocol,\\n /// spreading out yield over time\\n uint64 public rebasePerSecondMax;\\n\\n /// @notice target rebase rate limit, based on past rates and funds available.\\n uint64 public rebasePerSecondTarget;\\n\\n uint256 internal constant MAX_REBASE = 0.02 ether;\\n uint256 internal constant MAX_REBASE_PER_SECOND =\\n uint256(0.05 ether) / 1 days;\\n\\n /// @notice Default strategy for asset\\n address public defaultStrategy;\\n\\n // For future use\\n uint256[42] private __gap;\\n\\n /// @notice Index of WETH asset in allAssets array\\n /// Legacy OETHVaultCore code, relocated here for vault consistency.\\n uint256 private _deprecated_wethAssetIndex;\\n\\n /// @dev Address of the asset (eg. WETH or USDC)\\n address public immutable asset;\\n uint8 internal immutable assetDecimals;\\n\\n // slither-disable-end constable-states\\n // slither-disable-end uninitialized-state\\n\\n constructor(address _asset) {\\n uint8 _decimals = IERC20Metadata(_asset).decimals();\\n require(_decimals <= 18, \\\"invalid asset decimals\\\");\\n asset = _asset;\\n assetDecimals = _decimals;\\n }\\n\\n /// @notice Deprecated: use `oToken()` instead.\\n function oUSD() external view returns (OUSD) {\\n return oToken;\\n }\\n}\\n\",\"keccak256\":\"0xcca0e0ebbbbda50b23fba7aea96a1e34f6a9d58c8f2e86e84b0911d4a9e5d418\",\"license\":\"BUSL-1.1\"},\"lib/openzeppelin/interfaces/IERC4626.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.0;\\n\\nimport { IERC20Metadata } from \\\"@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol\\\";\\nimport { IERC20 } from \\\"@openzeppelin/contracts/token/ERC20/IERC20.sol\\\";\\n\\ninterface IERC4626 is IERC20, IERC20Metadata {\\n event Deposit(address indexed caller, address indexed owner, uint256 assets, uint256 shares);\\n\\n event Withdraw(\\n address indexed caller,\\n address indexed receiver,\\n address indexed owner,\\n uint256 assets,\\n uint256 shares\\n );\\n\\n /**\\n * @dev Returns the address of the underlying token used for the Vault for accounting, depositing, and withdrawing.\\n *\\n * - MUST be an ERC-20 token contract.\\n * - MUST NOT revert.\\n */\\n function asset() external view returns (address assetTokenAddress);\\n\\n /**\\n * @dev Returns the total amount of the underlying asset that is \\u201cmanaged\\u201d by Vault.\\n *\\n * - SHOULD include any compounding that occurs from yield.\\n * - MUST be inclusive of any fees that are charged against assets in the Vault.\\n * - MUST NOT revert.\\n */\\n function totalAssets() external view returns (uint256 totalManagedAssets);\\n\\n /**\\n * @dev Returns the amount of shares that the Vault would exchange for the amount of assets provided, in an ideal\\n * scenario where all the conditions are met.\\n *\\n * - MUST NOT be inclusive of any fees that are charged against assets in the Vault.\\n * - MUST NOT show any variations depending on the caller.\\n * - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange.\\n * - MUST NOT revert.\\n *\\n * NOTE: This calculation MAY NOT reflect the \\u201cper-user\\u201d price-per-share, and instead should reflect the\\n * \\u201caverage-user\\u2019s\\u201d price-per-share, meaning what the average user should expect to see when exchanging to and\\n * from.\\n */\\n function convertToShares(uint256 assets) external view returns (uint256 shares);\\n\\n /**\\n * @dev Returns the amount of assets that the Vault would exchange for the amount of shares provided, in an ideal\\n * scenario where all the conditions are met.\\n *\\n * - MUST NOT be inclusive of any fees that are charged against assets in the Vault.\\n * - MUST NOT show any variations depending on the caller.\\n * - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange.\\n * - MUST NOT revert.\\n *\\n * NOTE: This calculation MAY NOT reflect the \\u201cper-user\\u201d price-per-share, and instead should reflect the\\n * \\u201caverage-user\\u2019s\\u201d price-per-share, meaning what the average user should expect to see when exchanging to and\\n * from.\\n */\\n function convertToAssets(uint256 shares) external view returns (uint256 assets);\\n\\n /**\\n * @dev Returns the maximum amount of the underlying asset that can be deposited into the Vault for the receiver,\\n * through a deposit call.\\n *\\n * - MUST return a limited value if receiver is subject to some deposit limit.\\n * - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of assets that may be deposited.\\n * - MUST NOT revert.\\n */\\n function maxDeposit(address receiver) external view returns (uint256 maxAssets);\\n\\n /**\\n * @dev Allows an on-chain or off-chain user to simulate the effects of their deposit at the current block, given\\n * current on-chain conditions.\\n *\\n * - MUST return as close to and no more than the exact amount of Vault shares that would be minted in a deposit\\n * call in the same transaction. I.e. deposit should return the same or more shares as previewDeposit if called\\n * in the same transaction.\\n * - MUST NOT account for deposit limits like those returned from maxDeposit and should always act as though the\\n * deposit would be accepted, regardless if the user has enough tokens approved, etc.\\n * - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees.\\n * - MUST NOT revert.\\n *\\n * NOTE: any unfavorable discrepancy between convertToShares and previewDeposit SHOULD be considered slippage in\\n * share price or some other type of condition, meaning the depositor will lose assets by depositing.\\n */\\n function previewDeposit(uint256 assets) external view returns (uint256 shares);\\n\\n /**\\n * @dev Mints shares Vault shares to receiver by depositing exactly amount of underlying tokens.\\n *\\n * - MUST emit the Deposit event.\\n * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the\\n * deposit execution, and are accounted for during deposit.\\n * - MUST revert if all of assets cannot be deposited (due to deposit limit being reached, slippage, the user not\\n * approving enough underlying tokens to the Vault contract, etc).\\n *\\n * NOTE: most implementations will require pre-approval of the Vault with the Vault\\u2019s underlying asset token.\\n */\\n function deposit(uint256 assets, address receiver) external returns (uint256 shares);\\n\\n /**\\n * @dev Returns the maximum amount of the Vault shares that can be minted for the receiver, through a mint call.\\n * - MUST return a limited value if receiver is subject to some mint limit.\\n * - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of shares that may be minted.\\n * - MUST NOT revert.\\n */\\n function maxMint(address receiver) external view returns (uint256 maxShares);\\n\\n /**\\n * @dev Allows an on-chain or off-chain user to simulate the effects of their mint at the current block, given\\n * current on-chain conditions.\\n *\\n * - MUST return as close to and no fewer than the exact amount of assets that would be deposited in a mint call\\n * in the same transaction. I.e. mint should return the same or fewer assets as previewMint if called in the\\n * same transaction.\\n * - MUST NOT account for mint limits like those returned from maxMint and should always act as though the mint\\n * would be accepted, regardless if the user has enough tokens approved, etc.\\n * - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees.\\n * - MUST NOT revert.\\n *\\n * NOTE: any unfavorable discrepancy between convertToAssets and previewMint SHOULD be considered slippage in\\n * share price or some other type of condition, meaning the depositor will lose assets by minting.\\n */\\n function previewMint(uint256 shares) external view returns (uint256 assets);\\n\\n /**\\n * @dev Mints exactly shares Vault shares to receiver by depositing amount of underlying tokens.\\n *\\n * - MUST emit the Deposit event.\\n * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the mint\\n * execution, and are accounted for during mint.\\n * - MUST revert if all of shares cannot be minted (due to deposit limit being reached, slippage, the user not\\n * approving enough underlying tokens to the Vault contract, etc).\\n *\\n * NOTE: most implementations will require pre-approval of the Vault with the Vault\\u2019s underlying asset token.\\n */\\n function mint(uint256 shares, address receiver) external returns (uint256 assets);\\n\\n /**\\n * @dev Returns the maximum amount of the underlying asset that can be withdrawn from the owner balance in the\\n * Vault, through a withdraw call.\\n *\\n * - MUST return a limited value if owner is subject to some withdrawal limit or timelock.\\n * - MUST NOT revert.\\n */\\n function maxWithdraw(address owner) external view returns (uint256 maxAssets);\\n\\n /**\\n * @dev Allows an on-chain or off-chain user to simulate the effects of their withdrawal at the current block,\\n * given current on-chain conditions.\\n *\\n * - MUST return as close to and no fewer than the exact amount of Vault shares that would be burned in a withdraw\\n * call in the same transaction. I.e. withdraw should return the same or fewer shares as previewWithdraw if\\n * called\\n * in the same transaction.\\n * - MUST NOT account for withdrawal limits like those returned from maxWithdraw and should always act as though\\n * the withdrawal would be accepted, regardless if the user has enough shares, etc.\\n * - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees.\\n * - MUST NOT revert.\\n *\\n * NOTE: any unfavorable discrepancy between convertToShares and previewWithdraw SHOULD be considered slippage in\\n * share price or some other type of condition, meaning the depositor will lose assets by depositing.\\n */\\n function previewWithdraw(uint256 assets) external view returns (uint256 shares);\\n\\n /**\\n * @dev Burns shares from owner and sends exactly assets of underlying tokens to receiver.\\n *\\n * - MUST emit the Withdraw event.\\n * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the\\n * withdraw execution, and are accounted for during withdraw.\\n * - MUST revert if all of assets cannot be withdrawn (due to withdrawal limit being reached, slippage, the owner\\n * not having enough shares, etc).\\n *\\n * Note that some implementations will require pre-requesting to the Vault before a withdrawal may be performed.\\n * Those methods should be performed separately.\\n */\\n function withdraw(\\n uint256 assets,\\n address receiver,\\n address owner\\n ) external returns (uint256 shares);\\n\\n /**\\n * @dev Returns the maximum amount of Vault shares that can be redeemed from the owner balance in the Vault,\\n * through a redeem call.\\n *\\n * - MUST return a limited value if owner is subject to some withdrawal limit or timelock.\\n * - MUST return balanceOf(owner) if owner is not subject to any withdrawal limit or timelock.\\n * - MUST NOT revert.\\n */\\n function maxRedeem(address owner) external view returns (uint256 maxShares);\\n\\n /**\\n * @dev Allows an on-chain or off-chain user to simulate the effects of their redeemption at the current block,\\n * given current on-chain conditions.\\n *\\n * - MUST return as close to and no more than the exact amount of assets that would be withdrawn in a redeem call\\n * in the same transaction. I.e. redeem should return the same or more assets as previewRedeem if called in the\\n * same transaction.\\n * - MUST NOT account for redemption limits like those returned from maxRedeem and should always act as though the\\n * redemption would be accepted, regardless if the user has enough shares, etc.\\n * - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees.\\n * - MUST NOT revert.\\n *\\n * NOTE: any unfavorable discrepancy between convertToAssets and previewRedeem SHOULD be considered slippage in\\n * share price or some other type of condition, meaning the depositor will lose assets by redeeming.\\n */\\n function previewRedeem(uint256 shares) external view returns (uint256 assets);\\n\\n /**\\n * @dev Burns exactly shares from owner and sends assets of underlying tokens to receiver.\\n *\\n * - MUST emit the Withdraw event.\\n * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the\\n * redeem execution, and are accounted for during redeem.\\n * - MUST revert if all of shares cannot be redeemed (due to withdrawal limit being reached, slippage, the owner\\n * not having enough shares, etc).\\n *\\n * NOTE: some implementations will require pre-requesting to the Vault before a withdrawal may be performed.\\n * Those methods should be performed separately.\\n */\\n function redeem(\\n uint256 shares,\\n address receiver,\\n address owner\\n ) external returns (uint256 assets);\\n}\",\"keccak256\":\"0xd1abd028496aacc3eef98e585a744e1a449dcf9b2e818c59d15d5c0091c3f293\",\"license\":\"MIT\"}},\"version\":1}", + "bytecode": "0x6101c060405234801561001157600080fd5b506040516158a13803806158a18339810160408190526100309161066a565b6080810151829081836001600160a01b0383166100945760405162461bcd60e51b815260206004820152601460248201527f496e76616c69642055534443206164647265737300000000000000000000000060448201526064015b60405180910390fd5b60a08101516001600160a01b03166100ee5760405162461bcd60e51b815260206004820152601960248201527f496e76616c696420706565722055534443206164647265737300000000000000604482015260640161008b565b80516001600160a01b031661013b5760405162461bcd60e51b8152602060048201526013602482015272496e76616c6964204343545020636f6e66696760681b604482015260640161008b565b60208101516001600160a01b031661018b5760405162461bcd60e51b8152602060048201526013602482015272496e76616c6964204343545020636f6e66696760681b604482015260640161008b565b60608101516001600160a01b03166101e55760405162461bcd60e51b815260206004820152601d60248201527f496e76616c696420706565722073747261746567792061646472657373000000604482015260640161008b565b60208101516001600160a01b0390811660809081528251821660a052604083015163ffffffff166101005260608301518216610120528201805190911660c052516000906102329061046d565b90506000610249836080015161054d60201b60201c565b90508160061461029b5760405162461bcd60e51b815260206004820152601d60248201527f4261736520746f6b656e20646563696d616c73206d7573742062652036000000604482015260640161008b565b604051635553444360e01b602082015260240160405160208183030381529060405280519060200120816040516020016102d5919061076b565b60405160208183030381529060405280519060200120146103385760405162461bcd60e51b815260206004820152601960248201527f546f6b656e2073796d626f6c206d757374206265205553444300000000000000604482015260640161008b565b505060a001516001600160a01b0390811660e0528151811661014052602090910151811661016052915182166101805281166101a081905260c051909116146103b45760405162461bcd60e51b815260206004820152600e60248201526d0a8ded6cadc40dad2e6dac2e8c6d60931b604482015260640161008b565b81516001600160a01b031661040b5760405162461bcd60e51b815260206004820152601860248201527f496e76616c696420706c6174666f726d20616464726573730000000000000000604482015260640161008b565b60208201516001600160a01b0316156104665760405162461bcd60e51b815260206004820152601560248201527f496e76616c6964207661756c7420616464726573730000000000000000000000604482015260640161008b565b505061083e565b600080826001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa1580156104ae573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104d29190610787565b60ff169050600481101580156104e9575060128111155b6105475760405162461bcd60e51b815260206004820152602960248201527f546f6b656e206d75737420686176652073756666696369656e7420646563696d604482015268616c20706c6163657360b81b606482015260840161008b565b92915050565b60606000826001600160a01b03166395d89b416040518163ffffffff1660e01b8152600401600060405180830381865afa15801561058f573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526105b791908101906107aa565b9392505050565b634e487b7160e01b600052604160045260246000fd5b604080519081016001600160401b03811182821017156105f6576105f66105be565b60405290565b60405160c081016001600160401b03811182821017156105f6576105f66105be565b604051601f8201601f191681016001600160401b0381118282101715610646576106466105be565b604052919050565b80516001600160a01b038116811461066557600080fd5b919050565b60008082840361010081121561067f57600080fd5b604081121561068d57600080fd5b6106956105d4565b61069e8561064e565b81526106ac6020860161064e565b6020820152925060c0603f19820112156106c557600080fd5b506106ce6105fc565b6106da6040850161064e565b81526106e86060850161064e565b6020820152608084015163ffffffff8116811461070457600080fd5b604082015261071560a0850161064e565b606082015261072660c0850161064e565b608082015261073760e0850161064e565b60a0820152809150509250929050565b60005b8381101561076257818101518382015260200161074a565b50506000910152565b6000825161077d818460208701610747565b9190910192915050565b60006020828403121561079957600080fd5b815160ff811681146105b757600080fd5b6000602082840312156107bc57600080fd5b81516001600160401b038111156107d257600080fd5b8201601f810184136107e357600080fd5b80516001600160401b038111156107fc576107fc6105be565b61080f601f8201601f191660200161061e565b81815285602083850101111561082457600080fd5b610835826020830160208601610747565b95945050505050565b60805160a05160c05160e05161010051610120516101405161016051610180516101a051614e83610a1e6000396000818161038c01528181610707015281816108fb015281816118ff01526130940152600081816105ba015281816127a30152612ff0015260006104bb0152600081816107ca015281816115b50152818161195301528181611a4d015281816120ee0152818161263f01528181612e8b01526130640152600081816105800152818161118b015281816122a9015281816123ff015281816124b501528181613d660152613e4201526000818161065101528181610f1d01528181612235015281816123dd0152818161249301528181613d440152613e2001526000818161061a01526110460152600081816103d601528181610d3c015281816114c30152818161152a01528181611a7401528181611f410152818161209a015281816125d401528181612e19015281816133d70152818161345b0152818161349f015281816136ea0152818161376d015281816137ab0152818161382801528181613c7001528181613d8b0152613e640152600081816106d501528181610f9f01528181613c920152613d1401526000818161068d015281816109990152818161122f0152818161170901526123af0152614e836000f3fe608060405234801561001057600080fd5b506004361061030c5760003560e01c80636c9fa59e1161019d578063ad1728cb116100e9578063d9caed12116100a2578063df4426781161007c578063df442678146107f4578063f4537f7814610807578063f6ca71b014610822578063fc1b31131461083757600080fd5b8063d9caed12146107b2578063dbe55e56146107c5578063de5f6268146107ec57600080fd5b8063ad1728cb14610737578063b3ab15fb1461073f578063c2e1e3f414610752578063c7af335214610765578063d38bfff41461076d578063d7dd614b1461078057600080fd5b8063853828b6116101565780639136616a116101305780639136616a146106af57806396d538bb146106bd5780639748cf7c146106d0578063aa388af6146106f757600080fd5b8063853828b6146106445780638949f6f31461064c5780638c73eb041461068857600080fd5b80636c9fa59e146105b5578063773540b3146105dc5780637b2d9b2c146105ef5780637c92f219146106025780637ddfce2d146106155780638129fc1c1461063c57600080fd5b806342c8c11c1161025c578063570d8e1d116102155780635d36b190116101ef5780635d36b190146105605780635f515226146105685780636369e49f1461057b57806367c7066c146105a257600080fd5b8063570d8e1d146105315780635a063f63146105455780635a2364171461054d57600080fd5b806342c8c11c146104a3578063430bf08a146104b657806347e7ef24146104dd5780635324df32146104f0578063564a515814610504578063570ca7351461051757600080fd5b806311eac855116102c9578063237839f0116102a3578063237839f01461044457806323c9ab3d146104575780633335ad7f146104835780633c3dbbc71461048b57600080fd5b806311eac855146103d157806316a0f250146103f85780631801c4081461041957600080fd5b80630c340a24146103115780630ed57b3a146103365780630fc3b4c41461034b5780631072cbea146103745780631083f7611461038757806311cffb67146103ae575b600080fd5b610319610844565b6040516001600160a01b0390911681526020015b60405180910390f35b6103496103443660046141a2565b610861565b005b6103196103593660046141db565b6067602052600090815260409020546001600160a01b031681565b6103496103823660046141f8565b6108cd565b6103197f000000000000000000000000000000000000000000000000000000000000000081565b6103c16103bc3660046142f2565b61098c565b604051901515815260200161032d565b6103197f000000000000000000000000000000000000000000000000000000000000000081565b6033546104069061ffff1681565b60405161ffff909116815260200161032d565b603354600160201b90046001600160401b031660009081526034602052604090205460ff16156103c1565b6103496104523660046143a4565b610a72565b6103c1610465366004614414565b6001600160401b031660009081526034602052604090205460ff1690565b610349610c91565b610495620f424081565b60405190815260200161032d565b6103496104b1366004614443565b610d91565b6103197f000000000000000000000000000000000000000000000000000000000000000081565b6103496104eb3660046141f8565b610dc1565b6033546104069062010000900461ffff1681565b61034961051236600461445e565b610e43565b60335461031990600160601b90046001600160a01b031681565b61010354610319906001600160a01b031681565b61034961134d565b61034961055b366004614443565b6113ec565b610349611419565b6104956105763660046141db565b6114bf565b6103197f000000000000000000000000000000000000000000000000000000000000000081565b606b54610319906001600160a01b031681565b6103197f000000000000000000000000000000000000000000000000000000000000000081565b6103496105ea3660046141db565b6116a5565b6103196105fd3660046144c5565b6116d2565b6103c16106103660046142f2565b6116fc565b6103197f000000000000000000000000000000000000000000000000000000000000000081565b610349611834565b6103496119c7565b6106737f000000000000000000000000000000000000000000000000000000000000000081565b60405163ffffffff909116815260200161032d565b6103197f000000000000000000000000000000000000000000000000000000000000000081565b6103496103443660046144c5565b6103496106cb3660046144de565b611b5d565b6103197f000000000000000000000000000000000000000000000000000000000000000081565b6103c16107053660046141db565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0390811691161490565b610349611c7a565b61034961074d3660046141db565b611ca6565b6103496107603660046141db565b611cd3565b6103c1611d60565b61034961077b3660046141db565b611d91565b60335461079a90600160201b90046001600160401b031681565b6040516001600160401b03909116815260200161032d565b6103496107c0366004614513565b611e35565b6103197f000000000000000000000000000000000000000000000000000000000000000081565b610349611eb9565b610349610802366004614554565b611fbb565b610319733ef3d8ba38ebe18db133cec108f4d14ce00dd9ae81565b61082a612167565b60405161032d91906145f1565b6104956509184e72a00081565b600061085c600080516020614e2e8339815191525490565b905090565b610869611d60565b61088e5760405162461bcd60e51b815260040161088590614604565b60405180910390fd5b60405162461bcd60e51b81526020600482015260146024820152733ab739bab83837b93a32b210333ab731ba34b7b760611b6044820152606401610885565b6108d5611d60565b6108f15760405162461bcd60e51b815260040161088590614604565b6001600160a01b037f000000000000000000000000000000000000000000000000000000000000000081169083160361096c5760405162461bcd60e51b815260206004820152601f60248201527f43616e6e6f74207472616e7366657220737570706f72746564206173736574006044820152606401610885565b610988610977610844565b6001600160a01b03841690836121c9565b5050565b6000336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614610a065760405162461bcd60e51b815260206004820152601e60248201527f43616c6c6572206973206e6f742043435450207472616e736d697474657200006044820152606401610885565b6107d08363ffffffff161015610a5e5760405162461bcd60e51b815260206004820152601a60248201527f46696e616c697479207468726573686f6c6420746f6f206c6f770000000000006044820152606401610885565b610a69858584612231565b95945050505050565b604080516001808252818301909252600091602080830190803683370190505090503081600081518110610aa857610aa861463b565b6001600160a01b0392909216602092830291909101909101526040805160018082528183019092526000918160200160208202803683370190505090508581600081518110610af957610af961463b565b6001600160a01b0392909216602092830291909101909101526040805160018082528183019092526000918160200160208202803683370190505090508581600081518110610b4a57610b4a61463b565b6020908102919091010152604080516001808252818301909252600091816020015b6060815260200190600190039081610b6c5790505090508585808060200260200160405190810160405280939291908181526020018383602002808284376000920182905250855186945090925015159050610bca57610bca61463b565b60209081029190910101526040516301c7ba5760e61b8152733ef3d8ba38ebe18db133cec108f4d14ce00dd9ae906371ee95c090610c12908790879087908790600401614651565b600060405180830381600087803b158015610c2c57600080fd5b505af1158015610c40573d6000803e3d6000fd5b50505050876001600160a01b03167f2d5429efdeca7741a8cd94067b18d988bc4e5f1d5b8272c37b7bfc31e9bfa32c88604051610c7f91815260200190565b60405180910390a25050505050505050565b603354600160601b90046001600160a01b0316331480610cbc5750610103546001600160a01b031633145b80610cca5750610cca611d60565b610d355760405162461bcd60e51b815260206004820152603660248201527f43616c6c6572206973206e6f7420746865204f70657261746f722c20537472616044820152753a32b3b4b9ba1037b9103a34329023b7bb32b93737b960511b6064820152608401610885565b6000610d607f00000000000000000000000000000000000000000000000000000000000000006114bf565b90506000610d86603360049054906101000a90046001600160401b03168360004261232a565b905061098881612395565b610d99611d60565b610db55760405162461bcd60e51b815260040161088590614604565b610dbe816124ee565b50565b610103546001600160a01b0316331480610dde5750610dde611d60565b610dfa5760405162461bcd60e51b81526004016108859061473f565b600080516020614e0e83398151915280546001198101610e2c5760405162461bcd60e51b815260040161088590614787565b60028255610e3a8484612589565b50600190555050565b603354600160601b90046001600160a01b03163314610ea45760405162461bcd60e51b815260206004820152601a60248201527f43616c6c6572206973206e6f7420746865204f70657261746f720000000000006044820152606401610885565b6000806000806000610eb5876127ff565b94509450945094509450600163ffffffff168563ffffffff1614610f1b5760405162461bcd60e51b815260206004820152601c60248201527f496e76616c69642043435450206d6573736167652076657273696f6e000000006044820152606401610885565b7f000000000000000000000000000000000000000000000000000000000000000063ffffffff168463ffffffff1614610f8e5760405162461bcd60e51b81526020600482015260156024820152742ab735b737bbb71029b7bab931b2902237b6b0b4b760591b6044820152606401610885565b610f99816000612855565b945060007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316846001600160a01b031614905080156110da578563ffffffff166001148015610ff25750815160e411155b6110355760405162461bcd60e51b8152602060048201526014602482015273496e76616c6964206275726e206d65737361676560601b6044820152606401610885565b600061104283600461287d565b90507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316816001600160a01b0316146110ba5760405162461bcd60e51b815260206004820152601260248201527124b73b30b634b210313ab937103a37b5b2b760711b6044820152606401610885565b6110c583606461287d565b94506110d283602461287d565b935050611131565b63ffffffff86166103f2146111315760405162461bcd60e51b815260206004820152601b60248201527f556e737570706f72746564206d6573736167652076657273696f6e00000000006044820152606401610885565b306001600160a01b038416146111895760405162461bcd60e51b815260206004820152601c60248201527f556e657870656374656420726563697069656e742061646472657373000000006044820152606401610885565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316846001600160a01b0316146112155760405162461bcd60e51b815260206004820152602260248201527f496e636f72726563742073656e6465722f726563697069656e74206164647265604482015261737360f01b6064820152608401610885565b604051630afd9fa560e31b81526000906001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016906357ecfd2890611266908c908c906004016147ff565b6020604051808303816000875af1158015611285573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906112a99190614824565b9050806112f15760405162461bcd60e51b8152602060048201526016602482015275149958d95a5d99481b595cdcd859d94819985a5b195960521b6044820152606401610885565b811561134257825160009061130a90859060e490612897565b905060006113198560446129f1565b905060006113288660a46129f1565b905061133e611337828461485c565b8285612a0b565b5050505b505050505050505050565b606b546001600160a01b031633146113a75760405162461bcd60e51b815260206004820152601b60248201527f43616c6c6572206973206e6f74207468652048617276657374657200000000006044820152606401610885565b600080516020614e0e833981519152805460011981016113d95760405162461bcd60e51b815260040161088590614787565b600282556113e5612a70565b5060019055565b6113f4611d60565b6114105760405162461bcd60e51b815260040161088590614604565b610dbe81612b84565b7f44c4d30b2eaad5130ad70c3ba6972730566f3e6359ab83e800d905c61b1c51db546001600160a01b0316336001600160a01b0316146114b45760405162461bcd60e51b815260206004820152603060248201527f4f6e6c79207468652070656e64696e6720476f7665726e6f722063616e20636f60448201526f6d706c6574652074686520636c61696d60801b6064820152608401610885565b6114bd33612c21565b565b60007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316826001600160a01b0316146115125760405162461bcd60e51b81526004016108859061486f565b6040516370a0823160e01b81523060048201526000907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906370a0823190602401602060405180830381865afa158015611579573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061159d91906148a6565b6040516370a0823160e01b81523060048201529091507f00000000000000000000000000000000000000000000000000000000000000009082906001600160a01b03831690634cdad5069082906370a0823190602401602060405180830381865afa158015611610573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061163491906148a6565b6040518263ffffffff1660e01b815260040161165291815260200190565b602060405180830381865afa15801561166f573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061169391906148a6565b61169d91906148bf565b949350505050565b6116ad611d60565b6116c95760405162461bcd60e51b815260040161088590614604565b610dbe81612c80565b606c81815481106116e257600080fd5b6000918252602090912001546001600160a01b0316905081565b6000336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146117765760405162461bcd60e51b815260206004820152601e60248201527f43616c6c6572206973206e6f742043435450207472616e736d697474657200006044820152606401610885565b60335461ffff166103e8146117dc5760405162461bcd60e51b815260206004820152602660248201527f556e66696e616c697a6564206d6573736167657320617265206e6f74207375706044820152651c1bdc9d195960d21b6064820152608401610885565b6103e88363ffffffff161015610a5e5760405162461bcd60e51b815260206004820152601a60248201527f46696e616c697479207468726573686f6c6420746f6f206c6f770000000000006044820152606401610885565b61183c611d60565b6118585760405162461bcd60e51b815260040161088590614604565b600054610100900460ff1680611871575060005460ff16155b61188d5760405162461bcd60e51b8152600401610885906148d2565b600054610100900460ff161580156118af576000805461ffff19166101011790555b6040805160008082526001602080840182815260608501865293949293928501908036833750506040805160018082528183019092529293506000929150602080830190803683370190505090507f0000000000000000000000000000000000000000000000000000000000000000826000815181106119315761193161463b565b60200260200101906001600160a01b031690816001600160a01b0316815250507f0000000000000000000000000000000000000000000000000000000000000000816000815181106119855761198561463b565b60200260200101906001600160a01b031690816001600160a01b0316815250506119b0838383612ccf565b5050508015610dbe576000805461ff001916905550565b610103546001600160a01b03163314806119e457506119e4611d60565b611a005760405162461bcd60e51b81526004016108859061473f565b600080516020614e0e83398151915280546001198101611a325760405162461bcd60e51b815260040161088590614787565b600282556040516370a0823160e01b815230600482018190527f000000000000000000000000000000000000000000000000000000000000000091611b5591907f0000000000000000000000000000000000000000000000000000000000000000906001600160a01b03851690634cdad5069082906370a0823190602401602060405180830381865afa158015611acd573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611af191906148a6565b6040518263ffffffff1660e01b8152600401611b0f91815260200190565b602060405180830381865afa158015611b2c573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611b5091906148a6565b612d7b565b505060019055565b611b65611d60565b611b815760405162461bcd60e51b815260040161088590614604565b8060005b81811015611c2b576000848483818110611ba157611ba161463b565b9050602002016020810190611bb691906141db565b6001600160a01b031603611c235760405162461bcd60e51b815260206004820152602e60248201527f43616e206e6f742073657420616e20656d70747920616464726573732061732060448201526d30903932bbb0b932103a37b5b2b760911b6064820152608401610885565b600101611b85565b507f04c0b9649497d316554306e53678d5f5f5dbc3a06f97dec13ff4cfe98b986bbc606c8484604051611c6093929190614920565b60405180910390a1611c74606c84846140c0565b50505050565b611c82611d60565b611c9e5760405162461bcd60e51b815260040161088590614604565b6114bd61304d565b611cae611d60565b611cca5760405162461bcd60e51b815260040161088590614604565b610dbe81613101565b611cdb611d60565b611cf75760405162461bcd60e51b815260040161088590614604565b606b54604080516001600160a01b03928316815291831660208301527fe48386b84419f4d36e0f96c10cc3510b6fb1a33795620c5098b22472bbe90796910160405180910390a1606b80546001600160a01b0319166001600160a01b0392909216919091179055565b6000611d78600080516020614e2e8339815191525490565b6001600160a01b0316336001600160a01b031614905090565b611d99611d60565b611db55760405162461bcd60e51b815260040161088590614604565b611ddd817f44c4d30b2eaad5130ad70c3ba6972730566f3e6359ab83e800d905c61b1c51db55565b806001600160a01b0316611dfd600080516020614e2e8339815191525490565b6001600160a01b03167fa39cc5eb22d0f34d8beaefee8a3f17cc229c1a1d1ef87a5ad47313487b1c4f0d60405160405180910390a350565b610103546001600160a01b0316331480611e525750611e52611d60565b611e6e5760405162461bcd60e51b81526004016108859061473f565b600080516020614e0e83398151915280546001198101611ea05760405162461bcd60e51b815260040161088590614787565b60028255611eaf858585612d7b565b5060019055505050565b610103546001600160a01b0316331480611ed65750611ed6611d60565b611ef25760405162461bcd60e51b81526004016108859061473f565b600080516020614e0e83398151915280546001198101611f245760405162461bcd60e51b815260040161088590614787565b600282556040516370a0823160e01b81523060048201526113e5907f0000000000000000000000000000000000000000000000000000000000000000906001600160a01b038216906370a0823190602401602060405180830381865afa158015611f92573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611fb691906148a6565b612589565b611fc3611d60565b611fdf5760405162461bcd60e51b815260040161088590614604565b600054610100900460ff1680611ff8575060005460ff16155b6120145760405162461bcd60e51b8152600401610885906148d2565b600054610100900460ff16158015612036576000805461ffff19166101011790555b61204184848461315c565b61204a85612c80565b6040805160008082526001602080840182815260608501865293949293928501908036833750506040805160018082528183019092529293506000929150602080830190803683370190505090507f0000000000000000000000000000000000000000000000000000000000000000826000815181106120cc576120cc61463b565b60200260200101906001600160a01b031690816001600160a01b0316815250507f0000000000000000000000000000000000000000000000000000000000000000816000815181106121205761212061463b565b60200260200101906001600160a01b031690816001600160a01b03168152505061214b838383612ccf565b5050508015612160576000805461ff00191690555b5050505050565b6060606c8054806020026020016040519081016040528092919081815260200182805480156121bf57602002820191906000526020600020905b81546001600160a01b031681526001909101906020018083116121a1575b5050505050905090565b6040516001600160a01b03831660248201526044810182905261222c90849063a9059cbb60e01b906064015b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b0319909316929092179091526131b1565b505050565b60007f000000000000000000000000000000000000000000000000000000000000000063ffffffff168463ffffffff16146122a65760405162461bcd60e51b81526020600482015260156024820152742ab735b737bbb71029b7bab931b2902237b6b0b4b760591b6044820152606401610885565b827f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03908116908216146123145760405162461bcd60e51b815260206004820152600e60248201526d2ab735b737bbb71029b2b73232b960911b6044820152606401610885565b61231d83613283565b60019150505b9392505050565b604080516001600160401b038616602082015280820185905283151560608083019190915260808083018590528351808403909101815260a083019093529161237c916103f29160039160c0016149bb565b6040516020818303038152906040529050949350505050565b6033546040516314b157ab60e01b81526001600160a01b037f00000000000000000000000000000000000000000000000000000000000000008116926314b157ab92612433927f0000000000000000000000000000000000000000000000000000000000000000927f000000000000000000000000000000000000000000000000000000000000000090911691829161ffff16908890600401614a00565b600060405180830381600087803b15801561244d57600080fd5b505af1158015612461573d6000803e3d6000fd5b50506033546040517f90d92e80437532a834bc16642d235a7c34efc4d5690f3f6e69794c6d8c5c729093506124e392507f0000000000000000000000000000000000000000000000000000000000000000917f00000000000000000000000000000000000000000000000000000000000000009161ffff909116908690614a37565b60405180910390a150565b610bb88161ffff16111561253b5760405162461bcd60e51b815260206004820152601460248201527308ccaca40e0e4cadad2eada40e8dede40d0d2ced60631b6044820152606401610885565b6033805463ffff000019166201000061ffff8416908102919091179091556040519081527f938c72cb9ba014c7d4d26f156021299f8926dee2aab1421b32a2408a8261cf60906020016124e3565b600081116125d25760405162461bcd60e51b81526020600482015260166024820152754d757374206465706f73697420736f6d657468696e6760501b6044820152606401610885565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316826001600160a01b0316146126235760405162461bcd60e51b81526004016108859061486f565b604051636e553f6560e01b8152600481018290523060248201527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031690636e553f65906044016020604051808303816000875af19250505080156126ac575060408051601f3d908101601f191682019092526126a9918101906148a6565b60015b612794576126b8614a79565b806308c379a00361273057506126cc614a95565b806126d75750612732565b7fcce3c000dc0fa4d3156e53ca2c0771b6dfa039640d4885f2df488a7f732540d4816040516020016127099190614b18565b60408051601f198184030181529082905261272391614b50565b60405180910390a1505050565b505b3d80801561275c576040519150601f19603f3d011682016040523d82523d6000602084013e612761565b606091505b507fcce3c000dc0fa4d3156e53ca2c0771b6dfa039640d4885f2df488a7f732540d4816040516020016127099190614b63565b50604080516001600160a01b037f000000000000000000000000000000000000000000000000000000000000000081168252602082018490528416917f5548c837ab068cf56a2c2479df0882a4922fd203edb7517321831d95078c5f62910160405180910390a25050565b600080808060606128108683612855565b945061281d866004612855565b935061282a86602c61287d565b925061283786604c61287d565b865190925061284a908790609490612897565b905091939590929450565b600061287461286f848461286a6004826148bf565b612897565b6132f7565b90505b92915050565b6000612874612892848461286a6020826148bf565b61332d565b6060828210156128df5760405162461bcd60e51b8152602060048201526013602482015272496e76616c696420736c6963652072616e676560681b6044820152606401610885565b83518211156129305760405162461bcd60e51b815260206004820152601d60248201527f536c69636520656e6420657863656564732064617461206c656e6774680000006044820152606401610885565b600061293c848461485c565b90506000816001600160401b038111156129585761295861423d565b6040519080825280601f01601f191660200182016040528015612982576020820181803683370190505b50905060005b828110156129e7578661299b82886148bf565b815181106129ab576129ab61463b565b602001015160f81c60f81b8282815181106129c8576129c861463b565b60200101906001600160f81b031916908160001a905350600101612988565b5095945050505050565b6000612874612a06848461286a6020826148bf565b613364565b6000612a168261339b565b905063ffffffff8116600114612a655760405162461bcd60e51b8152602060048201526014602482015273496e76616c6964206d657373616765207479706560601b6044820152606401610885565b611c748484846133a8565b606c5460005b81811015610988576000606c8281548110612a9357612a9361463b565b60009182526020822001546040516370a0823160e01b81523060048201526001600160a01b03909116925082906370a0823190602401602060405180830381865afa158015612ae6573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612b0a91906148a6565b90508015612b7a57606b54604080516001600160a01b039283168152918416602083015281018290527ff6c07a063ed4e63808eb8da7112d46dbcd38de2b40a73dbcc9353c5a94c723539060600160405180910390a1606b54612b7a906001600160a01b038481169116836121c9565b5050600101612a76565b8061ffff166103e81480612b9d57508061ffff166107d0145b612bdd5760405162461bcd60e51b8152602060048201526011602482015270125b9d985b1a59081d1a1c995cda1bdb19607a1b6044820152606401610885565b6033805461ffff191661ffff83169081179091556040519081527fe8fbd074d54232549e55ac463e02d202c16b1023e5d6aae276b3d8391b8c14bc906020016124e3565b6001600160a01b038116612c775760405162461bcd60e51b815260206004820152601a60248201527f4e657720476f7665726e6f7220697320616464726573732830290000000000006044820152606401610885565b610dbe816134de565b61010380546001600160a01b0319166001600160a01b0383169081179091556040519081527f869e0abd13cc3a975de7b93be3df1cb2255c802b1cead85963cc79d99f131bee906020016124e3565b8251612ce290606c906020860190614123565b50815181518114612d2c5760405162461bcd60e51b8152602060048201526014602482015273496e76616c696420696e7075742061727261797360601b6044820152606401610885565b60005b8181101561216057612d73848281518110612d4c57612d4c61463b565b6020026020010151848381518110612d6657612d6661463b565b6020026020010151613545565b600101612d2f565b60008111612dcb5760405162461bcd60e51b815260206004820152601760248201527f4d75737420776974686472617720736f6d657468696e670000000000000000006044820152606401610885565b6001600160a01b0383163014612e175760405162461bcd60e51b8152602060048201526011602482015270125b9d985b1a59081c9958da5c1a595b9d607a1b6044820152606401610885565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316826001600160a01b031614612e685760405162461bcd60e51b81526004016108859061486f565b604051632d182be560e21b815260048101829052306024820181905260448201527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169063b460af94906064016020604051808303816000875af1925050508015612ef8575060408051601f3d908101601f19168201909252612ef5918101906148a6565b60015b612fe157612f04614a79565b806308c379a003612f7d5750612f18614a95565b80612f235750612f7f565b7f338db40d86fc8697e35f615235de184a040fdca34021e6bf367dabb0a408f83481604051602001612f559190614bc1565b60408051601f1981840301815290829052612f6f91614b50565b60405180910390a150505050565b505b3d808015612fa9576040519150601f19603f3d011682016040523d82523d6000602084013e612fae565b606091505b507f338db40d86fc8697e35f615235de184a040fdca34021e6bf367dabb0a408f83481604051602001612f559190614bfc565b50604080516001600160a01b037f000000000000000000000000000000000000000000000000000000000000000081168252602082018490528416917f2717ead6b9200dd235aad468c9809ea400fe33ac69b5bfaa6d3e90fc922b6398910160405180910390a2505050565b60405163095ea7b360e01b81526001600160a01b037f00000000000000000000000000000000000000000000000000000000000000008116600483015260001960248301527f0000000000000000000000000000000000000000000000000000000000000000169063095ea7b3906044016020604051808303816000875af11580156130dd573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610dbe9190614824565b603380546bffffffffffffffffffffffff16600160601b6001600160a01b038416908102919091179091556040519081527f4721129e0e676ed6a92909bb24e853ccdd63ad72280cc2e974e38e480e0e6e54906020016124e3565b61316583613101565b61316e82612b84565b613177816124ee565b5050600080525060346020527f2dc2afdad33a5feea586a9545052327b65d28efb10d11fa69e77da986a1031cd805460ff19166001179055565b6000613206826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b03166136aa9092919063ffffffff16565b80519091501561222c57808060200190518101906132249190614824565b61222c5760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b6064820152608401610885565b600061328e8261339b565b905060001963ffffffff821601156109885760011963ffffffff8216016132b857610988826136b9565b60405162461bcd60e51b8152602060048201526014602482015273556e6b6e6f776e206d657373616765207479706560601b6044820152606401610885565b6000815160041461331a5760405162461bcd60e51b815260040161088590614c5d565b60e061332583614c8a565b901c92915050565b600081516020146133505760405162461bcd60e51b815260040161088590614c5d565b818060200190518101906128779190614cb1565b600081516020146133875760405162461bcd60e51b815260040161088590614c5d565b8180602001905181019061287791906148a6565b6000612877826004612855565b60006133b38261390b565b5090506133bf81613944565b6040516370a0823160e01b81523060048201526000907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906370a0823190602401602060405180830381865afa158015613426573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061344a91906148a6565b9050620f42408110613480576134807f000000000000000000000000000000000000000000000000000000000000000082612589565b6033546000906134cb90600160201b90046001600160401b03166134c37f00000000000000000000000000000000000000000000000000000000000000006114bf565b60014261232a565b90506134d681612395565b505050505050565b806001600160a01b03166134fe600080516020614e2e8339815191525490565b6001600160a01b03167fc7c0c772add429241571afb3805861fb3cfa2af374534088b76cdb4325a87e9a60405160405180910390a3600080516020614e2e83398151915255565b6001600160a01b0382811660009081526067602052604090205416156135a25760405162461bcd60e51b81526020600482015260126024820152711c151bdad95b88185b1c9958591e481cd95d60721b6044820152606401610885565b6001600160a01b038216158015906135c257506001600160a01b03811615155b6136025760405162461bcd60e51b8152602060048201526011602482015270496e76616c69642061646472657373657360781b6044820152606401610885565b6001600160a01b03828116600081815260676020908152604080832080549587166001600160a01b031996871681179091556068805460018101825594527fa2153420d844928b4421650203c77babc8b33d7f2e7b450e2966db0c2209775390930180549095168417909455925190815290917fef6485b84315f9b1483beffa32aae9a0596890395e3d7521f1c5fbb51790e765910160405180910390a26109888282613ad7565b606061169d8484600085613adf565b6000806136c583613c07565b915091506136d282613944565b6040516370a0823160e01b81523060048201526000907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906370a0823190602401602060405180830381865afa158015613739573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061375d91906148a6565b90508181101561382157613796307f0000000000000000000000000000000000000000000000000000000000000000611b50848661485c565b6040516370a0823160e01b81523060048201527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906370a0823190602401602060405180830381865afa1580156137fa573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061381e91906148a6565b90505b600061384c7f00000000000000000000000000000000000000000000000000000000000000006114bf565b9050620f424083101580156138615750828210155b1561389c5760335460009061388a90600160201b90046001600160401b03166134c3868561485c565b90506138968482613c15565b50612160565b6033546000906138bf90600160201b90046001600160401b03168360014261232a565b90506138ca81612395565b60408051858152602081018590527fff463b52b1f42eb360c2a871ee18d12dbc93807adffafa5b8fc6dea0051857a9910160405180910390a1505050505050565b600080613919836001613e94565b60008061392585613f4d565b8060200190518101906139389190614cce565b90969095509350505050565b6033546001600160401b03600160201b909104811690821681111561399b5760405162461bcd60e51b815260206004820152600d60248201526c4e6f6e636520746f6f206c6f7760981b6044820152606401610885565b6001600160401b03821660009081526034602052604090205460ff1615613a045760405162461bcd60e51b815260206004820152601760248201527f4e6f6e636520616c72656164792070726f6365737365640000000000000000006044820152606401610885565b6001600160401b038216600081815260346020908152604091829020805460ff1916600117905590519182527f8aa30b3e46076ef0187550969c01982aeb9af5db2eada56ab253a53e4c9946e9910160405180910390a1806001600160401b0316826001600160401b03161461098857603380546bffffffffffffffff000000001916600160201b6001600160401b038516908102919091179091556040519081527fc328b88555c22b57c1f2678e88ad3d9982d056ca6b05be82546e7b6bc6fda2db9060200160405180910390a15050565b61098861304d565b606082471015613b405760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f6044820152651c8818d85b1b60d21b6064820152608401610885565b843b613b8e5760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610885565b600080866001600160a01b03168587604051613baa9190614cfc565b60006040518083038185875af1925050503d8060008114613be7576040519150601f19603f3d011682016040523d82523d6000602084013e613bec565b606091505b5091509150613bfc828286613f66565b979650505050505050565b600080613919836002613e94565b6509184e72a000821115613c635760405162461bcd60e51b81526020600482015260156024820152740a8ded6cadc40c2dadeeadce840e8dede40d0d2ced605b1b6044820152606401610885565b613cb76001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000167f000000000000000000000000000000000000000000000000000000000000000084613f9f565b60335460009062010000900461ffff16613cd2576000613cf7565b60335461271090613ced9062010000900461ffff1685614d18565b613cf79190614d2f565b60335460405163779b432d60e01b81529192506001600160a01b037f000000000000000000000000000000000000000000000000000000000000000081169263779b432d92613dc09288927f0000000000000000000000000000000000000000000000000000000000000000927f0000000000000000000000000000000000000000000000000000000000000000909216917f00000000000000000000000000000000000000000000000000000000000000009183918a9161ffff909116908c90600401614d51565b600060405180830381600087803b158015613dda57600080fd5b505af1158015613dee573d6000803e3d6000fd5b50506033546040517f149007d4a77eb98d041de59e6ca469bd27b351b74f16c4d0648978322a1b8a77935061272392507f0000000000000000000000000000000000000000000000000000000000000000917f0000000000000000000000000000000000000000000000000000000000000000917f0000000000000000000000000000000000000000000000000000000000000000918991889161ffff16908a90614db2565b6103f2613ea0836140b4565b63ffffffff1614613ef35760405162461bcd60e51b815260206004820152601e60248201527f496e76616c6964204f726967696e204d6573736167652056657273696f6e00006044820152606401610885565b8063ffffffff16613f038361339b565b63ffffffff16146109885760405162461bcd60e51b8152602060048201526014602482015273496e76616c6964204d657373616765207479706560601b6044820152606401610885565b606061287760088351846128979092919063ffffffff16565b60608315613f75575081612323565b825115613f855782518084602001fd5b8160405162461bcd60e51b81526004016108859190614b50565b8015806140195750604051636eb1769f60e11b81523060048201526001600160a01b03838116602483015284169063dd62ed3e90604401602060405180830381865afa158015613ff3573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061401791906148a6565b155b6140845760405162461bcd60e51b815260206004820152603660248201527f5361666545524332303a20617070726f76652066726f6d206e6f6e2d7a65726f60448201527520746f206e6f6e2d7a65726f20616c6c6f77616e636560501b6064820152608401610885565b6040516001600160a01b03831660248201526044810182905261222c90849063095ea7b360e01b906064016121f5565b60006128778282612855565b828054828255906000526020600020908101928215614113579160200282015b828111156141135781546001600160a01b0319166001600160a01b038435161782556020909201916001909101906140e0565b5061411f929150614178565b5090565b828054828255906000526020600020908101928215614113579160200282015b8281111561411357825182546001600160a01b0319166001600160a01b03909116178255602090920191600190910190614143565b5b8082111561411f5760008155600101614179565b6001600160a01b0381168114610dbe57600080fd5b600080604083850312156141b557600080fd5b82356141c08161418d565b915060208301356141d08161418d565b809150509250929050565b6000602082840312156141ed57600080fd5b81356123238161418d565b6000806040838503121561420b57600080fd5b82356142168161418d565b946020939093013593505050565b803563ffffffff8116811461423857600080fd5b919050565b634e487b7160e01b600052604160045260246000fd5b601f8201601f191681016001600160401b03811182821017156142785761427861423d565b6040525050565b600082601f83011261429057600080fd5b81356001600160401b038111156142a9576142a961423d565b6040516142c0601f8301601f191660200182614253565b8181528460208386010111156142d557600080fd5b816020850160208301376000918101602001919091529392505050565b6000806000806080858703121561430857600080fd5b61431185614224565b93506020850135925061432660408601614224565b915060608501356001600160401b0381111561434157600080fd5b61434d8782880161427f565b91505092959194509250565b60008083601f84011261436b57600080fd5b5081356001600160401b0381111561438257600080fd5b6020830191508360208260051b850101111561439d57600080fd5b9250929050565b600080600080606085870312156143ba57600080fd5b84356143c58161418d565b93506020850135925060408501356001600160401b038111156143e757600080fd5b6143f387828801614359565b95989497509550505050565b6001600160401b0381168114610dbe57600080fd5b60006020828403121561442657600080fd5b8135612323816143ff565b803561ffff8116811461423857600080fd5b60006020828403121561445557600080fd5b61287482614431565b6000806040838503121561447157600080fd5b82356001600160401b0381111561448757600080fd5b6144938582860161427f565b92505060208301356001600160401b038111156144af57600080fd5b6144bb8582860161427f565b9150509250929050565b6000602082840312156144d757600080fd5b5035919050565b600080602083850312156144f157600080fd5b82356001600160401b0381111561450757600080fd5b61393885828601614359565b60008060006060848603121561452857600080fd5b83356145338161418d565b925060208401356145438161418d565b929592945050506040919091013590565b6000806000806080858703121561456a57600080fd5b84356145758161418d565b935060208501356145858161418d565b925061459360408601614431565b91506145a160608601614431565b905092959194509250565b600081518084526020840193506020830160005b828110156145e75781516001600160a01b03168652602095860195909101906001016145c0565b5093949350505050565b60208152600061287460208301846145ac565b6020808252601a908201527f43616c6c6572206973206e6f742074686520476f7665726e6f72000000000000604082015260600190565b634e487b7160e01b600052603260045260246000fd5b60808152600061466460808301876145ac565b828103602084015261467681876145ac565b83810360408501528551808252602080880193509091019060005b818110156146af578351835260209384019390920191600101614691565b50508381036060850152845180825260208083019350600582901b8301810190870160005b8381101561472f57848303601f190186528151805180855260209182019185019060005b818110156147165783518352602093840193909201916001016146f8565b50506020978801979094509290920191506001016146d4565b50909a9950505050505050505050565b60208082526028908201527f43616c6c6572206973206e6f74207468652053747261746567697374206f722060408201526723b7bb32b93737b960c11b606082015260800190565b6020808252600e908201526d1499595b9d1c985b9d0818d85b1b60921b604082015260600190565b60005b838110156147ca5781810151838201526020016147b2565b50506000910152565b600081518084526147eb8160208601602086016147af565b601f01601f19169290920160200192915050565b60408152600061481260408301856147d3565b8281036020840152610a6981856147d3565b60006020828403121561483657600080fd5b8151801515811461232357600080fd5b634e487b7160e01b600052601160045260246000fd5b8181038181111561287757612877614846565b60208082526018908201527f556e657870656374656420617373657420616464726573730000000000000000604082015260600190565b6000602082840312156148b857600080fd5b5051919050565b8082018082111561287757612877614846565b6020808252602e908201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160408201526d191e481a5b9a5d1a585b1a5e995960921b606082015260800190565b6040808252845490820181905260008581526020812090916060840190835b818110156149665783546001600160a01b031683526001938401936020909301920161493f565b50508381036020808601919091528582520190508460005b858110156149af5781356149918161418d565b6001600160a01b03168352602092830192919091019060010161497e565b50909695505050505050565b63ffffffff60e01b8460e01b16815263ffffffff60e01b8360e01b166004820152600082516149f18160088501602087016147af565b91909101600801949350505050565b63ffffffff8616815284602082015283604082015263ffffffff8316606082015260a060808201526000613bfc60a08301846147d3565b63ffffffff85811682526001600160a01b038516602083015283166040820152608060608201819052600090614a6f908301846147d3565b9695505050505050565b600060033d1115614a925760046000803e5060005160e01c5b90565b600060443d1015614aa35790565b6040513d600319016004823e80513d60248201116001600160401b0382111715614acc57505090565b80820180516001600160401b03811115614ae7575050505090565b3d8401600319018282016020011115614b01575050505090565b614b1060208285010185614253565b509392505050565b6f02232b837b9b4ba103330b4b632b21d160851b815260008251614b438160108501602087016147af565b9190910160100192915050565b60208152600061287460208301846147d3565b7f4465706f736974206661696c65643a206c6f772d6c6576656c2063616c6c206681526f030b4b632b2103bb4ba34103230ba30960851b602082015260008251614bb48160308501602087016147af565b9190910160300192915050565b7202bb4ba34323930bbb0b6103330b4b632b21d1606d1b815260008251614bef8160138501602087016147af565b9190910160130192915050565b7f5769746864726177616c206661696c65643a206c6f772d6c6576656c2063616c815272036103330b4b632b2103bb4ba34103230ba309606d1b602082015260008251614c508160338501602087016147af565b9190910160330192915050565b602080825260139082015272092dcecc2d8d2c840c8c2e8c240d8cadccee8d606b1b604082015260600190565b80516020808301519190811015614cab576000198160200360031b1b821691505b50919050565b600060208284031215614cc357600080fd5b81516123238161418d565b60008060408385031215614ce157600080fd5b8251614cec816143ff565b6020939093015192949293505050565b60008251614d0e8184602087016147af565b9190910192915050565b808202811582820484141761287757612877614846565b600082614d4c57634e487b7160e01b600052601260045260246000fd5b500490565b88815263ffffffff8816602082015286604082015260018060a01b03861660608201528460808201528360a082015263ffffffff831660c082015261010060e08201526000614da46101008301846147d3565b9a9950505050505050505050565b63ffffffff88811682526001600160a01b038881166020840152871660408301526060820186905260808201859052831660a082015260e060c08201819052600090614e00908301846147d3565b999850505050505050505056fe53bf423e48ed90e97d02ab0ebab13b2a235a6bfbe9c321847d5c175333ac45357bea13895fa79d2831e0a9e28edede30099005a50d652d8957cf8a607ee6ca4aa2646970667358221220e2f059eccac446855423527343f655a81f4122f7e91825b0c4623d4ffd9afd4d64736f6c634300081c0033", + "deployedBytecode": "0x608060405234801561001057600080fd5b506004361061030c5760003560e01c80636c9fa59e1161019d578063ad1728cb116100e9578063d9caed12116100a2578063df4426781161007c578063df442678146107f4578063f4537f7814610807578063f6ca71b014610822578063fc1b31131461083757600080fd5b8063d9caed12146107b2578063dbe55e56146107c5578063de5f6268146107ec57600080fd5b8063ad1728cb14610737578063b3ab15fb1461073f578063c2e1e3f414610752578063c7af335214610765578063d38bfff41461076d578063d7dd614b1461078057600080fd5b8063853828b6116101565780639136616a116101305780639136616a146106af57806396d538bb146106bd5780639748cf7c146106d0578063aa388af6146106f757600080fd5b8063853828b6146106445780638949f6f31461064c5780638c73eb041461068857600080fd5b80636c9fa59e146105b5578063773540b3146105dc5780637b2d9b2c146105ef5780637c92f219146106025780637ddfce2d146106155780638129fc1c1461063c57600080fd5b806342c8c11c1161025c578063570d8e1d116102155780635d36b190116101ef5780635d36b190146105605780635f515226146105685780636369e49f1461057b57806367c7066c146105a257600080fd5b8063570d8e1d146105315780635a063f63146105455780635a2364171461054d57600080fd5b806342c8c11c146104a3578063430bf08a146104b657806347e7ef24146104dd5780635324df32146104f0578063564a515814610504578063570ca7351461051757600080fd5b806311eac855116102c9578063237839f0116102a3578063237839f01461044457806323c9ab3d146104575780633335ad7f146104835780633c3dbbc71461048b57600080fd5b806311eac855146103d157806316a0f250146103f85780631801c4081461041957600080fd5b80630c340a24146103115780630ed57b3a146103365780630fc3b4c41461034b5780631072cbea146103745780631083f7611461038757806311cffb67146103ae575b600080fd5b610319610844565b6040516001600160a01b0390911681526020015b60405180910390f35b6103496103443660046141a2565b610861565b005b6103196103593660046141db565b6067602052600090815260409020546001600160a01b031681565b6103496103823660046141f8565b6108cd565b6103197f000000000000000000000000000000000000000000000000000000000000000081565b6103c16103bc3660046142f2565b61098c565b604051901515815260200161032d565b6103197f000000000000000000000000000000000000000000000000000000000000000081565b6033546104069061ffff1681565b60405161ffff909116815260200161032d565b603354600160201b90046001600160401b031660009081526034602052604090205460ff16156103c1565b6103496104523660046143a4565b610a72565b6103c1610465366004614414565b6001600160401b031660009081526034602052604090205460ff1690565b610349610c91565b610495620f424081565b60405190815260200161032d565b6103496104b1366004614443565b610d91565b6103197f000000000000000000000000000000000000000000000000000000000000000081565b6103496104eb3660046141f8565b610dc1565b6033546104069062010000900461ffff1681565b61034961051236600461445e565b610e43565b60335461031990600160601b90046001600160a01b031681565b61010354610319906001600160a01b031681565b61034961134d565b61034961055b366004614443565b6113ec565b610349611419565b6104956105763660046141db565b6114bf565b6103197f000000000000000000000000000000000000000000000000000000000000000081565b606b54610319906001600160a01b031681565b6103197f000000000000000000000000000000000000000000000000000000000000000081565b6103496105ea3660046141db565b6116a5565b6103196105fd3660046144c5565b6116d2565b6103c16106103660046142f2565b6116fc565b6103197f000000000000000000000000000000000000000000000000000000000000000081565b610349611834565b6103496119c7565b6106737f000000000000000000000000000000000000000000000000000000000000000081565b60405163ffffffff909116815260200161032d565b6103197f000000000000000000000000000000000000000000000000000000000000000081565b6103496103443660046144c5565b6103496106cb3660046144de565b611b5d565b6103197f000000000000000000000000000000000000000000000000000000000000000081565b6103c16107053660046141db565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0390811691161490565b610349611c7a565b61034961074d3660046141db565b611ca6565b6103496107603660046141db565b611cd3565b6103c1611d60565b61034961077b3660046141db565b611d91565b60335461079a90600160201b90046001600160401b031681565b6040516001600160401b03909116815260200161032d565b6103496107c0366004614513565b611e35565b6103197f000000000000000000000000000000000000000000000000000000000000000081565b610349611eb9565b610349610802366004614554565b611fbb565b610319733ef3d8ba38ebe18db133cec108f4d14ce00dd9ae81565b61082a612167565b60405161032d91906145f1565b6104956509184e72a00081565b600061085c600080516020614e2e8339815191525490565b905090565b610869611d60565b61088e5760405162461bcd60e51b815260040161088590614604565b60405180910390fd5b60405162461bcd60e51b81526020600482015260146024820152733ab739bab83837b93a32b210333ab731ba34b7b760611b6044820152606401610885565b6108d5611d60565b6108f15760405162461bcd60e51b815260040161088590614604565b6001600160a01b037f000000000000000000000000000000000000000000000000000000000000000081169083160361096c5760405162461bcd60e51b815260206004820152601f60248201527f43616e6e6f74207472616e7366657220737570706f72746564206173736574006044820152606401610885565b610988610977610844565b6001600160a01b03841690836121c9565b5050565b6000336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614610a065760405162461bcd60e51b815260206004820152601e60248201527f43616c6c6572206973206e6f742043435450207472616e736d697474657200006044820152606401610885565b6107d08363ffffffff161015610a5e5760405162461bcd60e51b815260206004820152601a60248201527f46696e616c697479207468726573686f6c6420746f6f206c6f770000000000006044820152606401610885565b610a69858584612231565b95945050505050565b604080516001808252818301909252600091602080830190803683370190505090503081600081518110610aa857610aa861463b565b6001600160a01b0392909216602092830291909101909101526040805160018082528183019092526000918160200160208202803683370190505090508581600081518110610af957610af961463b565b6001600160a01b0392909216602092830291909101909101526040805160018082528183019092526000918160200160208202803683370190505090508581600081518110610b4a57610b4a61463b565b6020908102919091010152604080516001808252818301909252600091816020015b6060815260200190600190039081610b6c5790505090508585808060200260200160405190810160405280939291908181526020018383602002808284376000920182905250855186945090925015159050610bca57610bca61463b565b60209081029190910101526040516301c7ba5760e61b8152733ef3d8ba38ebe18db133cec108f4d14ce00dd9ae906371ee95c090610c12908790879087908790600401614651565b600060405180830381600087803b158015610c2c57600080fd5b505af1158015610c40573d6000803e3d6000fd5b50505050876001600160a01b03167f2d5429efdeca7741a8cd94067b18d988bc4e5f1d5b8272c37b7bfc31e9bfa32c88604051610c7f91815260200190565b60405180910390a25050505050505050565b603354600160601b90046001600160a01b0316331480610cbc5750610103546001600160a01b031633145b80610cca5750610cca611d60565b610d355760405162461bcd60e51b815260206004820152603660248201527f43616c6c6572206973206e6f7420746865204f70657261746f722c20537472616044820152753a32b3b4b9ba1037b9103a34329023b7bb32b93737b960511b6064820152608401610885565b6000610d607f00000000000000000000000000000000000000000000000000000000000000006114bf565b90506000610d86603360049054906101000a90046001600160401b03168360004261232a565b905061098881612395565b610d99611d60565b610db55760405162461bcd60e51b815260040161088590614604565b610dbe816124ee565b50565b610103546001600160a01b0316331480610dde5750610dde611d60565b610dfa5760405162461bcd60e51b81526004016108859061473f565b600080516020614e0e83398151915280546001198101610e2c5760405162461bcd60e51b815260040161088590614787565b60028255610e3a8484612589565b50600190555050565b603354600160601b90046001600160a01b03163314610ea45760405162461bcd60e51b815260206004820152601a60248201527f43616c6c6572206973206e6f7420746865204f70657261746f720000000000006044820152606401610885565b6000806000806000610eb5876127ff565b94509450945094509450600163ffffffff168563ffffffff1614610f1b5760405162461bcd60e51b815260206004820152601c60248201527f496e76616c69642043435450206d6573736167652076657273696f6e000000006044820152606401610885565b7f000000000000000000000000000000000000000000000000000000000000000063ffffffff168463ffffffff1614610f8e5760405162461bcd60e51b81526020600482015260156024820152742ab735b737bbb71029b7bab931b2902237b6b0b4b760591b6044820152606401610885565b610f99816000612855565b945060007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316846001600160a01b031614905080156110da578563ffffffff166001148015610ff25750815160e411155b6110355760405162461bcd60e51b8152602060048201526014602482015273496e76616c6964206275726e206d65737361676560601b6044820152606401610885565b600061104283600461287d565b90507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316816001600160a01b0316146110ba5760405162461bcd60e51b815260206004820152601260248201527124b73b30b634b210313ab937103a37b5b2b760711b6044820152606401610885565b6110c583606461287d565b94506110d283602461287d565b935050611131565b63ffffffff86166103f2146111315760405162461bcd60e51b815260206004820152601b60248201527f556e737570706f72746564206d6573736167652076657273696f6e00000000006044820152606401610885565b306001600160a01b038416146111895760405162461bcd60e51b815260206004820152601c60248201527f556e657870656374656420726563697069656e742061646472657373000000006044820152606401610885565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316846001600160a01b0316146112155760405162461bcd60e51b815260206004820152602260248201527f496e636f72726563742073656e6465722f726563697069656e74206164647265604482015261737360f01b6064820152608401610885565b604051630afd9fa560e31b81526000906001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016906357ecfd2890611266908c908c906004016147ff565b6020604051808303816000875af1158015611285573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906112a99190614824565b9050806112f15760405162461bcd60e51b8152602060048201526016602482015275149958d95a5d99481b595cdcd859d94819985a5b195960521b6044820152606401610885565b811561134257825160009061130a90859060e490612897565b905060006113198560446129f1565b905060006113288660a46129f1565b905061133e611337828461485c565b8285612a0b565b5050505b505050505050505050565b606b546001600160a01b031633146113a75760405162461bcd60e51b815260206004820152601b60248201527f43616c6c6572206973206e6f74207468652048617276657374657200000000006044820152606401610885565b600080516020614e0e833981519152805460011981016113d95760405162461bcd60e51b815260040161088590614787565b600282556113e5612a70565b5060019055565b6113f4611d60565b6114105760405162461bcd60e51b815260040161088590614604565b610dbe81612b84565b7f44c4d30b2eaad5130ad70c3ba6972730566f3e6359ab83e800d905c61b1c51db546001600160a01b0316336001600160a01b0316146114b45760405162461bcd60e51b815260206004820152603060248201527f4f6e6c79207468652070656e64696e6720476f7665726e6f722063616e20636f60448201526f6d706c6574652074686520636c61696d60801b6064820152608401610885565b6114bd33612c21565b565b60007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316826001600160a01b0316146115125760405162461bcd60e51b81526004016108859061486f565b6040516370a0823160e01b81523060048201526000907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906370a0823190602401602060405180830381865afa158015611579573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061159d91906148a6565b6040516370a0823160e01b81523060048201529091507f00000000000000000000000000000000000000000000000000000000000000009082906001600160a01b03831690634cdad5069082906370a0823190602401602060405180830381865afa158015611610573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061163491906148a6565b6040518263ffffffff1660e01b815260040161165291815260200190565b602060405180830381865afa15801561166f573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061169391906148a6565b61169d91906148bf565b949350505050565b6116ad611d60565b6116c95760405162461bcd60e51b815260040161088590614604565b610dbe81612c80565b606c81815481106116e257600080fd5b6000918252602090912001546001600160a01b0316905081565b6000336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146117765760405162461bcd60e51b815260206004820152601e60248201527f43616c6c6572206973206e6f742043435450207472616e736d697474657200006044820152606401610885565b60335461ffff166103e8146117dc5760405162461bcd60e51b815260206004820152602660248201527f556e66696e616c697a6564206d6573736167657320617265206e6f74207375706044820152651c1bdc9d195960d21b6064820152608401610885565b6103e88363ffffffff161015610a5e5760405162461bcd60e51b815260206004820152601a60248201527f46696e616c697479207468726573686f6c6420746f6f206c6f770000000000006044820152606401610885565b61183c611d60565b6118585760405162461bcd60e51b815260040161088590614604565b600054610100900460ff1680611871575060005460ff16155b61188d5760405162461bcd60e51b8152600401610885906148d2565b600054610100900460ff161580156118af576000805461ffff19166101011790555b6040805160008082526001602080840182815260608501865293949293928501908036833750506040805160018082528183019092529293506000929150602080830190803683370190505090507f0000000000000000000000000000000000000000000000000000000000000000826000815181106119315761193161463b565b60200260200101906001600160a01b031690816001600160a01b0316815250507f0000000000000000000000000000000000000000000000000000000000000000816000815181106119855761198561463b565b60200260200101906001600160a01b031690816001600160a01b0316815250506119b0838383612ccf565b5050508015610dbe576000805461ff001916905550565b610103546001600160a01b03163314806119e457506119e4611d60565b611a005760405162461bcd60e51b81526004016108859061473f565b600080516020614e0e83398151915280546001198101611a325760405162461bcd60e51b815260040161088590614787565b600282556040516370a0823160e01b815230600482018190527f000000000000000000000000000000000000000000000000000000000000000091611b5591907f0000000000000000000000000000000000000000000000000000000000000000906001600160a01b03851690634cdad5069082906370a0823190602401602060405180830381865afa158015611acd573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611af191906148a6565b6040518263ffffffff1660e01b8152600401611b0f91815260200190565b602060405180830381865afa158015611b2c573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611b5091906148a6565b612d7b565b505060019055565b611b65611d60565b611b815760405162461bcd60e51b815260040161088590614604565b8060005b81811015611c2b576000848483818110611ba157611ba161463b565b9050602002016020810190611bb691906141db565b6001600160a01b031603611c235760405162461bcd60e51b815260206004820152602e60248201527f43616e206e6f742073657420616e20656d70747920616464726573732061732060448201526d30903932bbb0b932103a37b5b2b760911b6064820152608401610885565b600101611b85565b507f04c0b9649497d316554306e53678d5f5f5dbc3a06f97dec13ff4cfe98b986bbc606c8484604051611c6093929190614920565b60405180910390a1611c74606c84846140c0565b50505050565b611c82611d60565b611c9e5760405162461bcd60e51b815260040161088590614604565b6114bd61304d565b611cae611d60565b611cca5760405162461bcd60e51b815260040161088590614604565b610dbe81613101565b611cdb611d60565b611cf75760405162461bcd60e51b815260040161088590614604565b606b54604080516001600160a01b03928316815291831660208301527fe48386b84419f4d36e0f96c10cc3510b6fb1a33795620c5098b22472bbe90796910160405180910390a1606b80546001600160a01b0319166001600160a01b0392909216919091179055565b6000611d78600080516020614e2e8339815191525490565b6001600160a01b0316336001600160a01b031614905090565b611d99611d60565b611db55760405162461bcd60e51b815260040161088590614604565b611ddd817f44c4d30b2eaad5130ad70c3ba6972730566f3e6359ab83e800d905c61b1c51db55565b806001600160a01b0316611dfd600080516020614e2e8339815191525490565b6001600160a01b03167fa39cc5eb22d0f34d8beaefee8a3f17cc229c1a1d1ef87a5ad47313487b1c4f0d60405160405180910390a350565b610103546001600160a01b0316331480611e525750611e52611d60565b611e6e5760405162461bcd60e51b81526004016108859061473f565b600080516020614e0e83398151915280546001198101611ea05760405162461bcd60e51b815260040161088590614787565b60028255611eaf858585612d7b565b5060019055505050565b610103546001600160a01b0316331480611ed65750611ed6611d60565b611ef25760405162461bcd60e51b81526004016108859061473f565b600080516020614e0e83398151915280546001198101611f245760405162461bcd60e51b815260040161088590614787565b600282556040516370a0823160e01b81523060048201526113e5907f0000000000000000000000000000000000000000000000000000000000000000906001600160a01b038216906370a0823190602401602060405180830381865afa158015611f92573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611fb691906148a6565b612589565b611fc3611d60565b611fdf5760405162461bcd60e51b815260040161088590614604565b600054610100900460ff1680611ff8575060005460ff16155b6120145760405162461bcd60e51b8152600401610885906148d2565b600054610100900460ff16158015612036576000805461ffff19166101011790555b61204184848461315c565b61204a85612c80565b6040805160008082526001602080840182815260608501865293949293928501908036833750506040805160018082528183019092529293506000929150602080830190803683370190505090507f0000000000000000000000000000000000000000000000000000000000000000826000815181106120cc576120cc61463b565b60200260200101906001600160a01b031690816001600160a01b0316815250507f0000000000000000000000000000000000000000000000000000000000000000816000815181106121205761212061463b565b60200260200101906001600160a01b031690816001600160a01b03168152505061214b838383612ccf565b5050508015612160576000805461ff00191690555b5050505050565b6060606c8054806020026020016040519081016040528092919081815260200182805480156121bf57602002820191906000526020600020905b81546001600160a01b031681526001909101906020018083116121a1575b5050505050905090565b6040516001600160a01b03831660248201526044810182905261222c90849063a9059cbb60e01b906064015b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b0319909316929092179091526131b1565b505050565b60007f000000000000000000000000000000000000000000000000000000000000000063ffffffff168463ffffffff16146122a65760405162461bcd60e51b81526020600482015260156024820152742ab735b737bbb71029b7bab931b2902237b6b0b4b760591b6044820152606401610885565b827f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03908116908216146123145760405162461bcd60e51b815260206004820152600e60248201526d2ab735b737bbb71029b2b73232b960911b6044820152606401610885565b61231d83613283565b60019150505b9392505050565b604080516001600160401b038616602082015280820185905283151560608083019190915260808083018590528351808403909101815260a083019093529161237c916103f29160039160c0016149bb565b6040516020818303038152906040529050949350505050565b6033546040516314b157ab60e01b81526001600160a01b037f00000000000000000000000000000000000000000000000000000000000000008116926314b157ab92612433927f0000000000000000000000000000000000000000000000000000000000000000927f000000000000000000000000000000000000000000000000000000000000000090911691829161ffff16908890600401614a00565b600060405180830381600087803b15801561244d57600080fd5b505af1158015612461573d6000803e3d6000fd5b50506033546040517f90d92e80437532a834bc16642d235a7c34efc4d5690f3f6e69794c6d8c5c729093506124e392507f0000000000000000000000000000000000000000000000000000000000000000917f00000000000000000000000000000000000000000000000000000000000000009161ffff909116908690614a37565b60405180910390a150565b610bb88161ffff16111561253b5760405162461bcd60e51b815260206004820152601460248201527308ccaca40e0e4cadad2eada40e8dede40d0d2ced60631b6044820152606401610885565b6033805463ffff000019166201000061ffff8416908102919091179091556040519081527f938c72cb9ba014c7d4d26f156021299f8926dee2aab1421b32a2408a8261cf60906020016124e3565b600081116125d25760405162461bcd60e51b81526020600482015260166024820152754d757374206465706f73697420736f6d657468696e6760501b6044820152606401610885565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316826001600160a01b0316146126235760405162461bcd60e51b81526004016108859061486f565b604051636e553f6560e01b8152600481018290523060248201527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031690636e553f65906044016020604051808303816000875af19250505080156126ac575060408051601f3d908101601f191682019092526126a9918101906148a6565b60015b612794576126b8614a79565b806308c379a00361273057506126cc614a95565b806126d75750612732565b7fcce3c000dc0fa4d3156e53ca2c0771b6dfa039640d4885f2df488a7f732540d4816040516020016127099190614b18565b60408051601f198184030181529082905261272391614b50565b60405180910390a1505050565b505b3d80801561275c576040519150601f19603f3d011682016040523d82523d6000602084013e612761565b606091505b507fcce3c000dc0fa4d3156e53ca2c0771b6dfa039640d4885f2df488a7f732540d4816040516020016127099190614b63565b50604080516001600160a01b037f000000000000000000000000000000000000000000000000000000000000000081168252602082018490528416917f5548c837ab068cf56a2c2479df0882a4922fd203edb7517321831d95078c5f62910160405180910390a25050565b600080808060606128108683612855565b945061281d866004612855565b935061282a86602c61287d565b925061283786604c61287d565b865190925061284a908790609490612897565b905091939590929450565b600061287461286f848461286a6004826148bf565b612897565b6132f7565b90505b92915050565b6000612874612892848461286a6020826148bf565b61332d565b6060828210156128df5760405162461bcd60e51b8152602060048201526013602482015272496e76616c696420736c6963652072616e676560681b6044820152606401610885565b83518211156129305760405162461bcd60e51b815260206004820152601d60248201527f536c69636520656e6420657863656564732064617461206c656e6774680000006044820152606401610885565b600061293c848461485c565b90506000816001600160401b038111156129585761295861423d565b6040519080825280601f01601f191660200182016040528015612982576020820181803683370190505b50905060005b828110156129e7578661299b82886148bf565b815181106129ab576129ab61463b565b602001015160f81c60f81b8282815181106129c8576129c861463b565b60200101906001600160f81b031916908160001a905350600101612988565b5095945050505050565b6000612874612a06848461286a6020826148bf565b613364565b6000612a168261339b565b905063ffffffff8116600114612a655760405162461bcd60e51b8152602060048201526014602482015273496e76616c6964206d657373616765207479706560601b6044820152606401610885565b611c748484846133a8565b606c5460005b81811015610988576000606c8281548110612a9357612a9361463b565b60009182526020822001546040516370a0823160e01b81523060048201526001600160a01b03909116925082906370a0823190602401602060405180830381865afa158015612ae6573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612b0a91906148a6565b90508015612b7a57606b54604080516001600160a01b039283168152918416602083015281018290527ff6c07a063ed4e63808eb8da7112d46dbcd38de2b40a73dbcc9353c5a94c723539060600160405180910390a1606b54612b7a906001600160a01b038481169116836121c9565b5050600101612a76565b8061ffff166103e81480612b9d57508061ffff166107d0145b612bdd5760405162461bcd60e51b8152602060048201526011602482015270125b9d985b1a59081d1a1c995cda1bdb19607a1b6044820152606401610885565b6033805461ffff191661ffff83169081179091556040519081527fe8fbd074d54232549e55ac463e02d202c16b1023e5d6aae276b3d8391b8c14bc906020016124e3565b6001600160a01b038116612c775760405162461bcd60e51b815260206004820152601a60248201527f4e657720476f7665726e6f7220697320616464726573732830290000000000006044820152606401610885565b610dbe816134de565b61010380546001600160a01b0319166001600160a01b0383169081179091556040519081527f869e0abd13cc3a975de7b93be3df1cb2255c802b1cead85963cc79d99f131bee906020016124e3565b8251612ce290606c906020860190614123565b50815181518114612d2c5760405162461bcd60e51b8152602060048201526014602482015273496e76616c696420696e7075742061727261797360601b6044820152606401610885565b60005b8181101561216057612d73848281518110612d4c57612d4c61463b565b6020026020010151848381518110612d6657612d6661463b565b6020026020010151613545565b600101612d2f565b60008111612dcb5760405162461bcd60e51b815260206004820152601760248201527f4d75737420776974686472617720736f6d657468696e670000000000000000006044820152606401610885565b6001600160a01b0383163014612e175760405162461bcd60e51b8152602060048201526011602482015270125b9d985b1a59081c9958da5c1a595b9d607a1b6044820152606401610885565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316826001600160a01b031614612e685760405162461bcd60e51b81526004016108859061486f565b604051632d182be560e21b815260048101829052306024820181905260448201527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169063b460af94906064016020604051808303816000875af1925050508015612ef8575060408051601f3d908101601f19168201909252612ef5918101906148a6565b60015b612fe157612f04614a79565b806308c379a003612f7d5750612f18614a95565b80612f235750612f7f565b7f338db40d86fc8697e35f615235de184a040fdca34021e6bf367dabb0a408f83481604051602001612f559190614bc1565b60408051601f1981840301815290829052612f6f91614b50565b60405180910390a150505050565b505b3d808015612fa9576040519150601f19603f3d011682016040523d82523d6000602084013e612fae565b606091505b507f338db40d86fc8697e35f615235de184a040fdca34021e6bf367dabb0a408f83481604051602001612f559190614bfc565b50604080516001600160a01b037f000000000000000000000000000000000000000000000000000000000000000081168252602082018490528416917f2717ead6b9200dd235aad468c9809ea400fe33ac69b5bfaa6d3e90fc922b6398910160405180910390a2505050565b60405163095ea7b360e01b81526001600160a01b037f00000000000000000000000000000000000000000000000000000000000000008116600483015260001960248301527f0000000000000000000000000000000000000000000000000000000000000000169063095ea7b3906044016020604051808303816000875af11580156130dd573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610dbe9190614824565b603380546bffffffffffffffffffffffff16600160601b6001600160a01b038416908102919091179091556040519081527f4721129e0e676ed6a92909bb24e853ccdd63ad72280cc2e974e38e480e0e6e54906020016124e3565b61316583613101565b61316e82612b84565b613177816124ee565b5050600080525060346020527f2dc2afdad33a5feea586a9545052327b65d28efb10d11fa69e77da986a1031cd805460ff19166001179055565b6000613206826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b03166136aa9092919063ffffffff16565b80519091501561222c57808060200190518101906132249190614824565b61222c5760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b6064820152608401610885565b600061328e8261339b565b905060001963ffffffff821601156109885760011963ffffffff8216016132b857610988826136b9565b60405162461bcd60e51b8152602060048201526014602482015273556e6b6e6f776e206d657373616765207479706560601b6044820152606401610885565b6000815160041461331a5760405162461bcd60e51b815260040161088590614c5d565b60e061332583614c8a565b901c92915050565b600081516020146133505760405162461bcd60e51b815260040161088590614c5d565b818060200190518101906128779190614cb1565b600081516020146133875760405162461bcd60e51b815260040161088590614c5d565b8180602001905181019061287791906148a6565b6000612877826004612855565b60006133b38261390b565b5090506133bf81613944565b6040516370a0823160e01b81523060048201526000907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906370a0823190602401602060405180830381865afa158015613426573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061344a91906148a6565b9050620f42408110613480576134807f000000000000000000000000000000000000000000000000000000000000000082612589565b6033546000906134cb90600160201b90046001600160401b03166134c37f00000000000000000000000000000000000000000000000000000000000000006114bf565b60014261232a565b90506134d681612395565b505050505050565b806001600160a01b03166134fe600080516020614e2e8339815191525490565b6001600160a01b03167fc7c0c772add429241571afb3805861fb3cfa2af374534088b76cdb4325a87e9a60405160405180910390a3600080516020614e2e83398151915255565b6001600160a01b0382811660009081526067602052604090205416156135a25760405162461bcd60e51b81526020600482015260126024820152711c151bdad95b88185b1c9958591e481cd95d60721b6044820152606401610885565b6001600160a01b038216158015906135c257506001600160a01b03811615155b6136025760405162461bcd60e51b8152602060048201526011602482015270496e76616c69642061646472657373657360781b6044820152606401610885565b6001600160a01b03828116600081815260676020908152604080832080549587166001600160a01b031996871681179091556068805460018101825594527fa2153420d844928b4421650203c77babc8b33d7f2e7b450e2966db0c2209775390930180549095168417909455925190815290917fef6485b84315f9b1483beffa32aae9a0596890395e3d7521f1c5fbb51790e765910160405180910390a26109888282613ad7565b606061169d8484600085613adf565b6000806136c583613c07565b915091506136d282613944565b6040516370a0823160e01b81523060048201526000907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906370a0823190602401602060405180830381865afa158015613739573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061375d91906148a6565b90508181101561382157613796307f0000000000000000000000000000000000000000000000000000000000000000611b50848661485c565b6040516370a0823160e01b81523060048201527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906370a0823190602401602060405180830381865afa1580156137fa573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061381e91906148a6565b90505b600061384c7f00000000000000000000000000000000000000000000000000000000000000006114bf565b9050620f424083101580156138615750828210155b1561389c5760335460009061388a90600160201b90046001600160401b03166134c3868561485c565b90506138968482613c15565b50612160565b6033546000906138bf90600160201b90046001600160401b03168360014261232a565b90506138ca81612395565b60408051858152602081018590527fff463b52b1f42eb360c2a871ee18d12dbc93807adffafa5b8fc6dea0051857a9910160405180910390a1505050505050565b600080613919836001613e94565b60008061392585613f4d565b8060200190518101906139389190614cce565b90969095509350505050565b6033546001600160401b03600160201b909104811690821681111561399b5760405162461bcd60e51b815260206004820152600d60248201526c4e6f6e636520746f6f206c6f7760981b6044820152606401610885565b6001600160401b03821660009081526034602052604090205460ff1615613a045760405162461bcd60e51b815260206004820152601760248201527f4e6f6e636520616c72656164792070726f6365737365640000000000000000006044820152606401610885565b6001600160401b038216600081815260346020908152604091829020805460ff1916600117905590519182527f8aa30b3e46076ef0187550969c01982aeb9af5db2eada56ab253a53e4c9946e9910160405180910390a1806001600160401b0316826001600160401b03161461098857603380546bffffffffffffffff000000001916600160201b6001600160401b038516908102919091179091556040519081527fc328b88555c22b57c1f2678e88ad3d9982d056ca6b05be82546e7b6bc6fda2db9060200160405180910390a15050565b61098861304d565b606082471015613b405760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f6044820152651c8818d85b1b60d21b6064820152608401610885565b843b613b8e5760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610885565b600080866001600160a01b03168587604051613baa9190614cfc565b60006040518083038185875af1925050503d8060008114613be7576040519150601f19603f3d011682016040523d82523d6000602084013e613bec565b606091505b5091509150613bfc828286613f66565b979650505050505050565b600080613919836002613e94565b6509184e72a000821115613c635760405162461bcd60e51b81526020600482015260156024820152740a8ded6cadc40c2dadeeadce840e8dede40d0d2ced605b1b6044820152606401610885565b613cb76001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000167f000000000000000000000000000000000000000000000000000000000000000084613f9f565b60335460009062010000900461ffff16613cd2576000613cf7565b60335461271090613ced9062010000900461ffff1685614d18565b613cf79190614d2f565b60335460405163779b432d60e01b81529192506001600160a01b037f000000000000000000000000000000000000000000000000000000000000000081169263779b432d92613dc09288927f0000000000000000000000000000000000000000000000000000000000000000927f0000000000000000000000000000000000000000000000000000000000000000909216917f00000000000000000000000000000000000000000000000000000000000000009183918a9161ffff909116908c90600401614d51565b600060405180830381600087803b158015613dda57600080fd5b505af1158015613dee573d6000803e3d6000fd5b50506033546040517f149007d4a77eb98d041de59e6ca469bd27b351b74f16c4d0648978322a1b8a77935061272392507f0000000000000000000000000000000000000000000000000000000000000000917f0000000000000000000000000000000000000000000000000000000000000000917f0000000000000000000000000000000000000000000000000000000000000000918991889161ffff16908a90614db2565b6103f2613ea0836140b4565b63ffffffff1614613ef35760405162461bcd60e51b815260206004820152601e60248201527f496e76616c6964204f726967696e204d6573736167652056657273696f6e00006044820152606401610885565b8063ffffffff16613f038361339b565b63ffffffff16146109885760405162461bcd60e51b8152602060048201526014602482015273496e76616c6964204d657373616765207479706560601b6044820152606401610885565b606061287760088351846128979092919063ffffffff16565b60608315613f75575081612323565b825115613f855782518084602001fd5b8160405162461bcd60e51b81526004016108859190614b50565b8015806140195750604051636eb1769f60e11b81523060048201526001600160a01b03838116602483015284169063dd62ed3e90604401602060405180830381865afa158015613ff3573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061401791906148a6565b155b6140845760405162461bcd60e51b815260206004820152603660248201527f5361666545524332303a20617070726f76652066726f6d206e6f6e2d7a65726f60448201527520746f206e6f6e2d7a65726f20616c6c6f77616e636560501b6064820152608401610885565b6040516001600160a01b03831660248201526044810182905261222c90849063095ea7b360e01b906064016121f5565b60006128778282612855565b828054828255906000526020600020908101928215614113579160200282015b828111156141135781546001600160a01b0319166001600160a01b038435161782556020909201916001909101906140e0565b5061411f929150614178565b5090565b828054828255906000526020600020908101928215614113579160200282015b8281111561411357825182546001600160a01b0319166001600160a01b03909116178255602090920191600190910190614143565b5b8082111561411f5760008155600101614179565b6001600160a01b0381168114610dbe57600080fd5b600080604083850312156141b557600080fd5b82356141c08161418d565b915060208301356141d08161418d565b809150509250929050565b6000602082840312156141ed57600080fd5b81356123238161418d565b6000806040838503121561420b57600080fd5b82356142168161418d565b946020939093013593505050565b803563ffffffff8116811461423857600080fd5b919050565b634e487b7160e01b600052604160045260246000fd5b601f8201601f191681016001600160401b03811182821017156142785761427861423d565b6040525050565b600082601f83011261429057600080fd5b81356001600160401b038111156142a9576142a961423d565b6040516142c0601f8301601f191660200182614253565b8181528460208386010111156142d557600080fd5b816020850160208301376000918101602001919091529392505050565b6000806000806080858703121561430857600080fd5b61431185614224565b93506020850135925061432660408601614224565b915060608501356001600160401b0381111561434157600080fd5b61434d8782880161427f565b91505092959194509250565b60008083601f84011261436b57600080fd5b5081356001600160401b0381111561438257600080fd5b6020830191508360208260051b850101111561439d57600080fd5b9250929050565b600080600080606085870312156143ba57600080fd5b84356143c58161418d565b93506020850135925060408501356001600160401b038111156143e757600080fd5b6143f387828801614359565b95989497509550505050565b6001600160401b0381168114610dbe57600080fd5b60006020828403121561442657600080fd5b8135612323816143ff565b803561ffff8116811461423857600080fd5b60006020828403121561445557600080fd5b61287482614431565b6000806040838503121561447157600080fd5b82356001600160401b0381111561448757600080fd5b6144938582860161427f565b92505060208301356001600160401b038111156144af57600080fd5b6144bb8582860161427f565b9150509250929050565b6000602082840312156144d757600080fd5b5035919050565b600080602083850312156144f157600080fd5b82356001600160401b0381111561450757600080fd5b61393885828601614359565b60008060006060848603121561452857600080fd5b83356145338161418d565b925060208401356145438161418d565b929592945050506040919091013590565b6000806000806080858703121561456a57600080fd5b84356145758161418d565b935060208501356145858161418d565b925061459360408601614431565b91506145a160608601614431565b905092959194509250565b600081518084526020840193506020830160005b828110156145e75781516001600160a01b03168652602095860195909101906001016145c0565b5093949350505050565b60208152600061287460208301846145ac565b6020808252601a908201527f43616c6c6572206973206e6f742074686520476f7665726e6f72000000000000604082015260600190565b634e487b7160e01b600052603260045260246000fd5b60808152600061466460808301876145ac565b828103602084015261467681876145ac565b83810360408501528551808252602080880193509091019060005b818110156146af578351835260209384019390920191600101614691565b50508381036060850152845180825260208083019350600582901b8301810190870160005b8381101561472f57848303601f190186528151805180855260209182019185019060005b818110156147165783518352602093840193909201916001016146f8565b50506020978801979094509290920191506001016146d4565b50909a9950505050505050505050565b60208082526028908201527f43616c6c6572206973206e6f74207468652053747261746567697374206f722060408201526723b7bb32b93737b960c11b606082015260800190565b6020808252600e908201526d1499595b9d1c985b9d0818d85b1b60921b604082015260600190565b60005b838110156147ca5781810151838201526020016147b2565b50506000910152565b600081518084526147eb8160208601602086016147af565b601f01601f19169290920160200192915050565b60408152600061481260408301856147d3565b8281036020840152610a6981856147d3565b60006020828403121561483657600080fd5b8151801515811461232357600080fd5b634e487b7160e01b600052601160045260246000fd5b8181038181111561287757612877614846565b60208082526018908201527f556e657870656374656420617373657420616464726573730000000000000000604082015260600190565b6000602082840312156148b857600080fd5b5051919050565b8082018082111561287757612877614846565b6020808252602e908201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160408201526d191e481a5b9a5d1a585b1a5e995960921b606082015260800190565b6040808252845490820181905260008581526020812090916060840190835b818110156149665783546001600160a01b031683526001938401936020909301920161493f565b50508381036020808601919091528582520190508460005b858110156149af5781356149918161418d565b6001600160a01b03168352602092830192919091019060010161497e565b50909695505050505050565b63ffffffff60e01b8460e01b16815263ffffffff60e01b8360e01b166004820152600082516149f18160088501602087016147af565b91909101600801949350505050565b63ffffffff8616815284602082015283604082015263ffffffff8316606082015260a060808201526000613bfc60a08301846147d3565b63ffffffff85811682526001600160a01b038516602083015283166040820152608060608201819052600090614a6f908301846147d3565b9695505050505050565b600060033d1115614a925760046000803e5060005160e01c5b90565b600060443d1015614aa35790565b6040513d600319016004823e80513d60248201116001600160401b0382111715614acc57505090565b80820180516001600160401b03811115614ae7575050505090565b3d8401600319018282016020011115614b01575050505090565b614b1060208285010185614253565b509392505050565b6f02232b837b9b4ba103330b4b632b21d160851b815260008251614b438160108501602087016147af565b9190910160100192915050565b60208152600061287460208301846147d3565b7f4465706f736974206661696c65643a206c6f772d6c6576656c2063616c6c206681526f030b4b632b2103bb4ba34103230ba30960851b602082015260008251614bb48160308501602087016147af565b9190910160300192915050565b7202bb4ba34323930bbb0b6103330b4b632b21d1606d1b815260008251614bef8160138501602087016147af565b9190910160130192915050565b7f5769746864726177616c206661696c65643a206c6f772d6c6576656c2063616c815272036103330b4b632b2103bb4ba34103230ba309606d1b602082015260008251614c508160338501602087016147af565b9190910160330192915050565b602080825260139082015272092dcecc2d8d2c840c8c2e8c240d8cadccee8d606b1b604082015260600190565b80516020808301519190811015614cab576000198160200360031b1b821691505b50919050565b600060208284031215614cc357600080fd5b81516123238161418d565b60008060408385031215614ce157600080fd5b8251614cec816143ff565b6020939093015192949293505050565b60008251614d0e8184602087016147af565b9190910192915050565b808202811582820484141761287757612877614846565b600082614d4c57634e487b7160e01b600052601260045260246000fd5b500490565b88815263ffffffff8816602082015286604082015260018060a01b03861660608201528460808201528360a082015263ffffffff831660c082015261010060e08201526000614da46101008301846147d3565b9a9950505050505050505050565b63ffffffff88811682526001600160a01b038881166020840152871660408301526060820186905260808201859052831660a082015260e060c08201819052600090614e00908301846147d3565b999850505050505050505056fe53bf423e48ed90e97d02ab0ebab13b2a235a6bfbe9c321847d5c175333ac45357bea13895fa79d2831e0a9e28edede30099005a50d652d8957cf8a607ee6ca4aa2646970667358221220e2f059eccac446855423527343f655a81f4122f7e91825b0c4623d4ffd9afd4d64736f6c634300081c0033", + "libraries": {}, + "devdoc": { + "kind": "dev", + "methods": { + "checkBalance(address)": { + "params": { + "_asset": "Address of the asset" + }, + "returns": { + "_0": "balance Total value of the asset in the platform and contract" + } + }, + "deposit(address,uint256)": { + "details": "Deposit assets by converting them to shares", + "params": { + "_amount": "Amount of asset to deposit", + "_asset": "Address of asset to deposit" + } + }, + "depositAll()": { + "details": "Deposit the entire balance of assetToken to gain shareToken" + }, + "getRewardTokenAddresses()": { + "returns": { + "_0": "address[] the reward token addresses." + } + }, + "handleReceiveFinalizedMessage(uint32,bytes32,uint32,bytes)": { + "details": "Handles a finalized CCTP message", + "params": { + "finalityThresholdExecuted": "Fidelity threshold executed", + "messageBody": "Message body", + "sender": "Sender of the message", + "sourceDomain": "Source domain of the message" + } + }, + "handleReceiveUnfinalizedMessage(uint32,bytes32,uint32,bytes)": { + "details": "Handles an unfinalized but safe CCTP message", + "params": { + "finalityThresholdExecuted": "Fidelity threshold executed", + "messageBody": "Message body", + "sender": "Sender of the message", + "sourceDomain": "Source domain of the message" + } + }, + "initialize(address,address,uint16,uint16)": { + "details": "Initialize the strategy implementation", + "params": { + "_feePremiumBps": "Fee premium in basis points", + "_minFinalityThreshold": "Minimum finality threshold", + "_operator": "Address of the operator", + "_strategist": "Address of the strategist" + } + }, + "isNonceProcessed(uint64)": { + "details": "Checks if a given nonce is processed. Nonce starts at 1, so 0 is disregarded.", + "params": { + "nonce": "Nonce to check" + }, + "returns": { + "_0": "True if the nonce is processed, false otherwise" + } + }, + "isTransferPending()": { + "details": "Checks if the last known transfer is pending. Nonce starts at 1, so 0 is disregarded.", + "returns": { + "_0": "True if a transfer is pending, false otherwise" + } + }, + "merkleClaim(address,uint256,bytes32[])": { + "params": { + "amount": "The amount of tokens to claim.", + "proof": "The Merkle proof to validate the claim.", + "token": "The address of the token to claim." + } + }, + "relay(bytes,bytes)": { + "details": "Receives a message from the peer strategy on the other chain, does some basic checks and relays it to the local MessageTransmitterV2. If the message is a burn message, it will also handle the hook data and call the _onTokenReceived function.", + "params": { + "attestation": "Attestation of the message", + "message": "Payload of the message to send" + } + }, + "removePToken(uint256)": { + "details": "If the ERC-4626 Tokenized Vault needed to be changed, a new contract would need to be deployed and the proxy updated." + }, + "sendBalanceUpdate()": { + "details": "Send balance update message to the peer strategy" + }, + "setFeePremiumBps(uint16)": { + "details": "Set the fee premium in basis points. Cannot be higher than 30% (3000 basis points).", + "params": { + "_feePremiumBps": "Fee premium in basis points" + } + }, + "setHarvesterAddress(address)": { + "params": { + "_harvesterAddress": "Address of the harvester contract." + } + }, + "setMinFinalityThreshold(uint16)": { + "details": "Set the minimum finality threshold at which the message is considered to be finalized to relay. Only accepts a value of 1000 (Safe, after 1 epoch) or 2000 (Finalized, after 2 epochs).", + "params": { + "_minFinalityThreshold": "Minimum finality threshold" + } + }, + "setOperator(address)": { + "details": "Set the operator address", + "params": { + "_operator": "Operator address" + } + }, + "setPTokenAddress(address,address)": { + "details": "If the ERC-4626 Tokenized Vault needed to be changed, a new contract would need to be deployed and the proxy updated." + }, + "setRewardTokenAddresses(address[])": { + "params": { + "_rewardTokenAddresses": "Array of reward token addresses" + } + }, + "setStrategistAddr(address)": { + "details": "Set address of Strategist", + "params": { + "_address": "Address of Strategist" + } + }, + "supportsAsset(address)": { + "details": "Returns bool indicating whether asset is supported by strategy", + "params": { + "_asset": "Address of the asset" + } + }, + "transferGovernance(address)": { + "params": { + "_newGovernor": "Address of the new Governor" + } + }, + "transferToken(address,uint256)": { + "params": { + "_amount": "Amount of the asset to transfer", + "_asset": "Address for the asset" + } + }, + "withdraw(address,address,uint256)": { + "details": "Interface requires a recipient, but for compatibility it must be address(this).", + "params": { + "_amount": "Amount of asset to withdraw", + "_asset": "Address of asset to withdraw", + "_recipient": "Address to receive withdrawn asset" + } + }, + "withdrawAll()": { + "details": "Remove all assets from platform and send them to Vault contract." + } + }, + "version": 1 + }, + "userdoc": { + "kind": "user", + "methods": { + "MAX_TRANSFER_AMOUNT()": { + "notice": "Max transfer threshold imposed by the CCTP Ref: https://developers.circle.com/cctp/evm-smart-contracts#depositforburn" + }, + "MIN_TRANSFER_AMOUNT()": { + "notice": "Minimum transfer amount to avoid zero or dust transfers" + }, + "assetToPToken(address)": { + "notice": "asset => pToken (Platform Specific Token Address)" + }, + "cctpMessageTransmitter()": { + "notice": "CCTP message transmitter contract" + }, + "cctpTokenMessenger()": { + "notice": "CCTP token messenger contract" + }, + "checkBalance(address)": { + "notice": "Get the total asset value held in the platform and contract" + }, + "claimGovernance()": { + "notice": "Claim Governance of the contract to a new account (`newGovernor`). Can only be called by the new Governor." + }, + "collectRewardTokens()": { + "notice": "Collect accumulated reward token and send to Vault." + }, + "feePremiumBps()": { + "notice": "Fee premium in basis points" + }, + "getRewardTokenAddresses()": { + "notice": "Get the reward token addresses." + }, + "governor()": { + "notice": "Returns the address of the current Governor." + }, + "harvesterAddress()": { + "notice": "Address of the Harvester contract allowed to collect reward tokens" + }, + "isGovernor()": { + "notice": "Returns true if the caller is the current Governor." + }, + "lastTransferNonce()": { + "notice": "Nonce of the last known deposit or withdrawal" + }, + "merkleClaim(address,uint256,bytes32[])": { + "notice": "Claim tokens from the Merkle Distributor" + }, + "merkleDistributor()": { + "notice": "The address of the Merkle Distributor contract." + }, + "minFinalityThreshold()": { + "notice": "Minimum finality threshold Can be 1000 (safe, after 1 epoch) or 2000 (finalized, after 2 epochs). Ref: https://developers.circle.com/cctp/technical-guide#finality-thresholds" + }, + "operator()": { + "notice": "Operator address: Can relay CCTP messages" + }, + "peerDomainID()": { + "notice": "Domain ID of the chain from which messages are accepted" + }, + "peerStrategy()": { + "notice": "Strategy address on other chain" + }, + "peerUsdcToken()": { + "notice": "USDC address on remote chain" + }, + "platformAddress()": { + "notice": "Address of the underlying platform" + }, + "removePToken(uint256)": { + "notice": "is not supported for this strategy as the asset and ERC-4626 Tokenized Vault are set at deploy time." + }, + "rewardTokenAddresses(uint256)": { + "notice": "Address of the reward tokens. eg CRV, BAL, CVX, AURA" + }, + "safeApproveAllTokens()": { + "notice": "Governor approves the ERC-4626 Tokenized Vault to spend the asset." + }, + "setHarvesterAddress(address)": { + "notice": "Set the Harvester contract that can collect rewards." + }, + "setPTokenAddress(address,address)": { + "notice": "is not supported for this strategy as the asset and ERC-4626 Tokenized Vault are set at deploy time." + }, + "setRewardTokenAddresses(address[])": { + "notice": "Set the reward token addresses. Any old addresses will be overwritten." + }, + "transferGovernance(address)": { + "notice": "Transfers Governance of the contract to a new account (`newGovernor`). Can only be called by the current Governor. Must be claimed for this to complete" + }, + "transferToken(address,uint256)": { + "notice": "Transfer token to governor. Intended for recovering tokens stuck in strategy contracts, i.e. mistaken sends." + }, + "usdcToken()": { + "notice": "USDC address on local chain" + }, + "vaultAddress()": { + "notice": "Address of the OToken vault" + } + }, + "version": 1 + }, + "storageLayout": { + "storage": [ + { + "astId": 55299, + "contract": "contracts/strategies/crosschain/CrossChainRemoteStrategy.sol:CrossChainRemoteStrategy", + "label": "initialized", + "offset": 0, + "slot": "0", + "type": "t_bool" + }, + { + "astId": 55302, + "contract": "contracts/strategies/crosschain/CrossChainRemoteStrategy.sol:CrossChainRemoteStrategy", + "label": "initializing", + "offset": 1, + "slot": "0", + "type": "t_bool" + }, + { + "astId": 55342, + "contract": "contracts/strategies/crosschain/CrossChainRemoteStrategy.sol:CrossChainRemoteStrategy", + "label": "______gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage" + }, + { + "astId": 45196, + "contract": "contracts/strategies/crosschain/CrossChainRemoteStrategy.sol:CrossChainRemoteStrategy", + "label": "minFinalityThreshold", + "offset": 0, + "slot": "51", + "type": "t_uint16" + }, + { + "astId": 45199, + "contract": "contracts/strategies/crosschain/CrossChainRemoteStrategy.sol:CrossChainRemoteStrategy", + "label": "feePremiumBps", + "offset": 2, + "slot": "51", + "type": "t_uint16" + }, + { + "astId": 45202, + "contract": "contracts/strategies/crosschain/CrossChainRemoteStrategy.sol:CrossChainRemoteStrategy", + "label": "lastTransferNonce", + "offset": 4, + "slot": "51", + "type": "t_uint64" + }, + { + "astId": 45205, + "contract": "contracts/strategies/crosschain/CrossChainRemoteStrategy.sol:CrossChainRemoteStrategy", + "label": "operator", + "offset": 12, + "slot": "51", + "type": "t_address" + }, + { + "astId": 45210, + "contract": "contracts/strategies/crosschain/CrossChainRemoteStrategy.sol:CrossChainRemoteStrategy", + "label": "nonceProcessed", + "offset": 0, + "slot": "52", + "type": "t_mapping(t_uint64,t_bool)" + }, + { + "astId": 45214, + "contract": "contracts/strategies/crosschain/CrossChainRemoteStrategy.sol:CrossChainRemoteStrategy", + "label": "__gap", + "offset": 0, + "slot": "53", + "type": "t_array(t_uint256)48_storage" + }, + { + "astId": 55422, + "contract": "contracts/strategies/crosschain/CrossChainRemoteStrategy.sol:CrossChainRemoteStrategy", + "label": "_deprecated_platformAddress", + "offset": 0, + "slot": "101", + "type": "t_address" + }, + { + "astId": 55425, + "contract": "contracts/strategies/crosschain/CrossChainRemoteStrategy.sol:CrossChainRemoteStrategy", + "label": "_deprecated_vaultAddress", + "offset": 0, + "slot": "102", + "type": "t_address" + }, + { + "astId": 55430, + "contract": "contracts/strategies/crosschain/CrossChainRemoteStrategy.sol:CrossChainRemoteStrategy", + "label": "assetToPToken", + "offset": 0, + "slot": "103", + "type": "t_mapping(t_address,t_address)" + }, + { + "astId": 55434, + "contract": "contracts/strategies/crosschain/CrossChainRemoteStrategy.sol:CrossChainRemoteStrategy", + "label": "assetsMapped", + "offset": 0, + "slot": "104", + "type": "t_array(t_address)dyn_storage" + }, + { + "astId": 55436, + "contract": "contracts/strategies/crosschain/CrossChainRemoteStrategy.sol:CrossChainRemoteStrategy", + "label": "_deprecated_rewardTokenAddress", + "offset": 0, + "slot": "105", + "type": "t_address" + }, + { + "astId": 55438, + "contract": "contracts/strategies/crosschain/CrossChainRemoteStrategy.sol:CrossChainRemoteStrategy", + "label": "_deprecated_rewardLiquidationThreshold", + "offset": 0, + "slot": "106", + "type": "t_uint256" + }, + { + "astId": 55441, + "contract": "contracts/strategies/crosschain/CrossChainRemoteStrategy.sol:CrossChainRemoteStrategy", + "label": "harvesterAddress", + "offset": 0, + "slot": "107", + "type": "t_address" + }, + { + "astId": 55445, + "contract": "contracts/strategies/crosschain/CrossChainRemoteStrategy.sol:CrossChainRemoteStrategy", + "label": "rewardTokenAddresses", + "offset": 0, + "slot": "108", + "type": "t_array(t_address)dyn_storage" + }, + { + "astId": 55449, + "contract": "contracts/strategies/crosschain/CrossChainRemoteStrategy.sol:CrossChainRemoteStrategy", + "label": "_reserved", + "offset": 0, + "slot": "109", + "type": "t_array(t_int256)98_storage" + }, + { + "astId": 33995, + "contract": "contracts/strategies/crosschain/CrossChainRemoteStrategy.sol:CrossChainRemoteStrategy", + "label": "_deprecate_shareToken", + "offset": 0, + "slot": "207", + "type": "t_address" + }, + { + "astId": 33998, + "contract": "contracts/strategies/crosschain/CrossChainRemoteStrategy.sol:CrossChainRemoteStrategy", + "label": "_deprecate_assetToken", + "offset": 0, + "slot": "208", + "type": "t_address" + }, + { + "astId": 34008, + "contract": "contracts/strategies/crosschain/CrossChainRemoteStrategy.sol:CrossChainRemoteStrategy", + "label": "__gap", + "offset": 0, + "slot": "209", + "type": "t_array(t_uint256)50_storage" + }, + { + "astId": 9290, + "contract": "contracts/strategies/crosschain/CrossChainRemoteStrategy.sol:CrossChainRemoteStrategy", + "label": "strategistAddr", + "offset": 0, + "slot": "259", + "type": "t_address" + }, + { + "astId": 9294, + "contract": "contracts/strategies/crosschain/CrossChainRemoteStrategy.sol:CrossChainRemoteStrategy", + "label": "__gap", + "offset": 0, + "slot": "260", + "type": "t_array(t_uint256)50_storage" + } + ], + "types": { + "t_address": { + "encoding": "inplace", + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_address)dyn_storage": { + "base": "t_address", + "encoding": "dynamic_array", + "label": "address[]", + "numberOfBytes": "32" + }, + "t_array(t_int256)98_storage": { + "base": "t_int256", + "encoding": "inplace", + "label": "int256[98]", + "numberOfBytes": "3136" + }, + "t_array(t_uint256)48_storage": { + "base": "t_uint256", + "encoding": "inplace", + "label": "uint256[48]", + "numberOfBytes": "1536" + }, + "t_array(t_uint256)50_storage": { + "base": "t_uint256", + "encoding": "inplace", + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "encoding": "inplace", + "label": "bool", + "numberOfBytes": "1" + }, + "t_int256": { + "encoding": "inplace", + "label": "int256", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_address)": { + "encoding": "mapping", + "key": "t_address", + "label": "mapping(address => address)", + "numberOfBytes": "32", + "value": "t_address" + }, + "t_mapping(t_uint64,t_bool)": { + "encoding": "mapping", + "key": "t_uint64", + "label": "mapping(uint64 => bool)", + "numberOfBytes": "32", + "value": "t_bool" + }, + "t_uint16": { + "encoding": "inplace", + "label": "uint16", + "numberOfBytes": "2" + }, + "t_uint256": { + "encoding": "inplace", + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint64": { + "encoding": "inplace", + "label": "uint64", + "numberOfBytes": "8" + } + } + } +} \ No newline at end of file diff --git a/contracts/deployments/base/create2Proxies.json b/contracts/deployments/base/create2Proxies.json index 9e26dfeeb6..4accae1715 100644 --- a/contracts/deployments/base/create2Proxies.json +++ b/contracts/deployments/base/create2Proxies.json @@ -1 +1,3 @@ -{} \ No newline at end of file +{ + "CrossChainStrategyProxy": "0xB1d624fc40824683e2bFBEfd19eB208DbBE00866" +} \ No newline at end of file diff --git a/contracts/deployments/base/operations/042_crosschain_strategy.execute.json b/contracts/deployments/base/operations/042_crosschain_strategy.execute.json new file mode 100644 index 0000000000..14ee66e182 --- /dev/null +++ b/contracts/deployments/base/operations/042_crosschain_strategy.execute.json @@ -0,0 +1,52 @@ +{ + "version": "1.0", + "chainId": "8453", + "createdAt": 1770807435, + "meta": { + "name": "Transaction Batch", + "description": "", + "txBuilderVersion": "1.16.1", + "createdFromSafeAddress": "0x92A19381444A001d62cE67BaFF066fA1111d7202", + "createdFromOwnerAddress": "" + }, + "transactions": [ + { + "to": "0xf817cb3092179083c48c014688D98B72fB61464f", + "value": "0", + "data": null, + "contractMethod": { + "inputs": [ + { + "type": "address[]", + "name": "targets" + }, + { + "type": "uint256[]", + "name": "values" + }, + { + "type": "bytes[]", + "name": "payloads" + }, + { + "type": "bytes32", + "name": "predecessor" + }, + { + "type": "bytes32", + "name": "salt" + } + ], + "name": "executeBatch", + "payable": true + }, + "contractInputsValues": { + "targets": "[\"0xB1d624fc40824683e2bFBEfd19eB208DbBE00866\",\"0xB1d624fc40824683e2bFBEfd19eB208DbBE00866\"]", + "values": "[\"0\",\"0\"]", + "payloads": "[\"0xad1728cb\",\"0xc2e1e3f40000000000000000000000004ff1b9d9ba8558f5eafcec096318ea0d8b541971\"]", + "predecessor": "0x0000000000000000000000000000000000000000000000000000000000000000", + "salt": "0x163f0d8486627d87fa054abf55d6e76533445aed2ca1b3837ff760baf919af27" + } + } + ] +} \ No newline at end of file diff --git a/contracts/deployments/base/operations/042_crosschain_strategy.schedule.json b/contracts/deployments/base/operations/042_crosschain_strategy.schedule.json new file mode 100644 index 0000000000..f28b7d458f --- /dev/null +++ b/contracts/deployments/base/operations/042_crosschain_strategy.schedule.json @@ -0,0 +1,57 @@ +{ + "version": "1.0", + "chainId": "8453", + "createdAt": 1770807435, + "meta": { + "name": "Transaction Batch", + "description": "", + "txBuilderVersion": "1.16.1", + "createdFromSafeAddress": "0x92A19381444A001d62cE67BaFF066fA1111d7202", + "createdFromOwnerAddress": "" + }, + "transactions": [ + { + "to": "0xf817cb3092179083c48c014688D98B72fB61464f", + "value": "0", + "data": null, + "contractMethod": { + "inputs": [ + { + "type": "address[]", + "name": "targets" + }, + { + "type": "uint256[]", + "name": "values" + }, + { + "type": "bytes[]", + "name": "payloads" + }, + { + "type": "bytes32", + "name": "predecessor" + }, + { + "type": "bytes32", + "name": "salt" + }, + { + "type": "uint256", + "name": "delay" + } + ], + "name": "scheduleBatch", + "payable": false + }, + "contractInputsValues": { + "targets": "[\"0xB1d624fc40824683e2bFBEfd19eB208DbBE00866\",\"0xB1d624fc40824683e2bFBEfd19eB208DbBE00866\"]", + "values": "[\"0\",\"0\"]", + "payloads": "[\"0xad1728cb\",\"0xc2e1e3f40000000000000000000000004ff1b9d9ba8558f5eafcec096318ea0d8b541971\"]", + "predecessor": "0x0000000000000000000000000000000000000000000000000000000000000000", + "salt": "0x163f0d8486627d87fa054abf55d6e76533445aed2ca1b3837ff760baf919af27", + "delay": "172800" + } + } + ] +} \ No newline at end of file diff --git a/contracts/deployments/base/solcInputs/00a8ab5ddcdb46bc9891a2222be9eb31.json b/contracts/deployments/base/solcInputs/00a8ab5ddcdb46bc9891a2222be9eb31.json new file mode 100644 index 0000000000..ff638403d8 --- /dev/null +++ b/contracts/deployments/base/solcInputs/00a8ab5ddcdb46bc9891a2222be9eb31.json @@ -0,0 +1,621 @@ +{ + "language": "Solidity", + "sources": { + "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IRouterClient.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport {Client} from \"../libraries/Client.sol\";\n\ninterface IRouterClient {\n error UnsupportedDestinationChain(uint64 destChainSelector);\n error InsufficientFeeTokenAmount();\n error InvalidMsgValue();\n\n /// @notice Checks if the given chain ID is supported for sending/receiving.\n /// @param chainSelector The chain to check.\n /// @return supported is true if it is supported, false if not.\n function isChainSupported(uint64 chainSelector) external view returns (bool supported);\n\n /// @notice Gets a list of all supported tokens which can be sent or received\n /// to/from a given chain id.\n /// @param chainSelector The chainSelector.\n /// @return tokens The addresses of all tokens that are supported.\n function getSupportedTokens(uint64 chainSelector) external view returns (address[] memory tokens);\n\n /// @param destinationChainSelector The destination chainSelector\n /// @param message The cross-chain CCIP message including data and/or tokens\n /// @return fee returns execution fee for the message\n /// delivery to destination chain, denominated in the feeToken specified in the message.\n /// @dev Reverts with appropriate reason upon invalid message.\n function getFee(\n uint64 destinationChainSelector,\n Client.EVM2AnyMessage memory message\n ) external view returns (uint256 fee);\n\n /// @notice Request a message to be sent to the destination chain\n /// @param destinationChainSelector The destination chain ID\n /// @param message The cross-chain CCIP message including data and/or tokens\n /// @return messageId The message ID\n /// @dev Note if msg.value is larger than the required fee (from getFee) we accept\n /// the overpayment with no refund.\n /// @dev Reverts with appropriate reason upon invalid message.\n function ccipSend(\n uint64 destinationChainSelector,\n Client.EVM2AnyMessage calldata message\n ) external payable returns (bytes32);\n}\n" + }, + "@chainlink/contracts-ccip/src/v0.8/ccip/libraries/Client.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\n// End consumer library.\nlibrary Client {\n /// @dev RMN depends on this struct, if changing, please notify the RMN maintainers.\n struct EVMTokenAmount {\n address token; // token address on the local chain.\n uint256 amount; // Amount of tokens.\n }\n\n struct Any2EVMMessage {\n bytes32 messageId; // MessageId corresponding to ccipSend on source.\n uint64 sourceChainSelector; // Source chain selector.\n bytes sender; // abi.decode(sender) if coming from an EVM chain.\n bytes data; // payload sent in original message.\n EVMTokenAmount[] destTokenAmounts; // Tokens and their amounts in their destination chain representation.\n }\n\n // If extraArgs is empty bytes, the default is 200k gas limit.\n struct EVM2AnyMessage {\n bytes receiver; // abi.encode(receiver address) for dest EVM chains\n bytes data; // Data payload\n EVMTokenAmount[] tokenAmounts; // Token transfers\n address feeToken; // Address of feeToken. address(0) means you will send msg.value.\n bytes extraArgs; // Populate this with _argsToBytes(EVMExtraArgsV1)\n }\n\n // bytes4(keccak256(\"CCIP EVMExtraArgsV1\"));\n bytes4 public constant EVM_EXTRA_ARGS_V1_TAG = 0x97a657c9;\n struct EVMExtraArgsV1 {\n uint256 gasLimit;\n }\n\n function _argsToBytes(EVMExtraArgsV1 memory extraArgs) internal pure returns (bytes memory bts) {\n return abi.encodeWithSelector(EVM_EXTRA_ARGS_V1_TAG, extraArgs);\n }\n}\n" + }, + "@layerzerolabs/lz-evm-messagelib-v2/contracts/libs/ExecutorOptions.sol": { + "content": "// SPDX-License-Identifier: LZBL-1.2\n\npragma solidity ^0.8.20;\n\nimport \"@layerzerolabs/lz-evm-protocol-v2/contracts/libs/CalldataBytesLib.sol\";\n\nlibrary ExecutorOptions {\n using CalldataBytesLib for bytes;\n\n uint8 internal constant WORKER_ID = 1;\n\n uint8 internal constant OPTION_TYPE_LZRECEIVE = 1;\n uint8 internal constant OPTION_TYPE_NATIVE_DROP = 2;\n uint8 internal constant OPTION_TYPE_LZCOMPOSE = 3;\n uint8 internal constant OPTION_TYPE_ORDERED_EXECUTION = 4;\n uint8 internal constant OPTION_TYPE_LZREAD = 5;\n\n error Executor_InvalidLzReceiveOption();\n error Executor_InvalidNativeDropOption();\n error Executor_InvalidLzComposeOption();\n error Executor_InvalidLzReadOption();\n\n /// @dev decode the next executor option from the options starting from the specified cursor\n /// @param _options [executor_id][executor_option][executor_id][executor_option]...\n /// executor_option = [option_size][option_type][option]\n /// option_size = len(option_type) + len(option)\n /// executor_id: uint8, option_size: uint16, option_type: uint8, option: bytes\n /// @param _cursor the cursor to start decoding from\n /// @return optionType the type of the option\n /// @return option the option of the executor\n /// @return cursor the cursor to start decoding the next executor option\n function nextExecutorOption(\n bytes calldata _options,\n uint256 _cursor\n ) internal pure returns (uint8 optionType, bytes calldata option, uint256 cursor) {\n unchecked {\n // skip worker id\n cursor = _cursor + 1;\n\n // read option size\n uint16 size = _options.toU16(cursor);\n cursor += 2;\n\n // read option type\n optionType = _options.toU8(cursor);\n\n // startCursor and endCursor are used to slice the option from _options\n uint256 startCursor = cursor + 1; // skip option type\n uint256 endCursor = cursor + size;\n option = _options[startCursor:endCursor];\n cursor += size;\n }\n }\n\n function decodeLzReceiveOption(bytes calldata _option) internal pure returns (uint128 gas, uint128 value) {\n if (_option.length != 16 && _option.length != 32) revert Executor_InvalidLzReceiveOption();\n gas = _option.toU128(0);\n value = _option.length == 32 ? _option.toU128(16) : 0;\n }\n\n function decodeNativeDropOption(bytes calldata _option) internal pure returns (uint128 amount, bytes32 receiver) {\n if (_option.length != 48) revert Executor_InvalidNativeDropOption();\n amount = _option.toU128(0);\n receiver = _option.toB32(16);\n }\n\n function decodeLzComposeOption(\n bytes calldata _option\n ) internal pure returns (uint16 index, uint128 gas, uint128 value) {\n if (_option.length != 18 && _option.length != 34) revert Executor_InvalidLzComposeOption();\n index = _option.toU16(0);\n gas = _option.toU128(2);\n value = _option.length == 34 ? _option.toU128(18) : 0;\n }\n\n function decodeLzReadOption(\n bytes calldata _option\n ) internal pure returns (uint128 gas, uint32 calldataSize, uint128 value) {\n if (_option.length != 20 && _option.length != 36) revert Executor_InvalidLzReadOption();\n gas = _option.toU128(0);\n calldataSize = _option.toU32(16);\n value = _option.length == 36 ? _option.toU128(20) : 0;\n }\n\n function encodeLzReceiveOption(uint128 _gas, uint128 _value) internal pure returns (bytes memory) {\n return _value == 0 ? abi.encodePacked(_gas) : abi.encodePacked(_gas, _value);\n }\n\n function encodeNativeDropOption(uint128 _amount, bytes32 _receiver) internal pure returns (bytes memory) {\n return abi.encodePacked(_amount, _receiver);\n }\n\n function encodeLzComposeOption(uint16 _index, uint128 _gas, uint128 _value) internal pure returns (bytes memory) {\n return _value == 0 ? abi.encodePacked(_index, _gas) : abi.encodePacked(_index, _gas, _value);\n }\n\n function encodeLzReadOption(\n uint128 _gas,\n uint32 _calldataSize,\n uint128 _value\n ) internal pure returns (bytes memory) {\n return _value == 0 ? abi.encodePacked(_gas, _calldataSize) : abi.encodePacked(_gas, _calldataSize, _value);\n }\n}\n" + }, + "@layerzerolabs/lz-evm-messagelib-v2/contracts/uln/libs/DVNOptions.sol": { + "content": "// SPDX-License-Identifier: LZBL-1.2\n\npragma solidity ^0.8.20;\n\nimport { BytesLib } from \"solidity-bytes-utils/contracts/BytesLib.sol\";\n\nimport { BitMap256 } from \"@layerzerolabs/lz-evm-protocol-v2/contracts/messagelib/libs/BitMaps.sol\";\nimport { CalldataBytesLib } from \"@layerzerolabs/lz-evm-protocol-v2/contracts/libs/CalldataBytesLib.sol\";\n\nlibrary DVNOptions {\n using CalldataBytesLib for bytes;\n using BytesLib for bytes;\n\n uint8 internal constant WORKER_ID = 2;\n uint8 internal constant OPTION_TYPE_PRECRIME = 1;\n\n error DVN_InvalidDVNIdx();\n error DVN_InvalidDVNOptions(uint256 cursor);\n\n /// @dev group dvn options by its idx\n /// @param _options [dvn_id][dvn_option][dvn_id][dvn_option]...\n /// dvn_option = [option_size][dvn_idx][option_type][option]\n /// option_size = len(dvn_idx) + len(option_type) + len(option)\n /// dvn_id: uint8, dvn_idx: uint8, option_size: uint16, option_type: uint8, option: bytes\n /// @return dvnOptions the grouped options, still share the same format of _options\n /// @return dvnIndices the dvn indices\n function groupDVNOptionsByIdx(\n bytes memory _options\n ) internal pure returns (bytes[] memory dvnOptions, uint8[] memory dvnIndices) {\n if (_options.length == 0) return (dvnOptions, dvnIndices);\n\n uint8 numDVNs = getNumDVNs(_options);\n\n // if there is only 1 dvn, we can just return the whole options\n if (numDVNs == 1) {\n dvnOptions = new bytes[](1);\n dvnOptions[0] = _options;\n\n dvnIndices = new uint8[](1);\n dvnIndices[0] = _options.toUint8(3); // dvn idx\n return (dvnOptions, dvnIndices);\n }\n\n // otherwise, we need to group the options by dvn_idx\n dvnIndices = new uint8[](numDVNs);\n dvnOptions = new bytes[](numDVNs);\n unchecked {\n uint256 cursor = 0;\n uint256 start = 0;\n uint8 lastDVNIdx = 255; // 255 is an invalid dvn_idx\n\n while (cursor < _options.length) {\n ++cursor; // skip worker_id\n\n // optionLength asserted in getNumDVNs (skip check)\n uint16 optionLength = _options.toUint16(cursor);\n cursor += 2;\n\n // dvnIdx asserted in getNumDVNs (skip check)\n uint8 dvnIdx = _options.toUint8(cursor);\n\n // dvnIdx must equal to the lastDVNIdx for the first option\n // so it is always skipped in the first option\n // this operation slices out options whenever the scan finds a different lastDVNIdx\n if (lastDVNIdx == 255) {\n lastDVNIdx = dvnIdx;\n } else if (dvnIdx != lastDVNIdx) {\n uint256 len = cursor - start - 3; // 3 is for worker_id and option_length\n bytes memory opt = _options.slice(start, len);\n _insertDVNOptions(dvnOptions, dvnIndices, lastDVNIdx, opt);\n\n // reset the start and lastDVNIdx\n start += len;\n lastDVNIdx = dvnIdx;\n }\n\n cursor += optionLength;\n }\n\n // skip check the cursor here because the cursor is asserted in getNumDVNs\n // if we have reached the end of the options, we need to process the last dvn\n uint256 size = cursor - start;\n bytes memory op = _options.slice(start, size);\n _insertDVNOptions(dvnOptions, dvnIndices, lastDVNIdx, op);\n\n // revert dvnIndices to start from 0\n for (uint8 i = 0; i < numDVNs; ++i) {\n --dvnIndices[i];\n }\n }\n }\n\n function _insertDVNOptions(\n bytes[] memory _dvnOptions,\n uint8[] memory _dvnIndices,\n uint8 _dvnIdx,\n bytes memory _newOptions\n ) internal pure {\n // dvnIdx starts from 0 but default value of dvnIndices is 0,\n // so we tell if the slot is empty by adding 1 to dvnIdx\n if (_dvnIdx == 255) revert DVN_InvalidDVNIdx();\n uint8 dvnIdxAdj = _dvnIdx + 1;\n\n for (uint256 j = 0; j < _dvnIndices.length; ++j) {\n uint8 index = _dvnIndices[j];\n if (dvnIdxAdj == index) {\n _dvnOptions[j] = abi.encodePacked(_dvnOptions[j], _newOptions);\n break;\n } else if (index == 0) {\n // empty slot, that means it is the first time we see this dvn\n _dvnIndices[j] = dvnIdxAdj;\n _dvnOptions[j] = _newOptions;\n break;\n }\n }\n }\n\n /// @dev get the number of unique dvns\n /// @param _options the format is the same as groupDVNOptionsByIdx\n function getNumDVNs(bytes memory _options) internal pure returns (uint8 numDVNs) {\n uint256 cursor = 0;\n BitMap256 bitmap;\n\n // find number of unique dvn_idx\n unchecked {\n while (cursor < _options.length) {\n ++cursor; // skip worker_id\n\n uint16 optionLength = _options.toUint16(cursor);\n cursor += 2;\n if (optionLength < 2) revert DVN_InvalidDVNOptions(cursor); // at least 1 byte for dvn_idx and 1 byte for option_type\n\n uint8 dvnIdx = _options.toUint8(cursor);\n\n // if dvnIdx is not set, increment numDVNs\n // max num of dvns is 255, 255 is an invalid dvn_idx\n // The order of the dvnIdx is not required to be sequential, as enforcing the order may weaken\n // the composability of the options. e.g. if we refrain from enforcing the order, an OApp that has\n // already enforced certain options can append additional options to the end of the enforced\n // ones without restrictions.\n if (dvnIdx == 255) revert DVN_InvalidDVNIdx();\n if (!bitmap.get(dvnIdx)) {\n ++numDVNs;\n bitmap = bitmap.set(dvnIdx);\n }\n\n cursor += optionLength;\n }\n }\n if (cursor != _options.length) revert DVN_InvalidDVNOptions(cursor);\n }\n\n /// @dev decode the next dvn option from _options starting from the specified cursor\n /// @param _options the format is the same as groupDVNOptionsByIdx\n /// @param _cursor the cursor to start decoding\n /// @return optionType the type of the option\n /// @return option the option\n /// @return cursor the cursor to start decoding the next option\n function nextDVNOption(\n bytes calldata _options,\n uint256 _cursor\n ) internal pure returns (uint8 optionType, bytes calldata option, uint256 cursor) {\n unchecked {\n // skip worker id\n cursor = _cursor + 1;\n\n // read option size\n uint16 size = _options.toU16(cursor);\n cursor += 2;\n\n // read option type\n optionType = _options.toU8(cursor + 1); // skip dvn_idx\n\n // startCursor and endCursor are used to slice the option from _options\n uint256 startCursor = cursor + 2; // skip option type and dvn_idx\n uint256 endCursor = cursor + size;\n option = _options[startCursor:endCursor];\n cursor += size;\n }\n }\n}\n" + }, + "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroEndpointV2.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity >=0.8.0;\n\nimport { IMessageLibManager } from \"./IMessageLibManager.sol\";\nimport { IMessagingComposer } from \"./IMessagingComposer.sol\";\nimport { IMessagingChannel } from \"./IMessagingChannel.sol\";\nimport { IMessagingContext } from \"./IMessagingContext.sol\";\n\nstruct MessagingParams {\n uint32 dstEid;\n bytes32 receiver;\n bytes message;\n bytes options;\n bool payInLzToken;\n}\n\nstruct MessagingReceipt {\n bytes32 guid;\n uint64 nonce;\n MessagingFee fee;\n}\n\nstruct MessagingFee {\n uint256 nativeFee;\n uint256 lzTokenFee;\n}\n\nstruct Origin {\n uint32 srcEid;\n bytes32 sender;\n uint64 nonce;\n}\n\ninterface ILayerZeroEndpointV2 is IMessageLibManager, IMessagingComposer, IMessagingChannel, IMessagingContext {\n event PacketSent(bytes encodedPayload, bytes options, address sendLibrary);\n\n event PacketVerified(Origin origin, address receiver, bytes32 payloadHash);\n\n event PacketDelivered(Origin origin, address receiver);\n\n event LzReceiveAlert(\n address indexed receiver,\n address indexed executor,\n Origin origin,\n bytes32 guid,\n uint256 gas,\n uint256 value,\n bytes message,\n bytes extraData,\n bytes reason\n );\n\n event LzTokenSet(address token);\n\n event DelegateSet(address sender, address delegate);\n\n function quote(MessagingParams calldata _params, address _sender) external view returns (MessagingFee memory);\n\n function send(\n MessagingParams calldata _params,\n address _refundAddress\n ) external payable returns (MessagingReceipt memory);\n\n function verify(Origin calldata _origin, address _receiver, bytes32 _payloadHash) external;\n\n function verifiable(Origin calldata _origin, address _receiver) external view returns (bool);\n\n function initializable(Origin calldata _origin, address _receiver) external view returns (bool);\n\n function lzReceive(\n Origin calldata _origin,\n address _receiver,\n bytes32 _guid,\n bytes calldata _message,\n bytes calldata _extraData\n ) external payable;\n\n // oapp can burn messages partially by calling this function with its own business logic if messages are verified in order\n function clear(address _oapp, Origin calldata _origin, bytes32 _guid, bytes calldata _message) external;\n\n function setLzToken(address _lzToken) external;\n\n function lzToken() external view returns (address);\n\n function nativeToken() external view returns (address);\n\n function setDelegate(address _delegate) external;\n}\n" + }, + "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/IMessageLibManager.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity >=0.8.0;\n\nstruct SetConfigParam {\n uint32 eid;\n uint32 configType;\n bytes config;\n}\n\ninterface IMessageLibManager {\n struct Timeout {\n address lib;\n uint256 expiry;\n }\n\n event LibraryRegistered(address newLib);\n event DefaultSendLibrarySet(uint32 eid, address newLib);\n event DefaultReceiveLibrarySet(uint32 eid, address newLib);\n event DefaultReceiveLibraryTimeoutSet(uint32 eid, address oldLib, uint256 expiry);\n event SendLibrarySet(address sender, uint32 eid, address newLib);\n event ReceiveLibrarySet(address receiver, uint32 eid, address newLib);\n event ReceiveLibraryTimeoutSet(address receiver, uint32 eid, address oldLib, uint256 timeout);\n\n function registerLibrary(address _lib) external;\n\n function isRegisteredLibrary(address _lib) external view returns (bool);\n\n function getRegisteredLibraries() external view returns (address[] memory);\n\n function setDefaultSendLibrary(uint32 _eid, address _newLib) external;\n\n function defaultSendLibrary(uint32 _eid) external view returns (address);\n\n function setDefaultReceiveLibrary(uint32 _eid, address _newLib, uint256 _gracePeriod) external;\n\n function defaultReceiveLibrary(uint32 _eid) external view returns (address);\n\n function setDefaultReceiveLibraryTimeout(uint32 _eid, address _lib, uint256 _expiry) external;\n\n function defaultReceiveLibraryTimeout(uint32 _eid) external view returns (address lib, uint256 expiry);\n\n function isSupportedEid(uint32 _eid) external view returns (bool);\n\n function isValidReceiveLibrary(address _receiver, uint32 _eid, address _lib) external view returns (bool);\n\n /// ------------------- OApp interfaces -------------------\n function setSendLibrary(address _oapp, uint32 _eid, address _newLib) external;\n\n function getSendLibrary(address _sender, uint32 _eid) external view returns (address lib);\n\n function isDefaultSendLibrary(address _sender, uint32 _eid) external view returns (bool);\n\n function setReceiveLibrary(address _oapp, uint32 _eid, address _newLib, uint256 _gracePeriod) external;\n\n function getReceiveLibrary(address _receiver, uint32 _eid) external view returns (address lib, bool isDefault);\n\n function setReceiveLibraryTimeout(address _oapp, uint32 _eid, address _lib, uint256 _expiry) external;\n\n function receiveLibraryTimeout(address _receiver, uint32 _eid) external view returns (address lib, uint256 expiry);\n\n function setConfig(address _oapp, address _lib, SetConfigParam[] calldata _params) external;\n\n function getConfig(\n address _oapp,\n address _lib,\n uint32 _eid,\n uint32 _configType\n ) external view returns (bytes memory config);\n}\n" + }, + "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/IMessagingChannel.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity >=0.8.0;\n\ninterface IMessagingChannel {\n event InboundNonceSkipped(uint32 srcEid, bytes32 sender, address receiver, uint64 nonce);\n event PacketNilified(uint32 srcEid, bytes32 sender, address receiver, uint64 nonce, bytes32 payloadHash);\n event PacketBurnt(uint32 srcEid, bytes32 sender, address receiver, uint64 nonce, bytes32 payloadHash);\n\n function eid() external view returns (uint32);\n\n // this is an emergency function if a message cannot be verified for some reasons\n // required to provide _nextNonce to avoid race condition\n function skip(address _oapp, uint32 _srcEid, bytes32 _sender, uint64 _nonce) external;\n\n function nilify(address _oapp, uint32 _srcEid, bytes32 _sender, uint64 _nonce, bytes32 _payloadHash) external;\n\n function burn(address _oapp, uint32 _srcEid, bytes32 _sender, uint64 _nonce, bytes32 _payloadHash) external;\n\n function nextGuid(address _sender, uint32 _dstEid, bytes32 _receiver) external view returns (bytes32);\n\n function inboundNonce(address _receiver, uint32 _srcEid, bytes32 _sender) external view returns (uint64);\n\n function outboundNonce(address _sender, uint32 _dstEid, bytes32 _receiver) external view returns (uint64);\n\n function inboundPayloadHash(\n address _receiver,\n uint32 _srcEid,\n bytes32 _sender,\n uint64 _nonce\n ) external view returns (bytes32);\n\n function lazyInboundNonce(address _receiver, uint32 _srcEid, bytes32 _sender) external view returns (uint64);\n}\n" + }, + "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/IMessagingComposer.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity >=0.8.0;\n\ninterface IMessagingComposer {\n event ComposeSent(address from, address to, bytes32 guid, uint16 index, bytes message);\n event ComposeDelivered(address from, address to, bytes32 guid, uint16 index);\n event LzComposeAlert(\n address indexed from,\n address indexed to,\n address indexed executor,\n bytes32 guid,\n uint16 index,\n uint256 gas,\n uint256 value,\n bytes message,\n bytes extraData,\n bytes reason\n );\n\n function composeQueue(\n address _from,\n address _to,\n bytes32 _guid,\n uint16 _index\n ) external view returns (bytes32 messageHash);\n\n function sendCompose(address _to, bytes32 _guid, uint16 _index, bytes calldata _message) external;\n\n function lzCompose(\n address _from,\n address _to,\n bytes32 _guid,\n uint16 _index,\n bytes calldata _message,\n bytes calldata _extraData\n ) external payable;\n}\n" + }, + "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/IMessagingContext.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity >=0.8.0;\n\ninterface IMessagingContext {\n function isSendingMessage() external view returns (bool);\n\n function getSendContext() external view returns (uint32 dstEid, address sender);\n}\n" + }, + "@layerzerolabs/lz-evm-protocol-v2/contracts/libs/CalldataBytesLib.sol": { + "content": "// SPDX-License-Identifier: LZBL-1.2\n\npragma solidity ^0.8.20;\n\nlibrary CalldataBytesLib {\n function toU8(bytes calldata _bytes, uint256 _start) internal pure returns (uint8) {\n return uint8(_bytes[_start]);\n }\n\n function toU16(bytes calldata _bytes, uint256 _start) internal pure returns (uint16) {\n unchecked {\n uint256 end = _start + 2;\n return uint16(bytes2(_bytes[_start:end]));\n }\n }\n\n function toU32(bytes calldata _bytes, uint256 _start) internal pure returns (uint32) {\n unchecked {\n uint256 end = _start + 4;\n return uint32(bytes4(_bytes[_start:end]));\n }\n }\n\n function toU64(bytes calldata _bytes, uint256 _start) internal pure returns (uint64) {\n unchecked {\n uint256 end = _start + 8;\n return uint64(bytes8(_bytes[_start:end]));\n }\n }\n\n function toU128(bytes calldata _bytes, uint256 _start) internal pure returns (uint128) {\n unchecked {\n uint256 end = _start + 16;\n return uint128(bytes16(_bytes[_start:end]));\n }\n }\n\n function toU256(bytes calldata _bytes, uint256 _start) internal pure returns (uint256) {\n unchecked {\n uint256 end = _start + 32;\n return uint256(bytes32(_bytes[_start:end]));\n }\n }\n\n function toAddr(bytes calldata _bytes, uint256 _start) internal pure returns (address) {\n unchecked {\n uint256 end = _start + 20;\n return address(bytes20(_bytes[_start:end]));\n }\n }\n\n function toB32(bytes calldata _bytes, uint256 _start) internal pure returns (bytes32) {\n unchecked {\n uint256 end = _start + 32;\n return bytes32(_bytes[_start:end]);\n }\n }\n}\n" + }, + "@layerzerolabs/lz-evm-protocol-v2/contracts/messagelib/libs/BitMaps.sol": { + "content": "// SPDX-License-Identifier: MIT\n\n// modified from https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/structs/BitMaps.sol\npragma solidity ^0.8.20;\n\ntype BitMap256 is uint256;\n\nusing BitMaps for BitMap256 global;\n\nlibrary BitMaps {\n /**\n * @dev Returns whether the bit at `index` is set.\n */\n function get(BitMap256 bitmap, uint8 index) internal pure returns (bool) {\n uint256 mask = 1 << index;\n return BitMap256.unwrap(bitmap) & mask != 0;\n }\n\n /**\n * @dev Sets the bit at `index`.\n */\n function set(BitMap256 bitmap, uint8 index) internal pure returns (BitMap256) {\n uint256 mask = 1 << index;\n return BitMap256.wrap(BitMap256.unwrap(bitmap) | mask);\n }\n}\n" + }, + "@layerzerolabs/oapp-evm/contracts/oapp/interfaces/IOAppCore.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\nimport { ILayerZeroEndpointV2 } from \"@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroEndpointV2.sol\";\n\n/**\n * @title IOAppCore\n */\ninterface IOAppCore {\n // Custom error messages\n error OnlyPeer(uint32 eid, bytes32 sender);\n error NoPeer(uint32 eid);\n error InvalidEndpointCall();\n error InvalidDelegate();\n\n // Event emitted when a peer (OApp) is set for a corresponding endpoint\n event PeerSet(uint32 eid, bytes32 peer);\n\n /**\n * @notice Retrieves the OApp version information.\n * @return senderVersion The version of the OAppSender.sol contract.\n * @return receiverVersion The version of the OAppReceiver.sol contract.\n */\n function oAppVersion() external view returns (uint64 senderVersion, uint64 receiverVersion);\n\n /**\n * @notice Retrieves the LayerZero endpoint associated with the OApp.\n * @return iEndpoint The LayerZero endpoint as an interface.\n */\n function endpoint() external view returns (ILayerZeroEndpointV2 iEndpoint);\n\n /**\n * @notice Retrieves the peer (OApp) associated with a corresponding endpoint.\n * @param _eid The endpoint ID.\n * @return peer The peer address (OApp instance) associated with the corresponding endpoint.\n */\n function peers(uint32 _eid) external view returns (bytes32 peer);\n\n /**\n * @notice Sets the peer address (OApp instance) for a corresponding endpoint.\n * @param _eid The endpoint ID.\n * @param _peer The address of the peer to be associated with the corresponding endpoint.\n */\n function setPeer(uint32 _eid, bytes32 _peer) external;\n\n /**\n * @notice Sets the delegate address for the OApp Core.\n * @param _delegate The address of the delegate to be set.\n */\n function setDelegate(address _delegate) external;\n}\n" + }, + "@layerzerolabs/oapp-evm/contracts/oapp/libs/OptionsBuilder.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\nimport { BytesLib } from \"solidity-bytes-utils/contracts/BytesLib.sol\";\nimport { SafeCast } from \"@openzeppelin/contracts/utils/math/SafeCast.sol\";\n\nimport { ExecutorOptions } from \"@layerzerolabs/lz-evm-messagelib-v2/contracts/libs/ExecutorOptions.sol\";\nimport { DVNOptions } from \"@layerzerolabs/lz-evm-messagelib-v2/contracts/uln/libs/DVNOptions.sol\";\n\n/**\n * @title OptionsBuilder\n * @dev Library for building and encoding various message options.\n */\nlibrary OptionsBuilder {\n using SafeCast for uint256;\n using BytesLib for bytes;\n\n // Constants for options types\n uint16 internal constant TYPE_1 = 1; // legacy options type 1\n uint16 internal constant TYPE_2 = 2; // legacy options type 2\n uint16 internal constant TYPE_3 = 3;\n\n // Custom error message\n error InvalidSize(uint256 max, uint256 actual);\n error InvalidOptionType(uint16 optionType);\n\n // Modifier to ensure only options of type 3 are used\n modifier onlyType3(bytes memory _options) {\n if (_options.toUint16(0) != TYPE_3) revert InvalidOptionType(_options.toUint16(0));\n _;\n }\n\n /**\n * @dev Creates a new options container with type 3.\n * @return options The newly created options container.\n */\n function newOptions() internal pure returns (bytes memory) {\n return abi.encodePacked(TYPE_3);\n }\n\n /**\n * @dev Adds an executor LZ receive option to the existing options.\n * @param _options The existing options container.\n * @param _gas The gasLimit used on the lzReceive() function in the OApp.\n * @param _value The msg.value passed to the lzReceive() function in the OApp.\n * @return options The updated options container.\n *\n * @dev When multiples of this option are added, they are summed by the executor\n * eg. if (_gas: 200k, and _value: 1 ether) AND (_gas: 100k, _value: 0.5 ether) are sent in an option to the LayerZeroEndpoint,\n * that becomes (300k, 1.5 ether) when the message is executed on the remote lzReceive() function.\n */\n function addExecutorLzReceiveOption(\n bytes memory _options,\n uint128 _gas,\n uint128 _value\n ) internal pure onlyType3(_options) returns (bytes memory) {\n bytes memory option = ExecutorOptions.encodeLzReceiveOption(_gas, _value);\n return addExecutorOption(_options, ExecutorOptions.OPTION_TYPE_LZRECEIVE, option);\n }\n\n /**\n * @dev Adds an executor native drop option to the existing options.\n * @param _options The existing options container.\n * @param _amount The amount for the native value that is airdropped to the 'receiver'.\n * @param _receiver The receiver address for the native drop option.\n * @return options The updated options container.\n *\n * @dev When multiples of this option are added, they are summed by the executor on the remote chain.\n */\n function addExecutorNativeDropOption(\n bytes memory _options,\n uint128 _amount,\n bytes32 _receiver\n ) internal pure onlyType3(_options) returns (bytes memory) {\n bytes memory option = ExecutorOptions.encodeNativeDropOption(_amount, _receiver);\n return addExecutorOption(_options, ExecutorOptions.OPTION_TYPE_NATIVE_DROP, option);\n }\n\n // /**\n // * @dev Adds an executor native drop option to the existing options.\n // * @param _options The existing options container.\n // * @param _amount The amount for the native value that is airdropped to the 'receiver'.\n // * @param _receiver The receiver address for the native drop option.\n // * @return options The updated options container.\n // *\n // * @dev When multiples of this option are added, they are summed by the executor on the remote chain.\n // */\n function addExecutorLzReadOption(\n bytes memory _options,\n uint128 _gas,\n uint32 _size,\n uint128 _value\n ) internal pure onlyType3(_options) returns (bytes memory) {\n bytes memory option = ExecutorOptions.encodeLzReadOption(_gas, _size, _value);\n return addExecutorOption(_options, ExecutorOptions.OPTION_TYPE_LZREAD, option);\n }\n\n /**\n * @dev Adds an executor LZ compose option to the existing options.\n * @param _options The existing options container.\n * @param _index The index for the lzCompose() function call.\n * @param _gas The gasLimit for the lzCompose() function call.\n * @param _value The msg.value for the lzCompose() function call.\n * @return options The updated options container.\n *\n * @dev When multiples of this option are added, they are summed PER index by the executor on the remote chain.\n * @dev If the OApp sends N lzCompose calls on the remote, you must provide N incremented indexes starting with 0.\n * ie. When your remote OApp composes (N = 3) messages, you must set this option for index 0,1,2\n */\n function addExecutorLzComposeOption(\n bytes memory _options,\n uint16 _index,\n uint128 _gas,\n uint128 _value\n ) internal pure onlyType3(_options) returns (bytes memory) {\n bytes memory option = ExecutorOptions.encodeLzComposeOption(_index, _gas, _value);\n return addExecutorOption(_options, ExecutorOptions.OPTION_TYPE_LZCOMPOSE, option);\n }\n\n /**\n * @dev Adds an executor ordered execution option to the existing options.\n * @param _options The existing options container.\n * @return options The updated options container.\n */\n function addExecutorOrderedExecutionOption(\n bytes memory _options\n ) internal pure onlyType3(_options) returns (bytes memory) {\n return addExecutorOption(_options, ExecutorOptions.OPTION_TYPE_ORDERED_EXECUTION, bytes(\"\"));\n }\n\n /**\n * @dev Adds a DVN pre-crime option to the existing options.\n * @param _options The existing options container.\n * @param _dvnIdx The DVN index for the pre-crime option.\n * @return options The updated options container.\n */\n function addDVNPreCrimeOption(\n bytes memory _options,\n uint8 _dvnIdx\n ) internal pure onlyType3(_options) returns (bytes memory) {\n return addDVNOption(_options, _dvnIdx, DVNOptions.OPTION_TYPE_PRECRIME, bytes(\"\"));\n }\n\n /**\n * @dev Adds an executor option to the existing options.\n * @param _options The existing options container.\n * @param _optionType The type of the executor option.\n * @param _option The encoded data for the executor option.\n * @return options The updated options container.\n */\n function addExecutorOption(\n bytes memory _options,\n uint8 _optionType,\n bytes memory _option\n ) internal pure onlyType3(_options) returns (bytes memory) {\n return\n abi.encodePacked(\n _options,\n ExecutorOptions.WORKER_ID,\n _option.length.toUint16() + 1, // +1 for optionType\n _optionType,\n _option\n );\n }\n\n /**\n * @dev Adds a DVN option to the existing options.\n * @param _options The existing options container.\n * @param _dvnIdx The DVN index for the DVN option.\n * @param _optionType The type of the DVN option.\n * @param _option The encoded data for the DVN option.\n * @return options The updated options container.\n */\n function addDVNOption(\n bytes memory _options,\n uint8 _dvnIdx,\n uint8 _optionType,\n bytes memory _option\n ) internal pure onlyType3(_options) returns (bytes memory) {\n return\n abi.encodePacked(\n _options,\n DVNOptions.WORKER_ID,\n _option.length.toUint16() + 2, // +2 for optionType and dvnIdx\n _dvnIdx,\n _optionType,\n _option\n );\n }\n\n /**\n * @dev Encodes legacy options of type 1.\n * @param _executionGas The gasLimit value passed to lzReceive().\n * @return legacyOptions The encoded legacy options.\n */\n function encodeLegacyOptionsType1(uint256 _executionGas) internal pure returns (bytes memory) {\n if (_executionGas > type(uint128).max) revert InvalidSize(type(uint128).max, _executionGas);\n return abi.encodePacked(TYPE_1, _executionGas);\n }\n\n /**\n * @dev Encodes legacy options of type 2.\n * @param _executionGas The gasLimit value passed to lzReceive().\n * @param _nativeForDst The amount of native air dropped to the receiver.\n * @param _receiver The _nativeForDst receiver address.\n * @return legacyOptions The encoded legacy options of type 2.\n */\n function encodeLegacyOptionsType2(\n uint256 _executionGas,\n uint256 _nativeForDst,\n bytes memory _receiver // @dev Use bytes instead of bytes32 in legacy type 2 for _receiver.\n ) internal pure returns (bytes memory) {\n if (_executionGas > type(uint128).max) revert InvalidSize(type(uint128).max, _executionGas);\n if (_nativeForDst > type(uint128).max) revert InvalidSize(type(uint128).max, _nativeForDst);\n if (_receiver.length > 32) revert InvalidSize(32, _receiver.length);\n return abi.encodePacked(TYPE_2, _executionGas, _nativeForDst, _receiver);\n }\n}\n" + }, + "@layerzerolabs/oapp-evm/contracts/oapp/OAppCore.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\nimport { Ownable } from \"@openzeppelin/contracts/access/Ownable.sol\";\nimport { IOAppCore, ILayerZeroEndpointV2 } from \"./interfaces/IOAppCore.sol\";\n\n/**\n * @title OAppCore\n * @dev Abstract contract implementing the IOAppCore interface with basic OApp configurations.\n */\nabstract contract OAppCore is IOAppCore, Ownable {\n // The LayerZero endpoint associated with the given OApp\n ILayerZeroEndpointV2 public immutable endpoint;\n\n // Mapping to store peers associated with corresponding endpoints\n mapping(uint32 eid => bytes32 peer) public peers;\n\n /**\n * @dev Constructor to initialize the OAppCore with the provided endpoint and delegate.\n * @param _endpoint The address of the LOCAL Layer Zero endpoint.\n * @param _delegate The delegate capable of making OApp configurations inside of the endpoint.\n *\n * @dev The delegate typically should be set as the owner of the contract.\n */\n constructor(address _endpoint, address _delegate) {\n endpoint = ILayerZeroEndpointV2(_endpoint);\n\n if (_delegate == address(0)) revert InvalidDelegate();\n endpoint.setDelegate(_delegate);\n }\n\n /**\n * @notice Sets the peer address (OApp instance) for a corresponding endpoint.\n * @param _eid The endpoint ID.\n * @param _peer The address of the peer to be associated with the corresponding endpoint.\n *\n * @dev Only the owner/admin of the OApp can call this function.\n * @dev Indicates that the peer is trusted to send LayerZero messages to this OApp.\n * @dev Set this to bytes32(0) to remove the peer address.\n * @dev Peer is a bytes32 to accommodate non-evm chains.\n */\n function setPeer(uint32 _eid, bytes32 _peer) public virtual onlyOwner {\n _setPeer(_eid, _peer);\n }\n\n /**\n * @notice Sets the peer address (OApp instance) for a corresponding endpoint.\n * @param _eid The endpoint ID.\n * @param _peer The address of the peer to be associated with the corresponding endpoint.\n *\n * @dev Indicates that the peer is trusted to send LayerZero messages to this OApp.\n * @dev Set this to bytes32(0) to remove the peer address.\n * @dev Peer is a bytes32 to accommodate non-evm chains.\n */\n function _setPeer(uint32 _eid, bytes32 _peer) internal virtual {\n peers[_eid] = _peer;\n emit PeerSet(_eid, _peer);\n }\n\n /**\n * @notice Internal function to get the peer address associated with a specific endpoint; reverts if NOT set.\n * ie. the peer is set to bytes32(0).\n * @param _eid The endpoint ID.\n * @return peer The address of the peer associated with the specified endpoint.\n */\n function _getPeerOrRevert(uint32 _eid) internal view virtual returns (bytes32) {\n bytes32 peer = peers[_eid];\n if (peer == bytes32(0)) revert NoPeer(_eid);\n return peer;\n }\n\n /**\n * @notice Sets the delegate address for the OApp.\n * @param _delegate The address of the delegate to be set.\n *\n * @dev Only the owner/admin of the OApp can call this function.\n * @dev Provides the ability for a delegate to set configs, on behalf of the OApp, directly on the Endpoint contract.\n */\n function setDelegate(address _delegate) public onlyOwner {\n endpoint.setDelegate(_delegate);\n }\n}\n" + }, + "@layerzerolabs/oapp-evm/contracts/oapp/OAppSender.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\nimport { SafeERC20, IERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport { MessagingParams, MessagingFee, MessagingReceipt } from \"@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroEndpointV2.sol\";\nimport { OAppCore } from \"./OAppCore.sol\";\n\n/**\n * @title OAppSender\n * @dev Abstract contract implementing the OAppSender functionality for sending messages to a LayerZero endpoint.\n */\nabstract contract OAppSender is OAppCore {\n using SafeERC20 for IERC20;\n\n // Custom error messages\n error NotEnoughNative(uint256 msgValue);\n error LzTokenUnavailable();\n\n // @dev The version of the OAppSender implementation.\n // @dev Version is bumped when changes are made to this contract.\n uint64 internal constant SENDER_VERSION = 1;\n\n /**\n * @notice Retrieves the OApp version information.\n * @return senderVersion The version of the OAppSender.sol contract.\n * @return receiverVersion The version of the OAppReceiver.sol contract.\n *\n * @dev Providing 0 as the default for OAppReceiver version. Indicates that the OAppReceiver is not implemented.\n * ie. this is a SEND only OApp.\n * @dev If the OApp uses both OAppSender and OAppReceiver, then this needs to be override returning the correct versions\n */\n function oAppVersion() public view virtual returns (uint64 senderVersion, uint64 receiverVersion) {\n return (SENDER_VERSION, 0);\n }\n\n /**\n * @dev Internal function to interact with the LayerZero EndpointV2.quote() for fee calculation.\n * @param _dstEid The destination endpoint ID.\n * @param _message The message payload.\n * @param _options Additional options for the message.\n * @param _payInLzToken Flag indicating whether to pay the fee in LZ tokens.\n * @return fee The calculated MessagingFee for the message.\n * - nativeFee: The native fee for the message.\n * - lzTokenFee: The LZ token fee for the message.\n */\n function _quote(\n uint32 _dstEid,\n bytes memory _message,\n bytes memory _options,\n bool _payInLzToken\n ) internal view virtual returns (MessagingFee memory fee) {\n return\n endpoint.quote(\n MessagingParams(_dstEid, _getPeerOrRevert(_dstEid), _message, _options, _payInLzToken),\n address(this)\n );\n }\n\n /**\n * @dev Internal function to interact with the LayerZero EndpointV2.send() for sending a message.\n * @param _dstEid The destination endpoint ID.\n * @param _message The message payload.\n * @param _options Additional options for the message.\n * @param _fee The calculated LayerZero fee for the message.\n * - nativeFee: The native fee.\n * - lzTokenFee: The lzToken fee.\n * @param _refundAddress The address to receive any excess fee values sent to the endpoint.\n * @return receipt The receipt for the sent message.\n * - guid: The unique identifier for the sent message.\n * - nonce: The nonce of the sent message.\n * - fee: The LayerZero fee incurred for the message.\n */\n function _lzSend(\n uint32 _dstEid,\n bytes memory _message,\n bytes memory _options,\n MessagingFee memory _fee,\n address _refundAddress\n ) internal virtual returns (MessagingReceipt memory receipt) {\n // @dev Push corresponding fees to the endpoint, any excess is sent back to the _refundAddress from the endpoint.\n uint256 messageValue = _payNative(_fee.nativeFee);\n if (_fee.lzTokenFee > 0) _payLzToken(_fee.lzTokenFee);\n\n return\n // solhint-disable-next-line check-send-result\n endpoint.send{ value: messageValue }(\n MessagingParams(_dstEid, _getPeerOrRevert(_dstEid), _message, _options, _fee.lzTokenFee > 0),\n _refundAddress\n );\n }\n\n /**\n * @dev Internal function to pay the native fee associated with the message.\n * @param _nativeFee The native fee to be paid.\n * @return nativeFee The amount of native currency paid.\n *\n * @dev If the OApp needs to initiate MULTIPLE LayerZero messages in a single transaction,\n * this will need to be overridden because msg.value would contain multiple lzFees.\n * @dev Should be overridden in the event the LayerZero endpoint requires a different native currency.\n * @dev Some EVMs use an ERC20 as a method for paying transactions/gasFees.\n * @dev The endpoint is EITHER/OR, ie. it will NOT support both types of native payment at a time.\n */\n function _payNative(uint256 _nativeFee) internal virtual returns (uint256 nativeFee) {\n if (msg.value != _nativeFee) revert NotEnoughNative(msg.value);\n return _nativeFee;\n }\n\n /**\n * @dev Internal function to pay the LZ token fee associated with the message.\n * @param _lzTokenFee The LZ token fee to be paid.\n *\n * @dev If the caller is trying to pay in the specified lzToken, then the lzTokenFee is passed to the endpoint.\n * @dev Any excess sent, is passed back to the specified _refundAddress in the _lzSend().\n */\n function _payLzToken(uint256 _lzTokenFee) internal virtual {\n // @dev Cannot cache the token because it is not immutable in the endpoint.\n address lzToken = endpoint.lzToken();\n if (lzToken == address(0)) revert LzTokenUnavailable();\n\n // Pay LZ token fee by sending tokens to the endpoint.\n IERC20(lzToken).safeTransferFrom(msg.sender, address(endpoint), _lzTokenFee);\n }\n}\n" + }, + "@layerzerolabs/oft-evm/contracts/interfaces/IOFT.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\nimport { MessagingReceipt, MessagingFee } from \"@layerzerolabs/oapp-evm/contracts/oapp/OAppSender.sol\";\n\n/**\n * @dev Struct representing token parameters for the OFT send() operation.\n */\nstruct SendParam {\n uint32 dstEid; // Destination endpoint ID.\n bytes32 to; // Recipient address.\n uint256 amountLD; // Amount to send in local decimals.\n uint256 minAmountLD; // Minimum amount to send in local decimals.\n bytes extraOptions; // Additional options supplied by the caller to be used in the LayerZero message.\n bytes composeMsg; // The composed message for the send() operation.\n bytes oftCmd; // The OFT command to be executed, unused in default OFT implementations.\n}\n\n/**\n * @dev Struct representing OFT limit information.\n * @dev These amounts can change dynamically and are up the specific oft implementation.\n */\nstruct OFTLimit {\n uint256 minAmountLD; // Minimum amount in local decimals that can be sent to the recipient.\n uint256 maxAmountLD; // Maximum amount in local decimals that can be sent to the recipient.\n}\n\n/**\n * @dev Struct representing OFT receipt information.\n */\nstruct OFTReceipt {\n uint256 amountSentLD; // Amount of tokens ACTUALLY debited from the sender in local decimals.\n // @dev In non-default implementations, the amountReceivedLD COULD differ from this value.\n uint256 amountReceivedLD; // Amount of tokens to be received on the remote side.\n}\n\n/**\n * @dev Struct representing OFT fee details.\n * @dev Future proof mechanism to provide a standardized way to communicate fees to things like a UI.\n */\nstruct OFTFeeDetail {\n int256 feeAmountLD; // Amount of the fee in local decimals.\n string description; // Description of the fee.\n}\n\n/**\n * @title IOFT\n * @dev Interface for the OftChain (OFT) token.\n * @dev Does not inherit ERC20 to accommodate usage by OFTAdapter as well.\n * @dev This specific interface ID is '0x02e49c2c'.\n */\ninterface IOFT {\n // Custom error messages\n error InvalidLocalDecimals();\n error SlippageExceeded(uint256 amountLD, uint256 minAmountLD);\n\n // Events\n event OFTSent(\n bytes32 indexed guid, // GUID of the OFT message.\n uint32 dstEid, // Destination Endpoint ID.\n address indexed fromAddress, // Address of the sender on the src chain.\n uint256 amountSentLD, // Amount of tokens sent in local decimals.\n uint256 amountReceivedLD // Amount of tokens received in local decimals.\n );\n event OFTReceived(\n bytes32 indexed guid, // GUID of the OFT message.\n uint32 srcEid, // Source Endpoint ID.\n address indexed toAddress, // Address of the recipient on the dst chain.\n uint256 amountReceivedLD // Amount of tokens received in local decimals.\n );\n\n /**\n * @notice Retrieves interfaceID and the version of the OFT.\n * @return interfaceId The interface ID.\n * @return version The version.\n *\n * @dev interfaceId: This specific interface ID is '0x02e49c2c'.\n * @dev version: Indicates a cross-chain compatible msg encoding with other OFTs.\n * @dev If a new feature is added to the OFT cross-chain msg encoding, the version will be incremented.\n * ie. localOFT version(x,1) CAN send messages to remoteOFT version(x,1)\n */\n function oftVersion() external view returns (bytes4 interfaceId, uint64 version);\n\n /**\n * @notice Retrieves the address of the token associated with the OFT.\n * @return token The address of the ERC20 token implementation.\n */\n function token() external view returns (address);\n\n /**\n * @notice Indicates whether the OFT contract requires approval of the 'token()' to send.\n * @return requiresApproval Needs approval of the underlying token implementation.\n *\n * @dev Allows things like wallet implementers to determine integration requirements,\n * without understanding the underlying token implementation.\n */\n function approvalRequired() external view returns (bool);\n\n /**\n * @notice Retrieves the shared decimals of the OFT.\n * @return sharedDecimals The shared decimals of the OFT.\n */\n function sharedDecimals() external view returns (uint8);\n\n /**\n * @notice Provides the fee breakdown and settings data for an OFT. Unused in the default implementation.\n * @param _sendParam The parameters for the send operation.\n * @return limit The OFT limit information.\n * @return oftFeeDetails The details of OFT fees.\n * @return receipt The OFT receipt information.\n */\n function quoteOFT(\n SendParam calldata _sendParam\n ) external view returns (OFTLimit memory, OFTFeeDetail[] memory oftFeeDetails, OFTReceipt memory);\n\n /**\n * @notice Provides a quote for the send() operation.\n * @param _sendParam The parameters for the send() operation.\n * @param _payInLzToken Flag indicating whether the caller is paying in the LZ token.\n * @return fee The calculated LayerZero messaging fee from the send() operation.\n *\n * @dev MessagingFee: LayerZero msg fee\n * - nativeFee: The native fee.\n * - lzTokenFee: The lzToken fee.\n */\n function quoteSend(SendParam calldata _sendParam, bool _payInLzToken) external view returns (MessagingFee memory);\n\n /**\n * @notice Executes the send() operation.\n * @param _sendParam The parameters for the send operation.\n * @param _fee The fee information supplied by the caller.\n * - nativeFee: The native fee.\n * - lzTokenFee: The lzToken fee.\n * @param _refundAddress The address to receive any excess funds from fees etc. on the src.\n * @return receipt The LayerZero messaging receipt from the send() operation.\n * @return oftReceipt The OFT receipt information.\n *\n * @dev MessagingReceipt: LayerZero msg receipt\n * - guid: The unique identifier for the sent message.\n * - nonce: The nonce of the sent message.\n * - fee: The LayerZero fee incurred for the message.\n */\n function send(\n SendParam calldata _sendParam,\n MessagingFee calldata _fee,\n address _refundAddress\n ) external payable returns (MessagingReceipt memory, OFTReceipt memory);\n}\n" + }, + "@openzeppelin/contracts/access/AccessControl.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (access/AccessControl.sol)\n\npragma solidity ^0.8.0;\n\nimport \"./IAccessControl.sol\";\nimport \"../utils/Context.sol\";\nimport \"../utils/Strings.sol\";\nimport \"../utils/introspection/ERC165.sol\";\n\n/**\n * @dev Contract module that allows children to implement role-based access\n * control mechanisms. This is a lightweight version that doesn't allow enumerating role\n * members except through off-chain means by accessing the contract event logs. Some\n * applications may benefit from on-chain enumerability, for those cases see\n * {AccessControlEnumerable}.\n *\n * Roles are referred to by their `bytes32` identifier. These should be exposed\n * in the external API and be unique. The best way to achieve this is by\n * using `public constant` hash digests:\n *\n * ```\n * bytes32 public constant MY_ROLE = keccak256(\"MY_ROLE\");\n * ```\n *\n * Roles can be used to represent a set of permissions. To restrict access to a\n * function call, use {hasRole}:\n *\n * ```\n * function foo() public {\n * require(hasRole(MY_ROLE, msg.sender));\n * ...\n * }\n * ```\n *\n * Roles can be granted and revoked dynamically via the {grantRole} and\n * {revokeRole} functions. Each role has an associated admin role, and only\n * accounts that have a role's admin role can call {grantRole} and {revokeRole}.\n *\n * By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means\n * that only accounts with this role will be able to grant or revoke other\n * roles. More complex role relationships can be created by using\n * {_setRoleAdmin}.\n *\n * WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to\n * grant and revoke this role. Extra precautions should be taken to secure\n * accounts that have been granted it.\n */\nabstract contract AccessControl is Context, IAccessControl, ERC165 {\n struct RoleData {\n mapping(address => bool) members;\n bytes32 adminRole;\n }\n\n mapping(bytes32 => RoleData) private _roles;\n\n bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;\n\n /**\n * @dev Modifier that checks that an account has a specific role. Reverts\n * with a standardized message including the required role.\n *\n * The format of the revert reason is given by the following regular expression:\n *\n * /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/\n *\n * _Available since v4.1._\n */\n modifier onlyRole(bytes32 role) {\n _checkRole(role, _msgSender());\n _;\n }\n\n /**\n * @dev See {IERC165-supportsInterface}.\n */\n function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {\n return interfaceId == type(IAccessControl).interfaceId || super.supportsInterface(interfaceId);\n }\n\n /**\n * @dev Returns `true` if `account` has been granted `role`.\n */\n function hasRole(bytes32 role, address account) public view override returns (bool) {\n return _roles[role].members[account];\n }\n\n /**\n * @dev Revert with a standard message if `account` is missing `role`.\n *\n * The format of the revert reason is given by the following regular expression:\n *\n * /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/\n */\n function _checkRole(bytes32 role, address account) internal view {\n if (!hasRole(role, account)) {\n revert(\n string(\n abi.encodePacked(\n \"AccessControl: account \",\n Strings.toHexString(uint160(account), 20),\n \" is missing role \",\n Strings.toHexString(uint256(role), 32)\n )\n )\n );\n }\n }\n\n /**\n * @dev Returns the admin role that controls `role`. See {grantRole} and\n * {revokeRole}.\n *\n * To change a role's admin, use {_setRoleAdmin}.\n */\n function getRoleAdmin(bytes32 role) public view override returns (bytes32) {\n return _roles[role].adminRole;\n }\n\n /**\n * @dev Grants `role` to `account`.\n *\n * If `account` had not been already granted `role`, emits a {RoleGranted}\n * event.\n *\n * Requirements:\n *\n * - the caller must have ``role``'s admin role.\n */\n function grantRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {\n _grantRole(role, account);\n }\n\n /**\n * @dev Revokes `role` from `account`.\n *\n * If `account` had been granted `role`, emits a {RoleRevoked} event.\n *\n * Requirements:\n *\n * - the caller must have ``role``'s admin role.\n */\n function revokeRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {\n _revokeRole(role, account);\n }\n\n /**\n * @dev Revokes `role` from the calling account.\n *\n * Roles are often managed via {grantRole} and {revokeRole}: this function's\n * purpose is to provide a mechanism for accounts to lose their privileges\n * if they are compromised (such as when a trusted device is misplaced).\n *\n * If the calling account had been revoked `role`, emits a {RoleRevoked}\n * event.\n *\n * Requirements:\n *\n * - the caller must be `account`.\n */\n function renounceRole(bytes32 role, address account) public virtual override {\n require(account == _msgSender(), \"AccessControl: can only renounce roles for self\");\n\n _revokeRole(role, account);\n }\n\n /**\n * @dev Grants `role` to `account`.\n *\n * If `account` had not been already granted `role`, emits a {RoleGranted}\n * event. Note that unlike {grantRole}, this function doesn't perform any\n * checks on the calling account.\n *\n * [WARNING]\n * ====\n * This function should only be called from the constructor when setting\n * up the initial roles for the system.\n *\n * Using this function in any other way is effectively circumventing the admin\n * system imposed by {AccessControl}.\n * ====\n *\n * NOTE: This function is deprecated in favor of {_grantRole}.\n */\n function _setupRole(bytes32 role, address account) internal virtual {\n _grantRole(role, account);\n }\n\n /**\n * @dev Sets `adminRole` as ``role``'s admin role.\n *\n * Emits a {RoleAdminChanged} event.\n */\n function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {\n bytes32 previousAdminRole = getRoleAdmin(role);\n _roles[role].adminRole = adminRole;\n emit RoleAdminChanged(role, previousAdminRole, adminRole);\n }\n\n /**\n * @dev Grants `role` to `account`.\n *\n * Internal function without access restriction.\n */\n function _grantRole(bytes32 role, address account) internal virtual {\n if (!hasRole(role, account)) {\n _roles[role].members[account] = true;\n emit RoleGranted(role, account, _msgSender());\n }\n }\n\n /**\n * @dev Revokes `role` from `account`.\n *\n * Internal function without access restriction.\n */\n function _revokeRole(bytes32 role, address account) internal virtual {\n if (hasRole(role, account)) {\n _roles[role].members[account] = false;\n emit RoleRevoked(role, account, _msgSender());\n }\n }\n}\n" + }, + "@openzeppelin/contracts/access/AccessControlEnumerable.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (access/AccessControlEnumerable.sol)\n\npragma solidity ^0.8.0;\n\nimport \"./IAccessControlEnumerable.sol\";\nimport \"./AccessControl.sol\";\nimport \"../utils/structs/EnumerableSet.sol\";\n\n/**\n * @dev Extension of {AccessControl} that allows enumerating the members of each role.\n */\nabstract contract AccessControlEnumerable is IAccessControlEnumerable, AccessControl {\n using EnumerableSet for EnumerableSet.AddressSet;\n\n mapping(bytes32 => EnumerableSet.AddressSet) private _roleMembers;\n\n /**\n * @dev See {IERC165-supportsInterface}.\n */\n function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {\n return interfaceId == type(IAccessControlEnumerable).interfaceId || super.supportsInterface(interfaceId);\n }\n\n /**\n * @dev Returns one of the accounts that have `role`. `index` must be a\n * value between 0 and {getRoleMemberCount}, non-inclusive.\n *\n * Role bearers are not sorted in any particular way, and their ordering may\n * change at any point.\n *\n * WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure\n * you perform all queries on the same block. See the following\n * https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post]\n * for more information.\n */\n function getRoleMember(bytes32 role, uint256 index) public view override returns (address) {\n return _roleMembers[role].at(index);\n }\n\n /**\n * @dev Returns the number of accounts that have `role`. Can be used\n * together with {getRoleMember} to enumerate all bearers of a role.\n */\n function getRoleMemberCount(bytes32 role) public view override returns (uint256) {\n return _roleMembers[role].length();\n }\n\n /**\n * @dev Overload {_grantRole} to track enumerable memberships\n */\n function _grantRole(bytes32 role, address account) internal virtual override {\n super._grantRole(role, account);\n _roleMembers[role].add(account);\n }\n\n /**\n * @dev Overload {_revokeRole} to track enumerable memberships\n */\n function _revokeRole(bytes32 role, address account) internal virtual override {\n super._revokeRole(role, account);\n _roleMembers[role].remove(account);\n }\n}\n" + }, + "@openzeppelin/contracts/access/IAccessControl.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (access/IAccessControl.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev External interface of AccessControl declared to support ERC165 detection.\n */\ninterface IAccessControl {\n /**\n * @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`\n *\n * `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite\n * {RoleAdminChanged} not being emitted signaling this.\n *\n * _Available since v3.1._\n */\n event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);\n\n /**\n * @dev Emitted when `account` is granted `role`.\n *\n * `sender` is the account that originated the contract call, an admin role\n * bearer except when using {AccessControl-_setupRole}.\n */\n event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);\n\n /**\n * @dev Emitted when `account` is revoked `role`.\n *\n * `sender` is the account that originated the contract call:\n * - if using `revokeRole`, it is the admin role bearer\n * - if using `renounceRole`, it is the role bearer (i.e. `account`)\n */\n event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);\n\n /**\n * @dev Returns `true` if `account` has been granted `role`.\n */\n function hasRole(bytes32 role, address account) external view returns (bool);\n\n /**\n * @dev Returns the admin role that controls `role`. See {grantRole} and\n * {revokeRole}.\n *\n * To change a role's admin, use {AccessControl-_setRoleAdmin}.\n */\n function getRoleAdmin(bytes32 role) external view returns (bytes32);\n\n /**\n * @dev Grants `role` to `account`.\n *\n * If `account` had not been already granted `role`, emits a {RoleGranted}\n * event.\n *\n * Requirements:\n *\n * - the caller must have ``role``'s admin role.\n */\n function grantRole(bytes32 role, address account) external;\n\n /**\n * @dev Revokes `role` from `account`.\n *\n * If `account` had been granted `role`, emits a {RoleRevoked} event.\n *\n * Requirements:\n *\n * - the caller must have ``role``'s admin role.\n */\n function revokeRole(bytes32 role, address account) external;\n\n /**\n * @dev Revokes `role` from the calling account.\n *\n * Roles are often managed via {grantRole} and {revokeRole}: this function's\n * purpose is to provide a mechanism for accounts to lose their privileges\n * if they are compromised (such as when a trusted device is misplaced).\n *\n * If the calling account had been granted `role`, emits a {RoleRevoked}\n * event.\n *\n * Requirements:\n *\n * - the caller must be `account`.\n */\n function renounceRole(bytes32 role, address account) external;\n}\n" + }, + "@openzeppelin/contracts/access/IAccessControlEnumerable.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (access/IAccessControlEnumerable.sol)\n\npragma solidity ^0.8.0;\n\nimport \"./IAccessControl.sol\";\n\n/**\n * @dev External interface of AccessControlEnumerable declared to support ERC165 detection.\n */\ninterface IAccessControlEnumerable is IAccessControl {\n /**\n * @dev Returns one of the accounts that have `role`. `index` must be a\n * value between 0 and {getRoleMemberCount}, non-inclusive.\n *\n * Role bearers are not sorted in any particular way, and their ordering may\n * change at any point.\n *\n * WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure\n * you perform all queries on the same block. See the following\n * https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post]\n * for more information.\n */\n function getRoleMember(bytes32 role, uint256 index) external view returns (address);\n\n /**\n * @dev Returns the number of accounts that have `role`. Can be used\n * together with {getRoleMember} to enumerate all bearers of a role.\n */\n function getRoleMemberCount(bytes32 role) external view returns (uint256);\n}\n" + }, + "@openzeppelin/contracts/access/Ownable.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (access/Ownable.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../utils/Context.sol\";\n\n/**\n * @dev Contract module which provides a basic access control mechanism, where\n * there is an account (an owner) that can be granted exclusive access to\n * specific functions.\n *\n * By default, the owner account will be the one that deploys the contract. This\n * can later be changed with {transferOwnership}.\n *\n * This module is used through inheritance. It will make available the modifier\n * `onlyOwner`, which can be applied to your functions to restrict their use to\n * the owner.\n */\nabstract contract Ownable is Context {\n address private _owner;\n\n event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\n\n /**\n * @dev Initializes the contract setting the deployer as the initial owner.\n */\n constructor() {\n _transferOwnership(_msgSender());\n }\n\n /**\n * @dev Returns the address of the current owner.\n */\n function owner() public view virtual returns (address) {\n return _owner;\n }\n\n /**\n * @dev Throws if called by any account other than the owner.\n */\n modifier onlyOwner() {\n require(owner() == _msgSender(), \"Ownable: caller is not the owner\");\n _;\n }\n\n /**\n * @dev Leaves the contract without owner. It will not be possible to call\n * `onlyOwner` functions anymore. Can only be called by the current owner.\n *\n * NOTE: Renouncing ownership will leave the contract without an owner,\n * thereby removing any functionality that is only available to the owner.\n */\n function renounceOwnership() public virtual onlyOwner {\n _transferOwnership(address(0));\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\n * Can only be called by the current owner.\n */\n function transferOwnership(address newOwner) public virtual onlyOwner {\n require(newOwner != address(0), \"Ownable: new owner is the zero address\");\n _transferOwnership(newOwner);\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\n * Internal function without access restriction.\n */\n function _transferOwnership(address newOwner) internal virtual {\n address oldOwner = _owner;\n _owner = newOwner;\n emit OwnershipTransferred(oldOwner, newOwner);\n }\n}\n" + }, + "@openzeppelin/contracts/security/Pausable.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (security/Pausable.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../utils/Context.sol\";\n\n/**\n * @dev Contract module which allows children to implement an emergency stop\n * mechanism that can be triggered by an authorized account.\n *\n * This module is used through inheritance. It will make available the\n * modifiers `whenNotPaused` and `whenPaused`, which can be applied to\n * the functions of your contract. Note that they will not be pausable by\n * simply including this module, only once the modifiers are put in place.\n */\nabstract contract Pausable is Context {\n /**\n * @dev Emitted when the pause is triggered by `account`.\n */\n event Paused(address account);\n\n /**\n * @dev Emitted when the pause is lifted by `account`.\n */\n event Unpaused(address account);\n\n bool private _paused;\n\n /**\n * @dev Initializes the contract in unpaused state.\n */\n constructor() {\n _paused = false;\n }\n\n /**\n * @dev Returns true if the contract is paused, and false otherwise.\n */\n function paused() public view virtual returns (bool) {\n return _paused;\n }\n\n /**\n * @dev Modifier to make a function callable only when the contract is not paused.\n *\n * Requirements:\n *\n * - The contract must not be paused.\n */\n modifier whenNotPaused() {\n require(!paused(), \"Pausable: paused\");\n _;\n }\n\n /**\n * @dev Modifier to make a function callable only when the contract is paused.\n *\n * Requirements:\n *\n * - The contract must be paused.\n */\n modifier whenPaused() {\n require(paused(), \"Pausable: not paused\");\n _;\n }\n\n /**\n * @dev Triggers stopped state.\n *\n * Requirements:\n *\n * - The contract must not be paused.\n */\n function _pause() internal virtual whenNotPaused {\n _paused = true;\n emit Paused(_msgSender());\n }\n\n /**\n * @dev Returns to normal state.\n *\n * Requirements:\n *\n * - The contract must be paused.\n */\n function _unpause() internal virtual whenPaused {\n _paused = false;\n emit Unpaused(_msgSender());\n }\n}\n" + }, + "@openzeppelin/contracts/token/ERC20/ERC20.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (token/ERC20/ERC20.sol)\n\npragma solidity ^0.8.0;\n\nimport \"./IERC20.sol\";\nimport \"./extensions/IERC20Metadata.sol\";\nimport \"../../utils/Context.sol\";\n\n/**\n * @dev Implementation of the {IERC20} interface.\n *\n * This implementation is agnostic to the way tokens are created. This means\n * that a supply mechanism has to be added in a derived contract using {_mint}.\n * For a generic mechanism see {ERC20PresetMinterPauser}.\n *\n * TIP: For a detailed writeup see our guide\n * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How\n * to implement supply mechanisms].\n *\n * We have followed general OpenZeppelin Contracts guidelines: functions revert\n * instead returning `false` on failure. This behavior is nonetheless\n * conventional and does not conflict with the expectations of ERC20\n * applications.\n *\n * Additionally, an {Approval} event is emitted on calls to {transferFrom}.\n * This allows applications to reconstruct the allowance for all accounts just\n * by listening to said events. Other implementations of the EIP may not emit\n * these events, as it isn't required by the specification.\n *\n * Finally, the non-standard {decreaseAllowance} and {increaseAllowance}\n * functions have been added to mitigate the well-known issues around setting\n * allowances. See {IERC20-approve}.\n */\ncontract ERC20 is Context, IERC20, IERC20Metadata {\n mapping(address => uint256) private _balances;\n\n mapping(address => mapping(address => uint256)) private _allowances;\n\n uint256 private _totalSupply;\n\n string private _name;\n string private _symbol;\n\n /**\n * @dev Sets the values for {name} and {symbol}.\n *\n * The default value of {decimals} is 18. To select a different value for\n * {decimals} you should overload it.\n *\n * All two of these values are immutable: they can only be set once during\n * construction.\n */\n constructor(string memory name_, string memory symbol_) {\n _name = name_;\n _symbol = symbol_;\n }\n\n /**\n * @dev Returns the name of the token.\n */\n function name() public view virtual override returns (string memory) {\n return _name;\n }\n\n /**\n * @dev Returns the symbol of the token, usually a shorter version of the\n * name.\n */\n function symbol() public view virtual override returns (string memory) {\n return _symbol;\n }\n\n /**\n * @dev Returns the number of decimals used to get its user representation.\n * For example, if `decimals` equals `2`, a balance of `505` tokens should\n * be displayed to a user as `5.05` (`505 / 10 ** 2`).\n *\n * Tokens usually opt for a value of 18, imitating the relationship between\n * Ether and Wei. This is the value {ERC20} uses, unless this function is\n * overridden;\n *\n * NOTE: This information is only used for _display_ purposes: it in\n * no way affects any of the arithmetic of the contract, including\n * {IERC20-balanceOf} and {IERC20-transfer}.\n */\n function decimals() public view virtual override returns (uint8) {\n return 18;\n }\n\n /**\n * @dev See {IERC20-totalSupply}.\n */\n function totalSupply() public view virtual override returns (uint256) {\n return _totalSupply;\n }\n\n /**\n * @dev See {IERC20-balanceOf}.\n */\n function balanceOf(address account) public view virtual override returns (uint256) {\n return _balances[account];\n }\n\n /**\n * @dev See {IERC20-transfer}.\n *\n * Requirements:\n *\n * - `recipient` cannot be the zero address.\n * - the caller must have a balance of at least `amount`.\n */\n function transfer(address recipient, uint256 amount) public virtual override returns (bool) {\n _transfer(_msgSender(), recipient, amount);\n return true;\n }\n\n /**\n * @dev See {IERC20-allowance}.\n */\n function allowance(address owner, address spender) public view virtual override returns (uint256) {\n return _allowances[owner][spender];\n }\n\n /**\n * @dev See {IERC20-approve}.\n *\n * Requirements:\n *\n * - `spender` cannot be the zero address.\n */\n function approve(address spender, uint256 amount) public virtual override returns (bool) {\n _approve(_msgSender(), spender, amount);\n return true;\n }\n\n /**\n * @dev See {IERC20-transferFrom}.\n *\n * Emits an {Approval} event indicating the updated allowance. This is not\n * required by the EIP. See the note at the beginning of {ERC20}.\n *\n * Requirements:\n *\n * - `sender` and `recipient` cannot be the zero address.\n * - `sender` must have a balance of at least `amount`.\n * - the caller must have allowance for ``sender``'s tokens of at least\n * `amount`.\n */\n function transferFrom(\n address sender,\n address recipient,\n uint256 amount\n ) public virtual override returns (bool) {\n _transfer(sender, recipient, amount);\n\n uint256 currentAllowance = _allowances[sender][_msgSender()];\n require(currentAllowance >= amount, \"ERC20: transfer amount exceeds allowance\");\n unchecked {\n _approve(sender, _msgSender(), currentAllowance - amount);\n }\n\n return true;\n }\n\n /**\n * @dev Atomically increases the allowance granted to `spender` by the caller.\n *\n * This is an alternative to {approve} that can be used as a mitigation for\n * problems described in {IERC20-approve}.\n *\n * Emits an {Approval} event indicating the updated allowance.\n *\n * Requirements:\n *\n * - `spender` cannot be the zero address.\n */\n function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {\n _approve(_msgSender(), spender, _allowances[_msgSender()][spender] + addedValue);\n return true;\n }\n\n /**\n * @dev Atomically decreases the allowance granted to `spender` by the caller.\n *\n * This is an alternative to {approve} that can be used as a mitigation for\n * problems described in {IERC20-approve}.\n *\n * Emits an {Approval} event indicating the updated allowance.\n *\n * Requirements:\n *\n * - `spender` cannot be the zero address.\n * - `spender` must have allowance for the caller of at least\n * `subtractedValue`.\n */\n function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {\n uint256 currentAllowance = _allowances[_msgSender()][spender];\n require(currentAllowance >= subtractedValue, \"ERC20: decreased allowance below zero\");\n unchecked {\n _approve(_msgSender(), spender, currentAllowance - subtractedValue);\n }\n\n return true;\n }\n\n /**\n * @dev Moves `amount` of tokens from `sender` to `recipient`.\n *\n * This internal function is equivalent to {transfer}, and can be used to\n * e.g. implement automatic token fees, slashing mechanisms, etc.\n *\n * Emits a {Transfer} event.\n *\n * Requirements:\n *\n * - `sender` cannot be the zero address.\n * - `recipient` cannot be the zero address.\n * - `sender` must have a balance of at least `amount`.\n */\n function _transfer(\n address sender,\n address recipient,\n uint256 amount\n ) internal virtual {\n require(sender != address(0), \"ERC20: transfer from the zero address\");\n require(recipient != address(0), \"ERC20: transfer to the zero address\");\n\n _beforeTokenTransfer(sender, recipient, amount);\n\n uint256 senderBalance = _balances[sender];\n require(senderBalance >= amount, \"ERC20: transfer amount exceeds balance\");\n unchecked {\n _balances[sender] = senderBalance - amount;\n }\n _balances[recipient] += amount;\n\n emit Transfer(sender, recipient, amount);\n\n _afterTokenTransfer(sender, recipient, amount);\n }\n\n /** @dev Creates `amount` tokens and assigns them to `account`, increasing\n * the total supply.\n *\n * Emits a {Transfer} event with `from` set to the zero address.\n *\n * Requirements:\n *\n * - `account` cannot be the zero address.\n */\n function _mint(address account, uint256 amount) internal virtual {\n require(account != address(0), \"ERC20: mint to the zero address\");\n\n _beforeTokenTransfer(address(0), account, amount);\n\n _totalSupply += amount;\n _balances[account] += amount;\n emit Transfer(address(0), account, amount);\n\n _afterTokenTransfer(address(0), account, amount);\n }\n\n /**\n * @dev Destroys `amount` tokens from `account`, reducing the\n * total supply.\n *\n * Emits a {Transfer} event with `to` set to the zero address.\n *\n * Requirements:\n *\n * - `account` cannot be the zero address.\n * - `account` must have at least `amount` tokens.\n */\n function _burn(address account, uint256 amount) internal virtual {\n require(account != address(0), \"ERC20: burn from the zero address\");\n\n _beforeTokenTransfer(account, address(0), amount);\n\n uint256 accountBalance = _balances[account];\n require(accountBalance >= amount, \"ERC20: burn amount exceeds balance\");\n unchecked {\n _balances[account] = accountBalance - amount;\n }\n _totalSupply -= amount;\n\n emit Transfer(account, address(0), amount);\n\n _afterTokenTransfer(account, address(0), amount);\n }\n\n /**\n * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.\n *\n * This internal function is equivalent to `approve`, and can be used to\n * e.g. set automatic allowances for certain subsystems, etc.\n *\n * Emits an {Approval} event.\n *\n * Requirements:\n *\n * - `owner` cannot be the zero address.\n * - `spender` cannot be the zero address.\n */\n function _approve(\n address owner,\n address spender,\n uint256 amount\n ) internal virtual {\n require(owner != address(0), \"ERC20: approve from the zero address\");\n require(spender != address(0), \"ERC20: approve to the zero address\");\n\n _allowances[owner][spender] = amount;\n emit Approval(owner, spender, amount);\n }\n\n /**\n * @dev Hook that is called before any transfer of tokens. This includes\n * minting and burning.\n *\n * Calling conditions:\n *\n * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens\n * will be transferred to `to`.\n * - when `from` is zero, `amount` tokens will be minted for `to`.\n * - when `to` is zero, `amount` of ``from``'s tokens will be burned.\n * - `from` and `to` are never both zero.\n *\n * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].\n */\n function _beforeTokenTransfer(\n address from,\n address to,\n uint256 amount\n ) internal virtual {}\n\n /**\n * @dev Hook that is called after any transfer of tokens. This includes\n * minting and burning.\n *\n * Calling conditions:\n *\n * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens\n * has been transferred to `to`.\n * - when `from` is zero, `amount` tokens have been minted for `to`.\n * - when `to` is zero, `amount` of ``from``'s tokens have been burned.\n * - `from` and `to` are never both zero.\n *\n * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].\n */\n function _afterTokenTransfer(\n address from,\n address to,\n uint256 amount\n ) internal virtual {}\n}\n" + }, + "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../IERC20.sol\";\n\n/**\n * @dev Interface for the optional metadata functions from the ERC20 standard.\n *\n * _Available since v4.1._\n */\ninterface IERC20Metadata is IERC20 {\n /**\n * @dev Returns the name of the token.\n */\n function name() external view returns (string memory);\n\n /**\n * @dev Returns the symbol of the token.\n */\n function symbol() external view returns (string memory);\n\n /**\n * @dev Returns the decimals places of the token.\n */\n function decimals() external view returns (uint8);\n}\n" + }, + "@openzeppelin/contracts/token/ERC20/IERC20.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (token/ERC20/IERC20.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Interface of the ERC20 standard as defined in the EIP.\n */\ninterface IERC20 {\n /**\n * @dev Returns the amount of tokens in existence.\n */\n function totalSupply() external view returns (uint256);\n\n /**\n * @dev Returns the amount of tokens owned by `account`.\n */\n function balanceOf(address account) external view returns (uint256);\n\n /**\n * @dev Moves `amount` tokens from the caller's account to `recipient`.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transfer(address recipient, uint256 amount) external returns (bool);\n\n /**\n * @dev Returns the remaining number of tokens that `spender` will be\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\n * zero by default.\n *\n * This value changes when {approve} or {transferFrom} are called.\n */\n function allowance(address owner, address spender) external view returns (uint256);\n\n /**\n * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\n * that someone may use both the old and the new allowance by unfortunate\n * transaction ordering. One possible solution to mitigate this race\n * condition is to first reduce the spender's allowance to 0 and set the\n * desired value afterwards:\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\n *\n * Emits an {Approval} event.\n */\n function approve(address spender, uint256 amount) external returns (bool);\n\n /**\n * @dev Moves `amount` tokens from `sender` to `recipient` using the\n * allowance mechanism. `amount` is then deducted from the caller's\n * allowance.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transferFrom(\n address sender,\n address recipient,\n uint256 amount\n ) external returns (bool);\n\n /**\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\n * another (`to`).\n *\n * Note that `value` may be zero.\n */\n event Transfer(address indexed from, address indexed to, uint256 value);\n\n /**\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\n * a call to {approve}. `value` is the new allowance.\n */\n event Approval(address indexed owner, address indexed spender, uint256 value);\n}\n" + }, + "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (token/ERC20/utils/SafeERC20.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../IERC20.sol\";\nimport \"../../../utils/Address.sol\";\n\n/**\n * @title SafeERC20\n * @dev Wrappers around ERC20 operations that throw on failure (when the token\n * contract returns false). Tokens that return no value (and instead revert or\n * throw on failure) are also supported, non-reverting calls are assumed to be\n * successful.\n * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,\n * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.\n */\nlibrary SafeERC20 {\n using Address for address;\n\n function safeTransfer(\n IERC20 token,\n address to,\n uint256 value\n ) internal {\n _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));\n }\n\n function safeTransferFrom(\n IERC20 token,\n address from,\n address to,\n uint256 value\n ) internal {\n _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));\n }\n\n /**\n * @dev Deprecated. This function has issues similar to the ones found in\n * {IERC20-approve}, and its usage is discouraged.\n *\n * Whenever possible, use {safeIncreaseAllowance} and\n * {safeDecreaseAllowance} instead.\n */\n function safeApprove(\n IERC20 token,\n address spender,\n uint256 value\n ) internal {\n // safeApprove should only be called when setting an initial allowance,\n // or when resetting it to zero. To increase and decrease it, use\n // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'\n require(\n (value == 0) || (token.allowance(address(this), spender) == 0),\n \"SafeERC20: approve from non-zero to non-zero allowance\"\n );\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));\n }\n\n function safeIncreaseAllowance(\n IERC20 token,\n address spender,\n uint256 value\n ) internal {\n uint256 newAllowance = token.allowance(address(this), spender) + value;\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));\n }\n\n function safeDecreaseAllowance(\n IERC20 token,\n address spender,\n uint256 value\n ) internal {\n unchecked {\n uint256 oldAllowance = token.allowance(address(this), spender);\n require(oldAllowance >= value, \"SafeERC20: decreased allowance below zero\");\n uint256 newAllowance = oldAllowance - value;\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));\n }\n }\n\n /**\n * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement\n * on the return value: the return value is optional (but if data is returned, it must not be false).\n * @param token The token targeted by the call.\n * @param data The call data (encoded using abi.encode or one of its variants).\n */\n function _callOptionalReturn(IERC20 token, bytes memory data) private {\n // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since\n // we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that\n // the target address contains contract code and also asserts for success in the low-level call.\n\n bytes memory returndata = address(token).functionCall(data, \"SafeERC20: low-level call failed\");\n if (returndata.length > 0) {\n // Return data is optional\n require(abi.decode(returndata, (bool)), \"SafeERC20: ERC20 operation did not succeed\");\n }\n }\n}\n" + }, + "@openzeppelin/contracts/utils/Address.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (utils/Address.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Collection of functions related to the address type\n */\nlibrary Address {\n /**\n * @dev Returns true if `account` is a contract.\n *\n * [IMPORTANT]\n * ====\n * It is unsafe to assume that an address for which this function returns\n * false is an externally-owned account (EOA) and not a contract.\n *\n * Among others, `isContract` will return false for the following\n * types of addresses:\n *\n * - an externally-owned account\n * - a contract in construction\n * - an address where a contract will be created\n * - an address where a contract lived, but was destroyed\n * ====\n */\n function isContract(address account) internal view returns (bool) {\n // This method relies on extcodesize, which returns 0 for contracts in\n // construction, since the code is only stored at the end of the\n // constructor execution.\n\n uint256 size;\n assembly {\n size := extcodesize(account)\n }\n return size > 0;\n }\n\n /**\n * @dev Replacement for Solidity's `transfer`: sends `amount` wei to\n * `recipient`, forwarding all available gas and reverting on errors.\n *\n * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost\n * of certain opcodes, possibly making contracts go over the 2300 gas limit\n * imposed by `transfer`, making them unable to receive funds via\n * `transfer`. {sendValue} removes this limitation.\n *\n * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].\n *\n * IMPORTANT: because control is transferred to `recipient`, care must be\n * taken to not create reentrancy vulnerabilities. Consider using\n * {ReentrancyGuard} or the\n * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].\n */\n function sendValue(address payable recipient, uint256 amount) internal {\n require(address(this).balance >= amount, \"Address: insufficient balance\");\n\n (bool success, ) = recipient.call{value: amount}(\"\");\n require(success, \"Address: unable to send value, recipient may have reverted\");\n }\n\n /**\n * @dev Performs a Solidity function call using a low level `call`. A\n * plain `call` is an unsafe replacement for a function call: use this\n * function instead.\n *\n * If `target` reverts with a revert reason, it is bubbled up by this\n * function (like regular Solidity function calls).\n *\n * Returns the raw returned data. To convert to the expected return value,\n * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].\n *\n * Requirements:\n *\n * - `target` must be a contract.\n * - calling `target` with `data` must not revert.\n *\n * _Available since v3.1._\n */\n function functionCall(address target, bytes memory data) internal returns (bytes memory) {\n return functionCall(target, data, \"Address: low-level call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with\n * `errorMessage` as a fallback revert reason when `target` reverts.\n *\n * _Available since v3.1._\n */\n function functionCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal returns (bytes memory) {\n return functionCallWithValue(target, data, 0, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but also transferring `value` wei to `target`.\n *\n * Requirements:\n *\n * - the calling contract must have an ETH balance of at least `value`.\n * - the called Solidity function must be `payable`.\n *\n * _Available since v3.1._\n */\n function functionCallWithValue(\n address target,\n bytes memory data,\n uint256 value\n ) internal returns (bytes memory) {\n return functionCallWithValue(target, data, value, \"Address: low-level call with value failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but\n * with `errorMessage` as a fallback revert reason when `target` reverts.\n *\n * _Available since v3.1._\n */\n function functionCallWithValue(\n address target,\n bytes memory data,\n uint256 value,\n string memory errorMessage\n ) internal returns (bytes memory) {\n require(address(this).balance >= value, \"Address: insufficient balance for call\");\n require(isContract(target), \"Address: call to non-contract\");\n\n (bool success, bytes memory returndata) = target.call{value: value}(data);\n return verifyCallResult(success, returndata, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but performing a static call.\n *\n * _Available since v3.3._\n */\n function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {\n return functionStaticCall(target, data, \"Address: low-level static call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\n * but performing a static call.\n *\n * _Available since v3.3._\n */\n function functionStaticCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal view returns (bytes memory) {\n require(isContract(target), \"Address: static call to non-contract\");\n\n (bool success, bytes memory returndata) = target.staticcall(data);\n return verifyCallResult(success, returndata, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but performing a delegate call.\n *\n * _Available since v3.4._\n */\n function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {\n return functionDelegateCall(target, data, \"Address: low-level delegate call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\n * but performing a delegate call.\n *\n * _Available since v3.4._\n */\n function functionDelegateCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal returns (bytes memory) {\n require(isContract(target), \"Address: delegate call to non-contract\");\n\n (bool success, bytes memory returndata) = target.delegatecall(data);\n return verifyCallResult(success, returndata, errorMessage);\n }\n\n /**\n * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the\n * revert reason using the provided one.\n *\n * _Available since v4.3._\n */\n function verifyCallResult(\n bool success,\n bytes memory returndata,\n string memory errorMessage\n ) internal pure returns (bytes memory) {\n if (success) {\n return returndata;\n } else {\n // Look for revert reason and bubble it up if present\n if (returndata.length > 0) {\n // The easiest way to bubble the revert reason is using memory via assembly\n\n assembly {\n let returndata_size := mload(returndata)\n revert(add(32, returndata), returndata_size)\n }\n } else {\n revert(errorMessage);\n }\n }\n }\n}\n" + }, + "@openzeppelin/contracts/utils/Context.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Provides information about the current execution context, including the\n * sender of the transaction and its data. While these are generally available\n * via msg.sender and msg.data, they should not be accessed in such a direct\n * manner, since when dealing with meta-transactions the account sending and\n * paying for execution may not be the actual sender (as far as an application\n * is concerned).\n *\n * This contract is only required for intermediate, library-like contracts.\n */\nabstract contract Context {\n function _msgSender() internal view virtual returns (address) {\n return msg.sender;\n }\n\n function _msgData() internal view virtual returns (bytes calldata) {\n return msg.data;\n }\n}\n" + }, + "@openzeppelin/contracts/utils/introspection/ERC165.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol)\n\npragma solidity ^0.8.0;\n\nimport \"./IERC165.sol\";\n\n/**\n * @dev Implementation of the {IERC165} interface.\n *\n * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check\n * for the additional interface id that will be supported. For example:\n *\n * ```solidity\n * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {\n * return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);\n * }\n * ```\n *\n * Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation.\n */\nabstract contract ERC165 is IERC165 {\n /**\n * @dev See {IERC165-supportsInterface}.\n */\n function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {\n return interfaceId == type(IERC165).interfaceId;\n }\n}\n" + }, + "@openzeppelin/contracts/utils/introspection/IERC165.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Interface of the ERC165 standard, as defined in the\n * https://eips.ethereum.org/EIPS/eip-165[EIP].\n *\n * Implementers can declare support of contract interfaces, which can then be\n * queried by others ({ERC165Checker}).\n *\n * For an implementation, see {ERC165}.\n */\ninterface IERC165 {\n /**\n * @dev Returns true if this contract implements the interface defined by\n * `interfaceId`. See the corresponding\n * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]\n * to learn more about how these ids are created.\n *\n * This function call must use less than 30 000 gas.\n */\n function supportsInterface(bytes4 interfaceId) external view returns (bool);\n}\n" + }, + "@openzeppelin/contracts/utils/math/Math.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (utils/math/Math.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Standard math utilities missing in the Solidity language.\n */\nlibrary Math {\n /**\n * @dev Returns the largest of two numbers.\n */\n function max(uint256 a, uint256 b) internal pure returns (uint256) {\n return a >= b ? a : b;\n }\n\n /**\n * @dev Returns the smallest of two numbers.\n */\n function min(uint256 a, uint256 b) internal pure returns (uint256) {\n return a < b ? a : b;\n }\n\n /**\n * @dev Returns the average of two numbers. The result is rounded towards\n * zero.\n */\n function average(uint256 a, uint256 b) internal pure returns (uint256) {\n // (a + b) / 2 can overflow.\n return (a & b) + (a ^ b) / 2;\n }\n\n /**\n * @dev Returns the ceiling of the division of two numbers.\n *\n * This differs from standard division with `/` in that it rounds up instead\n * of rounding down.\n */\n function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {\n // (a + b - 1) / b can overflow on addition, so we distribute.\n return a / b + (a % b == 0 ? 0 : 1);\n }\n}\n" + }, + "@openzeppelin/contracts/utils/math/SafeCast.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (utils/math/SafeCast.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Wrappers over Solidity's uintXX/intXX casting operators with added overflow\n * checks.\n *\n * Downcasting from uint256/int256 in Solidity does not revert on overflow. This can\n * easily result in undesired exploitation or bugs, since developers usually\n * assume that overflows raise errors. `SafeCast` restores this intuition by\n * reverting the transaction when such an operation overflows.\n *\n * Using this library instead of the unchecked operations eliminates an entire\n * class of bugs, so it's recommended to use it always.\n *\n * Can be combined with {SafeMath} and {SignedSafeMath} to extend it to smaller types, by performing\n * all math on `uint256` and `int256` and then downcasting.\n */\nlibrary SafeCast {\n /**\n * @dev Returns the downcasted uint224 from uint256, reverting on\n * overflow (when the input is greater than largest uint224).\n *\n * Counterpart to Solidity's `uint224` operator.\n *\n * Requirements:\n *\n * - input must fit into 224 bits\n */\n function toUint224(uint256 value) internal pure returns (uint224) {\n require(value <= type(uint224).max, \"SafeCast: value doesn't fit in 224 bits\");\n return uint224(value);\n }\n\n /**\n * @dev Returns the downcasted uint128 from uint256, reverting on\n * overflow (when the input is greater than largest uint128).\n *\n * Counterpart to Solidity's `uint128` operator.\n *\n * Requirements:\n *\n * - input must fit into 128 bits\n */\n function toUint128(uint256 value) internal pure returns (uint128) {\n require(value <= type(uint128).max, \"SafeCast: value doesn't fit in 128 bits\");\n return uint128(value);\n }\n\n /**\n * @dev Returns the downcasted uint96 from uint256, reverting on\n * overflow (when the input is greater than largest uint96).\n *\n * Counterpart to Solidity's `uint96` operator.\n *\n * Requirements:\n *\n * - input must fit into 96 bits\n */\n function toUint96(uint256 value) internal pure returns (uint96) {\n require(value <= type(uint96).max, \"SafeCast: value doesn't fit in 96 bits\");\n return uint96(value);\n }\n\n /**\n * @dev Returns the downcasted uint64 from uint256, reverting on\n * overflow (when the input is greater than largest uint64).\n *\n * Counterpart to Solidity's `uint64` operator.\n *\n * Requirements:\n *\n * - input must fit into 64 bits\n */\n function toUint64(uint256 value) internal pure returns (uint64) {\n require(value <= type(uint64).max, \"SafeCast: value doesn't fit in 64 bits\");\n return uint64(value);\n }\n\n /**\n * @dev Returns the downcasted uint32 from uint256, reverting on\n * overflow (when the input is greater than largest uint32).\n *\n * Counterpart to Solidity's `uint32` operator.\n *\n * Requirements:\n *\n * - input must fit into 32 bits\n */\n function toUint32(uint256 value) internal pure returns (uint32) {\n require(value <= type(uint32).max, \"SafeCast: value doesn't fit in 32 bits\");\n return uint32(value);\n }\n\n /**\n * @dev Returns the downcasted uint16 from uint256, reverting on\n * overflow (when the input is greater than largest uint16).\n *\n * Counterpart to Solidity's `uint16` operator.\n *\n * Requirements:\n *\n * - input must fit into 16 bits\n */\n function toUint16(uint256 value) internal pure returns (uint16) {\n require(value <= type(uint16).max, \"SafeCast: value doesn't fit in 16 bits\");\n return uint16(value);\n }\n\n /**\n * @dev Returns the downcasted uint8 from uint256, reverting on\n * overflow (when the input is greater than largest uint8).\n *\n * Counterpart to Solidity's `uint8` operator.\n *\n * Requirements:\n *\n * - input must fit into 8 bits.\n */\n function toUint8(uint256 value) internal pure returns (uint8) {\n require(value <= type(uint8).max, \"SafeCast: value doesn't fit in 8 bits\");\n return uint8(value);\n }\n\n /**\n * @dev Converts a signed int256 into an unsigned uint256.\n *\n * Requirements:\n *\n * - input must be greater than or equal to 0.\n */\n function toUint256(int256 value) internal pure returns (uint256) {\n require(value >= 0, \"SafeCast: value must be positive\");\n return uint256(value);\n }\n\n /**\n * @dev Returns the downcasted int128 from int256, reverting on\n * overflow (when the input is less than smallest int128 or\n * greater than largest int128).\n *\n * Counterpart to Solidity's `int128` operator.\n *\n * Requirements:\n *\n * - input must fit into 128 bits\n *\n * _Available since v3.1._\n */\n function toInt128(int256 value) internal pure returns (int128) {\n require(value >= type(int128).min && value <= type(int128).max, \"SafeCast: value doesn't fit in 128 bits\");\n return int128(value);\n }\n\n /**\n * @dev Returns the downcasted int64 from int256, reverting on\n * overflow (when the input is less than smallest int64 or\n * greater than largest int64).\n *\n * Counterpart to Solidity's `int64` operator.\n *\n * Requirements:\n *\n * - input must fit into 64 bits\n *\n * _Available since v3.1._\n */\n function toInt64(int256 value) internal pure returns (int64) {\n require(value >= type(int64).min && value <= type(int64).max, \"SafeCast: value doesn't fit in 64 bits\");\n return int64(value);\n }\n\n /**\n * @dev Returns the downcasted int32 from int256, reverting on\n * overflow (when the input is less than smallest int32 or\n * greater than largest int32).\n *\n * Counterpart to Solidity's `int32` operator.\n *\n * Requirements:\n *\n * - input must fit into 32 bits\n *\n * _Available since v3.1._\n */\n function toInt32(int256 value) internal pure returns (int32) {\n require(value >= type(int32).min && value <= type(int32).max, \"SafeCast: value doesn't fit in 32 bits\");\n return int32(value);\n }\n\n /**\n * @dev Returns the downcasted int16 from int256, reverting on\n * overflow (when the input is less than smallest int16 or\n * greater than largest int16).\n *\n * Counterpart to Solidity's `int16` operator.\n *\n * Requirements:\n *\n * - input must fit into 16 bits\n *\n * _Available since v3.1._\n */\n function toInt16(int256 value) internal pure returns (int16) {\n require(value >= type(int16).min && value <= type(int16).max, \"SafeCast: value doesn't fit in 16 bits\");\n return int16(value);\n }\n\n /**\n * @dev Returns the downcasted int8 from int256, reverting on\n * overflow (when the input is less than smallest int8 or\n * greater than largest int8).\n *\n * Counterpart to Solidity's `int8` operator.\n *\n * Requirements:\n *\n * - input must fit into 8 bits.\n *\n * _Available since v3.1._\n */\n function toInt8(int256 value) internal pure returns (int8) {\n require(value >= type(int8).min && value <= type(int8).max, \"SafeCast: value doesn't fit in 8 bits\");\n return int8(value);\n }\n\n /**\n * @dev Converts an unsigned uint256 into a signed int256.\n *\n * Requirements:\n *\n * - input must be less than or equal to maxInt256.\n */\n function toInt256(uint256 value) internal pure returns (int256) {\n // Note: Unsafe cast below is okay because `type(int256).max` is guaranteed to be positive\n require(value <= uint256(type(int256).max), \"SafeCast: value doesn't fit in an int256\");\n return int256(value);\n }\n}\n" + }, + "@openzeppelin/contracts/utils/math/SafeMath.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (utils/math/SafeMath.sol)\n\npragma solidity ^0.8.0;\n\n// CAUTION\n// This version of SafeMath should only be used with Solidity 0.8 or later,\n// because it relies on the compiler's built in overflow checks.\n\n/**\n * @dev Wrappers over Solidity's arithmetic operations.\n *\n * NOTE: `SafeMath` is generally not needed starting with Solidity 0.8, since the compiler\n * now has built in overflow checking.\n */\nlibrary SafeMath {\n /**\n * @dev Returns the addition of two unsigned integers, with an overflow flag.\n *\n * _Available since v3.4._\n */\n function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {\n unchecked {\n uint256 c = a + b;\n if (c < a) return (false, 0);\n return (true, c);\n }\n }\n\n /**\n * @dev Returns the substraction of two unsigned integers, with an overflow flag.\n *\n * _Available since v3.4._\n */\n function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {\n unchecked {\n if (b > a) return (false, 0);\n return (true, a - b);\n }\n }\n\n /**\n * @dev Returns the multiplication of two unsigned integers, with an overflow flag.\n *\n * _Available since v3.4._\n */\n function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {\n unchecked {\n // Gas optimization: this is cheaper than requiring 'a' not being zero, but the\n // benefit is lost if 'b' is also tested.\n // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522\n if (a == 0) return (true, 0);\n uint256 c = a * b;\n if (c / a != b) return (false, 0);\n return (true, c);\n }\n }\n\n /**\n * @dev Returns the division of two unsigned integers, with a division by zero flag.\n *\n * _Available since v3.4._\n */\n function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {\n unchecked {\n if (b == 0) return (false, 0);\n return (true, a / b);\n }\n }\n\n /**\n * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.\n *\n * _Available since v3.4._\n */\n function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {\n unchecked {\n if (b == 0) return (false, 0);\n return (true, a % b);\n }\n }\n\n /**\n * @dev Returns the addition of two unsigned integers, reverting on\n * overflow.\n *\n * Counterpart to Solidity's `+` operator.\n *\n * Requirements:\n *\n * - Addition cannot overflow.\n */\n function add(uint256 a, uint256 b) internal pure returns (uint256) {\n return a + b;\n }\n\n /**\n * @dev Returns the subtraction of two unsigned integers, reverting on\n * overflow (when the result is negative).\n *\n * Counterpart to Solidity's `-` operator.\n *\n * Requirements:\n *\n * - Subtraction cannot overflow.\n */\n function sub(uint256 a, uint256 b) internal pure returns (uint256) {\n return a - b;\n }\n\n /**\n * @dev Returns the multiplication of two unsigned integers, reverting on\n * overflow.\n *\n * Counterpart to Solidity's `*` operator.\n *\n * Requirements:\n *\n * - Multiplication cannot overflow.\n */\n function mul(uint256 a, uint256 b) internal pure returns (uint256) {\n return a * b;\n }\n\n /**\n * @dev Returns the integer division of two unsigned integers, reverting on\n * division by zero. The result is rounded towards zero.\n *\n * Counterpart to Solidity's `/` operator.\n *\n * Requirements:\n *\n * - The divisor cannot be zero.\n */\n function div(uint256 a, uint256 b) internal pure returns (uint256) {\n return a / b;\n }\n\n /**\n * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),\n * reverting when dividing by zero.\n *\n * Counterpart to Solidity's `%` operator. This function uses a `revert`\n * opcode (which leaves remaining gas untouched) while Solidity uses an\n * invalid opcode to revert (consuming all remaining gas).\n *\n * Requirements:\n *\n * - The divisor cannot be zero.\n */\n function mod(uint256 a, uint256 b) internal pure returns (uint256) {\n return a % b;\n }\n\n /**\n * @dev Returns the subtraction of two unsigned integers, reverting with custom message on\n * overflow (when the result is negative).\n *\n * CAUTION: This function is deprecated because it requires allocating memory for the error\n * message unnecessarily. For custom revert reasons use {trySub}.\n *\n * Counterpart to Solidity's `-` operator.\n *\n * Requirements:\n *\n * - Subtraction cannot overflow.\n */\n function sub(\n uint256 a,\n uint256 b,\n string memory errorMessage\n ) internal pure returns (uint256) {\n unchecked {\n require(b <= a, errorMessage);\n return a - b;\n }\n }\n\n /**\n * @dev Returns the integer division of two unsigned integers, reverting with custom message on\n * division by zero. The result is rounded towards zero.\n *\n * Counterpart to Solidity's `/` operator. Note: this function uses a\n * `revert` opcode (which leaves remaining gas untouched) while Solidity\n * uses an invalid opcode to revert (consuming all remaining gas).\n *\n * Requirements:\n *\n * - The divisor cannot be zero.\n */\n function div(\n uint256 a,\n uint256 b,\n string memory errorMessage\n ) internal pure returns (uint256) {\n unchecked {\n require(b > 0, errorMessage);\n return a / b;\n }\n }\n\n /**\n * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),\n * reverting with custom message when dividing by zero.\n *\n * CAUTION: This function is deprecated because it requires allocating memory for the error\n * message unnecessarily. For custom revert reasons use {tryMod}.\n *\n * Counterpart to Solidity's `%` operator. This function uses a `revert`\n * opcode (which leaves remaining gas untouched) while Solidity uses an\n * invalid opcode to revert (consuming all remaining gas).\n *\n * Requirements:\n *\n * - The divisor cannot be zero.\n */\n function mod(\n uint256 a,\n uint256 b,\n string memory errorMessage\n ) internal pure returns (uint256) {\n unchecked {\n require(b > 0, errorMessage);\n return a % b;\n }\n }\n}\n" + }, + "@openzeppelin/contracts/utils/Strings.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (utils/Strings.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev String operations.\n */\nlibrary Strings {\n bytes16 private constant _HEX_SYMBOLS = \"0123456789abcdef\";\n\n /**\n * @dev Converts a `uint256` to its ASCII `string` decimal representation.\n */\n function toString(uint256 value) internal pure returns (string memory) {\n // Inspired by OraclizeAPI's implementation - MIT licence\n // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol\n\n if (value == 0) {\n return \"0\";\n }\n uint256 temp = value;\n uint256 digits;\n while (temp != 0) {\n digits++;\n temp /= 10;\n }\n bytes memory buffer = new bytes(digits);\n while (value != 0) {\n digits -= 1;\n buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));\n value /= 10;\n }\n return string(buffer);\n }\n\n /**\n * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.\n */\n function toHexString(uint256 value) internal pure returns (string memory) {\n if (value == 0) {\n return \"0x00\";\n }\n uint256 temp = value;\n uint256 length = 0;\n while (temp != 0) {\n length++;\n temp >>= 8;\n }\n return toHexString(value, length);\n }\n\n /**\n * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.\n */\n function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {\n bytes memory buffer = new bytes(2 * length + 2);\n buffer[0] = \"0\";\n buffer[1] = \"x\";\n for (uint256 i = 2 * length + 1; i > 1; --i) {\n buffer[i] = _HEX_SYMBOLS[value & 0xf];\n value >>= 4;\n }\n require(value == 0, \"Strings: hex length insufficient\");\n return string(buffer);\n }\n}\n" + }, + "@openzeppelin/contracts/utils/structs/EnumerableSet.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (utils/structs/EnumerableSet.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Library for managing\n * https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive\n * types.\n *\n * Sets have the following properties:\n *\n * - Elements are added, removed, and checked for existence in constant time\n * (O(1)).\n * - Elements are enumerated in O(n). No guarantees are made on the ordering.\n *\n * ```\n * contract Example {\n * // Add the library methods\n * using EnumerableSet for EnumerableSet.AddressSet;\n *\n * // Declare a set state variable\n * EnumerableSet.AddressSet private mySet;\n * }\n * ```\n *\n * As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)\n * and `uint256` (`UintSet`) are supported.\n */\nlibrary EnumerableSet {\n // To implement this library for multiple types with as little code\n // repetition as possible, we write it in terms of a generic Set type with\n // bytes32 values.\n // The Set implementation uses private functions, and user-facing\n // implementations (such as AddressSet) are just wrappers around the\n // underlying Set.\n // This means that we can only create new EnumerableSets for types that fit\n // in bytes32.\n\n struct Set {\n // Storage of set values\n bytes32[] _values;\n // Position of the value in the `values` array, plus 1 because index 0\n // means a value is not in the set.\n mapping(bytes32 => uint256) _indexes;\n }\n\n /**\n * @dev Add a value to a set. O(1).\n *\n * Returns true if the value was added to the set, that is if it was not\n * already present.\n */\n function _add(Set storage set, bytes32 value) private returns (bool) {\n if (!_contains(set, value)) {\n set._values.push(value);\n // The value is stored at length-1, but we add 1 to all indexes\n // and use 0 as a sentinel value\n set._indexes[value] = set._values.length;\n return true;\n } else {\n return false;\n }\n }\n\n /**\n * @dev Removes a value from a set. O(1).\n *\n * Returns true if the value was removed from the set, that is if it was\n * present.\n */\n function _remove(Set storage set, bytes32 value) private returns (bool) {\n // We read and store the value's index to prevent multiple reads from the same storage slot\n uint256 valueIndex = set._indexes[value];\n\n if (valueIndex != 0) {\n // Equivalent to contains(set, value)\n // To delete an element from the _values array in O(1), we swap the element to delete with the last one in\n // the array, and then remove the last element (sometimes called as 'swap and pop').\n // This modifies the order of the array, as noted in {at}.\n\n uint256 toDeleteIndex = valueIndex - 1;\n uint256 lastIndex = set._values.length - 1;\n\n if (lastIndex != toDeleteIndex) {\n bytes32 lastvalue = set._values[lastIndex];\n\n // Move the last value to the index where the value to delete is\n set._values[toDeleteIndex] = lastvalue;\n // Update the index for the moved value\n set._indexes[lastvalue] = valueIndex; // Replace lastvalue's index to valueIndex\n }\n\n // Delete the slot where the moved value was stored\n set._values.pop();\n\n // Delete the index for the deleted slot\n delete set._indexes[value];\n\n return true;\n } else {\n return false;\n }\n }\n\n /**\n * @dev Returns true if the value is in the set. O(1).\n */\n function _contains(Set storage set, bytes32 value) private view returns (bool) {\n return set._indexes[value] != 0;\n }\n\n /**\n * @dev Returns the number of values on the set. O(1).\n */\n function _length(Set storage set) private view returns (uint256) {\n return set._values.length;\n }\n\n /**\n * @dev Returns the value stored at position `index` in the set. O(1).\n *\n * Note that there are no guarantees on the ordering of values inside the\n * array, and it may change when more values are added or removed.\n *\n * Requirements:\n *\n * - `index` must be strictly less than {length}.\n */\n function _at(Set storage set, uint256 index) private view returns (bytes32) {\n return set._values[index];\n }\n\n /**\n * @dev Return the entire set in an array\n *\n * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed\n * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that\n * this function has an unbounded cost, and using it as part of a state-changing function may render the function\n * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.\n */\n function _values(Set storage set) private view returns (bytes32[] memory) {\n return set._values;\n }\n\n // Bytes32Set\n\n struct Bytes32Set {\n Set _inner;\n }\n\n /**\n * @dev Add a value to a set. O(1).\n *\n * Returns true if the value was added to the set, that is if it was not\n * already present.\n */\n function add(Bytes32Set storage set, bytes32 value) internal returns (bool) {\n return _add(set._inner, value);\n }\n\n /**\n * @dev Removes a value from a set. O(1).\n *\n * Returns true if the value was removed from the set, that is if it was\n * present.\n */\n function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) {\n return _remove(set._inner, value);\n }\n\n /**\n * @dev Returns true if the value is in the set. O(1).\n */\n function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {\n return _contains(set._inner, value);\n }\n\n /**\n * @dev Returns the number of values in the set. O(1).\n */\n function length(Bytes32Set storage set) internal view returns (uint256) {\n return _length(set._inner);\n }\n\n /**\n * @dev Returns the value stored at position `index` in the set. O(1).\n *\n * Note that there are no guarantees on the ordering of values inside the\n * array, and it may change when more values are added or removed.\n *\n * Requirements:\n *\n * - `index` must be strictly less than {length}.\n */\n function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {\n return _at(set._inner, index);\n }\n\n /**\n * @dev Return the entire set in an array\n *\n * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed\n * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that\n * this function has an unbounded cost, and using it as part of a state-changing function may render the function\n * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.\n */\n function values(Bytes32Set storage set) internal view returns (bytes32[] memory) {\n return _values(set._inner);\n }\n\n // AddressSet\n\n struct AddressSet {\n Set _inner;\n }\n\n /**\n * @dev Add a value to a set. O(1).\n *\n * Returns true if the value was added to the set, that is if it was not\n * already present.\n */\n function add(AddressSet storage set, address value) internal returns (bool) {\n return _add(set._inner, bytes32(uint256(uint160(value))));\n }\n\n /**\n * @dev Removes a value from a set. O(1).\n *\n * Returns true if the value was removed from the set, that is if it was\n * present.\n */\n function remove(AddressSet storage set, address value) internal returns (bool) {\n return _remove(set._inner, bytes32(uint256(uint160(value))));\n }\n\n /**\n * @dev Returns true if the value is in the set. O(1).\n */\n function contains(AddressSet storage set, address value) internal view returns (bool) {\n return _contains(set._inner, bytes32(uint256(uint160(value))));\n }\n\n /**\n * @dev Returns the number of values in the set. O(1).\n */\n function length(AddressSet storage set) internal view returns (uint256) {\n return _length(set._inner);\n }\n\n /**\n * @dev Returns the value stored at position `index` in the set. O(1).\n *\n * Note that there are no guarantees on the ordering of values inside the\n * array, and it may change when more values are added or removed.\n *\n * Requirements:\n *\n * - `index` must be strictly less than {length}.\n */\n function at(AddressSet storage set, uint256 index) internal view returns (address) {\n return address(uint160(uint256(_at(set._inner, index))));\n }\n\n /**\n * @dev Return the entire set in an array\n *\n * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed\n * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that\n * this function has an unbounded cost, and using it as part of a state-changing function may render the function\n * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.\n */\n function values(AddressSet storage set) internal view returns (address[] memory) {\n bytes32[] memory store = _values(set._inner);\n address[] memory result;\n\n assembly {\n result := store\n }\n\n return result;\n }\n\n // UintSet\n\n struct UintSet {\n Set _inner;\n }\n\n /**\n * @dev Add a value to a set. O(1).\n *\n * Returns true if the value was added to the set, that is if it was not\n * already present.\n */\n function add(UintSet storage set, uint256 value) internal returns (bool) {\n return _add(set._inner, bytes32(value));\n }\n\n /**\n * @dev Removes a value from a set. O(1).\n *\n * Returns true if the value was removed from the set, that is if it was\n * present.\n */\n function remove(UintSet storage set, uint256 value) internal returns (bool) {\n return _remove(set._inner, bytes32(value));\n }\n\n /**\n * @dev Returns true if the value is in the set. O(1).\n */\n function contains(UintSet storage set, uint256 value) internal view returns (bool) {\n return _contains(set._inner, bytes32(value));\n }\n\n /**\n * @dev Returns the number of values on the set. O(1).\n */\n function length(UintSet storage set) internal view returns (uint256) {\n return _length(set._inner);\n }\n\n /**\n * @dev Returns the value stored at position `index` in the set. O(1).\n *\n * Note that there are no guarantees on the ordering of values inside the\n * array, and it may change when more values are added or removed.\n *\n * Requirements:\n *\n * - `index` must be strictly less than {length}.\n */\n function at(UintSet storage set, uint256 index) internal view returns (uint256) {\n return uint256(_at(set._inner, index));\n }\n\n /**\n * @dev Return the entire set in an array\n *\n * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed\n * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that\n * this function has an unbounded cost, and using it as part of a state-changing function may render the function\n * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.\n */\n function values(UintSet storage set) internal view returns (uint256[] memory) {\n bytes32[] memory store = _values(set._inner);\n uint256[] memory result;\n\n assembly {\n result := store\n }\n\n return result;\n }\n}\n" + }, + "contracts/automation/AbstractCCIPBridgeHelperModule.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { AbstractSafeModule } from \"./AbstractSafeModule.sol\";\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\n\nimport { IRouterClient } from \"@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IRouterClient.sol\";\nimport { Client } from \"@chainlink/contracts-ccip/src/v0.8/ccip/libraries/Client.sol\";\n\nabstract contract AbstractCCIPBridgeHelperModule is AbstractSafeModule {\n /**\n * @notice Bridges a token from the source chain to the destination chain using CCIP\n * @param ccipRouter The CCIP router contract\n * @param destinationChainSelector The selector for the destination chain\n * @param token The token to bridge\n * @param amount The amount of token to bridge\n */\n function _bridgeTokenWithCCIP(\n IRouterClient ccipRouter,\n uint64 destinationChainSelector,\n IERC20 token,\n uint256 amount\n ) internal {\n bool success;\n\n // Approve CCIP Router to move the token\n success = safeContract.execTransactionFromModule(\n address(token),\n 0, // Value\n abi.encodeWithSelector(token.approve.selector, ccipRouter, amount),\n 0 // Call\n );\n require(success, \"Failed to approve token\");\n\n Client.EVMTokenAmount[]\n memory tokenAmounts = new Client.EVMTokenAmount[](1);\n Client.EVMTokenAmount memory tokenAmount = Client.EVMTokenAmount({\n token: address(token),\n amount: amount\n });\n tokenAmounts[0] = tokenAmount;\n\n Client.EVM2AnyMessage memory ccipMessage = Client.EVM2AnyMessage({\n receiver: abi.encode(address(safeContract)), // ABI-encoded receiver address\n data: abi.encode(\"\"),\n tokenAmounts: tokenAmounts,\n extraArgs: Client._argsToBytes(\n Client.EVMExtraArgsV1({ gasLimit: 0 })\n ),\n feeToken: address(0)\n });\n\n // Get CCIP fee\n uint256 ccipFee = ccipRouter.getFee(\n destinationChainSelector,\n ccipMessage\n );\n\n // Send CCIP message\n success = safeContract.execTransactionFromModule(\n address(ccipRouter),\n ccipFee, // Value\n abi.encodeWithSelector(\n ccipRouter.ccipSend.selector,\n destinationChainSelector,\n ccipMessage\n ),\n 0 // Call\n );\n require(success, \"Failed to send CCIP message\");\n }\n}\n" + }, + "contracts/automation/AbstractLZBridgeHelperModule.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { AbstractSafeModule } from \"./AbstractSafeModule.sol\";\n\nimport { IOFT, SendParam } from \"@layerzerolabs/oft-evm/contracts/interfaces/IOFT.sol\";\nimport { MessagingFee } from \"@layerzerolabs/oapp-evm/contracts/oapp/OAppSender.sol\";\nimport { OptionsBuilder } from \"@layerzerolabs/oapp-evm/contracts/oapp/libs/OptionsBuilder.sol\";\n\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\n\nabstract contract AbstractLZBridgeHelperModule is AbstractSafeModule {\n using OptionsBuilder for bytes;\n\n /**\n * @dev Bridges token using LayerZero.\n * @param lzEndpointId LayerZero endpoint id.\n * @param token Token to bridge.\n * @param lzAdapter LZ Adapter to use.\n * @param amount Amount of token to bridge.\n * @param slippageBps Slippage in 10^4 basis points.\n * @param isNativeToken Whether the token is native token.\n */\n function _bridgeTokenWithLz(\n uint32 lzEndpointId,\n IERC20 token,\n IOFT lzAdapter,\n uint256 amount,\n uint256 slippageBps,\n bool isNativeToken\n ) internal {\n bool success;\n\n if (!isNativeToken) {\n // Approve LZ Adapter to move the token\n success = safeContract.execTransactionFromModule(\n address(token),\n 0, // Value\n abi.encodeWithSelector(\n token.approve.selector,\n address(lzAdapter),\n amount\n ),\n 0 // Call\n );\n require(success, \"Failed to approve token\");\n }\n\n // Calculate minimum amount to receive\n uint256 minAmount = (amount * (10000 - slippageBps)) / 10000;\n\n // Hardcoded gaslimit of 400k\n bytes memory options = OptionsBuilder\n .newOptions()\n .addExecutorLzReceiveOption(400000, 0);\n\n // Build send param\n SendParam memory sendParam = SendParam({\n dstEid: lzEndpointId,\n to: bytes32(uint256(uint160(address(safeContract)))),\n amountLD: amount,\n minAmountLD: minAmount,\n extraOptions: options,\n composeMsg: bytes(\"\"),\n oftCmd: bytes(\"\")\n });\n\n // Compute fees\n MessagingFee memory msgFee = lzAdapter.quoteSend(sendParam, false);\n\n uint256 value = isNativeToken\n ? amount + msgFee.nativeFee\n : msgFee.nativeFee;\n\n // Execute transaction\n success = safeContract.execTransactionFromModule(\n address(lzAdapter),\n value,\n abi.encodeWithSelector(\n lzAdapter.send.selector,\n sendParam,\n msgFee,\n address(safeContract)\n ),\n 0\n );\n require(success, \"Failed to bridge token\");\n }\n}\n" + }, + "contracts/automation/AbstractSafeModule.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { AccessControlEnumerable } from \"@openzeppelin/contracts/access/AccessControlEnumerable.sol\";\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport { ISafe } from \"../interfaces/ISafe.sol\";\n\nabstract contract AbstractSafeModule is AccessControlEnumerable {\n ISafe public immutable safeContract;\n\n bytes32 public constant OPERATOR_ROLE = keccak256(\"OPERATOR_ROLE\");\n\n modifier onlySafe() {\n require(\n msg.sender == address(safeContract),\n \"Caller is not the safe contract\"\n );\n _;\n }\n\n modifier onlyOperator() {\n require(\n hasRole(OPERATOR_ROLE, msg.sender),\n \"Caller is not an operator\"\n );\n _;\n }\n\n constructor(address _safeContract) {\n safeContract = ISafe(_safeContract);\n _grantRole(DEFAULT_ADMIN_ROLE, address(safeContract));\n _grantRole(OPERATOR_ROLE, address(safeContract));\n }\n\n /**\n * @dev Helps recovering any tokens accidentally sent to this module.\n * @param token Token to transfer. 0x0 to transfer Native token.\n * @param amount Amount to transfer. 0 to transfer all balance.\n */\n function transferTokens(address token, uint256 amount) external onlySafe {\n if (address(token) == address(0)) {\n // Move ETH\n amount = amount > 0 ? amount : address(this).balance;\n payable(address(safeContract)).transfer(amount);\n return;\n }\n\n // Move all balance if amount set to 0\n amount = amount > 0 ? amount : IERC20(token).balanceOf(address(this));\n\n // Transfer to Safe contract\n // slither-disable-next-line unchecked-transfer unused-return\n IERC20(token).transfer(address(safeContract), amount);\n }\n\n receive() external payable {\n // Accept ETH to pay for bridge fees\n }\n}\n" + }, + "contracts/automation/BaseBridgeHelperModule.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n// solhint-disable-next-line max-line-length\nimport { AbstractCCIPBridgeHelperModule, AbstractSafeModule, IRouterClient } from \"./AbstractCCIPBridgeHelperModule.sol\";\n\nimport { AccessControlEnumerable } from \"@openzeppelin/contracts/access/AccessControlEnumerable.sol\";\n\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport { IERC4626 } from \"../../lib/openzeppelin/interfaces/IERC4626.sol\";\nimport { IWETH9 } from \"../interfaces/IWETH9.sol\";\nimport { IVault } from \"../interfaces/IVault.sol\";\n\nimport { BridgedWOETHStrategy } from \"../strategies/BridgedWOETHStrategy.sol\";\n\ncontract BaseBridgeHelperModule is\n AccessControlEnumerable,\n AbstractCCIPBridgeHelperModule\n{\n IVault public constant vault =\n IVault(0x98a0CbeF61bD2D21435f433bE4CD42B56B38CC93);\n IWETH9 public constant weth =\n IWETH9(0x4200000000000000000000000000000000000006);\n IERC20 public constant oethb =\n IERC20(0xDBFeFD2e8460a6Ee4955A68582F85708BAEA60A3);\n IERC4626 public constant bridgedWOETH =\n IERC4626(0xD8724322f44E5c58D7A815F542036fb17DbbF839);\n\n BridgedWOETHStrategy public constant bridgedWOETHStrategy =\n BridgedWOETHStrategy(0x80c864704DD06C3693ed5179190786EE38ACf835);\n\n IRouterClient public constant CCIP_ROUTER =\n IRouterClient(0x881e3A65B4d4a04dD529061dd0071cf975F58bCD);\n\n uint64 public constant CCIP_ETHEREUM_CHAIN_SELECTOR = 5009297550715157269;\n\n constructor(address _safeContract) AbstractSafeModule(_safeContract) {}\n\n /**\n * @dev Bridges wOETH to Ethereum.\n * @param woethAmount Amount of wOETH to bridge.\n */\n function bridgeWOETHToEthereum(uint256 woethAmount)\n public\n payable\n onlyOperator\n {\n _bridgeTokenWithCCIP(\n CCIP_ROUTER,\n CCIP_ETHEREUM_CHAIN_SELECTOR,\n IERC20(address(bridgedWOETH)),\n woethAmount\n );\n }\n\n /**\n * @dev Bridges WETH to Ethereum.\n * @param wethAmount Amount of WETH to bridge.\n */\n function bridgeWETHToEthereum(uint256 wethAmount)\n public\n payable\n onlyOperator\n {\n _bridgeTokenWithCCIP(\n CCIP_ROUTER,\n CCIP_ETHEREUM_CHAIN_SELECTOR,\n IERC20(address(weth)),\n wethAmount\n );\n }\n\n /**\n * @dev Deposits wOETH into the bridgedWOETH strategy.\n * @param woethAmount Amount of wOETH to deposit.\n * @param redeemWithVault Whether to redeem the wOETH for WETH using the Vault.\n * @return Amount of WETH received.\n */\n function depositWOETH(uint256 woethAmount, bool redeemWithVault)\n external\n onlyOperator\n returns (uint256)\n {\n return _depositWOETH(woethAmount, redeemWithVault);\n }\n\n /**\n * @dev Deposits wOETH into the bridgedWOETH strategy and bridges it to Ethereum.\n * @param woethAmount Amount of wOETH to deposit.\n * @return Amount of WETH received.\n */\n function depositWOETHAndBridgeWETH(uint256 woethAmount)\n external\n onlyOperator\n returns (uint256)\n {\n uint256 wethAmount = _depositWOETH(woethAmount, true);\n bridgeWETHToEthereum(wethAmount);\n return wethAmount;\n }\n\n /**\n * @dev Deposits wOETH into the bridgedWOETH strategy.\n * @param woethAmount Amount of wOETH to deposit.\n * @param redeemWithVault Whether to redeem the wOETH for WETH using the Vault.\n * @return Amount of WETH received.\n */\n function _depositWOETH(uint256 woethAmount, bool redeemWithVault)\n internal\n returns (uint256)\n {\n // Update oracle price\n bridgedWOETHStrategy.updateWOETHOraclePrice();\n\n // Rebase to account for any yields from price update\n vault.rebase();\n\n uint256 oethbAmount = oethb.balanceOf(address(safeContract));\n\n // Approve bridgedWOETH strategy to move wOETH\n bool success = safeContract.execTransactionFromModule(\n address(bridgedWOETH),\n 0, // Value\n abi.encodeWithSelector(\n bridgedWOETH.approve.selector,\n address(bridgedWOETHStrategy),\n woethAmount\n ),\n 0 // Call\n );\n\n // Deposit to bridgedWOETH strategy\n success = safeContract.execTransactionFromModule(\n address(bridgedWOETHStrategy),\n 0, // Value\n abi.encodeWithSelector(\n bridgedWOETHStrategy.depositBridgedWOETH.selector,\n woethAmount\n ),\n 0 // Call\n );\n require(success, \"Failed to deposit bridged WOETH\");\n\n oethbAmount = oethb.balanceOf(address(safeContract)) - oethbAmount;\n\n // Rebase to account for any yields from price update\n // and backing asset change from deposit\n vault.rebase();\n\n if (!redeemWithVault) {\n return oethbAmount;\n }\n\n // Redeem for WETH using Vault\n success = safeContract.execTransactionFromModule(\n address(vault),\n 0, // Value\n abi.encodeWithSelector(\n vault.redeem.selector,\n oethbAmount,\n oethbAmount\n ),\n 0 // Call\n );\n require(success, \"Failed to redeem OETHb\");\n\n return oethbAmount;\n }\n\n /**\n * @dev Deposits WETH into the Vault and redeems wOETH from the bridgedWOETH strategy.\n * @param wethAmount Amount of WETH to deposit.\n * @return Amount of wOETH received.\n */\n function depositWETHAndRedeemWOETH(uint256 wethAmount)\n external\n onlyOperator\n returns (uint256)\n {\n return _withdrawWOETH(wethAmount);\n }\n\n function depositWETHAndBridgeWOETH(uint256 wethAmount)\n external\n onlyOperator\n returns (uint256)\n {\n uint256 woethAmount = _withdrawWOETH(wethAmount);\n bridgeWOETHToEthereum(woethAmount);\n return woethAmount;\n }\n\n /**\n * @dev Withdraws wOETH from the bridgedWOETH strategy.\n * @param wethAmount Amount of WETH to use to withdraw.\n * @return Amount of wOETH received.\n */\n function _withdrawWOETH(uint256 wethAmount) internal returns (uint256) {\n // Approve Vault to move WETH\n bool success = safeContract.execTransactionFromModule(\n address(weth),\n 0, // Value\n abi.encodeWithSelector(\n weth.approve.selector,\n address(vault),\n wethAmount\n ),\n 0 // Call\n );\n require(success, \"Failed to approve WETH\");\n\n // Mint OETHb with WETH\n success = safeContract.execTransactionFromModule(\n address(vault),\n 0, // Value\n abi.encodeWithSelector(\n vault.mint.selector,\n address(weth),\n wethAmount,\n wethAmount\n ),\n 0 // Call\n );\n require(success, \"Failed to mint OETHb\");\n\n // Approve bridgedWOETH strategy to move OETHb\n success = safeContract.execTransactionFromModule(\n address(oethb),\n 0, // Value\n abi.encodeWithSelector(\n oethb.approve.selector,\n address(bridgedWOETHStrategy),\n wethAmount\n ),\n 0 // Call\n );\n require(success, \"Failed to approve OETHb\");\n\n uint256 woethAmount = bridgedWOETH.balanceOf(address(safeContract));\n\n // Withdraw from bridgedWOETH strategy\n success = safeContract.execTransactionFromModule(\n address(bridgedWOETHStrategy),\n 0, // Value\n abi.encodeWithSelector(\n bridgedWOETHStrategy.withdrawBridgedWOETH.selector,\n wethAmount\n ),\n 0 // Call\n );\n require(success, \"Failed to withdraw bridged WOETH\");\n\n woethAmount =\n bridgedWOETH.balanceOf(address(safeContract)) -\n woethAmount;\n\n return woethAmount;\n }\n}\n" + }, + "contracts/automation/CurvePoolBoosterBribesModule.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { AbstractSafeModule } from \"./AbstractSafeModule.sol\";\n\ninterface ICurvePoolBooster {\n function manageTotalRewardAmount(\n uint256 bridgeFee,\n uint256 additionalGasLimit\n ) external;\n\n function manageNumberOfPeriods(\n uint8 extraNumberOfPeriods,\n uint256 bridgeFee,\n uint256 additionalGasLimit\n ) external;\n\n function manageRewardPerVote(\n uint256 newMaxRewardPerVote,\n uint256 bridgeFee,\n uint256 additionalGasLimit\n ) external;\n}\n\ncontract CurvePoolBoosterBribesModule is AbstractSafeModule {\n address[] public POOLS;\n\n event PoolBoosterAddressAdded(address pool);\n event PoolBoosterAddressRemoved(address pool);\n\n constructor(\n address _safeContract,\n address _operator,\n address[] memory _pools\n ) AbstractSafeModule(_safeContract) {\n _grantRole(OPERATOR_ROLE, _operator);\n _addPoolBoosterAddress(_pools);\n }\n\n function addPoolBoosterAddress(address[] memory pools)\n external\n onlyOperator\n {\n _addPoolBoosterAddress(pools);\n }\n\n function _addPoolBoosterAddress(address[] memory pools) internal {\n for (uint256 i = 0; i < pools.length; i++) {\n POOLS.push(pools[i]);\n emit PoolBoosterAddressAdded(pools[i]);\n }\n }\n\n function removePoolBoosterAddress(address[] calldata pools)\n external\n onlyOperator\n {\n for (uint256 i = 0; i < pools.length; i++) {\n _removePoolBoosterAddress(pools[i]);\n }\n }\n\n function _removePoolBoosterAddress(address pool) internal {\n uint256 length = POOLS.length;\n for (uint256 i = 0; i < length; i++) {\n if (POOLS[i] == pool) {\n POOLS[i] = POOLS[length - 1];\n POOLS.pop();\n emit PoolBoosterAddressRemoved(pool);\n break;\n }\n }\n }\n\n function manageBribes() external onlyOperator {\n uint256[] memory rewardsPerVote = new uint256[](POOLS.length);\n _manageBribes(rewardsPerVote);\n }\n\n function manageBribes(uint256[] memory rewardsPerVote)\n external\n onlyOperator\n {\n require(POOLS.length == rewardsPerVote.length, \"Length mismatch\");\n _manageBribes(rewardsPerVote);\n }\n\n function _manageBribes(uint256[] memory rewardsPerVote)\n internal\n onlyOperator\n {\n uint256 length = POOLS.length;\n for (uint256 i = 0; i < length; i++) {\n address poolBoosterAddress = POOLS[i];\n\n // PoolBooster need to have a balance of at least 0.003 ether to operate\n // 0.001 ether are used for the bridge fee\n require(\n poolBoosterAddress.balance > 0.003 ether,\n \"Insufficient balance for bribes\"\n );\n\n require(\n safeContract.execTransactionFromModule(\n poolBoosterAddress,\n 0, // Value\n abi.encodeWithSelector(\n ICurvePoolBooster.manageNumberOfPeriods.selector,\n 1, // extraNumberOfPeriods\n 0.001 ether, // bridgeFee\n 1000000 // additionalGasLimit\n ),\n 0\n ),\n \"Manage number of periods failed\"\n );\n\n require(\n safeContract.execTransactionFromModule(\n poolBoosterAddress,\n 0, // Value\n abi.encodeWithSelector(\n ICurvePoolBooster.manageTotalRewardAmount.selector,\n 0.001 ether, // bridgeFee\n 1000000 // additionalGasLimit\n ),\n 0\n ),\n \"Manage total reward failed\"\n );\n\n // Skip setting reward per vote if it's zero\n if (rewardsPerVote[i] == 0) continue;\n require(\n safeContract.execTransactionFromModule(\n poolBoosterAddress,\n 0, // Value\n abi.encodeWithSelector(\n ICurvePoolBooster.manageRewardPerVote.selector,\n rewardsPerVote[i], // newMaxRewardPerVote\n 0.001 ether, // bridgeFee\n 1000000 // additionalGasLimit\n ),\n 0\n ),\n \"Set reward per vote failed\"\n );\n }\n }\n\n function getPools() external view returns (address[] memory) {\n return POOLS;\n }\n}\n" + }, + "contracts/automation/PlumeBridgeHelperModule.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { AbstractSafeModule } from \"./AbstractSafeModule.sol\";\nimport { AbstractLZBridgeHelperModule } from \"./AbstractLZBridgeHelperModule.sol\";\n\nimport { AccessControlEnumerable } from \"@openzeppelin/contracts/access/AccessControlEnumerable.sol\";\n\nimport { IOFT } from \"@layerzerolabs/oft-evm/contracts/interfaces/IOFT.sol\";\n\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport { IERC4626 } from \"../../lib/openzeppelin/interfaces/IERC4626.sol\";\nimport { IWETH9 } from \"../interfaces/IWETH9.sol\";\nimport { IVault } from \"../interfaces/IVault.sol\";\n\nimport { BridgedWOETHStrategy } from \"../strategies/BridgedWOETHStrategy.sol\";\n\ncontract PlumeBridgeHelperModule is\n AccessControlEnumerable,\n AbstractLZBridgeHelperModule\n{\n IVault public constant vault =\n IVault(0xc8c8F8bEA5631A8AF26440AF32a55002138cB76a);\n IWETH9 public constant weth =\n IWETH9(0xca59cA09E5602fAe8B629DeE83FfA819741f14be);\n IERC20 public constant oethp =\n IERC20(0xFCbe50DbE43bF7E5C88C6F6Fb9ef432D4165406E);\n IERC4626 public constant bridgedWOETH =\n IERC4626(0xD8724322f44E5c58D7A815F542036fb17DbbF839);\n\n uint32 public constant LZ_ETHEREUM_ENDPOINT_ID = 30101;\n IOFT public constant LZ_WOETH_OMNICHAIN_ADAPTER =\n IOFT(0x592CB6A596E7919930bF49a27AdAeCA7C055e4DB);\n IOFT public constant LZ_ETH_OMNICHAIN_ADAPTER =\n IOFT(0x4683CE822272CD66CEa73F5F1f9f5cBcaEF4F066);\n\n BridgedWOETHStrategy public constant bridgedWOETHStrategy =\n BridgedWOETHStrategy(0x1E3EdD5e019207D6355Ea77F724b1F1BF639B569);\n\n constructor(address _safeContract) AbstractSafeModule(_safeContract) {}\n\n /**\n * @dev Bridges wOETH to Ethereum.\n * @param woethAmount Amount of wOETH to bridge.\n * @param slippageBps Slippage in 10^4 basis points.\n */\n function bridgeWOETHToEthereum(uint256 woethAmount, uint256 slippageBps)\n public\n payable\n onlyOperator\n {\n _bridgeTokenWithLz(\n LZ_ETHEREUM_ENDPOINT_ID,\n IERC20(address(bridgedWOETH)),\n LZ_WOETH_OMNICHAIN_ADAPTER,\n woethAmount,\n slippageBps,\n false\n );\n }\n\n /**\n * @dev Bridges wETH to Ethereum.\n * @param wethAmount Amount of wETH to bridge.\n * @param slippageBps Slippage in 10^4 basis points.\n */\n function bridgeWETHToEthereum(uint256 wethAmount, uint256 slippageBps)\n public\n payable\n onlyOperator\n {\n _bridgeTokenWithLz(\n LZ_ETHEREUM_ENDPOINT_ID,\n IERC20(address(weth)),\n LZ_ETH_OMNICHAIN_ADAPTER,\n wethAmount,\n slippageBps,\n false\n );\n }\n\n /**\n * @dev Deposits wOETH into the bridgedWOETH strategy.\n * @param woethAmount Amount of wOETH to deposit.\n * @param redeemWithVault Whether to redeem with Vault.\n * @return Amount of OETHp received.\n */\n function depositWOETH(uint256 woethAmount, bool redeemWithVault)\n external\n onlyOperator\n returns (uint256)\n {\n return _depositWOETH(woethAmount, redeemWithVault);\n }\n\n /**\n * @dev Deposits wOETH into the bridgedWOETH strategy and bridges it to Ethereum.\n * @param woethAmount Amount of wOETH to deposit.\n * @param slippageBps Slippage in 10^4 basis points.\n * @return Amount of WETH received.\n */\n function depositWOETHAndBridgeWETH(uint256 woethAmount, uint256 slippageBps)\n external\n payable\n onlyOperator\n returns (uint256)\n {\n uint256 wethAmount = _depositWOETH(woethAmount, true);\n bridgeWETHToEthereum(wethAmount, slippageBps);\n return wethAmount;\n }\n\n /**\n * @dev Deposits wOETH into the bridgedWOETH strategy.\n * @param woethAmount Amount of wOETH to deposit.\n * @param redeemWithVault Whether to redeem with Vault.\n * @return Amount of OETHp received.\n */\n function _depositWOETH(uint256 woethAmount, bool redeemWithVault)\n internal\n returns (uint256)\n {\n // Update oracle price\n bridgedWOETHStrategy.updateWOETHOraclePrice();\n\n // Rebase to account for any yields from price update\n vault.rebase();\n\n uint256 oethpAmount = oethp.balanceOf(address(safeContract));\n\n // Approve bridgedWOETH strategy to move wOETH\n bool success = safeContract.execTransactionFromModule(\n address(bridgedWOETH),\n 0, // Value\n abi.encodeWithSelector(\n bridgedWOETH.approve.selector,\n address(bridgedWOETHStrategy),\n woethAmount\n ),\n 0 // Call\n );\n\n // Deposit to bridgedWOETH strategy\n success = safeContract.execTransactionFromModule(\n address(bridgedWOETHStrategy),\n 0, // Value\n abi.encodeWithSelector(\n bridgedWOETHStrategy.depositBridgedWOETH.selector,\n woethAmount\n ),\n 0 // Call\n );\n require(success, \"Failed to deposit bridged WOETH\");\n\n oethpAmount = oethp.balanceOf(address(safeContract)) - oethpAmount;\n\n // Rebase to account for any yields from price update\n // and backing asset change from deposit\n vault.rebase();\n\n if (!redeemWithVault) {\n return oethpAmount;\n }\n\n // Redeem for WETH using Vault\n success = safeContract.execTransactionFromModule(\n address(vault),\n 0, // Value\n abi.encodeWithSelector(\n vault.redeem.selector,\n oethpAmount,\n oethpAmount\n ),\n 0 // Call\n );\n require(success, \"Failed to redeem OETHp\");\n\n return oethpAmount;\n }\n\n /**\n * @dev Deposits wETH into the vault.\n * @param wethAmount Amount of wETH to deposit.\n * @return Amount of OETHp received.\n */\n function depositWETHAndRedeemWOETH(uint256 wethAmount)\n external\n onlyOperator\n returns (uint256)\n {\n return _withdrawWOETH(wethAmount);\n }\n\n /**\n * @dev Deposits wETH into the vault and bridges it to Ethereum.\n * @param wethAmount Amount of wETH to deposit.\n * @param slippageBps Slippage in 10^4 basis points.\n * @return Amount of WOETH received.\n */\n function depositWETHAndBridgeWOETH(uint256 wethAmount, uint256 slippageBps)\n external\n payable\n onlyOperator\n returns (uint256)\n {\n uint256 woethAmount = _withdrawWOETH(wethAmount);\n bridgeWOETHToEthereum(woethAmount, slippageBps);\n return woethAmount;\n }\n\n /**\n * @dev Withdraws wOETH from the bridgedWOETH strategy.\n * @param wethAmount Amount of WETH to use to withdraw.\n * @return Amount of wOETH received.\n */\n function _withdrawWOETH(uint256 wethAmount) internal returns (uint256) {\n // Approve Vault to move WETH\n bool success = safeContract.execTransactionFromModule(\n address(weth),\n 0, // Value\n abi.encodeWithSelector(\n weth.approve.selector,\n address(vault),\n wethAmount\n ),\n 0 // Call\n );\n require(success, \"Failed to approve WETH\");\n\n // Mint OETHp with WETH\n success = safeContract.execTransactionFromModule(\n address(vault),\n 0, // Value\n abi.encodeWithSelector(\n vault.mint.selector,\n address(weth),\n wethAmount,\n wethAmount\n ),\n 0 // Call\n );\n require(success, \"Failed to mint OETHp\");\n\n // Approve bridgedWOETH strategy to move OETHp\n success = safeContract.execTransactionFromModule(\n address(oethp),\n 0, // Value\n abi.encodeWithSelector(\n oethp.approve.selector,\n address(bridgedWOETHStrategy),\n wethAmount\n ),\n 0 // Call\n );\n require(success, \"Failed to approve OETHp\");\n\n uint256 woethAmount = bridgedWOETH.balanceOf(address(safeContract));\n\n // Withdraw from bridgedWOETH strategy\n success = safeContract.execTransactionFromModule(\n address(bridgedWOETHStrategy),\n 0, // Value\n abi.encodeWithSelector(\n bridgedWOETHStrategy.withdrawBridgedWOETH.selector,\n wethAmount\n ),\n 0 // Call\n );\n require(success, \"Failed to withdraw bridged WOETH\");\n\n woethAmount =\n bridgedWOETH.balanceOf(address(safeContract)) -\n woethAmount;\n\n return woethAmount;\n }\n}\n" + }, + "contracts/beacon/BeaconRoots.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title Library to retrieve beacon block roots.\n * @author Origin Protocol Inc\n */\nlibrary BeaconRoots {\n /// @notice The address of beacon block roots oracle\n /// See https://eips.ethereum.org/EIPS/eip-4788\n address internal constant BEACON_ROOTS_ADDRESS =\n 0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02;\n\n /// @notice Returns the beacon block root for the previous block.\n /// This comes from the Beacon Roots contract defined in EIP-4788.\n /// This will revert if the block is more than 8,191 blocks old as\n /// that is the size of the beacon root's ring buffer.\n /// @param timestamp The timestamp of the block for which to get the parent root.\n /// @return parentRoot The parent block root for the given timestamp.\n function parentBlockRoot(uint64 timestamp)\n internal\n view\n returns (bytes32 parentRoot)\n {\n // Call the Beacon Roots contract to get the parent block root.\n // This does not have a function signature, so we use a staticcall.\n (bool success, bytes memory result) = BEACON_ROOTS_ADDRESS.staticcall(\n abi.encode(timestamp)\n );\n\n require(success && result.length > 0, \"Invalid beacon timestamp\");\n parentRoot = abi.decode(result, (bytes32));\n }\n}\n" + }, + "contracts/beacon/PartialWithdrawal.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title Library to request full or partial withdrawals from validators on the beacon chain.\n * @author Origin Protocol Inc\n */\nlibrary PartialWithdrawal {\n /// @notice The address where the withdrawal request is sent to\n /// See https://eips.ethereum.org/EIPS/eip-7002\n address internal constant WITHDRAWAL_REQUEST_ADDRESS =\n 0x00000961Ef480Eb55e80D19ad83579A64c007002;\n\n /// @notice Requests a partial withdrawal for a given validator public key and amount.\n /// @param validatorPubKey The public key of the validator to withdraw from\n /// @param amount The amount of ETH to withdraw\n function request(bytes calldata validatorPubKey, uint64 amount)\n internal\n returns (uint256 fee_)\n {\n require(validatorPubKey.length == 48, \"Invalid validator byte length\");\n fee_ = fee();\n\n // Call the Withdrawal Request contract with the validator public key\n // and amount to be withdrawn packed together\n\n // This is a general purpose EL to CL request:\n // https://eips.ethereum.org/EIPS/eip-7685\n (bool success, ) = WITHDRAWAL_REQUEST_ADDRESS.call{ value: fee_ }(\n abi.encodePacked(validatorPubKey, amount)\n );\n\n require(success, \"Withdrawal request failed\");\n }\n\n /// @notice Gets fee for withdrawal requests contract on Beacon chain\n function fee() internal view returns (uint256) {\n // Get fee from the withdrawal request contract\n (bool success, bytes memory result) = WITHDRAWAL_REQUEST_ADDRESS\n .staticcall(\"\");\n\n require(success && result.length > 0, \"Failed to get fee\");\n return abi.decode(result, (uint256));\n }\n}\n" + }, + "contracts/buyback/AbstractBuyback.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { Strategizable } from \"../governance/Strategizable.sol\";\nimport \"../interfaces/chainlink/AggregatorV3Interface.sol\";\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport { ICVXLocker } from \"../interfaces/ICVXLocker.sol\";\nimport { ISwapper } from \"../interfaces/ISwapper.sol\";\n\nimport { Initializable } from \"../utils/Initializable.sol\";\n\nabstract contract AbstractBuyback is Initializable, Strategizable {\n using SafeERC20 for IERC20;\n\n event SwapRouterUpdated(address indexed _address);\n\n event RewardsSourceUpdated(address indexed _address);\n event TreasuryManagerUpdated(address indexed _address);\n event CVXShareBpsUpdated(uint256 bps);\n\n // Emitted whenever OUSD/OETH is swapped for OGN/CVX or any other token\n event OTokenBuyback(\n address indexed oToken,\n address indexed swappedFor,\n uint256 swapAmountIn,\n uint256 amountOut\n );\n\n // Address of 1-inch Swap Router\n address public swapRouter;\n\n // slither-disable-next-line constable-states\n address private __deprecated_ousd;\n // slither-disable-next-line constable-states\n address private __deprecated_ogv;\n // slither-disable-next-line constable-states\n address private __deprecated_usdt;\n // slither-disable-next-line constable-states\n address private __deprecated_weth9;\n\n // Address that receives OGN after swaps\n address public rewardsSource;\n\n // Address that receives all other tokens after swaps\n address public treasuryManager;\n\n // slither-disable-next-line constable-states\n uint256 private __deprecated_treasuryBps;\n\n address public immutable oToken;\n address public immutable ogn;\n address public immutable cvx;\n address public immutable cvxLocker;\n\n // Amount of `oToken` balance to use for OGN buyback\n uint256 public balanceForOGN;\n\n // Amount of `oToken` balance to use for CVX buyback\n uint256 public balanceForCVX;\n\n // Percentage of `oToken` balance to be used for CVX\n uint256 public cvxShareBps; // 10000 = 100%\n\n constructor(\n address _oToken,\n address _ogn,\n address _cvx,\n address _cvxLocker\n ) {\n // Make sure nobody owns the implementation contract\n _setGovernor(address(0));\n\n oToken = _oToken;\n ogn = _ogn;\n cvx = _cvx;\n cvxLocker = _cvxLocker;\n }\n\n /**\n * @param _swapRouter Address of Uniswap V3 Router\n * @param _strategistAddr Address of Strategist multi-sig wallet\n * @param _treasuryManagerAddr Address that receives the treasury's share of OUSD\n * @param _rewardsSource Address of RewardsSource contract\n * @param _cvxShareBps Percentage of balance to use for CVX\n */\n function initialize(\n address _swapRouter,\n address _strategistAddr,\n address _treasuryManagerAddr,\n address _rewardsSource,\n uint256 _cvxShareBps\n ) external onlyGovernor initializer {\n _setStrategistAddr(_strategistAddr);\n\n _setSwapRouter(_swapRouter);\n _setRewardsSource(_rewardsSource);\n\n _setTreasuryManager(_treasuryManagerAddr);\n\n _setCVXShareBps(_cvxShareBps);\n }\n\n /**\n * @dev Set address of Uniswap Universal Router for performing liquidation\n * of platform fee tokens. Setting to 0x0 will pause swaps.\n *\n * @param _router Address of the Uniswap Universal router\n */\n function setSwapRouter(address _router) external onlyGovernor {\n _setSwapRouter(_router);\n }\n\n function _setSwapRouter(address _router) internal {\n address oldRouter = swapRouter;\n swapRouter = _router;\n\n if (oldRouter != address(0)) {\n // Remove allowance of old router, if any\n\n if (IERC20(ogn).allowance(address(this), oldRouter) != 0) {\n // slither-disable-next-line unused-return\n IERC20(ogn).safeApprove(oldRouter, 0);\n }\n\n if (IERC20(cvx).allowance(address(this), oldRouter) != 0) {\n // slither-disable-next-line unused-return\n IERC20(cvx).safeApprove(oldRouter, 0);\n }\n }\n\n emit SwapRouterUpdated(_router);\n }\n\n /**\n * @dev Sets the address that receives the OGN buyback rewards\n * @param _address Address\n */\n function setRewardsSource(address _address) external onlyGovernor {\n _setRewardsSource(_address);\n }\n\n function _setRewardsSource(address _address) internal {\n require(_address != address(0), \"Address not set\");\n rewardsSource = _address;\n emit RewardsSourceUpdated(_address);\n }\n\n /**\n * @dev Sets the address that can receive and manage the funds for Treasury\n * @param _address Address\n */\n function setTreasuryManager(address _address) external onlyGovernor {\n _setTreasuryManager(_address);\n }\n\n function _setTreasuryManager(address _address) internal {\n require(_address != address(0), \"Address not set\");\n treasuryManager = _address;\n emit TreasuryManagerUpdated(_address);\n }\n\n /**\n * @dev Sets the percentage of oToken to use for Flywheel tokens\n * @param _bps BPS, 10000 to 100%\n */\n function setCVXShareBps(uint256 _bps) external onlyGovernor {\n _setCVXShareBps(_bps);\n }\n\n function _setCVXShareBps(uint256 _bps) internal {\n require(_bps <= 10000, \"Invalid bps value\");\n cvxShareBps = _bps;\n emit CVXShareBpsUpdated(_bps);\n }\n\n /**\n * @dev Computes the split of oToken balance that can be\n * used for OGN and CVX buybacks.\n */\n function _updateBuybackSplits()\n internal\n returns (uint256 _balanceForOGN, uint256 _balanceForCVX)\n {\n _balanceForOGN = balanceForOGN;\n _balanceForCVX = balanceForCVX;\n\n uint256 totalBalance = IERC20(oToken).balanceOf(address(this));\n uint256 unsplitBalance = totalBalance - _balanceForOGN - _balanceForCVX;\n\n // Check if all balance is accounted for\n if (unsplitBalance != 0) {\n // If not, split unaccounted balance based on `cvxShareBps`\n uint256 addToCVX = (unsplitBalance * cvxShareBps) / 10000;\n _balanceForCVX = _balanceForCVX + addToCVX;\n _balanceForOGN = _balanceForOGN + unsplitBalance - addToCVX;\n\n // Update storage\n balanceForOGN = _balanceForOGN;\n balanceForCVX = _balanceForCVX;\n }\n }\n\n function updateBuybackSplits() external onlyGovernor {\n // slither-disable-next-line unused-return\n _updateBuybackSplits();\n }\n\n function _swapToken(\n address tokenOut,\n uint256 oTokenAmount,\n uint256 minAmountOut,\n bytes calldata swapData\n ) internal returns (uint256 amountOut) {\n require(oTokenAmount > 0, \"Invalid Swap Amount\");\n require(swapRouter != address(0), \"Swap Router not set\");\n require(minAmountOut > 0, \"Invalid minAmount\");\n\n // Transfer OToken to Swapper for swapping\n // slither-disable-next-line unchecked-transfer unused-return\n IERC20(oToken).transfer(swapRouter, oTokenAmount);\n\n // Swap\n amountOut = ISwapper(swapRouter).swap(\n oToken,\n tokenOut,\n oTokenAmount,\n minAmountOut,\n swapData\n );\n\n require(amountOut >= minAmountOut, \"Higher Slippage\");\n\n emit OTokenBuyback(oToken, tokenOut, oTokenAmount, amountOut);\n }\n\n /**\n * @dev Swaps `oTokenAmount` to OGN\n * @param oTokenAmount Amount of OUSD/OETH to swap\n * @param minOGN Minimum OGN to receive for oTokenAmount\n * @param swapData 1inch Swap Data\n */\n function swapForOGN(\n uint256 oTokenAmount,\n uint256 minOGN,\n bytes calldata swapData\n ) external onlyGovernorOrStrategist nonReentrant {\n (uint256 _amountForOGN, ) = _updateBuybackSplits();\n require(_amountForOGN >= oTokenAmount, \"Balance underflow\");\n require(rewardsSource != address(0), \"RewardsSource contract not set\");\n\n unchecked {\n // Subtract the amount to swap from net balance\n balanceForOGN = _amountForOGN - oTokenAmount;\n }\n\n uint256 ognReceived = _swapToken(ogn, oTokenAmount, minOGN, swapData);\n\n // Transfer OGN received to RewardsSource contract\n // slither-disable-next-line unchecked-transfer unused-return\n IERC20(ogn).transfer(rewardsSource, ognReceived);\n }\n\n /**\n * @dev Swaps `oTokenAmount` to CVX\n * @param oTokenAmount Amount of OUSD/OETH to swap\n * @param minCVX Minimum CVX to receive for oTokenAmount\n * @param swapData 1inch Swap Data\n */\n function swapForCVX(\n uint256 oTokenAmount,\n uint256 minCVX,\n bytes calldata swapData\n ) external onlyGovernorOrStrategist nonReentrant {\n (, uint256 _amountForCVX) = _updateBuybackSplits();\n require(_amountForCVX >= oTokenAmount, \"Balance underflow\");\n\n unchecked {\n // Subtract the amount to swap from net balance\n balanceForCVX = _amountForCVX - oTokenAmount;\n }\n\n uint256 cvxReceived = _swapToken(cvx, oTokenAmount, minCVX, swapData);\n\n // Lock all CVX\n _lockAllCVX(cvxReceived);\n }\n\n /**\n * @dev Locks all CVX held by the contract on behalf of the Treasury Manager\n */\n function lockAllCVX() external onlyGovernorOrStrategist {\n _lockAllCVX(IERC20(cvx).balanceOf(address(this)));\n }\n\n function _lockAllCVX(uint256 cvxAmount) internal {\n require(\n treasuryManager != address(0),\n \"Treasury manager address not set\"\n );\n\n // Lock all available CVX on behalf of `treasuryManager`\n ICVXLocker(cvxLocker).lock(treasuryManager, cvxAmount, 0);\n }\n\n /**\n * @dev Approve CVX Locker to move CVX held by this contract\n */\n function safeApproveAllTokens() external onlyGovernorOrStrategist {\n IERC20(cvx).safeApprove(cvxLocker, type(uint256).max);\n }\n\n /**\n * @notice Owner function to withdraw a specific amount of a token\n * @param token token to be transferered\n * @param amount amount of the token to be transferred\n */\n function transferToken(address token, uint256 amount)\n external\n onlyGovernor\n nonReentrant\n {\n IERC20(token).safeTransfer(_governor(), amount);\n }\n}\n" + }, + "contracts/buyback/ARMBuyback.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { AbstractBuyback } from \"./AbstractBuyback.sol\";\n\ncontract ARMBuyback is AbstractBuyback {\n constructor(\n address _oToken,\n address _ogn,\n address _cvx,\n address _cvxLocker\n ) AbstractBuyback(_oToken, _ogn, _cvx, _cvxLocker) {}\n}\n" + }, + "contracts/buyback/OETHBuyback.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { AbstractBuyback } from \"./AbstractBuyback.sol\";\n\ncontract OETHBuyback is AbstractBuyback {\n constructor(\n address _oToken,\n address _ogn,\n address _cvx,\n address _cvxLocker\n ) AbstractBuyback(_oToken, _ogn, _cvx, _cvxLocker) {}\n}\n" + }, + "contracts/buyback/OUSDBuyback.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { AbstractBuyback } from \"./AbstractBuyback.sol\";\n\ncontract OUSDBuyback is AbstractBuyback {\n constructor(\n address _oToken,\n address _ogn,\n address _cvx,\n address _cvxLocker\n ) AbstractBuyback(_oToken, _ogn, _cvx, _cvxLocker) {}\n}\n" + }, + "contracts/governance/Governable.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title Base for contracts that are managed by the Origin Protocol's Governor.\n * @dev Copy of the openzeppelin Ownable.sol contract with nomenclature change\n * from owner to governor and renounce methods removed. Does not use\n * Context.sol like Ownable.sol does for simplification.\n * @author Origin Protocol Inc\n */\nabstract contract Governable {\n // Storage position of the owner and pendingOwner of the contract\n // keccak256(\"OUSD.governor\");\n bytes32 private constant governorPosition =\n 0x7bea13895fa79d2831e0a9e28edede30099005a50d652d8957cf8a607ee6ca4a;\n\n // keccak256(\"OUSD.pending.governor\");\n bytes32 private constant pendingGovernorPosition =\n 0x44c4d30b2eaad5130ad70c3ba6972730566f3e6359ab83e800d905c61b1c51db;\n\n // keccak256(\"OUSD.reentry.status\");\n bytes32 private constant reentryStatusPosition =\n 0x53bf423e48ed90e97d02ab0ebab13b2a235a6bfbe9c321847d5c175333ac4535;\n\n // See OpenZeppelin ReentrancyGuard implementation\n uint256 constant _NOT_ENTERED = 1;\n uint256 constant _ENTERED = 2;\n\n event PendingGovernorshipTransfer(\n address indexed previousGovernor,\n address indexed newGovernor\n );\n\n event GovernorshipTransferred(\n address indexed previousGovernor,\n address indexed newGovernor\n );\n\n /**\n * @notice Returns the address of the current Governor.\n */\n function governor() public view returns (address) {\n return _governor();\n }\n\n /**\n * @dev Returns the address of the current Governor.\n */\n function _governor() internal view returns (address governorOut) {\n bytes32 position = governorPosition;\n // solhint-disable-next-line no-inline-assembly\n assembly {\n governorOut := sload(position)\n }\n }\n\n /**\n * @dev Returns the address of the pending Governor.\n */\n function _pendingGovernor()\n internal\n view\n returns (address pendingGovernor)\n {\n bytes32 position = pendingGovernorPosition;\n // solhint-disable-next-line no-inline-assembly\n assembly {\n pendingGovernor := sload(position)\n }\n }\n\n /**\n * @dev Throws if called by any account other than the Governor.\n */\n modifier onlyGovernor() {\n require(isGovernor(), \"Caller is not the Governor\");\n _;\n }\n\n /**\n * @notice Returns true if the caller is the current Governor.\n */\n function isGovernor() public view returns (bool) {\n return msg.sender == _governor();\n }\n\n function _setGovernor(address newGovernor) internal {\n emit GovernorshipTransferred(_governor(), newGovernor);\n\n bytes32 position = governorPosition;\n // solhint-disable-next-line no-inline-assembly\n assembly {\n sstore(position, newGovernor)\n }\n }\n\n /**\n * @dev Prevents a contract from calling itself, directly or indirectly.\n * Calling a `nonReentrant` function from another `nonReentrant`\n * function is not supported. It is possible to prevent this from happening\n * by making the `nonReentrant` function external, and make it call a\n * `private` function that does the actual work.\n */\n modifier nonReentrant() {\n bytes32 position = reentryStatusPosition;\n uint256 _reentry_status;\n // solhint-disable-next-line no-inline-assembly\n assembly {\n _reentry_status := sload(position)\n }\n\n // On the first call to nonReentrant, _notEntered will be true\n require(_reentry_status != _ENTERED, \"Reentrant call\");\n\n // Any calls to nonReentrant after this point will fail\n // solhint-disable-next-line no-inline-assembly\n assembly {\n sstore(position, _ENTERED)\n }\n\n _;\n\n // By storing the original value once again, a refund is triggered (see\n // https://eips.ethereum.org/EIPS/eip-2200)\n // solhint-disable-next-line no-inline-assembly\n assembly {\n sstore(position, _NOT_ENTERED)\n }\n }\n\n function _setPendingGovernor(address newGovernor) internal {\n bytes32 position = pendingGovernorPosition;\n // solhint-disable-next-line no-inline-assembly\n assembly {\n sstore(position, newGovernor)\n }\n }\n\n /**\n * @notice Transfers Governance of the contract to a new account (`newGovernor`).\n * Can only be called by the current Governor. Must be claimed for this to complete\n * @param _newGovernor Address of the new Governor\n */\n function transferGovernance(address _newGovernor) external onlyGovernor {\n _setPendingGovernor(_newGovernor);\n emit PendingGovernorshipTransfer(_governor(), _newGovernor);\n }\n\n /**\n * @notice Claim Governance of the contract to a new account (`newGovernor`).\n * Can only be called by the new Governor.\n */\n function claimGovernance() external {\n require(\n msg.sender == _pendingGovernor(),\n \"Only the pending Governor can complete the claim\"\n );\n _changeGovernor(msg.sender);\n }\n\n /**\n * @dev Change Governance of the contract to a new account (`newGovernor`).\n * @param _newGovernor Address of the new Governor\n */\n function _changeGovernor(address _newGovernor) internal {\n require(_newGovernor != address(0), \"New Governor is address(0)\");\n _setGovernor(_newGovernor);\n }\n}\n" + }, + "contracts/governance/Strategizable.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { Governable } from \"./Governable.sol\";\n\ncontract Strategizable is Governable {\n event StrategistUpdated(address _address);\n\n // Address of strategist\n address public strategistAddr;\n\n // For future use\n uint256[50] private __gap;\n\n /**\n * @dev Verifies that the caller is either Governor or Strategist.\n */\n modifier onlyGovernorOrStrategist() virtual {\n require(\n msg.sender == strategistAddr || isGovernor(),\n \"Caller is not the Strategist or Governor\"\n );\n _;\n }\n\n /**\n * @dev Set address of Strategist\n * @param _address Address of Strategist\n */\n function setStrategistAddr(address _address) external onlyGovernor {\n _setStrategistAddr(_address);\n }\n\n /**\n * @dev Set address of Strategist\n * @param _address Address of Strategist\n */\n function _setStrategistAddr(address _address) internal {\n strategistAddr = _address;\n emit StrategistUpdated(_address);\n }\n}\n" + }, + "contracts/harvest/AbstractHarvester.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport { SafeMath } from \"@openzeppelin/contracts/utils/math/SafeMath.sol\";\nimport \"@openzeppelin/contracts/utils/math/Math.sol\";\n\nimport { StableMath } from \"../utils/StableMath.sol\";\nimport { Governable } from \"../governance/Governable.sol\";\nimport { IVault } from \"../interfaces/IVault.sol\";\nimport { IOracle } from \"../interfaces/IOracle.sol\";\nimport { IStrategy } from \"../interfaces/IStrategy.sol\";\nimport { IUniswapV2Router } from \"../interfaces/uniswap/IUniswapV2Router02.sol\";\nimport { IUniswapV3Router } from \"../interfaces/uniswap/IUniswapV3Router.sol\";\nimport { IBalancerVault } from \"../interfaces/balancer/IBalancerVault.sol\";\nimport { ICurvePool } from \"../strategies/ICurvePool.sol\";\nimport \"../utils/Helpers.sol\";\n\nabstract contract AbstractHarvester is Governable {\n using SafeERC20 for IERC20;\n using SafeMath for uint256;\n using StableMath for uint256;\n\n enum SwapPlatform {\n UniswapV2Compatible,\n UniswapV3,\n Balancer,\n Curve\n }\n\n event SupportedStrategyUpdate(address strategyAddress, bool isSupported);\n event RewardTokenConfigUpdated(\n address tokenAddress,\n uint16 allowedSlippageBps,\n uint16 harvestRewardBps,\n SwapPlatform swapPlatform,\n address swapPlatformAddr,\n bytes swapData,\n uint256 liquidationLimit,\n bool doSwapRewardToken\n );\n event RewardTokenSwapped(\n address indexed rewardToken,\n address indexed swappedInto,\n SwapPlatform swapPlatform,\n uint256 amountIn,\n uint256 amountOut\n );\n event RewardProceedsTransferred(\n address indexed token,\n address farmer,\n uint256 protcolYield,\n uint256 farmerFee\n );\n event RewardProceedsAddressChanged(address newProceedsAddress);\n\n error EmptyAddress();\n error InvalidSlippageBps();\n error InvalidHarvestRewardBps();\n\n error InvalidSwapPlatform(SwapPlatform swapPlatform);\n\n error InvalidUniswapV2PathLength();\n error InvalidTokenInSwapPath(address token);\n error EmptyBalancerPoolId();\n error InvalidCurvePoolAssetIndex(address token);\n\n error UnsupportedStrategy(address strategyAddress);\n\n error SlippageError(uint256 actualBalance, uint256 minExpected);\n error BalanceMismatchAfterSwap(uint256 actualBalance, uint256 minExpected);\n\n // Configuration properties for harvesting logic of reward tokens\n struct RewardTokenConfig {\n // Max allowed slippage when swapping reward token for a stablecoin denominated in basis points.\n uint16 allowedSlippageBps;\n // Reward when calling a harvest function denominated in basis points.\n uint16 harvestRewardBps;\n // Address of compatible exchange protocol (Uniswap V2/V3, SushiSwap, Balancer and Curve).\n address swapPlatformAddr;\n /* When true the reward token is being swapped. In a need of (temporarily) disabling the swapping of\n * a reward token this needs to be set to false.\n */\n bool doSwapRewardToken;\n // Platform to use for Swapping\n SwapPlatform swapPlatform;\n /* How much token can be sold per one harvest call. If the balance of rewards tokens\n * exceeds that limit multiple harvest calls are required to harvest all of the tokens.\n * Set it to MAX_INT to effectively disable the limit.\n */\n uint256 liquidationLimit;\n }\n\n mapping(address => RewardTokenConfig) public rewardTokenConfigs;\n mapping(address => bool) public supportedStrategies;\n\n address public immutable vaultAddress;\n\n /**\n * Address receiving rewards proceeds. Initially the Vault contract later will possibly\n * be replaced by another contract that eases out rewards distribution.\n **/\n address public rewardProceedsAddress;\n\n /**\n * All tokens are swapped to this token before it gets transferred\n * to the `rewardProceedsAddress`. USDT for OUSD and WETH for OETH.\n **/\n address public immutable baseTokenAddress;\n // Cached decimals for `baseTokenAddress`\n uint256 public immutable baseTokenDecimals;\n\n // Uniswap V2 path for reward tokens using Uniswap V2 Router\n mapping(address => address[]) public uniswapV2Path;\n // Uniswap V3 path for reward tokens using Uniswap V3 Router\n mapping(address => bytes) public uniswapV3Path;\n // Pool ID to use for reward tokens on Balancer\n mapping(address => bytes32) public balancerPoolId;\n\n struct CurvePoolIndices {\n // Casted into uint128 and stored in a struct to save gas\n uint128 rewardTokenIndex;\n uint128 baseTokenIndex;\n }\n // Packed indices of assets on the Curve pool\n mapping(address => CurvePoolIndices) public curvePoolIndices;\n\n constructor(address _vaultAddress, address _baseTokenAddress) {\n require(_vaultAddress != address(0));\n require(_baseTokenAddress != address(0));\n\n vaultAddress = _vaultAddress;\n baseTokenAddress = _baseTokenAddress;\n\n // Cache decimals as well\n baseTokenDecimals = Helpers.getDecimals(_baseTokenAddress);\n }\n\n /***************************************\n Configuration\n ****************************************/\n\n /**\n * Set the Address receiving rewards proceeds.\n * @param _rewardProceedsAddress Address of the reward token\n */\n function setRewardProceedsAddress(address _rewardProceedsAddress)\n external\n onlyGovernor\n {\n if (_rewardProceedsAddress == address(0)) {\n revert EmptyAddress();\n }\n\n rewardProceedsAddress = _rewardProceedsAddress;\n emit RewardProceedsAddressChanged(_rewardProceedsAddress);\n }\n\n /**\n * @dev Add/update a reward token configuration that holds harvesting config variables\n * @param _tokenAddress Address of the reward token\n * @param tokenConfig.allowedSlippageBps uint16 maximum allowed slippage denominated in basis points.\n * Example: 300 == 3% slippage\n * @param tokenConfig.harvestRewardBps uint16 amount of reward tokens the caller of the function is rewarded.\n * Example: 100 == 1%\n * @param tokenConfig.swapPlatformAddr Address Address of a UniswapV2 compatible contract to perform\n * the exchange from reward tokens to stablecoin (currently hard-coded to USDT)\n * @param tokenConfig.liquidationLimit uint256 Maximum amount of token to be sold per one swap function call.\n * When value is 0 there is no limit.\n * @param tokenConfig.doSwapRewardToken bool Disables swapping of the token when set to true,\n * does not cause it to revert though.\n * @param tokenConfig.swapPlatform SwapPlatform to use for Swapping\n * @param swapData Additional data required for swapping\n */\n function setRewardTokenConfig(\n address _tokenAddress,\n RewardTokenConfig calldata tokenConfig,\n bytes calldata swapData\n ) external onlyGovernor {\n if (tokenConfig.allowedSlippageBps > 1000) {\n revert InvalidSlippageBps();\n }\n\n if (tokenConfig.harvestRewardBps > 1000) {\n revert InvalidHarvestRewardBps();\n }\n\n address newRouterAddress = tokenConfig.swapPlatformAddr;\n if (newRouterAddress == address(0)) {\n // Swap router address should be non zero address\n revert EmptyAddress();\n }\n\n address oldRouterAddress = rewardTokenConfigs[_tokenAddress]\n .swapPlatformAddr;\n rewardTokenConfigs[_tokenAddress] = tokenConfig;\n\n // Revert if feed does not exist\n // slither-disable-next-line unused-return\n\n IERC20 token = IERC20(_tokenAddress);\n // if changing token swap provider cancel existing allowance\n if (\n /* oldRouterAddress == address(0) when there is no pre-existing\n * configuration for said rewards token\n */\n oldRouterAddress != address(0) &&\n oldRouterAddress != newRouterAddress\n ) {\n token.safeApprove(oldRouterAddress, 0);\n }\n\n // Give SwapRouter infinite approval when needed\n if (oldRouterAddress != newRouterAddress) {\n token.safeApprove(newRouterAddress, 0);\n token.safeApprove(newRouterAddress, type(uint256).max);\n }\n\n SwapPlatform _platform = tokenConfig.swapPlatform;\n if (_platform == SwapPlatform.UniswapV2Compatible) {\n uniswapV2Path[_tokenAddress] = _decodeUniswapV2Path(\n swapData,\n _tokenAddress\n );\n } else if (_platform == SwapPlatform.UniswapV3) {\n uniswapV3Path[_tokenAddress] = _decodeUniswapV3Path(\n swapData,\n _tokenAddress\n );\n } else if (_platform == SwapPlatform.Balancer) {\n balancerPoolId[_tokenAddress] = _decodeBalancerPoolId(\n swapData,\n newRouterAddress,\n _tokenAddress\n );\n } else if (_platform == SwapPlatform.Curve) {\n curvePoolIndices[_tokenAddress] = _decodeCurvePoolIndices(\n swapData,\n newRouterAddress,\n _tokenAddress\n );\n } else {\n // Note: This code is unreachable since Solidity reverts when\n // the value is outside the range of defined values of the enum\n // (even if it's under the max length of the base type)\n revert InvalidSwapPlatform(_platform);\n }\n\n emit RewardTokenConfigUpdated(\n _tokenAddress,\n tokenConfig.allowedSlippageBps,\n tokenConfig.harvestRewardBps,\n _platform,\n newRouterAddress,\n swapData,\n tokenConfig.liquidationLimit,\n tokenConfig.doSwapRewardToken\n );\n }\n\n /**\n * @dev Decodes the data passed into Uniswap V2 path and validates\n * it to make sure the path is for `token` to `baseToken`\n *\n * @param data Ecnoded data passed to the `setRewardTokenConfig`\n * @param token The address of the reward token\n * @return path The validated Uniswap V2 path\n */\n function _decodeUniswapV2Path(bytes calldata data, address token)\n internal\n view\n returns (address[] memory path)\n {\n (path) = abi.decode(data, (address[]));\n uint256 len = path.length;\n\n if (len < 2) {\n // Path should have at least two tokens\n revert InvalidUniswapV2PathLength();\n }\n\n // Do some validation\n if (path[0] != token) {\n revert InvalidTokenInSwapPath(path[0]);\n }\n\n if (path[len - 1] != baseTokenAddress) {\n revert InvalidTokenInSwapPath(path[len - 1]);\n }\n }\n\n /**\n * @dev Decodes the data passed into Uniswap V3 path and validates\n * it to make sure the path is for `token` to `baseToken`\n *\n * @param data Ecnoded data passed to the `setRewardTokenConfig`\n * @param token The address of the reward token\n * @return path The validated Uniswap V3 path\n */\n function _decodeUniswapV3Path(bytes calldata data, address token)\n internal\n view\n returns (bytes calldata path)\n {\n path = data;\n\n address decodedAddress = address(uint160(bytes20(data[0:20])));\n\n if (decodedAddress != token) {\n // Invalid Reward Token in swap path\n revert InvalidTokenInSwapPath(decodedAddress);\n }\n\n decodedAddress = address(uint160(bytes20(data[path.length - 20:])));\n if (decodedAddress != baseTokenAddress) {\n // Invalid Base Token in swap path\n revert InvalidTokenInSwapPath(decodedAddress);\n }\n }\n\n /**\n * @dev Decodes the data passed to Balancer Pool ID\n *\n * @param data Ecnoded data passed to the `setRewardTokenConfig`\n * @return poolId The pool ID\n */\n function _decodeBalancerPoolId(\n bytes calldata data,\n address balancerVault,\n address token\n ) internal view returns (bytes32 poolId) {\n (poolId) = abi.decode(data, (bytes32));\n\n if (poolId == bytes32(0)) {\n revert EmptyBalancerPoolId();\n }\n\n IBalancerVault bVault = IBalancerVault(balancerVault);\n\n // Note: this reverts if token is not a pool asset\n // slither-disable-next-line unused-return\n bVault.getPoolTokenInfo(poolId, token);\n\n // slither-disable-next-line unused-return\n bVault.getPoolTokenInfo(poolId, baseTokenAddress);\n }\n\n /**\n * @dev Decodes the data passed to get the pool indices and\n * checks it against the Curve Pool to make sure it's\n * not misconfigured. The indices are packed into a single\n * uint256 for gas savings\n *\n * @param data Ecnoded data passed to the `setRewardTokenConfig`\n * @param poolAddress Curve pool address\n * @param token The address of the reward token\n * @return indices Packed pool asset indices\n */\n function _decodeCurvePoolIndices(\n bytes calldata data,\n address poolAddress,\n address token\n ) internal view returns (CurvePoolIndices memory indices) {\n indices = abi.decode(data, (CurvePoolIndices));\n\n ICurvePool pool = ICurvePool(poolAddress);\n if (token != pool.coins(indices.rewardTokenIndex)) {\n revert InvalidCurvePoolAssetIndex(token);\n }\n if (baseTokenAddress != pool.coins(indices.baseTokenIndex)) {\n revert InvalidCurvePoolAssetIndex(baseTokenAddress);\n }\n }\n\n /**\n * @dev Flags a strategy as supported or not supported one\n * @param _strategyAddress Address of the strategy\n * @param _isSupported Bool marking strategy as supported or not supported\n */\n function setSupportedStrategy(address _strategyAddress, bool _isSupported)\n external\n onlyGovernor\n {\n supportedStrategies[_strategyAddress] = _isSupported;\n emit SupportedStrategyUpdate(_strategyAddress, _isSupported);\n }\n\n /***************************************\n Rewards\n ****************************************/\n\n /**\n * @dev Transfer token to governor. Intended for recovering tokens stuck in\n * contract, i.e. mistaken sends.\n * @param _asset Address for the asset\n * @param _amount Amount of the asset to transfer\n */\n function transferToken(address _asset, uint256 _amount)\n external\n onlyGovernor\n {\n IERC20(_asset).safeTransfer(governor(), _amount);\n }\n\n /**\n * @dev Collect reward tokens from a specific strategy and swap them for\n * base token on the configured swap platform. Can be called by anyone.\n * Rewards incentivizing the caller are sent to the caller of this function.\n * @param _strategyAddr Address of the strategy to collect rewards from\n */\n function harvestAndSwap(address _strategyAddr) external nonReentrant {\n // Remember _harvest function checks for the validity of _strategyAddr\n _harvestAndSwap(_strategyAddr, msg.sender);\n }\n\n /**\n * @dev Collect reward tokens from a specific strategy and swap them for\n * base token on the configured swap platform. Can be called by anyone\n * @param _strategyAddr Address of the strategy to collect rewards from\n * @param _rewardTo Address where to send a share of harvest rewards to as an incentive\n * for executing this function\n */\n function harvestAndSwap(address _strategyAddr, address _rewardTo)\n external\n nonReentrant\n {\n // Remember _harvest function checks for the validity of _strategyAddr\n _harvestAndSwap(_strategyAddr, _rewardTo);\n }\n\n /**\n * @dev Collect reward tokens from a specific strategy and swap them for\n * base token on the configured swap platform\n * @param _strategyAddr Address of the strategy to collect rewards from\n * @param _rewardTo Address where to send a share of harvest rewards to as an incentive\n * for executing this function\n */\n function _harvestAndSwap(address _strategyAddr, address _rewardTo)\n internal\n {\n _harvest(_strategyAddr);\n IStrategy strategy = IStrategy(_strategyAddr);\n address[] memory rewardTokens = strategy.getRewardTokenAddresses();\n uint256 len = rewardTokens.length;\n for (uint256 i = 0; i < len; ++i) {\n // This harvester contract is not used anymore. Keeping the code\n // for passing test deployment. Safe to use address(0x1) as oracle.\n _swap(rewardTokens[i], _rewardTo, IOracle(address(0x1)));\n }\n }\n\n /**\n * @dev Collect reward tokens from a specific strategy and swap them for\n * base token on the configured swap platform\n * @param _strategyAddr Address of the strategy to collect rewards from.\n */\n function _harvest(address _strategyAddr) internal virtual {\n if (!supportedStrategies[_strategyAddr]) {\n revert UnsupportedStrategy(_strategyAddr);\n }\n\n IStrategy strategy = IStrategy(_strategyAddr);\n strategy.collectRewardTokens();\n }\n\n /**\n * @dev Swap a reward token for the base token on the configured\n * swap platform. The token must have a registered price feed\n * with the price provider\n * @param _swapToken Address of the token to swap\n * @param _rewardTo Address where to send the share of harvest rewards to\n * @param _priceProvider Oracle to get prices of the swap token\n */\n function _swap(\n address _swapToken,\n address _rewardTo,\n IOracle _priceProvider\n ) internal virtual {\n uint256 balance = IERC20(_swapToken).balanceOf(address(this));\n\n // No need to swap if the reward token is the base token. eg USDT or WETH.\n // There is also no limit on the transfer. Everything in the harvester will be transferred\n // to the Dripper regardless of the liquidationLimit config.\n if (_swapToken == baseTokenAddress) {\n IERC20(_swapToken).safeTransfer(rewardProceedsAddress, balance);\n // currently not paying the farmer any rewards as there is no swap\n emit RewardProceedsTransferred(\n baseTokenAddress,\n address(0),\n balance,\n 0\n );\n return;\n }\n\n RewardTokenConfig memory tokenConfig = rewardTokenConfigs[_swapToken];\n\n /* This will trigger a return when reward token configuration has not yet been set\n * or we have temporarily disabled swapping of specific reward token via setting\n * doSwapRewardToken to false.\n */\n if (!tokenConfig.doSwapRewardToken) {\n return;\n }\n\n if (balance == 0) {\n return;\n }\n\n if (tokenConfig.liquidationLimit > 0) {\n balance = Math.min(balance, tokenConfig.liquidationLimit);\n }\n\n // This'll revert if there is no price feed\n uint256 oraclePrice = _priceProvider.price(_swapToken);\n\n // Oracle price is 1e18\n uint256 minExpected = (balance *\n (1e4 - tokenConfig.allowedSlippageBps) * // max allowed slippage\n oraclePrice).scaleBy(\n baseTokenDecimals,\n Helpers.getDecimals(_swapToken)\n ) /\n 1e4 / // fix the max slippage decimal position\n 1e18; // and oracle price decimals position\n\n // Do the swap\n uint256 amountReceived = _doSwap(\n tokenConfig.swapPlatform,\n tokenConfig.swapPlatformAddr,\n _swapToken,\n balance,\n minExpected\n );\n\n if (amountReceived < minExpected) {\n revert SlippageError(amountReceived, minExpected);\n }\n\n emit RewardTokenSwapped(\n _swapToken,\n baseTokenAddress,\n tokenConfig.swapPlatform,\n balance,\n amountReceived\n );\n\n IERC20 baseToken = IERC20(baseTokenAddress);\n uint256 baseTokenBalance = baseToken.balanceOf(address(this));\n if (baseTokenBalance < amountReceived) {\n // Note: It's possible to bypass this check by transferring `baseToken`\n // directly to Harvester before calling the `harvestAndSwap`. However,\n // there's no incentive for an attacker to do that. Doing a balance diff\n // will increase the gas cost significantly\n revert BalanceMismatchAfterSwap(baseTokenBalance, amountReceived);\n }\n\n // Farmer only gets fee from the base amount they helped farm,\n // They do not get anything from anything that already was there\n // on the Harvester\n uint256 farmerFee = amountReceived.mulTruncateScale(\n tokenConfig.harvestRewardBps,\n 1e4\n );\n uint256 protocolYield = baseTokenBalance - farmerFee;\n\n baseToken.safeTransfer(rewardProceedsAddress, protocolYield);\n baseToken.safeTransfer(_rewardTo, farmerFee);\n emit RewardProceedsTransferred(\n baseTokenAddress,\n _rewardTo,\n protocolYield,\n farmerFee\n );\n }\n\n function _doSwap(\n SwapPlatform swapPlatform,\n address routerAddress,\n address rewardTokenAddress,\n uint256 amountIn,\n uint256 minAmountOut\n ) internal returns (uint256 amountOut) {\n if (swapPlatform == SwapPlatform.UniswapV2Compatible) {\n return\n _swapWithUniswapV2(\n routerAddress,\n rewardTokenAddress,\n amountIn,\n minAmountOut\n );\n } else if (swapPlatform == SwapPlatform.UniswapV3) {\n return\n _swapWithUniswapV3(\n routerAddress,\n rewardTokenAddress,\n amountIn,\n minAmountOut\n );\n } else if (swapPlatform == SwapPlatform.Balancer) {\n return\n _swapWithBalancer(\n routerAddress,\n rewardTokenAddress,\n amountIn,\n minAmountOut\n );\n } else if (swapPlatform == SwapPlatform.Curve) {\n return\n _swapWithCurve(\n routerAddress,\n rewardTokenAddress,\n amountIn,\n minAmountOut\n );\n } else {\n // Should never be invoked since we catch invalid values\n // in the `setRewardTokenConfig` function before it's set\n revert InvalidSwapPlatform(swapPlatform);\n }\n }\n\n /**\n * @dev Swaps the token to `baseToken` with Uniswap V2\n *\n * @param routerAddress Uniswap V2 Router address\n * @param swapToken Address of the tokenIn\n * @param amountIn Amount of `swapToken` to swap\n * @param minAmountOut Minimum expected amount of `baseToken`\n *\n * @return amountOut Amount of `baseToken` received after the swap\n */\n function _swapWithUniswapV2(\n address routerAddress,\n address swapToken,\n uint256 amountIn,\n uint256 minAmountOut\n ) internal returns (uint256 amountOut) {\n address[] memory path = uniswapV2Path[swapToken];\n\n uint256[] memory amounts = IUniswapV2Router(routerAddress)\n .swapExactTokensForTokens(\n amountIn,\n minAmountOut,\n path,\n address(this),\n block.timestamp\n );\n\n amountOut = amounts[amounts.length - 1];\n }\n\n /**\n * @dev Swaps the token to `baseToken` with Uniswap V3\n *\n * @param routerAddress Uniswap V3 Router address\n * @param swapToken Address of the tokenIn\n * @param amountIn Amount of `swapToken` to swap\n * @param minAmountOut Minimum expected amount of `baseToken`\n *\n * @return amountOut Amount of `baseToken` received after the swap\n */\n function _swapWithUniswapV3(\n address routerAddress,\n address swapToken,\n uint256 amountIn,\n uint256 minAmountOut\n ) internal returns (uint256 amountOut) {\n bytes memory path = uniswapV3Path[swapToken];\n\n IUniswapV3Router.ExactInputParams memory params = IUniswapV3Router\n .ExactInputParams({\n path: path,\n recipient: address(this),\n deadline: block.timestamp,\n amountIn: amountIn,\n amountOutMinimum: minAmountOut\n });\n amountOut = IUniswapV3Router(routerAddress).exactInput(params);\n }\n\n /**\n * @dev Swaps the token to `baseToken` on Balancer\n *\n * @param balancerVaultAddress BalancerVaultAddress\n * @param swapToken Address of the tokenIn\n * @param amountIn Amount of `swapToken` to swap\n * @param minAmountOut Minimum expected amount of `baseToken`\n *\n * @return amountOut Amount of `baseToken` received after the swap\n */\n function _swapWithBalancer(\n address balancerVaultAddress,\n address swapToken,\n uint256 amountIn,\n uint256 minAmountOut\n ) internal returns (uint256 amountOut) {\n bytes32 poolId = balancerPoolId[swapToken];\n\n IBalancerVault.SingleSwap memory singleSwap = IBalancerVault\n .SingleSwap({\n poolId: poolId,\n kind: IBalancerVault.SwapKind.GIVEN_IN,\n assetIn: swapToken,\n assetOut: baseTokenAddress,\n amount: amountIn,\n userData: hex\"\"\n });\n\n IBalancerVault.FundManagement memory fundMgmt = IBalancerVault\n .FundManagement({\n sender: address(this),\n fromInternalBalance: false,\n recipient: payable(address(this)),\n toInternalBalance: false\n });\n\n amountOut = IBalancerVault(balancerVaultAddress).swap(\n singleSwap,\n fundMgmt,\n minAmountOut,\n block.timestamp\n );\n }\n\n /**\n * @dev Swaps the token to `baseToken` on Curve\n *\n * @param poolAddress Curve Pool Address\n * @param swapToken Address of the tokenIn\n * @param amountIn Amount of `swapToken` to swap\n * @param minAmountOut Minimum expected amount of `baseToken`\n *\n * @return amountOut Amount of `baseToken` received after the swap\n */\n function _swapWithCurve(\n address poolAddress,\n address swapToken,\n uint256 amountIn,\n uint256 minAmountOut\n ) internal returns (uint256 amountOut) {\n CurvePoolIndices memory indices = curvePoolIndices[swapToken];\n\n // Note: Not all CurvePools return the `amountOut`, make sure\n // to use only pool that do. Otherwise the swap would revert\n // always\n amountOut = ICurvePool(poolAddress).exchange(\n uint256(indices.rewardTokenIndex),\n uint256(indices.baseTokenIndex),\n amountIn,\n minAmountOut\n );\n }\n}\n" + }, + "contracts/harvest/Harvester.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { AbstractHarvester } from \"./AbstractHarvester.sol\";\n\ncontract Harvester is AbstractHarvester {\n constructor(address _vault, address _usdtAddress)\n AbstractHarvester(_vault, _usdtAddress)\n {}\n}\n" + }, + "contracts/harvest/OETHHarvester.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { AbstractHarvester } from \"./AbstractHarvester.sol\";\n\ncontract OETHHarvester is AbstractHarvester {\n constructor(address _vault, address _wethAddress)\n AbstractHarvester(_vault, _wethAddress)\n {}\n}\n" + }, + "contracts/harvest/OETHHarvesterSimple.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { Strategizable } from \"../governance/Strategizable.sol\";\nimport { IStrategy } from \"../interfaces/IStrategy.sol\";\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport { Initializable } from \"../utils/Initializable.sol\";\n\n/// @title OETH Harvester Simple Contract\n/// @notice Contract to harvest rewards from strategies\n/// @author Origin Protocol Inc\ncontract OETHHarvesterSimple is Initializable, Strategizable {\n using SafeERC20 for IERC20;\n\n ////////////////////////////////////////////////////\n /// --- CONSTANTS & IMMUTABLES\n ////////////////////////////////////////////////////\n /// @notice wrapped native token address (WETH or wS)\n address public immutable wrappedNativeToken;\n\n ////////////////////////////////////////////////////\n /// --- STORAGE\n ////////////////////////////////////////////////////\n /// @notice Dripper address\n address public dripper;\n\n /// @notice Mapping of supported strategies\n mapping(address => bool) public supportedStrategies;\n\n /// @notice Gap for upgrade safety\n uint256[48] private ___gap;\n\n ////////////////////////////////////////////////////\n /// --- EVENTS\n ////////////////////////////////////////////////////\n event Harvested(\n address indexed strategy,\n address token,\n uint256 amount,\n address indexed receiver\n );\n event SupportedStrategyUpdated(address strategy, bool status);\n event DripperUpdated(address dripper);\n\n ////////////////////////////////////////////////////\n /// --- CONSTRUCTOR\n ////////////////////////////////////////////////////\n constructor(address _wrappedNativeToken) {\n wrappedNativeToken = _wrappedNativeToken;\n\n // prevent implementation contract to be governed\n _setGovernor(address(0));\n }\n\n /// @notice Initialize the contract\n function initialize() external onlyGovernor initializer {\n // Call it to set `initialized` to true and to prevent the implementation\n // from getting initialized in future through the proxy\n }\n\n ////////////////////////////////////////////////////\n /// --- MUTATIVE FUNCTIONS\n ////////////////////////////////////////////////////\n /// @notice Harvest rewards from a strategy and transfer to strategist or dripper\n /// @param _strategy Address of the strategy to harvest\n function harvestAndTransfer(address _strategy) external {\n _harvestAndTransfer(_strategy);\n }\n\n /// @notice Harvest rewards from multiple strategies and transfer to strategist or dripper\n /// @param _strategies Array of strategy addresses to harvest\n function harvestAndTransfer(address[] calldata _strategies) external {\n for (uint256 i = 0; i < _strategies.length; i++) {\n _harvestAndTransfer(_strategies[i]);\n }\n }\n\n /// @notice Internal logic to harvest rewards from a strategy\n function _harvestAndTransfer(address _strategy) internal virtual {\n // Ensure strategy is supported\n require(supportedStrategies[_strategy], \"Strategy not supported\");\n\n // Store locally for some gas savings\n address _strategist = strategistAddr;\n address _dripper = dripper;\n\n // Harvest rewards\n IStrategy(_strategy).collectRewardTokens();\n\n // Cache reward tokens\n address[] memory rewardTokens = IStrategy(_strategy)\n .getRewardTokenAddresses();\n\n uint256 len = rewardTokens.length;\n for (uint256 i = 0; i < len; i++) {\n // Cache balance\n address token = rewardTokens[i];\n uint256 balance = IERC20(token).balanceOf(address(this));\n if (balance > 0) {\n // Determine receiver\n address receiver = token == wrappedNativeToken\n ? _dripper\n : _strategist;\n require(receiver != address(0), \"Invalid receiver\");\n\n // Transfer to the Strategist or the Dripper\n IERC20(token).safeTransfer(receiver, balance);\n emit Harvested(_strategy, token, balance, receiver);\n }\n }\n }\n\n ////////////////////////////////////////////////////\n /// --- GOVERNANCE\n ////////////////////////////////////////////////////\n /// @notice Set supported strategy\n /// @param _strategy Address of the strategy\n /// @param _isSupported Boolean indicating if strategy is supported\n function setSupportedStrategy(address _strategy, bool _isSupported)\n external\n onlyGovernorOrStrategist\n {\n require(_strategy != address(0), \"Invalid strategy\");\n supportedStrategies[_strategy] = _isSupported;\n emit SupportedStrategyUpdated(_strategy, _isSupported);\n }\n\n /// @notice Transfer tokens to strategist\n /// @param _asset Address of the token\n /// @param _amount Amount of tokens to transfer\n function transferToken(address _asset, uint256 _amount)\n external\n onlyGovernorOrStrategist\n {\n IERC20(_asset).safeTransfer(strategistAddr, _amount);\n }\n\n /// @notice Set the dripper address\n /// @param _dripper Address of the dripper\n function setDripper(address _dripper) external onlyGovernor {\n _setDripper(_dripper);\n }\n\n /// @notice Internal logic to set the dripper address\n function _setDripper(address _dripper) internal {\n require(_dripper != address(0), \"Invalid dripper\");\n dripper = _dripper;\n emit DripperUpdated(_dripper);\n }\n}\n" + }, + "contracts/harvest/OSonicHarvester.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { SuperOETHHarvester } from \"./SuperOETHHarvester.sol\";\n\ncontract OSonicHarvester is SuperOETHHarvester {\n /// @param _wrappedNativeToken Address of the native Wrapped S (wS) token\n constructor(address _wrappedNativeToken)\n SuperOETHHarvester(_wrappedNativeToken)\n {}\n}\n" + }, + "contracts/harvest/SuperOETHHarvester.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { OETHHarvesterSimple, IERC20, IStrategy, SafeERC20 } from \"./OETHHarvesterSimple.sol\";\n\ncontract SuperOETHHarvester is OETHHarvesterSimple {\n using SafeERC20 for IERC20;\n\n constructor(address _wrappedNativeToken)\n OETHHarvesterSimple(_wrappedNativeToken)\n {}\n\n /// @inheritdoc OETHHarvesterSimple\n function _harvestAndTransfer(address _strategy) internal virtual override {\n // Ensure strategy is supported\n require(supportedStrategies[_strategy], \"Strategy not supported\");\n\n address receiver = strategistAddr;\n require(receiver != address(0), \"Invalid receiver\");\n\n // Harvest rewards\n IStrategy(_strategy).collectRewardTokens();\n\n // Cache reward tokens\n address[] memory rewardTokens = IStrategy(_strategy)\n .getRewardTokenAddresses();\n\n uint256 len = rewardTokens.length;\n for (uint256 i = 0; i < len; i++) {\n // Cache balance\n address token = rewardTokens[i];\n uint256 balance = IERC20(token).balanceOf(address(this));\n if (balance > 0) {\n // Transfer everything to the strategist\n IERC20(token).safeTransfer(receiver, balance);\n emit Harvested(_strategy, token, balance, receiver);\n }\n }\n }\n}\n" + }, + "contracts/interfaces/aerodrome/ICLGauge.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity >=0.8.0;\n\ninterface ICLGauge {\n /// @notice Returns the claimable rewards for a given account and tokenId\n /// @dev Throws if account is not the position owner\n /// @dev pool.updateRewardsGrowthGlobal() needs to be called first, to return the correct claimable rewards\n /// @param account The address of the user\n /// @param tokenId The tokenId of the position\n /// @return The amount of claimable reward\n function earned(address account, uint256 tokenId)\n external\n view\n returns (uint256);\n\n /// @notice Retrieve rewards for all tokens owned by an account\n /// @dev Throws if not called by the voter\n /// @param account The account of the user\n function getReward(address account) external;\n\n /// @notice Retrieve rewards for a tokenId\n /// @dev Throws if not called by the position owner\n /// @param tokenId The tokenId of the position\n function getReward(uint256 tokenId) external;\n\n /// @notice Notifies gauge of gauge rewards.\n /// @param amount Amount of gauge rewards (emissions) to notify. Must be greater than 604_800.\n function notifyRewardAmount(uint256 amount) external;\n\n /// @dev Notifies gauge of gauge rewards without distributing its fees.\n /// Assumes gauge reward tokens is 18 decimals.\n /// If not 18 decimals, rewardRate may have rounding issues.\n /// @param amount Amount of gauge rewards (emissions) to notify. Must be greater than 604_800.\n function notifyRewardWithoutClaim(uint256 amount) external;\n\n /// @notice Used to deposit a CL position into the gauge\n /// @notice Allows the user to receive emissions instead of fees\n /// @param tokenId The tokenId of the position\n function deposit(uint256 tokenId) external;\n\n /// @notice Used to withdraw a CL position from the gauge\n /// @notice Allows the user to receive fees instead of emissions\n /// @notice Outstanding emissions will be collected on withdrawal\n /// @param tokenId The tokenId of the position\n function withdraw(uint256 tokenId) external;\n\n // /// @notice Fetch all tokenIds staked by a given account\n // /// @param depositor The address of the user\n // /// @return The tokenIds of the staked positions\n // function stakedValues(address depositor) external view returns (uint256[] memory);\n\n // /// @notice Fetch a staked tokenId by index\n // /// @param depositor The address of the user\n // /// @param index The index of the staked tokenId\n // /// @return The tokenId of the staked position\n // function stakedByIndex(address depositor, uint256 index) external view returns (uint256);\n\n // /// @notice Check whether a position is staked in the gauge by a certain user\n // /// @param depositor The address of the user\n // /// @param tokenId The tokenId of the position\n // /// @return Whether the position is staked in the gauge\n // function stakedContains(address depositor, uint256 tokenId) external view returns (bool);\n\n // /// @notice The amount of positions staked in the gauge by a certain user\n // /// @param depositor The address of the user\n // /// @return The amount of positions staked in the gauge\n // function stakedLength(address depositor) external view returns (uint256);\n\n function feesVotingReward() external view returns (address);\n}\n" + }, + "contracts/interfaces/aerodrome/ICLPool.sol": { + "content": "pragma solidity >=0.5.0;\n\n/// @title The interface for a CL Pool\n/// @notice A CL pool facilitates swapping and automated market making between any two assets that strictly conform\n/// to the ERC20 specification\n/// @dev The pool interface is broken up into many smaller pieces\ninterface ICLPool {\n function slot0()\n external\n view\n returns (\n uint160 sqrtPriceX96,\n int24 tick,\n uint16 observationIndex,\n uint16 observationCardinality,\n uint16 observationCardinalityNext,\n bool unlocked\n );\n\n /// @notice The first of the two tokens of the pool, sorted by address\n /// @return The token contract address\n function token0() external view returns (address);\n\n /// @notice The second of the two tokens of the pool, sorted by address\n /// @return The token contract address\n function token1() external view returns (address);\n\n function tickSpacing() external view returns (int24);\n\n /// @notice The gauge corresponding to this pool\n /// @return The gauge contract address\n function gauge() external view returns (address);\n\n /// @notice The currently in range liquidity available to the pool\n /// @dev This value has no relationship to the total liquidity across all ticks\n /// @dev This value includes staked liquidity\n function liquidity() external view returns (uint128);\n\n /// @notice Look up information about a specific tick in the pool\n /// @param tick The tick to look up\n /// @return liquidityGross the total amount of position liquidity that uses the pool either as tick lower or\n /// tick upper,\n /// liquidityNet how much liquidity changes when the pool price crosses the tick,\n /// feeGrowthOutside0X128 the fee growth on the other side of the tick from the current tick in token0,\n /// feeGrowthOutside1X128 the fee growth on the other side of the tick from the current tick in token1,\n /// tickCumulativeOutside the cumulative tick value on the other side of the tick from the current tick\n /// secondsPerLiquidityOutsideX128 the seconds spent per liquidity on the other side of the tick from\n /// the current tick,\n /// secondsOutside the seconds spent on the other side of the tick from the current tick,\n /// initialized Set to true if the tick is initialized, i.e. liquidityGross is greater than 0, otherwise\n /// equal to false.\n /// Outside values can only be used if the tick is initialized, i.e. if liquidityGross is greater than 0.\n /// In addition, these values are only relative and must be used only in comparison to previous snapshots for\n /// a specific position.\n function ticks(int24 tick)\n external\n view\n returns (\n uint128 liquidityGross,\n int128 liquidityNet,\n uint256 feeGrowthOutside0X128,\n uint256 feeGrowthOutside1X128,\n int56 tickCumulativeOutside,\n uint160 secondsPerLiquidityOutsideX128,\n uint32 secondsOutside,\n bool initialized\n );\n}\n" + }, + "contracts/interfaces/aerodrome/INonfungiblePositionManager.sol": { + "content": "// SPDX-License-Identifier: GPL-2.0-or-later\npragma solidity >=0.7.5;\npragma abicoder v2;\n\n/// @title Non-fungible token for positions\n/// @notice Wraps CL positions in a non-fungible token interface which allows for them to be transferred\n/// and authorized.\n// slither-disable-start erc20-interface\ninterface INonfungiblePositionManager {\n /**\n * @dev See {IERC721-approve}.\n */\n function approve(address to, uint256 tokenId) external;\n\n /**\n * @dev See {IERC721-getApproved}.\n */\n function getApproved(uint256 tokenId) external returns (address);\n\n /**\n * @dev See {IERC721-ownerOf}.\n */\n function ownerOf(uint256 tokenId) external view returns (address);\n\n /// @notice Returns the position information associated with a given token ID.\n /// @dev Throws if the token ID is not valid.\n /// @param tokenId The ID of the token that represents the position\n /// @return nonce The nonce for permits\n /// @return operator The address that is approved for spending\n /// @return token0 The address of the token0 for a specific pool\n /// @return token1 The address of the token1 for a specific pool\n /// @return tickSpacing The tick spacing associated with the pool\n /// @return tickLower The lower end of the tick range for the position\n /// @return tickUpper The higher end of the tick range for the position\n /// @return liquidity The liquidity of the position\n /// @return feeGrowthInside0LastX128 The fee growth of token0 as of the last action on the individual position\n /// @return feeGrowthInside1LastX128 The fee growth of token1 as of the last action on the individual position\n /// @return tokensOwed0 The uncollected amount of token0 owed to the position as of the last computation\n /// @return tokensOwed1 The uncollected amount of token1 owed to the position as of the last computation\n function positions(uint256 tokenId)\n external\n view\n returns (\n uint96 nonce,\n address operator,\n address token0,\n address token1,\n int24 tickSpacing,\n int24 tickLower,\n int24 tickUpper,\n uint128 liquidity,\n uint256 feeGrowthInside0LastX128,\n uint256 feeGrowthInside1LastX128,\n uint128 tokensOwed0,\n uint128 tokensOwed1\n );\n\n struct MintParams {\n address token0;\n address token1;\n int24 tickSpacing;\n int24 tickLower;\n int24 tickUpper;\n uint256 amount0Desired;\n uint256 amount1Desired;\n uint256 amount0Min;\n uint256 amount1Min;\n address recipient;\n uint256 deadline;\n uint160 sqrtPriceX96;\n }\n\n /// @notice Creates a new position wrapped in a NFT\n /// @dev Call this when the pool does exist and is initialized. Note that if the pool is created but not initialized\n /// a method does not exist, i.e. the pool is assumed to be initialized.\n /// @param params The params necessary to mint a position, encoded as `MintParams` in calldata\n /// @return tokenId The ID of the token that represents the minted position\n /// @return liquidity The amount of liquidity for this position\n /// @return amount0 The amount of token0\n /// @return amount1 The amount of token1\n function mint(MintParams calldata params)\n external\n payable\n returns (\n uint256 tokenId,\n uint128 liquidity,\n uint256 amount0,\n uint256 amount1\n );\n\n struct IncreaseLiquidityParams {\n uint256 tokenId;\n uint256 amount0Desired;\n uint256 amount1Desired;\n uint256 amount0Min;\n uint256 amount1Min;\n uint256 deadline;\n }\n\n /// @notice Increases the amount of liquidity in a position, with tokens paid by the `msg.sender`\n /// @param params tokenId The ID of the token for which liquidity is being increased,\n /// amount0Desired The desired amount of token0 to be spent,\n /// amount1Desired The desired amount of token1 to be spent,\n /// amount0Min The minimum amount of token0 to spend, which serves as a slippage check,\n /// amount1Min The minimum amount of token1 to spend, which serves as a slippage check,\n /// deadline The time by which the transaction must be included to effect the change\n /// @return liquidity The new liquidity amount as a result of the increase\n /// @return amount0 The amount of token0 to acheive resulting liquidity\n /// @return amount1 The amount of token1 to acheive resulting liquidity\n function increaseLiquidity(IncreaseLiquidityParams calldata params)\n external\n payable\n returns (\n uint128 liquidity,\n uint256 amount0,\n uint256 amount1\n );\n\n struct DecreaseLiquidityParams {\n uint256 tokenId;\n uint128 liquidity;\n uint256 amount0Min;\n uint256 amount1Min;\n uint256 deadline;\n }\n\n /// @notice Decreases the amount of liquidity in a position and accounts it to the position\n /// @param params tokenId The ID of the token for which liquidity is being decreased,\n /// amount The amount by which liquidity will be decreased,\n /// amount0Min The minimum amount of token0 that should be accounted for the burned liquidity,\n /// amount1Min The minimum amount of token1 that should be accounted for the burned liquidity,\n /// deadline The time by which the transaction must be included to effect the change\n /// @return amount0 The amount of token0 accounted to the position's tokens owed\n /// @return amount1 The amount of token1 accounted to the position's tokens owed\n /// @dev The use of this function can cause a loss to users of the NonfungiblePositionManager\n /// @dev for tokens that have very high decimals.\n /// @dev The amount of tokens necessary for the loss is: 3.4028237e+38.\n /// @dev This is equivalent to 1e20 value with 18 decimals.\n function decreaseLiquidity(DecreaseLiquidityParams calldata params)\n external\n payable\n returns (uint256 amount0, uint256 amount1);\n\n struct CollectParams {\n uint256 tokenId;\n address recipient;\n uint128 amount0Max;\n uint128 amount1Max;\n }\n\n /// @notice Collects up to a maximum amount of fees owed to a specific position to the recipient\n /// @notice Used to update staked positions before deposit and withdraw\n /// @param params tokenId The ID of the NFT for which tokens are being collected,\n /// recipient The account that should receive the tokens,\n /// amount0Max The maximum amount of token0 to collect,\n /// amount1Max The maximum amount of token1 to collect\n /// @return amount0 The amount of fees collected in token0\n /// @return amount1 The amount of fees collected in token1\n function collect(CollectParams calldata params)\n external\n payable\n returns (uint256 amount0, uint256 amount1);\n\n /// @notice Burns a token ID, which deletes it from the NFT contract. The token must have 0 liquidity and all tokens\n /// must be collected first.\n /// @param tokenId The ID of the token that is being burned\n function burn(uint256 tokenId) external payable;\n\n /// @notice Sets a new Token Descriptor\n /// @param _tokenDescriptor Address of the new Token Descriptor to be chosen\n function setTokenDescriptor(address _tokenDescriptor) external;\n\n /// @notice Sets a new Owner address\n /// @param _owner Address of the new Owner to be chosen\n function setOwner(address _owner) external;\n}\n// slither-disable-end erc20-interface\n" + }, + "contracts/interfaces/aerodrome/ISugarHelper.sol": { + "content": "// SPDX-License-Identifier: GPL-2.0-or-later\npragma solidity >=0.5.0;\npragma abicoder v2;\n\nimport { INonfungiblePositionManager } from \"./INonfungiblePositionManager.sol\";\n\ninterface ISugarHelper {\n struct PopulatedTick {\n int24 tick;\n uint160 sqrtRatioX96;\n int128 liquidityNet;\n uint128 liquidityGross;\n }\n\n ///\n /// Wrappers for LiquidityAmounts\n ///\n\n function getAmountsForLiquidity(\n uint160 sqrtRatioX96,\n uint160 sqrtRatioAX96,\n uint160 sqrtRatioBX96,\n uint128 liquidity\n ) external pure returns (uint256 amount0, uint256 amount1);\n\n function getLiquidityForAmounts(\n uint256 amount0,\n uint256 amount1,\n uint160 sqrtRatioX96,\n uint160 sqrtRatioAX96,\n uint160 sqrtRatioBX96\n ) external pure returns (uint128 liquidity);\n\n /// @notice Computes the amount of token0 for a given amount of token1 and price range\n /// @param amount1 Amount of token1 to estimate liquidity\n /// @param pool Address of the pool to be used\n /// @param sqrtRatioX96 A sqrt price representing the current pool prices\n /// @param tickLow Lower tick boundary\n /// @param tickLow Upper tick boundary\n /// @dev If the given pool address is not the zero address, will fetch `sqrtRatioX96` from pool\n /// @return amount0 Estimated amount of token0\n function estimateAmount0(\n uint256 amount1,\n address pool,\n uint160 sqrtRatioX96,\n int24 tickLow,\n int24 tickHigh\n ) external view returns (uint256 amount0);\n\n /// @notice Computes the amount of token1 for a given amount of token0 and price range\n /// @param amount0 Amount of token0 to estimate liquidity\n /// @param pool Address of the pool to be used\n /// @param sqrtRatioX96 A sqrt price representing the current pool prices\n /// @param tickLow Lower tick boundary\n /// @param tickLow Upper tick boundary\n /// @dev If the given pool address is not the zero address, will fetch `sqrtRatioX96` from pool\n /// @return amount1 Estimated amount of token1\n function estimateAmount1(\n uint256 amount0,\n address pool,\n uint160 sqrtRatioX96,\n int24 tickLow,\n int24 tickHigh\n ) external view returns (uint256 amount1);\n\n ///\n /// Wrappers for PositionValue\n ///\n\n function principal(\n INonfungiblePositionManager positionManager,\n uint256 tokenId,\n uint160 sqrtRatioX96\n ) external view returns (uint256 amount0, uint256 amount1);\n\n function fees(INonfungiblePositionManager positionManager, uint256 tokenId)\n external\n view\n returns (uint256 amount0, uint256 amount1);\n\n ///\n /// Wrappers for TickMath\n ///\n\n function getSqrtRatioAtTick(int24 tick)\n external\n pure\n returns (uint160 sqrtRatioX96);\n\n function getTickAtSqrtRatio(uint160 sqrtRatioX96)\n external\n pure\n returns (int24 tick);\n\n /// @notice Fetches Tick Data for all populated Ticks in given bitmaps\n /// @param pool Address of the pool from which to fetch data\n /// @param startTick Tick from which the first bitmap will be fetched\n /// @dev The number of bitmaps fetched by this function should always be `MAX_BITMAPS`,\n /// unless there are less than `MAX_BITMAPS` left to iterate through\n /// @return populatedTicks Array of all Populated Ticks in the provided bitmaps\n function getPopulatedTicks(address pool, int24 startTick)\n external\n view\n returns (PopulatedTick[] memory populatedTicks);\n}\n" + }, + "contracts/interfaces/aerodrome/ISwapRouter.sol": { + "content": "// SPDX-License-Identifier: GPL-2.0-or-later\npragma solidity >=0.7.5;\npragma abicoder v2;\n\n/// @title Router token swapping functionality\n/// @notice Functions for swapping tokens via CL\ninterface ISwapRouter {\n struct ExactInputSingleParams {\n address tokenIn;\n address tokenOut;\n int24 tickSpacing;\n address recipient;\n uint256 deadline;\n uint256 amountIn;\n uint256 amountOutMinimum;\n uint160 sqrtPriceLimitX96;\n }\n\n /// @notice Swaps `amountIn` of one token for as much as possible of another token\n /// @param params The parameters necessary for the swap, encoded as `ExactInputSingleParams` in calldata\n /// @return amountOut The amount of the received token\n function exactInputSingle(ExactInputSingleParams calldata params)\n external\n payable\n returns (uint256 amountOut);\n\n struct ExactInputParams {\n bytes path;\n address recipient;\n uint256 deadline;\n uint256 amountIn;\n uint256 amountOutMinimum;\n }\n\n /// @notice Swaps `amountIn` of one token for as much as possible of another along the specified path\n /// @param params The parameters necessary for the multi-hop swap, encoded as `ExactInputParams` in calldata\n /// @return amountOut The amount of the received token\n function exactInput(ExactInputParams calldata params)\n external\n payable\n returns (uint256 amountOut);\n\n struct ExactOutputSingleParams {\n address tokenIn;\n address tokenOut;\n int24 tickSpacing;\n address recipient;\n uint256 deadline;\n uint256 amountOut;\n uint256 amountInMaximum;\n uint160 sqrtPriceLimitX96;\n }\n\n /// @notice Swaps as little as possible of one token for `amountOut` of another token\n /// @param params The parameters necessary for the swap, encoded as `ExactOutputSingleParams` in calldata\n /// @return amountIn The amount of the input token\n function exactOutputSingle(ExactOutputSingleParams calldata params)\n external\n payable\n returns (uint256 amountIn);\n\n struct ExactOutputParams {\n bytes path;\n address recipient;\n uint256 deadline;\n uint256 amountOut;\n uint256 amountInMaximum;\n }\n\n /// @notice Swaps as little as possible of one token for `amountOut` of another along the specified path (reversed)\n /// @param params The parameters necessary for the multi-hop swap, encoded as `ExactOutputParams` in calldata\n /// @return amountIn The amount of the input token\n function exactOutput(ExactOutputParams calldata params)\n external\n payable\n returns (uint256 amountIn);\n}\n" + }, + "contracts/interfaces/balancer/IBalancerVault.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport { IERC20 } from \"../../utils/InitializableAbstractStrategy.sol\";\n\ninterface IBalancerVault {\n enum WeightedPoolJoinKind {\n INIT,\n EXACT_TOKENS_IN_FOR_BPT_OUT,\n TOKEN_IN_FOR_EXACT_BPT_OUT,\n ALL_TOKENS_IN_FOR_EXACT_BPT_OUT,\n ADD_TOKEN\n }\n\n enum WeightedPoolExitKind {\n EXACT_BPT_IN_FOR_ONE_TOKEN_OUT,\n EXACT_BPT_IN_FOR_TOKENS_OUT,\n BPT_IN_FOR_EXACT_TOKENS_OUT,\n REMOVE_TOKEN\n }\n\n /**\n * @dev Called by users to join a Pool, which transfers tokens from `sender` into the Pool's balance. This will\n * trigger custom Pool behavior, which will typically grant something in return to `recipient` - often tokenized\n * Pool shares.\n *\n * If the caller is not `sender`, it must be an authorized relayer for them.\n *\n * The `assets` and `maxAmountsIn` arrays must have the same length, and each entry indicates the maximum amount\n * to send for each asset. The amounts to send are decided by the Pool and not the Vault: it just enforces\n * these maximums.\n *\n * If joining a Pool that holds WETH, it is possible to send ETH directly: the Vault will do the wrapping. To enable\n * this mechanism, the IAsset sentinel value (the zero address) must be passed in the `assets` array instead of the\n * WETH address. Note that it is not possible to combine ETH and WETH in the same join. Any excess ETH will be sent\n * back to the caller (not the sender, which is important for relayers).\n *\n * `assets` must have the same length and order as the array returned by `getPoolTokens`. This prevents issues when\n * interacting with Pools that register and deregister tokens frequently. If sending ETH however, the array must be\n * sorted *before* replacing the WETH address with the ETH sentinel value (the zero address), which means the final\n * `assets` array might not be sorted. Pools with no registered tokens cannot be joined.\n *\n * If `fromInternalBalance` is true, the caller's Internal Balance will be preferred: ERC20 transfers will only\n * be made for the difference between the requested amount and Internal Balance (if any). Note that ETH cannot be\n * withdrawn from Internal Balance: attempting to do so will trigger a revert.\n *\n * This causes the Vault to call the `IBasePool.onJoinPool` hook on the Pool's contract, where Pools implement\n * their own custom logic. This typically requires additional information from the user (such as the expected number\n * of Pool shares). This can be encoded in the `userData` argument, which is ignored by the Vault and passed\n * directly to the Pool's contract, as is `recipient`.\n *\n * Emits a `PoolBalanceChanged` event.\n */\n function joinPool(\n bytes32 poolId,\n address sender,\n address recipient,\n JoinPoolRequest memory request\n ) external payable;\n\n struct JoinPoolRequest {\n address[] assets;\n uint256[] maxAmountsIn;\n bytes userData;\n bool fromInternalBalance;\n }\n\n /**\n * @dev Called by users to exit a Pool, which transfers tokens from the Pool's balance to `recipient`. This will\n * trigger custom Pool behavior, which will typically ask for something in return from `sender` - often tokenized\n * Pool shares. The amount of tokens that can be withdrawn is limited by the Pool's `cash` balance (see\n * `getPoolTokenInfo`).\n *\n * If the caller is not `sender`, it must be an authorized relayer for them.\n *\n * The `tokens` and `minAmountsOut` arrays must have the same length, and each entry in these indicates the minimum\n * token amount to receive for each token contract. The amounts to send are decided by the Pool and not the Vault:\n * it just enforces these minimums.\n *\n * If exiting a Pool that holds WETH, it is possible to receive ETH directly: the Vault will do the unwrapping. To\n * enable this mechanism, the IAsset sentinel value (the zero address) must be passed in the `assets` array instead\n * of the WETH address. Note that it is not possible to combine ETH and WETH in the same exit.\n *\n * `assets` must have the same length and order as the array returned by `getPoolTokens`. This prevents issues when\n * interacting with Pools that register and deregister tokens frequently. If receiving ETH however, the array must\n * be sorted *before* replacing the WETH address with the ETH sentinel value (the zero address), which means the\n * final `assets` array might not be sorted. Pools with no registered tokens cannot be exited.\n *\n * If `toInternalBalance` is true, the tokens will be deposited to `recipient`'s Internal Balance. Otherwise,\n * an ERC20 transfer will be performed. Note that ETH cannot be deposited to Internal Balance: attempting to\n * do so will trigger a revert.\n *\n * `minAmountsOut` is the minimum amount of tokens the user expects to get out of the Pool, for each token in the\n * `tokens` array. This array must match the Pool's registered tokens.\n *\n * This causes the Vault to call the `IBasePool.onExitPool` hook on the Pool's contract, where Pools implement\n * their own custom logic. This typically requires additional information from the user (such as the expected number\n * of Pool shares to return). This can be encoded in the `userData` argument, which is ignored by the Vault and\n * passed directly to the Pool's contract.\n *\n * Emits a `PoolBalanceChanged` event.\n */\n function exitPool(\n bytes32 poolId,\n address sender,\n address payable recipient,\n ExitPoolRequest memory request\n ) external;\n\n struct ExitPoolRequest {\n address[] assets;\n uint256[] minAmountsOut;\n bytes userData;\n bool toInternalBalance;\n }\n\n /**\n * @dev Returns a Pool's registered tokens, the total balance for each, and the latest block when *any* of\n * the tokens' `balances` changed.\n *\n * The order of the `tokens` array is the same order that will be used in `joinPool`, `exitPool`, as well as in all\n * Pool hooks (where applicable). Calls to `registerTokens` and `deregisterTokens` may change this order.\n *\n * If a Pool only registers tokens once, and these are sorted in ascending order, they will be stored in the same\n * order as passed to `registerTokens`.\n *\n * Total balances include both tokens held by the Vault and those withdrawn by the Pool's Asset Managers. These are\n * the amounts used by joins, exits and swaps. For a detailed breakdown of token balances, use `getPoolTokenInfo`\n * instead.\n */\n function getPoolTokens(bytes32 poolId)\n external\n view\n returns (\n IERC20[] memory tokens,\n uint256[] memory balances,\n uint256 lastChangeBlock\n );\n\n /**\n * @dev Performs a set of user balance operations, which involve Internal Balance (deposit, withdraw or transfer)\n * and plain ERC20 transfers using the Vault's allowance. This last feature is particularly useful for relayers, as\n * it lets integrators reuse a user's Vault allowance.\n *\n * For each operation, if the caller is not `sender`, it must be an authorized relayer for them.\n */\n function manageUserBalance(UserBalanceOp[] memory ops) external payable;\n\n struct UserBalanceOp {\n UserBalanceOpKind kind;\n address asset;\n uint256 amount;\n address sender;\n address payable recipient;\n }\n\n enum UserBalanceOpKind {\n DEPOSIT_INTERNAL,\n WITHDRAW_INTERNAL,\n TRANSFER_INTERNAL,\n TRANSFER_EXTERNAL\n }\n\n enum SwapKind {\n GIVEN_IN,\n GIVEN_OUT\n }\n\n struct SingleSwap {\n bytes32 poolId;\n SwapKind kind;\n address assetIn;\n address assetOut;\n uint256 amount;\n bytes userData;\n }\n\n struct FundManagement {\n address sender;\n bool fromInternalBalance;\n address payable recipient;\n bool toInternalBalance;\n }\n\n function swap(\n SingleSwap calldata singleSwap,\n FundManagement calldata funds,\n uint256 limit,\n uint256 deadline\n ) external returns (uint256 amountCalculated);\n\n function getPoolTokenInfo(bytes32 poolId, address token)\n external\n view\n returns (\n uint256 cash,\n uint256 managed,\n uint256 lastChangeBlock,\n address assetManager\n );\n}\n" + }, + "contracts/interfaces/balancer/IMetaStablePool.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport { IRateProvider } from \"./IRateProvider.sol\";\n\ninterface IMetaStablePool {\n function getRateProviders()\n external\n view\n returns (IRateProvider[] memory providers);\n}\n" + }, + "contracts/interfaces/balancer/IRateProvider.sol": { + "content": "// SPDX-License-Identifier: GPL-3.0-or-later\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see .\n\npragma solidity ^0.8.0;\n\ninterface IRateProvider {\n function getRate() external view returns (uint256);\n}\n" + }, + "contracts/interfaces/cctp/ICCTP.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\ninterface ICCTPTokenMessenger {\n function depositForBurn(\n uint256 amount,\n uint32 destinationDomain,\n bytes32 mintRecipient,\n address burnToken,\n bytes32 destinationCaller,\n uint256 maxFee,\n uint32 minFinalityThreshold\n ) external;\n\n function depositForBurnWithHook(\n uint256 amount,\n uint32 destinationDomain,\n bytes32 mintRecipient,\n address burnToken,\n bytes32 destinationCaller,\n uint256 maxFee,\n uint32 minFinalityThreshold,\n bytes memory hookData\n ) external;\n\n function getMinFeeAmount(uint256 amount) external view returns (uint256);\n}\n\ninterface ICCTPMessageTransmitter {\n function sendMessage(\n uint32 destinationDomain,\n bytes32 recipient,\n bytes32 destinationCaller,\n uint32 minFinalityThreshold,\n bytes memory messageBody\n ) external;\n\n function receiveMessage(bytes calldata message, bytes calldata attestation)\n external\n returns (bool);\n}\n\ninterface IMessageHandlerV2 {\n /**\n * @notice Handles an incoming finalized message from an IReceiverV2\n * @dev Finalized messages have finality threshold values greater than or equal to 2000\n * @param sourceDomain The source domain of the message\n * @param sender The sender of the message\n * @param finalityThresholdExecuted the finality threshold at which the message was attested to\n * @param messageBody The raw bytes of the message body\n * @return success True, if successful; false, if not.\n */\n function handleReceiveFinalizedMessage(\n uint32 sourceDomain,\n bytes32 sender,\n uint32 finalityThresholdExecuted,\n bytes calldata messageBody\n ) external returns (bool);\n\n /**\n * @notice Handles an incoming unfinalized message from an IReceiverV2\n * @dev Unfinalized messages have finality threshold values less than 2000\n * @param sourceDomain The source domain of the message\n * @param sender The sender of the message\n * @param finalityThresholdExecuted The finality threshold at which the message was attested to\n * @param messageBody The raw bytes of the message body\n * @return success True, if successful; false, if not.\n */\n function handleReceiveUnfinalizedMessage(\n uint32 sourceDomain,\n bytes32 sender,\n uint32 finalityThresholdExecuted,\n bytes calldata messageBody\n ) external returns (bool);\n}\n" + }, + "contracts/interfaces/chainlink/AggregatorV3Interface.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ninterface AggregatorV3Interface {\n function decimals() external view returns (uint8);\n\n function description() external view returns (string memory);\n\n function version() external view returns (uint256);\n\n // getRoundData and latestRoundData should both raise \"No data present\"\n // if they do not have data to report, instead of returning unset values\n // which could be misinterpreted as actual reported values.\n function getRoundData(uint80 _roundId)\n external\n view\n returns (\n uint80 roundId,\n int256 answer,\n uint256 startedAt,\n uint256 updatedAt,\n uint80 answeredInRound\n );\n\n function latestRoundData()\n external\n view\n returns (\n uint80 roundId,\n int256 answer,\n uint256 startedAt,\n uint256 updatedAt,\n uint80 answeredInRound\n );\n}\n" + }, + "contracts/interfaces/IBasicToken.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ninterface IBasicToken {\n function symbol() external view returns (string memory);\n\n function decimals() external view returns (uint8);\n}\n" + }, + "contracts/interfaces/IBeaconProofs.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\ninterface IBeaconProofs {\n function verifyValidator(\n bytes32 beaconBlockRoot,\n bytes32 pubKeyHash,\n bytes calldata validatorPubKeyProof,\n uint40 validatorIndex,\n bytes32 withdrawalCredentials\n ) external view;\n\n function verifyValidatorWithdrawable(\n bytes32 beaconBlockRoot,\n uint40 validatorIndex,\n uint64 withdrawableEpoch,\n bytes calldata withdrawableEpochProof\n ) external view;\n\n function verifyBalancesContainer(\n bytes32 beaconBlockRoot,\n bytes32 balancesContainerLeaf,\n bytes calldata balancesContainerProof\n ) external view;\n\n function verifyValidatorBalance(\n bytes32 balancesContainerRoot,\n bytes32 validatorBalanceLeaf,\n bytes calldata balanceProof,\n uint40 validatorIndex\n ) external view returns (uint256 validatorBalance);\n\n function verifyPendingDepositsContainer(\n bytes32 beaconBlockRoot,\n bytes32 pendingDepositsContainerRoot,\n bytes calldata proof\n ) external view;\n\n function verifyPendingDeposit(\n bytes32 pendingDepositsContainerRoot,\n bytes32 pendingDepositRoot,\n bytes calldata proof,\n uint32 pendingDepositIndex\n ) external view;\n\n function verifyFirstPendingDeposit(\n bytes32 beaconBlockRoot,\n uint64 slot,\n bytes calldata firstPendingDepositSlotProof\n ) external view returns (bool isEmptyDepositQueue);\n\n function merkleizePendingDeposit(\n bytes32 pubKeyHash,\n bytes calldata withdrawalCredentials,\n uint64 amountGwei,\n bytes calldata signature,\n uint64 slot\n ) external pure returns (bytes32 root);\n\n function merkleizeSignature(bytes calldata signature)\n external\n pure\n returns (bytes32 root);\n}\n" + }, + "contracts/interfaces/ICampaignRemoteManager.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\ninterface ICampaignRemoteManager {\n function createCampaign(\n CampaignCreationParams memory params,\n uint256 destinationChainId,\n uint256 additionalGasLimit,\n address votemarket\n ) external payable;\n\n function manageCampaign(\n CampaignManagementParams memory params,\n uint256 destinationChainId,\n uint256 additionalGasLimit,\n address votemarket\n ) external payable;\n\n function closeCampaign(\n CampaignClosingParams memory params,\n uint256 destinationChainId,\n uint256 additionalGasLimit,\n address votemarket\n ) external payable;\n\n struct CampaignCreationParams {\n uint256 chainId;\n address gauge;\n address manager;\n address rewardToken;\n uint8 numberOfPeriods;\n uint256 maxRewardPerVote;\n uint256 totalRewardAmount;\n address[] addresses;\n address hook;\n bool isWhitelist;\n }\n\n struct CampaignManagementParams {\n uint256 campaignId;\n address rewardToken;\n uint8 numberOfPeriods;\n uint256 totalRewardAmount;\n uint256 maxRewardPerVote;\n }\n\n struct CampaignClosingParams {\n uint256 campaignId;\n }\n}\n" + }, + "contracts/interfaces/IChildLiquidityGaugeFactory.sol": { + "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.8.4;\n\ninterface IChildLiquidityGaugeFactory {\n event DeployedGauge(\n address indexed _implementation,\n address indexed _lp_token,\n address indexed _deployer,\n bytes32 _salt,\n address _gauge\n );\n event Minted(\n address indexed _user,\n address indexed _gauge,\n uint256 _new_total\n );\n event TransferOwnership(address _old_owner, address _new_owner);\n event UpdateCallProxy(address _old_call_proxy, address _new_call_proxy);\n event UpdateImplementation(\n address _old_implementation,\n address _new_implementation\n );\n event UpdateManager(address _manager);\n event UpdateMirrored(address indexed _gauge, bool _mirrored);\n event UpdateRoot(address _factory, address _implementation);\n event UpdateVotingEscrow(\n address _old_voting_escrow,\n address _new_voting_escrow\n );\n\n function accept_transfer_ownership() external;\n\n function call_proxy() external view returns (address);\n\n function commit_transfer_ownership(address _future_owner) external;\n\n function crv() external view returns (address);\n\n function deploy_gauge(address _lp_token, bytes32 _salt)\n external\n returns (address);\n\n function deploy_gauge(\n address _lp_token,\n bytes32 _salt,\n address _manager\n ) external returns (address);\n\n function future_owner() external view returns (address);\n\n function gauge_data(address arg0) external view returns (uint256);\n\n function get_gauge(uint256 arg0) external view returns (address);\n\n function get_gauge_count() external view returns (uint256);\n\n function get_gauge_from_lp_token(address arg0)\n external\n view\n returns (address);\n\n function get_implementation() external view returns (address);\n\n function is_mirrored(address _gauge) external view returns (bool);\n\n function is_valid_gauge(address _gauge) external view returns (bool);\n\n function last_request(address _gauge) external view returns (uint256);\n\n function manager() external view returns (address);\n\n function mint(address _gauge) external;\n\n function mint_many(address[32] memory _gauges) external;\n\n function minted(address arg0, address arg1) external view returns (uint256);\n\n function owner() external view returns (address);\n\n function root_factory() external view returns (address);\n\n function root_implementation() external view returns (address);\n\n function set_call_proxy(address _new_call_proxy) external;\n\n function set_crv(address _crv) external;\n\n function set_implementation(address _implementation) external;\n\n function set_manager(address _new_manager) external;\n\n function set_mirrored(address _gauge, bool _mirrored) external;\n\n function set_root(address _factory, address _implementation) external;\n\n function set_voting_escrow(address _voting_escrow) external;\n\n function version() external view returns (string memory);\n\n function voting_escrow() external view returns (address);\n}\n" + }, + "contracts/interfaces/IComptroller.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ninterface IComptroller {\n // Claim all the COMP accrued by specific holders in specific markets for their supplies and/or borrows\n function claimComp(\n address[] memory holders,\n address[] memory cTokens,\n bool borrowers,\n bool suppliers\n ) external;\n\n function oracle() external view returns (address);\n}\n" + }, + "contracts/interfaces/ICreateX.sol": { + "content": "// SPDX-License-Identifier: AGPL-3.0-only\npragma solidity ^0.8.4;\n\n/**\n * @title CreateX Factory Interface Definition\n * @author pcaversaccio (https://web.archive.org/web/20230921103111/https://pcaversaccio.com/)\n * @custom:coauthor Matt Solomon (https://web.archive.org/web/20230921103335/https://mattsolomon.dev/)\n */\ninterface ICreateX {\n /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/\n /* TYPES */\n /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/\n\n struct Values {\n uint256 constructorAmount;\n uint256 initCallAmount;\n }\n\n /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/\n /* EVENTS */\n /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/\n\n event ContractCreation(address indexed newContract, bytes32 indexed salt);\n event ContractCreation(address indexed newContract);\n event Create3ProxyContractCreation(\n address indexed newContract,\n bytes32 indexed salt\n );\n\n /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/\n /* CUSTOM ERRORS */\n /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/\n\n error FailedContractCreation(address emitter);\n error FailedContractInitialisation(address emitter, bytes revertData);\n error InvalidSalt(address emitter);\n error InvalidNonceValue(address emitter);\n error FailedEtherTransfer(address emitter, bytes revertData);\n\n /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/\n /* CREATE */\n /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/\n\n function deployCreate(bytes memory initCode)\n external\n payable\n returns (address newContract);\n\n function deployCreateAndInit(\n bytes memory initCode,\n bytes memory data,\n Values memory values,\n address refundAddress\n ) external payable returns (address newContract);\n\n function deployCreateAndInit(\n bytes memory initCode,\n bytes memory data,\n Values memory values\n ) external payable returns (address newContract);\n\n function deployCreateClone(address implementation, bytes memory data)\n external\n payable\n returns (address proxy);\n\n function computeCreateAddress(address deployer, uint256 nonce)\n external\n view\n returns (address computedAddress);\n\n function computeCreateAddress(uint256 nonce)\n external\n view\n returns (address computedAddress);\n\n /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/\n /* CREATE2 */\n /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/\n\n function deployCreate2(bytes32 salt, bytes memory initCode)\n external\n payable\n returns (address newContract);\n\n function deployCreate2(bytes memory initCode)\n external\n payable\n returns (address newContract);\n\n function deployCreate2AndInit(\n bytes32 salt,\n bytes memory initCode,\n bytes memory data,\n Values memory values,\n address refundAddress\n ) external payable returns (address newContract);\n\n function deployCreate2AndInit(\n bytes32 salt,\n bytes memory initCode,\n bytes memory data,\n Values memory values\n ) external payable returns (address newContract);\n\n function deployCreate2AndInit(\n bytes memory initCode,\n bytes memory data,\n Values memory values,\n address refundAddress\n ) external payable returns (address newContract);\n\n function deployCreate2AndInit(\n bytes memory initCode,\n bytes memory data,\n Values memory values\n ) external payable returns (address newContract);\n\n function deployCreate2Clone(\n bytes32 salt,\n address implementation,\n bytes memory data\n ) external payable returns (address proxy);\n\n function deployCreate2Clone(address implementation, bytes memory data)\n external\n payable\n returns (address proxy);\n\n function computeCreate2Address(\n bytes32 salt,\n bytes32 initCodeHash,\n address deployer\n ) external pure returns (address computedAddress);\n\n function computeCreate2Address(bytes32 salt, bytes32 initCodeHash)\n external\n view\n returns (address computedAddress);\n\n /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/\n /* CREATE3 */\n /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/\n\n function deployCreate3(bytes32 salt, bytes memory initCode)\n external\n payable\n returns (address newContract);\n\n function deployCreate3(bytes memory initCode)\n external\n payable\n returns (address newContract);\n\n function deployCreate3AndInit(\n bytes32 salt,\n bytes memory initCode,\n bytes memory data,\n Values memory values,\n address refundAddress\n ) external payable returns (address newContract);\n\n function deployCreate3AndInit(\n bytes32 salt,\n bytes memory initCode,\n bytes memory data,\n Values memory values\n ) external payable returns (address newContract);\n\n function deployCreate3AndInit(\n bytes memory initCode,\n bytes memory data,\n Values memory values,\n address refundAddress\n ) external payable returns (address newContract);\n\n function deployCreate3AndInit(\n bytes memory initCode,\n bytes memory data,\n Values memory values\n ) external payable returns (address newContract);\n\n function computeCreate3Address(bytes32 salt, address deployer)\n external\n pure\n returns (address computedAddress);\n\n function computeCreate3Address(bytes32 salt)\n external\n view\n returns (address computedAddress);\n}\n" + }, + "contracts/interfaces/ICurveLiquidityGaugeV6.sol": { + "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.8.4;\n\ninterface ICurveLiquidityGaugeV6 {\n event ApplyOwnership(address admin);\n event Approval(\n address indexed _owner,\n address indexed _spender,\n uint256 _value\n );\n event CommitOwnership(address admin);\n event Deposit(address indexed provider, uint256 value);\n event SetGaugeManager(address _gauge_manager);\n event Transfer(address indexed _from, address indexed _to, uint256 _value);\n event UpdateLiquidityLimit(\n address indexed user,\n uint256 original_balance,\n uint256 original_supply,\n uint256 working_balance,\n uint256 working_supply\n );\n event Withdraw(address indexed provider, uint256 value);\n\n function DOMAIN_SEPARATOR() external view returns (bytes32);\n\n function add_reward(address _reward_token, address _distributor) external;\n\n function allowance(address arg0, address arg1)\n external\n view\n returns (uint256);\n\n function approve(address _spender, uint256 _value) external returns (bool);\n\n function balanceOf(address arg0) external view returns (uint256);\n\n function claim_rewards() external;\n\n function claim_rewards(address _addr) external;\n\n function claim_rewards(address _addr, address _receiver) external;\n\n function claimable_reward(address _user, address _reward_token)\n external\n view\n returns (uint256);\n\n function claimable_tokens(address addr) external returns (uint256);\n\n function claimed_reward(address _addr, address _token)\n external\n view\n returns (uint256);\n\n function decimals() external view returns (uint256);\n\n function decreaseAllowance(address _spender, uint256 _subtracted_value)\n external\n returns (bool);\n\n function deposit(uint256 _value) external;\n\n function deposit(uint256 _value, address _addr) external;\n\n function deposit(\n uint256 _value,\n address _addr,\n bool _claim_rewards\n ) external;\n\n function deposit_reward_token(address _reward_token, uint256 _amount)\n external;\n\n function deposit_reward_token(\n address _reward_token,\n uint256 _amount,\n uint256 _epoch\n ) external;\n\n function factory() external view returns (address);\n\n function future_epoch_time() external view returns (uint256);\n\n function increaseAllowance(address _spender, uint256 _added_value)\n external\n returns (bool);\n\n function inflation_rate() external view returns (uint256);\n\n function integrate_checkpoint() external view returns (uint256);\n\n function integrate_checkpoint_of(address arg0)\n external\n view\n returns (uint256);\n\n function integrate_fraction(address arg0) external view returns (uint256);\n\n function integrate_inv_supply(uint256 arg0) external view returns (uint256);\n\n function integrate_inv_supply_of(address arg0)\n external\n view\n returns (uint256);\n\n function is_killed() external view returns (bool);\n\n function kick(address addr) external;\n\n function lp_token() external view returns (address);\n\n function manager() external view returns (address);\n\n function name() external view returns (string memory);\n\n function nonces(address arg0) external view returns (uint256);\n\n function period() external view returns (int128);\n\n function period_timestamp(uint256 arg0) external view returns (uint256);\n\n function permit(\n address _owner,\n address _spender,\n uint256 _value,\n uint256 _deadline,\n uint8 _v,\n bytes32 _r,\n bytes32 _s\n ) external returns (bool);\n\n function reward_count() external view returns (uint256);\n\n function reward_integral_for(address arg0, address arg1)\n external\n view\n returns (uint256);\n\n function reward_tokens(uint256 arg0) external view returns (address);\n\n function rewards_receiver(address arg0) external view returns (address);\n\n function salt() external view returns (bytes32);\n\n function set_gauge_manager(address _gauge_manager) external;\n\n function set_killed(bool _is_killed) external;\n\n function set_reward_distributor(address _reward_token, address _distributor)\n external;\n\n function set_rewards_receiver(address _receiver) external;\n\n function symbol() external view returns (string memory);\n\n function totalSupply() external view returns (uint256);\n\n function transfer(address _to, uint256 _value) external returns (bool);\n\n function transferFrom(\n address _from,\n address _to,\n uint256 _value\n ) external returns (bool);\n\n function user_checkpoint(address addr) external returns (bool);\n\n function version() external view returns (string memory);\n\n function withdraw(uint256 _value) external;\n\n function withdraw(uint256 _value, bool _claim_rewards) external;\n\n function working_balances(address arg0) external view returns (uint256);\n\n function working_supply() external view returns (uint256);\n}\n" + }, + "contracts/interfaces/ICurveMinter.sol": { + "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.8.4;\n\ninterface ICurveMinter {\n event Minted(address indexed recipient, address gauge, uint256 minted);\n\n function allowed_to_mint_for(address arg0, address arg1)\n external\n view\n returns (bool);\n\n function controller() external view returns (address);\n\n function mint(address gauge_addr) external;\n\n function mint_for(address gauge_addr, address _for) external;\n\n function mint_many(address[8] memory gauge_addrs) external;\n\n function minted(address arg0, address arg1) external view returns (uint256);\n\n function toggle_approve_mint(address minting_user) external;\n\n function token() external view returns (address);\n}\n" + }, + "contracts/interfaces/ICurveStableSwapNG.sol": { + "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.8.4;\n\ninterface ICurveStableSwapNG {\n event AddLiquidity(\n address indexed provider,\n uint256[] token_amounts,\n uint256[] fees,\n uint256 invariant,\n uint256 token_supply\n );\n event ApplyNewFee(uint256 fee, uint256 offpeg_fee_multiplier);\n event Approval(\n address indexed owner,\n address indexed spender,\n uint256 value\n );\n event RampA(\n uint256 old_A,\n uint256 new_A,\n uint256 initial_time,\n uint256 future_time\n );\n event RemoveLiquidity(\n address indexed provider,\n uint256[] token_amounts,\n uint256[] fees,\n uint256 token_supply\n );\n event RemoveLiquidityImbalance(\n address indexed provider,\n uint256[] token_amounts,\n uint256[] fees,\n uint256 invariant,\n uint256 token_supply\n );\n event RemoveLiquidityOne(\n address indexed provider,\n int128 token_id,\n uint256 token_amount,\n uint256 coin_amount,\n uint256 token_supply\n );\n event SetNewMATime(uint256 ma_exp_time, uint256 D_ma_time);\n event StopRampA(uint256 A, uint256 t);\n event TokenExchange(\n address indexed buyer,\n int128 sold_id,\n uint256 tokens_sold,\n int128 bought_id,\n uint256 tokens_bought\n );\n event TokenExchangeUnderlying(\n address indexed buyer,\n int128 sold_id,\n uint256 tokens_sold,\n int128 bought_id,\n uint256 tokens_bought\n );\n event Transfer(\n address indexed sender,\n address indexed receiver,\n uint256 value\n );\n\n function A() external view returns (uint256);\n\n function A_precise() external view returns (uint256);\n\n function DOMAIN_SEPARATOR() external view returns (bytes32);\n\n function D_ma_time() external view returns (uint256);\n\n function D_oracle() external view returns (uint256);\n\n function N_COINS() external view returns (uint256);\n\n function add_liquidity(uint256[] memory _amounts, uint256 _min_mint_amount)\n external\n returns (uint256);\n\n function add_liquidity(\n uint256[] memory _amounts,\n uint256 _min_mint_amount,\n address _receiver\n ) external returns (uint256);\n\n function admin_balances(uint256 arg0) external view returns (uint256);\n\n function admin_fee() external view returns (uint256);\n\n function allowance(address arg0, address arg1)\n external\n view\n returns (uint256);\n\n function approve(address _spender, uint256 _value) external returns (bool);\n\n function balanceOf(address arg0) external view returns (uint256);\n\n function balances(uint256 i) external view returns (uint256);\n\n function calc_token_amount(uint256[] memory _amounts, bool _is_deposit)\n external\n view\n returns (uint256);\n\n function calc_withdraw_one_coin(uint256 _burn_amount, int128 i)\n external\n view\n returns (uint256);\n\n function coins(uint256 arg0) external view returns (address);\n\n function decimals() external view returns (uint8);\n\n function dynamic_fee(int128 i, int128 j) external view returns (uint256);\n\n function ema_price(uint256 i) external view returns (uint256);\n\n function exchange(\n int128 i,\n int128 j,\n uint256 _dx,\n uint256 _min_dy\n ) external returns (uint256);\n\n function exchange(\n int128 i,\n int128 j,\n uint256 _dx,\n uint256 _min_dy,\n address _receiver\n ) external returns (uint256);\n\n function exchange_received(\n int128 i,\n int128 j,\n uint256 _dx,\n uint256 _min_dy\n ) external returns (uint256);\n\n function exchange_received(\n int128 i,\n int128 j,\n uint256 _dx,\n uint256 _min_dy,\n address _receiver\n ) external returns (uint256);\n\n function fee() external view returns (uint256);\n\n function future_A() external view returns (uint256);\n\n function future_A_time() external view returns (uint256);\n\n function get_balances() external view returns (uint256[] memory);\n\n function get_dx(\n int128 i,\n int128 j,\n uint256 dy\n ) external view returns (uint256);\n\n function get_dy(\n int128 i,\n int128 j,\n uint256 dx\n ) external view returns (uint256);\n\n function get_p(uint256 i) external view returns (uint256);\n\n function get_virtual_price() external view returns (uint256);\n\n function initial_A() external view returns (uint256);\n\n function initial_A_time() external view returns (uint256);\n\n function last_price(uint256 i) external view returns (uint256);\n\n function ma_exp_time() external view returns (uint256);\n\n function ma_last_time() external view returns (uint256);\n\n function name() external view returns (string memory);\n\n function nonces(address arg0) external view returns (uint256);\n\n function offpeg_fee_multiplier() external view returns (uint256);\n\n function permit(\n address _owner,\n address _spender,\n uint256 _value,\n uint256 _deadline,\n uint8 _v,\n bytes32 _r,\n bytes32 _s\n ) external returns (bool);\n\n function price_oracle(uint256 i) external view returns (uint256);\n\n function ramp_A(uint256 _future_A, uint256 _future_time) external;\n\n function remove_liquidity(\n uint256 _burn_amount,\n uint256[] memory _min_amounts\n ) external returns (uint256[] memory);\n\n function remove_liquidity(\n uint256 _burn_amount,\n uint256[] memory _min_amounts,\n address _receiver\n ) external returns (uint256[] memory);\n\n function remove_liquidity(\n uint256 _burn_amount,\n uint256[] memory _min_amounts,\n address _receiver,\n bool _claim_admin_fees\n ) external returns (uint256[] memory);\n\n function remove_liquidity_imbalance(\n uint256[] memory _amounts,\n uint256 _max_burn_amount\n ) external returns (uint256);\n\n function remove_liquidity_imbalance(\n uint256[] memory _amounts,\n uint256 _max_burn_amount,\n address _receiver\n ) external returns (uint256);\n\n function remove_liquidity_one_coin(\n uint256 _burn_amount,\n int128 i,\n uint256 _min_received\n ) external returns (uint256);\n\n function remove_liquidity_one_coin(\n uint256 _burn_amount,\n int128 i,\n uint256 _min_received,\n address _receiver\n ) external returns (uint256);\n\n function salt() external view returns (bytes32);\n\n function set_ma_exp_time(uint256 _ma_exp_time, uint256 _D_ma_time) external;\n\n function set_new_fee(uint256 _new_fee, uint256 _new_offpeg_fee_multiplier)\n external;\n\n function stop_ramp_A() external;\n\n function stored_rates() external view returns (uint256[] memory);\n\n function symbol() external view returns (string memory);\n\n function totalSupply() external view returns (uint256);\n\n function transfer(address _to, uint256 _value) external returns (bool);\n\n function transferFrom(\n address _from,\n address _to,\n uint256 _value\n ) external returns (bool);\n\n function version() external view returns (string memory);\n\n function withdraw_admin_fees() external;\n}\n" + }, + "contracts/interfaces/ICurveXChainLiquidityGauge.sol": { + "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.8.4;\n\ninterface ICurveXChainLiquidityGauge {\n event Approval(\n address indexed _owner,\n address indexed _spender,\n uint256 _value\n );\n event Deposit(address indexed provider, uint256 value);\n event SetGaugeManager(address _gauge_manager);\n event Transfer(address indexed _from, address indexed _to, uint256 _value);\n event UpdateLiquidityLimit(\n address indexed user,\n uint256 original_balance,\n uint256 original_supply,\n uint256 working_balance,\n uint256 working_supply\n );\n event Withdraw(address indexed provider, uint256 value);\n\n function DOMAIN_SEPARATOR() external view returns (bytes32);\n\n function add_reward(address _reward_token, address _distributor) external;\n\n function allowance(address arg0, address arg1)\n external\n view\n returns (uint256);\n\n function approve(address _spender, uint256 _value) external returns (bool);\n\n function balanceOf(address arg0) external view returns (uint256);\n\n function claim_rewards() external;\n\n function claim_rewards(address _addr) external;\n\n function claim_rewards(address _addr, address _receiver) external;\n\n function claimable_reward(address _user, address _reward_token)\n external\n view\n returns (uint256);\n\n function claimable_tokens(address addr) external returns (uint256);\n\n function claimed_reward(address _addr, address _token)\n external\n view\n returns (uint256);\n\n function decimals() external view returns (uint256);\n\n function decreaseAllowance(address _spender, uint256 _subtracted_value)\n external\n returns (bool);\n\n function deposit(uint256 _value) external;\n\n function deposit(uint256 _value, address _addr) external;\n\n function deposit(\n uint256 _value,\n address _addr,\n bool _claim_rewards\n ) external;\n\n function deposit_reward_token(address _reward_token, uint256 _amount)\n external;\n\n function deposit_reward_token(\n address _reward_token,\n uint256 _amount,\n uint256 _epoch\n ) external;\n\n function factory() external view returns (address);\n\n function increaseAllowance(address _spender, uint256 _added_value)\n external\n returns (bool);\n\n function inflation_rate(uint256 arg0) external view returns (uint256);\n\n function initialize(\n address _lp_token,\n address _root,\n address _manager\n ) external;\n\n function integrate_checkpoint() external view returns (uint256);\n\n function integrate_checkpoint_of(address arg0)\n external\n view\n returns (uint256);\n\n function integrate_fraction(address arg0) external view returns (uint256);\n\n function integrate_inv_supply(int128 arg0) external view returns (uint256);\n\n function integrate_inv_supply_of(address arg0)\n external\n view\n returns (uint256);\n\n function is_killed() external view returns (bool);\n\n function lp_token() external view returns (address);\n\n function manager() external view returns (address);\n\n function name() external view returns (string memory);\n\n function nonces(address arg0) external view returns (uint256);\n\n function period() external view returns (int128);\n\n function period_timestamp(int128 arg0) external view returns (uint256);\n\n function permit(\n address _owner,\n address _spender,\n uint256 _value,\n uint256 _deadline,\n uint8 _v,\n bytes32 _r,\n bytes32 _s\n ) external returns (bool);\n\n function recover_remaining(address _reward_token) external;\n\n function reward_count() external view returns (uint256);\n\n function reward_integral_for(address arg0, address arg1)\n external\n view\n returns (uint256);\n\n function reward_remaining(address arg0) external view returns (uint256);\n\n function reward_tokens(uint256 arg0) external view returns (address);\n\n function rewards_receiver(address arg0) external view returns (address);\n\n function root_gauge() external view returns (address);\n\n function set_gauge_manager(address _gauge_manager) external;\n\n function set_killed(bool _is_killed) external;\n\n function set_manager(address _gauge_manager) external;\n\n function set_reward_distributor(address _reward_token, address _distributor)\n external;\n\n function set_rewards_receiver(address _receiver) external;\n\n function set_root_gauge(address _root) external;\n\n function symbol() external view returns (string memory);\n\n function totalSupply() external view returns (uint256);\n\n function transfer(address _to, uint256 _value) external returns (bool);\n\n function transferFrom(\n address _from,\n address _to,\n uint256 _value\n ) external returns (bool);\n\n function update_voting_escrow() external;\n\n function user_checkpoint(address addr) external returns (bool);\n\n function version() external view returns (string memory);\n\n function voting_escrow() external view returns (address);\n\n function withdraw(uint256 _value) external;\n\n function withdraw(uint256 _value, bool _claim_rewards) external;\n\n function withdraw(\n uint256 _value,\n bool _claim_rewards,\n address _receiver\n ) external;\n\n function working_balances(address arg0) external view returns (uint256);\n\n function working_supply() external view returns (uint256);\n}\n" + }, + "contracts/interfaces/ICVXLocker.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ninterface ICVXLocker {\n function lock(\n address _account,\n uint256 _amount,\n uint256 _spendRatio\n ) external;\n\n function lockedBalanceOf(address _account) external view returns (uint256);\n}\n" + }, + "contracts/interfaces/IDepositContract.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ninterface IDepositContract {\n /// @notice A processed deposit event.\n event DepositEvent(\n bytes pubkey,\n bytes withdrawal_credentials,\n bytes amount,\n bytes signature,\n bytes index\n );\n\n /// @notice Submit a Phase 0 DepositData object.\n /// @param pubkey A BLS12-381 public key.\n /// @param withdrawal_credentials Commitment to a public key for withdrawals.\n /// @param signature A BLS12-381 signature.\n /// @param deposit_data_root The SHA-256 hash of the SSZ-encoded DepositData object.\n /// Used as a protection against malformed input.\n function deposit(\n bytes calldata pubkey,\n bytes calldata withdrawal_credentials,\n bytes calldata signature,\n bytes32 deposit_data_root\n ) external payable;\n\n /// @notice Query the current deposit root hash.\n /// @return The deposit root hash.\n function get_deposit_root() external view returns (bytes32);\n\n /// @notice Query the current deposit count.\n /// @return The deposit count encoded as a little endian 64-bit number.\n function get_deposit_count() external view returns (bytes memory);\n}\n" + }, + "contracts/interfaces/IMerkl.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\ninterface IDistributor {\n event Claimed(address indexed user, address indexed token, uint256 amount);\n\n function claim(\n address[] calldata users,\n address[] calldata tokens,\n uint256[] calldata amounts,\n bytes32[][] calldata proofs\n ) external;\n}\n" + }, + "contracts/interfaces/IOracle.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\ninterface IOracle {\n /**\n * @dev returns the asset price in USD, in 8 decimal digits.\n *\n * The version of priceProvider deployed for OETH has 18 decimal digits\n */\n function price(address asset) external view returns (uint256);\n}\n" + }, + "contracts/interfaces/ISafe.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\ninterface ISafe {\n function execTransactionFromModule(\n address,\n uint256,\n bytes memory,\n uint8\n ) external returns (bool);\n}\n" + }, + "contracts/interfaces/ISSVNetwork.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nstruct Cluster {\n uint32 validatorCount;\n uint64 networkFeeIndex;\n uint64 index;\n bool active;\n uint256 balance;\n}\n\ninterface ISSVNetwork {\n /**********/\n /* Errors */\n /**********/\n\n error CallerNotOwner(); // 0x5cd83192\n error CallerNotWhitelisted(); // 0x8c6e5d71\n error FeeTooLow(); // 0x732f9413\n error FeeExceedsIncreaseLimit(); // 0x958065d9\n error NoFeeDeclared(); // 0x1d226c30\n error ApprovalNotWithinTimeframe(); // 0x97e4b518\n error OperatorDoesNotExist(); // 0x961e3e8c\n error InsufficientBalance(); // 0xf4d678b8\n error ValidatorDoesNotExist(); // 0xe51315d2\n error ClusterNotLiquidatable(); // 0x60300a8d\n error InvalidPublicKeyLength(); // 0x637297a4\n error InvalidOperatorIdsLength(); // 0x38186224\n error ClusterAlreadyEnabled(); // 0x3babafd2\n error ClusterIsLiquidated(); // 0x95a0cf33\n error ClusterDoesNotExists(); // 0x185e2b16\n error IncorrectClusterState(); // 0x12e04c87\n error UnsortedOperatorsList(); // 0xdd020e25\n error NewBlockPeriodIsBelowMinimum(); // 0x6e6c9cac\n error ExceedValidatorLimit(); // 0x6df5ab76\n error TokenTransferFailed(); // 0x045c4b02\n error SameFeeChangeNotAllowed(); // 0xc81272f8\n error FeeIncreaseNotAllowed(); // 0x410a2b6c\n error NotAuthorized(); // 0xea8e4eb5\n error OperatorsListNotUnique(); // 0xa5a1ff5d\n error OperatorAlreadyExists(); // 0x289c9494\n error TargetModuleDoesNotExist(); // 0x8f9195fb\n error MaxValueExceeded(); // 0x91aa3017\n error FeeTooHigh(); // 0xcd4e6167\n error PublicKeysSharesLengthMismatch(); // 0x9ad467b8\n error IncorrectValidatorStateWithData(bytes publicKey); // 0x89307938\n error ValidatorAlreadyExistsWithData(bytes publicKey); // 0x388e7999\n error EmptyPublicKeysList(); // df83e679\n\n // legacy errors\n error ValidatorAlreadyExists(); // 0x8d09a73e\n error IncorrectValidatorState(); // 0x2feda3c1\n\n event AdminChanged(address previousAdmin, address newAdmin);\n event BeaconUpgraded(address indexed beacon);\n event ClusterDeposited(\n address indexed owner,\n uint64[] operatorIds,\n uint256 value,\n Cluster cluster\n );\n event ClusterLiquidated(\n address indexed owner,\n uint64[] operatorIds,\n Cluster cluster\n );\n event ClusterReactivated(\n address indexed owner,\n uint64[] operatorIds,\n Cluster cluster\n );\n event ClusterWithdrawn(\n address indexed owner,\n uint64[] operatorIds,\n uint256 value,\n Cluster cluster\n );\n event DeclareOperatorFeePeriodUpdated(uint64 value);\n event ExecuteOperatorFeePeriodUpdated(uint64 value);\n event FeeRecipientAddressUpdated(\n address indexed owner,\n address recipientAddress\n );\n event Initialized(uint8 version);\n event LiquidationThresholdPeriodUpdated(uint64 value);\n event MinimumLiquidationCollateralUpdated(uint256 value);\n event NetworkEarningsWithdrawn(uint256 value, address recipient);\n event NetworkFeeUpdated(uint256 oldFee, uint256 newFee);\n event OperatorAdded(\n uint64 indexed operatorId,\n address indexed owner,\n bytes publicKey,\n uint256 fee\n );\n event OperatorFeeDeclarationCancelled(\n address indexed owner,\n uint64 indexed operatorId\n );\n event OperatorFeeDeclared(\n address indexed owner,\n uint64 indexed operatorId,\n uint256 blockNumber,\n uint256 fee\n );\n event OperatorFeeExecuted(\n address indexed owner,\n uint64 indexed operatorId,\n uint256 blockNumber,\n uint256 fee\n );\n event OperatorFeeIncreaseLimitUpdated(uint64 value);\n event OperatorMaximumFeeUpdated(uint64 maxFee);\n event OperatorRemoved(uint64 indexed operatorId);\n event OperatorWhitelistUpdated(\n uint64 indexed operatorId,\n address whitelisted\n );\n event OperatorWithdrawn(\n address indexed owner,\n uint64 indexed operatorId,\n uint256 value\n );\n event OwnershipTransferStarted(\n address indexed previousOwner,\n address indexed newOwner\n );\n event OwnershipTransferred(\n address indexed previousOwner,\n address indexed newOwner\n );\n event Upgraded(address indexed implementation);\n event ValidatorAdded(\n address indexed owner,\n uint64[] operatorIds,\n bytes publicKey,\n bytes shares,\n Cluster cluster\n );\n event ValidatorExited(\n address indexed owner,\n uint64[] operatorIds,\n bytes publicKey\n );\n event ValidatorRemoved(\n address indexed owner,\n uint64[] operatorIds,\n bytes publicKey,\n Cluster cluster\n );\n\n fallback() external;\n\n function acceptOwnership() external;\n\n function cancelDeclaredOperatorFee(uint64 operatorId) external;\n\n function declareOperatorFee(uint64 operatorId, uint256 fee) external;\n\n function deposit(\n address clusterOwner,\n uint64[] memory operatorIds,\n uint256 amount,\n Cluster memory cluster\n ) external;\n\n function executeOperatorFee(uint64 operatorId) external;\n\n function exitValidator(bytes memory publicKey, uint64[] memory operatorIds)\n external;\n\n function bulkExitValidator(\n bytes[] calldata publicKeys,\n uint64[] calldata operatorIds\n ) external;\n\n function getVersion() external pure returns (string memory version);\n\n function initialize(\n address token_,\n address ssvOperators_,\n address ssvClusters_,\n address ssvDAO_,\n address ssvViews_,\n uint64 minimumBlocksBeforeLiquidation_,\n uint256 minimumLiquidationCollateral_,\n uint32 validatorsPerOperatorLimit_,\n uint64 declareOperatorFeePeriod_,\n uint64 executeOperatorFeePeriod_,\n uint64 operatorMaxFeeIncrease_\n ) external;\n\n function liquidate(\n address clusterOwner,\n uint64[] memory operatorIds,\n Cluster memory cluster\n ) external;\n\n function owner() external view returns (address);\n\n function pendingOwner() external view returns (address);\n\n function proxiableUUID() external view returns (bytes32);\n\n function reactivate(\n uint64[] memory operatorIds,\n uint256 amount,\n Cluster memory cluster\n ) external;\n\n function reduceOperatorFee(uint64 operatorId, uint256 fee) external;\n\n function registerOperator(bytes memory publicKey, uint256 fee)\n external\n returns (uint64 id);\n\n function registerValidator(\n bytes memory publicKey,\n uint64[] memory operatorIds,\n bytes memory sharesData,\n uint256 amount,\n Cluster memory cluster\n ) external;\n\n function bulkRegisterValidator(\n bytes[] calldata publicKeys,\n uint64[] calldata operatorIds,\n bytes[] calldata sharesData,\n uint256 amount,\n Cluster memory cluster\n ) external;\n\n function removeOperator(uint64 operatorId) external;\n\n function removeValidator(\n bytes memory publicKey,\n uint64[] memory operatorIds,\n Cluster memory cluster\n ) external;\n\n function bulkRemoveValidator(\n bytes[] calldata publicKeys,\n uint64[] calldata operatorIds,\n Cluster memory cluster\n ) external;\n\n function renounceOwnership() external;\n\n function setFeeRecipientAddress(address recipientAddress) external;\n\n function setOperatorWhitelist(uint64 operatorId, address whitelisted)\n external;\n\n function transferOwnership(address newOwner) external;\n\n function updateDeclareOperatorFeePeriod(uint64 timeInSeconds) external;\n\n function updateExecuteOperatorFeePeriod(uint64 timeInSeconds) external;\n\n function updateLiquidationThresholdPeriod(uint64 blocks) external;\n\n function updateMaximumOperatorFee(uint64 maxFee) external;\n\n function updateMinimumLiquidationCollateral(uint256 amount) external;\n\n function updateModule(uint8 moduleId, address moduleAddress) external;\n\n function updateNetworkFee(uint256 fee) external;\n\n function updateOperatorFeeIncreaseLimit(uint64 percentage) external;\n\n function upgradeTo(address newImplementation) external;\n\n function upgradeToAndCall(address newImplementation, bytes memory data)\n external\n payable;\n\n function withdraw(\n uint64[] memory operatorIds,\n uint256 amount,\n Cluster memory cluster\n ) external;\n\n function withdrawAllOperatorEarnings(uint64 operatorId) external;\n\n function withdrawNetworkEarnings(uint256 amount) external;\n\n function withdrawOperatorEarnings(uint64 operatorId, uint256 amount)\n external;\n}\n" + }, + "contracts/interfaces/IStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title Platform interface to integrate with lending platform like Compound, AAVE etc.\n */\ninterface IStrategy {\n /**\n * @dev Deposit the given asset to platform\n * @param _asset asset address\n * @param _amount Amount to deposit\n */\n function deposit(address _asset, uint256 _amount) external;\n\n /**\n * @dev Deposit the entire balance of all supported assets in the Strategy\n * to the platform\n */\n function depositAll() external;\n\n /**\n * @dev Withdraw given asset from Lending platform\n */\n function withdraw(\n address _recipient,\n address _asset,\n uint256 _amount\n ) external;\n\n /**\n * @dev Liquidate all assets in strategy and return them to Vault.\n */\n function withdrawAll() external;\n\n /**\n * @dev Returns the current balance of the given asset.\n */\n function checkBalance(address _asset)\n external\n view\n returns (uint256 balance);\n\n /**\n * @dev Returns bool indicating whether strategy supports asset.\n */\n function supportsAsset(address _asset) external view returns (bool);\n\n /**\n * @dev Collect reward tokens from the Strategy.\n */\n function collectRewardTokens() external;\n\n /**\n * @dev The address array of the reward tokens for the Strategy.\n */\n function getRewardTokenAddresses() external view returns (address[] memory);\n\n function harvesterAddress() external view returns (address);\n\n function transferToken(address token, uint256 amount) external;\n\n function setRewardTokenAddresses(address[] calldata _rewardTokenAddresses)\n external;\n}\n" + }, + "contracts/interfaces/ISwapper.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\ninterface ISwapper {\n /**\n * @param fromAsset The token address of the asset being sold.\n * @param toAsset The token address of the asset being purchased.\n * @param fromAssetAmount The amount of assets being sold.\n * @param minToAssetAmmount The minimum amount of assets to be purchased.\n * @param data tx.data returned from 1Inch's /v5.0/1/swap API\n */\n function swap(\n address fromAsset,\n address toAsset,\n uint256 fromAssetAmount,\n uint256 minToAssetAmmount,\n bytes calldata data\n ) external returns (uint256 toAssetAmount);\n}\n" + }, + "contracts/interfaces/IVault.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { VaultStorage } from \"../vault/VaultStorage.sol\";\n\ninterface IVault {\n // slither-disable-start constable-states\n\n event AssetAllocated(address _asset, address _strategy, uint256 _amount);\n event StrategyApproved(address _addr);\n event StrategyRemoved(address _addr);\n event Mint(address _addr, uint256 _value);\n event Redeem(address _addr, uint256 _value);\n event CapitalPaused();\n event CapitalUnpaused();\n event DefaultStrategyUpdated(address _strategy);\n event RebasePaused();\n event RebaseUnpaused();\n event VaultBufferUpdated(uint256 _vaultBuffer);\n event AllocateThresholdUpdated(uint256 _threshold);\n event RebaseThresholdUpdated(uint256 _threshold);\n event StrategistUpdated(address _address);\n event MaxSupplyDiffChanged(uint256 maxSupplyDiff);\n event YieldDistribution(address _to, uint256 _yield, uint256 _fee);\n event TrusteeFeeBpsChanged(uint256 _basis);\n event TrusteeAddressChanged(address _address);\n event StrategyAddedToMintWhitelist(address indexed strategy);\n event StrategyRemovedFromMintWhitelist(address indexed strategy);\n event RebasePerSecondMaxChanged(uint256 rebaseRatePerSecond);\n event DripDurationChanged(uint256 dripDuration);\n event WithdrawalRequested(\n address indexed _withdrawer,\n uint256 indexed _requestId,\n uint256 _amount,\n uint256 _queued\n );\n event WithdrawalClaimed(\n address indexed _withdrawer,\n uint256 indexed _requestId,\n uint256 _amount\n );\n event WithdrawalClaimable(uint256 _claimable, uint256 _newClaimable);\n event WithdrawalClaimDelayUpdated(uint256 _newDelay);\n\n // Governable.sol\n function transferGovernance(address _newGovernor) external;\n\n function claimGovernance() external;\n\n function governor() external view returns (address);\n\n // VaultAdmin.sol\n function setVaultBuffer(uint256 _vaultBuffer) external;\n\n function vaultBuffer() external view returns (uint256);\n\n function setAutoAllocateThreshold(uint256 _threshold) external;\n\n function autoAllocateThreshold() external view returns (uint256);\n\n function setRebaseThreshold(uint256 _threshold) external;\n\n function rebaseThreshold() external view returns (uint256);\n\n function setStrategistAddr(address _address) external;\n\n function strategistAddr() external view returns (address);\n\n function setMaxSupplyDiff(uint256 _maxSupplyDiff) external;\n\n function maxSupplyDiff() external view returns (uint256);\n\n function setTrusteeAddress(address _address) external;\n\n function trusteeAddress() external view returns (address);\n\n function setTrusteeFeeBps(uint256 _basis) external;\n\n function trusteeFeeBps() external view returns (uint256);\n\n function approveStrategy(address _addr) external;\n\n function removeStrategy(address _addr) external;\n\n function setDefaultStrategy(address _strategy) external;\n\n function defaultStrategy() external view returns (address);\n\n function pauseRebase() external;\n\n function unpauseRebase() external;\n\n function rebasePaused() external view returns (bool);\n\n function pauseCapital() external;\n\n function unpauseCapital() external;\n\n function capitalPaused() external view returns (bool);\n\n function transferToken(address _asset, uint256 _amount) external;\n\n function withdrawAllFromStrategy(address _strategyAddr) external;\n\n function withdrawAllFromStrategies() external;\n\n function withdrawFromStrategy(\n address _strategyFromAddress,\n address[] calldata _assets,\n uint256[] calldata _amounts\n ) external;\n\n function depositToStrategy(\n address _strategyToAddress,\n address[] calldata _assets,\n uint256[] calldata _amounts\n ) external;\n\n // VaultCore.sol\n function mint(\n address _asset,\n uint256 _amount,\n uint256 _minimumOusdAmount\n ) external;\n\n function mintForStrategy(uint256 _amount) external;\n\n function redeem(uint256 _amount, uint256 _minimumUnitAmount) external;\n\n function burnForStrategy(uint256 _amount) external;\n\n function allocate() external;\n\n function rebase() external;\n\n function totalValue() external view returns (uint256 value);\n\n function checkBalance(address _asset) external view returns (uint256);\n\n /// @notice Deprecated: use calculateRedeemOutput\n function calculateRedeemOutputs(uint256 _amount)\n external\n view\n returns (uint256[] memory);\n\n function calculateRedeemOutput(uint256 _amount)\n external\n view\n returns (uint256);\n\n function getAssetCount() external view returns (uint256);\n\n function getAllAssets() external view returns (address[] memory);\n\n function getStrategyCount() external view returns (uint256);\n\n function getAllStrategies() external view returns (address[] memory);\n\n /// @notice Deprecated.\n function isSupportedAsset(address _asset) external view returns (bool);\n\n function dripper() external view returns (address);\n\n function asset() external view returns (address);\n\n function initialize(address) external;\n\n function addWithdrawalQueueLiquidity() external;\n\n function requestWithdrawal(uint256 _amount)\n external\n returns (uint256 requestId, uint256 queued);\n\n function claimWithdrawal(uint256 requestId)\n external\n returns (uint256 amount);\n\n function claimWithdrawals(uint256[] memory requestIds)\n external\n returns (uint256[] memory amounts, uint256 totalAmount);\n\n function withdrawalQueueMetadata()\n external\n view\n returns (VaultStorage.WithdrawalQueueMetadata memory);\n\n function withdrawalRequests(uint256 requestId)\n external\n view\n returns (VaultStorage.WithdrawalRequest memory);\n\n function addStrategyToMintWhitelist(address strategyAddr) external;\n\n function removeStrategyFromMintWhitelist(address strategyAddr) external;\n\n function isMintWhitelistedStrategy(address strategyAddr)\n external\n view\n returns (bool);\n\n function withdrawalClaimDelay() external view returns (uint256);\n\n function setWithdrawalClaimDelay(uint256 newDelay) external;\n\n function lastRebase() external view returns (uint64);\n\n function dripDuration() external view returns (uint64);\n\n function setDripDuration(uint256 _dripDuration) external;\n\n function rebasePerSecondMax() external view returns (uint64);\n\n function setRebaseRateMax(uint256 yearlyApr) external;\n\n function rebasePerSecondTarget() external view returns (uint64);\n\n function previewYield() external view returns (uint256 yield);\n\n function weth() external view returns (address);\n\n // slither-disable-end constable-states\n}\n" + }, + "contracts/interfaces/IWETH9.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ninterface IWETH9 {\n event Approval(address indexed src, address indexed guy, uint256 wad);\n event Deposit(address indexed dst, uint256 wad);\n event Transfer(address indexed src, address indexed dst, uint256 wad);\n event Withdrawal(address indexed src, uint256 wad);\n\n function allowance(address, address) external view returns (uint256);\n\n function approve(address guy, uint256 wad) external returns (bool);\n\n function balanceOf(address) external view returns (uint256);\n\n function decimals() external view returns (uint8);\n\n function deposit() external payable;\n\n function name() external view returns (string memory);\n\n function symbol() external view returns (string memory);\n\n function totalSupply() external view returns (uint256);\n\n function transfer(address dst, uint256 wad) external returns (bool);\n\n function transferFrom(\n address src,\n address dst,\n uint256 wad\n ) external returns (bool);\n\n function withdraw(uint256 wad) external;\n}\n" + }, + "contracts/interfaces/IWstETH.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ninterface IWstETH {\n /**\n * @notice Get amount of wstETH for a given amount of stETH\n * @param _stETHAmount amount of stETH\n * @return Amount of wstETH for a given stETH amount\n */\n function getWstETHByStETH(uint256 _stETHAmount)\n external\n view\n returns (uint256);\n\n /**\n * @notice Get amount of stETH for a given amount of wstETH\n * @param _wstETHAmount amount of wstETH\n * @return Amount of stETH for a given wstETH amount\n */\n function getStETHByWstETH(uint256 _wstETHAmount)\n external\n view\n returns (uint256);\n\n /**\n * @notice Get amount of stETH for a one wstETH\n * @return Amount of stETH for 1 wstETH\n */\n function stEthPerToken() external view returns (uint256);\n\n /**\n * @notice Get amount of wstETH for a one stETH\n * @return Amount of wstETH for a 1 stETH\n */\n function tokensPerStEth() external view returns (uint256);\n\n /**\n * @notice Exchanges stETH to wstETH\n * @param _stETHAmount amount of stETH to wrap in exchange for wstETH\n * @dev Requirements:\n * - `_stETHAmount` must be non-zero\n * - msg.sender must approve at least `_stETHAmount` stETH to this\n * contract.\n * - msg.sender must have at least `_stETHAmount` of stETH.\n * User should first approve _stETHAmount to the WstETH contract\n * @return Amount of wstETH user receives after wrap\n */\n function wrap(uint256 _stETHAmount) external returns (uint256);\n\n /**\n * @notice Exchanges wstETH to stETH\n * @param _wstETHAmount amount of wstETH to uwrap in exchange for stETH\n * @dev Requirements:\n * - `_wstETHAmount` must be non-zero\n * - msg.sender must have at least `_wstETHAmount` wstETH.\n * @return Amount of stETH user receives after unwrap\n */\n function unwrap(uint256 _wstETHAmount) external returns (uint256);\n}\n" + }, + "contracts/interfaces/morpho/compound/ICompoundOracle.sol": { + "content": "// SPDX-License-Identifier: GNU AGPLv3\npragma solidity ^0.8.0;\n\ninterface ICompoundOracle {\n function getUnderlyingPrice(address) external view returns (uint256);\n}\n" + }, + "contracts/interfaces/morpho/ILens.sol": { + "content": "// SPDX-License-Identifier: GNU AGPLv3\npragma solidity ^0.8.0;\n\nimport \"./compound/ICompoundOracle.sol\";\nimport \"./IMorpho.sol\";\n\ninterface ILens {\n /// STORAGE ///\n\n function MAX_BASIS_POINTS() external view returns (uint256);\n\n function WAD() external view returns (uint256);\n\n function morpho() external view returns (IMorpho);\n\n function comptroller() external view returns (IComptroller);\n\n /// GENERAL ///\n\n function getTotalSupply()\n external\n view\n returns (\n uint256 p2pSupplyAmount,\n uint256 poolSupplyAmount,\n uint256 totalSupplyAmount\n );\n\n function getTotalBorrow()\n external\n view\n returns (\n uint256 p2pBorrowAmount,\n uint256 poolBorrowAmount,\n uint256 totalBorrowAmount\n );\n\n /// MARKETS ///\n\n function isMarketCreated(address _poolToken) external view returns (bool);\n\n function isMarketCreatedAndNotPaused(address _poolToken)\n external\n view\n returns (bool);\n\n function isMarketCreatedAndNotPausedNorPartiallyPaused(address _poolToken)\n external\n view\n returns (bool);\n\n function getAllMarkets()\n external\n view\n returns (address[] memory marketsCreated_);\n\n function getMainMarketData(address _poolToken)\n external\n view\n returns (\n uint256 avgSupplyRatePerBlock,\n uint256 avgBorrowRatePerBlock,\n uint256 p2pSupplyAmount,\n uint256 p2pBorrowAmount,\n uint256 poolSupplyAmount,\n uint256 poolBorrowAmount\n );\n\n function getAdvancedMarketData(address _poolToken)\n external\n view\n returns (\n uint256 p2pSupplyIndex,\n uint256 p2pBorrowIndex,\n uint256 poolSupplyIndex,\n uint256 poolBorrowIndex,\n uint32 lastUpdateBlockNumber,\n uint256 p2pSupplyDelta,\n uint256 p2pBorrowDelta\n );\n\n function getMarketConfiguration(address _poolToken)\n external\n view\n returns (\n address underlying,\n bool isCreated,\n bool p2pDisabled,\n bool isPaused,\n bool isPartiallyPaused,\n uint16 reserveFactor,\n uint16 p2pIndexCursor,\n uint256 collateralFactor\n );\n\n function getTotalMarketSupply(address _poolToken)\n external\n view\n returns (uint256 p2pSupplyAmount, uint256 poolSupplyAmount);\n\n function getTotalMarketBorrow(address _poolToken)\n external\n view\n returns (uint256 p2pBorrowAmount, uint256 poolBorrowAmount);\n\n /// INDEXES ///\n\n function getCurrentP2PSupplyIndex(address _poolToken)\n external\n view\n returns (uint256);\n\n function getCurrentP2PBorrowIndex(address _poolToken)\n external\n view\n returns (uint256);\n\n function getCurrentPoolIndexes(address _poolToken)\n external\n view\n returns (\n uint256 currentPoolSupplyIndex,\n uint256 currentPoolBorrowIndex\n );\n\n function getIndexes(address _poolToken, bool _computeUpdatedIndexes)\n external\n view\n returns (\n uint256 p2pSupplyIndex,\n uint256 p2pBorrowIndex,\n uint256 poolSupplyIndex,\n uint256 poolBorrowIndex\n );\n\n /// USERS ///\n\n function getEnteredMarkets(address _user)\n external\n view\n returns (address[] memory enteredMarkets);\n\n function getUserHealthFactor(\n address _user,\n address[] calldata _updatedMarkets\n ) external view returns (uint256);\n\n function getUserBalanceStates(\n address _user,\n address[] calldata _updatedMarkets\n )\n external\n view\n returns (\n uint256 collateralValue,\n uint256 debtValue,\n uint256 maxDebtValue\n );\n\n function getCurrentSupplyBalanceInOf(address _poolToken, address _user)\n external\n view\n returns (\n uint256 balanceOnPool,\n uint256 balanceInP2P,\n uint256 totalBalance\n );\n\n function getCurrentBorrowBalanceInOf(address _poolToken, address _user)\n external\n view\n returns (\n uint256 balanceOnPool,\n uint256 balanceInP2P,\n uint256 totalBalance\n );\n\n function getUserMaxCapacitiesForAsset(address _user, address _poolToken)\n external\n view\n returns (uint256 withdrawable, uint256 borrowable);\n\n function getUserHypotheticalBalanceStates(\n address _user,\n address _poolToken,\n uint256 _withdrawnAmount,\n uint256 _borrowedAmount\n ) external view returns (uint256 debtValue, uint256 maxDebtValue);\n\n function getUserLiquidityDataForAsset(\n address _user,\n address _poolToken,\n bool _computeUpdatedIndexes,\n ICompoundOracle _oracle\n ) external view returns (Types.AssetLiquidityData memory assetData);\n\n function isLiquidatable(address _user, address[] memory _updatedMarkets)\n external\n view\n returns (bool);\n\n function computeLiquidationRepayAmount(\n address _user,\n address _poolTokenBorrowed,\n address _poolTokenCollateral,\n address[] calldata _updatedMarkets\n ) external view returns (uint256 toRepay);\n\n /// RATES ///\n\n function getAverageSupplyRatePerBlock(address _poolToken)\n external\n view\n returns (\n uint256 avgSupplyRatePerBlock,\n uint256 p2pSupplyAmount,\n uint256 poolSupplyAmount\n );\n\n function getAverageBorrowRatePerBlock(address _poolToken)\n external\n view\n returns (\n uint256 avgBorrowRatePerBlock,\n uint256 p2pBorrowAmount,\n uint256 poolBorrowAmount\n );\n\n function getNextUserSupplyRatePerBlock(\n address _poolToken,\n address _user,\n uint256 _amount\n )\n external\n view\n returns (\n uint256 nextSupplyRatePerBlock,\n uint256 balanceOnPool,\n uint256 balanceInP2P,\n uint256 totalBalance\n );\n\n function getNextUserBorrowRatePerBlock(\n address _poolToken,\n address _user,\n uint256 _amount\n )\n external\n view\n returns (\n uint256 nextBorrowRatePerBlock,\n uint256 balanceOnPool,\n uint256 balanceInP2P,\n uint256 totalBalance\n );\n\n function getCurrentUserSupplyRatePerBlock(address _poolToken, address _user)\n external\n view\n returns (uint256);\n\n function getCurrentUserBorrowRatePerBlock(address _poolToken, address _user)\n external\n view\n returns (uint256);\n\n function getRatesPerBlock(address _poolToken)\n external\n view\n returns (\n uint256 p2pSupplyRate,\n uint256 p2pBorrowRate,\n uint256 poolSupplyRate,\n uint256 poolBorrowRate\n );\n\n /// REWARDS ///\n\n function getUserUnclaimedRewards(\n address[] calldata _poolTokens,\n address _user\n ) external view returns (uint256 unclaimedRewards);\n\n function getAccruedSupplierComp(\n address _supplier,\n address _poolToken,\n uint256 _balance\n ) external view returns (uint256);\n\n function getAccruedBorrowerComp(\n address _borrower,\n address _poolToken,\n uint256 _balance\n ) external view returns (uint256);\n\n function getCurrentCompSupplyIndex(address _poolToken)\n external\n view\n returns (uint256);\n\n function getCurrentCompBorrowIndex(address _poolToken)\n external\n view\n returns (uint256);\n}\n" + }, + "contracts/interfaces/morpho/IMorpho.sol": { + "content": "// SPDX-License-Identifier: GNU AGPLv3\npragma solidity ^0.8.0;\n\nimport \"./Types.sol\";\nimport \"../IComptroller.sol\";\nimport \"./compound/ICompoundOracle.sol\";\n\n// prettier-ignore\ninterface IMorpho {\n function comptroller() external view returns (IComptroller);\n function supply(address _poolTokenAddress, address _onBehalf, uint256 _amount) external;\n function supply(address _poolTokenAddress, address _onBehalf, uint256 _amount, uint256 _maxGasForMatching) external;\n function withdraw(address _poolTokenAddress, uint256 _amount) external;\n function claimRewards(\n address[] calldata _cTokenAddresses,\n bool _tradeForMorphoToken\n ) external returns (uint256 claimedAmount);\n}\n" + }, + "contracts/interfaces/morpho/Types.sol": { + "content": "// SPDX-License-Identifier: GNU AGPLv3\npragma solidity ^0.8.0;\n\n/// @title Types.\n/// @author Morpho Labs.\n/// @custom:contact security@morpho.xyz\n/// @dev Common types and structs used in Moprho contracts.\nlibrary Types {\n /// ENUMS ///\n\n enum PositionType {\n SUPPLIERS_IN_P2P,\n SUPPLIERS_ON_POOL,\n BORROWERS_IN_P2P,\n BORROWERS_ON_POOL\n }\n\n /// STRUCTS ///\n\n struct SupplyBalance {\n uint256 inP2P; // In supplier's peer-to-peer unit, a unit that grows in underlying value, to keep track of the interests earned by suppliers in peer-to-peer. Multiply by the peer-to-peer supply index to get the underlying amount.\n uint256 onPool; // In cToken. Multiply by the pool supply index to get the underlying amount.\n }\n\n struct BorrowBalance {\n uint256 inP2P; // In borrower's peer-to-peer unit, a unit that grows in underlying value, to keep track of the interests paid by borrowers in peer-to-peer. Multiply by the peer-to-peer borrow index to get the underlying amount.\n uint256 onPool; // In cdUnit, a unit that grows in value, to keep track of the debt increase when borrowers are on Compound. Multiply by the pool borrow index to get the underlying amount.\n }\n\n // Max gas to consume during the matching process for supply, borrow, withdraw and repay functions.\n struct MaxGasForMatching {\n uint64 supply;\n uint64 borrow;\n uint64 withdraw;\n uint64 repay;\n }\n\n struct Delta {\n uint256 p2pSupplyDelta; // Difference between the stored peer-to-peer supply amount and the real peer-to-peer supply amount (in pool supply unit).\n uint256 p2pBorrowDelta; // Difference between the stored peer-to-peer borrow amount and the real peer-to-peer borrow amount (in pool borrow unit).\n uint256 p2pSupplyAmount; // Sum of all stored peer-to-peer supply (in peer-to-peer supply unit).\n uint256 p2pBorrowAmount; // Sum of all stored peer-to-peer borrow (in peer-to-peer borrow unit).\n }\n\n struct AssetLiquidityData {\n uint256 collateralValue; // The collateral value of the asset.\n uint256 maxDebtValue; // The maximum possible debt value of the asset.\n uint256 debtValue; // The debt value of the asset.\n uint256 underlyingPrice; // The price of the token.\n uint256 collateralFactor; // The liquidation threshold applied on this token.\n }\n\n struct LiquidityData {\n uint256 collateralValue; // The collateral value.\n uint256 maxDebtValue; // The maximum debt value possible.\n uint256 debtValue; // The debt value.\n }\n\n // Variables are packed together to save gas (will not exceed their limit during Morpho's lifetime).\n struct LastPoolIndexes {\n uint32 lastUpdateBlockNumber; // The last time the peer-to-peer indexes were updated.\n uint112 lastSupplyPoolIndex; // Last pool supply index.\n uint112 lastBorrowPoolIndex; // Last pool borrow index.\n }\n\n struct MarketParameters {\n uint16 reserveFactor; // Proportion of the interest earned by users sent to the DAO for each market, in basis point (100% = 10 000). The value is set at market creation.\n uint16 p2pIndexCursor; // Position of the peer-to-peer rate in the pool's spread. Determine the weights of the weighted arithmetic average in the indexes computations ((1 - p2pIndexCursor) * r^S + p2pIndexCursor * r^B) (in basis point).\n }\n\n struct MarketStatus {\n bool isCreated; // Whether or not this market is created.\n bool isPaused; // Whether the market is paused or not (all entry points on Morpho are frozen; supply, borrow, withdraw, repay and liquidate).\n bool isPartiallyPaused; // Whether the market is partially paused or not (only supply and borrow are frozen).\n }\n}\n" + }, + "contracts/interfaces/plume/IFeeRegistry.sol": { + "content": "// SPDX-License-Identifier: GPL-2.0-or-later\n// As the copyright holder of this work, Ubiquity Labs retains\n// the right to distribute, use, and modify this code under any license of\n// their choosing, in addition to the terms of the GPL-v2 or later.\npragma solidity ^0.8.25;\n\ninterface IFeeRegistry {\n function registerFee(\n bool isTokenA,\n uint32 binId,\n uint256 binFeeInQuote\n ) external;\n}\n" + }, + "contracts/interfaces/plume/ILiquidityRegistry.sol": { + "content": "// SPDX-License-Identifier: GPL-2.0-or-later\n// As the copyright holder of this work, Ubiquity Labs retains\n// the right to distribute, use, and modify this code under any license of\n// their choosing, in addition to the terms of the GPL-v2 or later.\npragma solidity ^0.8.25;\n\nimport { IMaverickV2Pool } from \"./IMaverickV2Pool.sol\";\n\ninterface ILiquidityRegistry {\n function notifyBinLiquidity(\n IMaverickV2Pool pool,\n uint256 tokenId,\n uint32 binId,\n uint256 currentBinLpBalance\n ) external;\n}\n" + }, + "contracts/interfaces/plume/IMaverickV2Factory.sol": { + "content": "// SPDX-License-Identifier: GPL-2.0-or-later\n// As the copyright holder of this work, Ubiquity Labs retains\n// the right to distribute, use, and modify this code under any license of\n// their choosing, in addition to the terms of the GPL-v2 or later.\npragma solidity ^0.8.25;\n\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\n\nimport { IFeeRegistry } from \"./IFeeRegistry.sol\";\nimport { IMaverickV2Pool } from \"./IMaverickV2Pool.sol\";\n\ninterface IMaverickV2Factory {\n error FactorAlreadyInitialized();\n error FactorNotInitialized();\n error FactoryInvalidTokenOrder(IERC20 _tokenA, IERC20 _tokenB);\n error FactoryInvalidFee();\n error FactoryInvalidKinds(uint8 kinds);\n error FactoryInvalidTickSpacing(uint256 tickSpacing);\n error FactoryInvalidLookback(uint256 lookback);\n error FactoryInvalidTokenDecimals(uint8 decimalsA, uint8 decimalsB);\n error FactoryPoolAlreadyExists(\n uint256 feeAIn,\n uint256 feeBIn,\n uint256 tickSpacing,\n uint256 lookback,\n IERC20 tokenA,\n IERC20 tokenB,\n uint8 kinds,\n address accessor\n );\n error FactoryAccessorMustBeNonZero();\n error NotImplemented();\n\n event PoolCreated(\n IMaverickV2Pool poolAddress,\n uint8 protocolFeeRatio,\n uint256 feeAIn,\n uint256 feeBIn,\n uint256 tickSpacing,\n uint256 lookback,\n int32 activeTick,\n IERC20 tokenA,\n IERC20 tokenB,\n uint8 kinds,\n address accessor\n );\n event SetFactoryProtocolFeeReceiver(address receiver);\n event SetFactoryProtocolFeeRegistry(IFeeRegistry registry);\n\n struct DeployParameters {\n uint64 feeAIn;\n uint64 feeBIn;\n uint32 lookback;\n int32 activeTick;\n uint64 tokenAScale;\n uint64 tokenBScale;\n // slot\n IERC20 tokenA;\n // slot\n IERC20 tokenB;\n // slot\n uint16 tickSpacing;\n uint8 options;\n address accessor;\n }\n\n /**\n * @notice Called by deployer library to initialize a pool.\n */\n function deployParameters()\n external\n view\n returns (\n uint64 feeAIn,\n uint64 feeBIn,\n uint32 lookback,\n int32 activeTick,\n uint64 tokenAScale,\n uint64 tokenBScale,\n // slot\n IERC20 tokenA,\n // slot\n IERC20 tokenB,\n // slot\n uint16 tickSpacing,\n uint8 options,\n address accessor\n );\n\n /**\n * @notice Create a new MaverickV2Pool with symmetric swap fees.\n * @param fee Fraction of the pool swap amount that is retained as an LP in\n * D18 scale.\n * @param tickSpacing Tick spacing of pool where 1.0001^tickSpacing is the\n * bin width.\n * @param lookback Pool lookback in seconds.\n * @param tokenA Address of tokenA.\n * @param tokenB Address of tokenB.\n * @param activeTick Tick position that contains the active bins.\n * @param kinds 1-15 number to represent the active kinds\n * 0b0001 = static;\n * 0b0010 = right;\n * 0b0100 = left;\n * 0b1000 = both.\n * E.g. a pool with all 4 modes will have kinds = b1111 = 15\n */\n function create(\n uint64 fee,\n uint16 tickSpacing,\n uint32 lookback,\n IERC20 tokenA,\n IERC20 tokenB,\n int32 activeTick,\n uint8 kinds\n ) external returns (IMaverickV2Pool);\n\n /**\n * @notice Create a new MaverickV2Pool.\n * @param feeAIn Fraction of the pool swap amount for tokenA-input swaps\n * that is retained as an LP in D18 scale.\n * @param feeBIn Fraction of the pool swap amount for tokenB-input swaps\n * that is retained as an LP in D18 scale.\n * @param tickSpacing Tick spacing of pool where 1.0001^tickSpacing is the\n * bin width.\n * @param lookback Pool lookback in seconds.\n * @param tokenA Address of tokenA.\n * @param tokenB Address of tokenB.\n * @param activeTick Tick position that contains the active bins.\n * @param kinds 1-15 number to represent the active kinds\n * 0b0001 = static;\n * 0b0010 = right;\n * 0b0100 = left;\n * 0b1000 = both.\n * e.g. a pool with all 4 modes will have kinds = b1111 = 15\n */\n function create(\n uint64 feeAIn,\n uint64 feeBIn,\n uint16 tickSpacing,\n uint32 lookback,\n IERC20 tokenA,\n IERC20 tokenB,\n int32 activeTick,\n uint8 kinds\n ) external returns (IMaverickV2Pool);\n\n /**\n * @notice Bool indicating whether the pool was deployed from this factory.\n */\n function isFactoryPool(IMaverickV2Pool pool) external view returns (bool);\n\n /**\n * @notice Address that receives the protocol fee\n */\n function protocolFeeReceiver() external view returns (address);\n\n /**\n * @notice Address notified on swaps of the protocol fee\n */\n function protocolFeeRegistry() external view returns (IFeeRegistry);\n\n /**\n * @notice Lookup a pool for given parameters.\n */\n function lookup(\n uint256 feeAIn,\n uint256 feeBIn,\n uint256 tickSpacing,\n uint256 lookback,\n IERC20 tokenA,\n IERC20 tokenB,\n uint8 kinds\n ) external view returns (IMaverickV2Pool);\n\n /**\n * @notice Lookup a pool for given parameters.\n */\n function lookup(\n IERC20 _tokenA,\n IERC20 _tokenB,\n uint256 startIndex,\n uint256 endIndex\n ) external view returns (IMaverickV2Pool[] memory pools);\n\n /**\n * @notice Lookup a pool for given parameters.\n */\n function lookup(uint256 startIndex, uint256 endIndex)\n external\n view\n returns (IMaverickV2Pool[] memory pools);\n\n /**\n * @notice Count of permissionless pools.\n */\n function poolCount() external view returns (uint256 _poolCount);\n\n /**\n * @notice Count of pools for a given accessor and token pair. For\n * permissionless pools, pass `accessor = address(0)`.\n */\n function poolByTokenCount(\n IERC20 _tokenA,\n IERC20 _tokenB,\n address accessor\n ) external view returns (uint256 _poolCount);\n\n /**\n * @notice Get the current factory owner.\n */\n function owner() external view returns (address);\n}\n" + }, + "contracts/interfaces/plume/IMaverickV2LiquidityManager.sol": { + "content": "// SPDX-License-Identifier: GPL-2.0-or-later\npragma solidity ^0.8.25;\n\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport { IMaverickV2Pool } from \"./IMaverickV2Pool.sol\";\n\nimport { IMaverickV2Position } from \"./IMaverickV2Position.sol\";\nimport { IMaverickV2PoolLens } from \"./IMaverickV2PoolLens.sol\";\n\ninterface IMaverickV2LiquidityManager {\n error LiquidityManagerNotFactoryPool();\n error LiquidityManagerNotTokenIdOwner();\n\n /**\n * @notice Maverick V2 NFT position contract that tracks NFT-based\n * liquditiy positions.\n */\n function position() external view returns (IMaverickV2Position);\n\n /**\n * @notice Create Maverick V2 pool. Function is a pass through to the pool\n * factory and is provided here so that is can be assembled as part of a\n * multicall transaction.\n */\n function createPool(\n uint64 fee,\n uint16 tickSpacing,\n uint32 lookback,\n IERC20 tokenA,\n IERC20 tokenB,\n int32 activeTick,\n uint8 kinds\n ) external payable returns (IMaverickV2Pool pool);\n\n /**\n * @notice Add Liquidity position NFT for msg.sender by specifying\n * msg.sender's token index.\n * @dev Token index is different from tokenId.\n * On the Position NFT contract a user can own multiple NFT tokenIds and\n * these are indexes by an enumeration index which is the `index` input\n * here.\n *\n * See addLiquidity for a description of the add params.\n */\n function addPositionLiquidityToSenderByTokenIndex(\n IMaverickV2Pool pool,\n uint256 index,\n bytes memory packedSqrtPriceBreaks,\n bytes[] memory packedArgs\n )\n external\n payable\n returns (\n uint256 tokenAAmount,\n uint256 tokenBAmount,\n uint32[] memory binIds\n );\n\n /**\n * @notice Mint new tokenId in the Position NFt contract to msg.sender.\n * Both mints an NFT and adds liquidity to the pool that is held by the\n * NFT.\n */\n function mintPositionNftToSender(\n IMaverickV2Pool pool,\n bytes calldata packedSqrtPriceBreaks,\n bytes[] calldata packedArgs\n )\n external\n payable\n returns (\n uint256 tokenAAmount,\n uint256 tokenBAmount,\n uint32[] memory binIds,\n uint256 tokenId\n );\n\n /**\n * @notice Donates liqudity to a pool that is held by the position contract\n * and will never be retrievable. Can be used to start a pool and ensure\n * there will always be a base level of liquditiy in the pool.\n */\n function donateLiquidity(\n IMaverickV2Pool pool,\n IMaverickV2Pool.AddLiquidityParams memory args\n ) external payable;\n\n /**\n * @notice Packs sqrtPrice breaks array with this format: [length,\n * array[0], array[1],..., array[length-1]] where length is 1 byte.\n */\n function packUint88Array(uint88[] memory fullArray)\n external\n pure\n returns (bytes memory packedArray);\n\n /**\n * @notice Packs addLiquidity paramters array element-wise.\n */\n function packAddLiquidityArgsArray(\n IMaverickV2Pool.AddLiquidityParams[] memory args\n ) external pure returns (bytes[] memory argsPacked);\n}\n" + }, + "contracts/interfaces/plume/IMaverickV2Pool.sol": { + "content": "// SPDX-License-Identifier: GPL-2.0-or-later\n// As the copyright holder of this work, Ubiquity Labs retains\n// the right to distribute, use, and modify this code under any license of\n// their choosing, in addition to the terms of the GPL-v2 or later.\npragma solidity ^0.8.0;\n\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport { IMaverickV2Factory } from \"./IMaverickV2Factory.sol\";\n\ninterface IMaverickV2Pool {\n error PoolZeroLiquidityAdded();\n error PoolMinimumLiquidityNotMet();\n error PoolLocked();\n error PoolInvalidFee();\n error PoolTicksNotSorted(uint256 index, int256 previousTick, int256 tick);\n error PoolTicksAmountsLengthMismatch(\n uint256 ticksLength,\n uint256 amountsLength\n );\n error PoolBinIdsAmountsLengthMismatch(\n uint256 binIdsLength,\n uint256 amountsLength\n );\n error PoolKindNotSupported(uint256 kinds, uint256 kind);\n error PoolInsufficientBalance(\n uint256 deltaLpAmount,\n uint256 accountBalance\n );\n error PoolReservesExceedMaximum(uint256 amount);\n error PoolValueExceedsBits(uint256 amount, uint256 bits);\n error PoolTickMaxExceeded(uint256 tick);\n error PoolMigrateBinFirst();\n error PoolCurrentTickBeyondSwapLimit(int32 startingTick);\n error PoolSenderNotAccessor(address sender_, address accessor);\n error PoolSenderNotFactory(address sender_, address accessor);\n error PoolFunctionNotImplemented();\n error PoolTokenNotSolvent(\n uint256 internalReserve,\n uint256 tokenBalance,\n IERC20 token\n );\n error PoolNoProtocolFeeReceiverSet();\n\n event PoolSwap(\n address sender,\n address recipient,\n SwapParams params,\n uint256 amountIn,\n uint256 amountOut\n );\n event PoolFlashLoan(\n address sender,\n address recipient,\n uint256 amountA,\n uint256 amountB\n );\n event PoolProtocolFeeCollected(IERC20 token, uint256 protocolFee);\n\n event PoolAddLiquidity(\n address sender,\n address recipient,\n uint256 subaccount,\n AddLiquidityParams params,\n uint256 tokenAAmount,\n uint256 tokenBAmount,\n uint32[] binIds\n );\n\n event PoolMigrateBinsUpStack(\n address sender,\n uint32 binId,\n uint32 maxRecursion\n );\n\n event PoolRemoveLiquidity(\n address sender,\n address recipient,\n uint256 subaccount,\n RemoveLiquidityParams params,\n uint256 tokenAOut,\n uint256 tokenBOut\n );\n\n event PoolTickState(int32 tick, uint256 reserveA, uint256 reserveB);\n event PoolTickBinUpdate(int32 tick, uint8 kind, uint32 binId);\n event PoolSqrtPrice(uint256 sqrtPrice);\n\n /**\n * @notice Tick state parameters.\n */\n struct TickState {\n uint128 reserveA;\n uint128 reserveB;\n uint128 totalSupply;\n uint32[4] binIdsByTick;\n }\n\n /**\n * @notice Tick data parameters.\n * @param currentReserveA Current reserve of token A.\n * @param currentReserveB Current reserve of token B.\n * @param currentLiquidity Current liquidity amount.\n */\n struct TickData {\n uint256 currentReserveA;\n uint256 currentReserveB;\n uint256 currentLiquidity;\n }\n\n /**\n * @notice Bin state parameters.\n * @param mergeBinBalance LP token balance that this bin possesses of the merge bin.\n * @param mergeId Bin ID of the bin that this bin has merged into.\n * @param totalSupply Total amount of LP tokens in this bin.\n * @param kind One of the 4 kinds (0=static, 1=right, 2=left, 3=both).\n * @param tick The lower price tick of the bin in its current state.\n * @param tickBalance Balance of the tick.\n */\n struct BinState {\n uint128 mergeBinBalance;\n uint128 tickBalance;\n uint128 totalSupply;\n uint8 kind;\n int32 tick;\n uint32 mergeId;\n }\n\n /**\n * @notice Parameters for swap.\n * @param amount Amount of the token that is either the input if exactOutput is false\n * or the output if exactOutput is true.\n * @param tokenAIn Boolean indicating whether tokenA is the input.\n * @param exactOutput Boolean indicating whether the amount specified is\n * the exact output amount (true).\n * @param tickLimit The furthest tick a swap will execute in. If no limit\n * is desired, value should be set to type(int32).max for a tokenAIn swap\n * and type(int32).min for a swap where tokenB is the input.\n */\n struct SwapParams {\n uint256 amount;\n bool tokenAIn;\n bool exactOutput;\n int32 tickLimit;\n }\n\n /**\n * @notice Parameters associated with adding liquidity.\n * @param kind One of the 4 kinds (0=static, 1=right, 2=left, 3=both).\n * @param ticks Array of ticks to add liquidity to.\n * @param amounts Array of bin LP amounts to add.\n */\n struct AddLiquidityParams {\n uint8 kind;\n int32[] ticks;\n uint128[] amounts;\n }\n\n /**\n * @notice Parameters for each bin that will have liquidity removed.\n * @param binIds Index array of the bins losing liquidity.\n * @param amounts Array of bin LP amounts to remove.\n */\n struct RemoveLiquidityParams {\n uint32[] binIds;\n uint128[] amounts;\n }\n\n /**\n * @notice State of the pool.\n * @param reserveA Pool tokenA balanceOf at end of last operation\n * @param reserveB Pool tokenB balanceOf at end of last operation\n * @param lastTwaD8 Value of log time weighted average price at last block.\n * Value is 8-decimal scale and is in the fractional tick domain. E.g. a\n * value of 12.3e8 indicates the TWAP was 3/10ths of the way into the 12th\n * tick.\n * @param lastLogPriceD8 Value of log price at last block. Value is\n * 8-decimal scale and is in the fractional tick domain. E.g. a value of\n * 12.3e8 indicates the price was 3/10ths of the way into the 12th tick.\n * @param lastTimestamp Last block.timestamp value in seconds for latest\n * swap transaction.\n * @param activeTick Current tick position that contains the active bins.\n * @param isLocked Pool isLocked, E.g., locked or unlocked; isLocked values\n * defined in Pool.sol.\n * @param binCounter Index of the last bin created.\n * @param protocolFeeRatioD3 Ratio of the swap fee that is kept for the\n * protocol.\n */\n struct State {\n uint128 reserveA;\n uint128 reserveB;\n int64 lastTwaD8;\n int64 lastLogPriceD8;\n uint40 lastTimestamp;\n int32 activeTick;\n bool isLocked;\n uint32 binCounter;\n uint8 protocolFeeRatioD3;\n }\n\n /**\n * @notice Internal data used for data passing between Pool and Bin code.\n */\n struct BinDelta {\n uint128 deltaA;\n uint128 deltaB;\n }\n\n /**\n * @notice 1-15 number to represent the active kinds.\n * @notice 0b0001 = static;\n * @notice 0b0010 = right;\n * @notice 0b0100 = left;\n * @notice 0b1000 = both;\n *\n * E.g. a pool with all 4 modes will have kinds = b1111 = 15\n */\n function kinds() external view returns (uint8 _kinds);\n\n /**\n * @notice Pool swap fee for the given direction (A-in or B-in swap) in\n * 18-decimal format. E.g. 0.01e18 is a 1% swap fee.\n */\n function fee(bool tokenAIn) external view returns (uint256);\n\n /**\n * @notice TickSpacing of pool where 1.0001^tickSpacing is the bin width.\n */\n function tickSpacing() external view returns (uint256);\n\n /**\n * @notice Lookback period of pool in seconds.\n */\n function lookback() external view returns (uint256);\n\n /**\n * @notice Address of Pool accessor. This is Zero address for\n * permissionless pools.\n */\n function accessor() external view returns (address);\n\n /**\n * @notice Pool tokenA. Address of tokenA is such that tokenA < tokenB.\n */\n function tokenA() external view returns (IERC20);\n\n /**\n * @notice Pool tokenB.\n */\n function tokenB() external view returns (IERC20);\n\n /**\n * @notice Deploying factory of the pool and also contract that has ability\n * to set and collect protocol fees for the pool.\n */\n function factory() external view returns (IMaverickV2Factory);\n\n /**\n * @notice Most significant bit of scale value is a flag to indicate whether\n * tokenA has more or less than 18 decimals. Scale is used in conjuction\n * with Math.toScale/Math.fromScale functions to convert from token amounts\n * to D18 scale internal pool accounting.\n */\n function tokenAScale() external view returns (uint256);\n\n /**\n * @notice Most significant bit of scale value is a flag to indicate whether\n * tokenA has more or less than 18 decimals. Scale is used in conjuction\n * with Math.toScale/Math.fromScale functions to convert from token amounts\n * to D18 scale internal pool accounting.\n */\n function tokenBScale() external view returns (uint256);\n\n /**\n * @notice ID of bin at input tick position and kind.\n */\n function binIdByTickKind(int32 tick, uint256 kind)\n external\n view\n returns (uint32);\n\n /**\n * @notice Accumulated tokenA protocol fee.\n */\n function protocolFeeA() external view returns (uint128);\n\n /**\n * @notice Accumulated tokenB protocol fee.\n */\n function protocolFeeB() external view returns (uint128);\n\n /**\n * @notice Lending fee rate on flash loans.\n */\n function lendingFeeRateD18() external view returns (uint256);\n\n /**\n * @notice External function to get the current time-weighted average price.\n */\n function getCurrentTwa() external view returns (int256);\n\n /**\n * @notice External function to get the state of the pool.\n */\n function getState() external view returns (State memory);\n\n /**\n * @notice Return state of Bin at input binId.\n */\n function getBin(uint32 binId) external view returns (BinState memory bin);\n\n /**\n * @notice Return state of Tick at input tick position.\n */\n function getTick(int32 tick)\n external\n view\n returns (TickState memory tickState);\n\n /**\n * @notice Retrieves the balance of a user within a bin.\n * @param user The user's address.\n * @param subaccount The subaccount for the user.\n * @param binId The ID of the bin.\n */\n function balanceOf(\n address user,\n uint256 subaccount,\n uint32 binId\n ) external view returns (uint128 lpToken);\n\n /**\n * @notice Add liquidity to a pool. This function allows users to deposit\n * tokens into a liquidity pool.\n * @dev This function will call `maverickV2AddLiquidityCallback` on the\n * calling contract to collect the tokenA/tokenB payment.\n * @param recipient The account that will receive credit for the added liquidity.\n * @param subaccount The account that will receive credit for the added liquidity.\n * @param params Parameters containing the details for adding liquidity,\n * such as token types and amounts.\n * @param data Bytes information that gets passed to the callback.\n * @return tokenAAmount The amount of token A added to the pool.\n * @return tokenBAmount The amount of token B added to the pool.\n * @return binIds An array of bin IDs where the liquidity is stored.\n */\n function addLiquidity(\n address recipient,\n uint256 subaccount,\n AddLiquidityParams calldata params,\n bytes calldata data\n )\n external\n returns (\n uint256 tokenAAmount,\n uint256 tokenBAmount,\n uint32[] memory binIds\n );\n\n /**\n * @notice Removes liquidity from the pool.\n * @dev Liquidy can only be removed from a bin that is either unmerged or\n * has a mergeId of an unmerged bin. If a bin is merged more than one\n * level deep, it must be migrated up the merge stack to the root bin\n * before liquidity removal.\n * @param recipient The address to receive the tokens.\n * @param subaccount The subaccount for the recipient.\n * @param params The parameters for removing liquidity.\n * @return tokenAOut The amount of token A received.\n * @return tokenBOut The amount of token B received.\n */\n function removeLiquidity(\n address recipient,\n uint256 subaccount,\n RemoveLiquidityParams calldata params\n ) external returns (uint256 tokenAOut, uint256 tokenBOut);\n\n /**\n * @notice Migrate bins up the linked list of merged bins so that its\n * mergeId is the currrent active bin.\n * @dev Liquidy can only be removed from a bin that is either unmerged or\n * has a mergeId of an unmerged bin. If a bin is merged more than one\n * level deep, it must be migrated up the merge stack to the root bin\n * before liquidity removal.\n * @param binId The ID of the bin to migrate.\n * @param maxRecursion The maximum recursion depth for the migration.\n */\n function migrateBinUpStack(uint32 binId, uint32 maxRecursion) external;\n\n /**\n * @notice Swap tokenA/tokenB assets in the pool. The swap user has two\n * options for funding their swap.\n * - The user can push the input token amount to the pool before calling\n * the swap function. In order to avoid having the pool call the callback,\n * the user should pass a zero-length `data` bytes object with the swap\n * call.\n * - The user can send the input token amount to the pool when the pool\n * calls the `maverickV2SwapCallback` function on the calling contract.\n * That callback has input parameters that specify the token address of the\n * input token, the input and output amounts, and the bytes data sent to\n * the swap function.\n * @dev If the users elects to do a callback-based swap, the output\n * assets will be sent before the callback is called, allowing the user to\n * execute flash swaps. However, the pool does have reentrancy protection,\n * so a swapper will not be able to interact with the same pool again\n * while they are in the callback function.\n * @param recipient The address to receive the output tokens.\n * @param params Parameters containing the details of the swap\n * @param data Bytes information that gets passed to the callback.\n */\n function swap(\n address recipient,\n SwapParams memory params,\n bytes calldata data\n ) external returns (uint256 amountIn, uint256 amountOut);\n\n /**\n * @notice Loan tokenA/tokenB assets from the pool to recipient. The fee\n * rate of a loan is determined by `lendingFeeRateD18`, which is set at the\n * protocol level by the factory. This function calls\n * `maverickV2FlashLoanCallback` on the calling contract. At the end of\n * the callback, the caller must pay back the loan with fee (if there is a\n * fee).\n * @param recipient The address to receive the loaned tokens.\n * @param amountB Loan amount of tokenA sent to recipient.\n * @param amountB Loan amount of tokenB sent to recipient.\n * @param data Bytes information that gets passed to the callback.\n */\n function flashLoan(\n address recipient,\n uint256 amountA,\n uint256 amountB,\n bytes calldata data\n ) external;\n\n /**\n * @notice Distributes accumulated protocol fee to factory protocolFeeReceiver\n */\n function distributeFees(bool isTokenA)\n external\n returns (uint256 protocolFee, IERC20 token);\n}\n" + }, + "contracts/interfaces/plume/IMaverickV2PoolLens.sol": { + "content": "// SPDX-License-Identifier: GPL-2.0-or-later\npragma solidity ^0.8.25;\n\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport { IMaverickV2Factory } from \"./IMaverickV2Factory.sol\";\nimport { IMaverickV2Pool } from \"./IMaverickV2Pool.sol\";\n\ninterface IMaverickV2PoolLens {\n error LensTargetPriceOutOfBounds(\n uint256 targetSqrtPrice,\n uint256 sqrtLowerTickPrice,\n uint256 sqrtUpperTickPrice\n );\n error LensTooLittleLiquidity(\n uint256 relativeLiquidityAmount,\n uint256 deltaA,\n uint256 deltaB\n );\n error LensTargetingTokenWithNoDelta(\n bool targetIsA,\n uint256 deltaA,\n uint256 deltaB\n );\n\n /**\n * @notice Add liquidity slippage parameters for a distribution of liquidity.\n * @param pool Pool where liquidity is being added.\n * @param kind Bin kind; all bins must have the same kind in a given call\n * to addLiquidity.\n * @param ticks Array of tick values to add liquidity to.\n * @param relativeLiquidityAmounts Relative liquidity amounts for the\n * specified ticks. Liquidity in this case is not bin LP balance, it is\n * the bin liquidity as defined by liquidity = deltaA / (sqrt(upper) -\n * sqrt(lower)) or deltaB = liquidity / sqrt(lower) - liquidity /\n * sqrt(upper).\n * @param addSpec Slippage specification.\n */\n struct AddParamsViewInputs {\n IMaverickV2Pool pool;\n uint8 kind;\n int32[] ticks;\n uint128[] relativeLiquidityAmounts;\n AddParamsSpecification addSpec;\n }\n\n /**\n * @notice Multi-price add param specification.\n * @param slippageFactorD18 Max slippage allowed as a percent in D18 scale. e.g. 1% slippage is 0.01e18\n * @param numberOfPriceBreaksPerSide Number of price break values on either\n * side of current price.\n * @param targetAmount Target token contribution amount in tokenA if\n * targetIsA is true, otherwise this is the target amount for tokenB.\n * @param targetIsA Indicates if the target amount is for tokenA or tokenB\n */\n struct AddParamsSpecification {\n uint256 slippageFactorD18;\n uint256 numberOfPriceBreaksPerSide;\n uint256 targetAmount;\n bool targetIsA;\n }\n\n /**\n * @notice Specification for deriving create pool parameters. Creating a\n * pool in the liquidity manager has several steps:\n *\n * - Deploy pool\n * - Donate a small amount of initial liquidity in the activeTick\n * - Execute a small swap to set the pool price to the desired value\n * - Add liquidity\n *\n * In order to execute these steps, the caller must specify the parameters\n * of each step. The PoolLens has helper function to derive the values\n * used by the LiquidityManager, but this struct is the input to that\n * helper function and represents the core intent of the pool creator.\n *\n * @param fee Fraction of the pool swap amount that is retained as an LP in\n * D18 scale.\n * @param tickSpacing Tick spacing of pool where 1.0001^tickSpacing is the\n * bin width.\n * @param lookback Pool lookback in seconds.\n * @param tokenA Address of tokenA.\n * @param tokenB Address of tokenB.\n * @param activeTick Tick position that contains the active bins.\n * @param kinds 1-15 number to represent the active kinds\n * 0b0001 = static;\n * 0b0010 = right;\n * 0b0100 = left;\n * 0b1000 = both.\n * e.g. a pool with all 4 modes will have kinds = b1111 = 15\n * @param initialTargetB Amount of B to be donated to the pool after pool\n * create. This amount needs to be big enough to meet the minimum bin\n * liquidity.\n * @param sqrtPrice Target sqrt price of the pool.\n * @param kind Bin kind; all bins must have the same kind in a given call\n * to addLiquidity.\n * @param ticks Array of tick values to add liquidity to.\n * @param relativeLiquidityAmounts Relative liquidity amounts for the\n * specified ticks. Liquidity in this case is not bin LP balance, it is\n * the bin liquidity as defined by liquidity = deltaA / (sqrt(upper) -\n * sqrt(lower)) or deltaB = liquidity / sqrt(lower) - liquidity /\n * sqrt(upper).\n * @param targetAmount Target token contribution amount in tokenA if\n * targetIsA is true, otherwise this is the target amount for tokenB.\n * @param targetIsA Indicates if the target amount is for tokenA or tokenB\n */\n struct CreateAndAddParamsViewInputs {\n uint64 feeAIn;\n uint64 feeBIn;\n uint16 tickSpacing;\n uint32 lookback;\n IERC20 tokenA;\n IERC20 tokenB;\n int32 activeTick;\n uint8 kinds;\n // donate params\n uint256 initialTargetB;\n uint256 sqrtPrice;\n // add target\n uint8 kind;\n int32[] ticks;\n uint128[] relativeLiquidityAmounts;\n uint256 targetAmount;\n bool targetIsA;\n }\n\n struct Output {\n uint256 deltaAOut;\n uint256 deltaBOut;\n uint256[] deltaAs;\n uint256[] deltaBs;\n uint128[] deltaLpBalances;\n }\n\n struct Reserves {\n uint256 amountA;\n uint256 amountB;\n }\n\n struct BinPositionKinds {\n uint128[4] values;\n }\n\n struct PoolState {\n IMaverickV2Pool.TickState[] tickStateMapping;\n IMaverickV2Pool.BinState[] binStateMapping;\n BinPositionKinds[] binIdByTickKindMapping;\n IMaverickV2Pool.State state;\n Reserves protocolFees;\n }\n\n struct BoostedPositionSpecification {\n IMaverickV2Pool pool;\n uint32[] binIds;\n uint128[] ratios;\n uint8 kind;\n }\n\n struct CreateAndAddParamsInputs {\n uint64 feeAIn;\n uint64 feeBIn;\n uint16 tickSpacing;\n uint32 lookback;\n IERC20 tokenA;\n IERC20 tokenB;\n int32 activeTick;\n uint8 kinds;\n // donate params\n IMaverickV2Pool.AddLiquidityParams donateParams;\n // swap params\n uint256 swapAmount;\n // add params\n IMaverickV2Pool.AddLiquidityParams addParams;\n bytes[] packedAddParams;\n uint256 deltaAOut;\n uint256 deltaBOut;\n uint256 preAddReserveA;\n uint256 preAddReserveB;\n }\n\n struct TickDeltas {\n uint256 deltaAOut;\n uint256 deltaBOut;\n uint256[] deltaAs;\n uint256[] deltaBs;\n }\n\n /**\n * @notice Converts add parameter slippage specification into add\n * parameters. The return values are given in both raw format and as packed\n * values that can be used in the LiquidityManager contract.\n */\n function getAddLiquidityParams(AddParamsViewInputs memory params)\n external\n view\n returns (\n bytes memory packedSqrtPriceBreaks,\n bytes[] memory packedArgs,\n uint88[] memory sqrtPriceBreaks,\n IMaverickV2Pool.AddLiquidityParams[] memory addParams,\n IMaverickV2PoolLens.TickDeltas[] memory tickDeltas\n );\n\n /**\n * @notice Converts add parameter slippage specification and new pool\n * specification into CreateAndAddParamsInputs parameters that can be used in the\n * LiquidityManager contract.\n */\n function getCreatePoolAtPriceAndAddLiquidityParams(\n CreateAndAddParamsViewInputs memory params\n ) external view returns (CreateAndAddParamsInputs memory output);\n\n /**\n * @notice View function that provides information about pool ticks within\n * a tick radius from the activeTick. Ticks with no reserves are not\n * included in part o f the return array.\n */\n function getTicksAroundActive(IMaverickV2Pool pool, int32 tickRadius)\n external\n view\n returns (\n int32[] memory ticks,\n IMaverickV2Pool.TickState[] memory tickStates\n );\n\n /**\n * @notice View function that provides information about pool ticks within\n * a range. Ticks with no reserves are not included in part o f the return\n * array.\n */\n function getTicks(\n IMaverickV2Pool pool,\n int32 tickStart,\n int32 tickEnd\n )\n external\n view\n returns (\n int32[] memory ticks,\n IMaverickV2Pool.TickState[] memory tickStates\n );\n\n /**\n * @notice View function that provides information about pool ticks within\n * a range. Information returned includes all pool state needed to emulate\n * a swap off chain. Ticks with no reserves are not included in part o f\n * the return array.\n */\n function getTicksAroundActiveWLiquidity(\n IMaverickV2Pool pool,\n int32 tickRadius\n )\n external\n view\n returns (\n int32[] memory ticks,\n IMaverickV2Pool.TickState[] memory tickStates,\n uint256[] memory liquidities,\n uint256[] memory sqrtLowerTickPrices,\n uint256[] memory sqrtUpperTickPrices,\n IMaverickV2Pool.State memory poolState,\n uint256 sqrtPrice,\n uint256 feeAIn,\n uint256 feeBIn\n );\n\n /**\n * @notice View function that provides pool state information.\n */\n function getFullPoolState(\n IMaverickV2Pool pool,\n uint32 binStart,\n uint32 binEnd\n ) external view returns (PoolState memory poolState);\n\n /**\n * @notice View function that provides price and liquidity of a given tick.\n */\n function getTickSqrtPriceAndL(IMaverickV2Pool pool, int32 tick)\n external\n view\n returns (uint256 sqrtPrice, uint256 liquidity);\n\n /**\n * @notice Pool sqrt price.\n */\n function getPoolSqrtPrice(IMaverickV2Pool pool)\n external\n view\n returns (uint256 sqrtPrice);\n\n /**\n * @notice Pool price.\n */\n function getPoolPrice(IMaverickV2Pool pool)\n external\n view\n returns (uint256 price);\n\n /**\n * @notice Token scale of two tokens in a pool.\n */\n function tokenScales(IMaverickV2Pool pool)\n external\n view\n returns (uint256 tokenAScale, uint256 tokenBScale);\n}\n" + }, + "contracts/interfaces/plume/IMaverickV2Position.sol": { + "content": "// SPDX-License-Identifier: GPL-2.0-or-later\npragma solidity ^0.8.25;\n\nimport { IMaverickV2Factory } from \"./IMaverickV2Factory.sol\";\nimport { IMaverickV2Pool } from \"./IMaverickV2Pool.sol\";\nimport { ILiquidityRegistry } from \"./ILiquidityRegistry.sol\";\n\ninterface IMaverickV2Position {\n event PositionClearData(uint256 indexed tokenId);\n event PositionSetData(\n uint256 indexed tokenId,\n uint256 index,\n PositionPoolBinIds newData\n );\n event SetLpReward(ILiquidityRegistry lpReward);\n\n error PositionDuplicatePool(uint256 index, IMaverickV2Pool pool);\n error PositionNotFactoryPool();\n error PositionPermissionedLiquidityPool();\n\n struct PositionPoolBinIds {\n IMaverickV2Pool pool;\n uint32[] binIds;\n }\n\n struct PositionFullInformation {\n PositionPoolBinIds poolBinIds;\n uint256 amountA;\n uint256 amountB;\n uint256[] binAAmounts;\n uint256[] binBAmounts;\n int32[] ticks;\n uint256[] liquidities;\n }\n\n /**\n * @notice Factory that tracks lp rewards.\n */\n function lpReward() external view returns (ILiquidityRegistry);\n\n /**\n * @notice Pool factory.\n */\n function factory() external view returns (IMaverickV2Factory);\n\n /**\n * @notice Mint NFT that holds liquidity in a Maverick V2 Pool. To mint\n * liquidity to an NFT, add liquidity to bins in a pool where the\n * add liquidity recipient is this contract and the subaccount is the\n * tokenId. LiquidityManager can be used to simplify minting Position NFTs.\n */\n function mint(\n address recipient,\n IMaverickV2Pool pool,\n uint32[] memory binIds\n ) external returns (uint256 tokenId);\n\n /**\n * @notice Overwrites tokenId pool/binId information for a given data index.\n */\n function setTokenIdData(\n uint256 tokenId,\n uint256 index,\n IMaverickV2Pool pool,\n uint32[] memory binIds\n ) external;\n\n /**\n * @notice Overwrites entire pool/binId data set for a given tokenId.\n */\n function setTokenIdData(uint256 tokenId, PositionPoolBinIds[] memory data)\n external;\n\n /**\n * @notice Append new pool/binIds data array to tokenId.\n */\n function appendTokenIdData(\n uint256 tokenId,\n IMaverickV2Pool pool,\n uint32[] memory binIds\n ) external;\n\n /**\n * @notice Get array pool/binIds data for a given tokenId.\n */\n function getTokenIdData(uint256 tokenId)\n external\n view\n returns (PositionPoolBinIds[] memory);\n\n /**\n * @notice Get value from array of pool/binIds data for a given tokenId.\n */\n function getTokenIdData(uint256 tokenId, uint256 index)\n external\n view\n returns (PositionPoolBinIds memory);\n\n /**\n * @notice Length of array of pool/binIds data for a given tokenId.\n */\n function tokenIdDataLength(uint256 tokenId)\n external\n view\n returns (uint256 length);\n\n /**\n * @notice Remove liquidity from tokenId for a given pool. User can\n * specify arbitrary bins to remove from for their subaccount in the pool\n * even if those bins are not in the tokenIdData set.\n */\n function removeLiquidity(\n uint256 tokenId,\n address recipient,\n IMaverickV2Pool pool,\n IMaverickV2Pool.RemoveLiquidityParams memory params\n ) external returns (uint256 tokenAAmount, uint256 tokenBAmount);\n\n /**\n * @notice Remove liquidity from tokenId for a given pool to sender. User\n * can specify arbitrary bins to remove from for their subaccount in the\n * pool even if those bins are not in the tokenIdData set.\n */\n function removeLiquidityToSender(\n uint256 tokenId,\n IMaverickV2Pool pool,\n IMaverickV2Pool.RemoveLiquidityParams memory params\n ) external returns (uint256 tokenAAmount, uint256 tokenBAmount);\n\n /**\n * @notice NFT asset information for a given range of pool/binIds indexes.\n * This function only returns the liquidity in the pools/binIds stored as\n * part of the tokenIdData, but it is possible that the NFT has additional\n * liquidity in pools/binIds that have not been recorded.\n */\n function tokenIdPositionInformation(\n uint256 tokenId,\n uint256 startIndex,\n uint256 stopIndex\n ) external view returns (PositionFullInformation[] memory output);\n\n /**\n * @notice NFT asset information for a given pool/binIds index. This\n * function only returns the liquidity in the pools/binIds stored as part\n * of the tokenIdData, but it is possible that the NFT has additional\n * liquidity in pools/binIds that have not been recorded.\n */\n function tokenIdPositionInformation(uint256 tokenId, uint256 index)\n external\n view\n returns (PositionFullInformation memory output);\n\n /**\n * @notice Get remove parameters for removing a fractional part of the\n * liquidity owned by a given tokenId. The fractional factor to remove is\n * given by proporationD18 in 18-decimal scale.\n */\n function getRemoveParams(\n uint256 tokenId,\n uint256 index,\n uint256 proportionD18\n )\n external\n view\n returns (IMaverickV2Pool.RemoveLiquidityParams memory params);\n\n /**\n * @notice Register the bin balances in the nft with the LpReward contract.\n */\n function checkpointBinLpBalance(\n uint256 tokenId,\n IMaverickV2Pool pool,\n uint32[] memory binIds\n ) external;\n}\n" + }, + "contracts/interfaces/plume/IMaverickV2Quoter.sol": { + "content": "// SPDX-License-Identifier: GPL-2.0-or-later\npragma solidity ^0.8.25;\n\nimport { IMaverickV2Pool } from \"./IMaverickV2Pool.sol\";\n\ninterface IMaverickV2Quoter {\n error QuoterInvalidSwap();\n error QuoterInvalidAddLiquidity();\n\n /**\n * @notice Calculates a swap on a MaverickV2Pool and returns the resulting\n * amount and estimated gas. The gas estimate is only a rough estimate and\n * may not match a swap's gas.\n * @param pool The MaverickV2Pool to swap on.\n * @param amount The input amount.\n * @param tokenAIn Indicates if token A is the input token.\n * @param exactOutput Indicates if the amount is the output amount (true)\n * or input amount (false). If the tickLimit is reached, the full value of\n * the exactOutput may not be returned because the pool will stop swapping\n * before the whole order is filled.\n * @param tickLimit The tick limit for the swap. Once the swap lands in\n * this tick, it will stop and return the output amount swapped up to that\n * tick.\n */\n function calculateSwap(\n IMaverickV2Pool pool,\n uint128 amount,\n bool tokenAIn,\n bool exactOutput,\n int32 tickLimit\n )\n external\n returns (\n uint256 amountIn,\n uint256 amountOut,\n uint256 gasEstimate\n );\n\n /**\n * @notice Calculates a multihop swap and returns the resulting amount and\n * estimated gas. The gas estimate is only a rough estimate and\n * may not match a swap's gas.\n * @param path The path of pools to swap through. Path is given by an\n * packed array of (pool, tokenAIn) tuples. So each step in the path is 160\n * + 8 = 168 bits of data. e.g. path = abi.encodePacked(pool1, true, pool2, false);\n * @param amount The input amount.\n * @param exactOutput A boolean indicating if exact output is required.\n */\n function calculateMultiHopSwap(\n bytes memory path,\n uint256 amount,\n bool exactOutput\n ) external returns (uint256 returnAmount, uint256 gasEstimate);\n\n /**\n * @notice Computes the token amounts required for a given set of\n * addLiquidity parameters. The gas estimate is only a rough estimate and\n * may not match a add's gas.\n */\n function calculateAddLiquidity(\n IMaverickV2Pool pool,\n IMaverickV2Pool.AddLiquidityParams calldata params\n )\n external\n returns (\n uint256 amountA,\n uint256 amountB,\n uint256 gasEstimate\n );\n\n /**\n * @notice Pool's sqrt price.\n */\n function poolSqrtPrice(IMaverickV2Pool pool)\n external\n view\n returns (uint256 sqrtPrice);\n}\n" + }, + "contracts/interfaces/plume/IPoolDistributor.sol": { + "content": "// SPDX-License-Identifier: GPL-2.0-or-later\npragma solidity ^0.8.0;\n\nimport { IMaverickV2Pool } from \"./IMaverickV2Pool.sol\";\n\ninterface IPoolDistributor {\n function rewardToken() external view returns (address);\n\n function claimLp(\n address recipient,\n uint256 tokenId,\n IMaverickV2Pool pool,\n uint32[] memory binIds,\n uint256 epoch\n ) external returns (uint256 amount);\n}\n" + }, + "contracts/interfaces/plume/IVotingDistributor.sol": { + "content": "// SPDX-License-Identifier: GPL-2.0-or-later\npragma solidity ^0.8.0;\n\ninterface IVotingDistributor {\n function lastEpoch() external view returns (uint256);\n}\n" + }, + "contracts/interfaces/poolBooster/IMerklDistributor.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ninterface IMerklDistributor {\n struct CampaignParameters {\n // POPULATED ONCE CREATED\n\n // ID of the campaign. This can be left as a null bytes32 when creating campaigns\n // on Merkl.\n bytes32 campaignId;\n // CHOSEN BY CAMPAIGN CREATOR\n\n // Address of the campaign creator, if marked as address(0), it will be overriden with the\n // address of the `msg.sender` creating the campaign\n address creator;\n // Address of the token used as a reward\n address rewardToken;\n // Amount of `rewardToken` to distribute across all the epochs\n // Amount distributed per epoch is `amount/numEpoch`\n uint256 amount;\n // Type of campaign\n uint32 campaignType;\n // Timestamp at which the campaign should start\n uint32 startTimestamp;\n // Duration of the campaign in seconds. Has to be a multiple of EPOCH = 3600\n uint32 duration;\n // Extra data to pass to specify the campaign\n bytes campaignData;\n }\n\n function createCampaign(CampaignParameters memory newCampaign)\n external\n returns (bytes32);\n\n function signAndCreateCampaign(\n CampaignParameters memory newCampaign,\n bytes memory _signature\n ) external returns (bytes32);\n\n function sign(bytes memory _signature) external;\n\n function rewardTokenMinAmounts(address _rewardToken)\n external\n view\n returns (uint256);\n}\n" + }, + "contracts/interfaces/poolBooster/IPoolBoostCentralRegistry.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\ninterface IPoolBoostCentralRegistry {\n /**\n * @dev all the supported pool booster types are listed here. It is possible\n * to have multiple versions of the factory that supports the same type of\n * pool booster. Factories are immutable and this can happen when a factory\n * or related pool booster required code update.\n * e.g. \"PoolBoosterSwapxDouble\" & \"PoolBoosterSwapxDouble_v2\"\n */\n enum PoolBoosterType {\n // Supports bribing 2 contracts per pool. Appropriate for Ichi vault concentrated\n // liquidity pools where (which is expected in most/all cases) both pool gauges\n // require bribing.\n SwapXDoubleBooster,\n // Supports bribing a single contract per pool. Appropriate for Classic Stable &\n // Classic Volatile pools and Ichi vaults where only 1 side (1 of the 2 gauges)\n // needs bribing\n SwapXSingleBooster,\n // Supports bribing a single contract per pool. Appropriate for Metropolis pools\n MetropolisBooster,\n // Supports creating a Merkl campaign.\n MerklBooster,\n // Supports creating a plain Curve pool booster\n CurvePoolBoosterPlain\n }\n\n struct PoolBoosterEntry {\n address boosterAddress;\n address ammPoolAddress;\n PoolBoosterType boosterType;\n }\n\n event PoolBoosterCreated(\n address poolBoosterAddress,\n address ammPoolAddress,\n PoolBoosterType poolBoosterType,\n address factoryAddress\n );\n event PoolBoosterRemoved(address poolBoosterAddress);\n\n function emitPoolBoosterCreated(\n address _poolBoosterAddress,\n address _ammPoolAddress,\n PoolBoosterType _boosterType\n ) external;\n\n function emitPoolBoosterRemoved(address _poolBoosterAddress) external;\n}\n" + }, + "contracts/interfaces/poolBooster/IPoolBooster.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\ninterface IPoolBooster {\n event BribeExecuted(uint256 amount);\n\n /// @notice Execute the bribe action\n function bribe() external;\n}\n" + }, + "contracts/interfaces/poolBooster/ISwapXAlgebraBribe.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ninterface IBribe {\n /// @notice Notify a bribe amount\n /// @dev Rewards are saved into NEXT EPOCH mapping.\n function notifyRewardAmount(address _rewardsToken, uint256 reward) external;\n}\n" + }, + "contracts/interfaces/sonic/ISFC.sol": { + "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.8.0;\n\n/**\n * @title Special Fee Contract for Sonic network\n * @notice The SFC maintains a list of validators and delegators and distributes rewards to them.\n * @custom:security-contact security@fantom.foundation\n */\ninterface ISFC {\n error StakeIsFullySlashed();\n\n event CreatedValidator(\n uint256 indexed validatorID,\n address indexed auth,\n uint256 createdEpoch,\n uint256 createdTime\n );\n event Delegated(\n address indexed delegator,\n uint256 indexed validatorID,\n uint256 amount\n );\n event Undelegated(\n address indexed delegator,\n uint256 indexed validatorID,\n uint256 indexed wrID,\n uint256 amount\n );\n event Withdrawn(\n address indexed delegator,\n uint256 indexed validatorID,\n uint256 indexed wrID,\n uint256 amount,\n uint256 penalty\n );\n event ClaimedRewards(\n address indexed delegator,\n uint256 indexed validatorID,\n uint256 rewards\n );\n event RestakedRewards(\n address indexed delegator,\n uint256 indexed validatorID,\n uint256 rewards\n );\n event BurntFTM(uint256 amount);\n event UpdatedSlashingRefundRatio(\n uint256 indexed validatorID,\n uint256 refundRatio\n );\n event RefundedSlashedLegacyDelegation(\n address indexed delegator,\n uint256 indexed validatorID,\n uint256 amount\n );\n\n event DeactivatedValidator(\n uint256 indexed validatorID,\n uint256 deactivatedEpoch,\n uint256 deactivatedTime\n );\n event ChangedValidatorStatus(uint256 indexed validatorID, uint256 status);\n event AnnouncedRedirection(address indexed from, address indexed to);\n\n function currentSealedEpoch() external view returns (uint256);\n\n function getEpochSnapshot(uint256 epoch)\n external\n view\n returns (\n uint256 endTime,\n uint256 endBlock,\n uint256 epochFee,\n uint256 baseRewardPerSecond,\n uint256 totalStake,\n uint256 totalSupply\n );\n\n function getStake(address delegator, uint256 validatorID)\n external\n view\n returns (uint256);\n\n function getValidator(uint256 validatorID)\n external\n view\n returns (\n uint256 status,\n uint256 receivedStake,\n address auth,\n uint256 createdEpoch,\n uint256 createdTime,\n uint256 deactivatedTime,\n uint256 deactivatedEpoch\n );\n\n function getValidatorID(address auth) external view returns (uint256);\n\n function getValidatorPubkey(uint256 validatorID)\n external\n view\n returns (bytes memory);\n\n function pubkeyAddressvalidatorID(address pubkeyAddress)\n external\n view\n returns (uint256);\n\n function getWithdrawalRequest(\n address delegator,\n uint256 validatorID,\n uint256 wrID\n )\n external\n view\n returns (\n uint256 epoch,\n uint256 time,\n uint256 amount\n );\n\n function isOwner() external view returns (bool);\n\n function lastValidatorID() external view returns (uint256);\n\n function minGasPrice() external view returns (uint256);\n\n function owner() external view returns (address);\n\n function renounceOwnership() external;\n\n function slashingRefundRatio(uint256 validatorID)\n external\n view\n returns (uint256);\n\n function stashedRewardsUntilEpoch(address delegator, uint256 validatorID)\n external\n view\n returns (uint256);\n\n function totalActiveStake() external view returns (uint256);\n\n function totalStake() external view returns (uint256);\n\n function totalSupply() external view returns (uint256);\n\n function transferOwnership(address newOwner) external;\n\n function treasuryAddress() external view returns (address);\n\n function version() external pure returns (bytes3);\n\n function currentEpoch() external view returns (uint256);\n\n function updateConstsAddress(address v) external;\n\n function constsAddress() external view returns (address);\n\n function getEpochValidatorIDs(uint256 epoch)\n external\n view\n returns (uint256[] memory);\n\n function getEpochReceivedStake(uint256 epoch, uint256 validatorID)\n external\n view\n returns (uint256);\n\n function getEpochAccumulatedRewardPerToken(\n uint256 epoch,\n uint256 validatorID\n ) external view returns (uint256);\n\n function getEpochAccumulatedUptime(uint256 epoch, uint256 validatorID)\n external\n view\n returns (uint256);\n\n function getEpochAverageUptime(uint256 epoch, uint256 validatorID)\n external\n view\n returns (uint32);\n\n function getEpochAccumulatedOriginatedTxsFee(\n uint256 epoch,\n uint256 validatorID\n ) external view returns (uint256);\n\n function getEpochOfflineTime(uint256 epoch, uint256 validatorID)\n external\n view\n returns (uint256);\n\n function getEpochOfflineBlocks(uint256 epoch, uint256 validatorID)\n external\n view\n returns (uint256);\n\n function getEpochEndBlock(uint256 epoch) external view returns (uint256);\n\n function rewardsStash(address delegator, uint256 validatorID)\n external\n view\n returns (uint256);\n\n function createValidator(bytes calldata pubkey) external payable;\n\n function getSelfStake(uint256 validatorID) external view returns (uint256);\n\n function delegate(uint256 validatorID) external payable;\n\n function undelegate(\n uint256 validatorID,\n uint256 wrID,\n uint256 amount\n ) external;\n\n function isSlashed(uint256 validatorID) external view returns (bool);\n\n function withdraw(uint256 validatorID, uint256 wrID) external;\n\n function deactivateValidator(uint256 validatorID, uint256 status) external;\n\n function pendingRewards(address delegator, uint256 validatorID)\n external\n view\n returns (uint256);\n\n function stashRewards(address delegator, uint256 validatorID) external;\n\n function claimRewards(uint256 validatorID) external;\n\n function restakeRewards(uint256 validatorID) external;\n\n function updateSlashingRefundRatio(uint256 validatorID, uint256 refundRatio)\n external;\n\n function updateTreasuryAddress(address v) external;\n\n function burnFTM(uint256 amount) external;\n\n function sealEpoch(\n uint256[] calldata offlineTime,\n uint256[] calldata offlineBlocks,\n uint256[] calldata uptimes,\n uint256[] calldata originatedTxsFee\n ) external;\n\n function sealEpochValidators(uint256[] calldata nextValidatorIDs) external;\n\n function initialize(\n uint256 sealedEpoch,\n uint256 _totalSupply,\n address nodeDriver,\n address consts,\n address _owner\n ) external;\n\n function setGenesisValidator(\n address auth,\n uint256 validatorID,\n bytes calldata pubkey,\n uint256 createdTime\n ) external;\n\n function setGenesisDelegation(\n address delegator,\n uint256 validatorID,\n uint256 stake\n ) external;\n\n function updateStakeSubscriberAddress(address v) external;\n\n function stakeSubscriberAddress() external view returns (address);\n\n function setRedirectionAuthorizer(address v) external;\n\n function announceRedirection(address to) external;\n\n function initiateRedirection(address from, address to) external;\n\n function redirect(address to) external;\n}\n" + }, + "contracts/interfaces/sonic/ISwapXGauge.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ninterface IGauge {\n function owner() external view returns (address);\n\n function TOKEN() external view returns (address);\n\n function DISTRIBUTION() external view returns (address);\n\n function balanceOf(address account) external view returns (uint256);\n\n function claimFees() external returns (uint256 claimed0, uint256 claimed1);\n\n function deposit(uint256 amount) external;\n\n function depositAll() external;\n\n function earned(address account) external view returns (uint256);\n\n function getReward() external;\n\n function getReward(address _user) external;\n\n function isForPair() external view returns (bool);\n\n function lastTimeRewardApplicable() external view returns (uint256);\n\n function lastUpdateTime() external view returns (uint256);\n\n function notifyRewardAmount(address token, uint256 reward) external;\n\n function periodFinish() external view returns (uint256);\n\n function rewardForDuration() external view returns (uint256);\n\n function rewardPerToken() external view returns (uint256);\n\n function rewardPerTokenStored() external view returns (uint256);\n\n function rewardRate() external view returns (uint256);\n\n function rewardToken() external view returns (address);\n\n function rewards(address) external view returns (uint256);\n\n function totalSupply() external view returns (uint256);\n\n function userRewardPerTokenPaid(address) external view returns (uint256);\n\n function withdraw(uint256 amount) external;\n\n function withdrawAll() external;\n\n function withdrawAllAndHarvest() external;\n\n function withdrawExcess(address token, uint256 amount) external;\n\n function emergency() external returns (bool);\n\n function emergencyWithdraw() external;\n\n function activateEmergencyMode() external;\n\n function stopEmergencyMode() external;\n}\n" + }, + "contracts/interfaces/sonic/ISwapXPair.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ninterface IPair {\n event Approval(address indexed src, address indexed guy, uint256 wad);\n event Transfer(address indexed src, address indexed dst, uint256 wad);\n\n event Mint(address indexed sender, uint256 amount0, uint256 amount1);\n event Burn(\n address indexed sender,\n uint256 amount0,\n uint256 amount1,\n address indexed to\n );\n event Swap(\n address indexed sender,\n uint256 amount0In,\n uint256 amount1In,\n uint256 amount0Out,\n uint256 amount1Out,\n address indexed to\n );\n event Claim(\n address indexed sender,\n address indexed recipient,\n uint256 amount0,\n uint256 amount1\n );\n\n function metadata()\n external\n view\n returns (\n uint256 dec0,\n uint256 dec1,\n uint256 r0,\n uint256 r1,\n bool st,\n address t0,\n address t1\n );\n\n function claimFees() external returns (uint256, uint256);\n\n function tokens() external view returns (address, address);\n\n function token0() external view returns (address);\n\n function token1() external view returns (address);\n\n function permit(\n address owner,\n address spender,\n uint256 value,\n uint256 deadline,\n uint8 v,\n bytes32 r,\n bytes32 s\n ) external;\n\n function swap(\n uint256 amount0Out,\n uint256 amount1Out,\n address to,\n bytes calldata data\n ) external;\n\n function burn(address to)\n external\n returns (uint256 amount0, uint256 amount1);\n\n function mint(address to) external returns (uint256 liquidity);\n\n function getReserves()\n external\n view\n returns (\n uint256 _reserve0,\n uint256 _reserve1,\n uint256 _blockTimestampLast\n );\n\n function getAmountOut(uint256, address) external view returns (uint256);\n\n // ERC20 methods\n function name() external view returns (string memory);\n\n function symbol() external view returns (string memory);\n\n function decimals() external view returns (uint8);\n\n function totalSupply() external view returns (uint256);\n\n function balanceOf(address) external view returns (uint256);\n\n function transfer(address recipient, uint256 amount)\n external\n returns (bool);\n\n function transferFrom(\n address sender,\n address recipient,\n uint256 amount\n ) external returns (bool);\n\n function allowance(address owner, address spender)\n external\n view\n returns (uint256);\n\n function approve(address spender, uint256 value) external returns (bool);\n\n function claimable0(address _user) external view returns (uint256);\n\n function claimable1(address _user) external view returns (uint256);\n\n function isStable() external view returns (bool);\n\n function skim(address to) external;\n\n function sync() external;\n}\n" + }, + "contracts/interfaces/sonic/IWrappedSonic.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ninterface IWrappedSonic {\n event Deposit(address indexed account, uint256 value);\n event Withdrawal(address indexed account, uint256 value);\n event Transfer(address indexed from, address indexed to, uint256 value);\n event Approval(\n address indexed owner,\n address indexed spender,\n uint256 value\n );\n\n function allowance(address owner, address spender)\n external\n view\n returns (uint256);\n\n function approve(address spender, uint256 value) external returns (bool);\n\n function balanceOf(address account) external view returns (uint256);\n\n function decimals() external view returns (uint8);\n\n function deposit() external payable;\n\n function depositFor(address account) external payable returns (bool);\n\n function totalSupply() external view returns (uint256);\n\n function transfer(address to, uint256 value) external returns (bool);\n\n function transferFrom(\n address from,\n address to,\n uint256 value\n ) external returns (bool);\n\n function withdraw(uint256 value) external;\n\n function withdrawTo(address account, uint256 value) external returns (bool);\n}\n" + }, + "contracts/interfaces/uniswap/IUniswapV2Router02.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ninterface IUniswapV2Router {\n function WETH() external pure returns (address);\n\n function swapExactTokensForTokens(\n uint256 amountIn,\n uint256 amountOutMin,\n address[] calldata path,\n address to,\n uint256 deadline\n ) external returns (uint256[] memory amounts);\n\n function addLiquidity(\n address tokenA,\n address tokenB,\n uint256 amountADesired,\n uint256 amountBDesired,\n uint256 amountAMin,\n uint256 amountBMin,\n address to,\n uint256 deadline\n )\n external\n returns (\n uint256 amountA,\n uint256 amountB,\n uint256 liquidity\n );\n}\n" + }, + "contracts/interfaces/uniswap/IUniswapV3Router.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\n// -- Solididy v0.5.x compatible interface\ninterface IUniswapV3Router {\n struct ExactInputParams {\n bytes path;\n address recipient;\n uint256 deadline;\n uint256 amountIn;\n uint256 amountOutMinimum;\n }\n\n /// @notice Swaps `amountIn` of one token for as much as possible of another along the specified path\n /// @param params The parameters necessary for the multi-hop swap, encoded as `ExactInputParams` in calldata\n /// @return amountOut The amount of the received token\n function exactInput(ExactInputParams calldata params)\n external\n payable\n returns (uint256 amountOut);\n}\n" + }, + "contracts/mocks/crosschain/CCTPMessageTransmitterMock.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { ICCTPMessageTransmitter } from \"../../interfaces/cctp/ICCTP.sol\";\nimport { IERC20 } from \"../../utils/InitializableAbstractStrategy.sol\";\nimport { BytesHelper } from \"../../utils/BytesHelper.sol\";\nimport { AbstractCCTPIntegrator } from \"../../strategies/crosschain/AbstractCCTPIntegrator.sol\";\n\n/**\n * @title Mock conctract simulating the functionality of the CCTPTokenMessenger contract\n * for the porposes of unit testing.\n * @author Origin Protocol Inc\n */\n\ncontract CCTPMessageTransmitterMock is ICCTPMessageTransmitter {\n using BytesHelper for bytes;\n\n IERC20 public usdc;\n uint256 public nonce = 0;\n // Sender index in the burn message v2\n // Ref: https://github.com/circlefin/evm-cctp-contracts/blob/master/src/messages/v2/BurnMessageV2.sol\n uint8 constant BURN_MESSAGE_V2_MESSAGE_SENDER_INDEX = 100;\n uint8 constant BURN_MESSAGE_V2_HOOK_DATA_INDEX = 228;\n\n uint16 public messageFinality = 2000;\n address public messageSender;\n uint32 public sourceDomain = 4294967295; // 0xFFFFFFFF\n\n // Full message with header\n struct Message {\n uint32 version;\n uint32 sourceDomain;\n uint32 destinationDomain;\n bytes32 recipient;\n bytes32 messageHeaderRecipient;\n bytes32 sender;\n bytes32 destinationCaller;\n uint32 minFinalityThreshold;\n bool isTokenTransfer;\n uint256 tokenAmount;\n bytes messageBody;\n }\n\n Message[] public messages;\n // map of encoded messages to the corresponding message structs\n mapping(bytes32 => Message) public encodedMessages;\n\n constructor(address _usdc) {\n usdc = IERC20(_usdc);\n }\n\n // @dev for the porposes of unit tests queues the message to be mock-sent using\n // the cctp bridge.\n function sendMessage(\n uint32 destinationDomain,\n bytes32 recipient,\n bytes32 destinationCaller,\n uint32 minFinalityThreshold,\n bytes memory messageBody\n ) external virtual override {\n bytes32 nonceHash = keccak256(abi.encodePacked(nonce));\n nonce++;\n\n // If destination is mainnet, source is base and vice versa\n uint32 sourceDomain = destinationDomain == 0 ? 6 : 0;\n\n Message memory message = Message({\n version: 1,\n sourceDomain: sourceDomain,\n destinationDomain: destinationDomain,\n recipient: recipient,\n messageHeaderRecipient: recipient,\n sender: bytes32(uint256(uint160(msg.sender))),\n destinationCaller: destinationCaller,\n minFinalityThreshold: minFinalityThreshold,\n isTokenTransfer: false,\n tokenAmount: 0,\n messageBody: messageBody\n });\n\n messages.push(message);\n }\n\n // @dev for the porposes of unit tests queues the USDC burn/mint to be executed\n // using the cctp bridge.\n function sendTokenTransferMessage(\n uint32 destinationDomain,\n bytes32 recipient,\n bytes32 destinationCaller,\n uint32 minFinalityThreshold,\n uint256 tokenAmount,\n bytes memory messageBody\n ) external {\n bytes32 nonceHash = keccak256(abi.encodePacked(nonce));\n nonce++;\n\n // If destination is mainnet, source is base and vice versa\n uint32 sourceDomain = destinationDomain == 0 ? 6 : 0;\n\n Message memory message = Message({\n version: 1,\n sourceDomain: sourceDomain,\n destinationDomain: destinationDomain,\n recipient: recipient,\n messageHeaderRecipient: recipient,\n sender: bytes32(uint256(uint160(msg.sender))),\n destinationCaller: destinationCaller,\n minFinalityThreshold: minFinalityThreshold,\n isTokenTransfer: true,\n tokenAmount: tokenAmount,\n messageBody: messageBody\n });\n\n messages.push(message);\n }\n\n function receiveMessage(bytes memory message, bytes memory attestation)\n public\n virtual\n override\n returns (bool)\n {\n Message memory storedMsg = encodedMessages[keccak256(message)];\n AbstractCCTPIntegrator recipient = AbstractCCTPIntegrator(\n address(uint160(uint256(storedMsg.recipient)))\n );\n\n bytes32 sender = storedMsg.sender;\n bytes memory messageBody = storedMsg.messageBody;\n\n // Credit USDC in this step as it is done in the live cctp contracts\n if (storedMsg.isTokenTransfer) {\n usdc.transfer(address(recipient), storedMsg.tokenAmount);\n // override the sender with the one stored in the Burn message as the sender int he\n // message header is the TokenMessenger.\n sender = bytes32(\n uint256(\n uint160(\n storedMsg.messageBody.extractAddress(\n BURN_MESSAGE_V2_MESSAGE_SENDER_INDEX\n )\n )\n )\n );\n messageBody = storedMsg.messageBody.extractSlice(\n BURN_MESSAGE_V2_HOOK_DATA_INDEX,\n storedMsg.messageBody.length\n );\n } else {\n bytes32 overrideSenderBytes = bytes32(\n uint256(uint160(messageSender))\n );\n if (messageFinality >= 2000) {\n recipient.handleReceiveFinalizedMessage(\n sourceDomain == 4294967295\n ? storedMsg.sourceDomain\n : sourceDomain,\n messageSender == address(0) ? sender : overrideSenderBytes,\n messageFinality,\n messageBody\n );\n } else {\n recipient.handleReceiveUnfinalizedMessage(\n sourceDomain == 4294967295\n ? storedMsg.sourceDomain\n : sourceDomain,\n messageSender == address(0) ? sender : overrideSenderBytes,\n messageFinality,\n messageBody\n );\n }\n }\n\n return true;\n }\n\n function addMessage(Message memory storedMsg) external {\n messages.push(storedMsg);\n }\n\n function _encodeMessageHeader(\n uint32 version,\n uint32 sourceDomain,\n bytes32 sender,\n bytes32 recipient,\n bytes memory messageBody\n ) internal pure returns (bytes memory) {\n bytes memory header = abi.encodePacked(\n version, // 0-3\n sourceDomain, // 4-7\n bytes32(0), // 8-39 destinationDomain\n bytes4(0), // 40-43 nonce\n sender, // 44-75 sender\n recipient, // 76-107 recipient\n bytes32(0), // other stuff\n bytes8(0) // other stuff\n );\n return abi.encodePacked(header, messageBody);\n }\n\n function _removeFront() internal returns (Message memory) {\n require(messages.length > 0, \"No messages\");\n Message memory removed = messages[0];\n // Shift array\n for (uint256 i = 0; i < messages.length - 1; i++) {\n messages[i] = messages[i + 1];\n }\n messages.pop();\n return removed;\n }\n\n function _processMessage(Message memory storedMsg) internal {\n bytes memory encodedMessage = _encodeMessageHeader(\n storedMsg.version,\n storedMsg.sourceDomain,\n storedMsg.sender,\n storedMsg.messageHeaderRecipient,\n storedMsg.messageBody\n );\n\n encodedMessages[keccak256(encodedMessage)] = storedMsg;\n\n address recipient = address(uint160(uint256(storedMsg.recipient)));\n\n AbstractCCTPIntegrator(recipient).relay(encodedMessage, bytes(\"\"));\n }\n\n function _removeBack() internal returns (Message memory) {\n require(messages.length > 0, \"No messages\");\n Message memory last = messages[messages.length - 1];\n messages.pop();\n return last;\n }\n\n function messagesInQueue() external view returns (uint256) {\n return messages.length;\n }\n\n function processFrontOverrideHeader(bytes4 customHeader) external {\n Message memory storedMsg = _removeFront();\n\n bytes memory modifiedBody = abi.encodePacked(\n customHeader,\n storedMsg.messageBody.extractSlice(4, storedMsg.messageBody.length)\n );\n\n storedMsg.messageBody = modifiedBody;\n\n _processMessage(storedMsg);\n }\n\n function processFrontOverrideVersion(uint32 customVersion) external {\n Message memory storedMsg = _removeFront();\n storedMsg.version = customVersion;\n _processMessage(storedMsg);\n }\n\n function processFrontOverrideSender(address customSender) external {\n Message memory storedMsg = _removeFront();\n storedMsg.sender = bytes32(uint256(uint160(customSender)));\n _processMessage(storedMsg);\n }\n\n function processFrontOverrideRecipient(address customRecipient) external {\n Message memory storedMsg = _removeFront();\n storedMsg.messageHeaderRecipient = bytes32(\n uint256(uint160(customRecipient))\n );\n _processMessage(storedMsg);\n }\n\n function processFrontOverrideMessageBody(bytes memory customMessageBody)\n external\n {\n Message memory storedMsg = _removeFront();\n storedMsg.messageBody = customMessageBody;\n _processMessage(storedMsg);\n }\n\n function processFront() external {\n Message memory storedMsg = _removeFront();\n _processMessage(storedMsg);\n }\n\n function processBack() external {\n Message memory storedMsg = _removeBack();\n _processMessage(storedMsg);\n }\n\n function getMessagesLength() external view returns (uint256) {\n return messages.length;\n }\n\n function overrideMessageFinality(uint16 _finality) external {\n messageFinality = _finality;\n }\n\n function overrideSender(address _sender) external {\n messageSender = _sender;\n }\n\n function overrideSourceDomain(uint32 _sourceDomain) external {\n sourceDomain = _sourceDomain;\n }\n}\n" + }, + "contracts/mocks/crosschain/CCTPMessageTransmitterMock2.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { IMessageHandlerV2 } from \"../../interfaces/cctp/ICCTP.sol\";\nimport { BytesHelper } from \"../../utils/BytesHelper.sol\";\nimport { CCTPMessageTransmitterMock } from \"./CCTPMessageTransmitterMock.sol\";\n\nuint8 constant SOURCE_DOMAIN_INDEX = 4;\nuint8 constant RECIPIENT_INDEX = 76;\nuint8 constant SENDER_INDEX = 44;\nuint8 constant MESSAGE_BODY_INDEX = 148;\n\n/**\n * @title Mock conctract simulating the functionality of the CCTPTokenMessenger contract\n * for the porposes of unit testing.\n * @author Origin Protocol Inc\n */\n\ncontract CCTPMessageTransmitterMock2 is CCTPMessageTransmitterMock {\n using BytesHelper for bytes;\n\n address public cctpTokenMessenger;\n\n event MessageReceivedInMockTransmitter(bytes message);\n event MessageSent(bytes message);\n\n constructor(address _usdc) CCTPMessageTransmitterMock(_usdc) {}\n\n function setCCTPTokenMessenger(address _cctpTokenMessenger) external {\n cctpTokenMessenger = _cctpTokenMessenger;\n }\n\n function sendMessage(\n uint32 destinationDomain,\n bytes32 recipient,\n bytes32 destinationCaller,\n uint32 minFinalityThreshold,\n bytes memory messageBody\n ) external virtual override {\n bytes memory message = abi.encodePacked(\n uint32(1), // version\n uint32(destinationDomain == 0 ? 6 : 0), // source domain\n uint32(destinationDomain), // destination domain\n uint256(0),\n bytes32(uint256(uint160(msg.sender))), // sender\n recipient, // recipient\n destinationCaller, // destination caller\n minFinalityThreshold, // min finality threshold\n uint32(0),\n messageBody // message body\n );\n emit MessageSent(message);\n }\n\n function receiveMessage(bytes memory message, bytes memory attestation)\n public\n virtual\n override\n returns (bool)\n {\n uint32 sourceDomain = message.extractUint32(SOURCE_DOMAIN_INDEX);\n address recipient = message.extractAddress(RECIPIENT_INDEX);\n address sender = message.extractAddress(SENDER_INDEX);\n\n bytes memory messageBody = message.extractSlice(\n MESSAGE_BODY_INDEX,\n message.length\n );\n\n bool isBurnMessage = recipient == cctpTokenMessenger;\n\n if (isBurnMessage) {\n // recipient = messageBody.extractAddress(BURN_MESSAGE_V2_RECIPIENT_INDEX);\n // This step won't mint USDC, transfer it to the recipient address\n // in your tests\n } else {\n IMessageHandlerV2(recipient).handleReceiveFinalizedMessage(\n sourceDomain,\n bytes32(uint256(uint160(sender))),\n 2000,\n messageBody\n );\n }\n\n // This step won't mint USDC, transfer it to the recipient address\n // in your tests\n emit MessageReceivedInMockTransmitter(message);\n\n return true;\n }\n}\n" + }, + "contracts/mocks/crosschain/CCTPTokenMessengerMock.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { ICCTPTokenMessenger } from \"../../interfaces/cctp/ICCTP.sol\";\nimport { CCTPMessageTransmitterMock } from \"./CCTPMessageTransmitterMock.sol\";\nimport { IERC20 } from \"../../utils/InitializableAbstractStrategy.sol\";\n\n/**\n * @title Mock conctract simulating the functionality of the CCTPTokenMessenger contract\n * for the porposes of unit testing.\n * @author Origin Protocol Inc\n */\n\ncontract CCTPTokenMessengerMock is ICCTPTokenMessenger {\n IERC20 public usdc;\n CCTPMessageTransmitterMock public cctpMessageTransmitterMock;\n\n constructor(address _usdc, address _cctpMessageTransmitterMock) {\n usdc = IERC20(_usdc);\n cctpMessageTransmitterMock = CCTPMessageTransmitterMock(\n _cctpMessageTransmitterMock\n );\n }\n\n function depositForBurn(\n uint256 amount,\n uint32 destinationDomain,\n bytes32 mintRecipient,\n address burnToken,\n bytes32 destinationCaller,\n uint256 maxFee,\n uint32 minFinalityThreshold\n ) external override {\n revert(\"Not implemented\");\n }\n\n /**\n * @dev mocks the depositForBurnWithHook function by sending the USDC to the CCTPMessageTransmitterMock\n * called by the AbstractCCTPIntegrator contract.\n */\n function depositForBurnWithHook(\n uint256 amount,\n uint32 destinationDomain,\n bytes32 mintRecipient,\n address burnToken,\n bytes32 destinationCaller,\n uint256 maxFee,\n uint32 minFinalityThreshold,\n bytes memory hookData\n ) external override {\n require(burnToken == address(usdc), \"Invalid burn token\");\n\n usdc.transferFrom(msg.sender, address(this), maxFee);\n uint256 destinationAmount = amount - maxFee;\n usdc.transferFrom(\n msg.sender,\n address(cctpMessageTransmitterMock),\n destinationAmount\n );\n\n bytes memory burnMessage = _encodeBurnMessageV2(\n mintRecipient,\n amount,\n msg.sender,\n maxFee,\n maxFee,\n hookData\n );\n\n cctpMessageTransmitterMock.sendTokenTransferMessage(\n destinationDomain,\n mintRecipient,\n destinationCaller,\n minFinalityThreshold,\n destinationAmount,\n burnMessage\n );\n }\n\n function _encodeBurnMessageV2(\n bytes32 mintRecipient,\n uint256 amount,\n address messageSender,\n uint256 maxFee,\n uint256 feeExecuted,\n bytes memory hookData\n ) internal view returns (bytes memory) {\n bytes32 burnTokenBytes32 = bytes32(\n abi.encodePacked(bytes12(0), bytes20(uint160(address(usdc))))\n );\n bytes32 messageSenderBytes32 = bytes32(\n abi.encodePacked(bytes12(0), bytes20(uint160(messageSender)))\n );\n bytes32 expirationBlock = bytes32(0);\n\n // Ref: https://developers.circle.com/cctp/technical-guide#message-body\n return\n abi.encodePacked(\n uint32(1), // 0-3: version\n burnTokenBytes32, // 4-35: burnToken (bytes32 left-padded address)\n mintRecipient, // 36-67: mintRecipient (bytes32 left-padded address)\n amount, // 68-99: uint256 amount\n messageSenderBytes32, // 100-131: messageSender (bytes32 left-padded address)\n maxFee, // 132-163: uint256 maxFee\n feeExecuted, // 164-195: uint256 feeExecuted\n expirationBlock, // 196-227: bytes32 expirationBlock\n hookData // 228+: dynamic hookData\n );\n }\n\n function getMinFeeAmount(uint256 amount)\n external\n view\n override\n returns (uint256)\n {\n return 0;\n }\n}\n" + }, + "contracts/mocks/MintableERC20.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { ERC20 } from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\n\ninterface IMintableERC20 {\n function mint(uint256 value) external;\n\n function mintTo(address to, uint256 value) external;\n}\n\n/**\n * @title MintableERC20\n * @dev Exposes the mint function of ERC20 for tests\n */\nabstract contract MintableERC20 is IMintableERC20, ERC20 {\n /**\n * @dev Function to mint tokens\n * @param _value The amount of tokens to mint.\n */\n function mint(uint256 _value) public virtual override {\n _mint(msg.sender, _value);\n }\n\n /**\n * @dev Function to mint tokens\n * @param _to Address to mint to.\n * @param _value The amount of tokens to mint.\n */\n function mintTo(address _to, uint256 _value) public virtual override {\n _mint(_to, _value);\n }\n}\n" + }, + "contracts/mocks/MockBalancerVault.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport { IBalancerVault } from \"../interfaces/balancer/IBalancerVault.sol\";\nimport { MintableERC20 } from \"./MintableERC20.sol\";\nimport { StableMath } from \"../utils/StableMath.sol\";\n\n// import \"hardhat/console.sol\";\n\ncontract MockBalancerVault {\n using StableMath for uint256;\n uint256 public slippage = 1 ether;\n bool public transferDisabled = false;\n bool public slippageErrorDisabled = false;\n\n function swap(\n IBalancerVault.SingleSwap calldata singleSwap,\n IBalancerVault.FundManagement calldata funds,\n uint256 minAmountOut,\n uint256\n ) external returns (uint256 amountCalculated) {\n amountCalculated = (minAmountOut * slippage) / 1 ether;\n if (!slippageErrorDisabled) {\n require(amountCalculated >= minAmountOut, \"Slippage error\");\n }\n IERC20(singleSwap.assetIn).transferFrom(\n funds.sender,\n address(this),\n singleSwap.amount\n );\n if (!transferDisabled) {\n MintableERC20(singleSwap.assetOut).mintTo(\n funds.recipient,\n amountCalculated\n );\n }\n }\n\n function setSlippage(uint256 _slippage) external {\n slippage = _slippage;\n }\n\n function getPoolTokenInfo(bytes32 poolId, address token)\n external\n view\n returns (\n uint256,\n uint256,\n uint256,\n address\n )\n {}\n\n function disableTransfer() external {\n transferDisabled = true;\n }\n\n function disableSlippageError() external {\n slippageErrorDisabled = true;\n }\n}\n" + }, + "contracts/mocks/MockERC4626Vault.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport { IERC4626 } from \"../../lib/openzeppelin/interfaces/IERC4626.sol\";\nimport { ERC20 } from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\n\ncontract MockERC4626Vault is IERC4626, ERC20 {\n using SafeERC20 for IERC20;\n\n address public asset;\n uint8 public constant DECIMALS = 18;\n\n constructor(address _asset) ERC20(\"Mock Vault Share\", \"MVS\") {\n asset = _asset;\n }\n\n // ERC20 totalSupply is inherited\n\n // ERC20 balanceOf is inherited\n\n function deposit(uint256 assets, address receiver)\n public\n override\n returns (uint256 shares)\n {\n shares = previewDeposit(assets);\n IERC20(asset).safeTransferFrom(msg.sender, address(this), assets);\n _mint(receiver, shares);\n return shares;\n }\n\n function mint(uint256 shares, address receiver)\n public\n override\n returns (uint256 assets)\n {\n assets = previewMint(shares);\n IERC20(asset).safeTransferFrom(msg.sender, address(this), assets);\n _mint(receiver, shares);\n return assets;\n }\n\n function withdraw(\n uint256 assets,\n address receiver,\n address owner\n ) public override returns (uint256 shares) {\n shares = previewWithdraw(assets);\n if (msg.sender != owner) {\n // No approval check for mock\n }\n _burn(owner, shares);\n IERC20(asset).safeTransfer(receiver, assets);\n return shares;\n }\n\n function redeem(\n uint256 shares,\n address receiver,\n address owner\n ) public override returns (uint256 assets) {\n assets = previewRedeem(shares);\n if (msg.sender != owner) {\n // No approval check for mock\n }\n _burn(owner, shares);\n IERC20(asset).safeTransfer(receiver, assets);\n return assets;\n }\n\n function totalAssets() public view override returns (uint256) {\n return IERC20(asset).balanceOf(address(this));\n }\n\n function convertToShares(uint256 assets)\n public\n view\n override\n returns (uint256 shares)\n {\n uint256 supply = totalSupply(); // Use ERC20 totalSupply\n return\n supply == 0 || assets == 0\n ? assets\n : (assets * supply) / totalAssets();\n }\n\n function convertToAssets(uint256 shares)\n public\n view\n override\n returns (uint256 assets)\n {\n uint256 supply = totalSupply(); // Use ERC20 totalSupply\n return supply == 0 ? shares : (shares * totalAssets()) / supply;\n }\n\n function maxDeposit(address receiver)\n public\n view\n override\n returns (uint256)\n {\n return type(uint256).max;\n }\n\n function maxMint(address receiver) public view override returns (uint256) {\n return type(uint256).max;\n }\n\n function maxWithdraw(address owner) public view override returns (uint256) {\n return convertToAssets(balanceOf(owner));\n }\n\n function maxRedeem(address owner) public view override returns (uint256) {\n return balanceOf(owner);\n }\n\n function previewDeposit(uint256 assets)\n public\n view\n override\n returns (uint256 shares)\n {\n return convertToShares(assets);\n }\n\n function previewMint(uint256 shares)\n public\n view\n override\n returns (uint256 assets)\n {\n return convertToAssets(shares);\n }\n\n function previewWithdraw(uint256 assets)\n public\n view\n override\n returns (uint256 shares)\n {\n return convertToShares(assets);\n }\n\n function previewRedeem(uint256 shares)\n public\n view\n override\n returns (uint256 assets)\n {\n return convertToAssets(shares);\n }\n\n function _mint(address account, uint256 amount) internal override {\n super._mint(account, amount);\n }\n\n function _burn(address account, uint256 amount) internal override {\n super._burn(account, amount);\n }\n\n // Inherited from ERC20\n}\n" + }, + "contracts/mocks/MockEvilReentrantContract.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { IVault } from \"../interfaces/IVault.sol\";\nimport { IOracle } from \"../interfaces/IOracle.sol\";\nimport { IRateProvider } from \"../interfaces/balancer/IRateProvider.sol\";\n\nimport { IBalancerVault } from \"../interfaces/balancer/IBalancerVault.sol\";\nimport { IERC20 } from \"../utils/InitializableAbstractStrategy.sol\";\n\nimport { StableMath } from \"../utils/StableMath.sol\";\n\ncontract MockEvilReentrantContract {\n using StableMath for uint256;\n\n IBalancerVault public immutable balancerVault;\n IERC20 public immutable reth;\n IERC20 public immutable weth;\n IVault public immutable oethVault;\n address public immutable poolAddress;\n bytes32 public immutable balancerPoolId;\n address public immutable priceProvider;\n\n constructor(\n address _balancerVault,\n address _oethVault,\n address _reth,\n address _weth,\n address _poolAddress,\n bytes32 _poolId\n ) {\n balancerVault = IBalancerVault(_balancerVault);\n oethVault = IVault(_oethVault);\n reth = IERC20(_reth);\n weth = IERC20(_weth);\n poolAddress = _poolAddress;\n balancerPoolId = _poolId;\n }\n\n function doEvilStuff() public {\n uint256 rethPrice = IOracle(priceProvider).price(address(reth));\n\n // 1. Join pool\n uint256[] memory amounts = new uint256[](2);\n amounts[0] = uint256(10 ether);\n amounts[1] = rethPrice * 10;\n\n address[] memory assets = new address[](2);\n assets[0] = address(reth);\n assets[1] = address(weth);\n\n uint256 minBPT = getBPTExpected(assets, amounts).mulTruncate(\n 0.99 ether\n );\n\n bytes memory joinUserData = abi.encode(\n IBalancerVault.WeightedPoolJoinKind.EXACT_TOKENS_IN_FOR_BPT_OUT,\n amounts,\n minBPT\n );\n\n IBalancerVault.JoinPoolRequest memory joinRequest = IBalancerVault\n .JoinPoolRequest(assets, amounts, joinUserData, false);\n\n balancerVault.joinPool(\n balancerPoolId,\n address(this),\n address(this),\n joinRequest\n );\n\n uint256 bptTokenBalance = IERC20(poolAddress).balanceOf(address(this));\n\n // 2. Redeem as ETH\n bytes memory exitUserData = abi.encode(\n IBalancerVault.WeightedPoolExitKind.EXACT_BPT_IN_FOR_ONE_TOKEN_OUT,\n bptTokenBalance,\n 1\n );\n\n assets[1] = address(0); // Receive ETH instead of WETH\n uint256[] memory exitAmounts = new uint256[](2);\n exitAmounts[1] = 15 ether;\n IBalancerVault.ExitPoolRequest memory exitRequest = IBalancerVault\n .ExitPoolRequest(assets, exitAmounts, exitUserData, false);\n\n balancerVault.exitPool(\n balancerPoolId,\n address(this),\n payable(address(this)),\n exitRequest\n );\n bptTokenBalance = IERC20(poolAddress).balanceOf(address(this));\n }\n\n function getBPTExpected(address[] memory _assets, uint256[] memory _amounts)\n internal\n view\n virtual\n returns (uint256 bptExpected)\n {\n for (uint256 i = 0; i < _assets.length; ++i) {\n uint256 strategyAssetMarketPrice = IOracle(priceProvider).price(\n _assets[i]\n );\n // convert asset amount to ETH amount\n bptExpected =\n bptExpected +\n _amounts[i].mulTruncate(strategyAssetMarketPrice);\n }\n\n uint256 bptRate = IRateProvider(poolAddress).getRate();\n // Convert ETH amount to BPT amount\n bptExpected = bptExpected.divPrecisely(bptRate);\n }\n\n function approveAllTokens() public {\n // Approve all tokens\n weth.approve(address(oethVault), type(uint256).max);\n reth.approve(poolAddress, type(uint256).max);\n weth.approve(poolAddress, type(uint256).max);\n reth.approve(address(balancerVault), type(uint256).max);\n weth.approve(address(balancerVault), type(uint256).max);\n }\n\n receive() external payable {\n // 3. Try to mint OETH\n oethVault.mint(address(weth), 1 ether, 0.9 ether);\n }\n}\n" + }, + "contracts/mocks/MockRoosterAMOStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title Rooster AMO strategy exposing extra functionality\n * @author Origin Protocol Inc\n */\n\nimport { RoosterAMOStrategy } from \"../strategies/plume/RoosterAMOStrategy.sol\";\nimport { IMaverickV2Pool } from \"../interfaces/plume/IMaverickV2Pool.sol\";\n\ncontract MockRoosterAMOStrategy is RoosterAMOStrategy {\n constructor(\n BaseStrategyConfig memory _stratConfig,\n address _wethAddress,\n address _oethpAddress,\n address _liquidityManager,\n address _poolLens,\n address _maverickPosition,\n address _maverickQuoter,\n address _mPool,\n bool _upperTickAtParity,\n address _votingDistributor,\n address _poolDistributor\n )\n RoosterAMOStrategy(\n _stratConfig,\n _wethAddress,\n _oethpAddress,\n _liquidityManager,\n _poolLens,\n _maverickPosition,\n _maverickQuoter,\n _mPool,\n _upperTickAtParity,\n _votingDistributor,\n _poolDistributor\n )\n {}\n\n function getCurrentWethShare() external view returns (uint256) {\n uint256 _currentPrice = getPoolSqrtPrice();\n\n return _getWethShare(_currentPrice);\n }\n}\n" + }, + "contracts/poolBooster/AbstractPoolBoosterFactory.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { Governable } from \"../governance/Governable.sol\";\nimport { IPoolBooster } from \"../interfaces/poolBooster/IPoolBooster.sol\";\nimport { IPoolBoostCentralRegistry } from \"../interfaces/poolBooster/IPoolBoostCentralRegistry.sol\";\n\n/**\n * @title Abstract Pool booster factory\n * @author Origin Protocol Inc\n */\ncontract AbstractPoolBoosterFactory is Governable {\n struct PoolBoosterEntry {\n address boosterAddress;\n address ammPoolAddress;\n IPoolBoostCentralRegistry.PoolBoosterType boosterType;\n }\n\n // @notice address of Origin Token\n address public immutable oToken;\n // @notice Central registry contract\n IPoolBoostCentralRegistry public immutable centralRegistry;\n\n // @notice list of all the pool boosters created by this factory\n PoolBoosterEntry[] public poolBoosters;\n // @notice mapping of AMM pool to pool booster\n mapping(address => PoolBoosterEntry) public poolBoosterFromPool;\n\n // @param address _oToken address of the OToken token\n // @param address _governor address governor\n // @param address _centralRegistry address of the central registry\n constructor(\n address _oToken,\n address _governor,\n address _centralRegistry\n ) {\n require(_oToken != address(0), \"Invalid oToken address\");\n require(_governor != address(0), \"Invalid governor address\");\n require(\n _centralRegistry != address(0),\n \"Invalid central registry address\"\n );\n\n oToken = _oToken;\n centralRegistry = IPoolBoostCentralRegistry(_centralRegistry);\n _setGovernor(_governor);\n }\n\n /**\n * @notice Goes over all the pool boosters created by this factory and\n * calls bribe() on them.\n * @param _exclusionList A list of pool booster addresses to skip when\n * calling this function.\n */\n function bribeAll(address[] memory _exclusionList) external {\n uint256 lengthI = poolBoosters.length;\n for (uint256 i = 0; i < lengthI; i++) {\n address poolBoosterAddress = poolBoosters[i].boosterAddress;\n bool skipBribeCall = false;\n uint256 lengthJ = _exclusionList.length;\n for (uint256 j = 0; j < lengthJ; j++) {\n // pool booster in exclusion list\n if (_exclusionList[j] == poolBoosterAddress) {\n skipBribeCall = true;\n break;\n }\n }\n\n if (!skipBribeCall) {\n IPoolBooster(poolBoosterAddress).bribe();\n }\n }\n }\n\n /**\n * @notice Removes the pool booster from the internal list of pool boosters.\n * @dev This action does not destroy the pool booster contract nor does it\n * stop the yield delegation to it.\n * @param _poolBoosterAddress address of the pool booster\n */\n function removePoolBooster(address _poolBoosterAddress)\n external\n onlyGovernor\n {\n uint256 boostersLen = poolBoosters.length;\n for (uint256 i = 0; i < boostersLen; ++i) {\n if (poolBoosters[i].boosterAddress == _poolBoosterAddress) {\n // erase mapping\n delete poolBoosterFromPool[poolBoosters[i].ammPoolAddress];\n\n // overwrite current pool booster with the last entry in the list\n poolBoosters[i] = poolBoosters[boostersLen - 1];\n // drop the last entry\n poolBoosters.pop();\n\n centralRegistry.emitPoolBoosterRemoved(_poolBoosterAddress);\n break;\n }\n }\n }\n\n function _storePoolBoosterEntry(\n address _poolBoosterAddress,\n address _ammPoolAddress,\n IPoolBoostCentralRegistry.PoolBoosterType _boosterType\n ) internal {\n PoolBoosterEntry memory entry = PoolBoosterEntry(\n _poolBoosterAddress,\n _ammPoolAddress,\n _boosterType\n );\n\n poolBoosters.push(entry);\n poolBoosterFromPool[_ammPoolAddress] = entry;\n\n // emit the events of the pool booster created\n centralRegistry.emitPoolBoosterCreated(\n _poolBoosterAddress,\n _ammPoolAddress,\n _boosterType\n );\n }\n\n function _deployContract(bytes memory _bytecode, uint256 _salt)\n internal\n returns (address _address)\n {\n // solhint-disable-next-line no-inline-assembly\n assembly {\n _address := create2(\n 0,\n add(_bytecode, 0x20),\n mload(_bytecode),\n _salt\n )\n }\n\n require(\n _address.code.length > 0 && _address != address(0),\n \"Failed creating a pool booster\"\n );\n }\n\n // pre-compute the address of the deployed contract that will be\n // created when create2 is called\n function _computeAddress(bytes memory _bytecode, uint256 _salt)\n internal\n view\n returns (address)\n {\n bytes32 hash = keccak256(\n abi.encodePacked(\n bytes1(0xff),\n address(this),\n _salt,\n keccak256(_bytecode)\n )\n );\n\n // cast last 20 bytes of hash to address\n return address(uint160(uint256(hash)));\n }\n\n function poolBoosterLength() external view returns (uint256) {\n return poolBoosters.length;\n }\n}\n" + }, + "contracts/poolBooster/curve/CurvePoolBooster.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport { Initializable } from \"../../utils/Initializable.sol\";\nimport { Strategizable } from \"../../governance/Strategizable.sol\";\nimport { ICampaignRemoteManager } from \"../../interfaces/ICampaignRemoteManager.sol\";\n\n/// @title CurvePoolBooster\n/// @author Origin Protocol\n/// @notice Contract to manage interactions with VotemarketV2 for a dedicated Curve pool/gauge.\ncontract CurvePoolBooster is Initializable, Strategizable {\n using SafeERC20 for IERC20;\n\n ////////////////////////////////////////////////////\n /// --- CONSTANTS && IMMUTABLES\n ////////////////////////////////////////////////////\n /// @notice Base fee for the contract, 100%\n uint16 public constant FEE_BASE = 10_000;\n\n /// @notice Arbitrum where the votemarket is running\n uint256 public constant targetChainId = 42161;\n\n /// @notice Address of the gauge to manage\n address public immutable gauge;\n\n /// @notice Address of the reward token\n address public immutable rewardToken;\n\n ////////////////////////////////////////////////////\n /// --- STORAGE\n ////////////////////////////////////////////////////\n\n /// @notice Fee in FEE_BASE unit payed when managing campaign.\n uint16 public fee;\n\n /// @notice Address of the fee collector\n address public feeCollector;\n\n /// @notice Address of the campaignRemoteManager linked to VotemarketV2\n address public campaignRemoteManager;\n\n /// @notice Address of votemarket in L2\n address public votemarket;\n\n /// @notice Id of the campaign created\n uint256 public campaignId;\n\n ////////////////////////////////////////////////////\n /// --- EVENTS\n ////////////////////////////////////////////////////\n event FeeUpdated(uint16 newFee);\n event FeeCollected(address feeCollector, uint256 feeAmount);\n event FeeCollectorUpdated(address newFeeCollector);\n event VotemarketUpdated(address newVotemarket);\n event CampaignRemoteManagerUpdated(address newCampaignRemoteManager);\n event CampaignCreated(\n address gauge,\n address rewardToken,\n uint256 maxRewardPerVote,\n uint256 totalRewardAmount\n );\n event CampaignIdUpdated(uint256 newId);\n event CampaignClosed(uint256 campaignId);\n event TotalRewardAmountUpdated(uint256 extraTotalRewardAmount);\n event NumberOfPeriodsUpdated(uint8 extraNumberOfPeriods);\n event RewardPerVoteUpdated(uint256 newMaxRewardPerVote);\n event TokensRescued(address token, uint256 amount, address receiver);\n\n ////////////////////////////////////////////////////\n /// --- CONSTRUCTOR && INITIALIZATION\n ////////////////////////////////////////////////////\n constructor(address _rewardToken, address _gauge) {\n rewardToken = _rewardToken;\n gauge = _gauge;\n\n // Prevent implementation contract to be governed\n _setGovernor(address(0));\n }\n\n /// @notice initialize function, to set up initial internal state\n /// @param _strategist Address of the strategist\n /// @param _fee Fee in FEE_BASE unit payed when managing campaign\n /// @param _feeCollector Address of the fee collector\n function initialize(\n address _strategist,\n uint16 _fee,\n address _feeCollector,\n address _campaignRemoteManager,\n address _votemarket\n ) external onlyGovernor initializer {\n _setStrategistAddr(_strategist);\n _setFee(_fee);\n _setFeeCollector(_feeCollector);\n _setCampaignRemoteManager(_campaignRemoteManager);\n _setVotemarket(_votemarket);\n }\n\n ////////////////////////////////////////////////////\n /// --- MUTATIVE FUNCTIONS\n ////////////////////////////////////////////////////\n /// @notice Create a new campaign on VotemarketV2\n /// @dev This will use all token available in this contract\n /// @dev Caller must send ETH to pay for the bridge fee\n /// @param numberOfPeriods Duration of the campaign in weeks\n /// @param maxRewardPerVote Maximum reward per vote to distribute, to avoid overspending\n /// @param blacklist List of addresses to exclude from the campaign\n /// @param additionalGasLimit Additional gas limit for the bridge\n function createCampaign(\n uint8 numberOfPeriods,\n uint256 maxRewardPerVote,\n address[] calldata blacklist,\n uint256 additionalGasLimit\n ) external payable nonReentrant onlyGovernorOrStrategist {\n require(campaignId == 0, \"Campaign already created\");\n require(numberOfPeriods > 1, \"Invalid number of periods\");\n require(maxRewardPerVote > 0, \"Invalid reward per vote\");\n\n // Handle fee (if any)\n uint256 balanceSubFee = _handleFee();\n\n // Approve the balanceSubFee to the campaign manager\n IERC20(rewardToken).safeApprove(campaignRemoteManager, 0);\n IERC20(rewardToken).safeApprove(campaignRemoteManager, balanceSubFee);\n\n // Create a new campaign\n ICampaignRemoteManager(campaignRemoteManager).createCampaign{\n value: msg.value\n }(\n ICampaignRemoteManager.CampaignCreationParams({\n chainId: targetChainId,\n gauge: gauge,\n manager: address(this),\n rewardToken: rewardToken,\n numberOfPeriods: numberOfPeriods,\n maxRewardPerVote: maxRewardPerVote,\n totalRewardAmount: balanceSubFee,\n addresses: blacklist,\n hook: address(0),\n isWhitelist: false\n }),\n targetChainId,\n additionalGasLimit,\n votemarket\n );\n\n emit CampaignCreated(\n gauge,\n rewardToken,\n maxRewardPerVote,\n balanceSubFee\n );\n }\n\n /// @notice Manage campaign parameters in a single call\n /// @dev This function should be called after the campaign is created\n /// @dev Caller must send ETH to pay for the bridge fee\n /// @param totalRewardAmount Amount of reward tokens to add:\n /// - 0: no update\n /// - type(uint256).max: use all tokens in contract\n /// - other: use specific amount\n /// @param numberOfPeriods Number of additional periods (0 = no update)\n /// @param maxRewardPerVote New maximum reward per vote (0 = no update)\n /// @param additionalGasLimit Additional gas limit for the bridge\n function manageCampaign(\n uint256 totalRewardAmount,\n uint8 numberOfPeriods,\n uint256 maxRewardPerVote,\n uint256 additionalGasLimit\n ) external payable nonReentrant onlyGovernorOrStrategist {\n require(campaignId != 0, \"Campaign not created\");\n\n uint256 rewardAmount;\n\n if (totalRewardAmount != 0) {\n uint256 amount = min(\n IERC20(rewardToken).balanceOf(address(this)),\n totalRewardAmount\n );\n\n // Handle fee\n rewardAmount = _handleFee(amount);\n require(rewardAmount > 0, \"No reward to add\");\n\n // Approve the reward amount to the campaign manager\n IERC20(rewardToken).safeApprove(campaignRemoteManager, 0);\n IERC20(rewardToken).safeApprove(\n campaignRemoteManager,\n rewardAmount\n );\n }\n\n // Call remote manager\n ICampaignRemoteManager(campaignRemoteManager).manageCampaign{\n value: msg.value\n }(\n ICampaignRemoteManager.CampaignManagementParams({\n campaignId: campaignId,\n rewardToken: rewardToken,\n numberOfPeriods: numberOfPeriods,\n totalRewardAmount: rewardAmount,\n maxRewardPerVote: maxRewardPerVote\n }),\n targetChainId,\n additionalGasLimit,\n votemarket\n );\n\n // Emit relevant events\n if (rewardAmount > 0) {\n emit TotalRewardAmountUpdated(rewardAmount);\n }\n if (numberOfPeriods > 0) {\n emit NumberOfPeriodsUpdated(numberOfPeriods);\n }\n if (maxRewardPerVote > 0) {\n emit RewardPerVoteUpdated(maxRewardPerVote);\n }\n }\n\n /// @notice Close the campaign.\n /// @dev This function only work on the L2 chain. Not on mainnet.\n /// @dev Caller must send ETH to pay for the bridge fee\n /// @dev The _campaignId parameter is not related to the campaignId of this contract, allowing greater flexibility.\n /// @param _campaignId Id of the campaign to close\n /// @param additionalGasLimit Additional gas limit for the bridge\n // slither-disable-start reentrancy-eth\n function closeCampaign(uint256 _campaignId, uint256 additionalGasLimit)\n external\n payable\n nonReentrant\n onlyGovernorOrStrategist\n {\n ICampaignRemoteManager(campaignRemoteManager).closeCampaign{\n value: msg.value\n }(\n ICampaignRemoteManager.CampaignClosingParams({\n campaignId: campaignId\n }),\n targetChainId,\n additionalGasLimit,\n votemarket\n );\n campaignId = 0;\n emit CampaignClosed(_campaignId);\n }\n\n // slither-disable-end reentrancy-eth\n\n /// @notice Calculate the fee amount and transfer it to the feeCollector\n /// @dev Uses full contract balance\n /// @return Balance after fee\n function _handleFee() internal returns (uint256) {\n uint256 balance = IERC20(rewardToken).balanceOf(address(this));\n\n // This is not a problem if balance is 0, feeAmount will be 0 as well\n // We don't want to make the whole function revert just because of that.\n return _handleFee(balance);\n }\n\n /// @notice Calculate the fee amount and transfer it to the feeCollector\n /// @param amount Amount to take fee from\n /// @return Amount after fee\n function _handleFee(uint256 amount) internal returns (uint256) {\n uint256 feeAmount = (amount * fee) / FEE_BASE;\n\n // If there is a fee, transfer it to the feeCollector\n if (feeAmount > 0) {\n IERC20(rewardToken).safeTransfer(feeCollector, feeAmount);\n emit FeeCollected(feeCollector, feeAmount);\n }\n\n // Fetch balance again to avoid rounding issues\n return IERC20(rewardToken).balanceOf(address(this));\n }\n\n ////////////////////////////////////////////////////\n /// --- GOVERNANCE && OPERATION\n ////////////////////////////////////////////////////\n /// @notice Set the campaign id\n /// @dev Only callable by the governor or strategist\n /// @param _campaignId New campaign id\n function setCampaignId(uint256 _campaignId)\n external\n onlyGovernorOrStrategist\n {\n campaignId = _campaignId;\n emit CampaignIdUpdated(_campaignId);\n }\n\n /// @notice Rescue ETH from the contract\n /// @dev Only callable by the governor or strategist\n /// @param receiver Address to receive the ETH\n function rescueETH(address receiver)\n external\n nonReentrant\n onlyGovernorOrStrategist\n {\n require(receiver != address(0), \"Invalid receiver\");\n uint256 balance = address(this).balance;\n (bool success, ) = receiver.call{ value: balance }(\"\");\n require(success, \"Transfer failed\");\n emit TokensRescued(address(0), balance, receiver);\n }\n\n /// @notice Rescue ERC20 tokens from the contract\n /// @dev Only callable by the governor or strategist\n /// @param token Address of the token to rescue\n function rescueToken(address token, address receiver)\n external\n nonReentrant\n onlyGovernor\n {\n require(receiver != address(0), \"Invalid receiver\");\n uint256 balance = IERC20(token).balanceOf(address(this));\n IERC20(token).safeTransfer(receiver, balance);\n emit TokensRescued(token, balance, receiver);\n }\n\n /// @notice Set the fee\n /// @dev Only callable by the governor\n /// @param _fee New fee\n function setFee(uint16 _fee) external onlyGovernor {\n _setFee(_fee);\n }\n\n /// @notice Internal logic to set the fee\n function _setFee(uint16 _fee) internal {\n require(_fee <= FEE_BASE / 2, \"Fee too high\");\n fee = _fee;\n emit FeeUpdated(_fee);\n }\n\n /// @notice Set the fee collector\n /// @dev Only callable by the governor\n /// @param _feeCollector New fee collector\n function setFeeCollector(address _feeCollector) external onlyGovernor {\n _setFeeCollector(_feeCollector);\n }\n\n /// @notice Internal logic to set the fee collector\n function _setFeeCollector(address _feeCollector) internal {\n require(_feeCollector != address(0), \"Invalid fee collector\");\n feeCollector = _feeCollector;\n emit FeeCollectorUpdated(_feeCollector);\n }\n\n /// @notice Set the campaignRemoteManager\n /// @param _campaignRemoteManager New campaignRemoteManager address\n function setCampaignRemoteManager(address _campaignRemoteManager)\n external\n onlyGovernor\n {\n _setCampaignRemoteManager(_campaignRemoteManager);\n }\n\n /// @notice Internal logic to set the campaignRemoteManager\n /// @param _campaignRemoteManager New campaignRemoteManager address\n function _setCampaignRemoteManager(address _campaignRemoteManager)\n internal\n {\n require(\n _campaignRemoteManager != address(0),\n \"Invalid campaignRemoteManager\"\n );\n campaignRemoteManager = _campaignRemoteManager;\n emit CampaignRemoteManagerUpdated(_campaignRemoteManager);\n }\n\n /// @notice Set the votemarket address\n /// @param _votemarket New votemarket address\n function setVotemarket(address _votemarket) external onlyGovernor {\n _setVotemarket(_votemarket);\n }\n\n /// @notice Internal logic to set the votemarket address\n function _setVotemarket(address _votemarket) internal {\n require(_votemarket != address(0), \"Invalid votemarket\");\n votemarket = _votemarket;\n emit VotemarketUpdated(_votemarket);\n }\n\n /// @notice Return the minimum of two uint256 numbers\n function min(uint256 a, uint256 b) internal pure returns (uint256) {\n return a < b ? a : b;\n }\n\n receive() external payable {}\n}\n" + }, + "contracts/poolBooster/curve/CurvePoolBoosterFactory.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { ICreateX } from \"../../interfaces/ICreateX.sol\";\nimport { Initializable } from \"../../utils/Initializable.sol\";\nimport { Strategizable } from \"../../governance/Strategizable.sol\";\nimport { CurvePoolBoosterPlain } from \"./CurvePoolBoosterPlain.sol\";\nimport { IPoolBoostCentralRegistry } from \"../../interfaces/poolBooster/IPoolBoostCentralRegistry.sol\";\n\n/// @title CurvePoolBoosterFactory\n/// @author Origin Protocol\n/// @notice Factory contract to create CurvePoolBoosterPlain instances\ncontract CurvePoolBoosterFactory is Initializable, Strategizable {\n ////////////////////////////////////////////////////\n /// --- Structs\n ////////////////////////////////////////////////////\n struct PoolBoosterEntry {\n address boosterAddress;\n address ammPoolAddress;\n IPoolBoostCentralRegistry.PoolBoosterType boosterType;\n }\n\n ////////////////////////////////////////////////////\n /// --- Constants\n ////////////////////////////////////////////////////\n\n /// @notice Address of the CreateX contract\n ICreateX public constant CREATEX =\n ICreateX(0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed);\n\n ////////////////////////////////////////////////////\n /// --- Storage\n ////////////////////////////////////////////////////\n\n /// @notice list of all the pool boosters created by this factory\n PoolBoosterEntry[] public poolBoosters;\n /// @notice Central registry contract\n IPoolBoostCentralRegistry public centralRegistry;\n /// @notice mapping of AMM pool to pool booster\n mapping(address => PoolBoosterEntry) public poolBoosterFromPool;\n\n ////////////////////////////////////////////////////\n /// --- Initialize\n ////////////////////////////////////////////////////\n\n /// @notice Initialize the contract. Normally we'd rather have the governor and strategist set in the constructor,\n /// but since this contract is deployed by CreateX we need to set them in the initialize function because\n /// the constructor's parameters influence the address of the contract when deployed using CreateX.\n /// And having different governor and strategist on the same address on different chains would\n /// cause issues.\n /// @param _governor Address of the governor\n /// @param _strategist Address of the strategist\n /// @param _centralRegistry Address of the central registry\n function initialize(\n address _governor,\n address _strategist,\n address _centralRegistry\n ) external initializer {\n _setGovernor(_governor);\n _setStrategistAddr(_strategist);\n centralRegistry = IPoolBoostCentralRegistry(_centralRegistry);\n }\n\n ////////////////////////////////////////////////////\n /// --- External Mutative Functions\n ////////////////////////////////////////////////////\n\n /// @notice Create a new CurvePoolBoosterPlain instance\n /// @param _rewardToken Address of the reward token (OETH or OUSD)\n /// @param _gauge Address of the gauge (e.g. Curve OETH/WETH Gauge)\n /// @param _feeCollector Address of the fee collector (e.g. MultichainStrategist)\n /// @param _fee Fee in FEE_BASE unit payed when managing campaign\n /// @param _campaignRemoteManager Address of the campaign remote manager\n /// @param _votemarket Address of the votemarket\n /// @param _salt A unique number that affects the address of the pool booster created. Note: this number\n /// should match the one from `computePoolBoosterAddress` in order for the final deployed address\n /// and pre-computed address to match\n /// @param _expectedAddress The expected address of the pool booster. This is used to verify that the pool booster\n /// was deployed at the expected address, otherwise the transaction batch will revert. If set to 0 then the\n /// address verification is skipped.\n function createCurvePoolBoosterPlain(\n address _rewardToken,\n address _gauge,\n address _feeCollector,\n uint16 _fee,\n address _campaignRemoteManager,\n address _votemarket,\n bytes32 _salt,\n address _expectedAddress\n ) external onlyGovernorOrStrategist returns (address) {\n require(governor() != address(0), \"Governor not set\");\n require(strategistAddr != address(0), \"Strategist not set\");\n // salt encoded sender\n address senderAddress = address(bytes20(_salt));\n // the contract that calls the CreateX should be encoded in the salt to protect against front-running\n require(senderAddress == address(this), \"Front-run protection failed\");\n\n address poolBoosterAddress = CREATEX.deployCreate2(\n _salt,\n _getInitCode(_rewardToken, _gauge)\n );\n\n require(\n _expectedAddress == address(0) ||\n poolBoosterAddress == _expectedAddress,\n \"Pool booster deployed at unexpected address\"\n );\n\n CurvePoolBoosterPlain(payable(poolBoosterAddress)).initialize(\n governor(),\n strategistAddr,\n _fee,\n _feeCollector,\n _campaignRemoteManager,\n _votemarket\n );\n\n _storePoolBoosterEntry(poolBoosterAddress, _gauge);\n return poolBoosterAddress;\n }\n\n /// @notice Removes the pool booster from the internal list of pool boosters.\n /// @dev This action does not destroy the pool booster contract nor does it\n /// stop the yield delegation to it.\n /// @param _poolBoosterAddress address of the pool booster\n function removePoolBooster(address _poolBoosterAddress)\n external\n onlyGovernor\n {\n uint256 boostersLen = poolBoosters.length;\n for (uint256 i = 0; i < boostersLen; ++i) {\n if (poolBoosters[i].boosterAddress == _poolBoosterAddress) {\n // erase mapping\n delete poolBoosterFromPool[poolBoosters[i].ammPoolAddress];\n\n // overwrite current pool booster with the last entry in the list\n poolBoosters[i] = poolBoosters[boostersLen - 1];\n // drop the last entry\n poolBoosters.pop();\n\n // centralRegistry can be address(0) on some chains\n if (address(centralRegistry) != address(0)) {\n centralRegistry.emitPoolBoosterRemoved(_poolBoosterAddress);\n }\n break;\n }\n }\n }\n\n ////////////////////////////////////////////////////\n /// --- Internal Mutative Functions\n ////////////////////////////////////////////////////\n\n /// @notice Stores the pool booster entry in the internal list and mapping\n /// @param _poolBoosterAddress address of the pool booster\n /// @param _ammPoolAddress address of the AMM pool\n function _storePoolBoosterEntry(\n address _poolBoosterAddress,\n address _ammPoolAddress\n ) internal {\n IPoolBoostCentralRegistry.PoolBoosterType _boosterType = IPoolBoostCentralRegistry\n .PoolBoosterType\n .CurvePoolBoosterPlain;\n PoolBoosterEntry memory entry = PoolBoosterEntry(\n _poolBoosterAddress,\n _ammPoolAddress,\n _boosterType\n );\n\n poolBoosters.push(entry);\n poolBoosterFromPool[_ammPoolAddress] = entry;\n\n // emit the events of the pool booster created\n // centralRegistry can be address(0) on some chains\n if (address(centralRegistry) != address(0)) {\n centralRegistry.emitPoolBoosterCreated(\n _poolBoosterAddress,\n _ammPoolAddress,\n _boosterType\n );\n }\n }\n\n ////////////////////////////////////////////////////\n /// --- External View Functions\n ////////////////////////////////////////////////////\n\n /// @notice Create a new CurvePoolBoosterPlain instance (address computation version)\n /// @param _rewardToken Address of the reward token (OETH or OUSD)\n /// @param _gauge Address of the gauge (e.g. Curve OETH/WETH Gauge)\n /// @param _salt A unique number that affects the address of the pool booster created. Note: this number\n /// should match the one from `createCurvePoolBoosterPlain` in order for the final deployed address\n /// and pre-computed address to match\n function computePoolBoosterAddress(\n address _rewardToken,\n address _gauge,\n bytes32 _salt\n ) external view returns (address) {\n bytes32 guardedSalt = _computeGuardedSalt(_salt);\n return\n CREATEX.computeCreate2Address(\n guardedSalt,\n keccak256(_getInitCode(_rewardToken, _gauge)),\n address(CREATEX)\n );\n }\n\n /// @notice Encodes a salt for CreateX by concatenating deployer address (bytes20), cross-chain protection flag\n /// (bytes1), and the first 11 bytes of the provided salt (most significant bytes). This function is exposed\n /// for easier operations. For the salt value itself just use the epoch time when the operation is performed.\n /// @param salt The raw salt as uint256; converted to bytes32, then only the first 11 bytes (MSB) are used.\n /// @return encodedSalt The resulting 32-byte encoded salt.\n function encodeSaltForCreateX(uint256 salt)\n external\n view\n returns (bytes32 encodedSalt)\n {\n // only the right most 11 bytes are considered when encoding salt. Which is limited by the number in the below\n // require. If salt were higher, the higher bytes would need to be set to 0 to not affect the \"or\" way of\n // encoding the salt.\n require(salt <= 309485009821345068724781055, \"Invalid salt\");\n\n // prepare encoded salt guarded by this factory address. When the deployer part of the salt is the same as the\n // caller of CreateX the salt is re-hashed and thus guarded from front-running.\n address deployer = address(this);\n\n // Flag as uint8 (0)\n uint8 flag = 0;\n\n // Precompute parts\n uint256 deployerPart = uint256(uint160(deployer)) << 96; // 20 bytes shifted left 96 bits (12 bytes)\n uint256 flagPart = uint256(flag) << 88; // 1 byte shifted left 88 bits (11 bytes)\n\n // Concat via nested OR\n // solhint-disable-next-line no-inline-assembly\n assembly {\n encodedSalt := or(or(deployerPart, flagPart), salt)\n }\n }\n\n /// @notice Get the number of pool boosters created by this factory\n function poolBoosterLength() external view returns (uint256) {\n return poolBoosters.length;\n }\n\n /// @notice Get the list of all pool boosters created by this factory\n function getPoolBoosters()\n external\n view\n returns (PoolBoosterEntry[] memory)\n {\n return poolBoosters;\n }\n\n ////////////////////////////////////////////////////\n /// --- Internal View/Pure Functions\n ////////////////////////////////////////////////////\n\n /// @notice Get the init code for the CurvePoolBoosterPlain contract\n function _getInitCode(address _rewardToken, address _gauge)\n internal\n pure\n returns (bytes memory)\n {\n return\n abi.encodePacked(\n type(CurvePoolBoosterPlain).creationCode,\n abi.encode(_rewardToken, _gauge)\n );\n }\n\n /// @notice Compute the guarded salt for CreateX protections. This version of guarded\n /// salt expects that this factory contract is the one doing calls to the CreateX contract.\n function _computeGuardedSalt(bytes32 _salt)\n internal\n view\n returns (bytes32)\n {\n return\n _efficientHash({\n a: bytes32(uint256(uint160(address(this)))),\n b: _salt\n });\n }\n\n /// @notice Efficiently hash two bytes32 values together\n function _efficientHash(bytes32 a, bytes32 b)\n internal\n pure\n returns (bytes32 hash)\n {\n // solhint-disable-next-line no-inline-assembly\n assembly {\n mstore(0x00, a)\n mstore(0x20, b)\n hash := keccak256(0x00, 0x40)\n }\n }\n}\n" + }, + "contracts/poolBooster/curve/CurvePoolBoosterPlain.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { CurvePoolBooster } from \"./CurvePoolBooster.sol\";\n\n/// @title CurvePoolBoosterPlain\n/// @author Origin Protocol\n/// @notice Contract to manage interactions with VotemarketV2 for a dedicated Curve pool/gauge. It differs from the\n/// CurvePoolBooster in that it is not proxied.\n/// @dev Governor is not set in the constructor so that the same contract can be deployed on the same address on\n/// multiple chains. Governor is set in the initialize function.\ncontract CurvePoolBoosterPlain is CurvePoolBooster {\n constructor(address _rewardToken, address _gauge)\n CurvePoolBooster(_rewardToken, _gauge)\n {\n rewardToken = _rewardToken;\n gauge = _gauge;\n }\n\n /// @notice initialize function, to set up initial internal state\n /// @param _strategist Address of the strategist\n /// @param _fee Fee in FEE_BASE unit payed when managing campaign\n /// @param _feeCollector Address of the fee collector\n /// @dev Since this function is initialized in the same transaction as it is created the initialize function\n /// doesn't need role protection.\n /// Because the governor is only set in the initialisation function the base class initialize can not be\n /// called as it is not the governor who is issueing this call.\n function initialize(\n address _govenor,\n address _strategist,\n uint16 _fee,\n address _feeCollector,\n address _campaignRemoteManager,\n address _votemarket\n ) external initializer {\n _setStrategistAddr(_strategist);\n _setFee(_fee);\n _setFeeCollector(_feeCollector);\n _setCampaignRemoteManager(_campaignRemoteManager);\n _setVotemarket(_votemarket);\n\n // Set the governor to the provided governor\n _setGovernor(_govenor);\n }\n}\n" + }, + "contracts/poolBooster/PoolBoostCentralRegistry.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { Governable } from \"../governance/Governable.sol\";\nimport { IPoolBoostCentralRegistry } from \"../interfaces/poolBooster/IPoolBoostCentralRegistry.sol\";\n\n/**\n * @title Contract that holds all governance approved pool booster Factory\n * implementation deployments\n * @author Origin Protocol Inc\n */\ncontract PoolBoostCentralRegistry is Governable, IPoolBoostCentralRegistry {\n event FactoryApproved(address factoryAddress);\n event FactoryRemoved(address factoryAddress);\n\n // @notice List of approved factories\n address[] public factories;\n\n modifier onlyApprovedFactories() {\n require(isApprovedFactory(msg.sender), \"Not an approved factory\");\n _;\n }\n\n constructor() {\n // set the governor of the implementation contract to zero address\n _setGovernor(address(0));\n }\n\n /**\n * @notice Adds a factory address to the approved factory addresses\n * @param _factoryAddress address of the factory\n */\n function approveFactory(address _factoryAddress) external onlyGovernor {\n require(_factoryAddress != address(0), \"Invalid address\");\n require(\n !isApprovedFactory(_factoryAddress),\n \"Factory already approved\"\n );\n\n factories.push(_factoryAddress);\n emit FactoryApproved(_factoryAddress);\n }\n\n /**\n * @notice Removes the factory from approved factory addresses\n * @param _factoryAddress address of the factory\n */\n function removeFactory(address _factoryAddress) external onlyGovernor {\n require(_factoryAddress != address(0), \"Invalid address\");\n\n uint256 length = factories.length;\n bool factoryRemoved = false;\n for (uint256 i = 0; i < length; i++) {\n if (factories[i] != _factoryAddress) {\n continue;\n }\n\n factories[i] = factories[length - 1];\n factories.pop();\n emit FactoryRemoved(_factoryAddress);\n factoryRemoved = true;\n break;\n }\n require(factoryRemoved, \"Not an approved factory\");\n\n emit FactoryRemoved(_factoryAddress);\n }\n\n /**\n * @notice Emits a pool booster created event\n * @dev This has been created as a convenience method for the monitoring to have\n * an index of all of the created pool boosters by only listening to the\n * events of this contract.\n * @param _poolBoosterAddress address of the pool booster created\n * @param _ammPoolAddress address of the AMM pool forwarding yield to the pool booster\n * @param _boosterType PoolBoosterType the type of the pool booster\n */\n function emitPoolBoosterCreated(\n address _poolBoosterAddress,\n address _ammPoolAddress,\n PoolBoosterType _boosterType\n ) external onlyApprovedFactories {\n emit PoolBoosterCreated(\n _poolBoosterAddress,\n _ammPoolAddress,\n _boosterType,\n msg.sender // address of the factory\n );\n }\n\n /**\n * @notice Emits a pool booster removed event\n * @dev This has been created as a convenience method for the monitoring to have\n * an index of all of the removed pool boosters by only listening to the\n * events of this contract.\n * @param _poolBoosterAddress address of the pool booster to be removed\n */\n function emitPoolBoosterRemoved(address _poolBoosterAddress)\n external\n onlyApprovedFactories\n {\n emit PoolBoosterRemoved(_poolBoosterAddress);\n }\n\n /**\n * @notice Returns true if the factory is approved\n * @param _factoryAddress address of the factory\n */\n function isApprovedFactory(address _factoryAddress)\n public\n view\n returns (bool)\n {\n uint256 length = factories.length;\n for (uint256 i = 0; i < length; i++) {\n if (factories[i] == _factoryAddress) {\n return true;\n }\n }\n return false;\n }\n\n /**\n * @notice Returns all supported factories\n */\n function getAllFactories() external view returns (address[] memory) {\n return factories;\n }\n}\n" + }, + "contracts/poolBooster/PoolBoosterFactoryMerkl.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { PoolBoosterMerkl } from \"./PoolBoosterMerkl.sol\";\nimport { AbstractPoolBoosterFactory, IPoolBoostCentralRegistry } from \"./AbstractPoolBoosterFactory.sol\";\n\n/**\n * @title Pool booster factory for creating Merkl pool boosters.\n * @author Origin Protocol Inc\n */\ncontract PoolBoosterFactoryMerkl is AbstractPoolBoosterFactory {\n uint256 public constant version = 1;\n\n /// @notice address of the Merkl distributor\n address public merklDistributor;\n\n /// @notice event emitted when the Merkl distributor is updated\n event MerklDistributorUpdated(address newDistributor);\n\n /**\n * @param _oToken address of the OToken token\n * @param _governor address governor\n * @param _centralRegistry address of the central registry\n * @param _merklDistributor address of the Merkl distributor\n */\n constructor(\n address _oToken,\n address _governor,\n address _centralRegistry,\n address _merklDistributor\n ) AbstractPoolBoosterFactory(_oToken, _governor, _centralRegistry) {\n _setMerklDistributor(_merklDistributor);\n }\n\n /**\n * @dev Create a Pool Booster for Merkl.\n * @param _campaignType The type of campaign to create. This is used to determine the type of\n * bribe contract to create. The type is defined in the MerklDistributor contract.\n * @param _ammPoolAddress address of the AMM pool where the yield originates from\n * @param _campaignDuration The duration of the campaign in seconds\n * @param campaignData The data to be used for the campaign. This is used to determine the type of\n * bribe contract to create. The type is defined in the MerklDistributor contract.\n * This should be fetched from the Merkl UI.\n * @param _salt A unique number that affects the address of the pool booster created. Note: this number\n * should match the one from `computePoolBoosterAddress` in order for the final deployed address\n * and pre-computed address to match\n */\n function createPoolBoosterMerkl(\n uint32 _campaignType,\n address _ammPoolAddress,\n uint32 _campaignDuration,\n bytes calldata campaignData,\n uint256 _salt\n ) external onlyGovernor {\n require(\n _ammPoolAddress != address(0),\n \"Invalid ammPoolAddress address\"\n );\n require(_salt > 0, \"Invalid salt\");\n require(_campaignDuration > 1 hours, \"Invalid campaign duration\");\n require(campaignData.length > 0, \"Invalid campaign data\");\n\n address poolBoosterAddress = _deployContract(\n abi.encodePacked(\n type(PoolBoosterMerkl).creationCode,\n abi.encode(\n oToken,\n merklDistributor,\n _campaignDuration,\n _campaignType,\n governor(),\n campaignData\n )\n ),\n _salt\n );\n\n _storePoolBoosterEntry(\n poolBoosterAddress,\n _ammPoolAddress,\n IPoolBoostCentralRegistry.PoolBoosterType.MerklBooster\n );\n }\n\n /**\n * @dev Create a Pool Booster for Merkl.\n * @param _campaignType The type of campaign to create. This is used to determine the type of\n * bribe contract to create. The type is defined in the MerklDistributor contract.\n * @param _ammPoolAddress address of the AMM pool where the yield originates from\n * @param _salt A unique number that affects the address of the pool booster created. Note: this number\n * should match the one from `createPoolBoosterMerkl` in order for the final deployed address\n * and pre-computed address to match\n */\n function computePoolBoosterAddress(\n uint32 _campaignType,\n address _ammPoolAddress,\n uint32 _campaignDuration,\n bytes calldata campaignData,\n uint256 _salt\n ) external view returns (address) {\n require(\n _ammPoolAddress != address(0),\n \"Invalid ammPoolAddress address\"\n );\n require(_salt > 0, \"Invalid salt\");\n require(_campaignDuration > 1 hours, \"Invalid campaign duration\");\n require(campaignData.length > 0, \"Invalid campaign data\");\n\n return\n _computeAddress(\n abi.encodePacked(\n type(PoolBoosterMerkl).creationCode,\n abi.encode(\n oToken,\n merklDistributor,\n _campaignDuration,\n _campaignType,\n governor(),\n campaignData\n )\n ),\n _salt\n );\n }\n\n /**\n * @dev Set the address of the Merkl distributor\n * @param _merklDistributor The address of the Merkl distributor\n */\n function setMerklDistributor(address _merklDistributor)\n external\n onlyGovernor\n {\n _setMerklDistributor(_merklDistributor);\n }\n\n function _setMerklDistributor(address _merklDistributor) internal {\n require(\n _merklDistributor != address(0),\n \"Invalid merklDistributor address\"\n );\n merklDistributor = _merklDistributor;\n emit MerklDistributorUpdated(_merklDistributor);\n }\n}\n" + }, + "contracts/poolBooster/PoolBoosterFactoryMetropolis.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { PoolBoosterMetropolis } from \"./PoolBoosterMetropolis.sol\";\nimport { AbstractPoolBoosterFactory, IPoolBoostCentralRegistry } from \"./AbstractPoolBoosterFactory.sol\";\n\n/**\n * @title Pool booster factory for creating Metropolis pool boosters.\n * @author Origin Protocol Inc\n */\ncontract PoolBoosterFactoryMetropolis is AbstractPoolBoosterFactory {\n uint256 public constant version = 1;\n address public immutable rewardFactory;\n address public immutable voter;\n\n // @param address _oToken address of the OToken token\n // @param address _governor address governor\n // @param address _centralRegistry address of the central registry\n // @param address _rewardFactory address of the Metropolis reward factory\n // @param address _voter address of the Metropolis voter\n constructor(\n address _oToken,\n address _governor,\n address _centralRegistry,\n address _rewardFactory,\n address _voter\n ) AbstractPoolBoosterFactory(_oToken, _governor, _centralRegistry) {\n rewardFactory = _rewardFactory;\n voter = _voter;\n }\n\n /**\n * @dev Create a Pool Booster for Metropolis pool.\n * @param _ammPoolAddress address of the AMM pool where the yield originates from\n * @param _salt A unique number that affects the address of the pool booster created. Note: this number\n * should match the one from `computePoolBoosterAddress` in order for the final deployed address\n * and pre-computed address to match\n */\n function createPoolBoosterMetropolis(address _ammPoolAddress, uint256 _salt)\n external\n onlyGovernor\n {\n require(\n _ammPoolAddress != address(0),\n \"Invalid ammPoolAddress address\"\n );\n require(_salt > 0, \"Invalid salt\");\n\n address poolBoosterAddress = _deployContract(\n abi.encodePacked(\n type(PoolBoosterMetropolis).creationCode,\n abi.encode(oToken, rewardFactory, _ammPoolAddress, voter)\n ),\n _salt\n );\n\n _storePoolBoosterEntry(\n poolBoosterAddress,\n _ammPoolAddress,\n IPoolBoostCentralRegistry.PoolBoosterType.MetropolisBooster\n );\n }\n\n /**\n * @dev Create a Pool Booster for Metropolis pool.\n * @param _ammPoolAddress address of the AMM pool where the yield originates from\n * @param _salt A unique number that affects the address of the pool booster created. Note: this number\n * should match the one from `createPoolBoosterMetropolis` in order for the final deployed address\n * and pre-computed address to match\n */\n function computePoolBoosterAddress(address _ammPoolAddress, uint256 _salt)\n external\n view\n returns (address)\n {\n require(\n _ammPoolAddress != address(0),\n \"Invalid ammPoolAddress address\"\n );\n require(_salt > 0, \"Invalid salt\");\n\n return\n _computeAddress(\n abi.encodePacked(\n type(PoolBoosterMetropolis).creationCode,\n abi.encode(oToken, rewardFactory, _ammPoolAddress, voter)\n ),\n _salt\n );\n }\n}\n" + }, + "contracts/poolBooster/PoolBoosterFactorySwapxDouble.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { PoolBoosterSwapxDouble } from \"./PoolBoosterSwapxDouble.sol\";\nimport { AbstractPoolBoosterFactory, IPoolBoostCentralRegistry } from \"./AbstractPoolBoosterFactory.sol\";\n\n/**\n * @title Pool booster factory for creating Swapx Ichi pool boosters where both of the\n * gauges need incentivizing.\n * @author Origin Protocol Inc\n */\ncontract PoolBoosterFactorySwapxDouble is AbstractPoolBoosterFactory {\n uint256 public constant version = 1;\n\n // @param address _oToken address of the OToken token\n // @param address _governor address governor\n // @param address _centralRegistry address of the central registry\n constructor(\n address _oToken,\n address _governor,\n address _centralRegistry\n ) AbstractPoolBoosterFactory(_oToken, _governor, _centralRegistry) {}\n\n /**\n * @dev Create a Pool Booster for SwapX Ichi vault based pool where 2 Bribe contracts need to be\n * bribed\n * @param _bribeAddressOS address of the Bribes.sol(Bribe) contract for the OS token side\n * @param _bribeAddressOther address of the Bribes.sol(Bribe) contract for the other token in the pool\n * @param _ammPoolAddress address of the AMM pool where the yield originates from\n * @param _split 1e18 denominated split between OS and Other bribe. E.g. 0.4e17 means 40% to OS\n * bribe contract and 60% to other bribe contract\n * @param _salt A unique number that affects the address of the pool booster created. Note: this number\n * should match the one from `computePoolBoosterAddress` in order for the final deployed address\n * and pre-computed address to match\n */\n function createPoolBoosterSwapxDouble(\n address _bribeAddressOS,\n address _bribeAddressOther,\n address _ammPoolAddress,\n uint256 _split,\n uint256 _salt\n ) external onlyGovernor {\n require(\n _ammPoolAddress != address(0),\n \"Invalid ammPoolAddress address\"\n );\n require(_salt > 0, \"Invalid salt\");\n\n address poolBoosterAddress = _deployContract(\n abi.encodePacked(\n type(PoolBoosterSwapxDouble).creationCode,\n abi.encode(_bribeAddressOS, _bribeAddressOther, oToken, _split)\n ),\n _salt\n );\n\n _storePoolBoosterEntry(\n poolBoosterAddress,\n _ammPoolAddress,\n IPoolBoostCentralRegistry.PoolBoosterType.SwapXDoubleBooster\n );\n }\n\n /**\n * @dev Compute the address of the pool booster to be deployed.\n * @param _bribeAddressOS address of the Bribes.sol(Bribe) contract for the OS token side\n * @param _bribeAddressOther address of the Bribes.sol(Bribe) contract for the other token in the pool\n * @param _ammPoolAddress address of the AMM pool where the yield originates from\n * @param _split 1e18 denominated split between OS and Other bribe. E.g. 0.4e17 means 40% to OS\n * bribe contract and 60% to other bribe contract\n * @param _salt A unique number that affects the address of the pool booster created. Note: this number\n * should match the one from `createPoolBoosterSwapxDouble` in order for the final deployed address\n * and pre-computed address to match\n */\n function computePoolBoosterAddress(\n address _bribeAddressOS,\n address _bribeAddressOther,\n address _ammPoolAddress,\n uint256 _split,\n uint256 _salt\n ) external view returns (address) {\n require(\n _ammPoolAddress != address(0),\n \"Invalid ammPoolAddress address\"\n );\n require(_salt > 0, \"Invalid salt\");\n\n return\n _computeAddress(\n abi.encodePacked(\n type(PoolBoosterSwapxDouble).creationCode,\n abi.encode(\n _bribeAddressOS,\n _bribeAddressOther,\n oToken,\n _split\n )\n ),\n _salt\n );\n }\n}\n" + }, + "contracts/poolBooster/PoolBoosterFactorySwapxSingle.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { PoolBoosterSwapxSingle } from \"./PoolBoosterSwapxSingle.sol\";\nimport { AbstractPoolBoosterFactory, IPoolBoostCentralRegistry } from \"./AbstractPoolBoosterFactory.sol\";\n\n/**\n * @title Pool booster factory for creating Swapx Single pool boosters - where a single\n * gauge is created for a pool. this is appropriate for Classic Stable & Classic\n * Volatile SwapX pools.\n * @author Origin Protocol Inc\n */\ncontract PoolBoosterFactorySwapxSingle is AbstractPoolBoosterFactory {\n uint256 public constant version = 1;\n\n // @param address _oToken address of the OToken token\n // @param address _governor address governor\n // @param address _centralRegistry address of the central registry\n constructor(\n address _oToken,\n address _governor,\n address _centralRegistry\n ) AbstractPoolBoosterFactory(_oToken, _governor, _centralRegistry) {}\n\n /**\n * @dev Create a Pool Booster for SwapX classic volatile or classic stable pools where\n * a single Bribe contract is incentivized.\n * @param _bribeAddress address of the Bribes.sol contract\n * @param _ammPoolAddress address of the AMM pool where the yield originates from\n * @param _salt A unique number that affects the address of the pool booster created. Note: this number\n * should match the one from `computePoolBoosterAddress` in order for the final deployed address\n * and pre-computed address to match\n */\n function createPoolBoosterSwapxSingle(\n address _bribeAddress,\n address _ammPoolAddress,\n uint256 _salt\n ) external onlyGovernor {\n require(\n _ammPoolAddress != address(0),\n \"Invalid ammPoolAddress address\"\n );\n require(_salt > 0, \"Invalid salt\");\n\n address poolBoosterAddress = _deployContract(\n abi.encodePacked(\n type(PoolBoosterSwapxSingle).creationCode,\n abi.encode(_bribeAddress, oToken)\n ),\n _salt\n );\n\n _storePoolBoosterEntry(\n poolBoosterAddress,\n _ammPoolAddress,\n IPoolBoostCentralRegistry.PoolBoosterType.SwapXSingleBooster\n );\n }\n\n /**\n * @dev Create a Pool Booster for SwapX classic volatile or classic stable pools where\n * a single Bribe contract is incentivized.\n * @param _bribeAddress address of the Bribes.sol contract\n * @param _ammPoolAddress address of the AMM pool where the yield originates from\n * @param _salt A unique number that affects the address of the pool booster created. Note: this number\n * should match the one from `createPoolBoosterSwapxSingle` in order for the final deployed address\n * and pre-computed address to match\n */\n function computePoolBoosterAddress(\n address _bribeAddress,\n address _ammPoolAddress,\n uint256 _salt\n ) external view returns (address) {\n require(\n _ammPoolAddress != address(0),\n \"Invalid ammPoolAddress address\"\n );\n require(_salt > 0, \"Invalid salt\");\n\n return\n _computeAddress(\n abi.encodePacked(\n type(PoolBoosterSwapxSingle).creationCode,\n abi.encode(_bribeAddress, oToken)\n ),\n _salt\n );\n }\n}\n" + }, + "contracts/poolBooster/PoolBoosterMerkl.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport { IPoolBooster } from \"../interfaces/poolBooster/IPoolBooster.sol\";\nimport { IMerklDistributor } from \"../interfaces/poolBooster/IMerklDistributor.sol\";\n\ninterface IERC1271 {\n /**\n * @dev Should return whether the signature provided is valid for the provided data\n * @param hash Hash of the data to be signed\n * @param signature Signature byte array associated with _data\n */\n function isValidSignature(bytes32 hash, bytes memory signature)\n external\n view\n returns (bytes4 magicValue);\n}\n\n/**\n * @title Pool booster for Merkl distributor\n * @author Origin Protocol Inc\n */\ncontract PoolBoosterMerkl is IPoolBooster, IERC1271 {\n /// @notice address of merkl distributor\n IMerklDistributor public immutable merklDistributor;\n /// @notice address of the OS token\n IERC20 public immutable rewardToken;\n /// @notice if balance under this amount the bribe action is skipped\n uint256 public constant MIN_BRIBE_AMOUNT = 1e10;\n /// @notice Campaign duration in seconds\n uint32 public immutable duration; // -> should be immutable\n /// @notice Campaign type\n uint32 public immutable campaignType;\n /// @notice Owner of the campaign\n address public immutable creator;\n /// @notice Campaign data\n bytes public campaignData;\n\n constructor(\n address _rewardToken,\n address _merklDistributor,\n uint32 _duration,\n uint32 _campaignType,\n address _creator,\n bytes memory _campaignData\n ) {\n require(_rewardToken != address(0), \"Invalid rewardToken address\");\n require(\n _merklDistributor != address(0),\n \"Invalid merklDistributor address\"\n );\n require(_campaignData.length > 0, \"Invalid campaignData\");\n require(_duration > 1 hours, \"Invalid duration\");\n\n campaignType = _campaignType;\n duration = _duration;\n creator = _creator;\n\n merklDistributor = IMerklDistributor(_merklDistributor);\n rewardToken = IERC20(_rewardToken);\n campaignData = _campaignData;\n }\n\n /// @notice Create a campaign on the Merkl distributor\n function bribe() external override {\n // Ensure token is approved for the Merkl distributor\n uint256 minAmount = merklDistributor.rewardTokenMinAmounts(\n address(rewardToken)\n );\n require(minAmount > 0, \"Min reward amount must be > 0\");\n\n // if balance too small or below threshold, do no bribes\n uint256 balance = rewardToken.balanceOf(address(this));\n if (\n balance < MIN_BRIBE_AMOUNT ||\n (balance * 1 hours < minAmount * duration)\n ) {\n return;\n }\n\n // Approve the bribe contract to spend the reward token\n rewardToken.approve(address(merklDistributor), balance);\n\n // Notify the bribe contract of the reward amount\n merklDistributor.signAndCreateCampaign(\n IMerklDistributor.CampaignParameters({\n campaignId: bytes32(0),\n creator: creator,\n rewardToken: address(rewardToken),\n amount: balance,\n campaignType: campaignType,\n startTimestamp: getNextPeriodStartTime(),\n duration: duration,\n campaignData: campaignData\n }),\n bytes(\"\")\n );\n emit BribeExecuted(balance);\n }\n\n /// @notice Used to sign a campaign on the Merkl distributor\n function isValidSignature(bytes32, bytes memory)\n external\n view\n override\n returns (bytes4 magicValue)\n {\n require(msg.sender == address(merklDistributor), \"Invalid sender\");\n // bytes4(keccak256(\"isValidSignature(bytes32,bytes)\")) == 0x1626ba7e\n return bytes4(0x1626ba7e);\n }\n\n /// @notice Returns the timestamp for the start of the next period based on the configured duration\n function getNextPeriodStartTime() public view returns (uint32) {\n // Calculate the timestamp for the next period boundary\n return uint32((block.timestamp / duration + 1) * duration);\n }\n}\n" + }, + "contracts/poolBooster/PoolBoosterMetropolis.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { IPoolBooster } from \"../interfaces/poolBooster/IPoolBooster.sol\";\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\n\n/**\n * @title Pool booster for Metropolis pools\n * @author Origin Protocol Inc\n */\ncontract PoolBoosterMetropolis is IPoolBooster {\n // @notice address of the OS token\n IERC20 public immutable osToken;\n // @notice address of the pool\n address public immutable pool;\n // @notice if balance under this amount the bribe action is skipped\n uint256 public constant MIN_BRIBE_AMOUNT = 1e10;\n\n IRewarderFactory public immutable rewardFactory;\n\n IVoter public immutable voter;\n\n constructor(\n address _osToken,\n address _rewardFactory,\n address _pool,\n address _voter\n ) {\n require(_pool != address(0), \"Invalid pool address\");\n pool = _pool;\n // Abstract factory already validates this is not a zero address\n osToken = IERC20(_osToken);\n\n rewardFactory = IRewarderFactory(_rewardFactory);\n\n voter = IVoter(_voter);\n }\n\n function bribe() external override {\n uint256 balance = osToken.balanceOf(address(this));\n // balance too small, do no bribes\n (, uint256 minBribeAmount) = rewardFactory.getWhitelistedTokenInfo(\n address(osToken)\n );\n if (balance < MIN_BRIBE_AMOUNT || balance < minBribeAmount) {\n return;\n }\n\n uint256 id = voter.getCurrentVotingPeriod() + 1;\n\n // Deploy a rewarder\n IRewarder rewarder = IRewarder(\n rewardFactory.createBribeRewarder(address(osToken), pool)\n );\n\n // Approve the rewarder to spend the balance\n osToken.approve(address(rewarder), balance);\n\n // Fund and bribe the rewarder\n rewarder.fundAndBribe(id, id, balance);\n\n emit BribeExecuted(balance);\n }\n}\n\ninterface IRewarderFactory {\n function createBribeRewarder(address token, address pool)\n external\n returns (address rewarder);\n\n function getWhitelistedTokenInfo(address token)\n external\n view\n returns (bool isWhitelisted, uint256 minBribeAmount);\n}\n\ninterface IRewarder {\n function fundAndBribe(\n uint256 startId,\n uint256 lastId,\n uint256 amountPerPeriod\n ) external payable;\n}\n\ninterface IVoter {\n function getCurrentVotingPeriod() external view returns (uint256);\n}\n" + }, + "contracts/poolBooster/PoolBoosterSwapxDouble.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { IBribe } from \"../interfaces/poolBooster/ISwapXAlgebraBribe.sol\";\nimport { IPoolBooster } from \"../interfaces/poolBooster/IPoolBooster.sol\";\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport { StableMath } from \"../utils/StableMath.sol\";\n\n/**\n * @title Pool booster for SwapX concentrated liquidity where 2 gauges are created for\n * every pool. Ichi vaults currently have such setup.\n * @author Origin Protocol Inc\n */\ncontract PoolBoosterSwapxDouble is IPoolBooster {\n using StableMath for uint256;\n\n // @notice address of the Bribes.sol(Bribe) contract for the OS token side\n IBribe public immutable bribeContractOS;\n // @notice address of the Bribes.sol(Bribe) contract for the other token in the pool\n IBribe public immutable bribeContractOther;\n // @notice address of the OS token\n IERC20 public immutable osToken;\n // @notice 1e18 denominated split between OS and Other bribe. E.g. 0.4e17 means 40% to OS\n // bribe contract and 60% to other bribe contract\n uint256 public immutable split;\n\n // @notice if balance under this amount the bribe action is skipped\n uint256 public constant MIN_BRIBE_AMOUNT = 1e10;\n\n constructor(\n address _bribeContractOS,\n address _bribeContractOther,\n address _osToken,\n uint256 _split\n ) {\n require(\n _bribeContractOS != address(0),\n \"Invalid bribeContractOS address\"\n );\n require(\n _bribeContractOther != address(0),\n \"Invalid bribeContractOther address\"\n );\n // expect it to be between 1% & 99%\n require(_split > 1e16 && _split < 99e16, \"Unexpected split amount\");\n\n bribeContractOS = IBribe(_bribeContractOS);\n bribeContractOther = IBribe(_bribeContractOther);\n // Abstract factory already validates this is not a zero address\n osToken = IERC20(_osToken);\n split = _split;\n }\n\n function bribe() external override {\n uint256 balance = osToken.balanceOf(address(this));\n // balance too small, do no bribes\n if (balance < MIN_BRIBE_AMOUNT) {\n return;\n }\n\n uint256 osBribeAmount = balance.mulTruncate(split);\n uint256 otherBribeAmount = balance - osBribeAmount;\n\n osToken.approve(address(bribeContractOS), osBribeAmount);\n osToken.approve(address(bribeContractOther), otherBribeAmount);\n\n bribeContractOS.notifyRewardAmount(address(osToken), osBribeAmount);\n bribeContractOther.notifyRewardAmount(\n address(osToken),\n otherBribeAmount\n );\n\n emit BribeExecuted(balance);\n }\n}\n" + }, + "contracts/poolBooster/PoolBoosterSwapxSingle.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { IBribe } from \"../interfaces/poolBooster/ISwapXAlgebraBribe.sol\";\nimport { IPoolBooster } from \"../interfaces/poolBooster/IPoolBooster.sol\";\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\n\n/**\n * @title Pool booster for SwapX for Classic Stable Pools and Classic Volatile Pools\n * @author Origin Protocol Inc\n */\ncontract PoolBoosterSwapxSingle is IPoolBooster {\n // @notice address of the Bribes.sol(Bribe) contract\n IBribe public immutable bribeContract;\n // @notice address of the OS token\n IERC20 public immutable osToken;\n // @notice if balance under this amount the bribe action is skipped\n uint256 public constant MIN_BRIBE_AMOUNT = 1e10;\n\n constructor(address _bribeContract, address _osToken) {\n require(_bribeContract != address(0), \"Invalid bribeContract address\");\n bribeContract = IBribe(_bribeContract);\n // Abstract factory already validates this is not a zero address\n osToken = IERC20(_osToken);\n }\n\n function bribe() external override {\n uint256 balance = osToken.balanceOf(address(this));\n // balance too small, do no bribes\n if (balance < MIN_BRIBE_AMOUNT) {\n return;\n }\n\n osToken.approve(address(bribeContract), balance);\n\n bribeContract.notifyRewardAmount(address(osToken), balance);\n emit BribeExecuted(balance);\n }\n}\n" + }, + "contracts/proxies/create2/CrossChainStrategyProxy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { InitializeGovernedUpgradeabilityProxy2 } from \"../InitializeGovernedUpgradeabilityProxy2.sol\";\n\n// ********************************************************\n// ********************************************************\n// IMPORTANT: DO NOT CHANGE ANYTHING IN THIS FILE.\n// Any changes to this file (even whitespaces) will\n// affect the create2 address of the proxy\n// ********************************************************\n// ********************************************************\n\n/**\n * @notice CrossChainStrategyProxy delegates calls to a\n * CrossChainMasterStrategy or CrossChainRemoteStrategy\n * implementation contract.\n */\ncontract CrossChainStrategyProxy is InitializeGovernedUpgradeabilityProxy2 {\n constructor(address governor)\n InitializeGovernedUpgradeabilityProxy2(governor)\n {}\n}\n" + }, + "contracts/proxies/InitializeGovernedUpgradeabilityProxy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { Address } from \"@openzeppelin/contracts/utils/Address.sol\";\n\nimport { Governable } from \"../governance/Governable.sol\";\n\n/**\n * @title BaseGovernedUpgradeabilityProxy\n * @dev This contract combines an upgradeability proxy with our governor system.\n * It is based on an older version of OpenZeppelins BaseUpgradeabilityProxy\n * with Solidity ^0.8.0.\n * @author Origin Protocol Inc\n */\ncontract InitializeGovernedUpgradeabilityProxy is Governable {\n /**\n * @dev Emitted when the implementation is upgraded.\n * @param implementation Address of the new implementation.\n */\n event Upgraded(address indexed implementation);\n\n constructor() {\n _setGovernor(msg.sender);\n }\n\n /**\n * @dev Contract initializer with Governor enforcement\n * @param _logic Address of the initial implementation.\n * @param _initGovernor Address of the initial Governor.\n * @param _data Data to send as msg.data to the implementation to initialize\n * the proxied contract.\n * It should include the signature and the parameters of the function to be\n * called, as described in\n * https://solidity.readthedocs.io/en/v0.4.24/abi-spec.html#function-selector-and-argument-encoding.\n * This parameter is optional, if no data is given the initialization call\n * to proxied contract will be skipped.\n */\n function initialize(\n address _logic,\n address _initGovernor,\n bytes calldata _data\n ) public payable onlyGovernor {\n require(_implementation() == address(0));\n require(_logic != address(0), \"Implementation not set\");\n assert(\n IMPLEMENTATION_SLOT ==\n bytes32(uint256(keccak256(\"eip1967.proxy.implementation\")) - 1)\n );\n _setImplementation(_logic);\n if (_data.length > 0) {\n (bool success, ) = _logic.delegatecall(_data);\n require(success);\n }\n _changeGovernor(_initGovernor);\n }\n\n /**\n * @return The address of the proxy admin/it's also the governor.\n */\n function admin() external view returns (address) {\n return _governor();\n }\n\n /**\n * @return The address of the implementation.\n */\n function implementation() external view returns (address) {\n return _implementation();\n }\n\n /**\n * @dev Upgrade the backing implementation of the proxy.\n * Only the admin can call this function.\n * @param _newImplementation Address of the new implementation.\n */\n function upgradeTo(address _newImplementation) external onlyGovernor {\n _upgradeTo(_newImplementation);\n }\n\n /**\n * @dev Upgrade the backing implementation of the proxy and call a function\n * on the new implementation.\n * This is useful to initialize the proxied contract.\n * @param newImplementation Address of the new implementation.\n * @param data Data to send as msg.data in the low level call.\n * It should include the signature and the parameters of the function to be called, as described in\n * https://solidity.readthedocs.io/en/v0.4.24/abi-spec.html#function-selector-and-argument-encoding.\n */\n function upgradeToAndCall(address newImplementation, bytes calldata data)\n external\n payable\n onlyGovernor\n {\n _upgradeTo(newImplementation);\n (bool success, ) = newImplementation.delegatecall(data);\n require(success);\n }\n\n /**\n * @dev Fallback function.\n * Implemented entirely in `_fallback`.\n */\n fallback() external payable {\n _fallback();\n }\n\n /**\n * @dev Delegates execution to an implementation contract.\n * This is a low level function that doesn't return to its internal call site.\n * It will return to the external caller whatever the implementation returns.\n * @param _impl Address to delegate.\n */\n function _delegate(address _impl) internal {\n // solhint-disable-next-line no-inline-assembly\n assembly {\n // Copy msg.data. We take full control of memory in this inline assembly\n // block because it will not return to Solidity code. We overwrite the\n // Solidity scratch pad at memory position 0.\n calldatacopy(0, 0, calldatasize())\n\n // Call the implementation.\n // out and outsize are 0 because we don't know the size yet.\n let result := delegatecall(gas(), _impl, 0, calldatasize(), 0, 0)\n\n // Copy the returned data.\n returndatacopy(0, 0, returndatasize())\n\n switch result\n // delegatecall returns 0 on error.\n case 0 {\n revert(0, returndatasize())\n }\n default {\n return(0, returndatasize())\n }\n }\n }\n\n /**\n * @dev Function that is run as the first thing in the fallback function.\n * Can be redefined in derived contracts to add functionality.\n * Redefinitions must call super._willFallback().\n */\n function _willFallback() internal {}\n\n /**\n * @dev fallback implementation.\n * Extracted to enable manual triggering.\n */\n function _fallback() internal {\n _willFallback();\n _delegate(_implementation());\n }\n\n /**\n * @dev Storage slot with the address of the current implementation.\n * This is the keccak-256 hash of \"eip1967.proxy.implementation\" subtracted by 1, and is\n * validated in the constructor.\n */\n bytes32 internal constant IMPLEMENTATION_SLOT =\n 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;\n\n /**\n * @dev Returns the current implementation.\n * @return impl Address of the current implementation\n */\n function _implementation() internal view returns (address impl) {\n bytes32 slot = IMPLEMENTATION_SLOT;\n // solhint-disable-next-line no-inline-assembly\n assembly {\n impl := sload(slot)\n }\n }\n\n /**\n * @dev Upgrades the proxy to a new implementation.\n * @param newImplementation Address of the new implementation.\n */\n function _upgradeTo(address newImplementation) internal {\n _setImplementation(newImplementation);\n emit Upgraded(newImplementation);\n }\n\n /**\n * @dev Sets the implementation address of the proxy.\n * @param newImplementation Address of the new implementation.\n */\n function _setImplementation(address newImplementation) internal {\n require(\n Address.isContract(newImplementation),\n \"Cannot set a proxy implementation to a non-contract address\"\n );\n\n bytes32 slot = IMPLEMENTATION_SLOT;\n\n // solhint-disable-next-line no-inline-assembly\n assembly {\n sstore(slot, newImplementation)\n }\n }\n}\n" + }, + "contracts/proxies/InitializeGovernedUpgradeabilityProxy2.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { InitializeGovernedUpgradeabilityProxy } from \"./InitializeGovernedUpgradeabilityProxy.sol\";\n\n/**\n * @title BaseGovernedUpgradeabilityProxy2\n * @dev This is the same as InitializeGovernedUpgradeabilityProxy except that the\n * governor is defined in the constructor.\n * @author Origin Protocol Inc\n */\ncontract InitializeGovernedUpgradeabilityProxy2 is\n InitializeGovernedUpgradeabilityProxy\n{\n /**\n * This is used when the msg.sender can not be the governor. E.g. when the proxy is\n * deployed via CreateX\n */\n constructor(address governor) InitializeGovernedUpgradeabilityProxy() {\n _setGovernor(governor);\n }\n}\n" + }, + "contracts/proxies/Proxies.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { InitializeGovernedUpgradeabilityProxy } from \"./InitializeGovernedUpgradeabilityProxy.sol\";\nimport { InitializeGovernedUpgradeabilityProxy2 } from \"./InitializeGovernedUpgradeabilityProxy2.sol\";\n\n/**\n * @notice OUSDProxy delegates calls to an OUSD implementation\n */\ncontract OUSDProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice WrappedOUSDProxy delegates calls to a WrappedOUSD implementation\n */\ncontract WrappedOUSDProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice VaultProxy delegates calls to a Vault implementation\n */\ncontract VaultProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice CompoundStrategyProxy delegates calls to a CompoundStrategy implementation\n */\ncontract CompoundStrategyProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice AaveStrategyProxy delegates calls to a AaveStrategy implementation\n */\ncontract AaveStrategyProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice ConvexStrategyProxy delegates calls to a ConvexStrategy implementation\n */\ncontract ConvexStrategyProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice HarvesterProxy delegates calls to a Harvester implementation\n */\ncontract HarvesterProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice DripperProxy delegates calls to a Dripper implementation\n */\ncontract DripperProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice MorphoCompoundStrategyProxy delegates calls to a MorphoCompoundStrategy implementation\n */\ncontract MorphoCompoundStrategyProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice ConvexOUSDMetaStrategyProxy delegates calls to a ConvexOUSDMetaStrategy implementation\n */\ncontract ConvexOUSDMetaStrategyProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice MorphoAaveStrategyProxy delegates calls to a MorphoCompoundStrategy implementation\n */\ncontract MorphoAaveStrategyProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice OETHProxy delegates calls to nowhere for now\n */\ncontract OETHProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice WOETHProxy delegates calls to nowhere for now\n */\ncontract WOETHProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice OETHVaultProxy delegates calls to a Vault implementation\n */\ncontract OETHVaultProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice OETHDripperProxy delegates calls to a OETHDripper implementation\n */\ncontract OETHDripperProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice OETHHarvesterProxy delegates calls to a Harvester implementation\n */\ncontract OETHHarvesterProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice CurveEthStrategyProxy delegates calls to a CurveEthStrategy implementation\n */\ncontract ConvexEthMetaStrategyProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice BuybackProxy delegates calls to Buyback implementation\n */\ncontract BuybackProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice OETHMorphoAaveStrategyProxy delegates calls to a MorphoAaveStrategy implementation\n */\ncontract OETHMorphoAaveStrategyProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice OETHBalancerMetaPoolrEthStrategyProxy delegates calls to a BalancerMetaPoolStrategy implementation\n */\ncontract OETHBalancerMetaPoolrEthStrategyProxy is\n InitializeGovernedUpgradeabilityProxy\n{\n\n}\n\n/**\n * @notice OETHBalancerMetaPoolwstEthStrategyProxy delegates calls to a BalancerMetaPoolStrategy implementation\n */\ncontract OETHBalancerMetaPoolwstEthStrategyProxy is\n InitializeGovernedUpgradeabilityProxy\n{\n\n}\n\n/**\n * @notice MakerDsrStrategyProxy delegates calls to a Generalized4626Strategy implementation\n */\ncontract MakerDsrStrategyProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice OETHBuybackProxy delegates calls to Buyback implementation\n */\ncontract OETHBuybackProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice BridgedWOETHProxy delegates calls to BridgedWOETH implementation\n */\ncontract BridgedWOETHProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice NativeStakingSSVStrategyProxy delegates calls to NativeStakingSSVStrategy implementation\n */\ncontract NativeStakingSSVStrategyProxy is\n InitializeGovernedUpgradeabilityProxy\n{\n\n}\n\n/**\n * @notice NativeStakingFeeAccumulatorProxy delegates calls to FeeAccumulator implementation\n */\ncontract NativeStakingFeeAccumulatorProxy is\n InitializeGovernedUpgradeabilityProxy\n{\n\n}\n\n/**\n * @notice NativeStakingSSVStrategy2Proxy delegates calls to NativeStakingSSVStrategy implementation\n */\ncontract NativeStakingSSVStrategy2Proxy is\n InitializeGovernedUpgradeabilityProxy\n{\n\n}\n\n/**\n * @notice NativeStakingFeeAccumulator2Proxy delegates calls to FeeAccumulator implementation\n */\ncontract NativeStakingFeeAccumulator2Proxy is\n InitializeGovernedUpgradeabilityProxy\n{\n\n}\n\n/**\n * @notice NativeStakingSSVStrategy3Proxy delegates calls to NativeStakingSSVStrategy implementation\n */\ncontract NativeStakingSSVStrategy3Proxy is\n InitializeGovernedUpgradeabilityProxy\n{\n\n}\n\n/**\n * @notice NativeStakingFeeAccumulator3Proxy delegates calls to FeeAccumulator implementation\n */\ncontract NativeStakingFeeAccumulator3Proxy is\n InitializeGovernedUpgradeabilityProxy\n{\n\n}\n\n/**\n * @notice MetaMorphoStrategyProxy delegates calls to a Generalized4626Strategy implementation\n */\ncontract MetaMorphoStrategyProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice ARMBuybackProxy delegates calls to Buyback implementation\n */\ncontract ARMBuybackProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice MorphoGauntletPrimeUSDCStrategyProxy delegates calls to a Generalized4626Strategy implementation\n */\ncontract MorphoGauntletPrimeUSDCStrategyProxy is\n InitializeGovernedUpgradeabilityProxy\n{\n\n}\n\n/**\n * @notice MorphoGauntletPrimeUSDTStrategyProxy delegates calls to a Generalized4626USDTStrategy implementation\n */\ncontract MorphoGauntletPrimeUSDTStrategyProxy is\n InitializeGovernedUpgradeabilityProxy\n{\n\n}\n\n/**\n * @notice CurvePoolBoosterProxy delegates calls to a CurvePoolBooster implementation\n */\ncontract CurvePoolBoosterProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice OETHFixedRateDripperProxy delegates calls to a OETHFixedRateDripper implementation\n */\ncontract OETHFixedRateDripperProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice OETHSimpleHarvesterProxy delegates calls to a OETHSimpleHarvester implementation\n */\ncontract OETHSimpleHarvesterProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice PoolBoostCentralRegistryProxy delegates calls to the PoolBoostCentralRegistry implementation\n */\ncontract PoolBoostCentralRegistryProxy is\n InitializeGovernedUpgradeabilityProxy\n{\n\n}\n\n/**\n * @notice MakerSSRStrategyProxy delegates calls to a Generalized4626Strategy implementation\n */\ncontract MakerSSRStrategyProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice OUSDCurveAMOProxy delegates calls to a CurveAMOStrategy implementation\n */\ncontract OUSDCurveAMOProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice OETHCurveAMOProxy delegates calls to a CurveAMOStrategy implementation\n */\ncontract OETHCurveAMOProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice CompoundingStakingSSVStrategyProxy delegates calls to a CompoundingStakingSSVStrategy implementation\n */\ncontract CompoundingStakingSSVStrategyProxy is\n InitializeGovernedUpgradeabilityProxy\n{\n\n}\n\n/**\n * @notice OUSDMorphoV2StrategyProxy delegates calls to a Generalized4626Strategy implementation\n */\ncontract OUSDMorphoV2StrategyProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n" + }, + "contracts/strategies/AaveStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title OUSD Aave Strategy\n * @notice Investment strategy for investing stablecoins via Aave\n * @author Origin Protocol Inc\n */\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\n\nimport \"./IAave.sol\";\nimport { IERC20, InitializableAbstractStrategy } from \"../utils/InitializableAbstractStrategy.sol\";\n\nimport { IAaveStakedToken } from \"./IAaveStakeToken.sol\";\nimport { IAaveIncentivesController } from \"./IAaveIncentivesController.sol\";\n\ncontract AaveStrategy is InitializableAbstractStrategy {\n using SafeERC20 for IERC20;\n\n uint16 constant referralCode = 92;\n\n IAaveIncentivesController public incentivesController;\n IAaveStakedToken public stkAave;\n\n /**\n * @param _stratConfig The platform and OToken vault addresses\n */\n constructor(BaseStrategyConfig memory _stratConfig)\n InitializableAbstractStrategy(_stratConfig)\n {}\n\n /**\n * Initializer for setting up strategy internal state. This overrides the\n * InitializableAbstractStrategy initializer as AAVE needs several extra\n * addresses for the rewards program.\n * @param _rewardTokenAddresses Address of the AAVE token\n * @param _assets Addresses of supported assets\n * @param _pTokens Platform Token corresponding addresses\n * @param _incentivesAddress Address of the AAVE incentives controller\n * @param _stkAaveAddress Address of the stkAave contract\n */\n function initialize(\n address[] calldata _rewardTokenAddresses, // AAVE\n address[] calldata _assets,\n address[] calldata _pTokens,\n address _incentivesAddress,\n address _stkAaveAddress\n ) external onlyGovernor initializer {\n incentivesController = IAaveIncentivesController(_incentivesAddress);\n stkAave = IAaveStakedToken(_stkAaveAddress);\n InitializableAbstractStrategy._initialize(\n _rewardTokenAddresses,\n _assets,\n _pTokens\n );\n }\n\n /**\n * @dev Deposit asset into Aave\n * @param _asset Address of asset to deposit\n * @param _amount Amount of asset to deposit\n */\n function deposit(address _asset, uint256 _amount)\n external\n override\n onlyVault\n nonReentrant\n {\n _deposit(_asset, _amount);\n }\n\n /**\n * @dev Deposit asset into Aave\n * @param _asset Address of asset to deposit\n * @param _amount Amount of asset to deposit\n */\n function _deposit(address _asset, uint256 _amount) internal {\n require(_amount > 0, \"Must deposit something\");\n // Following line also doubles as a check that we are depositing\n // an asset that we support.\n emit Deposit(_asset, _getATokenFor(_asset), _amount);\n _getLendingPool().deposit(_asset, _amount, address(this), referralCode);\n }\n\n /**\n * @dev Deposit the entire balance of any supported asset into Aave\n */\n function depositAll() external override onlyVault nonReentrant {\n for (uint256 i = 0; i < assetsMapped.length; i++) {\n uint256 balance = IERC20(assetsMapped[i]).balanceOf(address(this));\n if (balance > 0) {\n _deposit(assetsMapped[i], balance);\n }\n }\n }\n\n /**\n * @dev Withdraw asset from Aave\n * @param _recipient Address to receive withdrawn asset\n * @param _asset Address of asset to withdraw\n * @param _amount Amount of asset to withdraw\n */\n function withdraw(\n address _recipient,\n address _asset,\n uint256 _amount\n ) external override onlyVault nonReentrant {\n require(_amount > 0, \"Must withdraw something\");\n require(_recipient != address(0), \"Must specify recipient\");\n\n emit Withdrawal(_asset, _getATokenFor(_asset), _amount);\n uint256 actual = _getLendingPool().withdraw(\n _asset,\n _amount,\n address(this)\n );\n require(actual == _amount, \"Did not withdraw enough\");\n IERC20(_asset).safeTransfer(_recipient, _amount);\n }\n\n /**\n * @dev Remove all assets from platform and send them to Vault contract.\n */\n function withdrawAll() external override onlyVaultOrGovernor nonReentrant {\n for (uint256 i = 0; i < assetsMapped.length; i++) {\n // Redeem entire balance of aToken\n IERC20 asset = IERC20(assetsMapped[i]);\n address aToken = _getATokenFor(assetsMapped[i]);\n uint256 balance = IERC20(aToken).balanceOf(address(this));\n if (balance > 0) {\n uint256 actual = _getLendingPool().withdraw(\n address(asset),\n balance,\n address(this)\n );\n require(actual == balance, \"Did not withdraw enough\");\n\n uint256 assetBalance = asset.balanceOf(address(this));\n // Transfer entire balance to Vault\n asset.safeTransfer(vaultAddress, assetBalance);\n\n emit Withdrawal(address(asset), aToken, assetBalance);\n }\n }\n }\n\n /**\n * @dev Get the total asset value held in the platform\n * @param _asset Address of the asset\n * @return balance Total value of the asset in the platform\n */\n function checkBalance(address _asset)\n external\n view\n override\n returns (uint256 balance)\n {\n // Balance is always with token aToken decimals\n address aToken = _getATokenFor(_asset);\n balance = IERC20(aToken).balanceOf(address(this));\n }\n\n /**\n * @dev Returns bool indicating whether asset is supported by strategy\n * @param _asset Address of the asset\n */\n function supportsAsset(address _asset) public view override returns (bool) {\n return assetToPToken[_asset] != address(0);\n }\n\n /**\n * @dev Approve the spending of all assets by their corresponding aToken,\n * if for some reason is it necessary.\n */\n function safeApproveAllTokens()\n external\n override\n onlyGovernor\n nonReentrant\n {\n address lendingPool = address(_getLendingPool());\n // approve the pool to spend the Asset\n for (uint256 i = 0; i < assetsMapped.length; i++) {\n address asset = assetsMapped[i];\n // Safe approval\n IERC20(asset).safeApprove(lendingPool, 0);\n IERC20(asset).safeApprove(lendingPool, type(uint256).max);\n }\n }\n\n /**\n * @dev Internal method to respond to the addition of new asset / aTokens\n We need to give the AAVE lending pool approval to transfer the\n asset.\n * @param _asset Address of the asset to approve\n * @param _aToken Address of the aToken\n */\n // solhint-disable-next-line no-unused-vars\n function _abstractSetPToken(address _asset, address _aToken)\n internal\n override\n {\n address lendingPool = address(_getLendingPool());\n IERC20(_asset).safeApprove(lendingPool, 0);\n IERC20(_asset).safeApprove(lendingPool, type(uint256).max);\n }\n\n /**\n * @dev Get the aToken wrapped in the IERC20 interface for this asset.\n * Fails if the pToken doesn't exist in our mappings.\n * @param _asset Address of the asset\n * @return Corresponding aToken to this asset\n */\n function _getATokenFor(address _asset) internal view returns (address) {\n address aToken = assetToPToken[_asset];\n require(aToken != address(0), \"aToken does not exist\");\n return aToken;\n }\n\n /**\n * @dev Get the current address of the Aave lending pool, which is the gateway to\n * depositing.\n * @return Current lending pool implementation\n */\n function _getLendingPool() internal view returns (IAaveLendingPool) {\n address lendingPool = ILendingPoolAddressesProvider(platformAddress)\n .getLendingPool();\n require(lendingPool != address(0), \"Lending pool does not exist\");\n return IAaveLendingPool(lendingPool);\n }\n\n /**\n * @dev Collect stkAave, convert it to AAVE send to Vault.\n */\n function collectRewardTokens()\n external\n override\n onlyHarvester\n nonReentrant\n {\n if (address(stkAave) == address(0)) {\n return;\n }\n\n // Check staked AAVE cooldown timer\n uint256 cooldown = stkAave.stakersCooldowns(address(this));\n uint256 windowStart = cooldown + stkAave.COOLDOWN_SECONDS();\n uint256 windowEnd = windowStart + stkAave.UNSTAKE_WINDOW();\n\n // If inside the unlock window, then we can redeem stkAave\n // for AAVE and send it to the vault.\n if (block.timestamp > windowStart && block.timestamp <= windowEnd) {\n // Redeem to AAVE\n uint256 stkAaveBalance = stkAave.balanceOf(address(this));\n stkAave.redeem(address(this), stkAaveBalance);\n\n // Transfer AAVE to harvesterAddress\n uint256 aaveBalance = IERC20(rewardTokenAddresses[0]).balanceOf(\n address(this)\n );\n if (aaveBalance > 0) {\n IERC20(rewardTokenAddresses[0]).safeTransfer(\n harvesterAddress,\n aaveBalance\n );\n }\n }\n\n // Collect available rewards and restart the cooldown timer, if either of\n // those should be run.\n if (block.timestamp > windowStart || cooldown == 0) {\n uint256 assetsLen = assetsMapped.length;\n // aToken addresses for incentives controller\n address[] memory aTokens = new address[](assetsLen);\n for (uint256 i = 0; i < assetsLen; ++i) {\n aTokens[i] = _getATokenFor(assetsMapped[i]);\n }\n\n // 1. If we have rewards availabile, collect them\n uint256 pendingRewards = incentivesController.getRewardsBalance(\n aTokens,\n address(this)\n );\n if (pendingRewards > 0) {\n // Because getting more stkAAVE from the incentives controller\n // with claimRewards() may push the stkAAVE cooldown time\n // forward, it is called after stakedAAVE has been turned into\n // AAVE.\n uint256 collected = incentivesController.claimRewards(\n aTokens,\n pendingRewards,\n address(this)\n );\n require(collected == pendingRewards, \"AAVE reward difference\");\n }\n\n // 2. Start cooldown counting down.\n if (stkAave.balanceOf(address(this)) > 0) {\n // Protected with if since cooldown call would revert\n // if no stkAave balance.\n stkAave.cooldown();\n }\n }\n }\n}\n" + }, + "contracts/strategies/AbstractCompoundStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title Base Compound Abstract Strategy\n * @author Origin Protocol Inc\n */\n\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport { ICERC20 } from \"./ICompound.sol\";\nimport { IComptroller } from \"../interfaces/IComptroller.sol\";\nimport { IERC20, InitializableAbstractStrategy } from \"../utils/InitializableAbstractStrategy.sol\";\n\nabstract contract AbstractCompoundStrategy is InitializableAbstractStrategy {\n using SafeERC20 for IERC20;\n\n int256[50] private __reserved;\n\n /**\n * @dev Retuns bool indicating whether asset is supported by strategy\n * @param _asset Address of the asset\n */\n function supportsAsset(address _asset) public view override returns (bool) {\n return assetToPToken[_asset] != address(0);\n }\n\n /**\n * @dev Get the cToken wrapped in the ICERC20 interface for this asset.\n * Fails if the pToken doesn't exist in our mappings.\n * @param _asset Address of the asset\n * @return Corresponding cToken to this asset\n */\n function _getCTokenFor(address _asset) internal view returns (ICERC20) {\n address cToken = assetToPToken[_asset];\n require(cToken != address(0), \"cToken does not exist\");\n return ICERC20(cToken);\n }\n\n /**\n * @dev Converts an underlying amount into cToken amount\n * cTokenAmt = (underlying * 1e18) / exchangeRate\n * @param _cToken cToken for which to change\n * @param _underlying Amount of underlying to convert\n * @return amount Equivalent amount of cTokens\n */\n function _convertUnderlyingToCToken(ICERC20 _cToken, uint256 _underlying)\n internal\n view\n returns (uint256 amount)\n {\n // e.g. 1e18*1e18 / 205316390724364402565641705 = 50e8\n // e.g. 1e8*1e18 / 205316390724364402565641705 = 0.45 or 0\n amount = (_underlying * 1e18) / _cToken.exchangeRateStored();\n }\n}\n" + }, + "contracts/strategies/AbstractConvexMetaStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title Curve Convex Strategy\n * @notice Investment strategy for investing stablecoins via Curve 3Pool\n * @author Origin Protocol Inc\n */\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\n\nimport { IRewardStaking } from \"./IRewardStaking.sol\";\nimport { ICurvePool } from \"./ICurvePool.sol\";\nimport { ICurveMetaPool } from \"./ICurveMetaPool.sol\";\nimport { IERC20, AbstractCurveStrategy, InitializableAbstractStrategy } from \"./AbstractCurveStrategy.sol\";\nimport { StableMath } from \"../utils/StableMath.sol\";\nimport { Helpers } from \"../utils/Helpers.sol\";\n\nabstract contract AbstractConvexMetaStrategy is AbstractCurveStrategy {\n using StableMath for uint256;\n using SafeERC20 for IERC20;\n\n event MaxWithdrawalSlippageUpdated(\n uint256 _prevMaxSlippagePercentage,\n uint256 _newMaxSlippagePercentage\n );\n\n // used to circumvent the stack too deep issue\n struct InitConfig {\n address cvxDepositorAddress; //Address of the Convex depositor(AKA booster) for this pool\n address metapoolAddress; //Address of the Curve MetaPool\n address metapoolMainToken; //Address of Main metapool token\n address cvxRewardStakerAddress; //Address of the CVX rewards staker\n address metapoolLPToken; //Address of metapool LP token\n uint256 cvxDepositorPTokenId; //Pid of the pool referred to by Depositor and staker\n }\n\n address internal cvxDepositorAddress;\n address internal cvxRewardStakerAddress;\n uint256 internal cvxDepositorPTokenId;\n ICurveMetaPool internal metapool;\n IERC20 internal metapoolMainToken;\n IERC20 internal metapoolLPToken;\n // Ordered list of metapool assets\n address[] internal metapoolAssets;\n // Max withdrawal slippage denominated in 1e18 (1e18 == 100%)\n uint256 public maxWithdrawalSlippage;\n uint128 internal crvCoinIndex;\n uint128 internal mainCoinIndex;\n\n int256[41] private ___reserved;\n\n /**\n * Initializer for setting up strategy internal state. This overrides the\n * InitializableAbstractStrategy initializer as Curve strategies don't fit\n * well within that abstraction.\n * @param _rewardTokenAddresses Address of CRV & CVX\n * @param _assets Addresses of supported assets. MUST be passed in the same\n * order as returned by coins on the pool contract, i.e.\n * DAI, USDC, USDT\n * @param _pTokens Platform Token corresponding addresses\n * @param initConfig Various addresses and info for initialization state\n */\n function initialize(\n address[] calldata _rewardTokenAddresses, // CRV + CVX\n address[] calldata _assets,\n address[] calldata _pTokens,\n InitConfig calldata initConfig\n ) external onlyGovernor initializer {\n require(_assets.length == 3, \"Must have exactly three assets\");\n // Should be set prior to abstract initialize call otherwise\n // abstractSetPToken calls will fail\n cvxDepositorAddress = initConfig.cvxDepositorAddress;\n pTokenAddress = _pTokens[0];\n metapool = ICurveMetaPool(initConfig.metapoolAddress);\n metapoolMainToken = IERC20(initConfig.metapoolMainToken);\n cvxRewardStakerAddress = initConfig.cvxRewardStakerAddress;\n metapoolLPToken = IERC20(initConfig.metapoolLPToken);\n cvxDepositorPTokenId = initConfig.cvxDepositorPTokenId;\n maxWithdrawalSlippage = 1e16;\n\n metapoolAssets = [metapool.coins(0), metapool.coins(1)];\n crvCoinIndex = _getMetapoolCoinIndex(pTokenAddress);\n mainCoinIndex = _getMetapoolCoinIndex(initConfig.metapoolMainToken);\n InitializableAbstractStrategy._initialize(\n _rewardTokenAddresses,\n _assets,\n _pTokens\n );\n _approveBase();\n }\n\n /**\n * @dev Get the total asset value held in the platform\n * @param _asset Address of the asset\n * @return balance Total value of the asset in the platform\n */\n function checkBalance(address _asset)\n public\n view\n virtual\n override\n returns (uint256 balance)\n {\n require(assetToPToken[_asset] != address(0), \"Unsupported asset\");\n balance = 0;\n\n // LP tokens in this contract. This should generally be nothing as we\n // should always stake the full balance in the Gauge, but include for\n // safety\n uint256 contractPTokens = IERC20(pTokenAddress).balanceOf(\n address(this)\n );\n ICurvePool curvePool = ICurvePool(platformAddress);\n if (contractPTokens > 0) {\n uint256 virtual_price = curvePool.get_virtual_price();\n uint256 value = contractPTokens.mulTruncate(virtual_price);\n balance += value;\n }\n\n /* We intentionally omit the metapoolLp tokens held by the metastrategyContract\n * since the contract should never (except in the middle of deposit/withdrawal\n * transaction) hold any amount of those tokens in normal operation. There\n * could be tokens sent to it by a 3rd party and we decide to actively ignore\n * those.\n */\n uint256 metapoolGaugePTokens = IRewardStaking(cvxRewardStakerAddress)\n .balanceOf(address(this));\n\n if (metapoolGaugePTokens > 0) {\n uint256 value = metapoolGaugePTokens.mulTruncate(\n metapool.get_virtual_price()\n );\n balance += value;\n }\n\n uint256 assetDecimals = Helpers.getDecimals(_asset);\n balance = balance.scaleBy(assetDecimals, 18) / THREEPOOL_ASSET_COUNT;\n }\n\n /**\n * @dev This function is completely analogous to _calcCurveTokenAmount[AbstractCurveStrategy]\n * and just utilizes different Curve (meta)pool API\n */\n function _calcCurveMetaTokenAmount(uint128 _coinIndex, uint256 _amount)\n internal\n returns (uint256 requiredMetapoolLP)\n {\n uint256[2] memory _amounts = [uint256(0), uint256(0)];\n _amounts[uint256(_coinIndex)] = _amount;\n\n // LP required when removing required asset ignoring fees\n uint256 lpRequiredNoFees = metapool.calc_token_amount(_amounts, false);\n /* LP required if fees would apply to entirety of removed amount\n *\n * fee is 1e10 denominated number: https://curve.readthedocs.io/exchange-pools.html#StableSwap.fee\n */\n uint256 lpRequiredFullFees = lpRequiredNoFees.mulTruncateScale(\n 1e10 + metapool.fee(),\n 1e10\n );\n\n /* asset received when withdrawing full fee applicable LP accounting for\n * slippage and fees\n */\n uint256 assetReceivedForFullLPFees = metapool.calc_withdraw_one_coin(\n lpRequiredFullFees,\n int128(_coinIndex)\n );\n\n // exact amount of LP required\n requiredMetapoolLP =\n (lpRequiredFullFees * _amount) /\n assetReceivedForFullLPFees;\n }\n\n function _approveBase() internal override {\n IERC20 pToken = IERC20(pTokenAddress);\n // 3Pool for LP token (required for removing liquidity)\n pToken.safeApprove(platformAddress, 0);\n pToken.safeApprove(platformAddress, type(uint256).max);\n // Gauge for LP token\n metapoolLPToken.safeApprove(cvxDepositorAddress, 0);\n metapoolLPToken.safeApprove(cvxDepositorAddress, type(uint256).max);\n // Metapool for LP token\n pToken.safeApprove(address(metapool), 0);\n pToken.safeApprove(address(metapool), type(uint256).max);\n // Metapool for Metapool main token\n metapoolMainToken.safeApprove(address(metapool), 0);\n metapoolMainToken.safeApprove(address(metapool), type(uint256).max);\n }\n\n /**\n * @dev Get the index of the coin\n */\n function _getMetapoolCoinIndex(address _asset)\n internal\n view\n returns (uint128)\n {\n for (uint128 i = 0; i < 2; i++) {\n if (metapoolAssets[i] == _asset) return i;\n }\n revert(\"Invalid Metapool asset\");\n }\n\n /**\n * @dev Sets max withdrawal slippage that is considered when removing\n * liquidity from Metapools.\n * @param _maxWithdrawalSlippage Max withdrawal slippage denominated in\n * wad (number with 18 decimals): 1e18 == 100%, 1e16 == 1%\n *\n * IMPORTANT Minimum maxWithdrawalSlippage should actually be 0.1% (1e15)\n * for production usage. Contract allows as low value as 0% for confirming\n * correct behavior in test suite.\n */\n function setMaxWithdrawalSlippage(uint256 _maxWithdrawalSlippage)\n external\n onlyVaultOrGovernorOrStrategist\n {\n require(\n _maxWithdrawalSlippage <= 1e18,\n \"Max withdrawal slippage needs to be between 0% - 100%\"\n );\n emit MaxWithdrawalSlippageUpdated(\n maxWithdrawalSlippage,\n _maxWithdrawalSlippage\n );\n maxWithdrawalSlippage = _maxWithdrawalSlippage;\n }\n\n /**\n * @dev Collect accumulated CRV and CVX and send to Harvester.\n */\n function collectRewardTokens()\n external\n override\n onlyHarvester\n nonReentrant\n {\n // Collect CRV and CVX\n IRewardStaking(cvxRewardStakerAddress).getReward();\n _collectRewardTokens();\n }\n\n /**\n * @dev Returns the largest of two numbers int256 version\n */\n function _max(int256 a, int256 b) internal pure returns (int256) {\n return a >= b ? a : b;\n }\n}\n" + }, + "contracts/strategies/AbstractCurveStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title Curve 3Pool Strategy\n * @notice Investment strategy for investing stablecoins via Curve 3Pool\n * @author Origin Protocol Inc\n */\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\n\nimport { ICurvePool } from \"./ICurvePool.sol\";\nimport { IERC20, InitializableAbstractStrategy } from \"../utils/InitializableAbstractStrategy.sol\";\nimport { StableMath } from \"../utils/StableMath.sol\";\nimport { Helpers } from \"../utils/Helpers.sol\";\n\nabstract contract AbstractCurveStrategy is InitializableAbstractStrategy {\n using StableMath for uint256;\n using SafeERC20 for IERC20;\n\n uint256 internal constant MAX_SLIPPAGE = 1e16; // 1%, same as the Curve UI\n // number of assets in Curve 3Pool (USDC, DAI, USDT)\n uint256 internal constant THREEPOOL_ASSET_COUNT = 3;\n address internal pTokenAddress;\n\n int256[49] private __reserved;\n\n /**\n * @dev Deposit asset into the Curve 3Pool\n * @param _asset Address of asset to deposit\n * @param _amount Amount of asset to deposit\n */\n function deposit(address _asset, uint256 _amount)\n external\n override\n onlyVault\n nonReentrant\n {\n require(_amount > 0, \"Must deposit something\");\n emit Deposit(_asset, pTokenAddress, _amount);\n\n // 3Pool requires passing deposit amounts for all 3 assets, set to 0 for\n // all\n uint256[3] memory _amounts;\n uint256 poolCoinIndex = _getCoinIndex(_asset);\n // Set the amount on the asset we want to deposit\n _amounts[poolCoinIndex] = _amount;\n ICurvePool curvePool = ICurvePool(platformAddress);\n uint256 assetDecimals = Helpers.getDecimals(_asset);\n uint256 depositValue = _amount.scaleBy(18, assetDecimals).divPrecisely(\n curvePool.get_virtual_price()\n );\n uint256 minMintAmount = depositValue.mulTruncate(\n uint256(1e18) - MAX_SLIPPAGE\n );\n // Do the deposit to 3pool\n curvePool.add_liquidity(_amounts, minMintAmount);\n _lpDepositAll();\n }\n\n function _lpDepositAll() internal virtual;\n\n /**\n * @dev Deposit the entire balance of any supported asset into the Curve 3pool\n */\n function depositAll() external override onlyVault nonReentrant {\n uint256[3] memory _amounts = [uint256(0), uint256(0), uint256(0)];\n uint256 depositValue = 0;\n ICurvePool curvePool = ICurvePool(platformAddress);\n uint256 curveVirtualPrice = curvePool.get_virtual_price();\n\n uint256 assetCount = assetsMapped.length;\n for (uint256 i = 0; i < assetCount; i++) {\n address assetAddress = assetsMapped[i];\n uint256 balance = IERC20(assetAddress).balanceOf(address(this));\n if (balance > 0) {\n uint256 poolCoinIndex = _getCoinIndex(assetAddress);\n // Set the amount on the asset we want to deposit\n _amounts[poolCoinIndex] = balance;\n uint256 assetDecimals = Helpers.getDecimals(assetAddress);\n // Get value of deposit in Curve LP token to later determine\n // the minMintAmount argument for add_liquidity\n depositValue =\n depositValue +\n balance.scaleBy(18, assetDecimals).divPrecisely(\n curveVirtualPrice\n );\n emit Deposit(assetAddress, pTokenAddress, balance);\n }\n }\n\n uint256 minMintAmount = depositValue.mulTruncate(\n uint256(1e18) - MAX_SLIPPAGE\n );\n // Do the deposit to 3pool\n curvePool.add_liquidity(_amounts, minMintAmount);\n\n /* In case of Curve Strategy all assets are mapped to the same pToken (3CrvLP). Let\n * descendants further handle the pToken. By either deploying it to the metapool and\n * resulting tokens in Gauge. Or deploying pTokens directly to the Gauge.\n */\n _lpDepositAll();\n }\n\n function _lpWithdraw(uint256 numCrvTokens) internal virtual;\n\n function _lpWithdrawAll() internal virtual;\n\n /**\n * @dev Withdraw asset from Curve 3Pool\n * @param _recipient Address to receive withdrawn asset\n * @param _asset Address of asset to withdraw\n * @param _amount Amount of asset to withdraw\n */\n function withdraw(\n address _recipient,\n address _asset,\n uint256 _amount\n ) external override onlyVault nonReentrant {\n require(_amount > 0, \"Invalid amount\");\n\n emit Withdrawal(_asset, pTokenAddress, _amount);\n\n uint256 contractCrv3Tokens = IERC20(pTokenAddress).balanceOf(\n address(this)\n );\n\n uint256 coinIndex = _getCoinIndex(_asset);\n ICurvePool curvePool = ICurvePool(platformAddress);\n\n uint256 requiredCrv3Tokens = _calcCurveTokenAmount(coinIndex, _amount);\n\n // We have enough LP tokens, make sure they are all on this contract\n if (contractCrv3Tokens < requiredCrv3Tokens) {\n _lpWithdraw(requiredCrv3Tokens - contractCrv3Tokens);\n }\n\n uint256[3] memory _amounts = [uint256(0), uint256(0), uint256(0)];\n _amounts[coinIndex] = _amount;\n\n curvePool.remove_liquidity_imbalance(_amounts, requiredCrv3Tokens);\n IERC20(_asset).safeTransfer(_recipient, _amount);\n }\n\n /**\n * @dev Calculate amount of LP required when withdrawing specific amount of one\n * of the underlying assets accounting for fees and slippage.\n *\n * Curve pools unfortunately do not contain a calculation function for\n * amount of LP required when withdrawing a specific amount of one of the\n * underlying tokens and also accounting for fees (Curve's calc_token_amount\n * does account for slippage but not fees).\n *\n * Steps taken to calculate the metric:\n * - get amount of LP required if fees wouldn't apply\n * - increase the LP amount as if fees would apply to the entirety of the underlying\n * asset withdrawal. (when withdrawing only one coin fees apply only to amounts\n * of other assets pool would return in case of balanced removal - since those need\n * to be swapped for the single underlying asset being withdrawn)\n * - get amount of underlying asset withdrawn (this Curve function does consider slippage\n * and fees) when using the increased LP amount. As LP amount is slightly over-increased\n * so is amount of underlying assets returned.\n * - since we know exactly how much asset we require take the rate of LP required for asset\n * withdrawn to get the exact amount of LP.\n */\n function _calcCurveTokenAmount(uint256 _coinIndex, uint256 _amount)\n internal\n returns (uint256 required3Crv)\n {\n ICurvePool curvePool = ICurvePool(platformAddress);\n\n uint256[3] memory _amounts = [uint256(0), uint256(0), uint256(0)];\n _amounts[_coinIndex] = _amount;\n\n // LP required when removing required asset ignoring fees\n uint256 lpRequiredNoFees = curvePool.calc_token_amount(_amounts, false);\n /* LP required if fees would apply to entirety of removed amount\n *\n * fee is 1e10 denominated number: https://curve.readthedocs.io/exchange-pools.html#StableSwap.fee\n */\n uint256 lpRequiredFullFees = lpRequiredNoFees.mulTruncateScale(\n 1e10 + curvePool.fee(),\n 1e10\n );\n\n /* asset received when withdrawing full fee applicable LP accounting for\n * slippage and fees\n */\n uint256 assetReceivedForFullLPFees = curvePool.calc_withdraw_one_coin(\n lpRequiredFullFees,\n int128(uint128(_coinIndex))\n );\n\n // exact amount of LP required\n required3Crv =\n (lpRequiredFullFees * _amount) /\n assetReceivedForFullLPFees;\n }\n\n /**\n * @dev Remove all assets from platform and send them to Vault contract.\n */\n function withdrawAll() external override onlyVaultOrGovernor nonReentrant {\n _lpWithdrawAll();\n // Withdraws are proportional to assets held by 3Pool\n uint256[3] memory minWithdrawAmounts = [\n uint256(0),\n uint256(0),\n uint256(0)\n ];\n\n // Remove liquidity\n ICurvePool threePool = ICurvePool(platformAddress);\n threePool.remove_liquidity(\n IERC20(pTokenAddress).balanceOf(address(this)),\n minWithdrawAmounts\n );\n // Transfer assets out of Vault\n // Note that Curve will provide all 3 of the assets in 3pool even if\n // we have not set PToken addresses for all of them in this strategy\n for (uint256 i = 0; i < assetsMapped.length; i++) {\n IERC20 asset = IERC20(threePool.coins(i));\n asset.safeTransfer(vaultAddress, asset.balanceOf(address(this)));\n }\n }\n\n /**\n * @dev Get the total asset value held in the platform\n * @param _asset Address of the asset\n * @return balance Total value of the asset in the platform\n */\n function checkBalance(address _asset)\n public\n view\n virtual\n override\n returns (uint256 balance)\n {\n require(assetToPToken[_asset] != address(0), \"Unsupported asset\");\n // LP tokens in this contract. This should generally be nothing as we\n // should always stake the full balance in the Gauge, but include for\n // safety\n uint256 totalPTokens = IERC20(pTokenAddress).balanceOf(address(this));\n ICurvePool curvePool = ICurvePool(platformAddress);\n if (totalPTokens > 0) {\n uint256 virtual_price = curvePool.get_virtual_price();\n uint256 value = (totalPTokens * virtual_price) / 1e18;\n uint256 assetDecimals = Helpers.getDecimals(_asset);\n balance = value.scaleBy(assetDecimals, 18) / THREEPOOL_ASSET_COUNT;\n }\n }\n\n /**\n * @dev Retuns bool indicating whether asset is supported by strategy\n * @param _asset Address of the asset\n */\n function supportsAsset(address _asset) public view override returns (bool) {\n return assetToPToken[_asset] != address(0);\n }\n\n /**\n * @dev Approve the spending of all assets by their corresponding pool tokens,\n * if for some reason is it necessary.\n */\n function safeApproveAllTokens()\n external\n override\n onlyGovernor\n nonReentrant\n {\n _approveBase();\n // This strategy is a special case since it only supports one asset\n for (uint256 i = 0; i < assetsMapped.length; i++) {\n _approveAsset(assetsMapped[i]);\n }\n }\n\n /**\n * @dev Call the necessary approvals for the Curve pool and gauge\n * @param _asset Address of the asset\n */\n function _abstractSetPToken(address _asset, address) internal override {\n _approveAsset(_asset);\n }\n\n function _approveAsset(address _asset) internal {\n IERC20 asset = IERC20(_asset);\n // 3Pool for asset (required for adding liquidity)\n asset.safeApprove(platformAddress, 0);\n asset.safeApprove(platformAddress, type(uint256).max);\n }\n\n function _approveBase() internal virtual;\n\n /**\n * @dev Get the index of the coin\n */\n function _getCoinIndex(address _asset) internal view returns (uint256) {\n for (uint256 i = 0; i < 3; i++) {\n if (assetsMapped[i] == _asset) return i;\n }\n revert(\"Invalid 3pool asset\");\n }\n}\n" + }, + "contracts/strategies/aerodrome/AerodromeAMOStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title Aerodrome AMO strategy\n * @author Origin Protocol Inc\n */\nimport { Math } from \"@openzeppelin/contracts/utils/math/Math.sol\";\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport { SafeCast } from \"@openzeppelin/contracts/utils/math/SafeCast.sol\";\n\nimport { IERC20, InitializableAbstractStrategy } from \"../../utils/InitializableAbstractStrategy.sol\";\nimport { StableMath } from \"../../utils/StableMath.sol\";\n\nimport { ISugarHelper } from \"../../interfaces/aerodrome/ISugarHelper.sol\";\nimport { INonfungiblePositionManager } from \"../../interfaces/aerodrome/INonfungiblePositionManager.sol\";\nimport { ISwapRouter } from \"../../interfaces/aerodrome/ISwapRouter.sol\";\nimport { ICLPool } from \"../../interfaces/aerodrome/ICLPool.sol\";\nimport { ICLGauge } from \"../../interfaces/aerodrome/ICLGauge.sol\";\nimport { IVault } from \"../../interfaces/IVault.sol\";\n\ncontract AerodromeAMOStrategy is InitializableAbstractStrategy {\n using StableMath for uint256;\n using SafeERC20 for IERC20;\n using SafeCast for uint256;\n\n /************************************************\n Important (!) setup configuration\n *************************************************/\n\n /**\n * In order to be able to remove a reasonable amount of complexity from the contract one of the\n * preconditions for this contract to function correctly is to have an outside account mint a small\n * amount of liquidity in the tick space where the contract will deploy's its liquidity and then send\n * that NFT LP position to a dead address (transfer to zero address not allowed.) See example of such\n * NFT LP token:\n * https://basescan.org/token/0x827922686190790b37229fd06084350e74485b72?a=413296#inventory\n */\n\n /***************************************\n Storage slot members\n ****************************************/\n\n /// @notice tokenId of the liquidity position\n uint256 public tokenId;\n /// @dev Minimum amount of tokens the strategy would be able to withdraw from the pool.\n /// minimum amount of tokens are withdrawn at a 1:1 price\n uint256 public underlyingAssets;\n /// @notice Marks the start of the interval that defines the allowed range of WETH share in\n /// the pre-configured pool's liquidity ticker\n uint256 public allowedWethShareStart;\n /// @notice Marks the end of the interval that defines the allowed range of WETH share in\n /// the pre-configured pool's liquidity ticker\n uint256 public allowedWethShareEnd;\n /// @dev reserved for inheritance\n int256[46] private __reserved;\n\n /***************************************\n Constants, structs and events\n ****************************************/\n\n /// @notice The address of the Wrapped ETH (WETH) token contract\n address public immutable WETH;\n /// @notice The address of the OETHb token contract\n address public immutable OETHb;\n /// @notice lower tick set to -1 representing the price of 1.0001 of WETH for 1 OETHb.\n int24 public immutable lowerTick;\n /// @notice lower tick set to 0 representing the price of 1.0000 of WETH for 1 OETHb.\n int24 public immutable upperTick;\n /// @notice tick spacing of the pool (set to 1)\n int24 public immutable tickSpacing;\n /// @notice the swapRouter for performing swaps\n ISwapRouter public immutable swapRouter;\n /// @notice the underlying AMO Slipstream pool\n ICLPool public immutable clPool;\n /// @notice the gauge for the corresponding Slipstream pool (clPool)\n /// @dev can become an immutable once the gauge is created on the base main-net\n ICLGauge public immutable clGauge;\n /// @notice the Position manager contract that is used to manage the pool's position\n INonfungiblePositionManager public immutable positionManager;\n /// @notice helper contract for liquidity and ticker math\n ISugarHelper public immutable helper;\n /// @notice sqrtRatioX96TickLower\n /// @dev tick lower has value -1 and represents the lowest price of WETH priced in OETHb. Meaning the pool\n /// offers less than 1 OETHb for 1 WETH. In other terms to get 1 OETHB the swap needs to offer 1.0001 WETH\n /// this is where purchasing OETHb with WETH within the liquidity position is most expensive\n uint160 public immutable sqrtRatioX96TickLower;\n /// @notice sqrtRatioX96TickHigher\n /// @dev tick higher has value 0 and represents 1:1 price parity of WETH to OETHb\n uint160 public immutable sqrtRatioX96TickHigher;\n /// @dev tick closest to 1:1 price parity\n /// Correctly assessing which tick is closer to 1:1 price parity is important since it affects\n /// the way we calculate the underlying assets in check Balance. The underlying aerodrome pool\n /// orders the tokens depending on the values of their addresses. If OETH token is token0 in the pool\n /// then sqrtRatioX96TickClosestToParity=sqrtRatioX96TickLower. If it is token1 in the pool then\n /// sqrtRatioX96TickClosestToParity=sqrtRatioX96TickHigher\n uint160 public immutable sqrtRatioX96TickClosestToParity;\n\n /// @dev a threshold under which the contract no longer allows for the protocol to rebalance. Guarding\n /// against a strategist / guardian being taken over and with multiple transactions draining the\n /// protocol funds.\n uint256 public constant SOLVENCY_THRESHOLD = 0.998 ether;\n\n error NotEnoughWethForSwap(uint256 wethBalance, uint256 requiredWeth); // 0x989e5ca8\n error NotEnoughWethLiquidity(uint256 wethBalance, uint256 requiredWeth); // 0xa6737d87\n error PoolRebalanceOutOfBounds(\n uint256 currentPoolWethShare,\n uint256 allowedWethShareStart,\n uint256 allowedWethShareEnd\n ); // 0x3681e8e0\n error OutsideExpectedTickRange(int24 currentTick); // 0x5a2eba75\n\n event PoolRebalanced(uint256 currentPoolWethShare);\n\n event PoolWethShareIntervalUpdated(\n uint256 allowedWethShareStart,\n uint256 allowedWethShareEnd\n );\n\n event LiquidityRemoved(\n uint256 withdrawLiquidityShare,\n uint256 removedWETHAmount,\n uint256 removedOETHbAmount,\n uint256 wethAmountCollected,\n uint256 oethbAmountCollected,\n uint256 underlyingAssets\n );\n\n event LiquidityAdded(\n uint256 wethAmountDesired,\n uint256 oethbAmountDesired,\n uint256 wethAmountSupplied,\n uint256 oethbAmountSupplied,\n uint256 tokenId,\n uint256 underlyingAssets\n );\n\n event UnderlyingAssetsUpdated(uint256 underlyingAssets);\n\n /**\n * @dev Un-stakes the token from the gauge for the execution duration of\n * the function and after that re-stakes it back in.\n *\n * It is important that the token is unstaked and owned by the strategy contract\n * during any liquidity altering operations and that it is re-staked back into the\n * gauge after liquidity changes. If the token fails to re-stake back to the\n * gauge it is not earning incentives.\n */\n // all functions using this modifier are used by functions with reentrancy check\n // slither-disable-start reentrancy-no-eth\n modifier gaugeUnstakeAndRestake() {\n // because of solidity short-circuit _isLpTokenStakedInGauge doesn't get called\n // when tokenId == 0\n if (tokenId != 0 && _isLpTokenStakedInGauge()) {\n clGauge.withdraw(tokenId);\n }\n _;\n // because of solidity short-circuit _isLpTokenStakedInGauge doesn't get called\n // when tokenId == 0\n if (tokenId != 0 && !_isLpTokenStakedInGauge()) {\n /**\n * It can happen that a withdrawal (or a full withdrawal) transactions would\n * remove all of the liquidity from the token with a NFT token still existing.\n * In that case the token can not be staked into the gauge, as some liquidity\n * needs to be added to it first.\n */\n if (_getLiquidity() > 0) {\n // if token liquidity changes the positionManager requires re-approval.\n // to any contract pre-approved to handle the token.\n positionManager.approve(address(clGauge), tokenId);\n clGauge.deposit(tokenId);\n }\n }\n }\n\n // slither-disable-end reentrancy-no-eth\n\n /// @notice the constructor\n /// @dev This contract is intended to be used as a proxy. To prevent the\n /// potential confusion of having a functional implementation contract\n /// the constructor has the `initializer` modifier. This way the\n /// `initialize` function can not be called on the implementation contract.\n /// For the same reason the implementation contract also has the governor\n /// set to a zero address.\n /// @param _stratConfig the basic strategy configuration\n /// @param _wethAddress Address of the Erc20 WETH Token contract\n /// @param _oethbAddress Address of the Erc20 OETHb Token contract\n /// @param _swapRouter Address of the Aerodrome Universal Swap Router\n /// @param _nonfungiblePositionManager Address of position manager to add/remove\n /// the liquidity\n /// @param _clPool Address of the Aerodrome concentrated liquidity pool\n /// @param _clGauge Address of the Aerodrome slipstream pool gauge\n /// @param _sugarHelper Address of the Aerodrome Sugar helper contract\n /// @param _lowerBoundingTick Smaller bounding tick of our liquidity position\n /// @param _upperBoundingTick Larger bounding tick of our liquidity position\n /// @param _tickClosestToParity Tick that is closer to 1:1 price parity\n constructor(\n BaseStrategyConfig memory _stratConfig,\n address _wethAddress,\n address _oethbAddress,\n address _swapRouter,\n address _nonfungiblePositionManager,\n address _clPool,\n address _clGauge,\n address _sugarHelper,\n int24 _lowerBoundingTick,\n int24 _upperBoundingTick,\n int24 _tickClosestToParity\n ) initializer InitializableAbstractStrategy(_stratConfig) {\n require(\n _lowerBoundingTick == _tickClosestToParity ||\n _upperBoundingTick == _tickClosestToParity,\n \"Misconfigured tickClosestToParity\"\n );\n require(\n ICLPool(_clPool).token0() == _wethAddress,\n \"Only WETH supported as token0\"\n );\n require(\n ICLPool(_clPool).token1() == _oethbAddress,\n \"Only OETHb supported as token1\"\n );\n int24 _tickSpacing = ICLPool(_clPool).tickSpacing();\n // when we generalize AMO we might support other tick spacings\n require(_tickSpacing == 1, \"Unsupported tickSpacing\");\n\n WETH = _wethAddress;\n OETHb = _oethbAddress;\n swapRouter = ISwapRouter(_swapRouter);\n positionManager = INonfungiblePositionManager(\n _nonfungiblePositionManager\n );\n clPool = ICLPool(_clPool);\n clGauge = ICLGauge(_clGauge);\n helper = ISugarHelper(_sugarHelper);\n sqrtRatioX96TickLower = ISugarHelper(_sugarHelper).getSqrtRatioAtTick(\n _lowerBoundingTick\n );\n sqrtRatioX96TickHigher = ISugarHelper(_sugarHelper).getSqrtRatioAtTick(\n _upperBoundingTick\n );\n sqrtRatioX96TickClosestToParity = ISugarHelper(_sugarHelper)\n .getSqrtRatioAtTick(_tickClosestToParity);\n\n lowerTick = _lowerBoundingTick;\n upperTick = _upperBoundingTick;\n tickSpacing = _tickSpacing;\n\n // prevent implementation contract to be governed\n _setGovernor(address(0));\n }\n\n /**\n * @notice initialize function, to set up initial internal state\n * @param _rewardTokenAddresses Address of reward token for platform\n */\n function initialize(address[] memory _rewardTokenAddresses)\n external\n onlyGovernor\n initializer\n {\n InitializableAbstractStrategy._initialize(\n _rewardTokenAddresses,\n new address[](0),\n new address[](0)\n );\n }\n\n /***************************************\n Configuration \n ****************************************/\n\n /**\n * @notice Set allowed pool weth share interval. After the rebalance happens\n * the share of WETH token in the ticker needs to be withing the specifications\n * of the interval.\n *\n * @param _allowedWethShareStart Start of WETH share interval expressed as 18 decimal amount\n * @param _allowedWethShareEnd End of WETH share interval expressed as 18 decimal amount\n */\n function setAllowedPoolWethShareInterval(\n uint256 _allowedWethShareStart,\n uint256 _allowedWethShareEnd\n ) external onlyGovernor {\n require(\n _allowedWethShareStart < _allowedWethShareEnd,\n \"Invalid interval\"\n );\n // can not go below 1% weth share\n require(_allowedWethShareStart > 0.01 ether, \"Invalid interval start\");\n // can not go above 95% weth share\n require(_allowedWethShareEnd < 0.95 ether, \"Invalid interval end\");\n\n allowedWethShareStart = _allowedWethShareStart;\n allowedWethShareEnd = _allowedWethShareEnd;\n emit PoolWethShareIntervalUpdated(\n allowedWethShareStart,\n allowedWethShareEnd\n );\n }\n\n /***************************************\n Periphery utils\n ****************************************/\n\n function _isLpTokenStakedInGauge() internal view returns (bool) {\n require(tokenId != 0, \"Missing NFT LP token\");\n\n address owner = positionManager.ownerOf(tokenId);\n require(\n owner == address(clGauge) || owner == address(this),\n \"Unexpected token owner\"\n );\n return owner == address(clGauge);\n }\n\n /***************************************\n Strategy overrides \n ****************************************/\n\n /**\n * @notice Deposit an amount of assets into the strategy contract. Calling deposit doesn't\n * automatically deposit funds into the underlying Aerodrome pool\n * @param _asset Address for the asset\n * @param _amount Units of asset to deposit\n */\n function deposit(address _asset, uint256 _amount)\n external\n override\n onlyVault\n nonReentrant\n {\n _deposit(_asset, _amount);\n }\n\n /**\n * @notice Deposit WETH to the strategy contract. This function does not add liquidity to the\n * underlying Aerodrome pool.\n */\n function depositAll() external override onlyVault nonReentrant {\n uint256 _wethBalance = IERC20(WETH).balanceOf(address(this));\n if (_wethBalance > 1e12) {\n _deposit(WETH, _wethBalance);\n }\n }\n\n /**\n * @dev Deposit WETH to the contract. This function doesn't deposit the liquidity to the\n * pool, that is done via the rebalance call.\n * @param _asset Address of the asset to deposit\n * @param _amount Amount of assets to deposit\n */\n function _deposit(address _asset, uint256 _amount) internal {\n require(_asset == WETH, \"Unsupported asset\");\n require(_amount > 0, \"Must deposit something\");\n emit Deposit(_asset, address(0), _amount);\n\n // if the pool price is not within the expected interval leave the WETH on the contract\n // as to not break the mints\n (bool _isExpectedRange, ) = _checkForExpectedPoolPrice(false);\n if (_isExpectedRange) {\n // deposit funds into the underlying pool\n _rebalance(0, false, 0);\n }\n }\n\n /**\n * @notice Rebalance the pool to the desired token split and Deposit any WETH on the contract to the\n * underlying aerodrome pool. Print the required amount of corresponding OETHb. After the rebalancing is\n * done burn any potentially remaining OETHb tokens still on the strategy contract.\n *\n * This function has a slightly different behaviour depending on the status of the underlying Aerodrome\n * slipstream pool. The function consists of the following 3 steps:\n * 1. withdrawPartialLiquidity -> so that moving the activeTrading price via a swap is cheaper\n * 2. swapToDesiredPosition -> move active trading price in the pool to be able to deposit WETH & OETHb\n * tokens with the desired pre-configured shares\n * 3. addLiquidity -> add liquidity into the pool respecting share split configuration\n *\n * Scenario 1: When there is no liquidity in the pool from the strategy but there is from other LPs then\n * only step 1 is skipped. (It is important to note that liquidity needs to exist in the configured\n * strategy tick ranges in order for the swap to be possible) Step 3 mints new liquidity position\n * instead of adding to an existing one.\n * Scenario 2: When there is strategy's liquidity in the pool all 3 steps are taken\n *\n *\n * Exact _amountToSwap, _swapWeth & _minTokenReceived parameters shall be determined by simulating the\n * transaction off-chain. The strategy checks that after the swap the share of the tokens is in the\n * expected ranges.\n *\n * @param _amountToSwap The amount of the token to swap\n * @param _swapWeth Swap using WETH when true, use OETHb when false\n * @param _minTokenReceived Slippage check -> minimum amount of token expected in return\n */\n function rebalance(\n uint256 _amountToSwap,\n bool _swapWeth,\n uint256 _minTokenReceived\n ) external nonReentrant onlyGovernorOrStrategist {\n _rebalance(_amountToSwap, _swapWeth, _minTokenReceived);\n }\n\n function _rebalance(\n uint256 _amountToSwap,\n bool _swapWeth,\n uint256 _minTokenReceived\n ) internal {\n /**\n * Would be nice to check if there is any total liquidity in the pool before performing this swap\n * but there is no easy way to do that in UniswapV3:\n * - clPool.liquidity() -> only liquidity in the active tick\n * - asset[1&2].balanceOf(address(clPool)) -> will include uncollected tokens of LP providers\n * after their liquidity position has been decreased\n */\n\n /**\n * When rebalance is called for the first time there is no strategy\n * liquidity in the pool yet. The liquidity removal is thus skipped.\n * Also execute this function when WETH is required for the swap.\n */\n if (tokenId != 0 && _swapWeth && _amountToSwap > 0) {\n _ensureWETHBalance(_amountToSwap);\n }\n\n // in some cases we will just want to add liquidity and not issue a swap to move the\n // active trading position within the pool\n if (_amountToSwap > 0) {\n _swapToDesiredPosition(_amountToSwap, _swapWeth, _minTokenReceived);\n }\n // calling check liquidity early so we don't get unexpected errors when adding liquidity\n // in the later stages of this function\n _checkForExpectedPoolPrice(true);\n\n _addLiquidity();\n\n // this call shouldn't be necessary, since adding liquidity shouldn't affect the active\n // trading price. It is a defensive programming measure.\n (, uint256 _wethSharePct) = _checkForExpectedPoolPrice(true);\n\n // revert if protocol insolvent\n _solvencyAssert();\n\n emit PoolRebalanced(_wethSharePct);\n }\n\n /**\n * Checks that the protocol is solvent, protecting from a rogue Strategist / Guardian that can\n * keep rebalancing the pool in both directions making the protocol lose a tiny amount of\n * funds each time.\n *\n * Protocol must be at least SOLVENCY_THRESHOLD (99,8 %) backed in order for the rebalances to\n * function.\n */\n function _solvencyAssert() internal view {\n uint256 _totalVaultValue = IVault(vaultAddress).totalValue();\n uint256 _totalOethbSupply = IERC20(OETHb).totalSupply();\n\n if (\n _totalVaultValue.divPrecisely(_totalOethbSupply) <\n SOLVENCY_THRESHOLD\n ) {\n revert(\"Protocol insolvent\");\n }\n }\n\n /**\n * @dev Decrease partial or all liquidity from the pool.\n * @param _liquidityToDecrease The amount of liquidity to remove expressed in 18 decimal point\n */\n function _removeLiquidity(uint256 _liquidityToDecrease)\n internal\n gaugeUnstakeAndRestake\n {\n require(_liquidityToDecrease > 0, \"Must remove some liquidity\");\n\n uint128 _liquidity = _getLiquidity();\n // need to convert to uint256 since intermittent result is to big for uint128 to handle\n uint128 _liquidityToRemove = uint256(_liquidity)\n .mulTruncate(_liquidityToDecrease)\n .toUint128();\n\n /**\n * There is no liquidity to remove -> exit function early. This can happen after a\n * withdraw/withdrawAll removes all of the liquidity while retaining the NFT token.\n */\n if (_liquidity == 0 || _liquidityToRemove == 0) {\n return;\n }\n\n (uint256 _amountWeth, uint256 _amountOethb) = positionManager\n .decreaseLiquidity(\n // Both expected amounts can be 0 since we don't really care if any swaps\n // happen just before the liquidity removal.\n INonfungiblePositionManager.DecreaseLiquidityParams({\n tokenId: tokenId,\n liquidity: _liquidityToRemove,\n amount0Min: 0,\n amount1Min: 0,\n deadline: block.timestamp\n })\n );\n\n (\n uint256 _amountWethCollected,\n uint256 _amountOethbCollected\n ) = positionManager.collect(\n INonfungiblePositionManager.CollectParams({\n tokenId: tokenId,\n recipient: address(this),\n amount0Max: type(uint128).max, // defaults to all tokens owed\n amount1Max: type(uint128).max // defaults to all tokens owed\n })\n );\n\n _updateUnderlyingAssets();\n\n emit LiquidityRemoved(\n _liquidityToDecrease,\n _amountWeth, //removedWethAmount\n _amountOethb, //removedOethbAmount\n _amountWethCollected,\n _amountOethbCollected,\n underlyingAssets\n );\n\n _burnOethbOnTheContract();\n }\n\n /**\n * @dev Perform a swap so that after the swap the ticker has the desired WETH to OETHb token share.\n */\n function _swapToDesiredPosition(\n uint256 _amountToSwap,\n bool _swapWeth,\n uint256 _minTokenReceived\n ) internal {\n IERC20 _tokenToSwap = IERC20(_swapWeth ? WETH : OETHb);\n uint256 _balance = _tokenToSwap.balanceOf(address(this));\n\n if (_balance < _amountToSwap) {\n // This should never trigger since _ensureWETHBalance will already\n // throw an error if there is not enough WETH\n if (_swapWeth) {\n revert NotEnoughWethForSwap(_balance, _amountToSwap);\n }\n // if swapping OETHb\n uint256 mintForSwap = _amountToSwap - _balance;\n IVault(vaultAddress).mintForStrategy(mintForSwap);\n }\n\n // approve the specific amount of WETH required\n if (_swapWeth) {\n IERC20(WETH).approve(address(swapRouter), _amountToSwap);\n }\n\n // Swap it\n swapRouter.exactInputSingle(\n // sqrtPriceLimitX96 is just a rough sanity check that we are within 0 -> 1 tick\n // a more fine check is performed in _checkForExpectedPoolPrice\n // Note: this needs further work if we want to generalize this approach\n ISwapRouter.ExactInputSingleParams({\n tokenIn: address(_tokenToSwap),\n tokenOut: _swapWeth ? OETHb : WETH,\n tickSpacing: tickSpacing, // set to 1\n recipient: address(this),\n deadline: block.timestamp,\n amountIn: _amountToSwap,\n amountOutMinimum: _minTokenReceived, // slippage check\n sqrtPriceLimitX96: _swapWeth\n ? sqrtRatioX96TickLower\n : sqrtRatioX96TickHigher\n })\n );\n\n /**\n * In the interest of each function in _rebalance to leave the contract state as\n * clean as possible the OETHb tokens here are burned. This decreases the\n * dependence where `_swapToDesiredPosition` function relies on later functions\n * (`addLiquidity`) to burn the OETHb. Reducing the risk of error introduction.\n */\n _burnOethbOnTheContract();\n }\n\n /**\n * @dev Add liquidity into the pool in the pre-configured WETH to OETHb share ratios\n * defined by the allowedPoolWethShareStart|End interval. This function will respect\n * liquidity ratios when there is no liquidity yet in the pool. If liquidity is already\n * present then it relies on the `_swapToDesiredPosition` function in a step before\n * to already move the trading price to desired position (with some tolerance).\n */\n // rebalance already has re-entrency checks\n // slither-disable-start reentrancy-no-eth\n function _addLiquidity() internal gaugeUnstakeAndRestake {\n uint256 _wethBalance = IERC20(WETH).balanceOf(address(this));\n uint256 _oethbBalance = IERC20(OETHb).balanceOf(address(this));\n // don't deposit small liquidity amounts\n if (_wethBalance <= 1e12) {\n return;\n }\n\n uint160 _currentPrice = getPoolX96Price();\n /**\n * Sanity check active trading price is positioned within our desired tick.\n *\n * We revert when price is equal to the lower tick even though that is still\n * a valid amount in regards to ticker position by Sugar.estimateAmount call.\n * Current price equaling tick bound at the 1:1 price parity results in\n * uint overfow when calculating the OETHb balance to deposit.\n */\n if (\n _currentPrice <= sqrtRatioX96TickLower ||\n _currentPrice >= sqrtRatioX96TickHigher\n ) {\n revert OutsideExpectedTickRange(getCurrentTradingTick());\n }\n\n /**\n * If estimateAmount1 call fails it could be due to _currentPrice being really\n * close to a tick and amount1 is a larger number than the sugar helper is able\n * to compute.\n *\n * If token addresses were reversed estimateAmount0 would be required here\n */\n uint256 _oethbRequired = helper.estimateAmount1(\n _wethBalance,\n address(0), // no need to pass pool address when current price is specified\n _currentPrice,\n lowerTick,\n upperTick\n );\n\n if (_oethbRequired > _oethbBalance) {\n IVault(vaultAddress).mintForStrategy(\n _oethbRequired - _oethbBalance\n );\n }\n\n // approve the specific amount of WETH required\n IERC20(WETH).approve(address(positionManager), _wethBalance);\n\n uint256 _wethAmountSupplied;\n uint256 _oethbAmountSupplied;\n if (tokenId == 0) {\n (\n tokenId,\n ,\n _wethAmountSupplied,\n _oethbAmountSupplied\n ) = positionManager.mint(\n /** amount0Min & amount1Min are left at 0 because slippage protection is ensured by the\n * _checkForExpectedPoolPrice\n *›\n * Also sqrtPriceX96 is 0 because the pool is already created\n * non zero amount attempts to create a new instance of the pool\n */\n INonfungiblePositionManager.MintParams({\n token0: WETH,\n token1: OETHb,\n tickSpacing: tickSpacing,\n tickLower: lowerTick,\n tickUpper: upperTick,\n amount0Desired: _wethBalance,\n amount1Desired: _oethbRequired,\n amount0Min: 0,\n amount1Min: 0,\n recipient: address(this),\n deadline: block.timestamp,\n sqrtPriceX96: 0\n })\n );\n } else {\n (, _wethAmountSupplied, _oethbAmountSupplied) = positionManager\n .increaseLiquidity(\n /** amount0Min & amount1Min are left at 0 because slippage protection is ensured by the\n * _checkForExpectedPoolPrice\n */\n INonfungiblePositionManager.IncreaseLiquidityParams({\n tokenId: tokenId,\n amount0Desired: _wethBalance,\n amount1Desired: _oethbRequired,\n amount0Min: 0,\n amount1Min: 0,\n deadline: block.timestamp\n })\n );\n }\n\n _updateUnderlyingAssets();\n emit LiquidityAdded(\n _wethBalance, // wethAmountDesired\n _oethbRequired, // oethbAmountDesired\n _wethAmountSupplied, // wethAmountSupplied\n _oethbAmountSupplied, // oethbAmountSupplied\n tokenId, // tokenId\n underlyingAssets\n );\n\n // burn remaining OETHb\n _burnOethbOnTheContract();\n }\n\n // slither-disable-end reentrancy-no-eth\n\n /**\n * @dev Check that the Aerodrome pool price is within the expected\n * parameters.\n * This function works whether the strategy contract has liquidity\n * position in the pool or not. The function returns _wethSharePct\n * as a gas optimization measure.\n * @param throwException when set to true the function throws an exception\n * when pool's price is not within expected range.\n * @return _isExpectedRange Bool expressing price is within expected range\n * @return _wethSharePct Share of WETH owned by this strategy contract in the\n * configured ticker.\n */\n function _checkForExpectedPoolPrice(bool throwException)\n internal\n view\n returns (bool _isExpectedRange, uint256 _wethSharePct)\n {\n require(\n allowedWethShareStart != 0 && allowedWethShareEnd != 0,\n \"Weth share interval not set\"\n );\n\n uint160 _currentPrice = getPoolX96Price();\n\n /**\n * First check we are in expected tick range\n *\n * We revert even though price being equal to the lower tick would still\n * count being within lower tick for the purpose of Sugar.estimateAmount calls\n */\n if (\n _currentPrice <= sqrtRatioX96TickLower ||\n _currentPrice >= sqrtRatioX96TickHigher\n ) {\n if (throwException) {\n revert OutsideExpectedTickRange(getCurrentTradingTick());\n }\n return (false, 0);\n }\n\n // 18 decimal number expressed WETH tick share\n _wethSharePct = _getWethShare(_currentPrice);\n\n if (\n _wethSharePct < allowedWethShareStart ||\n _wethSharePct > allowedWethShareEnd\n ) {\n if (throwException) {\n revert PoolRebalanceOutOfBounds(\n _wethSharePct,\n allowedWethShareStart,\n allowedWethShareEnd\n );\n }\n return (false, _wethSharePct);\n }\n\n return (true, _wethSharePct);\n }\n\n /**\n * Burns any OETHb tokens remaining on the strategy contract\n */\n function _burnOethbOnTheContract() internal {\n uint256 _oethbBalance = IERC20(OETHb).balanceOf(address(this));\n if (_oethbBalance > 1e12) {\n IVault(vaultAddress).burnForStrategy(_oethbBalance);\n }\n }\n\n /// @dev This function assumes there are no uncollected tokens in the clPool owned by the strategy contract.\n /// For that reason any liquidity withdrawals must also collect the tokens.\n function _updateUnderlyingAssets() internal {\n if (tokenId == 0) {\n underlyingAssets = 0;\n emit UnderlyingAssetsUpdated(underlyingAssets);\n return;\n }\n\n uint128 _liquidity = _getLiquidity();\n\n /**\n * Our net value represent the smallest amount of tokens we are able to extract from the position\n * given our liquidity.\n *\n * The least amount of tokens extraditable from the position is where the active trading price is\n * at the ticker 0 meaning the pool is offering 1:1 trades between WETH & OETHb. At that moment the pool\n * consists completely of OETHb and no WETH.\n *\n * The more swaps from WETH -> OETHb happen on the pool the more the price starts to move towards the -1\n * ticker making OETHb (priced in WETH) more expensive.\n *\n * An additional note: when liquidity is 0 then the helper returns 0 for both token amounts. And the\n * function set underlying assets to 0.\n */\n (uint256 _wethAmount, uint256 _oethbAmount) = helper\n .getAmountsForLiquidity(\n sqrtRatioX96TickClosestToParity, // sqrtRatioX96\n sqrtRatioX96TickLower, // sqrtRatioAX96\n sqrtRatioX96TickHigher, // sqrtRatioBX96\n _liquidity\n );\n\n require(_wethAmount == 0, \"Non zero wethAmount\");\n underlyingAssets = _oethbAmount;\n emit UnderlyingAssetsUpdated(underlyingAssets);\n }\n\n /**\n * @dev This function removes the appropriate amount of liquidity to assure that the required\n * amount of WETH is available on the contract\n *\n * @param _amount WETH balance required on the contract\n */\n function _ensureWETHBalance(uint256 _amount) internal {\n uint256 _wethBalance = IERC20(WETH).balanceOf(address(this));\n if (_wethBalance >= _amount) {\n return;\n }\n\n require(tokenId != 0, \"No liquidity available\");\n uint256 _additionalWethRequired = _amount - _wethBalance;\n (uint256 _wethInThePool, ) = getPositionPrincipal();\n\n if (_wethInThePool < _additionalWethRequired) {\n revert NotEnoughWethLiquidity(\n _wethInThePool,\n _additionalWethRequired\n );\n }\n\n uint256 shareOfWethToRemove = Math.min(\n _additionalWethRequired.divPrecisely(_wethInThePool) + 1,\n 1e18\n );\n _removeLiquidity(shareOfWethToRemove);\n }\n\n /**\n * @notice Withdraw an `amount` of assets from the platform and\n * send to the `_recipient`.\n * @param _recipient Address to which the asset should be sent\n * @param _asset WETH address\n * @param _amount Amount of WETH to withdraw\n */\n function withdraw(\n address _recipient,\n address _asset,\n uint256 _amount\n ) external override onlyVault nonReentrant {\n require(_asset == WETH, \"Unsupported asset\");\n require(_recipient == vaultAddress, \"Only withdraw to vault allowed\");\n\n _ensureWETHBalance(_amount);\n\n _withdraw(_recipient, _amount);\n }\n\n /**\n * @notice Withdraw WETH and sends it to the Vault.\n */\n function withdrawAll() external override onlyVault nonReentrant {\n if (tokenId != 0) {\n _removeLiquidity(1e18);\n }\n\n uint256 _balance = IERC20(WETH).balanceOf(address(this));\n if (_balance > 0) {\n _withdraw(vaultAddress, _balance);\n }\n }\n\n function _withdraw(address _recipient, uint256 _amount) internal {\n require(_amount > 0, \"Must withdraw something\");\n require(_recipient == vaultAddress, \"Only withdraw to vault allowed\");\n\n IERC20(WETH).safeTransfer(_recipient, _amount);\n emit Withdrawal(WETH, address(0), _amount);\n }\n\n /**\n * @dev Collect the AERO token from the gauge\n */\n function _collectRewardTokens() internal override {\n if (tokenId != 0 && _isLpTokenStakedInGauge()) {\n clGauge.getReward(tokenId);\n }\n super._collectRewardTokens();\n }\n\n /**\n * @dev Retuns bool indicating whether asset is supported by strategy\n * @param _asset Address of the asset\n */\n function supportsAsset(address _asset) public view override returns (bool) {\n return _asset == WETH;\n }\n\n /**\n * @dev Approve the spending of all assets\n */\n function safeApproveAllTokens()\n external\n override\n onlyGovernor\n nonReentrant\n {\n // to add liquidity to the clPool\n IERC20(OETHb).approve(address(positionManager), type(uint256).max);\n // to be able to rebalance using the swapRouter\n IERC20(OETHb).approve(address(swapRouter), type(uint256).max);\n\n /* the behaviour of this strategy has slightly changed and WETH could be\n * present on the contract between the transactions. For that reason we are\n * un-approving WETH to the swapRouter & positionManager and only approving\n * the required amount before a transaction\n */\n IERC20(WETH).approve(address(swapRouter), 0);\n IERC20(WETH).approve(address(positionManager), 0);\n }\n\n /***************************************\n Balances and Fees\n ****************************************/\n\n /**\n * @dev Get the total asset value held in the platform\n * @param _asset Address of the asset\n * @return balance Total value of the asset in the platform\n */\n function checkBalance(address _asset)\n external\n view\n override\n returns (uint256)\n {\n require(_asset == WETH, \"Only WETH supported\");\n\n // we could in theory deposit to the strategy and forget to call rebalance in the same\n // governance transaction batch. In that case the WETH that is on the strategy contract\n // also needs to be accounted for.\n uint256 _wethBalance = IERC20(WETH).balanceOf(address(this));\n // just paranoia check, in case there is OETHb in the strategy that for some reason hasn't\n // been burned yet.\n uint256 _oethbBalance = IERC20(OETHb).balanceOf(address(this));\n return underlyingAssets + _wethBalance + _oethbBalance;\n }\n\n /**\n * @dev Returns the balance of both tokens in a given position (excluding fees)\n * @return _amountWeth Amount of WETH in position\n * @return _amountOethb Amount of OETHb in position\n */\n function getPositionPrincipal()\n public\n view\n returns (uint256 _amountWeth, uint256 _amountOethb)\n {\n if (tokenId == 0) {\n return (0, 0);\n }\n\n uint160 _sqrtRatioX96 = getPoolX96Price();\n (_amountWeth, _amountOethb) = helper.principal(\n positionManager,\n tokenId,\n _sqrtRatioX96\n );\n }\n\n /**\n * @notice Returns the current pool price in X96 format\n * @return _sqrtRatioX96 Pool price\n */\n function getPoolX96Price() public view returns (uint160 _sqrtRatioX96) {\n (_sqrtRatioX96, , , , , ) = clPool.slot0();\n }\n\n /**\n * @notice Returns the current active trading tick of the underlying pool\n * @return _currentTick Current pool trading tick\n */\n function getCurrentTradingTick() public view returns (int24 _currentTick) {\n (, _currentTick, , , , ) = clPool.slot0();\n }\n\n /**\n * @notice Returns the percentage of WETH liquidity in the configured ticker\n * owned by this strategy contract.\n * @return uint256 1e18 denominated percentage expressing the share\n */\n function getWETHShare() external view returns (uint256) {\n uint160 _currentPrice = getPoolX96Price();\n return _getWethShare(_currentPrice);\n }\n\n /**\n * @notice Returns the amount of liquidity in the contract's LP position\n * @return _liquidity Amount of liquidity in the position\n */\n function _getLiquidity() internal view returns (uint128 _liquidity) {\n if (tokenId == 0) {\n revert(\"No LP position\");\n }\n\n (, , , , , , , _liquidity, , , , ) = positionManager.positions(tokenId);\n }\n\n function _getWethShare(uint160 _currentPrice)\n internal\n view\n returns (uint256)\n {\n /**\n * If estimateAmount1 call fails it could be due to _currentPrice being really\n * close to a tick and amount1 too big to compute.\n *\n * If token addresses were reversed estimateAmount0 would be required here\n */\n uint256 _normalizedWethAmount = 1 ether;\n uint256 _correspondingOethAmount = helper.estimateAmount1(\n _normalizedWethAmount,\n address(0), // no need to pass pool address when current price is specified\n _currentPrice,\n lowerTick,\n upperTick\n );\n\n // 18 decimal number expressed weth tick share\n return\n _normalizedWethAmount.divPrecisely(\n _normalizedWethAmount + _correspondingOethAmount\n );\n }\n\n /***************************************\n Hidden functions\n ****************************************/\n /// @inheritdoc InitializableAbstractStrategy\n function setPTokenAddress(address, address) external override {\n // The pool tokens can never change.\n revert(\"Unsupported method\");\n }\n\n /// @inheritdoc InitializableAbstractStrategy\n function removePToken(uint256) external override {\n // The pool tokens can never change.\n revert(\"Unsupported method\");\n }\n\n /**\n * @dev Not supported\n */\n function _abstractSetPToken(address, address) internal override {\n // the deployer shall call safeApproveAllTokens() to set necessary approvals\n revert(\"Unsupported method\");\n }\n\n /***************************************\n ERC721 management\n ****************************************/\n\n /// @notice Callback function for whenever a NFT is transferred to this contract\n // solhint-disable-next-line max-line-length\n /// Ref: https://docs.openzeppelin.com/contracts/3.x/api/token/erc721#IERC721Receiver-onERC721Received-address-address-uint256-bytes-\n function onERC721Received(\n address,\n address,\n uint256,\n bytes calldata\n ) external returns (bytes4) {\n return this.onERC721Received.selector;\n }\n}\n" + }, + "contracts/strategies/balancer/AbstractAuraStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title OETH Base Balancer Abstract Strategy\n * @author Origin Protocol Inc\n */\n\nimport { AbstractBalancerStrategy } from \"./AbstractBalancerStrategy.sol\";\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport { IERC20 } from \"../../utils/InitializableAbstractStrategy.sol\";\nimport { IERC4626 } from \"../../../lib/openzeppelin/interfaces/IERC4626.sol\";\nimport { StableMath } from \"../../utils/StableMath.sol\";\nimport { IRewardStaking } from \"../IRewardStaking.sol\";\n\nabstract contract AbstractAuraStrategy is AbstractBalancerStrategy {\n using SafeERC20 for IERC20;\n using StableMath for uint256;\n\n /// @notice Address of the Aura rewards pool\n address public immutable auraRewardPoolAddress;\n\n // renamed from __reserved to not shadow AbstractBalancerStrategy.__reserved,\n int256[50] private __reserved_baseAuraStrategy;\n\n constructor(address _auraRewardPoolAddress) {\n auraRewardPoolAddress = _auraRewardPoolAddress;\n }\n\n /**\n * @dev Deposit all Balancer Pool Tokens (BPT) in this strategy contract\n * to the Aura rewards pool.\n */\n function _lpDepositAll() internal virtual override {\n uint256 bptBalance = IERC20(platformAddress).balanceOf(address(this));\n uint256 auraLp = IERC4626(auraRewardPoolAddress).deposit(\n bptBalance,\n address(this)\n );\n require(bptBalance == auraLp, \"Aura LP != BPT\");\n }\n\n /**\n * @dev Withdraw `numBPTTokens` Balancer Pool Tokens (BPT) from\n * the Aura rewards pool to this strategy contract.\n * @param numBPTTokens Number of Balancer Pool Tokens (BPT) to withdraw\n */\n function _lpWithdraw(uint256 numBPTTokens) internal virtual override {\n IRewardStaking(auraRewardPoolAddress).withdrawAndUnwrap(\n numBPTTokens,\n true // also claim reward tokens\n );\n }\n\n /**\n * @dev Withdraw all Balancer Pool Tokens (BPT) from\n * the Aura rewards pool to this strategy contract.\n */\n function _lpWithdrawAll() internal virtual override {\n // Get all the strategy's BPTs in Aura\n // maxRedeem is implemented as balanceOf(address) in Aura\n uint256 bptBalance = IERC4626(auraRewardPoolAddress).maxRedeem(\n address(this)\n );\n\n IRewardStaking(auraRewardPoolAddress).withdrawAndUnwrap(\n bptBalance,\n true // also claim reward tokens\n );\n }\n\n /**\n * @notice Collects BAL and AURA tokens from the rewards pool.\n */\n function collectRewardTokens()\n external\n virtual\n override\n onlyHarvester\n nonReentrant\n {\n /* Similar to Convex, calling this function collects both of the\n * accrued BAL and AURA tokens.\n */\n IRewardStaking(auraRewardPoolAddress).getReward();\n _collectRewardTokens();\n }\n\n /// @notice Balancer Pool Tokens (BPT) in the Balancer pool and the Aura rewards pool.\n function _getBalancerPoolTokens()\n internal\n view\n override\n returns (uint256 balancerPoolTokens)\n {\n balancerPoolTokens =\n IERC20(platformAddress).balanceOf(address(this)) +\n // maxRedeem is implemented as balanceOf(address) in Aura\n IERC4626(auraRewardPoolAddress).maxRedeem(address(this));\n }\n\n function _approveBase() internal virtual override {\n super._approveBase();\n\n IERC20 pToken = IERC20(platformAddress);\n pToken.safeApprove(auraRewardPoolAddress, type(uint256).max);\n }\n}\n" + }, + "contracts/strategies/balancer/AbstractBalancerStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title OETH Balancer Abstract Strategy\n * @author Origin Protocol Inc\n */\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport { IERC20, InitializableAbstractStrategy } from \"../../utils/InitializableAbstractStrategy.sol\";\nimport { IBalancerVault } from \"../../interfaces/balancer/IBalancerVault.sol\";\nimport { IRateProvider } from \"../../interfaces/balancer/IRateProvider.sol\";\nimport { VaultReentrancyLib } from \"./VaultReentrancyLib.sol\";\nimport { IOracle } from \"../../interfaces/IOracle.sol\";\nimport { IWstETH } from \"../../interfaces/IWstETH.sol\";\nimport { IERC4626 } from \"../../../lib/openzeppelin/interfaces/IERC4626.sol\";\nimport { StableMath } from \"../../utils/StableMath.sol\";\n\nabstract contract AbstractBalancerStrategy is InitializableAbstractStrategy {\n using SafeERC20 for IERC20;\n using StableMath for uint256;\n\n address public immutable rETH;\n address public immutable stETH;\n address public immutable wstETH;\n address public immutable frxETH;\n address public immutable sfrxETH;\n\n /// @notice Address of the Balancer vault\n IBalancerVault public immutable balancerVault;\n /// @notice Balancer pool identifier\n bytes32 public immutable balancerPoolId;\n\n // Max withdrawal deviation denominated in 1e18 (1e18 == 100%)\n uint256 public maxWithdrawalDeviation;\n // Max deposit deviation denominated in 1e18 (1e18 == 100%)\n uint256 public maxDepositDeviation;\n\n int256[48] private __reserved;\n\n struct BaseBalancerConfig {\n address rEthAddress; // Address of the rETH token\n address stEthAddress; // Address of the stETH token\n address wstEthAddress; // Address of the wstETH token\n address frxEthAddress; // Address of the frxEth token\n address sfrxEthAddress; // Address of the sfrxEth token\n address balancerVaultAddress; // Address of the Balancer vault\n bytes32 balancerPoolId; // Balancer pool identifier\n }\n\n event MaxWithdrawalDeviationUpdated(\n uint256 _prevMaxDeviationPercentage,\n uint256 _newMaxDeviationPercentage\n );\n event MaxDepositDeviationUpdated(\n uint256 _prevMaxDeviationPercentage,\n uint256 _newMaxDeviationPercentage\n );\n\n /**\n * @dev Ensure we are not in a Vault context when this function is called, by attempting a no-op internal\n * balance operation. If we are already in a Vault transaction (e.g., a swap, join, or exit), the Vault's\n * reentrancy protection will cause this function to revert.\n *\n * Use this modifier with any function that can cause a state change in a pool and is either public itself,\n * or called by a public function *outside* a Vault operation (e.g., join, exit, or swap).\n *\n * This is to protect against Balancer's read-only re-entrancy vulnerability:\n * https://www.notion.so/originprotocol/Balancer-read-only-reentrancy-c686e72c82414ef18fa34312bb02e11b\n */\n modifier whenNotInBalancerVaultContext() {\n VaultReentrancyLib.ensureNotInVaultContext(balancerVault);\n _;\n }\n\n constructor(BaseBalancerConfig memory _balancerConfig) {\n rETH = _balancerConfig.rEthAddress;\n stETH = _balancerConfig.stEthAddress;\n wstETH = _balancerConfig.wstEthAddress;\n frxETH = _balancerConfig.frxEthAddress;\n sfrxETH = _balancerConfig.sfrxEthAddress;\n\n balancerVault = IBalancerVault(_balancerConfig.balancerVaultAddress);\n balancerPoolId = _balancerConfig.balancerPoolId;\n }\n\n /**\n * Initializer for setting up strategy internal state. This overrides the\n * InitializableAbstractStrategy initializer as Balancer's strategies don't fit\n * well within that abstraction.\n * @param _rewardTokenAddresses Address of BAL & AURA\n * @param _assets Addresses of supported assets. MUST be passed in the same\n * order as returned by coins on the pool contract, i.e.\n * WETH, stETH\n * @param _pTokens Platform Token corresponding addresses\n */\n function initialize(\n address[] calldata _rewardTokenAddresses, // BAL & AURA\n address[] calldata _assets,\n address[] calldata _pTokens\n ) external onlyGovernor initializer {\n maxWithdrawalDeviation = 1e16;\n maxDepositDeviation = 1e16;\n\n emit MaxWithdrawalDeviationUpdated(0, maxWithdrawalDeviation);\n emit MaxDepositDeviationUpdated(0, maxDepositDeviation);\n\n IERC20[] memory poolAssets = _getPoolAssets();\n require(\n poolAssets.length == _assets.length,\n \"Pool assets length mismatch\"\n );\n for (uint256 i = 0; i < _assets.length; ++i) {\n address asset = _fromPoolAsset(address(poolAssets[i]));\n require(_assets[i] == asset, \"Pool assets mismatch\");\n }\n\n InitializableAbstractStrategy._initialize(\n _rewardTokenAddresses,\n _assets,\n _pTokens\n );\n _approveBase();\n }\n\n /**\n * @notice Returns bool indicating whether asset is supported by strategy\n * @param _asset Address of the asset\n */\n function supportsAsset(address _asset) public view override returns (bool) {\n return assetToPToken[_asset] != address(0);\n }\n\n /**\n * @notice Get strategy's share of an assets in the Balancer pool.\n * This is not denominated in OUSD/ETH value of the assets in the Balancer pool.\n * @param _asset Address of the Vault collateral asset\n * @return amount the amount of vault collateral assets\n *\n * IMPORTANT if this function is overridden it needs to have a whenNotInBalancerVaultContext\n * modifier on it or it is susceptible to read-only re-entrancy attack\n *\n * @dev it is important that this function is not affected by reporting inflated\n * values of assets in case of any pool manipulation. Such a manipulation could easily\n * exploit the protocol by:\n * - minting OETH\n * - tilting Balancer pool to report higher balances of assets\n * - rebasing() -> all that extra token balances get distributed to OETH holders\n * - tilting pool back\n * - redeeming OETH\n */\n function checkBalance(address _asset)\n external\n view\n virtual\n override\n whenNotInBalancerVaultContext\n returns (uint256 amount)\n {\n require(assetToPToken[_asset] != address(0), \"Unsupported asset\");\n\n uint256 bptBalance = _getBalancerPoolTokens();\n\n /* To calculate the worth of queried asset:\n * - assume that all tokens normalized to their ETH value have an equal split balance\n * in the pool when it is balanced\n * - multiply the BPT amount with the bpt rate to get the ETH denominated amount\n * of strategy's holdings\n * - divide that by the number of tokens we support in the pool to get ETH denominated\n * amount that is applicable to each supported token in the pool.\n *\n * It would be possible to support only 1 asset in the pool (and be exposed to all\n * the assets while holding BPT tokens) and deposit/withdraw/checkBalance using only\n * that asset. TBD: changes to other functions still required if we ever decide to\n * go with such configuration.\n */\n amount = (bptBalance.mulTruncate(\n IRateProvider(platformAddress).getRate()\n ) / assetsMapped.length);\n\n /* If the pool asset is equal to (strategy )_asset it means that a rate\n * provider for that asset exists and that asset is not necessarily\n * pegged to a unit (ETH).\n *\n * Because this function returns the balance of the asset and is not denominated in\n * ETH units we need to convert the ETH denominated amount to asset amount.\n */\n if (_toPoolAsset(_asset) == _asset) {\n amount = amount.divPrecisely(_getRateProviderRate(_asset));\n }\n }\n\n /**\n * @notice Returns the value of all assets managed by this strategy.\n * Uses the Balancer pool's rate (virtual price) to convert the strategy's\n * Balancer Pool Tokens (BPT) to ETH value.\n * @return value The ETH value\n *\n * IMPORTANT if this function is overridden it needs to have a whenNotInBalancerVaultContext\n * modifier on it or it is susceptible to read-only re-entrancy attack\n */\n function checkBalance()\n external\n view\n virtual\n whenNotInBalancerVaultContext\n returns (uint256 value)\n {\n uint256 bptBalance = _getBalancerPoolTokens();\n\n // Convert BPT to ETH value\n value = bptBalance.mulTruncate(\n IRateProvider(platformAddress).getRate()\n );\n }\n\n /// @notice Balancer Pool Tokens (BPT) in the Balancer pool.\n function _getBalancerPoolTokens()\n internal\n view\n virtual\n returns (uint256 balancerPoolTokens)\n {\n balancerPoolTokens = IERC20(platformAddress).balanceOf(address(this));\n }\n\n /* solhint-disable max-line-length */\n /**\n * @notice BPT price is calculated by taking the rate from the rateProvider of the asset in\n * question. If one does not exist it defaults to 1e18. To get the final BPT expected that\n * is multiplied by the underlying asset amount divided by BPT token rate. BPT token rate is\n * similar to Curve's virtual_price and expresses how much has the price of BPT appreciated\n * (e.g. due to swap fees) in relation to the underlying assets\n *\n * Using the above approach makes the strategy vulnerable to a possible MEV attack using\n * flash loan to manipulate the pool before a deposit/withdrawal since the function ignores\n * market values of the assets being priced in BPT.\n *\n * At the time of writing there is no safe on-chain approach to pricing BPT in a way that it\n * would make it invulnerable to MEV pool manipulation. See recent Balancer exploit:\n * https://www.notion.so/originprotocol/Balancer-OETH-strategy-9becdea132704e588782a919d7d471eb?pvs=4#1cf07de12fc64f1888072321e0644348\n *\n * To mitigate MEV possibilities during deposits and withdraws, the VaultValueChecker will use checkBalance before and after the move\n * to ensure the expected changes took place.\n *\n * @param _asset Address of the Balancer pool asset\n * @param _amount Amount of the Balancer pool asset\n * @return bptExpected of BPT expected in exchange for the asset\n *\n * @dev\n * bptAssetPrice = 1e18 (asset peg) * pool_asset_rate\n *\n * bptExpected = bptAssetPrice * asset_amount / BPT_token_rate\n *\n * bptExpected = 1e18 (asset peg) * pool_asset_rate * asset_amount / BPT_token_rate\n * bptExpected = asset_amount * pool_asset_rate / BPT_token_rate\n *\n * further information available here:\n * https://www.notion.so/originprotocol/Balancer-OETH-strategy-9becdea132704e588782a919d7d471eb?pvs=4#ce01495ae70346d8971f5dced809fb83\n */\n /* solhint-enable max-line-length */\n function _getBPTExpected(address _asset, uint256 _amount)\n internal\n view\n virtual\n returns (uint256 bptExpected)\n {\n uint256 bptRate = IRateProvider(platformAddress).getRate();\n uint256 poolAssetRate = _getRateProviderRate(_asset);\n bptExpected = _amount.mulTruncate(poolAssetRate).divPrecisely(bptRate);\n }\n\n function _getBPTExpected(\n address[] memory _assets,\n uint256[] memory _amounts\n ) internal view virtual returns (uint256 bptExpected) {\n require(_assets.length == _amounts.length, \"Assets & amounts mismatch\");\n\n for (uint256 i = 0; i < _assets.length; ++i) {\n uint256 poolAssetRate = _getRateProviderRate(_assets[i]);\n // convert asset amount to ETH amount\n bptExpected += _amounts[i].mulTruncate(poolAssetRate);\n }\n\n uint256 bptRate = IRateProvider(platformAddress).getRate();\n // Convert ETH amount to BPT amount\n bptExpected = bptExpected.divPrecisely(bptRate);\n }\n\n function _lpDepositAll() internal virtual;\n\n function _lpWithdraw(uint256 numBPTTokens) internal virtual;\n\n function _lpWithdrawAll() internal virtual;\n\n /**\n * @notice Balancer returns assets and rateProviders for corresponding assets ordered\n * by numerical order.\n */\n function _getPoolAssets() internal view returns (IERC20[] memory assets) {\n // slither-disable-next-line unused-return\n (assets, , ) = balancerVault.getPoolTokens(balancerPoolId);\n }\n\n /**\n * @dev If an asset is rebasing the Balancer pools have a wrapped versions of assets\n * that the strategy supports. This function converts the pool(wrapped) asset\n * and corresponding amount to strategy asset.\n */\n function _toPoolAsset(address asset, uint256 amount)\n internal\n view\n returns (address poolAsset, uint256 poolAmount)\n {\n if (asset == stETH) {\n poolAsset = wstETH;\n if (amount > 0) {\n poolAmount = IWstETH(wstETH).getWstETHByStETH(amount);\n }\n } else if (asset == frxETH) {\n poolAsset = sfrxETH;\n if (amount > 0) {\n poolAmount = IERC4626(sfrxETH).convertToShares(amount);\n }\n } else {\n poolAsset = asset;\n poolAmount = amount;\n }\n }\n\n /**\n * @dev Converts a Vault collateral asset to a Balancer pool asset.\n * stETH becomes wstETH, frxETH becomes sfrxETH and everything else stays the same.\n * @param asset Address of the Vault collateral asset.\n * @return Address of the Balancer pool asset.\n */\n function _toPoolAsset(address asset) internal view returns (address) {\n if (asset == stETH) {\n return wstETH;\n } else if (asset == frxETH) {\n return sfrxETH;\n }\n return asset;\n }\n\n /**\n * @dev Converts rebasing asset to its wrapped counterpart.\n */\n function _wrapPoolAsset(address asset, uint256 amount)\n internal\n returns (address wrappedAsset, uint256 wrappedAmount)\n {\n if (asset == stETH) {\n wrappedAsset = wstETH;\n if (amount > 0) {\n wrappedAmount = IWstETH(wstETH).wrap(amount);\n }\n } else if (asset == frxETH) {\n wrappedAsset = sfrxETH;\n if (amount > 0) {\n wrappedAmount = IERC4626(sfrxETH).deposit(\n amount,\n address(this)\n );\n }\n } else {\n wrappedAsset = asset;\n wrappedAmount = amount;\n }\n }\n\n /**\n * @dev Converts wrapped asset to its rebasing counterpart.\n */\n function _unwrapPoolAsset(address asset, uint256 amount)\n internal\n returns (uint256 unwrappedAmount)\n {\n if (asset == stETH) {\n unwrappedAmount = IWstETH(wstETH).unwrap(amount);\n } else if (asset == frxETH) {\n unwrappedAmount = IERC4626(sfrxETH).withdraw(\n amount,\n address(this),\n address(this)\n );\n } else {\n unwrappedAmount = amount;\n }\n }\n\n /**\n * @dev If an asset is rebasing the Balancer pools have a wrapped versions of assets\n * that the strategy supports. This function converts the rebasing strategy asset\n * and corresponding amount to wrapped(pool) asset.\n */\n function _fromPoolAsset(address poolAsset, uint256 poolAmount)\n internal\n view\n returns (address asset, uint256 amount)\n {\n if (poolAsset == wstETH) {\n asset = stETH;\n if (poolAmount > 0) {\n amount = IWstETH(wstETH).getStETHByWstETH(poolAmount);\n }\n } else if (poolAsset == sfrxETH) {\n asset = frxETH;\n if (poolAmount > 0) {\n amount = IERC4626(sfrxETH).convertToAssets(poolAmount);\n }\n } else {\n asset = poolAsset;\n amount = poolAmount;\n }\n }\n\n function _fromPoolAsset(address poolAsset)\n internal\n view\n returns (address asset)\n {\n if (poolAsset == wstETH) {\n asset = stETH;\n } else if (poolAsset == sfrxETH) {\n asset = frxETH;\n } else {\n asset = poolAsset;\n }\n }\n\n /**\n * @notice Sets max withdrawal deviation that is considered when removing\n * liquidity from Balancer pools.\n * @param _maxWithdrawalDeviation Max withdrawal deviation denominated in\n * wad (number with 18 decimals): 1e18 == 100%, 1e16 == 1%\n *\n * IMPORTANT Minimum maxWithdrawalDeviation will be 1% (1e16) for production\n * usage. Vault value checker in combination with checkBalance will\n * catch any unexpected manipulation.\n */\n function setMaxWithdrawalDeviation(uint256 _maxWithdrawalDeviation)\n external\n onlyVaultOrGovernorOrStrategist\n {\n require(\n _maxWithdrawalDeviation <= 1e18,\n \"Withdrawal dev. out of bounds\"\n );\n emit MaxWithdrawalDeviationUpdated(\n maxWithdrawalDeviation,\n _maxWithdrawalDeviation\n );\n maxWithdrawalDeviation = _maxWithdrawalDeviation;\n }\n\n /**\n * @notice Sets max deposit deviation that is considered when adding\n * liquidity to Balancer pools.\n * @param _maxDepositDeviation Max deposit deviation denominated in\n * wad (number with 18 decimals): 1e18 == 100%, 1e16 == 1%\n *\n * IMPORTANT Minimum maxDepositDeviation will default to 1% (1e16)\n * for production usage. Vault value checker in combination with\n * checkBalance will catch any unexpected manipulation.\n */\n function setMaxDepositDeviation(uint256 _maxDepositDeviation)\n external\n onlyVaultOrGovernorOrStrategist\n {\n require(_maxDepositDeviation <= 1e18, \"Deposit dev. out of bounds\");\n emit MaxDepositDeviationUpdated(\n maxDepositDeviation,\n _maxDepositDeviation\n );\n maxDepositDeviation = _maxDepositDeviation;\n }\n\n function _approveBase() internal virtual {\n IERC20 pToken = IERC20(platformAddress);\n // Balancer vault for BPT token (required for removing liquidity)\n pToken.safeApprove(address(balancerVault), type(uint256).max);\n }\n\n function _getRateProviderRate(address _asset)\n internal\n view\n virtual\n returns (uint256);\n}\n" + }, + "contracts/strategies/balancer/BalancerMetaPoolStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title OETH Balancer MetaStablePool Strategy\n * @author Origin Protocol Inc\n */\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport { AbstractAuraStrategy, AbstractBalancerStrategy } from \"./AbstractAuraStrategy.sol\";\nimport { IBalancerVault } from \"../../interfaces/balancer/IBalancerVault.sol\";\nimport { IRateProvider } from \"../../interfaces/balancer/IRateProvider.sol\";\nimport { IMetaStablePool } from \"../../interfaces/balancer/IMetaStablePool.sol\";\nimport { IERC20, InitializableAbstractStrategy } from \"../../utils/InitializableAbstractStrategy.sol\";\nimport { StableMath } from \"../../utils/StableMath.sol\";\n\ncontract BalancerMetaPoolStrategy is AbstractAuraStrategy {\n using SafeERC20 for IERC20;\n using StableMath for uint256;\n\n constructor(\n BaseStrategyConfig memory _stratConfig,\n BaseBalancerConfig memory _balancerConfig,\n address _auraRewardPoolAddress\n )\n InitializableAbstractStrategy(_stratConfig)\n AbstractBalancerStrategy(_balancerConfig)\n AbstractAuraStrategy(_auraRewardPoolAddress)\n {}\n\n /**\n * @notice There are no plans to configure BalancerMetaPool as a default\n * asset strategy. For that reason there is no need to support this\n * functionality.\n */\n function deposit(address, uint256)\n external\n override\n onlyVault\n nonReentrant\n {\n revert(\"Not supported\");\n }\n\n /**\n * @notice There are no plans to configure BalancerMetaPool as a default\n * asset strategy. For that reason there is no need to support this\n * functionality.\n */\n function deposit(address[] calldata, uint256[] calldata)\n external\n onlyVault\n nonReentrant\n {\n revert(\"Not supported\");\n }\n\n /**\n * @notice Deposits all supported assets in this strategy contract to the Balancer pool.\n */\n function depositAll() external override onlyVault nonReentrant {\n uint256 assetsLength = assetsMapped.length;\n address[] memory strategyAssets = new address[](assetsLength);\n uint256[] memory strategyAmounts = new uint256[](assetsLength);\n\n // For each vault collateral asset\n for (uint256 i = 0; i < assetsLength; ++i) {\n strategyAssets[i] = assetsMapped[i];\n // Get the asset balance in this strategy contract\n strategyAmounts[i] = IERC20(strategyAssets[i]).balanceOf(\n address(this)\n );\n }\n _deposit(strategyAssets, strategyAmounts);\n }\n\n /*\n * _deposit doesn't require a read-only re-entrancy protection since during the deposit\n * the function enters the Balancer Vault Context. If this function were called as part of\n * the attacking contract (while intercepting execution flow upon receiving ETH) the read-only\n * protection of the Balancer Vault would be triggered. Since the attacking contract would\n * already be in the Balancer Vault context and wouldn't be able to enter it again.\n */\n function _deposit(\n address[] memory _strategyAssets,\n uint256[] memory _strategyAmounts\n ) internal {\n require(\n _strategyAssets.length == _strategyAmounts.length,\n \"Array length missmatch\"\n );\n\n (IERC20[] memory tokens, , ) = balancerVault.getPoolTokens(\n balancerPoolId\n );\n\n uint256[] memory strategyAssetAmountsToPoolAssetAmounts = new uint256[](\n _strategyAssets.length\n );\n address[] memory strategyAssetsToPoolAssets = new address[](\n _strategyAssets.length\n );\n\n for (uint256 i = 0; i < _strategyAssets.length; ++i) {\n address strategyAsset = _strategyAssets[i];\n uint256 strategyAmount = _strategyAmounts[i];\n\n require(\n assetToPToken[strategyAsset] != address(0),\n \"Unsupported asset\"\n );\n strategyAssetsToPoolAssets[i] = _toPoolAsset(strategyAsset);\n\n if (strategyAmount > 0) {\n emit Deposit(strategyAsset, platformAddress, strategyAmount);\n\n // wrap rebasing assets like stETH and frxETH to wstETH and sfrxETH\n (, strategyAssetAmountsToPoolAssetAmounts[i]) = _wrapPoolAsset(\n strategyAsset,\n strategyAmount\n );\n }\n }\n\n uint256[] memory amountsIn = new uint256[](tokens.length);\n address[] memory poolAssets = new address[](tokens.length);\n for (uint256 i = 0; i < tokens.length; ++i) {\n // Convert IERC20 type to address\n poolAssets[i] = address(tokens[i]);\n\n // For each of the mapped assets\n for (uint256 j = 0; j < strategyAssetsToPoolAssets.length; ++j) {\n // If the pool asset is the same as the mapped asset\n if (poolAssets[i] == strategyAssetsToPoolAssets[j]) {\n amountsIn[i] = strategyAssetAmountsToPoolAssetAmounts[j];\n }\n }\n }\n\n uint256 minBPT = _getBPTExpected(\n strategyAssetsToPoolAssets,\n strategyAssetAmountsToPoolAssetAmounts\n );\n uint256 minBPTwDeviation = minBPT.mulTruncate(\n 1e18 - maxDepositDeviation\n );\n\n /* EXACT_TOKENS_IN_FOR_BPT_OUT:\n * User sends precise quantities of tokens, and receives an\n * estimated but unknown (computed at run time) quantity of BPT.\n *\n * ['uint256', 'uint256[]', 'uint256']\n * [EXACT_TOKENS_IN_FOR_BPT_OUT, amountsIn, minimumBPT]\n */\n bytes memory userData = abi.encode(\n IBalancerVault.WeightedPoolJoinKind.EXACT_TOKENS_IN_FOR_BPT_OUT,\n amountsIn,\n minBPTwDeviation\n );\n\n IBalancerVault.JoinPoolRequest memory request = IBalancerVault\n .JoinPoolRequest(poolAssets, amountsIn, userData, false);\n\n // Add the pool assets in this strategy to the balancer pool\n balancerVault.joinPool(\n balancerPoolId,\n address(this),\n address(this),\n request\n );\n\n // Deposit the Balancer Pool Tokens (BPT) into Aura\n _lpDepositAll();\n }\n\n /**\n * @notice Withdraw a Vault collateral asset from the Balancer pool.\n * @param _recipient Address to receive the Vault collateral assets. Typically is the Vault.\n * @param _strategyAsset Address of the Vault collateral asset\n * @param _strategyAmount The amount of Vault collateral assets to withdraw\n */\n function withdraw(\n address _recipient,\n address _strategyAsset,\n uint256 _strategyAmount\n ) external override onlyVault nonReentrant {\n address[] memory strategyAssets = new address[](1);\n uint256[] memory strategyAmounts = new uint256[](1);\n strategyAssets[0] = _strategyAsset;\n strategyAmounts[0] = _strategyAmount;\n\n _withdraw(_recipient, strategyAssets, strategyAmounts);\n }\n\n /**\n * @notice Withdraw multiple Vault collateral asset from the Balancer pool.\n * @param _recipient Address to receive the Vault collateral assets. Typically is the Vault.\n * @param _strategyAssets Addresses of the Vault collateral assets\n * @param _strategyAmounts The amounts of Vault collateral assets to withdraw\n */\n function withdraw(\n address _recipient,\n address[] calldata _strategyAssets,\n uint256[] calldata _strategyAmounts\n ) external onlyVault nonReentrant {\n _withdraw(_recipient, _strategyAssets, _strategyAmounts);\n }\n\n /**\n * @dev Withdraw multiple Vault collateral asset from the Balancer pool.\n * @param _recipient Address to receive the Vault collateral assets. Typically is the Vault.\n * @param _strategyAssets Addresses of the Vault collateral assets\n * @param _strategyAmounts The amounts of Vault collateral assets to withdraw\n *\n * _withdrawal doesn't require a read-only re-entrancy protection since during the withdrawal\n * the function enters the Balancer Vault Context. If this function were called as part of\n * the attacking contract (while intercepting execution flow upon receiving ETH) the read-only\n * protection of the Balancer Vault would be triggered. Since the attacking contract would\n * already be in the Balancer Vault context and wouldn't be able to enter it again.\n */\n function _withdraw(\n address _recipient,\n address[] memory _strategyAssets,\n uint256[] memory _strategyAmounts\n ) internal {\n require(\n _strategyAssets.length == _strategyAmounts.length,\n \"Invalid input arrays\"\n );\n\n for (uint256 i = 0; i < _strategyAssets.length; ++i) {\n require(\n assetToPToken[_strategyAssets[i]] != address(0),\n \"Unsupported asset\"\n );\n }\n\n // STEP 1 - Calculate the Balancer pool assets and amounts from the vault collateral assets\n\n // Get all the supported balancer pool assets\n (IERC20[] memory tokens, , ) = balancerVault.getPoolTokens(\n balancerPoolId\n );\n // Calculate the balancer pool assets and amounts to withdraw\n uint256[] memory poolAssetsAmountsOut = new uint256[](tokens.length);\n address[] memory poolAssets = new address[](tokens.length);\n // Is the wrapped asset amount indexed by the assets array, not the order of the Balancer pool tokens\n // eg wstETH and sfrxETH amounts, not the stETH and frxETH amounts\n uint256[] memory strategyAssetsToPoolAssetsAmounts = new uint256[](\n _strategyAssets.length\n );\n\n // For each of the Balancer pool assets\n for (uint256 i = 0; i < tokens.length; ++i) {\n poolAssets[i] = address(tokens[i]);\n\n // Convert the Balancer pool asset back to a vault collateral asset\n address strategyAsset = _fromPoolAsset(poolAssets[i]);\n\n // for each of the vault assets\n for (uint256 j = 0; j < _strategyAssets.length; ++j) {\n // If the vault asset equals the vault asset mapped from the Balancer pool asset\n if (_strategyAssets[j] == strategyAsset) {\n (, poolAssetsAmountsOut[i]) = _toPoolAsset(\n strategyAsset,\n _strategyAmounts[j]\n );\n strategyAssetsToPoolAssetsAmounts[j] = poolAssetsAmountsOut[\n i\n ];\n\n /* Because of the potential Balancer rounding error mentioned below\n * the contract might receive 1-2 WEI smaller amount than required\n * in the withdraw user data encoding. If slightly lesser token amount\n * is received the strategy can not unwrap the pool asset as it is\n * smaller than expected.\n *\n * For that reason we `overshoot` the required tokens expected to\n * circumvent the error\n */\n if (poolAssetsAmountsOut[i] > 0) {\n poolAssetsAmountsOut[i] += 2;\n }\n }\n }\n }\n\n // STEP 2 - Calculate the max about of Balancer Pool Tokens (BPT) to withdraw\n\n // Estimate the required amount of Balancer Pool Tokens (BPT) for the assets\n uint256 maxBPTtoWithdraw = _getBPTExpected(\n poolAssets,\n /* all non 0 values are overshot by 2 WEI and with the expected mainnet\n * ~1% withdrawal deviation, the 2 WEI aren't important\n */\n poolAssetsAmountsOut\n );\n // Increase BPTs by the max allowed deviation\n // Any excess BPTs will be left in this strategy contract\n maxBPTtoWithdraw = maxBPTtoWithdraw.mulTruncate(\n 1e18 + maxWithdrawalDeviation\n );\n\n // STEP 3 - Withdraw the Balancer Pool Tokens (BPT) from Aura to this strategy contract\n\n // Withdraw BPT from Aura allowing for BPTs left in this strategy contract from previous withdrawals\n _lpWithdraw(\n maxBPTtoWithdraw - IERC20(platformAddress).balanceOf(address(this))\n );\n\n // STEP 4 - Withdraw the balancer pool assets from the pool\n\n /* Custom asset exit: BPT_IN_FOR_EXACT_TOKENS_OUT:\n * User sends an estimated but unknown (computed at run time) quantity of BPT,\n * and receives precise quantities of specified tokens.\n *\n * ['uint256', 'uint256[]', 'uint256']\n * [BPT_IN_FOR_EXACT_TOKENS_OUT, amountsOut, maxBPTAmountIn]\n */\n bytes memory userData = abi.encode(\n IBalancerVault.WeightedPoolExitKind.BPT_IN_FOR_EXACT_TOKENS_OUT,\n poolAssetsAmountsOut,\n maxBPTtoWithdraw\n );\n\n IBalancerVault.ExitPoolRequest memory request = IBalancerVault\n .ExitPoolRequest(\n poolAssets,\n /* We specify the exact amount of a tokens we are expecting in the encoded\n * userData, for that reason we don't need to specify the amountsOut here.\n *\n * Also Balancer has a rounding issue that can make a transaction fail:\n * https://github.com/balancer/balancer-v2-monorepo/issues/2541\n * which is an extra reason why this field is empty.\n */\n new uint256[](tokens.length),\n userData,\n false\n );\n\n balancerVault.exitPool(\n balancerPoolId,\n address(this),\n /* Payable keyword is required because of the IBalancerVault interface even though\n * this strategy shall never be receiving native ETH\n */\n payable(address(this)),\n request\n );\n\n // STEP 5 - Re-deposit any left over BPT tokens back into Aura\n /* When concluding how much of BPT we need to withdraw from Aura we overshoot by\n * roughly around 1% (initial mainnet setting of maxWithdrawalDeviation). After exiting\n * the pool strategy could have left over BPT tokens that are not earning boosted yield.\n * We re-deploy those back in.\n */\n _lpDepositAll();\n\n // STEP 6 - Unswap balancer pool assets to vault collateral assets and send to the vault.\n\n // For each of the specified assets\n for (uint256 i = 0; i < _strategyAssets.length; ++i) {\n // Unwrap assets like wstETH and sfrxETH to rebasing assets stETH and frxETH\n if (strategyAssetsToPoolAssetsAmounts[i] > 0) {\n _unwrapPoolAsset(\n _strategyAssets[i],\n strategyAssetsToPoolAssetsAmounts[i]\n );\n }\n\n // Transfer the vault collateral assets to the recipient, which is typically the vault\n if (_strategyAmounts[i] > 0) {\n IERC20(_strategyAssets[i]).safeTransfer(\n _recipient,\n _strategyAmounts[i]\n );\n\n emit Withdrawal(\n _strategyAssets[i],\n platformAddress,\n _strategyAmounts[i]\n );\n }\n }\n }\n\n /**\n * @notice Withdraws all supported Vault collateral assets from the Balancer pool\n * and send to the OToken's Vault.\n *\n * Is only executable by the OToken's Vault or the Governor.\n */\n function withdrawAll() external override onlyVaultOrGovernor nonReentrant {\n // STEP 1 - Withdraw all Balancer Pool Tokens (BPT) from Aura to this strategy contract\n\n _lpWithdrawAll();\n // Get the BPTs withdrawn from Aura plus any that were already in this strategy contract\n uint256 BPTtoWithdraw = IERC20(platformAddress).balanceOf(\n address(this)\n );\n // Get the balancer pool assets and their total balances\n (IERC20[] memory tokens, , ) = balancerVault.getPoolTokens(\n balancerPoolId\n );\n uint256[] memory minAmountsOut = new uint256[](tokens.length);\n address[] memory poolAssets = new address[](tokens.length);\n for (uint256 i = 0; i < tokens.length; ++i) {\n poolAssets[i] = address(tokens[i]);\n }\n\n // STEP 2 - Withdraw the Balancer pool assets from the pool\n /* Proportional exit: EXACT_BPT_IN_FOR_TOKENS_OUT:\n * User sends a precise quantity of BPT, and receives an estimated but unknown\n * (computed at run time) quantity of a single token\n *\n * ['uint256', 'uint256']\n * [EXACT_BPT_IN_FOR_TOKENS_OUT, bptAmountIn]\n *\n * It is ok to pass an empty minAmountsOut since tilting the pool in any direction\n * when doing a proportional exit can only be beneficial to the strategy. Since\n * it will receive more of the underlying tokens for the BPT traded in.\n */\n bytes memory userData = abi.encode(\n IBalancerVault.WeightedPoolExitKind.EXACT_BPT_IN_FOR_TOKENS_OUT,\n BPTtoWithdraw\n );\n\n IBalancerVault.ExitPoolRequest memory request = IBalancerVault\n .ExitPoolRequest(poolAssets, minAmountsOut, userData, false);\n\n balancerVault.exitPool(\n balancerPoolId,\n address(this),\n /* Payable keyword is required because of the IBalancerVault interface even though\n * this strategy shall never be receiving native ETH\n */\n payable(address(this)),\n request\n );\n\n // STEP 3 - Convert the balancer pool assets to the vault collateral assets and send to the vault\n // For each of the Balancer pool assets\n for (uint256 i = 0; i < tokens.length; ++i) {\n address poolAsset = address(tokens[i]);\n // Convert the balancer pool asset to the strategy asset\n address strategyAsset = _fromPoolAsset(poolAsset);\n // Get the balancer pool assets withdraw from the pool plus any that were already in this strategy contract\n uint256 poolAssetAmount = IERC20(poolAsset).balanceOf(\n address(this)\n );\n\n // Unwrap assets like wstETH and sfrxETH to rebasing assets stETH and frxETH\n uint256 unwrappedAmount = 0;\n if (poolAssetAmount > 0) {\n unwrappedAmount = _unwrapPoolAsset(\n strategyAsset,\n poolAssetAmount\n );\n }\n\n // Transfer the vault collateral assets to the vault\n if (unwrappedAmount > 0) {\n IERC20(strategyAsset).safeTransfer(\n vaultAddress,\n unwrappedAmount\n );\n emit Withdrawal(\n strategyAsset,\n platformAddress,\n unwrappedAmount\n );\n }\n }\n }\n\n /**\n * @notice Approves the Balancer Vault to transfer poolAsset counterparts\n * of all of the supported assets from this strategy. E.g. stETH is a supported\n * strategy and Balancer Vault gets unlimited approval to transfer wstETH.\n *\n * If Balancer pool uses a wrapped version of a supported asset then also approve\n * unlimited usage of an asset to the contract responsible for wrapping.\n *\n * Approve unlimited spending by Balancer Vault and Aura reward pool of the\n * pool BPT tokens.\n *\n * Is only executable by the Governor.\n */\n function safeApproveAllTokens()\n external\n override\n onlyGovernor\n nonReentrant\n {\n uint256 assetCount = assetsMapped.length;\n for (uint256 i = 0; i < assetCount; ++i) {\n _abstractSetPToken(assetsMapped[i], platformAddress);\n }\n _approveBase();\n }\n\n // solhint-disable-next-line no-unused-vars\n function _abstractSetPToken(address _asset, address) internal override {\n address poolAsset = _toPoolAsset(_asset);\n if (_asset == stETH) {\n // slither-disable-next-line unused-return\n IERC20(stETH).approve(wstETH, type(uint256).max);\n } else if (_asset == frxETH) {\n // slither-disable-next-line unused-return\n IERC20(frxETH).approve(sfrxETH, type(uint256).max);\n }\n _approveAsset(poolAsset);\n }\n\n /**\n * @dev Approves the Balancer Vault to transfer an asset from\n * this strategy. The assets could be a Vault collateral asset\n * like WETH or rETH; or a Balancer pool asset that wraps the vault asset\n * like wstETH or sfrxETH.\n */\n function _approveAsset(address _asset) internal {\n IERC20 asset = IERC20(_asset);\n // slither-disable-next-line unused-return\n asset.approve(address(balancerVault), type(uint256).max);\n }\n\n /**\n * @notice Returns the rate supplied by the Balancer configured rate\n * provider. Rate is used to normalize the token to common underlying\n * pool denominator. (ETH for ETH Liquid staking derivatives)\n *\n * @param _asset Address of the Balancer pool asset\n * @return rate of the corresponding asset\n */\n function _getRateProviderRate(address _asset)\n internal\n view\n override\n returns (uint256)\n {\n IMetaStablePool pool = IMetaStablePool(platformAddress);\n IRateProvider[] memory providers = pool.getRateProviders();\n (IERC20[] memory tokens, , ) = balancerVault.getPoolTokens(\n balancerPoolId\n );\n\n uint256 providersLength = providers.length;\n for (uint256 i = 0; i < providersLength; ++i) {\n // _assets and corresponding rate providers are all in the same order\n if (address(tokens[i]) == _asset) {\n // rate provider doesn't exist, defaults to 1e18\n if (address(providers[i]) == address(0)) {\n return 1e18;\n }\n return providers[i].getRate();\n }\n }\n\n // should never happen\n assert(false);\n }\n}\n" + }, + "contracts/strategies/balancer/VaultReentrancyLib.sol": { + "content": "// SPDX-License-Identifier: GPL-3.0-or-later\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see .\n\npragma solidity >=0.7.0 <0.9.0;\n\nimport \"../../utils/BalancerErrors.sol\";\nimport { IBalancerVault } from \"../../interfaces/balancer/IBalancerVault.sol\";\n\nlibrary VaultReentrancyLib {\n /**\n * @dev Ensure we are not in a Vault context when this function is called, by attempting a no-op internal\n * balance operation. If we are already in a Vault transaction (e.g., a swap, join, or exit), the Vault's\n * reentrancy protection will cause this function to revert.\n *\n * The exact function call doesn't really matter: we're just trying to trigger the Vault reentrancy check\n * (and not hurt anything in case it works). An empty operation array with no specific operation at all works\n * for that purpose, and is also the least expensive in terms of gas and bytecode size.\n *\n * Call this at the top of any function that can cause a state change in a pool and is either public itself,\n * or called by a public function *outside* a Vault operation (e.g., join, exit, or swap).\n *\n * If this is *not* called in functions that are vulnerable to the read-only reentrancy issue described\n * here (https://forum.balancer.fi/t/reentrancy-vulnerability-scope-expanded/4345), those functions are unsafe,\n * and subject to manipulation that may result in loss of funds.\n */\n function ensureNotInVaultContext(IBalancerVault vault) internal view {\n // Perform the following operation to trigger the Vault's reentrancy guard:\n //\n // IBalancerVault.UserBalanceOp[] memory noop = new IBalancerVault.UserBalanceOp[](0);\n // _vault.manageUserBalance(noop);\n //\n // However, use a static call so that it can be a view function (even though the function is non-view).\n // This allows the library to be used more widely, as some functions that need to be protected might be\n // view.\n //\n // This staticcall always reverts, but we need to make sure it doesn't fail due to a re-entrancy attack.\n // Staticcalls consume all gas forwarded to them on a revert caused by storage modification.\n // By default, almost the entire available gas is forwarded to the staticcall,\n // causing the entire call to revert with an 'out of gas' error.\n //\n // We set the gas limit to 10k for the staticcall to\n // avoid wasting gas when it reverts due to storage modification.\n // `manageUserBalance` is a non-reentrant function in the Vault, so calling it invokes `_enterNonReentrant`\n // in the `ReentrancyGuard` contract, reproduced here:\n //\n // function _enterNonReentrant() private {\n // // If the Vault is actually being reentered, it will revert in the first line, at the `_require` that\n // // checks the reentrancy flag, with \"BAL#400\" (corresponding to Errors.REENTRANCY) in the revertData.\n // // The full revertData will be: `abi.encodeWithSignature(\"Error(string)\", \"BAL#400\")`.\n // _require(_status != _ENTERED, Errors.REENTRANCY);\n //\n // // If the Vault is not being reentered, the check above will pass: but it will *still* revert,\n // // because the next line attempts to modify storage during a staticcall. However, this type of\n // // failure results in empty revertData.\n // _status = _ENTERED;\n // }\n //\n // So based on this analysis, there are only two possible revertData values: empty, or abi.encoded BAL#400.\n //\n // It is of course much more bytecode and gas efficient to check for zero-length revertData than to compare it\n // to the encoded REENTRANCY revertData.\n //\n // While it should be impossible for the call to fail in any other way (especially since it reverts before\n // `manageUserBalance` even gets called), any other error would generate non-zero revertData, so checking for\n // empty data guards against this case too.\n\n (, bytes memory revertData) = address(vault).staticcall{ gas: 10_000 }(\n abi.encodeWithSelector(vault.manageUserBalance.selector, 0)\n );\n\n _require(revertData.length == 0, Errors.REENTRANCY);\n }\n}\n" + }, + "contracts/strategies/BaseCurveAMOStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title Curve Automated Market Maker (AMO) Strategy\n * @notice AMO strategy for the Curve OETH/WETH pool\n * @author Origin Protocol Inc\n */\nimport { Math } from \"@openzeppelin/contracts/utils/math/Math.sol\";\nimport { SafeCast } from \"@openzeppelin/contracts/utils/math/SafeCast.sol\";\n\nimport { IERC20, InitializableAbstractStrategy } from \"../utils/InitializableAbstractStrategy.sol\";\nimport { StableMath } from \"../utils/StableMath.sol\";\nimport { IVault } from \"../interfaces/IVault.sol\";\nimport { IWETH9 } from \"../interfaces/IWETH9.sol\";\nimport { ICurveStableSwapNG } from \"../interfaces/ICurveStableSwapNG.sol\";\nimport { ICurveXChainLiquidityGauge } from \"../interfaces/ICurveXChainLiquidityGauge.sol\";\nimport { IChildLiquidityGaugeFactory } from \"../interfaces/IChildLiquidityGaugeFactory.sol\";\n\ncontract BaseCurveAMOStrategy is InitializableAbstractStrategy {\n using StableMath for uint256;\n using SafeCast for uint256;\n\n /**\n * @dev a threshold under which the contract no longer allows for the protocol to manually rebalance.\n * Guarding against a strategist / guardian being taken over and with multiple transactions\n * draining the protocol funds.\n */\n uint256 public constant SOLVENCY_THRESHOLD = 0.998 ether;\n\n // New immutable variables that must be set in the constructor\n /**\n * @notice Address of the Wrapped ETH (WETH) contract.\n */\n IWETH9 public immutable weth;\n\n /**\n * @notice Address of the OETH token contract.\n */\n IERC20 public immutable oeth;\n\n /**\n * @notice Address of the LP (Liquidity Provider) token contract.\n */\n IERC20 public immutable lpToken;\n\n /**\n * @notice Address of the Curve StableSwap NG pool contract.\n */\n ICurveStableSwapNG public immutable curvePool;\n\n /**\n * @notice Address of the Curve X-Chain Liquidity Gauge contract.\n */\n ICurveXChainLiquidityGauge public immutable gauge;\n\n /**\n * @notice Address of the Child Liquidity Gauge Factory contract.\n */\n IChildLiquidityGaugeFactory public immutable gaugeFactory;\n\n // Ordered list of pool assets\n uint128 public immutable oethCoinIndex;\n uint128 public immutable wethCoinIndex;\n\n /**\n * @notice Maximum slippage allowed for adding/removing liquidity from the Curve pool.\n */\n uint256 public maxSlippage;\n\n event MaxSlippageUpdated(uint256 newMaxSlippage);\n\n /**\n * @dev Verifies that the caller is the Strategist.\n */\n modifier onlyStrategist() {\n require(\n msg.sender == IVault(vaultAddress).strategistAddr(),\n \"Caller is not the Strategist\"\n );\n _;\n }\n\n /**\n * @dev Checks the Curve pool's balances have improved and the balances\n * have not tipped to the other side.\n * This modifier is only applied to functions that do a single sided add or remove.\n * The standard deposit function adds to both sides of the pool in a way that\n * the pool's balance is not worsened.\n * Withdrawals are proportional so doesn't change the pools asset balance.\n */\n modifier improvePoolBalance() {\n // Get the asset and OToken balances in the Curve pool\n uint256[] memory balancesBefore = curvePool.get_balances();\n // diff = ETH balance - OETH balance\n int256 diffBefore = balancesBefore[wethCoinIndex].toInt256() -\n balancesBefore[oethCoinIndex].toInt256();\n\n _;\n\n // Get the asset and OToken balances in the Curve pool\n uint256[] memory balancesAfter = curvePool.get_balances();\n // diff = ETH balance - OETH balance\n int256 diffAfter = balancesAfter[wethCoinIndex].toInt256() -\n balancesAfter[oethCoinIndex].toInt256();\n\n if (diffBefore == 0) {\n require(diffAfter == 0, \"Position balance is worsened\");\n } else if (diffBefore < 0) {\n // If the pool was originally imbalanced in favor of OETH, then\n // we want to check that the pool is now more balanced\n require(diffAfter <= 0, \"OTokens overshot peg\");\n require(diffBefore < diffAfter, \"OTokens balance worse\");\n } else if (diffBefore > 0) {\n // If the pool was originally imbalanced in favor of ETH, then\n // we want to check that the pool is now more balanced\n require(diffAfter >= 0, \"Assets overshot peg\");\n require(diffAfter < diffBefore, \"Assets balance worse\");\n }\n }\n\n constructor(\n BaseStrategyConfig memory _baseConfig,\n address _oeth,\n address _weth,\n address _gauge,\n address _gaugeFactory,\n uint128 _oethCoinIndex,\n uint128 _wethCoinIndex\n ) InitializableAbstractStrategy(_baseConfig) {\n oethCoinIndex = _oethCoinIndex;\n wethCoinIndex = _wethCoinIndex;\n\n lpToken = IERC20(_baseConfig.platformAddress);\n curvePool = ICurveStableSwapNG(_baseConfig.platformAddress);\n\n oeth = IERC20(_oeth);\n weth = IWETH9(_weth);\n gauge = ICurveXChainLiquidityGauge(_gauge);\n gaugeFactory = IChildLiquidityGaugeFactory(_gaugeFactory);\n\n _setGovernor(address(0));\n }\n\n /**\n * Initializer for setting up strategy internal state. This overrides the\n * InitializableAbstractStrategy initializer as Curve strategies don't fit\n * well within that abstraction.\n * @param _rewardTokenAddresses Address of CRV\n * @param _maxSlippage Maximum slippage allowed for adding/removing liquidity from the Curve pool.\n */\n function initialize(\n address[] calldata _rewardTokenAddresses, // CRV\n uint256 _maxSlippage\n ) external onlyGovernor initializer {\n address[] memory pTokens = new address[](1);\n pTokens[0] = address(curvePool);\n\n address[] memory _assets = new address[](1);\n _assets[0] = address(weth);\n\n InitializableAbstractStrategy._initialize(\n _rewardTokenAddresses,\n _assets,\n pTokens\n );\n\n _approveBase();\n _setMaxSlippage(_maxSlippage);\n }\n\n /***************************************\n Deposit\n ****************************************/\n\n /**\n * @notice Deposit WETH into the Curve pool\n * @param _weth Address of Wrapped ETH (WETH) contract.\n * @param _amount Amount of WETH to deposit.\n */\n function deposit(address _weth, uint256 _amount)\n external\n override\n onlyVault\n nonReentrant\n {\n _deposit(_weth, _amount);\n }\n\n function _deposit(address _weth, uint256 _wethAmount) internal {\n require(_wethAmount > 0, \"Must deposit something\");\n require(_weth == address(weth), \"Can only deposit WETH\");\n\n emit Deposit(_weth, address(lpToken), _wethAmount);\n\n // Get the asset and OToken balances in the Curve pool\n uint256[] memory balances = curvePool.get_balances();\n // safe to cast since min value is at least 0\n uint256 oethToAdd = uint256(\n _max(\n 0,\n balances[wethCoinIndex].toInt256() +\n _wethAmount.toInt256() -\n balances[oethCoinIndex].toInt256()\n )\n );\n\n /* Add so much OETH so that the pool ends up being balanced. And at minimum\n * add as much OETH as WETH and at maximum twice as much OETH.\n */\n oethToAdd = Math.max(oethToAdd, _wethAmount);\n oethToAdd = Math.min(oethToAdd, _wethAmount * 2);\n\n /* Mint OETH with a strategy that attempts to contribute to stability of OETH/WETH pool. Try\n * to mint so much OETH that after deployment of liquidity pool ends up being balanced.\n *\n * To manage unpredictability minimal OETH minted will always be at least equal or greater\n * to WETH amount deployed. And never larger than twice the WETH amount deployed even if\n * it would have a further beneficial effect on pool stability.\n */\n IVault(vaultAddress).mintForStrategy(oethToAdd);\n\n emit Deposit(address(oeth), address(lpToken), oethToAdd);\n\n uint256[] memory _amounts = new uint256[](2);\n _amounts[wethCoinIndex] = _wethAmount;\n _amounts[oethCoinIndex] = oethToAdd;\n\n uint256 valueInLpTokens = (_wethAmount + oethToAdd).divPrecisely(\n curvePool.get_virtual_price()\n );\n uint256 minMintAmount = valueInLpTokens.mulTruncate(\n uint256(1e18) - maxSlippage\n );\n\n // Do the deposit to the Curve pool\n uint256 lpDeposited = curvePool.add_liquidity(_amounts, minMintAmount);\n require(lpDeposited >= minMintAmount, \"Min LP amount error\");\n\n // Deposit the Curve pool's LP tokens into the Curve gauge\n gauge.deposit(lpDeposited);\n\n // Ensure solvency of the vault\n _solvencyAssert();\n }\n\n /**\n * @notice Deposit the strategy's entire balance of WETH into the Curve pool\n */\n function depositAll() external override onlyVault nonReentrant {\n uint256 balance = weth.balanceOf(address(this));\n if (balance > 0) {\n _deposit(address(weth), balance);\n }\n }\n\n /***************************************\n Withdraw\n ****************************************/\n\n /**\n * @notice Withdraw ETH and OETH from the Curve pool, burn the OETH,\n * convert the ETH to WETH and transfer to the recipient.\n * @param _recipient Address to receive withdrawn asset which is normally the Vault.\n * @param _weth Address of the Wrapped ETH (WETH) contract.\n * @param _amount Amount of WETH to withdraw.\n */\n function withdraw(\n address _recipient,\n address _weth,\n uint256 _amount\n ) external override onlyVault nonReentrant {\n require(_amount > 0, \"Must withdraw something\");\n require(_weth == address(weth), \"Can only withdraw WETH\");\n\n emit Withdrawal(_weth, address(lpToken), _amount);\n\n uint256 requiredLpTokens = calcTokenToBurn(_amount);\n\n _lpWithdraw(requiredLpTokens);\n\n /* math in requiredLpTokens should correctly calculate the amount of LP to remove\n * in that the strategy receives enough WETH on balanced removal\n */\n uint256[] memory _minWithdrawalAmounts = new uint256[](2);\n _minWithdrawalAmounts[wethCoinIndex] = _amount;\n // slither-disable-next-line unused-return\n curvePool.remove_liquidity(requiredLpTokens, _minWithdrawalAmounts);\n\n // Burn all the removed OETH and any that was left in the strategy\n uint256 oethToBurn = oeth.balanceOf(address(this));\n IVault(vaultAddress).burnForStrategy(oethToBurn);\n\n emit Withdrawal(address(oeth), address(lpToken), oethToBurn);\n\n // Transfer WETH to the recipient\n require(\n weth.transfer(_recipient, _amount),\n \"Transfer of WETH not successful\"\n );\n\n // Ensure solvency of the vault\n _solvencyAssert();\n }\n\n function calcTokenToBurn(uint256 _wethAmount)\n internal\n view\n returns (uint256 lpToBurn)\n {\n /* The rate between coins in the pool determines the rate at which pool returns\n * tokens when doing balanced removal (remove_liquidity call). And by knowing how much WETH\n * we want we can determine how much of OETH we receive by removing liquidity.\n *\n * Because we are doing balanced removal we should be making profit when removing liquidity in a\n * pool tilted to either side.\n *\n * Important: A downside is that the Strategist / Governor needs to be\n * cognisant of not removing too much liquidity. And while the proposal to remove liquidity\n * is being voted on the pool tilt might change so much that the proposal that has been valid while\n * created is no longer valid.\n */\n\n uint256 poolWETHBalance = curvePool.balances(wethCoinIndex);\n /* K is multiplied by 1e36 which is used for higher precision calculation of required\n * pool LP tokens. Without it the end value can have rounding errors up to precision of\n * 10 digits. This way we move the decimal point by 36 places when doing the calculation\n * and again by 36 places when we are done with it.\n */\n uint256 k = (1e36 * lpToken.totalSupply()) / poolWETHBalance;\n // prettier-ignore\n // slither-disable-next-line divide-before-multiply\n uint256 diff = (_wethAmount + 1) * k;\n lpToBurn = diff / 1e36;\n }\n\n /**\n * @notice Remove all ETH and OETH from the Curve pool, burn the OETH,\n * convert the ETH to WETH and transfer to the Vault contract.\n */\n function withdrawAll() external override onlyVaultOrGovernor nonReentrant {\n uint256 gaugeTokens = gauge.balanceOf(address(this));\n // Can not withdraw zero LP tokens from the gauge\n if (gaugeTokens == 0) return;\n _lpWithdraw(gaugeTokens);\n\n // Withdraws are proportional to assets held by 3Pool\n uint256[] memory minWithdrawAmounts = new uint256[](2);\n\n // Remove liquidity\n // slither-disable-next-line unused-return\n curvePool.remove_liquidity(\n lpToken.balanceOf(address(this)),\n minWithdrawAmounts\n );\n\n // Burn all OETH\n uint256 oethToBurn = oeth.balanceOf(address(this));\n IVault(vaultAddress).burnForStrategy(oethToBurn);\n\n // Get the strategy contract's WETH balance.\n // This includes all that was removed from the Curve pool and\n // any ether that was sitting in the strategy contract before the removal.\n uint256 ethBalance = weth.balanceOf(address(this));\n require(\n weth.transfer(vaultAddress, ethBalance),\n \"Transfer of WETH not successful\"\n );\n\n emit Withdrawal(address(weth), address(lpToken), ethBalance);\n emit Withdrawal(address(oeth), address(lpToken), oethToBurn);\n }\n\n /***************************************\n Curve pool Rebalancing\n ****************************************/\n\n /**\n * @notice Mint OTokens and one-sided add to the Curve pool.\n * This is used when the Curve pool does not have enough OTokens and too many ETH.\n * The OToken/Asset, eg OETH/ETH, price with increase.\n * The amount of assets in the vault is unchanged.\n * The total supply of OTokens is increased.\n * The asset value of the strategy and vault is increased.\n * @param _oTokens The amount of OTokens to be minted and added to the pool.\n */\n function mintAndAddOTokens(uint256 _oTokens)\n external\n onlyStrategist\n nonReentrant\n improvePoolBalance\n {\n IVault(vaultAddress).mintForStrategy(_oTokens);\n\n uint256[] memory amounts = new uint256[](2);\n amounts[oethCoinIndex] = _oTokens;\n\n // Convert OETH to Curve pool LP tokens\n uint256 valueInLpTokens = (_oTokens).divPrecisely(\n curvePool.get_virtual_price()\n );\n // Apply slippage to LP tokens\n uint256 minMintAmount = valueInLpTokens.mulTruncate(\n uint256(1e18) - maxSlippage\n );\n\n // Add the minted OTokens to the Curve pool\n uint256 lpDeposited = curvePool.add_liquidity(amounts, minMintAmount);\n require(lpDeposited >= minMintAmount, \"Min LP amount error\");\n\n // Deposit the Curve pool LP tokens to the Curve gauge\n gauge.deposit(lpDeposited);\n\n // Ensure solvency of the vault\n _solvencyAssert();\n\n emit Deposit(address(oeth), address(lpToken), _oTokens);\n }\n\n /**\n * @notice One-sided remove of OTokens from the Curve pool which are then burned.\n * This is used when the Curve pool has too many OTokens and not enough ETH.\n * The amount of assets in the vault is unchanged.\n * The total supply of OTokens is reduced.\n * The asset value of the strategy and vault is reduced.\n * @param _lpTokens The amount of Curve pool LP tokens to be burned for OTokens.\n */\n function removeAndBurnOTokens(uint256 _lpTokens)\n external\n onlyStrategist\n nonReentrant\n improvePoolBalance\n {\n // Withdraw Curve pool LP tokens from Convex and remove OTokens from the Curve pool\n uint256 oethToBurn = _withdrawAndRemoveFromPool(\n _lpTokens,\n oethCoinIndex\n );\n\n // The vault burns the OTokens from this strategy\n IVault(vaultAddress).burnForStrategy(oethToBurn);\n\n // Ensure solvency of the vault\n _solvencyAssert();\n\n emit Withdrawal(address(oeth), address(lpToken), oethToBurn);\n }\n\n /**\n * @notice One-sided remove of ETH from the Curve pool, convert to WETH\n * and transfer to the vault.\n * This is used when the Curve pool does not have enough OTokens and too many ETH.\n * The OToken/Asset, eg OETH/ETH, price with decrease.\n * The amount of assets in the vault increases.\n * The total supply of OTokens does not change.\n * The asset value of the strategy reduces.\n * The asset value of the vault should be close to the same.\n * @param _lpTokens The amount of Curve pool LP tokens to be burned for ETH.\n * @dev Curve pool LP tokens is used rather than WETH assets as Curve does not\n * have a way to accurately calculate the amount of LP tokens for a required\n * amount of ETH. Curve's `calc_token_amount` functioun does not include fees.\n * A 3rd party libary can be used that takes into account the fees, but this\n * is a gas intensive process. It's easier for the trusted strategist to\n * caclulate the amount of Curve pool LP tokens required off-chain.\n */\n function removeOnlyAssets(uint256 _lpTokens)\n external\n onlyStrategist\n nonReentrant\n improvePoolBalance\n {\n // Withdraw Curve pool LP tokens from Curve gauge and remove ETH from the Curve pool\n uint256 ethAmount = _withdrawAndRemoveFromPool(\n _lpTokens,\n wethCoinIndex\n );\n\n // Transfer WETH to the vault\n require(\n weth.transfer(vaultAddress, ethAmount),\n \"Transfer of WETH not successful\"\n );\n\n // Ensure solvency of the vault\n _solvencyAssert();\n\n emit Withdrawal(address(weth), address(lpToken), ethAmount);\n }\n\n /**\n * @dev Remove Curve pool LP tokens from the Convex pool and\n * do a one-sided remove of ETH or OETH from the Curve pool.\n * @param _lpTokens The amount of Curve pool LP tokens to be removed from the Convex pool.\n * @param coinIndex The index of the coin to be removed from the Curve pool. 0 = ETH, 1 = OETH.\n * @return coinsRemoved The amount of ETH or OETH removed from the Curve pool.\n */\n function _withdrawAndRemoveFromPool(uint256 _lpTokens, uint128 coinIndex)\n internal\n returns (uint256 coinsRemoved)\n {\n // Withdraw Curve pool LP tokens from Curve gauge\n _lpWithdraw(_lpTokens);\n\n // Convert Curve pool LP tokens to ETH value\n uint256 valueInEth = _lpTokens.mulTruncate(\n curvePool.get_virtual_price()\n );\n // Apply slippage to ETH value\n uint256 minAmount = valueInEth.mulTruncate(uint256(1e18) - maxSlippage);\n\n // Remove just the ETH from the Curve pool\n coinsRemoved = curvePool.remove_liquidity_one_coin(\n _lpTokens,\n int128(coinIndex),\n minAmount,\n address(this)\n );\n }\n\n /**\n * Checks that the protocol is solvent, protecting from a rogue Strategist / Guardian that can\n * keep rebalancing the pool in both directions making the protocol lose a tiny amount of\n * funds each time.\n *\n * Protocol must be at least SOLVENCY_THRESHOLD (99,8 %) backed in order for the rebalances to\n * function.\n */\n function _solvencyAssert() internal view {\n uint256 _totalVaultValue = IVault(vaultAddress).totalValue();\n uint256 _totalOethbSupply = oeth.totalSupply();\n\n if (\n _totalVaultValue.divPrecisely(_totalOethbSupply) <\n SOLVENCY_THRESHOLD\n ) {\n revert(\"Protocol insolvent\");\n }\n }\n\n /***************************************\n Assets and Rewards\n ****************************************/\n\n /**\n * @notice Collect accumulated CRV (and other) rewards and send to the Harvester.\n */\n function collectRewardTokens()\n external\n override\n onlyHarvester\n nonReentrant\n {\n // CRV rewards flow.\n //---\n // CRV inflation:\n // Gauge receive CRV rewards from inflation.\n // Each checkpoint on the gauge send this CRV inflation to gauge factory.\n // This strategy should call mint on the gauge factory to collect the CRV rewards.\n // ---\n // Extra rewards:\n // Calling claim_rewards on the gauge will only claim extra rewards (outside of CRV).\n // ---\n\n // Mint CRV on Child Liquidity gauge factory\n gaugeFactory.mint(address(gauge));\n // Collect extra gauge rewards (outside of CRV)\n gauge.claim_rewards();\n\n _collectRewardTokens();\n }\n\n function _lpWithdraw(uint256 _lpAmount) internal {\n // withdraw lp tokens from the gauge without claiming rewards\n gauge.withdraw(_lpAmount);\n }\n\n /**\n * @notice Get the total asset value held in the platform\n * @param _asset Address of the asset\n * @return balance Total value of the asset in the platform\n */\n function checkBalance(address _asset)\n external\n view\n override\n returns (uint256 balance)\n {\n require(_asset == address(weth), \"Unsupported asset\");\n\n // WETH balance needed here for the balance check that happens from vault during depositing.\n balance = weth.balanceOf(address(this));\n uint256 lpTokens = gauge.balanceOf(address(this));\n if (lpTokens > 0) {\n balance += (lpTokens * curvePool.get_virtual_price()) / 1e18;\n }\n }\n\n /**\n * @notice Returns bool indicating whether asset is supported by strategy\n * @param _asset Address of the asset\n */\n function supportsAsset(address _asset) public view override returns (bool) {\n return _asset == address(weth);\n }\n\n /***************************************\n Approvals\n ****************************************/\n\n /**\n * @notice Sets the maximum slippage allowed for any swap/liquidity operation\n * @param _maxSlippage Maximum slippage allowed, 1e18 = 100%.\n */\n function setMaxSlippage(uint256 _maxSlippage) external onlyGovernor {\n _setMaxSlippage(_maxSlippage);\n }\n\n function _setMaxSlippage(uint256 _maxSlippage) internal {\n require(_maxSlippage <= 5e16, \"Slippage must be less than 100%\");\n maxSlippage = _maxSlippage;\n emit MaxSlippageUpdated(_maxSlippage);\n }\n\n /**\n * @notice Approve the spending of all assets by their corresponding pool tokens,\n * if for some reason is it necessary.\n */\n function safeApproveAllTokens()\n external\n override\n onlyGovernor\n nonReentrant\n {\n _approveBase();\n }\n\n /**\n * @dev Since we are unwrapping WETH before depositing it to Curve\n * there is no need to set an approval for WETH on the Curve\n * pool\n * @param _asset Address of the asset\n * @param _pToken Address of the Curve LP token\n */\n // solhint-disable-next-line no-unused-vars\n function _abstractSetPToken(address _asset, address _pToken)\n internal\n override\n {}\n\n function _approveBase() internal {\n // Approve Curve pool for OETH (required for adding liquidity)\n // slither-disable-next-line unused-return\n oeth.approve(platformAddress, type(uint256).max);\n\n // Approve Curve pool for WETH (required for adding liquidity)\n // slither-disable-next-line unused-return\n weth.approve(platformAddress, type(uint256).max);\n\n // Approve Curve gauge contract to transfer Curve pool LP tokens\n // This is needed for deposits if Curve pool LP tokens into the Curve gauge.\n // slither-disable-next-line unused-return\n lpToken.approve(address(gauge), type(uint256).max);\n }\n\n /**\n * @dev Returns the largest of two numbers int256 version\n */\n function _max(int256 a, int256 b) internal pure returns (int256) {\n return a >= b ? a : b;\n }\n}\n" + }, + "contracts/strategies/BridgedWOETHStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { IERC20, SafeERC20, InitializableAbstractStrategy } from \"../utils/InitializableAbstractStrategy.sol\";\nimport { IWETH9 } from \"../interfaces/IWETH9.sol\";\nimport { IVault } from \"../interfaces/IVault.sol\";\nimport { AggregatorV3Interface } from \"../interfaces/chainlink/AggregatorV3Interface.sol\";\nimport { StableMath } from \"../utils/StableMath.sol\";\nimport { SafeCast } from \"@openzeppelin/contracts/utils/math/SafeCast.sol\";\nimport { IOracle } from \"../interfaces/IOracle.sol\";\n\ncontract BridgedWOETHStrategy is InitializableAbstractStrategy {\n using StableMath for uint256;\n using StableMath for uint128;\n using SafeCast for uint256;\n using SafeERC20 for IERC20;\n\n event MaxPriceDiffBpsUpdated(uint128 oldValue, uint128 newValue);\n event WOETHPriceUpdated(uint128 oldValue, uint128 newValue);\n\n IWETH9 public immutable weth;\n IERC20 public immutable bridgedWOETH;\n IERC20 public immutable oethb;\n IOracle public immutable oracle;\n\n uint128 public lastOraclePrice;\n uint128 public maxPriceDiffBps;\n\n constructor(\n BaseStrategyConfig memory _stratConfig,\n address _weth,\n address _bridgedWOETH,\n address _oethb,\n address _oracle\n ) InitializableAbstractStrategy(_stratConfig) {\n weth = IWETH9(_weth);\n bridgedWOETH = IERC20(_bridgedWOETH);\n oethb = IERC20(_oethb);\n oracle = IOracle(_oracle);\n }\n\n function initialize(uint128 _maxPriceDiffBps)\n external\n onlyGovernor\n initializer\n {\n InitializableAbstractStrategy._initialize(\n new address[](0), // No reward tokens\n new address[](0), // No assets\n new address[](0) // No pTokens\n );\n\n _setMaxPriceDiffBps(_maxPriceDiffBps);\n }\n\n /**\n * @dev Sets the max price diff bps for the wOETH value appreciation\n * @param _maxPriceDiffBps Bps value, 10k == 100%\n */\n function setMaxPriceDiffBps(uint128 _maxPriceDiffBps)\n external\n onlyGovernor\n {\n _setMaxPriceDiffBps(_maxPriceDiffBps);\n }\n\n /**\n * @dev Sets the max price diff bps for the wOETH value appreciation\n * @param _maxPriceDiffBps Bps value, 10k == 100%\n */\n function _setMaxPriceDiffBps(uint128 _maxPriceDiffBps) internal {\n require(\n _maxPriceDiffBps > 0 && _maxPriceDiffBps <= 10000,\n \"Invalid bps value\"\n );\n\n emit MaxPriceDiffBpsUpdated(maxPriceDiffBps, _maxPriceDiffBps);\n\n maxPriceDiffBps = _maxPriceDiffBps;\n }\n\n /**\n * @dev Wrapper for _updateWOETHOraclePrice with nonReentrant flag\n * @return The latest price of wOETH from Oracle\n */\n function updateWOETHOraclePrice() external nonReentrant returns (uint256) {\n return _updateWOETHOraclePrice();\n }\n\n /**\n * @dev Finds the value of bridged wOETH from the Oracle.\n * Ensures that it's within the bounds and reasonable.\n * And stores it.\n *\n * NOTE: Intentionally not caching `Vault.priceProvider` here,\n * since doing so would mean that we also have to update this\n * strategy every time there's a change in oracle router.\n * Besides on L2, the gas is considerably cheaper than mainnet.\n *\n * @return Latest price from oracle\n */\n function _updateWOETHOraclePrice() internal returns (uint256) {\n // WETH price per unit of bridged wOETH\n uint256 oraclePrice = oracle.price(address(bridgedWOETH));\n\n // 1 wOETH > 1 WETH, always\n require(oraclePrice > 1 ether, \"Invalid wOETH value\");\n\n uint128 oraclePrice128 = oraclePrice.toUint128();\n\n // Do some checks\n if (lastOraclePrice > 0) {\n // Make sure the value only goes up\n require(oraclePrice128 >= lastOraclePrice, \"Negative wOETH yield\");\n\n // lastOraclePrice * (1 + maxPriceDiffBps)\n uint256 maxPrice = (lastOraclePrice * (1e4 + maxPriceDiffBps)) /\n 1e4;\n\n // And that it's within the bounds.\n require(oraclePrice128 <= maxPrice, \"Price diff beyond threshold\");\n }\n\n emit WOETHPriceUpdated(lastOraclePrice, oraclePrice128);\n\n // Store the price\n lastOraclePrice = oraclePrice128;\n\n return oraclePrice;\n }\n\n /**\n * @dev Computes & returns the value of given wOETH in WETH\n * @param woethAmount Amount of wOETH\n * @return Value of wOETH in WETH (using the last stored oracle price)\n */\n function getBridgedWOETHValue(uint256 woethAmount)\n public\n view\n returns (uint256)\n {\n return (woethAmount * lastOraclePrice) / 1 ether;\n }\n\n /**\n * @dev Takes in bridged wOETH and mints & returns\n * equivalent amount of OETHb.\n * @param woethAmount Amount of bridged wOETH to transfer in\n */\n function depositBridgedWOETH(uint256 woethAmount)\n external\n onlyGovernorOrStrategist\n nonReentrant\n {\n // Update wOETH price\n uint256 oraclePrice = _updateWOETHOraclePrice();\n\n // Figure out how much they are worth\n uint256 oethToMint = (woethAmount * oraclePrice) / 1 ether;\n\n require(oethToMint > 0, \"Invalid deposit amount\");\n\n // There's no pToken, however, it just uses WOETH address in the event\n emit Deposit(address(weth), address(bridgedWOETH), oethToMint);\n\n // Mint OETHb tokens and transfer it to the caller\n IVault(vaultAddress).mintForStrategy(oethToMint);\n\n // Transfer out minted OETHb\n // slither-disable-next-line unchecked-transfer unused-return\n oethb.transfer(msg.sender, oethToMint);\n\n // Transfer in all bridged wOETH tokens\n // slither-disable-next-line unchecked-transfer unused-return\n bridgedWOETH.transferFrom(msg.sender, address(this), woethAmount);\n }\n\n /**\n * @dev Takes in OETHb and burns it and returns\n * equivalent amount of bridged wOETH.\n * @param oethToBurn Amount of OETHb to burn\n */\n function withdrawBridgedWOETH(uint256 oethToBurn)\n external\n onlyGovernorOrStrategist\n nonReentrant\n {\n // Update wOETH price\n uint256 oraclePrice = _updateWOETHOraclePrice();\n\n // Figure out how much they are worth\n uint256 woethAmount = (oethToBurn * 1 ether) / oraclePrice;\n\n require(woethAmount > 0, \"Invalid withdraw amount\");\n\n // There's no pToken, however, it just uses WOETH address in the event\n emit Withdrawal(address(weth), address(bridgedWOETH), oethToBurn);\n\n // Transfer WOETH back\n // slither-disable-next-line unchecked-transfer unused-return\n bridgedWOETH.transfer(msg.sender, woethAmount);\n\n // Transfer in OETHb\n // slither-disable-next-line unchecked-transfer unused-return\n oethb.transferFrom(msg.sender, address(this), oethToBurn);\n\n // Burn OETHb\n IVault(vaultAddress).burnForStrategy(oethToBurn);\n }\n\n /**\n * @notice Returns the amount of backing WETH the strategy holds\n * @param _asset Address of the asset\n * @return balance Total value of the asset in the platform\n */\n function checkBalance(address _asset)\n external\n view\n override\n returns (uint256 balance)\n {\n require(_asset == address(weth), \"Unsupported asset\");\n\n // Figure out how much wOETH is worth at the time.\n // Always uses the last stored oracle price.\n // Call updateWOETHOraclePrice manually to pull in latest yields.\n\n // NOTE: If the contract has been deployed but the call to\n // `updateWOETHOraclePrice()` has never been made, then this\n // will return zero. It should be fine because the strategy\n // should update the price whenever a deposit/withdraw happens.\n\n // If `updateWOETHOraclePrice()` hasn't been called in a while,\n // the strategy will underreport its holdings but never overreport it.\n\n balance =\n (bridgedWOETH.balanceOf(address(this)) * lastOraclePrice) /\n 1 ether;\n }\n\n /**\n * @notice Check if an asset is supported.\n * @param _asset Address of the asset\n * @return bool Whether asset is supported\n */\n function supportsAsset(address _asset) public view override returns (bool) {\n // Strategist deposits bridged wOETH but the contract only\n // reports the balance in WETH. As far as Vault is concerned,\n // it isn't aware of bridged wOETH token\n return _asset == address(weth);\n }\n\n /***************************************\n Overridden methods\n ****************************************/\n /**\n * @inheritdoc InitializableAbstractStrategy\n */\n function transferToken(address _asset, uint256 _amount)\n public\n override\n onlyGovernor\n {\n require(\n _asset != address(bridgedWOETH) && _asset != address(weth),\n \"Cannot transfer supported asset\"\n );\n // Use SafeERC20 only for rescuing unknown assets; core tokens are standard.\n IERC20(_asset).safeTransfer(governor(), _amount);\n }\n\n /**\n * @notice deposit() function not used for this strategy\n */\n function deposit(address, uint256)\n external\n override\n onlyVault\n nonReentrant\n {\n // Use depositBridgedWOETH() instead\n require(false, \"Deposit disabled\");\n }\n\n /**\n * @notice depositAll() function not used for this strategy\n */\n function depositAll() external override onlyVault nonReentrant {\n // Use depositBridgedWOETH() instead\n require(false, \"Deposit disabled\");\n }\n\n /**\n * @notice withdraw() function not used for this strategy\n */\n function withdraw(\n // solhint-disable-next-line no-unused-vars\n address _recipient,\n // solhint-disable-next-line no-unused-vars\n address _asset,\n // solhint-disable-next-line no-unused-vars\n uint256 _amount\n ) external override onlyVault nonReentrant {\n require(false, \"Withdrawal disabled\");\n }\n\n /**\n * @notice withdrawAll() function not used for this strategy\n */\n function withdrawAll() external override onlyVaultOrGovernor nonReentrant {\n // Withdrawal disabled\n }\n\n function _abstractSetPToken(address, address) internal override {\n revert(\"No pTokens are used\");\n }\n\n function safeApproveAllTokens() external override {}\n\n /**\n * @inheritdoc InitializableAbstractStrategy\n */\n function removePToken(uint256) external override {\n revert(\"No pTokens are used\");\n }\n\n /**\n * @inheritdoc InitializableAbstractStrategy\n */\n function collectRewardTokens() external override {}\n}\n" + }, + "contracts/strategies/CompoundStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title Compound Strategy\n * @notice Investment strategy for Compound like lending platforms. eg Compound and Flux\n * @author Origin Protocol Inc\n */\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\n\nimport { ICERC20 } from \"./ICompound.sol\";\nimport { AbstractCompoundStrategy, InitializableAbstractStrategy } from \"./AbstractCompoundStrategy.sol\";\nimport { IComptroller } from \"../interfaces/IComptroller.sol\";\nimport { IERC20 } from \"../utils/InitializableAbstractStrategy.sol\";\n\ncontract CompoundStrategy is AbstractCompoundStrategy {\n using SafeERC20 for IERC20;\n event SkippedWithdrawal(address asset, uint256 amount);\n\n constructor(BaseStrategyConfig memory _stratConfig)\n InitializableAbstractStrategy(_stratConfig)\n {}\n\n /**\n * @notice initialize function, to set up initial internal state\n * @param _rewardTokenAddresses Address of reward token for platform\n * @param _assets Addresses of initial supported assets\n * @param _pTokens Platform Token corresponding addresses\n */\n function initialize(\n address[] memory _rewardTokenAddresses,\n address[] memory _assets,\n address[] memory _pTokens\n ) external onlyGovernor initializer {\n InitializableAbstractStrategy._initialize(\n _rewardTokenAddresses,\n _assets,\n _pTokens\n );\n }\n\n /**\n * @notice Collect accumulated COMP and send to Harvester.\n */\n function collectRewardTokens()\n external\n virtual\n override\n onlyHarvester\n nonReentrant\n {\n // Claim COMP from Comptroller\n ICERC20 cToken = _getCTokenFor(assetsMapped[0]);\n IComptroller comptroller = IComptroller(cToken.comptroller());\n // Only collect from active cTokens, saves gas\n address[] memory ctokensToCollect = new address[](assetsMapped.length);\n uint256 assetCount = assetsMapped.length;\n for (uint256 i = 0; i < assetCount; ++i) {\n ctokensToCollect[i] = address(_getCTokenFor(assetsMapped[i]));\n }\n // Claim only for this strategy\n address[] memory claimers = new address[](1);\n claimers[0] = address(this);\n // Claim COMP from Comptroller. Only collect for supply, saves gas\n comptroller.claimComp(claimers, ctokensToCollect, false, true);\n // Transfer COMP to Harvester\n IERC20 rewardToken = IERC20(rewardTokenAddresses[0]);\n uint256 balance = rewardToken.balanceOf(address(this));\n emit RewardTokenCollected(\n harvesterAddress,\n rewardTokenAddresses[0],\n balance\n );\n rewardToken.safeTransfer(harvesterAddress, balance);\n }\n\n /**\n * @notice Deposit asset into the underlying platform\n * @param _asset Address of asset to deposit\n * @param _amount Amount of assets to deposit\n */\n function deposit(address _asset, uint256 _amount)\n external\n override\n onlyVault\n nonReentrant\n {\n _deposit(_asset, _amount);\n }\n\n /**\n * @dev Deposit an asset into the underlying platform\n * @param _asset Address of the asset to deposit\n * @param _amount Amount of assets to deposit\n */\n function _deposit(address _asset, uint256 _amount) internal {\n require(_amount > 0, \"Must deposit something\");\n ICERC20 cToken = _getCTokenFor(_asset);\n emit Deposit(_asset, address(cToken), _amount);\n require(cToken.mint(_amount) == 0, \"cToken mint failed\");\n }\n\n /**\n * @notice Deposit the entire balance of any supported asset in the strategy into the underlying platform\n */\n function depositAll() external override onlyVault nonReentrant {\n uint256 assetCount = assetsMapped.length;\n for (uint256 i = 0; i < assetCount; ++i) {\n IERC20 asset = IERC20(assetsMapped[i]);\n uint256 assetBalance = asset.balanceOf(address(this));\n if (assetBalance > 0) {\n _deposit(address(asset), assetBalance);\n }\n }\n }\n\n /**\n * @notice Withdraw an asset from the underlying platform\n * @param _recipient Address to receive withdrawn assets\n * @param _asset Address of the asset to withdraw\n * @param _amount Amount of assets to withdraw\n */\n function withdraw(\n address _recipient,\n address _asset,\n uint256 _amount\n ) external override onlyVault nonReentrant {\n require(_amount > 0, \"Must withdraw something\");\n require(_recipient != address(0), \"Must specify recipient\");\n\n ICERC20 cToken = _getCTokenFor(_asset);\n // If redeeming 0 cTokens, just skip, else COMP will revert\n uint256 cTokensToRedeem = _convertUnderlyingToCToken(cToken, _amount);\n if (cTokensToRedeem == 0) {\n emit SkippedWithdrawal(_asset, _amount);\n return;\n }\n\n emit Withdrawal(_asset, address(cToken), _amount);\n require(cToken.redeemUnderlying(_amount) == 0, \"Redeem failed\");\n IERC20(_asset).safeTransfer(_recipient, _amount);\n }\n\n /**\n * @dev Internal method to respond to the addition of new asset / cTokens\n * We need to approve the cToken and give it permission to spend the asset\n * @param _asset Address of the asset to approve. eg DAI\n * @param _pToken The pToken for the approval. eg cDAI or fDAI\n */\n function _abstractSetPToken(address _asset, address _pToken)\n internal\n override\n {\n // Safe approval\n IERC20(_asset).safeApprove(_pToken, 0);\n IERC20(_asset).safeApprove(_pToken, type(uint256).max);\n }\n\n /**\n * @notice Remove all supported assets from the underlying platform and send them to Vault contract.\n */\n function withdrawAll() external override onlyVaultOrGovernor nonReentrant {\n uint256 assetCount = assetsMapped.length;\n for (uint256 i = 0; i < assetCount; ++i) {\n IERC20 asset = IERC20(assetsMapped[i]);\n // Redeem entire balance of cToken\n ICERC20 cToken = _getCTokenFor(address(asset));\n uint256 cTokenBalance = cToken.balanceOf(address(this));\n if (cTokenBalance > 0) {\n require(cToken.redeem(cTokenBalance) == 0, \"Redeem failed\");\n uint256 assetBalance = asset.balanceOf(address(this));\n // Transfer entire balance to Vault\n asset.safeTransfer(vaultAddress, assetBalance);\n\n emit Withdrawal(address(asset), address(cToken), assetBalance);\n }\n }\n }\n\n /**\n * @notice Get the total asset value held in the underlying platform\n * This includes any interest that was generated since depositing.\n * The exchange rate between the cToken and asset gradually increases,\n * causing the cToken to be worth more corresponding asset.\n * @param _asset Address of the asset\n * @return balance Total value of the asset in the platform\n */\n function checkBalance(address _asset)\n external\n view\n override\n returns (uint256 balance)\n {\n // Balance is always with token cToken decimals\n ICERC20 cToken = _getCTokenFor(_asset);\n balance = _checkBalance(cToken);\n }\n\n /**\n * @dev Get the total asset value held in the platform\n * underlying = (cTokenAmt * exchangeRate) / 1e18\n * @param _cToken cToken for which to check balance\n * @return balance Total value of the asset in the platform\n */\n function _checkBalance(ICERC20 _cToken)\n internal\n view\n returns (uint256 balance)\n {\n // e.g. 50e8*205316390724364402565641705 / 1e18 = 1.0265..e18\n balance =\n (_cToken.balanceOf(address(this)) * _cToken.exchangeRateStored()) /\n 1e18;\n }\n\n /**\n * @notice Approve the spending of all assets by their corresponding cToken,\n * if for some reason is it necessary.\n */\n function safeApproveAllTokens() external override {\n uint256 assetCount = assetsMapped.length;\n for (uint256 i = 0; i < assetCount; ++i) {\n IERC20 asset = IERC20(assetsMapped[i]);\n address cToken = assetToPToken[address(asset)];\n // Safe approval\n asset.safeApprove(cToken, 0);\n asset.safeApprove(cToken, type(uint256).max);\n }\n }\n}\n" + }, + "contracts/strategies/ConvexEthMetaStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title Convex Automated Market Maker (AMO) Strategy\n * @notice AMO strategy for the Curve OETH/ETH pool\n * @author Origin Protocol Inc\n */\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport \"@openzeppelin/contracts/utils/math/Math.sol\";\n\nimport { ICurveETHPoolV1 } from \"./ICurveETHPoolV1.sol\";\nimport { IERC20, InitializableAbstractStrategy } from \"../utils/InitializableAbstractStrategy.sol\";\nimport { StableMath } from \"../utils/StableMath.sol\";\nimport { IVault } from \"../interfaces/IVault.sol\";\nimport { IWETH9 } from \"../interfaces/IWETH9.sol\";\nimport { IConvexDeposits } from \"./IConvexDeposits.sol\";\nimport { IRewardStaking } from \"./IRewardStaking.sol\";\n\ncontract ConvexEthMetaStrategy is InitializableAbstractStrategy {\n using StableMath for uint256;\n using SafeERC20 for IERC20;\n\n uint256 public constant MAX_SLIPPAGE = 1e16; // 1%, same as the Curve UI\n address public constant ETH_ADDRESS =\n 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;\n\n // The following slots have been deprecated with immutable variables\n // slither-disable-next-line constable-states\n address private _deprecated_cvxDepositorAddress;\n // slither-disable-next-line constable-states\n address private _deprecated_cvxRewardStaker;\n // slither-disable-next-line constable-states\n uint256 private _deprecated_cvxDepositorPTokenId;\n // slither-disable-next-line constable-states\n address private _deprecated_curvePool;\n // slither-disable-next-line constable-states\n address private _deprecated_lpToken;\n // slither-disable-next-line constable-states\n address private _deprecated_oeth;\n // slither-disable-next-line constable-states\n address private _deprecated_weth;\n\n // Ordered list of pool assets\n // slither-disable-next-line constable-states\n uint128 private _deprecated_oethCoinIndex;\n // slither-disable-next-line constable-states\n uint128 private _deprecated_ethCoinIndex;\n\n // New immutable variables that must be set in the constructor\n address public immutable cvxDepositorAddress;\n IRewardStaking public immutable cvxRewardStaker;\n uint256 public immutable cvxDepositorPTokenId;\n ICurveETHPoolV1 public immutable curvePool;\n IERC20 public immutable lpToken;\n IERC20 public immutable oeth;\n IWETH9 public immutable weth;\n\n // Ordered list of pool assets\n uint128 public constant oethCoinIndex = 1;\n uint128 public constant ethCoinIndex = 0;\n\n /**\n * @dev Verifies that the caller is the Strategist.\n */\n modifier onlyStrategist() {\n require(\n msg.sender == IVault(vaultAddress).strategistAddr(),\n \"Caller is not the Strategist\"\n );\n _;\n }\n\n /**\n * @dev Checks the Curve pool's balances have improved and the balances\n * have not tipped to the other side.\n * This modifier only works on functions that do a single sided add or remove.\n * The standard deposit function adds to both sides of the pool in a way that\n * the pool's balance is not worsened.\n * Withdrawals are proportional so doesn't change the pools asset balance.\n */\n modifier improvePoolBalance() {\n // Get the asset and OToken balances in the Curve pool\n uint256[2] memory balancesBefore = curvePool.get_balances();\n // diff = ETH balance - OETH balance\n int256 diffBefore = int256(balancesBefore[ethCoinIndex]) -\n int256(balancesBefore[oethCoinIndex]);\n\n _;\n\n // Get the asset and OToken balances in the Curve pool\n uint256[2] memory balancesAfter = curvePool.get_balances();\n // diff = ETH balance - OETH balance\n int256 diffAfter = int256(balancesAfter[ethCoinIndex]) -\n int256(balancesAfter[oethCoinIndex]);\n\n if (diffBefore <= 0) {\n // If the pool was originally imbalanced in favor of OETH, then\n // we want to check that the pool is now more balanced\n require(diffAfter <= 0, \"OTokens overshot peg\");\n require(diffBefore < diffAfter, \"OTokens balance worse\");\n }\n if (diffBefore >= 0) {\n // If the pool was originally imbalanced in favor of ETH, then\n // we want to check that the pool is now more balanced\n require(diffAfter >= 0, \"Assets overshot peg\");\n require(diffAfter < diffBefore, \"Assets balance worse\");\n }\n }\n\n // Used to circumvent the stack too deep issue\n struct ConvexEthMetaConfig {\n address cvxDepositorAddress; //Address of the Convex depositor(AKA booster) for this pool\n address cvxRewardStakerAddress; //Address of the CVX rewards staker\n uint256 cvxDepositorPTokenId; //Pid of the pool referred to by Depositor and staker\n address oethAddress; //Address of OETH token\n address wethAddress; //Address of WETH\n }\n\n constructor(\n BaseStrategyConfig memory _baseConfig,\n ConvexEthMetaConfig memory _convexConfig\n ) InitializableAbstractStrategy(_baseConfig) {\n lpToken = IERC20(_baseConfig.platformAddress);\n curvePool = ICurveETHPoolV1(_baseConfig.platformAddress);\n\n cvxDepositorAddress = _convexConfig.cvxDepositorAddress;\n cvxRewardStaker = IRewardStaking(_convexConfig.cvxRewardStakerAddress);\n cvxDepositorPTokenId = _convexConfig.cvxDepositorPTokenId;\n oeth = IERC20(_convexConfig.oethAddress);\n weth = IWETH9(_convexConfig.wethAddress);\n }\n\n /**\n * Initializer for setting up strategy internal state. This overrides the\n * InitializableAbstractStrategy initializer as Curve strategies don't fit\n * well within that abstraction.\n * @param _rewardTokenAddresses Address of CRV & CVX\n * @param _assets Addresses of supported assets. eg WETH\n */\n function initialize(\n address[] calldata _rewardTokenAddresses, // CRV + CVX\n address[] calldata _assets // WETH\n ) external onlyGovernor initializer {\n require(_assets.length == 1, \"Must have exactly one asset\");\n require(_assets[0] == address(weth), \"Asset not WETH\");\n\n address[] memory pTokens = new address[](1);\n pTokens[0] = address(curvePool);\n\n InitializableAbstractStrategy._initialize(\n _rewardTokenAddresses,\n _assets,\n pTokens\n );\n\n _approveBase();\n }\n\n /***************************************\n Deposit\n ****************************************/\n\n /**\n * @notice Deposit WETH into the Curve pool\n * @param _weth Address of Wrapped ETH (WETH) contract.\n * @param _amount Amount of WETH to deposit.\n */\n function deposit(address _weth, uint256 _amount)\n external\n override\n onlyVault\n nonReentrant\n {\n _deposit(_weth, _amount);\n }\n\n function _deposit(address _weth, uint256 _wethAmount) internal {\n require(_wethAmount > 0, \"Must deposit something\");\n require(_weth == address(weth), \"Can only deposit WETH\");\n weth.withdraw(_wethAmount);\n\n emit Deposit(_weth, address(lpToken), _wethAmount);\n\n // Get the asset and OToken balances in the Curve pool\n uint256[2] memory balances = curvePool.get_balances();\n // safe to cast since min value is at least 0\n uint256 oethToAdd = uint256(\n _max(\n 0,\n int256(balances[ethCoinIndex]) +\n int256(_wethAmount) -\n int256(balances[oethCoinIndex])\n )\n );\n\n /* Add so much OETH so that the pool ends up being balanced. And at minimum\n * add as much OETH as WETH and at maximum twice as much OETH.\n */\n oethToAdd = Math.max(oethToAdd, _wethAmount);\n oethToAdd = Math.min(oethToAdd, _wethAmount * 2);\n\n /* Mint OETH with a strategy that attempts to contribute to stability of OETH/WETH pool. Try\n * to mint so much OETH that after deployment of liquidity pool ends up being balanced.\n *\n * To manage unpredictability minimal OETH minted will always be at least equal or greater\n * to WETH amount deployed. And never larger than twice the WETH amount deployed even if\n * it would have a further beneficial effect on pool stability.\n */\n IVault(vaultAddress).mintForStrategy(oethToAdd);\n\n emit Deposit(address(oeth), address(lpToken), oethToAdd);\n\n uint256[2] memory _amounts;\n _amounts[ethCoinIndex] = _wethAmount;\n _amounts[oethCoinIndex] = oethToAdd;\n\n uint256 valueInLpTokens = (_wethAmount + oethToAdd).divPrecisely(\n curvePool.get_virtual_price()\n );\n uint256 minMintAmount = valueInLpTokens.mulTruncate(\n uint256(1e18) - MAX_SLIPPAGE\n );\n\n // Do the deposit to the Curve pool\n // slither-disable-next-line arbitrary-send\n uint256 lpDeposited = curvePool.add_liquidity{ value: _wethAmount }(\n _amounts,\n minMintAmount\n );\n\n // Deposit the Curve pool's LP tokens into the Convex rewards pool\n require(\n IConvexDeposits(cvxDepositorAddress).deposit(\n cvxDepositorPTokenId,\n lpDeposited,\n true // Deposit with staking\n ),\n \"Depositing LP to Convex not successful\"\n );\n }\n\n /**\n * @notice Deposit the strategy's entire balance of WETH into the Curve pool\n */\n function depositAll() external override onlyVault nonReentrant {\n uint256 balance = weth.balanceOf(address(this));\n if (balance > 0) {\n _deposit(address(weth), balance);\n }\n }\n\n /***************************************\n Withdraw\n ****************************************/\n\n /**\n * @notice Withdraw ETH and OETH from the Curve pool, burn the OETH,\n * convert the ETH to WETH and transfer to the recipient.\n * @param _recipient Address to receive withdrawn asset which is normally the Vault.\n * @param _weth Address of the Wrapped ETH (WETH) contract.\n * @param _amount Amount of WETH to withdraw.\n */\n function withdraw(\n address _recipient,\n address _weth,\n uint256 _amount\n ) external override onlyVault nonReentrant {\n require(_amount > 0, \"Invalid amount\");\n require(_weth == address(weth), \"Can only withdraw WETH\");\n\n emit Withdrawal(_weth, address(lpToken), _amount);\n\n uint256 requiredLpTokens = calcTokenToBurn(_amount);\n\n _lpWithdraw(requiredLpTokens);\n\n /* math in requiredLpTokens should correctly calculate the amount of LP to remove\n * in that the strategy receives enough WETH on balanced removal\n */\n uint256[2] memory _minWithdrawalAmounts = [uint256(0), uint256(0)];\n _minWithdrawalAmounts[ethCoinIndex] = _amount;\n // slither-disable-next-line unused-return\n curvePool.remove_liquidity(requiredLpTokens, _minWithdrawalAmounts);\n\n // Burn all the removed OETH and any that was left in the strategy\n uint256 oethToBurn = oeth.balanceOf(address(this));\n IVault(vaultAddress).burnForStrategy(oethToBurn);\n\n emit Withdrawal(address(oeth), address(lpToken), oethToBurn);\n\n // Transfer WETH to the recipient\n weth.deposit{ value: _amount }();\n require(\n weth.transfer(_recipient, _amount),\n \"Transfer of WETH not successful\"\n );\n }\n\n function calcTokenToBurn(uint256 _wethAmount)\n internal\n view\n returns (uint256 lpToBurn)\n {\n /* The rate between coins in the pool determines the rate at which pool returns\n * tokens when doing balanced removal (remove_liquidity call). And by knowing how much WETH\n * we want we can determine how much of OETH we receive by removing liquidity.\n *\n * Because we are doing balanced removal we should be making profit when removing liquidity in a\n * pool tilted to either side.\n *\n * Important: A downside is that the Strategist / Governor needs to be\n * cognisant of not removing too much liquidity. And while the proposal to remove liquidity\n * is being voted on the pool tilt might change so much that the proposal that has been valid while\n * created is no longer valid.\n */\n\n uint256 poolWETHBalance = curvePool.balances(ethCoinIndex);\n /* K is multiplied by 1e36 which is used for higher precision calculation of required\n * pool LP tokens. Without it the end value can have rounding errors up to precision of\n * 10 digits. This way we move the decimal point by 36 places when doing the calculation\n * and again by 36 places when we are done with it.\n */\n uint256 k = (1e36 * lpToken.totalSupply()) / poolWETHBalance;\n // prettier-ignore\n // slither-disable-next-line divide-before-multiply\n uint256 diff = (_wethAmount + 1) * k;\n lpToBurn = diff / 1e36;\n }\n\n /**\n * @notice Remove all ETH and OETH from the Curve pool, burn the OETH,\n * convert the ETH to WETH and transfer to the Vault contract.\n */\n function withdrawAll() external override onlyVaultOrGovernor nonReentrant {\n uint256 gaugeTokens = cvxRewardStaker.balanceOf(address(this));\n _lpWithdraw(gaugeTokens);\n\n // Withdraws are proportional to assets held by 3Pool\n uint256[2] memory minWithdrawAmounts = [uint256(0), uint256(0)];\n\n // Remove liquidity\n // slither-disable-next-line unused-return\n curvePool.remove_liquidity(\n lpToken.balanceOf(address(this)),\n minWithdrawAmounts\n );\n\n // Burn all OETH\n uint256 oethToBurn = oeth.balanceOf(address(this));\n IVault(vaultAddress).burnForStrategy(oethToBurn);\n\n // Get the strategy contract's ether balance.\n // This includes all that was removed from the Curve pool and\n // any ether that was sitting in the strategy contract before the removal.\n uint256 ethBalance = address(this).balance;\n // Convert all the strategy contract's ether to WETH and transfer to the vault.\n weth.deposit{ value: ethBalance }();\n require(\n weth.transfer(vaultAddress, ethBalance),\n \"Transfer of WETH not successful\"\n );\n\n emit Withdrawal(address(weth), address(lpToken), ethBalance);\n emit Withdrawal(address(oeth), address(lpToken), oethToBurn);\n }\n\n /***************************************\n Curve pool Rebalancing\n ****************************************/\n\n /**\n * @notice Mint OTokens and one-sided add to the Curve pool.\n * This is used when the Curve pool does not have enough OTokens and too many ETH.\n * The OToken/Asset, eg OETH/ETH, price with increase.\n * The amount of assets in the vault is unchanged.\n * The total supply of OTokens is increased.\n * The asset value of the strategy and vault is increased.\n * @param _oTokens The amount of OTokens to be minted and added to the pool.\n */\n function mintAndAddOTokens(uint256 _oTokens)\n external\n onlyStrategist\n nonReentrant\n improvePoolBalance\n {\n IVault(vaultAddress).mintForStrategy(_oTokens);\n\n uint256[2] memory amounts = [uint256(0), uint256(0)];\n amounts[oethCoinIndex] = _oTokens;\n\n // Convert OETH to Curve pool LP tokens\n uint256 valueInLpTokens = (_oTokens).divPrecisely(\n curvePool.get_virtual_price()\n );\n // Apply slippage to LP tokens\n uint256 minMintAmount = valueInLpTokens.mulTruncate(\n uint256(1e18) - MAX_SLIPPAGE\n );\n\n // Add the minted OTokens to the Curve pool\n uint256 lpDeposited = curvePool.add_liquidity(amounts, minMintAmount);\n\n // Deposit the Curve pool LP tokens to the Convex rewards pool\n require(\n IConvexDeposits(cvxDepositorAddress).deposit(\n cvxDepositorPTokenId,\n lpDeposited,\n true // Deposit with staking\n ),\n \"Failed to Deposit LP to Convex\"\n );\n\n emit Deposit(address(oeth), address(lpToken), _oTokens);\n }\n\n /**\n * @notice One-sided remove of OTokens from the Curve pool which are then burned.\n * This is used when the Curve pool has too many OTokens and not enough ETH.\n * The amount of assets in the vault is unchanged.\n * The total supply of OTokens is reduced.\n * The asset value of the strategy and vault is reduced.\n * @param _lpTokens The amount of Curve pool LP tokens to be burned for OTokens.\n */\n function removeAndBurnOTokens(uint256 _lpTokens)\n external\n onlyStrategist\n nonReentrant\n improvePoolBalance\n {\n // Withdraw Curve pool LP tokens from Convex and remove OTokens from the Curve pool\n uint256 oethToBurn = _withdrawAndRemoveFromPool(\n _lpTokens,\n oethCoinIndex\n );\n\n // The vault burns the OTokens from this strategy\n IVault(vaultAddress).burnForStrategy(oethToBurn);\n\n emit Withdrawal(address(oeth), address(lpToken), oethToBurn);\n }\n\n /**\n * @notice One-sided remove of ETH from the Curve pool, convert to WETH\n * and transfer to the vault.\n * This is used when the Curve pool does not have enough OTokens and too many ETH.\n * The OToken/Asset, eg OETH/ETH, price with decrease.\n * The amount of assets in the vault increases.\n * The total supply of OTokens does not change.\n * The asset value of the strategy reduces.\n * The asset value of the vault should be close to the same.\n * @param _lpTokens The amount of Curve pool LP tokens to be burned for ETH.\n * @dev Curve pool LP tokens is used rather than WETH assets as Curve does not\n * have a way to accurately calculate the amount of LP tokens for a required\n * amount of ETH. Curve's `calc_token_amount` functioun does not include fees.\n * A 3rd party libary can be used that takes into account the fees, but this\n * is a gas intensive process. It's easier for the trusted strategist to\n * caclulate the amount of Curve pool LP tokens required off-chain.\n */\n function removeOnlyAssets(uint256 _lpTokens)\n external\n onlyStrategist\n nonReentrant\n improvePoolBalance\n {\n // Withdraw Curve pool LP tokens from Convex and remove ETH from the Curve pool\n uint256 ethAmount = _withdrawAndRemoveFromPool(_lpTokens, ethCoinIndex);\n\n // Convert ETH to WETH and transfer to the vault\n weth.deposit{ value: ethAmount }();\n require(\n weth.transfer(vaultAddress, ethAmount),\n \"Transfer of WETH not successful\"\n );\n\n emit Withdrawal(address(weth), address(lpToken), ethAmount);\n }\n\n /**\n * @dev Remove Curve pool LP tokens from the Convex pool and\n * do a one-sided remove of ETH or OETH from the Curve pool.\n * @param _lpTokens The amount of Curve pool LP tokens to be removed from the Convex pool.\n * @param coinIndex The index of the coin to be removed from the Curve pool. 0 = ETH, 1 = OETH.\n * @return coinsRemoved The amount of ETH or OETH removed from the Curve pool.\n */\n function _withdrawAndRemoveFromPool(uint256 _lpTokens, uint128 coinIndex)\n internal\n returns (uint256 coinsRemoved)\n {\n // Withdraw Curve pool LP tokens from Convex pool\n _lpWithdraw(_lpTokens);\n\n // Convert Curve pool LP tokens to ETH value\n uint256 valueInEth = _lpTokens.mulTruncate(\n curvePool.get_virtual_price()\n );\n // Apply slippage to ETH value\n uint256 minAmount = valueInEth.mulTruncate(\n uint256(1e18) - MAX_SLIPPAGE\n );\n\n // Remove just the ETH from the Curve pool\n coinsRemoved = curvePool.remove_liquidity_one_coin(\n _lpTokens,\n int128(coinIndex),\n minAmount,\n address(this)\n );\n }\n\n /***************************************\n Assets and Rewards\n ****************************************/\n\n /**\n * @notice Collect accumulated CRV and CVX rewards and send to the Harvester.\n */\n function collectRewardTokens()\n external\n override\n onlyHarvester\n nonReentrant\n {\n // Collect CRV and CVX\n cvxRewardStaker.getReward();\n _collectRewardTokens();\n }\n\n function _lpWithdraw(uint256 _wethAmount) internal {\n // withdraw and unwrap with claim takes back the lpTokens\n // and also collects the rewards for deposit\n cvxRewardStaker.withdrawAndUnwrap(_wethAmount, true);\n }\n\n /**\n * @notice Get the total asset value held in the platform\n * @param _asset Address of the asset\n * @return balance Total value of the asset in the platform\n */\n function checkBalance(address _asset)\n public\n view\n override\n returns (uint256 balance)\n {\n require(_asset == address(weth), \"Unsupported asset\");\n\n // Eth balance needed here for the balance check that happens from vault during depositing.\n balance = address(this).balance;\n uint256 lpTokens = cvxRewardStaker.balanceOf(address(this));\n if (lpTokens > 0) {\n balance += (lpTokens * curvePool.get_virtual_price()) / 1e18;\n }\n }\n\n /**\n * @notice Returns bool indicating whether asset is supported by strategy\n * @param _asset Address of the asset\n */\n function supportsAsset(address _asset) public view override returns (bool) {\n return _asset == address(weth);\n }\n\n /***************************************\n Approvals\n ****************************************/\n\n /**\n * @notice Approve the spending of all assets by their corresponding pool tokens,\n * if for some reason is it necessary.\n */\n function safeApproveAllTokens()\n external\n override\n onlyGovernor\n nonReentrant\n {\n _approveBase();\n }\n\n /**\n * @notice Accept unwrapped WETH\n */\n receive() external payable {}\n\n /**\n * @dev Since we are unwrapping WETH before depositing it to Curve\n * there is no need to set an approval for WETH on the Curve\n * pool\n * @param _asset Address of the asset\n * @param _pToken Address of the Curve LP token\n */\n // solhint-disable-next-line no-unused-vars\n function _abstractSetPToken(address _asset, address _pToken)\n internal\n override\n {}\n\n function _approveBase() internal {\n // Approve Curve pool for OETH (required for adding liquidity)\n // No approval is needed for ETH\n // slither-disable-next-line unused-return\n oeth.approve(platformAddress, type(uint256).max);\n\n // Approve Convex deposit contract to transfer Curve pool LP tokens\n // This is needed for deposits if Curve pool LP tokens into the Convex rewards pool\n // slither-disable-next-line unused-return\n lpToken.approve(cvxDepositorAddress, type(uint256).max);\n }\n\n /**\n * @dev Returns the largest of two numbers int256 version\n */\n function _max(int256 a, int256 b) internal pure returns (int256) {\n return a >= b ? a : b;\n }\n}\n" + }, + "contracts/strategies/ConvexGeneralizedMetaStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title Curve Convex Strategy\n * @notice Investment strategy for investing stablecoins via Curve 3Pool\n * @author Origin Protocol Inc\n */\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport \"@openzeppelin/contracts/utils/Strings.sol\";\n\nimport { IRewardStaking } from \"./IRewardStaking.sol\";\nimport { IConvexDeposits } from \"./IConvexDeposits.sol\";\nimport { ICurvePool } from \"./ICurvePool.sol\";\nimport { IERC20, InitializableAbstractStrategy } from \"./AbstractCurveStrategy.sol\";\nimport { AbstractConvexMetaStrategy } from \"./AbstractConvexMetaStrategy.sol\";\nimport { StableMath } from \"../utils/StableMath.sol\";\n\ncontract ConvexGeneralizedMetaStrategy is AbstractConvexMetaStrategy {\n using StableMath for uint256;\n using SafeERC20 for IERC20;\n\n constructor(BaseStrategyConfig memory _stratConfig)\n InitializableAbstractStrategy(_stratConfig)\n {}\n\n /* Take 3pool LP and deposit it to metapool. Take the LP from metapool\n * and deposit them to Convex.\n */\n function _lpDepositAll() internal override {\n IERC20 threePoolLp = IERC20(pTokenAddress);\n ICurvePool curvePool = ICurvePool(platformAddress);\n\n uint256 threePoolLpBalance = threePoolLp.balanceOf(address(this));\n uint256 curve3PoolVirtualPrice = curvePool.get_virtual_price();\n uint256 threePoolLpDollarValue = threePoolLpBalance.mulTruncate(\n curve3PoolVirtualPrice\n );\n\n uint256[2] memory _amounts = [0, threePoolLpBalance];\n\n uint256 metapoolVirtualPrice = metapool.get_virtual_price();\n /**\n * First convert all the deposited tokens to dollar values,\n * then divide by virtual price to convert to metapool LP tokens\n * and apply the max slippage\n */\n uint256 minReceived = threePoolLpDollarValue\n .divPrecisely(metapoolVirtualPrice)\n .mulTruncate(uint256(1e18) - MAX_SLIPPAGE);\n\n uint256 metapoolLp = metapool.add_liquidity(_amounts, minReceived);\n\n bool success = IConvexDeposits(cvxDepositorAddress).deposit(\n cvxDepositorPTokenId,\n metapoolLp,\n true // Deposit with staking\n );\n\n require(success, \"Failed to deposit to Convex\");\n }\n\n /**\n * Withdraw the specified amount of tokens from the gauge. And use all the resulting tokens\n * to remove liquidity from metapool\n * @param num3CrvTokens Number of Convex 3pool LP tokens to withdraw from metapool\n */\n function _lpWithdraw(uint256 num3CrvTokens) internal override {\n uint256 gaugeTokens = IRewardStaking(cvxRewardStakerAddress).balanceOf(\n address(this)\n );\n\n uint256 requiredMetapoolLpTokens = _calcCurveMetaTokenAmount(\n crvCoinIndex,\n num3CrvTokens\n );\n\n require(\n requiredMetapoolLpTokens <= gaugeTokens,\n string(\n bytes.concat(\n bytes(\"Attempting to withdraw \"),\n bytes(Strings.toString(requiredMetapoolLpTokens)),\n bytes(\", metapoolLP but only \"),\n bytes(Strings.toString(gaugeTokens)),\n bytes(\" available.\")\n )\n )\n );\n\n // withdraw and unwrap with claim takes back the lpTokens and also collects the rewards for deposit\n IRewardStaking(cvxRewardStakerAddress).withdrawAndUnwrap(\n requiredMetapoolLpTokens,\n true\n );\n\n if (requiredMetapoolLpTokens > 0) {\n // slither-disable-next-line unused-return\n metapool.remove_liquidity_one_coin(\n requiredMetapoolLpTokens,\n int128(crvCoinIndex),\n num3CrvTokens\n );\n }\n }\n\n function _lpWithdrawAll() internal override {\n uint256 gaugeTokens = IRewardStaking(cvxRewardStakerAddress).balanceOf(\n address(this)\n );\n IRewardStaking(cvxRewardStakerAddress).withdrawAndUnwrap(\n gaugeTokens,\n true\n );\n\n if (gaugeTokens > 0) {\n uint256 burnDollarAmount = gaugeTokens.mulTruncate(\n metapool.get_virtual_price()\n );\n uint256 curve3PoolExpected = burnDollarAmount.divPrecisely(\n ICurvePool(platformAddress).get_virtual_price()\n );\n\n // Always withdraw all of the available metapool LP tokens (similar to how we always deposit all)\n // slither-disable-next-line unused-return\n metapool.remove_liquidity_one_coin(\n gaugeTokens,\n int128(crvCoinIndex),\n curve3PoolExpected -\n curve3PoolExpected.mulTruncate(maxWithdrawalSlippage)\n );\n }\n }\n}\n" + }, + "contracts/strategies/ConvexOUSDMetaStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title Curve Convex Strategy\n * @notice Investment strategy for investing stablecoins via Curve 3Pool\n * @author Origin Protocol Inc\n */\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport \"@openzeppelin/contracts/utils/Strings.sol\";\nimport \"@openzeppelin/contracts/utils/math/Math.sol\";\n\nimport { IRewardStaking } from \"./IRewardStaking.sol\";\nimport { IConvexDeposits } from \"./IConvexDeposits.sol\";\nimport { ICurvePool } from \"./ICurvePool.sol\";\nimport { IERC20, InitializableAbstractStrategy } from \"./AbstractCurveStrategy.sol\";\nimport { AbstractConvexMetaStrategy } from \"./AbstractConvexMetaStrategy.sol\";\nimport { StableMath } from \"../utils/StableMath.sol\";\nimport { IVault } from \"../interfaces/IVault.sol\";\n\ncontract ConvexOUSDMetaStrategy is AbstractConvexMetaStrategy {\n using StableMath for uint256;\n using SafeERC20 for IERC20;\n\n constructor(BaseStrategyConfig memory _stratConfig)\n InitializableAbstractStrategy(_stratConfig)\n {}\n\n /* Take 3pool LP and mint the corresponding amount of ousd. Deposit and stake that to\n * ousd Curve Metapool. Take the LP from metapool and deposit them to Convex.\n */\n function _lpDepositAll() internal override {\n ICurvePool curvePool = ICurvePool(platformAddress);\n\n uint256 threePoolLpBalance = IERC20(pTokenAddress).balanceOf(\n address(this)\n );\n uint256 curve3PoolVirtualPrice = curvePool.get_virtual_price();\n uint256 threePoolLpDollarValue = threePoolLpBalance.mulTruncate(\n curve3PoolVirtualPrice\n );\n\n // safe to cast since min value is at least 0\n uint256 ousdToAdd = uint256(\n _max(\n 0,\n int256(\n metapool.balances(crvCoinIndex).mulTruncate(\n curve3PoolVirtualPrice\n )\n ) -\n int256(metapool.balances(mainCoinIndex)) +\n int256(threePoolLpDollarValue)\n )\n );\n\n /* Add so much OUSD so that the pool ends up being balanced. And at minimum\n * add twice as much OUSD as 3poolLP and at maximum at twice as\n * much OUSD.\n */\n ousdToAdd = Math.max(ousdToAdd, threePoolLpDollarValue);\n ousdToAdd = Math.min(ousdToAdd, threePoolLpDollarValue * 2);\n\n /* Mint OUSD with a strategy that attempts to contribute to stability of OUSD metapool. Try\n * to mint so much OUSD that after deployment of liquidity pool ends up being balanced.\n *\n * To manage unpredictability minimal OUSD minted will always be at least equal or greater\n * to stablecoin(DAI, USDC, USDT) amount of 3CRVLP deployed. And never larger than twice the\n * stablecoin amount of 3CRVLP deployed even if it would have a further beneficial effect\n * on pool stability.\n */\n if (ousdToAdd > 0) {\n IVault(vaultAddress).mintForStrategy(ousdToAdd);\n }\n\n uint256[2] memory _amounts = [ousdToAdd, threePoolLpBalance];\n\n uint256 metapoolVirtualPrice = metapool.get_virtual_price();\n /**\n * First convert all the deposited tokens to dollar values,\n * then divide by virtual price to convert to metapool LP tokens\n * and apply the max slippage\n */\n uint256 minReceived = (ousdToAdd + threePoolLpDollarValue)\n .divPrecisely(metapoolVirtualPrice)\n .mulTruncate(uint256(1e18) - MAX_SLIPPAGE);\n\n uint256 metapoolLp = metapool.add_liquidity(_amounts, minReceived);\n\n bool success = IConvexDeposits(cvxDepositorAddress).deposit(\n cvxDepositorPTokenId,\n metapoolLp,\n true // Deposit with staking\n );\n\n require(success, \"Failed to deposit to Convex\");\n }\n\n /**\n * Withdraw the specified amount of tokens from the gauge. And use all the resulting tokens\n * to remove liquidity from metapool\n * @param num3CrvTokens Number of 3CRV tokens to withdraw from metapool\n */\n function _lpWithdraw(uint256 num3CrvTokens) internal override {\n ICurvePool curvePool = ICurvePool(platformAddress);\n /* The rate between coins in the metapool determines the rate at which metapool returns\n * tokens when doing balanced removal (remove_liquidity call). And by knowing how much 3crvLp\n * we want we can determine how much of OUSD we receive by removing liquidity.\n *\n * Because we are doing balanced removal we should be making profit when removing liquidity in a\n * pool tilted to either side.\n *\n * Important: A downside is that the Strategist / Governor needs to be\n * cognisant of not removing too much liquidity. And while the proposal to remove liquidity\n * is being voted on the pool tilt might change so much that the proposal that has been valid while\n * created is no longer valid.\n */\n\n uint256 crvPoolBalance = metapool.balances(crvCoinIndex);\n /* K is multiplied by 1e36 which is used for higher precision calculation of required\n * metapool LP tokens. Without it the end value can have rounding errors up to precision of\n * 10 digits. This way we move the decimal point by 36 places when doing the calculation\n * and again by 36 places when we are done with it.\n */\n uint256 k = (1e36 * metapoolLPToken.totalSupply()) / crvPoolBalance;\n // simplifying below to: `uint256 diff = (num3CrvTokens - 1) * k` causes loss of precision\n // prettier-ignore\n // slither-disable-next-line divide-before-multiply\n uint256 diff = crvPoolBalance * k -\n (crvPoolBalance - num3CrvTokens - 1) * k;\n uint256 lpToBurn = diff / 1e36;\n\n uint256 gaugeTokens = IRewardStaking(cvxRewardStakerAddress).balanceOf(\n address(this)\n );\n\n require(\n lpToBurn <= gaugeTokens,\n string(\n bytes.concat(\n bytes(\"Attempting to withdraw \"),\n bytes(Strings.toString(lpToBurn)),\n bytes(\", metapoolLP but only \"),\n bytes(Strings.toString(gaugeTokens)),\n bytes(\" available.\")\n )\n )\n );\n\n // withdraw and unwrap with claim takes back the lpTokens and also collects the rewards for deposit\n IRewardStaking(cvxRewardStakerAddress).withdrawAndUnwrap(\n lpToBurn,\n true\n );\n\n // calculate the min amount of OUSD expected for the specified amount of LP tokens\n uint256 minOUSDAmount = lpToBurn.mulTruncate(\n metapool.get_virtual_price()\n ) -\n num3CrvTokens.mulTruncate(curvePool.get_virtual_price()) -\n 1;\n\n // withdraw the liquidity from metapool\n uint256[2] memory _removedAmounts = metapool.remove_liquidity(\n lpToBurn,\n [minOUSDAmount, num3CrvTokens]\n );\n\n IVault(vaultAddress).burnForStrategy(_removedAmounts[mainCoinIndex]);\n }\n\n function _lpWithdrawAll() internal override {\n IERC20 metapoolErc20 = IERC20(address(metapool));\n uint256 gaugeTokens = IRewardStaking(cvxRewardStakerAddress).balanceOf(\n address(this)\n );\n IRewardStaking(cvxRewardStakerAddress).withdrawAndUnwrap(\n gaugeTokens,\n true\n );\n\n uint256[2] memory _minAmounts = [uint256(0), uint256(0)];\n uint256[2] memory _removedAmounts = metapool.remove_liquidity(\n metapoolErc20.balanceOf(address(this)),\n _minAmounts\n );\n\n IVault(vaultAddress).burnForStrategy(_removedAmounts[mainCoinIndex]);\n }\n}\n" + }, + "contracts/strategies/ConvexStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title Curve Convex Strategy\n * @notice Investment strategy for investing stablecoins via Curve 3Pool\n * @author Origin Protocol Inc\n */\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\n\nimport { ICurvePool } from \"./ICurvePool.sol\";\nimport { IRewardStaking } from \"./IRewardStaking.sol\";\nimport { IConvexDeposits } from \"./IConvexDeposits.sol\";\nimport { IERC20, AbstractCurveStrategy, InitializableAbstractStrategy } from \"./AbstractCurveStrategy.sol\";\nimport { StableMath } from \"../utils/StableMath.sol\";\nimport { Helpers } from \"../utils/Helpers.sol\";\n\n/*\n * IMPORTANT(!) If ConvexStrategy needs to be re-deployed, it requires new\n * proxy contract with fresh storage slots. Changes in `AbstractCurveStrategy`\n * storage slots would break existing implementation.\n *\n * Remove this notice if ConvexStrategy is re-deployed\n */\ncontract ConvexStrategy is AbstractCurveStrategy {\n using StableMath for uint256;\n using SafeERC20 for IERC20;\n\n address internal cvxDepositorAddress;\n address internal cvxRewardStakerAddress;\n // slither-disable-next-line constable-states\n address private _deprecated_cvxRewardTokenAddress;\n uint256 internal cvxDepositorPTokenId;\n\n constructor(BaseStrategyConfig memory _stratConfig)\n InitializableAbstractStrategy(_stratConfig)\n {}\n\n /**\n * Initializer for setting up strategy internal state. This overrides the\n * InitializableAbstractStrategy initializer as Curve strategies don't fit\n * well within that abstraction.\n * @param _rewardTokenAddresses Address of CRV & CVX\n * @param _assets Addresses of supported assets. MUST be passed in the same\n * order as returned by coins on the pool contract, i.e.\n * DAI, USDC, USDT\n * @param _pTokens Platform Token corresponding addresses\n * @param _cvxDepositorAddress Address of the Convex depositor(AKA booster) for this pool\n * @param _cvxRewardStakerAddress Address of the CVX rewards staker\n * @param _cvxDepositorPTokenId Pid of the pool referred to by Depositor and staker\n */\n function initialize(\n address[] calldata _rewardTokenAddresses, // CRV + CVX\n address[] calldata _assets,\n address[] calldata _pTokens,\n address _cvxDepositorAddress,\n address _cvxRewardStakerAddress,\n uint256 _cvxDepositorPTokenId\n ) external onlyGovernor initializer {\n require(_assets.length == 3, \"Must have exactly three assets\");\n // Should be set prior to abstract initialize call otherwise\n // abstractSetPToken calls will fail\n cvxDepositorAddress = _cvxDepositorAddress;\n cvxRewardStakerAddress = _cvxRewardStakerAddress;\n cvxDepositorPTokenId = _cvxDepositorPTokenId;\n pTokenAddress = _pTokens[0];\n\n InitializableAbstractStrategy._initialize(\n _rewardTokenAddresses,\n _assets,\n _pTokens\n );\n _approveBase();\n }\n\n function _lpDepositAll() internal override {\n IERC20 pToken = IERC20(pTokenAddress);\n // Deposit with staking\n bool success = IConvexDeposits(cvxDepositorAddress).deposit(\n cvxDepositorPTokenId,\n pToken.balanceOf(address(this)),\n true\n );\n require(success, \"Failed to deposit to Convex\");\n }\n\n function _lpWithdraw(uint256 numCrvTokens) internal override {\n uint256 gaugePTokens = IRewardStaking(cvxRewardStakerAddress).balanceOf(\n address(this)\n );\n\n // Not enough in this contract or in the Gauge, can't proceed\n require(numCrvTokens > gaugePTokens, \"Insufficient 3CRV balance\");\n\n // withdraw and unwrap with claim takes back the lpTokens and also collects the rewards to this\n IRewardStaking(cvxRewardStakerAddress).withdrawAndUnwrap(\n numCrvTokens,\n true\n );\n }\n\n function _lpWithdrawAll() internal override {\n // withdraw and unwrap with claim takes back the lpTokens and also collects the rewards to this\n IRewardStaking(cvxRewardStakerAddress).withdrawAndUnwrap(\n IRewardStaking(cvxRewardStakerAddress).balanceOf(address(this)),\n true\n );\n }\n\n function _approveBase() internal override {\n IERC20 pToken = IERC20(pTokenAddress);\n // 3Pool for LP token (required for removing liquidity)\n pToken.safeApprove(platformAddress, 0);\n pToken.safeApprove(platformAddress, type(uint256).max);\n // Gauge for LP token\n pToken.safeApprove(cvxDepositorAddress, 0);\n pToken.safeApprove(cvxDepositorAddress, type(uint256).max);\n }\n\n /**\n * @dev Get the total asset value held in the platform\n * @param _asset Address of the asset\n * @return balance Total value of the asset in the platform\n */\n function checkBalance(address _asset)\n public\n view\n override\n returns (uint256 balance)\n {\n require(assetToPToken[_asset] != address(0), \"Unsupported asset\");\n // LP tokens in this contract. This should generally be nothing as we\n // should always stake the full balance in the Gauge, but include for\n // safety\n uint256 contractPTokens = IERC20(pTokenAddress).balanceOf(\n address(this)\n );\n uint256 gaugePTokens = IRewardStaking(cvxRewardStakerAddress).balanceOf(\n address(this)\n );\n uint256 totalPTokens = contractPTokens + gaugePTokens;\n\n ICurvePool curvePool = ICurvePool(platformAddress);\n if (totalPTokens > 0) {\n uint256 virtual_price = curvePool.get_virtual_price();\n uint256 value = (totalPTokens * virtual_price) / 1e18;\n uint256 assetDecimals = Helpers.getDecimals(_asset);\n balance = value.scaleBy(assetDecimals, 18) / 3;\n }\n }\n\n /**\n * @dev Collect accumulated CRV and CVX and send to Vault.\n */\n function collectRewardTokens()\n external\n override\n onlyHarvester\n nonReentrant\n {\n // Collect CRV and CVX\n IRewardStaking(cvxRewardStakerAddress).getReward();\n _collectRewardTokens();\n }\n}\n" + }, + "contracts/strategies/crosschain/AbstractCCTPIntegrator.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title AbstractCCTPIntegrator\n * @author Origin Protocol Inc\n *\n * @dev Abstract contract that contains all the logic used to integrate with CCTP.\n */\n\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport { IERC20 } from \"../../utils/InitializableAbstractStrategy.sol\";\n\nimport { ICCTPTokenMessenger, ICCTPMessageTransmitter, IMessageHandlerV2 } from \"../../interfaces/cctp/ICCTP.sol\";\n\nimport { CrossChainStrategyHelper } from \"./CrossChainStrategyHelper.sol\";\nimport { Governable } from \"../../governance/Governable.sol\";\nimport { BytesHelper } from \"../../utils/BytesHelper.sol\";\nimport \"../../utils/Helpers.sol\";\n\nabstract contract AbstractCCTPIntegrator is Governable, IMessageHandlerV2 {\n using SafeERC20 for IERC20;\n\n using BytesHelper for bytes;\n using CrossChainStrategyHelper for bytes;\n\n event LastTransferNonceUpdated(uint64 lastTransferNonce);\n event NonceProcessed(uint64 nonce);\n\n event CCTPMinFinalityThresholdSet(uint16 minFinalityThreshold);\n event CCTPFeePremiumBpsSet(uint16 feePremiumBps);\n event OperatorChanged(address operator);\n event TokensBridged(\n uint32 destinationDomain,\n address peerStrategy,\n address tokenAddress,\n uint256 tokenAmount,\n uint256 maxFee,\n uint32 minFinalityThreshold,\n bytes hookData\n );\n event MessageTransmitted(\n uint32 destinationDomain,\n address peerStrategy,\n uint32 minFinalityThreshold,\n bytes message\n );\n\n // Message body V2 fields\n // Ref: https://developers.circle.com/cctp/technical-guide#message-body\n // Ref: https://github.com/circlefin/evm-cctp-contracts/blob/master/src/messages/v2/BurnMessageV2.sol\n uint8 private constant BURN_MESSAGE_V2_VERSION_INDEX = 0;\n uint8 private constant BURN_MESSAGE_V2_BURN_TOKEN_INDEX = 4;\n uint8 private constant BURN_MESSAGE_V2_RECIPIENT_INDEX = 36;\n uint8 private constant BURN_MESSAGE_V2_AMOUNT_INDEX = 68;\n uint8 private constant BURN_MESSAGE_V2_MESSAGE_SENDER_INDEX = 100;\n uint8 private constant BURN_MESSAGE_V2_FEE_EXECUTED_INDEX = 164;\n uint8 private constant BURN_MESSAGE_V2_HOOK_DATA_INDEX = 228;\n\n /**\n * @notice Max transfer threshold imposed by the CCTP\n * Ref: https://developers.circle.com/cctp/evm-smart-contracts#depositforburn\n * @dev 10M USDC limit applies to both standard and fast transfer modes. The fast transfer mode has\n * an additional limitation that is not present on-chain and Circle may alter that amount off-chain\n * at their preference. The amount available for fast transfer can be queried here:\n * https://iris-api.circle.com/v2/fastBurn/USDC/allowance .\n * If a fast transfer token transaction has been issued and there is not enough allowance for it\n * the off-chain Iris component will re-attempt the transaction and if it fails it will fallback\n * to a standard transfer. Reference section 4.3 in the whitepaper:\n * https://6778953.fs1.hubspotusercontent-na1.net/hubfs/6778953/PDFs/Whitepapers/CCTPV2_White_Paper.pdf\n */\n uint256 public constant MAX_TRANSFER_AMOUNT = 10_000_000 * 10**6; // 10M USDC\n\n /// @notice Minimum transfer amount to avoid zero or dust transfers\n uint256 public constant MIN_TRANSFER_AMOUNT = 10**6;\n\n // CCTP contracts\n // This implementation assumes that remote and local chains have these contracts\n // deployed on the same addresses.\n /// @notice CCTP message transmitter contract\n ICCTPMessageTransmitter public immutable cctpMessageTransmitter;\n /// @notice CCTP token messenger contract\n ICCTPTokenMessenger public immutable cctpTokenMessenger;\n\n /// @notice USDC address on local chain\n address public immutable usdcToken;\n\n /// @notice USDC address on remote chain\n address public immutable peerUsdcToken;\n\n /// @notice Domain ID of the chain from which messages are accepted\n uint32 public immutable peerDomainID;\n\n /// @notice Strategy address on other chain\n address public immutable peerStrategy;\n\n /**\n * @notice Minimum finality threshold\n * Can be 1000 (safe, after 1 epoch) or 2000 (finalized, after 2 epochs).\n * Ref: https://developers.circle.com/cctp/technical-guide#finality-thresholds\n * @dev When configuring the contract for fast transfer we should check the available\n * allowance of USDC that can be bridged using fast mode:\n * wget https://iris-api.circle.com/v2/fastBurn/USDC/allowance\n */\n uint16 public minFinalityThreshold;\n\n /// @notice Fee premium in basis points\n uint16 public feePremiumBps;\n\n /// @notice Nonce of the last known deposit or withdrawal\n uint64 public lastTransferNonce;\n\n /// @notice Operator address: Can relay CCTP messages\n address public operator;\n\n /// @notice Mapping of processed nonces\n mapping(uint64 => bool) private nonceProcessed;\n\n // For future use\n uint256[48] private __gap;\n\n modifier onlyCCTPMessageTransmitter() {\n require(\n msg.sender == address(cctpMessageTransmitter),\n \"Caller is not CCTP transmitter\"\n );\n _;\n }\n\n modifier onlyOperator() {\n require(msg.sender == operator, \"Caller is not the Operator\");\n _;\n }\n\n /**\n * @notice Configuration for CCTP integration\n * @param cctpTokenMessenger Address of the CCTP token messenger contract\n * @param cctpMessageTransmitter Address of the CCTP message transmitter contract\n * @param peerDomainID Domain ID of the chain from which messages are accepted.\n * 0 for Ethereum, 6 for Base, etc.\n * Ref: https://developers.circle.com/cctp/cctp-supported-blockchains\n * @param peerStrategy Address of the master or remote strategy on the other chain\n * @param usdcToken USDC address on local chain\n */\n struct CCTPIntegrationConfig {\n address cctpTokenMessenger;\n address cctpMessageTransmitter;\n uint32 peerDomainID;\n address peerStrategy;\n address usdcToken;\n address peerUsdcToken;\n }\n\n constructor(CCTPIntegrationConfig memory _config) {\n require(_config.usdcToken != address(0), \"Invalid USDC address\");\n require(\n _config.peerUsdcToken != address(0),\n \"Invalid peer USDC address\"\n );\n require(\n _config.cctpTokenMessenger != address(0),\n \"Invalid CCTP config\"\n );\n require(\n _config.cctpMessageTransmitter != address(0),\n \"Invalid CCTP config\"\n );\n require(\n _config.peerStrategy != address(0),\n \"Invalid peer strategy address\"\n );\n\n cctpMessageTransmitter = ICCTPMessageTransmitter(\n _config.cctpMessageTransmitter\n );\n cctpTokenMessenger = ICCTPTokenMessenger(_config.cctpTokenMessenger);\n\n // Domain ID of the chain from which messages are accepted\n peerDomainID = _config.peerDomainID;\n\n // Strategy address on other chain, should\n // always be same as the proxy of this strategy\n peerStrategy = _config.peerStrategy;\n\n // USDC address on local chain\n usdcToken = _config.usdcToken;\n\n // Just a sanity check to ensure the base token is USDC\n uint256 _usdcTokenDecimals = Helpers.getDecimals(_config.usdcToken);\n string memory _usdcTokenSymbol = Helpers.getSymbol(_config.usdcToken);\n require(_usdcTokenDecimals == 6, \"Base token decimals must be 6\");\n require(\n keccak256(abi.encodePacked(_usdcTokenSymbol)) ==\n keccak256(abi.encodePacked(\"USDC\")),\n \"Token symbol must be USDC\"\n );\n\n // USDC address on remote chain\n peerUsdcToken = _config.peerUsdcToken;\n }\n\n /**\n * @dev Initialize the implementation contract\n * @param _operator Operator address\n * @param _minFinalityThreshold Minimum finality threshold\n * @param _feePremiumBps Fee premium in basis points\n */\n function _initialize(\n address _operator,\n uint16 _minFinalityThreshold,\n uint16 _feePremiumBps\n ) internal {\n _setOperator(_operator);\n _setMinFinalityThreshold(_minFinalityThreshold);\n _setFeePremiumBps(_feePremiumBps);\n\n // Nonce starts at 1, so assume nonce 0 as processed.\n // NOTE: This will cause the deposit/withdraw to fail if the\n // strategy is not initialized properly (which is expected).\n nonceProcessed[0] = true;\n }\n\n /***************************************\n Settings\n ****************************************/\n /**\n * @dev Set the operator address\n * @param _operator Operator address\n */\n function setOperator(address _operator) external onlyGovernor {\n _setOperator(_operator);\n }\n\n /**\n * @dev Set the operator address\n * @param _operator Operator address\n */\n function _setOperator(address _operator) internal {\n operator = _operator;\n emit OperatorChanged(_operator);\n }\n\n /**\n * @dev Set the minimum finality threshold at which\n * the message is considered to be finalized to relay.\n * Only accepts a value of 1000 (Safe, after 1 epoch) or\n * 2000 (Finalized, after 2 epochs).\n * @param _minFinalityThreshold Minimum finality threshold\n */\n function setMinFinalityThreshold(uint16 _minFinalityThreshold)\n external\n onlyGovernor\n {\n _setMinFinalityThreshold(_minFinalityThreshold);\n }\n\n /**\n * @dev Set the minimum finality threshold\n * @param _minFinalityThreshold Minimum finality threshold\n */\n function _setMinFinalityThreshold(uint16 _minFinalityThreshold) internal {\n // 1000 for fast transfer and 2000 for standard transfer\n require(\n _minFinalityThreshold == 1000 || _minFinalityThreshold == 2000,\n \"Invalid threshold\"\n );\n\n minFinalityThreshold = _minFinalityThreshold;\n emit CCTPMinFinalityThresholdSet(_minFinalityThreshold);\n }\n\n /**\n * @dev Set the fee premium in basis points.\n * Cannot be higher than 30% (3000 basis points).\n * @param _feePremiumBps Fee premium in basis points\n */\n function setFeePremiumBps(uint16 _feePremiumBps) external onlyGovernor {\n _setFeePremiumBps(_feePremiumBps);\n }\n\n /**\n * @dev Set the fee premium in basis points\n * Cannot be higher than 30% (3000 basis points).\n * Ref: https://developers.circle.com/cctp/technical-guide#fees\n * @param _feePremiumBps Fee premium in basis points\n */\n function _setFeePremiumBps(uint16 _feePremiumBps) internal {\n require(_feePremiumBps <= 3000, \"Fee premium too high\"); // 30%\n\n feePremiumBps = _feePremiumBps;\n emit CCTPFeePremiumBpsSet(_feePremiumBps);\n }\n\n /***************************************\n CCTP message handling\n ****************************************/\n\n /**\n * @dev Handles a finalized CCTP message\n * @param sourceDomain Source domain of the message\n * @param sender Sender of the message\n * @param finalityThresholdExecuted Fidelity threshold executed\n * @param messageBody Message body\n */\n function handleReceiveFinalizedMessage(\n uint32 sourceDomain,\n bytes32 sender,\n uint32 finalityThresholdExecuted,\n bytes memory messageBody\n ) external override onlyCCTPMessageTransmitter returns (bool) {\n // Make sure the finality threshold at execution is at least 2000\n require(\n finalityThresholdExecuted >= 2000,\n \"Finality threshold too low\"\n );\n\n return _handleReceivedMessage(sourceDomain, sender, messageBody);\n }\n\n /**\n * @dev Handles an unfinalized but safe CCTP message\n * @param sourceDomain Source domain of the message\n * @param sender Sender of the message\n * @param finalityThresholdExecuted Fidelity threshold executed\n * @param messageBody Message body\n */\n function handleReceiveUnfinalizedMessage(\n uint32 sourceDomain,\n bytes32 sender,\n uint32 finalityThresholdExecuted,\n bytes memory messageBody\n ) external override onlyCCTPMessageTransmitter returns (bool) {\n // Make sure the contract is configured to handle unfinalized messages\n require(\n minFinalityThreshold == 1000,\n \"Unfinalized messages are not supported\"\n );\n // Make sure the finality threshold at execution is at least 1000\n require(\n finalityThresholdExecuted >= 1000,\n \"Finality threshold too low\"\n );\n\n return _handleReceivedMessage(sourceDomain, sender, messageBody);\n }\n\n /**\n * @dev Handles a CCTP message\n * @param sourceDomain Source domain of the message\n * @param sender Sender of the message\n * @param messageBody Message body\n */\n function _handleReceivedMessage(\n uint32 sourceDomain,\n bytes32 sender,\n bytes memory messageBody\n ) internal returns (bool) {\n require(sourceDomain == peerDomainID, \"Unknown Source Domain\");\n\n // Extract address from bytes32 (CCTP stores addresses as right-padded bytes32)\n address senderAddress = address(uint160(uint256(sender)));\n require(senderAddress == peerStrategy, \"Unknown Sender\");\n\n _onMessageReceived(messageBody);\n\n return true;\n }\n\n /**\n * @dev Sends tokens to the peer strategy using CCTP Token Messenger\n * @param tokenAmount Amount of tokens to send\n * @param hookData Hook data\n */\n function _sendTokens(uint256 tokenAmount, bytes memory hookData)\n internal\n virtual\n {\n // CCTP has a maximum transfer amount of 10M USDC per tx\n require(tokenAmount <= MAX_TRANSFER_AMOUNT, \"Token amount too high\");\n\n // Approve only what needs to be transferred\n IERC20(usdcToken).safeApprove(address(cctpTokenMessenger), tokenAmount);\n\n // Compute the max fee to be paid.\n // Ref: https://developers.circle.com/cctp/evm-smart-contracts#getminfeeamount\n // The right way to compute fees would be to use CCTP's getMinFeeAmount function.\n // The issue is that the getMinFeeAmount is not present on v2.0 contracts, but is on\n // v2.1. Some of CCTP's deployed contracts are v2.0, some are v2.1.\n // We will only be using standard transfers and fee on those is 0 for now. If they\n // ever start implementing fee for standard transfers or if we decide to use fast\n // trasnfer, we can use feePremiumBps as a workaround.\n uint256 maxFee = feePremiumBps > 0\n ? (tokenAmount * feePremiumBps) / 10000\n : 0;\n\n // Send tokens to the peer strategy using CCTP Token Messenger\n cctpTokenMessenger.depositForBurnWithHook(\n tokenAmount,\n peerDomainID,\n bytes32(uint256(uint160(peerStrategy))),\n address(usdcToken),\n bytes32(uint256(uint160(peerStrategy))),\n maxFee,\n uint32(minFinalityThreshold),\n hookData\n );\n\n emit TokensBridged(\n peerDomainID,\n peerStrategy,\n usdcToken,\n tokenAmount,\n maxFee,\n uint32(minFinalityThreshold),\n hookData\n );\n }\n\n /**\n * @dev Sends a message to the peer strategy using CCTP Message Transmitter\n * @param message Payload of the message to send\n */\n function _sendMessage(bytes memory message) internal virtual {\n cctpMessageTransmitter.sendMessage(\n peerDomainID,\n bytes32(uint256(uint160(peerStrategy))),\n bytes32(uint256(uint160(peerStrategy))),\n uint32(minFinalityThreshold),\n message\n );\n\n emit MessageTransmitted(\n peerDomainID,\n peerStrategy,\n uint32(minFinalityThreshold),\n message\n );\n }\n\n /**\n * @dev Receives a message from the peer strategy on the other chain,\n * does some basic checks and relays it to the local MessageTransmitterV2.\n * If the message is a burn message, it will also handle the hook data\n * and call the _onTokenReceived function.\n * @param message Payload of the message to send\n * @param attestation Attestation of the message\n */\n function relay(bytes memory message, bytes memory attestation)\n external\n onlyOperator\n {\n (\n uint32 version,\n uint32 sourceDomainID,\n address sender,\n address recipient,\n bytes memory messageBody\n ) = message.decodeMessageHeader();\n\n // Ensure that it's a CCTP message\n require(\n version == CrossChainStrategyHelper.CCTP_MESSAGE_VERSION,\n \"Invalid CCTP message version\"\n );\n\n // Ensure that the source domain is the peer domain\n require(sourceDomainID == peerDomainID, \"Unknown Source Domain\");\n\n // Ensure message body version\n version = messageBody.extractUint32(BURN_MESSAGE_V2_VERSION_INDEX);\n\n // NOTE: There's a possibility that the CCTP Token Messenger might\n // send other types of messages in future, not just the burn message.\n // If it ever comes to that, this shouldn't cause us any problems\n // because it has to still go through the followign checks:\n // - version check\n // - message body length check\n // - sender and recipient (which should be in the same slots and same as address(this))\n // - hook data handling (which will revert even if all the above checks pass)\n bool isBurnMessageV1 = sender == address(cctpTokenMessenger);\n\n if (isBurnMessageV1) {\n // Handle burn message\n require(\n version == 1 &&\n messageBody.length >= BURN_MESSAGE_V2_HOOK_DATA_INDEX,\n \"Invalid burn message\"\n );\n\n // Ensure the burn token is USDC\n address burnToken = messageBody.extractAddress(\n BURN_MESSAGE_V2_BURN_TOKEN_INDEX\n );\n require(burnToken == peerUsdcToken, \"Invalid burn token\");\n\n // Address of caller of depositForBurn (or depositForBurnWithCaller) on source domain\n sender = messageBody.extractAddress(\n BURN_MESSAGE_V2_MESSAGE_SENDER_INDEX\n );\n\n recipient = messageBody.extractAddress(\n BURN_MESSAGE_V2_RECIPIENT_INDEX\n );\n } else {\n // We handle only Burn message or our custom messagee\n require(\n version == CrossChainStrategyHelper.ORIGIN_MESSAGE_VERSION,\n \"Unsupported message version\"\n );\n }\n\n // Ensure the recipient is this contract\n // Both sender and recipient should be deployed to same address on both chains.\n require(address(this) == recipient, \"Unexpected recipient address\");\n require(sender == peerStrategy, \"Incorrect sender/recipient address\");\n\n // Relay the message\n // This step also mints USDC and transfers it to the recipient wallet\n bool relaySuccess = cctpMessageTransmitter.receiveMessage(\n message,\n attestation\n );\n require(relaySuccess, \"Receive message failed\");\n\n if (isBurnMessageV1) {\n // Extract the hook data from the message body\n bytes memory hookData = messageBody.extractSlice(\n BURN_MESSAGE_V2_HOOK_DATA_INDEX,\n messageBody.length\n );\n\n // Extract the token amount from the message body\n uint256 tokenAmount = messageBody.extractUint256(\n BURN_MESSAGE_V2_AMOUNT_INDEX\n );\n\n // Extract the fee executed from the message body\n uint256 feeExecuted = messageBody.extractUint256(\n BURN_MESSAGE_V2_FEE_EXECUTED_INDEX\n );\n\n // Call the _onTokenReceived function\n _onTokenReceived(tokenAmount - feeExecuted, feeExecuted, hookData);\n }\n }\n\n /***************************************\n Message utils\n ****************************************/\n\n /***************************************\n Nonce Handling\n ****************************************/\n /**\n * @dev Checks if the last known transfer is pending.\n * Nonce starts at 1, so 0 is disregarded.\n * @return True if a transfer is pending, false otherwise\n */\n function isTransferPending() public view returns (bool) {\n return !nonceProcessed[lastTransferNonce];\n }\n\n /**\n * @dev Checks if a given nonce is processed.\n * Nonce starts at 1, so 0 is disregarded.\n * @param nonce Nonce to check\n * @return True if the nonce is processed, false otherwise\n */\n function isNonceProcessed(uint64 nonce) public view returns (bool) {\n return nonceProcessed[nonce];\n }\n\n /**\n * @dev Marks a given nonce as processed.\n * Can only mark nonce as processed once. New nonce should\n * always be greater than the last known nonce. Also updates\n * the last known nonce.\n * @param nonce Nonce to mark as processed\n */\n function _markNonceAsProcessed(uint64 nonce) internal {\n uint64 lastNonce = lastTransferNonce;\n\n // Can only mark latest nonce as processed\n // Master strategy when receiving a message from the remote strategy\n // will have lastNone == nonce, as the nonce is increase at the start\n // of deposit / withdrawal flow.\n // Remote strategy will have lastNonce < nonce, as a new nonce initiated\n // from master will be greater than the last one.\n require(nonce >= lastNonce, \"Nonce too low\");\n // Can only mark nonce as processed once\n require(!nonceProcessed[nonce], \"Nonce already processed\");\n\n nonceProcessed[nonce] = true;\n emit NonceProcessed(nonce);\n\n if (nonce != lastNonce) {\n // Update last known nonce\n lastTransferNonce = nonce;\n emit LastTransferNonceUpdated(nonce);\n }\n }\n\n /**\n * @dev Gets the next nonce to use.\n * Nonce starts at 1, so 0 is disregarded.\n * Reverts if last nonce hasn't been processed yet.\n * @return Next nonce\n */\n function _getNextNonce() internal returns (uint64) {\n uint64 nonce = lastTransferNonce;\n\n require(nonceProcessed[nonce], \"Pending token transfer\");\n\n nonce = nonce + 1;\n lastTransferNonce = nonce;\n emit LastTransferNonceUpdated(nonce);\n\n return nonce;\n }\n\n /***************************************\n Inheritence overrides\n ****************************************/\n\n /**\n * @dev Called when the USDC is received from the CCTP\n * @param tokenAmount The actual amount of USDC received (amount sent - fee executed)\n * @param feeExecuted The fee executed\n * @param payload The payload of the message (hook data)\n */\n function _onTokenReceived(\n uint256 tokenAmount,\n uint256 feeExecuted,\n bytes memory payload\n ) internal virtual;\n\n /**\n * @dev Called when the message is received\n * @param payload The payload of the message\n */\n function _onMessageReceived(bytes memory payload) internal virtual;\n}\n" + }, + "contracts/strategies/crosschain/CrossChainMasterStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title OUSD Yearn V3 Master Strategy - the Mainnet part\n * @author Origin Protocol Inc\n *\n * @dev This strategy can only perform 1 deposit or withdrawal at a time. For that\n * reason it shouldn't be configured as an asset default strategy.\n */\n\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport { IERC20, InitializableAbstractStrategy } from \"../../utils/InitializableAbstractStrategy.sol\";\nimport { AbstractCCTPIntegrator } from \"./AbstractCCTPIntegrator.sol\";\nimport { CrossChainStrategyHelper } from \"./CrossChainStrategyHelper.sol\";\n\ncontract CrossChainMasterStrategy is\n AbstractCCTPIntegrator,\n InitializableAbstractStrategy\n{\n using SafeERC20 for IERC20;\n using CrossChainStrategyHelper for bytes;\n\n /**\n * @notice Remote strategy balance\n * @dev The remote balance is cached and might not reflect the actual\n * real-time balance of the remote strategy.\n */\n uint256 public remoteStrategyBalance;\n\n /// @notice Amount that's bridged due to a pending Deposit process\n /// but with no acknowledgement from the remote strategy yet\n uint256 public pendingAmount;\n\n uint256 internal constant MAX_BALANCE_CHECK_AGE = 1 days;\n\n event RemoteStrategyBalanceUpdated(uint256 balance);\n event WithdrawRequested(address indexed asset, uint256 amount);\n event WithdrawAllSkipped();\n event BalanceCheckIgnored(uint64 nonce, uint256 timestamp, bool isTooOld);\n\n /**\n * @param _stratConfig The platform and OToken vault addresses\n */\n constructor(\n BaseStrategyConfig memory _stratConfig,\n CCTPIntegrationConfig memory _cctpConfig\n )\n InitializableAbstractStrategy(_stratConfig)\n AbstractCCTPIntegrator(_cctpConfig)\n {\n require(\n _stratConfig.platformAddress == address(0),\n \"Invalid platform address\"\n );\n require(\n _stratConfig.vaultAddress != address(0),\n \"Invalid Vault address\"\n );\n }\n\n /**\n * @dev Initialize the strategy implementation\n * @param _operator Address of the operator\n * @param _minFinalityThreshold Minimum finality threshold\n * @param _feePremiumBps Fee premium in basis points\n */\n function initialize(\n address _operator,\n uint16 _minFinalityThreshold,\n uint16 _feePremiumBps\n ) external virtual onlyGovernor initializer {\n _initialize(_operator, _minFinalityThreshold, _feePremiumBps);\n\n address[] memory rewardTokens = new address[](0);\n address[] memory assets = new address[](0);\n address[] memory pTokens = new address[](0);\n\n InitializableAbstractStrategy._initialize(\n rewardTokens,\n assets,\n pTokens\n );\n }\n\n /// @inheritdoc InitializableAbstractStrategy\n function deposit(address _asset, uint256 _amount)\n external\n override\n onlyVault\n nonReentrant\n {\n _deposit(_asset, _amount);\n }\n\n /// @inheritdoc InitializableAbstractStrategy\n function depositAll() external override onlyVault nonReentrant {\n uint256 balance = IERC20(usdcToken).balanceOf(address(this));\n // Deposit if balance is greater than 1 USDC\n if (balance >= MIN_TRANSFER_AMOUNT) {\n _deposit(usdcToken, balance);\n }\n }\n\n /// @inheritdoc InitializableAbstractStrategy\n function withdraw(\n address _recipient,\n address _asset,\n uint256 _amount\n ) external override onlyVault nonReentrant {\n require(_recipient == vaultAddress, \"Only Vault can withdraw\");\n _withdraw(_asset, _amount);\n }\n\n /// @inheritdoc InitializableAbstractStrategy\n function withdrawAll() external override onlyVaultOrGovernor nonReentrant {\n if (isTransferPending()) {\n // Do nothing if there is a pending transfer\n // Note: We never want withdrawAll to fail, so\n // emit an event to indicate that the withdrawal was skipped\n emit WithdrawAllSkipped();\n return;\n }\n\n // Withdraw everything in Remote strategy\n uint256 _remoteBalance = remoteStrategyBalance;\n if (_remoteBalance < MIN_TRANSFER_AMOUNT) {\n // Do nothing if there is less than 1 USDC in the Remote strategy\n return;\n }\n\n _withdraw(\n usdcToken,\n _remoteBalance > MAX_TRANSFER_AMOUNT\n ? MAX_TRANSFER_AMOUNT\n : _remoteBalance\n );\n }\n\n /**\n * @notice Check the balance of the strategy that includes\n * the balance of the asset on this contract,\n * the amount of the asset being bridged,\n * and the balance reported by the Remote strategy.\n * @param _asset Address of the asset to check\n * @return balance Total balance of the asset\n */\n function checkBalance(address _asset)\n public\n view\n override\n returns (uint256 balance)\n {\n require(_asset == usdcToken, \"Unsupported asset\");\n\n // USDC balance on this contract\n // + USDC being bridged\n // + USDC cached in the corresponding Remote part of this contract\n return\n IERC20(usdcToken).balanceOf(address(this)) +\n pendingAmount +\n remoteStrategyBalance;\n }\n\n /// @inheritdoc InitializableAbstractStrategy\n function supportsAsset(address _asset) public view override returns (bool) {\n return _asset == usdcToken;\n }\n\n /// @inheritdoc InitializableAbstractStrategy\n function safeApproveAllTokens()\n external\n override\n onlyGovernor\n nonReentrant\n {}\n\n /// @inheritdoc InitializableAbstractStrategy\n function _abstractSetPToken(address, address) internal override {}\n\n /// @inheritdoc InitializableAbstractStrategy\n function collectRewardTokens()\n external\n override\n onlyHarvester\n nonReentrant\n {}\n\n /// @inheritdoc AbstractCCTPIntegrator\n function _onMessageReceived(bytes memory payload) internal override {\n if (\n payload.getMessageType() ==\n CrossChainStrategyHelper.BALANCE_CHECK_MESSAGE\n ) {\n // Received when Remote strategy checks the balance\n _processBalanceCheckMessage(payload);\n return;\n }\n\n revert(\"Unknown message type\");\n }\n\n /// @inheritdoc AbstractCCTPIntegrator\n function _onTokenReceived(\n uint256 tokenAmount,\n // solhint-disable-next-line no-unused-vars\n uint256 feeExecuted,\n bytes memory payload\n ) internal override {\n uint64 _nonce = lastTransferNonce;\n\n // Should be expecting an acknowledgement\n require(!isNonceProcessed(_nonce), \"Nonce already processed\");\n\n // Now relay to the regular flow\n // NOTE: Calling _onMessageReceived would mean that we are bypassing a\n // few checks that the regular flow does (like sourceDomainID check\n // and sender check in `handleReceiveFinalizedMessage`). However,\n // CCTPMessageRelayer relays the message first (which will go through\n // all the checks) and not update balance and then finally calls this\n // `_onTokenReceived` which will update the balance.\n // So, if any of the checks fail during the first no-balance-update flow,\n // this won't happen either, since the tx would revert.\n _onMessageReceived(payload);\n\n // Send any tokens in the contract to the Vault\n uint256 usdcBalance = IERC20(usdcToken).balanceOf(address(this));\n // Should always have enough tokens\n require(usdcBalance >= tokenAmount, \"Insufficient balance\");\n // Transfer all tokens to the Vault to not leave any dust\n IERC20(usdcToken).safeTransfer(vaultAddress, usdcBalance);\n\n // Emit withdrawal amount\n emit Withdrawal(usdcToken, usdcToken, usdcBalance);\n }\n\n /**\n * @dev Bridge and deposit asset into the remote strategy\n * @param _asset Address of the asset to deposit\n * @param depositAmount Amount of the asset to deposit\n */\n function _deposit(address _asset, uint256 depositAmount) internal virtual {\n require(_asset == usdcToken, \"Unsupported asset\");\n require(pendingAmount == 0, \"Unexpected pending amount\");\n // Deposit at least 1 USDC\n require(\n depositAmount >= MIN_TRANSFER_AMOUNT,\n \"Deposit amount too small\"\n );\n require(\n depositAmount <= MAX_TRANSFER_AMOUNT,\n \"Deposit amount too high\"\n );\n\n // Get the next nonce\n // Note: reverts if a transfer is pending\n uint64 nonce = _getNextNonce();\n\n // Set pending amount\n pendingAmount = depositAmount;\n\n // Build deposit message payload\n bytes memory message = CrossChainStrategyHelper.encodeDepositMessage(\n nonce,\n depositAmount\n );\n\n // Send deposit message to the remote strategy\n _sendTokens(depositAmount, message);\n\n // Emit deposit event\n emit Deposit(_asset, _asset, depositAmount);\n }\n\n /**\n * @dev Send a withdraw request to the remote strategy\n * @param _asset Address of the asset to withdraw\n * @param _amount Amount of the asset to withdraw\n */\n function _withdraw(address _asset, uint256 _amount) internal virtual {\n require(_asset == usdcToken, \"Unsupported asset\");\n // Withdraw at least 1 USDC\n require(_amount >= MIN_TRANSFER_AMOUNT, \"Withdraw amount too small\");\n require(\n _amount <= remoteStrategyBalance,\n \"Withdraw amount exceeds remote strategy balance\"\n );\n require(\n _amount <= MAX_TRANSFER_AMOUNT,\n \"Withdraw amount exceeds max transfer amount\"\n );\n\n // Get the next nonce\n // Note: reverts if a transfer is pending\n uint64 nonce = _getNextNonce();\n\n // Build and send withdrawal message with payload\n bytes memory message = CrossChainStrategyHelper.encodeWithdrawMessage(\n nonce,\n _amount\n );\n _sendMessage(message);\n\n // Emit WithdrawRequested event here,\n // Withdraw will be emitted in _onTokenReceived\n emit WithdrawRequested(usdcToken, _amount);\n }\n\n /**\n * @dev Process balance check:\n * - Confirms a deposit to the remote strategy\n * - Skips balance update if there's a pending withdrawal\n * - Updates the remote strategy balance\n * @param message The message containing the nonce and balance\n */\n function _processBalanceCheckMessage(bytes memory message)\n internal\n virtual\n {\n // Decode the message\n // When transferConfirmation is true, it means that the message is a result of a deposit or a withdrawal\n // process.\n (\n uint64 nonce,\n uint256 balance,\n bool transferConfirmation,\n uint256 timestamp\n ) = message.decodeBalanceCheckMessage();\n // Get the last cached nonce\n uint64 _lastCachedNonce = lastTransferNonce;\n\n if (nonce != _lastCachedNonce) {\n // If nonce is not the last cached nonce, it is an outdated message\n // Ignore it\n return;\n }\n\n // A received message nonce not yet processed indicates there is a\n // deposit or withdrawal in progress.\n bool transferInProgress = isTransferPending();\n\n if (transferInProgress) {\n if (transferConfirmation) {\n // Apply the effects of the deposit / withdrawal completion\n _markNonceAsProcessed(nonce);\n pendingAmount = 0;\n } else {\n // A balanceCheck arrived that is not part of the deposit / withdrawal process\n // that has been generated on the Remote contract after the deposit / withdrawal which is\n // still pending. This can happen when the CCTP bridge delivers the messages out of order.\n // Ignore it, since the pending deposit / withdrawal must first be cofirmed.\n emit BalanceCheckIgnored(nonce, timestamp, false);\n return;\n }\n } else {\n if (block.timestamp > timestamp + MAX_BALANCE_CHECK_AGE) {\n // Balance check is too old, ignore it\n emit BalanceCheckIgnored(nonce, timestamp, true);\n return;\n }\n }\n\n // At this point update the strategy balance the balanceCheck message is either:\n // - a confirmation of a deposit / withdrawal\n // - a message that updates balances when no deposit / withdrawal is in progress\n remoteStrategyBalance = balance;\n emit RemoteStrategyBalanceUpdated(balance);\n }\n}\n" + }, + "contracts/strategies/crosschain/CrossChainRemoteStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title CrossChainRemoteStrategy\n * @author Origin Protocol Inc\n *\n * @dev Part of the cross-chain strategy that lives on the remote chain.\n * Handles deposits and withdrawals from the master strategy on peer chain\n * and locally deposits the funds to a 4626 compatible vault.\n */\n\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport { IERC20 } from \"../../utils/InitializableAbstractStrategy.sol\";\nimport { IERC4626 } from \"../../../lib/openzeppelin/interfaces/IERC4626.sol\";\nimport { Generalized4626Strategy } from \"../Generalized4626Strategy.sol\";\nimport { AbstractCCTPIntegrator } from \"./AbstractCCTPIntegrator.sol\";\nimport { CrossChainStrategyHelper } from \"./CrossChainStrategyHelper.sol\";\nimport { InitializableAbstractStrategy } from \"../../utils/InitializableAbstractStrategy.sol\";\nimport { Strategizable } from \"../../governance/Strategizable.sol\";\n\ncontract CrossChainRemoteStrategy is\n AbstractCCTPIntegrator,\n Generalized4626Strategy,\n Strategizable\n{\n using SafeERC20 for IERC20;\n using CrossChainStrategyHelper for bytes;\n\n event DepositUnderlyingFailed(string reason);\n event WithdrawalFailed(uint256 amountRequested, uint256 amountAvailable);\n event WithdrawUnderlyingFailed(string reason);\n\n modifier onlyOperatorOrStrategistOrGovernor() {\n require(\n msg.sender == operator ||\n msg.sender == strategistAddr ||\n isGovernor(),\n \"Caller is not the Operator, Strategist or the Governor\"\n );\n _;\n }\n\n modifier onlyGovernorOrStrategist()\n override(InitializableAbstractStrategy, Strategizable) {\n require(\n msg.sender == strategistAddr || isGovernor(),\n \"Caller is not the Strategist or Governor\"\n );\n _;\n }\n\n constructor(\n BaseStrategyConfig memory _baseConfig,\n CCTPIntegrationConfig memory _cctpConfig\n )\n AbstractCCTPIntegrator(_cctpConfig)\n Generalized4626Strategy(_baseConfig, _cctpConfig.usdcToken)\n {\n require(usdcToken == address(assetToken), \"Token mismatch\");\n require(\n _baseConfig.platformAddress != address(0),\n \"Invalid platform address\"\n );\n // Vault address must always be address(0) for the remote strategy\n require(\n _baseConfig.vaultAddress == address(0),\n \"Invalid vault address\"\n );\n }\n\n /**\n * @dev Initialize the strategy implementation\n * @param _strategist Address of the strategist\n * @param _operator Address of the operator\n * @param _minFinalityThreshold Minimum finality threshold\n * @param _feePremiumBps Fee premium in basis points\n */\n function initialize(\n address _strategist,\n address _operator,\n uint16 _minFinalityThreshold,\n uint16 _feePremiumBps\n ) external virtual onlyGovernor initializer {\n _initialize(_operator, _minFinalityThreshold, _feePremiumBps);\n _setStrategistAddr(_strategist);\n\n address[] memory rewardTokens = new address[](0);\n address[] memory assets = new address[](1);\n address[] memory pTokens = new address[](1);\n\n assets[0] = address(usdcToken);\n pTokens[0] = address(platformAddress);\n\n InitializableAbstractStrategy._initialize(\n rewardTokens,\n assets,\n pTokens\n );\n }\n\n /// @inheritdoc Generalized4626Strategy\n function deposit(address _asset, uint256 _amount)\n external\n virtual\n override\n onlyGovernorOrStrategist\n nonReentrant\n {\n _deposit(_asset, _amount);\n }\n\n /// @inheritdoc Generalized4626Strategy\n function depositAll()\n external\n virtual\n override\n onlyGovernorOrStrategist\n nonReentrant\n {\n _deposit(usdcToken, IERC20(usdcToken).balanceOf(address(this)));\n }\n\n /// @inheritdoc Generalized4626Strategy\n /// @dev Interface requires a recipient, but for compatibility it must be address(this).\n function withdraw(\n address _recipient,\n address _asset,\n uint256 _amount\n ) external virtual override onlyGovernorOrStrategist nonReentrant {\n _withdraw(_recipient, _asset, _amount);\n }\n\n /// @inheritdoc Generalized4626Strategy\n function withdrawAll()\n external\n virtual\n override\n onlyGovernorOrStrategist\n nonReentrant\n {\n IERC4626 platform = IERC4626(platformAddress);\n _withdraw(\n address(this),\n usdcToken,\n platform.previewRedeem(platform.balanceOf(address(this)))\n );\n }\n\n /// @inheritdoc AbstractCCTPIntegrator\n function _onMessageReceived(bytes memory payload) internal override {\n uint32 messageType = payload.getMessageType();\n if (messageType == CrossChainStrategyHelper.DEPOSIT_MESSAGE) {\n // Received when Master strategy sends tokens to the remote strategy\n // Do nothing because we receive acknowledgement with token transfer,\n // so _onTokenReceived will handle it\n } else if (messageType == CrossChainStrategyHelper.WITHDRAW_MESSAGE) {\n // Received when Master strategy requests a withdrawal\n _processWithdrawMessage(payload);\n } else {\n revert(\"Unknown message type\");\n }\n }\n\n /**\n * @dev Process deposit message from peer strategy\n * @param tokenAmount Amount of tokens received\n * @param feeExecuted Fee executed\n * @param payload Payload of the message\n */\n function _processDepositMessage(\n // solhint-disable-next-line no-unused-vars\n uint256 tokenAmount,\n // solhint-disable-next-line no-unused-vars\n uint256 feeExecuted,\n bytes memory payload\n ) internal virtual {\n (uint64 nonce, ) = payload.decodeDepositMessage();\n\n // Replay protection is part of the _markNonceAsProcessed function\n _markNonceAsProcessed(nonce);\n\n // Deposit everything we got, not just what was bridged\n uint256 balance = IERC20(usdcToken).balanceOf(address(this));\n\n // Underlying call to deposit funds can fail. It mustn't affect the overall\n // flow as confirmation message should still be sent.\n if (balance >= MIN_TRANSFER_AMOUNT) {\n _deposit(usdcToken, balance);\n }\n\n // Send balance check message to the peer strategy\n bytes memory message = CrossChainStrategyHelper\n .encodeBalanceCheckMessage(\n lastTransferNonce,\n checkBalance(usdcToken),\n true,\n block.timestamp\n );\n _sendMessage(message);\n }\n\n /**\n * @dev Deposit assets by converting them to shares\n * @param _asset Address of asset to deposit\n * @param _amount Amount of asset to deposit\n */\n function _deposit(address _asset, uint256 _amount) internal override {\n // By design, this function should not revert. Otherwise, it'd\n // not be able to process messages and might freeze the contracts\n // state. However these two require statements would never fail\n // in every function invoking this. The same kind of checks should\n // be enforced in all the calling functions for these two and any\n // other require statements added to this function.\n require(_amount > 0, \"Must deposit something\");\n require(_asset == address(usdcToken), \"Unexpected asset address\");\n\n // This call can fail, and the failure doesn't need to bubble up to the _processDepositMessage function\n // as the flow is not affected by the failure.\n\n try IERC4626(platformAddress).deposit(_amount, address(this)) {\n emit Deposit(_asset, address(shareToken), _amount);\n } catch Error(string memory reason) {\n emit DepositUnderlyingFailed(\n string(abi.encodePacked(\"Deposit failed: \", reason))\n );\n } catch (bytes memory lowLevelData) {\n emit DepositUnderlyingFailed(\n string(\n abi.encodePacked(\n \"Deposit failed: low-level call failed with data \",\n lowLevelData\n )\n )\n );\n }\n }\n\n /**\n * @dev Process withdrawal message from peer strategy\n * @param payload Payload of the message\n */\n function _processWithdrawMessage(bytes memory payload) internal virtual {\n (uint64 nonce, uint256 withdrawAmount) = payload\n .decodeWithdrawMessage();\n\n // Replay protection is part of the _markNonceAsProcessed function\n _markNonceAsProcessed(nonce);\n\n uint256 usdcBalance = IERC20(usdcToken).balanceOf(address(this));\n\n if (usdcBalance < withdrawAmount) {\n // Withdraw the missing funds from the remote strategy. This call can fail and\n // the failure doesn't bubble up to the _processWithdrawMessage function\n _withdraw(address(this), usdcToken, withdrawAmount - usdcBalance);\n\n // Update the possible increase in the balance on the contract.\n usdcBalance = IERC20(usdcToken).balanceOf(address(this));\n }\n\n // Check balance after withdrawal\n uint256 strategyBalance = checkBalance(usdcToken);\n\n // If there are some tokens to be sent AND the balance is sufficient\n // to satisfy the withdrawal request then send the funds to the peer strategy.\n // In case a direct withdraw(All) has previously been called\n // there is a possibility of USDC funds remaining on the contract.\n // A separate withdraw to extract or deposit to the Morpho vault needs to be\n // initiated from the peer Master strategy to utilise USDC funds.\n if (\n withdrawAmount >= MIN_TRANSFER_AMOUNT &&\n usdcBalance >= withdrawAmount\n ) {\n // The new balance on the contract needs to have USDC subtracted from it as\n // that will be withdrawn in the next step\n bytes memory message = CrossChainStrategyHelper\n .encodeBalanceCheckMessage(\n lastTransferNonce,\n strategyBalance - withdrawAmount,\n true,\n block.timestamp\n );\n _sendTokens(withdrawAmount, message);\n } else {\n // Contract either:\n // - only has small dust amount of USDC\n // - doesn't have sufficient funds to satisfy the withdrawal request\n // In both cases send the balance update message to the peer strategy.\n bytes memory message = CrossChainStrategyHelper\n .encodeBalanceCheckMessage(\n lastTransferNonce,\n strategyBalance,\n true,\n block.timestamp\n );\n _sendMessage(message);\n emit WithdrawalFailed(withdrawAmount, usdcBalance);\n }\n }\n\n /**\n * @dev Withdraw asset by burning shares\n * @param _recipient Address to receive withdrawn asset\n * @param _asset Address of asset to withdraw\n * @param _amount Amount of asset to withdraw\n */\n function _withdraw(\n address _recipient,\n address _asset,\n uint256 _amount\n ) internal override {\n require(_amount > 0, \"Must withdraw something\");\n require(_recipient == address(this), \"Invalid recipient\");\n require(_asset == address(usdcToken), \"Unexpected asset address\");\n\n // This call can fail, and the failure doesn't need to bubble up to the _processWithdrawMessage function\n // as the flow is not affected by the failure.\n try\n // slither-disable-next-line unused-return\n IERC4626(platformAddress).withdraw(\n _amount,\n address(this),\n address(this)\n )\n {\n emit Withdrawal(_asset, address(shareToken), _amount);\n } catch Error(string memory reason) {\n emit WithdrawUnderlyingFailed(\n string(abi.encodePacked(\"Withdrawal failed: \", reason))\n );\n } catch (bytes memory lowLevelData) {\n emit WithdrawUnderlyingFailed(\n string(\n abi.encodePacked(\n \"Withdrawal failed: low-level call failed with data \",\n lowLevelData\n )\n )\n );\n }\n }\n\n /**\n * @dev Process token received message from peer strategy\n * @param tokenAmount Amount of tokens received\n * @param feeExecuted Fee executed\n * @param payload Payload of the message\n */\n function _onTokenReceived(\n uint256 tokenAmount,\n uint256 feeExecuted,\n bytes memory payload\n ) internal override {\n uint32 messageType = payload.getMessageType();\n\n require(\n messageType == CrossChainStrategyHelper.DEPOSIT_MESSAGE,\n \"Invalid message type\"\n );\n\n _processDepositMessage(tokenAmount, feeExecuted, payload);\n }\n\n /**\n * @dev Send balance update message to the peer strategy\n */\n function sendBalanceUpdate()\n external\n virtual\n onlyOperatorOrStrategistOrGovernor\n {\n uint256 balance = checkBalance(usdcToken);\n bytes memory message = CrossChainStrategyHelper\n .encodeBalanceCheckMessage(\n lastTransferNonce,\n balance,\n false,\n block.timestamp\n );\n _sendMessage(message);\n }\n\n /**\n * @notice Get the total asset value held in the platform and contract\n * @param _asset Address of the asset\n * @return balance Total value of the asset in the platform and contract\n */\n function checkBalance(address _asset)\n public\n view\n override\n returns (uint256)\n {\n require(_asset == usdcToken, \"Unexpected asset address\");\n /**\n * Balance of USDC on the contract is counted towards the total balance, since a deposit\n * to the Morpho V2 might fail and the USDC might remain on this contract as a result of a\n * bridged transfer.\n */\n uint256 balanceOnContract = IERC20(usdcToken).balanceOf(address(this));\n\n IERC4626 platform = IERC4626(platformAddress);\n return\n platform.previewRedeem(platform.balanceOf(address(this))) +\n balanceOnContract;\n }\n}\n" + }, + "contracts/strategies/crosschain/CrossChainStrategyHelper.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title CrossChainStrategyHelper\n * @author Origin Protocol Inc\n * @dev This library is used to encode and decode the messages for the cross-chain strategy.\n * It is used to ensure that the messages are valid and to get the message version and type.\n */\n\nimport { BytesHelper } from \"../../utils/BytesHelper.sol\";\n\nlibrary CrossChainStrategyHelper {\n using BytesHelper for bytes;\n\n uint32 public constant DEPOSIT_MESSAGE = 1;\n uint32 public constant WITHDRAW_MESSAGE = 2;\n uint32 public constant BALANCE_CHECK_MESSAGE = 3;\n\n uint32 public constant CCTP_MESSAGE_VERSION = 1;\n uint32 public constant ORIGIN_MESSAGE_VERSION = 1010;\n\n // CCTP Message Header fields\n // Ref: https://developers.circle.com/cctp/technical-guide#message-header\n uint8 private constant VERSION_INDEX = 0;\n uint8 private constant SOURCE_DOMAIN_INDEX = 4;\n uint8 private constant SENDER_INDEX = 44;\n uint8 private constant RECIPIENT_INDEX = 76;\n uint8 private constant MESSAGE_BODY_INDEX = 148;\n\n /**\n * @dev Get the message version from the message.\n * It should always be 4 bytes long,\n * starting from the 0th index.\n * @param message The message to get the version from\n * @return The message version\n */\n function getMessageVersion(bytes memory message)\n internal\n pure\n returns (uint32)\n {\n // uint32 bytes 0 to 4 is Origin message version\n // uint32 bytes 4 to 8 is Message type\n return message.extractUint32(0);\n }\n\n /**\n * @dev Get the message type from the message.\n * It should always be 4 bytes long,\n * starting from the 4th index.\n * @param message The message to get the type from\n * @return The message type\n */\n function getMessageType(bytes memory message)\n internal\n pure\n returns (uint32)\n {\n // uint32 bytes 0 to 4 is Origin message version\n // uint32 bytes 4 to 8 is Message type\n return message.extractUint32(4);\n }\n\n /**\n * @dev Verify the message version and type.\n * The message version should be the same as the Origin message version,\n * and the message type should be the same as the expected message type.\n * @param _message The message to verify\n * @param _type The expected message type\n */\n function verifyMessageVersionAndType(bytes memory _message, uint32 _type)\n internal\n pure\n {\n require(\n getMessageVersion(_message) == ORIGIN_MESSAGE_VERSION,\n \"Invalid Origin Message Version\"\n );\n require(getMessageType(_message) == _type, \"Invalid Message type\");\n }\n\n /**\n * @dev Get the message payload from the message.\n * The payload starts at the 8th byte.\n * @param message The message to get the payload from\n * @return The message payload\n */\n function getMessagePayload(bytes memory message)\n internal\n pure\n returns (bytes memory)\n {\n // uint32 bytes 0 to 4 is Origin message version\n // uint32 bytes 4 to 8 is Message type\n // Payload starts at byte 8\n return message.extractSlice(8, message.length);\n }\n\n /**\n * @dev Encode the deposit message.\n * The message version and type are always encoded in the message.\n * @param nonce The nonce of the deposit\n * @param depositAmount The amount of the deposit\n * @return The encoded deposit message\n */\n function encodeDepositMessage(uint64 nonce, uint256 depositAmount)\n internal\n pure\n returns (bytes memory)\n {\n return\n abi.encodePacked(\n ORIGIN_MESSAGE_VERSION,\n DEPOSIT_MESSAGE,\n abi.encode(nonce, depositAmount)\n );\n }\n\n /**\n * @dev Decode the deposit message.\n * The message version and type are verified in the message.\n * @param message The message to decode\n * @return The nonce and the amount of the deposit\n */\n function decodeDepositMessage(bytes memory message)\n internal\n pure\n returns (uint64, uint256)\n {\n verifyMessageVersionAndType(message, DEPOSIT_MESSAGE);\n\n (uint64 nonce, uint256 depositAmount) = abi.decode(\n getMessagePayload(message),\n (uint64, uint256)\n );\n return (nonce, depositAmount);\n }\n\n /**\n * @dev Encode the withdrawal message.\n * The message version and type are always encoded in the message.\n * @param nonce The nonce of the withdrawal\n * @param withdrawAmount The amount of the withdrawal\n * @return The encoded withdrawal message\n */\n function encodeWithdrawMessage(uint64 nonce, uint256 withdrawAmount)\n internal\n pure\n returns (bytes memory)\n {\n return\n abi.encodePacked(\n ORIGIN_MESSAGE_VERSION,\n WITHDRAW_MESSAGE,\n abi.encode(nonce, withdrawAmount)\n );\n }\n\n /**\n * @dev Decode the withdrawal message.\n * The message version and type are verified in the message.\n * @param message The message to decode\n * @return The nonce and the amount of the withdrawal\n */\n function decodeWithdrawMessage(bytes memory message)\n internal\n pure\n returns (uint64, uint256)\n {\n verifyMessageVersionAndType(message, WITHDRAW_MESSAGE);\n\n (uint64 nonce, uint256 withdrawAmount) = abi.decode(\n getMessagePayload(message),\n (uint64, uint256)\n );\n return (nonce, withdrawAmount);\n }\n\n /**\n * @dev Encode the balance check message.\n * The message version and type are always encoded in the message.\n * @param nonce The nonce of the balance check\n * @param balance The balance to check\n * @param transferConfirmation Indicates if the message is a transfer confirmation. This is true\n * when the message is a result of a deposit or a withdrawal.\n * @return The encoded balance check message\n */\n function encodeBalanceCheckMessage(\n uint64 nonce,\n uint256 balance,\n bool transferConfirmation,\n uint256 timestamp\n ) internal pure returns (bytes memory) {\n return\n abi.encodePacked(\n ORIGIN_MESSAGE_VERSION,\n BALANCE_CHECK_MESSAGE,\n abi.encode(nonce, balance, transferConfirmation, timestamp)\n );\n }\n\n /**\n * @dev Decode the balance check message.\n * The message version and type are verified in the message.\n * @param message The message to decode\n * @return The nonce, the balance and indicates if the message is a transfer confirmation\n */\n function decodeBalanceCheckMessage(bytes memory message)\n internal\n pure\n returns (\n uint64,\n uint256,\n bool,\n uint256\n )\n {\n verifyMessageVersionAndType(message, BALANCE_CHECK_MESSAGE);\n\n (\n uint64 nonce,\n uint256 balance,\n bool transferConfirmation,\n uint256 timestamp\n ) = abi.decode(\n getMessagePayload(message),\n (uint64, uint256, bool, uint256)\n );\n return (nonce, balance, transferConfirmation, timestamp);\n }\n\n /**\n * @dev Decode the CCTP message header\n * @param message Message to decode\n * @return version Version of the message\n * @return sourceDomainID Source domain ID\n * @return sender Sender of the message\n * @return recipient Recipient of the message\n * @return messageBody Message body\n */\n function decodeMessageHeader(bytes memory message)\n internal\n pure\n returns (\n uint32 version,\n uint32 sourceDomainID,\n address sender,\n address recipient,\n bytes memory messageBody\n )\n {\n version = message.extractUint32(VERSION_INDEX);\n sourceDomainID = message.extractUint32(SOURCE_DOMAIN_INDEX);\n // Address of MessageTransmitterV2 caller on source domain\n sender = message.extractAddress(SENDER_INDEX);\n // Address to handle message body on destination domain\n recipient = message.extractAddress(RECIPIENT_INDEX);\n messageBody = message.extractSlice(MESSAGE_BODY_INDEX, message.length);\n }\n}\n" + }, + "contracts/strategies/CurveAMOStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title Curve Automated Market Maker (AMO) Strategy\n * @notice AMO strategy for a Curve pool using an OToken.\n * @author Origin Protocol Inc\n */\nimport { Math } from \"@openzeppelin/contracts/utils/math/Math.sol\";\nimport { SafeCast } from \"@openzeppelin/contracts/utils/math/SafeCast.sol\";\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\n\nimport { IERC20, InitializableAbstractStrategy } from \"../utils/InitializableAbstractStrategy.sol\";\nimport { StableMath } from \"../utils/StableMath.sol\";\nimport { IVault } from \"../interfaces/IVault.sol\";\nimport { ICurveStableSwapNG } from \"../interfaces/ICurveStableSwapNG.sol\";\nimport { ICurveLiquidityGaugeV6 } from \"../interfaces/ICurveLiquidityGaugeV6.sol\";\nimport { IBasicToken } from \"../interfaces/IBasicToken.sol\";\nimport { ICurveMinter } from \"../interfaces/ICurveMinter.sol\";\n\ncontract CurveAMOStrategy is InitializableAbstractStrategy {\n using SafeCast for uint256;\n using SafeERC20 for IERC20;\n using StableMath for uint256;\n\n /**\n * @dev a threshold under which the contract no longer allows for the protocol to manually rebalance.\n * Guarding against a strategist / guardian being taken over and with multiple transactions\n * draining the protocol funds.\n */\n uint256 public constant SOLVENCY_THRESHOLD = 0.998 ether;\n\n // New immutable variables that must be set in the constructor\n /**\n * @notice Address of the hard asset (weth, usdt, usdc).\n */\n IERC20 public immutable hardAsset;\n\n /**\n * @notice Address of the OTOKEN token contract.\n */\n IERC20 public immutable oToken;\n\n /**\n * @notice Address of the LP (Liquidity Provider) token contract.\n */\n IERC20 public immutable lpToken;\n\n /**\n * @notice Address of the Curve StableSwap NG pool contract.\n */\n ICurveStableSwapNG public immutable curvePool;\n\n /**\n * @notice Address of the Curve X-Chain Liquidity Gauge contract.\n */\n ICurveLiquidityGaugeV6 public immutable gauge;\n\n /**\n * @notice Address of the Curve Minter contract.\n */\n ICurveMinter public immutable minter;\n\n /**\n * @notice Index of the OTOKEN and hardAsset in the Curve pool.\n */\n uint128 public immutable otokenCoinIndex;\n uint128 public immutable hardAssetCoinIndex;\n\n /**\n * @notice Decimals of the OTOKEN and hardAsset.\n */\n uint8 public immutable decimalsOToken;\n uint8 public immutable decimalsHardAsset;\n\n /**\n * @notice Maximum slippage allowed for adding/removing liquidity from the Curve pool.\n */\n uint256 public maxSlippage;\n\n event MaxSlippageUpdated(uint256 newMaxSlippage);\n\n /**\n * @dev Verifies that the caller is the Strategist.\n */\n modifier onlyStrategist() {\n require(\n msg.sender == IVault(vaultAddress).strategistAddr(),\n \"Caller is not the Strategist\"\n );\n _;\n }\n\n /**\n * @dev Checks the Curve pool's balances have improved and the balances\n * have not tipped to the other side.\n * This modifier is only applied to functions that do a single sided add or remove.\n * The standard deposit function adds to both sides of the pool in a way that\n * the pool's balance is not worsened.\n * Withdrawals are proportional so doesn't change the pools asset balance.\n */\n modifier improvePoolBalance() {\n // Get the hard asset and OToken balances in the Curve pool\n uint256[] memory balancesBefore = curvePool.get_balances();\n // diff = hardAsset balance - OTOKEN balance\n int256 diffBefore = (\n balancesBefore[hardAssetCoinIndex].scaleBy(\n decimalsOToken,\n decimalsHardAsset\n )\n ).toInt256() - balancesBefore[otokenCoinIndex].toInt256();\n\n _;\n\n // Get the hard asset and OToken balances in the Curve pool\n uint256[] memory balancesAfter = curvePool.get_balances();\n // diff = hardAsset balance - OTOKEN balance\n int256 diffAfter = (\n balancesAfter[hardAssetCoinIndex].scaleBy(\n decimalsOToken,\n decimalsHardAsset\n )\n ).toInt256() - balancesAfter[otokenCoinIndex].toInt256();\n\n if (diffBefore == 0) {\n require(diffAfter == 0, \"Position balance is worsened\");\n } else if (diffBefore < 0) {\n // If the pool was originally imbalanced in favor of OTOKEN, then\n // we want to check that the pool is now more balanced\n require(diffAfter <= 0, \"OTokens overshot peg\");\n require(diffBefore < diffAfter, \"OTokens balance worse\");\n } else if (diffBefore > 0) {\n // If the pool was originally imbalanced in favor of hardAsset, then\n // we want to check that the pool is now more balanced\n require(diffAfter >= 0, \"Assets overshot peg\");\n require(diffAfter < diffBefore, \"Assets balance worse\");\n }\n }\n\n constructor(\n BaseStrategyConfig memory _baseConfig,\n address _otoken,\n address _hardAsset,\n address _gauge,\n address _minter\n ) InitializableAbstractStrategy(_baseConfig) {\n lpToken = IERC20(_baseConfig.platformAddress);\n curvePool = ICurveStableSwapNG(_baseConfig.platformAddress);\n minter = ICurveMinter(_minter);\n\n oToken = IERC20(_otoken);\n hardAsset = IERC20(_hardAsset);\n gauge = ICurveLiquidityGaugeV6(_gauge);\n decimalsHardAsset = IBasicToken(_hardAsset).decimals();\n decimalsOToken = IBasicToken(_otoken).decimals();\n\n (hardAssetCoinIndex, otokenCoinIndex) = curvePool.coins(0) == _hardAsset\n ? (0, 1)\n : (1, 0);\n require(\n curvePool.coins(otokenCoinIndex) == _otoken &&\n curvePool.coins(hardAssetCoinIndex) == _hardAsset,\n \"Invalid coin indexes\"\n );\n require(gauge.lp_token() == address(curvePool), \"Invalid pool\");\n }\n\n /**\n * Initializer for setting up strategy internal state. This overrides the\n * InitializableAbstractStrategy initializer as Curve strategies don't fit\n * well within that abstraction.\n * @param _rewardTokenAddresses Address of CRV\n * @param _maxSlippage Maximum slippage allowed for adding/removing liquidity from the Curve pool.\n */\n function initialize(\n address[] calldata _rewardTokenAddresses, // CRV\n uint256 _maxSlippage\n ) external onlyGovernor initializer {\n address[] memory pTokens = new address[](1);\n pTokens[0] = address(curvePool);\n\n address[] memory _assets = new address[](1);\n _assets[0] = address(hardAsset);\n\n InitializableAbstractStrategy._initialize(\n _rewardTokenAddresses,\n _assets,\n pTokens\n );\n\n _approveBase();\n _setMaxSlippage(_maxSlippage);\n }\n\n /***************************************\n Deposit\n ****************************************/\n\n /**\n * @notice Deposit hard asset into the Curve pool\n * @param _hardAsset Address of hard asset contract.\n * @param _amount Amount of hard asset to deposit.\n */\n function deposit(address _hardAsset, uint256 _amount)\n external\n override\n onlyVault\n nonReentrant\n {\n _deposit(_hardAsset, _amount);\n }\n\n function _deposit(address _hardAsset, uint256 _hardAssetAmount) internal {\n require(_hardAssetAmount > 0, \"Must deposit something\");\n require(_hardAsset == address(hardAsset), \"Unsupported asset\");\n\n emit Deposit(_hardAsset, address(lpToken), _hardAssetAmount);\n uint256 scaledHardAssetAmount = _hardAssetAmount.scaleBy(\n decimalsOToken,\n decimalsHardAsset\n );\n\n // Get the asset and OToken balances in the Curve pool\n uint256[] memory balances = curvePool.get_balances();\n // safe to cast since min value is at least 0\n uint256 otokenToAdd = uint256(\n _max(\n 0,\n (\n balances[hardAssetCoinIndex].scaleBy(\n decimalsOToken,\n decimalsHardAsset\n )\n ).toInt256() +\n scaledHardAssetAmount.toInt256() -\n balances[otokenCoinIndex].toInt256()\n )\n );\n\n /* Add so much OTOKEN so that the pool ends up being balanced. And at minimum\n * add as much OTOKEN as hard asset and at maximum twice as much OTOKEN.\n */\n otokenToAdd = Math.max(otokenToAdd, scaledHardAssetAmount);\n otokenToAdd = Math.min(otokenToAdd, scaledHardAssetAmount * 2);\n\n /* Mint OTOKEN with a strategy that attempts to contribute to stability of OTOKEN/hardAsset pool. Try\n * to mint so much OTOKEN that after deployment of liquidity pool ends up being balanced.\n *\n * To manage unpredictability minimal OTOKEN minted will always be at least equal or greater\n * to hardAsset amount deployed. And never larger than twice the hardAsset amount deployed even if\n * it would have a further beneficial effect on pool stability.\n */\n IVault(vaultAddress).mintForStrategy(otokenToAdd);\n\n emit Deposit(address(oToken), address(lpToken), otokenToAdd);\n\n uint256[] memory _amounts = new uint256[](2);\n _amounts[hardAssetCoinIndex] = _hardAssetAmount;\n _amounts[otokenCoinIndex] = otokenToAdd;\n\n uint256 valueInLpTokens = (scaledHardAssetAmount + otokenToAdd)\n .divPrecisely(curvePool.get_virtual_price());\n uint256 minMintAmount = valueInLpTokens.mulTruncate(\n uint256(1e18) - maxSlippage\n );\n\n // Do the deposit to the Curve pool\n uint256 lpDeposited = curvePool.add_liquidity(_amounts, minMintAmount);\n require(lpDeposited >= minMintAmount, \"Min LP amount error\");\n\n // Deposit the Curve pool's LP tokens into the Curve gauge\n gauge.deposit(lpDeposited);\n\n // Ensure solvency of the vault\n _solvencyAssert();\n }\n\n /**\n * @notice Deposit the strategy's entire balance of hardAsset into the Curve pool\n */\n function depositAll() external override onlyVault nonReentrant {\n uint256 balance = hardAsset.balanceOf(address(this));\n if (balance > 0) {\n _deposit(address(hardAsset), balance);\n }\n }\n\n /***************************************\n Withdraw\n ****************************************/\n\n /**\n * @notice Withdraw hardAsset and OTOKEN from the Curve pool, burn the OTOKEN,\n * transfer hardAsset to the recipient.\n * @param _recipient Address to receive withdrawn asset which is normally the Vault.\n * @param _hardAsset Address of the hardAsset contract.\n * @param _amount Amount of hardAsset to withdraw.\n */\n function withdraw(\n address _recipient,\n address _hardAsset,\n uint256 _amount\n ) external override onlyVault nonReentrant {\n require(_amount > 0, \"Must withdraw something\");\n require(\n _hardAsset == address(hardAsset),\n \"Can only withdraw hard asset\"\n );\n\n emit Withdrawal(_hardAsset, address(lpToken), _amount);\n\n uint256 requiredLpTokens = calcTokenToBurn(\n _amount.scaleBy(decimalsOToken, decimalsHardAsset)\n );\n\n _lpWithdraw(requiredLpTokens);\n\n /* math in requiredLpTokens should correctly calculate the amount of LP to remove\n * in that the strategy receives enough hardAsset on balanced removal\n */\n uint256[] memory _minWithdrawalAmounts = new uint256[](2);\n _minWithdrawalAmounts[hardAssetCoinIndex] = _amount;\n // slither-disable-next-line unused-return\n curvePool.remove_liquidity(requiredLpTokens, _minWithdrawalAmounts);\n\n // Burn all the removed OTOKEN and any that was left in the strategy\n uint256 otokenToBurn = oToken.balanceOf(address(this));\n IVault(vaultAddress).burnForStrategy(otokenToBurn);\n\n emit Withdrawal(address(oToken), address(lpToken), otokenToBurn);\n\n // Transfer hardAsset to the recipient\n hardAsset.safeTransfer(_recipient, _amount);\n\n // Ensure solvency of the vault\n _solvencyAssert();\n }\n\n function calcTokenToBurn(uint256 _hardAssetAmount)\n internal\n view\n returns (uint256 lpToBurn)\n {\n /* The rate between coins in the pool determines the rate at which pool returns\n * tokens when doing balanced removal (remove_liquidity call). And by knowing how much hardAsset\n * we want we can determine how much of OTOKEN we receive by removing liquidity.\n *\n * Because we are doing balanced removal we should be making profit when removing liquidity in a\n * pool tilted to either side.\n *\n * Important: A downside is that the Strategist / Governor needs to be\n * cognisant of not removing too much liquidity. And while the proposal to remove liquidity\n * is being voted on the pool tilt might change so much that the proposal that has been valid while\n * created is no longer valid.\n */\n\n uint256 poolHardAssetBalance = curvePool\n .balances(hardAssetCoinIndex)\n .scaleBy(decimalsOToken, decimalsHardAsset);\n /* K is multiplied by 1e36 which is used for higher precision calculation of required\n * pool LP tokens. Without it the end value can have rounding errors up to precision of\n * 10 digits. This way we move the decimal point by 36 places when doing the calculation\n * and again by 36 places when we are done with it.\n */\n uint256 k = (1e36 * lpToken.totalSupply()) / poolHardAssetBalance;\n // prettier-ignore\n // slither-disable-next-line divide-before-multiply\n uint256 diff = (_hardAssetAmount + 1) * k;\n lpToBurn = diff / 1e36;\n }\n\n /**\n * @notice Remove all hardAsset and OTOKEN from the Curve pool, burn the OTOKEN,\n * transfer hardAsset to the Vault contract.\n */\n function withdrawAll() external override onlyVaultOrGovernor nonReentrant {\n uint256 gaugeTokens = gauge.balanceOf(address(this));\n _lpWithdraw(gaugeTokens);\n\n // Withdraws are proportional to assets held by 3Pool\n uint256[] memory minWithdrawAmounts = new uint256[](2);\n\n // Check balance of LP tokens in the strategy, if 0 return\n uint256 lpBalance = lpToken.balanceOf(address(this));\n\n // Remove liquidity\n // slither-disable-next-line unused-return\n if (lpBalance > 0) {\n curvePool.remove_liquidity(lpBalance, minWithdrawAmounts);\n }\n\n // Burn all OTOKEN\n uint256 otokenToBurn = oToken.balanceOf(address(this));\n IVault(vaultAddress).burnForStrategy(otokenToBurn);\n\n // Get the strategy contract's hardAsset balance.\n // This includes all that was removed from the Curve pool and\n // any hardAsset that was sitting in the strategy contract before the removal.\n uint256 hardAssetBalance = hardAsset.balanceOf(address(this));\n hardAsset.safeTransfer(vaultAddress, hardAssetBalance);\n\n if (hardAssetBalance > 0)\n emit Withdrawal(\n address(hardAsset),\n address(lpToken),\n hardAssetBalance\n );\n if (otokenToBurn > 0)\n emit Withdrawal(address(oToken), address(lpToken), otokenToBurn);\n }\n\n /***************************************\n Curve pool Rebalancing\n ****************************************/\n\n /**\n * @notice Mint OTokens and one-sided add to the Curve pool.\n * This is used when the Curve pool does not have enough OTokens and too many hardAsset.\n * The OToken/Asset, eg OTOKEN/hardAsset, price with increase.\n * The amount of assets in the vault is unchanged.\n * The total supply of OTokens is increased.\n * The asset value of the strategy and vault is increased.\n * @param _oTokens The amount of OTokens to be minted and added to the pool.\n */\n function mintAndAddOTokens(uint256 _oTokens)\n external\n onlyStrategist\n nonReentrant\n improvePoolBalance\n {\n IVault(vaultAddress).mintForStrategy(_oTokens);\n\n uint256[] memory amounts = new uint256[](2);\n amounts[otokenCoinIndex] = _oTokens;\n\n // Convert OTOKEN to Curve pool LP tokens\n uint256 valueInLpTokens = (_oTokens).divPrecisely(\n curvePool.get_virtual_price()\n );\n // Apply slippage to LP tokens\n uint256 minMintAmount = valueInLpTokens.mulTruncate(\n uint256(1e18) - maxSlippage\n );\n\n // Add the minted OTokens to the Curve pool\n uint256 lpDeposited = curvePool.add_liquidity(amounts, minMintAmount);\n require(lpDeposited >= minMintAmount, \"Min LP amount error\");\n\n // Deposit the Curve pool LP tokens to the Curve gauge\n gauge.deposit(lpDeposited);\n\n // Ensure solvency of the vault\n _solvencyAssert();\n\n emit Deposit(address(oToken), address(lpToken), _oTokens);\n }\n\n /**\n * @notice One-sided remove of OTokens from the Curve pool which are then burned.\n * This is used when the Curve pool has too many OTokens and not enough hardAsset.\n * The amount of assets in the vault is unchanged.\n * The total supply of OTokens is reduced.\n * The asset value of the strategy and vault is reduced.\n * @param _lpTokens The amount of Curve pool LP tokens to be burned for OTokens.\n */\n function removeAndBurnOTokens(uint256 _lpTokens)\n external\n onlyStrategist\n nonReentrant\n improvePoolBalance\n {\n // Withdraw Curve pool LP tokens from gauge and remove OTokens from the Curve pool\n uint256 otokenToBurn = _withdrawAndRemoveFromPool(\n _lpTokens,\n otokenCoinIndex\n );\n\n // The vault burns the OTokens from this strategy\n IVault(vaultAddress).burnForStrategy(otokenToBurn);\n\n // Ensure solvency of the vault\n _solvencyAssert();\n\n emit Withdrawal(address(oToken), address(lpToken), otokenToBurn);\n }\n\n /**\n * @notice One-sided remove of hardAsset from the Curve pool and transfer to the vault.\n * This is used when the Curve pool does not have enough OTokens and too many hardAsset.\n * The OToken/Asset, eg OTOKEN/hardAsset, price with decrease.\n * The amount of assets in the vault increases.\n * The total supply of OTokens does not change.\n * The asset value of the strategy reduces.\n * The asset value of the vault should be close to the same.\n * @param _lpTokens The amount of Curve pool LP tokens to be burned for hardAsset.\n * @dev Curve pool LP tokens is used rather than hardAsset assets as Curve does not\n * have a way to accurately calculate the amount of LP tokens for a required\n * amount of hardAsset. Curve's `calc_token_amount` function does not include fees.\n * A 3rd party library can be used that takes into account the fees, but this\n * is a gas intensive process. It's easier for the trusted strategist to\n * calculate the amount of Curve pool LP tokens required off-chain.\n */\n function removeOnlyAssets(uint256 _lpTokens)\n external\n onlyStrategist\n nonReentrant\n improvePoolBalance\n {\n // Withdraw Curve pool LP tokens from Curve gauge and remove hardAsset from the Curve pool\n uint256 hardAssetAmount = _withdrawAndRemoveFromPool(\n _lpTokens,\n hardAssetCoinIndex\n );\n\n // Transfer hardAsset to the vault\n hardAsset.safeTransfer(vaultAddress, hardAssetAmount);\n\n // Ensure solvency of the vault\n _solvencyAssert();\n\n emit Withdrawal(address(hardAsset), address(lpToken), hardAssetAmount);\n }\n\n /**\n * @dev Remove Curve pool LP tokens from the gauge and\n * do a one-sided remove of hardAsset or OTOKEN from the Curve pool.\n * @param _lpTokens The amount of Curve pool LP tokens to be removed from the gauge\n * @param coinIndex The index of the coin to be removed from the Curve pool. 0 = hardAsset, 1 = OTOKEN.\n * @return coinsRemoved The amount of hardAsset or OTOKEN removed from the Curve pool.\n */\n function _withdrawAndRemoveFromPool(uint256 _lpTokens, uint128 coinIndex)\n internal\n returns (uint256 coinsRemoved)\n {\n // Withdraw Curve pool LP tokens from Curve gauge\n _lpWithdraw(_lpTokens);\n\n // Convert Curve pool LP tokens to hardAsset value\n uint256 valueInEth = _lpTokens.mulTruncate(\n curvePool.get_virtual_price()\n );\n\n if (coinIndex == hardAssetCoinIndex) {\n valueInEth = valueInEth.scaleBy(decimalsHardAsset, decimalsOToken);\n }\n\n // Apply slippage to hardAsset value\n uint256 minAmount = valueInEth.mulTruncate(uint256(1e18) - maxSlippage);\n\n // Remove just the hardAsset from the Curve pool\n coinsRemoved = curvePool.remove_liquidity_one_coin(\n _lpTokens,\n int128(coinIndex),\n minAmount,\n address(this)\n );\n }\n\n /**\n * Checks that the protocol is solvent, protecting from a rogue Strategist / Guardian that can\n * keep rebalancing the pool in both directions making the protocol lose a tiny amount of\n * funds each time.\n *\n * Protocol must be at least SOLVENCY_THRESHOLD (99,8 %) backed in order for the rebalances to\n * function.\n */\n function _solvencyAssert() internal view {\n uint256 _totalVaultValue = IVault(vaultAddress).totalValue();\n uint256 _totalOtokenSupply = oToken.totalSupply();\n\n if (\n _totalVaultValue.divPrecisely(_totalOtokenSupply) <\n SOLVENCY_THRESHOLD\n ) {\n revert(\"Protocol insolvent\");\n }\n }\n\n /***************************************\n Assets and Rewards\n ****************************************/\n\n /**\n * @notice Collect accumulated CRV (and other) rewards and send to the Harvester.\n */\n function collectRewardTokens()\n external\n override\n onlyHarvester\n nonReentrant\n {\n // Collect CRV rewards from inflation\n minter.mint(address(gauge));\n\n // Collect extra gauge rewards (outside of CRV)\n gauge.claim_rewards();\n\n _collectRewardTokens();\n }\n\n function _lpWithdraw(uint256 _lpAmount) internal {\n require(\n gauge.balanceOf(address(this)) >= _lpAmount,\n \"Insufficient LP tokens\"\n );\n // withdraw lp tokens from the gauge without claiming rewards\n gauge.withdraw(_lpAmount);\n }\n\n /**\n * @notice Get the total asset value held in the platform\n * @param _asset Address of the asset\n * @return balance Total value of the asset in the platform\n */\n function checkBalance(address _asset)\n external\n view\n override\n returns (uint256 balance)\n {\n require(_asset == address(hardAsset), \"Unsupported asset\");\n\n // hardAsset balance needed here for the balance check that happens from vault during depositing.\n balance = hardAsset.balanceOf(address(this));\n uint256 lpTokens = gauge.balanceOf(address(this));\n if (lpTokens > 0) {\n balance += ((lpTokens * curvePool.get_virtual_price()) / 1e18)\n .scaleBy(decimalsHardAsset, decimalsOToken);\n }\n }\n\n /**\n * @notice Returns bool indicating whether asset is supported by strategy\n * @param _asset Address of the asset\n */\n function supportsAsset(address _asset) public view override returns (bool) {\n return _asset == address(hardAsset);\n }\n\n /***************************************\n Approvals\n ****************************************/\n\n /**\n * @notice Sets the maximum slippage allowed for any swap/liquidity operation\n * @param _maxSlippage Maximum slippage allowed, 1e18 = 100%.\n */\n function setMaxSlippage(uint256 _maxSlippage) external onlyGovernor {\n _setMaxSlippage(_maxSlippage);\n }\n\n function _setMaxSlippage(uint256 _maxSlippage) internal {\n require(_maxSlippage <= 5e16, \"Slippage must be less than 100%\");\n maxSlippage = _maxSlippage;\n emit MaxSlippageUpdated(_maxSlippage);\n }\n\n /**\n * @notice Approve the spending of all assets by their corresponding pool tokens,\n * if for some reason is it necessary.\n */\n function safeApproveAllTokens()\n external\n override\n onlyGovernor\n nonReentrant\n {\n _approveBase();\n }\n\n /**\n * @dev Since we are unwrapping WETH before depositing it to Curve\n * there is no need to set an approval for WETH on the Curve\n * pool\n * @param _asset Address of the asset\n * @param _pToken Address of the Curve LP token\n */\n // solhint-disable-next-line no-unused-vars\n function _abstractSetPToken(address _asset, address _pToken)\n internal\n override\n {}\n\n function _approveBase() internal {\n // Approve Curve pool for OTOKEN (required for adding liquidity)\n // slither-disable-next-line unused-return\n oToken.approve(platformAddress, type(uint256).max);\n\n // Approve Curve pool for hardAsset (required for adding liquidity)\n // slither-disable-next-line unused-return\n hardAsset.safeApprove(platformAddress, type(uint256).max);\n\n // Approve Curve gauge contract to transfer Curve pool LP tokens\n // This is needed for deposits if Curve pool LP tokens into the Curve gauge.\n // slither-disable-next-line unused-return\n lpToken.approve(address(gauge), type(uint256).max);\n }\n\n /**\n * @dev Returns the largest of two numbers int256 version\n */\n function _max(int256 a, int256 b) internal pure returns (int256) {\n return a >= b ? a : b;\n }\n}\n" + }, + "contracts/strategies/Generalized4626Strategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title Generalized 4626 Strategy\n * @notice Investment strategy for ERC-4626 Tokenized Vaults\n * @author Origin Protocol Inc\n */\nimport { IERC4626 } from \"../../lib/openzeppelin/interfaces/IERC4626.sol\";\nimport { IERC20, InitializableAbstractStrategy } from \"../utils/InitializableAbstractStrategy.sol\";\nimport { IDistributor } from \"../interfaces/IMerkl.sol\";\n\ncontract Generalized4626Strategy is InitializableAbstractStrategy {\n /// @notice The address of the Merkle Distributor contract.\n IDistributor public constant merkleDistributor =\n IDistributor(0x3Ef3D8bA38EBe18DB133cEc108f4D14CE00Dd9Ae);\n\n /// @dev Replaced with an immutable variable\n // slither-disable-next-line constable-states\n address private _deprecate_shareToken;\n /// @dev Replaced with an immutable variable\n // slither-disable-next-line constable-states\n address private _deprecate_assetToken;\n\n IERC20 public immutable shareToken;\n IERC20 public immutable assetToken;\n\n // For future use\n uint256[50] private __gap;\n\n event ClaimedRewards(address indexed token, uint256 amount);\n\n /**\n * @param _baseConfig Base strategy config with platformAddress (ERC-4626 Vault contract), eg sfrxETH or sDAI,\n * and vaultAddress (OToken Vault contract), eg VaultProxy or OETHVaultProxy\n * @param _assetToken Address of the ERC-4626 asset token. eg frxETH or DAI\n */\n constructor(BaseStrategyConfig memory _baseConfig, address _assetToken)\n InitializableAbstractStrategy(_baseConfig)\n {\n shareToken = IERC20(_baseConfig.platformAddress);\n assetToken = IERC20(_assetToken);\n }\n\n function initialize() external virtual onlyGovernor initializer {\n address[] memory rewardTokens = new address[](0);\n address[] memory assets = new address[](1);\n address[] memory pTokens = new address[](1);\n\n assets[0] = address(assetToken);\n pTokens[0] = address(platformAddress);\n\n InitializableAbstractStrategy._initialize(\n rewardTokens,\n assets,\n pTokens\n );\n }\n\n /**\n * @dev Deposit assets by converting them to shares\n * @param _asset Address of asset to deposit\n * @param _amount Amount of asset to deposit\n */\n function deposit(address _asset, uint256 _amount)\n external\n virtual\n override\n onlyVault\n nonReentrant\n {\n _deposit(_asset, _amount);\n }\n\n /**\n * @dev Deposit assets by converting them to shares\n * @param _asset Address of asset to deposit\n * @param _amount Amount of asset to deposit\n */\n function _deposit(address _asset, uint256 _amount) internal virtual {\n require(_amount > 0, \"Must deposit something\");\n require(_asset == address(assetToken), \"Unexpected asset address\");\n\n // slither-disable-next-line unused-return\n IERC4626(platformAddress).deposit(_amount, address(this));\n emit Deposit(_asset, address(shareToken), _amount);\n }\n\n /**\n * @dev Deposit the entire balance of assetToken to gain shareToken\n */\n function depositAll() external virtual override onlyVault nonReentrant {\n uint256 balance = assetToken.balanceOf(address(this));\n if (balance > 0) {\n _deposit(address(assetToken), balance);\n }\n }\n\n /**\n * @dev Withdraw asset by burning shares\n * @param _recipient Address to receive withdrawn asset\n * @param _asset Address of asset to withdraw\n * @param _amount Amount of asset to withdraw\n */\n function withdraw(\n address _recipient,\n address _asset,\n uint256 _amount\n ) external virtual override onlyVault nonReentrant {\n _withdraw(_recipient, _asset, _amount);\n }\n\n function _withdraw(\n address _recipient,\n address _asset,\n uint256 _amount\n ) internal virtual {\n require(_amount > 0, \"Must withdraw something\");\n require(_recipient != address(0), \"Must specify recipient\");\n require(_asset == address(assetToken), \"Unexpected asset address\");\n\n // slither-disable-next-line unused-return\n IERC4626(platformAddress).withdraw(_amount, _recipient, address(this));\n emit Withdrawal(_asset, address(shareToken), _amount);\n }\n\n /**\n * @dev Internal method to respond to the addition of new asset / share tokens\n */\n function _abstractSetPToken(address, address) internal virtual override {\n _approveBase();\n }\n\n /**\n * @dev Remove all assets from platform and send them to Vault contract.\n */\n function withdrawAll()\n external\n virtual\n override\n onlyVaultOrGovernor\n nonReentrant\n {\n uint256 shareBalance = shareToken.balanceOf(address(this));\n uint256 assetAmount = 0;\n if (shareBalance > 0) {\n assetAmount = IERC4626(platformAddress).redeem(\n shareBalance,\n vaultAddress,\n address(this)\n );\n emit Withdrawal(\n address(assetToken),\n address(shareToken),\n assetAmount\n );\n }\n }\n\n /**\n * @notice Get the total asset value held in the platform\n * @param _asset Address of the asset\n * @return balance Total value of the asset in the platform\n */\n function checkBalance(address _asset)\n public\n view\n virtual\n override\n returns (uint256 balance)\n {\n require(_asset == address(assetToken), \"Unexpected asset address\");\n /* We are intentionally not counting the amount of assetToken parked on the\n * contract toward the checkBalance. The deposit and withdraw functions\n * should not result in assetToken being unused and owned by this strategy\n * contract.\n */\n IERC4626 platform = IERC4626(platformAddress);\n return platform.previewRedeem(platform.balanceOf(address(this)));\n }\n\n /**\n * @notice Governor approves the ERC-4626 Tokenized Vault to spend the asset.\n */\n function safeApproveAllTokens() external override onlyGovernor {\n _approveBase();\n }\n\n function _approveBase() internal virtual {\n // Approval the asset to be transferred to the ERC-4626 Tokenized Vault.\n // Used by the ERC-4626 deposit() and mint() functions\n // slither-disable-next-line unused-return\n assetToken.approve(platformAddress, type(uint256).max);\n }\n\n /**\n * @dev Returns bool indicating whether asset is supported by strategy\n * @param _asset Address of the asset\n */\n function supportsAsset(address _asset)\n public\n view\n virtual\n override\n returns (bool)\n {\n return _asset == address(assetToken);\n }\n\n /**\n * @notice is not supported for this strategy as the asset and\n * ERC-4626 Tokenized Vault are set at deploy time.\n * @dev If the ERC-4626 Tokenized Vault needed to be changed, a new\n * contract would need to be deployed and the proxy updated.\n */\n function setPTokenAddress(address, address) external override onlyGovernor {\n revert(\"unsupported function\");\n }\n\n /**\n * @notice is not supported for this strategy as the asset and\n * ERC-4626 Tokenized Vault are set at deploy time.\n * @dev If the ERC-4626 Tokenized Vault needed to be changed, a new\n * contract would need to be deployed and the proxy updated.\n */\n function removePToken(uint256) external override onlyGovernor {\n revert(\"unsupported function\");\n }\n\n /// @notice Claim tokens from the Merkle Distributor\n /// @param token The address of the token to claim.\n /// @param amount The amount of tokens to claim.\n /// @param proof The Merkle proof to validate the claim.\n function merkleClaim(\n address token,\n uint256 amount,\n bytes32[] calldata proof\n ) external {\n address[] memory users = new address[](1);\n users[0] = address(this);\n\n address[] memory tokens = new address[](1);\n tokens[0] = token;\n\n uint256[] memory amounts = new uint256[](1);\n amounts[0] = amount;\n\n bytes32[][] memory proofs = new bytes32[][](1);\n proofs[0] = proof;\n\n merkleDistributor.claim(users, tokens, amounts, proofs);\n\n emit ClaimedRewards(token, amount);\n }\n}\n" + }, + "contracts/strategies/Generalized4626USDTStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\ninterface IUSDT {\n // Tether's approve does not return a bool like standard IERC20 contracts\n // slither-disable-next-line erc20-interface\n function approve(address _spender, uint256 _value) external;\n}\n\n/**\n * @title Generalized 4626 Strategy when asset is Tether USD (USDT)\n * @notice Investment strategy for ERC-4626 Tokenized Vaults for the USDT asset.\n * @author Origin Protocol Inc\n */\nimport { Generalized4626Strategy } from \"./Generalized4626Strategy.sol\";\n\ncontract Generalized4626USDTStrategy is Generalized4626Strategy {\n /**\n * @param _baseConfig Base strategy config with platformAddress (ERC-4626 Vault contract), eg sfrxETH or sDAI,\n * and vaultAddress (OToken Vault contract), eg VaultProxy or OETHVaultProxy\n * @param _assetToken Address of the ERC-4626 asset token. eg frxETH or DAI\n */\n constructor(BaseStrategyConfig memory _baseConfig, address _assetToken)\n Generalized4626Strategy(_baseConfig, _assetToken)\n {}\n\n /// @dev Override for Tether as USDT does not return a bool on approve.\n /// Using assetToken.approve will fail as it expects a bool return value\n function _approveBase() internal virtual override {\n // Approval the asset to be transferred to the ERC-4626 Tokenized Vault.\n // Used by the ERC-4626 deposit() and mint() functions\n // slither-disable-next-line unused-return\n IUSDT(address(assetToken)).approve(platformAddress, type(uint256).max);\n }\n}\n" + }, + "contracts/strategies/IAave.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @dev Interface for Aaves Lending Pool\n * Documentation: https://developers.aave.com/#lendingpool\n */\ninterface IAaveLendingPool {\n /**\n * @dev Deposits an `amount` of underlying asset into the reserve, receiving in return overlying aTokens.\n * - E.g. User deposits 100 USDC and gets in return 100 aUSDC\n * @param asset The address of the underlying asset to deposit\n * @param amount The amount to be deposited\n * @param onBehalfOf The address that will receive the aTokens, same as msg.sender if the user\n * wants to receive them on his own wallet, or a different address if the beneficiary of aTokens\n * is a different wallet\n * @param referralCode Code used to register the integrator originating the operation, for potential rewards.\n * 0 if the action is executed directly by the user, without any middle-man\n **/\n function deposit(\n address asset,\n uint256 amount,\n address onBehalfOf,\n uint16 referralCode\n ) external;\n\n /**\n * @dev Withdraws an `amount` of underlying asset from the reserve, burning the equivalent aTokens owned\n * E.g. User has 100 aUSDC, calls withdraw() and receives 100 USDC, burning the 100 aUSDC\n * @param asset The address of the underlying asset to withdraw\n * @param amount The underlying amount to be withdrawn\n * - Send the value type(uint256).max in order to withdraw the whole aToken balance\n * @param to Address that will receive the underlying, same as msg.sender if the user\n * wants to receive it on his own wallet, or a different address if the beneficiary is a\n * different wallet\n * @return The final amount withdrawn\n **/\n function withdraw(\n address asset,\n uint256 amount,\n address to\n ) external returns (uint256);\n}\n\n/**\n * @dev Interface for Aaves Lending Pool\n * Documentation: https://developers.aave.com/#lendingpooladdressesprovider\n */\ninterface ILendingPoolAddressesProvider {\n /**\n * @notice Get the current address for Aave LendingPool\n * @dev Lending pool is the core contract on which to call deposit\n */\n function getLendingPool() external view returns (address);\n}\n" + }, + "contracts/strategies/IAaveIncentivesController.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\ninterface IAaveIncentivesController {\n event RewardsAccrued(address indexed user, uint256 amount);\n\n event RewardsClaimed(\n address indexed user,\n address indexed to,\n uint256 amount\n );\n\n event RewardsClaimed(\n address indexed user,\n address indexed to,\n address indexed claimer,\n uint256 amount\n );\n\n event ClaimerSet(address indexed user, address indexed claimer);\n\n /*\n * @dev Returns the configuration of the distribution for a certain asset\n * @param asset The address of the reference asset of the distribution\n * @return The asset index, the emission per second and the last updated timestamp\n **/\n function getAssetData(address asset)\n external\n view\n returns (\n uint256,\n uint256,\n uint256\n );\n\n /**\n * @dev Whitelists an address to claim the rewards on behalf of another address\n * @param user The address of the user\n * @param claimer The address of the claimer\n */\n function setClaimer(address user, address claimer) external;\n\n /**\n * @dev Returns the whitelisted claimer for a certain address (0x0 if not set)\n * @param user The address of the user\n * @return The claimer address\n */\n function getClaimer(address user) external view returns (address);\n\n /**\n * @dev Configure assets for a certain rewards emission\n * @param assets The assets to incentivize\n * @param emissionsPerSecond The emission for each asset\n */\n function configureAssets(\n address[] calldata assets,\n uint256[] calldata emissionsPerSecond\n ) external;\n\n /**\n * @dev Called by the corresponding asset on any update that affects the rewards distribution\n * @param asset The address of the user\n * @param userBalance The balance of the user of the asset in the lending pool\n * @param totalSupply The total supply of the asset in the lending pool\n **/\n function handleAction(\n address asset,\n uint256 userBalance,\n uint256 totalSupply\n ) external;\n\n /**\n * @dev Returns the total of rewards of an user, already accrued + not yet accrued\n * @param user The address of the user\n * @return The rewards\n **/\n function getRewardsBalance(address[] calldata assets, address user)\n external\n view\n returns (uint256);\n\n /**\n * @dev Claims reward for an user, on all the assets of the lending pool,\n * accumulating the pending rewards\n * @param amount Amount of rewards to claim\n * @param to Address that will be receiving the rewards\n * @return Rewards claimed\n **/\n function claimRewards(\n address[] calldata assets,\n uint256 amount,\n address to\n ) external returns (uint256);\n\n /**\n * @dev Claims reward for an user on behalf, on all the assets of the\n * lending pool, accumulating the pending rewards. The caller must\n * be whitelisted via \"allowClaimOnBehalf\" function by the RewardsAdmin role manager\n * @param amount Amount of rewards to claim\n * @param user Address to check and claim rewards\n * @param to Address that will be receiving the rewards\n * @return Rewards claimed\n **/\n function claimRewardsOnBehalf(\n address[] calldata assets,\n uint256 amount,\n address user,\n address to\n ) external returns (uint256);\n\n /**\n * @dev returns the unclaimed rewards of the user\n * @param user the address of the user\n * @return the unclaimed user rewards\n */\n function getUserUnclaimedRewards(address user)\n external\n view\n returns (uint256);\n\n /**\n * @dev returns the unclaimed rewards of the user\n * @param user the address of the user\n * @param asset The asset to incentivize\n * @return the user index for the asset\n */\n function getUserAssetData(address user, address asset)\n external\n view\n returns (uint256);\n\n /**\n * @dev for backward compatibility with previous implementation of the Incentives controller\n */\n function REWARD_TOKEN() external view returns (address);\n\n /**\n * @dev for backward compatibility with previous implementation of the Incentives controller\n */\n function PRECISION() external view returns (uint8);\n\n /**\n * @dev Gets the distribution end timestamp of the emissions\n */\n function DISTRIBUTION_END() external view returns (uint256);\n}\n" + }, + "contracts/strategies/IAaveStakeToken.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\ninterface IAaveStakedToken {\n function COOLDOWN_SECONDS() external returns (uint256);\n\n function UNSTAKE_WINDOW() external returns (uint256);\n\n function balanceOf(address addr) external returns (uint256);\n\n function redeem(address to, uint256 amount) external;\n\n function stakersCooldowns(address addr) external returns (uint256);\n\n function cooldown() external;\n}\n" + }, + "contracts/strategies/ICompound.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @dev Compound C Token interface\n * Documentation: https://compound.finance/developers/ctokens\n */\ninterface ICERC20 {\n /**\n * @notice The mint function transfers an asset into the protocol, which begins accumulating\n * interest based on the current Supply Rate for the asset. The user receives a quantity of\n * cTokens equal to the underlying tokens supplied, divided by the current Exchange Rate.\n * @param mintAmount The amount of the asset to be supplied, in units of the underlying asset.\n * @return 0 on success, otherwise an Error codes\n */\n function mint(uint256 mintAmount) external returns (uint256);\n\n /**\n * @notice Sender redeems cTokens in exchange for the underlying asset\n * @dev Accrues interest whether or not the operation succeeds, unless reverted\n * @param redeemTokens The number of cTokens to redeem into underlying\n * @return uint 0=success, otherwise an error code.\n */\n function redeem(uint256 redeemTokens) external returns (uint256);\n\n /**\n * @notice The redeem underlying function converts cTokens into a specified quantity of the underlying\n * asset, and returns them to the user. The amount of cTokens redeemed is equal to the quantity of\n * underlying tokens received, divided by the current Exchange Rate. The amount redeemed must be less\n * than the user's Account Liquidity and the market's available liquidity.\n * @param redeemAmount The amount of underlying to be redeemed.\n * @return 0 on success, otherwise an error code.\n */\n function redeemUnderlying(uint256 redeemAmount) external returns (uint256);\n\n /**\n * @notice The user's underlying balance, representing their assets in the protocol, is equal to\n * the user's cToken balance multiplied by the Exchange Rate.\n * @param owner The account to get the underlying balance of.\n * @return The amount of underlying currently owned by the account.\n */\n function balanceOfUnderlying(address owner) external returns (uint256);\n\n /**\n * @notice Calculates the exchange rate from the underlying to the CToken\n * @dev This function does not accrue interest before calculating the exchange rate\n * @return Calculated exchange rate scaled by 1e18\n */\n function exchangeRateStored() external view returns (uint256);\n\n /**\n * @notice Get the token balance of the `owner`\n * @param owner The address of the account to query\n * @return The number of tokens owned by `owner`\n */\n function balanceOf(address owner) external view returns (uint256);\n\n /**\n * @notice Get the supply rate per block for supplying the token to Compound.\n */\n function supplyRatePerBlock() external view returns (uint256);\n\n /**\n * @notice Address of the Compound Comptroller.\n */\n function comptroller() external view returns (address);\n}\n" + }, + "contracts/strategies/IConvexDeposits.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\ninterface IConvexDeposits {\n function deposit(\n uint256 _pid,\n uint256 _amount,\n bool _stake\n ) external returns (bool);\n\n function deposit(\n uint256 _amount,\n bool _lock,\n address _stakeAddress\n ) external;\n}\n" + }, + "contracts/strategies/ICurveETHPoolV1.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\ninterface ICurveETHPoolV1 {\n event AddLiquidity(\n address indexed provider,\n uint256[2] token_amounts,\n uint256[2] fees,\n uint256 invariant,\n uint256 token_supply\n );\n event ApplyNewFee(uint256 fee);\n event Approval(\n address indexed owner,\n address indexed spender,\n uint256 value\n );\n event CommitNewFee(uint256 new_fee);\n event RampA(\n uint256 old_A,\n uint256 new_A,\n uint256 initial_time,\n uint256 future_time\n );\n event RemoveLiquidity(\n address indexed provider,\n uint256[2] token_amounts,\n uint256[2] fees,\n uint256 token_supply\n );\n event RemoveLiquidityImbalance(\n address indexed provider,\n uint256[2] token_amounts,\n uint256[2] fees,\n uint256 invariant,\n uint256 token_supply\n );\n event RemoveLiquidityOne(\n address indexed provider,\n uint256 token_amount,\n uint256 coin_amount,\n uint256 token_supply\n );\n event StopRampA(uint256 A, uint256 t);\n event TokenExchange(\n address indexed buyer,\n int128 sold_id,\n uint256 tokens_sold,\n int128 bought_id,\n uint256 tokens_bought\n );\n event Transfer(\n address indexed sender,\n address indexed receiver,\n uint256 value\n );\n\n function A() external view returns (uint256);\n\n function A_precise() external view returns (uint256);\n\n function DOMAIN_SEPARATOR() external view returns (bytes32);\n\n function add_liquidity(uint256[2] memory _amounts, uint256 _min_mint_amount)\n external\n payable\n returns (uint256);\n\n function add_liquidity(\n uint256[2] memory _amounts,\n uint256 _min_mint_amount,\n address _receiver\n ) external payable returns (uint256);\n\n function admin_action_deadline() external view returns (uint256);\n\n function admin_balances(uint256 i) external view returns (uint256);\n\n function admin_fee() external view returns (uint256);\n\n function allowance(address arg0, address arg1)\n external\n view\n returns (uint256);\n\n function apply_new_fee() external;\n\n function approve(address _spender, uint256 _value) external returns (bool);\n\n function balanceOf(address arg0) external view returns (uint256);\n\n function balances(uint256 arg0) external view returns (uint256);\n\n function calc_token_amount(uint256[2] memory _amounts, bool _is_deposit)\n external\n view\n returns (uint256);\n\n function calc_withdraw_one_coin(uint256 _burn_amount, int128 i)\n external\n view\n returns (uint256);\n\n function coins(uint256 arg0) external view returns (address);\n\n function commit_new_fee(uint256 _new_fee) external;\n\n function decimals() external view returns (uint256);\n\n function ema_price() external view returns (uint256);\n\n function exchange(\n int128 i,\n int128 j,\n uint256 _dx,\n uint256 _min_dy\n ) external payable returns (uint256);\n\n function exchange(\n int128 i,\n int128 j,\n uint256 _dx,\n uint256 _min_dy,\n address _receiver\n ) external payable returns (uint256);\n\n function fee() external view returns (uint256);\n\n function future_A() external view returns (uint256);\n\n function future_A_time() external view returns (uint256);\n\n function future_fee() external view returns (uint256);\n\n function get_balances() external view returns (uint256[2] memory);\n\n function get_dy(\n int128 i,\n int128 j,\n uint256 dx\n ) external view returns (uint256);\n\n function get_p() external view returns (uint256);\n\n function get_virtual_price() external view returns (uint256);\n\n function initial_A() external view returns (uint256);\n\n function initial_A_time() external view returns (uint256);\n\n function initialize(\n string memory _name,\n string memory _symbol,\n address[4] memory _coins,\n uint256[4] memory _rate_multipliers,\n uint256 _A,\n uint256 _fee\n ) external;\n\n function last_price() external view returns (uint256);\n\n function ma_exp_time() external view returns (uint256);\n\n function ma_last_time() external view returns (uint256);\n\n function name() external view returns (string memory);\n\n function nonces(address arg0) external view returns (uint256);\n\n function permit(\n address _owner,\n address _spender,\n uint256 _value,\n uint256 _deadline,\n uint8 _v,\n bytes32 _r,\n bytes32 _s\n ) external returns (bool);\n\n function price_oracle() external view returns (uint256);\n\n function ramp_A(uint256 _future_A, uint256 _future_time) external;\n\n function remove_liquidity(\n uint256 _burn_amount,\n uint256[2] memory _min_amounts\n ) external returns (uint256[2] memory);\n\n function remove_liquidity(\n uint256 _burn_amount,\n uint256[2] memory _min_amounts,\n address _receiver\n ) external returns (uint256[2] memory);\n\n function remove_liquidity_imbalance(\n uint256[2] memory _amounts,\n uint256 _max_burn_amount\n ) external returns (uint256);\n\n function remove_liquidity_imbalance(\n uint256[2] memory _amounts,\n uint256 _max_burn_amount,\n address _receiver\n ) external returns (uint256);\n\n function remove_liquidity_one_coin(\n uint256 _burn_amount,\n int128 i,\n uint256 _min_received\n ) external returns (uint256);\n\n function remove_liquidity_one_coin(\n uint256 _burn_amount,\n int128 i,\n uint256 _min_received,\n address _receiver\n ) external returns (uint256);\n\n function set_ma_exp_time(uint256 _ma_exp_time) external;\n\n function stop_ramp_A() external;\n\n function symbol() external view returns (string memory);\n\n function totalSupply() external view returns (uint256);\n\n function transfer(address _to, uint256 _value) external returns (bool);\n\n function transferFrom(\n address _from,\n address _to,\n uint256 _value\n ) external returns (bool);\n\n function version() external view returns (string memory);\n\n function withdraw_admin_fees() external;\n}\n" + }, + "contracts/strategies/ICurveMetaPool.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.4;\n\ninterface ICurveMetaPool {\n function add_liquidity(uint256[2] calldata amounts, uint256 min_mint_amount)\n external\n returns (uint256);\n\n function get_virtual_price() external view returns (uint256);\n\n function remove_liquidity(uint256 _amount, uint256[2] calldata min_amounts)\n external\n returns (uint256[2] calldata);\n\n function remove_liquidity_one_coin(\n uint256 _token_amount,\n int128 i,\n uint256 min_amount\n ) external returns (uint256);\n\n function remove_liquidity_imbalance(\n uint256[2] calldata amounts,\n uint256 max_burn_amount\n ) external returns (uint256);\n\n function calc_withdraw_one_coin(uint256 _token_amount, int128 i)\n external\n view\n returns (uint256);\n\n function balances(uint256 i) external view returns (uint256);\n\n function calc_token_amount(uint256[2] calldata amounts, bool deposit)\n external\n view\n returns (uint256);\n\n function base_pool() external view returns (address);\n\n function fee() external view returns (uint256);\n\n function coins(uint256 i) external view returns (address);\n\n function exchange(\n int128 i,\n int128 j,\n uint256 dx,\n uint256 min_dy\n ) external returns (uint256);\n\n function get_dy(\n int128 i,\n int128 j,\n uint256 dx\n ) external view returns (uint256);\n}\n" + }, + "contracts/strategies/ICurvePool.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\ninterface ICurvePool {\n function get_virtual_price() external view returns (uint256);\n\n function add_liquidity(uint256[3] calldata _amounts, uint256 _min) external;\n\n function balances(uint256) external view returns (uint256);\n\n function calc_token_amount(uint256[3] calldata _amounts, bool _deposit)\n external\n returns (uint256);\n\n function fee() external view returns (uint256);\n\n function remove_liquidity_one_coin(\n uint256 _amount,\n int128 _index,\n uint256 _minAmount\n ) external;\n\n function remove_liquidity(\n uint256 _amount,\n uint256[3] calldata _minWithdrawAmounts\n ) external;\n\n function calc_withdraw_one_coin(uint256 _amount, int128 _index)\n external\n view\n returns (uint256);\n\n function exchange(\n uint256 i,\n uint256 j,\n uint256 dx,\n uint256 min_dy\n ) external returns (uint256);\n\n function coins(uint256 _index) external view returns (address);\n\n function remove_liquidity_imbalance(\n uint256[3] calldata _amounts,\n uint256 maxBurnAmount\n ) external;\n}\n" + }, + "contracts/strategies/IRewardStaking.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\ninterface IRewardStaking {\n function stakeFor(address, uint256) external;\n\n function stake(uint256) external;\n\n function withdraw(uint256 amount, bool claim) external;\n\n function withdrawAndUnwrap(uint256 amount, bool claim) external;\n\n function earned(address account) external view returns (uint256);\n\n function getReward() external;\n\n function getReward(address _account, bool _claimExtras) external;\n\n function extraRewardsLength() external returns (uint256);\n\n function extraRewards(uint256 _pid) external returns (address);\n\n function rewardToken() external returns (address);\n\n function balanceOf(address account) external view returns (uint256);\n}\n" + }, + "contracts/strategies/MorphoAaveStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title OUSD Morpho Aave Strategy\n * @notice Investment strategy for investing stablecoins via Morpho (Aave)\n * @author Origin Protocol Inc\n */\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport { IERC20, InitializableAbstractStrategy } from \"../utils/InitializableAbstractStrategy.sol\";\nimport { IMorpho } from \"../interfaces/morpho/IMorpho.sol\";\nimport { ILens } from \"../interfaces/morpho/ILens.sol\";\nimport { StableMath } from \"../utils/StableMath.sol\";\n\ncontract MorphoAaveStrategy is InitializableAbstractStrategy {\n address public constant MORPHO = 0x777777c9898D384F785Ee44Acfe945efDFf5f3E0;\n address public constant LENS = 0x507fA343d0A90786d86C7cd885f5C49263A91FF4;\n\n using SafeERC20 for IERC20;\n using StableMath for uint256;\n\n constructor(BaseStrategyConfig memory _stratConfig)\n InitializableAbstractStrategy(_stratConfig)\n {}\n\n /**\n * @dev Initialize function, to set up initial internal state\n * @param _rewardTokenAddresses Address of reward token for platform\n * @param _assets Addresses of initial supported assets\n * @param _pTokens Platform Token corresponding addresses\n */\n function initialize(\n address[] calldata _rewardTokenAddresses,\n address[] calldata _assets,\n address[] calldata _pTokens\n ) external onlyGovernor initializer {\n InitializableAbstractStrategy._initialize(\n _rewardTokenAddresses,\n _assets,\n _pTokens\n );\n }\n\n /**\n * @dev Approve the spending of all assets by main Morpho contract,\n * if for some reason is it necessary.\n */\n function safeApproveAllTokens()\n external\n override\n onlyGovernor\n nonReentrant\n {\n uint256 assetCount = assetsMapped.length;\n for (uint256 i = 0; i < assetCount; i++) {\n address asset = assetsMapped[i];\n\n // Safe approval\n IERC20(asset).safeApprove(MORPHO, 0);\n IERC20(asset).safeApprove(MORPHO, type(uint256).max);\n }\n }\n\n /**\n * @dev Internal method to respond to the addition of new asset\n * We need to approve and allow Morpho to move them\n * @param _asset Address of the asset to approve\n * @param _pToken The pToken for the approval\n */\n // solhint-disable-next-line no-unused-vars\n function _abstractSetPToken(address _asset, address _pToken)\n internal\n override\n {\n IERC20(_asset).safeApprove(MORPHO, 0);\n IERC20(_asset).safeApprove(MORPHO, type(uint256).max);\n }\n\n /**\n * @dev Collect accumulated rewards and send them to Harvester.\n */\n function collectRewardTokens()\n external\n override\n onlyHarvester\n nonReentrant\n {\n // Morpho Aave-v2 doesn't distribute reward tokens\n // solhint-disable-next-line max-line-length\n // Ref: https://developers.morpho.xyz/interact-with-morpho/get-started/interact-with-morpho/claim-rewards#morpho-aave-v2\n }\n\n /**\n * @dev Get the amount of rewards pending to be collected from the protocol\n */\n function getPendingRewards() external view returns (uint256 balance) {\n // Morpho Aave-v2 doesn't distribute reward tokens\n // solhint-disable-next-line max-line-length\n // Ref: https://developers.morpho.xyz/interact-with-morpho/get-started/interact-with-morpho/claim-rewards#morpho-aave-v2\n return 0;\n }\n\n /**\n * @dev Deposit asset into Morpho\n * @param _asset Address of asset to deposit\n * @param _amount Amount of asset to deposit\n */\n function deposit(address _asset, uint256 _amount)\n external\n override\n onlyVault\n nonReentrant\n {\n _deposit(_asset, _amount);\n }\n\n /**\n * @dev Deposit asset into Morpho\n * @param _asset Address of asset to deposit\n * @param _amount Amount of asset to deposit\n */\n function _deposit(address _asset, uint256 _amount) internal {\n require(_amount > 0, \"Must deposit something\");\n\n address pToken = address(_getPTokenFor(_asset));\n\n IMorpho(MORPHO).supply(\n pToken,\n address(this), // the address of the user you want to supply on behalf of\n _amount\n );\n emit Deposit(_asset, pToken, _amount);\n }\n\n /**\n * @dev Deposit the entire balance of any supported asset into Morpho\n */\n function depositAll() external override onlyVault nonReentrant {\n for (uint256 i = 0; i < assetsMapped.length; i++) {\n uint256 balance = IERC20(assetsMapped[i]).balanceOf(address(this));\n if (balance > 0) {\n _deposit(assetsMapped[i], balance);\n }\n }\n }\n\n /**\n * @dev Withdraw asset from Morpho\n * @param _recipient Address to receive withdrawn asset\n * @param _asset Address of asset to withdraw\n * @param _amount Amount of asset to withdraw\n */\n function withdraw(\n address _recipient,\n address _asset,\n uint256 _amount\n ) external override onlyVault nonReentrant {\n _withdraw(_recipient, _asset, _amount);\n }\n\n function _withdraw(\n address _recipient,\n address _asset,\n uint256 _amount\n ) internal {\n require(_amount > 0, \"Must withdraw something\");\n require(_recipient != address(0), \"Must specify recipient\");\n\n address pToken = address(_getPTokenFor(_asset));\n\n IMorpho(MORPHO).withdraw(pToken, _amount);\n emit Withdrawal(_asset, pToken, _amount);\n IERC20(_asset).safeTransfer(_recipient, _amount);\n }\n\n /**\n * @dev Remove all assets from platform and send them to Vault contract.\n */\n function withdrawAll() external override onlyVaultOrGovernor nonReentrant {\n for (uint256 i = 0; i < assetsMapped.length; i++) {\n uint256 balance = _checkBalance(assetsMapped[i]);\n if (balance > 0) {\n _withdraw(vaultAddress, assetsMapped[i], balance);\n }\n }\n }\n\n /**\n * @dev Return total value of an asset held in the platform\n * @param _asset Address of the asset\n * @return balance Total value of the asset in the platform\n */\n function checkBalance(address _asset)\n external\n view\n override\n returns (uint256 balance)\n {\n return _checkBalance(_asset);\n }\n\n function _checkBalance(address _asset)\n internal\n view\n returns (uint256 balance)\n {\n address pToken = address(_getPTokenFor(_asset));\n\n // Total value represented by decimal position of underlying token\n (, , balance) = ILens(LENS).getCurrentSupplyBalanceInOf(\n pToken,\n address(this)\n );\n }\n\n /**\n * @dev Retuns bool indicating whether asset is supported by strategy\n * @param _asset Address of the asset\n */\n function supportsAsset(address _asset) public view override returns (bool) {\n return assetToPToken[_asset] != address(0);\n }\n\n /**\n * @dev Get the pToken wrapped in the IERC20 interface for this asset.\n * Fails if the pToken doesn't exist in our mappings.\n * @param _asset Address of the asset\n * @return pToken Corresponding pToken to this asset\n */\n function _getPTokenFor(address _asset) internal view returns (IERC20) {\n address pToken = assetToPToken[_asset];\n require(pToken != address(0), \"pToken does not exist\");\n return IERC20(pToken);\n }\n}\n" + }, + "contracts/strategies/MorphoCompoundStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title OUSD Morpho Compound Strategy\n * @notice Investment strategy for investing stablecoins via Morpho (Compound)\n * @author Origin Protocol Inc\n */\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport { IERC20, AbstractCompoundStrategy, InitializableAbstractStrategy } from \"./AbstractCompoundStrategy.sol\";\nimport { IMorpho } from \"../interfaces/morpho/IMorpho.sol\";\nimport { ILens } from \"../interfaces/morpho/ILens.sol\";\nimport { StableMath } from \"../utils/StableMath.sol\";\nimport \"../utils/Helpers.sol\";\n\ncontract MorphoCompoundStrategy is AbstractCompoundStrategy {\n address public constant MORPHO = 0x8888882f8f843896699869179fB6E4f7e3B58888;\n address public constant LENS = 0x930f1b46e1D081Ec1524efD95752bE3eCe51EF67;\n using SafeERC20 for IERC20;\n using StableMath for uint256;\n\n constructor(BaseStrategyConfig memory _stratConfig)\n InitializableAbstractStrategy(_stratConfig)\n {}\n\n /**\n * @dev Initialize function, to set up initial internal state\n * @param _rewardTokenAddresses Address of reward token for platform\n * @param _assets Addresses of initial supported assets\n * @param _pTokens Platform Token corresponding addresses\n */\n function initialize(\n address[] calldata _rewardTokenAddresses,\n address[] calldata _assets,\n address[] calldata _pTokens\n ) external onlyGovernor initializer {\n InitializableAbstractStrategy._initialize(\n _rewardTokenAddresses,\n _assets,\n _pTokens\n );\n }\n\n /**\n * @dev Approve the spending of all assets by main Morpho contract,\n * if for some reason is it necessary.\n */\n function safeApproveAllTokens()\n external\n override\n onlyGovernor\n nonReentrant\n {\n uint256 assetCount = assetsMapped.length;\n for (uint256 i = 0; i < assetCount; i++) {\n address asset = assetsMapped[i];\n\n // Safe approval\n IERC20(asset).safeApprove(MORPHO, 0);\n IERC20(asset).safeApprove(MORPHO, type(uint256).max);\n }\n }\n\n /**\n * @dev Internal method to respond to the addition of new asset\n * We need to approve and allow Morpho to move them\n * @param _asset Address of the asset to approve\n * @param _pToken The pToken for the approval\n */\n // solhint-disable-next-line no-unused-vars\n function _abstractSetPToken(address _asset, address _pToken)\n internal\n override\n {\n IERC20(_asset).safeApprove(MORPHO, 0);\n IERC20(_asset).safeApprove(MORPHO, type(uint256).max);\n }\n\n /**\n * @dev Collect accumulated rewards and send them to Harvester.\n */\n function collectRewardTokens()\n external\n override\n onlyHarvester\n nonReentrant\n {\n /**\n * Gas considerations. We could query Morpho LENS's `getUserUnclaimedRewards` for each\n * cToken separately and only claimRewards where it is economically feasible. Each call\n * (out of 3) costs ~60k gas extra.\n *\n * Each extra cToken in the `poolTokens` of `claimRewards` function makes that call\n * 89-120k more expensive gas wise.\n *\n * With Lens query in case where:\n * - there is only 1 reward token to collect. Net gas usage in best case would be\n * 3*60 - 2*120 = -60k -> saving 60k gas\n * - there are 2 reward tokens to collect. Net gas usage in best case would be\n * 3*60 - 120 = 60k -> more expensive for 60k gas\n * - there are 3 reward tokens to collect. Net gas usage in best case would be\n * 3*60 = 180k -> more expensive for 180k gas\n *\n * For the above reasoning such \"optimization\" is not implemented\n */\n\n address[] memory poolTokens = new address[](assetsMapped.length);\n for (uint256 i = 0; i < assetsMapped.length; i++) {\n poolTokens[i] = assetToPToken[assetsMapped[i]];\n }\n\n // slither-disable-next-line unused-return\n IMorpho(MORPHO).claimRewards(\n poolTokens, // The addresses of the underlying protocol's pools to claim rewards from\n false // Whether to trade the accrued rewards for MORPHO token, with a premium\n );\n\n // Transfer COMP to Harvester\n IERC20 rewardToken = IERC20(rewardTokenAddresses[0]);\n uint256 balance = rewardToken.balanceOf(address(this));\n emit RewardTokenCollected(\n harvesterAddress,\n rewardTokenAddresses[0],\n balance\n );\n rewardToken.safeTransfer(harvesterAddress, balance);\n }\n\n /**\n * @dev Get the amount of rewards pending to be collected from the protocol\n */\n function getPendingRewards() external view returns (uint256 balance) {\n address[] memory poolTokens = new address[](assetsMapped.length);\n for (uint256 i = 0; i < assetsMapped.length; i++) {\n poolTokens[i] = assetToPToken[assetsMapped[i]];\n }\n\n return ILens(LENS).getUserUnclaimedRewards(poolTokens, address(this));\n }\n\n /**\n * @dev Deposit asset into Morpho\n * @param _asset Address of asset to deposit\n * @param _amount Amount of asset to deposit\n */\n function deposit(address _asset, uint256 _amount)\n external\n override\n onlyVault\n nonReentrant\n {\n _deposit(_asset, _amount);\n }\n\n /**\n * @dev Deposit asset into Morpho\n * @param _asset Address of asset to deposit\n * @param _amount Amount of asset to deposit\n */\n function _deposit(address _asset, uint256 _amount) internal {\n require(_amount > 0, \"Must deposit something\");\n\n IMorpho(MORPHO).supply(\n address(_getCTokenFor(_asset)),\n address(this), // the address of the user you want to supply on behalf of\n _amount\n );\n emit Deposit(_asset, address(_getCTokenFor(_asset)), _amount);\n }\n\n /**\n * @dev Deposit the entire balance of any supported asset into Morpho\n */\n function depositAll() external override onlyVault nonReentrant {\n for (uint256 i = 0; i < assetsMapped.length; i++) {\n uint256 balance = IERC20(assetsMapped[i]).balanceOf(address(this));\n if (balance > 0) {\n _deposit(assetsMapped[i], balance);\n }\n }\n }\n\n /**\n * @dev Withdraw asset from Morpho\n * @param _recipient Address to receive withdrawn asset\n * @param _asset Address of asset to withdraw\n * @param _amount Amount of asset to withdraw\n */\n function withdraw(\n address _recipient,\n address _asset,\n uint256 _amount\n ) external override onlyVault nonReentrant {\n _withdraw(_recipient, _asset, _amount);\n }\n\n function _withdraw(\n address _recipient,\n address _asset,\n uint256 _amount\n ) internal {\n require(_amount > 0, \"Must withdraw something\");\n require(_recipient != address(0), \"Must specify recipient\");\n\n address pToken = assetToPToken[_asset];\n\n IMorpho(MORPHO).withdraw(pToken, _amount);\n emit Withdrawal(_asset, address(_getCTokenFor(_asset)), _amount);\n IERC20(_asset).safeTransfer(_recipient, _amount);\n }\n\n /**\n * @dev Remove all assets from platform and send them to Vault contract.\n */\n function withdrawAll() external override onlyVaultOrGovernor nonReentrant {\n for (uint256 i = 0; i < assetsMapped.length; i++) {\n uint256 balance = _checkBalance(assetsMapped[i]);\n if (balance > 0) {\n _withdraw(vaultAddress, assetsMapped[i], balance);\n }\n }\n }\n\n /**\n * @dev Return total value of an asset held in the platform\n * @param _asset Address of the asset\n * @return balance Total value of the asset in the platform\n */\n function checkBalance(address _asset)\n external\n view\n override\n returns (uint256 balance)\n {\n return _checkBalance(_asset);\n }\n\n function _checkBalance(address _asset)\n internal\n view\n returns (uint256 balance)\n {\n address pToken = assetToPToken[_asset];\n\n // Total value represented by decimal position of underlying token\n (, , balance) = ILens(LENS).getCurrentSupplyBalanceInOf(\n pToken,\n address(this)\n );\n }\n}\n" + }, + "contracts/strategies/NativeStaking/CompoundingStakingSSVStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\n\nimport { InitializableAbstractStrategy } from \"../../utils/InitializableAbstractStrategy.sol\";\nimport { IWETH9 } from \"../../interfaces/IWETH9.sol\";\nimport { CompoundingValidatorManager } from \"./CompoundingValidatorManager.sol\";\n\n/// @title Compounding Staking SSV Strategy\n/// @notice Strategy to deploy funds into DVT validators powered by the SSV Network\n/// @author Origin Protocol Inc\ncontract CompoundingStakingSSVStrategy is\n CompoundingValidatorManager,\n InitializableAbstractStrategy\n{\n /// @notice SSV ERC20 token that serves as a payment for operating SSV validators\n address public immutable SSV_TOKEN;\n\n // For future use\n uint256[50] private __gap;\n\n /// @param _baseConfig Base strategy config with\n /// `platformAddress` not used so empty address\n /// `vaultAddress` the address of the OETH Vault contract\n /// @param _wethAddress Address of the WETH Token contract\n /// @param _ssvToken Address of the SSV Token contract\n /// @param _ssvNetwork Address of the SSV Network contract\n /// @param _beaconChainDepositContract Address of the beacon chain deposit contract\n /// @param _beaconProofs Address of the Beacon Proofs contract that verifies beacon chain data\n /// @param _beaconGenesisTimestamp The timestamp of the Beacon chain's genesis.\n constructor(\n BaseStrategyConfig memory _baseConfig,\n address _wethAddress,\n address _ssvToken,\n address _ssvNetwork,\n address _beaconChainDepositContract,\n address _beaconProofs,\n uint64 _beaconGenesisTimestamp\n )\n InitializableAbstractStrategy(_baseConfig)\n CompoundingValidatorManager(\n _wethAddress,\n _baseConfig.vaultAddress,\n _beaconChainDepositContract,\n _ssvNetwork,\n _beaconProofs,\n _beaconGenesisTimestamp\n )\n {\n SSV_TOKEN = _ssvToken;\n\n // Make sure nobody owns the implementation contract\n _setGovernor(address(0));\n }\n\n /// @notice Set up initial internal state including\n /// 1. approving the SSVNetwork to transfer SSV tokens from this strategy contract\n /// @param _rewardTokenAddresses Not used so empty array\n /// @param _assets Not used so empty array\n /// @param _pTokens Not used so empty array\n function initialize(\n address[] memory _rewardTokenAddresses,\n address[] memory _assets,\n address[] memory _pTokens\n ) external onlyGovernor initializer {\n InitializableAbstractStrategy._initialize(\n _rewardTokenAddresses,\n _assets,\n _pTokens\n );\n\n safeApproveAllTokens();\n }\n\n /// @notice Unlike other strategies, this does not deposit assets into the underlying platform.\n /// It just checks the asset is WETH and emits the Deposit event.\n /// To deposit WETH into validators, `registerSsvValidator` and `stakeEth` must be used.\n /// @param _asset Address of the WETH token.\n /// @param _amount Amount of WETH that was transferred to the strategy by the vault.\n function deposit(address _asset, uint256 _amount)\n external\n override\n onlyVault\n nonReentrant\n {\n require(_asset == WETH, \"Unsupported asset\");\n require(_amount > 0, \"Must deposit something\");\n\n // Account for the new WETH\n depositedWethAccountedFor += _amount;\n\n emit Deposit(_asset, address(0), _amount);\n }\n\n /// @notice Unlike other strategies, this does not deposit assets into the underlying platform.\n /// It just emits the Deposit event.\n /// To deposit WETH into validators `registerSsvValidator` and `stakeEth` must be used.\n function depositAll() external override onlyVault nonReentrant {\n uint256 wethBalance = IERC20(WETH).balanceOf(address(this));\n uint256 newWeth = wethBalance - depositedWethAccountedFor;\n\n if (newWeth > 0) {\n // Account for the new WETH\n depositedWethAccountedFor = wethBalance;\n\n emit Deposit(WETH, address(0), newWeth);\n }\n }\n\n /// @notice Withdraw ETH and WETH from this strategy contract.\n /// @param _recipient Address to receive withdrawn assets.\n /// @param _asset Address of the WETH token.\n /// @param _amount Amount of WETH to withdraw.\n function withdraw(\n address _recipient,\n address _asset,\n uint256 _amount\n ) external override nonReentrant {\n require(_asset == WETH, \"Unsupported asset\");\n require(\n msg.sender == vaultAddress || msg.sender == validatorRegistrator,\n \"Caller not Vault or Registrator\"\n );\n\n _withdraw(_recipient, _amount, address(this).balance);\n }\n\n function _withdraw(\n address _recipient,\n uint256 _withdrawAmount,\n uint256 _ethBalance\n ) internal {\n require(_withdrawAmount > 0, \"Must withdraw something\");\n require(_recipient == vaultAddress, \"Recipient not Vault\");\n\n // Convert any ETH from validator partial withdrawals, exits\n // or execution rewards to WETH and do the necessary accounting.\n if (_ethBalance > 0) _convertEthToWeth(_ethBalance);\n\n // Transfer WETH to the recipient and do the necessary accounting.\n _transferWeth(_withdrawAmount, _recipient);\n\n emit Withdrawal(WETH, address(0), _withdrawAmount);\n }\n\n /// @notice Transfer all WETH deposits, ETH from validator withdrawals and ETH from\n /// execution rewards in this strategy to the vault.\n /// This does not withdraw from the validators. That has to be done separately with the\n /// `validatorWithdrawal` operation.\n function withdrawAll() external override onlyVaultOrGovernor nonReentrant {\n uint256 ethBalance = address(this).balance;\n uint256 withdrawAmount = IERC20(WETH).balanceOf(address(this)) +\n ethBalance;\n\n if (withdrawAmount > 0) {\n _withdraw(vaultAddress, withdrawAmount, ethBalance);\n }\n }\n\n /// @notice Accounts for all the assets managed by this strategy which includes:\n /// 1. The current WETH in this strategy contract\n /// 2. The last verified ETH balance, total deposits and total validator balances\n /// @param _asset Address of WETH asset.\n /// @return balance Total value in ETH\n function checkBalance(address _asset)\n external\n view\n override\n returns (uint256 balance)\n {\n require(_asset == WETH, \"Unsupported asset\");\n\n // Load the last verified balance from the storage\n // and add to the latest WETH balance of this strategy.\n balance =\n lastVerifiedEthBalance +\n IWETH9(WETH).balanceOf(address(this));\n }\n\n /// @notice Returns bool indicating whether asset is supported by the strategy.\n /// @param _asset The address of the WETH token.\n function supportsAsset(address _asset) public view override returns (bool) {\n return _asset == WETH;\n }\n\n /// @notice Approves the SSV Network contract to transfer SSV tokens for validator registration.\n function safeApproveAllTokens() public override {\n // Approves the SSV Network contract to transfer SSV tokens when validators are registered\n IERC20(SSV_TOKEN).approve(SSV_NETWORK, type(uint256).max);\n }\n\n /**\n * @notice We can accept ETH directly to this contract from anyone as it does not impact our accounting\n * like it did in the legacy NativeStakingStrategy.\n * The new ETH will be accounted for in `checkBalance` after the next snapBalances and verifyBalances txs.\n */\n receive() external payable {}\n\n /***************************************\n Internal functions\n ****************************************/\n\n /// @notice is not supported for this strategy as there is no platform token.\n function setPTokenAddress(address, address) external pure override {\n revert(\"Unsupported function\");\n }\n\n /// @notice is not supported for this strategy as there is no platform token.\n function removePToken(uint256) external pure override {\n revert(\"Unsupported function\");\n }\n\n /// @dev This strategy does not use a platform token like the old Aave and Compound strategies.\n function _abstractSetPToken(address _asset, address) internal override {}\n\n /// @dev Consensus rewards are compounded to the validator's balance instead of being\n /// swept to this strategy contract.\n /// Execution rewards from MEV and tx priority accumulate as ETH in this strategy contract.\n /// Withdrawals from validators also accumulate as ETH in this strategy contract.\n /// It's too complex to separate the rewards from withdrawals so this function is not implemented.\n /// Besides, ETH rewards are not sent to the Dripper any more. The Vault can now regulate\n /// the increase in assets.\n function _collectRewardTokens() internal pure override {\n revert(\"Unsupported function\");\n }\n}\n" + }, + "contracts/strategies/NativeStaking/CompoundingValidatorManager.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport { SafeCast } from \"@openzeppelin/contracts/utils/math/SafeCast.sol\";\nimport { Math } from \"@openzeppelin/contracts/utils/math/Math.sol\";\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport { Pausable } from \"@openzeppelin/contracts/security/Pausable.sol\";\nimport { Governable } from \"../../governance/Governable.sol\";\nimport { IDepositContract } from \"../../interfaces/IDepositContract.sol\";\nimport { IWETH9 } from \"../../interfaces/IWETH9.sol\";\nimport { ISSVNetwork, Cluster } from \"../../interfaces/ISSVNetwork.sol\";\nimport { BeaconRoots } from \"../../beacon/BeaconRoots.sol\";\nimport { PartialWithdrawal } from \"../../beacon/PartialWithdrawal.sol\";\nimport { IBeaconProofs } from \"../../interfaces/IBeaconProofs.sol\";\n\n/**\n * @title Validator lifecycle management contract\n * @notice This contract implements all the required functionality to\n * register, deposit, withdraw, exit and remove validators.\n * @author Origin Protocol Inc\n */\nabstract contract CompoundingValidatorManager is Governable, Pausable {\n using SafeERC20 for IERC20;\n\n /// @dev The amount of ETH in wei that is required for a deposit to a new validator.\n uint256 internal constant DEPOSIT_AMOUNT_WEI = 1 ether;\n /// @dev Validator balances over this amount will eventually become active on the beacon chain.\n /// Due to hysteresis, if the effective balance is 31 ETH, the actual balance\n /// must rise to 32.25 ETH to trigger an effective balance update to 32 ETH.\n /// https://eth2book.info/capella/part2/incentives/balances/#hysteresis\n uint256 internal constant MIN_ACTIVATION_BALANCE_GWEI = 32.25 ether / 1e9;\n /// @dev The maximum number of deposits that are waiting to be verified as processed on the beacon chain.\n uint256 internal constant MAX_DEPOSITS = 32;\n /// @dev The maximum number of validators that can be verified.\n uint256 internal constant MAX_VERIFIED_VALIDATORS = 48;\n /// @dev The default withdrawable epoch value on the Beacon chain.\n /// A value in the far future means the validator is not exiting.\n uint64 internal constant FAR_FUTURE_EPOCH = type(uint64).max;\n /// @dev The number of seconds between each beacon chain slot.\n uint64 internal constant SLOT_DURATION = 12;\n /// @dev The number of slots in each beacon chain epoch.\n uint64 internal constant SLOTS_PER_EPOCH = 32;\n /// @dev Minimum time in seconds to allow snapped balances to be verified.\n /// Set to 35 slots which is 3 slots more than 1 epoch (32 slots). Deposits get processed\n /// once per epoch. This larger than 1 epoch delay should achieve that `snapBalances` sometimes\n /// get called in the middle (or towards the end) of the epoch. Giving the off-chain script\n /// sufficient time after the end of the epoch to prepare the proofs and call `verifyBalances`.\n /// This is considering a malicious actor would keep calling `snapBalances` as frequent as possible\n /// to disturb our operations.\n uint64 internal constant SNAP_BALANCES_DELAY = 35 * SLOT_DURATION;\n\n /// @notice The address of the Wrapped ETH (WETH) token contract\n address internal immutable WETH;\n /// @notice The address of the beacon chain deposit contract\n address internal immutable BEACON_CHAIN_DEPOSIT_CONTRACT;\n /// @notice The address of the SSV Network contract used to interface with\n address internal immutable SSV_NETWORK;\n /// @notice Address of the OETH Vault proxy contract\n address internal immutable VAULT_ADDRESS;\n /// @notice Address of the Beacon Proofs contract that verifies beacon chain data\n address public immutable BEACON_PROOFS;\n /// @notice The timestamp of the Beacon chain genesis.\n /// @dev this is different on Testnets like Hoodi so is set at deployment time.\n uint64 internal immutable BEACON_GENESIS_TIMESTAMP;\n\n /// @notice Address of the registrator - allowed to register, withdraw, exit and remove validators\n address public validatorRegistrator;\n\n /// @notice Deposit data for new compounding validators.\n /// @dev A `VERIFIED` deposit can mean 3 separate things:\n /// - a deposit has been processed by the beacon chain and shall be included in the\n /// balance of the next verifyBalances call\n /// - a deposit has been done to a slashed validator and has probably been recovered\n /// back to this strategy. Probably because we can not know for certain. This contract\n /// only detects when the validator has passed its withdrawal epoch. It is close to impossible\n /// to prove with Merkle Proofs that the postponed deposit this contract is responsible for\n /// creating is not present anymore in BeaconChain.state.pending_deposits. This in effect\n /// means that there might be a period where this contract thinks the deposit has been already\n /// returned as ETH balance before it happens. This will result in some days (or weeks)\n /// -> depending on the size of deposit queue of showing a deficit when calling `checkBalance`.\n /// As this only offsets the yield and doesn't cause a critical double-counting we are not addressing\n /// this issue.\n /// - A deposit has been done to the validator, but our deposit has been front run by a malicious\n /// actor. Funds in the deposit this contract makes are not recoverable.\n enum DepositStatus {\n UNKNOWN, // default value\n PENDING, // deposit is pending and waiting to be verified\n VERIFIED // deposit has been verified\n }\n\n /// @param pubKeyHash Hash of validator's public key using the Beacon Chain's format\n /// @param amountGwei Amount of ETH in gwei that has been deposited to the beacon chain deposit contract\n /// @param slot The beacon chain slot number when the deposit has been made\n /// @param depositIndex The index of the deposit in the list of active deposits\n /// @param status The status of the deposit, either UNKNOWN, PENDING or VERIFIED\n struct DepositData {\n bytes32 pubKeyHash;\n uint64 amountGwei;\n uint64 slot;\n uint32 depositIndex;\n DepositStatus status;\n }\n /// @notice Restricts to only one deposit to an unverified validator at a time.\n /// This is to limit front-running attacks of deposits to the beacon chain contract.\n ///\n /// @dev The value is set to true when a deposit to a new validator has been done that has\n /// not yet be verified.\n bool public firstDeposit;\n /// @notice Mapping of the pending deposit roots to the deposit data\n mapping(bytes32 => DepositData) public deposits;\n /// @notice List of strategy deposit IDs to a validator.\n /// The ID is the merkle root of the pending deposit data which is unique for each validator, amount and block.\n /// Duplicate pending deposit roots are prevented so can be used as an identifier to each strategy deposit.\n /// The list can be for deposits waiting to be verified as processed on the beacon chain,\n /// or deposits that have been verified to an exiting validator and is now waiting for the\n /// validator's balance to be swept.\n /// The list may not be ordered by time of deposit.\n /// Removed deposits will move the last deposit to the removed index.\n bytes32[] public depositList;\n\n enum ValidatorState {\n NON_REGISTERED, // validator is not registered on the SSV network\n REGISTERED, // validator is registered on the SSV network\n STAKED, // validator has funds staked\n VERIFIED, // validator has been verified to exist on the beacon chain\n ACTIVE, // The validator balance is at least 32 ETH. The validator may not yet be active on the beacon chain.\n EXITING, // The validator has been requested to exit\n EXITED, // The validator has been verified to have a zero balance\n REMOVED, // validator has funds withdrawn to this strategy contract and is removed from the SSV\n INVALID // The validator has been front-run and the withdrawal address is not this strategy\n }\n\n // Validator data\n struct ValidatorData {\n ValidatorState state; // The state of the validator known to this contract\n uint40 index; // The index of the validator on the beacon chain\n }\n /// @notice List of validator public key hashes that have been verified to exist on the beacon chain.\n /// These have had a deposit processed and the validator's balance increased.\n /// Validators will be removed from this list when its verified they have a zero balance.\n bytes32[] public verifiedValidators;\n /// @notice Mapping of the hash of the validator's public key to the validator state and index.\n /// Uses the Beacon chain hashing for BLSPubkey which is sha256(abi.encodePacked(validator.pubkey, bytes16(0)))\n mapping(bytes32 => ValidatorData) public validator;\n\n /// @param blockRoot Beacon chain block root of the snapshot\n /// @param timestamp Timestamp of the snapshot\n /// @param ethBalance The balance of ETH in the strategy contract at the snapshot\n struct Balances {\n bytes32 blockRoot;\n uint64 timestamp;\n uint128 ethBalance;\n }\n /// @notice Mapping of the block root to the balances at that slot\n Balances public snappedBalance;\n /// @notice The last verified ETH balance of the strategy\n uint256 public lastVerifiedEthBalance;\n\n /// @dev This contract receives WETH as the deposit asset, but unlike other strategies doesn't immediately\n /// deposit it to an underlying platform. Rather a special privilege account stakes it to the validators.\n /// For that reason calling WETH.balanceOf(this) in a deposit function can contain WETH that has just been\n /// deposited and also WETH that has previously been deposited. To keep a correct count we need to keep track\n /// of WETH that has already been accounted for.\n /// This value represents the amount of WETH balance of this contract that has already been accounted for by the\n /// deposit events.\n /// It is important to note that this variable is not concerned with WETH that is a result of full/partial\n /// withdrawal of the validators. It is strictly concerned with WETH that has been deposited and is waiting to\n /// be staked.\n uint256 public depositedWethAccountedFor;\n\n // For future use\n uint256[41] private __gap;\n\n event RegistratorChanged(address indexed newAddress);\n event FirstDepositReset();\n event SSVValidatorRegistered(\n bytes32 indexed pubKeyHash,\n uint64[] operatorIds\n );\n event SSVValidatorRemoved(bytes32 indexed pubKeyHash, uint64[] operatorIds);\n event ETHStaked(\n bytes32 indexed pubKeyHash,\n bytes32 indexed pendingDepositRoot,\n bytes pubKey,\n uint256 amountWei\n );\n event ValidatorVerified(\n bytes32 indexed pubKeyHash,\n uint40 indexed validatorIndex\n );\n event ValidatorInvalid(bytes32 indexed pubKeyHash);\n event DepositVerified(\n bytes32 indexed pendingDepositRoot,\n uint256 amountWei\n );\n event ValidatorWithdraw(bytes32 indexed pubKeyHash, uint256 amountWei);\n event BalancesSnapped(bytes32 indexed blockRoot, uint256 ethBalance);\n event BalancesVerified(\n uint64 indexed timestamp,\n uint256 totalDepositsWei,\n uint256 totalValidatorBalance,\n uint256 ethBalance\n );\n\n /// @dev Throws if called by any account other than the Registrator\n modifier onlyRegistrator() {\n require(msg.sender == validatorRegistrator, \"Not Registrator\");\n _;\n }\n\n /// @dev Throws if called by any account other than the Registrator or Governor\n modifier onlyRegistratorOrGovernor() {\n require(\n msg.sender == validatorRegistrator || isGovernor(),\n \"Not Registrator or Governor\"\n );\n _;\n }\n\n /// @param _wethAddress Address of the Erc20 WETH Token contract\n /// @param _vaultAddress Address of the Vault\n /// @param _beaconChainDepositContract Address of the beacon chain deposit contract\n /// @param _ssvNetwork Address of the SSV Network contract\n /// @param _beaconProofs Address of the Beacon Proofs contract that verifies beacon chain data\n /// @param _beaconGenesisTimestamp The timestamp of the Beacon chain's genesis.\n constructor(\n address _wethAddress,\n address _vaultAddress,\n address _beaconChainDepositContract,\n address _ssvNetwork,\n address _beaconProofs,\n uint64 _beaconGenesisTimestamp\n ) {\n WETH = _wethAddress;\n BEACON_CHAIN_DEPOSIT_CONTRACT = _beaconChainDepositContract;\n SSV_NETWORK = _ssvNetwork;\n VAULT_ADDRESS = _vaultAddress;\n BEACON_PROOFS = _beaconProofs;\n BEACON_GENESIS_TIMESTAMP = _beaconGenesisTimestamp;\n\n require(\n block.timestamp > _beaconGenesisTimestamp,\n \"Invalid genesis timestamp\"\n );\n }\n\n /**\n *\n * Admin Functions\n *\n */\n\n /// @notice Set the address of the registrator which can register, exit and remove validators\n function setRegistrator(address _address) external onlyGovernor {\n validatorRegistrator = _address;\n emit RegistratorChanged(_address);\n }\n\n /// @notice Reset the `firstDeposit` flag to false so deposits to unverified validators can be made again.\n function resetFirstDeposit() external onlyGovernor {\n require(firstDeposit, \"No first deposit\");\n\n firstDeposit = false;\n\n emit FirstDepositReset();\n }\n\n function pause() external onlyRegistratorOrGovernor {\n _pause();\n }\n\n function unPause() external onlyGovernor {\n _unpause();\n }\n\n /**\n *\n * Validator Management\n *\n */\n\n /// @notice Registers a single validator in a SSV Cluster.\n /// Only the Registrator can call this function.\n /// @param publicKey The public key of the validator\n /// @param operatorIds The operator IDs of the SSV Cluster\n /// @param sharesData The shares data for the validator\n /// @param ssvAmount The amount of SSV tokens to be deposited to the SSV cluster\n /// @param cluster The SSV cluster details including the validator count and SSV balance\n // slither-disable-start reentrancy-no-eth\n function registerSsvValidator(\n bytes calldata publicKey,\n uint64[] calldata operatorIds,\n bytes calldata sharesData,\n uint256 ssvAmount,\n Cluster calldata cluster\n ) external onlyRegistrator whenNotPaused {\n // Hash the public key using the Beacon Chain's format\n bytes32 pubKeyHash = _hashPubKey(publicKey);\n // Check each public key has not already been used\n require(\n validator[pubKeyHash].state == ValidatorState.NON_REGISTERED,\n \"Validator already registered\"\n );\n\n // Store the validator state as registered\n validator[pubKeyHash].state = ValidatorState.REGISTERED;\n\n ISSVNetwork(SSV_NETWORK).registerValidator(\n publicKey,\n operatorIds,\n sharesData,\n ssvAmount,\n cluster\n );\n\n emit SSVValidatorRegistered(pubKeyHash, operatorIds);\n }\n\n // slither-disable-end reentrancy-no-eth\n\n struct ValidatorStakeData {\n bytes pubkey;\n bytes signature;\n bytes32 depositDataRoot;\n }\n\n /// @notice Stakes WETH in this strategy to a compounding validator.\n /// The first deposit to a new validator, the amount must be 1 ETH.\n /// Another deposit of at least 31 ETH is required for the validator to be activated.\n /// This second deposit has to be done after the validator has been verified.\n /// Does not convert any ETH sitting in this strategy to WETH.\n /// There can not be two deposits to the same validator in the same block for the same amount.\n /// Function is pausable so in case a run-away Registrator can be prevented from continuing\n /// to deposit funds to slashed or undesired validators.\n /// @param validatorStakeData validator data needed to stake.\n /// The `ValidatorStakeData` struct contains the pubkey, signature and depositDataRoot.\n /// Only the registrator can call this function.\n /// @param depositAmountGwei The amount of WETH to stake to the validator in Gwei.\n // slither-disable-start reentrancy-eth,reentrancy-no-eth\n function stakeEth(\n ValidatorStakeData calldata validatorStakeData,\n uint64 depositAmountGwei\n ) external onlyRegistrator whenNotPaused {\n uint256 depositAmountWei = uint256(depositAmountGwei) * 1 gwei;\n // Check there is enough WETH from the deposits sitting in this strategy contract\n // There could be ETH from withdrawals but we'll ignore that. If it's really needed\n // the ETH can be withdrawn and then deposited back to the strategy.\n require(\n depositAmountWei <= IWETH9(WETH).balanceOf(address(this)),\n \"Insufficient WETH\"\n );\n require(depositList.length < MAX_DEPOSITS, \"Max deposits\");\n\n // Convert required ETH from WETH and do the necessary accounting\n _convertWethToEth(depositAmountWei);\n\n // Hash the public key using the Beacon Chain's hashing for BLSPubkey\n bytes32 pubKeyHash = _hashPubKey(validatorStakeData.pubkey);\n ValidatorState currentState = validator[pubKeyHash].state;\n // Can only stake to a validator that has been registered, verified or active.\n // Can not stake to a validator that has been staked but not yet verified.\n require(\n (currentState == ValidatorState.REGISTERED ||\n currentState == ValidatorState.VERIFIED ||\n currentState == ValidatorState.ACTIVE),\n \"Not registered or verified\"\n );\n require(depositAmountWei >= 1 ether, \"Deposit too small\");\n if (currentState == ValidatorState.REGISTERED) {\n // Can only have one pending deposit to an unverified validator at a time.\n // This is to limit front-running deposit attacks to a single deposit.\n // The exiting deposit needs to be verified before another deposit can be made.\n // If there was a front-running attack, the validator needs to be verified as invalid\n // and the Governor calls `resetFirstDeposit` to set `firstDeposit` to false.\n require(!firstDeposit, \"Existing first deposit\");\n // Limits the amount of ETH that can be at risk from a front-running deposit attack.\n require(\n depositAmountWei == DEPOSIT_AMOUNT_WEI,\n \"Invalid first deposit amount\"\n );\n // Limits the number of validator balance proofs to verifyBalances\n require(\n verifiedValidators.length + 1 <= MAX_VERIFIED_VALIDATORS,\n \"Max validators\"\n );\n\n // Flag a deposit to an unverified validator so no other deposits can be made\n // to an unverified validator.\n firstDeposit = true;\n validator[pubKeyHash].state = ValidatorState.STAKED;\n }\n\n /* 0x02 to indicate that withdrawal credentials are for a compounding validator\n * that was introduced with the Pectra upgrade.\n * bytes11(0) to fill up the required zeros\n * remaining bytes20 are for the address\n */\n bytes memory withdrawalCredentials = abi.encodePacked(\n bytes1(0x02),\n bytes11(0),\n address(this)\n );\n\n /// After the Pectra upgrade the validators have a new restriction when proposing\n /// blocks. The timestamps are at strict intervals of 12 seconds from the genesis block\n /// forward. Each slot is created at strict 12 second intervals and those slots can\n /// either have blocks attached to them or not. This way using the block.timestamp\n /// the slot number can easily be calculated.\n uint64 depositSlot = (SafeCast.toUint64(block.timestamp) -\n BEACON_GENESIS_TIMESTAMP) / SLOT_DURATION;\n\n // Calculate the merkle root of the beacon chain pending deposit data.\n // This is used as the unique ID of the deposit.\n bytes32 pendingDepositRoot = IBeaconProofs(BEACON_PROOFS)\n .merkleizePendingDeposit(\n pubKeyHash,\n withdrawalCredentials,\n depositAmountGwei,\n validatorStakeData.signature,\n depositSlot\n );\n require(\n deposits[pendingDepositRoot].status == DepositStatus.UNKNOWN,\n \"Duplicate deposit\"\n );\n\n // Store the deposit data for verifyDeposit and verifyBalances\n deposits[pendingDepositRoot] = DepositData({\n pubKeyHash: pubKeyHash,\n amountGwei: depositAmountGwei,\n slot: depositSlot,\n depositIndex: SafeCast.toUint32(depositList.length),\n status: DepositStatus.PENDING\n });\n depositList.push(pendingDepositRoot);\n\n // Deposit to the Beacon Chain deposit contract.\n // This will create a deposit in the beacon chain's pending deposit queue.\n IDepositContract(BEACON_CHAIN_DEPOSIT_CONTRACT).deposit{\n value: depositAmountWei\n }(\n validatorStakeData.pubkey,\n withdrawalCredentials,\n validatorStakeData.signature,\n validatorStakeData.depositDataRoot\n );\n\n emit ETHStaked(\n pubKeyHash,\n pendingDepositRoot,\n validatorStakeData.pubkey,\n depositAmountWei\n );\n }\n\n // slither-disable-end reentrancy-eth,reentrancy-no-eth\n\n /// @notice Request a full or partial withdrawal from a validator.\n /// A zero amount will trigger a full withdrawal.\n /// If the remaining balance is < 32 ETH then only the amount in excess of 32 ETH will be withdrawn.\n /// Only the Registrator can call this function.\n /// 1 wei of value should be sent with the tx to pay for the withdrawal request fee.\n /// If no value sent, 1 wei will be taken from the strategy's ETH balance if it has any.\n /// If no ETH balance, the tx will revert.\n /// @param publicKey The public key of the validator\n /// @param amountGwei The amount of ETH to be withdrawn from the validator in Gwei.\n /// A zero amount will trigger a full withdrawal.\n // slither-disable-start reentrancy-no-eth\n function validatorWithdrawal(bytes calldata publicKey, uint64 amountGwei)\n external\n payable\n onlyRegistrator\n {\n // Hash the public key using the Beacon Chain's format\n bytes32 pubKeyHash = _hashPubKey(publicKey);\n ValidatorData memory validatorDataMem = validator[pubKeyHash];\n // Validator full withdrawal could be denied due to multiple reasons:\n // - the validator has not been activated or active long enough\n // (current_epoch < activation_epoch + SHARD_COMMITTEE_PERIOD)\n // - the validator has pending balance to withdraw from a previous partial withdrawal request\n //\n // Meaning that the on-chain to beacon chain full withdrawal request could fail. Instead\n // of adding complexity of verifying if a validator is eligible for a full exit, we allow\n // multiple full withdrawal requests per validator.\n require(\n validatorDataMem.state == ValidatorState.ACTIVE ||\n validatorDataMem.state == ValidatorState.EXITING,\n \"Validator not active/exiting\"\n );\n\n // If a full withdrawal (validator exit)\n if (amountGwei == 0) {\n // For each staking strategy's deposits\n uint256 depositsCount = depositList.length;\n for (uint256 i = 0; i < depositsCount; ++i) {\n bytes32 pendingDepositRoot = depositList[i];\n // Check there is no pending deposits to the exiting validator\n require(\n pubKeyHash != deposits[pendingDepositRoot].pubKeyHash,\n \"Pending deposit\"\n );\n }\n\n // Store the validator state as exiting so no more deposits can be made to it.\n // This may already be EXITING if the previous exit request failed. eg the validator\n // was not active long enough.\n validator[pubKeyHash].state = ValidatorState.EXITING;\n }\n\n // Do not remove from the list of verified validators.\n // This is done in the verifyBalances function once the validator's balance has been verified to be zero.\n // The validator state will be set to EXITED in the verifyBalances function.\n\n PartialWithdrawal.request(publicKey, amountGwei);\n\n emit ValidatorWithdraw(pubKeyHash, uint256(amountGwei) * 1 gwei);\n }\n\n // slither-disable-end reentrancy-no-eth\n\n /// @notice Remove the validator from the SSV Cluster after:\n /// - the validator has been exited from `validatorWithdrawal` or slashed\n /// - the validator has incorrectly registered and can not be staked to\n /// - the initial deposit was front-run and the withdrawal address is not this strategy's address.\n /// Make sure `validatorWithdrawal` is called with a zero amount and the validator has exited the Beacon chain.\n /// If removed before the validator has exited the beacon chain will result in the validator being slashed.\n /// Only the registrator can call this function.\n /// @param publicKey The public key of the validator\n /// @param operatorIds The operator IDs of the SSV Cluster\n /// @param cluster The SSV cluster details including the validator count and SSV balance\n // slither-disable-start reentrancy-no-eth\n function removeSsvValidator(\n bytes calldata publicKey,\n uint64[] calldata operatorIds,\n Cluster calldata cluster\n ) external onlyRegistrator {\n // Hash the public key using the Beacon Chain's format\n bytes32 pubKeyHash = _hashPubKey(publicKey);\n ValidatorState currentState = validator[pubKeyHash].state;\n // Can remove SSV validators that were incorrectly registered and can not be deposited to.\n require(\n currentState == ValidatorState.REGISTERED ||\n currentState == ValidatorState.EXITED ||\n currentState == ValidatorState.INVALID,\n \"Validator not regd or exited\"\n );\n\n validator[pubKeyHash].state = ValidatorState.REMOVED;\n\n ISSVNetwork(SSV_NETWORK).removeValidator(\n publicKey,\n operatorIds,\n cluster\n );\n\n emit SSVValidatorRemoved(pubKeyHash, operatorIds);\n }\n\n /**\n *\n * SSV Management\n *\n */\n\n // slither-disable-end reentrancy-no-eth\n\n /// `depositSSV` has been removed as `deposit` on the SSVNetwork contract can be called directly\n /// by the Strategist which is already holding SSV tokens.\n\n /// @notice Withdraws excess SSV Tokens from the SSV Network contract which was used to pay the SSV Operators.\n /// @dev A SSV cluster is defined by the SSVOwnerAddress and the set of operatorIds.\n /// @param operatorIds The operator IDs of the SSV Cluster\n /// @param ssvAmount The amount of SSV tokens to be withdrawn from the SSV cluster\n /// @param cluster The SSV cluster details including the validator count and SSV balance\n function withdrawSSV(\n uint64[] memory operatorIds,\n uint256 ssvAmount,\n Cluster memory cluster\n ) external onlyGovernor {\n ISSVNetwork(SSV_NETWORK).withdraw(operatorIds, ssvAmount, cluster);\n }\n\n /**\n *\n * Beacon Chain Proofs\n *\n */\n\n /// @notice Verifies a validator's index to its public key.\n /// Adds to the list of verified validators if the validator's withdrawal address is this strategy's address.\n /// Marks the validator as invalid and removes the deposit if the withdrawal address is not this strategy's address.\n /// @param nextBlockTimestamp The timestamp of the execution layer block after the beacon chain slot\n /// we are verifying.\n /// The next one is needed as the Beacon Oracle returns the parent beacon block root for a block timestamp,\n /// which is the beacon block root of the previous block.\n /// @param validatorIndex The index of the validator on the beacon chain.\n /// @param pubKeyHash The hash of the validator's public key using the Beacon Chain's format\n /// @param withdrawalCredentials contain the validator type and withdrawal address. These can be incorrect and/or\n /// malformed. In case of incorrect withdrawalCredentials the validator deposit has been front run\n /// @param validatorPubKeyProof The merkle proof for the validator public key to the beacon block root.\n /// This is 53 witness hashes of 32 bytes each concatenated together starting from the leaf node.\n /// BeaconBlock.state.validators[validatorIndex].pubkey\n function verifyValidator(\n uint64 nextBlockTimestamp,\n uint40 validatorIndex,\n bytes32 pubKeyHash,\n bytes32 withdrawalCredentials,\n bytes calldata validatorPubKeyProof\n ) external {\n require(\n validator[pubKeyHash].state == ValidatorState.STAKED,\n \"Validator not staked\"\n );\n\n // Get the beacon block root of the slot we are verifying the validator in.\n // The parent beacon block root of the next block is the beacon block root of the slot we are verifying.\n bytes32 blockRoot = BeaconRoots.parentBlockRoot(nextBlockTimestamp);\n\n // Verify the validator index is for the validator with the given public key.\n // Also verify the validator's withdrawal credentials\n IBeaconProofs(BEACON_PROOFS).verifyValidator(\n blockRoot,\n pubKeyHash,\n validatorPubKeyProof,\n validatorIndex,\n withdrawalCredentials\n );\n\n // Store the validator state as verified\n validator[pubKeyHash] = ValidatorData({\n state: ValidatorState.VERIFIED,\n index: validatorIndex\n });\n\n bytes32 expectedWithdrawalCredentials = bytes32(\n abi.encodePacked(bytes1(0x02), bytes11(0), address(this))\n );\n\n // If the initial deposit was front-run and the withdrawal address is not this strategy\n // or the validator type is not a compounding validator (0x02)\n if (expectedWithdrawalCredentials != withdrawalCredentials) {\n // override the validator state\n validator[pubKeyHash].state = ValidatorState.INVALID;\n\n // Find and remove the deposit as the funds can not be recovered\n uint256 depositCount = depositList.length;\n for (uint256 i = 0; i < depositCount; i++) {\n DepositData memory deposit = deposits[depositList[i]];\n if (deposit.pubKeyHash == pubKeyHash) {\n // next verifyBalances will correctly account for the loss of a front-run\n // deposit. Doing it here accounts for the loss as soon as possible\n lastVerifiedEthBalance -= Math.min(\n lastVerifiedEthBalance,\n uint256(deposit.amountGwei) * 1 gwei\n );\n _removeDeposit(depositList[i], deposit);\n break;\n }\n }\n\n // Leave the `firstDeposit` flag as true so no more deposits to unverified validators can be made.\n // The Governor has to reset the `firstDeposit` to false before another deposit to\n // an unverified validator can be made.\n // The Governor can set a new `validatorRegistrator` if they suspect it has been compromised.\n\n emit ValidatorInvalid(pubKeyHash);\n return;\n }\n\n // Add the new validator to the list of verified validators\n verifiedValidators.push(pubKeyHash);\n\n // Reset the firstDeposit flag as the first deposit to an unverified validator has been verified.\n firstDeposit = false;\n\n emit ValidatorVerified(pubKeyHash, validatorIndex);\n }\n\n struct FirstPendingDepositSlotProofData {\n uint64 slot;\n bytes proof;\n }\n\n struct StrategyValidatorProofData {\n uint64 withdrawableEpoch;\n bytes withdrawableEpochProof;\n }\n\n /// @notice Verifies a deposit on the execution layer has been processed by the beacon chain.\n /// This means the accounting of the strategy's ETH moves from a pending deposit to a validator balance.\n ///\n /// Important: this function has a limitation where `depositProcessedSlot` that is passed by the off-chain\n /// verifier requires a slot immediately after it to propose a block otherwise the `BeaconRoots.parentBlockRoot`\n /// will fail. This shouldn't be a problem, since by the current behaviour of beacon chain only 1%-3% slots\n /// don't propose a block.\n /// @param pendingDepositRoot The unique identifier of the deposit emitted in `ETHStaked` from\n /// the `stakeEth` function.\n /// @param depositProcessedSlot Any slot on or after the strategy's deposit was processed on the beacon chain.\n /// Can not be a slot with pending deposits with the same slot as the deposit being verified.\n /// Can not be a slot before a missed slot as the Beacon Root contract will have the parent block root\n /// set for the next block timestamp in 12 seconds time.\n /// @param firstPendingDeposit a `FirstPendingDepositSlotProofData` struct containing:\n /// - slot: The beacon chain slot of the first deposit in the beacon chain's deposit queue.\n /// Can be any non-zero value if the deposit queue is empty.\n /// - proof: The merkle proof of the first pending deposit's slot to the beacon block root.\n /// Can be either:\n /// * 40 witness hashes for BeaconBlock.state.PendingDeposits[0].slot when the deposit queue is not empty.\n /// * 37 witness hashes for BeaconBlock.state.PendingDeposits[0] when the deposit queue is empty.\n /// The 32 byte witness hashes are concatenated together starting from the leaf node.\n /// @param strategyValidatorData a `StrategyValidatorProofData` struct containing:\n /// - withdrawableEpoch: The withdrawable epoch of the validator the strategy is depositing to.\n /// - withdrawableEpochProof: The merkle proof for the withdrawable epoch of the validator the strategy\n /// is depositing to, to the beacon block root.\n /// This is 53 witness hashes of 32 bytes each concatenated together starting from the leaf node.\n // slither-disable-start reentrancy-no-eth\n function verifyDeposit(\n bytes32 pendingDepositRoot,\n uint64 depositProcessedSlot,\n FirstPendingDepositSlotProofData calldata firstPendingDeposit,\n StrategyValidatorProofData calldata strategyValidatorData\n ) external {\n // Load into memory the previously saved deposit data\n DepositData memory deposit = deposits[pendingDepositRoot];\n ValidatorData memory strategyValidator = validator[deposit.pubKeyHash];\n require(deposit.status == DepositStatus.PENDING, \"Deposit not pending\");\n require(firstPendingDeposit.slot != 0, \"Zero 1st pending deposit slot\");\n\n // We should allow the verification of deposits for validators that have been marked as exiting\n // to cover this situation:\n // - there are 2 pending deposits\n // - beacon chain has slashed the validator\n // - when verifyDeposit is called for the first deposit it sets the Validator state to EXITING\n // - verifyDeposit should allow a secondary call for the other deposit to a slashed validator\n require(\n strategyValidator.state == ValidatorState.VERIFIED ||\n strategyValidator.state == ValidatorState.ACTIVE ||\n strategyValidator.state == ValidatorState.EXITING,\n \"Not verified/active/exiting\"\n );\n // The verification slot must be after the deposit's slot.\n // This is needed for when the deposit queue is empty.\n require(deposit.slot < depositProcessedSlot, \"Slot not after deposit\");\n\n uint64 snapTimestamp = snappedBalance.timestamp;\n\n // This check prevents an accounting error that can happen if:\n // - snapBalances are snapped at the time of T\n // - deposit is processed on the beacon chain after time T and before verifyBalances()\n // - verifyDeposit is called before verifyBalances which removes a deposit from depositList\n // and deposit balance from totalDepositsWei\n // - verifyBalances is called under-reporting the strategy's balance\n require(\n (_calcNextBlockTimestamp(depositProcessedSlot) <= snapTimestamp) ||\n snapTimestamp == 0,\n \"Deposit after balance snapshot\"\n );\n\n // Get the parent beacon block root of the next block which is the block root of the deposit verification slot.\n // This will revert if the slot after the verification slot was missed.\n bytes32 depositBlockRoot = BeaconRoots.parentBlockRoot(\n _calcNextBlockTimestamp(depositProcessedSlot)\n );\n\n // Verify the slot of the first pending deposit matches the beacon chain\n bool isDepositQueueEmpty = IBeaconProofs(BEACON_PROOFS)\n .verifyFirstPendingDeposit(\n depositBlockRoot,\n firstPendingDeposit.slot,\n firstPendingDeposit.proof\n );\n\n // Verify the withdrawableEpoch on the validator of the strategy's deposit\n IBeaconProofs(BEACON_PROOFS).verifyValidatorWithdrawable(\n depositBlockRoot,\n strategyValidator.index,\n strategyValidatorData.withdrawableEpoch,\n strategyValidatorData.withdrawableEpochProof\n );\n\n uint64 firstPendingDepositEpoch = firstPendingDeposit.slot /\n SLOTS_PER_EPOCH;\n\n // If deposit queue is empty all deposits have certainly been processed. If not\n // a validator can either be not exiting and no further checks are required.\n // Or a validator is exiting then this function needs to make sure that the\n // pending deposit to an exited validator has certainly been processed. The\n // slot/epoch of first pending deposit is the one that contains the transaction\n // where the deposit to the ETH Deposit Contract has been made.\n //\n // Once the firstPendingDepositEpoch becomes greater than the withdrawableEpoch of\n // the slashed validator then the deposit has certainly been processed. When the beacon\n // chain reaches the withdrawableEpoch of the validator the deposit will no longer be\n // postponed. And any new deposits created (and present in the deposit queue)\n // will have an equal or larger withdrawableEpoch.\n require(\n strategyValidatorData.withdrawableEpoch == FAR_FUTURE_EPOCH ||\n strategyValidatorData.withdrawableEpoch <=\n firstPendingDepositEpoch ||\n isDepositQueueEmpty,\n \"Exit Deposit likely not proc.\"\n );\n\n // solhint-disable max-line-length\n // Check the deposit slot is before the first pending deposit's slot on the beacon chain.\n // If this is not true then we can't guarantee the deposit has been processed by the beacon chain.\n // The deposit's slot can not be the same slot as the first pending deposit as there could be\n // many deposits in the same block, hence have the same pending deposit slot.\n // If the deposit queue is empty then our deposit must have been processed on the beacon chain.\n // The deposit slot can be zero for validators consolidating to a compounding validator or 0x01 validator\n // being promoted to a compounding one. Reference:\n // - [switch_to_compounding_validator](https://ethereum.github.io/consensus-specs/specs/electra/beacon-chain/#new-switch_to_compounding_validator\n // - [queue_excess_active_balance](https://ethereum.github.io/consensus-specs/specs/electra/beacon-chain/#new-queue_excess_active_balance)\n // - [process_consolidation_request](https://ethereum.github.io/consensus-specs/specs/electra/beacon-chain/#new-process_consolidation_request)\n // We can not guarantee that the deposit has been processed in that case.\n // solhint-enable max-line-length\n require(\n deposit.slot < firstPendingDeposit.slot || isDepositQueueEmpty,\n \"Deposit likely not processed\"\n );\n\n // Remove the deposit now it has been verified as processed on the beacon chain.\n _removeDeposit(pendingDepositRoot, deposit);\n\n emit DepositVerified(\n pendingDepositRoot,\n uint256(deposit.amountGwei) * 1 gwei\n );\n }\n\n function _removeDeposit(\n bytes32 pendingDepositRoot,\n DepositData memory deposit\n ) internal {\n // After verifying the proof, update the contract storage\n deposits[pendingDepositRoot].status = DepositStatus.VERIFIED;\n // Move the last deposit to the index of the verified deposit\n bytes32 lastDeposit = depositList[depositList.length - 1];\n depositList[deposit.depositIndex] = lastDeposit;\n deposits[lastDeposit].depositIndex = deposit.depositIndex;\n // Delete the last deposit from the list\n depositList.pop();\n }\n\n /// @dev Calculates the timestamp of the next execution block from the given slot.\n /// @param slot The beacon chain slot number used for merkle proof verification.\n function _calcNextBlockTimestamp(uint64 slot)\n internal\n view\n returns (uint64)\n {\n // Calculate the next block timestamp from the slot.\n return SLOT_DURATION * slot + BEACON_GENESIS_TIMESTAMP + SLOT_DURATION;\n }\n\n // slither-disable-end reentrancy-no-eth\n\n /// @notice Stores the current ETH balance at the current block and beacon block root\n /// of the slot that is associated with the previous block.\n ///\n /// When snapping / verifying balance it is of a high importance that there is no\n /// miss-match in respect to ETH that is held by the contract and balances that are\n /// verified on the validators.\n ///\n /// First some context on the beacon-chain block building behaviour. Relevant parts of\n /// constructing a block on the beacon chain consist of:\n /// - process_withdrawals: ETH is deducted from the validator's balance\n /// - process_execution_payload: immediately after the previous step executing all the\n /// transactions\n /// - apply the withdrawals: adding ETH to the recipient which is the withdrawal address\n /// contained in the withdrawal credentials of the exited validators\n ///\n /// That means that balance increases which are part of the post-block execution state are\n /// done within the block, but the transaction that are contained within that block can not\n /// see / interact with the balance from the exited validators. Only transactions in the\n /// next block can do that.\n ///\n /// When snap balances is performed the state of the chain is snapped across 2 separate\n /// chain states:\n /// - ETH balance of the contract is recorded on block X -> and corresponding slot Y\n /// - beacon chain block root is recorded of block X - 1 -> and corresponding slot Y - 1\n /// given there were no missed slots. It could also be Y - 2, Y - 3 depending on how\n /// many slots have not managed to propose a block. For the sake of simplicity this slot\n /// will be referred to as Y - 1 as it makes no difference in the argument\n ///\n /// Given these 2 separate chain states it is paramount that verify balances can not experience\n /// miss-counting ETH or much more dangerous double counting of the ETH.\n ///\n /// When verifyBalances is called it is performed on the current block Z where Z > X. Verify\n /// balances adds up all the ETH (omitting WETH) controlled by this contract:\n /// - ETH balance in the contract on block X\n /// - ETH balance in Deposits on block Z that haven't been yet processed in slot Y - 1\n /// - ETH balance in validators that are active in slot Y - 1\n /// - skips the ETH balance in validators that have withdrawn in slot Y - 1 (or sooner)\n /// and have their balance visible to transactions in slot Y and corresponding block X\n /// (or sooner)\n ///\n /// Lets verify the correctness of ETH accounting given the above described behaviour.\n ///\n /// *ETH balance in the contract on block X*\n ///\n /// This is an ETH balance of the contract on a non current X block. Any ETH leaving the\n /// contract as a result of a withdrawal subtracts from the ETH accounted for on block X\n /// if `verifyBalances` has already been called. It also invalidates a `snapBalances` in\n /// case `verifyBalances` has not been called yet. Not performing this would result in not\n /// accounting for the withdrawn ETH that has happened anywhere in the block interval [X + 1, Z].\n ///\n /// Similarly to withdrawals any `stakeEth` deposits to the deposit contract adds to the ETH\n /// accounted for since the last `verifyBalances` has been called. And it invalidates the\n /// `snapBalances` in case `verifyBalances` hasn't been yet called. Not performing this\n /// would result in double counting the `stakedEth` since it would be present once in the\n /// snapped contract balance and the second time in deposit storage variables.\n ///\n /// This behaviour is correct.\n ///\n /// *ETH balance in Deposits on block Z that haven't been yet processed in slot Y - 1*\n ///\n /// The contract sums up all the ETH that has been deposited to the Beacon chain deposit\n /// contract at block Z. The execution layer doesn't have direct access to the state of\n /// deposits on the beacon chain. And if it is to sum up all the ETH that is marked to be\n /// deposited it needs to be sure to not double count ETH that is in deposits (storage vars)\n /// and could also be part of the validator balances. It does that by verifying that at\n /// slot Y - 1 none of the deposits visible on block Z have been processed. Meaning since\n /// the last snap till now all are still in queue. Which ensures they can not be part of\n /// the validator balances in later steps.\n ///\n /// This behaviour is correct.\n ///\n /// *ETH balance in validators that are active in slot Y - 1*\n ///\n /// The contract is verifying none of the deposits on Y - 1 slot have been processed and\n /// for that reason it checks the validator balances in the same slot. Ensuring accounting\n /// correctness.\n ///\n /// This behaviour is correct.\n ///\n /// *The withdrawn validators*\n ///\n /// The withdrawn validators could have their balances deducted in any slot before slot\n /// Y - 1 and the execution layer sees the balance increase in the subsequent slot. Lets\n /// look at the \"worst case scenario\" where the validator withdrawal is processed in the\n /// slot Y - 1 (snapped slot) and see their balance increase (in execution layer) in slot\n /// Y -> block X. The ETH balance on the contract is snapped at block X meaning that\n /// even if the validator exits at the latest possible time it is paramount that the ETH\n /// balance on the execution layer is recorded in the next block. Correctly accounting\n /// for the withdrawn ETH.\n ///\n /// Worth mentioning if the validator exit is processed by the slot Y and balance increase\n /// seen on the execution layer on block X + 1 the withdrawal is ignored by both the\n /// validator balance verification as well as execution layer contract balance snap.\n ///\n /// This behaviour is correct.\n ///\n /// The validator balances on the beacon chain can then be proved with `verifyBalances`.\n function snapBalances() external {\n uint64 currentTimestamp = SafeCast.toUint64(block.timestamp);\n require(\n snappedBalance.timestamp + SNAP_BALANCES_DELAY < currentTimestamp,\n \"Snap too soon\"\n );\n\n bytes32 blockRoot = BeaconRoots.parentBlockRoot(currentTimestamp);\n // Get the current ETH balance\n uint256 ethBalance = address(this).balance;\n\n // Store the snapped balance\n snappedBalance = Balances({\n blockRoot: blockRoot,\n timestamp: currentTimestamp,\n ethBalance: SafeCast.toUint128(ethBalance)\n });\n\n emit BalancesSnapped(blockRoot, ethBalance);\n }\n\n // A struct is used to avoid stack too deep errors\n struct BalanceProofs {\n // BeaconBlock.state.balances\n bytes32 balancesContainerRoot;\n bytes balancesContainerProof;\n // BeaconBlock.state.balances[validatorIndex]\n bytes32[] validatorBalanceLeaves;\n bytes[] validatorBalanceProofs;\n }\n\n struct PendingDepositProofs {\n bytes32 pendingDepositContainerRoot;\n bytes pendingDepositContainerProof;\n uint32[] pendingDepositIndexes;\n bytes[] pendingDepositProofs;\n }\n\n /// @notice Verifies the balances of all active validators on the beacon chain\n /// and checks each of the strategy's deposits are still to be processed by the beacon chain.\n /// @param balanceProofs a `BalanceProofs` struct containing the following:\n /// - balancesContainerRoot: The merkle root of the balances container\n /// - balancesContainerProof: The merkle proof for the balances container to the beacon block root.\n /// This is 9 witness hashes of 32 bytes each concatenated together starting from the leaf node.\n /// - validatorBalanceLeaves: Array of leaf nodes containing the validator balance with three other balances.\n /// - validatorBalanceProofs: Array of merkle proofs for the validator balance to the Balances container root.\n /// This is 39 witness hashes of 32 bytes each concatenated together starting from the leaf node.\n /// @param pendingDepositProofs a `PendingDepositProofs` struct containing the following:\n /// - pendingDepositContainerRoot: The merkle root of the pending deposits list container\n /// - pendingDepositContainerProof: The merkle proof from the pending deposits list container\n /// to the beacon block root.\n /// This is 9 witness hashes of 32 bytes each concatenated together starting from the leaf node.\n /// - pendingDepositIndexes: Array of indexes in the pending deposits list container for each\n /// of the strategy's deposits.\n /// - pendingDepositProofs: Array of merkle proofs for each strategy deposit in the\n /// beacon chain's pending deposit list container to the pending deposits list container root.\n /// These are 28 witness hashes of 32 bytes each concatenated together starting from the leaf node.\n // slither-disable-start reentrancy-no-eth\n function verifyBalances(\n BalanceProofs calldata balanceProofs,\n PendingDepositProofs calldata pendingDepositProofs\n ) external {\n // Load previously snapped balances for the given block root\n Balances memory balancesMem = snappedBalance;\n // Check the balances are the latest\n require(balancesMem.timestamp > 0, \"No snapped balances\");\n\n uint256 verifiedValidatorsCount = verifiedValidators.length;\n uint256 totalValidatorBalance = 0;\n uint256 depositsCount = depositList.length;\n\n // If there are no verified validators then we can skip the balance verification\n if (verifiedValidatorsCount > 0) {\n require(\n balanceProofs.validatorBalanceProofs.length ==\n verifiedValidatorsCount,\n \"Invalid balance proofs\"\n );\n require(\n balanceProofs.validatorBalanceLeaves.length ==\n verifiedValidatorsCount,\n \"Invalid balance leaves\"\n );\n // verify beaconBlock.state.balances root to beacon block root\n IBeaconProofs(BEACON_PROOFS).verifyBalancesContainer(\n balancesMem.blockRoot,\n balanceProofs.balancesContainerRoot,\n balanceProofs.balancesContainerProof\n );\n\n bytes32[]\n memory validatorHashesMem = _getPendingDepositValidatorHashes(\n depositsCount\n );\n\n // for each validator in reverse order so we can pop off exited validators at the end\n for (uint256 i = verifiedValidatorsCount; i > 0; ) {\n --i;\n ValidatorData memory validatorDataMem = validator[\n verifiedValidators[i]\n ];\n // verify validator's balance in beaconBlock.state.balances to the\n // beaconBlock.state.balances container root\n uint256 validatorBalanceGwei = IBeaconProofs(BEACON_PROOFS)\n .verifyValidatorBalance(\n balanceProofs.balancesContainerRoot,\n balanceProofs.validatorBalanceLeaves[i],\n balanceProofs.validatorBalanceProofs[i],\n validatorDataMem.index\n );\n\n // If the validator has exited and the balance is now zero\n if (validatorBalanceGwei == 0) {\n // Check if there are any pending deposits to this validator\n bool depositPending = false;\n for (uint256 j = 0; j < validatorHashesMem.length; j++) {\n if (validatorHashesMem[j] == verifiedValidators[i]) {\n depositPending = true;\n break;\n }\n }\n\n // If validator has a pending deposit we can not remove due to\n // the following situation:\n // - validator has a pending deposit\n // - validator has been slashed\n // - sweep cycle has withdrawn all ETH from the validator. Balance is 0\n // - beacon chain has processed the deposit and set the validator balance\n // to deposit amount\n // - if validator is no longer in the list of verifiedValidators its\n // balance will not be considered and be under-counted.\n if (!depositPending) {\n // Store the validator state as exited\n // This could have been in VERIFIED, ACTIVE or EXITING state\n validator[verifiedValidators[i]].state = ValidatorState\n .EXITED;\n\n // Remove the validator with a zero balance from the list of verified validators\n\n // Reduce the count of verified validators which is the last index before the pop removes it.\n verifiedValidatorsCount -= 1;\n\n // Move the last validator that has already been verified to the current index.\n // There's an extra SSTORE if i is the last active validator but that's fine,\n // It's not a common case and the code is simpler this way.\n verifiedValidators[i] = verifiedValidators[\n verifiedValidatorsCount\n ];\n // Delete the last validator from the list\n verifiedValidators.pop();\n }\n\n // The validator balance is zero so not need to add to totalValidatorBalance\n continue;\n } else if (\n validatorDataMem.state == ValidatorState.VERIFIED &&\n validatorBalanceGwei > MIN_ACTIVATION_BALANCE_GWEI\n ) {\n // Store the validator state as active. This does not necessarily mean the\n // validator is active on the beacon chain yet. It just means the validator has\n // enough balance that it can become active.\n validator[verifiedValidators[i]].state = ValidatorState\n .ACTIVE;\n }\n\n // convert Gwei balance to Wei and add to the total validator balance\n totalValidatorBalance += validatorBalanceGwei * 1 gwei;\n }\n }\n\n uint256 totalDepositsWei = 0;\n\n // If there are no deposits then we can skip the deposit verification.\n // This section is after the validator balance verifications so an exited validator will be marked\n // as EXITED before the deposits are verified. If there was a deposit to an exited validator\n // then the deposit can only be removed once the validator is fully exited.\n // It is possible that validator fully exits and a postponed deposit to an exited validator increases\n // its balance again. In such case the contract will erroneously consider a deposit applied before it\n // has been applied on the beacon chain showing a smaller than real `totalValidatorBalance`.\n if (depositsCount > 0) {\n require(\n pendingDepositProofs.pendingDepositProofs.length ==\n depositsCount,\n \"Invalid deposit proofs\"\n );\n require(\n pendingDepositProofs.pendingDepositIndexes.length ==\n depositsCount,\n \"Invalid deposit indexes\"\n );\n\n // Verify from the root of the pending deposit list container to the beacon block root\n IBeaconProofs(BEACON_PROOFS).verifyPendingDepositsContainer(\n balancesMem.blockRoot,\n pendingDepositProofs.pendingDepositContainerRoot,\n pendingDepositProofs.pendingDepositContainerProof\n );\n\n // For each staking strategy's deposit.\n for (uint256 i = 0; i < depositsCount; ++i) {\n bytes32 pendingDepositRoot = depositList[i];\n\n // Verify the strategy's deposit is still pending on the beacon chain.\n IBeaconProofs(BEACON_PROOFS).verifyPendingDeposit(\n pendingDepositProofs.pendingDepositContainerRoot,\n pendingDepositRoot,\n pendingDepositProofs.pendingDepositProofs[i],\n pendingDepositProofs.pendingDepositIndexes[i]\n );\n\n // Convert the deposit amount from Gwei to Wei and add to the total\n totalDepositsWei +=\n uint256(deposits[pendingDepositRoot].amountGwei) *\n 1 gwei;\n }\n }\n\n // Store the verified balance in storage\n lastVerifiedEthBalance =\n totalDepositsWei +\n totalValidatorBalance +\n balancesMem.ethBalance;\n // Reset the last snap timestamp so a new snapBalances has to be made\n snappedBalance.timestamp = 0;\n\n emit BalancesVerified(\n balancesMem.timestamp,\n totalDepositsWei,\n totalValidatorBalance,\n balancesMem.ethBalance\n );\n }\n\n // slither-disable-end reentrancy-no-eth\n\n /// @notice get a list of all validator hashes present in the pending deposits\n /// list can have duplicate entries\n function _getPendingDepositValidatorHashes(uint256 depositsCount)\n internal\n view\n returns (bytes32[] memory validatorHashes)\n {\n validatorHashes = new bytes32[](depositsCount);\n for (uint256 i = 0; i < depositsCount; i++) {\n validatorHashes[i] = deposits[depositList[i]].pubKeyHash;\n }\n }\n\n /// @notice Hash a validator public key using the Beacon Chain's format\n function _hashPubKey(bytes memory pubKey) internal pure returns (bytes32) {\n require(pubKey.length == 48, \"Invalid public key\");\n return sha256(abi.encodePacked(pubKey, bytes16(0)));\n }\n\n /**\n *\n * WETH and ETH Accounting\n *\n */\n\n /// @dev Called when WETH is transferred out of the strategy so\n /// the strategy knows how much WETH it has on deposit.\n /// This is so it can emit the correct amount in the Deposit event in depositAll().\n function _transferWeth(uint256 _amount, address _recipient) internal {\n IERC20(WETH).safeTransfer(_recipient, _amount);\n\n // The min is required as more WETH can be withdrawn than deposited\n // as the strategy earns consensus and execution rewards.\n uint256 deductAmount = Math.min(_amount, depositedWethAccountedFor);\n depositedWethAccountedFor -= deductAmount;\n\n // No change in ETH balance so no need to snapshot the balances\n }\n\n /// @dev Converts ETH to WETH and updates the accounting.\n /// @param _ethAmount The amount of ETH in wei.\n function _convertEthToWeth(uint256 _ethAmount) internal {\n // slither-disable-next-line arbitrary-send-eth\n IWETH9(WETH).deposit{ value: _ethAmount }();\n\n depositedWethAccountedFor += _ethAmount;\n\n // Store the reduced ETH balance.\n // The ETH balance in this strategy contract can be more than the last verified ETH balance\n // due to partial withdrawals or full exits being processed by the beacon chain since the last snapBalances.\n // It can also happen from execution rewards (MEV) or ETH donations.\n lastVerifiedEthBalance -= Math.min(lastVerifiedEthBalance, _ethAmount);\n\n // The ETH balance was decreased to WETH so we need to invalidate the last balances snap.\n snappedBalance.timestamp = 0;\n }\n\n /// @dev Converts WETH to ETH and updates the accounting.\n /// @param _wethAmount The amount of WETH in wei.\n function _convertWethToEth(uint256 _wethAmount) internal {\n IWETH9(WETH).withdraw(_wethAmount);\n\n uint256 deductAmount = Math.min(_wethAmount, depositedWethAccountedFor);\n depositedWethAccountedFor -= deductAmount;\n\n // Store the increased ETH balance\n lastVerifiedEthBalance += _wethAmount;\n\n // The ETH balance was increased from WETH so we need to invalidate the last balances snap.\n snappedBalance.timestamp = 0;\n }\n\n /**\n *\n * View Functions\n *\n */\n\n /// @notice Returns the number of deposits waiting to be verified as processed on the beacon chain,\n /// or deposits that have been verified to an exiting validator and is now waiting for the\n /// validator's balance to be swept.\n function depositListLength() external view returns (uint256) {\n return depositList.length;\n }\n\n /// @notice Returns the number of verified validators.\n function verifiedValidatorsLength() external view returns (uint256) {\n return verifiedValidators.length;\n }\n}\n" + }, + "contracts/strategies/NativeStaking/FeeAccumulator.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { Address } from \"@openzeppelin/contracts/utils/Address.sol\";\n\n/**\n * @title Fee Accumulator for Native Staking SSV Strategy\n * @notice Receives execution rewards which includes tx fees and\n * MEV rewards like tx priority and tx ordering.\n * It does NOT include swept ETH from beacon chain consensus rewards or full validator withdrawals.\n * @author Origin Protocol Inc\n */\ncontract FeeAccumulator {\n /// @notice The address of the Native Staking Strategy\n address public immutable STRATEGY;\n\n event ExecutionRewardsCollected(address indexed strategy, uint256 amount);\n\n /**\n * @param _strategy Address of the Native Staking Strategy\n */\n constructor(address _strategy) {\n STRATEGY = _strategy;\n }\n\n /**\n * @notice sends all ETH in this FeeAccumulator contract to the Native Staking Strategy.\n * @return eth The amount of execution rewards that were sent to the Native Staking Strategy\n */\n function collect() external returns (uint256 eth) {\n require(msg.sender == STRATEGY, \"Caller is not the Strategy\");\n\n eth = address(this).balance;\n if (eth > 0) {\n // Send the ETH to the Native Staking Strategy\n Address.sendValue(payable(STRATEGY), eth);\n\n emit ExecutionRewardsCollected(STRATEGY, eth);\n }\n }\n\n /**\n * @dev Accept ETH\n */\n receive() external payable {}\n}\n" + }, + "contracts/strategies/NativeStaking/NativeStakingSSVStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport \"@openzeppelin/contracts/utils/math/Math.sol\";\n\nimport { InitializableAbstractStrategy } from \"../../utils/InitializableAbstractStrategy.sol\";\nimport { IWETH9 } from \"../../interfaces/IWETH9.sol\";\nimport { FeeAccumulator } from \"./FeeAccumulator.sol\";\nimport { ValidatorAccountant } from \"./ValidatorAccountant.sol\";\nimport { ISSVNetwork } from \"../../interfaces/ISSVNetwork.sol\";\n\nstruct ValidatorStakeData {\n bytes pubkey;\n bytes signature;\n bytes32 depositDataRoot;\n}\n\n/// @title Native Staking SSV Strategy\n/// @notice Strategy to deploy funds into DVT validators powered by the SSV Network\n/// @author Origin Protocol Inc\n/// @dev This contract handles WETH and ETH and in some operations interchanges between the two. Any WETH that\n/// is on the contract across multiple blocks (and not just transitory within a transaction) is considered an\n/// asset. Meaning deposits increase the balance of the asset and withdrawal decrease it. As opposed to all\n/// our other strategies the WETH doesn't immediately get deposited into an underlying strategy and can be present\n/// across multiple blocks waiting to be unwrapped to ETH and staked to validators. This separation of WETH and ETH is\n/// required since the rewards (reward token) is also in ETH.\n///\n/// To simplify the accounting of WETH there is another difference in behavior compared to the other strategies.\n/// To withdraw WETH asset - exit message is posted to validators and the ETH hits this contract with multiple days\n/// delay. In order to simplify the WETH accounting upon detection of such an event the ValidatorAccountant\n/// immediately wraps ETH to WETH and sends it to the Vault.\n///\n/// On the other hand any ETH on the contract (across multiple blocks) is there either:\n/// - as a result of already accounted for consensus rewards\n/// - as a result of not yet accounted for consensus rewards\n/// - as a results of not yet accounted for full validator withdrawals (or validator slashes)\n///\n/// Even though the strategy assets and rewards are a very similar asset the consensus layer rewards and the\n/// execution layer rewards are considered rewards and those are dripped to the Vault over a configurable time\n/// interval and not immediately.\ncontract NativeStakingSSVStrategy is\n ValidatorAccountant,\n InitializableAbstractStrategy\n{\n using SafeERC20 for IERC20;\n\n /// @notice SSV ERC20 token that serves as a payment for operating SSV validators\n address public immutable SSV_TOKEN;\n /// @notice Fee collector address\n /// @dev this address will receive maximal extractable value (MEV) rewards. These are\n /// rewards for arranging transactions in a way that benefits the validator.\n address payable public immutable FEE_ACCUMULATOR_ADDRESS;\n\n /// @dev This contract receives WETH as the deposit asset, but unlike other strategies doesn't immediately\n /// deposit it to an underlying platform. Rather a special privilege account stakes it to the validators.\n /// For that reason calling WETH.balanceOf(this) in a deposit function can contain WETH that has just been\n /// deposited and also WETH that has previously been deposited. To keep a correct count we need to keep track\n /// of WETH that has already been accounted for.\n /// This value represents the amount of WETH balance of this contract that has already been accounted for by the\n /// deposit events.\n /// It is important to note that this variable is not concerned with WETH that is a result of full/partial\n /// withdrawal of the validators. It is strictly concerned with WETH that has been deposited and is waiting to\n /// be staked.\n uint256 public depositedWethAccountedFor;\n\n // For future use\n uint256[49] private __gap;\n\n /// @param _baseConfig Base strategy config with platformAddress (ERC-4626 Vault contract), eg sfrxETH or sDAI,\n /// and vaultAddress (OToken Vault contract), eg VaultProxy or OETHVaultProxy\n /// @param _wethAddress Address of the Erc20 WETH Token contract\n /// @param _ssvToken Address of the Erc20 SSV Token contract\n /// @param _ssvNetwork Address of the SSV Network contract\n /// @param _maxValidators Maximum number of validators that can be registered in the strategy\n /// @param _feeAccumulator Address of the fee accumulator receiving execution layer validator rewards\n /// @param _beaconChainDepositContract Address of the beacon chain deposit contract\n constructor(\n BaseStrategyConfig memory _baseConfig,\n address _wethAddress,\n address _ssvToken,\n address _ssvNetwork,\n uint256 _maxValidators,\n address _feeAccumulator,\n address _beaconChainDepositContract\n )\n InitializableAbstractStrategy(_baseConfig)\n ValidatorAccountant(\n _wethAddress,\n _baseConfig.vaultAddress,\n _beaconChainDepositContract,\n _ssvNetwork,\n _maxValidators\n )\n {\n SSV_TOKEN = _ssvToken;\n FEE_ACCUMULATOR_ADDRESS = payable(_feeAccumulator);\n }\n\n /// @notice Set up initial internal state including\n /// 1. approving the SSVNetwork to transfer SSV tokens from this strategy contract\n /// 2. setting the recipient of SSV validator MEV rewards to the FeeAccumulator contract.\n /// @param _rewardTokenAddresses Address of reward token for platform\n /// @param _assets Addresses of initial supported assets\n /// @param _pTokens Platform Token corresponding addresses\n function initialize(\n address[] memory _rewardTokenAddresses,\n address[] memory _assets,\n address[] memory _pTokens\n ) external onlyGovernor initializer {\n InitializableAbstractStrategy._initialize(\n _rewardTokenAddresses,\n _assets,\n _pTokens\n );\n\n // Approves the SSV Network contract to transfer SSV tokens for deposits\n IERC20(SSV_TOKEN).approve(SSV_NETWORK, type(uint256).max);\n\n // Set the FeeAccumulator as the address for SSV validators to send MEV rewards to\n ISSVNetwork(SSV_NETWORK).setFeeRecipientAddress(\n FEE_ACCUMULATOR_ADDRESS\n );\n }\n\n /// @notice Unlike other strategies, this does not deposit assets into the underlying platform.\n /// It just checks the asset is WETH and emits the Deposit event.\n /// To deposit WETH into validators `registerSsvValidator` and `stakeEth` must be used.\n /// Will NOT revert if the strategy is paused from an accounting failure.\n /// @param _asset Address of asset to deposit. Has to be WETH.\n /// @param _amount Amount of assets that were transferred to the strategy by the vault.\n function deposit(address _asset, uint256 _amount)\n external\n override\n onlyVault\n nonReentrant\n {\n require(_asset == WETH, \"Unsupported asset\");\n depositedWethAccountedFor += _amount;\n _deposit(_asset, _amount);\n }\n\n /// @dev Deposit WETH to this strategy so it can later be staked into a validator.\n /// @param _asset Address of WETH\n /// @param _amount Amount of WETH to deposit\n function _deposit(address _asset, uint256 _amount) internal {\n require(_amount > 0, \"Must deposit something\");\n /*\n * We could do a check here that would revert when \"_amount % 32 ether != 0\". With the idea of\n * not allowing deposits that will result in WETH sitting on the strategy after all the possible batches\n * of 32ETH have been staked.\n * But someone could mess with our strategy by sending some WETH to it. And we might want to deposit just\n * enough WETH to add it up to 32 so it can be staked. For that reason the check is left out.\n *\n * WETH sitting on the strategy won't interfere with the accounting since accounting only operates on ETH.\n */\n emit Deposit(_asset, address(0), _amount);\n }\n\n /// @notice Unlike other strategies, this does not deposit assets into the underlying platform.\n /// It just emits the Deposit event.\n /// To deposit WETH into validators `registerSsvValidator` and `stakeEth` must be used.\n /// Will NOT revert if the strategy is paused from an accounting failure.\n function depositAll() external override onlyVault nonReentrant {\n uint256 wethBalance = IERC20(WETH).balanceOf(address(this));\n uint256 newWeth = wethBalance - depositedWethAccountedFor;\n\n if (newWeth > 0) {\n depositedWethAccountedFor = wethBalance;\n\n _deposit(WETH, newWeth);\n }\n }\n\n /// @notice Withdraw WETH from this contract. Used only if some WETH for is lingering on the contract.\n /// That can happen when:\n /// - after mints if the strategy is the default\n /// - time between depositToStrategy and stakeEth\n /// - the deposit was not a multiple of 32 WETH\n /// - someone sent WETH directly to this contract\n /// Will NOT revert if the strategy is paused from an accounting failure.\n /// @param _recipient Address to receive withdrawn assets\n /// @param _asset WETH to withdraw\n /// @param _amount Amount of WETH to withdraw\n function withdraw(\n address _recipient,\n address _asset,\n uint256 _amount\n ) external override onlyVault nonReentrant {\n require(_asset == WETH, \"Unsupported asset\");\n _withdraw(_recipient, _asset, _amount);\n }\n\n function _withdraw(\n address _recipient,\n address _asset,\n uint256 _amount\n ) internal {\n require(_amount > 0, \"Must withdraw something\");\n require(_recipient != address(0), \"Must specify recipient\");\n\n _wethWithdrawn(_amount);\n\n IERC20(_asset).safeTransfer(_recipient, _amount);\n emit Withdrawal(_asset, address(0), _amount);\n }\n\n /// @notice transfer all WETH deposits back to the vault.\n /// This does not withdraw from the validators. That has to be done separately with the\n /// `exitSsvValidator` and `removeSsvValidator` operations.\n /// This does not withdraw any execution rewards from the FeeAccumulator or\n /// consensus rewards in this strategy.\n /// Any ETH in this strategy that was swept from a full validator withdrawal will not be withdrawn.\n /// ETH from full validator withdrawals is sent to the Vault using `doAccounting`.\n /// Will NOT revert if the strategy is paused from an accounting failure.\n function withdrawAll() external override onlyVaultOrGovernor nonReentrant {\n uint256 wethBalance = IERC20(WETH).balanceOf(address(this));\n if (wethBalance > 0) {\n _withdraw(vaultAddress, WETH, wethBalance);\n }\n }\n\n /// @notice Returns the total value of (W)ETH that is staked to the validators\n /// and WETH deposits that are still to be staked.\n /// This does not include ETH from consensus rewards sitting in this strategy\n /// or ETH from MEV rewards in the FeeAccumulator. These rewards are harvested\n /// and sent to the Dripper so will eventually be sent to the Vault as WETH.\n /// @param _asset Address of weth asset\n /// @return balance Total value of (W)ETH\n function checkBalance(address _asset)\n external\n view\n override\n returns (uint256 balance)\n {\n require(_asset == WETH, \"Unsupported asset\");\n\n balance =\n // add the ETH that has been staked in validators\n activeDepositedValidators *\n FULL_STAKE +\n // add the WETH in the strategy from deposits that are still to be staked\n IERC20(WETH).balanceOf(address(this));\n }\n\n function pause() external onlyStrategist {\n _pause();\n }\n\n /// @notice Returns bool indicating whether asset is supported by strategy.\n /// @param _asset The address of the asset token.\n function supportsAsset(address _asset) public view override returns (bool) {\n return _asset == WETH;\n }\n\n /// @notice Approves the SSV Network contract to transfer SSV tokens for deposits\n function safeApproveAllTokens() external override {\n // Approves the SSV Network contract to transfer SSV tokens for deposits\n IERC20(SSV_TOKEN).approve(SSV_NETWORK, type(uint256).max);\n }\n\n /// @notice Set the FeeAccumulator as the address for SSV validators to send MEV rewards to\n function setFeeRecipient() external {\n ISSVNetwork(SSV_NETWORK).setFeeRecipientAddress(\n FEE_ACCUMULATOR_ADDRESS\n );\n }\n\n /**\n * @notice Only accept ETH from the FeeAccumulator and the WETH contract - required when\n * unwrapping WETH just before staking it to the validator.\n * The strategy will also receive ETH from the priority fees of transactions when producing blocks\n * as defined in EIP-1559.\n * The tx fees come from the Beacon chain so do not need any EVM level permissions to receive ETH.\n * The tx fees are paid with each block produced. They are not included in the consensus rewards\n * which are periodically swept from the validators to this strategy.\n * For accounting purposes, the priority fees of transactions will be considered consensus rewards\n * and will be included in the AccountingConsensusRewards event.\n * @dev don't want to receive donations from anyone else as donations over the fuse limits will\n * mess with the accounting of the consensus rewards and validator full withdrawals.\n */\n receive() external payable {\n require(\n msg.sender == FEE_ACCUMULATOR_ADDRESS || msg.sender == WETH,\n \"Eth not from allowed contracts\"\n );\n }\n\n /***************************************\n Internal functions\n ****************************************/\n\n function _abstractSetPToken(address _asset, address) internal override {}\n\n /// @dev Convert accumulated ETH to WETH and send to the Harvester.\n /// Will revert if the strategy is paused for accounting.\n function _collectRewardTokens() internal override whenNotPaused {\n // collect ETH from execution rewards from the fee accumulator\n uint256 executionRewards = FeeAccumulator(FEE_ACCUMULATOR_ADDRESS)\n .collect();\n\n // total ETH rewards to be harvested = execution rewards + consensus rewards\n uint256 ethRewards = executionRewards + consensusRewards;\n\n require(\n address(this).balance >= ethRewards,\n \"Insufficient eth balance\"\n );\n\n if (ethRewards > 0) {\n // reset the counter keeping track of beacon chain consensus rewards\n consensusRewards = 0;\n\n // Convert ETH rewards to WETH\n IWETH9(WETH).deposit{ value: ethRewards }();\n\n IERC20(WETH).safeTransfer(harvesterAddress, ethRewards);\n emit RewardTokenCollected(harvesterAddress, WETH, ethRewards);\n }\n }\n\n /// @dev emits Withdrawal event from NativeStakingSSVStrategy\n function _wethWithdrawnToVault(uint256 _amount) internal override {\n emit Withdrawal(WETH, address(0), _amount);\n }\n\n /// @dev Called when WETH is withdrawn from the strategy or staked to a validator so\n /// the strategy knows how much WETH it has on deposit.\n /// This is so it can emit the correct amount in the Deposit event in depositAll().\n function _wethWithdrawn(uint256 _amount) internal override {\n /* In an ideal world we wouldn't need to reduce the deduction amount when the\n * depositedWethAccountedFor is smaller than the _amount.\n *\n * The reason this is required is that a malicious actor could sent WETH directly\n * to this contract and that would circumvent the increase of depositedWethAccountedFor\n * property. When the ETH would be staked the depositedWethAccountedFor amount could\n * be deducted so much that it would be negative.\n */\n uint256 deductAmount = Math.min(_amount, depositedWethAccountedFor);\n depositedWethAccountedFor -= deductAmount;\n }\n}\n" + }, + "contracts/strategies/NativeStaking/ValidatorAccountant.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { ValidatorRegistrator } from \"./ValidatorRegistrator.sol\";\nimport { IWETH9 } from \"../../interfaces/IWETH9.sol\";\n\n/// @title Validator Accountant\n/// @notice Attributes the ETH swept from beacon chain validators to this strategy contract\n/// as either full or partial withdrawals. Partial withdrawals being consensus rewards.\n/// Full withdrawals are from exited validators.\n/// @author Origin Protocol Inc\nabstract contract ValidatorAccountant is ValidatorRegistrator {\n /// @notice The minimum amount of blocks that need to pass between two calls to manuallyFixAccounting\n uint256 public constant MIN_FIX_ACCOUNTING_CADENCE = 7200; // 1 day\n\n /// @notice Keeps track of the total consensus rewards swept from the beacon chain\n uint256 public consensusRewards;\n\n /// @notice start of fuse interval\n uint256 public fuseIntervalStart;\n /// @notice end of fuse interval\n uint256 public fuseIntervalEnd;\n /// @notice last block number manuallyFixAccounting has been called\n uint256 public lastFixAccountingBlockNumber;\n\n uint256[49] private __gap;\n\n event FuseIntervalUpdated(uint256 start, uint256 end);\n event AccountingFullyWithdrawnValidator(\n uint256 noOfValidators,\n uint256 remainingValidators,\n uint256 wethSentToVault\n );\n event AccountingValidatorSlashed(\n uint256 remainingValidators,\n uint256 wethSentToVault\n );\n event AccountingConsensusRewards(uint256 amount);\n\n event AccountingManuallyFixed(\n int256 validatorsDelta,\n int256 consensusRewardsDelta,\n uint256 wethToVault\n );\n\n /// @param _wethAddress Address of the Erc20 WETH Token contract\n /// @param _vaultAddress Address of the Vault\n /// @param _beaconChainDepositContract Address of the beacon chain deposit contract\n /// @param _ssvNetwork Address of the SSV Network contract\n /// @param _maxValidators Maximum number of validators that can be registered in the strategy\n constructor(\n address _wethAddress,\n address _vaultAddress,\n address _beaconChainDepositContract,\n address _ssvNetwork,\n uint256 _maxValidators\n )\n ValidatorRegistrator(\n _wethAddress,\n _vaultAddress,\n _beaconChainDepositContract,\n _ssvNetwork,\n _maxValidators\n )\n {}\n\n /// @notice set fuse interval values\n function setFuseInterval(\n uint256 _fuseIntervalStart,\n uint256 _fuseIntervalEnd\n ) external onlyGovernor {\n require(\n _fuseIntervalStart < _fuseIntervalEnd &&\n _fuseIntervalEnd < 32 ether &&\n _fuseIntervalEnd - _fuseIntervalStart >= 4 ether,\n \"Incorrect fuse interval\"\n );\n\n fuseIntervalStart = _fuseIntervalStart;\n fuseIntervalEnd = _fuseIntervalEnd;\n\n emit FuseIntervalUpdated(_fuseIntervalStart, _fuseIntervalEnd);\n }\n\n /* solhint-disable max-line-length */\n /// This notion page offers a good explanation of how the accounting functions\n /// https://www.notion.so/originprotocol/Limited-simplified-native-staking-accounting-67a217c8420d40678eb943b9da0ee77d\n /// In short, after dividing by 32, if the ETH remaining on the contract falls between 0 and fuseIntervalStart,\n /// the accounting function will treat that ETH as Beacon chain consensus rewards.\n /// On the contrary, if after dividing by 32, the ETH remaining on the contract falls between fuseIntervalEnd and 32,\n /// the accounting function will treat that as a validator slashing.\n /// @notice Perform the accounting attributing beacon chain ETH to either full or partial withdrawals. Returns true when\n /// accounting is valid and fuse isn't \"blown\". Returns false when fuse is blown.\n /// @dev This function could in theory be permission-less but lets allow only the Registrator (Defender Action) to call it\n /// for now.\n /// @return accountingValid true if accounting was successful, false if fuse is blown\n /* solhint-enable max-line-length */\n function doAccounting()\n external\n onlyRegistrator\n whenNotPaused\n nonReentrant\n returns (bool accountingValid)\n {\n // pause the accounting on failure\n accountingValid = _doAccounting(true);\n }\n\n // slither-disable-start reentrancy-eth\n function _doAccounting(bool pauseOnFail)\n internal\n returns (bool accountingValid)\n {\n if (address(this).balance < consensusRewards) {\n return _failAccounting(pauseOnFail);\n }\n\n // Calculate all the new ETH that has been swept to the contract since the last accounting\n uint256 newSweptETH = address(this).balance - consensusRewards;\n accountingValid = true;\n\n // send the ETH that is from fully withdrawn validators to the Vault\n if (newSweptETH >= FULL_STAKE) {\n uint256 fullyWithdrawnValidators;\n // explicitly cast to uint256 as we want to round to a whole number of validators\n fullyWithdrawnValidators = uint256(newSweptETH / FULL_STAKE);\n activeDepositedValidators -= fullyWithdrawnValidators;\n\n uint256 wethToVault = FULL_STAKE * fullyWithdrawnValidators;\n IWETH9(WETH).deposit{ value: wethToVault }();\n // slither-disable-next-line unchecked-transfer\n IWETH9(WETH).transfer(VAULT_ADDRESS, wethToVault);\n _wethWithdrawnToVault(wethToVault);\n\n emit AccountingFullyWithdrawnValidator(\n fullyWithdrawnValidators,\n activeDepositedValidators,\n wethToVault\n );\n }\n\n uint256 ethRemaining = address(this).balance - consensusRewards;\n // should be less than a whole validator stake\n require(ethRemaining < FULL_STAKE, \"Unexpected accounting\");\n\n // If no Beacon chain consensus rewards swept\n if (ethRemaining == 0) {\n // do nothing\n return accountingValid;\n } else if (ethRemaining < fuseIntervalStart) {\n // Beacon chain consensus rewards swept (partial validator withdrawals)\n // solhint-disable-next-line reentrancy\n consensusRewards += ethRemaining;\n emit AccountingConsensusRewards(ethRemaining);\n } else if (ethRemaining > fuseIntervalEnd) {\n // Beacon chain consensus rewards swept but also a slashed validator fully exited\n IWETH9(WETH).deposit{ value: ethRemaining }();\n // slither-disable-next-line unchecked-transfer\n IWETH9(WETH).transfer(VAULT_ADDRESS, ethRemaining);\n activeDepositedValidators -= 1;\n\n _wethWithdrawnToVault(ethRemaining);\n\n emit AccountingValidatorSlashed(\n activeDepositedValidators,\n ethRemaining\n );\n }\n // Oh no... Fuse is blown. The Strategist needs to adjust the accounting values.\n else {\n return _failAccounting(pauseOnFail);\n }\n }\n\n // slither-disable-end reentrancy-eth\n\n /// @dev pause any further accounting if required and return false\n function _failAccounting(bool pauseOnFail)\n internal\n returns (bool accountingValid)\n {\n // pause if not already\n if (pauseOnFail) {\n _pause();\n }\n // fail the accounting\n accountingValid = false;\n }\n\n /// @notice Allow the Strategist to fix the accounting of this strategy and unpause.\n /// @param _validatorsDelta adjust the active validators by up to plus three or minus three\n /// @param _consensusRewardsDelta adjust the accounted for consensus rewards up or down\n /// @param _ethToVaultAmount the amount of ETH that gets wrapped into WETH and sent to the Vault\n /// @dev There is a case when a validator(s) gets slashed so much that the eth swept from\n /// the beacon chain enters the fuse area and there are no consensus rewards on the contract\n /// to \"dip into\"/use. To increase the amount of unaccounted ETH over the fuse end interval\n /// we need to reduce the amount of active deposited validators and immediately send WETH\n /// to the vault, so it doesn't interfere with further accounting.\n function manuallyFixAccounting(\n int256 _validatorsDelta,\n int256 _consensusRewardsDelta,\n uint256 _ethToVaultAmount\n ) external onlyStrategist whenPaused nonReentrant {\n require(\n lastFixAccountingBlockNumber + MIN_FIX_ACCOUNTING_CADENCE <\n block.number,\n \"Fix accounting called too soon\"\n );\n require(\n _validatorsDelta >= -3 &&\n _validatorsDelta <= 3 &&\n // new value must be positive\n int256(activeDepositedValidators) + _validatorsDelta >= 0,\n \"Invalid validatorsDelta\"\n );\n require(\n _consensusRewardsDelta >= -332 ether &&\n _consensusRewardsDelta <= 332 ether &&\n // new value must be positive\n int256(consensusRewards) + _consensusRewardsDelta >= 0,\n \"Invalid consensusRewardsDelta\"\n );\n require(_ethToVaultAmount <= 32 ether * 3, \"Invalid wethToVaultAmount\");\n\n activeDepositedValidators = uint256(\n int256(activeDepositedValidators) + _validatorsDelta\n );\n consensusRewards = uint256(\n int256(consensusRewards) + _consensusRewardsDelta\n );\n lastFixAccountingBlockNumber = block.number;\n if (_ethToVaultAmount > 0) {\n IWETH9(WETH).deposit{ value: _ethToVaultAmount }();\n // slither-disable-next-line unchecked-transfer\n IWETH9(WETH).transfer(VAULT_ADDRESS, _ethToVaultAmount);\n _wethWithdrawnToVault(_ethToVaultAmount);\n }\n\n emit AccountingManuallyFixed(\n _validatorsDelta,\n _consensusRewardsDelta,\n _ethToVaultAmount\n );\n\n // rerun the accounting to see if it has now been fixed.\n // Do not pause the accounting on failure as it is already paused\n require(_doAccounting(false), \"Fuse still blown\");\n\n // unpause since doAccounting was successful\n _unpause();\n }\n\n /***************************************\n Abstract\n ****************************************/\n\n /// @dev allows for NativeStakingSSVStrategy contract to emit the Withdrawal event\n function _wethWithdrawnToVault(uint256 _amount) internal virtual;\n}\n" + }, + "contracts/strategies/NativeStaking/ValidatorRegistrator.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { Pausable } from \"@openzeppelin/contracts/security/Pausable.sol\";\nimport { Governable } from \"../../governance/Governable.sol\";\nimport { IDepositContract } from \"../../interfaces/IDepositContract.sol\";\nimport { IVault } from \"../../interfaces/IVault.sol\";\nimport { IWETH9 } from \"../../interfaces/IWETH9.sol\";\nimport { ISSVNetwork, Cluster } from \"../../interfaces/ISSVNetwork.sol\";\n\nstruct ValidatorStakeData {\n bytes pubkey;\n bytes signature;\n bytes32 depositDataRoot;\n}\n\n/**\n * @title Registrator of the validators\n * @notice This contract implements all the required functionality to register, exit and remove validators.\n * @author Origin Protocol Inc\n */\nabstract contract ValidatorRegistrator is Governable, Pausable {\n /// @notice The maximum amount of ETH that can be staked by a validator\n /// @dev this can change in the future with EIP-7251, Increase the MAX_EFFECTIVE_BALANCE\n uint256 public constant FULL_STAKE = 32 ether;\n\n /// @notice The address of the Wrapped ETH (WETH) token contract\n address public immutable WETH;\n /// @notice The address of the beacon chain deposit contract\n address public immutable BEACON_CHAIN_DEPOSIT_CONTRACT;\n /// @notice The address of the SSV Network contract used to interface with\n address public immutable SSV_NETWORK;\n /// @notice Address of the OETH Vault proxy contract\n address public immutable VAULT_ADDRESS;\n /// @notice Maximum number of validators that can be registered in this strategy\n uint256 public immutable MAX_VALIDATORS;\n\n /// @notice Address of the registrator - allowed to register, exit and remove validators\n address public validatorRegistrator;\n /// @notice The number of validators that have 32 (!) ETH actively deposited. When a new deposit\n /// to a validator happens this number increases, when a validator exit is detected this number\n /// decreases.\n uint256 public activeDepositedValidators;\n /// @notice State of the validators keccak256(pubKey) => state\n mapping(bytes32 => VALIDATOR_STATE) public validatorsStates;\n /// @notice The account that is allowed to modify stakeETHThreshold and reset stakeETHTally\n address public stakingMonitor;\n /// @notice Amount of ETH that can be staked before staking on the contract is suspended\n /// and the `stakingMonitor` needs to approve further staking by calling `resetStakeETHTally`\n uint256 public stakeETHThreshold;\n /// @notice Amount of ETH that has been staked since the `stakingMonitor` last called `resetStakeETHTally`.\n /// This can not go above `stakeETHThreshold`.\n uint256 public stakeETHTally;\n // For future use\n uint256[47] private __gap;\n\n enum VALIDATOR_STATE {\n NON_REGISTERED, // validator is not registered on the SSV network\n REGISTERED, // validator is registered on the SSV network\n STAKED, // validator has funds staked\n EXITING, // exit message has been posted and validator is in the process of exiting\n EXIT_COMPLETE // validator has funds withdrawn to the EigenPod and is removed from the SSV\n }\n\n event RegistratorChanged(address indexed newAddress);\n event StakingMonitorChanged(address indexed newAddress);\n event ETHStaked(bytes32 indexed pubKeyHash, bytes pubKey, uint256 amount);\n event SSVValidatorRegistered(\n bytes32 indexed pubKeyHash,\n bytes pubKey,\n uint64[] operatorIds\n );\n event SSVValidatorExitInitiated(\n bytes32 indexed pubKeyHash,\n bytes pubKey,\n uint64[] operatorIds\n );\n event SSVValidatorExitCompleted(\n bytes32 indexed pubKeyHash,\n bytes pubKey,\n uint64[] operatorIds\n );\n event StakeETHThresholdChanged(uint256 amount);\n event StakeETHTallyReset();\n\n /// @dev Throws if called by any account other than the Registrator\n modifier onlyRegistrator() {\n require(\n msg.sender == validatorRegistrator,\n \"Caller is not the Registrator\"\n );\n _;\n }\n\n /// @dev Throws if called by any account other than the Staking monitor\n modifier onlyStakingMonitor() {\n require(msg.sender == stakingMonitor, \"Caller is not the Monitor\");\n _;\n }\n\n /// @dev Throws if called by any account other than the Strategist\n modifier onlyStrategist() {\n require(\n msg.sender == IVault(VAULT_ADDRESS).strategistAddr(),\n \"Caller is not the Strategist\"\n );\n _;\n }\n\n /// @param _wethAddress Address of the Erc20 WETH Token contract\n /// @param _vaultAddress Address of the Vault\n /// @param _beaconChainDepositContract Address of the beacon chain deposit contract\n /// @param _ssvNetwork Address of the SSV Network contract\n /// @param _maxValidators Maximum number of validators that can be registered in the strategy\n constructor(\n address _wethAddress,\n address _vaultAddress,\n address _beaconChainDepositContract,\n address _ssvNetwork,\n uint256 _maxValidators\n ) {\n WETH = _wethAddress;\n BEACON_CHAIN_DEPOSIT_CONTRACT = _beaconChainDepositContract;\n SSV_NETWORK = _ssvNetwork;\n VAULT_ADDRESS = _vaultAddress;\n MAX_VALIDATORS = _maxValidators;\n }\n\n /// @notice Set the address of the registrator which can register, exit and remove validators\n function setRegistrator(address _address) external onlyGovernor {\n validatorRegistrator = _address;\n emit RegistratorChanged(_address);\n }\n\n /// @notice Set the address of the staking monitor that is allowed to reset stakeETHTally\n function setStakingMonitor(address _address) external onlyGovernor {\n stakingMonitor = _address;\n emit StakingMonitorChanged(_address);\n }\n\n /// @notice Set the amount of ETH that can be staked before staking monitor\n // needs to a approve further staking by resetting the stake ETH tally\n function setStakeETHThreshold(uint256 _amount) external onlyGovernor {\n stakeETHThreshold = _amount;\n emit StakeETHThresholdChanged(_amount);\n }\n\n /// @notice Reset the stakeETHTally\n function resetStakeETHTally() external onlyStakingMonitor {\n stakeETHTally = 0;\n emit StakeETHTallyReset();\n }\n\n /// @notice Stakes WETH to the node validators\n /// @param validators A list of validator data needed to stake.\n /// The `ValidatorStakeData` struct contains the pubkey, signature and depositDataRoot.\n /// Only the registrator can call this function.\n // slither-disable-start reentrancy-eth\n function stakeEth(ValidatorStakeData[] calldata validators)\n external\n onlyRegistrator\n whenNotPaused\n nonReentrant\n {\n uint256 requiredETH = validators.length * FULL_STAKE;\n\n // Check there is enough WETH from the deposits sitting in this strategy contract\n require(\n requiredETH <= IWETH9(WETH).balanceOf(address(this)),\n \"Insufficient WETH\"\n );\n require(\n activeDepositedValidators + validators.length <= MAX_VALIDATORS,\n \"Max validators reached\"\n );\n\n require(\n stakeETHTally + requiredETH <= stakeETHThreshold,\n \"Staking ETH over threshold\"\n );\n stakeETHTally += requiredETH;\n\n // Convert required ETH from WETH\n IWETH9(WETH).withdraw(requiredETH);\n _wethWithdrawn(requiredETH);\n\n /* 0x01 to indicate that withdrawal credentials will contain an EOA address that the sweeping function\n * can sweep funds to.\n * bytes11(0) to fill up the required zeros\n * remaining bytes20 are for the address\n */\n bytes memory withdrawalCredentials = abi.encodePacked(\n bytes1(0x01),\n bytes11(0),\n address(this)\n );\n\n // For each validator\n for (uint256 i = 0; i < validators.length; ++i) {\n bytes32 pubKeyHash = keccak256(validators[i].pubkey);\n\n require(\n validatorsStates[pubKeyHash] == VALIDATOR_STATE.REGISTERED,\n \"Validator not registered\"\n );\n\n IDepositContract(BEACON_CHAIN_DEPOSIT_CONTRACT).deposit{\n value: FULL_STAKE\n }(\n validators[i].pubkey,\n withdrawalCredentials,\n validators[i].signature,\n validators[i].depositDataRoot\n );\n\n validatorsStates[pubKeyHash] = VALIDATOR_STATE.STAKED;\n\n emit ETHStaked(pubKeyHash, validators[i].pubkey, FULL_STAKE);\n }\n // save gas by changing this storage variable only once rather each time in the loop.\n activeDepositedValidators += validators.length;\n }\n\n // slither-disable-end reentrancy-eth\n\n /// @notice Registers a new validator in the SSV Cluster.\n /// Only the registrator can call this function.\n /// @param publicKeys The public keys of the validators\n /// @param operatorIds The operator IDs of the SSV Cluster\n /// @param sharesData The shares data for each validator\n /// @param ssvAmount The amount of SSV tokens to be deposited to the SSV cluster\n /// @param cluster The SSV cluster details including the validator count and SSV balance\n // slither-disable-start reentrancy-no-eth\n function registerSsvValidators(\n bytes[] calldata publicKeys,\n uint64[] calldata operatorIds,\n bytes[] calldata sharesData,\n uint256 ssvAmount,\n Cluster calldata cluster\n ) external onlyRegistrator whenNotPaused {\n require(\n publicKeys.length == sharesData.length,\n \"Pubkey sharesData mismatch\"\n );\n // Check each public key has not already been used\n bytes32 pubKeyHash;\n VALIDATOR_STATE currentState;\n for (uint256 i = 0; i < publicKeys.length; ++i) {\n pubKeyHash = keccak256(publicKeys[i]);\n currentState = validatorsStates[pubKeyHash];\n require(\n currentState == VALIDATOR_STATE.NON_REGISTERED,\n \"Validator already registered\"\n );\n\n validatorsStates[pubKeyHash] = VALIDATOR_STATE.REGISTERED;\n\n emit SSVValidatorRegistered(pubKeyHash, publicKeys[i], operatorIds);\n }\n\n ISSVNetwork(SSV_NETWORK).bulkRegisterValidator(\n publicKeys,\n operatorIds,\n sharesData,\n ssvAmount,\n cluster\n );\n }\n\n // slither-disable-end reentrancy-no-eth\n\n /// @notice Exit a validator from the Beacon chain.\n /// The staked ETH will eventually swept to this native staking strategy.\n /// Only the registrator can call this function.\n /// @param publicKey The public key of the validator\n /// @param operatorIds The operator IDs of the SSV Cluster\n // slither-disable-start reentrancy-no-eth\n function exitSsvValidator(\n bytes calldata publicKey,\n uint64[] calldata operatorIds\n ) external onlyRegistrator whenNotPaused {\n bytes32 pubKeyHash = keccak256(publicKey);\n VALIDATOR_STATE currentState = validatorsStates[pubKeyHash];\n require(currentState == VALIDATOR_STATE.STAKED, \"Validator not staked\");\n\n ISSVNetwork(SSV_NETWORK).exitValidator(publicKey, operatorIds);\n\n validatorsStates[pubKeyHash] = VALIDATOR_STATE.EXITING;\n\n emit SSVValidatorExitInitiated(pubKeyHash, publicKey, operatorIds);\n }\n\n // slither-disable-end reentrancy-no-eth\n\n /// @notice Remove a validator from the SSV Cluster.\n /// Make sure `exitSsvValidator` is called before and the validate has exited the Beacon chain.\n /// If removed before the validator has exited the beacon chain will result in the validator being slashed.\n /// Only the registrator can call this function.\n /// @param publicKey The public key of the validator\n /// @param operatorIds The operator IDs of the SSV Cluster\n /// @param cluster The SSV cluster details including the validator count and SSV balance\n // slither-disable-start reentrancy-no-eth\n function removeSsvValidator(\n bytes calldata publicKey,\n uint64[] calldata operatorIds,\n Cluster calldata cluster\n ) external onlyRegistrator whenNotPaused {\n bytes32 pubKeyHash = keccak256(publicKey);\n VALIDATOR_STATE currentState = validatorsStates[pubKeyHash];\n // Can remove SSV validators that were incorrectly registered and can not be deposited to.\n require(\n currentState == VALIDATOR_STATE.EXITING ||\n currentState == VALIDATOR_STATE.REGISTERED,\n \"Validator not regd or exiting\"\n );\n\n ISSVNetwork(SSV_NETWORK).removeValidator(\n publicKey,\n operatorIds,\n cluster\n );\n\n validatorsStates[pubKeyHash] = VALIDATOR_STATE.EXIT_COMPLETE;\n\n emit SSVValidatorExitCompleted(pubKeyHash, publicKey, operatorIds);\n }\n\n // slither-disable-end reentrancy-no-eth\n\n /// @notice Deposits more SSV Tokens to the SSV Network contract which is used to pay the SSV Operators.\n /// @dev A SSV cluster is defined by the SSVOwnerAddress and the set of operatorIds.\n /// uses \"onlyStrategist\" modifier so continuous front-running can't DOS our maintenance service\n /// that tries to top up SSV tokens.\n /// @param operatorIds The operator IDs of the SSV Cluster\n /// @param ssvAmount The amount of SSV tokens to be deposited to the SSV cluster\n /// @param cluster The SSV cluster details including the validator count and SSV balance\n function depositSSV(\n uint64[] memory operatorIds,\n uint256 ssvAmount,\n Cluster memory cluster\n ) external onlyStrategist {\n ISSVNetwork(SSV_NETWORK).deposit(\n address(this),\n operatorIds,\n ssvAmount,\n cluster\n );\n }\n\n /// @notice Withdraws excess SSV Tokens from the SSV Network contract which was used to pay the SSV Operators.\n /// @dev A SSV cluster is defined by the SSVOwnerAddress and the set of operatorIds.\n /// @param operatorIds The operator IDs of the SSV Cluster\n /// @param ssvAmount The amount of SSV tokens to be deposited to the SSV cluster\n /// @param cluster The SSV cluster details including the validator count and SSV balance\n function withdrawSSV(\n uint64[] memory operatorIds,\n uint256 ssvAmount,\n Cluster memory cluster\n ) external onlyGovernor {\n ISSVNetwork(SSV_NETWORK).withdraw(operatorIds, ssvAmount, cluster);\n }\n\n /***************************************\n Abstract\n ****************************************/\n\n /// @dev Called when WETH is withdrawn from the strategy or staked to a validator so\n /// the strategy knows how much WETH it has on deposit.\n /// This is so it can emit the correct amount in the Deposit event in depositAll().\n function _wethWithdrawn(uint256 _amount) internal virtual;\n}\n" + }, + "contracts/strategies/plume/RoosterAMOStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title Rooster AMO strategy\n * @author Origin Protocol Inc\n * @custom:security-contact security@originprotocol.com\n */\nimport { Math as MathRooster } from \"../../../lib/rooster/v2-common/libraries/Math.sol\";\nimport { Math as Math_v5 } from \"../../../lib/rooster/openzeppelin-custom/contracts/utils/math/Math.sol\";\nimport { StableMath } from \"../../utils/StableMath.sol\";\n\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport { SafeCast } from \"@openzeppelin/contracts/utils/math/SafeCast.sol\";\n\nimport { IERC20, InitializableAbstractStrategy } from \"../../utils/InitializableAbstractStrategy.sol\";\nimport { IVault } from \"../../interfaces/IVault.sol\";\nimport { IMaverickV2Pool } from \"../../interfaces/plume/IMaverickV2Pool.sol\";\nimport { IMaverickV2Quoter } from \"../../interfaces/plume/IMaverickV2Quoter.sol\";\nimport { IMaverickV2LiquidityManager } from \"../../interfaces/plume/IMaverickV2LiquidityManager.sol\";\nimport { IMaverickV2PoolLens } from \"../../interfaces/plume/IMaverickV2PoolLens.sol\";\nimport { IMaverickV2Position } from \"../../interfaces/plume/IMaverickV2Position.sol\";\nimport { IVotingDistributor } from \"../../interfaces/plume/IVotingDistributor.sol\";\nimport { IPoolDistributor } from \"../../interfaces/plume/IPoolDistributor.sol\";\n// importing custom version of rooster TickMath because of dependency collision. Maverick uses\n// a newer OpenZepplin Math library with functionality that is not present in 4.4.2 (the one we use)\nimport { TickMath } from \"../../../lib/rooster/v2-common/libraries/TickMath.sol\";\n\ncontract RoosterAMOStrategy is InitializableAbstractStrategy {\n using StableMath for uint256;\n using SafeERC20 for IERC20;\n using SafeCast for uint256;\n\n /***************************************\n Storage slot members\n ****************************************/\n\n /// @notice NFT tokenId of the liquidity position\n ///\n /// @dev starts with value of 1 and can not be 0\n // solhint-disable-next-line max-line-length\n /// https://github.com/rooster-protocol/rooster-contracts/blob/fbfecbc519e4495b12598024a42630b4a8ea4489/v2-common/contracts/base/Nft.sol#L14\n uint256 public tokenId;\n /// @dev Minimum amount of tokens the strategy would be able to withdraw from the pool.\n /// minimum amount of tokens are withdrawn at a 1:1 price\n /// Important: Underlying assets contains only assets that are deposited in the underlying Rooster pool.\n /// WETH or OETH held by this contract is not accounted for in underlying assets\n uint256 public underlyingAssets;\n /// @notice Marks the start of the interval that defines the allowed range of WETH share in\n /// the pre-configured pool's liquidity ticker\n uint256 public allowedWethShareStart;\n /// @notice Marks the end of the interval that defines the allowed range of WETH share in\n /// the pre-configured pool's liquidity ticker\n uint256 public allowedWethShareEnd;\n /// @dev reserved for inheritance\n int256[46] private __reserved;\n\n /***************************************\n Constants, structs and events\n ****************************************/\n\n /// @notice The address of the Wrapped ETH (WETH) token contract\n address public immutable WETH;\n /// @notice The address of the OETH token contract\n address public immutable OETH;\n /// @notice the underlying AMO Maverick (Rooster) pool\n IMaverickV2Pool public immutable mPool;\n /// @notice the Liquidity manager used to add liquidity to the pool\n IMaverickV2LiquidityManager public immutable liquidityManager;\n /// @notice the Maverick V2 poolLens\n ///\n /// @dev only used to provide the pool's current sqrtPrice\n IMaverickV2PoolLens public immutable poolLens;\n /// @notice the Maverick V2 position\n ///\n /// @dev provides details of the NFT LP position and offers functions to\n /// remove the liquidity.\n IMaverickV2Position public immutable maverickPosition;\n /// @notice the Maverick Quoter\n IMaverickV2Quoter public immutable quoter;\n /// @notice the Maverick Voting Distributor\n IVotingDistributor public immutable votingDistributor;\n /// @notice the Maverick Pool Distributor\n IPoolDistributor public immutable poolDistributor;\n\n /// @notice sqrtPriceTickLower\n /// @dev tick lower represents the lower price of OETH priced in WETH. Meaning the pool\n /// offers more than 1 OETH for 1 WETH. In other terms to get 1 OETH the swap needs to offer 0.9999 WETH\n /// this is where purchasing OETH with WETH within the liquidity position is the cheapest.\n ///\n /// _____________________\n /// | | |\n /// | WETH | OETH |\n /// | | |\n /// | | |\n /// --------- * ---- * ---------- * ---------\n /// currentPrice\n /// sqrtPriceHigher-(1:1 parity)\n /// sqrtPriceLower\n ///\n ///\n /// Price is defined as price of token1 in terms of token0. (token1 / token0)\n /// @notice sqrtPriceTickLower - OETH is priced 0.9999 WETH\n uint256 public immutable sqrtPriceTickLower;\n /// @notice sqrtPriceTickHigher\n /// @dev tick higher represents 1:1 price parity of WETH to OETH\n uint256 public immutable sqrtPriceTickHigher;\n /// @dev price at parity (in OETH this is equal to sqrtPriceTickHigher)\n uint256 public immutable sqrtPriceAtParity;\n /// @notice The tick where the strategy deploys the liquidity to\n int32 public constant TICK_NUMBER = -1;\n /// @notice Minimum liquidity that must be exceeded to continue with the action\n /// e.g. deposit, add liquidity\n uint256 public constant ACTION_THRESHOLD = 1e12;\n /// @notice Maverick pool static liquidity bin type\n uint8 public constant MAV_STATIC_BIN_KIND = 0;\n /// @dev a threshold under which the contract no longer allows for the protocol to rebalance. Guarding\n /// against a strategist / guardian being taken over and with multiple transactions draining the\n /// protocol funds.\n uint256 public constant SOLVENCY_THRESHOLD = 0.998 ether;\n /// @notice Emitted when the allowed interval within which the strategy contract is allowed to deposit\n /// liquidity to the underlying pool is updated.\n /// @param allowedWethShareStart The start of the interval\n /// @param allowedWethShareEnd The end of the interval\n event PoolWethShareIntervalUpdated(\n uint256 allowedWethShareStart,\n uint256 allowedWethShareEnd\n );\n /// @notice Emitted when liquidity is removed from the underlying pool\n /// @param withdrawLiquidityShare Share of strategy's liquidity that has been removed\n /// @param removedWETHAmount The amount of WETH removed\n /// @param removedOETHAmount The amount of OETH removed\n /// @param underlyingAssets Updated amount of strategy's underlying assets\n event LiquidityRemoved(\n uint256 withdrawLiquidityShare,\n uint256 removedWETHAmount,\n uint256 removedOETHAmount,\n uint256 underlyingAssets\n );\n\n /// @notice Emitted when the underlying pool is rebalanced\n /// @param currentPoolWethShare The resulting share of strategy's liquidity\n /// in the TICK_NUMBER\n event PoolRebalanced(uint256 currentPoolWethShare);\n\n /// @notice Emitted when the amount of underlying assets the strategy hold as\n /// liquidity in the pool is updated.\n /// @param underlyingAssets Updated amount of strategy's underlying assets\n event UnderlyingAssetsUpdated(uint256 underlyingAssets);\n\n /// @notice Emitted when liquidity is added to the underlying pool\n /// @param wethAmountDesired Amount of WETH desired to be deposited\n /// @param oethAmountDesired Amount of OETH desired to be deposited\n /// @param wethAmountSupplied Amount of WETH deposited\n /// @param oethAmountSupplied Amount of OETH deposited\n /// @param tokenId NFT liquidity token id\n /// @param underlyingAssets Updated amount of underlying assets\n event LiquidityAdded(\n uint256 wethAmountDesired,\n uint256 oethAmountDesired,\n uint256 wethAmountSupplied,\n uint256 oethAmountSupplied,\n uint256 tokenId,\n uint256 underlyingAssets\n ); // 0x1530ec74\n\n error PoolRebalanceOutOfBounds(\n uint256 currentPoolWethShare,\n uint256 allowedWethShareStart,\n uint256 allowedWethShareEnd\n ); // 0x3681e8e0\n\n error NotEnoughWethForSwap(uint256 wethBalance, uint256 requiredWeth); // 0x989e5ca8\n error NotEnoughWethLiquidity(uint256 wethBalance, uint256 requiredWeth); // 0xa6737d87\n error OutsideExpectedTickRange(); // 0xa6e1bad2\n error SlippageCheck(uint256 tokenReceived); // 0x355cdb78\n\n /// @notice the constructor\n /// @dev This contract is intended to be used as a proxy. To prevent the\n /// potential confusion of having a functional implementation contract\n /// the constructor has the `initializer` modifier. This way the\n /// `initialize` function can not be called on the implementation contract.\n /// For the same reason the implementation contract also has the governor\n /// set to a zero address.\n /// @param _stratConfig the basic strategy configuration\n /// @param _wethAddress Address of the Erc20 WETH Token contract\n /// @param _oethAddress Address of the Erc20 OETH Token contract\n /// @param _liquidityManager Address of liquidity manager to add\n /// the liquidity\n /// @param _poolLens Address of the pool lens contract\n /// @param _maverickPosition Address of the Maverick's position contract\n /// @param _maverickQuoter Address of the Maverick's Quoter contract\n /// @param _mPool Address of the Rooster concentrated liquidity pool\n /// @param _upperTickAtParity Bool when true upperTick is the one where the\n /// price of OETH and WETH are at parity\n constructor(\n BaseStrategyConfig memory _stratConfig,\n address _wethAddress,\n address _oethAddress,\n address _liquidityManager,\n address _poolLens,\n address _maverickPosition,\n address _maverickQuoter,\n address _mPool,\n bool _upperTickAtParity,\n address _votingDistributor,\n address _poolDistributor\n ) initializer InitializableAbstractStrategy(_stratConfig) {\n require(\n address(IMaverickV2Pool(_mPool).tokenA()) == _wethAddress,\n \"WETH not TokenA\"\n );\n require(\n address(IMaverickV2Pool(_mPool).tokenB()) == _oethAddress,\n \"OETH not TokenB\"\n );\n require(\n _liquidityManager != address(0),\n \"LiquidityManager zero address not allowed\"\n );\n require(\n _maverickQuoter != address(0),\n \"Quoter zero address not allowed\"\n );\n require(_poolLens != address(0), \"PoolLens zero address not allowed\");\n require(\n _maverickPosition != address(0),\n \"Position zero address not allowed\"\n );\n require(\n _votingDistributor != address(0),\n \"Voting distributor zero address not allowed\"\n );\n require(\n _poolDistributor != address(0),\n \"Pool distributor zero address not allowed\"\n );\n\n uint256 _tickSpacing = IMaverickV2Pool(_mPool).tickSpacing();\n require(_tickSpacing == 1, \"Unsupported tickSpacing\");\n\n // tickSpacing == 1\n (sqrtPriceTickLower, sqrtPriceTickHigher) = TickMath.tickSqrtPrices(\n _tickSpacing,\n TICK_NUMBER\n );\n sqrtPriceAtParity = _upperTickAtParity\n ? sqrtPriceTickHigher\n : sqrtPriceTickLower;\n\n WETH = _wethAddress;\n OETH = _oethAddress;\n liquidityManager = IMaverickV2LiquidityManager(_liquidityManager);\n poolLens = IMaverickV2PoolLens(_poolLens);\n maverickPosition = IMaverickV2Position(_maverickPosition);\n quoter = IMaverickV2Quoter(_maverickQuoter);\n mPool = IMaverickV2Pool(_mPool);\n votingDistributor = IVotingDistributor(_votingDistributor);\n poolDistributor = IPoolDistributor(_poolDistributor);\n\n // prevent implementation contract to be governed\n _setGovernor(address(0));\n }\n\n /**\n * @notice initialize function, to set up initial internal state\n */\n function initialize() external onlyGovernor initializer {\n // Read reward\n address[] memory _rewardTokens = new address[](1);\n _rewardTokens[0] = poolDistributor.rewardToken();\n\n require(_rewardTokens[0] != address(0), \"No reward token configured\");\n\n InitializableAbstractStrategy._initialize(\n _rewardTokens,\n new address[](0),\n new address[](0)\n );\n }\n\n /***************************************\n Configuration \n ****************************************/\n\n /**\n * @notice Set allowed pool weth share interval. After the rebalance happens\n * the share of WETH token in the ticker needs to be within the specifications\n * of the interval.\n *\n * @param _allowedWethShareStart Start of WETH share interval expressed as 18 decimal amount\n * @param _allowedWethShareEnd End of WETH share interval expressed as 18 decimal amount\n */\n function setAllowedPoolWethShareInterval(\n uint256 _allowedWethShareStart,\n uint256 _allowedWethShareEnd\n ) external onlyGovernor {\n require(\n _allowedWethShareStart < _allowedWethShareEnd,\n \"Invalid interval\"\n );\n // can not go below 1% weth share\n require(_allowedWethShareStart > 0.01 ether, \"Invalid interval start\");\n // can not go above 95% weth share\n require(_allowedWethShareEnd < 0.95 ether, \"Invalid interval end\");\n\n allowedWethShareStart = _allowedWethShareStart;\n allowedWethShareEnd = _allowedWethShareEnd;\n emit PoolWethShareIntervalUpdated(\n _allowedWethShareStart,\n _allowedWethShareEnd\n );\n }\n\n /***************************************\n Strategy overrides \n ****************************************/\n\n /**\n * @notice Deposits funds to the strategy which deposits them to the\n * underlying Rooster pool if the pool price is within the expected interval.\n * @param _asset Address for the asset\n * @param _amount Units of asset to deposit\n */\n function deposit(address _asset, uint256 _amount)\n external\n override\n onlyVault\n nonReentrant\n {\n _deposit(_asset, _amount);\n }\n\n /**\n * @notice Deposits all the funds to the strategy which deposits them to the\n * underlying Rooster pool if the pool price is within the expected interval.\n */\n function depositAll() external override onlyVault nonReentrant {\n uint256 _wethBalance = IERC20(WETH).balanceOf(address(this));\n _deposit(WETH, _wethBalance);\n }\n\n /**\n * @dev Deposits funds to the strategy which deposits them to the\n * underlying Rooster pool if the pool price is within the expected interval.\n * Before this function can be called the initial pool position needs to already\n * be minted.\n * @param _asset Address of the asset to deposit\n * @param _amount Amount of assets to deposit\n */\n function _deposit(address _asset, uint256 _amount) internal {\n require(_asset == WETH, \"Unsupported asset\");\n require(_amount > 0, \"Must deposit something\");\n require(tokenId > 0, \"Initial position not minted\");\n emit Deposit(_asset, address(0), _amount);\n\n // if the pool price is not within the expected interval leave the WETH on the contract\n // as to not break the mints - in case it would be configured as a default asset strategy\n (bool _isExpectedRange, ) = _checkForExpectedPoolPrice(false);\n if (_isExpectedRange) {\n // deposit funds into the underlying pool. Because no swap is performed there is no\n // need to remove any of the liquidity beforehand.\n _rebalance(0, false, 0, 0);\n }\n }\n\n /**\n * @notice Withdraw an `amount` of WETH from the platform and\n * send to the `_recipient`.\n * @param _recipient Address to which the asset should be sent\n * @param _asset WETH address\n * @param _amount Amount of WETH to withdraw\n */\n function withdraw(\n address _recipient,\n address _asset,\n uint256 _amount\n ) external override onlyVault nonReentrant {\n require(_asset == WETH, \"Unsupported asset\");\n require(_amount > 0, \"Must withdraw something\");\n require(_recipient == vaultAddress, \"Only withdraw to vault allowed\");\n\n _ensureWETHBalance(_amount);\n\n _withdraw(_recipient, _amount);\n }\n\n /**\n * @notice Withdraw WETH and sends it to the Vault.\n */\n function withdrawAll() external override onlyVault nonReentrant {\n if (tokenId != 0) {\n _removeLiquidity(1e18);\n }\n\n uint256 _balance = IERC20(WETH).balanceOf(address(this));\n if (_balance > 0) {\n _withdraw(vaultAddress, _balance);\n }\n }\n\n function _withdraw(address _recipient, uint256 _amount) internal {\n IERC20(WETH).safeTransfer(_recipient, _amount);\n emit Withdrawal(WETH, address(0), _amount);\n }\n\n /**\n * @dev Retuns bool indicating whether asset is supported by strategy\n * @param _asset Address of the asset\n * @return bool True when the _asset is WETH\n */\n function supportsAsset(address _asset) public view override returns (bool) {\n return _asset == WETH;\n }\n\n /**\n * @dev Approve the spending amounts for the assets\n */\n function _approveTokenAmounts(\n uint256 _wethAllowance,\n uint256 _oethAllowance\n ) internal {\n IERC20(WETH).approve(address(liquidityManager), _wethAllowance);\n IERC20(OETH).approve(address(liquidityManager), _oethAllowance);\n }\n\n /***************************************\n Liquidity management\n ****************************************/\n /**\n * @dev Add liquidity into the pool in the pre-configured WETH to OETH share ratios\n * defined by the allowedPoolWethShareStart|End interval.\n *\n * Normally a PoolLens contract is used to prepare the parameters to add liquidity to the\n * Rooster pools. It has some errors when doing those calculation and for that reason a\n * much more accurate Quoter contract is used. This is possible due to our requirement of\n * adding liquidity only to one tick - PoolLens supports adding liquidity into multiple ticks\n * using different distribution ratios.\n */\n function _addLiquidity() internal {\n uint256 _wethBalance = IERC20(WETH).balanceOf(address(this));\n uint256 _oethBalance = IERC20(OETH).balanceOf(address(this));\n // don't deposit small liquidity amounts\n if (_wethBalance <= ACTION_THRESHOLD) {\n return;\n }\n\n (\n bytes memory packedSqrtPriceBreaks,\n bytes[] memory packedArgs,\n uint256 WETHRequired,\n uint256 OETHRequired\n ) = _getAddLiquidityParams(_wethBalance, 1e30);\n\n if (OETHRequired > _oethBalance) {\n IVault(vaultAddress).mintForStrategy(OETHRequired - _oethBalance);\n }\n\n _approveTokenAmounts(WETHRequired, OETHRequired);\n\n (\n uint256 _wethAmount,\n uint256 _oethAmount,\n uint32[] memory binIds\n ) = liquidityManager.addPositionLiquidityToSenderByTokenIndex(\n mPool,\n 0, // NFT token index\n packedSqrtPriceBreaks,\n packedArgs\n );\n\n require(binIds.length == 1, \"Unexpected binIds length\");\n\n // burn remaining OETH\n _burnOethOnTheContract();\n _updateUnderlyingAssets();\n\n // needs to be called after _updateUnderlyingAssets so the updated amount\n // is reflected in the event\n emit LiquidityAdded(\n _wethBalance, // wethAmountDesired\n OETHRequired, // oethAmountDesired\n _wethAmount, // wethAmountSupplied\n _oethAmount, // oethAmountSupplied\n tokenId, // tokenId\n underlyingAssets\n );\n }\n\n /**\n * @dev The function creates liquidity parameters required to be able to add liquidity to the pool.\n * The function needs to handle the 3 different cases of the way liquidity is added:\n * - only WETH present in the tick\n * - only OETH present in the tick\n * - both tokens present in the tick\n *\n */\n function _getAddLiquidityParams(uint256 _maxWETH, uint256 _maxOETH)\n internal\n returns (\n bytes memory packedSqrtPriceBreaks,\n bytes[] memory packedArgs,\n uint256 WETHRequired,\n uint256 OETHRequired\n )\n {\n IMaverickV2Pool.AddLiquidityParams[]\n memory addParams = new IMaverickV2Pool.AddLiquidityParams[](1);\n int32[] memory ticks = new int32[](1);\n uint128[] memory amounts = new uint128[](1);\n ticks[0] = TICK_NUMBER;\n // arbitrary LP amount\n amounts[0] = 1e24;\n\n // construct value for Quoter with arbitrary LP amount\n IMaverickV2Pool.AddLiquidityParams memory addParam = IMaverickV2Pool\n .AddLiquidityParams({\n kind: MAV_STATIC_BIN_KIND,\n ticks: ticks,\n amounts: amounts\n });\n\n // get the WETH and OETH required to get the proportion of tokens required\n // given the arbitrary liquidity\n (WETHRequired, OETHRequired, ) = quoter.calculateAddLiquidity(\n mPool,\n addParam\n );\n\n /**\n * If either token required is 0 then the tick consists only of the other token. In that\n * case the liquidity calculations need to be done using the non 0 token. By setting the\n * tokenRequired from 0 to 1 the `min` in next step will ignore that (the bigger) value.\n */\n WETHRequired = WETHRequired == 0 ? 1 : WETHRequired;\n OETHRequired = OETHRequired == 0 ? 1 : OETHRequired;\n\n addParam.amounts[0] = Math_v5\n .min(\n ((_maxWETH - 1) * 1e24) / WETHRequired,\n ((_maxOETH - 1) * 1e24) / OETHRequired\n )\n .toUint128();\n\n // update the quotes with the actual amounts\n (WETHRequired, OETHRequired, ) = quoter.calculateAddLiquidity(\n mPool,\n addParam\n );\n\n require(_maxWETH >= WETHRequired, \"More WETH required than specified\");\n require(_maxOETH >= OETHRequired, \"More OETH required than specified\");\n\n // organize values to be used by manager\n addParams[0] = addParam;\n packedArgs = liquidityManager.packAddLiquidityArgsArray(addParams);\n // price can stay 0 if array only has one element\n packedSqrtPriceBreaks = liquidityManager.packUint88Array(\n new uint88[](1)\n );\n }\n\n /**\n * @dev Check that the Rooster pool price is within the expected\n * parameters.\n * This function works whether the strategy contract has liquidity\n * position in the pool or not. The function returns _wethSharePct\n * as a gas optimization measure.\n * @param _throwException when set to true the function throws an exception\n * when pool's price is not within expected range.\n * @return _isExpectedRange Bool expressing price is within expected range\n * @return _wethSharePct Share of WETH owned by this strategy contract in the\n * configured ticker.\n */\n function _checkForExpectedPoolPrice(bool _throwException)\n internal\n view\n returns (bool _isExpectedRange, uint256 _wethSharePct)\n {\n require(\n allowedWethShareStart != 0 && allowedWethShareEnd != 0,\n \"Weth share interval not set\"\n );\n\n uint256 _currentPrice = getPoolSqrtPrice();\n\n /**\n * First check pool price is in expected tick range\n *\n * A revert is issued even though price being equal to the lower bound as that can not\n * be within the approved tick range.\n */\n if (\n _currentPrice <= sqrtPriceTickLower ||\n _currentPrice >= sqrtPriceTickHigher\n ) {\n if (_throwException) {\n revert OutsideExpectedTickRange();\n }\n\n return (false, _currentPrice <= sqrtPriceTickLower ? 0 : 1e18);\n }\n\n // 18 decimal number expressed WETH tick share\n _wethSharePct = _getWethShare(_currentPrice);\n\n if (\n _wethSharePct < allowedWethShareStart ||\n _wethSharePct > allowedWethShareEnd\n ) {\n if (_throwException) {\n revert PoolRebalanceOutOfBounds(\n _wethSharePct,\n allowedWethShareStart,\n allowedWethShareEnd\n );\n }\n return (false, _wethSharePct);\n }\n\n return (true, _wethSharePct);\n }\n\n /**\n * @notice Rebalance the pool to the desired token split and Deposit any WETH on the contract to the\n * underlying rooster pool. Print the required amount of corresponding OETH. After the rebalancing is\n * done burn any potentially remaining OETH tokens still on the strategy contract.\n *\n * This function has a slightly different behaviour depending on the status of the underlying Rooster\n * pool. The function consists of the following 3 steps:\n * 1. withdrawLiquidityOption -> this is a configurable option where either only part of the liquidity\n * necessary for the swap is removed, or all of it. This way the rebalance\n * is able to optimize for volume, for efficiency or anything in between\n * 2. swapToDesiredPosition -> move active trading price in the pool to be able to deposit WETH & OETH\n * tokens with the desired pre-configured ratios\n * 3. addLiquidity -> add liquidity into the pool respecting ratio split configuration\n *\n *\n * Exact _amountToSwap, _swapWeth & _minTokenReceived parameters shall be determined by simulating the\n * transaction off-chain. The strategy checks that after the swap the share of the tokens is in the\n * expected ranges.\n *\n * @param _amountToSwap The amount of the token to swap\n * @param _swapWeth Swap using WETH when true, use OETH when false\n * @param _minTokenReceived Slippage check -> minimum amount of token expected in return\n * @param _liquidityToRemovePct Percentage of liquidity to remove -> the percentage amount of liquidity to\n * remove before performing the swap. 1e18 denominated\n */\n function rebalance(\n uint256 _amountToSwap,\n bool _swapWeth,\n uint256 _minTokenReceived,\n uint256 _liquidityToRemovePct\n ) external nonReentrant onlyGovernorOrStrategist {\n _rebalance(\n _amountToSwap,\n _swapWeth,\n _minTokenReceived,\n _liquidityToRemovePct\n );\n }\n\n // slither-disable-start reentrancy-no-eth\n function _rebalance(\n uint256 _amountToSwap,\n bool _swapWeth,\n uint256 _minTokenReceived,\n uint256 _liquidityToRemovePct\n ) internal {\n // Remove the required amount of liquidity\n if (_liquidityToRemovePct > 0) {\n _removeLiquidity(_liquidityToRemovePct);\n }\n\n // in some cases (e.g. deposits) we will just want to add liquidity and not\n // issue a swap to move the active trading position within the pool. Before or after a\n // deposit or as a standalone call the strategist might issue a rebalance to move the\n // active trading price to a more desired position.\n if (_amountToSwap > 0) {\n // In case liquidity has been removed and there is still not enough WETH owned by the\n // strategy contract remove additional required amount of WETH.\n if (_swapWeth) _ensureWETHBalance(_amountToSwap);\n\n _swapToDesiredPosition(_amountToSwap, _swapWeth, _minTokenReceived);\n }\n\n // calling check liquidity early so we don't get unexpected errors when adding liquidity\n // in the later stages of this function\n _checkForExpectedPoolPrice(true);\n\n _addLiquidity();\n\n // this call shouldn't be necessary, since adding liquidity shouldn't affect the active\n // trading price. It is a defensive programming measure.\n (, uint256 _wethSharePct) = _checkForExpectedPoolPrice(true);\n\n // revert if protocol insolvent\n _solvencyAssert();\n\n emit PoolRebalanced(_wethSharePct);\n }\n\n // slither-disable-end reentrancy-no-eth\n\n /**\n * @dev Perform a swap so that after the swap the tick has the desired WETH to OETH token share.\n */\n function _swapToDesiredPosition(\n uint256 _amountToSwap,\n bool _swapWeth,\n uint256 _minTokenReceived\n ) internal {\n IERC20 _tokenToSwap = IERC20(_swapWeth ? WETH : OETH);\n uint256 _balance = _tokenToSwap.balanceOf(address(this));\n\n if (_balance < _amountToSwap) {\n // This should never trigger since _ensureWETHBalance will already\n // throw an error if there is not enough WETH\n if (_swapWeth) {\n revert NotEnoughWethForSwap(_balance, _amountToSwap);\n }\n // if swapping OETH\n uint256 mintForSwap = _amountToSwap - _balance;\n IVault(vaultAddress).mintForStrategy(mintForSwap);\n }\n\n // SafeERC20 is used for IERC20 transfers. Not sure why slither complains\n // slither-disable-next-line unchecked-transfer\n _tokenToSwap.transfer(address(mPool), _amountToSwap);\n\n // tickLimit: the furthest tick a swap will execute in. If no limit is desired,\n // value should be set to type(int32).max for a tokenAIn (WETH) swap\n // and type(int32).min for a swap where tokenB (OETH) is the input\n\n IMaverickV2Pool.SwapParams memory swapParams = IMaverickV2Pool\n // exactOutput defines whether the amount specified is the output\n // or the input amount of the swap\n .SwapParams({\n amount: _amountToSwap,\n tokenAIn: _swapWeth,\n exactOutput: false,\n tickLimit: TICK_NUMBER\n });\n\n // swaps without a callback as the assets are already sent to the pool\n (, uint256 amountOut) = mPool.swap(\n address(this),\n swapParams,\n bytes(\"\")\n );\n\n /**\n * There could be additional checks here for validating minTokenReceived is within the\n * expected range (e.g. 99% - 101% of the token sent in). Though that doesn't provide\n * any additional security. After the swap the `_checkForExpectedPoolPrice` validates\n * that the swap has moved the price into the expected tick (# -1).\n *\n * If the guardian forgets to set a `_minTokenReceived` and a sandwich attack bends\n * the pool before the swap the `_checkForExpectedPoolPrice` will fail the transaction.\n *\n * A check would not prevent a compromised guardian from stealing funds as multiple\n * transactions each loosing smaller amount of funds are still possible.\n */\n if (amountOut < _minTokenReceived) {\n revert SlippageCheck(amountOut);\n }\n\n /**\n * In the interest of each function in `_rebalance` to leave the contract state as\n * clean as possible the OETH tokens here are burned. This decreases the\n * dependence where `_swapToDesiredPosition` function relies on later functions\n * (`addLiquidity`) to burn the OETH. Reducing the risk of error introduction.\n */\n _burnOethOnTheContract();\n }\n\n /**\n * @dev This function removes the appropriate amount of liquidity to ensure that the required\n * amount of WETH is available on the contract\n *\n * @param _amount WETH balance required on the contract\n */\n function _ensureWETHBalance(uint256 _amount) internal {\n uint256 _wethBalance = IERC20(WETH).balanceOf(address(this));\n if (_wethBalance >= _amount) {\n return;\n }\n\n require(tokenId != 0, \"No liquidity available\");\n uint256 _additionalWethRequired = _amount - _wethBalance;\n (uint256 _wethInThePool, ) = getPositionPrincipal();\n\n if (_wethInThePool < _additionalWethRequired) {\n revert NotEnoughWethLiquidity(\n _wethInThePool,\n _additionalWethRequired\n );\n }\n\n uint256 shareOfWethToRemove = _wethInThePool <= 1\n ? 1e18\n : Math_v5.min(\n /**\n * When dealing with shares of liquidity to remove there is always some\n * rounding involved. After extensive fuzz testing the below approach\n * yielded the best results where the strategy overdraws the least and\n * never removes insufficient amount of WETH.\n */\n (_additionalWethRequired + 2).divPrecisely(_wethInThePool - 1) +\n 2,\n 1e18\n );\n\n _removeLiquidity(shareOfWethToRemove);\n }\n\n /**\n * @dev Decrease partial or all liquidity from the pool.\n * @param _liquidityToDecrease The amount of liquidity to remove denominated in 1e18\n */\n function _removeLiquidity(uint256 _liquidityToDecrease) internal {\n require(_liquidityToDecrease > 0, \"Must remove some liquidity\");\n require(\n _liquidityToDecrease <= 1e18,\n \"Can not remove more than 100% of liquidity\"\n );\n\n // 0 indicates the first (and only) bin in the NFT LP position.\n IMaverickV2Pool.RemoveLiquidityParams memory params = maverickPosition\n .getRemoveParams(tokenId, 0, _liquidityToDecrease);\n (uint256 _amountWeth, uint256 _amountOeth) = maverickPosition\n .removeLiquidityToSender(tokenId, mPool, params);\n\n _burnOethOnTheContract();\n _updateUnderlyingAssets();\n\n // needs to be called after the _updateUnderlyingAssets so the updated amount is reflected\n // in the event\n emit LiquidityRemoved(\n _liquidityToDecrease,\n _amountWeth,\n _amountOeth,\n underlyingAssets\n );\n }\n\n /**\n * @dev Burns any OETH tokens remaining on the strategy contract if the balance is\n * above the action threshold.\n */\n function _burnOethOnTheContract() internal {\n uint256 _oethBalance = IERC20(OETH).balanceOf(address(this));\n IVault(vaultAddress).burnForStrategy(_oethBalance);\n }\n\n /**\n * @notice Returns the percentage of WETH liquidity in the configured ticker\n * owned by this strategy contract.\n * @return uint256 1e18 denominated percentage expressing the share\n */\n function getWETHShare() external view returns (uint256) {\n uint256 _currentPrice = getPoolSqrtPrice();\n return _getWethShare(_currentPrice);\n }\n\n /**\n * @dev Returns the share of WETH in tick denominated in 1e18\n */\n function _getWethShare(uint256 _currentPrice)\n internal\n view\n returns (uint256)\n {\n (\n uint256 wethAmount,\n uint256 oethAmount\n ) = _reservesInTickForGivenPriceAndLiquidity(\n sqrtPriceTickLower,\n sqrtPriceTickHigher,\n _currentPrice,\n 1e24\n );\n\n return wethAmount.divPrecisely(wethAmount + oethAmount);\n }\n\n /**\n * @notice Returns the current pool price in square root\n * @return Square root of the pool price\n */\n function getPoolSqrtPrice() public view returns (uint256) {\n return poolLens.getPoolSqrtPrice(mPool);\n }\n\n /**\n * @notice Returns the current active trading tick of the underlying pool\n * @return _currentTick Current pool trading tick\n */\n function getCurrentTradingTick() public view returns (int32 _currentTick) {\n _currentTick = mPool.getState().activeTick;\n }\n\n /**\n * @notice Mint the initial NFT position\n *\n * @dev This amount is \"gifted\" to the strategy contract and will count as a yield\n * surplus.\n */\n // slither-disable-start reentrancy-no-eth\n function mintInitialPosition() external onlyGovernor nonReentrant {\n require(tokenId == 0, \"Initial position already minted\");\n (\n bytes memory packedSqrtPriceBreaks,\n bytes[] memory packedArgs,\n uint256 WETHRequired,\n uint256 OETHRequired\n ) = _getAddLiquidityParams(1e16, 1e16);\n\n // Mint rounded up OETH amount\n if (OETHRequired > 0) {\n IVault(vaultAddress).mintForStrategy(OETHRequired);\n }\n\n _approveTokenAmounts(WETHRequired, OETHRequired);\n\n // Store the tokenId before calling updateUnderlyingAssets as it relies on the tokenId\n // not being 0\n (, , , tokenId) = liquidityManager.mintPositionNftToSender(\n mPool,\n packedSqrtPriceBreaks,\n packedArgs\n );\n\n // burn remaining OETH\n _burnOethOnTheContract();\n _updateUnderlyingAssets();\n }\n\n // slither-disable-end reentrancy-no-eth\n\n /**\n * @notice Returns the balance of tokens the strategy holds in the LP position\n * @return _amountWeth Amount of WETH in position\n * @return _amountOeth Amount of OETH in position\n */\n function getPositionPrincipal()\n public\n view\n returns (uint256 _amountWeth, uint256 _amountOeth)\n {\n if (tokenId == 0) {\n return (0, 0);\n }\n\n (_amountWeth, _amountOeth, ) = _getPositionInformation();\n }\n\n /**\n * @dev Returns the balance of tokens the strategy holds in the LP position\n * @return _amountWeth Amount of WETH in position\n * @return _amountOeth Amount of OETH in position\n * @return liquidity Amount of liquidity in the position\n */\n function _getPositionInformation()\n internal\n view\n returns (\n uint256 _amountWeth,\n uint256 _amountOeth,\n uint256 liquidity\n )\n {\n IMaverickV2Position.PositionFullInformation\n memory positionInfo = maverickPosition.tokenIdPositionInformation(\n tokenId,\n 0\n );\n\n require(\n positionInfo.liquidities.length == 1,\n \"Unexpected liquidities length\"\n );\n require(positionInfo.ticks.length == 1, \"Unexpected ticks length\");\n\n _amountWeth = positionInfo.amountA;\n _amountOeth = positionInfo.amountB;\n liquidity = positionInfo.liquidities[0];\n }\n\n /**\n * Checks that the protocol is solvent, protecting from a rogue Strategist / Guardian that can\n * keep rebalancing the pool in both directions making the protocol lose a tiny amount of\n * funds each time.\n *\n * Protocol must be at least SOLVENCY_THRESHOLD (99.8%) backed in order for the rebalances to\n * function.\n */\n function _solvencyAssert() internal view {\n uint256 _totalVaultValue = IVault(vaultAddress).totalValue();\n uint256 _totalOethSupply = IERC20(OETH).totalSupply();\n\n if (\n _totalVaultValue.divPrecisely(_totalOethSupply) < SOLVENCY_THRESHOLD\n ) {\n revert(\"Protocol insolvent\");\n }\n }\n\n /**\n * @dev Collect Rooster reward token, and send it to the harvesterAddress\n */\n function collectRewardTokens()\n external\n override\n onlyHarvester\n nonReentrant\n {\n // Do nothing if there's no position minted\n if (tokenId > 0) {\n uint32[] memory binIds = new uint32[](1);\n IMaverickV2Pool.TickState memory tickState = mPool.getTick(\n TICK_NUMBER\n );\n // get the binId for the MAV_STATIC_BIN_KIND in tick TICK_NUMBER (-1)\n binIds[0] = tickState.binIdsByTick[0];\n\n uint256 lastEpoch = votingDistributor.lastEpoch();\n\n poolDistributor.claimLp(\n address(this),\n tokenId,\n mPool,\n binIds,\n lastEpoch\n );\n }\n\n // Run the internal inherited function\n _collectRewardTokens();\n }\n\n /***************************************\n Balances and Fees\n ****************************************/\n\n /**\n * @dev Get the total asset value held in the platform\n * @param _asset Address of the asset\n * @return balance Total value of the asset in the platform\n */\n function checkBalance(address _asset)\n external\n view\n override\n returns (uint256)\n {\n require(_asset == WETH, \"Only WETH supported\");\n\n // because of PoolLens inaccuracy there is usually some dust WETH left on the contract\n uint256 _wethBalance = IERC20(WETH).balanceOf(address(this));\n // just paranoia check, in case there is OETH in the strategy that for some reason hasn't\n // been burned yet. This should always be 0.\n uint256 _oethBalance = IERC20(OETH).balanceOf(address(this));\n return underlyingAssets + _wethBalance + _oethBalance;\n }\n\n /// @dev This function updates the amount of underlying assets with the approach of the least possible\n /// total tokens extracted for the current liquidity in the pool.\n function _updateUnderlyingAssets() internal {\n /**\n * Our net value represent the smallest amount of tokens we are able to extract from the position\n * given our liquidity.\n *\n * The least amount of tokens ex-tractable from the position is where the active trading price is\n * at the edge between tick -1 & tick 0. There the pool is offering 1:1 trades between WETH & OETH.\n * At that moment the pool consists completely of WETH and no OETH.\n *\n * The more swaps from OETH -> WETH happen on the pool the more the price starts to move away from the tick 0\n * towards the middle of tick -1 making OETH (priced in WETH) cheaper.\n */\n\n uint256 _wethAmount = tokenId == 0 ? 0 : _balanceInPosition();\n\n underlyingAssets = _wethAmount;\n emit UnderlyingAssetsUpdated(_wethAmount);\n }\n\n /**\n * @dev Strategy reserves (which consist only of WETH in case of Rooster - Plume pool)\n * when the tick price is closest to parity - assuring the lowest amount of tokens\n * returned for the current position liquidity.\n */\n function _balanceInPosition() internal view returns (uint256 _wethBalance) {\n (, , uint256 liquidity) = _getPositionInformation();\n\n uint256 _oethBalance;\n\n (_wethBalance, _oethBalance) = _reservesInTickForGivenPriceAndLiquidity(\n sqrtPriceTickLower,\n sqrtPriceTickHigher,\n sqrtPriceAtParity,\n liquidity\n );\n\n require(_oethBalance == 0, \"Non zero oethBalance\");\n }\n\n /**\n * @notice Tick dominance denominated in 1e18\n * @return _tickDominance The share of liquidity in TICK_NUMBER tick owned\n * by the strategy contract denominated in 1e18\n */\n function tickDominance() public view returns (uint256 _tickDominance) {\n IMaverickV2Pool.TickState memory tickState = mPool.getTick(TICK_NUMBER);\n\n uint256 wethReserve = tickState.reserveA;\n uint256 oethReserve = tickState.reserveB;\n\n // prettier-ignore\n (uint256 _amountWeth, uint256 _amountOeth, ) = _getPositionInformation();\n\n if (wethReserve + oethReserve == 0) {\n return 0;\n }\n\n _tickDominance = (_amountWeth + _amountOeth).divPrecisely(\n wethReserve + oethReserve\n );\n }\n\n /***************************************\n Hidden functions\n ****************************************/\n /**\n * @dev Unsupported\n */\n function setPTokenAddress(address, address) external pure override {\n // The pool tokens can never change.\n revert(\"Unsupported method\");\n }\n\n /**\n * @dev Unsupported\n */\n function removePToken(uint256) external pure override {\n // The pool tokens can never change.\n revert(\"Unsupported method\");\n }\n\n /**\n * @dev Unsupported\n */\n function _abstractSetPToken(address, address) internal pure override {\n revert(\"Unsupported method\");\n }\n\n /**\n * @dev Unsupported\n */\n function safeApproveAllTokens() external pure override {\n // all the amounts are approved at the time required\n revert(\"Unsupported method\");\n }\n\n /***************************************\n Maverick liquidity utilities\n ****************************************/\n\n /// @notice Calculates deltaA = liquidity * (sqrt(upper) - sqrt(lower))\n /// Calculates deltaB = liquidity / sqrt(lower) - liquidity / sqrt(upper),\n /// i.e. liquidity * (sqrt(upper) - sqrt(lower)) / (sqrt(upper) * sqrt(lower))\n ///\n /// @dev refactored from here:\n // solhint-disable-next-line max-line-length\n /// https://github.com/rooster-protocol/rooster-contracts/blob/main/v2-supplemental/contracts/libraries/LiquidityUtilities.sol#L665-L695\n function _reservesInTickForGivenPriceAndLiquidity(\n uint256 _lowerSqrtPrice,\n uint256 _upperSqrtPrice,\n uint256 _newSqrtPrice,\n uint256 _liquidity\n ) internal pure returns (uint128 reserveA, uint128 reserveB) {\n if (_liquidity == 0) {\n (reserveA, reserveB) = (0, 0);\n } else {\n uint256 lowerEdge = MathRooster.max(_lowerSqrtPrice, _newSqrtPrice);\n\n reserveA = MathRooster\n .mulCeil(\n _liquidity,\n MathRooster.clip(\n MathRooster.min(_upperSqrtPrice, _newSqrtPrice),\n _lowerSqrtPrice\n )\n )\n .toUint128();\n reserveB = MathRooster\n .mulDivCeil(\n _liquidity,\n 1e18 * MathRooster.clip(_upperSqrtPrice, lowerEdge),\n _upperSqrtPrice * lowerEdge\n )\n .toUint128();\n }\n }\n}\n" + }, + "contracts/strategies/sonic/SonicStakingStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { Math } from \"@openzeppelin/contracts/utils/math/Math.sol\";\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport { SonicValidatorDelegator } from \"./SonicValidatorDelegator.sol\";\nimport { IWrappedSonic } from \"../../interfaces/sonic/IWrappedSonic.sol\";\n\n/**\n * @title Staking Strategy for Sonic's native S currency\n * @author Origin Protocol Inc\n */\ncontract SonicStakingStrategy is SonicValidatorDelegator {\n // For future use\n uint256[50] private __gap;\n\n constructor(\n BaseStrategyConfig memory _baseConfig,\n address _wrappedSonic,\n address _sfc\n ) SonicValidatorDelegator(_baseConfig, _wrappedSonic, _sfc) {}\n\n /// @notice Deposit wrapped S asset into the underlying platform.\n /// @param _asset Address of asset to deposit. Has to be Wrapped Sonic (wS).\n /// @param _amount Amount of assets that were transferred to the strategy by the vault.\n function deposit(address _asset, uint256 _amount)\n external\n override\n onlyVault\n nonReentrant\n {\n require(_asset == wrappedSonic, \"Unsupported asset\");\n _deposit(_asset, _amount);\n }\n\n /**\n * @notice Deposit Wrapped Sonic (wS) to this strategy and delegate to a validator.\n * @param _asset Address of Wrapped Sonic (wS) token\n * @param _amount Amount of Wrapped Sonic (wS) to deposit\n */\n function _deposit(address _asset, uint256 _amount) internal virtual {\n require(_amount > 0, \"Must deposit something\");\n\n _delegate(_amount);\n emit Deposit(_asset, address(0), _amount);\n }\n\n /**\n * @notice Deposit the entire balance of wrapped S in this strategy contract into\n * the underlying platform.\n */\n function depositAll() external virtual override onlyVault nonReentrant {\n uint256 wSBalance = IERC20(wrappedSonic).balanceOf(address(this));\n\n if (wSBalance > 0) {\n _deposit(wrappedSonic, wSBalance);\n }\n }\n\n /// @notice Withdraw Wrapped Sonic (wS) from this strategy contract.\n /// Used only if some wS is lingering on the contract.\n /// That can happen only when someone sends wS directly to this contract\n /// @param _recipient Address to receive withdrawn assets\n /// @param _asset Address of the Wrapped Sonic (wS) token\n /// @param _amount Amount of Wrapped Sonic (wS) to withdraw\n function withdraw(\n address _recipient,\n address _asset,\n uint256 _amount\n ) external override onlyVault nonReentrant {\n require(_asset == wrappedSonic, \"Unsupported asset\");\n _withdraw(_recipient, _asset, _amount);\n }\n\n function _withdraw(\n address _recipient,\n address _asset,\n uint256 _amount\n ) internal override {\n require(_amount > 0, \"Must withdraw something\");\n require(_recipient != address(0), \"Must specify recipient\");\n\n // slither-disable-next-line unchecked-transfer unused-return\n IERC20(_asset).transfer(_recipient, _amount);\n\n emit Withdrawal(wrappedSonic, address(0), _amount);\n }\n\n /// @notice Transfer all Wrapped Sonic (wS) deposits back to the vault.\n /// This does not withdraw from delegated validators. That has to be done separately with `undelegate`.\n /// Any native S in this strategy will be withdrawn.\n function withdrawAll() external override onlyVaultOrGovernor nonReentrant {\n uint256 balance = address(this).balance;\n if (balance > 0) {\n IWrappedSonic(wrappedSonic).deposit{ value: balance }();\n }\n uint256 wSBalance = IERC20(wrappedSonic).balanceOf(address(this));\n if (wSBalance > 0) {\n _withdraw(vaultAddress, wrappedSonic, wSBalance);\n }\n }\n\n /**\n * @dev Returns bool indicating whether asset is supported by strategy\n * @param _asset Address of the asset token\n */\n function supportsAsset(address _asset)\n public\n view\n virtual\n override\n returns (bool)\n {\n return _asset == wrappedSonic;\n }\n\n /**\n * @notice is not supported for this strategy as the\n * Wrapped Sonic (wS) token is set at deploy time.\n */\n function setPTokenAddress(address, address)\n external\n view\n override\n onlyGovernor\n {\n revert(\"unsupported function\");\n }\n\n /// @notice is not used by this strategy as all staking rewards are restaked\n function collectRewardTokens() external override nonReentrant {\n revert(\"unsupported function\");\n }\n\n /**\n * @notice is not supported for this strategy as the\n * Wrapped Sonic (wS) token is set at deploy time.\n */\n function removePToken(uint256) external view override onlyGovernor {\n revert(\"unsupported function\");\n }\n\n /// @dev is not used by this strategy but must be implemented as it's abstract\n /// in the inherited `InitializableAbstractStrategy` contract.\n function _abstractSetPToken(address, address) internal virtual override {}\n\n /// @notice is not used by this strategy\n function safeApproveAllTokens() external override onlyGovernor {}\n}\n" + }, + "contracts/strategies/sonic/SonicSwapXAMOStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title SwapX Algorithmic Market Maker (AMO) Strategy\n * @notice AMO strategy for the SwapX OS/wS stable pool\n * @author Origin Protocol Inc\n */\nimport { SafeCast } from \"@openzeppelin/contracts/utils/math/SafeCast.sol\";\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\n\nimport { IERC20, InitializableAbstractStrategy } from \"../../utils/InitializableAbstractStrategy.sol\";\nimport { StableMath } from \"../../utils/StableMath.sol\";\nimport { sqrt } from \"../../utils/PRBMath.sol\";\nimport { IBasicToken } from \"../../interfaces/IBasicToken.sol\";\nimport { IPair } from \"../../interfaces/sonic/ISwapXPair.sol\";\nimport { IGauge } from \"../../interfaces/sonic/ISwapXGauge.sol\";\nimport { IVault } from \"../../interfaces/IVault.sol\";\n\ncontract SonicSwapXAMOStrategy is InitializableAbstractStrategy {\n using SafeERC20 for IERC20;\n using StableMath for uint256;\n using SafeCast for uint256;\n\n /**\n * @notice a threshold under which the contract no longer allows for the protocol to manually rebalance.\n * Guarding against a strategist / guardian being taken over and with multiple transactions\n * draining the protocol funds.\n */\n uint256 public constant SOLVENCY_THRESHOLD = 0.998 ether;\n\n /// @notice Precision for the SwapX Stable AMM (sAMM) invariant k.\n uint256 public constant PRECISION = 1e18;\n\n /// @notice Address of the Wrapped S (wS) token.\n address public immutable ws;\n\n /// @notice Address of the OS token contract.\n address public immutable os;\n\n /// @notice Address of the SwapX Stable pool contract.\n address public immutable pool;\n\n /// @notice Address of the SwapX Gauge contract.\n address public immutable gauge;\n\n /// @notice The max amount the OS/wS price can deviate from peg (1e18)\n /// before deposits are reverted scaled to 18 decimals.\n /// eg 0.01e18 or 1e16 is 1% which is 100 basis points.\n /// This is the amount below and above peg so a 50 basis point deviation (0.005e18)\n /// allows a price range from 0.995 to 1.005.\n uint256 public maxDepeg;\n\n event SwapOTokensToPool(\n uint256 osMinted,\n uint256 wsDepositAmount,\n uint256 osDepositAmount,\n uint256 lpTokens\n );\n event SwapAssetsToPool(\n uint256 wsSwapped,\n uint256 lpTokens,\n uint256 osBurnt\n );\n event MaxDepegUpdated(uint256 maxDepeg);\n\n /**\n * @dev Verifies that the caller is the Strategist of the Vault.\n */\n modifier onlyStrategist() {\n require(\n msg.sender == IVault(vaultAddress).strategistAddr(),\n \"Caller is not the Strategist\"\n );\n _;\n }\n\n /**\n * @dev Skim the SwapX pool in case any extra wS or OS tokens were added\n */\n modifier skimPool() {\n IPair(pool).skim(address(this));\n _;\n }\n\n /**\n * @dev Checks the pool is balanced enough to allow deposits.\n */\n modifier nearBalancedPool() {\n // OS/wS price = wS / OS\n // Get the OS/wS price for selling 1 OS for wS\n // As OS is 1, the wS amount is the OS/wS price\n uint256 sellPrice = IPair(pool).getAmountOut(1e18, os);\n\n // Get the amount of OS received from selling 1 wS. This is buying OS.\n uint256 osAmount = IPair(pool).getAmountOut(1e18, ws);\n // Convert to a OS/wS price = wS / OS\n uint256 buyPrice = 1e36 / osAmount;\n\n uint256 pegPrice = 1e18;\n\n require(\n sellPrice >= pegPrice - maxDepeg && buyPrice <= pegPrice + maxDepeg,\n \"price out of range\"\n );\n _;\n }\n\n /**\n * @dev Checks the pool's balances have improved and the balances\n * have not tipped to the other side.\n * This modifier is only applied to functions that do swaps against the pool.\n * Deposits and withdrawals are proportional to the pool's balances hence don't need this check.\n */\n modifier improvePoolBalance() {\n // Get the asset and OToken balances in the pool\n (uint256 wsReservesBefore, uint256 osReservesBefore, ) = IPair(pool)\n .getReserves();\n // diff = wS balance - OS balance\n int256 diffBefore = wsReservesBefore.toInt256() -\n osReservesBefore.toInt256();\n\n _;\n\n // Get the asset and OToken balances in the pool\n (uint256 wsReservesAfter, uint256 osReservesAfter, ) = IPair(pool)\n .getReserves();\n // diff = wS balance - OS balance\n int256 diffAfter = wsReservesAfter.toInt256() -\n osReservesAfter.toInt256();\n\n if (diffBefore == 0) {\n require(diffAfter == 0, \"Position balance is worsened\");\n } else if (diffBefore < 0) {\n // If the pool was originally imbalanced in favor of OS, then\n // we want to check that the pool is now more balanced\n require(diffAfter <= 0, \"Assets overshot peg\");\n require(diffBefore < diffAfter, \"OTokens balance worse\");\n } else if (diffBefore > 0) {\n // If the pool was originally imbalanced in favor of wS, then\n // we want to check that the pool is now more balanced\n require(diffAfter >= 0, \"OTokens overshot peg\");\n require(diffAfter < diffBefore, \"Assets balance worse\");\n }\n }\n\n /**\n * @param _baseConfig The `platformAddress` is the address of the SwapX pool.\n * The `vaultAddress` is the address of the Origin Sonic Vault.\n * @param _os Address of the OS token.\n * @param _ws Address of the Wrapped S (wS) token.\n * @param _gauge Address of the SwapX gauge for the pool.\n */\n constructor(\n BaseStrategyConfig memory _baseConfig,\n address _os,\n address _ws,\n address _gauge\n ) InitializableAbstractStrategy(_baseConfig) {\n // Check the pool tokens are correct\n require(\n IPair(_baseConfig.platformAddress).token0() == _ws &&\n IPair(_baseConfig.platformAddress).token1() == _os,\n \"Incorrect pool tokens\"\n );\n // Checked both tokens are to 18 decimals\n require(\n IBasicToken(_ws).decimals() == 18 &&\n IBasicToken(_os).decimals() == 18,\n \"Incorrect token decimals\"\n );\n // Check the SwapX pool is a Stable AMM (sAMM)\n require(\n IPair(_baseConfig.platformAddress).isStable() == true,\n \"Pool not stable\"\n );\n // Check the gauge is for the pool\n require(\n IGauge(_gauge).TOKEN() == _baseConfig.platformAddress,\n \"Incorrect gauge\"\n );\n\n // Set the immutable variables\n os = _os;\n ws = _ws;\n pool = _baseConfig.platformAddress;\n gauge = _gauge;\n\n // This is an implementation contract. The governor is set in the proxy contract.\n _setGovernor(address(0));\n }\n\n /**\n * Initializer for setting up strategy internal state. This overrides the\n * InitializableAbstractStrategy initializer as SwapX strategies don't fit\n * well within that abstraction.\n * @param _rewardTokenAddresses Array containing SWPx token address\n * @param _maxDepeg The max amount the OS/wS price can deviate from peg (1e18) before deposits are reverted.\n */\n function initialize(\n address[] calldata _rewardTokenAddresses,\n uint256 _maxDepeg\n ) external onlyGovernor initializer {\n address[] memory pTokens = new address[](1);\n pTokens[0] = pool;\n\n address[] memory _assets = new address[](1);\n _assets[0] = ws;\n\n InitializableAbstractStrategy._initialize(\n _rewardTokenAddresses,\n _assets,\n pTokens\n );\n\n maxDepeg = _maxDepeg;\n\n _approveBase();\n }\n\n /***************************************\n Deposit\n ****************************************/\n\n /**\n * @notice Deposit an amount of Wrapped S (wS) into the SwapX pool.\n * Mint OS in proportion to the pool's wS and OS reserves,\n * transfer Wrapped S (wS) and OS to the pool,\n * mint the pool's LP token and deposit in the gauge.\n * @dev This tx must be wrapped by the VaultValueChecker.\n * To minimize loses, the pool should be rebalanced before depositing.\n * The pool's OS/wS price must be within the maxDepeg range.\n * @param _asset Address of Wrapped S (wS) token.\n * @param _wsAmount Amount of Wrapped S (wS) tokens to deposit.\n */\n function deposit(address _asset, uint256 _wsAmount)\n external\n override\n onlyVault\n nonReentrant\n skimPool\n nearBalancedPool\n {\n require(_asset == ws, \"Unsupported asset\");\n require(_wsAmount > 0, \"Must deposit something\");\n\n (uint256 osDepositAmount, ) = _deposit(_wsAmount);\n\n // Ensure solvency of the vault\n _solvencyAssert();\n\n // Emit event for the deposited wS tokens\n emit Deposit(ws, pool, _wsAmount);\n // Emit event for the minted OS tokens\n emit Deposit(os, pool, osDepositAmount);\n }\n\n /**\n * @notice Deposit all the strategy's Wrapped S (wS) tokens into the SwapX pool.\n * Mint OS in proportion to the pool's wS and OS reserves,\n * transfer Wrapped S (wS) and OS to the pool,\n * mint the pool's LP token and deposit in the gauge.\n * @dev This tx must be wrapped by the VaultValueChecker.\n * To minimize loses, the pool should be rebalanced before depositing.\n * The pool's OS/wS price must be within the maxDepeg range.\n */\n function depositAll()\n external\n override\n onlyVault\n nonReentrant\n skimPool\n nearBalancedPool\n {\n uint256 wsBalance = IERC20(ws).balanceOf(address(this));\n if (wsBalance > 0) {\n (uint256 osDepositAmount, ) = _deposit(wsBalance);\n\n // Ensure solvency of the vault\n _solvencyAssert();\n\n // Emit event for the deposited wS tokens\n emit Deposit(ws, pool, wsBalance);\n // Emit event for the minted OS tokens\n emit Deposit(os, pool, osDepositAmount);\n }\n }\n\n /**\n * @dev Mint OS in proportion to the pool's wS and OS reserves,\n * transfer Wrapped S (wS) and OS to the pool,\n * mint the pool's LP token and deposit in the gauge.\n * @param _wsAmount Amount of Wrapped S (wS) tokens to deposit.\n * @return osDepositAmount Amount of OS tokens minted and deposited into the pool.\n * @return lpTokens Amount of SwapX pool LP tokens minted and deposited into the gauge.\n */\n function _deposit(uint256 _wsAmount)\n internal\n returns (uint256 osDepositAmount, uint256 lpTokens)\n {\n // Calculate the required amount of OS to mint based on the wS amount.\n osDepositAmount = _calcTokensToMint(_wsAmount);\n\n // Mint the required OS tokens to this strategy\n IVault(vaultAddress).mintForStrategy(osDepositAmount);\n\n // Add wS and OS liquidity to the pool and stake in gauge\n lpTokens = _depositToPoolAndGauge(_wsAmount, osDepositAmount);\n }\n\n /***************************************\n Withdraw\n ****************************************/\n\n /**\n * @notice Withdraw wS and OS from the SwapX pool, burn the OS,\n * and transfer the wS to the recipient.\n * @param _recipient Address of the Vault.\n * @param _asset Address of the Wrapped S (wS) contract.\n * @param _wsAmount Amount of Wrapped S (wS) to withdraw.\n */\n function withdraw(\n address _recipient,\n address _asset,\n uint256 _wsAmount\n ) external override onlyVault nonReentrant skimPool {\n require(_wsAmount > 0, \"Must withdraw something\");\n require(_asset == ws, \"Unsupported asset\");\n // This strategy can't be set as a default strategy for wS in the Vault.\n // This means the recipient must always be the Vault.\n require(_recipient == vaultAddress, \"Only withdraw to vault allowed\");\n\n // Calculate how much pool LP tokens to burn to get the required amount of wS tokens back\n uint256 lpTokens = _calcTokensToBurn(_wsAmount);\n\n // Withdraw pool LP tokens from the gauge and remove assets from from the pool\n _withdrawFromGaugeAndPool(lpTokens);\n\n // Burn all the removed OS and any that was left in the strategy\n uint256 osToBurn = IERC20(os).balanceOf(address(this));\n IVault(vaultAddress).burnForStrategy(osToBurn);\n\n // Transfer wS to the recipient\n // Note there can be a dust amount of wS left in the strategy as\n // the burn of the pool's LP tokens is rounded up\n require(\n IERC20(ws).balanceOf(address(this)) >= _wsAmount,\n \"Not enough wS removed from pool\"\n );\n IERC20(ws).safeTransfer(_recipient, _wsAmount);\n\n // Ensure solvency of the vault\n _solvencyAssert();\n\n // Emit event for the withdrawn wS tokens\n emit Withdrawal(ws, pool, _wsAmount);\n // Emit event for the burnt OS tokens\n emit Withdrawal(os, pool, osToBurn);\n }\n\n /**\n * @notice Withdraw all pool LP tokens from the gauge,\n * remove all wS and OS from the SwapX pool,\n * burn all the OS tokens,\n * and transfer all the wS to the Vault contract.\n * @dev There is no solvency check here as withdrawAll can be called to\n * quickly secure assets to the Vault in emergencies.\n */\n function withdrawAll()\n external\n override\n onlyVaultOrGovernor\n nonReentrant\n skimPool\n {\n // Get all the pool LP tokens the strategy has staked in the gauge\n uint256 lpTokens = IGauge(gauge).balanceOf(address(this));\n // Can not withdraw zero LP tokens from the gauge\n if (lpTokens == 0) return;\n\n if (IGauge(gauge).emergency()) {\n // The gauge is in emergency mode\n _emergencyWithdrawFromGaugeAndPool();\n } else {\n // Withdraw pool LP tokens from the gauge and remove assets from from the pool\n _withdrawFromGaugeAndPool(lpTokens);\n }\n\n // Burn all OS in this strategy contract\n uint256 osToBurn = IERC20(os).balanceOf(address(this));\n IVault(vaultAddress).burnForStrategy(osToBurn);\n\n // Get the strategy contract's wS balance.\n // This includes all that was removed from the SwapX pool and\n // any that was sitting in the strategy contract before the removal.\n uint256 wsBalance = IERC20(ws).balanceOf(address(this));\n IERC20(ws).safeTransfer(vaultAddress, wsBalance);\n\n // Emit event for the withdrawn wS tokens\n emit Withdrawal(ws, pool, wsBalance);\n // Emit event for the burnt OS tokens\n emit Withdrawal(os, pool, osToBurn);\n }\n\n /***************************************\n Pool Rebalancing\n ****************************************/\n\n /** @notice Used when there is more OS than wS in the pool.\n * wS and OS is removed from the pool, the received wS is swapped for OS\n * and the left over OS in the strategy is burnt.\n * The OS/wS price is < 1.0 so OS is being bought at a discount.\n * @param _wsAmount Amount of Wrapped S (wS) to swap into the pool.\n */\n function swapAssetsToPool(uint256 _wsAmount)\n external\n onlyStrategist\n nonReentrant\n improvePoolBalance\n skimPool\n {\n require(_wsAmount > 0, \"Must swap something\");\n\n // 1. Partially remove liquidity so there’s enough wS for the swap\n\n // Calculate how much pool LP tokens to burn to get the required amount of wS tokens back\n uint256 lpTokens = _calcTokensToBurn(_wsAmount);\n require(lpTokens > 0, \"No LP tokens to burn\");\n\n _withdrawFromGaugeAndPool(lpTokens);\n\n // 2. Swap wS for OS against the pool\n // Swap exact amount of wS for OS against the pool\n // There can be a dust amount of wS left in the strategy as the burn of the pool's LP tokens is rounded up\n _swapExactTokensForTokens(_wsAmount, ws, os);\n\n // 3. Burn all the OS left in the strategy from the remove liquidity and swap\n uint256 osToBurn = IERC20(os).balanceOf(address(this));\n IVault(vaultAddress).burnForStrategy(osToBurn);\n\n // Ensure solvency of the vault\n _solvencyAssert();\n\n // Emit event for the burnt OS tokens\n emit Withdrawal(os, pool, osToBurn);\n // Emit event for the swap\n emit SwapAssetsToPool(_wsAmount, lpTokens, osToBurn);\n }\n\n /**\n * @notice Used when there is more wS than OS in the pool.\n * OS is minted and swapped for wS against the pool,\n * more OS is minted and added back into the pool with the swapped out wS.\n * The OS/wS price is > 1.0 so OS is being sold at a premium.\n * @param _osAmount Amount of OS to swap into the pool.\n */\n function swapOTokensToPool(uint256 _osAmount)\n external\n onlyStrategist\n nonReentrant\n improvePoolBalance\n skimPool\n {\n require(_osAmount > 0, \"Must swap something\");\n\n // 1. Mint OS so it can be swapped into the pool\n\n // There can be OS in the strategy from skimming the pool\n uint256 osInStrategy = IERC20(os).balanceOf(address(this));\n require(_osAmount >= osInStrategy, \"Too much OS in strategy\");\n uint256 osToMint = _osAmount - osInStrategy;\n\n // Mint the required OS tokens to this strategy\n IVault(vaultAddress).mintForStrategy(osToMint);\n\n // 2. Swap OS for wS against the pool\n _swapExactTokensForTokens(_osAmount, os, ws);\n\n // The wS is from the swap and any wS that was sitting in the strategy\n uint256 wsDepositAmount = IERC20(ws).balanceOf(address(this));\n\n // 3. Add wS and OS back to the pool in proportion to the pool's reserves\n (uint256 osDepositAmount, uint256 lpTokens) = _deposit(wsDepositAmount);\n\n // Ensure solvency of the vault\n _solvencyAssert();\n\n // Emit event for the minted OS tokens\n emit Deposit(os, pool, osToMint + osDepositAmount);\n // Emit event for the swap\n emit SwapOTokensToPool(\n osToMint,\n wsDepositAmount,\n osDepositAmount,\n lpTokens\n );\n }\n\n /***************************************\n Assets and Rewards\n ****************************************/\n\n /**\n * @notice Get the wS value of assets in the strategy and SwapX pool.\n * The value of the assets in the pool is calculated assuming the pool is balanced.\n * This way the value can not be manipulated by changing the pool's token balances.\n * @param _asset Address of the Wrapped S (wS) token\n * @return balance Total value in wS.\n */\n function checkBalance(address _asset)\n external\n view\n override\n returns (uint256 balance)\n {\n require(_asset == ws, \"Unsupported asset\");\n\n // wS balance needed here for the balance check that happens from vault during depositing.\n balance = IERC20(ws).balanceOf(address(this));\n\n // This assumes 1 gauge LP token = 1 pool LP token\n uint256 lpTokens = IGauge(gauge).balanceOf(address(this));\n if (lpTokens == 0) return balance;\n\n // Add the strategy’s share of the wS and OS tokens in the SwapX pool if the pool was balanced.\n balance += _lpValue(lpTokens);\n }\n\n /**\n * @notice Returns bool indicating whether asset is supported by strategy\n * @param _asset Address of the asset\n */\n function supportsAsset(address _asset) public view override returns (bool) {\n return _asset == ws;\n }\n\n /**\n * @notice Collect accumulated SWPx (and other) rewards and send to the Harvester.\n */\n function collectRewardTokens()\n external\n override\n onlyHarvester\n nonReentrant\n {\n // Collect SWPx rewards from the gauge\n IGauge(gauge).getReward();\n\n _collectRewardTokens();\n }\n\n /***************************************\n Internal SwapX Pool and Gauge Functions\n ****************************************/\n\n /**\n * @dev Calculate the required amount of OS to mint based on the wS amount.\n * This ensures the proportion of OS tokens being added to the pool matches the proportion of wS tokens.\n * For example, if the added wS tokens is 10% of existing wS tokens in the pool,\n * then the OS tokens being added should also be 10% of the OS tokens in the pool.\n * @param _wsAmount Amount of Wrapped S (wS) to be added to the pool.\n * @return osAmount Amount of OS to be minted and added to the pool.\n */\n function _calcTokensToMint(uint256 _wsAmount)\n internal\n view\n returns (uint256 osAmount)\n {\n (uint256 wsReserves, uint256 osReserves, ) = IPair(pool).getReserves();\n require(wsReserves > 0, \"Empty pool\");\n\n // OS to add = (wS being added * OS in pool) / wS in pool\n osAmount = (_wsAmount * osReserves) / wsReserves;\n }\n\n /**\n * @dev Calculate how much pool LP tokens to burn to get the required amount of wS tokens back\n * from the pool.\n * @param _wsAmount Amount of Wrapped S (wS) to be removed from the pool.\n * @return lpTokens Amount of SwapX pool LP tokens to burn.\n */\n function _calcTokensToBurn(uint256 _wsAmount)\n internal\n view\n returns (uint256 lpTokens)\n {\n /* The SwapX pool proportionally returns the reserve tokens when removing liquidity.\n * First, calculate the proportion of required wS tokens against the pools wS reserves.\n * That same proportion is used to calculate the required amount of pool LP tokens.\n * For example, if the required wS tokens is 10% of the pool's wS reserves,\n * then 10% of the pool's LP supply needs to be burned.\n *\n * Because we are doing balanced removal we should be making profit when removing liquidity in a\n * pool tilted to either side.\n *\n * Important: A downside is that the Strategist / Governor needs to be\n * cognizant of not removing too much liquidity. And while the proposal to remove liquidity\n * is being voted on, the pool tilt might change so much that the proposal that has been valid while\n * created is no longer valid.\n */\n\n (uint256 wsReserves, , ) = IPair(pool).getReserves();\n require(wsReserves > 0, \"Empty pool\");\n\n lpTokens = (_wsAmount * IPair(pool).totalSupply()) / wsReserves;\n lpTokens += 1; // Add 1 to ensure we get enough LP tokens with rounding\n }\n\n /**\n * @dev Deposit Wrapped S (wS) and OS liquidity to the SwapX pool\n * and stake the pool's LP token in the gauge.\n * @param _wsAmount Amount of Wrapped S (wS) to deposit.\n * @param _osAmount Amount of OS to deposit.\n * @return lpTokens Amount of SwapX pool LP tokens minted.\n */\n function _depositToPoolAndGauge(uint256 _wsAmount, uint256 _osAmount)\n internal\n returns (uint256 lpTokens)\n {\n // Transfer wS to the pool\n IERC20(ws).safeTransfer(pool, _wsAmount);\n // Transfer OS to the pool\n IERC20(os).safeTransfer(pool, _osAmount);\n\n // Mint LP tokens from the pool\n lpTokens = IPair(pool).mint(address(this));\n\n // Deposit the pool's LP tokens into the gauge\n IGauge(gauge).deposit(lpTokens);\n }\n\n /**\n * @dev Withdraw pool LP tokens from the gauge and remove wS and OS from the pool.\n * @param _lpTokens Amount of SwapX pool LP tokens to withdraw from the gauge\n */\n function _withdrawFromGaugeAndPool(uint256 _lpTokens) internal {\n require(\n IGauge(gauge).balanceOf(address(this)) >= _lpTokens,\n \"Not enough LP tokens in gauge\"\n );\n\n // Withdraw pool LP tokens from the gauge\n IGauge(gauge).withdraw(_lpTokens);\n\n // Transfer the pool LP tokens to the pool\n IERC20(pool).safeTransfer(pool, _lpTokens);\n\n // Burn the LP tokens and transfer the wS and OS back to the strategy\n IPair(pool).burn(address(this));\n }\n\n /**\n * @dev Withdraw all pool LP tokens from the gauge when it's in emergency mode\n * and remove wS and OS from the pool.\n */\n function _emergencyWithdrawFromGaugeAndPool() internal {\n // Withdraw all pool LP tokens from the gauge\n IGauge(gauge).emergencyWithdraw();\n\n // Get the pool LP tokens in strategy\n uint256 _lpTokens = IERC20(pool).balanceOf(address(this));\n\n // Transfer the pool LP tokens to the pool\n IERC20(pool).safeTransfer(pool, _lpTokens);\n\n // Burn the LP tokens and transfer the wS and OS back to the strategy\n IPair(pool).burn(address(this));\n }\n\n /**\n * @dev Swap exact amount of tokens for another token against the pool.\n * @param _amountIn Amount of tokens to swap into the pool.\n * @param _tokenIn Address of the token going into the pool.\n * @param _tokenOut Address of the token being swapped out of the pool.\n */\n function _swapExactTokensForTokens(\n uint256 _amountIn,\n address _tokenIn,\n address _tokenOut\n ) internal {\n // Transfer in tokens to the pool\n IERC20(_tokenIn).safeTransfer(pool, _amountIn);\n\n // Calculate how much out tokens we get from the swap\n uint256 amountOut = IPair(pool).getAmountOut(_amountIn, _tokenIn);\n\n // Safety check that we are dealing with the correct pool tokens\n require(\n (_tokenIn == ws && _tokenOut == os) ||\n (_tokenIn == os && _tokenOut == ws),\n \"Unsupported swap\"\n );\n\n // Work out the correct order of the amounts for the pool\n (uint256 amount0, uint256 amount1) = _tokenIn == ws\n ? (uint256(0), amountOut)\n : (amountOut, 0);\n\n // Perform the swap on the pool\n IPair(pool).swap(amount0, amount1, address(this), new bytes(0));\n\n // The slippage protection against the amount out is indirectly done\n // via the improvePoolBalance\n }\n\n /// @dev Calculate the value of a LP position in a SwapX stable pool\n /// if the pool was balanced.\n /// @param _lpTokens Amount of LP tokens in the SwapX pool\n /// @return value The wS value of the LP tokens when the pool is balanced\n function _lpValue(uint256 _lpTokens) internal view returns (uint256 value) {\n // Get total supply of LP tokens\n uint256 totalSupply = IPair(pool).totalSupply();\n if (totalSupply == 0) return 0;\n\n // Get the current reserves of the pool\n (uint256 wsReserves, uint256 osReserves, ) = IPair(pool).getReserves();\n\n // Calculate the invariant of the pool assuming both tokens have 18 decimals.\n // k is scaled to 18 decimals.\n uint256 k = _invariant(wsReserves, osReserves);\n\n // If x = y, let’s denote x = y = z (where z is the common reserve value)\n // Substitute z into the invariant:\n // k = z^3 * z + z * z^3\n // k = 2 * z^4\n // Going back the other way to calculate the common reserve value z\n // z = (k / 2) ^ (1/4)\n // the total value of the pool when x = y is 2 * z, which is 2 * (k / 2) ^ (1/4)\n uint256 zSquared = sqrt((k * 1e18) / 2); // 18 + 18 = 36 decimals becomes 18 decimals after sqrt\n uint256 z = sqrt(zSquared * 1e18); // 18 + 18 = 36 decimals becomes 18 decimals after sqrt\n uint256 totalValueOfPool = 2 * z;\n\n // lp value = lp tokens * value of pool / total supply\n value = (_lpTokens * totalValueOfPool) / totalSupply;\n }\n\n /**\n * @dev Compute the invariant for a SwapX stable pool.\n * This assumed both x and y tokens are to 18 decimals which is checked in the constructor.\n * invariant: k = x^3 * y + x * y^3\n * @dev This implementation is copied from SwapX's Pair contract.\n * @param _x The amount of Wrapped S (wS) tokens in the pool\n * @param _y The amount of the OS tokens in the pool\n * @return k The invariant of the SwapX stable pool\n */\n function _invariant(uint256 _x, uint256 _y)\n internal\n pure\n returns (uint256 k)\n {\n uint256 _a = (_x * _y) / PRECISION;\n uint256 _b = ((_x * _x) / PRECISION + (_y * _y) / PRECISION);\n // slither-disable-next-line divide-before-multiply\n k = (_a * _b) / PRECISION;\n }\n\n /**\n * @dev Checks that the protocol is solvent, protecting from a rogue Strategist / Guardian that can\n * keep rebalancing the pool in both directions making the protocol lose a tiny amount of\n * funds each time.\n *\n * Protocol must be at least SOLVENCY_THRESHOLD (99,8 %) backed in order for the rebalances to\n * function.\n */\n function _solvencyAssert() internal view {\n uint256 _totalVaultValue = IVault(vaultAddress).totalValue();\n uint256 _totalSupply = IERC20(os).totalSupply();\n\n if (\n _totalSupply > 0 &&\n _totalVaultValue.divPrecisely(_totalSupply) < SOLVENCY_THRESHOLD\n ) {\n revert(\"Protocol insolvent\");\n }\n }\n\n /***************************************\n Setters\n ****************************************/\n\n /**\n * @notice Set the maximum deviation from the OS/wS peg (1e18) before deposits are reverted.\n * @param _maxDepeg the OS/wS price from peg (1e18) in 18 decimals.\n * eg 0.01e18 or 1e16 is 1% which is 100 basis points.\n */\n function setMaxDepeg(uint256 _maxDepeg) external onlyGovernor {\n maxDepeg = _maxDepeg;\n\n emit MaxDepegUpdated(_maxDepeg);\n }\n\n /***************************************\n Approvals\n ****************************************/\n\n /**\n * @notice Approve the spending of all assets by their corresponding pool tokens,\n * if for some reason is it necessary.\n */\n function safeApproveAllTokens()\n external\n override\n onlyGovernor\n nonReentrant\n {\n _approveBase();\n }\n\n // solhint-disable-next-line no-unused-vars\n function _abstractSetPToken(address _asset, address _pToken)\n internal\n override\n {}\n\n function _approveBase() internal {\n // Approve SwapX gauge contract to transfer SwapX pool LP tokens\n // This is needed for deposits of SwapX pool LP tokens into the gauge.\n // slither-disable-next-line unused-return\n IPair(pool).approve(address(gauge), type(uint256).max);\n }\n}\n" + }, + "contracts/strategies/sonic/SonicValidatorDelegator.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { IERC20, InitializableAbstractStrategy } from \"../../utils/InitializableAbstractStrategy.sol\";\nimport { IVault } from \"../../interfaces/IVault.sol\";\nimport { ISFC } from \"../../interfaces/sonic/ISFC.sol\";\nimport { IWrappedSonic } from \"../../interfaces/sonic/IWrappedSonic.sol\";\n\n/**\n * @title Manages delegation to Sonic validators\n * @notice This contract implements all the required functionality to delegate to,\n undelegate from and withdraw from validators.\n * @author Origin Protocol Inc\n */\nabstract contract SonicValidatorDelegator is InitializableAbstractStrategy {\n /// @notice Address of Sonic's wrapped S token\n address public immutable wrappedSonic;\n /// @notice Sonic's Special Fee Contract (SFC)\n ISFC public immutable sfc;\n\n /// @notice a unique ID for each withdrawal request\n uint256 public nextWithdrawId;\n /// @notice Sonic (S) that is pending withdrawal after undelegating\n uint256 public pendingWithdrawals;\n\n /// @notice List of supported validator IDs that can be delegated to\n uint256[] public supportedValidators;\n\n /// @notice Default validator id to deposit to\n uint256 public defaultValidatorId;\n\n struct WithdrawRequest {\n uint256 validatorId;\n uint256 undelegatedAmount;\n uint256 timestamp;\n }\n /// @notice Mapping of withdrawIds to validatorIds and undelegatedAmounts\n mapping(uint256 => WithdrawRequest) public withdrawals;\n\n /// @notice Address of the registrator - allowed to register, exit and remove validators\n address public validatorRegistrator;\n\n // For future use\n uint256[44] private __gap;\n\n event Delegated(uint256 indexed validatorId, uint256 delegatedAmount);\n event Undelegated(\n uint256 indexed withdrawId,\n uint256 indexed validatorId,\n uint256 undelegatedAmount\n );\n event Withdrawn(\n uint256 indexed withdrawId,\n uint256 indexed validatorId,\n uint256 undelegatedAmount,\n uint256 withdrawnAmount\n );\n event RegistratorChanged(address indexed newAddress);\n event SupportedValidator(uint256 indexed validatorId);\n event UnsupportedValidator(uint256 indexed validatorId);\n event DefaultValidatorIdChanged(uint256 indexed validatorId);\n\n /// @dev Throws if called by any account other than the Registrator or Strategist\n modifier onlyRegistratorOrStrategist() {\n require(\n msg.sender == validatorRegistrator ||\n msg.sender == IVault(vaultAddress).strategistAddr(),\n \"Caller is not the Registrator or Strategist\"\n );\n _;\n }\n\n constructor(\n BaseStrategyConfig memory _baseConfig,\n address _wrappedSonic,\n address _sfc\n ) InitializableAbstractStrategy(_baseConfig) {\n wrappedSonic = _wrappedSonic;\n sfc = ISFC(_sfc);\n }\n\n function initialize() external virtual onlyGovernor initializer {\n address[] memory rewardTokens = new address[](0);\n address[] memory assets = new address[](1);\n address[] memory pTokens = new address[](1);\n\n assets[0] = address(wrappedSonic);\n pTokens[0] = address(platformAddress);\n\n InitializableAbstractStrategy._initialize(\n rewardTokens,\n assets,\n pTokens\n );\n }\n\n /// @notice Returns the total value of Sonic (S) that is delegated validators.\n /// Wrapped Sonic (wS) deposits that are still to be delegated and any undelegated amounts\n /// still pending a withdrawal.\n /// @param _asset Address of Wrapped Sonic (wS) token\n /// @return balance Total value managed by the strategy\n function checkBalance(address _asset)\n external\n view\n virtual\n override\n returns (uint256 balance)\n {\n require(_asset == wrappedSonic, \"Unsupported asset\");\n\n // add the Wrapped Sonic (wS) in the strategy from deposits that are still to be delegated\n // and any undelegated amounts still pending a withdrawal\n balance =\n IERC20(wrappedSonic).balanceOf(address(this)) +\n pendingWithdrawals;\n\n // For each supported validator, get the staked amount and pending rewards\n uint256 validatorLen = supportedValidators.length;\n for (uint256 i = 0; i < validatorLen; i++) {\n uint256 validator = supportedValidators[i];\n balance += sfc.getStake(address(this), validator);\n balance += sfc.pendingRewards(address(this), validator);\n }\n }\n\n /**\n * @dev Delegate from this strategy to a specific Sonic validator. Called\n * automatically on asset deposit\n * @param _amount the amount of Sonic (S) to delegate.\n */\n function _delegate(uint256 _amount) internal {\n require(\n isSupportedValidator(defaultValidatorId),\n \"Validator not supported\"\n );\n\n // unwrap Wrapped Sonic (wS) to native Sonic (S)\n IWrappedSonic(wrappedSonic).withdraw(_amount);\n\n //slither-disable-next-line arbitrary-send-eth\n sfc.delegate{ value: _amount }(defaultValidatorId);\n\n emit Delegated(defaultValidatorId, _amount);\n }\n\n /**\n * @notice Undelegate from a specific Sonic validator.\n * This needs to be followed by a `withdrawFromSFC` two weeks later.\n * @param _validatorId The Sonic validator ID to undelegate from.\n * @param _undelegateAmount the amount of Sonic (S) to undelegate.\n * @return withdrawId The unique ID of the withdrawal request.\n */\n function undelegate(uint256 _validatorId, uint256 _undelegateAmount)\n external\n onlyRegistratorOrStrategist\n nonReentrant\n returns (uint256 withdrawId)\n {\n withdrawId = _undelegate(_validatorId, _undelegateAmount);\n }\n\n function _undelegate(uint256 _validatorId, uint256 _undelegateAmount)\n internal\n returns (uint256 withdrawId)\n {\n // Can still undelegate even if the validator is no longer supported\n require(_undelegateAmount > 0, \"Must undelegate something\");\n\n uint256 amountDelegated = sfc.getStake(address(this), _validatorId);\n require(\n _undelegateAmount <= amountDelegated,\n \"Insufficient delegation\"\n );\n\n withdrawId = nextWithdrawId++;\n\n withdrawals[withdrawId] = WithdrawRequest(\n _validatorId,\n _undelegateAmount,\n block.timestamp\n );\n pendingWithdrawals += _undelegateAmount;\n\n sfc.undelegate(_validatorId, withdrawId, _undelegateAmount);\n\n emit Undelegated(withdrawId, _validatorId, _undelegateAmount);\n }\n\n /**\n * @notice Withdraw native S from a previously undelegated validator.\n * The native S is wrapped wS and transferred to the Vault.\n * @param _withdrawId The unique withdraw ID used to `undelegate`\n * @return withdrawnAmount The amount of Sonic (S) withdrawn.\n * This can be less than the undelegated amount in the event of slashing.\n */\n function withdrawFromSFC(uint256 _withdrawId)\n external\n onlyRegistratorOrStrategist\n nonReentrant\n returns (uint256 withdrawnAmount)\n {\n require(_withdrawId < nextWithdrawId, \"Invalid withdrawId\");\n\n // Can still withdraw even if the validator is no longer supported\n // Load the withdrawal from storage into memory\n WithdrawRequest memory withdrawal = withdrawals[_withdrawId];\n require(!isWithdrawnFromSFC(_withdrawId), \"Already withdrawn\");\n\n withdrawals[_withdrawId].undelegatedAmount = 0;\n pendingWithdrawals -= withdrawal.undelegatedAmount;\n\n uint256 sBalanceBefore = address(this).balance;\n\n // Try to withdraw from SFC\n try sfc.withdraw(withdrawal.validatorId, _withdrawId) {\n // continue below\n } catch (bytes memory err) {\n bytes4 errorSelector = bytes4(err);\n\n // If the validator has been fully slashed, SFC's withdraw function will\n // revert with a StakeIsFullySlashed custom error.\n if (errorSelector == ISFC.StakeIsFullySlashed.selector) {\n // The validator was fully slashed, so all the delegated amounts were lost.\n // Will swallow the error as we still want to update the\n // withdrawals and pendingWithdrawals storage variables.\n\n // The return param defaults to zero but lets set it explicitly so it's clear\n withdrawnAmount = 0;\n\n emit Withdrawn(\n _withdrawId,\n withdrawal.validatorId,\n withdrawal.undelegatedAmount,\n withdrawnAmount\n );\n\n // Exit here as there is nothing to transfer to the Vault\n return withdrawnAmount;\n } else {\n // Bubble up any other SFC custom errors.\n // Inline assembly is currently the only way to generically rethrow the exact same custom error\n // from the raw bytes err in a catch block while preserving its original selector and parameters.\n // solhint-disable-next-line no-inline-assembly\n assembly {\n revert(add(32, err), mload(err))\n }\n }\n }\n\n // Set return parameter\n withdrawnAmount = address(this).balance - sBalanceBefore;\n\n // Wrap Sonic (S) to Wrapped Sonic (wS)\n IWrappedSonic(wrappedSonic).deposit{ value: withdrawnAmount }();\n\n // Transfer the Wrapped Sonic (wS) to the Vault\n _withdraw(vaultAddress, wrappedSonic, withdrawnAmount);\n\n // withdrawal.undelegatedAmount & withdrawnAmount can differ in case of slashing\n emit Withdrawn(\n _withdrawId,\n withdrawal.validatorId,\n withdrawal.undelegatedAmount,\n withdrawnAmount\n );\n }\n\n /// @notice returns a bool whether a withdrawalId has already been withdrawn or not\n /// @param _withdrawId The unique withdraw ID used to `undelegate`\n function isWithdrawnFromSFC(uint256 _withdrawId)\n public\n view\n returns (bool)\n {\n WithdrawRequest memory withdrawal = withdrawals[_withdrawId];\n require(withdrawal.validatorId > 0, \"Invalid withdrawId\");\n return withdrawal.undelegatedAmount == 0;\n }\n\n /**\n * @notice Restake any pending validator rewards for all supported validators\n * @param _validatorIds List of Sonic validator IDs to restake rewards\n */\n function restakeRewards(uint256[] calldata _validatorIds)\n external\n nonReentrant\n {\n for (uint256 i = 0; i < _validatorIds.length; ++i) {\n require(\n isSupportedValidator(_validatorIds[i]),\n \"Validator not supported\"\n );\n\n uint256 rewards = sfc.pendingRewards(\n address(this),\n _validatorIds[i]\n );\n\n if (rewards > 0) {\n sfc.restakeRewards(_validatorIds[i]);\n }\n }\n\n // The SFC contract will emit Delegated and RestakedRewards events.\n // The checkBalance function should not change as the pending rewards will moved to the staked amount.\n }\n\n /**\n * @notice Claim any pending rewards from validators\n * @param _validatorIds List of Sonic validator IDs to claim rewards\n */\n function collectRewards(uint256[] calldata _validatorIds)\n external\n onlyRegistratorOrStrategist\n nonReentrant\n {\n uint256 sBalanceBefore = address(this).balance;\n\n for (uint256 i = 0; i < _validatorIds.length; ++i) {\n uint256 rewards = sfc.pendingRewards(\n address(this),\n _validatorIds[i]\n );\n\n if (rewards > 0) {\n // The SFC contract will emit ClaimedRewards(delegator (this), validatorId, rewards)\n sfc.claimRewards(_validatorIds[i]);\n }\n }\n\n uint256 rewardsAmount = address(this).balance - sBalanceBefore;\n\n // Wrap Sonic (S) to Wrapped Sonic (wS)\n IWrappedSonic(wrappedSonic).deposit{ value: rewardsAmount }();\n\n // Transfer the Wrapped Sonic (wS) to the Vault\n _withdraw(vaultAddress, wrappedSonic, rewardsAmount);\n }\n\n /**\n * @notice To receive native S from SFC and Wrapped Sonic (wS)\n *\n * @dev This does not prevent donating S tokens to the contract\n * as wrappedSonic has a `withdrawTo` function where a third party\n * owner of wrappedSonic can withdraw to this contract.\n */\n receive() external payable {\n require(\n msg.sender == address(sfc) || msg.sender == wrappedSonic,\n \"S not from allowed contracts\"\n );\n }\n\n /***************************************\n Admin functions\n ****************************************/\n\n /// @notice Set the address of the Registrator which can undelegate, withdraw and collect rewards\n /// @param _validatorRegistrator The address of the Registrator\n function setRegistrator(address _validatorRegistrator)\n external\n onlyGovernor\n {\n validatorRegistrator = _validatorRegistrator;\n emit RegistratorChanged(_validatorRegistrator);\n }\n\n /// @notice Set the default validatorId to delegate to on deposit\n /// @param _validatorId The validator identifier. eg 18\n function setDefaultValidatorId(uint256 _validatorId)\n external\n onlyRegistratorOrStrategist\n {\n require(isSupportedValidator(_validatorId), \"Validator not supported\");\n defaultValidatorId = _validatorId;\n emit DefaultValidatorIdChanged(_validatorId);\n }\n\n /// @notice Allows a validator to be delegated to by the Registrator\n /// @param _validatorId The validator identifier. eg 18\n function supportValidator(uint256 _validatorId) external onlyGovernor {\n require(\n !isSupportedValidator(_validatorId),\n \"Validator already supported\"\n );\n\n supportedValidators.push(_validatorId);\n\n emit SupportedValidator(_validatorId);\n }\n\n /// @notice Removes a validator from the supported list.\n /// Unsupported validators can still be undelegated from, withdrawn from and rewards collected.\n /// @param _validatorId The validator identifier. eg 18\n function unsupportValidator(uint256 _validatorId) external onlyGovernor {\n require(isSupportedValidator(_validatorId), \"Validator not supported\");\n\n uint256 validatorLen = supportedValidators.length;\n for (uint256 i = 0; i < validatorLen; ++i) {\n if (supportedValidators[i] == _validatorId) {\n supportedValidators[i] = supportedValidators[validatorLen - 1];\n supportedValidators.pop();\n break;\n }\n }\n\n uint256 stake = sfc.getStake(address(this), _validatorId);\n\n // undelegate if validator still has funds staked\n if (stake > 0) {\n _undelegate(_validatorId, stake);\n }\n emit UnsupportedValidator(_validatorId);\n }\n\n /// @notice Returns the length of the supportedValidators array\n function supportedValidatorsLength() external view returns (uint256) {\n return supportedValidators.length;\n }\n\n /// @notice Returns whether a validator is supported by this strategy\n /// @param _validatorId The validator identifier\n function isSupportedValidator(uint256 _validatorId)\n public\n view\n returns (bool)\n {\n uint256 validatorLen = supportedValidators.length;\n for (uint256 i = 0; i < validatorLen; ++i) {\n if (supportedValidators[i] == _validatorId) {\n return true;\n }\n }\n return false;\n }\n\n function _withdraw(\n address _recipient,\n address _asset,\n uint256 _amount\n ) internal virtual;\n}\n" + }, + "contracts/token/OUSD.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title OUSD Token Contract\n * @dev ERC20 compatible contract for OUSD\n * @dev Implements an elastic supply\n * @author Origin Protocol Inc\n */\nimport { IVault } from \"../interfaces/IVault.sol\";\nimport { Governable } from \"../governance/Governable.sol\";\nimport { SafeCast } from \"@openzeppelin/contracts/utils/math/SafeCast.sol\";\n\ncontract OUSD is Governable {\n using SafeCast for int256;\n using SafeCast for uint256;\n\n /// @dev Event triggered when the supply changes\n /// @param totalSupply Updated token total supply\n /// @param rebasingCredits Updated token rebasing credits\n /// @param rebasingCreditsPerToken Updated token rebasing credits per token\n event TotalSupplyUpdatedHighres(\n uint256 totalSupply,\n uint256 rebasingCredits,\n uint256 rebasingCreditsPerToken\n );\n /// @dev Event triggered when an account opts in for rebasing\n /// @param account Address of the account\n event AccountRebasingEnabled(address account);\n /// @dev Event triggered when an account opts out of rebasing\n /// @param account Address of the account\n event AccountRebasingDisabled(address account);\n /// @dev Emitted when `value` tokens are moved from one account `from` to\n /// another `to`.\n /// @param from Address of the account tokens are moved from\n /// @param to Address of the account tokens are moved to\n /// @param value Amount of tokens transferred\n event Transfer(address indexed from, address indexed to, uint256 value);\n /// @dev Emitted when the allowance of a `spender` for an `owner` is set by\n /// a call to {approve}. `value` is the new allowance.\n /// @param owner Address of the owner approving allowance\n /// @param spender Address of the spender allowance is granted to\n /// @param value Amount of tokens spender can transfer\n event Approval(\n address indexed owner,\n address indexed spender,\n uint256 value\n );\n /// @dev Yield resulting from {changeSupply} that a `source` account would\n /// receive is directed to `target` account.\n /// @param source Address of the source forwarding the yield\n /// @param target Address of the target receiving the yield\n event YieldDelegated(address source, address target);\n /// @dev Yield delegation from `source` account to the `target` account is\n /// suspended.\n /// @param source Address of the source suspending yield forwarding\n /// @param target Address of the target no longer receiving yield from `source`\n /// account\n event YieldUndelegated(address source, address target);\n\n enum RebaseOptions {\n NotSet,\n StdNonRebasing,\n StdRebasing,\n YieldDelegationSource,\n YieldDelegationTarget\n }\n\n uint256[154] private _gap; // Slots to align with deployed contract\n uint256 private constant MAX_SUPPLY = type(uint128).max;\n /// @dev The amount of tokens in existence\n uint256 public totalSupply;\n mapping(address => mapping(address => uint256)) private allowances;\n /// @dev The vault with privileges to execute {mint}, {burn}\n /// and {changeSupply}\n address public vaultAddress;\n mapping(address => uint256) internal creditBalances;\n // the 2 storage variables below need trailing underscores to not name collide with public functions\n uint256 private rebasingCredits_; // Sum of all rebasing credits (creditBalances for rebasing accounts)\n uint256 private rebasingCreditsPerToken_;\n /// @dev The amount of tokens that are not rebasing - receiving yield\n uint256 public nonRebasingSupply;\n mapping(address => uint256) internal alternativeCreditsPerToken;\n /// @dev A map of all addresses and their respective RebaseOptions\n mapping(address => RebaseOptions) public rebaseState;\n mapping(address => uint256) private __deprecated_isUpgraded;\n /// @dev A map of addresses that have yields forwarded to. This is an\n /// inverse mapping of {yieldFrom}\n /// Key Account forwarding yield\n /// Value Account receiving yield\n mapping(address => address) public yieldTo;\n /// @dev A map of addresses that are receiving the yield. This is an\n /// inverse mapping of {yieldTo}\n /// Key Account receiving yield\n /// Value Account forwarding yield\n mapping(address => address) public yieldFrom;\n\n uint256 private constant RESOLUTION_INCREASE = 1e9;\n uint256[34] private __gap; // including below gap totals up to 200\n\n /// @dev Verifies that the caller is the Governor or Strategist.\n modifier onlyGovernorOrStrategist() {\n require(\n isGovernor() || msg.sender == IVault(vaultAddress).strategistAddr(),\n \"Caller is not the Strategist or Governor\"\n );\n _;\n }\n\n /// @dev Initializes the contract and sets necessary variables.\n /// @param _vaultAddress Address of the vault contract\n /// @param _initialCreditsPerToken The starting rebasing credits per token.\n function initialize(address _vaultAddress, uint256 _initialCreditsPerToken)\n external\n onlyGovernor\n {\n require(_vaultAddress != address(0), \"Zero vault address\");\n require(vaultAddress == address(0), \"Already initialized\");\n\n rebasingCreditsPerToken_ = _initialCreditsPerToken;\n vaultAddress = _vaultAddress;\n }\n\n /// @dev Returns the symbol of the token, a shorter version\n /// of the name.\n function symbol() external pure virtual returns (string memory) {\n return \"OUSD\";\n }\n\n /// @dev Returns the name of the token.\n function name() external pure virtual returns (string memory) {\n return \"Origin Dollar\";\n }\n\n /// @dev Returns the number of decimals used to get its user representation.\n function decimals() external pure virtual returns (uint8) {\n return 18;\n }\n\n /**\n * @dev Verifies that the caller is the Vault contract\n */\n modifier onlyVault() {\n require(vaultAddress == msg.sender, \"Caller is not the Vault\");\n _;\n }\n\n /**\n * @return High resolution rebasingCreditsPerToken\n */\n function rebasingCreditsPerTokenHighres() external view returns (uint256) {\n return rebasingCreditsPerToken_;\n }\n\n /**\n * @return Low resolution rebasingCreditsPerToken\n */\n function rebasingCreditsPerToken() external view returns (uint256) {\n return rebasingCreditsPerToken_ / RESOLUTION_INCREASE;\n }\n\n /**\n * @return High resolution total number of rebasing credits\n */\n function rebasingCreditsHighres() external view returns (uint256) {\n return rebasingCredits_;\n }\n\n /**\n * @return Low resolution total number of rebasing credits\n */\n function rebasingCredits() external view returns (uint256) {\n return rebasingCredits_ / RESOLUTION_INCREASE;\n }\n\n /**\n * @notice Gets the balance of the specified address.\n * @param _account Address to query the balance of.\n * @return A uint256 representing the amount of base units owned by the\n * specified address.\n */\n function balanceOf(address _account) public view returns (uint256) {\n RebaseOptions state = rebaseState[_account];\n if (state == RebaseOptions.YieldDelegationSource) {\n // Saves a slot read when transferring to or from a yield delegating source\n // since we know creditBalances equals the balance.\n return creditBalances[_account];\n }\n uint256 baseBalance = (creditBalances[_account] * 1e18) /\n _creditsPerToken(_account);\n if (state == RebaseOptions.YieldDelegationTarget) {\n // creditBalances of yieldFrom accounts equals token balances\n return baseBalance - creditBalances[yieldFrom[_account]];\n }\n return baseBalance;\n }\n\n /**\n * @notice Gets the credits balance of the specified address.\n * @dev Backwards compatible with old low res credits per token.\n * @param _account The address to query the balance of.\n * @return (uint256, uint256) Credit balance and credits per token of the\n * address\n */\n function creditsBalanceOf(address _account)\n external\n view\n returns (uint256, uint256)\n {\n uint256 cpt = _creditsPerToken(_account);\n if (cpt == 1e27) {\n // For a period before the resolution upgrade, we created all new\n // contract accounts at high resolution. Since they are not changing\n // as a result of this upgrade, we will return their true values\n return (creditBalances[_account], cpt);\n } else {\n return (\n creditBalances[_account] / RESOLUTION_INCREASE,\n cpt / RESOLUTION_INCREASE\n );\n }\n }\n\n /**\n * @notice Gets the credits balance of the specified address.\n * @param _account The address to query the balance of.\n * @return (uint256, uint256, bool) Credit balance, credits per token of the\n * address, and isUpgraded\n */\n function creditsBalanceOfHighres(address _account)\n external\n view\n returns (\n uint256,\n uint256,\n bool\n )\n {\n return (\n creditBalances[_account],\n _creditsPerToken(_account),\n true // all accounts have their resolution \"upgraded\"\n );\n }\n\n // Backwards compatible view\n function nonRebasingCreditsPerToken(address _account)\n external\n view\n returns (uint256)\n {\n return alternativeCreditsPerToken[_account];\n }\n\n /**\n * @notice Transfer tokens to a specified address.\n * @param _to the address to transfer to.\n * @param _value the amount to be transferred.\n * @return true on success.\n */\n function transfer(address _to, uint256 _value) external returns (bool) {\n require(_to != address(0), \"Transfer to zero address\");\n\n _executeTransfer(msg.sender, _to, _value);\n\n emit Transfer(msg.sender, _to, _value);\n return true;\n }\n\n /**\n * @notice Transfer tokens from one address to another.\n * @param _from The address you want to send tokens from.\n * @param _to The address you want to transfer to.\n * @param _value The amount of tokens to be transferred.\n * @return true on success.\n */\n function transferFrom(\n address _from,\n address _to,\n uint256 _value\n ) external returns (bool) {\n require(_to != address(0), \"Transfer to zero address\");\n uint256 userAllowance = allowances[_from][msg.sender];\n require(_value <= userAllowance, \"Allowance exceeded\");\n\n unchecked {\n allowances[_from][msg.sender] = userAllowance - _value;\n }\n\n _executeTransfer(_from, _to, _value);\n\n emit Transfer(_from, _to, _value);\n return true;\n }\n\n function _executeTransfer(\n address _from,\n address _to,\n uint256 _value\n ) internal {\n (\n int256 fromRebasingCreditsDiff,\n int256 fromNonRebasingSupplyDiff\n ) = _adjustAccount(_from, -_value.toInt256());\n (\n int256 toRebasingCreditsDiff,\n int256 toNonRebasingSupplyDiff\n ) = _adjustAccount(_to, _value.toInt256());\n\n _adjustGlobals(\n fromRebasingCreditsDiff + toRebasingCreditsDiff,\n fromNonRebasingSupplyDiff + toNonRebasingSupplyDiff\n );\n }\n\n function _adjustAccount(address _account, int256 _balanceChange)\n internal\n returns (int256 rebasingCreditsDiff, int256 nonRebasingSupplyDiff)\n {\n RebaseOptions state = rebaseState[_account];\n int256 currentBalance = balanceOf(_account).toInt256();\n if (currentBalance + _balanceChange < 0) {\n revert(\"Transfer amount exceeds balance\");\n }\n uint256 newBalance = (currentBalance + _balanceChange).toUint256();\n\n if (state == RebaseOptions.YieldDelegationSource) {\n address target = yieldTo[_account];\n uint256 targetOldBalance = balanceOf(target);\n uint256 targetNewCredits = _balanceToRebasingCredits(\n targetOldBalance + newBalance\n );\n rebasingCreditsDiff =\n targetNewCredits.toInt256() -\n creditBalances[target].toInt256();\n\n creditBalances[_account] = newBalance;\n creditBalances[target] = targetNewCredits;\n } else if (state == RebaseOptions.YieldDelegationTarget) {\n uint256 newCredits = _balanceToRebasingCredits(\n newBalance + creditBalances[yieldFrom[_account]]\n );\n rebasingCreditsDiff =\n newCredits.toInt256() -\n creditBalances[_account].toInt256();\n creditBalances[_account] = newCredits;\n } else {\n _autoMigrate(_account);\n uint256 alternativeCreditsPerTokenMem = alternativeCreditsPerToken[\n _account\n ];\n if (alternativeCreditsPerTokenMem > 0) {\n nonRebasingSupplyDiff = _balanceChange;\n if (alternativeCreditsPerTokenMem != 1e18) {\n alternativeCreditsPerToken[_account] = 1e18;\n }\n creditBalances[_account] = newBalance;\n } else {\n uint256 newCredits = _balanceToRebasingCredits(newBalance);\n rebasingCreditsDiff =\n newCredits.toInt256() -\n creditBalances[_account].toInt256();\n creditBalances[_account] = newCredits;\n }\n }\n }\n\n function _adjustGlobals(\n int256 _rebasingCreditsDiff,\n int256 _nonRebasingSupplyDiff\n ) internal {\n if (_rebasingCreditsDiff != 0) {\n rebasingCredits_ = (rebasingCredits_.toInt256() +\n _rebasingCreditsDiff).toUint256();\n }\n if (_nonRebasingSupplyDiff != 0) {\n nonRebasingSupply = (nonRebasingSupply.toInt256() +\n _nonRebasingSupplyDiff).toUint256();\n }\n }\n\n /**\n * @notice Function to check the amount of tokens that _owner has allowed\n * to `_spender`.\n * @param _owner The address which owns the funds.\n * @param _spender The address which will spend the funds.\n * @return The number of tokens still available for the _spender.\n */\n function allowance(address _owner, address _spender)\n external\n view\n returns (uint256)\n {\n return allowances[_owner][_spender];\n }\n\n /**\n * @notice Approve the passed address to spend the specified amount of\n * tokens on behalf of msg.sender.\n * @param _spender The address which will spend the funds.\n * @param _value The amount of tokens to be spent.\n * @return true on success.\n */\n function approve(address _spender, uint256 _value) external returns (bool) {\n allowances[msg.sender][_spender] = _value;\n emit Approval(msg.sender, _spender, _value);\n return true;\n }\n\n /**\n * @notice Creates `_amount` tokens and assigns them to `_account`,\n * increasing the total supply.\n */\n function mint(address _account, uint256 _amount) external onlyVault {\n require(_account != address(0), \"Mint to the zero address\");\n\n // Account\n (\n int256 toRebasingCreditsDiff,\n int256 toNonRebasingSupplyDiff\n ) = _adjustAccount(_account, _amount.toInt256());\n // Globals\n _adjustGlobals(toRebasingCreditsDiff, toNonRebasingSupplyDiff);\n totalSupply = totalSupply + _amount;\n\n require(totalSupply < MAX_SUPPLY, \"Max supply\");\n emit Transfer(address(0), _account, _amount);\n }\n\n /**\n * @notice Destroys `_amount` tokens from `_account`,\n * reducing the total supply.\n */\n function burn(address _account, uint256 _amount) external onlyVault {\n require(_account != address(0), \"Burn from the zero address\");\n if (_amount == 0) {\n return;\n }\n\n // Account\n (\n int256 toRebasingCreditsDiff,\n int256 toNonRebasingSupplyDiff\n ) = _adjustAccount(_account, -_amount.toInt256());\n // Globals\n _adjustGlobals(toRebasingCreditsDiff, toNonRebasingSupplyDiff);\n totalSupply = totalSupply - _amount;\n\n emit Transfer(_account, address(0), _amount);\n }\n\n /**\n * @dev Get the credits per token for an account. Returns a fixed amount\n * if the account is non-rebasing.\n * @param _account Address of the account.\n */\n function _creditsPerToken(address _account)\n internal\n view\n returns (uint256)\n {\n uint256 alternativeCreditsPerTokenMem = alternativeCreditsPerToken[\n _account\n ];\n if (alternativeCreditsPerTokenMem != 0) {\n return alternativeCreditsPerTokenMem;\n } else {\n return rebasingCreditsPerToken_;\n }\n }\n\n /**\n * @dev Auto migrate contracts to be non rebasing,\n * unless they have opted into yield.\n * @param _account Address of the account.\n */\n function _autoMigrate(address _account) internal {\n uint256 codeLen = _account.code.length;\n bool isEOA = (codeLen == 0) ||\n (codeLen == 23 && bytes3(_account.code) == 0xef0100);\n // In previous code versions, contracts would not have had their\n // rebaseState[_account] set to RebaseOptions.NonRebasing when migrated\n // therefore we check the actual accounting used on the account as well.\n if (\n (!isEOA) &&\n rebaseState[_account] == RebaseOptions.NotSet &&\n alternativeCreditsPerToken[_account] == 0\n ) {\n _rebaseOptOut(_account);\n }\n }\n\n /**\n * @dev Calculates credits from contract's global rebasingCreditsPerToken_, and\n * also balance that corresponds to those credits. The latter is important\n * when adjusting the contract's global nonRebasingSupply to circumvent any\n * possible rounding errors.\n *\n * @param _balance Balance of the account.\n */\n function _balanceToRebasingCredits(uint256 _balance)\n internal\n view\n returns (uint256 rebasingCredits)\n {\n // Rounds up, because we need to ensure that accounts always have\n // at least the balance that they should have.\n // Note this should always be used on an absolute account value,\n // not on a possibly negative diff, because then the rounding would be wrong.\n return ((_balance) * rebasingCreditsPerToken_ + 1e18 - 1) / 1e18;\n }\n\n /**\n * @notice The calling account will start receiving yield after a successful call.\n * @param _account Address of the account.\n */\n function governanceRebaseOptIn(address _account) external onlyGovernor {\n require(_account != address(0), \"Zero address not allowed\");\n _rebaseOptIn(_account);\n }\n\n /**\n * @notice The calling account will start receiving yield after a successful call.\n */\n function rebaseOptIn() external {\n _rebaseOptIn(msg.sender);\n }\n\n function _rebaseOptIn(address _account) internal {\n uint256 balance = balanceOf(_account);\n\n // prettier-ignore\n require(\n alternativeCreditsPerToken[_account] > 0 ||\n // Accounts may explicitly `rebaseOptIn` regardless of\n // accounting if they have a 0 balance.\n creditBalances[_account] == 0\n ,\n \"Account must be non-rebasing\"\n );\n RebaseOptions state = rebaseState[_account];\n // prettier-ignore\n require(\n state == RebaseOptions.StdNonRebasing ||\n state == RebaseOptions.NotSet,\n \"Only standard non-rebasing accounts can opt in\"\n );\n\n uint256 newCredits = _balanceToRebasingCredits(balance);\n\n // Account\n rebaseState[_account] = RebaseOptions.StdRebasing;\n alternativeCreditsPerToken[_account] = 0;\n creditBalances[_account] = newCredits;\n // Globals\n _adjustGlobals(newCredits.toInt256(), -balance.toInt256());\n\n emit AccountRebasingEnabled(_account);\n }\n\n /**\n * @notice The calling account will no longer receive yield\n */\n function rebaseOptOut() external {\n _rebaseOptOut(msg.sender);\n }\n\n function _rebaseOptOut(address _account) internal {\n require(\n alternativeCreditsPerToken[_account] == 0,\n \"Account must be rebasing\"\n );\n RebaseOptions state = rebaseState[_account];\n require(\n state == RebaseOptions.StdRebasing || state == RebaseOptions.NotSet,\n \"Only standard rebasing accounts can opt out\"\n );\n\n uint256 oldCredits = creditBalances[_account];\n uint256 balance = balanceOf(_account);\n\n // Account\n rebaseState[_account] = RebaseOptions.StdNonRebasing;\n alternativeCreditsPerToken[_account] = 1e18;\n creditBalances[_account] = balance;\n // Globals\n _adjustGlobals(-oldCredits.toInt256(), balance.toInt256());\n\n emit AccountRebasingDisabled(_account);\n }\n\n /**\n * @notice Distribute yield to users. This changes the exchange rate\n * between \"credits\" and OUSD tokens to change rebasing user's balances.\n * @param _newTotalSupply New total supply of OUSD.\n */\n function changeSupply(uint256 _newTotalSupply) external onlyVault {\n require(totalSupply > 0, \"Cannot increase 0 supply\");\n\n if (totalSupply == _newTotalSupply) {\n emit TotalSupplyUpdatedHighres(\n totalSupply,\n rebasingCredits_,\n rebasingCreditsPerToken_\n );\n return;\n }\n\n totalSupply = _newTotalSupply > MAX_SUPPLY\n ? MAX_SUPPLY\n : _newTotalSupply;\n\n uint256 rebasingSupply = totalSupply - nonRebasingSupply;\n // round up in the favour of the protocol\n rebasingCreditsPerToken_ =\n (rebasingCredits_ * 1e18 + rebasingSupply - 1) /\n rebasingSupply;\n\n require(rebasingCreditsPerToken_ > 0, \"Invalid change in supply\");\n\n emit TotalSupplyUpdatedHighres(\n totalSupply,\n rebasingCredits_,\n rebasingCreditsPerToken_\n );\n }\n\n /*\n * @notice Send the yield from one account to another account.\n * Each account keeps its own balances.\n */\n function delegateYield(address _from, address _to)\n external\n onlyGovernorOrStrategist\n {\n require(_from != address(0), \"Zero from address not allowed\");\n require(_to != address(0), \"Zero to address not allowed\");\n\n require(_from != _to, \"Cannot delegate to self\");\n require(\n yieldFrom[_to] == address(0) &&\n yieldTo[_to] == address(0) &&\n yieldFrom[_from] == address(0) &&\n yieldTo[_from] == address(0),\n \"Blocked by existing yield delegation\"\n );\n RebaseOptions stateFrom = rebaseState[_from];\n RebaseOptions stateTo = rebaseState[_to];\n\n require(\n stateFrom == RebaseOptions.NotSet ||\n stateFrom == RebaseOptions.StdNonRebasing ||\n stateFrom == RebaseOptions.StdRebasing,\n \"Invalid rebaseState from\"\n );\n\n require(\n stateTo == RebaseOptions.NotSet ||\n stateTo == RebaseOptions.StdNonRebasing ||\n stateTo == RebaseOptions.StdRebasing,\n \"Invalid rebaseState to\"\n );\n\n if (alternativeCreditsPerToken[_from] == 0) {\n _rebaseOptOut(_from);\n }\n if (alternativeCreditsPerToken[_to] > 0) {\n _rebaseOptIn(_to);\n }\n\n uint256 fromBalance = balanceOf(_from);\n uint256 toBalance = balanceOf(_to);\n uint256 oldToCredits = creditBalances[_to];\n uint256 newToCredits = _balanceToRebasingCredits(\n fromBalance + toBalance\n );\n\n // Set up the bidirectional links\n yieldTo[_from] = _to;\n yieldFrom[_to] = _from;\n\n // Local\n rebaseState[_from] = RebaseOptions.YieldDelegationSource;\n alternativeCreditsPerToken[_from] = 1e18;\n creditBalances[_from] = fromBalance;\n rebaseState[_to] = RebaseOptions.YieldDelegationTarget;\n creditBalances[_to] = newToCredits;\n\n // Global\n int256 creditsChange = newToCredits.toInt256() -\n oldToCredits.toInt256();\n _adjustGlobals(creditsChange, -(fromBalance).toInt256());\n emit YieldDelegated(_from, _to);\n }\n\n /*\n * @notice Stop sending the yield from one account to another account.\n */\n function undelegateYield(address _from) external onlyGovernorOrStrategist {\n // Require a delegation, which will also ensure a valid delegation\n require(yieldTo[_from] != address(0), \"Zero address not allowed\");\n\n address to = yieldTo[_from];\n uint256 fromBalance = balanceOf(_from);\n uint256 toBalance = balanceOf(to);\n uint256 oldToCredits = creditBalances[to];\n uint256 newToCredits = _balanceToRebasingCredits(toBalance);\n\n // Remove the bidirectional links\n yieldFrom[to] = address(0);\n yieldTo[_from] = address(0);\n\n // Local\n rebaseState[_from] = RebaseOptions.StdNonRebasing;\n // alternativeCreditsPerToken[from] already 1e18 from `delegateYield()`\n creditBalances[_from] = fromBalance;\n rebaseState[to] = RebaseOptions.StdRebasing;\n // alternativeCreditsPerToken[to] already 0 from `delegateYield()`\n creditBalances[to] = newToCredits;\n\n // Global\n int256 creditsChange = newToCredits.toInt256() -\n oldToCredits.toInt256();\n _adjustGlobals(creditsChange, fromBalance.toInt256());\n emit YieldUndelegated(_from, to);\n }\n}\n" + }, + "contracts/utils/BalancerErrors.sol": { + "content": "// SPDX-License-Identifier: GPL-3.0-or-later\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see .\n\npragma solidity >=0.7.4 <0.9.0;\n\n// solhint-disable\n\n/**\n * @dev Reverts if `condition` is false, with a revert reason containing `errorCode`. Only codes up to 999 are\n * supported.\n * Uses the default 'BAL' prefix for the error code\n */\nfunction _require(bool condition, uint256 errorCode) pure {\n if (!condition) _revert(errorCode);\n}\n\n/**\n * @dev Reverts if `condition` is false, with a revert reason containing `errorCode`. Only codes up to 999 are\n * supported.\n */\nfunction _require(\n bool condition,\n uint256 errorCode,\n bytes3 prefix\n) pure {\n if (!condition) _revert(errorCode, prefix);\n}\n\n/**\n * @dev Reverts with a revert reason containing `errorCode`. Only codes up to 999 are supported.\n * Uses the default 'BAL' prefix for the error code\n */\nfunction _revert(uint256 errorCode) pure {\n _revert(errorCode, 0x42414c); // This is the raw byte representation of \"BAL\"\n}\n\n/**\n * @dev Reverts with a revert reason containing `errorCode`. Only codes up to 999 are supported.\n */\nfunction _revert(uint256 errorCode, bytes3 prefix) pure {\n uint256 prefixUint = uint256(uint24(prefix));\n // We're going to dynamically create a revert string based on the error code, with the following format:\n // 'BAL#{errorCode}'\n // where the code is left-padded with zeroes to three digits (so they range from 000 to 999).\n //\n // We don't have revert strings embedded in the contract to save bytecode size: it takes much less space to store a\n // number (8 to 16 bits) than the individual string characters.\n //\n // The dynamic string creation algorithm that follows could be implemented in Solidity, but assembly allows for a\n // much denser implementation, again saving bytecode size. Given this function unconditionally reverts, this is a\n // safe place to rely on it without worrying about how its usage might affect e.g. memory contents.\n assembly {\n // First, we need to compute the ASCII representation of the error code. We assume that it is in the 0-999\n // range, so we only need to convert three digits. To convert the digits to ASCII, we add 0x30, the value for\n // the '0' character.\n\n let units := add(mod(errorCode, 10), 0x30)\n\n errorCode := div(errorCode, 10)\n let tenths := add(mod(errorCode, 10), 0x30)\n\n errorCode := div(errorCode, 10)\n let hundreds := add(mod(errorCode, 10), 0x30)\n\n // With the individual characters, we can now construct the full string.\n // We first append the '#' character (0x23) to the prefix. In the case of 'BAL', it results in 0x42414c23 ('BAL#')\n // Then, we shift this by 24 (to provide space for the 3 bytes of the error code), and add the\n // characters to it, each shifted by a multiple of 8.\n // The revert reason is then shifted left by 200 bits (256 minus the length of the string, 7 characters * 8 bits\n // per character = 56) to locate it in the most significant part of the 256 slot (the beginning of a byte\n // array).\n let formattedPrefix := shl(24, add(0x23, shl(8, prefixUint)))\n\n let revertReason := shl(\n 200,\n add(\n formattedPrefix,\n add(add(units, shl(8, tenths)), shl(16, hundreds))\n )\n )\n\n // We can now encode the reason in memory, which can be safely overwritten as we're about to revert. The encoded\n // message will have the following layout:\n // [ revert reason identifier ] [ string location offset ] [ string length ] [ string contents ]\n\n // The Solidity revert reason identifier is 0x08c739a0, the function selector of the Error(string) function. We\n // also write zeroes to the next 28 bytes of memory, but those are about to be overwritten.\n mstore(\n 0x0,\n 0x08c379a000000000000000000000000000000000000000000000000000000000\n )\n // Next is the offset to the location of the string, which will be placed immediately after (20 bytes away).\n mstore(\n 0x04,\n 0x0000000000000000000000000000000000000000000000000000000000000020\n )\n // The string length is fixed: 7 characters.\n mstore(0x24, 7)\n // Finally, the string itself is stored.\n mstore(0x44, revertReason)\n\n // Even if the string is only 7 bytes long, we need to return a full 32 byte slot containing it. The length of\n // the encoded message is therefore 4 + 32 + 32 + 32 = 100.\n revert(0, 100)\n }\n}\n\nlibrary Errors {\n // Math\n uint256 internal constant ADD_OVERFLOW = 0;\n uint256 internal constant SUB_OVERFLOW = 1;\n uint256 internal constant SUB_UNDERFLOW = 2;\n uint256 internal constant MUL_OVERFLOW = 3;\n uint256 internal constant ZERO_DIVISION = 4;\n uint256 internal constant DIV_INTERNAL = 5;\n uint256 internal constant X_OUT_OF_BOUNDS = 6;\n uint256 internal constant Y_OUT_OF_BOUNDS = 7;\n uint256 internal constant PRODUCT_OUT_OF_BOUNDS = 8;\n uint256 internal constant INVALID_EXPONENT = 9;\n\n // Input\n uint256 internal constant OUT_OF_BOUNDS = 100;\n uint256 internal constant UNSORTED_ARRAY = 101;\n uint256 internal constant UNSORTED_TOKENS = 102;\n uint256 internal constant INPUT_LENGTH_MISMATCH = 103;\n uint256 internal constant ZERO_TOKEN = 104;\n uint256 internal constant INSUFFICIENT_DATA = 105;\n\n // Shared pools\n uint256 internal constant MIN_TOKENS = 200;\n uint256 internal constant MAX_TOKENS = 201;\n uint256 internal constant MAX_SWAP_FEE_PERCENTAGE = 202;\n uint256 internal constant MIN_SWAP_FEE_PERCENTAGE = 203;\n uint256 internal constant MINIMUM_BPT = 204;\n uint256 internal constant CALLER_NOT_VAULT = 205;\n uint256 internal constant UNINITIALIZED = 206;\n uint256 internal constant BPT_IN_MAX_AMOUNT = 207;\n uint256 internal constant BPT_OUT_MIN_AMOUNT = 208;\n uint256 internal constant EXPIRED_PERMIT = 209;\n uint256 internal constant NOT_TWO_TOKENS = 210;\n uint256 internal constant DISABLED = 211;\n\n // Pools\n uint256 internal constant MIN_AMP = 300;\n uint256 internal constant MAX_AMP = 301;\n uint256 internal constant MIN_WEIGHT = 302;\n uint256 internal constant MAX_STABLE_TOKENS = 303;\n uint256 internal constant MAX_IN_RATIO = 304;\n uint256 internal constant MAX_OUT_RATIO = 305;\n uint256 internal constant MIN_BPT_IN_FOR_TOKEN_OUT = 306;\n uint256 internal constant MAX_OUT_BPT_FOR_TOKEN_IN = 307;\n uint256 internal constant NORMALIZED_WEIGHT_INVARIANT = 308;\n uint256 internal constant INVALID_TOKEN = 309;\n uint256 internal constant UNHANDLED_JOIN_KIND = 310;\n uint256 internal constant ZERO_INVARIANT = 311;\n uint256 internal constant ORACLE_INVALID_SECONDS_QUERY = 312;\n uint256 internal constant ORACLE_NOT_INITIALIZED = 313;\n uint256 internal constant ORACLE_QUERY_TOO_OLD = 314;\n uint256 internal constant ORACLE_INVALID_INDEX = 315;\n uint256 internal constant ORACLE_BAD_SECS = 316;\n uint256 internal constant AMP_END_TIME_TOO_CLOSE = 317;\n uint256 internal constant AMP_ONGOING_UPDATE = 318;\n uint256 internal constant AMP_RATE_TOO_HIGH = 319;\n uint256 internal constant AMP_NO_ONGOING_UPDATE = 320;\n uint256 internal constant STABLE_INVARIANT_DIDNT_CONVERGE = 321;\n uint256 internal constant STABLE_GET_BALANCE_DIDNT_CONVERGE = 322;\n uint256 internal constant RELAYER_NOT_CONTRACT = 323;\n uint256 internal constant BASE_POOL_RELAYER_NOT_CALLED = 324;\n uint256 internal constant REBALANCING_RELAYER_REENTERED = 325;\n uint256 internal constant GRADUAL_UPDATE_TIME_TRAVEL = 326;\n uint256 internal constant SWAPS_DISABLED = 327;\n uint256 internal constant CALLER_IS_NOT_LBP_OWNER = 328;\n uint256 internal constant PRICE_RATE_OVERFLOW = 329;\n uint256 internal constant INVALID_JOIN_EXIT_KIND_WHILE_SWAPS_DISABLED = 330;\n uint256 internal constant WEIGHT_CHANGE_TOO_FAST = 331;\n uint256 internal constant LOWER_GREATER_THAN_UPPER_TARGET = 332;\n uint256 internal constant UPPER_TARGET_TOO_HIGH = 333;\n uint256 internal constant UNHANDLED_BY_LINEAR_POOL = 334;\n uint256 internal constant OUT_OF_TARGET_RANGE = 335;\n uint256 internal constant UNHANDLED_EXIT_KIND = 336;\n uint256 internal constant UNAUTHORIZED_EXIT = 337;\n uint256 internal constant MAX_MANAGEMENT_SWAP_FEE_PERCENTAGE = 338;\n uint256 internal constant UNHANDLED_BY_MANAGED_POOL = 339;\n uint256 internal constant UNHANDLED_BY_PHANTOM_POOL = 340;\n uint256 internal constant TOKEN_DOES_NOT_HAVE_RATE_PROVIDER = 341;\n uint256 internal constant INVALID_INITIALIZATION = 342;\n uint256 internal constant OUT_OF_NEW_TARGET_RANGE = 343;\n uint256 internal constant FEATURE_DISABLED = 344;\n uint256 internal constant UNINITIALIZED_POOL_CONTROLLER = 345;\n uint256 internal constant SET_SWAP_FEE_DURING_FEE_CHANGE = 346;\n uint256 internal constant SET_SWAP_FEE_PENDING_FEE_CHANGE = 347;\n uint256 internal constant CHANGE_TOKENS_DURING_WEIGHT_CHANGE = 348;\n uint256 internal constant CHANGE_TOKENS_PENDING_WEIGHT_CHANGE = 349;\n uint256 internal constant MAX_WEIGHT = 350;\n uint256 internal constant UNAUTHORIZED_JOIN = 351;\n uint256 internal constant MAX_MANAGEMENT_AUM_FEE_PERCENTAGE = 352;\n uint256 internal constant FRACTIONAL_TARGET = 353;\n uint256 internal constant ADD_OR_REMOVE_BPT = 354;\n uint256 internal constant INVALID_CIRCUIT_BREAKER_BOUNDS = 355;\n uint256 internal constant CIRCUIT_BREAKER_TRIPPED = 356;\n uint256 internal constant MALICIOUS_QUERY_REVERT = 357;\n uint256 internal constant JOINS_EXITS_DISABLED = 358;\n\n // Lib\n uint256 internal constant REENTRANCY = 400;\n uint256 internal constant SENDER_NOT_ALLOWED = 401;\n uint256 internal constant PAUSED = 402;\n uint256 internal constant PAUSE_WINDOW_EXPIRED = 403;\n uint256 internal constant MAX_PAUSE_WINDOW_DURATION = 404;\n uint256 internal constant MAX_BUFFER_PERIOD_DURATION = 405;\n uint256 internal constant INSUFFICIENT_BALANCE = 406;\n uint256 internal constant INSUFFICIENT_ALLOWANCE = 407;\n uint256 internal constant ERC20_TRANSFER_FROM_ZERO_ADDRESS = 408;\n uint256 internal constant ERC20_TRANSFER_TO_ZERO_ADDRESS = 409;\n uint256 internal constant ERC20_MINT_TO_ZERO_ADDRESS = 410;\n uint256 internal constant ERC20_BURN_FROM_ZERO_ADDRESS = 411;\n uint256 internal constant ERC20_APPROVE_FROM_ZERO_ADDRESS = 412;\n uint256 internal constant ERC20_APPROVE_TO_ZERO_ADDRESS = 413;\n uint256 internal constant ERC20_TRANSFER_EXCEEDS_ALLOWANCE = 414;\n uint256 internal constant ERC20_DECREASED_ALLOWANCE_BELOW_ZERO = 415;\n uint256 internal constant ERC20_TRANSFER_EXCEEDS_BALANCE = 416;\n uint256 internal constant ERC20_BURN_EXCEEDS_ALLOWANCE = 417;\n uint256 internal constant SAFE_ERC20_CALL_FAILED = 418;\n uint256 internal constant ADDRESS_INSUFFICIENT_BALANCE = 419;\n uint256 internal constant ADDRESS_CANNOT_SEND_VALUE = 420;\n uint256 internal constant SAFE_CAST_VALUE_CANT_FIT_INT256 = 421;\n uint256 internal constant GRANT_SENDER_NOT_ADMIN = 422;\n uint256 internal constant REVOKE_SENDER_NOT_ADMIN = 423;\n uint256 internal constant RENOUNCE_SENDER_NOT_ALLOWED = 424;\n uint256 internal constant BUFFER_PERIOD_EXPIRED = 425;\n uint256 internal constant CALLER_IS_NOT_OWNER = 426;\n uint256 internal constant NEW_OWNER_IS_ZERO = 427;\n uint256 internal constant CODE_DEPLOYMENT_FAILED = 428;\n uint256 internal constant CALL_TO_NON_CONTRACT = 429;\n uint256 internal constant LOW_LEVEL_CALL_FAILED = 430;\n uint256 internal constant NOT_PAUSED = 431;\n uint256 internal constant ADDRESS_ALREADY_ALLOWLISTED = 432;\n uint256 internal constant ADDRESS_NOT_ALLOWLISTED = 433;\n uint256 internal constant ERC20_BURN_EXCEEDS_BALANCE = 434;\n uint256 internal constant INVALID_OPERATION = 435;\n uint256 internal constant CODEC_OVERFLOW = 436;\n uint256 internal constant IN_RECOVERY_MODE = 437;\n uint256 internal constant NOT_IN_RECOVERY_MODE = 438;\n uint256 internal constant INDUCED_FAILURE = 439;\n uint256 internal constant EXPIRED_SIGNATURE = 440;\n uint256 internal constant MALFORMED_SIGNATURE = 441;\n uint256 internal constant SAFE_CAST_VALUE_CANT_FIT_UINT64 = 442;\n uint256 internal constant UNHANDLED_FEE_TYPE = 443;\n uint256 internal constant BURN_FROM_ZERO = 444;\n\n // Vault\n uint256 internal constant INVALID_POOL_ID = 500;\n uint256 internal constant CALLER_NOT_POOL = 501;\n uint256 internal constant SENDER_NOT_ASSET_MANAGER = 502;\n uint256 internal constant USER_DOESNT_ALLOW_RELAYER = 503;\n uint256 internal constant INVALID_SIGNATURE = 504;\n uint256 internal constant EXIT_BELOW_MIN = 505;\n uint256 internal constant JOIN_ABOVE_MAX = 506;\n uint256 internal constant SWAP_LIMIT = 507;\n uint256 internal constant SWAP_DEADLINE = 508;\n uint256 internal constant CANNOT_SWAP_SAME_TOKEN = 509;\n uint256 internal constant UNKNOWN_AMOUNT_IN_FIRST_SWAP = 510;\n uint256 internal constant MALCONSTRUCTED_MULTIHOP_SWAP = 511;\n uint256 internal constant INTERNAL_BALANCE_OVERFLOW = 512;\n uint256 internal constant INSUFFICIENT_INTERNAL_BALANCE = 513;\n uint256 internal constant INVALID_ETH_INTERNAL_BALANCE = 514;\n uint256 internal constant INVALID_POST_LOAN_BALANCE = 515;\n uint256 internal constant INSUFFICIENT_ETH = 516;\n uint256 internal constant UNALLOCATED_ETH = 517;\n uint256 internal constant ETH_TRANSFER = 518;\n uint256 internal constant CANNOT_USE_ETH_SENTINEL = 519;\n uint256 internal constant TOKENS_MISMATCH = 520;\n uint256 internal constant TOKEN_NOT_REGISTERED = 521;\n uint256 internal constant TOKEN_ALREADY_REGISTERED = 522;\n uint256 internal constant TOKENS_ALREADY_SET = 523;\n uint256 internal constant TOKENS_LENGTH_MUST_BE_2 = 524;\n uint256 internal constant NONZERO_TOKEN_BALANCE = 525;\n uint256 internal constant BALANCE_TOTAL_OVERFLOW = 526;\n uint256 internal constant POOL_NO_TOKENS = 527;\n uint256 internal constant INSUFFICIENT_FLASH_LOAN_BALANCE = 528;\n\n // Fees\n uint256 internal constant SWAP_FEE_PERCENTAGE_TOO_HIGH = 600;\n uint256 internal constant FLASH_LOAN_FEE_PERCENTAGE_TOO_HIGH = 601;\n uint256 internal constant INSUFFICIENT_FLASH_LOAN_FEE_AMOUNT = 602;\n uint256 internal constant AUM_FEE_PERCENTAGE_TOO_HIGH = 603;\n\n // FeeSplitter\n uint256 internal constant SPLITTER_FEE_PERCENTAGE_TOO_HIGH = 700;\n\n // Misc\n uint256 internal constant UNIMPLEMENTED = 998;\n uint256 internal constant SHOULD_NOT_HAPPEN = 999;\n}\n" + }, + "contracts/utils/BytesHelper.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nuint256 constant UINT32_LENGTH = 4;\nuint256 constant UINT64_LENGTH = 8;\nuint256 constant UINT256_LENGTH = 32;\n// Address is 20 bytes, but we expect the data to be padded with 0s to 32 bytes\nuint256 constant ADDRESS_LENGTH = 32;\n\nlibrary BytesHelper {\n /**\n * @dev Extract a slice from bytes memory\n * @param data The bytes memory to slice\n * @param start The start index (inclusive)\n * @param end The end index (exclusive)\n * @return result A new bytes memory containing the slice\n */\n function extractSlice(\n bytes memory data,\n uint256 start,\n uint256 end\n ) internal pure returns (bytes memory) {\n require(end >= start, \"Invalid slice range\");\n require(end <= data.length, \"Slice end exceeds data length\");\n\n uint256 length = end - start;\n bytes memory result = new bytes(length);\n\n // Simple byte-by-byte copy\n for (uint256 i = 0; i < length; i++) {\n result[i] = data[start + i];\n }\n\n return result;\n }\n\n /**\n * @dev Decode a uint32 from a bytes memory\n * @param data The bytes memory to decode\n * @return uint32 The decoded uint32\n */\n function decodeUint32(bytes memory data) internal pure returns (uint32) {\n require(data.length == 4, \"Invalid data length\");\n return uint32(uint256(bytes32(data)) >> 224);\n }\n\n /**\n * @dev Extract a uint32 from a bytes memory\n * @param data The bytes memory to extract from\n * @param start The start index (inclusive)\n * @return uint32 The extracted uint32\n */\n function extractUint32(bytes memory data, uint256 start)\n internal\n pure\n returns (uint32)\n {\n return decodeUint32(extractSlice(data, start, start + UINT32_LENGTH));\n }\n\n /**\n * @dev Decode an address from a bytes memory.\n * Expects the data to be padded with 0s to 32 bytes.\n * @param data The bytes memory to decode\n * @return address The decoded address\n */\n function decodeAddress(bytes memory data) internal pure returns (address) {\n // We expect the data to be padded with 0s, so length is 32 not 20\n require(data.length == 32, \"Invalid data length\");\n return abi.decode(data, (address));\n }\n\n /**\n * @dev Extract an address from a bytes memory\n * @param data The bytes memory to extract from\n * @param start The start index (inclusive)\n * @return address The extracted address\n */\n function extractAddress(bytes memory data, uint256 start)\n internal\n pure\n returns (address)\n {\n return decodeAddress(extractSlice(data, start, start + ADDRESS_LENGTH));\n }\n\n /**\n * @dev Decode a uint256 from a bytes memory\n * @param data The bytes memory to decode\n * @return uint256 The decoded uint256\n */\n function decodeUint256(bytes memory data) internal pure returns (uint256) {\n require(data.length == 32, \"Invalid data length\");\n return abi.decode(data, (uint256));\n }\n\n /**\n * @dev Extract a uint256 from a bytes memory\n * @param data The bytes memory to extract from\n * @param start The start index (inclusive)\n * @return uint256 The extracted uint256\n */\n function extractUint256(bytes memory data, uint256 start)\n internal\n pure\n returns (uint256)\n {\n return decodeUint256(extractSlice(data, start, start + UINT256_LENGTH));\n }\n}\n" + }, + "contracts/utils/Helpers.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { IBasicToken } from \"../interfaces/IBasicToken.sol\";\n\nlibrary Helpers {\n /**\n * @notice Fetch the `symbol()` from an ERC20 token\n * @dev Grabs the `symbol()` from a contract\n * @param _token Address of the ERC20 token\n * @return string Symbol of the ERC20 token\n */\n function getSymbol(address _token) internal view returns (string memory) {\n string memory symbol = IBasicToken(_token).symbol();\n return symbol;\n }\n\n /**\n * @notice Fetch the `decimals()` from an ERC20 token\n * @dev Grabs the `decimals()` from a contract and fails if\n * the decimal value does not live within a certain range\n * @param _token Address of the ERC20 token\n * @return uint256 Decimals of the ERC20 token\n */\n function getDecimals(address _token) internal view returns (uint256) {\n uint256 decimals = IBasicToken(_token).decimals();\n require(\n decimals >= 4 && decimals <= 18,\n \"Token must have sufficient decimal places\"\n );\n\n return decimals;\n }\n}\n" + }, + "contracts/utils/Initializable.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title Base contract any contracts that need to initialize state after deployment.\n * @author Origin Protocol Inc\n */\nabstract contract Initializable {\n /**\n * @dev Indicates that the contract has been initialized.\n */\n bool private initialized;\n\n /**\n * @dev Indicates that the contract is in the process of being initialized.\n */\n bool private initializing;\n\n /**\n * @dev Modifier to protect an initializer function from being invoked twice.\n */\n modifier initializer() {\n require(\n initializing || !initialized,\n \"Initializable: contract is already initialized\"\n );\n\n bool isTopLevelCall = !initializing;\n if (isTopLevelCall) {\n initializing = true;\n initialized = true;\n }\n\n _;\n\n if (isTopLevelCall) {\n initializing = false;\n }\n }\n\n uint256[50] private ______gap;\n}\n" + }, + "contracts/utils/InitializableAbstractStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title Base contract for vault strategies.\n * @author Origin Protocol Inc\n */\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\n\nimport { Initializable } from \"../utils/Initializable.sol\";\nimport { Governable } from \"../governance/Governable.sol\";\nimport { IVault } from \"../interfaces/IVault.sol\";\n\nabstract contract InitializableAbstractStrategy is Initializable, Governable {\n using SafeERC20 for IERC20;\n\n event PTokenAdded(address indexed _asset, address _pToken);\n event PTokenRemoved(address indexed _asset, address _pToken);\n event Deposit(address indexed _asset, address _pToken, uint256 _amount);\n event Withdrawal(address indexed _asset, address _pToken, uint256 _amount);\n event RewardTokenCollected(\n address recipient,\n address rewardToken,\n uint256 amount\n );\n event RewardTokenAddressesUpdated(\n address[] _oldAddresses,\n address[] _newAddresses\n );\n event HarvesterAddressesUpdated(\n address _oldHarvesterAddress,\n address _newHarvesterAddress\n );\n\n /// @notice Address of the underlying platform\n address public immutable platformAddress;\n /// @notice Address of the OToken vault\n address public immutable vaultAddress;\n\n /// @dev Replaced with an immutable variable\n // slither-disable-next-line constable-states\n address private _deprecated_platformAddress;\n\n /// @dev Replaced with an immutable\n // slither-disable-next-line constable-states\n address private _deprecated_vaultAddress;\n\n /// @notice asset => pToken (Platform Specific Token Address)\n mapping(address => address) public assetToPToken;\n\n /// @notice Full list of all assets supported by the strategy\n address[] internal assetsMapped;\n\n // Deprecated: Reward token address\n // slither-disable-next-line constable-states\n address private _deprecated_rewardTokenAddress;\n\n // Deprecated: now resides in Harvester's rewardTokenConfigs\n // slither-disable-next-line constable-states\n uint256 private _deprecated_rewardLiquidationThreshold;\n\n /// @notice Address of the Harvester contract allowed to collect reward tokens\n address public harvesterAddress;\n\n /// @notice Address of the reward tokens. eg CRV, BAL, CVX, AURA\n address[] public rewardTokenAddresses;\n\n /* Reserved for future expansion. Used to be 100 storage slots\n * and has decreased to accommodate:\n * - harvesterAddress\n * - rewardTokenAddresses\n */\n int256[98] private _reserved;\n\n struct BaseStrategyConfig {\n address platformAddress; // Address of the underlying platform\n address vaultAddress; // Address of the OToken's Vault\n }\n\n /**\n * @dev Verifies that the caller is the Governor or Strategist.\n */\n modifier onlyGovernorOrStrategist() virtual {\n require(\n isGovernor() || msg.sender == IVault(vaultAddress).strategistAddr(),\n \"Caller is not the Strategist or Governor\"\n );\n _;\n }\n\n /**\n * @param _config The platform and OToken vault addresses\n */\n constructor(BaseStrategyConfig memory _config) {\n platformAddress = _config.platformAddress;\n vaultAddress = _config.vaultAddress;\n }\n\n /**\n * @dev Internal initialize function, to set up initial internal state\n * @param _rewardTokenAddresses Address of reward token for platform\n * @param _assets Addresses of initial supported assets\n * @param _pTokens Platform Token corresponding addresses\n */\n function _initialize(\n address[] memory _rewardTokenAddresses,\n address[] memory _assets,\n address[] memory _pTokens\n ) internal {\n rewardTokenAddresses = _rewardTokenAddresses;\n\n uint256 assetCount = _assets.length;\n require(assetCount == _pTokens.length, \"Invalid input arrays\");\n for (uint256 i = 0; i < assetCount; ++i) {\n _setPTokenAddress(_assets[i], _pTokens[i]);\n }\n }\n\n /**\n * @notice Collect accumulated reward token and send to Vault.\n */\n function collectRewardTokens() external virtual onlyHarvester nonReentrant {\n _collectRewardTokens();\n }\n\n /**\n * @dev Default implementation that transfers reward tokens to the Harvester\n * Implementing strategies need to add custom logic to collect the rewards.\n */\n function _collectRewardTokens() internal virtual {\n uint256 rewardTokenCount = rewardTokenAddresses.length;\n for (uint256 i = 0; i < rewardTokenCount; ++i) {\n IERC20 rewardToken = IERC20(rewardTokenAddresses[i]);\n uint256 balance = rewardToken.balanceOf(address(this));\n if (balance > 0) {\n emit RewardTokenCollected(\n harvesterAddress,\n address(rewardToken),\n balance\n );\n rewardToken.safeTransfer(harvesterAddress, balance);\n }\n }\n }\n\n /**\n * @dev Verifies that the caller is the Vault.\n */\n modifier onlyVault() {\n require(msg.sender == vaultAddress, \"Caller is not the Vault\");\n _;\n }\n\n /**\n * @dev Verifies that the caller is the Harvester.\n */\n modifier onlyHarvester() {\n require(msg.sender == harvesterAddress, \"Caller is not the Harvester\");\n _;\n }\n\n /**\n * @dev Verifies that the caller is the Vault or Governor.\n */\n modifier onlyVaultOrGovernor() {\n require(\n msg.sender == vaultAddress || msg.sender == governor(),\n \"Caller is not the Vault or Governor\"\n );\n _;\n }\n\n /**\n * @dev Verifies that the caller is the Vault, Governor, or Strategist.\n */\n modifier onlyVaultOrGovernorOrStrategist() {\n require(\n msg.sender == vaultAddress ||\n msg.sender == governor() ||\n msg.sender == IVault(vaultAddress).strategistAddr(),\n \"Caller is not the Vault, Governor, or Strategist\"\n );\n _;\n }\n\n /**\n * @notice Set the reward token addresses. Any old addresses will be overwritten.\n * @param _rewardTokenAddresses Array of reward token addresses\n */\n function setRewardTokenAddresses(address[] calldata _rewardTokenAddresses)\n external\n onlyGovernor\n {\n uint256 rewardTokenCount = _rewardTokenAddresses.length;\n for (uint256 i = 0; i < rewardTokenCount; ++i) {\n require(\n _rewardTokenAddresses[i] != address(0),\n \"Can not set an empty address as a reward token\"\n );\n }\n\n emit RewardTokenAddressesUpdated(\n rewardTokenAddresses,\n _rewardTokenAddresses\n );\n rewardTokenAddresses = _rewardTokenAddresses;\n }\n\n /**\n * @notice Get the reward token addresses.\n * @return address[] the reward token addresses.\n */\n function getRewardTokenAddresses()\n external\n view\n returns (address[] memory)\n {\n return rewardTokenAddresses;\n }\n\n /**\n * @notice Provide support for asset by passing its pToken address.\n * This method can only be called by the system Governor\n * @param _asset Address for the asset\n * @param _pToken Address for the corresponding platform token\n */\n function setPTokenAddress(address _asset, address _pToken)\n external\n virtual\n onlyGovernor\n {\n _setPTokenAddress(_asset, _pToken);\n }\n\n /**\n * @notice Remove a supported asset by passing its index.\n * This method can only be called by the system Governor\n * @param _assetIndex Index of the asset to be removed\n */\n function removePToken(uint256 _assetIndex) external virtual onlyGovernor {\n require(_assetIndex < assetsMapped.length, \"Invalid index\");\n address asset = assetsMapped[_assetIndex];\n address pToken = assetToPToken[asset];\n\n if (_assetIndex < assetsMapped.length - 1) {\n assetsMapped[_assetIndex] = assetsMapped[assetsMapped.length - 1];\n }\n assetsMapped.pop();\n assetToPToken[asset] = address(0);\n\n emit PTokenRemoved(asset, pToken);\n }\n\n /**\n * @notice Provide support for asset by passing its pToken address.\n * Add to internal mappings and execute the platform specific,\n * abstract method `_abstractSetPToken`\n * @param _asset Address for the asset\n * @param _pToken Address for the corresponding platform token\n */\n function _setPTokenAddress(address _asset, address _pToken) internal {\n require(assetToPToken[_asset] == address(0), \"pToken already set\");\n require(\n _asset != address(0) && _pToken != address(0),\n \"Invalid addresses\"\n );\n\n assetToPToken[_asset] = _pToken;\n assetsMapped.push(_asset);\n\n emit PTokenAdded(_asset, _pToken);\n\n _abstractSetPToken(_asset, _pToken);\n }\n\n /**\n * @notice Transfer token to governor. Intended for recovering tokens stuck in\n * strategy contracts, i.e. mistaken sends.\n * @param _asset Address for the asset\n * @param _amount Amount of the asset to transfer\n */\n function transferToken(address _asset, uint256 _amount)\n public\n virtual\n onlyGovernor\n {\n require(!supportsAsset(_asset), \"Cannot transfer supported asset\");\n IERC20(_asset).safeTransfer(governor(), _amount);\n }\n\n /**\n * @notice Set the Harvester contract that can collect rewards.\n * @param _harvesterAddress Address of the harvester contract.\n */\n function setHarvesterAddress(address _harvesterAddress)\n external\n onlyGovernor\n {\n emit HarvesterAddressesUpdated(harvesterAddress, _harvesterAddress);\n harvesterAddress = _harvesterAddress;\n }\n\n /***************************************\n Abstract\n ****************************************/\n\n function _abstractSetPToken(address _asset, address _pToken)\n internal\n virtual;\n\n function safeApproveAllTokens() external virtual;\n\n /**\n * @notice Deposit an amount of assets into the platform\n * @param _asset Address for the asset\n * @param _amount Units of asset to deposit\n */\n function deposit(address _asset, uint256 _amount) external virtual;\n\n /**\n * @notice Deposit all supported assets in this strategy contract to the platform\n */\n function depositAll() external virtual;\n\n /**\n * @notice Withdraw an `amount` of assets from the platform and\n * send to the `_recipient`.\n * @param _recipient Address to which the asset should be sent\n * @param _asset Address of the asset\n * @param _amount Units of asset to withdraw\n */\n function withdraw(\n address _recipient,\n address _asset,\n uint256 _amount\n ) external virtual;\n\n /**\n * @notice Withdraw all supported assets from platform and\n * sends to the OToken's Vault.\n */\n function withdrawAll() external virtual;\n\n /**\n * @notice Get the total asset value held in the platform.\n * This includes any interest that was generated since depositing.\n * @param _asset Address of the asset\n * @return balance Total value of the asset in the platform\n */\n function checkBalance(address _asset)\n external\n view\n virtual\n returns (uint256 balance);\n\n /**\n * @notice Check if an asset is supported.\n * @param _asset Address of the asset\n * @return bool Whether asset is supported\n */\n function supportsAsset(address _asset) public view virtual returns (bool);\n}\n" + }, + "contracts/utils/PRBMath.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n// Copied from the PRBMath library\n// https://github.com/PaulRBerg/prb-math/blob/main/src/Common.sol\n\n/// @notice Calculates the square root of x using the Babylonian method.\n///\n/// @dev See https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method.\n///\n/// Notes:\n/// - If x is not a perfect square, the result is rounded down.\n/// - Credits to OpenZeppelin for the explanations in comments below.\n///\n/// @param x The uint256 number for which to calculate the square root.\n/// @return result The result as a uint256.\n/// @custom:smtchecker abstract-function-nondet\nfunction sqrt(uint256 x) pure returns (uint256 result) {\n if (x == 0) {\n return 0;\n }\n\n // For our first guess, we calculate the biggest power of 2 which is smaller than the square root of x.\n //\n // We know that the \"msb\" (most significant bit) of x is a power of 2 such that we have:\n //\n // $$\n // msb(x) <= x <= 2*msb(x)$\n // $$\n //\n // We write $msb(x)$ as $2^k$, and we get:\n //\n // $$\n // k = log_2(x)\n // $$\n //\n // Thus, we can write the initial inequality as:\n //\n // $$\n // 2^{log_2(x)} <= x <= 2*2^{log_2(x)+1} \\\\\n // sqrt(2^k) <= sqrt(x) < sqrt(2^{k+1}) \\\\\n // 2^{k/2} <= sqrt(x) < 2^{(k+1)/2} <= 2^{(k/2)+1}\n // $$\n //\n // Consequently, $2^{log_2(x) /2} is a good first approximation of sqrt(x) with at least one correct bit.\n uint256 xAux = uint256(x);\n result = 1;\n if (xAux >= 2**128) {\n xAux >>= 128;\n result <<= 64;\n }\n if (xAux >= 2**64) {\n xAux >>= 64;\n result <<= 32;\n }\n if (xAux >= 2**32) {\n xAux >>= 32;\n result <<= 16;\n }\n if (xAux >= 2**16) {\n xAux >>= 16;\n result <<= 8;\n }\n if (xAux >= 2**8) {\n xAux >>= 8;\n result <<= 4;\n }\n if (xAux >= 2**4) {\n xAux >>= 4;\n result <<= 2;\n }\n if (xAux >= 2**2) {\n result <<= 1;\n }\n\n // At this point, `result` is an estimation with at least one bit of precision. We know the true value has at\n // most 128 bits, since it is the square root of a uint256. Newton's method converges quadratically (precision\n // doubles at every iteration). We thus need at most 7 iteration to turn our partial result with one bit of\n // precision into the expected uint128 result.\n unchecked {\n result = (result + x / result) >> 1;\n result = (result + x / result) >> 1;\n result = (result + x / result) >> 1;\n result = (result + x / result) >> 1;\n result = (result + x / result) >> 1;\n result = (result + x / result) >> 1;\n result = (result + x / result) >> 1;\n\n // If x is not a perfect square, round the result toward zero.\n uint256 roundedResult = x / result;\n if (result >= roundedResult) {\n result = roundedResult;\n }\n }\n}\n" + }, + "contracts/utils/StableMath.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { SafeMath } from \"@openzeppelin/contracts/utils/math/SafeMath.sol\";\n\n// Based on StableMath from Stability Labs Pty. Ltd.\n// https://github.com/mstable/mStable-contracts/blob/master/contracts/shared/StableMath.sol\n\nlibrary StableMath {\n using SafeMath for uint256;\n\n /**\n * @dev Scaling unit for use in specific calculations,\n * where 1 * 10**18, or 1e18 represents a unit '1'\n */\n uint256 private constant FULL_SCALE = 1e18;\n\n /***************************************\n Helpers\n ****************************************/\n\n /**\n * @dev Adjust the scale of an integer\n * @param to Decimals to scale to\n * @param from Decimals to scale from\n */\n function scaleBy(\n uint256 x,\n uint256 to,\n uint256 from\n ) internal pure returns (uint256) {\n if (to > from) {\n x = x.mul(10**(to - from));\n } else if (to < from) {\n // slither-disable-next-line divide-before-multiply\n x = x.div(10**(from - to));\n }\n return x;\n }\n\n /***************************************\n Precise Arithmetic\n ****************************************/\n\n /**\n * @dev Multiplies two precise units, and then truncates by the full scale\n * @param x Left hand input to multiplication\n * @param y Right hand input to multiplication\n * @return Result after multiplying the two inputs and then dividing by the shared\n * scale unit\n */\n function mulTruncate(uint256 x, uint256 y) internal pure returns (uint256) {\n return mulTruncateScale(x, y, FULL_SCALE);\n }\n\n /**\n * @dev Multiplies two precise units, and then truncates by the given scale. For example,\n * when calculating 90% of 10e18, (10e18 * 9e17) / 1e18 = (9e36) / 1e18 = 9e18\n * @param x Left hand input to multiplication\n * @param y Right hand input to multiplication\n * @param scale Scale unit\n * @return Result after multiplying the two inputs and then dividing by the shared\n * scale unit\n */\n function mulTruncateScale(\n uint256 x,\n uint256 y,\n uint256 scale\n ) internal pure returns (uint256) {\n // e.g. assume scale = fullScale\n // z = 10e18 * 9e17 = 9e36\n uint256 z = x.mul(y);\n // return 9e36 / 1e18 = 9e18\n return z.div(scale);\n }\n\n /**\n * @dev Multiplies two precise units, and then truncates by the full scale, rounding up the result\n * @param x Left hand input to multiplication\n * @param y Right hand input to multiplication\n * @return Result after multiplying the two inputs and then dividing by the shared\n * scale unit, rounded up to the closest base unit.\n */\n function mulTruncateCeil(uint256 x, uint256 y)\n internal\n pure\n returns (uint256)\n {\n // e.g. 8e17 * 17268172638 = 138145381104e17\n uint256 scaled = x.mul(y);\n // e.g. 138145381104e17 + 9.99...e17 = 138145381113.99...e17\n uint256 ceil = scaled.add(FULL_SCALE.sub(1));\n // e.g. 13814538111.399...e18 / 1e18 = 13814538111\n return ceil.div(FULL_SCALE);\n }\n\n /**\n * @dev Precisely divides two units, by first scaling the left hand operand. Useful\n * for finding percentage weightings, i.e. 8e18/10e18 = 80% (or 8e17)\n * @param x Left hand input to division\n * @param y Right hand input to division\n * @return Result after multiplying the left operand by the scale, and\n * executing the division on the right hand input.\n */\n function divPrecisely(uint256 x, uint256 y)\n internal\n pure\n returns (uint256)\n {\n // e.g. 8e18 * 1e18 = 8e36\n uint256 z = x.mul(FULL_SCALE);\n // e.g. 8e36 / 10e18 = 8e17\n return z.div(y);\n }\n}\n" + }, + "contracts/vault/VaultStorage.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title OToken VaultStorage contract\n * @notice The VaultStorage contract defines the storage for the Vault contracts\n * @author Origin Protocol Inc\n */\n\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport { Address } from \"@openzeppelin/contracts/utils/Address.sol\";\n\nimport { IStrategy } from \"../interfaces/IStrategy.sol\";\nimport { IERC20Metadata } from \"@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol\";\nimport { Governable } from \"../governance/Governable.sol\";\nimport { OUSD } from \"../token/OUSD.sol\";\nimport { Initializable } from \"../utils/Initializable.sol\";\nimport \"../utils/Helpers.sol\";\n\nabstract contract VaultStorage is Initializable, Governable {\n using SafeERC20 for IERC20;\n\n event AssetAllocated(address _asset, address _strategy, uint256 _amount);\n event StrategyApproved(address _addr);\n event StrategyRemoved(address _addr);\n event Mint(address _addr, uint256 _value);\n event Redeem(address _addr, uint256 _value);\n event CapitalPaused();\n event CapitalUnpaused();\n event DefaultStrategyUpdated(address _strategy);\n event RebasePaused();\n event RebaseUnpaused();\n event VaultBufferUpdated(uint256 _vaultBuffer);\n event AllocateThresholdUpdated(uint256 _threshold);\n event RebaseThresholdUpdated(uint256 _threshold);\n event StrategistUpdated(address _address);\n event MaxSupplyDiffChanged(uint256 maxSupplyDiff);\n event YieldDistribution(address _to, uint256 _yield, uint256 _fee);\n event TrusteeFeeBpsChanged(uint256 _basis);\n event TrusteeAddressChanged(address _address);\n event StrategyAddedToMintWhitelist(address indexed strategy);\n event StrategyRemovedFromMintWhitelist(address indexed strategy);\n event RebasePerSecondMaxChanged(uint256 rebaseRatePerSecond);\n event DripDurationChanged(uint256 dripDuration);\n event WithdrawalRequested(\n address indexed _withdrawer,\n uint256 indexed _requestId,\n uint256 _amount,\n uint256 _queued\n );\n event WithdrawalClaimed(\n address indexed _withdrawer,\n uint256 indexed _requestId,\n uint256 _amount\n );\n event WithdrawalClaimable(uint256 _claimable, uint256 _newClaimable);\n event WithdrawalClaimDelayUpdated(uint256 _newDelay);\n\n // Since we are proxy, all state should be uninitalized.\n // Since this storage contract does not have logic directly on it\n // we should not be checking for to see if these variables can be constant.\n // slither-disable-start uninitialized-state\n // slither-disable-start constable-states\n\n /// @dev mapping of supported vault assets to their configuration\n uint256 private _deprecated_assets;\n /// @dev list of all assets supported by the vault.\n address[] private _deprecated_allAssets;\n\n // Strategies approved for use by the Vault\n struct Strategy {\n bool isSupported;\n uint256 _deprecated; // Deprecated storage slot\n }\n /// @dev mapping of strategy contracts to their configuration\n mapping(address => Strategy) public strategies;\n /// @dev list of all vault strategies\n address[] internal allStrategies;\n\n /// @notice Address of the Oracle price provider contract\n address private _deprecated_priceProvider;\n /// @notice pause rebasing if true\n bool public rebasePaused;\n /// @notice pause operations that change the OToken supply.\n /// eg mint, redeem, allocate, mint/burn for strategy\n bool public capitalPaused;\n /// @notice Redemption fee in basis points. eg 50 = 0.5%\n uint256 private _deprecated_redeemFeeBps;\n /// @notice Percentage of assets to keep in Vault to handle (most) withdrawals. 100% = 1e18.\n uint256 public vaultBuffer;\n /// @notice OToken mints over this amount automatically allocate funds. 18 decimals.\n uint256 public autoAllocateThreshold;\n /// @notice OToken mints over this amount automatically rebase. 18 decimals.\n uint256 public rebaseThreshold;\n\n /// @dev Address of the OToken token. eg OUSD or OETH.\n OUSD public oToken;\n\n /// @dev Address of the contract responsible for post rebase syncs with AMMs\n address private _deprecated_rebaseHooksAddr = address(0);\n\n /// @dev Deprecated: Address of Uniswap\n address private _deprecated_uniswapAddr = address(0);\n\n /// @notice Address of the Strategist\n address public strategistAddr = address(0);\n\n /// @notice Mapping of asset address to the Strategy that they should automatically\n // be allocated to\n uint256 private _deprecated_assetDefaultStrategies;\n\n /// @notice Max difference between total supply and total value of assets. 18 decimals.\n uint256 public maxSupplyDiff;\n\n /// @notice Trustee contract that can collect a percentage of yield\n address public trusteeAddress;\n\n /// @notice Amount of yield collected in basis points. eg 2000 = 20%\n uint256 public trusteeFeeBps;\n\n /// @dev Deprecated: Tokens that should be swapped for stablecoins\n address[] private _deprecated_swapTokens;\n\n /// @notice Metapool strategy that is allowed to mint/burn OTokens without changing collateral\n\n address private _deprecated_ousdMetaStrategy;\n\n /// @notice How much OTokens are currently minted by the strategy\n int256 private _deprecated_netOusdMintedForStrategy;\n\n /// @notice How much net total OTokens are allowed to be minted by all strategies\n uint256 private _deprecated_netOusdMintForStrategyThreshold;\n\n uint256 private _deprecated_swapConfig;\n\n // List of strategies that can mint oTokens directly\n // Used in OETHBaseVaultCore\n mapping(address => bool) public isMintWhitelistedStrategy;\n\n /// @notice Address of the Dripper contract that streams harvested rewards to the Vault\n /// @dev The vault is proxied so needs to be set with setDripper against the proxy contract.\n address private _deprecated_dripper;\n\n /// Withdrawal Queue Storage /////\n\n struct WithdrawalQueueMetadata {\n // cumulative total of all withdrawal requests included the ones that have already been claimed\n uint128 queued;\n // cumulative total of all the requests that can be claimed including the ones that have already been claimed\n uint128 claimable;\n // total of all the requests that have been claimed\n uint128 claimed;\n // index of the next withdrawal request starting at 0\n uint128 nextWithdrawalIndex;\n }\n\n /// @notice Global metadata for the withdrawal queue including:\n /// queued - cumulative total of all withdrawal requests included the ones that have already been claimed\n /// claimable - cumulative total of all the requests that can be claimed including the ones already claimed\n /// claimed - total of all the requests that have been claimed\n /// nextWithdrawalIndex - index of the next withdrawal request starting at 0\n WithdrawalQueueMetadata public withdrawalQueueMetadata;\n\n struct WithdrawalRequest {\n address withdrawer;\n bool claimed;\n uint40 timestamp; // timestamp of the withdrawal request\n // Amount of oTokens to redeem. eg OETH\n uint128 amount;\n // cumulative total of all withdrawal requests including this one.\n // this request can be claimed when this queued amount is less than or equal to the queue's claimable amount.\n uint128 queued;\n }\n\n /// @notice Mapping of withdrawal request indices to the user withdrawal request data\n mapping(uint256 => WithdrawalRequest) public withdrawalRequests;\n\n /// @notice Sets a minimum delay that is required to elapse between\n /// requesting async withdrawals and claiming the request.\n /// When set to 0 async withdrawals are disabled.\n uint256 public withdrawalClaimDelay;\n\n /// @notice Time in seconds that the vault last rebased yield.\n uint64 public lastRebase;\n\n /// @notice Automatic rebase yield calculations. In seconds. Set to 0 or 1 to disable.\n uint64 public dripDuration;\n\n /// @notice max rebase percentage per second\n /// Can be used to set maximum yield of the protocol,\n /// spreading out yield over time\n uint64 public rebasePerSecondMax;\n\n /// @notice target rebase rate limit, based on past rates and funds available.\n uint64 public rebasePerSecondTarget;\n\n uint256 internal constant MAX_REBASE = 0.02 ether;\n uint256 internal constant MAX_REBASE_PER_SECOND =\n uint256(0.05 ether) / 1 days;\n\n /// @notice Default strategy for asset\n address public defaultStrategy;\n\n // For future use\n uint256[42] private __gap;\n\n /// @notice Index of WETH asset in allAssets array\n /// Legacy OETHVaultCore code, relocated here for vault consistency.\n uint256 private _deprecated_wethAssetIndex;\n\n /// @dev Address of the asset (eg. WETH or USDC)\n address public immutable asset;\n uint8 internal immutable assetDecimals;\n\n // slither-disable-end constable-states\n // slither-disable-end uninitialized-state\n\n constructor(address _asset) {\n uint8 _decimals = IERC20Metadata(_asset).decimals();\n require(_decimals <= 18, \"invalid asset decimals\");\n asset = _asset;\n assetDecimals = _decimals;\n }\n\n /// @notice Deprecated: use `oToken()` instead.\n function oUSD() external view returns (OUSD) {\n return oToken;\n }\n}\n" + }, + "lib/openzeppelin/interfaces/IERC4626.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\nimport { IERC20Metadata } from \"@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol\";\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\n\ninterface IERC4626 is IERC20, IERC20Metadata {\n event Deposit(address indexed caller, address indexed owner, uint256 assets, uint256 shares);\n\n event Withdraw(\n address indexed caller,\n address indexed receiver,\n address indexed owner,\n uint256 assets,\n uint256 shares\n );\n\n /**\n * @dev Returns the address of the underlying token used for the Vault for accounting, depositing, and withdrawing.\n *\n * - MUST be an ERC-20 token contract.\n * - MUST NOT revert.\n */\n function asset() external view returns (address assetTokenAddress);\n\n /**\n * @dev Returns the total amount of the underlying asset that is “managed” by Vault.\n *\n * - SHOULD include any compounding that occurs from yield.\n * - MUST be inclusive of any fees that are charged against assets in the Vault.\n * - MUST NOT revert.\n */\n function totalAssets() external view returns (uint256 totalManagedAssets);\n\n /**\n * @dev Returns the amount of shares that the Vault would exchange for the amount of assets provided, in an ideal\n * scenario where all the conditions are met.\n *\n * - MUST NOT be inclusive of any fees that are charged against assets in the Vault.\n * - MUST NOT show any variations depending on the caller.\n * - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange.\n * - MUST NOT revert.\n *\n * NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the\n * “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and\n * from.\n */\n function convertToShares(uint256 assets) external view returns (uint256 shares);\n\n /**\n * @dev Returns the amount of assets that the Vault would exchange for the amount of shares provided, in an ideal\n * scenario where all the conditions are met.\n *\n * - MUST NOT be inclusive of any fees that are charged against assets in the Vault.\n * - MUST NOT show any variations depending on the caller.\n * - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange.\n * - MUST NOT revert.\n *\n * NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the\n * “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and\n * from.\n */\n function convertToAssets(uint256 shares) external view returns (uint256 assets);\n\n /**\n * @dev Returns the maximum amount of the underlying asset that can be deposited into the Vault for the receiver,\n * through a deposit call.\n *\n * - MUST return a limited value if receiver is subject to some deposit limit.\n * - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of assets that may be deposited.\n * - MUST NOT revert.\n */\n function maxDeposit(address receiver) external view returns (uint256 maxAssets);\n\n /**\n * @dev Allows an on-chain or off-chain user to simulate the effects of their deposit at the current block, given\n * current on-chain conditions.\n *\n * - MUST return as close to and no more than the exact amount of Vault shares that would be minted in a deposit\n * call in the same transaction. I.e. deposit should return the same or more shares as previewDeposit if called\n * in the same transaction.\n * - MUST NOT account for deposit limits like those returned from maxDeposit and should always act as though the\n * deposit would be accepted, regardless if the user has enough tokens approved, etc.\n * - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees.\n * - MUST NOT revert.\n *\n * NOTE: any unfavorable discrepancy between convertToShares and previewDeposit SHOULD be considered slippage in\n * share price or some other type of condition, meaning the depositor will lose assets by depositing.\n */\n function previewDeposit(uint256 assets) external view returns (uint256 shares);\n\n /**\n * @dev Mints shares Vault shares to receiver by depositing exactly amount of underlying tokens.\n *\n * - MUST emit the Deposit event.\n * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the\n * deposit execution, and are accounted for during deposit.\n * - MUST revert if all of assets cannot be deposited (due to deposit limit being reached, slippage, the user not\n * approving enough underlying tokens to the Vault contract, etc).\n *\n * NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token.\n */\n function deposit(uint256 assets, address receiver) external returns (uint256 shares);\n\n /**\n * @dev Returns the maximum amount of the Vault shares that can be minted for the receiver, through a mint call.\n * - MUST return a limited value if receiver is subject to some mint limit.\n * - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of shares that may be minted.\n * - MUST NOT revert.\n */\n function maxMint(address receiver) external view returns (uint256 maxShares);\n\n /**\n * @dev Allows an on-chain or off-chain user to simulate the effects of their mint at the current block, given\n * current on-chain conditions.\n *\n * - MUST return as close to and no fewer than the exact amount of assets that would be deposited in a mint call\n * in the same transaction. I.e. mint should return the same or fewer assets as previewMint if called in the\n * same transaction.\n * - MUST NOT account for mint limits like those returned from maxMint and should always act as though the mint\n * would be accepted, regardless if the user has enough tokens approved, etc.\n * - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees.\n * - MUST NOT revert.\n *\n * NOTE: any unfavorable discrepancy between convertToAssets and previewMint SHOULD be considered slippage in\n * share price or some other type of condition, meaning the depositor will lose assets by minting.\n */\n function previewMint(uint256 shares) external view returns (uint256 assets);\n\n /**\n * @dev Mints exactly shares Vault shares to receiver by depositing amount of underlying tokens.\n *\n * - MUST emit the Deposit event.\n * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the mint\n * execution, and are accounted for during mint.\n * - MUST revert if all of shares cannot be minted (due to deposit limit being reached, slippage, the user not\n * approving enough underlying tokens to the Vault contract, etc).\n *\n * NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token.\n */\n function mint(uint256 shares, address receiver) external returns (uint256 assets);\n\n /**\n * @dev Returns the maximum amount of the underlying asset that can be withdrawn from the owner balance in the\n * Vault, through a withdraw call.\n *\n * - MUST return a limited value if owner is subject to some withdrawal limit or timelock.\n * - MUST NOT revert.\n */\n function maxWithdraw(address owner) external view returns (uint256 maxAssets);\n\n /**\n * @dev Allows an on-chain or off-chain user to simulate the effects of their withdrawal at the current block,\n * given current on-chain conditions.\n *\n * - MUST return as close to and no fewer than the exact amount of Vault shares that would be burned in a withdraw\n * call in the same transaction. I.e. withdraw should return the same or fewer shares as previewWithdraw if\n * called\n * in the same transaction.\n * - MUST NOT account for withdrawal limits like those returned from maxWithdraw and should always act as though\n * the withdrawal would be accepted, regardless if the user has enough shares, etc.\n * - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees.\n * - MUST NOT revert.\n *\n * NOTE: any unfavorable discrepancy between convertToShares and previewWithdraw SHOULD be considered slippage in\n * share price or some other type of condition, meaning the depositor will lose assets by depositing.\n */\n function previewWithdraw(uint256 assets) external view returns (uint256 shares);\n\n /**\n * @dev Burns shares from owner and sends exactly assets of underlying tokens to receiver.\n *\n * - MUST emit the Withdraw event.\n * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the\n * withdraw execution, and are accounted for during withdraw.\n * - MUST revert if all of assets cannot be withdrawn (due to withdrawal limit being reached, slippage, the owner\n * not having enough shares, etc).\n *\n * Note that some implementations will require pre-requesting to the Vault before a withdrawal may be performed.\n * Those methods should be performed separately.\n */\n function withdraw(\n uint256 assets,\n address receiver,\n address owner\n ) external returns (uint256 shares);\n\n /**\n * @dev Returns the maximum amount of Vault shares that can be redeemed from the owner balance in the Vault,\n * through a redeem call.\n *\n * - MUST return a limited value if owner is subject to some withdrawal limit or timelock.\n * - MUST return balanceOf(owner) if owner is not subject to any withdrawal limit or timelock.\n * - MUST NOT revert.\n */\n function maxRedeem(address owner) external view returns (uint256 maxShares);\n\n /**\n * @dev Allows an on-chain or off-chain user to simulate the effects of their redeemption at the current block,\n * given current on-chain conditions.\n *\n * - MUST return as close to and no more than the exact amount of assets that would be withdrawn in a redeem call\n * in the same transaction. I.e. redeem should return the same or more assets as previewRedeem if called in the\n * same transaction.\n * - MUST NOT account for redemption limits like those returned from maxRedeem and should always act as though the\n * redemption would be accepted, regardless if the user has enough shares, etc.\n * - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees.\n * - MUST NOT revert.\n *\n * NOTE: any unfavorable discrepancy between convertToAssets and previewRedeem SHOULD be considered slippage in\n * share price or some other type of condition, meaning the depositor will lose assets by redeeming.\n */\n function previewRedeem(uint256 shares) external view returns (uint256 assets);\n\n /**\n * @dev Burns exactly shares from owner and sends assets of underlying tokens to receiver.\n *\n * - MUST emit the Withdraw event.\n * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the\n * redeem execution, and are accounted for during redeem.\n * - MUST revert if all of shares cannot be redeemed (due to withdrawal limit being reached, slippage, the owner\n * not having enough shares, etc).\n *\n * NOTE: some implementations will require pre-requesting to the Vault before a withdrawal may be performed.\n * Those methods should be performed separately.\n */\n function redeem(\n uint256 shares,\n address receiver,\n address owner\n ) external returns (uint256 assets);\n}" + }, + "lib/rooster/openzeppelin-custom/contracts/utils/math/Math.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.1.0) (utils/math/Math.sol)\n\npragma solidity ^0.8.20;\n\nimport {Panic} from \"../Panic.sol\";\nimport {SafeCast} from \"./SafeCast.sol\";\n\n/**\n * @dev Standard math utilities missing in the Solidity language.\n */\nlibrary Math {\n enum Rounding {\n Floor, // Toward negative infinity\n Ceil, // Toward positive infinity\n Trunc, // Toward zero\n Expand // Away from zero\n }\n\n /**\n * @dev Return the 512-bit addition of two uint256.\n *\n * The result is stored in two 256 variables such that sum = high * 2²⁵⁶ + low.\n */\n function add512(uint256 a, uint256 b) internal pure returns (uint256 high, uint256 low) {\n assembly (\"memory-safe\") {\n low := add(a, b)\n high := lt(low, a)\n }\n }\n\n /**\n * @dev Return the 512-bit multiplication of two uint256.\n *\n * The result is stored in two 256 variables such that product = high * 2²⁵⁶ + low.\n */\n function mul512(uint256 a, uint256 b) internal pure returns (uint256 high, uint256 low) {\n // 512-bit multiply [high low] = x * y. Compute the product mod 2²⁵⁶ and mod 2²⁵⁶ - 1, then use\n // the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256\n // variables such that product = high * 2²⁵⁶ + low.\n assembly (\"memory-safe\") {\n let mm := mulmod(a, b, not(0))\n low := mul(a, b)\n high := sub(sub(mm, low), lt(mm, low))\n }\n }\n\n /**\n * @dev Returns the addition of two unsigned integers, with a success flag (no overflow).\n */\n function tryAdd(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {\n unchecked {\n uint256 c = a + b;\n success = c >= a;\n result = c * SafeCast.toUint(success);\n }\n }\n\n /**\n * @dev Returns the subtraction of two unsigned integers, with a success flag (no overflow).\n */\n function trySub(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {\n unchecked {\n uint256 c = a - b;\n success = c <= a;\n result = c * SafeCast.toUint(success);\n }\n }\n\n /**\n * @dev Returns the multiplication of two unsigned integers, with a success flag (no overflow).\n */\n function tryMul(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {\n unchecked {\n uint256 c = a * b;\n assembly (\"memory-safe\") {\n // Only true when the multiplication doesn't overflow\n // (c / a == b) || (a == 0)\n success := or(eq(div(c, a), b), iszero(a))\n }\n // equivalent to: success ? c : 0\n result = c * SafeCast.toUint(success);\n }\n }\n\n /**\n * @dev Returns the division of two unsigned integers, with a success flag (no division by zero).\n */\n function tryDiv(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {\n unchecked {\n success = b > 0;\n assembly (\"memory-safe\") {\n // The `DIV` opcode returns zero when the denominator is 0.\n result := div(a, b)\n }\n }\n }\n\n /**\n * @dev Returns the remainder of dividing two unsigned integers, with a success flag (no division by zero).\n */\n function tryMod(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {\n unchecked {\n success = b > 0;\n assembly (\"memory-safe\") {\n // The `MOD` opcode returns zero when the denominator is 0.\n result := mod(a, b)\n }\n }\n }\n\n /**\n * @dev Unsigned saturating addition, bounds to `2²⁵⁶ - 1` instead of overflowing.\n */\n function saturatingAdd(uint256 a, uint256 b) internal pure returns (uint256) {\n (bool success, uint256 result) = tryAdd(a, b);\n return ternary(success, result, type(uint256).max);\n }\n\n /**\n * @dev Unsigned saturating subtraction, bounds to zero instead of overflowing.\n */\n function saturatingSub(uint256 a, uint256 b) internal pure returns (uint256) {\n (, uint256 result) = trySub(a, b);\n return result;\n }\n\n /**\n * @dev Unsigned saturating multiplication, bounds to `2²⁵⁶ - 1` instead of overflowing.\n */\n function saturatingMul(uint256 a, uint256 b) internal pure returns (uint256) {\n (bool success, uint256 result) = tryMul(a, b);\n return ternary(success, result, type(uint256).max);\n }\n\n /**\n * @dev Branchless ternary evaluation for `a ? b : c`. Gas costs are constant.\n *\n * IMPORTANT: This function may reduce bytecode size and consume less gas when used standalone.\n * However, the compiler may optimize Solidity ternary operations (i.e. `a ? b : c`) to only compute\n * one branch when needed, making this function more expensive.\n */\n function ternary(bool condition, uint256 a, uint256 b) internal pure returns (uint256) {\n unchecked {\n // branchless ternary works because:\n // b ^ (a ^ b) == a\n // b ^ 0 == b\n return b ^ ((a ^ b) * SafeCast.toUint(condition));\n }\n }\n\n /**\n * @dev Returns the largest of two numbers.\n */\n function max(uint256 a, uint256 b) internal pure returns (uint256) {\n return ternary(a > b, a, b);\n }\n\n /**\n * @dev Returns the smallest of two numbers.\n */\n function min(uint256 a, uint256 b) internal pure returns (uint256) {\n return ternary(a < b, a, b);\n }\n\n /**\n * @dev Returns the average of two numbers. The result is rounded towards\n * zero.\n */\n function average(uint256 a, uint256 b) internal pure returns (uint256) {\n // (a + b) / 2 can overflow.\n return (a & b) + (a ^ b) / 2;\n }\n\n /**\n * @dev Returns the ceiling of the division of two numbers.\n *\n * This differs from standard division with `/` in that it rounds towards infinity instead\n * of rounding towards zero.\n */\n function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {\n if (b == 0) {\n // Guarantee the same behavior as in a regular Solidity division.\n Panic.panic(Panic.DIVISION_BY_ZERO);\n }\n\n // The following calculation ensures accurate ceiling division without overflow.\n // Since a is non-zero, (a - 1) / b will not overflow.\n // The largest possible result occurs when (a - 1) / b is type(uint256).max,\n // but the largest value we can obtain is type(uint256).max - 1, which happens\n // when a = type(uint256).max and b = 1.\n unchecked {\n return SafeCast.toUint(a > 0) * ((a - 1) / b + 1);\n }\n }\n\n /**\n * @dev Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or\n * denominator == 0.\n *\n * Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv) with further edits by\n * Uniswap Labs also under MIT license.\n */\n function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {\n unchecked {\n (uint256 high, uint256 low) = mul512(x, y);\n\n // Handle non-overflow cases, 256 by 256 division.\n if (high == 0) {\n // Solidity will revert if denominator == 0, unlike the div opcode on its own.\n // The surrounding unchecked block does not change this fact.\n // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.\n return low / denominator;\n }\n\n // Make sure the result is less than 2²⁵⁶. Also prevents denominator == 0.\n if (denominator <= high) {\n Panic.panic(ternary(denominator == 0, Panic.DIVISION_BY_ZERO, Panic.UNDER_OVERFLOW));\n }\n\n ///////////////////////////////////////////////\n // 512 by 256 division.\n ///////////////////////////////////////////////\n\n // Make division exact by subtracting the remainder from [high low].\n uint256 remainder;\n assembly (\"memory-safe\") {\n // Compute remainder using mulmod.\n remainder := mulmod(x, y, denominator)\n\n // Subtract 256 bit number from 512 bit number.\n high := sub(high, gt(remainder, low))\n low := sub(low, remainder)\n }\n\n // Factor powers of two out of denominator and compute largest power of two divisor of denominator.\n // Always >= 1. See https://cs.stackexchange.com/q/138556/92363.\n\n uint256 twos = denominator & (0 - denominator);\n assembly (\"memory-safe\") {\n // Divide denominator by twos.\n denominator := div(denominator, twos)\n\n // Divide [high low] by twos.\n low := div(low, twos)\n\n // Flip twos such that it is 2²⁵⁶ / twos. If twos is zero, then it becomes one.\n twos := add(div(sub(0, twos), twos), 1)\n }\n\n // Shift in bits from high into low.\n low |= high * twos;\n\n // Invert denominator mod 2²⁵⁶. Now that denominator is an odd number, it has an inverse modulo 2²⁵⁶ such\n // that denominator * inv ≡ 1 mod 2²⁵⁶. Compute the inverse by starting with a seed that is correct for\n // four bits. That is, denominator * inv ≡ 1 mod 2⁴.\n uint256 inverse = (3 * denominator) ^ 2;\n\n // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also\n // works in modular arithmetic, doubling the correct bits in each step.\n inverse *= 2 - denominator * inverse; // inverse mod 2⁸\n inverse *= 2 - denominator * inverse; // inverse mod 2¹⁶\n inverse *= 2 - denominator * inverse; // inverse mod 2³²\n inverse *= 2 - denominator * inverse; // inverse mod 2⁶⁴\n inverse *= 2 - denominator * inverse; // inverse mod 2¹²⁸\n inverse *= 2 - denominator * inverse; // inverse mod 2²⁵⁶\n\n // Because the division is now exact we can divide by multiplying with the modular inverse of denominator.\n // This will give us the correct result modulo 2²⁵⁶. Since the preconditions guarantee that the outcome is\n // less than 2²⁵⁶, this is the final result. We don't need to compute the high bits of the result and high\n // is no longer required.\n result = low * inverse;\n return result;\n }\n }\n\n /**\n * @dev Calculates x * y / denominator with full precision, following the selected rounding direction.\n */\n function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {\n return mulDiv(x, y, denominator) + SafeCast.toUint(unsignedRoundsUp(rounding) && mulmod(x, y, denominator) > 0);\n }\n\n /**\n * @dev Calculates floor(x * y >> n) with full precision. Throws if result overflows a uint256.\n */\n function mulShr(uint256 x, uint256 y, uint8 n) internal pure returns (uint256 result) {\n unchecked {\n (uint256 high, uint256 low) = mul512(x, y);\n if (high >= 1 << n) {\n Panic.panic(Panic.UNDER_OVERFLOW);\n }\n return (high << (256 - n)) | (low >> n);\n }\n }\n\n /**\n * @dev Calculates x * y >> n with full precision, following the selected rounding direction.\n */\n function mulShr(uint256 x, uint256 y, uint8 n, Rounding rounding) internal pure returns (uint256) {\n return mulShr(x, y, n) + SafeCast.toUint(unsignedRoundsUp(rounding) && mulmod(x, y, 1 << n) > 0);\n }\n\n /**\n * @dev Calculate the modular multiplicative inverse of a number in Z/nZ.\n *\n * If n is a prime, then Z/nZ is a field. In that case all elements are inversible, except 0.\n * If n is not a prime, then Z/nZ is not a field, and some elements might not be inversible.\n *\n * If the input value is not inversible, 0 is returned.\n *\n * NOTE: If you know for sure that n is (big) a prime, it may be cheaper to use Fermat's little theorem and get the\n * inverse using `Math.modExp(a, n - 2, n)`. See {invModPrime}.\n */\n function invMod(uint256 a, uint256 n) internal pure returns (uint256) {\n unchecked {\n if (n == 0) return 0;\n\n // The inverse modulo is calculated using the Extended Euclidean Algorithm (iterative version)\n // Used to compute integers x and y such that: ax + ny = gcd(a, n).\n // When the gcd is 1, then the inverse of a modulo n exists and it's x.\n // ax + ny = 1\n // ax = 1 + (-y)n\n // ax ≡ 1 (mod n) # x is the inverse of a modulo n\n\n // If the remainder is 0 the gcd is n right away.\n uint256 remainder = a % n;\n uint256 gcd = n;\n\n // Therefore the initial coefficients are:\n // ax + ny = gcd(a, n) = n\n // 0a + 1n = n\n int256 x = 0;\n int256 y = 1;\n\n while (remainder != 0) {\n uint256 quotient = gcd / remainder;\n\n (gcd, remainder) = (\n // The old remainder is the next gcd to try.\n remainder,\n // Compute the next remainder.\n // Can't overflow given that (a % gcd) * (gcd // (a % gcd)) <= gcd\n // where gcd is at most n (capped to type(uint256).max)\n gcd - remainder * quotient\n );\n\n (x, y) = (\n // Increment the coefficient of a.\n y,\n // Decrement the coefficient of n.\n // Can overflow, but the result is casted to uint256 so that the\n // next value of y is \"wrapped around\" to a value between 0 and n - 1.\n x - y * int256(quotient)\n );\n }\n\n if (gcd != 1) return 0; // No inverse exists.\n return ternary(x < 0, n - uint256(-x), uint256(x)); // Wrap the result if it's negative.\n }\n }\n\n /**\n * @dev Variant of {invMod}. More efficient, but only works if `p` is known to be a prime greater than `2`.\n *\n * From https://en.wikipedia.org/wiki/Fermat%27s_little_theorem[Fermat's little theorem], we know that if p is\n * prime, then `a**(p-1) ≡ 1 mod p`. As a consequence, we have `a * a**(p-2) ≡ 1 mod p`, which means that\n * `a**(p-2)` is the modular multiplicative inverse of a in Fp.\n *\n * NOTE: this function does NOT check that `p` is a prime greater than `2`.\n */\n function invModPrime(uint256 a, uint256 p) internal view returns (uint256) {\n unchecked {\n return Math.modExp(a, p - 2, p);\n }\n }\n\n /**\n * @dev Returns the modular exponentiation of the specified base, exponent and modulus (b ** e % m)\n *\n * Requirements:\n * - modulus can't be zero\n * - underlying staticcall to precompile must succeed\n *\n * IMPORTANT: The result is only valid if the underlying call succeeds. When using this function, make\n * sure the chain you're using it on supports the precompiled contract for modular exponentiation\n * at address 0x05 as specified in https://eips.ethereum.org/EIPS/eip-198[EIP-198]. Otherwise,\n * the underlying function will succeed given the lack of a revert, but the result may be incorrectly\n * interpreted as 0.\n */\n function modExp(uint256 b, uint256 e, uint256 m) internal view returns (uint256) {\n (bool success, uint256 result) = tryModExp(b, e, m);\n if (!success) {\n Panic.panic(Panic.DIVISION_BY_ZERO);\n }\n return result;\n }\n\n /**\n * @dev Returns the modular exponentiation of the specified base, exponent and modulus (b ** e % m).\n * It includes a success flag indicating if the operation succeeded. Operation will be marked as failed if trying\n * to operate modulo 0 or if the underlying precompile reverted.\n *\n * IMPORTANT: The result is only valid if the success flag is true. When using this function, make sure the chain\n * you're using it on supports the precompiled contract for modular exponentiation at address 0x05 as specified in\n * https://eips.ethereum.org/EIPS/eip-198[EIP-198]. Otherwise, the underlying function will succeed given the lack\n * of a revert, but the result may be incorrectly interpreted as 0.\n */\n function tryModExp(uint256 b, uint256 e, uint256 m) internal view returns (bool success, uint256 result) {\n if (m == 0) return (false, 0);\n assembly (\"memory-safe\") {\n let ptr := mload(0x40)\n // | Offset | Content | Content (Hex) |\n // |-----------|------------|--------------------------------------------------------------------|\n // | 0x00:0x1f | size of b | 0x0000000000000000000000000000000000000000000000000000000000000020 |\n // | 0x20:0x3f | size of e | 0x0000000000000000000000000000000000000000000000000000000000000020 |\n // | 0x40:0x5f | size of m | 0x0000000000000000000000000000000000000000000000000000000000000020 |\n // | 0x60:0x7f | value of b | 0x<.............................................................b> |\n // | 0x80:0x9f | value of e | 0x<.............................................................e> |\n // | 0xa0:0xbf | value of m | 0x<.............................................................m> |\n mstore(ptr, 0x20)\n mstore(add(ptr, 0x20), 0x20)\n mstore(add(ptr, 0x40), 0x20)\n mstore(add(ptr, 0x60), b)\n mstore(add(ptr, 0x80), e)\n mstore(add(ptr, 0xa0), m)\n\n // Given the result < m, it's guaranteed to fit in 32 bytes,\n // so we can use the memory scratch space located at offset 0.\n success := staticcall(gas(), 0x05, ptr, 0xc0, 0x00, 0x20)\n result := mload(0x00)\n }\n }\n\n /**\n * @dev Variant of {modExp} that supports inputs of arbitrary length.\n */\n function modExp(bytes memory b, bytes memory e, bytes memory m) internal view returns (bytes memory) {\n (bool success, bytes memory result) = tryModExp(b, e, m);\n if (!success) {\n Panic.panic(Panic.DIVISION_BY_ZERO);\n }\n return result;\n }\n\n /**\n * @dev Variant of {tryModExp} that supports inputs of arbitrary length.\n */\n function tryModExp(\n bytes memory b,\n bytes memory e,\n bytes memory m\n ) internal view returns (bool success, bytes memory result) {\n if (_zeroBytes(m)) return (false, new bytes(0));\n\n uint256 mLen = m.length;\n\n // Encode call args in result and move the free memory pointer\n result = abi.encodePacked(b.length, e.length, mLen, b, e, m);\n\n assembly (\"memory-safe\") {\n let dataPtr := add(result, 0x20)\n // Write result on top of args to avoid allocating extra memory.\n success := staticcall(gas(), 0x05, dataPtr, mload(result), dataPtr, mLen)\n // Overwrite the length.\n // result.length > returndatasize() is guaranteed because returndatasize() == m.length\n mstore(result, mLen)\n // Set the memory pointer after the returned data.\n mstore(0x40, add(dataPtr, mLen))\n }\n }\n\n /**\n * @dev Returns whether the provided byte array is zero.\n */\n function _zeroBytes(bytes memory byteArray) private pure returns (bool) {\n for (uint256 i = 0; i < byteArray.length; ++i) {\n if (byteArray[i] != 0) {\n return false;\n }\n }\n return true;\n }\n\n /**\n * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded\n * towards zero.\n *\n * This method is based on Newton's method for computing square roots; the algorithm is restricted to only\n * using integer operations.\n */\n function sqrt(uint256 a) internal pure returns (uint256) {\n unchecked {\n // Take care of easy edge cases when a == 0 or a == 1\n if (a <= 1) {\n return a;\n }\n\n // In this function, we use Newton's method to get a root of `f(x) := x² - a`. It involves building a\n // sequence x_n that converges toward sqrt(a). For each iteration x_n, we also define the error between\n // the current value as `ε_n = | x_n - sqrt(a) |`.\n //\n // For our first estimation, we consider `e` the smallest power of 2 which is bigger than the square root\n // of the target. (i.e. `2**(e-1) ≤ sqrt(a) < 2**e`). We know that `e ≤ 128` because `(2¹²⁸)² = 2²⁵⁶` is\n // bigger than any uint256.\n //\n // By noticing that\n // `2**(e-1) ≤ sqrt(a) < 2**e → (2**(e-1))² ≤ a < (2**e)² → 2**(2*e-2) ≤ a < 2**(2*e)`\n // we can deduce that `e - 1` is `log2(a) / 2`. We can thus compute `x_n = 2**(e-1)` using a method similar\n // to the msb function.\n uint256 aa = a;\n uint256 xn = 1;\n\n if (aa >= (1 << 128)) {\n aa >>= 128;\n xn <<= 64;\n }\n if (aa >= (1 << 64)) {\n aa >>= 64;\n xn <<= 32;\n }\n if (aa >= (1 << 32)) {\n aa >>= 32;\n xn <<= 16;\n }\n if (aa >= (1 << 16)) {\n aa >>= 16;\n xn <<= 8;\n }\n if (aa >= (1 << 8)) {\n aa >>= 8;\n xn <<= 4;\n }\n if (aa >= (1 << 4)) {\n aa >>= 4;\n xn <<= 2;\n }\n if (aa >= (1 << 2)) {\n xn <<= 1;\n }\n\n // We now have x_n such that `x_n = 2**(e-1) ≤ sqrt(a) < 2**e = 2 * x_n`. This implies ε_n ≤ 2**(e-1).\n //\n // We can refine our estimation by noticing that the middle of that interval minimizes the error.\n // If we move x_n to equal 2**(e-1) + 2**(e-2), then we reduce the error to ε_n ≤ 2**(e-2).\n // This is going to be our x_0 (and ε_0)\n xn = (3 * xn) >> 1; // ε_0 := | x_0 - sqrt(a) | ≤ 2**(e-2)\n\n // From here, Newton's method give us:\n // x_{n+1} = (x_n + a / x_n) / 2\n //\n // One should note that:\n // x_{n+1}² - a = ((x_n + a / x_n) / 2)² - a\n // = ((x_n² + a) / (2 * x_n))² - a\n // = (x_n⁴ + 2 * a * x_n² + a²) / (4 * x_n²) - a\n // = (x_n⁴ + 2 * a * x_n² + a² - 4 * a * x_n²) / (4 * x_n²)\n // = (x_n⁴ - 2 * a * x_n² + a²) / (4 * x_n²)\n // = (x_n² - a)² / (2 * x_n)²\n // = ((x_n² - a) / (2 * x_n))²\n // ≥ 0\n // Which proves that for all n ≥ 1, sqrt(a) ≤ x_n\n //\n // This gives us the proof of quadratic convergence of the sequence:\n // ε_{n+1} = | x_{n+1} - sqrt(a) |\n // = | (x_n + a / x_n) / 2 - sqrt(a) |\n // = | (x_n² + a - 2*x_n*sqrt(a)) / (2 * x_n) |\n // = | (x_n - sqrt(a))² / (2 * x_n) |\n // = | ε_n² / (2 * x_n) |\n // = ε_n² / | (2 * x_n) |\n //\n // For the first iteration, we have a special case where x_0 is known:\n // ε_1 = ε_0² / | (2 * x_0) |\n // ≤ (2**(e-2))² / (2 * (2**(e-1) + 2**(e-2)))\n // ≤ 2**(2*e-4) / (3 * 2**(e-1))\n // ≤ 2**(e-3) / 3\n // ≤ 2**(e-3-log2(3))\n // ≤ 2**(e-4.5)\n //\n // For the following iterations, we use the fact that, 2**(e-1) ≤ sqrt(a) ≤ x_n:\n // ε_{n+1} = ε_n² / | (2 * x_n) |\n // ≤ (2**(e-k))² / (2 * 2**(e-1))\n // ≤ 2**(2*e-2*k) / 2**e\n // ≤ 2**(e-2*k)\n xn = (xn + a / xn) >> 1; // ε_1 := | x_1 - sqrt(a) | ≤ 2**(e-4.5) -- special case, see above\n xn = (xn + a / xn) >> 1; // ε_2 := | x_2 - sqrt(a) | ≤ 2**(e-9) -- general case with k = 4.5\n xn = (xn + a / xn) >> 1; // ε_3 := | x_3 - sqrt(a) | ≤ 2**(e-18) -- general case with k = 9\n xn = (xn + a / xn) >> 1; // ε_4 := | x_4 - sqrt(a) | ≤ 2**(e-36) -- general case with k = 18\n xn = (xn + a / xn) >> 1; // ε_5 := | x_5 - sqrt(a) | ≤ 2**(e-72) -- general case with k = 36\n xn = (xn + a / xn) >> 1; // ε_6 := | x_6 - sqrt(a) | ≤ 2**(e-144) -- general case with k = 72\n\n // Because e ≤ 128 (as discussed during the first estimation phase), we know have reached a precision\n // ε_6 ≤ 2**(e-144) < 1. Given we're operating on integers, then we can ensure that xn is now either\n // sqrt(a) or sqrt(a) + 1.\n return xn - SafeCast.toUint(xn > a / xn);\n }\n }\n\n /**\n * @dev Calculates sqrt(a), following the selected rounding direction.\n */\n function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {\n unchecked {\n uint256 result = sqrt(a);\n return result + SafeCast.toUint(unsignedRoundsUp(rounding) && result * result < a);\n }\n }\n\n /**\n * @dev Return the log in base 2 of a positive value rounded towards zero.\n * Returns 0 if given 0.\n */\n function log2(uint256 x) internal pure returns (uint256 r) {\n // If value has upper 128 bits set, log2 result is at least 128\n r = SafeCast.toUint(x > 0xffffffffffffffffffffffffffffffff) << 7;\n // If upper 64 bits of 128-bit half set, add 64 to result\n r |= SafeCast.toUint((x >> r) > 0xffffffffffffffff) << 6;\n // If upper 32 bits of 64-bit half set, add 32 to result\n r |= SafeCast.toUint((x >> r) > 0xffffffff) << 5;\n // If upper 16 bits of 32-bit half set, add 16 to result\n r |= SafeCast.toUint((x >> r) > 0xffff) << 4;\n // If upper 8 bits of 16-bit half set, add 8 to result\n r |= SafeCast.toUint((x >> r) > 0xff) << 3;\n // If upper 4 bits of 8-bit half set, add 4 to result\n r |= SafeCast.toUint((x >> r) > 0xf) << 2;\n\n // Shifts value right by the current result and use it as an index into this lookup table:\n //\n // | x (4 bits) | index | table[index] = MSB position |\n // |------------|---------|-----------------------------|\n // | 0000 | 0 | table[0] = 0 |\n // | 0001 | 1 | table[1] = 0 |\n // | 0010 | 2 | table[2] = 1 |\n // | 0011 | 3 | table[3] = 1 |\n // | 0100 | 4 | table[4] = 2 |\n // | 0101 | 5 | table[5] = 2 |\n // | 0110 | 6 | table[6] = 2 |\n // | 0111 | 7 | table[7] = 2 |\n // | 1000 | 8 | table[8] = 3 |\n // | 1001 | 9 | table[9] = 3 |\n // | 1010 | 10 | table[10] = 3 |\n // | 1011 | 11 | table[11] = 3 |\n // | 1100 | 12 | table[12] = 3 |\n // | 1101 | 13 | table[13] = 3 |\n // | 1110 | 14 | table[14] = 3 |\n // | 1111 | 15 | table[15] = 3 |\n //\n // The lookup table is represented as a 32-byte value with the MSB positions for 0-15 in the last 16 bytes.\n assembly (\"memory-safe\") {\n r := or(r, byte(shr(r, x), 0x0000010102020202030303030303030300000000000000000000000000000000))\n }\n }\n\n /**\n * @dev Return the log in base 2, following the selected rounding direction, of a positive value.\n * Returns 0 if given 0.\n */\n function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {\n unchecked {\n uint256 result = log2(value);\n return result + SafeCast.toUint(unsignedRoundsUp(rounding) && 1 << result < value);\n }\n }\n\n /**\n * @dev Return the log in base 10 of a positive value rounded towards zero.\n * Returns 0 if given 0.\n */\n function log10(uint256 value) internal pure returns (uint256) {\n uint256 result = 0;\n unchecked {\n if (value >= 10 ** 64) {\n value /= 10 ** 64;\n result += 64;\n }\n if (value >= 10 ** 32) {\n value /= 10 ** 32;\n result += 32;\n }\n if (value >= 10 ** 16) {\n value /= 10 ** 16;\n result += 16;\n }\n if (value >= 10 ** 8) {\n value /= 10 ** 8;\n result += 8;\n }\n if (value >= 10 ** 4) {\n value /= 10 ** 4;\n result += 4;\n }\n if (value >= 10 ** 2) {\n value /= 10 ** 2;\n result += 2;\n }\n if (value >= 10 ** 1) {\n result += 1;\n }\n }\n return result;\n }\n\n /**\n * @dev Return the log in base 10, following the selected rounding direction, of a positive value.\n * Returns 0 if given 0.\n */\n function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {\n unchecked {\n uint256 result = log10(value);\n return result + SafeCast.toUint(unsignedRoundsUp(rounding) && 10 ** result < value);\n }\n }\n\n /**\n * @dev Return the log in base 256 of a positive value rounded towards zero.\n * Returns 0 if given 0.\n *\n * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.\n */\n function log256(uint256 x) internal pure returns (uint256 r) {\n // If value has upper 128 bits set, log2 result is at least 128\n r = SafeCast.toUint(x > 0xffffffffffffffffffffffffffffffff) << 7;\n // If upper 64 bits of 128-bit half set, add 64 to result\n r |= SafeCast.toUint((x >> r) > 0xffffffffffffffff) << 6;\n // If upper 32 bits of 64-bit half set, add 32 to result\n r |= SafeCast.toUint((x >> r) > 0xffffffff) << 5;\n // If upper 16 bits of 32-bit half set, add 16 to result\n r |= SafeCast.toUint((x >> r) > 0xffff) << 4;\n // Add 1 if upper 8 bits of 16-bit half set, and divide accumulated result by 8\n return (r >> 3) | SafeCast.toUint((x >> r) > 0xff);\n }\n\n /**\n * @dev Return the log in base 256, following the selected rounding direction, of a positive value.\n * Returns 0 if given 0.\n */\n function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {\n unchecked {\n uint256 result = log256(value);\n return result + SafeCast.toUint(unsignedRoundsUp(rounding) && 1 << (result << 3) < value);\n }\n }\n\n /**\n * @dev Returns whether a provided rounding mode is considered rounding up for unsigned integers.\n */\n function unsignedRoundsUp(Rounding rounding) internal pure returns (bool) {\n return uint8(rounding) % 2 == 1;\n }\n}" + }, + "lib/rooster/openzeppelin-custom/contracts/utils/math/SafeCast.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.1.0) (utils/math/SafeCast.sol)\n// This file was procedurally generated from scripts/generate/templates/SafeCast.js.\n\npragma solidity ^0.8.20;\n\n/**\n * @dev Wrappers over Solidity's uintXX/intXX/bool casting operators with added overflow\n * checks.\n *\n * Downcasting from uint256/int256 in Solidity does not revert on overflow. This can\n * easily result in undesired exploitation or bugs, since developers usually\n * assume that overflows raise errors. `SafeCast` restores this intuition by\n * reverting the transaction when such an operation overflows.\n *\n * Using this library instead of the unchecked operations eliminates an entire\n * class of bugs, so it's recommended to use it always.\n */\nlibrary SafeCast {\n /**\n * @dev Value doesn't fit in an uint of `bits` size.\n */\n error SafeCastOverflowedUintDowncast(uint8 bits, uint256 value);\n\n /**\n * @dev An int value doesn't fit in an uint of `bits` size.\n */\n error SafeCastOverflowedIntToUint(int256 value);\n\n /**\n * @dev Value doesn't fit in an int of `bits` size.\n */\n error SafeCastOverflowedIntDowncast(uint8 bits, int256 value);\n\n /**\n * @dev An uint value doesn't fit in an int of `bits` size.\n */\n error SafeCastOverflowedUintToInt(uint256 value);\n\n /**\n * @dev Returns the downcasted uint248 from uint256, reverting on\n * overflow (when the input is greater than largest uint248).\n *\n * Counterpart to Solidity's `uint248` operator.\n *\n * Requirements:\n *\n * - input must fit into 248 bits\n */\n function toUint248(uint256 value) internal pure returns (uint248) {\n if (value > type(uint248).max) {\n revert SafeCastOverflowedUintDowncast(248, value);\n }\n return uint248(value);\n }\n\n /**\n * @dev Returns the downcasted uint240 from uint256, reverting on\n * overflow (when the input is greater than largest uint240).\n *\n * Counterpart to Solidity's `uint240` operator.\n *\n * Requirements:\n *\n * - input must fit into 240 bits\n */\n function toUint240(uint256 value) internal pure returns (uint240) {\n if (value > type(uint240).max) {\n revert SafeCastOverflowedUintDowncast(240, value);\n }\n return uint240(value);\n }\n\n /**\n * @dev Returns the downcasted uint232 from uint256, reverting on\n * overflow (when the input is greater than largest uint232).\n *\n * Counterpart to Solidity's `uint232` operator.\n *\n * Requirements:\n *\n * - input must fit into 232 bits\n */\n function toUint232(uint256 value) internal pure returns (uint232) {\n if (value > type(uint232).max) {\n revert SafeCastOverflowedUintDowncast(232, value);\n }\n return uint232(value);\n }\n\n /**\n * @dev Returns the downcasted uint224 from uint256, reverting on\n * overflow (when the input is greater than largest uint224).\n *\n * Counterpart to Solidity's `uint224` operator.\n *\n * Requirements:\n *\n * - input must fit into 224 bits\n */\n function toUint224(uint256 value) internal pure returns (uint224) {\n if (value > type(uint224).max) {\n revert SafeCastOverflowedUintDowncast(224, value);\n }\n return uint224(value);\n }\n\n /**\n * @dev Returns the downcasted uint216 from uint256, reverting on\n * overflow (when the input is greater than largest uint216).\n *\n * Counterpart to Solidity's `uint216` operator.\n *\n * Requirements:\n *\n * - input must fit into 216 bits\n */\n function toUint216(uint256 value) internal pure returns (uint216) {\n if (value > type(uint216).max) {\n revert SafeCastOverflowedUintDowncast(216, value);\n }\n return uint216(value);\n }\n\n /**\n * @dev Returns the downcasted uint208 from uint256, reverting on\n * overflow (when the input is greater than largest uint208).\n *\n * Counterpart to Solidity's `uint208` operator.\n *\n * Requirements:\n *\n * - input must fit into 208 bits\n */\n function toUint208(uint256 value) internal pure returns (uint208) {\n if (value > type(uint208).max) {\n revert SafeCastOverflowedUintDowncast(208, value);\n }\n return uint208(value);\n }\n\n /**\n * @dev Returns the downcasted uint200 from uint256, reverting on\n * overflow (when the input is greater than largest uint200).\n *\n * Counterpart to Solidity's `uint200` operator.\n *\n * Requirements:\n *\n * - input must fit into 200 bits\n */\n function toUint200(uint256 value) internal pure returns (uint200) {\n if (value > type(uint200).max) {\n revert SafeCastOverflowedUintDowncast(200, value);\n }\n return uint200(value);\n }\n\n /**\n * @dev Returns the downcasted uint192 from uint256, reverting on\n * overflow (when the input is greater than largest uint192).\n *\n * Counterpart to Solidity's `uint192` operator.\n *\n * Requirements:\n *\n * - input must fit into 192 bits\n */\n function toUint192(uint256 value) internal pure returns (uint192) {\n if (value > type(uint192).max) {\n revert SafeCastOverflowedUintDowncast(192, value);\n }\n return uint192(value);\n }\n\n /**\n * @dev Returns the downcasted uint184 from uint256, reverting on\n * overflow (when the input is greater than largest uint184).\n *\n * Counterpart to Solidity's `uint184` operator.\n *\n * Requirements:\n *\n * - input must fit into 184 bits\n */\n function toUint184(uint256 value) internal pure returns (uint184) {\n if (value > type(uint184).max) {\n revert SafeCastOverflowedUintDowncast(184, value);\n }\n return uint184(value);\n }\n\n /**\n * @dev Returns the downcasted uint176 from uint256, reverting on\n * overflow (when the input is greater than largest uint176).\n *\n * Counterpart to Solidity's `uint176` operator.\n *\n * Requirements:\n *\n * - input must fit into 176 bits\n */\n function toUint176(uint256 value) internal pure returns (uint176) {\n if (value > type(uint176).max) {\n revert SafeCastOverflowedUintDowncast(176, value);\n }\n return uint176(value);\n }\n\n /**\n * @dev Returns the downcasted uint168 from uint256, reverting on\n * overflow (when the input is greater than largest uint168).\n *\n * Counterpart to Solidity's `uint168` operator.\n *\n * Requirements:\n *\n * - input must fit into 168 bits\n */\n function toUint168(uint256 value) internal pure returns (uint168) {\n if (value > type(uint168).max) {\n revert SafeCastOverflowedUintDowncast(168, value);\n }\n return uint168(value);\n }\n\n /**\n * @dev Returns the downcasted uint160 from uint256, reverting on\n * overflow (when the input is greater than largest uint160).\n *\n * Counterpart to Solidity's `uint160` operator.\n *\n * Requirements:\n *\n * - input must fit into 160 bits\n */\n function toUint160(uint256 value) internal pure returns (uint160) {\n if (value > type(uint160).max) {\n revert SafeCastOverflowedUintDowncast(160, value);\n }\n return uint160(value);\n }\n\n /**\n * @dev Returns the downcasted uint152 from uint256, reverting on\n * overflow (when the input is greater than largest uint152).\n *\n * Counterpart to Solidity's `uint152` operator.\n *\n * Requirements:\n *\n * - input must fit into 152 bits\n */\n function toUint152(uint256 value) internal pure returns (uint152) {\n if (value > type(uint152).max) {\n revert SafeCastOverflowedUintDowncast(152, value);\n }\n return uint152(value);\n }\n\n /**\n * @dev Returns the downcasted uint144 from uint256, reverting on\n * overflow (when the input is greater than largest uint144).\n *\n * Counterpart to Solidity's `uint144` operator.\n *\n * Requirements:\n *\n * - input must fit into 144 bits\n */\n function toUint144(uint256 value) internal pure returns (uint144) {\n if (value > type(uint144).max) {\n revert SafeCastOverflowedUintDowncast(144, value);\n }\n return uint144(value);\n }\n\n /**\n * @dev Returns the downcasted uint136 from uint256, reverting on\n * overflow (when the input is greater than largest uint136).\n *\n * Counterpart to Solidity's `uint136` operator.\n *\n * Requirements:\n *\n * - input must fit into 136 bits\n */\n function toUint136(uint256 value) internal pure returns (uint136) {\n if (value > type(uint136).max) {\n revert SafeCastOverflowedUintDowncast(136, value);\n }\n return uint136(value);\n }\n\n /**\n * @dev Returns the downcasted uint128 from uint256, reverting on\n * overflow (when the input is greater than largest uint128).\n *\n * Counterpart to Solidity's `uint128` operator.\n *\n * Requirements:\n *\n * - input must fit into 128 bits\n */\n function toUint128(uint256 value) internal pure returns (uint128) {\n if (value > type(uint128).max) {\n revert SafeCastOverflowedUintDowncast(128, value);\n }\n return uint128(value);\n }\n\n /**\n * @dev Returns the downcasted uint120 from uint256, reverting on\n * overflow (when the input is greater than largest uint120).\n *\n * Counterpart to Solidity's `uint120` operator.\n *\n * Requirements:\n *\n * - input must fit into 120 bits\n */\n function toUint120(uint256 value) internal pure returns (uint120) {\n if (value > type(uint120).max) {\n revert SafeCastOverflowedUintDowncast(120, value);\n }\n return uint120(value);\n }\n\n /**\n * @dev Returns the downcasted uint112 from uint256, reverting on\n * overflow (when the input is greater than largest uint112).\n *\n * Counterpart to Solidity's `uint112` operator.\n *\n * Requirements:\n *\n * - input must fit into 112 bits\n */\n function toUint112(uint256 value) internal pure returns (uint112) {\n if (value > type(uint112).max) {\n revert SafeCastOverflowedUintDowncast(112, value);\n }\n return uint112(value);\n }\n\n /**\n * @dev Returns the downcasted uint104 from uint256, reverting on\n * overflow (when the input is greater than largest uint104).\n *\n * Counterpart to Solidity's `uint104` operator.\n *\n * Requirements:\n *\n * - input must fit into 104 bits\n */\n function toUint104(uint256 value) internal pure returns (uint104) {\n if (value > type(uint104).max) {\n revert SafeCastOverflowedUintDowncast(104, value);\n }\n return uint104(value);\n }\n\n /**\n * @dev Returns the downcasted uint96 from uint256, reverting on\n * overflow (when the input is greater than largest uint96).\n *\n * Counterpart to Solidity's `uint96` operator.\n *\n * Requirements:\n *\n * - input must fit into 96 bits\n */\n function toUint96(uint256 value) internal pure returns (uint96) {\n if (value > type(uint96).max) {\n revert SafeCastOverflowedUintDowncast(96, value);\n }\n return uint96(value);\n }\n\n /**\n * @dev Returns the downcasted uint88 from uint256, reverting on\n * overflow (when the input is greater than largest uint88).\n *\n * Counterpart to Solidity's `uint88` operator.\n *\n * Requirements:\n *\n * - input must fit into 88 bits\n */\n function toUint88(uint256 value) internal pure returns (uint88) {\n if (value > type(uint88).max) {\n revert SafeCastOverflowedUintDowncast(88, value);\n }\n return uint88(value);\n }\n\n /**\n * @dev Returns the downcasted uint80 from uint256, reverting on\n * overflow (when the input is greater than largest uint80).\n *\n * Counterpart to Solidity's `uint80` operator.\n *\n * Requirements:\n *\n * - input must fit into 80 bits\n */\n function toUint80(uint256 value) internal pure returns (uint80) {\n if (value > type(uint80).max) {\n revert SafeCastOverflowedUintDowncast(80, value);\n }\n return uint80(value);\n }\n\n /**\n * @dev Returns the downcasted uint72 from uint256, reverting on\n * overflow (when the input is greater than largest uint72).\n *\n * Counterpart to Solidity's `uint72` operator.\n *\n * Requirements:\n *\n * - input must fit into 72 bits\n */\n function toUint72(uint256 value) internal pure returns (uint72) {\n if (value > type(uint72).max) {\n revert SafeCastOverflowedUintDowncast(72, value);\n }\n return uint72(value);\n }\n\n /**\n * @dev Returns the downcasted uint64 from uint256, reverting on\n * overflow (when the input is greater than largest uint64).\n *\n * Counterpart to Solidity's `uint64` operator.\n *\n * Requirements:\n *\n * - input must fit into 64 bits\n */\n function toUint64(uint256 value) internal pure returns (uint64) {\n if (value > type(uint64).max) {\n revert SafeCastOverflowedUintDowncast(64, value);\n }\n return uint64(value);\n }\n\n /**\n * @dev Returns the downcasted uint56 from uint256, reverting on\n * overflow (when the input is greater than largest uint56).\n *\n * Counterpart to Solidity's `uint56` operator.\n *\n * Requirements:\n *\n * - input must fit into 56 bits\n */\n function toUint56(uint256 value) internal pure returns (uint56) {\n if (value > type(uint56).max) {\n revert SafeCastOverflowedUintDowncast(56, value);\n }\n return uint56(value);\n }\n\n /**\n * @dev Returns the downcasted uint48 from uint256, reverting on\n * overflow (when the input is greater than largest uint48).\n *\n * Counterpart to Solidity's `uint48` operator.\n *\n * Requirements:\n *\n * - input must fit into 48 bits\n */\n function toUint48(uint256 value) internal pure returns (uint48) {\n if (value > type(uint48).max) {\n revert SafeCastOverflowedUintDowncast(48, value);\n }\n return uint48(value);\n }\n\n /**\n * @dev Returns the downcasted uint40 from uint256, reverting on\n * overflow (when the input is greater than largest uint40).\n *\n * Counterpart to Solidity's `uint40` operator.\n *\n * Requirements:\n *\n * - input must fit into 40 bits\n */\n function toUint40(uint256 value) internal pure returns (uint40) {\n if (value > type(uint40).max) {\n revert SafeCastOverflowedUintDowncast(40, value);\n }\n return uint40(value);\n }\n\n /**\n * @dev Returns the downcasted uint32 from uint256, reverting on\n * overflow (when the input is greater than largest uint32).\n *\n * Counterpart to Solidity's `uint32` operator.\n *\n * Requirements:\n *\n * - input must fit into 32 bits\n */\n function toUint32(uint256 value) internal pure returns (uint32) {\n if (value > type(uint32).max) {\n revert SafeCastOverflowedUintDowncast(32, value);\n }\n return uint32(value);\n }\n\n /**\n * @dev Returns the downcasted uint24 from uint256, reverting on\n * overflow (when the input is greater than largest uint24).\n *\n * Counterpart to Solidity's `uint24` operator.\n *\n * Requirements:\n *\n * - input must fit into 24 bits\n */\n function toUint24(uint256 value) internal pure returns (uint24) {\n if (value > type(uint24).max) {\n revert SafeCastOverflowedUintDowncast(24, value);\n }\n return uint24(value);\n }\n\n /**\n * @dev Returns the downcasted uint16 from uint256, reverting on\n * overflow (when the input is greater than largest uint16).\n *\n * Counterpart to Solidity's `uint16` operator.\n *\n * Requirements:\n *\n * - input must fit into 16 bits\n */\n function toUint16(uint256 value) internal pure returns (uint16) {\n if (value > type(uint16).max) {\n revert SafeCastOverflowedUintDowncast(16, value);\n }\n return uint16(value);\n }\n\n /**\n * @dev Returns the downcasted uint8 from uint256, reverting on\n * overflow (when the input is greater than largest uint8).\n *\n * Counterpart to Solidity's `uint8` operator.\n *\n * Requirements:\n *\n * - input must fit into 8 bits\n */\n function toUint8(uint256 value) internal pure returns (uint8) {\n if (value > type(uint8).max) {\n revert SafeCastOverflowedUintDowncast(8, value);\n }\n return uint8(value);\n }\n\n /**\n * @dev Converts a signed int256 into an unsigned uint256.\n *\n * Requirements:\n *\n * - input must be greater than or equal to 0.\n */\n function toUint256(int256 value) internal pure returns (uint256) {\n if (value < 0) {\n revert SafeCastOverflowedIntToUint(value);\n }\n return uint256(value);\n }\n\n /**\n * @dev Returns the downcasted int248 from int256, reverting on\n * overflow (when the input is less than smallest int248 or\n * greater than largest int248).\n *\n * Counterpart to Solidity's `int248` operator.\n *\n * Requirements:\n *\n * - input must fit into 248 bits\n */\n function toInt248(int256 value) internal pure returns (int248 downcasted) {\n downcasted = int248(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(248, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int240 from int256, reverting on\n * overflow (when the input is less than smallest int240 or\n * greater than largest int240).\n *\n * Counterpart to Solidity's `int240` operator.\n *\n * Requirements:\n *\n * - input must fit into 240 bits\n */\n function toInt240(int256 value) internal pure returns (int240 downcasted) {\n downcasted = int240(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(240, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int232 from int256, reverting on\n * overflow (when the input is less than smallest int232 or\n * greater than largest int232).\n *\n * Counterpart to Solidity's `int232` operator.\n *\n * Requirements:\n *\n * - input must fit into 232 bits\n */\n function toInt232(int256 value) internal pure returns (int232 downcasted) {\n downcasted = int232(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(232, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int224 from int256, reverting on\n * overflow (when the input is less than smallest int224 or\n * greater than largest int224).\n *\n * Counterpart to Solidity's `int224` operator.\n *\n * Requirements:\n *\n * - input must fit into 224 bits\n */\n function toInt224(int256 value) internal pure returns (int224 downcasted) {\n downcasted = int224(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(224, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int216 from int256, reverting on\n * overflow (when the input is less than smallest int216 or\n * greater than largest int216).\n *\n * Counterpart to Solidity's `int216` operator.\n *\n * Requirements:\n *\n * - input must fit into 216 bits\n */\n function toInt216(int256 value) internal pure returns (int216 downcasted) {\n downcasted = int216(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(216, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int208 from int256, reverting on\n * overflow (when the input is less than smallest int208 or\n * greater than largest int208).\n *\n * Counterpart to Solidity's `int208` operator.\n *\n * Requirements:\n *\n * - input must fit into 208 bits\n */\n function toInt208(int256 value) internal pure returns (int208 downcasted) {\n downcasted = int208(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(208, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int200 from int256, reverting on\n * overflow (when the input is less than smallest int200 or\n * greater than largest int200).\n *\n * Counterpart to Solidity's `int200` operator.\n *\n * Requirements:\n *\n * - input must fit into 200 bits\n */\n function toInt200(int256 value) internal pure returns (int200 downcasted) {\n downcasted = int200(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(200, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int192 from int256, reverting on\n * overflow (when the input is less than smallest int192 or\n * greater than largest int192).\n *\n * Counterpart to Solidity's `int192` operator.\n *\n * Requirements:\n *\n * - input must fit into 192 bits\n */\n function toInt192(int256 value) internal pure returns (int192 downcasted) {\n downcasted = int192(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(192, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int184 from int256, reverting on\n * overflow (when the input is less than smallest int184 or\n * greater than largest int184).\n *\n * Counterpart to Solidity's `int184` operator.\n *\n * Requirements:\n *\n * - input must fit into 184 bits\n */\n function toInt184(int256 value) internal pure returns (int184 downcasted) {\n downcasted = int184(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(184, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int176 from int256, reverting on\n * overflow (when the input is less than smallest int176 or\n * greater than largest int176).\n *\n * Counterpart to Solidity's `int176` operator.\n *\n * Requirements:\n *\n * - input must fit into 176 bits\n */\n function toInt176(int256 value) internal pure returns (int176 downcasted) {\n downcasted = int176(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(176, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int168 from int256, reverting on\n * overflow (when the input is less than smallest int168 or\n * greater than largest int168).\n *\n * Counterpart to Solidity's `int168` operator.\n *\n * Requirements:\n *\n * - input must fit into 168 bits\n */\n function toInt168(int256 value) internal pure returns (int168 downcasted) {\n downcasted = int168(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(168, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int160 from int256, reverting on\n * overflow (when the input is less than smallest int160 or\n * greater than largest int160).\n *\n * Counterpart to Solidity's `int160` operator.\n *\n * Requirements:\n *\n * - input must fit into 160 bits\n */\n function toInt160(int256 value) internal pure returns (int160 downcasted) {\n downcasted = int160(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(160, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int152 from int256, reverting on\n * overflow (when the input is less than smallest int152 or\n * greater than largest int152).\n *\n * Counterpart to Solidity's `int152` operator.\n *\n * Requirements:\n *\n * - input must fit into 152 bits\n */\n function toInt152(int256 value) internal pure returns (int152 downcasted) {\n downcasted = int152(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(152, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int144 from int256, reverting on\n * overflow (when the input is less than smallest int144 or\n * greater than largest int144).\n *\n * Counterpart to Solidity's `int144` operator.\n *\n * Requirements:\n *\n * - input must fit into 144 bits\n */\n function toInt144(int256 value) internal pure returns (int144 downcasted) {\n downcasted = int144(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(144, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int136 from int256, reverting on\n * overflow (when the input is less than smallest int136 or\n * greater than largest int136).\n *\n * Counterpart to Solidity's `int136` operator.\n *\n * Requirements:\n *\n * - input must fit into 136 bits\n */\n function toInt136(int256 value) internal pure returns (int136 downcasted) {\n downcasted = int136(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(136, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int128 from int256, reverting on\n * overflow (when the input is less than smallest int128 or\n * greater than largest int128).\n *\n * Counterpart to Solidity's `int128` operator.\n *\n * Requirements:\n *\n * - input must fit into 128 bits\n */\n function toInt128(int256 value) internal pure returns (int128 downcasted) {\n downcasted = int128(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(128, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int120 from int256, reverting on\n * overflow (when the input is less than smallest int120 or\n * greater than largest int120).\n *\n * Counterpart to Solidity's `int120` operator.\n *\n * Requirements:\n *\n * - input must fit into 120 bits\n */\n function toInt120(int256 value) internal pure returns (int120 downcasted) {\n downcasted = int120(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(120, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int112 from int256, reverting on\n * overflow (when the input is less than smallest int112 or\n * greater than largest int112).\n *\n * Counterpart to Solidity's `int112` operator.\n *\n * Requirements:\n *\n * - input must fit into 112 bits\n */\n function toInt112(int256 value) internal pure returns (int112 downcasted) {\n downcasted = int112(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(112, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int104 from int256, reverting on\n * overflow (when the input is less than smallest int104 or\n * greater than largest int104).\n *\n * Counterpart to Solidity's `int104` operator.\n *\n * Requirements:\n *\n * - input must fit into 104 bits\n */\n function toInt104(int256 value) internal pure returns (int104 downcasted) {\n downcasted = int104(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(104, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int96 from int256, reverting on\n * overflow (when the input is less than smallest int96 or\n * greater than largest int96).\n *\n * Counterpart to Solidity's `int96` operator.\n *\n * Requirements:\n *\n * - input must fit into 96 bits\n */\n function toInt96(int256 value) internal pure returns (int96 downcasted) {\n downcasted = int96(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(96, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int88 from int256, reverting on\n * overflow (when the input is less than smallest int88 or\n * greater than largest int88).\n *\n * Counterpart to Solidity's `int88` operator.\n *\n * Requirements:\n *\n * - input must fit into 88 bits\n */\n function toInt88(int256 value) internal pure returns (int88 downcasted) {\n downcasted = int88(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(88, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int80 from int256, reverting on\n * overflow (when the input is less than smallest int80 or\n * greater than largest int80).\n *\n * Counterpart to Solidity's `int80` operator.\n *\n * Requirements:\n *\n * - input must fit into 80 bits\n */\n function toInt80(int256 value) internal pure returns (int80 downcasted) {\n downcasted = int80(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(80, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int72 from int256, reverting on\n * overflow (when the input is less than smallest int72 or\n * greater than largest int72).\n *\n * Counterpart to Solidity's `int72` operator.\n *\n * Requirements:\n *\n * - input must fit into 72 bits\n */\n function toInt72(int256 value) internal pure returns (int72 downcasted) {\n downcasted = int72(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(72, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int64 from int256, reverting on\n * overflow (when the input is less than smallest int64 or\n * greater than largest int64).\n *\n * Counterpart to Solidity's `int64` operator.\n *\n * Requirements:\n *\n * - input must fit into 64 bits\n */\n function toInt64(int256 value) internal pure returns (int64 downcasted) {\n downcasted = int64(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(64, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int56 from int256, reverting on\n * overflow (when the input is less than smallest int56 or\n * greater than largest int56).\n *\n * Counterpart to Solidity's `int56` operator.\n *\n * Requirements:\n *\n * - input must fit into 56 bits\n */\n function toInt56(int256 value) internal pure returns (int56 downcasted) {\n downcasted = int56(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(56, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int48 from int256, reverting on\n * overflow (when the input is less than smallest int48 or\n * greater than largest int48).\n *\n * Counterpart to Solidity's `int48` operator.\n *\n * Requirements:\n *\n * - input must fit into 48 bits\n */\n function toInt48(int256 value) internal pure returns (int48 downcasted) {\n downcasted = int48(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(48, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int40 from int256, reverting on\n * overflow (when the input is less than smallest int40 or\n * greater than largest int40).\n *\n * Counterpart to Solidity's `int40` operator.\n *\n * Requirements:\n *\n * - input must fit into 40 bits\n */\n function toInt40(int256 value) internal pure returns (int40 downcasted) {\n downcasted = int40(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(40, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int32 from int256, reverting on\n * overflow (when the input is less than smallest int32 or\n * greater than largest int32).\n *\n * Counterpart to Solidity's `int32` operator.\n *\n * Requirements:\n *\n * - input must fit into 32 bits\n */\n function toInt32(int256 value) internal pure returns (int32 downcasted) {\n downcasted = int32(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(32, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int24 from int256, reverting on\n * overflow (when the input is less than smallest int24 or\n * greater than largest int24).\n *\n * Counterpart to Solidity's `int24` operator.\n *\n * Requirements:\n *\n * - input must fit into 24 bits\n */\n function toInt24(int256 value) internal pure returns (int24 downcasted) {\n downcasted = int24(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(24, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int16 from int256, reverting on\n * overflow (when the input is less than smallest int16 or\n * greater than largest int16).\n *\n * Counterpart to Solidity's `int16` operator.\n *\n * Requirements:\n *\n * - input must fit into 16 bits\n */\n function toInt16(int256 value) internal pure returns (int16 downcasted) {\n downcasted = int16(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(16, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int8 from int256, reverting on\n * overflow (when the input is less than smallest int8 or\n * greater than largest int8).\n *\n * Counterpart to Solidity's `int8` operator.\n *\n * Requirements:\n *\n * - input must fit into 8 bits\n */\n function toInt8(int256 value) internal pure returns (int8 downcasted) {\n downcasted = int8(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(8, value);\n }\n }\n\n /**\n * @dev Converts an unsigned uint256 into a signed int256.\n *\n * Requirements:\n *\n * - input must be less than or equal to maxInt256.\n */\n function toInt256(uint256 value) internal pure returns (int256) {\n // Note: Unsafe cast below is okay because `type(int256).max` is guaranteed to be positive\n if (value > uint256(type(int256).max)) {\n revert SafeCastOverflowedUintToInt(value);\n }\n return int256(value);\n }\n\n /**\n * @dev Cast a boolean (false or true) to a uint256 (0 or 1) with no jump.\n */\n function toUint(bool b) internal pure returns (uint256 u) {\n assembly (\"memory-safe\") {\n u := iszero(iszero(b))\n }\n }\n}" + }, + "lib/rooster/openzeppelin-custom/contracts/utils/Panic.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.1.0) (utils/Panic.sol)\n\npragma solidity ^0.8.20;\n\n/**\n * @dev Helper library for emitting standardized panic codes.\n *\n * ```solidity\n * contract Example {\n * using Panic for uint256;\n *\n * // Use any of the declared internal constants\n * function foo() { Panic.GENERIC.panic(); }\n *\n * // Alternatively\n * function foo() { Panic.panic(Panic.GENERIC); }\n * }\n * ```\n *\n * Follows the list from https://github.com/ethereum/solidity/blob/v0.8.24/libsolutil/ErrorCodes.h[libsolutil].\n *\n * _Available since v5.1._\n */\n// slither-disable-next-line unused-state\nlibrary Panic {\n /// @dev generic / unspecified error\n uint256 internal constant GENERIC = 0x00;\n /// @dev used by the assert() builtin\n uint256 internal constant ASSERT = 0x01;\n /// @dev arithmetic underflow or overflow\n uint256 internal constant UNDER_OVERFLOW = 0x11;\n /// @dev division or modulo by zero\n uint256 internal constant DIVISION_BY_ZERO = 0x12;\n /// @dev enum conversion error\n uint256 internal constant ENUM_CONVERSION_ERROR = 0x21;\n /// @dev invalid encoding in storage\n uint256 internal constant STORAGE_ENCODING_ERROR = 0x22;\n /// @dev empty array pop\n uint256 internal constant EMPTY_ARRAY_POP = 0x31;\n /// @dev array out of bounds access\n uint256 internal constant ARRAY_OUT_OF_BOUNDS = 0x32;\n /// @dev resource error (too large allocation or too large array)\n uint256 internal constant RESOURCE_ERROR = 0x41;\n /// @dev calling invalid internal function\n uint256 internal constant INVALID_INTERNAL_FUNCTION = 0x51;\n\n /// @dev Reverts with a panic code. Recommended to use with\n /// the internal constants with predefined codes.\n function panic(uint256 code) internal pure {\n assembly (\"memory-safe\") {\n mstore(0x00, 0x4e487b71)\n mstore(0x20, code)\n revert(0x1c, 0x24)\n }\n }\n}" + }, + "lib/rooster/v2-common/libraries/Constants.sol": { + "content": "// SPDX-License-Identifier: GPL-2.0-or-later\n// As the copyright holder of this work, Ubiquity Labs retains\n// the right to distribute, use, and modify this code under any license of\n// their choosing, in addition to the terms of the GPL-v2 or later.\npragma solidity ^0.8.25;\n\n// factory contraints on pools\nuint8 constant MAX_PROTOCOL_FEE_RATIO_D3 = 0.25e3; // 25%\nuint256 constant MAX_PROTOCOL_LENDING_FEE_RATE_D18 = 0.02e18; // 2%\nuint64 constant MAX_POOL_FEE_D18 = 0.9e18; // 90%\nuint64 constant MIN_LOOKBACK = 1 seconds;\n\n// pool constraints\nuint8 constant NUMBER_OF_KINDS = 4;\nint32 constant NUMBER_OF_KINDS_32 = int32(int8(NUMBER_OF_KINDS));\nuint256 constant MAX_TICK = 322_378; // max price 1e14 in D18 scale\nint32 constant MAX_TICK_32 = int32(int256(MAX_TICK));\nint32 constant MIN_TICK_32 = int32(-int256(MAX_TICK));\nuint256 constant MAX_BINS_TO_MERGE = 3;\nuint128 constant MINIMUM_LIQUIDITY = 1e8;\n\n// accessor named constants\nuint8 constant ALL_KINDS_MASK = 0xF; // 0b1111\nuint8 constant PERMISSIONED_LIQUIDITY_MASK = 0x10; // 0b010000\nuint8 constant PERMISSIONED_SWAP_MASK = 0x20; // 0b100000\nuint8 constant OPTIONS_MASK = ALL_KINDS_MASK | PERMISSIONED_LIQUIDITY_MASK | PERMISSIONED_SWAP_MASK; // 0b111111\n\n// named values\naddress constant MERGED_LP_BALANCE_ADDRESS = address(0);\nuint256 constant MERGED_LP_BALANCE_SUBACCOUNT = 0;\nuint128 constant ONE = 1e18;\nuint128 constant ONE_SQUARED = 1e36;\nint256 constant INT256_ONE = 1e18;\nuint256 constant ONE_D8 = 1e8;\nuint256 constant ONE_D3 = 1e3;\nint40 constant INT_ONE_D8 = 1e8;\nint40 constant HALF_TICK_D8 = 0.5e8;\nuint8 constant DEFAULT_DECIMALS = 18;\nuint256 constant DEFAULT_SCALE = 1;\nbytes constant EMPTY_PRICE_BREAKS = hex\"010000000000000000000000\";" + }, + "lib/rooster/v2-common/libraries/Math.sol": { + "content": "// SPDX-License-Identifier: GPL-2.0-or-later\n// As the copyright holder of this work, Ubiquity Labs retains\n// the right to distribute, use, and modify this code under any license of\n// their choosing, in addition to the terms of the GPL-v2 or later.\npragma solidity ^0.8.25;\n\nimport {Math as OzMath} from \"../../openzeppelin-custom/contracts/utils/math/Math.sol\";\n\nimport {ONE, DEFAULT_SCALE, DEFAULT_DECIMALS, INT_ONE_D8, ONE_SQUARED} from \"./Constants.sol\";\n\n/**\n * @notice Math functions.\n */\nlibrary Math {\n /**\n * @notice Returns the lesser of two values.\n * @param x First uint256 value.\n * @param y Second uint256 value.\n */\n function min(uint256 x, uint256 y) internal pure returns (uint256 z) {\n assembly (\"memory-safe\") {\n z := xor(x, mul(xor(x, y), lt(y, x)))\n }\n }\n\n /**\n * @notice Returns the lesser of two uint128 values.\n * @param x First uint128 value.\n * @param y Second uint128 value.\n */\n function min128(uint128 x, uint128 y) internal pure returns (uint128 z) {\n assembly (\"memory-safe\") {\n z := xor(x, mul(xor(x, y), lt(y, x)))\n }\n }\n\n /**\n * @notice Returns the lesser of two int256 values.\n * @param x First int256 value.\n * @param y Second int256 value.\n */\n function min(int256 x, int256 y) internal pure returns (int256 z) {\n assembly (\"memory-safe\") {\n z := xor(x, mul(xor(x, y), slt(y, x)))\n }\n }\n\n /**\n * @notice Returns the greater of two uint256 values.\n * @param x First uint256 value.\n * @param y Second uint256 value.\n */\n function max(uint256 x, uint256 y) internal pure returns (uint256 z) {\n assembly (\"memory-safe\") {\n z := xor(x, mul(xor(x, y), gt(y, x)))\n }\n }\n\n /**\n * @notice Returns the greater of two int256 values.\n * @param x First int256 value.\n * @param y Second int256 value.\n */\n function max(int256 x, int256 y) internal pure returns (int256 z) {\n assembly (\"memory-safe\") {\n z := xor(x, mul(xor(x, y), sgt(y, x)))\n }\n }\n\n /**\n * @notice Returns the greater of two uint128 values.\n * @param x First uint128 value.\n * @param y Second uint128 value.\n */\n function max128(uint128 x, uint128 y) internal pure returns (uint128 z) {\n assembly (\"memory-safe\") {\n z := xor(x, mul(xor(x, y), gt(y, x)))\n }\n }\n\n /**\n * @notice Thresholds a value to be within the specified bounds.\n * @param value The value to bound.\n * @param lowerLimit The minimum allowable value.\n * @param upperLimit The maximum allowable value.\n */\n function boundValue(\n uint256 value,\n uint256 lowerLimit,\n uint256 upperLimit\n ) internal pure returns (uint256 outputValue) {\n outputValue = min(max(value, lowerLimit), upperLimit);\n }\n\n /**\n * @notice Returns the difference between two uint128 values or zero if the result would be negative.\n * @param x The minuend.\n * @param y The subtrahend.\n */\n function clip128(uint128 x, uint128 y) internal pure returns (uint128) {\n unchecked {\n return x < y ? 0 : x - y;\n }\n }\n\n /**\n * @notice Returns the difference between two uint256 values or zero if the result would be negative.\n * @param x The minuend.\n * @param y The subtrahend.\n */\n function clip(uint256 x, uint256 y) internal pure returns (uint256) {\n unchecked {\n return x < y ? 0 : x - y;\n }\n }\n\n /**\n * @notice Divides one uint256 by another, rounding down to the nearest\n * integer.\n * @param x The dividend.\n * @param y The divisor.\n */\n function divFloor(uint256 x, uint256 y) internal pure returns (uint256) {\n return mulDivFloor(x, ONE, y);\n }\n\n /**\n * @notice Divides one uint256 by another, rounding up to the nearest integer.\n * @param x The dividend.\n * @param y The divisor.\n */\n function divCeil(uint256 x, uint256 y) internal pure returns (uint256) {\n return mulDivCeil(x, ONE, y);\n }\n\n /**\n * @notice Multiplies two uint256 values and then divides by ONE, rounding down.\n * @param x The multiplicand.\n * @param y The multiplier.\n */\n function mulFloor(uint256 x, uint256 y) internal pure returns (uint256) {\n return OzMath.mulDiv(x, y, ONE);\n }\n\n /**\n * @notice Multiplies two uint256 values and then divides by ONE, rounding up.\n * @param x The multiplicand.\n * @param y The multiplier.\n */\n function mulCeil(uint256 x, uint256 y) internal pure returns (uint256) {\n return mulDivCeil(x, y, ONE);\n }\n\n /**\n * @notice Calculates the multiplicative inverse of a uint256, rounding down.\n * @param x The value to invert.\n */\n function invFloor(uint256 x) internal pure returns (uint256) {\n unchecked {\n return ONE_SQUARED / x;\n }\n }\n\n /**\n * @notice Calculates the multiplicative inverse of a uint256, rounding up.\n * @param denominator The value to invert.\n */\n function invCeil(uint256 denominator) internal pure returns (uint256 z) {\n assembly (\"memory-safe\") {\n // divide z - 1 by the denominator and add 1.\n z := add(div(sub(ONE_SQUARED, 1), denominator), 1)\n }\n }\n\n /**\n * @notice Multiplies two uint256 values and divides by a third, rounding down.\n * @param x The multiplicand.\n * @param y The multiplier.\n * @param k The divisor.\n */\n function mulDivFloor(uint256 x, uint256 y, uint256 k) internal pure returns (uint256 result) {\n result = OzMath.mulDiv(x, y, max(1, k));\n }\n\n /**\n * @notice Multiplies two uint256 values and divides by a third, rounding up if there's a remainder.\n * @param x The multiplicand.\n * @param y The multiplier.\n * @param k The divisor.\n */\n function mulDivCeil(uint256 x, uint256 y, uint256 k) internal pure returns (uint256 result) {\n result = mulDivFloor(x, y, k);\n if (mulmod(x, y, max(1, k)) != 0) result = result + 1;\n }\n\n /**\n * @notice Multiplies two uint256 values and divides by a third, rounding\n * down. Will revert if `x * y` is larger than `type(uint256).max`.\n * @param x The first operand for multiplication.\n * @param y The second operand for multiplication.\n * @param denominator The divisor after multiplication.\n */\n function mulDivDown(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 z) {\n assembly (\"memory-safe\") {\n // Store x * y in z for now.\n z := mul(x, y)\n if iszero(denominator) {\n denominator := 1\n }\n\n if iszero(or(iszero(x), eq(div(z, x), y))) {\n revert(0, 0)\n }\n\n // Divide z by the denominator.\n z := div(z, denominator)\n }\n }\n\n /**\n * @notice Multiplies two uint256 values and divides by a third, rounding\n * up. Will revert if `x * y` is larger than `type(uint256).max`.\n * @param x The first operand for multiplication.\n * @param y The second operand for multiplication.\n * @param denominator The divisor after multiplication.\n */\n function mulDivUp(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 z) {\n assembly (\"memory-safe\") {\n // Store x * y in z for now.\n z := mul(x, y)\n if iszero(denominator) {\n denominator := 1\n }\n\n if iszero(or(iszero(x), eq(div(z, x), y))) {\n revert(0, 0)\n }\n\n // First, divide z - 1 by the denominator and add 1.\n // We allow z - 1 to underflow if z is 0, because we multiply the\n // end result by 0 if z is zero, ensuring we return 0 if z is zero.\n z := mul(iszero(iszero(z)), add(div(sub(z, 1), denominator), 1))\n }\n }\n\n /**\n * @notice Multiplies a uint256 by another and divides by a constant,\n * rounding down. Will revert if `x * y` is larger than\n * `type(uint256).max`.\n * @param x The multiplicand.\n * @param y The multiplier.\n */\n function mulDown(uint256 x, uint256 y) internal pure returns (uint256) {\n return mulDivDown(x, y, ONE);\n }\n\n /**\n * @notice Divides a uint256 by another, rounding down the result. Will\n * revert if `x * 1e18` is larger than `type(uint256).max`.\n * @param x The dividend.\n * @param y The divisor.\n */\n function divDown(uint256 x, uint256 y) internal pure returns (uint256) {\n return mulDivDown(x, ONE, y);\n }\n\n /**\n * @notice Divides a uint256 by another, rounding up the result. Will\n * revert if `x * 1e18` is larger than `type(uint256).max`.\n * @param x The dividend.\n * @param y The divisor.\n */\n function divUp(uint256 x, uint256 y) internal pure returns (uint256) {\n return mulDivUp(x, ONE, y);\n }\n\n /**\n * @notice Scales a number based on a difference in decimals from a default.\n * @param decimals The new decimal precision.\n */\n function scale(uint8 decimals) internal pure returns (uint256) {\n unchecked {\n if (decimals == DEFAULT_DECIMALS) {\n return DEFAULT_SCALE;\n } else {\n return 10 ** (DEFAULT_DECIMALS - decimals);\n }\n }\n }\n\n /**\n * @notice Adjusts a scaled amount to the token decimal scale.\n * @param amount The scaled amount.\n * @param scaleFactor The scaling factor to adjust by.\n * @param ceil Whether to round up (true) or down (false).\n */\n function ammScaleToTokenScale(uint256 amount, uint256 scaleFactor, bool ceil) internal pure returns (uint256 z) {\n unchecked {\n if (scaleFactor == DEFAULT_SCALE || amount == 0) {\n return amount;\n } else {\n if (!ceil) return amount / scaleFactor;\n assembly (\"memory-safe\") {\n z := add(div(sub(amount, 1), scaleFactor), 1)\n }\n }\n }\n }\n\n /**\n * @notice Adjusts a token amount to the D18 AMM scale.\n * @param amount The amount in token scale.\n * @param scaleFactor The scale factor for adjustment.\n */\n function tokenScaleToAmmScale(uint256 amount, uint256 scaleFactor) internal pure returns (uint256) {\n if (scaleFactor == DEFAULT_SCALE) {\n return amount;\n } else {\n return amount * scaleFactor;\n }\n }\n\n /**\n * @notice Returns the absolute value of a signed 32-bit integer.\n * @param x The integer to take the absolute value of.\n */\n function abs32(int32 x) internal pure returns (uint32) {\n unchecked {\n return uint32(x < 0 ? -x : x);\n }\n }\n\n /**\n * @notice Returns the absolute value of a signed 256-bit integer.\n * @param x The integer to take the absolute value of.\n */\n function abs(int256 x) internal pure returns (uint256) {\n unchecked {\n return uint256(x < 0 ? -x : x);\n }\n }\n\n /**\n * @notice Calculates the integer square root of a uint256 rounded down.\n * @param x The number to take the square root of.\n */\n function sqrt(uint256 x) internal pure returns (uint256 z) {\n // from https://github.com/transmissions11/solmate/blob/e8f96f25d48fe702117ce76c79228ca4f20206cb/src/utils/FixedPointMathLib.sol\n assembly (\"memory-safe\") {\n let y := x\n z := 181\n\n if iszero(lt(y, 0x10000000000000000000000000000000000)) {\n y := shr(128, y)\n z := shl(64, z)\n }\n if iszero(lt(y, 0x1000000000000000000)) {\n y := shr(64, y)\n z := shl(32, z)\n }\n if iszero(lt(y, 0x10000000000)) {\n y := shr(32, y)\n z := shl(16, z)\n }\n if iszero(lt(y, 0x1000000)) {\n y := shr(16, y)\n z := shl(8, z)\n }\n\n z := shr(18, mul(z, add(y, 65536)))\n\n z := shr(1, add(z, div(x, z)))\n z := shr(1, add(z, div(x, z)))\n z := shr(1, add(z, div(x, z)))\n z := shr(1, add(z, div(x, z)))\n z := shr(1, add(z, div(x, z)))\n z := shr(1, add(z, div(x, z)))\n z := shr(1, add(z, div(x, z)))\n\n z := sub(z, lt(div(x, z), z))\n }\n }\n\n /**\n * @notice Computes the floor of a D8-scaled number as an int32, ignoring\n * potential overflow in the cast.\n * @param val The D8-scaled number.\n */\n function floorD8Unchecked(int256 val) internal pure returns (int32) {\n int32 val32;\n bool check;\n unchecked {\n val32 = int32(val / INT_ONE_D8);\n check = (val < 0 && val % INT_ONE_D8 != 0);\n }\n return check ? val32 - 1 : val32;\n }\n}" + }, + "lib/rooster/v2-common/libraries/TickMath.sol": { + "content": "// SPDX-License-Identifier: GPL-2.0-or-later\n// As the copyright holder of this work, Ubiquity Labs retains\n// the right to distribute, use, and modify this code under any license of\n// their choosing, in addition to the terms of the GPL-v2 or later.\npragma solidity ^0.8.25;\n\nimport {Math as OzMath} from \"../../openzeppelin-custom/contracts/utils/math/Math.sol\";\nimport {Math} from \"./Math.sol\";\nimport {MAX_TICK, ONE} from \"./Constants.sol\";\n\n/**\n * @notice Math functions related to tick operations.\n */\n// slither-disable-start divide-before-multiply\nlibrary TickMath {\n using Math for uint256;\n\n error TickMaxExceeded(int256 tick);\n\n /**\n * @notice Compute the lower and upper sqrtPrice of a tick.\n * @param tickSpacing The tick spacing used for calculations.\n * @param _tick The input tick value.\n */\n function tickSqrtPrices(\n uint256 tickSpacing,\n int32 _tick\n ) internal pure returns (uint256 sqrtLowerPrice, uint256 sqrtUpperPrice) {\n unchecked {\n sqrtLowerPrice = tickSqrtPrice(tickSpacing, _tick);\n sqrtUpperPrice = tickSqrtPrice(tickSpacing, _tick + 1);\n }\n }\n\n /**\n * @notice Compute the base tick value from the pool tick and the\n * tickSpacing. Revert if base tick is beyond the max tick boundary.\n * @param tickSpacing The tick spacing used for calculations.\n * @param _tick The input tick value.\n */\n function subTickIndex(uint256 tickSpacing, int32 _tick) internal pure returns (uint32 subTick) {\n subTick = Math.abs32(_tick);\n subTick *= uint32(tickSpacing);\n if (subTick > MAX_TICK) {\n revert TickMaxExceeded(_tick);\n }\n }\n\n /**\n * @notice Calculate the square root price for a given tick and tick spacing.\n * @param tickSpacing The tick spacing used for calculations.\n * @param _tick The input tick value.\n * @return _result The square root price.\n */\n function tickSqrtPrice(uint256 tickSpacing, int32 _tick) internal pure returns (uint256 _result) {\n unchecked {\n uint256 tick = subTickIndex(tickSpacing, _tick);\n\n uint256 ratio = tick & 0x1 != 0 ? 0xfffcb933bd6fad9d3af5f0b9f25db4d6 : 0x100000000000000000000000000000000;\n if (tick & 0x2 != 0) ratio = (ratio * 0xfff97272373d41fd789c8cb37ffcaa1c) >> 128;\n if (tick & 0x4 != 0) ratio = (ratio * 0xfff2e50f5f656ac9229c67059486f389) >> 128;\n if (tick & 0x8 != 0) ratio = (ratio * 0xffe5caca7e10e81259b3cddc7a064941) >> 128;\n if (tick & 0x10 != 0) ratio = (ratio * 0xffcb9843d60f67b19e8887e0bd251eb7) >> 128;\n if (tick & 0x20 != 0) ratio = (ratio * 0xff973b41fa98cd2e57b660be99eb2c4a) >> 128;\n if (tick & 0x40 != 0) ratio = (ratio * 0xff2ea16466c9838804e327cb417cafcb) >> 128;\n if (tick & 0x80 != 0) ratio = (ratio * 0xfe5dee046a99d51e2cc356c2f617dbe0) >> 128;\n if (tick & 0x100 != 0) ratio = (ratio * 0xfcbe86c7900aecf64236ab31f1f9dcb5) >> 128;\n if (tick & 0x200 != 0) ratio = (ratio * 0xf987a7253ac4d9194200696907cf2e37) >> 128;\n if (tick & 0x400 != 0) ratio = (ratio * 0xf3392b0822b88206f8abe8a3b44dd9be) >> 128;\n if (tick & 0x800 != 0) ratio = (ratio * 0xe7159475a2c578ef4f1d17b2b235d480) >> 128;\n if (tick & 0x1000 != 0) ratio = (ratio * 0xd097f3bdfd254ee83bdd3f248e7e785e) >> 128;\n if (tick & 0x2000 != 0) ratio = (ratio * 0xa9f746462d8f7dd10e744d913d033333) >> 128;\n if (tick & 0x4000 != 0) ratio = (ratio * 0x70d869a156ddd32a39e257bc3f50aa9b) >> 128;\n if (tick & 0x8000 != 0) ratio = (ratio * 0x31be135f97da6e09a19dc367e3b6da40) >> 128;\n if (tick & 0x10000 != 0) ratio = (ratio * 0x9aa508b5b7e5a9780b0cc4e25d61a56) >> 128;\n if (tick & 0x20000 != 0) ratio = (ratio * 0x5d6af8dedbcb3a6ccb7ce618d14225) >> 128;\n if (tick & 0x40000 != 0) ratio = (ratio * 0x2216e584f630389b2052b8db590e) >> 128;\n if (_tick > 0) ratio = type(uint256).max / ratio;\n _result = (ratio * ONE) >> 128;\n }\n }\n\n /**\n * @notice Calculate liquidity of a tick.\n * @param reserveA Tick reserve of token A.\n * @param reserveB Tick reserve of token B.\n * @param sqrtLowerTickPrice The square root price of the lower tick edge.\n * @param sqrtUpperTickPrice The square root price of the upper tick edge.\n */\n function getTickL(\n uint256 reserveA,\n uint256 reserveB,\n uint256 sqrtLowerTickPrice,\n uint256 sqrtUpperTickPrice\n ) internal pure returns (uint256 liquidity) {\n // known:\n // - sqrt price values are different\n // - reserveA and reserveB fit in 128 bit\n // - sqrt price is in (1e-7, 1e7)\n // - D18 max for uint256 is 1.15e59\n // - D18 min is 1e-18\n\n unchecked {\n // diff is in (5e-12, 4e6); max tick spacing is 10_000\n uint256 diff = sqrtUpperTickPrice - sqrtLowerTickPrice;\n\n // Need to maximize precision by shifting small values A and B up so\n // that they use more of the available bit range. Two constraints to\n // consider: we need A * B * diff / sqrtPrice to be bigger than 1e-18\n // when the bump is not in play. This constrains the threshold for\n // bumping to be at least 77 bit; ie, either a or b needs 2^77 which\n // means that term A * B * diff / sqrtPrice > 1e-18.\n //\n // At the other end, the second constraint is that b^2 needs to fit in\n // a 256-bit number, so, post bump, the max reserve value needs to be\n // less than 6e22. With a 78-bit threshold and a 57-bit bump, we have A\n // and B are in (1.4e-1, 4.4e22 (2^(78+57))) with bump, and one of A or\n // B is at least 2^78 without the bump, but the other reserve value may\n // be as small as 1 wei.\n uint256 precisionBump = 0;\n if ((reserveA >> 78) == 0 && (reserveB >> 78) == 0) {\n precisionBump = 57;\n reserveA <<= precisionBump;\n reserveB <<= precisionBump;\n }\n\n if (reserveB == 0) return Math.divDown(reserveA, diff) >> precisionBump;\n if (reserveA == 0)\n return Math.mulDivDown(reserveB.mulDown(sqrtLowerTickPrice), sqrtUpperTickPrice, diff) >> precisionBump;\n\n // b is in (7.2e-9 (2^57 / 1e7 / 2), 2.8e29 (2^(78+57) * 1e7 / 2)) with bump\n // b is in a subset of the same range without bump\n uint256 b = (reserveA.divDown(sqrtUpperTickPrice) + reserveB.mulDown(sqrtLowerTickPrice)) >> 1;\n\n // b^2 is in (5.1e-17, 4.8e58); and will not overflow on either end;\n // A*B is in (3e-13 (2^78 / 1e18 * 1e-18), 1.9e45) without bump and is in a subset range with bump\n // A*B*diff/sqrtUpper is in (1.5e-17 (3e-13 * 5e-12 * 1e7), 7.6e58);\n\n // Since b^2 is at the upper edge of the precision range, we are not\n // able to multiply the argument of the sqrt by 1e18, instead, we move\n // this factor outside of the sqrt. The resulting loss of precision\n // means that this liquidity value is a lower bound on the tick\n // liquidity\n return\n OzMath.mulDiv(\n b +\n Math.sqrt(\n (OzMath.mulDiv(b, b, ONE) +\n OzMath.mulDiv(reserveB.mulFloor(reserveA), diff, sqrtUpperTickPrice))\n ) *\n 1e9,\n sqrtUpperTickPrice,\n diff\n ) >> precisionBump;\n }\n }\n\n /**\n * @notice Calculate square root price of a tick. Returns left edge of the\n * tick if the tick has no reserves.\n * @param reserveA Tick reserve of token A.\n * @param reserveB Tick reserve of token B.\n * @param sqrtLowerTickPrice The square root price of the lower tick edge.\n * @param sqrtUpperTickPrice The square root price of the upper tick edge.\n * @return sqrtPrice The calculated square root price.\n */\n function getSqrtPrice(\n uint256 reserveA,\n uint256 reserveB,\n uint256 sqrtLowerTickPrice,\n uint256 sqrtUpperTickPrice,\n uint256 liquidity\n ) internal pure returns (uint256 sqrtPrice) {\n unchecked {\n if (reserveA == 0) {\n return sqrtLowerTickPrice;\n }\n if (reserveB == 0) {\n return sqrtUpperTickPrice;\n }\n sqrtPrice = Math.sqrt(\n ONE *\n (reserveA + liquidity.mulDown(sqrtLowerTickPrice)).divDown(\n reserveB + liquidity.divDown(sqrtUpperTickPrice)\n )\n );\n sqrtPrice = Math.boundValue(sqrtPrice, sqrtLowerTickPrice, sqrtUpperTickPrice);\n }\n }\n\n /**\n * @notice Calculate square root price of a tick. Returns left edge of the\n * tick if the tick has no reserves.\n * @param reserveA Tick reserve of token A.\n * @param reserveB Tick reserve of token B.\n * @param sqrtLowerTickPrice The square root price of the lower tick edge.\n * @param sqrtUpperTickPrice The square root price of the upper tick edge.\n * @return sqrtPrice The calculated square root price.\n * @return liquidity The calculated liquidity.\n */\n function getTickSqrtPriceAndL(\n uint256 reserveA,\n uint256 reserveB,\n uint256 sqrtLowerTickPrice,\n uint256 sqrtUpperTickPrice\n ) internal pure returns (uint256 sqrtPrice, uint256 liquidity) {\n liquidity = getTickL(reserveA, reserveB, sqrtLowerTickPrice, sqrtUpperTickPrice);\n sqrtPrice = getSqrtPrice(reserveA, reserveB, sqrtLowerTickPrice, sqrtUpperTickPrice, liquidity);\n }\n}\n// slither-disable-end divide-before-multiply" + }, + "solidity-bytes-utils/contracts/BytesLib.sol": { + "content": "// SPDX-License-Identifier: Unlicense\n/*\n * @title Solidity Bytes Arrays Utils\n * @author Gonçalo Sá \n *\n * @dev Bytes tightly packed arrays utility library for ethereum contracts written in Solidity.\n * The library lets you concatenate, slice and type cast bytes arrays both in memory and storage.\n */\npragma solidity >=0.8.0 <0.9.0;\n\n\nlibrary BytesLib {\n function concat(\n bytes memory _preBytes,\n bytes memory _postBytes\n )\n internal\n pure\n returns (bytes memory)\n {\n bytes memory tempBytes;\n\n assembly {\n // Get a location of some free memory and store it in tempBytes as\n // Solidity does for memory variables.\n tempBytes := mload(0x40)\n\n // Store the length of the first bytes array at the beginning of\n // the memory for tempBytes.\n let length := mload(_preBytes)\n mstore(tempBytes, length)\n\n // Maintain a memory counter for the current write location in the\n // temp bytes array by adding the 32 bytes for the array length to\n // the starting location.\n let mc := add(tempBytes, 0x20)\n // Stop copying when the memory counter reaches the length of the\n // first bytes array.\n let end := add(mc, length)\n\n for {\n // Initialize a copy counter to the start of the _preBytes data,\n // 32 bytes into its memory.\n let cc := add(_preBytes, 0x20)\n } lt(mc, end) {\n // Increase both counters by 32 bytes each iteration.\n mc := add(mc, 0x20)\n cc := add(cc, 0x20)\n } {\n // Write the _preBytes data into the tempBytes memory 32 bytes\n // at a time.\n mstore(mc, mload(cc))\n }\n\n // Add the length of _postBytes to the current length of tempBytes\n // and store it as the new length in the first 32 bytes of the\n // tempBytes memory.\n length := mload(_postBytes)\n mstore(tempBytes, add(length, mload(tempBytes)))\n\n // Move the memory counter back from a multiple of 0x20 to the\n // actual end of the _preBytes data.\n mc := end\n // Stop copying when the memory counter reaches the new combined\n // length of the arrays.\n end := add(mc, length)\n\n for {\n let cc := add(_postBytes, 0x20)\n } lt(mc, end) {\n mc := add(mc, 0x20)\n cc := add(cc, 0x20)\n } {\n mstore(mc, mload(cc))\n }\n\n // Update the free-memory pointer by padding our last write location\n // to 32 bytes: add 31 bytes to the end of tempBytes to move to the\n // next 32 byte block, then round down to the nearest multiple of\n // 32. If the sum of the length of the two arrays is zero then add\n // one before rounding down to leave a blank 32 bytes (the length block with 0).\n mstore(0x40, and(\n add(add(end, iszero(add(length, mload(_preBytes)))), 31),\n not(31) // Round down to the nearest 32 bytes.\n ))\n }\n\n return tempBytes;\n }\n\n function concatStorage(bytes storage _preBytes, bytes memory _postBytes) internal {\n assembly {\n // Read the first 32 bytes of _preBytes storage, which is the length\n // of the array. (We don't need to use the offset into the slot\n // because arrays use the entire slot.)\n let fslot := sload(_preBytes.slot)\n // Arrays of 31 bytes or less have an even value in their slot,\n // while longer arrays have an odd value. The actual length is\n // the slot divided by two for odd values, and the lowest order\n // byte divided by two for even values.\n // If the slot is even, bitwise and the slot with 255 and divide by\n // two to get the length. If the slot is odd, bitwise and the slot\n // with -1 and divide by two.\n let slength := div(and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)), 2)\n let mlength := mload(_postBytes)\n let newlength := add(slength, mlength)\n // slength can contain both the length and contents of the array\n // if length < 32 bytes so let's prepare for that\n // v. http://solidity.readthedocs.io/en/latest/miscellaneous.html#layout-of-state-variables-in-storage\n switch add(lt(slength, 32), lt(newlength, 32))\n case 2 {\n // Since the new array still fits in the slot, we just need to\n // update the contents of the slot.\n // uint256(bytes_storage) = uint256(bytes_storage) + uint256(bytes_memory) + new_length\n sstore(\n _preBytes.slot,\n // all the modifications to the slot are inside this\n // next block\n add(\n // we can just add to the slot contents because the\n // bytes we want to change are the LSBs\n fslot,\n add(\n mul(\n div(\n // load the bytes from memory\n mload(add(_postBytes, 0x20)),\n // zero all bytes to the right\n exp(0x100, sub(32, mlength))\n ),\n // and now shift left the number of bytes to\n // leave space for the length in the slot\n exp(0x100, sub(32, newlength))\n ),\n // increase length by the double of the memory\n // bytes length\n mul(mlength, 2)\n )\n )\n )\n }\n case 1 {\n // The stored value fits in the slot, but the combined value\n // will exceed it.\n // get the keccak hash to get the contents of the array\n mstore(0x0, _preBytes.slot)\n let sc := add(keccak256(0x0, 0x20), div(slength, 32))\n\n // save new length\n sstore(_preBytes.slot, add(mul(newlength, 2), 1))\n\n // The contents of the _postBytes array start 32 bytes into\n // the structure. Our first read should obtain the `submod`\n // bytes that can fit into the unused space in the last word\n // of the stored array. To get this, we read 32 bytes starting\n // from `submod`, so the data we read overlaps with the array\n // contents by `submod` bytes. Masking the lowest-order\n // `submod` bytes allows us to add that value directly to the\n // stored value.\n\n let submod := sub(32, slength)\n let mc := add(_postBytes, submod)\n let end := add(_postBytes, mlength)\n let mask := sub(exp(0x100, submod), 1)\n\n sstore(\n sc,\n add(\n and(\n fslot,\n 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00\n ),\n and(mload(mc), mask)\n )\n )\n\n for {\n mc := add(mc, 0x20)\n sc := add(sc, 1)\n } lt(mc, end) {\n sc := add(sc, 1)\n mc := add(mc, 0x20)\n } {\n sstore(sc, mload(mc))\n }\n\n mask := exp(0x100, sub(mc, end))\n\n sstore(sc, mul(div(mload(mc), mask), mask))\n }\n default {\n // get the keccak hash to get the contents of the array\n mstore(0x0, _preBytes.slot)\n // Start copying to the last used word of the stored array.\n let sc := add(keccak256(0x0, 0x20), div(slength, 32))\n\n // save new length\n sstore(_preBytes.slot, add(mul(newlength, 2), 1))\n\n // Copy over the first `submod` bytes of the new data as in\n // case 1 above.\n let slengthmod := mod(slength, 32)\n let mlengthmod := mod(mlength, 32)\n let submod := sub(32, slengthmod)\n let mc := add(_postBytes, submod)\n let end := add(_postBytes, mlength)\n let mask := sub(exp(0x100, submod), 1)\n\n sstore(sc, add(sload(sc), and(mload(mc), mask)))\n\n for {\n sc := add(sc, 1)\n mc := add(mc, 0x20)\n } lt(mc, end) {\n sc := add(sc, 1)\n mc := add(mc, 0x20)\n } {\n sstore(sc, mload(mc))\n }\n\n mask := exp(0x100, sub(mc, end))\n\n sstore(sc, mul(div(mload(mc), mask), mask))\n }\n }\n }\n\n function slice(\n bytes memory _bytes,\n uint256 _start,\n uint256 _length\n )\n internal\n pure\n returns (bytes memory)\n {\n // We're using the unchecked block below because otherwise execution ends \n // with the native overflow error code.\n unchecked {\n require(_length + 31 >= _length, \"slice_overflow\");\n }\n require(_bytes.length >= _start + _length, \"slice_outOfBounds\");\n\n bytes memory tempBytes;\n\n assembly {\n switch iszero(_length)\n case 0 {\n // Get a location of some free memory and store it in tempBytes as\n // Solidity does for memory variables.\n tempBytes := mload(0x40)\n\n // The first word of the slice result is potentially a partial\n // word read from the original array. To read it, we calculate\n // the length of that partial word and start copying that many\n // bytes into the array. The first word we copy will start with\n // data we don't care about, but the last `lengthmod` bytes will\n // land at the beginning of the contents of the new array. When\n // we're done copying, we overwrite the full first word with\n // the actual length of the slice.\n let lengthmod := and(_length, 31)\n\n // The multiplication in the next line is necessary\n // because when slicing multiples of 32 bytes (lengthmod == 0)\n // the following copy loop was copying the origin's length\n // and then ending prematurely not copying everything it should.\n let mc := add(add(tempBytes, lengthmod), mul(0x20, iszero(lengthmod)))\n let end := add(mc, _length)\n\n for {\n // The multiplication in the next line has the same exact purpose\n // as the one above.\n let cc := add(add(add(_bytes, lengthmod), mul(0x20, iszero(lengthmod))), _start)\n } lt(mc, end) {\n mc := add(mc, 0x20)\n cc := add(cc, 0x20)\n } {\n mstore(mc, mload(cc))\n }\n\n mstore(tempBytes, _length)\n\n //update free-memory pointer\n //allocating the array padded to 32 bytes like the compiler does now\n mstore(0x40, and(add(mc, 31), not(31)))\n }\n //if we want a zero-length slice let's just return a zero-length array\n default {\n tempBytes := mload(0x40)\n //zero out the 32 bytes slice we are about to return\n //we need to do it because Solidity does not garbage collect\n mstore(tempBytes, 0)\n\n mstore(0x40, add(tempBytes, 0x20))\n }\n }\n\n return tempBytes;\n }\n\n function toAddress(bytes memory _bytes, uint256 _start) internal pure returns (address) {\n require(_bytes.length >= _start + 20, \"toAddress_outOfBounds\");\n address tempAddress;\n\n assembly {\n tempAddress := div(mload(add(add(_bytes, 0x20), _start)), 0x1000000000000000000000000)\n }\n\n return tempAddress;\n }\n\n function toUint8(bytes memory _bytes, uint256 _start) internal pure returns (uint8) {\n require(_bytes.length >= _start + 1 , \"toUint8_outOfBounds\");\n uint8 tempUint;\n\n assembly {\n tempUint := mload(add(add(_bytes, 0x1), _start))\n }\n\n return tempUint;\n }\n\n function toUint16(bytes memory _bytes, uint256 _start) internal pure returns (uint16) {\n require(_bytes.length >= _start + 2, \"toUint16_outOfBounds\");\n uint16 tempUint;\n\n assembly {\n tempUint := mload(add(add(_bytes, 0x2), _start))\n }\n\n return tempUint;\n }\n\n function toUint32(bytes memory _bytes, uint256 _start) internal pure returns (uint32) {\n require(_bytes.length >= _start + 4, \"toUint32_outOfBounds\");\n uint32 tempUint;\n\n assembly {\n tempUint := mload(add(add(_bytes, 0x4), _start))\n }\n\n return tempUint;\n }\n\n function toUint64(bytes memory _bytes, uint256 _start) internal pure returns (uint64) {\n require(_bytes.length >= _start + 8, \"toUint64_outOfBounds\");\n uint64 tempUint;\n\n assembly {\n tempUint := mload(add(add(_bytes, 0x8), _start))\n }\n\n return tempUint;\n }\n\n function toUint96(bytes memory _bytes, uint256 _start) internal pure returns (uint96) {\n require(_bytes.length >= _start + 12, \"toUint96_outOfBounds\");\n uint96 tempUint;\n\n assembly {\n tempUint := mload(add(add(_bytes, 0xc), _start))\n }\n\n return tempUint;\n }\n\n function toUint128(bytes memory _bytes, uint256 _start) internal pure returns (uint128) {\n require(_bytes.length >= _start + 16, \"toUint128_outOfBounds\");\n uint128 tempUint;\n\n assembly {\n tempUint := mload(add(add(_bytes, 0x10), _start))\n }\n\n return tempUint;\n }\n\n function toUint256(bytes memory _bytes, uint256 _start) internal pure returns (uint256) {\n require(_bytes.length >= _start + 32, \"toUint256_outOfBounds\");\n uint256 tempUint;\n\n assembly {\n tempUint := mload(add(add(_bytes, 0x20), _start))\n }\n\n return tempUint;\n }\n\n function toBytes32(bytes memory _bytes, uint256 _start) internal pure returns (bytes32) {\n require(_bytes.length >= _start + 32, \"toBytes32_outOfBounds\");\n bytes32 tempBytes32;\n\n assembly {\n tempBytes32 := mload(add(add(_bytes, 0x20), _start))\n }\n\n return tempBytes32;\n }\n\n function equal(bytes memory _preBytes, bytes memory _postBytes) internal pure returns (bool) {\n bool success = true;\n\n assembly {\n let length := mload(_preBytes)\n\n // if lengths don't match the arrays are not equal\n switch eq(length, mload(_postBytes))\n case 1 {\n // cb is a circuit breaker in the for loop since there's\n // no said feature for inline assembly loops\n // cb = 1 - don't breaker\n // cb = 0 - break\n let cb := 1\n\n let mc := add(_preBytes, 0x20)\n let end := add(mc, length)\n\n for {\n let cc := add(_postBytes, 0x20)\n // the next line is the loop condition:\n // while(uint256(mc < end) + cb == 2)\n } eq(add(lt(mc, end), cb), 2) {\n mc := add(mc, 0x20)\n cc := add(cc, 0x20)\n } {\n // if any of these checks fails then arrays are not equal\n if iszero(eq(mload(mc), mload(cc))) {\n // unsuccess:\n success := 0\n cb := 0\n }\n }\n }\n default {\n // unsuccess:\n success := 0\n }\n }\n\n return success;\n }\n\n function equalStorage(\n bytes storage _preBytes,\n bytes memory _postBytes\n )\n internal\n view\n returns (bool)\n {\n bool success = true;\n\n assembly {\n // we know _preBytes_offset is 0\n let fslot := sload(_preBytes.slot)\n // Decode the length of the stored array like in concatStorage().\n let slength := div(and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)), 2)\n let mlength := mload(_postBytes)\n\n // if lengths don't match the arrays are not equal\n switch eq(slength, mlength)\n case 1 {\n // slength can contain both the length and contents of the array\n // if length < 32 bytes so let's prepare for that\n // v. http://solidity.readthedocs.io/en/latest/miscellaneous.html#layout-of-state-variables-in-storage\n if iszero(iszero(slength)) {\n switch lt(slength, 32)\n case 1 {\n // blank the last byte which is the length\n fslot := mul(div(fslot, 0x100), 0x100)\n\n if iszero(eq(fslot, mload(add(_postBytes, 0x20)))) {\n // unsuccess:\n success := 0\n }\n }\n default {\n // cb is a circuit breaker in the for loop since there's\n // no said feature for inline assembly loops\n // cb = 1 - don't breaker\n // cb = 0 - break\n let cb := 1\n\n // get the keccak hash to get the contents of the array\n mstore(0x0, _preBytes.slot)\n let sc := keccak256(0x0, 0x20)\n\n let mc := add(_postBytes, 0x20)\n let end := add(mc, mlength)\n\n // the next line is the loop condition:\n // while(uint256(mc < end) + cb == 2)\n for {} eq(add(lt(mc, end), cb), 2) {\n sc := add(sc, 1)\n mc := add(mc, 0x20)\n } {\n if iszero(eq(sload(sc), mload(mc))) {\n // unsuccess:\n success := 0\n cb := 0\n }\n }\n }\n }\n }\n default {\n // unsuccess:\n success := 0\n }\n }\n\n return success;\n }\n}\n" + } + }, + "settings": { + "optimizer": { + "enabled": true, + "runs": 200 + }, + "evmVersion": "paris", + "outputSelection": { + "*": { + "*": [ + "abi", + "evm.bytecode", + "evm.deployedBytecode", + "evm.methodIdentifiers", + "metadata", + "devdoc", + "userdoc", + "storageLayout", + "evm.gasEstimates" + ], + "": [ + "ast" + ] + } + }, + "metadata": { + "useLiteralContent": true + } + } +} \ No newline at end of file diff --git a/contracts/deployments/mainnet/.migrations.json b/contracts/deployments/mainnet/.migrations.json index 5701802f4c..2c7ea56107 100644 --- a/contracts/deployments/mainnet/.migrations.json +++ b/contracts/deployments/mainnet/.migrations.json @@ -61,5 +61,7 @@ "164_fix_curve_pb_module": 1769002767, "165_improve_curve_pb_module": 1769002768, "166_curve_pool_booster_factory": 1769174027, + "168_crosschain_strategy_proxies": 1770738208, + "169_crosschain_strategy": 1770738961, "173_improve_curve_pb_module": 1770818413 } \ No newline at end of file diff --git a/contracts/deployments/mainnet/CrossChainMasterStrategy.json b/contracts/deployments/mainnet/CrossChainMasterStrategy.json new file mode 100644 index 0000000000..afbf570533 --- /dev/null +++ b/contracts/deployments/mainnet/CrossChainMasterStrategy.json @@ -0,0 +1,1674 @@ +{ + "address": "0x2567fc741C6b9EFE589F2583e4431Ac4E69D8B8d", + "abi": [ + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "platformAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "vaultAddress", + "type": "address" + } + ], + "internalType": "struct InitializableAbstractStrategy.BaseStrategyConfig", + "name": "_stratConfig", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "address", + "name": "cctpTokenMessenger", + "type": "address" + }, + { + "internalType": "address", + "name": "cctpMessageTransmitter", + "type": "address" + }, + { + "internalType": "uint32", + "name": "peerDomainID", + "type": "uint32" + }, + { + "internalType": "address", + "name": "peerStrategy", + "type": "address" + }, + { + "internalType": "address", + "name": "usdcToken", + "type": "address" + }, + { + "internalType": "address", + "name": "peerUsdcToken", + "type": "address" + } + ], + "internalType": "struct AbstractCCTPIntegrator.CCTPIntegrationConfig", + "name": "_cctpConfig", + "type": "tuple" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint64", + "name": "nonce", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "timestamp", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bool", + "name": "isTooOld", + "type": "bool" + } + ], + "name": "BalanceCheckIgnored", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint16", + "name": "feePremiumBps", + "type": "uint16" + } + ], + "name": "CCTPFeePremiumBpsSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint16", + "name": "minFinalityThreshold", + "type": "uint16" + } + ], + "name": "CCTPMinFinalityThresholdSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "_asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "_pToken", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "Deposit", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousGovernor", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newGovernor", + "type": "address" + } + ], + "name": "GovernorshipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "_oldHarvesterAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "_newHarvesterAddress", + "type": "address" + } + ], + "name": "HarvesterAddressesUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint64", + "name": "lastTransferNonce", + "type": "uint64" + } + ], + "name": "LastTransferNonceUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint32", + "name": "destinationDomain", + "type": "uint32" + }, + { + "indexed": false, + "internalType": "address", + "name": "peerStrategy", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint32", + "name": "minFinalityThreshold", + "type": "uint32" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "message", + "type": "bytes" + } + ], + "name": "MessageTransmitted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint64", + "name": "nonce", + "type": "uint64" + } + ], + "name": "NonceProcessed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "name": "OperatorChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "_asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "_pToken", + "type": "address" + } + ], + "name": "PTokenAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "_asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "_pToken", + "type": "address" + } + ], + "name": "PTokenRemoved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousGovernor", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newGovernor", + "type": "address" + } + ], + "name": "PendingGovernorshipTransfer", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "balance", + "type": "uint256" + } + ], + "name": "RemoteStrategyBalanceUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address[]", + "name": "_oldAddresses", + "type": "address[]" + }, + { + "indexed": false, + "internalType": "address[]", + "name": "_newAddresses", + "type": "address[]" + } + ], + "name": "RewardTokenAddressesUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "rewardToken", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "RewardTokenCollected", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint32", + "name": "destinationDomain", + "type": "uint32" + }, + { + "indexed": false, + "internalType": "address", + "name": "peerStrategy", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "tokenAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "maxFee", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint32", + "name": "minFinalityThreshold", + "type": "uint32" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "hookData", + "type": "bytes" + } + ], + "name": "TokensBridged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [], + "name": "WithdrawAllSkipped", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "WithdrawRequested", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "_asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "_pToken", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "Withdrawal", + "type": "event" + }, + { + "inputs": [], + "name": "MAX_TRANSFER_AMOUNT", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MIN_TRANSFER_AMOUNT", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "assetToPToken", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "cctpMessageTransmitter", + "outputs": [ + { + "internalType": "contract ICCTPMessageTransmitter", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "cctpTokenMessenger", + "outputs": [ + { + "internalType": "contract ICCTPTokenMessenger", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_asset", + "type": "address" + } + ], + "name": "checkBalance", + "outputs": [ + { + "internalType": "uint256", + "name": "balance", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "claimGovernance", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "collectRewardTokens", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_asset", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "deposit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "depositAll", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "feePremiumBps", + "outputs": [ + { + "internalType": "uint16", + "name": "", + "type": "uint16" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getRewardTokenAddresses", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "governor", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "sourceDomain", + "type": "uint32" + }, + { + "internalType": "bytes32", + "name": "sender", + "type": "bytes32" + }, + { + "internalType": "uint32", + "name": "finalityThresholdExecuted", + "type": "uint32" + }, + { + "internalType": "bytes", + "name": "messageBody", + "type": "bytes" + } + ], + "name": "handleReceiveFinalizedMessage", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "sourceDomain", + "type": "uint32" + }, + { + "internalType": "bytes32", + "name": "sender", + "type": "bytes32" + }, + { + "internalType": "uint32", + "name": "finalityThresholdExecuted", + "type": "uint32" + }, + { + "internalType": "bytes", + "name": "messageBody", + "type": "bytes" + } + ], + "name": "handleReceiveUnfinalizedMessage", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "harvesterAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_operator", + "type": "address" + }, + { + "internalType": "uint16", + "name": "_minFinalityThreshold", + "type": "uint16" + }, + { + "internalType": "uint16", + "name": "_feePremiumBps", + "type": "uint16" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "isGovernor", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "nonce", + "type": "uint64" + } + ], + "name": "isNonceProcessed", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "isTransferPending", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "lastTransferNonce", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "minFinalityThreshold", + "outputs": [ + { + "internalType": "uint16", + "name": "", + "type": "uint16" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "operator", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "peerDomainID", + "outputs": [ + { + "internalType": "uint32", + "name": "", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "peerStrategy", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "peerUsdcToken", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pendingAmount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "platformAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "message", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "attestation", + "type": "bytes" + } + ], + "name": "relay", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "remoteStrategyBalance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_assetIndex", + "type": "uint256" + } + ], + "name": "removePToken", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "rewardTokenAddresses", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "safeApproveAllTokens", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint16", + "name": "_feePremiumBps", + "type": "uint16" + } + ], + "name": "setFeePremiumBps", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_harvesterAddress", + "type": "address" + } + ], + "name": "setHarvesterAddress", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint16", + "name": "_minFinalityThreshold", + "type": "uint16" + } + ], + "name": "setMinFinalityThreshold", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_operator", + "type": "address" + } + ], + "name": "setOperator", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_asset", + "type": "address" + }, + { + "internalType": "address", + "name": "_pToken", + "type": "address" + } + ], + "name": "setPTokenAddress", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "_rewardTokenAddresses", + "type": "address[]" + } + ], + "name": "setRewardTokenAddresses", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_asset", + "type": "address" + } + ], + "name": "supportsAsset", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_newGovernor", + "type": "address" + } + ], + "name": "transferGovernance", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_asset", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "transferToken", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "usdcToken", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "vaultAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_recipient", + "type": "address" + }, + { + "internalType": "address", + "name": "_asset", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "withdraw", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "withdrawAll", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "transactionHash": "0xf1d87c5d5a6f9b572c25ef322320e9be4c2657ea1dafa4b400d4ff877ee76512", + "receipt": { + "to": null, + "from": "0x58890A9cB27586E83Cb51d2d26bbE18a1a647245", + "contractAddress": "0x2567fc741C6b9EFE589F2583e4431Ac4E69D8B8d", + "transactionIndex": 73, + "gasUsed": "3780521", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xfba6ec50a3db1c11ba0d1e3b82bcf74bb7d7d5795fb6443a5c5a913cafd23b35", + "transactionHash": "0xf1d87c5d5a6f9b572c25ef322320e9be4c2657ea1dafa4b400d4ff877ee76512", + "logs": [], + "blockNumber": 24427345, + "cumulativeGasUsed": "9632394", + "status": 1, + "byzantium": true + }, + "args": [ + [ + "0x0000000000000000000000000000000000000000", + "0xE75D77B1865Ae93c7eaa3040B038D7aA7BC02F70" + ], + [ + "0x28b5a0e9c621a5badaa536219b3a228c8168cf5d", + "0x81D40F21F12A8F0E3252Bccb954D722d4c464B64", + 6, + "0xB1d624fc40824683e2bFBEfd19eB208DbBE00866", + "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913" + ] + ], + "numDeployments": 1, + "solcInputHash": "00a8ab5ddcdb46bc9891a2222be9eb31", + "metadata": "{\"compiler\":{\"version\":\"0.8.28+commit.7893614a\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"platformAddress\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"vaultAddress\",\"type\":\"address\"}],\"internalType\":\"struct InitializableAbstractStrategy.BaseStrategyConfig\",\"name\":\"_stratConfig\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"cctpTokenMessenger\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"cctpMessageTransmitter\",\"type\":\"address\"},{\"internalType\":\"uint32\",\"name\":\"peerDomainID\",\"type\":\"uint32\"},{\"internalType\":\"address\",\"name\":\"peerStrategy\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"usdcToken\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"peerUsdcToken\",\"type\":\"address\"}],\"internalType\":\"struct AbstractCCTPIntegrator.CCTPIntegrationConfig\",\"name\":\"_cctpConfig\",\"type\":\"tuple\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"nonce\",\"type\":\"uint64\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"timestamp\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"bool\",\"name\":\"isTooOld\",\"type\":\"bool\"}],\"name\":\"BalanceCheckIgnored\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint16\",\"name\":\"feePremiumBps\",\"type\":\"uint16\"}],\"name\":\"CCTPFeePremiumBpsSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint16\",\"name\":\"minFinalityThreshold\",\"type\":\"uint16\"}],\"name\":\"CCTPMinFinalityThresholdSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"_asset\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"_pToken\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"_amount\",\"type\":\"uint256\"}],\"name\":\"Deposit\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"previousGovernor\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newGovernor\",\"type\":\"address\"}],\"name\":\"GovernorshipTransferred\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"_oldHarvesterAddress\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"_newHarvesterAddress\",\"type\":\"address\"}],\"name\":\"HarvesterAddressesUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"lastTransferNonce\",\"type\":\"uint64\"}],\"name\":\"LastTransferNonceUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"destinationDomain\",\"type\":\"uint32\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"peerStrategy\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"minFinalityThreshold\",\"type\":\"uint32\"},{\"indexed\":false,\"internalType\":\"bytes\",\"name\":\"message\",\"type\":\"bytes\"}],\"name\":\"MessageTransmitted\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"nonce\",\"type\":\"uint64\"}],\"name\":\"NonceProcessed\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"operator\",\"type\":\"address\"}],\"name\":\"OperatorChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"_asset\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"_pToken\",\"type\":\"address\"}],\"name\":\"PTokenAdded\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"_asset\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"_pToken\",\"type\":\"address\"}],\"name\":\"PTokenRemoved\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"previousGovernor\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newGovernor\",\"type\":\"address\"}],\"name\":\"PendingGovernorshipTransfer\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"balance\",\"type\":\"uint256\"}],\"name\":\"RemoteStrategyBalanceUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address[]\",\"name\":\"_oldAddresses\",\"type\":\"address[]\"},{\"indexed\":false,\"internalType\":\"address[]\",\"name\":\"_newAddresses\",\"type\":\"address[]\"}],\"name\":\"RewardTokenAddressesUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"rewardToken\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"RewardTokenCollected\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"destinationDomain\",\"type\":\"uint32\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"peerStrategy\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"tokenAddress\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"tokenAmount\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"maxFee\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"minFinalityThreshold\",\"type\":\"uint32\"},{\"indexed\":false,\"internalType\":\"bytes\",\"name\":\"hookData\",\"type\":\"bytes\"}],\"name\":\"TokensBridged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[],\"name\":\"WithdrawAllSkipped\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"asset\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"WithdrawRequested\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"_asset\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"_pToken\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"_amount\",\"type\":\"uint256\"}],\"name\":\"Withdrawal\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"MAX_TRANSFER_AMOUNT\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"MIN_TRANSFER_AMOUNT\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"assetToPToken\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"cctpMessageTransmitter\",\"outputs\":[{\"internalType\":\"contract ICCTPMessageTransmitter\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"cctpTokenMessenger\",\"outputs\":[{\"internalType\":\"contract ICCTPTokenMessenger\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_asset\",\"type\":\"address\"}],\"name\":\"checkBalance\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"balance\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"claimGovernance\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"collectRewardTokens\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_asset\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_amount\",\"type\":\"uint256\"}],\"name\":\"deposit\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"depositAll\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"feePremiumBps\",\"outputs\":[{\"internalType\":\"uint16\",\"name\":\"\",\"type\":\"uint16\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getRewardTokenAddresses\",\"outputs\":[{\"internalType\":\"address[]\",\"name\":\"\",\"type\":\"address[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"governor\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"sourceDomain\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"sender\",\"type\":\"bytes32\"},{\"internalType\":\"uint32\",\"name\":\"finalityThresholdExecuted\",\"type\":\"uint32\"},{\"internalType\":\"bytes\",\"name\":\"messageBody\",\"type\":\"bytes\"}],\"name\":\"handleReceiveFinalizedMessage\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"sourceDomain\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"sender\",\"type\":\"bytes32\"},{\"internalType\":\"uint32\",\"name\":\"finalityThresholdExecuted\",\"type\":\"uint32\"},{\"internalType\":\"bytes\",\"name\":\"messageBody\",\"type\":\"bytes\"}],\"name\":\"handleReceiveUnfinalizedMessage\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"harvesterAddress\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_operator\",\"type\":\"address\"},{\"internalType\":\"uint16\",\"name\":\"_minFinalityThreshold\",\"type\":\"uint16\"},{\"internalType\":\"uint16\",\"name\":\"_feePremiumBps\",\"type\":\"uint16\"}],\"name\":\"initialize\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"isGovernor\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"nonce\",\"type\":\"uint64\"}],\"name\":\"isNonceProcessed\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"isTransferPending\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"lastTransferNonce\",\"outputs\":[{\"internalType\":\"uint64\",\"name\":\"\",\"type\":\"uint64\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"minFinalityThreshold\",\"outputs\":[{\"internalType\":\"uint16\",\"name\":\"\",\"type\":\"uint16\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"operator\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"peerDomainID\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"peerStrategy\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"peerUsdcToken\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"pendingAmount\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"platformAddress\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"message\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"attestation\",\"type\":\"bytes\"}],\"name\":\"relay\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"remoteStrategyBalance\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_assetIndex\",\"type\":\"uint256\"}],\"name\":\"removePToken\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"rewardTokenAddresses\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"safeApproveAllTokens\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint16\",\"name\":\"_feePremiumBps\",\"type\":\"uint16\"}],\"name\":\"setFeePremiumBps\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_harvesterAddress\",\"type\":\"address\"}],\"name\":\"setHarvesterAddress\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint16\",\"name\":\"_minFinalityThreshold\",\"type\":\"uint16\"}],\"name\":\"setMinFinalityThreshold\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_operator\",\"type\":\"address\"}],\"name\":\"setOperator\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_asset\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_pToken\",\"type\":\"address\"}],\"name\":\"setPTokenAddress\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"_rewardTokenAddresses\",\"type\":\"address[]\"}],\"name\":\"setRewardTokenAddresses\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_asset\",\"type\":\"address\"}],\"name\":\"supportsAsset\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_newGovernor\",\"type\":\"address\"}],\"name\":\"transferGovernance\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_asset\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_amount\",\"type\":\"uint256\"}],\"name\":\"transferToken\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"usdcToken\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"vaultAddress\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_recipient\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_asset\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_amount\",\"type\":\"uint256\"}],\"name\":\"withdraw\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"withdrawAll\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{\"checkBalance(address)\":{\"params\":{\"_asset\":\"Address of the asset to check\"},\"returns\":{\"balance\":\"Total balance of the asset\"}},\"constructor\":{\"params\":{\"_stratConfig\":\"The platform and OToken vault addresses\"}},\"deposit(address,uint256)\":{\"params\":{\"_amount\":\"Units of asset to deposit\",\"_asset\":\"Address for the asset\"}},\"getRewardTokenAddresses()\":{\"returns\":{\"_0\":\"address[] the reward token addresses.\"}},\"handleReceiveFinalizedMessage(uint32,bytes32,uint32,bytes)\":{\"details\":\"Handles a finalized CCTP message\",\"params\":{\"finalityThresholdExecuted\":\"Fidelity threshold executed\",\"messageBody\":\"Message body\",\"sender\":\"Sender of the message\",\"sourceDomain\":\"Source domain of the message\"}},\"handleReceiveUnfinalizedMessage(uint32,bytes32,uint32,bytes)\":{\"details\":\"Handles an unfinalized but safe CCTP message\",\"params\":{\"finalityThresholdExecuted\":\"Fidelity threshold executed\",\"messageBody\":\"Message body\",\"sender\":\"Sender of the message\",\"sourceDomain\":\"Source domain of the message\"}},\"initialize(address,uint16,uint16)\":{\"details\":\"Initialize the strategy implementation\",\"params\":{\"_feePremiumBps\":\"Fee premium in basis points\",\"_minFinalityThreshold\":\"Minimum finality threshold\",\"_operator\":\"Address of the operator\"}},\"isNonceProcessed(uint64)\":{\"details\":\"Checks if a given nonce is processed. Nonce starts at 1, so 0 is disregarded.\",\"params\":{\"nonce\":\"Nonce to check\"},\"returns\":{\"_0\":\"True if the nonce is processed, false otherwise\"}},\"isTransferPending()\":{\"details\":\"Checks if the last known transfer is pending. Nonce starts at 1, so 0 is disregarded.\",\"returns\":{\"_0\":\"True if a transfer is pending, false otherwise\"}},\"relay(bytes,bytes)\":{\"details\":\"Receives a message from the peer strategy on the other chain, does some basic checks and relays it to the local MessageTransmitterV2. If the message is a burn message, it will also handle the hook data and call the _onTokenReceived function.\",\"params\":{\"attestation\":\"Attestation of the message\",\"message\":\"Payload of the message to send\"}},\"removePToken(uint256)\":{\"params\":{\"_assetIndex\":\"Index of the asset to be removed\"}},\"setFeePremiumBps(uint16)\":{\"details\":\"Set the fee premium in basis points. Cannot be higher than 30% (3000 basis points).\",\"params\":{\"_feePremiumBps\":\"Fee premium in basis points\"}},\"setHarvesterAddress(address)\":{\"params\":{\"_harvesterAddress\":\"Address of the harvester contract.\"}},\"setMinFinalityThreshold(uint16)\":{\"details\":\"Set the minimum finality threshold at which the message is considered to be finalized to relay. Only accepts a value of 1000 (Safe, after 1 epoch) or 2000 (Finalized, after 2 epochs).\",\"params\":{\"_minFinalityThreshold\":\"Minimum finality threshold\"}},\"setOperator(address)\":{\"details\":\"Set the operator address\",\"params\":{\"_operator\":\"Operator address\"}},\"setPTokenAddress(address,address)\":{\"params\":{\"_asset\":\"Address for the asset\",\"_pToken\":\"Address for the corresponding platform token\"}},\"setRewardTokenAddresses(address[])\":{\"params\":{\"_rewardTokenAddresses\":\"Array of reward token addresses\"}},\"supportsAsset(address)\":{\"params\":{\"_asset\":\"Address of the asset\"},\"returns\":{\"_0\":\"bool Whether asset is supported\"}},\"transferGovernance(address)\":{\"params\":{\"_newGovernor\":\"Address of the new Governor\"}},\"transferToken(address,uint256)\":{\"params\":{\"_amount\":\"Amount of the asset to transfer\",\"_asset\":\"Address for the asset\"}},\"withdraw(address,address,uint256)\":{\"params\":{\"_amount\":\"Units of asset to withdraw\",\"_asset\":\"Address of the asset\",\"_recipient\":\"Address to which the asset should be sent\"}}},\"stateVariables\":{\"remoteStrategyBalance\":{\"details\":\"The remote balance is cached and might not reflect the actual real-time balance of the remote strategy.\"}},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{\"MAX_TRANSFER_AMOUNT()\":{\"notice\":\"Max transfer threshold imposed by the CCTP Ref: https://developers.circle.com/cctp/evm-smart-contracts#depositforburn\"},\"MIN_TRANSFER_AMOUNT()\":{\"notice\":\"Minimum transfer amount to avoid zero or dust transfers\"},\"assetToPToken(address)\":{\"notice\":\"asset => pToken (Platform Specific Token Address)\"},\"cctpMessageTransmitter()\":{\"notice\":\"CCTP message transmitter contract\"},\"cctpTokenMessenger()\":{\"notice\":\"CCTP token messenger contract\"},\"checkBalance(address)\":{\"notice\":\"Check the balance of the strategy that includes the balance of the asset on this contract, the amount of the asset being bridged, and the balance reported by the Remote strategy.\"},\"claimGovernance()\":{\"notice\":\"Claim Governance of the contract to a new account (`newGovernor`). Can only be called by the new Governor.\"},\"collectRewardTokens()\":{\"notice\":\"Collect accumulated reward token and send to Vault.\"},\"deposit(address,uint256)\":{\"notice\":\"Deposit an amount of assets into the platform\"},\"depositAll()\":{\"notice\":\"Deposit all supported assets in this strategy contract to the platform\"},\"feePremiumBps()\":{\"notice\":\"Fee premium in basis points\"},\"getRewardTokenAddresses()\":{\"notice\":\"Get the reward token addresses.\"},\"governor()\":{\"notice\":\"Returns the address of the current Governor.\"},\"harvesterAddress()\":{\"notice\":\"Address of the Harvester contract allowed to collect reward tokens\"},\"isGovernor()\":{\"notice\":\"Returns true if the caller is the current Governor.\"},\"lastTransferNonce()\":{\"notice\":\"Nonce of the last known deposit or withdrawal\"},\"minFinalityThreshold()\":{\"notice\":\"Minimum finality threshold Can be 1000 (safe, after 1 epoch) or 2000 (finalized, after 2 epochs). Ref: https://developers.circle.com/cctp/technical-guide#finality-thresholds\"},\"operator()\":{\"notice\":\"Operator address: Can relay CCTP messages\"},\"peerDomainID()\":{\"notice\":\"Domain ID of the chain from which messages are accepted\"},\"peerStrategy()\":{\"notice\":\"Strategy address on other chain\"},\"peerUsdcToken()\":{\"notice\":\"USDC address on remote chain\"},\"pendingAmount()\":{\"notice\":\"Amount that's bridged due to a pending Deposit process but with no acknowledgement from the remote strategy yet\"},\"platformAddress()\":{\"notice\":\"Address of the underlying platform\"},\"remoteStrategyBalance()\":{\"notice\":\"Remote strategy balance\"},\"removePToken(uint256)\":{\"notice\":\"Remove a supported asset by passing its index. This method can only be called by the system Governor\"},\"rewardTokenAddresses(uint256)\":{\"notice\":\"Address of the reward tokens. eg CRV, BAL, CVX, AURA\"},\"setHarvesterAddress(address)\":{\"notice\":\"Set the Harvester contract that can collect rewards.\"},\"setPTokenAddress(address,address)\":{\"notice\":\"Provide support for asset by passing its pToken address. This method can only be called by the system Governor\"},\"setRewardTokenAddresses(address[])\":{\"notice\":\"Set the reward token addresses. Any old addresses will be overwritten.\"},\"supportsAsset(address)\":{\"notice\":\"Check if an asset is supported.\"},\"transferGovernance(address)\":{\"notice\":\"Transfers Governance of the contract to a new account (`newGovernor`). Can only be called by the current Governor. Must be claimed for this to complete\"},\"transferToken(address,uint256)\":{\"notice\":\"Transfer token to governor. Intended for recovering tokens stuck in strategy contracts, i.e. mistaken sends.\"},\"usdcToken()\":{\"notice\":\"USDC address on local chain\"},\"vaultAddress()\":{\"notice\":\"Address of the OToken vault\"},\"withdraw(address,address,uint256)\":{\"notice\":\"Withdraw an `amount` of assets from the platform and send to the `_recipient`.\"},\"withdrawAll()\":{\"notice\":\"Withdraw all supported assets from platform and sends to the OToken's Vault.\"}},\"version\":1}},\"settings\":{\"compilationTarget\":{\"contracts/strategies/crosschain/CrossChainMasterStrategy.sol\":\"CrossChainMasterStrategy\"},\"evmVersion\":\"paris\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\",\"useLiteralContent\":true},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[]},\"sources\":{\"@openzeppelin/contracts/token/ERC20/IERC20.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (token/ERC20/IERC20.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev Interface of the ERC20 standard as defined in the EIP.\\n */\\ninterface IERC20 {\\n /**\\n * @dev Returns the amount of tokens in existence.\\n */\\n function totalSupply() external view returns (uint256);\\n\\n /**\\n * @dev Returns the amount of tokens owned by `account`.\\n */\\n function balanceOf(address account) external view returns (uint256);\\n\\n /**\\n * @dev Moves `amount` tokens from the caller's account to `recipient`.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * Emits a {Transfer} event.\\n */\\n function transfer(address recipient, uint256 amount) external returns (bool);\\n\\n /**\\n * @dev Returns the remaining number of tokens that `spender` will be\\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\\n * zero by default.\\n *\\n * This value changes when {approve} or {transferFrom} are called.\\n */\\n function allowance(address owner, address spender) external view returns (uint256);\\n\\n /**\\n * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\\n * that someone may use both the old and the new allowance by unfortunate\\n * transaction ordering. One possible solution to mitigate this race\\n * condition is to first reduce the spender's allowance to 0 and set the\\n * desired value afterwards:\\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\\n *\\n * Emits an {Approval} event.\\n */\\n function approve(address spender, uint256 amount) external returns (bool);\\n\\n /**\\n * @dev Moves `amount` tokens from `sender` to `recipient` using the\\n * allowance mechanism. `amount` is then deducted from the caller's\\n * allowance.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * Emits a {Transfer} event.\\n */\\n function transferFrom(\\n address sender,\\n address recipient,\\n uint256 amount\\n ) external returns (bool);\\n\\n /**\\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\\n * another (`to`).\\n *\\n * Note that `value` may be zero.\\n */\\n event Transfer(address indexed from, address indexed to, uint256 value);\\n\\n /**\\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\\n * a call to {approve}. `value` is the new allowance.\\n */\\n event Approval(address indexed owner, address indexed spender, uint256 value);\\n}\\n\",\"keccak256\":\"0x61437cb513a887a1bbad006e7b1c8b414478427d33de47c5600af3c748f108da\",\"license\":\"MIT\"},\"@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"../IERC20.sol\\\";\\n\\n/**\\n * @dev Interface for the optional metadata functions from the ERC20 standard.\\n *\\n * _Available since v4.1._\\n */\\ninterface IERC20Metadata is IERC20 {\\n /**\\n * @dev Returns the name of the token.\\n */\\n function name() external view returns (string memory);\\n\\n /**\\n * @dev Returns the symbol of the token.\\n */\\n function symbol() external view returns (string memory);\\n\\n /**\\n * @dev Returns the decimals places of the token.\\n */\\n function decimals() external view returns (uint8);\\n}\\n\",\"keccak256\":\"0x8de418a5503946cabe331f35fe242d3201a73f67f77aaeb7110acb1f30423aca\",\"license\":\"MIT\"},\"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (token/ERC20/utils/SafeERC20.sol)\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"../IERC20.sol\\\";\\nimport \\\"../../../utils/Address.sol\\\";\\n\\n/**\\n * @title SafeERC20\\n * @dev Wrappers around ERC20 operations that throw on failure (when the token\\n * contract returns false). Tokens that return no value (and instead revert or\\n * throw on failure) are also supported, non-reverting calls are assumed to be\\n * successful.\\n * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,\\n * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.\\n */\\nlibrary SafeERC20 {\\n using Address for address;\\n\\n function safeTransfer(\\n IERC20 token,\\n address to,\\n uint256 value\\n ) internal {\\n _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));\\n }\\n\\n function safeTransferFrom(\\n IERC20 token,\\n address from,\\n address to,\\n uint256 value\\n ) internal {\\n _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));\\n }\\n\\n /**\\n * @dev Deprecated. This function has issues similar to the ones found in\\n * {IERC20-approve}, and its usage is discouraged.\\n *\\n * Whenever possible, use {safeIncreaseAllowance} and\\n * {safeDecreaseAllowance} instead.\\n */\\n function safeApprove(\\n IERC20 token,\\n address spender,\\n uint256 value\\n ) internal {\\n // safeApprove should only be called when setting an initial allowance,\\n // or when resetting it to zero. To increase and decrease it, use\\n // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'\\n require(\\n (value == 0) || (token.allowance(address(this), spender) == 0),\\n \\\"SafeERC20: approve from non-zero to non-zero allowance\\\"\\n );\\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));\\n }\\n\\n function safeIncreaseAllowance(\\n IERC20 token,\\n address spender,\\n uint256 value\\n ) internal {\\n uint256 newAllowance = token.allowance(address(this), spender) + value;\\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));\\n }\\n\\n function safeDecreaseAllowance(\\n IERC20 token,\\n address spender,\\n uint256 value\\n ) internal {\\n unchecked {\\n uint256 oldAllowance = token.allowance(address(this), spender);\\n require(oldAllowance >= value, \\\"SafeERC20: decreased allowance below zero\\\");\\n uint256 newAllowance = oldAllowance - value;\\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));\\n }\\n }\\n\\n /**\\n * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement\\n * on the return value: the return value is optional (but if data is returned, it must not be false).\\n * @param token The token targeted by the call.\\n * @param data The call data (encoded using abi.encode or one of its variants).\\n */\\n function _callOptionalReturn(IERC20 token, bytes memory data) private {\\n // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since\\n // we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that\\n // the target address contains contract code and also asserts for success in the low-level call.\\n\\n bytes memory returndata = address(token).functionCall(data, \\\"SafeERC20: low-level call failed\\\");\\n if (returndata.length > 0) {\\n // Return data is optional\\n require(abi.decode(returndata, (bool)), \\\"SafeERC20: ERC20 operation did not succeed\\\");\\n }\\n }\\n}\\n\",\"keccak256\":\"0xc3d946432c0ddbb1f846a0d3985be71299df331b91d06732152117f62f0be2b5\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/Address.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (utils/Address.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev Collection of functions related to the address type\\n */\\nlibrary Address {\\n /**\\n * @dev Returns true if `account` is a contract.\\n *\\n * [IMPORTANT]\\n * ====\\n * It is unsafe to assume that an address for which this function returns\\n * false is an externally-owned account (EOA) and not a contract.\\n *\\n * Among others, `isContract` will return false for the following\\n * types of addresses:\\n *\\n * - an externally-owned account\\n * - a contract in construction\\n * - an address where a contract will be created\\n * - an address where a contract lived, but was destroyed\\n * ====\\n */\\n function isContract(address account) internal view returns (bool) {\\n // This method relies on extcodesize, which returns 0 for contracts in\\n // construction, since the code is only stored at the end of the\\n // constructor execution.\\n\\n uint256 size;\\n assembly {\\n size := extcodesize(account)\\n }\\n return size > 0;\\n }\\n\\n /**\\n * @dev Replacement for Solidity's `transfer`: sends `amount` wei to\\n * `recipient`, forwarding all available gas and reverting on errors.\\n *\\n * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost\\n * of certain opcodes, possibly making contracts go over the 2300 gas limit\\n * imposed by `transfer`, making them unable to receive funds via\\n * `transfer`. {sendValue} removes this limitation.\\n *\\n * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].\\n *\\n * IMPORTANT: because control is transferred to `recipient`, care must be\\n * taken to not create reentrancy vulnerabilities. Consider using\\n * {ReentrancyGuard} or the\\n * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].\\n */\\n function sendValue(address payable recipient, uint256 amount) internal {\\n require(address(this).balance >= amount, \\\"Address: insufficient balance\\\");\\n\\n (bool success, ) = recipient.call{value: amount}(\\\"\\\");\\n require(success, \\\"Address: unable to send value, recipient may have reverted\\\");\\n }\\n\\n /**\\n * @dev Performs a Solidity function call using a low level `call`. A\\n * plain `call` is an unsafe replacement for a function call: use this\\n * function instead.\\n *\\n * If `target` reverts with a revert reason, it is bubbled up by this\\n * function (like regular Solidity function calls).\\n *\\n * Returns the raw returned data. To convert to the expected return value,\\n * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].\\n *\\n * Requirements:\\n *\\n * - `target` must be a contract.\\n * - calling `target` with `data` must not revert.\\n *\\n * _Available since v3.1._\\n */\\n function functionCall(address target, bytes memory data) internal returns (bytes memory) {\\n return functionCall(target, data, \\\"Address: low-level call failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with\\n * `errorMessage` as a fallback revert reason when `target` reverts.\\n *\\n * _Available since v3.1._\\n */\\n function functionCall(\\n address target,\\n bytes memory data,\\n string memory errorMessage\\n ) internal returns (bytes memory) {\\n return functionCallWithValue(target, data, 0, errorMessage);\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\\n * but also transferring `value` wei to `target`.\\n *\\n * Requirements:\\n *\\n * - the calling contract must have an ETH balance of at least `value`.\\n * - the called Solidity function must be `payable`.\\n *\\n * _Available since v3.1._\\n */\\n function functionCallWithValue(\\n address target,\\n bytes memory data,\\n uint256 value\\n ) internal returns (bytes memory) {\\n return functionCallWithValue(target, data, value, \\\"Address: low-level call with value failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but\\n * with `errorMessage` as a fallback revert reason when `target` reverts.\\n *\\n * _Available since v3.1._\\n */\\n function functionCallWithValue(\\n address target,\\n bytes memory data,\\n uint256 value,\\n string memory errorMessage\\n ) internal returns (bytes memory) {\\n require(address(this).balance >= value, \\\"Address: insufficient balance for call\\\");\\n require(isContract(target), \\\"Address: call to non-contract\\\");\\n\\n (bool success, bytes memory returndata) = target.call{value: value}(data);\\n return verifyCallResult(success, returndata, errorMessage);\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\\n * but performing a static call.\\n *\\n * _Available since v3.3._\\n */\\n function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {\\n return functionStaticCall(target, data, \\\"Address: low-level static call failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\\n * but performing a static call.\\n *\\n * _Available since v3.3._\\n */\\n function functionStaticCall(\\n address target,\\n bytes memory data,\\n string memory errorMessage\\n ) internal view returns (bytes memory) {\\n require(isContract(target), \\\"Address: static call to non-contract\\\");\\n\\n (bool success, bytes memory returndata) = target.staticcall(data);\\n return verifyCallResult(success, returndata, errorMessage);\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\\n * but performing a delegate call.\\n *\\n * _Available since v3.4._\\n */\\n function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {\\n return functionDelegateCall(target, data, \\\"Address: low-level delegate call failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\\n * but performing a delegate call.\\n *\\n * _Available since v3.4._\\n */\\n function functionDelegateCall(\\n address target,\\n bytes memory data,\\n string memory errorMessage\\n ) internal returns (bytes memory) {\\n require(isContract(target), \\\"Address: delegate call to non-contract\\\");\\n\\n (bool success, bytes memory returndata) = target.delegatecall(data);\\n return verifyCallResult(success, returndata, errorMessage);\\n }\\n\\n /**\\n * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the\\n * revert reason using the provided one.\\n *\\n * _Available since v4.3._\\n */\\n function verifyCallResult(\\n bool success,\\n bytes memory returndata,\\n string memory errorMessage\\n ) internal pure returns (bytes memory) {\\n if (success) {\\n return returndata;\\n } else {\\n // Look for revert reason and bubble it up if present\\n if (returndata.length > 0) {\\n // The easiest way to bubble the revert reason is using memory via assembly\\n\\n assembly {\\n let returndata_size := mload(returndata)\\n revert(add(32, returndata), returndata_size)\\n }\\n } else {\\n revert(errorMessage);\\n }\\n }\\n }\\n}\\n\",\"keccak256\":\"0x51b758a8815ecc9596c66c37d56b1d33883a444631a3f916b9fe65cb863ef7c4\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/math/SafeCast.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (utils/math/SafeCast.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev Wrappers over Solidity's uintXX/intXX casting operators with added overflow\\n * checks.\\n *\\n * Downcasting from uint256/int256 in Solidity does not revert on overflow. This can\\n * easily result in undesired exploitation or bugs, since developers usually\\n * assume that overflows raise errors. `SafeCast` restores this intuition by\\n * reverting the transaction when such an operation overflows.\\n *\\n * Using this library instead of the unchecked operations eliminates an entire\\n * class of bugs, so it's recommended to use it always.\\n *\\n * Can be combined with {SafeMath} and {SignedSafeMath} to extend it to smaller types, by performing\\n * all math on `uint256` and `int256` and then downcasting.\\n */\\nlibrary SafeCast {\\n /**\\n * @dev Returns the downcasted uint224 from uint256, reverting on\\n * overflow (when the input is greater than largest uint224).\\n *\\n * Counterpart to Solidity's `uint224` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 224 bits\\n */\\n function toUint224(uint256 value) internal pure returns (uint224) {\\n require(value <= type(uint224).max, \\\"SafeCast: value doesn't fit in 224 bits\\\");\\n return uint224(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted uint128 from uint256, reverting on\\n * overflow (when the input is greater than largest uint128).\\n *\\n * Counterpart to Solidity's `uint128` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 128 bits\\n */\\n function toUint128(uint256 value) internal pure returns (uint128) {\\n require(value <= type(uint128).max, \\\"SafeCast: value doesn't fit in 128 bits\\\");\\n return uint128(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted uint96 from uint256, reverting on\\n * overflow (when the input is greater than largest uint96).\\n *\\n * Counterpart to Solidity's `uint96` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 96 bits\\n */\\n function toUint96(uint256 value) internal pure returns (uint96) {\\n require(value <= type(uint96).max, \\\"SafeCast: value doesn't fit in 96 bits\\\");\\n return uint96(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted uint64 from uint256, reverting on\\n * overflow (when the input is greater than largest uint64).\\n *\\n * Counterpart to Solidity's `uint64` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 64 bits\\n */\\n function toUint64(uint256 value) internal pure returns (uint64) {\\n require(value <= type(uint64).max, \\\"SafeCast: value doesn't fit in 64 bits\\\");\\n return uint64(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted uint32 from uint256, reverting on\\n * overflow (when the input is greater than largest uint32).\\n *\\n * Counterpart to Solidity's `uint32` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 32 bits\\n */\\n function toUint32(uint256 value) internal pure returns (uint32) {\\n require(value <= type(uint32).max, \\\"SafeCast: value doesn't fit in 32 bits\\\");\\n return uint32(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted uint16 from uint256, reverting on\\n * overflow (when the input is greater than largest uint16).\\n *\\n * Counterpart to Solidity's `uint16` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 16 bits\\n */\\n function toUint16(uint256 value) internal pure returns (uint16) {\\n require(value <= type(uint16).max, \\\"SafeCast: value doesn't fit in 16 bits\\\");\\n return uint16(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted uint8 from uint256, reverting on\\n * overflow (when the input is greater than largest uint8).\\n *\\n * Counterpart to Solidity's `uint8` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 8 bits.\\n */\\n function toUint8(uint256 value) internal pure returns (uint8) {\\n require(value <= type(uint8).max, \\\"SafeCast: value doesn't fit in 8 bits\\\");\\n return uint8(value);\\n }\\n\\n /**\\n * @dev Converts a signed int256 into an unsigned uint256.\\n *\\n * Requirements:\\n *\\n * - input must be greater than or equal to 0.\\n */\\n function toUint256(int256 value) internal pure returns (uint256) {\\n require(value >= 0, \\\"SafeCast: value must be positive\\\");\\n return uint256(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted int128 from int256, reverting on\\n * overflow (when the input is less than smallest int128 or\\n * greater than largest int128).\\n *\\n * Counterpart to Solidity's `int128` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 128 bits\\n *\\n * _Available since v3.1._\\n */\\n function toInt128(int256 value) internal pure returns (int128) {\\n require(value >= type(int128).min && value <= type(int128).max, \\\"SafeCast: value doesn't fit in 128 bits\\\");\\n return int128(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted int64 from int256, reverting on\\n * overflow (when the input is less than smallest int64 or\\n * greater than largest int64).\\n *\\n * Counterpart to Solidity's `int64` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 64 bits\\n *\\n * _Available since v3.1._\\n */\\n function toInt64(int256 value) internal pure returns (int64) {\\n require(value >= type(int64).min && value <= type(int64).max, \\\"SafeCast: value doesn't fit in 64 bits\\\");\\n return int64(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted int32 from int256, reverting on\\n * overflow (when the input is less than smallest int32 or\\n * greater than largest int32).\\n *\\n * Counterpart to Solidity's `int32` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 32 bits\\n *\\n * _Available since v3.1._\\n */\\n function toInt32(int256 value) internal pure returns (int32) {\\n require(value >= type(int32).min && value <= type(int32).max, \\\"SafeCast: value doesn't fit in 32 bits\\\");\\n return int32(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted int16 from int256, reverting on\\n * overflow (when the input is less than smallest int16 or\\n * greater than largest int16).\\n *\\n * Counterpart to Solidity's `int16` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 16 bits\\n *\\n * _Available since v3.1._\\n */\\n function toInt16(int256 value) internal pure returns (int16) {\\n require(value >= type(int16).min && value <= type(int16).max, \\\"SafeCast: value doesn't fit in 16 bits\\\");\\n return int16(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted int8 from int256, reverting on\\n * overflow (when the input is less than smallest int8 or\\n * greater than largest int8).\\n *\\n * Counterpart to Solidity's `int8` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 8 bits.\\n *\\n * _Available since v3.1._\\n */\\n function toInt8(int256 value) internal pure returns (int8) {\\n require(value >= type(int8).min && value <= type(int8).max, \\\"SafeCast: value doesn't fit in 8 bits\\\");\\n return int8(value);\\n }\\n\\n /**\\n * @dev Converts an unsigned uint256 into a signed int256.\\n *\\n * Requirements:\\n *\\n * - input must be less than or equal to maxInt256.\\n */\\n function toInt256(uint256 value) internal pure returns (int256) {\\n // Note: Unsafe cast below is okay because `type(int256).max` is guaranteed to be positive\\n require(value <= uint256(type(int256).max), \\\"SafeCast: value doesn't fit in an int256\\\");\\n return int256(value);\\n }\\n}\\n\",\"keccak256\":\"0x5c6caab697d302ad7eb59c234a4d2dbc965c1bae87709bd2850060b7695b28c7\",\"license\":\"MIT\"},\"contracts/governance/Governable.sol\":{\"content\":\"// SPDX-License-Identifier: BUSL-1.1\\npragma solidity ^0.8.0;\\n\\n/**\\n * @title Base for contracts that are managed by the Origin Protocol's Governor.\\n * @dev Copy of the openzeppelin Ownable.sol contract with nomenclature change\\n * from owner to governor and renounce methods removed. Does not use\\n * Context.sol like Ownable.sol does for simplification.\\n * @author Origin Protocol Inc\\n */\\nabstract contract Governable {\\n // Storage position of the owner and pendingOwner of the contract\\n // keccak256(\\\"OUSD.governor\\\");\\n bytes32 private constant governorPosition =\\n 0x7bea13895fa79d2831e0a9e28edede30099005a50d652d8957cf8a607ee6ca4a;\\n\\n // keccak256(\\\"OUSD.pending.governor\\\");\\n bytes32 private constant pendingGovernorPosition =\\n 0x44c4d30b2eaad5130ad70c3ba6972730566f3e6359ab83e800d905c61b1c51db;\\n\\n // keccak256(\\\"OUSD.reentry.status\\\");\\n bytes32 private constant reentryStatusPosition =\\n 0x53bf423e48ed90e97d02ab0ebab13b2a235a6bfbe9c321847d5c175333ac4535;\\n\\n // See OpenZeppelin ReentrancyGuard implementation\\n uint256 constant _NOT_ENTERED = 1;\\n uint256 constant _ENTERED = 2;\\n\\n event PendingGovernorshipTransfer(\\n address indexed previousGovernor,\\n address indexed newGovernor\\n );\\n\\n event GovernorshipTransferred(\\n address indexed previousGovernor,\\n address indexed newGovernor\\n );\\n\\n /**\\n * @notice Returns the address of the current Governor.\\n */\\n function governor() public view returns (address) {\\n return _governor();\\n }\\n\\n /**\\n * @dev Returns the address of the current Governor.\\n */\\n function _governor() internal view returns (address governorOut) {\\n bytes32 position = governorPosition;\\n // solhint-disable-next-line no-inline-assembly\\n assembly {\\n governorOut := sload(position)\\n }\\n }\\n\\n /**\\n * @dev Returns the address of the pending Governor.\\n */\\n function _pendingGovernor()\\n internal\\n view\\n returns (address pendingGovernor)\\n {\\n bytes32 position = pendingGovernorPosition;\\n // solhint-disable-next-line no-inline-assembly\\n assembly {\\n pendingGovernor := sload(position)\\n }\\n }\\n\\n /**\\n * @dev Throws if called by any account other than the Governor.\\n */\\n modifier onlyGovernor() {\\n require(isGovernor(), \\\"Caller is not the Governor\\\");\\n _;\\n }\\n\\n /**\\n * @notice Returns true if the caller is the current Governor.\\n */\\n function isGovernor() public view returns (bool) {\\n return msg.sender == _governor();\\n }\\n\\n function _setGovernor(address newGovernor) internal {\\n emit GovernorshipTransferred(_governor(), newGovernor);\\n\\n bytes32 position = governorPosition;\\n // solhint-disable-next-line no-inline-assembly\\n assembly {\\n sstore(position, newGovernor)\\n }\\n }\\n\\n /**\\n * @dev Prevents a contract from calling itself, directly or indirectly.\\n * Calling a `nonReentrant` function from another `nonReentrant`\\n * function is not supported. It is possible to prevent this from happening\\n * by making the `nonReentrant` function external, and make it call a\\n * `private` function that does the actual work.\\n */\\n modifier nonReentrant() {\\n bytes32 position = reentryStatusPosition;\\n uint256 _reentry_status;\\n // solhint-disable-next-line no-inline-assembly\\n assembly {\\n _reentry_status := sload(position)\\n }\\n\\n // On the first call to nonReentrant, _notEntered will be true\\n require(_reentry_status != _ENTERED, \\\"Reentrant call\\\");\\n\\n // Any calls to nonReentrant after this point will fail\\n // solhint-disable-next-line no-inline-assembly\\n assembly {\\n sstore(position, _ENTERED)\\n }\\n\\n _;\\n\\n // By storing the original value once again, a refund is triggered (see\\n // https://eips.ethereum.org/EIPS/eip-2200)\\n // solhint-disable-next-line no-inline-assembly\\n assembly {\\n sstore(position, _NOT_ENTERED)\\n }\\n }\\n\\n function _setPendingGovernor(address newGovernor) internal {\\n bytes32 position = pendingGovernorPosition;\\n // solhint-disable-next-line no-inline-assembly\\n assembly {\\n sstore(position, newGovernor)\\n }\\n }\\n\\n /**\\n * @notice Transfers Governance of the contract to a new account (`newGovernor`).\\n * Can only be called by the current Governor. Must be claimed for this to complete\\n * @param _newGovernor Address of the new Governor\\n */\\n function transferGovernance(address _newGovernor) external onlyGovernor {\\n _setPendingGovernor(_newGovernor);\\n emit PendingGovernorshipTransfer(_governor(), _newGovernor);\\n }\\n\\n /**\\n * @notice Claim Governance of the contract to a new account (`newGovernor`).\\n * Can only be called by the new Governor.\\n */\\n function claimGovernance() external {\\n require(\\n msg.sender == _pendingGovernor(),\\n \\\"Only the pending Governor can complete the claim\\\"\\n );\\n _changeGovernor(msg.sender);\\n }\\n\\n /**\\n * @dev Change Governance of the contract to a new account (`newGovernor`).\\n * @param _newGovernor Address of the new Governor\\n */\\n function _changeGovernor(address _newGovernor) internal {\\n require(_newGovernor != address(0), \\\"New Governor is address(0)\\\");\\n _setGovernor(_newGovernor);\\n }\\n}\\n\",\"keccak256\":\"0xf32f873c8bfbacf2e5f01d0cf37bc7f54fbd5aa656e95c8a599114229946f107\",\"license\":\"BUSL-1.1\"},\"contracts/interfaces/IBasicToken.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.0;\\n\\ninterface IBasicToken {\\n function symbol() external view returns (string memory);\\n\\n function decimals() external view returns (uint8);\\n}\\n\",\"keccak256\":\"0xa562062698aa12572123b36dfd2072f1a39e44fed2031cc19c2c9fd522f96ec2\",\"license\":\"MIT\"},\"contracts/interfaces/IStrategy.sol\":{\"content\":\"// SPDX-License-Identifier: BUSL-1.1\\npragma solidity ^0.8.0;\\n\\n/**\\n * @title Platform interface to integrate with lending platform like Compound, AAVE etc.\\n */\\ninterface IStrategy {\\n /**\\n * @dev Deposit the given asset to platform\\n * @param _asset asset address\\n * @param _amount Amount to deposit\\n */\\n function deposit(address _asset, uint256 _amount) external;\\n\\n /**\\n * @dev Deposit the entire balance of all supported assets in the Strategy\\n * to the platform\\n */\\n function depositAll() external;\\n\\n /**\\n * @dev Withdraw given asset from Lending platform\\n */\\n function withdraw(\\n address _recipient,\\n address _asset,\\n uint256 _amount\\n ) external;\\n\\n /**\\n * @dev Liquidate all assets in strategy and return them to Vault.\\n */\\n function withdrawAll() external;\\n\\n /**\\n * @dev Returns the current balance of the given asset.\\n */\\n function checkBalance(address _asset)\\n external\\n view\\n returns (uint256 balance);\\n\\n /**\\n * @dev Returns bool indicating whether strategy supports asset.\\n */\\n function supportsAsset(address _asset) external view returns (bool);\\n\\n /**\\n * @dev Collect reward tokens from the Strategy.\\n */\\n function collectRewardTokens() external;\\n\\n /**\\n * @dev The address array of the reward tokens for the Strategy.\\n */\\n function getRewardTokenAddresses() external view returns (address[] memory);\\n\\n function harvesterAddress() external view returns (address);\\n\\n function transferToken(address token, uint256 amount) external;\\n\\n function setRewardTokenAddresses(address[] calldata _rewardTokenAddresses)\\n external;\\n}\\n\",\"keccak256\":\"0x79ca47defb3b5a56bba13f14c440838152fd1c1aa640476154516a16da4da8ba\",\"license\":\"BUSL-1.1\"},\"contracts/interfaces/IVault.sol\":{\"content\":\"// SPDX-License-Identifier: BUSL-1.1\\npragma solidity ^0.8.0;\\n\\nimport { VaultStorage } from \\\"../vault/VaultStorage.sol\\\";\\n\\ninterface IVault {\\n // slither-disable-start constable-states\\n\\n event AssetAllocated(address _asset, address _strategy, uint256 _amount);\\n event StrategyApproved(address _addr);\\n event StrategyRemoved(address _addr);\\n event Mint(address _addr, uint256 _value);\\n event Redeem(address _addr, uint256 _value);\\n event CapitalPaused();\\n event CapitalUnpaused();\\n event DefaultStrategyUpdated(address _strategy);\\n event RebasePaused();\\n event RebaseUnpaused();\\n event VaultBufferUpdated(uint256 _vaultBuffer);\\n event AllocateThresholdUpdated(uint256 _threshold);\\n event RebaseThresholdUpdated(uint256 _threshold);\\n event StrategistUpdated(address _address);\\n event MaxSupplyDiffChanged(uint256 maxSupplyDiff);\\n event YieldDistribution(address _to, uint256 _yield, uint256 _fee);\\n event TrusteeFeeBpsChanged(uint256 _basis);\\n event TrusteeAddressChanged(address _address);\\n event StrategyAddedToMintWhitelist(address indexed strategy);\\n event StrategyRemovedFromMintWhitelist(address indexed strategy);\\n event RebasePerSecondMaxChanged(uint256 rebaseRatePerSecond);\\n event DripDurationChanged(uint256 dripDuration);\\n event WithdrawalRequested(\\n address indexed _withdrawer,\\n uint256 indexed _requestId,\\n uint256 _amount,\\n uint256 _queued\\n );\\n event WithdrawalClaimed(\\n address indexed _withdrawer,\\n uint256 indexed _requestId,\\n uint256 _amount\\n );\\n event WithdrawalClaimable(uint256 _claimable, uint256 _newClaimable);\\n event WithdrawalClaimDelayUpdated(uint256 _newDelay);\\n\\n // Governable.sol\\n function transferGovernance(address _newGovernor) external;\\n\\n function claimGovernance() external;\\n\\n function governor() external view returns (address);\\n\\n // VaultAdmin.sol\\n function setVaultBuffer(uint256 _vaultBuffer) external;\\n\\n function vaultBuffer() external view returns (uint256);\\n\\n function setAutoAllocateThreshold(uint256 _threshold) external;\\n\\n function autoAllocateThreshold() external view returns (uint256);\\n\\n function setRebaseThreshold(uint256 _threshold) external;\\n\\n function rebaseThreshold() external view returns (uint256);\\n\\n function setStrategistAddr(address _address) external;\\n\\n function strategistAddr() external view returns (address);\\n\\n function setMaxSupplyDiff(uint256 _maxSupplyDiff) external;\\n\\n function maxSupplyDiff() external view returns (uint256);\\n\\n function setTrusteeAddress(address _address) external;\\n\\n function trusteeAddress() external view returns (address);\\n\\n function setTrusteeFeeBps(uint256 _basis) external;\\n\\n function trusteeFeeBps() external view returns (uint256);\\n\\n function approveStrategy(address _addr) external;\\n\\n function removeStrategy(address _addr) external;\\n\\n function setDefaultStrategy(address _strategy) external;\\n\\n function defaultStrategy() external view returns (address);\\n\\n function pauseRebase() external;\\n\\n function unpauseRebase() external;\\n\\n function rebasePaused() external view returns (bool);\\n\\n function pauseCapital() external;\\n\\n function unpauseCapital() external;\\n\\n function capitalPaused() external view returns (bool);\\n\\n function transferToken(address _asset, uint256 _amount) external;\\n\\n function withdrawAllFromStrategy(address _strategyAddr) external;\\n\\n function withdrawAllFromStrategies() external;\\n\\n function withdrawFromStrategy(\\n address _strategyFromAddress,\\n address[] calldata _assets,\\n uint256[] calldata _amounts\\n ) external;\\n\\n function depositToStrategy(\\n address _strategyToAddress,\\n address[] calldata _assets,\\n uint256[] calldata _amounts\\n ) external;\\n\\n // VaultCore.sol\\n function mint(\\n address _asset,\\n uint256 _amount,\\n uint256 _minimumOusdAmount\\n ) external;\\n\\n function mintForStrategy(uint256 _amount) external;\\n\\n function redeem(uint256 _amount, uint256 _minimumUnitAmount) external;\\n\\n function burnForStrategy(uint256 _amount) external;\\n\\n function allocate() external;\\n\\n function rebase() external;\\n\\n function totalValue() external view returns (uint256 value);\\n\\n function checkBalance(address _asset) external view returns (uint256);\\n\\n /// @notice Deprecated: use calculateRedeemOutput\\n function calculateRedeemOutputs(uint256 _amount)\\n external\\n view\\n returns (uint256[] memory);\\n\\n function calculateRedeemOutput(uint256 _amount)\\n external\\n view\\n returns (uint256);\\n\\n function getAssetCount() external view returns (uint256);\\n\\n function getAllAssets() external view returns (address[] memory);\\n\\n function getStrategyCount() external view returns (uint256);\\n\\n function getAllStrategies() external view returns (address[] memory);\\n\\n /// @notice Deprecated.\\n function isSupportedAsset(address _asset) external view returns (bool);\\n\\n function dripper() external view returns (address);\\n\\n function asset() external view returns (address);\\n\\n function initialize(address) external;\\n\\n function addWithdrawalQueueLiquidity() external;\\n\\n function requestWithdrawal(uint256 _amount)\\n external\\n returns (uint256 requestId, uint256 queued);\\n\\n function claimWithdrawal(uint256 requestId)\\n external\\n returns (uint256 amount);\\n\\n function claimWithdrawals(uint256[] memory requestIds)\\n external\\n returns (uint256[] memory amounts, uint256 totalAmount);\\n\\n function withdrawalQueueMetadata()\\n external\\n view\\n returns (VaultStorage.WithdrawalQueueMetadata memory);\\n\\n function withdrawalRequests(uint256 requestId)\\n external\\n view\\n returns (VaultStorage.WithdrawalRequest memory);\\n\\n function addStrategyToMintWhitelist(address strategyAddr) external;\\n\\n function removeStrategyFromMintWhitelist(address strategyAddr) external;\\n\\n function isMintWhitelistedStrategy(address strategyAddr)\\n external\\n view\\n returns (bool);\\n\\n function withdrawalClaimDelay() external view returns (uint256);\\n\\n function setWithdrawalClaimDelay(uint256 newDelay) external;\\n\\n function lastRebase() external view returns (uint64);\\n\\n function dripDuration() external view returns (uint64);\\n\\n function setDripDuration(uint256 _dripDuration) external;\\n\\n function rebasePerSecondMax() external view returns (uint64);\\n\\n function setRebaseRateMax(uint256 yearlyApr) external;\\n\\n function rebasePerSecondTarget() external view returns (uint64);\\n\\n function previewYield() external view returns (uint256 yield);\\n\\n function weth() external view returns (address);\\n\\n // slither-disable-end constable-states\\n}\\n\",\"keccak256\":\"0x61e2ad6f41abac69275ba86e51d9d3cd95dfd6fc6b81960cf75b1e06adb6251d\",\"license\":\"BUSL-1.1\"},\"contracts/interfaces/cctp/ICCTP.sol\":{\"content\":\"// SPDX-License-Identifier: BUSL-1.1\\npragma solidity ^0.8.0;\\n\\ninterface ICCTPTokenMessenger {\\n function depositForBurn(\\n uint256 amount,\\n uint32 destinationDomain,\\n bytes32 mintRecipient,\\n address burnToken,\\n bytes32 destinationCaller,\\n uint256 maxFee,\\n uint32 minFinalityThreshold\\n ) external;\\n\\n function depositForBurnWithHook(\\n uint256 amount,\\n uint32 destinationDomain,\\n bytes32 mintRecipient,\\n address burnToken,\\n bytes32 destinationCaller,\\n uint256 maxFee,\\n uint32 minFinalityThreshold,\\n bytes memory hookData\\n ) external;\\n\\n function getMinFeeAmount(uint256 amount) external view returns (uint256);\\n}\\n\\ninterface ICCTPMessageTransmitter {\\n function sendMessage(\\n uint32 destinationDomain,\\n bytes32 recipient,\\n bytes32 destinationCaller,\\n uint32 minFinalityThreshold,\\n bytes memory messageBody\\n ) external;\\n\\n function receiveMessage(bytes calldata message, bytes calldata attestation)\\n external\\n returns (bool);\\n}\\n\\ninterface IMessageHandlerV2 {\\n /**\\n * @notice Handles an incoming finalized message from an IReceiverV2\\n * @dev Finalized messages have finality threshold values greater than or equal to 2000\\n * @param sourceDomain The source domain of the message\\n * @param sender The sender of the message\\n * @param finalityThresholdExecuted the finality threshold at which the message was attested to\\n * @param messageBody The raw bytes of the message body\\n * @return success True, if successful; false, if not.\\n */\\n function handleReceiveFinalizedMessage(\\n uint32 sourceDomain,\\n bytes32 sender,\\n uint32 finalityThresholdExecuted,\\n bytes calldata messageBody\\n ) external returns (bool);\\n\\n /**\\n * @notice Handles an incoming unfinalized message from an IReceiverV2\\n * @dev Unfinalized messages have finality threshold values less than 2000\\n * @param sourceDomain The source domain of the message\\n * @param sender The sender of the message\\n * @param finalityThresholdExecuted The finality threshold at which the message was attested to\\n * @param messageBody The raw bytes of the message body\\n * @return success True, if successful; false, if not.\\n */\\n function handleReceiveUnfinalizedMessage(\\n uint32 sourceDomain,\\n bytes32 sender,\\n uint32 finalityThresholdExecuted,\\n bytes calldata messageBody\\n ) external returns (bool);\\n}\\n\",\"keccak256\":\"0x67c60863105b4a694bc6edc77777560120262fd4f883a5bbece53f0c70d88e7b\",\"license\":\"BUSL-1.1\"},\"contracts/strategies/crosschain/AbstractCCTPIntegrator.sol\":{\"content\":\"// SPDX-License-Identifier: BUSL-1.1\\npragma solidity ^0.8.0;\\n\\n/**\\n * @title AbstractCCTPIntegrator\\n * @author Origin Protocol Inc\\n *\\n * @dev Abstract contract that contains all the logic used to integrate with CCTP.\\n */\\n\\nimport { SafeERC20 } from \\\"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\\\";\\nimport { IERC20 } from \\\"../../utils/InitializableAbstractStrategy.sol\\\";\\n\\nimport { ICCTPTokenMessenger, ICCTPMessageTransmitter, IMessageHandlerV2 } from \\\"../../interfaces/cctp/ICCTP.sol\\\";\\n\\nimport { CrossChainStrategyHelper } from \\\"./CrossChainStrategyHelper.sol\\\";\\nimport { Governable } from \\\"../../governance/Governable.sol\\\";\\nimport { BytesHelper } from \\\"../../utils/BytesHelper.sol\\\";\\nimport \\\"../../utils/Helpers.sol\\\";\\n\\nabstract contract AbstractCCTPIntegrator is Governable, IMessageHandlerV2 {\\n using SafeERC20 for IERC20;\\n\\n using BytesHelper for bytes;\\n using CrossChainStrategyHelper for bytes;\\n\\n event LastTransferNonceUpdated(uint64 lastTransferNonce);\\n event NonceProcessed(uint64 nonce);\\n\\n event CCTPMinFinalityThresholdSet(uint16 minFinalityThreshold);\\n event CCTPFeePremiumBpsSet(uint16 feePremiumBps);\\n event OperatorChanged(address operator);\\n event TokensBridged(\\n uint32 destinationDomain,\\n address peerStrategy,\\n address tokenAddress,\\n uint256 tokenAmount,\\n uint256 maxFee,\\n uint32 minFinalityThreshold,\\n bytes hookData\\n );\\n event MessageTransmitted(\\n uint32 destinationDomain,\\n address peerStrategy,\\n uint32 minFinalityThreshold,\\n bytes message\\n );\\n\\n // Message body V2 fields\\n // Ref: https://developers.circle.com/cctp/technical-guide#message-body\\n // Ref: https://github.com/circlefin/evm-cctp-contracts/blob/master/src/messages/v2/BurnMessageV2.sol\\n uint8 private constant BURN_MESSAGE_V2_VERSION_INDEX = 0;\\n uint8 private constant BURN_MESSAGE_V2_BURN_TOKEN_INDEX = 4;\\n uint8 private constant BURN_MESSAGE_V2_RECIPIENT_INDEX = 36;\\n uint8 private constant BURN_MESSAGE_V2_AMOUNT_INDEX = 68;\\n uint8 private constant BURN_MESSAGE_V2_MESSAGE_SENDER_INDEX = 100;\\n uint8 private constant BURN_MESSAGE_V2_FEE_EXECUTED_INDEX = 164;\\n uint8 private constant BURN_MESSAGE_V2_HOOK_DATA_INDEX = 228;\\n\\n /**\\n * @notice Max transfer threshold imposed by the CCTP\\n * Ref: https://developers.circle.com/cctp/evm-smart-contracts#depositforburn\\n * @dev 10M USDC limit applies to both standard and fast transfer modes. The fast transfer mode has\\n * an additional limitation that is not present on-chain and Circle may alter that amount off-chain\\n * at their preference. The amount available for fast transfer can be queried here:\\n * https://iris-api.circle.com/v2/fastBurn/USDC/allowance .\\n * If a fast transfer token transaction has been issued and there is not enough allowance for it\\n * the off-chain Iris component will re-attempt the transaction and if it fails it will fallback\\n * to a standard transfer. Reference section 4.3 in the whitepaper:\\n * https://6778953.fs1.hubspotusercontent-na1.net/hubfs/6778953/PDFs/Whitepapers/CCTPV2_White_Paper.pdf\\n */\\n uint256 public constant MAX_TRANSFER_AMOUNT = 10_000_000 * 10**6; // 10M USDC\\n\\n /// @notice Minimum transfer amount to avoid zero or dust transfers\\n uint256 public constant MIN_TRANSFER_AMOUNT = 10**6;\\n\\n // CCTP contracts\\n // This implementation assumes that remote and local chains have these contracts\\n // deployed on the same addresses.\\n /// @notice CCTP message transmitter contract\\n ICCTPMessageTransmitter public immutable cctpMessageTransmitter;\\n /// @notice CCTP token messenger contract\\n ICCTPTokenMessenger public immutable cctpTokenMessenger;\\n\\n /// @notice USDC address on local chain\\n address public immutable usdcToken;\\n\\n /// @notice USDC address on remote chain\\n address public immutable peerUsdcToken;\\n\\n /// @notice Domain ID of the chain from which messages are accepted\\n uint32 public immutable peerDomainID;\\n\\n /// @notice Strategy address on other chain\\n address public immutable peerStrategy;\\n\\n /**\\n * @notice Minimum finality threshold\\n * Can be 1000 (safe, after 1 epoch) or 2000 (finalized, after 2 epochs).\\n * Ref: https://developers.circle.com/cctp/technical-guide#finality-thresholds\\n * @dev When configuring the contract for fast transfer we should check the available\\n * allowance of USDC that can be bridged using fast mode:\\n * wget https://iris-api.circle.com/v2/fastBurn/USDC/allowance\\n */\\n uint16 public minFinalityThreshold;\\n\\n /// @notice Fee premium in basis points\\n uint16 public feePremiumBps;\\n\\n /// @notice Nonce of the last known deposit or withdrawal\\n uint64 public lastTransferNonce;\\n\\n /// @notice Operator address: Can relay CCTP messages\\n address public operator;\\n\\n /// @notice Mapping of processed nonces\\n mapping(uint64 => bool) private nonceProcessed;\\n\\n // For future use\\n uint256[48] private __gap;\\n\\n modifier onlyCCTPMessageTransmitter() {\\n require(\\n msg.sender == address(cctpMessageTransmitter),\\n \\\"Caller is not CCTP transmitter\\\"\\n );\\n _;\\n }\\n\\n modifier onlyOperator() {\\n require(msg.sender == operator, \\\"Caller is not the Operator\\\");\\n _;\\n }\\n\\n /**\\n * @notice Configuration for CCTP integration\\n * @param cctpTokenMessenger Address of the CCTP token messenger contract\\n * @param cctpMessageTransmitter Address of the CCTP message transmitter contract\\n * @param peerDomainID Domain ID of the chain from which messages are accepted.\\n * 0 for Ethereum, 6 for Base, etc.\\n * Ref: https://developers.circle.com/cctp/cctp-supported-blockchains\\n * @param peerStrategy Address of the master or remote strategy on the other chain\\n * @param usdcToken USDC address on local chain\\n */\\n struct CCTPIntegrationConfig {\\n address cctpTokenMessenger;\\n address cctpMessageTransmitter;\\n uint32 peerDomainID;\\n address peerStrategy;\\n address usdcToken;\\n address peerUsdcToken;\\n }\\n\\n constructor(CCTPIntegrationConfig memory _config) {\\n require(_config.usdcToken != address(0), \\\"Invalid USDC address\\\");\\n require(\\n _config.peerUsdcToken != address(0),\\n \\\"Invalid peer USDC address\\\"\\n );\\n require(\\n _config.cctpTokenMessenger != address(0),\\n \\\"Invalid CCTP config\\\"\\n );\\n require(\\n _config.cctpMessageTransmitter != address(0),\\n \\\"Invalid CCTP config\\\"\\n );\\n require(\\n _config.peerStrategy != address(0),\\n \\\"Invalid peer strategy address\\\"\\n );\\n\\n cctpMessageTransmitter = ICCTPMessageTransmitter(\\n _config.cctpMessageTransmitter\\n );\\n cctpTokenMessenger = ICCTPTokenMessenger(_config.cctpTokenMessenger);\\n\\n // Domain ID of the chain from which messages are accepted\\n peerDomainID = _config.peerDomainID;\\n\\n // Strategy address on other chain, should\\n // always be same as the proxy of this strategy\\n peerStrategy = _config.peerStrategy;\\n\\n // USDC address on local chain\\n usdcToken = _config.usdcToken;\\n\\n // Just a sanity check to ensure the base token is USDC\\n uint256 _usdcTokenDecimals = Helpers.getDecimals(_config.usdcToken);\\n string memory _usdcTokenSymbol = Helpers.getSymbol(_config.usdcToken);\\n require(_usdcTokenDecimals == 6, \\\"Base token decimals must be 6\\\");\\n require(\\n keccak256(abi.encodePacked(_usdcTokenSymbol)) ==\\n keccak256(abi.encodePacked(\\\"USDC\\\")),\\n \\\"Token symbol must be USDC\\\"\\n );\\n\\n // USDC address on remote chain\\n peerUsdcToken = _config.peerUsdcToken;\\n }\\n\\n /**\\n * @dev Initialize the implementation contract\\n * @param _operator Operator address\\n * @param _minFinalityThreshold Minimum finality threshold\\n * @param _feePremiumBps Fee premium in basis points\\n */\\n function _initialize(\\n address _operator,\\n uint16 _minFinalityThreshold,\\n uint16 _feePremiumBps\\n ) internal {\\n _setOperator(_operator);\\n _setMinFinalityThreshold(_minFinalityThreshold);\\n _setFeePremiumBps(_feePremiumBps);\\n\\n // Nonce starts at 1, so assume nonce 0 as processed.\\n // NOTE: This will cause the deposit/withdraw to fail if the\\n // strategy is not initialized properly (which is expected).\\n nonceProcessed[0] = true;\\n }\\n\\n /***************************************\\n Settings\\n ****************************************/\\n /**\\n * @dev Set the operator address\\n * @param _operator Operator address\\n */\\n function setOperator(address _operator) external onlyGovernor {\\n _setOperator(_operator);\\n }\\n\\n /**\\n * @dev Set the operator address\\n * @param _operator Operator address\\n */\\n function _setOperator(address _operator) internal {\\n operator = _operator;\\n emit OperatorChanged(_operator);\\n }\\n\\n /**\\n * @dev Set the minimum finality threshold at which\\n * the message is considered to be finalized to relay.\\n * Only accepts a value of 1000 (Safe, after 1 epoch) or\\n * 2000 (Finalized, after 2 epochs).\\n * @param _minFinalityThreshold Minimum finality threshold\\n */\\n function setMinFinalityThreshold(uint16 _minFinalityThreshold)\\n external\\n onlyGovernor\\n {\\n _setMinFinalityThreshold(_minFinalityThreshold);\\n }\\n\\n /**\\n * @dev Set the minimum finality threshold\\n * @param _minFinalityThreshold Minimum finality threshold\\n */\\n function _setMinFinalityThreshold(uint16 _minFinalityThreshold) internal {\\n // 1000 for fast transfer and 2000 for standard transfer\\n require(\\n _minFinalityThreshold == 1000 || _minFinalityThreshold == 2000,\\n \\\"Invalid threshold\\\"\\n );\\n\\n minFinalityThreshold = _minFinalityThreshold;\\n emit CCTPMinFinalityThresholdSet(_minFinalityThreshold);\\n }\\n\\n /**\\n * @dev Set the fee premium in basis points.\\n * Cannot be higher than 30% (3000 basis points).\\n * @param _feePremiumBps Fee premium in basis points\\n */\\n function setFeePremiumBps(uint16 _feePremiumBps) external onlyGovernor {\\n _setFeePremiumBps(_feePremiumBps);\\n }\\n\\n /**\\n * @dev Set the fee premium in basis points\\n * Cannot be higher than 30% (3000 basis points).\\n * Ref: https://developers.circle.com/cctp/technical-guide#fees\\n * @param _feePremiumBps Fee premium in basis points\\n */\\n function _setFeePremiumBps(uint16 _feePremiumBps) internal {\\n require(_feePremiumBps <= 3000, \\\"Fee premium too high\\\"); // 30%\\n\\n feePremiumBps = _feePremiumBps;\\n emit CCTPFeePremiumBpsSet(_feePremiumBps);\\n }\\n\\n /***************************************\\n CCTP message handling\\n ****************************************/\\n\\n /**\\n * @dev Handles a finalized CCTP message\\n * @param sourceDomain Source domain of the message\\n * @param sender Sender of the message\\n * @param finalityThresholdExecuted Fidelity threshold executed\\n * @param messageBody Message body\\n */\\n function handleReceiveFinalizedMessage(\\n uint32 sourceDomain,\\n bytes32 sender,\\n uint32 finalityThresholdExecuted,\\n bytes memory messageBody\\n ) external override onlyCCTPMessageTransmitter returns (bool) {\\n // Make sure the finality threshold at execution is at least 2000\\n require(\\n finalityThresholdExecuted >= 2000,\\n \\\"Finality threshold too low\\\"\\n );\\n\\n return _handleReceivedMessage(sourceDomain, sender, messageBody);\\n }\\n\\n /**\\n * @dev Handles an unfinalized but safe CCTP message\\n * @param sourceDomain Source domain of the message\\n * @param sender Sender of the message\\n * @param finalityThresholdExecuted Fidelity threshold executed\\n * @param messageBody Message body\\n */\\n function handleReceiveUnfinalizedMessage(\\n uint32 sourceDomain,\\n bytes32 sender,\\n uint32 finalityThresholdExecuted,\\n bytes memory messageBody\\n ) external override onlyCCTPMessageTransmitter returns (bool) {\\n // Make sure the contract is configured to handle unfinalized messages\\n require(\\n minFinalityThreshold == 1000,\\n \\\"Unfinalized messages are not supported\\\"\\n );\\n // Make sure the finality threshold at execution is at least 1000\\n require(\\n finalityThresholdExecuted >= 1000,\\n \\\"Finality threshold too low\\\"\\n );\\n\\n return _handleReceivedMessage(sourceDomain, sender, messageBody);\\n }\\n\\n /**\\n * @dev Handles a CCTP message\\n * @param sourceDomain Source domain of the message\\n * @param sender Sender of the message\\n * @param messageBody Message body\\n */\\n function _handleReceivedMessage(\\n uint32 sourceDomain,\\n bytes32 sender,\\n bytes memory messageBody\\n ) internal returns (bool) {\\n require(sourceDomain == peerDomainID, \\\"Unknown Source Domain\\\");\\n\\n // Extract address from bytes32 (CCTP stores addresses as right-padded bytes32)\\n address senderAddress = address(uint160(uint256(sender)));\\n require(senderAddress == peerStrategy, \\\"Unknown Sender\\\");\\n\\n _onMessageReceived(messageBody);\\n\\n return true;\\n }\\n\\n /**\\n * @dev Sends tokens to the peer strategy using CCTP Token Messenger\\n * @param tokenAmount Amount of tokens to send\\n * @param hookData Hook data\\n */\\n function _sendTokens(uint256 tokenAmount, bytes memory hookData)\\n internal\\n virtual\\n {\\n // CCTP has a maximum transfer amount of 10M USDC per tx\\n require(tokenAmount <= MAX_TRANSFER_AMOUNT, \\\"Token amount too high\\\");\\n\\n // Approve only what needs to be transferred\\n IERC20(usdcToken).safeApprove(address(cctpTokenMessenger), tokenAmount);\\n\\n // Compute the max fee to be paid.\\n // Ref: https://developers.circle.com/cctp/evm-smart-contracts#getminfeeamount\\n // The right way to compute fees would be to use CCTP's getMinFeeAmount function.\\n // The issue is that the getMinFeeAmount is not present on v2.0 contracts, but is on\\n // v2.1. Some of CCTP's deployed contracts are v2.0, some are v2.1.\\n // We will only be using standard transfers and fee on those is 0 for now. If they\\n // ever start implementing fee for standard transfers or if we decide to use fast\\n // trasnfer, we can use feePremiumBps as a workaround.\\n uint256 maxFee = feePremiumBps > 0\\n ? (tokenAmount * feePremiumBps) / 10000\\n : 0;\\n\\n // Send tokens to the peer strategy using CCTP Token Messenger\\n cctpTokenMessenger.depositForBurnWithHook(\\n tokenAmount,\\n peerDomainID,\\n bytes32(uint256(uint160(peerStrategy))),\\n address(usdcToken),\\n bytes32(uint256(uint160(peerStrategy))),\\n maxFee,\\n uint32(minFinalityThreshold),\\n hookData\\n );\\n\\n emit TokensBridged(\\n peerDomainID,\\n peerStrategy,\\n usdcToken,\\n tokenAmount,\\n maxFee,\\n uint32(minFinalityThreshold),\\n hookData\\n );\\n }\\n\\n /**\\n * @dev Sends a message to the peer strategy using CCTP Message Transmitter\\n * @param message Payload of the message to send\\n */\\n function _sendMessage(bytes memory message) internal virtual {\\n cctpMessageTransmitter.sendMessage(\\n peerDomainID,\\n bytes32(uint256(uint160(peerStrategy))),\\n bytes32(uint256(uint160(peerStrategy))),\\n uint32(minFinalityThreshold),\\n message\\n );\\n\\n emit MessageTransmitted(\\n peerDomainID,\\n peerStrategy,\\n uint32(minFinalityThreshold),\\n message\\n );\\n }\\n\\n /**\\n * @dev Receives a message from the peer strategy on the other chain,\\n * does some basic checks and relays it to the local MessageTransmitterV2.\\n * If the message is a burn message, it will also handle the hook data\\n * and call the _onTokenReceived function.\\n * @param message Payload of the message to send\\n * @param attestation Attestation of the message\\n */\\n function relay(bytes memory message, bytes memory attestation)\\n external\\n onlyOperator\\n {\\n (\\n uint32 version,\\n uint32 sourceDomainID,\\n address sender,\\n address recipient,\\n bytes memory messageBody\\n ) = message.decodeMessageHeader();\\n\\n // Ensure that it's a CCTP message\\n require(\\n version == CrossChainStrategyHelper.CCTP_MESSAGE_VERSION,\\n \\\"Invalid CCTP message version\\\"\\n );\\n\\n // Ensure that the source domain is the peer domain\\n require(sourceDomainID == peerDomainID, \\\"Unknown Source Domain\\\");\\n\\n // Ensure message body version\\n version = messageBody.extractUint32(BURN_MESSAGE_V2_VERSION_INDEX);\\n\\n // NOTE: There's a possibility that the CCTP Token Messenger might\\n // send other types of messages in future, not just the burn message.\\n // If it ever comes to that, this shouldn't cause us any problems\\n // because it has to still go through the followign checks:\\n // - version check\\n // - message body length check\\n // - sender and recipient (which should be in the same slots and same as address(this))\\n // - hook data handling (which will revert even if all the above checks pass)\\n bool isBurnMessageV1 = sender == address(cctpTokenMessenger);\\n\\n if (isBurnMessageV1) {\\n // Handle burn message\\n require(\\n version == 1 &&\\n messageBody.length >= BURN_MESSAGE_V2_HOOK_DATA_INDEX,\\n \\\"Invalid burn message\\\"\\n );\\n\\n // Ensure the burn token is USDC\\n address burnToken = messageBody.extractAddress(\\n BURN_MESSAGE_V2_BURN_TOKEN_INDEX\\n );\\n require(burnToken == peerUsdcToken, \\\"Invalid burn token\\\");\\n\\n // Address of caller of depositForBurn (or depositForBurnWithCaller) on source domain\\n sender = messageBody.extractAddress(\\n BURN_MESSAGE_V2_MESSAGE_SENDER_INDEX\\n );\\n\\n recipient = messageBody.extractAddress(\\n BURN_MESSAGE_V2_RECIPIENT_INDEX\\n );\\n } else {\\n // We handle only Burn message or our custom messagee\\n require(\\n version == CrossChainStrategyHelper.ORIGIN_MESSAGE_VERSION,\\n \\\"Unsupported message version\\\"\\n );\\n }\\n\\n // Ensure the recipient is this contract\\n // Both sender and recipient should be deployed to same address on both chains.\\n require(address(this) == recipient, \\\"Unexpected recipient address\\\");\\n require(sender == peerStrategy, \\\"Incorrect sender/recipient address\\\");\\n\\n // Relay the message\\n // This step also mints USDC and transfers it to the recipient wallet\\n bool relaySuccess = cctpMessageTransmitter.receiveMessage(\\n message,\\n attestation\\n );\\n require(relaySuccess, \\\"Receive message failed\\\");\\n\\n if (isBurnMessageV1) {\\n // Extract the hook data from the message body\\n bytes memory hookData = messageBody.extractSlice(\\n BURN_MESSAGE_V2_HOOK_DATA_INDEX,\\n messageBody.length\\n );\\n\\n // Extract the token amount from the message body\\n uint256 tokenAmount = messageBody.extractUint256(\\n BURN_MESSAGE_V2_AMOUNT_INDEX\\n );\\n\\n // Extract the fee executed from the message body\\n uint256 feeExecuted = messageBody.extractUint256(\\n BURN_MESSAGE_V2_FEE_EXECUTED_INDEX\\n );\\n\\n // Call the _onTokenReceived function\\n _onTokenReceived(tokenAmount - feeExecuted, feeExecuted, hookData);\\n }\\n }\\n\\n /***************************************\\n Message utils\\n ****************************************/\\n\\n /***************************************\\n Nonce Handling\\n ****************************************/\\n /**\\n * @dev Checks if the last known transfer is pending.\\n * Nonce starts at 1, so 0 is disregarded.\\n * @return True if a transfer is pending, false otherwise\\n */\\n function isTransferPending() public view returns (bool) {\\n return !nonceProcessed[lastTransferNonce];\\n }\\n\\n /**\\n * @dev Checks if a given nonce is processed.\\n * Nonce starts at 1, so 0 is disregarded.\\n * @param nonce Nonce to check\\n * @return True if the nonce is processed, false otherwise\\n */\\n function isNonceProcessed(uint64 nonce) public view returns (bool) {\\n return nonceProcessed[nonce];\\n }\\n\\n /**\\n * @dev Marks a given nonce as processed.\\n * Can only mark nonce as processed once. New nonce should\\n * always be greater than the last known nonce. Also updates\\n * the last known nonce.\\n * @param nonce Nonce to mark as processed\\n */\\n function _markNonceAsProcessed(uint64 nonce) internal {\\n uint64 lastNonce = lastTransferNonce;\\n\\n // Can only mark latest nonce as processed\\n // Master strategy when receiving a message from the remote strategy\\n // will have lastNone == nonce, as the nonce is increase at the start\\n // of deposit / withdrawal flow.\\n // Remote strategy will have lastNonce < nonce, as a new nonce initiated\\n // from master will be greater than the last one.\\n require(nonce >= lastNonce, \\\"Nonce too low\\\");\\n // Can only mark nonce as processed once\\n require(!nonceProcessed[nonce], \\\"Nonce already processed\\\");\\n\\n nonceProcessed[nonce] = true;\\n emit NonceProcessed(nonce);\\n\\n if (nonce != lastNonce) {\\n // Update last known nonce\\n lastTransferNonce = nonce;\\n emit LastTransferNonceUpdated(nonce);\\n }\\n }\\n\\n /**\\n * @dev Gets the next nonce to use.\\n * Nonce starts at 1, so 0 is disregarded.\\n * Reverts if last nonce hasn't been processed yet.\\n * @return Next nonce\\n */\\n function _getNextNonce() internal returns (uint64) {\\n uint64 nonce = lastTransferNonce;\\n\\n require(nonceProcessed[nonce], \\\"Pending token transfer\\\");\\n\\n nonce = nonce + 1;\\n lastTransferNonce = nonce;\\n emit LastTransferNonceUpdated(nonce);\\n\\n return nonce;\\n }\\n\\n /***************************************\\n Inheritence overrides\\n ****************************************/\\n\\n /**\\n * @dev Called when the USDC is received from the CCTP\\n * @param tokenAmount The actual amount of USDC received (amount sent - fee executed)\\n * @param feeExecuted The fee executed\\n * @param payload The payload of the message (hook data)\\n */\\n function _onTokenReceived(\\n uint256 tokenAmount,\\n uint256 feeExecuted,\\n bytes memory payload\\n ) internal virtual;\\n\\n /**\\n * @dev Called when the message is received\\n * @param payload The payload of the message\\n */\\n function _onMessageReceived(bytes memory payload) internal virtual;\\n}\\n\",\"keccak256\":\"0x25f8398d7225b832a4d633e7478743272de704293210a2edf13c156bd1c6a9f0\",\"license\":\"BUSL-1.1\"},\"contracts/strategies/crosschain/CrossChainMasterStrategy.sol\":{\"content\":\"// SPDX-License-Identifier: BUSL-1.1\\npragma solidity ^0.8.0;\\n\\n/**\\n * @title OUSD Yearn V3 Master Strategy - the Mainnet part\\n * @author Origin Protocol Inc\\n *\\n * @dev This strategy can only perform 1 deposit or withdrawal at a time. For that\\n * reason it shouldn't be configured as an asset default strategy.\\n */\\n\\nimport { SafeERC20 } from \\\"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\\\";\\nimport { IERC20, InitializableAbstractStrategy } from \\\"../../utils/InitializableAbstractStrategy.sol\\\";\\nimport { AbstractCCTPIntegrator } from \\\"./AbstractCCTPIntegrator.sol\\\";\\nimport { CrossChainStrategyHelper } from \\\"./CrossChainStrategyHelper.sol\\\";\\n\\ncontract CrossChainMasterStrategy is\\n AbstractCCTPIntegrator,\\n InitializableAbstractStrategy\\n{\\n using SafeERC20 for IERC20;\\n using CrossChainStrategyHelper for bytes;\\n\\n /**\\n * @notice Remote strategy balance\\n * @dev The remote balance is cached and might not reflect the actual\\n * real-time balance of the remote strategy.\\n */\\n uint256 public remoteStrategyBalance;\\n\\n /// @notice Amount that's bridged due to a pending Deposit process\\n /// but with no acknowledgement from the remote strategy yet\\n uint256 public pendingAmount;\\n\\n uint256 internal constant MAX_BALANCE_CHECK_AGE = 1 days;\\n\\n event RemoteStrategyBalanceUpdated(uint256 balance);\\n event WithdrawRequested(address indexed asset, uint256 amount);\\n event WithdrawAllSkipped();\\n event BalanceCheckIgnored(uint64 nonce, uint256 timestamp, bool isTooOld);\\n\\n /**\\n * @param _stratConfig The platform and OToken vault addresses\\n */\\n constructor(\\n BaseStrategyConfig memory _stratConfig,\\n CCTPIntegrationConfig memory _cctpConfig\\n )\\n InitializableAbstractStrategy(_stratConfig)\\n AbstractCCTPIntegrator(_cctpConfig)\\n {\\n require(\\n _stratConfig.platformAddress == address(0),\\n \\\"Invalid platform address\\\"\\n );\\n require(\\n _stratConfig.vaultAddress != address(0),\\n \\\"Invalid Vault address\\\"\\n );\\n }\\n\\n /**\\n * @dev Initialize the strategy implementation\\n * @param _operator Address of the operator\\n * @param _minFinalityThreshold Minimum finality threshold\\n * @param _feePremiumBps Fee premium in basis points\\n */\\n function initialize(\\n address _operator,\\n uint16 _minFinalityThreshold,\\n uint16 _feePremiumBps\\n ) external virtual onlyGovernor initializer {\\n _initialize(_operator, _minFinalityThreshold, _feePremiumBps);\\n\\n address[] memory rewardTokens = new address[](0);\\n address[] memory assets = new address[](0);\\n address[] memory pTokens = new address[](0);\\n\\n InitializableAbstractStrategy._initialize(\\n rewardTokens,\\n assets,\\n pTokens\\n );\\n }\\n\\n /// @inheritdoc InitializableAbstractStrategy\\n function deposit(address _asset, uint256 _amount)\\n external\\n override\\n onlyVault\\n nonReentrant\\n {\\n _deposit(_asset, _amount);\\n }\\n\\n /// @inheritdoc InitializableAbstractStrategy\\n function depositAll() external override onlyVault nonReentrant {\\n uint256 balance = IERC20(usdcToken).balanceOf(address(this));\\n // Deposit if balance is greater than 1 USDC\\n if (balance >= MIN_TRANSFER_AMOUNT) {\\n _deposit(usdcToken, balance);\\n }\\n }\\n\\n /// @inheritdoc InitializableAbstractStrategy\\n function withdraw(\\n address _recipient,\\n address _asset,\\n uint256 _amount\\n ) external override onlyVault nonReentrant {\\n require(_recipient == vaultAddress, \\\"Only Vault can withdraw\\\");\\n _withdraw(_asset, _amount);\\n }\\n\\n /// @inheritdoc InitializableAbstractStrategy\\n function withdrawAll() external override onlyVaultOrGovernor nonReentrant {\\n if (isTransferPending()) {\\n // Do nothing if there is a pending transfer\\n // Note: We never want withdrawAll to fail, so\\n // emit an event to indicate that the withdrawal was skipped\\n emit WithdrawAllSkipped();\\n return;\\n }\\n\\n // Withdraw everything in Remote strategy\\n uint256 _remoteBalance = remoteStrategyBalance;\\n if (_remoteBalance < MIN_TRANSFER_AMOUNT) {\\n // Do nothing if there is less than 1 USDC in the Remote strategy\\n return;\\n }\\n\\n _withdraw(\\n usdcToken,\\n _remoteBalance > MAX_TRANSFER_AMOUNT\\n ? MAX_TRANSFER_AMOUNT\\n : _remoteBalance\\n );\\n }\\n\\n /**\\n * @notice Check the balance of the strategy that includes\\n * the balance of the asset on this contract,\\n * the amount of the asset being bridged,\\n * and the balance reported by the Remote strategy.\\n * @param _asset Address of the asset to check\\n * @return balance Total balance of the asset\\n */\\n function checkBalance(address _asset)\\n public\\n view\\n override\\n returns (uint256 balance)\\n {\\n require(_asset == usdcToken, \\\"Unsupported asset\\\");\\n\\n // USDC balance on this contract\\n // + USDC being bridged\\n // + USDC cached in the corresponding Remote part of this contract\\n return\\n IERC20(usdcToken).balanceOf(address(this)) +\\n pendingAmount +\\n remoteStrategyBalance;\\n }\\n\\n /// @inheritdoc InitializableAbstractStrategy\\n function supportsAsset(address _asset) public view override returns (bool) {\\n return _asset == usdcToken;\\n }\\n\\n /// @inheritdoc InitializableAbstractStrategy\\n function safeApproveAllTokens()\\n external\\n override\\n onlyGovernor\\n nonReentrant\\n {}\\n\\n /// @inheritdoc InitializableAbstractStrategy\\n function _abstractSetPToken(address, address) internal override {}\\n\\n /// @inheritdoc InitializableAbstractStrategy\\n function collectRewardTokens()\\n external\\n override\\n onlyHarvester\\n nonReentrant\\n {}\\n\\n /// @inheritdoc AbstractCCTPIntegrator\\n function _onMessageReceived(bytes memory payload) internal override {\\n if (\\n payload.getMessageType() ==\\n CrossChainStrategyHelper.BALANCE_CHECK_MESSAGE\\n ) {\\n // Received when Remote strategy checks the balance\\n _processBalanceCheckMessage(payload);\\n return;\\n }\\n\\n revert(\\\"Unknown message type\\\");\\n }\\n\\n /// @inheritdoc AbstractCCTPIntegrator\\n function _onTokenReceived(\\n uint256 tokenAmount,\\n // solhint-disable-next-line no-unused-vars\\n uint256 feeExecuted,\\n bytes memory payload\\n ) internal override {\\n uint64 _nonce = lastTransferNonce;\\n\\n // Should be expecting an acknowledgement\\n require(!isNonceProcessed(_nonce), \\\"Nonce already processed\\\");\\n\\n // Now relay to the regular flow\\n // NOTE: Calling _onMessageReceived would mean that we are bypassing a\\n // few checks that the regular flow does (like sourceDomainID check\\n // and sender check in `handleReceiveFinalizedMessage`). However,\\n // CCTPMessageRelayer relays the message first (which will go through\\n // all the checks) and not update balance and then finally calls this\\n // `_onTokenReceived` which will update the balance.\\n // So, if any of the checks fail during the first no-balance-update flow,\\n // this won't happen either, since the tx would revert.\\n _onMessageReceived(payload);\\n\\n // Send any tokens in the contract to the Vault\\n uint256 usdcBalance = IERC20(usdcToken).balanceOf(address(this));\\n // Should always have enough tokens\\n require(usdcBalance >= tokenAmount, \\\"Insufficient balance\\\");\\n // Transfer all tokens to the Vault to not leave any dust\\n IERC20(usdcToken).safeTransfer(vaultAddress, usdcBalance);\\n\\n // Emit withdrawal amount\\n emit Withdrawal(usdcToken, usdcToken, usdcBalance);\\n }\\n\\n /**\\n * @dev Bridge and deposit asset into the remote strategy\\n * @param _asset Address of the asset to deposit\\n * @param depositAmount Amount of the asset to deposit\\n */\\n function _deposit(address _asset, uint256 depositAmount) internal virtual {\\n require(_asset == usdcToken, \\\"Unsupported asset\\\");\\n require(pendingAmount == 0, \\\"Unexpected pending amount\\\");\\n // Deposit at least 1 USDC\\n require(\\n depositAmount >= MIN_TRANSFER_AMOUNT,\\n \\\"Deposit amount too small\\\"\\n );\\n require(\\n depositAmount <= MAX_TRANSFER_AMOUNT,\\n \\\"Deposit amount too high\\\"\\n );\\n\\n // Get the next nonce\\n // Note: reverts if a transfer is pending\\n uint64 nonce = _getNextNonce();\\n\\n // Set pending amount\\n pendingAmount = depositAmount;\\n\\n // Build deposit message payload\\n bytes memory message = CrossChainStrategyHelper.encodeDepositMessage(\\n nonce,\\n depositAmount\\n );\\n\\n // Send deposit message to the remote strategy\\n _sendTokens(depositAmount, message);\\n\\n // Emit deposit event\\n emit Deposit(_asset, _asset, depositAmount);\\n }\\n\\n /**\\n * @dev Send a withdraw request to the remote strategy\\n * @param _asset Address of the asset to withdraw\\n * @param _amount Amount of the asset to withdraw\\n */\\n function _withdraw(address _asset, uint256 _amount) internal virtual {\\n require(_asset == usdcToken, \\\"Unsupported asset\\\");\\n // Withdraw at least 1 USDC\\n require(_amount >= MIN_TRANSFER_AMOUNT, \\\"Withdraw amount too small\\\");\\n require(\\n _amount <= remoteStrategyBalance,\\n \\\"Withdraw amount exceeds remote strategy balance\\\"\\n );\\n require(\\n _amount <= MAX_TRANSFER_AMOUNT,\\n \\\"Withdraw amount exceeds max transfer amount\\\"\\n );\\n\\n // Get the next nonce\\n // Note: reverts if a transfer is pending\\n uint64 nonce = _getNextNonce();\\n\\n // Build and send withdrawal message with payload\\n bytes memory message = CrossChainStrategyHelper.encodeWithdrawMessage(\\n nonce,\\n _amount\\n );\\n _sendMessage(message);\\n\\n // Emit WithdrawRequested event here,\\n // Withdraw will be emitted in _onTokenReceived\\n emit WithdrawRequested(usdcToken, _amount);\\n }\\n\\n /**\\n * @dev Process balance check:\\n * - Confirms a deposit to the remote strategy\\n * - Skips balance update if there's a pending withdrawal\\n * - Updates the remote strategy balance\\n * @param message The message containing the nonce and balance\\n */\\n function _processBalanceCheckMessage(bytes memory message)\\n internal\\n virtual\\n {\\n // Decode the message\\n // When transferConfirmation is true, it means that the message is a result of a deposit or a withdrawal\\n // process.\\n (\\n uint64 nonce,\\n uint256 balance,\\n bool transferConfirmation,\\n uint256 timestamp\\n ) = message.decodeBalanceCheckMessage();\\n // Get the last cached nonce\\n uint64 _lastCachedNonce = lastTransferNonce;\\n\\n if (nonce != _lastCachedNonce) {\\n // If nonce is not the last cached nonce, it is an outdated message\\n // Ignore it\\n return;\\n }\\n\\n // A received message nonce not yet processed indicates there is a\\n // deposit or withdrawal in progress.\\n bool transferInProgress = isTransferPending();\\n\\n if (transferInProgress) {\\n if (transferConfirmation) {\\n // Apply the effects of the deposit / withdrawal completion\\n _markNonceAsProcessed(nonce);\\n pendingAmount = 0;\\n } else {\\n // A balanceCheck arrived that is not part of the deposit / withdrawal process\\n // that has been generated on the Remote contract after the deposit / withdrawal which is\\n // still pending. This can happen when the CCTP bridge delivers the messages out of order.\\n // Ignore it, since the pending deposit / withdrawal must first be cofirmed.\\n emit BalanceCheckIgnored(nonce, timestamp, false);\\n return;\\n }\\n } else {\\n if (block.timestamp > timestamp + MAX_BALANCE_CHECK_AGE) {\\n // Balance check is too old, ignore it\\n emit BalanceCheckIgnored(nonce, timestamp, true);\\n return;\\n }\\n }\\n\\n // At this point update the strategy balance the balanceCheck message is either:\\n // - a confirmation of a deposit / withdrawal\\n // - a message that updates balances when no deposit / withdrawal is in progress\\n remoteStrategyBalance = balance;\\n emit RemoteStrategyBalanceUpdated(balance);\\n }\\n}\\n\",\"keccak256\":\"0xee4e604391eb6fafd765e1f4f4c9d41dbf81bfd06985ab123f53aedc004652f2\",\"license\":\"BUSL-1.1\"},\"contracts/strategies/crosschain/CrossChainStrategyHelper.sol\":{\"content\":\"// SPDX-License-Identifier: BUSL-1.1\\npragma solidity ^0.8.0;\\n\\n/**\\n * @title CrossChainStrategyHelper\\n * @author Origin Protocol Inc\\n * @dev This library is used to encode and decode the messages for the cross-chain strategy.\\n * It is used to ensure that the messages are valid and to get the message version and type.\\n */\\n\\nimport { BytesHelper } from \\\"../../utils/BytesHelper.sol\\\";\\n\\nlibrary CrossChainStrategyHelper {\\n using BytesHelper for bytes;\\n\\n uint32 public constant DEPOSIT_MESSAGE = 1;\\n uint32 public constant WITHDRAW_MESSAGE = 2;\\n uint32 public constant BALANCE_CHECK_MESSAGE = 3;\\n\\n uint32 public constant CCTP_MESSAGE_VERSION = 1;\\n uint32 public constant ORIGIN_MESSAGE_VERSION = 1010;\\n\\n // CCTP Message Header fields\\n // Ref: https://developers.circle.com/cctp/technical-guide#message-header\\n uint8 private constant VERSION_INDEX = 0;\\n uint8 private constant SOURCE_DOMAIN_INDEX = 4;\\n uint8 private constant SENDER_INDEX = 44;\\n uint8 private constant RECIPIENT_INDEX = 76;\\n uint8 private constant MESSAGE_BODY_INDEX = 148;\\n\\n /**\\n * @dev Get the message version from the message.\\n * It should always be 4 bytes long,\\n * starting from the 0th index.\\n * @param message The message to get the version from\\n * @return The message version\\n */\\n function getMessageVersion(bytes memory message)\\n internal\\n pure\\n returns (uint32)\\n {\\n // uint32 bytes 0 to 4 is Origin message version\\n // uint32 bytes 4 to 8 is Message type\\n return message.extractUint32(0);\\n }\\n\\n /**\\n * @dev Get the message type from the message.\\n * It should always be 4 bytes long,\\n * starting from the 4th index.\\n * @param message The message to get the type from\\n * @return The message type\\n */\\n function getMessageType(bytes memory message)\\n internal\\n pure\\n returns (uint32)\\n {\\n // uint32 bytes 0 to 4 is Origin message version\\n // uint32 bytes 4 to 8 is Message type\\n return message.extractUint32(4);\\n }\\n\\n /**\\n * @dev Verify the message version and type.\\n * The message version should be the same as the Origin message version,\\n * and the message type should be the same as the expected message type.\\n * @param _message The message to verify\\n * @param _type The expected message type\\n */\\n function verifyMessageVersionAndType(bytes memory _message, uint32 _type)\\n internal\\n pure\\n {\\n require(\\n getMessageVersion(_message) == ORIGIN_MESSAGE_VERSION,\\n \\\"Invalid Origin Message Version\\\"\\n );\\n require(getMessageType(_message) == _type, \\\"Invalid Message type\\\");\\n }\\n\\n /**\\n * @dev Get the message payload from the message.\\n * The payload starts at the 8th byte.\\n * @param message The message to get the payload from\\n * @return The message payload\\n */\\n function getMessagePayload(bytes memory message)\\n internal\\n pure\\n returns (bytes memory)\\n {\\n // uint32 bytes 0 to 4 is Origin message version\\n // uint32 bytes 4 to 8 is Message type\\n // Payload starts at byte 8\\n return message.extractSlice(8, message.length);\\n }\\n\\n /**\\n * @dev Encode the deposit message.\\n * The message version and type are always encoded in the message.\\n * @param nonce The nonce of the deposit\\n * @param depositAmount The amount of the deposit\\n * @return The encoded deposit message\\n */\\n function encodeDepositMessage(uint64 nonce, uint256 depositAmount)\\n internal\\n pure\\n returns (bytes memory)\\n {\\n return\\n abi.encodePacked(\\n ORIGIN_MESSAGE_VERSION,\\n DEPOSIT_MESSAGE,\\n abi.encode(nonce, depositAmount)\\n );\\n }\\n\\n /**\\n * @dev Decode the deposit message.\\n * The message version and type are verified in the message.\\n * @param message The message to decode\\n * @return The nonce and the amount of the deposit\\n */\\n function decodeDepositMessage(bytes memory message)\\n internal\\n pure\\n returns (uint64, uint256)\\n {\\n verifyMessageVersionAndType(message, DEPOSIT_MESSAGE);\\n\\n (uint64 nonce, uint256 depositAmount) = abi.decode(\\n getMessagePayload(message),\\n (uint64, uint256)\\n );\\n return (nonce, depositAmount);\\n }\\n\\n /**\\n * @dev Encode the withdrawal message.\\n * The message version and type are always encoded in the message.\\n * @param nonce The nonce of the withdrawal\\n * @param withdrawAmount The amount of the withdrawal\\n * @return The encoded withdrawal message\\n */\\n function encodeWithdrawMessage(uint64 nonce, uint256 withdrawAmount)\\n internal\\n pure\\n returns (bytes memory)\\n {\\n return\\n abi.encodePacked(\\n ORIGIN_MESSAGE_VERSION,\\n WITHDRAW_MESSAGE,\\n abi.encode(nonce, withdrawAmount)\\n );\\n }\\n\\n /**\\n * @dev Decode the withdrawal message.\\n * The message version and type are verified in the message.\\n * @param message The message to decode\\n * @return The nonce and the amount of the withdrawal\\n */\\n function decodeWithdrawMessage(bytes memory message)\\n internal\\n pure\\n returns (uint64, uint256)\\n {\\n verifyMessageVersionAndType(message, WITHDRAW_MESSAGE);\\n\\n (uint64 nonce, uint256 withdrawAmount) = abi.decode(\\n getMessagePayload(message),\\n (uint64, uint256)\\n );\\n return (nonce, withdrawAmount);\\n }\\n\\n /**\\n * @dev Encode the balance check message.\\n * The message version and type are always encoded in the message.\\n * @param nonce The nonce of the balance check\\n * @param balance The balance to check\\n * @param transferConfirmation Indicates if the message is a transfer confirmation. This is true\\n * when the message is a result of a deposit or a withdrawal.\\n * @return The encoded balance check message\\n */\\n function encodeBalanceCheckMessage(\\n uint64 nonce,\\n uint256 balance,\\n bool transferConfirmation,\\n uint256 timestamp\\n ) internal pure returns (bytes memory) {\\n return\\n abi.encodePacked(\\n ORIGIN_MESSAGE_VERSION,\\n BALANCE_CHECK_MESSAGE,\\n abi.encode(nonce, balance, transferConfirmation, timestamp)\\n );\\n }\\n\\n /**\\n * @dev Decode the balance check message.\\n * The message version and type are verified in the message.\\n * @param message The message to decode\\n * @return The nonce, the balance and indicates if the message is a transfer confirmation\\n */\\n function decodeBalanceCheckMessage(bytes memory message)\\n internal\\n pure\\n returns (\\n uint64,\\n uint256,\\n bool,\\n uint256\\n )\\n {\\n verifyMessageVersionAndType(message, BALANCE_CHECK_MESSAGE);\\n\\n (\\n uint64 nonce,\\n uint256 balance,\\n bool transferConfirmation,\\n uint256 timestamp\\n ) = abi.decode(\\n getMessagePayload(message),\\n (uint64, uint256, bool, uint256)\\n );\\n return (nonce, balance, transferConfirmation, timestamp);\\n }\\n\\n /**\\n * @dev Decode the CCTP message header\\n * @param message Message to decode\\n * @return version Version of the message\\n * @return sourceDomainID Source domain ID\\n * @return sender Sender of the message\\n * @return recipient Recipient of the message\\n * @return messageBody Message body\\n */\\n function decodeMessageHeader(bytes memory message)\\n internal\\n pure\\n returns (\\n uint32 version,\\n uint32 sourceDomainID,\\n address sender,\\n address recipient,\\n bytes memory messageBody\\n )\\n {\\n version = message.extractUint32(VERSION_INDEX);\\n sourceDomainID = message.extractUint32(SOURCE_DOMAIN_INDEX);\\n // Address of MessageTransmitterV2 caller on source domain\\n sender = message.extractAddress(SENDER_INDEX);\\n // Address to handle message body on destination domain\\n recipient = message.extractAddress(RECIPIENT_INDEX);\\n messageBody = message.extractSlice(MESSAGE_BODY_INDEX, message.length);\\n }\\n}\\n\",\"keccak256\":\"0xbe640419f622ed08d59c10f3ce206a015c524eaa2543c8882ff55a0cea4c6297\",\"license\":\"BUSL-1.1\"},\"contracts/token/OUSD.sol\":{\"content\":\"// SPDX-License-Identifier: BUSL-1.1\\npragma solidity ^0.8.0;\\n\\n/**\\n * @title OUSD Token Contract\\n * @dev ERC20 compatible contract for OUSD\\n * @dev Implements an elastic supply\\n * @author Origin Protocol Inc\\n */\\nimport { IVault } from \\\"../interfaces/IVault.sol\\\";\\nimport { Governable } from \\\"../governance/Governable.sol\\\";\\nimport { SafeCast } from \\\"@openzeppelin/contracts/utils/math/SafeCast.sol\\\";\\n\\ncontract OUSD is Governable {\\n using SafeCast for int256;\\n using SafeCast for uint256;\\n\\n /// @dev Event triggered when the supply changes\\n /// @param totalSupply Updated token total supply\\n /// @param rebasingCredits Updated token rebasing credits\\n /// @param rebasingCreditsPerToken Updated token rebasing credits per token\\n event TotalSupplyUpdatedHighres(\\n uint256 totalSupply,\\n uint256 rebasingCredits,\\n uint256 rebasingCreditsPerToken\\n );\\n /// @dev Event triggered when an account opts in for rebasing\\n /// @param account Address of the account\\n event AccountRebasingEnabled(address account);\\n /// @dev Event triggered when an account opts out of rebasing\\n /// @param account Address of the account\\n event AccountRebasingDisabled(address account);\\n /// @dev Emitted when `value` tokens are moved from one account `from` to\\n /// another `to`.\\n /// @param from Address of the account tokens are moved from\\n /// @param to Address of the account tokens are moved to\\n /// @param value Amount of tokens transferred\\n event Transfer(address indexed from, address indexed to, uint256 value);\\n /// @dev Emitted when the allowance of a `spender` for an `owner` is set by\\n /// a call to {approve}. `value` is the new allowance.\\n /// @param owner Address of the owner approving allowance\\n /// @param spender Address of the spender allowance is granted to\\n /// @param value Amount of tokens spender can transfer\\n event Approval(\\n address indexed owner,\\n address indexed spender,\\n uint256 value\\n );\\n /// @dev Yield resulting from {changeSupply} that a `source` account would\\n /// receive is directed to `target` account.\\n /// @param source Address of the source forwarding the yield\\n /// @param target Address of the target receiving the yield\\n event YieldDelegated(address source, address target);\\n /// @dev Yield delegation from `source` account to the `target` account is\\n /// suspended.\\n /// @param source Address of the source suspending yield forwarding\\n /// @param target Address of the target no longer receiving yield from `source`\\n /// account\\n event YieldUndelegated(address source, address target);\\n\\n enum RebaseOptions {\\n NotSet,\\n StdNonRebasing,\\n StdRebasing,\\n YieldDelegationSource,\\n YieldDelegationTarget\\n }\\n\\n uint256[154] private _gap; // Slots to align with deployed contract\\n uint256 private constant MAX_SUPPLY = type(uint128).max;\\n /// @dev The amount of tokens in existence\\n uint256 public totalSupply;\\n mapping(address => mapping(address => uint256)) private allowances;\\n /// @dev The vault with privileges to execute {mint}, {burn}\\n /// and {changeSupply}\\n address public vaultAddress;\\n mapping(address => uint256) internal creditBalances;\\n // the 2 storage variables below need trailing underscores to not name collide with public functions\\n uint256 private rebasingCredits_; // Sum of all rebasing credits (creditBalances for rebasing accounts)\\n uint256 private rebasingCreditsPerToken_;\\n /// @dev The amount of tokens that are not rebasing - receiving yield\\n uint256 public nonRebasingSupply;\\n mapping(address => uint256) internal alternativeCreditsPerToken;\\n /// @dev A map of all addresses and their respective RebaseOptions\\n mapping(address => RebaseOptions) public rebaseState;\\n mapping(address => uint256) private __deprecated_isUpgraded;\\n /// @dev A map of addresses that have yields forwarded to. This is an\\n /// inverse mapping of {yieldFrom}\\n /// Key Account forwarding yield\\n /// Value Account receiving yield\\n mapping(address => address) public yieldTo;\\n /// @dev A map of addresses that are receiving the yield. This is an\\n /// inverse mapping of {yieldTo}\\n /// Key Account receiving yield\\n /// Value Account forwarding yield\\n mapping(address => address) public yieldFrom;\\n\\n uint256 private constant RESOLUTION_INCREASE = 1e9;\\n uint256[34] private __gap; // including below gap totals up to 200\\n\\n /// @dev Verifies that the caller is the Governor or Strategist.\\n modifier onlyGovernorOrStrategist() {\\n require(\\n isGovernor() || msg.sender == IVault(vaultAddress).strategistAddr(),\\n \\\"Caller is not the Strategist or Governor\\\"\\n );\\n _;\\n }\\n\\n /// @dev Initializes the contract and sets necessary variables.\\n /// @param _vaultAddress Address of the vault contract\\n /// @param _initialCreditsPerToken The starting rebasing credits per token.\\n function initialize(address _vaultAddress, uint256 _initialCreditsPerToken)\\n external\\n onlyGovernor\\n {\\n require(_vaultAddress != address(0), \\\"Zero vault address\\\");\\n require(vaultAddress == address(0), \\\"Already initialized\\\");\\n\\n rebasingCreditsPerToken_ = _initialCreditsPerToken;\\n vaultAddress = _vaultAddress;\\n }\\n\\n /// @dev Returns the symbol of the token, a shorter version\\n /// of the name.\\n function symbol() external pure virtual returns (string memory) {\\n return \\\"OUSD\\\";\\n }\\n\\n /// @dev Returns the name of the token.\\n function name() external pure virtual returns (string memory) {\\n return \\\"Origin Dollar\\\";\\n }\\n\\n /// @dev Returns the number of decimals used to get its user representation.\\n function decimals() external pure virtual returns (uint8) {\\n return 18;\\n }\\n\\n /**\\n * @dev Verifies that the caller is the Vault contract\\n */\\n modifier onlyVault() {\\n require(vaultAddress == msg.sender, \\\"Caller is not the Vault\\\");\\n _;\\n }\\n\\n /**\\n * @return High resolution rebasingCreditsPerToken\\n */\\n function rebasingCreditsPerTokenHighres() external view returns (uint256) {\\n return rebasingCreditsPerToken_;\\n }\\n\\n /**\\n * @return Low resolution rebasingCreditsPerToken\\n */\\n function rebasingCreditsPerToken() external view returns (uint256) {\\n return rebasingCreditsPerToken_ / RESOLUTION_INCREASE;\\n }\\n\\n /**\\n * @return High resolution total number of rebasing credits\\n */\\n function rebasingCreditsHighres() external view returns (uint256) {\\n return rebasingCredits_;\\n }\\n\\n /**\\n * @return Low resolution total number of rebasing credits\\n */\\n function rebasingCredits() external view returns (uint256) {\\n return rebasingCredits_ / RESOLUTION_INCREASE;\\n }\\n\\n /**\\n * @notice Gets the balance of the specified address.\\n * @param _account Address to query the balance of.\\n * @return A uint256 representing the amount of base units owned by the\\n * specified address.\\n */\\n function balanceOf(address _account) public view returns (uint256) {\\n RebaseOptions state = rebaseState[_account];\\n if (state == RebaseOptions.YieldDelegationSource) {\\n // Saves a slot read when transferring to or from a yield delegating source\\n // since we know creditBalances equals the balance.\\n return creditBalances[_account];\\n }\\n uint256 baseBalance = (creditBalances[_account] * 1e18) /\\n _creditsPerToken(_account);\\n if (state == RebaseOptions.YieldDelegationTarget) {\\n // creditBalances of yieldFrom accounts equals token balances\\n return baseBalance - creditBalances[yieldFrom[_account]];\\n }\\n return baseBalance;\\n }\\n\\n /**\\n * @notice Gets the credits balance of the specified address.\\n * @dev Backwards compatible with old low res credits per token.\\n * @param _account The address to query the balance of.\\n * @return (uint256, uint256) Credit balance and credits per token of the\\n * address\\n */\\n function creditsBalanceOf(address _account)\\n external\\n view\\n returns (uint256, uint256)\\n {\\n uint256 cpt = _creditsPerToken(_account);\\n if (cpt == 1e27) {\\n // For a period before the resolution upgrade, we created all new\\n // contract accounts at high resolution. Since they are not changing\\n // as a result of this upgrade, we will return their true values\\n return (creditBalances[_account], cpt);\\n } else {\\n return (\\n creditBalances[_account] / RESOLUTION_INCREASE,\\n cpt / RESOLUTION_INCREASE\\n );\\n }\\n }\\n\\n /**\\n * @notice Gets the credits balance of the specified address.\\n * @param _account The address to query the balance of.\\n * @return (uint256, uint256, bool) Credit balance, credits per token of the\\n * address, and isUpgraded\\n */\\n function creditsBalanceOfHighres(address _account)\\n external\\n view\\n returns (\\n uint256,\\n uint256,\\n bool\\n )\\n {\\n return (\\n creditBalances[_account],\\n _creditsPerToken(_account),\\n true // all accounts have their resolution \\\"upgraded\\\"\\n );\\n }\\n\\n // Backwards compatible view\\n function nonRebasingCreditsPerToken(address _account)\\n external\\n view\\n returns (uint256)\\n {\\n return alternativeCreditsPerToken[_account];\\n }\\n\\n /**\\n * @notice Transfer tokens to a specified address.\\n * @param _to the address to transfer to.\\n * @param _value the amount to be transferred.\\n * @return true on success.\\n */\\n function transfer(address _to, uint256 _value) external returns (bool) {\\n require(_to != address(0), \\\"Transfer to zero address\\\");\\n\\n _executeTransfer(msg.sender, _to, _value);\\n\\n emit Transfer(msg.sender, _to, _value);\\n return true;\\n }\\n\\n /**\\n * @notice Transfer tokens from one address to another.\\n * @param _from The address you want to send tokens from.\\n * @param _to The address you want to transfer to.\\n * @param _value The amount of tokens to be transferred.\\n * @return true on success.\\n */\\n function transferFrom(\\n address _from,\\n address _to,\\n uint256 _value\\n ) external returns (bool) {\\n require(_to != address(0), \\\"Transfer to zero address\\\");\\n uint256 userAllowance = allowances[_from][msg.sender];\\n require(_value <= userAllowance, \\\"Allowance exceeded\\\");\\n\\n unchecked {\\n allowances[_from][msg.sender] = userAllowance - _value;\\n }\\n\\n _executeTransfer(_from, _to, _value);\\n\\n emit Transfer(_from, _to, _value);\\n return true;\\n }\\n\\n function _executeTransfer(\\n address _from,\\n address _to,\\n uint256 _value\\n ) internal {\\n (\\n int256 fromRebasingCreditsDiff,\\n int256 fromNonRebasingSupplyDiff\\n ) = _adjustAccount(_from, -_value.toInt256());\\n (\\n int256 toRebasingCreditsDiff,\\n int256 toNonRebasingSupplyDiff\\n ) = _adjustAccount(_to, _value.toInt256());\\n\\n _adjustGlobals(\\n fromRebasingCreditsDiff + toRebasingCreditsDiff,\\n fromNonRebasingSupplyDiff + toNonRebasingSupplyDiff\\n );\\n }\\n\\n function _adjustAccount(address _account, int256 _balanceChange)\\n internal\\n returns (int256 rebasingCreditsDiff, int256 nonRebasingSupplyDiff)\\n {\\n RebaseOptions state = rebaseState[_account];\\n int256 currentBalance = balanceOf(_account).toInt256();\\n if (currentBalance + _balanceChange < 0) {\\n revert(\\\"Transfer amount exceeds balance\\\");\\n }\\n uint256 newBalance = (currentBalance + _balanceChange).toUint256();\\n\\n if (state == RebaseOptions.YieldDelegationSource) {\\n address target = yieldTo[_account];\\n uint256 targetOldBalance = balanceOf(target);\\n uint256 targetNewCredits = _balanceToRebasingCredits(\\n targetOldBalance + newBalance\\n );\\n rebasingCreditsDiff =\\n targetNewCredits.toInt256() -\\n creditBalances[target].toInt256();\\n\\n creditBalances[_account] = newBalance;\\n creditBalances[target] = targetNewCredits;\\n } else if (state == RebaseOptions.YieldDelegationTarget) {\\n uint256 newCredits = _balanceToRebasingCredits(\\n newBalance + creditBalances[yieldFrom[_account]]\\n );\\n rebasingCreditsDiff =\\n newCredits.toInt256() -\\n creditBalances[_account].toInt256();\\n creditBalances[_account] = newCredits;\\n } else {\\n _autoMigrate(_account);\\n uint256 alternativeCreditsPerTokenMem = alternativeCreditsPerToken[\\n _account\\n ];\\n if (alternativeCreditsPerTokenMem > 0) {\\n nonRebasingSupplyDiff = _balanceChange;\\n if (alternativeCreditsPerTokenMem != 1e18) {\\n alternativeCreditsPerToken[_account] = 1e18;\\n }\\n creditBalances[_account] = newBalance;\\n } else {\\n uint256 newCredits = _balanceToRebasingCredits(newBalance);\\n rebasingCreditsDiff =\\n newCredits.toInt256() -\\n creditBalances[_account].toInt256();\\n creditBalances[_account] = newCredits;\\n }\\n }\\n }\\n\\n function _adjustGlobals(\\n int256 _rebasingCreditsDiff,\\n int256 _nonRebasingSupplyDiff\\n ) internal {\\n if (_rebasingCreditsDiff != 0) {\\n rebasingCredits_ = (rebasingCredits_.toInt256() +\\n _rebasingCreditsDiff).toUint256();\\n }\\n if (_nonRebasingSupplyDiff != 0) {\\n nonRebasingSupply = (nonRebasingSupply.toInt256() +\\n _nonRebasingSupplyDiff).toUint256();\\n }\\n }\\n\\n /**\\n * @notice Function to check the amount of tokens that _owner has allowed\\n * to `_spender`.\\n * @param _owner The address which owns the funds.\\n * @param _spender The address which will spend the funds.\\n * @return The number of tokens still available for the _spender.\\n */\\n function allowance(address _owner, address _spender)\\n external\\n view\\n returns (uint256)\\n {\\n return allowances[_owner][_spender];\\n }\\n\\n /**\\n * @notice Approve the passed address to spend the specified amount of\\n * tokens on behalf of msg.sender.\\n * @param _spender The address which will spend the funds.\\n * @param _value The amount of tokens to be spent.\\n * @return true on success.\\n */\\n function approve(address _spender, uint256 _value) external returns (bool) {\\n allowances[msg.sender][_spender] = _value;\\n emit Approval(msg.sender, _spender, _value);\\n return true;\\n }\\n\\n /**\\n * @notice Creates `_amount` tokens and assigns them to `_account`,\\n * increasing the total supply.\\n */\\n function mint(address _account, uint256 _amount) external onlyVault {\\n require(_account != address(0), \\\"Mint to the zero address\\\");\\n\\n // Account\\n (\\n int256 toRebasingCreditsDiff,\\n int256 toNonRebasingSupplyDiff\\n ) = _adjustAccount(_account, _amount.toInt256());\\n // Globals\\n _adjustGlobals(toRebasingCreditsDiff, toNonRebasingSupplyDiff);\\n totalSupply = totalSupply + _amount;\\n\\n require(totalSupply < MAX_SUPPLY, \\\"Max supply\\\");\\n emit Transfer(address(0), _account, _amount);\\n }\\n\\n /**\\n * @notice Destroys `_amount` tokens from `_account`,\\n * reducing the total supply.\\n */\\n function burn(address _account, uint256 _amount) external onlyVault {\\n require(_account != address(0), \\\"Burn from the zero address\\\");\\n if (_amount == 0) {\\n return;\\n }\\n\\n // Account\\n (\\n int256 toRebasingCreditsDiff,\\n int256 toNonRebasingSupplyDiff\\n ) = _adjustAccount(_account, -_amount.toInt256());\\n // Globals\\n _adjustGlobals(toRebasingCreditsDiff, toNonRebasingSupplyDiff);\\n totalSupply = totalSupply - _amount;\\n\\n emit Transfer(_account, address(0), _amount);\\n }\\n\\n /**\\n * @dev Get the credits per token for an account. Returns a fixed amount\\n * if the account is non-rebasing.\\n * @param _account Address of the account.\\n */\\n function _creditsPerToken(address _account)\\n internal\\n view\\n returns (uint256)\\n {\\n uint256 alternativeCreditsPerTokenMem = alternativeCreditsPerToken[\\n _account\\n ];\\n if (alternativeCreditsPerTokenMem != 0) {\\n return alternativeCreditsPerTokenMem;\\n } else {\\n return rebasingCreditsPerToken_;\\n }\\n }\\n\\n /**\\n * @dev Auto migrate contracts to be non rebasing,\\n * unless they have opted into yield.\\n * @param _account Address of the account.\\n */\\n function _autoMigrate(address _account) internal {\\n uint256 codeLen = _account.code.length;\\n bool isEOA = (codeLen == 0) ||\\n (codeLen == 23 && bytes3(_account.code) == 0xef0100);\\n // In previous code versions, contracts would not have had their\\n // rebaseState[_account] set to RebaseOptions.NonRebasing when migrated\\n // therefore we check the actual accounting used on the account as well.\\n if (\\n (!isEOA) &&\\n rebaseState[_account] == RebaseOptions.NotSet &&\\n alternativeCreditsPerToken[_account] == 0\\n ) {\\n _rebaseOptOut(_account);\\n }\\n }\\n\\n /**\\n * @dev Calculates credits from contract's global rebasingCreditsPerToken_, and\\n * also balance that corresponds to those credits. The latter is important\\n * when adjusting the contract's global nonRebasingSupply to circumvent any\\n * possible rounding errors.\\n *\\n * @param _balance Balance of the account.\\n */\\n function _balanceToRebasingCredits(uint256 _balance)\\n internal\\n view\\n returns (uint256 rebasingCredits)\\n {\\n // Rounds up, because we need to ensure that accounts always have\\n // at least the balance that they should have.\\n // Note this should always be used on an absolute account value,\\n // not on a possibly negative diff, because then the rounding would be wrong.\\n return ((_balance) * rebasingCreditsPerToken_ + 1e18 - 1) / 1e18;\\n }\\n\\n /**\\n * @notice The calling account will start receiving yield after a successful call.\\n * @param _account Address of the account.\\n */\\n function governanceRebaseOptIn(address _account) external onlyGovernor {\\n require(_account != address(0), \\\"Zero address not allowed\\\");\\n _rebaseOptIn(_account);\\n }\\n\\n /**\\n * @notice The calling account will start receiving yield after a successful call.\\n */\\n function rebaseOptIn() external {\\n _rebaseOptIn(msg.sender);\\n }\\n\\n function _rebaseOptIn(address _account) internal {\\n uint256 balance = balanceOf(_account);\\n\\n // prettier-ignore\\n require(\\n alternativeCreditsPerToken[_account] > 0 ||\\n // Accounts may explicitly `rebaseOptIn` regardless of\\n // accounting if they have a 0 balance.\\n creditBalances[_account] == 0\\n ,\\n \\\"Account must be non-rebasing\\\"\\n );\\n RebaseOptions state = rebaseState[_account];\\n // prettier-ignore\\n require(\\n state == RebaseOptions.StdNonRebasing ||\\n state == RebaseOptions.NotSet,\\n \\\"Only standard non-rebasing accounts can opt in\\\"\\n );\\n\\n uint256 newCredits = _balanceToRebasingCredits(balance);\\n\\n // Account\\n rebaseState[_account] = RebaseOptions.StdRebasing;\\n alternativeCreditsPerToken[_account] = 0;\\n creditBalances[_account] = newCredits;\\n // Globals\\n _adjustGlobals(newCredits.toInt256(), -balance.toInt256());\\n\\n emit AccountRebasingEnabled(_account);\\n }\\n\\n /**\\n * @notice The calling account will no longer receive yield\\n */\\n function rebaseOptOut() external {\\n _rebaseOptOut(msg.sender);\\n }\\n\\n function _rebaseOptOut(address _account) internal {\\n require(\\n alternativeCreditsPerToken[_account] == 0,\\n \\\"Account must be rebasing\\\"\\n );\\n RebaseOptions state = rebaseState[_account];\\n require(\\n state == RebaseOptions.StdRebasing || state == RebaseOptions.NotSet,\\n \\\"Only standard rebasing accounts can opt out\\\"\\n );\\n\\n uint256 oldCredits = creditBalances[_account];\\n uint256 balance = balanceOf(_account);\\n\\n // Account\\n rebaseState[_account] = RebaseOptions.StdNonRebasing;\\n alternativeCreditsPerToken[_account] = 1e18;\\n creditBalances[_account] = balance;\\n // Globals\\n _adjustGlobals(-oldCredits.toInt256(), balance.toInt256());\\n\\n emit AccountRebasingDisabled(_account);\\n }\\n\\n /**\\n * @notice Distribute yield to users. This changes the exchange rate\\n * between \\\"credits\\\" and OUSD tokens to change rebasing user's balances.\\n * @param _newTotalSupply New total supply of OUSD.\\n */\\n function changeSupply(uint256 _newTotalSupply) external onlyVault {\\n require(totalSupply > 0, \\\"Cannot increase 0 supply\\\");\\n\\n if (totalSupply == _newTotalSupply) {\\n emit TotalSupplyUpdatedHighres(\\n totalSupply,\\n rebasingCredits_,\\n rebasingCreditsPerToken_\\n );\\n return;\\n }\\n\\n totalSupply = _newTotalSupply > MAX_SUPPLY\\n ? MAX_SUPPLY\\n : _newTotalSupply;\\n\\n uint256 rebasingSupply = totalSupply - nonRebasingSupply;\\n // round up in the favour of the protocol\\n rebasingCreditsPerToken_ =\\n (rebasingCredits_ * 1e18 + rebasingSupply - 1) /\\n rebasingSupply;\\n\\n require(rebasingCreditsPerToken_ > 0, \\\"Invalid change in supply\\\");\\n\\n emit TotalSupplyUpdatedHighres(\\n totalSupply,\\n rebasingCredits_,\\n rebasingCreditsPerToken_\\n );\\n }\\n\\n /*\\n * @notice Send the yield from one account to another account.\\n * Each account keeps its own balances.\\n */\\n function delegateYield(address _from, address _to)\\n external\\n onlyGovernorOrStrategist\\n {\\n require(_from != address(0), \\\"Zero from address not allowed\\\");\\n require(_to != address(0), \\\"Zero to address not allowed\\\");\\n\\n require(_from != _to, \\\"Cannot delegate to self\\\");\\n require(\\n yieldFrom[_to] == address(0) &&\\n yieldTo[_to] == address(0) &&\\n yieldFrom[_from] == address(0) &&\\n yieldTo[_from] == address(0),\\n \\\"Blocked by existing yield delegation\\\"\\n );\\n RebaseOptions stateFrom = rebaseState[_from];\\n RebaseOptions stateTo = rebaseState[_to];\\n\\n require(\\n stateFrom == RebaseOptions.NotSet ||\\n stateFrom == RebaseOptions.StdNonRebasing ||\\n stateFrom == RebaseOptions.StdRebasing,\\n \\\"Invalid rebaseState from\\\"\\n );\\n\\n require(\\n stateTo == RebaseOptions.NotSet ||\\n stateTo == RebaseOptions.StdNonRebasing ||\\n stateTo == RebaseOptions.StdRebasing,\\n \\\"Invalid rebaseState to\\\"\\n );\\n\\n if (alternativeCreditsPerToken[_from] == 0) {\\n _rebaseOptOut(_from);\\n }\\n if (alternativeCreditsPerToken[_to] > 0) {\\n _rebaseOptIn(_to);\\n }\\n\\n uint256 fromBalance = balanceOf(_from);\\n uint256 toBalance = balanceOf(_to);\\n uint256 oldToCredits = creditBalances[_to];\\n uint256 newToCredits = _balanceToRebasingCredits(\\n fromBalance + toBalance\\n );\\n\\n // Set up the bidirectional links\\n yieldTo[_from] = _to;\\n yieldFrom[_to] = _from;\\n\\n // Local\\n rebaseState[_from] = RebaseOptions.YieldDelegationSource;\\n alternativeCreditsPerToken[_from] = 1e18;\\n creditBalances[_from] = fromBalance;\\n rebaseState[_to] = RebaseOptions.YieldDelegationTarget;\\n creditBalances[_to] = newToCredits;\\n\\n // Global\\n int256 creditsChange = newToCredits.toInt256() -\\n oldToCredits.toInt256();\\n _adjustGlobals(creditsChange, -(fromBalance).toInt256());\\n emit YieldDelegated(_from, _to);\\n }\\n\\n /*\\n * @notice Stop sending the yield from one account to another account.\\n */\\n function undelegateYield(address _from) external onlyGovernorOrStrategist {\\n // Require a delegation, which will also ensure a valid delegation\\n require(yieldTo[_from] != address(0), \\\"Zero address not allowed\\\");\\n\\n address to = yieldTo[_from];\\n uint256 fromBalance = balanceOf(_from);\\n uint256 toBalance = balanceOf(to);\\n uint256 oldToCredits = creditBalances[to];\\n uint256 newToCredits = _balanceToRebasingCredits(toBalance);\\n\\n // Remove the bidirectional links\\n yieldFrom[to] = address(0);\\n yieldTo[_from] = address(0);\\n\\n // Local\\n rebaseState[_from] = RebaseOptions.StdNonRebasing;\\n // alternativeCreditsPerToken[from] already 1e18 from `delegateYield()`\\n creditBalances[_from] = fromBalance;\\n rebaseState[to] = RebaseOptions.StdRebasing;\\n // alternativeCreditsPerToken[to] already 0 from `delegateYield()`\\n creditBalances[to] = newToCredits;\\n\\n // Global\\n int256 creditsChange = newToCredits.toInt256() -\\n oldToCredits.toInt256();\\n _adjustGlobals(creditsChange, fromBalance.toInt256());\\n emit YieldUndelegated(_from, to);\\n }\\n}\\n\",\"keccak256\":\"0x73439bef6569f5adf6f5ce2cb54a5f0d3109d4819457532236e172a7091980a9\",\"license\":\"BUSL-1.1\"},\"contracts/utils/BytesHelper.sol\":{\"content\":\"// SPDX-License-Identifier: BUSL-1.1\\npragma solidity ^0.8.0;\\n\\nuint256 constant UINT32_LENGTH = 4;\\nuint256 constant UINT64_LENGTH = 8;\\nuint256 constant UINT256_LENGTH = 32;\\n// Address is 20 bytes, but we expect the data to be padded with 0s to 32 bytes\\nuint256 constant ADDRESS_LENGTH = 32;\\n\\nlibrary BytesHelper {\\n /**\\n * @dev Extract a slice from bytes memory\\n * @param data The bytes memory to slice\\n * @param start The start index (inclusive)\\n * @param end The end index (exclusive)\\n * @return result A new bytes memory containing the slice\\n */\\n function extractSlice(\\n bytes memory data,\\n uint256 start,\\n uint256 end\\n ) internal pure returns (bytes memory) {\\n require(end >= start, \\\"Invalid slice range\\\");\\n require(end <= data.length, \\\"Slice end exceeds data length\\\");\\n\\n uint256 length = end - start;\\n bytes memory result = new bytes(length);\\n\\n // Simple byte-by-byte copy\\n for (uint256 i = 0; i < length; i++) {\\n result[i] = data[start + i];\\n }\\n\\n return result;\\n }\\n\\n /**\\n * @dev Decode a uint32 from a bytes memory\\n * @param data The bytes memory to decode\\n * @return uint32 The decoded uint32\\n */\\n function decodeUint32(bytes memory data) internal pure returns (uint32) {\\n require(data.length == 4, \\\"Invalid data length\\\");\\n return uint32(uint256(bytes32(data)) >> 224);\\n }\\n\\n /**\\n * @dev Extract a uint32 from a bytes memory\\n * @param data The bytes memory to extract from\\n * @param start The start index (inclusive)\\n * @return uint32 The extracted uint32\\n */\\n function extractUint32(bytes memory data, uint256 start)\\n internal\\n pure\\n returns (uint32)\\n {\\n return decodeUint32(extractSlice(data, start, start + UINT32_LENGTH));\\n }\\n\\n /**\\n * @dev Decode an address from a bytes memory.\\n * Expects the data to be padded with 0s to 32 bytes.\\n * @param data The bytes memory to decode\\n * @return address The decoded address\\n */\\n function decodeAddress(bytes memory data) internal pure returns (address) {\\n // We expect the data to be padded with 0s, so length is 32 not 20\\n require(data.length == 32, \\\"Invalid data length\\\");\\n return abi.decode(data, (address));\\n }\\n\\n /**\\n * @dev Extract an address from a bytes memory\\n * @param data The bytes memory to extract from\\n * @param start The start index (inclusive)\\n * @return address The extracted address\\n */\\n function extractAddress(bytes memory data, uint256 start)\\n internal\\n pure\\n returns (address)\\n {\\n return decodeAddress(extractSlice(data, start, start + ADDRESS_LENGTH));\\n }\\n\\n /**\\n * @dev Decode a uint256 from a bytes memory\\n * @param data The bytes memory to decode\\n * @return uint256 The decoded uint256\\n */\\n function decodeUint256(bytes memory data) internal pure returns (uint256) {\\n require(data.length == 32, \\\"Invalid data length\\\");\\n return abi.decode(data, (uint256));\\n }\\n\\n /**\\n * @dev Extract a uint256 from a bytes memory\\n * @param data The bytes memory to extract from\\n * @param start The start index (inclusive)\\n * @return uint256 The extracted uint256\\n */\\n function extractUint256(bytes memory data, uint256 start)\\n internal\\n pure\\n returns (uint256)\\n {\\n return decodeUint256(extractSlice(data, start, start + UINT256_LENGTH));\\n }\\n}\\n\",\"keccak256\":\"0x471aff77f4b6750abd8ed018fa3e89707f045ae377c73b225d413d62c1e9b28f\",\"license\":\"BUSL-1.1\"},\"contracts/utils/Helpers.sol\":{\"content\":\"// SPDX-License-Identifier: BUSL-1.1\\npragma solidity ^0.8.0;\\n\\nimport { IBasicToken } from \\\"../interfaces/IBasicToken.sol\\\";\\n\\nlibrary Helpers {\\n /**\\n * @notice Fetch the `symbol()` from an ERC20 token\\n * @dev Grabs the `symbol()` from a contract\\n * @param _token Address of the ERC20 token\\n * @return string Symbol of the ERC20 token\\n */\\n function getSymbol(address _token) internal view returns (string memory) {\\n string memory symbol = IBasicToken(_token).symbol();\\n return symbol;\\n }\\n\\n /**\\n * @notice Fetch the `decimals()` from an ERC20 token\\n * @dev Grabs the `decimals()` from a contract and fails if\\n * the decimal value does not live within a certain range\\n * @param _token Address of the ERC20 token\\n * @return uint256 Decimals of the ERC20 token\\n */\\n function getDecimals(address _token) internal view returns (uint256) {\\n uint256 decimals = IBasicToken(_token).decimals();\\n require(\\n decimals >= 4 && decimals <= 18,\\n \\\"Token must have sufficient decimal places\\\"\\n );\\n\\n return decimals;\\n }\\n}\\n\",\"keccak256\":\"0x4366f8d90b34c1eef8bbaaf369b1e5cd59f04027bb3c111f208eaee65bbc0346\",\"license\":\"BUSL-1.1\"},\"contracts/utils/Initializable.sol\":{\"content\":\"// SPDX-License-Identifier: BUSL-1.1\\npragma solidity ^0.8.0;\\n\\n/**\\n * @title Base contract any contracts that need to initialize state after deployment.\\n * @author Origin Protocol Inc\\n */\\nabstract contract Initializable {\\n /**\\n * @dev Indicates that the contract has been initialized.\\n */\\n bool private initialized;\\n\\n /**\\n * @dev Indicates that the contract is in the process of being initialized.\\n */\\n bool private initializing;\\n\\n /**\\n * @dev Modifier to protect an initializer function from being invoked twice.\\n */\\n modifier initializer() {\\n require(\\n initializing || !initialized,\\n \\\"Initializable: contract is already initialized\\\"\\n );\\n\\n bool isTopLevelCall = !initializing;\\n if (isTopLevelCall) {\\n initializing = true;\\n initialized = true;\\n }\\n\\n _;\\n\\n if (isTopLevelCall) {\\n initializing = false;\\n }\\n }\\n\\n uint256[50] private ______gap;\\n}\\n\",\"keccak256\":\"0x50d39ebf38a3d3111f2b77a6c75ece1d4ae731552fec4697ab16fcf6c0d4d5e8\",\"license\":\"BUSL-1.1\"},\"contracts/utils/InitializableAbstractStrategy.sol\":{\"content\":\"// SPDX-License-Identifier: BUSL-1.1\\npragma solidity ^0.8.0;\\n\\n/**\\n * @title Base contract for vault strategies.\\n * @author Origin Protocol Inc\\n */\\nimport { IERC20 } from \\\"@openzeppelin/contracts/token/ERC20/IERC20.sol\\\";\\nimport { SafeERC20 } from \\\"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\\\";\\n\\nimport { Initializable } from \\\"../utils/Initializable.sol\\\";\\nimport { Governable } from \\\"../governance/Governable.sol\\\";\\nimport { IVault } from \\\"../interfaces/IVault.sol\\\";\\n\\nabstract contract InitializableAbstractStrategy is Initializable, Governable {\\n using SafeERC20 for IERC20;\\n\\n event PTokenAdded(address indexed _asset, address _pToken);\\n event PTokenRemoved(address indexed _asset, address _pToken);\\n event Deposit(address indexed _asset, address _pToken, uint256 _amount);\\n event Withdrawal(address indexed _asset, address _pToken, uint256 _amount);\\n event RewardTokenCollected(\\n address recipient,\\n address rewardToken,\\n uint256 amount\\n );\\n event RewardTokenAddressesUpdated(\\n address[] _oldAddresses,\\n address[] _newAddresses\\n );\\n event HarvesterAddressesUpdated(\\n address _oldHarvesterAddress,\\n address _newHarvesterAddress\\n );\\n\\n /// @notice Address of the underlying platform\\n address public immutable platformAddress;\\n /// @notice Address of the OToken vault\\n address public immutable vaultAddress;\\n\\n /// @dev Replaced with an immutable variable\\n // slither-disable-next-line constable-states\\n address private _deprecated_platformAddress;\\n\\n /// @dev Replaced with an immutable\\n // slither-disable-next-line constable-states\\n address private _deprecated_vaultAddress;\\n\\n /// @notice asset => pToken (Platform Specific Token Address)\\n mapping(address => address) public assetToPToken;\\n\\n /// @notice Full list of all assets supported by the strategy\\n address[] internal assetsMapped;\\n\\n // Deprecated: Reward token address\\n // slither-disable-next-line constable-states\\n address private _deprecated_rewardTokenAddress;\\n\\n // Deprecated: now resides in Harvester's rewardTokenConfigs\\n // slither-disable-next-line constable-states\\n uint256 private _deprecated_rewardLiquidationThreshold;\\n\\n /// @notice Address of the Harvester contract allowed to collect reward tokens\\n address public harvesterAddress;\\n\\n /// @notice Address of the reward tokens. eg CRV, BAL, CVX, AURA\\n address[] public rewardTokenAddresses;\\n\\n /* Reserved for future expansion. Used to be 100 storage slots\\n * and has decreased to accommodate:\\n * - harvesterAddress\\n * - rewardTokenAddresses\\n */\\n int256[98] private _reserved;\\n\\n struct BaseStrategyConfig {\\n address platformAddress; // Address of the underlying platform\\n address vaultAddress; // Address of the OToken's Vault\\n }\\n\\n /**\\n * @dev Verifies that the caller is the Governor or Strategist.\\n */\\n modifier onlyGovernorOrStrategist() virtual {\\n require(\\n isGovernor() || msg.sender == IVault(vaultAddress).strategistAddr(),\\n \\\"Caller is not the Strategist or Governor\\\"\\n );\\n _;\\n }\\n\\n /**\\n * @param _config The platform and OToken vault addresses\\n */\\n constructor(BaseStrategyConfig memory _config) {\\n platformAddress = _config.platformAddress;\\n vaultAddress = _config.vaultAddress;\\n }\\n\\n /**\\n * @dev Internal initialize function, to set up initial internal state\\n * @param _rewardTokenAddresses Address of reward token for platform\\n * @param _assets Addresses of initial supported assets\\n * @param _pTokens Platform Token corresponding addresses\\n */\\n function _initialize(\\n address[] memory _rewardTokenAddresses,\\n address[] memory _assets,\\n address[] memory _pTokens\\n ) internal {\\n rewardTokenAddresses = _rewardTokenAddresses;\\n\\n uint256 assetCount = _assets.length;\\n require(assetCount == _pTokens.length, \\\"Invalid input arrays\\\");\\n for (uint256 i = 0; i < assetCount; ++i) {\\n _setPTokenAddress(_assets[i], _pTokens[i]);\\n }\\n }\\n\\n /**\\n * @notice Collect accumulated reward token and send to Vault.\\n */\\n function collectRewardTokens() external virtual onlyHarvester nonReentrant {\\n _collectRewardTokens();\\n }\\n\\n /**\\n * @dev Default implementation that transfers reward tokens to the Harvester\\n * Implementing strategies need to add custom logic to collect the rewards.\\n */\\n function _collectRewardTokens() internal virtual {\\n uint256 rewardTokenCount = rewardTokenAddresses.length;\\n for (uint256 i = 0; i < rewardTokenCount; ++i) {\\n IERC20 rewardToken = IERC20(rewardTokenAddresses[i]);\\n uint256 balance = rewardToken.balanceOf(address(this));\\n if (balance > 0) {\\n emit RewardTokenCollected(\\n harvesterAddress,\\n address(rewardToken),\\n balance\\n );\\n rewardToken.safeTransfer(harvesterAddress, balance);\\n }\\n }\\n }\\n\\n /**\\n * @dev Verifies that the caller is the Vault.\\n */\\n modifier onlyVault() {\\n require(msg.sender == vaultAddress, \\\"Caller is not the Vault\\\");\\n _;\\n }\\n\\n /**\\n * @dev Verifies that the caller is the Harvester.\\n */\\n modifier onlyHarvester() {\\n require(msg.sender == harvesterAddress, \\\"Caller is not the Harvester\\\");\\n _;\\n }\\n\\n /**\\n * @dev Verifies that the caller is the Vault or Governor.\\n */\\n modifier onlyVaultOrGovernor() {\\n require(\\n msg.sender == vaultAddress || msg.sender == governor(),\\n \\\"Caller is not the Vault or Governor\\\"\\n );\\n _;\\n }\\n\\n /**\\n * @dev Verifies that the caller is the Vault, Governor, or Strategist.\\n */\\n modifier onlyVaultOrGovernorOrStrategist() {\\n require(\\n msg.sender == vaultAddress ||\\n msg.sender == governor() ||\\n msg.sender == IVault(vaultAddress).strategistAddr(),\\n \\\"Caller is not the Vault, Governor, or Strategist\\\"\\n );\\n _;\\n }\\n\\n /**\\n * @notice Set the reward token addresses. Any old addresses will be overwritten.\\n * @param _rewardTokenAddresses Array of reward token addresses\\n */\\n function setRewardTokenAddresses(address[] calldata _rewardTokenAddresses)\\n external\\n onlyGovernor\\n {\\n uint256 rewardTokenCount = _rewardTokenAddresses.length;\\n for (uint256 i = 0; i < rewardTokenCount; ++i) {\\n require(\\n _rewardTokenAddresses[i] != address(0),\\n \\\"Can not set an empty address as a reward token\\\"\\n );\\n }\\n\\n emit RewardTokenAddressesUpdated(\\n rewardTokenAddresses,\\n _rewardTokenAddresses\\n );\\n rewardTokenAddresses = _rewardTokenAddresses;\\n }\\n\\n /**\\n * @notice Get the reward token addresses.\\n * @return address[] the reward token addresses.\\n */\\n function getRewardTokenAddresses()\\n external\\n view\\n returns (address[] memory)\\n {\\n return rewardTokenAddresses;\\n }\\n\\n /**\\n * @notice Provide support for asset by passing its pToken address.\\n * This method can only be called by the system Governor\\n * @param _asset Address for the asset\\n * @param _pToken Address for the corresponding platform token\\n */\\n function setPTokenAddress(address _asset, address _pToken)\\n external\\n virtual\\n onlyGovernor\\n {\\n _setPTokenAddress(_asset, _pToken);\\n }\\n\\n /**\\n * @notice Remove a supported asset by passing its index.\\n * This method can only be called by the system Governor\\n * @param _assetIndex Index of the asset to be removed\\n */\\n function removePToken(uint256 _assetIndex) external virtual onlyGovernor {\\n require(_assetIndex < assetsMapped.length, \\\"Invalid index\\\");\\n address asset = assetsMapped[_assetIndex];\\n address pToken = assetToPToken[asset];\\n\\n if (_assetIndex < assetsMapped.length - 1) {\\n assetsMapped[_assetIndex] = assetsMapped[assetsMapped.length - 1];\\n }\\n assetsMapped.pop();\\n assetToPToken[asset] = address(0);\\n\\n emit PTokenRemoved(asset, pToken);\\n }\\n\\n /**\\n * @notice Provide support for asset by passing its pToken address.\\n * Add to internal mappings and execute the platform specific,\\n * abstract method `_abstractSetPToken`\\n * @param _asset Address for the asset\\n * @param _pToken Address for the corresponding platform token\\n */\\n function _setPTokenAddress(address _asset, address _pToken) internal {\\n require(assetToPToken[_asset] == address(0), \\\"pToken already set\\\");\\n require(\\n _asset != address(0) && _pToken != address(0),\\n \\\"Invalid addresses\\\"\\n );\\n\\n assetToPToken[_asset] = _pToken;\\n assetsMapped.push(_asset);\\n\\n emit PTokenAdded(_asset, _pToken);\\n\\n _abstractSetPToken(_asset, _pToken);\\n }\\n\\n /**\\n * @notice Transfer token to governor. Intended for recovering tokens stuck in\\n * strategy contracts, i.e. mistaken sends.\\n * @param _asset Address for the asset\\n * @param _amount Amount of the asset to transfer\\n */\\n function transferToken(address _asset, uint256 _amount)\\n public\\n virtual\\n onlyGovernor\\n {\\n require(!supportsAsset(_asset), \\\"Cannot transfer supported asset\\\");\\n IERC20(_asset).safeTransfer(governor(), _amount);\\n }\\n\\n /**\\n * @notice Set the Harvester contract that can collect rewards.\\n * @param _harvesterAddress Address of the harvester contract.\\n */\\n function setHarvesterAddress(address _harvesterAddress)\\n external\\n onlyGovernor\\n {\\n emit HarvesterAddressesUpdated(harvesterAddress, _harvesterAddress);\\n harvesterAddress = _harvesterAddress;\\n }\\n\\n /***************************************\\n Abstract\\n ****************************************/\\n\\n function _abstractSetPToken(address _asset, address _pToken)\\n internal\\n virtual;\\n\\n function safeApproveAllTokens() external virtual;\\n\\n /**\\n * @notice Deposit an amount of assets into the platform\\n * @param _asset Address for the asset\\n * @param _amount Units of asset to deposit\\n */\\n function deposit(address _asset, uint256 _amount) external virtual;\\n\\n /**\\n * @notice Deposit all supported assets in this strategy contract to the platform\\n */\\n function depositAll() external virtual;\\n\\n /**\\n * @notice Withdraw an `amount` of assets from the platform and\\n * send to the `_recipient`.\\n * @param _recipient Address to which the asset should be sent\\n * @param _asset Address of the asset\\n * @param _amount Units of asset to withdraw\\n */\\n function withdraw(\\n address _recipient,\\n address _asset,\\n uint256 _amount\\n ) external virtual;\\n\\n /**\\n * @notice Withdraw all supported assets from platform and\\n * sends to the OToken's Vault.\\n */\\n function withdrawAll() external virtual;\\n\\n /**\\n * @notice Get the total asset value held in the platform.\\n * This includes any interest that was generated since depositing.\\n * @param _asset Address of the asset\\n * @return balance Total value of the asset in the platform\\n */\\n function checkBalance(address _asset)\\n external\\n view\\n virtual\\n returns (uint256 balance);\\n\\n /**\\n * @notice Check if an asset is supported.\\n * @param _asset Address of the asset\\n * @return bool Whether asset is supported\\n */\\n function supportsAsset(address _asset) public view virtual returns (bool);\\n}\\n\",\"keccak256\":\"0x6e99dee31bac1365445ef297fead7e055eb7993cebce078e36b09474b2e0c035\",\"license\":\"BUSL-1.1\"},\"contracts/vault/VaultStorage.sol\":{\"content\":\"// SPDX-License-Identifier: BUSL-1.1\\npragma solidity ^0.8.0;\\n\\n/**\\n * @title OToken VaultStorage contract\\n * @notice The VaultStorage contract defines the storage for the Vault contracts\\n * @author Origin Protocol Inc\\n */\\n\\nimport { IERC20 } from \\\"@openzeppelin/contracts/token/ERC20/IERC20.sol\\\";\\nimport { SafeERC20 } from \\\"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\\\";\\nimport { Address } from \\\"@openzeppelin/contracts/utils/Address.sol\\\";\\n\\nimport { IStrategy } from \\\"../interfaces/IStrategy.sol\\\";\\nimport { IERC20Metadata } from \\\"@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol\\\";\\nimport { Governable } from \\\"../governance/Governable.sol\\\";\\nimport { OUSD } from \\\"../token/OUSD.sol\\\";\\nimport { Initializable } from \\\"../utils/Initializable.sol\\\";\\nimport \\\"../utils/Helpers.sol\\\";\\n\\nabstract contract VaultStorage is Initializable, Governable {\\n using SafeERC20 for IERC20;\\n\\n event AssetAllocated(address _asset, address _strategy, uint256 _amount);\\n event StrategyApproved(address _addr);\\n event StrategyRemoved(address _addr);\\n event Mint(address _addr, uint256 _value);\\n event Redeem(address _addr, uint256 _value);\\n event CapitalPaused();\\n event CapitalUnpaused();\\n event DefaultStrategyUpdated(address _strategy);\\n event RebasePaused();\\n event RebaseUnpaused();\\n event VaultBufferUpdated(uint256 _vaultBuffer);\\n event AllocateThresholdUpdated(uint256 _threshold);\\n event RebaseThresholdUpdated(uint256 _threshold);\\n event StrategistUpdated(address _address);\\n event MaxSupplyDiffChanged(uint256 maxSupplyDiff);\\n event YieldDistribution(address _to, uint256 _yield, uint256 _fee);\\n event TrusteeFeeBpsChanged(uint256 _basis);\\n event TrusteeAddressChanged(address _address);\\n event StrategyAddedToMintWhitelist(address indexed strategy);\\n event StrategyRemovedFromMintWhitelist(address indexed strategy);\\n event RebasePerSecondMaxChanged(uint256 rebaseRatePerSecond);\\n event DripDurationChanged(uint256 dripDuration);\\n event WithdrawalRequested(\\n address indexed _withdrawer,\\n uint256 indexed _requestId,\\n uint256 _amount,\\n uint256 _queued\\n );\\n event WithdrawalClaimed(\\n address indexed _withdrawer,\\n uint256 indexed _requestId,\\n uint256 _amount\\n );\\n event WithdrawalClaimable(uint256 _claimable, uint256 _newClaimable);\\n event WithdrawalClaimDelayUpdated(uint256 _newDelay);\\n\\n // Since we are proxy, all state should be uninitalized.\\n // Since this storage contract does not have logic directly on it\\n // we should not be checking for to see if these variables can be constant.\\n // slither-disable-start uninitialized-state\\n // slither-disable-start constable-states\\n\\n /// @dev mapping of supported vault assets to their configuration\\n uint256 private _deprecated_assets;\\n /// @dev list of all assets supported by the vault.\\n address[] private _deprecated_allAssets;\\n\\n // Strategies approved for use by the Vault\\n struct Strategy {\\n bool isSupported;\\n uint256 _deprecated; // Deprecated storage slot\\n }\\n /// @dev mapping of strategy contracts to their configuration\\n mapping(address => Strategy) public strategies;\\n /// @dev list of all vault strategies\\n address[] internal allStrategies;\\n\\n /// @notice Address of the Oracle price provider contract\\n address private _deprecated_priceProvider;\\n /// @notice pause rebasing if true\\n bool public rebasePaused;\\n /// @notice pause operations that change the OToken supply.\\n /// eg mint, redeem, allocate, mint/burn for strategy\\n bool public capitalPaused;\\n /// @notice Redemption fee in basis points. eg 50 = 0.5%\\n uint256 private _deprecated_redeemFeeBps;\\n /// @notice Percentage of assets to keep in Vault to handle (most) withdrawals. 100% = 1e18.\\n uint256 public vaultBuffer;\\n /// @notice OToken mints over this amount automatically allocate funds. 18 decimals.\\n uint256 public autoAllocateThreshold;\\n /// @notice OToken mints over this amount automatically rebase. 18 decimals.\\n uint256 public rebaseThreshold;\\n\\n /// @dev Address of the OToken token. eg OUSD or OETH.\\n OUSD public oToken;\\n\\n /// @dev Address of the contract responsible for post rebase syncs with AMMs\\n address private _deprecated_rebaseHooksAddr = address(0);\\n\\n /// @dev Deprecated: Address of Uniswap\\n address private _deprecated_uniswapAddr = address(0);\\n\\n /// @notice Address of the Strategist\\n address public strategistAddr = address(0);\\n\\n /// @notice Mapping of asset address to the Strategy that they should automatically\\n // be allocated to\\n uint256 private _deprecated_assetDefaultStrategies;\\n\\n /// @notice Max difference between total supply and total value of assets. 18 decimals.\\n uint256 public maxSupplyDiff;\\n\\n /// @notice Trustee contract that can collect a percentage of yield\\n address public trusteeAddress;\\n\\n /// @notice Amount of yield collected in basis points. eg 2000 = 20%\\n uint256 public trusteeFeeBps;\\n\\n /// @dev Deprecated: Tokens that should be swapped for stablecoins\\n address[] private _deprecated_swapTokens;\\n\\n /// @notice Metapool strategy that is allowed to mint/burn OTokens without changing collateral\\n\\n address private _deprecated_ousdMetaStrategy;\\n\\n /// @notice How much OTokens are currently minted by the strategy\\n int256 private _deprecated_netOusdMintedForStrategy;\\n\\n /// @notice How much net total OTokens are allowed to be minted by all strategies\\n uint256 private _deprecated_netOusdMintForStrategyThreshold;\\n\\n uint256 private _deprecated_swapConfig;\\n\\n // List of strategies that can mint oTokens directly\\n // Used in OETHBaseVaultCore\\n mapping(address => bool) public isMintWhitelistedStrategy;\\n\\n /// @notice Address of the Dripper contract that streams harvested rewards to the Vault\\n /// @dev The vault is proxied so needs to be set with setDripper against the proxy contract.\\n address private _deprecated_dripper;\\n\\n /// Withdrawal Queue Storage /////\\n\\n struct WithdrawalQueueMetadata {\\n // cumulative total of all withdrawal requests included the ones that have already been claimed\\n uint128 queued;\\n // cumulative total of all the requests that can be claimed including the ones that have already been claimed\\n uint128 claimable;\\n // total of all the requests that have been claimed\\n uint128 claimed;\\n // index of the next withdrawal request starting at 0\\n uint128 nextWithdrawalIndex;\\n }\\n\\n /// @notice Global metadata for the withdrawal queue including:\\n /// queued - cumulative total of all withdrawal requests included the ones that have already been claimed\\n /// claimable - cumulative total of all the requests that can be claimed including the ones already claimed\\n /// claimed - total of all the requests that have been claimed\\n /// nextWithdrawalIndex - index of the next withdrawal request starting at 0\\n WithdrawalQueueMetadata public withdrawalQueueMetadata;\\n\\n struct WithdrawalRequest {\\n address withdrawer;\\n bool claimed;\\n uint40 timestamp; // timestamp of the withdrawal request\\n // Amount of oTokens to redeem. eg OETH\\n uint128 amount;\\n // cumulative total of all withdrawal requests including this one.\\n // this request can be claimed when this queued amount is less than or equal to the queue's claimable amount.\\n uint128 queued;\\n }\\n\\n /// @notice Mapping of withdrawal request indices to the user withdrawal request data\\n mapping(uint256 => WithdrawalRequest) public withdrawalRequests;\\n\\n /// @notice Sets a minimum delay that is required to elapse between\\n /// requesting async withdrawals and claiming the request.\\n /// When set to 0 async withdrawals are disabled.\\n uint256 public withdrawalClaimDelay;\\n\\n /// @notice Time in seconds that the vault last rebased yield.\\n uint64 public lastRebase;\\n\\n /// @notice Automatic rebase yield calculations. In seconds. Set to 0 or 1 to disable.\\n uint64 public dripDuration;\\n\\n /// @notice max rebase percentage per second\\n /// Can be used to set maximum yield of the protocol,\\n /// spreading out yield over time\\n uint64 public rebasePerSecondMax;\\n\\n /// @notice target rebase rate limit, based on past rates and funds available.\\n uint64 public rebasePerSecondTarget;\\n\\n uint256 internal constant MAX_REBASE = 0.02 ether;\\n uint256 internal constant MAX_REBASE_PER_SECOND =\\n uint256(0.05 ether) / 1 days;\\n\\n /// @notice Default strategy for asset\\n address public defaultStrategy;\\n\\n // For future use\\n uint256[42] private __gap;\\n\\n /// @notice Index of WETH asset in allAssets array\\n /// Legacy OETHVaultCore code, relocated here for vault consistency.\\n uint256 private _deprecated_wethAssetIndex;\\n\\n /// @dev Address of the asset (eg. WETH or USDC)\\n address public immutable asset;\\n uint8 internal immutable assetDecimals;\\n\\n // slither-disable-end constable-states\\n // slither-disable-end uninitialized-state\\n\\n constructor(address _asset) {\\n uint8 _decimals = IERC20Metadata(_asset).decimals();\\n require(_decimals <= 18, \\\"invalid asset decimals\\\");\\n asset = _asset;\\n assetDecimals = _decimals;\\n }\\n\\n /// @notice Deprecated: use `oToken()` instead.\\n function oUSD() external view returns (OUSD) {\\n return oToken;\\n }\\n}\\n\",\"keccak256\":\"0xcca0e0ebbbbda50b23fba7aea96a1e34f6a9d58c8f2e86e84b0911d4a9e5d418\",\"license\":\"BUSL-1.1\"}},\"version\":1}", + "bytecode": "0x61018060405234801561001157600080fd5b50604051614c14380380614c148339810160408190526100309161060d565b6080810151829082906001600160a01b03166100935760405162461bcd60e51b815260206004820152601460248201527f496e76616c69642055534443206164647265737300000000000000000000000060448201526064015b60405180910390fd5b60a08101516001600160a01b03166100ed5760405162461bcd60e51b815260206004820152601960248201527f496e76616c696420706565722055534443206164647265737300000000000000604482015260640161008a565b80516001600160a01b031661013a5760405162461bcd60e51b8152602060048201526013602482015272496e76616c6964204343545020636f6e66696760681b604482015260640161008a565b60208101516001600160a01b031661018a5760405162461bcd60e51b8152602060048201526013602482015272496e76616c6964204343545020636f6e66696760681b604482015260640161008a565b60608101516001600160a01b03166101e45760405162461bcd60e51b815260206004820152601d60248201527f496e76616c696420706565722073747261746567792061646472657373000000604482015260640161008a565b60208101516001600160a01b0390811660809081528251821660a052604083015163ffffffff166101005260608301518216610120528201805190911660c0525160009061023190610410565b9050600061024883608001516104f060201b60201c565b90508160061461029a5760405162461bcd60e51b815260206004820152601d60248201527f4261736520746f6b656e20646563696d616c73206d7573742062652036000000604482015260640161008a565b604051635553444360e01b602082015260240160405160208183030381529060405280519060200120816040516020016102d4919061070e565b60405160208183030381529060405280519060200120146103375760405162461bcd60e51b815260206004820152601960248201527f546f6b656e2073796d626f6c206d757374206265205553444300000000000000604482015260640161008a565b505060a001516001600160a01b0390811660e0528151811661014052602090910151811661016052825116156103af5760405162461bcd60e51b815260206004820152601860248201527f496e76616c696420706c6174666f726d20616464726573730000000000000000604482015260640161008a565b60208201516001600160a01b03166104095760405162461bcd60e51b815260206004820152601560248201527f496e76616c6964205661756c7420616464726573730000000000000000000000604482015260640161008a565b50506107e1565b600080826001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015610451573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610475919061072a565b60ff1690506004811015801561048c575060128111155b6104ea5760405162461bcd60e51b815260206004820152602960248201527f546f6b656e206d75737420686176652073756666696369656e7420646563696d604482015268616c20706c6163657360b81b606482015260840161008a565b92915050565b60606000826001600160a01b03166395d89b416040518163ffffffff1660e01b8152600401600060405180830381865afa158015610532573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f1916820160405261055a919081019061074d565b9392505050565b634e487b7160e01b600052604160045260246000fd5b604080519081016001600160401b038111828210171561059957610599610561565b60405290565b60405160c081016001600160401b038111828210171561059957610599610561565b604051601f8201601f191681016001600160401b03811182821017156105e9576105e9610561565b604052919050565b80516001600160a01b038116811461060857600080fd5b919050565b60008082840361010081121561062257600080fd5b604081121561063057600080fd5b610638610577565b610641856105f1565b815261064f602086016105f1565b6020820152925060c0603f198201121561066857600080fd5b5061067161059f565b61067d604085016105f1565b815261068b606085016105f1565b6020820152608084015163ffffffff811681146106a757600080fd5b60408201526106b860a085016105f1565b60608201526106c960c085016105f1565b60808201526106da60e085016105f1565b60a0820152809150509250929050565b60005b838110156107055781810151838201526020016106ed565b50506000910152565b600082516107208184602087016106ea565b9190910192915050565b60006020828403121561073c57600080fd5b815160ff8116811461055a57600080fd5b60006020828403121561075f57600080fd5b81516001600160401b0381111561077557600080fd5b8201601f8101841361078657600080fd5b80516001600160401b0381111561079f5761079f610561565b6107b2601f8201601f19166020016105c1565b8181528560208385010111156107c757600080fd5b6107d88260208301602086016106ea565b95945050505050565b60805160a05160c05160e051610100516101205161014051610160516142a861096c6000396000818161040a01528181610a6d015281816113cb015281816119f301528181611a6801528181611b06015261263f015260006106d10152600081816104bb01528181610e3b01528181611edb01528181612dd801528181612eb4015281816130b2015261316801526000818161054a01528181610bcd01528181611e6701528181612db601528181612e9201528181613090015261314601526000818161051b0152610cf601526000818161032d01528181610605015281816107ac01528181611167015281816111d10152818161150d01528181611b9101528181611c1501528181612108015281816125540152818161261d01528181612672015281816127cd0152818161296801528181612ce201528181612dfd0152612ed60152600081816105d301528181610c4f01528181612d040152612d860152600081816105860152818161084601528181610edf01528181611295015261306201526142a86000f3fe608060405234801561001057600080fd5b506004361061028a5760003560e01c80637b2d9b2c1161015c578063b3ab15fb116100ce578063d9caed1211610087578063d9caed12146106b9578063dbe55e56146106cc578063de5f6268146106f3578063ed42412b146106fb578063f6ca71b014610704578063fc1b31131461071957600080fd5b8063b3ab15fb1461063d578063ba1066ed14610650578063c2e1e3f414610659578063c7af33521461066c578063d38bfff414610674578063d7dd614b1461068757600080fd5b80638c73eb04116101205780638c73eb04146105815780639136616a146105a857806396d538bb146105bb5780639748cf7c146105ce578063aa388af6146105f5578063ad1728cb1461063557600080fd5b80637b2d9b2c146104f05780637c92f219146105035780637ddfce2d14610516578063853828b61461053d5780638949f6f31461054557600080fd5b806342c8c11c116102005780635a063f63116101b95780635a063f63146104805780635a236417146104885780635d36b1901461049b5780635f515226146104a35780636369e49f146104b657806367c7066c146104dd57600080fd5b806342c8c11c146103f2578063430bf08a1461040557806347e7ef241461042c5780635324df321461043f578063564a515814610453578063570ca7351461046657600080fd5b806311eac8551161025257806311eac8551461032857806316a0f2501461034f5780631801c408146103705780631c4b995a1461039b57806323c9ab3d146103ae5780633c3dbbc7146103da57600080fd5b80630c340a241461028f5780630ed57b3a146102b45780630fc3b4c4146102c95780631072cbea146102f257806311cffb6714610305575b600080fd5b610297610726565b6040516001600160a01b0390911681526020015b60405180910390f35b6102c76102c2366004613931565b610743565b005b6102976102d736600461396a565b6067602052600090815260409020546001600160a01b031681565b6102c7610300366004613987565b61077e565b610318610313366004613a6f565b610839565b60405190151581526020016102ab565b6102977f000000000000000000000000000000000000000000000000000000000000000081565b60335461035d9061ffff1681565b60405161ffff90911681526020016102ab565b603354600160201b90046001600160401b031660009081526034602052604090205460ff1615610318565b6102c76103a9366004613ae8565b61091f565b6103186103bc366004613b42565b6001600160401b031660009081526034602052604090205460ff1690565b6103e4620f424081565b6040519081526020016102ab565b6102c7610400366004613b5f565b610a32565b6102977f000000000000000000000000000000000000000000000000000000000000000081565b6102c761043a366004613987565b610a62565b60335461035d9062010000900461ffff1681565b6102c7610461366004613b7a565b610af3565b60335461029790600160601b90046001600160a01b031681565b6102c7610ffd565b6102c7610496366004613b5f565b611090565b6102c76110bd565b6103e46104b136600461396a565b611163565b6102977f000000000000000000000000000000000000000000000000000000000000000081565b606b54610297906001600160a01b031681565b6102976104fe366004613be1565b61125e565b610318610511366004613a6f565b611288565b6102977f000000000000000000000000000000000000000000000000000000000000000081565b6102c76113c0565b61056c7f000000000000000000000000000000000000000000000000000000000000000081565b60405163ffffffff90911681526020016102ab565b6102977f000000000000000000000000000000000000000000000000000000000000000081565b6102c76105b6366004613be1565b611553565b6102c76105c9366004613bfa565b61171e565b6102977f000000000000000000000000000000000000000000000000000000000000000081565b61031861060336600461396a565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0390811691161490565b6102c7611835565b6102c761064b36600461396a565b611859565b6103e460d05481565b6102c761066736600461396a565b611886565b610318611913565b6102c761068236600461396a565b611944565b6033546106a190600160201b90046001600160401b031681565b6040516001600160401b0390911681526020016102ab565b6102c76106c7366004613c6f565b6119e8565b6102977f000000000000000000000000000000000000000000000000000000000000000081565b6102c7611afb565b6103e460cf5481565b61070c611c3a565b6040516102ab9190613cb0565b6103e46509184e72a00081565b600061073e6000805160206142538339815191525490565b905090565b61074b611913565b6107705760405162461bcd60e51b815260040161076790613cfc565b60405180910390fd5b61077a8282611c9c565b5050565b610786611913565b6107a25760405162461bcd60e51b815260040161076790613cfc565b6001600160a01b037f000000000000000000000000000000000000000000000000000000000000000081169083160361081d5760405162461bcd60e51b815260206004820152601f60248201527f43616e6e6f74207472616e7366657220737570706f72746564206173736574006044820152606401610767565b61077a610828610726565b6001600160a01b0384169083611dfb565b6000336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146108b35760405162461bcd60e51b815260206004820152601e60248201527f43616c6c6572206973206e6f742043435450207472616e736d697474657200006044820152606401610767565b6107d08363ffffffff16101561090b5760405162461bcd60e51b815260206004820152601a60248201527f46696e616c697479207468726573686f6c6420746f6f206c6f770000000000006044820152606401610767565b610916858584611e63565b95945050505050565b610927611913565b6109435760405162461bcd60e51b815260040161076790613cfc565b600054610100900460ff168061095c575060005460ff16155b6109bf5760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b6064820152608401610767565b600054610100900460ff161580156109e1576000805461ffff19166101011790555b6109ec848484611f5c565b6040805160008082526020820181815282840191825260608301909352909190610a17838383611fb1565b5050508015610a2c576000805461ff00191690555b50505050565b610a3a611913565b610a565760405162461bcd60e51b815260040161076790613cfc565b610a5f81612064565b50565b336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614610aaa5760405162461bcd60e51b815260040161076790613d33565b60008051602061423383398151915280546001198101610adc5760405162461bcd60e51b815260040161076790613d6a565b60028255610aea8484612106565b50600190555050565b603354600160601b90046001600160a01b03163314610b545760405162461bcd60e51b815260206004820152601a60248201527f43616c6c6572206973206e6f7420746865204f70657261746f720000000000006044820152606401610767565b6000806000806000610b65876122c4565b94509450945094509450600163ffffffff168563ffffffff1614610bcb5760405162461bcd60e51b815260206004820152601c60248201527f496e76616c69642043435450206d6573736167652076657273696f6e000000006044820152606401610767565b7f000000000000000000000000000000000000000000000000000000000000000063ffffffff168463ffffffff1614610c3e5760405162461bcd60e51b81526020600482015260156024820152742ab735b737bbb71029b7bab931b2902237b6b0b4b760591b6044820152606401610767565b610c4981600061231a565b945060007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316846001600160a01b03161490508015610d8a578563ffffffff166001148015610ca25750815160e411155b610ce55760405162461bcd60e51b8152602060048201526014602482015273496e76616c6964206275726e206d65737361676560601b6044820152606401610767565b6000610cf2836004612339565b90507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316816001600160a01b031614610d6a5760405162461bcd60e51b815260206004820152601260248201527124b73b30b634b210313ab937103a37b5b2b760711b6044820152606401610767565b610d75836064612339565b9450610d82836024612339565b935050610de1565b63ffffffff86166103f214610de15760405162461bcd60e51b815260206004820152601b60248201527f556e737570706f72746564206d6573736167652076657273696f6e00000000006044820152606401610767565b306001600160a01b03841614610e395760405162461bcd60e51b815260206004820152601c60248201527f556e657870656374656420726563697069656e742061646472657373000000006044820152606401610767565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316846001600160a01b031614610ec55760405162461bcd60e51b815260206004820152602260248201527f496e636f72726563742073656e6465722f726563697069656e74206164647265604482015261737360f01b6064820152608401610767565b604051630afd9fa560e31b81526000906001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016906357ecfd2890610f16908c908c90600401613de2565b6020604051808303816000875af1158015610f35573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f599190613e17565b905080610fa15760405162461bcd60e51b8152602060048201526016602482015275149958d95a5d99481b595cdcd859d94819985a5b195960521b6044820152606401610767565b8115610ff2578251600090610fba90859060e490612353565b90506000610fc98560446124ad565b90506000610fd88660a46124ad565b9050610fee610fe78284613e48565b82856124c7565b5050505b505050505050505050565b606b546001600160a01b031633146110575760405162461bcd60e51b815260206004820152601b60248201527f43616c6c6572206973206e6f74207468652048617276657374657200000000006044820152606401610767565b600080516020614233833981519152805460011981016110895760405162461bcd60e51b815260040161076790613d6a565b5060019055565b611098611913565b6110b45760405162461bcd60e51b815260040161076790613cfc565b610a5f816126cf565b7f44c4d30b2eaad5130ad70c3ba6972730566f3e6359ab83e800d905c61b1c51db546001600160a01b0316336001600160a01b0316146111585760405162461bcd60e51b815260206004820152603060248201527f4f6e6c79207468652070656e64696e6720476f7665726e6f722063616e20636f60448201526f6d706c6574652074686520636c61696d60801b6064820152608401610767565b6111613361276c565b565b60007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316826001600160a01b0316146111b65760405162461bcd60e51b815260040161076790613e5b565b60cf5460d0546040516370a0823160e01b81523060048201527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906370a0823190602401602060405180830381865afa158015611220573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906112449190613e86565b61124e9190613e9f565b6112589190613e9f565b92915050565b606c818154811061126e57600080fd5b6000918252602090912001546001600160a01b0316905081565b6000336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146113025760405162461bcd60e51b815260206004820152601e60248201527f43616c6c6572206973206e6f742043435450207472616e736d697474657200006044820152606401610767565b60335461ffff166103e8146113685760405162461bcd60e51b815260206004820152602660248201527f556e66696e616c697a6564206d6573736167657320617265206e6f74207375706044820152651c1bdc9d195960d21b6064820152608401610767565b6103e88363ffffffff16101561090b5760405162461bcd60e51b815260206004820152601a60248201527f46696e616c697479207468726573686f6c6420746f6f206c6f770000000000006044820152606401610767565b336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016148061140f57506113fa610726565b6001600160a01b0316336001600160a01b0316145b6114675760405162461bcd60e51b815260206004820152602360248201527f43616c6c6572206973206e6f7420746865205661756c74206f7220476f7665726044820152623737b960e91b6064820152608401610767565b600080516020614233833981519152805460011981016114995760405162461bcd60e51b815260040161076790613d6a565b60028255603354600160201b90046001600160401b031660009081526034602052604090205460ff166114f4576040517ff49d67e0c10520c98c98114270c479ce28ba2e7bef7908bb15914eb5a3b6165190600090a1611089565b60cf54620f42408110156115085750611089565b61154b7f00000000000000000000000000000000000000000000000000000000000000006509184e72a000831161153f57826127cb565b6509184e72a0006127cb565b505060019055565b61155b611913565b6115775760405162461bcd60e51b815260040161076790613cfc565b60685481106115b85760405162461bcd60e51b815260206004820152600d60248201526c092dcecc2d8d2c840d2dcc8caf609b1b6044820152606401610767565b6000606882815481106115cd576115cd613eb2565b60009182526020808320909101546001600160a01b0390811680845260679092526040909220546068549193509091169061160a90600190613e48565b83101561168c576068805461162190600190613e48565b8154811061163157611631613eb2565b600091825260209091200154606880546001600160a01b03909216918590811061165d5761165d613eb2565b9060005260206000200160006101000a8154816001600160a01b0302191690836001600160a01b031602179055505b606880548061169d5761169d613ec8565b60008281526020808220600019908401810180546001600160a01b031990811690915593019093556001600160a01b038581168083526067855260409283902080549094169093559051908416815290917f16b7600acff27e39a8a96056b3d533045298de927507f5c1d97e4accde60488c910160405180910390a2505050565b611726611913565b6117425760405162461bcd60e51b815260040161076790613cfc565b8060005b818110156117ec57600084848381811061176257611762613eb2565b9050602002016020810190611777919061396a565b6001600160a01b0316036117e45760405162461bcd60e51b815260206004820152602e60248201527f43616e206e6f742073657420616e20656d70747920616464726573732061732060448201526d30903932bbb0b932103a37b5b2b760911b6064820152608401610767565b600101611746565b507f04c0b9649497d316554306e53678d5f5f5dbc3a06f97dec13ff4cfe98b986bbc606c848460405161182193929190613ede565b60405180910390a1610a2c606c848461384f565b61183d611913565b6110575760405162461bcd60e51b815260040161076790613cfc565b611861611913565b61187d5760405162461bcd60e51b815260040161076790613cfc565b610a5f816129c1565b61188e611913565b6118aa5760405162461bcd60e51b815260040161076790613cfc565b606b54604080516001600160a01b03928316815291831660208301527fe48386b84419f4d36e0f96c10cc3510b6fb1a33795620c5098b22472bbe90796910160405180910390a1606b80546001600160a01b0319166001600160a01b0392909216919091179055565b600061192b6000805160206142538339815191525490565b6001600160a01b0316336001600160a01b031614905090565b61194c611913565b6119685760405162461bcd60e51b815260040161076790613cfc565b611990817f44c4d30b2eaad5130ad70c3ba6972730566f3e6359ab83e800d905c61b1c51db55565b806001600160a01b03166119b06000805160206142538339815191525490565b6001600160a01b03167fa39cc5eb22d0f34d8beaefee8a3f17cc229c1a1d1ef87a5ad47313487b1c4f0d60405160405180910390a350565b336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614611a305760405162461bcd60e51b815260040161076790613d33565b60008051602061423383398151915280546001198101611a625760405162461bcd60e51b815260040161076790613d6a565b600282557f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316856001600160a01b031614611ae75760405162461bcd60e51b815260206004820152601760248201527f4f6e6c79205661756c742063616e2077697468647261770000000000000000006044820152606401610767565b611af184846127cb565b5060019055505050565b336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614611b435760405162461bcd60e51b815260040161076790613d33565b60008051602061423383398151915280546001198101611b755760405162461bcd60e51b815260040161076790613d6a565b600282556040516370a0823160e01b81523060048201526000907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906370a0823190602401602060405180830381865afa158015611be0573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611c049190613e86565b9050620f4240811061154b5761154b7f000000000000000000000000000000000000000000000000000000000000000082612106565b6060606c805480602002602001604051908101604052809291908181526020018280548015611c9257602002820191906000526020600020905b81546001600160a01b03168152600190910190602001808311611c74575b5050505050905090565b6001600160a01b038281166000908152606760205260409020541615611cf95760405162461bcd60e51b81526020600482015260126024820152711c151bdad95b88185b1c9958591e481cd95d60721b6044820152606401610767565b6001600160a01b03821615801590611d1957506001600160a01b03811615155b611d595760405162461bcd60e51b8152602060048201526011602482015270496e76616c69642061646472657373657360781b6044820152606401610767565b6001600160a01b03828116600081815260676020908152604080832080549587166001600160a01b031996871681179091556068805460018101825594527fa2153420d844928b4421650203c77babc8b33d7f2e7b450e2966db0c2209775390930180549095168417909455925190815290917fef6485b84315f9b1483beffa32aae9a0596890395e3d7521f1c5fbb51790e765910160405180910390a25050565b6040516001600160a01b038316602482015260448101829052611e5e90849063a9059cbb60e01b906064015b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b031990931692909217909152612a1c565b505050565b60007f000000000000000000000000000000000000000000000000000000000000000063ffffffff168463ffffffff1614611ed85760405162461bcd60e51b81526020600482015260156024820152742ab735b737bbb71029b7bab931b2902237b6b0b4b760591b6044820152606401610767565b827f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0390811690821614611f465760405162461bcd60e51b815260206004820152600e60248201526d2ab735b737bbb71029b2b73232b960911b6044820152606401610767565b611f4f83612aee565b60019150505b9392505050565b611f65836129c1565b611f6e826126cf565b611f7781612064565b5050600080525060346020527f2dc2afdad33a5feea586a9545052327b65d28efb10d11fa69e77da986a1031cd805460ff19166001179055565b8251611fc490606c9060208601906138b2565b5081518151811461200e5760405162461bcd60e51b8152602060048201526014602482015273496e76616c696420696e7075742061727261797360601b6044820152606401610767565b60005b8181101561205d5761205584828151811061202e5761202e613eb2565b602002602001015184838151811061204857612048613eb2565b6020026020010151611c9c565b600101612011565b5050505050565b610bb88161ffff1611156120b15760405162461bcd60e51b815260206004820152601460248201527308ccaca40e0e4cadad2eada40e8dede40d0d2ced60631b6044820152606401610767565b6033805463ffff000019166201000061ffff8416908102919091179091556040519081527f938c72cb9ba014c7d4d26f156021299f8926dee2aab1421b32a2408a8261cf60906020015b60405180910390a150565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316826001600160a01b0316146121575760405162461bcd60e51b815260040161076790613e5b565b60d054156121a75760405162461bcd60e51b815260206004820152601960248201527f556e65787065637465642070656e64696e6720616d6f756e74000000000000006044820152606401610767565b620f42408110156121fa5760405162461bcd60e51b815260206004820152601860248201527f4465706f73697420616d6f756e7420746f6f20736d616c6c00000000000000006044820152606401610767565b6509184e72a0008111156122505760405162461bcd60e51b815260206004820152601760248201527f4465706f73697420616d6f756e7420746f6f20686967680000000000000000006044820152606401610767565b600061225a612b4c565b60d08390559050600061226d8284612c2b565b90506122798382612c87565b604080516001600160a01b03861680825260208201869052917f5548c837ab068cf56a2c2479df0882a4922fd203edb7517321831d95078c5f6291015b60405180910390a250505050565b600080808060606122d5868361231a565b94506122e286600461231a565b93506122ef86602c612339565b92506122fc86604c612339565b865190925061230f908790609490612353565b905091939590929450565b6000611f55612334848461232f600482613e9f565b612353565b612f13565b6000611f5561234e848461232f602082613e9f565b612f49565b60608282101561239b5760405162461bcd60e51b8152602060048201526013602482015272496e76616c696420736c6963652072616e676560681b6044820152606401610767565b83518211156123ec5760405162461bcd60e51b815260206004820152601d60248201527f536c69636520656e6420657863656564732064617461206c656e6774680000006044820152606401610767565b60006123f88484613e48565b90506000816001600160401b03811115612414576124146139cc565b6040519080825280601f01601f19166020018201604052801561243e576020820181803683370190505b50905060005b828110156124a357866124578288613e9f565b8151811061246757612467613eb2565b602001015160f81c60f81b82828151811061248457612484613eb2565b60200101906001600160f81b031916908160001a905350600101612444565b5095945050505050565b6000611f556124c2848461232f602082613e9f565b612f80565b603354600160201b90046001600160401b031660008181526034602052604090205460ff16156125335760405162461bcd60e51b8152602060048201526017602482015276139bdb98d948185b1c9958591e481c1c9bd8d95cdcd959604a1b6044820152606401610767565b61253c82612aee565b6040516370a0823160e01b81523060048201526000907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906370a0823190602401602060405180830381865afa1580156125a3573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906125c79190613e86565b9050848110156126105760405162461bcd60e51b8152602060048201526014602482015273496e73756666696369656e742062616c616e636560601b6044820152606401610767565b6126646001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000167f000000000000000000000000000000000000000000000000000000000000000083611dfb565b604080516001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001680825260208201849052917f2717ead6b9200dd235aad468c9809ea400fe33ac69b5bfaa6d3e90fc922b6398910160405180910390a25050505050565b8061ffff166103e814806126e857508061ffff166107d0145b6127285760405162461bcd60e51b8152602060048201526011602482015270125b9d985b1a59081d1a1c995cda1bdb19607a1b6044820152606401610767565b6033805461ffff191661ffff83169081179091556040519081527fe8fbd074d54232549e55ac463e02d202c16b1023e5d6aae276b3d8391b8c14bc906020016120fb565b6001600160a01b0381166127c25760405162461bcd60e51b815260206004820152601a60248201527f4e657720476f7665726e6f7220697320616464726573732830290000000000006044820152606401610767565b610a5f81612fb7565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316826001600160a01b03161461281c5760405162461bcd60e51b815260040161076790613e5b565b620f424081101561286f5760405162461bcd60e51b815260206004820152601960248201527f576974686472617720616d6f756e7420746f6f20736d616c6c000000000000006044820152606401610767565b60cf548111156128d95760405162461bcd60e51b815260206004820152602f60248201527f576974686472617720616d6f756e7420657863656564732072656d6f7465207360448201526e747261746567792062616c616e636560881b6064820152608401610767565b6509184e72a0008111156129435760405162461bcd60e51b815260206004820152602b60248201527f576974686472617720616d6f756e742065786365656473206d6178207472616e60448201526a1cd9995c88185b5bdd5b9d60aa1b6064820152608401610767565b600061294d612b4c565b9050600061295b828461301e565b905061296681613048565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03167ff7774b688d56120b783560a913ee60792a73dfd511812b7be5eccf10d08c6689846040516122b691815260200190565b603380546bffffffffffffffffffffffff16600160601b6001600160a01b038416908102919091179091556040519081527f4721129e0e676ed6a92909bb24e853ccdd63ad72280cc2e974e38e480e0e6e54906020016120fb565b6000612a71826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b03166131969092919063ffffffff16565b805190915015611e5e5780806020019051810190612a8f9190613e17565b611e5e5760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b6064820152608401610767565b6003612af9826131ad565b63ffffffff1603612b0d57610a5f816131ba565b60405162461bcd60e51b8152602060048201526014602482015273556e6b6e6f776e206d657373616765207479706560601b6044820152606401610767565b603354600160201b90046001600160401b031660008181526034602052604081205490919060ff16612bb95760405162461bcd60e51b81526020600482015260166024820152752832b73234b733903a37b5b2b7103a3930b739b332b960511b6044820152606401610767565b612bc4816001613f79565b603380546bffffffffffffffff000000001916600160201b6001600160401b038416908102919091179091556040519081529091507fc328b88555c22b57c1f2678e88ad3d9982d056ca6b05be82546e7b6bc6fda2db9060200160405180910390a1919050565b604080516001600160401b03841660208201529081018290526060906103f29060019083015b60408051601f1981840301815290829052612c70939291602001613f98565b604051602081830303815290604052905092915050565b6509184e72a000821115612cd55760405162461bcd60e51b81526020600482015260156024820152740a8ded6cadc40c2dadeeadce840e8dede40d0d2ced605b1b6044820152606401610767565b612d296001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000167f000000000000000000000000000000000000000000000000000000000000000084613328565b60335460009062010000900461ffff16612d44576000612d69565b60335461271090612d5f9062010000900461ffff1685613fdd565b612d699190613ff4565b60335460405163779b432d60e01b81529192506001600160a01b037f000000000000000000000000000000000000000000000000000000000000000081169263779b432d92612e329288927f0000000000000000000000000000000000000000000000000000000000000000927f0000000000000000000000000000000000000000000000000000000000000000909216917f00000000000000000000000000000000000000000000000000000000000000009183918a9161ffff909116908c90600401614016565b600060405180830381600087803b158015612e4c57600080fd5b505af1158015612e60573d6000803e3d6000fd5b50506033546040517f149007d4a77eb98d041de59e6ca469bd27b351b74f16c4d0648978322a1b8a779350612f0692507f0000000000000000000000000000000000000000000000000000000000000000917f0000000000000000000000000000000000000000000000000000000000000000917f0000000000000000000000000000000000000000000000000000000000000000918991889161ffff16908a90614077565b60405180910390a1505050565b60008151600414612f365760405162461bcd60e51b8152600401610767906140d2565b60e0612f41836140ff565b901c92915050565b60008151602014612f6c5760405162461bcd60e51b8152600401610767906140d2565b818060200190518101906112589190614126565b60008151602014612fa35760405162461bcd60e51b8152600401610767906140d2565b818060200190518101906112589190613e86565b806001600160a01b0316612fd76000805160206142538339815191525490565b6001600160a01b03167fc7c0c772add429241571afb3805861fb3cfa2af374534088b76cdb4325a87e9a60405160405180910390a360008051602061425383398151915255565b604080516001600160401b03841660208201529081018290526060906103f2906002908301612c51565b6033546040516314b157ab60e01b81526001600160a01b037f00000000000000000000000000000000000000000000000000000000000000008116926314b157ab926130e6927f0000000000000000000000000000000000000000000000000000000000000000927f000000000000000000000000000000000000000000000000000000000000000090911691829161ffff16908890600401614143565b600060405180830381600087803b15801561310057600080fd5b505af1158015613114573d6000803e3d6000fd5b50506033546040517f90d92e80437532a834bc16642d235a7c34efc4d5690f3f6e69794c6d8c5c729093506120fb92507f0000000000000000000000000000000000000000000000000000000000000000917f00000000000000000000000000000000000000000000000000000000000000009161ffff90911690869061417a565b60606131a5848460008561343d565b949350505050565b600061125882600461231a565b6000806000806131c985613565565b603354939750919550935091506001600160401b03600160201b909104811690851681146131f957505050505050565b603354600160201b90046001600160401b031660009081526034602052604090205460ff1615801561329557831561323e57613234866135ab565b600060d0556132f3565b604080516001600160401b0388168152602081018590526000918101919091527fb9bc68243850860710a5d53a1a72d81fc5caf0cf30620ed544cc098bd07dbd51906060015b60405180910390a150505050505050565b6132a26201518084613e9f565b4211156132f357604080516001600160401b0388168152602081018590526001918101919091527fb9bc68243850860710a5d53a1a72d81fc5caf0cf30620ed544cc098bd07dbd5190606001613284565b60cf8590556040518581527f1cc993845fe77d504bd0e3391d651586e84db1cefbcdd9b4f85e8ed1c1f13c1090602001613284565b8015806133a25750604051636eb1769f60e11b81523060048201526001600160a01b03838116602483015284169063dd62ed3e90604401602060405180830381865afa15801561337c573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906133a09190613e86565b155b61340d5760405162461bcd60e51b815260206004820152603660248201527f5361666545524332303a20617070726f76652066726f6d206e6f6e2d7a65726f60448201527520746f206e6f6e2d7a65726f20616c6c6f77616e636560501b6064820152608401610767565b6040516001600160a01b038316602482015260448101829052611e5e90849063095ea7b360e01b90606401611e27565b60608247101561349e5760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f6044820152651c8818d85b1b60d21b6064820152608401610767565b843b6134ec5760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610767565b600080866001600160a01b0316858760405161350891906141bc565b60006040518083038185875af1925050503d8060008114613545576040519150601f19603f3d011682016040523d82523d6000602084013e61354a565b606091505b509150915061355a828286613738565b979650505050505050565b600080600080613576856003613771565b6000806000806135858961382a565b80602001905181019061359891906141d8565b929c919b50995090975095505050505050565b6033546001600160401b03600160201b90910481169082168111156136025760405162461bcd60e51b815260206004820152600d60248201526c4e6f6e636520746f6f206c6f7760981b6044820152606401610767565b6001600160401b03821660009081526034602052604090205460ff16156136655760405162461bcd60e51b8152602060048201526017602482015276139bdb98d948185b1c9958591e481c1c9bd8d95cdcd959604a1b6044820152606401610767565b6001600160401b038216600081815260346020908152604091829020805460ff1916600117905590519182527f8aa30b3e46076ef0187550969c01982aeb9af5db2eada56ab253a53e4c9946e9910160405180910390a1806001600160401b0316826001600160401b03161461077a57603380546bffffffffffffffff000000001916600160201b6001600160401b038516908102919091179091556040519081527fc328b88555c22b57c1f2678e88ad3d9982d056ca6b05be82546e7b6bc6fda2db9060200160405180910390a15050565b60608315613747575081611f55565b8251156137575782518084602001fd5b8160405162461bcd60e51b8152600401610767919061421f565b6103f261377d83613843565b63ffffffff16146137d05760405162461bcd60e51b815260206004820152601e60248201527f496e76616c6964204f726967696e204d6573736167652056657273696f6e00006044820152606401610767565b8063ffffffff166137e0836131ad565b63ffffffff161461077a5760405162461bcd60e51b8152602060048201526014602482015273496e76616c6964204d657373616765207479706560601b6044820152606401610767565b606061125860088351846123539092919063ffffffff16565b6000611258828261231a565b8280548282559060005260206000209081019282156138a2579160200282015b828111156138a25781546001600160a01b0319166001600160a01b0384351617825560209092019160019091019061386f565b506138ae929150613907565b5090565b8280548282559060005260206000209081019282156138a2579160200282015b828111156138a257825182546001600160a01b0319166001600160a01b039091161782556020909201916001909101906138d2565b5b808211156138ae5760008155600101613908565b6001600160a01b0381168114610a5f57600080fd5b6000806040838503121561394457600080fd5b823561394f8161391c565b9150602083013561395f8161391c565b809150509250929050565b60006020828403121561397c57600080fd5b8135611f558161391c565b6000806040838503121561399a57600080fd5b82356139a58161391c565b946020939093013593505050565b803563ffffffff811681146139c757600080fd5b919050565b634e487b7160e01b600052604160045260246000fd5b600082601f8301126139f357600080fd5b81356001600160401b03811115613a0c57613a0c6139cc565b604051601f8201601f19908116603f011681016001600160401b0381118282101715613a3a57613a3a6139cc565b604052818152838201602001851015613a5257600080fd5b816020850160208301376000918101602001919091529392505050565b60008060008060808587031215613a8557600080fd5b613a8e856139b3565b935060208501359250613aa3604086016139b3565b915060608501356001600160401b03811115613abe57600080fd5b613aca878288016139e2565b91505092959194509250565b803561ffff811681146139c757600080fd5b600080600060608486031215613afd57600080fd5b8335613b088161391c565b9250613b1660208501613ad6565b9150613b2460408501613ad6565b90509250925092565b6001600160401b0381168114610a5f57600080fd5b600060208284031215613b5457600080fd5b8135611f5581613b2d565b600060208284031215613b7157600080fd5b611f5582613ad6565b60008060408385031215613b8d57600080fd5b82356001600160401b03811115613ba357600080fd5b613baf858286016139e2565b92505060208301356001600160401b03811115613bcb57600080fd5b613bd7858286016139e2565b9150509250929050565b600060208284031215613bf357600080fd5b5035919050565b60008060208385031215613c0d57600080fd5b82356001600160401b03811115613c2357600080fd5b8301601f81018513613c3457600080fd5b80356001600160401b03811115613c4a57600080fd5b8560208260051b8401011115613c5f57600080fd5b6020919091019590945092505050565b600080600060608486031215613c8457600080fd5b8335613c8f8161391c565b92506020840135613c9f8161391c565b929592945050506040919091013590565b602080825282518282018190526000918401906040840190835b81811015613cf15783516001600160a01b0316835260209384019390920191600101613cca565b509095945050505050565b6020808252601a908201527f43616c6c6572206973206e6f742074686520476f7665726e6f72000000000000604082015260600190565b60208082526017908201527f43616c6c6572206973206e6f7420746865205661756c74000000000000000000604082015260600190565b6020808252600e908201526d1499595b9d1c985b9d0818d85b1b60921b604082015260600190565b60005b83811015613dad578181015183820152602001613d95565b50506000910152565b60008151808452613dce816020860160208601613d92565b601f01601f19169290920160200192915050565b604081526000613df56040830185613db6565b82810360208401526109168185613db6565b805180151581146139c757600080fd5b600060208284031215613e2957600080fd5b611f5582613e07565b634e487b7160e01b600052601160045260246000fd5b8181038181111561125857611258613e32565b602080825260119082015270155b9cdd5c1c1bdc9d195908185cdcd95d607a1b604082015260600190565b600060208284031215613e9857600080fd5b5051919050565b8082018082111561125857611258613e32565b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052603160045260246000fd5b6040808252845490820181905260008581526020812090916060840190835b81811015613f245783546001600160a01b0316835260019384019360209093019201613efd565b50508381036020808601919091528582520190508460005b85811015613f6d578135613f4f8161391c565b6001600160a01b031683526020928301929190910190600101613f3c565b50909695505050505050565b6001600160401b03818116838216019081111561125857611258613e32565b63ffffffff60e01b8460e01b16815263ffffffff60e01b8360e01b16600482015260008251613fce816008850160208701613d92565b91909101600801949350505050565b808202811582820484141761125857611258613e32565b60008261401157634e487b7160e01b600052601260045260246000fd5b500490565b88815263ffffffff8816602082015286604082015260018060a01b03861660608201528460808201528360a082015263ffffffff831660c082015261010060e08201526000614069610100830184613db6565b9a9950505050505050505050565b63ffffffff88811682526001600160a01b038881166020840152871660408301526060820186905260808201859052831660a082015260e060c082018190526000906140c590830184613db6565b9998505050505050505050565b602080825260139082015272092dcecc2d8d2c840c8c2e8c240d8cadccee8d606b1b604082015260600190565b80516020808301519190811015614120576000198160200360031b1b821691505b50919050565b60006020828403121561413857600080fd5b8151611f558161391c565b63ffffffff8616815284602082015283604082015263ffffffff8316606082015260a06080820152600061355a60a0830184613db6565b63ffffffff85811682526001600160a01b0385166020830152831660408201526080606082018190526000906141b290830184613db6565b9695505050505050565b600082516141ce818460208701613d92565b9190910192915050565b600080600080608085870312156141ee57600080fd5b84516141f981613b2d565b6020860151909450925061420f60408601613e07565b6060959095015193969295505050565b602081526000611f556020830184613db656fe53bf423e48ed90e97d02ab0ebab13b2a235a6bfbe9c321847d5c175333ac45357bea13895fa79d2831e0a9e28edede30099005a50d652d8957cf8a607ee6ca4aa2646970667358221220f9e4003bc05a0917ebba798c964944fadf1dc0e546995b8d06b1544dadc5345b64736f6c634300081c0033", + "deployedBytecode": "0x608060405234801561001057600080fd5b506004361061028a5760003560e01c80637b2d9b2c1161015c578063b3ab15fb116100ce578063d9caed1211610087578063d9caed12146106b9578063dbe55e56146106cc578063de5f6268146106f3578063ed42412b146106fb578063f6ca71b014610704578063fc1b31131461071957600080fd5b8063b3ab15fb1461063d578063ba1066ed14610650578063c2e1e3f414610659578063c7af33521461066c578063d38bfff414610674578063d7dd614b1461068757600080fd5b80638c73eb04116101205780638c73eb04146105815780639136616a146105a857806396d538bb146105bb5780639748cf7c146105ce578063aa388af6146105f5578063ad1728cb1461063557600080fd5b80637b2d9b2c146104f05780637c92f219146105035780637ddfce2d14610516578063853828b61461053d5780638949f6f31461054557600080fd5b806342c8c11c116102005780635a063f63116101b95780635a063f63146104805780635a236417146104885780635d36b1901461049b5780635f515226146104a35780636369e49f146104b657806367c7066c146104dd57600080fd5b806342c8c11c146103f2578063430bf08a1461040557806347e7ef241461042c5780635324df321461043f578063564a515814610453578063570ca7351461046657600080fd5b806311eac8551161025257806311eac8551461032857806316a0f2501461034f5780631801c408146103705780631c4b995a1461039b57806323c9ab3d146103ae5780633c3dbbc7146103da57600080fd5b80630c340a241461028f5780630ed57b3a146102b45780630fc3b4c4146102c95780631072cbea146102f257806311cffb6714610305575b600080fd5b610297610726565b6040516001600160a01b0390911681526020015b60405180910390f35b6102c76102c2366004613931565b610743565b005b6102976102d736600461396a565b6067602052600090815260409020546001600160a01b031681565b6102c7610300366004613987565b61077e565b610318610313366004613a6f565b610839565b60405190151581526020016102ab565b6102977f000000000000000000000000000000000000000000000000000000000000000081565b60335461035d9061ffff1681565b60405161ffff90911681526020016102ab565b603354600160201b90046001600160401b031660009081526034602052604090205460ff1615610318565b6102c76103a9366004613ae8565b61091f565b6103186103bc366004613b42565b6001600160401b031660009081526034602052604090205460ff1690565b6103e4620f424081565b6040519081526020016102ab565b6102c7610400366004613b5f565b610a32565b6102977f000000000000000000000000000000000000000000000000000000000000000081565b6102c761043a366004613987565b610a62565b60335461035d9062010000900461ffff1681565b6102c7610461366004613b7a565b610af3565b60335461029790600160601b90046001600160a01b031681565b6102c7610ffd565b6102c7610496366004613b5f565b611090565b6102c76110bd565b6103e46104b136600461396a565b611163565b6102977f000000000000000000000000000000000000000000000000000000000000000081565b606b54610297906001600160a01b031681565b6102976104fe366004613be1565b61125e565b610318610511366004613a6f565b611288565b6102977f000000000000000000000000000000000000000000000000000000000000000081565b6102c76113c0565b61056c7f000000000000000000000000000000000000000000000000000000000000000081565b60405163ffffffff90911681526020016102ab565b6102977f000000000000000000000000000000000000000000000000000000000000000081565b6102c76105b6366004613be1565b611553565b6102c76105c9366004613bfa565b61171e565b6102977f000000000000000000000000000000000000000000000000000000000000000081565b61031861060336600461396a565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0390811691161490565b6102c7611835565b6102c761064b36600461396a565b611859565b6103e460d05481565b6102c761066736600461396a565b611886565b610318611913565b6102c761068236600461396a565b611944565b6033546106a190600160201b90046001600160401b031681565b6040516001600160401b0390911681526020016102ab565b6102c76106c7366004613c6f565b6119e8565b6102977f000000000000000000000000000000000000000000000000000000000000000081565b6102c7611afb565b6103e460cf5481565b61070c611c3a565b6040516102ab9190613cb0565b6103e46509184e72a00081565b600061073e6000805160206142538339815191525490565b905090565b61074b611913565b6107705760405162461bcd60e51b815260040161076790613cfc565b60405180910390fd5b61077a8282611c9c565b5050565b610786611913565b6107a25760405162461bcd60e51b815260040161076790613cfc565b6001600160a01b037f000000000000000000000000000000000000000000000000000000000000000081169083160361081d5760405162461bcd60e51b815260206004820152601f60248201527f43616e6e6f74207472616e7366657220737570706f72746564206173736574006044820152606401610767565b61077a610828610726565b6001600160a01b0384169083611dfb565b6000336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146108b35760405162461bcd60e51b815260206004820152601e60248201527f43616c6c6572206973206e6f742043435450207472616e736d697474657200006044820152606401610767565b6107d08363ffffffff16101561090b5760405162461bcd60e51b815260206004820152601a60248201527f46696e616c697479207468726573686f6c6420746f6f206c6f770000000000006044820152606401610767565b610916858584611e63565b95945050505050565b610927611913565b6109435760405162461bcd60e51b815260040161076790613cfc565b600054610100900460ff168061095c575060005460ff16155b6109bf5760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b6064820152608401610767565b600054610100900460ff161580156109e1576000805461ffff19166101011790555b6109ec848484611f5c565b6040805160008082526020820181815282840191825260608301909352909190610a17838383611fb1565b5050508015610a2c576000805461ff00191690555b50505050565b610a3a611913565b610a565760405162461bcd60e51b815260040161076790613cfc565b610a5f81612064565b50565b336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614610aaa5760405162461bcd60e51b815260040161076790613d33565b60008051602061423383398151915280546001198101610adc5760405162461bcd60e51b815260040161076790613d6a565b60028255610aea8484612106565b50600190555050565b603354600160601b90046001600160a01b03163314610b545760405162461bcd60e51b815260206004820152601a60248201527f43616c6c6572206973206e6f7420746865204f70657261746f720000000000006044820152606401610767565b6000806000806000610b65876122c4565b94509450945094509450600163ffffffff168563ffffffff1614610bcb5760405162461bcd60e51b815260206004820152601c60248201527f496e76616c69642043435450206d6573736167652076657273696f6e000000006044820152606401610767565b7f000000000000000000000000000000000000000000000000000000000000000063ffffffff168463ffffffff1614610c3e5760405162461bcd60e51b81526020600482015260156024820152742ab735b737bbb71029b7bab931b2902237b6b0b4b760591b6044820152606401610767565b610c4981600061231a565b945060007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316846001600160a01b03161490508015610d8a578563ffffffff166001148015610ca25750815160e411155b610ce55760405162461bcd60e51b8152602060048201526014602482015273496e76616c6964206275726e206d65737361676560601b6044820152606401610767565b6000610cf2836004612339565b90507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316816001600160a01b031614610d6a5760405162461bcd60e51b815260206004820152601260248201527124b73b30b634b210313ab937103a37b5b2b760711b6044820152606401610767565b610d75836064612339565b9450610d82836024612339565b935050610de1565b63ffffffff86166103f214610de15760405162461bcd60e51b815260206004820152601b60248201527f556e737570706f72746564206d6573736167652076657273696f6e00000000006044820152606401610767565b306001600160a01b03841614610e395760405162461bcd60e51b815260206004820152601c60248201527f556e657870656374656420726563697069656e742061646472657373000000006044820152606401610767565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316846001600160a01b031614610ec55760405162461bcd60e51b815260206004820152602260248201527f496e636f72726563742073656e6465722f726563697069656e74206164647265604482015261737360f01b6064820152608401610767565b604051630afd9fa560e31b81526000906001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016906357ecfd2890610f16908c908c90600401613de2565b6020604051808303816000875af1158015610f35573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f599190613e17565b905080610fa15760405162461bcd60e51b8152602060048201526016602482015275149958d95a5d99481b595cdcd859d94819985a5b195960521b6044820152606401610767565b8115610ff2578251600090610fba90859060e490612353565b90506000610fc98560446124ad565b90506000610fd88660a46124ad565b9050610fee610fe78284613e48565b82856124c7565b5050505b505050505050505050565b606b546001600160a01b031633146110575760405162461bcd60e51b815260206004820152601b60248201527f43616c6c6572206973206e6f74207468652048617276657374657200000000006044820152606401610767565b600080516020614233833981519152805460011981016110895760405162461bcd60e51b815260040161076790613d6a565b5060019055565b611098611913565b6110b45760405162461bcd60e51b815260040161076790613cfc565b610a5f816126cf565b7f44c4d30b2eaad5130ad70c3ba6972730566f3e6359ab83e800d905c61b1c51db546001600160a01b0316336001600160a01b0316146111585760405162461bcd60e51b815260206004820152603060248201527f4f6e6c79207468652070656e64696e6720476f7665726e6f722063616e20636f60448201526f6d706c6574652074686520636c61696d60801b6064820152608401610767565b6111613361276c565b565b60007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316826001600160a01b0316146111b65760405162461bcd60e51b815260040161076790613e5b565b60cf5460d0546040516370a0823160e01b81523060048201527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906370a0823190602401602060405180830381865afa158015611220573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906112449190613e86565b61124e9190613e9f565b6112589190613e9f565b92915050565b606c818154811061126e57600080fd5b6000918252602090912001546001600160a01b0316905081565b6000336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146113025760405162461bcd60e51b815260206004820152601e60248201527f43616c6c6572206973206e6f742043435450207472616e736d697474657200006044820152606401610767565b60335461ffff166103e8146113685760405162461bcd60e51b815260206004820152602660248201527f556e66696e616c697a6564206d6573736167657320617265206e6f74207375706044820152651c1bdc9d195960d21b6064820152608401610767565b6103e88363ffffffff16101561090b5760405162461bcd60e51b815260206004820152601a60248201527f46696e616c697479207468726573686f6c6420746f6f206c6f770000000000006044820152606401610767565b336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016148061140f57506113fa610726565b6001600160a01b0316336001600160a01b0316145b6114675760405162461bcd60e51b815260206004820152602360248201527f43616c6c6572206973206e6f7420746865205661756c74206f7220476f7665726044820152623737b960e91b6064820152608401610767565b600080516020614233833981519152805460011981016114995760405162461bcd60e51b815260040161076790613d6a565b60028255603354600160201b90046001600160401b031660009081526034602052604090205460ff166114f4576040517ff49d67e0c10520c98c98114270c479ce28ba2e7bef7908bb15914eb5a3b6165190600090a1611089565b60cf54620f42408110156115085750611089565b61154b7f00000000000000000000000000000000000000000000000000000000000000006509184e72a000831161153f57826127cb565b6509184e72a0006127cb565b505060019055565b61155b611913565b6115775760405162461bcd60e51b815260040161076790613cfc565b60685481106115b85760405162461bcd60e51b815260206004820152600d60248201526c092dcecc2d8d2c840d2dcc8caf609b1b6044820152606401610767565b6000606882815481106115cd576115cd613eb2565b60009182526020808320909101546001600160a01b0390811680845260679092526040909220546068549193509091169061160a90600190613e48565b83101561168c576068805461162190600190613e48565b8154811061163157611631613eb2565b600091825260209091200154606880546001600160a01b03909216918590811061165d5761165d613eb2565b9060005260206000200160006101000a8154816001600160a01b0302191690836001600160a01b031602179055505b606880548061169d5761169d613ec8565b60008281526020808220600019908401810180546001600160a01b031990811690915593019093556001600160a01b038581168083526067855260409283902080549094169093559051908416815290917f16b7600acff27e39a8a96056b3d533045298de927507f5c1d97e4accde60488c910160405180910390a2505050565b611726611913565b6117425760405162461bcd60e51b815260040161076790613cfc565b8060005b818110156117ec57600084848381811061176257611762613eb2565b9050602002016020810190611777919061396a565b6001600160a01b0316036117e45760405162461bcd60e51b815260206004820152602e60248201527f43616e206e6f742073657420616e20656d70747920616464726573732061732060448201526d30903932bbb0b932103a37b5b2b760911b6064820152608401610767565b600101611746565b507f04c0b9649497d316554306e53678d5f5f5dbc3a06f97dec13ff4cfe98b986bbc606c848460405161182193929190613ede565b60405180910390a1610a2c606c848461384f565b61183d611913565b6110575760405162461bcd60e51b815260040161076790613cfc565b611861611913565b61187d5760405162461bcd60e51b815260040161076790613cfc565b610a5f816129c1565b61188e611913565b6118aa5760405162461bcd60e51b815260040161076790613cfc565b606b54604080516001600160a01b03928316815291831660208301527fe48386b84419f4d36e0f96c10cc3510b6fb1a33795620c5098b22472bbe90796910160405180910390a1606b80546001600160a01b0319166001600160a01b0392909216919091179055565b600061192b6000805160206142538339815191525490565b6001600160a01b0316336001600160a01b031614905090565b61194c611913565b6119685760405162461bcd60e51b815260040161076790613cfc565b611990817f44c4d30b2eaad5130ad70c3ba6972730566f3e6359ab83e800d905c61b1c51db55565b806001600160a01b03166119b06000805160206142538339815191525490565b6001600160a01b03167fa39cc5eb22d0f34d8beaefee8a3f17cc229c1a1d1ef87a5ad47313487b1c4f0d60405160405180910390a350565b336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614611a305760405162461bcd60e51b815260040161076790613d33565b60008051602061423383398151915280546001198101611a625760405162461bcd60e51b815260040161076790613d6a565b600282557f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316856001600160a01b031614611ae75760405162461bcd60e51b815260206004820152601760248201527f4f6e6c79205661756c742063616e2077697468647261770000000000000000006044820152606401610767565b611af184846127cb565b5060019055505050565b336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614611b435760405162461bcd60e51b815260040161076790613d33565b60008051602061423383398151915280546001198101611b755760405162461bcd60e51b815260040161076790613d6a565b600282556040516370a0823160e01b81523060048201526000907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906370a0823190602401602060405180830381865afa158015611be0573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611c049190613e86565b9050620f4240811061154b5761154b7f000000000000000000000000000000000000000000000000000000000000000082612106565b6060606c805480602002602001604051908101604052809291908181526020018280548015611c9257602002820191906000526020600020905b81546001600160a01b03168152600190910190602001808311611c74575b5050505050905090565b6001600160a01b038281166000908152606760205260409020541615611cf95760405162461bcd60e51b81526020600482015260126024820152711c151bdad95b88185b1c9958591e481cd95d60721b6044820152606401610767565b6001600160a01b03821615801590611d1957506001600160a01b03811615155b611d595760405162461bcd60e51b8152602060048201526011602482015270496e76616c69642061646472657373657360781b6044820152606401610767565b6001600160a01b03828116600081815260676020908152604080832080549587166001600160a01b031996871681179091556068805460018101825594527fa2153420d844928b4421650203c77babc8b33d7f2e7b450e2966db0c2209775390930180549095168417909455925190815290917fef6485b84315f9b1483beffa32aae9a0596890395e3d7521f1c5fbb51790e765910160405180910390a25050565b6040516001600160a01b038316602482015260448101829052611e5e90849063a9059cbb60e01b906064015b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b031990931692909217909152612a1c565b505050565b60007f000000000000000000000000000000000000000000000000000000000000000063ffffffff168463ffffffff1614611ed85760405162461bcd60e51b81526020600482015260156024820152742ab735b737bbb71029b7bab931b2902237b6b0b4b760591b6044820152606401610767565b827f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0390811690821614611f465760405162461bcd60e51b815260206004820152600e60248201526d2ab735b737bbb71029b2b73232b960911b6044820152606401610767565b611f4f83612aee565b60019150505b9392505050565b611f65836129c1565b611f6e826126cf565b611f7781612064565b5050600080525060346020527f2dc2afdad33a5feea586a9545052327b65d28efb10d11fa69e77da986a1031cd805460ff19166001179055565b8251611fc490606c9060208601906138b2565b5081518151811461200e5760405162461bcd60e51b8152602060048201526014602482015273496e76616c696420696e7075742061727261797360601b6044820152606401610767565b60005b8181101561205d5761205584828151811061202e5761202e613eb2565b602002602001015184838151811061204857612048613eb2565b6020026020010151611c9c565b600101612011565b5050505050565b610bb88161ffff1611156120b15760405162461bcd60e51b815260206004820152601460248201527308ccaca40e0e4cadad2eada40e8dede40d0d2ced60631b6044820152606401610767565b6033805463ffff000019166201000061ffff8416908102919091179091556040519081527f938c72cb9ba014c7d4d26f156021299f8926dee2aab1421b32a2408a8261cf60906020015b60405180910390a150565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316826001600160a01b0316146121575760405162461bcd60e51b815260040161076790613e5b565b60d054156121a75760405162461bcd60e51b815260206004820152601960248201527f556e65787065637465642070656e64696e6720616d6f756e74000000000000006044820152606401610767565b620f42408110156121fa5760405162461bcd60e51b815260206004820152601860248201527f4465706f73697420616d6f756e7420746f6f20736d616c6c00000000000000006044820152606401610767565b6509184e72a0008111156122505760405162461bcd60e51b815260206004820152601760248201527f4465706f73697420616d6f756e7420746f6f20686967680000000000000000006044820152606401610767565b600061225a612b4c565b60d08390559050600061226d8284612c2b565b90506122798382612c87565b604080516001600160a01b03861680825260208201869052917f5548c837ab068cf56a2c2479df0882a4922fd203edb7517321831d95078c5f6291015b60405180910390a250505050565b600080808060606122d5868361231a565b94506122e286600461231a565b93506122ef86602c612339565b92506122fc86604c612339565b865190925061230f908790609490612353565b905091939590929450565b6000611f55612334848461232f600482613e9f565b612353565b612f13565b6000611f5561234e848461232f602082613e9f565b612f49565b60608282101561239b5760405162461bcd60e51b8152602060048201526013602482015272496e76616c696420736c6963652072616e676560681b6044820152606401610767565b83518211156123ec5760405162461bcd60e51b815260206004820152601d60248201527f536c69636520656e6420657863656564732064617461206c656e6774680000006044820152606401610767565b60006123f88484613e48565b90506000816001600160401b03811115612414576124146139cc565b6040519080825280601f01601f19166020018201604052801561243e576020820181803683370190505b50905060005b828110156124a357866124578288613e9f565b8151811061246757612467613eb2565b602001015160f81c60f81b82828151811061248457612484613eb2565b60200101906001600160f81b031916908160001a905350600101612444565b5095945050505050565b6000611f556124c2848461232f602082613e9f565b612f80565b603354600160201b90046001600160401b031660008181526034602052604090205460ff16156125335760405162461bcd60e51b8152602060048201526017602482015276139bdb98d948185b1c9958591e481c1c9bd8d95cdcd959604a1b6044820152606401610767565b61253c82612aee565b6040516370a0823160e01b81523060048201526000907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906370a0823190602401602060405180830381865afa1580156125a3573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906125c79190613e86565b9050848110156126105760405162461bcd60e51b8152602060048201526014602482015273496e73756666696369656e742062616c616e636560601b6044820152606401610767565b6126646001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000167f000000000000000000000000000000000000000000000000000000000000000083611dfb565b604080516001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001680825260208201849052917f2717ead6b9200dd235aad468c9809ea400fe33ac69b5bfaa6d3e90fc922b6398910160405180910390a25050505050565b8061ffff166103e814806126e857508061ffff166107d0145b6127285760405162461bcd60e51b8152602060048201526011602482015270125b9d985b1a59081d1a1c995cda1bdb19607a1b6044820152606401610767565b6033805461ffff191661ffff83169081179091556040519081527fe8fbd074d54232549e55ac463e02d202c16b1023e5d6aae276b3d8391b8c14bc906020016120fb565b6001600160a01b0381166127c25760405162461bcd60e51b815260206004820152601a60248201527f4e657720476f7665726e6f7220697320616464726573732830290000000000006044820152606401610767565b610a5f81612fb7565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316826001600160a01b03161461281c5760405162461bcd60e51b815260040161076790613e5b565b620f424081101561286f5760405162461bcd60e51b815260206004820152601960248201527f576974686472617720616d6f756e7420746f6f20736d616c6c000000000000006044820152606401610767565b60cf548111156128d95760405162461bcd60e51b815260206004820152602f60248201527f576974686472617720616d6f756e7420657863656564732072656d6f7465207360448201526e747261746567792062616c616e636560881b6064820152608401610767565b6509184e72a0008111156129435760405162461bcd60e51b815260206004820152602b60248201527f576974686472617720616d6f756e742065786365656473206d6178207472616e60448201526a1cd9995c88185b5bdd5b9d60aa1b6064820152608401610767565b600061294d612b4c565b9050600061295b828461301e565b905061296681613048565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03167ff7774b688d56120b783560a913ee60792a73dfd511812b7be5eccf10d08c6689846040516122b691815260200190565b603380546bffffffffffffffffffffffff16600160601b6001600160a01b038416908102919091179091556040519081527f4721129e0e676ed6a92909bb24e853ccdd63ad72280cc2e974e38e480e0e6e54906020016120fb565b6000612a71826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b03166131969092919063ffffffff16565b805190915015611e5e5780806020019051810190612a8f9190613e17565b611e5e5760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b6064820152608401610767565b6003612af9826131ad565b63ffffffff1603612b0d57610a5f816131ba565b60405162461bcd60e51b8152602060048201526014602482015273556e6b6e6f776e206d657373616765207479706560601b6044820152606401610767565b603354600160201b90046001600160401b031660008181526034602052604081205490919060ff16612bb95760405162461bcd60e51b81526020600482015260166024820152752832b73234b733903a37b5b2b7103a3930b739b332b960511b6044820152606401610767565b612bc4816001613f79565b603380546bffffffffffffffff000000001916600160201b6001600160401b038416908102919091179091556040519081529091507fc328b88555c22b57c1f2678e88ad3d9982d056ca6b05be82546e7b6bc6fda2db9060200160405180910390a1919050565b604080516001600160401b03841660208201529081018290526060906103f29060019083015b60408051601f1981840301815290829052612c70939291602001613f98565b604051602081830303815290604052905092915050565b6509184e72a000821115612cd55760405162461bcd60e51b81526020600482015260156024820152740a8ded6cadc40c2dadeeadce840e8dede40d0d2ced605b1b6044820152606401610767565b612d296001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000167f000000000000000000000000000000000000000000000000000000000000000084613328565b60335460009062010000900461ffff16612d44576000612d69565b60335461271090612d5f9062010000900461ffff1685613fdd565b612d699190613ff4565b60335460405163779b432d60e01b81529192506001600160a01b037f000000000000000000000000000000000000000000000000000000000000000081169263779b432d92612e329288927f0000000000000000000000000000000000000000000000000000000000000000927f0000000000000000000000000000000000000000000000000000000000000000909216917f00000000000000000000000000000000000000000000000000000000000000009183918a9161ffff909116908c90600401614016565b600060405180830381600087803b158015612e4c57600080fd5b505af1158015612e60573d6000803e3d6000fd5b50506033546040517f149007d4a77eb98d041de59e6ca469bd27b351b74f16c4d0648978322a1b8a779350612f0692507f0000000000000000000000000000000000000000000000000000000000000000917f0000000000000000000000000000000000000000000000000000000000000000917f0000000000000000000000000000000000000000000000000000000000000000918991889161ffff16908a90614077565b60405180910390a1505050565b60008151600414612f365760405162461bcd60e51b8152600401610767906140d2565b60e0612f41836140ff565b901c92915050565b60008151602014612f6c5760405162461bcd60e51b8152600401610767906140d2565b818060200190518101906112589190614126565b60008151602014612fa35760405162461bcd60e51b8152600401610767906140d2565b818060200190518101906112589190613e86565b806001600160a01b0316612fd76000805160206142538339815191525490565b6001600160a01b03167fc7c0c772add429241571afb3805861fb3cfa2af374534088b76cdb4325a87e9a60405160405180910390a360008051602061425383398151915255565b604080516001600160401b03841660208201529081018290526060906103f2906002908301612c51565b6033546040516314b157ab60e01b81526001600160a01b037f00000000000000000000000000000000000000000000000000000000000000008116926314b157ab926130e6927f0000000000000000000000000000000000000000000000000000000000000000927f000000000000000000000000000000000000000000000000000000000000000090911691829161ffff16908890600401614143565b600060405180830381600087803b15801561310057600080fd5b505af1158015613114573d6000803e3d6000fd5b50506033546040517f90d92e80437532a834bc16642d235a7c34efc4d5690f3f6e69794c6d8c5c729093506120fb92507f0000000000000000000000000000000000000000000000000000000000000000917f00000000000000000000000000000000000000000000000000000000000000009161ffff90911690869061417a565b60606131a5848460008561343d565b949350505050565b600061125882600461231a565b6000806000806131c985613565565b603354939750919550935091506001600160401b03600160201b909104811690851681146131f957505050505050565b603354600160201b90046001600160401b031660009081526034602052604090205460ff1615801561329557831561323e57613234866135ab565b600060d0556132f3565b604080516001600160401b0388168152602081018590526000918101919091527fb9bc68243850860710a5d53a1a72d81fc5caf0cf30620ed544cc098bd07dbd51906060015b60405180910390a150505050505050565b6132a26201518084613e9f565b4211156132f357604080516001600160401b0388168152602081018590526001918101919091527fb9bc68243850860710a5d53a1a72d81fc5caf0cf30620ed544cc098bd07dbd5190606001613284565b60cf8590556040518581527f1cc993845fe77d504bd0e3391d651586e84db1cefbcdd9b4f85e8ed1c1f13c1090602001613284565b8015806133a25750604051636eb1769f60e11b81523060048201526001600160a01b03838116602483015284169063dd62ed3e90604401602060405180830381865afa15801561337c573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906133a09190613e86565b155b61340d5760405162461bcd60e51b815260206004820152603660248201527f5361666545524332303a20617070726f76652066726f6d206e6f6e2d7a65726f60448201527520746f206e6f6e2d7a65726f20616c6c6f77616e636560501b6064820152608401610767565b6040516001600160a01b038316602482015260448101829052611e5e90849063095ea7b360e01b90606401611e27565b60608247101561349e5760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f6044820152651c8818d85b1b60d21b6064820152608401610767565b843b6134ec5760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610767565b600080866001600160a01b0316858760405161350891906141bc565b60006040518083038185875af1925050503d8060008114613545576040519150601f19603f3d011682016040523d82523d6000602084013e61354a565b606091505b509150915061355a828286613738565b979650505050505050565b600080600080613576856003613771565b6000806000806135858961382a565b80602001905181019061359891906141d8565b929c919b50995090975095505050505050565b6033546001600160401b03600160201b90910481169082168111156136025760405162461bcd60e51b815260206004820152600d60248201526c4e6f6e636520746f6f206c6f7760981b6044820152606401610767565b6001600160401b03821660009081526034602052604090205460ff16156136655760405162461bcd60e51b8152602060048201526017602482015276139bdb98d948185b1c9958591e481c1c9bd8d95cdcd959604a1b6044820152606401610767565b6001600160401b038216600081815260346020908152604091829020805460ff1916600117905590519182527f8aa30b3e46076ef0187550969c01982aeb9af5db2eada56ab253a53e4c9946e9910160405180910390a1806001600160401b0316826001600160401b03161461077a57603380546bffffffffffffffff000000001916600160201b6001600160401b038516908102919091179091556040519081527fc328b88555c22b57c1f2678e88ad3d9982d056ca6b05be82546e7b6bc6fda2db9060200160405180910390a15050565b60608315613747575081611f55565b8251156137575782518084602001fd5b8160405162461bcd60e51b8152600401610767919061421f565b6103f261377d83613843565b63ffffffff16146137d05760405162461bcd60e51b815260206004820152601e60248201527f496e76616c6964204f726967696e204d6573736167652056657273696f6e00006044820152606401610767565b8063ffffffff166137e0836131ad565b63ffffffff161461077a5760405162461bcd60e51b8152602060048201526014602482015273496e76616c6964204d657373616765207479706560601b6044820152606401610767565b606061125860088351846123539092919063ffffffff16565b6000611258828261231a565b8280548282559060005260206000209081019282156138a2579160200282015b828111156138a25781546001600160a01b0319166001600160a01b0384351617825560209092019160019091019061386f565b506138ae929150613907565b5090565b8280548282559060005260206000209081019282156138a2579160200282015b828111156138a257825182546001600160a01b0319166001600160a01b039091161782556020909201916001909101906138d2565b5b808211156138ae5760008155600101613908565b6001600160a01b0381168114610a5f57600080fd5b6000806040838503121561394457600080fd5b823561394f8161391c565b9150602083013561395f8161391c565b809150509250929050565b60006020828403121561397c57600080fd5b8135611f558161391c565b6000806040838503121561399a57600080fd5b82356139a58161391c565b946020939093013593505050565b803563ffffffff811681146139c757600080fd5b919050565b634e487b7160e01b600052604160045260246000fd5b600082601f8301126139f357600080fd5b81356001600160401b03811115613a0c57613a0c6139cc565b604051601f8201601f19908116603f011681016001600160401b0381118282101715613a3a57613a3a6139cc565b604052818152838201602001851015613a5257600080fd5b816020850160208301376000918101602001919091529392505050565b60008060008060808587031215613a8557600080fd5b613a8e856139b3565b935060208501359250613aa3604086016139b3565b915060608501356001600160401b03811115613abe57600080fd5b613aca878288016139e2565b91505092959194509250565b803561ffff811681146139c757600080fd5b600080600060608486031215613afd57600080fd5b8335613b088161391c565b9250613b1660208501613ad6565b9150613b2460408501613ad6565b90509250925092565b6001600160401b0381168114610a5f57600080fd5b600060208284031215613b5457600080fd5b8135611f5581613b2d565b600060208284031215613b7157600080fd5b611f5582613ad6565b60008060408385031215613b8d57600080fd5b82356001600160401b03811115613ba357600080fd5b613baf858286016139e2565b92505060208301356001600160401b03811115613bcb57600080fd5b613bd7858286016139e2565b9150509250929050565b600060208284031215613bf357600080fd5b5035919050565b60008060208385031215613c0d57600080fd5b82356001600160401b03811115613c2357600080fd5b8301601f81018513613c3457600080fd5b80356001600160401b03811115613c4a57600080fd5b8560208260051b8401011115613c5f57600080fd5b6020919091019590945092505050565b600080600060608486031215613c8457600080fd5b8335613c8f8161391c565b92506020840135613c9f8161391c565b929592945050506040919091013590565b602080825282518282018190526000918401906040840190835b81811015613cf15783516001600160a01b0316835260209384019390920191600101613cca565b509095945050505050565b6020808252601a908201527f43616c6c6572206973206e6f742074686520476f7665726e6f72000000000000604082015260600190565b60208082526017908201527f43616c6c6572206973206e6f7420746865205661756c74000000000000000000604082015260600190565b6020808252600e908201526d1499595b9d1c985b9d0818d85b1b60921b604082015260600190565b60005b83811015613dad578181015183820152602001613d95565b50506000910152565b60008151808452613dce816020860160208601613d92565b601f01601f19169290920160200192915050565b604081526000613df56040830185613db6565b82810360208401526109168185613db6565b805180151581146139c757600080fd5b600060208284031215613e2957600080fd5b611f5582613e07565b634e487b7160e01b600052601160045260246000fd5b8181038181111561125857611258613e32565b602080825260119082015270155b9cdd5c1c1bdc9d195908185cdcd95d607a1b604082015260600190565b600060208284031215613e9857600080fd5b5051919050565b8082018082111561125857611258613e32565b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052603160045260246000fd5b6040808252845490820181905260008581526020812090916060840190835b81811015613f245783546001600160a01b0316835260019384019360209093019201613efd565b50508381036020808601919091528582520190508460005b85811015613f6d578135613f4f8161391c565b6001600160a01b031683526020928301929190910190600101613f3c565b50909695505050505050565b6001600160401b03818116838216019081111561125857611258613e32565b63ffffffff60e01b8460e01b16815263ffffffff60e01b8360e01b16600482015260008251613fce816008850160208701613d92565b91909101600801949350505050565b808202811582820484141761125857611258613e32565b60008261401157634e487b7160e01b600052601260045260246000fd5b500490565b88815263ffffffff8816602082015286604082015260018060a01b03861660608201528460808201528360a082015263ffffffff831660c082015261010060e08201526000614069610100830184613db6565b9a9950505050505050505050565b63ffffffff88811682526001600160a01b038881166020840152871660408301526060820186905260808201859052831660a082015260e060c082018190526000906140c590830184613db6565b9998505050505050505050565b602080825260139082015272092dcecc2d8d2c840c8c2e8c240d8cadccee8d606b1b604082015260600190565b80516020808301519190811015614120576000198160200360031b1b821691505b50919050565b60006020828403121561413857600080fd5b8151611f558161391c565b63ffffffff8616815284602082015283604082015263ffffffff8316606082015260a06080820152600061355a60a0830184613db6565b63ffffffff85811682526001600160a01b0385166020830152831660408201526080606082018190526000906141b290830184613db6565b9695505050505050565b600082516141ce818460208701613d92565b9190910192915050565b600080600080608085870312156141ee57600080fd5b84516141f981613b2d565b6020860151909450925061420f60408601613e07565b6060959095015193969295505050565b602081526000611f556020830184613db656fe53bf423e48ed90e97d02ab0ebab13b2a235a6bfbe9c321847d5c175333ac45357bea13895fa79d2831e0a9e28edede30099005a50d652d8957cf8a607ee6ca4aa2646970667358221220f9e4003bc05a0917ebba798c964944fadf1dc0e546995b8d06b1544dadc5345b64736f6c634300081c0033", + "libraries": {}, + "devdoc": { + "kind": "dev", + "methods": { + "checkBalance(address)": { + "params": { + "_asset": "Address of the asset to check" + }, + "returns": { + "balance": "Total balance of the asset" + } + }, + "constructor": { + "params": { + "_stratConfig": "The platform and OToken vault addresses" + } + }, + "deposit(address,uint256)": { + "params": { + "_amount": "Units of asset to deposit", + "_asset": "Address for the asset" + } + }, + "getRewardTokenAddresses()": { + "returns": { + "_0": "address[] the reward token addresses." + } + }, + "handleReceiveFinalizedMessage(uint32,bytes32,uint32,bytes)": { + "details": "Handles a finalized CCTP message", + "params": { + "finalityThresholdExecuted": "Fidelity threshold executed", + "messageBody": "Message body", + "sender": "Sender of the message", + "sourceDomain": "Source domain of the message" + } + }, + "handleReceiveUnfinalizedMessage(uint32,bytes32,uint32,bytes)": { + "details": "Handles an unfinalized but safe CCTP message", + "params": { + "finalityThresholdExecuted": "Fidelity threshold executed", + "messageBody": "Message body", + "sender": "Sender of the message", + "sourceDomain": "Source domain of the message" + } + }, + "initialize(address,uint16,uint16)": { + "details": "Initialize the strategy implementation", + "params": { + "_feePremiumBps": "Fee premium in basis points", + "_minFinalityThreshold": "Minimum finality threshold", + "_operator": "Address of the operator" + } + }, + "isNonceProcessed(uint64)": { + "details": "Checks if a given nonce is processed. Nonce starts at 1, so 0 is disregarded.", + "params": { + "nonce": "Nonce to check" + }, + "returns": { + "_0": "True if the nonce is processed, false otherwise" + } + }, + "isTransferPending()": { + "details": "Checks if the last known transfer is pending. Nonce starts at 1, so 0 is disregarded.", + "returns": { + "_0": "True if a transfer is pending, false otherwise" + } + }, + "relay(bytes,bytes)": { + "details": "Receives a message from the peer strategy on the other chain, does some basic checks and relays it to the local MessageTransmitterV2. If the message is a burn message, it will also handle the hook data and call the _onTokenReceived function.", + "params": { + "attestation": "Attestation of the message", + "message": "Payload of the message to send" + } + }, + "removePToken(uint256)": { + "params": { + "_assetIndex": "Index of the asset to be removed" + } + }, + "setFeePremiumBps(uint16)": { + "details": "Set the fee premium in basis points. Cannot be higher than 30% (3000 basis points).", + "params": { + "_feePremiumBps": "Fee premium in basis points" + } + }, + "setHarvesterAddress(address)": { + "params": { + "_harvesterAddress": "Address of the harvester contract." + } + }, + "setMinFinalityThreshold(uint16)": { + "details": "Set the minimum finality threshold at which the message is considered to be finalized to relay. Only accepts a value of 1000 (Safe, after 1 epoch) or 2000 (Finalized, after 2 epochs).", + "params": { + "_minFinalityThreshold": "Minimum finality threshold" + } + }, + "setOperator(address)": { + "details": "Set the operator address", + "params": { + "_operator": "Operator address" + } + }, + "setPTokenAddress(address,address)": { + "params": { + "_asset": "Address for the asset", + "_pToken": "Address for the corresponding platform token" + } + }, + "setRewardTokenAddresses(address[])": { + "params": { + "_rewardTokenAddresses": "Array of reward token addresses" + } + }, + "supportsAsset(address)": { + "params": { + "_asset": "Address of the asset" + }, + "returns": { + "_0": "bool Whether asset is supported" + } + }, + "transferGovernance(address)": { + "params": { + "_newGovernor": "Address of the new Governor" + } + }, + "transferToken(address,uint256)": { + "params": { + "_amount": "Amount of the asset to transfer", + "_asset": "Address for the asset" + } + }, + "withdraw(address,address,uint256)": { + "params": { + "_amount": "Units of asset to withdraw", + "_asset": "Address of the asset", + "_recipient": "Address to which the asset should be sent" + } + } + }, + "stateVariables": { + "remoteStrategyBalance": { + "details": "The remote balance is cached and might not reflect the actual real-time balance of the remote strategy." + } + }, + "version": 1 + }, + "userdoc": { + "kind": "user", + "methods": { + "MAX_TRANSFER_AMOUNT()": { + "notice": "Max transfer threshold imposed by the CCTP Ref: https://developers.circle.com/cctp/evm-smart-contracts#depositforburn" + }, + "MIN_TRANSFER_AMOUNT()": { + "notice": "Minimum transfer amount to avoid zero or dust transfers" + }, + "assetToPToken(address)": { + "notice": "asset => pToken (Platform Specific Token Address)" + }, + "cctpMessageTransmitter()": { + "notice": "CCTP message transmitter contract" + }, + "cctpTokenMessenger()": { + "notice": "CCTP token messenger contract" + }, + "checkBalance(address)": { + "notice": "Check the balance of the strategy that includes the balance of the asset on this contract, the amount of the asset being bridged, and the balance reported by the Remote strategy." + }, + "claimGovernance()": { + "notice": "Claim Governance of the contract to a new account (`newGovernor`). Can only be called by the new Governor." + }, + "collectRewardTokens()": { + "notice": "Collect accumulated reward token and send to Vault." + }, + "deposit(address,uint256)": { + "notice": "Deposit an amount of assets into the platform" + }, + "depositAll()": { + "notice": "Deposit all supported assets in this strategy contract to the platform" + }, + "feePremiumBps()": { + "notice": "Fee premium in basis points" + }, + "getRewardTokenAddresses()": { + "notice": "Get the reward token addresses." + }, + "governor()": { + "notice": "Returns the address of the current Governor." + }, + "harvesterAddress()": { + "notice": "Address of the Harvester contract allowed to collect reward tokens" + }, + "isGovernor()": { + "notice": "Returns true if the caller is the current Governor." + }, + "lastTransferNonce()": { + "notice": "Nonce of the last known deposit or withdrawal" + }, + "minFinalityThreshold()": { + "notice": "Minimum finality threshold Can be 1000 (safe, after 1 epoch) or 2000 (finalized, after 2 epochs). Ref: https://developers.circle.com/cctp/technical-guide#finality-thresholds" + }, + "operator()": { + "notice": "Operator address: Can relay CCTP messages" + }, + "peerDomainID()": { + "notice": "Domain ID of the chain from which messages are accepted" + }, + "peerStrategy()": { + "notice": "Strategy address on other chain" + }, + "peerUsdcToken()": { + "notice": "USDC address on remote chain" + }, + "pendingAmount()": { + "notice": "Amount that's bridged due to a pending Deposit process but with no acknowledgement from the remote strategy yet" + }, + "platformAddress()": { + "notice": "Address of the underlying platform" + }, + "remoteStrategyBalance()": { + "notice": "Remote strategy balance" + }, + "removePToken(uint256)": { + "notice": "Remove a supported asset by passing its index. This method can only be called by the system Governor" + }, + "rewardTokenAddresses(uint256)": { + "notice": "Address of the reward tokens. eg CRV, BAL, CVX, AURA" + }, + "setHarvesterAddress(address)": { + "notice": "Set the Harvester contract that can collect rewards." + }, + "setPTokenAddress(address,address)": { + "notice": "Provide support for asset by passing its pToken address. This method can only be called by the system Governor" + }, + "setRewardTokenAddresses(address[])": { + "notice": "Set the reward token addresses. Any old addresses will be overwritten." + }, + "supportsAsset(address)": { + "notice": "Check if an asset is supported." + }, + "transferGovernance(address)": { + "notice": "Transfers Governance of the contract to a new account (`newGovernor`). Can only be called by the current Governor. Must be claimed for this to complete" + }, + "transferToken(address,uint256)": { + "notice": "Transfer token to governor. Intended for recovering tokens stuck in strategy contracts, i.e. mistaken sends." + }, + "usdcToken()": { + "notice": "USDC address on local chain" + }, + "vaultAddress()": { + "notice": "Address of the OToken vault" + }, + "withdraw(address,address,uint256)": { + "notice": "Withdraw an `amount` of assets from the platform and send to the `_recipient`." + }, + "withdrawAll()": { + "notice": "Withdraw all supported assets from platform and sends to the OToken's Vault." + } + }, + "version": 1 + }, + "storageLayout": { + "storage": [ + { + "astId": 55299, + "contract": "contracts/strategies/crosschain/CrossChainMasterStrategy.sol:CrossChainMasterStrategy", + "label": "initialized", + "offset": 0, + "slot": "0", + "type": "t_bool" + }, + { + "astId": 55302, + "contract": "contracts/strategies/crosschain/CrossChainMasterStrategy.sol:CrossChainMasterStrategy", + "label": "initializing", + "offset": 1, + "slot": "0", + "type": "t_bool" + }, + { + "astId": 55342, + "contract": "contracts/strategies/crosschain/CrossChainMasterStrategy.sol:CrossChainMasterStrategy", + "label": "______gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage" + }, + { + "astId": 45196, + "contract": "contracts/strategies/crosschain/CrossChainMasterStrategy.sol:CrossChainMasterStrategy", + "label": "minFinalityThreshold", + "offset": 0, + "slot": "51", + "type": "t_uint16" + }, + { + "astId": 45199, + "contract": "contracts/strategies/crosschain/CrossChainMasterStrategy.sol:CrossChainMasterStrategy", + "label": "feePremiumBps", + "offset": 2, + "slot": "51", + "type": "t_uint16" + }, + { + "astId": 45202, + "contract": "contracts/strategies/crosschain/CrossChainMasterStrategy.sol:CrossChainMasterStrategy", + "label": "lastTransferNonce", + "offset": 4, + "slot": "51", + "type": "t_uint64" + }, + { + "astId": 45205, + "contract": "contracts/strategies/crosschain/CrossChainMasterStrategy.sol:CrossChainMasterStrategy", + "label": "operator", + "offset": 12, + "slot": "51", + "type": "t_address" + }, + { + "astId": 45210, + "contract": "contracts/strategies/crosschain/CrossChainMasterStrategy.sol:CrossChainMasterStrategy", + "label": "nonceProcessed", + "offset": 0, + "slot": "52", + "type": "t_mapping(t_uint64,t_bool)" + }, + { + "astId": 45214, + "contract": "contracts/strategies/crosschain/CrossChainMasterStrategy.sol:CrossChainMasterStrategy", + "label": "__gap", + "offset": 0, + "slot": "53", + "type": "t_array(t_uint256)48_storage" + }, + { + "astId": 55422, + "contract": "contracts/strategies/crosschain/CrossChainMasterStrategy.sol:CrossChainMasterStrategy", + "label": "_deprecated_platformAddress", + "offset": 0, + "slot": "101", + "type": "t_address" + }, + { + "astId": 55425, + "contract": "contracts/strategies/crosschain/CrossChainMasterStrategy.sol:CrossChainMasterStrategy", + "label": "_deprecated_vaultAddress", + "offset": 0, + "slot": "102", + "type": "t_address" + }, + { + "astId": 55430, + "contract": "contracts/strategies/crosschain/CrossChainMasterStrategy.sol:CrossChainMasterStrategy", + "label": "assetToPToken", + "offset": 0, + "slot": "103", + "type": "t_mapping(t_address,t_address)" + }, + { + "astId": 55434, + "contract": "contracts/strategies/crosschain/CrossChainMasterStrategy.sol:CrossChainMasterStrategy", + "label": "assetsMapped", + "offset": 0, + "slot": "104", + "type": "t_array(t_address)dyn_storage" + }, + { + "astId": 55436, + "contract": "contracts/strategies/crosschain/CrossChainMasterStrategy.sol:CrossChainMasterStrategy", + "label": "_deprecated_rewardTokenAddress", + "offset": 0, + "slot": "105", + "type": "t_address" + }, + { + "astId": 55438, + "contract": "contracts/strategies/crosschain/CrossChainMasterStrategy.sol:CrossChainMasterStrategy", + "label": "_deprecated_rewardLiquidationThreshold", + "offset": 0, + "slot": "106", + "type": "t_uint256" + }, + { + "astId": 55441, + "contract": "contracts/strategies/crosschain/CrossChainMasterStrategy.sol:CrossChainMasterStrategy", + "label": "harvesterAddress", + "offset": 0, + "slot": "107", + "type": "t_address" + }, + { + "astId": 55445, + "contract": "contracts/strategies/crosschain/CrossChainMasterStrategy.sol:CrossChainMasterStrategy", + "label": "rewardTokenAddresses", + "offset": 0, + "slot": "108", + "type": "t_array(t_address)dyn_storage" + }, + { + "astId": 55449, + "contract": "contracts/strategies/crosschain/CrossChainMasterStrategy.sol:CrossChainMasterStrategy", + "label": "_reserved", + "offset": 0, + "slot": "109", + "type": "t_array(t_int256)98_storage" + }, + { + "astId": 46098, + "contract": "contracts/strategies/crosschain/CrossChainMasterStrategy.sol:CrossChainMasterStrategy", + "label": "remoteStrategyBalance", + "offset": 0, + "slot": "207", + "type": "t_uint256" + }, + { + "astId": 46101, + "contract": "contracts/strategies/crosschain/CrossChainMasterStrategy.sol:CrossChainMasterStrategy", + "label": "pendingAmount", + "offset": 0, + "slot": "208", + "type": "t_uint256" + } + ], + "types": { + "t_address": { + "encoding": "inplace", + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_address)dyn_storage": { + "base": "t_address", + "encoding": "dynamic_array", + "label": "address[]", + "numberOfBytes": "32" + }, + "t_array(t_int256)98_storage": { + "base": "t_int256", + "encoding": "inplace", + "label": "int256[98]", + "numberOfBytes": "3136" + }, + "t_array(t_uint256)48_storage": { + "base": "t_uint256", + "encoding": "inplace", + "label": "uint256[48]", + "numberOfBytes": "1536" + }, + "t_array(t_uint256)50_storage": { + "base": "t_uint256", + "encoding": "inplace", + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "encoding": "inplace", + "label": "bool", + "numberOfBytes": "1" + }, + "t_int256": { + "encoding": "inplace", + "label": "int256", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_address)": { + "encoding": "mapping", + "key": "t_address", + "label": "mapping(address => address)", + "numberOfBytes": "32", + "value": "t_address" + }, + "t_mapping(t_uint64,t_bool)": { + "encoding": "mapping", + "key": "t_uint64", + "label": "mapping(uint64 => bool)", + "numberOfBytes": "32", + "value": "t_bool" + }, + "t_uint16": { + "encoding": "inplace", + "label": "uint16", + "numberOfBytes": "2" + }, + "t_uint256": { + "encoding": "inplace", + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint64": { + "encoding": "inplace", + "label": "uint64", + "numberOfBytes": "8" + } + } + } +} \ No newline at end of file diff --git a/contracts/deployments/mainnet/create2Proxies.json b/contracts/deployments/mainnet/create2Proxies.json index 9e26dfeeb6..4accae1715 100644 --- a/contracts/deployments/mainnet/create2Proxies.json +++ b/contracts/deployments/mainnet/create2Proxies.json @@ -1 +1,3 @@ -{} \ No newline at end of file +{ + "CrossChainStrategyProxy": "0xB1d624fc40824683e2bFBEfd19eB208DbBE00866" +} \ No newline at end of file diff --git a/contracts/deployments/mainnet/solcInputs/00a8ab5ddcdb46bc9891a2222be9eb31.json b/contracts/deployments/mainnet/solcInputs/00a8ab5ddcdb46bc9891a2222be9eb31.json new file mode 100644 index 0000000000..ff638403d8 --- /dev/null +++ b/contracts/deployments/mainnet/solcInputs/00a8ab5ddcdb46bc9891a2222be9eb31.json @@ -0,0 +1,621 @@ +{ + "language": "Solidity", + "sources": { + "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IRouterClient.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport {Client} from \"../libraries/Client.sol\";\n\ninterface IRouterClient {\n error UnsupportedDestinationChain(uint64 destChainSelector);\n error InsufficientFeeTokenAmount();\n error InvalidMsgValue();\n\n /// @notice Checks if the given chain ID is supported for sending/receiving.\n /// @param chainSelector The chain to check.\n /// @return supported is true if it is supported, false if not.\n function isChainSupported(uint64 chainSelector) external view returns (bool supported);\n\n /// @notice Gets a list of all supported tokens which can be sent or received\n /// to/from a given chain id.\n /// @param chainSelector The chainSelector.\n /// @return tokens The addresses of all tokens that are supported.\n function getSupportedTokens(uint64 chainSelector) external view returns (address[] memory tokens);\n\n /// @param destinationChainSelector The destination chainSelector\n /// @param message The cross-chain CCIP message including data and/or tokens\n /// @return fee returns execution fee for the message\n /// delivery to destination chain, denominated in the feeToken specified in the message.\n /// @dev Reverts with appropriate reason upon invalid message.\n function getFee(\n uint64 destinationChainSelector,\n Client.EVM2AnyMessage memory message\n ) external view returns (uint256 fee);\n\n /// @notice Request a message to be sent to the destination chain\n /// @param destinationChainSelector The destination chain ID\n /// @param message The cross-chain CCIP message including data and/or tokens\n /// @return messageId The message ID\n /// @dev Note if msg.value is larger than the required fee (from getFee) we accept\n /// the overpayment with no refund.\n /// @dev Reverts with appropriate reason upon invalid message.\n function ccipSend(\n uint64 destinationChainSelector,\n Client.EVM2AnyMessage calldata message\n ) external payable returns (bytes32);\n}\n" + }, + "@chainlink/contracts-ccip/src/v0.8/ccip/libraries/Client.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\n// End consumer library.\nlibrary Client {\n /// @dev RMN depends on this struct, if changing, please notify the RMN maintainers.\n struct EVMTokenAmount {\n address token; // token address on the local chain.\n uint256 amount; // Amount of tokens.\n }\n\n struct Any2EVMMessage {\n bytes32 messageId; // MessageId corresponding to ccipSend on source.\n uint64 sourceChainSelector; // Source chain selector.\n bytes sender; // abi.decode(sender) if coming from an EVM chain.\n bytes data; // payload sent in original message.\n EVMTokenAmount[] destTokenAmounts; // Tokens and their amounts in their destination chain representation.\n }\n\n // If extraArgs is empty bytes, the default is 200k gas limit.\n struct EVM2AnyMessage {\n bytes receiver; // abi.encode(receiver address) for dest EVM chains\n bytes data; // Data payload\n EVMTokenAmount[] tokenAmounts; // Token transfers\n address feeToken; // Address of feeToken. address(0) means you will send msg.value.\n bytes extraArgs; // Populate this with _argsToBytes(EVMExtraArgsV1)\n }\n\n // bytes4(keccak256(\"CCIP EVMExtraArgsV1\"));\n bytes4 public constant EVM_EXTRA_ARGS_V1_TAG = 0x97a657c9;\n struct EVMExtraArgsV1 {\n uint256 gasLimit;\n }\n\n function _argsToBytes(EVMExtraArgsV1 memory extraArgs) internal pure returns (bytes memory bts) {\n return abi.encodeWithSelector(EVM_EXTRA_ARGS_V1_TAG, extraArgs);\n }\n}\n" + }, + "@layerzerolabs/lz-evm-messagelib-v2/contracts/libs/ExecutorOptions.sol": { + "content": "// SPDX-License-Identifier: LZBL-1.2\n\npragma solidity ^0.8.20;\n\nimport \"@layerzerolabs/lz-evm-protocol-v2/contracts/libs/CalldataBytesLib.sol\";\n\nlibrary ExecutorOptions {\n using CalldataBytesLib for bytes;\n\n uint8 internal constant WORKER_ID = 1;\n\n uint8 internal constant OPTION_TYPE_LZRECEIVE = 1;\n uint8 internal constant OPTION_TYPE_NATIVE_DROP = 2;\n uint8 internal constant OPTION_TYPE_LZCOMPOSE = 3;\n uint8 internal constant OPTION_TYPE_ORDERED_EXECUTION = 4;\n uint8 internal constant OPTION_TYPE_LZREAD = 5;\n\n error Executor_InvalidLzReceiveOption();\n error Executor_InvalidNativeDropOption();\n error Executor_InvalidLzComposeOption();\n error Executor_InvalidLzReadOption();\n\n /// @dev decode the next executor option from the options starting from the specified cursor\n /// @param _options [executor_id][executor_option][executor_id][executor_option]...\n /// executor_option = [option_size][option_type][option]\n /// option_size = len(option_type) + len(option)\n /// executor_id: uint8, option_size: uint16, option_type: uint8, option: bytes\n /// @param _cursor the cursor to start decoding from\n /// @return optionType the type of the option\n /// @return option the option of the executor\n /// @return cursor the cursor to start decoding the next executor option\n function nextExecutorOption(\n bytes calldata _options,\n uint256 _cursor\n ) internal pure returns (uint8 optionType, bytes calldata option, uint256 cursor) {\n unchecked {\n // skip worker id\n cursor = _cursor + 1;\n\n // read option size\n uint16 size = _options.toU16(cursor);\n cursor += 2;\n\n // read option type\n optionType = _options.toU8(cursor);\n\n // startCursor and endCursor are used to slice the option from _options\n uint256 startCursor = cursor + 1; // skip option type\n uint256 endCursor = cursor + size;\n option = _options[startCursor:endCursor];\n cursor += size;\n }\n }\n\n function decodeLzReceiveOption(bytes calldata _option) internal pure returns (uint128 gas, uint128 value) {\n if (_option.length != 16 && _option.length != 32) revert Executor_InvalidLzReceiveOption();\n gas = _option.toU128(0);\n value = _option.length == 32 ? _option.toU128(16) : 0;\n }\n\n function decodeNativeDropOption(bytes calldata _option) internal pure returns (uint128 amount, bytes32 receiver) {\n if (_option.length != 48) revert Executor_InvalidNativeDropOption();\n amount = _option.toU128(0);\n receiver = _option.toB32(16);\n }\n\n function decodeLzComposeOption(\n bytes calldata _option\n ) internal pure returns (uint16 index, uint128 gas, uint128 value) {\n if (_option.length != 18 && _option.length != 34) revert Executor_InvalidLzComposeOption();\n index = _option.toU16(0);\n gas = _option.toU128(2);\n value = _option.length == 34 ? _option.toU128(18) : 0;\n }\n\n function decodeLzReadOption(\n bytes calldata _option\n ) internal pure returns (uint128 gas, uint32 calldataSize, uint128 value) {\n if (_option.length != 20 && _option.length != 36) revert Executor_InvalidLzReadOption();\n gas = _option.toU128(0);\n calldataSize = _option.toU32(16);\n value = _option.length == 36 ? _option.toU128(20) : 0;\n }\n\n function encodeLzReceiveOption(uint128 _gas, uint128 _value) internal pure returns (bytes memory) {\n return _value == 0 ? abi.encodePacked(_gas) : abi.encodePacked(_gas, _value);\n }\n\n function encodeNativeDropOption(uint128 _amount, bytes32 _receiver) internal pure returns (bytes memory) {\n return abi.encodePacked(_amount, _receiver);\n }\n\n function encodeLzComposeOption(uint16 _index, uint128 _gas, uint128 _value) internal pure returns (bytes memory) {\n return _value == 0 ? abi.encodePacked(_index, _gas) : abi.encodePacked(_index, _gas, _value);\n }\n\n function encodeLzReadOption(\n uint128 _gas,\n uint32 _calldataSize,\n uint128 _value\n ) internal pure returns (bytes memory) {\n return _value == 0 ? abi.encodePacked(_gas, _calldataSize) : abi.encodePacked(_gas, _calldataSize, _value);\n }\n}\n" + }, + "@layerzerolabs/lz-evm-messagelib-v2/contracts/uln/libs/DVNOptions.sol": { + "content": "// SPDX-License-Identifier: LZBL-1.2\n\npragma solidity ^0.8.20;\n\nimport { BytesLib } from \"solidity-bytes-utils/contracts/BytesLib.sol\";\n\nimport { BitMap256 } from \"@layerzerolabs/lz-evm-protocol-v2/contracts/messagelib/libs/BitMaps.sol\";\nimport { CalldataBytesLib } from \"@layerzerolabs/lz-evm-protocol-v2/contracts/libs/CalldataBytesLib.sol\";\n\nlibrary DVNOptions {\n using CalldataBytesLib for bytes;\n using BytesLib for bytes;\n\n uint8 internal constant WORKER_ID = 2;\n uint8 internal constant OPTION_TYPE_PRECRIME = 1;\n\n error DVN_InvalidDVNIdx();\n error DVN_InvalidDVNOptions(uint256 cursor);\n\n /// @dev group dvn options by its idx\n /// @param _options [dvn_id][dvn_option][dvn_id][dvn_option]...\n /// dvn_option = [option_size][dvn_idx][option_type][option]\n /// option_size = len(dvn_idx) + len(option_type) + len(option)\n /// dvn_id: uint8, dvn_idx: uint8, option_size: uint16, option_type: uint8, option: bytes\n /// @return dvnOptions the grouped options, still share the same format of _options\n /// @return dvnIndices the dvn indices\n function groupDVNOptionsByIdx(\n bytes memory _options\n ) internal pure returns (bytes[] memory dvnOptions, uint8[] memory dvnIndices) {\n if (_options.length == 0) return (dvnOptions, dvnIndices);\n\n uint8 numDVNs = getNumDVNs(_options);\n\n // if there is only 1 dvn, we can just return the whole options\n if (numDVNs == 1) {\n dvnOptions = new bytes[](1);\n dvnOptions[0] = _options;\n\n dvnIndices = new uint8[](1);\n dvnIndices[0] = _options.toUint8(3); // dvn idx\n return (dvnOptions, dvnIndices);\n }\n\n // otherwise, we need to group the options by dvn_idx\n dvnIndices = new uint8[](numDVNs);\n dvnOptions = new bytes[](numDVNs);\n unchecked {\n uint256 cursor = 0;\n uint256 start = 0;\n uint8 lastDVNIdx = 255; // 255 is an invalid dvn_idx\n\n while (cursor < _options.length) {\n ++cursor; // skip worker_id\n\n // optionLength asserted in getNumDVNs (skip check)\n uint16 optionLength = _options.toUint16(cursor);\n cursor += 2;\n\n // dvnIdx asserted in getNumDVNs (skip check)\n uint8 dvnIdx = _options.toUint8(cursor);\n\n // dvnIdx must equal to the lastDVNIdx for the first option\n // so it is always skipped in the first option\n // this operation slices out options whenever the scan finds a different lastDVNIdx\n if (lastDVNIdx == 255) {\n lastDVNIdx = dvnIdx;\n } else if (dvnIdx != lastDVNIdx) {\n uint256 len = cursor - start - 3; // 3 is for worker_id and option_length\n bytes memory opt = _options.slice(start, len);\n _insertDVNOptions(dvnOptions, dvnIndices, lastDVNIdx, opt);\n\n // reset the start and lastDVNIdx\n start += len;\n lastDVNIdx = dvnIdx;\n }\n\n cursor += optionLength;\n }\n\n // skip check the cursor here because the cursor is asserted in getNumDVNs\n // if we have reached the end of the options, we need to process the last dvn\n uint256 size = cursor - start;\n bytes memory op = _options.slice(start, size);\n _insertDVNOptions(dvnOptions, dvnIndices, lastDVNIdx, op);\n\n // revert dvnIndices to start from 0\n for (uint8 i = 0; i < numDVNs; ++i) {\n --dvnIndices[i];\n }\n }\n }\n\n function _insertDVNOptions(\n bytes[] memory _dvnOptions,\n uint8[] memory _dvnIndices,\n uint8 _dvnIdx,\n bytes memory _newOptions\n ) internal pure {\n // dvnIdx starts from 0 but default value of dvnIndices is 0,\n // so we tell if the slot is empty by adding 1 to dvnIdx\n if (_dvnIdx == 255) revert DVN_InvalidDVNIdx();\n uint8 dvnIdxAdj = _dvnIdx + 1;\n\n for (uint256 j = 0; j < _dvnIndices.length; ++j) {\n uint8 index = _dvnIndices[j];\n if (dvnIdxAdj == index) {\n _dvnOptions[j] = abi.encodePacked(_dvnOptions[j], _newOptions);\n break;\n } else if (index == 0) {\n // empty slot, that means it is the first time we see this dvn\n _dvnIndices[j] = dvnIdxAdj;\n _dvnOptions[j] = _newOptions;\n break;\n }\n }\n }\n\n /// @dev get the number of unique dvns\n /// @param _options the format is the same as groupDVNOptionsByIdx\n function getNumDVNs(bytes memory _options) internal pure returns (uint8 numDVNs) {\n uint256 cursor = 0;\n BitMap256 bitmap;\n\n // find number of unique dvn_idx\n unchecked {\n while (cursor < _options.length) {\n ++cursor; // skip worker_id\n\n uint16 optionLength = _options.toUint16(cursor);\n cursor += 2;\n if (optionLength < 2) revert DVN_InvalidDVNOptions(cursor); // at least 1 byte for dvn_idx and 1 byte for option_type\n\n uint8 dvnIdx = _options.toUint8(cursor);\n\n // if dvnIdx is not set, increment numDVNs\n // max num of dvns is 255, 255 is an invalid dvn_idx\n // The order of the dvnIdx is not required to be sequential, as enforcing the order may weaken\n // the composability of the options. e.g. if we refrain from enforcing the order, an OApp that has\n // already enforced certain options can append additional options to the end of the enforced\n // ones without restrictions.\n if (dvnIdx == 255) revert DVN_InvalidDVNIdx();\n if (!bitmap.get(dvnIdx)) {\n ++numDVNs;\n bitmap = bitmap.set(dvnIdx);\n }\n\n cursor += optionLength;\n }\n }\n if (cursor != _options.length) revert DVN_InvalidDVNOptions(cursor);\n }\n\n /// @dev decode the next dvn option from _options starting from the specified cursor\n /// @param _options the format is the same as groupDVNOptionsByIdx\n /// @param _cursor the cursor to start decoding\n /// @return optionType the type of the option\n /// @return option the option\n /// @return cursor the cursor to start decoding the next option\n function nextDVNOption(\n bytes calldata _options,\n uint256 _cursor\n ) internal pure returns (uint8 optionType, bytes calldata option, uint256 cursor) {\n unchecked {\n // skip worker id\n cursor = _cursor + 1;\n\n // read option size\n uint16 size = _options.toU16(cursor);\n cursor += 2;\n\n // read option type\n optionType = _options.toU8(cursor + 1); // skip dvn_idx\n\n // startCursor and endCursor are used to slice the option from _options\n uint256 startCursor = cursor + 2; // skip option type and dvn_idx\n uint256 endCursor = cursor + size;\n option = _options[startCursor:endCursor];\n cursor += size;\n }\n }\n}\n" + }, + "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroEndpointV2.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity >=0.8.0;\n\nimport { IMessageLibManager } from \"./IMessageLibManager.sol\";\nimport { IMessagingComposer } from \"./IMessagingComposer.sol\";\nimport { IMessagingChannel } from \"./IMessagingChannel.sol\";\nimport { IMessagingContext } from \"./IMessagingContext.sol\";\n\nstruct MessagingParams {\n uint32 dstEid;\n bytes32 receiver;\n bytes message;\n bytes options;\n bool payInLzToken;\n}\n\nstruct MessagingReceipt {\n bytes32 guid;\n uint64 nonce;\n MessagingFee fee;\n}\n\nstruct MessagingFee {\n uint256 nativeFee;\n uint256 lzTokenFee;\n}\n\nstruct Origin {\n uint32 srcEid;\n bytes32 sender;\n uint64 nonce;\n}\n\ninterface ILayerZeroEndpointV2 is IMessageLibManager, IMessagingComposer, IMessagingChannel, IMessagingContext {\n event PacketSent(bytes encodedPayload, bytes options, address sendLibrary);\n\n event PacketVerified(Origin origin, address receiver, bytes32 payloadHash);\n\n event PacketDelivered(Origin origin, address receiver);\n\n event LzReceiveAlert(\n address indexed receiver,\n address indexed executor,\n Origin origin,\n bytes32 guid,\n uint256 gas,\n uint256 value,\n bytes message,\n bytes extraData,\n bytes reason\n );\n\n event LzTokenSet(address token);\n\n event DelegateSet(address sender, address delegate);\n\n function quote(MessagingParams calldata _params, address _sender) external view returns (MessagingFee memory);\n\n function send(\n MessagingParams calldata _params,\n address _refundAddress\n ) external payable returns (MessagingReceipt memory);\n\n function verify(Origin calldata _origin, address _receiver, bytes32 _payloadHash) external;\n\n function verifiable(Origin calldata _origin, address _receiver) external view returns (bool);\n\n function initializable(Origin calldata _origin, address _receiver) external view returns (bool);\n\n function lzReceive(\n Origin calldata _origin,\n address _receiver,\n bytes32 _guid,\n bytes calldata _message,\n bytes calldata _extraData\n ) external payable;\n\n // oapp can burn messages partially by calling this function with its own business logic if messages are verified in order\n function clear(address _oapp, Origin calldata _origin, bytes32 _guid, bytes calldata _message) external;\n\n function setLzToken(address _lzToken) external;\n\n function lzToken() external view returns (address);\n\n function nativeToken() external view returns (address);\n\n function setDelegate(address _delegate) external;\n}\n" + }, + "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/IMessageLibManager.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity >=0.8.0;\n\nstruct SetConfigParam {\n uint32 eid;\n uint32 configType;\n bytes config;\n}\n\ninterface IMessageLibManager {\n struct Timeout {\n address lib;\n uint256 expiry;\n }\n\n event LibraryRegistered(address newLib);\n event DefaultSendLibrarySet(uint32 eid, address newLib);\n event DefaultReceiveLibrarySet(uint32 eid, address newLib);\n event DefaultReceiveLibraryTimeoutSet(uint32 eid, address oldLib, uint256 expiry);\n event SendLibrarySet(address sender, uint32 eid, address newLib);\n event ReceiveLibrarySet(address receiver, uint32 eid, address newLib);\n event ReceiveLibraryTimeoutSet(address receiver, uint32 eid, address oldLib, uint256 timeout);\n\n function registerLibrary(address _lib) external;\n\n function isRegisteredLibrary(address _lib) external view returns (bool);\n\n function getRegisteredLibraries() external view returns (address[] memory);\n\n function setDefaultSendLibrary(uint32 _eid, address _newLib) external;\n\n function defaultSendLibrary(uint32 _eid) external view returns (address);\n\n function setDefaultReceiveLibrary(uint32 _eid, address _newLib, uint256 _gracePeriod) external;\n\n function defaultReceiveLibrary(uint32 _eid) external view returns (address);\n\n function setDefaultReceiveLibraryTimeout(uint32 _eid, address _lib, uint256 _expiry) external;\n\n function defaultReceiveLibraryTimeout(uint32 _eid) external view returns (address lib, uint256 expiry);\n\n function isSupportedEid(uint32 _eid) external view returns (bool);\n\n function isValidReceiveLibrary(address _receiver, uint32 _eid, address _lib) external view returns (bool);\n\n /// ------------------- OApp interfaces -------------------\n function setSendLibrary(address _oapp, uint32 _eid, address _newLib) external;\n\n function getSendLibrary(address _sender, uint32 _eid) external view returns (address lib);\n\n function isDefaultSendLibrary(address _sender, uint32 _eid) external view returns (bool);\n\n function setReceiveLibrary(address _oapp, uint32 _eid, address _newLib, uint256 _gracePeriod) external;\n\n function getReceiveLibrary(address _receiver, uint32 _eid) external view returns (address lib, bool isDefault);\n\n function setReceiveLibraryTimeout(address _oapp, uint32 _eid, address _lib, uint256 _expiry) external;\n\n function receiveLibraryTimeout(address _receiver, uint32 _eid) external view returns (address lib, uint256 expiry);\n\n function setConfig(address _oapp, address _lib, SetConfigParam[] calldata _params) external;\n\n function getConfig(\n address _oapp,\n address _lib,\n uint32 _eid,\n uint32 _configType\n ) external view returns (bytes memory config);\n}\n" + }, + "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/IMessagingChannel.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity >=0.8.0;\n\ninterface IMessagingChannel {\n event InboundNonceSkipped(uint32 srcEid, bytes32 sender, address receiver, uint64 nonce);\n event PacketNilified(uint32 srcEid, bytes32 sender, address receiver, uint64 nonce, bytes32 payloadHash);\n event PacketBurnt(uint32 srcEid, bytes32 sender, address receiver, uint64 nonce, bytes32 payloadHash);\n\n function eid() external view returns (uint32);\n\n // this is an emergency function if a message cannot be verified for some reasons\n // required to provide _nextNonce to avoid race condition\n function skip(address _oapp, uint32 _srcEid, bytes32 _sender, uint64 _nonce) external;\n\n function nilify(address _oapp, uint32 _srcEid, bytes32 _sender, uint64 _nonce, bytes32 _payloadHash) external;\n\n function burn(address _oapp, uint32 _srcEid, bytes32 _sender, uint64 _nonce, bytes32 _payloadHash) external;\n\n function nextGuid(address _sender, uint32 _dstEid, bytes32 _receiver) external view returns (bytes32);\n\n function inboundNonce(address _receiver, uint32 _srcEid, bytes32 _sender) external view returns (uint64);\n\n function outboundNonce(address _sender, uint32 _dstEid, bytes32 _receiver) external view returns (uint64);\n\n function inboundPayloadHash(\n address _receiver,\n uint32 _srcEid,\n bytes32 _sender,\n uint64 _nonce\n ) external view returns (bytes32);\n\n function lazyInboundNonce(address _receiver, uint32 _srcEid, bytes32 _sender) external view returns (uint64);\n}\n" + }, + "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/IMessagingComposer.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity >=0.8.0;\n\ninterface IMessagingComposer {\n event ComposeSent(address from, address to, bytes32 guid, uint16 index, bytes message);\n event ComposeDelivered(address from, address to, bytes32 guid, uint16 index);\n event LzComposeAlert(\n address indexed from,\n address indexed to,\n address indexed executor,\n bytes32 guid,\n uint16 index,\n uint256 gas,\n uint256 value,\n bytes message,\n bytes extraData,\n bytes reason\n );\n\n function composeQueue(\n address _from,\n address _to,\n bytes32 _guid,\n uint16 _index\n ) external view returns (bytes32 messageHash);\n\n function sendCompose(address _to, bytes32 _guid, uint16 _index, bytes calldata _message) external;\n\n function lzCompose(\n address _from,\n address _to,\n bytes32 _guid,\n uint16 _index,\n bytes calldata _message,\n bytes calldata _extraData\n ) external payable;\n}\n" + }, + "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/IMessagingContext.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity >=0.8.0;\n\ninterface IMessagingContext {\n function isSendingMessage() external view returns (bool);\n\n function getSendContext() external view returns (uint32 dstEid, address sender);\n}\n" + }, + "@layerzerolabs/lz-evm-protocol-v2/contracts/libs/CalldataBytesLib.sol": { + "content": "// SPDX-License-Identifier: LZBL-1.2\n\npragma solidity ^0.8.20;\n\nlibrary CalldataBytesLib {\n function toU8(bytes calldata _bytes, uint256 _start) internal pure returns (uint8) {\n return uint8(_bytes[_start]);\n }\n\n function toU16(bytes calldata _bytes, uint256 _start) internal pure returns (uint16) {\n unchecked {\n uint256 end = _start + 2;\n return uint16(bytes2(_bytes[_start:end]));\n }\n }\n\n function toU32(bytes calldata _bytes, uint256 _start) internal pure returns (uint32) {\n unchecked {\n uint256 end = _start + 4;\n return uint32(bytes4(_bytes[_start:end]));\n }\n }\n\n function toU64(bytes calldata _bytes, uint256 _start) internal pure returns (uint64) {\n unchecked {\n uint256 end = _start + 8;\n return uint64(bytes8(_bytes[_start:end]));\n }\n }\n\n function toU128(bytes calldata _bytes, uint256 _start) internal pure returns (uint128) {\n unchecked {\n uint256 end = _start + 16;\n return uint128(bytes16(_bytes[_start:end]));\n }\n }\n\n function toU256(bytes calldata _bytes, uint256 _start) internal pure returns (uint256) {\n unchecked {\n uint256 end = _start + 32;\n return uint256(bytes32(_bytes[_start:end]));\n }\n }\n\n function toAddr(bytes calldata _bytes, uint256 _start) internal pure returns (address) {\n unchecked {\n uint256 end = _start + 20;\n return address(bytes20(_bytes[_start:end]));\n }\n }\n\n function toB32(bytes calldata _bytes, uint256 _start) internal pure returns (bytes32) {\n unchecked {\n uint256 end = _start + 32;\n return bytes32(_bytes[_start:end]);\n }\n }\n}\n" + }, + "@layerzerolabs/lz-evm-protocol-v2/contracts/messagelib/libs/BitMaps.sol": { + "content": "// SPDX-License-Identifier: MIT\n\n// modified from https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/structs/BitMaps.sol\npragma solidity ^0.8.20;\n\ntype BitMap256 is uint256;\n\nusing BitMaps for BitMap256 global;\n\nlibrary BitMaps {\n /**\n * @dev Returns whether the bit at `index` is set.\n */\n function get(BitMap256 bitmap, uint8 index) internal pure returns (bool) {\n uint256 mask = 1 << index;\n return BitMap256.unwrap(bitmap) & mask != 0;\n }\n\n /**\n * @dev Sets the bit at `index`.\n */\n function set(BitMap256 bitmap, uint8 index) internal pure returns (BitMap256) {\n uint256 mask = 1 << index;\n return BitMap256.wrap(BitMap256.unwrap(bitmap) | mask);\n }\n}\n" + }, + "@layerzerolabs/oapp-evm/contracts/oapp/interfaces/IOAppCore.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\nimport { ILayerZeroEndpointV2 } from \"@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroEndpointV2.sol\";\n\n/**\n * @title IOAppCore\n */\ninterface IOAppCore {\n // Custom error messages\n error OnlyPeer(uint32 eid, bytes32 sender);\n error NoPeer(uint32 eid);\n error InvalidEndpointCall();\n error InvalidDelegate();\n\n // Event emitted when a peer (OApp) is set for a corresponding endpoint\n event PeerSet(uint32 eid, bytes32 peer);\n\n /**\n * @notice Retrieves the OApp version information.\n * @return senderVersion The version of the OAppSender.sol contract.\n * @return receiverVersion The version of the OAppReceiver.sol contract.\n */\n function oAppVersion() external view returns (uint64 senderVersion, uint64 receiverVersion);\n\n /**\n * @notice Retrieves the LayerZero endpoint associated with the OApp.\n * @return iEndpoint The LayerZero endpoint as an interface.\n */\n function endpoint() external view returns (ILayerZeroEndpointV2 iEndpoint);\n\n /**\n * @notice Retrieves the peer (OApp) associated with a corresponding endpoint.\n * @param _eid The endpoint ID.\n * @return peer The peer address (OApp instance) associated with the corresponding endpoint.\n */\n function peers(uint32 _eid) external view returns (bytes32 peer);\n\n /**\n * @notice Sets the peer address (OApp instance) for a corresponding endpoint.\n * @param _eid The endpoint ID.\n * @param _peer The address of the peer to be associated with the corresponding endpoint.\n */\n function setPeer(uint32 _eid, bytes32 _peer) external;\n\n /**\n * @notice Sets the delegate address for the OApp Core.\n * @param _delegate The address of the delegate to be set.\n */\n function setDelegate(address _delegate) external;\n}\n" + }, + "@layerzerolabs/oapp-evm/contracts/oapp/libs/OptionsBuilder.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\nimport { BytesLib } from \"solidity-bytes-utils/contracts/BytesLib.sol\";\nimport { SafeCast } from \"@openzeppelin/contracts/utils/math/SafeCast.sol\";\n\nimport { ExecutorOptions } from \"@layerzerolabs/lz-evm-messagelib-v2/contracts/libs/ExecutorOptions.sol\";\nimport { DVNOptions } from \"@layerzerolabs/lz-evm-messagelib-v2/contracts/uln/libs/DVNOptions.sol\";\n\n/**\n * @title OptionsBuilder\n * @dev Library for building and encoding various message options.\n */\nlibrary OptionsBuilder {\n using SafeCast for uint256;\n using BytesLib for bytes;\n\n // Constants for options types\n uint16 internal constant TYPE_1 = 1; // legacy options type 1\n uint16 internal constant TYPE_2 = 2; // legacy options type 2\n uint16 internal constant TYPE_3 = 3;\n\n // Custom error message\n error InvalidSize(uint256 max, uint256 actual);\n error InvalidOptionType(uint16 optionType);\n\n // Modifier to ensure only options of type 3 are used\n modifier onlyType3(bytes memory _options) {\n if (_options.toUint16(0) != TYPE_3) revert InvalidOptionType(_options.toUint16(0));\n _;\n }\n\n /**\n * @dev Creates a new options container with type 3.\n * @return options The newly created options container.\n */\n function newOptions() internal pure returns (bytes memory) {\n return abi.encodePacked(TYPE_3);\n }\n\n /**\n * @dev Adds an executor LZ receive option to the existing options.\n * @param _options The existing options container.\n * @param _gas The gasLimit used on the lzReceive() function in the OApp.\n * @param _value The msg.value passed to the lzReceive() function in the OApp.\n * @return options The updated options container.\n *\n * @dev When multiples of this option are added, they are summed by the executor\n * eg. if (_gas: 200k, and _value: 1 ether) AND (_gas: 100k, _value: 0.5 ether) are sent in an option to the LayerZeroEndpoint,\n * that becomes (300k, 1.5 ether) when the message is executed on the remote lzReceive() function.\n */\n function addExecutorLzReceiveOption(\n bytes memory _options,\n uint128 _gas,\n uint128 _value\n ) internal pure onlyType3(_options) returns (bytes memory) {\n bytes memory option = ExecutorOptions.encodeLzReceiveOption(_gas, _value);\n return addExecutorOption(_options, ExecutorOptions.OPTION_TYPE_LZRECEIVE, option);\n }\n\n /**\n * @dev Adds an executor native drop option to the existing options.\n * @param _options The existing options container.\n * @param _amount The amount for the native value that is airdropped to the 'receiver'.\n * @param _receiver The receiver address for the native drop option.\n * @return options The updated options container.\n *\n * @dev When multiples of this option are added, they are summed by the executor on the remote chain.\n */\n function addExecutorNativeDropOption(\n bytes memory _options,\n uint128 _amount,\n bytes32 _receiver\n ) internal pure onlyType3(_options) returns (bytes memory) {\n bytes memory option = ExecutorOptions.encodeNativeDropOption(_amount, _receiver);\n return addExecutorOption(_options, ExecutorOptions.OPTION_TYPE_NATIVE_DROP, option);\n }\n\n // /**\n // * @dev Adds an executor native drop option to the existing options.\n // * @param _options The existing options container.\n // * @param _amount The amount for the native value that is airdropped to the 'receiver'.\n // * @param _receiver The receiver address for the native drop option.\n // * @return options The updated options container.\n // *\n // * @dev When multiples of this option are added, they are summed by the executor on the remote chain.\n // */\n function addExecutorLzReadOption(\n bytes memory _options,\n uint128 _gas,\n uint32 _size,\n uint128 _value\n ) internal pure onlyType3(_options) returns (bytes memory) {\n bytes memory option = ExecutorOptions.encodeLzReadOption(_gas, _size, _value);\n return addExecutorOption(_options, ExecutorOptions.OPTION_TYPE_LZREAD, option);\n }\n\n /**\n * @dev Adds an executor LZ compose option to the existing options.\n * @param _options The existing options container.\n * @param _index The index for the lzCompose() function call.\n * @param _gas The gasLimit for the lzCompose() function call.\n * @param _value The msg.value for the lzCompose() function call.\n * @return options The updated options container.\n *\n * @dev When multiples of this option are added, they are summed PER index by the executor on the remote chain.\n * @dev If the OApp sends N lzCompose calls on the remote, you must provide N incremented indexes starting with 0.\n * ie. When your remote OApp composes (N = 3) messages, you must set this option for index 0,1,2\n */\n function addExecutorLzComposeOption(\n bytes memory _options,\n uint16 _index,\n uint128 _gas,\n uint128 _value\n ) internal pure onlyType3(_options) returns (bytes memory) {\n bytes memory option = ExecutorOptions.encodeLzComposeOption(_index, _gas, _value);\n return addExecutorOption(_options, ExecutorOptions.OPTION_TYPE_LZCOMPOSE, option);\n }\n\n /**\n * @dev Adds an executor ordered execution option to the existing options.\n * @param _options The existing options container.\n * @return options The updated options container.\n */\n function addExecutorOrderedExecutionOption(\n bytes memory _options\n ) internal pure onlyType3(_options) returns (bytes memory) {\n return addExecutorOption(_options, ExecutorOptions.OPTION_TYPE_ORDERED_EXECUTION, bytes(\"\"));\n }\n\n /**\n * @dev Adds a DVN pre-crime option to the existing options.\n * @param _options The existing options container.\n * @param _dvnIdx The DVN index for the pre-crime option.\n * @return options The updated options container.\n */\n function addDVNPreCrimeOption(\n bytes memory _options,\n uint8 _dvnIdx\n ) internal pure onlyType3(_options) returns (bytes memory) {\n return addDVNOption(_options, _dvnIdx, DVNOptions.OPTION_TYPE_PRECRIME, bytes(\"\"));\n }\n\n /**\n * @dev Adds an executor option to the existing options.\n * @param _options The existing options container.\n * @param _optionType The type of the executor option.\n * @param _option The encoded data for the executor option.\n * @return options The updated options container.\n */\n function addExecutorOption(\n bytes memory _options,\n uint8 _optionType,\n bytes memory _option\n ) internal pure onlyType3(_options) returns (bytes memory) {\n return\n abi.encodePacked(\n _options,\n ExecutorOptions.WORKER_ID,\n _option.length.toUint16() + 1, // +1 for optionType\n _optionType,\n _option\n );\n }\n\n /**\n * @dev Adds a DVN option to the existing options.\n * @param _options The existing options container.\n * @param _dvnIdx The DVN index for the DVN option.\n * @param _optionType The type of the DVN option.\n * @param _option The encoded data for the DVN option.\n * @return options The updated options container.\n */\n function addDVNOption(\n bytes memory _options,\n uint8 _dvnIdx,\n uint8 _optionType,\n bytes memory _option\n ) internal pure onlyType3(_options) returns (bytes memory) {\n return\n abi.encodePacked(\n _options,\n DVNOptions.WORKER_ID,\n _option.length.toUint16() + 2, // +2 for optionType and dvnIdx\n _dvnIdx,\n _optionType,\n _option\n );\n }\n\n /**\n * @dev Encodes legacy options of type 1.\n * @param _executionGas The gasLimit value passed to lzReceive().\n * @return legacyOptions The encoded legacy options.\n */\n function encodeLegacyOptionsType1(uint256 _executionGas) internal pure returns (bytes memory) {\n if (_executionGas > type(uint128).max) revert InvalidSize(type(uint128).max, _executionGas);\n return abi.encodePacked(TYPE_1, _executionGas);\n }\n\n /**\n * @dev Encodes legacy options of type 2.\n * @param _executionGas The gasLimit value passed to lzReceive().\n * @param _nativeForDst The amount of native air dropped to the receiver.\n * @param _receiver The _nativeForDst receiver address.\n * @return legacyOptions The encoded legacy options of type 2.\n */\n function encodeLegacyOptionsType2(\n uint256 _executionGas,\n uint256 _nativeForDst,\n bytes memory _receiver // @dev Use bytes instead of bytes32 in legacy type 2 for _receiver.\n ) internal pure returns (bytes memory) {\n if (_executionGas > type(uint128).max) revert InvalidSize(type(uint128).max, _executionGas);\n if (_nativeForDst > type(uint128).max) revert InvalidSize(type(uint128).max, _nativeForDst);\n if (_receiver.length > 32) revert InvalidSize(32, _receiver.length);\n return abi.encodePacked(TYPE_2, _executionGas, _nativeForDst, _receiver);\n }\n}\n" + }, + "@layerzerolabs/oapp-evm/contracts/oapp/OAppCore.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\nimport { Ownable } from \"@openzeppelin/contracts/access/Ownable.sol\";\nimport { IOAppCore, ILayerZeroEndpointV2 } from \"./interfaces/IOAppCore.sol\";\n\n/**\n * @title OAppCore\n * @dev Abstract contract implementing the IOAppCore interface with basic OApp configurations.\n */\nabstract contract OAppCore is IOAppCore, Ownable {\n // The LayerZero endpoint associated with the given OApp\n ILayerZeroEndpointV2 public immutable endpoint;\n\n // Mapping to store peers associated with corresponding endpoints\n mapping(uint32 eid => bytes32 peer) public peers;\n\n /**\n * @dev Constructor to initialize the OAppCore with the provided endpoint and delegate.\n * @param _endpoint The address of the LOCAL Layer Zero endpoint.\n * @param _delegate The delegate capable of making OApp configurations inside of the endpoint.\n *\n * @dev The delegate typically should be set as the owner of the contract.\n */\n constructor(address _endpoint, address _delegate) {\n endpoint = ILayerZeroEndpointV2(_endpoint);\n\n if (_delegate == address(0)) revert InvalidDelegate();\n endpoint.setDelegate(_delegate);\n }\n\n /**\n * @notice Sets the peer address (OApp instance) for a corresponding endpoint.\n * @param _eid The endpoint ID.\n * @param _peer The address of the peer to be associated with the corresponding endpoint.\n *\n * @dev Only the owner/admin of the OApp can call this function.\n * @dev Indicates that the peer is trusted to send LayerZero messages to this OApp.\n * @dev Set this to bytes32(0) to remove the peer address.\n * @dev Peer is a bytes32 to accommodate non-evm chains.\n */\n function setPeer(uint32 _eid, bytes32 _peer) public virtual onlyOwner {\n _setPeer(_eid, _peer);\n }\n\n /**\n * @notice Sets the peer address (OApp instance) for a corresponding endpoint.\n * @param _eid The endpoint ID.\n * @param _peer The address of the peer to be associated with the corresponding endpoint.\n *\n * @dev Indicates that the peer is trusted to send LayerZero messages to this OApp.\n * @dev Set this to bytes32(0) to remove the peer address.\n * @dev Peer is a bytes32 to accommodate non-evm chains.\n */\n function _setPeer(uint32 _eid, bytes32 _peer) internal virtual {\n peers[_eid] = _peer;\n emit PeerSet(_eid, _peer);\n }\n\n /**\n * @notice Internal function to get the peer address associated with a specific endpoint; reverts if NOT set.\n * ie. the peer is set to bytes32(0).\n * @param _eid The endpoint ID.\n * @return peer The address of the peer associated with the specified endpoint.\n */\n function _getPeerOrRevert(uint32 _eid) internal view virtual returns (bytes32) {\n bytes32 peer = peers[_eid];\n if (peer == bytes32(0)) revert NoPeer(_eid);\n return peer;\n }\n\n /**\n * @notice Sets the delegate address for the OApp.\n * @param _delegate The address of the delegate to be set.\n *\n * @dev Only the owner/admin of the OApp can call this function.\n * @dev Provides the ability for a delegate to set configs, on behalf of the OApp, directly on the Endpoint contract.\n */\n function setDelegate(address _delegate) public onlyOwner {\n endpoint.setDelegate(_delegate);\n }\n}\n" + }, + "@layerzerolabs/oapp-evm/contracts/oapp/OAppSender.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\nimport { SafeERC20, IERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport { MessagingParams, MessagingFee, MessagingReceipt } from \"@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroEndpointV2.sol\";\nimport { OAppCore } from \"./OAppCore.sol\";\n\n/**\n * @title OAppSender\n * @dev Abstract contract implementing the OAppSender functionality for sending messages to a LayerZero endpoint.\n */\nabstract contract OAppSender is OAppCore {\n using SafeERC20 for IERC20;\n\n // Custom error messages\n error NotEnoughNative(uint256 msgValue);\n error LzTokenUnavailable();\n\n // @dev The version of the OAppSender implementation.\n // @dev Version is bumped when changes are made to this contract.\n uint64 internal constant SENDER_VERSION = 1;\n\n /**\n * @notice Retrieves the OApp version information.\n * @return senderVersion The version of the OAppSender.sol contract.\n * @return receiverVersion The version of the OAppReceiver.sol contract.\n *\n * @dev Providing 0 as the default for OAppReceiver version. Indicates that the OAppReceiver is not implemented.\n * ie. this is a SEND only OApp.\n * @dev If the OApp uses both OAppSender and OAppReceiver, then this needs to be override returning the correct versions\n */\n function oAppVersion() public view virtual returns (uint64 senderVersion, uint64 receiverVersion) {\n return (SENDER_VERSION, 0);\n }\n\n /**\n * @dev Internal function to interact with the LayerZero EndpointV2.quote() for fee calculation.\n * @param _dstEid The destination endpoint ID.\n * @param _message The message payload.\n * @param _options Additional options for the message.\n * @param _payInLzToken Flag indicating whether to pay the fee in LZ tokens.\n * @return fee The calculated MessagingFee for the message.\n * - nativeFee: The native fee for the message.\n * - lzTokenFee: The LZ token fee for the message.\n */\n function _quote(\n uint32 _dstEid,\n bytes memory _message,\n bytes memory _options,\n bool _payInLzToken\n ) internal view virtual returns (MessagingFee memory fee) {\n return\n endpoint.quote(\n MessagingParams(_dstEid, _getPeerOrRevert(_dstEid), _message, _options, _payInLzToken),\n address(this)\n );\n }\n\n /**\n * @dev Internal function to interact with the LayerZero EndpointV2.send() for sending a message.\n * @param _dstEid The destination endpoint ID.\n * @param _message The message payload.\n * @param _options Additional options for the message.\n * @param _fee The calculated LayerZero fee for the message.\n * - nativeFee: The native fee.\n * - lzTokenFee: The lzToken fee.\n * @param _refundAddress The address to receive any excess fee values sent to the endpoint.\n * @return receipt The receipt for the sent message.\n * - guid: The unique identifier for the sent message.\n * - nonce: The nonce of the sent message.\n * - fee: The LayerZero fee incurred for the message.\n */\n function _lzSend(\n uint32 _dstEid,\n bytes memory _message,\n bytes memory _options,\n MessagingFee memory _fee,\n address _refundAddress\n ) internal virtual returns (MessagingReceipt memory receipt) {\n // @dev Push corresponding fees to the endpoint, any excess is sent back to the _refundAddress from the endpoint.\n uint256 messageValue = _payNative(_fee.nativeFee);\n if (_fee.lzTokenFee > 0) _payLzToken(_fee.lzTokenFee);\n\n return\n // solhint-disable-next-line check-send-result\n endpoint.send{ value: messageValue }(\n MessagingParams(_dstEid, _getPeerOrRevert(_dstEid), _message, _options, _fee.lzTokenFee > 0),\n _refundAddress\n );\n }\n\n /**\n * @dev Internal function to pay the native fee associated with the message.\n * @param _nativeFee The native fee to be paid.\n * @return nativeFee The amount of native currency paid.\n *\n * @dev If the OApp needs to initiate MULTIPLE LayerZero messages in a single transaction,\n * this will need to be overridden because msg.value would contain multiple lzFees.\n * @dev Should be overridden in the event the LayerZero endpoint requires a different native currency.\n * @dev Some EVMs use an ERC20 as a method for paying transactions/gasFees.\n * @dev The endpoint is EITHER/OR, ie. it will NOT support both types of native payment at a time.\n */\n function _payNative(uint256 _nativeFee) internal virtual returns (uint256 nativeFee) {\n if (msg.value != _nativeFee) revert NotEnoughNative(msg.value);\n return _nativeFee;\n }\n\n /**\n * @dev Internal function to pay the LZ token fee associated with the message.\n * @param _lzTokenFee The LZ token fee to be paid.\n *\n * @dev If the caller is trying to pay in the specified lzToken, then the lzTokenFee is passed to the endpoint.\n * @dev Any excess sent, is passed back to the specified _refundAddress in the _lzSend().\n */\n function _payLzToken(uint256 _lzTokenFee) internal virtual {\n // @dev Cannot cache the token because it is not immutable in the endpoint.\n address lzToken = endpoint.lzToken();\n if (lzToken == address(0)) revert LzTokenUnavailable();\n\n // Pay LZ token fee by sending tokens to the endpoint.\n IERC20(lzToken).safeTransferFrom(msg.sender, address(endpoint), _lzTokenFee);\n }\n}\n" + }, + "@layerzerolabs/oft-evm/contracts/interfaces/IOFT.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\nimport { MessagingReceipt, MessagingFee } from \"@layerzerolabs/oapp-evm/contracts/oapp/OAppSender.sol\";\n\n/**\n * @dev Struct representing token parameters for the OFT send() operation.\n */\nstruct SendParam {\n uint32 dstEid; // Destination endpoint ID.\n bytes32 to; // Recipient address.\n uint256 amountLD; // Amount to send in local decimals.\n uint256 minAmountLD; // Minimum amount to send in local decimals.\n bytes extraOptions; // Additional options supplied by the caller to be used in the LayerZero message.\n bytes composeMsg; // The composed message for the send() operation.\n bytes oftCmd; // The OFT command to be executed, unused in default OFT implementations.\n}\n\n/**\n * @dev Struct representing OFT limit information.\n * @dev These amounts can change dynamically and are up the specific oft implementation.\n */\nstruct OFTLimit {\n uint256 minAmountLD; // Minimum amount in local decimals that can be sent to the recipient.\n uint256 maxAmountLD; // Maximum amount in local decimals that can be sent to the recipient.\n}\n\n/**\n * @dev Struct representing OFT receipt information.\n */\nstruct OFTReceipt {\n uint256 amountSentLD; // Amount of tokens ACTUALLY debited from the sender in local decimals.\n // @dev In non-default implementations, the amountReceivedLD COULD differ from this value.\n uint256 amountReceivedLD; // Amount of tokens to be received on the remote side.\n}\n\n/**\n * @dev Struct representing OFT fee details.\n * @dev Future proof mechanism to provide a standardized way to communicate fees to things like a UI.\n */\nstruct OFTFeeDetail {\n int256 feeAmountLD; // Amount of the fee in local decimals.\n string description; // Description of the fee.\n}\n\n/**\n * @title IOFT\n * @dev Interface for the OftChain (OFT) token.\n * @dev Does not inherit ERC20 to accommodate usage by OFTAdapter as well.\n * @dev This specific interface ID is '0x02e49c2c'.\n */\ninterface IOFT {\n // Custom error messages\n error InvalidLocalDecimals();\n error SlippageExceeded(uint256 amountLD, uint256 minAmountLD);\n\n // Events\n event OFTSent(\n bytes32 indexed guid, // GUID of the OFT message.\n uint32 dstEid, // Destination Endpoint ID.\n address indexed fromAddress, // Address of the sender on the src chain.\n uint256 amountSentLD, // Amount of tokens sent in local decimals.\n uint256 amountReceivedLD // Amount of tokens received in local decimals.\n );\n event OFTReceived(\n bytes32 indexed guid, // GUID of the OFT message.\n uint32 srcEid, // Source Endpoint ID.\n address indexed toAddress, // Address of the recipient on the dst chain.\n uint256 amountReceivedLD // Amount of tokens received in local decimals.\n );\n\n /**\n * @notice Retrieves interfaceID and the version of the OFT.\n * @return interfaceId The interface ID.\n * @return version The version.\n *\n * @dev interfaceId: This specific interface ID is '0x02e49c2c'.\n * @dev version: Indicates a cross-chain compatible msg encoding with other OFTs.\n * @dev If a new feature is added to the OFT cross-chain msg encoding, the version will be incremented.\n * ie. localOFT version(x,1) CAN send messages to remoteOFT version(x,1)\n */\n function oftVersion() external view returns (bytes4 interfaceId, uint64 version);\n\n /**\n * @notice Retrieves the address of the token associated with the OFT.\n * @return token The address of the ERC20 token implementation.\n */\n function token() external view returns (address);\n\n /**\n * @notice Indicates whether the OFT contract requires approval of the 'token()' to send.\n * @return requiresApproval Needs approval of the underlying token implementation.\n *\n * @dev Allows things like wallet implementers to determine integration requirements,\n * without understanding the underlying token implementation.\n */\n function approvalRequired() external view returns (bool);\n\n /**\n * @notice Retrieves the shared decimals of the OFT.\n * @return sharedDecimals The shared decimals of the OFT.\n */\n function sharedDecimals() external view returns (uint8);\n\n /**\n * @notice Provides the fee breakdown and settings data for an OFT. Unused in the default implementation.\n * @param _sendParam The parameters for the send operation.\n * @return limit The OFT limit information.\n * @return oftFeeDetails The details of OFT fees.\n * @return receipt The OFT receipt information.\n */\n function quoteOFT(\n SendParam calldata _sendParam\n ) external view returns (OFTLimit memory, OFTFeeDetail[] memory oftFeeDetails, OFTReceipt memory);\n\n /**\n * @notice Provides a quote for the send() operation.\n * @param _sendParam The parameters for the send() operation.\n * @param _payInLzToken Flag indicating whether the caller is paying in the LZ token.\n * @return fee The calculated LayerZero messaging fee from the send() operation.\n *\n * @dev MessagingFee: LayerZero msg fee\n * - nativeFee: The native fee.\n * - lzTokenFee: The lzToken fee.\n */\n function quoteSend(SendParam calldata _sendParam, bool _payInLzToken) external view returns (MessagingFee memory);\n\n /**\n * @notice Executes the send() operation.\n * @param _sendParam The parameters for the send operation.\n * @param _fee The fee information supplied by the caller.\n * - nativeFee: The native fee.\n * - lzTokenFee: The lzToken fee.\n * @param _refundAddress The address to receive any excess funds from fees etc. on the src.\n * @return receipt The LayerZero messaging receipt from the send() operation.\n * @return oftReceipt The OFT receipt information.\n *\n * @dev MessagingReceipt: LayerZero msg receipt\n * - guid: The unique identifier for the sent message.\n * - nonce: The nonce of the sent message.\n * - fee: The LayerZero fee incurred for the message.\n */\n function send(\n SendParam calldata _sendParam,\n MessagingFee calldata _fee,\n address _refundAddress\n ) external payable returns (MessagingReceipt memory, OFTReceipt memory);\n}\n" + }, + "@openzeppelin/contracts/access/AccessControl.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (access/AccessControl.sol)\n\npragma solidity ^0.8.0;\n\nimport \"./IAccessControl.sol\";\nimport \"../utils/Context.sol\";\nimport \"../utils/Strings.sol\";\nimport \"../utils/introspection/ERC165.sol\";\n\n/**\n * @dev Contract module that allows children to implement role-based access\n * control mechanisms. This is a lightweight version that doesn't allow enumerating role\n * members except through off-chain means by accessing the contract event logs. Some\n * applications may benefit from on-chain enumerability, for those cases see\n * {AccessControlEnumerable}.\n *\n * Roles are referred to by their `bytes32` identifier. These should be exposed\n * in the external API and be unique. The best way to achieve this is by\n * using `public constant` hash digests:\n *\n * ```\n * bytes32 public constant MY_ROLE = keccak256(\"MY_ROLE\");\n * ```\n *\n * Roles can be used to represent a set of permissions. To restrict access to a\n * function call, use {hasRole}:\n *\n * ```\n * function foo() public {\n * require(hasRole(MY_ROLE, msg.sender));\n * ...\n * }\n * ```\n *\n * Roles can be granted and revoked dynamically via the {grantRole} and\n * {revokeRole} functions. Each role has an associated admin role, and only\n * accounts that have a role's admin role can call {grantRole} and {revokeRole}.\n *\n * By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means\n * that only accounts with this role will be able to grant or revoke other\n * roles. More complex role relationships can be created by using\n * {_setRoleAdmin}.\n *\n * WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to\n * grant and revoke this role. Extra precautions should be taken to secure\n * accounts that have been granted it.\n */\nabstract contract AccessControl is Context, IAccessControl, ERC165 {\n struct RoleData {\n mapping(address => bool) members;\n bytes32 adminRole;\n }\n\n mapping(bytes32 => RoleData) private _roles;\n\n bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;\n\n /**\n * @dev Modifier that checks that an account has a specific role. Reverts\n * with a standardized message including the required role.\n *\n * The format of the revert reason is given by the following regular expression:\n *\n * /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/\n *\n * _Available since v4.1._\n */\n modifier onlyRole(bytes32 role) {\n _checkRole(role, _msgSender());\n _;\n }\n\n /**\n * @dev See {IERC165-supportsInterface}.\n */\n function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {\n return interfaceId == type(IAccessControl).interfaceId || super.supportsInterface(interfaceId);\n }\n\n /**\n * @dev Returns `true` if `account` has been granted `role`.\n */\n function hasRole(bytes32 role, address account) public view override returns (bool) {\n return _roles[role].members[account];\n }\n\n /**\n * @dev Revert with a standard message if `account` is missing `role`.\n *\n * The format of the revert reason is given by the following regular expression:\n *\n * /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/\n */\n function _checkRole(bytes32 role, address account) internal view {\n if (!hasRole(role, account)) {\n revert(\n string(\n abi.encodePacked(\n \"AccessControl: account \",\n Strings.toHexString(uint160(account), 20),\n \" is missing role \",\n Strings.toHexString(uint256(role), 32)\n )\n )\n );\n }\n }\n\n /**\n * @dev Returns the admin role that controls `role`. See {grantRole} and\n * {revokeRole}.\n *\n * To change a role's admin, use {_setRoleAdmin}.\n */\n function getRoleAdmin(bytes32 role) public view override returns (bytes32) {\n return _roles[role].adminRole;\n }\n\n /**\n * @dev Grants `role` to `account`.\n *\n * If `account` had not been already granted `role`, emits a {RoleGranted}\n * event.\n *\n * Requirements:\n *\n * - the caller must have ``role``'s admin role.\n */\n function grantRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {\n _grantRole(role, account);\n }\n\n /**\n * @dev Revokes `role` from `account`.\n *\n * If `account` had been granted `role`, emits a {RoleRevoked} event.\n *\n * Requirements:\n *\n * - the caller must have ``role``'s admin role.\n */\n function revokeRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {\n _revokeRole(role, account);\n }\n\n /**\n * @dev Revokes `role` from the calling account.\n *\n * Roles are often managed via {grantRole} and {revokeRole}: this function's\n * purpose is to provide a mechanism for accounts to lose their privileges\n * if they are compromised (such as when a trusted device is misplaced).\n *\n * If the calling account had been revoked `role`, emits a {RoleRevoked}\n * event.\n *\n * Requirements:\n *\n * - the caller must be `account`.\n */\n function renounceRole(bytes32 role, address account) public virtual override {\n require(account == _msgSender(), \"AccessControl: can only renounce roles for self\");\n\n _revokeRole(role, account);\n }\n\n /**\n * @dev Grants `role` to `account`.\n *\n * If `account` had not been already granted `role`, emits a {RoleGranted}\n * event. Note that unlike {grantRole}, this function doesn't perform any\n * checks on the calling account.\n *\n * [WARNING]\n * ====\n * This function should only be called from the constructor when setting\n * up the initial roles for the system.\n *\n * Using this function in any other way is effectively circumventing the admin\n * system imposed by {AccessControl}.\n * ====\n *\n * NOTE: This function is deprecated in favor of {_grantRole}.\n */\n function _setupRole(bytes32 role, address account) internal virtual {\n _grantRole(role, account);\n }\n\n /**\n * @dev Sets `adminRole` as ``role``'s admin role.\n *\n * Emits a {RoleAdminChanged} event.\n */\n function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {\n bytes32 previousAdminRole = getRoleAdmin(role);\n _roles[role].adminRole = adminRole;\n emit RoleAdminChanged(role, previousAdminRole, adminRole);\n }\n\n /**\n * @dev Grants `role` to `account`.\n *\n * Internal function without access restriction.\n */\n function _grantRole(bytes32 role, address account) internal virtual {\n if (!hasRole(role, account)) {\n _roles[role].members[account] = true;\n emit RoleGranted(role, account, _msgSender());\n }\n }\n\n /**\n * @dev Revokes `role` from `account`.\n *\n * Internal function without access restriction.\n */\n function _revokeRole(bytes32 role, address account) internal virtual {\n if (hasRole(role, account)) {\n _roles[role].members[account] = false;\n emit RoleRevoked(role, account, _msgSender());\n }\n }\n}\n" + }, + "@openzeppelin/contracts/access/AccessControlEnumerable.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (access/AccessControlEnumerable.sol)\n\npragma solidity ^0.8.0;\n\nimport \"./IAccessControlEnumerable.sol\";\nimport \"./AccessControl.sol\";\nimport \"../utils/structs/EnumerableSet.sol\";\n\n/**\n * @dev Extension of {AccessControl} that allows enumerating the members of each role.\n */\nabstract contract AccessControlEnumerable is IAccessControlEnumerable, AccessControl {\n using EnumerableSet for EnumerableSet.AddressSet;\n\n mapping(bytes32 => EnumerableSet.AddressSet) private _roleMembers;\n\n /**\n * @dev See {IERC165-supportsInterface}.\n */\n function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {\n return interfaceId == type(IAccessControlEnumerable).interfaceId || super.supportsInterface(interfaceId);\n }\n\n /**\n * @dev Returns one of the accounts that have `role`. `index` must be a\n * value between 0 and {getRoleMemberCount}, non-inclusive.\n *\n * Role bearers are not sorted in any particular way, and their ordering may\n * change at any point.\n *\n * WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure\n * you perform all queries on the same block. See the following\n * https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post]\n * for more information.\n */\n function getRoleMember(bytes32 role, uint256 index) public view override returns (address) {\n return _roleMembers[role].at(index);\n }\n\n /**\n * @dev Returns the number of accounts that have `role`. Can be used\n * together with {getRoleMember} to enumerate all bearers of a role.\n */\n function getRoleMemberCount(bytes32 role) public view override returns (uint256) {\n return _roleMembers[role].length();\n }\n\n /**\n * @dev Overload {_grantRole} to track enumerable memberships\n */\n function _grantRole(bytes32 role, address account) internal virtual override {\n super._grantRole(role, account);\n _roleMembers[role].add(account);\n }\n\n /**\n * @dev Overload {_revokeRole} to track enumerable memberships\n */\n function _revokeRole(bytes32 role, address account) internal virtual override {\n super._revokeRole(role, account);\n _roleMembers[role].remove(account);\n }\n}\n" + }, + "@openzeppelin/contracts/access/IAccessControl.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (access/IAccessControl.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev External interface of AccessControl declared to support ERC165 detection.\n */\ninterface IAccessControl {\n /**\n * @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`\n *\n * `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite\n * {RoleAdminChanged} not being emitted signaling this.\n *\n * _Available since v3.1._\n */\n event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);\n\n /**\n * @dev Emitted when `account` is granted `role`.\n *\n * `sender` is the account that originated the contract call, an admin role\n * bearer except when using {AccessControl-_setupRole}.\n */\n event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);\n\n /**\n * @dev Emitted when `account` is revoked `role`.\n *\n * `sender` is the account that originated the contract call:\n * - if using `revokeRole`, it is the admin role bearer\n * - if using `renounceRole`, it is the role bearer (i.e. `account`)\n */\n event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);\n\n /**\n * @dev Returns `true` if `account` has been granted `role`.\n */\n function hasRole(bytes32 role, address account) external view returns (bool);\n\n /**\n * @dev Returns the admin role that controls `role`. See {grantRole} and\n * {revokeRole}.\n *\n * To change a role's admin, use {AccessControl-_setRoleAdmin}.\n */\n function getRoleAdmin(bytes32 role) external view returns (bytes32);\n\n /**\n * @dev Grants `role` to `account`.\n *\n * If `account` had not been already granted `role`, emits a {RoleGranted}\n * event.\n *\n * Requirements:\n *\n * - the caller must have ``role``'s admin role.\n */\n function grantRole(bytes32 role, address account) external;\n\n /**\n * @dev Revokes `role` from `account`.\n *\n * If `account` had been granted `role`, emits a {RoleRevoked} event.\n *\n * Requirements:\n *\n * - the caller must have ``role``'s admin role.\n */\n function revokeRole(bytes32 role, address account) external;\n\n /**\n * @dev Revokes `role` from the calling account.\n *\n * Roles are often managed via {grantRole} and {revokeRole}: this function's\n * purpose is to provide a mechanism for accounts to lose their privileges\n * if they are compromised (such as when a trusted device is misplaced).\n *\n * If the calling account had been granted `role`, emits a {RoleRevoked}\n * event.\n *\n * Requirements:\n *\n * - the caller must be `account`.\n */\n function renounceRole(bytes32 role, address account) external;\n}\n" + }, + "@openzeppelin/contracts/access/IAccessControlEnumerable.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (access/IAccessControlEnumerable.sol)\n\npragma solidity ^0.8.0;\n\nimport \"./IAccessControl.sol\";\n\n/**\n * @dev External interface of AccessControlEnumerable declared to support ERC165 detection.\n */\ninterface IAccessControlEnumerable is IAccessControl {\n /**\n * @dev Returns one of the accounts that have `role`. `index` must be a\n * value between 0 and {getRoleMemberCount}, non-inclusive.\n *\n * Role bearers are not sorted in any particular way, and their ordering may\n * change at any point.\n *\n * WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure\n * you perform all queries on the same block. See the following\n * https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post]\n * for more information.\n */\n function getRoleMember(bytes32 role, uint256 index) external view returns (address);\n\n /**\n * @dev Returns the number of accounts that have `role`. Can be used\n * together with {getRoleMember} to enumerate all bearers of a role.\n */\n function getRoleMemberCount(bytes32 role) external view returns (uint256);\n}\n" + }, + "@openzeppelin/contracts/access/Ownable.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (access/Ownable.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../utils/Context.sol\";\n\n/**\n * @dev Contract module which provides a basic access control mechanism, where\n * there is an account (an owner) that can be granted exclusive access to\n * specific functions.\n *\n * By default, the owner account will be the one that deploys the contract. This\n * can later be changed with {transferOwnership}.\n *\n * This module is used through inheritance. It will make available the modifier\n * `onlyOwner`, which can be applied to your functions to restrict their use to\n * the owner.\n */\nabstract contract Ownable is Context {\n address private _owner;\n\n event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\n\n /**\n * @dev Initializes the contract setting the deployer as the initial owner.\n */\n constructor() {\n _transferOwnership(_msgSender());\n }\n\n /**\n * @dev Returns the address of the current owner.\n */\n function owner() public view virtual returns (address) {\n return _owner;\n }\n\n /**\n * @dev Throws if called by any account other than the owner.\n */\n modifier onlyOwner() {\n require(owner() == _msgSender(), \"Ownable: caller is not the owner\");\n _;\n }\n\n /**\n * @dev Leaves the contract without owner. It will not be possible to call\n * `onlyOwner` functions anymore. Can only be called by the current owner.\n *\n * NOTE: Renouncing ownership will leave the contract without an owner,\n * thereby removing any functionality that is only available to the owner.\n */\n function renounceOwnership() public virtual onlyOwner {\n _transferOwnership(address(0));\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\n * Can only be called by the current owner.\n */\n function transferOwnership(address newOwner) public virtual onlyOwner {\n require(newOwner != address(0), \"Ownable: new owner is the zero address\");\n _transferOwnership(newOwner);\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\n * Internal function without access restriction.\n */\n function _transferOwnership(address newOwner) internal virtual {\n address oldOwner = _owner;\n _owner = newOwner;\n emit OwnershipTransferred(oldOwner, newOwner);\n }\n}\n" + }, + "@openzeppelin/contracts/security/Pausable.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (security/Pausable.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../utils/Context.sol\";\n\n/**\n * @dev Contract module which allows children to implement an emergency stop\n * mechanism that can be triggered by an authorized account.\n *\n * This module is used through inheritance. It will make available the\n * modifiers `whenNotPaused` and `whenPaused`, which can be applied to\n * the functions of your contract. Note that they will not be pausable by\n * simply including this module, only once the modifiers are put in place.\n */\nabstract contract Pausable is Context {\n /**\n * @dev Emitted when the pause is triggered by `account`.\n */\n event Paused(address account);\n\n /**\n * @dev Emitted when the pause is lifted by `account`.\n */\n event Unpaused(address account);\n\n bool private _paused;\n\n /**\n * @dev Initializes the contract in unpaused state.\n */\n constructor() {\n _paused = false;\n }\n\n /**\n * @dev Returns true if the contract is paused, and false otherwise.\n */\n function paused() public view virtual returns (bool) {\n return _paused;\n }\n\n /**\n * @dev Modifier to make a function callable only when the contract is not paused.\n *\n * Requirements:\n *\n * - The contract must not be paused.\n */\n modifier whenNotPaused() {\n require(!paused(), \"Pausable: paused\");\n _;\n }\n\n /**\n * @dev Modifier to make a function callable only when the contract is paused.\n *\n * Requirements:\n *\n * - The contract must be paused.\n */\n modifier whenPaused() {\n require(paused(), \"Pausable: not paused\");\n _;\n }\n\n /**\n * @dev Triggers stopped state.\n *\n * Requirements:\n *\n * - The contract must not be paused.\n */\n function _pause() internal virtual whenNotPaused {\n _paused = true;\n emit Paused(_msgSender());\n }\n\n /**\n * @dev Returns to normal state.\n *\n * Requirements:\n *\n * - The contract must be paused.\n */\n function _unpause() internal virtual whenPaused {\n _paused = false;\n emit Unpaused(_msgSender());\n }\n}\n" + }, + "@openzeppelin/contracts/token/ERC20/ERC20.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (token/ERC20/ERC20.sol)\n\npragma solidity ^0.8.0;\n\nimport \"./IERC20.sol\";\nimport \"./extensions/IERC20Metadata.sol\";\nimport \"../../utils/Context.sol\";\n\n/**\n * @dev Implementation of the {IERC20} interface.\n *\n * This implementation is agnostic to the way tokens are created. This means\n * that a supply mechanism has to be added in a derived contract using {_mint}.\n * For a generic mechanism see {ERC20PresetMinterPauser}.\n *\n * TIP: For a detailed writeup see our guide\n * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How\n * to implement supply mechanisms].\n *\n * We have followed general OpenZeppelin Contracts guidelines: functions revert\n * instead returning `false` on failure. This behavior is nonetheless\n * conventional and does not conflict with the expectations of ERC20\n * applications.\n *\n * Additionally, an {Approval} event is emitted on calls to {transferFrom}.\n * This allows applications to reconstruct the allowance for all accounts just\n * by listening to said events. Other implementations of the EIP may not emit\n * these events, as it isn't required by the specification.\n *\n * Finally, the non-standard {decreaseAllowance} and {increaseAllowance}\n * functions have been added to mitigate the well-known issues around setting\n * allowances. See {IERC20-approve}.\n */\ncontract ERC20 is Context, IERC20, IERC20Metadata {\n mapping(address => uint256) private _balances;\n\n mapping(address => mapping(address => uint256)) private _allowances;\n\n uint256 private _totalSupply;\n\n string private _name;\n string private _symbol;\n\n /**\n * @dev Sets the values for {name} and {symbol}.\n *\n * The default value of {decimals} is 18. To select a different value for\n * {decimals} you should overload it.\n *\n * All two of these values are immutable: they can only be set once during\n * construction.\n */\n constructor(string memory name_, string memory symbol_) {\n _name = name_;\n _symbol = symbol_;\n }\n\n /**\n * @dev Returns the name of the token.\n */\n function name() public view virtual override returns (string memory) {\n return _name;\n }\n\n /**\n * @dev Returns the symbol of the token, usually a shorter version of the\n * name.\n */\n function symbol() public view virtual override returns (string memory) {\n return _symbol;\n }\n\n /**\n * @dev Returns the number of decimals used to get its user representation.\n * For example, if `decimals` equals `2`, a balance of `505` tokens should\n * be displayed to a user as `5.05` (`505 / 10 ** 2`).\n *\n * Tokens usually opt for a value of 18, imitating the relationship between\n * Ether and Wei. This is the value {ERC20} uses, unless this function is\n * overridden;\n *\n * NOTE: This information is only used for _display_ purposes: it in\n * no way affects any of the arithmetic of the contract, including\n * {IERC20-balanceOf} and {IERC20-transfer}.\n */\n function decimals() public view virtual override returns (uint8) {\n return 18;\n }\n\n /**\n * @dev See {IERC20-totalSupply}.\n */\n function totalSupply() public view virtual override returns (uint256) {\n return _totalSupply;\n }\n\n /**\n * @dev See {IERC20-balanceOf}.\n */\n function balanceOf(address account) public view virtual override returns (uint256) {\n return _balances[account];\n }\n\n /**\n * @dev See {IERC20-transfer}.\n *\n * Requirements:\n *\n * - `recipient` cannot be the zero address.\n * - the caller must have a balance of at least `amount`.\n */\n function transfer(address recipient, uint256 amount) public virtual override returns (bool) {\n _transfer(_msgSender(), recipient, amount);\n return true;\n }\n\n /**\n * @dev See {IERC20-allowance}.\n */\n function allowance(address owner, address spender) public view virtual override returns (uint256) {\n return _allowances[owner][spender];\n }\n\n /**\n * @dev See {IERC20-approve}.\n *\n * Requirements:\n *\n * - `spender` cannot be the zero address.\n */\n function approve(address spender, uint256 amount) public virtual override returns (bool) {\n _approve(_msgSender(), spender, amount);\n return true;\n }\n\n /**\n * @dev See {IERC20-transferFrom}.\n *\n * Emits an {Approval} event indicating the updated allowance. This is not\n * required by the EIP. See the note at the beginning of {ERC20}.\n *\n * Requirements:\n *\n * - `sender` and `recipient` cannot be the zero address.\n * - `sender` must have a balance of at least `amount`.\n * - the caller must have allowance for ``sender``'s tokens of at least\n * `amount`.\n */\n function transferFrom(\n address sender,\n address recipient,\n uint256 amount\n ) public virtual override returns (bool) {\n _transfer(sender, recipient, amount);\n\n uint256 currentAllowance = _allowances[sender][_msgSender()];\n require(currentAllowance >= amount, \"ERC20: transfer amount exceeds allowance\");\n unchecked {\n _approve(sender, _msgSender(), currentAllowance - amount);\n }\n\n return true;\n }\n\n /**\n * @dev Atomically increases the allowance granted to `spender` by the caller.\n *\n * This is an alternative to {approve} that can be used as a mitigation for\n * problems described in {IERC20-approve}.\n *\n * Emits an {Approval} event indicating the updated allowance.\n *\n * Requirements:\n *\n * - `spender` cannot be the zero address.\n */\n function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {\n _approve(_msgSender(), spender, _allowances[_msgSender()][spender] + addedValue);\n return true;\n }\n\n /**\n * @dev Atomically decreases the allowance granted to `spender` by the caller.\n *\n * This is an alternative to {approve} that can be used as a mitigation for\n * problems described in {IERC20-approve}.\n *\n * Emits an {Approval} event indicating the updated allowance.\n *\n * Requirements:\n *\n * - `spender` cannot be the zero address.\n * - `spender` must have allowance for the caller of at least\n * `subtractedValue`.\n */\n function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {\n uint256 currentAllowance = _allowances[_msgSender()][spender];\n require(currentAllowance >= subtractedValue, \"ERC20: decreased allowance below zero\");\n unchecked {\n _approve(_msgSender(), spender, currentAllowance - subtractedValue);\n }\n\n return true;\n }\n\n /**\n * @dev Moves `amount` of tokens from `sender` to `recipient`.\n *\n * This internal function is equivalent to {transfer}, and can be used to\n * e.g. implement automatic token fees, slashing mechanisms, etc.\n *\n * Emits a {Transfer} event.\n *\n * Requirements:\n *\n * - `sender` cannot be the zero address.\n * - `recipient` cannot be the zero address.\n * - `sender` must have a balance of at least `amount`.\n */\n function _transfer(\n address sender,\n address recipient,\n uint256 amount\n ) internal virtual {\n require(sender != address(0), \"ERC20: transfer from the zero address\");\n require(recipient != address(0), \"ERC20: transfer to the zero address\");\n\n _beforeTokenTransfer(sender, recipient, amount);\n\n uint256 senderBalance = _balances[sender];\n require(senderBalance >= amount, \"ERC20: transfer amount exceeds balance\");\n unchecked {\n _balances[sender] = senderBalance - amount;\n }\n _balances[recipient] += amount;\n\n emit Transfer(sender, recipient, amount);\n\n _afterTokenTransfer(sender, recipient, amount);\n }\n\n /** @dev Creates `amount` tokens and assigns them to `account`, increasing\n * the total supply.\n *\n * Emits a {Transfer} event with `from` set to the zero address.\n *\n * Requirements:\n *\n * - `account` cannot be the zero address.\n */\n function _mint(address account, uint256 amount) internal virtual {\n require(account != address(0), \"ERC20: mint to the zero address\");\n\n _beforeTokenTransfer(address(0), account, amount);\n\n _totalSupply += amount;\n _balances[account] += amount;\n emit Transfer(address(0), account, amount);\n\n _afterTokenTransfer(address(0), account, amount);\n }\n\n /**\n * @dev Destroys `amount` tokens from `account`, reducing the\n * total supply.\n *\n * Emits a {Transfer} event with `to` set to the zero address.\n *\n * Requirements:\n *\n * - `account` cannot be the zero address.\n * - `account` must have at least `amount` tokens.\n */\n function _burn(address account, uint256 amount) internal virtual {\n require(account != address(0), \"ERC20: burn from the zero address\");\n\n _beforeTokenTransfer(account, address(0), amount);\n\n uint256 accountBalance = _balances[account];\n require(accountBalance >= amount, \"ERC20: burn amount exceeds balance\");\n unchecked {\n _balances[account] = accountBalance - amount;\n }\n _totalSupply -= amount;\n\n emit Transfer(account, address(0), amount);\n\n _afterTokenTransfer(account, address(0), amount);\n }\n\n /**\n * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.\n *\n * This internal function is equivalent to `approve`, and can be used to\n * e.g. set automatic allowances for certain subsystems, etc.\n *\n * Emits an {Approval} event.\n *\n * Requirements:\n *\n * - `owner` cannot be the zero address.\n * - `spender` cannot be the zero address.\n */\n function _approve(\n address owner,\n address spender,\n uint256 amount\n ) internal virtual {\n require(owner != address(0), \"ERC20: approve from the zero address\");\n require(spender != address(0), \"ERC20: approve to the zero address\");\n\n _allowances[owner][spender] = amount;\n emit Approval(owner, spender, amount);\n }\n\n /**\n * @dev Hook that is called before any transfer of tokens. This includes\n * minting and burning.\n *\n * Calling conditions:\n *\n * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens\n * will be transferred to `to`.\n * - when `from` is zero, `amount` tokens will be minted for `to`.\n * - when `to` is zero, `amount` of ``from``'s tokens will be burned.\n * - `from` and `to` are never both zero.\n *\n * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].\n */\n function _beforeTokenTransfer(\n address from,\n address to,\n uint256 amount\n ) internal virtual {}\n\n /**\n * @dev Hook that is called after any transfer of tokens. This includes\n * minting and burning.\n *\n * Calling conditions:\n *\n * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens\n * has been transferred to `to`.\n * - when `from` is zero, `amount` tokens have been minted for `to`.\n * - when `to` is zero, `amount` of ``from``'s tokens have been burned.\n * - `from` and `to` are never both zero.\n *\n * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].\n */\n function _afterTokenTransfer(\n address from,\n address to,\n uint256 amount\n ) internal virtual {}\n}\n" + }, + "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../IERC20.sol\";\n\n/**\n * @dev Interface for the optional metadata functions from the ERC20 standard.\n *\n * _Available since v4.1._\n */\ninterface IERC20Metadata is IERC20 {\n /**\n * @dev Returns the name of the token.\n */\n function name() external view returns (string memory);\n\n /**\n * @dev Returns the symbol of the token.\n */\n function symbol() external view returns (string memory);\n\n /**\n * @dev Returns the decimals places of the token.\n */\n function decimals() external view returns (uint8);\n}\n" + }, + "@openzeppelin/contracts/token/ERC20/IERC20.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (token/ERC20/IERC20.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Interface of the ERC20 standard as defined in the EIP.\n */\ninterface IERC20 {\n /**\n * @dev Returns the amount of tokens in existence.\n */\n function totalSupply() external view returns (uint256);\n\n /**\n * @dev Returns the amount of tokens owned by `account`.\n */\n function balanceOf(address account) external view returns (uint256);\n\n /**\n * @dev Moves `amount` tokens from the caller's account to `recipient`.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transfer(address recipient, uint256 amount) external returns (bool);\n\n /**\n * @dev Returns the remaining number of tokens that `spender` will be\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\n * zero by default.\n *\n * This value changes when {approve} or {transferFrom} are called.\n */\n function allowance(address owner, address spender) external view returns (uint256);\n\n /**\n * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\n * that someone may use both the old and the new allowance by unfortunate\n * transaction ordering. One possible solution to mitigate this race\n * condition is to first reduce the spender's allowance to 0 and set the\n * desired value afterwards:\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\n *\n * Emits an {Approval} event.\n */\n function approve(address spender, uint256 amount) external returns (bool);\n\n /**\n * @dev Moves `amount` tokens from `sender` to `recipient` using the\n * allowance mechanism. `amount` is then deducted from the caller's\n * allowance.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transferFrom(\n address sender,\n address recipient,\n uint256 amount\n ) external returns (bool);\n\n /**\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\n * another (`to`).\n *\n * Note that `value` may be zero.\n */\n event Transfer(address indexed from, address indexed to, uint256 value);\n\n /**\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\n * a call to {approve}. `value` is the new allowance.\n */\n event Approval(address indexed owner, address indexed spender, uint256 value);\n}\n" + }, + "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (token/ERC20/utils/SafeERC20.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../IERC20.sol\";\nimport \"../../../utils/Address.sol\";\n\n/**\n * @title SafeERC20\n * @dev Wrappers around ERC20 operations that throw on failure (when the token\n * contract returns false). Tokens that return no value (and instead revert or\n * throw on failure) are also supported, non-reverting calls are assumed to be\n * successful.\n * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,\n * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.\n */\nlibrary SafeERC20 {\n using Address for address;\n\n function safeTransfer(\n IERC20 token,\n address to,\n uint256 value\n ) internal {\n _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));\n }\n\n function safeTransferFrom(\n IERC20 token,\n address from,\n address to,\n uint256 value\n ) internal {\n _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));\n }\n\n /**\n * @dev Deprecated. This function has issues similar to the ones found in\n * {IERC20-approve}, and its usage is discouraged.\n *\n * Whenever possible, use {safeIncreaseAllowance} and\n * {safeDecreaseAllowance} instead.\n */\n function safeApprove(\n IERC20 token,\n address spender,\n uint256 value\n ) internal {\n // safeApprove should only be called when setting an initial allowance,\n // or when resetting it to zero. To increase and decrease it, use\n // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'\n require(\n (value == 0) || (token.allowance(address(this), spender) == 0),\n \"SafeERC20: approve from non-zero to non-zero allowance\"\n );\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));\n }\n\n function safeIncreaseAllowance(\n IERC20 token,\n address spender,\n uint256 value\n ) internal {\n uint256 newAllowance = token.allowance(address(this), spender) + value;\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));\n }\n\n function safeDecreaseAllowance(\n IERC20 token,\n address spender,\n uint256 value\n ) internal {\n unchecked {\n uint256 oldAllowance = token.allowance(address(this), spender);\n require(oldAllowance >= value, \"SafeERC20: decreased allowance below zero\");\n uint256 newAllowance = oldAllowance - value;\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));\n }\n }\n\n /**\n * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement\n * on the return value: the return value is optional (but if data is returned, it must not be false).\n * @param token The token targeted by the call.\n * @param data The call data (encoded using abi.encode or one of its variants).\n */\n function _callOptionalReturn(IERC20 token, bytes memory data) private {\n // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since\n // we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that\n // the target address contains contract code and also asserts for success in the low-level call.\n\n bytes memory returndata = address(token).functionCall(data, \"SafeERC20: low-level call failed\");\n if (returndata.length > 0) {\n // Return data is optional\n require(abi.decode(returndata, (bool)), \"SafeERC20: ERC20 operation did not succeed\");\n }\n }\n}\n" + }, + "@openzeppelin/contracts/utils/Address.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (utils/Address.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Collection of functions related to the address type\n */\nlibrary Address {\n /**\n * @dev Returns true if `account` is a contract.\n *\n * [IMPORTANT]\n * ====\n * It is unsafe to assume that an address for which this function returns\n * false is an externally-owned account (EOA) and not a contract.\n *\n * Among others, `isContract` will return false for the following\n * types of addresses:\n *\n * - an externally-owned account\n * - a contract in construction\n * - an address where a contract will be created\n * - an address where a contract lived, but was destroyed\n * ====\n */\n function isContract(address account) internal view returns (bool) {\n // This method relies on extcodesize, which returns 0 for contracts in\n // construction, since the code is only stored at the end of the\n // constructor execution.\n\n uint256 size;\n assembly {\n size := extcodesize(account)\n }\n return size > 0;\n }\n\n /**\n * @dev Replacement for Solidity's `transfer`: sends `amount` wei to\n * `recipient`, forwarding all available gas and reverting on errors.\n *\n * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost\n * of certain opcodes, possibly making contracts go over the 2300 gas limit\n * imposed by `transfer`, making them unable to receive funds via\n * `transfer`. {sendValue} removes this limitation.\n *\n * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].\n *\n * IMPORTANT: because control is transferred to `recipient`, care must be\n * taken to not create reentrancy vulnerabilities. Consider using\n * {ReentrancyGuard} or the\n * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].\n */\n function sendValue(address payable recipient, uint256 amount) internal {\n require(address(this).balance >= amount, \"Address: insufficient balance\");\n\n (bool success, ) = recipient.call{value: amount}(\"\");\n require(success, \"Address: unable to send value, recipient may have reverted\");\n }\n\n /**\n * @dev Performs a Solidity function call using a low level `call`. A\n * plain `call` is an unsafe replacement for a function call: use this\n * function instead.\n *\n * If `target` reverts with a revert reason, it is bubbled up by this\n * function (like regular Solidity function calls).\n *\n * Returns the raw returned data. To convert to the expected return value,\n * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].\n *\n * Requirements:\n *\n * - `target` must be a contract.\n * - calling `target` with `data` must not revert.\n *\n * _Available since v3.1._\n */\n function functionCall(address target, bytes memory data) internal returns (bytes memory) {\n return functionCall(target, data, \"Address: low-level call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with\n * `errorMessage` as a fallback revert reason when `target` reverts.\n *\n * _Available since v3.1._\n */\n function functionCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal returns (bytes memory) {\n return functionCallWithValue(target, data, 0, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but also transferring `value` wei to `target`.\n *\n * Requirements:\n *\n * - the calling contract must have an ETH balance of at least `value`.\n * - the called Solidity function must be `payable`.\n *\n * _Available since v3.1._\n */\n function functionCallWithValue(\n address target,\n bytes memory data,\n uint256 value\n ) internal returns (bytes memory) {\n return functionCallWithValue(target, data, value, \"Address: low-level call with value failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but\n * with `errorMessage` as a fallback revert reason when `target` reverts.\n *\n * _Available since v3.1._\n */\n function functionCallWithValue(\n address target,\n bytes memory data,\n uint256 value,\n string memory errorMessage\n ) internal returns (bytes memory) {\n require(address(this).balance >= value, \"Address: insufficient balance for call\");\n require(isContract(target), \"Address: call to non-contract\");\n\n (bool success, bytes memory returndata) = target.call{value: value}(data);\n return verifyCallResult(success, returndata, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but performing a static call.\n *\n * _Available since v3.3._\n */\n function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {\n return functionStaticCall(target, data, \"Address: low-level static call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\n * but performing a static call.\n *\n * _Available since v3.3._\n */\n function functionStaticCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal view returns (bytes memory) {\n require(isContract(target), \"Address: static call to non-contract\");\n\n (bool success, bytes memory returndata) = target.staticcall(data);\n return verifyCallResult(success, returndata, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but performing a delegate call.\n *\n * _Available since v3.4._\n */\n function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {\n return functionDelegateCall(target, data, \"Address: low-level delegate call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\n * but performing a delegate call.\n *\n * _Available since v3.4._\n */\n function functionDelegateCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal returns (bytes memory) {\n require(isContract(target), \"Address: delegate call to non-contract\");\n\n (bool success, bytes memory returndata) = target.delegatecall(data);\n return verifyCallResult(success, returndata, errorMessage);\n }\n\n /**\n * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the\n * revert reason using the provided one.\n *\n * _Available since v4.3._\n */\n function verifyCallResult(\n bool success,\n bytes memory returndata,\n string memory errorMessage\n ) internal pure returns (bytes memory) {\n if (success) {\n return returndata;\n } else {\n // Look for revert reason and bubble it up if present\n if (returndata.length > 0) {\n // The easiest way to bubble the revert reason is using memory via assembly\n\n assembly {\n let returndata_size := mload(returndata)\n revert(add(32, returndata), returndata_size)\n }\n } else {\n revert(errorMessage);\n }\n }\n }\n}\n" + }, + "@openzeppelin/contracts/utils/Context.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Provides information about the current execution context, including the\n * sender of the transaction and its data. While these are generally available\n * via msg.sender and msg.data, they should not be accessed in such a direct\n * manner, since when dealing with meta-transactions the account sending and\n * paying for execution may not be the actual sender (as far as an application\n * is concerned).\n *\n * This contract is only required for intermediate, library-like contracts.\n */\nabstract contract Context {\n function _msgSender() internal view virtual returns (address) {\n return msg.sender;\n }\n\n function _msgData() internal view virtual returns (bytes calldata) {\n return msg.data;\n }\n}\n" + }, + "@openzeppelin/contracts/utils/introspection/ERC165.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol)\n\npragma solidity ^0.8.0;\n\nimport \"./IERC165.sol\";\n\n/**\n * @dev Implementation of the {IERC165} interface.\n *\n * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check\n * for the additional interface id that will be supported. For example:\n *\n * ```solidity\n * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {\n * return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);\n * }\n * ```\n *\n * Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation.\n */\nabstract contract ERC165 is IERC165 {\n /**\n * @dev See {IERC165-supportsInterface}.\n */\n function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {\n return interfaceId == type(IERC165).interfaceId;\n }\n}\n" + }, + "@openzeppelin/contracts/utils/introspection/IERC165.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Interface of the ERC165 standard, as defined in the\n * https://eips.ethereum.org/EIPS/eip-165[EIP].\n *\n * Implementers can declare support of contract interfaces, which can then be\n * queried by others ({ERC165Checker}).\n *\n * For an implementation, see {ERC165}.\n */\ninterface IERC165 {\n /**\n * @dev Returns true if this contract implements the interface defined by\n * `interfaceId`. See the corresponding\n * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]\n * to learn more about how these ids are created.\n *\n * This function call must use less than 30 000 gas.\n */\n function supportsInterface(bytes4 interfaceId) external view returns (bool);\n}\n" + }, + "@openzeppelin/contracts/utils/math/Math.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (utils/math/Math.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Standard math utilities missing in the Solidity language.\n */\nlibrary Math {\n /**\n * @dev Returns the largest of two numbers.\n */\n function max(uint256 a, uint256 b) internal pure returns (uint256) {\n return a >= b ? a : b;\n }\n\n /**\n * @dev Returns the smallest of two numbers.\n */\n function min(uint256 a, uint256 b) internal pure returns (uint256) {\n return a < b ? a : b;\n }\n\n /**\n * @dev Returns the average of two numbers. The result is rounded towards\n * zero.\n */\n function average(uint256 a, uint256 b) internal pure returns (uint256) {\n // (a + b) / 2 can overflow.\n return (a & b) + (a ^ b) / 2;\n }\n\n /**\n * @dev Returns the ceiling of the division of two numbers.\n *\n * This differs from standard division with `/` in that it rounds up instead\n * of rounding down.\n */\n function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {\n // (a + b - 1) / b can overflow on addition, so we distribute.\n return a / b + (a % b == 0 ? 0 : 1);\n }\n}\n" + }, + "@openzeppelin/contracts/utils/math/SafeCast.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (utils/math/SafeCast.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Wrappers over Solidity's uintXX/intXX casting operators with added overflow\n * checks.\n *\n * Downcasting from uint256/int256 in Solidity does not revert on overflow. This can\n * easily result in undesired exploitation or bugs, since developers usually\n * assume that overflows raise errors. `SafeCast` restores this intuition by\n * reverting the transaction when such an operation overflows.\n *\n * Using this library instead of the unchecked operations eliminates an entire\n * class of bugs, so it's recommended to use it always.\n *\n * Can be combined with {SafeMath} and {SignedSafeMath} to extend it to smaller types, by performing\n * all math on `uint256` and `int256` and then downcasting.\n */\nlibrary SafeCast {\n /**\n * @dev Returns the downcasted uint224 from uint256, reverting on\n * overflow (when the input is greater than largest uint224).\n *\n * Counterpart to Solidity's `uint224` operator.\n *\n * Requirements:\n *\n * - input must fit into 224 bits\n */\n function toUint224(uint256 value) internal pure returns (uint224) {\n require(value <= type(uint224).max, \"SafeCast: value doesn't fit in 224 bits\");\n return uint224(value);\n }\n\n /**\n * @dev Returns the downcasted uint128 from uint256, reverting on\n * overflow (when the input is greater than largest uint128).\n *\n * Counterpart to Solidity's `uint128` operator.\n *\n * Requirements:\n *\n * - input must fit into 128 bits\n */\n function toUint128(uint256 value) internal pure returns (uint128) {\n require(value <= type(uint128).max, \"SafeCast: value doesn't fit in 128 bits\");\n return uint128(value);\n }\n\n /**\n * @dev Returns the downcasted uint96 from uint256, reverting on\n * overflow (when the input is greater than largest uint96).\n *\n * Counterpart to Solidity's `uint96` operator.\n *\n * Requirements:\n *\n * - input must fit into 96 bits\n */\n function toUint96(uint256 value) internal pure returns (uint96) {\n require(value <= type(uint96).max, \"SafeCast: value doesn't fit in 96 bits\");\n return uint96(value);\n }\n\n /**\n * @dev Returns the downcasted uint64 from uint256, reverting on\n * overflow (when the input is greater than largest uint64).\n *\n * Counterpart to Solidity's `uint64` operator.\n *\n * Requirements:\n *\n * - input must fit into 64 bits\n */\n function toUint64(uint256 value) internal pure returns (uint64) {\n require(value <= type(uint64).max, \"SafeCast: value doesn't fit in 64 bits\");\n return uint64(value);\n }\n\n /**\n * @dev Returns the downcasted uint32 from uint256, reverting on\n * overflow (when the input is greater than largest uint32).\n *\n * Counterpart to Solidity's `uint32` operator.\n *\n * Requirements:\n *\n * - input must fit into 32 bits\n */\n function toUint32(uint256 value) internal pure returns (uint32) {\n require(value <= type(uint32).max, \"SafeCast: value doesn't fit in 32 bits\");\n return uint32(value);\n }\n\n /**\n * @dev Returns the downcasted uint16 from uint256, reverting on\n * overflow (when the input is greater than largest uint16).\n *\n * Counterpart to Solidity's `uint16` operator.\n *\n * Requirements:\n *\n * - input must fit into 16 bits\n */\n function toUint16(uint256 value) internal pure returns (uint16) {\n require(value <= type(uint16).max, \"SafeCast: value doesn't fit in 16 bits\");\n return uint16(value);\n }\n\n /**\n * @dev Returns the downcasted uint8 from uint256, reverting on\n * overflow (when the input is greater than largest uint8).\n *\n * Counterpart to Solidity's `uint8` operator.\n *\n * Requirements:\n *\n * - input must fit into 8 bits.\n */\n function toUint8(uint256 value) internal pure returns (uint8) {\n require(value <= type(uint8).max, \"SafeCast: value doesn't fit in 8 bits\");\n return uint8(value);\n }\n\n /**\n * @dev Converts a signed int256 into an unsigned uint256.\n *\n * Requirements:\n *\n * - input must be greater than or equal to 0.\n */\n function toUint256(int256 value) internal pure returns (uint256) {\n require(value >= 0, \"SafeCast: value must be positive\");\n return uint256(value);\n }\n\n /**\n * @dev Returns the downcasted int128 from int256, reverting on\n * overflow (when the input is less than smallest int128 or\n * greater than largest int128).\n *\n * Counterpart to Solidity's `int128` operator.\n *\n * Requirements:\n *\n * - input must fit into 128 bits\n *\n * _Available since v3.1._\n */\n function toInt128(int256 value) internal pure returns (int128) {\n require(value >= type(int128).min && value <= type(int128).max, \"SafeCast: value doesn't fit in 128 bits\");\n return int128(value);\n }\n\n /**\n * @dev Returns the downcasted int64 from int256, reverting on\n * overflow (when the input is less than smallest int64 or\n * greater than largest int64).\n *\n * Counterpart to Solidity's `int64` operator.\n *\n * Requirements:\n *\n * - input must fit into 64 bits\n *\n * _Available since v3.1._\n */\n function toInt64(int256 value) internal pure returns (int64) {\n require(value >= type(int64).min && value <= type(int64).max, \"SafeCast: value doesn't fit in 64 bits\");\n return int64(value);\n }\n\n /**\n * @dev Returns the downcasted int32 from int256, reverting on\n * overflow (when the input is less than smallest int32 or\n * greater than largest int32).\n *\n * Counterpart to Solidity's `int32` operator.\n *\n * Requirements:\n *\n * - input must fit into 32 bits\n *\n * _Available since v3.1._\n */\n function toInt32(int256 value) internal pure returns (int32) {\n require(value >= type(int32).min && value <= type(int32).max, \"SafeCast: value doesn't fit in 32 bits\");\n return int32(value);\n }\n\n /**\n * @dev Returns the downcasted int16 from int256, reverting on\n * overflow (when the input is less than smallest int16 or\n * greater than largest int16).\n *\n * Counterpart to Solidity's `int16` operator.\n *\n * Requirements:\n *\n * - input must fit into 16 bits\n *\n * _Available since v3.1._\n */\n function toInt16(int256 value) internal pure returns (int16) {\n require(value >= type(int16).min && value <= type(int16).max, \"SafeCast: value doesn't fit in 16 bits\");\n return int16(value);\n }\n\n /**\n * @dev Returns the downcasted int8 from int256, reverting on\n * overflow (when the input is less than smallest int8 or\n * greater than largest int8).\n *\n * Counterpart to Solidity's `int8` operator.\n *\n * Requirements:\n *\n * - input must fit into 8 bits.\n *\n * _Available since v3.1._\n */\n function toInt8(int256 value) internal pure returns (int8) {\n require(value >= type(int8).min && value <= type(int8).max, \"SafeCast: value doesn't fit in 8 bits\");\n return int8(value);\n }\n\n /**\n * @dev Converts an unsigned uint256 into a signed int256.\n *\n * Requirements:\n *\n * - input must be less than or equal to maxInt256.\n */\n function toInt256(uint256 value) internal pure returns (int256) {\n // Note: Unsafe cast below is okay because `type(int256).max` is guaranteed to be positive\n require(value <= uint256(type(int256).max), \"SafeCast: value doesn't fit in an int256\");\n return int256(value);\n }\n}\n" + }, + "@openzeppelin/contracts/utils/math/SafeMath.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (utils/math/SafeMath.sol)\n\npragma solidity ^0.8.0;\n\n// CAUTION\n// This version of SafeMath should only be used with Solidity 0.8 or later,\n// because it relies on the compiler's built in overflow checks.\n\n/**\n * @dev Wrappers over Solidity's arithmetic operations.\n *\n * NOTE: `SafeMath` is generally not needed starting with Solidity 0.8, since the compiler\n * now has built in overflow checking.\n */\nlibrary SafeMath {\n /**\n * @dev Returns the addition of two unsigned integers, with an overflow flag.\n *\n * _Available since v3.4._\n */\n function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {\n unchecked {\n uint256 c = a + b;\n if (c < a) return (false, 0);\n return (true, c);\n }\n }\n\n /**\n * @dev Returns the substraction of two unsigned integers, with an overflow flag.\n *\n * _Available since v3.4._\n */\n function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {\n unchecked {\n if (b > a) return (false, 0);\n return (true, a - b);\n }\n }\n\n /**\n * @dev Returns the multiplication of two unsigned integers, with an overflow flag.\n *\n * _Available since v3.4._\n */\n function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {\n unchecked {\n // Gas optimization: this is cheaper than requiring 'a' not being zero, but the\n // benefit is lost if 'b' is also tested.\n // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522\n if (a == 0) return (true, 0);\n uint256 c = a * b;\n if (c / a != b) return (false, 0);\n return (true, c);\n }\n }\n\n /**\n * @dev Returns the division of two unsigned integers, with a division by zero flag.\n *\n * _Available since v3.4._\n */\n function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {\n unchecked {\n if (b == 0) return (false, 0);\n return (true, a / b);\n }\n }\n\n /**\n * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.\n *\n * _Available since v3.4._\n */\n function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {\n unchecked {\n if (b == 0) return (false, 0);\n return (true, a % b);\n }\n }\n\n /**\n * @dev Returns the addition of two unsigned integers, reverting on\n * overflow.\n *\n * Counterpart to Solidity's `+` operator.\n *\n * Requirements:\n *\n * - Addition cannot overflow.\n */\n function add(uint256 a, uint256 b) internal pure returns (uint256) {\n return a + b;\n }\n\n /**\n * @dev Returns the subtraction of two unsigned integers, reverting on\n * overflow (when the result is negative).\n *\n * Counterpart to Solidity's `-` operator.\n *\n * Requirements:\n *\n * - Subtraction cannot overflow.\n */\n function sub(uint256 a, uint256 b) internal pure returns (uint256) {\n return a - b;\n }\n\n /**\n * @dev Returns the multiplication of two unsigned integers, reverting on\n * overflow.\n *\n * Counterpart to Solidity's `*` operator.\n *\n * Requirements:\n *\n * - Multiplication cannot overflow.\n */\n function mul(uint256 a, uint256 b) internal pure returns (uint256) {\n return a * b;\n }\n\n /**\n * @dev Returns the integer division of two unsigned integers, reverting on\n * division by zero. The result is rounded towards zero.\n *\n * Counterpart to Solidity's `/` operator.\n *\n * Requirements:\n *\n * - The divisor cannot be zero.\n */\n function div(uint256 a, uint256 b) internal pure returns (uint256) {\n return a / b;\n }\n\n /**\n * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),\n * reverting when dividing by zero.\n *\n * Counterpart to Solidity's `%` operator. This function uses a `revert`\n * opcode (which leaves remaining gas untouched) while Solidity uses an\n * invalid opcode to revert (consuming all remaining gas).\n *\n * Requirements:\n *\n * - The divisor cannot be zero.\n */\n function mod(uint256 a, uint256 b) internal pure returns (uint256) {\n return a % b;\n }\n\n /**\n * @dev Returns the subtraction of two unsigned integers, reverting with custom message on\n * overflow (when the result is negative).\n *\n * CAUTION: This function is deprecated because it requires allocating memory for the error\n * message unnecessarily. For custom revert reasons use {trySub}.\n *\n * Counterpart to Solidity's `-` operator.\n *\n * Requirements:\n *\n * - Subtraction cannot overflow.\n */\n function sub(\n uint256 a,\n uint256 b,\n string memory errorMessage\n ) internal pure returns (uint256) {\n unchecked {\n require(b <= a, errorMessage);\n return a - b;\n }\n }\n\n /**\n * @dev Returns the integer division of two unsigned integers, reverting with custom message on\n * division by zero. The result is rounded towards zero.\n *\n * Counterpart to Solidity's `/` operator. Note: this function uses a\n * `revert` opcode (which leaves remaining gas untouched) while Solidity\n * uses an invalid opcode to revert (consuming all remaining gas).\n *\n * Requirements:\n *\n * - The divisor cannot be zero.\n */\n function div(\n uint256 a,\n uint256 b,\n string memory errorMessage\n ) internal pure returns (uint256) {\n unchecked {\n require(b > 0, errorMessage);\n return a / b;\n }\n }\n\n /**\n * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),\n * reverting with custom message when dividing by zero.\n *\n * CAUTION: This function is deprecated because it requires allocating memory for the error\n * message unnecessarily. For custom revert reasons use {tryMod}.\n *\n * Counterpart to Solidity's `%` operator. This function uses a `revert`\n * opcode (which leaves remaining gas untouched) while Solidity uses an\n * invalid opcode to revert (consuming all remaining gas).\n *\n * Requirements:\n *\n * - The divisor cannot be zero.\n */\n function mod(\n uint256 a,\n uint256 b,\n string memory errorMessage\n ) internal pure returns (uint256) {\n unchecked {\n require(b > 0, errorMessage);\n return a % b;\n }\n }\n}\n" + }, + "@openzeppelin/contracts/utils/Strings.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (utils/Strings.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev String operations.\n */\nlibrary Strings {\n bytes16 private constant _HEX_SYMBOLS = \"0123456789abcdef\";\n\n /**\n * @dev Converts a `uint256` to its ASCII `string` decimal representation.\n */\n function toString(uint256 value) internal pure returns (string memory) {\n // Inspired by OraclizeAPI's implementation - MIT licence\n // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol\n\n if (value == 0) {\n return \"0\";\n }\n uint256 temp = value;\n uint256 digits;\n while (temp != 0) {\n digits++;\n temp /= 10;\n }\n bytes memory buffer = new bytes(digits);\n while (value != 0) {\n digits -= 1;\n buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));\n value /= 10;\n }\n return string(buffer);\n }\n\n /**\n * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.\n */\n function toHexString(uint256 value) internal pure returns (string memory) {\n if (value == 0) {\n return \"0x00\";\n }\n uint256 temp = value;\n uint256 length = 0;\n while (temp != 0) {\n length++;\n temp >>= 8;\n }\n return toHexString(value, length);\n }\n\n /**\n * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.\n */\n function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {\n bytes memory buffer = new bytes(2 * length + 2);\n buffer[0] = \"0\";\n buffer[1] = \"x\";\n for (uint256 i = 2 * length + 1; i > 1; --i) {\n buffer[i] = _HEX_SYMBOLS[value & 0xf];\n value >>= 4;\n }\n require(value == 0, \"Strings: hex length insufficient\");\n return string(buffer);\n }\n}\n" + }, + "@openzeppelin/contracts/utils/structs/EnumerableSet.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (utils/structs/EnumerableSet.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Library for managing\n * https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive\n * types.\n *\n * Sets have the following properties:\n *\n * - Elements are added, removed, and checked for existence in constant time\n * (O(1)).\n * - Elements are enumerated in O(n). No guarantees are made on the ordering.\n *\n * ```\n * contract Example {\n * // Add the library methods\n * using EnumerableSet for EnumerableSet.AddressSet;\n *\n * // Declare a set state variable\n * EnumerableSet.AddressSet private mySet;\n * }\n * ```\n *\n * As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)\n * and `uint256` (`UintSet`) are supported.\n */\nlibrary EnumerableSet {\n // To implement this library for multiple types with as little code\n // repetition as possible, we write it in terms of a generic Set type with\n // bytes32 values.\n // The Set implementation uses private functions, and user-facing\n // implementations (such as AddressSet) are just wrappers around the\n // underlying Set.\n // This means that we can only create new EnumerableSets for types that fit\n // in bytes32.\n\n struct Set {\n // Storage of set values\n bytes32[] _values;\n // Position of the value in the `values` array, plus 1 because index 0\n // means a value is not in the set.\n mapping(bytes32 => uint256) _indexes;\n }\n\n /**\n * @dev Add a value to a set. O(1).\n *\n * Returns true if the value was added to the set, that is if it was not\n * already present.\n */\n function _add(Set storage set, bytes32 value) private returns (bool) {\n if (!_contains(set, value)) {\n set._values.push(value);\n // The value is stored at length-1, but we add 1 to all indexes\n // and use 0 as a sentinel value\n set._indexes[value] = set._values.length;\n return true;\n } else {\n return false;\n }\n }\n\n /**\n * @dev Removes a value from a set. O(1).\n *\n * Returns true if the value was removed from the set, that is if it was\n * present.\n */\n function _remove(Set storage set, bytes32 value) private returns (bool) {\n // We read and store the value's index to prevent multiple reads from the same storage slot\n uint256 valueIndex = set._indexes[value];\n\n if (valueIndex != 0) {\n // Equivalent to contains(set, value)\n // To delete an element from the _values array in O(1), we swap the element to delete with the last one in\n // the array, and then remove the last element (sometimes called as 'swap and pop').\n // This modifies the order of the array, as noted in {at}.\n\n uint256 toDeleteIndex = valueIndex - 1;\n uint256 lastIndex = set._values.length - 1;\n\n if (lastIndex != toDeleteIndex) {\n bytes32 lastvalue = set._values[lastIndex];\n\n // Move the last value to the index where the value to delete is\n set._values[toDeleteIndex] = lastvalue;\n // Update the index for the moved value\n set._indexes[lastvalue] = valueIndex; // Replace lastvalue's index to valueIndex\n }\n\n // Delete the slot where the moved value was stored\n set._values.pop();\n\n // Delete the index for the deleted slot\n delete set._indexes[value];\n\n return true;\n } else {\n return false;\n }\n }\n\n /**\n * @dev Returns true if the value is in the set. O(1).\n */\n function _contains(Set storage set, bytes32 value) private view returns (bool) {\n return set._indexes[value] != 0;\n }\n\n /**\n * @dev Returns the number of values on the set. O(1).\n */\n function _length(Set storage set) private view returns (uint256) {\n return set._values.length;\n }\n\n /**\n * @dev Returns the value stored at position `index` in the set. O(1).\n *\n * Note that there are no guarantees on the ordering of values inside the\n * array, and it may change when more values are added or removed.\n *\n * Requirements:\n *\n * - `index` must be strictly less than {length}.\n */\n function _at(Set storage set, uint256 index) private view returns (bytes32) {\n return set._values[index];\n }\n\n /**\n * @dev Return the entire set in an array\n *\n * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed\n * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that\n * this function has an unbounded cost, and using it as part of a state-changing function may render the function\n * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.\n */\n function _values(Set storage set) private view returns (bytes32[] memory) {\n return set._values;\n }\n\n // Bytes32Set\n\n struct Bytes32Set {\n Set _inner;\n }\n\n /**\n * @dev Add a value to a set. O(1).\n *\n * Returns true if the value was added to the set, that is if it was not\n * already present.\n */\n function add(Bytes32Set storage set, bytes32 value) internal returns (bool) {\n return _add(set._inner, value);\n }\n\n /**\n * @dev Removes a value from a set. O(1).\n *\n * Returns true if the value was removed from the set, that is if it was\n * present.\n */\n function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) {\n return _remove(set._inner, value);\n }\n\n /**\n * @dev Returns true if the value is in the set. O(1).\n */\n function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {\n return _contains(set._inner, value);\n }\n\n /**\n * @dev Returns the number of values in the set. O(1).\n */\n function length(Bytes32Set storage set) internal view returns (uint256) {\n return _length(set._inner);\n }\n\n /**\n * @dev Returns the value stored at position `index` in the set. O(1).\n *\n * Note that there are no guarantees on the ordering of values inside the\n * array, and it may change when more values are added or removed.\n *\n * Requirements:\n *\n * - `index` must be strictly less than {length}.\n */\n function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {\n return _at(set._inner, index);\n }\n\n /**\n * @dev Return the entire set in an array\n *\n * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed\n * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that\n * this function has an unbounded cost, and using it as part of a state-changing function may render the function\n * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.\n */\n function values(Bytes32Set storage set) internal view returns (bytes32[] memory) {\n return _values(set._inner);\n }\n\n // AddressSet\n\n struct AddressSet {\n Set _inner;\n }\n\n /**\n * @dev Add a value to a set. O(1).\n *\n * Returns true if the value was added to the set, that is if it was not\n * already present.\n */\n function add(AddressSet storage set, address value) internal returns (bool) {\n return _add(set._inner, bytes32(uint256(uint160(value))));\n }\n\n /**\n * @dev Removes a value from a set. O(1).\n *\n * Returns true if the value was removed from the set, that is if it was\n * present.\n */\n function remove(AddressSet storage set, address value) internal returns (bool) {\n return _remove(set._inner, bytes32(uint256(uint160(value))));\n }\n\n /**\n * @dev Returns true if the value is in the set. O(1).\n */\n function contains(AddressSet storage set, address value) internal view returns (bool) {\n return _contains(set._inner, bytes32(uint256(uint160(value))));\n }\n\n /**\n * @dev Returns the number of values in the set. O(1).\n */\n function length(AddressSet storage set) internal view returns (uint256) {\n return _length(set._inner);\n }\n\n /**\n * @dev Returns the value stored at position `index` in the set. O(1).\n *\n * Note that there are no guarantees on the ordering of values inside the\n * array, and it may change when more values are added or removed.\n *\n * Requirements:\n *\n * - `index` must be strictly less than {length}.\n */\n function at(AddressSet storage set, uint256 index) internal view returns (address) {\n return address(uint160(uint256(_at(set._inner, index))));\n }\n\n /**\n * @dev Return the entire set in an array\n *\n * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed\n * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that\n * this function has an unbounded cost, and using it as part of a state-changing function may render the function\n * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.\n */\n function values(AddressSet storage set) internal view returns (address[] memory) {\n bytes32[] memory store = _values(set._inner);\n address[] memory result;\n\n assembly {\n result := store\n }\n\n return result;\n }\n\n // UintSet\n\n struct UintSet {\n Set _inner;\n }\n\n /**\n * @dev Add a value to a set. O(1).\n *\n * Returns true if the value was added to the set, that is if it was not\n * already present.\n */\n function add(UintSet storage set, uint256 value) internal returns (bool) {\n return _add(set._inner, bytes32(value));\n }\n\n /**\n * @dev Removes a value from a set. O(1).\n *\n * Returns true if the value was removed from the set, that is if it was\n * present.\n */\n function remove(UintSet storage set, uint256 value) internal returns (bool) {\n return _remove(set._inner, bytes32(value));\n }\n\n /**\n * @dev Returns true if the value is in the set. O(1).\n */\n function contains(UintSet storage set, uint256 value) internal view returns (bool) {\n return _contains(set._inner, bytes32(value));\n }\n\n /**\n * @dev Returns the number of values on the set. O(1).\n */\n function length(UintSet storage set) internal view returns (uint256) {\n return _length(set._inner);\n }\n\n /**\n * @dev Returns the value stored at position `index` in the set. O(1).\n *\n * Note that there are no guarantees on the ordering of values inside the\n * array, and it may change when more values are added or removed.\n *\n * Requirements:\n *\n * - `index` must be strictly less than {length}.\n */\n function at(UintSet storage set, uint256 index) internal view returns (uint256) {\n return uint256(_at(set._inner, index));\n }\n\n /**\n * @dev Return the entire set in an array\n *\n * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed\n * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that\n * this function has an unbounded cost, and using it as part of a state-changing function may render the function\n * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.\n */\n function values(UintSet storage set) internal view returns (uint256[] memory) {\n bytes32[] memory store = _values(set._inner);\n uint256[] memory result;\n\n assembly {\n result := store\n }\n\n return result;\n }\n}\n" + }, + "contracts/automation/AbstractCCIPBridgeHelperModule.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { AbstractSafeModule } from \"./AbstractSafeModule.sol\";\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\n\nimport { IRouterClient } from \"@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IRouterClient.sol\";\nimport { Client } from \"@chainlink/contracts-ccip/src/v0.8/ccip/libraries/Client.sol\";\n\nabstract contract AbstractCCIPBridgeHelperModule is AbstractSafeModule {\n /**\n * @notice Bridges a token from the source chain to the destination chain using CCIP\n * @param ccipRouter The CCIP router contract\n * @param destinationChainSelector The selector for the destination chain\n * @param token The token to bridge\n * @param amount The amount of token to bridge\n */\n function _bridgeTokenWithCCIP(\n IRouterClient ccipRouter,\n uint64 destinationChainSelector,\n IERC20 token,\n uint256 amount\n ) internal {\n bool success;\n\n // Approve CCIP Router to move the token\n success = safeContract.execTransactionFromModule(\n address(token),\n 0, // Value\n abi.encodeWithSelector(token.approve.selector, ccipRouter, amount),\n 0 // Call\n );\n require(success, \"Failed to approve token\");\n\n Client.EVMTokenAmount[]\n memory tokenAmounts = new Client.EVMTokenAmount[](1);\n Client.EVMTokenAmount memory tokenAmount = Client.EVMTokenAmount({\n token: address(token),\n amount: amount\n });\n tokenAmounts[0] = tokenAmount;\n\n Client.EVM2AnyMessage memory ccipMessage = Client.EVM2AnyMessage({\n receiver: abi.encode(address(safeContract)), // ABI-encoded receiver address\n data: abi.encode(\"\"),\n tokenAmounts: tokenAmounts,\n extraArgs: Client._argsToBytes(\n Client.EVMExtraArgsV1({ gasLimit: 0 })\n ),\n feeToken: address(0)\n });\n\n // Get CCIP fee\n uint256 ccipFee = ccipRouter.getFee(\n destinationChainSelector,\n ccipMessage\n );\n\n // Send CCIP message\n success = safeContract.execTransactionFromModule(\n address(ccipRouter),\n ccipFee, // Value\n abi.encodeWithSelector(\n ccipRouter.ccipSend.selector,\n destinationChainSelector,\n ccipMessage\n ),\n 0 // Call\n );\n require(success, \"Failed to send CCIP message\");\n }\n}\n" + }, + "contracts/automation/AbstractLZBridgeHelperModule.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { AbstractSafeModule } from \"./AbstractSafeModule.sol\";\n\nimport { IOFT, SendParam } from \"@layerzerolabs/oft-evm/contracts/interfaces/IOFT.sol\";\nimport { MessagingFee } from \"@layerzerolabs/oapp-evm/contracts/oapp/OAppSender.sol\";\nimport { OptionsBuilder } from \"@layerzerolabs/oapp-evm/contracts/oapp/libs/OptionsBuilder.sol\";\n\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\n\nabstract contract AbstractLZBridgeHelperModule is AbstractSafeModule {\n using OptionsBuilder for bytes;\n\n /**\n * @dev Bridges token using LayerZero.\n * @param lzEndpointId LayerZero endpoint id.\n * @param token Token to bridge.\n * @param lzAdapter LZ Adapter to use.\n * @param amount Amount of token to bridge.\n * @param slippageBps Slippage in 10^4 basis points.\n * @param isNativeToken Whether the token is native token.\n */\n function _bridgeTokenWithLz(\n uint32 lzEndpointId,\n IERC20 token,\n IOFT lzAdapter,\n uint256 amount,\n uint256 slippageBps,\n bool isNativeToken\n ) internal {\n bool success;\n\n if (!isNativeToken) {\n // Approve LZ Adapter to move the token\n success = safeContract.execTransactionFromModule(\n address(token),\n 0, // Value\n abi.encodeWithSelector(\n token.approve.selector,\n address(lzAdapter),\n amount\n ),\n 0 // Call\n );\n require(success, \"Failed to approve token\");\n }\n\n // Calculate minimum amount to receive\n uint256 minAmount = (amount * (10000 - slippageBps)) / 10000;\n\n // Hardcoded gaslimit of 400k\n bytes memory options = OptionsBuilder\n .newOptions()\n .addExecutorLzReceiveOption(400000, 0);\n\n // Build send param\n SendParam memory sendParam = SendParam({\n dstEid: lzEndpointId,\n to: bytes32(uint256(uint160(address(safeContract)))),\n amountLD: amount,\n minAmountLD: minAmount,\n extraOptions: options,\n composeMsg: bytes(\"\"),\n oftCmd: bytes(\"\")\n });\n\n // Compute fees\n MessagingFee memory msgFee = lzAdapter.quoteSend(sendParam, false);\n\n uint256 value = isNativeToken\n ? amount + msgFee.nativeFee\n : msgFee.nativeFee;\n\n // Execute transaction\n success = safeContract.execTransactionFromModule(\n address(lzAdapter),\n value,\n abi.encodeWithSelector(\n lzAdapter.send.selector,\n sendParam,\n msgFee,\n address(safeContract)\n ),\n 0\n );\n require(success, \"Failed to bridge token\");\n }\n}\n" + }, + "contracts/automation/AbstractSafeModule.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { AccessControlEnumerable } from \"@openzeppelin/contracts/access/AccessControlEnumerable.sol\";\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport { ISafe } from \"../interfaces/ISafe.sol\";\n\nabstract contract AbstractSafeModule is AccessControlEnumerable {\n ISafe public immutable safeContract;\n\n bytes32 public constant OPERATOR_ROLE = keccak256(\"OPERATOR_ROLE\");\n\n modifier onlySafe() {\n require(\n msg.sender == address(safeContract),\n \"Caller is not the safe contract\"\n );\n _;\n }\n\n modifier onlyOperator() {\n require(\n hasRole(OPERATOR_ROLE, msg.sender),\n \"Caller is not an operator\"\n );\n _;\n }\n\n constructor(address _safeContract) {\n safeContract = ISafe(_safeContract);\n _grantRole(DEFAULT_ADMIN_ROLE, address(safeContract));\n _grantRole(OPERATOR_ROLE, address(safeContract));\n }\n\n /**\n * @dev Helps recovering any tokens accidentally sent to this module.\n * @param token Token to transfer. 0x0 to transfer Native token.\n * @param amount Amount to transfer. 0 to transfer all balance.\n */\n function transferTokens(address token, uint256 amount) external onlySafe {\n if (address(token) == address(0)) {\n // Move ETH\n amount = amount > 0 ? amount : address(this).balance;\n payable(address(safeContract)).transfer(amount);\n return;\n }\n\n // Move all balance if amount set to 0\n amount = amount > 0 ? amount : IERC20(token).balanceOf(address(this));\n\n // Transfer to Safe contract\n // slither-disable-next-line unchecked-transfer unused-return\n IERC20(token).transfer(address(safeContract), amount);\n }\n\n receive() external payable {\n // Accept ETH to pay for bridge fees\n }\n}\n" + }, + "contracts/automation/BaseBridgeHelperModule.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n// solhint-disable-next-line max-line-length\nimport { AbstractCCIPBridgeHelperModule, AbstractSafeModule, IRouterClient } from \"./AbstractCCIPBridgeHelperModule.sol\";\n\nimport { AccessControlEnumerable } from \"@openzeppelin/contracts/access/AccessControlEnumerable.sol\";\n\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport { IERC4626 } from \"../../lib/openzeppelin/interfaces/IERC4626.sol\";\nimport { IWETH9 } from \"../interfaces/IWETH9.sol\";\nimport { IVault } from \"../interfaces/IVault.sol\";\n\nimport { BridgedWOETHStrategy } from \"../strategies/BridgedWOETHStrategy.sol\";\n\ncontract BaseBridgeHelperModule is\n AccessControlEnumerable,\n AbstractCCIPBridgeHelperModule\n{\n IVault public constant vault =\n IVault(0x98a0CbeF61bD2D21435f433bE4CD42B56B38CC93);\n IWETH9 public constant weth =\n IWETH9(0x4200000000000000000000000000000000000006);\n IERC20 public constant oethb =\n IERC20(0xDBFeFD2e8460a6Ee4955A68582F85708BAEA60A3);\n IERC4626 public constant bridgedWOETH =\n IERC4626(0xD8724322f44E5c58D7A815F542036fb17DbbF839);\n\n BridgedWOETHStrategy public constant bridgedWOETHStrategy =\n BridgedWOETHStrategy(0x80c864704DD06C3693ed5179190786EE38ACf835);\n\n IRouterClient public constant CCIP_ROUTER =\n IRouterClient(0x881e3A65B4d4a04dD529061dd0071cf975F58bCD);\n\n uint64 public constant CCIP_ETHEREUM_CHAIN_SELECTOR = 5009297550715157269;\n\n constructor(address _safeContract) AbstractSafeModule(_safeContract) {}\n\n /**\n * @dev Bridges wOETH to Ethereum.\n * @param woethAmount Amount of wOETH to bridge.\n */\n function bridgeWOETHToEthereum(uint256 woethAmount)\n public\n payable\n onlyOperator\n {\n _bridgeTokenWithCCIP(\n CCIP_ROUTER,\n CCIP_ETHEREUM_CHAIN_SELECTOR,\n IERC20(address(bridgedWOETH)),\n woethAmount\n );\n }\n\n /**\n * @dev Bridges WETH to Ethereum.\n * @param wethAmount Amount of WETH to bridge.\n */\n function bridgeWETHToEthereum(uint256 wethAmount)\n public\n payable\n onlyOperator\n {\n _bridgeTokenWithCCIP(\n CCIP_ROUTER,\n CCIP_ETHEREUM_CHAIN_SELECTOR,\n IERC20(address(weth)),\n wethAmount\n );\n }\n\n /**\n * @dev Deposits wOETH into the bridgedWOETH strategy.\n * @param woethAmount Amount of wOETH to deposit.\n * @param redeemWithVault Whether to redeem the wOETH for WETH using the Vault.\n * @return Amount of WETH received.\n */\n function depositWOETH(uint256 woethAmount, bool redeemWithVault)\n external\n onlyOperator\n returns (uint256)\n {\n return _depositWOETH(woethAmount, redeemWithVault);\n }\n\n /**\n * @dev Deposits wOETH into the bridgedWOETH strategy and bridges it to Ethereum.\n * @param woethAmount Amount of wOETH to deposit.\n * @return Amount of WETH received.\n */\n function depositWOETHAndBridgeWETH(uint256 woethAmount)\n external\n onlyOperator\n returns (uint256)\n {\n uint256 wethAmount = _depositWOETH(woethAmount, true);\n bridgeWETHToEthereum(wethAmount);\n return wethAmount;\n }\n\n /**\n * @dev Deposits wOETH into the bridgedWOETH strategy.\n * @param woethAmount Amount of wOETH to deposit.\n * @param redeemWithVault Whether to redeem the wOETH for WETH using the Vault.\n * @return Amount of WETH received.\n */\n function _depositWOETH(uint256 woethAmount, bool redeemWithVault)\n internal\n returns (uint256)\n {\n // Update oracle price\n bridgedWOETHStrategy.updateWOETHOraclePrice();\n\n // Rebase to account for any yields from price update\n vault.rebase();\n\n uint256 oethbAmount = oethb.balanceOf(address(safeContract));\n\n // Approve bridgedWOETH strategy to move wOETH\n bool success = safeContract.execTransactionFromModule(\n address(bridgedWOETH),\n 0, // Value\n abi.encodeWithSelector(\n bridgedWOETH.approve.selector,\n address(bridgedWOETHStrategy),\n woethAmount\n ),\n 0 // Call\n );\n\n // Deposit to bridgedWOETH strategy\n success = safeContract.execTransactionFromModule(\n address(bridgedWOETHStrategy),\n 0, // Value\n abi.encodeWithSelector(\n bridgedWOETHStrategy.depositBridgedWOETH.selector,\n woethAmount\n ),\n 0 // Call\n );\n require(success, \"Failed to deposit bridged WOETH\");\n\n oethbAmount = oethb.balanceOf(address(safeContract)) - oethbAmount;\n\n // Rebase to account for any yields from price update\n // and backing asset change from deposit\n vault.rebase();\n\n if (!redeemWithVault) {\n return oethbAmount;\n }\n\n // Redeem for WETH using Vault\n success = safeContract.execTransactionFromModule(\n address(vault),\n 0, // Value\n abi.encodeWithSelector(\n vault.redeem.selector,\n oethbAmount,\n oethbAmount\n ),\n 0 // Call\n );\n require(success, \"Failed to redeem OETHb\");\n\n return oethbAmount;\n }\n\n /**\n * @dev Deposits WETH into the Vault and redeems wOETH from the bridgedWOETH strategy.\n * @param wethAmount Amount of WETH to deposit.\n * @return Amount of wOETH received.\n */\n function depositWETHAndRedeemWOETH(uint256 wethAmount)\n external\n onlyOperator\n returns (uint256)\n {\n return _withdrawWOETH(wethAmount);\n }\n\n function depositWETHAndBridgeWOETH(uint256 wethAmount)\n external\n onlyOperator\n returns (uint256)\n {\n uint256 woethAmount = _withdrawWOETH(wethAmount);\n bridgeWOETHToEthereum(woethAmount);\n return woethAmount;\n }\n\n /**\n * @dev Withdraws wOETH from the bridgedWOETH strategy.\n * @param wethAmount Amount of WETH to use to withdraw.\n * @return Amount of wOETH received.\n */\n function _withdrawWOETH(uint256 wethAmount) internal returns (uint256) {\n // Approve Vault to move WETH\n bool success = safeContract.execTransactionFromModule(\n address(weth),\n 0, // Value\n abi.encodeWithSelector(\n weth.approve.selector,\n address(vault),\n wethAmount\n ),\n 0 // Call\n );\n require(success, \"Failed to approve WETH\");\n\n // Mint OETHb with WETH\n success = safeContract.execTransactionFromModule(\n address(vault),\n 0, // Value\n abi.encodeWithSelector(\n vault.mint.selector,\n address(weth),\n wethAmount,\n wethAmount\n ),\n 0 // Call\n );\n require(success, \"Failed to mint OETHb\");\n\n // Approve bridgedWOETH strategy to move OETHb\n success = safeContract.execTransactionFromModule(\n address(oethb),\n 0, // Value\n abi.encodeWithSelector(\n oethb.approve.selector,\n address(bridgedWOETHStrategy),\n wethAmount\n ),\n 0 // Call\n );\n require(success, \"Failed to approve OETHb\");\n\n uint256 woethAmount = bridgedWOETH.balanceOf(address(safeContract));\n\n // Withdraw from bridgedWOETH strategy\n success = safeContract.execTransactionFromModule(\n address(bridgedWOETHStrategy),\n 0, // Value\n abi.encodeWithSelector(\n bridgedWOETHStrategy.withdrawBridgedWOETH.selector,\n wethAmount\n ),\n 0 // Call\n );\n require(success, \"Failed to withdraw bridged WOETH\");\n\n woethAmount =\n bridgedWOETH.balanceOf(address(safeContract)) -\n woethAmount;\n\n return woethAmount;\n }\n}\n" + }, + "contracts/automation/CurvePoolBoosterBribesModule.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { AbstractSafeModule } from \"./AbstractSafeModule.sol\";\n\ninterface ICurvePoolBooster {\n function manageTotalRewardAmount(\n uint256 bridgeFee,\n uint256 additionalGasLimit\n ) external;\n\n function manageNumberOfPeriods(\n uint8 extraNumberOfPeriods,\n uint256 bridgeFee,\n uint256 additionalGasLimit\n ) external;\n\n function manageRewardPerVote(\n uint256 newMaxRewardPerVote,\n uint256 bridgeFee,\n uint256 additionalGasLimit\n ) external;\n}\n\ncontract CurvePoolBoosterBribesModule is AbstractSafeModule {\n address[] public POOLS;\n\n event PoolBoosterAddressAdded(address pool);\n event PoolBoosterAddressRemoved(address pool);\n\n constructor(\n address _safeContract,\n address _operator,\n address[] memory _pools\n ) AbstractSafeModule(_safeContract) {\n _grantRole(OPERATOR_ROLE, _operator);\n _addPoolBoosterAddress(_pools);\n }\n\n function addPoolBoosterAddress(address[] memory pools)\n external\n onlyOperator\n {\n _addPoolBoosterAddress(pools);\n }\n\n function _addPoolBoosterAddress(address[] memory pools) internal {\n for (uint256 i = 0; i < pools.length; i++) {\n POOLS.push(pools[i]);\n emit PoolBoosterAddressAdded(pools[i]);\n }\n }\n\n function removePoolBoosterAddress(address[] calldata pools)\n external\n onlyOperator\n {\n for (uint256 i = 0; i < pools.length; i++) {\n _removePoolBoosterAddress(pools[i]);\n }\n }\n\n function _removePoolBoosterAddress(address pool) internal {\n uint256 length = POOLS.length;\n for (uint256 i = 0; i < length; i++) {\n if (POOLS[i] == pool) {\n POOLS[i] = POOLS[length - 1];\n POOLS.pop();\n emit PoolBoosterAddressRemoved(pool);\n break;\n }\n }\n }\n\n function manageBribes() external onlyOperator {\n uint256[] memory rewardsPerVote = new uint256[](POOLS.length);\n _manageBribes(rewardsPerVote);\n }\n\n function manageBribes(uint256[] memory rewardsPerVote)\n external\n onlyOperator\n {\n require(POOLS.length == rewardsPerVote.length, \"Length mismatch\");\n _manageBribes(rewardsPerVote);\n }\n\n function _manageBribes(uint256[] memory rewardsPerVote)\n internal\n onlyOperator\n {\n uint256 length = POOLS.length;\n for (uint256 i = 0; i < length; i++) {\n address poolBoosterAddress = POOLS[i];\n\n // PoolBooster need to have a balance of at least 0.003 ether to operate\n // 0.001 ether are used for the bridge fee\n require(\n poolBoosterAddress.balance > 0.003 ether,\n \"Insufficient balance for bribes\"\n );\n\n require(\n safeContract.execTransactionFromModule(\n poolBoosterAddress,\n 0, // Value\n abi.encodeWithSelector(\n ICurvePoolBooster.manageNumberOfPeriods.selector,\n 1, // extraNumberOfPeriods\n 0.001 ether, // bridgeFee\n 1000000 // additionalGasLimit\n ),\n 0\n ),\n \"Manage number of periods failed\"\n );\n\n require(\n safeContract.execTransactionFromModule(\n poolBoosterAddress,\n 0, // Value\n abi.encodeWithSelector(\n ICurvePoolBooster.manageTotalRewardAmount.selector,\n 0.001 ether, // bridgeFee\n 1000000 // additionalGasLimit\n ),\n 0\n ),\n \"Manage total reward failed\"\n );\n\n // Skip setting reward per vote if it's zero\n if (rewardsPerVote[i] == 0) continue;\n require(\n safeContract.execTransactionFromModule(\n poolBoosterAddress,\n 0, // Value\n abi.encodeWithSelector(\n ICurvePoolBooster.manageRewardPerVote.selector,\n rewardsPerVote[i], // newMaxRewardPerVote\n 0.001 ether, // bridgeFee\n 1000000 // additionalGasLimit\n ),\n 0\n ),\n \"Set reward per vote failed\"\n );\n }\n }\n\n function getPools() external view returns (address[] memory) {\n return POOLS;\n }\n}\n" + }, + "contracts/automation/PlumeBridgeHelperModule.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { AbstractSafeModule } from \"./AbstractSafeModule.sol\";\nimport { AbstractLZBridgeHelperModule } from \"./AbstractLZBridgeHelperModule.sol\";\n\nimport { AccessControlEnumerable } from \"@openzeppelin/contracts/access/AccessControlEnumerable.sol\";\n\nimport { IOFT } from \"@layerzerolabs/oft-evm/contracts/interfaces/IOFT.sol\";\n\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport { IERC4626 } from \"../../lib/openzeppelin/interfaces/IERC4626.sol\";\nimport { IWETH9 } from \"../interfaces/IWETH9.sol\";\nimport { IVault } from \"../interfaces/IVault.sol\";\n\nimport { BridgedWOETHStrategy } from \"../strategies/BridgedWOETHStrategy.sol\";\n\ncontract PlumeBridgeHelperModule is\n AccessControlEnumerable,\n AbstractLZBridgeHelperModule\n{\n IVault public constant vault =\n IVault(0xc8c8F8bEA5631A8AF26440AF32a55002138cB76a);\n IWETH9 public constant weth =\n IWETH9(0xca59cA09E5602fAe8B629DeE83FfA819741f14be);\n IERC20 public constant oethp =\n IERC20(0xFCbe50DbE43bF7E5C88C6F6Fb9ef432D4165406E);\n IERC4626 public constant bridgedWOETH =\n IERC4626(0xD8724322f44E5c58D7A815F542036fb17DbbF839);\n\n uint32 public constant LZ_ETHEREUM_ENDPOINT_ID = 30101;\n IOFT public constant LZ_WOETH_OMNICHAIN_ADAPTER =\n IOFT(0x592CB6A596E7919930bF49a27AdAeCA7C055e4DB);\n IOFT public constant LZ_ETH_OMNICHAIN_ADAPTER =\n IOFT(0x4683CE822272CD66CEa73F5F1f9f5cBcaEF4F066);\n\n BridgedWOETHStrategy public constant bridgedWOETHStrategy =\n BridgedWOETHStrategy(0x1E3EdD5e019207D6355Ea77F724b1F1BF639B569);\n\n constructor(address _safeContract) AbstractSafeModule(_safeContract) {}\n\n /**\n * @dev Bridges wOETH to Ethereum.\n * @param woethAmount Amount of wOETH to bridge.\n * @param slippageBps Slippage in 10^4 basis points.\n */\n function bridgeWOETHToEthereum(uint256 woethAmount, uint256 slippageBps)\n public\n payable\n onlyOperator\n {\n _bridgeTokenWithLz(\n LZ_ETHEREUM_ENDPOINT_ID,\n IERC20(address(bridgedWOETH)),\n LZ_WOETH_OMNICHAIN_ADAPTER,\n woethAmount,\n slippageBps,\n false\n );\n }\n\n /**\n * @dev Bridges wETH to Ethereum.\n * @param wethAmount Amount of wETH to bridge.\n * @param slippageBps Slippage in 10^4 basis points.\n */\n function bridgeWETHToEthereum(uint256 wethAmount, uint256 slippageBps)\n public\n payable\n onlyOperator\n {\n _bridgeTokenWithLz(\n LZ_ETHEREUM_ENDPOINT_ID,\n IERC20(address(weth)),\n LZ_ETH_OMNICHAIN_ADAPTER,\n wethAmount,\n slippageBps,\n false\n );\n }\n\n /**\n * @dev Deposits wOETH into the bridgedWOETH strategy.\n * @param woethAmount Amount of wOETH to deposit.\n * @param redeemWithVault Whether to redeem with Vault.\n * @return Amount of OETHp received.\n */\n function depositWOETH(uint256 woethAmount, bool redeemWithVault)\n external\n onlyOperator\n returns (uint256)\n {\n return _depositWOETH(woethAmount, redeemWithVault);\n }\n\n /**\n * @dev Deposits wOETH into the bridgedWOETH strategy and bridges it to Ethereum.\n * @param woethAmount Amount of wOETH to deposit.\n * @param slippageBps Slippage in 10^4 basis points.\n * @return Amount of WETH received.\n */\n function depositWOETHAndBridgeWETH(uint256 woethAmount, uint256 slippageBps)\n external\n payable\n onlyOperator\n returns (uint256)\n {\n uint256 wethAmount = _depositWOETH(woethAmount, true);\n bridgeWETHToEthereum(wethAmount, slippageBps);\n return wethAmount;\n }\n\n /**\n * @dev Deposits wOETH into the bridgedWOETH strategy.\n * @param woethAmount Amount of wOETH to deposit.\n * @param redeemWithVault Whether to redeem with Vault.\n * @return Amount of OETHp received.\n */\n function _depositWOETH(uint256 woethAmount, bool redeemWithVault)\n internal\n returns (uint256)\n {\n // Update oracle price\n bridgedWOETHStrategy.updateWOETHOraclePrice();\n\n // Rebase to account for any yields from price update\n vault.rebase();\n\n uint256 oethpAmount = oethp.balanceOf(address(safeContract));\n\n // Approve bridgedWOETH strategy to move wOETH\n bool success = safeContract.execTransactionFromModule(\n address(bridgedWOETH),\n 0, // Value\n abi.encodeWithSelector(\n bridgedWOETH.approve.selector,\n address(bridgedWOETHStrategy),\n woethAmount\n ),\n 0 // Call\n );\n\n // Deposit to bridgedWOETH strategy\n success = safeContract.execTransactionFromModule(\n address(bridgedWOETHStrategy),\n 0, // Value\n abi.encodeWithSelector(\n bridgedWOETHStrategy.depositBridgedWOETH.selector,\n woethAmount\n ),\n 0 // Call\n );\n require(success, \"Failed to deposit bridged WOETH\");\n\n oethpAmount = oethp.balanceOf(address(safeContract)) - oethpAmount;\n\n // Rebase to account for any yields from price update\n // and backing asset change from deposit\n vault.rebase();\n\n if (!redeemWithVault) {\n return oethpAmount;\n }\n\n // Redeem for WETH using Vault\n success = safeContract.execTransactionFromModule(\n address(vault),\n 0, // Value\n abi.encodeWithSelector(\n vault.redeem.selector,\n oethpAmount,\n oethpAmount\n ),\n 0 // Call\n );\n require(success, \"Failed to redeem OETHp\");\n\n return oethpAmount;\n }\n\n /**\n * @dev Deposits wETH into the vault.\n * @param wethAmount Amount of wETH to deposit.\n * @return Amount of OETHp received.\n */\n function depositWETHAndRedeemWOETH(uint256 wethAmount)\n external\n onlyOperator\n returns (uint256)\n {\n return _withdrawWOETH(wethAmount);\n }\n\n /**\n * @dev Deposits wETH into the vault and bridges it to Ethereum.\n * @param wethAmount Amount of wETH to deposit.\n * @param slippageBps Slippage in 10^4 basis points.\n * @return Amount of WOETH received.\n */\n function depositWETHAndBridgeWOETH(uint256 wethAmount, uint256 slippageBps)\n external\n payable\n onlyOperator\n returns (uint256)\n {\n uint256 woethAmount = _withdrawWOETH(wethAmount);\n bridgeWOETHToEthereum(woethAmount, slippageBps);\n return woethAmount;\n }\n\n /**\n * @dev Withdraws wOETH from the bridgedWOETH strategy.\n * @param wethAmount Amount of WETH to use to withdraw.\n * @return Amount of wOETH received.\n */\n function _withdrawWOETH(uint256 wethAmount) internal returns (uint256) {\n // Approve Vault to move WETH\n bool success = safeContract.execTransactionFromModule(\n address(weth),\n 0, // Value\n abi.encodeWithSelector(\n weth.approve.selector,\n address(vault),\n wethAmount\n ),\n 0 // Call\n );\n require(success, \"Failed to approve WETH\");\n\n // Mint OETHp with WETH\n success = safeContract.execTransactionFromModule(\n address(vault),\n 0, // Value\n abi.encodeWithSelector(\n vault.mint.selector,\n address(weth),\n wethAmount,\n wethAmount\n ),\n 0 // Call\n );\n require(success, \"Failed to mint OETHp\");\n\n // Approve bridgedWOETH strategy to move OETHp\n success = safeContract.execTransactionFromModule(\n address(oethp),\n 0, // Value\n abi.encodeWithSelector(\n oethp.approve.selector,\n address(bridgedWOETHStrategy),\n wethAmount\n ),\n 0 // Call\n );\n require(success, \"Failed to approve OETHp\");\n\n uint256 woethAmount = bridgedWOETH.balanceOf(address(safeContract));\n\n // Withdraw from bridgedWOETH strategy\n success = safeContract.execTransactionFromModule(\n address(bridgedWOETHStrategy),\n 0, // Value\n abi.encodeWithSelector(\n bridgedWOETHStrategy.withdrawBridgedWOETH.selector,\n wethAmount\n ),\n 0 // Call\n );\n require(success, \"Failed to withdraw bridged WOETH\");\n\n woethAmount =\n bridgedWOETH.balanceOf(address(safeContract)) -\n woethAmount;\n\n return woethAmount;\n }\n}\n" + }, + "contracts/beacon/BeaconRoots.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title Library to retrieve beacon block roots.\n * @author Origin Protocol Inc\n */\nlibrary BeaconRoots {\n /// @notice The address of beacon block roots oracle\n /// See https://eips.ethereum.org/EIPS/eip-4788\n address internal constant BEACON_ROOTS_ADDRESS =\n 0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02;\n\n /// @notice Returns the beacon block root for the previous block.\n /// This comes from the Beacon Roots contract defined in EIP-4788.\n /// This will revert if the block is more than 8,191 blocks old as\n /// that is the size of the beacon root's ring buffer.\n /// @param timestamp The timestamp of the block for which to get the parent root.\n /// @return parentRoot The parent block root for the given timestamp.\n function parentBlockRoot(uint64 timestamp)\n internal\n view\n returns (bytes32 parentRoot)\n {\n // Call the Beacon Roots contract to get the parent block root.\n // This does not have a function signature, so we use a staticcall.\n (bool success, bytes memory result) = BEACON_ROOTS_ADDRESS.staticcall(\n abi.encode(timestamp)\n );\n\n require(success && result.length > 0, \"Invalid beacon timestamp\");\n parentRoot = abi.decode(result, (bytes32));\n }\n}\n" + }, + "contracts/beacon/PartialWithdrawal.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title Library to request full or partial withdrawals from validators on the beacon chain.\n * @author Origin Protocol Inc\n */\nlibrary PartialWithdrawal {\n /// @notice The address where the withdrawal request is sent to\n /// See https://eips.ethereum.org/EIPS/eip-7002\n address internal constant WITHDRAWAL_REQUEST_ADDRESS =\n 0x00000961Ef480Eb55e80D19ad83579A64c007002;\n\n /// @notice Requests a partial withdrawal for a given validator public key and amount.\n /// @param validatorPubKey The public key of the validator to withdraw from\n /// @param amount The amount of ETH to withdraw\n function request(bytes calldata validatorPubKey, uint64 amount)\n internal\n returns (uint256 fee_)\n {\n require(validatorPubKey.length == 48, \"Invalid validator byte length\");\n fee_ = fee();\n\n // Call the Withdrawal Request contract with the validator public key\n // and amount to be withdrawn packed together\n\n // This is a general purpose EL to CL request:\n // https://eips.ethereum.org/EIPS/eip-7685\n (bool success, ) = WITHDRAWAL_REQUEST_ADDRESS.call{ value: fee_ }(\n abi.encodePacked(validatorPubKey, amount)\n );\n\n require(success, \"Withdrawal request failed\");\n }\n\n /// @notice Gets fee for withdrawal requests contract on Beacon chain\n function fee() internal view returns (uint256) {\n // Get fee from the withdrawal request contract\n (bool success, bytes memory result) = WITHDRAWAL_REQUEST_ADDRESS\n .staticcall(\"\");\n\n require(success && result.length > 0, \"Failed to get fee\");\n return abi.decode(result, (uint256));\n }\n}\n" + }, + "contracts/buyback/AbstractBuyback.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { Strategizable } from \"../governance/Strategizable.sol\";\nimport \"../interfaces/chainlink/AggregatorV3Interface.sol\";\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport { ICVXLocker } from \"../interfaces/ICVXLocker.sol\";\nimport { ISwapper } from \"../interfaces/ISwapper.sol\";\n\nimport { Initializable } from \"../utils/Initializable.sol\";\n\nabstract contract AbstractBuyback is Initializable, Strategizable {\n using SafeERC20 for IERC20;\n\n event SwapRouterUpdated(address indexed _address);\n\n event RewardsSourceUpdated(address indexed _address);\n event TreasuryManagerUpdated(address indexed _address);\n event CVXShareBpsUpdated(uint256 bps);\n\n // Emitted whenever OUSD/OETH is swapped for OGN/CVX or any other token\n event OTokenBuyback(\n address indexed oToken,\n address indexed swappedFor,\n uint256 swapAmountIn,\n uint256 amountOut\n );\n\n // Address of 1-inch Swap Router\n address public swapRouter;\n\n // slither-disable-next-line constable-states\n address private __deprecated_ousd;\n // slither-disable-next-line constable-states\n address private __deprecated_ogv;\n // slither-disable-next-line constable-states\n address private __deprecated_usdt;\n // slither-disable-next-line constable-states\n address private __deprecated_weth9;\n\n // Address that receives OGN after swaps\n address public rewardsSource;\n\n // Address that receives all other tokens after swaps\n address public treasuryManager;\n\n // slither-disable-next-line constable-states\n uint256 private __deprecated_treasuryBps;\n\n address public immutable oToken;\n address public immutable ogn;\n address public immutable cvx;\n address public immutable cvxLocker;\n\n // Amount of `oToken` balance to use for OGN buyback\n uint256 public balanceForOGN;\n\n // Amount of `oToken` balance to use for CVX buyback\n uint256 public balanceForCVX;\n\n // Percentage of `oToken` balance to be used for CVX\n uint256 public cvxShareBps; // 10000 = 100%\n\n constructor(\n address _oToken,\n address _ogn,\n address _cvx,\n address _cvxLocker\n ) {\n // Make sure nobody owns the implementation contract\n _setGovernor(address(0));\n\n oToken = _oToken;\n ogn = _ogn;\n cvx = _cvx;\n cvxLocker = _cvxLocker;\n }\n\n /**\n * @param _swapRouter Address of Uniswap V3 Router\n * @param _strategistAddr Address of Strategist multi-sig wallet\n * @param _treasuryManagerAddr Address that receives the treasury's share of OUSD\n * @param _rewardsSource Address of RewardsSource contract\n * @param _cvxShareBps Percentage of balance to use for CVX\n */\n function initialize(\n address _swapRouter,\n address _strategistAddr,\n address _treasuryManagerAddr,\n address _rewardsSource,\n uint256 _cvxShareBps\n ) external onlyGovernor initializer {\n _setStrategistAddr(_strategistAddr);\n\n _setSwapRouter(_swapRouter);\n _setRewardsSource(_rewardsSource);\n\n _setTreasuryManager(_treasuryManagerAddr);\n\n _setCVXShareBps(_cvxShareBps);\n }\n\n /**\n * @dev Set address of Uniswap Universal Router for performing liquidation\n * of platform fee tokens. Setting to 0x0 will pause swaps.\n *\n * @param _router Address of the Uniswap Universal router\n */\n function setSwapRouter(address _router) external onlyGovernor {\n _setSwapRouter(_router);\n }\n\n function _setSwapRouter(address _router) internal {\n address oldRouter = swapRouter;\n swapRouter = _router;\n\n if (oldRouter != address(0)) {\n // Remove allowance of old router, if any\n\n if (IERC20(ogn).allowance(address(this), oldRouter) != 0) {\n // slither-disable-next-line unused-return\n IERC20(ogn).safeApprove(oldRouter, 0);\n }\n\n if (IERC20(cvx).allowance(address(this), oldRouter) != 0) {\n // slither-disable-next-line unused-return\n IERC20(cvx).safeApprove(oldRouter, 0);\n }\n }\n\n emit SwapRouterUpdated(_router);\n }\n\n /**\n * @dev Sets the address that receives the OGN buyback rewards\n * @param _address Address\n */\n function setRewardsSource(address _address) external onlyGovernor {\n _setRewardsSource(_address);\n }\n\n function _setRewardsSource(address _address) internal {\n require(_address != address(0), \"Address not set\");\n rewardsSource = _address;\n emit RewardsSourceUpdated(_address);\n }\n\n /**\n * @dev Sets the address that can receive and manage the funds for Treasury\n * @param _address Address\n */\n function setTreasuryManager(address _address) external onlyGovernor {\n _setTreasuryManager(_address);\n }\n\n function _setTreasuryManager(address _address) internal {\n require(_address != address(0), \"Address not set\");\n treasuryManager = _address;\n emit TreasuryManagerUpdated(_address);\n }\n\n /**\n * @dev Sets the percentage of oToken to use for Flywheel tokens\n * @param _bps BPS, 10000 to 100%\n */\n function setCVXShareBps(uint256 _bps) external onlyGovernor {\n _setCVXShareBps(_bps);\n }\n\n function _setCVXShareBps(uint256 _bps) internal {\n require(_bps <= 10000, \"Invalid bps value\");\n cvxShareBps = _bps;\n emit CVXShareBpsUpdated(_bps);\n }\n\n /**\n * @dev Computes the split of oToken balance that can be\n * used for OGN and CVX buybacks.\n */\n function _updateBuybackSplits()\n internal\n returns (uint256 _balanceForOGN, uint256 _balanceForCVX)\n {\n _balanceForOGN = balanceForOGN;\n _balanceForCVX = balanceForCVX;\n\n uint256 totalBalance = IERC20(oToken).balanceOf(address(this));\n uint256 unsplitBalance = totalBalance - _balanceForOGN - _balanceForCVX;\n\n // Check if all balance is accounted for\n if (unsplitBalance != 0) {\n // If not, split unaccounted balance based on `cvxShareBps`\n uint256 addToCVX = (unsplitBalance * cvxShareBps) / 10000;\n _balanceForCVX = _balanceForCVX + addToCVX;\n _balanceForOGN = _balanceForOGN + unsplitBalance - addToCVX;\n\n // Update storage\n balanceForOGN = _balanceForOGN;\n balanceForCVX = _balanceForCVX;\n }\n }\n\n function updateBuybackSplits() external onlyGovernor {\n // slither-disable-next-line unused-return\n _updateBuybackSplits();\n }\n\n function _swapToken(\n address tokenOut,\n uint256 oTokenAmount,\n uint256 minAmountOut,\n bytes calldata swapData\n ) internal returns (uint256 amountOut) {\n require(oTokenAmount > 0, \"Invalid Swap Amount\");\n require(swapRouter != address(0), \"Swap Router not set\");\n require(minAmountOut > 0, \"Invalid minAmount\");\n\n // Transfer OToken to Swapper for swapping\n // slither-disable-next-line unchecked-transfer unused-return\n IERC20(oToken).transfer(swapRouter, oTokenAmount);\n\n // Swap\n amountOut = ISwapper(swapRouter).swap(\n oToken,\n tokenOut,\n oTokenAmount,\n minAmountOut,\n swapData\n );\n\n require(amountOut >= minAmountOut, \"Higher Slippage\");\n\n emit OTokenBuyback(oToken, tokenOut, oTokenAmount, amountOut);\n }\n\n /**\n * @dev Swaps `oTokenAmount` to OGN\n * @param oTokenAmount Amount of OUSD/OETH to swap\n * @param minOGN Minimum OGN to receive for oTokenAmount\n * @param swapData 1inch Swap Data\n */\n function swapForOGN(\n uint256 oTokenAmount,\n uint256 minOGN,\n bytes calldata swapData\n ) external onlyGovernorOrStrategist nonReentrant {\n (uint256 _amountForOGN, ) = _updateBuybackSplits();\n require(_amountForOGN >= oTokenAmount, \"Balance underflow\");\n require(rewardsSource != address(0), \"RewardsSource contract not set\");\n\n unchecked {\n // Subtract the amount to swap from net balance\n balanceForOGN = _amountForOGN - oTokenAmount;\n }\n\n uint256 ognReceived = _swapToken(ogn, oTokenAmount, minOGN, swapData);\n\n // Transfer OGN received to RewardsSource contract\n // slither-disable-next-line unchecked-transfer unused-return\n IERC20(ogn).transfer(rewardsSource, ognReceived);\n }\n\n /**\n * @dev Swaps `oTokenAmount` to CVX\n * @param oTokenAmount Amount of OUSD/OETH to swap\n * @param minCVX Minimum CVX to receive for oTokenAmount\n * @param swapData 1inch Swap Data\n */\n function swapForCVX(\n uint256 oTokenAmount,\n uint256 minCVX,\n bytes calldata swapData\n ) external onlyGovernorOrStrategist nonReentrant {\n (, uint256 _amountForCVX) = _updateBuybackSplits();\n require(_amountForCVX >= oTokenAmount, \"Balance underflow\");\n\n unchecked {\n // Subtract the amount to swap from net balance\n balanceForCVX = _amountForCVX - oTokenAmount;\n }\n\n uint256 cvxReceived = _swapToken(cvx, oTokenAmount, minCVX, swapData);\n\n // Lock all CVX\n _lockAllCVX(cvxReceived);\n }\n\n /**\n * @dev Locks all CVX held by the contract on behalf of the Treasury Manager\n */\n function lockAllCVX() external onlyGovernorOrStrategist {\n _lockAllCVX(IERC20(cvx).balanceOf(address(this)));\n }\n\n function _lockAllCVX(uint256 cvxAmount) internal {\n require(\n treasuryManager != address(0),\n \"Treasury manager address not set\"\n );\n\n // Lock all available CVX on behalf of `treasuryManager`\n ICVXLocker(cvxLocker).lock(treasuryManager, cvxAmount, 0);\n }\n\n /**\n * @dev Approve CVX Locker to move CVX held by this contract\n */\n function safeApproveAllTokens() external onlyGovernorOrStrategist {\n IERC20(cvx).safeApprove(cvxLocker, type(uint256).max);\n }\n\n /**\n * @notice Owner function to withdraw a specific amount of a token\n * @param token token to be transferered\n * @param amount amount of the token to be transferred\n */\n function transferToken(address token, uint256 amount)\n external\n onlyGovernor\n nonReentrant\n {\n IERC20(token).safeTransfer(_governor(), amount);\n }\n}\n" + }, + "contracts/buyback/ARMBuyback.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { AbstractBuyback } from \"./AbstractBuyback.sol\";\n\ncontract ARMBuyback is AbstractBuyback {\n constructor(\n address _oToken,\n address _ogn,\n address _cvx,\n address _cvxLocker\n ) AbstractBuyback(_oToken, _ogn, _cvx, _cvxLocker) {}\n}\n" + }, + "contracts/buyback/OETHBuyback.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { AbstractBuyback } from \"./AbstractBuyback.sol\";\n\ncontract OETHBuyback is AbstractBuyback {\n constructor(\n address _oToken,\n address _ogn,\n address _cvx,\n address _cvxLocker\n ) AbstractBuyback(_oToken, _ogn, _cvx, _cvxLocker) {}\n}\n" + }, + "contracts/buyback/OUSDBuyback.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { AbstractBuyback } from \"./AbstractBuyback.sol\";\n\ncontract OUSDBuyback is AbstractBuyback {\n constructor(\n address _oToken,\n address _ogn,\n address _cvx,\n address _cvxLocker\n ) AbstractBuyback(_oToken, _ogn, _cvx, _cvxLocker) {}\n}\n" + }, + "contracts/governance/Governable.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title Base for contracts that are managed by the Origin Protocol's Governor.\n * @dev Copy of the openzeppelin Ownable.sol contract with nomenclature change\n * from owner to governor and renounce methods removed. Does not use\n * Context.sol like Ownable.sol does for simplification.\n * @author Origin Protocol Inc\n */\nabstract contract Governable {\n // Storage position of the owner and pendingOwner of the contract\n // keccak256(\"OUSD.governor\");\n bytes32 private constant governorPosition =\n 0x7bea13895fa79d2831e0a9e28edede30099005a50d652d8957cf8a607ee6ca4a;\n\n // keccak256(\"OUSD.pending.governor\");\n bytes32 private constant pendingGovernorPosition =\n 0x44c4d30b2eaad5130ad70c3ba6972730566f3e6359ab83e800d905c61b1c51db;\n\n // keccak256(\"OUSD.reentry.status\");\n bytes32 private constant reentryStatusPosition =\n 0x53bf423e48ed90e97d02ab0ebab13b2a235a6bfbe9c321847d5c175333ac4535;\n\n // See OpenZeppelin ReentrancyGuard implementation\n uint256 constant _NOT_ENTERED = 1;\n uint256 constant _ENTERED = 2;\n\n event PendingGovernorshipTransfer(\n address indexed previousGovernor,\n address indexed newGovernor\n );\n\n event GovernorshipTransferred(\n address indexed previousGovernor,\n address indexed newGovernor\n );\n\n /**\n * @notice Returns the address of the current Governor.\n */\n function governor() public view returns (address) {\n return _governor();\n }\n\n /**\n * @dev Returns the address of the current Governor.\n */\n function _governor() internal view returns (address governorOut) {\n bytes32 position = governorPosition;\n // solhint-disable-next-line no-inline-assembly\n assembly {\n governorOut := sload(position)\n }\n }\n\n /**\n * @dev Returns the address of the pending Governor.\n */\n function _pendingGovernor()\n internal\n view\n returns (address pendingGovernor)\n {\n bytes32 position = pendingGovernorPosition;\n // solhint-disable-next-line no-inline-assembly\n assembly {\n pendingGovernor := sload(position)\n }\n }\n\n /**\n * @dev Throws if called by any account other than the Governor.\n */\n modifier onlyGovernor() {\n require(isGovernor(), \"Caller is not the Governor\");\n _;\n }\n\n /**\n * @notice Returns true if the caller is the current Governor.\n */\n function isGovernor() public view returns (bool) {\n return msg.sender == _governor();\n }\n\n function _setGovernor(address newGovernor) internal {\n emit GovernorshipTransferred(_governor(), newGovernor);\n\n bytes32 position = governorPosition;\n // solhint-disable-next-line no-inline-assembly\n assembly {\n sstore(position, newGovernor)\n }\n }\n\n /**\n * @dev Prevents a contract from calling itself, directly or indirectly.\n * Calling a `nonReentrant` function from another `nonReentrant`\n * function is not supported. It is possible to prevent this from happening\n * by making the `nonReentrant` function external, and make it call a\n * `private` function that does the actual work.\n */\n modifier nonReentrant() {\n bytes32 position = reentryStatusPosition;\n uint256 _reentry_status;\n // solhint-disable-next-line no-inline-assembly\n assembly {\n _reentry_status := sload(position)\n }\n\n // On the first call to nonReentrant, _notEntered will be true\n require(_reentry_status != _ENTERED, \"Reentrant call\");\n\n // Any calls to nonReentrant after this point will fail\n // solhint-disable-next-line no-inline-assembly\n assembly {\n sstore(position, _ENTERED)\n }\n\n _;\n\n // By storing the original value once again, a refund is triggered (see\n // https://eips.ethereum.org/EIPS/eip-2200)\n // solhint-disable-next-line no-inline-assembly\n assembly {\n sstore(position, _NOT_ENTERED)\n }\n }\n\n function _setPendingGovernor(address newGovernor) internal {\n bytes32 position = pendingGovernorPosition;\n // solhint-disable-next-line no-inline-assembly\n assembly {\n sstore(position, newGovernor)\n }\n }\n\n /**\n * @notice Transfers Governance of the contract to a new account (`newGovernor`).\n * Can only be called by the current Governor. Must be claimed for this to complete\n * @param _newGovernor Address of the new Governor\n */\n function transferGovernance(address _newGovernor) external onlyGovernor {\n _setPendingGovernor(_newGovernor);\n emit PendingGovernorshipTransfer(_governor(), _newGovernor);\n }\n\n /**\n * @notice Claim Governance of the contract to a new account (`newGovernor`).\n * Can only be called by the new Governor.\n */\n function claimGovernance() external {\n require(\n msg.sender == _pendingGovernor(),\n \"Only the pending Governor can complete the claim\"\n );\n _changeGovernor(msg.sender);\n }\n\n /**\n * @dev Change Governance of the contract to a new account (`newGovernor`).\n * @param _newGovernor Address of the new Governor\n */\n function _changeGovernor(address _newGovernor) internal {\n require(_newGovernor != address(0), \"New Governor is address(0)\");\n _setGovernor(_newGovernor);\n }\n}\n" + }, + "contracts/governance/Strategizable.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { Governable } from \"./Governable.sol\";\n\ncontract Strategizable is Governable {\n event StrategistUpdated(address _address);\n\n // Address of strategist\n address public strategistAddr;\n\n // For future use\n uint256[50] private __gap;\n\n /**\n * @dev Verifies that the caller is either Governor or Strategist.\n */\n modifier onlyGovernorOrStrategist() virtual {\n require(\n msg.sender == strategistAddr || isGovernor(),\n \"Caller is not the Strategist or Governor\"\n );\n _;\n }\n\n /**\n * @dev Set address of Strategist\n * @param _address Address of Strategist\n */\n function setStrategistAddr(address _address) external onlyGovernor {\n _setStrategistAddr(_address);\n }\n\n /**\n * @dev Set address of Strategist\n * @param _address Address of Strategist\n */\n function _setStrategistAddr(address _address) internal {\n strategistAddr = _address;\n emit StrategistUpdated(_address);\n }\n}\n" + }, + "contracts/harvest/AbstractHarvester.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport { SafeMath } from \"@openzeppelin/contracts/utils/math/SafeMath.sol\";\nimport \"@openzeppelin/contracts/utils/math/Math.sol\";\n\nimport { StableMath } from \"../utils/StableMath.sol\";\nimport { Governable } from \"../governance/Governable.sol\";\nimport { IVault } from \"../interfaces/IVault.sol\";\nimport { IOracle } from \"../interfaces/IOracle.sol\";\nimport { IStrategy } from \"../interfaces/IStrategy.sol\";\nimport { IUniswapV2Router } from \"../interfaces/uniswap/IUniswapV2Router02.sol\";\nimport { IUniswapV3Router } from \"../interfaces/uniswap/IUniswapV3Router.sol\";\nimport { IBalancerVault } from \"../interfaces/balancer/IBalancerVault.sol\";\nimport { ICurvePool } from \"../strategies/ICurvePool.sol\";\nimport \"../utils/Helpers.sol\";\n\nabstract contract AbstractHarvester is Governable {\n using SafeERC20 for IERC20;\n using SafeMath for uint256;\n using StableMath for uint256;\n\n enum SwapPlatform {\n UniswapV2Compatible,\n UniswapV3,\n Balancer,\n Curve\n }\n\n event SupportedStrategyUpdate(address strategyAddress, bool isSupported);\n event RewardTokenConfigUpdated(\n address tokenAddress,\n uint16 allowedSlippageBps,\n uint16 harvestRewardBps,\n SwapPlatform swapPlatform,\n address swapPlatformAddr,\n bytes swapData,\n uint256 liquidationLimit,\n bool doSwapRewardToken\n );\n event RewardTokenSwapped(\n address indexed rewardToken,\n address indexed swappedInto,\n SwapPlatform swapPlatform,\n uint256 amountIn,\n uint256 amountOut\n );\n event RewardProceedsTransferred(\n address indexed token,\n address farmer,\n uint256 protcolYield,\n uint256 farmerFee\n );\n event RewardProceedsAddressChanged(address newProceedsAddress);\n\n error EmptyAddress();\n error InvalidSlippageBps();\n error InvalidHarvestRewardBps();\n\n error InvalidSwapPlatform(SwapPlatform swapPlatform);\n\n error InvalidUniswapV2PathLength();\n error InvalidTokenInSwapPath(address token);\n error EmptyBalancerPoolId();\n error InvalidCurvePoolAssetIndex(address token);\n\n error UnsupportedStrategy(address strategyAddress);\n\n error SlippageError(uint256 actualBalance, uint256 minExpected);\n error BalanceMismatchAfterSwap(uint256 actualBalance, uint256 minExpected);\n\n // Configuration properties for harvesting logic of reward tokens\n struct RewardTokenConfig {\n // Max allowed slippage when swapping reward token for a stablecoin denominated in basis points.\n uint16 allowedSlippageBps;\n // Reward when calling a harvest function denominated in basis points.\n uint16 harvestRewardBps;\n // Address of compatible exchange protocol (Uniswap V2/V3, SushiSwap, Balancer and Curve).\n address swapPlatformAddr;\n /* When true the reward token is being swapped. In a need of (temporarily) disabling the swapping of\n * a reward token this needs to be set to false.\n */\n bool doSwapRewardToken;\n // Platform to use for Swapping\n SwapPlatform swapPlatform;\n /* How much token can be sold per one harvest call. If the balance of rewards tokens\n * exceeds that limit multiple harvest calls are required to harvest all of the tokens.\n * Set it to MAX_INT to effectively disable the limit.\n */\n uint256 liquidationLimit;\n }\n\n mapping(address => RewardTokenConfig) public rewardTokenConfigs;\n mapping(address => bool) public supportedStrategies;\n\n address public immutable vaultAddress;\n\n /**\n * Address receiving rewards proceeds. Initially the Vault contract later will possibly\n * be replaced by another contract that eases out rewards distribution.\n **/\n address public rewardProceedsAddress;\n\n /**\n * All tokens are swapped to this token before it gets transferred\n * to the `rewardProceedsAddress`. USDT for OUSD and WETH for OETH.\n **/\n address public immutable baseTokenAddress;\n // Cached decimals for `baseTokenAddress`\n uint256 public immutable baseTokenDecimals;\n\n // Uniswap V2 path for reward tokens using Uniswap V2 Router\n mapping(address => address[]) public uniswapV2Path;\n // Uniswap V3 path for reward tokens using Uniswap V3 Router\n mapping(address => bytes) public uniswapV3Path;\n // Pool ID to use for reward tokens on Balancer\n mapping(address => bytes32) public balancerPoolId;\n\n struct CurvePoolIndices {\n // Casted into uint128 and stored in a struct to save gas\n uint128 rewardTokenIndex;\n uint128 baseTokenIndex;\n }\n // Packed indices of assets on the Curve pool\n mapping(address => CurvePoolIndices) public curvePoolIndices;\n\n constructor(address _vaultAddress, address _baseTokenAddress) {\n require(_vaultAddress != address(0));\n require(_baseTokenAddress != address(0));\n\n vaultAddress = _vaultAddress;\n baseTokenAddress = _baseTokenAddress;\n\n // Cache decimals as well\n baseTokenDecimals = Helpers.getDecimals(_baseTokenAddress);\n }\n\n /***************************************\n Configuration\n ****************************************/\n\n /**\n * Set the Address receiving rewards proceeds.\n * @param _rewardProceedsAddress Address of the reward token\n */\n function setRewardProceedsAddress(address _rewardProceedsAddress)\n external\n onlyGovernor\n {\n if (_rewardProceedsAddress == address(0)) {\n revert EmptyAddress();\n }\n\n rewardProceedsAddress = _rewardProceedsAddress;\n emit RewardProceedsAddressChanged(_rewardProceedsAddress);\n }\n\n /**\n * @dev Add/update a reward token configuration that holds harvesting config variables\n * @param _tokenAddress Address of the reward token\n * @param tokenConfig.allowedSlippageBps uint16 maximum allowed slippage denominated in basis points.\n * Example: 300 == 3% slippage\n * @param tokenConfig.harvestRewardBps uint16 amount of reward tokens the caller of the function is rewarded.\n * Example: 100 == 1%\n * @param tokenConfig.swapPlatformAddr Address Address of a UniswapV2 compatible contract to perform\n * the exchange from reward tokens to stablecoin (currently hard-coded to USDT)\n * @param tokenConfig.liquidationLimit uint256 Maximum amount of token to be sold per one swap function call.\n * When value is 0 there is no limit.\n * @param tokenConfig.doSwapRewardToken bool Disables swapping of the token when set to true,\n * does not cause it to revert though.\n * @param tokenConfig.swapPlatform SwapPlatform to use for Swapping\n * @param swapData Additional data required for swapping\n */\n function setRewardTokenConfig(\n address _tokenAddress,\n RewardTokenConfig calldata tokenConfig,\n bytes calldata swapData\n ) external onlyGovernor {\n if (tokenConfig.allowedSlippageBps > 1000) {\n revert InvalidSlippageBps();\n }\n\n if (tokenConfig.harvestRewardBps > 1000) {\n revert InvalidHarvestRewardBps();\n }\n\n address newRouterAddress = tokenConfig.swapPlatformAddr;\n if (newRouterAddress == address(0)) {\n // Swap router address should be non zero address\n revert EmptyAddress();\n }\n\n address oldRouterAddress = rewardTokenConfigs[_tokenAddress]\n .swapPlatformAddr;\n rewardTokenConfigs[_tokenAddress] = tokenConfig;\n\n // Revert if feed does not exist\n // slither-disable-next-line unused-return\n\n IERC20 token = IERC20(_tokenAddress);\n // if changing token swap provider cancel existing allowance\n if (\n /* oldRouterAddress == address(0) when there is no pre-existing\n * configuration for said rewards token\n */\n oldRouterAddress != address(0) &&\n oldRouterAddress != newRouterAddress\n ) {\n token.safeApprove(oldRouterAddress, 0);\n }\n\n // Give SwapRouter infinite approval when needed\n if (oldRouterAddress != newRouterAddress) {\n token.safeApprove(newRouterAddress, 0);\n token.safeApprove(newRouterAddress, type(uint256).max);\n }\n\n SwapPlatform _platform = tokenConfig.swapPlatform;\n if (_platform == SwapPlatform.UniswapV2Compatible) {\n uniswapV2Path[_tokenAddress] = _decodeUniswapV2Path(\n swapData,\n _tokenAddress\n );\n } else if (_platform == SwapPlatform.UniswapV3) {\n uniswapV3Path[_tokenAddress] = _decodeUniswapV3Path(\n swapData,\n _tokenAddress\n );\n } else if (_platform == SwapPlatform.Balancer) {\n balancerPoolId[_tokenAddress] = _decodeBalancerPoolId(\n swapData,\n newRouterAddress,\n _tokenAddress\n );\n } else if (_platform == SwapPlatform.Curve) {\n curvePoolIndices[_tokenAddress] = _decodeCurvePoolIndices(\n swapData,\n newRouterAddress,\n _tokenAddress\n );\n } else {\n // Note: This code is unreachable since Solidity reverts when\n // the value is outside the range of defined values of the enum\n // (even if it's under the max length of the base type)\n revert InvalidSwapPlatform(_platform);\n }\n\n emit RewardTokenConfigUpdated(\n _tokenAddress,\n tokenConfig.allowedSlippageBps,\n tokenConfig.harvestRewardBps,\n _platform,\n newRouterAddress,\n swapData,\n tokenConfig.liquidationLimit,\n tokenConfig.doSwapRewardToken\n );\n }\n\n /**\n * @dev Decodes the data passed into Uniswap V2 path and validates\n * it to make sure the path is for `token` to `baseToken`\n *\n * @param data Ecnoded data passed to the `setRewardTokenConfig`\n * @param token The address of the reward token\n * @return path The validated Uniswap V2 path\n */\n function _decodeUniswapV2Path(bytes calldata data, address token)\n internal\n view\n returns (address[] memory path)\n {\n (path) = abi.decode(data, (address[]));\n uint256 len = path.length;\n\n if (len < 2) {\n // Path should have at least two tokens\n revert InvalidUniswapV2PathLength();\n }\n\n // Do some validation\n if (path[0] != token) {\n revert InvalidTokenInSwapPath(path[0]);\n }\n\n if (path[len - 1] != baseTokenAddress) {\n revert InvalidTokenInSwapPath(path[len - 1]);\n }\n }\n\n /**\n * @dev Decodes the data passed into Uniswap V3 path and validates\n * it to make sure the path is for `token` to `baseToken`\n *\n * @param data Ecnoded data passed to the `setRewardTokenConfig`\n * @param token The address of the reward token\n * @return path The validated Uniswap V3 path\n */\n function _decodeUniswapV3Path(bytes calldata data, address token)\n internal\n view\n returns (bytes calldata path)\n {\n path = data;\n\n address decodedAddress = address(uint160(bytes20(data[0:20])));\n\n if (decodedAddress != token) {\n // Invalid Reward Token in swap path\n revert InvalidTokenInSwapPath(decodedAddress);\n }\n\n decodedAddress = address(uint160(bytes20(data[path.length - 20:])));\n if (decodedAddress != baseTokenAddress) {\n // Invalid Base Token in swap path\n revert InvalidTokenInSwapPath(decodedAddress);\n }\n }\n\n /**\n * @dev Decodes the data passed to Balancer Pool ID\n *\n * @param data Ecnoded data passed to the `setRewardTokenConfig`\n * @return poolId The pool ID\n */\n function _decodeBalancerPoolId(\n bytes calldata data,\n address balancerVault,\n address token\n ) internal view returns (bytes32 poolId) {\n (poolId) = abi.decode(data, (bytes32));\n\n if (poolId == bytes32(0)) {\n revert EmptyBalancerPoolId();\n }\n\n IBalancerVault bVault = IBalancerVault(balancerVault);\n\n // Note: this reverts if token is not a pool asset\n // slither-disable-next-line unused-return\n bVault.getPoolTokenInfo(poolId, token);\n\n // slither-disable-next-line unused-return\n bVault.getPoolTokenInfo(poolId, baseTokenAddress);\n }\n\n /**\n * @dev Decodes the data passed to get the pool indices and\n * checks it against the Curve Pool to make sure it's\n * not misconfigured. The indices are packed into a single\n * uint256 for gas savings\n *\n * @param data Ecnoded data passed to the `setRewardTokenConfig`\n * @param poolAddress Curve pool address\n * @param token The address of the reward token\n * @return indices Packed pool asset indices\n */\n function _decodeCurvePoolIndices(\n bytes calldata data,\n address poolAddress,\n address token\n ) internal view returns (CurvePoolIndices memory indices) {\n indices = abi.decode(data, (CurvePoolIndices));\n\n ICurvePool pool = ICurvePool(poolAddress);\n if (token != pool.coins(indices.rewardTokenIndex)) {\n revert InvalidCurvePoolAssetIndex(token);\n }\n if (baseTokenAddress != pool.coins(indices.baseTokenIndex)) {\n revert InvalidCurvePoolAssetIndex(baseTokenAddress);\n }\n }\n\n /**\n * @dev Flags a strategy as supported or not supported one\n * @param _strategyAddress Address of the strategy\n * @param _isSupported Bool marking strategy as supported or not supported\n */\n function setSupportedStrategy(address _strategyAddress, bool _isSupported)\n external\n onlyGovernor\n {\n supportedStrategies[_strategyAddress] = _isSupported;\n emit SupportedStrategyUpdate(_strategyAddress, _isSupported);\n }\n\n /***************************************\n Rewards\n ****************************************/\n\n /**\n * @dev Transfer token to governor. Intended for recovering tokens stuck in\n * contract, i.e. mistaken sends.\n * @param _asset Address for the asset\n * @param _amount Amount of the asset to transfer\n */\n function transferToken(address _asset, uint256 _amount)\n external\n onlyGovernor\n {\n IERC20(_asset).safeTransfer(governor(), _amount);\n }\n\n /**\n * @dev Collect reward tokens from a specific strategy and swap them for\n * base token on the configured swap platform. Can be called by anyone.\n * Rewards incentivizing the caller are sent to the caller of this function.\n * @param _strategyAddr Address of the strategy to collect rewards from\n */\n function harvestAndSwap(address _strategyAddr) external nonReentrant {\n // Remember _harvest function checks for the validity of _strategyAddr\n _harvestAndSwap(_strategyAddr, msg.sender);\n }\n\n /**\n * @dev Collect reward tokens from a specific strategy and swap them for\n * base token on the configured swap platform. Can be called by anyone\n * @param _strategyAddr Address of the strategy to collect rewards from\n * @param _rewardTo Address where to send a share of harvest rewards to as an incentive\n * for executing this function\n */\n function harvestAndSwap(address _strategyAddr, address _rewardTo)\n external\n nonReentrant\n {\n // Remember _harvest function checks for the validity of _strategyAddr\n _harvestAndSwap(_strategyAddr, _rewardTo);\n }\n\n /**\n * @dev Collect reward tokens from a specific strategy and swap them for\n * base token on the configured swap platform\n * @param _strategyAddr Address of the strategy to collect rewards from\n * @param _rewardTo Address where to send a share of harvest rewards to as an incentive\n * for executing this function\n */\n function _harvestAndSwap(address _strategyAddr, address _rewardTo)\n internal\n {\n _harvest(_strategyAddr);\n IStrategy strategy = IStrategy(_strategyAddr);\n address[] memory rewardTokens = strategy.getRewardTokenAddresses();\n uint256 len = rewardTokens.length;\n for (uint256 i = 0; i < len; ++i) {\n // This harvester contract is not used anymore. Keeping the code\n // for passing test deployment. Safe to use address(0x1) as oracle.\n _swap(rewardTokens[i], _rewardTo, IOracle(address(0x1)));\n }\n }\n\n /**\n * @dev Collect reward tokens from a specific strategy and swap them for\n * base token on the configured swap platform\n * @param _strategyAddr Address of the strategy to collect rewards from.\n */\n function _harvest(address _strategyAddr) internal virtual {\n if (!supportedStrategies[_strategyAddr]) {\n revert UnsupportedStrategy(_strategyAddr);\n }\n\n IStrategy strategy = IStrategy(_strategyAddr);\n strategy.collectRewardTokens();\n }\n\n /**\n * @dev Swap a reward token for the base token on the configured\n * swap platform. The token must have a registered price feed\n * with the price provider\n * @param _swapToken Address of the token to swap\n * @param _rewardTo Address where to send the share of harvest rewards to\n * @param _priceProvider Oracle to get prices of the swap token\n */\n function _swap(\n address _swapToken,\n address _rewardTo,\n IOracle _priceProvider\n ) internal virtual {\n uint256 balance = IERC20(_swapToken).balanceOf(address(this));\n\n // No need to swap if the reward token is the base token. eg USDT or WETH.\n // There is also no limit on the transfer. Everything in the harvester will be transferred\n // to the Dripper regardless of the liquidationLimit config.\n if (_swapToken == baseTokenAddress) {\n IERC20(_swapToken).safeTransfer(rewardProceedsAddress, balance);\n // currently not paying the farmer any rewards as there is no swap\n emit RewardProceedsTransferred(\n baseTokenAddress,\n address(0),\n balance,\n 0\n );\n return;\n }\n\n RewardTokenConfig memory tokenConfig = rewardTokenConfigs[_swapToken];\n\n /* This will trigger a return when reward token configuration has not yet been set\n * or we have temporarily disabled swapping of specific reward token via setting\n * doSwapRewardToken to false.\n */\n if (!tokenConfig.doSwapRewardToken) {\n return;\n }\n\n if (balance == 0) {\n return;\n }\n\n if (tokenConfig.liquidationLimit > 0) {\n balance = Math.min(balance, tokenConfig.liquidationLimit);\n }\n\n // This'll revert if there is no price feed\n uint256 oraclePrice = _priceProvider.price(_swapToken);\n\n // Oracle price is 1e18\n uint256 minExpected = (balance *\n (1e4 - tokenConfig.allowedSlippageBps) * // max allowed slippage\n oraclePrice).scaleBy(\n baseTokenDecimals,\n Helpers.getDecimals(_swapToken)\n ) /\n 1e4 / // fix the max slippage decimal position\n 1e18; // and oracle price decimals position\n\n // Do the swap\n uint256 amountReceived = _doSwap(\n tokenConfig.swapPlatform,\n tokenConfig.swapPlatformAddr,\n _swapToken,\n balance,\n minExpected\n );\n\n if (amountReceived < minExpected) {\n revert SlippageError(amountReceived, minExpected);\n }\n\n emit RewardTokenSwapped(\n _swapToken,\n baseTokenAddress,\n tokenConfig.swapPlatform,\n balance,\n amountReceived\n );\n\n IERC20 baseToken = IERC20(baseTokenAddress);\n uint256 baseTokenBalance = baseToken.balanceOf(address(this));\n if (baseTokenBalance < amountReceived) {\n // Note: It's possible to bypass this check by transferring `baseToken`\n // directly to Harvester before calling the `harvestAndSwap`. However,\n // there's no incentive for an attacker to do that. Doing a balance diff\n // will increase the gas cost significantly\n revert BalanceMismatchAfterSwap(baseTokenBalance, amountReceived);\n }\n\n // Farmer only gets fee from the base amount they helped farm,\n // They do not get anything from anything that already was there\n // on the Harvester\n uint256 farmerFee = amountReceived.mulTruncateScale(\n tokenConfig.harvestRewardBps,\n 1e4\n );\n uint256 protocolYield = baseTokenBalance - farmerFee;\n\n baseToken.safeTransfer(rewardProceedsAddress, protocolYield);\n baseToken.safeTransfer(_rewardTo, farmerFee);\n emit RewardProceedsTransferred(\n baseTokenAddress,\n _rewardTo,\n protocolYield,\n farmerFee\n );\n }\n\n function _doSwap(\n SwapPlatform swapPlatform,\n address routerAddress,\n address rewardTokenAddress,\n uint256 amountIn,\n uint256 minAmountOut\n ) internal returns (uint256 amountOut) {\n if (swapPlatform == SwapPlatform.UniswapV2Compatible) {\n return\n _swapWithUniswapV2(\n routerAddress,\n rewardTokenAddress,\n amountIn,\n minAmountOut\n );\n } else if (swapPlatform == SwapPlatform.UniswapV3) {\n return\n _swapWithUniswapV3(\n routerAddress,\n rewardTokenAddress,\n amountIn,\n minAmountOut\n );\n } else if (swapPlatform == SwapPlatform.Balancer) {\n return\n _swapWithBalancer(\n routerAddress,\n rewardTokenAddress,\n amountIn,\n minAmountOut\n );\n } else if (swapPlatform == SwapPlatform.Curve) {\n return\n _swapWithCurve(\n routerAddress,\n rewardTokenAddress,\n amountIn,\n minAmountOut\n );\n } else {\n // Should never be invoked since we catch invalid values\n // in the `setRewardTokenConfig` function before it's set\n revert InvalidSwapPlatform(swapPlatform);\n }\n }\n\n /**\n * @dev Swaps the token to `baseToken` with Uniswap V2\n *\n * @param routerAddress Uniswap V2 Router address\n * @param swapToken Address of the tokenIn\n * @param amountIn Amount of `swapToken` to swap\n * @param minAmountOut Minimum expected amount of `baseToken`\n *\n * @return amountOut Amount of `baseToken` received after the swap\n */\n function _swapWithUniswapV2(\n address routerAddress,\n address swapToken,\n uint256 amountIn,\n uint256 minAmountOut\n ) internal returns (uint256 amountOut) {\n address[] memory path = uniswapV2Path[swapToken];\n\n uint256[] memory amounts = IUniswapV2Router(routerAddress)\n .swapExactTokensForTokens(\n amountIn,\n minAmountOut,\n path,\n address(this),\n block.timestamp\n );\n\n amountOut = amounts[amounts.length - 1];\n }\n\n /**\n * @dev Swaps the token to `baseToken` with Uniswap V3\n *\n * @param routerAddress Uniswap V3 Router address\n * @param swapToken Address of the tokenIn\n * @param amountIn Amount of `swapToken` to swap\n * @param minAmountOut Minimum expected amount of `baseToken`\n *\n * @return amountOut Amount of `baseToken` received after the swap\n */\n function _swapWithUniswapV3(\n address routerAddress,\n address swapToken,\n uint256 amountIn,\n uint256 minAmountOut\n ) internal returns (uint256 amountOut) {\n bytes memory path = uniswapV3Path[swapToken];\n\n IUniswapV3Router.ExactInputParams memory params = IUniswapV3Router\n .ExactInputParams({\n path: path,\n recipient: address(this),\n deadline: block.timestamp,\n amountIn: amountIn,\n amountOutMinimum: minAmountOut\n });\n amountOut = IUniswapV3Router(routerAddress).exactInput(params);\n }\n\n /**\n * @dev Swaps the token to `baseToken` on Balancer\n *\n * @param balancerVaultAddress BalancerVaultAddress\n * @param swapToken Address of the tokenIn\n * @param amountIn Amount of `swapToken` to swap\n * @param minAmountOut Minimum expected amount of `baseToken`\n *\n * @return amountOut Amount of `baseToken` received after the swap\n */\n function _swapWithBalancer(\n address balancerVaultAddress,\n address swapToken,\n uint256 amountIn,\n uint256 minAmountOut\n ) internal returns (uint256 amountOut) {\n bytes32 poolId = balancerPoolId[swapToken];\n\n IBalancerVault.SingleSwap memory singleSwap = IBalancerVault\n .SingleSwap({\n poolId: poolId,\n kind: IBalancerVault.SwapKind.GIVEN_IN,\n assetIn: swapToken,\n assetOut: baseTokenAddress,\n amount: amountIn,\n userData: hex\"\"\n });\n\n IBalancerVault.FundManagement memory fundMgmt = IBalancerVault\n .FundManagement({\n sender: address(this),\n fromInternalBalance: false,\n recipient: payable(address(this)),\n toInternalBalance: false\n });\n\n amountOut = IBalancerVault(balancerVaultAddress).swap(\n singleSwap,\n fundMgmt,\n minAmountOut,\n block.timestamp\n );\n }\n\n /**\n * @dev Swaps the token to `baseToken` on Curve\n *\n * @param poolAddress Curve Pool Address\n * @param swapToken Address of the tokenIn\n * @param amountIn Amount of `swapToken` to swap\n * @param minAmountOut Minimum expected amount of `baseToken`\n *\n * @return amountOut Amount of `baseToken` received after the swap\n */\n function _swapWithCurve(\n address poolAddress,\n address swapToken,\n uint256 amountIn,\n uint256 minAmountOut\n ) internal returns (uint256 amountOut) {\n CurvePoolIndices memory indices = curvePoolIndices[swapToken];\n\n // Note: Not all CurvePools return the `amountOut`, make sure\n // to use only pool that do. Otherwise the swap would revert\n // always\n amountOut = ICurvePool(poolAddress).exchange(\n uint256(indices.rewardTokenIndex),\n uint256(indices.baseTokenIndex),\n amountIn,\n minAmountOut\n );\n }\n}\n" + }, + "contracts/harvest/Harvester.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { AbstractHarvester } from \"./AbstractHarvester.sol\";\n\ncontract Harvester is AbstractHarvester {\n constructor(address _vault, address _usdtAddress)\n AbstractHarvester(_vault, _usdtAddress)\n {}\n}\n" + }, + "contracts/harvest/OETHHarvester.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { AbstractHarvester } from \"./AbstractHarvester.sol\";\n\ncontract OETHHarvester is AbstractHarvester {\n constructor(address _vault, address _wethAddress)\n AbstractHarvester(_vault, _wethAddress)\n {}\n}\n" + }, + "contracts/harvest/OETHHarvesterSimple.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { Strategizable } from \"../governance/Strategizable.sol\";\nimport { IStrategy } from \"../interfaces/IStrategy.sol\";\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport { Initializable } from \"../utils/Initializable.sol\";\n\n/// @title OETH Harvester Simple Contract\n/// @notice Contract to harvest rewards from strategies\n/// @author Origin Protocol Inc\ncontract OETHHarvesterSimple is Initializable, Strategizable {\n using SafeERC20 for IERC20;\n\n ////////////////////////////////////////////////////\n /// --- CONSTANTS & IMMUTABLES\n ////////////////////////////////////////////////////\n /// @notice wrapped native token address (WETH or wS)\n address public immutable wrappedNativeToken;\n\n ////////////////////////////////////////////////////\n /// --- STORAGE\n ////////////////////////////////////////////////////\n /// @notice Dripper address\n address public dripper;\n\n /// @notice Mapping of supported strategies\n mapping(address => bool) public supportedStrategies;\n\n /// @notice Gap for upgrade safety\n uint256[48] private ___gap;\n\n ////////////////////////////////////////////////////\n /// --- EVENTS\n ////////////////////////////////////////////////////\n event Harvested(\n address indexed strategy,\n address token,\n uint256 amount,\n address indexed receiver\n );\n event SupportedStrategyUpdated(address strategy, bool status);\n event DripperUpdated(address dripper);\n\n ////////////////////////////////////////////////////\n /// --- CONSTRUCTOR\n ////////////////////////////////////////////////////\n constructor(address _wrappedNativeToken) {\n wrappedNativeToken = _wrappedNativeToken;\n\n // prevent implementation contract to be governed\n _setGovernor(address(0));\n }\n\n /// @notice Initialize the contract\n function initialize() external onlyGovernor initializer {\n // Call it to set `initialized` to true and to prevent the implementation\n // from getting initialized in future through the proxy\n }\n\n ////////////////////////////////////////////////////\n /// --- MUTATIVE FUNCTIONS\n ////////////////////////////////////////////////////\n /// @notice Harvest rewards from a strategy and transfer to strategist or dripper\n /// @param _strategy Address of the strategy to harvest\n function harvestAndTransfer(address _strategy) external {\n _harvestAndTransfer(_strategy);\n }\n\n /// @notice Harvest rewards from multiple strategies and transfer to strategist or dripper\n /// @param _strategies Array of strategy addresses to harvest\n function harvestAndTransfer(address[] calldata _strategies) external {\n for (uint256 i = 0; i < _strategies.length; i++) {\n _harvestAndTransfer(_strategies[i]);\n }\n }\n\n /// @notice Internal logic to harvest rewards from a strategy\n function _harvestAndTransfer(address _strategy) internal virtual {\n // Ensure strategy is supported\n require(supportedStrategies[_strategy], \"Strategy not supported\");\n\n // Store locally for some gas savings\n address _strategist = strategistAddr;\n address _dripper = dripper;\n\n // Harvest rewards\n IStrategy(_strategy).collectRewardTokens();\n\n // Cache reward tokens\n address[] memory rewardTokens = IStrategy(_strategy)\n .getRewardTokenAddresses();\n\n uint256 len = rewardTokens.length;\n for (uint256 i = 0; i < len; i++) {\n // Cache balance\n address token = rewardTokens[i];\n uint256 balance = IERC20(token).balanceOf(address(this));\n if (balance > 0) {\n // Determine receiver\n address receiver = token == wrappedNativeToken\n ? _dripper\n : _strategist;\n require(receiver != address(0), \"Invalid receiver\");\n\n // Transfer to the Strategist or the Dripper\n IERC20(token).safeTransfer(receiver, balance);\n emit Harvested(_strategy, token, balance, receiver);\n }\n }\n }\n\n ////////////////////////////////////////////////////\n /// --- GOVERNANCE\n ////////////////////////////////////////////////////\n /// @notice Set supported strategy\n /// @param _strategy Address of the strategy\n /// @param _isSupported Boolean indicating if strategy is supported\n function setSupportedStrategy(address _strategy, bool _isSupported)\n external\n onlyGovernorOrStrategist\n {\n require(_strategy != address(0), \"Invalid strategy\");\n supportedStrategies[_strategy] = _isSupported;\n emit SupportedStrategyUpdated(_strategy, _isSupported);\n }\n\n /// @notice Transfer tokens to strategist\n /// @param _asset Address of the token\n /// @param _amount Amount of tokens to transfer\n function transferToken(address _asset, uint256 _amount)\n external\n onlyGovernorOrStrategist\n {\n IERC20(_asset).safeTransfer(strategistAddr, _amount);\n }\n\n /// @notice Set the dripper address\n /// @param _dripper Address of the dripper\n function setDripper(address _dripper) external onlyGovernor {\n _setDripper(_dripper);\n }\n\n /// @notice Internal logic to set the dripper address\n function _setDripper(address _dripper) internal {\n require(_dripper != address(0), \"Invalid dripper\");\n dripper = _dripper;\n emit DripperUpdated(_dripper);\n }\n}\n" + }, + "contracts/harvest/OSonicHarvester.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { SuperOETHHarvester } from \"./SuperOETHHarvester.sol\";\n\ncontract OSonicHarvester is SuperOETHHarvester {\n /// @param _wrappedNativeToken Address of the native Wrapped S (wS) token\n constructor(address _wrappedNativeToken)\n SuperOETHHarvester(_wrappedNativeToken)\n {}\n}\n" + }, + "contracts/harvest/SuperOETHHarvester.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { OETHHarvesterSimple, IERC20, IStrategy, SafeERC20 } from \"./OETHHarvesterSimple.sol\";\n\ncontract SuperOETHHarvester is OETHHarvesterSimple {\n using SafeERC20 for IERC20;\n\n constructor(address _wrappedNativeToken)\n OETHHarvesterSimple(_wrappedNativeToken)\n {}\n\n /// @inheritdoc OETHHarvesterSimple\n function _harvestAndTransfer(address _strategy) internal virtual override {\n // Ensure strategy is supported\n require(supportedStrategies[_strategy], \"Strategy not supported\");\n\n address receiver = strategistAddr;\n require(receiver != address(0), \"Invalid receiver\");\n\n // Harvest rewards\n IStrategy(_strategy).collectRewardTokens();\n\n // Cache reward tokens\n address[] memory rewardTokens = IStrategy(_strategy)\n .getRewardTokenAddresses();\n\n uint256 len = rewardTokens.length;\n for (uint256 i = 0; i < len; i++) {\n // Cache balance\n address token = rewardTokens[i];\n uint256 balance = IERC20(token).balanceOf(address(this));\n if (balance > 0) {\n // Transfer everything to the strategist\n IERC20(token).safeTransfer(receiver, balance);\n emit Harvested(_strategy, token, balance, receiver);\n }\n }\n }\n}\n" + }, + "contracts/interfaces/aerodrome/ICLGauge.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity >=0.8.0;\n\ninterface ICLGauge {\n /// @notice Returns the claimable rewards for a given account and tokenId\n /// @dev Throws if account is not the position owner\n /// @dev pool.updateRewardsGrowthGlobal() needs to be called first, to return the correct claimable rewards\n /// @param account The address of the user\n /// @param tokenId The tokenId of the position\n /// @return The amount of claimable reward\n function earned(address account, uint256 tokenId)\n external\n view\n returns (uint256);\n\n /// @notice Retrieve rewards for all tokens owned by an account\n /// @dev Throws if not called by the voter\n /// @param account The account of the user\n function getReward(address account) external;\n\n /// @notice Retrieve rewards for a tokenId\n /// @dev Throws if not called by the position owner\n /// @param tokenId The tokenId of the position\n function getReward(uint256 tokenId) external;\n\n /// @notice Notifies gauge of gauge rewards.\n /// @param amount Amount of gauge rewards (emissions) to notify. Must be greater than 604_800.\n function notifyRewardAmount(uint256 amount) external;\n\n /// @dev Notifies gauge of gauge rewards without distributing its fees.\n /// Assumes gauge reward tokens is 18 decimals.\n /// If not 18 decimals, rewardRate may have rounding issues.\n /// @param amount Amount of gauge rewards (emissions) to notify. Must be greater than 604_800.\n function notifyRewardWithoutClaim(uint256 amount) external;\n\n /// @notice Used to deposit a CL position into the gauge\n /// @notice Allows the user to receive emissions instead of fees\n /// @param tokenId The tokenId of the position\n function deposit(uint256 tokenId) external;\n\n /// @notice Used to withdraw a CL position from the gauge\n /// @notice Allows the user to receive fees instead of emissions\n /// @notice Outstanding emissions will be collected on withdrawal\n /// @param tokenId The tokenId of the position\n function withdraw(uint256 tokenId) external;\n\n // /// @notice Fetch all tokenIds staked by a given account\n // /// @param depositor The address of the user\n // /// @return The tokenIds of the staked positions\n // function stakedValues(address depositor) external view returns (uint256[] memory);\n\n // /// @notice Fetch a staked tokenId by index\n // /// @param depositor The address of the user\n // /// @param index The index of the staked tokenId\n // /// @return The tokenId of the staked position\n // function stakedByIndex(address depositor, uint256 index) external view returns (uint256);\n\n // /// @notice Check whether a position is staked in the gauge by a certain user\n // /// @param depositor The address of the user\n // /// @param tokenId The tokenId of the position\n // /// @return Whether the position is staked in the gauge\n // function stakedContains(address depositor, uint256 tokenId) external view returns (bool);\n\n // /// @notice The amount of positions staked in the gauge by a certain user\n // /// @param depositor The address of the user\n // /// @return The amount of positions staked in the gauge\n // function stakedLength(address depositor) external view returns (uint256);\n\n function feesVotingReward() external view returns (address);\n}\n" + }, + "contracts/interfaces/aerodrome/ICLPool.sol": { + "content": "pragma solidity >=0.5.0;\n\n/// @title The interface for a CL Pool\n/// @notice A CL pool facilitates swapping and automated market making between any two assets that strictly conform\n/// to the ERC20 specification\n/// @dev The pool interface is broken up into many smaller pieces\ninterface ICLPool {\n function slot0()\n external\n view\n returns (\n uint160 sqrtPriceX96,\n int24 tick,\n uint16 observationIndex,\n uint16 observationCardinality,\n uint16 observationCardinalityNext,\n bool unlocked\n );\n\n /// @notice The first of the two tokens of the pool, sorted by address\n /// @return The token contract address\n function token0() external view returns (address);\n\n /// @notice The second of the two tokens of the pool, sorted by address\n /// @return The token contract address\n function token1() external view returns (address);\n\n function tickSpacing() external view returns (int24);\n\n /// @notice The gauge corresponding to this pool\n /// @return The gauge contract address\n function gauge() external view returns (address);\n\n /// @notice The currently in range liquidity available to the pool\n /// @dev This value has no relationship to the total liquidity across all ticks\n /// @dev This value includes staked liquidity\n function liquidity() external view returns (uint128);\n\n /// @notice Look up information about a specific tick in the pool\n /// @param tick The tick to look up\n /// @return liquidityGross the total amount of position liquidity that uses the pool either as tick lower or\n /// tick upper,\n /// liquidityNet how much liquidity changes when the pool price crosses the tick,\n /// feeGrowthOutside0X128 the fee growth on the other side of the tick from the current tick in token0,\n /// feeGrowthOutside1X128 the fee growth on the other side of the tick from the current tick in token1,\n /// tickCumulativeOutside the cumulative tick value on the other side of the tick from the current tick\n /// secondsPerLiquidityOutsideX128 the seconds spent per liquidity on the other side of the tick from\n /// the current tick,\n /// secondsOutside the seconds spent on the other side of the tick from the current tick,\n /// initialized Set to true if the tick is initialized, i.e. liquidityGross is greater than 0, otherwise\n /// equal to false.\n /// Outside values can only be used if the tick is initialized, i.e. if liquidityGross is greater than 0.\n /// In addition, these values are only relative and must be used only in comparison to previous snapshots for\n /// a specific position.\n function ticks(int24 tick)\n external\n view\n returns (\n uint128 liquidityGross,\n int128 liquidityNet,\n uint256 feeGrowthOutside0X128,\n uint256 feeGrowthOutside1X128,\n int56 tickCumulativeOutside,\n uint160 secondsPerLiquidityOutsideX128,\n uint32 secondsOutside,\n bool initialized\n );\n}\n" + }, + "contracts/interfaces/aerodrome/INonfungiblePositionManager.sol": { + "content": "// SPDX-License-Identifier: GPL-2.0-or-later\npragma solidity >=0.7.5;\npragma abicoder v2;\n\n/// @title Non-fungible token for positions\n/// @notice Wraps CL positions in a non-fungible token interface which allows for them to be transferred\n/// and authorized.\n// slither-disable-start erc20-interface\ninterface INonfungiblePositionManager {\n /**\n * @dev See {IERC721-approve}.\n */\n function approve(address to, uint256 tokenId) external;\n\n /**\n * @dev See {IERC721-getApproved}.\n */\n function getApproved(uint256 tokenId) external returns (address);\n\n /**\n * @dev See {IERC721-ownerOf}.\n */\n function ownerOf(uint256 tokenId) external view returns (address);\n\n /// @notice Returns the position information associated with a given token ID.\n /// @dev Throws if the token ID is not valid.\n /// @param tokenId The ID of the token that represents the position\n /// @return nonce The nonce for permits\n /// @return operator The address that is approved for spending\n /// @return token0 The address of the token0 for a specific pool\n /// @return token1 The address of the token1 for a specific pool\n /// @return tickSpacing The tick spacing associated with the pool\n /// @return tickLower The lower end of the tick range for the position\n /// @return tickUpper The higher end of the tick range for the position\n /// @return liquidity The liquidity of the position\n /// @return feeGrowthInside0LastX128 The fee growth of token0 as of the last action on the individual position\n /// @return feeGrowthInside1LastX128 The fee growth of token1 as of the last action on the individual position\n /// @return tokensOwed0 The uncollected amount of token0 owed to the position as of the last computation\n /// @return tokensOwed1 The uncollected amount of token1 owed to the position as of the last computation\n function positions(uint256 tokenId)\n external\n view\n returns (\n uint96 nonce,\n address operator,\n address token0,\n address token1,\n int24 tickSpacing,\n int24 tickLower,\n int24 tickUpper,\n uint128 liquidity,\n uint256 feeGrowthInside0LastX128,\n uint256 feeGrowthInside1LastX128,\n uint128 tokensOwed0,\n uint128 tokensOwed1\n );\n\n struct MintParams {\n address token0;\n address token1;\n int24 tickSpacing;\n int24 tickLower;\n int24 tickUpper;\n uint256 amount0Desired;\n uint256 amount1Desired;\n uint256 amount0Min;\n uint256 amount1Min;\n address recipient;\n uint256 deadline;\n uint160 sqrtPriceX96;\n }\n\n /// @notice Creates a new position wrapped in a NFT\n /// @dev Call this when the pool does exist and is initialized. Note that if the pool is created but not initialized\n /// a method does not exist, i.e. the pool is assumed to be initialized.\n /// @param params The params necessary to mint a position, encoded as `MintParams` in calldata\n /// @return tokenId The ID of the token that represents the minted position\n /// @return liquidity The amount of liquidity for this position\n /// @return amount0 The amount of token0\n /// @return amount1 The amount of token1\n function mint(MintParams calldata params)\n external\n payable\n returns (\n uint256 tokenId,\n uint128 liquidity,\n uint256 amount0,\n uint256 amount1\n );\n\n struct IncreaseLiquidityParams {\n uint256 tokenId;\n uint256 amount0Desired;\n uint256 amount1Desired;\n uint256 amount0Min;\n uint256 amount1Min;\n uint256 deadline;\n }\n\n /// @notice Increases the amount of liquidity in a position, with tokens paid by the `msg.sender`\n /// @param params tokenId The ID of the token for which liquidity is being increased,\n /// amount0Desired The desired amount of token0 to be spent,\n /// amount1Desired The desired amount of token1 to be spent,\n /// amount0Min The minimum amount of token0 to spend, which serves as a slippage check,\n /// amount1Min The minimum amount of token1 to spend, which serves as a slippage check,\n /// deadline The time by which the transaction must be included to effect the change\n /// @return liquidity The new liquidity amount as a result of the increase\n /// @return amount0 The amount of token0 to acheive resulting liquidity\n /// @return amount1 The amount of token1 to acheive resulting liquidity\n function increaseLiquidity(IncreaseLiquidityParams calldata params)\n external\n payable\n returns (\n uint128 liquidity,\n uint256 amount0,\n uint256 amount1\n );\n\n struct DecreaseLiquidityParams {\n uint256 tokenId;\n uint128 liquidity;\n uint256 amount0Min;\n uint256 amount1Min;\n uint256 deadline;\n }\n\n /// @notice Decreases the amount of liquidity in a position and accounts it to the position\n /// @param params tokenId The ID of the token for which liquidity is being decreased,\n /// amount The amount by which liquidity will be decreased,\n /// amount0Min The minimum amount of token0 that should be accounted for the burned liquidity,\n /// amount1Min The minimum amount of token1 that should be accounted for the burned liquidity,\n /// deadline The time by which the transaction must be included to effect the change\n /// @return amount0 The amount of token0 accounted to the position's tokens owed\n /// @return amount1 The amount of token1 accounted to the position's tokens owed\n /// @dev The use of this function can cause a loss to users of the NonfungiblePositionManager\n /// @dev for tokens that have very high decimals.\n /// @dev The amount of tokens necessary for the loss is: 3.4028237e+38.\n /// @dev This is equivalent to 1e20 value with 18 decimals.\n function decreaseLiquidity(DecreaseLiquidityParams calldata params)\n external\n payable\n returns (uint256 amount0, uint256 amount1);\n\n struct CollectParams {\n uint256 tokenId;\n address recipient;\n uint128 amount0Max;\n uint128 amount1Max;\n }\n\n /// @notice Collects up to a maximum amount of fees owed to a specific position to the recipient\n /// @notice Used to update staked positions before deposit and withdraw\n /// @param params tokenId The ID of the NFT for which tokens are being collected,\n /// recipient The account that should receive the tokens,\n /// amount0Max The maximum amount of token0 to collect,\n /// amount1Max The maximum amount of token1 to collect\n /// @return amount0 The amount of fees collected in token0\n /// @return amount1 The amount of fees collected in token1\n function collect(CollectParams calldata params)\n external\n payable\n returns (uint256 amount0, uint256 amount1);\n\n /// @notice Burns a token ID, which deletes it from the NFT contract. The token must have 0 liquidity and all tokens\n /// must be collected first.\n /// @param tokenId The ID of the token that is being burned\n function burn(uint256 tokenId) external payable;\n\n /// @notice Sets a new Token Descriptor\n /// @param _tokenDescriptor Address of the new Token Descriptor to be chosen\n function setTokenDescriptor(address _tokenDescriptor) external;\n\n /// @notice Sets a new Owner address\n /// @param _owner Address of the new Owner to be chosen\n function setOwner(address _owner) external;\n}\n// slither-disable-end erc20-interface\n" + }, + "contracts/interfaces/aerodrome/ISugarHelper.sol": { + "content": "// SPDX-License-Identifier: GPL-2.0-or-later\npragma solidity >=0.5.0;\npragma abicoder v2;\n\nimport { INonfungiblePositionManager } from \"./INonfungiblePositionManager.sol\";\n\ninterface ISugarHelper {\n struct PopulatedTick {\n int24 tick;\n uint160 sqrtRatioX96;\n int128 liquidityNet;\n uint128 liquidityGross;\n }\n\n ///\n /// Wrappers for LiquidityAmounts\n ///\n\n function getAmountsForLiquidity(\n uint160 sqrtRatioX96,\n uint160 sqrtRatioAX96,\n uint160 sqrtRatioBX96,\n uint128 liquidity\n ) external pure returns (uint256 amount0, uint256 amount1);\n\n function getLiquidityForAmounts(\n uint256 amount0,\n uint256 amount1,\n uint160 sqrtRatioX96,\n uint160 sqrtRatioAX96,\n uint160 sqrtRatioBX96\n ) external pure returns (uint128 liquidity);\n\n /// @notice Computes the amount of token0 for a given amount of token1 and price range\n /// @param amount1 Amount of token1 to estimate liquidity\n /// @param pool Address of the pool to be used\n /// @param sqrtRatioX96 A sqrt price representing the current pool prices\n /// @param tickLow Lower tick boundary\n /// @param tickLow Upper tick boundary\n /// @dev If the given pool address is not the zero address, will fetch `sqrtRatioX96` from pool\n /// @return amount0 Estimated amount of token0\n function estimateAmount0(\n uint256 amount1,\n address pool,\n uint160 sqrtRatioX96,\n int24 tickLow,\n int24 tickHigh\n ) external view returns (uint256 amount0);\n\n /// @notice Computes the amount of token1 for a given amount of token0 and price range\n /// @param amount0 Amount of token0 to estimate liquidity\n /// @param pool Address of the pool to be used\n /// @param sqrtRatioX96 A sqrt price representing the current pool prices\n /// @param tickLow Lower tick boundary\n /// @param tickLow Upper tick boundary\n /// @dev If the given pool address is not the zero address, will fetch `sqrtRatioX96` from pool\n /// @return amount1 Estimated amount of token1\n function estimateAmount1(\n uint256 amount0,\n address pool,\n uint160 sqrtRatioX96,\n int24 tickLow,\n int24 tickHigh\n ) external view returns (uint256 amount1);\n\n ///\n /// Wrappers for PositionValue\n ///\n\n function principal(\n INonfungiblePositionManager positionManager,\n uint256 tokenId,\n uint160 sqrtRatioX96\n ) external view returns (uint256 amount0, uint256 amount1);\n\n function fees(INonfungiblePositionManager positionManager, uint256 tokenId)\n external\n view\n returns (uint256 amount0, uint256 amount1);\n\n ///\n /// Wrappers for TickMath\n ///\n\n function getSqrtRatioAtTick(int24 tick)\n external\n pure\n returns (uint160 sqrtRatioX96);\n\n function getTickAtSqrtRatio(uint160 sqrtRatioX96)\n external\n pure\n returns (int24 tick);\n\n /// @notice Fetches Tick Data for all populated Ticks in given bitmaps\n /// @param pool Address of the pool from which to fetch data\n /// @param startTick Tick from which the first bitmap will be fetched\n /// @dev The number of bitmaps fetched by this function should always be `MAX_BITMAPS`,\n /// unless there are less than `MAX_BITMAPS` left to iterate through\n /// @return populatedTicks Array of all Populated Ticks in the provided bitmaps\n function getPopulatedTicks(address pool, int24 startTick)\n external\n view\n returns (PopulatedTick[] memory populatedTicks);\n}\n" + }, + "contracts/interfaces/aerodrome/ISwapRouter.sol": { + "content": "// SPDX-License-Identifier: GPL-2.0-or-later\npragma solidity >=0.7.5;\npragma abicoder v2;\n\n/// @title Router token swapping functionality\n/// @notice Functions for swapping tokens via CL\ninterface ISwapRouter {\n struct ExactInputSingleParams {\n address tokenIn;\n address tokenOut;\n int24 tickSpacing;\n address recipient;\n uint256 deadline;\n uint256 amountIn;\n uint256 amountOutMinimum;\n uint160 sqrtPriceLimitX96;\n }\n\n /// @notice Swaps `amountIn` of one token for as much as possible of another token\n /// @param params The parameters necessary for the swap, encoded as `ExactInputSingleParams` in calldata\n /// @return amountOut The amount of the received token\n function exactInputSingle(ExactInputSingleParams calldata params)\n external\n payable\n returns (uint256 amountOut);\n\n struct ExactInputParams {\n bytes path;\n address recipient;\n uint256 deadline;\n uint256 amountIn;\n uint256 amountOutMinimum;\n }\n\n /// @notice Swaps `amountIn` of one token for as much as possible of another along the specified path\n /// @param params The parameters necessary for the multi-hop swap, encoded as `ExactInputParams` in calldata\n /// @return amountOut The amount of the received token\n function exactInput(ExactInputParams calldata params)\n external\n payable\n returns (uint256 amountOut);\n\n struct ExactOutputSingleParams {\n address tokenIn;\n address tokenOut;\n int24 tickSpacing;\n address recipient;\n uint256 deadline;\n uint256 amountOut;\n uint256 amountInMaximum;\n uint160 sqrtPriceLimitX96;\n }\n\n /// @notice Swaps as little as possible of one token for `amountOut` of another token\n /// @param params The parameters necessary for the swap, encoded as `ExactOutputSingleParams` in calldata\n /// @return amountIn The amount of the input token\n function exactOutputSingle(ExactOutputSingleParams calldata params)\n external\n payable\n returns (uint256 amountIn);\n\n struct ExactOutputParams {\n bytes path;\n address recipient;\n uint256 deadline;\n uint256 amountOut;\n uint256 amountInMaximum;\n }\n\n /// @notice Swaps as little as possible of one token for `amountOut` of another along the specified path (reversed)\n /// @param params The parameters necessary for the multi-hop swap, encoded as `ExactOutputParams` in calldata\n /// @return amountIn The amount of the input token\n function exactOutput(ExactOutputParams calldata params)\n external\n payable\n returns (uint256 amountIn);\n}\n" + }, + "contracts/interfaces/balancer/IBalancerVault.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport { IERC20 } from \"../../utils/InitializableAbstractStrategy.sol\";\n\ninterface IBalancerVault {\n enum WeightedPoolJoinKind {\n INIT,\n EXACT_TOKENS_IN_FOR_BPT_OUT,\n TOKEN_IN_FOR_EXACT_BPT_OUT,\n ALL_TOKENS_IN_FOR_EXACT_BPT_OUT,\n ADD_TOKEN\n }\n\n enum WeightedPoolExitKind {\n EXACT_BPT_IN_FOR_ONE_TOKEN_OUT,\n EXACT_BPT_IN_FOR_TOKENS_OUT,\n BPT_IN_FOR_EXACT_TOKENS_OUT,\n REMOVE_TOKEN\n }\n\n /**\n * @dev Called by users to join a Pool, which transfers tokens from `sender` into the Pool's balance. This will\n * trigger custom Pool behavior, which will typically grant something in return to `recipient` - often tokenized\n * Pool shares.\n *\n * If the caller is not `sender`, it must be an authorized relayer for them.\n *\n * The `assets` and `maxAmountsIn` arrays must have the same length, and each entry indicates the maximum amount\n * to send for each asset. The amounts to send are decided by the Pool and not the Vault: it just enforces\n * these maximums.\n *\n * If joining a Pool that holds WETH, it is possible to send ETH directly: the Vault will do the wrapping. To enable\n * this mechanism, the IAsset sentinel value (the zero address) must be passed in the `assets` array instead of the\n * WETH address. Note that it is not possible to combine ETH and WETH in the same join. Any excess ETH will be sent\n * back to the caller (not the sender, which is important for relayers).\n *\n * `assets` must have the same length and order as the array returned by `getPoolTokens`. This prevents issues when\n * interacting with Pools that register and deregister tokens frequently. If sending ETH however, the array must be\n * sorted *before* replacing the WETH address with the ETH sentinel value (the zero address), which means the final\n * `assets` array might not be sorted. Pools with no registered tokens cannot be joined.\n *\n * If `fromInternalBalance` is true, the caller's Internal Balance will be preferred: ERC20 transfers will only\n * be made for the difference between the requested amount and Internal Balance (if any). Note that ETH cannot be\n * withdrawn from Internal Balance: attempting to do so will trigger a revert.\n *\n * This causes the Vault to call the `IBasePool.onJoinPool` hook on the Pool's contract, where Pools implement\n * their own custom logic. This typically requires additional information from the user (such as the expected number\n * of Pool shares). This can be encoded in the `userData` argument, which is ignored by the Vault and passed\n * directly to the Pool's contract, as is `recipient`.\n *\n * Emits a `PoolBalanceChanged` event.\n */\n function joinPool(\n bytes32 poolId,\n address sender,\n address recipient,\n JoinPoolRequest memory request\n ) external payable;\n\n struct JoinPoolRequest {\n address[] assets;\n uint256[] maxAmountsIn;\n bytes userData;\n bool fromInternalBalance;\n }\n\n /**\n * @dev Called by users to exit a Pool, which transfers tokens from the Pool's balance to `recipient`. This will\n * trigger custom Pool behavior, which will typically ask for something in return from `sender` - often tokenized\n * Pool shares. The amount of tokens that can be withdrawn is limited by the Pool's `cash` balance (see\n * `getPoolTokenInfo`).\n *\n * If the caller is not `sender`, it must be an authorized relayer for them.\n *\n * The `tokens` and `minAmountsOut` arrays must have the same length, and each entry in these indicates the minimum\n * token amount to receive for each token contract. The amounts to send are decided by the Pool and not the Vault:\n * it just enforces these minimums.\n *\n * If exiting a Pool that holds WETH, it is possible to receive ETH directly: the Vault will do the unwrapping. To\n * enable this mechanism, the IAsset sentinel value (the zero address) must be passed in the `assets` array instead\n * of the WETH address. Note that it is not possible to combine ETH and WETH in the same exit.\n *\n * `assets` must have the same length and order as the array returned by `getPoolTokens`. This prevents issues when\n * interacting with Pools that register and deregister tokens frequently. If receiving ETH however, the array must\n * be sorted *before* replacing the WETH address with the ETH sentinel value (the zero address), which means the\n * final `assets` array might not be sorted. Pools with no registered tokens cannot be exited.\n *\n * If `toInternalBalance` is true, the tokens will be deposited to `recipient`'s Internal Balance. Otherwise,\n * an ERC20 transfer will be performed. Note that ETH cannot be deposited to Internal Balance: attempting to\n * do so will trigger a revert.\n *\n * `minAmountsOut` is the minimum amount of tokens the user expects to get out of the Pool, for each token in the\n * `tokens` array. This array must match the Pool's registered tokens.\n *\n * This causes the Vault to call the `IBasePool.onExitPool` hook on the Pool's contract, where Pools implement\n * their own custom logic. This typically requires additional information from the user (such as the expected number\n * of Pool shares to return). This can be encoded in the `userData` argument, which is ignored by the Vault and\n * passed directly to the Pool's contract.\n *\n * Emits a `PoolBalanceChanged` event.\n */\n function exitPool(\n bytes32 poolId,\n address sender,\n address payable recipient,\n ExitPoolRequest memory request\n ) external;\n\n struct ExitPoolRequest {\n address[] assets;\n uint256[] minAmountsOut;\n bytes userData;\n bool toInternalBalance;\n }\n\n /**\n * @dev Returns a Pool's registered tokens, the total balance for each, and the latest block when *any* of\n * the tokens' `balances` changed.\n *\n * The order of the `tokens` array is the same order that will be used in `joinPool`, `exitPool`, as well as in all\n * Pool hooks (where applicable). Calls to `registerTokens` and `deregisterTokens` may change this order.\n *\n * If a Pool only registers tokens once, and these are sorted in ascending order, they will be stored in the same\n * order as passed to `registerTokens`.\n *\n * Total balances include both tokens held by the Vault and those withdrawn by the Pool's Asset Managers. These are\n * the amounts used by joins, exits and swaps. For a detailed breakdown of token balances, use `getPoolTokenInfo`\n * instead.\n */\n function getPoolTokens(bytes32 poolId)\n external\n view\n returns (\n IERC20[] memory tokens,\n uint256[] memory balances,\n uint256 lastChangeBlock\n );\n\n /**\n * @dev Performs a set of user balance operations, which involve Internal Balance (deposit, withdraw or transfer)\n * and plain ERC20 transfers using the Vault's allowance. This last feature is particularly useful for relayers, as\n * it lets integrators reuse a user's Vault allowance.\n *\n * For each operation, if the caller is not `sender`, it must be an authorized relayer for them.\n */\n function manageUserBalance(UserBalanceOp[] memory ops) external payable;\n\n struct UserBalanceOp {\n UserBalanceOpKind kind;\n address asset;\n uint256 amount;\n address sender;\n address payable recipient;\n }\n\n enum UserBalanceOpKind {\n DEPOSIT_INTERNAL,\n WITHDRAW_INTERNAL,\n TRANSFER_INTERNAL,\n TRANSFER_EXTERNAL\n }\n\n enum SwapKind {\n GIVEN_IN,\n GIVEN_OUT\n }\n\n struct SingleSwap {\n bytes32 poolId;\n SwapKind kind;\n address assetIn;\n address assetOut;\n uint256 amount;\n bytes userData;\n }\n\n struct FundManagement {\n address sender;\n bool fromInternalBalance;\n address payable recipient;\n bool toInternalBalance;\n }\n\n function swap(\n SingleSwap calldata singleSwap,\n FundManagement calldata funds,\n uint256 limit,\n uint256 deadline\n ) external returns (uint256 amountCalculated);\n\n function getPoolTokenInfo(bytes32 poolId, address token)\n external\n view\n returns (\n uint256 cash,\n uint256 managed,\n uint256 lastChangeBlock,\n address assetManager\n );\n}\n" + }, + "contracts/interfaces/balancer/IMetaStablePool.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport { IRateProvider } from \"./IRateProvider.sol\";\n\ninterface IMetaStablePool {\n function getRateProviders()\n external\n view\n returns (IRateProvider[] memory providers);\n}\n" + }, + "contracts/interfaces/balancer/IRateProvider.sol": { + "content": "// SPDX-License-Identifier: GPL-3.0-or-later\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see .\n\npragma solidity ^0.8.0;\n\ninterface IRateProvider {\n function getRate() external view returns (uint256);\n}\n" + }, + "contracts/interfaces/cctp/ICCTP.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\ninterface ICCTPTokenMessenger {\n function depositForBurn(\n uint256 amount,\n uint32 destinationDomain,\n bytes32 mintRecipient,\n address burnToken,\n bytes32 destinationCaller,\n uint256 maxFee,\n uint32 minFinalityThreshold\n ) external;\n\n function depositForBurnWithHook(\n uint256 amount,\n uint32 destinationDomain,\n bytes32 mintRecipient,\n address burnToken,\n bytes32 destinationCaller,\n uint256 maxFee,\n uint32 minFinalityThreshold,\n bytes memory hookData\n ) external;\n\n function getMinFeeAmount(uint256 amount) external view returns (uint256);\n}\n\ninterface ICCTPMessageTransmitter {\n function sendMessage(\n uint32 destinationDomain,\n bytes32 recipient,\n bytes32 destinationCaller,\n uint32 minFinalityThreshold,\n bytes memory messageBody\n ) external;\n\n function receiveMessage(bytes calldata message, bytes calldata attestation)\n external\n returns (bool);\n}\n\ninterface IMessageHandlerV2 {\n /**\n * @notice Handles an incoming finalized message from an IReceiverV2\n * @dev Finalized messages have finality threshold values greater than or equal to 2000\n * @param sourceDomain The source domain of the message\n * @param sender The sender of the message\n * @param finalityThresholdExecuted the finality threshold at which the message was attested to\n * @param messageBody The raw bytes of the message body\n * @return success True, if successful; false, if not.\n */\n function handleReceiveFinalizedMessage(\n uint32 sourceDomain,\n bytes32 sender,\n uint32 finalityThresholdExecuted,\n bytes calldata messageBody\n ) external returns (bool);\n\n /**\n * @notice Handles an incoming unfinalized message from an IReceiverV2\n * @dev Unfinalized messages have finality threshold values less than 2000\n * @param sourceDomain The source domain of the message\n * @param sender The sender of the message\n * @param finalityThresholdExecuted The finality threshold at which the message was attested to\n * @param messageBody The raw bytes of the message body\n * @return success True, if successful; false, if not.\n */\n function handleReceiveUnfinalizedMessage(\n uint32 sourceDomain,\n bytes32 sender,\n uint32 finalityThresholdExecuted,\n bytes calldata messageBody\n ) external returns (bool);\n}\n" + }, + "contracts/interfaces/chainlink/AggregatorV3Interface.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ninterface AggregatorV3Interface {\n function decimals() external view returns (uint8);\n\n function description() external view returns (string memory);\n\n function version() external view returns (uint256);\n\n // getRoundData and latestRoundData should both raise \"No data present\"\n // if they do not have data to report, instead of returning unset values\n // which could be misinterpreted as actual reported values.\n function getRoundData(uint80 _roundId)\n external\n view\n returns (\n uint80 roundId,\n int256 answer,\n uint256 startedAt,\n uint256 updatedAt,\n uint80 answeredInRound\n );\n\n function latestRoundData()\n external\n view\n returns (\n uint80 roundId,\n int256 answer,\n uint256 startedAt,\n uint256 updatedAt,\n uint80 answeredInRound\n );\n}\n" + }, + "contracts/interfaces/IBasicToken.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ninterface IBasicToken {\n function symbol() external view returns (string memory);\n\n function decimals() external view returns (uint8);\n}\n" + }, + "contracts/interfaces/IBeaconProofs.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\ninterface IBeaconProofs {\n function verifyValidator(\n bytes32 beaconBlockRoot,\n bytes32 pubKeyHash,\n bytes calldata validatorPubKeyProof,\n uint40 validatorIndex,\n bytes32 withdrawalCredentials\n ) external view;\n\n function verifyValidatorWithdrawable(\n bytes32 beaconBlockRoot,\n uint40 validatorIndex,\n uint64 withdrawableEpoch,\n bytes calldata withdrawableEpochProof\n ) external view;\n\n function verifyBalancesContainer(\n bytes32 beaconBlockRoot,\n bytes32 balancesContainerLeaf,\n bytes calldata balancesContainerProof\n ) external view;\n\n function verifyValidatorBalance(\n bytes32 balancesContainerRoot,\n bytes32 validatorBalanceLeaf,\n bytes calldata balanceProof,\n uint40 validatorIndex\n ) external view returns (uint256 validatorBalance);\n\n function verifyPendingDepositsContainer(\n bytes32 beaconBlockRoot,\n bytes32 pendingDepositsContainerRoot,\n bytes calldata proof\n ) external view;\n\n function verifyPendingDeposit(\n bytes32 pendingDepositsContainerRoot,\n bytes32 pendingDepositRoot,\n bytes calldata proof,\n uint32 pendingDepositIndex\n ) external view;\n\n function verifyFirstPendingDeposit(\n bytes32 beaconBlockRoot,\n uint64 slot,\n bytes calldata firstPendingDepositSlotProof\n ) external view returns (bool isEmptyDepositQueue);\n\n function merkleizePendingDeposit(\n bytes32 pubKeyHash,\n bytes calldata withdrawalCredentials,\n uint64 amountGwei,\n bytes calldata signature,\n uint64 slot\n ) external pure returns (bytes32 root);\n\n function merkleizeSignature(bytes calldata signature)\n external\n pure\n returns (bytes32 root);\n}\n" + }, + "contracts/interfaces/ICampaignRemoteManager.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\ninterface ICampaignRemoteManager {\n function createCampaign(\n CampaignCreationParams memory params,\n uint256 destinationChainId,\n uint256 additionalGasLimit,\n address votemarket\n ) external payable;\n\n function manageCampaign(\n CampaignManagementParams memory params,\n uint256 destinationChainId,\n uint256 additionalGasLimit,\n address votemarket\n ) external payable;\n\n function closeCampaign(\n CampaignClosingParams memory params,\n uint256 destinationChainId,\n uint256 additionalGasLimit,\n address votemarket\n ) external payable;\n\n struct CampaignCreationParams {\n uint256 chainId;\n address gauge;\n address manager;\n address rewardToken;\n uint8 numberOfPeriods;\n uint256 maxRewardPerVote;\n uint256 totalRewardAmount;\n address[] addresses;\n address hook;\n bool isWhitelist;\n }\n\n struct CampaignManagementParams {\n uint256 campaignId;\n address rewardToken;\n uint8 numberOfPeriods;\n uint256 totalRewardAmount;\n uint256 maxRewardPerVote;\n }\n\n struct CampaignClosingParams {\n uint256 campaignId;\n }\n}\n" + }, + "contracts/interfaces/IChildLiquidityGaugeFactory.sol": { + "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.8.4;\n\ninterface IChildLiquidityGaugeFactory {\n event DeployedGauge(\n address indexed _implementation,\n address indexed _lp_token,\n address indexed _deployer,\n bytes32 _salt,\n address _gauge\n );\n event Minted(\n address indexed _user,\n address indexed _gauge,\n uint256 _new_total\n );\n event TransferOwnership(address _old_owner, address _new_owner);\n event UpdateCallProxy(address _old_call_proxy, address _new_call_proxy);\n event UpdateImplementation(\n address _old_implementation,\n address _new_implementation\n );\n event UpdateManager(address _manager);\n event UpdateMirrored(address indexed _gauge, bool _mirrored);\n event UpdateRoot(address _factory, address _implementation);\n event UpdateVotingEscrow(\n address _old_voting_escrow,\n address _new_voting_escrow\n );\n\n function accept_transfer_ownership() external;\n\n function call_proxy() external view returns (address);\n\n function commit_transfer_ownership(address _future_owner) external;\n\n function crv() external view returns (address);\n\n function deploy_gauge(address _lp_token, bytes32 _salt)\n external\n returns (address);\n\n function deploy_gauge(\n address _lp_token,\n bytes32 _salt,\n address _manager\n ) external returns (address);\n\n function future_owner() external view returns (address);\n\n function gauge_data(address arg0) external view returns (uint256);\n\n function get_gauge(uint256 arg0) external view returns (address);\n\n function get_gauge_count() external view returns (uint256);\n\n function get_gauge_from_lp_token(address arg0)\n external\n view\n returns (address);\n\n function get_implementation() external view returns (address);\n\n function is_mirrored(address _gauge) external view returns (bool);\n\n function is_valid_gauge(address _gauge) external view returns (bool);\n\n function last_request(address _gauge) external view returns (uint256);\n\n function manager() external view returns (address);\n\n function mint(address _gauge) external;\n\n function mint_many(address[32] memory _gauges) external;\n\n function minted(address arg0, address arg1) external view returns (uint256);\n\n function owner() external view returns (address);\n\n function root_factory() external view returns (address);\n\n function root_implementation() external view returns (address);\n\n function set_call_proxy(address _new_call_proxy) external;\n\n function set_crv(address _crv) external;\n\n function set_implementation(address _implementation) external;\n\n function set_manager(address _new_manager) external;\n\n function set_mirrored(address _gauge, bool _mirrored) external;\n\n function set_root(address _factory, address _implementation) external;\n\n function set_voting_escrow(address _voting_escrow) external;\n\n function version() external view returns (string memory);\n\n function voting_escrow() external view returns (address);\n}\n" + }, + "contracts/interfaces/IComptroller.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ninterface IComptroller {\n // Claim all the COMP accrued by specific holders in specific markets for their supplies and/or borrows\n function claimComp(\n address[] memory holders,\n address[] memory cTokens,\n bool borrowers,\n bool suppliers\n ) external;\n\n function oracle() external view returns (address);\n}\n" + }, + "contracts/interfaces/ICreateX.sol": { + "content": "// SPDX-License-Identifier: AGPL-3.0-only\npragma solidity ^0.8.4;\n\n/**\n * @title CreateX Factory Interface Definition\n * @author pcaversaccio (https://web.archive.org/web/20230921103111/https://pcaversaccio.com/)\n * @custom:coauthor Matt Solomon (https://web.archive.org/web/20230921103335/https://mattsolomon.dev/)\n */\ninterface ICreateX {\n /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/\n /* TYPES */\n /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/\n\n struct Values {\n uint256 constructorAmount;\n uint256 initCallAmount;\n }\n\n /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/\n /* EVENTS */\n /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/\n\n event ContractCreation(address indexed newContract, bytes32 indexed salt);\n event ContractCreation(address indexed newContract);\n event Create3ProxyContractCreation(\n address indexed newContract,\n bytes32 indexed salt\n );\n\n /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/\n /* CUSTOM ERRORS */\n /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/\n\n error FailedContractCreation(address emitter);\n error FailedContractInitialisation(address emitter, bytes revertData);\n error InvalidSalt(address emitter);\n error InvalidNonceValue(address emitter);\n error FailedEtherTransfer(address emitter, bytes revertData);\n\n /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/\n /* CREATE */\n /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/\n\n function deployCreate(bytes memory initCode)\n external\n payable\n returns (address newContract);\n\n function deployCreateAndInit(\n bytes memory initCode,\n bytes memory data,\n Values memory values,\n address refundAddress\n ) external payable returns (address newContract);\n\n function deployCreateAndInit(\n bytes memory initCode,\n bytes memory data,\n Values memory values\n ) external payable returns (address newContract);\n\n function deployCreateClone(address implementation, bytes memory data)\n external\n payable\n returns (address proxy);\n\n function computeCreateAddress(address deployer, uint256 nonce)\n external\n view\n returns (address computedAddress);\n\n function computeCreateAddress(uint256 nonce)\n external\n view\n returns (address computedAddress);\n\n /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/\n /* CREATE2 */\n /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/\n\n function deployCreate2(bytes32 salt, bytes memory initCode)\n external\n payable\n returns (address newContract);\n\n function deployCreate2(bytes memory initCode)\n external\n payable\n returns (address newContract);\n\n function deployCreate2AndInit(\n bytes32 salt,\n bytes memory initCode,\n bytes memory data,\n Values memory values,\n address refundAddress\n ) external payable returns (address newContract);\n\n function deployCreate2AndInit(\n bytes32 salt,\n bytes memory initCode,\n bytes memory data,\n Values memory values\n ) external payable returns (address newContract);\n\n function deployCreate2AndInit(\n bytes memory initCode,\n bytes memory data,\n Values memory values,\n address refundAddress\n ) external payable returns (address newContract);\n\n function deployCreate2AndInit(\n bytes memory initCode,\n bytes memory data,\n Values memory values\n ) external payable returns (address newContract);\n\n function deployCreate2Clone(\n bytes32 salt,\n address implementation,\n bytes memory data\n ) external payable returns (address proxy);\n\n function deployCreate2Clone(address implementation, bytes memory data)\n external\n payable\n returns (address proxy);\n\n function computeCreate2Address(\n bytes32 salt,\n bytes32 initCodeHash,\n address deployer\n ) external pure returns (address computedAddress);\n\n function computeCreate2Address(bytes32 salt, bytes32 initCodeHash)\n external\n view\n returns (address computedAddress);\n\n /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/\n /* CREATE3 */\n /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/\n\n function deployCreate3(bytes32 salt, bytes memory initCode)\n external\n payable\n returns (address newContract);\n\n function deployCreate3(bytes memory initCode)\n external\n payable\n returns (address newContract);\n\n function deployCreate3AndInit(\n bytes32 salt,\n bytes memory initCode,\n bytes memory data,\n Values memory values,\n address refundAddress\n ) external payable returns (address newContract);\n\n function deployCreate3AndInit(\n bytes32 salt,\n bytes memory initCode,\n bytes memory data,\n Values memory values\n ) external payable returns (address newContract);\n\n function deployCreate3AndInit(\n bytes memory initCode,\n bytes memory data,\n Values memory values,\n address refundAddress\n ) external payable returns (address newContract);\n\n function deployCreate3AndInit(\n bytes memory initCode,\n bytes memory data,\n Values memory values\n ) external payable returns (address newContract);\n\n function computeCreate3Address(bytes32 salt, address deployer)\n external\n pure\n returns (address computedAddress);\n\n function computeCreate3Address(bytes32 salt)\n external\n view\n returns (address computedAddress);\n}\n" + }, + "contracts/interfaces/ICurveLiquidityGaugeV6.sol": { + "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.8.4;\n\ninterface ICurveLiquidityGaugeV6 {\n event ApplyOwnership(address admin);\n event Approval(\n address indexed _owner,\n address indexed _spender,\n uint256 _value\n );\n event CommitOwnership(address admin);\n event Deposit(address indexed provider, uint256 value);\n event SetGaugeManager(address _gauge_manager);\n event Transfer(address indexed _from, address indexed _to, uint256 _value);\n event UpdateLiquidityLimit(\n address indexed user,\n uint256 original_balance,\n uint256 original_supply,\n uint256 working_balance,\n uint256 working_supply\n );\n event Withdraw(address indexed provider, uint256 value);\n\n function DOMAIN_SEPARATOR() external view returns (bytes32);\n\n function add_reward(address _reward_token, address _distributor) external;\n\n function allowance(address arg0, address arg1)\n external\n view\n returns (uint256);\n\n function approve(address _spender, uint256 _value) external returns (bool);\n\n function balanceOf(address arg0) external view returns (uint256);\n\n function claim_rewards() external;\n\n function claim_rewards(address _addr) external;\n\n function claim_rewards(address _addr, address _receiver) external;\n\n function claimable_reward(address _user, address _reward_token)\n external\n view\n returns (uint256);\n\n function claimable_tokens(address addr) external returns (uint256);\n\n function claimed_reward(address _addr, address _token)\n external\n view\n returns (uint256);\n\n function decimals() external view returns (uint256);\n\n function decreaseAllowance(address _spender, uint256 _subtracted_value)\n external\n returns (bool);\n\n function deposit(uint256 _value) external;\n\n function deposit(uint256 _value, address _addr) external;\n\n function deposit(\n uint256 _value,\n address _addr,\n bool _claim_rewards\n ) external;\n\n function deposit_reward_token(address _reward_token, uint256 _amount)\n external;\n\n function deposit_reward_token(\n address _reward_token,\n uint256 _amount,\n uint256 _epoch\n ) external;\n\n function factory() external view returns (address);\n\n function future_epoch_time() external view returns (uint256);\n\n function increaseAllowance(address _spender, uint256 _added_value)\n external\n returns (bool);\n\n function inflation_rate() external view returns (uint256);\n\n function integrate_checkpoint() external view returns (uint256);\n\n function integrate_checkpoint_of(address arg0)\n external\n view\n returns (uint256);\n\n function integrate_fraction(address arg0) external view returns (uint256);\n\n function integrate_inv_supply(uint256 arg0) external view returns (uint256);\n\n function integrate_inv_supply_of(address arg0)\n external\n view\n returns (uint256);\n\n function is_killed() external view returns (bool);\n\n function kick(address addr) external;\n\n function lp_token() external view returns (address);\n\n function manager() external view returns (address);\n\n function name() external view returns (string memory);\n\n function nonces(address arg0) external view returns (uint256);\n\n function period() external view returns (int128);\n\n function period_timestamp(uint256 arg0) external view returns (uint256);\n\n function permit(\n address _owner,\n address _spender,\n uint256 _value,\n uint256 _deadline,\n uint8 _v,\n bytes32 _r,\n bytes32 _s\n ) external returns (bool);\n\n function reward_count() external view returns (uint256);\n\n function reward_integral_for(address arg0, address arg1)\n external\n view\n returns (uint256);\n\n function reward_tokens(uint256 arg0) external view returns (address);\n\n function rewards_receiver(address arg0) external view returns (address);\n\n function salt() external view returns (bytes32);\n\n function set_gauge_manager(address _gauge_manager) external;\n\n function set_killed(bool _is_killed) external;\n\n function set_reward_distributor(address _reward_token, address _distributor)\n external;\n\n function set_rewards_receiver(address _receiver) external;\n\n function symbol() external view returns (string memory);\n\n function totalSupply() external view returns (uint256);\n\n function transfer(address _to, uint256 _value) external returns (bool);\n\n function transferFrom(\n address _from,\n address _to,\n uint256 _value\n ) external returns (bool);\n\n function user_checkpoint(address addr) external returns (bool);\n\n function version() external view returns (string memory);\n\n function withdraw(uint256 _value) external;\n\n function withdraw(uint256 _value, bool _claim_rewards) external;\n\n function working_balances(address arg0) external view returns (uint256);\n\n function working_supply() external view returns (uint256);\n}\n" + }, + "contracts/interfaces/ICurveMinter.sol": { + "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.8.4;\n\ninterface ICurveMinter {\n event Minted(address indexed recipient, address gauge, uint256 minted);\n\n function allowed_to_mint_for(address arg0, address arg1)\n external\n view\n returns (bool);\n\n function controller() external view returns (address);\n\n function mint(address gauge_addr) external;\n\n function mint_for(address gauge_addr, address _for) external;\n\n function mint_many(address[8] memory gauge_addrs) external;\n\n function minted(address arg0, address arg1) external view returns (uint256);\n\n function toggle_approve_mint(address minting_user) external;\n\n function token() external view returns (address);\n}\n" + }, + "contracts/interfaces/ICurveStableSwapNG.sol": { + "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.8.4;\n\ninterface ICurveStableSwapNG {\n event AddLiquidity(\n address indexed provider,\n uint256[] token_amounts,\n uint256[] fees,\n uint256 invariant,\n uint256 token_supply\n );\n event ApplyNewFee(uint256 fee, uint256 offpeg_fee_multiplier);\n event Approval(\n address indexed owner,\n address indexed spender,\n uint256 value\n );\n event RampA(\n uint256 old_A,\n uint256 new_A,\n uint256 initial_time,\n uint256 future_time\n );\n event RemoveLiquidity(\n address indexed provider,\n uint256[] token_amounts,\n uint256[] fees,\n uint256 token_supply\n );\n event RemoveLiquidityImbalance(\n address indexed provider,\n uint256[] token_amounts,\n uint256[] fees,\n uint256 invariant,\n uint256 token_supply\n );\n event RemoveLiquidityOne(\n address indexed provider,\n int128 token_id,\n uint256 token_amount,\n uint256 coin_amount,\n uint256 token_supply\n );\n event SetNewMATime(uint256 ma_exp_time, uint256 D_ma_time);\n event StopRampA(uint256 A, uint256 t);\n event TokenExchange(\n address indexed buyer,\n int128 sold_id,\n uint256 tokens_sold,\n int128 bought_id,\n uint256 tokens_bought\n );\n event TokenExchangeUnderlying(\n address indexed buyer,\n int128 sold_id,\n uint256 tokens_sold,\n int128 bought_id,\n uint256 tokens_bought\n );\n event Transfer(\n address indexed sender,\n address indexed receiver,\n uint256 value\n );\n\n function A() external view returns (uint256);\n\n function A_precise() external view returns (uint256);\n\n function DOMAIN_SEPARATOR() external view returns (bytes32);\n\n function D_ma_time() external view returns (uint256);\n\n function D_oracle() external view returns (uint256);\n\n function N_COINS() external view returns (uint256);\n\n function add_liquidity(uint256[] memory _amounts, uint256 _min_mint_amount)\n external\n returns (uint256);\n\n function add_liquidity(\n uint256[] memory _amounts,\n uint256 _min_mint_amount,\n address _receiver\n ) external returns (uint256);\n\n function admin_balances(uint256 arg0) external view returns (uint256);\n\n function admin_fee() external view returns (uint256);\n\n function allowance(address arg0, address arg1)\n external\n view\n returns (uint256);\n\n function approve(address _spender, uint256 _value) external returns (bool);\n\n function balanceOf(address arg0) external view returns (uint256);\n\n function balances(uint256 i) external view returns (uint256);\n\n function calc_token_amount(uint256[] memory _amounts, bool _is_deposit)\n external\n view\n returns (uint256);\n\n function calc_withdraw_one_coin(uint256 _burn_amount, int128 i)\n external\n view\n returns (uint256);\n\n function coins(uint256 arg0) external view returns (address);\n\n function decimals() external view returns (uint8);\n\n function dynamic_fee(int128 i, int128 j) external view returns (uint256);\n\n function ema_price(uint256 i) external view returns (uint256);\n\n function exchange(\n int128 i,\n int128 j,\n uint256 _dx,\n uint256 _min_dy\n ) external returns (uint256);\n\n function exchange(\n int128 i,\n int128 j,\n uint256 _dx,\n uint256 _min_dy,\n address _receiver\n ) external returns (uint256);\n\n function exchange_received(\n int128 i,\n int128 j,\n uint256 _dx,\n uint256 _min_dy\n ) external returns (uint256);\n\n function exchange_received(\n int128 i,\n int128 j,\n uint256 _dx,\n uint256 _min_dy,\n address _receiver\n ) external returns (uint256);\n\n function fee() external view returns (uint256);\n\n function future_A() external view returns (uint256);\n\n function future_A_time() external view returns (uint256);\n\n function get_balances() external view returns (uint256[] memory);\n\n function get_dx(\n int128 i,\n int128 j,\n uint256 dy\n ) external view returns (uint256);\n\n function get_dy(\n int128 i,\n int128 j,\n uint256 dx\n ) external view returns (uint256);\n\n function get_p(uint256 i) external view returns (uint256);\n\n function get_virtual_price() external view returns (uint256);\n\n function initial_A() external view returns (uint256);\n\n function initial_A_time() external view returns (uint256);\n\n function last_price(uint256 i) external view returns (uint256);\n\n function ma_exp_time() external view returns (uint256);\n\n function ma_last_time() external view returns (uint256);\n\n function name() external view returns (string memory);\n\n function nonces(address arg0) external view returns (uint256);\n\n function offpeg_fee_multiplier() external view returns (uint256);\n\n function permit(\n address _owner,\n address _spender,\n uint256 _value,\n uint256 _deadline,\n uint8 _v,\n bytes32 _r,\n bytes32 _s\n ) external returns (bool);\n\n function price_oracle(uint256 i) external view returns (uint256);\n\n function ramp_A(uint256 _future_A, uint256 _future_time) external;\n\n function remove_liquidity(\n uint256 _burn_amount,\n uint256[] memory _min_amounts\n ) external returns (uint256[] memory);\n\n function remove_liquidity(\n uint256 _burn_amount,\n uint256[] memory _min_amounts,\n address _receiver\n ) external returns (uint256[] memory);\n\n function remove_liquidity(\n uint256 _burn_amount,\n uint256[] memory _min_amounts,\n address _receiver,\n bool _claim_admin_fees\n ) external returns (uint256[] memory);\n\n function remove_liquidity_imbalance(\n uint256[] memory _amounts,\n uint256 _max_burn_amount\n ) external returns (uint256);\n\n function remove_liquidity_imbalance(\n uint256[] memory _amounts,\n uint256 _max_burn_amount,\n address _receiver\n ) external returns (uint256);\n\n function remove_liquidity_one_coin(\n uint256 _burn_amount,\n int128 i,\n uint256 _min_received\n ) external returns (uint256);\n\n function remove_liquidity_one_coin(\n uint256 _burn_amount,\n int128 i,\n uint256 _min_received,\n address _receiver\n ) external returns (uint256);\n\n function salt() external view returns (bytes32);\n\n function set_ma_exp_time(uint256 _ma_exp_time, uint256 _D_ma_time) external;\n\n function set_new_fee(uint256 _new_fee, uint256 _new_offpeg_fee_multiplier)\n external;\n\n function stop_ramp_A() external;\n\n function stored_rates() external view returns (uint256[] memory);\n\n function symbol() external view returns (string memory);\n\n function totalSupply() external view returns (uint256);\n\n function transfer(address _to, uint256 _value) external returns (bool);\n\n function transferFrom(\n address _from,\n address _to,\n uint256 _value\n ) external returns (bool);\n\n function version() external view returns (string memory);\n\n function withdraw_admin_fees() external;\n}\n" + }, + "contracts/interfaces/ICurveXChainLiquidityGauge.sol": { + "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.8.4;\n\ninterface ICurveXChainLiquidityGauge {\n event Approval(\n address indexed _owner,\n address indexed _spender,\n uint256 _value\n );\n event Deposit(address indexed provider, uint256 value);\n event SetGaugeManager(address _gauge_manager);\n event Transfer(address indexed _from, address indexed _to, uint256 _value);\n event UpdateLiquidityLimit(\n address indexed user,\n uint256 original_balance,\n uint256 original_supply,\n uint256 working_balance,\n uint256 working_supply\n );\n event Withdraw(address indexed provider, uint256 value);\n\n function DOMAIN_SEPARATOR() external view returns (bytes32);\n\n function add_reward(address _reward_token, address _distributor) external;\n\n function allowance(address arg0, address arg1)\n external\n view\n returns (uint256);\n\n function approve(address _spender, uint256 _value) external returns (bool);\n\n function balanceOf(address arg0) external view returns (uint256);\n\n function claim_rewards() external;\n\n function claim_rewards(address _addr) external;\n\n function claim_rewards(address _addr, address _receiver) external;\n\n function claimable_reward(address _user, address _reward_token)\n external\n view\n returns (uint256);\n\n function claimable_tokens(address addr) external returns (uint256);\n\n function claimed_reward(address _addr, address _token)\n external\n view\n returns (uint256);\n\n function decimals() external view returns (uint256);\n\n function decreaseAllowance(address _spender, uint256 _subtracted_value)\n external\n returns (bool);\n\n function deposit(uint256 _value) external;\n\n function deposit(uint256 _value, address _addr) external;\n\n function deposit(\n uint256 _value,\n address _addr,\n bool _claim_rewards\n ) external;\n\n function deposit_reward_token(address _reward_token, uint256 _amount)\n external;\n\n function deposit_reward_token(\n address _reward_token,\n uint256 _amount,\n uint256 _epoch\n ) external;\n\n function factory() external view returns (address);\n\n function increaseAllowance(address _spender, uint256 _added_value)\n external\n returns (bool);\n\n function inflation_rate(uint256 arg0) external view returns (uint256);\n\n function initialize(\n address _lp_token,\n address _root,\n address _manager\n ) external;\n\n function integrate_checkpoint() external view returns (uint256);\n\n function integrate_checkpoint_of(address arg0)\n external\n view\n returns (uint256);\n\n function integrate_fraction(address arg0) external view returns (uint256);\n\n function integrate_inv_supply(int128 arg0) external view returns (uint256);\n\n function integrate_inv_supply_of(address arg0)\n external\n view\n returns (uint256);\n\n function is_killed() external view returns (bool);\n\n function lp_token() external view returns (address);\n\n function manager() external view returns (address);\n\n function name() external view returns (string memory);\n\n function nonces(address arg0) external view returns (uint256);\n\n function period() external view returns (int128);\n\n function period_timestamp(int128 arg0) external view returns (uint256);\n\n function permit(\n address _owner,\n address _spender,\n uint256 _value,\n uint256 _deadline,\n uint8 _v,\n bytes32 _r,\n bytes32 _s\n ) external returns (bool);\n\n function recover_remaining(address _reward_token) external;\n\n function reward_count() external view returns (uint256);\n\n function reward_integral_for(address arg0, address arg1)\n external\n view\n returns (uint256);\n\n function reward_remaining(address arg0) external view returns (uint256);\n\n function reward_tokens(uint256 arg0) external view returns (address);\n\n function rewards_receiver(address arg0) external view returns (address);\n\n function root_gauge() external view returns (address);\n\n function set_gauge_manager(address _gauge_manager) external;\n\n function set_killed(bool _is_killed) external;\n\n function set_manager(address _gauge_manager) external;\n\n function set_reward_distributor(address _reward_token, address _distributor)\n external;\n\n function set_rewards_receiver(address _receiver) external;\n\n function set_root_gauge(address _root) external;\n\n function symbol() external view returns (string memory);\n\n function totalSupply() external view returns (uint256);\n\n function transfer(address _to, uint256 _value) external returns (bool);\n\n function transferFrom(\n address _from,\n address _to,\n uint256 _value\n ) external returns (bool);\n\n function update_voting_escrow() external;\n\n function user_checkpoint(address addr) external returns (bool);\n\n function version() external view returns (string memory);\n\n function voting_escrow() external view returns (address);\n\n function withdraw(uint256 _value) external;\n\n function withdraw(uint256 _value, bool _claim_rewards) external;\n\n function withdraw(\n uint256 _value,\n bool _claim_rewards,\n address _receiver\n ) external;\n\n function working_balances(address arg0) external view returns (uint256);\n\n function working_supply() external view returns (uint256);\n}\n" + }, + "contracts/interfaces/ICVXLocker.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ninterface ICVXLocker {\n function lock(\n address _account,\n uint256 _amount,\n uint256 _spendRatio\n ) external;\n\n function lockedBalanceOf(address _account) external view returns (uint256);\n}\n" + }, + "contracts/interfaces/IDepositContract.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ninterface IDepositContract {\n /// @notice A processed deposit event.\n event DepositEvent(\n bytes pubkey,\n bytes withdrawal_credentials,\n bytes amount,\n bytes signature,\n bytes index\n );\n\n /// @notice Submit a Phase 0 DepositData object.\n /// @param pubkey A BLS12-381 public key.\n /// @param withdrawal_credentials Commitment to a public key for withdrawals.\n /// @param signature A BLS12-381 signature.\n /// @param deposit_data_root The SHA-256 hash of the SSZ-encoded DepositData object.\n /// Used as a protection against malformed input.\n function deposit(\n bytes calldata pubkey,\n bytes calldata withdrawal_credentials,\n bytes calldata signature,\n bytes32 deposit_data_root\n ) external payable;\n\n /// @notice Query the current deposit root hash.\n /// @return The deposit root hash.\n function get_deposit_root() external view returns (bytes32);\n\n /// @notice Query the current deposit count.\n /// @return The deposit count encoded as a little endian 64-bit number.\n function get_deposit_count() external view returns (bytes memory);\n}\n" + }, + "contracts/interfaces/IMerkl.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\ninterface IDistributor {\n event Claimed(address indexed user, address indexed token, uint256 amount);\n\n function claim(\n address[] calldata users,\n address[] calldata tokens,\n uint256[] calldata amounts,\n bytes32[][] calldata proofs\n ) external;\n}\n" + }, + "contracts/interfaces/IOracle.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\ninterface IOracle {\n /**\n * @dev returns the asset price in USD, in 8 decimal digits.\n *\n * The version of priceProvider deployed for OETH has 18 decimal digits\n */\n function price(address asset) external view returns (uint256);\n}\n" + }, + "contracts/interfaces/ISafe.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\ninterface ISafe {\n function execTransactionFromModule(\n address,\n uint256,\n bytes memory,\n uint8\n ) external returns (bool);\n}\n" + }, + "contracts/interfaces/ISSVNetwork.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nstruct Cluster {\n uint32 validatorCount;\n uint64 networkFeeIndex;\n uint64 index;\n bool active;\n uint256 balance;\n}\n\ninterface ISSVNetwork {\n /**********/\n /* Errors */\n /**********/\n\n error CallerNotOwner(); // 0x5cd83192\n error CallerNotWhitelisted(); // 0x8c6e5d71\n error FeeTooLow(); // 0x732f9413\n error FeeExceedsIncreaseLimit(); // 0x958065d9\n error NoFeeDeclared(); // 0x1d226c30\n error ApprovalNotWithinTimeframe(); // 0x97e4b518\n error OperatorDoesNotExist(); // 0x961e3e8c\n error InsufficientBalance(); // 0xf4d678b8\n error ValidatorDoesNotExist(); // 0xe51315d2\n error ClusterNotLiquidatable(); // 0x60300a8d\n error InvalidPublicKeyLength(); // 0x637297a4\n error InvalidOperatorIdsLength(); // 0x38186224\n error ClusterAlreadyEnabled(); // 0x3babafd2\n error ClusterIsLiquidated(); // 0x95a0cf33\n error ClusterDoesNotExists(); // 0x185e2b16\n error IncorrectClusterState(); // 0x12e04c87\n error UnsortedOperatorsList(); // 0xdd020e25\n error NewBlockPeriodIsBelowMinimum(); // 0x6e6c9cac\n error ExceedValidatorLimit(); // 0x6df5ab76\n error TokenTransferFailed(); // 0x045c4b02\n error SameFeeChangeNotAllowed(); // 0xc81272f8\n error FeeIncreaseNotAllowed(); // 0x410a2b6c\n error NotAuthorized(); // 0xea8e4eb5\n error OperatorsListNotUnique(); // 0xa5a1ff5d\n error OperatorAlreadyExists(); // 0x289c9494\n error TargetModuleDoesNotExist(); // 0x8f9195fb\n error MaxValueExceeded(); // 0x91aa3017\n error FeeTooHigh(); // 0xcd4e6167\n error PublicKeysSharesLengthMismatch(); // 0x9ad467b8\n error IncorrectValidatorStateWithData(bytes publicKey); // 0x89307938\n error ValidatorAlreadyExistsWithData(bytes publicKey); // 0x388e7999\n error EmptyPublicKeysList(); // df83e679\n\n // legacy errors\n error ValidatorAlreadyExists(); // 0x8d09a73e\n error IncorrectValidatorState(); // 0x2feda3c1\n\n event AdminChanged(address previousAdmin, address newAdmin);\n event BeaconUpgraded(address indexed beacon);\n event ClusterDeposited(\n address indexed owner,\n uint64[] operatorIds,\n uint256 value,\n Cluster cluster\n );\n event ClusterLiquidated(\n address indexed owner,\n uint64[] operatorIds,\n Cluster cluster\n );\n event ClusterReactivated(\n address indexed owner,\n uint64[] operatorIds,\n Cluster cluster\n );\n event ClusterWithdrawn(\n address indexed owner,\n uint64[] operatorIds,\n uint256 value,\n Cluster cluster\n );\n event DeclareOperatorFeePeriodUpdated(uint64 value);\n event ExecuteOperatorFeePeriodUpdated(uint64 value);\n event FeeRecipientAddressUpdated(\n address indexed owner,\n address recipientAddress\n );\n event Initialized(uint8 version);\n event LiquidationThresholdPeriodUpdated(uint64 value);\n event MinimumLiquidationCollateralUpdated(uint256 value);\n event NetworkEarningsWithdrawn(uint256 value, address recipient);\n event NetworkFeeUpdated(uint256 oldFee, uint256 newFee);\n event OperatorAdded(\n uint64 indexed operatorId,\n address indexed owner,\n bytes publicKey,\n uint256 fee\n );\n event OperatorFeeDeclarationCancelled(\n address indexed owner,\n uint64 indexed operatorId\n );\n event OperatorFeeDeclared(\n address indexed owner,\n uint64 indexed operatorId,\n uint256 blockNumber,\n uint256 fee\n );\n event OperatorFeeExecuted(\n address indexed owner,\n uint64 indexed operatorId,\n uint256 blockNumber,\n uint256 fee\n );\n event OperatorFeeIncreaseLimitUpdated(uint64 value);\n event OperatorMaximumFeeUpdated(uint64 maxFee);\n event OperatorRemoved(uint64 indexed operatorId);\n event OperatorWhitelistUpdated(\n uint64 indexed operatorId,\n address whitelisted\n );\n event OperatorWithdrawn(\n address indexed owner,\n uint64 indexed operatorId,\n uint256 value\n );\n event OwnershipTransferStarted(\n address indexed previousOwner,\n address indexed newOwner\n );\n event OwnershipTransferred(\n address indexed previousOwner,\n address indexed newOwner\n );\n event Upgraded(address indexed implementation);\n event ValidatorAdded(\n address indexed owner,\n uint64[] operatorIds,\n bytes publicKey,\n bytes shares,\n Cluster cluster\n );\n event ValidatorExited(\n address indexed owner,\n uint64[] operatorIds,\n bytes publicKey\n );\n event ValidatorRemoved(\n address indexed owner,\n uint64[] operatorIds,\n bytes publicKey,\n Cluster cluster\n );\n\n fallback() external;\n\n function acceptOwnership() external;\n\n function cancelDeclaredOperatorFee(uint64 operatorId) external;\n\n function declareOperatorFee(uint64 operatorId, uint256 fee) external;\n\n function deposit(\n address clusterOwner,\n uint64[] memory operatorIds,\n uint256 amount,\n Cluster memory cluster\n ) external;\n\n function executeOperatorFee(uint64 operatorId) external;\n\n function exitValidator(bytes memory publicKey, uint64[] memory operatorIds)\n external;\n\n function bulkExitValidator(\n bytes[] calldata publicKeys,\n uint64[] calldata operatorIds\n ) external;\n\n function getVersion() external pure returns (string memory version);\n\n function initialize(\n address token_,\n address ssvOperators_,\n address ssvClusters_,\n address ssvDAO_,\n address ssvViews_,\n uint64 minimumBlocksBeforeLiquidation_,\n uint256 minimumLiquidationCollateral_,\n uint32 validatorsPerOperatorLimit_,\n uint64 declareOperatorFeePeriod_,\n uint64 executeOperatorFeePeriod_,\n uint64 operatorMaxFeeIncrease_\n ) external;\n\n function liquidate(\n address clusterOwner,\n uint64[] memory operatorIds,\n Cluster memory cluster\n ) external;\n\n function owner() external view returns (address);\n\n function pendingOwner() external view returns (address);\n\n function proxiableUUID() external view returns (bytes32);\n\n function reactivate(\n uint64[] memory operatorIds,\n uint256 amount,\n Cluster memory cluster\n ) external;\n\n function reduceOperatorFee(uint64 operatorId, uint256 fee) external;\n\n function registerOperator(bytes memory publicKey, uint256 fee)\n external\n returns (uint64 id);\n\n function registerValidator(\n bytes memory publicKey,\n uint64[] memory operatorIds,\n bytes memory sharesData,\n uint256 amount,\n Cluster memory cluster\n ) external;\n\n function bulkRegisterValidator(\n bytes[] calldata publicKeys,\n uint64[] calldata operatorIds,\n bytes[] calldata sharesData,\n uint256 amount,\n Cluster memory cluster\n ) external;\n\n function removeOperator(uint64 operatorId) external;\n\n function removeValidator(\n bytes memory publicKey,\n uint64[] memory operatorIds,\n Cluster memory cluster\n ) external;\n\n function bulkRemoveValidator(\n bytes[] calldata publicKeys,\n uint64[] calldata operatorIds,\n Cluster memory cluster\n ) external;\n\n function renounceOwnership() external;\n\n function setFeeRecipientAddress(address recipientAddress) external;\n\n function setOperatorWhitelist(uint64 operatorId, address whitelisted)\n external;\n\n function transferOwnership(address newOwner) external;\n\n function updateDeclareOperatorFeePeriod(uint64 timeInSeconds) external;\n\n function updateExecuteOperatorFeePeriod(uint64 timeInSeconds) external;\n\n function updateLiquidationThresholdPeriod(uint64 blocks) external;\n\n function updateMaximumOperatorFee(uint64 maxFee) external;\n\n function updateMinimumLiquidationCollateral(uint256 amount) external;\n\n function updateModule(uint8 moduleId, address moduleAddress) external;\n\n function updateNetworkFee(uint256 fee) external;\n\n function updateOperatorFeeIncreaseLimit(uint64 percentage) external;\n\n function upgradeTo(address newImplementation) external;\n\n function upgradeToAndCall(address newImplementation, bytes memory data)\n external\n payable;\n\n function withdraw(\n uint64[] memory operatorIds,\n uint256 amount,\n Cluster memory cluster\n ) external;\n\n function withdrawAllOperatorEarnings(uint64 operatorId) external;\n\n function withdrawNetworkEarnings(uint256 amount) external;\n\n function withdrawOperatorEarnings(uint64 operatorId, uint256 amount)\n external;\n}\n" + }, + "contracts/interfaces/IStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title Platform interface to integrate with lending platform like Compound, AAVE etc.\n */\ninterface IStrategy {\n /**\n * @dev Deposit the given asset to platform\n * @param _asset asset address\n * @param _amount Amount to deposit\n */\n function deposit(address _asset, uint256 _amount) external;\n\n /**\n * @dev Deposit the entire balance of all supported assets in the Strategy\n * to the platform\n */\n function depositAll() external;\n\n /**\n * @dev Withdraw given asset from Lending platform\n */\n function withdraw(\n address _recipient,\n address _asset,\n uint256 _amount\n ) external;\n\n /**\n * @dev Liquidate all assets in strategy and return them to Vault.\n */\n function withdrawAll() external;\n\n /**\n * @dev Returns the current balance of the given asset.\n */\n function checkBalance(address _asset)\n external\n view\n returns (uint256 balance);\n\n /**\n * @dev Returns bool indicating whether strategy supports asset.\n */\n function supportsAsset(address _asset) external view returns (bool);\n\n /**\n * @dev Collect reward tokens from the Strategy.\n */\n function collectRewardTokens() external;\n\n /**\n * @dev The address array of the reward tokens for the Strategy.\n */\n function getRewardTokenAddresses() external view returns (address[] memory);\n\n function harvesterAddress() external view returns (address);\n\n function transferToken(address token, uint256 amount) external;\n\n function setRewardTokenAddresses(address[] calldata _rewardTokenAddresses)\n external;\n}\n" + }, + "contracts/interfaces/ISwapper.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\ninterface ISwapper {\n /**\n * @param fromAsset The token address of the asset being sold.\n * @param toAsset The token address of the asset being purchased.\n * @param fromAssetAmount The amount of assets being sold.\n * @param minToAssetAmmount The minimum amount of assets to be purchased.\n * @param data tx.data returned from 1Inch's /v5.0/1/swap API\n */\n function swap(\n address fromAsset,\n address toAsset,\n uint256 fromAssetAmount,\n uint256 minToAssetAmmount,\n bytes calldata data\n ) external returns (uint256 toAssetAmount);\n}\n" + }, + "contracts/interfaces/IVault.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { VaultStorage } from \"../vault/VaultStorage.sol\";\n\ninterface IVault {\n // slither-disable-start constable-states\n\n event AssetAllocated(address _asset, address _strategy, uint256 _amount);\n event StrategyApproved(address _addr);\n event StrategyRemoved(address _addr);\n event Mint(address _addr, uint256 _value);\n event Redeem(address _addr, uint256 _value);\n event CapitalPaused();\n event CapitalUnpaused();\n event DefaultStrategyUpdated(address _strategy);\n event RebasePaused();\n event RebaseUnpaused();\n event VaultBufferUpdated(uint256 _vaultBuffer);\n event AllocateThresholdUpdated(uint256 _threshold);\n event RebaseThresholdUpdated(uint256 _threshold);\n event StrategistUpdated(address _address);\n event MaxSupplyDiffChanged(uint256 maxSupplyDiff);\n event YieldDistribution(address _to, uint256 _yield, uint256 _fee);\n event TrusteeFeeBpsChanged(uint256 _basis);\n event TrusteeAddressChanged(address _address);\n event StrategyAddedToMintWhitelist(address indexed strategy);\n event StrategyRemovedFromMintWhitelist(address indexed strategy);\n event RebasePerSecondMaxChanged(uint256 rebaseRatePerSecond);\n event DripDurationChanged(uint256 dripDuration);\n event WithdrawalRequested(\n address indexed _withdrawer,\n uint256 indexed _requestId,\n uint256 _amount,\n uint256 _queued\n );\n event WithdrawalClaimed(\n address indexed _withdrawer,\n uint256 indexed _requestId,\n uint256 _amount\n );\n event WithdrawalClaimable(uint256 _claimable, uint256 _newClaimable);\n event WithdrawalClaimDelayUpdated(uint256 _newDelay);\n\n // Governable.sol\n function transferGovernance(address _newGovernor) external;\n\n function claimGovernance() external;\n\n function governor() external view returns (address);\n\n // VaultAdmin.sol\n function setVaultBuffer(uint256 _vaultBuffer) external;\n\n function vaultBuffer() external view returns (uint256);\n\n function setAutoAllocateThreshold(uint256 _threshold) external;\n\n function autoAllocateThreshold() external view returns (uint256);\n\n function setRebaseThreshold(uint256 _threshold) external;\n\n function rebaseThreshold() external view returns (uint256);\n\n function setStrategistAddr(address _address) external;\n\n function strategistAddr() external view returns (address);\n\n function setMaxSupplyDiff(uint256 _maxSupplyDiff) external;\n\n function maxSupplyDiff() external view returns (uint256);\n\n function setTrusteeAddress(address _address) external;\n\n function trusteeAddress() external view returns (address);\n\n function setTrusteeFeeBps(uint256 _basis) external;\n\n function trusteeFeeBps() external view returns (uint256);\n\n function approveStrategy(address _addr) external;\n\n function removeStrategy(address _addr) external;\n\n function setDefaultStrategy(address _strategy) external;\n\n function defaultStrategy() external view returns (address);\n\n function pauseRebase() external;\n\n function unpauseRebase() external;\n\n function rebasePaused() external view returns (bool);\n\n function pauseCapital() external;\n\n function unpauseCapital() external;\n\n function capitalPaused() external view returns (bool);\n\n function transferToken(address _asset, uint256 _amount) external;\n\n function withdrawAllFromStrategy(address _strategyAddr) external;\n\n function withdrawAllFromStrategies() external;\n\n function withdrawFromStrategy(\n address _strategyFromAddress,\n address[] calldata _assets,\n uint256[] calldata _amounts\n ) external;\n\n function depositToStrategy(\n address _strategyToAddress,\n address[] calldata _assets,\n uint256[] calldata _amounts\n ) external;\n\n // VaultCore.sol\n function mint(\n address _asset,\n uint256 _amount,\n uint256 _minimumOusdAmount\n ) external;\n\n function mintForStrategy(uint256 _amount) external;\n\n function redeem(uint256 _amount, uint256 _minimumUnitAmount) external;\n\n function burnForStrategy(uint256 _amount) external;\n\n function allocate() external;\n\n function rebase() external;\n\n function totalValue() external view returns (uint256 value);\n\n function checkBalance(address _asset) external view returns (uint256);\n\n /// @notice Deprecated: use calculateRedeemOutput\n function calculateRedeemOutputs(uint256 _amount)\n external\n view\n returns (uint256[] memory);\n\n function calculateRedeemOutput(uint256 _amount)\n external\n view\n returns (uint256);\n\n function getAssetCount() external view returns (uint256);\n\n function getAllAssets() external view returns (address[] memory);\n\n function getStrategyCount() external view returns (uint256);\n\n function getAllStrategies() external view returns (address[] memory);\n\n /// @notice Deprecated.\n function isSupportedAsset(address _asset) external view returns (bool);\n\n function dripper() external view returns (address);\n\n function asset() external view returns (address);\n\n function initialize(address) external;\n\n function addWithdrawalQueueLiquidity() external;\n\n function requestWithdrawal(uint256 _amount)\n external\n returns (uint256 requestId, uint256 queued);\n\n function claimWithdrawal(uint256 requestId)\n external\n returns (uint256 amount);\n\n function claimWithdrawals(uint256[] memory requestIds)\n external\n returns (uint256[] memory amounts, uint256 totalAmount);\n\n function withdrawalQueueMetadata()\n external\n view\n returns (VaultStorage.WithdrawalQueueMetadata memory);\n\n function withdrawalRequests(uint256 requestId)\n external\n view\n returns (VaultStorage.WithdrawalRequest memory);\n\n function addStrategyToMintWhitelist(address strategyAddr) external;\n\n function removeStrategyFromMintWhitelist(address strategyAddr) external;\n\n function isMintWhitelistedStrategy(address strategyAddr)\n external\n view\n returns (bool);\n\n function withdrawalClaimDelay() external view returns (uint256);\n\n function setWithdrawalClaimDelay(uint256 newDelay) external;\n\n function lastRebase() external view returns (uint64);\n\n function dripDuration() external view returns (uint64);\n\n function setDripDuration(uint256 _dripDuration) external;\n\n function rebasePerSecondMax() external view returns (uint64);\n\n function setRebaseRateMax(uint256 yearlyApr) external;\n\n function rebasePerSecondTarget() external view returns (uint64);\n\n function previewYield() external view returns (uint256 yield);\n\n function weth() external view returns (address);\n\n // slither-disable-end constable-states\n}\n" + }, + "contracts/interfaces/IWETH9.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ninterface IWETH9 {\n event Approval(address indexed src, address indexed guy, uint256 wad);\n event Deposit(address indexed dst, uint256 wad);\n event Transfer(address indexed src, address indexed dst, uint256 wad);\n event Withdrawal(address indexed src, uint256 wad);\n\n function allowance(address, address) external view returns (uint256);\n\n function approve(address guy, uint256 wad) external returns (bool);\n\n function balanceOf(address) external view returns (uint256);\n\n function decimals() external view returns (uint8);\n\n function deposit() external payable;\n\n function name() external view returns (string memory);\n\n function symbol() external view returns (string memory);\n\n function totalSupply() external view returns (uint256);\n\n function transfer(address dst, uint256 wad) external returns (bool);\n\n function transferFrom(\n address src,\n address dst,\n uint256 wad\n ) external returns (bool);\n\n function withdraw(uint256 wad) external;\n}\n" + }, + "contracts/interfaces/IWstETH.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ninterface IWstETH {\n /**\n * @notice Get amount of wstETH for a given amount of stETH\n * @param _stETHAmount amount of stETH\n * @return Amount of wstETH for a given stETH amount\n */\n function getWstETHByStETH(uint256 _stETHAmount)\n external\n view\n returns (uint256);\n\n /**\n * @notice Get amount of stETH for a given amount of wstETH\n * @param _wstETHAmount amount of wstETH\n * @return Amount of stETH for a given wstETH amount\n */\n function getStETHByWstETH(uint256 _wstETHAmount)\n external\n view\n returns (uint256);\n\n /**\n * @notice Get amount of stETH for a one wstETH\n * @return Amount of stETH for 1 wstETH\n */\n function stEthPerToken() external view returns (uint256);\n\n /**\n * @notice Get amount of wstETH for a one stETH\n * @return Amount of wstETH for a 1 stETH\n */\n function tokensPerStEth() external view returns (uint256);\n\n /**\n * @notice Exchanges stETH to wstETH\n * @param _stETHAmount amount of stETH to wrap in exchange for wstETH\n * @dev Requirements:\n * - `_stETHAmount` must be non-zero\n * - msg.sender must approve at least `_stETHAmount` stETH to this\n * contract.\n * - msg.sender must have at least `_stETHAmount` of stETH.\n * User should first approve _stETHAmount to the WstETH contract\n * @return Amount of wstETH user receives after wrap\n */\n function wrap(uint256 _stETHAmount) external returns (uint256);\n\n /**\n * @notice Exchanges wstETH to stETH\n * @param _wstETHAmount amount of wstETH to uwrap in exchange for stETH\n * @dev Requirements:\n * - `_wstETHAmount` must be non-zero\n * - msg.sender must have at least `_wstETHAmount` wstETH.\n * @return Amount of stETH user receives after unwrap\n */\n function unwrap(uint256 _wstETHAmount) external returns (uint256);\n}\n" + }, + "contracts/interfaces/morpho/compound/ICompoundOracle.sol": { + "content": "// SPDX-License-Identifier: GNU AGPLv3\npragma solidity ^0.8.0;\n\ninterface ICompoundOracle {\n function getUnderlyingPrice(address) external view returns (uint256);\n}\n" + }, + "contracts/interfaces/morpho/ILens.sol": { + "content": "// SPDX-License-Identifier: GNU AGPLv3\npragma solidity ^0.8.0;\n\nimport \"./compound/ICompoundOracle.sol\";\nimport \"./IMorpho.sol\";\n\ninterface ILens {\n /// STORAGE ///\n\n function MAX_BASIS_POINTS() external view returns (uint256);\n\n function WAD() external view returns (uint256);\n\n function morpho() external view returns (IMorpho);\n\n function comptroller() external view returns (IComptroller);\n\n /// GENERAL ///\n\n function getTotalSupply()\n external\n view\n returns (\n uint256 p2pSupplyAmount,\n uint256 poolSupplyAmount,\n uint256 totalSupplyAmount\n );\n\n function getTotalBorrow()\n external\n view\n returns (\n uint256 p2pBorrowAmount,\n uint256 poolBorrowAmount,\n uint256 totalBorrowAmount\n );\n\n /// MARKETS ///\n\n function isMarketCreated(address _poolToken) external view returns (bool);\n\n function isMarketCreatedAndNotPaused(address _poolToken)\n external\n view\n returns (bool);\n\n function isMarketCreatedAndNotPausedNorPartiallyPaused(address _poolToken)\n external\n view\n returns (bool);\n\n function getAllMarkets()\n external\n view\n returns (address[] memory marketsCreated_);\n\n function getMainMarketData(address _poolToken)\n external\n view\n returns (\n uint256 avgSupplyRatePerBlock,\n uint256 avgBorrowRatePerBlock,\n uint256 p2pSupplyAmount,\n uint256 p2pBorrowAmount,\n uint256 poolSupplyAmount,\n uint256 poolBorrowAmount\n );\n\n function getAdvancedMarketData(address _poolToken)\n external\n view\n returns (\n uint256 p2pSupplyIndex,\n uint256 p2pBorrowIndex,\n uint256 poolSupplyIndex,\n uint256 poolBorrowIndex,\n uint32 lastUpdateBlockNumber,\n uint256 p2pSupplyDelta,\n uint256 p2pBorrowDelta\n );\n\n function getMarketConfiguration(address _poolToken)\n external\n view\n returns (\n address underlying,\n bool isCreated,\n bool p2pDisabled,\n bool isPaused,\n bool isPartiallyPaused,\n uint16 reserveFactor,\n uint16 p2pIndexCursor,\n uint256 collateralFactor\n );\n\n function getTotalMarketSupply(address _poolToken)\n external\n view\n returns (uint256 p2pSupplyAmount, uint256 poolSupplyAmount);\n\n function getTotalMarketBorrow(address _poolToken)\n external\n view\n returns (uint256 p2pBorrowAmount, uint256 poolBorrowAmount);\n\n /// INDEXES ///\n\n function getCurrentP2PSupplyIndex(address _poolToken)\n external\n view\n returns (uint256);\n\n function getCurrentP2PBorrowIndex(address _poolToken)\n external\n view\n returns (uint256);\n\n function getCurrentPoolIndexes(address _poolToken)\n external\n view\n returns (\n uint256 currentPoolSupplyIndex,\n uint256 currentPoolBorrowIndex\n );\n\n function getIndexes(address _poolToken, bool _computeUpdatedIndexes)\n external\n view\n returns (\n uint256 p2pSupplyIndex,\n uint256 p2pBorrowIndex,\n uint256 poolSupplyIndex,\n uint256 poolBorrowIndex\n );\n\n /// USERS ///\n\n function getEnteredMarkets(address _user)\n external\n view\n returns (address[] memory enteredMarkets);\n\n function getUserHealthFactor(\n address _user,\n address[] calldata _updatedMarkets\n ) external view returns (uint256);\n\n function getUserBalanceStates(\n address _user,\n address[] calldata _updatedMarkets\n )\n external\n view\n returns (\n uint256 collateralValue,\n uint256 debtValue,\n uint256 maxDebtValue\n );\n\n function getCurrentSupplyBalanceInOf(address _poolToken, address _user)\n external\n view\n returns (\n uint256 balanceOnPool,\n uint256 balanceInP2P,\n uint256 totalBalance\n );\n\n function getCurrentBorrowBalanceInOf(address _poolToken, address _user)\n external\n view\n returns (\n uint256 balanceOnPool,\n uint256 balanceInP2P,\n uint256 totalBalance\n );\n\n function getUserMaxCapacitiesForAsset(address _user, address _poolToken)\n external\n view\n returns (uint256 withdrawable, uint256 borrowable);\n\n function getUserHypotheticalBalanceStates(\n address _user,\n address _poolToken,\n uint256 _withdrawnAmount,\n uint256 _borrowedAmount\n ) external view returns (uint256 debtValue, uint256 maxDebtValue);\n\n function getUserLiquidityDataForAsset(\n address _user,\n address _poolToken,\n bool _computeUpdatedIndexes,\n ICompoundOracle _oracle\n ) external view returns (Types.AssetLiquidityData memory assetData);\n\n function isLiquidatable(address _user, address[] memory _updatedMarkets)\n external\n view\n returns (bool);\n\n function computeLiquidationRepayAmount(\n address _user,\n address _poolTokenBorrowed,\n address _poolTokenCollateral,\n address[] calldata _updatedMarkets\n ) external view returns (uint256 toRepay);\n\n /// RATES ///\n\n function getAverageSupplyRatePerBlock(address _poolToken)\n external\n view\n returns (\n uint256 avgSupplyRatePerBlock,\n uint256 p2pSupplyAmount,\n uint256 poolSupplyAmount\n );\n\n function getAverageBorrowRatePerBlock(address _poolToken)\n external\n view\n returns (\n uint256 avgBorrowRatePerBlock,\n uint256 p2pBorrowAmount,\n uint256 poolBorrowAmount\n );\n\n function getNextUserSupplyRatePerBlock(\n address _poolToken,\n address _user,\n uint256 _amount\n )\n external\n view\n returns (\n uint256 nextSupplyRatePerBlock,\n uint256 balanceOnPool,\n uint256 balanceInP2P,\n uint256 totalBalance\n );\n\n function getNextUserBorrowRatePerBlock(\n address _poolToken,\n address _user,\n uint256 _amount\n )\n external\n view\n returns (\n uint256 nextBorrowRatePerBlock,\n uint256 balanceOnPool,\n uint256 balanceInP2P,\n uint256 totalBalance\n );\n\n function getCurrentUserSupplyRatePerBlock(address _poolToken, address _user)\n external\n view\n returns (uint256);\n\n function getCurrentUserBorrowRatePerBlock(address _poolToken, address _user)\n external\n view\n returns (uint256);\n\n function getRatesPerBlock(address _poolToken)\n external\n view\n returns (\n uint256 p2pSupplyRate,\n uint256 p2pBorrowRate,\n uint256 poolSupplyRate,\n uint256 poolBorrowRate\n );\n\n /// REWARDS ///\n\n function getUserUnclaimedRewards(\n address[] calldata _poolTokens,\n address _user\n ) external view returns (uint256 unclaimedRewards);\n\n function getAccruedSupplierComp(\n address _supplier,\n address _poolToken,\n uint256 _balance\n ) external view returns (uint256);\n\n function getAccruedBorrowerComp(\n address _borrower,\n address _poolToken,\n uint256 _balance\n ) external view returns (uint256);\n\n function getCurrentCompSupplyIndex(address _poolToken)\n external\n view\n returns (uint256);\n\n function getCurrentCompBorrowIndex(address _poolToken)\n external\n view\n returns (uint256);\n}\n" + }, + "contracts/interfaces/morpho/IMorpho.sol": { + "content": "// SPDX-License-Identifier: GNU AGPLv3\npragma solidity ^0.8.0;\n\nimport \"./Types.sol\";\nimport \"../IComptroller.sol\";\nimport \"./compound/ICompoundOracle.sol\";\n\n// prettier-ignore\ninterface IMorpho {\n function comptroller() external view returns (IComptroller);\n function supply(address _poolTokenAddress, address _onBehalf, uint256 _amount) external;\n function supply(address _poolTokenAddress, address _onBehalf, uint256 _amount, uint256 _maxGasForMatching) external;\n function withdraw(address _poolTokenAddress, uint256 _amount) external;\n function claimRewards(\n address[] calldata _cTokenAddresses,\n bool _tradeForMorphoToken\n ) external returns (uint256 claimedAmount);\n}\n" + }, + "contracts/interfaces/morpho/Types.sol": { + "content": "// SPDX-License-Identifier: GNU AGPLv3\npragma solidity ^0.8.0;\n\n/// @title Types.\n/// @author Morpho Labs.\n/// @custom:contact security@morpho.xyz\n/// @dev Common types and structs used in Moprho contracts.\nlibrary Types {\n /// ENUMS ///\n\n enum PositionType {\n SUPPLIERS_IN_P2P,\n SUPPLIERS_ON_POOL,\n BORROWERS_IN_P2P,\n BORROWERS_ON_POOL\n }\n\n /// STRUCTS ///\n\n struct SupplyBalance {\n uint256 inP2P; // In supplier's peer-to-peer unit, a unit that grows in underlying value, to keep track of the interests earned by suppliers in peer-to-peer. Multiply by the peer-to-peer supply index to get the underlying amount.\n uint256 onPool; // In cToken. Multiply by the pool supply index to get the underlying amount.\n }\n\n struct BorrowBalance {\n uint256 inP2P; // In borrower's peer-to-peer unit, a unit that grows in underlying value, to keep track of the interests paid by borrowers in peer-to-peer. Multiply by the peer-to-peer borrow index to get the underlying amount.\n uint256 onPool; // In cdUnit, a unit that grows in value, to keep track of the debt increase when borrowers are on Compound. Multiply by the pool borrow index to get the underlying amount.\n }\n\n // Max gas to consume during the matching process for supply, borrow, withdraw and repay functions.\n struct MaxGasForMatching {\n uint64 supply;\n uint64 borrow;\n uint64 withdraw;\n uint64 repay;\n }\n\n struct Delta {\n uint256 p2pSupplyDelta; // Difference between the stored peer-to-peer supply amount and the real peer-to-peer supply amount (in pool supply unit).\n uint256 p2pBorrowDelta; // Difference between the stored peer-to-peer borrow amount and the real peer-to-peer borrow amount (in pool borrow unit).\n uint256 p2pSupplyAmount; // Sum of all stored peer-to-peer supply (in peer-to-peer supply unit).\n uint256 p2pBorrowAmount; // Sum of all stored peer-to-peer borrow (in peer-to-peer borrow unit).\n }\n\n struct AssetLiquidityData {\n uint256 collateralValue; // The collateral value of the asset.\n uint256 maxDebtValue; // The maximum possible debt value of the asset.\n uint256 debtValue; // The debt value of the asset.\n uint256 underlyingPrice; // The price of the token.\n uint256 collateralFactor; // The liquidation threshold applied on this token.\n }\n\n struct LiquidityData {\n uint256 collateralValue; // The collateral value.\n uint256 maxDebtValue; // The maximum debt value possible.\n uint256 debtValue; // The debt value.\n }\n\n // Variables are packed together to save gas (will not exceed their limit during Morpho's lifetime).\n struct LastPoolIndexes {\n uint32 lastUpdateBlockNumber; // The last time the peer-to-peer indexes were updated.\n uint112 lastSupplyPoolIndex; // Last pool supply index.\n uint112 lastBorrowPoolIndex; // Last pool borrow index.\n }\n\n struct MarketParameters {\n uint16 reserveFactor; // Proportion of the interest earned by users sent to the DAO for each market, in basis point (100% = 10 000). The value is set at market creation.\n uint16 p2pIndexCursor; // Position of the peer-to-peer rate in the pool's spread. Determine the weights of the weighted arithmetic average in the indexes computations ((1 - p2pIndexCursor) * r^S + p2pIndexCursor * r^B) (in basis point).\n }\n\n struct MarketStatus {\n bool isCreated; // Whether or not this market is created.\n bool isPaused; // Whether the market is paused or not (all entry points on Morpho are frozen; supply, borrow, withdraw, repay and liquidate).\n bool isPartiallyPaused; // Whether the market is partially paused or not (only supply and borrow are frozen).\n }\n}\n" + }, + "contracts/interfaces/plume/IFeeRegistry.sol": { + "content": "// SPDX-License-Identifier: GPL-2.0-or-later\n// As the copyright holder of this work, Ubiquity Labs retains\n// the right to distribute, use, and modify this code under any license of\n// their choosing, in addition to the terms of the GPL-v2 or later.\npragma solidity ^0.8.25;\n\ninterface IFeeRegistry {\n function registerFee(\n bool isTokenA,\n uint32 binId,\n uint256 binFeeInQuote\n ) external;\n}\n" + }, + "contracts/interfaces/plume/ILiquidityRegistry.sol": { + "content": "// SPDX-License-Identifier: GPL-2.0-or-later\n// As the copyright holder of this work, Ubiquity Labs retains\n// the right to distribute, use, and modify this code under any license of\n// their choosing, in addition to the terms of the GPL-v2 or later.\npragma solidity ^0.8.25;\n\nimport { IMaverickV2Pool } from \"./IMaverickV2Pool.sol\";\n\ninterface ILiquidityRegistry {\n function notifyBinLiquidity(\n IMaverickV2Pool pool,\n uint256 tokenId,\n uint32 binId,\n uint256 currentBinLpBalance\n ) external;\n}\n" + }, + "contracts/interfaces/plume/IMaverickV2Factory.sol": { + "content": "// SPDX-License-Identifier: GPL-2.0-or-later\n// As the copyright holder of this work, Ubiquity Labs retains\n// the right to distribute, use, and modify this code under any license of\n// their choosing, in addition to the terms of the GPL-v2 or later.\npragma solidity ^0.8.25;\n\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\n\nimport { IFeeRegistry } from \"./IFeeRegistry.sol\";\nimport { IMaverickV2Pool } from \"./IMaverickV2Pool.sol\";\n\ninterface IMaverickV2Factory {\n error FactorAlreadyInitialized();\n error FactorNotInitialized();\n error FactoryInvalidTokenOrder(IERC20 _tokenA, IERC20 _tokenB);\n error FactoryInvalidFee();\n error FactoryInvalidKinds(uint8 kinds);\n error FactoryInvalidTickSpacing(uint256 tickSpacing);\n error FactoryInvalidLookback(uint256 lookback);\n error FactoryInvalidTokenDecimals(uint8 decimalsA, uint8 decimalsB);\n error FactoryPoolAlreadyExists(\n uint256 feeAIn,\n uint256 feeBIn,\n uint256 tickSpacing,\n uint256 lookback,\n IERC20 tokenA,\n IERC20 tokenB,\n uint8 kinds,\n address accessor\n );\n error FactoryAccessorMustBeNonZero();\n error NotImplemented();\n\n event PoolCreated(\n IMaverickV2Pool poolAddress,\n uint8 protocolFeeRatio,\n uint256 feeAIn,\n uint256 feeBIn,\n uint256 tickSpacing,\n uint256 lookback,\n int32 activeTick,\n IERC20 tokenA,\n IERC20 tokenB,\n uint8 kinds,\n address accessor\n );\n event SetFactoryProtocolFeeReceiver(address receiver);\n event SetFactoryProtocolFeeRegistry(IFeeRegistry registry);\n\n struct DeployParameters {\n uint64 feeAIn;\n uint64 feeBIn;\n uint32 lookback;\n int32 activeTick;\n uint64 tokenAScale;\n uint64 tokenBScale;\n // slot\n IERC20 tokenA;\n // slot\n IERC20 tokenB;\n // slot\n uint16 tickSpacing;\n uint8 options;\n address accessor;\n }\n\n /**\n * @notice Called by deployer library to initialize a pool.\n */\n function deployParameters()\n external\n view\n returns (\n uint64 feeAIn,\n uint64 feeBIn,\n uint32 lookback,\n int32 activeTick,\n uint64 tokenAScale,\n uint64 tokenBScale,\n // slot\n IERC20 tokenA,\n // slot\n IERC20 tokenB,\n // slot\n uint16 tickSpacing,\n uint8 options,\n address accessor\n );\n\n /**\n * @notice Create a new MaverickV2Pool with symmetric swap fees.\n * @param fee Fraction of the pool swap amount that is retained as an LP in\n * D18 scale.\n * @param tickSpacing Tick spacing of pool where 1.0001^tickSpacing is the\n * bin width.\n * @param lookback Pool lookback in seconds.\n * @param tokenA Address of tokenA.\n * @param tokenB Address of tokenB.\n * @param activeTick Tick position that contains the active bins.\n * @param kinds 1-15 number to represent the active kinds\n * 0b0001 = static;\n * 0b0010 = right;\n * 0b0100 = left;\n * 0b1000 = both.\n * E.g. a pool with all 4 modes will have kinds = b1111 = 15\n */\n function create(\n uint64 fee,\n uint16 tickSpacing,\n uint32 lookback,\n IERC20 tokenA,\n IERC20 tokenB,\n int32 activeTick,\n uint8 kinds\n ) external returns (IMaverickV2Pool);\n\n /**\n * @notice Create a new MaverickV2Pool.\n * @param feeAIn Fraction of the pool swap amount for tokenA-input swaps\n * that is retained as an LP in D18 scale.\n * @param feeBIn Fraction of the pool swap amount for tokenB-input swaps\n * that is retained as an LP in D18 scale.\n * @param tickSpacing Tick spacing of pool where 1.0001^tickSpacing is the\n * bin width.\n * @param lookback Pool lookback in seconds.\n * @param tokenA Address of tokenA.\n * @param tokenB Address of tokenB.\n * @param activeTick Tick position that contains the active bins.\n * @param kinds 1-15 number to represent the active kinds\n * 0b0001 = static;\n * 0b0010 = right;\n * 0b0100 = left;\n * 0b1000 = both.\n * e.g. a pool with all 4 modes will have kinds = b1111 = 15\n */\n function create(\n uint64 feeAIn,\n uint64 feeBIn,\n uint16 tickSpacing,\n uint32 lookback,\n IERC20 tokenA,\n IERC20 tokenB,\n int32 activeTick,\n uint8 kinds\n ) external returns (IMaverickV2Pool);\n\n /**\n * @notice Bool indicating whether the pool was deployed from this factory.\n */\n function isFactoryPool(IMaverickV2Pool pool) external view returns (bool);\n\n /**\n * @notice Address that receives the protocol fee\n */\n function protocolFeeReceiver() external view returns (address);\n\n /**\n * @notice Address notified on swaps of the protocol fee\n */\n function protocolFeeRegistry() external view returns (IFeeRegistry);\n\n /**\n * @notice Lookup a pool for given parameters.\n */\n function lookup(\n uint256 feeAIn,\n uint256 feeBIn,\n uint256 tickSpacing,\n uint256 lookback,\n IERC20 tokenA,\n IERC20 tokenB,\n uint8 kinds\n ) external view returns (IMaverickV2Pool);\n\n /**\n * @notice Lookup a pool for given parameters.\n */\n function lookup(\n IERC20 _tokenA,\n IERC20 _tokenB,\n uint256 startIndex,\n uint256 endIndex\n ) external view returns (IMaverickV2Pool[] memory pools);\n\n /**\n * @notice Lookup a pool for given parameters.\n */\n function lookup(uint256 startIndex, uint256 endIndex)\n external\n view\n returns (IMaverickV2Pool[] memory pools);\n\n /**\n * @notice Count of permissionless pools.\n */\n function poolCount() external view returns (uint256 _poolCount);\n\n /**\n * @notice Count of pools for a given accessor and token pair. For\n * permissionless pools, pass `accessor = address(0)`.\n */\n function poolByTokenCount(\n IERC20 _tokenA,\n IERC20 _tokenB,\n address accessor\n ) external view returns (uint256 _poolCount);\n\n /**\n * @notice Get the current factory owner.\n */\n function owner() external view returns (address);\n}\n" + }, + "contracts/interfaces/plume/IMaverickV2LiquidityManager.sol": { + "content": "// SPDX-License-Identifier: GPL-2.0-or-later\npragma solidity ^0.8.25;\n\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport { IMaverickV2Pool } from \"./IMaverickV2Pool.sol\";\n\nimport { IMaverickV2Position } from \"./IMaverickV2Position.sol\";\nimport { IMaverickV2PoolLens } from \"./IMaverickV2PoolLens.sol\";\n\ninterface IMaverickV2LiquidityManager {\n error LiquidityManagerNotFactoryPool();\n error LiquidityManagerNotTokenIdOwner();\n\n /**\n * @notice Maverick V2 NFT position contract that tracks NFT-based\n * liquditiy positions.\n */\n function position() external view returns (IMaverickV2Position);\n\n /**\n * @notice Create Maverick V2 pool. Function is a pass through to the pool\n * factory and is provided here so that is can be assembled as part of a\n * multicall transaction.\n */\n function createPool(\n uint64 fee,\n uint16 tickSpacing,\n uint32 lookback,\n IERC20 tokenA,\n IERC20 tokenB,\n int32 activeTick,\n uint8 kinds\n ) external payable returns (IMaverickV2Pool pool);\n\n /**\n * @notice Add Liquidity position NFT for msg.sender by specifying\n * msg.sender's token index.\n * @dev Token index is different from tokenId.\n * On the Position NFT contract a user can own multiple NFT tokenIds and\n * these are indexes by an enumeration index which is the `index` input\n * here.\n *\n * See addLiquidity for a description of the add params.\n */\n function addPositionLiquidityToSenderByTokenIndex(\n IMaverickV2Pool pool,\n uint256 index,\n bytes memory packedSqrtPriceBreaks,\n bytes[] memory packedArgs\n )\n external\n payable\n returns (\n uint256 tokenAAmount,\n uint256 tokenBAmount,\n uint32[] memory binIds\n );\n\n /**\n * @notice Mint new tokenId in the Position NFt contract to msg.sender.\n * Both mints an NFT and adds liquidity to the pool that is held by the\n * NFT.\n */\n function mintPositionNftToSender(\n IMaverickV2Pool pool,\n bytes calldata packedSqrtPriceBreaks,\n bytes[] calldata packedArgs\n )\n external\n payable\n returns (\n uint256 tokenAAmount,\n uint256 tokenBAmount,\n uint32[] memory binIds,\n uint256 tokenId\n );\n\n /**\n * @notice Donates liqudity to a pool that is held by the position contract\n * and will never be retrievable. Can be used to start a pool and ensure\n * there will always be a base level of liquditiy in the pool.\n */\n function donateLiquidity(\n IMaverickV2Pool pool,\n IMaverickV2Pool.AddLiquidityParams memory args\n ) external payable;\n\n /**\n * @notice Packs sqrtPrice breaks array with this format: [length,\n * array[0], array[1],..., array[length-1]] where length is 1 byte.\n */\n function packUint88Array(uint88[] memory fullArray)\n external\n pure\n returns (bytes memory packedArray);\n\n /**\n * @notice Packs addLiquidity paramters array element-wise.\n */\n function packAddLiquidityArgsArray(\n IMaverickV2Pool.AddLiquidityParams[] memory args\n ) external pure returns (bytes[] memory argsPacked);\n}\n" + }, + "contracts/interfaces/plume/IMaverickV2Pool.sol": { + "content": "// SPDX-License-Identifier: GPL-2.0-or-later\n// As the copyright holder of this work, Ubiquity Labs retains\n// the right to distribute, use, and modify this code under any license of\n// their choosing, in addition to the terms of the GPL-v2 or later.\npragma solidity ^0.8.0;\n\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport { IMaverickV2Factory } from \"./IMaverickV2Factory.sol\";\n\ninterface IMaverickV2Pool {\n error PoolZeroLiquidityAdded();\n error PoolMinimumLiquidityNotMet();\n error PoolLocked();\n error PoolInvalidFee();\n error PoolTicksNotSorted(uint256 index, int256 previousTick, int256 tick);\n error PoolTicksAmountsLengthMismatch(\n uint256 ticksLength,\n uint256 amountsLength\n );\n error PoolBinIdsAmountsLengthMismatch(\n uint256 binIdsLength,\n uint256 amountsLength\n );\n error PoolKindNotSupported(uint256 kinds, uint256 kind);\n error PoolInsufficientBalance(\n uint256 deltaLpAmount,\n uint256 accountBalance\n );\n error PoolReservesExceedMaximum(uint256 amount);\n error PoolValueExceedsBits(uint256 amount, uint256 bits);\n error PoolTickMaxExceeded(uint256 tick);\n error PoolMigrateBinFirst();\n error PoolCurrentTickBeyondSwapLimit(int32 startingTick);\n error PoolSenderNotAccessor(address sender_, address accessor);\n error PoolSenderNotFactory(address sender_, address accessor);\n error PoolFunctionNotImplemented();\n error PoolTokenNotSolvent(\n uint256 internalReserve,\n uint256 tokenBalance,\n IERC20 token\n );\n error PoolNoProtocolFeeReceiverSet();\n\n event PoolSwap(\n address sender,\n address recipient,\n SwapParams params,\n uint256 amountIn,\n uint256 amountOut\n );\n event PoolFlashLoan(\n address sender,\n address recipient,\n uint256 amountA,\n uint256 amountB\n );\n event PoolProtocolFeeCollected(IERC20 token, uint256 protocolFee);\n\n event PoolAddLiquidity(\n address sender,\n address recipient,\n uint256 subaccount,\n AddLiquidityParams params,\n uint256 tokenAAmount,\n uint256 tokenBAmount,\n uint32[] binIds\n );\n\n event PoolMigrateBinsUpStack(\n address sender,\n uint32 binId,\n uint32 maxRecursion\n );\n\n event PoolRemoveLiquidity(\n address sender,\n address recipient,\n uint256 subaccount,\n RemoveLiquidityParams params,\n uint256 tokenAOut,\n uint256 tokenBOut\n );\n\n event PoolTickState(int32 tick, uint256 reserveA, uint256 reserveB);\n event PoolTickBinUpdate(int32 tick, uint8 kind, uint32 binId);\n event PoolSqrtPrice(uint256 sqrtPrice);\n\n /**\n * @notice Tick state parameters.\n */\n struct TickState {\n uint128 reserveA;\n uint128 reserveB;\n uint128 totalSupply;\n uint32[4] binIdsByTick;\n }\n\n /**\n * @notice Tick data parameters.\n * @param currentReserveA Current reserve of token A.\n * @param currentReserveB Current reserve of token B.\n * @param currentLiquidity Current liquidity amount.\n */\n struct TickData {\n uint256 currentReserveA;\n uint256 currentReserveB;\n uint256 currentLiquidity;\n }\n\n /**\n * @notice Bin state parameters.\n * @param mergeBinBalance LP token balance that this bin possesses of the merge bin.\n * @param mergeId Bin ID of the bin that this bin has merged into.\n * @param totalSupply Total amount of LP tokens in this bin.\n * @param kind One of the 4 kinds (0=static, 1=right, 2=left, 3=both).\n * @param tick The lower price tick of the bin in its current state.\n * @param tickBalance Balance of the tick.\n */\n struct BinState {\n uint128 mergeBinBalance;\n uint128 tickBalance;\n uint128 totalSupply;\n uint8 kind;\n int32 tick;\n uint32 mergeId;\n }\n\n /**\n * @notice Parameters for swap.\n * @param amount Amount of the token that is either the input if exactOutput is false\n * or the output if exactOutput is true.\n * @param tokenAIn Boolean indicating whether tokenA is the input.\n * @param exactOutput Boolean indicating whether the amount specified is\n * the exact output amount (true).\n * @param tickLimit The furthest tick a swap will execute in. If no limit\n * is desired, value should be set to type(int32).max for a tokenAIn swap\n * and type(int32).min for a swap where tokenB is the input.\n */\n struct SwapParams {\n uint256 amount;\n bool tokenAIn;\n bool exactOutput;\n int32 tickLimit;\n }\n\n /**\n * @notice Parameters associated with adding liquidity.\n * @param kind One of the 4 kinds (0=static, 1=right, 2=left, 3=both).\n * @param ticks Array of ticks to add liquidity to.\n * @param amounts Array of bin LP amounts to add.\n */\n struct AddLiquidityParams {\n uint8 kind;\n int32[] ticks;\n uint128[] amounts;\n }\n\n /**\n * @notice Parameters for each bin that will have liquidity removed.\n * @param binIds Index array of the bins losing liquidity.\n * @param amounts Array of bin LP amounts to remove.\n */\n struct RemoveLiquidityParams {\n uint32[] binIds;\n uint128[] amounts;\n }\n\n /**\n * @notice State of the pool.\n * @param reserveA Pool tokenA balanceOf at end of last operation\n * @param reserveB Pool tokenB balanceOf at end of last operation\n * @param lastTwaD8 Value of log time weighted average price at last block.\n * Value is 8-decimal scale and is in the fractional tick domain. E.g. a\n * value of 12.3e8 indicates the TWAP was 3/10ths of the way into the 12th\n * tick.\n * @param lastLogPriceD8 Value of log price at last block. Value is\n * 8-decimal scale and is in the fractional tick domain. E.g. a value of\n * 12.3e8 indicates the price was 3/10ths of the way into the 12th tick.\n * @param lastTimestamp Last block.timestamp value in seconds for latest\n * swap transaction.\n * @param activeTick Current tick position that contains the active bins.\n * @param isLocked Pool isLocked, E.g., locked or unlocked; isLocked values\n * defined in Pool.sol.\n * @param binCounter Index of the last bin created.\n * @param protocolFeeRatioD3 Ratio of the swap fee that is kept for the\n * protocol.\n */\n struct State {\n uint128 reserveA;\n uint128 reserveB;\n int64 lastTwaD8;\n int64 lastLogPriceD8;\n uint40 lastTimestamp;\n int32 activeTick;\n bool isLocked;\n uint32 binCounter;\n uint8 protocolFeeRatioD3;\n }\n\n /**\n * @notice Internal data used for data passing between Pool and Bin code.\n */\n struct BinDelta {\n uint128 deltaA;\n uint128 deltaB;\n }\n\n /**\n * @notice 1-15 number to represent the active kinds.\n * @notice 0b0001 = static;\n * @notice 0b0010 = right;\n * @notice 0b0100 = left;\n * @notice 0b1000 = both;\n *\n * E.g. a pool with all 4 modes will have kinds = b1111 = 15\n */\n function kinds() external view returns (uint8 _kinds);\n\n /**\n * @notice Pool swap fee for the given direction (A-in or B-in swap) in\n * 18-decimal format. E.g. 0.01e18 is a 1% swap fee.\n */\n function fee(bool tokenAIn) external view returns (uint256);\n\n /**\n * @notice TickSpacing of pool where 1.0001^tickSpacing is the bin width.\n */\n function tickSpacing() external view returns (uint256);\n\n /**\n * @notice Lookback period of pool in seconds.\n */\n function lookback() external view returns (uint256);\n\n /**\n * @notice Address of Pool accessor. This is Zero address for\n * permissionless pools.\n */\n function accessor() external view returns (address);\n\n /**\n * @notice Pool tokenA. Address of tokenA is such that tokenA < tokenB.\n */\n function tokenA() external view returns (IERC20);\n\n /**\n * @notice Pool tokenB.\n */\n function tokenB() external view returns (IERC20);\n\n /**\n * @notice Deploying factory of the pool and also contract that has ability\n * to set and collect protocol fees for the pool.\n */\n function factory() external view returns (IMaverickV2Factory);\n\n /**\n * @notice Most significant bit of scale value is a flag to indicate whether\n * tokenA has more or less than 18 decimals. Scale is used in conjuction\n * with Math.toScale/Math.fromScale functions to convert from token amounts\n * to D18 scale internal pool accounting.\n */\n function tokenAScale() external view returns (uint256);\n\n /**\n * @notice Most significant bit of scale value is a flag to indicate whether\n * tokenA has more or less than 18 decimals. Scale is used in conjuction\n * with Math.toScale/Math.fromScale functions to convert from token amounts\n * to D18 scale internal pool accounting.\n */\n function tokenBScale() external view returns (uint256);\n\n /**\n * @notice ID of bin at input tick position and kind.\n */\n function binIdByTickKind(int32 tick, uint256 kind)\n external\n view\n returns (uint32);\n\n /**\n * @notice Accumulated tokenA protocol fee.\n */\n function protocolFeeA() external view returns (uint128);\n\n /**\n * @notice Accumulated tokenB protocol fee.\n */\n function protocolFeeB() external view returns (uint128);\n\n /**\n * @notice Lending fee rate on flash loans.\n */\n function lendingFeeRateD18() external view returns (uint256);\n\n /**\n * @notice External function to get the current time-weighted average price.\n */\n function getCurrentTwa() external view returns (int256);\n\n /**\n * @notice External function to get the state of the pool.\n */\n function getState() external view returns (State memory);\n\n /**\n * @notice Return state of Bin at input binId.\n */\n function getBin(uint32 binId) external view returns (BinState memory bin);\n\n /**\n * @notice Return state of Tick at input tick position.\n */\n function getTick(int32 tick)\n external\n view\n returns (TickState memory tickState);\n\n /**\n * @notice Retrieves the balance of a user within a bin.\n * @param user The user's address.\n * @param subaccount The subaccount for the user.\n * @param binId The ID of the bin.\n */\n function balanceOf(\n address user,\n uint256 subaccount,\n uint32 binId\n ) external view returns (uint128 lpToken);\n\n /**\n * @notice Add liquidity to a pool. This function allows users to deposit\n * tokens into a liquidity pool.\n * @dev This function will call `maverickV2AddLiquidityCallback` on the\n * calling contract to collect the tokenA/tokenB payment.\n * @param recipient The account that will receive credit for the added liquidity.\n * @param subaccount The account that will receive credit for the added liquidity.\n * @param params Parameters containing the details for adding liquidity,\n * such as token types and amounts.\n * @param data Bytes information that gets passed to the callback.\n * @return tokenAAmount The amount of token A added to the pool.\n * @return tokenBAmount The amount of token B added to the pool.\n * @return binIds An array of bin IDs where the liquidity is stored.\n */\n function addLiquidity(\n address recipient,\n uint256 subaccount,\n AddLiquidityParams calldata params,\n bytes calldata data\n )\n external\n returns (\n uint256 tokenAAmount,\n uint256 tokenBAmount,\n uint32[] memory binIds\n );\n\n /**\n * @notice Removes liquidity from the pool.\n * @dev Liquidy can only be removed from a bin that is either unmerged or\n * has a mergeId of an unmerged bin. If a bin is merged more than one\n * level deep, it must be migrated up the merge stack to the root bin\n * before liquidity removal.\n * @param recipient The address to receive the tokens.\n * @param subaccount The subaccount for the recipient.\n * @param params The parameters for removing liquidity.\n * @return tokenAOut The amount of token A received.\n * @return tokenBOut The amount of token B received.\n */\n function removeLiquidity(\n address recipient,\n uint256 subaccount,\n RemoveLiquidityParams calldata params\n ) external returns (uint256 tokenAOut, uint256 tokenBOut);\n\n /**\n * @notice Migrate bins up the linked list of merged bins so that its\n * mergeId is the currrent active bin.\n * @dev Liquidy can only be removed from a bin that is either unmerged or\n * has a mergeId of an unmerged bin. If a bin is merged more than one\n * level deep, it must be migrated up the merge stack to the root bin\n * before liquidity removal.\n * @param binId The ID of the bin to migrate.\n * @param maxRecursion The maximum recursion depth for the migration.\n */\n function migrateBinUpStack(uint32 binId, uint32 maxRecursion) external;\n\n /**\n * @notice Swap tokenA/tokenB assets in the pool. The swap user has two\n * options for funding their swap.\n * - The user can push the input token amount to the pool before calling\n * the swap function. In order to avoid having the pool call the callback,\n * the user should pass a zero-length `data` bytes object with the swap\n * call.\n * - The user can send the input token amount to the pool when the pool\n * calls the `maverickV2SwapCallback` function on the calling contract.\n * That callback has input parameters that specify the token address of the\n * input token, the input and output amounts, and the bytes data sent to\n * the swap function.\n * @dev If the users elects to do a callback-based swap, the output\n * assets will be sent before the callback is called, allowing the user to\n * execute flash swaps. However, the pool does have reentrancy protection,\n * so a swapper will not be able to interact with the same pool again\n * while they are in the callback function.\n * @param recipient The address to receive the output tokens.\n * @param params Parameters containing the details of the swap\n * @param data Bytes information that gets passed to the callback.\n */\n function swap(\n address recipient,\n SwapParams memory params,\n bytes calldata data\n ) external returns (uint256 amountIn, uint256 amountOut);\n\n /**\n * @notice Loan tokenA/tokenB assets from the pool to recipient. The fee\n * rate of a loan is determined by `lendingFeeRateD18`, which is set at the\n * protocol level by the factory. This function calls\n * `maverickV2FlashLoanCallback` on the calling contract. At the end of\n * the callback, the caller must pay back the loan with fee (if there is a\n * fee).\n * @param recipient The address to receive the loaned tokens.\n * @param amountB Loan amount of tokenA sent to recipient.\n * @param amountB Loan amount of tokenB sent to recipient.\n * @param data Bytes information that gets passed to the callback.\n */\n function flashLoan(\n address recipient,\n uint256 amountA,\n uint256 amountB,\n bytes calldata data\n ) external;\n\n /**\n * @notice Distributes accumulated protocol fee to factory protocolFeeReceiver\n */\n function distributeFees(bool isTokenA)\n external\n returns (uint256 protocolFee, IERC20 token);\n}\n" + }, + "contracts/interfaces/plume/IMaverickV2PoolLens.sol": { + "content": "// SPDX-License-Identifier: GPL-2.0-or-later\npragma solidity ^0.8.25;\n\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport { IMaverickV2Factory } from \"./IMaverickV2Factory.sol\";\nimport { IMaverickV2Pool } from \"./IMaverickV2Pool.sol\";\n\ninterface IMaverickV2PoolLens {\n error LensTargetPriceOutOfBounds(\n uint256 targetSqrtPrice,\n uint256 sqrtLowerTickPrice,\n uint256 sqrtUpperTickPrice\n );\n error LensTooLittleLiquidity(\n uint256 relativeLiquidityAmount,\n uint256 deltaA,\n uint256 deltaB\n );\n error LensTargetingTokenWithNoDelta(\n bool targetIsA,\n uint256 deltaA,\n uint256 deltaB\n );\n\n /**\n * @notice Add liquidity slippage parameters for a distribution of liquidity.\n * @param pool Pool where liquidity is being added.\n * @param kind Bin kind; all bins must have the same kind in a given call\n * to addLiquidity.\n * @param ticks Array of tick values to add liquidity to.\n * @param relativeLiquidityAmounts Relative liquidity amounts for the\n * specified ticks. Liquidity in this case is not bin LP balance, it is\n * the bin liquidity as defined by liquidity = deltaA / (sqrt(upper) -\n * sqrt(lower)) or deltaB = liquidity / sqrt(lower) - liquidity /\n * sqrt(upper).\n * @param addSpec Slippage specification.\n */\n struct AddParamsViewInputs {\n IMaverickV2Pool pool;\n uint8 kind;\n int32[] ticks;\n uint128[] relativeLiquidityAmounts;\n AddParamsSpecification addSpec;\n }\n\n /**\n * @notice Multi-price add param specification.\n * @param slippageFactorD18 Max slippage allowed as a percent in D18 scale. e.g. 1% slippage is 0.01e18\n * @param numberOfPriceBreaksPerSide Number of price break values on either\n * side of current price.\n * @param targetAmount Target token contribution amount in tokenA if\n * targetIsA is true, otherwise this is the target amount for tokenB.\n * @param targetIsA Indicates if the target amount is for tokenA or tokenB\n */\n struct AddParamsSpecification {\n uint256 slippageFactorD18;\n uint256 numberOfPriceBreaksPerSide;\n uint256 targetAmount;\n bool targetIsA;\n }\n\n /**\n * @notice Specification for deriving create pool parameters. Creating a\n * pool in the liquidity manager has several steps:\n *\n * - Deploy pool\n * - Donate a small amount of initial liquidity in the activeTick\n * - Execute a small swap to set the pool price to the desired value\n * - Add liquidity\n *\n * In order to execute these steps, the caller must specify the parameters\n * of each step. The PoolLens has helper function to derive the values\n * used by the LiquidityManager, but this struct is the input to that\n * helper function and represents the core intent of the pool creator.\n *\n * @param fee Fraction of the pool swap amount that is retained as an LP in\n * D18 scale.\n * @param tickSpacing Tick spacing of pool where 1.0001^tickSpacing is the\n * bin width.\n * @param lookback Pool lookback in seconds.\n * @param tokenA Address of tokenA.\n * @param tokenB Address of tokenB.\n * @param activeTick Tick position that contains the active bins.\n * @param kinds 1-15 number to represent the active kinds\n * 0b0001 = static;\n * 0b0010 = right;\n * 0b0100 = left;\n * 0b1000 = both.\n * e.g. a pool with all 4 modes will have kinds = b1111 = 15\n * @param initialTargetB Amount of B to be donated to the pool after pool\n * create. This amount needs to be big enough to meet the minimum bin\n * liquidity.\n * @param sqrtPrice Target sqrt price of the pool.\n * @param kind Bin kind; all bins must have the same kind in a given call\n * to addLiquidity.\n * @param ticks Array of tick values to add liquidity to.\n * @param relativeLiquidityAmounts Relative liquidity amounts for the\n * specified ticks. Liquidity in this case is not bin LP balance, it is\n * the bin liquidity as defined by liquidity = deltaA / (sqrt(upper) -\n * sqrt(lower)) or deltaB = liquidity / sqrt(lower) - liquidity /\n * sqrt(upper).\n * @param targetAmount Target token contribution amount in tokenA if\n * targetIsA is true, otherwise this is the target amount for tokenB.\n * @param targetIsA Indicates if the target amount is for tokenA or tokenB\n */\n struct CreateAndAddParamsViewInputs {\n uint64 feeAIn;\n uint64 feeBIn;\n uint16 tickSpacing;\n uint32 lookback;\n IERC20 tokenA;\n IERC20 tokenB;\n int32 activeTick;\n uint8 kinds;\n // donate params\n uint256 initialTargetB;\n uint256 sqrtPrice;\n // add target\n uint8 kind;\n int32[] ticks;\n uint128[] relativeLiquidityAmounts;\n uint256 targetAmount;\n bool targetIsA;\n }\n\n struct Output {\n uint256 deltaAOut;\n uint256 deltaBOut;\n uint256[] deltaAs;\n uint256[] deltaBs;\n uint128[] deltaLpBalances;\n }\n\n struct Reserves {\n uint256 amountA;\n uint256 amountB;\n }\n\n struct BinPositionKinds {\n uint128[4] values;\n }\n\n struct PoolState {\n IMaverickV2Pool.TickState[] tickStateMapping;\n IMaverickV2Pool.BinState[] binStateMapping;\n BinPositionKinds[] binIdByTickKindMapping;\n IMaverickV2Pool.State state;\n Reserves protocolFees;\n }\n\n struct BoostedPositionSpecification {\n IMaverickV2Pool pool;\n uint32[] binIds;\n uint128[] ratios;\n uint8 kind;\n }\n\n struct CreateAndAddParamsInputs {\n uint64 feeAIn;\n uint64 feeBIn;\n uint16 tickSpacing;\n uint32 lookback;\n IERC20 tokenA;\n IERC20 tokenB;\n int32 activeTick;\n uint8 kinds;\n // donate params\n IMaverickV2Pool.AddLiquidityParams donateParams;\n // swap params\n uint256 swapAmount;\n // add params\n IMaverickV2Pool.AddLiquidityParams addParams;\n bytes[] packedAddParams;\n uint256 deltaAOut;\n uint256 deltaBOut;\n uint256 preAddReserveA;\n uint256 preAddReserveB;\n }\n\n struct TickDeltas {\n uint256 deltaAOut;\n uint256 deltaBOut;\n uint256[] deltaAs;\n uint256[] deltaBs;\n }\n\n /**\n * @notice Converts add parameter slippage specification into add\n * parameters. The return values are given in both raw format and as packed\n * values that can be used in the LiquidityManager contract.\n */\n function getAddLiquidityParams(AddParamsViewInputs memory params)\n external\n view\n returns (\n bytes memory packedSqrtPriceBreaks,\n bytes[] memory packedArgs,\n uint88[] memory sqrtPriceBreaks,\n IMaverickV2Pool.AddLiquidityParams[] memory addParams,\n IMaverickV2PoolLens.TickDeltas[] memory tickDeltas\n );\n\n /**\n * @notice Converts add parameter slippage specification and new pool\n * specification into CreateAndAddParamsInputs parameters that can be used in the\n * LiquidityManager contract.\n */\n function getCreatePoolAtPriceAndAddLiquidityParams(\n CreateAndAddParamsViewInputs memory params\n ) external view returns (CreateAndAddParamsInputs memory output);\n\n /**\n * @notice View function that provides information about pool ticks within\n * a tick radius from the activeTick. Ticks with no reserves are not\n * included in part o f the return array.\n */\n function getTicksAroundActive(IMaverickV2Pool pool, int32 tickRadius)\n external\n view\n returns (\n int32[] memory ticks,\n IMaverickV2Pool.TickState[] memory tickStates\n );\n\n /**\n * @notice View function that provides information about pool ticks within\n * a range. Ticks with no reserves are not included in part o f the return\n * array.\n */\n function getTicks(\n IMaverickV2Pool pool,\n int32 tickStart,\n int32 tickEnd\n )\n external\n view\n returns (\n int32[] memory ticks,\n IMaverickV2Pool.TickState[] memory tickStates\n );\n\n /**\n * @notice View function that provides information about pool ticks within\n * a range. Information returned includes all pool state needed to emulate\n * a swap off chain. Ticks with no reserves are not included in part o f\n * the return array.\n */\n function getTicksAroundActiveWLiquidity(\n IMaverickV2Pool pool,\n int32 tickRadius\n )\n external\n view\n returns (\n int32[] memory ticks,\n IMaverickV2Pool.TickState[] memory tickStates,\n uint256[] memory liquidities,\n uint256[] memory sqrtLowerTickPrices,\n uint256[] memory sqrtUpperTickPrices,\n IMaverickV2Pool.State memory poolState,\n uint256 sqrtPrice,\n uint256 feeAIn,\n uint256 feeBIn\n );\n\n /**\n * @notice View function that provides pool state information.\n */\n function getFullPoolState(\n IMaverickV2Pool pool,\n uint32 binStart,\n uint32 binEnd\n ) external view returns (PoolState memory poolState);\n\n /**\n * @notice View function that provides price and liquidity of a given tick.\n */\n function getTickSqrtPriceAndL(IMaverickV2Pool pool, int32 tick)\n external\n view\n returns (uint256 sqrtPrice, uint256 liquidity);\n\n /**\n * @notice Pool sqrt price.\n */\n function getPoolSqrtPrice(IMaverickV2Pool pool)\n external\n view\n returns (uint256 sqrtPrice);\n\n /**\n * @notice Pool price.\n */\n function getPoolPrice(IMaverickV2Pool pool)\n external\n view\n returns (uint256 price);\n\n /**\n * @notice Token scale of two tokens in a pool.\n */\n function tokenScales(IMaverickV2Pool pool)\n external\n view\n returns (uint256 tokenAScale, uint256 tokenBScale);\n}\n" + }, + "contracts/interfaces/plume/IMaverickV2Position.sol": { + "content": "// SPDX-License-Identifier: GPL-2.0-or-later\npragma solidity ^0.8.25;\n\nimport { IMaverickV2Factory } from \"./IMaverickV2Factory.sol\";\nimport { IMaverickV2Pool } from \"./IMaverickV2Pool.sol\";\nimport { ILiquidityRegistry } from \"./ILiquidityRegistry.sol\";\n\ninterface IMaverickV2Position {\n event PositionClearData(uint256 indexed tokenId);\n event PositionSetData(\n uint256 indexed tokenId,\n uint256 index,\n PositionPoolBinIds newData\n );\n event SetLpReward(ILiquidityRegistry lpReward);\n\n error PositionDuplicatePool(uint256 index, IMaverickV2Pool pool);\n error PositionNotFactoryPool();\n error PositionPermissionedLiquidityPool();\n\n struct PositionPoolBinIds {\n IMaverickV2Pool pool;\n uint32[] binIds;\n }\n\n struct PositionFullInformation {\n PositionPoolBinIds poolBinIds;\n uint256 amountA;\n uint256 amountB;\n uint256[] binAAmounts;\n uint256[] binBAmounts;\n int32[] ticks;\n uint256[] liquidities;\n }\n\n /**\n * @notice Factory that tracks lp rewards.\n */\n function lpReward() external view returns (ILiquidityRegistry);\n\n /**\n * @notice Pool factory.\n */\n function factory() external view returns (IMaverickV2Factory);\n\n /**\n * @notice Mint NFT that holds liquidity in a Maverick V2 Pool. To mint\n * liquidity to an NFT, add liquidity to bins in a pool where the\n * add liquidity recipient is this contract and the subaccount is the\n * tokenId. LiquidityManager can be used to simplify minting Position NFTs.\n */\n function mint(\n address recipient,\n IMaverickV2Pool pool,\n uint32[] memory binIds\n ) external returns (uint256 tokenId);\n\n /**\n * @notice Overwrites tokenId pool/binId information for a given data index.\n */\n function setTokenIdData(\n uint256 tokenId,\n uint256 index,\n IMaverickV2Pool pool,\n uint32[] memory binIds\n ) external;\n\n /**\n * @notice Overwrites entire pool/binId data set for a given tokenId.\n */\n function setTokenIdData(uint256 tokenId, PositionPoolBinIds[] memory data)\n external;\n\n /**\n * @notice Append new pool/binIds data array to tokenId.\n */\n function appendTokenIdData(\n uint256 tokenId,\n IMaverickV2Pool pool,\n uint32[] memory binIds\n ) external;\n\n /**\n * @notice Get array pool/binIds data for a given tokenId.\n */\n function getTokenIdData(uint256 tokenId)\n external\n view\n returns (PositionPoolBinIds[] memory);\n\n /**\n * @notice Get value from array of pool/binIds data for a given tokenId.\n */\n function getTokenIdData(uint256 tokenId, uint256 index)\n external\n view\n returns (PositionPoolBinIds memory);\n\n /**\n * @notice Length of array of pool/binIds data for a given tokenId.\n */\n function tokenIdDataLength(uint256 tokenId)\n external\n view\n returns (uint256 length);\n\n /**\n * @notice Remove liquidity from tokenId for a given pool. User can\n * specify arbitrary bins to remove from for their subaccount in the pool\n * even if those bins are not in the tokenIdData set.\n */\n function removeLiquidity(\n uint256 tokenId,\n address recipient,\n IMaverickV2Pool pool,\n IMaverickV2Pool.RemoveLiquidityParams memory params\n ) external returns (uint256 tokenAAmount, uint256 tokenBAmount);\n\n /**\n * @notice Remove liquidity from tokenId for a given pool to sender. User\n * can specify arbitrary bins to remove from for their subaccount in the\n * pool even if those bins are not in the tokenIdData set.\n */\n function removeLiquidityToSender(\n uint256 tokenId,\n IMaverickV2Pool pool,\n IMaverickV2Pool.RemoveLiquidityParams memory params\n ) external returns (uint256 tokenAAmount, uint256 tokenBAmount);\n\n /**\n * @notice NFT asset information for a given range of pool/binIds indexes.\n * This function only returns the liquidity in the pools/binIds stored as\n * part of the tokenIdData, but it is possible that the NFT has additional\n * liquidity in pools/binIds that have not been recorded.\n */\n function tokenIdPositionInformation(\n uint256 tokenId,\n uint256 startIndex,\n uint256 stopIndex\n ) external view returns (PositionFullInformation[] memory output);\n\n /**\n * @notice NFT asset information for a given pool/binIds index. This\n * function only returns the liquidity in the pools/binIds stored as part\n * of the tokenIdData, but it is possible that the NFT has additional\n * liquidity in pools/binIds that have not been recorded.\n */\n function tokenIdPositionInformation(uint256 tokenId, uint256 index)\n external\n view\n returns (PositionFullInformation memory output);\n\n /**\n * @notice Get remove parameters for removing a fractional part of the\n * liquidity owned by a given tokenId. The fractional factor to remove is\n * given by proporationD18 in 18-decimal scale.\n */\n function getRemoveParams(\n uint256 tokenId,\n uint256 index,\n uint256 proportionD18\n )\n external\n view\n returns (IMaverickV2Pool.RemoveLiquidityParams memory params);\n\n /**\n * @notice Register the bin balances in the nft with the LpReward contract.\n */\n function checkpointBinLpBalance(\n uint256 tokenId,\n IMaverickV2Pool pool,\n uint32[] memory binIds\n ) external;\n}\n" + }, + "contracts/interfaces/plume/IMaverickV2Quoter.sol": { + "content": "// SPDX-License-Identifier: GPL-2.0-or-later\npragma solidity ^0.8.25;\n\nimport { IMaverickV2Pool } from \"./IMaverickV2Pool.sol\";\n\ninterface IMaverickV2Quoter {\n error QuoterInvalidSwap();\n error QuoterInvalidAddLiquidity();\n\n /**\n * @notice Calculates a swap on a MaverickV2Pool and returns the resulting\n * amount and estimated gas. The gas estimate is only a rough estimate and\n * may not match a swap's gas.\n * @param pool The MaverickV2Pool to swap on.\n * @param amount The input amount.\n * @param tokenAIn Indicates if token A is the input token.\n * @param exactOutput Indicates if the amount is the output amount (true)\n * or input amount (false). If the tickLimit is reached, the full value of\n * the exactOutput may not be returned because the pool will stop swapping\n * before the whole order is filled.\n * @param tickLimit The tick limit for the swap. Once the swap lands in\n * this tick, it will stop and return the output amount swapped up to that\n * tick.\n */\n function calculateSwap(\n IMaverickV2Pool pool,\n uint128 amount,\n bool tokenAIn,\n bool exactOutput,\n int32 tickLimit\n )\n external\n returns (\n uint256 amountIn,\n uint256 amountOut,\n uint256 gasEstimate\n );\n\n /**\n * @notice Calculates a multihop swap and returns the resulting amount and\n * estimated gas. The gas estimate is only a rough estimate and\n * may not match a swap's gas.\n * @param path The path of pools to swap through. Path is given by an\n * packed array of (pool, tokenAIn) tuples. So each step in the path is 160\n * + 8 = 168 bits of data. e.g. path = abi.encodePacked(pool1, true, pool2, false);\n * @param amount The input amount.\n * @param exactOutput A boolean indicating if exact output is required.\n */\n function calculateMultiHopSwap(\n bytes memory path,\n uint256 amount,\n bool exactOutput\n ) external returns (uint256 returnAmount, uint256 gasEstimate);\n\n /**\n * @notice Computes the token amounts required for a given set of\n * addLiquidity parameters. The gas estimate is only a rough estimate and\n * may not match a add's gas.\n */\n function calculateAddLiquidity(\n IMaverickV2Pool pool,\n IMaverickV2Pool.AddLiquidityParams calldata params\n )\n external\n returns (\n uint256 amountA,\n uint256 amountB,\n uint256 gasEstimate\n );\n\n /**\n * @notice Pool's sqrt price.\n */\n function poolSqrtPrice(IMaverickV2Pool pool)\n external\n view\n returns (uint256 sqrtPrice);\n}\n" + }, + "contracts/interfaces/plume/IPoolDistributor.sol": { + "content": "// SPDX-License-Identifier: GPL-2.0-or-later\npragma solidity ^0.8.0;\n\nimport { IMaverickV2Pool } from \"./IMaverickV2Pool.sol\";\n\ninterface IPoolDistributor {\n function rewardToken() external view returns (address);\n\n function claimLp(\n address recipient,\n uint256 tokenId,\n IMaverickV2Pool pool,\n uint32[] memory binIds,\n uint256 epoch\n ) external returns (uint256 amount);\n}\n" + }, + "contracts/interfaces/plume/IVotingDistributor.sol": { + "content": "// SPDX-License-Identifier: GPL-2.0-or-later\npragma solidity ^0.8.0;\n\ninterface IVotingDistributor {\n function lastEpoch() external view returns (uint256);\n}\n" + }, + "contracts/interfaces/poolBooster/IMerklDistributor.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ninterface IMerklDistributor {\n struct CampaignParameters {\n // POPULATED ONCE CREATED\n\n // ID of the campaign. This can be left as a null bytes32 when creating campaigns\n // on Merkl.\n bytes32 campaignId;\n // CHOSEN BY CAMPAIGN CREATOR\n\n // Address of the campaign creator, if marked as address(0), it will be overriden with the\n // address of the `msg.sender` creating the campaign\n address creator;\n // Address of the token used as a reward\n address rewardToken;\n // Amount of `rewardToken` to distribute across all the epochs\n // Amount distributed per epoch is `amount/numEpoch`\n uint256 amount;\n // Type of campaign\n uint32 campaignType;\n // Timestamp at which the campaign should start\n uint32 startTimestamp;\n // Duration of the campaign in seconds. Has to be a multiple of EPOCH = 3600\n uint32 duration;\n // Extra data to pass to specify the campaign\n bytes campaignData;\n }\n\n function createCampaign(CampaignParameters memory newCampaign)\n external\n returns (bytes32);\n\n function signAndCreateCampaign(\n CampaignParameters memory newCampaign,\n bytes memory _signature\n ) external returns (bytes32);\n\n function sign(bytes memory _signature) external;\n\n function rewardTokenMinAmounts(address _rewardToken)\n external\n view\n returns (uint256);\n}\n" + }, + "contracts/interfaces/poolBooster/IPoolBoostCentralRegistry.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\ninterface IPoolBoostCentralRegistry {\n /**\n * @dev all the supported pool booster types are listed here. It is possible\n * to have multiple versions of the factory that supports the same type of\n * pool booster. Factories are immutable and this can happen when a factory\n * or related pool booster required code update.\n * e.g. \"PoolBoosterSwapxDouble\" & \"PoolBoosterSwapxDouble_v2\"\n */\n enum PoolBoosterType {\n // Supports bribing 2 contracts per pool. Appropriate for Ichi vault concentrated\n // liquidity pools where (which is expected in most/all cases) both pool gauges\n // require bribing.\n SwapXDoubleBooster,\n // Supports bribing a single contract per pool. Appropriate for Classic Stable &\n // Classic Volatile pools and Ichi vaults where only 1 side (1 of the 2 gauges)\n // needs bribing\n SwapXSingleBooster,\n // Supports bribing a single contract per pool. Appropriate for Metropolis pools\n MetropolisBooster,\n // Supports creating a Merkl campaign.\n MerklBooster,\n // Supports creating a plain Curve pool booster\n CurvePoolBoosterPlain\n }\n\n struct PoolBoosterEntry {\n address boosterAddress;\n address ammPoolAddress;\n PoolBoosterType boosterType;\n }\n\n event PoolBoosterCreated(\n address poolBoosterAddress,\n address ammPoolAddress,\n PoolBoosterType poolBoosterType,\n address factoryAddress\n );\n event PoolBoosterRemoved(address poolBoosterAddress);\n\n function emitPoolBoosterCreated(\n address _poolBoosterAddress,\n address _ammPoolAddress,\n PoolBoosterType _boosterType\n ) external;\n\n function emitPoolBoosterRemoved(address _poolBoosterAddress) external;\n}\n" + }, + "contracts/interfaces/poolBooster/IPoolBooster.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\ninterface IPoolBooster {\n event BribeExecuted(uint256 amount);\n\n /// @notice Execute the bribe action\n function bribe() external;\n}\n" + }, + "contracts/interfaces/poolBooster/ISwapXAlgebraBribe.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ninterface IBribe {\n /// @notice Notify a bribe amount\n /// @dev Rewards are saved into NEXT EPOCH mapping.\n function notifyRewardAmount(address _rewardsToken, uint256 reward) external;\n}\n" + }, + "contracts/interfaces/sonic/ISFC.sol": { + "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.8.0;\n\n/**\n * @title Special Fee Contract for Sonic network\n * @notice The SFC maintains a list of validators and delegators and distributes rewards to them.\n * @custom:security-contact security@fantom.foundation\n */\ninterface ISFC {\n error StakeIsFullySlashed();\n\n event CreatedValidator(\n uint256 indexed validatorID,\n address indexed auth,\n uint256 createdEpoch,\n uint256 createdTime\n );\n event Delegated(\n address indexed delegator,\n uint256 indexed validatorID,\n uint256 amount\n );\n event Undelegated(\n address indexed delegator,\n uint256 indexed validatorID,\n uint256 indexed wrID,\n uint256 amount\n );\n event Withdrawn(\n address indexed delegator,\n uint256 indexed validatorID,\n uint256 indexed wrID,\n uint256 amount,\n uint256 penalty\n );\n event ClaimedRewards(\n address indexed delegator,\n uint256 indexed validatorID,\n uint256 rewards\n );\n event RestakedRewards(\n address indexed delegator,\n uint256 indexed validatorID,\n uint256 rewards\n );\n event BurntFTM(uint256 amount);\n event UpdatedSlashingRefundRatio(\n uint256 indexed validatorID,\n uint256 refundRatio\n );\n event RefundedSlashedLegacyDelegation(\n address indexed delegator,\n uint256 indexed validatorID,\n uint256 amount\n );\n\n event DeactivatedValidator(\n uint256 indexed validatorID,\n uint256 deactivatedEpoch,\n uint256 deactivatedTime\n );\n event ChangedValidatorStatus(uint256 indexed validatorID, uint256 status);\n event AnnouncedRedirection(address indexed from, address indexed to);\n\n function currentSealedEpoch() external view returns (uint256);\n\n function getEpochSnapshot(uint256 epoch)\n external\n view\n returns (\n uint256 endTime,\n uint256 endBlock,\n uint256 epochFee,\n uint256 baseRewardPerSecond,\n uint256 totalStake,\n uint256 totalSupply\n );\n\n function getStake(address delegator, uint256 validatorID)\n external\n view\n returns (uint256);\n\n function getValidator(uint256 validatorID)\n external\n view\n returns (\n uint256 status,\n uint256 receivedStake,\n address auth,\n uint256 createdEpoch,\n uint256 createdTime,\n uint256 deactivatedTime,\n uint256 deactivatedEpoch\n );\n\n function getValidatorID(address auth) external view returns (uint256);\n\n function getValidatorPubkey(uint256 validatorID)\n external\n view\n returns (bytes memory);\n\n function pubkeyAddressvalidatorID(address pubkeyAddress)\n external\n view\n returns (uint256);\n\n function getWithdrawalRequest(\n address delegator,\n uint256 validatorID,\n uint256 wrID\n )\n external\n view\n returns (\n uint256 epoch,\n uint256 time,\n uint256 amount\n );\n\n function isOwner() external view returns (bool);\n\n function lastValidatorID() external view returns (uint256);\n\n function minGasPrice() external view returns (uint256);\n\n function owner() external view returns (address);\n\n function renounceOwnership() external;\n\n function slashingRefundRatio(uint256 validatorID)\n external\n view\n returns (uint256);\n\n function stashedRewardsUntilEpoch(address delegator, uint256 validatorID)\n external\n view\n returns (uint256);\n\n function totalActiveStake() external view returns (uint256);\n\n function totalStake() external view returns (uint256);\n\n function totalSupply() external view returns (uint256);\n\n function transferOwnership(address newOwner) external;\n\n function treasuryAddress() external view returns (address);\n\n function version() external pure returns (bytes3);\n\n function currentEpoch() external view returns (uint256);\n\n function updateConstsAddress(address v) external;\n\n function constsAddress() external view returns (address);\n\n function getEpochValidatorIDs(uint256 epoch)\n external\n view\n returns (uint256[] memory);\n\n function getEpochReceivedStake(uint256 epoch, uint256 validatorID)\n external\n view\n returns (uint256);\n\n function getEpochAccumulatedRewardPerToken(\n uint256 epoch,\n uint256 validatorID\n ) external view returns (uint256);\n\n function getEpochAccumulatedUptime(uint256 epoch, uint256 validatorID)\n external\n view\n returns (uint256);\n\n function getEpochAverageUptime(uint256 epoch, uint256 validatorID)\n external\n view\n returns (uint32);\n\n function getEpochAccumulatedOriginatedTxsFee(\n uint256 epoch,\n uint256 validatorID\n ) external view returns (uint256);\n\n function getEpochOfflineTime(uint256 epoch, uint256 validatorID)\n external\n view\n returns (uint256);\n\n function getEpochOfflineBlocks(uint256 epoch, uint256 validatorID)\n external\n view\n returns (uint256);\n\n function getEpochEndBlock(uint256 epoch) external view returns (uint256);\n\n function rewardsStash(address delegator, uint256 validatorID)\n external\n view\n returns (uint256);\n\n function createValidator(bytes calldata pubkey) external payable;\n\n function getSelfStake(uint256 validatorID) external view returns (uint256);\n\n function delegate(uint256 validatorID) external payable;\n\n function undelegate(\n uint256 validatorID,\n uint256 wrID,\n uint256 amount\n ) external;\n\n function isSlashed(uint256 validatorID) external view returns (bool);\n\n function withdraw(uint256 validatorID, uint256 wrID) external;\n\n function deactivateValidator(uint256 validatorID, uint256 status) external;\n\n function pendingRewards(address delegator, uint256 validatorID)\n external\n view\n returns (uint256);\n\n function stashRewards(address delegator, uint256 validatorID) external;\n\n function claimRewards(uint256 validatorID) external;\n\n function restakeRewards(uint256 validatorID) external;\n\n function updateSlashingRefundRatio(uint256 validatorID, uint256 refundRatio)\n external;\n\n function updateTreasuryAddress(address v) external;\n\n function burnFTM(uint256 amount) external;\n\n function sealEpoch(\n uint256[] calldata offlineTime,\n uint256[] calldata offlineBlocks,\n uint256[] calldata uptimes,\n uint256[] calldata originatedTxsFee\n ) external;\n\n function sealEpochValidators(uint256[] calldata nextValidatorIDs) external;\n\n function initialize(\n uint256 sealedEpoch,\n uint256 _totalSupply,\n address nodeDriver,\n address consts,\n address _owner\n ) external;\n\n function setGenesisValidator(\n address auth,\n uint256 validatorID,\n bytes calldata pubkey,\n uint256 createdTime\n ) external;\n\n function setGenesisDelegation(\n address delegator,\n uint256 validatorID,\n uint256 stake\n ) external;\n\n function updateStakeSubscriberAddress(address v) external;\n\n function stakeSubscriberAddress() external view returns (address);\n\n function setRedirectionAuthorizer(address v) external;\n\n function announceRedirection(address to) external;\n\n function initiateRedirection(address from, address to) external;\n\n function redirect(address to) external;\n}\n" + }, + "contracts/interfaces/sonic/ISwapXGauge.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ninterface IGauge {\n function owner() external view returns (address);\n\n function TOKEN() external view returns (address);\n\n function DISTRIBUTION() external view returns (address);\n\n function balanceOf(address account) external view returns (uint256);\n\n function claimFees() external returns (uint256 claimed0, uint256 claimed1);\n\n function deposit(uint256 amount) external;\n\n function depositAll() external;\n\n function earned(address account) external view returns (uint256);\n\n function getReward() external;\n\n function getReward(address _user) external;\n\n function isForPair() external view returns (bool);\n\n function lastTimeRewardApplicable() external view returns (uint256);\n\n function lastUpdateTime() external view returns (uint256);\n\n function notifyRewardAmount(address token, uint256 reward) external;\n\n function periodFinish() external view returns (uint256);\n\n function rewardForDuration() external view returns (uint256);\n\n function rewardPerToken() external view returns (uint256);\n\n function rewardPerTokenStored() external view returns (uint256);\n\n function rewardRate() external view returns (uint256);\n\n function rewardToken() external view returns (address);\n\n function rewards(address) external view returns (uint256);\n\n function totalSupply() external view returns (uint256);\n\n function userRewardPerTokenPaid(address) external view returns (uint256);\n\n function withdraw(uint256 amount) external;\n\n function withdrawAll() external;\n\n function withdrawAllAndHarvest() external;\n\n function withdrawExcess(address token, uint256 amount) external;\n\n function emergency() external returns (bool);\n\n function emergencyWithdraw() external;\n\n function activateEmergencyMode() external;\n\n function stopEmergencyMode() external;\n}\n" + }, + "contracts/interfaces/sonic/ISwapXPair.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ninterface IPair {\n event Approval(address indexed src, address indexed guy, uint256 wad);\n event Transfer(address indexed src, address indexed dst, uint256 wad);\n\n event Mint(address indexed sender, uint256 amount0, uint256 amount1);\n event Burn(\n address indexed sender,\n uint256 amount0,\n uint256 amount1,\n address indexed to\n );\n event Swap(\n address indexed sender,\n uint256 amount0In,\n uint256 amount1In,\n uint256 amount0Out,\n uint256 amount1Out,\n address indexed to\n );\n event Claim(\n address indexed sender,\n address indexed recipient,\n uint256 amount0,\n uint256 amount1\n );\n\n function metadata()\n external\n view\n returns (\n uint256 dec0,\n uint256 dec1,\n uint256 r0,\n uint256 r1,\n bool st,\n address t0,\n address t1\n );\n\n function claimFees() external returns (uint256, uint256);\n\n function tokens() external view returns (address, address);\n\n function token0() external view returns (address);\n\n function token1() external view returns (address);\n\n function permit(\n address owner,\n address spender,\n uint256 value,\n uint256 deadline,\n uint8 v,\n bytes32 r,\n bytes32 s\n ) external;\n\n function swap(\n uint256 amount0Out,\n uint256 amount1Out,\n address to,\n bytes calldata data\n ) external;\n\n function burn(address to)\n external\n returns (uint256 amount0, uint256 amount1);\n\n function mint(address to) external returns (uint256 liquidity);\n\n function getReserves()\n external\n view\n returns (\n uint256 _reserve0,\n uint256 _reserve1,\n uint256 _blockTimestampLast\n );\n\n function getAmountOut(uint256, address) external view returns (uint256);\n\n // ERC20 methods\n function name() external view returns (string memory);\n\n function symbol() external view returns (string memory);\n\n function decimals() external view returns (uint8);\n\n function totalSupply() external view returns (uint256);\n\n function balanceOf(address) external view returns (uint256);\n\n function transfer(address recipient, uint256 amount)\n external\n returns (bool);\n\n function transferFrom(\n address sender,\n address recipient,\n uint256 amount\n ) external returns (bool);\n\n function allowance(address owner, address spender)\n external\n view\n returns (uint256);\n\n function approve(address spender, uint256 value) external returns (bool);\n\n function claimable0(address _user) external view returns (uint256);\n\n function claimable1(address _user) external view returns (uint256);\n\n function isStable() external view returns (bool);\n\n function skim(address to) external;\n\n function sync() external;\n}\n" + }, + "contracts/interfaces/sonic/IWrappedSonic.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ninterface IWrappedSonic {\n event Deposit(address indexed account, uint256 value);\n event Withdrawal(address indexed account, uint256 value);\n event Transfer(address indexed from, address indexed to, uint256 value);\n event Approval(\n address indexed owner,\n address indexed spender,\n uint256 value\n );\n\n function allowance(address owner, address spender)\n external\n view\n returns (uint256);\n\n function approve(address spender, uint256 value) external returns (bool);\n\n function balanceOf(address account) external view returns (uint256);\n\n function decimals() external view returns (uint8);\n\n function deposit() external payable;\n\n function depositFor(address account) external payable returns (bool);\n\n function totalSupply() external view returns (uint256);\n\n function transfer(address to, uint256 value) external returns (bool);\n\n function transferFrom(\n address from,\n address to,\n uint256 value\n ) external returns (bool);\n\n function withdraw(uint256 value) external;\n\n function withdrawTo(address account, uint256 value) external returns (bool);\n}\n" + }, + "contracts/interfaces/uniswap/IUniswapV2Router02.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ninterface IUniswapV2Router {\n function WETH() external pure returns (address);\n\n function swapExactTokensForTokens(\n uint256 amountIn,\n uint256 amountOutMin,\n address[] calldata path,\n address to,\n uint256 deadline\n ) external returns (uint256[] memory amounts);\n\n function addLiquidity(\n address tokenA,\n address tokenB,\n uint256 amountADesired,\n uint256 amountBDesired,\n uint256 amountAMin,\n uint256 amountBMin,\n address to,\n uint256 deadline\n )\n external\n returns (\n uint256 amountA,\n uint256 amountB,\n uint256 liquidity\n );\n}\n" + }, + "contracts/interfaces/uniswap/IUniswapV3Router.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\n// -- Solididy v0.5.x compatible interface\ninterface IUniswapV3Router {\n struct ExactInputParams {\n bytes path;\n address recipient;\n uint256 deadline;\n uint256 amountIn;\n uint256 amountOutMinimum;\n }\n\n /// @notice Swaps `amountIn` of one token for as much as possible of another along the specified path\n /// @param params The parameters necessary for the multi-hop swap, encoded as `ExactInputParams` in calldata\n /// @return amountOut The amount of the received token\n function exactInput(ExactInputParams calldata params)\n external\n payable\n returns (uint256 amountOut);\n}\n" + }, + "contracts/mocks/crosschain/CCTPMessageTransmitterMock.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { ICCTPMessageTransmitter } from \"../../interfaces/cctp/ICCTP.sol\";\nimport { IERC20 } from \"../../utils/InitializableAbstractStrategy.sol\";\nimport { BytesHelper } from \"../../utils/BytesHelper.sol\";\nimport { AbstractCCTPIntegrator } from \"../../strategies/crosschain/AbstractCCTPIntegrator.sol\";\n\n/**\n * @title Mock conctract simulating the functionality of the CCTPTokenMessenger contract\n * for the porposes of unit testing.\n * @author Origin Protocol Inc\n */\n\ncontract CCTPMessageTransmitterMock is ICCTPMessageTransmitter {\n using BytesHelper for bytes;\n\n IERC20 public usdc;\n uint256 public nonce = 0;\n // Sender index in the burn message v2\n // Ref: https://github.com/circlefin/evm-cctp-contracts/blob/master/src/messages/v2/BurnMessageV2.sol\n uint8 constant BURN_MESSAGE_V2_MESSAGE_SENDER_INDEX = 100;\n uint8 constant BURN_MESSAGE_V2_HOOK_DATA_INDEX = 228;\n\n uint16 public messageFinality = 2000;\n address public messageSender;\n uint32 public sourceDomain = 4294967295; // 0xFFFFFFFF\n\n // Full message with header\n struct Message {\n uint32 version;\n uint32 sourceDomain;\n uint32 destinationDomain;\n bytes32 recipient;\n bytes32 messageHeaderRecipient;\n bytes32 sender;\n bytes32 destinationCaller;\n uint32 minFinalityThreshold;\n bool isTokenTransfer;\n uint256 tokenAmount;\n bytes messageBody;\n }\n\n Message[] public messages;\n // map of encoded messages to the corresponding message structs\n mapping(bytes32 => Message) public encodedMessages;\n\n constructor(address _usdc) {\n usdc = IERC20(_usdc);\n }\n\n // @dev for the porposes of unit tests queues the message to be mock-sent using\n // the cctp bridge.\n function sendMessage(\n uint32 destinationDomain,\n bytes32 recipient,\n bytes32 destinationCaller,\n uint32 minFinalityThreshold,\n bytes memory messageBody\n ) external virtual override {\n bytes32 nonceHash = keccak256(abi.encodePacked(nonce));\n nonce++;\n\n // If destination is mainnet, source is base and vice versa\n uint32 sourceDomain = destinationDomain == 0 ? 6 : 0;\n\n Message memory message = Message({\n version: 1,\n sourceDomain: sourceDomain,\n destinationDomain: destinationDomain,\n recipient: recipient,\n messageHeaderRecipient: recipient,\n sender: bytes32(uint256(uint160(msg.sender))),\n destinationCaller: destinationCaller,\n minFinalityThreshold: minFinalityThreshold,\n isTokenTransfer: false,\n tokenAmount: 0,\n messageBody: messageBody\n });\n\n messages.push(message);\n }\n\n // @dev for the porposes of unit tests queues the USDC burn/mint to be executed\n // using the cctp bridge.\n function sendTokenTransferMessage(\n uint32 destinationDomain,\n bytes32 recipient,\n bytes32 destinationCaller,\n uint32 minFinalityThreshold,\n uint256 tokenAmount,\n bytes memory messageBody\n ) external {\n bytes32 nonceHash = keccak256(abi.encodePacked(nonce));\n nonce++;\n\n // If destination is mainnet, source is base and vice versa\n uint32 sourceDomain = destinationDomain == 0 ? 6 : 0;\n\n Message memory message = Message({\n version: 1,\n sourceDomain: sourceDomain,\n destinationDomain: destinationDomain,\n recipient: recipient,\n messageHeaderRecipient: recipient,\n sender: bytes32(uint256(uint160(msg.sender))),\n destinationCaller: destinationCaller,\n minFinalityThreshold: minFinalityThreshold,\n isTokenTransfer: true,\n tokenAmount: tokenAmount,\n messageBody: messageBody\n });\n\n messages.push(message);\n }\n\n function receiveMessage(bytes memory message, bytes memory attestation)\n public\n virtual\n override\n returns (bool)\n {\n Message memory storedMsg = encodedMessages[keccak256(message)];\n AbstractCCTPIntegrator recipient = AbstractCCTPIntegrator(\n address(uint160(uint256(storedMsg.recipient)))\n );\n\n bytes32 sender = storedMsg.sender;\n bytes memory messageBody = storedMsg.messageBody;\n\n // Credit USDC in this step as it is done in the live cctp contracts\n if (storedMsg.isTokenTransfer) {\n usdc.transfer(address(recipient), storedMsg.tokenAmount);\n // override the sender with the one stored in the Burn message as the sender int he\n // message header is the TokenMessenger.\n sender = bytes32(\n uint256(\n uint160(\n storedMsg.messageBody.extractAddress(\n BURN_MESSAGE_V2_MESSAGE_SENDER_INDEX\n )\n )\n )\n );\n messageBody = storedMsg.messageBody.extractSlice(\n BURN_MESSAGE_V2_HOOK_DATA_INDEX,\n storedMsg.messageBody.length\n );\n } else {\n bytes32 overrideSenderBytes = bytes32(\n uint256(uint160(messageSender))\n );\n if (messageFinality >= 2000) {\n recipient.handleReceiveFinalizedMessage(\n sourceDomain == 4294967295\n ? storedMsg.sourceDomain\n : sourceDomain,\n messageSender == address(0) ? sender : overrideSenderBytes,\n messageFinality,\n messageBody\n );\n } else {\n recipient.handleReceiveUnfinalizedMessage(\n sourceDomain == 4294967295\n ? storedMsg.sourceDomain\n : sourceDomain,\n messageSender == address(0) ? sender : overrideSenderBytes,\n messageFinality,\n messageBody\n );\n }\n }\n\n return true;\n }\n\n function addMessage(Message memory storedMsg) external {\n messages.push(storedMsg);\n }\n\n function _encodeMessageHeader(\n uint32 version,\n uint32 sourceDomain,\n bytes32 sender,\n bytes32 recipient,\n bytes memory messageBody\n ) internal pure returns (bytes memory) {\n bytes memory header = abi.encodePacked(\n version, // 0-3\n sourceDomain, // 4-7\n bytes32(0), // 8-39 destinationDomain\n bytes4(0), // 40-43 nonce\n sender, // 44-75 sender\n recipient, // 76-107 recipient\n bytes32(0), // other stuff\n bytes8(0) // other stuff\n );\n return abi.encodePacked(header, messageBody);\n }\n\n function _removeFront() internal returns (Message memory) {\n require(messages.length > 0, \"No messages\");\n Message memory removed = messages[0];\n // Shift array\n for (uint256 i = 0; i < messages.length - 1; i++) {\n messages[i] = messages[i + 1];\n }\n messages.pop();\n return removed;\n }\n\n function _processMessage(Message memory storedMsg) internal {\n bytes memory encodedMessage = _encodeMessageHeader(\n storedMsg.version,\n storedMsg.sourceDomain,\n storedMsg.sender,\n storedMsg.messageHeaderRecipient,\n storedMsg.messageBody\n );\n\n encodedMessages[keccak256(encodedMessage)] = storedMsg;\n\n address recipient = address(uint160(uint256(storedMsg.recipient)));\n\n AbstractCCTPIntegrator(recipient).relay(encodedMessage, bytes(\"\"));\n }\n\n function _removeBack() internal returns (Message memory) {\n require(messages.length > 0, \"No messages\");\n Message memory last = messages[messages.length - 1];\n messages.pop();\n return last;\n }\n\n function messagesInQueue() external view returns (uint256) {\n return messages.length;\n }\n\n function processFrontOverrideHeader(bytes4 customHeader) external {\n Message memory storedMsg = _removeFront();\n\n bytes memory modifiedBody = abi.encodePacked(\n customHeader,\n storedMsg.messageBody.extractSlice(4, storedMsg.messageBody.length)\n );\n\n storedMsg.messageBody = modifiedBody;\n\n _processMessage(storedMsg);\n }\n\n function processFrontOverrideVersion(uint32 customVersion) external {\n Message memory storedMsg = _removeFront();\n storedMsg.version = customVersion;\n _processMessage(storedMsg);\n }\n\n function processFrontOverrideSender(address customSender) external {\n Message memory storedMsg = _removeFront();\n storedMsg.sender = bytes32(uint256(uint160(customSender)));\n _processMessage(storedMsg);\n }\n\n function processFrontOverrideRecipient(address customRecipient) external {\n Message memory storedMsg = _removeFront();\n storedMsg.messageHeaderRecipient = bytes32(\n uint256(uint160(customRecipient))\n );\n _processMessage(storedMsg);\n }\n\n function processFrontOverrideMessageBody(bytes memory customMessageBody)\n external\n {\n Message memory storedMsg = _removeFront();\n storedMsg.messageBody = customMessageBody;\n _processMessage(storedMsg);\n }\n\n function processFront() external {\n Message memory storedMsg = _removeFront();\n _processMessage(storedMsg);\n }\n\n function processBack() external {\n Message memory storedMsg = _removeBack();\n _processMessage(storedMsg);\n }\n\n function getMessagesLength() external view returns (uint256) {\n return messages.length;\n }\n\n function overrideMessageFinality(uint16 _finality) external {\n messageFinality = _finality;\n }\n\n function overrideSender(address _sender) external {\n messageSender = _sender;\n }\n\n function overrideSourceDomain(uint32 _sourceDomain) external {\n sourceDomain = _sourceDomain;\n }\n}\n" + }, + "contracts/mocks/crosschain/CCTPMessageTransmitterMock2.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { IMessageHandlerV2 } from \"../../interfaces/cctp/ICCTP.sol\";\nimport { BytesHelper } from \"../../utils/BytesHelper.sol\";\nimport { CCTPMessageTransmitterMock } from \"./CCTPMessageTransmitterMock.sol\";\n\nuint8 constant SOURCE_DOMAIN_INDEX = 4;\nuint8 constant RECIPIENT_INDEX = 76;\nuint8 constant SENDER_INDEX = 44;\nuint8 constant MESSAGE_BODY_INDEX = 148;\n\n/**\n * @title Mock conctract simulating the functionality of the CCTPTokenMessenger contract\n * for the porposes of unit testing.\n * @author Origin Protocol Inc\n */\n\ncontract CCTPMessageTransmitterMock2 is CCTPMessageTransmitterMock {\n using BytesHelper for bytes;\n\n address public cctpTokenMessenger;\n\n event MessageReceivedInMockTransmitter(bytes message);\n event MessageSent(bytes message);\n\n constructor(address _usdc) CCTPMessageTransmitterMock(_usdc) {}\n\n function setCCTPTokenMessenger(address _cctpTokenMessenger) external {\n cctpTokenMessenger = _cctpTokenMessenger;\n }\n\n function sendMessage(\n uint32 destinationDomain,\n bytes32 recipient,\n bytes32 destinationCaller,\n uint32 minFinalityThreshold,\n bytes memory messageBody\n ) external virtual override {\n bytes memory message = abi.encodePacked(\n uint32(1), // version\n uint32(destinationDomain == 0 ? 6 : 0), // source domain\n uint32(destinationDomain), // destination domain\n uint256(0),\n bytes32(uint256(uint160(msg.sender))), // sender\n recipient, // recipient\n destinationCaller, // destination caller\n minFinalityThreshold, // min finality threshold\n uint32(0),\n messageBody // message body\n );\n emit MessageSent(message);\n }\n\n function receiveMessage(bytes memory message, bytes memory attestation)\n public\n virtual\n override\n returns (bool)\n {\n uint32 sourceDomain = message.extractUint32(SOURCE_DOMAIN_INDEX);\n address recipient = message.extractAddress(RECIPIENT_INDEX);\n address sender = message.extractAddress(SENDER_INDEX);\n\n bytes memory messageBody = message.extractSlice(\n MESSAGE_BODY_INDEX,\n message.length\n );\n\n bool isBurnMessage = recipient == cctpTokenMessenger;\n\n if (isBurnMessage) {\n // recipient = messageBody.extractAddress(BURN_MESSAGE_V2_RECIPIENT_INDEX);\n // This step won't mint USDC, transfer it to the recipient address\n // in your tests\n } else {\n IMessageHandlerV2(recipient).handleReceiveFinalizedMessage(\n sourceDomain,\n bytes32(uint256(uint160(sender))),\n 2000,\n messageBody\n );\n }\n\n // This step won't mint USDC, transfer it to the recipient address\n // in your tests\n emit MessageReceivedInMockTransmitter(message);\n\n return true;\n }\n}\n" + }, + "contracts/mocks/crosschain/CCTPTokenMessengerMock.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { ICCTPTokenMessenger } from \"../../interfaces/cctp/ICCTP.sol\";\nimport { CCTPMessageTransmitterMock } from \"./CCTPMessageTransmitterMock.sol\";\nimport { IERC20 } from \"../../utils/InitializableAbstractStrategy.sol\";\n\n/**\n * @title Mock conctract simulating the functionality of the CCTPTokenMessenger contract\n * for the porposes of unit testing.\n * @author Origin Protocol Inc\n */\n\ncontract CCTPTokenMessengerMock is ICCTPTokenMessenger {\n IERC20 public usdc;\n CCTPMessageTransmitterMock public cctpMessageTransmitterMock;\n\n constructor(address _usdc, address _cctpMessageTransmitterMock) {\n usdc = IERC20(_usdc);\n cctpMessageTransmitterMock = CCTPMessageTransmitterMock(\n _cctpMessageTransmitterMock\n );\n }\n\n function depositForBurn(\n uint256 amount,\n uint32 destinationDomain,\n bytes32 mintRecipient,\n address burnToken,\n bytes32 destinationCaller,\n uint256 maxFee,\n uint32 minFinalityThreshold\n ) external override {\n revert(\"Not implemented\");\n }\n\n /**\n * @dev mocks the depositForBurnWithHook function by sending the USDC to the CCTPMessageTransmitterMock\n * called by the AbstractCCTPIntegrator contract.\n */\n function depositForBurnWithHook(\n uint256 amount,\n uint32 destinationDomain,\n bytes32 mintRecipient,\n address burnToken,\n bytes32 destinationCaller,\n uint256 maxFee,\n uint32 minFinalityThreshold,\n bytes memory hookData\n ) external override {\n require(burnToken == address(usdc), \"Invalid burn token\");\n\n usdc.transferFrom(msg.sender, address(this), maxFee);\n uint256 destinationAmount = amount - maxFee;\n usdc.transferFrom(\n msg.sender,\n address(cctpMessageTransmitterMock),\n destinationAmount\n );\n\n bytes memory burnMessage = _encodeBurnMessageV2(\n mintRecipient,\n amount,\n msg.sender,\n maxFee,\n maxFee,\n hookData\n );\n\n cctpMessageTransmitterMock.sendTokenTransferMessage(\n destinationDomain,\n mintRecipient,\n destinationCaller,\n minFinalityThreshold,\n destinationAmount,\n burnMessage\n );\n }\n\n function _encodeBurnMessageV2(\n bytes32 mintRecipient,\n uint256 amount,\n address messageSender,\n uint256 maxFee,\n uint256 feeExecuted,\n bytes memory hookData\n ) internal view returns (bytes memory) {\n bytes32 burnTokenBytes32 = bytes32(\n abi.encodePacked(bytes12(0), bytes20(uint160(address(usdc))))\n );\n bytes32 messageSenderBytes32 = bytes32(\n abi.encodePacked(bytes12(0), bytes20(uint160(messageSender)))\n );\n bytes32 expirationBlock = bytes32(0);\n\n // Ref: https://developers.circle.com/cctp/technical-guide#message-body\n return\n abi.encodePacked(\n uint32(1), // 0-3: version\n burnTokenBytes32, // 4-35: burnToken (bytes32 left-padded address)\n mintRecipient, // 36-67: mintRecipient (bytes32 left-padded address)\n amount, // 68-99: uint256 amount\n messageSenderBytes32, // 100-131: messageSender (bytes32 left-padded address)\n maxFee, // 132-163: uint256 maxFee\n feeExecuted, // 164-195: uint256 feeExecuted\n expirationBlock, // 196-227: bytes32 expirationBlock\n hookData // 228+: dynamic hookData\n );\n }\n\n function getMinFeeAmount(uint256 amount)\n external\n view\n override\n returns (uint256)\n {\n return 0;\n }\n}\n" + }, + "contracts/mocks/MintableERC20.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { ERC20 } from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\n\ninterface IMintableERC20 {\n function mint(uint256 value) external;\n\n function mintTo(address to, uint256 value) external;\n}\n\n/**\n * @title MintableERC20\n * @dev Exposes the mint function of ERC20 for tests\n */\nabstract contract MintableERC20 is IMintableERC20, ERC20 {\n /**\n * @dev Function to mint tokens\n * @param _value The amount of tokens to mint.\n */\n function mint(uint256 _value) public virtual override {\n _mint(msg.sender, _value);\n }\n\n /**\n * @dev Function to mint tokens\n * @param _to Address to mint to.\n * @param _value The amount of tokens to mint.\n */\n function mintTo(address _to, uint256 _value) public virtual override {\n _mint(_to, _value);\n }\n}\n" + }, + "contracts/mocks/MockBalancerVault.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport { IBalancerVault } from \"../interfaces/balancer/IBalancerVault.sol\";\nimport { MintableERC20 } from \"./MintableERC20.sol\";\nimport { StableMath } from \"../utils/StableMath.sol\";\n\n// import \"hardhat/console.sol\";\n\ncontract MockBalancerVault {\n using StableMath for uint256;\n uint256 public slippage = 1 ether;\n bool public transferDisabled = false;\n bool public slippageErrorDisabled = false;\n\n function swap(\n IBalancerVault.SingleSwap calldata singleSwap,\n IBalancerVault.FundManagement calldata funds,\n uint256 minAmountOut,\n uint256\n ) external returns (uint256 amountCalculated) {\n amountCalculated = (minAmountOut * slippage) / 1 ether;\n if (!slippageErrorDisabled) {\n require(amountCalculated >= minAmountOut, \"Slippage error\");\n }\n IERC20(singleSwap.assetIn).transferFrom(\n funds.sender,\n address(this),\n singleSwap.amount\n );\n if (!transferDisabled) {\n MintableERC20(singleSwap.assetOut).mintTo(\n funds.recipient,\n amountCalculated\n );\n }\n }\n\n function setSlippage(uint256 _slippage) external {\n slippage = _slippage;\n }\n\n function getPoolTokenInfo(bytes32 poolId, address token)\n external\n view\n returns (\n uint256,\n uint256,\n uint256,\n address\n )\n {}\n\n function disableTransfer() external {\n transferDisabled = true;\n }\n\n function disableSlippageError() external {\n slippageErrorDisabled = true;\n }\n}\n" + }, + "contracts/mocks/MockERC4626Vault.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport { IERC4626 } from \"../../lib/openzeppelin/interfaces/IERC4626.sol\";\nimport { ERC20 } from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\n\ncontract MockERC4626Vault is IERC4626, ERC20 {\n using SafeERC20 for IERC20;\n\n address public asset;\n uint8 public constant DECIMALS = 18;\n\n constructor(address _asset) ERC20(\"Mock Vault Share\", \"MVS\") {\n asset = _asset;\n }\n\n // ERC20 totalSupply is inherited\n\n // ERC20 balanceOf is inherited\n\n function deposit(uint256 assets, address receiver)\n public\n override\n returns (uint256 shares)\n {\n shares = previewDeposit(assets);\n IERC20(asset).safeTransferFrom(msg.sender, address(this), assets);\n _mint(receiver, shares);\n return shares;\n }\n\n function mint(uint256 shares, address receiver)\n public\n override\n returns (uint256 assets)\n {\n assets = previewMint(shares);\n IERC20(asset).safeTransferFrom(msg.sender, address(this), assets);\n _mint(receiver, shares);\n return assets;\n }\n\n function withdraw(\n uint256 assets,\n address receiver,\n address owner\n ) public override returns (uint256 shares) {\n shares = previewWithdraw(assets);\n if (msg.sender != owner) {\n // No approval check for mock\n }\n _burn(owner, shares);\n IERC20(asset).safeTransfer(receiver, assets);\n return shares;\n }\n\n function redeem(\n uint256 shares,\n address receiver,\n address owner\n ) public override returns (uint256 assets) {\n assets = previewRedeem(shares);\n if (msg.sender != owner) {\n // No approval check for mock\n }\n _burn(owner, shares);\n IERC20(asset).safeTransfer(receiver, assets);\n return assets;\n }\n\n function totalAssets() public view override returns (uint256) {\n return IERC20(asset).balanceOf(address(this));\n }\n\n function convertToShares(uint256 assets)\n public\n view\n override\n returns (uint256 shares)\n {\n uint256 supply = totalSupply(); // Use ERC20 totalSupply\n return\n supply == 0 || assets == 0\n ? assets\n : (assets * supply) / totalAssets();\n }\n\n function convertToAssets(uint256 shares)\n public\n view\n override\n returns (uint256 assets)\n {\n uint256 supply = totalSupply(); // Use ERC20 totalSupply\n return supply == 0 ? shares : (shares * totalAssets()) / supply;\n }\n\n function maxDeposit(address receiver)\n public\n view\n override\n returns (uint256)\n {\n return type(uint256).max;\n }\n\n function maxMint(address receiver) public view override returns (uint256) {\n return type(uint256).max;\n }\n\n function maxWithdraw(address owner) public view override returns (uint256) {\n return convertToAssets(balanceOf(owner));\n }\n\n function maxRedeem(address owner) public view override returns (uint256) {\n return balanceOf(owner);\n }\n\n function previewDeposit(uint256 assets)\n public\n view\n override\n returns (uint256 shares)\n {\n return convertToShares(assets);\n }\n\n function previewMint(uint256 shares)\n public\n view\n override\n returns (uint256 assets)\n {\n return convertToAssets(shares);\n }\n\n function previewWithdraw(uint256 assets)\n public\n view\n override\n returns (uint256 shares)\n {\n return convertToShares(assets);\n }\n\n function previewRedeem(uint256 shares)\n public\n view\n override\n returns (uint256 assets)\n {\n return convertToAssets(shares);\n }\n\n function _mint(address account, uint256 amount) internal override {\n super._mint(account, amount);\n }\n\n function _burn(address account, uint256 amount) internal override {\n super._burn(account, amount);\n }\n\n // Inherited from ERC20\n}\n" + }, + "contracts/mocks/MockEvilReentrantContract.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { IVault } from \"../interfaces/IVault.sol\";\nimport { IOracle } from \"../interfaces/IOracle.sol\";\nimport { IRateProvider } from \"../interfaces/balancer/IRateProvider.sol\";\n\nimport { IBalancerVault } from \"../interfaces/balancer/IBalancerVault.sol\";\nimport { IERC20 } from \"../utils/InitializableAbstractStrategy.sol\";\n\nimport { StableMath } from \"../utils/StableMath.sol\";\n\ncontract MockEvilReentrantContract {\n using StableMath for uint256;\n\n IBalancerVault public immutable balancerVault;\n IERC20 public immutable reth;\n IERC20 public immutable weth;\n IVault public immutable oethVault;\n address public immutable poolAddress;\n bytes32 public immutable balancerPoolId;\n address public immutable priceProvider;\n\n constructor(\n address _balancerVault,\n address _oethVault,\n address _reth,\n address _weth,\n address _poolAddress,\n bytes32 _poolId\n ) {\n balancerVault = IBalancerVault(_balancerVault);\n oethVault = IVault(_oethVault);\n reth = IERC20(_reth);\n weth = IERC20(_weth);\n poolAddress = _poolAddress;\n balancerPoolId = _poolId;\n }\n\n function doEvilStuff() public {\n uint256 rethPrice = IOracle(priceProvider).price(address(reth));\n\n // 1. Join pool\n uint256[] memory amounts = new uint256[](2);\n amounts[0] = uint256(10 ether);\n amounts[1] = rethPrice * 10;\n\n address[] memory assets = new address[](2);\n assets[0] = address(reth);\n assets[1] = address(weth);\n\n uint256 minBPT = getBPTExpected(assets, amounts).mulTruncate(\n 0.99 ether\n );\n\n bytes memory joinUserData = abi.encode(\n IBalancerVault.WeightedPoolJoinKind.EXACT_TOKENS_IN_FOR_BPT_OUT,\n amounts,\n minBPT\n );\n\n IBalancerVault.JoinPoolRequest memory joinRequest = IBalancerVault\n .JoinPoolRequest(assets, amounts, joinUserData, false);\n\n balancerVault.joinPool(\n balancerPoolId,\n address(this),\n address(this),\n joinRequest\n );\n\n uint256 bptTokenBalance = IERC20(poolAddress).balanceOf(address(this));\n\n // 2. Redeem as ETH\n bytes memory exitUserData = abi.encode(\n IBalancerVault.WeightedPoolExitKind.EXACT_BPT_IN_FOR_ONE_TOKEN_OUT,\n bptTokenBalance,\n 1\n );\n\n assets[1] = address(0); // Receive ETH instead of WETH\n uint256[] memory exitAmounts = new uint256[](2);\n exitAmounts[1] = 15 ether;\n IBalancerVault.ExitPoolRequest memory exitRequest = IBalancerVault\n .ExitPoolRequest(assets, exitAmounts, exitUserData, false);\n\n balancerVault.exitPool(\n balancerPoolId,\n address(this),\n payable(address(this)),\n exitRequest\n );\n bptTokenBalance = IERC20(poolAddress).balanceOf(address(this));\n }\n\n function getBPTExpected(address[] memory _assets, uint256[] memory _amounts)\n internal\n view\n virtual\n returns (uint256 bptExpected)\n {\n for (uint256 i = 0; i < _assets.length; ++i) {\n uint256 strategyAssetMarketPrice = IOracle(priceProvider).price(\n _assets[i]\n );\n // convert asset amount to ETH amount\n bptExpected =\n bptExpected +\n _amounts[i].mulTruncate(strategyAssetMarketPrice);\n }\n\n uint256 bptRate = IRateProvider(poolAddress).getRate();\n // Convert ETH amount to BPT amount\n bptExpected = bptExpected.divPrecisely(bptRate);\n }\n\n function approveAllTokens() public {\n // Approve all tokens\n weth.approve(address(oethVault), type(uint256).max);\n reth.approve(poolAddress, type(uint256).max);\n weth.approve(poolAddress, type(uint256).max);\n reth.approve(address(balancerVault), type(uint256).max);\n weth.approve(address(balancerVault), type(uint256).max);\n }\n\n receive() external payable {\n // 3. Try to mint OETH\n oethVault.mint(address(weth), 1 ether, 0.9 ether);\n }\n}\n" + }, + "contracts/mocks/MockRoosterAMOStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title Rooster AMO strategy exposing extra functionality\n * @author Origin Protocol Inc\n */\n\nimport { RoosterAMOStrategy } from \"../strategies/plume/RoosterAMOStrategy.sol\";\nimport { IMaverickV2Pool } from \"../interfaces/plume/IMaverickV2Pool.sol\";\n\ncontract MockRoosterAMOStrategy is RoosterAMOStrategy {\n constructor(\n BaseStrategyConfig memory _stratConfig,\n address _wethAddress,\n address _oethpAddress,\n address _liquidityManager,\n address _poolLens,\n address _maverickPosition,\n address _maverickQuoter,\n address _mPool,\n bool _upperTickAtParity,\n address _votingDistributor,\n address _poolDistributor\n )\n RoosterAMOStrategy(\n _stratConfig,\n _wethAddress,\n _oethpAddress,\n _liquidityManager,\n _poolLens,\n _maverickPosition,\n _maverickQuoter,\n _mPool,\n _upperTickAtParity,\n _votingDistributor,\n _poolDistributor\n )\n {}\n\n function getCurrentWethShare() external view returns (uint256) {\n uint256 _currentPrice = getPoolSqrtPrice();\n\n return _getWethShare(_currentPrice);\n }\n}\n" + }, + "contracts/poolBooster/AbstractPoolBoosterFactory.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { Governable } from \"../governance/Governable.sol\";\nimport { IPoolBooster } from \"../interfaces/poolBooster/IPoolBooster.sol\";\nimport { IPoolBoostCentralRegistry } from \"../interfaces/poolBooster/IPoolBoostCentralRegistry.sol\";\n\n/**\n * @title Abstract Pool booster factory\n * @author Origin Protocol Inc\n */\ncontract AbstractPoolBoosterFactory is Governable {\n struct PoolBoosterEntry {\n address boosterAddress;\n address ammPoolAddress;\n IPoolBoostCentralRegistry.PoolBoosterType boosterType;\n }\n\n // @notice address of Origin Token\n address public immutable oToken;\n // @notice Central registry contract\n IPoolBoostCentralRegistry public immutable centralRegistry;\n\n // @notice list of all the pool boosters created by this factory\n PoolBoosterEntry[] public poolBoosters;\n // @notice mapping of AMM pool to pool booster\n mapping(address => PoolBoosterEntry) public poolBoosterFromPool;\n\n // @param address _oToken address of the OToken token\n // @param address _governor address governor\n // @param address _centralRegistry address of the central registry\n constructor(\n address _oToken,\n address _governor,\n address _centralRegistry\n ) {\n require(_oToken != address(0), \"Invalid oToken address\");\n require(_governor != address(0), \"Invalid governor address\");\n require(\n _centralRegistry != address(0),\n \"Invalid central registry address\"\n );\n\n oToken = _oToken;\n centralRegistry = IPoolBoostCentralRegistry(_centralRegistry);\n _setGovernor(_governor);\n }\n\n /**\n * @notice Goes over all the pool boosters created by this factory and\n * calls bribe() on them.\n * @param _exclusionList A list of pool booster addresses to skip when\n * calling this function.\n */\n function bribeAll(address[] memory _exclusionList) external {\n uint256 lengthI = poolBoosters.length;\n for (uint256 i = 0; i < lengthI; i++) {\n address poolBoosterAddress = poolBoosters[i].boosterAddress;\n bool skipBribeCall = false;\n uint256 lengthJ = _exclusionList.length;\n for (uint256 j = 0; j < lengthJ; j++) {\n // pool booster in exclusion list\n if (_exclusionList[j] == poolBoosterAddress) {\n skipBribeCall = true;\n break;\n }\n }\n\n if (!skipBribeCall) {\n IPoolBooster(poolBoosterAddress).bribe();\n }\n }\n }\n\n /**\n * @notice Removes the pool booster from the internal list of pool boosters.\n * @dev This action does not destroy the pool booster contract nor does it\n * stop the yield delegation to it.\n * @param _poolBoosterAddress address of the pool booster\n */\n function removePoolBooster(address _poolBoosterAddress)\n external\n onlyGovernor\n {\n uint256 boostersLen = poolBoosters.length;\n for (uint256 i = 0; i < boostersLen; ++i) {\n if (poolBoosters[i].boosterAddress == _poolBoosterAddress) {\n // erase mapping\n delete poolBoosterFromPool[poolBoosters[i].ammPoolAddress];\n\n // overwrite current pool booster with the last entry in the list\n poolBoosters[i] = poolBoosters[boostersLen - 1];\n // drop the last entry\n poolBoosters.pop();\n\n centralRegistry.emitPoolBoosterRemoved(_poolBoosterAddress);\n break;\n }\n }\n }\n\n function _storePoolBoosterEntry(\n address _poolBoosterAddress,\n address _ammPoolAddress,\n IPoolBoostCentralRegistry.PoolBoosterType _boosterType\n ) internal {\n PoolBoosterEntry memory entry = PoolBoosterEntry(\n _poolBoosterAddress,\n _ammPoolAddress,\n _boosterType\n );\n\n poolBoosters.push(entry);\n poolBoosterFromPool[_ammPoolAddress] = entry;\n\n // emit the events of the pool booster created\n centralRegistry.emitPoolBoosterCreated(\n _poolBoosterAddress,\n _ammPoolAddress,\n _boosterType\n );\n }\n\n function _deployContract(bytes memory _bytecode, uint256 _salt)\n internal\n returns (address _address)\n {\n // solhint-disable-next-line no-inline-assembly\n assembly {\n _address := create2(\n 0,\n add(_bytecode, 0x20),\n mload(_bytecode),\n _salt\n )\n }\n\n require(\n _address.code.length > 0 && _address != address(0),\n \"Failed creating a pool booster\"\n );\n }\n\n // pre-compute the address of the deployed contract that will be\n // created when create2 is called\n function _computeAddress(bytes memory _bytecode, uint256 _salt)\n internal\n view\n returns (address)\n {\n bytes32 hash = keccak256(\n abi.encodePacked(\n bytes1(0xff),\n address(this),\n _salt,\n keccak256(_bytecode)\n )\n );\n\n // cast last 20 bytes of hash to address\n return address(uint160(uint256(hash)));\n }\n\n function poolBoosterLength() external view returns (uint256) {\n return poolBoosters.length;\n }\n}\n" + }, + "contracts/poolBooster/curve/CurvePoolBooster.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport { Initializable } from \"../../utils/Initializable.sol\";\nimport { Strategizable } from \"../../governance/Strategizable.sol\";\nimport { ICampaignRemoteManager } from \"../../interfaces/ICampaignRemoteManager.sol\";\n\n/// @title CurvePoolBooster\n/// @author Origin Protocol\n/// @notice Contract to manage interactions with VotemarketV2 for a dedicated Curve pool/gauge.\ncontract CurvePoolBooster is Initializable, Strategizable {\n using SafeERC20 for IERC20;\n\n ////////////////////////////////////////////////////\n /// --- CONSTANTS && IMMUTABLES\n ////////////////////////////////////////////////////\n /// @notice Base fee for the contract, 100%\n uint16 public constant FEE_BASE = 10_000;\n\n /// @notice Arbitrum where the votemarket is running\n uint256 public constant targetChainId = 42161;\n\n /// @notice Address of the gauge to manage\n address public immutable gauge;\n\n /// @notice Address of the reward token\n address public immutable rewardToken;\n\n ////////////////////////////////////////////////////\n /// --- STORAGE\n ////////////////////////////////////////////////////\n\n /// @notice Fee in FEE_BASE unit payed when managing campaign.\n uint16 public fee;\n\n /// @notice Address of the fee collector\n address public feeCollector;\n\n /// @notice Address of the campaignRemoteManager linked to VotemarketV2\n address public campaignRemoteManager;\n\n /// @notice Address of votemarket in L2\n address public votemarket;\n\n /// @notice Id of the campaign created\n uint256 public campaignId;\n\n ////////////////////////////////////////////////////\n /// --- EVENTS\n ////////////////////////////////////////////////////\n event FeeUpdated(uint16 newFee);\n event FeeCollected(address feeCollector, uint256 feeAmount);\n event FeeCollectorUpdated(address newFeeCollector);\n event VotemarketUpdated(address newVotemarket);\n event CampaignRemoteManagerUpdated(address newCampaignRemoteManager);\n event CampaignCreated(\n address gauge,\n address rewardToken,\n uint256 maxRewardPerVote,\n uint256 totalRewardAmount\n );\n event CampaignIdUpdated(uint256 newId);\n event CampaignClosed(uint256 campaignId);\n event TotalRewardAmountUpdated(uint256 extraTotalRewardAmount);\n event NumberOfPeriodsUpdated(uint8 extraNumberOfPeriods);\n event RewardPerVoteUpdated(uint256 newMaxRewardPerVote);\n event TokensRescued(address token, uint256 amount, address receiver);\n\n ////////////////////////////////////////////////////\n /// --- CONSTRUCTOR && INITIALIZATION\n ////////////////////////////////////////////////////\n constructor(address _rewardToken, address _gauge) {\n rewardToken = _rewardToken;\n gauge = _gauge;\n\n // Prevent implementation contract to be governed\n _setGovernor(address(0));\n }\n\n /// @notice initialize function, to set up initial internal state\n /// @param _strategist Address of the strategist\n /// @param _fee Fee in FEE_BASE unit payed when managing campaign\n /// @param _feeCollector Address of the fee collector\n function initialize(\n address _strategist,\n uint16 _fee,\n address _feeCollector,\n address _campaignRemoteManager,\n address _votemarket\n ) external onlyGovernor initializer {\n _setStrategistAddr(_strategist);\n _setFee(_fee);\n _setFeeCollector(_feeCollector);\n _setCampaignRemoteManager(_campaignRemoteManager);\n _setVotemarket(_votemarket);\n }\n\n ////////////////////////////////////////////////////\n /// --- MUTATIVE FUNCTIONS\n ////////////////////////////////////////////////////\n /// @notice Create a new campaign on VotemarketV2\n /// @dev This will use all token available in this contract\n /// @dev Caller must send ETH to pay for the bridge fee\n /// @param numberOfPeriods Duration of the campaign in weeks\n /// @param maxRewardPerVote Maximum reward per vote to distribute, to avoid overspending\n /// @param blacklist List of addresses to exclude from the campaign\n /// @param additionalGasLimit Additional gas limit for the bridge\n function createCampaign(\n uint8 numberOfPeriods,\n uint256 maxRewardPerVote,\n address[] calldata blacklist,\n uint256 additionalGasLimit\n ) external payable nonReentrant onlyGovernorOrStrategist {\n require(campaignId == 0, \"Campaign already created\");\n require(numberOfPeriods > 1, \"Invalid number of periods\");\n require(maxRewardPerVote > 0, \"Invalid reward per vote\");\n\n // Handle fee (if any)\n uint256 balanceSubFee = _handleFee();\n\n // Approve the balanceSubFee to the campaign manager\n IERC20(rewardToken).safeApprove(campaignRemoteManager, 0);\n IERC20(rewardToken).safeApprove(campaignRemoteManager, balanceSubFee);\n\n // Create a new campaign\n ICampaignRemoteManager(campaignRemoteManager).createCampaign{\n value: msg.value\n }(\n ICampaignRemoteManager.CampaignCreationParams({\n chainId: targetChainId,\n gauge: gauge,\n manager: address(this),\n rewardToken: rewardToken,\n numberOfPeriods: numberOfPeriods,\n maxRewardPerVote: maxRewardPerVote,\n totalRewardAmount: balanceSubFee,\n addresses: blacklist,\n hook: address(0),\n isWhitelist: false\n }),\n targetChainId,\n additionalGasLimit,\n votemarket\n );\n\n emit CampaignCreated(\n gauge,\n rewardToken,\n maxRewardPerVote,\n balanceSubFee\n );\n }\n\n /// @notice Manage campaign parameters in a single call\n /// @dev This function should be called after the campaign is created\n /// @dev Caller must send ETH to pay for the bridge fee\n /// @param totalRewardAmount Amount of reward tokens to add:\n /// - 0: no update\n /// - type(uint256).max: use all tokens in contract\n /// - other: use specific amount\n /// @param numberOfPeriods Number of additional periods (0 = no update)\n /// @param maxRewardPerVote New maximum reward per vote (0 = no update)\n /// @param additionalGasLimit Additional gas limit for the bridge\n function manageCampaign(\n uint256 totalRewardAmount,\n uint8 numberOfPeriods,\n uint256 maxRewardPerVote,\n uint256 additionalGasLimit\n ) external payable nonReentrant onlyGovernorOrStrategist {\n require(campaignId != 0, \"Campaign not created\");\n\n uint256 rewardAmount;\n\n if (totalRewardAmount != 0) {\n uint256 amount = min(\n IERC20(rewardToken).balanceOf(address(this)),\n totalRewardAmount\n );\n\n // Handle fee\n rewardAmount = _handleFee(amount);\n require(rewardAmount > 0, \"No reward to add\");\n\n // Approve the reward amount to the campaign manager\n IERC20(rewardToken).safeApprove(campaignRemoteManager, 0);\n IERC20(rewardToken).safeApprove(\n campaignRemoteManager,\n rewardAmount\n );\n }\n\n // Call remote manager\n ICampaignRemoteManager(campaignRemoteManager).manageCampaign{\n value: msg.value\n }(\n ICampaignRemoteManager.CampaignManagementParams({\n campaignId: campaignId,\n rewardToken: rewardToken,\n numberOfPeriods: numberOfPeriods,\n totalRewardAmount: rewardAmount,\n maxRewardPerVote: maxRewardPerVote\n }),\n targetChainId,\n additionalGasLimit,\n votemarket\n );\n\n // Emit relevant events\n if (rewardAmount > 0) {\n emit TotalRewardAmountUpdated(rewardAmount);\n }\n if (numberOfPeriods > 0) {\n emit NumberOfPeriodsUpdated(numberOfPeriods);\n }\n if (maxRewardPerVote > 0) {\n emit RewardPerVoteUpdated(maxRewardPerVote);\n }\n }\n\n /// @notice Close the campaign.\n /// @dev This function only work on the L2 chain. Not on mainnet.\n /// @dev Caller must send ETH to pay for the bridge fee\n /// @dev The _campaignId parameter is not related to the campaignId of this contract, allowing greater flexibility.\n /// @param _campaignId Id of the campaign to close\n /// @param additionalGasLimit Additional gas limit for the bridge\n // slither-disable-start reentrancy-eth\n function closeCampaign(uint256 _campaignId, uint256 additionalGasLimit)\n external\n payable\n nonReentrant\n onlyGovernorOrStrategist\n {\n ICampaignRemoteManager(campaignRemoteManager).closeCampaign{\n value: msg.value\n }(\n ICampaignRemoteManager.CampaignClosingParams({\n campaignId: campaignId\n }),\n targetChainId,\n additionalGasLimit,\n votemarket\n );\n campaignId = 0;\n emit CampaignClosed(_campaignId);\n }\n\n // slither-disable-end reentrancy-eth\n\n /// @notice Calculate the fee amount and transfer it to the feeCollector\n /// @dev Uses full contract balance\n /// @return Balance after fee\n function _handleFee() internal returns (uint256) {\n uint256 balance = IERC20(rewardToken).balanceOf(address(this));\n\n // This is not a problem if balance is 0, feeAmount will be 0 as well\n // We don't want to make the whole function revert just because of that.\n return _handleFee(balance);\n }\n\n /// @notice Calculate the fee amount and transfer it to the feeCollector\n /// @param amount Amount to take fee from\n /// @return Amount after fee\n function _handleFee(uint256 amount) internal returns (uint256) {\n uint256 feeAmount = (amount * fee) / FEE_BASE;\n\n // If there is a fee, transfer it to the feeCollector\n if (feeAmount > 0) {\n IERC20(rewardToken).safeTransfer(feeCollector, feeAmount);\n emit FeeCollected(feeCollector, feeAmount);\n }\n\n // Fetch balance again to avoid rounding issues\n return IERC20(rewardToken).balanceOf(address(this));\n }\n\n ////////////////////////////////////////////////////\n /// --- GOVERNANCE && OPERATION\n ////////////////////////////////////////////////////\n /// @notice Set the campaign id\n /// @dev Only callable by the governor or strategist\n /// @param _campaignId New campaign id\n function setCampaignId(uint256 _campaignId)\n external\n onlyGovernorOrStrategist\n {\n campaignId = _campaignId;\n emit CampaignIdUpdated(_campaignId);\n }\n\n /// @notice Rescue ETH from the contract\n /// @dev Only callable by the governor or strategist\n /// @param receiver Address to receive the ETH\n function rescueETH(address receiver)\n external\n nonReentrant\n onlyGovernorOrStrategist\n {\n require(receiver != address(0), \"Invalid receiver\");\n uint256 balance = address(this).balance;\n (bool success, ) = receiver.call{ value: balance }(\"\");\n require(success, \"Transfer failed\");\n emit TokensRescued(address(0), balance, receiver);\n }\n\n /// @notice Rescue ERC20 tokens from the contract\n /// @dev Only callable by the governor or strategist\n /// @param token Address of the token to rescue\n function rescueToken(address token, address receiver)\n external\n nonReentrant\n onlyGovernor\n {\n require(receiver != address(0), \"Invalid receiver\");\n uint256 balance = IERC20(token).balanceOf(address(this));\n IERC20(token).safeTransfer(receiver, balance);\n emit TokensRescued(token, balance, receiver);\n }\n\n /// @notice Set the fee\n /// @dev Only callable by the governor\n /// @param _fee New fee\n function setFee(uint16 _fee) external onlyGovernor {\n _setFee(_fee);\n }\n\n /// @notice Internal logic to set the fee\n function _setFee(uint16 _fee) internal {\n require(_fee <= FEE_BASE / 2, \"Fee too high\");\n fee = _fee;\n emit FeeUpdated(_fee);\n }\n\n /// @notice Set the fee collector\n /// @dev Only callable by the governor\n /// @param _feeCollector New fee collector\n function setFeeCollector(address _feeCollector) external onlyGovernor {\n _setFeeCollector(_feeCollector);\n }\n\n /// @notice Internal logic to set the fee collector\n function _setFeeCollector(address _feeCollector) internal {\n require(_feeCollector != address(0), \"Invalid fee collector\");\n feeCollector = _feeCollector;\n emit FeeCollectorUpdated(_feeCollector);\n }\n\n /// @notice Set the campaignRemoteManager\n /// @param _campaignRemoteManager New campaignRemoteManager address\n function setCampaignRemoteManager(address _campaignRemoteManager)\n external\n onlyGovernor\n {\n _setCampaignRemoteManager(_campaignRemoteManager);\n }\n\n /// @notice Internal logic to set the campaignRemoteManager\n /// @param _campaignRemoteManager New campaignRemoteManager address\n function _setCampaignRemoteManager(address _campaignRemoteManager)\n internal\n {\n require(\n _campaignRemoteManager != address(0),\n \"Invalid campaignRemoteManager\"\n );\n campaignRemoteManager = _campaignRemoteManager;\n emit CampaignRemoteManagerUpdated(_campaignRemoteManager);\n }\n\n /// @notice Set the votemarket address\n /// @param _votemarket New votemarket address\n function setVotemarket(address _votemarket) external onlyGovernor {\n _setVotemarket(_votemarket);\n }\n\n /// @notice Internal logic to set the votemarket address\n function _setVotemarket(address _votemarket) internal {\n require(_votemarket != address(0), \"Invalid votemarket\");\n votemarket = _votemarket;\n emit VotemarketUpdated(_votemarket);\n }\n\n /// @notice Return the minimum of two uint256 numbers\n function min(uint256 a, uint256 b) internal pure returns (uint256) {\n return a < b ? a : b;\n }\n\n receive() external payable {}\n}\n" + }, + "contracts/poolBooster/curve/CurvePoolBoosterFactory.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { ICreateX } from \"../../interfaces/ICreateX.sol\";\nimport { Initializable } from \"../../utils/Initializable.sol\";\nimport { Strategizable } from \"../../governance/Strategizable.sol\";\nimport { CurvePoolBoosterPlain } from \"./CurvePoolBoosterPlain.sol\";\nimport { IPoolBoostCentralRegistry } from \"../../interfaces/poolBooster/IPoolBoostCentralRegistry.sol\";\n\n/// @title CurvePoolBoosterFactory\n/// @author Origin Protocol\n/// @notice Factory contract to create CurvePoolBoosterPlain instances\ncontract CurvePoolBoosterFactory is Initializable, Strategizable {\n ////////////////////////////////////////////////////\n /// --- Structs\n ////////////////////////////////////////////////////\n struct PoolBoosterEntry {\n address boosterAddress;\n address ammPoolAddress;\n IPoolBoostCentralRegistry.PoolBoosterType boosterType;\n }\n\n ////////////////////////////////////////////////////\n /// --- Constants\n ////////////////////////////////////////////////////\n\n /// @notice Address of the CreateX contract\n ICreateX public constant CREATEX =\n ICreateX(0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed);\n\n ////////////////////////////////////////////////////\n /// --- Storage\n ////////////////////////////////////////////////////\n\n /// @notice list of all the pool boosters created by this factory\n PoolBoosterEntry[] public poolBoosters;\n /// @notice Central registry contract\n IPoolBoostCentralRegistry public centralRegistry;\n /// @notice mapping of AMM pool to pool booster\n mapping(address => PoolBoosterEntry) public poolBoosterFromPool;\n\n ////////////////////////////////////////////////////\n /// --- Initialize\n ////////////////////////////////////////////////////\n\n /// @notice Initialize the contract. Normally we'd rather have the governor and strategist set in the constructor,\n /// but since this contract is deployed by CreateX we need to set them in the initialize function because\n /// the constructor's parameters influence the address of the contract when deployed using CreateX.\n /// And having different governor and strategist on the same address on different chains would\n /// cause issues.\n /// @param _governor Address of the governor\n /// @param _strategist Address of the strategist\n /// @param _centralRegistry Address of the central registry\n function initialize(\n address _governor,\n address _strategist,\n address _centralRegistry\n ) external initializer {\n _setGovernor(_governor);\n _setStrategistAddr(_strategist);\n centralRegistry = IPoolBoostCentralRegistry(_centralRegistry);\n }\n\n ////////////////////////////////////////////////////\n /// --- External Mutative Functions\n ////////////////////////////////////////////////////\n\n /// @notice Create a new CurvePoolBoosterPlain instance\n /// @param _rewardToken Address of the reward token (OETH or OUSD)\n /// @param _gauge Address of the gauge (e.g. Curve OETH/WETH Gauge)\n /// @param _feeCollector Address of the fee collector (e.g. MultichainStrategist)\n /// @param _fee Fee in FEE_BASE unit payed when managing campaign\n /// @param _campaignRemoteManager Address of the campaign remote manager\n /// @param _votemarket Address of the votemarket\n /// @param _salt A unique number that affects the address of the pool booster created. Note: this number\n /// should match the one from `computePoolBoosterAddress` in order for the final deployed address\n /// and pre-computed address to match\n /// @param _expectedAddress The expected address of the pool booster. This is used to verify that the pool booster\n /// was deployed at the expected address, otherwise the transaction batch will revert. If set to 0 then the\n /// address verification is skipped.\n function createCurvePoolBoosterPlain(\n address _rewardToken,\n address _gauge,\n address _feeCollector,\n uint16 _fee,\n address _campaignRemoteManager,\n address _votemarket,\n bytes32 _salt,\n address _expectedAddress\n ) external onlyGovernorOrStrategist returns (address) {\n require(governor() != address(0), \"Governor not set\");\n require(strategistAddr != address(0), \"Strategist not set\");\n // salt encoded sender\n address senderAddress = address(bytes20(_salt));\n // the contract that calls the CreateX should be encoded in the salt to protect against front-running\n require(senderAddress == address(this), \"Front-run protection failed\");\n\n address poolBoosterAddress = CREATEX.deployCreate2(\n _salt,\n _getInitCode(_rewardToken, _gauge)\n );\n\n require(\n _expectedAddress == address(0) ||\n poolBoosterAddress == _expectedAddress,\n \"Pool booster deployed at unexpected address\"\n );\n\n CurvePoolBoosterPlain(payable(poolBoosterAddress)).initialize(\n governor(),\n strategistAddr,\n _fee,\n _feeCollector,\n _campaignRemoteManager,\n _votemarket\n );\n\n _storePoolBoosterEntry(poolBoosterAddress, _gauge);\n return poolBoosterAddress;\n }\n\n /// @notice Removes the pool booster from the internal list of pool boosters.\n /// @dev This action does not destroy the pool booster contract nor does it\n /// stop the yield delegation to it.\n /// @param _poolBoosterAddress address of the pool booster\n function removePoolBooster(address _poolBoosterAddress)\n external\n onlyGovernor\n {\n uint256 boostersLen = poolBoosters.length;\n for (uint256 i = 0; i < boostersLen; ++i) {\n if (poolBoosters[i].boosterAddress == _poolBoosterAddress) {\n // erase mapping\n delete poolBoosterFromPool[poolBoosters[i].ammPoolAddress];\n\n // overwrite current pool booster with the last entry in the list\n poolBoosters[i] = poolBoosters[boostersLen - 1];\n // drop the last entry\n poolBoosters.pop();\n\n // centralRegistry can be address(0) on some chains\n if (address(centralRegistry) != address(0)) {\n centralRegistry.emitPoolBoosterRemoved(_poolBoosterAddress);\n }\n break;\n }\n }\n }\n\n ////////////////////////////////////////////////////\n /// --- Internal Mutative Functions\n ////////////////////////////////////////////////////\n\n /// @notice Stores the pool booster entry in the internal list and mapping\n /// @param _poolBoosterAddress address of the pool booster\n /// @param _ammPoolAddress address of the AMM pool\n function _storePoolBoosterEntry(\n address _poolBoosterAddress,\n address _ammPoolAddress\n ) internal {\n IPoolBoostCentralRegistry.PoolBoosterType _boosterType = IPoolBoostCentralRegistry\n .PoolBoosterType\n .CurvePoolBoosterPlain;\n PoolBoosterEntry memory entry = PoolBoosterEntry(\n _poolBoosterAddress,\n _ammPoolAddress,\n _boosterType\n );\n\n poolBoosters.push(entry);\n poolBoosterFromPool[_ammPoolAddress] = entry;\n\n // emit the events of the pool booster created\n // centralRegistry can be address(0) on some chains\n if (address(centralRegistry) != address(0)) {\n centralRegistry.emitPoolBoosterCreated(\n _poolBoosterAddress,\n _ammPoolAddress,\n _boosterType\n );\n }\n }\n\n ////////////////////////////////////////////////////\n /// --- External View Functions\n ////////////////////////////////////////////////////\n\n /// @notice Create a new CurvePoolBoosterPlain instance (address computation version)\n /// @param _rewardToken Address of the reward token (OETH or OUSD)\n /// @param _gauge Address of the gauge (e.g. Curve OETH/WETH Gauge)\n /// @param _salt A unique number that affects the address of the pool booster created. Note: this number\n /// should match the one from `createCurvePoolBoosterPlain` in order for the final deployed address\n /// and pre-computed address to match\n function computePoolBoosterAddress(\n address _rewardToken,\n address _gauge,\n bytes32 _salt\n ) external view returns (address) {\n bytes32 guardedSalt = _computeGuardedSalt(_salt);\n return\n CREATEX.computeCreate2Address(\n guardedSalt,\n keccak256(_getInitCode(_rewardToken, _gauge)),\n address(CREATEX)\n );\n }\n\n /// @notice Encodes a salt for CreateX by concatenating deployer address (bytes20), cross-chain protection flag\n /// (bytes1), and the first 11 bytes of the provided salt (most significant bytes). This function is exposed\n /// for easier operations. For the salt value itself just use the epoch time when the operation is performed.\n /// @param salt The raw salt as uint256; converted to bytes32, then only the first 11 bytes (MSB) are used.\n /// @return encodedSalt The resulting 32-byte encoded salt.\n function encodeSaltForCreateX(uint256 salt)\n external\n view\n returns (bytes32 encodedSalt)\n {\n // only the right most 11 bytes are considered when encoding salt. Which is limited by the number in the below\n // require. If salt were higher, the higher bytes would need to be set to 0 to not affect the \"or\" way of\n // encoding the salt.\n require(salt <= 309485009821345068724781055, \"Invalid salt\");\n\n // prepare encoded salt guarded by this factory address. When the deployer part of the salt is the same as the\n // caller of CreateX the salt is re-hashed and thus guarded from front-running.\n address deployer = address(this);\n\n // Flag as uint8 (0)\n uint8 flag = 0;\n\n // Precompute parts\n uint256 deployerPart = uint256(uint160(deployer)) << 96; // 20 bytes shifted left 96 bits (12 bytes)\n uint256 flagPart = uint256(flag) << 88; // 1 byte shifted left 88 bits (11 bytes)\n\n // Concat via nested OR\n // solhint-disable-next-line no-inline-assembly\n assembly {\n encodedSalt := or(or(deployerPart, flagPart), salt)\n }\n }\n\n /// @notice Get the number of pool boosters created by this factory\n function poolBoosterLength() external view returns (uint256) {\n return poolBoosters.length;\n }\n\n /// @notice Get the list of all pool boosters created by this factory\n function getPoolBoosters()\n external\n view\n returns (PoolBoosterEntry[] memory)\n {\n return poolBoosters;\n }\n\n ////////////////////////////////////////////////////\n /// --- Internal View/Pure Functions\n ////////////////////////////////////////////////////\n\n /// @notice Get the init code for the CurvePoolBoosterPlain contract\n function _getInitCode(address _rewardToken, address _gauge)\n internal\n pure\n returns (bytes memory)\n {\n return\n abi.encodePacked(\n type(CurvePoolBoosterPlain).creationCode,\n abi.encode(_rewardToken, _gauge)\n );\n }\n\n /// @notice Compute the guarded salt for CreateX protections. This version of guarded\n /// salt expects that this factory contract is the one doing calls to the CreateX contract.\n function _computeGuardedSalt(bytes32 _salt)\n internal\n view\n returns (bytes32)\n {\n return\n _efficientHash({\n a: bytes32(uint256(uint160(address(this)))),\n b: _salt\n });\n }\n\n /// @notice Efficiently hash two bytes32 values together\n function _efficientHash(bytes32 a, bytes32 b)\n internal\n pure\n returns (bytes32 hash)\n {\n // solhint-disable-next-line no-inline-assembly\n assembly {\n mstore(0x00, a)\n mstore(0x20, b)\n hash := keccak256(0x00, 0x40)\n }\n }\n}\n" + }, + "contracts/poolBooster/curve/CurvePoolBoosterPlain.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { CurvePoolBooster } from \"./CurvePoolBooster.sol\";\n\n/// @title CurvePoolBoosterPlain\n/// @author Origin Protocol\n/// @notice Contract to manage interactions with VotemarketV2 for a dedicated Curve pool/gauge. It differs from the\n/// CurvePoolBooster in that it is not proxied.\n/// @dev Governor is not set in the constructor so that the same contract can be deployed on the same address on\n/// multiple chains. Governor is set in the initialize function.\ncontract CurvePoolBoosterPlain is CurvePoolBooster {\n constructor(address _rewardToken, address _gauge)\n CurvePoolBooster(_rewardToken, _gauge)\n {\n rewardToken = _rewardToken;\n gauge = _gauge;\n }\n\n /// @notice initialize function, to set up initial internal state\n /// @param _strategist Address of the strategist\n /// @param _fee Fee in FEE_BASE unit payed when managing campaign\n /// @param _feeCollector Address of the fee collector\n /// @dev Since this function is initialized in the same transaction as it is created the initialize function\n /// doesn't need role protection.\n /// Because the governor is only set in the initialisation function the base class initialize can not be\n /// called as it is not the governor who is issueing this call.\n function initialize(\n address _govenor,\n address _strategist,\n uint16 _fee,\n address _feeCollector,\n address _campaignRemoteManager,\n address _votemarket\n ) external initializer {\n _setStrategistAddr(_strategist);\n _setFee(_fee);\n _setFeeCollector(_feeCollector);\n _setCampaignRemoteManager(_campaignRemoteManager);\n _setVotemarket(_votemarket);\n\n // Set the governor to the provided governor\n _setGovernor(_govenor);\n }\n}\n" + }, + "contracts/poolBooster/PoolBoostCentralRegistry.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { Governable } from \"../governance/Governable.sol\";\nimport { IPoolBoostCentralRegistry } from \"../interfaces/poolBooster/IPoolBoostCentralRegistry.sol\";\n\n/**\n * @title Contract that holds all governance approved pool booster Factory\n * implementation deployments\n * @author Origin Protocol Inc\n */\ncontract PoolBoostCentralRegistry is Governable, IPoolBoostCentralRegistry {\n event FactoryApproved(address factoryAddress);\n event FactoryRemoved(address factoryAddress);\n\n // @notice List of approved factories\n address[] public factories;\n\n modifier onlyApprovedFactories() {\n require(isApprovedFactory(msg.sender), \"Not an approved factory\");\n _;\n }\n\n constructor() {\n // set the governor of the implementation contract to zero address\n _setGovernor(address(0));\n }\n\n /**\n * @notice Adds a factory address to the approved factory addresses\n * @param _factoryAddress address of the factory\n */\n function approveFactory(address _factoryAddress) external onlyGovernor {\n require(_factoryAddress != address(0), \"Invalid address\");\n require(\n !isApprovedFactory(_factoryAddress),\n \"Factory already approved\"\n );\n\n factories.push(_factoryAddress);\n emit FactoryApproved(_factoryAddress);\n }\n\n /**\n * @notice Removes the factory from approved factory addresses\n * @param _factoryAddress address of the factory\n */\n function removeFactory(address _factoryAddress) external onlyGovernor {\n require(_factoryAddress != address(0), \"Invalid address\");\n\n uint256 length = factories.length;\n bool factoryRemoved = false;\n for (uint256 i = 0; i < length; i++) {\n if (factories[i] != _factoryAddress) {\n continue;\n }\n\n factories[i] = factories[length - 1];\n factories.pop();\n emit FactoryRemoved(_factoryAddress);\n factoryRemoved = true;\n break;\n }\n require(factoryRemoved, \"Not an approved factory\");\n\n emit FactoryRemoved(_factoryAddress);\n }\n\n /**\n * @notice Emits a pool booster created event\n * @dev This has been created as a convenience method for the monitoring to have\n * an index of all of the created pool boosters by only listening to the\n * events of this contract.\n * @param _poolBoosterAddress address of the pool booster created\n * @param _ammPoolAddress address of the AMM pool forwarding yield to the pool booster\n * @param _boosterType PoolBoosterType the type of the pool booster\n */\n function emitPoolBoosterCreated(\n address _poolBoosterAddress,\n address _ammPoolAddress,\n PoolBoosterType _boosterType\n ) external onlyApprovedFactories {\n emit PoolBoosterCreated(\n _poolBoosterAddress,\n _ammPoolAddress,\n _boosterType,\n msg.sender // address of the factory\n );\n }\n\n /**\n * @notice Emits a pool booster removed event\n * @dev This has been created as a convenience method for the monitoring to have\n * an index of all of the removed pool boosters by only listening to the\n * events of this contract.\n * @param _poolBoosterAddress address of the pool booster to be removed\n */\n function emitPoolBoosterRemoved(address _poolBoosterAddress)\n external\n onlyApprovedFactories\n {\n emit PoolBoosterRemoved(_poolBoosterAddress);\n }\n\n /**\n * @notice Returns true if the factory is approved\n * @param _factoryAddress address of the factory\n */\n function isApprovedFactory(address _factoryAddress)\n public\n view\n returns (bool)\n {\n uint256 length = factories.length;\n for (uint256 i = 0; i < length; i++) {\n if (factories[i] == _factoryAddress) {\n return true;\n }\n }\n return false;\n }\n\n /**\n * @notice Returns all supported factories\n */\n function getAllFactories() external view returns (address[] memory) {\n return factories;\n }\n}\n" + }, + "contracts/poolBooster/PoolBoosterFactoryMerkl.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { PoolBoosterMerkl } from \"./PoolBoosterMerkl.sol\";\nimport { AbstractPoolBoosterFactory, IPoolBoostCentralRegistry } from \"./AbstractPoolBoosterFactory.sol\";\n\n/**\n * @title Pool booster factory for creating Merkl pool boosters.\n * @author Origin Protocol Inc\n */\ncontract PoolBoosterFactoryMerkl is AbstractPoolBoosterFactory {\n uint256 public constant version = 1;\n\n /// @notice address of the Merkl distributor\n address public merklDistributor;\n\n /// @notice event emitted when the Merkl distributor is updated\n event MerklDistributorUpdated(address newDistributor);\n\n /**\n * @param _oToken address of the OToken token\n * @param _governor address governor\n * @param _centralRegistry address of the central registry\n * @param _merklDistributor address of the Merkl distributor\n */\n constructor(\n address _oToken,\n address _governor,\n address _centralRegistry,\n address _merklDistributor\n ) AbstractPoolBoosterFactory(_oToken, _governor, _centralRegistry) {\n _setMerklDistributor(_merklDistributor);\n }\n\n /**\n * @dev Create a Pool Booster for Merkl.\n * @param _campaignType The type of campaign to create. This is used to determine the type of\n * bribe contract to create. The type is defined in the MerklDistributor contract.\n * @param _ammPoolAddress address of the AMM pool where the yield originates from\n * @param _campaignDuration The duration of the campaign in seconds\n * @param campaignData The data to be used for the campaign. This is used to determine the type of\n * bribe contract to create. The type is defined in the MerklDistributor contract.\n * This should be fetched from the Merkl UI.\n * @param _salt A unique number that affects the address of the pool booster created. Note: this number\n * should match the one from `computePoolBoosterAddress` in order for the final deployed address\n * and pre-computed address to match\n */\n function createPoolBoosterMerkl(\n uint32 _campaignType,\n address _ammPoolAddress,\n uint32 _campaignDuration,\n bytes calldata campaignData,\n uint256 _salt\n ) external onlyGovernor {\n require(\n _ammPoolAddress != address(0),\n \"Invalid ammPoolAddress address\"\n );\n require(_salt > 0, \"Invalid salt\");\n require(_campaignDuration > 1 hours, \"Invalid campaign duration\");\n require(campaignData.length > 0, \"Invalid campaign data\");\n\n address poolBoosterAddress = _deployContract(\n abi.encodePacked(\n type(PoolBoosterMerkl).creationCode,\n abi.encode(\n oToken,\n merklDistributor,\n _campaignDuration,\n _campaignType,\n governor(),\n campaignData\n )\n ),\n _salt\n );\n\n _storePoolBoosterEntry(\n poolBoosterAddress,\n _ammPoolAddress,\n IPoolBoostCentralRegistry.PoolBoosterType.MerklBooster\n );\n }\n\n /**\n * @dev Create a Pool Booster for Merkl.\n * @param _campaignType The type of campaign to create. This is used to determine the type of\n * bribe contract to create. The type is defined in the MerklDistributor contract.\n * @param _ammPoolAddress address of the AMM pool where the yield originates from\n * @param _salt A unique number that affects the address of the pool booster created. Note: this number\n * should match the one from `createPoolBoosterMerkl` in order for the final deployed address\n * and pre-computed address to match\n */\n function computePoolBoosterAddress(\n uint32 _campaignType,\n address _ammPoolAddress,\n uint32 _campaignDuration,\n bytes calldata campaignData,\n uint256 _salt\n ) external view returns (address) {\n require(\n _ammPoolAddress != address(0),\n \"Invalid ammPoolAddress address\"\n );\n require(_salt > 0, \"Invalid salt\");\n require(_campaignDuration > 1 hours, \"Invalid campaign duration\");\n require(campaignData.length > 0, \"Invalid campaign data\");\n\n return\n _computeAddress(\n abi.encodePacked(\n type(PoolBoosterMerkl).creationCode,\n abi.encode(\n oToken,\n merklDistributor,\n _campaignDuration,\n _campaignType,\n governor(),\n campaignData\n )\n ),\n _salt\n );\n }\n\n /**\n * @dev Set the address of the Merkl distributor\n * @param _merklDistributor The address of the Merkl distributor\n */\n function setMerklDistributor(address _merklDistributor)\n external\n onlyGovernor\n {\n _setMerklDistributor(_merklDistributor);\n }\n\n function _setMerklDistributor(address _merklDistributor) internal {\n require(\n _merklDistributor != address(0),\n \"Invalid merklDistributor address\"\n );\n merklDistributor = _merklDistributor;\n emit MerklDistributorUpdated(_merklDistributor);\n }\n}\n" + }, + "contracts/poolBooster/PoolBoosterFactoryMetropolis.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { PoolBoosterMetropolis } from \"./PoolBoosterMetropolis.sol\";\nimport { AbstractPoolBoosterFactory, IPoolBoostCentralRegistry } from \"./AbstractPoolBoosterFactory.sol\";\n\n/**\n * @title Pool booster factory for creating Metropolis pool boosters.\n * @author Origin Protocol Inc\n */\ncontract PoolBoosterFactoryMetropolis is AbstractPoolBoosterFactory {\n uint256 public constant version = 1;\n address public immutable rewardFactory;\n address public immutable voter;\n\n // @param address _oToken address of the OToken token\n // @param address _governor address governor\n // @param address _centralRegistry address of the central registry\n // @param address _rewardFactory address of the Metropolis reward factory\n // @param address _voter address of the Metropolis voter\n constructor(\n address _oToken,\n address _governor,\n address _centralRegistry,\n address _rewardFactory,\n address _voter\n ) AbstractPoolBoosterFactory(_oToken, _governor, _centralRegistry) {\n rewardFactory = _rewardFactory;\n voter = _voter;\n }\n\n /**\n * @dev Create a Pool Booster for Metropolis pool.\n * @param _ammPoolAddress address of the AMM pool where the yield originates from\n * @param _salt A unique number that affects the address of the pool booster created. Note: this number\n * should match the one from `computePoolBoosterAddress` in order for the final deployed address\n * and pre-computed address to match\n */\n function createPoolBoosterMetropolis(address _ammPoolAddress, uint256 _salt)\n external\n onlyGovernor\n {\n require(\n _ammPoolAddress != address(0),\n \"Invalid ammPoolAddress address\"\n );\n require(_salt > 0, \"Invalid salt\");\n\n address poolBoosterAddress = _deployContract(\n abi.encodePacked(\n type(PoolBoosterMetropolis).creationCode,\n abi.encode(oToken, rewardFactory, _ammPoolAddress, voter)\n ),\n _salt\n );\n\n _storePoolBoosterEntry(\n poolBoosterAddress,\n _ammPoolAddress,\n IPoolBoostCentralRegistry.PoolBoosterType.MetropolisBooster\n );\n }\n\n /**\n * @dev Create a Pool Booster for Metropolis pool.\n * @param _ammPoolAddress address of the AMM pool where the yield originates from\n * @param _salt A unique number that affects the address of the pool booster created. Note: this number\n * should match the one from `createPoolBoosterMetropolis` in order for the final deployed address\n * and pre-computed address to match\n */\n function computePoolBoosterAddress(address _ammPoolAddress, uint256 _salt)\n external\n view\n returns (address)\n {\n require(\n _ammPoolAddress != address(0),\n \"Invalid ammPoolAddress address\"\n );\n require(_salt > 0, \"Invalid salt\");\n\n return\n _computeAddress(\n abi.encodePacked(\n type(PoolBoosterMetropolis).creationCode,\n abi.encode(oToken, rewardFactory, _ammPoolAddress, voter)\n ),\n _salt\n );\n }\n}\n" + }, + "contracts/poolBooster/PoolBoosterFactorySwapxDouble.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { PoolBoosterSwapxDouble } from \"./PoolBoosterSwapxDouble.sol\";\nimport { AbstractPoolBoosterFactory, IPoolBoostCentralRegistry } from \"./AbstractPoolBoosterFactory.sol\";\n\n/**\n * @title Pool booster factory for creating Swapx Ichi pool boosters where both of the\n * gauges need incentivizing.\n * @author Origin Protocol Inc\n */\ncontract PoolBoosterFactorySwapxDouble is AbstractPoolBoosterFactory {\n uint256 public constant version = 1;\n\n // @param address _oToken address of the OToken token\n // @param address _governor address governor\n // @param address _centralRegistry address of the central registry\n constructor(\n address _oToken,\n address _governor,\n address _centralRegistry\n ) AbstractPoolBoosterFactory(_oToken, _governor, _centralRegistry) {}\n\n /**\n * @dev Create a Pool Booster for SwapX Ichi vault based pool where 2 Bribe contracts need to be\n * bribed\n * @param _bribeAddressOS address of the Bribes.sol(Bribe) contract for the OS token side\n * @param _bribeAddressOther address of the Bribes.sol(Bribe) contract for the other token in the pool\n * @param _ammPoolAddress address of the AMM pool where the yield originates from\n * @param _split 1e18 denominated split between OS and Other bribe. E.g. 0.4e17 means 40% to OS\n * bribe contract and 60% to other bribe contract\n * @param _salt A unique number that affects the address of the pool booster created. Note: this number\n * should match the one from `computePoolBoosterAddress` in order for the final deployed address\n * and pre-computed address to match\n */\n function createPoolBoosterSwapxDouble(\n address _bribeAddressOS,\n address _bribeAddressOther,\n address _ammPoolAddress,\n uint256 _split,\n uint256 _salt\n ) external onlyGovernor {\n require(\n _ammPoolAddress != address(0),\n \"Invalid ammPoolAddress address\"\n );\n require(_salt > 0, \"Invalid salt\");\n\n address poolBoosterAddress = _deployContract(\n abi.encodePacked(\n type(PoolBoosterSwapxDouble).creationCode,\n abi.encode(_bribeAddressOS, _bribeAddressOther, oToken, _split)\n ),\n _salt\n );\n\n _storePoolBoosterEntry(\n poolBoosterAddress,\n _ammPoolAddress,\n IPoolBoostCentralRegistry.PoolBoosterType.SwapXDoubleBooster\n );\n }\n\n /**\n * @dev Compute the address of the pool booster to be deployed.\n * @param _bribeAddressOS address of the Bribes.sol(Bribe) contract for the OS token side\n * @param _bribeAddressOther address of the Bribes.sol(Bribe) contract for the other token in the pool\n * @param _ammPoolAddress address of the AMM pool where the yield originates from\n * @param _split 1e18 denominated split between OS and Other bribe. E.g. 0.4e17 means 40% to OS\n * bribe contract and 60% to other bribe contract\n * @param _salt A unique number that affects the address of the pool booster created. Note: this number\n * should match the one from `createPoolBoosterSwapxDouble` in order for the final deployed address\n * and pre-computed address to match\n */\n function computePoolBoosterAddress(\n address _bribeAddressOS,\n address _bribeAddressOther,\n address _ammPoolAddress,\n uint256 _split,\n uint256 _salt\n ) external view returns (address) {\n require(\n _ammPoolAddress != address(0),\n \"Invalid ammPoolAddress address\"\n );\n require(_salt > 0, \"Invalid salt\");\n\n return\n _computeAddress(\n abi.encodePacked(\n type(PoolBoosterSwapxDouble).creationCode,\n abi.encode(\n _bribeAddressOS,\n _bribeAddressOther,\n oToken,\n _split\n )\n ),\n _salt\n );\n }\n}\n" + }, + "contracts/poolBooster/PoolBoosterFactorySwapxSingle.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { PoolBoosterSwapxSingle } from \"./PoolBoosterSwapxSingle.sol\";\nimport { AbstractPoolBoosterFactory, IPoolBoostCentralRegistry } from \"./AbstractPoolBoosterFactory.sol\";\n\n/**\n * @title Pool booster factory for creating Swapx Single pool boosters - where a single\n * gauge is created for a pool. this is appropriate for Classic Stable & Classic\n * Volatile SwapX pools.\n * @author Origin Protocol Inc\n */\ncontract PoolBoosterFactorySwapxSingle is AbstractPoolBoosterFactory {\n uint256 public constant version = 1;\n\n // @param address _oToken address of the OToken token\n // @param address _governor address governor\n // @param address _centralRegistry address of the central registry\n constructor(\n address _oToken,\n address _governor,\n address _centralRegistry\n ) AbstractPoolBoosterFactory(_oToken, _governor, _centralRegistry) {}\n\n /**\n * @dev Create a Pool Booster for SwapX classic volatile or classic stable pools where\n * a single Bribe contract is incentivized.\n * @param _bribeAddress address of the Bribes.sol contract\n * @param _ammPoolAddress address of the AMM pool where the yield originates from\n * @param _salt A unique number that affects the address of the pool booster created. Note: this number\n * should match the one from `computePoolBoosterAddress` in order for the final deployed address\n * and pre-computed address to match\n */\n function createPoolBoosterSwapxSingle(\n address _bribeAddress,\n address _ammPoolAddress,\n uint256 _salt\n ) external onlyGovernor {\n require(\n _ammPoolAddress != address(0),\n \"Invalid ammPoolAddress address\"\n );\n require(_salt > 0, \"Invalid salt\");\n\n address poolBoosterAddress = _deployContract(\n abi.encodePacked(\n type(PoolBoosterSwapxSingle).creationCode,\n abi.encode(_bribeAddress, oToken)\n ),\n _salt\n );\n\n _storePoolBoosterEntry(\n poolBoosterAddress,\n _ammPoolAddress,\n IPoolBoostCentralRegistry.PoolBoosterType.SwapXSingleBooster\n );\n }\n\n /**\n * @dev Create a Pool Booster for SwapX classic volatile or classic stable pools where\n * a single Bribe contract is incentivized.\n * @param _bribeAddress address of the Bribes.sol contract\n * @param _ammPoolAddress address of the AMM pool where the yield originates from\n * @param _salt A unique number that affects the address of the pool booster created. Note: this number\n * should match the one from `createPoolBoosterSwapxSingle` in order for the final deployed address\n * and pre-computed address to match\n */\n function computePoolBoosterAddress(\n address _bribeAddress,\n address _ammPoolAddress,\n uint256 _salt\n ) external view returns (address) {\n require(\n _ammPoolAddress != address(0),\n \"Invalid ammPoolAddress address\"\n );\n require(_salt > 0, \"Invalid salt\");\n\n return\n _computeAddress(\n abi.encodePacked(\n type(PoolBoosterSwapxSingle).creationCode,\n abi.encode(_bribeAddress, oToken)\n ),\n _salt\n );\n }\n}\n" + }, + "contracts/poolBooster/PoolBoosterMerkl.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport { IPoolBooster } from \"../interfaces/poolBooster/IPoolBooster.sol\";\nimport { IMerklDistributor } from \"../interfaces/poolBooster/IMerklDistributor.sol\";\n\ninterface IERC1271 {\n /**\n * @dev Should return whether the signature provided is valid for the provided data\n * @param hash Hash of the data to be signed\n * @param signature Signature byte array associated with _data\n */\n function isValidSignature(bytes32 hash, bytes memory signature)\n external\n view\n returns (bytes4 magicValue);\n}\n\n/**\n * @title Pool booster for Merkl distributor\n * @author Origin Protocol Inc\n */\ncontract PoolBoosterMerkl is IPoolBooster, IERC1271 {\n /// @notice address of merkl distributor\n IMerklDistributor public immutable merklDistributor;\n /// @notice address of the OS token\n IERC20 public immutable rewardToken;\n /// @notice if balance under this amount the bribe action is skipped\n uint256 public constant MIN_BRIBE_AMOUNT = 1e10;\n /// @notice Campaign duration in seconds\n uint32 public immutable duration; // -> should be immutable\n /// @notice Campaign type\n uint32 public immutable campaignType;\n /// @notice Owner of the campaign\n address public immutable creator;\n /// @notice Campaign data\n bytes public campaignData;\n\n constructor(\n address _rewardToken,\n address _merklDistributor,\n uint32 _duration,\n uint32 _campaignType,\n address _creator,\n bytes memory _campaignData\n ) {\n require(_rewardToken != address(0), \"Invalid rewardToken address\");\n require(\n _merklDistributor != address(0),\n \"Invalid merklDistributor address\"\n );\n require(_campaignData.length > 0, \"Invalid campaignData\");\n require(_duration > 1 hours, \"Invalid duration\");\n\n campaignType = _campaignType;\n duration = _duration;\n creator = _creator;\n\n merklDistributor = IMerklDistributor(_merklDistributor);\n rewardToken = IERC20(_rewardToken);\n campaignData = _campaignData;\n }\n\n /// @notice Create a campaign on the Merkl distributor\n function bribe() external override {\n // Ensure token is approved for the Merkl distributor\n uint256 minAmount = merklDistributor.rewardTokenMinAmounts(\n address(rewardToken)\n );\n require(minAmount > 0, \"Min reward amount must be > 0\");\n\n // if balance too small or below threshold, do no bribes\n uint256 balance = rewardToken.balanceOf(address(this));\n if (\n balance < MIN_BRIBE_AMOUNT ||\n (balance * 1 hours < minAmount * duration)\n ) {\n return;\n }\n\n // Approve the bribe contract to spend the reward token\n rewardToken.approve(address(merklDistributor), balance);\n\n // Notify the bribe contract of the reward amount\n merklDistributor.signAndCreateCampaign(\n IMerklDistributor.CampaignParameters({\n campaignId: bytes32(0),\n creator: creator,\n rewardToken: address(rewardToken),\n amount: balance,\n campaignType: campaignType,\n startTimestamp: getNextPeriodStartTime(),\n duration: duration,\n campaignData: campaignData\n }),\n bytes(\"\")\n );\n emit BribeExecuted(balance);\n }\n\n /// @notice Used to sign a campaign on the Merkl distributor\n function isValidSignature(bytes32, bytes memory)\n external\n view\n override\n returns (bytes4 magicValue)\n {\n require(msg.sender == address(merklDistributor), \"Invalid sender\");\n // bytes4(keccak256(\"isValidSignature(bytes32,bytes)\")) == 0x1626ba7e\n return bytes4(0x1626ba7e);\n }\n\n /// @notice Returns the timestamp for the start of the next period based on the configured duration\n function getNextPeriodStartTime() public view returns (uint32) {\n // Calculate the timestamp for the next period boundary\n return uint32((block.timestamp / duration + 1) * duration);\n }\n}\n" + }, + "contracts/poolBooster/PoolBoosterMetropolis.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { IPoolBooster } from \"../interfaces/poolBooster/IPoolBooster.sol\";\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\n\n/**\n * @title Pool booster for Metropolis pools\n * @author Origin Protocol Inc\n */\ncontract PoolBoosterMetropolis is IPoolBooster {\n // @notice address of the OS token\n IERC20 public immutable osToken;\n // @notice address of the pool\n address public immutable pool;\n // @notice if balance under this amount the bribe action is skipped\n uint256 public constant MIN_BRIBE_AMOUNT = 1e10;\n\n IRewarderFactory public immutable rewardFactory;\n\n IVoter public immutable voter;\n\n constructor(\n address _osToken,\n address _rewardFactory,\n address _pool,\n address _voter\n ) {\n require(_pool != address(0), \"Invalid pool address\");\n pool = _pool;\n // Abstract factory already validates this is not a zero address\n osToken = IERC20(_osToken);\n\n rewardFactory = IRewarderFactory(_rewardFactory);\n\n voter = IVoter(_voter);\n }\n\n function bribe() external override {\n uint256 balance = osToken.balanceOf(address(this));\n // balance too small, do no bribes\n (, uint256 minBribeAmount) = rewardFactory.getWhitelistedTokenInfo(\n address(osToken)\n );\n if (balance < MIN_BRIBE_AMOUNT || balance < minBribeAmount) {\n return;\n }\n\n uint256 id = voter.getCurrentVotingPeriod() + 1;\n\n // Deploy a rewarder\n IRewarder rewarder = IRewarder(\n rewardFactory.createBribeRewarder(address(osToken), pool)\n );\n\n // Approve the rewarder to spend the balance\n osToken.approve(address(rewarder), balance);\n\n // Fund and bribe the rewarder\n rewarder.fundAndBribe(id, id, balance);\n\n emit BribeExecuted(balance);\n }\n}\n\ninterface IRewarderFactory {\n function createBribeRewarder(address token, address pool)\n external\n returns (address rewarder);\n\n function getWhitelistedTokenInfo(address token)\n external\n view\n returns (bool isWhitelisted, uint256 minBribeAmount);\n}\n\ninterface IRewarder {\n function fundAndBribe(\n uint256 startId,\n uint256 lastId,\n uint256 amountPerPeriod\n ) external payable;\n}\n\ninterface IVoter {\n function getCurrentVotingPeriod() external view returns (uint256);\n}\n" + }, + "contracts/poolBooster/PoolBoosterSwapxDouble.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { IBribe } from \"../interfaces/poolBooster/ISwapXAlgebraBribe.sol\";\nimport { IPoolBooster } from \"../interfaces/poolBooster/IPoolBooster.sol\";\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport { StableMath } from \"../utils/StableMath.sol\";\n\n/**\n * @title Pool booster for SwapX concentrated liquidity where 2 gauges are created for\n * every pool. Ichi vaults currently have such setup.\n * @author Origin Protocol Inc\n */\ncontract PoolBoosterSwapxDouble is IPoolBooster {\n using StableMath for uint256;\n\n // @notice address of the Bribes.sol(Bribe) contract for the OS token side\n IBribe public immutable bribeContractOS;\n // @notice address of the Bribes.sol(Bribe) contract for the other token in the pool\n IBribe public immutable bribeContractOther;\n // @notice address of the OS token\n IERC20 public immutable osToken;\n // @notice 1e18 denominated split between OS and Other bribe. E.g. 0.4e17 means 40% to OS\n // bribe contract and 60% to other bribe contract\n uint256 public immutable split;\n\n // @notice if balance under this amount the bribe action is skipped\n uint256 public constant MIN_BRIBE_AMOUNT = 1e10;\n\n constructor(\n address _bribeContractOS,\n address _bribeContractOther,\n address _osToken,\n uint256 _split\n ) {\n require(\n _bribeContractOS != address(0),\n \"Invalid bribeContractOS address\"\n );\n require(\n _bribeContractOther != address(0),\n \"Invalid bribeContractOther address\"\n );\n // expect it to be between 1% & 99%\n require(_split > 1e16 && _split < 99e16, \"Unexpected split amount\");\n\n bribeContractOS = IBribe(_bribeContractOS);\n bribeContractOther = IBribe(_bribeContractOther);\n // Abstract factory already validates this is not a zero address\n osToken = IERC20(_osToken);\n split = _split;\n }\n\n function bribe() external override {\n uint256 balance = osToken.balanceOf(address(this));\n // balance too small, do no bribes\n if (balance < MIN_BRIBE_AMOUNT) {\n return;\n }\n\n uint256 osBribeAmount = balance.mulTruncate(split);\n uint256 otherBribeAmount = balance - osBribeAmount;\n\n osToken.approve(address(bribeContractOS), osBribeAmount);\n osToken.approve(address(bribeContractOther), otherBribeAmount);\n\n bribeContractOS.notifyRewardAmount(address(osToken), osBribeAmount);\n bribeContractOther.notifyRewardAmount(\n address(osToken),\n otherBribeAmount\n );\n\n emit BribeExecuted(balance);\n }\n}\n" + }, + "contracts/poolBooster/PoolBoosterSwapxSingle.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { IBribe } from \"../interfaces/poolBooster/ISwapXAlgebraBribe.sol\";\nimport { IPoolBooster } from \"../interfaces/poolBooster/IPoolBooster.sol\";\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\n\n/**\n * @title Pool booster for SwapX for Classic Stable Pools and Classic Volatile Pools\n * @author Origin Protocol Inc\n */\ncontract PoolBoosterSwapxSingle is IPoolBooster {\n // @notice address of the Bribes.sol(Bribe) contract\n IBribe public immutable bribeContract;\n // @notice address of the OS token\n IERC20 public immutable osToken;\n // @notice if balance under this amount the bribe action is skipped\n uint256 public constant MIN_BRIBE_AMOUNT = 1e10;\n\n constructor(address _bribeContract, address _osToken) {\n require(_bribeContract != address(0), \"Invalid bribeContract address\");\n bribeContract = IBribe(_bribeContract);\n // Abstract factory already validates this is not a zero address\n osToken = IERC20(_osToken);\n }\n\n function bribe() external override {\n uint256 balance = osToken.balanceOf(address(this));\n // balance too small, do no bribes\n if (balance < MIN_BRIBE_AMOUNT) {\n return;\n }\n\n osToken.approve(address(bribeContract), balance);\n\n bribeContract.notifyRewardAmount(address(osToken), balance);\n emit BribeExecuted(balance);\n }\n}\n" + }, + "contracts/proxies/create2/CrossChainStrategyProxy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { InitializeGovernedUpgradeabilityProxy2 } from \"../InitializeGovernedUpgradeabilityProxy2.sol\";\n\n// ********************************************************\n// ********************************************************\n// IMPORTANT: DO NOT CHANGE ANYTHING IN THIS FILE.\n// Any changes to this file (even whitespaces) will\n// affect the create2 address of the proxy\n// ********************************************************\n// ********************************************************\n\n/**\n * @notice CrossChainStrategyProxy delegates calls to a\n * CrossChainMasterStrategy or CrossChainRemoteStrategy\n * implementation contract.\n */\ncontract CrossChainStrategyProxy is InitializeGovernedUpgradeabilityProxy2 {\n constructor(address governor)\n InitializeGovernedUpgradeabilityProxy2(governor)\n {}\n}\n" + }, + "contracts/proxies/InitializeGovernedUpgradeabilityProxy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { Address } from \"@openzeppelin/contracts/utils/Address.sol\";\n\nimport { Governable } from \"../governance/Governable.sol\";\n\n/**\n * @title BaseGovernedUpgradeabilityProxy\n * @dev This contract combines an upgradeability proxy with our governor system.\n * It is based on an older version of OpenZeppelins BaseUpgradeabilityProxy\n * with Solidity ^0.8.0.\n * @author Origin Protocol Inc\n */\ncontract InitializeGovernedUpgradeabilityProxy is Governable {\n /**\n * @dev Emitted when the implementation is upgraded.\n * @param implementation Address of the new implementation.\n */\n event Upgraded(address indexed implementation);\n\n constructor() {\n _setGovernor(msg.sender);\n }\n\n /**\n * @dev Contract initializer with Governor enforcement\n * @param _logic Address of the initial implementation.\n * @param _initGovernor Address of the initial Governor.\n * @param _data Data to send as msg.data to the implementation to initialize\n * the proxied contract.\n * It should include the signature and the parameters of the function to be\n * called, as described in\n * https://solidity.readthedocs.io/en/v0.4.24/abi-spec.html#function-selector-and-argument-encoding.\n * This parameter is optional, if no data is given the initialization call\n * to proxied contract will be skipped.\n */\n function initialize(\n address _logic,\n address _initGovernor,\n bytes calldata _data\n ) public payable onlyGovernor {\n require(_implementation() == address(0));\n require(_logic != address(0), \"Implementation not set\");\n assert(\n IMPLEMENTATION_SLOT ==\n bytes32(uint256(keccak256(\"eip1967.proxy.implementation\")) - 1)\n );\n _setImplementation(_logic);\n if (_data.length > 0) {\n (bool success, ) = _logic.delegatecall(_data);\n require(success);\n }\n _changeGovernor(_initGovernor);\n }\n\n /**\n * @return The address of the proxy admin/it's also the governor.\n */\n function admin() external view returns (address) {\n return _governor();\n }\n\n /**\n * @return The address of the implementation.\n */\n function implementation() external view returns (address) {\n return _implementation();\n }\n\n /**\n * @dev Upgrade the backing implementation of the proxy.\n * Only the admin can call this function.\n * @param _newImplementation Address of the new implementation.\n */\n function upgradeTo(address _newImplementation) external onlyGovernor {\n _upgradeTo(_newImplementation);\n }\n\n /**\n * @dev Upgrade the backing implementation of the proxy and call a function\n * on the new implementation.\n * This is useful to initialize the proxied contract.\n * @param newImplementation Address of the new implementation.\n * @param data Data to send as msg.data in the low level call.\n * It should include the signature and the parameters of the function to be called, as described in\n * https://solidity.readthedocs.io/en/v0.4.24/abi-spec.html#function-selector-and-argument-encoding.\n */\n function upgradeToAndCall(address newImplementation, bytes calldata data)\n external\n payable\n onlyGovernor\n {\n _upgradeTo(newImplementation);\n (bool success, ) = newImplementation.delegatecall(data);\n require(success);\n }\n\n /**\n * @dev Fallback function.\n * Implemented entirely in `_fallback`.\n */\n fallback() external payable {\n _fallback();\n }\n\n /**\n * @dev Delegates execution to an implementation contract.\n * This is a low level function that doesn't return to its internal call site.\n * It will return to the external caller whatever the implementation returns.\n * @param _impl Address to delegate.\n */\n function _delegate(address _impl) internal {\n // solhint-disable-next-line no-inline-assembly\n assembly {\n // Copy msg.data. We take full control of memory in this inline assembly\n // block because it will not return to Solidity code. We overwrite the\n // Solidity scratch pad at memory position 0.\n calldatacopy(0, 0, calldatasize())\n\n // Call the implementation.\n // out and outsize are 0 because we don't know the size yet.\n let result := delegatecall(gas(), _impl, 0, calldatasize(), 0, 0)\n\n // Copy the returned data.\n returndatacopy(0, 0, returndatasize())\n\n switch result\n // delegatecall returns 0 on error.\n case 0 {\n revert(0, returndatasize())\n }\n default {\n return(0, returndatasize())\n }\n }\n }\n\n /**\n * @dev Function that is run as the first thing in the fallback function.\n * Can be redefined in derived contracts to add functionality.\n * Redefinitions must call super._willFallback().\n */\n function _willFallback() internal {}\n\n /**\n * @dev fallback implementation.\n * Extracted to enable manual triggering.\n */\n function _fallback() internal {\n _willFallback();\n _delegate(_implementation());\n }\n\n /**\n * @dev Storage slot with the address of the current implementation.\n * This is the keccak-256 hash of \"eip1967.proxy.implementation\" subtracted by 1, and is\n * validated in the constructor.\n */\n bytes32 internal constant IMPLEMENTATION_SLOT =\n 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;\n\n /**\n * @dev Returns the current implementation.\n * @return impl Address of the current implementation\n */\n function _implementation() internal view returns (address impl) {\n bytes32 slot = IMPLEMENTATION_SLOT;\n // solhint-disable-next-line no-inline-assembly\n assembly {\n impl := sload(slot)\n }\n }\n\n /**\n * @dev Upgrades the proxy to a new implementation.\n * @param newImplementation Address of the new implementation.\n */\n function _upgradeTo(address newImplementation) internal {\n _setImplementation(newImplementation);\n emit Upgraded(newImplementation);\n }\n\n /**\n * @dev Sets the implementation address of the proxy.\n * @param newImplementation Address of the new implementation.\n */\n function _setImplementation(address newImplementation) internal {\n require(\n Address.isContract(newImplementation),\n \"Cannot set a proxy implementation to a non-contract address\"\n );\n\n bytes32 slot = IMPLEMENTATION_SLOT;\n\n // solhint-disable-next-line no-inline-assembly\n assembly {\n sstore(slot, newImplementation)\n }\n }\n}\n" + }, + "contracts/proxies/InitializeGovernedUpgradeabilityProxy2.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { InitializeGovernedUpgradeabilityProxy } from \"./InitializeGovernedUpgradeabilityProxy.sol\";\n\n/**\n * @title BaseGovernedUpgradeabilityProxy2\n * @dev This is the same as InitializeGovernedUpgradeabilityProxy except that the\n * governor is defined in the constructor.\n * @author Origin Protocol Inc\n */\ncontract InitializeGovernedUpgradeabilityProxy2 is\n InitializeGovernedUpgradeabilityProxy\n{\n /**\n * This is used when the msg.sender can not be the governor. E.g. when the proxy is\n * deployed via CreateX\n */\n constructor(address governor) InitializeGovernedUpgradeabilityProxy() {\n _setGovernor(governor);\n }\n}\n" + }, + "contracts/proxies/Proxies.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { InitializeGovernedUpgradeabilityProxy } from \"./InitializeGovernedUpgradeabilityProxy.sol\";\nimport { InitializeGovernedUpgradeabilityProxy2 } from \"./InitializeGovernedUpgradeabilityProxy2.sol\";\n\n/**\n * @notice OUSDProxy delegates calls to an OUSD implementation\n */\ncontract OUSDProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice WrappedOUSDProxy delegates calls to a WrappedOUSD implementation\n */\ncontract WrappedOUSDProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice VaultProxy delegates calls to a Vault implementation\n */\ncontract VaultProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice CompoundStrategyProxy delegates calls to a CompoundStrategy implementation\n */\ncontract CompoundStrategyProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice AaveStrategyProxy delegates calls to a AaveStrategy implementation\n */\ncontract AaveStrategyProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice ConvexStrategyProxy delegates calls to a ConvexStrategy implementation\n */\ncontract ConvexStrategyProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice HarvesterProxy delegates calls to a Harvester implementation\n */\ncontract HarvesterProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice DripperProxy delegates calls to a Dripper implementation\n */\ncontract DripperProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice MorphoCompoundStrategyProxy delegates calls to a MorphoCompoundStrategy implementation\n */\ncontract MorphoCompoundStrategyProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice ConvexOUSDMetaStrategyProxy delegates calls to a ConvexOUSDMetaStrategy implementation\n */\ncontract ConvexOUSDMetaStrategyProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice MorphoAaveStrategyProxy delegates calls to a MorphoCompoundStrategy implementation\n */\ncontract MorphoAaveStrategyProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice OETHProxy delegates calls to nowhere for now\n */\ncontract OETHProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice WOETHProxy delegates calls to nowhere for now\n */\ncontract WOETHProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice OETHVaultProxy delegates calls to a Vault implementation\n */\ncontract OETHVaultProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice OETHDripperProxy delegates calls to a OETHDripper implementation\n */\ncontract OETHDripperProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice OETHHarvesterProxy delegates calls to a Harvester implementation\n */\ncontract OETHHarvesterProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice CurveEthStrategyProxy delegates calls to a CurveEthStrategy implementation\n */\ncontract ConvexEthMetaStrategyProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice BuybackProxy delegates calls to Buyback implementation\n */\ncontract BuybackProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice OETHMorphoAaveStrategyProxy delegates calls to a MorphoAaveStrategy implementation\n */\ncontract OETHMorphoAaveStrategyProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice OETHBalancerMetaPoolrEthStrategyProxy delegates calls to a BalancerMetaPoolStrategy implementation\n */\ncontract OETHBalancerMetaPoolrEthStrategyProxy is\n InitializeGovernedUpgradeabilityProxy\n{\n\n}\n\n/**\n * @notice OETHBalancerMetaPoolwstEthStrategyProxy delegates calls to a BalancerMetaPoolStrategy implementation\n */\ncontract OETHBalancerMetaPoolwstEthStrategyProxy is\n InitializeGovernedUpgradeabilityProxy\n{\n\n}\n\n/**\n * @notice MakerDsrStrategyProxy delegates calls to a Generalized4626Strategy implementation\n */\ncontract MakerDsrStrategyProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice OETHBuybackProxy delegates calls to Buyback implementation\n */\ncontract OETHBuybackProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice BridgedWOETHProxy delegates calls to BridgedWOETH implementation\n */\ncontract BridgedWOETHProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice NativeStakingSSVStrategyProxy delegates calls to NativeStakingSSVStrategy implementation\n */\ncontract NativeStakingSSVStrategyProxy is\n InitializeGovernedUpgradeabilityProxy\n{\n\n}\n\n/**\n * @notice NativeStakingFeeAccumulatorProxy delegates calls to FeeAccumulator implementation\n */\ncontract NativeStakingFeeAccumulatorProxy is\n InitializeGovernedUpgradeabilityProxy\n{\n\n}\n\n/**\n * @notice NativeStakingSSVStrategy2Proxy delegates calls to NativeStakingSSVStrategy implementation\n */\ncontract NativeStakingSSVStrategy2Proxy is\n InitializeGovernedUpgradeabilityProxy\n{\n\n}\n\n/**\n * @notice NativeStakingFeeAccumulator2Proxy delegates calls to FeeAccumulator implementation\n */\ncontract NativeStakingFeeAccumulator2Proxy is\n InitializeGovernedUpgradeabilityProxy\n{\n\n}\n\n/**\n * @notice NativeStakingSSVStrategy3Proxy delegates calls to NativeStakingSSVStrategy implementation\n */\ncontract NativeStakingSSVStrategy3Proxy is\n InitializeGovernedUpgradeabilityProxy\n{\n\n}\n\n/**\n * @notice NativeStakingFeeAccumulator3Proxy delegates calls to FeeAccumulator implementation\n */\ncontract NativeStakingFeeAccumulator3Proxy is\n InitializeGovernedUpgradeabilityProxy\n{\n\n}\n\n/**\n * @notice MetaMorphoStrategyProxy delegates calls to a Generalized4626Strategy implementation\n */\ncontract MetaMorphoStrategyProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice ARMBuybackProxy delegates calls to Buyback implementation\n */\ncontract ARMBuybackProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice MorphoGauntletPrimeUSDCStrategyProxy delegates calls to a Generalized4626Strategy implementation\n */\ncontract MorphoGauntletPrimeUSDCStrategyProxy is\n InitializeGovernedUpgradeabilityProxy\n{\n\n}\n\n/**\n * @notice MorphoGauntletPrimeUSDTStrategyProxy delegates calls to a Generalized4626USDTStrategy implementation\n */\ncontract MorphoGauntletPrimeUSDTStrategyProxy is\n InitializeGovernedUpgradeabilityProxy\n{\n\n}\n\n/**\n * @notice CurvePoolBoosterProxy delegates calls to a CurvePoolBooster implementation\n */\ncontract CurvePoolBoosterProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice OETHFixedRateDripperProxy delegates calls to a OETHFixedRateDripper implementation\n */\ncontract OETHFixedRateDripperProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice OETHSimpleHarvesterProxy delegates calls to a OETHSimpleHarvester implementation\n */\ncontract OETHSimpleHarvesterProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice PoolBoostCentralRegistryProxy delegates calls to the PoolBoostCentralRegistry implementation\n */\ncontract PoolBoostCentralRegistryProxy is\n InitializeGovernedUpgradeabilityProxy\n{\n\n}\n\n/**\n * @notice MakerSSRStrategyProxy delegates calls to a Generalized4626Strategy implementation\n */\ncontract MakerSSRStrategyProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice OUSDCurveAMOProxy delegates calls to a CurveAMOStrategy implementation\n */\ncontract OUSDCurveAMOProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice OETHCurveAMOProxy delegates calls to a CurveAMOStrategy implementation\n */\ncontract OETHCurveAMOProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice CompoundingStakingSSVStrategyProxy delegates calls to a CompoundingStakingSSVStrategy implementation\n */\ncontract CompoundingStakingSSVStrategyProxy is\n InitializeGovernedUpgradeabilityProxy\n{\n\n}\n\n/**\n * @notice OUSDMorphoV2StrategyProxy delegates calls to a Generalized4626Strategy implementation\n */\ncontract OUSDMorphoV2StrategyProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n" + }, + "contracts/strategies/AaveStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title OUSD Aave Strategy\n * @notice Investment strategy for investing stablecoins via Aave\n * @author Origin Protocol Inc\n */\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\n\nimport \"./IAave.sol\";\nimport { IERC20, InitializableAbstractStrategy } from \"../utils/InitializableAbstractStrategy.sol\";\n\nimport { IAaveStakedToken } from \"./IAaveStakeToken.sol\";\nimport { IAaveIncentivesController } from \"./IAaveIncentivesController.sol\";\n\ncontract AaveStrategy is InitializableAbstractStrategy {\n using SafeERC20 for IERC20;\n\n uint16 constant referralCode = 92;\n\n IAaveIncentivesController public incentivesController;\n IAaveStakedToken public stkAave;\n\n /**\n * @param _stratConfig The platform and OToken vault addresses\n */\n constructor(BaseStrategyConfig memory _stratConfig)\n InitializableAbstractStrategy(_stratConfig)\n {}\n\n /**\n * Initializer for setting up strategy internal state. This overrides the\n * InitializableAbstractStrategy initializer as AAVE needs several extra\n * addresses for the rewards program.\n * @param _rewardTokenAddresses Address of the AAVE token\n * @param _assets Addresses of supported assets\n * @param _pTokens Platform Token corresponding addresses\n * @param _incentivesAddress Address of the AAVE incentives controller\n * @param _stkAaveAddress Address of the stkAave contract\n */\n function initialize(\n address[] calldata _rewardTokenAddresses, // AAVE\n address[] calldata _assets,\n address[] calldata _pTokens,\n address _incentivesAddress,\n address _stkAaveAddress\n ) external onlyGovernor initializer {\n incentivesController = IAaveIncentivesController(_incentivesAddress);\n stkAave = IAaveStakedToken(_stkAaveAddress);\n InitializableAbstractStrategy._initialize(\n _rewardTokenAddresses,\n _assets,\n _pTokens\n );\n }\n\n /**\n * @dev Deposit asset into Aave\n * @param _asset Address of asset to deposit\n * @param _amount Amount of asset to deposit\n */\n function deposit(address _asset, uint256 _amount)\n external\n override\n onlyVault\n nonReentrant\n {\n _deposit(_asset, _amount);\n }\n\n /**\n * @dev Deposit asset into Aave\n * @param _asset Address of asset to deposit\n * @param _amount Amount of asset to deposit\n */\n function _deposit(address _asset, uint256 _amount) internal {\n require(_amount > 0, \"Must deposit something\");\n // Following line also doubles as a check that we are depositing\n // an asset that we support.\n emit Deposit(_asset, _getATokenFor(_asset), _amount);\n _getLendingPool().deposit(_asset, _amount, address(this), referralCode);\n }\n\n /**\n * @dev Deposit the entire balance of any supported asset into Aave\n */\n function depositAll() external override onlyVault nonReentrant {\n for (uint256 i = 0; i < assetsMapped.length; i++) {\n uint256 balance = IERC20(assetsMapped[i]).balanceOf(address(this));\n if (balance > 0) {\n _deposit(assetsMapped[i], balance);\n }\n }\n }\n\n /**\n * @dev Withdraw asset from Aave\n * @param _recipient Address to receive withdrawn asset\n * @param _asset Address of asset to withdraw\n * @param _amount Amount of asset to withdraw\n */\n function withdraw(\n address _recipient,\n address _asset,\n uint256 _amount\n ) external override onlyVault nonReentrant {\n require(_amount > 0, \"Must withdraw something\");\n require(_recipient != address(0), \"Must specify recipient\");\n\n emit Withdrawal(_asset, _getATokenFor(_asset), _amount);\n uint256 actual = _getLendingPool().withdraw(\n _asset,\n _amount,\n address(this)\n );\n require(actual == _amount, \"Did not withdraw enough\");\n IERC20(_asset).safeTransfer(_recipient, _amount);\n }\n\n /**\n * @dev Remove all assets from platform and send them to Vault contract.\n */\n function withdrawAll() external override onlyVaultOrGovernor nonReentrant {\n for (uint256 i = 0; i < assetsMapped.length; i++) {\n // Redeem entire balance of aToken\n IERC20 asset = IERC20(assetsMapped[i]);\n address aToken = _getATokenFor(assetsMapped[i]);\n uint256 balance = IERC20(aToken).balanceOf(address(this));\n if (balance > 0) {\n uint256 actual = _getLendingPool().withdraw(\n address(asset),\n balance,\n address(this)\n );\n require(actual == balance, \"Did not withdraw enough\");\n\n uint256 assetBalance = asset.balanceOf(address(this));\n // Transfer entire balance to Vault\n asset.safeTransfer(vaultAddress, assetBalance);\n\n emit Withdrawal(address(asset), aToken, assetBalance);\n }\n }\n }\n\n /**\n * @dev Get the total asset value held in the platform\n * @param _asset Address of the asset\n * @return balance Total value of the asset in the platform\n */\n function checkBalance(address _asset)\n external\n view\n override\n returns (uint256 balance)\n {\n // Balance is always with token aToken decimals\n address aToken = _getATokenFor(_asset);\n balance = IERC20(aToken).balanceOf(address(this));\n }\n\n /**\n * @dev Returns bool indicating whether asset is supported by strategy\n * @param _asset Address of the asset\n */\n function supportsAsset(address _asset) public view override returns (bool) {\n return assetToPToken[_asset] != address(0);\n }\n\n /**\n * @dev Approve the spending of all assets by their corresponding aToken,\n * if for some reason is it necessary.\n */\n function safeApproveAllTokens()\n external\n override\n onlyGovernor\n nonReentrant\n {\n address lendingPool = address(_getLendingPool());\n // approve the pool to spend the Asset\n for (uint256 i = 0; i < assetsMapped.length; i++) {\n address asset = assetsMapped[i];\n // Safe approval\n IERC20(asset).safeApprove(lendingPool, 0);\n IERC20(asset).safeApprove(lendingPool, type(uint256).max);\n }\n }\n\n /**\n * @dev Internal method to respond to the addition of new asset / aTokens\n We need to give the AAVE lending pool approval to transfer the\n asset.\n * @param _asset Address of the asset to approve\n * @param _aToken Address of the aToken\n */\n // solhint-disable-next-line no-unused-vars\n function _abstractSetPToken(address _asset, address _aToken)\n internal\n override\n {\n address lendingPool = address(_getLendingPool());\n IERC20(_asset).safeApprove(lendingPool, 0);\n IERC20(_asset).safeApprove(lendingPool, type(uint256).max);\n }\n\n /**\n * @dev Get the aToken wrapped in the IERC20 interface for this asset.\n * Fails if the pToken doesn't exist in our mappings.\n * @param _asset Address of the asset\n * @return Corresponding aToken to this asset\n */\n function _getATokenFor(address _asset) internal view returns (address) {\n address aToken = assetToPToken[_asset];\n require(aToken != address(0), \"aToken does not exist\");\n return aToken;\n }\n\n /**\n * @dev Get the current address of the Aave lending pool, which is the gateway to\n * depositing.\n * @return Current lending pool implementation\n */\n function _getLendingPool() internal view returns (IAaveLendingPool) {\n address lendingPool = ILendingPoolAddressesProvider(platformAddress)\n .getLendingPool();\n require(lendingPool != address(0), \"Lending pool does not exist\");\n return IAaveLendingPool(lendingPool);\n }\n\n /**\n * @dev Collect stkAave, convert it to AAVE send to Vault.\n */\n function collectRewardTokens()\n external\n override\n onlyHarvester\n nonReentrant\n {\n if (address(stkAave) == address(0)) {\n return;\n }\n\n // Check staked AAVE cooldown timer\n uint256 cooldown = stkAave.stakersCooldowns(address(this));\n uint256 windowStart = cooldown + stkAave.COOLDOWN_SECONDS();\n uint256 windowEnd = windowStart + stkAave.UNSTAKE_WINDOW();\n\n // If inside the unlock window, then we can redeem stkAave\n // for AAVE and send it to the vault.\n if (block.timestamp > windowStart && block.timestamp <= windowEnd) {\n // Redeem to AAVE\n uint256 stkAaveBalance = stkAave.balanceOf(address(this));\n stkAave.redeem(address(this), stkAaveBalance);\n\n // Transfer AAVE to harvesterAddress\n uint256 aaveBalance = IERC20(rewardTokenAddresses[0]).balanceOf(\n address(this)\n );\n if (aaveBalance > 0) {\n IERC20(rewardTokenAddresses[0]).safeTransfer(\n harvesterAddress,\n aaveBalance\n );\n }\n }\n\n // Collect available rewards and restart the cooldown timer, if either of\n // those should be run.\n if (block.timestamp > windowStart || cooldown == 0) {\n uint256 assetsLen = assetsMapped.length;\n // aToken addresses for incentives controller\n address[] memory aTokens = new address[](assetsLen);\n for (uint256 i = 0; i < assetsLen; ++i) {\n aTokens[i] = _getATokenFor(assetsMapped[i]);\n }\n\n // 1. If we have rewards availabile, collect them\n uint256 pendingRewards = incentivesController.getRewardsBalance(\n aTokens,\n address(this)\n );\n if (pendingRewards > 0) {\n // Because getting more stkAAVE from the incentives controller\n // with claimRewards() may push the stkAAVE cooldown time\n // forward, it is called after stakedAAVE has been turned into\n // AAVE.\n uint256 collected = incentivesController.claimRewards(\n aTokens,\n pendingRewards,\n address(this)\n );\n require(collected == pendingRewards, \"AAVE reward difference\");\n }\n\n // 2. Start cooldown counting down.\n if (stkAave.balanceOf(address(this)) > 0) {\n // Protected with if since cooldown call would revert\n // if no stkAave balance.\n stkAave.cooldown();\n }\n }\n }\n}\n" + }, + "contracts/strategies/AbstractCompoundStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title Base Compound Abstract Strategy\n * @author Origin Protocol Inc\n */\n\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport { ICERC20 } from \"./ICompound.sol\";\nimport { IComptroller } from \"../interfaces/IComptroller.sol\";\nimport { IERC20, InitializableAbstractStrategy } from \"../utils/InitializableAbstractStrategy.sol\";\n\nabstract contract AbstractCompoundStrategy is InitializableAbstractStrategy {\n using SafeERC20 for IERC20;\n\n int256[50] private __reserved;\n\n /**\n * @dev Retuns bool indicating whether asset is supported by strategy\n * @param _asset Address of the asset\n */\n function supportsAsset(address _asset) public view override returns (bool) {\n return assetToPToken[_asset] != address(0);\n }\n\n /**\n * @dev Get the cToken wrapped in the ICERC20 interface for this asset.\n * Fails if the pToken doesn't exist in our mappings.\n * @param _asset Address of the asset\n * @return Corresponding cToken to this asset\n */\n function _getCTokenFor(address _asset) internal view returns (ICERC20) {\n address cToken = assetToPToken[_asset];\n require(cToken != address(0), \"cToken does not exist\");\n return ICERC20(cToken);\n }\n\n /**\n * @dev Converts an underlying amount into cToken amount\n * cTokenAmt = (underlying * 1e18) / exchangeRate\n * @param _cToken cToken for which to change\n * @param _underlying Amount of underlying to convert\n * @return amount Equivalent amount of cTokens\n */\n function _convertUnderlyingToCToken(ICERC20 _cToken, uint256 _underlying)\n internal\n view\n returns (uint256 amount)\n {\n // e.g. 1e18*1e18 / 205316390724364402565641705 = 50e8\n // e.g. 1e8*1e18 / 205316390724364402565641705 = 0.45 or 0\n amount = (_underlying * 1e18) / _cToken.exchangeRateStored();\n }\n}\n" + }, + "contracts/strategies/AbstractConvexMetaStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title Curve Convex Strategy\n * @notice Investment strategy for investing stablecoins via Curve 3Pool\n * @author Origin Protocol Inc\n */\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\n\nimport { IRewardStaking } from \"./IRewardStaking.sol\";\nimport { ICurvePool } from \"./ICurvePool.sol\";\nimport { ICurveMetaPool } from \"./ICurveMetaPool.sol\";\nimport { IERC20, AbstractCurveStrategy, InitializableAbstractStrategy } from \"./AbstractCurveStrategy.sol\";\nimport { StableMath } from \"../utils/StableMath.sol\";\nimport { Helpers } from \"../utils/Helpers.sol\";\n\nabstract contract AbstractConvexMetaStrategy is AbstractCurveStrategy {\n using StableMath for uint256;\n using SafeERC20 for IERC20;\n\n event MaxWithdrawalSlippageUpdated(\n uint256 _prevMaxSlippagePercentage,\n uint256 _newMaxSlippagePercentage\n );\n\n // used to circumvent the stack too deep issue\n struct InitConfig {\n address cvxDepositorAddress; //Address of the Convex depositor(AKA booster) for this pool\n address metapoolAddress; //Address of the Curve MetaPool\n address metapoolMainToken; //Address of Main metapool token\n address cvxRewardStakerAddress; //Address of the CVX rewards staker\n address metapoolLPToken; //Address of metapool LP token\n uint256 cvxDepositorPTokenId; //Pid of the pool referred to by Depositor and staker\n }\n\n address internal cvxDepositorAddress;\n address internal cvxRewardStakerAddress;\n uint256 internal cvxDepositorPTokenId;\n ICurveMetaPool internal metapool;\n IERC20 internal metapoolMainToken;\n IERC20 internal metapoolLPToken;\n // Ordered list of metapool assets\n address[] internal metapoolAssets;\n // Max withdrawal slippage denominated in 1e18 (1e18 == 100%)\n uint256 public maxWithdrawalSlippage;\n uint128 internal crvCoinIndex;\n uint128 internal mainCoinIndex;\n\n int256[41] private ___reserved;\n\n /**\n * Initializer for setting up strategy internal state. This overrides the\n * InitializableAbstractStrategy initializer as Curve strategies don't fit\n * well within that abstraction.\n * @param _rewardTokenAddresses Address of CRV & CVX\n * @param _assets Addresses of supported assets. MUST be passed in the same\n * order as returned by coins on the pool contract, i.e.\n * DAI, USDC, USDT\n * @param _pTokens Platform Token corresponding addresses\n * @param initConfig Various addresses and info for initialization state\n */\n function initialize(\n address[] calldata _rewardTokenAddresses, // CRV + CVX\n address[] calldata _assets,\n address[] calldata _pTokens,\n InitConfig calldata initConfig\n ) external onlyGovernor initializer {\n require(_assets.length == 3, \"Must have exactly three assets\");\n // Should be set prior to abstract initialize call otherwise\n // abstractSetPToken calls will fail\n cvxDepositorAddress = initConfig.cvxDepositorAddress;\n pTokenAddress = _pTokens[0];\n metapool = ICurveMetaPool(initConfig.metapoolAddress);\n metapoolMainToken = IERC20(initConfig.metapoolMainToken);\n cvxRewardStakerAddress = initConfig.cvxRewardStakerAddress;\n metapoolLPToken = IERC20(initConfig.metapoolLPToken);\n cvxDepositorPTokenId = initConfig.cvxDepositorPTokenId;\n maxWithdrawalSlippage = 1e16;\n\n metapoolAssets = [metapool.coins(0), metapool.coins(1)];\n crvCoinIndex = _getMetapoolCoinIndex(pTokenAddress);\n mainCoinIndex = _getMetapoolCoinIndex(initConfig.metapoolMainToken);\n InitializableAbstractStrategy._initialize(\n _rewardTokenAddresses,\n _assets,\n _pTokens\n );\n _approveBase();\n }\n\n /**\n * @dev Get the total asset value held in the platform\n * @param _asset Address of the asset\n * @return balance Total value of the asset in the platform\n */\n function checkBalance(address _asset)\n public\n view\n virtual\n override\n returns (uint256 balance)\n {\n require(assetToPToken[_asset] != address(0), \"Unsupported asset\");\n balance = 0;\n\n // LP tokens in this contract. This should generally be nothing as we\n // should always stake the full balance in the Gauge, but include for\n // safety\n uint256 contractPTokens = IERC20(pTokenAddress).balanceOf(\n address(this)\n );\n ICurvePool curvePool = ICurvePool(platformAddress);\n if (contractPTokens > 0) {\n uint256 virtual_price = curvePool.get_virtual_price();\n uint256 value = contractPTokens.mulTruncate(virtual_price);\n balance += value;\n }\n\n /* We intentionally omit the metapoolLp tokens held by the metastrategyContract\n * since the contract should never (except in the middle of deposit/withdrawal\n * transaction) hold any amount of those tokens in normal operation. There\n * could be tokens sent to it by a 3rd party and we decide to actively ignore\n * those.\n */\n uint256 metapoolGaugePTokens = IRewardStaking(cvxRewardStakerAddress)\n .balanceOf(address(this));\n\n if (metapoolGaugePTokens > 0) {\n uint256 value = metapoolGaugePTokens.mulTruncate(\n metapool.get_virtual_price()\n );\n balance += value;\n }\n\n uint256 assetDecimals = Helpers.getDecimals(_asset);\n balance = balance.scaleBy(assetDecimals, 18) / THREEPOOL_ASSET_COUNT;\n }\n\n /**\n * @dev This function is completely analogous to _calcCurveTokenAmount[AbstractCurveStrategy]\n * and just utilizes different Curve (meta)pool API\n */\n function _calcCurveMetaTokenAmount(uint128 _coinIndex, uint256 _amount)\n internal\n returns (uint256 requiredMetapoolLP)\n {\n uint256[2] memory _amounts = [uint256(0), uint256(0)];\n _amounts[uint256(_coinIndex)] = _amount;\n\n // LP required when removing required asset ignoring fees\n uint256 lpRequiredNoFees = metapool.calc_token_amount(_amounts, false);\n /* LP required if fees would apply to entirety of removed amount\n *\n * fee is 1e10 denominated number: https://curve.readthedocs.io/exchange-pools.html#StableSwap.fee\n */\n uint256 lpRequiredFullFees = lpRequiredNoFees.mulTruncateScale(\n 1e10 + metapool.fee(),\n 1e10\n );\n\n /* asset received when withdrawing full fee applicable LP accounting for\n * slippage and fees\n */\n uint256 assetReceivedForFullLPFees = metapool.calc_withdraw_one_coin(\n lpRequiredFullFees,\n int128(_coinIndex)\n );\n\n // exact amount of LP required\n requiredMetapoolLP =\n (lpRequiredFullFees * _amount) /\n assetReceivedForFullLPFees;\n }\n\n function _approveBase() internal override {\n IERC20 pToken = IERC20(pTokenAddress);\n // 3Pool for LP token (required for removing liquidity)\n pToken.safeApprove(platformAddress, 0);\n pToken.safeApprove(platformAddress, type(uint256).max);\n // Gauge for LP token\n metapoolLPToken.safeApprove(cvxDepositorAddress, 0);\n metapoolLPToken.safeApprove(cvxDepositorAddress, type(uint256).max);\n // Metapool for LP token\n pToken.safeApprove(address(metapool), 0);\n pToken.safeApprove(address(metapool), type(uint256).max);\n // Metapool for Metapool main token\n metapoolMainToken.safeApprove(address(metapool), 0);\n metapoolMainToken.safeApprove(address(metapool), type(uint256).max);\n }\n\n /**\n * @dev Get the index of the coin\n */\n function _getMetapoolCoinIndex(address _asset)\n internal\n view\n returns (uint128)\n {\n for (uint128 i = 0; i < 2; i++) {\n if (metapoolAssets[i] == _asset) return i;\n }\n revert(\"Invalid Metapool asset\");\n }\n\n /**\n * @dev Sets max withdrawal slippage that is considered when removing\n * liquidity from Metapools.\n * @param _maxWithdrawalSlippage Max withdrawal slippage denominated in\n * wad (number with 18 decimals): 1e18 == 100%, 1e16 == 1%\n *\n * IMPORTANT Minimum maxWithdrawalSlippage should actually be 0.1% (1e15)\n * for production usage. Contract allows as low value as 0% for confirming\n * correct behavior in test suite.\n */\n function setMaxWithdrawalSlippage(uint256 _maxWithdrawalSlippage)\n external\n onlyVaultOrGovernorOrStrategist\n {\n require(\n _maxWithdrawalSlippage <= 1e18,\n \"Max withdrawal slippage needs to be between 0% - 100%\"\n );\n emit MaxWithdrawalSlippageUpdated(\n maxWithdrawalSlippage,\n _maxWithdrawalSlippage\n );\n maxWithdrawalSlippage = _maxWithdrawalSlippage;\n }\n\n /**\n * @dev Collect accumulated CRV and CVX and send to Harvester.\n */\n function collectRewardTokens()\n external\n override\n onlyHarvester\n nonReentrant\n {\n // Collect CRV and CVX\n IRewardStaking(cvxRewardStakerAddress).getReward();\n _collectRewardTokens();\n }\n\n /**\n * @dev Returns the largest of two numbers int256 version\n */\n function _max(int256 a, int256 b) internal pure returns (int256) {\n return a >= b ? a : b;\n }\n}\n" + }, + "contracts/strategies/AbstractCurveStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title Curve 3Pool Strategy\n * @notice Investment strategy for investing stablecoins via Curve 3Pool\n * @author Origin Protocol Inc\n */\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\n\nimport { ICurvePool } from \"./ICurvePool.sol\";\nimport { IERC20, InitializableAbstractStrategy } from \"../utils/InitializableAbstractStrategy.sol\";\nimport { StableMath } from \"../utils/StableMath.sol\";\nimport { Helpers } from \"../utils/Helpers.sol\";\n\nabstract contract AbstractCurveStrategy is InitializableAbstractStrategy {\n using StableMath for uint256;\n using SafeERC20 for IERC20;\n\n uint256 internal constant MAX_SLIPPAGE = 1e16; // 1%, same as the Curve UI\n // number of assets in Curve 3Pool (USDC, DAI, USDT)\n uint256 internal constant THREEPOOL_ASSET_COUNT = 3;\n address internal pTokenAddress;\n\n int256[49] private __reserved;\n\n /**\n * @dev Deposit asset into the Curve 3Pool\n * @param _asset Address of asset to deposit\n * @param _amount Amount of asset to deposit\n */\n function deposit(address _asset, uint256 _amount)\n external\n override\n onlyVault\n nonReentrant\n {\n require(_amount > 0, \"Must deposit something\");\n emit Deposit(_asset, pTokenAddress, _amount);\n\n // 3Pool requires passing deposit amounts for all 3 assets, set to 0 for\n // all\n uint256[3] memory _amounts;\n uint256 poolCoinIndex = _getCoinIndex(_asset);\n // Set the amount on the asset we want to deposit\n _amounts[poolCoinIndex] = _amount;\n ICurvePool curvePool = ICurvePool(platformAddress);\n uint256 assetDecimals = Helpers.getDecimals(_asset);\n uint256 depositValue = _amount.scaleBy(18, assetDecimals).divPrecisely(\n curvePool.get_virtual_price()\n );\n uint256 minMintAmount = depositValue.mulTruncate(\n uint256(1e18) - MAX_SLIPPAGE\n );\n // Do the deposit to 3pool\n curvePool.add_liquidity(_amounts, minMintAmount);\n _lpDepositAll();\n }\n\n function _lpDepositAll() internal virtual;\n\n /**\n * @dev Deposit the entire balance of any supported asset into the Curve 3pool\n */\n function depositAll() external override onlyVault nonReentrant {\n uint256[3] memory _amounts = [uint256(0), uint256(0), uint256(0)];\n uint256 depositValue = 0;\n ICurvePool curvePool = ICurvePool(platformAddress);\n uint256 curveVirtualPrice = curvePool.get_virtual_price();\n\n uint256 assetCount = assetsMapped.length;\n for (uint256 i = 0; i < assetCount; i++) {\n address assetAddress = assetsMapped[i];\n uint256 balance = IERC20(assetAddress).balanceOf(address(this));\n if (balance > 0) {\n uint256 poolCoinIndex = _getCoinIndex(assetAddress);\n // Set the amount on the asset we want to deposit\n _amounts[poolCoinIndex] = balance;\n uint256 assetDecimals = Helpers.getDecimals(assetAddress);\n // Get value of deposit in Curve LP token to later determine\n // the minMintAmount argument for add_liquidity\n depositValue =\n depositValue +\n balance.scaleBy(18, assetDecimals).divPrecisely(\n curveVirtualPrice\n );\n emit Deposit(assetAddress, pTokenAddress, balance);\n }\n }\n\n uint256 minMintAmount = depositValue.mulTruncate(\n uint256(1e18) - MAX_SLIPPAGE\n );\n // Do the deposit to 3pool\n curvePool.add_liquidity(_amounts, minMintAmount);\n\n /* In case of Curve Strategy all assets are mapped to the same pToken (3CrvLP). Let\n * descendants further handle the pToken. By either deploying it to the metapool and\n * resulting tokens in Gauge. Or deploying pTokens directly to the Gauge.\n */\n _lpDepositAll();\n }\n\n function _lpWithdraw(uint256 numCrvTokens) internal virtual;\n\n function _lpWithdrawAll() internal virtual;\n\n /**\n * @dev Withdraw asset from Curve 3Pool\n * @param _recipient Address to receive withdrawn asset\n * @param _asset Address of asset to withdraw\n * @param _amount Amount of asset to withdraw\n */\n function withdraw(\n address _recipient,\n address _asset,\n uint256 _amount\n ) external override onlyVault nonReentrant {\n require(_amount > 0, \"Invalid amount\");\n\n emit Withdrawal(_asset, pTokenAddress, _amount);\n\n uint256 contractCrv3Tokens = IERC20(pTokenAddress).balanceOf(\n address(this)\n );\n\n uint256 coinIndex = _getCoinIndex(_asset);\n ICurvePool curvePool = ICurvePool(platformAddress);\n\n uint256 requiredCrv3Tokens = _calcCurveTokenAmount(coinIndex, _amount);\n\n // We have enough LP tokens, make sure they are all on this contract\n if (contractCrv3Tokens < requiredCrv3Tokens) {\n _lpWithdraw(requiredCrv3Tokens - contractCrv3Tokens);\n }\n\n uint256[3] memory _amounts = [uint256(0), uint256(0), uint256(0)];\n _amounts[coinIndex] = _amount;\n\n curvePool.remove_liquidity_imbalance(_amounts, requiredCrv3Tokens);\n IERC20(_asset).safeTransfer(_recipient, _amount);\n }\n\n /**\n * @dev Calculate amount of LP required when withdrawing specific amount of one\n * of the underlying assets accounting for fees and slippage.\n *\n * Curve pools unfortunately do not contain a calculation function for\n * amount of LP required when withdrawing a specific amount of one of the\n * underlying tokens and also accounting for fees (Curve's calc_token_amount\n * does account for slippage but not fees).\n *\n * Steps taken to calculate the metric:\n * - get amount of LP required if fees wouldn't apply\n * - increase the LP amount as if fees would apply to the entirety of the underlying\n * asset withdrawal. (when withdrawing only one coin fees apply only to amounts\n * of other assets pool would return in case of balanced removal - since those need\n * to be swapped for the single underlying asset being withdrawn)\n * - get amount of underlying asset withdrawn (this Curve function does consider slippage\n * and fees) when using the increased LP amount. As LP amount is slightly over-increased\n * so is amount of underlying assets returned.\n * - since we know exactly how much asset we require take the rate of LP required for asset\n * withdrawn to get the exact amount of LP.\n */\n function _calcCurveTokenAmount(uint256 _coinIndex, uint256 _amount)\n internal\n returns (uint256 required3Crv)\n {\n ICurvePool curvePool = ICurvePool(platformAddress);\n\n uint256[3] memory _amounts = [uint256(0), uint256(0), uint256(0)];\n _amounts[_coinIndex] = _amount;\n\n // LP required when removing required asset ignoring fees\n uint256 lpRequiredNoFees = curvePool.calc_token_amount(_amounts, false);\n /* LP required if fees would apply to entirety of removed amount\n *\n * fee is 1e10 denominated number: https://curve.readthedocs.io/exchange-pools.html#StableSwap.fee\n */\n uint256 lpRequiredFullFees = lpRequiredNoFees.mulTruncateScale(\n 1e10 + curvePool.fee(),\n 1e10\n );\n\n /* asset received when withdrawing full fee applicable LP accounting for\n * slippage and fees\n */\n uint256 assetReceivedForFullLPFees = curvePool.calc_withdraw_one_coin(\n lpRequiredFullFees,\n int128(uint128(_coinIndex))\n );\n\n // exact amount of LP required\n required3Crv =\n (lpRequiredFullFees * _amount) /\n assetReceivedForFullLPFees;\n }\n\n /**\n * @dev Remove all assets from platform and send them to Vault contract.\n */\n function withdrawAll() external override onlyVaultOrGovernor nonReentrant {\n _lpWithdrawAll();\n // Withdraws are proportional to assets held by 3Pool\n uint256[3] memory minWithdrawAmounts = [\n uint256(0),\n uint256(0),\n uint256(0)\n ];\n\n // Remove liquidity\n ICurvePool threePool = ICurvePool(platformAddress);\n threePool.remove_liquidity(\n IERC20(pTokenAddress).balanceOf(address(this)),\n minWithdrawAmounts\n );\n // Transfer assets out of Vault\n // Note that Curve will provide all 3 of the assets in 3pool even if\n // we have not set PToken addresses for all of them in this strategy\n for (uint256 i = 0; i < assetsMapped.length; i++) {\n IERC20 asset = IERC20(threePool.coins(i));\n asset.safeTransfer(vaultAddress, asset.balanceOf(address(this)));\n }\n }\n\n /**\n * @dev Get the total asset value held in the platform\n * @param _asset Address of the asset\n * @return balance Total value of the asset in the platform\n */\n function checkBalance(address _asset)\n public\n view\n virtual\n override\n returns (uint256 balance)\n {\n require(assetToPToken[_asset] != address(0), \"Unsupported asset\");\n // LP tokens in this contract. This should generally be nothing as we\n // should always stake the full balance in the Gauge, but include for\n // safety\n uint256 totalPTokens = IERC20(pTokenAddress).balanceOf(address(this));\n ICurvePool curvePool = ICurvePool(platformAddress);\n if (totalPTokens > 0) {\n uint256 virtual_price = curvePool.get_virtual_price();\n uint256 value = (totalPTokens * virtual_price) / 1e18;\n uint256 assetDecimals = Helpers.getDecimals(_asset);\n balance = value.scaleBy(assetDecimals, 18) / THREEPOOL_ASSET_COUNT;\n }\n }\n\n /**\n * @dev Retuns bool indicating whether asset is supported by strategy\n * @param _asset Address of the asset\n */\n function supportsAsset(address _asset) public view override returns (bool) {\n return assetToPToken[_asset] != address(0);\n }\n\n /**\n * @dev Approve the spending of all assets by their corresponding pool tokens,\n * if for some reason is it necessary.\n */\n function safeApproveAllTokens()\n external\n override\n onlyGovernor\n nonReentrant\n {\n _approveBase();\n // This strategy is a special case since it only supports one asset\n for (uint256 i = 0; i < assetsMapped.length; i++) {\n _approveAsset(assetsMapped[i]);\n }\n }\n\n /**\n * @dev Call the necessary approvals for the Curve pool and gauge\n * @param _asset Address of the asset\n */\n function _abstractSetPToken(address _asset, address) internal override {\n _approveAsset(_asset);\n }\n\n function _approveAsset(address _asset) internal {\n IERC20 asset = IERC20(_asset);\n // 3Pool for asset (required for adding liquidity)\n asset.safeApprove(platformAddress, 0);\n asset.safeApprove(platformAddress, type(uint256).max);\n }\n\n function _approveBase() internal virtual;\n\n /**\n * @dev Get the index of the coin\n */\n function _getCoinIndex(address _asset) internal view returns (uint256) {\n for (uint256 i = 0; i < 3; i++) {\n if (assetsMapped[i] == _asset) return i;\n }\n revert(\"Invalid 3pool asset\");\n }\n}\n" + }, + "contracts/strategies/aerodrome/AerodromeAMOStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title Aerodrome AMO strategy\n * @author Origin Protocol Inc\n */\nimport { Math } from \"@openzeppelin/contracts/utils/math/Math.sol\";\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport { SafeCast } from \"@openzeppelin/contracts/utils/math/SafeCast.sol\";\n\nimport { IERC20, InitializableAbstractStrategy } from \"../../utils/InitializableAbstractStrategy.sol\";\nimport { StableMath } from \"../../utils/StableMath.sol\";\n\nimport { ISugarHelper } from \"../../interfaces/aerodrome/ISugarHelper.sol\";\nimport { INonfungiblePositionManager } from \"../../interfaces/aerodrome/INonfungiblePositionManager.sol\";\nimport { ISwapRouter } from \"../../interfaces/aerodrome/ISwapRouter.sol\";\nimport { ICLPool } from \"../../interfaces/aerodrome/ICLPool.sol\";\nimport { ICLGauge } from \"../../interfaces/aerodrome/ICLGauge.sol\";\nimport { IVault } from \"../../interfaces/IVault.sol\";\n\ncontract AerodromeAMOStrategy is InitializableAbstractStrategy {\n using StableMath for uint256;\n using SafeERC20 for IERC20;\n using SafeCast for uint256;\n\n /************************************************\n Important (!) setup configuration\n *************************************************/\n\n /**\n * In order to be able to remove a reasonable amount of complexity from the contract one of the\n * preconditions for this contract to function correctly is to have an outside account mint a small\n * amount of liquidity in the tick space where the contract will deploy's its liquidity and then send\n * that NFT LP position to a dead address (transfer to zero address not allowed.) See example of such\n * NFT LP token:\n * https://basescan.org/token/0x827922686190790b37229fd06084350e74485b72?a=413296#inventory\n */\n\n /***************************************\n Storage slot members\n ****************************************/\n\n /// @notice tokenId of the liquidity position\n uint256 public tokenId;\n /// @dev Minimum amount of tokens the strategy would be able to withdraw from the pool.\n /// minimum amount of tokens are withdrawn at a 1:1 price\n uint256 public underlyingAssets;\n /// @notice Marks the start of the interval that defines the allowed range of WETH share in\n /// the pre-configured pool's liquidity ticker\n uint256 public allowedWethShareStart;\n /// @notice Marks the end of the interval that defines the allowed range of WETH share in\n /// the pre-configured pool's liquidity ticker\n uint256 public allowedWethShareEnd;\n /// @dev reserved for inheritance\n int256[46] private __reserved;\n\n /***************************************\n Constants, structs and events\n ****************************************/\n\n /// @notice The address of the Wrapped ETH (WETH) token contract\n address public immutable WETH;\n /// @notice The address of the OETHb token contract\n address public immutable OETHb;\n /// @notice lower tick set to -1 representing the price of 1.0001 of WETH for 1 OETHb.\n int24 public immutable lowerTick;\n /// @notice lower tick set to 0 representing the price of 1.0000 of WETH for 1 OETHb.\n int24 public immutable upperTick;\n /// @notice tick spacing of the pool (set to 1)\n int24 public immutable tickSpacing;\n /// @notice the swapRouter for performing swaps\n ISwapRouter public immutable swapRouter;\n /// @notice the underlying AMO Slipstream pool\n ICLPool public immutable clPool;\n /// @notice the gauge for the corresponding Slipstream pool (clPool)\n /// @dev can become an immutable once the gauge is created on the base main-net\n ICLGauge public immutable clGauge;\n /// @notice the Position manager contract that is used to manage the pool's position\n INonfungiblePositionManager public immutable positionManager;\n /// @notice helper contract for liquidity and ticker math\n ISugarHelper public immutable helper;\n /// @notice sqrtRatioX96TickLower\n /// @dev tick lower has value -1 and represents the lowest price of WETH priced in OETHb. Meaning the pool\n /// offers less than 1 OETHb for 1 WETH. In other terms to get 1 OETHB the swap needs to offer 1.0001 WETH\n /// this is where purchasing OETHb with WETH within the liquidity position is most expensive\n uint160 public immutable sqrtRatioX96TickLower;\n /// @notice sqrtRatioX96TickHigher\n /// @dev tick higher has value 0 and represents 1:1 price parity of WETH to OETHb\n uint160 public immutable sqrtRatioX96TickHigher;\n /// @dev tick closest to 1:1 price parity\n /// Correctly assessing which tick is closer to 1:1 price parity is important since it affects\n /// the way we calculate the underlying assets in check Balance. The underlying aerodrome pool\n /// orders the tokens depending on the values of their addresses. If OETH token is token0 in the pool\n /// then sqrtRatioX96TickClosestToParity=sqrtRatioX96TickLower. If it is token1 in the pool then\n /// sqrtRatioX96TickClosestToParity=sqrtRatioX96TickHigher\n uint160 public immutable sqrtRatioX96TickClosestToParity;\n\n /// @dev a threshold under which the contract no longer allows for the protocol to rebalance. Guarding\n /// against a strategist / guardian being taken over and with multiple transactions draining the\n /// protocol funds.\n uint256 public constant SOLVENCY_THRESHOLD = 0.998 ether;\n\n error NotEnoughWethForSwap(uint256 wethBalance, uint256 requiredWeth); // 0x989e5ca8\n error NotEnoughWethLiquidity(uint256 wethBalance, uint256 requiredWeth); // 0xa6737d87\n error PoolRebalanceOutOfBounds(\n uint256 currentPoolWethShare,\n uint256 allowedWethShareStart,\n uint256 allowedWethShareEnd\n ); // 0x3681e8e0\n error OutsideExpectedTickRange(int24 currentTick); // 0x5a2eba75\n\n event PoolRebalanced(uint256 currentPoolWethShare);\n\n event PoolWethShareIntervalUpdated(\n uint256 allowedWethShareStart,\n uint256 allowedWethShareEnd\n );\n\n event LiquidityRemoved(\n uint256 withdrawLiquidityShare,\n uint256 removedWETHAmount,\n uint256 removedOETHbAmount,\n uint256 wethAmountCollected,\n uint256 oethbAmountCollected,\n uint256 underlyingAssets\n );\n\n event LiquidityAdded(\n uint256 wethAmountDesired,\n uint256 oethbAmountDesired,\n uint256 wethAmountSupplied,\n uint256 oethbAmountSupplied,\n uint256 tokenId,\n uint256 underlyingAssets\n );\n\n event UnderlyingAssetsUpdated(uint256 underlyingAssets);\n\n /**\n * @dev Un-stakes the token from the gauge for the execution duration of\n * the function and after that re-stakes it back in.\n *\n * It is important that the token is unstaked and owned by the strategy contract\n * during any liquidity altering operations and that it is re-staked back into the\n * gauge after liquidity changes. If the token fails to re-stake back to the\n * gauge it is not earning incentives.\n */\n // all functions using this modifier are used by functions with reentrancy check\n // slither-disable-start reentrancy-no-eth\n modifier gaugeUnstakeAndRestake() {\n // because of solidity short-circuit _isLpTokenStakedInGauge doesn't get called\n // when tokenId == 0\n if (tokenId != 0 && _isLpTokenStakedInGauge()) {\n clGauge.withdraw(tokenId);\n }\n _;\n // because of solidity short-circuit _isLpTokenStakedInGauge doesn't get called\n // when tokenId == 0\n if (tokenId != 0 && !_isLpTokenStakedInGauge()) {\n /**\n * It can happen that a withdrawal (or a full withdrawal) transactions would\n * remove all of the liquidity from the token with a NFT token still existing.\n * In that case the token can not be staked into the gauge, as some liquidity\n * needs to be added to it first.\n */\n if (_getLiquidity() > 0) {\n // if token liquidity changes the positionManager requires re-approval.\n // to any contract pre-approved to handle the token.\n positionManager.approve(address(clGauge), tokenId);\n clGauge.deposit(tokenId);\n }\n }\n }\n\n // slither-disable-end reentrancy-no-eth\n\n /// @notice the constructor\n /// @dev This contract is intended to be used as a proxy. To prevent the\n /// potential confusion of having a functional implementation contract\n /// the constructor has the `initializer` modifier. This way the\n /// `initialize` function can not be called on the implementation contract.\n /// For the same reason the implementation contract also has the governor\n /// set to a zero address.\n /// @param _stratConfig the basic strategy configuration\n /// @param _wethAddress Address of the Erc20 WETH Token contract\n /// @param _oethbAddress Address of the Erc20 OETHb Token contract\n /// @param _swapRouter Address of the Aerodrome Universal Swap Router\n /// @param _nonfungiblePositionManager Address of position manager to add/remove\n /// the liquidity\n /// @param _clPool Address of the Aerodrome concentrated liquidity pool\n /// @param _clGauge Address of the Aerodrome slipstream pool gauge\n /// @param _sugarHelper Address of the Aerodrome Sugar helper contract\n /// @param _lowerBoundingTick Smaller bounding tick of our liquidity position\n /// @param _upperBoundingTick Larger bounding tick of our liquidity position\n /// @param _tickClosestToParity Tick that is closer to 1:1 price parity\n constructor(\n BaseStrategyConfig memory _stratConfig,\n address _wethAddress,\n address _oethbAddress,\n address _swapRouter,\n address _nonfungiblePositionManager,\n address _clPool,\n address _clGauge,\n address _sugarHelper,\n int24 _lowerBoundingTick,\n int24 _upperBoundingTick,\n int24 _tickClosestToParity\n ) initializer InitializableAbstractStrategy(_stratConfig) {\n require(\n _lowerBoundingTick == _tickClosestToParity ||\n _upperBoundingTick == _tickClosestToParity,\n \"Misconfigured tickClosestToParity\"\n );\n require(\n ICLPool(_clPool).token0() == _wethAddress,\n \"Only WETH supported as token0\"\n );\n require(\n ICLPool(_clPool).token1() == _oethbAddress,\n \"Only OETHb supported as token1\"\n );\n int24 _tickSpacing = ICLPool(_clPool).tickSpacing();\n // when we generalize AMO we might support other tick spacings\n require(_tickSpacing == 1, \"Unsupported tickSpacing\");\n\n WETH = _wethAddress;\n OETHb = _oethbAddress;\n swapRouter = ISwapRouter(_swapRouter);\n positionManager = INonfungiblePositionManager(\n _nonfungiblePositionManager\n );\n clPool = ICLPool(_clPool);\n clGauge = ICLGauge(_clGauge);\n helper = ISugarHelper(_sugarHelper);\n sqrtRatioX96TickLower = ISugarHelper(_sugarHelper).getSqrtRatioAtTick(\n _lowerBoundingTick\n );\n sqrtRatioX96TickHigher = ISugarHelper(_sugarHelper).getSqrtRatioAtTick(\n _upperBoundingTick\n );\n sqrtRatioX96TickClosestToParity = ISugarHelper(_sugarHelper)\n .getSqrtRatioAtTick(_tickClosestToParity);\n\n lowerTick = _lowerBoundingTick;\n upperTick = _upperBoundingTick;\n tickSpacing = _tickSpacing;\n\n // prevent implementation contract to be governed\n _setGovernor(address(0));\n }\n\n /**\n * @notice initialize function, to set up initial internal state\n * @param _rewardTokenAddresses Address of reward token for platform\n */\n function initialize(address[] memory _rewardTokenAddresses)\n external\n onlyGovernor\n initializer\n {\n InitializableAbstractStrategy._initialize(\n _rewardTokenAddresses,\n new address[](0),\n new address[](0)\n );\n }\n\n /***************************************\n Configuration \n ****************************************/\n\n /**\n * @notice Set allowed pool weth share interval. After the rebalance happens\n * the share of WETH token in the ticker needs to be withing the specifications\n * of the interval.\n *\n * @param _allowedWethShareStart Start of WETH share interval expressed as 18 decimal amount\n * @param _allowedWethShareEnd End of WETH share interval expressed as 18 decimal amount\n */\n function setAllowedPoolWethShareInterval(\n uint256 _allowedWethShareStart,\n uint256 _allowedWethShareEnd\n ) external onlyGovernor {\n require(\n _allowedWethShareStart < _allowedWethShareEnd,\n \"Invalid interval\"\n );\n // can not go below 1% weth share\n require(_allowedWethShareStart > 0.01 ether, \"Invalid interval start\");\n // can not go above 95% weth share\n require(_allowedWethShareEnd < 0.95 ether, \"Invalid interval end\");\n\n allowedWethShareStart = _allowedWethShareStart;\n allowedWethShareEnd = _allowedWethShareEnd;\n emit PoolWethShareIntervalUpdated(\n allowedWethShareStart,\n allowedWethShareEnd\n );\n }\n\n /***************************************\n Periphery utils\n ****************************************/\n\n function _isLpTokenStakedInGauge() internal view returns (bool) {\n require(tokenId != 0, \"Missing NFT LP token\");\n\n address owner = positionManager.ownerOf(tokenId);\n require(\n owner == address(clGauge) || owner == address(this),\n \"Unexpected token owner\"\n );\n return owner == address(clGauge);\n }\n\n /***************************************\n Strategy overrides \n ****************************************/\n\n /**\n * @notice Deposit an amount of assets into the strategy contract. Calling deposit doesn't\n * automatically deposit funds into the underlying Aerodrome pool\n * @param _asset Address for the asset\n * @param _amount Units of asset to deposit\n */\n function deposit(address _asset, uint256 _amount)\n external\n override\n onlyVault\n nonReentrant\n {\n _deposit(_asset, _amount);\n }\n\n /**\n * @notice Deposit WETH to the strategy contract. This function does not add liquidity to the\n * underlying Aerodrome pool.\n */\n function depositAll() external override onlyVault nonReentrant {\n uint256 _wethBalance = IERC20(WETH).balanceOf(address(this));\n if (_wethBalance > 1e12) {\n _deposit(WETH, _wethBalance);\n }\n }\n\n /**\n * @dev Deposit WETH to the contract. This function doesn't deposit the liquidity to the\n * pool, that is done via the rebalance call.\n * @param _asset Address of the asset to deposit\n * @param _amount Amount of assets to deposit\n */\n function _deposit(address _asset, uint256 _amount) internal {\n require(_asset == WETH, \"Unsupported asset\");\n require(_amount > 0, \"Must deposit something\");\n emit Deposit(_asset, address(0), _amount);\n\n // if the pool price is not within the expected interval leave the WETH on the contract\n // as to not break the mints\n (bool _isExpectedRange, ) = _checkForExpectedPoolPrice(false);\n if (_isExpectedRange) {\n // deposit funds into the underlying pool\n _rebalance(0, false, 0);\n }\n }\n\n /**\n * @notice Rebalance the pool to the desired token split and Deposit any WETH on the contract to the\n * underlying aerodrome pool. Print the required amount of corresponding OETHb. After the rebalancing is\n * done burn any potentially remaining OETHb tokens still on the strategy contract.\n *\n * This function has a slightly different behaviour depending on the status of the underlying Aerodrome\n * slipstream pool. The function consists of the following 3 steps:\n * 1. withdrawPartialLiquidity -> so that moving the activeTrading price via a swap is cheaper\n * 2. swapToDesiredPosition -> move active trading price in the pool to be able to deposit WETH & OETHb\n * tokens with the desired pre-configured shares\n * 3. addLiquidity -> add liquidity into the pool respecting share split configuration\n *\n * Scenario 1: When there is no liquidity in the pool from the strategy but there is from other LPs then\n * only step 1 is skipped. (It is important to note that liquidity needs to exist in the configured\n * strategy tick ranges in order for the swap to be possible) Step 3 mints new liquidity position\n * instead of adding to an existing one.\n * Scenario 2: When there is strategy's liquidity in the pool all 3 steps are taken\n *\n *\n * Exact _amountToSwap, _swapWeth & _minTokenReceived parameters shall be determined by simulating the\n * transaction off-chain. The strategy checks that after the swap the share of the tokens is in the\n * expected ranges.\n *\n * @param _amountToSwap The amount of the token to swap\n * @param _swapWeth Swap using WETH when true, use OETHb when false\n * @param _minTokenReceived Slippage check -> minimum amount of token expected in return\n */\n function rebalance(\n uint256 _amountToSwap,\n bool _swapWeth,\n uint256 _minTokenReceived\n ) external nonReentrant onlyGovernorOrStrategist {\n _rebalance(_amountToSwap, _swapWeth, _minTokenReceived);\n }\n\n function _rebalance(\n uint256 _amountToSwap,\n bool _swapWeth,\n uint256 _minTokenReceived\n ) internal {\n /**\n * Would be nice to check if there is any total liquidity in the pool before performing this swap\n * but there is no easy way to do that in UniswapV3:\n * - clPool.liquidity() -> only liquidity in the active tick\n * - asset[1&2].balanceOf(address(clPool)) -> will include uncollected tokens of LP providers\n * after their liquidity position has been decreased\n */\n\n /**\n * When rebalance is called for the first time there is no strategy\n * liquidity in the pool yet. The liquidity removal is thus skipped.\n * Also execute this function when WETH is required for the swap.\n */\n if (tokenId != 0 && _swapWeth && _amountToSwap > 0) {\n _ensureWETHBalance(_amountToSwap);\n }\n\n // in some cases we will just want to add liquidity and not issue a swap to move the\n // active trading position within the pool\n if (_amountToSwap > 0) {\n _swapToDesiredPosition(_amountToSwap, _swapWeth, _minTokenReceived);\n }\n // calling check liquidity early so we don't get unexpected errors when adding liquidity\n // in the later stages of this function\n _checkForExpectedPoolPrice(true);\n\n _addLiquidity();\n\n // this call shouldn't be necessary, since adding liquidity shouldn't affect the active\n // trading price. It is a defensive programming measure.\n (, uint256 _wethSharePct) = _checkForExpectedPoolPrice(true);\n\n // revert if protocol insolvent\n _solvencyAssert();\n\n emit PoolRebalanced(_wethSharePct);\n }\n\n /**\n * Checks that the protocol is solvent, protecting from a rogue Strategist / Guardian that can\n * keep rebalancing the pool in both directions making the protocol lose a tiny amount of\n * funds each time.\n *\n * Protocol must be at least SOLVENCY_THRESHOLD (99,8 %) backed in order for the rebalances to\n * function.\n */\n function _solvencyAssert() internal view {\n uint256 _totalVaultValue = IVault(vaultAddress).totalValue();\n uint256 _totalOethbSupply = IERC20(OETHb).totalSupply();\n\n if (\n _totalVaultValue.divPrecisely(_totalOethbSupply) <\n SOLVENCY_THRESHOLD\n ) {\n revert(\"Protocol insolvent\");\n }\n }\n\n /**\n * @dev Decrease partial or all liquidity from the pool.\n * @param _liquidityToDecrease The amount of liquidity to remove expressed in 18 decimal point\n */\n function _removeLiquidity(uint256 _liquidityToDecrease)\n internal\n gaugeUnstakeAndRestake\n {\n require(_liquidityToDecrease > 0, \"Must remove some liquidity\");\n\n uint128 _liquidity = _getLiquidity();\n // need to convert to uint256 since intermittent result is to big for uint128 to handle\n uint128 _liquidityToRemove = uint256(_liquidity)\n .mulTruncate(_liquidityToDecrease)\n .toUint128();\n\n /**\n * There is no liquidity to remove -> exit function early. This can happen after a\n * withdraw/withdrawAll removes all of the liquidity while retaining the NFT token.\n */\n if (_liquidity == 0 || _liquidityToRemove == 0) {\n return;\n }\n\n (uint256 _amountWeth, uint256 _amountOethb) = positionManager\n .decreaseLiquidity(\n // Both expected amounts can be 0 since we don't really care if any swaps\n // happen just before the liquidity removal.\n INonfungiblePositionManager.DecreaseLiquidityParams({\n tokenId: tokenId,\n liquidity: _liquidityToRemove,\n amount0Min: 0,\n amount1Min: 0,\n deadline: block.timestamp\n })\n );\n\n (\n uint256 _amountWethCollected,\n uint256 _amountOethbCollected\n ) = positionManager.collect(\n INonfungiblePositionManager.CollectParams({\n tokenId: tokenId,\n recipient: address(this),\n amount0Max: type(uint128).max, // defaults to all tokens owed\n amount1Max: type(uint128).max // defaults to all tokens owed\n })\n );\n\n _updateUnderlyingAssets();\n\n emit LiquidityRemoved(\n _liquidityToDecrease,\n _amountWeth, //removedWethAmount\n _amountOethb, //removedOethbAmount\n _amountWethCollected,\n _amountOethbCollected,\n underlyingAssets\n );\n\n _burnOethbOnTheContract();\n }\n\n /**\n * @dev Perform a swap so that after the swap the ticker has the desired WETH to OETHb token share.\n */\n function _swapToDesiredPosition(\n uint256 _amountToSwap,\n bool _swapWeth,\n uint256 _minTokenReceived\n ) internal {\n IERC20 _tokenToSwap = IERC20(_swapWeth ? WETH : OETHb);\n uint256 _balance = _tokenToSwap.balanceOf(address(this));\n\n if (_balance < _amountToSwap) {\n // This should never trigger since _ensureWETHBalance will already\n // throw an error if there is not enough WETH\n if (_swapWeth) {\n revert NotEnoughWethForSwap(_balance, _amountToSwap);\n }\n // if swapping OETHb\n uint256 mintForSwap = _amountToSwap - _balance;\n IVault(vaultAddress).mintForStrategy(mintForSwap);\n }\n\n // approve the specific amount of WETH required\n if (_swapWeth) {\n IERC20(WETH).approve(address(swapRouter), _amountToSwap);\n }\n\n // Swap it\n swapRouter.exactInputSingle(\n // sqrtPriceLimitX96 is just a rough sanity check that we are within 0 -> 1 tick\n // a more fine check is performed in _checkForExpectedPoolPrice\n // Note: this needs further work if we want to generalize this approach\n ISwapRouter.ExactInputSingleParams({\n tokenIn: address(_tokenToSwap),\n tokenOut: _swapWeth ? OETHb : WETH,\n tickSpacing: tickSpacing, // set to 1\n recipient: address(this),\n deadline: block.timestamp,\n amountIn: _amountToSwap,\n amountOutMinimum: _minTokenReceived, // slippage check\n sqrtPriceLimitX96: _swapWeth\n ? sqrtRatioX96TickLower\n : sqrtRatioX96TickHigher\n })\n );\n\n /**\n * In the interest of each function in _rebalance to leave the contract state as\n * clean as possible the OETHb tokens here are burned. This decreases the\n * dependence where `_swapToDesiredPosition` function relies on later functions\n * (`addLiquidity`) to burn the OETHb. Reducing the risk of error introduction.\n */\n _burnOethbOnTheContract();\n }\n\n /**\n * @dev Add liquidity into the pool in the pre-configured WETH to OETHb share ratios\n * defined by the allowedPoolWethShareStart|End interval. This function will respect\n * liquidity ratios when there is no liquidity yet in the pool. If liquidity is already\n * present then it relies on the `_swapToDesiredPosition` function in a step before\n * to already move the trading price to desired position (with some tolerance).\n */\n // rebalance already has re-entrency checks\n // slither-disable-start reentrancy-no-eth\n function _addLiquidity() internal gaugeUnstakeAndRestake {\n uint256 _wethBalance = IERC20(WETH).balanceOf(address(this));\n uint256 _oethbBalance = IERC20(OETHb).balanceOf(address(this));\n // don't deposit small liquidity amounts\n if (_wethBalance <= 1e12) {\n return;\n }\n\n uint160 _currentPrice = getPoolX96Price();\n /**\n * Sanity check active trading price is positioned within our desired tick.\n *\n * We revert when price is equal to the lower tick even though that is still\n * a valid amount in regards to ticker position by Sugar.estimateAmount call.\n * Current price equaling tick bound at the 1:1 price parity results in\n * uint overfow when calculating the OETHb balance to deposit.\n */\n if (\n _currentPrice <= sqrtRatioX96TickLower ||\n _currentPrice >= sqrtRatioX96TickHigher\n ) {\n revert OutsideExpectedTickRange(getCurrentTradingTick());\n }\n\n /**\n * If estimateAmount1 call fails it could be due to _currentPrice being really\n * close to a tick and amount1 is a larger number than the sugar helper is able\n * to compute.\n *\n * If token addresses were reversed estimateAmount0 would be required here\n */\n uint256 _oethbRequired = helper.estimateAmount1(\n _wethBalance,\n address(0), // no need to pass pool address when current price is specified\n _currentPrice,\n lowerTick,\n upperTick\n );\n\n if (_oethbRequired > _oethbBalance) {\n IVault(vaultAddress).mintForStrategy(\n _oethbRequired - _oethbBalance\n );\n }\n\n // approve the specific amount of WETH required\n IERC20(WETH).approve(address(positionManager), _wethBalance);\n\n uint256 _wethAmountSupplied;\n uint256 _oethbAmountSupplied;\n if (tokenId == 0) {\n (\n tokenId,\n ,\n _wethAmountSupplied,\n _oethbAmountSupplied\n ) = positionManager.mint(\n /** amount0Min & amount1Min are left at 0 because slippage protection is ensured by the\n * _checkForExpectedPoolPrice\n *›\n * Also sqrtPriceX96 is 0 because the pool is already created\n * non zero amount attempts to create a new instance of the pool\n */\n INonfungiblePositionManager.MintParams({\n token0: WETH,\n token1: OETHb,\n tickSpacing: tickSpacing,\n tickLower: lowerTick,\n tickUpper: upperTick,\n amount0Desired: _wethBalance,\n amount1Desired: _oethbRequired,\n amount0Min: 0,\n amount1Min: 0,\n recipient: address(this),\n deadline: block.timestamp,\n sqrtPriceX96: 0\n })\n );\n } else {\n (, _wethAmountSupplied, _oethbAmountSupplied) = positionManager\n .increaseLiquidity(\n /** amount0Min & amount1Min are left at 0 because slippage protection is ensured by the\n * _checkForExpectedPoolPrice\n */\n INonfungiblePositionManager.IncreaseLiquidityParams({\n tokenId: tokenId,\n amount0Desired: _wethBalance,\n amount1Desired: _oethbRequired,\n amount0Min: 0,\n amount1Min: 0,\n deadline: block.timestamp\n })\n );\n }\n\n _updateUnderlyingAssets();\n emit LiquidityAdded(\n _wethBalance, // wethAmountDesired\n _oethbRequired, // oethbAmountDesired\n _wethAmountSupplied, // wethAmountSupplied\n _oethbAmountSupplied, // oethbAmountSupplied\n tokenId, // tokenId\n underlyingAssets\n );\n\n // burn remaining OETHb\n _burnOethbOnTheContract();\n }\n\n // slither-disable-end reentrancy-no-eth\n\n /**\n * @dev Check that the Aerodrome pool price is within the expected\n * parameters.\n * This function works whether the strategy contract has liquidity\n * position in the pool or not. The function returns _wethSharePct\n * as a gas optimization measure.\n * @param throwException when set to true the function throws an exception\n * when pool's price is not within expected range.\n * @return _isExpectedRange Bool expressing price is within expected range\n * @return _wethSharePct Share of WETH owned by this strategy contract in the\n * configured ticker.\n */\n function _checkForExpectedPoolPrice(bool throwException)\n internal\n view\n returns (bool _isExpectedRange, uint256 _wethSharePct)\n {\n require(\n allowedWethShareStart != 0 && allowedWethShareEnd != 0,\n \"Weth share interval not set\"\n );\n\n uint160 _currentPrice = getPoolX96Price();\n\n /**\n * First check we are in expected tick range\n *\n * We revert even though price being equal to the lower tick would still\n * count being within lower tick for the purpose of Sugar.estimateAmount calls\n */\n if (\n _currentPrice <= sqrtRatioX96TickLower ||\n _currentPrice >= sqrtRatioX96TickHigher\n ) {\n if (throwException) {\n revert OutsideExpectedTickRange(getCurrentTradingTick());\n }\n return (false, 0);\n }\n\n // 18 decimal number expressed WETH tick share\n _wethSharePct = _getWethShare(_currentPrice);\n\n if (\n _wethSharePct < allowedWethShareStart ||\n _wethSharePct > allowedWethShareEnd\n ) {\n if (throwException) {\n revert PoolRebalanceOutOfBounds(\n _wethSharePct,\n allowedWethShareStart,\n allowedWethShareEnd\n );\n }\n return (false, _wethSharePct);\n }\n\n return (true, _wethSharePct);\n }\n\n /**\n * Burns any OETHb tokens remaining on the strategy contract\n */\n function _burnOethbOnTheContract() internal {\n uint256 _oethbBalance = IERC20(OETHb).balanceOf(address(this));\n if (_oethbBalance > 1e12) {\n IVault(vaultAddress).burnForStrategy(_oethbBalance);\n }\n }\n\n /// @dev This function assumes there are no uncollected tokens in the clPool owned by the strategy contract.\n /// For that reason any liquidity withdrawals must also collect the tokens.\n function _updateUnderlyingAssets() internal {\n if (tokenId == 0) {\n underlyingAssets = 0;\n emit UnderlyingAssetsUpdated(underlyingAssets);\n return;\n }\n\n uint128 _liquidity = _getLiquidity();\n\n /**\n * Our net value represent the smallest amount of tokens we are able to extract from the position\n * given our liquidity.\n *\n * The least amount of tokens extraditable from the position is where the active trading price is\n * at the ticker 0 meaning the pool is offering 1:1 trades between WETH & OETHb. At that moment the pool\n * consists completely of OETHb and no WETH.\n *\n * The more swaps from WETH -> OETHb happen on the pool the more the price starts to move towards the -1\n * ticker making OETHb (priced in WETH) more expensive.\n *\n * An additional note: when liquidity is 0 then the helper returns 0 for both token amounts. And the\n * function set underlying assets to 0.\n */\n (uint256 _wethAmount, uint256 _oethbAmount) = helper\n .getAmountsForLiquidity(\n sqrtRatioX96TickClosestToParity, // sqrtRatioX96\n sqrtRatioX96TickLower, // sqrtRatioAX96\n sqrtRatioX96TickHigher, // sqrtRatioBX96\n _liquidity\n );\n\n require(_wethAmount == 0, \"Non zero wethAmount\");\n underlyingAssets = _oethbAmount;\n emit UnderlyingAssetsUpdated(underlyingAssets);\n }\n\n /**\n * @dev This function removes the appropriate amount of liquidity to assure that the required\n * amount of WETH is available on the contract\n *\n * @param _amount WETH balance required on the contract\n */\n function _ensureWETHBalance(uint256 _amount) internal {\n uint256 _wethBalance = IERC20(WETH).balanceOf(address(this));\n if (_wethBalance >= _amount) {\n return;\n }\n\n require(tokenId != 0, \"No liquidity available\");\n uint256 _additionalWethRequired = _amount - _wethBalance;\n (uint256 _wethInThePool, ) = getPositionPrincipal();\n\n if (_wethInThePool < _additionalWethRequired) {\n revert NotEnoughWethLiquidity(\n _wethInThePool,\n _additionalWethRequired\n );\n }\n\n uint256 shareOfWethToRemove = Math.min(\n _additionalWethRequired.divPrecisely(_wethInThePool) + 1,\n 1e18\n );\n _removeLiquidity(shareOfWethToRemove);\n }\n\n /**\n * @notice Withdraw an `amount` of assets from the platform and\n * send to the `_recipient`.\n * @param _recipient Address to which the asset should be sent\n * @param _asset WETH address\n * @param _amount Amount of WETH to withdraw\n */\n function withdraw(\n address _recipient,\n address _asset,\n uint256 _amount\n ) external override onlyVault nonReentrant {\n require(_asset == WETH, \"Unsupported asset\");\n require(_recipient == vaultAddress, \"Only withdraw to vault allowed\");\n\n _ensureWETHBalance(_amount);\n\n _withdraw(_recipient, _amount);\n }\n\n /**\n * @notice Withdraw WETH and sends it to the Vault.\n */\n function withdrawAll() external override onlyVault nonReentrant {\n if (tokenId != 0) {\n _removeLiquidity(1e18);\n }\n\n uint256 _balance = IERC20(WETH).balanceOf(address(this));\n if (_balance > 0) {\n _withdraw(vaultAddress, _balance);\n }\n }\n\n function _withdraw(address _recipient, uint256 _amount) internal {\n require(_amount > 0, \"Must withdraw something\");\n require(_recipient == vaultAddress, \"Only withdraw to vault allowed\");\n\n IERC20(WETH).safeTransfer(_recipient, _amount);\n emit Withdrawal(WETH, address(0), _amount);\n }\n\n /**\n * @dev Collect the AERO token from the gauge\n */\n function _collectRewardTokens() internal override {\n if (tokenId != 0 && _isLpTokenStakedInGauge()) {\n clGauge.getReward(tokenId);\n }\n super._collectRewardTokens();\n }\n\n /**\n * @dev Retuns bool indicating whether asset is supported by strategy\n * @param _asset Address of the asset\n */\n function supportsAsset(address _asset) public view override returns (bool) {\n return _asset == WETH;\n }\n\n /**\n * @dev Approve the spending of all assets\n */\n function safeApproveAllTokens()\n external\n override\n onlyGovernor\n nonReentrant\n {\n // to add liquidity to the clPool\n IERC20(OETHb).approve(address(positionManager), type(uint256).max);\n // to be able to rebalance using the swapRouter\n IERC20(OETHb).approve(address(swapRouter), type(uint256).max);\n\n /* the behaviour of this strategy has slightly changed and WETH could be\n * present on the contract between the transactions. For that reason we are\n * un-approving WETH to the swapRouter & positionManager and only approving\n * the required amount before a transaction\n */\n IERC20(WETH).approve(address(swapRouter), 0);\n IERC20(WETH).approve(address(positionManager), 0);\n }\n\n /***************************************\n Balances and Fees\n ****************************************/\n\n /**\n * @dev Get the total asset value held in the platform\n * @param _asset Address of the asset\n * @return balance Total value of the asset in the platform\n */\n function checkBalance(address _asset)\n external\n view\n override\n returns (uint256)\n {\n require(_asset == WETH, \"Only WETH supported\");\n\n // we could in theory deposit to the strategy and forget to call rebalance in the same\n // governance transaction batch. In that case the WETH that is on the strategy contract\n // also needs to be accounted for.\n uint256 _wethBalance = IERC20(WETH).balanceOf(address(this));\n // just paranoia check, in case there is OETHb in the strategy that for some reason hasn't\n // been burned yet.\n uint256 _oethbBalance = IERC20(OETHb).balanceOf(address(this));\n return underlyingAssets + _wethBalance + _oethbBalance;\n }\n\n /**\n * @dev Returns the balance of both tokens in a given position (excluding fees)\n * @return _amountWeth Amount of WETH in position\n * @return _amountOethb Amount of OETHb in position\n */\n function getPositionPrincipal()\n public\n view\n returns (uint256 _amountWeth, uint256 _amountOethb)\n {\n if (tokenId == 0) {\n return (0, 0);\n }\n\n uint160 _sqrtRatioX96 = getPoolX96Price();\n (_amountWeth, _amountOethb) = helper.principal(\n positionManager,\n tokenId,\n _sqrtRatioX96\n );\n }\n\n /**\n * @notice Returns the current pool price in X96 format\n * @return _sqrtRatioX96 Pool price\n */\n function getPoolX96Price() public view returns (uint160 _sqrtRatioX96) {\n (_sqrtRatioX96, , , , , ) = clPool.slot0();\n }\n\n /**\n * @notice Returns the current active trading tick of the underlying pool\n * @return _currentTick Current pool trading tick\n */\n function getCurrentTradingTick() public view returns (int24 _currentTick) {\n (, _currentTick, , , , ) = clPool.slot0();\n }\n\n /**\n * @notice Returns the percentage of WETH liquidity in the configured ticker\n * owned by this strategy contract.\n * @return uint256 1e18 denominated percentage expressing the share\n */\n function getWETHShare() external view returns (uint256) {\n uint160 _currentPrice = getPoolX96Price();\n return _getWethShare(_currentPrice);\n }\n\n /**\n * @notice Returns the amount of liquidity in the contract's LP position\n * @return _liquidity Amount of liquidity in the position\n */\n function _getLiquidity() internal view returns (uint128 _liquidity) {\n if (tokenId == 0) {\n revert(\"No LP position\");\n }\n\n (, , , , , , , _liquidity, , , , ) = positionManager.positions(tokenId);\n }\n\n function _getWethShare(uint160 _currentPrice)\n internal\n view\n returns (uint256)\n {\n /**\n * If estimateAmount1 call fails it could be due to _currentPrice being really\n * close to a tick and amount1 too big to compute.\n *\n * If token addresses were reversed estimateAmount0 would be required here\n */\n uint256 _normalizedWethAmount = 1 ether;\n uint256 _correspondingOethAmount = helper.estimateAmount1(\n _normalizedWethAmount,\n address(0), // no need to pass pool address when current price is specified\n _currentPrice,\n lowerTick,\n upperTick\n );\n\n // 18 decimal number expressed weth tick share\n return\n _normalizedWethAmount.divPrecisely(\n _normalizedWethAmount + _correspondingOethAmount\n );\n }\n\n /***************************************\n Hidden functions\n ****************************************/\n /// @inheritdoc InitializableAbstractStrategy\n function setPTokenAddress(address, address) external override {\n // The pool tokens can never change.\n revert(\"Unsupported method\");\n }\n\n /// @inheritdoc InitializableAbstractStrategy\n function removePToken(uint256) external override {\n // The pool tokens can never change.\n revert(\"Unsupported method\");\n }\n\n /**\n * @dev Not supported\n */\n function _abstractSetPToken(address, address) internal override {\n // the deployer shall call safeApproveAllTokens() to set necessary approvals\n revert(\"Unsupported method\");\n }\n\n /***************************************\n ERC721 management\n ****************************************/\n\n /// @notice Callback function for whenever a NFT is transferred to this contract\n // solhint-disable-next-line max-line-length\n /// Ref: https://docs.openzeppelin.com/contracts/3.x/api/token/erc721#IERC721Receiver-onERC721Received-address-address-uint256-bytes-\n function onERC721Received(\n address,\n address,\n uint256,\n bytes calldata\n ) external returns (bytes4) {\n return this.onERC721Received.selector;\n }\n}\n" + }, + "contracts/strategies/balancer/AbstractAuraStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title OETH Base Balancer Abstract Strategy\n * @author Origin Protocol Inc\n */\n\nimport { AbstractBalancerStrategy } from \"./AbstractBalancerStrategy.sol\";\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport { IERC20 } from \"../../utils/InitializableAbstractStrategy.sol\";\nimport { IERC4626 } from \"../../../lib/openzeppelin/interfaces/IERC4626.sol\";\nimport { StableMath } from \"../../utils/StableMath.sol\";\nimport { IRewardStaking } from \"../IRewardStaking.sol\";\n\nabstract contract AbstractAuraStrategy is AbstractBalancerStrategy {\n using SafeERC20 for IERC20;\n using StableMath for uint256;\n\n /// @notice Address of the Aura rewards pool\n address public immutable auraRewardPoolAddress;\n\n // renamed from __reserved to not shadow AbstractBalancerStrategy.__reserved,\n int256[50] private __reserved_baseAuraStrategy;\n\n constructor(address _auraRewardPoolAddress) {\n auraRewardPoolAddress = _auraRewardPoolAddress;\n }\n\n /**\n * @dev Deposit all Balancer Pool Tokens (BPT) in this strategy contract\n * to the Aura rewards pool.\n */\n function _lpDepositAll() internal virtual override {\n uint256 bptBalance = IERC20(platformAddress).balanceOf(address(this));\n uint256 auraLp = IERC4626(auraRewardPoolAddress).deposit(\n bptBalance,\n address(this)\n );\n require(bptBalance == auraLp, \"Aura LP != BPT\");\n }\n\n /**\n * @dev Withdraw `numBPTTokens` Balancer Pool Tokens (BPT) from\n * the Aura rewards pool to this strategy contract.\n * @param numBPTTokens Number of Balancer Pool Tokens (BPT) to withdraw\n */\n function _lpWithdraw(uint256 numBPTTokens) internal virtual override {\n IRewardStaking(auraRewardPoolAddress).withdrawAndUnwrap(\n numBPTTokens,\n true // also claim reward tokens\n );\n }\n\n /**\n * @dev Withdraw all Balancer Pool Tokens (BPT) from\n * the Aura rewards pool to this strategy contract.\n */\n function _lpWithdrawAll() internal virtual override {\n // Get all the strategy's BPTs in Aura\n // maxRedeem is implemented as balanceOf(address) in Aura\n uint256 bptBalance = IERC4626(auraRewardPoolAddress).maxRedeem(\n address(this)\n );\n\n IRewardStaking(auraRewardPoolAddress).withdrawAndUnwrap(\n bptBalance,\n true // also claim reward tokens\n );\n }\n\n /**\n * @notice Collects BAL and AURA tokens from the rewards pool.\n */\n function collectRewardTokens()\n external\n virtual\n override\n onlyHarvester\n nonReentrant\n {\n /* Similar to Convex, calling this function collects both of the\n * accrued BAL and AURA tokens.\n */\n IRewardStaking(auraRewardPoolAddress).getReward();\n _collectRewardTokens();\n }\n\n /// @notice Balancer Pool Tokens (BPT) in the Balancer pool and the Aura rewards pool.\n function _getBalancerPoolTokens()\n internal\n view\n override\n returns (uint256 balancerPoolTokens)\n {\n balancerPoolTokens =\n IERC20(platformAddress).balanceOf(address(this)) +\n // maxRedeem is implemented as balanceOf(address) in Aura\n IERC4626(auraRewardPoolAddress).maxRedeem(address(this));\n }\n\n function _approveBase() internal virtual override {\n super._approveBase();\n\n IERC20 pToken = IERC20(platformAddress);\n pToken.safeApprove(auraRewardPoolAddress, type(uint256).max);\n }\n}\n" + }, + "contracts/strategies/balancer/AbstractBalancerStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title OETH Balancer Abstract Strategy\n * @author Origin Protocol Inc\n */\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport { IERC20, InitializableAbstractStrategy } from \"../../utils/InitializableAbstractStrategy.sol\";\nimport { IBalancerVault } from \"../../interfaces/balancer/IBalancerVault.sol\";\nimport { IRateProvider } from \"../../interfaces/balancer/IRateProvider.sol\";\nimport { VaultReentrancyLib } from \"./VaultReentrancyLib.sol\";\nimport { IOracle } from \"../../interfaces/IOracle.sol\";\nimport { IWstETH } from \"../../interfaces/IWstETH.sol\";\nimport { IERC4626 } from \"../../../lib/openzeppelin/interfaces/IERC4626.sol\";\nimport { StableMath } from \"../../utils/StableMath.sol\";\n\nabstract contract AbstractBalancerStrategy is InitializableAbstractStrategy {\n using SafeERC20 for IERC20;\n using StableMath for uint256;\n\n address public immutable rETH;\n address public immutable stETH;\n address public immutable wstETH;\n address public immutable frxETH;\n address public immutable sfrxETH;\n\n /// @notice Address of the Balancer vault\n IBalancerVault public immutable balancerVault;\n /// @notice Balancer pool identifier\n bytes32 public immutable balancerPoolId;\n\n // Max withdrawal deviation denominated in 1e18 (1e18 == 100%)\n uint256 public maxWithdrawalDeviation;\n // Max deposit deviation denominated in 1e18 (1e18 == 100%)\n uint256 public maxDepositDeviation;\n\n int256[48] private __reserved;\n\n struct BaseBalancerConfig {\n address rEthAddress; // Address of the rETH token\n address stEthAddress; // Address of the stETH token\n address wstEthAddress; // Address of the wstETH token\n address frxEthAddress; // Address of the frxEth token\n address sfrxEthAddress; // Address of the sfrxEth token\n address balancerVaultAddress; // Address of the Balancer vault\n bytes32 balancerPoolId; // Balancer pool identifier\n }\n\n event MaxWithdrawalDeviationUpdated(\n uint256 _prevMaxDeviationPercentage,\n uint256 _newMaxDeviationPercentage\n );\n event MaxDepositDeviationUpdated(\n uint256 _prevMaxDeviationPercentage,\n uint256 _newMaxDeviationPercentage\n );\n\n /**\n * @dev Ensure we are not in a Vault context when this function is called, by attempting a no-op internal\n * balance operation. If we are already in a Vault transaction (e.g., a swap, join, or exit), the Vault's\n * reentrancy protection will cause this function to revert.\n *\n * Use this modifier with any function that can cause a state change in a pool and is either public itself,\n * or called by a public function *outside* a Vault operation (e.g., join, exit, or swap).\n *\n * This is to protect against Balancer's read-only re-entrancy vulnerability:\n * https://www.notion.so/originprotocol/Balancer-read-only-reentrancy-c686e72c82414ef18fa34312bb02e11b\n */\n modifier whenNotInBalancerVaultContext() {\n VaultReentrancyLib.ensureNotInVaultContext(balancerVault);\n _;\n }\n\n constructor(BaseBalancerConfig memory _balancerConfig) {\n rETH = _balancerConfig.rEthAddress;\n stETH = _balancerConfig.stEthAddress;\n wstETH = _balancerConfig.wstEthAddress;\n frxETH = _balancerConfig.frxEthAddress;\n sfrxETH = _balancerConfig.sfrxEthAddress;\n\n balancerVault = IBalancerVault(_balancerConfig.balancerVaultAddress);\n balancerPoolId = _balancerConfig.balancerPoolId;\n }\n\n /**\n * Initializer for setting up strategy internal state. This overrides the\n * InitializableAbstractStrategy initializer as Balancer's strategies don't fit\n * well within that abstraction.\n * @param _rewardTokenAddresses Address of BAL & AURA\n * @param _assets Addresses of supported assets. MUST be passed in the same\n * order as returned by coins on the pool contract, i.e.\n * WETH, stETH\n * @param _pTokens Platform Token corresponding addresses\n */\n function initialize(\n address[] calldata _rewardTokenAddresses, // BAL & AURA\n address[] calldata _assets,\n address[] calldata _pTokens\n ) external onlyGovernor initializer {\n maxWithdrawalDeviation = 1e16;\n maxDepositDeviation = 1e16;\n\n emit MaxWithdrawalDeviationUpdated(0, maxWithdrawalDeviation);\n emit MaxDepositDeviationUpdated(0, maxDepositDeviation);\n\n IERC20[] memory poolAssets = _getPoolAssets();\n require(\n poolAssets.length == _assets.length,\n \"Pool assets length mismatch\"\n );\n for (uint256 i = 0; i < _assets.length; ++i) {\n address asset = _fromPoolAsset(address(poolAssets[i]));\n require(_assets[i] == asset, \"Pool assets mismatch\");\n }\n\n InitializableAbstractStrategy._initialize(\n _rewardTokenAddresses,\n _assets,\n _pTokens\n );\n _approveBase();\n }\n\n /**\n * @notice Returns bool indicating whether asset is supported by strategy\n * @param _asset Address of the asset\n */\n function supportsAsset(address _asset) public view override returns (bool) {\n return assetToPToken[_asset] != address(0);\n }\n\n /**\n * @notice Get strategy's share of an assets in the Balancer pool.\n * This is not denominated in OUSD/ETH value of the assets in the Balancer pool.\n * @param _asset Address of the Vault collateral asset\n * @return amount the amount of vault collateral assets\n *\n * IMPORTANT if this function is overridden it needs to have a whenNotInBalancerVaultContext\n * modifier on it or it is susceptible to read-only re-entrancy attack\n *\n * @dev it is important that this function is not affected by reporting inflated\n * values of assets in case of any pool manipulation. Such a manipulation could easily\n * exploit the protocol by:\n * - minting OETH\n * - tilting Balancer pool to report higher balances of assets\n * - rebasing() -> all that extra token balances get distributed to OETH holders\n * - tilting pool back\n * - redeeming OETH\n */\n function checkBalance(address _asset)\n external\n view\n virtual\n override\n whenNotInBalancerVaultContext\n returns (uint256 amount)\n {\n require(assetToPToken[_asset] != address(0), \"Unsupported asset\");\n\n uint256 bptBalance = _getBalancerPoolTokens();\n\n /* To calculate the worth of queried asset:\n * - assume that all tokens normalized to their ETH value have an equal split balance\n * in the pool when it is balanced\n * - multiply the BPT amount with the bpt rate to get the ETH denominated amount\n * of strategy's holdings\n * - divide that by the number of tokens we support in the pool to get ETH denominated\n * amount that is applicable to each supported token in the pool.\n *\n * It would be possible to support only 1 asset in the pool (and be exposed to all\n * the assets while holding BPT tokens) and deposit/withdraw/checkBalance using only\n * that asset. TBD: changes to other functions still required if we ever decide to\n * go with such configuration.\n */\n amount = (bptBalance.mulTruncate(\n IRateProvider(platformAddress).getRate()\n ) / assetsMapped.length);\n\n /* If the pool asset is equal to (strategy )_asset it means that a rate\n * provider for that asset exists and that asset is not necessarily\n * pegged to a unit (ETH).\n *\n * Because this function returns the balance of the asset and is not denominated in\n * ETH units we need to convert the ETH denominated amount to asset amount.\n */\n if (_toPoolAsset(_asset) == _asset) {\n amount = amount.divPrecisely(_getRateProviderRate(_asset));\n }\n }\n\n /**\n * @notice Returns the value of all assets managed by this strategy.\n * Uses the Balancer pool's rate (virtual price) to convert the strategy's\n * Balancer Pool Tokens (BPT) to ETH value.\n * @return value The ETH value\n *\n * IMPORTANT if this function is overridden it needs to have a whenNotInBalancerVaultContext\n * modifier on it or it is susceptible to read-only re-entrancy attack\n */\n function checkBalance()\n external\n view\n virtual\n whenNotInBalancerVaultContext\n returns (uint256 value)\n {\n uint256 bptBalance = _getBalancerPoolTokens();\n\n // Convert BPT to ETH value\n value = bptBalance.mulTruncate(\n IRateProvider(platformAddress).getRate()\n );\n }\n\n /// @notice Balancer Pool Tokens (BPT) in the Balancer pool.\n function _getBalancerPoolTokens()\n internal\n view\n virtual\n returns (uint256 balancerPoolTokens)\n {\n balancerPoolTokens = IERC20(platformAddress).balanceOf(address(this));\n }\n\n /* solhint-disable max-line-length */\n /**\n * @notice BPT price is calculated by taking the rate from the rateProvider of the asset in\n * question. If one does not exist it defaults to 1e18. To get the final BPT expected that\n * is multiplied by the underlying asset amount divided by BPT token rate. BPT token rate is\n * similar to Curve's virtual_price and expresses how much has the price of BPT appreciated\n * (e.g. due to swap fees) in relation to the underlying assets\n *\n * Using the above approach makes the strategy vulnerable to a possible MEV attack using\n * flash loan to manipulate the pool before a deposit/withdrawal since the function ignores\n * market values of the assets being priced in BPT.\n *\n * At the time of writing there is no safe on-chain approach to pricing BPT in a way that it\n * would make it invulnerable to MEV pool manipulation. See recent Balancer exploit:\n * https://www.notion.so/originprotocol/Balancer-OETH-strategy-9becdea132704e588782a919d7d471eb?pvs=4#1cf07de12fc64f1888072321e0644348\n *\n * To mitigate MEV possibilities during deposits and withdraws, the VaultValueChecker will use checkBalance before and after the move\n * to ensure the expected changes took place.\n *\n * @param _asset Address of the Balancer pool asset\n * @param _amount Amount of the Balancer pool asset\n * @return bptExpected of BPT expected in exchange for the asset\n *\n * @dev\n * bptAssetPrice = 1e18 (asset peg) * pool_asset_rate\n *\n * bptExpected = bptAssetPrice * asset_amount / BPT_token_rate\n *\n * bptExpected = 1e18 (asset peg) * pool_asset_rate * asset_amount / BPT_token_rate\n * bptExpected = asset_amount * pool_asset_rate / BPT_token_rate\n *\n * further information available here:\n * https://www.notion.so/originprotocol/Balancer-OETH-strategy-9becdea132704e588782a919d7d471eb?pvs=4#ce01495ae70346d8971f5dced809fb83\n */\n /* solhint-enable max-line-length */\n function _getBPTExpected(address _asset, uint256 _amount)\n internal\n view\n virtual\n returns (uint256 bptExpected)\n {\n uint256 bptRate = IRateProvider(platformAddress).getRate();\n uint256 poolAssetRate = _getRateProviderRate(_asset);\n bptExpected = _amount.mulTruncate(poolAssetRate).divPrecisely(bptRate);\n }\n\n function _getBPTExpected(\n address[] memory _assets,\n uint256[] memory _amounts\n ) internal view virtual returns (uint256 bptExpected) {\n require(_assets.length == _amounts.length, \"Assets & amounts mismatch\");\n\n for (uint256 i = 0; i < _assets.length; ++i) {\n uint256 poolAssetRate = _getRateProviderRate(_assets[i]);\n // convert asset amount to ETH amount\n bptExpected += _amounts[i].mulTruncate(poolAssetRate);\n }\n\n uint256 bptRate = IRateProvider(platformAddress).getRate();\n // Convert ETH amount to BPT amount\n bptExpected = bptExpected.divPrecisely(bptRate);\n }\n\n function _lpDepositAll() internal virtual;\n\n function _lpWithdraw(uint256 numBPTTokens) internal virtual;\n\n function _lpWithdrawAll() internal virtual;\n\n /**\n * @notice Balancer returns assets and rateProviders for corresponding assets ordered\n * by numerical order.\n */\n function _getPoolAssets() internal view returns (IERC20[] memory assets) {\n // slither-disable-next-line unused-return\n (assets, , ) = balancerVault.getPoolTokens(balancerPoolId);\n }\n\n /**\n * @dev If an asset is rebasing the Balancer pools have a wrapped versions of assets\n * that the strategy supports. This function converts the pool(wrapped) asset\n * and corresponding amount to strategy asset.\n */\n function _toPoolAsset(address asset, uint256 amount)\n internal\n view\n returns (address poolAsset, uint256 poolAmount)\n {\n if (asset == stETH) {\n poolAsset = wstETH;\n if (amount > 0) {\n poolAmount = IWstETH(wstETH).getWstETHByStETH(amount);\n }\n } else if (asset == frxETH) {\n poolAsset = sfrxETH;\n if (amount > 0) {\n poolAmount = IERC4626(sfrxETH).convertToShares(amount);\n }\n } else {\n poolAsset = asset;\n poolAmount = amount;\n }\n }\n\n /**\n * @dev Converts a Vault collateral asset to a Balancer pool asset.\n * stETH becomes wstETH, frxETH becomes sfrxETH and everything else stays the same.\n * @param asset Address of the Vault collateral asset.\n * @return Address of the Balancer pool asset.\n */\n function _toPoolAsset(address asset) internal view returns (address) {\n if (asset == stETH) {\n return wstETH;\n } else if (asset == frxETH) {\n return sfrxETH;\n }\n return asset;\n }\n\n /**\n * @dev Converts rebasing asset to its wrapped counterpart.\n */\n function _wrapPoolAsset(address asset, uint256 amount)\n internal\n returns (address wrappedAsset, uint256 wrappedAmount)\n {\n if (asset == stETH) {\n wrappedAsset = wstETH;\n if (amount > 0) {\n wrappedAmount = IWstETH(wstETH).wrap(amount);\n }\n } else if (asset == frxETH) {\n wrappedAsset = sfrxETH;\n if (amount > 0) {\n wrappedAmount = IERC4626(sfrxETH).deposit(\n amount,\n address(this)\n );\n }\n } else {\n wrappedAsset = asset;\n wrappedAmount = amount;\n }\n }\n\n /**\n * @dev Converts wrapped asset to its rebasing counterpart.\n */\n function _unwrapPoolAsset(address asset, uint256 amount)\n internal\n returns (uint256 unwrappedAmount)\n {\n if (asset == stETH) {\n unwrappedAmount = IWstETH(wstETH).unwrap(amount);\n } else if (asset == frxETH) {\n unwrappedAmount = IERC4626(sfrxETH).withdraw(\n amount,\n address(this),\n address(this)\n );\n } else {\n unwrappedAmount = amount;\n }\n }\n\n /**\n * @dev If an asset is rebasing the Balancer pools have a wrapped versions of assets\n * that the strategy supports. This function converts the rebasing strategy asset\n * and corresponding amount to wrapped(pool) asset.\n */\n function _fromPoolAsset(address poolAsset, uint256 poolAmount)\n internal\n view\n returns (address asset, uint256 amount)\n {\n if (poolAsset == wstETH) {\n asset = stETH;\n if (poolAmount > 0) {\n amount = IWstETH(wstETH).getStETHByWstETH(poolAmount);\n }\n } else if (poolAsset == sfrxETH) {\n asset = frxETH;\n if (poolAmount > 0) {\n amount = IERC4626(sfrxETH).convertToAssets(poolAmount);\n }\n } else {\n asset = poolAsset;\n amount = poolAmount;\n }\n }\n\n function _fromPoolAsset(address poolAsset)\n internal\n view\n returns (address asset)\n {\n if (poolAsset == wstETH) {\n asset = stETH;\n } else if (poolAsset == sfrxETH) {\n asset = frxETH;\n } else {\n asset = poolAsset;\n }\n }\n\n /**\n * @notice Sets max withdrawal deviation that is considered when removing\n * liquidity from Balancer pools.\n * @param _maxWithdrawalDeviation Max withdrawal deviation denominated in\n * wad (number with 18 decimals): 1e18 == 100%, 1e16 == 1%\n *\n * IMPORTANT Minimum maxWithdrawalDeviation will be 1% (1e16) for production\n * usage. Vault value checker in combination with checkBalance will\n * catch any unexpected manipulation.\n */\n function setMaxWithdrawalDeviation(uint256 _maxWithdrawalDeviation)\n external\n onlyVaultOrGovernorOrStrategist\n {\n require(\n _maxWithdrawalDeviation <= 1e18,\n \"Withdrawal dev. out of bounds\"\n );\n emit MaxWithdrawalDeviationUpdated(\n maxWithdrawalDeviation,\n _maxWithdrawalDeviation\n );\n maxWithdrawalDeviation = _maxWithdrawalDeviation;\n }\n\n /**\n * @notice Sets max deposit deviation that is considered when adding\n * liquidity to Balancer pools.\n * @param _maxDepositDeviation Max deposit deviation denominated in\n * wad (number with 18 decimals): 1e18 == 100%, 1e16 == 1%\n *\n * IMPORTANT Minimum maxDepositDeviation will default to 1% (1e16)\n * for production usage. Vault value checker in combination with\n * checkBalance will catch any unexpected manipulation.\n */\n function setMaxDepositDeviation(uint256 _maxDepositDeviation)\n external\n onlyVaultOrGovernorOrStrategist\n {\n require(_maxDepositDeviation <= 1e18, \"Deposit dev. out of bounds\");\n emit MaxDepositDeviationUpdated(\n maxDepositDeviation,\n _maxDepositDeviation\n );\n maxDepositDeviation = _maxDepositDeviation;\n }\n\n function _approveBase() internal virtual {\n IERC20 pToken = IERC20(platformAddress);\n // Balancer vault for BPT token (required for removing liquidity)\n pToken.safeApprove(address(balancerVault), type(uint256).max);\n }\n\n function _getRateProviderRate(address _asset)\n internal\n view\n virtual\n returns (uint256);\n}\n" + }, + "contracts/strategies/balancer/BalancerMetaPoolStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title OETH Balancer MetaStablePool Strategy\n * @author Origin Protocol Inc\n */\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport { AbstractAuraStrategy, AbstractBalancerStrategy } from \"./AbstractAuraStrategy.sol\";\nimport { IBalancerVault } from \"../../interfaces/balancer/IBalancerVault.sol\";\nimport { IRateProvider } from \"../../interfaces/balancer/IRateProvider.sol\";\nimport { IMetaStablePool } from \"../../interfaces/balancer/IMetaStablePool.sol\";\nimport { IERC20, InitializableAbstractStrategy } from \"../../utils/InitializableAbstractStrategy.sol\";\nimport { StableMath } from \"../../utils/StableMath.sol\";\n\ncontract BalancerMetaPoolStrategy is AbstractAuraStrategy {\n using SafeERC20 for IERC20;\n using StableMath for uint256;\n\n constructor(\n BaseStrategyConfig memory _stratConfig,\n BaseBalancerConfig memory _balancerConfig,\n address _auraRewardPoolAddress\n )\n InitializableAbstractStrategy(_stratConfig)\n AbstractBalancerStrategy(_balancerConfig)\n AbstractAuraStrategy(_auraRewardPoolAddress)\n {}\n\n /**\n * @notice There are no plans to configure BalancerMetaPool as a default\n * asset strategy. For that reason there is no need to support this\n * functionality.\n */\n function deposit(address, uint256)\n external\n override\n onlyVault\n nonReentrant\n {\n revert(\"Not supported\");\n }\n\n /**\n * @notice There are no plans to configure BalancerMetaPool as a default\n * asset strategy. For that reason there is no need to support this\n * functionality.\n */\n function deposit(address[] calldata, uint256[] calldata)\n external\n onlyVault\n nonReentrant\n {\n revert(\"Not supported\");\n }\n\n /**\n * @notice Deposits all supported assets in this strategy contract to the Balancer pool.\n */\n function depositAll() external override onlyVault nonReentrant {\n uint256 assetsLength = assetsMapped.length;\n address[] memory strategyAssets = new address[](assetsLength);\n uint256[] memory strategyAmounts = new uint256[](assetsLength);\n\n // For each vault collateral asset\n for (uint256 i = 0; i < assetsLength; ++i) {\n strategyAssets[i] = assetsMapped[i];\n // Get the asset balance in this strategy contract\n strategyAmounts[i] = IERC20(strategyAssets[i]).balanceOf(\n address(this)\n );\n }\n _deposit(strategyAssets, strategyAmounts);\n }\n\n /*\n * _deposit doesn't require a read-only re-entrancy protection since during the deposit\n * the function enters the Balancer Vault Context. If this function were called as part of\n * the attacking contract (while intercepting execution flow upon receiving ETH) the read-only\n * protection of the Balancer Vault would be triggered. Since the attacking contract would\n * already be in the Balancer Vault context and wouldn't be able to enter it again.\n */\n function _deposit(\n address[] memory _strategyAssets,\n uint256[] memory _strategyAmounts\n ) internal {\n require(\n _strategyAssets.length == _strategyAmounts.length,\n \"Array length missmatch\"\n );\n\n (IERC20[] memory tokens, , ) = balancerVault.getPoolTokens(\n balancerPoolId\n );\n\n uint256[] memory strategyAssetAmountsToPoolAssetAmounts = new uint256[](\n _strategyAssets.length\n );\n address[] memory strategyAssetsToPoolAssets = new address[](\n _strategyAssets.length\n );\n\n for (uint256 i = 0; i < _strategyAssets.length; ++i) {\n address strategyAsset = _strategyAssets[i];\n uint256 strategyAmount = _strategyAmounts[i];\n\n require(\n assetToPToken[strategyAsset] != address(0),\n \"Unsupported asset\"\n );\n strategyAssetsToPoolAssets[i] = _toPoolAsset(strategyAsset);\n\n if (strategyAmount > 0) {\n emit Deposit(strategyAsset, platformAddress, strategyAmount);\n\n // wrap rebasing assets like stETH and frxETH to wstETH and sfrxETH\n (, strategyAssetAmountsToPoolAssetAmounts[i]) = _wrapPoolAsset(\n strategyAsset,\n strategyAmount\n );\n }\n }\n\n uint256[] memory amountsIn = new uint256[](tokens.length);\n address[] memory poolAssets = new address[](tokens.length);\n for (uint256 i = 0; i < tokens.length; ++i) {\n // Convert IERC20 type to address\n poolAssets[i] = address(tokens[i]);\n\n // For each of the mapped assets\n for (uint256 j = 0; j < strategyAssetsToPoolAssets.length; ++j) {\n // If the pool asset is the same as the mapped asset\n if (poolAssets[i] == strategyAssetsToPoolAssets[j]) {\n amountsIn[i] = strategyAssetAmountsToPoolAssetAmounts[j];\n }\n }\n }\n\n uint256 minBPT = _getBPTExpected(\n strategyAssetsToPoolAssets,\n strategyAssetAmountsToPoolAssetAmounts\n );\n uint256 minBPTwDeviation = minBPT.mulTruncate(\n 1e18 - maxDepositDeviation\n );\n\n /* EXACT_TOKENS_IN_FOR_BPT_OUT:\n * User sends precise quantities of tokens, and receives an\n * estimated but unknown (computed at run time) quantity of BPT.\n *\n * ['uint256', 'uint256[]', 'uint256']\n * [EXACT_TOKENS_IN_FOR_BPT_OUT, amountsIn, minimumBPT]\n */\n bytes memory userData = abi.encode(\n IBalancerVault.WeightedPoolJoinKind.EXACT_TOKENS_IN_FOR_BPT_OUT,\n amountsIn,\n minBPTwDeviation\n );\n\n IBalancerVault.JoinPoolRequest memory request = IBalancerVault\n .JoinPoolRequest(poolAssets, amountsIn, userData, false);\n\n // Add the pool assets in this strategy to the balancer pool\n balancerVault.joinPool(\n balancerPoolId,\n address(this),\n address(this),\n request\n );\n\n // Deposit the Balancer Pool Tokens (BPT) into Aura\n _lpDepositAll();\n }\n\n /**\n * @notice Withdraw a Vault collateral asset from the Balancer pool.\n * @param _recipient Address to receive the Vault collateral assets. Typically is the Vault.\n * @param _strategyAsset Address of the Vault collateral asset\n * @param _strategyAmount The amount of Vault collateral assets to withdraw\n */\n function withdraw(\n address _recipient,\n address _strategyAsset,\n uint256 _strategyAmount\n ) external override onlyVault nonReentrant {\n address[] memory strategyAssets = new address[](1);\n uint256[] memory strategyAmounts = new uint256[](1);\n strategyAssets[0] = _strategyAsset;\n strategyAmounts[0] = _strategyAmount;\n\n _withdraw(_recipient, strategyAssets, strategyAmounts);\n }\n\n /**\n * @notice Withdraw multiple Vault collateral asset from the Balancer pool.\n * @param _recipient Address to receive the Vault collateral assets. Typically is the Vault.\n * @param _strategyAssets Addresses of the Vault collateral assets\n * @param _strategyAmounts The amounts of Vault collateral assets to withdraw\n */\n function withdraw(\n address _recipient,\n address[] calldata _strategyAssets,\n uint256[] calldata _strategyAmounts\n ) external onlyVault nonReentrant {\n _withdraw(_recipient, _strategyAssets, _strategyAmounts);\n }\n\n /**\n * @dev Withdraw multiple Vault collateral asset from the Balancer pool.\n * @param _recipient Address to receive the Vault collateral assets. Typically is the Vault.\n * @param _strategyAssets Addresses of the Vault collateral assets\n * @param _strategyAmounts The amounts of Vault collateral assets to withdraw\n *\n * _withdrawal doesn't require a read-only re-entrancy protection since during the withdrawal\n * the function enters the Balancer Vault Context. If this function were called as part of\n * the attacking contract (while intercepting execution flow upon receiving ETH) the read-only\n * protection of the Balancer Vault would be triggered. Since the attacking contract would\n * already be in the Balancer Vault context and wouldn't be able to enter it again.\n */\n function _withdraw(\n address _recipient,\n address[] memory _strategyAssets,\n uint256[] memory _strategyAmounts\n ) internal {\n require(\n _strategyAssets.length == _strategyAmounts.length,\n \"Invalid input arrays\"\n );\n\n for (uint256 i = 0; i < _strategyAssets.length; ++i) {\n require(\n assetToPToken[_strategyAssets[i]] != address(0),\n \"Unsupported asset\"\n );\n }\n\n // STEP 1 - Calculate the Balancer pool assets and amounts from the vault collateral assets\n\n // Get all the supported balancer pool assets\n (IERC20[] memory tokens, , ) = balancerVault.getPoolTokens(\n balancerPoolId\n );\n // Calculate the balancer pool assets and amounts to withdraw\n uint256[] memory poolAssetsAmountsOut = new uint256[](tokens.length);\n address[] memory poolAssets = new address[](tokens.length);\n // Is the wrapped asset amount indexed by the assets array, not the order of the Balancer pool tokens\n // eg wstETH and sfrxETH amounts, not the stETH and frxETH amounts\n uint256[] memory strategyAssetsToPoolAssetsAmounts = new uint256[](\n _strategyAssets.length\n );\n\n // For each of the Balancer pool assets\n for (uint256 i = 0; i < tokens.length; ++i) {\n poolAssets[i] = address(tokens[i]);\n\n // Convert the Balancer pool asset back to a vault collateral asset\n address strategyAsset = _fromPoolAsset(poolAssets[i]);\n\n // for each of the vault assets\n for (uint256 j = 0; j < _strategyAssets.length; ++j) {\n // If the vault asset equals the vault asset mapped from the Balancer pool asset\n if (_strategyAssets[j] == strategyAsset) {\n (, poolAssetsAmountsOut[i]) = _toPoolAsset(\n strategyAsset,\n _strategyAmounts[j]\n );\n strategyAssetsToPoolAssetsAmounts[j] = poolAssetsAmountsOut[\n i\n ];\n\n /* Because of the potential Balancer rounding error mentioned below\n * the contract might receive 1-2 WEI smaller amount than required\n * in the withdraw user data encoding. If slightly lesser token amount\n * is received the strategy can not unwrap the pool asset as it is\n * smaller than expected.\n *\n * For that reason we `overshoot` the required tokens expected to\n * circumvent the error\n */\n if (poolAssetsAmountsOut[i] > 0) {\n poolAssetsAmountsOut[i] += 2;\n }\n }\n }\n }\n\n // STEP 2 - Calculate the max about of Balancer Pool Tokens (BPT) to withdraw\n\n // Estimate the required amount of Balancer Pool Tokens (BPT) for the assets\n uint256 maxBPTtoWithdraw = _getBPTExpected(\n poolAssets,\n /* all non 0 values are overshot by 2 WEI and with the expected mainnet\n * ~1% withdrawal deviation, the 2 WEI aren't important\n */\n poolAssetsAmountsOut\n );\n // Increase BPTs by the max allowed deviation\n // Any excess BPTs will be left in this strategy contract\n maxBPTtoWithdraw = maxBPTtoWithdraw.mulTruncate(\n 1e18 + maxWithdrawalDeviation\n );\n\n // STEP 3 - Withdraw the Balancer Pool Tokens (BPT) from Aura to this strategy contract\n\n // Withdraw BPT from Aura allowing for BPTs left in this strategy contract from previous withdrawals\n _lpWithdraw(\n maxBPTtoWithdraw - IERC20(platformAddress).balanceOf(address(this))\n );\n\n // STEP 4 - Withdraw the balancer pool assets from the pool\n\n /* Custom asset exit: BPT_IN_FOR_EXACT_TOKENS_OUT:\n * User sends an estimated but unknown (computed at run time) quantity of BPT,\n * and receives precise quantities of specified tokens.\n *\n * ['uint256', 'uint256[]', 'uint256']\n * [BPT_IN_FOR_EXACT_TOKENS_OUT, amountsOut, maxBPTAmountIn]\n */\n bytes memory userData = abi.encode(\n IBalancerVault.WeightedPoolExitKind.BPT_IN_FOR_EXACT_TOKENS_OUT,\n poolAssetsAmountsOut,\n maxBPTtoWithdraw\n );\n\n IBalancerVault.ExitPoolRequest memory request = IBalancerVault\n .ExitPoolRequest(\n poolAssets,\n /* We specify the exact amount of a tokens we are expecting in the encoded\n * userData, for that reason we don't need to specify the amountsOut here.\n *\n * Also Balancer has a rounding issue that can make a transaction fail:\n * https://github.com/balancer/balancer-v2-monorepo/issues/2541\n * which is an extra reason why this field is empty.\n */\n new uint256[](tokens.length),\n userData,\n false\n );\n\n balancerVault.exitPool(\n balancerPoolId,\n address(this),\n /* Payable keyword is required because of the IBalancerVault interface even though\n * this strategy shall never be receiving native ETH\n */\n payable(address(this)),\n request\n );\n\n // STEP 5 - Re-deposit any left over BPT tokens back into Aura\n /* When concluding how much of BPT we need to withdraw from Aura we overshoot by\n * roughly around 1% (initial mainnet setting of maxWithdrawalDeviation). After exiting\n * the pool strategy could have left over BPT tokens that are not earning boosted yield.\n * We re-deploy those back in.\n */\n _lpDepositAll();\n\n // STEP 6 - Unswap balancer pool assets to vault collateral assets and send to the vault.\n\n // For each of the specified assets\n for (uint256 i = 0; i < _strategyAssets.length; ++i) {\n // Unwrap assets like wstETH and sfrxETH to rebasing assets stETH and frxETH\n if (strategyAssetsToPoolAssetsAmounts[i] > 0) {\n _unwrapPoolAsset(\n _strategyAssets[i],\n strategyAssetsToPoolAssetsAmounts[i]\n );\n }\n\n // Transfer the vault collateral assets to the recipient, which is typically the vault\n if (_strategyAmounts[i] > 0) {\n IERC20(_strategyAssets[i]).safeTransfer(\n _recipient,\n _strategyAmounts[i]\n );\n\n emit Withdrawal(\n _strategyAssets[i],\n platformAddress,\n _strategyAmounts[i]\n );\n }\n }\n }\n\n /**\n * @notice Withdraws all supported Vault collateral assets from the Balancer pool\n * and send to the OToken's Vault.\n *\n * Is only executable by the OToken's Vault or the Governor.\n */\n function withdrawAll() external override onlyVaultOrGovernor nonReentrant {\n // STEP 1 - Withdraw all Balancer Pool Tokens (BPT) from Aura to this strategy contract\n\n _lpWithdrawAll();\n // Get the BPTs withdrawn from Aura plus any that were already in this strategy contract\n uint256 BPTtoWithdraw = IERC20(platformAddress).balanceOf(\n address(this)\n );\n // Get the balancer pool assets and their total balances\n (IERC20[] memory tokens, , ) = balancerVault.getPoolTokens(\n balancerPoolId\n );\n uint256[] memory minAmountsOut = new uint256[](tokens.length);\n address[] memory poolAssets = new address[](tokens.length);\n for (uint256 i = 0; i < tokens.length; ++i) {\n poolAssets[i] = address(tokens[i]);\n }\n\n // STEP 2 - Withdraw the Balancer pool assets from the pool\n /* Proportional exit: EXACT_BPT_IN_FOR_TOKENS_OUT:\n * User sends a precise quantity of BPT, and receives an estimated but unknown\n * (computed at run time) quantity of a single token\n *\n * ['uint256', 'uint256']\n * [EXACT_BPT_IN_FOR_TOKENS_OUT, bptAmountIn]\n *\n * It is ok to pass an empty minAmountsOut since tilting the pool in any direction\n * when doing a proportional exit can only be beneficial to the strategy. Since\n * it will receive more of the underlying tokens for the BPT traded in.\n */\n bytes memory userData = abi.encode(\n IBalancerVault.WeightedPoolExitKind.EXACT_BPT_IN_FOR_TOKENS_OUT,\n BPTtoWithdraw\n );\n\n IBalancerVault.ExitPoolRequest memory request = IBalancerVault\n .ExitPoolRequest(poolAssets, minAmountsOut, userData, false);\n\n balancerVault.exitPool(\n balancerPoolId,\n address(this),\n /* Payable keyword is required because of the IBalancerVault interface even though\n * this strategy shall never be receiving native ETH\n */\n payable(address(this)),\n request\n );\n\n // STEP 3 - Convert the balancer pool assets to the vault collateral assets and send to the vault\n // For each of the Balancer pool assets\n for (uint256 i = 0; i < tokens.length; ++i) {\n address poolAsset = address(tokens[i]);\n // Convert the balancer pool asset to the strategy asset\n address strategyAsset = _fromPoolAsset(poolAsset);\n // Get the balancer pool assets withdraw from the pool plus any that were already in this strategy contract\n uint256 poolAssetAmount = IERC20(poolAsset).balanceOf(\n address(this)\n );\n\n // Unwrap assets like wstETH and sfrxETH to rebasing assets stETH and frxETH\n uint256 unwrappedAmount = 0;\n if (poolAssetAmount > 0) {\n unwrappedAmount = _unwrapPoolAsset(\n strategyAsset,\n poolAssetAmount\n );\n }\n\n // Transfer the vault collateral assets to the vault\n if (unwrappedAmount > 0) {\n IERC20(strategyAsset).safeTransfer(\n vaultAddress,\n unwrappedAmount\n );\n emit Withdrawal(\n strategyAsset,\n platformAddress,\n unwrappedAmount\n );\n }\n }\n }\n\n /**\n * @notice Approves the Balancer Vault to transfer poolAsset counterparts\n * of all of the supported assets from this strategy. E.g. stETH is a supported\n * strategy and Balancer Vault gets unlimited approval to transfer wstETH.\n *\n * If Balancer pool uses a wrapped version of a supported asset then also approve\n * unlimited usage of an asset to the contract responsible for wrapping.\n *\n * Approve unlimited spending by Balancer Vault and Aura reward pool of the\n * pool BPT tokens.\n *\n * Is only executable by the Governor.\n */\n function safeApproveAllTokens()\n external\n override\n onlyGovernor\n nonReentrant\n {\n uint256 assetCount = assetsMapped.length;\n for (uint256 i = 0; i < assetCount; ++i) {\n _abstractSetPToken(assetsMapped[i], platformAddress);\n }\n _approveBase();\n }\n\n // solhint-disable-next-line no-unused-vars\n function _abstractSetPToken(address _asset, address) internal override {\n address poolAsset = _toPoolAsset(_asset);\n if (_asset == stETH) {\n // slither-disable-next-line unused-return\n IERC20(stETH).approve(wstETH, type(uint256).max);\n } else if (_asset == frxETH) {\n // slither-disable-next-line unused-return\n IERC20(frxETH).approve(sfrxETH, type(uint256).max);\n }\n _approveAsset(poolAsset);\n }\n\n /**\n * @dev Approves the Balancer Vault to transfer an asset from\n * this strategy. The assets could be a Vault collateral asset\n * like WETH or rETH; or a Balancer pool asset that wraps the vault asset\n * like wstETH or sfrxETH.\n */\n function _approveAsset(address _asset) internal {\n IERC20 asset = IERC20(_asset);\n // slither-disable-next-line unused-return\n asset.approve(address(balancerVault), type(uint256).max);\n }\n\n /**\n * @notice Returns the rate supplied by the Balancer configured rate\n * provider. Rate is used to normalize the token to common underlying\n * pool denominator. (ETH for ETH Liquid staking derivatives)\n *\n * @param _asset Address of the Balancer pool asset\n * @return rate of the corresponding asset\n */\n function _getRateProviderRate(address _asset)\n internal\n view\n override\n returns (uint256)\n {\n IMetaStablePool pool = IMetaStablePool(platformAddress);\n IRateProvider[] memory providers = pool.getRateProviders();\n (IERC20[] memory tokens, , ) = balancerVault.getPoolTokens(\n balancerPoolId\n );\n\n uint256 providersLength = providers.length;\n for (uint256 i = 0; i < providersLength; ++i) {\n // _assets and corresponding rate providers are all in the same order\n if (address(tokens[i]) == _asset) {\n // rate provider doesn't exist, defaults to 1e18\n if (address(providers[i]) == address(0)) {\n return 1e18;\n }\n return providers[i].getRate();\n }\n }\n\n // should never happen\n assert(false);\n }\n}\n" + }, + "contracts/strategies/balancer/VaultReentrancyLib.sol": { + "content": "// SPDX-License-Identifier: GPL-3.0-or-later\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see .\n\npragma solidity >=0.7.0 <0.9.0;\n\nimport \"../../utils/BalancerErrors.sol\";\nimport { IBalancerVault } from \"../../interfaces/balancer/IBalancerVault.sol\";\n\nlibrary VaultReentrancyLib {\n /**\n * @dev Ensure we are not in a Vault context when this function is called, by attempting a no-op internal\n * balance operation. If we are already in a Vault transaction (e.g., a swap, join, or exit), the Vault's\n * reentrancy protection will cause this function to revert.\n *\n * The exact function call doesn't really matter: we're just trying to trigger the Vault reentrancy check\n * (and not hurt anything in case it works). An empty operation array with no specific operation at all works\n * for that purpose, and is also the least expensive in terms of gas and bytecode size.\n *\n * Call this at the top of any function that can cause a state change in a pool and is either public itself,\n * or called by a public function *outside* a Vault operation (e.g., join, exit, or swap).\n *\n * If this is *not* called in functions that are vulnerable to the read-only reentrancy issue described\n * here (https://forum.balancer.fi/t/reentrancy-vulnerability-scope-expanded/4345), those functions are unsafe,\n * and subject to manipulation that may result in loss of funds.\n */\n function ensureNotInVaultContext(IBalancerVault vault) internal view {\n // Perform the following operation to trigger the Vault's reentrancy guard:\n //\n // IBalancerVault.UserBalanceOp[] memory noop = new IBalancerVault.UserBalanceOp[](0);\n // _vault.manageUserBalance(noop);\n //\n // However, use a static call so that it can be a view function (even though the function is non-view).\n // This allows the library to be used more widely, as some functions that need to be protected might be\n // view.\n //\n // This staticcall always reverts, but we need to make sure it doesn't fail due to a re-entrancy attack.\n // Staticcalls consume all gas forwarded to them on a revert caused by storage modification.\n // By default, almost the entire available gas is forwarded to the staticcall,\n // causing the entire call to revert with an 'out of gas' error.\n //\n // We set the gas limit to 10k for the staticcall to\n // avoid wasting gas when it reverts due to storage modification.\n // `manageUserBalance` is a non-reentrant function in the Vault, so calling it invokes `_enterNonReentrant`\n // in the `ReentrancyGuard` contract, reproduced here:\n //\n // function _enterNonReentrant() private {\n // // If the Vault is actually being reentered, it will revert in the first line, at the `_require` that\n // // checks the reentrancy flag, with \"BAL#400\" (corresponding to Errors.REENTRANCY) in the revertData.\n // // The full revertData will be: `abi.encodeWithSignature(\"Error(string)\", \"BAL#400\")`.\n // _require(_status != _ENTERED, Errors.REENTRANCY);\n //\n // // If the Vault is not being reentered, the check above will pass: but it will *still* revert,\n // // because the next line attempts to modify storage during a staticcall. However, this type of\n // // failure results in empty revertData.\n // _status = _ENTERED;\n // }\n //\n // So based on this analysis, there are only two possible revertData values: empty, or abi.encoded BAL#400.\n //\n // It is of course much more bytecode and gas efficient to check for zero-length revertData than to compare it\n // to the encoded REENTRANCY revertData.\n //\n // While it should be impossible for the call to fail in any other way (especially since it reverts before\n // `manageUserBalance` even gets called), any other error would generate non-zero revertData, so checking for\n // empty data guards against this case too.\n\n (, bytes memory revertData) = address(vault).staticcall{ gas: 10_000 }(\n abi.encodeWithSelector(vault.manageUserBalance.selector, 0)\n );\n\n _require(revertData.length == 0, Errors.REENTRANCY);\n }\n}\n" + }, + "contracts/strategies/BaseCurveAMOStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title Curve Automated Market Maker (AMO) Strategy\n * @notice AMO strategy for the Curve OETH/WETH pool\n * @author Origin Protocol Inc\n */\nimport { Math } from \"@openzeppelin/contracts/utils/math/Math.sol\";\nimport { SafeCast } from \"@openzeppelin/contracts/utils/math/SafeCast.sol\";\n\nimport { IERC20, InitializableAbstractStrategy } from \"../utils/InitializableAbstractStrategy.sol\";\nimport { StableMath } from \"../utils/StableMath.sol\";\nimport { IVault } from \"../interfaces/IVault.sol\";\nimport { IWETH9 } from \"../interfaces/IWETH9.sol\";\nimport { ICurveStableSwapNG } from \"../interfaces/ICurveStableSwapNG.sol\";\nimport { ICurveXChainLiquidityGauge } from \"../interfaces/ICurveXChainLiquidityGauge.sol\";\nimport { IChildLiquidityGaugeFactory } from \"../interfaces/IChildLiquidityGaugeFactory.sol\";\n\ncontract BaseCurveAMOStrategy is InitializableAbstractStrategy {\n using StableMath for uint256;\n using SafeCast for uint256;\n\n /**\n * @dev a threshold under which the contract no longer allows for the protocol to manually rebalance.\n * Guarding against a strategist / guardian being taken over and with multiple transactions\n * draining the protocol funds.\n */\n uint256 public constant SOLVENCY_THRESHOLD = 0.998 ether;\n\n // New immutable variables that must be set in the constructor\n /**\n * @notice Address of the Wrapped ETH (WETH) contract.\n */\n IWETH9 public immutable weth;\n\n /**\n * @notice Address of the OETH token contract.\n */\n IERC20 public immutable oeth;\n\n /**\n * @notice Address of the LP (Liquidity Provider) token contract.\n */\n IERC20 public immutable lpToken;\n\n /**\n * @notice Address of the Curve StableSwap NG pool contract.\n */\n ICurveStableSwapNG public immutable curvePool;\n\n /**\n * @notice Address of the Curve X-Chain Liquidity Gauge contract.\n */\n ICurveXChainLiquidityGauge public immutable gauge;\n\n /**\n * @notice Address of the Child Liquidity Gauge Factory contract.\n */\n IChildLiquidityGaugeFactory public immutable gaugeFactory;\n\n // Ordered list of pool assets\n uint128 public immutable oethCoinIndex;\n uint128 public immutable wethCoinIndex;\n\n /**\n * @notice Maximum slippage allowed for adding/removing liquidity from the Curve pool.\n */\n uint256 public maxSlippage;\n\n event MaxSlippageUpdated(uint256 newMaxSlippage);\n\n /**\n * @dev Verifies that the caller is the Strategist.\n */\n modifier onlyStrategist() {\n require(\n msg.sender == IVault(vaultAddress).strategistAddr(),\n \"Caller is not the Strategist\"\n );\n _;\n }\n\n /**\n * @dev Checks the Curve pool's balances have improved and the balances\n * have not tipped to the other side.\n * This modifier is only applied to functions that do a single sided add or remove.\n * The standard deposit function adds to both sides of the pool in a way that\n * the pool's balance is not worsened.\n * Withdrawals are proportional so doesn't change the pools asset balance.\n */\n modifier improvePoolBalance() {\n // Get the asset and OToken balances in the Curve pool\n uint256[] memory balancesBefore = curvePool.get_balances();\n // diff = ETH balance - OETH balance\n int256 diffBefore = balancesBefore[wethCoinIndex].toInt256() -\n balancesBefore[oethCoinIndex].toInt256();\n\n _;\n\n // Get the asset and OToken balances in the Curve pool\n uint256[] memory balancesAfter = curvePool.get_balances();\n // diff = ETH balance - OETH balance\n int256 diffAfter = balancesAfter[wethCoinIndex].toInt256() -\n balancesAfter[oethCoinIndex].toInt256();\n\n if (diffBefore == 0) {\n require(diffAfter == 0, \"Position balance is worsened\");\n } else if (diffBefore < 0) {\n // If the pool was originally imbalanced in favor of OETH, then\n // we want to check that the pool is now more balanced\n require(diffAfter <= 0, \"OTokens overshot peg\");\n require(diffBefore < diffAfter, \"OTokens balance worse\");\n } else if (diffBefore > 0) {\n // If the pool was originally imbalanced in favor of ETH, then\n // we want to check that the pool is now more balanced\n require(diffAfter >= 0, \"Assets overshot peg\");\n require(diffAfter < diffBefore, \"Assets balance worse\");\n }\n }\n\n constructor(\n BaseStrategyConfig memory _baseConfig,\n address _oeth,\n address _weth,\n address _gauge,\n address _gaugeFactory,\n uint128 _oethCoinIndex,\n uint128 _wethCoinIndex\n ) InitializableAbstractStrategy(_baseConfig) {\n oethCoinIndex = _oethCoinIndex;\n wethCoinIndex = _wethCoinIndex;\n\n lpToken = IERC20(_baseConfig.platformAddress);\n curvePool = ICurveStableSwapNG(_baseConfig.platformAddress);\n\n oeth = IERC20(_oeth);\n weth = IWETH9(_weth);\n gauge = ICurveXChainLiquidityGauge(_gauge);\n gaugeFactory = IChildLiquidityGaugeFactory(_gaugeFactory);\n\n _setGovernor(address(0));\n }\n\n /**\n * Initializer for setting up strategy internal state. This overrides the\n * InitializableAbstractStrategy initializer as Curve strategies don't fit\n * well within that abstraction.\n * @param _rewardTokenAddresses Address of CRV\n * @param _maxSlippage Maximum slippage allowed for adding/removing liquidity from the Curve pool.\n */\n function initialize(\n address[] calldata _rewardTokenAddresses, // CRV\n uint256 _maxSlippage\n ) external onlyGovernor initializer {\n address[] memory pTokens = new address[](1);\n pTokens[0] = address(curvePool);\n\n address[] memory _assets = new address[](1);\n _assets[0] = address(weth);\n\n InitializableAbstractStrategy._initialize(\n _rewardTokenAddresses,\n _assets,\n pTokens\n );\n\n _approveBase();\n _setMaxSlippage(_maxSlippage);\n }\n\n /***************************************\n Deposit\n ****************************************/\n\n /**\n * @notice Deposit WETH into the Curve pool\n * @param _weth Address of Wrapped ETH (WETH) contract.\n * @param _amount Amount of WETH to deposit.\n */\n function deposit(address _weth, uint256 _amount)\n external\n override\n onlyVault\n nonReentrant\n {\n _deposit(_weth, _amount);\n }\n\n function _deposit(address _weth, uint256 _wethAmount) internal {\n require(_wethAmount > 0, \"Must deposit something\");\n require(_weth == address(weth), \"Can only deposit WETH\");\n\n emit Deposit(_weth, address(lpToken), _wethAmount);\n\n // Get the asset and OToken balances in the Curve pool\n uint256[] memory balances = curvePool.get_balances();\n // safe to cast since min value is at least 0\n uint256 oethToAdd = uint256(\n _max(\n 0,\n balances[wethCoinIndex].toInt256() +\n _wethAmount.toInt256() -\n balances[oethCoinIndex].toInt256()\n )\n );\n\n /* Add so much OETH so that the pool ends up being balanced. And at minimum\n * add as much OETH as WETH and at maximum twice as much OETH.\n */\n oethToAdd = Math.max(oethToAdd, _wethAmount);\n oethToAdd = Math.min(oethToAdd, _wethAmount * 2);\n\n /* Mint OETH with a strategy that attempts to contribute to stability of OETH/WETH pool. Try\n * to mint so much OETH that after deployment of liquidity pool ends up being balanced.\n *\n * To manage unpredictability minimal OETH minted will always be at least equal or greater\n * to WETH amount deployed. And never larger than twice the WETH amount deployed even if\n * it would have a further beneficial effect on pool stability.\n */\n IVault(vaultAddress).mintForStrategy(oethToAdd);\n\n emit Deposit(address(oeth), address(lpToken), oethToAdd);\n\n uint256[] memory _amounts = new uint256[](2);\n _amounts[wethCoinIndex] = _wethAmount;\n _amounts[oethCoinIndex] = oethToAdd;\n\n uint256 valueInLpTokens = (_wethAmount + oethToAdd).divPrecisely(\n curvePool.get_virtual_price()\n );\n uint256 minMintAmount = valueInLpTokens.mulTruncate(\n uint256(1e18) - maxSlippage\n );\n\n // Do the deposit to the Curve pool\n uint256 lpDeposited = curvePool.add_liquidity(_amounts, minMintAmount);\n require(lpDeposited >= minMintAmount, \"Min LP amount error\");\n\n // Deposit the Curve pool's LP tokens into the Curve gauge\n gauge.deposit(lpDeposited);\n\n // Ensure solvency of the vault\n _solvencyAssert();\n }\n\n /**\n * @notice Deposit the strategy's entire balance of WETH into the Curve pool\n */\n function depositAll() external override onlyVault nonReentrant {\n uint256 balance = weth.balanceOf(address(this));\n if (balance > 0) {\n _deposit(address(weth), balance);\n }\n }\n\n /***************************************\n Withdraw\n ****************************************/\n\n /**\n * @notice Withdraw ETH and OETH from the Curve pool, burn the OETH,\n * convert the ETH to WETH and transfer to the recipient.\n * @param _recipient Address to receive withdrawn asset which is normally the Vault.\n * @param _weth Address of the Wrapped ETH (WETH) contract.\n * @param _amount Amount of WETH to withdraw.\n */\n function withdraw(\n address _recipient,\n address _weth,\n uint256 _amount\n ) external override onlyVault nonReentrant {\n require(_amount > 0, \"Must withdraw something\");\n require(_weth == address(weth), \"Can only withdraw WETH\");\n\n emit Withdrawal(_weth, address(lpToken), _amount);\n\n uint256 requiredLpTokens = calcTokenToBurn(_amount);\n\n _lpWithdraw(requiredLpTokens);\n\n /* math in requiredLpTokens should correctly calculate the amount of LP to remove\n * in that the strategy receives enough WETH on balanced removal\n */\n uint256[] memory _minWithdrawalAmounts = new uint256[](2);\n _minWithdrawalAmounts[wethCoinIndex] = _amount;\n // slither-disable-next-line unused-return\n curvePool.remove_liquidity(requiredLpTokens, _minWithdrawalAmounts);\n\n // Burn all the removed OETH and any that was left in the strategy\n uint256 oethToBurn = oeth.balanceOf(address(this));\n IVault(vaultAddress).burnForStrategy(oethToBurn);\n\n emit Withdrawal(address(oeth), address(lpToken), oethToBurn);\n\n // Transfer WETH to the recipient\n require(\n weth.transfer(_recipient, _amount),\n \"Transfer of WETH not successful\"\n );\n\n // Ensure solvency of the vault\n _solvencyAssert();\n }\n\n function calcTokenToBurn(uint256 _wethAmount)\n internal\n view\n returns (uint256 lpToBurn)\n {\n /* The rate between coins in the pool determines the rate at which pool returns\n * tokens when doing balanced removal (remove_liquidity call). And by knowing how much WETH\n * we want we can determine how much of OETH we receive by removing liquidity.\n *\n * Because we are doing balanced removal we should be making profit when removing liquidity in a\n * pool tilted to either side.\n *\n * Important: A downside is that the Strategist / Governor needs to be\n * cognisant of not removing too much liquidity. And while the proposal to remove liquidity\n * is being voted on the pool tilt might change so much that the proposal that has been valid while\n * created is no longer valid.\n */\n\n uint256 poolWETHBalance = curvePool.balances(wethCoinIndex);\n /* K is multiplied by 1e36 which is used for higher precision calculation of required\n * pool LP tokens. Without it the end value can have rounding errors up to precision of\n * 10 digits. This way we move the decimal point by 36 places when doing the calculation\n * and again by 36 places when we are done with it.\n */\n uint256 k = (1e36 * lpToken.totalSupply()) / poolWETHBalance;\n // prettier-ignore\n // slither-disable-next-line divide-before-multiply\n uint256 diff = (_wethAmount + 1) * k;\n lpToBurn = diff / 1e36;\n }\n\n /**\n * @notice Remove all ETH and OETH from the Curve pool, burn the OETH,\n * convert the ETH to WETH and transfer to the Vault contract.\n */\n function withdrawAll() external override onlyVaultOrGovernor nonReentrant {\n uint256 gaugeTokens = gauge.balanceOf(address(this));\n // Can not withdraw zero LP tokens from the gauge\n if (gaugeTokens == 0) return;\n _lpWithdraw(gaugeTokens);\n\n // Withdraws are proportional to assets held by 3Pool\n uint256[] memory minWithdrawAmounts = new uint256[](2);\n\n // Remove liquidity\n // slither-disable-next-line unused-return\n curvePool.remove_liquidity(\n lpToken.balanceOf(address(this)),\n minWithdrawAmounts\n );\n\n // Burn all OETH\n uint256 oethToBurn = oeth.balanceOf(address(this));\n IVault(vaultAddress).burnForStrategy(oethToBurn);\n\n // Get the strategy contract's WETH balance.\n // This includes all that was removed from the Curve pool and\n // any ether that was sitting in the strategy contract before the removal.\n uint256 ethBalance = weth.balanceOf(address(this));\n require(\n weth.transfer(vaultAddress, ethBalance),\n \"Transfer of WETH not successful\"\n );\n\n emit Withdrawal(address(weth), address(lpToken), ethBalance);\n emit Withdrawal(address(oeth), address(lpToken), oethToBurn);\n }\n\n /***************************************\n Curve pool Rebalancing\n ****************************************/\n\n /**\n * @notice Mint OTokens and one-sided add to the Curve pool.\n * This is used when the Curve pool does not have enough OTokens and too many ETH.\n * The OToken/Asset, eg OETH/ETH, price with increase.\n * The amount of assets in the vault is unchanged.\n * The total supply of OTokens is increased.\n * The asset value of the strategy and vault is increased.\n * @param _oTokens The amount of OTokens to be minted and added to the pool.\n */\n function mintAndAddOTokens(uint256 _oTokens)\n external\n onlyStrategist\n nonReentrant\n improvePoolBalance\n {\n IVault(vaultAddress).mintForStrategy(_oTokens);\n\n uint256[] memory amounts = new uint256[](2);\n amounts[oethCoinIndex] = _oTokens;\n\n // Convert OETH to Curve pool LP tokens\n uint256 valueInLpTokens = (_oTokens).divPrecisely(\n curvePool.get_virtual_price()\n );\n // Apply slippage to LP tokens\n uint256 minMintAmount = valueInLpTokens.mulTruncate(\n uint256(1e18) - maxSlippage\n );\n\n // Add the minted OTokens to the Curve pool\n uint256 lpDeposited = curvePool.add_liquidity(amounts, minMintAmount);\n require(lpDeposited >= minMintAmount, \"Min LP amount error\");\n\n // Deposit the Curve pool LP tokens to the Curve gauge\n gauge.deposit(lpDeposited);\n\n // Ensure solvency of the vault\n _solvencyAssert();\n\n emit Deposit(address(oeth), address(lpToken), _oTokens);\n }\n\n /**\n * @notice One-sided remove of OTokens from the Curve pool which are then burned.\n * This is used when the Curve pool has too many OTokens and not enough ETH.\n * The amount of assets in the vault is unchanged.\n * The total supply of OTokens is reduced.\n * The asset value of the strategy and vault is reduced.\n * @param _lpTokens The amount of Curve pool LP tokens to be burned for OTokens.\n */\n function removeAndBurnOTokens(uint256 _lpTokens)\n external\n onlyStrategist\n nonReentrant\n improvePoolBalance\n {\n // Withdraw Curve pool LP tokens from Convex and remove OTokens from the Curve pool\n uint256 oethToBurn = _withdrawAndRemoveFromPool(\n _lpTokens,\n oethCoinIndex\n );\n\n // The vault burns the OTokens from this strategy\n IVault(vaultAddress).burnForStrategy(oethToBurn);\n\n // Ensure solvency of the vault\n _solvencyAssert();\n\n emit Withdrawal(address(oeth), address(lpToken), oethToBurn);\n }\n\n /**\n * @notice One-sided remove of ETH from the Curve pool, convert to WETH\n * and transfer to the vault.\n * This is used when the Curve pool does not have enough OTokens and too many ETH.\n * The OToken/Asset, eg OETH/ETH, price with decrease.\n * The amount of assets in the vault increases.\n * The total supply of OTokens does not change.\n * The asset value of the strategy reduces.\n * The asset value of the vault should be close to the same.\n * @param _lpTokens The amount of Curve pool LP tokens to be burned for ETH.\n * @dev Curve pool LP tokens is used rather than WETH assets as Curve does not\n * have a way to accurately calculate the amount of LP tokens for a required\n * amount of ETH. Curve's `calc_token_amount` functioun does not include fees.\n * A 3rd party libary can be used that takes into account the fees, but this\n * is a gas intensive process. It's easier for the trusted strategist to\n * caclulate the amount of Curve pool LP tokens required off-chain.\n */\n function removeOnlyAssets(uint256 _lpTokens)\n external\n onlyStrategist\n nonReentrant\n improvePoolBalance\n {\n // Withdraw Curve pool LP tokens from Curve gauge and remove ETH from the Curve pool\n uint256 ethAmount = _withdrawAndRemoveFromPool(\n _lpTokens,\n wethCoinIndex\n );\n\n // Transfer WETH to the vault\n require(\n weth.transfer(vaultAddress, ethAmount),\n \"Transfer of WETH not successful\"\n );\n\n // Ensure solvency of the vault\n _solvencyAssert();\n\n emit Withdrawal(address(weth), address(lpToken), ethAmount);\n }\n\n /**\n * @dev Remove Curve pool LP tokens from the Convex pool and\n * do a one-sided remove of ETH or OETH from the Curve pool.\n * @param _lpTokens The amount of Curve pool LP tokens to be removed from the Convex pool.\n * @param coinIndex The index of the coin to be removed from the Curve pool. 0 = ETH, 1 = OETH.\n * @return coinsRemoved The amount of ETH or OETH removed from the Curve pool.\n */\n function _withdrawAndRemoveFromPool(uint256 _lpTokens, uint128 coinIndex)\n internal\n returns (uint256 coinsRemoved)\n {\n // Withdraw Curve pool LP tokens from Curve gauge\n _lpWithdraw(_lpTokens);\n\n // Convert Curve pool LP tokens to ETH value\n uint256 valueInEth = _lpTokens.mulTruncate(\n curvePool.get_virtual_price()\n );\n // Apply slippage to ETH value\n uint256 minAmount = valueInEth.mulTruncate(uint256(1e18) - maxSlippage);\n\n // Remove just the ETH from the Curve pool\n coinsRemoved = curvePool.remove_liquidity_one_coin(\n _lpTokens,\n int128(coinIndex),\n minAmount,\n address(this)\n );\n }\n\n /**\n * Checks that the protocol is solvent, protecting from a rogue Strategist / Guardian that can\n * keep rebalancing the pool in both directions making the protocol lose a tiny amount of\n * funds each time.\n *\n * Protocol must be at least SOLVENCY_THRESHOLD (99,8 %) backed in order for the rebalances to\n * function.\n */\n function _solvencyAssert() internal view {\n uint256 _totalVaultValue = IVault(vaultAddress).totalValue();\n uint256 _totalOethbSupply = oeth.totalSupply();\n\n if (\n _totalVaultValue.divPrecisely(_totalOethbSupply) <\n SOLVENCY_THRESHOLD\n ) {\n revert(\"Protocol insolvent\");\n }\n }\n\n /***************************************\n Assets and Rewards\n ****************************************/\n\n /**\n * @notice Collect accumulated CRV (and other) rewards and send to the Harvester.\n */\n function collectRewardTokens()\n external\n override\n onlyHarvester\n nonReentrant\n {\n // CRV rewards flow.\n //---\n // CRV inflation:\n // Gauge receive CRV rewards from inflation.\n // Each checkpoint on the gauge send this CRV inflation to gauge factory.\n // This strategy should call mint on the gauge factory to collect the CRV rewards.\n // ---\n // Extra rewards:\n // Calling claim_rewards on the gauge will only claim extra rewards (outside of CRV).\n // ---\n\n // Mint CRV on Child Liquidity gauge factory\n gaugeFactory.mint(address(gauge));\n // Collect extra gauge rewards (outside of CRV)\n gauge.claim_rewards();\n\n _collectRewardTokens();\n }\n\n function _lpWithdraw(uint256 _lpAmount) internal {\n // withdraw lp tokens from the gauge without claiming rewards\n gauge.withdraw(_lpAmount);\n }\n\n /**\n * @notice Get the total asset value held in the platform\n * @param _asset Address of the asset\n * @return balance Total value of the asset in the platform\n */\n function checkBalance(address _asset)\n external\n view\n override\n returns (uint256 balance)\n {\n require(_asset == address(weth), \"Unsupported asset\");\n\n // WETH balance needed here for the balance check that happens from vault during depositing.\n balance = weth.balanceOf(address(this));\n uint256 lpTokens = gauge.balanceOf(address(this));\n if (lpTokens > 0) {\n balance += (lpTokens * curvePool.get_virtual_price()) / 1e18;\n }\n }\n\n /**\n * @notice Returns bool indicating whether asset is supported by strategy\n * @param _asset Address of the asset\n */\n function supportsAsset(address _asset) public view override returns (bool) {\n return _asset == address(weth);\n }\n\n /***************************************\n Approvals\n ****************************************/\n\n /**\n * @notice Sets the maximum slippage allowed for any swap/liquidity operation\n * @param _maxSlippage Maximum slippage allowed, 1e18 = 100%.\n */\n function setMaxSlippage(uint256 _maxSlippage) external onlyGovernor {\n _setMaxSlippage(_maxSlippage);\n }\n\n function _setMaxSlippage(uint256 _maxSlippage) internal {\n require(_maxSlippage <= 5e16, \"Slippage must be less than 100%\");\n maxSlippage = _maxSlippage;\n emit MaxSlippageUpdated(_maxSlippage);\n }\n\n /**\n * @notice Approve the spending of all assets by their corresponding pool tokens,\n * if for some reason is it necessary.\n */\n function safeApproveAllTokens()\n external\n override\n onlyGovernor\n nonReentrant\n {\n _approveBase();\n }\n\n /**\n * @dev Since we are unwrapping WETH before depositing it to Curve\n * there is no need to set an approval for WETH on the Curve\n * pool\n * @param _asset Address of the asset\n * @param _pToken Address of the Curve LP token\n */\n // solhint-disable-next-line no-unused-vars\n function _abstractSetPToken(address _asset, address _pToken)\n internal\n override\n {}\n\n function _approveBase() internal {\n // Approve Curve pool for OETH (required for adding liquidity)\n // slither-disable-next-line unused-return\n oeth.approve(platformAddress, type(uint256).max);\n\n // Approve Curve pool for WETH (required for adding liquidity)\n // slither-disable-next-line unused-return\n weth.approve(platformAddress, type(uint256).max);\n\n // Approve Curve gauge contract to transfer Curve pool LP tokens\n // This is needed for deposits if Curve pool LP tokens into the Curve gauge.\n // slither-disable-next-line unused-return\n lpToken.approve(address(gauge), type(uint256).max);\n }\n\n /**\n * @dev Returns the largest of two numbers int256 version\n */\n function _max(int256 a, int256 b) internal pure returns (int256) {\n return a >= b ? a : b;\n }\n}\n" + }, + "contracts/strategies/BridgedWOETHStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { IERC20, SafeERC20, InitializableAbstractStrategy } from \"../utils/InitializableAbstractStrategy.sol\";\nimport { IWETH9 } from \"../interfaces/IWETH9.sol\";\nimport { IVault } from \"../interfaces/IVault.sol\";\nimport { AggregatorV3Interface } from \"../interfaces/chainlink/AggregatorV3Interface.sol\";\nimport { StableMath } from \"../utils/StableMath.sol\";\nimport { SafeCast } from \"@openzeppelin/contracts/utils/math/SafeCast.sol\";\nimport { IOracle } from \"../interfaces/IOracle.sol\";\n\ncontract BridgedWOETHStrategy is InitializableAbstractStrategy {\n using StableMath for uint256;\n using StableMath for uint128;\n using SafeCast for uint256;\n using SafeERC20 for IERC20;\n\n event MaxPriceDiffBpsUpdated(uint128 oldValue, uint128 newValue);\n event WOETHPriceUpdated(uint128 oldValue, uint128 newValue);\n\n IWETH9 public immutable weth;\n IERC20 public immutable bridgedWOETH;\n IERC20 public immutable oethb;\n IOracle public immutable oracle;\n\n uint128 public lastOraclePrice;\n uint128 public maxPriceDiffBps;\n\n constructor(\n BaseStrategyConfig memory _stratConfig,\n address _weth,\n address _bridgedWOETH,\n address _oethb,\n address _oracle\n ) InitializableAbstractStrategy(_stratConfig) {\n weth = IWETH9(_weth);\n bridgedWOETH = IERC20(_bridgedWOETH);\n oethb = IERC20(_oethb);\n oracle = IOracle(_oracle);\n }\n\n function initialize(uint128 _maxPriceDiffBps)\n external\n onlyGovernor\n initializer\n {\n InitializableAbstractStrategy._initialize(\n new address[](0), // No reward tokens\n new address[](0), // No assets\n new address[](0) // No pTokens\n );\n\n _setMaxPriceDiffBps(_maxPriceDiffBps);\n }\n\n /**\n * @dev Sets the max price diff bps for the wOETH value appreciation\n * @param _maxPriceDiffBps Bps value, 10k == 100%\n */\n function setMaxPriceDiffBps(uint128 _maxPriceDiffBps)\n external\n onlyGovernor\n {\n _setMaxPriceDiffBps(_maxPriceDiffBps);\n }\n\n /**\n * @dev Sets the max price diff bps for the wOETH value appreciation\n * @param _maxPriceDiffBps Bps value, 10k == 100%\n */\n function _setMaxPriceDiffBps(uint128 _maxPriceDiffBps) internal {\n require(\n _maxPriceDiffBps > 0 && _maxPriceDiffBps <= 10000,\n \"Invalid bps value\"\n );\n\n emit MaxPriceDiffBpsUpdated(maxPriceDiffBps, _maxPriceDiffBps);\n\n maxPriceDiffBps = _maxPriceDiffBps;\n }\n\n /**\n * @dev Wrapper for _updateWOETHOraclePrice with nonReentrant flag\n * @return The latest price of wOETH from Oracle\n */\n function updateWOETHOraclePrice() external nonReentrant returns (uint256) {\n return _updateWOETHOraclePrice();\n }\n\n /**\n * @dev Finds the value of bridged wOETH from the Oracle.\n * Ensures that it's within the bounds and reasonable.\n * And stores it.\n *\n * NOTE: Intentionally not caching `Vault.priceProvider` here,\n * since doing so would mean that we also have to update this\n * strategy every time there's a change in oracle router.\n * Besides on L2, the gas is considerably cheaper than mainnet.\n *\n * @return Latest price from oracle\n */\n function _updateWOETHOraclePrice() internal returns (uint256) {\n // WETH price per unit of bridged wOETH\n uint256 oraclePrice = oracle.price(address(bridgedWOETH));\n\n // 1 wOETH > 1 WETH, always\n require(oraclePrice > 1 ether, \"Invalid wOETH value\");\n\n uint128 oraclePrice128 = oraclePrice.toUint128();\n\n // Do some checks\n if (lastOraclePrice > 0) {\n // Make sure the value only goes up\n require(oraclePrice128 >= lastOraclePrice, \"Negative wOETH yield\");\n\n // lastOraclePrice * (1 + maxPriceDiffBps)\n uint256 maxPrice = (lastOraclePrice * (1e4 + maxPriceDiffBps)) /\n 1e4;\n\n // And that it's within the bounds.\n require(oraclePrice128 <= maxPrice, \"Price diff beyond threshold\");\n }\n\n emit WOETHPriceUpdated(lastOraclePrice, oraclePrice128);\n\n // Store the price\n lastOraclePrice = oraclePrice128;\n\n return oraclePrice;\n }\n\n /**\n * @dev Computes & returns the value of given wOETH in WETH\n * @param woethAmount Amount of wOETH\n * @return Value of wOETH in WETH (using the last stored oracle price)\n */\n function getBridgedWOETHValue(uint256 woethAmount)\n public\n view\n returns (uint256)\n {\n return (woethAmount * lastOraclePrice) / 1 ether;\n }\n\n /**\n * @dev Takes in bridged wOETH and mints & returns\n * equivalent amount of OETHb.\n * @param woethAmount Amount of bridged wOETH to transfer in\n */\n function depositBridgedWOETH(uint256 woethAmount)\n external\n onlyGovernorOrStrategist\n nonReentrant\n {\n // Update wOETH price\n uint256 oraclePrice = _updateWOETHOraclePrice();\n\n // Figure out how much they are worth\n uint256 oethToMint = (woethAmount * oraclePrice) / 1 ether;\n\n require(oethToMint > 0, \"Invalid deposit amount\");\n\n // There's no pToken, however, it just uses WOETH address in the event\n emit Deposit(address(weth), address(bridgedWOETH), oethToMint);\n\n // Mint OETHb tokens and transfer it to the caller\n IVault(vaultAddress).mintForStrategy(oethToMint);\n\n // Transfer out minted OETHb\n // slither-disable-next-line unchecked-transfer unused-return\n oethb.transfer(msg.sender, oethToMint);\n\n // Transfer in all bridged wOETH tokens\n // slither-disable-next-line unchecked-transfer unused-return\n bridgedWOETH.transferFrom(msg.sender, address(this), woethAmount);\n }\n\n /**\n * @dev Takes in OETHb and burns it and returns\n * equivalent amount of bridged wOETH.\n * @param oethToBurn Amount of OETHb to burn\n */\n function withdrawBridgedWOETH(uint256 oethToBurn)\n external\n onlyGovernorOrStrategist\n nonReentrant\n {\n // Update wOETH price\n uint256 oraclePrice = _updateWOETHOraclePrice();\n\n // Figure out how much they are worth\n uint256 woethAmount = (oethToBurn * 1 ether) / oraclePrice;\n\n require(woethAmount > 0, \"Invalid withdraw amount\");\n\n // There's no pToken, however, it just uses WOETH address in the event\n emit Withdrawal(address(weth), address(bridgedWOETH), oethToBurn);\n\n // Transfer WOETH back\n // slither-disable-next-line unchecked-transfer unused-return\n bridgedWOETH.transfer(msg.sender, woethAmount);\n\n // Transfer in OETHb\n // slither-disable-next-line unchecked-transfer unused-return\n oethb.transferFrom(msg.sender, address(this), oethToBurn);\n\n // Burn OETHb\n IVault(vaultAddress).burnForStrategy(oethToBurn);\n }\n\n /**\n * @notice Returns the amount of backing WETH the strategy holds\n * @param _asset Address of the asset\n * @return balance Total value of the asset in the platform\n */\n function checkBalance(address _asset)\n external\n view\n override\n returns (uint256 balance)\n {\n require(_asset == address(weth), \"Unsupported asset\");\n\n // Figure out how much wOETH is worth at the time.\n // Always uses the last stored oracle price.\n // Call updateWOETHOraclePrice manually to pull in latest yields.\n\n // NOTE: If the contract has been deployed but the call to\n // `updateWOETHOraclePrice()` has never been made, then this\n // will return zero. It should be fine because the strategy\n // should update the price whenever a deposit/withdraw happens.\n\n // If `updateWOETHOraclePrice()` hasn't been called in a while,\n // the strategy will underreport its holdings but never overreport it.\n\n balance =\n (bridgedWOETH.balanceOf(address(this)) * lastOraclePrice) /\n 1 ether;\n }\n\n /**\n * @notice Check if an asset is supported.\n * @param _asset Address of the asset\n * @return bool Whether asset is supported\n */\n function supportsAsset(address _asset) public view override returns (bool) {\n // Strategist deposits bridged wOETH but the contract only\n // reports the balance in WETH. As far as Vault is concerned,\n // it isn't aware of bridged wOETH token\n return _asset == address(weth);\n }\n\n /***************************************\n Overridden methods\n ****************************************/\n /**\n * @inheritdoc InitializableAbstractStrategy\n */\n function transferToken(address _asset, uint256 _amount)\n public\n override\n onlyGovernor\n {\n require(\n _asset != address(bridgedWOETH) && _asset != address(weth),\n \"Cannot transfer supported asset\"\n );\n // Use SafeERC20 only for rescuing unknown assets; core tokens are standard.\n IERC20(_asset).safeTransfer(governor(), _amount);\n }\n\n /**\n * @notice deposit() function not used for this strategy\n */\n function deposit(address, uint256)\n external\n override\n onlyVault\n nonReentrant\n {\n // Use depositBridgedWOETH() instead\n require(false, \"Deposit disabled\");\n }\n\n /**\n * @notice depositAll() function not used for this strategy\n */\n function depositAll() external override onlyVault nonReentrant {\n // Use depositBridgedWOETH() instead\n require(false, \"Deposit disabled\");\n }\n\n /**\n * @notice withdraw() function not used for this strategy\n */\n function withdraw(\n // solhint-disable-next-line no-unused-vars\n address _recipient,\n // solhint-disable-next-line no-unused-vars\n address _asset,\n // solhint-disable-next-line no-unused-vars\n uint256 _amount\n ) external override onlyVault nonReentrant {\n require(false, \"Withdrawal disabled\");\n }\n\n /**\n * @notice withdrawAll() function not used for this strategy\n */\n function withdrawAll() external override onlyVaultOrGovernor nonReentrant {\n // Withdrawal disabled\n }\n\n function _abstractSetPToken(address, address) internal override {\n revert(\"No pTokens are used\");\n }\n\n function safeApproveAllTokens() external override {}\n\n /**\n * @inheritdoc InitializableAbstractStrategy\n */\n function removePToken(uint256) external override {\n revert(\"No pTokens are used\");\n }\n\n /**\n * @inheritdoc InitializableAbstractStrategy\n */\n function collectRewardTokens() external override {}\n}\n" + }, + "contracts/strategies/CompoundStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title Compound Strategy\n * @notice Investment strategy for Compound like lending platforms. eg Compound and Flux\n * @author Origin Protocol Inc\n */\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\n\nimport { ICERC20 } from \"./ICompound.sol\";\nimport { AbstractCompoundStrategy, InitializableAbstractStrategy } from \"./AbstractCompoundStrategy.sol\";\nimport { IComptroller } from \"../interfaces/IComptroller.sol\";\nimport { IERC20 } from \"../utils/InitializableAbstractStrategy.sol\";\n\ncontract CompoundStrategy is AbstractCompoundStrategy {\n using SafeERC20 for IERC20;\n event SkippedWithdrawal(address asset, uint256 amount);\n\n constructor(BaseStrategyConfig memory _stratConfig)\n InitializableAbstractStrategy(_stratConfig)\n {}\n\n /**\n * @notice initialize function, to set up initial internal state\n * @param _rewardTokenAddresses Address of reward token for platform\n * @param _assets Addresses of initial supported assets\n * @param _pTokens Platform Token corresponding addresses\n */\n function initialize(\n address[] memory _rewardTokenAddresses,\n address[] memory _assets,\n address[] memory _pTokens\n ) external onlyGovernor initializer {\n InitializableAbstractStrategy._initialize(\n _rewardTokenAddresses,\n _assets,\n _pTokens\n );\n }\n\n /**\n * @notice Collect accumulated COMP and send to Harvester.\n */\n function collectRewardTokens()\n external\n virtual\n override\n onlyHarvester\n nonReentrant\n {\n // Claim COMP from Comptroller\n ICERC20 cToken = _getCTokenFor(assetsMapped[0]);\n IComptroller comptroller = IComptroller(cToken.comptroller());\n // Only collect from active cTokens, saves gas\n address[] memory ctokensToCollect = new address[](assetsMapped.length);\n uint256 assetCount = assetsMapped.length;\n for (uint256 i = 0; i < assetCount; ++i) {\n ctokensToCollect[i] = address(_getCTokenFor(assetsMapped[i]));\n }\n // Claim only for this strategy\n address[] memory claimers = new address[](1);\n claimers[0] = address(this);\n // Claim COMP from Comptroller. Only collect for supply, saves gas\n comptroller.claimComp(claimers, ctokensToCollect, false, true);\n // Transfer COMP to Harvester\n IERC20 rewardToken = IERC20(rewardTokenAddresses[0]);\n uint256 balance = rewardToken.balanceOf(address(this));\n emit RewardTokenCollected(\n harvesterAddress,\n rewardTokenAddresses[0],\n balance\n );\n rewardToken.safeTransfer(harvesterAddress, balance);\n }\n\n /**\n * @notice Deposit asset into the underlying platform\n * @param _asset Address of asset to deposit\n * @param _amount Amount of assets to deposit\n */\n function deposit(address _asset, uint256 _amount)\n external\n override\n onlyVault\n nonReentrant\n {\n _deposit(_asset, _amount);\n }\n\n /**\n * @dev Deposit an asset into the underlying platform\n * @param _asset Address of the asset to deposit\n * @param _amount Amount of assets to deposit\n */\n function _deposit(address _asset, uint256 _amount) internal {\n require(_amount > 0, \"Must deposit something\");\n ICERC20 cToken = _getCTokenFor(_asset);\n emit Deposit(_asset, address(cToken), _amount);\n require(cToken.mint(_amount) == 0, \"cToken mint failed\");\n }\n\n /**\n * @notice Deposit the entire balance of any supported asset in the strategy into the underlying platform\n */\n function depositAll() external override onlyVault nonReentrant {\n uint256 assetCount = assetsMapped.length;\n for (uint256 i = 0; i < assetCount; ++i) {\n IERC20 asset = IERC20(assetsMapped[i]);\n uint256 assetBalance = asset.balanceOf(address(this));\n if (assetBalance > 0) {\n _deposit(address(asset), assetBalance);\n }\n }\n }\n\n /**\n * @notice Withdraw an asset from the underlying platform\n * @param _recipient Address to receive withdrawn assets\n * @param _asset Address of the asset to withdraw\n * @param _amount Amount of assets to withdraw\n */\n function withdraw(\n address _recipient,\n address _asset,\n uint256 _amount\n ) external override onlyVault nonReentrant {\n require(_amount > 0, \"Must withdraw something\");\n require(_recipient != address(0), \"Must specify recipient\");\n\n ICERC20 cToken = _getCTokenFor(_asset);\n // If redeeming 0 cTokens, just skip, else COMP will revert\n uint256 cTokensToRedeem = _convertUnderlyingToCToken(cToken, _amount);\n if (cTokensToRedeem == 0) {\n emit SkippedWithdrawal(_asset, _amount);\n return;\n }\n\n emit Withdrawal(_asset, address(cToken), _amount);\n require(cToken.redeemUnderlying(_amount) == 0, \"Redeem failed\");\n IERC20(_asset).safeTransfer(_recipient, _amount);\n }\n\n /**\n * @dev Internal method to respond to the addition of new asset / cTokens\n * We need to approve the cToken and give it permission to spend the asset\n * @param _asset Address of the asset to approve. eg DAI\n * @param _pToken The pToken for the approval. eg cDAI or fDAI\n */\n function _abstractSetPToken(address _asset, address _pToken)\n internal\n override\n {\n // Safe approval\n IERC20(_asset).safeApprove(_pToken, 0);\n IERC20(_asset).safeApprove(_pToken, type(uint256).max);\n }\n\n /**\n * @notice Remove all supported assets from the underlying platform and send them to Vault contract.\n */\n function withdrawAll() external override onlyVaultOrGovernor nonReentrant {\n uint256 assetCount = assetsMapped.length;\n for (uint256 i = 0; i < assetCount; ++i) {\n IERC20 asset = IERC20(assetsMapped[i]);\n // Redeem entire balance of cToken\n ICERC20 cToken = _getCTokenFor(address(asset));\n uint256 cTokenBalance = cToken.balanceOf(address(this));\n if (cTokenBalance > 0) {\n require(cToken.redeem(cTokenBalance) == 0, \"Redeem failed\");\n uint256 assetBalance = asset.balanceOf(address(this));\n // Transfer entire balance to Vault\n asset.safeTransfer(vaultAddress, assetBalance);\n\n emit Withdrawal(address(asset), address(cToken), assetBalance);\n }\n }\n }\n\n /**\n * @notice Get the total asset value held in the underlying platform\n * This includes any interest that was generated since depositing.\n * The exchange rate between the cToken and asset gradually increases,\n * causing the cToken to be worth more corresponding asset.\n * @param _asset Address of the asset\n * @return balance Total value of the asset in the platform\n */\n function checkBalance(address _asset)\n external\n view\n override\n returns (uint256 balance)\n {\n // Balance is always with token cToken decimals\n ICERC20 cToken = _getCTokenFor(_asset);\n balance = _checkBalance(cToken);\n }\n\n /**\n * @dev Get the total asset value held in the platform\n * underlying = (cTokenAmt * exchangeRate) / 1e18\n * @param _cToken cToken for which to check balance\n * @return balance Total value of the asset in the platform\n */\n function _checkBalance(ICERC20 _cToken)\n internal\n view\n returns (uint256 balance)\n {\n // e.g. 50e8*205316390724364402565641705 / 1e18 = 1.0265..e18\n balance =\n (_cToken.balanceOf(address(this)) * _cToken.exchangeRateStored()) /\n 1e18;\n }\n\n /**\n * @notice Approve the spending of all assets by their corresponding cToken,\n * if for some reason is it necessary.\n */\n function safeApproveAllTokens() external override {\n uint256 assetCount = assetsMapped.length;\n for (uint256 i = 0; i < assetCount; ++i) {\n IERC20 asset = IERC20(assetsMapped[i]);\n address cToken = assetToPToken[address(asset)];\n // Safe approval\n asset.safeApprove(cToken, 0);\n asset.safeApprove(cToken, type(uint256).max);\n }\n }\n}\n" + }, + "contracts/strategies/ConvexEthMetaStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title Convex Automated Market Maker (AMO) Strategy\n * @notice AMO strategy for the Curve OETH/ETH pool\n * @author Origin Protocol Inc\n */\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport \"@openzeppelin/contracts/utils/math/Math.sol\";\n\nimport { ICurveETHPoolV1 } from \"./ICurveETHPoolV1.sol\";\nimport { IERC20, InitializableAbstractStrategy } from \"../utils/InitializableAbstractStrategy.sol\";\nimport { StableMath } from \"../utils/StableMath.sol\";\nimport { IVault } from \"../interfaces/IVault.sol\";\nimport { IWETH9 } from \"../interfaces/IWETH9.sol\";\nimport { IConvexDeposits } from \"./IConvexDeposits.sol\";\nimport { IRewardStaking } from \"./IRewardStaking.sol\";\n\ncontract ConvexEthMetaStrategy is InitializableAbstractStrategy {\n using StableMath for uint256;\n using SafeERC20 for IERC20;\n\n uint256 public constant MAX_SLIPPAGE = 1e16; // 1%, same as the Curve UI\n address public constant ETH_ADDRESS =\n 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;\n\n // The following slots have been deprecated with immutable variables\n // slither-disable-next-line constable-states\n address private _deprecated_cvxDepositorAddress;\n // slither-disable-next-line constable-states\n address private _deprecated_cvxRewardStaker;\n // slither-disable-next-line constable-states\n uint256 private _deprecated_cvxDepositorPTokenId;\n // slither-disable-next-line constable-states\n address private _deprecated_curvePool;\n // slither-disable-next-line constable-states\n address private _deprecated_lpToken;\n // slither-disable-next-line constable-states\n address private _deprecated_oeth;\n // slither-disable-next-line constable-states\n address private _deprecated_weth;\n\n // Ordered list of pool assets\n // slither-disable-next-line constable-states\n uint128 private _deprecated_oethCoinIndex;\n // slither-disable-next-line constable-states\n uint128 private _deprecated_ethCoinIndex;\n\n // New immutable variables that must be set in the constructor\n address public immutable cvxDepositorAddress;\n IRewardStaking public immutable cvxRewardStaker;\n uint256 public immutable cvxDepositorPTokenId;\n ICurveETHPoolV1 public immutable curvePool;\n IERC20 public immutable lpToken;\n IERC20 public immutable oeth;\n IWETH9 public immutable weth;\n\n // Ordered list of pool assets\n uint128 public constant oethCoinIndex = 1;\n uint128 public constant ethCoinIndex = 0;\n\n /**\n * @dev Verifies that the caller is the Strategist.\n */\n modifier onlyStrategist() {\n require(\n msg.sender == IVault(vaultAddress).strategistAddr(),\n \"Caller is not the Strategist\"\n );\n _;\n }\n\n /**\n * @dev Checks the Curve pool's balances have improved and the balances\n * have not tipped to the other side.\n * This modifier only works on functions that do a single sided add or remove.\n * The standard deposit function adds to both sides of the pool in a way that\n * the pool's balance is not worsened.\n * Withdrawals are proportional so doesn't change the pools asset balance.\n */\n modifier improvePoolBalance() {\n // Get the asset and OToken balances in the Curve pool\n uint256[2] memory balancesBefore = curvePool.get_balances();\n // diff = ETH balance - OETH balance\n int256 diffBefore = int256(balancesBefore[ethCoinIndex]) -\n int256(balancesBefore[oethCoinIndex]);\n\n _;\n\n // Get the asset and OToken balances in the Curve pool\n uint256[2] memory balancesAfter = curvePool.get_balances();\n // diff = ETH balance - OETH balance\n int256 diffAfter = int256(balancesAfter[ethCoinIndex]) -\n int256(balancesAfter[oethCoinIndex]);\n\n if (diffBefore <= 0) {\n // If the pool was originally imbalanced in favor of OETH, then\n // we want to check that the pool is now more balanced\n require(diffAfter <= 0, \"OTokens overshot peg\");\n require(diffBefore < diffAfter, \"OTokens balance worse\");\n }\n if (diffBefore >= 0) {\n // If the pool was originally imbalanced in favor of ETH, then\n // we want to check that the pool is now more balanced\n require(diffAfter >= 0, \"Assets overshot peg\");\n require(diffAfter < diffBefore, \"Assets balance worse\");\n }\n }\n\n // Used to circumvent the stack too deep issue\n struct ConvexEthMetaConfig {\n address cvxDepositorAddress; //Address of the Convex depositor(AKA booster) for this pool\n address cvxRewardStakerAddress; //Address of the CVX rewards staker\n uint256 cvxDepositorPTokenId; //Pid of the pool referred to by Depositor and staker\n address oethAddress; //Address of OETH token\n address wethAddress; //Address of WETH\n }\n\n constructor(\n BaseStrategyConfig memory _baseConfig,\n ConvexEthMetaConfig memory _convexConfig\n ) InitializableAbstractStrategy(_baseConfig) {\n lpToken = IERC20(_baseConfig.platformAddress);\n curvePool = ICurveETHPoolV1(_baseConfig.platformAddress);\n\n cvxDepositorAddress = _convexConfig.cvxDepositorAddress;\n cvxRewardStaker = IRewardStaking(_convexConfig.cvxRewardStakerAddress);\n cvxDepositorPTokenId = _convexConfig.cvxDepositorPTokenId;\n oeth = IERC20(_convexConfig.oethAddress);\n weth = IWETH9(_convexConfig.wethAddress);\n }\n\n /**\n * Initializer for setting up strategy internal state. This overrides the\n * InitializableAbstractStrategy initializer as Curve strategies don't fit\n * well within that abstraction.\n * @param _rewardTokenAddresses Address of CRV & CVX\n * @param _assets Addresses of supported assets. eg WETH\n */\n function initialize(\n address[] calldata _rewardTokenAddresses, // CRV + CVX\n address[] calldata _assets // WETH\n ) external onlyGovernor initializer {\n require(_assets.length == 1, \"Must have exactly one asset\");\n require(_assets[0] == address(weth), \"Asset not WETH\");\n\n address[] memory pTokens = new address[](1);\n pTokens[0] = address(curvePool);\n\n InitializableAbstractStrategy._initialize(\n _rewardTokenAddresses,\n _assets,\n pTokens\n );\n\n _approveBase();\n }\n\n /***************************************\n Deposit\n ****************************************/\n\n /**\n * @notice Deposit WETH into the Curve pool\n * @param _weth Address of Wrapped ETH (WETH) contract.\n * @param _amount Amount of WETH to deposit.\n */\n function deposit(address _weth, uint256 _amount)\n external\n override\n onlyVault\n nonReentrant\n {\n _deposit(_weth, _amount);\n }\n\n function _deposit(address _weth, uint256 _wethAmount) internal {\n require(_wethAmount > 0, \"Must deposit something\");\n require(_weth == address(weth), \"Can only deposit WETH\");\n weth.withdraw(_wethAmount);\n\n emit Deposit(_weth, address(lpToken), _wethAmount);\n\n // Get the asset and OToken balances in the Curve pool\n uint256[2] memory balances = curvePool.get_balances();\n // safe to cast since min value is at least 0\n uint256 oethToAdd = uint256(\n _max(\n 0,\n int256(balances[ethCoinIndex]) +\n int256(_wethAmount) -\n int256(balances[oethCoinIndex])\n )\n );\n\n /* Add so much OETH so that the pool ends up being balanced. And at minimum\n * add as much OETH as WETH and at maximum twice as much OETH.\n */\n oethToAdd = Math.max(oethToAdd, _wethAmount);\n oethToAdd = Math.min(oethToAdd, _wethAmount * 2);\n\n /* Mint OETH with a strategy that attempts to contribute to stability of OETH/WETH pool. Try\n * to mint so much OETH that after deployment of liquidity pool ends up being balanced.\n *\n * To manage unpredictability minimal OETH minted will always be at least equal or greater\n * to WETH amount deployed. And never larger than twice the WETH amount deployed even if\n * it would have a further beneficial effect on pool stability.\n */\n IVault(vaultAddress).mintForStrategy(oethToAdd);\n\n emit Deposit(address(oeth), address(lpToken), oethToAdd);\n\n uint256[2] memory _amounts;\n _amounts[ethCoinIndex] = _wethAmount;\n _amounts[oethCoinIndex] = oethToAdd;\n\n uint256 valueInLpTokens = (_wethAmount + oethToAdd).divPrecisely(\n curvePool.get_virtual_price()\n );\n uint256 minMintAmount = valueInLpTokens.mulTruncate(\n uint256(1e18) - MAX_SLIPPAGE\n );\n\n // Do the deposit to the Curve pool\n // slither-disable-next-line arbitrary-send\n uint256 lpDeposited = curvePool.add_liquidity{ value: _wethAmount }(\n _amounts,\n minMintAmount\n );\n\n // Deposit the Curve pool's LP tokens into the Convex rewards pool\n require(\n IConvexDeposits(cvxDepositorAddress).deposit(\n cvxDepositorPTokenId,\n lpDeposited,\n true // Deposit with staking\n ),\n \"Depositing LP to Convex not successful\"\n );\n }\n\n /**\n * @notice Deposit the strategy's entire balance of WETH into the Curve pool\n */\n function depositAll() external override onlyVault nonReentrant {\n uint256 balance = weth.balanceOf(address(this));\n if (balance > 0) {\n _deposit(address(weth), balance);\n }\n }\n\n /***************************************\n Withdraw\n ****************************************/\n\n /**\n * @notice Withdraw ETH and OETH from the Curve pool, burn the OETH,\n * convert the ETH to WETH and transfer to the recipient.\n * @param _recipient Address to receive withdrawn asset which is normally the Vault.\n * @param _weth Address of the Wrapped ETH (WETH) contract.\n * @param _amount Amount of WETH to withdraw.\n */\n function withdraw(\n address _recipient,\n address _weth,\n uint256 _amount\n ) external override onlyVault nonReentrant {\n require(_amount > 0, \"Invalid amount\");\n require(_weth == address(weth), \"Can only withdraw WETH\");\n\n emit Withdrawal(_weth, address(lpToken), _amount);\n\n uint256 requiredLpTokens = calcTokenToBurn(_amount);\n\n _lpWithdraw(requiredLpTokens);\n\n /* math in requiredLpTokens should correctly calculate the amount of LP to remove\n * in that the strategy receives enough WETH on balanced removal\n */\n uint256[2] memory _minWithdrawalAmounts = [uint256(0), uint256(0)];\n _minWithdrawalAmounts[ethCoinIndex] = _amount;\n // slither-disable-next-line unused-return\n curvePool.remove_liquidity(requiredLpTokens, _minWithdrawalAmounts);\n\n // Burn all the removed OETH and any that was left in the strategy\n uint256 oethToBurn = oeth.balanceOf(address(this));\n IVault(vaultAddress).burnForStrategy(oethToBurn);\n\n emit Withdrawal(address(oeth), address(lpToken), oethToBurn);\n\n // Transfer WETH to the recipient\n weth.deposit{ value: _amount }();\n require(\n weth.transfer(_recipient, _amount),\n \"Transfer of WETH not successful\"\n );\n }\n\n function calcTokenToBurn(uint256 _wethAmount)\n internal\n view\n returns (uint256 lpToBurn)\n {\n /* The rate between coins in the pool determines the rate at which pool returns\n * tokens when doing balanced removal (remove_liquidity call). And by knowing how much WETH\n * we want we can determine how much of OETH we receive by removing liquidity.\n *\n * Because we are doing balanced removal we should be making profit when removing liquidity in a\n * pool tilted to either side.\n *\n * Important: A downside is that the Strategist / Governor needs to be\n * cognisant of not removing too much liquidity. And while the proposal to remove liquidity\n * is being voted on the pool tilt might change so much that the proposal that has been valid while\n * created is no longer valid.\n */\n\n uint256 poolWETHBalance = curvePool.balances(ethCoinIndex);\n /* K is multiplied by 1e36 which is used for higher precision calculation of required\n * pool LP tokens. Without it the end value can have rounding errors up to precision of\n * 10 digits. This way we move the decimal point by 36 places when doing the calculation\n * and again by 36 places when we are done with it.\n */\n uint256 k = (1e36 * lpToken.totalSupply()) / poolWETHBalance;\n // prettier-ignore\n // slither-disable-next-line divide-before-multiply\n uint256 diff = (_wethAmount + 1) * k;\n lpToBurn = diff / 1e36;\n }\n\n /**\n * @notice Remove all ETH and OETH from the Curve pool, burn the OETH,\n * convert the ETH to WETH and transfer to the Vault contract.\n */\n function withdrawAll() external override onlyVaultOrGovernor nonReentrant {\n uint256 gaugeTokens = cvxRewardStaker.balanceOf(address(this));\n _lpWithdraw(gaugeTokens);\n\n // Withdraws are proportional to assets held by 3Pool\n uint256[2] memory minWithdrawAmounts = [uint256(0), uint256(0)];\n\n // Remove liquidity\n // slither-disable-next-line unused-return\n curvePool.remove_liquidity(\n lpToken.balanceOf(address(this)),\n minWithdrawAmounts\n );\n\n // Burn all OETH\n uint256 oethToBurn = oeth.balanceOf(address(this));\n IVault(vaultAddress).burnForStrategy(oethToBurn);\n\n // Get the strategy contract's ether balance.\n // This includes all that was removed from the Curve pool and\n // any ether that was sitting in the strategy contract before the removal.\n uint256 ethBalance = address(this).balance;\n // Convert all the strategy contract's ether to WETH and transfer to the vault.\n weth.deposit{ value: ethBalance }();\n require(\n weth.transfer(vaultAddress, ethBalance),\n \"Transfer of WETH not successful\"\n );\n\n emit Withdrawal(address(weth), address(lpToken), ethBalance);\n emit Withdrawal(address(oeth), address(lpToken), oethToBurn);\n }\n\n /***************************************\n Curve pool Rebalancing\n ****************************************/\n\n /**\n * @notice Mint OTokens and one-sided add to the Curve pool.\n * This is used when the Curve pool does not have enough OTokens and too many ETH.\n * The OToken/Asset, eg OETH/ETH, price with increase.\n * The amount of assets in the vault is unchanged.\n * The total supply of OTokens is increased.\n * The asset value of the strategy and vault is increased.\n * @param _oTokens The amount of OTokens to be minted and added to the pool.\n */\n function mintAndAddOTokens(uint256 _oTokens)\n external\n onlyStrategist\n nonReentrant\n improvePoolBalance\n {\n IVault(vaultAddress).mintForStrategy(_oTokens);\n\n uint256[2] memory amounts = [uint256(0), uint256(0)];\n amounts[oethCoinIndex] = _oTokens;\n\n // Convert OETH to Curve pool LP tokens\n uint256 valueInLpTokens = (_oTokens).divPrecisely(\n curvePool.get_virtual_price()\n );\n // Apply slippage to LP tokens\n uint256 minMintAmount = valueInLpTokens.mulTruncate(\n uint256(1e18) - MAX_SLIPPAGE\n );\n\n // Add the minted OTokens to the Curve pool\n uint256 lpDeposited = curvePool.add_liquidity(amounts, minMintAmount);\n\n // Deposit the Curve pool LP tokens to the Convex rewards pool\n require(\n IConvexDeposits(cvxDepositorAddress).deposit(\n cvxDepositorPTokenId,\n lpDeposited,\n true // Deposit with staking\n ),\n \"Failed to Deposit LP to Convex\"\n );\n\n emit Deposit(address(oeth), address(lpToken), _oTokens);\n }\n\n /**\n * @notice One-sided remove of OTokens from the Curve pool which are then burned.\n * This is used when the Curve pool has too many OTokens and not enough ETH.\n * The amount of assets in the vault is unchanged.\n * The total supply of OTokens is reduced.\n * The asset value of the strategy and vault is reduced.\n * @param _lpTokens The amount of Curve pool LP tokens to be burned for OTokens.\n */\n function removeAndBurnOTokens(uint256 _lpTokens)\n external\n onlyStrategist\n nonReentrant\n improvePoolBalance\n {\n // Withdraw Curve pool LP tokens from Convex and remove OTokens from the Curve pool\n uint256 oethToBurn = _withdrawAndRemoveFromPool(\n _lpTokens,\n oethCoinIndex\n );\n\n // The vault burns the OTokens from this strategy\n IVault(vaultAddress).burnForStrategy(oethToBurn);\n\n emit Withdrawal(address(oeth), address(lpToken), oethToBurn);\n }\n\n /**\n * @notice One-sided remove of ETH from the Curve pool, convert to WETH\n * and transfer to the vault.\n * This is used when the Curve pool does not have enough OTokens and too many ETH.\n * The OToken/Asset, eg OETH/ETH, price with decrease.\n * The amount of assets in the vault increases.\n * The total supply of OTokens does not change.\n * The asset value of the strategy reduces.\n * The asset value of the vault should be close to the same.\n * @param _lpTokens The amount of Curve pool LP tokens to be burned for ETH.\n * @dev Curve pool LP tokens is used rather than WETH assets as Curve does not\n * have a way to accurately calculate the amount of LP tokens for a required\n * amount of ETH. Curve's `calc_token_amount` functioun does not include fees.\n * A 3rd party libary can be used that takes into account the fees, but this\n * is a gas intensive process. It's easier for the trusted strategist to\n * caclulate the amount of Curve pool LP tokens required off-chain.\n */\n function removeOnlyAssets(uint256 _lpTokens)\n external\n onlyStrategist\n nonReentrant\n improvePoolBalance\n {\n // Withdraw Curve pool LP tokens from Convex and remove ETH from the Curve pool\n uint256 ethAmount = _withdrawAndRemoveFromPool(_lpTokens, ethCoinIndex);\n\n // Convert ETH to WETH and transfer to the vault\n weth.deposit{ value: ethAmount }();\n require(\n weth.transfer(vaultAddress, ethAmount),\n \"Transfer of WETH not successful\"\n );\n\n emit Withdrawal(address(weth), address(lpToken), ethAmount);\n }\n\n /**\n * @dev Remove Curve pool LP tokens from the Convex pool and\n * do a one-sided remove of ETH or OETH from the Curve pool.\n * @param _lpTokens The amount of Curve pool LP tokens to be removed from the Convex pool.\n * @param coinIndex The index of the coin to be removed from the Curve pool. 0 = ETH, 1 = OETH.\n * @return coinsRemoved The amount of ETH or OETH removed from the Curve pool.\n */\n function _withdrawAndRemoveFromPool(uint256 _lpTokens, uint128 coinIndex)\n internal\n returns (uint256 coinsRemoved)\n {\n // Withdraw Curve pool LP tokens from Convex pool\n _lpWithdraw(_lpTokens);\n\n // Convert Curve pool LP tokens to ETH value\n uint256 valueInEth = _lpTokens.mulTruncate(\n curvePool.get_virtual_price()\n );\n // Apply slippage to ETH value\n uint256 minAmount = valueInEth.mulTruncate(\n uint256(1e18) - MAX_SLIPPAGE\n );\n\n // Remove just the ETH from the Curve pool\n coinsRemoved = curvePool.remove_liquidity_one_coin(\n _lpTokens,\n int128(coinIndex),\n minAmount,\n address(this)\n );\n }\n\n /***************************************\n Assets and Rewards\n ****************************************/\n\n /**\n * @notice Collect accumulated CRV and CVX rewards and send to the Harvester.\n */\n function collectRewardTokens()\n external\n override\n onlyHarvester\n nonReentrant\n {\n // Collect CRV and CVX\n cvxRewardStaker.getReward();\n _collectRewardTokens();\n }\n\n function _lpWithdraw(uint256 _wethAmount) internal {\n // withdraw and unwrap with claim takes back the lpTokens\n // and also collects the rewards for deposit\n cvxRewardStaker.withdrawAndUnwrap(_wethAmount, true);\n }\n\n /**\n * @notice Get the total asset value held in the platform\n * @param _asset Address of the asset\n * @return balance Total value of the asset in the platform\n */\n function checkBalance(address _asset)\n public\n view\n override\n returns (uint256 balance)\n {\n require(_asset == address(weth), \"Unsupported asset\");\n\n // Eth balance needed here for the balance check that happens from vault during depositing.\n balance = address(this).balance;\n uint256 lpTokens = cvxRewardStaker.balanceOf(address(this));\n if (lpTokens > 0) {\n balance += (lpTokens * curvePool.get_virtual_price()) / 1e18;\n }\n }\n\n /**\n * @notice Returns bool indicating whether asset is supported by strategy\n * @param _asset Address of the asset\n */\n function supportsAsset(address _asset) public view override returns (bool) {\n return _asset == address(weth);\n }\n\n /***************************************\n Approvals\n ****************************************/\n\n /**\n * @notice Approve the spending of all assets by their corresponding pool tokens,\n * if for some reason is it necessary.\n */\n function safeApproveAllTokens()\n external\n override\n onlyGovernor\n nonReentrant\n {\n _approveBase();\n }\n\n /**\n * @notice Accept unwrapped WETH\n */\n receive() external payable {}\n\n /**\n * @dev Since we are unwrapping WETH before depositing it to Curve\n * there is no need to set an approval for WETH on the Curve\n * pool\n * @param _asset Address of the asset\n * @param _pToken Address of the Curve LP token\n */\n // solhint-disable-next-line no-unused-vars\n function _abstractSetPToken(address _asset, address _pToken)\n internal\n override\n {}\n\n function _approveBase() internal {\n // Approve Curve pool for OETH (required for adding liquidity)\n // No approval is needed for ETH\n // slither-disable-next-line unused-return\n oeth.approve(platformAddress, type(uint256).max);\n\n // Approve Convex deposit contract to transfer Curve pool LP tokens\n // This is needed for deposits if Curve pool LP tokens into the Convex rewards pool\n // slither-disable-next-line unused-return\n lpToken.approve(cvxDepositorAddress, type(uint256).max);\n }\n\n /**\n * @dev Returns the largest of two numbers int256 version\n */\n function _max(int256 a, int256 b) internal pure returns (int256) {\n return a >= b ? a : b;\n }\n}\n" + }, + "contracts/strategies/ConvexGeneralizedMetaStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title Curve Convex Strategy\n * @notice Investment strategy for investing stablecoins via Curve 3Pool\n * @author Origin Protocol Inc\n */\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport \"@openzeppelin/contracts/utils/Strings.sol\";\n\nimport { IRewardStaking } from \"./IRewardStaking.sol\";\nimport { IConvexDeposits } from \"./IConvexDeposits.sol\";\nimport { ICurvePool } from \"./ICurvePool.sol\";\nimport { IERC20, InitializableAbstractStrategy } from \"./AbstractCurveStrategy.sol\";\nimport { AbstractConvexMetaStrategy } from \"./AbstractConvexMetaStrategy.sol\";\nimport { StableMath } from \"../utils/StableMath.sol\";\n\ncontract ConvexGeneralizedMetaStrategy is AbstractConvexMetaStrategy {\n using StableMath for uint256;\n using SafeERC20 for IERC20;\n\n constructor(BaseStrategyConfig memory _stratConfig)\n InitializableAbstractStrategy(_stratConfig)\n {}\n\n /* Take 3pool LP and deposit it to metapool. Take the LP from metapool\n * and deposit them to Convex.\n */\n function _lpDepositAll() internal override {\n IERC20 threePoolLp = IERC20(pTokenAddress);\n ICurvePool curvePool = ICurvePool(platformAddress);\n\n uint256 threePoolLpBalance = threePoolLp.balanceOf(address(this));\n uint256 curve3PoolVirtualPrice = curvePool.get_virtual_price();\n uint256 threePoolLpDollarValue = threePoolLpBalance.mulTruncate(\n curve3PoolVirtualPrice\n );\n\n uint256[2] memory _amounts = [0, threePoolLpBalance];\n\n uint256 metapoolVirtualPrice = metapool.get_virtual_price();\n /**\n * First convert all the deposited tokens to dollar values,\n * then divide by virtual price to convert to metapool LP tokens\n * and apply the max slippage\n */\n uint256 minReceived = threePoolLpDollarValue\n .divPrecisely(metapoolVirtualPrice)\n .mulTruncate(uint256(1e18) - MAX_SLIPPAGE);\n\n uint256 metapoolLp = metapool.add_liquidity(_amounts, minReceived);\n\n bool success = IConvexDeposits(cvxDepositorAddress).deposit(\n cvxDepositorPTokenId,\n metapoolLp,\n true // Deposit with staking\n );\n\n require(success, \"Failed to deposit to Convex\");\n }\n\n /**\n * Withdraw the specified amount of tokens from the gauge. And use all the resulting tokens\n * to remove liquidity from metapool\n * @param num3CrvTokens Number of Convex 3pool LP tokens to withdraw from metapool\n */\n function _lpWithdraw(uint256 num3CrvTokens) internal override {\n uint256 gaugeTokens = IRewardStaking(cvxRewardStakerAddress).balanceOf(\n address(this)\n );\n\n uint256 requiredMetapoolLpTokens = _calcCurveMetaTokenAmount(\n crvCoinIndex,\n num3CrvTokens\n );\n\n require(\n requiredMetapoolLpTokens <= gaugeTokens,\n string(\n bytes.concat(\n bytes(\"Attempting to withdraw \"),\n bytes(Strings.toString(requiredMetapoolLpTokens)),\n bytes(\", metapoolLP but only \"),\n bytes(Strings.toString(gaugeTokens)),\n bytes(\" available.\")\n )\n )\n );\n\n // withdraw and unwrap with claim takes back the lpTokens and also collects the rewards for deposit\n IRewardStaking(cvxRewardStakerAddress).withdrawAndUnwrap(\n requiredMetapoolLpTokens,\n true\n );\n\n if (requiredMetapoolLpTokens > 0) {\n // slither-disable-next-line unused-return\n metapool.remove_liquidity_one_coin(\n requiredMetapoolLpTokens,\n int128(crvCoinIndex),\n num3CrvTokens\n );\n }\n }\n\n function _lpWithdrawAll() internal override {\n uint256 gaugeTokens = IRewardStaking(cvxRewardStakerAddress).balanceOf(\n address(this)\n );\n IRewardStaking(cvxRewardStakerAddress).withdrawAndUnwrap(\n gaugeTokens,\n true\n );\n\n if (gaugeTokens > 0) {\n uint256 burnDollarAmount = gaugeTokens.mulTruncate(\n metapool.get_virtual_price()\n );\n uint256 curve3PoolExpected = burnDollarAmount.divPrecisely(\n ICurvePool(platformAddress).get_virtual_price()\n );\n\n // Always withdraw all of the available metapool LP tokens (similar to how we always deposit all)\n // slither-disable-next-line unused-return\n metapool.remove_liquidity_one_coin(\n gaugeTokens,\n int128(crvCoinIndex),\n curve3PoolExpected -\n curve3PoolExpected.mulTruncate(maxWithdrawalSlippage)\n );\n }\n }\n}\n" + }, + "contracts/strategies/ConvexOUSDMetaStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title Curve Convex Strategy\n * @notice Investment strategy for investing stablecoins via Curve 3Pool\n * @author Origin Protocol Inc\n */\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport \"@openzeppelin/contracts/utils/Strings.sol\";\nimport \"@openzeppelin/contracts/utils/math/Math.sol\";\n\nimport { IRewardStaking } from \"./IRewardStaking.sol\";\nimport { IConvexDeposits } from \"./IConvexDeposits.sol\";\nimport { ICurvePool } from \"./ICurvePool.sol\";\nimport { IERC20, InitializableAbstractStrategy } from \"./AbstractCurveStrategy.sol\";\nimport { AbstractConvexMetaStrategy } from \"./AbstractConvexMetaStrategy.sol\";\nimport { StableMath } from \"../utils/StableMath.sol\";\nimport { IVault } from \"../interfaces/IVault.sol\";\n\ncontract ConvexOUSDMetaStrategy is AbstractConvexMetaStrategy {\n using StableMath for uint256;\n using SafeERC20 for IERC20;\n\n constructor(BaseStrategyConfig memory _stratConfig)\n InitializableAbstractStrategy(_stratConfig)\n {}\n\n /* Take 3pool LP and mint the corresponding amount of ousd. Deposit and stake that to\n * ousd Curve Metapool. Take the LP from metapool and deposit them to Convex.\n */\n function _lpDepositAll() internal override {\n ICurvePool curvePool = ICurvePool(platformAddress);\n\n uint256 threePoolLpBalance = IERC20(pTokenAddress).balanceOf(\n address(this)\n );\n uint256 curve3PoolVirtualPrice = curvePool.get_virtual_price();\n uint256 threePoolLpDollarValue = threePoolLpBalance.mulTruncate(\n curve3PoolVirtualPrice\n );\n\n // safe to cast since min value is at least 0\n uint256 ousdToAdd = uint256(\n _max(\n 0,\n int256(\n metapool.balances(crvCoinIndex).mulTruncate(\n curve3PoolVirtualPrice\n )\n ) -\n int256(metapool.balances(mainCoinIndex)) +\n int256(threePoolLpDollarValue)\n )\n );\n\n /* Add so much OUSD so that the pool ends up being balanced. And at minimum\n * add twice as much OUSD as 3poolLP and at maximum at twice as\n * much OUSD.\n */\n ousdToAdd = Math.max(ousdToAdd, threePoolLpDollarValue);\n ousdToAdd = Math.min(ousdToAdd, threePoolLpDollarValue * 2);\n\n /* Mint OUSD with a strategy that attempts to contribute to stability of OUSD metapool. Try\n * to mint so much OUSD that after deployment of liquidity pool ends up being balanced.\n *\n * To manage unpredictability minimal OUSD minted will always be at least equal or greater\n * to stablecoin(DAI, USDC, USDT) amount of 3CRVLP deployed. And never larger than twice the\n * stablecoin amount of 3CRVLP deployed even if it would have a further beneficial effect\n * on pool stability.\n */\n if (ousdToAdd > 0) {\n IVault(vaultAddress).mintForStrategy(ousdToAdd);\n }\n\n uint256[2] memory _amounts = [ousdToAdd, threePoolLpBalance];\n\n uint256 metapoolVirtualPrice = metapool.get_virtual_price();\n /**\n * First convert all the deposited tokens to dollar values,\n * then divide by virtual price to convert to metapool LP tokens\n * and apply the max slippage\n */\n uint256 minReceived = (ousdToAdd + threePoolLpDollarValue)\n .divPrecisely(metapoolVirtualPrice)\n .mulTruncate(uint256(1e18) - MAX_SLIPPAGE);\n\n uint256 metapoolLp = metapool.add_liquidity(_amounts, minReceived);\n\n bool success = IConvexDeposits(cvxDepositorAddress).deposit(\n cvxDepositorPTokenId,\n metapoolLp,\n true // Deposit with staking\n );\n\n require(success, \"Failed to deposit to Convex\");\n }\n\n /**\n * Withdraw the specified amount of tokens from the gauge. And use all the resulting tokens\n * to remove liquidity from metapool\n * @param num3CrvTokens Number of 3CRV tokens to withdraw from metapool\n */\n function _lpWithdraw(uint256 num3CrvTokens) internal override {\n ICurvePool curvePool = ICurvePool(platformAddress);\n /* The rate between coins in the metapool determines the rate at which metapool returns\n * tokens when doing balanced removal (remove_liquidity call). And by knowing how much 3crvLp\n * we want we can determine how much of OUSD we receive by removing liquidity.\n *\n * Because we are doing balanced removal we should be making profit when removing liquidity in a\n * pool tilted to either side.\n *\n * Important: A downside is that the Strategist / Governor needs to be\n * cognisant of not removing too much liquidity. And while the proposal to remove liquidity\n * is being voted on the pool tilt might change so much that the proposal that has been valid while\n * created is no longer valid.\n */\n\n uint256 crvPoolBalance = metapool.balances(crvCoinIndex);\n /* K is multiplied by 1e36 which is used for higher precision calculation of required\n * metapool LP tokens. Without it the end value can have rounding errors up to precision of\n * 10 digits. This way we move the decimal point by 36 places when doing the calculation\n * and again by 36 places when we are done with it.\n */\n uint256 k = (1e36 * metapoolLPToken.totalSupply()) / crvPoolBalance;\n // simplifying below to: `uint256 diff = (num3CrvTokens - 1) * k` causes loss of precision\n // prettier-ignore\n // slither-disable-next-line divide-before-multiply\n uint256 diff = crvPoolBalance * k -\n (crvPoolBalance - num3CrvTokens - 1) * k;\n uint256 lpToBurn = diff / 1e36;\n\n uint256 gaugeTokens = IRewardStaking(cvxRewardStakerAddress).balanceOf(\n address(this)\n );\n\n require(\n lpToBurn <= gaugeTokens,\n string(\n bytes.concat(\n bytes(\"Attempting to withdraw \"),\n bytes(Strings.toString(lpToBurn)),\n bytes(\", metapoolLP but only \"),\n bytes(Strings.toString(gaugeTokens)),\n bytes(\" available.\")\n )\n )\n );\n\n // withdraw and unwrap with claim takes back the lpTokens and also collects the rewards for deposit\n IRewardStaking(cvxRewardStakerAddress).withdrawAndUnwrap(\n lpToBurn,\n true\n );\n\n // calculate the min amount of OUSD expected for the specified amount of LP tokens\n uint256 minOUSDAmount = lpToBurn.mulTruncate(\n metapool.get_virtual_price()\n ) -\n num3CrvTokens.mulTruncate(curvePool.get_virtual_price()) -\n 1;\n\n // withdraw the liquidity from metapool\n uint256[2] memory _removedAmounts = metapool.remove_liquidity(\n lpToBurn,\n [minOUSDAmount, num3CrvTokens]\n );\n\n IVault(vaultAddress).burnForStrategy(_removedAmounts[mainCoinIndex]);\n }\n\n function _lpWithdrawAll() internal override {\n IERC20 metapoolErc20 = IERC20(address(metapool));\n uint256 gaugeTokens = IRewardStaking(cvxRewardStakerAddress).balanceOf(\n address(this)\n );\n IRewardStaking(cvxRewardStakerAddress).withdrawAndUnwrap(\n gaugeTokens,\n true\n );\n\n uint256[2] memory _minAmounts = [uint256(0), uint256(0)];\n uint256[2] memory _removedAmounts = metapool.remove_liquidity(\n metapoolErc20.balanceOf(address(this)),\n _minAmounts\n );\n\n IVault(vaultAddress).burnForStrategy(_removedAmounts[mainCoinIndex]);\n }\n}\n" + }, + "contracts/strategies/ConvexStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title Curve Convex Strategy\n * @notice Investment strategy for investing stablecoins via Curve 3Pool\n * @author Origin Protocol Inc\n */\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\n\nimport { ICurvePool } from \"./ICurvePool.sol\";\nimport { IRewardStaking } from \"./IRewardStaking.sol\";\nimport { IConvexDeposits } from \"./IConvexDeposits.sol\";\nimport { IERC20, AbstractCurveStrategy, InitializableAbstractStrategy } from \"./AbstractCurveStrategy.sol\";\nimport { StableMath } from \"../utils/StableMath.sol\";\nimport { Helpers } from \"../utils/Helpers.sol\";\n\n/*\n * IMPORTANT(!) If ConvexStrategy needs to be re-deployed, it requires new\n * proxy contract with fresh storage slots. Changes in `AbstractCurveStrategy`\n * storage slots would break existing implementation.\n *\n * Remove this notice if ConvexStrategy is re-deployed\n */\ncontract ConvexStrategy is AbstractCurveStrategy {\n using StableMath for uint256;\n using SafeERC20 for IERC20;\n\n address internal cvxDepositorAddress;\n address internal cvxRewardStakerAddress;\n // slither-disable-next-line constable-states\n address private _deprecated_cvxRewardTokenAddress;\n uint256 internal cvxDepositorPTokenId;\n\n constructor(BaseStrategyConfig memory _stratConfig)\n InitializableAbstractStrategy(_stratConfig)\n {}\n\n /**\n * Initializer for setting up strategy internal state. This overrides the\n * InitializableAbstractStrategy initializer as Curve strategies don't fit\n * well within that abstraction.\n * @param _rewardTokenAddresses Address of CRV & CVX\n * @param _assets Addresses of supported assets. MUST be passed in the same\n * order as returned by coins on the pool contract, i.e.\n * DAI, USDC, USDT\n * @param _pTokens Platform Token corresponding addresses\n * @param _cvxDepositorAddress Address of the Convex depositor(AKA booster) for this pool\n * @param _cvxRewardStakerAddress Address of the CVX rewards staker\n * @param _cvxDepositorPTokenId Pid of the pool referred to by Depositor and staker\n */\n function initialize(\n address[] calldata _rewardTokenAddresses, // CRV + CVX\n address[] calldata _assets,\n address[] calldata _pTokens,\n address _cvxDepositorAddress,\n address _cvxRewardStakerAddress,\n uint256 _cvxDepositorPTokenId\n ) external onlyGovernor initializer {\n require(_assets.length == 3, \"Must have exactly three assets\");\n // Should be set prior to abstract initialize call otherwise\n // abstractSetPToken calls will fail\n cvxDepositorAddress = _cvxDepositorAddress;\n cvxRewardStakerAddress = _cvxRewardStakerAddress;\n cvxDepositorPTokenId = _cvxDepositorPTokenId;\n pTokenAddress = _pTokens[0];\n\n InitializableAbstractStrategy._initialize(\n _rewardTokenAddresses,\n _assets,\n _pTokens\n );\n _approveBase();\n }\n\n function _lpDepositAll() internal override {\n IERC20 pToken = IERC20(pTokenAddress);\n // Deposit with staking\n bool success = IConvexDeposits(cvxDepositorAddress).deposit(\n cvxDepositorPTokenId,\n pToken.balanceOf(address(this)),\n true\n );\n require(success, \"Failed to deposit to Convex\");\n }\n\n function _lpWithdraw(uint256 numCrvTokens) internal override {\n uint256 gaugePTokens = IRewardStaking(cvxRewardStakerAddress).balanceOf(\n address(this)\n );\n\n // Not enough in this contract or in the Gauge, can't proceed\n require(numCrvTokens > gaugePTokens, \"Insufficient 3CRV balance\");\n\n // withdraw and unwrap with claim takes back the lpTokens and also collects the rewards to this\n IRewardStaking(cvxRewardStakerAddress).withdrawAndUnwrap(\n numCrvTokens,\n true\n );\n }\n\n function _lpWithdrawAll() internal override {\n // withdraw and unwrap with claim takes back the lpTokens and also collects the rewards to this\n IRewardStaking(cvxRewardStakerAddress).withdrawAndUnwrap(\n IRewardStaking(cvxRewardStakerAddress).balanceOf(address(this)),\n true\n );\n }\n\n function _approveBase() internal override {\n IERC20 pToken = IERC20(pTokenAddress);\n // 3Pool for LP token (required for removing liquidity)\n pToken.safeApprove(platformAddress, 0);\n pToken.safeApprove(platformAddress, type(uint256).max);\n // Gauge for LP token\n pToken.safeApprove(cvxDepositorAddress, 0);\n pToken.safeApprove(cvxDepositorAddress, type(uint256).max);\n }\n\n /**\n * @dev Get the total asset value held in the platform\n * @param _asset Address of the asset\n * @return balance Total value of the asset in the platform\n */\n function checkBalance(address _asset)\n public\n view\n override\n returns (uint256 balance)\n {\n require(assetToPToken[_asset] != address(0), \"Unsupported asset\");\n // LP tokens in this contract. This should generally be nothing as we\n // should always stake the full balance in the Gauge, but include for\n // safety\n uint256 contractPTokens = IERC20(pTokenAddress).balanceOf(\n address(this)\n );\n uint256 gaugePTokens = IRewardStaking(cvxRewardStakerAddress).balanceOf(\n address(this)\n );\n uint256 totalPTokens = contractPTokens + gaugePTokens;\n\n ICurvePool curvePool = ICurvePool(platformAddress);\n if (totalPTokens > 0) {\n uint256 virtual_price = curvePool.get_virtual_price();\n uint256 value = (totalPTokens * virtual_price) / 1e18;\n uint256 assetDecimals = Helpers.getDecimals(_asset);\n balance = value.scaleBy(assetDecimals, 18) / 3;\n }\n }\n\n /**\n * @dev Collect accumulated CRV and CVX and send to Vault.\n */\n function collectRewardTokens()\n external\n override\n onlyHarvester\n nonReentrant\n {\n // Collect CRV and CVX\n IRewardStaking(cvxRewardStakerAddress).getReward();\n _collectRewardTokens();\n }\n}\n" + }, + "contracts/strategies/crosschain/AbstractCCTPIntegrator.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title AbstractCCTPIntegrator\n * @author Origin Protocol Inc\n *\n * @dev Abstract contract that contains all the logic used to integrate with CCTP.\n */\n\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport { IERC20 } from \"../../utils/InitializableAbstractStrategy.sol\";\n\nimport { ICCTPTokenMessenger, ICCTPMessageTransmitter, IMessageHandlerV2 } from \"../../interfaces/cctp/ICCTP.sol\";\n\nimport { CrossChainStrategyHelper } from \"./CrossChainStrategyHelper.sol\";\nimport { Governable } from \"../../governance/Governable.sol\";\nimport { BytesHelper } from \"../../utils/BytesHelper.sol\";\nimport \"../../utils/Helpers.sol\";\n\nabstract contract AbstractCCTPIntegrator is Governable, IMessageHandlerV2 {\n using SafeERC20 for IERC20;\n\n using BytesHelper for bytes;\n using CrossChainStrategyHelper for bytes;\n\n event LastTransferNonceUpdated(uint64 lastTransferNonce);\n event NonceProcessed(uint64 nonce);\n\n event CCTPMinFinalityThresholdSet(uint16 minFinalityThreshold);\n event CCTPFeePremiumBpsSet(uint16 feePremiumBps);\n event OperatorChanged(address operator);\n event TokensBridged(\n uint32 destinationDomain,\n address peerStrategy,\n address tokenAddress,\n uint256 tokenAmount,\n uint256 maxFee,\n uint32 minFinalityThreshold,\n bytes hookData\n );\n event MessageTransmitted(\n uint32 destinationDomain,\n address peerStrategy,\n uint32 minFinalityThreshold,\n bytes message\n );\n\n // Message body V2 fields\n // Ref: https://developers.circle.com/cctp/technical-guide#message-body\n // Ref: https://github.com/circlefin/evm-cctp-contracts/blob/master/src/messages/v2/BurnMessageV2.sol\n uint8 private constant BURN_MESSAGE_V2_VERSION_INDEX = 0;\n uint8 private constant BURN_MESSAGE_V2_BURN_TOKEN_INDEX = 4;\n uint8 private constant BURN_MESSAGE_V2_RECIPIENT_INDEX = 36;\n uint8 private constant BURN_MESSAGE_V2_AMOUNT_INDEX = 68;\n uint8 private constant BURN_MESSAGE_V2_MESSAGE_SENDER_INDEX = 100;\n uint8 private constant BURN_MESSAGE_V2_FEE_EXECUTED_INDEX = 164;\n uint8 private constant BURN_MESSAGE_V2_HOOK_DATA_INDEX = 228;\n\n /**\n * @notice Max transfer threshold imposed by the CCTP\n * Ref: https://developers.circle.com/cctp/evm-smart-contracts#depositforburn\n * @dev 10M USDC limit applies to both standard and fast transfer modes. The fast transfer mode has\n * an additional limitation that is not present on-chain and Circle may alter that amount off-chain\n * at their preference. The amount available for fast transfer can be queried here:\n * https://iris-api.circle.com/v2/fastBurn/USDC/allowance .\n * If a fast transfer token transaction has been issued and there is not enough allowance for it\n * the off-chain Iris component will re-attempt the transaction and if it fails it will fallback\n * to a standard transfer. Reference section 4.3 in the whitepaper:\n * https://6778953.fs1.hubspotusercontent-na1.net/hubfs/6778953/PDFs/Whitepapers/CCTPV2_White_Paper.pdf\n */\n uint256 public constant MAX_TRANSFER_AMOUNT = 10_000_000 * 10**6; // 10M USDC\n\n /// @notice Minimum transfer amount to avoid zero or dust transfers\n uint256 public constant MIN_TRANSFER_AMOUNT = 10**6;\n\n // CCTP contracts\n // This implementation assumes that remote and local chains have these contracts\n // deployed on the same addresses.\n /// @notice CCTP message transmitter contract\n ICCTPMessageTransmitter public immutable cctpMessageTransmitter;\n /// @notice CCTP token messenger contract\n ICCTPTokenMessenger public immutable cctpTokenMessenger;\n\n /// @notice USDC address on local chain\n address public immutable usdcToken;\n\n /// @notice USDC address on remote chain\n address public immutable peerUsdcToken;\n\n /// @notice Domain ID of the chain from which messages are accepted\n uint32 public immutable peerDomainID;\n\n /// @notice Strategy address on other chain\n address public immutable peerStrategy;\n\n /**\n * @notice Minimum finality threshold\n * Can be 1000 (safe, after 1 epoch) or 2000 (finalized, after 2 epochs).\n * Ref: https://developers.circle.com/cctp/technical-guide#finality-thresholds\n * @dev When configuring the contract for fast transfer we should check the available\n * allowance of USDC that can be bridged using fast mode:\n * wget https://iris-api.circle.com/v2/fastBurn/USDC/allowance\n */\n uint16 public minFinalityThreshold;\n\n /// @notice Fee premium in basis points\n uint16 public feePremiumBps;\n\n /// @notice Nonce of the last known deposit or withdrawal\n uint64 public lastTransferNonce;\n\n /// @notice Operator address: Can relay CCTP messages\n address public operator;\n\n /// @notice Mapping of processed nonces\n mapping(uint64 => bool) private nonceProcessed;\n\n // For future use\n uint256[48] private __gap;\n\n modifier onlyCCTPMessageTransmitter() {\n require(\n msg.sender == address(cctpMessageTransmitter),\n \"Caller is not CCTP transmitter\"\n );\n _;\n }\n\n modifier onlyOperator() {\n require(msg.sender == operator, \"Caller is not the Operator\");\n _;\n }\n\n /**\n * @notice Configuration for CCTP integration\n * @param cctpTokenMessenger Address of the CCTP token messenger contract\n * @param cctpMessageTransmitter Address of the CCTP message transmitter contract\n * @param peerDomainID Domain ID of the chain from which messages are accepted.\n * 0 for Ethereum, 6 for Base, etc.\n * Ref: https://developers.circle.com/cctp/cctp-supported-blockchains\n * @param peerStrategy Address of the master or remote strategy on the other chain\n * @param usdcToken USDC address on local chain\n */\n struct CCTPIntegrationConfig {\n address cctpTokenMessenger;\n address cctpMessageTransmitter;\n uint32 peerDomainID;\n address peerStrategy;\n address usdcToken;\n address peerUsdcToken;\n }\n\n constructor(CCTPIntegrationConfig memory _config) {\n require(_config.usdcToken != address(0), \"Invalid USDC address\");\n require(\n _config.peerUsdcToken != address(0),\n \"Invalid peer USDC address\"\n );\n require(\n _config.cctpTokenMessenger != address(0),\n \"Invalid CCTP config\"\n );\n require(\n _config.cctpMessageTransmitter != address(0),\n \"Invalid CCTP config\"\n );\n require(\n _config.peerStrategy != address(0),\n \"Invalid peer strategy address\"\n );\n\n cctpMessageTransmitter = ICCTPMessageTransmitter(\n _config.cctpMessageTransmitter\n );\n cctpTokenMessenger = ICCTPTokenMessenger(_config.cctpTokenMessenger);\n\n // Domain ID of the chain from which messages are accepted\n peerDomainID = _config.peerDomainID;\n\n // Strategy address on other chain, should\n // always be same as the proxy of this strategy\n peerStrategy = _config.peerStrategy;\n\n // USDC address on local chain\n usdcToken = _config.usdcToken;\n\n // Just a sanity check to ensure the base token is USDC\n uint256 _usdcTokenDecimals = Helpers.getDecimals(_config.usdcToken);\n string memory _usdcTokenSymbol = Helpers.getSymbol(_config.usdcToken);\n require(_usdcTokenDecimals == 6, \"Base token decimals must be 6\");\n require(\n keccak256(abi.encodePacked(_usdcTokenSymbol)) ==\n keccak256(abi.encodePacked(\"USDC\")),\n \"Token symbol must be USDC\"\n );\n\n // USDC address on remote chain\n peerUsdcToken = _config.peerUsdcToken;\n }\n\n /**\n * @dev Initialize the implementation contract\n * @param _operator Operator address\n * @param _minFinalityThreshold Minimum finality threshold\n * @param _feePremiumBps Fee premium in basis points\n */\n function _initialize(\n address _operator,\n uint16 _minFinalityThreshold,\n uint16 _feePremiumBps\n ) internal {\n _setOperator(_operator);\n _setMinFinalityThreshold(_minFinalityThreshold);\n _setFeePremiumBps(_feePremiumBps);\n\n // Nonce starts at 1, so assume nonce 0 as processed.\n // NOTE: This will cause the deposit/withdraw to fail if the\n // strategy is not initialized properly (which is expected).\n nonceProcessed[0] = true;\n }\n\n /***************************************\n Settings\n ****************************************/\n /**\n * @dev Set the operator address\n * @param _operator Operator address\n */\n function setOperator(address _operator) external onlyGovernor {\n _setOperator(_operator);\n }\n\n /**\n * @dev Set the operator address\n * @param _operator Operator address\n */\n function _setOperator(address _operator) internal {\n operator = _operator;\n emit OperatorChanged(_operator);\n }\n\n /**\n * @dev Set the minimum finality threshold at which\n * the message is considered to be finalized to relay.\n * Only accepts a value of 1000 (Safe, after 1 epoch) or\n * 2000 (Finalized, after 2 epochs).\n * @param _minFinalityThreshold Minimum finality threshold\n */\n function setMinFinalityThreshold(uint16 _minFinalityThreshold)\n external\n onlyGovernor\n {\n _setMinFinalityThreshold(_minFinalityThreshold);\n }\n\n /**\n * @dev Set the minimum finality threshold\n * @param _minFinalityThreshold Minimum finality threshold\n */\n function _setMinFinalityThreshold(uint16 _minFinalityThreshold) internal {\n // 1000 for fast transfer and 2000 for standard transfer\n require(\n _minFinalityThreshold == 1000 || _minFinalityThreshold == 2000,\n \"Invalid threshold\"\n );\n\n minFinalityThreshold = _minFinalityThreshold;\n emit CCTPMinFinalityThresholdSet(_minFinalityThreshold);\n }\n\n /**\n * @dev Set the fee premium in basis points.\n * Cannot be higher than 30% (3000 basis points).\n * @param _feePremiumBps Fee premium in basis points\n */\n function setFeePremiumBps(uint16 _feePremiumBps) external onlyGovernor {\n _setFeePremiumBps(_feePremiumBps);\n }\n\n /**\n * @dev Set the fee premium in basis points\n * Cannot be higher than 30% (3000 basis points).\n * Ref: https://developers.circle.com/cctp/technical-guide#fees\n * @param _feePremiumBps Fee premium in basis points\n */\n function _setFeePremiumBps(uint16 _feePremiumBps) internal {\n require(_feePremiumBps <= 3000, \"Fee premium too high\"); // 30%\n\n feePremiumBps = _feePremiumBps;\n emit CCTPFeePremiumBpsSet(_feePremiumBps);\n }\n\n /***************************************\n CCTP message handling\n ****************************************/\n\n /**\n * @dev Handles a finalized CCTP message\n * @param sourceDomain Source domain of the message\n * @param sender Sender of the message\n * @param finalityThresholdExecuted Fidelity threshold executed\n * @param messageBody Message body\n */\n function handleReceiveFinalizedMessage(\n uint32 sourceDomain,\n bytes32 sender,\n uint32 finalityThresholdExecuted,\n bytes memory messageBody\n ) external override onlyCCTPMessageTransmitter returns (bool) {\n // Make sure the finality threshold at execution is at least 2000\n require(\n finalityThresholdExecuted >= 2000,\n \"Finality threshold too low\"\n );\n\n return _handleReceivedMessage(sourceDomain, sender, messageBody);\n }\n\n /**\n * @dev Handles an unfinalized but safe CCTP message\n * @param sourceDomain Source domain of the message\n * @param sender Sender of the message\n * @param finalityThresholdExecuted Fidelity threshold executed\n * @param messageBody Message body\n */\n function handleReceiveUnfinalizedMessage(\n uint32 sourceDomain,\n bytes32 sender,\n uint32 finalityThresholdExecuted,\n bytes memory messageBody\n ) external override onlyCCTPMessageTransmitter returns (bool) {\n // Make sure the contract is configured to handle unfinalized messages\n require(\n minFinalityThreshold == 1000,\n \"Unfinalized messages are not supported\"\n );\n // Make sure the finality threshold at execution is at least 1000\n require(\n finalityThresholdExecuted >= 1000,\n \"Finality threshold too low\"\n );\n\n return _handleReceivedMessage(sourceDomain, sender, messageBody);\n }\n\n /**\n * @dev Handles a CCTP message\n * @param sourceDomain Source domain of the message\n * @param sender Sender of the message\n * @param messageBody Message body\n */\n function _handleReceivedMessage(\n uint32 sourceDomain,\n bytes32 sender,\n bytes memory messageBody\n ) internal returns (bool) {\n require(sourceDomain == peerDomainID, \"Unknown Source Domain\");\n\n // Extract address from bytes32 (CCTP stores addresses as right-padded bytes32)\n address senderAddress = address(uint160(uint256(sender)));\n require(senderAddress == peerStrategy, \"Unknown Sender\");\n\n _onMessageReceived(messageBody);\n\n return true;\n }\n\n /**\n * @dev Sends tokens to the peer strategy using CCTP Token Messenger\n * @param tokenAmount Amount of tokens to send\n * @param hookData Hook data\n */\n function _sendTokens(uint256 tokenAmount, bytes memory hookData)\n internal\n virtual\n {\n // CCTP has a maximum transfer amount of 10M USDC per tx\n require(tokenAmount <= MAX_TRANSFER_AMOUNT, \"Token amount too high\");\n\n // Approve only what needs to be transferred\n IERC20(usdcToken).safeApprove(address(cctpTokenMessenger), tokenAmount);\n\n // Compute the max fee to be paid.\n // Ref: https://developers.circle.com/cctp/evm-smart-contracts#getminfeeamount\n // The right way to compute fees would be to use CCTP's getMinFeeAmount function.\n // The issue is that the getMinFeeAmount is not present on v2.0 contracts, but is on\n // v2.1. Some of CCTP's deployed contracts are v2.0, some are v2.1.\n // We will only be using standard transfers and fee on those is 0 for now. If they\n // ever start implementing fee for standard transfers or if we decide to use fast\n // trasnfer, we can use feePremiumBps as a workaround.\n uint256 maxFee = feePremiumBps > 0\n ? (tokenAmount * feePremiumBps) / 10000\n : 0;\n\n // Send tokens to the peer strategy using CCTP Token Messenger\n cctpTokenMessenger.depositForBurnWithHook(\n tokenAmount,\n peerDomainID,\n bytes32(uint256(uint160(peerStrategy))),\n address(usdcToken),\n bytes32(uint256(uint160(peerStrategy))),\n maxFee,\n uint32(minFinalityThreshold),\n hookData\n );\n\n emit TokensBridged(\n peerDomainID,\n peerStrategy,\n usdcToken,\n tokenAmount,\n maxFee,\n uint32(minFinalityThreshold),\n hookData\n );\n }\n\n /**\n * @dev Sends a message to the peer strategy using CCTP Message Transmitter\n * @param message Payload of the message to send\n */\n function _sendMessage(bytes memory message) internal virtual {\n cctpMessageTransmitter.sendMessage(\n peerDomainID,\n bytes32(uint256(uint160(peerStrategy))),\n bytes32(uint256(uint160(peerStrategy))),\n uint32(minFinalityThreshold),\n message\n );\n\n emit MessageTransmitted(\n peerDomainID,\n peerStrategy,\n uint32(minFinalityThreshold),\n message\n );\n }\n\n /**\n * @dev Receives a message from the peer strategy on the other chain,\n * does some basic checks and relays it to the local MessageTransmitterV2.\n * If the message is a burn message, it will also handle the hook data\n * and call the _onTokenReceived function.\n * @param message Payload of the message to send\n * @param attestation Attestation of the message\n */\n function relay(bytes memory message, bytes memory attestation)\n external\n onlyOperator\n {\n (\n uint32 version,\n uint32 sourceDomainID,\n address sender,\n address recipient,\n bytes memory messageBody\n ) = message.decodeMessageHeader();\n\n // Ensure that it's a CCTP message\n require(\n version == CrossChainStrategyHelper.CCTP_MESSAGE_VERSION,\n \"Invalid CCTP message version\"\n );\n\n // Ensure that the source domain is the peer domain\n require(sourceDomainID == peerDomainID, \"Unknown Source Domain\");\n\n // Ensure message body version\n version = messageBody.extractUint32(BURN_MESSAGE_V2_VERSION_INDEX);\n\n // NOTE: There's a possibility that the CCTP Token Messenger might\n // send other types of messages in future, not just the burn message.\n // If it ever comes to that, this shouldn't cause us any problems\n // because it has to still go through the followign checks:\n // - version check\n // - message body length check\n // - sender and recipient (which should be in the same slots and same as address(this))\n // - hook data handling (which will revert even if all the above checks pass)\n bool isBurnMessageV1 = sender == address(cctpTokenMessenger);\n\n if (isBurnMessageV1) {\n // Handle burn message\n require(\n version == 1 &&\n messageBody.length >= BURN_MESSAGE_V2_HOOK_DATA_INDEX,\n \"Invalid burn message\"\n );\n\n // Ensure the burn token is USDC\n address burnToken = messageBody.extractAddress(\n BURN_MESSAGE_V2_BURN_TOKEN_INDEX\n );\n require(burnToken == peerUsdcToken, \"Invalid burn token\");\n\n // Address of caller of depositForBurn (or depositForBurnWithCaller) on source domain\n sender = messageBody.extractAddress(\n BURN_MESSAGE_V2_MESSAGE_SENDER_INDEX\n );\n\n recipient = messageBody.extractAddress(\n BURN_MESSAGE_V2_RECIPIENT_INDEX\n );\n } else {\n // We handle only Burn message or our custom messagee\n require(\n version == CrossChainStrategyHelper.ORIGIN_MESSAGE_VERSION,\n \"Unsupported message version\"\n );\n }\n\n // Ensure the recipient is this contract\n // Both sender and recipient should be deployed to same address on both chains.\n require(address(this) == recipient, \"Unexpected recipient address\");\n require(sender == peerStrategy, \"Incorrect sender/recipient address\");\n\n // Relay the message\n // This step also mints USDC and transfers it to the recipient wallet\n bool relaySuccess = cctpMessageTransmitter.receiveMessage(\n message,\n attestation\n );\n require(relaySuccess, \"Receive message failed\");\n\n if (isBurnMessageV1) {\n // Extract the hook data from the message body\n bytes memory hookData = messageBody.extractSlice(\n BURN_MESSAGE_V2_HOOK_DATA_INDEX,\n messageBody.length\n );\n\n // Extract the token amount from the message body\n uint256 tokenAmount = messageBody.extractUint256(\n BURN_MESSAGE_V2_AMOUNT_INDEX\n );\n\n // Extract the fee executed from the message body\n uint256 feeExecuted = messageBody.extractUint256(\n BURN_MESSAGE_V2_FEE_EXECUTED_INDEX\n );\n\n // Call the _onTokenReceived function\n _onTokenReceived(tokenAmount - feeExecuted, feeExecuted, hookData);\n }\n }\n\n /***************************************\n Message utils\n ****************************************/\n\n /***************************************\n Nonce Handling\n ****************************************/\n /**\n * @dev Checks if the last known transfer is pending.\n * Nonce starts at 1, so 0 is disregarded.\n * @return True if a transfer is pending, false otherwise\n */\n function isTransferPending() public view returns (bool) {\n return !nonceProcessed[lastTransferNonce];\n }\n\n /**\n * @dev Checks if a given nonce is processed.\n * Nonce starts at 1, so 0 is disregarded.\n * @param nonce Nonce to check\n * @return True if the nonce is processed, false otherwise\n */\n function isNonceProcessed(uint64 nonce) public view returns (bool) {\n return nonceProcessed[nonce];\n }\n\n /**\n * @dev Marks a given nonce as processed.\n * Can only mark nonce as processed once. New nonce should\n * always be greater than the last known nonce. Also updates\n * the last known nonce.\n * @param nonce Nonce to mark as processed\n */\n function _markNonceAsProcessed(uint64 nonce) internal {\n uint64 lastNonce = lastTransferNonce;\n\n // Can only mark latest nonce as processed\n // Master strategy when receiving a message from the remote strategy\n // will have lastNone == nonce, as the nonce is increase at the start\n // of deposit / withdrawal flow.\n // Remote strategy will have lastNonce < nonce, as a new nonce initiated\n // from master will be greater than the last one.\n require(nonce >= lastNonce, \"Nonce too low\");\n // Can only mark nonce as processed once\n require(!nonceProcessed[nonce], \"Nonce already processed\");\n\n nonceProcessed[nonce] = true;\n emit NonceProcessed(nonce);\n\n if (nonce != lastNonce) {\n // Update last known nonce\n lastTransferNonce = nonce;\n emit LastTransferNonceUpdated(nonce);\n }\n }\n\n /**\n * @dev Gets the next nonce to use.\n * Nonce starts at 1, so 0 is disregarded.\n * Reverts if last nonce hasn't been processed yet.\n * @return Next nonce\n */\n function _getNextNonce() internal returns (uint64) {\n uint64 nonce = lastTransferNonce;\n\n require(nonceProcessed[nonce], \"Pending token transfer\");\n\n nonce = nonce + 1;\n lastTransferNonce = nonce;\n emit LastTransferNonceUpdated(nonce);\n\n return nonce;\n }\n\n /***************************************\n Inheritence overrides\n ****************************************/\n\n /**\n * @dev Called when the USDC is received from the CCTP\n * @param tokenAmount The actual amount of USDC received (amount sent - fee executed)\n * @param feeExecuted The fee executed\n * @param payload The payload of the message (hook data)\n */\n function _onTokenReceived(\n uint256 tokenAmount,\n uint256 feeExecuted,\n bytes memory payload\n ) internal virtual;\n\n /**\n * @dev Called when the message is received\n * @param payload The payload of the message\n */\n function _onMessageReceived(bytes memory payload) internal virtual;\n}\n" + }, + "contracts/strategies/crosschain/CrossChainMasterStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title OUSD Yearn V3 Master Strategy - the Mainnet part\n * @author Origin Protocol Inc\n *\n * @dev This strategy can only perform 1 deposit or withdrawal at a time. For that\n * reason it shouldn't be configured as an asset default strategy.\n */\n\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport { IERC20, InitializableAbstractStrategy } from \"../../utils/InitializableAbstractStrategy.sol\";\nimport { AbstractCCTPIntegrator } from \"./AbstractCCTPIntegrator.sol\";\nimport { CrossChainStrategyHelper } from \"./CrossChainStrategyHelper.sol\";\n\ncontract CrossChainMasterStrategy is\n AbstractCCTPIntegrator,\n InitializableAbstractStrategy\n{\n using SafeERC20 for IERC20;\n using CrossChainStrategyHelper for bytes;\n\n /**\n * @notice Remote strategy balance\n * @dev The remote balance is cached and might not reflect the actual\n * real-time balance of the remote strategy.\n */\n uint256 public remoteStrategyBalance;\n\n /// @notice Amount that's bridged due to a pending Deposit process\n /// but with no acknowledgement from the remote strategy yet\n uint256 public pendingAmount;\n\n uint256 internal constant MAX_BALANCE_CHECK_AGE = 1 days;\n\n event RemoteStrategyBalanceUpdated(uint256 balance);\n event WithdrawRequested(address indexed asset, uint256 amount);\n event WithdrawAllSkipped();\n event BalanceCheckIgnored(uint64 nonce, uint256 timestamp, bool isTooOld);\n\n /**\n * @param _stratConfig The platform and OToken vault addresses\n */\n constructor(\n BaseStrategyConfig memory _stratConfig,\n CCTPIntegrationConfig memory _cctpConfig\n )\n InitializableAbstractStrategy(_stratConfig)\n AbstractCCTPIntegrator(_cctpConfig)\n {\n require(\n _stratConfig.platformAddress == address(0),\n \"Invalid platform address\"\n );\n require(\n _stratConfig.vaultAddress != address(0),\n \"Invalid Vault address\"\n );\n }\n\n /**\n * @dev Initialize the strategy implementation\n * @param _operator Address of the operator\n * @param _minFinalityThreshold Minimum finality threshold\n * @param _feePremiumBps Fee premium in basis points\n */\n function initialize(\n address _operator,\n uint16 _minFinalityThreshold,\n uint16 _feePremiumBps\n ) external virtual onlyGovernor initializer {\n _initialize(_operator, _minFinalityThreshold, _feePremiumBps);\n\n address[] memory rewardTokens = new address[](0);\n address[] memory assets = new address[](0);\n address[] memory pTokens = new address[](0);\n\n InitializableAbstractStrategy._initialize(\n rewardTokens,\n assets,\n pTokens\n );\n }\n\n /// @inheritdoc InitializableAbstractStrategy\n function deposit(address _asset, uint256 _amount)\n external\n override\n onlyVault\n nonReentrant\n {\n _deposit(_asset, _amount);\n }\n\n /// @inheritdoc InitializableAbstractStrategy\n function depositAll() external override onlyVault nonReentrant {\n uint256 balance = IERC20(usdcToken).balanceOf(address(this));\n // Deposit if balance is greater than 1 USDC\n if (balance >= MIN_TRANSFER_AMOUNT) {\n _deposit(usdcToken, balance);\n }\n }\n\n /// @inheritdoc InitializableAbstractStrategy\n function withdraw(\n address _recipient,\n address _asset,\n uint256 _amount\n ) external override onlyVault nonReentrant {\n require(_recipient == vaultAddress, \"Only Vault can withdraw\");\n _withdraw(_asset, _amount);\n }\n\n /// @inheritdoc InitializableAbstractStrategy\n function withdrawAll() external override onlyVaultOrGovernor nonReentrant {\n if (isTransferPending()) {\n // Do nothing if there is a pending transfer\n // Note: We never want withdrawAll to fail, so\n // emit an event to indicate that the withdrawal was skipped\n emit WithdrawAllSkipped();\n return;\n }\n\n // Withdraw everything in Remote strategy\n uint256 _remoteBalance = remoteStrategyBalance;\n if (_remoteBalance < MIN_TRANSFER_AMOUNT) {\n // Do nothing if there is less than 1 USDC in the Remote strategy\n return;\n }\n\n _withdraw(\n usdcToken,\n _remoteBalance > MAX_TRANSFER_AMOUNT\n ? MAX_TRANSFER_AMOUNT\n : _remoteBalance\n );\n }\n\n /**\n * @notice Check the balance of the strategy that includes\n * the balance of the asset on this contract,\n * the amount of the asset being bridged,\n * and the balance reported by the Remote strategy.\n * @param _asset Address of the asset to check\n * @return balance Total balance of the asset\n */\n function checkBalance(address _asset)\n public\n view\n override\n returns (uint256 balance)\n {\n require(_asset == usdcToken, \"Unsupported asset\");\n\n // USDC balance on this contract\n // + USDC being bridged\n // + USDC cached in the corresponding Remote part of this contract\n return\n IERC20(usdcToken).balanceOf(address(this)) +\n pendingAmount +\n remoteStrategyBalance;\n }\n\n /// @inheritdoc InitializableAbstractStrategy\n function supportsAsset(address _asset) public view override returns (bool) {\n return _asset == usdcToken;\n }\n\n /// @inheritdoc InitializableAbstractStrategy\n function safeApproveAllTokens()\n external\n override\n onlyGovernor\n nonReentrant\n {}\n\n /// @inheritdoc InitializableAbstractStrategy\n function _abstractSetPToken(address, address) internal override {}\n\n /// @inheritdoc InitializableAbstractStrategy\n function collectRewardTokens()\n external\n override\n onlyHarvester\n nonReentrant\n {}\n\n /// @inheritdoc AbstractCCTPIntegrator\n function _onMessageReceived(bytes memory payload) internal override {\n if (\n payload.getMessageType() ==\n CrossChainStrategyHelper.BALANCE_CHECK_MESSAGE\n ) {\n // Received when Remote strategy checks the balance\n _processBalanceCheckMessage(payload);\n return;\n }\n\n revert(\"Unknown message type\");\n }\n\n /// @inheritdoc AbstractCCTPIntegrator\n function _onTokenReceived(\n uint256 tokenAmount,\n // solhint-disable-next-line no-unused-vars\n uint256 feeExecuted,\n bytes memory payload\n ) internal override {\n uint64 _nonce = lastTransferNonce;\n\n // Should be expecting an acknowledgement\n require(!isNonceProcessed(_nonce), \"Nonce already processed\");\n\n // Now relay to the regular flow\n // NOTE: Calling _onMessageReceived would mean that we are bypassing a\n // few checks that the regular flow does (like sourceDomainID check\n // and sender check in `handleReceiveFinalizedMessage`). However,\n // CCTPMessageRelayer relays the message first (which will go through\n // all the checks) and not update balance and then finally calls this\n // `_onTokenReceived` which will update the balance.\n // So, if any of the checks fail during the first no-balance-update flow,\n // this won't happen either, since the tx would revert.\n _onMessageReceived(payload);\n\n // Send any tokens in the contract to the Vault\n uint256 usdcBalance = IERC20(usdcToken).balanceOf(address(this));\n // Should always have enough tokens\n require(usdcBalance >= tokenAmount, \"Insufficient balance\");\n // Transfer all tokens to the Vault to not leave any dust\n IERC20(usdcToken).safeTransfer(vaultAddress, usdcBalance);\n\n // Emit withdrawal amount\n emit Withdrawal(usdcToken, usdcToken, usdcBalance);\n }\n\n /**\n * @dev Bridge and deposit asset into the remote strategy\n * @param _asset Address of the asset to deposit\n * @param depositAmount Amount of the asset to deposit\n */\n function _deposit(address _asset, uint256 depositAmount) internal virtual {\n require(_asset == usdcToken, \"Unsupported asset\");\n require(pendingAmount == 0, \"Unexpected pending amount\");\n // Deposit at least 1 USDC\n require(\n depositAmount >= MIN_TRANSFER_AMOUNT,\n \"Deposit amount too small\"\n );\n require(\n depositAmount <= MAX_TRANSFER_AMOUNT,\n \"Deposit amount too high\"\n );\n\n // Get the next nonce\n // Note: reverts if a transfer is pending\n uint64 nonce = _getNextNonce();\n\n // Set pending amount\n pendingAmount = depositAmount;\n\n // Build deposit message payload\n bytes memory message = CrossChainStrategyHelper.encodeDepositMessage(\n nonce,\n depositAmount\n );\n\n // Send deposit message to the remote strategy\n _sendTokens(depositAmount, message);\n\n // Emit deposit event\n emit Deposit(_asset, _asset, depositAmount);\n }\n\n /**\n * @dev Send a withdraw request to the remote strategy\n * @param _asset Address of the asset to withdraw\n * @param _amount Amount of the asset to withdraw\n */\n function _withdraw(address _asset, uint256 _amount) internal virtual {\n require(_asset == usdcToken, \"Unsupported asset\");\n // Withdraw at least 1 USDC\n require(_amount >= MIN_TRANSFER_AMOUNT, \"Withdraw amount too small\");\n require(\n _amount <= remoteStrategyBalance,\n \"Withdraw amount exceeds remote strategy balance\"\n );\n require(\n _amount <= MAX_TRANSFER_AMOUNT,\n \"Withdraw amount exceeds max transfer amount\"\n );\n\n // Get the next nonce\n // Note: reverts if a transfer is pending\n uint64 nonce = _getNextNonce();\n\n // Build and send withdrawal message with payload\n bytes memory message = CrossChainStrategyHelper.encodeWithdrawMessage(\n nonce,\n _amount\n );\n _sendMessage(message);\n\n // Emit WithdrawRequested event here,\n // Withdraw will be emitted in _onTokenReceived\n emit WithdrawRequested(usdcToken, _amount);\n }\n\n /**\n * @dev Process balance check:\n * - Confirms a deposit to the remote strategy\n * - Skips balance update if there's a pending withdrawal\n * - Updates the remote strategy balance\n * @param message The message containing the nonce and balance\n */\n function _processBalanceCheckMessage(bytes memory message)\n internal\n virtual\n {\n // Decode the message\n // When transferConfirmation is true, it means that the message is a result of a deposit or a withdrawal\n // process.\n (\n uint64 nonce,\n uint256 balance,\n bool transferConfirmation,\n uint256 timestamp\n ) = message.decodeBalanceCheckMessage();\n // Get the last cached nonce\n uint64 _lastCachedNonce = lastTransferNonce;\n\n if (nonce != _lastCachedNonce) {\n // If nonce is not the last cached nonce, it is an outdated message\n // Ignore it\n return;\n }\n\n // A received message nonce not yet processed indicates there is a\n // deposit or withdrawal in progress.\n bool transferInProgress = isTransferPending();\n\n if (transferInProgress) {\n if (transferConfirmation) {\n // Apply the effects of the deposit / withdrawal completion\n _markNonceAsProcessed(nonce);\n pendingAmount = 0;\n } else {\n // A balanceCheck arrived that is not part of the deposit / withdrawal process\n // that has been generated on the Remote contract after the deposit / withdrawal which is\n // still pending. This can happen when the CCTP bridge delivers the messages out of order.\n // Ignore it, since the pending deposit / withdrawal must first be cofirmed.\n emit BalanceCheckIgnored(nonce, timestamp, false);\n return;\n }\n } else {\n if (block.timestamp > timestamp + MAX_BALANCE_CHECK_AGE) {\n // Balance check is too old, ignore it\n emit BalanceCheckIgnored(nonce, timestamp, true);\n return;\n }\n }\n\n // At this point update the strategy balance the balanceCheck message is either:\n // - a confirmation of a deposit / withdrawal\n // - a message that updates balances when no deposit / withdrawal is in progress\n remoteStrategyBalance = balance;\n emit RemoteStrategyBalanceUpdated(balance);\n }\n}\n" + }, + "contracts/strategies/crosschain/CrossChainRemoteStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title CrossChainRemoteStrategy\n * @author Origin Protocol Inc\n *\n * @dev Part of the cross-chain strategy that lives on the remote chain.\n * Handles deposits and withdrawals from the master strategy on peer chain\n * and locally deposits the funds to a 4626 compatible vault.\n */\n\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport { IERC20 } from \"../../utils/InitializableAbstractStrategy.sol\";\nimport { IERC4626 } from \"../../../lib/openzeppelin/interfaces/IERC4626.sol\";\nimport { Generalized4626Strategy } from \"../Generalized4626Strategy.sol\";\nimport { AbstractCCTPIntegrator } from \"./AbstractCCTPIntegrator.sol\";\nimport { CrossChainStrategyHelper } from \"./CrossChainStrategyHelper.sol\";\nimport { InitializableAbstractStrategy } from \"../../utils/InitializableAbstractStrategy.sol\";\nimport { Strategizable } from \"../../governance/Strategizable.sol\";\n\ncontract CrossChainRemoteStrategy is\n AbstractCCTPIntegrator,\n Generalized4626Strategy,\n Strategizable\n{\n using SafeERC20 for IERC20;\n using CrossChainStrategyHelper for bytes;\n\n event DepositUnderlyingFailed(string reason);\n event WithdrawalFailed(uint256 amountRequested, uint256 amountAvailable);\n event WithdrawUnderlyingFailed(string reason);\n\n modifier onlyOperatorOrStrategistOrGovernor() {\n require(\n msg.sender == operator ||\n msg.sender == strategistAddr ||\n isGovernor(),\n \"Caller is not the Operator, Strategist or the Governor\"\n );\n _;\n }\n\n modifier onlyGovernorOrStrategist()\n override(InitializableAbstractStrategy, Strategizable) {\n require(\n msg.sender == strategistAddr || isGovernor(),\n \"Caller is not the Strategist or Governor\"\n );\n _;\n }\n\n constructor(\n BaseStrategyConfig memory _baseConfig,\n CCTPIntegrationConfig memory _cctpConfig\n )\n AbstractCCTPIntegrator(_cctpConfig)\n Generalized4626Strategy(_baseConfig, _cctpConfig.usdcToken)\n {\n require(usdcToken == address(assetToken), \"Token mismatch\");\n require(\n _baseConfig.platformAddress != address(0),\n \"Invalid platform address\"\n );\n // Vault address must always be address(0) for the remote strategy\n require(\n _baseConfig.vaultAddress == address(0),\n \"Invalid vault address\"\n );\n }\n\n /**\n * @dev Initialize the strategy implementation\n * @param _strategist Address of the strategist\n * @param _operator Address of the operator\n * @param _minFinalityThreshold Minimum finality threshold\n * @param _feePremiumBps Fee premium in basis points\n */\n function initialize(\n address _strategist,\n address _operator,\n uint16 _minFinalityThreshold,\n uint16 _feePremiumBps\n ) external virtual onlyGovernor initializer {\n _initialize(_operator, _minFinalityThreshold, _feePremiumBps);\n _setStrategistAddr(_strategist);\n\n address[] memory rewardTokens = new address[](0);\n address[] memory assets = new address[](1);\n address[] memory pTokens = new address[](1);\n\n assets[0] = address(usdcToken);\n pTokens[0] = address(platformAddress);\n\n InitializableAbstractStrategy._initialize(\n rewardTokens,\n assets,\n pTokens\n );\n }\n\n /// @inheritdoc Generalized4626Strategy\n function deposit(address _asset, uint256 _amount)\n external\n virtual\n override\n onlyGovernorOrStrategist\n nonReentrant\n {\n _deposit(_asset, _amount);\n }\n\n /// @inheritdoc Generalized4626Strategy\n function depositAll()\n external\n virtual\n override\n onlyGovernorOrStrategist\n nonReentrant\n {\n _deposit(usdcToken, IERC20(usdcToken).balanceOf(address(this)));\n }\n\n /// @inheritdoc Generalized4626Strategy\n /// @dev Interface requires a recipient, but for compatibility it must be address(this).\n function withdraw(\n address _recipient,\n address _asset,\n uint256 _amount\n ) external virtual override onlyGovernorOrStrategist nonReentrant {\n _withdraw(_recipient, _asset, _amount);\n }\n\n /// @inheritdoc Generalized4626Strategy\n function withdrawAll()\n external\n virtual\n override\n onlyGovernorOrStrategist\n nonReentrant\n {\n IERC4626 platform = IERC4626(platformAddress);\n _withdraw(\n address(this),\n usdcToken,\n platform.previewRedeem(platform.balanceOf(address(this)))\n );\n }\n\n /// @inheritdoc AbstractCCTPIntegrator\n function _onMessageReceived(bytes memory payload) internal override {\n uint32 messageType = payload.getMessageType();\n if (messageType == CrossChainStrategyHelper.DEPOSIT_MESSAGE) {\n // Received when Master strategy sends tokens to the remote strategy\n // Do nothing because we receive acknowledgement with token transfer,\n // so _onTokenReceived will handle it\n } else if (messageType == CrossChainStrategyHelper.WITHDRAW_MESSAGE) {\n // Received when Master strategy requests a withdrawal\n _processWithdrawMessage(payload);\n } else {\n revert(\"Unknown message type\");\n }\n }\n\n /**\n * @dev Process deposit message from peer strategy\n * @param tokenAmount Amount of tokens received\n * @param feeExecuted Fee executed\n * @param payload Payload of the message\n */\n function _processDepositMessage(\n // solhint-disable-next-line no-unused-vars\n uint256 tokenAmount,\n // solhint-disable-next-line no-unused-vars\n uint256 feeExecuted,\n bytes memory payload\n ) internal virtual {\n (uint64 nonce, ) = payload.decodeDepositMessage();\n\n // Replay protection is part of the _markNonceAsProcessed function\n _markNonceAsProcessed(nonce);\n\n // Deposit everything we got, not just what was bridged\n uint256 balance = IERC20(usdcToken).balanceOf(address(this));\n\n // Underlying call to deposit funds can fail. It mustn't affect the overall\n // flow as confirmation message should still be sent.\n if (balance >= MIN_TRANSFER_AMOUNT) {\n _deposit(usdcToken, balance);\n }\n\n // Send balance check message to the peer strategy\n bytes memory message = CrossChainStrategyHelper\n .encodeBalanceCheckMessage(\n lastTransferNonce,\n checkBalance(usdcToken),\n true,\n block.timestamp\n );\n _sendMessage(message);\n }\n\n /**\n * @dev Deposit assets by converting them to shares\n * @param _asset Address of asset to deposit\n * @param _amount Amount of asset to deposit\n */\n function _deposit(address _asset, uint256 _amount) internal override {\n // By design, this function should not revert. Otherwise, it'd\n // not be able to process messages and might freeze the contracts\n // state. However these two require statements would never fail\n // in every function invoking this. The same kind of checks should\n // be enforced in all the calling functions for these two and any\n // other require statements added to this function.\n require(_amount > 0, \"Must deposit something\");\n require(_asset == address(usdcToken), \"Unexpected asset address\");\n\n // This call can fail, and the failure doesn't need to bubble up to the _processDepositMessage function\n // as the flow is not affected by the failure.\n\n try IERC4626(platformAddress).deposit(_amount, address(this)) {\n emit Deposit(_asset, address(shareToken), _amount);\n } catch Error(string memory reason) {\n emit DepositUnderlyingFailed(\n string(abi.encodePacked(\"Deposit failed: \", reason))\n );\n } catch (bytes memory lowLevelData) {\n emit DepositUnderlyingFailed(\n string(\n abi.encodePacked(\n \"Deposit failed: low-level call failed with data \",\n lowLevelData\n )\n )\n );\n }\n }\n\n /**\n * @dev Process withdrawal message from peer strategy\n * @param payload Payload of the message\n */\n function _processWithdrawMessage(bytes memory payload) internal virtual {\n (uint64 nonce, uint256 withdrawAmount) = payload\n .decodeWithdrawMessage();\n\n // Replay protection is part of the _markNonceAsProcessed function\n _markNonceAsProcessed(nonce);\n\n uint256 usdcBalance = IERC20(usdcToken).balanceOf(address(this));\n\n if (usdcBalance < withdrawAmount) {\n // Withdraw the missing funds from the remote strategy. This call can fail and\n // the failure doesn't bubble up to the _processWithdrawMessage function\n _withdraw(address(this), usdcToken, withdrawAmount - usdcBalance);\n\n // Update the possible increase in the balance on the contract.\n usdcBalance = IERC20(usdcToken).balanceOf(address(this));\n }\n\n // Check balance after withdrawal\n uint256 strategyBalance = checkBalance(usdcToken);\n\n // If there are some tokens to be sent AND the balance is sufficient\n // to satisfy the withdrawal request then send the funds to the peer strategy.\n // In case a direct withdraw(All) has previously been called\n // there is a possibility of USDC funds remaining on the contract.\n // A separate withdraw to extract or deposit to the Morpho vault needs to be\n // initiated from the peer Master strategy to utilise USDC funds.\n if (\n withdrawAmount >= MIN_TRANSFER_AMOUNT &&\n usdcBalance >= withdrawAmount\n ) {\n // The new balance on the contract needs to have USDC subtracted from it as\n // that will be withdrawn in the next step\n bytes memory message = CrossChainStrategyHelper\n .encodeBalanceCheckMessage(\n lastTransferNonce,\n strategyBalance - withdrawAmount,\n true,\n block.timestamp\n );\n _sendTokens(withdrawAmount, message);\n } else {\n // Contract either:\n // - only has small dust amount of USDC\n // - doesn't have sufficient funds to satisfy the withdrawal request\n // In both cases send the balance update message to the peer strategy.\n bytes memory message = CrossChainStrategyHelper\n .encodeBalanceCheckMessage(\n lastTransferNonce,\n strategyBalance,\n true,\n block.timestamp\n );\n _sendMessage(message);\n emit WithdrawalFailed(withdrawAmount, usdcBalance);\n }\n }\n\n /**\n * @dev Withdraw asset by burning shares\n * @param _recipient Address to receive withdrawn asset\n * @param _asset Address of asset to withdraw\n * @param _amount Amount of asset to withdraw\n */\n function _withdraw(\n address _recipient,\n address _asset,\n uint256 _amount\n ) internal override {\n require(_amount > 0, \"Must withdraw something\");\n require(_recipient == address(this), \"Invalid recipient\");\n require(_asset == address(usdcToken), \"Unexpected asset address\");\n\n // This call can fail, and the failure doesn't need to bubble up to the _processWithdrawMessage function\n // as the flow is not affected by the failure.\n try\n // slither-disable-next-line unused-return\n IERC4626(platformAddress).withdraw(\n _amount,\n address(this),\n address(this)\n )\n {\n emit Withdrawal(_asset, address(shareToken), _amount);\n } catch Error(string memory reason) {\n emit WithdrawUnderlyingFailed(\n string(abi.encodePacked(\"Withdrawal failed: \", reason))\n );\n } catch (bytes memory lowLevelData) {\n emit WithdrawUnderlyingFailed(\n string(\n abi.encodePacked(\n \"Withdrawal failed: low-level call failed with data \",\n lowLevelData\n )\n )\n );\n }\n }\n\n /**\n * @dev Process token received message from peer strategy\n * @param tokenAmount Amount of tokens received\n * @param feeExecuted Fee executed\n * @param payload Payload of the message\n */\n function _onTokenReceived(\n uint256 tokenAmount,\n uint256 feeExecuted,\n bytes memory payload\n ) internal override {\n uint32 messageType = payload.getMessageType();\n\n require(\n messageType == CrossChainStrategyHelper.DEPOSIT_MESSAGE,\n \"Invalid message type\"\n );\n\n _processDepositMessage(tokenAmount, feeExecuted, payload);\n }\n\n /**\n * @dev Send balance update message to the peer strategy\n */\n function sendBalanceUpdate()\n external\n virtual\n onlyOperatorOrStrategistOrGovernor\n {\n uint256 balance = checkBalance(usdcToken);\n bytes memory message = CrossChainStrategyHelper\n .encodeBalanceCheckMessage(\n lastTransferNonce,\n balance,\n false,\n block.timestamp\n );\n _sendMessage(message);\n }\n\n /**\n * @notice Get the total asset value held in the platform and contract\n * @param _asset Address of the asset\n * @return balance Total value of the asset in the platform and contract\n */\n function checkBalance(address _asset)\n public\n view\n override\n returns (uint256)\n {\n require(_asset == usdcToken, \"Unexpected asset address\");\n /**\n * Balance of USDC on the contract is counted towards the total balance, since a deposit\n * to the Morpho V2 might fail and the USDC might remain on this contract as a result of a\n * bridged transfer.\n */\n uint256 balanceOnContract = IERC20(usdcToken).balanceOf(address(this));\n\n IERC4626 platform = IERC4626(platformAddress);\n return\n platform.previewRedeem(platform.balanceOf(address(this))) +\n balanceOnContract;\n }\n}\n" + }, + "contracts/strategies/crosschain/CrossChainStrategyHelper.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title CrossChainStrategyHelper\n * @author Origin Protocol Inc\n * @dev This library is used to encode and decode the messages for the cross-chain strategy.\n * It is used to ensure that the messages are valid and to get the message version and type.\n */\n\nimport { BytesHelper } from \"../../utils/BytesHelper.sol\";\n\nlibrary CrossChainStrategyHelper {\n using BytesHelper for bytes;\n\n uint32 public constant DEPOSIT_MESSAGE = 1;\n uint32 public constant WITHDRAW_MESSAGE = 2;\n uint32 public constant BALANCE_CHECK_MESSAGE = 3;\n\n uint32 public constant CCTP_MESSAGE_VERSION = 1;\n uint32 public constant ORIGIN_MESSAGE_VERSION = 1010;\n\n // CCTP Message Header fields\n // Ref: https://developers.circle.com/cctp/technical-guide#message-header\n uint8 private constant VERSION_INDEX = 0;\n uint8 private constant SOURCE_DOMAIN_INDEX = 4;\n uint8 private constant SENDER_INDEX = 44;\n uint8 private constant RECIPIENT_INDEX = 76;\n uint8 private constant MESSAGE_BODY_INDEX = 148;\n\n /**\n * @dev Get the message version from the message.\n * It should always be 4 bytes long,\n * starting from the 0th index.\n * @param message The message to get the version from\n * @return The message version\n */\n function getMessageVersion(bytes memory message)\n internal\n pure\n returns (uint32)\n {\n // uint32 bytes 0 to 4 is Origin message version\n // uint32 bytes 4 to 8 is Message type\n return message.extractUint32(0);\n }\n\n /**\n * @dev Get the message type from the message.\n * It should always be 4 bytes long,\n * starting from the 4th index.\n * @param message The message to get the type from\n * @return The message type\n */\n function getMessageType(bytes memory message)\n internal\n pure\n returns (uint32)\n {\n // uint32 bytes 0 to 4 is Origin message version\n // uint32 bytes 4 to 8 is Message type\n return message.extractUint32(4);\n }\n\n /**\n * @dev Verify the message version and type.\n * The message version should be the same as the Origin message version,\n * and the message type should be the same as the expected message type.\n * @param _message The message to verify\n * @param _type The expected message type\n */\n function verifyMessageVersionAndType(bytes memory _message, uint32 _type)\n internal\n pure\n {\n require(\n getMessageVersion(_message) == ORIGIN_MESSAGE_VERSION,\n \"Invalid Origin Message Version\"\n );\n require(getMessageType(_message) == _type, \"Invalid Message type\");\n }\n\n /**\n * @dev Get the message payload from the message.\n * The payload starts at the 8th byte.\n * @param message The message to get the payload from\n * @return The message payload\n */\n function getMessagePayload(bytes memory message)\n internal\n pure\n returns (bytes memory)\n {\n // uint32 bytes 0 to 4 is Origin message version\n // uint32 bytes 4 to 8 is Message type\n // Payload starts at byte 8\n return message.extractSlice(8, message.length);\n }\n\n /**\n * @dev Encode the deposit message.\n * The message version and type are always encoded in the message.\n * @param nonce The nonce of the deposit\n * @param depositAmount The amount of the deposit\n * @return The encoded deposit message\n */\n function encodeDepositMessage(uint64 nonce, uint256 depositAmount)\n internal\n pure\n returns (bytes memory)\n {\n return\n abi.encodePacked(\n ORIGIN_MESSAGE_VERSION,\n DEPOSIT_MESSAGE,\n abi.encode(nonce, depositAmount)\n );\n }\n\n /**\n * @dev Decode the deposit message.\n * The message version and type are verified in the message.\n * @param message The message to decode\n * @return The nonce and the amount of the deposit\n */\n function decodeDepositMessage(bytes memory message)\n internal\n pure\n returns (uint64, uint256)\n {\n verifyMessageVersionAndType(message, DEPOSIT_MESSAGE);\n\n (uint64 nonce, uint256 depositAmount) = abi.decode(\n getMessagePayload(message),\n (uint64, uint256)\n );\n return (nonce, depositAmount);\n }\n\n /**\n * @dev Encode the withdrawal message.\n * The message version and type are always encoded in the message.\n * @param nonce The nonce of the withdrawal\n * @param withdrawAmount The amount of the withdrawal\n * @return The encoded withdrawal message\n */\n function encodeWithdrawMessage(uint64 nonce, uint256 withdrawAmount)\n internal\n pure\n returns (bytes memory)\n {\n return\n abi.encodePacked(\n ORIGIN_MESSAGE_VERSION,\n WITHDRAW_MESSAGE,\n abi.encode(nonce, withdrawAmount)\n );\n }\n\n /**\n * @dev Decode the withdrawal message.\n * The message version and type are verified in the message.\n * @param message The message to decode\n * @return The nonce and the amount of the withdrawal\n */\n function decodeWithdrawMessage(bytes memory message)\n internal\n pure\n returns (uint64, uint256)\n {\n verifyMessageVersionAndType(message, WITHDRAW_MESSAGE);\n\n (uint64 nonce, uint256 withdrawAmount) = abi.decode(\n getMessagePayload(message),\n (uint64, uint256)\n );\n return (nonce, withdrawAmount);\n }\n\n /**\n * @dev Encode the balance check message.\n * The message version and type are always encoded in the message.\n * @param nonce The nonce of the balance check\n * @param balance The balance to check\n * @param transferConfirmation Indicates if the message is a transfer confirmation. This is true\n * when the message is a result of a deposit or a withdrawal.\n * @return The encoded balance check message\n */\n function encodeBalanceCheckMessage(\n uint64 nonce,\n uint256 balance,\n bool transferConfirmation,\n uint256 timestamp\n ) internal pure returns (bytes memory) {\n return\n abi.encodePacked(\n ORIGIN_MESSAGE_VERSION,\n BALANCE_CHECK_MESSAGE,\n abi.encode(nonce, balance, transferConfirmation, timestamp)\n );\n }\n\n /**\n * @dev Decode the balance check message.\n * The message version and type are verified in the message.\n * @param message The message to decode\n * @return The nonce, the balance and indicates if the message is a transfer confirmation\n */\n function decodeBalanceCheckMessage(bytes memory message)\n internal\n pure\n returns (\n uint64,\n uint256,\n bool,\n uint256\n )\n {\n verifyMessageVersionAndType(message, BALANCE_CHECK_MESSAGE);\n\n (\n uint64 nonce,\n uint256 balance,\n bool transferConfirmation,\n uint256 timestamp\n ) = abi.decode(\n getMessagePayload(message),\n (uint64, uint256, bool, uint256)\n );\n return (nonce, balance, transferConfirmation, timestamp);\n }\n\n /**\n * @dev Decode the CCTP message header\n * @param message Message to decode\n * @return version Version of the message\n * @return sourceDomainID Source domain ID\n * @return sender Sender of the message\n * @return recipient Recipient of the message\n * @return messageBody Message body\n */\n function decodeMessageHeader(bytes memory message)\n internal\n pure\n returns (\n uint32 version,\n uint32 sourceDomainID,\n address sender,\n address recipient,\n bytes memory messageBody\n )\n {\n version = message.extractUint32(VERSION_INDEX);\n sourceDomainID = message.extractUint32(SOURCE_DOMAIN_INDEX);\n // Address of MessageTransmitterV2 caller on source domain\n sender = message.extractAddress(SENDER_INDEX);\n // Address to handle message body on destination domain\n recipient = message.extractAddress(RECIPIENT_INDEX);\n messageBody = message.extractSlice(MESSAGE_BODY_INDEX, message.length);\n }\n}\n" + }, + "contracts/strategies/CurveAMOStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title Curve Automated Market Maker (AMO) Strategy\n * @notice AMO strategy for a Curve pool using an OToken.\n * @author Origin Protocol Inc\n */\nimport { Math } from \"@openzeppelin/contracts/utils/math/Math.sol\";\nimport { SafeCast } from \"@openzeppelin/contracts/utils/math/SafeCast.sol\";\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\n\nimport { IERC20, InitializableAbstractStrategy } from \"../utils/InitializableAbstractStrategy.sol\";\nimport { StableMath } from \"../utils/StableMath.sol\";\nimport { IVault } from \"../interfaces/IVault.sol\";\nimport { ICurveStableSwapNG } from \"../interfaces/ICurveStableSwapNG.sol\";\nimport { ICurveLiquidityGaugeV6 } from \"../interfaces/ICurveLiquidityGaugeV6.sol\";\nimport { IBasicToken } from \"../interfaces/IBasicToken.sol\";\nimport { ICurveMinter } from \"../interfaces/ICurveMinter.sol\";\n\ncontract CurveAMOStrategy is InitializableAbstractStrategy {\n using SafeCast for uint256;\n using SafeERC20 for IERC20;\n using StableMath for uint256;\n\n /**\n * @dev a threshold under which the contract no longer allows for the protocol to manually rebalance.\n * Guarding against a strategist / guardian being taken over and with multiple transactions\n * draining the protocol funds.\n */\n uint256 public constant SOLVENCY_THRESHOLD = 0.998 ether;\n\n // New immutable variables that must be set in the constructor\n /**\n * @notice Address of the hard asset (weth, usdt, usdc).\n */\n IERC20 public immutable hardAsset;\n\n /**\n * @notice Address of the OTOKEN token contract.\n */\n IERC20 public immutable oToken;\n\n /**\n * @notice Address of the LP (Liquidity Provider) token contract.\n */\n IERC20 public immutable lpToken;\n\n /**\n * @notice Address of the Curve StableSwap NG pool contract.\n */\n ICurveStableSwapNG public immutable curvePool;\n\n /**\n * @notice Address of the Curve X-Chain Liquidity Gauge contract.\n */\n ICurveLiquidityGaugeV6 public immutable gauge;\n\n /**\n * @notice Address of the Curve Minter contract.\n */\n ICurveMinter public immutable minter;\n\n /**\n * @notice Index of the OTOKEN and hardAsset in the Curve pool.\n */\n uint128 public immutable otokenCoinIndex;\n uint128 public immutable hardAssetCoinIndex;\n\n /**\n * @notice Decimals of the OTOKEN and hardAsset.\n */\n uint8 public immutable decimalsOToken;\n uint8 public immutable decimalsHardAsset;\n\n /**\n * @notice Maximum slippage allowed for adding/removing liquidity from the Curve pool.\n */\n uint256 public maxSlippage;\n\n event MaxSlippageUpdated(uint256 newMaxSlippage);\n\n /**\n * @dev Verifies that the caller is the Strategist.\n */\n modifier onlyStrategist() {\n require(\n msg.sender == IVault(vaultAddress).strategistAddr(),\n \"Caller is not the Strategist\"\n );\n _;\n }\n\n /**\n * @dev Checks the Curve pool's balances have improved and the balances\n * have not tipped to the other side.\n * This modifier is only applied to functions that do a single sided add or remove.\n * The standard deposit function adds to both sides of the pool in a way that\n * the pool's balance is not worsened.\n * Withdrawals are proportional so doesn't change the pools asset balance.\n */\n modifier improvePoolBalance() {\n // Get the hard asset and OToken balances in the Curve pool\n uint256[] memory balancesBefore = curvePool.get_balances();\n // diff = hardAsset balance - OTOKEN balance\n int256 diffBefore = (\n balancesBefore[hardAssetCoinIndex].scaleBy(\n decimalsOToken,\n decimalsHardAsset\n )\n ).toInt256() - balancesBefore[otokenCoinIndex].toInt256();\n\n _;\n\n // Get the hard asset and OToken balances in the Curve pool\n uint256[] memory balancesAfter = curvePool.get_balances();\n // diff = hardAsset balance - OTOKEN balance\n int256 diffAfter = (\n balancesAfter[hardAssetCoinIndex].scaleBy(\n decimalsOToken,\n decimalsHardAsset\n )\n ).toInt256() - balancesAfter[otokenCoinIndex].toInt256();\n\n if (diffBefore == 0) {\n require(diffAfter == 0, \"Position balance is worsened\");\n } else if (diffBefore < 0) {\n // If the pool was originally imbalanced in favor of OTOKEN, then\n // we want to check that the pool is now more balanced\n require(diffAfter <= 0, \"OTokens overshot peg\");\n require(diffBefore < diffAfter, \"OTokens balance worse\");\n } else if (diffBefore > 0) {\n // If the pool was originally imbalanced in favor of hardAsset, then\n // we want to check that the pool is now more balanced\n require(diffAfter >= 0, \"Assets overshot peg\");\n require(diffAfter < diffBefore, \"Assets balance worse\");\n }\n }\n\n constructor(\n BaseStrategyConfig memory _baseConfig,\n address _otoken,\n address _hardAsset,\n address _gauge,\n address _minter\n ) InitializableAbstractStrategy(_baseConfig) {\n lpToken = IERC20(_baseConfig.platformAddress);\n curvePool = ICurveStableSwapNG(_baseConfig.platformAddress);\n minter = ICurveMinter(_minter);\n\n oToken = IERC20(_otoken);\n hardAsset = IERC20(_hardAsset);\n gauge = ICurveLiquidityGaugeV6(_gauge);\n decimalsHardAsset = IBasicToken(_hardAsset).decimals();\n decimalsOToken = IBasicToken(_otoken).decimals();\n\n (hardAssetCoinIndex, otokenCoinIndex) = curvePool.coins(0) == _hardAsset\n ? (0, 1)\n : (1, 0);\n require(\n curvePool.coins(otokenCoinIndex) == _otoken &&\n curvePool.coins(hardAssetCoinIndex) == _hardAsset,\n \"Invalid coin indexes\"\n );\n require(gauge.lp_token() == address(curvePool), \"Invalid pool\");\n }\n\n /**\n * Initializer for setting up strategy internal state. This overrides the\n * InitializableAbstractStrategy initializer as Curve strategies don't fit\n * well within that abstraction.\n * @param _rewardTokenAddresses Address of CRV\n * @param _maxSlippage Maximum slippage allowed for adding/removing liquidity from the Curve pool.\n */\n function initialize(\n address[] calldata _rewardTokenAddresses, // CRV\n uint256 _maxSlippage\n ) external onlyGovernor initializer {\n address[] memory pTokens = new address[](1);\n pTokens[0] = address(curvePool);\n\n address[] memory _assets = new address[](1);\n _assets[0] = address(hardAsset);\n\n InitializableAbstractStrategy._initialize(\n _rewardTokenAddresses,\n _assets,\n pTokens\n );\n\n _approveBase();\n _setMaxSlippage(_maxSlippage);\n }\n\n /***************************************\n Deposit\n ****************************************/\n\n /**\n * @notice Deposit hard asset into the Curve pool\n * @param _hardAsset Address of hard asset contract.\n * @param _amount Amount of hard asset to deposit.\n */\n function deposit(address _hardAsset, uint256 _amount)\n external\n override\n onlyVault\n nonReentrant\n {\n _deposit(_hardAsset, _amount);\n }\n\n function _deposit(address _hardAsset, uint256 _hardAssetAmount) internal {\n require(_hardAssetAmount > 0, \"Must deposit something\");\n require(_hardAsset == address(hardAsset), \"Unsupported asset\");\n\n emit Deposit(_hardAsset, address(lpToken), _hardAssetAmount);\n uint256 scaledHardAssetAmount = _hardAssetAmount.scaleBy(\n decimalsOToken,\n decimalsHardAsset\n );\n\n // Get the asset and OToken balances in the Curve pool\n uint256[] memory balances = curvePool.get_balances();\n // safe to cast since min value is at least 0\n uint256 otokenToAdd = uint256(\n _max(\n 0,\n (\n balances[hardAssetCoinIndex].scaleBy(\n decimalsOToken,\n decimalsHardAsset\n )\n ).toInt256() +\n scaledHardAssetAmount.toInt256() -\n balances[otokenCoinIndex].toInt256()\n )\n );\n\n /* Add so much OTOKEN so that the pool ends up being balanced. And at minimum\n * add as much OTOKEN as hard asset and at maximum twice as much OTOKEN.\n */\n otokenToAdd = Math.max(otokenToAdd, scaledHardAssetAmount);\n otokenToAdd = Math.min(otokenToAdd, scaledHardAssetAmount * 2);\n\n /* Mint OTOKEN with a strategy that attempts to contribute to stability of OTOKEN/hardAsset pool. Try\n * to mint so much OTOKEN that after deployment of liquidity pool ends up being balanced.\n *\n * To manage unpredictability minimal OTOKEN minted will always be at least equal or greater\n * to hardAsset amount deployed. And never larger than twice the hardAsset amount deployed even if\n * it would have a further beneficial effect on pool stability.\n */\n IVault(vaultAddress).mintForStrategy(otokenToAdd);\n\n emit Deposit(address(oToken), address(lpToken), otokenToAdd);\n\n uint256[] memory _amounts = new uint256[](2);\n _amounts[hardAssetCoinIndex] = _hardAssetAmount;\n _amounts[otokenCoinIndex] = otokenToAdd;\n\n uint256 valueInLpTokens = (scaledHardAssetAmount + otokenToAdd)\n .divPrecisely(curvePool.get_virtual_price());\n uint256 minMintAmount = valueInLpTokens.mulTruncate(\n uint256(1e18) - maxSlippage\n );\n\n // Do the deposit to the Curve pool\n uint256 lpDeposited = curvePool.add_liquidity(_amounts, minMintAmount);\n require(lpDeposited >= minMintAmount, \"Min LP amount error\");\n\n // Deposit the Curve pool's LP tokens into the Curve gauge\n gauge.deposit(lpDeposited);\n\n // Ensure solvency of the vault\n _solvencyAssert();\n }\n\n /**\n * @notice Deposit the strategy's entire balance of hardAsset into the Curve pool\n */\n function depositAll() external override onlyVault nonReentrant {\n uint256 balance = hardAsset.balanceOf(address(this));\n if (balance > 0) {\n _deposit(address(hardAsset), balance);\n }\n }\n\n /***************************************\n Withdraw\n ****************************************/\n\n /**\n * @notice Withdraw hardAsset and OTOKEN from the Curve pool, burn the OTOKEN,\n * transfer hardAsset to the recipient.\n * @param _recipient Address to receive withdrawn asset which is normally the Vault.\n * @param _hardAsset Address of the hardAsset contract.\n * @param _amount Amount of hardAsset to withdraw.\n */\n function withdraw(\n address _recipient,\n address _hardAsset,\n uint256 _amount\n ) external override onlyVault nonReentrant {\n require(_amount > 0, \"Must withdraw something\");\n require(\n _hardAsset == address(hardAsset),\n \"Can only withdraw hard asset\"\n );\n\n emit Withdrawal(_hardAsset, address(lpToken), _amount);\n\n uint256 requiredLpTokens = calcTokenToBurn(\n _amount.scaleBy(decimalsOToken, decimalsHardAsset)\n );\n\n _lpWithdraw(requiredLpTokens);\n\n /* math in requiredLpTokens should correctly calculate the amount of LP to remove\n * in that the strategy receives enough hardAsset on balanced removal\n */\n uint256[] memory _minWithdrawalAmounts = new uint256[](2);\n _minWithdrawalAmounts[hardAssetCoinIndex] = _amount;\n // slither-disable-next-line unused-return\n curvePool.remove_liquidity(requiredLpTokens, _minWithdrawalAmounts);\n\n // Burn all the removed OTOKEN and any that was left in the strategy\n uint256 otokenToBurn = oToken.balanceOf(address(this));\n IVault(vaultAddress).burnForStrategy(otokenToBurn);\n\n emit Withdrawal(address(oToken), address(lpToken), otokenToBurn);\n\n // Transfer hardAsset to the recipient\n hardAsset.safeTransfer(_recipient, _amount);\n\n // Ensure solvency of the vault\n _solvencyAssert();\n }\n\n function calcTokenToBurn(uint256 _hardAssetAmount)\n internal\n view\n returns (uint256 lpToBurn)\n {\n /* The rate between coins in the pool determines the rate at which pool returns\n * tokens when doing balanced removal (remove_liquidity call). And by knowing how much hardAsset\n * we want we can determine how much of OTOKEN we receive by removing liquidity.\n *\n * Because we are doing balanced removal we should be making profit when removing liquidity in a\n * pool tilted to either side.\n *\n * Important: A downside is that the Strategist / Governor needs to be\n * cognisant of not removing too much liquidity. And while the proposal to remove liquidity\n * is being voted on the pool tilt might change so much that the proposal that has been valid while\n * created is no longer valid.\n */\n\n uint256 poolHardAssetBalance = curvePool\n .balances(hardAssetCoinIndex)\n .scaleBy(decimalsOToken, decimalsHardAsset);\n /* K is multiplied by 1e36 which is used for higher precision calculation of required\n * pool LP tokens. Without it the end value can have rounding errors up to precision of\n * 10 digits. This way we move the decimal point by 36 places when doing the calculation\n * and again by 36 places when we are done with it.\n */\n uint256 k = (1e36 * lpToken.totalSupply()) / poolHardAssetBalance;\n // prettier-ignore\n // slither-disable-next-line divide-before-multiply\n uint256 diff = (_hardAssetAmount + 1) * k;\n lpToBurn = diff / 1e36;\n }\n\n /**\n * @notice Remove all hardAsset and OTOKEN from the Curve pool, burn the OTOKEN,\n * transfer hardAsset to the Vault contract.\n */\n function withdrawAll() external override onlyVaultOrGovernor nonReentrant {\n uint256 gaugeTokens = gauge.balanceOf(address(this));\n _lpWithdraw(gaugeTokens);\n\n // Withdraws are proportional to assets held by 3Pool\n uint256[] memory minWithdrawAmounts = new uint256[](2);\n\n // Check balance of LP tokens in the strategy, if 0 return\n uint256 lpBalance = lpToken.balanceOf(address(this));\n\n // Remove liquidity\n // slither-disable-next-line unused-return\n if (lpBalance > 0) {\n curvePool.remove_liquidity(lpBalance, minWithdrawAmounts);\n }\n\n // Burn all OTOKEN\n uint256 otokenToBurn = oToken.balanceOf(address(this));\n IVault(vaultAddress).burnForStrategy(otokenToBurn);\n\n // Get the strategy contract's hardAsset balance.\n // This includes all that was removed from the Curve pool and\n // any hardAsset that was sitting in the strategy contract before the removal.\n uint256 hardAssetBalance = hardAsset.balanceOf(address(this));\n hardAsset.safeTransfer(vaultAddress, hardAssetBalance);\n\n if (hardAssetBalance > 0)\n emit Withdrawal(\n address(hardAsset),\n address(lpToken),\n hardAssetBalance\n );\n if (otokenToBurn > 0)\n emit Withdrawal(address(oToken), address(lpToken), otokenToBurn);\n }\n\n /***************************************\n Curve pool Rebalancing\n ****************************************/\n\n /**\n * @notice Mint OTokens and one-sided add to the Curve pool.\n * This is used when the Curve pool does not have enough OTokens and too many hardAsset.\n * The OToken/Asset, eg OTOKEN/hardAsset, price with increase.\n * The amount of assets in the vault is unchanged.\n * The total supply of OTokens is increased.\n * The asset value of the strategy and vault is increased.\n * @param _oTokens The amount of OTokens to be minted and added to the pool.\n */\n function mintAndAddOTokens(uint256 _oTokens)\n external\n onlyStrategist\n nonReentrant\n improvePoolBalance\n {\n IVault(vaultAddress).mintForStrategy(_oTokens);\n\n uint256[] memory amounts = new uint256[](2);\n amounts[otokenCoinIndex] = _oTokens;\n\n // Convert OTOKEN to Curve pool LP tokens\n uint256 valueInLpTokens = (_oTokens).divPrecisely(\n curvePool.get_virtual_price()\n );\n // Apply slippage to LP tokens\n uint256 minMintAmount = valueInLpTokens.mulTruncate(\n uint256(1e18) - maxSlippage\n );\n\n // Add the minted OTokens to the Curve pool\n uint256 lpDeposited = curvePool.add_liquidity(amounts, minMintAmount);\n require(lpDeposited >= minMintAmount, \"Min LP amount error\");\n\n // Deposit the Curve pool LP tokens to the Curve gauge\n gauge.deposit(lpDeposited);\n\n // Ensure solvency of the vault\n _solvencyAssert();\n\n emit Deposit(address(oToken), address(lpToken), _oTokens);\n }\n\n /**\n * @notice One-sided remove of OTokens from the Curve pool which are then burned.\n * This is used when the Curve pool has too many OTokens and not enough hardAsset.\n * The amount of assets in the vault is unchanged.\n * The total supply of OTokens is reduced.\n * The asset value of the strategy and vault is reduced.\n * @param _lpTokens The amount of Curve pool LP tokens to be burned for OTokens.\n */\n function removeAndBurnOTokens(uint256 _lpTokens)\n external\n onlyStrategist\n nonReentrant\n improvePoolBalance\n {\n // Withdraw Curve pool LP tokens from gauge and remove OTokens from the Curve pool\n uint256 otokenToBurn = _withdrawAndRemoveFromPool(\n _lpTokens,\n otokenCoinIndex\n );\n\n // The vault burns the OTokens from this strategy\n IVault(vaultAddress).burnForStrategy(otokenToBurn);\n\n // Ensure solvency of the vault\n _solvencyAssert();\n\n emit Withdrawal(address(oToken), address(lpToken), otokenToBurn);\n }\n\n /**\n * @notice One-sided remove of hardAsset from the Curve pool and transfer to the vault.\n * This is used when the Curve pool does not have enough OTokens and too many hardAsset.\n * The OToken/Asset, eg OTOKEN/hardAsset, price with decrease.\n * The amount of assets in the vault increases.\n * The total supply of OTokens does not change.\n * The asset value of the strategy reduces.\n * The asset value of the vault should be close to the same.\n * @param _lpTokens The amount of Curve pool LP tokens to be burned for hardAsset.\n * @dev Curve pool LP tokens is used rather than hardAsset assets as Curve does not\n * have a way to accurately calculate the amount of LP tokens for a required\n * amount of hardAsset. Curve's `calc_token_amount` function does not include fees.\n * A 3rd party library can be used that takes into account the fees, but this\n * is a gas intensive process. It's easier for the trusted strategist to\n * calculate the amount of Curve pool LP tokens required off-chain.\n */\n function removeOnlyAssets(uint256 _lpTokens)\n external\n onlyStrategist\n nonReentrant\n improvePoolBalance\n {\n // Withdraw Curve pool LP tokens from Curve gauge and remove hardAsset from the Curve pool\n uint256 hardAssetAmount = _withdrawAndRemoveFromPool(\n _lpTokens,\n hardAssetCoinIndex\n );\n\n // Transfer hardAsset to the vault\n hardAsset.safeTransfer(vaultAddress, hardAssetAmount);\n\n // Ensure solvency of the vault\n _solvencyAssert();\n\n emit Withdrawal(address(hardAsset), address(lpToken), hardAssetAmount);\n }\n\n /**\n * @dev Remove Curve pool LP tokens from the gauge and\n * do a one-sided remove of hardAsset or OTOKEN from the Curve pool.\n * @param _lpTokens The amount of Curve pool LP tokens to be removed from the gauge\n * @param coinIndex The index of the coin to be removed from the Curve pool. 0 = hardAsset, 1 = OTOKEN.\n * @return coinsRemoved The amount of hardAsset or OTOKEN removed from the Curve pool.\n */\n function _withdrawAndRemoveFromPool(uint256 _lpTokens, uint128 coinIndex)\n internal\n returns (uint256 coinsRemoved)\n {\n // Withdraw Curve pool LP tokens from Curve gauge\n _lpWithdraw(_lpTokens);\n\n // Convert Curve pool LP tokens to hardAsset value\n uint256 valueInEth = _lpTokens.mulTruncate(\n curvePool.get_virtual_price()\n );\n\n if (coinIndex == hardAssetCoinIndex) {\n valueInEth = valueInEth.scaleBy(decimalsHardAsset, decimalsOToken);\n }\n\n // Apply slippage to hardAsset value\n uint256 minAmount = valueInEth.mulTruncate(uint256(1e18) - maxSlippage);\n\n // Remove just the hardAsset from the Curve pool\n coinsRemoved = curvePool.remove_liquidity_one_coin(\n _lpTokens,\n int128(coinIndex),\n minAmount,\n address(this)\n );\n }\n\n /**\n * Checks that the protocol is solvent, protecting from a rogue Strategist / Guardian that can\n * keep rebalancing the pool in both directions making the protocol lose a tiny amount of\n * funds each time.\n *\n * Protocol must be at least SOLVENCY_THRESHOLD (99,8 %) backed in order for the rebalances to\n * function.\n */\n function _solvencyAssert() internal view {\n uint256 _totalVaultValue = IVault(vaultAddress).totalValue();\n uint256 _totalOtokenSupply = oToken.totalSupply();\n\n if (\n _totalVaultValue.divPrecisely(_totalOtokenSupply) <\n SOLVENCY_THRESHOLD\n ) {\n revert(\"Protocol insolvent\");\n }\n }\n\n /***************************************\n Assets and Rewards\n ****************************************/\n\n /**\n * @notice Collect accumulated CRV (and other) rewards and send to the Harvester.\n */\n function collectRewardTokens()\n external\n override\n onlyHarvester\n nonReentrant\n {\n // Collect CRV rewards from inflation\n minter.mint(address(gauge));\n\n // Collect extra gauge rewards (outside of CRV)\n gauge.claim_rewards();\n\n _collectRewardTokens();\n }\n\n function _lpWithdraw(uint256 _lpAmount) internal {\n require(\n gauge.balanceOf(address(this)) >= _lpAmount,\n \"Insufficient LP tokens\"\n );\n // withdraw lp tokens from the gauge without claiming rewards\n gauge.withdraw(_lpAmount);\n }\n\n /**\n * @notice Get the total asset value held in the platform\n * @param _asset Address of the asset\n * @return balance Total value of the asset in the platform\n */\n function checkBalance(address _asset)\n external\n view\n override\n returns (uint256 balance)\n {\n require(_asset == address(hardAsset), \"Unsupported asset\");\n\n // hardAsset balance needed here for the balance check that happens from vault during depositing.\n balance = hardAsset.balanceOf(address(this));\n uint256 lpTokens = gauge.balanceOf(address(this));\n if (lpTokens > 0) {\n balance += ((lpTokens * curvePool.get_virtual_price()) / 1e18)\n .scaleBy(decimalsHardAsset, decimalsOToken);\n }\n }\n\n /**\n * @notice Returns bool indicating whether asset is supported by strategy\n * @param _asset Address of the asset\n */\n function supportsAsset(address _asset) public view override returns (bool) {\n return _asset == address(hardAsset);\n }\n\n /***************************************\n Approvals\n ****************************************/\n\n /**\n * @notice Sets the maximum slippage allowed for any swap/liquidity operation\n * @param _maxSlippage Maximum slippage allowed, 1e18 = 100%.\n */\n function setMaxSlippage(uint256 _maxSlippage) external onlyGovernor {\n _setMaxSlippage(_maxSlippage);\n }\n\n function _setMaxSlippage(uint256 _maxSlippage) internal {\n require(_maxSlippage <= 5e16, \"Slippage must be less than 100%\");\n maxSlippage = _maxSlippage;\n emit MaxSlippageUpdated(_maxSlippage);\n }\n\n /**\n * @notice Approve the spending of all assets by their corresponding pool tokens,\n * if for some reason is it necessary.\n */\n function safeApproveAllTokens()\n external\n override\n onlyGovernor\n nonReentrant\n {\n _approveBase();\n }\n\n /**\n * @dev Since we are unwrapping WETH before depositing it to Curve\n * there is no need to set an approval for WETH on the Curve\n * pool\n * @param _asset Address of the asset\n * @param _pToken Address of the Curve LP token\n */\n // solhint-disable-next-line no-unused-vars\n function _abstractSetPToken(address _asset, address _pToken)\n internal\n override\n {}\n\n function _approveBase() internal {\n // Approve Curve pool for OTOKEN (required for adding liquidity)\n // slither-disable-next-line unused-return\n oToken.approve(platformAddress, type(uint256).max);\n\n // Approve Curve pool for hardAsset (required for adding liquidity)\n // slither-disable-next-line unused-return\n hardAsset.safeApprove(platformAddress, type(uint256).max);\n\n // Approve Curve gauge contract to transfer Curve pool LP tokens\n // This is needed for deposits if Curve pool LP tokens into the Curve gauge.\n // slither-disable-next-line unused-return\n lpToken.approve(address(gauge), type(uint256).max);\n }\n\n /**\n * @dev Returns the largest of two numbers int256 version\n */\n function _max(int256 a, int256 b) internal pure returns (int256) {\n return a >= b ? a : b;\n }\n}\n" + }, + "contracts/strategies/Generalized4626Strategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title Generalized 4626 Strategy\n * @notice Investment strategy for ERC-4626 Tokenized Vaults\n * @author Origin Protocol Inc\n */\nimport { IERC4626 } from \"../../lib/openzeppelin/interfaces/IERC4626.sol\";\nimport { IERC20, InitializableAbstractStrategy } from \"../utils/InitializableAbstractStrategy.sol\";\nimport { IDistributor } from \"../interfaces/IMerkl.sol\";\n\ncontract Generalized4626Strategy is InitializableAbstractStrategy {\n /// @notice The address of the Merkle Distributor contract.\n IDistributor public constant merkleDistributor =\n IDistributor(0x3Ef3D8bA38EBe18DB133cEc108f4D14CE00Dd9Ae);\n\n /// @dev Replaced with an immutable variable\n // slither-disable-next-line constable-states\n address private _deprecate_shareToken;\n /// @dev Replaced with an immutable variable\n // slither-disable-next-line constable-states\n address private _deprecate_assetToken;\n\n IERC20 public immutable shareToken;\n IERC20 public immutable assetToken;\n\n // For future use\n uint256[50] private __gap;\n\n event ClaimedRewards(address indexed token, uint256 amount);\n\n /**\n * @param _baseConfig Base strategy config with platformAddress (ERC-4626 Vault contract), eg sfrxETH or sDAI,\n * and vaultAddress (OToken Vault contract), eg VaultProxy or OETHVaultProxy\n * @param _assetToken Address of the ERC-4626 asset token. eg frxETH or DAI\n */\n constructor(BaseStrategyConfig memory _baseConfig, address _assetToken)\n InitializableAbstractStrategy(_baseConfig)\n {\n shareToken = IERC20(_baseConfig.platformAddress);\n assetToken = IERC20(_assetToken);\n }\n\n function initialize() external virtual onlyGovernor initializer {\n address[] memory rewardTokens = new address[](0);\n address[] memory assets = new address[](1);\n address[] memory pTokens = new address[](1);\n\n assets[0] = address(assetToken);\n pTokens[0] = address(platformAddress);\n\n InitializableAbstractStrategy._initialize(\n rewardTokens,\n assets,\n pTokens\n );\n }\n\n /**\n * @dev Deposit assets by converting them to shares\n * @param _asset Address of asset to deposit\n * @param _amount Amount of asset to deposit\n */\n function deposit(address _asset, uint256 _amount)\n external\n virtual\n override\n onlyVault\n nonReentrant\n {\n _deposit(_asset, _amount);\n }\n\n /**\n * @dev Deposit assets by converting them to shares\n * @param _asset Address of asset to deposit\n * @param _amount Amount of asset to deposit\n */\n function _deposit(address _asset, uint256 _amount) internal virtual {\n require(_amount > 0, \"Must deposit something\");\n require(_asset == address(assetToken), \"Unexpected asset address\");\n\n // slither-disable-next-line unused-return\n IERC4626(platformAddress).deposit(_amount, address(this));\n emit Deposit(_asset, address(shareToken), _amount);\n }\n\n /**\n * @dev Deposit the entire balance of assetToken to gain shareToken\n */\n function depositAll() external virtual override onlyVault nonReentrant {\n uint256 balance = assetToken.balanceOf(address(this));\n if (balance > 0) {\n _deposit(address(assetToken), balance);\n }\n }\n\n /**\n * @dev Withdraw asset by burning shares\n * @param _recipient Address to receive withdrawn asset\n * @param _asset Address of asset to withdraw\n * @param _amount Amount of asset to withdraw\n */\n function withdraw(\n address _recipient,\n address _asset,\n uint256 _amount\n ) external virtual override onlyVault nonReentrant {\n _withdraw(_recipient, _asset, _amount);\n }\n\n function _withdraw(\n address _recipient,\n address _asset,\n uint256 _amount\n ) internal virtual {\n require(_amount > 0, \"Must withdraw something\");\n require(_recipient != address(0), \"Must specify recipient\");\n require(_asset == address(assetToken), \"Unexpected asset address\");\n\n // slither-disable-next-line unused-return\n IERC4626(platformAddress).withdraw(_amount, _recipient, address(this));\n emit Withdrawal(_asset, address(shareToken), _amount);\n }\n\n /**\n * @dev Internal method to respond to the addition of new asset / share tokens\n */\n function _abstractSetPToken(address, address) internal virtual override {\n _approveBase();\n }\n\n /**\n * @dev Remove all assets from platform and send them to Vault contract.\n */\n function withdrawAll()\n external\n virtual\n override\n onlyVaultOrGovernor\n nonReentrant\n {\n uint256 shareBalance = shareToken.balanceOf(address(this));\n uint256 assetAmount = 0;\n if (shareBalance > 0) {\n assetAmount = IERC4626(platformAddress).redeem(\n shareBalance,\n vaultAddress,\n address(this)\n );\n emit Withdrawal(\n address(assetToken),\n address(shareToken),\n assetAmount\n );\n }\n }\n\n /**\n * @notice Get the total asset value held in the platform\n * @param _asset Address of the asset\n * @return balance Total value of the asset in the platform\n */\n function checkBalance(address _asset)\n public\n view\n virtual\n override\n returns (uint256 balance)\n {\n require(_asset == address(assetToken), \"Unexpected asset address\");\n /* We are intentionally not counting the amount of assetToken parked on the\n * contract toward the checkBalance. The deposit and withdraw functions\n * should not result in assetToken being unused and owned by this strategy\n * contract.\n */\n IERC4626 platform = IERC4626(platformAddress);\n return platform.previewRedeem(platform.balanceOf(address(this)));\n }\n\n /**\n * @notice Governor approves the ERC-4626 Tokenized Vault to spend the asset.\n */\n function safeApproveAllTokens() external override onlyGovernor {\n _approveBase();\n }\n\n function _approveBase() internal virtual {\n // Approval the asset to be transferred to the ERC-4626 Tokenized Vault.\n // Used by the ERC-4626 deposit() and mint() functions\n // slither-disable-next-line unused-return\n assetToken.approve(platformAddress, type(uint256).max);\n }\n\n /**\n * @dev Returns bool indicating whether asset is supported by strategy\n * @param _asset Address of the asset\n */\n function supportsAsset(address _asset)\n public\n view\n virtual\n override\n returns (bool)\n {\n return _asset == address(assetToken);\n }\n\n /**\n * @notice is not supported for this strategy as the asset and\n * ERC-4626 Tokenized Vault are set at deploy time.\n * @dev If the ERC-4626 Tokenized Vault needed to be changed, a new\n * contract would need to be deployed and the proxy updated.\n */\n function setPTokenAddress(address, address) external override onlyGovernor {\n revert(\"unsupported function\");\n }\n\n /**\n * @notice is not supported for this strategy as the asset and\n * ERC-4626 Tokenized Vault are set at deploy time.\n * @dev If the ERC-4626 Tokenized Vault needed to be changed, a new\n * contract would need to be deployed and the proxy updated.\n */\n function removePToken(uint256) external override onlyGovernor {\n revert(\"unsupported function\");\n }\n\n /// @notice Claim tokens from the Merkle Distributor\n /// @param token The address of the token to claim.\n /// @param amount The amount of tokens to claim.\n /// @param proof The Merkle proof to validate the claim.\n function merkleClaim(\n address token,\n uint256 amount,\n bytes32[] calldata proof\n ) external {\n address[] memory users = new address[](1);\n users[0] = address(this);\n\n address[] memory tokens = new address[](1);\n tokens[0] = token;\n\n uint256[] memory amounts = new uint256[](1);\n amounts[0] = amount;\n\n bytes32[][] memory proofs = new bytes32[][](1);\n proofs[0] = proof;\n\n merkleDistributor.claim(users, tokens, amounts, proofs);\n\n emit ClaimedRewards(token, amount);\n }\n}\n" + }, + "contracts/strategies/Generalized4626USDTStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\ninterface IUSDT {\n // Tether's approve does not return a bool like standard IERC20 contracts\n // slither-disable-next-line erc20-interface\n function approve(address _spender, uint256 _value) external;\n}\n\n/**\n * @title Generalized 4626 Strategy when asset is Tether USD (USDT)\n * @notice Investment strategy for ERC-4626 Tokenized Vaults for the USDT asset.\n * @author Origin Protocol Inc\n */\nimport { Generalized4626Strategy } from \"./Generalized4626Strategy.sol\";\n\ncontract Generalized4626USDTStrategy is Generalized4626Strategy {\n /**\n * @param _baseConfig Base strategy config with platformAddress (ERC-4626 Vault contract), eg sfrxETH or sDAI,\n * and vaultAddress (OToken Vault contract), eg VaultProxy or OETHVaultProxy\n * @param _assetToken Address of the ERC-4626 asset token. eg frxETH or DAI\n */\n constructor(BaseStrategyConfig memory _baseConfig, address _assetToken)\n Generalized4626Strategy(_baseConfig, _assetToken)\n {}\n\n /// @dev Override for Tether as USDT does not return a bool on approve.\n /// Using assetToken.approve will fail as it expects a bool return value\n function _approveBase() internal virtual override {\n // Approval the asset to be transferred to the ERC-4626 Tokenized Vault.\n // Used by the ERC-4626 deposit() and mint() functions\n // slither-disable-next-line unused-return\n IUSDT(address(assetToken)).approve(platformAddress, type(uint256).max);\n }\n}\n" + }, + "contracts/strategies/IAave.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @dev Interface for Aaves Lending Pool\n * Documentation: https://developers.aave.com/#lendingpool\n */\ninterface IAaveLendingPool {\n /**\n * @dev Deposits an `amount` of underlying asset into the reserve, receiving in return overlying aTokens.\n * - E.g. User deposits 100 USDC and gets in return 100 aUSDC\n * @param asset The address of the underlying asset to deposit\n * @param amount The amount to be deposited\n * @param onBehalfOf The address that will receive the aTokens, same as msg.sender if the user\n * wants to receive them on his own wallet, or a different address if the beneficiary of aTokens\n * is a different wallet\n * @param referralCode Code used to register the integrator originating the operation, for potential rewards.\n * 0 if the action is executed directly by the user, without any middle-man\n **/\n function deposit(\n address asset,\n uint256 amount,\n address onBehalfOf,\n uint16 referralCode\n ) external;\n\n /**\n * @dev Withdraws an `amount` of underlying asset from the reserve, burning the equivalent aTokens owned\n * E.g. User has 100 aUSDC, calls withdraw() and receives 100 USDC, burning the 100 aUSDC\n * @param asset The address of the underlying asset to withdraw\n * @param amount The underlying amount to be withdrawn\n * - Send the value type(uint256).max in order to withdraw the whole aToken balance\n * @param to Address that will receive the underlying, same as msg.sender if the user\n * wants to receive it on his own wallet, or a different address if the beneficiary is a\n * different wallet\n * @return The final amount withdrawn\n **/\n function withdraw(\n address asset,\n uint256 amount,\n address to\n ) external returns (uint256);\n}\n\n/**\n * @dev Interface for Aaves Lending Pool\n * Documentation: https://developers.aave.com/#lendingpooladdressesprovider\n */\ninterface ILendingPoolAddressesProvider {\n /**\n * @notice Get the current address for Aave LendingPool\n * @dev Lending pool is the core contract on which to call deposit\n */\n function getLendingPool() external view returns (address);\n}\n" + }, + "contracts/strategies/IAaveIncentivesController.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\ninterface IAaveIncentivesController {\n event RewardsAccrued(address indexed user, uint256 amount);\n\n event RewardsClaimed(\n address indexed user,\n address indexed to,\n uint256 amount\n );\n\n event RewardsClaimed(\n address indexed user,\n address indexed to,\n address indexed claimer,\n uint256 amount\n );\n\n event ClaimerSet(address indexed user, address indexed claimer);\n\n /*\n * @dev Returns the configuration of the distribution for a certain asset\n * @param asset The address of the reference asset of the distribution\n * @return The asset index, the emission per second and the last updated timestamp\n **/\n function getAssetData(address asset)\n external\n view\n returns (\n uint256,\n uint256,\n uint256\n );\n\n /**\n * @dev Whitelists an address to claim the rewards on behalf of another address\n * @param user The address of the user\n * @param claimer The address of the claimer\n */\n function setClaimer(address user, address claimer) external;\n\n /**\n * @dev Returns the whitelisted claimer for a certain address (0x0 if not set)\n * @param user The address of the user\n * @return The claimer address\n */\n function getClaimer(address user) external view returns (address);\n\n /**\n * @dev Configure assets for a certain rewards emission\n * @param assets The assets to incentivize\n * @param emissionsPerSecond The emission for each asset\n */\n function configureAssets(\n address[] calldata assets,\n uint256[] calldata emissionsPerSecond\n ) external;\n\n /**\n * @dev Called by the corresponding asset on any update that affects the rewards distribution\n * @param asset The address of the user\n * @param userBalance The balance of the user of the asset in the lending pool\n * @param totalSupply The total supply of the asset in the lending pool\n **/\n function handleAction(\n address asset,\n uint256 userBalance,\n uint256 totalSupply\n ) external;\n\n /**\n * @dev Returns the total of rewards of an user, already accrued + not yet accrued\n * @param user The address of the user\n * @return The rewards\n **/\n function getRewardsBalance(address[] calldata assets, address user)\n external\n view\n returns (uint256);\n\n /**\n * @dev Claims reward for an user, on all the assets of the lending pool,\n * accumulating the pending rewards\n * @param amount Amount of rewards to claim\n * @param to Address that will be receiving the rewards\n * @return Rewards claimed\n **/\n function claimRewards(\n address[] calldata assets,\n uint256 amount,\n address to\n ) external returns (uint256);\n\n /**\n * @dev Claims reward for an user on behalf, on all the assets of the\n * lending pool, accumulating the pending rewards. The caller must\n * be whitelisted via \"allowClaimOnBehalf\" function by the RewardsAdmin role manager\n * @param amount Amount of rewards to claim\n * @param user Address to check and claim rewards\n * @param to Address that will be receiving the rewards\n * @return Rewards claimed\n **/\n function claimRewardsOnBehalf(\n address[] calldata assets,\n uint256 amount,\n address user,\n address to\n ) external returns (uint256);\n\n /**\n * @dev returns the unclaimed rewards of the user\n * @param user the address of the user\n * @return the unclaimed user rewards\n */\n function getUserUnclaimedRewards(address user)\n external\n view\n returns (uint256);\n\n /**\n * @dev returns the unclaimed rewards of the user\n * @param user the address of the user\n * @param asset The asset to incentivize\n * @return the user index for the asset\n */\n function getUserAssetData(address user, address asset)\n external\n view\n returns (uint256);\n\n /**\n * @dev for backward compatibility with previous implementation of the Incentives controller\n */\n function REWARD_TOKEN() external view returns (address);\n\n /**\n * @dev for backward compatibility with previous implementation of the Incentives controller\n */\n function PRECISION() external view returns (uint8);\n\n /**\n * @dev Gets the distribution end timestamp of the emissions\n */\n function DISTRIBUTION_END() external view returns (uint256);\n}\n" + }, + "contracts/strategies/IAaveStakeToken.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\ninterface IAaveStakedToken {\n function COOLDOWN_SECONDS() external returns (uint256);\n\n function UNSTAKE_WINDOW() external returns (uint256);\n\n function balanceOf(address addr) external returns (uint256);\n\n function redeem(address to, uint256 amount) external;\n\n function stakersCooldowns(address addr) external returns (uint256);\n\n function cooldown() external;\n}\n" + }, + "contracts/strategies/ICompound.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @dev Compound C Token interface\n * Documentation: https://compound.finance/developers/ctokens\n */\ninterface ICERC20 {\n /**\n * @notice The mint function transfers an asset into the protocol, which begins accumulating\n * interest based on the current Supply Rate for the asset. The user receives a quantity of\n * cTokens equal to the underlying tokens supplied, divided by the current Exchange Rate.\n * @param mintAmount The amount of the asset to be supplied, in units of the underlying asset.\n * @return 0 on success, otherwise an Error codes\n */\n function mint(uint256 mintAmount) external returns (uint256);\n\n /**\n * @notice Sender redeems cTokens in exchange for the underlying asset\n * @dev Accrues interest whether or not the operation succeeds, unless reverted\n * @param redeemTokens The number of cTokens to redeem into underlying\n * @return uint 0=success, otherwise an error code.\n */\n function redeem(uint256 redeemTokens) external returns (uint256);\n\n /**\n * @notice The redeem underlying function converts cTokens into a specified quantity of the underlying\n * asset, and returns them to the user. The amount of cTokens redeemed is equal to the quantity of\n * underlying tokens received, divided by the current Exchange Rate. The amount redeemed must be less\n * than the user's Account Liquidity and the market's available liquidity.\n * @param redeemAmount The amount of underlying to be redeemed.\n * @return 0 on success, otherwise an error code.\n */\n function redeemUnderlying(uint256 redeemAmount) external returns (uint256);\n\n /**\n * @notice The user's underlying balance, representing their assets in the protocol, is equal to\n * the user's cToken balance multiplied by the Exchange Rate.\n * @param owner The account to get the underlying balance of.\n * @return The amount of underlying currently owned by the account.\n */\n function balanceOfUnderlying(address owner) external returns (uint256);\n\n /**\n * @notice Calculates the exchange rate from the underlying to the CToken\n * @dev This function does not accrue interest before calculating the exchange rate\n * @return Calculated exchange rate scaled by 1e18\n */\n function exchangeRateStored() external view returns (uint256);\n\n /**\n * @notice Get the token balance of the `owner`\n * @param owner The address of the account to query\n * @return The number of tokens owned by `owner`\n */\n function balanceOf(address owner) external view returns (uint256);\n\n /**\n * @notice Get the supply rate per block for supplying the token to Compound.\n */\n function supplyRatePerBlock() external view returns (uint256);\n\n /**\n * @notice Address of the Compound Comptroller.\n */\n function comptroller() external view returns (address);\n}\n" + }, + "contracts/strategies/IConvexDeposits.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\ninterface IConvexDeposits {\n function deposit(\n uint256 _pid,\n uint256 _amount,\n bool _stake\n ) external returns (bool);\n\n function deposit(\n uint256 _amount,\n bool _lock,\n address _stakeAddress\n ) external;\n}\n" + }, + "contracts/strategies/ICurveETHPoolV1.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\ninterface ICurveETHPoolV1 {\n event AddLiquidity(\n address indexed provider,\n uint256[2] token_amounts,\n uint256[2] fees,\n uint256 invariant,\n uint256 token_supply\n );\n event ApplyNewFee(uint256 fee);\n event Approval(\n address indexed owner,\n address indexed spender,\n uint256 value\n );\n event CommitNewFee(uint256 new_fee);\n event RampA(\n uint256 old_A,\n uint256 new_A,\n uint256 initial_time,\n uint256 future_time\n );\n event RemoveLiquidity(\n address indexed provider,\n uint256[2] token_amounts,\n uint256[2] fees,\n uint256 token_supply\n );\n event RemoveLiquidityImbalance(\n address indexed provider,\n uint256[2] token_amounts,\n uint256[2] fees,\n uint256 invariant,\n uint256 token_supply\n );\n event RemoveLiquidityOne(\n address indexed provider,\n uint256 token_amount,\n uint256 coin_amount,\n uint256 token_supply\n );\n event StopRampA(uint256 A, uint256 t);\n event TokenExchange(\n address indexed buyer,\n int128 sold_id,\n uint256 tokens_sold,\n int128 bought_id,\n uint256 tokens_bought\n );\n event Transfer(\n address indexed sender,\n address indexed receiver,\n uint256 value\n );\n\n function A() external view returns (uint256);\n\n function A_precise() external view returns (uint256);\n\n function DOMAIN_SEPARATOR() external view returns (bytes32);\n\n function add_liquidity(uint256[2] memory _amounts, uint256 _min_mint_amount)\n external\n payable\n returns (uint256);\n\n function add_liquidity(\n uint256[2] memory _amounts,\n uint256 _min_mint_amount,\n address _receiver\n ) external payable returns (uint256);\n\n function admin_action_deadline() external view returns (uint256);\n\n function admin_balances(uint256 i) external view returns (uint256);\n\n function admin_fee() external view returns (uint256);\n\n function allowance(address arg0, address arg1)\n external\n view\n returns (uint256);\n\n function apply_new_fee() external;\n\n function approve(address _spender, uint256 _value) external returns (bool);\n\n function balanceOf(address arg0) external view returns (uint256);\n\n function balances(uint256 arg0) external view returns (uint256);\n\n function calc_token_amount(uint256[2] memory _amounts, bool _is_deposit)\n external\n view\n returns (uint256);\n\n function calc_withdraw_one_coin(uint256 _burn_amount, int128 i)\n external\n view\n returns (uint256);\n\n function coins(uint256 arg0) external view returns (address);\n\n function commit_new_fee(uint256 _new_fee) external;\n\n function decimals() external view returns (uint256);\n\n function ema_price() external view returns (uint256);\n\n function exchange(\n int128 i,\n int128 j,\n uint256 _dx,\n uint256 _min_dy\n ) external payable returns (uint256);\n\n function exchange(\n int128 i,\n int128 j,\n uint256 _dx,\n uint256 _min_dy,\n address _receiver\n ) external payable returns (uint256);\n\n function fee() external view returns (uint256);\n\n function future_A() external view returns (uint256);\n\n function future_A_time() external view returns (uint256);\n\n function future_fee() external view returns (uint256);\n\n function get_balances() external view returns (uint256[2] memory);\n\n function get_dy(\n int128 i,\n int128 j,\n uint256 dx\n ) external view returns (uint256);\n\n function get_p() external view returns (uint256);\n\n function get_virtual_price() external view returns (uint256);\n\n function initial_A() external view returns (uint256);\n\n function initial_A_time() external view returns (uint256);\n\n function initialize(\n string memory _name,\n string memory _symbol,\n address[4] memory _coins,\n uint256[4] memory _rate_multipliers,\n uint256 _A,\n uint256 _fee\n ) external;\n\n function last_price() external view returns (uint256);\n\n function ma_exp_time() external view returns (uint256);\n\n function ma_last_time() external view returns (uint256);\n\n function name() external view returns (string memory);\n\n function nonces(address arg0) external view returns (uint256);\n\n function permit(\n address _owner,\n address _spender,\n uint256 _value,\n uint256 _deadline,\n uint8 _v,\n bytes32 _r,\n bytes32 _s\n ) external returns (bool);\n\n function price_oracle() external view returns (uint256);\n\n function ramp_A(uint256 _future_A, uint256 _future_time) external;\n\n function remove_liquidity(\n uint256 _burn_amount,\n uint256[2] memory _min_amounts\n ) external returns (uint256[2] memory);\n\n function remove_liquidity(\n uint256 _burn_amount,\n uint256[2] memory _min_amounts,\n address _receiver\n ) external returns (uint256[2] memory);\n\n function remove_liquidity_imbalance(\n uint256[2] memory _amounts,\n uint256 _max_burn_amount\n ) external returns (uint256);\n\n function remove_liquidity_imbalance(\n uint256[2] memory _amounts,\n uint256 _max_burn_amount,\n address _receiver\n ) external returns (uint256);\n\n function remove_liquidity_one_coin(\n uint256 _burn_amount,\n int128 i,\n uint256 _min_received\n ) external returns (uint256);\n\n function remove_liquidity_one_coin(\n uint256 _burn_amount,\n int128 i,\n uint256 _min_received,\n address _receiver\n ) external returns (uint256);\n\n function set_ma_exp_time(uint256 _ma_exp_time) external;\n\n function stop_ramp_A() external;\n\n function symbol() external view returns (string memory);\n\n function totalSupply() external view returns (uint256);\n\n function transfer(address _to, uint256 _value) external returns (bool);\n\n function transferFrom(\n address _from,\n address _to,\n uint256 _value\n ) external returns (bool);\n\n function version() external view returns (string memory);\n\n function withdraw_admin_fees() external;\n}\n" + }, + "contracts/strategies/ICurveMetaPool.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.4;\n\ninterface ICurveMetaPool {\n function add_liquidity(uint256[2] calldata amounts, uint256 min_mint_amount)\n external\n returns (uint256);\n\n function get_virtual_price() external view returns (uint256);\n\n function remove_liquidity(uint256 _amount, uint256[2] calldata min_amounts)\n external\n returns (uint256[2] calldata);\n\n function remove_liquidity_one_coin(\n uint256 _token_amount,\n int128 i,\n uint256 min_amount\n ) external returns (uint256);\n\n function remove_liquidity_imbalance(\n uint256[2] calldata amounts,\n uint256 max_burn_amount\n ) external returns (uint256);\n\n function calc_withdraw_one_coin(uint256 _token_amount, int128 i)\n external\n view\n returns (uint256);\n\n function balances(uint256 i) external view returns (uint256);\n\n function calc_token_amount(uint256[2] calldata amounts, bool deposit)\n external\n view\n returns (uint256);\n\n function base_pool() external view returns (address);\n\n function fee() external view returns (uint256);\n\n function coins(uint256 i) external view returns (address);\n\n function exchange(\n int128 i,\n int128 j,\n uint256 dx,\n uint256 min_dy\n ) external returns (uint256);\n\n function get_dy(\n int128 i,\n int128 j,\n uint256 dx\n ) external view returns (uint256);\n}\n" + }, + "contracts/strategies/ICurvePool.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\ninterface ICurvePool {\n function get_virtual_price() external view returns (uint256);\n\n function add_liquidity(uint256[3] calldata _amounts, uint256 _min) external;\n\n function balances(uint256) external view returns (uint256);\n\n function calc_token_amount(uint256[3] calldata _amounts, bool _deposit)\n external\n returns (uint256);\n\n function fee() external view returns (uint256);\n\n function remove_liquidity_one_coin(\n uint256 _amount,\n int128 _index,\n uint256 _minAmount\n ) external;\n\n function remove_liquidity(\n uint256 _amount,\n uint256[3] calldata _minWithdrawAmounts\n ) external;\n\n function calc_withdraw_one_coin(uint256 _amount, int128 _index)\n external\n view\n returns (uint256);\n\n function exchange(\n uint256 i,\n uint256 j,\n uint256 dx,\n uint256 min_dy\n ) external returns (uint256);\n\n function coins(uint256 _index) external view returns (address);\n\n function remove_liquidity_imbalance(\n uint256[3] calldata _amounts,\n uint256 maxBurnAmount\n ) external;\n}\n" + }, + "contracts/strategies/IRewardStaking.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\ninterface IRewardStaking {\n function stakeFor(address, uint256) external;\n\n function stake(uint256) external;\n\n function withdraw(uint256 amount, bool claim) external;\n\n function withdrawAndUnwrap(uint256 amount, bool claim) external;\n\n function earned(address account) external view returns (uint256);\n\n function getReward() external;\n\n function getReward(address _account, bool _claimExtras) external;\n\n function extraRewardsLength() external returns (uint256);\n\n function extraRewards(uint256 _pid) external returns (address);\n\n function rewardToken() external returns (address);\n\n function balanceOf(address account) external view returns (uint256);\n}\n" + }, + "contracts/strategies/MorphoAaveStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title OUSD Morpho Aave Strategy\n * @notice Investment strategy for investing stablecoins via Morpho (Aave)\n * @author Origin Protocol Inc\n */\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport { IERC20, InitializableAbstractStrategy } from \"../utils/InitializableAbstractStrategy.sol\";\nimport { IMorpho } from \"../interfaces/morpho/IMorpho.sol\";\nimport { ILens } from \"../interfaces/morpho/ILens.sol\";\nimport { StableMath } from \"../utils/StableMath.sol\";\n\ncontract MorphoAaveStrategy is InitializableAbstractStrategy {\n address public constant MORPHO = 0x777777c9898D384F785Ee44Acfe945efDFf5f3E0;\n address public constant LENS = 0x507fA343d0A90786d86C7cd885f5C49263A91FF4;\n\n using SafeERC20 for IERC20;\n using StableMath for uint256;\n\n constructor(BaseStrategyConfig memory _stratConfig)\n InitializableAbstractStrategy(_stratConfig)\n {}\n\n /**\n * @dev Initialize function, to set up initial internal state\n * @param _rewardTokenAddresses Address of reward token for platform\n * @param _assets Addresses of initial supported assets\n * @param _pTokens Platform Token corresponding addresses\n */\n function initialize(\n address[] calldata _rewardTokenAddresses,\n address[] calldata _assets,\n address[] calldata _pTokens\n ) external onlyGovernor initializer {\n InitializableAbstractStrategy._initialize(\n _rewardTokenAddresses,\n _assets,\n _pTokens\n );\n }\n\n /**\n * @dev Approve the spending of all assets by main Morpho contract,\n * if for some reason is it necessary.\n */\n function safeApproveAllTokens()\n external\n override\n onlyGovernor\n nonReentrant\n {\n uint256 assetCount = assetsMapped.length;\n for (uint256 i = 0; i < assetCount; i++) {\n address asset = assetsMapped[i];\n\n // Safe approval\n IERC20(asset).safeApprove(MORPHO, 0);\n IERC20(asset).safeApprove(MORPHO, type(uint256).max);\n }\n }\n\n /**\n * @dev Internal method to respond to the addition of new asset\n * We need to approve and allow Morpho to move them\n * @param _asset Address of the asset to approve\n * @param _pToken The pToken for the approval\n */\n // solhint-disable-next-line no-unused-vars\n function _abstractSetPToken(address _asset, address _pToken)\n internal\n override\n {\n IERC20(_asset).safeApprove(MORPHO, 0);\n IERC20(_asset).safeApprove(MORPHO, type(uint256).max);\n }\n\n /**\n * @dev Collect accumulated rewards and send them to Harvester.\n */\n function collectRewardTokens()\n external\n override\n onlyHarvester\n nonReentrant\n {\n // Morpho Aave-v2 doesn't distribute reward tokens\n // solhint-disable-next-line max-line-length\n // Ref: https://developers.morpho.xyz/interact-with-morpho/get-started/interact-with-morpho/claim-rewards#morpho-aave-v2\n }\n\n /**\n * @dev Get the amount of rewards pending to be collected from the protocol\n */\n function getPendingRewards() external view returns (uint256 balance) {\n // Morpho Aave-v2 doesn't distribute reward tokens\n // solhint-disable-next-line max-line-length\n // Ref: https://developers.morpho.xyz/interact-with-morpho/get-started/interact-with-morpho/claim-rewards#morpho-aave-v2\n return 0;\n }\n\n /**\n * @dev Deposit asset into Morpho\n * @param _asset Address of asset to deposit\n * @param _amount Amount of asset to deposit\n */\n function deposit(address _asset, uint256 _amount)\n external\n override\n onlyVault\n nonReentrant\n {\n _deposit(_asset, _amount);\n }\n\n /**\n * @dev Deposit asset into Morpho\n * @param _asset Address of asset to deposit\n * @param _amount Amount of asset to deposit\n */\n function _deposit(address _asset, uint256 _amount) internal {\n require(_amount > 0, \"Must deposit something\");\n\n address pToken = address(_getPTokenFor(_asset));\n\n IMorpho(MORPHO).supply(\n pToken,\n address(this), // the address of the user you want to supply on behalf of\n _amount\n );\n emit Deposit(_asset, pToken, _amount);\n }\n\n /**\n * @dev Deposit the entire balance of any supported asset into Morpho\n */\n function depositAll() external override onlyVault nonReentrant {\n for (uint256 i = 0; i < assetsMapped.length; i++) {\n uint256 balance = IERC20(assetsMapped[i]).balanceOf(address(this));\n if (balance > 0) {\n _deposit(assetsMapped[i], balance);\n }\n }\n }\n\n /**\n * @dev Withdraw asset from Morpho\n * @param _recipient Address to receive withdrawn asset\n * @param _asset Address of asset to withdraw\n * @param _amount Amount of asset to withdraw\n */\n function withdraw(\n address _recipient,\n address _asset,\n uint256 _amount\n ) external override onlyVault nonReentrant {\n _withdraw(_recipient, _asset, _amount);\n }\n\n function _withdraw(\n address _recipient,\n address _asset,\n uint256 _amount\n ) internal {\n require(_amount > 0, \"Must withdraw something\");\n require(_recipient != address(0), \"Must specify recipient\");\n\n address pToken = address(_getPTokenFor(_asset));\n\n IMorpho(MORPHO).withdraw(pToken, _amount);\n emit Withdrawal(_asset, pToken, _amount);\n IERC20(_asset).safeTransfer(_recipient, _amount);\n }\n\n /**\n * @dev Remove all assets from platform and send them to Vault contract.\n */\n function withdrawAll() external override onlyVaultOrGovernor nonReentrant {\n for (uint256 i = 0; i < assetsMapped.length; i++) {\n uint256 balance = _checkBalance(assetsMapped[i]);\n if (balance > 0) {\n _withdraw(vaultAddress, assetsMapped[i], balance);\n }\n }\n }\n\n /**\n * @dev Return total value of an asset held in the platform\n * @param _asset Address of the asset\n * @return balance Total value of the asset in the platform\n */\n function checkBalance(address _asset)\n external\n view\n override\n returns (uint256 balance)\n {\n return _checkBalance(_asset);\n }\n\n function _checkBalance(address _asset)\n internal\n view\n returns (uint256 balance)\n {\n address pToken = address(_getPTokenFor(_asset));\n\n // Total value represented by decimal position of underlying token\n (, , balance) = ILens(LENS).getCurrentSupplyBalanceInOf(\n pToken,\n address(this)\n );\n }\n\n /**\n * @dev Retuns bool indicating whether asset is supported by strategy\n * @param _asset Address of the asset\n */\n function supportsAsset(address _asset) public view override returns (bool) {\n return assetToPToken[_asset] != address(0);\n }\n\n /**\n * @dev Get the pToken wrapped in the IERC20 interface for this asset.\n * Fails if the pToken doesn't exist in our mappings.\n * @param _asset Address of the asset\n * @return pToken Corresponding pToken to this asset\n */\n function _getPTokenFor(address _asset) internal view returns (IERC20) {\n address pToken = assetToPToken[_asset];\n require(pToken != address(0), \"pToken does not exist\");\n return IERC20(pToken);\n }\n}\n" + }, + "contracts/strategies/MorphoCompoundStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title OUSD Morpho Compound Strategy\n * @notice Investment strategy for investing stablecoins via Morpho (Compound)\n * @author Origin Protocol Inc\n */\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport { IERC20, AbstractCompoundStrategy, InitializableAbstractStrategy } from \"./AbstractCompoundStrategy.sol\";\nimport { IMorpho } from \"../interfaces/morpho/IMorpho.sol\";\nimport { ILens } from \"../interfaces/morpho/ILens.sol\";\nimport { StableMath } from \"../utils/StableMath.sol\";\nimport \"../utils/Helpers.sol\";\n\ncontract MorphoCompoundStrategy is AbstractCompoundStrategy {\n address public constant MORPHO = 0x8888882f8f843896699869179fB6E4f7e3B58888;\n address public constant LENS = 0x930f1b46e1D081Ec1524efD95752bE3eCe51EF67;\n using SafeERC20 for IERC20;\n using StableMath for uint256;\n\n constructor(BaseStrategyConfig memory _stratConfig)\n InitializableAbstractStrategy(_stratConfig)\n {}\n\n /**\n * @dev Initialize function, to set up initial internal state\n * @param _rewardTokenAddresses Address of reward token for platform\n * @param _assets Addresses of initial supported assets\n * @param _pTokens Platform Token corresponding addresses\n */\n function initialize(\n address[] calldata _rewardTokenAddresses,\n address[] calldata _assets,\n address[] calldata _pTokens\n ) external onlyGovernor initializer {\n InitializableAbstractStrategy._initialize(\n _rewardTokenAddresses,\n _assets,\n _pTokens\n );\n }\n\n /**\n * @dev Approve the spending of all assets by main Morpho contract,\n * if for some reason is it necessary.\n */\n function safeApproveAllTokens()\n external\n override\n onlyGovernor\n nonReentrant\n {\n uint256 assetCount = assetsMapped.length;\n for (uint256 i = 0; i < assetCount; i++) {\n address asset = assetsMapped[i];\n\n // Safe approval\n IERC20(asset).safeApprove(MORPHO, 0);\n IERC20(asset).safeApprove(MORPHO, type(uint256).max);\n }\n }\n\n /**\n * @dev Internal method to respond to the addition of new asset\n * We need to approve and allow Morpho to move them\n * @param _asset Address of the asset to approve\n * @param _pToken The pToken for the approval\n */\n // solhint-disable-next-line no-unused-vars\n function _abstractSetPToken(address _asset, address _pToken)\n internal\n override\n {\n IERC20(_asset).safeApprove(MORPHO, 0);\n IERC20(_asset).safeApprove(MORPHO, type(uint256).max);\n }\n\n /**\n * @dev Collect accumulated rewards and send them to Harvester.\n */\n function collectRewardTokens()\n external\n override\n onlyHarvester\n nonReentrant\n {\n /**\n * Gas considerations. We could query Morpho LENS's `getUserUnclaimedRewards` for each\n * cToken separately and only claimRewards where it is economically feasible. Each call\n * (out of 3) costs ~60k gas extra.\n *\n * Each extra cToken in the `poolTokens` of `claimRewards` function makes that call\n * 89-120k more expensive gas wise.\n *\n * With Lens query in case where:\n * - there is only 1 reward token to collect. Net gas usage in best case would be\n * 3*60 - 2*120 = -60k -> saving 60k gas\n * - there are 2 reward tokens to collect. Net gas usage in best case would be\n * 3*60 - 120 = 60k -> more expensive for 60k gas\n * - there are 3 reward tokens to collect. Net gas usage in best case would be\n * 3*60 = 180k -> more expensive for 180k gas\n *\n * For the above reasoning such \"optimization\" is not implemented\n */\n\n address[] memory poolTokens = new address[](assetsMapped.length);\n for (uint256 i = 0; i < assetsMapped.length; i++) {\n poolTokens[i] = assetToPToken[assetsMapped[i]];\n }\n\n // slither-disable-next-line unused-return\n IMorpho(MORPHO).claimRewards(\n poolTokens, // The addresses of the underlying protocol's pools to claim rewards from\n false // Whether to trade the accrued rewards for MORPHO token, with a premium\n );\n\n // Transfer COMP to Harvester\n IERC20 rewardToken = IERC20(rewardTokenAddresses[0]);\n uint256 balance = rewardToken.balanceOf(address(this));\n emit RewardTokenCollected(\n harvesterAddress,\n rewardTokenAddresses[0],\n balance\n );\n rewardToken.safeTransfer(harvesterAddress, balance);\n }\n\n /**\n * @dev Get the amount of rewards pending to be collected from the protocol\n */\n function getPendingRewards() external view returns (uint256 balance) {\n address[] memory poolTokens = new address[](assetsMapped.length);\n for (uint256 i = 0; i < assetsMapped.length; i++) {\n poolTokens[i] = assetToPToken[assetsMapped[i]];\n }\n\n return ILens(LENS).getUserUnclaimedRewards(poolTokens, address(this));\n }\n\n /**\n * @dev Deposit asset into Morpho\n * @param _asset Address of asset to deposit\n * @param _amount Amount of asset to deposit\n */\n function deposit(address _asset, uint256 _amount)\n external\n override\n onlyVault\n nonReentrant\n {\n _deposit(_asset, _amount);\n }\n\n /**\n * @dev Deposit asset into Morpho\n * @param _asset Address of asset to deposit\n * @param _amount Amount of asset to deposit\n */\n function _deposit(address _asset, uint256 _amount) internal {\n require(_amount > 0, \"Must deposit something\");\n\n IMorpho(MORPHO).supply(\n address(_getCTokenFor(_asset)),\n address(this), // the address of the user you want to supply on behalf of\n _amount\n );\n emit Deposit(_asset, address(_getCTokenFor(_asset)), _amount);\n }\n\n /**\n * @dev Deposit the entire balance of any supported asset into Morpho\n */\n function depositAll() external override onlyVault nonReentrant {\n for (uint256 i = 0; i < assetsMapped.length; i++) {\n uint256 balance = IERC20(assetsMapped[i]).balanceOf(address(this));\n if (balance > 0) {\n _deposit(assetsMapped[i], balance);\n }\n }\n }\n\n /**\n * @dev Withdraw asset from Morpho\n * @param _recipient Address to receive withdrawn asset\n * @param _asset Address of asset to withdraw\n * @param _amount Amount of asset to withdraw\n */\n function withdraw(\n address _recipient,\n address _asset,\n uint256 _amount\n ) external override onlyVault nonReentrant {\n _withdraw(_recipient, _asset, _amount);\n }\n\n function _withdraw(\n address _recipient,\n address _asset,\n uint256 _amount\n ) internal {\n require(_amount > 0, \"Must withdraw something\");\n require(_recipient != address(0), \"Must specify recipient\");\n\n address pToken = assetToPToken[_asset];\n\n IMorpho(MORPHO).withdraw(pToken, _amount);\n emit Withdrawal(_asset, address(_getCTokenFor(_asset)), _amount);\n IERC20(_asset).safeTransfer(_recipient, _amount);\n }\n\n /**\n * @dev Remove all assets from platform and send them to Vault contract.\n */\n function withdrawAll() external override onlyVaultOrGovernor nonReentrant {\n for (uint256 i = 0; i < assetsMapped.length; i++) {\n uint256 balance = _checkBalance(assetsMapped[i]);\n if (balance > 0) {\n _withdraw(vaultAddress, assetsMapped[i], balance);\n }\n }\n }\n\n /**\n * @dev Return total value of an asset held in the platform\n * @param _asset Address of the asset\n * @return balance Total value of the asset in the platform\n */\n function checkBalance(address _asset)\n external\n view\n override\n returns (uint256 balance)\n {\n return _checkBalance(_asset);\n }\n\n function _checkBalance(address _asset)\n internal\n view\n returns (uint256 balance)\n {\n address pToken = assetToPToken[_asset];\n\n // Total value represented by decimal position of underlying token\n (, , balance) = ILens(LENS).getCurrentSupplyBalanceInOf(\n pToken,\n address(this)\n );\n }\n}\n" + }, + "contracts/strategies/NativeStaking/CompoundingStakingSSVStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\n\nimport { InitializableAbstractStrategy } from \"../../utils/InitializableAbstractStrategy.sol\";\nimport { IWETH9 } from \"../../interfaces/IWETH9.sol\";\nimport { CompoundingValidatorManager } from \"./CompoundingValidatorManager.sol\";\n\n/// @title Compounding Staking SSV Strategy\n/// @notice Strategy to deploy funds into DVT validators powered by the SSV Network\n/// @author Origin Protocol Inc\ncontract CompoundingStakingSSVStrategy is\n CompoundingValidatorManager,\n InitializableAbstractStrategy\n{\n /// @notice SSV ERC20 token that serves as a payment for operating SSV validators\n address public immutable SSV_TOKEN;\n\n // For future use\n uint256[50] private __gap;\n\n /// @param _baseConfig Base strategy config with\n /// `platformAddress` not used so empty address\n /// `vaultAddress` the address of the OETH Vault contract\n /// @param _wethAddress Address of the WETH Token contract\n /// @param _ssvToken Address of the SSV Token contract\n /// @param _ssvNetwork Address of the SSV Network contract\n /// @param _beaconChainDepositContract Address of the beacon chain deposit contract\n /// @param _beaconProofs Address of the Beacon Proofs contract that verifies beacon chain data\n /// @param _beaconGenesisTimestamp The timestamp of the Beacon chain's genesis.\n constructor(\n BaseStrategyConfig memory _baseConfig,\n address _wethAddress,\n address _ssvToken,\n address _ssvNetwork,\n address _beaconChainDepositContract,\n address _beaconProofs,\n uint64 _beaconGenesisTimestamp\n )\n InitializableAbstractStrategy(_baseConfig)\n CompoundingValidatorManager(\n _wethAddress,\n _baseConfig.vaultAddress,\n _beaconChainDepositContract,\n _ssvNetwork,\n _beaconProofs,\n _beaconGenesisTimestamp\n )\n {\n SSV_TOKEN = _ssvToken;\n\n // Make sure nobody owns the implementation contract\n _setGovernor(address(0));\n }\n\n /// @notice Set up initial internal state including\n /// 1. approving the SSVNetwork to transfer SSV tokens from this strategy contract\n /// @param _rewardTokenAddresses Not used so empty array\n /// @param _assets Not used so empty array\n /// @param _pTokens Not used so empty array\n function initialize(\n address[] memory _rewardTokenAddresses,\n address[] memory _assets,\n address[] memory _pTokens\n ) external onlyGovernor initializer {\n InitializableAbstractStrategy._initialize(\n _rewardTokenAddresses,\n _assets,\n _pTokens\n );\n\n safeApproveAllTokens();\n }\n\n /// @notice Unlike other strategies, this does not deposit assets into the underlying platform.\n /// It just checks the asset is WETH and emits the Deposit event.\n /// To deposit WETH into validators, `registerSsvValidator` and `stakeEth` must be used.\n /// @param _asset Address of the WETH token.\n /// @param _amount Amount of WETH that was transferred to the strategy by the vault.\n function deposit(address _asset, uint256 _amount)\n external\n override\n onlyVault\n nonReentrant\n {\n require(_asset == WETH, \"Unsupported asset\");\n require(_amount > 0, \"Must deposit something\");\n\n // Account for the new WETH\n depositedWethAccountedFor += _amount;\n\n emit Deposit(_asset, address(0), _amount);\n }\n\n /// @notice Unlike other strategies, this does not deposit assets into the underlying platform.\n /// It just emits the Deposit event.\n /// To deposit WETH into validators `registerSsvValidator` and `stakeEth` must be used.\n function depositAll() external override onlyVault nonReentrant {\n uint256 wethBalance = IERC20(WETH).balanceOf(address(this));\n uint256 newWeth = wethBalance - depositedWethAccountedFor;\n\n if (newWeth > 0) {\n // Account for the new WETH\n depositedWethAccountedFor = wethBalance;\n\n emit Deposit(WETH, address(0), newWeth);\n }\n }\n\n /// @notice Withdraw ETH and WETH from this strategy contract.\n /// @param _recipient Address to receive withdrawn assets.\n /// @param _asset Address of the WETH token.\n /// @param _amount Amount of WETH to withdraw.\n function withdraw(\n address _recipient,\n address _asset,\n uint256 _amount\n ) external override nonReentrant {\n require(_asset == WETH, \"Unsupported asset\");\n require(\n msg.sender == vaultAddress || msg.sender == validatorRegistrator,\n \"Caller not Vault or Registrator\"\n );\n\n _withdraw(_recipient, _amount, address(this).balance);\n }\n\n function _withdraw(\n address _recipient,\n uint256 _withdrawAmount,\n uint256 _ethBalance\n ) internal {\n require(_withdrawAmount > 0, \"Must withdraw something\");\n require(_recipient == vaultAddress, \"Recipient not Vault\");\n\n // Convert any ETH from validator partial withdrawals, exits\n // or execution rewards to WETH and do the necessary accounting.\n if (_ethBalance > 0) _convertEthToWeth(_ethBalance);\n\n // Transfer WETH to the recipient and do the necessary accounting.\n _transferWeth(_withdrawAmount, _recipient);\n\n emit Withdrawal(WETH, address(0), _withdrawAmount);\n }\n\n /// @notice Transfer all WETH deposits, ETH from validator withdrawals and ETH from\n /// execution rewards in this strategy to the vault.\n /// This does not withdraw from the validators. That has to be done separately with the\n /// `validatorWithdrawal` operation.\n function withdrawAll() external override onlyVaultOrGovernor nonReentrant {\n uint256 ethBalance = address(this).balance;\n uint256 withdrawAmount = IERC20(WETH).balanceOf(address(this)) +\n ethBalance;\n\n if (withdrawAmount > 0) {\n _withdraw(vaultAddress, withdrawAmount, ethBalance);\n }\n }\n\n /// @notice Accounts for all the assets managed by this strategy which includes:\n /// 1. The current WETH in this strategy contract\n /// 2. The last verified ETH balance, total deposits and total validator balances\n /// @param _asset Address of WETH asset.\n /// @return balance Total value in ETH\n function checkBalance(address _asset)\n external\n view\n override\n returns (uint256 balance)\n {\n require(_asset == WETH, \"Unsupported asset\");\n\n // Load the last verified balance from the storage\n // and add to the latest WETH balance of this strategy.\n balance =\n lastVerifiedEthBalance +\n IWETH9(WETH).balanceOf(address(this));\n }\n\n /// @notice Returns bool indicating whether asset is supported by the strategy.\n /// @param _asset The address of the WETH token.\n function supportsAsset(address _asset) public view override returns (bool) {\n return _asset == WETH;\n }\n\n /// @notice Approves the SSV Network contract to transfer SSV tokens for validator registration.\n function safeApproveAllTokens() public override {\n // Approves the SSV Network contract to transfer SSV tokens when validators are registered\n IERC20(SSV_TOKEN).approve(SSV_NETWORK, type(uint256).max);\n }\n\n /**\n * @notice We can accept ETH directly to this contract from anyone as it does not impact our accounting\n * like it did in the legacy NativeStakingStrategy.\n * The new ETH will be accounted for in `checkBalance` after the next snapBalances and verifyBalances txs.\n */\n receive() external payable {}\n\n /***************************************\n Internal functions\n ****************************************/\n\n /// @notice is not supported for this strategy as there is no platform token.\n function setPTokenAddress(address, address) external pure override {\n revert(\"Unsupported function\");\n }\n\n /// @notice is not supported for this strategy as there is no platform token.\n function removePToken(uint256) external pure override {\n revert(\"Unsupported function\");\n }\n\n /// @dev This strategy does not use a platform token like the old Aave and Compound strategies.\n function _abstractSetPToken(address _asset, address) internal override {}\n\n /// @dev Consensus rewards are compounded to the validator's balance instead of being\n /// swept to this strategy contract.\n /// Execution rewards from MEV and tx priority accumulate as ETH in this strategy contract.\n /// Withdrawals from validators also accumulate as ETH in this strategy contract.\n /// It's too complex to separate the rewards from withdrawals so this function is not implemented.\n /// Besides, ETH rewards are not sent to the Dripper any more. The Vault can now regulate\n /// the increase in assets.\n function _collectRewardTokens() internal pure override {\n revert(\"Unsupported function\");\n }\n}\n" + }, + "contracts/strategies/NativeStaking/CompoundingValidatorManager.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport { SafeCast } from \"@openzeppelin/contracts/utils/math/SafeCast.sol\";\nimport { Math } from \"@openzeppelin/contracts/utils/math/Math.sol\";\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport { Pausable } from \"@openzeppelin/contracts/security/Pausable.sol\";\nimport { Governable } from \"../../governance/Governable.sol\";\nimport { IDepositContract } from \"../../interfaces/IDepositContract.sol\";\nimport { IWETH9 } from \"../../interfaces/IWETH9.sol\";\nimport { ISSVNetwork, Cluster } from \"../../interfaces/ISSVNetwork.sol\";\nimport { BeaconRoots } from \"../../beacon/BeaconRoots.sol\";\nimport { PartialWithdrawal } from \"../../beacon/PartialWithdrawal.sol\";\nimport { IBeaconProofs } from \"../../interfaces/IBeaconProofs.sol\";\n\n/**\n * @title Validator lifecycle management contract\n * @notice This contract implements all the required functionality to\n * register, deposit, withdraw, exit and remove validators.\n * @author Origin Protocol Inc\n */\nabstract contract CompoundingValidatorManager is Governable, Pausable {\n using SafeERC20 for IERC20;\n\n /// @dev The amount of ETH in wei that is required for a deposit to a new validator.\n uint256 internal constant DEPOSIT_AMOUNT_WEI = 1 ether;\n /// @dev Validator balances over this amount will eventually become active on the beacon chain.\n /// Due to hysteresis, if the effective balance is 31 ETH, the actual balance\n /// must rise to 32.25 ETH to trigger an effective balance update to 32 ETH.\n /// https://eth2book.info/capella/part2/incentives/balances/#hysteresis\n uint256 internal constant MIN_ACTIVATION_BALANCE_GWEI = 32.25 ether / 1e9;\n /// @dev The maximum number of deposits that are waiting to be verified as processed on the beacon chain.\n uint256 internal constant MAX_DEPOSITS = 32;\n /// @dev The maximum number of validators that can be verified.\n uint256 internal constant MAX_VERIFIED_VALIDATORS = 48;\n /// @dev The default withdrawable epoch value on the Beacon chain.\n /// A value in the far future means the validator is not exiting.\n uint64 internal constant FAR_FUTURE_EPOCH = type(uint64).max;\n /// @dev The number of seconds between each beacon chain slot.\n uint64 internal constant SLOT_DURATION = 12;\n /// @dev The number of slots in each beacon chain epoch.\n uint64 internal constant SLOTS_PER_EPOCH = 32;\n /// @dev Minimum time in seconds to allow snapped balances to be verified.\n /// Set to 35 slots which is 3 slots more than 1 epoch (32 slots). Deposits get processed\n /// once per epoch. This larger than 1 epoch delay should achieve that `snapBalances` sometimes\n /// get called in the middle (or towards the end) of the epoch. Giving the off-chain script\n /// sufficient time after the end of the epoch to prepare the proofs and call `verifyBalances`.\n /// This is considering a malicious actor would keep calling `snapBalances` as frequent as possible\n /// to disturb our operations.\n uint64 internal constant SNAP_BALANCES_DELAY = 35 * SLOT_DURATION;\n\n /// @notice The address of the Wrapped ETH (WETH) token contract\n address internal immutable WETH;\n /// @notice The address of the beacon chain deposit contract\n address internal immutable BEACON_CHAIN_DEPOSIT_CONTRACT;\n /// @notice The address of the SSV Network contract used to interface with\n address internal immutable SSV_NETWORK;\n /// @notice Address of the OETH Vault proxy contract\n address internal immutable VAULT_ADDRESS;\n /// @notice Address of the Beacon Proofs contract that verifies beacon chain data\n address public immutable BEACON_PROOFS;\n /// @notice The timestamp of the Beacon chain genesis.\n /// @dev this is different on Testnets like Hoodi so is set at deployment time.\n uint64 internal immutable BEACON_GENESIS_TIMESTAMP;\n\n /// @notice Address of the registrator - allowed to register, withdraw, exit and remove validators\n address public validatorRegistrator;\n\n /// @notice Deposit data for new compounding validators.\n /// @dev A `VERIFIED` deposit can mean 3 separate things:\n /// - a deposit has been processed by the beacon chain and shall be included in the\n /// balance of the next verifyBalances call\n /// - a deposit has been done to a slashed validator and has probably been recovered\n /// back to this strategy. Probably because we can not know for certain. This contract\n /// only detects when the validator has passed its withdrawal epoch. It is close to impossible\n /// to prove with Merkle Proofs that the postponed deposit this contract is responsible for\n /// creating is not present anymore in BeaconChain.state.pending_deposits. This in effect\n /// means that there might be a period where this contract thinks the deposit has been already\n /// returned as ETH balance before it happens. This will result in some days (or weeks)\n /// -> depending on the size of deposit queue of showing a deficit when calling `checkBalance`.\n /// As this only offsets the yield and doesn't cause a critical double-counting we are not addressing\n /// this issue.\n /// - A deposit has been done to the validator, but our deposit has been front run by a malicious\n /// actor. Funds in the deposit this contract makes are not recoverable.\n enum DepositStatus {\n UNKNOWN, // default value\n PENDING, // deposit is pending and waiting to be verified\n VERIFIED // deposit has been verified\n }\n\n /// @param pubKeyHash Hash of validator's public key using the Beacon Chain's format\n /// @param amountGwei Amount of ETH in gwei that has been deposited to the beacon chain deposit contract\n /// @param slot The beacon chain slot number when the deposit has been made\n /// @param depositIndex The index of the deposit in the list of active deposits\n /// @param status The status of the deposit, either UNKNOWN, PENDING or VERIFIED\n struct DepositData {\n bytes32 pubKeyHash;\n uint64 amountGwei;\n uint64 slot;\n uint32 depositIndex;\n DepositStatus status;\n }\n /// @notice Restricts to only one deposit to an unverified validator at a time.\n /// This is to limit front-running attacks of deposits to the beacon chain contract.\n ///\n /// @dev The value is set to true when a deposit to a new validator has been done that has\n /// not yet be verified.\n bool public firstDeposit;\n /// @notice Mapping of the pending deposit roots to the deposit data\n mapping(bytes32 => DepositData) public deposits;\n /// @notice List of strategy deposit IDs to a validator.\n /// The ID is the merkle root of the pending deposit data which is unique for each validator, amount and block.\n /// Duplicate pending deposit roots are prevented so can be used as an identifier to each strategy deposit.\n /// The list can be for deposits waiting to be verified as processed on the beacon chain,\n /// or deposits that have been verified to an exiting validator and is now waiting for the\n /// validator's balance to be swept.\n /// The list may not be ordered by time of deposit.\n /// Removed deposits will move the last deposit to the removed index.\n bytes32[] public depositList;\n\n enum ValidatorState {\n NON_REGISTERED, // validator is not registered on the SSV network\n REGISTERED, // validator is registered on the SSV network\n STAKED, // validator has funds staked\n VERIFIED, // validator has been verified to exist on the beacon chain\n ACTIVE, // The validator balance is at least 32 ETH. The validator may not yet be active on the beacon chain.\n EXITING, // The validator has been requested to exit\n EXITED, // The validator has been verified to have a zero balance\n REMOVED, // validator has funds withdrawn to this strategy contract and is removed from the SSV\n INVALID // The validator has been front-run and the withdrawal address is not this strategy\n }\n\n // Validator data\n struct ValidatorData {\n ValidatorState state; // The state of the validator known to this contract\n uint40 index; // The index of the validator on the beacon chain\n }\n /// @notice List of validator public key hashes that have been verified to exist on the beacon chain.\n /// These have had a deposit processed and the validator's balance increased.\n /// Validators will be removed from this list when its verified they have a zero balance.\n bytes32[] public verifiedValidators;\n /// @notice Mapping of the hash of the validator's public key to the validator state and index.\n /// Uses the Beacon chain hashing for BLSPubkey which is sha256(abi.encodePacked(validator.pubkey, bytes16(0)))\n mapping(bytes32 => ValidatorData) public validator;\n\n /// @param blockRoot Beacon chain block root of the snapshot\n /// @param timestamp Timestamp of the snapshot\n /// @param ethBalance The balance of ETH in the strategy contract at the snapshot\n struct Balances {\n bytes32 blockRoot;\n uint64 timestamp;\n uint128 ethBalance;\n }\n /// @notice Mapping of the block root to the balances at that slot\n Balances public snappedBalance;\n /// @notice The last verified ETH balance of the strategy\n uint256 public lastVerifiedEthBalance;\n\n /// @dev This contract receives WETH as the deposit asset, but unlike other strategies doesn't immediately\n /// deposit it to an underlying platform. Rather a special privilege account stakes it to the validators.\n /// For that reason calling WETH.balanceOf(this) in a deposit function can contain WETH that has just been\n /// deposited and also WETH that has previously been deposited. To keep a correct count we need to keep track\n /// of WETH that has already been accounted for.\n /// This value represents the amount of WETH balance of this contract that has already been accounted for by the\n /// deposit events.\n /// It is important to note that this variable is not concerned with WETH that is a result of full/partial\n /// withdrawal of the validators. It is strictly concerned with WETH that has been deposited and is waiting to\n /// be staked.\n uint256 public depositedWethAccountedFor;\n\n // For future use\n uint256[41] private __gap;\n\n event RegistratorChanged(address indexed newAddress);\n event FirstDepositReset();\n event SSVValidatorRegistered(\n bytes32 indexed pubKeyHash,\n uint64[] operatorIds\n );\n event SSVValidatorRemoved(bytes32 indexed pubKeyHash, uint64[] operatorIds);\n event ETHStaked(\n bytes32 indexed pubKeyHash,\n bytes32 indexed pendingDepositRoot,\n bytes pubKey,\n uint256 amountWei\n );\n event ValidatorVerified(\n bytes32 indexed pubKeyHash,\n uint40 indexed validatorIndex\n );\n event ValidatorInvalid(bytes32 indexed pubKeyHash);\n event DepositVerified(\n bytes32 indexed pendingDepositRoot,\n uint256 amountWei\n );\n event ValidatorWithdraw(bytes32 indexed pubKeyHash, uint256 amountWei);\n event BalancesSnapped(bytes32 indexed blockRoot, uint256 ethBalance);\n event BalancesVerified(\n uint64 indexed timestamp,\n uint256 totalDepositsWei,\n uint256 totalValidatorBalance,\n uint256 ethBalance\n );\n\n /// @dev Throws if called by any account other than the Registrator\n modifier onlyRegistrator() {\n require(msg.sender == validatorRegistrator, \"Not Registrator\");\n _;\n }\n\n /// @dev Throws if called by any account other than the Registrator or Governor\n modifier onlyRegistratorOrGovernor() {\n require(\n msg.sender == validatorRegistrator || isGovernor(),\n \"Not Registrator or Governor\"\n );\n _;\n }\n\n /// @param _wethAddress Address of the Erc20 WETH Token contract\n /// @param _vaultAddress Address of the Vault\n /// @param _beaconChainDepositContract Address of the beacon chain deposit contract\n /// @param _ssvNetwork Address of the SSV Network contract\n /// @param _beaconProofs Address of the Beacon Proofs contract that verifies beacon chain data\n /// @param _beaconGenesisTimestamp The timestamp of the Beacon chain's genesis.\n constructor(\n address _wethAddress,\n address _vaultAddress,\n address _beaconChainDepositContract,\n address _ssvNetwork,\n address _beaconProofs,\n uint64 _beaconGenesisTimestamp\n ) {\n WETH = _wethAddress;\n BEACON_CHAIN_DEPOSIT_CONTRACT = _beaconChainDepositContract;\n SSV_NETWORK = _ssvNetwork;\n VAULT_ADDRESS = _vaultAddress;\n BEACON_PROOFS = _beaconProofs;\n BEACON_GENESIS_TIMESTAMP = _beaconGenesisTimestamp;\n\n require(\n block.timestamp > _beaconGenesisTimestamp,\n \"Invalid genesis timestamp\"\n );\n }\n\n /**\n *\n * Admin Functions\n *\n */\n\n /// @notice Set the address of the registrator which can register, exit and remove validators\n function setRegistrator(address _address) external onlyGovernor {\n validatorRegistrator = _address;\n emit RegistratorChanged(_address);\n }\n\n /// @notice Reset the `firstDeposit` flag to false so deposits to unverified validators can be made again.\n function resetFirstDeposit() external onlyGovernor {\n require(firstDeposit, \"No first deposit\");\n\n firstDeposit = false;\n\n emit FirstDepositReset();\n }\n\n function pause() external onlyRegistratorOrGovernor {\n _pause();\n }\n\n function unPause() external onlyGovernor {\n _unpause();\n }\n\n /**\n *\n * Validator Management\n *\n */\n\n /// @notice Registers a single validator in a SSV Cluster.\n /// Only the Registrator can call this function.\n /// @param publicKey The public key of the validator\n /// @param operatorIds The operator IDs of the SSV Cluster\n /// @param sharesData The shares data for the validator\n /// @param ssvAmount The amount of SSV tokens to be deposited to the SSV cluster\n /// @param cluster The SSV cluster details including the validator count and SSV balance\n // slither-disable-start reentrancy-no-eth\n function registerSsvValidator(\n bytes calldata publicKey,\n uint64[] calldata operatorIds,\n bytes calldata sharesData,\n uint256 ssvAmount,\n Cluster calldata cluster\n ) external onlyRegistrator whenNotPaused {\n // Hash the public key using the Beacon Chain's format\n bytes32 pubKeyHash = _hashPubKey(publicKey);\n // Check each public key has not already been used\n require(\n validator[pubKeyHash].state == ValidatorState.NON_REGISTERED,\n \"Validator already registered\"\n );\n\n // Store the validator state as registered\n validator[pubKeyHash].state = ValidatorState.REGISTERED;\n\n ISSVNetwork(SSV_NETWORK).registerValidator(\n publicKey,\n operatorIds,\n sharesData,\n ssvAmount,\n cluster\n );\n\n emit SSVValidatorRegistered(pubKeyHash, operatorIds);\n }\n\n // slither-disable-end reentrancy-no-eth\n\n struct ValidatorStakeData {\n bytes pubkey;\n bytes signature;\n bytes32 depositDataRoot;\n }\n\n /// @notice Stakes WETH in this strategy to a compounding validator.\n /// The first deposit to a new validator, the amount must be 1 ETH.\n /// Another deposit of at least 31 ETH is required for the validator to be activated.\n /// This second deposit has to be done after the validator has been verified.\n /// Does not convert any ETH sitting in this strategy to WETH.\n /// There can not be two deposits to the same validator in the same block for the same amount.\n /// Function is pausable so in case a run-away Registrator can be prevented from continuing\n /// to deposit funds to slashed or undesired validators.\n /// @param validatorStakeData validator data needed to stake.\n /// The `ValidatorStakeData` struct contains the pubkey, signature and depositDataRoot.\n /// Only the registrator can call this function.\n /// @param depositAmountGwei The amount of WETH to stake to the validator in Gwei.\n // slither-disable-start reentrancy-eth,reentrancy-no-eth\n function stakeEth(\n ValidatorStakeData calldata validatorStakeData,\n uint64 depositAmountGwei\n ) external onlyRegistrator whenNotPaused {\n uint256 depositAmountWei = uint256(depositAmountGwei) * 1 gwei;\n // Check there is enough WETH from the deposits sitting in this strategy contract\n // There could be ETH from withdrawals but we'll ignore that. If it's really needed\n // the ETH can be withdrawn and then deposited back to the strategy.\n require(\n depositAmountWei <= IWETH9(WETH).balanceOf(address(this)),\n \"Insufficient WETH\"\n );\n require(depositList.length < MAX_DEPOSITS, \"Max deposits\");\n\n // Convert required ETH from WETH and do the necessary accounting\n _convertWethToEth(depositAmountWei);\n\n // Hash the public key using the Beacon Chain's hashing for BLSPubkey\n bytes32 pubKeyHash = _hashPubKey(validatorStakeData.pubkey);\n ValidatorState currentState = validator[pubKeyHash].state;\n // Can only stake to a validator that has been registered, verified or active.\n // Can not stake to a validator that has been staked but not yet verified.\n require(\n (currentState == ValidatorState.REGISTERED ||\n currentState == ValidatorState.VERIFIED ||\n currentState == ValidatorState.ACTIVE),\n \"Not registered or verified\"\n );\n require(depositAmountWei >= 1 ether, \"Deposit too small\");\n if (currentState == ValidatorState.REGISTERED) {\n // Can only have one pending deposit to an unverified validator at a time.\n // This is to limit front-running deposit attacks to a single deposit.\n // The exiting deposit needs to be verified before another deposit can be made.\n // If there was a front-running attack, the validator needs to be verified as invalid\n // and the Governor calls `resetFirstDeposit` to set `firstDeposit` to false.\n require(!firstDeposit, \"Existing first deposit\");\n // Limits the amount of ETH that can be at risk from a front-running deposit attack.\n require(\n depositAmountWei == DEPOSIT_AMOUNT_WEI,\n \"Invalid first deposit amount\"\n );\n // Limits the number of validator balance proofs to verifyBalances\n require(\n verifiedValidators.length + 1 <= MAX_VERIFIED_VALIDATORS,\n \"Max validators\"\n );\n\n // Flag a deposit to an unverified validator so no other deposits can be made\n // to an unverified validator.\n firstDeposit = true;\n validator[pubKeyHash].state = ValidatorState.STAKED;\n }\n\n /* 0x02 to indicate that withdrawal credentials are for a compounding validator\n * that was introduced with the Pectra upgrade.\n * bytes11(0) to fill up the required zeros\n * remaining bytes20 are for the address\n */\n bytes memory withdrawalCredentials = abi.encodePacked(\n bytes1(0x02),\n bytes11(0),\n address(this)\n );\n\n /// After the Pectra upgrade the validators have a new restriction when proposing\n /// blocks. The timestamps are at strict intervals of 12 seconds from the genesis block\n /// forward. Each slot is created at strict 12 second intervals and those slots can\n /// either have blocks attached to them or not. This way using the block.timestamp\n /// the slot number can easily be calculated.\n uint64 depositSlot = (SafeCast.toUint64(block.timestamp) -\n BEACON_GENESIS_TIMESTAMP) / SLOT_DURATION;\n\n // Calculate the merkle root of the beacon chain pending deposit data.\n // This is used as the unique ID of the deposit.\n bytes32 pendingDepositRoot = IBeaconProofs(BEACON_PROOFS)\n .merkleizePendingDeposit(\n pubKeyHash,\n withdrawalCredentials,\n depositAmountGwei,\n validatorStakeData.signature,\n depositSlot\n );\n require(\n deposits[pendingDepositRoot].status == DepositStatus.UNKNOWN,\n \"Duplicate deposit\"\n );\n\n // Store the deposit data for verifyDeposit and verifyBalances\n deposits[pendingDepositRoot] = DepositData({\n pubKeyHash: pubKeyHash,\n amountGwei: depositAmountGwei,\n slot: depositSlot,\n depositIndex: SafeCast.toUint32(depositList.length),\n status: DepositStatus.PENDING\n });\n depositList.push(pendingDepositRoot);\n\n // Deposit to the Beacon Chain deposit contract.\n // This will create a deposit in the beacon chain's pending deposit queue.\n IDepositContract(BEACON_CHAIN_DEPOSIT_CONTRACT).deposit{\n value: depositAmountWei\n }(\n validatorStakeData.pubkey,\n withdrawalCredentials,\n validatorStakeData.signature,\n validatorStakeData.depositDataRoot\n );\n\n emit ETHStaked(\n pubKeyHash,\n pendingDepositRoot,\n validatorStakeData.pubkey,\n depositAmountWei\n );\n }\n\n // slither-disable-end reentrancy-eth,reentrancy-no-eth\n\n /// @notice Request a full or partial withdrawal from a validator.\n /// A zero amount will trigger a full withdrawal.\n /// If the remaining balance is < 32 ETH then only the amount in excess of 32 ETH will be withdrawn.\n /// Only the Registrator can call this function.\n /// 1 wei of value should be sent with the tx to pay for the withdrawal request fee.\n /// If no value sent, 1 wei will be taken from the strategy's ETH balance if it has any.\n /// If no ETH balance, the tx will revert.\n /// @param publicKey The public key of the validator\n /// @param amountGwei The amount of ETH to be withdrawn from the validator in Gwei.\n /// A zero amount will trigger a full withdrawal.\n // slither-disable-start reentrancy-no-eth\n function validatorWithdrawal(bytes calldata publicKey, uint64 amountGwei)\n external\n payable\n onlyRegistrator\n {\n // Hash the public key using the Beacon Chain's format\n bytes32 pubKeyHash = _hashPubKey(publicKey);\n ValidatorData memory validatorDataMem = validator[pubKeyHash];\n // Validator full withdrawal could be denied due to multiple reasons:\n // - the validator has not been activated or active long enough\n // (current_epoch < activation_epoch + SHARD_COMMITTEE_PERIOD)\n // - the validator has pending balance to withdraw from a previous partial withdrawal request\n //\n // Meaning that the on-chain to beacon chain full withdrawal request could fail. Instead\n // of adding complexity of verifying if a validator is eligible for a full exit, we allow\n // multiple full withdrawal requests per validator.\n require(\n validatorDataMem.state == ValidatorState.ACTIVE ||\n validatorDataMem.state == ValidatorState.EXITING,\n \"Validator not active/exiting\"\n );\n\n // If a full withdrawal (validator exit)\n if (amountGwei == 0) {\n // For each staking strategy's deposits\n uint256 depositsCount = depositList.length;\n for (uint256 i = 0; i < depositsCount; ++i) {\n bytes32 pendingDepositRoot = depositList[i];\n // Check there is no pending deposits to the exiting validator\n require(\n pubKeyHash != deposits[pendingDepositRoot].pubKeyHash,\n \"Pending deposit\"\n );\n }\n\n // Store the validator state as exiting so no more deposits can be made to it.\n // This may already be EXITING if the previous exit request failed. eg the validator\n // was not active long enough.\n validator[pubKeyHash].state = ValidatorState.EXITING;\n }\n\n // Do not remove from the list of verified validators.\n // This is done in the verifyBalances function once the validator's balance has been verified to be zero.\n // The validator state will be set to EXITED in the verifyBalances function.\n\n PartialWithdrawal.request(publicKey, amountGwei);\n\n emit ValidatorWithdraw(pubKeyHash, uint256(amountGwei) * 1 gwei);\n }\n\n // slither-disable-end reentrancy-no-eth\n\n /// @notice Remove the validator from the SSV Cluster after:\n /// - the validator has been exited from `validatorWithdrawal` or slashed\n /// - the validator has incorrectly registered and can not be staked to\n /// - the initial deposit was front-run and the withdrawal address is not this strategy's address.\n /// Make sure `validatorWithdrawal` is called with a zero amount and the validator has exited the Beacon chain.\n /// If removed before the validator has exited the beacon chain will result in the validator being slashed.\n /// Only the registrator can call this function.\n /// @param publicKey The public key of the validator\n /// @param operatorIds The operator IDs of the SSV Cluster\n /// @param cluster The SSV cluster details including the validator count and SSV balance\n // slither-disable-start reentrancy-no-eth\n function removeSsvValidator(\n bytes calldata publicKey,\n uint64[] calldata operatorIds,\n Cluster calldata cluster\n ) external onlyRegistrator {\n // Hash the public key using the Beacon Chain's format\n bytes32 pubKeyHash = _hashPubKey(publicKey);\n ValidatorState currentState = validator[pubKeyHash].state;\n // Can remove SSV validators that were incorrectly registered and can not be deposited to.\n require(\n currentState == ValidatorState.REGISTERED ||\n currentState == ValidatorState.EXITED ||\n currentState == ValidatorState.INVALID,\n \"Validator not regd or exited\"\n );\n\n validator[pubKeyHash].state = ValidatorState.REMOVED;\n\n ISSVNetwork(SSV_NETWORK).removeValidator(\n publicKey,\n operatorIds,\n cluster\n );\n\n emit SSVValidatorRemoved(pubKeyHash, operatorIds);\n }\n\n /**\n *\n * SSV Management\n *\n */\n\n // slither-disable-end reentrancy-no-eth\n\n /// `depositSSV` has been removed as `deposit` on the SSVNetwork contract can be called directly\n /// by the Strategist which is already holding SSV tokens.\n\n /// @notice Withdraws excess SSV Tokens from the SSV Network contract which was used to pay the SSV Operators.\n /// @dev A SSV cluster is defined by the SSVOwnerAddress and the set of operatorIds.\n /// @param operatorIds The operator IDs of the SSV Cluster\n /// @param ssvAmount The amount of SSV tokens to be withdrawn from the SSV cluster\n /// @param cluster The SSV cluster details including the validator count and SSV balance\n function withdrawSSV(\n uint64[] memory operatorIds,\n uint256 ssvAmount,\n Cluster memory cluster\n ) external onlyGovernor {\n ISSVNetwork(SSV_NETWORK).withdraw(operatorIds, ssvAmount, cluster);\n }\n\n /**\n *\n * Beacon Chain Proofs\n *\n */\n\n /// @notice Verifies a validator's index to its public key.\n /// Adds to the list of verified validators if the validator's withdrawal address is this strategy's address.\n /// Marks the validator as invalid and removes the deposit if the withdrawal address is not this strategy's address.\n /// @param nextBlockTimestamp The timestamp of the execution layer block after the beacon chain slot\n /// we are verifying.\n /// The next one is needed as the Beacon Oracle returns the parent beacon block root for a block timestamp,\n /// which is the beacon block root of the previous block.\n /// @param validatorIndex The index of the validator on the beacon chain.\n /// @param pubKeyHash The hash of the validator's public key using the Beacon Chain's format\n /// @param withdrawalCredentials contain the validator type and withdrawal address. These can be incorrect and/or\n /// malformed. In case of incorrect withdrawalCredentials the validator deposit has been front run\n /// @param validatorPubKeyProof The merkle proof for the validator public key to the beacon block root.\n /// This is 53 witness hashes of 32 bytes each concatenated together starting from the leaf node.\n /// BeaconBlock.state.validators[validatorIndex].pubkey\n function verifyValidator(\n uint64 nextBlockTimestamp,\n uint40 validatorIndex,\n bytes32 pubKeyHash,\n bytes32 withdrawalCredentials,\n bytes calldata validatorPubKeyProof\n ) external {\n require(\n validator[pubKeyHash].state == ValidatorState.STAKED,\n \"Validator not staked\"\n );\n\n // Get the beacon block root of the slot we are verifying the validator in.\n // The parent beacon block root of the next block is the beacon block root of the slot we are verifying.\n bytes32 blockRoot = BeaconRoots.parentBlockRoot(nextBlockTimestamp);\n\n // Verify the validator index is for the validator with the given public key.\n // Also verify the validator's withdrawal credentials\n IBeaconProofs(BEACON_PROOFS).verifyValidator(\n blockRoot,\n pubKeyHash,\n validatorPubKeyProof,\n validatorIndex,\n withdrawalCredentials\n );\n\n // Store the validator state as verified\n validator[pubKeyHash] = ValidatorData({\n state: ValidatorState.VERIFIED,\n index: validatorIndex\n });\n\n bytes32 expectedWithdrawalCredentials = bytes32(\n abi.encodePacked(bytes1(0x02), bytes11(0), address(this))\n );\n\n // If the initial deposit was front-run and the withdrawal address is not this strategy\n // or the validator type is not a compounding validator (0x02)\n if (expectedWithdrawalCredentials != withdrawalCredentials) {\n // override the validator state\n validator[pubKeyHash].state = ValidatorState.INVALID;\n\n // Find and remove the deposit as the funds can not be recovered\n uint256 depositCount = depositList.length;\n for (uint256 i = 0; i < depositCount; i++) {\n DepositData memory deposit = deposits[depositList[i]];\n if (deposit.pubKeyHash == pubKeyHash) {\n // next verifyBalances will correctly account for the loss of a front-run\n // deposit. Doing it here accounts for the loss as soon as possible\n lastVerifiedEthBalance -= Math.min(\n lastVerifiedEthBalance,\n uint256(deposit.amountGwei) * 1 gwei\n );\n _removeDeposit(depositList[i], deposit);\n break;\n }\n }\n\n // Leave the `firstDeposit` flag as true so no more deposits to unverified validators can be made.\n // The Governor has to reset the `firstDeposit` to false before another deposit to\n // an unverified validator can be made.\n // The Governor can set a new `validatorRegistrator` if they suspect it has been compromised.\n\n emit ValidatorInvalid(pubKeyHash);\n return;\n }\n\n // Add the new validator to the list of verified validators\n verifiedValidators.push(pubKeyHash);\n\n // Reset the firstDeposit flag as the first deposit to an unverified validator has been verified.\n firstDeposit = false;\n\n emit ValidatorVerified(pubKeyHash, validatorIndex);\n }\n\n struct FirstPendingDepositSlotProofData {\n uint64 slot;\n bytes proof;\n }\n\n struct StrategyValidatorProofData {\n uint64 withdrawableEpoch;\n bytes withdrawableEpochProof;\n }\n\n /// @notice Verifies a deposit on the execution layer has been processed by the beacon chain.\n /// This means the accounting of the strategy's ETH moves from a pending deposit to a validator balance.\n ///\n /// Important: this function has a limitation where `depositProcessedSlot` that is passed by the off-chain\n /// verifier requires a slot immediately after it to propose a block otherwise the `BeaconRoots.parentBlockRoot`\n /// will fail. This shouldn't be a problem, since by the current behaviour of beacon chain only 1%-3% slots\n /// don't propose a block.\n /// @param pendingDepositRoot The unique identifier of the deposit emitted in `ETHStaked` from\n /// the `stakeEth` function.\n /// @param depositProcessedSlot Any slot on or after the strategy's deposit was processed on the beacon chain.\n /// Can not be a slot with pending deposits with the same slot as the deposit being verified.\n /// Can not be a slot before a missed slot as the Beacon Root contract will have the parent block root\n /// set for the next block timestamp in 12 seconds time.\n /// @param firstPendingDeposit a `FirstPendingDepositSlotProofData` struct containing:\n /// - slot: The beacon chain slot of the first deposit in the beacon chain's deposit queue.\n /// Can be any non-zero value if the deposit queue is empty.\n /// - proof: The merkle proof of the first pending deposit's slot to the beacon block root.\n /// Can be either:\n /// * 40 witness hashes for BeaconBlock.state.PendingDeposits[0].slot when the deposit queue is not empty.\n /// * 37 witness hashes for BeaconBlock.state.PendingDeposits[0] when the deposit queue is empty.\n /// The 32 byte witness hashes are concatenated together starting from the leaf node.\n /// @param strategyValidatorData a `StrategyValidatorProofData` struct containing:\n /// - withdrawableEpoch: The withdrawable epoch of the validator the strategy is depositing to.\n /// - withdrawableEpochProof: The merkle proof for the withdrawable epoch of the validator the strategy\n /// is depositing to, to the beacon block root.\n /// This is 53 witness hashes of 32 bytes each concatenated together starting from the leaf node.\n // slither-disable-start reentrancy-no-eth\n function verifyDeposit(\n bytes32 pendingDepositRoot,\n uint64 depositProcessedSlot,\n FirstPendingDepositSlotProofData calldata firstPendingDeposit,\n StrategyValidatorProofData calldata strategyValidatorData\n ) external {\n // Load into memory the previously saved deposit data\n DepositData memory deposit = deposits[pendingDepositRoot];\n ValidatorData memory strategyValidator = validator[deposit.pubKeyHash];\n require(deposit.status == DepositStatus.PENDING, \"Deposit not pending\");\n require(firstPendingDeposit.slot != 0, \"Zero 1st pending deposit slot\");\n\n // We should allow the verification of deposits for validators that have been marked as exiting\n // to cover this situation:\n // - there are 2 pending deposits\n // - beacon chain has slashed the validator\n // - when verifyDeposit is called for the first deposit it sets the Validator state to EXITING\n // - verifyDeposit should allow a secondary call for the other deposit to a slashed validator\n require(\n strategyValidator.state == ValidatorState.VERIFIED ||\n strategyValidator.state == ValidatorState.ACTIVE ||\n strategyValidator.state == ValidatorState.EXITING,\n \"Not verified/active/exiting\"\n );\n // The verification slot must be after the deposit's slot.\n // This is needed for when the deposit queue is empty.\n require(deposit.slot < depositProcessedSlot, \"Slot not after deposit\");\n\n uint64 snapTimestamp = snappedBalance.timestamp;\n\n // This check prevents an accounting error that can happen if:\n // - snapBalances are snapped at the time of T\n // - deposit is processed on the beacon chain after time T and before verifyBalances()\n // - verifyDeposit is called before verifyBalances which removes a deposit from depositList\n // and deposit balance from totalDepositsWei\n // - verifyBalances is called under-reporting the strategy's balance\n require(\n (_calcNextBlockTimestamp(depositProcessedSlot) <= snapTimestamp) ||\n snapTimestamp == 0,\n \"Deposit after balance snapshot\"\n );\n\n // Get the parent beacon block root of the next block which is the block root of the deposit verification slot.\n // This will revert if the slot after the verification slot was missed.\n bytes32 depositBlockRoot = BeaconRoots.parentBlockRoot(\n _calcNextBlockTimestamp(depositProcessedSlot)\n );\n\n // Verify the slot of the first pending deposit matches the beacon chain\n bool isDepositQueueEmpty = IBeaconProofs(BEACON_PROOFS)\n .verifyFirstPendingDeposit(\n depositBlockRoot,\n firstPendingDeposit.slot,\n firstPendingDeposit.proof\n );\n\n // Verify the withdrawableEpoch on the validator of the strategy's deposit\n IBeaconProofs(BEACON_PROOFS).verifyValidatorWithdrawable(\n depositBlockRoot,\n strategyValidator.index,\n strategyValidatorData.withdrawableEpoch,\n strategyValidatorData.withdrawableEpochProof\n );\n\n uint64 firstPendingDepositEpoch = firstPendingDeposit.slot /\n SLOTS_PER_EPOCH;\n\n // If deposit queue is empty all deposits have certainly been processed. If not\n // a validator can either be not exiting and no further checks are required.\n // Or a validator is exiting then this function needs to make sure that the\n // pending deposit to an exited validator has certainly been processed. The\n // slot/epoch of first pending deposit is the one that contains the transaction\n // where the deposit to the ETH Deposit Contract has been made.\n //\n // Once the firstPendingDepositEpoch becomes greater than the withdrawableEpoch of\n // the slashed validator then the deposit has certainly been processed. When the beacon\n // chain reaches the withdrawableEpoch of the validator the deposit will no longer be\n // postponed. And any new deposits created (and present in the deposit queue)\n // will have an equal or larger withdrawableEpoch.\n require(\n strategyValidatorData.withdrawableEpoch == FAR_FUTURE_EPOCH ||\n strategyValidatorData.withdrawableEpoch <=\n firstPendingDepositEpoch ||\n isDepositQueueEmpty,\n \"Exit Deposit likely not proc.\"\n );\n\n // solhint-disable max-line-length\n // Check the deposit slot is before the first pending deposit's slot on the beacon chain.\n // If this is not true then we can't guarantee the deposit has been processed by the beacon chain.\n // The deposit's slot can not be the same slot as the first pending deposit as there could be\n // many deposits in the same block, hence have the same pending deposit slot.\n // If the deposit queue is empty then our deposit must have been processed on the beacon chain.\n // The deposit slot can be zero for validators consolidating to a compounding validator or 0x01 validator\n // being promoted to a compounding one. Reference:\n // - [switch_to_compounding_validator](https://ethereum.github.io/consensus-specs/specs/electra/beacon-chain/#new-switch_to_compounding_validator\n // - [queue_excess_active_balance](https://ethereum.github.io/consensus-specs/specs/electra/beacon-chain/#new-queue_excess_active_balance)\n // - [process_consolidation_request](https://ethereum.github.io/consensus-specs/specs/electra/beacon-chain/#new-process_consolidation_request)\n // We can not guarantee that the deposit has been processed in that case.\n // solhint-enable max-line-length\n require(\n deposit.slot < firstPendingDeposit.slot || isDepositQueueEmpty,\n \"Deposit likely not processed\"\n );\n\n // Remove the deposit now it has been verified as processed on the beacon chain.\n _removeDeposit(pendingDepositRoot, deposit);\n\n emit DepositVerified(\n pendingDepositRoot,\n uint256(deposit.amountGwei) * 1 gwei\n );\n }\n\n function _removeDeposit(\n bytes32 pendingDepositRoot,\n DepositData memory deposit\n ) internal {\n // After verifying the proof, update the contract storage\n deposits[pendingDepositRoot].status = DepositStatus.VERIFIED;\n // Move the last deposit to the index of the verified deposit\n bytes32 lastDeposit = depositList[depositList.length - 1];\n depositList[deposit.depositIndex] = lastDeposit;\n deposits[lastDeposit].depositIndex = deposit.depositIndex;\n // Delete the last deposit from the list\n depositList.pop();\n }\n\n /// @dev Calculates the timestamp of the next execution block from the given slot.\n /// @param slot The beacon chain slot number used for merkle proof verification.\n function _calcNextBlockTimestamp(uint64 slot)\n internal\n view\n returns (uint64)\n {\n // Calculate the next block timestamp from the slot.\n return SLOT_DURATION * slot + BEACON_GENESIS_TIMESTAMP + SLOT_DURATION;\n }\n\n // slither-disable-end reentrancy-no-eth\n\n /// @notice Stores the current ETH balance at the current block and beacon block root\n /// of the slot that is associated with the previous block.\n ///\n /// When snapping / verifying balance it is of a high importance that there is no\n /// miss-match in respect to ETH that is held by the contract and balances that are\n /// verified on the validators.\n ///\n /// First some context on the beacon-chain block building behaviour. Relevant parts of\n /// constructing a block on the beacon chain consist of:\n /// - process_withdrawals: ETH is deducted from the validator's balance\n /// - process_execution_payload: immediately after the previous step executing all the\n /// transactions\n /// - apply the withdrawals: adding ETH to the recipient which is the withdrawal address\n /// contained in the withdrawal credentials of the exited validators\n ///\n /// That means that balance increases which are part of the post-block execution state are\n /// done within the block, but the transaction that are contained within that block can not\n /// see / interact with the balance from the exited validators. Only transactions in the\n /// next block can do that.\n ///\n /// When snap balances is performed the state of the chain is snapped across 2 separate\n /// chain states:\n /// - ETH balance of the contract is recorded on block X -> and corresponding slot Y\n /// - beacon chain block root is recorded of block X - 1 -> and corresponding slot Y - 1\n /// given there were no missed slots. It could also be Y - 2, Y - 3 depending on how\n /// many slots have not managed to propose a block. For the sake of simplicity this slot\n /// will be referred to as Y - 1 as it makes no difference in the argument\n ///\n /// Given these 2 separate chain states it is paramount that verify balances can not experience\n /// miss-counting ETH or much more dangerous double counting of the ETH.\n ///\n /// When verifyBalances is called it is performed on the current block Z where Z > X. Verify\n /// balances adds up all the ETH (omitting WETH) controlled by this contract:\n /// - ETH balance in the contract on block X\n /// - ETH balance in Deposits on block Z that haven't been yet processed in slot Y - 1\n /// - ETH balance in validators that are active in slot Y - 1\n /// - skips the ETH balance in validators that have withdrawn in slot Y - 1 (or sooner)\n /// and have their balance visible to transactions in slot Y and corresponding block X\n /// (or sooner)\n ///\n /// Lets verify the correctness of ETH accounting given the above described behaviour.\n ///\n /// *ETH balance in the contract on block X*\n ///\n /// This is an ETH balance of the contract on a non current X block. Any ETH leaving the\n /// contract as a result of a withdrawal subtracts from the ETH accounted for on block X\n /// if `verifyBalances` has already been called. It also invalidates a `snapBalances` in\n /// case `verifyBalances` has not been called yet. Not performing this would result in not\n /// accounting for the withdrawn ETH that has happened anywhere in the block interval [X + 1, Z].\n ///\n /// Similarly to withdrawals any `stakeEth` deposits to the deposit contract adds to the ETH\n /// accounted for since the last `verifyBalances` has been called. And it invalidates the\n /// `snapBalances` in case `verifyBalances` hasn't been yet called. Not performing this\n /// would result in double counting the `stakedEth` since it would be present once in the\n /// snapped contract balance and the second time in deposit storage variables.\n ///\n /// This behaviour is correct.\n ///\n /// *ETH balance in Deposits on block Z that haven't been yet processed in slot Y - 1*\n ///\n /// The contract sums up all the ETH that has been deposited to the Beacon chain deposit\n /// contract at block Z. The execution layer doesn't have direct access to the state of\n /// deposits on the beacon chain. And if it is to sum up all the ETH that is marked to be\n /// deposited it needs to be sure to not double count ETH that is in deposits (storage vars)\n /// and could also be part of the validator balances. It does that by verifying that at\n /// slot Y - 1 none of the deposits visible on block Z have been processed. Meaning since\n /// the last snap till now all are still in queue. Which ensures they can not be part of\n /// the validator balances in later steps.\n ///\n /// This behaviour is correct.\n ///\n /// *ETH balance in validators that are active in slot Y - 1*\n ///\n /// The contract is verifying none of the deposits on Y - 1 slot have been processed and\n /// for that reason it checks the validator balances in the same slot. Ensuring accounting\n /// correctness.\n ///\n /// This behaviour is correct.\n ///\n /// *The withdrawn validators*\n ///\n /// The withdrawn validators could have their balances deducted in any slot before slot\n /// Y - 1 and the execution layer sees the balance increase in the subsequent slot. Lets\n /// look at the \"worst case scenario\" where the validator withdrawal is processed in the\n /// slot Y - 1 (snapped slot) and see their balance increase (in execution layer) in slot\n /// Y -> block X. The ETH balance on the contract is snapped at block X meaning that\n /// even if the validator exits at the latest possible time it is paramount that the ETH\n /// balance on the execution layer is recorded in the next block. Correctly accounting\n /// for the withdrawn ETH.\n ///\n /// Worth mentioning if the validator exit is processed by the slot Y and balance increase\n /// seen on the execution layer on block X + 1 the withdrawal is ignored by both the\n /// validator balance verification as well as execution layer contract balance snap.\n ///\n /// This behaviour is correct.\n ///\n /// The validator balances on the beacon chain can then be proved with `verifyBalances`.\n function snapBalances() external {\n uint64 currentTimestamp = SafeCast.toUint64(block.timestamp);\n require(\n snappedBalance.timestamp + SNAP_BALANCES_DELAY < currentTimestamp,\n \"Snap too soon\"\n );\n\n bytes32 blockRoot = BeaconRoots.parentBlockRoot(currentTimestamp);\n // Get the current ETH balance\n uint256 ethBalance = address(this).balance;\n\n // Store the snapped balance\n snappedBalance = Balances({\n blockRoot: blockRoot,\n timestamp: currentTimestamp,\n ethBalance: SafeCast.toUint128(ethBalance)\n });\n\n emit BalancesSnapped(blockRoot, ethBalance);\n }\n\n // A struct is used to avoid stack too deep errors\n struct BalanceProofs {\n // BeaconBlock.state.balances\n bytes32 balancesContainerRoot;\n bytes balancesContainerProof;\n // BeaconBlock.state.balances[validatorIndex]\n bytes32[] validatorBalanceLeaves;\n bytes[] validatorBalanceProofs;\n }\n\n struct PendingDepositProofs {\n bytes32 pendingDepositContainerRoot;\n bytes pendingDepositContainerProof;\n uint32[] pendingDepositIndexes;\n bytes[] pendingDepositProofs;\n }\n\n /// @notice Verifies the balances of all active validators on the beacon chain\n /// and checks each of the strategy's deposits are still to be processed by the beacon chain.\n /// @param balanceProofs a `BalanceProofs` struct containing the following:\n /// - balancesContainerRoot: The merkle root of the balances container\n /// - balancesContainerProof: The merkle proof for the balances container to the beacon block root.\n /// This is 9 witness hashes of 32 bytes each concatenated together starting from the leaf node.\n /// - validatorBalanceLeaves: Array of leaf nodes containing the validator balance with three other balances.\n /// - validatorBalanceProofs: Array of merkle proofs for the validator balance to the Balances container root.\n /// This is 39 witness hashes of 32 bytes each concatenated together starting from the leaf node.\n /// @param pendingDepositProofs a `PendingDepositProofs` struct containing the following:\n /// - pendingDepositContainerRoot: The merkle root of the pending deposits list container\n /// - pendingDepositContainerProof: The merkle proof from the pending deposits list container\n /// to the beacon block root.\n /// This is 9 witness hashes of 32 bytes each concatenated together starting from the leaf node.\n /// - pendingDepositIndexes: Array of indexes in the pending deposits list container for each\n /// of the strategy's deposits.\n /// - pendingDepositProofs: Array of merkle proofs for each strategy deposit in the\n /// beacon chain's pending deposit list container to the pending deposits list container root.\n /// These are 28 witness hashes of 32 bytes each concatenated together starting from the leaf node.\n // slither-disable-start reentrancy-no-eth\n function verifyBalances(\n BalanceProofs calldata balanceProofs,\n PendingDepositProofs calldata pendingDepositProofs\n ) external {\n // Load previously snapped balances for the given block root\n Balances memory balancesMem = snappedBalance;\n // Check the balances are the latest\n require(balancesMem.timestamp > 0, \"No snapped balances\");\n\n uint256 verifiedValidatorsCount = verifiedValidators.length;\n uint256 totalValidatorBalance = 0;\n uint256 depositsCount = depositList.length;\n\n // If there are no verified validators then we can skip the balance verification\n if (verifiedValidatorsCount > 0) {\n require(\n balanceProofs.validatorBalanceProofs.length ==\n verifiedValidatorsCount,\n \"Invalid balance proofs\"\n );\n require(\n balanceProofs.validatorBalanceLeaves.length ==\n verifiedValidatorsCount,\n \"Invalid balance leaves\"\n );\n // verify beaconBlock.state.balances root to beacon block root\n IBeaconProofs(BEACON_PROOFS).verifyBalancesContainer(\n balancesMem.blockRoot,\n balanceProofs.balancesContainerRoot,\n balanceProofs.balancesContainerProof\n );\n\n bytes32[]\n memory validatorHashesMem = _getPendingDepositValidatorHashes(\n depositsCount\n );\n\n // for each validator in reverse order so we can pop off exited validators at the end\n for (uint256 i = verifiedValidatorsCount; i > 0; ) {\n --i;\n ValidatorData memory validatorDataMem = validator[\n verifiedValidators[i]\n ];\n // verify validator's balance in beaconBlock.state.balances to the\n // beaconBlock.state.balances container root\n uint256 validatorBalanceGwei = IBeaconProofs(BEACON_PROOFS)\n .verifyValidatorBalance(\n balanceProofs.balancesContainerRoot,\n balanceProofs.validatorBalanceLeaves[i],\n balanceProofs.validatorBalanceProofs[i],\n validatorDataMem.index\n );\n\n // If the validator has exited and the balance is now zero\n if (validatorBalanceGwei == 0) {\n // Check if there are any pending deposits to this validator\n bool depositPending = false;\n for (uint256 j = 0; j < validatorHashesMem.length; j++) {\n if (validatorHashesMem[j] == verifiedValidators[i]) {\n depositPending = true;\n break;\n }\n }\n\n // If validator has a pending deposit we can not remove due to\n // the following situation:\n // - validator has a pending deposit\n // - validator has been slashed\n // - sweep cycle has withdrawn all ETH from the validator. Balance is 0\n // - beacon chain has processed the deposit and set the validator balance\n // to deposit amount\n // - if validator is no longer in the list of verifiedValidators its\n // balance will not be considered and be under-counted.\n if (!depositPending) {\n // Store the validator state as exited\n // This could have been in VERIFIED, ACTIVE or EXITING state\n validator[verifiedValidators[i]].state = ValidatorState\n .EXITED;\n\n // Remove the validator with a zero balance from the list of verified validators\n\n // Reduce the count of verified validators which is the last index before the pop removes it.\n verifiedValidatorsCount -= 1;\n\n // Move the last validator that has already been verified to the current index.\n // There's an extra SSTORE if i is the last active validator but that's fine,\n // It's not a common case and the code is simpler this way.\n verifiedValidators[i] = verifiedValidators[\n verifiedValidatorsCount\n ];\n // Delete the last validator from the list\n verifiedValidators.pop();\n }\n\n // The validator balance is zero so not need to add to totalValidatorBalance\n continue;\n } else if (\n validatorDataMem.state == ValidatorState.VERIFIED &&\n validatorBalanceGwei > MIN_ACTIVATION_BALANCE_GWEI\n ) {\n // Store the validator state as active. This does not necessarily mean the\n // validator is active on the beacon chain yet. It just means the validator has\n // enough balance that it can become active.\n validator[verifiedValidators[i]].state = ValidatorState\n .ACTIVE;\n }\n\n // convert Gwei balance to Wei and add to the total validator balance\n totalValidatorBalance += validatorBalanceGwei * 1 gwei;\n }\n }\n\n uint256 totalDepositsWei = 0;\n\n // If there are no deposits then we can skip the deposit verification.\n // This section is after the validator balance verifications so an exited validator will be marked\n // as EXITED before the deposits are verified. If there was a deposit to an exited validator\n // then the deposit can only be removed once the validator is fully exited.\n // It is possible that validator fully exits and a postponed deposit to an exited validator increases\n // its balance again. In such case the contract will erroneously consider a deposit applied before it\n // has been applied on the beacon chain showing a smaller than real `totalValidatorBalance`.\n if (depositsCount > 0) {\n require(\n pendingDepositProofs.pendingDepositProofs.length ==\n depositsCount,\n \"Invalid deposit proofs\"\n );\n require(\n pendingDepositProofs.pendingDepositIndexes.length ==\n depositsCount,\n \"Invalid deposit indexes\"\n );\n\n // Verify from the root of the pending deposit list container to the beacon block root\n IBeaconProofs(BEACON_PROOFS).verifyPendingDepositsContainer(\n balancesMem.blockRoot,\n pendingDepositProofs.pendingDepositContainerRoot,\n pendingDepositProofs.pendingDepositContainerProof\n );\n\n // For each staking strategy's deposit.\n for (uint256 i = 0; i < depositsCount; ++i) {\n bytes32 pendingDepositRoot = depositList[i];\n\n // Verify the strategy's deposit is still pending on the beacon chain.\n IBeaconProofs(BEACON_PROOFS).verifyPendingDeposit(\n pendingDepositProofs.pendingDepositContainerRoot,\n pendingDepositRoot,\n pendingDepositProofs.pendingDepositProofs[i],\n pendingDepositProofs.pendingDepositIndexes[i]\n );\n\n // Convert the deposit amount from Gwei to Wei and add to the total\n totalDepositsWei +=\n uint256(deposits[pendingDepositRoot].amountGwei) *\n 1 gwei;\n }\n }\n\n // Store the verified balance in storage\n lastVerifiedEthBalance =\n totalDepositsWei +\n totalValidatorBalance +\n balancesMem.ethBalance;\n // Reset the last snap timestamp so a new snapBalances has to be made\n snappedBalance.timestamp = 0;\n\n emit BalancesVerified(\n balancesMem.timestamp,\n totalDepositsWei,\n totalValidatorBalance,\n balancesMem.ethBalance\n );\n }\n\n // slither-disable-end reentrancy-no-eth\n\n /// @notice get a list of all validator hashes present in the pending deposits\n /// list can have duplicate entries\n function _getPendingDepositValidatorHashes(uint256 depositsCount)\n internal\n view\n returns (bytes32[] memory validatorHashes)\n {\n validatorHashes = new bytes32[](depositsCount);\n for (uint256 i = 0; i < depositsCount; i++) {\n validatorHashes[i] = deposits[depositList[i]].pubKeyHash;\n }\n }\n\n /// @notice Hash a validator public key using the Beacon Chain's format\n function _hashPubKey(bytes memory pubKey) internal pure returns (bytes32) {\n require(pubKey.length == 48, \"Invalid public key\");\n return sha256(abi.encodePacked(pubKey, bytes16(0)));\n }\n\n /**\n *\n * WETH and ETH Accounting\n *\n */\n\n /// @dev Called when WETH is transferred out of the strategy so\n /// the strategy knows how much WETH it has on deposit.\n /// This is so it can emit the correct amount in the Deposit event in depositAll().\n function _transferWeth(uint256 _amount, address _recipient) internal {\n IERC20(WETH).safeTransfer(_recipient, _amount);\n\n // The min is required as more WETH can be withdrawn than deposited\n // as the strategy earns consensus and execution rewards.\n uint256 deductAmount = Math.min(_amount, depositedWethAccountedFor);\n depositedWethAccountedFor -= deductAmount;\n\n // No change in ETH balance so no need to snapshot the balances\n }\n\n /// @dev Converts ETH to WETH and updates the accounting.\n /// @param _ethAmount The amount of ETH in wei.\n function _convertEthToWeth(uint256 _ethAmount) internal {\n // slither-disable-next-line arbitrary-send-eth\n IWETH9(WETH).deposit{ value: _ethAmount }();\n\n depositedWethAccountedFor += _ethAmount;\n\n // Store the reduced ETH balance.\n // The ETH balance in this strategy contract can be more than the last verified ETH balance\n // due to partial withdrawals or full exits being processed by the beacon chain since the last snapBalances.\n // It can also happen from execution rewards (MEV) or ETH donations.\n lastVerifiedEthBalance -= Math.min(lastVerifiedEthBalance, _ethAmount);\n\n // The ETH balance was decreased to WETH so we need to invalidate the last balances snap.\n snappedBalance.timestamp = 0;\n }\n\n /// @dev Converts WETH to ETH and updates the accounting.\n /// @param _wethAmount The amount of WETH in wei.\n function _convertWethToEth(uint256 _wethAmount) internal {\n IWETH9(WETH).withdraw(_wethAmount);\n\n uint256 deductAmount = Math.min(_wethAmount, depositedWethAccountedFor);\n depositedWethAccountedFor -= deductAmount;\n\n // Store the increased ETH balance\n lastVerifiedEthBalance += _wethAmount;\n\n // The ETH balance was increased from WETH so we need to invalidate the last balances snap.\n snappedBalance.timestamp = 0;\n }\n\n /**\n *\n * View Functions\n *\n */\n\n /// @notice Returns the number of deposits waiting to be verified as processed on the beacon chain,\n /// or deposits that have been verified to an exiting validator and is now waiting for the\n /// validator's balance to be swept.\n function depositListLength() external view returns (uint256) {\n return depositList.length;\n }\n\n /// @notice Returns the number of verified validators.\n function verifiedValidatorsLength() external view returns (uint256) {\n return verifiedValidators.length;\n }\n}\n" + }, + "contracts/strategies/NativeStaking/FeeAccumulator.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { Address } from \"@openzeppelin/contracts/utils/Address.sol\";\n\n/**\n * @title Fee Accumulator for Native Staking SSV Strategy\n * @notice Receives execution rewards which includes tx fees and\n * MEV rewards like tx priority and tx ordering.\n * It does NOT include swept ETH from beacon chain consensus rewards or full validator withdrawals.\n * @author Origin Protocol Inc\n */\ncontract FeeAccumulator {\n /// @notice The address of the Native Staking Strategy\n address public immutable STRATEGY;\n\n event ExecutionRewardsCollected(address indexed strategy, uint256 amount);\n\n /**\n * @param _strategy Address of the Native Staking Strategy\n */\n constructor(address _strategy) {\n STRATEGY = _strategy;\n }\n\n /**\n * @notice sends all ETH in this FeeAccumulator contract to the Native Staking Strategy.\n * @return eth The amount of execution rewards that were sent to the Native Staking Strategy\n */\n function collect() external returns (uint256 eth) {\n require(msg.sender == STRATEGY, \"Caller is not the Strategy\");\n\n eth = address(this).balance;\n if (eth > 0) {\n // Send the ETH to the Native Staking Strategy\n Address.sendValue(payable(STRATEGY), eth);\n\n emit ExecutionRewardsCollected(STRATEGY, eth);\n }\n }\n\n /**\n * @dev Accept ETH\n */\n receive() external payable {}\n}\n" + }, + "contracts/strategies/NativeStaking/NativeStakingSSVStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport \"@openzeppelin/contracts/utils/math/Math.sol\";\n\nimport { InitializableAbstractStrategy } from \"../../utils/InitializableAbstractStrategy.sol\";\nimport { IWETH9 } from \"../../interfaces/IWETH9.sol\";\nimport { FeeAccumulator } from \"./FeeAccumulator.sol\";\nimport { ValidatorAccountant } from \"./ValidatorAccountant.sol\";\nimport { ISSVNetwork } from \"../../interfaces/ISSVNetwork.sol\";\n\nstruct ValidatorStakeData {\n bytes pubkey;\n bytes signature;\n bytes32 depositDataRoot;\n}\n\n/// @title Native Staking SSV Strategy\n/// @notice Strategy to deploy funds into DVT validators powered by the SSV Network\n/// @author Origin Protocol Inc\n/// @dev This contract handles WETH and ETH and in some operations interchanges between the two. Any WETH that\n/// is on the contract across multiple blocks (and not just transitory within a transaction) is considered an\n/// asset. Meaning deposits increase the balance of the asset and withdrawal decrease it. As opposed to all\n/// our other strategies the WETH doesn't immediately get deposited into an underlying strategy and can be present\n/// across multiple blocks waiting to be unwrapped to ETH and staked to validators. This separation of WETH and ETH is\n/// required since the rewards (reward token) is also in ETH.\n///\n/// To simplify the accounting of WETH there is another difference in behavior compared to the other strategies.\n/// To withdraw WETH asset - exit message is posted to validators and the ETH hits this contract with multiple days\n/// delay. In order to simplify the WETH accounting upon detection of such an event the ValidatorAccountant\n/// immediately wraps ETH to WETH and sends it to the Vault.\n///\n/// On the other hand any ETH on the contract (across multiple blocks) is there either:\n/// - as a result of already accounted for consensus rewards\n/// - as a result of not yet accounted for consensus rewards\n/// - as a results of not yet accounted for full validator withdrawals (or validator slashes)\n///\n/// Even though the strategy assets and rewards are a very similar asset the consensus layer rewards and the\n/// execution layer rewards are considered rewards and those are dripped to the Vault over a configurable time\n/// interval and not immediately.\ncontract NativeStakingSSVStrategy is\n ValidatorAccountant,\n InitializableAbstractStrategy\n{\n using SafeERC20 for IERC20;\n\n /// @notice SSV ERC20 token that serves as a payment for operating SSV validators\n address public immutable SSV_TOKEN;\n /// @notice Fee collector address\n /// @dev this address will receive maximal extractable value (MEV) rewards. These are\n /// rewards for arranging transactions in a way that benefits the validator.\n address payable public immutable FEE_ACCUMULATOR_ADDRESS;\n\n /// @dev This contract receives WETH as the deposit asset, but unlike other strategies doesn't immediately\n /// deposit it to an underlying platform. Rather a special privilege account stakes it to the validators.\n /// For that reason calling WETH.balanceOf(this) in a deposit function can contain WETH that has just been\n /// deposited and also WETH that has previously been deposited. To keep a correct count we need to keep track\n /// of WETH that has already been accounted for.\n /// This value represents the amount of WETH balance of this contract that has already been accounted for by the\n /// deposit events.\n /// It is important to note that this variable is not concerned with WETH that is a result of full/partial\n /// withdrawal of the validators. It is strictly concerned with WETH that has been deposited and is waiting to\n /// be staked.\n uint256 public depositedWethAccountedFor;\n\n // For future use\n uint256[49] private __gap;\n\n /// @param _baseConfig Base strategy config with platformAddress (ERC-4626 Vault contract), eg sfrxETH or sDAI,\n /// and vaultAddress (OToken Vault contract), eg VaultProxy or OETHVaultProxy\n /// @param _wethAddress Address of the Erc20 WETH Token contract\n /// @param _ssvToken Address of the Erc20 SSV Token contract\n /// @param _ssvNetwork Address of the SSV Network contract\n /// @param _maxValidators Maximum number of validators that can be registered in the strategy\n /// @param _feeAccumulator Address of the fee accumulator receiving execution layer validator rewards\n /// @param _beaconChainDepositContract Address of the beacon chain deposit contract\n constructor(\n BaseStrategyConfig memory _baseConfig,\n address _wethAddress,\n address _ssvToken,\n address _ssvNetwork,\n uint256 _maxValidators,\n address _feeAccumulator,\n address _beaconChainDepositContract\n )\n InitializableAbstractStrategy(_baseConfig)\n ValidatorAccountant(\n _wethAddress,\n _baseConfig.vaultAddress,\n _beaconChainDepositContract,\n _ssvNetwork,\n _maxValidators\n )\n {\n SSV_TOKEN = _ssvToken;\n FEE_ACCUMULATOR_ADDRESS = payable(_feeAccumulator);\n }\n\n /// @notice Set up initial internal state including\n /// 1. approving the SSVNetwork to transfer SSV tokens from this strategy contract\n /// 2. setting the recipient of SSV validator MEV rewards to the FeeAccumulator contract.\n /// @param _rewardTokenAddresses Address of reward token for platform\n /// @param _assets Addresses of initial supported assets\n /// @param _pTokens Platform Token corresponding addresses\n function initialize(\n address[] memory _rewardTokenAddresses,\n address[] memory _assets,\n address[] memory _pTokens\n ) external onlyGovernor initializer {\n InitializableAbstractStrategy._initialize(\n _rewardTokenAddresses,\n _assets,\n _pTokens\n );\n\n // Approves the SSV Network contract to transfer SSV tokens for deposits\n IERC20(SSV_TOKEN).approve(SSV_NETWORK, type(uint256).max);\n\n // Set the FeeAccumulator as the address for SSV validators to send MEV rewards to\n ISSVNetwork(SSV_NETWORK).setFeeRecipientAddress(\n FEE_ACCUMULATOR_ADDRESS\n );\n }\n\n /// @notice Unlike other strategies, this does not deposit assets into the underlying platform.\n /// It just checks the asset is WETH and emits the Deposit event.\n /// To deposit WETH into validators `registerSsvValidator` and `stakeEth` must be used.\n /// Will NOT revert if the strategy is paused from an accounting failure.\n /// @param _asset Address of asset to deposit. Has to be WETH.\n /// @param _amount Amount of assets that were transferred to the strategy by the vault.\n function deposit(address _asset, uint256 _amount)\n external\n override\n onlyVault\n nonReentrant\n {\n require(_asset == WETH, \"Unsupported asset\");\n depositedWethAccountedFor += _amount;\n _deposit(_asset, _amount);\n }\n\n /// @dev Deposit WETH to this strategy so it can later be staked into a validator.\n /// @param _asset Address of WETH\n /// @param _amount Amount of WETH to deposit\n function _deposit(address _asset, uint256 _amount) internal {\n require(_amount > 0, \"Must deposit something\");\n /*\n * We could do a check here that would revert when \"_amount % 32 ether != 0\". With the idea of\n * not allowing deposits that will result in WETH sitting on the strategy after all the possible batches\n * of 32ETH have been staked.\n * But someone could mess with our strategy by sending some WETH to it. And we might want to deposit just\n * enough WETH to add it up to 32 so it can be staked. For that reason the check is left out.\n *\n * WETH sitting on the strategy won't interfere with the accounting since accounting only operates on ETH.\n */\n emit Deposit(_asset, address(0), _amount);\n }\n\n /// @notice Unlike other strategies, this does not deposit assets into the underlying platform.\n /// It just emits the Deposit event.\n /// To deposit WETH into validators `registerSsvValidator` and `stakeEth` must be used.\n /// Will NOT revert if the strategy is paused from an accounting failure.\n function depositAll() external override onlyVault nonReentrant {\n uint256 wethBalance = IERC20(WETH).balanceOf(address(this));\n uint256 newWeth = wethBalance - depositedWethAccountedFor;\n\n if (newWeth > 0) {\n depositedWethAccountedFor = wethBalance;\n\n _deposit(WETH, newWeth);\n }\n }\n\n /// @notice Withdraw WETH from this contract. Used only if some WETH for is lingering on the contract.\n /// That can happen when:\n /// - after mints if the strategy is the default\n /// - time between depositToStrategy and stakeEth\n /// - the deposit was not a multiple of 32 WETH\n /// - someone sent WETH directly to this contract\n /// Will NOT revert if the strategy is paused from an accounting failure.\n /// @param _recipient Address to receive withdrawn assets\n /// @param _asset WETH to withdraw\n /// @param _amount Amount of WETH to withdraw\n function withdraw(\n address _recipient,\n address _asset,\n uint256 _amount\n ) external override onlyVault nonReentrant {\n require(_asset == WETH, \"Unsupported asset\");\n _withdraw(_recipient, _asset, _amount);\n }\n\n function _withdraw(\n address _recipient,\n address _asset,\n uint256 _amount\n ) internal {\n require(_amount > 0, \"Must withdraw something\");\n require(_recipient != address(0), \"Must specify recipient\");\n\n _wethWithdrawn(_amount);\n\n IERC20(_asset).safeTransfer(_recipient, _amount);\n emit Withdrawal(_asset, address(0), _amount);\n }\n\n /// @notice transfer all WETH deposits back to the vault.\n /// This does not withdraw from the validators. That has to be done separately with the\n /// `exitSsvValidator` and `removeSsvValidator` operations.\n /// This does not withdraw any execution rewards from the FeeAccumulator or\n /// consensus rewards in this strategy.\n /// Any ETH in this strategy that was swept from a full validator withdrawal will not be withdrawn.\n /// ETH from full validator withdrawals is sent to the Vault using `doAccounting`.\n /// Will NOT revert if the strategy is paused from an accounting failure.\n function withdrawAll() external override onlyVaultOrGovernor nonReentrant {\n uint256 wethBalance = IERC20(WETH).balanceOf(address(this));\n if (wethBalance > 0) {\n _withdraw(vaultAddress, WETH, wethBalance);\n }\n }\n\n /// @notice Returns the total value of (W)ETH that is staked to the validators\n /// and WETH deposits that are still to be staked.\n /// This does not include ETH from consensus rewards sitting in this strategy\n /// or ETH from MEV rewards in the FeeAccumulator. These rewards are harvested\n /// and sent to the Dripper so will eventually be sent to the Vault as WETH.\n /// @param _asset Address of weth asset\n /// @return balance Total value of (W)ETH\n function checkBalance(address _asset)\n external\n view\n override\n returns (uint256 balance)\n {\n require(_asset == WETH, \"Unsupported asset\");\n\n balance =\n // add the ETH that has been staked in validators\n activeDepositedValidators *\n FULL_STAKE +\n // add the WETH in the strategy from deposits that are still to be staked\n IERC20(WETH).balanceOf(address(this));\n }\n\n function pause() external onlyStrategist {\n _pause();\n }\n\n /// @notice Returns bool indicating whether asset is supported by strategy.\n /// @param _asset The address of the asset token.\n function supportsAsset(address _asset) public view override returns (bool) {\n return _asset == WETH;\n }\n\n /// @notice Approves the SSV Network contract to transfer SSV tokens for deposits\n function safeApproveAllTokens() external override {\n // Approves the SSV Network contract to transfer SSV tokens for deposits\n IERC20(SSV_TOKEN).approve(SSV_NETWORK, type(uint256).max);\n }\n\n /// @notice Set the FeeAccumulator as the address for SSV validators to send MEV rewards to\n function setFeeRecipient() external {\n ISSVNetwork(SSV_NETWORK).setFeeRecipientAddress(\n FEE_ACCUMULATOR_ADDRESS\n );\n }\n\n /**\n * @notice Only accept ETH from the FeeAccumulator and the WETH contract - required when\n * unwrapping WETH just before staking it to the validator.\n * The strategy will also receive ETH from the priority fees of transactions when producing blocks\n * as defined in EIP-1559.\n * The tx fees come from the Beacon chain so do not need any EVM level permissions to receive ETH.\n * The tx fees are paid with each block produced. They are not included in the consensus rewards\n * which are periodically swept from the validators to this strategy.\n * For accounting purposes, the priority fees of transactions will be considered consensus rewards\n * and will be included in the AccountingConsensusRewards event.\n * @dev don't want to receive donations from anyone else as donations over the fuse limits will\n * mess with the accounting of the consensus rewards and validator full withdrawals.\n */\n receive() external payable {\n require(\n msg.sender == FEE_ACCUMULATOR_ADDRESS || msg.sender == WETH,\n \"Eth not from allowed contracts\"\n );\n }\n\n /***************************************\n Internal functions\n ****************************************/\n\n function _abstractSetPToken(address _asset, address) internal override {}\n\n /// @dev Convert accumulated ETH to WETH and send to the Harvester.\n /// Will revert if the strategy is paused for accounting.\n function _collectRewardTokens() internal override whenNotPaused {\n // collect ETH from execution rewards from the fee accumulator\n uint256 executionRewards = FeeAccumulator(FEE_ACCUMULATOR_ADDRESS)\n .collect();\n\n // total ETH rewards to be harvested = execution rewards + consensus rewards\n uint256 ethRewards = executionRewards + consensusRewards;\n\n require(\n address(this).balance >= ethRewards,\n \"Insufficient eth balance\"\n );\n\n if (ethRewards > 0) {\n // reset the counter keeping track of beacon chain consensus rewards\n consensusRewards = 0;\n\n // Convert ETH rewards to WETH\n IWETH9(WETH).deposit{ value: ethRewards }();\n\n IERC20(WETH).safeTransfer(harvesterAddress, ethRewards);\n emit RewardTokenCollected(harvesterAddress, WETH, ethRewards);\n }\n }\n\n /// @dev emits Withdrawal event from NativeStakingSSVStrategy\n function _wethWithdrawnToVault(uint256 _amount) internal override {\n emit Withdrawal(WETH, address(0), _amount);\n }\n\n /// @dev Called when WETH is withdrawn from the strategy or staked to a validator so\n /// the strategy knows how much WETH it has on deposit.\n /// This is so it can emit the correct amount in the Deposit event in depositAll().\n function _wethWithdrawn(uint256 _amount) internal override {\n /* In an ideal world we wouldn't need to reduce the deduction amount when the\n * depositedWethAccountedFor is smaller than the _amount.\n *\n * The reason this is required is that a malicious actor could sent WETH directly\n * to this contract and that would circumvent the increase of depositedWethAccountedFor\n * property. When the ETH would be staked the depositedWethAccountedFor amount could\n * be deducted so much that it would be negative.\n */\n uint256 deductAmount = Math.min(_amount, depositedWethAccountedFor);\n depositedWethAccountedFor -= deductAmount;\n }\n}\n" + }, + "contracts/strategies/NativeStaking/ValidatorAccountant.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { ValidatorRegistrator } from \"./ValidatorRegistrator.sol\";\nimport { IWETH9 } from \"../../interfaces/IWETH9.sol\";\n\n/// @title Validator Accountant\n/// @notice Attributes the ETH swept from beacon chain validators to this strategy contract\n/// as either full or partial withdrawals. Partial withdrawals being consensus rewards.\n/// Full withdrawals are from exited validators.\n/// @author Origin Protocol Inc\nabstract contract ValidatorAccountant is ValidatorRegistrator {\n /// @notice The minimum amount of blocks that need to pass between two calls to manuallyFixAccounting\n uint256 public constant MIN_FIX_ACCOUNTING_CADENCE = 7200; // 1 day\n\n /// @notice Keeps track of the total consensus rewards swept from the beacon chain\n uint256 public consensusRewards;\n\n /// @notice start of fuse interval\n uint256 public fuseIntervalStart;\n /// @notice end of fuse interval\n uint256 public fuseIntervalEnd;\n /// @notice last block number manuallyFixAccounting has been called\n uint256 public lastFixAccountingBlockNumber;\n\n uint256[49] private __gap;\n\n event FuseIntervalUpdated(uint256 start, uint256 end);\n event AccountingFullyWithdrawnValidator(\n uint256 noOfValidators,\n uint256 remainingValidators,\n uint256 wethSentToVault\n );\n event AccountingValidatorSlashed(\n uint256 remainingValidators,\n uint256 wethSentToVault\n );\n event AccountingConsensusRewards(uint256 amount);\n\n event AccountingManuallyFixed(\n int256 validatorsDelta,\n int256 consensusRewardsDelta,\n uint256 wethToVault\n );\n\n /// @param _wethAddress Address of the Erc20 WETH Token contract\n /// @param _vaultAddress Address of the Vault\n /// @param _beaconChainDepositContract Address of the beacon chain deposit contract\n /// @param _ssvNetwork Address of the SSV Network contract\n /// @param _maxValidators Maximum number of validators that can be registered in the strategy\n constructor(\n address _wethAddress,\n address _vaultAddress,\n address _beaconChainDepositContract,\n address _ssvNetwork,\n uint256 _maxValidators\n )\n ValidatorRegistrator(\n _wethAddress,\n _vaultAddress,\n _beaconChainDepositContract,\n _ssvNetwork,\n _maxValidators\n )\n {}\n\n /// @notice set fuse interval values\n function setFuseInterval(\n uint256 _fuseIntervalStart,\n uint256 _fuseIntervalEnd\n ) external onlyGovernor {\n require(\n _fuseIntervalStart < _fuseIntervalEnd &&\n _fuseIntervalEnd < 32 ether &&\n _fuseIntervalEnd - _fuseIntervalStart >= 4 ether,\n \"Incorrect fuse interval\"\n );\n\n fuseIntervalStart = _fuseIntervalStart;\n fuseIntervalEnd = _fuseIntervalEnd;\n\n emit FuseIntervalUpdated(_fuseIntervalStart, _fuseIntervalEnd);\n }\n\n /* solhint-disable max-line-length */\n /// This notion page offers a good explanation of how the accounting functions\n /// https://www.notion.so/originprotocol/Limited-simplified-native-staking-accounting-67a217c8420d40678eb943b9da0ee77d\n /// In short, after dividing by 32, if the ETH remaining on the contract falls between 0 and fuseIntervalStart,\n /// the accounting function will treat that ETH as Beacon chain consensus rewards.\n /// On the contrary, if after dividing by 32, the ETH remaining on the contract falls between fuseIntervalEnd and 32,\n /// the accounting function will treat that as a validator slashing.\n /// @notice Perform the accounting attributing beacon chain ETH to either full or partial withdrawals. Returns true when\n /// accounting is valid and fuse isn't \"blown\". Returns false when fuse is blown.\n /// @dev This function could in theory be permission-less but lets allow only the Registrator (Defender Action) to call it\n /// for now.\n /// @return accountingValid true if accounting was successful, false if fuse is blown\n /* solhint-enable max-line-length */\n function doAccounting()\n external\n onlyRegistrator\n whenNotPaused\n nonReentrant\n returns (bool accountingValid)\n {\n // pause the accounting on failure\n accountingValid = _doAccounting(true);\n }\n\n // slither-disable-start reentrancy-eth\n function _doAccounting(bool pauseOnFail)\n internal\n returns (bool accountingValid)\n {\n if (address(this).balance < consensusRewards) {\n return _failAccounting(pauseOnFail);\n }\n\n // Calculate all the new ETH that has been swept to the contract since the last accounting\n uint256 newSweptETH = address(this).balance - consensusRewards;\n accountingValid = true;\n\n // send the ETH that is from fully withdrawn validators to the Vault\n if (newSweptETH >= FULL_STAKE) {\n uint256 fullyWithdrawnValidators;\n // explicitly cast to uint256 as we want to round to a whole number of validators\n fullyWithdrawnValidators = uint256(newSweptETH / FULL_STAKE);\n activeDepositedValidators -= fullyWithdrawnValidators;\n\n uint256 wethToVault = FULL_STAKE * fullyWithdrawnValidators;\n IWETH9(WETH).deposit{ value: wethToVault }();\n // slither-disable-next-line unchecked-transfer\n IWETH9(WETH).transfer(VAULT_ADDRESS, wethToVault);\n _wethWithdrawnToVault(wethToVault);\n\n emit AccountingFullyWithdrawnValidator(\n fullyWithdrawnValidators,\n activeDepositedValidators,\n wethToVault\n );\n }\n\n uint256 ethRemaining = address(this).balance - consensusRewards;\n // should be less than a whole validator stake\n require(ethRemaining < FULL_STAKE, \"Unexpected accounting\");\n\n // If no Beacon chain consensus rewards swept\n if (ethRemaining == 0) {\n // do nothing\n return accountingValid;\n } else if (ethRemaining < fuseIntervalStart) {\n // Beacon chain consensus rewards swept (partial validator withdrawals)\n // solhint-disable-next-line reentrancy\n consensusRewards += ethRemaining;\n emit AccountingConsensusRewards(ethRemaining);\n } else if (ethRemaining > fuseIntervalEnd) {\n // Beacon chain consensus rewards swept but also a slashed validator fully exited\n IWETH9(WETH).deposit{ value: ethRemaining }();\n // slither-disable-next-line unchecked-transfer\n IWETH9(WETH).transfer(VAULT_ADDRESS, ethRemaining);\n activeDepositedValidators -= 1;\n\n _wethWithdrawnToVault(ethRemaining);\n\n emit AccountingValidatorSlashed(\n activeDepositedValidators,\n ethRemaining\n );\n }\n // Oh no... Fuse is blown. The Strategist needs to adjust the accounting values.\n else {\n return _failAccounting(pauseOnFail);\n }\n }\n\n // slither-disable-end reentrancy-eth\n\n /// @dev pause any further accounting if required and return false\n function _failAccounting(bool pauseOnFail)\n internal\n returns (bool accountingValid)\n {\n // pause if not already\n if (pauseOnFail) {\n _pause();\n }\n // fail the accounting\n accountingValid = false;\n }\n\n /// @notice Allow the Strategist to fix the accounting of this strategy and unpause.\n /// @param _validatorsDelta adjust the active validators by up to plus three or minus three\n /// @param _consensusRewardsDelta adjust the accounted for consensus rewards up or down\n /// @param _ethToVaultAmount the amount of ETH that gets wrapped into WETH and sent to the Vault\n /// @dev There is a case when a validator(s) gets slashed so much that the eth swept from\n /// the beacon chain enters the fuse area and there are no consensus rewards on the contract\n /// to \"dip into\"/use. To increase the amount of unaccounted ETH over the fuse end interval\n /// we need to reduce the amount of active deposited validators and immediately send WETH\n /// to the vault, so it doesn't interfere with further accounting.\n function manuallyFixAccounting(\n int256 _validatorsDelta,\n int256 _consensusRewardsDelta,\n uint256 _ethToVaultAmount\n ) external onlyStrategist whenPaused nonReentrant {\n require(\n lastFixAccountingBlockNumber + MIN_FIX_ACCOUNTING_CADENCE <\n block.number,\n \"Fix accounting called too soon\"\n );\n require(\n _validatorsDelta >= -3 &&\n _validatorsDelta <= 3 &&\n // new value must be positive\n int256(activeDepositedValidators) + _validatorsDelta >= 0,\n \"Invalid validatorsDelta\"\n );\n require(\n _consensusRewardsDelta >= -332 ether &&\n _consensusRewardsDelta <= 332 ether &&\n // new value must be positive\n int256(consensusRewards) + _consensusRewardsDelta >= 0,\n \"Invalid consensusRewardsDelta\"\n );\n require(_ethToVaultAmount <= 32 ether * 3, \"Invalid wethToVaultAmount\");\n\n activeDepositedValidators = uint256(\n int256(activeDepositedValidators) + _validatorsDelta\n );\n consensusRewards = uint256(\n int256(consensusRewards) + _consensusRewardsDelta\n );\n lastFixAccountingBlockNumber = block.number;\n if (_ethToVaultAmount > 0) {\n IWETH9(WETH).deposit{ value: _ethToVaultAmount }();\n // slither-disable-next-line unchecked-transfer\n IWETH9(WETH).transfer(VAULT_ADDRESS, _ethToVaultAmount);\n _wethWithdrawnToVault(_ethToVaultAmount);\n }\n\n emit AccountingManuallyFixed(\n _validatorsDelta,\n _consensusRewardsDelta,\n _ethToVaultAmount\n );\n\n // rerun the accounting to see if it has now been fixed.\n // Do not pause the accounting on failure as it is already paused\n require(_doAccounting(false), \"Fuse still blown\");\n\n // unpause since doAccounting was successful\n _unpause();\n }\n\n /***************************************\n Abstract\n ****************************************/\n\n /// @dev allows for NativeStakingSSVStrategy contract to emit the Withdrawal event\n function _wethWithdrawnToVault(uint256 _amount) internal virtual;\n}\n" + }, + "contracts/strategies/NativeStaking/ValidatorRegistrator.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { Pausable } from \"@openzeppelin/contracts/security/Pausable.sol\";\nimport { Governable } from \"../../governance/Governable.sol\";\nimport { IDepositContract } from \"../../interfaces/IDepositContract.sol\";\nimport { IVault } from \"../../interfaces/IVault.sol\";\nimport { IWETH9 } from \"../../interfaces/IWETH9.sol\";\nimport { ISSVNetwork, Cluster } from \"../../interfaces/ISSVNetwork.sol\";\n\nstruct ValidatorStakeData {\n bytes pubkey;\n bytes signature;\n bytes32 depositDataRoot;\n}\n\n/**\n * @title Registrator of the validators\n * @notice This contract implements all the required functionality to register, exit and remove validators.\n * @author Origin Protocol Inc\n */\nabstract contract ValidatorRegistrator is Governable, Pausable {\n /// @notice The maximum amount of ETH that can be staked by a validator\n /// @dev this can change in the future with EIP-7251, Increase the MAX_EFFECTIVE_BALANCE\n uint256 public constant FULL_STAKE = 32 ether;\n\n /// @notice The address of the Wrapped ETH (WETH) token contract\n address public immutable WETH;\n /// @notice The address of the beacon chain deposit contract\n address public immutable BEACON_CHAIN_DEPOSIT_CONTRACT;\n /// @notice The address of the SSV Network contract used to interface with\n address public immutable SSV_NETWORK;\n /// @notice Address of the OETH Vault proxy contract\n address public immutable VAULT_ADDRESS;\n /// @notice Maximum number of validators that can be registered in this strategy\n uint256 public immutable MAX_VALIDATORS;\n\n /// @notice Address of the registrator - allowed to register, exit and remove validators\n address public validatorRegistrator;\n /// @notice The number of validators that have 32 (!) ETH actively deposited. When a new deposit\n /// to a validator happens this number increases, when a validator exit is detected this number\n /// decreases.\n uint256 public activeDepositedValidators;\n /// @notice State of the validators keccak256(pubKey) => state\n mapping(bytes32 => VALIDATOR_STATE) public validatorsStates;\n /// @notice The account that is allowed to modify stakeETHThreshold and reset stakeETHTally\n address public stakingMonitor;\n /// @notice Amount of ETH that can be staked before staking on the contract is suspended\n /// and the `stakingMonitor` needs to approve further staking by calling `resetStakeETHTally`\n uint256 public stakeETHThreshold;\n /// @notice Amount of ETH that has been staked since the `stakingMonitor` last called `resetStakeETHTally`.\n /// This can not go above `stakeETHThreshold`.\n uint256 public stakeETHTally;\n // For future use\n uint256[47] private __gap;\n\n enum VALIDATOR_STATE {\n NON_REGISTERED, // validator is not registered on the SSV network\n REGISTERED, // validator is registered on the SSV network\n STAKED, // validator has funds staked\n EXITING, // exit message has been posted and validator is in the process of exiting\n EXIT_COMPLETE // validator has funds withdrawn to the EigenPod and is removed from the SSV\n }\n\n event RegistratorChanged(address indexed newAddress);\n event StakingMonitorChanged(address indexed newAddress);\n event ETHStaked(bytes32 indexed pubKeyHash, bytes pubKey, uint256 amount);\n event SSVValidatorRegistered(\n bytes32 indexed pubKeyHash,\n bytes pubKey,\n uint64[] operatorIds\n );\n event SSVValidatorExitInitiated(\n bytes32 indexed pubKeyHash,\n bytes pubKey,\n uint64[] operatorIds\n );\n event SSVValidatorExitCompleted(\n bytes32 indexed pubKeyHash,\n bytes pubKey,\n uint64[] operatorIds\n );\n event StakeETHThresholdChanged(uint256 amount);\n event StakeETHTallyReset();\n\n /// @dev Throws if called by any account other than the Registrator\n modifier onlyRegistrator() {\n require(\n msg.sender == validatorRegistrator,\n \"Caller is not the Registrator\"\n );\n _;\n }\n\n /// @dev Throws if called by any account other than the Staking monitor\n modifier onlyStakingMonitor() {\n require(msg.sender == stakingMonitor, \"Caller is not the Monitor\");\n _;\n }\n\n /// @dev Throws if called by any account other than the Strategist\n modifier onlyStrategist() {\n require(\n msg.sender == IVault(VAULT_ADDRESS).strategistAddr(),\n \"Caller is not the Strategist\"\n );\n _;\n }\n\n /// @param _wethAddress Address of the Erc20 WETH Token contract\n /// @param _vaultAddress Address of the Vault\n /// @param _beaconChainDepositContract Address of the beacon chain deposit contract\n /// @param _ssvNetwork Address of the SSV Network contract\n /// @param _maxValidators Maximum number of validators that can be registered in the strategy\n constructor(\n address _wethAddress,\n address _vaultAddress,\n address _beaconChainDepositContract,\n address _ssvNetwork,\n uint256 _maxValidators\n ) {\n WETH = _wethAddress;\n BEACON_CHAIN_DEPOSIT_CONTRACT = _beaconChainDepositContract;\n SSV_NETWORK = _ssvNetwork;\n VAULT_ADDRESS = _vaultAddress;\n MAX_VALIDATORS = _maxValidators;\n }\n\n /// @notice Set the address of the registrator which can register, exit and remove validators\n function setRegistrator(address _address) external onlyGovernor {\n validatorRegistrator = _address;\n emit RegistratorChanged(_address);\n }\n\n /// @notice Set the address of the staking monitor that is allowed to reset stakeETHTally\n function setStakingMonitor(address _address) external onlyGovernor {\n stakingMonitor = _address;\n emit StakingMonitorChanged(_address);\n }\n\n /// @notice Set the amount of ETH that can be staked before staking monitor\n // needs to a approve further staking by resetting the stake ETH tally\n function setStakeETHThreshold(uint256 _amount) external onlyGovernor {\n stakeETHThreshold = _amount;\n emit StakeETHThresholdChanged(_amount);\n }\n\n /// @notice Reset the stakeETHTally\n function resetStakeETHTally() external onlyStakingMonitor {\n stakeETHTally = 0;\n emit StakeETHTallyReset();\n }\n\n /// @notice Stakes WETH to the node validators\n /// @param validators A list of validator data needed to stake.\n /// The `ValidatorStakeData` struct contains the pubkey, signature and depositDataRoot.\n /// Only the registrator can call this function.\n // slither-disable-start reentrancy-eth\n function stakeEth(ValidatorStakeData[] calldata validators)\n external\n onlyRegistrator\n whenNotPaused\n nonReentrant\n {\n uint256 requiredETH = validators.length * FULL_STAKE;\n\n // Check there is enough WETH from the deposits sitting in this strategy contract\n require(\n requiredETH <= IWETH9(WETH).balanceOf(address(this)),\n \"Insufficient WETH\"\n );\n require(\n activeDepositedValidators + validators.length <= MAX_VALIDATORS,\n \"Max validators reached\"\n );\n\n require(\n stakeETHTally + requiredETH <= stakeETHThreshold,\n \"Staking ETH over threshold\"\n );\n stakeETHTally += requiredETH;\n\n // Convert required ETH from WETH\n IWETH9(WETH).withdraw(requiredETH);\n _wethWithdrawn(requiredETH);\n\n /* 0x01 to indicate that withdrawal credentials will contain an EOA address that the sweeping function\n * can sweep funds to.\n * bytes11(0) to fill up the required zeros\n * remaining bytes20 are for the address\n */\n bytes memory withdrawalCredentials = abi.encodePacked(\n bytes1(0x01),\n bytes11(0),\n address(this)\n );\n\n // For each validator\n for (uint256 i = 0; i < validators.length; ++i) {\n bytes32 pubKeyHash = keccak256(validators[i].pubkey);\n\n require(\n validatorsStates[pubKeyHash] == VALIDATOR_STATE.REGISTERED,\n \"Validator not registered\"\n );\n\n IDepositContract(BEACON_CHAIN_DEPOSIT_CONTRACT).deposit{\n value: FULL_STAKE\n }(\n validators[i].pubkey,\n withdrawalCredentials,\n validators[i].signature,\n validators[i].depositDataRoot\n );\n\n validatorsStates[pubKeyHash] = VALIDATOR_STATE.STAKED;\n\n emit ETHStaked(pubKeyHash, validators[i].pubkey, FULL_STAKE);\n }\n // save gas by changing this storage variable only once rather each time in the loop.\n activeDepositedValidators += validators.length;\n }\n\n // slither-disable-end reentrancy-eth\n\n /// @notice Registers a new validator in the SSV Cluster.\n /// Only the registrator can call this function.\n /// @param publicKeys The public keys of the validators\n /// @param operatorIds The operator IDs of the SSV Cluster\n /// @param sharesData The shares data for each validator\n /// @param ssvAmount The amount of SSV tokens to be deposited to the SSV cluster\n /// @param cluster The SSV cluster details including the validator count and SSV balance\n // slither-disable-start reentrancy-no-eth\n function registerSsvValidators(\n bytes[] calldata publicKeys,\n uint64[] calldata operatorIds,\n bytes[] calldata sharesData,\n uint256 ssvAmount,\n Cluster calldata cluster\n ) external onlyRegistrator whenNotPaused {\n require(\n publicKeys.length == sharesData.length,\n \"Pubkey sharesData mismatch\"\n );\n // Check each public key has not already been used\n bytes32 pubKeyHash;\n VALIDATOR_STATE currentState;\n for (uint256 i = 0; i < publicKeys.length; ++i) {\n pubKeyHash = keccak256(publicKeys[i]);\n currentState = validatorsStates[pubKeyHash];\n require(\n currentState == VALIDATOR_STATE.NON_REGISTERED,\n \"Validator already registered\"\n );\n\n validatorsStates[pubKeyHash] = VALIDATOR_STATE.REGISTERED;\n\n emit SSVValidatorRegistered(pubKeyHash, publicKeys[i], operatorIds);\n }\n\n ISSVNetwork(SSV_NETWORK).bulkRegisterValidator(\n publicKeys,\n operatorIds,\n sharesData,\n ssvAmount,\n cluster\n );\n }\n\n // slither-disable-end reentrancy-no-eth\n\n /// @notice Exit a validator from the Beacon chain.\n /// The staked ETH will eventually swept to this native staking strategy.\n /// Only the registrator can call this function.\n /// @param publicKey The public key of the validator\n /// @param operatorIds The operator IDs of the SSV Cluster\n // slither-disable-start reentrancy-no-eth\n function exitSsvValidator(\n bytes calldata publicKey,\n uint64[] calldata operatorIds\n ) external onlyRegistrator whenNotPaused {\n bytes32 pubKeyHash = keccak256(publicKey);\n VALIDATOR_STATE currentState = validatorsStates[pubKeyHash];\n require(currentState == VALIDATOR_STATE.STAKED, \"Validator not staked\");\n\n ISSVNetwork(SSV_NETWORK).exitValidator(publicKey, operatorIds);\n\n validatorsStates[pubKeyHash] = VALIDATOR_STATE.EXITING;\n\n emit SSVValidatorExitInitiated(pubKeyHash, publicKey, operatorIds);\n }\n\n // slither-disable-end reentrancy-no-eth\n\n /// @notice Remove a validator from the SSV Cluster.\n /// Make sure `exitSsvValidator` is called before and the validate has exited the Beacon chain.\n /// If removed before the validator has exited the beacon chain will result in the validator being slashed.\n /// Only the registrator can call this function.\n /// @param publicKey The public key of the validator\n /// @param operatorIds The operator IDs of the SSV Cluster\n /// @param cluster The SSV cluster details including the validator count and SSV balance\n // slither-disable-start reentrancy-no-eth\n function removeSsvValidator(\n bytes calldata publicKey,\n uint64[] calldata operatorIds,\n Cluster calldata cluster\n ) external onlyRegistrator whenNotPaused {\n bytes32 pubKeyHash = keccak256(publicKey);\n VALIDATOR_STATE currentState = validatorsStates[pubKeyHash];\n // Can remove SSV validators that were incorrectly registered and can not be deposited to.\n require(\n currentState == VALIDATOR_STATE.EXITING ||\n currentState == VALIDATOR_STATE.REGISTERED,\n \"Validator not regd or exiting\"\n );\n\n ISSVNetwork(SSV_NETWORK).removeValidator(\n publicKey,\n operatorIds,\n cluster\n );\n\n validatorsStates[pubKeyHash] = VALIDATOR_STATE.EXIT_COMPLETE;\n\n emit SSVValidatorExitCompleted(pubKeyHash, publicKey, operatorIds);\n }\n\n // slither-disable-end reentrancy-no-eth\n\n /// @notice Deposits more SSV Tokens to the SSV Network contract which is used to pay the SSV Operators.\n /// @dev A SSV cluster is defined by the SSVOwnerAddress and the set of operatorIds.\n /// uses \"onlyStrategist\" modifier so continuous front-running can't DOS our maintenance service\n /// that tries to top up SSV tokens.\n /// @param operatorIds The operator IDs of the SSV Cluster\n /// @param ssvAmount The amount of SSV tokens to be deposited to the SSV cluster\n /// @param cluster The SSV cluster details including the validator count and SSV balance\n function depositSSV(\n uint64[] memory operatorIds,\n uint256 ssvAmount,\n Cluster memory cluster\n ) external onlyStrategist {\n ISSVNetwork(SSV_NETWORK).deposit(\n address(this),\n operatorIds,\n ssvAmount,\n cluster\n );\n }\n\n /// @notice Withdraws excess SSV Tokens from the SSV Network contract which was used to pay the SSV Operators.\n /// @dev A SSV cluster is defined by the SSVOwnerAddress and the set of operatorIds.\n /// @param operatorIds The operator IDs of the SSV Cluster\n /// @param ssvAmount The amount of SSV tokens to be deposited to the SSV cluster\n /// @param cluster The SSV cluster details including the validator count and SSV balance\n function withdrawSSV(\n uint64[] memory operatorIds,\n uint256 ssvAmount,\n Cluster memory cluster\n ) external onlyGovernor {\n ISSVNetwork(SSV_NETWORK).withdraw(operatorIds, ssvAmount, cluster);\n }\n\n /***************************************\n Abstract\n ****************************************/\n\n /// @dev Called when WETH is withdrawn from the strategy or staked to a validator so\n /// the strategy knows how much WETH it has on deposit.\n /// This is so it can emit the correct amount in the Deposit event in depositAll().\n function _wethWithdrawn(uint256 _amount) internal virtual;\n}\n" + }, + "contracts/strategies/plume/RoosterAMOStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title Rooster AMO strategy\n * @author Origin Protocol Inc\n * @custom:security-contact security@originprotocol.com\n */\nimport { Math as MathRooster } from \"../../../lib/rooster/v2-common/libraries/Math.sol\";\nimport { Math as Math_v5 } from \"../../../lib/rooster/openzeppelin-custom/contracts/utils/math/Math.sol\";\nimport { StableMath } from \"../../utils/StableMath.sol\";\n\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport { SafeCast } from \"@openzeppelin/contracts/utils/math/SafeCast.sol\";\n\nimport { IERC20, InitializableAbstractStrategy } from \"../../utils/InitializableAbstractStrategy.sol\";\nimport { IVault } from \"../../interfaces/IVault.sol\";\nimport { IMaverickV2Pool } from \"../../interfaces/plume/IMaverickV2Pool.sol\";\nimport { IMaverickV2Quoter } from \"../../interfaces/plume/IMaverickV2Quoter.sol\";\nimport { IMaverickV2LiquidityManager } from \"../../interfaces/plume/IMaverickV2LiquidityManager.sol\";\nimport { IMaverickV2PoolLens } from \"../../interfaces/plume/IMaverickV2PoolLens.sol\";\nimport { IMaverickV2Position } from \"../../interfaces/plume/IMaverickV2Position.sol\";\nimport { IVotingDistributor } from \"../../interfaces/plume/IVotingDistributor.sol\";\nimport { IPoolDistributor } from \"../../interfaces/plume/IPoolDistributor.sol\";\n// importing custom version of rooster TickMath because of dependency collision. Maverick uses\n// a newer OpenZepplin Math library with functionality that is not present in 4.4.2 (the one we use)\nimport { TickMath } from \"../../../lib/rooster/v2-common/libraries/TickMath.sol\";\n\ncontract RoosterAMOStrategy is InitializableAbstractStrategy {\n using StableMath for uint256;\n using SafeERC20 for IERC20;\n using SafeCast for uint256;\n\n /***************************************\n Storage slot members\n ****************************************/\n\n /// @notice NFT tokenId of the liquidity position\n ///\n /// @dev starts with value of 1 and can not be 0\n // solhint-disable-next-line max-line-length\n /// https://github.com/rooster-protocol/rooster-contracts/blob/fbfecbc519e4495b12598024a42630b4a8ea4489/v2-common/contracts/base/Nft.sol#L14\n uint256 public tokenId;\n /// @dev Minimum amount of tokens the strategy would be able to withdraw from the pool.\n /// minimum amount of tokens are withdrawn at a 1:1 price\n /// Important: Underlying assets contains only assets that are deposited in the underlying Rooster pool.\n /// WETH or OETH held by this contract is not accounted for in underlying assets\n uint256 public underlyingAssets;\n /// @notice Marks the start of the interval that defines the allowed range of WETH share in\n /// the pre-configured pool's liquidity ticker\n uint256 public allowedWethShareStart;\n /// @notice Marks the end of the interval that defines the allowed range of WETH share in\n /// the pre-configured pool's liquidity ticker\n uint256 public allowedWethShareEnd;\n /// @dev reserved for inheritance\n int256[46] private __reserved;\n\n /***************************************\n Constants, structs and events\n ****************************************/\n\n /// @notice The address of the Wrapped ETH (WETH) token contract\n address public immutable WETH;\n /// @notice The address of the OETH token contract\n address public immutable OETH;\n /// @notice the underlying AMO Maverick (Rooster) pool\n IMaverickV2Pool public immutable mPool;\n /// @notice the Liquidity manager used to add liquidity to the pool\n IMaverickV2LiquidityManager public immutable liquidityManager;\n /// @notice the Maverick V2 poolLens\n ///\n /// @dev only used to provide the pool's current sqrtPrice\n IMaverickV2PoolLens public immutable poolLens;\n /// @notice the Maverick V2 position\n ///\n /// @dev provides details of the NFT LP position and offers functions to\n /// remove the liquidity.\n IMaverickV2Position public immutable maverickPosition;\n /// @notice the Maverick Quoter\n IMaverickV2Quoter public immutable quoter;\n /// @notice the Maverick Voting Distributor\n IVotingDistributor public immutable votingDistributor;\n /// @notice the Maverick Pool Distributor\n IPoolDistributor public immutable poolDistributor;\n\n /// @notice sqrtPriceTickLower\n /// @dev tick lower represents the lower price of OETH priced in WETH. Meaning the pool\n /// offers more than 1 OETH for 1 WETH. In other terms to get 1 OETH the swap needs to offer 0.9999 WETH\n /// this is where purchasing OETH with WETH within the liquidity position is the cheapest.\n ///\n /// _____________________\n /// | | |\n /// | WETH | OETH |\n /// | | |\n /// | | |\n /// --------- * ---- * ---------- * ---------\n /// currentPrice\n /// sqrtPriceHigher-(1:1 parity)\n /// sqrtPriceLower\n ///\n ///\n /// Price is defined as price of token1 in terms of token0. (token1 / token0)\n /// @notice sqrtPriceTickLower - OETH is priced 0.9999 WETH\n uint256 public immutable sqrtPriceTickLower;\n /// @notice sqrtPriceTickHigher\n /// @dev tick higher represents 1:1 price parity of WETH to OETH\n uint256 public immutable sqrtPriceTickHigher;\n /// @dev price at parity (in OETH this is equal to sqrtPriceTickHigher)\n uint256 public immutable sqrtPriceAtParity;\n /// @notice The tick where the strategy deploys the liquidity to\n int32 public constant TICK_NUMBER = -1;\n /// @notice Minimum liquidity that must be exceeded to continue with the action\n /// e.g. deposit, add liquidity\n uint256 public constant ACTION_THRESHOLD = 1e12;\n /// @notice Maverick pool static liquidity bin type\n uint8 public constant MAV_STATIC_BIN_KIND = 0;\n /// @dev a threshold under which the contract no longer allows for the protocol to rebalance. Guarding\n /// against a strategist / guardian being taken over and with multiple transactions draining the\n /// protocol funds.\n uint256 public constant SOLVENCY_THRESHOLD = 0.998 ether;\n /// @notice Emitted when the allowed interval within which the strategy contract is allowed to deposit\n /// liquidity to the underlying pool is updated.\n /// @param allowedWethShareStart The start of the interval\n /// @param allowedWethShareEnd The end of the interval\n event PoolWethShareIntervalUpdated(\n uint256 allowedWethShareStart,\n uint256 allowedWethShareEnd\n );\n /// @notice Emitted when liquidity is removed from the underlying pool\n /// @param withdrawLiquidityShare Share of strategy's liquidity that has been removed\n /// @param removedWETHAmount The amount of WETH removed\n /// @param removedOETHAmount The amount of OETH removed\n /// @param underlyingAssets Updated amount of strategy's underlying assets\n event LiquidityRemoved(\n uint256 withdrawLiquidityShare,\n uint256 removedWETHAmount,\n uint256 removedOETHAmount,\n uint256 underlyingAssets\n );\n\n /// @notice Emitted when the underlying pool is rebalanced\n /// @param currentPoolWethShare The resulting share of strategy's liquidity\n /// in the TICK_NUMBER\n event PoolRebalanced(uint256 currentPoolWethShare);\n\n /// @notice Emitted when the amount of underlying assets the strategy hold as\n /// liquidity in the pool is updated.\n /// @param underlyingAssets Updated amount of strategy's underlying assets\n event UnderlyingAssetsUpdated(uint256 underlyingAssets);\n\n /// @notice Emitted when liquidity is added to the underlying pool\n /// @param wethAmountDesired Amount of WETH desired to be deposited\n /// @param oethAmountDesired Amount of OETH desired to be deposited\n /// @param wethAmountSupplied Amount of WETH deposited\n /// @param oethAmountSupplied Amount of OETH deposited\n /// @param tokenId NFT liquidity token id\n /// @param underlyingAssets Updated amount of underlying assets\n event LiquidityAdded(\n uint256 wethAmountDesired,\n uint256 oethAmountDesired,\n uint256 wethAmountSupplied,\n uint256 oethAmountSupplied,\n uint256 tokenId,\n uint256 underlyingAssets\n ); // 0x1530ec74\n\n error PoolRebalanceOutOfBounds(\n uint256 currentPoolWethShare,\n uint256 allowedWethShareStart,\n uint256 allowedWethShareEnd\n ); // 0x3681e8e0\n\n error NotEnoughWethForSwap(uint256 wethBalance, uint256 requiredWeth); // 0x989e5ca8\n error NotEnoughWethLiquidity(uint256 wethBalance, uint256 requiredWeth); // 0xa6737d87\n error OutsideExpectedTickRange(); // 0xa6e1bad2\n error SlippageCheck(uint256 tokenReceived); // 0x355cdb78\n\n /// @notice the constructor\n /// @dev This contract is intended to be used as a proxy. To prevent the\n /// potential confusion of having a functional implementation contract\n /// the constructor has the `initializer` modifier. This way the\n /// `initialize` function can not be called on the implementation contract.\n /// For the same reason the implementation contract also has the governor\n /// set to a zero address.\n /// @param _stratConfig the basic strategy configuration\n /// @param _wethAddress Address of the Erc20 WETH Token contract\n /// @param _oethAddress Address of the Erc20 OETH Token contract\n /// @param _liquidityManager Address of liquidity manager to add\n /// the liquidity\n /// @param _poolLens Address of the pool lens contract\n /// @param _maverickPosition Address of the Maverick's position contract\n /// @param _maverickQuoter Address of the Maverick's Quoter contract\n /// @param _mPool Address of the Rooster concentrated liquidity pool\n /// @param _upperTickAtParity Bool when true upperTick is the one where the\n /// price of OETH and WETH are at parity\n constructor(\n BaseStrategyConfig memory _stratConfig,\n address _wethAddress,\n address _oethAddress,\n address _liquidityManager,\n address _poolLens,\n address _maverickPosition,\n address _maverickQuoter,\n address _mPool,\n bool _upperTickAtParity,\n address _votingDistributor,\n address _poolDistributor\n ) initializer InitializableAbstractStrategy(_stratConfig) {\n require(\n address(IMaverickV2Pool(_mPool).tokenA()) == _wethAddress,\n \"WETH not TokenA\"\n );\n require(\n address(IMaverickV2Pool(_mPool).tokenB()) == _oethAddress,\n \"OETH not TokenB\"\n );\n require(\n _liquidityManager != address(0),\n \"LiquidityManager zero address not allowed\"\n );\n require(\n _maverickQuoter != address(0),\n \"Quoter zero address not allowed\"\n );\n require(_poolLens != address(0), \"PoolLens zero address not allowed\");\n require(\n _maverickPosition != address(0),\n \"Position zero address not allowed\"\n );\n require(\n _votingDistributor != address(0),\n \"Voting distributor zero address not allowed\"\n );\n require(\n _poolDistributor != address(0),\n \"Pool distributor zero address not allowed\"\n );\n\n uint256 _tickSpacing = IMaverickV2Pool(_mPool).tickSpacing();\n require(_tickSpacing == 1, \"Unsupported tickSpacing\");\n\n // tickSpacing == 1\n (sqrtPriceTickLower, sqrtPriceTickHigher) = TickMath.tickSqrtPrices(\n _tickSpacing,\n TICK_NUMBER\n );\n sqrtPriceAtParity = _upperTickAtParity\n ? sqrtPriceTickHigher\n : sqrtPriceTickLower;\n\n WETH = _wethAddress;\n OETH = _oethAddress;\n liquidityManager = IMaverickV2LiquidityManager(_liquidityManager);\n poolLens = IMaverickV2PoolLens(_poolLens);\n maverickPosition = IMaverickV2Position(_maverickPosition);\n quoter = IMaverickV2Quoter(_maverickQuoter);\n mPool = IMaverickV2Pool(_mPool);\n votingDistributor = IVotingDistributor(_votingDistributor);\n poolDistributor = IPoolDistributor(_poolDistributor);\n\n // prevent implementation contract to be governed\n _setGovernor(address(0));\n }\n\n /**\n * @notice initialize function, to set up initial internal state\n */\n function initialize() external onlyGovernor initializer {\n // Read reward\n address[] memory _rewardTokens = new address[](1);\n _rewardTokens[0] = poolDistributor.rewardToken();\n\n require(_rewardTokens[0] != address(0), \"No reward token configured\");\n\n InitializableAbstractStrategy._initialize(\n _rewardTokens,\n new address[](0),\n new address[](0)\n );\n }\n\n /***************************************\n Configuration \n ****************************************/\n\n /**\n * @notice Set allowed pool weth share interval. After the rebalance happens\n * the share of WETH token in the ticker needs to be within the specifications\n * of the interval.\n *\n * @param _allowedWethShareStart Start of WETH share interval expressed as 18 decimal amount\n * @param _allowedWethShareEnd End of WETH share interval expressed as 18 decimal amount\n */\n function setAllowedPoolWethShareInterval(\n uint256 _allowedWethShareStart,\n uint256 _allowedWethShareEnd\n ) external onlyGovernor {\n require(\n _allowedWethShareStart < _allowedWethShareEnd,\n \"Invalid interval\"\n );\n // can not go below 1% weth share\n require(_allowedWethShareStart > 0.01 ether, \"Invalid interval start\");\n // can not go above 95% weth share\n require(_allowedWethShareEnd < 0.95 ether, \"Invalid interval end\");\n\n allowedWethShareStart = _allowedWethShareStart;\n allowedWethShareEnd = _allowedWethShareEnd;\n emit PoolWethShareIntervalUpdated(\n _allowedWethShareStart,\n _allowedWethShareEnd\n );\n }\n\n /***************************************\n Strategy overrides \n ****************************************/\n\n /**\n * @notice Deposits funds to the strategy which deposits them to the\n * underlying Rooster pool if the pool price is within the expected interval.\n * @param _asset Address for the asset\n * @param _amount Units of asset to deposit\n */\n function deposit(address _asset, uint256 _amount)\n external\n override\n onlyVault\n nonReentrant\n {\n _deposit(_asset, _amount);\n }\n\n /**\n * @notice Deposits all the funds to the strategy which deposits them to the\n * underlying Rooster pool if the pool price is within the expected interval.\n */\n function depositAll() external override onlyVault nonReentrant {\n uint256 _wethBalance = IERC20(WETH).balanceOf(address(this));\n _deposit(WETH, _wethBalance);\n }\n\n /**\n * @dev Deposits funds to the strategy which deposits them to the\n * underlying Rooster pool if the pool price is within the expected interval.\n * Before this function can be called the initial pool position needs to already\n * be minted.\n * @param _asset Address of the asset to deposit\n * @param _amount Amount of assets to deposit\n */\n function _deposit(address _asset, uint256 _amount) internal {\n require(_asset == WETH, \"Unsupported asset\");\n require(_amount > 0, \"Must deposit something\");\n require(tokenId > 0, \"Initial position not minted\");\n emit Deposit(_asset, address(0), _amount);\n\n // if the pool price is not within the expected interval leave the WETH on the contract\n // as to not break the mints - in case it would be configured as a default asset strategy\n (bool _isExpectedRange, ) = _checkForExpectedPoolPrice(false);\n if (_isExpectedRange) {\n // deposit funds into the underlying pool. Because no swap is performed there is no\n // need to remove any of the liquidity beforehand.\n _rebalance(0, false, 0, 0);\n }\n }\n\n /**\n * @notice Withdraw an `amount` of WETH from the platform and\n * send to the `_recipient`.\n * @param _recipient Address to which the asset should be sent\n * @param _asset WETH address\n * @param _amount Amount of WETH to withdraw\n */\n function withdraw(\n address _recipient,\n address _asset,\n uint256 _amount\n ) external override onlyVault nonReentrant {\n require(_asset == WETH, \"Unsupported asset\");\n require(_amount > 0, \"Must withdraw something\");\n require(_recipient == vaultAddress, \"Only withdraw to vault allowed\");\n\n _ensureWETHBalance(_amount);\n\n _withdraw(_recipient, _amount);\n }\n\n /**\n * @notice Withdraw WETH and sends it to the Vault.\n */\n function withdrawAll() external override onlyVault nonReentrant {\n if (tokenId != 0) {\n _removeLiquidity(1e18);\n }\n\n uint256 _balance = IERC20(WETH).balanceOf(address(this));\n if (_balance > 0) {\n _withdraw(vaultAddress, _balance);\n }\n }\n\n function _withdraw(address _recipient, uint256 _amount) internal {\n IERC20(WETH).safeTransfer(_recipient, _amount);\n emit Withdrawal(WETH, address(0), _amount);\n }\n\n /**\n * @dev Retuns bool indicating whether asset is supported by strategy\n * @param _asset Address of the asset\n * @return bool True when the _asset is WETH\n */\n function supportsAsset(address _asset) public view override returns (bool) {\n return _asset == WETH;\n }\n\n /**\n * @dev Approve the spending amounts for the assets\n */\n function _approveTokenAmounts(\n uint256 _wethAllowance,\n uint256 _oethAllowance\n ) internal {\n IERC20(WETH).approve(address(liquidityManager), _wethAllowance);\n IERC20(OETH).approve(address(liquidityManager), _oethAllowance);\n }\n\n /***************************************\n Liquidity management\n ****************************************/\n /**\n * @dev Add liquidity into the pool in the pre-configured WETH to OETH share ratios\n * defined by the allowedPoolWethShareStart|End interval.\n *\n * Normally a PoolLens contract is used to prepare the parameters to add liquidity to the\n * Rooster pools. It has some errors when doing those calculation and for that reason a\n * much more accurate Quoter contract is used. This is possible due to our requirement of\n * adding liquidity only to one tick - PoolLens supports adding liquidity into multiple ticks\n * using different distribution ratios.\n */\n function _addLiquidity() internal {\n uint256 _wethBalance = IERC20(WETH).balanceOf(address(this));\n uint256 _oethBalance = IERC20(OETH).balanceOf(address(this));\n // don't deposit small liquidity amounts\n if (_wethBalance <= ACTION_THRESHOLD) {\n return;\n }\n\n (\n bytes memory packedSqrtPriceBreaks,\n bytes[] memory packedArgs,\n uint256 WETHRequired,\n uint256 OETHRequired\n ) = _getAddLiquidityParams(_wethBalance, 1e30);\n\n if (OETHRequired > _oethBalance) {\n IVault(vaultAddress).mintForStrategy(OETHRequired - _oethBalance);\n }\n\n _approveTokenAmounts(WETHRequired, OETHRequired);\n\n (\n uint256 _wethAmount,\n uint256 _oethAmount,\n uint32[] memory binIds\n ) = liquidityManager.addPositionLiquidityToSenderByTokenIndex(\n mPool,\n 0, // NFT token index\n packedSqrtPriceBreaks,\n packedArgs\n );\n\n require(binIds.length == 1, \"Unexpected binIds length\");\n\n // burn remaining OETH\n _burnOethOnTheContract();\n _updateUnderlyingAssets();\n\n // needs to be called after _updateUnderlyingAssets so the updated amount\n // is reflected in the event\n emit LiquidityAdded(\n _wethBalance, // wethAmountDesired\n OETHRequired, // oethAmountDesired\n _wethAmount, // wethAmountSupplied\n _oethAmount, // oethAmountSupplied\n tokenId, // tokenId\n underlyingAssets\n );\n }\n\n /**\n * @dev The function creates liquidity parameters required to be able to add liquidity to the pool.\n * The function needs to handle the 3 different cases of the way liquidity is added:\n * - only WETH present in the tick\n * - only OETH present in the tick\n * - both tokens present in the tick\n *\n */\n function _getAddLiquidityParams(uint256 _maxWETH, uint256 _maxOETH)\n internal\n returns (\n bytes memory packedSqrtPriceBreaks,\n bytes[] memory packedArgs,\n uint256 WETHRequired,\n uint256 OETHRequired\n )\n {\n IMaverickV2Pool.AddLiquidityParams[]\n memory addParams = new IMaverickV2Pool.AddLiquidityParams[](1);\n int32[] memory ticks = new int32[](1);\n uint128[] memory amounts = new uint128[](1);\n ticks[0] = TICK_NUMBER;\n // arbitrary LP amount\n amounts[0] = 1e24;\n\n // construct value for Quoter with arbitrary LP amount\n IMaverickV2Pool.AddLiquidityParams memory addParam = IMaverickV2Pool\n .AddLiquidityParams({\n kind: MAV_STATIC_BIN_KIND,\n ticks: ticks,\n amounts: amounts\n });\n\n // get the WETH and OETH required to get the proportion of tokens required\n // given the arbitrary liquidity\n (WETHRequired, OETHRequired, ) = quoter.calculateAddLiquidity(\n mPool,\n addParam\n );\n\n /**\n * If either token required is 0 then the tick consists only of the other token. In that\n * case the liquidity calculations need to be done using the non 0 token. By setting the\n * tokenRequired from 0 to 1 the `min` in next step will ignore that (the bigger) value.\n */\n WETHRequired = WETHRequired == 0 ? 1 : WETHRequired;\n OETHRequired = OETHRequired == 0 ? 1 : OETHRequired;\n\n addParam.amounts[0] = Math_v5\n .min(\n ((_maxWETH - 1) * 1e24) / WETHRequired,\n ((_maxOETH - 1) * 1e24) / OETHRequired\n )\n .toUint128();\n\n // update the quotes with the actual amounts\n (WETHRequired, OETHRequired, ) = quoter.calculateAddLiquidity(\n mPool,\n addParam\n );\n\n require(_maxWETH >= WETHRequired, \"More WETH required than specified\");\n require(_maxOETH >= OETHRequired, \"More OETH required than specified\");\n\n // organize values to be used by manager\n addParams[0] = addParam;\n packedArgs = liquidityManager.packAddLiquidityArgsArray(addParams);\n // price can stay 0 if array only has one element\n packedSqrtPriceBreaks = liquidityManager.packUint88Array(\n new uint88[](1)\n );\n }\n\n /**\n * @dev Check that the Rooster pool price is within the expected\n * parameters.\n * This function works whether the strategy contract has liquidity\n * position in the pool or not. The function returns _wethSharePct\n * as a gas optimization measure.\n * @param _throwException when set to true the function throws an exception\n * when pool's price is not within expected range.\n * @return _isExpectedRange Bool expressing price is within expected range\n * @return _wethSharePct Share of WETH owned by this strategy contract in the\n * configured ticker.\n */\n function _checkForExpectedPoolPrice(bool _throwException)\n internal\n view\n returns (bool _isExpectedRange, uint256 _wethSharePct)\n {\n require(\n allowedWethShareStart != 0 && allowedWethShareEnd != 0,\n \"Weth share interval not set\"\n );\n\n uint256 _currentPrice = getPoolSqrtPrice();\n\n /**\n * First check pool price is in expected tick range\n *\n * A revert is issued even though price being equal to the lower bound as that can not\n * be within the approved tick range.\n */\n if (\n _currentPrice <= sqrtPriceTickLower ||\n _currentPrice >= sqrtPriceTickHigher\n ) {\n if (_throwException) {\n revert OutsideExpectedTickRange();\n }\n\n return (false, _currentPrice <= sqrtPriceTickLower ? 0 : 1e18);\n }\n\n // 18 decimal number expressed WETH tick share\n _wethSharePct = _getWethShare(_currentPrice);\n\n if (\n _wethSharePct < allowedWethShareStart ||\n _wethSharePct > allowedWethShareEnd\n ) {\n if (_throwException) {\n revert PoolRebalanceOutOfBounds(\n _wethSharePct,\n allowedWethShareStart,\n allowedWethShareEnd\n );\n }\n return (false, _wethSharePct);\n }\n\n return (true, _wethSharePct);\n }\n\n /**\n * @notice Rebalance the pool to the desired token split and Deposit any WETH on the contract to the\n * underlying rooster pool. Print the required amount of corresponding OETH. After the rebalancing is\n * done burn any potentially remaining OETH tokens still on the strategy contract.\n *\n * This function has a slightly different behaviour depending on the status of the underlying Rooster\n * pool. The function consists of the following 3 steps:\n * 1. withdrawLiquidityOption -> this is a configurable option where either only part of the liquidity\n * necessary for the swap is removed, or all of it. This way the rebalance\n * is able to optimize for volume, for efficiency or anything in between\n * 2. swapToDesiredPosition -> move active trading price in the pool to be able to deposit WETH & OETH\n * tokens with the desired pre-configured ratios\n * 3. addLiquidity -> add liquidity into the pool respecting ratio split configuration\n *\n *\n * Exact _amountToSwap, _swapWeth & _minTokenReceived parameters shall be determined by simulating the\n * transaction off-chain. The strategy checks that after the swap the share of the tokens is in the\n * expected ranges.\n *\n * @param _amountToSwap The amount of the token to swap\n * @param _swapWeth Swap using WETH when true, use OETH when false\n * @param _minTokenReceived Slippage check -> minimum amount of token expected in return\n * @param _liquidityToRemovePct Percentage of liquidity to remove -> the percentage amount of liquidity to\n * remove before performing the swap. 1e18 denominated\n */\n function rebalance(\n uint256 _amountToSwap,\n bool _swapWeth,\n uint256 _minTokenReceived,\n uint256 _liquidityToRemovePct\n ) external nonReentrant onlyGovernorOrStrategist {\n _rebalance(\n _amountToSwap,\n _swapWeth,\n _minTokenReceived,\n _liquidityToRemovePct\n );\n }\n\n // slither-disable-start reentrancy-no-eth\n function _rebalance(\n uint256 _amountToSwap,\n bool _swapWeth,\n uint256 _minTokenReceived,\n uint256 _liquidityToRemovePct\n ) internal {\n // Remove the required amount of liquidity\n if (_liquidityToRemovePct > 0) {\n _removeLiquidity(_liquidityToRemovePct);\n }\n\n // in some cases (e.g. deposits) we will just want to add liquidity and not\n // issue a swap to move the active trading position within the pool. Before or after a\n // deposit or as a standalone call the strategist might issue a rebalance to move the\n // active trading price to a more desired position.\n if (_amountToSwap > 0) {\n // In case liquidity has been removed and there is still not enough WETH owned by the\n // strategy contract remove additional required amount of WETH.\n if (_swapWeth) _ensureWETHBalance(_amountToSwap);\n\n _swapToDesiredPosition(_amountToSwap, _swapWeth, _minTokenReceived);\n }\n\n // calling check liquidity early so we don't get unexpected errors when adding liquidity\n // in the later stages of this function\n _checkForExpectedPoolPrice(true);\n\n _addLiquidity();\n\n // this call shouldn't be necessary, since adding liquidity shouldn't affect the active\n // trading price. It is a defensive programming measure.\n (, uint256 _wethSharePct) = _checkForExpectedPoolPrice(true);\n\n // revert if protocol insolvent\n _solvencyAssert();\n\n emit PoolRebalanced(_wethSharePct);\n }\n\n // slither-disable-end reentrancy-no-eth\n\n /**\n * @dev Perform a swap so that after the swap the tick has the desired WETH to OETH token share.\n */\n function _swapToDesiredPosition(\n uint256 _amountToSwap,\n bool _swapWeth,\n uint256 _minTokenReceived\n ) internal {\n IERC20 _tokenToSwap = IERC20(_swapWeth ? WETH : OETH);\n uint256 _balance = _tokenToSwap.balanceOf(address(this));\n\n if (_balance < _amountToSwap) {\n // This should never trigger since _ensureWETHBalance will already\n // throw an error if there is not enough WETH\n if (_swapWeth) {\n revert NotEnoughWethForSwap(_balance, _amountToSwap);\n }\n // if swapping OETH\n uint256 mintForSwap = _amountToSwap - _balance;\n IVault(vaultAddress).mintForStrategy(mintForSwap);\n }\n\n // SafeERC20 is used for IERC20 transfers. Not sure why slither complains\n // slither-disable-next-line unchecked-transfer\n _tokenToSwap.transfer(address(mPool), _amountToSwap);\n\n // tickLimit: the furthest tick a swap will execute in. If no limit is desired,\n // value should be set to type(int32).max for a tokenAIn (WETH) swap\n // and type(int32).min for a swap where tokenB (OETH) is the input\n\n IMaverickV2Pool.SwapParams memory swapParams = IMaverickV2Pool\n // exactOutput defines whether the amount specified is the output\n // or the input amount of the swap\n .SwapParams({\n amount: _amountToSwap,\n tokenAIn: _swapWeth,\n exactOutput: false,\n tickLimit: TICK_NUMBER\n });\n\n // swaps without a callback as the assets are already sent to the pool\n (, uint256 amountOut) = mPool.swap(\n address(this),\n swapParams,\n bytes(\"\")\n );\n\n /**\n * There could be additional checks here for validating minTokenReceived is within the\n * expected range (e.g. 99% - 101% of the token sent in). Though that doesn't provide\n * any additional security. After the swap the `_checkForExpectedPoolPrice` validates\n * that the swap has moved the price into the expected tick (# -1).\n *\n * If the guardian forgets to set a `_minTokenReceived` and a sandwich attack bends\n * the pool before the swap the `_checkForExpectedPoolPrice` will fail the transaction.\n *\n * A check would not prevent a compromised guardian from stealing funds as multiple\n * transactions each loosing smaller amount of funds are still possible.\n */\n if (amountOut < _minTokenReceived) {\n revert SlippageCheck(amountOut);\n }\n\n /**\n * In the interest of each function in `_rebalance` to leave the contract state as\n * clean as possible the OETH tokens here are burned. This decreases the\n * dependence where `_swapToDesiredPosition` function relies on later functions\n * (`addLiquidity`) to burn the OETH. Reducing the risk of error introduction.\n */\n _burnOethOnTheContract();\n }\n\n /**\n * @dev This function removes the appropriate amount of liquidity to ensure that the required\n * amount of WETH is available on the contract\n *\n * @param _amount WETH balance required on the contract\n */\n function _ensureWETHBalance(uint256 _amount) internal {\n uint256 _wethBalance = IERC20(WETH).balanceOf(address(this));\n if (_wethBalance >= _amount) {\n return;\n }\n\n require(tokenId != 0, \"No liquidity available\");\n uint256 _additionalWethRequired = _amount - _wethBalance;\n (uint256 _wethInThePool, ) = getPositionPrincipal();\n\n if (_wethInThePool < _additionalWethRequired) {\n revert NotEnoughWethLiquidity(\n _wethInThePool,\n _additionalWethRequired\n );\n }\n\n uint256 shareOfWethToRemove = _wethInThePool <= 1\n ? 1e18\n : Math_v5.min(\n /**\n * When dealing with shares of liquidity to remove there is always some\n * rounding involved. After extensive fuzz testing the below approach\n * yielded the best results where the strategy overdraws the least and\n * never removes insufficient amount of WETH.\n */\n (_additionalWethRequired + 2).divPrecisely(_wethInThePool - 1) +\n 2,\n 1e18\n );\n\n _removeLiquidity(shareOfWethToRemove);\n }\n\n /**\n * @dev Decrease partial or all liquidity from the pool.\n * @param _liquidityToDecrease The amount of liquidity to remove denominated in 1e18\n */\n function _removeLiquidity(uint256 _liquidityToDecrease) internal {\n require(_liquidityToDecrease > 0, \"Must remove some liquidity\");\n require(\n _liquidityToDecrease <= 1e18,\n \"Can not remove more than 100% of liquidity\"\n );\n\n // 0 indicates the first (and only) bin in the NFT LP position.\n IMaverickV2Pool.RemoveLiquidityParams memory params = maverickPosition\n .getRemoveParams(tokenId, 0, _liquidityToDecrease);\n (uint256 _amountWeth, uint256 _amountOeth) = maverickPosition\n .removeLiquidityToSender(tokenId, mPool, params);\n\n _burnOethOnTheContract();\n _updateUnderlyingAssets();\n\n // needs to be called after the _updateUnderlyingAssets so the updated amount is reflected\n // in the event\n emit LiquidityRemoved(\n _liquidityToDecrease,\n _amountWeth,\n _amountOeth,\n underlyingAssets\n );\n }\n\n /**\n * @dev Burns any OETH tokens remaining on the strategy contract if the balance is\n * above the action threshold.\n */\n function _burnOethOnTheContract() internal {\n uint256 _oethBalance = IERC20(OETH).balanceOf(address(this));\n IVault(vaultAddress).burnForStrategy(_oethBalance);\n }\n\n /**\n * @notice Returns the percentage of WETH liquidity in the configured ticker\n * owned by this strategy contract.\n * @return uint256 1e18 denominated percentage expressing the share\n */\n function getWETHShare() external view returns (uint256) {\n uint256 _currentPrice = getPoolSqrtPrice();\n return _getWethShare(_currentPrice);\n }\n\n /**\n * @dev Returns the share of WETH in tick denominated in 1e18\n */\n function _getWethShare(uint256 _currentPrice)\n internal\n view\n returns (uint256)\n {\n (\n uint256 wethAmount,\n uint256 oethAmount\n ) = _reservesInTickForGivenPriceAndLiquidity(\n sqrtPriceTickLower,\n sqrtPriceTickHigher,\n _currentPrice,\n 1e24\n );\n\n return wethAmount.divPrecisely(wethAmount + oethAmount);\n }\n\n /**\n * @notice Returns the current pool price in square root\n * @return Square root of the pool price\n */\n function getPoolSqrtPrice() public view returns (uint256) {\n return poolLens.getPoolSqrtPrice(mPool);\n }\n\n /**\n * @notice Returns the current active trading tick of the underlying pool\n * @return _currentTick Current pool trading tick\n */\n function getCurrentTradingTick() public view returns (int32 _currentTick) {\n _currentTick = mPool.getState().activeTick;\n }\n\n /**\n * @notice Mint the initial NFT position\n *\n * @dev This amount is \"gifted\" to the strategy contract and will count as a yield\n * surplus.\n */\n // slither-disable-start reentrancy-no-eth\n function mintInitialPosition() external onlyGovernor nonReentrant {\n require(tokenId == 0, \"Initial position already minted\");\n (\n bytes memory packedSqrtPriceBreaks,\n bytes[] memory packedArgs,\n uint256 WETHRequired,\n uint256 OETHRequired\n ) = _getAddLiquidityParams(1e16, 1e16);\n\n // Mint rounded up OETH amount\n if (OETHRequired > 0) {\n IVault(vaultAddress).mintForStrategy(OETHRequired);\n }\n\n _approveTokenAmounts(WETHRequired, OETHRequired);\n\n // Store the tokenId before calling updateUnderlyingAssets as it relies on the tokenId\n // not being 0\n (, , , tokenId) = liquidityManager.mintPositionNftToSender(\n mPool,\n packedSqrtPriceBreaks,\n packedArgs\n );\n\n // burn remaining OETH\n _burnOethOnTheContract();\n _updateUnderlyingAssets();\n }\n\n // slither-disable-end reentrancy-no-eth\n\n /**\n * @notice Returns the balance of tokens the strategy holds in the LP position\n * @return _amountWeth Amount of WETH in position\n * @return _amountOeth Amount of OETH in position\n */\n function getPositionPrincipal()\n public\n view\n returns (uint256 _amountWeth, uint256 _amountOeth)\n {\n if (tokenId == 0) {\n return (0, 0);\n }\n\n (_amountWeth, _amountOeth, ) = _getPositionInformation();\n }\n\n /**\n * @dev Returns the balance of tokens the strategy holds in the LP position\n * @return _amountWeth Amount of WETH in position\n * @return _amountOeth Amount of OETH in position\n * @return liquidity Amount of liquidity in the position\n */\n function _getPositionInformation()\n internal\n view\n returns (\n uint256 _amountWeth,\n uint256 _amountOeth,\n uint256 liquidity\n )\n {\n IMaverickV2Position.PositionFullInformation\n memory positionInfo = maverickPosition.tokenIdPositionInformation(\n tokenId,\n 0\n );\n\n require(\n positionInfo.liquidities.length == 1,\n \"Unexpected liquidities length\"\n );\n require(positionInfo.ticks.length == 1, \"Unexpected ticks length\");\n\n _amountWeth = positionInfo.amountA;\n _amountOeth = positionInfo.amountB;\n liquidity = positionInfo.liquidities[0];\n }\n\n /**\n * Checks that the protocol is solvent, protecting from a rogue Strategist / Guardian that can\n * keep rebalancing the pool in both directions making the protocol lose a tiny amount of\n * funds each time.\n *\n * Protocol must be at least SOLVENCY_THRESHOLD (99.8%) backed in order for the rebalances to\n * function.\n */\n function _solvencyAssert() internal view {\n uint256 _totalVaultValue = IVault(vaultAddress).totalValue();\n uint256 _totalOethSupply = IERC20(OETH).totalSupply();\n\n if (\n _totalVaultValue.divPrecisely(_totalOethSupply) < SOLVENCY_THRESHOLD\n ) {\n revert(\"Protocol insolvent\");\n }\n }\n\n /**\n * @dev Collect Rooster reward token, and send it to the harvesterAddress\n */\n function collectRewardTokens()\n external\n override\n onlyHarvester\n nonReentrant\n {\n // Do nothing if there's no position minted\n if (tokenId > 0) {\n uint32[] memory binIds = new uint32[](1);\n IMaverickV2Pool.TickState memory tickState = mPool.getTick(\n TICK_NUMBER\n );\n // get the binId for the MAV_STATIC_BIN_KIND in tick TICK_NUMBER (-1)\n binIds[0] = tickState.binIdsByTick[0];\n\n uint256 lastEpoch = votingDistributor.lastEpoch();\n\n poolDistributor.claimLp(\n address(this),\n tokenId,\n mPool,\n binIds,\n lastEpoch\n );\n }\n\n // Run the internal inherited function\n _collectRewardTokens();\n }\n\n /***************************************\n Balances and Fees\n ****************************************/\n\n /**\n * @dev Get the total asset value held in the platform\n * @param _asset Address of the asset\n * @return balance Total value of the asset in the platform\n */\n function checkBalance(address _asset)\n external\n view\n override\n returns (uint256)\n {\n require(_asset == WETH, \"Only WETH supported\");\n\n // because of PoolLens inaccuracy there is usually some dust WETH left on the contract\n uint256 _wethBalance = IERC20(WETH).balanceOf(address(this));\n // just paranoia check, in case there is OETH in the strategy that for some reason hasn't\n // been burned yet. This should always be 0.\n uint256 _oethBalance = IERC20(OETH).balanceOf(address(this));\n return underlyingAssets + _wethBalance + _oethBalance;\n }\n\n /// @dev This function updates the amount of underlying assets with the approach of the least possible\n /// total tokens extracted for the current liquidity in the pool.\n function _updateUnderlyingAssets() internal {\n /**\n * Our net value represent the smallest amount of tokens we are able to extract from the position\n * given our liquidity.\n *\n * The least amount of tokens ex-tractable from the position is where the active trading price is\n * at the edge between tick -1 & tick 0. There the pool is offering 1:1 trades between WETH & OETH.\n * At that moment the pool consists completely of WETH and no OETH.\n *\n * The more swaps from OETH -> WETH happen on the pool the more the price starts to move away from the tick 0\n * towards the middle of tick -1 making OETH (priced in WETH) cheaper.\n */\n\n uint256 _wethAmount = tokenId == 0 ? 0 : _balanceInPosition();\n\n underlyingAssets = _wethAmount;\n emit UnderlyingAssetsUpdated(_wethAmount);\n }\n\n /**\n * @dev Strategy reserves (which consist only of WETH in case of Rooster - Plume pool)\n * when the tick price is closest to parity - assuring the lowest amount of tokens\n * returned for the current position liquidity.\n */\n function _balanceInPosition() internal view returns (uint256 _wethBalance) {\n (, , uint256 liquidity) = _getPositionInformation();\n\n uint256 _oethBalance;\n\n (_wethBalance, _oethBalance) = _reservesInTickForGivenPriceAndLiquidity(\n sqrtPriceTickLower,\n sqrtPriceTickHigher,\n sqrtPriceAtParity,\n liquidity\n );\n\n require(_oethBalance == 0, \"Non zero oethBalance\");\n }\n\n /**\n * @notice Tick dominance denominated in 1e18\n * @return _tickDominance The share of liquidity in TICK_NUMBER tick owned\n * by the strategy contract denominated in 1e18\n */\n function tickDominance() public view returns (uint256 _tickDominance) {\n IMaverickV2Pool.TickState memory tickState = mPool.getTick(TICK_NUMBER);\n\n uint256 wethReserve = tickState.reserveA;\n uint256 oethReserve = tickState.reserveB;\n\n // prettier-ignore\n (uint256 _amountWeth, uint256 _amountOeth, ) = _getPositionInformation();\n\n if (wethReserve + oethReserve == 0) {\n return 0;\n }\n\n _tickDominance = (_amountWeth + _amountOeth).divPrecisely(\n wethReserve + oethReserve\n );\n }\n\n /***************************************\n Hidden functions\n ****************************************/\n /**\n * @dev Unsupported\n */\n function setPTokenAddress(address, address) external pure override {\n // The pool tokens can never change.\n revert(\"Unsupported method\");\n }\n\n /**\n * @dev Unsupported\n */\n function removePToken(uint256) external pure override {\n // The pool tokens can never change.\n revert(\"Unsupported method\");\n }\n\n /**\n * @dev Unsupported\n */\n function _abstractSetPToken(address, address) internal pure override {\n revert(\"Unsupported method\");\n }\n\n /**\n * @dev Unsupported\n */\n function safeApproveAllTokens() external pure override {\n // all the amounts are approved at the time required\n revert(\"Unsupported method\");\n }\n\n /***************************************\n Maverick liquidity utilities\n ****************************************/\n\n /// @notice Calculates deltaA = liquidity * (sqrt(upper) - sqrt(lower))\n /// Calculates deltaB = liquidity / sqrt(lower) - liquidity / sqrt(upper),\n /// i.e. liquidity * (sqrt(upper) - sqrt(lower)) / (sqrt(upper) * sqrt(lower))\n ///\n /// @dev refactored from here:\n // solhint-disable-next-line max-line-length\n /// https://github.com/rooster-protocol/rooster-contracts/blob/main/v2-supplemental/contracts/libraries/LiquidityUtilities.sol#L665-L695\n function _reservesInTickForGivenPriceAndLiquidity(\n uint256 _lowerSqrtPrice,\n uint256 _upperSqrtPrice,\n uint256 _newSqrtPrice,\n uint256 _liquidity\n ) internal pure returns (uint128 reserveA, uint128 reserveB) {\n if (_liquidity == 0) {\n (reserveA, reserveB) = (0, 0);\n } else {\n uint256 lowerEdge = MathRooster.max(_lowerSqrtPrice, _newSqrtPrice);\n\n reserveA = MathRooster\n .mulCeil(\n _liquidity,\n MathRooster.clip(\n MathRooster.min(_upperSqrtPrice, _newSqrtPrice),\n _lowerSqrtPrice\n )\n )\n .toUint128();\n reserveB = MathRooster\n .mulDivCeil(\n _liquidity,\n 1e18 * MathRooster.clip(_upperSqrtPrice, lowerEdge),\n _upperSqrtPrice * lowerEdge\n )\n .toUint128();\n }\n }\n}\n" + }, + "contracts/strategies/sonic/SonicStakingStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { Math } from \"@openzeppelin/contracts/utils/math/Math.sol\";\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport { SonicValidatorDelegator } from \"./SonicValidatorDelegator.sol\";\nimport { IWrappedSonic } from \"../../interfaces/sonic/IWrappedSonic.sol\";\n\n/**\n * @title Staking Strategy for Sonic's native S currency\n * @author Origin Protocol Inc\n */\ncontract SonicStakingStrategy is SonicValidatorDelegator {\n // For future use\n uint256[50] private __gap;\n\n constructor(\n BaseStrategyConfig memory _baseConfig,\n address _wrappedSonic,\n address _sfc\n ) SonicValidatorDelegator(_baseConfig, _wrappedSonic, _sfc) {}\n\n /// @notice Deposit wrapped S asset into the underlying platform.\n /// @param _asset Address of asset to deposit. Has to be Wrapped Sonic (wS).\n /// @param _amount Amount of assets that were transferred to the strategy by the vault.\n function deposit(address _asset, uint256 _amount)\n external\n override\n onlyVault\n nonReentrant\n {\n require(_asset == wrappedSonic, \"Unsupported asset\");\n _deposit(_asset, _amount);\n }\n\n /**\n * @notice Deposit Wrapped Sonic (wS) to this strategy and delegate to a validator.\n * @param _asset Address of Wrapped Sonic (wS) token\n * @param _amount Amount of Wrapped Sonic (wS) to deposit\n */\n function _deposit(address _asset, uint256 _amount) internal virtual {\n require(_amount > 0, \"Must deposit something\");\n\n _delegate(_amount);\n emit Deposit(_asset, address(0), _amount);\n }\n\n /**\n * @notice Deposit the entire balance of wrapped S in this strategy contract into\n * the underlying platform.\n */\n function depositAll() external virtual override onlyVault nonReentrant {\n uint256 wSBalance = IERC20(wrappedSonic).balanceOf(address(this));\n\n if (wSBalance > 0) {\n _deposit(wrappedSonic, wSBalance);\n }\n }\n\n /// @notice Withdraw Wrapped Sonic (wS) from this strategy contract.\n /// Used only if some wS is lingering on the contract.\n /// That can happen only when someone sends wS directly to this contract\n /// @param _recipient Address to receive withdrawn assets\n /// @param _asset Address of the Wrapped Sonic (wS) token\n /// @param _amount Amount of Wrapped Sonic (wS) to withdraw\n function withdraw(\n address _recipient,\n address _asset,\n uint256 _amount\n ) external override onlyVault nonReentrant {\n require(_asset == wrappedSonic, \"Unsupported asset\");\n _withdraw(_recipient, _asset, _amount);\n }\n\n function _withdraw(\n address _recipient,\n address _asset,\n uint256 _amount\n ) internal override {\n require(_amount > 0, \"Must withdraw something\");\n require(_recipient != address(0), \"Must specify recipient\");\n\n // slither-disable-next-line unchecked-transfer unused-return\n IERC20(_asset).transfer(_recipient, _amount);\n\n emit Withdrawal(wrappedSonic, address(0), _amount);\n }\n\n /// @notice Transfer all Wrapped Sonic (wS) deposits back to the vault.\n /// This does not withdraw from delegated validators. That has to be done separately with `undelegate`.\n /// Any native S in this strategy will be withdrawn.\n function withdrawAll() external override onlyVaultOrGovernor nonReentrant {\n uint256 balance = address(this).balance;\n if (balance > 0) {\n IWrappedSonic(wrappedSonic).deposit{ value: balance }();\n }\n uint256 wSBalance = IERC20(wrappedSonic).balanceOf(address(this));\n if (wSBalance > 0) {\n _withdraw(vaultAddress, wrappedSonic, wSBalance);\n }\n }\n\n /**\n * @dev Returns bool indicating whether asset is supported by strategy\n * @param _asset Address of the asset token\n */\n function supportsAsset(address _asset)\n public\n view\n virtual\n override\n returns (bool)\n {\n return _asset == wrappedSonic;\n }\n\n /**\n * @notice is not supported for this strategy as the\n * Wrapped Sonic (wS) token is set at deploy time.\n */\n function setPTokenAddress(address, address)\n external\n view\n override\n onlyGovernor\n {\n revert(\"unsupported function\");\n }\n\n /// @notice is not used by this strategy as all staking rewards are restaked\n function collectRewardTokens() external override nonReentrant {\n revert(\"unsupported function\");\n }\n\n /**\n * @notice is not supported for this strategy as the\n * Wrapped Sonic (wS) token is set at deploy time.\n */\n function removePToken(uint256) external view override onlyGovernor {\n revert(\"unsupported function\");\n }\n\n /// @dev is not used by this strategy but must be implemented as it's abstract\n /// in the inherited `InitializableAbstractStrategy` contract.\n function _abstractSetPToken(address, address) internal virtual override {}\n\n /// @notice is not used by this strategy\n function safeApproveAllTokens() external override onlyGovernor {}\n}\n" + }, + "contracts/strategies/sonic/SonicSwapXAMOStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title SwapX Algorithmic Market Maker (AMO) Strategy\n * @notice AMO strategy for the SwapX OS/wS stable pool\n * @author Origin Protocol Inc\n */\nimport { SafeCast } from \"@openzeppelin/contracts/utils/math/SafeCast.sol\";\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\n\nimport { IERC20, InitializableAbstractStrategy } from \"../../utils/InitializableAbstractStrategy.sol\";\nimport { StableMath } from \"../../utils/StableMath.sol\";\nimport { sqrt } from \"../../utils/PRBMath.sol\";\nimport { IBasicToken } from \"../../interfaces/IBasicToken.sol\";\nimport { IPair } from \"../../interfaces/sonic/ISwapXPair.sol\";\nimport { IGauge } from \"../../interfaces/sonic/ISwapXGauge.sol\";\nimport { IVault } from \"../../interfaces/IVault.sol\";\n\ncontract SonicSwapXAMOStrategy is InitializableAbstractStrategy {\n using SafeERC20 for IERC20;\n using StableMath for uint256;\n using SafeCast for uint256;\n\n /**\n * @notice a threshold under which the contract no longer allows for the protocol to manually rebalance.\n * Guarding against a strategist / guardian being taken over and with multiple transactions\n * draining the protocol funds.\n */\n uint256 public constant SOLVENCY_THRESHOLD = 0.998 ether;\n\n /// @notice Precision for the SwapX Stable AMM (sAMM) invariant k.\n uint256 public constant PRECISION = 1e18;\n\n /// @notice Address of the Wrapped S (wS) token.\n address public immutable ws;\n\n /// @notice Address of the OS token contract.\n address public immutable os;\n\n /// @notice Address of the SwapX Stable pool contract.\n address public immutable pool;\n\n /// @notice Address of the SwapX Gauge contract.\n address public immutable gauge;\n\n /// @notice The max amount the OS/wS price can deviate from peg (1e18)\n /// before deposits are reverted scaled to 18 decimals.\n /// eg 0.01e18 or 1e16 is 1% which is 100 basis points.\n /// This is the amount below and above peg so a 50 basis point deviation (0.005e18)\n /// allows a price range from 0.995 to 1.005.\n uint256 public maxDepeg;\n\n event SwapOTokensToPool(\n uint256 osMinted,\n uint256 wsDepositAmount,\n uint256 osDepositAmount,\n uint256 lpTokens\n );\n event SwapAssetsToPool(\n uint256 wsSwapped,\n uint256 lpTokens,\n uint256 osBurnt\n );\n event MaxDepegUpdated(uint256 maxDepeg);\n\n /**\n * @dev Verifies that the caller is the Strategist of the Vault.\n */\n modifier onlyStrategist() {\n require(\n msg.sender == IVault(vaultAddress).strategistAddr(),\n \"Caller is not the Strategist\"\n );\n _;\n }\n\n /**\n * @dev Skim the SwapX pool in case any extra wS or OS tokens were added\n */\n modifier skimPool() {\n IPair(pool).skim(address(this));\n _;\n }\n\n /**\n * @dev Checks the pool is balanced enough to allow deposits.\n */\n modifier nearBalancedPool() {\n // OS/wS price = wS / OS\n // Get the OS/wS price for selling 1 OS for wS\n // As OS is 1, the wS amount is the OS/wS price\n uint256 sellPrice = IPair(pool).getAmountOut(1e18, os);\n\n // Get the amount of OS received from selling 1 wS. This is buying OS.\n uint256 osAmount = IPair(pool).getAmountOut(1e18, ws);\n // Convert to a OS/wS price = wS / OS\n uint256 buyPrice = 1e36 / osAmount;\n\n uint256 pegPrice = 1e18;\n\n require(\n sellPrice >= pegPrice - maxDepeg && buyPrice <= pegPrice + maxDepeg,\n \"price out of range\"\n );\n _;\n }\n\n /**\n * @dev Checks the pool's balances have improved and the balances\n * have not tipped to the other side.\n * This modifier is only applied to functions that do swaps against the pool.\n * Deposits and withdrawals are proportional to the pool's balances hence don't need this check.\n */\n modifier improvePoolBalance() {\n // Get the asset and OToken balances in the pool\n (uint256 wsReservesBefore, uint256 osReservesBefore, ) = IPair(pool)\n .getReserves();\n // diff = wS balance - OS balance\n int256 diffBefore = wsReservesBefore.toInt256() -\n osReservesBefore.toInt256();\n\n _;\n\n // Get the asset and OToken balances in the pool\n (uint256 wsReservesAfter, uint256 osReservesAfter, ) = IPair(pool)\n .getReserves();\n // diff = wS balance - OS balance\n int256 diffAfter = wsReservesAfter.toInt256() -\n osReservesAfter.toInt256();\n\n if (diffBefore == 0) {\n require(diffAfter == 0, \"Position balance is worsened\");\n } else if (diffBefore < 0) {\n // If the pool was originally imbalanced in favor of OS, then\n // we want to check that the pool is now more balanced\n require(diffAfter <= 0, \"Assets overshot peg\");\n require(diffBefore < diffAfter, \"OTokens balance worse\");\n } else if (diffBefore > 0) {\n // If the pool was originally imbalanced in favor of wS, then\n // we want to check that the pool is now more balanced\n require(diffAfter >= 0, \"OTokens overshot peg\");\n require(diffAfter < diffBefore, \"Assets balance worse\");\n }\n }\n\n /**\n * @param _baseConfig The `platformAddress` is the address of the SwapX pool.\n * The `vaultAddress` is the address of the Origin Sonic Vault.\n * @param _os Address of the OS token.\n * @param _ws Address of the Wrapped S (wS) token.\n * @param _gauge Address of the SwapX gauge for the pool.\n */\n constructor(\n BaseStrategyConfig memory _baseConfig,\n address _os,\n address _ws,\n address _gauge\n ) InitializableAbstractStrategy(_baseConfig) {\n // Check the pool tokens are correct\n require(\n IPair(_baseConfig.platformAddress).token0() == _ws &&\n IPair(_baseConfig.platformAddress).token1() == _os,\n \"Incorrect pool tokens\"\n );\n // Checked both tokens are to 18 decimals\n require(\n IBasicToken(_ws).decimals() == 18 &&\n IBasicToken(_os).decimals() == 18,\n \"Incorrect token decimals\"\n );\n // Check the SwapX pool is a Stable AMM (sAMM)\n require(\n IPair(_baseConfig.platformAddress).isStable() == true,\n \"Pool not stable\"\n );\n // Check the gauge is for the pool\n require(\n IGauge(_gauge).TOKEN() == _baseConfig.platformAddress,\n \"Incorrect gauge\"\n );\n\n // Set the immutable variables\n os = _os;\n ws = _ws;\n pool = _baseConfig.platformAddress;\n gauge = _gauge;\n\n // This is an implementation contract. The governor is set in the proxy contract.\n _setGovernor(address(0));\n }\n\n /**\n * Initializer for setting up strategy internal state. This overrides the\n * InitializableAbstractStrategy initializer as SwapX strategies don't fit\n * well within that abstraction.\n * @param _rewardTokenAddresses Array containing SWPx token address\n * @param _maxDepeg The max amount the OS/wS price can deviate from peg (1e18) before deposits are reverted.\n */\n function initialize(\n address[] calldata _rewardTokenAddresses,\n uint256 _maxDepeg\n ) external onlyGovernor initializer {\n address[] memory pTokens = new address[](1);\n pTokens[0] = pool;\n\n address[] memory _assets = new address[](1);\n _assets[0] = ws;\n\n InitializableAbstractStrategy._initialize(\n _rewardTokenAddresses,\n _assets,\n pTokens\n );\n\n maxDepeg = _maxDepeg;\n\n _approveBase();\n }\n\n /***************************************\n Deposit\n ****************************************/\n\n /**\n * @notice Deposit an amount of Wrapped S (wS) into the SwapX pool.\n * Mint OS in proportion to the pool's wS and OS reserves,\n * transfer Wrapped S (wS) and OS to the pool,\n * mint the pool's LP token and deposit in the gauge.\n * @dev This tx must be wrapped by the VaultValueChecker.\n * To minimize loses, the pool should be rebalanced before depositing.\n * The pool's OS/wS price must be within the maxDepeg range.\n * @param _asset Address of Wrapped S (wS) token.\n * @param _wsAmount Amount of Wrapped S (wS) tokens to deposit.\n */\n function deposit(address _asset, uint256 _wsAmount)\n external\n override\n onlyVault\n nonReentrant\n skimPool\n nearBalancedPool\n {\n require(_asset == ws, \"Unsupported asset\");\n require(_wsAmount > 0, \"Must deposit something\");\n\n (uint256 osDepositAmount, ) = _deposit(_wsAmount);\n\n // Ensure solvency of the vault\n _solvencyAssert();\n\n // Emit event for the deposited wS tokens\n emit Deposit(ws, pool, _wsAmount);\n // Emit event for the minted OS tokens\n emit Deposit(os, pool, osDepositAmount);\n }\n\n /**\n * @notice Deposit all the strategy's Wrapped S (wS) tokens into the SwapX pool.\n * Mint OS in proportion to the pool's wS and OS reserves,\n * transfer Wrapped S (wS) and OS to the pool,\n * mint the pool's LP token and deposit in the gauge.\n * @dev This tx must be wrapped by the VaultValueChecker.\n * To minimize loses, the pool should be rebalanced before depositing.\n * The pool's OS/wS price must be within the maxDepeg range.\n */\n function depositAll()\n external\n override\n onlyVault\n nonReentrant\n skimPool\n nearBalancedPool\n {\n uint256 wsBalance = IERC20(ws).balanceOf(address(this));\n if (wsBalance > 0) {\n (uint256 osDepositAmount, ) = _deposit(wsBalance);\n\n // Ensure solvency of the vault\n _solvencyAssert();\n\n // Emit event for the deposited wS tokens\n emit Deposit(ws, pool, wsBalance);\n // Emit event for the minted OS tokens\n emit Deposit(os, pool, osDepositAmount);\n }\n }\n\n /**\n * @dev Mint OS in proportion to the pool's wS and OS reserves,\n * transfer Wrapped S (wS) and OS to the pool,\n * mint the pool's LP token and deposit in the gauge.\n * @param _wsAmount Amount of Wrapped S (wS) tokens to deposit.\n * @return osDepositAmount Amount of OS tokens minted and deposited into the pool.\n * @return lpTokens Amount of SwapX pool LP tokens minted and deposited into the gauge.\n */\n function _deposit(uint256 _wsAmount)\n internal\n returns (uint256 osDepositAmount, uint256 lpTokens)\n {\n // Calculate the required amount of OS to mint based on the wS amount.\n osDepositAmount = _calcTokensToMint(_wsAmount);\n\n // Mint the required OS tokens to this strategy\n IVault(vaultAddress).mintForStrategy(osDepositAmount);\n\n // Add wS and OS liquidity to the pool and stake in gauge\n lpTokens = _depositToPoolAndGauge(_wsAmount, osDepositAmount);\n }\n\n /***************************************\n Withdraw\n ****************************************/\n\n /**\n * @notice Withdraw wS and OS from the SwapX pool, burn the OS,\n * and transfer the wS to the recipient.\n * @param _recipient Address of the Vault.\n * @param _asset Address of the Wrapped S (wS) contract.\n * @param _wsAmount Amount of Wrapped S (wS) to withdraw.\n */\n function withdraw(\n address _recipient,\n address _asset,\n uint256 _wsAmount\n ) external override onlyVault nonReentrant skimPool {\n require(_wsAmount > 0, \"Must withdraw something\");\n require(_asset == ws, \"Unsupported asset\");\n // This strategy can't be set as a default strategy for wS in the Vault.\n // This means the recipient must always be the Vault.\n require(_recipient == vaultAddress, \"Only withdraw to vault allowed\");\n\n // Calculate how much pool LP tokens to burn to get the required amount of wS tokens back\n uint256 lpTokens = _calcTokensToBurn(_wsAmount);\n\n // Withdraw pool LP tokens from the gauge and remove assets from from the pool\n _withdrawFromGaugeAndPool(lpTokens);\n\n // Burn all the removed OS and any that was left in the strategy\n uint256 osToBurn = IERC20(os).balanceOf(address(this));\n IVault(vaultAddress).burnForStrategy(osToBurn);\n\n // Transfer wS to the recipient\n // Note there can be a dust amount of wS left in the strategy as\n // the burn of the pool's LP tokens is rounded up\n require(\n IERC20(ws).balanceOf(address(this)) >= _wsAmount,\n \"Not enough wS removed from pool\"\n );\n IERC20(ws).safeTransfer(_recipient, _wsAmount);\n\n // Ensure solvency of the vault\n _solvencyAssert();\n\n // Emit event for the withdrawn wS tokens\n emit Withdrawal(ws, pool, _wsAmount);\n // Emit event for the burnt OS tokens\n emit Withdrawal(os, pool, osToBurn);\n }\n\n /**\n * @notice Withdraw all pool LP tokens from the gauge,\n * remove all wS and OS from the SwapX pool,\n * burn all the OS tokens,\n * and transfer all the wS to the Vault contract.\n * @dev There is no solvency check here as withdrawAll can be called to\n * quickly secure assets to the Vault in emergencies.\n */\n function withdrawAll()\n external\n override\n onlyVaultOrGovernor\n nonReentrant\n skimPool\n {\n // Get all the pool LP tokens the strategy has staked in the gauge\n uint256 lpTokens = IGauge(gauge).balanceOf(address(this));\n // Can not withdraw zero LP tokens from the gauge\n if (lpTokens == 0) return;\n\n if (IGauge(gauge).emergency()) {\n // The gauge is in emergency mode\n _emergencyWithdrawFromGaugeAndPool();\n } else {\n // Withdraw pool LP tokens from the gauge and remove assets from from the pool\n _withdrawFromGaugeAndPool(lpTokens);\n }\n\n // Burn all OS in this strategy contract\n uint256 osToBurn = IERC20(os).balanceOf(address(this));\n IVault(vaultAddress).burnForStrategy(osToBurn);\n\n // Get the strategy contract's wS balance.\n // This includes all that was removed from the SwapX pool and\n // any that was sitting in the strategy contract before the removal.\n uint256 wsBalance = IERC20(ws).balanceOf(address(this));\n IERC20(ws).safeTransfer(vaultAddress, wsBalance);\n\n // Emit event for the withdrawn wS tokens\n emit Withdrawal(ws, pool, wsBalance);\n // Emit event for the burnt OS tokens\n emit Withdrawal(os, pool, osToBurn);\n }\n\n /***************************************\n Pool Rebalancing\n ****************************************/\n\n /** @notice Used when there is more OS than wS in the pool.\n * wS and OS is removed from the pool, the received wS is swapped for OS\n * and the left over OS in the strategy is burnt.\n * The OS/wS price is < 1.0 so OS is being bought at a discount.\n * @param _wsAmount Amount of Wrapped S (wS) to swap into the pool.\n */\n function swapAssetsToPool(uint256 _wsAmount)\n external\n onlyStrategist\n nonReentrant\n improvePoolBalance\n skimPool\n {\n require(_wsAmount > 0, \"Must swap something\");\n\n // 1. Partially remove liquidity so there’s enough wS for the swap\n\n // Calculate how much pool LP tokens to burn to get the required amount of wS tokens back\n uint256 lpTokens = _calcTokensToBurn(_wsAmount);\n require(lpTokens > 0, \"No LP tokens to burn\");\n\n _withdrawFromGaugeAndPool(lpTokens);\n\n // 2. Swap wS for OS against the pool\n // Swap exact amount of wS for OS against the pool\n // There can be a dust amount of wS left in the strategy as the burn of the pool's LP tokens is rounded up\n _swapExactTokensForTokens(_wsAmount, ws, os);\n\n // 3. Burn all the OS left in the strategy from the remove liquidity and swap\n uint256 osToBurn = IERC20(os).balanceOf(address(this));\n IVault(vaultAddress).burnForStrategy(osToBurn);\n\n // Ensure solvency of the vault\n _solvencyAssert();\n\n // Emit event for the burnt OS tokens\n emit Withdrawal(os, pool, osToBurn);\n // Emit event for the swap\n emit SwapAssetsToPool(_wsAmount, lpTokens, osToBurn);\n }\n\n /**\n * @notice Used when there is more wS than OS in the pool.\n * OS is minted and swapped for wS against the pool,\n * more OS is minted and added back into the pool with the swapped out wS.\n * The OS/wS price is > 1.0 so OS is being sold at a premium.\n * @param _osAmount Amount of OS to swap into the pool.\n */\n function swapOTokensToPool(uint256 _osAmount)\n external\n onlyStrategist\n nonReentrant\n improvePoolBalance\n skimPool\n {\n require(_osAmount > 0, \"Must swap something\");\n\n // 1. Mint OS so it can be swapped into the pool\n\n // There can be OS in the strategy from skimming the pool\n uint256 osInStrategy = IERC20(os).balanceOf(address(this));\n require(_osAmount >= osInStrategy, \"Too much OS in strategy\");\n uint256 osToMint = _osAmount - osInStrategy;\n\n // Mint the required OS tokens to this strategy\n IVault(vaultAddress).mintForStrategy(osToMint);\n\n // 2. Swap OS for wS against the pool\n _swapExactTokensForTokens(_osAmount, os, ws);\n\n // The wS is from the swap and any wS that was sitting in the strategy\n uint256 wsDepositAmount = IERC20(ws).balanceOf(address(this));\n\n // 3. Add wS and OS back to the pool in proportion to the pool's reserves\n (uint256 osDepositAmount, uint256 lpTokens) = _deposit(wsDepositAmount);\n\n // Ensure solvency of the vault\n _solvencyAssert();\n\n // Emit event for the minted OS tokens\n emit Deposit(os, pool, osToMint + osDepositAmount);\n // Emit event for the swap\n emit SwapOTokensToPool(\n osToMint,\n wsDepositAmount,\n osDepositAmount,\n lpTokens\n );\n }\n\n /***************************************\n Assets and Rewards\n ****************************************/\n\n /**\n * @notice Get the wS value of assets in the strategy and SwapX pool.\n * The value of the assets in the pool is calculated assuming the pool is balanced.\n * This way the value can not be manipulated by changing the pool's token balances.\n * @param _asset Address of the Wrapped S (wS) token\n * @return balance Total value in wS.\n */\n function checkBalance(address _asset)\n external\n view\n override\n returns (uint256 balance)\n {\n require(_asset == ws, \"Unsupported asset\");\n\n // wS balance needed here for the balance check that happens from vault during depositing.\n balance = IERC20(ws).balanceOf(address(this));\n\n // This assumes 1 gauge LP token = 1 pool LP token\n uint256 lpTokens = IGauge(gauge).balanceOf(address(this));\n if (lpTokens == 0) return balance;\n\n // Add the strategy’s share of the wS and OS tokens in the SwapX pool if the pool was balanced.\n balance += _lpValue(lpTokens);\n }\n\n /**\n * @notice Returns bool indicating whether asset is supported by strategy\n * @param _asset Address of the asset\n */\n function supportsAsset(address _asset) public view override returns (bool) {\n return _asset == ws;\n }\n\n /**\n * @notice Collect accumulated SWPx (and other) rewards and send to the Harvester.\n */\n function collectRewardTokens()\n external\n override\n onlyHarvester\n nonReentrant\n {\n // Collect SWPx rewards from the gauge\n IGauge(gauge).getReward();\n\n _collectRewardTokens();\n }\n\n /***************************************\n Internal SwapX Pool and Gauge Functions\n ****************************************/\n\n /**\n * @dev Calculate the required amount of OS to mint based on the wS amount.\n * This ensures the proportion of OS tokens being added to the pool matches the proportion of wS tokens.\n * For example, if the added wS tokens is 10% of existing wS tokens in the pool,\n * then the OS tokens being added should also be 10% of the OS tokens in the pool.\n * @param _wsAmount Amount of Wrapped S (wS) to be added to the pool.\n * @return osAmount Amount of OS to be minted and added to the pool.\n */\n function _calcTokensToMint(uint256 _wsAmount)\n internal\n view\n returns (uint256 osAmount)\n {\n (uint256 wsReserves, uint256 osReserves, ) = IPair(pool).getReserves();\n require(wsReserves > 0, \"Empty pool\");\n\n // OS to add = (wS being added * OS in pool) / wS in pool\n osAmount = (_wsAmount * osReserves) / wsReserves;\n }\n\n /**\n * @dev Calculate how much pool LP tokens to burn to get the required amount of wS tokens back\n * from the pool.\n * @param _wsAmount Amount of Wrapped S (wS) to be removed from the pool.\n * @return lpTokens Amount of SwapX pool LP tokens to burn.\n */\n function _calcTokensToBurn(uint256 _wsAmount)\n internal\n view\n returns (uint256 lpTokens)\n {\n /* The SwapX pool proportionally returns the reserve tokens when removing liquidity.\n * First, calculate the proportion of required wS tokens against the pools wS reserves.\n * That same proportion is used to calculate the required amount of pool LP tokens.\n * For example, if the required wS tokens is 10% of the pool's wS reserves,\n * then 10% of the pool's LP supply needs to be burned.\n *\n * Because we are doing balanced removal we should be making profit when removing liquidity in a\n * pool tilted to either side.\n *\n * Important: A downside is that the Strategist / Governor needs to be\n * cognizant of not removing too much liquidity. And while the proposal to remove liquidity\n * is being voted on, the pool tilt might change so much that the proposal that has been valid while\n * created is no longer valid.\n */\n\n (uint256 wsReserves, , ) = IPair(pool).getReserves();\n require(wsReserves > 0, \"Empty pool\");\n\n lpTokens = (_wsAmount * IPair(pool).totalSupply()) / wsReserves;\n lpTokens += 1; // Add 1 to ensure we get enough LP tokens with rounding\n }\n\n /**\n * @dev Deposit Wrapped S (wS) and OS liquidity to the SwapX pool\n * and stake the pool's LP token in the gauge.\n * @param _wsAmount Amount of Wrapped S (wS) to deposit.\n * @param _osAmount Amount of OS to deposit.\n * @return lpTokens Amount of SwapX pool LP tokens minted.\n */\n function _depositToPoolAndGauge(uint256 _wsAmount, uint256 _osAmount)\n internal\n returns (uint256 lpTokens)\n {\n // Transfer wS to the pool\n IERC20(ws).safeTransfer(pool, _wsAmount);\n // Transfer OS to the pool\n IERC20(os).safeTransfer(pool, _osAmount);\n\n // Mint LP tokens from the pool\n lpTokens = IPair(pool).mint(address(this));\n\n // Deposit the pool's LP tokens into the gauge\n IGauge(gauge).deposit(lpTokens);\n }\n\n /**\n * @dev Withdraw pool LP tokens from the gauge and remove wS and OS from the pool.\n * @param _lpTokens Amount of SwapX pool LP tokens to withdraw from the gauge\n */\n function _withdrawFromGaugeAndPool(uint256 _lpTokens) internal {\n require(\n IGauge(gauge).balanceOf(address(this)) >= _lpTokens,\n \"Not enough LP tokens in gauge\"\n );\n\n // Withdraw pool LP tokens from the gauge\n IGauge(gauge).withdraw(_lpTokens);\n\n // Transfer the pool LP tokens to the pool\n IERC20(pool).safeTransfer(pool, _lpTokens);\n\n // Burn the LP tokens and transfer the wS and OS back to the strategy\n IPair(pool).burn(address(this));\n }\n\n /**\n * @dev Withdraw all pool LP tokens from the gauge when it's in emergency mode\n * and remove wS and OS from the pool.\n */\n function _emergencyWithdrawFromGaugeAndPool() internal {\n // Withdraw all pool LP tokens from the gauge\n IGauge(gauge).emergencyWithdraw();\n\n // Get the pool LP tokens in strategy\n uint256 _lpTokens = IERC20(pool).balanceOf(address(this));\n\n // Transfer the pool LP tokens to the pool\n IERC20(pool).safeTransfer(pool, _lpTokens);\n\n // Burn the LP tokens and transfer the wS and OS back to the strategy\n IPair(pool).burn(address(this));\n }\n\n /**\n * @dev Swap exact amount of tokens for another token against the pool.\n * @param _amountIn Amount of tokens to swap into the pool.\n * @param _tokenIn Address of the token going into the pool.\n * @param _tokenOut Address of the token being swapped out of the pool.\n */\n function _swapExactTokensForTokens(\n uint256 _amountIn,\n address _tokenIn,\n address _tokenOut\n ) internal {\n // Transfer in tokens to the pool\n IERC20(_tokenIn).safeTransfer(pool, _amountIn);\n\n // Calculate how much out tokens we get from the swap\n uint256 amountOut = IPair(pool).getAmountOut(_amountIn, _tokenIn);\n\n // Safety check that we are dealing with the correct pool tokens\n require(\n (_tokenIn == ws && _tokenOut == os) ||\n (_tokenIn == os && _tokenOut == ws),\n \"Unsupported swap\"\n );\n\n // Work out the correct order of the amounts for the pool\n (uint256 amount0, uint256 amount1) = _tokenIn == ws\n ? (uint256(0), amountOut)\n : (amountOut, 0);\n\n // Perform the swap on the pool\n IPair(pool).swap(amount0, amount1, address(this), new bytes(0));\n\n // The slippage protection against the amount out is indirectly done\n // via the improvePoolBalance\n }\n\n /// @dev Calculate the value of a LP position in a SwapX stable pool\n /// if the pool was balanced.\n /// @param _lpTokens Amount of LP tokens in the SwapX pool\n /// @return value The wS value of the LP tokens when the pool is balanced\n function _lpValue(uint256 _lpTokens) internal view returns (uint256 value) {\n // Get total supply of LP tokens\n uint256 totalSupply = IPair(pool).totalSupply();\n if (totalSupply == 0) return 0;\n\n // Get the current reserves of the pool\n (uint256 wsReserves, uint256 osReserves, ) = IPair(pool).getReserves();\n\n // Calculate the invariant of the pool assuming both tokens have 18 decimals.\n // k is scaled to 18 decimals.\n uint256 k = _invariant(wsReserves, osReserves);\n\n // If x = y, let’s denote x = y = z (where z is the common reserve value)\n // Substitute z into the invariant:\n // k = z^3 * z + z * z^3\n // k = 2 * z^4\n // Going back the other way to calculate the common reserve value z\n // z = (k / 2) ^ (1/4)\n // the total value of the pool when x = y is 2 * z, which is 2 * (k / 2) ^ (1/4)\n uint256 zSquared = sqrt((k * 1e18) / 2); // 18 + 18 = 36 decimals becomes 18 decimals after sqrt\n uint256 z = sqrt(zSquared * 1e18); // 18 + 18 = 36 decimals becomes 18 decimals after sqrt\n uint256 totalValueOfPool = 2 * z;\n\n // lp value = lp tokens * value of pool / total supply\n value = (_lpTokens * totalValueOfPool) / totalSupply;\n }\n\n /**\n * @dev Compute the invariant for a SwapX stable pool.\n * This assumed both x and y tokens are to 18 decimals which is checked in the constructor.\n * invariant: k = x^3 * y + x * y^3\n * @dev This implementation is copied from SwapX's Pair contract.\n * @param _x The amount of Wrapped S (wS) tokens in the pool\n * @param _y The amount of the OS tokens in the pool\n * @return k The invariant of the SwapX stable pool\n */\n function _invariant(uint256 _x, uint256 _y)\n internal\n pure\n returns (uint256 k)\n {\n uint256 _a = (_x * _y) / PRECISION;\n uint256 _b = ((_x * _x) / PRECISION + (_y * _y) / PRECISION);\n // slither-disable-next-line divide-before-multiply\n k = (_a * _b) / PRECISION;\n }\n\n /**\n * @dev Checks that the protocol is solvent, protecting from a rogue Strategist / Guardian that can\n * keep rebalancing the pool in both directions making the protocol lose a tiny amount of\n * funds each time.\n *\n * Protocol must be at least SOLVENCY_THRESHOLD (99,8 %) backed in order for the rebalances to\n * function.\n */\n function _solvencyAssert() internal view {\n uint256 _totalVaultValue = IVault(vaultAddress).totalValue();\n uint256 _totalSupply = IERC20(os).totalSupply();\n\n if (\n _totalSupply > 0 &&\n _totalVaultValue.divPrecisely(_totalSupply) < SOLVENCY_THRESHOLD\n ) {\n revert(\"Protocol insolvent\");\n }\n }\n\n /***************************************\n Setters\n ****************************************/\n\n /**\n * @notice Set the maximum deviation from the OS/wS peg (1e18) before deposits are reverted.\n * @param _maxDepeg the OS/wS price from peg (1e18) in 18 decimals.\n * eg 0.01e18 or 1e16 is 1% which is 100 basis points.\n */\n function setMaxDepeg(uint256 _maxDepeg) external onlyGovernor {\n maxDepeg = _maxDepeg;\n\n emit MaxDepegUpdated(_maxDepeg);\n }\n\n /***************************************\n Approvals\n ****************************************/\n\n /**\n * @notice Approve the spending of all assets by their corresponding pool tokens,\n * if for some reason is it necessary.\n */\n function safeApproveAllTokens()\n external\n override\n onlyGovernor\n nonReentrant\n {\n _approveBase();\n }\n\n // solhint-disable-next-line no-unused-vars\n function _abstractSetPToken(address _asset, address _pToken)\n internal\n override\n {}\n\n function _approveBase() internal {\n // Approve SwapX gauge contract to transfer SwapX pool LP tokens\n // This is needed for deposits of SwapX pool LP tokens into the gauge.\n // slither-disable-next-line unused-return\n IPair(pool).approve(address(gauge), type(uint256).max);\n }\n}\n" + }, + "contracts/strategies/sonic/SonicValidatorDelegator.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { IERC20, InitializableAbstractStrategy } from \"../../utils/InitializableAbstractStrategy.sol\";\nimport { IVault } from \"../../interfaces/IVault.sol\";\nimport { ISFC } from \"../../interfaces/sonic/ISFC.sol\";\nimport { IWrappedSonic } from \"../../interfaces/sonic/IWrappedSonic.sol\";\n\n/**\n * @title Manages delegation to Sonic validators\n * @notice This contract implements all the required functionality to delegate to,\n undelegate from and withdraw from validators.\n * @author Origin Protocol Inc\n */\nabstract contract SonicValidatorDelegator is InitializableAbstractStrategy {\n /// @notice Address of Sonic's wrapped S token\n address public immutable wrappedSonic;\n /// @notice Sonic's Special Fee Contract (SFC)\n ISFC public immutable sfc;\n\n /// @notice a unique ID for each withdrawal request\n uint256 public nextWithdrawId;\n /// @notice Sonic (S) that is pending withdrawal after undelegating\n uint256 public pendingWithdrawals;\n\n /// @notice List of supported validator IDs that can be delegated to\n uint256[] public supportedValidators;\n\n /// @notice Default validator id to deposit to\n uint256 public defaultValidatorId;\n\n struct WithdrawRequest {\n uint256 validatorId;\n uint256 undelegatedAmount;\n uint256 timestamp;\n }\n /// @notice Mapping of withdrawIds to validatorIds and undelegatedAmounts\n mapping(uint256 => WithdrawRequest) public withdrawals;\n\n /// @notice Address of the registrator - allowed to register, exit and remove validators\n address public validatorRegistrator;\n\n // For future use\n uint256[44] private __gap;\n\n event Delegated(uint256 indexed validatorId, uint256 delegatedAmount);\n event Undelegated(\n uint256 indexed withdrawId,\n uint256 indexed validatorId,\n uint256 undelegatedAmount\n );\n event Withdrawn(\n uint256 indexed withdrawId,\n uint256 indexed validatorId,\n uint256 undelegatedAmount,\n uint256 withdrawnAmount\n );\n event RegistratorChanged(address indexed newAddress);\n event SupportedValidator(uint256 indexed validatorId);\n event UnsupportedValidator(uint256 indexed validatorId);\n event DefaultValidatorIdChanged(uint256 indexed validatorId);\n\n /// @dev Throws if called by any account other than the Registrator or Strategist\n modifier onlyRegistratorOrStrategist() {\n require(\n msg.sender == validatorRegistrator ||\n msg.sender == IVault(vaultAddress).strategistAddr(),\n \"Caller is not the Registrator or Strategist\"\n );\n _;\n }\n\n constructor(\n BaseStrategyConfig memory _baseConfig,\n address _wrappedSonic,\n address _sfc\n ) InitializableAbstractStrategy(_baseConfig) {\n wrappedSonic = _wrappedSonic;\n sfc = ISFC(_sfc);\n }\n\n function initialize() external virtual onlyGovernor initializer {\n address[] memory rewardTokens = new address[](0);\n address[] memory assets = new address[](1);\n address[] memory pTokens = new address[](1);\n\n assets[0] = address(wrappedSonic);\n pTokens[0] = address(platformAddress);\n\n InitializableAbstractStrategy._initialize(\n rewardTokens,\n assets,\n pTokens\n );\n }\n\n /// @notice Returns the total value of Sonic (S) that is delegated validators.\n /// Wrapped Sonic (wS) deposits that are still to be delegated and any undelegated amounts\n /// still pending a withdrawal.\n /// @param _asset Address of Wrapped Sonic (wS) token\n /// @return balance Total value managed by the strategy\n function checkBalance(address _asset)\n external\n view\n virtual\n override\n returns (uint256 balance)\n {\n require(_asset == wrappedSonic, \"Unsupported asset\");\n\n // add the Wrapped Sonic (wS) in the strategy from deposits that are still to be delegated\n // and any undelegated amounts still pending a withdrawal\n balance =\n IERC20(wrappedSonic).balanceOf(address(this)) +\n pendingWithdrawals;\n\n // For each supported validator, get the staked amount and pending rewards\n uint256 validatorLen = supportedValidators.length;\n for (uint256 i = 0; i < validatorLen; i++) {\n uint256 validator = supportedValidators[i];\n balance += sfc.getStake(address(this), validator);\n balance += sfc.pendingRewards(address(this), validator);\n }\n }\n\n /**\n * @dev Delegate from this strategy to a specific Sonic validator. Called\n * automatically on asset deposit\n * @param _amount the amount of Sonic (S) to delegate.\n */\n function _delegate(uint256 _amount) internal {\n require(\n isSupportedValidator(defaultValidatorId),\n \"Validator not supported\"\n );\n\n // unwrap Wrapped Sonic (wS) to native Sonic (S)\n IWrappedSonic(wrappedSonic).withdraw(_amount);\n\n //slither-disable-next-line arbitrary-send-eth\n sfc.delegate{ value: _amount }(defaultValidatorId);\n\n emit Delegated(defaultValidatorId, _amount);\n }\n\n /**\n * @notice Undelegate from a specific Sonic validator.\n * This needs to be followed by a `withdrawFromSFC` two weeks later.\n * @param _validatorId The Sonic validator ID to undelegate from.\n * @param _undelegateAmount the amount of Sonic (S) to undelegate.\n * @return withdrawId The unique ID of the withdrawal request.\n */\n function undelegate(uint256 _validatorId, uint256 _undelegateAmount)\n external\n onlyRegistratorOrStrategist\n nonReentrant\n returns (uint256 withdrawId)\n {\n withdrawId = _undelegate(_validatorId, _undelegateAmount);\n }\n\n function _undelegate(uint256 _validatorId, uint256 _undelegateAmount)\n internal\n returns (uint256 withdrawId)\n {\n // Can still undelegate even if the validator is no longer supported\n require(_undelegateAmount > 0, \"Must undelegate something\");\n\n uint256 amountDelegated = sfc.getStake(address(this), _validatorId);\n require(\n _undelegateAmount <= amountDelegated,\n \"Insufficient delegation\"\n );\n\n withdrawId = nextWithdrawId++;\n\n withdrawals[withdrawId] = WithdrawRequest(\n _validatorId,\n _undelegateAmount,\n block.timestamp\n );\n pendingWithdrawals += _undelegateAmount;\n\n sfc.undelegate(_validatorId, withdrawId, _undelegateAmount);\n\n emit Undelegated(withdrawId, _validatorId, _undelegateAmount);\n }\n\n /**\n * @notice Withdraw native S from a previously undelegated validator.\n * The native S is wrapped wS and transferred to the Vault.\n * @param _withdrawId The unique withdraw ID used to `undelegate`\n * @return withdrawnAmount The amount of Sonic (S) withdrawn.\n * This can be less than the undelegated amount in the event of slashing.\n */\n function withdrawFromSFC(uint256 _withdrawId)\n external\n onlyRegistratorOrStrategist\n nonReentrant\n returns (uint256 withdrawnAmount)\n {\n require(_withdrawId < nextWithdrawId, \"Invalid withdrawId\");\n\n // Can still withdraw even if the validator is no longer supported\n // Load the withdrawal from storage into memory\n WithdrawRequest memory withdrawal = withdrawals[_withdrawId];\n require(!isWithdrawnFromSFC(_withdrawId), \"Already withdrawn\");\n\n withdrawals[_withdrawId].undelegatedAmount = 0;\n pendingWithdrawals -= withdrawal.undelegatedAmount;\n\n uint256 sBalanceBefore = address(this).balance;\n\n // Try to withdraw from SFC\n try sfc.withdraw(withdrawal.validatorId, _withdrawId) {\n // continue below\n } catch (bytes memory err) {\n bytes4 errorSelector = bytes4(err);\n\n // If the validator has been fully slashed, SFC's withdraw function will\n // revert with a StakeIsFullySlashed custom error.\n if (errorSelector == ISFC.StakeIsFullySlashed.selector) {\n // The validator was fully slashed, so all the delegated amounts were lost.\n // Will swallow the error as we still want to update the\n // withdrawals and pendingWithdrawals storage variables.\n\n // The return param defaults to zero but lets set it explicitly so it's clear\n withdrawnAmount = 0;\n\n emit Withdrawn(\n _withdrawId,\n withdrawal.validatorId,\n withdrawal.undelegatedAmount,\n withdrawnAmount\n );\n\n // Exit here as there is nothing to transfer to the Vault\n return withdrawnAmount;\n } else {\n // Bubble up any other SFC custom errors.\n // Inline assembly is currently the only way to generically rethrow the exact same custom error\n // from the raw bytes err in a catch block while preserving its original selector and parameters.\n // solhint-disable-next-line no-inline-assembly\n assembly {\n revert(add(32, err), mload(err))\n }\n }\n }\n\n // Set return parameter\n withdrawnAmount = address(this).balance - sBalanceBefore;\n\n // Wrap Sonic (S) to Wrapped Sonic (wS)\n IWrappedSonic(wrappedSonic).deposit{ value: withdrawnAmount }();\n\n // Transfer the Wrapped Sonic (wS) to the Vault\n _withdraw(vaultAddress, wrappedSonic, withdrawnAmount);\n\n // withdrawal.undelegatedAmount & withdrawnAmount can differ in case of slashing\n emit Withdrawn(\n _withdrawId,\n withdrawal.validatorId,\n withdrawal.undelegatedAmount,\n withdrawnAmount\n );\n }\n\n /// @notice returns a bool whether a withdrawalId has already been withdrawn or not\n /// @param _withdrawId The unique withdraw ID used to `undelegate`\n function isWithdrawnFromSFC(uint256 _withdrawId)\n public\n view\n returns (bool)\n {\n WithdrawRequest memory withdrawal = withdrawals[_withdrawId];\n require(withdrawal.validatorId > 0, \"Invalid withdrawId\");\n return withdrawal.undelegatedAmount == 0;\n }\n\n /**\n * @notice Restake any pending validator rewards for all supported validators\n * @param _validatorIds List of Sonic validator IDs to restake rewards\n */\n function restakeRewards(uint256[] calldata _validatorIds)\n external\n nonReentrant\n {\n for (uint256 i = 0; i < _validatorIds.length; ++i) {\n require(\n isSupportedValidator(_validatorIds[i]),\n \"Validator not supported\"\n );\n\n uint256 rewards = sfc.pendingRewards(\n address(this),\n _validatorIds[i]\n );\n\n if (rewards > 0) {\n sfc.restakeRewards(_validatorIds[i]);\n }\n }\n\n // The SFC contract will emit Delegated and RestakedRewards events.\n // The checkBalance function should not change as the pending rewards will moved to the staked amount.\n }\n\n /**\n * @notice Claim any pending rewards from validators\n * @param _validatorIds List of Sonic validator IDs to claim rewards\n */\n function collectRewards(uint256[] calldata _validatorIds)\n external\n onlyRegistratorOrStrategist\n nonReentrant\n {\n uint256 sBalanceBefore = address(this).balance;\n\n for (uint256 i = 0; i < _validatorIds.length; ++i) {\n uint256 rewards = sfc.pendingRewards(\n address(this),\n _validatorIds[i]\n );\n\n if (rewards > 0) {\n // The SFC contract will emit ClaimedRewards(delegator (this), validatorId, rewards)\n sfc.claimRewards(_validatorIds[i]);\n }\n }\n\n uint256 rewardsAmount = address(this).balance - sBalanceBefore;\n\n // Wrap Sonic (S) to Wrapped Sonic (wS)\n IWrappedSonic(wrappedSonic).deposit{ value: rewardsAmount }();\n\n // Transfer the Wrapped Sonic (wS) to the Vault\n _withdraw(vaultAddress, wrappedSonic, rewardsAmount);\n }\n\n /**\n * @notice To receive native S from SFC and Wrapped Sonic (wS)\n *\n * @dev This does not prevent donating S tokens to the contract\n * as wrappedSonic has a `withdrawTo` function where a third party\n * owner of wrappedSonic can withdraw to this contract.\n */\n receive() external payable {\n require(\n msg.sender == address(sfc) || msg.sender == wrappedSonic,\n \"S not from allowed contracts\"\n );\n }\n\n /***************************************\n Admin functions\n ****************************************/\n\n /// @notice Set the address of the Registrator which can undelegate, withdraw and collect rewards\n /// @param _validatorRegistrator The address of the Registrator\n function setRegistrator(address _validatorRegistrator)\n external\n onlyGovernor\n {\n validatorRegistrator = _validatorRegistrator;\n emit RegistratorChanged(_validatorRegistrator);\n }\n\n /// @notice Set the default validatorId to delegate to on deposit\n /// @param _validatorId The validator identifier. eg 18\n function setDefaultValidatorId(uint256 _validatorId)\n external\n onlyRegistratorOrStrategist\n {\n require(isSupportedValidator(_validatorId), \"Validator not supported\");\n defaultValidatorId = _validatorId;\n emit DefaultValidatorIdChanged(_validatorId);\n }\n\n /// @notice Allows a validator to be delegated to by the Registrator\n /// @param _validatorId The validator identifier. eg 18\n function supportValidator(uint256 _validatorId) external onlyGovernor {\n require(\n !isSupportedValidator(_validatorId),\n \"Validator already supported\"\n );\n\n supportedValidators.push(_validatorId);\n\n emit SupportedValidator(_validatorId);\n }\n\n /// @notice Removes a validator from the supported list.\n /// Unsupported validators can still be undelegated from, withdrawn from and rewards collected.\n /// @param _validatorId The validator identifier. eg 18\n function unsupportValidator(uint256 _validatorId) external onlyGovernor {\n require(isSupportedValidator(_validatorId), \"Validator not supported\");\n\n uint256 validatorLen = supportedValidators.length;\n for (uint256 i = 0; i < validatorLen; ++i) {\n if (supportedValidators[i] == _validatorId) {\n supportedValidators[i] = supportedValidators[validatorLen - 1];\n supportedValidators.pop();\n break;\n }\n }\n\n uint256 stake = sfc.getStake(address(this), _validatorId);\n\n // undelegate if validator still has funds staked\n if (stake > 0) {\n _undelegate(_validatorId, stake);\n }\n emit UnsupportedValidator(_validatorId);\n }\n\n /// @notice Returns the length of the supportedValidators array\n function supportedValidatorsLength() external view returns (uint256) {\n return supportedValidators.length;\n }\n\n /// @notice Returns whether a validator is supported by this strategy\n /// @param _validatorId The validator identifier\n function isSupportedValidator(uint256 _validatorId)\n public\n view\n returns (bool)\n {\n uint256 validatorLen = supportedValidators.length;\n for (uint256 i = 0; i < validatorLen; ++i) {\n if (supportedValidators[i] == _validatorId) {\n return true;\n }\n }\n return false;\n }\n\n function _withdraw(\n address _recipient,\n address _asset,\n uint256 _amount\n ) internal virtual;\n}\n" + }, + "contracts/token/OUSD.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title OUSD Token Contract\n * @dev ERC20 compatible contract for OUSD\n * @dev Implements an elastic supply\n * @author Origin Protocol Inc\n */\nimport { IVault } from \"../interfaces/IVault.sol\";\nimport { Governable } from \"../governance/Governable.sol\";\nimport { SafeCast } from \"@openzeppelin/contracts/utils/math/SafeCast.sol\";\n\ncontract OUSD is Governable {\n using SafeCast for int256;\n using SafeCast for uint256;\n\n /// @dev Event triggered when the supply changes\n /// @param totalSupply Updated token total supply\n /// @param rebasingCredits Updated token rebasing credits\n /// @param rebasingCreditsPerToken Updated token rebasing credits per token\n event TotalSupplyUpdatedHighres(\n uint256 totalSupply,\n uint256 rebasingCredits,\n uint256 rebasingCreditsPerToken\n );\n /// @dev Event triggered when an account opts in for rebasing\n /// @param account Address of the account\n event AccountRebasingEnabled(address account);\n /// @dev Event triggered when an account opts out of rebasing\n /// @param account Address of the account\n event AccountRebasingDisabled(address account);\n /// @dev Emitted when `value` tokens are moved from one account `from` to\n /// another `to`.\n /// @param from Address of the account tokens are moved from\n /// @param to Address of the account tokens are moved to\n /// @param value Amount of tokens transferred\n event Transfer(address indexed from, address indexed to, uint256 value);\n /// @dev Emitted when the allowance of a `spender` for an `owner` is set by\n /// a call to {approve}. `value` is the new allowance.\n /// @param owner Address of the owner approving allowance\n /// @param spender Address of the spender allowance is granted to\n /// @param value Amount of tokens spender can transfer\n event Approval(\n address indexed owner,\n address indexed spender,\n uint256 value\n );\n /// @dev Yield resulting from {changeSupply} that a `source` account would\n /// receive is directed to `target` account.\n /// @param source Address of the source forwarding the yield\n /// @param target Address of the target receiving the yield\n event YieldDelegated(address source, address target);\n /// @dev Yield delegation from `source` account to the `target` account is\n /// suspended.\n /// @param source Address of the source suspending yield forwarding\n /// @param target Address of the target no longer receiving yield from `source`\n /// account\n event YieldUndelegated(address source, address target);\n\n enum RebaseOptions {\n NotSet,\n StdNonRebasing,\n StdRebasing,\n YieldDelegationSource,\n YieldDelegationTarget\n }\n\n uint256[154] private _gap; // Slots to align with deployed contract\n uint256 private constant MAX_SUPPLY = type(uint128).max;\n /// @dev The amount of tokens in existence\n uint256 public totalSupply;\n mapping(address => mapping(address => uint256)) private allowances;\n /// @dev The vault with privileges to execute {mint}, {burn}\n /// and {changeSupply}\n address public vaultAddress;\n mapping(address => uint256) internal creditBalances;\n // the 2 storage variables below need trailing underscores to not name collide with public functions\n uint256 private rebasingCredits_; // Sum of all rebasing credits (creditBalances for rebasing accounts)\n uint256 private rebasingCreditsPerToken_;\n /// @dev The amount of tokens that are not rebasing - receiving yield\n uint256 public nonRebasingSupply;\n mapping(address => uint256) internal alternativeCreditsPerToken;\n /// @dev A map of all addresses and their respective RebaseOptions\n mapping(address => RebaseOptions) public rebaseState;\n mapping(address => uint256) private __deprecated_isUpgraded;\n /// @dev A map of addresses that have yields forwarded to. This is an\n /// inverse mapping of {yieldFrom}\n /// Key Account forwarding yield\n /// Value Account receiving yield\n mapping(address => address) public yieldTo;\n /// @dev A map of addresses that are receiving the yield. This is an\n /// inverse mapping of {yieldTo}\n /// Key Account receiving yield\n /// Value Account forwarding yield\n mapping(address => address) public yieldFrom;\n\n uint256 private constant RESOLUTION_INCREASE = 1e9;\n uint256[34] private __gap; // including below gap totals up to 200\n\n /// @dev Verifies that the caller is the Governor or Strategist.\n modifier onlyGovernorOrStrategist() {\n require(\n isGovernor() || msg.sender == IVault(vaultAddress).strategistAddr(),\n \"Caller is not the Strategist or Governor\"\n );\n _;\n }\n\n /// @dev Initializes the contract and sets necessary variables.\n /// @param _vaultAddress Address of the vault contract\n /// @param _initialCreditsPerToken The starting rebasing credits per token.\n function initialize(address _vaultAddress, uint256 _initialCreditsPerToken)\n external\n onlyGovernor\n {\n require(_vaultAddress != address(0), \"Zero vault address\");\n require(vaultAddress == address(0), \"Already initialized\");\n\n rebasingCreditsPerToken_ = _initialCreditsPerToken;\n vaultAddress = _vaultAddress;\n }\n\n /// @dev Returns the symbol of the token, a shorter version\n /// of the name.\n function symbol() external pure virtual returns (string memory) {\n return \"OUSD\";\n }\n\n /// @dev Returns the name of the token.\n function name() external pure virtual returns (string memory) {\n return \"Origin Dollar\";\n }\n\n /// @dev Returns the number of decimals used to get its user representation.\n function decimals() external pure virtual returns (uint8) {\n return 18;\n }\n\n /**\n * @dev Verifies that the caller is the Vault contract\n */\n modifier onlyVault() {\n require(vaultAddress == msg.sender, \"Caller is not the Vault\");\n _;\n }\n\n /**\n * @return High resolution rebasingCreditsPerToken\n */\n function rebasingCreditsPerTokenHighres() external view returns (uint256) {\n return rebasingCreditsPerToken_;\n }\n\n /**\n * @return Low resolution rebasingCreditsPerToken\n */\n function rebasingCreditsPerToken() external view returns (uint256) {\n return rebasingCreditsPerToken_ / RESOLUTION_INCREASE;\n }\n\n /**\n * @return High resolution total number of rebasing credits\n */\n function rebasingCreditsHighres() external view returns (uint256) {\n return rebasingCredits_;\n }\n\n /**\n * @return Low resolution total number of rebasing credits\n */\n function rebasingCredits() external view returns (uint256) {\n return rebasingCredits_ / RESOLUTION_INCREASE;\n }\n\n /**\n * @notice Gets the balance of the specified address.\n * @param _account Address to query the balance of.\n * @return A uint256 representing the amount of base units owned by the\n * specified address.\n */\n function balanceOf(address _account) public view returns (uint256) {\n RebaseOptions state = rebaseState[_account];\n if (state == RebaseOptions.YieldDelegationSource) {\n // Saves a slot read when transferring to or from a yield delegating source\n // since we know creditBalances equals the balance.\n return creditBalances[_account];\n }\n uint256 baseBalance = (creditBalances[_account] * 1e18) /\n _creditsPerToken(_account);\n if (state == RebaseOptions.YieldDelegationTarget) {\n // creditBalances of yieldFrom accounts equals token balances\n return baseBalance - creditBalances[yieldFrom[_account]];\n }\n return baseBalance;\n }\n\n /**\n * @notice Gets the credits balance of the specified address.\n * @dev Backwards compatible with old low res credits per token.\n * @param _account The address to query the balance of.\n * @return (uint256, uint256) Credit balance and credits per token of the\n * address\n */\n function creditsBalanceOf(address _account)\n external\n view\n returns (uint256, uint256)\n {\n uint256 cpt = _creditsPerToken(_account);\n if (cpt == 1e27) {\n // For a period before the resolution upgrade, we created all new\n // contract accounts at high resolution. Since they are not changing\n // as a result of this upgrade, we will return their true values\n return (creditBalances[_account], cpt);\n } else {\n return (\n creditBalances[_account] / RESOLUTION_INCREASE,\n cpt / RESOLUTION_INCREASE\n );\n }\n }\n\n /**\n * @notice Gets the credits balance of the specified address.\n * @param _account The address to query the balance of.\n * @return (uint256, uint256, bool) Credit balance, credits per token of the\n * address, and isUpgraded\n */\n function creditsBalanceOfHighres(address _account)\n external\n view\n returns (\n uint256,\n uint256,\n bool\n )\n {\n return (\n creditBalances[_account],\n _creditsPerToken(_account),\n true // all accounts have their resolution \"upgraded\"\n );\n }\n\n // Backwards compatible view\n function nonRebasingCreditsPerToken(address _account)\n external\n view\n returns (uint256)\n {\n return alternativeCreditsPerToken[_account];\n }\n\n /**\n * @notice Transfer tokens to a specified address.\n * @param _to the address to transfer to.\n * @param _value the amount to be transferred.\n * @return true on success.\n */\n function transfer(address _to, uint256 _value) external returns (bool) {\n require(_to != address(0), \"Transfer to zero address\");\n\n _executeTransfer(msg.sender, _to, _value);\n\n emit Transfer(msg.sender, _to, _value);\n return true;\n }\n\n /**\n * @notice Transfer tokens from one address to another.\n * @param _from The address you want to send tokens from.\n * @param _to The address you want to transfer to.\n * @param _value The amount of tokens to be transferred.\n * @return true on success.\n */\n function transferFrom(\n address _from,\n address _to,\n uint256 _value\n ) external returns (bool) {\n require(_to != address(0), \"Transfer to zero address\");\n uint256 userAllowance = allowances[_from][msg.sender];\n require(_value <= userAllowance, \"Allowance exceeded\");\n\n unchecked {\n allowances[_from][msg.sender] = userAllowance - _value;\n }\n\n _executeTransfer(_from, _to, _value);\n\n emit Transfer(_from, _to, _value);\n return true;\n }\n\n function _executeTransfer(\n address _from,\n address _to,\n uint256 _value\n ) internal {\n (\n int256 fromRebasingCreditsDiff,\n int256 fromNonRebasingSupplyDiff\n ) = _adjustAccount(_from, -_value.toInt256());\n (\n int256 toRebasingCreditsDiff,\n int256 toNonRebasingSupplyDiff\n ) = _adjustAccount(_to, _value.toInt256());\n\n _adjustGlobals(\n fromRebasingCreditsDiff + toRebasingCreditsDiff,\n fromNonRebasingSupplyDiff + toNonRebasingSupplyDiff\n );\n }\n\n function _adjustAccount(address _account, int256 _balanceChange)\n internal\n returns (int256 rebasingCreditsDiff, int256 nonRebasingSupplyDiff)\n {\n RebaseOptions state = rebaseState[_account];\n int256 currentBalance = balanceOf(_account).toInt256();\n if (currentBalance + _balanceChange < 0) {\n revert(\"Transfer amount exceeds balance\");\n }\n uint256 newBalance = (currentBalance + _balanceChange).toUint256();\n\n if (state == RebaseOptions.YieldDelegationSource) {\n address target = yieldTo[_account];\n uint256 targetOldBalance = balanceOf(target);\n uint256 targetNewCredits = _balanceToRebasingCredits(\n targetOldBalance + newBalance\n );\n rebasingCreditsDiff =\n targetNewCredits.toInt256() -\n creditBalances[target].toInt256();\n\n creditBalances[_account] = newBalance;\n creditBalances[target] = targetNewCredits;\n } else if (state == RebaseOptions.YieldDelegationTarget) {\n uint256 newCredits = _balanceToRebasingCredits(\n newBalance + creditBalances[yieldFrom[_account]]\n );\n rebasingCreditsDiff =\n newCredits.toInt256() -\n creditBalances[_account].toInt256();\n creditBalances[_account] = newCredits;\n } else {\n _autoMigrate(_account);\n uint256 alternativeCreditsPerTokenMem = alternativeCreditsPerToken[\n _account\n ];\n if (alternativeCreditsPerTokenMem > 0) {\n nonRebasingSupplyDiff = _balanceChange;\n if (alternativeCreditsPerTokenMem != 1e18) {\n alternativeCreditsPerToken[_account] = 1e18;\n }\n creditBalances[_account] = newBalance;\n } else {\n uint256 newCredits = _balanceToRebasingCredits(newBalance);\n rebasingCreditsDiff =\n newCredits.toInt256() -\n creditBalances[_account].toInt256();\n creditBalances[_account] = newCredits;\n }\n }\n }\n\n function _adjustGlobals(\n int256 _rebasingCreditsDiff,\n int256 _nonRebasingSupplyDiff\n ) internal {\n if (_rebasingCreditsDiff != 0) {\n rebasingCredits_ = (rebasingCredits_.toInt256() +\n _rebasingCreditsDiff).toUint256();\n }\n if (_nonRebasingSupplyDiff != 0) {\n nonRebasingSupply = (nonRebasingSupply.toInt256() +\n _nonRebasingSupplyDiff).toUint256();\n }\n }\n\n /**\n * @notice Function to check the amount of tokens that _owner has allowed\n * to `_spender`.\n * @param _owner The address which owns the funds.\n * @param _spender The address which will spend the funds.\n * @return The number of tokens still available for the _spender.\n */\n function allowance(address _owner, address _spender)\n external\n view\n returns (uint256)\n {\n return allowances[_owner][_spender];\n }\n\n /**\n * @notice Approve the passed address to spend the specified amount of\n * tokens on behalf of msg.sender.\n * @param _spender The address which will spend the funds.\n * @param _value The amount of tokens to be spent.\n * @return true on success.\n */\n function approve(address _spender, uint256 _value) external returns (bool) {\n allowances[msg.sender][_spender] = _value;\n emit Approval(msg.sender, _spender, _value);\n return true;\n }\n\n /**\n * @notice Creates `_amount` tokens and assigns them to `_account`,\n * increasing the total supply.\n */\n function mint(address _account, uint256 _amount) external onlyVault {\n require(_account != address(0), \"Mint to the zero address\");\n\n // Account\n (\n int256 toRebasingCreditsDiff,\n int256 toNonRebasingSupplyDiff\n ) = _adjustAccount(_account, _amount.toInt256());\n // Globals\n _adjustGlobals(toRebasingCreditsDiff, toNonRebasingSupplyDiff);\n totalSupply = totalSupply + _amount;\n\n require(totalSupply < MAX_SUPPLY, \"Max supply\");\n emit Transfer(address(0), _account, _amount);\n }\n\n /**\n * @notice Destroys `_amount` tokens from `_account`,\n * reducing the total supply.\n */\n function burn(address _account, uint256 _amount) external onlyVault {\n require(_account != address(0), \"Burn from the zero address\");\n if (_amount == 0) {\n return;\n }\n\n // Account\n (\n int256 toRebasingCreditsDiff,\n int256 toNonRebasingSupplyDiff\n ) = _adjustAccount(_account, -_amount.toInt256());\n // Globals\n _adjustGlobals(toRebasingCreditsDiff, toNonRebasingSupplyDiff);\n totalSupply = totalSupply - _amount;\n\n emit Transfer(_account, address(0), _amount);\n }\n\n /**\n * @dev Get the credits per token for an account. Returns a fixed amount\n * if the account is non-rebasing.\n * @param _account Address of the account.\n */\n function _creditsPerToken(address _account)\n internal\n view\n returns (uint256)\n {\n uint256 alternativeCreditsPerTokenMem = alternativeCreditsPerToken[\n _account\n ];\n if (alternativeCreditsPerTokenMem != 0) {\n return alternativeCreditsPerTokenMem;\n } else {\n return rebasingCreditsPerToken_;\n }\n }\n\n /**\n * @dev Auto migrate contracts to be non rebasing,\n * unless they have opted into yield.\n * @param _account Address of the account.\n */\n function _autoMigrate(address _account) internal {\n uint256 codeLen = _account.code.length;\n bool isEOA = (codeLen == 0) ||\n (codeLen == 23 && bytes3(_account.code) == 0xef0100);\n // In previous code versions, contracts would not have had their\n // rebaseState[_account] set to RebaseOptions.NonRebasing when migrated\n // therefore we check the actual accounting used on the account as well.\n if (\n (!isEOA) &&\n rebaseState[_account] == RebaseOptions.NotSet &&\n alternativeCreditsPerToken[_account] == 0\n ) {\n _rebaseOptOut(_account);\n }\n }\n\n /**\n * @dev Calculates credits from contract's global rebasingCreditsPerToken_, and\n * also balance that corresponds to those credits. The latter is important\n * when adjusting the contract's global nonRebasingSupply to circumvent any\n * possible rounding errors.\n *\n * @param _balance Balance of the account.\n */\n function _balanceToRebasingCredits(uint256 _balance)\n internal\n view\n returns (uint256 rebasingCredits)\n {\n // Rounds up, because we need to ensure that accounts always have\n // at least the balance that they should have.\n // Note this should always be used on an absolute account value,\n // not on a possibly negative diff, because then the rounding would be wrong.\n return ((_balance) * rebasingCreditsPerToken_ + 1e18 - 1) / 1e18;\n }\n\n /**\n * @notice The calling account will start receiving yield after a successful call.\n * @param _account Address of the account.\n */\n function governanceRebaseOptIn(address _account) external onlyGovernor {\n require(_account != address(0), \"Zero address not allowed\");\n _rebaseOptIn(_account);\n }\n\n /**\n * @notice The calling account will start receiving yield after a successful call.\n */\n function rebaseOptIn() external {\n _rebaseOptIn(msg.sender);\n }\n\n function _rebaseOptIn(address _account) internal {\n uint256 balance = balanceOf(_account);\n\n // prettier-ignore\n require(\n alternativeCreditsPerToken[_account] > 0 ||\n // Accounts may explicitly `rebaseOptIn` regardless of\n // accounting if they have a 0 balance.\n creditBalances[_account] == 0\n ,\n \"Account must be non-rebasing\"\n );\n RebaseOptions state = rebaseState[_account];\n // prettier-ignore\n require(\n state == RebaseOptions.StdNonRebasing ||\n state == RebaseOptions.NotSet,\n \"Only standard non-rebasing accounts can opt in\"\n );\n\n uint256 newCredits = _balanceToRebasingCredits(balance);\n\n // Account\n rebaseState[_account] = RebaseOptions.StdRebasing;\n alternativeCreditsPerToken[_account] = 0;\n creditBalances[_account] = newCredits;\n // Globals\n _adjustGlobals(newCredits.toInt256(), -balance.toInt256());\n\n emit AccountRebasingEnabled(_account);\n }\n\n /**\n * @notice The calling account will no longer receive yield\n */\n function rebaseOptOut() external {\n _rebaseOptOut(msg.sender);\n }\n\n function _rebaseOptOut(address _account) internal {\n require(\n alternativeCreditsPerToken[_account] == 0,\n \"Account must be rebasing\"\n );\n RebaseOptions state = rebaseState[_account];\n require(\n state == RebaseOptions.StdRebasing || state == RebaseOptions.NotSet,\n \"Only standard rebasing accounts can opt out\"\n );\n\n uint256 oldCredits = creditBalances[_account];\n uint256 balance = balanceOf(_account);\n\n // Account\n rebaseState[_account] = RebaseOptions.StdNonRebasing;\n alternativeCreditsPerToken[_account] = 1e18;\n creditBalances[_account] = balance;\n // Globals\n _adjustGlobals(-oldCredits.toInt256(), balance.toInt256());\n\n emit AccountRebasingDisabled(_account);\n }\n\n /**\n * @notice Distribute yield to users. This changes the exchange rate\n * between \"credits\" and OUSD tokens to change rebasing user's balances.\n * @param _newTotalSupply New total supply of OUSD.\n */\n function changeSupply(uint256 _newTotalSupply) external onlyVault {\n require(totalSupply > 0, \"Cannot increase 0 supply\");\n\n if (totalSupply == _newTotalSupply) {\n emit TotalSupplyUpdatedHighres(\n totalSupply,\n rebasingCredits_,\n rebasingCreditsPerToken_\n );\n return;\n }\n\n totalSupply = _newTotalSupply > MAX_SUPPLY\n ? MAX_SUPPLY\n : _newTotalSupply;\n\n uint256 rebasingSupply = totalSupply - nonRebasingSupply;\n // round up in the favour of the protocol\n rebasingCreditsPerToken_ =\n (rebasingCredits_ * 1e18 + rebasingSupply - 1) /\n rebasingSupply;\n\n require(rebasingCreditsPerToken_ > 0, \"Invalid change in supply\");\n\n emit TotalSupplyUpdatedHighres(\n totalSupply,\n rebasingCredits_,\n rebasingCreditsPerToken_\n );\n }\n\n /*\n * @notice Send the yield from one account to another account.\n * Each account keeps its own balances.\n */\n function delegateYield(address _from, address _to)\n external\n onlyGovernorOrStrategist\n {\n require(_from != address(0), \"Zero from address not allowed\");\n require(_to != address(0), \"Zero to address not allowed\");\n\n require(_from != _to, \"Cannot delegate to self\");\n require(\n yieldFrom[_to] == address(0) &&\n yieldTo[_to] == address(0) &&\n yieldFrom[_from] == address(0) &&\n yieldTo[_from] == address(0),\n \"Blocked by existing yield delegation\"\n );\n RebaseOptions stateFrom = rebaseState[_from];\n RebaseOptions stateTo = rebaseState[_to];\n\n require(\n stateFrom == RebaseOptions.NotSet ||\n stateFrom == RebaseOptions.StdNonRebasing ||\n stateFrom == RebaseOptions.StdRebasing,\n \"Invalid rebaseState from\"\n );\n\n require(\n stateTo == RebaseOptions.NotSet ||\n stateTo == RebaseOptions.StdNonRebasing ||\n stateTo == RebaseOptions.StdRebasing,\n \"Invalid rebaseState to\"\n );\n\n if (alternativeCreditsPerToken[_from] == 0) {\n _rebaseOptOut(_from);\n }\n if (alternativeCreditsPerToken[_to] > 0) {\n _rebaseOptIn(_to);\n }\n\n uint256 fromBalance = balanceOf(_from);\n uint256 toBalance = balanceOf(_to);\n uint256 oldToCredits = creditBalances[_to];\n uint256 newToCredits = _balanceToRebasingCredits(\n fromBalance + toBalance\n );\n\n // Set up the bidirectional links\n yieldTo[_from] = _to;\n yieldFrom[_to] = _from;\n\n // Local\n rebaseState[_from] = RebaseOptions.YieldDelegationSource;\n alternativeCreditsPerToken[_from] = 1e18;\n creditBalances[_from] = fromBalance;\n rebaseState[_to] = RebaseOptions.YieldDelegationTarget;\n creditBalances[_to] = newToCredits;\n\n // Global\n int256 creditsChange = newToCredits.toInt256() -\n oldToCredits.toInt256();\n _adjustGlobals(creditsChange, -(fromBalance).toInt256());\n emit YieldDelegated(_from, _to);\n }\n\n /*\n * @notice Stop sending the yield from one account to another account.\n */\n function undelegateYield(address _from) external onlyGovernorOrStrategist {\n // Require a delegation, which will also ensure a valid delegation\n require(yieldTo[_from] != address(0), \"Zero address not allowed\");\n\n address to = yieldTo[_from];\n uint256 fromBalance = balanceOf(_from);\n uint256 toBalance = balanceOf(to);\n uint256 oldToCredits = creditBalances[to];\n uint256 newToCredits = _balanceToRebasingCredits(toBalance);\n\n // Remove the bidirectional links\n yieldFrom[to] = address(0);\n yieldTo[_from] = address(0);\n\n // Local\n rebaseState[_from] = RebaseOptions.StdNonRebasing;\n // alternativeCreditsPerToken[from] already 1e18 from `delegateYield()`\n creditBalances[_from] = fromBalance;\n rebaseState[to] = RebaseOptions.StdRebasing;\n // alternativeCreditsPerToken[to] already 0 from `delegateYield()`\n creditBalances[to] = newToCredits;\n\n // Global\n int256 creditsChange = newToCredits.toInt256() -\n oldToCredits.toInt256();\n _adjustGlobals(creditsChange, fromBalance.toInt256());\n emit YieldUndelegated(_from, to);\n }\n}\n" + }, + "contracts/utils/BalancerErrors.sol": { + "content": "// SPDX-License-Identifier: GPL-3.0-or-later\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see .\n\npragma solidity >=0.7.4 <0.9.0;\n\n// solhint-disable\n\n/**\n * @dev Reverts if `condition` is false, with a revert reason containing `errorCode`. Only codes up to 999 are\n * supported.\n * Uses the default 'BAL' prefix for the error code\n */\nfunction _require(bool condition, uint256 errorCode) pure {\n if (!condition) _revert(errorCode);\n}\n\n/**\n * @dev Reverts if `condition` is false, with a revert reason containing `errorCode`. Only codes up to 999 are\n * supported.\n */\nfunction _require(\n bool condition,\n uint256 errorCode,\n bytes3 prefix\n) pure {\n if (!condition) _revert(errorCode, prefix);\n}\n\n/**\n * @dev Reverts with a revert reason containing `errorCode`. Only codes up to 999 are supported.\n * Uses the default 'BAL' prefix for the error code\n */\nfunction _revert(uint256 errorCode) pure {\n _revert(errorCode, 0x42414c); // This is the raw byte representation of \"BAL\"\n}\n\n/**\n * @dev Reverts with a revert reason containing `errorCode`. Only codes up to 999 are supported.\n */\nfunction _revert(uint256 errorCode, bytes3 prefix) pure {\n uint256 prefixUint = uint256(uint24(prefix));\n // We're going to dynamically create a revert string based on the error code, with the following format:\n // 'BAL#{errorCode}'\n // where the code is left-padded with zeroes to three digits (so they range from 000 to 999).\n //\n // We don't have revert strings embedded in the contract to save bytecode size: it takes much less space to store a\n // number (8 to 16 bits) than the individual string characters.\n //\n // The dynamic string creation algorithm that follows could be implemented in Solidity, but assembly allows for a\n // much denser implementation, again saving bytecode size. Given this function unconditionally reverts, this is a\n // safe place to rely on it without worrying about how its usage might affect e.g. memory contents.\n assembly {\n // First, we need to compute the ASCII representation of the error code. We assume that it is in the 0-999\n // range, so we only need to convert three digits. To convert the digits to ASCII, we add 0x30, the value for\n // the '0' character.\n\n let units := add(mod(errorCode, 10), 0x30)\n\n errorCode := div(errorCode, 10)\n let tenths := add(mod(errorCode, 10), 0x30)\n\n errorCode := div(errorCode, 10)\n let hundreds := add(mod(errorCode, 10), 0x30)\n\n // With the individual characters, we can now construct the full string.\n // We first append the '#' character (0x23) to the prefix. In the case of 'BAL', it results in 0x42414c23 ('BAL#')\n // Then, we shift this by 24 (to provide space for the 3 bytes of the error code), and add the\n // characters to it, each shifted by a multiple of 8.\n // The revert reason is then shifted left by 200 bits (256 minus the length of the string, 7 characters * 8 bits\n // per character = 56) to locate it in the most significant part of the 256 slot (the beginning of a byte\n // array).\n let formattedPrefix := shl(24, add(0x23, shl(8, prefixUint)))\n\n let revertReason := shl(\n 200,\n add(\n formattedPrefix,\n add(add(units, shl(8, tenths)), shl(16, hundreds))\n )\n )\n\n // We can now encode the reason in memory, which can be safely overwritten as we're about to revert. The encoded\n // message will have the following layout:\n // [ revert reason identifier ] [ string location offset ] [ string length ] [ string contents ]\n\n // The Solidity revert reason identifier is 0x08c739a0, the function selector of the Error(string) function. We\n // also write zeroes to the next 28 bytes of memory, but those are about to be overwritten.\n mstore(\n 0x0,\n 0x08c379a000000000000000000000000000000000000000000000000000000000\n )\n // Next is the offset to the location of the string, which will be placed immediately after (20 bytes away).\n mstore(\n 0x04,\n 0x0000000000000000000000000000000000000000000000000000000000000020\n )\n // The string length is fixed: 7 characters.\n mstore(0x24, 7)\n // Finally, the string itself is stored.\n mstore(0x44, revertReason)\n\n // Even if the string is only 7 bytes long, we need to return a full 32 byte slot containing it. The length of\n // the encoded message is therefore 4 + 32 + 32 + 32 = 100.\n revert(0, 100)\n }\n}\n\nlibrary Errors {\n // Math\n uint256 internal constant ADD_OVERFLOW = 0;\n uint256 internal constant SUB_OVERFLOW = 1;\n uint256 internal constant SUB_UNDERFLOW = 2;\n uint256 internal constant MUL_OVERFLOW = 3;\n uint256 internal constant ZERO_DIVISION = 4;\n uint256 internal constant DIV_INTERNAL = 5;\n uint256 internal constant X_OUT_OF_BOUNDS = 6;\n uint256 internal constant Y_OUT_OF_BOUNDS = 7;\n uint256 internal constant PRODUCT_OUT_OF_BOUNDS = 8;\n uint256 internal constant INVALID_EXPONENT = 9;\n\n // Input\n uint256 internal constant OUT_OF_BOUNDS = 100;\n uint256 internal constant UNSORTED_ARRAY = 101;\n uint256 internal constant UNSORTED_TOKENS = 102;\n uint256 internal constant INPUT_LENGTH_MISMATCH = 103;\n uint256 internal constant ZERO_TOKEN = 104;\n uint256 internal constant INSUFFICIENT_DATA = 105;\n\n // Shared pools\n uint256 internal constant MIN_TOKENS = 200;\n uint256 internal constant MAX_TOKENS = 201;\n uint256 internal constant MAX_SWAP_FEE_PERCENTAGE = 202;\n uint256 internal constant MIN_SWAP_FEE_PERCENTAGE = 203;\n uint256 internal constant MINIMUM_BPT = 204;\n uint256 internal constant CALLER_NOT_VAULT = 205;\n uint256 internal constant UNINITIALIZED = 206;\n uint256 internal constant BPT_IN_MAX_AMOUNT = 207;\n uint256 internal constant BPT_OUT_MIN_AMOUNT = 208;\n uint256 internal constant EXPIRED_PERMIT = 209;\n uint256 internal constant NOT_TWO_TOKENS = 210;\n uint256 internal constant DISABLED = 211;\n\n // Pools\n uint256 internal constant MIN_AMP = 300;\n uint256 internal constant MAX_AMP = 301;\n uint256 internal constant MIN_WEIGHT = 302;\n uint256 internal constant MAX_STABLE_TOKENS = 303;\n uint256 internal constant MAX_IN_RATIO = 304;\n uint256 internal constant MAX_OUT_RATIO = 305;\n uint256 internal constant MIN_BPT_IN_FOR_TOKEN_OUT = 306;\n uint256 internal constant MAX_OUT_BPT_FOR_TOKEN_IN = 307;\n uint256 internal constant NORMALIZED_WEIGHT_INVARIANT = 308;\n uint256 internal constant INVALID_TOKEN = 309;\n uint256 internal constant UNHANDLED_JOIN_KIND = 310;\n uint256 internal constant ZERO_INVARIANT = 311;\n uint256 internal constant ORACLE_INVALID_SECONDS_QUERY = 312;\n uint256 internal constant ORACLE_NOT_INITIALIZED = 313;\n uint256 internal constant ORACLE_QUERY_TOO_OLD = 314;\n uint256 internal constant ORACLE_INVALID_INDEX = 315;\n uint256 internal constant ORACLE_BAD_SECS = 316;\n uint256 internal constant AMP_END_TIME_TOO_CLOSE = 317;\n uint256 internal constant AMP_ONGOING_UPDATE = 318;\n uint256 internal constant AMP_RATE_TOO_HIGH = 319;\n uint256 internal constant AMP_NO_ONGOING_UPDATE = 320;\n uint256 internal constant STABLE_INVARIANT_DIDNT_CONVERGE = 321;\n uint256 internal constant STABLE_GET_BALANCE_DIDNT_CONVERGE = 322;\n uint256 internal constant RELAYER_NOT_CONTRACT = 323;\n uint256 internal constant BASE_POOL_RELAYER_NOT_CALLED = 324;\n uint256 internal constant REBALANCING_RELAYER_REENTERED = 325;\n uint256 internal constant GRADUAL_UPDATE_TIME_TRAVEL = 326;\n uint256 internal constant SWAPS_DISABLED = 327;\n uint256 internal constant CALLER_IS_NOT_LBP_OWNER = 328;\n uint256 internal constant PRICE_RATE_OVERFLOW = 329;\n uint256 internal constant INVALID_JOIN_EXIT_KIND_WHILE_SWAPS_DISABLED = 330;\n uint256 internal constant WEIGHT_CHANGE_TOO_FAST = 331;\n uint256 internal constant LOWER_GREATER_THAN_UPPER_TARGET = 332;\n uint256 internal constant UPPER_TARGET_TOO_HIGH = 333;\n uint256 internal constant UNHANDLED_BY_LINEAR_POOL = 334;\n uint256 internal constant OUT_OF_TARGET_RANGE = 335;\n uint256 internal constant UNHANDLED_EXIT_KIND = 336;\n uint256 internal constant UNAUTHORIZED_EXIT = 337;\n uint256 internal constant MAX_MANAGEMENT_SWAP_FEE_PERCENTAGE = 338;\n uint256 internal constant UNHANDLED_BY_MANAGED_POOL = 339;\n uint256 internal constant UNHANDLED_BY_PHANTOM_POOL = 340;\n uint256 internal constant TOKEN_DOES_NOT_HAVE_RATE_PROVIDER = 341;\n uint256 internal constant INVALID_INITIALIZATION = 342;\n uint256 internal constant OUT_OF_NEW_TARGET_RANGE = 343;\n uint256 internal constant FEATURE_DISABLED = 344;\n uint256 internal constant UNINITIALIZED_POOL_CONTROLLER = 345;\n uint256 internal constant SET_SWAP_FEE_DURING_FEE_CHANGE = 346;\n uint256 internal constant SET_SWAP_FEE_PENDING_FEE_CHANGE = 347;\n uint256 internal constant CHANGE_TOKENS_DURING_WEIGHT_CHANGE = 348;\n uint256 internal constant CHANGE_TOKENS_PENDING_WEIGHT_CHANGE = 349;\n uint256 internal constant MAX_WEIGHT = 350;\n uint256 internal constant UNAUTHORIZED_JOIN = 351;\n uint256 internal constant MAX_MANAGEMENT_AUM_FEE_PERCENTAGE = 352;\n uint256 internal constant FRACTIONAL_TARGET = 353;\n uint256 internal constant ADD_OR_REMOVE_BPT = 354;\n uint256 internal constant INVALID_CIRCUIT_BREAKER_BOUNDS = 355;\n uint256 internal constant CIRCUIT_BREAKER_TRIPPED = 356;\n uint256 internal constant MALICIOUS_QUERY_REVERT = 357;\n uint256 internal constant JOINS_EXITS_DISABLED = 358;\n\n // Lib\n uint256 internal constant REENTRANCY = 400;\n uint256 internal constant SENDER_NOT_ALLOWED = 401;\n uint256 internal constant PAUSED = 402;\n uint256 internal constant PAUSE_WINDOW_EXPIRED = 403;\n uint256 internal constant MAX_PAUSE_WINDOW_DURATION = 404;\n uint256 internal constant MAX_BUFFER_PERIOD_DURATION = 405;\n uint256 internal constant INSUFFICIENT_BALANCE = 406;\n uint256 internal constant INSUFFICIENT_ALLOWANCE = 407;\n uint256 internal constant ERC20_TRANSFER_FROM_ZERO_ADDRESS = 408;\n uint256 internal constant ERC20_TRANSFER_TO_ZERO_ADDRESS = 409;\n uint256 internal constant ERC20_MINT_TO_ZERO_ADDRESS = 410;\n uint256 internal constant ERC20_BURN_FROM_ZERO_ADDRESS = 411;\n uint256 internal constant ERC20_APPROVE_FROM_ZERO_ADDRESS = 412;\n uint256 internal constant ERC20_APPROVE_TO_ZERO_ADDRESS = 413;\n uint256 internal constant ERC20_TRANSFER_EXCEEDS_ALLOWANCE = 414;\n uint256 internal constant ERC20_DECREASED_ALLOWANCE_BELOW_ZERO = 415;\n uint256 internal constant ERC20_TRANSFER_EXCEEDS_BALANCE = 416;\n uint256 internal constant ERC20_BURN_EXCEEDS_ALLOWANCE = 417;\n uint256 internal constant SAFE_ERC20_CALL_FAILED = 418;\n uint256 internal constant ADDRESS_INSUFFICIENT_BALANCE = 419;\n uint256 internal constant ADDRESS_CANNOT_SEND_VALUE = 420;\n uint256 internal constant SAFE_CAST_VALUE_CANT_FIT_INT256 = 421;\n uint256 internal constant GRANT_SENDER_NOT_ADMIN = 422;\n uint256 internal constant REVOKE_SENDER_NOT_ADMIN = 423;\n uint256 internal constant RENOUNCE_SENDER_NOT_ALLOWED = 424;\n uint256 internal constant BUFFER_PERIOD_EXPIRED = 425;\n uint256 internal constant CALLER_IS_NOT_OWNER = 426;\n uint256 internal constant NEW_OWNER_IS_ZERO = 427;\n uint256 internal constant CODE_DEPLOYMENT_FAILED = 428;\n uint256 internal constant CALL_TO_NON_CONTRACT = 429;\n uint256 internal constant LOW_LEVEL_CALL_FAILED = 430;\n uint256 internal constant NOT_PAUSED = 431;\n uint256 internal constant ADDRESS_ALREADY_ALLOWLISTED = 432;\n uint256 internal constant ADDRESS_NOT_ALLOWLISTED = 433;\n uint256 internal constant ERC20_BURN_EXCEEDS_BALANCE = 434;\n uint256 internal constant INVALID_OPERATION = 435;\n uint256 internal constant CODEC_OVERFLOW = 436;\n uint256 internal constant IN_RECOVERY_MODE = 437;\n uint256 internal constant NOT_IN_RECOVERY_MODE = 438;\n uint256 internal constant INDUCED_FAILURE = 439;\n uint256 internal constant EXPIRED_SIGNATURE = 440;\n uint256 internal constant MALFORMED_SIGNATURE = 441;\n uint256 internal constant SAFE_CAST_VALUE_CANT_FIT_UINT64 = 442;\n uint256 internal constant UNHANDLED_FEE_TYPE = 443;\n uint256 internal constant BURN_FROM_ZERO = 444;\n\n // Vault\n uint256 internal constant INVALID_POOL_ID = 500;\n uint256 internal constant CALLER_NOT_POOL = 501;\n uint256 internal constant SENDER_NOT_ASSET_MANAGER = 502;\n uint256 internal constant USER_DOESNT_ALLOW_RELAYER = 503;\n uint256 internal constant INVALID_SIGNATURE = 504;\n uint256 internal constant EXIT_BELOW_MIN = 505;\n uint256 internal constant JOIN_ABOVE_MAX = 506;\n uint256 internal constant SWAP_LIMIT = 507;\n uint256 internal constant SWAP_DEADLINE = 508;\n uint256 internal constant CANNOT_SWAP_SAME_TOKEN = 509;\n uint256 internal constant UNKNOWN_AMOUNT_IN_FIRST_SWAP = 510;\n uint256 internal constant MALCONSTRUCTED_MULTIHOP_SWAP = 511;\n uint256 internal constant INTERNAL_BALANCE_OVERFLOW = 512;\n uint256 internal constant INSUFFICIENT_INTERNAL_BALANCE = 513;\n uint256 internal constant INVALID_ETH_INTERNAL_BALANCE = 514;\n uint256 internal constant INVALID_POST_LOAN_BALANCE = 515;\n uint256 internal constant INSUFFICIENT_ETH = 516;\n uint256 internal constant UNALLOCATED_ETH = 517;\n uint256 internal constant ETH_TRANSFER = 518;\n uint256 internal constant CANNOT_USE_ETH_SENTINEL = 519;\n uint256 internal constant TOKENS_MISMATCH = 520;\n uint256 internal constant TOKEN_NOT_REGISTERED = 521;\n uint256 internal constant TOKEN_ALREADY_REGISTERED = 522;\n uint256 internal constant TOKENS_ALREADY_SET = 523;\n uint256 internal constant TOKENS_LENGTH_MUST_BE_2 = 524;\n uint256 internal constant NONZERO_TOKEN_BALANCE = 525;\n uint256 internal constant BALANCE_TOTAL_OVERFLOW = 526;\n uint256 internal constant POOL_NO_TOKENS = 527;\n uint256 internal constant INSUFFICIENT_FLASH_LOAN_BALANCE = 528;\n\n // Fees\n uint256 internal constant SWAP_FEE_PERCENTAGE_TOO_HIGH = 600;\n uint256 internal constant FLASH_LOAN_FEE_PERCENTAGE_TOO_HIGH = 601;\n uint256 internal constant INSUFFICIENT_FLASH_LOAN_FEE_AMOUNT = 602;\n uint256 internal constant AUM_FEE_PERCENTAGE_TOO_HIGH = 603;\n\n // FeeSplitter\n uint256 internal constant SPLITTER_FEE_PERCENTAGE_TOO_HIGH = 700;\n\n // Misc\n uint256 internal constant UNIMPLEMENTED = 998;\n uint256 internal constant SHOULD_NOT_HAPPEN = 999;\n}\n" + }, + "contracts/utils/BytesHelper.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nuint256 constant UINT32_LENGTH = 4;\nuint256 constant UINT64_LENGTH = 8;\nuint256 constant UINT256_LENGTH = 32;\n// Address is 20 bytes, but we expect the data to be padded with 0s to 32 bytes\nuint256 constant ADDRESS_LENGTH = 32;\n\nlibrary BytesHelper {\n /**\n * @dev Extract a slice from bytes memory\n * @param data The bytes memory to slice\n * @param start The start index (inclusive)\n * @param end The end index (exclusive)\n * @return result A new bytes memory containing the slice\n */\n function extractSlice(\n bytes memory data,\n uint256 start,\n uint256 end\n ) internal pure returns (bytes memory) {\n require(end >= start, \"Invalid slice range\");\n require(end <= data.length, \"Slice end exceeds data length\");\n\n uint256 length = end - start;\n bytes memory result = new bytes(length);\n\n // Simple byte-by-byte copy\n for (uint256 i = 0; i < length; i++) {\n result[i] = data[start + i];\n }\n\n return result;\n }\n\n /**\n * @dev Decode a uint32 from a bytes memory\n * @param data The bytes memory to decode\n * @return uint32 The decoded uint32\n */\n function decodeUint32(bytes memory data) internal pure returns (uint32) {\n require(data.length == 4, \"Invalid data length\");\n return uint32(uint256(bytes32(data)) >> 224);\n }\n\n /**\n * @dev Extract a uint32 from a bytes memory\n * @param data The bytes memory to extract from\n * @param start The start index (inclusive)\n * @return uint32 The extracted uint32\n */\n function extractUint32(bytes memory data, uint256 start)\n internal\n pure\n returns (uint32)\n {\n return decodeUint32(extractSlice(data, start, start + UINT32_LENGTH));\n }\n\n /**\n * @dev Decode an address from a bytes memory.\n * Expects the data to be padded with 0s to 32 bytes.\n * @param data The bytes memory to decode\n * @return address The decoded address\n */\n function decodeAddress(bytes memory data) internal pure returns (address) {\n // We expect the data to be padded with 0s, so length is 32 not 20\n require(data.length == 32, \"Invalid data length\");\n return abi.decode(data, (address));\n }\n\n /**\n * @dev Extract an address from a bytes memory\n * @param data The bytes memory to extract from\n * @param start The start index (inclusive)\n * @return address The extracted address\n */\n function extractAddress(bytes memory data, uint256 start)\n internal\n pure\n returns (address)\n {\n return decodeAddress(extractSlice(data, start, start + ADDRESS_LENGTH));\n }\n\n /**\n * @dev Decode a uint256 from a bytes memory\n * @param data The bytes memory to decode\n * @return uint256 The decoded uint256\n */\n function decodeUint256(bytes memory data) internal pure returns (uint256) {\n require(data.length == 32, \"Invalid data length\");\n return abi.decode(data, (uint256));\n }\n\n /**\n * @dev Extract a uint256 from a bytes memory\n * @param data The bytes memory to extract from\n * @param start The start index (inclusive)\n * @return uint256 The extracted uint256\n */\n function extractUint256(bytes memory data, uint256 start)\n internal\n pure\n returns (uint256)\n {\n return decodeUint256(extractSlice(data, start, start + UINT256_LENGTH));\n }\n}\n" + }, + "contracts/utils/Helpers.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { IBasicToken } from \"../interfaces/IBasicToken.sol\";\n\nlibrary Helpers {\n /**\n * @notice Fetch the `symbol()` from an ERC20 token\n * @dev Grabs the `symbol()` from a contract\n * @param _token Address of the ERC20 token\n * @return string Symbol of the ERC20 token\n */\n function getSymbol(address _token) internal view returns (string memory) {\n string memory symbol = IBasicToken(_token).symbol();\n return symbol;\n }\n\n /**\n * @notice Fetch the `decimals()` from an ERC20 token\n * @dev Grabs the `decimals()` from a contract and fails if\n * the decimal value does not live within a certain range\n * @param _token Address of the ERC20 token\n * @return uint256 Decimals of the ERC20 token\n */\n function getDecimals(address _token) internal view returns (uint256) {\n uint256 decimals = IBasicToken(_token).decimals();\n require(\n decimals >= 4 && decimals <= 18,\n \"Token must have sufficient decimal places\"\n );\n\n return decimals;\n }\n}\n" + }, + "contracts/utils/Initializable.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title Base contract any contracts that need to initialize state after deployment.\n * @author Origin Protocol Inc\n */\nabstract contract Initializable {\n /**\n * @dev Indicates that the contract has been initialized.\n */\n bool private initialized;\n\n /**\n * @dev Indicates that the contract is in the process of being initialized.\n */\n bool private initializing;\n\n /**\n * @dev Modifier to protect an initializer function from being invoked twice.\n */\n modifier initializer() {\n require(\n initializing || !initialized,\n \"Initializable: contract is already initialized\"\n );\n\n bool isTopLevelCall = !initializing;\n if (isTopLevelCall) {\n initializing = true;\n initialized = true;\n }\n\n _;\n\n if (isTopLevelCall) {\n initializing = false;\n }\n }\n\n uint256[50] private ______gap;\n}\n" + }, + "contracts/utils/InitializableAbstractStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title Base contract for vault strategies.\n * @author Origin Protocol Inc\n */\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\n\nimport { Initializable } from \"../utils/Initializable.sol\";\nimport { Governable } from \"../governance/Governable.sol\";\nimport { IVault } from \"../interfaces/IVault.sol\";\n\nabstract contract InitializableAbstractStrategy is Initializable, Governable {\n using SafeERC20 for IERC20;\n\n event PTokenAdded(address indexed _asset, address _pToken);\n event PTokenRemoved(address indexed _asset, address _pToken);\n event Deposit(address indexed _asset, address _pToken, uint256 _amount);\n event Withdrawal(address indexed _asset, address _pToken, uint256 _amount);\n event RewardTokenCollected(\n address recipient,\n address rewardToken,\n uint256 amount\n );\n event RewardTokenAddressesUpdated(\n address[] _oldAddresses,\n address[] _newAddresses\n );\n event HarvesterAddressesUpdated(\n address _oldHarvesterAddress,\n address _newHarvesterAddress\n );\n\n /// @notice Address of the underlying platform\n address public immutable platformAddress;\n /// @notice Address of the OToken vault\n address public immutable vaultAddress;\n\n /// @dev Replaced with an immutable variable\n // slither-disable-next-line constable-states\n address private _deprecated_platformAddress;\n\n /// @dev Replaced with an immutable\n // slither-disable-next-line constable-states\n address private _deprecated_vaultAddress;\n\n /// @notice asset => pToken (Platform Specific Token Address)\n mapping(address => address) public assetToPToken;\n\n /// @notice Full list of all assets supported by the strategy\n address[] internal assetsMapped;\n\n // Deprecated: Reward token address\n // slither-disable-next-line constable-states\n address private _deprecated_rewardTokenAddress;\n\n // Deprecated: now resides in Harvester's rewardTokenConfigs\n // slither-disable-next-line constable-states\n uint256 private _deprecated_rewardLiquidationThreshold;\n\n /// @notice Address of the Harvester contract allowed to collect reward tokens\n address public harvesterAddress;\n\n /// @notice Address of the reward tokens. eg CRV, BAL, CVX, AURA\n address[] public rewardTokenAddresses;\n\n /* Reserved for future expansion. Used to be 100 storage slots\n * and has decreased to accommodate:\n * - harvesterAddress\n * - rewardTokenAddresses\n */\n int256[98] private _reserved;\n\n struct BaseStrategyConfig {\n address platformAddress; // Address of the underlying platform\n address vaultAddress; // Address of the OToken's Vault\n }\n\n /**\n * @dev Verifies that the caller is the Governor or Strategist.\n */\n modifier onlyGovernorOrStrategist() virtual {\n require(\n isGovernor() || msg.sender == IVault(vaultAddress).strategistAddr(),\n \"Caller is not the Strategist or Governor\"\n );\n _;\n }\n\n /**\n * @param _config The platform and OToken vault addresses\n */\n constructor(BaseStrategyConfig memory _config) {\n platformAddress = _config.platformAddress;\n vaultAddress = _config.vaultAddress;\n }\n\n /**\n * @dev Internal initialize function, to set up initial internal state\n * @param _rewardTokenAddresses Address of reward token for platform\n * @param _assets Addresses of initial supported assets\n * @param _pTokens Platform Token corresponding addresses\n */\n function _initialize(\n address[] memory _rewardTokenAddresses,\n address[] memory _assets,\n address[] memory _pTokens\n ) internal {\n rewardTokenAddresses = _rewardTokenAddresses;\n\n uint256 assetCount = _assets.length;\n require(assetCount == _pTokens.length, \"Invalid input arrays\");\n for (uint256 i = 0; i < assetCount; ++i) {\n _setPTokenAddress(_assets[i], _pTokens[i]);\n }\n }\n\n /**\n * @notice Collect accumulated reward token and send to Vault.\n */\n function collectRewardTokens() external virtual onlyHarvester nonReentrant {\n _collectRewardTokens();\n }\n\n /**\n * @dev Default implementation that transfers reward tokens to the Harvester\n * Implementing strategies need to add custom logic to collect the rewards.\n */\n function _collectRewardTokens() internal virtual {\n uint256 rewardTokenCount = rewardTokenAddresses.length;\n for (uint256 i = 0; i < rewardTokenCount; ++i) {\n IERC20 rewardToken = IERC20(rewardTokenAddresses[i]);\n uint256 balance = rewardToken.balanceOf(address(this));\n if (balance > 0) {\n emit RewardTokenCollected(\n harvesterAddress,\n address(rewardToken),\n balance\n );\n rewardToken.safeTransfer(harvesterAddress, balance);\n }\n }\n }\n\n /**\n * @dev Verifies that the caller is the Vault.\n */\n modifier onlyVault() {\n require(msg.sender == vaultAddress, \"Caller is not the Vault\");\n _;\n }\n\n /**\n * @dev Verifies that the caller is the Harvester.\n */\n modifier onlyHarvester() {\n require(msg.sender == harvesterAddress, \"Caller is not the Harvester\");\n _;\n }\n\n /**\n * @dev Verifies that the caller is the Vault or Governor.\n */\n modifier onlyVaultOrGovernor() {\n require(\n msg.sender == vaultAddress || msg.sender == governor(),\n \"Caller is not the Vault or Governor\"\n );\n _;\n }\n\n /**\n * @dev Verifies that the caller is the Vault, Governor, or Strategist.\n */\n modifier onlyVaultOrGovernorOrStrategist() {\n require(\n msg.sender == vaultAddress ||\n msg.sender == governor() ||\n msg.sender == IVault(vaultAddress).strategistAddr(),\n \"Caller is not the Vault, Governor, or Strategist\"\n );\n _;\n }\n\n /**\n * @notice Set the reward token addresses. Any old addresses will be overwritten.\n * @param _rewardTokenAddresses Array of reward token addresses\n */\n function setRewardTokenAddresses(address[] calldata _rewardTokenAddresses)\n external\n onlyGovernor\n {\n uint256 rewardTokenCount = _rewardTokenAddresses.length;\n for (uint256 i = 0; i < rewardTokenCount; ++i) {\n require(\n _rewardTokenAddresses[i] != address(0),\n \"Can not set an empty address as a reward token\"\n );\n }\n\n emit RewardTokenAddressesUpdated(\n rewardTokenAddresses,\n _rewardTokenAddresses\n );\n rewardTokenAddresses = _rewardTokenAddresses;\n }\n\n /**\n * @notice Get the reward token addresses.\n * @return address[] the reward token addresses.\n */\n function getRewardTokenAddresses()\n external\n view\n returns (address[] memory)\n {\n return rewardTokenAddresses;\n }\n\n /**\n * @notice Provide support for asset by passing its pToken address.\n * This method can only be called by the system Governor\n * @param _asset Address for the asset\n * @param _pToken Address for the corresponding platform token\n */\n function setPTokenAddress(address _asset, address _pToken)\n external\n virtual\n onlyGovernor\n {\n _setPTokenAddress(_asset, _pToken);\n }\n\n /**\n * @notice Remove a supported asset by passing its index.\n * This method can only be called by the system Governor\n * @param _assetIndex Index of the asset to be removed\n */\n function removePToken(uint256 _assetIndex) external virtual onlyGovernor {\n require(_assetIndex < assetsMapped.length, \"Invalid index\");\n address asset = assetsMapped[_assetIndex];\n address pToken = assetToPToken[asset];\n\n if (_assetIndex < assetsMapped.length - 1) {\n assetsMapped[_assetIndex] = assetsMapped[assetsMapped.length - 1];\n }\n assetsMapped.pop();\n assetToPToken[asset] = address(0);\n\n emit PTokenRemoved(asset, pToken);\n }\n\n /**\n * @notice Provide support for asset by passing its pToken address.\n * Add to internal mappings and execute the platform specific,\n * abstract method `_abstractSetPToken`\n * @param _asset Address for the asset\n * @param _pToken Address for the corresponding platform token\n */\n function _setPTokenAddress(address _asset, address _pToken) internal {\n require(assetToPToken[_asset] == address(0), \"pToken already set\");\n require(\n _asset != address(0) && _pToken != address(0),\n \"Invalid addresses\"\n );\n\n assetToPToken[_asset] = _pToken;\n assetsMapped.push(_asset);\n\n emit PTokenAdded(_asset, _pToken);\n\n _abstractSetPToken(_asset, _pToken);\n }\n\n /**\n * @notice Transfer token to governor. Intended for recovering tokens stuck in\n * strategy contracts, i.e. mistaken sends.\n * @param _asset Address for the asset\n * @param _amount Amount of the asset to transfer\n */\n function transferToken(address _asset, uint256 _amount)\n public\n virtual\n onlyGovernor\n {\n require(!supportsAsset(_asset), \"Cannot transfer supported asset\");\n IERC20(_asset).safeTransfer(governor(), _amount);\n }\n\n /**\n * @notice Set the Harvester contract that can collect rewards.\n * @param _harvesterAddress Address of the harvester contract.\n */\n function setHarvesterAddress(address _harvesterAddress)\n external\n onlyGovernor\n {\n emit HarvesterAddressesUpdated(harvesterAddress, _harvesterAddress);\n harvesterAddress = _harvesterAddress;\n }\n\n /***************************************\n Abstract\n ****************************************/\n\n function _abstractSetPToken(address _asset, address _pToken)\n internal\n virtual;\n\n function safeApproveAllTokens() external virtual;\n\n /**\n * @notice Deposit an amount of assets into the platform\n * @param _asset Address for the asset\n * @param _amount Units of asset to deposit\n */\n function deposit(address _asset, uint256 _amount) external virtual;\n\n /**\n * @notice Deposit all supported assets in this strategy contract to the platform\n */\n function depositAll() external virtual;\n\n /**\n * @notice Withdraw an `amount` of assets from the platform and\n * send to the `_recipient`.\n * @param _recipient Address to which the asset should be sent\n * @param _asset Address of the asset\n * @param _amount Units of asset to withdraw\n */\n function withdraw(\n address _recipient,\n address _asset,\n uint256 _amount\n ) external virtual;\n\n /**\n * @notice Withdraw all supported assets from platform and\n * sends to the OToken's Vault.\n */\n function withdrawAll() external virtual;\n\n /**\n * @notice Get the total asset value held in the platform.\n * This includes any interest that was generated since depositing.\n * @param _asset Address of the asset\n * @return balance Total value of the asset in the platform\n */\n function checkBalance(address _asset)\n external\n view\n virtual\n returns (uint256 balance);\n\n /**\n * @notice Check if an asset is supported.\n * @param _asset Address of the asset\n * @return bool Whether asset is supported\n */\n function supportsAsset(address _asset) public view virtual returns (bool);\n}\n" + }, + "contracts/utils/PRBMath.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n// Copied from the PRBMath library\n// https://github.com/PaulRBerg/prb-math/blob/main/src/Common.sol\n\n/// @notice Calculates the square root of x using the Babylonian method.\n///\n/// @dev See https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method.\n///\n/// Notes:\n/// - If x is not a perfect square, the result is rounded down.\n/// - Credits to OpenZeppelin for the explanations in comments below.\n///\n/// @param x The uint256 number for which to calculate the square root.\n/// @return result The result as a uint256.\n/// @custom:smtchecker abstract-function-nondet\nfunction sqrt(uint256 x) pure returns (uint256 result) {\n if (x == 0) {\n return 0;\n }\n\n // For our first guess, we calculate the biggest power of 2 which is smaller than the square root of x.\n //\n // We know that the \"msb\" (most significant bit) of x is a power of 2 such that we have:\n //\n // $$\n // msb(x) <= x <= 2*msb(x)$\n // $$\n //\n // We write $msb(x)$ as $2^k$, and we get:\n //\n // $$\n // k = log_2(x)\n // $$\n //\n // Thus, we can write the initial inequality as:\n //\n // $$\n // 2^{log_2(x)} <= x <= 2*2^{log_2(x)+1} \\\\\n // sqrt(2^k) <= sqrt(x) < sqrt(2^{k+1}) \\\\\n // 2^{k/2} <= sqrt(x) < 2^{(k+1)/2} <= 2^{(k/2)+1}\n // $$\n //\n // Consequently, $2^{log_2(x) /2} is a good first approximation of sqrt(x) with at least one correct bit.\n uint256 xAux = uint256(x);\n result = 1;\n if (xAux >= 2**128) {\n xAux >>= 128;\n result <<= 64;\n }\n if (xAux >= 2**64) {\n xAux >>= 64;\n result <<= 32;\n }\n if (xAux >= 2**32) {\n xAux >>= 32;\n result <<= 16;\n }\n if (xAux >= 2**16) {\n xAux >>= 16;\n result <<= 8;\n }\n if (xAux >= 2**8) {\n xAux >>= 8;\n result <<= 4;\n }\n if (xAux >= 2**4) {\n xAux >>= 4;\n result <<= 2;\n }\n if (xAux >= 2**2) {\n result <<= 1;\n }\n\n // At this point, `result` is an estimation with at least one bit of precision. We know the true value has at\n // most 128 bits, since it is the square root of a uint256. Newton's method converges quadratically (precision\n // doubles at every iteration). We thus need at most 7 iteration to turn our partial result with one bit of\n // precision into the expected uint128 result.\n unchecked {\n result = (result + x / result) >> 1;\n result = (result + x / result) >> 1;\n result = (result + x / result) >> 1;\n result = (result + x / result) >> 1;\n result = (result + x / result) >> 1;\n result = (result + x / result) >> 1;\n result = (result + x / result) >> 1;\n\n // If x is not a perfect square, round the result toward zero.\n uint256 roundedResult = x / result;\n if (result >= roundedResult) {\n result = roundedResult;\n }\n }\n}\n" + }, + "contracts/utils/StableMath.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { SafeMath } from \"@openzeppelin/contracts/utils/math/SafeMath.sol\";\n\n// Based on StableMath from Stability Labs Pty. Ltd.\n// https://github.com/mstable/mStable-contracts/blob/master/contracts/shared/StableMath.sol\n\nlibrary StableMath {\n using SafeMath for uint256;\n\n /**\n * @dev Scaling unit for use in specific calculations,\n * where 1 * 10**18, or 1e18 represents a unit '1'\n */\n uint256 private constant FULL_SCALE = 1e18;\n\n /***************************************\n Helpers\n ****************************************/\n\n /**\n * @dev Adjust the scale of an integer\n * @param to Decimals to scale to\n * @param from Decimals to scale from\n */\n function scaleBy(\n uint256 x,\n uint256 to,\n uint256 from\n ) internal pure returns (uint256) {\n if (to > from) {\n x = x.mul(10**(to - from));\n } else if (to < from) {\n // slither-disable-next-line divide-before-multiply\n x = x.div(10**(from - to));\n }\n return x;\n }\n\n /***************************************\n Precise Arithmetic\n ****************************************/\n\n /**\n * @dev Multiplies two precise units, and then truncates by the full scale\n * @param x Left hand input to multiplication\n * @param y Right hand input to multiplication\n * @return Result after multiplying the two inputs and then dividing by the shared\n * scale unit\n */\n function mulTruncate(uint256 x, uint256 y) internal pure returns (uint256) {\n return mulTruncateScale(x, y, FULL_SCALE);\n }\n\n /**\n * @dev Multiplies two precise units, and then truncates by the given scale. For example,\n * when calculating 90% of 10e18, (10e18 * 9e17) / 1e18 = (9e36) / 1e18 = 9e18\n * @param x Left hand input to multiplication\n * @param y Right hand input to multiplication\n * @param scale Scale unit\n * @return Result after multiplying the two inputs and then dividing by the shared\n * scale unit\n */\n function mulTruncateScale(\n uint256 x,\n uint256 y,\n uint256 scale\n ) internal pure returns (uint256) {\n // e.g. assume scale = fullScale\n // z = 10e18 * 9e17 = 9e36\n uint256 z = x.mul(y);\n // return 9e36 / 1e18 = 9e18\n return z.div(scale);\n }\n\n /**\n * @dev Multiplies two precise units, and then truncates by the full scale, rounding up the result\n * @param x Left hand input to multiplication\n * @param y Right hand input to multiplication\n * @return Result after multiplying the two inputs and then dividing by the shared\n * scale unit, rounded up to the closest base unit.\n */\n function mulTruncateCeil(uint256 x, uint256 y)\n internal\n pure\n returns (uint256)\n {\n // e.g. 8e17 * 17268172638 = 138145381104e17\n uint256 scaled = x.mul(y);\n // e.g. 138145381104e17 + 9.99...e17 = 138145381113.99...e17\n uint256 ceil = scaled.add(FULL_SCALE.sub(1));\n // e.g. 13814538111.399...e18 / 1e18 = 13814538111\n return ceil.div(FULL_SCALE);\n }\n\n /**\n * @dev Precisely divides two units, by first scaling the left hand operand. Useful\n * for finding percentage weightings, i.e. 8e18/10e18 = 80% (or 8e17)\n * @param x Left hand input to division\n * @param y Right hand input to division\n * @return Result after multiplying the left operand by the scale, and\n * executing the division on the right hand input.\n */\n function divPrecisely(uint256 x, uint256 y)\n internal\n pure\n returns (uint256)\n {\n // e.g. 8e18 * 1e18 = 8e36\n uint256 z = x.mul(FULL_SCALE);\n // e.g. 8e36 / 10e18 = 8e17\n return z.div(y);\n }\n}\n" + }, + "contracts/vault/VaultStorage.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title OToken VaultStorage contract\n * @notice The VaultStorage contract defines the storage for the Vault contracts\n * @author Origin Protocol Inc\n */\n\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport { Address } from \"@openzeppelin/contracts/utils/Address.sol\";\n\nimport { IStrategy } from \"../interfaces/IStrategy.sol\";\nimport { IERC20Metadata } from \"@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol\";\nimport { Governable } from \"../governance/Governable.sol\";\nimport { OUSD } from \"../token/OUSD.sol\";\nimport { Initializable } from \"../utils/Initializable.sol\";\nimport \"../utils/Helpers.sol\";\n\nabstract contract VaultStorage is Initializable, Governable {\n using SafeERC20 for IERC20;\n\n event AssetAllocated(address _asset, address _strategy, uint256 _amount);\n event StrategyApproved(address _addr);\n event StrategyRemoved(address _addr);\n event Mint(address _addr, uint256 _value);\n event Redeem(address _addr, uint256 _value);\n event CapitalPaused();\n event CapitalUnpaused();\n event DefaultStrategyUpdated(address _strategy);\n event RebasePaused();\n event RebaseUnpaused();\n event VaultBufferUpdated(uint256 _vaultBuffer);\n event AllocateThresholdUpdated(uint256 _threshold);\n event RebaseThresholdUpdated(uint256 _threshold);\n event StrategistUpdated(address _address);\n event MaxSupplyDiffChanged(uint256 maxSupplyDiff);\n event YieldDistribution(address _to, uint256 _yield, uint256 _fee);\n event TrusteeFeeBpsChanged(uint256 _basis);\n event TrusteeAddressChanged(address _address);\n event StrategyAddedToMintWhitelist(address indexed strategy);\n event StrategyRemovedFromMintWhitelist(address indexed strategy);\n event RebasePerSecondMaxChanged(uint256 rebaseRatePerSecond);\n event DripDurationChanged(uint256 dripDuration);\n event WithdrawalRequested(\n address indexed _withdrawer,\n uint256 indexed _requestId,\n uint256 _amount,\n uint256 _queued\n );\n event WithdrawalClaimed(\n address indexed _withdrawer,\n uint256 indexed _requestId,\n uint256 _amount\n );\n event WithdrawalClaimable(uint256 _claimable, uint256 _newClaimable);\n event WithdrawalClaimDelayUpdated(uint256 _newDelay);\n\n // Since we are proxy, all state should be uninitalized.\n // Since this storage contract does not have logic directly on it\n // we should not be checking for to see if these variables can be constant.\n // slither-disable-start uninitialized-state\n // slither-disable-start constable-states\n\n /// @dev mapping of supported vault assets to their configuration\n uint256 private _deprecated_assets;\n /// @dev list of all assets supported by the vault.\n address[] private _deprecated_allAssets;\n\n // Strategies approved for use by the Vault\n struct Strategy {\n bool isSupported;\n uint256 _deprecated; // Deprecated storage slot\n }\n /// @dev mapping of strategy contracts to their configuration\n mapping(address => Strategy) public strategies;\n /// @dev list of all vault strategies\n address[] internal allStrategies;\n\n /// @notice Address of the Oracle price provider contract\n address private _deprecated_priceProvider;\n /// @notice pause rebasing if true\n bool public rebasePaused;\n /// @notice pause operations that change the OToken supply.\n /// eg mint, redeem, allocate, mint/burn for strategy\n bool public capitalPaused;\n /// @notice Redemption fee in basis points. eg 50 = 0.5%\n uint256 private _deprecated_redeemFeeBps;\n /// @notice Percentage of assets to keep in Vault to handle (most) withdrawals. 100% = 1e18.\n uint256 public vaultBuffer;\n /// @notice OToken mints over this amount automatically allocate funds. 18 decimals.\n uint256 public autoAllocateThreshold;\n /// @notice OToken mints over this amount automatically rebase. 18 decimals.\n uint256 public rebaseThreshold;\n\n /// @dev Address of the OToken token. eg OUSD or OETH.\n OUSD public oToken;\n\n /// @dev Address of the contract responsible for post rebase syncs with AMMs\n address private _deprecated_rebaseHooksAddr = address(0);\n\n /// @dev Deprecated: Address of Uniswap\n address private _deprecated_uniswapAddr = address(0);\n\n /// @notice Address of the Strategist\n address public strategistAddr = address(0);\n\n /// @notice Mapping of asset address to the Strategy that they should automatically\n // be allocated to\n uint256 private _deprecated_assetDefaultStrategies;\n\n /// @notice Max difference between total supply and total value of assets. 18 decimals.\n uint256 public maxSupplyDiff;\n\n /// @notice Trustee contract that can collect a percentage of yield\n address public trusteeAddress;\n\n /// @notice Amount of yield collected in basis points. eg 2000 = 20%\n uint256 public trusteeFeeBps;\n\n /// @dev Deprecated: Tokens that should be swapped for stablecoins\n address[] private _deprecated_swapTokens;\n\n /// @notice Metapool strategy that is allowed to mint/burn OTokens without changing collateral\n\n address private _deprecated_ousdMetaStrategy;\n\n /// @notice How much OTokens are currently minted by the strategy\n int256 private _deprecated_netOusdMintedForStrategy;\n\n /// @notice How much net total OTokens are allowed to be minted by all strategies\n uint256 private _deprecated_netOusdMintForStrategyThreshold;\n\n uint256 private _deprecated_swapConfig;\n\n // List of strategies that can mint oTokens directly\n // Used in OETHBaseVaultCore\n mapping(address => bool) public isMintWhitelistedStrategy;\n\n /// @notice Address of the Dripper contract that streams harvested rewards to the Vault\n /// @dev The vault is proxied so needs to be set with setDripper against the proxy contract.\n address private _deprecated_dripper;\n\n /// Withdrawal Queue Storage /////\n\n struct WithdrawalQueueMetadata {\n // cumulative total of all withdrawal requests included the ones that have already been claimed\n uint128 queued;\n // cumulative total of all the requests that can be claimed including the ones that have already been claimed\n uint128 claimable;\n // total of all the requests that have been claimed\n uint128 claimed;\n // index of the next withdrawal request starting at 0\n uint128 nextWithdrawalIndex;\n }\n\n /// @notice Global metadata for the withdrawal queue including:\n /// queued - cumulative total of all withdrawal requests included the ones that have already been claimed\n /// claimable - cumulative total of all the requests that can be claimed including the ones already claimed\n /// claimed - total of all the requests that have been claimed\n /// nextWithdrawalIndex - index of the next withdrawal request starting at 0\n WithdrawalQueueMetadata public withdrawalQueueMetadata;\n\n struct WithdrawalRequest {\n address withdrawer;\n bool claimed;\n uint40 timestamp; // timestamp of the withdrawal request\n // Amount of oTokens to redeem. eg OETH\n uint128 amount;\n // cumulative total of all withdrawal requests including this one.\n // this request can be claimed when this queued amount is less than or equal to the queue's claimable amount.\n uint128 queued;\n }\n\n /// @notice Mapping of withdrawal request indices to the user withdrawal request data\n mapping(uint256 => WithdrawalRequest) public withdrawalRequests;\n\n /// @notice Sets a minimum delay that is required to elapse between\n /// requesting async withdrawals and claiming the request.\n /// When set to 0 async withdrawals are disabled.\n uint256 public withdrawalClaimDelay;\n\n /// @notice Time in seconds that the vault last rebased yield.\n uint64 public lastRebase;\n\n /// @notice Automatic rebase yield calculations. In seconds. Set to 0 or 1 to disable.\n uint64 public dripDuration;\n\n /// @notice max rebase percentage per second\n /// Can be used to set maximum yield of the protocol,\n /// spreading out yield over time\n uint64 public rebasePerSecondMax;\n\n /// @notice target rebase rate limit, based on past rates and funds available.\n uint64 public rebasePerSecondTarget;\n\n uint256 internal constant MAX_REBASE = 0.02 ether;\n uint256 internal constant MAX_REBASE_PER_SECOND =\n uint256(0.05 ether) / 1 days;\n\n /// @notice Default strategy for asset\n address public defaultStrategy;\n\n // For future use\n uint256[42] private __gap;\n\n /// @notice Index of WETH asset in allAssets array\n /// Legacy OETHVaultCore code, relocated here for vault consistency.\n uint256 private _deprecated_wethAssetIndex;\n\n /// @dev Address of the asset (eg. WETH or USDC)\n address public immutable asset;\n uint8 internal immutable assetDecimals;\n\n // slither-disable-end constable-states\n // slither-disable-end uninitialized-state\n\n constructor(address _asset) {\n uint8 _decimals = IERC20Metadata(_asset).decimals();\n require(_decimals <= 18, \"invalid asset decimals\");\n asset = _asset;\n assetDecimals = _decimals;\n }\n\n /// @notice Deprecated: use `oToken()` instead.\n function oUSD() external view returns (OUSD) {\n return oToken;\n }\n}\n" + }, + "lib/openzeppelin/interfaces/IERC4626.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\nimport { IERC20Metadata } from \"@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol\";\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\n\ninterface IERC4626 is IERC20, IERC20Metadata {\n event Deposit(address indexed caller, address indexed owner, uint256 assets, uint256 shares);\n\n event Withdraw(\n address indexed caller,\n address indexed receiver,\n address indexed owner,\n uint256 assets,\n uint256 shares\n );\n\n /**\n * @dev Returns the address of the underlying token used for the Vault for accounting, depositing, and withdrawing.\n *\n * - MUST be an ERC-20 token contract.\n * - MUST NOT revert.\n */\n function asset() external view returns (address assetTokenAddress);\n\n /**\n * @dev Returns the total amount of the underlying asset that is “managed” by Vault.\n *\n * - SHOULD include any compounding that occurs from yield.\n * - MUST be inclusive of any fees that are charged against assets in the Vault.\n * - MUST NOT revert.\n */\n function totalAssets() external view returns (uint256 totalManagedAssets);\n\n /**\n * @dev Returns the amount of shares that the Vault would exchange for the amount of assets provided, in an ideal\n * scenario where all the conditions are met.\n *\n * - MUST NOT be inclusive of any fees that are charged against assets in the Vault.\n * - MUST NOT show any variations depending on the caller.\n * - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange.\n * - MUST NOT revert.\n *\n * NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the\n * “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and\n * from.\n */\n function convertToShares(uint256 assets) external view returns (uint256 shares);\n\n /**\n * @dev Returns the amount of assets that the Vault would exchange for the amount of shares provided, in an ideal\n * scenario where all the conditions are met.\n *\n * - MUST NOT be inclusive of any fees that are charged against assets in the Vault.\n * - MUST NOT show any variations depending on the caller.\n * - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange.\n * - MUST NOT revert.\n *\n * NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the\n * “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and\n * from.\n */\n function convertToAssets(uint256 shares) external view returns (uint256 assets);\n\n /**\n * @dev Returns the maximum amount of the underlying asset that can be deposited into the Vault for the receiver,\n * through a deposit call.\n *\n * - MUST return a limited value if receiver is subject to some deposit limit.\n * - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of assets that may be deposited.\n * - MUST NOT revert.\n */\n function maxDeposit(address receiver) external view returns (uint256 maxAssets);\n\n /**\n * @dev Allows an on-chain or off-chain user to simulate the effects of their deposit at the current block, given\n * current on-chain conditions.\n *\n * - MUST return as close to and no more than the exact amount of Vault shares that would be minted in a deposit\n * call in the same transaction. I.e. deposit should return the same or more shares as previewDeposit if called\n * in the same transaction.\n * - MUST NOT account for deposit limits like those returned from maxDeposit and should always act as though the\n * deposit would be accepted, regardless if the user has enough tokens approved, etc.\n * - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees.\n * - MUST NOT revert.\n *\n * NOTE: any unfavorable discrepancy between convertToShares and previewDeposit SHOULD be considered slippage in\n * share price or some other type of condition, meaning the depositor will lose assets by depositing.\n */\n function previewDeposit(uint256 assets) external view returns (uint256 shares);\n\n /**\n * @dev Mints shares Vault shares to receiver by depositing exactly amount of underlying tokens.\n *\n * - MUST emit the Deposit event.\n * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the\n * deposit execution, and are accounted for during deposit.\n * - MUST revert if all of assets cannot be deposited (due to deposit limit being reached, slippage, the user not\n * approving enough underlying tokens to the Vault contract, etc).\n *\n * NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token.\n */\n function deposit(uint256 assets, address receiver) external returns (uint256 shares);\n\n /**\n * @dev Returns the maximum amount of the Vault shares that can be minted for the receiver, through a mint call.\n * - MUST return a limited value if receiver is subject to some mint limit.\n * - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of shares that may be minted.\n * - MUST NOT revert.\n */\n function maxMint(address receiver) external view returns (uint256 maxShares);\n\n /**\n * @dev Allows an on-chain or off-chain user to simulate the effects of their mint at the current block, given\n * current on-chain conditions.\n *\n * - MUST return as close to and no fewer than the exact amount of assets that would be deposited in a mint call\n * in the same transaction. I.e. mint should return the same or fewer assets as previewMint if called in the\n * same transaction.\n * - MUST NOT account for mint limits like those returned from maxMint and should always act as though the mint\n * would be accepted, regardless if the user has enough tokens approved, etc.\n * - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees.\n * - MUST NOT revert.\n *\n * NOTE: any unfavorable discrepancy between convertToAssets and previewMint SHOULD be considered slippage in\n * share price or some other type of condition, meaning the depositor will lose assets by minting.\n */\n function previewMint(uint256 shares) external view returns (uint256 assets);\n\n /**\n * @dev Mints exactly shares Vault shares to receiver by depositing amount of underlying tokens.\n *\n * - MUST emit the Deposit event.\n * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the mint\n * execution, and are accounted for during mint.\n * - MUST revert if all of shares cannot be minted (due to deposit limit being reached, slippage, the user not\n * approving enough underlying tokens to the Vault contract, etc).\n *\n * NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token.\n */\n function mint(uint256 shares, address receiver) external returns (uint256 assets);\n\n /**\n * @dev Returns the maximum amount of the underlying asset that can be withdrawn from the owner balance in the\n * Vault, through a withdraw call.\n *\n * - MUST return a limited value if owner is subject to some withdrawal limit or timelock.\n * - MUST NOT revert.\n */\n function maxWithdraw(address owner) external view returns (uint256 maxAssets);\n\n /**\n * @dev Allows an on-chain or off-chain user to simulate the effects of their withdrawal at the current block,\n * given current on-chain conditions.\n *\n * - MUST return as close to and no fewer than the exact amount of Vault shares that would be burned in a withdraw\n * call in the same transaction. I.e. withdraw should return the same or fewer shares as previewWithdraw if\n * called\n * in the same transaction.\n * - MUST NOT account for withdrawal limits like those returned from maxWithdraw and should always act as though\n * the withdrawal would be accepted, regardless if the user has enough shares, etc.\n * - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees.\n * - MUST NOT revert.\n *\n * NOTE: any unfavorable discrepancy between convertToShares and previewWithdraw SHOULD be considered slippage in\n * share price or some other type of condition, meaning the depositor will lose assets by depositing.\n */\n function previewWithdraw(uint256 assets) external view returns (uint256 shares);\n\n /**\n * @dev Burns shares from owner and sends exactly assets of underlying tokens to receiver.\n *\n * - MUST emit the Withdraw event.\n * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the\n * withdraw execution, and are accounted for during withdraw.\n * - MUST revert if all of assets cannot be withdrawn (due to withdrawal limit being reached, slippage, the owner\n * not having enough shares, etc).\n *\n * Note that some implementations will require pre-requesting to the Vault before a withdrawal may be performed.\n * Those methods should be performed separately.\n */\n function withdraw(\n uint256 assets,\n address receiver,\n address owner\n ) external returns (uint256 shares);\n\n /**\n * @dev Returns the maximum amount of Vault shares that can be redeemed from the owner balance in the Vault,\n * through a redeem call.\n *\n * - MUST return a limited value if owner is subject to some withdrawal limit or timelock.\n * - MUST return balanceOf(owner) if owner is not subject to any withdrawal limit or timelock.\n * - MUST NOT revert.\n */\n function maxRedeem(address owner) external view returns (uint256 maxShares);\n\n /**\n * @dev Allows an on-chain or off-chain user to simulate the effects of their redeemption at the current block,\n * given current on-chain conditions.\n *\n * - MUST return as close to and no more than the exact amount of assets that would be withdrawn in a redeem call\n * in the same transaction. I.e. redeem should return the same or more assets as previewRedeem if called in the\n * same transaction.\n * - MUST NOT account for redemption limits like those returned from maxRedeem and should always act as though the\n * redemption would be accepted, regardless if the user has enough shares, etc.\n * - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees.\n * - MUST NOT revert.\n *\n * NOTE: any unfavorable discrepancy between convertToAssets and previewRedeem SHOULD be considered slippage in\n * share price or some other type of condition, meaning the depositor will lose assets by redeeming.\n */\n function previewRedeem(uint256 shares) external view returns (uint256 assets);\n\n /**\n * @dev Burns exactly shares from owner and sends assets of underlying tokens to receiver.\n *\n * - MUST emit the Withdraw event.\n * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the\n * redeem execution, and are accounted for during redeem.\n * - MUST revert if all of shares cannot be redeemed (due to withdrawal limit being reached, slippage, the owner\n * not having enough shares, etc).\n *\n * NOTE: some implementations will require pre-requesting to the Vault before a withdrawal may be performed.\n * Those methods should be performed separately.\n */\n function redeem(\n uint256 shares,\n address receiver,\n address owner\n ) external returns (uint256 assets);\n}" + }, + "lib/rooster/openzeppelin-custom/contracts/utils/math/Math.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.1.0) (utils/math/Math.sol)\n\npragma solidity ^0.8.20;\n\nimport {Panic} from \"../Panic.sol\";\nimport {SafeCast} from \"./SafeCast.sol\";\n\n/**\n * @dev Standard math utilities missing in the Solidity language.\n */\nlibrary Math {\n enum Rounding {\n Floor, // Toward negative infinity\n Ceil, // Toward positive infinity\n Trunc, // Toward zero\n Expand // Away from zero\n }\n\n /**\n * @dev Return the 512-bit addition of two uint256.\n *\n * The result is stored in two 256 variables such that sum = high * 2²⁵⁶ + low.\n */\n function add512(uint256 a, uint256 b) internal pure returns (uint256 high, uint256 low) {\n assembly (\"memory-safe\") {\n low := add(a, b)\n high := lt(low, a)\n }\n }\n\n /**\n * @dev Return the 512-bit multiplication of two uint256.\n *\n * The result is stored in two 256 variables such that product = high * 2²⁵⁶ + low.\n */\n function mul512(uint256 a, uint256 b) internal pure returns (uint256 high, uint256 low) {\n // 512-bit multiply [high low] = x * y. Compute the product mod 2²⁵⁶ and mod 2²⁵⁶ - 1, then use\n // the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256\n // variables such that product = high * 2²⁵⁶ + low.\n assembly (\"memory-safe\") {\n let mm := mulmod(a, b, not(0))\n low := mul(a, b)\n high := sub(sub(mm, low), lt(mm, low))\n }\n }\n\n /**\n * @dev Returns the addition of two unsigned integers, with a success flag (no overflow).\n */\n function tryAdd(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {\n unchecked {\n uint256 c = a + b;\n success = c >= a;\n result = c * SafeCast.toUint(success);\n }\n }\n\n /**\n * @dev Returns the subtraction of two unsigned integers, with a success flag (no overflow).\n */\n function trySub(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {\n unchecked {\n uint256 c = a - b;\n success = c <= a;\n result = c * SafeCast.toUint(success);\n }\n }\n\n /**\n * @dev Returns the multiplication of two unsigned integers, with a success flag (no overflow).\n */\n function tryMul(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {\n unchecked {\n uint256 c = a * b;\n assembly (\"memory-safe\") {\n // Only true when the multiplication doesn't overflow\n // (c / a == b) || (a == 0)\n success := or(eq(div(c, a), b), iszero(a))\n }\n // equivalent to: success ? c : 0\n result = c * SafeCast.toUint(success);\n }\n }\n\n /**\n * @dev Returns the division of two unsigned integers, with a success flag (no division by zero).\n */\n function tryDiv(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {\n unchecked {\n success = b > 0;\n assembly (\"memory-safe\") {\n // The `DIV` opcode returns zero when the denominator is 0.\n result := div(a, b)\n }\n }\n }\n\n /**\n * @dev Returns the remainder of dividing two unsigned integers, with a success flag (no division by zero).\n */\n function tryMod(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {\n unchecked {\n success = b > 0;\n assembly (\"memory-safe\") {\n // The `MOD` opcode returns zero when the denominator is 0.\n result := mod(a, b)\n }\n }\n }\n\n /**\n * @dev Unsigned saturating addition, bounds to `2²⁵⁶ - 1` instead of overflowing.\n */\n function saturatingAdd(uint256 a, uint256 b) internal pure returns (uint256) {\n (bool success, uint256 result) = tryAdd(a, b);\n return ternary(success, result, type(uint256).max);\n }\n\n /**\n * @dev Unsigned saturating subtraction, bounds to zero instead of overflowing.\n */\n function saturatingSub(uint256 a, uint256 b) internal pure returns (uint256) {\n (, uint256 result) = trySub(a, b);\n return result;\n }\n\n /**\n * @dev Unsigned saturating multiplication, bounds to `2²⁵⁶ - 1` instead of overflowing.\n */\n function saturatingMul(uint256 a, uint256 b) internal pure returns (uint256) {\n (bool success, uint256 result) = tryMul(a, b);\n return ternary(success, result, type(uint256).max);\n }\n\n /**\n * @dev Branchless ternary evaluation for `a ? b : c`. Gas costs are constant.\n *\n * IMPORTANT: This function may reduce bytecode size and consume less gas when used standalone.\n * However, the compiler may optimize Solidity ternary operations (i.e. `a ? b : c`) to only compute\n * one branch when needed, making this function more expensive.\n */\n function ternary(bool condition, uint256 a, uint256 b) internal pure returns (uint256) {\n unchecked {\n // branchless ternary works because:\n // b ^ (a ^ b) == a\n // b ^ 0 == b\n return b ^ ((a ^ b) * SafeCast.toUint(condition));\n }\n }\n\n /**\n * @dev Returns the largest of two numbers.\n */\n function max(uint256 a, uint256 b) internal pure returns (uint256) {\n return ternary(a > b, a, b);\n }\n\n /**\n * @dev Returns the smallest of two numbers.\n */\n function min(uint256 a, uint256 b) internal pure returns (uint256) {\n return ternary(a < b, a, b);\n }\n\n /**\n * @dev Returns the average of two numbers. The result is rounded towards\n * zero.\n */\n function average(uint256 a, uint256 b) internal pure returns (uint256) {\n // (a + b) / 2 can overflow.\n return (a & b) + (a ^ b) / 2;\n }\n\n /**\n * @dev Returns the ceiling of the division of two numbers.\n *\n * This differs from standard division with `/` in that it rounds towards infinity instead\n * of rounding towards zero.\n */\n function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {\n if (b == 0) {\n // Guarantee the same behavior as in a regular Solidity division.\n Panic.panic(Panic.DIVISION_BY_ZERO);\n }\n\n // The following calculation ensures accurate ceiling division without overflow.\n // Since a is non-zero, (a - 1) / b will not overflow.\n // The largest possible result occurs when (a - 1) / b is type(uint256).max,\n // but the largest value we can obtain is type(uint256).max - 1, which happens\n // when a = type(uint256).max and b = 1.\n unchecked {\n return SafeCast.toUint(a > 0) * ((a - 1) / b + 1);\n }\n }\n\n /**\n * @dev Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or\n * denominator == 0.\n *\n * Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv) with further edits by\n * Uniswap Labs also under MIT license.\n */\n function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {\n unchecked {\n (uint256 high, uint256 low) = mul512(x, y);\n\n // Handle non-overflow cases, 256 by 256 division.\n if (high == 0) {\n // Solidity will revert if denominator == 0, unlike the div opcode on its own.\n // The surrounding unchecked block does not change this fact.\n // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.\n return low / denominator;\n }\n\n // Make sure the result is less than 2²⁵⁶. Also prevents denominator == 0.\n if (denominator <= high) {\n Panic.panic(ternary(denominator == 0, Panic.DIVISION_BY_ZERO, Panic.UNDER_OVERFLOW));\n }\n\n ///////////////////////////////////////////////\n // 512 by 256 division.\n ///////////////////////////////////////////////\n\n // Make division exact by subtracting the remainder from [high low].\n uint256 remainder;\n assembly (\"memory-safe\") {\n // Compute remainder using mulmod.\n remainder := mulmod(x, y, denominator)\n\n // Subtract 256 bit number from 512 bit number.\n high := sub(high, gt(remainder, low))\n low := sub(low, remainder)\n }\n\n // Factor powers of two out of denominator and compute largest power of two divisor of denominator.\n // Always >= 1. See https://cs.stackexchange.com/q/138556/92363.\n\n uint256 twos = denominator & (0 - denominator);\n assembly (\"memory-safe\") {\n // Divide denominator by twos.\n denominator := div(denominator, twos)\n\n // Divide [high low] by twos.\n low := div(low, twos)\n\n // Flip twos such that it is 2²⁵⁶ / twos. If twos is zero, then it becomes one.\n twos := add(div(sub(0, twos), twos), 1)\n }\n\n // Shift in bits from high into low.\n low |= high * twos;\n\n // Invert denominator mod 2²⁵⁶. Now that denominator is an odd number, it has an inverse modulo 2²⁵⁶ such\n // that denominator * inv ≡ 1 mod 2²⁵⁶. Compute the inverse by starting with a seed that is correct for\n // four bits. That is, denominator * inv ≡ 1 mod 2⁴.\n uint256 inverse = (3 * denominator) ^ 2;\n\n // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also\n // works in modular arithmetic, doubling the correct bits in each step.\n inverse *= 2 - denominator * inverse; // inverse mod 2⁸\n inverse *= 2 - denominator * inverse; // inverse mod 2¹⁶\n inverse *= 2 - denominator * inverse; // inverse mod 2³²\n inverse *= 2 - denominator * inverse; // inverse mod 2⁶⁴\n inverse *= 2 - denominator * inverse; // inverse mod 2¹²⁸\n inverse *= 2 - denominator * inverse; // inverse mod 2²⁵⁶\n\n // Because the division is now exact we can divide by multiplying with the modular inverse of denominator.\n // This will give us the correct result modulo 2²⁵⁶. Since the preconditions guarantee that the outcome is\n // less than 2²⁵⁶, this is the final result. We don't need to compute the high bits of the result and high\n // is no longer required.\n result = low * inverse;\n return result;\n }\n }\n\n /**\n * @dev Calculates x * y / denominator with full precision, following the selected rounding direction.\n */\n function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {\n return mulDiv(x, y, denominator) + SafeCast.toUint(unsignedRoundsUp(rounding) && mulmod(x, y, denominator) > 0);\n }\n\n /**\n * @dev Calculates floor(x * y >> n) with full precision. Throws if result overflows a uint256.\n */\n function mulShr(uint256 x, uint256 y, uint8 n) internal pure returns (uint256 result) {\n unchecked {\n (uint256 high, uint256 low) = mul512(x, y);\n if (high >= 1 << n) {\n Panic.panic(Panic.UNDER_OVERFLOW);\n }\n return (high << (256 - n)) | (low >> n);\n }\n }\n\n /**\n * @dev Calculates x * y >> n with full precision, following the selected rounding direction.\n */\n function mulShr(uint256 x, uint256 y, uint8 n, Rounding rounding) internal pure returns (uint256) {\n return mulShr(x, y, n) + SafeCast.toUint(unsignedRoundsUp(rounding) && mulmod(x, y, 1 << n) > 0);\n }\n\n /**\n * @dev Calculate the modular multiplicative inverse of a number in Z/nZ.\n *\n * If n is a prime, then Z/nZ is a field. In that case all elements are inversible, except 0.\n * If n is not a prime, then Z/nZ is not a field, and some elements might not be inversible.\n *\n * If the input value is not inversible, 0 is returned.\n *\n * NOTE: If you know for sure that n is (big) a prime, it may be cheaper to use Fermat's little theorem and get the\n * inverse using `Math.modExp(a, n - 2, n)`. See {invModPrime}.\n */\n function invMod(uint256 a, uint256 n) internal pure returns (uint256) {\n unchecked {\n if (n == 0) return 0;\n\n // The inverse modulo is calculated using the Extended Euclidean Algorithm (iterative version)\n // Used to compute integers x and y such that: ax + ny = gcd(a, n).\n // When the gcd is 1, then the inverse of a modulo n exists and it's x.\n // ax + ny = 1\n // ax = 1 + (-y)n\n // ax ≡ 1 (mod n) # x is the inverse of a modulo n\n\n // If the remainder is 0 the gcd is n right away.\n uint256 remainder = a % n;\n uint256 gcd = n;\n\n // Therefore the initial coefficients are:\n // ax + ny = gcd(a, n) = n\n // 0a + 1n = n\n int256 x = 0;\n int256 y = 1;\n\n while (remainder != 0) {\n uint256 quotient = gcd / remainder;\n\n (gcd, remainder) = (\n // The old remainder is the next gcd to try.\n remainder,\n // Compute the next remainder.\n // Can't overflow given that (a % gcd) * (gcd // (a % gcd)) <= gcd\n // where gcd is at most n (capped to type(uint256).max)\n gcd - remainder * quotient\n );\n\n (x, y) = (\n // Increment the coefficient of a.\n y,\n // Decrement the coefficient of n.\n // Can overflow, but the result is casted to uint256 so that the\n // next value of y is \"wrapped around\" to a value between 0 and n - 1.\n x - y * int256(quotient)\n );\n }\n\n if (gcd != 1) return 0; // No inverse exists.\n return ternary(x < 0, n - uint256(-x), uint256(x)); // Wrap the result if it's negative.\n }\n }\n\n /**\n * @dev Variant of {invMod}. More efficient, but only works if `p` is known to be a prime greater than `2`.\n *\n * From https://en.wikipedia.org/wiki/Fermat%27s_little_theorem[Fermat's little theorem], we know that if p is\n * prime, then `a**(p-1) ≡ 1 mod p`. As a consequence, we have `a * a**(p-2) ≡ 1 mod p`, which means that\n * `a**(p-2)` is the modular multiplicative inverse of a in Fp.\n *\n * NOTE: this function does NOT check that `p` is a prime greater than `2`.\n */\n function invModPrime(uint256 a, uint256 p) internal view returns (uint256) {\n unchecked {\n return Math.modExp(a, p - 2, p);\n }\n }\n\n /**\n * @dev Returns the modular exponentiation of the specified base, exponent and modulus (b ** e % m)\n *\n * Requirements:\n * - modulus can't be zero\n * - underlying staticcall to precompile must succeed\n *\n * IMPORTANT: The result is only valid if the underlying call succeeds. When using this function, make\n * sure the chain you're using it on supports the precompiled contract for modular exponentiation\n * at address 0x05 as specified in https://eips.ethereum.org/EIPS/eip-198[EIP-198]. Otherwise,\n * the underlying function will succeed given the lack of a revert, but the result may be incorrectly\n * interpreted as 0.\n */\n function modExp(uint256 b, uint256 e, uint256 m) internal view returns (uint256) {\n (bool success, uint256 result) = tryModExp(b, e, m);\n if (!success) {\n Panic.panic(Panic.DIVISION_BY_ZERO);\n }\n return result;\n }\n\n /**\n * @dev Returns the modular exponentiation of the specified base, exponent and modulus (b ** e % m).\n * It includes a success flag indicating if the operation succeeded. Operation will be marked as failed if trying\n * to operate modulo 0 or if the underlying precompile reverted.\n *\n * IMPORTANT: The result is only valid if the success flag is true. When using this function, make sure the chain\n * you're using it on supports the precompiled contract for modular exponentiation at address 0x05 as specified in\n * https://eips.ethereum.org/EIPS/eip-198[EIP-198]. Otherwise, the underlying function will succeed given the lack\n * of a revert, but the result may be incorrectly interpreted as 0.\n */\n function tryModExp(uint256 b, uint256 e, uint256 m) internal view returns (bool success, uint256 result) {\n if (m == 0) return (false, 0);\n assembly (\"memory-safe\") {\n let ptr := mload(0x40)\n // | Offset | Content | Content (Hex) |\n // |-----------|------------|--------------------------------------------------------------------|\n // | 0x00:0x1f | size of b | 0x0000000000000000000000000000000000000000000000000000000000000020 |\n // | 0x20:0x3f | size of e | 0x0000000000000000000000000000000000000000000000000000000000000020 |\n // | 0x40:0x5f | size of m | 0x0000000000000000000000000000000000000000000000000000000000000020 |\n // | 0x60:0x7f | value of b | 0x<.............................................................b> |\n // | 0x80:0x9f | value of e | 0x<.............................................................e> |\n // | 0xa0:0xbf | value of m | 0x<.............................................................m> |\n mstore(ptr, 0x20)\n mstore(add(ptr, 0x20), 0x20)\n mstore(add(ptr, 0x40), 0x20)\n mstore(add(ptr, 0x60), b)\n mstore(add(ptr, 0x80), e)\n mstore(add(ptr, 0xa0), m)\n\n // Given the result < m, it's guaranteed to fit in 32 bytes,\n // so we can use the memory scratch space located at offset 0.\n success := staticcall(gas(), 0x05, ptr, 0xc0, 0x00, 0x20)\n result := mload(0x00)\n }\n }\n\n /**\n * @dev Variant of {modExp} that supports inputs of arbitrary length.\n */\n function modExp(bytes memory b, bytes memory e, bytes memory m) internal view returns (bytes memory) {\n (bool success, bytes memory result) = tryModExp(b, e, m);\n if (!success) {\n Panic.panic(Panic.DIVISION_BY_ZERO);\n }\n return result;\n }\n\n /**\n * @dev Variant of {tryModExp} that supports inputs of arbitrary length.\n */\n function tryModExp(\n bytes memory b,\n bytes memory e,\n bytes memory m\n ) internal view returns (bool success, bytes memory result) {\n if (_zeroBytes(m)) return (false, new bytes(0));\n\n uint256 mLen = m.length;\n\n // Encode call args in result and move the free memory pointer\n result = abi.encodePacked(b.length, e.length, mLen, b, e, m);\n\n assembly (\"memory-safe\") {\n let dataPtr := add(result, 0x20)\n // Write result on top of args to avoid allocating extra memory.\n success := staticcall(gas(), 0x05, dataPtr, mload(result), dataPtr, mLen)\n // Overwrite the length.\n // result.length > returndatasize() is guaranteed because returndatasize() == m.length\n mstore(result, mLen)\n // Set the memory pointer after the returned data.\n mstore(0x40, add(dataPtr, mLen))\n }\n }\n\n /**\n * @dev Returns whether the provided byte array is zero.\n */\n function _zeroBytes(bytes memory byteArray) private pure returns (bool) {\n for (uint256 i = 0; i < byteArray.length; ++i) {\n if (byteArray[i] != 0) {\n return false;\n }\n }\n return true;\n }\n\n /**\n * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded\n * towards zero.\n *\n * This method is based on Newton's method for computing square roots; the algorithm is restricted to only\n * using integer operations.\n */\n function sqrt(uint256 a) internal pure returns (uint256) {\n unchecked {\n // Take care of easy edge cases when a == 0 or a == 1\n if (a <= 1) {\n return a;\n }\n\n // In this function, we use Newton's method to get a root of `f(x) := x² - a`. It involves building a\n // sequence x_n that converges toward sqrt(a). For each iteration x_n, we also define the error between\n // the current value as `ε_n = | x_n - sqrt(a) |`.\n //\n // For our first estimation, we consider `e` the smallest power of 2 which is bigger than the square root\n // of the target. (i.e. `2**(e-1) ≤ sqrt(a) < 2**e`). We know that `e ≤ 128` because `(2¹²⁸)² = 2²⁵⁶` is\n // bigger than any uint256.\n //\n // By noticing that\n // `2**(e-1) ≤ sqrt(a) < 2**e → (2**(e-1))² ≤ a < (2**e)² → 2**(2*e-2) ≤ a < 2**(2*e)`\n // we can deduce that `e - 1` is `log2(a) / 2`. We can thus compute `x_n = 2**(e-1)` using a method similar\n // to the msb function.\n uint256 aa = a;\n uint256 xn = 1;\n\n if (aa >= (1 << 128)) {\n aa >>= 128;\n xn <<= 64;\n }\n if (aa >= (1 << 64)) {\n aa >>= 64;\n xn <<= 32;\n }\n if (aa >= (1 << 32)) {\n aa >>= 32;\n xn <<= 16;\n }\n if (aa >= (1 << 16)) {\n aa >>= 16;\n xn <<= 8;\n }\n if (aa >= (1 << 8)) {\n aa >>= 8;\n xn <<= 4;\n }\n if (aa >= (1 << 4)) {\n aa >>= 4;\n xn <<= 2;\n }\n if (aa >= (1 << 2)) {\n xn <<= 1;\n }\n\n // We now have x_n such that `x_n = 2**(e-1) ≤ sqrt(a) < 2**e = 2 * x_n`. This implies ε_n ≤ 2**(e-1).\n //\n // We can refine our estimation by noticing that the middle of that interval minimizes the error.\n // If we move x_n to equal 2**(e-1) + 2**(e-2), then we reduce the error to ε_n ≤ 2**(e-2).\n // This is going to be our x_0 (and ε_0)\n xn = (3 * xn) >> 1; // ε_0 := | x_0 - sqrt(a) | ≤ 2**(e-2)\n\n // From here, Newton's method give us:\n // x_{n+1} = (x_n + a / x_n) / 2\n //\n // One should note that:\n // x_{n+1}² - a = ((x_n + a / x_n) / 2)² - a\n // = ((x_n² + a) / (2 * x_n))² - a\n // = (x_n⁴ + 2 * a * x_n² + a²) / (4 * x_n²) - a\n // = (x_n⁴ + 2 * a * x_n² + a² - 4 * a * x_n²) / (4 * x_n²)\n // = (x_n⁴ - 2 * a * x_n² + a²) / (4 * x_n²)\n // = (x_n² - a)² / (2 * x_n)²\n // = ((x_n² - a) / (2 * x_n))²\n // ≥ 0\n // Which proves that for all n ≥ 1, sqrt(a) ≤ x_n\n //\n // This gives us the proof of quadratic convergence of the sequence:\n // ε_{n+1} = | x_{n+1} - sqrt(a) |\n // = | (x_n + a / x_n) / 2 - sqrt(a) |\n // = | (x_n² + a - 2*x_n*sqrt(a)) / (2 * x_n) |\n // = | (x_n - sqrt(a))² / (2 * x_n) |\n // = | ε_n² / (2 * x_n) |\n // = ε_n² / | (2 * x_n) |\n //\n // For the first iteration, we have a special case where x_0 is known:\n // ε_1 = ε_0² / | (2 * x_0) |\n // ≤ (2**(e-2))² / (2 * (2**(e-1) + 2**(e-2)))\n // ≤ 2**(2*e-4) / (3 * 2**(e-1))\n // ≤ 2**(e-3) / 3\n // ≤ 2**(e-3-log2(3))\n // ≤ 2**(e-4.5)\n //\n // For the following iterations, we use the fact that, 2**(e-1) ≤ sqrt(a) ≤ x_n:\n // ε_{n+1} = ε_n² / | (2 * x_n) |\n // ≤ (2**(e-k))² / (2 * 2**(e-1))\n // ≤ 2**(2*e-2*k) / 2**e\n // ≤ 2**(e-2*k)\n xn = (xn + a / xn) >> 1; // ε_1 := | x_1 - sqrt(a) | ≤ 2**(e-4.5) -- special case, see above\n xn = (xn + a / xn) >> 1; // ε_2 := | x_2 - sqrt(a) | ≤ 2**(e-9) -- general case with k = 4.5\n xn = (xn + a / xn) >> 1; // ε_3 := | x_3 - sqrt(a) | ≤ 2**(e-18) -- general case with k = 9\n xn = (xn + a / xn) >> 1; // ε_4 := | x_4 - sqrt(a) | ≤ 2**(e-36) -- general case with k = 18\n xn = (xn + a / xn) >> 1; // ε_5 := | x_5 - sqrt(a) | ≤ 2**(e-72) -- general case with k = 36\n xn = (xn + a / xn) >> 1; // ε_6 := | x_6 - sqrt(a) | ≤ 2**(e-144) -- general case with k = 72\n\n // Because e ≤ 128 (as discussed during the first estimation phase), we know have reached a precision\n // ε_6 ≤ 2**(e-144) < 1. Given we're operating on integers, then we can ensure that xn is now either\n // sqrt(a) or sqrt(a) + 1.\n return xn - SafeCast.toUint(xn > a / xn);\n }\n }\n\n /**\n * @dev Calculates sqrt(a), following the selected rounding direction.\n */\n function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {\n unchecked {\n uint256 result = sqrt(a);\n return result + SafeCast.toUint(unsignedRoundsUp(rounding) && result * result < a);\n }\n }\n\n /**\n * @dev Return the log in base 2 of a positive value rounded towards zero.\n * Returns 0 if given 0.\n */\n function log2(uint256 x) internal pure returns (uint256 r) {\n // If value has upper 128 bits set, log2 result is at least 128\n r = SafeCast.toUint(x > 0xffffffffffffffffffffffffffffffff) << 7;\n // If upper 64 bits of 128-bit half set, add 64 to result\n r |= SafeCast.toUint((x >> r) > 0xffffffffffffffff) << 6;\n // If upper 32 bits of 64-bit half set, add 32 to result\n r |= SafeCast.toUint((x >> r) > 0xffffffff) << 5;\n // If upper 16 bits of 32-bit half set, add 16 to result\n r |= SafeCast.toUint((x >> r) > 0xffff) << 4;\n // If upper 8 bits of 16-bit half set, add 8 to result\n r |= SafeCast.toUint((x >> r) > 0xff) << 3;\n // If upper 4 bits of 8-bit half set, add 4 to result\n r |= SafeCast.toUint((x >> r) > 0xf) << 2;\n\n // Shifts value right by the current result and use it as an index into this lookup table:\n //\n // | x (4 bits) | index | table[index] = MSB position |\n // |------------|---------|-----------------------------|\n // | 0000 | 0 | table[0] = 0 |\n // | 0001 | 1 | table[1] = 0 |\n // | 0010 | 2 | table[2] = 1 |\n // | 0011 | 3 | table[3] = 1 |\n // | 0100 | 4 | table[4] = 2 |\n // | 0101 | 5 | table[5] = 2 |\n // | 0110 | 6 | table[6] = 2 |\n // | 0111 | 7 | table[7] = 2 |\n // | 1000 | 8 | table[8] = 3 |\n // | 1001 | 9 | table[9] = 3 |\n // | 1010 | 10 | table[10] = 3 |\n // | 1011 | 11 | table[11] = 3 |\n // | 1100 | 12 | table[12] = 3 |\n // | 1101 | 13 | table[13] = 3 |\n // | 1110 | 14 | table[14] = 3 |\n // | 1111 | 15 | table[15] = 3 |\n //\n // The lookup table is represented as a 32-byte value with the MSB positions for 0-15 in the last 16 bytes.\n assembly (\"memory-safe\") {\n r := or(r, byte(shr(r, x), 0x0000010102020202030303030303030300000000000000000000000000000000))\n }\n }\n\n /**\n * @dev Return the log in base 2, following the selected rounding direction, of a positive value.\n * Returns 0 if given 0.\n */\n function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {\n unchecked {\n uint256 result = log2(value);\n return result + SafeCast.toUint(unsignedRoundsUp(rounding) && 1 << result < value);\n }\n }\n\n /**\n * @dev Return the log in base 10 of a positive value rounded towards zero.\n * Returns 0 if given 0.\n */\n function log10(uint256 value) internal pure returns (uint256) {\n uint256 result = 0;\n unchecked {\n if (value >= 10 ** 64) {\n value /= 10 ** 64;\n result += 64;\n }\n if (value >= 10 ** 32) {\n value /= 10 ** 32;\n result += 32;\n }\n if (value >= 10 ** 16) {\n value /= 10 ** 16;\n result += 16;\n }\n if (value >= 10 ** 8) {\n value /= 10 ** 8;\n result += 8;\n }\n if (value >= 10 ** 4) {\n value /= 10 ** 4;\n result += 4;\n }\n if (value >= 10 ** 2) {\n value /= 10 ** 2;\n result += 2;\n }\n if (value >= 10 ** 1) {\n result += 1;\n }\n }\n return result;\n }\n\n /**\n * @dev Return the log in base 10, following the selected rounding direction, of a positive value.\n * Returns 0 if given 0.\n */\n function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {\n unchecked {\n uint256 result = log10(value);\n return result + SafeCast.toUint(unsignedRoundsUp(rounding) && 10 ** result < value);\n }\n }\n\n /**\n * @dev Return the log in base 256 of a positive value rounded towards zero.\n * Returns 0 if given 0.\n *\n * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.\n */\n function log256(uint256 x) internal pure returns (uint256 r) {\n // If value has upper 128 bits set, log2 result is at least 128\n r = SafeCast.toUint(x > 0xffffffffffffffffffffffffffffffff) << 7;\n // If upper 64 bits of 128-bit half set, add 64 to result\n r |= SafeCast.toUint((x >> r) > 0xffffffffffffffff) << 6;\n // If upper 32 bits of 64-bit half set, add 32 to result\n r |= SafeCast.toUint((x >> r) > 0xffffffff) << 5;\n // If upper 16 bits of 32-bit half set, add 16 to result\n r |= SafeCast.toUint((x >> r) > 0xffff) << 4;\n // Add 1 if upper 8 bits of 16-bit half set, and divide accumulated result by 8\n return (r >> 3) | SafeCast.toUint((x >> r) > 0xff);\n }\n\n /**\n * @dev Return the log in base 256, following the selected rounding direction, of a positive value.\n * Returns 0 if given 0.\n */\n function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {\n unchecked {\n uint256 result = log256(value);\n return result + SafeCast.toUint(unsignedRoundsUp(rounding) && 1 << (result << 3) < value);\n }\n }\n\n /**\n * @dev Returns whether a provided rounding mode is considered rounding up for unsigned integers.\n */\n function unsignedRoundsUp(Rounding rounding) internal pure returns (bool) {\n return uint8(rounding) % 2 == 1;\n }\n}" + }, + "lib/rooster/openzeppelin-custom/contracts/utils/math/SafeCast.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.1.0) (utils/math/SafeCast.sol)\n// This file was procedurally generated from scripts/generate/templates/SafeCast.js.\n\npragma solidity ^0.8.20;\n\n/**\n * @dev Wrappers over Solidity's uintXX/intXX/bool casting operators with added overflow\n * checks.\n *\n * Downcasting from uint256/int256 in Solidity does not revert on overflow. This can\n * easily result in undesired exploitation or bugs, since developers usually\n * assume that overflows raise errors. `SafeCast` restores this intuition by\n * reverting the transaction when such an operation overflows.\n *\n * Using this library instead of the unchecked operations eliminates an entire\n * class of bugs, so it's recommended to use it always.\n */\nlibrary SafeCast {\n /**\n * @dev Value doesn't fit in an uint of `bits` size.\n */\n error SafeCastOverflowedUintDowncast(uint8 bits, uint256 value);\n\n /**\n * @dev An int value doesn't fit in an uint of `bits` size.\n */\n error SafeCastOverflowedIntToUint(int256 value);\n\n /**\n * @dev Value doesn't fit in an int of `bits` size.\n */\n error SafeCastOverflowedIntDowncast(uint8 bits, int256 value);\n\n /**\n * @dev An uint value doesn't fit in an int of `bits` size.\n */\n error SafeCastOverflowedUintToInt(uint256 value);\n\n /**\n * @dev Returns the downcasted uint248 from uint256, reverting on\n * overflow (when the input is greater than largest uint248).\n *\n * Counterpart to Solidity's `uint248` operator.\n *\n * Requirements:\n *\n * - input must fit into 248 bits\n */\n function toUint248(uint256 value) internal pure returns (uint248) {\n if (value > type(uint248).max) {\n revert SafeCastOverflowedUintDowncast(248, value);\n }\n return uint248(value);\n }\n\n /**\n * @dev Returns the downcasted uint240 from uint256, reverting on\n * overflow (when the input is greater than largest uint240).\n *\n * Counterpart to Solidity's `uint240` operator.\n *\n * Requirements:\n *\n * - input must fit into 240 bits\n */\n function toUint240(uint256 value) internal pure returns (uint240) {\n if (value > type(uint240).max) {\n revert SafeCastOverflowedUintDowncast(240, value);\n }\n return uint240(value);\n }\n\n /**\n * @dev Returns the downcasted uint232 from uint256, reverting on\n * overflow (when the input is greater than largest uint232).\n *\n * Counterpart to Solidity's `uint232` operator.\n *\n * Requirements:\n *\n * - input must fit into 232 bits\n */\n function toUint232(uint256 value) internal pure returns (uint232) {\n if (value > type(uint232).max) {\n revert SafeCastOverflowedUintDowncast(232, value);\n }\n return uint232(value);\n }\n\n /**\n * @dev Returns the downcasted uint224 from uint256, reverting on\n * overflow (when the input is greater than largest uint224).\n *\n * Counterpart to Solidity's `uint224` operator.\n *\n * Requirements:\n *\n * - input must fit into 224 bits\n */\n function toUint224(uint256 value) internal pure returns (uint224) {\n if (value > type(uint224).max) {\n revert SafeCastOverflowedUintDowncast(224, value);\n }\n return uint224(value);\n }\n\n /**\n * @dev Returns the downcasted uint216 from uint256, reverting on\n * overflow (when the input is greater than largest uint216).\n *\n * Counterpart to Solidity's `uint216` operator.\n *\n * Requirements:\n *\n * - input must fit into 216 bits\n */\n function toUint216(uint256 value) internal pure returns (uint216) {\n if (value > type(uint216).max) {\n revert SafeCastOverflowedUintDowncast(216, value);\n }\n return uint216(value);\n }\n\n /**\n * @dev Returns the downcasted uint208 from uint256, reverting on\n * overflow (when the input is greater than largest uint208).\n *\n * Counterpart to Solidity's `uint208` operator.\n *\n * Requirements:\n *\n * - input must fit into 208 bits\n */\n function toUint208(uint256 value) internal pure returns (uint208) {\n if (value > type(uint208).max) {\n revert SafeCastOverflowedUintDowncast(208, value);\n }\n return uint208(value);\n }\n\n /**\n * @dev Returns the downcasted uint200 from uint256, reverting on\n * overflow (when the input is greater than largest uint200).\n *\n * Counterpart to Solidity's `uint200` operator.\n *\n * Requirements:\n *\n * - input must fit into 200 bits\n */\n function toUint200(uint256 value) internal pure returns (uint200) {\n if (value > type(uint200).max) {\n revert SafeCastOverflowedUintDowncast(200, value);\n }\n return uint200(value);\n }\n\n /**\n * @dev Returns the downcasted uint192 from uint256, reverting on\n * overflow (when the input is greater than largest uint192).\n *\n * Counterpart to Solidity's `uint192` operator.\n *\n * Requirements:\n *\n * - input must fit into 192 bits\n */\n function toUint192(uint256 value) internal pure returns (uint192) {\n if (value > type(uint192).max) {\n revert SafeCastOverflowedUintDowncast(192, value);\n }\n return uint192(value);\n }\n\n /**\n * @dev Returns the downcasted uint184 from uint256, reverting on\n * overflow (when the input is greater than largest uint184).\n *\n * Counterpart to Solidity's `uint184` operator.\n *\n * Requirements:\n *\n * - input must fit into 184 bits\n */\n function toUint184(uint256 value) internal pure returns (uint184) {\n if (value > type(uint184).max) {\n revert SafeCastOverflowedUintDowncast(184, value);\n }\n return uint184(value);\n }\n\n /**\n * @dev Returns the downcasted uint176 from uint256, reverting on\n * overflow (when the input is greater than largest uint176).\n *\n * Counterpart to Solidity's `uint176` operator.\n *\n * Requirements:\n *\n * - input must fit into 176 bits\n */\n function toUint176(uint256 value) internal pure returns (uint176) {\n if (value > type(uint176).max) {\n revert SafeCastOverflowedUintDowncast(176, value);\n }\n return uint176(value);\n }\n\n /**\n * @dev Returns the downcasted uint168 from uint256, reverting on\n * overflow (when the input is greater than largest uint168).\n *\n * Counterpart to Solidity's `uint168` operator.\n *\n * Requirements:\n *\n * - input must fit into 168 bits\n */\n function toUint168(uint256 value) internal pure returns (uint168) {\n if (value > type(uint168).max) {\n revert SafeCastOverflowedUintDowncast(168, value);\n }\n return uint168(value);\n }\n\n /**\n * @dev Returns the downcasted uint160 from uint256, reverting on\n * overflow (when the input is greater than largest uint160).\n *\n * Counterpart to Solidity's `uint160` operator.\n *\n * Requirements:\n *\n * - input must fit into 160 bits\n */\n function toUint160(uint256 value) internal pure returns (uint160) {\n if (value > type(uint160).max) {\n revert SafeCastOverflowedUintDowncast(160, value);\n }\n return uint160(value);\n }\n\n /**\n * @dev Returns the downcasted uint152 from uint256, reverting on\n * overflow (when the input is greater than largest uint152).\n *\n * Counterpart to Solidity's `uint152` operator.\n *\n * Requirements:\n *\n * - input must fit into 152 bits\n */\n function toUint152(uint256 value) internal pure returns (uint152) {\n if (value > type(uint152).max) {\n revert SafeCastOverflowedUintDowncast(152, value);\n }\n return uint152(value);\n }\n\n /**\n * @dev Returns the downcasted uint144 from uint256, reverting on\n * overflow (when the input is greater than largest uint144).\n *\n * Counterpart to Solidity's `uint144` operator.\n *\n * Requirements:\n *\n * - input must fit into 144 bits\n */\n function toUint144(uint256 value) internal pure returns (uint144) {\n if (value > type(uint144).max) {\n revert SafeCastOverflowedUintDowncast(144, value);\n }\n return uint144(value);\n }\n\n /**\n * @dev Returns the downcasted uint136 from uint256, reverting on\n * overflow (when the input is greater than largest uint136).\n *\n * Counterpart to Solidity's `uint136` operator.\n *\n * Requirements:\n *\n * - input must fit into 136 bits\n */\n function toUint136(uint256 value) internal pure returns (uint136) {\n if (value > type(uint136).max) {\n revert SafeCastOverflowedUintDowncast(136, value);\n }\n return uint136(value);\n }\n\n /**\n * @dev Returns the downcasted uint128 from uint256, reverting on\n * overflow (when the input is greater than largest uint128).\n *\n * Counterpart to Solidity's `uint128` operator.\n *\n * Requirements:\n *\n * - input must fit into 128 bits\n */\n function toUint128(uint256 value) internal pure returns (uint128) {\n if (value > type(uint128).max) {\n revert SafeCastOverflowedUintDowncast(128, value);\n }\n return uint128(value);\n }\n\n /**\n * @dev Returns the downcasted uint120 from uint256, reverting on\n * overflow (when the input is greater than largest uint120).\n *\n * Counterpart to Solidity's `uint120` operator.\n *\n * Requirements:\n *\n * - input must fit into 120 bits\n */\n function toUint120(uint256 value) internal pure returns (uint120) {\n if (value > type(uint120).max) {\n revert SafeCastOverflowedUintDowncast(120, value);\n }\n return uint120(value);\n }\n\n /**\n * @dev Returns the downcasted uint112 from uint256, reverting on\n * overflow (when the input is greater than largest uint112).\n *\n * Counterpart to Solidity's `uint112` operator.\n *\n * Requirements:\n *\n * - input must fit into 112 bits\n */\n function toUint112(uint256 value) internal pure returns (uint112) {\n if (value > type(uint112).max) {\n revert SafeCastOverflowedUintDowncast(112, value);\n }\n return uint112(value);\n }\n\n /**\n * @dev Returns the downcasted uint104 from uint256, reverting on\n * overflow (when the input is greater than largest uint104).\n *\n * Counterpart to Solidity's `uint104` operator.\n *\n * Requirements:\n *\n * - input must fit into 104 bits\n */\n function toUint104(uint256 value) internal pure returns (uint104) {\n if (value > type(uint104).max) {\n revert SafeCastOverflowedUintDowncast(104, value);\n }\n return uint104(value);\n }\n\n /**\n * @dev Returns the downcasted uint96 from uint256, reverting on\n * overflow (when the input is greater than largest uint96).\n *\n * Counterpart to Solidity's `uint96` operator.\n *\n * Requirements:\n *\n * - input must fit into 96 bits\n */\n function toUint96(uint256 value) internal pure returns (uint96) {\n if (value > type(uint96).max) {\n revert SafeCastOverflowedUintDowncast(96, value);\n }\n return uint96(value);\n }\n\n /**\n * @dev Returns the downcasted uint88 from uint256, reverting on\n * overflow (when the input is greater than largest uint88).\n *\n * Counterpart to Solidity's `uint88` operator.\n *\n * Requirements:\n *\n * - input must fit into 88 bits\n */\n function toUint88(uint256 value) internal pure returns (uint88) {\n if (value > type(uint88).max) {\n revert SafeCastOverflowedUintDowncast(88, value);\n }\n return uint88(value);\n }\n\n /**\n * @dev Returns the downcasted uint80 from uint256, reverting on\n * overflow (when the input is greater than largest uint80).\n *\n * Counterpart to Solidity's `uint80` operator.\n *\n * Requirements:\n *\n * - input must fit into 80 bits\n */\n function toUint80(uint256 value) internal pure returns (uint80) {\n if (value > type(uint80).max) {\n revert SafeCastOverflowedUintDowncast(80, value);\n }\n return uint80(value);\n }\n\n /**\n * @dev Returns the downcasted uint72 from uint256, reverting on\n * overflow (when the input is greater than largest uint72).\n *\n * Counterpart to Solidity's `uint72` operator.\n *\n * Requirements:\n *\n * - input must fit into 72 bits\n */\n function toUint72(uint256 value) internal pure returns (uint72) {\n if (value > type(uint72).max) {\n revert SafeCastOverflowedUintDowncast(72, value);\n }\n return uint72(value);\n }\n\n /**\n * @dev Returns the downcasted uint64 from uint256, reverting on\n * overflow (when the input is greater than largest uint64).\n *\n * Counterpart to Solidity's `uint64` operator.\n *\n * Requirements:\n *\n * - input must fit into 64 bits\n */\n function toUint64(uint256 value) internal pure returns (uint64) {\n if (value > type(uint64).max) {\n revert SafeCastOverflowedUintDowncast(64, value);\n }\n return uint64(value);\n }\n\n /**\n * @dev Returns the downcasted uint56 from uint256, reverting on\n * overflow (when the input is greater than largest uint56).\n *\n * Counterpart to Solidity's `uint56` operator.\n *\n * Requirements:\n *\n * - input must fit into 56 bits\n */\n function toUint56(uint256 value) internal pure returns (uint56) {\n if (value > type(uint56).max) {\n revert SafeCastOverflowedUintDowncast(56, value);\n }\n return uint56(value);\n }\n\n /**\n * @dev Returns the downcasted uint48 from uint256, reverting on\n * overflow (when the input is greater than largest uint48).\n *\n * Counterpart to Solidity's `uint48` operator.\n *\n * Requirements:\n *\n * - input must fit into 48 bits\n */\n function toUint48(uint256 value) internal pure returns (uint48) {\n if (value > type(uint48).max) {\n revert SafeCastOverflowedUintDowncast(48, value);\n }\n return uint48(value);\n }\n\n /**\n * @dev Returns the downcasted uint40 from uint256, reverting on\n * overflow (when the input is greater than largest uint40).\n *\n * Counterpart to Solidity's `uint40` operator.\n *\n * Requirements:\n *\n * - input must fit into 40 bits\n */\n function toUint40(uint256 value) internal pure returns (uint40) {\n if (value > type(uint40).max) {\n revert SafeCastOverflowedUintDowncast(40, value);\n }\n return uint40(value);\n }\n\n /**\n * @dev Returns the downcasted uint32 from uint256, reverting on\n * overflow (when the input is greater than largest uint32).\n *\n * Counterpart to Solidity's `uint32` operator.\n *\n * Requirements:\n *\n * - input must fit into 32 bits\n */\n function toUint32(uint256 value) internal pure returns (uint32) {\n if (value > type(uint32).max) {\n revert SafeCastOverflowedUintDowncast(32, value);\n }\n return uint32(value);\n }\n\n /**\n * @dev Returns the downcasted uint24 from uint256, reverting on\n * overflow (when the input is greater than largest uint24).\n *\n * Counterpart to Solidity's `uint24` operator.\n *\n * Requirements:\n *\n * - input must fit into 24 bits\n */\n function toUint24(uint256 value) internal pure returns (uint24) {\n if (value > type(uint24).max) {\n revert SafeCastOverflowedUintDowncast(24, value);\n }\n return uint24(value);\n }\n\n /**\n * @dev Returns the downcasted uint16 from uint256, reverting on\n * overflow (when the input is greater than largest uint16).\n *\n * Counterpart to Solidity's `uint16` operator.\n *\n * Requirements:\n *\n * - input must fit into 16 bits\n */\n function toUint16(uint256 value) internal pure returns (uint16) {\n if (value > type(uint16).max) {\n revert SafeCastOverflowedUintDowncast(16, value);\n }\n return uint16(value);\n }\n\n /**\n * @dev Returns the downcasted uint8 from uint256, reverting on\n * overflow (when the input is greater than largest uint8).\n *\n * Counterpart to Solidity's `uint8` operator.\n *\n * Requirements:\n *\n * - input must fit into 8 bits\n */\n function toUint8(uint256 value) internal pure returns (uint8) {\n if (value > type(uint8).max) {\n revert SafeCastOverflowedUintDowncast(8, value);\n }\n return uint8(value);\n }\n\n /**\n * @dev Converts a signed int256 into an unsigned uint256.\n *\n * Requirements:\n *\n * - input must be greater than or equal to 0.\n */\n function toUint256(int256 value) internal pure returns (uint256) {\n if (value < 0) {\n revert SafeCastOverflowedIntToUint(value);\n }\n return uint256(value);\n }\n\n /**\n * @dev Returns the downcasted int248 from int256, reverting on\n * overflow (when the input is less than smallest int248 or\n * greater than largest int248).\n *\n * Counterpart to Solidity's `int248` operator.\n *\n * Requirements:\n *\n * - input must fit into 248 bits\n */\n function toInt248(int256 value) internal pure returns (int248 downcasted) {\n downcasted = int248(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(248, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int240 from int256, reverting on\n * overflow (when the input is less than smallest int240 or\n * greater than largest int240).\n *\n * Counterpart to Solidity's `int240` operator.\n *\n * Requirements:\n *\n * - input must fit into 240 bits\n */\n function toInt240(int256 value) internal pure returns (int240 downcasted) {\n downcasted = int240(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(240, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int232 from int256, reverting on\n * overflow (when the input is less than smallest int232 or\n * greater than largest int232).\n *\n * Counterpart to Solidity's `int232` operator.\n *\n * Requirements:\n *\n * - input must fit into 232 bits\n */\n function toInt232(int256 value) internal pure returns (int232 downcasted) {\n downcasted = int232(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(232, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int224 from int256, reverting on\n * overflow (when the input is less than smallest int224 or\n * greater than largest int224).\n *\n * Counterpart to Solidity's `int224` operator.\n *\n * Requirements:\n *\n * - input must fit into 224 bits\n */\n function toInt224(int256 value) internal pure returns (int224 downcasted) {\n downcasted = int224(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(224, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int216 from int256, reverting on\n * overflow (when the input is less than smallest int216 or\n * greater than largest int216).\n *\n * Counterpart to Solidity's `int216` operator.\n *\n * Requirements:\n *\n * - input must fit into 216 bits\n */\n function toInt216(int256 value) internal pure returns (int216 downcasted) {\n downcasted = int216(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(216, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int208 from int256, reverting on\n * overflow (when the input is less than smallest int208 or\n * greater than largest int208).\n *\n * Counterpart to Solidity's `int208` operator.\n *\n * Requirements:\n *\n * - input must fit into 208 bits\n */\n function toInt208(int256 value) internal pure returns (int208 downcasted) {\n downcasted = int208(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(208, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int200 from int256, reverting on\n * overflow (when the input is less than smallest int200 or\n * greater than largest int200).\n *\n * Counterpart to Solidity's `int200` operator.\n *\n * Requirements:\n *\n * - input must fit into 200 bits\n */\n function toInt200(int256 value) internal pure returns (int200 downcasted) {\n downcasted = int200(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(200, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int192 from int256, reverting on\n * overflow (when the input is less than smallest int192 or\n * greater than largest int192).\n *\n * Counterpart to Solidity's `int192` operator.\n *\n * Requirements:\n *\n * - input must fit into 192 bits\n */\n function toInt192(int256 value) internal pure returns (int192 downcasted) {\n downcasted = int192(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(192, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int184 from int256, reverting on\n * overflow (when the input is less than smallest int184 or\n * greater than largest int184).\n *\n * Counterpart to Solidity's `int184` operator.\n *\n * Requirements:\n *\n * - input must fit into 184 bits\n */\n function toInt184(int256 value) internal pure returns (int184 downcasted) {\n downcasted = int184(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(184, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int176 from int256, reverting on\n * overflow (when the input is less than smallest int176 or\n * greater than largest int176).\n *\n * Counterpart to Solidity's `int176` operator.\n *\n * Requirements:\n *\n * - input must fit into 176 bits\n */\n function toInt176(int256 value) internal pure returns (int176 downcasted) {\n downcasted = int176(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(176, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int168 from int256, reverting on\n * overflow (when the input is less than smallest int168 or\n * greater than largest int168).\n *\n * Counterpart to Solidity's `int168` operator.\n *\n * Requirements:\n *\n * - input must fit into 168 bits\n */\n function toInt168(int256 value) internal pure returns (int168 downcasted) {\n downcasted = int168(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(168, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int160 from int256, reverting on\n * overflow (when the input is less than smallest int160 or\n * greater than largest int160).\n *\n * Counterpart to Solidity's `int160` operator.\n *\n * Requirements:\n *\n * - input must fit into 160 bits\n */\n function toInt160(int256 value) internal pure returns (int160 downcasted) {\n downcasted = int160(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(160, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int152 from int256, reverting on\n * overflow (when the input is less than smallest int152 or\n * greater than largest int152).\n *\n * Counterpart to Solidity's `int152` operator.\n *\n * Requirements:\n *\n * - input must fit into 152 bits\n */\n function toInt152(int256 value) internal pure returns (int152 downcasted) {\n downcasted = int152(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(152, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int144 from int256, reverting on\n * overflow (when the input is less than smallest int144 or\n * greater than largest int144).\n *\n * Counterpart to Solidity's `int144` operator.\n *\n * Requirements:\n *\n * - input must fit into 144 bits\n */\n function toInt144(int256 value) internal pure returns (int144 downcasted) {\n downcasted = int144(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(144, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int136 from int256, reverting on\n * overflow (when the input is less than smallest int136 or\n * greater than largest int136).\n *\n * Counterpart to Solidity's `int136` operator.\n *\n * Requirements:\n *\n * - input must fit into 136 bits\n */\n function toInt136(int256 value) internal pure returns (int136 downcasted) {\n downcasted = int136(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(136, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int128 from int256, reverting on\n * overflow (when the input is less than smallest int128 or\n * greater than largest int128).\n *\n * Counterpart to Solidity's `int128` operator.\n *\n * Requirements:\n *\n * - input must fit into 128 bits\n */\n function toInt128(int256 value) internal pure returns (int128 downcasted) {\n downcasted = int128(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(128, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int120 from int256, reverting on\n * overflow (when the input is less than smallest int120 or\n * greater than largest int120).\n *\n * Counterpart to Solidity's `int120` operator.\n *\n * Requirements:\n *\n * - input must fit into 120 bits\n */\n function toInt120(int256 value) internal pure returns (int120 downcasted) {\n downcasted = int120(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(120, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int112 from int256, reverting on\n * overflow (when the input is less than smallest int112 or\n * greater than largest int112).\n *\n * Counterpart to Solidity's `int112` operator.\n *\n * Requirements:\n *\n * - input must fit into 112 bits\n */\n function toInt112(int256 value) internal pure returns (int112 downcasted) {\n downcasted = int112(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(112, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int104 from int256, reverting on\n * overflow (when the input is less than smallest int104 or\n * greater than largest int104).\n *\n * Counterpart to Solidity's `int104` operator.\n *\n * Requirements:\n *\n * - input must fit into 104 bits\n */\n function toInt104(int256 value) internal pure returns (int104 downcasted) {\n downcasted = int104(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(104, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int96 from int256, reverting on\n * overflow (when the input is less than smallest int96 or\n * greater than largest int96).\n *\n * Counterpart to Solidity's `int96` operator.\n *\n * Requirements:\n *\n * - input must fit into 96 bits\n */\n function toInt96(int256 value) internal pure returns (int96 downcasted) {\n downcasted = int96(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(96, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int88 from int256, reverting on\n * overflow (when the input is less than smallest int88 or\n * greater than largest int88).\n *\n * Counterpart to Solidity's `int88` operator.\n *\n * Requirements:\n *\n * - input must fit into 88 bits\n */\n function toInt88(int256 value) internal pure returns (int88 downcasted) {\n downcasted = int88(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(88, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int80 from int256, reverting on\n * overflow (when the input is less than smallest int80 or\n * greater than largest int80).\n *\n * Counterpart to Solidity's `int80` operator.\n *\n * Requirements:\n *\n * - input must fit into 80 bits\n */\n function toInt80(int256 value) internal pure returns (int80 downcasted) {\n downcasted = int80(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(80, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int72 from int256, reverting on\n * overflow (when the input is less than smallest int72 or\n * greater than largest int72).\n *\n * Counterpart to Solidity's `int72` operator.\n *\n * Requirements:\n *\n * - input must fit into 72 bits\n */\n function toInt72(int256 value) internal pure returns (int72 downcasted) {\n downcasted = int72(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(72, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int64 from int256, reverting on\n * overflow (when the input is less than smallest int64 or\n * greater than largest int64).\n *\n * Counterpart to Solidity's `int64` operator.\n *\n * Requirements:\n *\n * - input must fit into 64 bits\n */\n function toInt64(int256 value) internal pure returns (int64 downcasted) {\n downcasted = int64(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(64, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int56 from int256, reverting on\n * overflow (when the input is less than smallest int56 or\n * greater than largest int56).\n *\n * Counterpart to Solidity's `int56` operator.\n *\n * Requirements:\n *\n * - input must fit into 56 bits\n */\n function toInt56(int256 value) internal pure returns (int56 downcasted) {\n downcasted = int56(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(56, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int48 from int256, reverting on\n * overflow (when the input is less than smallest int48 or\n * greater than largest int48).\n *\n * Counterpart to Solidity's `int48` operator.\n *\n * Requirements:\n *\n * - input must fit into 48 bits\n */\n function toInt48(int256 value) internal pure returns (int48 downcasted) {\n downcasted = int48(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(48, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int40 from int256, reverting on\n * overflow (when the input is less than smallest int40 or\n * greater than largest int40).\n *\n * Counterpart to Solidity's `int40` operator.\n *\n * Requirements:\n *\n * - input must fit into 40 bits\n */\n function toInt40(int256 value) internal pure returns (int40 downcasted) {\n downcasted = int40(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(40, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int32 from int256, reverting on\n * overflow (when the input is less than smallest int32 or\n * greater than largest int32).\n *\n * Counterpart to Solidity's `int32` operator.\n *\n * Requirements:\n *\n * - input must fit into 32 bits\n */\n function toInt32(int256 value) internal pure returns (int32 downcasted) {\n downcasted = int32(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(32, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int24 from int256, reverting on\n * overflow (when the input is less than smallest int24 or\n * greater than largest int24).\n *\n * Counterpart to Solidity's `int24` operator.\n *\n * Requirements:\n *\n * - input must fit into 24 bits\n */\n function toInt24(int256 value) internal pure returns (int24 downcasted) {\n downcasted = int24(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(24, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int16 from int256, reverting on\n * overflow (when the input is less than smallest int16 or\n * greater than largest int16).\n *\n * Counterpart to Solidity's `int16` operator.\n *\n * Requirements:\n *\n * - input must fit into 16 bits\n */\n function toInt16(int256 value) internal pure returns (int16 downcasted) {\n downcasted = int16(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(16, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int8 from int256, reverting on\n * overflow (when the input is less than smallest int8 or\n * greater than largest int8).\n *\n * Counterpart to Solidity's `int8` operator.\n *\n * Requirements:\n *\n * - input must fit into 8 bits\n */\n function toInt8(int256 value) internal pure returns (int8 downcasted) {\n downcasted = int8(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(8, value);\n }\n }\n\n /**\n * @dev Converts an unsigned uint256 into a signed int256.\n *\n * Requirements:\n *\n * - input must be less than or equal to maxInt256.\n */\n function toInt256(uint256 value) internal pure returns (int256) {\n // Note: Unsafe cast below is okay because `type(int256).max` is guaranteed to be positive\n if (value > uint256(type(int256).max)) {\n revert SafeCastOverflowedUintToInt(value);\n }\n return int256(value);\n }\n\n /**\n * @dev Cast a boolean (false or true) to a uint256 (0 or 1) with no jump.\n */\n function toUint(bool b) internal pure returns (uint256 u) {\n assembly (\"memory-safe\") {\n u := iszero(iszero(b))\n }\n }\n}" + }, + "lib/rooster/openzeppelin-custom/contracts/utils/Panic.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.1.0) (utils/Panic.sol)\n\npragma solidity ^0.8.20;\n\n/**\n * @dev Helper library for emitting standardized panic codes.\n *\n * ```solidity\n * contract Example {\n * using Panic for uint256;\n *\n * // Use any of the declared internal constants\n * function foo() { Panic.GENERIC.panic(); }\n *\n * // Alternatively\n * function foo() { Panic.panic(Panic.GENERIC); }\n * }\n * ```\n *\n * Follows the list from https://github.com/ethereum/solidity/blob/v0.8.24/libsolutil/ErrorCodes.h[libsolutil].\n *\n * _Available since v5.1._\n */\n// slither-disable-next-line unused-state\nlibrary Panic {\n /// @dev generic / unspecified error\n uint256 internal constant GENERIC = 0x00;\n /// @dev used by the assert() builtin\n uint256 internal constant ASSERT = 0x01;\n /// @dev arithmetic underflow or overflow\n uint256 internal constant UNDER_OVERFLOW = 0x11;\n /// @dev division or modulo by zero\n uint256 internal constant DIVISION_BY_ZERO = 0x12;\n /// @dev enum conversion error\n uint256 internal constant ENUM_CONVERSION_ERROR = 0x21;\n /// @dev invalid encoding in storage\n uint256 internal constant STORAGE_ENCODING_ERROR = 0x22;\n /// @dev empty array pop\n uint256 internal constant EMPTY_ARRAY_POP = 0x31;\n /// @dev array out of bounds access\n uint256 internal constant ARRAY_OUT_OF_BOUNDS = 0x32;\n /// @dev resource error (too large allocation or too large array)\n uint256 internal constant RESOURCE_ERROR = 0x41;\n /// @dev calling invalid internal function\n uint256 internal constant INVALID_INTERNAL_FUNCTION = 0x51;\n\n /// @dev Reverts with a panic code. Recommended to use with\n /// the internal constants with predefined codes.\n function panic(uint256 code) internal pure {\n assembly (\"memory-safe\") {\n mstore(0x00, 0x4e487b71)\n mstore(0x20, code)\n revert(0x1c, 0x24)\n }\n }\n}" + }, + "lib/rooster/v2-common/libraries/Constants.sol": { + "content": "// SPDX-License-Identifier: GPL-2.0-or-later\n// As the copyright holder of this work, Ubiquity Labs retains\n// the right to distribute, use, and modify this code under any license of\n// their choosing, in addition to the terms of the GPL-v2 or later.\npragma solidity ^0.8.25;\n\n// factory contraints on pools\nuint8 constant MAX_PROTOCOL_FEE_RATIO_D3 = 0.25e3; // 25%\nuint256 constant MAX_PROTOCOL_LENDING_FEE_RATE_D18 = 0.02e18; // 2%\nuint64 constant MAX_POOL_FEE_D18 = 0.9e18; // 90%\nuint64 constant MIN_LOOKBACK = 1 seconds;\n\n// pool constraints\nuint8 constant NUMBER_OF_KINDS = 4;\nint32 constant NUMBER_OF_KINDS_32 = int32(int8(NUMBER_OF_KINDS));\nuint256 constant MAX_TICK = 322_378; // max price 1e14 in D18 scale\nint32 constant MAX_TICK_32 = int32(int256(MAX_TICK));\nint32 constant MIN_TICK_32 = int32(-int256(MAX_TICK));\nuint256 constant MAX_BINS_TO_MERGE = 3;\nuint128 constant MINIMUM_LIQUIDITY = 1e8;\n\n// accessor named constants\nuint8 constant ALL_KINDS_MASK = 0xF; // 0b1111\nuint8 constant PERMISSIONED_LIQUIDITY_MASK = 0x10; // 0b010000\nuint8 constant PERMISSIONED_SWAP_MASK = 0x20; // 0b100000\nuint8 constant OPTIONS_MASK = ALL_KINDS_MASK | PERMISSIONED_LIQUIDITY_MASK | PERMISSIONED_SWAP_MASK; // 0b111111\n\n// named values\naddress constant MERGED_LP_BALANCE_ADDRESS = address(0);\nuint256 constant MERGED_LP_BALANCE_SUBACCOUNT = 0;\nuint128 constant ONE = 1e18;\nuint128 constant ONE_SQUARED = 1e36;\nint256 constant INT256_ONE = 1e18;\nuint256 constant ONE_D8 = 1e8;\nuint256 constant ONE_D3 = 1e3;\nint40 constant INT_ONE_D8 = 1e8;\nint40 constant HALF_TICK_D8 = 0.5e8;\nuint8 constant DEFAULT_DECIMALS = 18;\nuint256 constant DEFAULT_SCALE = 1;\nbytes constant EMPTY_PRICE_BREAKS = hex\"010000000000000000000000\";" + }, + "lib/rooster/v2-common/libraries/Math.sol": { + "content": "// SPDX-License-Identifier: GPL-2.0-or-later\n// As the copyright holder of this work, Ubiquity Labs retains\n// the right to distribute, use, and modify this code under any license of\n// their choosing, in addition to the terms of the GPL-v2 or later.\npragma solidity ^0.8.25;\n\nimport {Math as OzMath} from \"../../openzeppelin-custom/contracts/utils/math/Math.sol\";\n\nimport {ONE, DEFAULT_SCALE, DEFAULT_DECIMALS, INT_ONE_D8, ONE_SQUARED} from \"./Constants.sol\";\n\n/**\n * @notice Math functions.\n */\nlibrary Math {\n /**\n * @notice Returns the lesser of two values.\n * @param x First uint256 value.\n * @param y Second uint256 value.\n */\n function min(uint256 x, uint256 y) internal pure returns (uint256 z) {\n assembly (\"memory-safe\") {\n z := xor(x, mul(xor(x, y), lt(y, x)))\n }\n }\n\n /**\n * @notice Returns the lesser of two uint128 values.\n * @param x First uint128 value.\n * @param y Second uint128 value.\n */\n function min128(uint128 x, uint128 y) internal pure returns (uint128 z) {\n assembly (\"memory-safe\") {\n z := xor(x, mul(xor(x, y), lt(y, x)))\n }\n }\n\n /**\n * @notice Returns the lesser of two int256 values.\n * @param x First int256 value.\n * @param y Second int256 value.\n */\n function min(int256 x, int256 y) internal pure returns (int256 z) {\n assembly (\"memory-safe\") {\n z := xor(x, mul(xor(x, y), slt(y, x)))\n }\n }\n\n /**\n * @notice Returns the greater of two uint256 values.\n * @param x First uint256 value.\n * @param y Second uint256 value.\n */\n function max(uint256 x, uint256 y) internal pure returns (uint256 z) {\n assembly (\"memory-safe\") {\n z := xor(x, mul(xor(x, y), gt(y, x)))\n }\n }\n\n /**\n * @notice Returns the greater of two int256 values.\n * @param x First int256 value.\n * @param y Second int256 value.\n */\n function max(int256 x, int256 y) internal pure returns (int256 z) {\n assembly (\"memory-safe\") {\n z := xor(x, mul(xor(x, y), sgt(y, x)))\n }\n }\n\n /**\n * @notice Returns the greater of two uint128 values.\n * @param x First uint128 value.\n * @param y Second uint128 value.\n */\n function max128(uint128 x, uint128 y) internal pure returns (uint128 z) {\n assembly (\"memory-safe\") {\n z := xor(x, mul(xor(x, y), gt(y, x)))\n }\n }\n\n /**\n * @notice Thresholds a value to be within the specified bounds.\n * @param value The value to bound.\n * @param lowerLimit The minimum allowable value.\n * @param upperLimit The maximum allowable value.\n */\n function boundValue(\n uint256 value,\n uint256 lowerLimit,\n uint256 upperLimit\n ) internal pure returns (uint256 outputValue) {\n outputValue = min(max(value, lowerLimit), upperLimit);\n }\n\n /**\n * @notice Returns the difference between two uint128 values or zero if the result would be negative.\n * @param x The minuend.\n * @param y The subtrahend.\n */\n function clip128(uint128 x, uint128 y) internal pure returns (uint128) {\n unchecked {\n return x < y ? 0 : x - y;\n }\n }\n\n /**\n * @notice Returns the difference between two uint256 values or zero if the result would be negative.\n * @param x The minuend.\n * @param y The subtrahend.\n */\n function clip(uint256 x, uint256 y) internal pure returns (uint256) {\n unchecked {\n return x < y ? 0 : x - y;\n }\n }\n\n /**\n * @notice Divides one uint256 by another, rounding down to the nearest\n * integer.\n * @param x The dividend.\n * @param y The divisor.\n */\n function divFloor(uint256 x, uint256 y) internal pure returns (uint256) {\n return mulDivFloor(x, ONE, y);\n }\n\n /**\n * @notice Divides one uint256 by another, rounding up to the nearest integer.\n * @param x The dividend.\n * @param y The divisor.\n */\n function divCeil(uint256 x, uint256 y) internal pure returns (uint256) {\n return mulDivCeil(x, ONE, y);\n }\n\n /**\n * @notice Multiplies two uint256 values and then divides by ONE, rounding down.\n * @param x The multiplicand.\n * @param y The multiplier.\n */\n function mulFloor(uint256 x, uint256 y) internal pure returns (uint256) {\n return OzMath.mulDiv(x, y, ONE);\n }\n\n /**\n * @notice Multiplies two uint256 values and then divides by ONE, rounding up.\n * @param x The multiplicand.\n * @param y The multiplier.\n */\n function mulCeil(uint256 x, uint256 y) internal pure returns (uint256) {\n return mulDivCeil(x, y, ONE);\n }\n\n /**\n * @notice Calculates the multiplicative inverse of a uint256, rounding down.\n * @param x The value to invert.\n */\n function invFloor(uint256 x) internal pure returns (uint256) {\n unchecked {\n return ONE_SQUARED / x;\n }\n }\n\n /**\n * @notice Calculates the multiplicative inverse of a uint256, rounding up.\n * @param denominator The value to invert.\n */\n function invCeil(uint256 denominator) internal pure returns (uint256 z) {\n assembly (\"memory-safe\") {\n // divide z - 1 by the denominator and add 1.\n z := add(div(sub(ONE_SQUARED, 1), denominator), 1)\n }\n }\n\n /**\n * @notice Multiplies two uint256 values and divides by a third, rounding down.\n * @param x The multiplicand.\n * @param y The multiplier.\n * @param k The divisor.\n */\n function mulDivFloor(uint256 x, uint256 y, uint256 k) internal pure returns (uint256 result) {\n result = OzMath.mulDiv(x, y, max(1, k));\n }\n\n /**\n * @notice Multiplies two uint256 values and divides by a third, rounding up if there's a remainder.\n * @param x The multiplicand.\n * @param y The multiplier.\n * @param k The divisor.\n */\n function mulDivCeil(uint256 x, uint256 y, uint256 k) internal pure returns (uint256 result) {\n result = mulDivFloor(x, y, k);\n if (mulmod(x, y, max(1, k)) != 0) result = result + 1;\n }\n\n /**\n * @notice Multiplies two uint256 values and divides by a third, rounding\n * down. Will revert if `x * y` is larger than `type(uint256).max`.\n * @param x The first operand for multiplication.\n * @param y The second operand for multiplication.\n * @param denominator The divisor after multiplication.\n */\n function mulDivDown(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 z) {\n assembly (\"memory-safe\") {\n // Store x * y in z for now.\n z := mul(x, y)\n if iszero(denominator) {\n denominator := 1\n }\n\n if iszero(or(iszero(x), eq(div(z, x), y))) {\n revert(0, 0)\n }\n\n // Divide z by the denominator.\n z := div(z, denominator)\n }\n }\n\n /**\n * @notice Multiplies two uint256 values and divides by a third, rounding\n * up. Will revert if `x * y` is larger than `type(uint256).max`.\n * @param x The first operand for multiplication.\n * @param y The second operand for multiplication.\n * @param denominator The divisor after multiplication.\n */\n function mulDivUp(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 z) {\n assembly (\"memory-safe\") {\n // Store x * y in z for now.\n z := mul(x, y)\n if iszero(denominator) {\n denominator := 1\n }\n\n if iszero(or(iszero(x), eq(div(z, x), y))) {\n revert(0, 0)\n }\n\n // First, divide z - 1 by the denominator and add 1.\n // We allow z - 1 to underflow if z is 0, because we multiply the\n // end result by 0 if z is zero, ensuring we return 0 if z is zero.\n z := mul(iszero(iszero(z)), add(div(sub(z, 1), denominator), 1))\n }\n }\n\n /**\n * @notice Multiplies a uint256 by another and divides by a constant,\n * rounding down. Will revert if `x * y` is larger than\n * `type(uint256).max`.\n * @param x The multiplicand.\n * @param y The multiplier.\n */\n function mulDown(uint256 x, uint256 y) internal pure returns (uint256) {\n return mulDivDown(x, y, ONE);\n }\n\n /**\n * @notice Divides a uint256 by another, rounding down the result. Will\n * revert if `x * 1e18` is larger than `type(uint256).max`.\n * @param x The dividend.\n * @param y The divisor.\n */\n function divDown(uint256 x, uint256 y) internal pure returns (uint256) {\n return mulDivDown(x, ONE, y);\n }\n\n /**\n * @notice Divides a uint256 by another, rounding up the result. Will\n * revert if `x * 1e18` is larger than `type(uint256).max`.\n * @param x The dividend.\n * @param y The divisor.\n */\n function divUp(uint256 x, uint256 y) internal pure returns (uint256) {\n return mulDivUp(x, ONE, y);\n }\n\n /**\n * @notice Scales a number based on a difference in decimals from a default.\n * @param decimals The new decimal precision.\n */\n function scale(uint8 decimals) internal pure returns (uint256) {\n unchecked {\n if (decimals == DEFAULT_DECIMALS) {\n return DEFAULT_SCALE;\n } else {\n return 10 ** (DEFAULT_DECIMALS - decimals);\n }\n }\n }\n\n /**\n * @notice Adjusts a scaled amount to the token decimal scale.\n * @param amount The scaled amount.\n * @param scaleFactor The scaling factor to adjust by.\n * @param ceil Whether to round up (true) or down (false).\n */\n function ammScaleToTokenScale(uint256 amount, uint256 scaleFactor, bool ceil) internal pure returns (uint256 z) {\n unchecked {\n if (scaleFactor == DEFAULT_SCALE || amount == 0) {\n return amount;\n } else {\n if (!ceil) return amount / scaleFactor;\n assembly (\"memory-safe\") {\n z := add(div(sub(amount, 1), scaleFactor), 1)\n }\n }\n }\n }\n\n /**\n * @notice Adjusts a token amount to the D18 AMM scale.\n * @param amount The amount in token scale.\n * @param scaleFactor The scale factor for adjustment.\n */\n function tokenScaleToAmmScale(uint256 amount, uint256 scaleFactor) internal pure returns (uint256) {\n if (scaleFactor == DEFAULT_SCALE) {\n return amount;\n } else {\n return amount * scaleFactor;\n }\n }\n\n /**\n * @notice Returns the absolute value of a signed 32-bit integer.\n * @param x The integer to take the absolute value of.\n */\n function abs32(int32 x) internal pure returns (uint32) {\n unchecked {\n return uint32(x < 0 ? -x : x);\n }\n }\n\n /**\n * @notice Returns the absolute value of a signed 256-bit integer.\n * @param x The integer to take the absolute value of.\n */\n function abs(int256 x) internal pure returns (uint256) {\n unchecked {\n return uint256(x < 0 ? -x : x);\n }\n }\n\n /**\n * @notice Calculates the integer square root of a uint256 rounded down.\n * @param x The number to take the square root of.\n */\n function sqrt(uint256 x) internal pure returns (uint256 z) {\n // from https://github.com/transmissions11/solmate/blob/e8f96f25d48fe702117ce76c79228ca4f20206cb/src/utils/FixedPointMathLib.sol\n assembly (\"memory-safe\") {\n let y := x\n z := 181\n\n if iszero(lt(y, 0x10000000000000000000000000000000000)) {\n y := shr(128, y)\n z := shl(64, z)\n }\n if iszero(lt(y, 0x1000000000000000000)) {\n y := shr(64, y)\n z := shl(32, z)\n }\n if iszero(lt(y, 0x10000000000)) {\n y := shr(32, y)\n z := shl(16, z)\n }\n if iszero(lt(y, 0x1000000)) {\n y := shr(16, y)\n z := shl(8, z)\n }\n\n z := shr(18, mul(z, add(y, 65536)))\n\n z := shr(1, add(z, div(x, z)))\n z := shr(1, add(z, div(x, z)))\n z := shr(1, add(z, div(x, z)))\n z := shr(1, add(z, div(x, z)))\n z := shr(1, add(z, div(x, z)))\n z := shr(1, add(z, div(x, z)))\n z := shr(1, add(z, div(x, z)))\n\n z := sub(z, lt(div(x, z), z))\n }\n }\n\n /**\n * @notice Computes the floor of a D8-scaled number as an int32, ignoring\n * potential overflow in the cast.\n * @param val The D8-scaled number.\n */\n function floorD8Unchecked(int256 val) internal pure returns (int32) {\n int32 val32;\n bool check;\n unchecked {\n val32 = int32(val / INT_ONE_D8);\n check = (val < 0 && val % INT_ONE_D8 != 0);\n }\n return check ? val32 - 1 : val32;\n }\n}" + }, + "lib/rooster/v2-common/libraries/TickMath.sol": { + "content": "// SPDX-License-Identifier: GPL-2.0-or-later\n// As the copyright holder of this work, Ubiquity Labs retains\n// the right to distribute, use, and modify this code under any license of\n// their choosing, in addition to the terms of the GPL-v2 or later.\npragma solidity ^0.8.25;\n\nimport {Math as OzMath} from \"../../openzeppelin-custom/contracts/utils/math/Math.sol\";\nimport {Math} from \"./Math.sol\";\nimport {MAX_TICK, ONE} from \"./Constants.sol\";\n\n/**\n * @notice Math functions related to tick operations.\n */\n// slither-disable-start divide-before-multiply\nlibrary TickMath {\n using Math for uint256;\n\n error TickMaxExceeded(int256 tick);\n\n /**\n * @notice Compute the lower and upper sqrtPrice of a tick.\n * @param tickSpacing The tick spacing used for calculations.\n * @param _tick The input tick value.\n */\n function tickSqrtPrices(\n uint256 tickSpacing,\n int32 _tick\n ) internal pure returns (uint256 sqrtLowerPrice, uint256 sqrtUpperPrice) {\n unchecked {\n sqrtLowerPrice = tickSqrtPrice(tickSpacing, _tick);\n sqrtUpperPrice = tickSqrtPrice(tickSpacing, _tick + 1);\n }\n }\n\n /**\n * @notice Compute the base tick value from the pool tick and the\n * tickSpacing. Revert if base tick is beyond the max tick boundary.\n * @param tickSpacing The tick spacing used for calculations.\n * @param _tick The input tick value.\n */\n function subTickIndex(uint256 tickSpacing, int32 _tick) internal pure returns (uint32 subTick) {\n subTick = Math.abs32(_tick);\n subTick *= uint32(tickSpacing);\n if (subTick > MAX_TICK) {\n revert TickMaxExceeded(_tick);\n }\n }\n\n /**\n * @notice Calculate the square root price for a given tick and tick spacing.\n * @param tickSpacing The tick spacing used for calculations.\n * @param _tick The input tick value.\n * @return _result The square root price.\n */\n function tickSqrtPrice(uint256 tickSpacing, int32 _tick) internal pure returns (uint256 _result) {\n unchecked {\n uint256 tick = subTickIndex(tickSpacing, _tick);\n\n uint256 ratio = tick & 0x1 != 0 ? 0xfffcb933bd6fad9d3af5f0b9f25db4d6 : 0x100000000000000000000000000000000;\n if (tick & 0x2 != 0) ratio = (ratio * 0xfff97272373d41fd789c8cb37ffcaa1c) >> 128;\n if (tick & 0x4 != 0) ratio = (ratio * 0xfff2e50f5f656ac9229c67059486f389) >> 128;\n if (tick & 0x8 != 0) ratio = (ratio * 0xffe5caca7e10e81259b3cddc7a064941) >> 128;\n if (tick & 0x10 != 0) ratio = (ratio * 0xffcb9843d60f67b19e8887e0bd251eb7) >> 128;\n if (tick & 0x20 != 0) ratio = (ratio * 0xff973b41fa98cd2e57b660be99eb2c4a) >> 128;\n if (tick & 0x40 != 0) ratio = (ratio * 0xff2ea16466c9838804e327cb417cafcb) >> 128;\n if (tick & 0x80 != 0) ratio = (ratio * 0xfe5dee046a99d51e2cc356c2f617dbe0) >> 128;\n if (tick & 0x100 != 0) ratio = (ratio * 0xfcbe86c7900aecf64236ab31f1f9dcb5) >> 128;\n if (tick & 0x200 != 0) ratio = (ratio * 0xf987a7253ac4d9194200696907cf2e37) >> 128;\n if (tick & 0x400 != 0) ratio = (ratio * 0xf3392b0822b88206f8abe8a3b44dd9be) >> 128;\n if (tick & 0x800 != 0) ratio = (ratio * 0xe7159475a2c578ef4f1d17b2b235d480) >> 128;\n if (tick & 0x1000 != 0) ratio = (ratio * 0xd097f3bdfd254ee83bdd3f248e7e785e) >> 128;\n if (tick & 0x2000 != 0) ratio = (ratio * 0xa9f746462d8f7dd10e744d913d033333) >> 128;\n if (tick & 0x4000 != 0) ratio = (ratio * 0x70d869a156ddd32a39e257bc3f50aa9b) >> 128;\n if (tick & 0x8000 != 0) ratio = (ratio * 0x31be135f97da6e09a19dc367e3b6da40) >> 128;\n if (tick & 0x10000 != 0) ratio = (ratio * 0x9aa508b5b7e5a9780b0cc4e25d61a56) >> 128;\n if (tick & 0x20000 != 0) ratio = (ratio * 0x5d6af8dedbcb3a6ccb7ce618d14225) >> 128;\n if (tick & 0x40000 != 0) ratio = (ratio * 0x2216e584f630389b2052b8db590e) >> 128;\n if (_tick > 0) ratio = type(uint256).max / ratio;\n _result = (ratio * ONE) >> 128;\n }\n }\n\n /**\n * @notice Calculate liquidity of a tick.\n * @param reserveA Tick reserve of token A.\n * @param reserveB Tick reserve of token B.\n * @param sqrtLowerTickPrice The square root price of the lower tick edge.\n * @param sqrtUpperTickPrice The square root price of the upper tick edge.\n */\n function getTickL(\n uint256 reserveA,\n uint256 reserveB,\n uint256 sqrtLowerTickPrice,\n uint256 sqrtUpperTickPrice\n ) internal pure returns (uint256 liquidity) {\n // known:\n // - sqrt price values are different\n // - reserveA and reserveB fit in 128 bit\n // - sqrt price is in (1e-7, 1e7)\n // - D18 max for uint256 is 1.15e59\n // - D18 min is 1e-18\n\n unchecked {\n // diff is in (5e-12, 4e6); max tick spacing is 10_000\n uint256 diff = sqrtUpperTickPrice - sqrtLowerTickPrice;\n\n // Need to maximize precision by shifting small values A and B up so\n // that they use more of the available bit range. Two constraints to\n // consider: we need A * B * diff / sqrtPrice to be bigger than 1e-18\n // when the bump is not in play. This constrains the threshold for\n // bumping to be at least 77 bit; ie, either a or b needs 2^77 which\n // means that term A * B * diff / sqrtPrice > 1e-18.\n //\n // At the other end, the second constraint is that b^2 needs to fit in\n // a 256-bit number, so, post bump, the max reserve value needs to be\n // less than 6e22. With a 78-bit threshold and a 57-bit bump, we have A\n // and B are in (1.4e-1, 4.4e22 (2^(78+57))) with bump, and one of A or\n // B is at least 2^78 without the bump, but the other reserve value may\n // be as small as 1 wei.\n uint256 precisionBump = 0;\n if ((reserveA >> 78) == 0 && (reserveB >> 78) == 0) {\n precisionBump = 57;\n reserveA <<= precisionBump;\n reserveB <<= precisionBump;\n }\n\n if (reserveB == 0) return Math.divDown(reserveA, diff) >> precisionBump;\n if (reserveA == 0)\n return Math.mulDivDown(reserveB.mulDown(sqrtLowerTickPrice), sqrtUpperTickPrice, diff) >> precisionBump;\n\n // b is in (7.2e-9 (2^57 / 1e7 / 2), 2.8e29 (2^(78+57) * 1e7 / 2)) with bump\n // b is in a subset of the same range without bump\n uint256 b = (reserveA.divDown(sqrtUpperTickPrice) + reserveB.mulDown(sqrtLowerTickPrice)) >> 1;\n\n // b^2 is in (5.1e-17, 4.8e58); and will not overflow on either end;\n // A*B is in (3e-13 (2^78 / 1e18 * 1e-18), 1.9e45) without bump and is in a subset range with bump\n // A*B*diff/sqrtUpper is in (1.5e-17 (3e-13 * 5e-12 * 1e7), 7.6e58);\n\n // Since b^2 is at the upper edge of the precision range, we are not\n // able to multiply the argument of the sqrt by 1e18, instead, we move\n // this factor outside of the sqrt. The resulting loss of precision\n // means that this liquidity value is a lower bound on the tick\n // liquidity\n return\n OzMath.mulDiv(\n b +\n Math.sqrt(\n (OzMath.mulDiv(b, b, ONE) +\n OzMath.mulDiv(reserveB.mulFloor(reserveA), diff, sqrtUpperTickPrice))\n ) *\n 1e9,\n sqrtUpperTickPrice,\n diff\n ) >> precisionBump;\n }\n }\n\n /**\n * @notice Calculate square root price of a tick. Returns left edge of the\n * tick if the tick has no reserves.\n * @param reserveA Tick reserve of token A.\n * @param reserveB Tick reserve of token B.\n * @param sqrtLowerTickPrice The square root price of the lower tick edge.\n * @param sqrtUpperTickPrice The square root price of the upper tick edge.\n * @return sqrtPrice The calculated square root price.\n */\n function getSqrtPrice(\n uint256 reserveA,\n uint256 reserveB,\n uint256 sqrtLowerTickPrice,\n uint256 sqrtUpperTickPrice,\n uint256 liquidity\n ) internal pure returns (uint256 sqrtPrice) {\n unchecked {\n if (reserveA == 0) {\n return sqrtLowerTickPrice;\n }\n if (reserveB == 0) {\n return sqrtUpperTickPrice;\n }\n sqrtPrice = Math.sqrt(\n ONE *\n (reserveA + liquidity.mulDown(sqrtLowerTickPrice)).divDown(\n reserveB + liquidity.divDown(sqrtUpperTickPrice)\n )\n );\n sqrtPrice = Math.boundValue(sqrtPrice, sqrtLowerTickPrice, sqrtUpperTickPrice);\n }\n }\n\n /**\n * @notice Calculate square root price of a tick. Returns left edge of the\n * tick if the tick has no reserves.\n * @param reserveA Tick reserve of token A.\n * @param reserveB Tick reserve of token B.\n * @param sqrtLowerTickPrice The square root price of the lower tick edge.\n * @param sqrtUpperTickPrice The square root price of the upper tick edge.\n * @return sqrtPrice The calculated square root price.\n * @return liquidity The calculated liquidity.\n */\n function getTickSqrtPriceAndL(\n uint256 reserveA,\n uint256 reserveB,\n uint256 sqrtLowerTickPrice,\n uint256 sqrtUpperTickPrice\n ) internal pure returns (uint256 sqrtPrice, uint256 liquidity) {\n liquidity = getTickL(reserveA, reserveB, sqrtLowerTickPrice, sqrtUpperTickPrice);\n sqrtPrice = getSqrtPrice(reserveA, reserveB, sqrtLowerTickPrice, sqrtUpperTickPrice, liquidity);\n }\n}\n// slither-disable-end divide-before-multiply" + }, + "solidity-bytes-utils/contracts/BytesLib.sol": { + "content": "// SPDX-License-Identifier: Unlicense\n/*\n * @title Solidity Bytes Arrays Utils\n * @author Gonçalo Sá \n *\n * @dev Bytes tightly packed arrays utility library for ethereum contracts written in Solidity.\n * The library lets you concatenate, slice and type cast bytes arrays both in memory and storage.\n */\npragma solidity >=0.8.0 <0.9.0;\n\n\nlibrary BytesLib {\n function concat(\n bytes memory _preBytes,\n bytes memory _postBytes\n )\n internal\n pure\n returns (bytes memory)\n {\n bytes memory tempBytes;\n\n assembly {\n // Get a location of some free memory and store it in tempBytes as\n // Solidity does for memory variables.\n tempBytes := mload(0x40)\n\n // Store the length of the first bytes array at the beginning of\n // the memory for tempBytes.\n let length := mload(_preBytes)\n mstore(tempBytes, length)\n\n // Maintain a memory counter for the current write location in the\n // temp bytes array by adding the 32 bytes for the array length to\n // the starting location.\n let mc := add(tempBytes, 0x20)\n // Stop copying when the memory counter reaches the length of the\n // first bytes array.\n let end := add(mc, length)\n\n for {\n // Initialize a copy counter to the start of the _preBytes data,\n // 32 bytes into its memory.\n let cc := add(_preBytes, 0x20)\n } lt(mc, end) {\n // Increase both counters by 32 bytes each iteration.\n mc := add(mc, 0x20)\n cc := add(cc, 0x20)\n } {\n // Write the _preBytes data into the tempBytes memory 32 bytes\n // at a time.\n mstore(mc, mload(cc))\n }\n\n // Add the length of _postBytes to the current length of tempBytes\n // and store it as the new length in the first 32 bytes of the\n // tempBytes memory.\n length := mload(_postBytes)\n mstore(tempBytes, add(length, mload(tempBytes)))\n\n // Move the memory counter back from a multiple of 0x20 to the\n // actual end of the _preBytes data.\n mc := end\n // Stop copying when the memory counter reaches the new combined\n // length of the arrays.\n end := add(mc, length)\n\n for {\n let cc := add(_postBytes, 0x20)\n } lt(mc, end) {\n mc := add(mc, 0x20)\n cc := add(cc, 0x20)\n } {\n mstore(mc, mload(cc))\n }\n\n // Update the free-memory pointer by padding our last write location\n // to 32 bytes: add 31 bytes to the end of tempBytes to move to the\n // next 32 byte block, then round down to the nearest multiple of\n // 32. If the sum of the length of the two arrays is zero then add\n // one before rounding down to leave a blank 32 bytes (the length block with 0).\n mstore(0x40, and(\n add(add(end, iszero(add(length, mload(_preBytes)))), 31),\n not(31) // Round down to the nearest 32 bytes.\n ))\n }\n\n return tempBytes;\n }\n\n function concatStorage(bytes storage _preBytes, bytes memory _postBytes) internal {\n assembly {\n // Read the first 32 bytes of _preBytes storage, which is the length\n // of the array. (We don't need to use the offset into the slot\n // because arrays use the entire slot.)\n let fslot := sload(_preBytes.slot)\n // Arrays of 31 bytes or less have an even value in their slot,\n // while longer arrays have an odd value. The actual length is\n // the slot divided by two for odd values, and the lowest order\n // byte divided by two for even values.\n // If the slot is even, bitwise and the slot with 255 and divide by\n // two to get the length. If the slot is odd, bitwise and the slot\n // with -1 and divide by two.\n let slength := div(and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)), 2)\n let mlength := mload(_postBytes)\n let newlength := add(slength, mlength)\n // slength can contain both the length and contents of the array\n // if length < 32 bytes so let's prepare for that\n // v. http://solidity.readthedocs.io/en/latest/miscellaneous.html#layout-of-state-variables-in-storage\n switch add(lt(slength, 32), lt(newlength, 32))\n case 2 {\n // Since the new array still fits in the slot, we just need to\n // update the contents of the slot.\n // uint256(bytes_storage) = uint256(bytes_storage) + uint256(bytes_memory) + new_length\n sstore(\n _preBytes.slot,\n // all the modifications to the slot are inside this\n // next block\n add(\n // we can just add to the slot contents because the\n // bytes we want to change are the LSBs\n fslot,\n add(\n mul(\n div(\n // load the bytes from memory\n mload(add(_postBytes, 0x20)),\n // zero all bytes to the right\n exp(0x100, sub(32, mlength))\n ),\n // and now shift left the number of bytes to\n // leave space for the length in the slot\n exp(0x100, sub(32, newlength))\n ),\n // increase length by the double of the memory\n // bytes length\n mul(mlength, 2)\n )\n )\n )\n }\n case 1 {\n // The stored value fits in the slot, but the combined value\n // will exceed it.\n // get the keccak hash to get the contents of the array\n mstore(0x0, _preBytes.slot)\n let sc := add(keccak256(0x0, 0x20), div(slength, 32))\n\n // save new length\n sstore(_preBytes.slot, add(mul(newlength, 2), 1))\n\n // The contents of the _postBytes array start 32 bytes into\n // the structure. Our first read should obtain the `submod`\n // bytes that can fit into the unused space in the last word\n // of the stored array. To get this, we read 32 bytes starting\n // from `submod`, so the data we read overlaps with the array\n // contents by `submod` bytes. Masking the lowest-order\n // `submod` bytes allows us to add that value directly to the\n // stored value.\n\n let submod := sub(32, slength)\n let mc := add(_postBytes, submod)\n let end := add(_postBytes, mlength)\n let mask := sub(exp(0x100, submod), 1)\n\n sstore(\n sc,\n add(\n and(\n fslot,\n 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00\n ),\n and(mload(mc), mask)\n )\n )\n\n for {\n mc := add(mc, 0x20)\n sc := add(sc, 1)\n } lt(mc, end) {\n sc := add(sc, 1)\n mc := add(mc, 0x20)\n } {\n sstore(sc, mload(mc))\n }\n\n mask := exp(0x100, sub(mc, end))\n\n sstore(sc, mul(div(mload(mc), mask), mask))\n }\n default {\n // get the keccak hash to get the contents of the array\n mstore(0x0, _preBytes.slot)\n // Start copying to the last used word of the stored array.\n let sc := add(keccak256(0x0, 0x20), div(slength, 32))\n\n // save new length\n sstore(_preBytes.slot, add(mul(newlength, 2), 1))\n\n // Copy over the first `submod` bytes of the new data as in\n // case 1 above.\n let slengthmod := mod(slength, 32)\n let mlengthmod := mod(mlength, 32)\n let submod := sub(32, slengthmod)\n let mc := add(_postBytes, submod)\n let end := add(_postBytes, mlength)\n let mask := sub(exp(0x100, submod), 1)\n\n sstore(sc, add(sload(sc), and(mload(mc), mask)))\n\n for {\n sc := add(sc, 1)\n mc := add(mc, 0x20)\n } lt(mc, end) {\n sc := add(sc, 1)\n mc := add(mc, 0x20)\n } {\n sstore(sc, mload(mc))\n }\n\n mask := exp(0x100, sub(mc, end))\n\n sstore(sc, mul(div(mload(mc), mask), mask))\n }\n }\n }\n\n function slice(\n bytes memory _bytes,\n uint256 _start,\n uint256 _length\n )\n internal\n pure\n returns (bytes memory)\n {\n // We're using the unchecked block below because otherwise execution ends \n // with the native overflow error code.\n unchecked {\n require(_length + 31 >= _length, \"slice_overflow\");\n }\n require(_bytes.length >= _start + _length, \"slice_outOfBounds\");\n\n bytes memory tempBytes;\n\n assembly {\n switch iszero(_length)\n case 0 {\n // Get a location of some free memory and store it in tempBytes as\n // Solidity does for memory variables.\n tempBytes := mload(0x40)\n\n // The first word of the slice result is potentially a partial\n // word read from the original array. To read it, we calculate\n // the length of that partial word and start copying that many\n // bytes into the array. The first word we copy will start with\n // data we don't care about, but the last `lengthmod` bytes will\n // land at the beginning of the contents of the new array. When\n // we're done copying, we overwrite the full first word with\n // the actual length of the slice.\n let lengthmod := and(_length, 31)\n\n // The multiplication in the next line is necessary\n // because when slicing multiples of 32 bytes (lengthmod == 0)\n // the following copy loop was copying the origin's length\n // and then ending prematurely not copying everything it should.\n let mc := add(add(tempBytes, lengthmod), mul(0x20, iszero(lengthmod)))\n let end := add(mc, _length)\n\n for {\n // The multiplication in the next line has the same exact purpose\n // as the one above.\n let cc := add(add(add(_bytes, lengthmod), mul(0x20, iszero(lengthmod))), _start)\n } lt(mc, end) {\n mc := add(mc, 0x20)\n cc := add(cc, 0x20)\n } {\n mstore(mc, mload(cc))\n }\n\n mstore(tempBytes, _length)\n\n //update free-memory pointer\n //allocating the array padded to 32 bytes like the compiler does now\n mstore(0x40, and(add(mc, 31), not(31)))\n }\n //if we want a zero-length slice let's just return a zero-length array\n default {\n tempBytes := mload(0x40)\n //zero out the 32 bytes slice we are about to return\n //we need to do it because Solidity does not garbage collect\n mstore(tempBytes, 0)\n\n mstore(0x40, add(tempBytes, 0x20))\n }\n }\n\n return tempBytes;\n }\n\n function toAddress(bytes memory _bytes, uint256 _start) internal pure returns (address) {\n require(_bytes.length >= _start + 20, \"toAddress_outOfBounds\");\n address tempAddress;\n\n assembly {\n tempAddress := div(mload(add(add(_bytes, 0x20), _start)), 0x1000000000000000000000000)\n }\n\n return tempAddress;\n }\n\n function toUint8(bytes memory _bytes, uint256 _start) internal pure returns (uint8) {\n require(_bytes.length >= _start + 1 , \"toUint8_outOfBounds\");\n uint8 tempUint;\n\n assembly {\n tempUint := mload(add(add(_bytes, 0x1), _start))\n }\n\n return tempUint;\n }\n\n function toUint16(bytes memory _bytes, uint256 _start) internal pure returns (uint16) {\n require(_bytes.length >= _start + 2, \"toUint16_outOfBounds\");\n uint16 tempUint;\n\n assembly {\n tempUint := mload(add(add(_bytes, 0x2), _start))\n }\n\n return tempUint;\n }\n\n function toUint32(bytes memory _bytes, uint256 _start) internal pure returns (uint32) {\n require(_bytes.length >= _start + 4, \"toUint32_outOfBounds\");\n uint32 tempUint;\n\n assembly {\n tempUint := mload(add(add(_bytes, 0x4), _start))\n }\n\n return tempUint;\n }\n\n function toUint64(bytes memory _bytes, uint256 _start) internal pure returns (uint64) {\n require(_bytes.length >= _start + 8, \"toUint64_outOfBounds\");\n uint64 tempUint;\n\n assembly {\n tempUint := mload(add(add(_bytes, 0x8), _start))\n }\n\n return tempUint;\n }\n\n function toUint96(bytes memory _bytes, uint256 _start) internal pure returns (uint96) {\n require(_bytes.length >= _start + 12, \"toUint96_outOfBounds\");\n uint96 tempUint;\n\n assembly {\n tempUint := mload(add(add(_bytes, 0xc), _start))\n }\n\n return tempUint;\n }\n\n function toUint128(bytes memory _bytes, uint256 _start) internal pure returns (uint128) {\n require(_bytes.length >= _start + 16, \"toUint128_outOfBounds\");\n uint128 tempUint;\n\n assembly {\n tempUint := mload(add(add(_bytes, 0x10), _start))\n }\n\n return tempUint;\n }\n\n function toUint256(bytes memory _bytes, uint256 _start) internal pure returns (uint256) {\n require(_bytes.length >= _start + 32, \"toUint256_outOfBounds\");\n uint256 tempUint;\n\n assembly {\n tempUint := mload(add(add(_bytes, 0x20), _start))\n }\n\n return tempUint;\n }\n\n function toBytes32(bytes memory _bytes, uint256 _start) internal pure returns (bytes32) {\n require(_bytes.length >= _start + 32, \"toBytes32_outOfBounds\");\n bytes32 tempBytes32;\n\n assembly {\n tempBytes32 := mload(add(add(_bytes, 0x20), _start))\n }\n\n return tempBytes32;\n }\n\n function equal(bytes memory _preBytes, bytes memory _postBytes) internal pure returns (bool) {\n bool success = true;\n\n assembly {\n let length := mload(_preBytes)\n\n // if lengths don't match the arrays are not equal\n switch eq(length, mload(_postBytes))\n case 1 {\n // cb is a circuit breaker in the for loop since there's\n // no said feature for inline assembly loops\n // cb = 1 - don't breaker\n // cb = 0 - break\n let cb := 1\n\n let mc := add(_preBytes, 0x20)\n let end := add(mc, length)\n\n for {\n let cc := add(_postBytes, 0x20)\n // the next line is the loop condition:\n // while(uint256(mc < end) + cb == 2)\n } eq(add(lt(mc, end), cb), 2) {\n mc := add(mc, 0x20)\n cc := add(cc, 0x20)\n } {\n // if any of these checks fails then arrays are not equal\n if iszero(eq(mload(mc), mload(cc))) {\n // unsuccess:\n success := 0\n cb := 0\n }\n }\n }\n default {\n // unsuccess:\n success := 0\n }\n }\n\n return success;\n }\n\n function equalStorage(\n bytes storage _preBytes,\n bytes memory _postBytes\n )\n internal\n view\n returns (bool)\n {\n bool success = true;\n\n assembly {\n // we know _preBytes_offset is 0\n let fslot := sload(_preBytes.slot)\n // Decode the length of the stored array like in concatStorage().\n let slength := div(and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)), 2)\n let mlength := mload(_postBytes)\n\n // if lengths don't match the arrays are not equal\n switch eq(slength, mlength)\n case 1 {\n // slength can contain both the length and contents of the array\n // if length < 32 bytes so let's prepare for that\n // v. http://solidity.readthedocs.io/en/latest/miscellaneous.html#layout-of-state-variables-in-storage\n if iszero(iszero(slength)) {\n switch lt(slength, 32)\n case 1 {\n // blank the last byte which is the length\n fslot := mul(div(fslot, 0x100), 0x100)\n\n if iszero(eq(fslot, mload(add(_postBytes, 0x20)))) {\n // unsuccess:\n success := 0\n }\n }\n default {\n // cb is a circuit breaker in the for loop since there's\n // no said feature for inline assembly loops\n // cb = 1 - don't breaker\n // cb = 0 - break\n let cb := 1\n\n // get the keccak hash to get the contents of the array\n mstore(0x0, _preBytes.slot)\n let sc := keccak256(0x0, 0x20)\n\n let mc := add(_postBytes, 0x20)\n let end := add(mc, mlength)\n\n // the next line is the loop condition:\n // while(uint256(mc < end) + cb == 2)\n for {} eq(add(lt(mc, end), cb), 2) {\n sc := add(sc, 1)\n mc := add(mc, 0x20)\n } {\n if iszero(eq(sload(sc), mload(mc))) {\n // unsuccess:\n success := 0\n cb := 0\n }\n }\n }\n }\n }\n default {\n // unsuccess:\n success := 0\n }\n }\n\n return success;\n }\n}\n" + } + }, + "settings": { + "optimizer": { + "enabled": true, + "runs": 200 + }, + "evmVersion": "paris", + "outputSelection": { + "*": { + "*": [ + "abi", + "evm.bytecode", + "evm.deployedBytecode", + "evm.methodIdentifiers", + "metadata", + "devdoc", + "userdoc", + "storageLayout", + "evm.gasEstimates" + ], + "": [ + "ast" + ] + } + }, + "metadata": { + "useLiteralContent": true + } + } +} \ No newline at end of file diff --git a/contracts/storageLayout/base/CrossChainRemoteStrategy.json b/contracts/storageLayout/base/CrossChainRemoteStrategy.json new file mode 100644 index 0000000000..7bda8955cf --- /dev/null +++ b/contracts/storageLayout/base/CrossChainRemoteStrategy.json @@ -0,0 +1,240 @@ +{ + "solcVersion": "0.8.28", + "storage": [ + { + "label": "initialized", + "offset": 0, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "contracts/utils/Initializable.sol:12" + }, + { + "label": "initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "contracts/utils/Initializable.sol:17" + }, + { + "label": "______gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "Initializable", + "src": "contracts/utils/Initializable.sol:41" + }, + { + "label": "minFinalityThreshold", + "offset": 0, + "slot": "51", + "type": "t_uint16", + "contract": "AbstractCCTPIntegrator", + "src": "contracts/strategies/crosschain/AbstractCCTPIntegrator.sol:105" + }, + { + "label": "feePremiumBps", + "offset": 2, + "slot": "51", + "type": "t_uint16", + "contract": "AbstractCCTPIntegrator", + "src": "contracts/strategies/crosschain/AbstractCCTPIntegrator.sol:108" + }, + { + "label": "lastTransferNonce", + "offset": 4, + "slot": "51", + "type": "t_uint64", + "contract": "AbstractCCTPIntegrator", + "src": "contracts/strategies/crosschain/AbstractCCTPIntegrator.sol:111" + }, + { + "label": "operator", + "offset": 12, + "slot": "51", + "type": "t_address", + "contract": "AbstractCCTPIntegrator", + "src": "contracts/strategies/crosschain/AbstractCCTPIntegrator.sol:114" + }, + { + "label": "nonceProcessed", + "offset": 0, + "slot": "52", + "type": "t_mapping(t_uint64,t_bool)", + "contract": "AbstractCCTPIntegrator", + "src": "contracts/strategies/crosschain/AbstractCCTPIntegrator.sol:117" + }, + { + "label": "__gap", + "offset": 0, + "slot": "53", + "type": "t_array(t_uint256)48_storage", + "contract": "AbstractCCTPIntegrator", + "src": "contracts/strategies/crosschain/AbstractCCTPIntegrator.sol:120" + }, + { + "label": "_deprecated_platformAddress", + "offset": 0, + "slot": "101", + "type": "t_address", + "contract": "InitializableAbstractStrategy", + "src": "contracts/utils/InitializableAbstractStrategy.sol:43" + }, + { + "label": "_deprecated_vaultAddress", + "offset": 0, + "slot": "102", + "type": "t_address", + "contract": "InitializableAbstractStrategy", + "src": "contracts/utils/InitializableAbstractStrategy.sol:47" + }, + { + "label": "assetToPToken", + "offset": 0, + "slot": "103", + "type": "t_mapping(t_address,t_address)", + "contract": "InitializableAbstractStrategy", + "src": "contracts/utils/InitializableAbstractStrategy.sol:50" + }, + { + "label": "assetsMapped", + "offset": 0, + "slot": "104", + "type": "t_array(t_address)dyn_storage", + "contract": "InitializableAbstractStrategy", + "src": "contracts/utils/InitializableAbstractStrategy.sol:53" + }, + { + "label": "_deprecated_rewardTokenAddress", + "offset": 0, + "slot": "105", + "type": "t_address", + "contract": "InitializableAbstractStrategy", + "src": "contracts/utils/InitializableAbstractStrategy.sol:57" + }, + { + "label": "_deprecated_rewardLiquidationThreshold", + "offset": 0, + "slot": "106", + "type": "t_uint256", + "contract": "InitializableAbstractStrategy", + "src": "contracts/utils/InitializableAbstractStrategy.sol:61" + }, + { + "label": "harvesterAddress", + "offset": 0, + "slot": "107", + "type": "t_address", + "contract": "InitializableAbstractStrategy", + "src": "contracts/utils/InitializableAbstractStrategy.sol:64" + }, + { + "label": "rewardTokenAddresses", + "offset": 0, + "slot": "108", + "type": "t_array(t_address)dyn_storage", + "contract": "InitializableAbstractStrategy", + "src": "contracts/utils/InitializableAbstractStrategy.sol:67" + }, + { + "label": "_reserved", + "offset": 0, + "slot": "109", + "type": "t_array(t_int256)98_storage", + "contract": "InitializableAbstractStrategy", + "src": "contracts/utils/InitializableAbstractStrategy.sol:74" + }, + { + "label": "_deprecate_shareToken", + "offset": 0, + "slot": "207", + "type": "t_address", + "contract": "Generalized4626Strategy", + "src": "contracts/strategies/Generalized4626Strategy.sol:20" + }, + { + "label": "_deprecate_assetToken", + "offset": 0, + "slot": "208", + "type": "t_address", + "contract": "Generalized4626Strategy", + "src": "contracts/strategies/Generalized4626Strategy.sol:23" + }, + { + "label": "__gap", + "offset": 0, + "slot": "209", + "type": "t_array(t_uint256)50_storage", + "contract": "Generalized4626Strategy", + "src": "contracts/strategies/Generalized4626Strategy.sol:29" + }, + { + "label": "strategistAddr", + "offset": 0, + "slot": "259", + "type": "t_address", + "contract": "Strategizable", + "src": "contracts/governance/Strategizable.sol:10" + }, + { + "label": "__gap", + "offset": 0, + "slot": "260", + "type": "t_array(t_uint256)50_storage", + "contract": "Strategizable", + "src": "contracts/governance/Strategizable.sol:13" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_address)dyn_storage": { + "label": "address[]", + "numberOfBytes": "32" + }, + "t_array(t_int256)98_storage": { + "label": "int256[98]", + "numberOfBytes": "3136" + }, + "t_array(t_uint256)48_storage": { + "label": "uint256[48]", + "numberOfBytes": "1536" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_int256": { + "label": "int256", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_address)": { + "label": "mapping(address => address)", + "numberOfBytes": "32" + }, + "t_mapping(t_uint64,t_bool)": { + "label": "mapping(uint64 => bool)", + "numberOfBytes": "32" + }, + "t_uint16": { + "label": "uint16", + "numberOfBytes": "2" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint64": { + "label": "uint64", + "numberOfBytes": "8" + } + }, + "namespaces": {} +} \ No newline at end of file diff --git a/contracts/storageLayout/mainnet/CrossChainMasterStrategy.json b/contracts/storageLayout/mainnet/CrossChainMasterStrategy.json new file mode 100644 index 0000000000..2ebb479885 --- /dev/null +++ b/contracts/storageLayout/mainnet/CrossChainMasterStrategy.json @@ -0,0 +1,216 @@ +{ + "solcVersion": "0.8.28", + "storage": [ + { + "label": "initialized", + "offset": 0, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "contracts/utils/Initializable.sol:12" + }, + { + "label": "initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "contracts/utils/Initializable.sol:17" + }, + { + "label": "______gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "Initializable", + "src": "contracts/utils/Initializable.sol:41" + }, + { + "label": "minFinalityThreshold", + "offset": 0, + "slot": "51", + "type": "t_uint16", + "contract": "AbstractCCTPIntegrator", + "src": "contracts/strategies/crosschain/AbstractCCTPIntegrator.sol:105" + }, + { + "label": "feePremiumBps", + "offset": 2, + "slot": "51", + "type": "t_uint16", + "contract": "AbstractCCTPIntegrator", + "src": "contracts/strategies/crosschain/AbstractCCTPIntegrator.sol:108" + }, + { + "label": "lastTransferNonce", + "offset": 4, + "slot": "51", + "type": "t_uint64", + "contract": "AbstractCCTPIntegrator", + "src": "contracts/strategies/crosschain/AbstractCCTPIntegrator.sol:111" + }, + { + "label": "operator", + "offset": 12, + "slot": "51", + "type": "t_address", + "contract": "AbstractCCTPIntegrator", + "src": "contracts/strategies/crosschain/AbstractCCTPIntegrator.sol:114" + }, + { + "label": "nonceProcessed", + "offset": 0, + "slot": "52", + "type": "t_mapping(t_uint64,t_bool)", + "contract": "AbstractCCTPIntegrator", + "src": "contracts/strategies/crosschain/AbstractCCTPIntegrator.sol:117" + }, + { + "label": "__gap", + "offset": 0, + "slot": "53", + "type": "t_array(t_uint256)48_storage", + "contract": "AbstractCCTPIntegrator", + "src": "contracts/strategies/crosschain/AbstractCCTPIntegrator.sol:120" + }, + { + "label": "_deprecated_platformAddress", + "offset": 0, + "slot": "101", + "type": "t_address", + "contract": "InitializableAbstractStrategy", + "src": "contracts/utils/InitializableAbstractStrategy.sol:43" + }, + { + "label": "_deprecated_vaultAddress", + "offset": 0, + "slot": "102", + "type": "t_address", + "contract": "InitializableAbstractStrategy", + "src": "contracts/utils/InitializableAbstractStrategy.sol:47" + }, + { + "label": "assetToPToken", + "offset": 0, + "slot": "103", + "type": "t_mapping(t_address,t_address)", + "contract": "InitializableAbstractStrategy", + "src": "contracts/utils/InitializableAbstractStrategy.sol:50" + }, + { + "label": "assetsMapped", + "offset": 0, + "slot": "104", + "type": "t_array(t_address)dyn_storage", + "contract": "InitializableAbstractStrategy", + "src": "contracts/utils/InitializableAbstractStrategy.sol:53" + }, + { + "label": "_deprecated_rewardTokenAddress", + "offset": 0, + "slot": "105", + "type": "t_address", + "contract": "InitializableAbstractStrategy", + "src": "contracts/utils/InitializableAbstractStrategy.sol:57" + }, + { + "label": "_deprecated_rewardLiquidationThreshold", + "offset": 0, + "slot": "106", + "type": "t_uint256", + "contract": "InitializableAbstractStrategy", + "src": "contracts/utils/InitializableAbstractStrategy.sol:61" + }, + { + "label": "harvesterAddress", + "offset": 0, + "slot": "107", + "type": "t_address", + "contract": "InitializableAbstractStrategy", + "src": "contracts/utils/InitializableAbstractStrategy.sol:64" + }, + { + "label": "rewardTokenAddresses", + "offset": 0, + "slot": "108", + "type": "t_array(t_address)dyn_storage", + "contract": "InitializableAbstractStrategy", + "src": "contracts/utils/InitializableAbstractStrategy.sol:67" + }, + { + "label": "_reserved", + "offset": 0, + "slot": "109", + "type": "t_array(t_int256)98_storage", + "contract": "InitializableAbstractStrategy", + "src": "contracts/utils/InitializableAbstractStrategy.sol:74" + }, + { + "label": "remoteStrategyBalance", + "offset": 0, + "slot": "207", + "type": "t_uint256", + "contract": "CrossChainMasterStrategy", + "src": "contracts/strategies/crosschain/CrossChainMasterStrategy.sol:29" + }, + { + "label": "pendingAmount", + "offset": 0, + "slot": "208", + "type": "t_uint256", + "contract": "CrossChainMasterStrategy", + "src": "contracts/strategies/crosschain/CrossChainMasterStrategy.sol:33" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_address)dyn_storage": { + "label": "address[]", + "numberOfBytes": "32" + }, + "t_array(t_int256)98_storage": { + "label": "int256[98]", + "numberOfBytes": "3136" + }, + "t_array(t_uint256)48_storage": { + "label": "uint256[48]", + "numberOfBytes": "1536" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_int256": { + "label": "int256", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_address)": { + "label": "mapping(address => address)", + "numberOfBytes": "32" + }, + "t_mapping(t_uint64,t_bool)": { + "label": "mapping(uint64 => bool)", + "numberOfBytes": "32" + }, + "t_uint16": { + "label": "uint16", + "numberOfBytes": "2" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint64": { + "label": "uint64", + "numberOfBytes": "8" + } + }, + "namespaces": {} +} \ No newline at end of file From 904a9a1ba25e5ecb82c0577bbf1c4dbdfa813ae0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment?= <55331875+clement-ux@users.noreply.github.com> Date: Thu, 12 Feb 2026 11:11:40 +0100 Subject: [PATCH 15/36] [OUSDVault] Enable Async Withdraw. (#2795) * Deploy 174 * Add proposal id --- .../mainnet/174_ousd_enable_async_withdraw.js | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 contracts/deploy/mainnet/174_ousd_enable_async_withdraw.js diff --git a/contracts/deploy/mainnet/174_ousd_enable_async_withdraw.js b/contracts/deploy/mainnet/174_ousd_enable_async_withdraw.js new file mode 100644 index 0000000000..5190c77d1a --- /dev/null +++ b/contracts/deploy/mainnet/174_ousd_enable_async_withdraw.js @@ -0,0 +1,30 @@ +const addresses = require("../../utils/addresses"); +const { deploymentWithGovernanceProposal } = require("../../utils/deploy"); + +module.exports = deploymentWithGovernanceProposal( + { + deployName: "174_ousd_enable_async_withdraw", + forceDeploy: false, + reduceQueueTime: true, + deployerIsProposer: false, + proposalId: + "80837757447669692892403190167850973826061122692899866950563027606125775267397", + }, + async () => { + // 1. Connect to the OUSD Vault as its governor via the proxy + const cVaultProxy = await ethers.getContract("VaultProxy"); + const cVault = await ethers.getContractAt("IVault", cVaultProxy.address); + + return { + name: "Enable async withdraws for OUSD", + actions: [ + // 1. Enable async withdraws in the vault + { + contract: cVault, + signature: "setWithdrawalClaimDelay(uint256)", + args: [600], // 10 minutes + }, + ], + }; + } +); From 9a91da34435b145afcaabac551010fc5d42e604a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment?= <55331875+clement-ux@users.noreply.github.com> Date: Thu, 12 Feb 2026 11:11:57 +0100 Subject: [PATCH 16/36] Deploy 171 (#2792) --- .../deploy/mainnet/171_ousd_vault_upgrade.js | 3 +- contracts/deployments/mainnet/OUSDVault.json | 2316 +++++++++++++++++ .../246907a3fd0d7c6bfd72156e1588273e.json | 747 ++++++ .../storageLayout/mainnet/OUSDVault.json | 460 ++++ 4 files changed, 3525 insertions(+), 1 deletion(-) create mode 100644 contracts/deployments/mainnet/OUSDVault.json create mode 100644 contracts/deployments/mainnet/solcInputs/246907a3fd0d7c6bfd72156e1588273e.json create mode 100644 contracts/storageLayout/mainnet/OUSDVault.json diff --git a/contracts/deploy/mainnet/171_ousd_vault_upgrade.js b/contracts/deploy/mainnet/171_ousd_vault_upgrade.js index e105c66fbf..462334a6b1 100644 --- a/contracts/deploy/mainnet/171_ousd_vault_upgrade.js +++ b/contracts/deploy/mainnet/171_ousd_vault_upgrade.js @@ -8,7 +8,8 @@ module.exports = deploymentWithGovernanceProposal( //forceSkip: true, reduceQueueTime: true, deployerIsProposer: false, - proposalId: "", + proposalId: + "54701237860996162345391578621003018342359490030642059972010789096768410041031", }, async ({ deployWithConfirmation }) => { // Deployer Actions diff --git a/contracts/deployments/mainnet/OUSDVault.json b/contracts/deployments/mainnet/OUSDVault.json new file mode 100644 index 0000000000..ea0ee9ebe6 --- /dev/null +++ b/contracts/deployments/mainnet/OUSDVault.json @@ -0,0 +1,2316 @@ +{ + "address": "0xE3A9F4eDaF8aBD275Beea7e6a19fDbE6B314578e", + "abi": [ + { + "inputs": [ + { + "internalType": "address", + "name": "_usdc", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "_threshold", + "type": "uint256" + } + ], + "name": "AllocateThresholdUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "_asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "_strategy", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "AssetAllocated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [], + "name": "CapitalPaused", + "type": "event" + }, + { + "anonymous": false, + "inputs": [], + "name": "CapitalUnpaused", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "_strategy", + "type": "address" + } + ], + "name": "DefaultStrategyUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "dripDuration", + "type": "uint256" + } + ], + "name": "DripDurationChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousGovernor", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newGovernor", + "type": "address" + } + ], + "name": "GovernorshipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "maxSupplyDiff", + "type": "uint256" + } + ], + "name": "MaxSupplyDiffChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "_addr", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "_value", + "type": "uint256" + } + ], + "name": "Mint", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousGovernor", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newGovernor", + "type": "address" + } + ], + "name": "PendingGovernorshipTransfer", + "type": "event" + }, + { + "anonymous": false, + "inputs": [], + "name": "RebasePaused", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "rebaseRatePerSecond", + "type": "uint256" + } + ], + "name": "RebasePerSecondMaxChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "_threshold", + "type": "uint256" + } + ], + "name": "RebaseThresholdUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [], + "name": "RebaseUnpaused", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "_addr", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "_value", + "type": "uint256" + } + ], + "name": "Redeem", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "_address", + "type": "address" + } + ], + "name": "StrategistUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "strategy", + "type": "address" + } + ], + "name": "StrategyAddedToMintWhitelist", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "_addr", + "type": "address" + } + ], + "name": "StrategyApproved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "_addr", + "type": "address" + } + ], + "name": "StrategyRemoved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "strategy", + "type": "address" + } + ], + "name": "StrategyRemovedFromMintWhitelist", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "_address", + "type": "address" + } + ], + "name": "TrusteeAddressChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "_basis", + "type": "uint256" + } + ], + "name": "TrusteeFeeBpsChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "_vaultBuffer", + "type": "uint256" + } + ], + "name": "VaultBufferUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "_newDelay", + "type": "uint256" + } + ], + "name": "WithdrawalClaimDelayUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "_claimable", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "_newClaimable", + "type": "uint256" + } + ], + "name": "WithdrawalClaimable", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "_withdrawer", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "_requestId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "WithdrawalClaimed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "_withdrawer", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "_requestId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "_queued", + "type": "uint256" + } + ], + "name": "WithdrawalRequested", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "_to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "_yield", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "_fee", + "type": "uint256" + } + ], + "name": "YieldDistribution", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "strategyAddr", + "type": "address" + } + ], + "name": "addStrategyToMintWhitelist", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "addWithdrawalQueueLiquidity", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "allocate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_addr", + "type": "address" + } + ], + "name": "approveStrategy", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "asset", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "autoAllocateThreshold", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "burnForStrategy", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "capitalPaused", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_asset", + "type": "address" + } + ], + "name": "checkBalance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "claimGovernance", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_requestId", + "type": "uint256" + } + ], + "name": "claimWithdrawal", + "outputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256[]", + "name": "_requestIds", + "type": "uint256[]" + } + ], + "name": "claimWithdrawals", + "outputs": [ + { + "internalType": "uint256[]", + "name": "amounts", + "type": "uint256[]" + }, + { + "internalType": "uint256", + "name": "totalAmount", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "defaultStrategy", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_strategyToAddress", + "type": "address" + }, + { + "internalType": "address[]", + "name": "_assets", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "_amounts", + "type": "uint256[]" + } + ], + "name": "depositToStrategy", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "dripDuration", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getAllAssets", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getAllStrategies", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getAssetCount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getStrategyCount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "governor", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_oToken", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "isGovernor", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "isMintWhitelistedStrategy", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_asset", + "type": "address" + } + ], + "name": "isSupportedAsset", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "lastRebase", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "maxSupplyDiff", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "mint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "mint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "mintForStrategy", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "oToken", + "outputs": [ + { + "internalType": "contract OUSD", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "oUSD", + "outputs": [ + { + "internalType": "contract OUSD", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pauseCapital", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "pauseRebase", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "previewYield", + "outputs": [ + { + "internalType": "uint256", + "name": "yield", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "rebase", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "rebasePaused", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "rebasePerSecondMax", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "rebasePerSecondTarget", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "rebaseThreshold", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_addr", + "type": "address" + } + ], + "name": "removeStrategy", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "strategyAddr", + "type": "address" + } + ], + "name": "removeStrategyFromMintWhitelist", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "requestWithdrawal", + "outputs": [ + { + "internalType": "uint256", + "name": "requestId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "queued", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_threshold", + "type": "uint256" + } + ], + "name": "setAutoAllocateThreshold", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_strategy", + "type": "address" + } + ], + "name": "setDefaultStrategy", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_dripDuration", + "type": "uint256" + } + ], + "name": "setDripDuration", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_maxSupplyDiff", + "type": "uint256" + } + ], + "name": "setMaxSupplyDiff", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "apr", + "type": "uint256" + } + ], + "name": "setRebaseRateMax", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_threshold", + "type": "uint256" + } + ], + "name": "setRebaseThreshold", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_address", + "type": "address" + } + ], + "name": "setStrategistAddr", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_address", + "type": "address" + } + ], + "name": "setTrusteeAddress", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_basis", + "type": "uint256" + } + ], + "name": "setTrusteeFeeBps", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_vaultBuffer", + "type": "uint256" + } + ], + "name": "setVaultBuffer", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_delay", + "type": "uint256" + } + ], + "name": "setWithdrawalClaimDelay", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "strategies", + "outputs": [ + { + "internalType": "bool", + "name": "isSupported", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "_deprecated", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "strategistAddr", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalValue", + "outputs": [ + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_newGovernor", + "type": "address" + } + ], + "name": "transferGovernance", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_asset", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "transferToken", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "trusteeAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "trusteeFeeBps", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "unpauseCapital", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "unpauseRebase", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "vaultBuffer", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "withdrawAllFromStrategies", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_strategyAddr", + "type": "address" + } + ], + "name": "withdrawAllFromStrategy", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_strategyFromAddress", + "type": "address" + }, + { + "internalType": "address[]", + "name": "_assets", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "_amounts", + "type": "uint256[]" + } + ], + "name": "withdrawFromStrategy", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "withdrawalClaimDelay", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "withdrawalQueueMetadata", + "outputs": [ + { + "internalType": "uint128", + "name": "queued", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "claimable", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "claimed", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "nextWithdrawalIndex", + "type": "uint128" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "withdrawalRequests", + "outputs": [ + { + "internalType": "address", + "name": "withdrawer", + "type": "address" + }, + { + "internalType": "bool", + "name": "claimed", + "type": "bool" + }, + { + "internalType": "uint40", + "name": "timestamp", + "type": "uint40" + }, + { + "internalType": "uint128", + "name": "amount", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "queued", + "type": "uint128" + } + ], + "stateMutability": "view", + "type": "function" + } + ], + "transactionHash": "0xa361816fd836104b056b300916eba6be18d0c0234466b3545bf2718a589775cf", + "receipt": { + "to": null, + "from": "0x074105fdD39e982B2ffE749A193c942db1046AB9", + "contractAddress": "0xE3A9F4eDaF8aBD275Beea7e6a19fDbE6B314578e", + "transactionIndex": 16, + "gasUsed": "4601180", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xc0b4bd2c584a48acacf82e025883875e25f323367d309ce5ff8d99fb3895623b", + "transactionHash": "0xa361816fd836104b056b300916eba6be18d0c0234466b3545bf2718a589775cf", + "logs": [], + "blockNumber": 24426857, + "cumulativeGasUsed": "10347312", + "status": 1, + "byzantium": true + }, + "args": [ + "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" + ], + "numDeployments": 1, + "solcInputHash": "246907a3fd0d7c6bfd72156e1588273e", + "metadata": "{\"compiler\":{\"version\":\"0.8.28+commit.7893614a\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_usdc\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"_threshold\",\"type\":\"uint256\"}],\"name\":\"AllocateThresholdUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"_asset\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"_strategy\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"_amount\",\"type\":\"uint256\"}],\"name\":\"AssetAllocated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[],\"name\":\"CapitalPaused\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[],\"name\":\"CapitalUnpaused\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"_strategy\",\"type\":\"address\"}],\"name\":\"DefaultStrategyUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"dripDuration\",\"type\":\"uint256\"}],\"name\":\"DripDurationChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"previousGovernor\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newGovernor\",\"type\":\"address\"}],\"name\":\"GovernorshipTransferred\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"maxSupplyDiff\",\"type\":\"uint256\"}],\"name\":\"MaxSupplyDiffChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"_addr\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"_value\",\"type\":\"uint256\"}],\"name\":\"Mint\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"previousGovernor\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newGovernor\",\"type\":\"address\"}],\"name\":\"PendingGovernorshipTransfer\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[],\"name\":\"RebasePaused\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"rebaseRatePerSecond\",\"type\":\"uint256\"}],\"name\":\"RebasePerSecondMaxChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"_threshold\",\"type\":\"uint256\"}],\"name\":\"RebaseThresholdUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[],\"name\":\"RebaseUnpaused\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"_addr\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"_value\",\"type\":\"uint256\"}],\"name\":\"Redeem\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"_address\",\"type\":\"address\"}],\"name\":\"StrategistUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"strategy\",\"type\":\"address\"}],\"name\":\"StrategyAddedToMintWhitelist\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"_addr\",\"type\":\"address\"}],\"name\":\"StrategyApproved\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"_addr\",\"type\":\"address\"}],\"name\":\"StrategyRemoved\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"strategy\",\"type\":\"address\"}],\"name\":\"StrategyRemovedFromMintWhitelist\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"_address\",\"type\":\"address\"}],\"name\":\"TrusteeAddressChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"_basis\",\"type\":\"uint256\"}],\"name\":\"TrusteeFeeBpsChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"_vaultBuffer\",\"type\":\"uint256\"}],\"name\":\"VaultBufferUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"_newDelay\",\"type\":\"uint256\"}],\"name\":\"WithdrawalClaimDelayUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"_claimable\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"_newClaimable\",\"type\":\"uint256\"}],\"name\":\"WithdrawalClaimable\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"_withdrawer\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"_requestId\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"_amount\",\"type\":\"uint256\"}],\"name\":\"WithdrawalClaimed\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"_withdrawer\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"_requestId\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"_amount\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"_queued\",\"type\":\"uint256\"}],\"name\":\"WithdrawalRequested\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"_to\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"_yield\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"_fee\",\"type\":\"uint256\"}],\"name\":\"YieldDistribution\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"strategyAddr\",\"type\":\"address\"}],\"name\":\"addStrategyToMintWhitelist\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"addWithdrawalQueueLiquidity\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"allocate\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_addr\",\"type\":\"address\"}],\"name\":\"approveStrategy\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"asset\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"autoAllocateThreshold\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_amount\",\"type\":\"uint256\"}],\"name\":\"burnForStrategy\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"capitalPaused\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_asset\",\"type\":\"address\"}],\"name\":\"checkBalance\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"claimGovernance\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_requestId\",\"type\":\"uint256\"}],\"name\":\"claimWithdrawal\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256[]\",\"name\":\"_requestIds\",\"type\":\"uint256[]\"}],\"name\":\"claimWithdrawals\",\"outputs\":[{\"internalType\":\"uint256[]\",\"name\":\"amounts\",\"type\":\"uint256[]\"},{\"internalType\":\"uint256\",\"name\":\"totalAmount\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"defaultStrategy\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_strategyToAddress\",\"type\":\"address\"},{\"internalType\":\"address[]\",\"name\":\"_assets\",\"type\":\"address[]\"},{\"internalType\":\"uint256[]\",\"name\":\"_amounts\",\"type\":\"uint256[]\"}],\"name\":\"depositToStrategy\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"dripDuration\",\"outputs\":[{\"internalType\":\"uint64\",\"name\":\"\",\"type\":\"uint64\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getAllAssets\",\"outputs\":[{\"internalType\":\"address[]\",\"name\":\"\",\"type\":\"address[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getAllStrategies\",\"outputs\":[{\"internalType\":\"address[]\",\"name\":\"\",\"type\":\"address[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getAssetCount\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getStrategyCount\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"governor\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_oToken\",\"type\":\"address\"}],\"name\":\"initialize\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"isGovernor\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"isMintWhitelistedStrategy\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_asset\",\"type\":\"address\"}],\"name\":\"isSupportedAsset\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"lastRebase\",\"outputs\":[{\"internalType\":\"uint64\",\"name\":\"\",\"type\":\"uint64\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"maxSupplyDiff\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_amount\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"mint\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_amount\",\"type\":\"uint256\"}],\"name\":\"mint\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_amount\",\"type\":\"uint256\"}],\"name\":\"mintForStrategy\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"oToken\",\"outputs\":[{\"internalType\":\"contract OUSD\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"oUSD\",\"outputs\":[{\"internalType\":\"contract OUSD\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"pauseCapital\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"pauseRebase\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"previewYield\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"yield\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"rebase\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"rebasePaused\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"rebasePerSecondMax\",\"outputs\":[{\"internalType\":\"uint64\",\"name\":\"\",\"type\":\"uint64\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"rebasePerSecondTarget\",\"outputs\":[{\"internalType\":\"uint64\",\"name\":\"\",\"type\":\"uint64\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"rebaseThreshold\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_addr\",\"type\":\"address\"}],\"name\":\"removeStrategy\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"strategyAddr\",\"type\":\"address\"}],\"name\":\"removeStrategyFromMintWhitelist\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_amount\",\"type\":\"uint256\"}],\"name\":\"requestWithdrawal\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"requestId\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"queued\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_threshold\",\"type\":\"uint256\"}],\"name\":\"setAutoAllocateThreshold\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_strategy\",\"type\":\"address\"}],\"name\":\"setDefaultStrategy\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_dripDuration\",\"type\":\"uint256\"}],\"name\":\"setDripDuration\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_maxSupplyDiff\",\"type\":\"uint256\"}],\"name\":\"setMaxSupplyDiff\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"apr\",\"type\":\"uint256\"}],\"name\":\"setRebaseRateMax\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_threshold\",\"type\":\"uint256\"}],\"name\":\"setRebaseThreshold\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_address\",\"type\":\"address\"}],\"name\":\"setStrategistAddr\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_address\",\"type\":\"address\"}],\"name\":\"setTrusteeAddress\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_basis\",\"type\":\"uint256\"}],\"name\":\"setTrusteeFeeBps\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_vaultBuffer\",\"type\":\"uint256\"}],\"name\":\"setVaultBuffer\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_delay\",\"type\":\"uint256\"}],\"name\":\"setWithdrawalClaimDelay\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"strategies\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"isSupported\",\"type\":\"bool\"},{\"internalType\":\"uint256\",\"name\":\"_deprecated\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"strategistAddr\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"totalValue\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_newGovernor\",\"type\":\"address\"}],\"name\":\"transferGovernance\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_asset\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_amount\",\"type\":\"uint256\"}],\"name\":\"transferToken\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"trusteeAddress\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"trusteeFeeBps\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"unpauseCapital\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"unpauseRebase\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"vaultBuffer\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"withdrawAllFromStrategies\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_strategyAddr\",\"type\":\"address\"}],\"name\":\"withdrawAllFromStrategy\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_strategyFromAddress\",\"type\":\"address\"},{\"internalType\":\"address[]\",\"name\":\"_assets\",\"type\":\"address[]\"},{\"internalType\":\"uint256[]\",\"name\":\"_amounts\",\"type\":\"uint256[]\"}],\"name\":\"withdrawFromStrategy\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"withdrawalClaimDelay\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"withdrawalQueueMetadata\",\"outputs\":[{\"internalType\":\"uint128\",\"name\":\"queued\",\"type\":\"uint128\"},{\"internalType\":\"uint128\",\"name\":\"claimable\",\"type\":\"uint128\"},{\"internalType\":\"uint128\",\"name\":\"claimed\",\"type\":\"uint128\"},{\"internalType\":\"uint128\",\"name\":\"nextWithdrawalIndex\",\"type\":\"uint128\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"withdrawalRequests\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"withdrawer\",\"type\":\"address\"},{\"internalType\":\"bool\",\"name\":\"claimed\",\"type\":\"bool\"},{\"internalType\":\"uint40\",\"name\":\"timestamp\",\"type\":\"uint40\"},{\"internalType\":\"uint128\",\"name\":\"amount\",\"type\":\"uint128\"},{\"internalType\":\"uint128\",\"name\":\"queued\",\"type\":\"uint128\"}],\"stateMutability\":\"view\",\"type\":\"function\"}],\"devdoc\":{\"author\":\"Origin Protocol Inc\",\"kind\":\"dev\",\"methods\":{\"addStrategyToMintWhitelist(address)\":{\"params\":{\"strategyAddr\":\"Strategy address\"}},\"addWithdrawalQueueLiquidity()\":{\"details\":\"is called from the Native Staking strategy when validator withdrawals are processed. It also called before any WETH is allocated to a strategy.\"},\"approveStrategy(address)\":{\"params\":{\"_addr\":\"Address of the strategy to add\"}},\"burnForStrategy(uint256)\":{\"details\":\"Notice: can't use `nonReentrant` modifier since the `redeem` function could require withdrawal on an AMO strategy and that one can call `burnForStrategy` while the execution of the `redeem` has not yet completed -> causing a `nonReentrant` collision. Also important to understand is that this is a limitation imposed by the test suite. Production / mainnet contracts should never be configured in a way where mint/redeem functions that are moving funds between the Vault and end user wallets can influence strategies utilizing this function.\",\"params\":{\"_amount\":\"Amount of OToken to burn\"}},\"checkBalance(address)\":{\"params\":{\"_asset\":\"Address of asset\"},\"returns\":{\"_0\":\"uint256 Balance of asset in decimals of asset\"}},\"claimWithdrawal(uint256)\":{\"params\":{\"_requestId\":\"Unique ID for the withdrawal request\"},\"returns\":{\"amount\":\"Amount of asset transferred to the withdrawer\"}},\"claimWithdrawals(uint256[])\":{\"params\":{\"_requestIds\":\"Unique ID of each withdrawal request\"},\"returns\":{\"amounts\":\"Amount of asset received for each request\",\"totalAmount\":\"Total amount of asset transferred to the withdrawer\"}},\"depositToStrategy(address,address[],uint256[])\":{\"params\":{\"_amounts\":\"Array of amounts of each corresponding asset to deposit.\",\"_assets\":\"Array of asset address that will be deposited into the strategy.\",\"_strategyToAddress\":\"Address of the Strategy to deposit asset into.\"}},\"isSupportedAsset(address)\":{\"params\":{\"_asset\":\"address of the asset\"},\"returns\":{\"_0\":\"true if supported\"}},\"mint(address,uint256,uint256)\":{\"details\":\"Deprecated: use `mint(uint256 _amount)` instead.Deprecated: param _asset Address of the asset being depositedDeprecated: param _minimumOusdAmount Minimum OTokens to mint\",\"params\":{\"_amount\":\"Amount of the asset being deposited\"}},\"mint(uint256)\":{\"params\":{\"_amount\":\"Amount of the asset being deposited\"}},\"mintForStrategy(uint256)\":{\"params\":{\"_amount\":\"Amount of OToken to mint Notice: can't use `nonReentrant` modifier since the `mint` function can call `allocate`, and that can trigger an AMO strategy to call this function while the execution of the `mint` has not yet completed -> causing a `nonReentrant` collision. Also important to understand is that this is a limitation imposed by the test suite. Production / mainnet contracts should never be configured in a way where mint/redeem functions that are moving funds between the Vault and end user wallets can influence strategies utilizing this function.\"}},\"previewYield()\":{\"returns\":{\"yield\":\"amount of expected yield\"}},\"removeStrategy(address)\":{\"params\":{\"_addr\":\"Address of the strategy to remove\"}},\"removeStrategyFromMintWhitelist(address)\":{\"params\":{\"strategyAddr\":\"Strategy address\"}},\"requestWithdrawal(uint256)\":{\"params\":{\"_amount\":\"Amount of OToken to burn.\"},\"returns\":{\"queued\":\"Cumulative total of all asset queued including already claimed requests.\",\"requestId\":\"Unique ID for the withdrawal request\"}},\"setAutoAllocateThreshold(uint256)\":{\"params\":{\"_threshold\":\"OToken amount with 18 fixed decimals.\"}},\"setDefaultStrategy(address)\":{\"params\":{\"_strategy\":\"Address of the Strategy\"}},\"setDripDuration(uint256)\":{\"params\":{\"_dripDuration\":\"Time in seconds to target a constant yield rate\"}},\"setRebaseRateMax(uint256)\":{\"params\":{\"apr\":\"in 1e18 notation. 3 * 1e18 = 3% APR\"}},\"setRebaseThreshold(uint256)\":{\"params\":{\"_threshold\":\"OToken amount with 18 fixed decimals.\"}},\"setStrategistAddr(address)\":{\"params\":{\"_address\":\"Address of Strategist\"}},\"setVaultBuffer(uint256)\":{\"params\":{\"_vaultBuffer\":\"Percentage using 18 decimals. 100% = 1e18.\"}},\"setWithdrawalClaimDelay(uint256)\":{\"params\":{\"_delay\":\"Delay period (should be between 10 mins to 7 days). Set to 0 to disable async withdrawals\"}},\"totalValue()\":{\"returns\":{\"value\":\"Total value in USD/ETH (1e18)\"}},\"transferGovernance(address)\":{\"params\":{\"_newGovernor\":\"Address of the new Governor\"}},\"transferToken(address,uint256)\":{\"params\":{\"_amount\":\"Amount of the asset to transfer\",\"_asset\":\"Address for the asset\"}},\"withdrawAllFromStrategy(address)\":{\"params\":{\"_strategyAddr\":\"Strategy address.\"}},\"withdrawFromStrategy(address,address[],uint256[])\":{\"params\":{\"_amounts\":\"Array of amounts of each corresponding asset to withdraw.\",\"_assets\":\"Array of asset address that will be withdrawn from the strategy.\",\"_strategyFromAddress\":\"Address of the Strategy to withdraw asset from.\"}}},\"title\":\"OUSD VaultAdmin Contract\",\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{\"addStrategyToMintWhitelist(address)\":{\"notice\":\"Adds a strategy to the mint whitelist. Reverts if strategy isn't approved on Vault.\"},\"addWithdrawalQueueLiquidity()\":{\"notice\":\"Adds WETH to the withdrawal queue if there is a funding shortfall.\"},\"allocate()\":{\"notice\":\"Allocate unallocated funds on Vault to strategies.\"},\"approveStrategy(address)\":{\"notice\":\"Add a strategy to the Vault.\"},\"autoAllocateThreshold()\":{\"notice\":\"OToken mints over this amount automatically allocate funds. 18 decimals.\"},\"burnForStrategy(uint256)\":{\"notice\":\"Burn OTokens for an allowed Strategy\"},\"capitalPaused()\":{\"notice\":\"pause operations that change the OToken supply. eg mint, redeem, allocate, mint/burn for strategy\"},\"checkBalance(address)\":{\"notice\":\"Get the balance of an asset held in Vault and all strategies.\"},\"claimGovernance()\":{\"notice\":\"Claim Governance of the contract to a new account (`newGovernor`). Can only be called by the new Governor.\"},\"claimWithdrawal(uint256)\":{\"notice\":\"Claim a previously requested withdrawal once it is claimable. This request can be claimed once the withdrawal queue's `claimable` amount is greater than or equal this request's `queued` amount and 10 minutes has passed. If the requests is not claimable, the transaction will revert with `Queue pending liquidity`. If the request is not older than 10 minutes, the transaction will revert with `Claim delay not met`. OToken is converted to asset at 1:1.\"},\"claimWithdrawals(uint256[])\":{\"notice\":\"Claim a previously requested withdrawals once they are claimable. This requests can be claimed once the withdrawal queue's `claimable` amount is greater than or equal each request's `queued` amount and 10 minutes has passed. If one of the requests is not claimable, the whole transaction will revert with `Queue pending liquidity`. If one of the requests is not older than 10 minutes, the whole transaction will revert with `Claim delay not met`.\"},\"defaultStrategy()\":{\"notice\":\"Default strategy for asset\"},\"depositToStrategy(address,address[],uint256[])\":{\"notice\":\"Deposit multiple asset from the vault into the strategy.\"},\"dripDuration()\":{\"notice\":\"Automatic rebase yield calculations. In seconds. Set to 0 or 1 to disable.\"},\"getAllAssets()\":{\"notice\":\"Return all vault asset addresses in order\"},\"getAllStrategies()\":{\"notice\":\"Return the array of all strategies\"},\"getAssetCount()\":{\"notice\":\"Return the number of asset supported by the Vault.\"},\"getStrategyCount()\":{\"notice\":\"Return the number of strategies active on the Vault.\"},\"governor()\":{\"notice\":\"Returns the address of the current Governor.\"},\"isGovernor()\":{\"notice\":\"Returns true if the caller is the current Governor.\"},\"isSupportedAsset(address)\":{\"notice\":\"Returns whether the vault supports the asset\"},\"lastRebase()\":{\"notice\":\"Time in seconds that the vault last rebased yield.\"},\"maxSupplyDiff()\":{\"notice\":\"Max difference between total supply and total value of assets. 18 decimals.\"},\"mint(address,uint256,uint256)\":{\"notice\":\"Deposit a supported asset and mint OTokens.\"},\"mint(uint256)\":{\"notice\":\"Deposit a supported asset and mint OTokens.\"},\"mintForStrategy(uint256)\":{\"notice\":\"Mint OTokens for an allowed Strategy\"},\"oUSD()\":{\"notice\":\"Deprecated: use `oToken()` instead.\"},\"pauseCapital()\":{\"notice\":\"Set the deposit paused flag to true to prevent capital movement.\"},\"pauseRebase()\":{\"notice\":\"Set the deposit paused flag to true to prevent rebasing.\"},\"previewYield()\":{\"notice\":\"Calculates the amount that would rebase at next rebase. This is before any fees.\"},\"rebase()\":{\"notice\":\"Calculate the total value of asset held by the Vault and all strategies and update the supply of OTokens.\"},\"rebasePaused()\":{\"notice\":\"pause rebasing if true\"},\"rebasePerSecondMax()\":{\"notice\":\"max rebase percentage per second Can be used to set maximum yield of the protocol, spreading out yield over time\"},\"rebasePerSecondTarget()\":{\"notice\":\"target rebase rate limit, based on past rates and funds available.\"},\"rebaseThreshold()\":{\"notice\":\"OToken mints over this amount automatically rebase. 18 decimals.\"},\"removeStrategy(address)\":{\"notice\":\"Remove a strategy from the Vault.\"},\"removeStrategyFromMintWhitelist(address)\":{\"notice\":\"Removes a strategy from the mint whitelist.\"},\"requestWithdrawal(uint256)\":{\"notice\":\"Request an asynchronous withdrawal of asset in exchange for OToken. The OToken is burned on request and the asset is transferred to the withdrawer on claim. This request can be claimed once the withdrawal queue's `claimable` amount is greater than or equal this request's `queued` amount. There is a minimum of 10 minutes before a request can be claimed. After that, the request just needs enough asset liquidity in the Vault to satisfy all the outstanding requests to that point in the queue. OToken is converted to asset at 1:1.\"},\"setAutoAllocateThreshold(uint256)\":{\"notice\":\"Sets the minimum amount of OTokens in a mint to trigger an automatic allocation of funds afterwords.\"},\"setDefaultStrategy(address)\":{\"notice\":\"Set the default Strategy for asset, i.e. the one which the asset will be automatically allocated to and withdrawn from\"},\"setDripDuration(uint256)\":{\"notice\":\"Set the drip duration period\"},\"setMaxSupplyDiff(uint256)\":{\"notice\":\"Sets the maximum allowable difference between total supply and asset' value.\"},\"setRebaseRateMax(uint256)\":{\"notice\":\"Set a yield streaming max rate. This spreads yield over time if it is above the max rate. This is a per rebase APR which due to compounding differs from the yearly APR. Governance should consider this fact when picking a desired APR\"},\"setRebaseThreshold(uint256)\":{\"notice\":\"Set a minimum amount of OTokens in a mint or redeem that triggers a rebase\"},\"setStrategistAddr(address)\":{\"notice\":\"Set address of Strategist\"},\"setTrusteeAddress(address)\":{\"notice\":\"Sets the trusteeAddress that can receive a portion of yield. Setting to the zero address disables this feature.\"},\"setTrusteeFeeBps(uint256)\":{\"notice\":\"Sets the TrusteeFeeBps to the percentage of yield that should be received in basis points.\"},\"setVaultBuffer(uint256)\":{\"notice\":\"Set a buffer of asset to keep in the Vault to handle most redemptions without needing to spend gas unwinding asset from a Strategy.\"},\"setWithdrawalClaimDelay(uint256)\":{\"notice\":\"Changes the async withdrawal claim period for OETH & superOETHb\"},\"strategistAddr()\":{\"notice\":\"Address of the Strategist\"},\"totalValue()\":{\"notice\":\"Determine the total value of asset held by the vault and its strategies.\"},\"transferGovernance(address)\":{\"notice\":\"Transfers Governance of the contract to a new account (`newGovernor`). Can only be called by the current Governor. Must be claimed for this to complete\"},\"transferToken(address,uint256)\":{\"notice\":\"Transfer token to governor. Intended for recovering tokens stuck in contract, i.e. mistaken sends.\"},\"trusteeAddress()\":{\"notice\":\"Trustee contract that can collect a percentage of yield\"},\"trusteeFeeBps()\":{\"notice\":\"Amount of yield collected in basis points. eg 2000 = 20%\"},\"unpauseCapital()\":{\"notice\":\"Set the deposit paused flag to false to enable capital movement.\"},\"unpauseRebase()\":{\"notice\":\"Set the deposit paused flag to true to allow rebasing.\"},\"vaultBuffer()\":{\"notice\":\"Percentage of assets to keep in Vault to handle (most) withdrawals. 100% = 1e18.\"},\"withdrawAllFromStrategies()\":{\"notice\":\"Withdraws all asset from all the strategies and sends asset to the Vault.\"},\"withdrawAllFromStrategy(address)\":{\"notice\":\"Withdraws all asset from the strategy and sends asset to the Vault.\"},\"withdrawFromStrategy(address,address[],uint256[])\":{\"notice\":\"Withdraw multiple asset from the strategy to the vault.\"},\"withdrawalClaimDelay()\":{\"notice\":\"Sets a minimum delay that is required to elapse between requesting async withdrawals and claiming the request. When set to 0 async withdrawals are disabled.\"},\"withdrawalQueueMetadata()\":{\"notice\":\"Global metadata for the withdrawal queue including: queued - cumulative total of all withdrawal requests included the ones that have already been claimed claimable - cumulative total of all the requests that can be claimed including the ones already claimed claimed - total of all the requests that have been claimed nextWithdrawalIndex - index of the next withdrawal request starting at 0\"},\"withdrawalRequests(uint256)\":{\"notice\":\"Mapping of withdrawal request indices to the user withdrawal request data\"}},\"version\":1}},\"settings\":{\"compilationTarget\":{\"contracts/vault/OUSDVault.sol\":\"OUSDVault\"},\"evmVersion\":\"paris\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\",\"useLiteralContent\":true},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[]},\"sources\":{\"@openzeppelin/contracts/token/ERC20/IERC20.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (token/ERC20/IERC20.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev Interface of the ERC20 standard as defined in the EIP.\\n */\\ninterface IERC20 {\\n /**\\n * @dev Returns the amount of tokens in existence.\\n */\\n function totalSupply() external view returns (uint256);\\n\\n /**\\n * @dev Returns the amount of tokens owned by `account`.\\n */\\n function balanceOf(address account) external view returns (uint256);\\n\\n /**\\n * @dev Moves `amount` tokens from the caller's account to `recipient`.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * Emits a {Transfer} event.\\n */\\n function transfer(address recipient, uint256 amount) external returns (bool);\\n\\n /**\\n * @dev Returns the remaining number of tokens that `spender` will be\\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\\n * zero by default.\\n *\\n * This value changes when {approve} or {transferFrom} are called.\\n */\\n function allowance(address owner, address spender) external view returns (uint256);\\n\\n /**\\n * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\\n * that someone may use both the old and the new allowance by unfortunate\\n * transaction ordering. One possible solution to mitigate this race\\n * condition is to first reduce the spender's allowance to 0 and set the\\n * desired value afterwards:\\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\\n *\\n * Emits an {Approval} event.\\n */\\n function approve(address spender, uint256 amount) external returns (bool);\\n\\n /**\\n * @dev Moves `amount` tokens from `sender` to `recipient` using the\\n * allowance mechanism. `amount` is then deducted from the caller's\\n * allowance.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * Emits a {Transfer} event.\\n */\\n function transferFrom(\\n address sender,\\n address recipient,\\n uint256 amount\\n ) external returns (bool);\\n\\n /**\\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\\n * another (`to`).\\n *\\n * Note that `value` may be zero.\\n */\\n event Transfer(address indexed from, address indexed to, uint256 value);\\n\\n /**\\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\\n * a call to {approve}. `value` is the new allowance.\\n */\\n event Approval(address indexed owner, address indexed spender, uint256 value);\\n}\\n\",\"keccak256\":\"0x61437cb513a887a1bbad006e7b1c8b414478427d33de47c5600af3c748f108da\",\"license\":\"MIT\"},\"@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"../IERC20.sol\\\";\\n\\n/**\\n * @dev Interface for the optional metadata functions from the ERC20 standard.\\n *\\n * _Available since v4.1._\\n */\\ninterface IERC20Metadata is IERC20 {\\n /**\\n * @dev Returns the name of the token.\\n */\\n function name() external view returns (string memory);\\n\\n /**\\n * @dev Returns the symbol of the token.\\n */\\n function symbol() external view returns (string memory);\\n\\n /**\\n * @dev Returns the decimals places of the token.\\n */\\n function decimals() external view returns (uint8);\\n}\\n\",\"keccak256\":\"0x8de418a5503946cabe331f35fe242d3201a73f67f77aaeb7110acb1f30423aca\",\"license\":\"MIT\"},\"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (token/ERC20/utils/SafeERC20.sol)\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"../IERC20.sol\\\";\\nimport \\\"../../../utils/Address.sol\\\";\\n\\n/**\\n * @title SafeERC20\\n * @dev Wrappers around ERC20 operations that throw on failure (when the token\\n * contract returns false). Tokens that return no value (and instead revert or\\n * throw on failure) are also supported, non-reverting calls are assumed to be\\n * successful.\\n * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,\\n * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.\\n */\\nlibrary SafeERC20 {\\n using Address for address;\\n\\n function safeTransfer(\\n IERC20 token,\\n address to,\\n uint256 value\\n ) internal {\\n _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));\\n }\\n\\n function safeTransferFrom(\\n IERC20 token,\\n address from,\\n address to,\\n uint256 value\\n ) internal {\\n _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));\\n }\\n\\n /**\\n * @dev Deprecated. This function has issues similar to the ones found in\\n * {IERC20-approve}, and its usage is discouraged.\\n *\\n * Whenever possible, use {safeIncreaseAllowance} and\\n * {safeDecreaseAllowance} instead.\\n */\\n function safeApprove(\\n IERC20 token,\\n address spender,\\n uint256 value\\n ) internal {\\n // safeApprove should only be called when setting an initial allowance,\\n // or when resetting it to zero. To increase and decrease it, use\\n // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'\\n require(\\n (value == 0) || (token.allowance(address(this), spender) == 0),\\n \\\"SafeERC20: approve from non-zero to non-zero allowance\\\"\\n );\\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));\\n }\\n\\n function safeIncreaseAllowance(\\n IERC20 token,\\n address spender,\\n uint256 value\\n ) internal {\\n uint256 newAllowance = token.allowance(address(this), spender) + value;\\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));\\n }\\n\\n function safeDecreaseAllowance(\\n IERC20 token,\\n address spender,\\n uint256 value\\n ) internal {\\n unchecked {\\n uint256 oldAllowance = token.allowance(address(this), spender);\\n require(oldAllowance >= value, \\\"SafeERC20: decreased allowance below zero\\\");\\n uint256 newAllowance = oldAllowance - value;\\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));\\n }\\n }\\n\\n /**\\n * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement\\n * on the return value: the return value is optional (but if data is returned, it must not be false).\\n * @param token The token targeted by the call.\\n * @param data The call data (encoded using abi.encode or one of its variants).\\n */\\n function _callOptionalReturn(IERC20 token, bytes memory data) private {\\n // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since\\n // we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that\\n // the target address contains contract code and also asserts for success in the low-level call.\\n\\n bytes memory returndata = address(token).functionCall(data, \\\"SafeERC20: low-level call failed\\\");\\n if (returndata.length > 0) {\\n // Return data is optional\\n require(abi.decode(returndata, (bool)), \\\"SafeERC20: ERC20 operation did not succeed\\\");\\n }\\n }\\n}\\n\",\"keccak256\":\"0xc3d946432c0ddbb1f846a0d3985be71299df331b91d06732152117f62f0be2b5\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/Address.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (utils/Address.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev Collection of functions related to the address type\\n */\\nlibrary Address {\\n /**\\n * @dev Returns true if `account` is a contract.\\n *\\n * [IMPORTANT]\\n * ====\\n * It is unsafe to assume that an address for which this function returns\\n * false is an externally-owned account (EOA) and not a contract.\\n *\\n * Among others, `isContract` will return false for the following\\n * types of addresses:\\n *\\n * - an externally-owned account\\n * - a contract in construction\\n * - an address where a contract will be created\\n * - an address where a contract lived, but was destroyed\\n * ====\\n */\\n function isContract(address account) internal view returns (bool) {\\n // This method relies on extcodesize, which returns 0 for contracts in\\n // construction, since the code is only stored at the end of the\\n // constructor execution.\\n\\n uint256 size;\\n assembly {\\n size := extcodesize(account)\\n }\\n return size > 0;\\n }\\n\\n /**\\n * @dev Replacement for Solidity's `transfer`: sends `amount` wei to\\n * `recipient`, forwarding all available gas and reverting on errors.\\n *\\n * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost\\n * of certain opcodes, possibly making contracts go over the 2300 gas limit\\n * imposed by `transfer`, making them unable to receive funds via\\n * `transfer`. {sendValue} removes this limitation.\\n *\\n * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].\\n *\\n * IMPORTANT: because control is transferred to `recipient`, care must be\\n * taken to not create reentrancy vulnerabilities. Consider using\\n * {ReentrancyGuard} or the\\n * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].\\n */\\n function sendValue(address payable recipient, uint256 amount) internal {\\n require(address(this).balance >= amount, \\\"Address: insufficient balance\\\");\\n\\n (bool success, ) = recipient.call{value: amount}(\\\"\\\");\\n require(success, \\\"Address: unable to send value, recipient may have reverted\\\");\\n }\\n\\n /**\\n * @dev Performs a Solidity function call using a low level `call`. A\\n * plain `call` is an unsafe replacement for a function call: use this\\n * function instead.\\n *\\n * If `target` reverts with a revert reason, it is bubbled up by this\\n * function (like regular Solidity function calls).\\n *\\n * Returns the raw returned data. To convert to the expected return value,\\n * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].\\n *\\n * Requirements:\\n *\\n * - `target` must be a contract.\\n * - calling `target` with `data` must not revert.\\n *\\n * _Available since v3.1._\\n */\\n function functionCall(address target, bytes memory data) internal returns (bytes memory) {\\n return functionCall(target, data, \\\"Address: low-level call failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with\\n * `errorMessage` as a fallback revert reason when `target` reverts.\\n *\\n * _Available since v3.1._\\n */\\n function functionCall(\\n address target,\\n bytes memory data,\\n string memory errorMessage\\n ) internal returns (bytes memory) {\\n return functionCallWithValue(target, data, 0, errorMessage);\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\\n * but also transferring `value` wei to `target`.\\n *\\n * Requirements:\\n *\\n * - the calling contract must have an ETH balance of at least `value`.\\n * - the called Solidity function must be `payable`.\\n *\\n * _Available since v3.1._\\n */\\n function functionCallWithValue(\\n address target,\\n bytes memory data,\\n uint256 value\\n ) internal returns (bytes memory) {\\n return functionCallWithValue(target, data, value, \\\"Address: low-level call with value failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but\\n * with `errorMessage` as a fallback revert reason when `target` reverts.\\n *\\n * _Available since v3.1._\\n */\\n function functionCallWithValue(\\n address target,\\n bytes memory data,\\n uint256 value,\\n string memory errorMessage\\n ) internal returns (bytes memory) {\\n require(address(this).balance >= value, \\\"Address: insufficient balance for call\\\");\\n require(isContract(target), \\\"Address: call to non-contract\\\");\\n\\n (bool success, bytes memory returndata) = target.call{value: value}(data);\\n return verifyCallResult(success, returndata, errorMessage);\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\\n * but performing a static call.\\n *\\n * _Available since v3.3._\\n */\\n function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {\\n return functionStaticCall(target, data, \\\"Address: low-level static call failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\\n * but performing a static call.\\n *\\n * _Available since v3.3._\\n */\\n function functionStaticCall(\\n address target,\\n bytes memory data,\\n string memory errorMessage\\n ) internal view returns (bytes memory) {\\n require(isContract(target), \\\"Address: static call to non-contract\\\");\\n\\n (bool success, bytes memory returndata) = target.staticcall(data);\\n return verifyCallResult(success, returndata, errorMessage);\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\\n * but performing a delegate call.\\n *\\n * _Available since v3.4._\\n */\\n function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {\\n return functionDelegateCall(target, data, \\\"Address: low-level delegate call failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\\n * but performing a delegate call.\\n *\\n * _Available since v3.4._\\n */\\n function functionDelegateCall(\\n address target,\\n bytes memory data,\\n string memory errorMessage\\n ) internal returns (bytes memory) {\\n require(isContract(target), \\\"Address: delegate call to non-contract\\\");\\n\\n (bool success, bytes memory returndata) = target.delegatecall(data);\\n return verifyCallResult(success, returndata, errorMessage);\\n }\\n\\n /**\\n * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the\\n * revert reason using the provided one.\\n *\\n * _Available since v4.3._\\n */\\n function verifyCallResult(\\n bool success,\\n bytes memory returndata,\\n string memory errorMessage\\n ) internal pure returns (bytes memory) {\\n if (success) {\\n return returndata;\\n } else {\\n // Look for revert reason and bubble it up if present\\n if (returndata.length > 0) {\\n // The easiest way to bubble the revert reason is using memory via assembly\\n\\n assembly {\\n let returndata_size := mload(returndata)\\n revert(add(32, returndata), returndata_size)\\n }\\n } else {\\n revert(errorMessage);\\n }\\n }\\n }\\n}\\n\",\"keccak256\":\"0x51b758a8815ecc9596c66c37d56b1d33883a444631a3f916b9fe65cb863ef7c4\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/math/SafeCast.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (utils/math/SafeCast.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev Wrappers over Solidity's uintXX/intXX casting operators with added overflow\\n * checks.\\n *\\n * Downcasting from uint256/int256 in Solidity does not revert on overflow. This can\\n * easily result in undesired exploitation or bugs, since developers usually\\n * assume that overflows raise errors. `SafeCast` restores this intuition by\\n * reverting the transaction when such an operation overflows.\\n *\\n * Using this library instead of the unchecked operations eliminates an entire\\n * class of bugs, so it's recommended to use it always.\\n *\\n * Can be combined with {SafeMath} and {SignedSafeMath} to extend it to smaller types, by performing\\n * all math on `uint256` and `int256` and then downcasting.\\n */\\nlibrary SafeCast {\\n /**\\n * @dev Returns the downcasted uint224 from uint256, reverting on\\n * overflow (when the input is greater than largest uint224).\\n *\\n * Counterpart to Solidity's `uint224` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 224 bits\\n */\\n function toUint224(uint256 value) internal pure returns (uint224) {\\n require(value <= type(uint224).max, \\\"SafeCast: value doesn't fit in 224 bits\\\");\\n return uint224(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted uint128 from uint256, reverting on\\n * overflow (when the input is greater than largest uint128).\\n *\\n * Counterpart to Solidity's `uint128` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 128 bits\\n */\\n function toUint128(uint256 value) internal pure returns (uint128) {\\n require(value <= type(uint128).max, \\\"SafeCast: value doesn't fit in 128 bits\\\");\\n return uint128(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted uint96 from uint256, reverting on\\n * overflow (when the input is greater than largest uint96).\\n *\\n * Counterpart to Solidity's `uint96` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 96 bits\\n */\\n function toUint96(uint256 value) internal pure returns (uint96) {\\n require(value <= type(uint96).max, \\\"SafeCast: value doesn't fit in 96 bits\\\");\\n return uint96(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted uint64 from uint256, reverting on\\n * overflow (when the input is greater than largest uint64).\\n *\\n * Counterpart to Solidity's `uint64` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 64 bits\\n */\\n function toUint64(uint256 value) internal pure returns (uint64) {\\n require(value <= type(uint64).max, \\\"SafeCast: value doesn't fit in 64 bits\\\");\\n return uint64(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted uint32 from uint256, reverting on\\n * overflow (when the input is greater than largest uint32).\\n *\\n * Counterpart to Solidity's `uint32` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 32 bits\\n */\\n function toUint32(uint256 value) internal pure returns (uint32) {\\n require(value <= type(uint32).max, \\\"SafeCast: value doesn't fit in 32 bits\\\");\\n return uint32(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted uint16 from uint256, reverting on\\n * overflow (when the input is greater than largest uint16).\\n *\\n * Counterpart to Solidity's `uint16` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 16 bits\\n */\\n function toUint16(uint256 value) internal pure returns (uint16) {\\n require(value <= type(uint16).max, \\\"SafeCast: value doesn't fit in 16 bits\\\");\\n return uint16(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted uint8 from uint256, reverting on\\n * overflow (when the input is greater than largest uint8).\\n *\\n * Counterpart to Solidity's `uint8` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 8 bits.\\n */\\n function toUint8(uint256 value) internal pure returns (uint8) {\\n require(value <= type(uint8).max, \\\"SafeCast: value doesn't fit in 8 bits\\\");\\n return uint8(value);\\n }\\n\\n /**\\n * @dev Converts a signed int256 into an unsigned uint256.\\n *\\n * Requirements:\\n *\\n * - input must be greater than or equal to 0.\\n */\\n function toUint256(int256 value) internal pure returns (uint256) {\\n require(value >= 0, \\\"SafeCast: value must be positive\\\");\\n return uint256(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted int128 from int256, reverting on\\n * overflow (when the input is less than smallest int128 or\\n * greater than largest int128).\\n *\\n * Counterpart to Solidity's `int128` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 128 bits\\n *\\n * _Available since v3.1._\\n */\\n function toInt128(int256 value) internal pure returns (int128) {\\n require(value >= type(int128).min && value <= type(int128).max, \\\"SafeCast: value doesn't fit in 128 bits\\\");\\n return int128(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted int64 from int256, reverting on\\n * overflow (when the input is less than smallest int64 or\\n * greater than largest int64).\\n *\\n * Counterpart to Solidity's `int64` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 64 bits\\n *\\n * _Available since v3.1._\\n */\\n function toInt64(int256 value) internal pure returns (int64) {\\n require(value >= type(int64).min && value <= type(int64).max, \\\"SafeCast: value doesn't fit in 64 bits\\\");\\n return int64(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted int32 from int256, reverting on\\n * overflow (when the input is less than smallest int32 or\\n * greater than largest int32).\\n *\\n * Counterpart to Solidity's `int32` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 32 bits\\n *\\n * _Available since v3.1._\\n */\\n function toInt32(int256 value) internal pure returns (int32) {\\n require(value >= type(int32).min && value <= type(int32).max, \\\"SafeCast: value doesn't fit in 32 bits\\\");\\n return int32(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted int16 from int256, reverting on\\n * overflow (when the input is less than smallest int16 or\\n * greater than largest int16).\\n *\\n * Counterpart to Solidity's `int16` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 16 bits\\n *\\n * _Available since v3.1._\\n */\\n function toInt16(int256 value) internal pure returns (int16) {\\n require(value >= type(int16).min && value <= type(int16).max, \\\"SafeCast: value doesn't fit in 16 bits\\\");\\n return int16(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted int8 from int256, reverting on\\n * overflow (when the input is less than smallest int8 or\\n * greater than largest int8).\\n *\\n * Counterpart to Solidity's `int8` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 8 bits.\\n *\\n * _Available since v3.1._\\n */\\n function toInt8(int256 value) internal pure returns (int8) {\\n require(value >= type(int8).min && value <= type(int8).max, \\\"SafeCast: value doesn't fit in 8 bits\\\");\\n return int8(value);\\n }\\n\\n /**\\n * @dev Converts an unsigned uint256 into a signed int256.\\n *\\n * Requirements:\\n *\\n * - input must be less than or equal to maxInt256.\\n */\\n function toInt256(uint256 value) internal pure returns (int256) {\\n // Note: Unsafe cast below is okay because `type(int256).max` is guaranteed to be positive\\n require(value <= uint256(type(int256).max), \\\"SafeCast: value doesn't fit in an int256\\\");\\n return int256(value);\\n }\\n}\\n\",\"keccak256\":\"0x5c6caab697d302ad7eb59c234a4d2dbc965c1bae87709bd2850060b7695b28c7\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/math/SafeMath.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (utils/math/SafeMath.sol)\\n\\npragma solidity ^0.8.0;\\n\\n// CAUTION\\n// This version of SafeMath should only be used with Solidity 0.8 or later,\\n// because it relies on the compiler's built in overflow checks.\\n\\n/**\\n * @dev Wrappers over Solidity's arithmetic operations.\\n *\\n * NOTE: `SafeMath` is generally not needed starting with Solidity 0.8, since the compiler\\n * now has built in overflow checking.\\n */\\nlibrary SafeMath {\\n /**\\n * @dev Returns the addition of two unsigned integers, with an overflow flag.\\n *\\n * _Available since v3.4._\\n */\\n function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {\\n unchecked {\\n uint256 c = a + b;\\n if (c < a) return (false, 0);\\n return (true, c);\\n }\\n }\\n\\n /**\\n * @dev Returns the substraction of two unsigned integers, with an overflow flag.\\n *\\n * _Available since v3.4._\\n */\\n function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {\\n unchecked {\\n if (b > a) return (false, 0);\\n return (true, a - b);\\n }\\n }\\n\\n /**\\n * @dev Returns the multiplication of two unsigned integers, with an overflow flag.\\n *\\n * _Available since v3.4._\\n */\\n function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {\\n unchecked {\\n // Gas optimization: this is cheaper than requiring 'a' not being zero, but the\\n // benefit is lost if 'b' is also tested.\\n // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522\\n if (a == 0) return (true, 0);\\n uint256 c = a * b;\\n if (c / a != b) return (false, 0);\\n return (true, c);\\n }\\n }\\n\\n /**\\n * @dev Returns the division of two unsigned integers, with a division by zero flag.\\n *\\n * _Available since v3.4._\\n */\\n function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {\\n unchecked {\\n if (b == 0) return (false, 0);\\n return (true, a / b);\\n }\\n }\\n\\n /**\\n * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.\\n *\\n * _Available since v3.4._\\n */\\n function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {\\n unchecked {\\n if (b == 0) return (false, 0);\\n return (true, a % b);\\n }\\n }\\n\\n /**\\n * @dev Returns the addition of two unsigned integers, reverting on\\n * overflow.\\n *\\n * Counterpart to Solidity's `+` operator.\\n *\\n * Requirements:\\n *\\n * - Addition cannot overflow.\\n */\\n function add(uint256 a, uint256 b) internal pure returns (uint256) {\\n return a + b;\\n }\\n\\n /**\\n * @dev Returns the subtraction of two unsigned integers, reverting on\\n * overflow (when the result is negative).\\n *\\n * Counterpart to Solidity's `-` operator.\\n *\\n * Requirements:\\n *\\n * - Subtraction cannot overflow.\\n */\\n function sub(uint256 a, uint256 b) internal pure returns (uint256) {\\n return a - b;\\n }\\n\\n /**\\n * @dev Returns the multiplication of two unsigned integers, reverting on\\n * overflow.\\n *\\n * Counterpart to Solidity's `*` operator.\\n *\\n * Requirements:\\n *\\n * - Multiplication cannot overflow.\\n */\\n function mul(uint256 a, uint256 b) internal pure returns (uint256) {\\n return a * b;\\n }\\n\\n /**\\n * @dev Returns the integer division of two unsigned integers, reverting on\\n * division by zero. The result is rounded towards zero.\\n *\\n * Counterpart to Solidity's `/` operator.\\n *\\n * Requirements:\\n *\\n * - The divisor cannot be zero.\\n */\\n function div(uint256 a, uint256 b) internal pure returns (uint256) {\\n return a / b;\\n }\\n\\n /**\\n * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),\\n * reverting when dividing by zero.\\n *\\n * Counterpart to Solidity's `%` operator. This function uses a `revert`\\n * opcode (which leaves remaining gas untouched) while Solidity uses an\\n * invalid opcode to revert (consuming all remaining gas).\\n *\\n * Requirements:\\n *\\n * - The divisor cannot be zero.\\n */\\n function mod(uint256 a, uint256 b) internal pure returns (uint256) {\\n return a % b;\\n }\\n\\n /**\\n * @dev Returns the subtraction of two unsigned integers, reverting with custom message on\\n * overflow (when the result is negative).\\n *\\n * CAUTION: This function is deprecated because it requires allocating memory for the error\\n * message unnecessarily. For custom revert reasons use {trySub}.\\n *\\n * Counterpart to Solidity's `-` operator.\\n *\\n * Requirements:\\n *\\n * - Subtraction cannot overflow.\\n */\\n function sub(\\n uint256 a,\\n uint256 b,\\n string memory errorMessage\\n ) internal pure returns (uint256) {\\n unchecked {\\n require(b <= a, errorMessage);\\n return a - b;\\n }\\n }\\n\\n /**\\n * @dev Returns the integer division of two unsigned integers, reverting with custom message on\\n * division by zero. The result is rounded towards zero.\\n *\\n * Counterpart to Solidity's `/` operator. Note: this function uses a\\n * `revert` opcode (which leaves remaining gas untouched) while Solidity\\n * uses an invalid opcode to revert (consuming all remaining gas).\\n *\\n * Requirements:\\n *\\n * - The divisor cannot be zero.\\n */\\n function div(\\n uint256 a,\\n uint256 b,\\n string memory errorMessage\\n ) internal pure returns (uint256) {\\n unchecked {\\n require(b > 0, errorMessage);\\n return a / b;\\n }\\n }\\n\\n /**\\n * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),\\n * reverting with custom message when dividing by zero.\\n *\\n * CAUTION: This function is deprecated because it requires allocating memory for the error\\n * message unnecessarily. For custom revert reasons use {tryMod}.\\n *\\n * Counterpart to Solidity's `%` operator. This function uses a `revert`\\n * opcode (which leaves remaining gas untouched) while Solidity uses an\\n * invalid opcode to revert (consuming all remaining gas).\\n *\\n * Requirements:\\n *\\n * - The divisor cannot be zero.\\n */\\n function mod(\\n uint256 a,\\n uint256 b,\\n string memory errorMessage\\n ) internal pure returns (uint256) {\\n unchecked {\\n require(b > 0, errorMessage);\\n return a % b;\\n }\\n }\\n}\\n\",\"keccak256\":\"0xa2f576be637946f767aa56601c26d717f48a0aff44f82e46f13807eea1009a21\",\"license\":\"MIT\"},\"contracts/governance/Governable.sol\":{\"content\":\"// SPDX-License-Identifier: BUSL-1.1\\npragma solidity ^0.8.0;\\n\\n/**\\n * @title Base for contracts that are managed by the Origin Protocol's Governor.\\n * @dev Copy of the openzeppelin Ownable.sol contract with nomenclature change\\n * from owner to governor and renounce methods removed. Does not use\\n * Context.sol like Ownable.sol does for simplification.\\n * @author Origin Protocol Inc\\n */\\nabstract contract Governable {\\n // Storage position of the owner and pendingOwner of the contract\\n // keccak256(\\\"OUSD.governor\\\");\\n bytes32 private constant governorPosition =\\n 0x7bea13895fa79d2831e0a9e28edede30099005a50d652d8957cf8a607ee6ca4a;\\n\\n // keccak256(\\\"OUSD.pending.governor\\\");\\n bytes32 private constant pendingGovernorPosition =\\n 0x44c4d30b2eaad5130ad70c3ba6972730566f3e6359ab83e800d905c61b1c51db;\\n\\n // keccak256(\\\"OUSD.reentry.status\\\");\\n bytes32 private constant reentryStatusPosition =\\n 0x53bf423e48ed90e97d02ab0ebab13b2a235a6bfbe9c321847d5c175333ac4535;\\n\\n // See OpenZeppelin ReentrancyGuard implementation\\n uint256 constant _NOT_ENTERED = 1;\\n uint256 constant _ENTERED = 2;\\n\\n event PendingGovernorshipTransfer(\\n address indexed previousGovernor,\\n address indexed newGovernor\\n );\\n\\n event GovernorshipTransferred(\\n address indexed previousGovernor,\\n address indexed newGovernor\\n );\\n\\n /**\\n * @notice Returns the address of the current Governor.\\n */\\n function governor() public view returns (address) {\\n return _governor();\\n }\\n\\n /**\\n * @dev Returns the address of the current Governor.\\n */\\n function _governor() internal view returns (address governorOut) {\\n bytes32 position = governorPosition;\\n // solhint-disable-next-line no-inline-assembly\\n assembly {\\n governorOut := sload(position)\\n }\\n }\\n\\n /**\\n * @dev Returns the address of the pending Governor.\\n */\\n function _pendingGovernor()\\n internal\\n view\\n returns (address pendingGovernor)\\n {\\n bytes32 position = pendingGovernorPosition;\\n // solhint-disable-next-line no-inline-assembly\\n assembly {\\n pendingGovernor := sload(position)\\n }\\n }\\n\\n /**\\n * @dev Throws if called by any account other than the Governor.\\n */\\n modifier onlyGovernor() {\\n require(isGovernor(), \\\"Caller is not the Governor\\\");\\n _;\\n }\\n\\n /**\\n * @notice Returns true if the caller is the current Governor.\\n */\\n function isGovernor() public view returns (bool) {\\n return msg.sender == _governor();\\n }\\n\\n function _setGovernor(address newGovernor) internal {\\n emit GovernorshipTransferred(_governor(), newGovernor);\\n\\n bytes32 position = governorPosition;\\n // solhint-disable-next-line no-inline-assembly\\n assembly {\\n sstore(position, newGovernor)\\n }\\n }\\n\\n /**\\n * @dev Prevents a contract from calling itself, directly or indirectly.\\n * Calling a `nonReentrant` function from another `nonReentrant`\\n * function is not supported. It is possible to prevent this from happening\\n * by making the `nonReentrant` function external, and make it call a\\n * `private` function that does the actual work.\\n */\\n modifier nonReentrant() {\\n bytes32 position = reentryStatusPosition;\\n uint256 _reentry_status;\\n // solhint-disable-next-line no-inline-assembly\\n assembly {\\n _reentry_status := sload(position)\\n }\\n\\n // On the first call to nonReentrant, _notEntered will be true\\n require(_reentry_status != _ENTERED, \\\"Reentrant call\\\");\\n\\n // Any calls to nonReentrant after this point will fail\\n // solhint-disable-next-line no-inline-assembly\\n assembly {\\n sstore(position, _ENTERED)\\n }\\n\\n _;\\n\\n // By storing the original value once again, a refund is triggered (see\\n // https://eips.ethereum.org/EIPS/eip-2200)\\n // solhint-disable-next-line no-inline-assembly\\n assembly {\\n sstore(position, _NOT_ENTERED)\\n }\\n }\\n\\n function _setPendingGovernor(address newGovernor) internal {\\n bytes32 position = pendingGovernorPosition;\\n // solhint-disable-next-line no-inline-assembly\\n assembly {\\n sstore(position, newGovernor)\\n }\\n }\\n\\n /**\\n * @notice Transfers Governance of the contract to a new account (`newGovernor`).\\n * Can only be called by the current Governor. Must be claimed for this to complete\\n * @param _newGovernor Address of the new Governor\\n */\\n function transferGovernance(address _newGovernor) external onlyGovernor {\\n _setPendingGovernor(_newGovernor);\\n emit PendingGovernorshipTransfer(_governor(), _newGovernor);\\n }\\n\\n /**\\n * @notice Claim Governance of the contract to a new account (`newGovernor`).\\n * Can only be called by the new Governor.\\n */\\n function claimGovernance() external {\\n require(\\n msg.sender == _pendingGovernor(),\\n \\\"Only the pending Governor can complete the claim\\\"\\n );\\n _changeGovernor(msg.sender);\\n }\\n\\n /**\\n * @dev Change Governance of the contract to a new account (`newGovernor`).\\n * @param _newGovernor Address of the new Governor\\n */\\n function _changeGovernor(address _newGovernor) internal {\\n require(_newGovernor != address(0), \\\"New Governor is address(0)\\\");\\n _setGovernor(_newGovernor);\\n }\\n}\\n\",\"keccak256\":\"0xf32f873c8bfbacf2e5f01d0cf37bc7f54fbd5aa656e95c8a599114229946f107\",\"license\":\"BUSL-1.1\"},\"contracts/interfaces/IBasicToken.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.0;\\n\\ninterface IBasicToken {\\n function symbol() external view returns (string memory);\\n\\n function decimals() external view returns (uint8);\\n}\\n\",\"keccak256\":\"0xa562062698aa12572123b36dfd2072f1a39e44fed2031cc19c2c9fd522f96ec2\",\"license\":\"MIT\"},\"contracts/interfaces/IStrategy.sol\":{\"content\":\"// SPDX-License-Identifier: BUSL-1.1\\npragma solidity ^0.8.0;\\n\\n/**\\n * @title Platform interface to integrate with lending platform like Compound, AAVE etc.\\n */\\ninterface IStrategy {\\n /**\\n * @dev Deposit the given asset to platform\\n * @param _asset asset address\\n * @param _amount Amount to deposit\\n */\\n function deposit(address _asset, uint256 _amount) external;\\n\\n /**\\n * @dev Deposit the entire balance of all supported assets in the Strategy\\n * to the platform\\n */\\n function depositAll() external;\\n\\n /**\\n * @dev Withdraw given asset from Lending platform\\n */\\n function withdraw(\\n address _recipient,\\n address _asset,\\n uint256 _amount\\n ) external;\\n\\n /**\\n * @dev Liquidate all assets in strategy and return them to Vault.\\n */\\n function withdrawAll() external;\\n\\n /**\\n * @dev Returns the current balance of the given asset.\\n */\\n function checkBalance(address _asset)\\n external\\n view\\n returns (uint256 balance);\\n\\n /**\\n * @dev Returns bool indicating whether strategy supports asset.\\n */\\n function supportsAsset(address _asset) external view returns (bool);\\n\\n /**\\n * @dev Collect reward tokens from the Strategy.\\n */\\n function collectRewardTokens() external;\\n\\n /**\\n * @dev The address array of the reward tokens for the Strategy.\\n */\\n function getRewardTokenAddresses() external view returns (address[] memory);\\n\\n function harvesterAddress() external view returns (address);\\n\\n function transferToken(address token, uint256 amount) external;\\n\\n function setRewardTokenAddresses(address[] calldata _rewardTokenAddresses)\\n external;\\n}\\n\",\"keccak256\":\"0x79ca47defb3b5a56bba13f14c440838152fd1c1aa640476154516a16da4da8ba\",\"license\":\"BUSL-1.1\"},\"contracts/interfaces/IVault.sol\":{\"content\":\"// SPDX-License-Identifier: BUSL-1.1\\npragma solidity ^0.8.0;\\n\\nimport { VaultStorage } from \\\"../vault/VaultStorage.sol\\\";\\n\\ninterface IVault {\\n // slither-disable-start constable-states\\n\\n event AssetAllocated(address _asset, address _strategy, uint256 _amount);\\n event StrategyApproved(address _addr);\\n event StrategyRemoved(address _addr);\\n event Mint(address _addr, uint256 _value);\\n event Redeem(address _addr, uint256 _value);\\n event CapitalPaused();\\n event CapitalUnpaused();\\n event DefaultStrategyUpdated(address _strategy);\\n event RebasePaused();\\n event RebaseUnpaused();\\n event VaultBufferUpdated(uint256 _vaultBuffer);\\n event AllocateThresholdUpdated(uint256 _threshold);\\n event RebaseThresholdUpdated(uint256 _threshold);\\n event StrategistUpdated(address _address);\\n event MaxSupplyDiffChanged(uint256 maxSupplyDiff);\\n event YieldDistribution(address _to, uint256 _yield, uint256 _fee);\\n event TrusteeFeeBpsChanged(uint256 _basis);\\n event TrusteeAddressChanged(address _address);\\n event StrategyAddedToMintWhitelist(address indexed strategy);\\n event StrategyRemovedFromMintWhitelist(address indexed strategy);\\n event RebasePerSecondMaxChanged(uint256 rebaseRatePerSecond);\\n event DripDurationChanged(uint256 dripDuration);\\n event WithdrawalRequested(\\n address indexed _withdrawer,\\n uint256 indexed _requestId,\\n uint256 _amount,\\n uint256 _queued\\n );\\n event WithdrawalClaimed(\\n address indexed _withdrawer,\\n uint256 indexed _requestId,\\n uint256 _amount\\n );\\n event WithdrawalClaimable(uint256 _claimable, uint256 _newClaimable);\\n event WithdrawalClaimDelayUpdated(uint256 _newDelay);\\n\\n // Governable.sol\\n function transferGovernance(address _newGovernor) external;\\n\\n function claimGovernance() external;\\n\\n function governor() external view returns (address);\\n\\n // VaultAdmin.sol\\n function setVaultBuffer(uint256 _vaultBuffer) external;\\n\\n function vaultBuffer() external view returns (uint256);\\n\\n function setAutoAllocateThreshold(uint256 _threshold) external;\\n\\n function autoAllocateThreshold() external view returns (uint256);\\n\\n function setRebaseThreshold(uint256 _threshold) external;\\n\\n function rebaseThreshold() external view returns (uint256);\\n\\n function setStrategistAddr(address _address) external;\\n\\n function strategistAddr() external view returns (address);\\n\\n function setMaxSupplyDiff(uint256 _maxSupplyDiff) external;\\n\\n function maxSupplyDiff() external view returns (uint256);\\n\\n function setTrusteeAddress(address _address) external;\\n\\n function trusteeAddress() external view returns (address);\\n\\n function setTrusteeFeeBps(uint256 _basis) external;\\n\\n function trusteeFeeBps() external view returns (uint256);\\n\\n function approveStrategy(address _addr) external;\\n\\n function removeStrategy(address _addr) external;\\n\\n function setDefaultStrategy(address _strategy) external;\\n\\n function defaultStrategy() external view returns (address);\\n\\n function pauseRebase() external;\\n\\n function unpauseRebase() external;\\n\\n function rebasePaused() external view returns (bool);\\n\\n function pauseCapital() external;\\n\\n function unpauseCapital() external;\\n\\n function capitalPaused() external view returns (bool);\\n\\n function transferToken(address _asset, uint256 _amount) external;\\n\\n function withdrawAllFromStrategy(address _strategyAddr) external;\\n\\n function withdrawAllFromStrategies() external;\\n\\n function withdrawFromStrategy(\\n address _strategyFromAddress,\\n address[] calldata _assets,\\n uint256[] calldata _amounts\\n ) external;\\n\\n function depositToStrategy(\\n address _strategyToAddress,\\n address[] calldata _assets,\\n uint256[] calldata _amounts\\n ) external;\\n\\n // VaultCore.sol\\n function mint(\\n address _asset,\\n uint256 _amount,\\n uint256 _minimumOusdAmount\\n ) external;\\n\\n function mintForStrategy(uint256 _amount) external;\\n\\n function redeem(uint256 _amount, uint256 _minimumUnitAmount) external;\\n\\n function burnForStrategy(uint256 _amount) external;\\n\\n function allocate() external;\\n\\n function rebase() external;\\n\\n function totalValue() external view returns (uint256 value);\\n\\n function checkBalance(address _asset) external view returns (uint256);\\n\\n /// @notice Deprecated: use calculateRedeemOutput\\n function calculateRedeemOutputs(uint256 _amount)\\n external\\n view\\n returns (uint256[] memory);\\n\\n function calculateRedeemOutput(uint256 _amount)\\n external\\n view\\n returns (uint256);\\n\\n function getAssetCount() external view returns (uint256);\\n\\n function getAllAssets() external view returns (address[] memory);\\n\\n function getStrategyCount() external view returns (uint256);\\n\\n function getAllStrategies() external view returns (address[] memory);\\n\\n /// @notice Deprecated.\\n function isSupportedAsset(address _asset) external view returns (bool);\\n\\n function dripper() external view returns (address);\\n\\n function asset() external view returns (address);\\n\\n function initialize(address) external;\\n\\n function addWithdrawalQueueLiquidity() external;\\n\\n function requestWithdrawal(uint256 _amount)\\n external\\n returns (uint256 requestId, uint256 queued);\\n\\n function claimWithdrawal(uint256 requestId)\\n external\\n returns (uint256 amount);\\n\\n function claimWithdrawals(uint256[] memory requestIds)\\n external\\n returns (uint256[] memory amounts, uint256 totalAmount);\\n\\n function withdrawalQueueMetadata()\\n external\\n view\\n returns (VaultStorage.WithdrawalQueueMetadata memory);\\n\\n function withdrawalRequests(uint256 requestId)\\n external\\n view\\n returns (VaultStorage.WithdrawalRequest memory);\\n\\n function addStrategyToMintWhitelist(address strategyAddr) external;\\n\\n function removeStrategyFromMintWhitelist(address strategyAddr) external;\\n\\n function isMintWhitelistedStrategy(address strategyAddr)\\n external\\n view\\n returns (bool);\\n\\n function withdrawalClaimDelay() external view returns (uint256);\\n\\n function setWithdrawalClaimDelay(uint256 newDelay) external;\\n\\n function lastRebase() external view returns (uint64);\\n\\n function dripDuration() external view returns (uint64);\\n\\n function setDripDuration(uint256 _dripDuration) external;\\n\\n function rebasePerSecondMax() external view returns (uint64);\\n\\n function setRebaseRateMax(uint256 yearlyApr) external;\\n\\n function rebasePerSecondTarget() external view returns (uint64);\\n\\n function previewYield() external view returns (uint256 yield);\\n\\n function weth() external view returns (address);\\n\\n // slither-disable-end constable-states\\n}\\n\",\"keccak256\":\"0x61e2ad6f41abac69275ba86e51d9d3cd95dfd6fc6b81960cf75b1e06adb6251d\",\"license\":\"BUSL-1.1\"},\"contracts/token/OUSD.sol\":{\"content\":\"// SPDX-License-Identifier: BUSL-1.1\\npragma solidity ^0.8.0;\\n\\n/**\\n * @title OUSD Token Contract\\n * @dev ERC20 compatible contract for OUSD\\n * @dev Implements an elastic supply\\n * @author Origin Protocol Inc\\n */\\nimport { IVault } from \\\"../interfaces/IVault.sol\\\";\\nimport { Governable } from \\\"../governance/Governable.sol\\\";\\nimport { SafeCast } from \\\"@openzeppelin/contracts/utils/math/SafeCast.sol\\\";\\n\\ncontract OUSD is Governable {\\n using SafeCast for int256;\\n using SafeCast for uint256;\\n\\n /// @dev Event triggered when the supply changes\\n /// @param totalSupply Updated token total supply\\n /// @param rebasingCredits Updated token rebasing credits\\n /// @param rebasingCreditsPerToken Updated token rebasing credits per token\\n event TotalSupplyUpdatedHighres(\\n uint256 totalSupply,\\n uint256 rebasingCredits,\\n uint256 rebasingCreditsPerToken\\n );\\n /// @dev Event triggered when an account opts in for rebasing\\n /// @param account Address of the account\\n event AccountRebasingEnabled(address account);\\n /// @dev Event triggered when an account opts out of rebasing\\n /// @param account Address of the account\\n event AccountRebasingDisabled(address account);\\n /// @dev Emitted when `value` tokens are moved from one account `from` to\\n /// another `to`.\\n /// @param from Address of the account tokens are moved from\\n /// @param to Address of the account tokens are moved to\\n /// @param value Amount of tokens transferred\\n event Transfer(address indexed from, address indexed to, uint256 value);\\n /// @dev Emitted when the allowance of a `spender` for an `owner` is set by\\n /// a call to {approve}. `value` is the new allowance.\\n /// @param owner Address of the owner approving allowance\\n /// @param spender Address of the spender allowance is granted to\\n /// @param value Amount of tokens spender can transfer\\n event Approval(\\n address indexed owner,\\n address indexed spender,\\n uint256 value\\n );\\n /// @dev Yield resulting from {changeSupply} that a `source` account would\\n /// receive is directed to `target` account.\\n /// @param source Address of the source forwarding the yield\\n /// @param target Address of the target receiving the yield\\n event YieldDelegated(address source, address target);\\n /// @dev Yield delegation from `source` account to the `target` account is\\n /// suspended.\\n /// @param source Address of the source suspending yield forwarding\\n /// @param target Address of the target no longer receiving yield from `source`\\n /// account\\n event YieldUndelegated(address source, address target);\\n\\n enum RebaseOptions {\\n NotSet,\\n StdNonRebasing,\\n StdRebasing,\\n YieldDelegationSource,\\n YieldDelegationTarget\\n }\\n\\n uint256[154] private _gap; // Slots to align with deployed contract\\n uint256 private constant MAX_SUPPLY = type(uint128).max;\\n /// @dev The amount of tokens in existence\\n uint256 public totalSupply;\\n mapping(address => mapping(address => uint256)) private allowances;\\n /// @dev The vault with privileges to execute {mint}, {burn}\\n /// and {changeSupply}\\n address public vaultAddress;\\n mapping(address => uint256) internal creditBalances;\\n // the 2 storage variables below need trailing underscores to not name collide with public functions\\n uint256 private rebasingCredits_; // Sum of all rebasing credits (creditBalances for rebasing accounts)\\n uint256 private rebasingCreditsPerToken_;\\n /// @dev The amount of tokens that are not rebasing - receiving yield\\n uint256 public nonRebasingSupply;\\n mapping(address => uint256) internal alternativeCreditsPerToken;\\n /// @dev A map of all addresses and their respective RebaseOptions\\n mapping(address => RebaseOptions) public rebaseState;\\n mapping(address => uint256) private __deprecated_isUpgraded;\\n /// @dev A map of addresses that have yields forwarded to. This is an\\n /// inverse mapping of {yieldFrom}\\n /// Key Account forwarding yield\\n /// Value Account receiving yield\\n mapping(address => address) public yieldTo;\\n /// @dev A map of addresses that are receiving the yield. This is an\\n /// inverse mapping of {yieldTo}\\n /// Key Account receiving yield\\n /// Value Account forwarding yield\\n mapping(address => address) public yieldFrom;\\n\\n uint256 private constant RESOLUTION_INCREASE = 1e9;\\n uint256[34] private __gap; // including below gap totals up to 200\\n\\n /// @dev Verifies that the caller is the Governor or Strategist.\\n modifier onlyGovernorOrStrategist() {\\n require(\\n isGovernor() || msg.sender == IVault(vaultAddress).strategistAddr(),\\n \\\"Caller is not the Strategist or Governor\\\"\\n );\\n _;\\n }\\n\\n /// @dev Initializes the contract and sets necessary variables.\\n /// @param _vaultAddress Address of the vault contract\\n /// @param _initialCreditsPerToken The starting rebasing credits per token.\\n function initialize(address _vaultAddress, uint256 _initialCreditsPerToken)\\n external\\n onlyGovernor\\n {\\n require(_vaultAddress != address(0), \\\"Zero vault address\\\");\\n require(vaultAddress == address(0), \\\"Already initialized\\\");\\n\\n rebasingCreditsPerToken_ = _initialCreditsPerToken;\\n vaultAddress = _vaultAddress;\\n }\\n\\n /// @dev Returns the symbol of the token, a shorter version\\n /// of the name.\\n function symbol() external pure virtual returns (string memory) {\\n return \\\"OUSD\\\";\\n }\\n\\n /// @dev Returns the name of the token.\\n function name() external pure virtual returns (string memory) {\\n return \\\"Origin Dollar\\\";\\n }\\n\\n /// @dev Returns the number of decimals used to get its user representation.\\n function decimals() external pure virtual returns (uint8) {\\n return 18;\\n }\\n\\n /**\\n * @dev Verifies that the caller is the Vault contract\\n */\\n modifier onlyVault() {\\n require(vaultAddress == msg.sender, \\\"Caller is not the Vault\\\");\\n _;\\n }\\n\\n /**\\n * @return High resolution rebasingCreditsPerToken\\n */\\n function rebasingCreditsPerTokenHighres() external view returns (uint256) {\\n return rebasingCreditsPerToken_;\\n }\\n\\n /**\\n * @return Low resolution rebasingCreditsPerToken\\n */\\n function rebasingCreditsPerToken() external view returns (uint256) {\\n return rebasingCreditsPerToken_ / RESOLUTION_INCREASE;\\n }\\n\\n /**\\n * @return High resolution total number of rebasing credits\\n */\\n function rebasingCreditsHighres() external view returns (uint256) {\\n return rebasingCredits_;\\n }\\n\\n /**\\n * @return Low resolution total number of rebasing credits\\n */\\n function rebasingCredits() external view returns (uint256) {\\n return rebasingCredits_ / RESOLUTION_INCREASE;\\n }\\n\\n /**\\n * @notice Gets the balance of the specified address.\\n * @param _account Address to query the balance of.\\n * @return A uint256 representing the amount of base units owned by the\\n * specified address.\\n */\\n function balanceOf(address _account) public view returns (uint256) {\\n RebaseOptions state = rebaseState[_account];\\n if (state == RebaseOptions.YieldDelegationSource) {\\n // Saves a slot read when transferring to or from a yield delegating source\\n // since we know creditBalances equals the balance.\\n return creditBalances[_account];\\n }\\n uint256 baseBalance = (creditBalances[_account] * 1e18) /\\n _creditsPerToken(_account);\\n if (state == RebaseOptions.YieldDelegationTarget) {\\n // creditBalances of yieldFrom accounts equals token balances\\n return baseBalance - creditBalances[yieldFrom[_account]];\\n }\\n return baseBalance;\\n }\\n\\n /**\\n * @notice Gets the credits balance of the specified address.\\n * @dev Backwards compatible with old low res credits per token.\\n * @param _account The address to query the balance of.\\n * @return (uint256, uint256) Credit balance and credits per token of the\\n * address\\n */\\n function creditsBalanceOf(address _account)\\n external\\n view\\n returns (uint256, uint256)\\n {\\n uint256 cpt = _creditsPerToken(_account);\\n if (cpt == 1e27) {\\n // For a period before the resolution upgrade, we created all new\\n // contract accounts at high resolution. Since they are not changing\\n // as a result of this upgrade, we will return their true values\\n return (creditBalances[_account], cpt);\\n } else {\\n return (\\n creditBalances[_account] / RESOLUTION_INCREASE,\\n cpt / RESOLUTION_INCREASE\\n );\\n }\\n }\\n\\n /**\\n * @notice Gets the credits balance of the specified address.\\n * @param _account The address to query the balance of.\\n * @return (uint256, uint256, bool) Credit balance, credits per token of the\\n * address, and isUpgraded\\n */\\n function creditsBalanceOfHighres(address _account)\\n external\\n view\\n returns (\\n uint256,\\n uint256,\\n bool\\n )\\n {\\n return (\\n creditBalances[_account],\\n _creditsPerToken(_account),\\n true // all accounts have their resolution \\\"upgraded\\\"\\n );\\n }\\n\\n // Backwards compatible view\\n function nonRebasingCreditsPerToken(address _account)\\n external\\n view\\n returns (uint256)\\n {\\n return alternativeCreditsPerToken[_account];\\n }\\n\\n /**\\n * @notice Transfer tokens to a specified address.\\n * @param _to the address to transfer to.\\n * @param _value the amount to be transferred.\\n * @return true on success.\\n */\\n function transfer(address _to, uint256 _value) external returns (bool) {\\n require(_to != address(0), \\\"Transfer to zero address\\\");\\n\\n _executeTransfer(msg.sender, _to, _value);\\n\\n emit Transfer(msg.sender, _to, _value);\\n return true;\\n }\\n\\n /**\\n * @notice Transfer tokens from one address to another.\\n * @param _from The address you want to send tokens from.\\n * @param _to The address you want to transfer to.\\n * @param _value The amount of tokens to be transferred.\\n * @return true on success.\\n */\\n function transferFrom(\\n address _from,\\n address _to,\\n uint256 _value\\n ) external returns (bool) {\\n require(_to != address(0), \\\"Transfer to zero address\\\");\\n uint256 userAllowance = allowances[_from][msg.sender];\\n require(_value <= userAllowance, \\\"Allowance exceeded\\\");\\n\\n unchecked {\\n allowances[_from][msg.sender] = userAllowance - _value;\\n }\\n\\n _executeTransfer(_from, _to, _value);\\n\\n emit Transfer(_from, _to, _value);\\n return true;\\n }\\n\\n function _executeTransfer(\\n address _from,\\n address _to,\\n uint256 _value\\n ) internal {\\n (\\n int256 fromRebasingCreditsDiff,\\n int256 fromNonRebasingSupplyDiff\\n ) = _adjustAccount(_from, -_value.toInt256());\\n (\\n int256 toRebasingCreditsDiff,\\n int256 toNonRebasingSupplyDiff\\n ) = _adjustAccount(_to, _value.toInt256());\\n\\n _adjustGlobals(\\n fromRebasingCreditsDiff + toRebasingCreditsDiff,\\n fromNonRebasingSupplyDiff + toNonRebasingSupplyDiff\\n );\\n }\\n\\n function _adjustAccount(address _account, int256 _balanceChange)\\n internal\\n returns (int256 rebasingCreditsDiff, int256 nonRebasingSupplyDiff)\\n {\\n RebaseOptions state = rebaseState[_account];\\n int256 currentBalance = balanceOf(_account).toInt256();\\n if (currentBalance + _balanceChange < 0) {\\n revert(\\\"Transfer amount exceeds balance\\\");\\n }\\n uint256 newBalance = (currentBalance + _balanceChange).toUint256();\\n\\n if (state == RebaseOptions.YieldDelegationSource) {\\n address target = yieldTo[_account];\\n uint256 targetOldBalance = balanceOf(target);\\n uint256 targetNewCredits = _balanceToRebasingCredits(\\n targetOldBalance + newBalance\\n );\\n rebasingCreditsDiff =\\n targetNewCredits.toInt256() -\\n creditBalances[target].toInt256();\\n\\n creditBalances[_account] = newBalance;\\n creditBalances[target] = targetNewCredits;\\n } else if (state == RebaseOptions.YieldDelegationTarget) {\\n uint256 newCredits = _balanceToRebasingCredits(\\n newBalance + creditBalances[yieldFrom[_account]]\\n );\\n rebasingCreditsDiff =\\n newCredits.toInt256() -\\n creditBalances[_account].toInt256();\\n creditBalances[_account] = newCredits;\\n } else {\\n _autoMigrate(_account);\\n uint256 alternativeCreditsPerTokenMem = alternativeCreditsPerToken[\\n _account\\n ];\\n if (alternativeCreditsPerTokenMem > 0) {\\n nonRebasingSupplyDiff = _balanceChange;\\n if (alternativeCreditsPerTokenMem != 1e18) {\\n alternativeCreditsPerToken[_account] = 1e18;\\n }\\n creditBalances[_account] = newBalance;\\n } else {\\n uint256 newCredits = _balanceToRebasingCredits(newBalance);\\n rebasingCreditsDiff =\\n newCredits.toInt256() -\\n creditBalances[_account].toInt256();\\n creditBalances[_account] = newCredits;\\n }\\n }\\n }\\n\\n function _adjustGlobals(\\n int256 _rebasingCreditsDiff,\\n int256 _nonRebasingSupplyDiff\\n ) internal {\\n if (_rebasingCreditsDiff != 0) {\\n rebasingCredits_ = (rebasingCredits_.toInt256() +\\n _rebasingCreditsDiff).toUint256();\\n }\\n if (_nonRebasingSupplyDiff != 0) {\\n nonRebasingSupply = (nonRebasingSupply.toInt256() +\\n _nonRebasingSupplyDiff).toUint256();\\n }\\n }\\n\\n /**\\n * @notice Function to check the amount of tokens that _owner has allowed\\n * to `_spender`.\\n * @param _owner The address which owns the funds.\\n * @param _spender The address which will spend the funds.\\n * @return The number of tokens still available for the _spender.\\n */\\n function allowance(address _owner, address _spender)\\n external\\n view\\n returns (uint256)\\n {\\n return allowances[_owner][_spender];\\n }\\n\\n /**\\n * @notice Approve the passed address to spend the specified amount of\\n * tokens on behalf of msg.sender.\\n * @param _spender The address which will spend the funds.\\n * @param _value The amount of tokens to be spent.\\n * @return true on success.\\n */\\n function approve(address _spender, uint256 _value) external returns (bool) {\\n allowances[msg.sender][_spender] = _value;\\n emit Approval(msg.sender, _spender, _value);\\n return true;\\n }\\n\\n /**\\n * @notice Creates `_amount` tokens and assigns them to `_account`,\\n * increasing the total supply.\\n */\\n function mint(address _account, uint256 _amount) external onlyVault {\\n require(_account != address(0), \\\"Mint to the zero address\\\");\\n\\n // Account\\n (\\n int256 toRebasingCreditsDiff,\\n int256 toNonRebasingSupplyDiff\\n ) = _adjustAccount(_account, _amount.toInt256());\\n // Globals\\n _adjustGlobals(toRebasingCreditsDiff, toNonRebasingSupplyDiff);\\n totalSupply = totalSupply + _amount;\\n\\n require(totalSupply < MAX_SUPPLY, \\\"Max supply\\\");\\n emit Transfer(address(0), _account, _amount);\\n }\\n\\n /**\\n * @notice Destroys `_amount` tokens from `_account`,\\n * reducing the total supply.\\n */\\n function burn(address _account, uint256 _amount) external onlyVault {\\n require(_account != address(0), \\\"Burn from the zero address\\\");\\n if (_amount == 0) {\\n return;\\n }\\n\\n // Account\\n (\\n int256 toRebasingCreditsDiff,\\n int256 toNonRebasingSupplyDiff\\n ) = _adjustAccount(_account, -_amount.toInt256());\\n // Globals\\n _adjustGlobals(toRebasingCreditsDiff, toNonRebasingSupplyDiff);\\n totalSupply = totalSupply - _amount;\\n\\n emit Transfer(_account, address(0), _amount);\\n }\\n\\n /**\\n * @dev Get the credits per token for an account. Returns a fixed amount\\n * if the account is non-rebasing.\\n * @param _account Address of the account.\\n */\\n function _creditsPerToken(address _account)\\n internal\\n view\\n returns (uint256)\\n {\\n uint256 alternativeCreditsPerTokenMem = alternativeCreditsPerToken[\\n _account\\n ];\\n if (alternativeCreditsPerTokenMem != 0) {\\n return alternativeCreditsPerTokenMem;\\n } else {\\n return rebasingCreditsPerToken_;\\n }\\n }\\n\\n /**\\n * @dev Auto migrate contracts to be non rebasing,\\n * unless they have opted into yield.\\n * @param _account Address of the account.\\n */\\n function _autoMigrate(address _account) internal {\\n uint256 codeLen = _account.code.length;\\n bool isEOA = (codeLen == 0) ||\\n (codeLen == 23 && bytes3(_account.code) == 0xef0100);\\n // In previous code versions, contracts would not have had their\\n // rebaseState[_account] set to RebaseOptions.NonRebasing when migrated\\n // therefore we check the actual accounting used on the account as well.\\n if (\\n (!isEOA) &&\\n rebaseState[_account] == RebaseOptions.NotSet &&\\n alternativeCreditsPerToken[_account] == 0\\n ) {\\n _rebaseOptOut(_account);\\n }\\n }\\n\\n /**\\n * @dev Calculates credits from contract's global rebasingCreditsPerToken_, and\\n * also balance that corresponds to those credits. The latter is important\\n * when adjusting the contract's global nonRebasingSupply to circumvent any\\n * possible rounding errors.\\n *\\n * @param _balance Balance of the account.\\n */\\n function _balanceToRebasingCredits(uint256 _balance)\\n internal\\n view\\n returns (uint256 rebasingCredits)\\n {\\n // Rounds up, because we need to ensure that accounts always have\\n // at least the balance that they should have.\\n // Note this should always be used on an absolute account value,\\n // not on a possibly negative diff, because then the rounding would be wrong.\\n return ((_balance) * rebasingCreditsPerToken_ + 1e18 - 1) / 1e18;\\n }\\n\\n /**\\n * @notice The calling account will start receiving yield after a successful call.\\n * @param _account Address of the account.\\n */\\n function governanceRebaseOptIn(address _account) external onlyGovernor {\\n require(_account != address(0), \\\"Zero address not allowed\\\");\\n _rebaseOptIn(_account);\\n }\\n\\n /**\\n * @notice The calling account will start receiving yield after a successful call.\\n */\\n function rebaseOptIn() external {\\n _rebaseOptIn(msg.sender);\\n }\\n\\n function _rebaseOptIn(address _account) internal {\\n uint256 balance = balanceOf(_account);\\n\\n // prettier-ignore\\n require(\\n alternativeCreditsPerToken[_account] > 0 ||\\n // Accounts may explicitly `rebaseOptIn` regardless of\\n // accounting if they have a 0 balance.\\n creditBalances[_account] == 0\\n ,\\n \\\"Account must be non-rebasing\\\"\\n );\\n RebaseOptions state = rebaseState[_account];\\n // prettier-ignore\\n require(\\n state == RebaseOptions.StdNonRebasing ||\\n state == RebaseOptions.NotSet,\\n \\\"Only standard non-rebasing accounts can opt in\\\"\\n );\\n\\n uint256 newCredits = _balanceToRebasingCredits(balance);\\n\\n // Account\\n rebaseState[_account] = RebaseOptions.StdRebasing;\\n alternativeCreditsPerToken[_account] = 0;\\n creditBalances[_account] = newCredits;\\n // Globals\\n _adjustGlobals(newCredits.toInt256(), -balance.toInt256());\\n\\n emit AccountRebasingEnabled(_account);\\n }\\n\\n /**\\n * @notice The calling account will no longer receive yield\\n */\\n function rebaseOptOut() external {\\n _rebaseOptOut(msg.sender);\\n }\\n\\n function _rebaseOptOut(address _account) internal {\\n require(\\n alternativeCreditsPerToken[_account] == 0,\\n \\\"Account must be rebasing\\\"\\n );\\n RebaseOptions state = rebaseState[_account];\\n require(\\n state == RebaseOptions.StdRebasing || state == RebaseOptions.NotSet,\\n \\\"Only standard rebasing accounts can opt out\\\"\\n );\\n\\n uint256 oldCredits = creditBalances[_account];\\n uint256 balance = balanceOf(_account);\\n\\n // Account\\n rebaseState[_account] = RebaseOptions.StdNonRebasing;\\n alternativeCreditsPerToken[_account] = 1e18;\\n creditBalances[_account] = balance;\\n // Globals\\n _adjustGlobals(-oldCredits.toInt256(), balance.toInt256());\\n\\n emit AccountRebasingDisabled(_account);\\n }\\n\\n /**\\n * @notice Distribute yield to users. This changes the exchange rate\\n * between \\\"credits\\\" and OUSD tokens to change rebasing user's balances.\\n * @param _newTotalSupply New total supply of OUSD.\\n */\\n function changeSupply(uint256 _newTotalSupply) external onlyVault {\\n require(totalSupply > 0, \\\"Cannot increase 0 supply\\\");\\n\\n if (totalSupply == _newTotalSupply) {\\n emit TotalSupplyUpdatedHighres(\\n totalSupply,\\n rebasingCredits_,\\n rebasingCreditsPerToken_\\n );\\n return;\\n }\\n\\n totalSupply = _newTotalSupply > MAX_SUPPLY\\n ? MAX_SUPPLY\\n : _newTotalSupply;\\n\\n uint256 rebasingSupply = totalSupply - nonRebasingSupply;\\n // round up in the favour of the protocol\\n rebasingCreditsPerToken_ =\\n (rebasingCredits_ * 1e18 + rebasingSupply - 1) /\\n rebasingSupply;\\n\\n require(rebasingCreditsPerToken_ > 0, \\\"Invalid change in supply\\\");\\n\\n emit TotalSupplyUpdatedHighres(\\n totalSupply,\\n rebasingCredits_,\\n rebasingCreditsPerToken_\\n );\\n }\\n\\n /*\\n * @notice Send the yield from one account to another account.\\n * Each account keeps its own balances.\\n */\\n function delegateYield(address _from, address _to)\\n external\\n onlyGovernorOrStrategist\\n {\\n require(_from != address(0), \\\"Zero from address not allowed\\\");\\n require(_to != address(0), \\\"Zero to address not allowed\\\");\\n\\n require(_from != _to, \\\"Cannot delegate to self\\\");\\n require(\\n yieldFrom[_to] == address(0) &&\\n yieldTo[_to] == address(0) &&\\n yieldFrom[_from] == address(0) &&\\n yieldTo[_from] == address(0),\\n \\\"Blocked by existing yield delegation\\\"\\n );\\n RebaseOptions stateFrom = rebaseState[_from];\\n RebaseOptions stateTo = rebaseState[_to];\\n\\n require(\\n stateFrom == RebaseOptions.NotSet ||\\n stateFrom == RebaseOptions.StdNonRebasing ||\\n stateFrom == RebaseOptions.StdRebasing,\\n \\\"Invalid rebaseState from\\\"\\n );\\n\\n require(\\n stateTo == RebaseOptions.NotSet ||\\n stateTo == RebaseOptions.StdNonRebasing ||\\n stateTo == RebaseOptions.StdRebasing,\\n \\\"Invalid rebaseState to\\\"\\n );\\n\\n if (alternativeCreditsPerToken[_from] == 0) {\\n _rebaseOptOut(_from);\\n }\\n if (alternativeCreditsPerToken[_to] > 0) {\\n _rebaseOptIn(_to);\\n }\\n\\n uint256 fromBalance = balanceOf(_from);\\n uint256 toBalance = balanceOf(_to);\\n uint256 oldToCredits = creditBalances[_to];\\n uint256 newToCredits = _balanceToRebasingCredits(\\n fromBalance + toBalance\\n );\\n\\n // Set up the bidirectional links\\n yieldTo[_from] = _to;\\n yieldFrom[_to] = _from;\\n\\n // Local\\n rebaseState[_from] = RebaseOptions.YieldDelegationSource;\\n alternativeCreditsPerToken[_from] = 1e18;\\n creditBalances[_from] = fromBalance;\\n rebaseState[_to] = RebaseOptions.YieldDelegationTarget;\\n creditBalances[_to] = newToCredits;\\n\\n // Global\\n int256 creditsChange = newToCredits.toInt256() -\\n oldToCredits.toInt256();\\n _adjustGlobals(creditsChange, -(fromBalance).toInt256());\\n emit YieldDelegated(_from, _to);\\n }\\n\\n /*\\n * @notice Stop sending the yield from one account to another account.\\n */\\n function undelegateYield(address _from) external onlyGovernorOrStrategist {\\n // Require a delegation, which will also ensure a valid delegation\\n require(yieldTo[_from] != address(0), \\\"Zero address not allowed\\\");\\n\\n address to = yieldTo[_from];\\n uint256 fromBalance = balanceOf(_from);\\n uint256 toBalance = balanceOf(to);\\n uint256 oldToCredits = creditBalances[to];\\n uint256 newToCredits = _balanceToRebasingCredits(toBalance);\\n\\n // Remove the bidirectional links\\n yieldFrom[to] = address(0);\\n yieldTo[_from] = address(0);\\n\\n // Local\\n rebaseState[_from] = RebaseOptions.StdNonRebasing;\\n // alternativeCreditsPerToken[from] already 1e18 from `delegateYield()`\\n creditBalances[_from] = fromBalance;\\n rebaseState[to] = RebaseOptions.StdRebasing;\\n // alternativeCreditsPerToken[to] already 0 from `delegateYield()`\\n creditBalances[to] = newToCredits;\\n\\n // Global\\n int256 creditsChange = newToCredits.toInt256() -\\n oldToCredits.toInt256();\\n _adjustGlobals(creditsChange, fromBalance.toInt256());\\n emit YieldUndelegated(_from, to);\\n }\\n}\\n\",\"keccak256\":\"0x73439bef6569f5adf6f5ce2cb54a5f0d3109d4819457532236e172a7091980a9\",\"license\":\"BUSL-1.1\"},\"contracts/utils/Helpers.sol\":{\"content\":\"// SPDX-License-Identifier: BUSL-1.1\\npragma solidity ^0.8.0;\\n\\nimport { IBasicToken } from \\\"../interfaces/IBasicToken.sol\\\";\\n\\nlibrary Helpers {\\n /**\\n * @notice Fetch the `symbol()` from an ERC20 token\\n * @dev Grabs the `symbol()` from a contract\\n * @param _token Address of the ERC20 token\\n * @return string Symbol of the ERC20 token\\n */\\n function getSymbol(address _token) internal view returns (string memory) {\\n string memory symbol = IBasicToken(_token).symbol();\\n return symbol;\\n }\\n\\n /**\\n * @notice Fetch the `decimals()` from an ERC20 token\\n * @dev Grabs the `decimals()` from a contract and fails if\\n * the decimal value does not live within a certain range\\n * @param _token Address of the ERC20 token\\n * @return uint256 Decimals of the ERC20 token\\n */\\n function getDecimals(address _token) internal view returns (uint256) {\\n uint256 decimals = IBasicToken(_token).decimals();\\n require(\\n decimals >= 4 && decimals <= 18,\\n \\\"Token must have sufficient decimal places\\\"\\n );\\n\\n return decimals;\\n }\\n}\\n\",\"keccak256\":\"0x4366f8d90b34c1eef8bbaaf369b1e5cd59f04027bb3c111f208eaee65bbc0346\",\"license\":\"BUSL-1.1\"},\"contracts/utils/Initializable.sol\":{\"content\":\"// SPDX-License-Identifier: BUSL-1.1\\npragma solidity ^0.8.0;\\n\\n/**\\n * @title Base contract any contracts that need to initialize state after deployment.\\n * @author Origin Protocol Inc\\n */\\nabstract contract Initializable {\\n /**\\n * @dev Indicates that the contract has been initialized.\\n */\\n bool private initialized;\\n\\n /**\\n * @dev Indicates that the contract is in the process of being initialized.\\n */\\n bool private initializing;\\n\\n /**\\n * @dev Modifier to protect an initializer function from being invoked twice.\\n */\\n modifier initializer() {\\n require(\\n initializing || !initialized,\\n \\\"Initializable: contract is already initialized\\\"\\n );\\n\\n bool isTopLevelCall = !initializing;\\n if (isTopLevelCall) {\\n initializing = true;\\n initialized = true;\\n }\\n\\n _;\\n\\n if (isTopLevelCall) {\\n initializing = false;\\n }\\n }\\n\\n uint256[50] private ______gap;\\n}\\n\",\"keccak256\":\"0x50d39ebf38a3d3111f2b77a6c75ece1d4ae731552fec4697ab16fcf6c0d4d5e8\",\"license\":\"BUSL-1.1\"},\"contracts/utils/StableMath.sol\":{\"content\":\"// SPDX-License-Identifier: BUSL-1.1\\npragma solidity ^0.8.0;\\n\\nimport { SafeMath } from \\\"@openzeppelin/contracts/utils/math/SafeMath.sol\\\";\\n\\n// Based on StableMath from Stability Labs Pty. Ltd.\\n// https://github.com/mstable/mStable-contracts/blob/master/contracts/shared/StableMath.sol\\n\\nlibrary StableMath {\\n using SafeMath for uint256;\\n\\n /**\\n * @dev Scaling unit for use in specific calculations,\\n * where 1 * 10**18, or 1e18 represents a unit '1'\\n */\\n uint256 private constant FULL_SCALE = 1e18;\\n\\n /***************************************\\n Helpers\\n ****************************************/\\n\\n /**\\n * @dev Adjust the scale of an integer\\n * @param to Decimals to scale to\\n * @param from Decimals to scale from\\n */\\n function scaleBy(\\n uint256 x,\\n uint256 to,\\n uint256 from\\n ) internal pure returns (uint256) {\\n if (to > from) {\\n x = x.mul(10**(to - from));\\n } else if (to < from) {\\n // slither-disable-next-line divide-before-multiply\\n x = x.div(10**(from - to));\\n }\\n return x;\\n }\\n\\n /***************************************\\n Precise Arithmetic\\n ****************************************/\\n\\n /**\\n * @dev Multiplies two precise units, and then truncates by the full scale\\n * @param x Left hand input to multiplication\\n * @param y Right hand input to multiplication\\n * @return Result after multiplying the two inputs and then dividing by the shared\\n * scale unit\\n */\\n function mulTruncate(uint256 x, uint256 y) internal pure returns (uint256) {\\n return mulTruncateScale(x, y, FULL_SCALE);\\n }\\n\\n /**\\n * @dev Multiplies two precise units, and then truncates by the given scale. For example,\\n * when calculating 90% of 10e18, (10e18 * 9e17) / 1e18 = (9e36) / 1e18 = 9e18\\n * @param x Left hand input to multiplication\\n * @param y Right hand input to multiplication\\n * @param scale Scale unit\\n * @return Result after multiplying the two inputs and then dividing by the shared\\n * scale unit\\n */\\n function mulTruncateScale(\\n uint256 x,\\n uint256 y,\\n uint256 scale\\n ) internal pure returns (uint256) {\\n // e.g. assume scale = fullScale\\n // z = 10e18 * 9e17 = 9e36\\n uint256 z = x.mul(y);\\n // return 9e36 / 1e18 = 9e18\\n return z.div(scale);\\n }\\n\\n /**\\n * @dev Multiplies two precise units, and then truncates by the full scale, rounding up the result\\n * @param x Left hand input to multiplication\\n * @param y Right hand input to multiplication\\n * @return Result after multiplying the two inputs and then dividing by the shared\\n * scale unit, rounded up to the closest base unit.\\n */\\n function mulTruncateCeil(uint256 x, uint256 y)\\n internal\\n pure\\n returns (uint256)\\n {\\n // e.g. 8e17 * 17268172638 = 138145381104e17\\n uint256 scaled = x.mul(y);\\n // e.g. 138145381104e17 + 9.99...e17 = 138145381113.99...e17\\n uint256 ceil = scaled.add(FULL_SCALE.sub(1));\\n // e.g. 13814538111.399...e18 / 1e18 = 13814538111\\n return ceil.div(FULL_SCALE);\\n }\\n\\n /**\\n * @dev Precisely divides two units, by first scaling the left hand operand. Useful\\n * for finding percentage weightings, i.e. 8e18/10e18 = 80% (or 8e17)\\n * @param x Left hand input to division\\n * @param y Right hand input to division\\n * @return Result after multiplying the left operand by the scale, and\\n * executing the division on the right hand input.\\n */\\n function divPrecisely(uint256 x, uint256 y)\\n internal\\n pure\\n returns (uint256)\\n {\\n // e.g. 8e18 * 1e18 = 8e36\\n uint256 z = x.mul(FULL_SCALE);\\n // e.g. 8e36 / 10e18 = 8e17\\n return z.div(y);\\n }\\n}\\n\",\"keccak256\":\"0x71d6ed0053a1e5ef018d27c3b6d024f336d8157ab6f6859e400b3243a50a71b7\",\"license\":\"BUSL-1.1\"},\"contracts/vault/OUSDVault.sol\":{\"content\":\"// SPDX-License-Identifier: BUSL-1.1\\npragma solidity ^0.8.0;\\n\\nimport { VaultAdmin } from \\\"./VaultAdmin.sol\\\";\\n\\n/**\\n * @title OUSD VaultAdmin Contract\\n * @author Origin Protocol Inc\\n */\\ncontract OUSDVault is VaultAdmin {\\n constructor(address _usdc) VaultAdmin(_usdc) {}\\n}\\n\",\"keccak256\":\"0xb9112bcf70fc7adffe9051e62d3f71ee69f7e06bd7ec7dcce2a149ab8d58693c\",\"license\":\"BUSL-1.1\"},\"contracts/vault/VaultAdmin.sol\":{\"content\":\"// SPDX-License-Identifier: BUSL-1.1\\npragma solidity ^0.8.0;\\n\\n/**\\n * @title OToken VaultAdmin contract\\n * @notice The VaultAdmin contract makes configuration and admin calls on the vault.\\n * @author Origin Protocol Inc\\n */\\n\\nimport { SafeERC20 } from \\\"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\\\";\\n\\nimport { IVault } from \\\"../interfaces/IVault.sol\\\";\\nimport { StableMath } from \\\"../utils/StableMath.sol\\\";\\nimport { SafeCast } from \\\"@openzeppelin/contracts/utils/math/SafeCast.sol\\\";\\n\\nimport \\\"./VaultCore.sol\\\";\\n\\nabstract contract VaultAdmin is VaultCore {\\n using SafeERC20 for IERC20;\\n using StableMath for uint256;\\n using SafeCast for uint256;\\n\\n /**\\n * @dev Verifies that the caller is the Governor or Strategist.\\n */\\n modifier onlyGovernorOrStrategist() {\\n require(\\n msg.sender == strategistAddr || isGovernor(),\\n \\\"Caller is not the Strategist or Governor\\\"\\n );\\n _;\\n }\\n\\n constructor(address _asset) VaultCore(_asset) {}\\n\\n /***************************************\\n Configuration\\n ****************************************/\\n /**\\n * @notice Set a buffer of asset to keep in the Vault to handle most\\n * redemptions without needing to spend gas unwinding asset from a Strategy.\\n * @param _vaultBuffer Percentage using 18 decimals. 100% = 1e18.\\n */\\n function setVaultBuffer(uint256 _vaultBuffer)\\n external\\n onlyGovernorOrStrategist\\n {\\n require(_vaultBuffer <= 1e18, \\\"Invalid value\\\");\\n vaultBuffer = _vaultBuffer;\\n emit VaultBufferUpdated(_vaultBuffer);\\n }\\n\\n /**\\n * @notice Sets the minimum amount of OTokens in a mint to trigger an\\n * automatic allocation of funds afterwords.\\n * @param _threshold OToken amount with 18 fixed decimals.\\n */\\n function setAutoAllocateThreshold(uint256 _threshold)\\n external\\n onlyGovernor\\n {\\n autoAllocateThreshold = _threshold;\\n emit AllocateThresholdUpdated(_threshold);\\n }\\n\\n /**\\n * @notice Set a minimum amount of OTokens in a mint or redeem that triggers a\\n * rebase\\n * @param _threshold OToken amount with 18 fixed decimals.\\n */\\n function setRebaseThreshold(uint256 _threshold) external onlyGovernor {\\n rebaseThreshold = _threshold;\\n emit RebaseThresholdUpdated(_threshold);\\n }\\n\\n /**\\n * @notice Set address of Strategist\\n * @param _address Address of Strategist\\n */\\n function setStrategistAddr(address _address) external onlyGovernor {\\n strategistAddr = _address;\\n emit StrategistUpdated(_address);\\n }\\n\\n /**\\n * @notice Set the default Strategy for asset, i.e. the one which\\n * the asset will be automatically allocated to and withdrawn from\\n * @param _strategy Address of the Strategy\\n */\\n function setDefaultStrategy(address _strategy)\\n external\\n onlyGovernorOrStrategist\\n {\\n emit DefaultStrategyUpdated(_strategy);\\n // If its a zero address being passed for the strategy we are removing\\n // the default strategy\\n if (_strategy != address(0)) {\\n // Make sure the strategy meets some criteria\\n require(strategies[_strategy].isSupported, \\\"Strategy not approved\\\");\\n require(\\n IStrategy(_strategy).supportsAsset(asset),\\n \\\"Asset not supported by Strategy\\\"\\n );\\n }\\n defaultStrategy = _strategy;\\n }\\n\\n /**\\n * @notice Changes the async withdrawal claim period for OETH & superOETHb\\n * @param _delay Delay period (should be between 10 mins to 7 days).\\n * Set to 0 to disable async withdrawals\\n */\\n function setWithdrawalClaimDelay(uint256 _delay) external onlyGovernor {\\n require(\\n _delay == 0 || (_delay >= 10 minutes && _delay <= 15 days),\\n \\\"Invalid claim delay period\\\"\\n );\\n withdrawalClaimDelay = _delay;\\n emit WithdrawalClaimDelayUpdated(_delay);\\n }\\n\\n // slither-disable-start reentrancy-no-eth\\n /**\\n * @notice Set a yield streaming max rate. This spreads yield over\\n * time if it is above the max rate. This is a per rebase APR which\\n * due to compounding differs from the yearly APR. Governance should\\n * consider this fact when picking a desired APR\\n * @param apr in 1e18 notation. 3 * 1e18 = 3% APR\\n */\\n function setRebaseRateMax(uint256 apr) external onlyGovernorOrStrategist {\\n // The old yield will be at the old rate\\n _rebase();\\n // Change the rate\\n uint256 newPerSecond = apr / 100 / 365 days;\\n require(newPerSecond <= MAX_REBASE_PER_SECOND, \\\"Rate too high\\\");\\n rebasePerSecondMax = newPerSecond.toUint64();\\n emit RebasePerSecondMaxChanged(newPerSecond);\\n }\\n\\n // slither-disable-end reentrancy-no-eth\\n\\n // slither-disable-start reentrancy-no-eth\\n /**\\n * @notice Set the drip duration period\\n * @param _dripDuration Time in seconds to target a constant yield rate\\n */\\n function setDripDuration(uint256 _dripDuration)\\n external\\n onlyGovernorOrStrategist\\n {\\n // The old yield will be at the old rate\\n _rebase();\\n dripDuration = _dripDuration.toUint64();\\n emit DripDurationChanged(_dripDuration);\\n }\\n\\n // slither-disable-end reentrancy-no-eth\\n\\n /***************************************\\n Strategy Config\\n ****************************************/\\n\\n /**\\n * @notice Add a strategy to the Vault.\\n * @param _addr Address of the strategy to add\\n */\\n function approveStrategy(address _addr) external onlyGovernor {\\n require(!strategies[_addr].isSupported, \\\"Strategy already approved\\\");\\n require(\\n IStrategy(_addr).supportsAsset(asset),\\n \\\"Asset not supported by Strategy\\\"\\n );\\n strategies[_addr] = Strategy({ isSupported: true, _deprecated: 0 });\\n allStrategies.push(_addr);\\n emit StrategyApproved(_addr);\\n }\\n\\n /**\\n * @notice Remove a strategy from the Vault.\\n * @param _addr Address of the strategy to remove\\n */\\n\\n function removeStrategy(address _addr) external onlyGovernor {\\n require(strategies[_addr].isSupported, \\\"Strategy not approved\\\");\\n require(defaultStrategy != _addr, \\\"Strategy is default for asset\\\");\\n\\n // Initialize strategyIndex with out of bounds result so function will\\n // revert if no valid index found\\n uint256 stratCount = allStrategies.length;\\n uint256 strategyIndex = stratCount;\\n for (uint256 i = 0; i < stratCount; ++i) {\\n if (allStrategies[i] == _addr) {\\n strategyIndex = i;\\n break;\\n }\\n }\\n\\n if (strategyIndex < stratCount) {\\n allStrategies[strategyIndex] = allStrategies[stratCount - 1];\\n allStrategies.pop();\\n\\n // Mark the strategy as not supported\\n strategies[_addr].isSupported = false;\\n isMintWhitelistedStrategy[_addr] = false;\\n\\n // Withdraw all asset\\n IStrategy strategy = IStrategy(_addr);\\n strategy.withdrawAll();\\n\\n // 1e13 for 18 decimals. And 1e1(10) for 6 decimals\\n uint256 maxDustBalance = uint256(1e13).scaleBy(assetDecimals, 18);\\n\\n /*\\n * Some strategies are not able to withdraw all of their funds in a synchronous call.\\n * Prevent the possible accidental removal of such strategies before their funds are withdrawn.\\n */\\n require(\\n strategy.checkBalance(asset) < maxDustBalance,\\n \\\"Strategy has funds\\\"\\n );\\n emit StrategyRemoved(_addr);\\n }\\n }\\n\\n /**\\n * @notice Adds a strategy to the mint whitelist.\\n * Reverts if strategy isn't approved on Vault.\\n * @param strategyAddr Strategy address\\n */\\n function addStrategyToMintWhitelist(address strategyAddr)\\n external\\n onlyGovernor\\n {\\n require(strategies[strategyAddr].isSupported, \\\"Strategy not approved\\\");\\n\\n require(\\n !isMintWhitelistedStrategy[strategyAddr],\\n \\\"Already whitelisted\\\"\\n );\\n\\n isMintWhitelistedStrategy[strategyAddr] = true;\\n\\n emit StrategyAddedToMintWhitelist(strategyAddr);\\n }\\n\\n /**\\n * @notice Removes a strategy from the mint whitelist.\\n * @param strategyAddr Strategy address\\n */\\n function removeStrategyFromMintWhitelist(address strategyAddr)\\n external\\n onlyGovernor\\n {\\n // Intentionally skipping `strategies.isSupported` check since\\n // we may wanna remove an address even after removing the strategy\\n\\n require(isMintWhitelistedStrategy[strategyAddr], \\\"Not whitelisted\\\");\\n\\n isMintWhitelistedStrategy[strategyAddr] = false;\\n\\n emit StrategyRemovedFromMintWhitelist(strategyAddr);\\n }\\n\\n /***************************************\\n Strategies\\n ****************************************/\\n\\n /**\\n * @notice Deposit multiple asset from the vault into the strategy.\\n * @param _strategyToAddress Address of the Strategy to deposit asset into.\\n * @param _assets Array of asset address that will be deposited into the strategy.\\n * @param _amounts Array of amounts of each corresponding asset to deposit.\\n */\\n function depositToStrategy(\\n address _strategyToAddress,\\n address[] calldata _assets,\\n uint256[] calldata _amounts\\n ) external onlyGovernorOrStrategist nonReentrant {\\n _depositToStrategy(_strategyToAddress, _assets, _amounts);\\n }\\n\\n function _depositToStrategy(\\n address _strategyToAddress,\\n address[] calldata _assets,\\n uint256[] calldata _amounts\\n ) internal virtual {\\n require(\\n strategies[_strategyToAddress].isSupported,\\n \\\"Invalid to Strategy\\\"\\n );\\n require(\\n _assets.length == 1 && _amounts.length == 1 && _assets[0] == asset,\\n \\\"Only asset is supported\\\"\\n );\\n\\n // Check the there is enough asset to transfer once the backing\\n // asset reserved for the withdrawal queue is accounted for\\n require(\\n _amounts[0] <= _assetAvailable(),\\n \\\"Not enough assets available\\\"\\n );\\n\\n // Send required amount of funds to the strategy\\n IERC20(asset).safeTransfer(_strategyToAddress, _amounts[0]);\\n\\n // Deposit all the funds that have been sent to the strategy\\n IStrategy(_strategyToAddress).depositAll();\\n }\\n\\n /**\\n * @notice Withdraw multiple asset from the strategy to the vault.\\n * @param _strategyFromAddress Address of the Strategy to withdraw asset from.\\n * @param _assets Array of asset address that will be withdrawn from the strategy.\\n * @param _amounts Array of amounts of each corresponding asset to withdraw.\\n */\\n function withdrawFromStrategy(\\n address _strategyFromAddress,\\n address[] calldata _assets,\\n uint256[] calldata _amounts\\n ) external onlyGovernorOrStrategist nonReentrant {\\n _withdrawFromStrategy(\\n address(this),\\n _strategyFromAddress,\\n _assets,\\n _amounts\\n );\\n }\\n\\n /**\\n * @param _recipient can either be a strategy or the Vault\\n */\\n function _withdrawFromStrategy(\\n address _recipient,\\n address _strategyFromAddress,\\n address[] calldata _assets,\\n uint256[] calldata _amounts\\n ) internal virtual {\\n require(\\n strategies[_strategyFromAddress].isSupported,\\n \\\"Invalid from Strategy\\\"\\n );\\n require(_assets.length == _amounts.length, \\\"Parameter length mismatch\\\");\\n\\n uint256 assetCount = _assets.length;\\n for (uint256 i = 0; i < assetCount; ++i) {\\n // Withdraw from Strategy to the recipient\\n IStrategy(_strategyFromAddress).withdraw(\\n _recipient,\\n _assets[i],\\n _amounts[i]\\n );\\n }\\n\\n _addWithdrawalQueueLiquidity();\\n }\\n\\n /**\\n * @notice Sets the maximum allowable difference between\\n * total supply and asset' value.\\n */\\n function setMaxSupplyDiff(uint256 _maxSupplyDiff) external onlyGovernor {\\n maxSupplyDiff = _maxSupplyDiff;\\n emit MaxSupplyDiffChanged(_maxSupplyDiff);\\n }\\n\\n /**\\n * @notice Sets the trusteeAddress that can receive a portion of yield.\\n * Setting to the zero address disables this feature.\\n */\\n function setTrusteeAddress(address _address) external onlyGovernor {\\n trusteeAddress = _address;\\n emit TrusteeAddressChanged(_address);\\n }\\n\\n /**\\n * @notice Sets the TrusteeFeeBps to the percentage of yield that should be\\n * received in basis points.\\n */\\n function setTrusteeFeeBps(uint256 _basis) external onlyGovernor {\\n require(_basis <= 5000, \\\"basis cannot exceed 50%\\\");\\n trusteeFeeBps = _basis;\\n emit TrusteeFeeBpsChanged(_basis);\\n }\\n\\n /***************************************\\n Pause\\n ****************************************/\\n\\n /**\\n * @notice Set the deposit paused flag to true to prevent rebasing.\\n */\\n function pauseRebase() external onlyGovernorOrStrategist {\\n rebasePaused = true;\\n emit RebasePaused();\\n }\\n\\n /**\\n * @notice Set the deposit paused flag to true to allow rebasing.\\n */\\n function unpauseRebase() external onlyGovernorOrStrategist {\\n rebasePaused = false;\\n emit RebaseUnpaused();\\n }\\n\\n /**\\n * @notice Set the deposit paused flag to true to prevent capital movement.\\n */\\n function pauseCapital() external onlyGovernorOrStrategist {\\n capitalPaused = true;\\n emit CapitalPaused();\\n }\\n\\n /**\\n * @notice Set the deposit paused flag to false to enable capital movement.\\n */\\n function unpauseCapital() external onlyGovernorOrStrategist {\\n capitalPaused = false;\\n emit CapitalUnpaused();\\n }\\n\\n /***************************************\\n Utils\\n ****************************************/\\n\\n /**\\n * @notice Transfer token to governor. Intended for recovering tokens stuck in\\n * contract, i.e. mistaken sends.\\n * @param _asset Address for the asset\\n * @param _amount Amount of the asset to transfer\\n */\\n function transferToken(address _asset, uint256 _amount)\\n external\\n onlyGovernor\\n {\\n require(asset != _asset, \\\"Only unsupported asset\\\");\\n IERC20(_asset).safeTransfer(governor(), _amount);\\n }\\n\\n /***************************************\\n Strategies Admin\\n ****************************************/\\n\\n /**\\n * @notice Withdraws all asset from the strategy and sends asset to the Vault.\\n * @param _strategyAddr Strategy address.\\n */\\n function withdrawAllFromStrategy(address _strategyAddr)\\n external\\n onlyGovernorOrStrategist\\n {\\n _withdrawAllFromStrategy(_strategyAddr);\\n }\\n\\n function _withdrawAllFromStrategy(address _strategyAddr) internal virtual {\\n require(\\n strategies[_strategyAddr].isSupported,\\n \\\"Strategy is not supported\\\"\\n );\\n IStrategy strategy = IStrategy(_strategyAddr);\\n strategy.withdrawAll();\\n _addWithdrawalQueueLiquidity();\\n }\\n\\n /**\\n * @notice Withdraws all asset from all the strategies and sends asset to the Vault.\\n */\\n function withdrawAllFromStrategies() external onlyGovernorOrStrategist {\\n _withdrawAllFromStrategies();\\n }\\n\\n function _withdrawAllFromStrategies() internal virtual {\\n uint256 stratCount = allStrategies.length;\\n for (uint256 i = 0; i < stratCount; ++i) {\\n IStrategy(allStrategies[i]).withdrawAll();\\n }\\n _addWithdrawalQueueLiquidity();\\n }\\n}\\n\",\"keccak256\":\"0x3ba59fd88e9e3dae7197f04bc76213143f36b2e0922958c6f69d406a4a25f9c2\",\"license\":\"BUSL-1.1\"},\"contracts/vault/VaultCore.sol\":{\"content\":\"// SPDX-License-Identifier: BUSL-1.1\\npragma solidity ^0.8.0;\\n\\n/**\\n * @title OToken VaultCore contract\\n * @notice The Vault contract stores asset. On a deposit, OTokens will be minted\\n and sent to the depositor. On a withdrawal, OTokens will be burned and\\n asset will be sent to the withdrawer. The Vault accepts deposits of\\n interest from yield bearing strategies which will modify the supply\\n of OTokens.\\n * @author Origin Protocol Inc\\n */\\n\\nimport { SafeERC20 } from \\\"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\\\";\\nimport { SafeCast } from \\\"@openzeppelin/contracts/utils/math/SafeCast.sol\\\";\\n\\nimport { StableMath } from \\\"../utils/StableMath.sol\\\";\\n\\nimport \\\"./VaultInitializer.sol\\\";\\n\\nabstract contract VaultCore is VaultInitializer {\\n using SafeERC20 for IERC20;\\n using StableMath for uint256;\\n\\n /**\\n * @dev Verifies that the rebasing is not paused.\\n */\\n modifier whenNotRebasePaused() {\\n require(!rebasePaused, \\\"Rebasing paused\\\");\\n _;\\n }\\n\\n /**\\n * @dev Verifies that the deposits are not paused.\\n */\\n modifier whenNotCapitalPaused() {\\n require(!capitalPaused, \\\"Capital paused\\\");\\n _;\\n }\\n\\n constructor(address _asset) VaultInitializer(_asset) {}\\n\\n ////////////////////////////////////////////////////\\n /// MINT / BURN ///\\n ////////////////////////////////////////////////////\\n /**\\n * @notice Deposit a supported asset and mint OTokens.\\n * @dev Deprecated: use `mint(uint256 _amount)` instead.\\n * @dev Deprecated: param _asset Address of the asset being deposited\\n * @param _amount Amount of the asset being deposited\\n * @dev Deprecated: param _minimumOusdAmount Minimum OTokens to mint\\n */\\n function mint(\\n address,\\n uint256 _amount,\\n uint256\\n ) external whenNotCapitalPaused nonReentrant {\\n _mint(_amount);\\n }\\n\\n /**\\n * @notice Deposit a supported asset and mint OTokens.\\n * @param _amount Amount of the asset being deposited\\n */\\n function mint(uint256 _amount) external whenNotCapitalPaused nonReentrant {\\n _mint(_amount);\\n }\\n\\n // slither-disable-start reentrancy-no-eth\\n /**\\n * @dev Deposit a supported asset and mint OTokens.\\n * @param _amount Amount of the asset being deposited\\n */\\n function _mint(uint256 _amount) internal virtual {\\n require(_amount > 0, \\\"Amount must be greater than 0\\\");\\n\\n // Scale amount to 18 decimals\\n uint256 scaledAmount = _amount.scaleBy(18, assetDecimals);\\n\\n emit Mint(msg.sender, scaledAmount);\\n\\n // Rebase must happen before any transfers occur.\\n if (!rebasePaused && scaledAmount >= rebaseThreshold) {\\n _rebase();\\n }\\n\\n // Mint oTokens\\n oToken.mint(msg.sender, scaledAmount);\\n\\n IERC20(asset).safeTransferFrom(msg.sender, address(this), _amount);\\n\\n // Give priority to the withdrawal queue for the new asset liquidity\\n _addWithdrawalQueueLiquidity();\\n\\n // Auto-allocate if necessary\\n if (scaledAmount >= autoAllocateThreshold) {\\n _allocate();\\n }\\n }\\n\\n // slither-disable-end reentrancy-no-eth\\n\\n /**\\n * @notice Mint OTokens for an allowed Strategy\\n * @param _amount Amount of OToken to mint\\n *\\n * Notice: can't use `nonReentrant` modifier since the `mint` function can\\n * call `allocate`, and that can trigger an AMO strategy to call this function\\n * while the execution of the `mint` has not yet completed -> causing a `nonReentrant` collision.\\n *\\n * Also important to understand is that this is a limitation imposed by the test suite.\\n * Production / mainnet contracts should never be configured in a way where mint/redeem functions\\n * that are moving funds between the Vault and end user wallets can influence strategies\\n * utilizing this function.\\n */\\n function mintForStrategy(uint256 _amount)\\n external\\n virtual\\n whenNotCapitalPaused\\n {\\n require(\\n strategies[msg.sender].isSupported == true,\\n \\\"Unsupported strategy\\\"\\n );\\n require(\\n isMintWhitelistedStrategy[msg.sender] == true,\\n \\\"Not whitelisted strategy\\\"\\n );\\n\\n emit Mint(msg.sender, _amount);\\n // Mint matching amount of OTokens\\n oToken.mint(msg.sender, _amount);\\n }\\n\\n /**\\n * @notice Burn OTokens for an allowed Strategy\\n * @param _amount Amount of OToken to burn\\n *\\n * @dev Notice: can't use `nonReentrant` modifier since the `redeem` function could\\n * require withdrawal on an AMO strategy and that one can call `burnForStrategy`\\n * while the execution of the `redeem` has not yet completed -> causing a `nonReentrant` collision.\\n *\\n * Also important to understand is that this is a limitation imposed by the test suite.\\n * Production / mainnet contracts should never be configured in a way where mint/redeem functions\\n * that are moving funds between the Vault and end user wallets can influence strategies\\n * utilizing this function.\\n */\\n function burnForStrategy(uint256 _amount)\\n external\\n virtual\\n whenNotCapitalPaused\\n {\\n require(\\n strategies[msg.sender].isSupported == true,\\n \\\"Unsupported strategy\\\"\\n );\\n require(\\n isMintWhitelistedStrategy[msg.sender] == true,\\n \\\"Not whitelisted strategy\\\"\\n );\\n\\n emit Redeem(msg.sender, _amount);\\n\\n // Burn OTokens\\n oToken.burn(msg.sender, _amount);\\n }\\n\\n ////////////////////////////////////////////////////\\n /// ASYNC WITHDRAWALS ///\\n ////////////////////////////////////////////////////\\n /**\\n * @notice Request an asynchronous withdrawal of asset in exchange for OToken.\\n * The OToken is burned on request and the asset is transferred to the withdrawer on claim.\\n * This request can be claimed once the withdrawal queue's `claimable` amount\\n * is greater than or equal this request's `queued` amount.\\n * There is a minimum of 10 minutes before a request can be claimed. After that, the request just needs\\n * enough asset liquidity in the Vault to satisfy all the outstanding requests to that point in the queue.\\n * OToken is converted to asset at 1:1.\\n * @param _amount Amount of OToken to burn.\\n * @return requestId Unique ID for the withdrawal request\\n * @return queued Cumulative total of all asset queued including already claimed requests.\\n */\\n function requestWithdrawal(uint256 _amount)\\n external\\n virtual\\n whenNotCapitalPaused\\n nonReentrant\\n returns (uint256 requestId, uint256 queued)\\n {\\n require(_amount > 0, \\\"Amount must be greater than 0\\\");\\n require(withdrawalClaimDelay > 0, \\\"Async withdrawals not enabled\\\");\\n\\n // The check that the requester has enough OToken is done in to later burn call\\n\\n requestId = withdrawalQueueMetadata.nextWithdrawalIndex;\\n queued =\\n withdrawalQueueMetadata.queued +\\n _amount.scaleBy(assetDecimals, 18);\\n\\n // Store the next withdrawal request\\n withdrawalQueueMetadata.nextWithdrawalIndex = SafeCast.toUint128(\\n requestId + 1\\n );\\n // Store the updated queued amount which reserves asset in the withdrawal queue\\n // and reduces the vault's total asset\\n withdrawalQueueMetadata.queued = SafeCast.toUint128(queued);\\n // Store the user's withdrawal request\\n // `queued` is in asset decimals, while `amount` is in OToken decimals (18)\\n withdrawalRequests[requestId] = WithdrawalRequest({\\n withdrawer: msg.sender,\\n claimed: false,\\n timestamp: uint40(block.timestamp),\\n amount: SafeCast.toUint128(_amount),\\n queued: SafeCast.toUint128(queued)\\n });\\n\\n // Burn the user's OToken\\n oToken.burn(msg.sender, _amount);\\n\\n // Prevent withdrawal if the vault is solvent by more than the allowed percentage\\n _postRedeem(_amount);\\n\\n emit WithdrawalRequested(msg.sender, requestId, _amount, queued);\\n }\\n\\n // slither-disable-start reentrancy-no-eth\\n /**\\n * @notice Claim a previously requested withdrawal once it is claimable.\\n * This request can be claimed once the withdrawal queue's `claimable` amount\\n * is greater than or equal this request's `queued` amount and 10 minutes has passed.\\n * If the requests is not claimable, the transaction will revert with `Queue pending liquidity`.\\n * If the request is not older than 10 minutes, the transaction will revert with `Claim delay not met`.\\n * OToken is converted to asset at 1:1.\\n * @param _requestId Unique ID for the withdrawal request\\n * @return amount Amount of asset transferred to the withdrawer\\n */\\n function claimWithdrawal(uint256 _requestId)\\n external\\n virtual\\n whenNotCapitalPaused\\n nonReentrant\\n returns (uint256 amount)\\n {\\n // Try and get more liquidity if there is not enough available\\n if (\\n withdrawalRequests[_requestId].queued >\\n withdrawalQueueMetadata.claimable\\n ) {\\n // Add any asset to the withdrawal queue\\n // this needs to remain here as:\\n // - Vault can be funded and `addWithdrawalQueueLiquidity` is not externally called\\n // - funds can be withdrawn from a strategy\\n //\\n // Those funds need to be added to withdrawal queue liquidity\\n _addWithdrawalQueueLiquidity();\\n }\\n\\n // Scale amount to asset decimals\\n amount = _claimWithdrawal(_requestId);\\n\\n // transfer asset from the vault to the withdrawer\\n IERC20(asset).safeTransfer(msg.sender, amount);\\n\\n // Prevent insolvency\\n _postRedeem(amount.scaleBy(18, assetDecimals));\\n }\\n\\n // slither-disable-end reentrancy-no-eth\\n /**\\n * @notice Claim a previously requested withdrawals once they are claimable.\\n * This requests can be claimed once the withdrawal queue's `claimable` amount\\n * is greater than or equal each request's `queued` amount and 10 minutes has passed.\\n * If one of the requests is not claimable, the whole transaction will revert with `Queue pending liquidity`.\\n * If one of the requests is not older than 10 minutes,\\n * the whole transaction will revert with `Claim delay not met`.\\n * @param _requestIds Unique ID of each withdrawal request\\n * @return amounts Amount of asset received for each request\\n * @return totalAmount Total amount of asset transferred to the withdrawer\\n */\\n function claimWithdrawals(uint256[] calldata _requestIds)\\n external\\n virtual\\n whenNotCapitalPaused\\n nonReentrant\\n returns (uint256[] memory amounts, uint256 totalAmount)\\n {\\n // Add any asset to the withdrawal queue\\n // this needs to remain here as:\\n // - Vault can be funded and `addWithdrawalQueueLiquidity` is not externally called\\n // - funds can be withdrawn from a strategy\\n //\\n // Those funds need to be added to withdrawal queue liquidity\\n _addWithdrawalQueueLiquidity();\\n\\n amounts = new uint256[](_requestIds.length);\\n for (uint256 i; i < _requestIds.length; ++i) {\\n // Scale all amounts to asset decimals, thus totalAmount is also in asset decimals\\n amounts[i] = _claimWithdrawal(_requestIds[i]);\\n totalAmount += amounts[i];\\n }\\n\\n // transfer all the claimed asset from the vault to the withdrawer\\n IERC20(asset).safeTransfer(msg.sender, totalAmount);\\n\\n // Prevent insolvency\\n _postRedeem(totalAmount.scaleBy(18, assetDecimals));\\n\\n return (amounts, totalAmount);\\n }\\n\\n function _claimWithdrawal(uint256 requestId)\\n internal\\n returns (uint256 amount)\\n {\\n require(withdrawalClaimDelay > 0, \\\"Async withdrawals not enabled\\\");\\n\\n // Load the structs from storage into memory\\n WithdrawalRequest memory request = withdrawalRequests[requestId];\\n WithdrawalQueueMetadata memory queue = withdrawalQueueMetadata;\\n\\n require(\\n request.timestamp + withdrawalClaimDelay <= block.timestamp,\\n \\\"Claim delay not met\\\"\\n );\\n // If there isn't enough reserved liquidity in the queue to claim\\n require(request.queued <= queue.claimable, \\\"Queue pending liquidity\\\");\\n require(request.withdrawer == msg.sender, \\\"Not requester\\\");\\n require(request.claimed == false, \\\"Already claimed\\\");\\n\\n // Store the request as claimed\\n withdrawalRequests[requestId].claimed = true;\\n // Store the updated claimed amount\\n withdrawalQueueMetadata.claimed =\\n queue.claimed +\\n SafeCast.toUint128(\\n StableMath.scaleBy(request.amount, assetDecimals, 18)\\n );\\n\\n emit WithdrawalClaimed(msg.sender, requestId, request.amount);\\n\\n return StableMath.scaleBy(request.amount, assetDecimals, 18);\\n }\\n\\n function _postRedeem(uint256 _amount) internal {\\n // Until we can prove that we won't affect the prices of our asset\\n // by withdrawing them, this should be here.\\n // It's possible that a strategy was off on its asset total, perhaps\\n // a reward token sold for more or for less than anticipated.\\n uint256 totalUnits = 0;\\n if (_amount >= rebaseThreshold && !rebasePaused) {\\n totalUnits = _rebase();\\n } else {\\n totalUnits = _totalValue();\\n }\\n\\n // Check that the OTokens are backed by enough asset\\n if (maxSupplyDiff > 0) {\\n // If there are more outstanding withdrawal requests than asset in the vault and strategies\\n // then the available asset will be negative and totalUnits will be rounded up to zero.\\n // As we don't know the exact shortfall amount, we will reject all redeem and withdrawals\\n require(totalUnits > 0, \\\"Too many outstanding requests\\\");\\n\\n // Allow a max difference of maxSupplyDiff% between\\n // asset value and OUSD total supply\\n uint256 diff = oToken.totalSupply().divPrecisely(totalUnits);\\n require(\\n (diff > 1e18 ? diff - 1e18 : 1e18 - diff) <= maxSupplyDiff,\\n \\\"Backing supply liquidity error\\\"\\n );\\n }\\n }\\n\\n /**\\n * @notice Allocate unallocated funds on Vault to strategies.\\n */\\n function allocate() external virtual whenNotCapitalPaused nonReentrant {\\n // Add any unallocated asset to the withdrawal queue first\\n _addWithdrawalQueueLiquidity();\\n\\n _allocate();\\n }\\n\\n /**\\n * @dev Allocate asset (eg. WETH or USDC) to the default asset strategy\\n * if there is excess to the Vault buffer.\\n * This is called from either `mint` or `allocate` and assumes `_addWithdrawalQueueLiquidity`\\n * has been called before this function.\\n */\\n function _allocate() internal virtual {\\n // No need to do anything if no default strategy for asset\\n address depositStrategyAddr = defaultStrategy;\\n if (depositStrategyAddr == address(0)) return;\\n\\n uint256 assetAvailableInVault = _assetAvailable();\\n // No need to do anything if there isn't any asset in the vault to allocate\\n if (assetAvailableInVault == 0) return;\\n\\n // Calculate the target buffer for the vault using the total supply\\n uint256 totalSupply = oToken.totalSupply();\\n // Scaled to asset decimals\\n uint256 targetBuffer = totalSupply.mulTruncate(vaultBuffer).scaleBy(\\n assetDecimals,\\n 18\\n );\\n\\n // If available asset in the Vault is below or equal the target buffer then there's nothing to allocate\\n if (assetAvailableInVault <= targetBuffer) return;\\n\\n // The amount of asset to allocate to the default strategy\\n uint256 allocateAmount = assetAvailableInVault - targetBuffer;\\n\\n IStrategy strategy = IStrategy(depositStrategyAddr);\\n // Transfer asset to the strategy and call the strategy's deposit function\\n IERC20(asset).safeTransfer(address(strategy), allocateAmount);\\n strategy.deposit(asset, allocateAmount);\\n\\n emit AssetAllocated(asset, depositStrategyAddr, allocateAmount);\\n }\\n\\n /**\\n * @notice Calculate the total value of asset held by the Vault and all\\n * strategies and update the supply of OTokens.\\n */\\n function rebase() external virtual nonReentrant {\\n _rebase();\\n }\\n\\n /**\\n * @dev Calculate the total value of asset held by the Vault and all\\n * strategies and update the supply of OTokens, optionally sending a\\n * portion of the yield to the trustee.\\n * @return totalUnits Total balance of Vault in units\\n */\\n function _rebase() internal whenNotRebasePaused returns (uint256) {\\n uint256 supply = oToken.totalSupply();\\n uint256 vaultValue = _totalValue();\\n // If no supply yet, do not rebase\\n if (supply == 0) {\\n return vaultValue;\\n }\\n\\n // Calculate yield and new supply\\n (uint256 yield, uint256 targetRate) = _nextYield(supply, vaultValue);\\n uint256 newSupply = supply + yield;\\n // Only rebase upwards and if we have enough backing funds\\n if (newSupply <= supply || newSupply > vaultValue) {\\n return vaultValue;\\n }\\n\\n rebasePerSecondTarget = uint64(_min(targetRate, type(uint64).max));\\n lastRebase = uint64(block.timestamp); // Intentional cast\\n\\n // Fee collection on yield\\n address _trusteeAddress = trusteeAddress; // gas savings\\n uint256 fee = 0;\\n if (_trusteeAddress != address(0)) {\\n fee = (yield * trusteeFeeBps) / 1e4;\\n if (fee > 0) {\\n require(fee < yield, \\\"Fee must not be greater than yield\\\");\\n oToken.mint(_trusteeAddress, fee);\\n }\\n }\\n emit YieldDistribution(_trusteeAddress, yield, fee);\\n\\n // Only ratchet OToken supply upwards\\n // Final check uses latest totalSupply\\n if (newSupply > oToken.totalSupply()) {\\n oToken.changeSupply(newSupply);\\n }\\n return vaultValue;\\n }\\n\\n /**\\n * @notice Calculates the amount that would rebase at next rebase.\\n * This is before any fees.\\n * @return yield amount of expected yield\\n */\\n function previewYield() external view returns (uint256 yield) {\\n (yield, ) = _nextYield(oToken.totalSupply(), _totalValue());\\n return yield;\\n }\\n\\n /**\\n * @dev Calculates the amount that would rebase at next rebase.\\n * See this Readme for detailed explanation:\\n * contracts/contracts/vault/README - Yield Limits.md\\n */\\n function _nextYield(uint256 supply, uint256 vaultValue)\\n internal\\n view\\n virtual\\n returns (uint256 yield, uint256 targetRate)\\n {\\n uint256 nonRebasing = oToken.nonRebasingSupply();\\n uint256 rebasing = supply - nonRebasing;\\n uint256 elapsed = block.timestamp - lastRebase;\\n targetRate = rebasePerSecondTarget;\\n\\n if (\\n elapsed == 0 || // Yield only once per block.\\n rebasing == 0 || // No yield if there are no rebasing tokens to give it to.\\n supply > vaultValue || // No yield if we do not have yield to give.\\n block.timestamp >= type(uint64).max // No yield if we are too far in the future to calculate it correctly.\\n ) {\\n return (0, targetRate);\\n }\\n\\n // Start with the full difference available\\n yield = vaultValue - supply;\\n\\n // Cap via optional automatic duration smoothing\\n uint256 _dripDuration = dripDuration;\\n if (_dripDuration > 1) {\\n // If we are able to sustain an increased drip rate for\\n // double the duration, then increase the target drip rate\\n targetRate = _max(targetRate, yield / (_dripDuration * 2));\\n // If we cannot sustain the target rate any more,\\n // then rebase what we can, and reduce the target\\n targetRate = _min(targetRate, yield / _dripDuration);\\n // drip at the new target rate\\n yield = _min(yield, targetRate * elapsed);\\n }\\n\\n // Cap per second. elapsed is not 1e18 denominated\\n yield = _min(yield, (rebasing * elapsed * rebasePerSecondMax) / 1e18);\\n\\n // Cap at a hard max per rebase, to avoid long durations resulting in huge rebases\\n yield = _min(yield, (rebasing * MAX_REBASE) / 1e18);\\n\\n return (yield, targetRate);\\n }\\n\\n /**\\n * @notice Determine the total value of asset held by the vault and its\\n * strategies.\\n * @return value Total value in USD/ETH (1e18)\\n */\\n function totalValue() external view virtual returns (uint256 value) {\\n value = _totalValue();\\n }\\n\\n /**\\n * @dev Internal Calculate the total value of the asset held by the\\n * vault and its strategies.\\n * @dev The total value of all WETH held by the vault and all its strategies\\n * less any WETH that is reserved for the withdrawal queue.\\n * If there is not enough WETH in the vault and all strategies to cover\\n * all outstanding withdrawal requests then return a total value of 0.\\n * @return value Total value in USD/ETH (1e18)\\n */\\n function _totalValue() internal view virtual returns (uint256 value) {\\n // As asset is the only asset, just return the asset balance\\n value = _checkBalance(asset).scaleBy(18, assetDecimals);\\n }\\n\\n /**\\n * @notice Get the balance of an asset held in Vault and all strategies.\\n * @param _asset Address of asset\\n * @return uint256 Balance of asset in decimals of asset\\n */\\n function checkBalance(address _asset) external view returns (uint256) {\\n return _checkBalance(_asset);\\n }\\n\\n /**\\n * @notice Get the balance of an asset held in Vault and all strategies.\\n * @dev Get the balance of an asset held in Vault and all strategies\\n * less any asset that is reserved for the withdrawal queue.\\n * BaseAsset is the only asset that can return a non-zero balance.\\n * All other asset will return 0 even if there is some dust amounts left in the Vault.\\n * For example, there is 1 wei left of stETH (or USDC) in the OETH (or OUSD) Vault but\\n * will return 0 in this function.\\n *\\n * If there is not enough asset in the vault and all strategies to cover all outstanding\\n * withdrawal requests then return a asset balance of 0\\n * @param _asset Address of asset\\n * @return balance Balance of asset in decimals of asset\\n */\\n function _checkBalance(address _asset)\\n internal\\n view\\n virtual\\n returns (uint256 balance)\\n {\\n if (_asset != asset) return 0;\\n\\n // Get the asset in the vault and the strategies\\n IERC20 asset = IERC20(_asset);\\n balance = asset.balanceOf(address(this));\\n uint256 stratCount = allStrategies.length;\\n for (uint256 i = 0; i < stratCount; ++i) {\\n IStrategy strategy = IStrategy(allStrategies[i]);\\n if (strategy.supportsAsset(_asset)) {\\n balance = balance + strategy.checkBalance(_asset);\\n }\\n }\\n\\n WithdrawalQueueMetadata memory queue = withdrawalQueueMetadata;\\n\\n // If the vault becomes insolvent enough that the total value in the vault and all strategies\\n // is less than the outstanding withdrawals.\\n // For example, there was a mass slashing event and most users request a withdrawal.\\n if (balance + queue.claimed < queue.queued) {\\n return 0;\\n }\\n\\n // Need to remove asset that is reserved for the withdrawal queue\\n return balance + queue.claimed - queue.queued;\\n }\\n\\n /**\\n * @notice Adds WETH to the withdrawal queue if there is a funding shortfall.\\n * @dev is called from the Native Staking strategy when validator withdrawals are processed.\\n * It also called before any WETH is allocated to a strategy.\\n */\\n function addWithdrawalQueueLiquidity() external {\\n _addWithdrawalQueueLiquidity();\\n }\\n\\n /**\\n * @dev Adds asset (eg. WETH or USDC) to the withdrawal queue if there is a funding shortfall.\\n * This assumes 1 asset equal 1 corresponding OToken.\\n */\\n function _addWithdrawalQueueLiquidity()\\n internal\\n returns (uint256 addedClaimable)\\n {\\n WithdrawalQueueMetadata memory queue = withdrawalQueueMetadata;\\n\\n // Check if the claimable asset is less than the queued amount\\n uint256 queueShortfall = queue.queued - queue.claimable;\\n\\n // No need to do anything is the withdrawal queue is full funded\\n if (queueShortfall == 0) {\\n return 0;\\n }\\n\\n uint256 assetBalance = IERC20(asset).balanceOf(address(this));\\n\\n // Of the claimable withdrawal requests, how much is unclaimed?\\n // That is, the amount of asset that is currently allocated for the withdrawal queue\\n uint256 allocatedBaseAsset = queue.claimable - queue.claimed;\\n\\n // If there is no unallocated asset then there is nothing to add to the queue\\n if (assetBalance <= allocatedBaseAsset) {\\n return 0;\\n }\\n\\n uint256 unallocatedBaseAsset = assetBalance - allocatedBaseAsset;\\n // the new claimable amount is the smaller of the queue shortfall or unallocated asset\\n addedClaimable = queueShortfall < unallocatedBaseAsset\\n ? queueShortfall\\n : unallocatedBaseAsset;\\n uint256 newClaimable = queue.claimable + addedClaimable;\\n\\n // Store the new claimable amount back to storage\\n withdrawalQueueMetadata.claimable = SafeCast.toUint128(newClaimable);\\n\\n // emit a WithdrawalClaimable event\\n emit WithdrawalClaimable(newClaimable, addedClaimable);\\n }\\n\\n /**\\n * @dev Calculate how much asset (eg. WETH or USDC) in the vault is not reserved for the withdrawal queue.\\n * That is, it is available to be redeemed or deposited into a strategy.\\n */\\n function _assetAvailable() internal view returns (uint256 assetAvailable) {\\n WithdrawalQueueMetadata memory queue = withdrawalQueueMetadata;\\n\\n // The amount of asset that is still to be claimed in the withdrawal queue\\n uint256 outstandingWithdrawals = queue.queued - queue.claimed;\\n\\n // The amount of sitting in asset in the vault\\n uint256 assetBalance = IERC20(asset).balanceOf(address(this));\\n // If there is not enough asset in the vault to cover the outstanding withdrawals\\n if (assetBalance <= outstandingWithdrawals) return 0;\\n\\n return assetBalance - outstandingWithdrawals;\\n }\\n\\n /***************************************\\n Utils\\n ****************************************/\\n\\n /**\\n * @notice Return the number of asset supported by the Vault.\\n */\\n function getAssetCount() public view returns (uint256) {\\n return 1;\\n }\\n\\n /**\\n * @notice Return all vault asset addresses in order\\n */\\n function getAllAssets() external view returns (address[] memory) {\\n address[] memory a = new address[](1);\\n a[0] = asset;\\n return a;\\n }\\n\\n /**\\n * @notice Return the number of strategies active on the Vault.\\n */\\n function getStrategyCount() external view returns (uint256) {\\n return allStrategies.length;\\n }\\n\\n /**\\n * @notice Return the array of all strategies\\n */\\n function getAllStrategies() external view returns (address[] memory) {\\n return allStrategies;\\n }\\n\\n /**\\n * @notice Returns whether the vault supports the asset\\n * @param _asset address of the asset\\n * @return true if supported\\n */\\n function isSupportedAsset(address _asset) external view returns (bool) {\\n return asset == _asset;\\n }\\n\\n function _min(uint256 a, uint256 b) internal pure returns (uint256) {\\n return a < b ? a : b;\\n }\\n\\n function _max(uint256 a, uint256 b) internal pure returns (uint256) {\\n return a > b ? a : b;\\n }\\n}\\n\",\"keccak256\":\"0xf5c7295587db70ea009853009724ded12486dfabf420d589ee1eec37e9a681eb\",\"license\":\"BUSL-1.1\"},\"contracts/vault/VaultInitializer.sol\":{\"content\":\"// SPDX-License-Identifier: BUSL-1.1\\npragma solidity ^0.8.0;\\n\\n/**\\n * @title OToken VaultInitializer contract\\n * @notice The Vault contract initializes the vault.\\n * @author Origin Protocol Inc\\n */\\n\\nimport \\\"./VaultStorage.sol\\\";\\n\\nabstract contract VaultInitializer is VaultStorage {\\n constructor(address _asset) VaultStorage(_asset) {}\\n\\n function initialize(address _oToken) external onlyGovernor initializer {\\n require(_oToken != address(0), \\\"oToken address is zero\\\");\\n\\n oToken = OUSD(_oToken);\\n\\n rebasePaused = false;\\n capitalPaused = true;\\n\\n // Initial Vault buffer of 0%\\n vaultBuffer = 0;\\n // Initial allocate threshold of 25,000 OUSD\\n autoAllocateThreshold = 25000e18;\\n // Threshold for rebasing\\n rebaseThreshold = 1000e18;\\n // Initialize all strategies\\n allStrategies = new address[](0);\\n // Start with drip duration: 7 days\\n dripDuration = 604800;\\n }\\n}\\n\",\"keccak256\":\"0xb0b99b4b9279ab87b6008fb9675b581cc009a51cc5f9c0fb838eda86b1bca02b\",\"license\":\"BUSL-1.1\"},\"contracts/vault/VaultStorage.sol\":{\"content\":\"// SPDX-License-Identifier: BUSL-1.1\\npragma solidity ^0.8.0;\\n\\n/**\\n * @title OToken VaultStorage contract\\n * @notice The VaultStorage contract defines the storage for the Vault contracts\\n * @author Origin Protocol Inc\\n */\\n\\nimport { IERC20 } from \\\"@openzeppelin/contracts/token/ERC20/IERC20.sol\\\";\\nimport { SafeERC20 } from \\\"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\\\";\\nimport { Address } from \\\"@openzeppelin/contracts/utils/Address.sol\\\";\\n\\nimport { IStrategy } from \\\"../interfaces/IStrategy.sol\\\";\\nimport { IERC20Metadata } from \\\"@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol\\\";\\nimport { Governable } from \\\"../governance/Governable.sol\\\";\\nimport { OUSD } from \\\"../token/OUSD.sol\\\";\\nimport { Initializable } from \\\"../utils/Initializable.sol\\\";\\nimport \\\"../utils/Helpers.sol\\\";\\n\\nabstract contract VaultStorage is Initializable, Governable {\\n using SafeERC20 for IERC20;\\n\\n event AssetAllocated(address _asset, address _strategy, uint256 _amount);\\n event StrategyApproved(address _addr);\\n event StrategyRemoved(address _addr);\\n event Mint(address _addr, uint256 _value);\\n event Redeem(address _addr, uint256 _value);\\n event CapitalPaused();\\n event CapitalUnpaused();\\n event DefaultStrategyUpdated(address _strategy);\\n event RebasePaused();\\n event RebaseUnpaused();\\n event VaultBufferUpdated(uint256 _vaultBuffer);\\n event AllocateThresholdUpdated(uint256 _threshold);\\n event RebaseThresholdUpdated(uint256 _threshold);\\n event StrategistUpdated(address _address);\\n event MaxSupplyDiffChanged(uint256 maxSupplyDiff);\\n event YieldDistribution(address _to, uint256 _yield, uint256 _fee);\\n event TrusteeFeeBpsChanged(uint256 _basis);\\n event TrusteeAddressChanged(address _address);\\n event StrategyAddedToMintWhitelist(address indexed strategy);\\n event StrategyRemovedFromMintWhitelist(address indexed strategy);\\n event RebasePerSecondMaxChanged(uint256 rebaseRatePerSecond);\\n event DripDurationChanged(uint256 dripDuration);\\n event WithdrawalRequested(\\n address indexed _withdrawer,\\n uint256 indexed _requestId,\\n uint256 _amount,\\n uint256 _queued\\n );\\n event WithdrawalClaimed(\\n address indexed _withdrawer,\\n uint256 indexed _requestId,\\n uint256 _amount\\n );\\n event WithdrawalClaimable(uint256 _claimable, uint256 _newClaimable);\\n event WithdrawalClaimDelayUpdated(uint256 _newDelay);\\n\\n // Since we are proxy, all state should be uninitalized.\\n // Since this storage contract does not have logic directly on it\\n // we should not be checking for to see if these variables can be constant.\\n // slither-disable-start uninitialized-state\\n // slither-disable-start constable-states\\n\\n /// @dev mapping of supported vault assets to their configuration\\n uint256 private _deprecated_assets;\\n /// @dev list of all assets supported by the vault.\\n address[] private _deprecated_allAssets;\\n\\n // Strategies approved for use by the Vault\\n struct Strategy {\\n bool isSupported;\\n uint256 _deprecated; // Deprecated storage slot\\n }\\n /// @dev mapping of strategy contracts to their configuration\\n mapping(address => Strategy) public strategies;\\n /// @dev list of all vault strategies\\n address[] internal allStrategies;\\n\\n /// @notice Address of the Oracle price provider contract\\n address private _deprecated_priceProvider;\\n /// @notice pause rebasing if true\\n bool public rebasePaused;\\n /// @notice pause operations that change the OToken supply.\\n /// eg mint, redeem, allocate, mint/burn for strategy\\n bool public capitalPaused;\\n /// @notice Redemption fee in basis points. eg 50 = 0.5%\\n uint256 private _deprecated_redeemFeeBps;\\n /// @notice Percentage of assets to keep in Vault to handle (most) withdrawals. 100% = 1e18.\\n uint256 public vaultBuffer;\\n /// @notice OToken mints over this amount automatically allocate funds. 18 decimals.\\n uint256 public autoAllocateThreshold;\\n /// @notice OToken mints over this amount automatically rebase. 18 decimals.\\n uint256 public rebaseThreshold;\\n\\n /// @dev Address of the OToken token. eg OUSD or OETH.\\n OUSD public oToken;\\n\\n /// @dev Address of the contract responsible for post rebase syncs with AMMs\\n address private _deprecated_rebaseHooksAddr = address(0);\\n\\n /// @dev Deprecated: Address of Uniswap\\n address private _deprecated_uniswapAddr = address(0);\\n\\n /// @notice Address of the Strategist\\n address public strategistAddr = address(0);\\n\\n /// @notice Mapping of asset address to the Strategy that they should automatically\\n // be allocated to\\n uint256 private _deprecated_assetDefaultStrategies;\\n\\n /// @notice Max difference between total supply and total value of assets. 18 decimals.\\n uint256 public maxSupplyDiff;\\n\\n /// @notice Trustee contract that can collect a percentage of yield\\n address public trusteeAddress;\\n\\n /// @notice Amount of yield collected in basis points. eg 2000 = 20%\\n uint256 public trusteeFeeBps;\\n\\n /// @dev Deprecated: Tokens that should be swapped for stablecoins\\n address[] private _deprecated_swapTokens;\\n\\n /// @notice Metapool strategy that is allowed to mint/burn OTokens without changing collateral\\n\\n address private _deprecated_ousdMetaStrategy;\\n\\n /// @notice How much OTokens are currently minted by the strategy\\n int256 private _deprecated_netOusdMintedForStrategy;\\n\\n /// @notice How much net total OTokens are allowed to be minted by all strategies\\n uint256 private _deprecated_netOusdMintForStrategyThreshold;\\n\\n uint256 private _deprecated_swapConfig;\\n\\n // List of strategies that can mint oTokens directly\\n // Used in OETHBaseVaultCore\\n mapping(address => bool) public isMintWhitelistedStrategy;\\n\\n /// @notice Address of the Dripper contract that streams harvested rewards to the Vault\\n /// @dev The vault is proxied so needs to be set with setDripper against the proxy contract.\\n address private _deprecated_dripper;\\n\\n /// Withdrawal Queue Storage /////\\n\\n struct WithdrawalQueueMetadata {\\n // cumulative total of all withdrawal requests included the ones that have already been claimed\\n uint128 queued;\\n // cumulative total of all the requests that can be claimed including the ones that have already been claimed\\n uint128 claimable;\\n // total of all the requests that have been claimed\\n uint128 claimed;\\n // index of the next withdrawal request starting at 0\\n uint128 nextWithdrawalIndex;\\n }\\n\\n /// @notice Global metadata for the withdrawal queue including:\\n /// queued - cumulative total of all withdrawal requests included the ones that have already been claimed\\n /// claimable - cumulative total of all the requests that can be claimed including the ones already claimed\\n /// claimed - total of all the requests that have been claimed\\n /// nextWithdrawalIndex - index of the next withdrawal request starting at 0\\n WithdrawalQueueMetadata public withdrawalQueueMetadata;\\n\\n struct WithdrawalRequest {\\n address withdrawer;\\n bool claimed;\\n uint40 timestamp; // timestamp of the withdrawal request\\n // Amount of oTokens to redeem. eg OETH\\n uint128 amount;\\n // cumulative total of all withdrawal requests including this one.\\n // this request can be claimed when this queued amount is less than or equal to the queue's claimable amount.\\n uint128 queued;\\n }\\n\\n /// @notice Mapping of withdrawal request indices to the user withdrawal request data\\n mapping(uint256 => WithdrawalRequest) public withdrawalRequests;\\n\\n /// @notice Sets a minimum delay that is required to elapse between\\n /// requesting async withdrawals and claiming the request.\\n /// When set to 0 async withdrawals are disabled.\\n uint256 public withdrawalClaimDelay;\\n\\n /// @notice Time in seconds that the vault last rebased yield.\\n uint64 public lastRebase;\\n\\n /// @notice Automatic rebase yield calculations. In seconds. Set to 0 or 1 to disable.\\n uint64 public dripDuration;\\n\\n /// @notice max rebase percentage per second\\n /// Can be used to set maximum yield of the protocol,\\n /// spreading out yield over time\\n uint64 public rebasePerSecondMax;\\n\\n /// @notice target rebase rate limit, based on past rates and funds available.\\n uint64 public rebasePerSecondTarget;\\n\\n uint256 internal constant MAX_REBASE = 0.02 ether;\\n uint256 internal constant MAX_REBASE_PER_SECOND =\\n uint256(0.05 ether) / 1 days;\\n\\n /// @notice Default strategy for asset\\n address public defaultStrategy;\\n\\n // For future use\\n uint256[42] private __gap;\\n\\n /// @notice Index of WETH asset in allAssets array\\n /// Legacy OETHVaultCore code, relocated here for vault consistency.\\n uint256 private _deprecated_wethAssetIndex;\\n\\n /// @dev Address of the asset (eg. WETH or USDC)\\n address public immutable asset;\\n uint8 internal immutable assetDecimals;\\n\\n // slither-disable-end constable-states\\n // slither-disable-end uninitialized-state\\n\\n constructor(address _asset) {\\n uint8 _decimals = IERC20Metadata(_asset).decimals();\\n require(_decimals <= 18, \\\"invalid asset decimals\\\");\\n asset = _asset;\\n assetDecimals = _decimals;\\n }\\n\\n /// @notice Deprecated: use `oToken()` instead.\\n function oUSD() external view returns (OUSD) {\\n return oToken;\\n }\\n}\\n\",\"keccak256\":\"0xcca0e0ebbbbda50b23fba7aea96a1e34f6a9d58c8f2e86e84b0911d4a9e5d418\",\"license\":\"BUSL-1.1\"}},\"version\":1}", + "bytecode": "0x60c0604052603d80546001600160a01b0319908116909155603e805482169055603f8054909116905534801561003457600080fd5b5060405161544638038061544683398101604081905261005391610133565b808080806000816001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015610097573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100bb9190610163565b905060128160ff1611156101155760405162461bcd60e51b815260206004820152601660248201527f696e76616c696420617373657420646563696d616c7300000000000000000000604482015260640160405180910390fd5b6001600160a01b0390911660805260ff1660a0525061018692505050565b60006020828403121561014557600080fd5b81516001600160a01b038116811461015c57600080fd5b9392505050565b60006020828403121561017557600080fd5b815160ff8116811461015c57600080fd5b60805160a0516151e561026160003960008181610fa60152818161178b0152818161205401528181612c6f015281816131560152818161376201528181613806015281816140ae01526144630152600081816105870152818161089401528181610c4e01528181610fe4015281816112d301528181611438015281816117570152818161189401528181612c3b015281816132520152818161338001528181613b0501528181613dda01528181613f110152818161411901528181614164015281816141cc0152818161448a01526148c501526151e56000f3fe608060405234801561001057600080fd5b50600436106104075760003560e01c80636217f3ea11610220578063ae69f3cb11610130578063c5f00841116100b8578063d4c3eea011610087578063d4c3eea0146109e6578063e6cc5432146109ee578063ea33b8e414610a02578063f844443614610a0a578063fac5bb9b14610a1d57600080fd5b8063c5f00841146109bb578063c7af3352146109c3578063c9919112146109cb578063d38bfff4146109d357600080fd5b8063b890ebf6116100ff578063b890ebf61461096b578063b9b17f9f1461097e578063bb7a632e14610986578063c3b28864146109a0578063c4d66de8146109a857600080fd5b8063ae69f3cb1461092a578063af14052c1461093d578063b2c9336d14610945578063b4925a201461095857600080fd5b806394828ffd116101b35780639fa1826e116101825780639fa1826e146108ec578063a0712d68146108f5578063a0aead4d14610908578063ab80dafb1461090f578063abaa99161461092257600080fd5b806394828ffd1461086957806395b166bc146108715780639be918e6146108845780639ee679e8146108c457600080fd5b8063840c4c7a116101ef578063840c4c7a146107925780638e510b52146107a55780638ec489a2146107ae578063937b2581146107c157600080fd5b80636217f3ea14610746578063663e64ce14610759578063773540b31461076c57806378f353a11461077f57600080fd5b806339ebf8231161031b578063527e83a8116102ae57806357bee9441161027d57806357bee944146106f45780635802a17214610707578063597c8910146107185780635d36b1901461072b5780635f5152261461073357600080fd5b8063527e83a8146106aa57806352d38e5d146106c457806353ca9f24146106cd578063570d8e1d146106e157600080fd5b806345e4213b116102ea57806345e4213b1461063b57806348e30f541461064457806349c1d54d146106655780634d5f46291461067857600080fd5b806339ebf823146105a95780633b8ae397146105ed5780633dbc911f146106005780634530820a1461060857600080fd5b80631a32aad61161039e5780632acada4d1161036d5780632acada4d146104f35780632da845a81461050857806331e19cfa1461051b578063362bd1a31461052357806338d52e0f1461058257600080fd5b80631a32aad6146104ad5780631cfbe7bc146104c05780631edfe3da146104d3578063207134b0146104ea57600080fd5b80631072cbea116103da5780631072cbea14610461578063156e29f614610474578063175188e8146104875780631816dd4a1461049a57600080fd5b80630493a0fa1461040c57806309f49bf5146104215780630acbda75146104295780630c340a241461043c575b600080fd5b61041f61041a366004614b73565b610a30565b005b61041f610ae6565b61041f610437366004614b73565b610b56565b610444610c01565b6040516001600160a01b0390911681526020015b60405180910390f35b61041f61046f366004614ba8565b610c1e565b61041f610482366004614bd2565b610cdc565b61041f610495366004614c05565b610d4f565b61041f6104a8366004614c05565b6110e2565b603c54610444906001600160a01b031681565b61041f6104ce366004614b73565b6111e9565b6104dc60395481565b604051908152602001610458565b6104dc60435481565b6104fb6112ab565b6040516104589190614c20565b61041f610516366004614c05565b611322565b6036546104dc565b604b54604c5461054f916001600160801b0380821692600160801b928390048216928183169291041684565b604080516001600160801b0395861681529385166020850152918416918301919091529091166060820152608001610458565b6104447f000000000000000000000000000000000000000000000000000000000000000081565b6105d66105b7366004614c05565b6035602052600090815260409020805460019091015460ff9091169082565b604080519215158352602083019190915201610458565b61041f6105fb366004614c05565b611394565b61041f6115a7565b61062b610616366004614c05565b60496020526000908152604090205460ff1681565b6040519015158152602001610458565b6104dc604e5481565b610657610652366004614cb0565b61161d565b604051610458929190614cf1565b604254610444906001600160a01b031681565b604f5461069290600160c01b90046001600160401b031681565b6040516001600160401b039091168152602001610458565b604f5461069290600160801b90046001600160401b031681565b6104dc603b5481565b60375461062b90600160a01b900460ff1681565b603f54610444906001600160a01b031681565b61041f610702366004614c05565b6117c2565b603c546001600160a01b0316610444565b61041f610726366004614c05565b611975565b61041f6119b9565b6104dc610741366004614c05565b611a5f565b61041f610754366004614b73565b611a70565b61041f610767366004614b73565b611bf4565b61041f61077a366004614c05565b611c4d565b604f54610692906001600160401b031681565b61041f6107a0366004614d3e565b611cbf565b6104dc60415481565b61041f6107bc366004614b73565b611d46565b6108226107cf366004614b73565b604d60205260009081526040902080546001909101546001600160a01b03821691600160a01b810460ff1691600160a81b90910464ffffffffff16906001600160801b0380821691600160801b90041685565b604080516001600160a01b039096168652931515602086015264ffffffffff909216928401929092526001600160801b03918216606084015216608082015260a001610458565b61041f611dfb565b61041f61087f366004614c05565b611e6b565b61062b610892366004614c05565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0390811691161490565b6108d76108d2366004614b73565b611f32565b60408051928352602083019190915201610458565b6104dc603a5481565b61041f610903366004614b73565b612273565b60016104dc565b61041f61091d366004614b73565b6122e4565b61041f612433565b61041f610938366004614d3e565b6124ab565b61041f612527565b61041f610953366004614b73565b61256d565b61041f610966366004614b73565b6125c6565b61041f610979366004614b73565b6126e4565b61041f61273d565b604f5461069290600160401b90046001600160401b031681565b6104fb612745565b61041f6109b6366004614c05565b6127a7565b61041f61295f565b61062b6129d5565b61041f612a06565b61041f6109e1366004614c05565b612a46565b6104dc612aea565b60375461062b90600160a81b900460ff1681565b6104dc612af4565b6104dc610a18366004614b73565b612b83565b605054610444906001600160a01b031681565b603f546001600160a01b0316331480610a4c5750610a4c6129d5565b610a715760405162461bcd60e51b8152600401610a6890614dc2565b60405180910390fd5b610a79612c9e565b50610a8381613038565b604f80546001600160401b0392909216600160401b0267ffffffffffffffff60401b199092169190911790556040518181527f406e15fbca1d8ded2dbb06765fea3a54f18395c54125a4c9916dd00ea14ee15e906020015b60405180910390a150565b603f546001600160a01b0316331480610b025750610b026129d5565b610b1e5760405162461bcd60e51b8152600401610a6890614dc2565b6037805460ff60a01b191690556040517fbc044409505c95b6b851433df96e1beae715c909d8e7c1d6d7ab783300d4e3b990600090a1565b610b5e6129d5565b610b7a5760405162461bcd60e51b8152600401610a6890614e0a565b611388811115610bcc5760405162461bcd60e51b815260206004820152601760248201527f62617369732063616e6e6f7420657863656564203530250000000000000000006044820152606401610a68565b60438190556040518181527f56287a45051933ea374811b3d5d165033047be5572cac676f7c28b8be4f746c790602001610adb565b6000610c196000805160206151908339815191525490565b905090565b610c266129d5565b610c425760405162461bcd60e51b8152600401610a6890614e0a565b816001600160a01b03167f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031603610cbc5760405162461bcd60e51b815260206004820152601660248201527513db9b1e481d5b9cdd5c1c1bdc9d195908185cdcd95d60521b6044820152606401610a68565b610cd8610cc7610c01565b6001600160a01b03841690836130a4565b5050565b603754600160a81b900460ff1615610d065760405162461bcd60e51b8152600401610a6890614e41565b60008051602061517083398151915280546001198101610d385760405162461bcd60e51b8152600401610a6890614e69565b60028255610d45846130fa565b5060019055505050565b610d576129d5565b610d735760405162461bcd60e51b8152600401610a6890614e0a565b6001600160a01b03811660009081526035602052604090205460ff16610dab5760405162461bcd60e51b8152600401610a6890614e91565b6050546001600160a01b03808316911603610e085760405162461bcd60e51b815260206004820152601d60248201527f53747261746567792069732064656661756c7420666f722061737365740000006044820152606401610a68565b6036548060005b82811015610e5e57836001600160a01b031660368281548110610e3457610e34614ec0565b6000918252602090912001546001600160a01b031603610e5657809150610e5e565b600101610e0f565b50818110156110dd576036610e74600184614eec565b81548110610e8457610e84614ec0565b600091825260209091200154603680546001600160a01b039092169183908110610eb057610eb0614ec0565b9060005260206000200160006101000a8154816001600160a01b0302191690836001600160a01b031602179055506036805480610eef57610eef614eff565b60008281526020808220830160001990810180546001600160a01b03191690559092019092556001600160a01b038516808352603582526040808420805460ff1990811690915560499093528084208054909316909255815163429c145b60e11b815291518693919263853828b692600480830193919282900301818387803b158015610f7b57600080fd5b505af1158015610f8f573d6000803e3d6000fd5b5060009250610fcd91506509184e72a000905060ff7f0000000000000000000000000000000000000000000000000000000000000000166012613296565b604051632fa8a91360e11b81526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000811660048301529192508291841690635f51522690602401602060405180830381865afa158015611038573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061105c9190614f15565b1061109e5760405162461bcd60e51b81526020600482015260126024820152715374726174656779206861732066756e647360701b6044820152606401610a68565b6040516001600160a01b03861681527f09a1db4b80c32706328728508c941a6b954f31eb5affd32f236c1fd405f8fea49060200160405180910390a150505b505050565b6110ea6129d5565b6111065760405162461bcd60e51b8152600401610a6890614e0a565b6001600160a01b03811660009081526035602052604090205460ff1661113e5760405162461bcd60e51b8152600401610a6890614e91565b6001600160a01b03811660009081526049602052604090205460ff161561119d5760405162461bcd60e51b8152602060048201526013602482015272105b1c9958591e481dda1a5d195b1a5cdd1959606a1b6044820152606401610a68565b6001600160a01b038116600081815260496020526040808220805460ff19166001179055517f47c8c96a5942f094264111c5fe7f6a4fe86efe63254a6fa7afa5fc84f07d58e89190a250565b6111f16129d5565b61120d5760405162461bcd60e51b8152600401610a6890614e0a565b80158061122a5750610258811015801561122a57506213c6808111155b6112765760405162461bcd60e51b815260206004820152601a60248201527f496e76616c696420636c61696d2064656c617920706572696f640000000000006044820152606401610a68565b604e8190556040518181527fc59f5e32049abab44ddea11021f5abb89422a2f550837afcf25df9fc8d0db6b090602001610adb565b60408051600180825281830190925260609160009190602080830190803683370190505090507f00000000000000000000000000000000000000000000000000000000000000008160008151811061130557611305614ec0565b6001600160a01b0390921660209283029190910190910152919050565b61132a6129d5565b6113465760405162461bcd60e51b8152600401610a6890614e0a565b604280546001600160a01b0319166001600160a01b0383169081179091556040519081527f1e4af5ac389e8cde1bdaa6830881b6c987c62a45cfb3b33d27d805cde3b5775090602001610adb565b61139c6129d5565b6113b85760405162461bcd60e51b8152600401610a6890614e0a565b6001600160a01b03811660009081526035602052604090205460ff16156114215760405162461bcd60e51b815260206004820152601960248201527f537472617465677920616c726561647920617070726f766564000000000000006044820152606401610a68565b60405163551c457b60e11b81526001600160a01b037f00000000000000000000000000000000000000000000000000000000000000008116600483015282169063aa388af690602401602060405180830381865afa158015611487573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906114ab9190614f44565b6114f75760405162461bcd60e51b815260206004820152601f60248201527f4173736574206e6f7420737570706f72746564206279205374726174656779006044820152606401610a68565b6040805180820182526001808252600060208084018281526001600160a01b038716808452603583528684209551865460ff19169015151786559051948401949094556036805493840181559091527f4a11f94e20a93c79f6ec743a1954ec4fc2c08429ae2122118bf234b2185c81b890910180546001600160a01b0319168317905591519081527f960dd94cbb79169f09a4e445d58b895df2d9bffa5b31055d0932d801724a20d19101610adb565b603f546001600160a01b03163314806115c357506115c36129d5565b6115df5760405162461bcd60e51b8152600401610a6890614dc2565b6037805460ff60a81b1916600160a81b1790556040517f71f0e5b62f846a22e0b4d159e516e62fa9c2b8eb570be15f83e67d98a2ee51e090600090a1565b603754606090600090600160a81b900460ff161561164d5760405162461bcd60e51b8152600401610a6890614e41565b6000805160206151708339815191528054600119810161167f5760405162461bcd60e51b8152600401610a6890614e69565b6002825561168b6132fa565b50846001600160401b038111156116a4576116a4614f2e565b6040519080825280602002602001820160405280156116cd578160200160208202803683370190505b50935060005b85811015611749576116fc8787838181106116f0576116f0614ec0565b905060200201356134d6565b85828151811061170e5761170e614ec0565b60200260200101818152505084818151811061172c5761172c614ec0565b60200260200101518461173f9190614f66565b93506001016116d3565b5061177e6001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633856130a4565b6117b56117b084601260ff7f000000000000000000000000000000000000000000000000000000000000000016613296565b613837565b6001825550509250929050565b603f546001600160a01b03163314806117de57506117de6129d5565b6117fa5760405162461bcd60e51b8152600401610a6890614dc2565b6040516001600160a01b03821681527f45a9e715f8779ebeab88d3e8d081fb8b484b7730857e7c4478a95a5b1d8f79489060200160405180910390a16001600160a01b03811615611953576001600160a01b03811660009081526035602052604090205460ff1661187d5760405162461bcd60e51b8152600401610a6890614e91565b60405163551c457b60e11b81526001600160a01b037f00000000000000000000000000000000000000000000000000000000000000008116600483015282169063aa388af690602401602060405180830381865afa1580156118e3573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906119079190614f44565b6119535760405162461bcd60e51b815260206004820152601f60248201527f4173736574206e6f7420737570706f72746564206279205374726174656779006044820152606401610a68565b605080546001600160a01b0319166001600160a01b0392909216919091179055565b603f546001600160a01b031633148061199157506119916129d5565b6119ad5760405162461bcd60e51b8152600401610a6890614dc2565b6119b6816139da565b50565b7f44c4d30b2eaad5130ad70c3ba6972730566f3e6359ab83e800d905c61b1c51db546001600160a01b0316336001600160a01b031614611a545760405162461bcd60e51b815260206004820152603060248201527f4f6e6c79207468652070656e64696e6720476f7665726e6f722063616e20636f60448201526f6d706c6574652074686520636c61696d60801b6064820152608401610a68565b611a5d33613aa2565b565b6000611a6a82613b01565b92915050565b603754600160a81b900460ff1615611a9a5760405162461bcd60e51b8152600401610a6890614e41565b3360009081526035602052604090205460ff161515600114611af55760405162461bcd60e51b8152602060048201526014602482015273556e737570706f7274656420737472617465677960601b6044820152606401610a68565b3360009081526049602052604090205460ff161515600114611b545760405162461bcd60e51b81526020600482015260186024820152774e6f742077686974656c697374656420737472617465677960401b6044820152606401610a68565b7f222838db2794d11532d940e8dec38ae307ed0b63cd97c233322e221f998767a63382604051611b85929190614f79565b60405180910390a1603c54604051632770a7eb60e21b81526001600160a01b0390911690639dc29fac90611bbf9033908590600401614f79565b600060405180830381600087803b158015611bd957600080fd5b505af1158015611bed573d6000803e3d6000fd5b5050505050565b611bfc6129d5565b611c185760405162461bcd60e51b8152600401610a6890614e0a565b60418190556040518181527f95201f9c21f26877223b1ff4073936a6484c35495649e60e55730497aeb60d9390602001610adb565b611c556129d5565b611c715760405162461bcd60e51b8152600401610a6890614e0a565b603f80546001600160a01b0319166001600160a01b0383169081179091556040519081527f869e0abd13cc3a975de7b93be3df1cb2255c802b1cead85963cc79d99f131bee90602001610adb565b603f546001600160a01b0316331480611cdb5750611cdb6129d5565b611cf75760405162461bcd60e51b8152600401610a6890614dc2565b60008051602061517083398151915280546001198101611d295760405162461bcd60e51b8152600401610a6890614e69565b60028255611d3a8787878787613d63565b50600190555050505050565b603f546001600160a01b0316331480611d625750611d626129d5565b611d7e5760405162461bcd60e51b8152600401610a6890614dc2565b670de0b6b3a7640000811115611dc65760405162461bcd60e51b815260206004820152600d60248201526c496e76616c69642076616c756560981b6044820152606401610a68565b60398190556040518181527f41ecb23a0e7865b25f38c268b7c3012220d822929e9edff07326e89d5bb822b590602001610adb565b603f546001600160a01b0316331480611e175750611e176129d5565b611e335760405162461bcd60e51b8152600401610a6890614dc2565b6037805460ff60a81b191690556040517f891ebab18da80ebeeea06b1b1cede098329c4c008906a98370c2ac7a80b571cb90600090a1565b611e736129d5565b611e8f5760405162461bcd60e51b8152600401610a6890614e0a565b6001600160a01b03811660009081526049602052604090205460ff16611ee95760405162461bcd60e51b815260206004820152600f60248201526e139bdd081dda1a5d195b1a5cdd1959608a1b6044820152606401610a68565b6001600160a01b038116600081815260496020526040808220805460ff19169055517f0ec40967a61509853550658e51d0e4297f7cba244fe4adc8ba18b5631ac20e2f9190a250565b6037546000908190600160a81b900460ff1615611f615760405162461bcd60e51b8152600401610a6890614e41565b60008051602061517083398151915280546001198101611f935760405162461bcd60e51b8152600401610a6890614e69565b6002825560008511611fe75760405162461bcd60e51b815260206004820152601d60248201527f416d6f756e74206d7573742062652067726561746572207468616e20300000006044820152606401610a68565b6000604e54116120395760405162461bcd60e51b815260206004820152601d60248201527f4173796e63207769746864726177616c73206e6f7420656e61626c65640000006044820152606401610a68565b604c54600160801b90046001600160801b0316935061207d857f000000000000000000000000000000000000000000000000000000000000000060ff166012613296565b604b5461209391906001600160801b0316614f66565b92506120a86120a3856001614f66565b613fa2565b604c80546001600160801b03928316600160801b0292169190911790556120ce83613fa2565b604b80546001600160801b0319166001600160801b03929092169190911790556040805160a081018252338152600060208201524264ffffffffff16918101919091526060810161211e87613fa2565b6001600160801b0316815260200161213585613fa2565b6001600160801b039081169091526000868152604d602090815260409182902084518154928601518685015164ffffffffff16600160a81b0264ffffffffff60a81b19911515600160a01b026001600160a81b03199095166001600160a01b0393841617949094171692909217815560608501516080909501518416600160801b029490931693909317600190920191909155603c549051632770a7eb60e21b8152911690639dc29fac906121f09033908990600401614f79565b600060405180830381600087803b15801561220a57600080fd5b505af115801561221e573d6000803e3d6000fd5b5050505061222b85613837565b6040805186815260208101859052859133917f38e3d972947cfef94205163d483d6287ef27eb312e20cb8e0b13a49989db232e910160405180910390a3600182555050915091565b603754600160a81b900460ff161561229d5760405162461bcd60e51b8152600401610a6890614e41565b600080516020615170833981519152805460011981016122cf5760405162461bcd60e51b8152600401610a6890614e69565b600282556122dc836130fa565b506001905550565b603754600160a81b900460ff161561230e5760405162461bcd60e51b8152600401610a6890614e41565b3360009081526035602052604090205460ff1615156001146123695760405162461bcd60e51b8152602060048201526014602482015273556e737570706f7274656420737472617465677960601b6044820152606401610a68565b3360009081526049602052604090205460ff1615156001146123c85760405162461bcd60e51b81526020600482015260186024820152774e6f742077686974656c697374656420737472617465677960401b6044820152606401610a68565b7f0f6798a560793a54c3bcfe86a93cde1e73087d944c0ea20544137d412139688533826040516123f9929190614f79565b60405180910390a1603c546040516340c10f1960e01b81526001600160a01b03909116906340c10f1990611bbf9033908590600401614f79565b603754600160a81b900460ff161561245d5760405162461bcd60e51b8152600401610a6890614e41565b6000805160206151708339815191528054600119810161248f5760405162461bcd60e51b8152600401610a6890614e69565b6002825561249b6132fa565b506124a461400b565b5060019055565b603f546001600160a01b03163314806124c757506124c76129d5565b6124e35760405162461bcd60e51b8152600401610a6890614dc2565b600080516020615170833981519152805460011981016125155760405162461bcd60e51b8152600401610a6890614e69565b60028255611d3a308888888888614234565b600080516020615170833981519152805460011981016125595760405162461bcd60e51b8152600401610a6890614e69565b60028255612565612c9e565b505060019055565b6125756129d5565b6125915760405162461bcd60e51b8152600401610a6890614e0a565b603a8190556040518181527f2ec5fb5a3d2703edc461252d92ccd2799c3c74f01d97212b20388207fa17ae4590602001610adb565b603f546001600160a01b03163314806125e257506125e26129d5565b6125fe5760405162461bcd60e51b8152600401610a6890614dc2565b612606612c9e565b5060006301e13380612619606484614f92565b6126239190614f92565b90506126396201518066b1a2bc2ec50000614f92565b8111156126785760405162461bcd60e51b815260206004820152600d60248201526c0a4c2e8ca40e8dede40d0d2ced609b1b6044820152606401610a68565b61268181613038565b604f80546001600160401b0392909216600160801b0267ffffffffffffffff60801b199092169190911790556040518181527fef46f143af5fead0010484fe7d6ec2e2972420faa76157f5a6075aa72e614cb59060200160405180910390a15050565b6126ec6129d5565b6127085760405162461bcd60e51b8152600401610a6890614e0a565b603b8190556040518181527f39367850377ac04920a9a670f2180e7a94d83b15ad302e59875ec58fd10bd37d90602001610adb565b6119b66132fa565b6060603680548060200260200160405190810160405280929190818152602001828054801561279d57602002820191906000526020600020905b81546001600160a01b0316815260019091019060200180831161277f575b5050505050905090565b6127af6129d5565b6127cb5760405162461bcd60e51b8152600401610a6890614e0a565b600054610100900460ff16806127e4575060005460ff16155b6128475760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b6064820152608401610a68565b600054610100900460ff16158015612869576000805461ffff19166101011790555b6001600160a01b0382166128b85760405162461bcd60e51b81526020600482015260166024820152756f546f6b656e2061646472657373206973207a65726f60501b6044820152606401610a68565b603c80546001600160a01b0384166001600160a01b03199091161790556037805461ffff60a01b1916600160a81b1790556000603981905569054b40b1f852bda00000603a55683635c9adc5dea00000603b55604080519182526020820190819052905161292891603691614b02565b50604f805467ffffffffffffffff60401b19166a093a8000000000000000001790558015610cd8576000805461ff00191690555050565b603f546001600160a01b031633148061297b575061297b6129d5565b6129975760405162461bcd60e51b8152600401610a6890614dc2565b6037805460ff60a01b1916600160a01b1790556040517f8cff26a5985614b3d30629cc4ab83824bf115aec971b718d8f2f99562032e97290600090a1565b60006129ed6000805160206151908339815191525490565b6001600160a01b0316336001600160a01b031614905090565b603f546001600160a01b0316331480612a225750612a226129d5565b612a3e5760405162461bcd60e51b8152600401610a6890614dc2565b611a5d6143c5565b612a4e6129d5565b612a6a5760405162461bcd60e51b8152600401610a6890614e0a565b612a92817f44c4d30b2eaad5130ad70c3ba6972730566f3e6359ab83e800d905c61b1c51db55565b806001600160a01b0316612ab26000805160206151908339815191525490565b6001600160a01b03167fa39cc5eb22d0f34d8beaefee8a3f17cc229c1a1d1ef87a5ad47313487b1c4f0d60405160405180910390a350565b6000610c1961445a565b6000612b7d603c60009054906101000a90046001600160a01b03166001600160a01b03166318160ddd6040518163ffffffff1660e01b8152600401602060405180830381865afa158015612b4c573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612b709190614f15565b612b7861445a565b6144ae565b50919050565b603754600090600160a81b900460ff1615612bb05760405162461bcd60e51b8152600401610a6890614e41565b60008051602061517083398151915280546001198101612be25760405162461bcd60e51b8152600401610a6890614e69565b60028255604b546000858152604d60205260409020600101546001600160801b03600160801b92839004811692909104161115612c2357612c216132fa565b505b612c2c846134d6565b9250612c626001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633856130a4565b612c946117b084601260ff7f000000000000000000000000000000000000000000000000000000000000000016613296565b5060019055919050565b603754600090600160a01b900460ff1615612ced5760405162461bcd60e51b815260206004820152600f60248201526e149958985cda5b99c81c185d5cd959608a1b6044820152606401610a68565b603c54604080516318160ddd60e01b815290516000926001600160a01b0316916318160ddd9160048083019260209291908290030181865afa158015612d37573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612d5b9190614f15565b90506000612d6761445a565b905081600003612d7a5791506130359050565b600080612d8784846144ae565b90925090506000612d988386614f66565b90508481111580612da857508381115b15612db7575091949350505050565b612dc8826001600160401b03614681565b604f805477ffffffffffffffffffffffffffffffff000000000000000016600160c01b6001600160401b039384160267ffffffffffffffff19161742929092169190911790556042546001600160a01b031660008115612f045761271060435486612e339190614fb4565b612e3d9190614f92565b90508015612f0457848110612e9f5760405162461bcd60e51b815260206004820152602260248201527f466565206d757374206e6f742062652067726561746572207468616e207969656044820152611b1960f21b6064820152608401610a68565b603c546040516340c10f1960e01b81526001600160a01b03909116906340c10f1990612ed19085908590600401614f79565b600060405180830381600087803b158015612eeb57600080fd5b505af1158015612eff573d6000803e3d6000fd5b505050505b604080516001600160a01b0384168152602081018790529081018290527f09516ecf4a8a86e59780a9befc6dee948bc9e60a36e3be68d31ea817ee8d2c809060600160405180910390a1603c60009054906101000a90046001600160a01b03166001600160a01b03166318160ddd6040518163ffffffff1660e01b8152600401602060405180830381865afa158015612fa1573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612fc59190614f15565b83111561302b57603c546040516339a7919f60e01b8152600481018590526001600160a01b03909116906339a7919f90602401600060405180830381600087803b15801561301257600080fd5b505af1158015613026573d6000803e3d6000fd5b505050505b5093955050505050505b90565b60006001600160401b038211156130a05760405162461bcd60e51b815260206004820152602660248201527f53616665436173743a2076616c756520646f65736e27742066697420696e203660448201526534206269747360d01b6064820152608401610a68565b5090565b6110dd8363a9059cbb60e01b84846040516024016130c3929190614f79565b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b031990931692909217909152614697565b6000811161314a5760405162461bcd60e51b815260206004820152601d60248201527f416d6f756e74206d7573742062652067726561746572207468616e20300000006044820152606401610a68565b600061317b82601260ff7f000000000000000000000000000000000000000000000000000000000000000016613296565b90507f0f6798a560793a54c3bcfe86a93cde1e73087d944c0ea20544137d412139688533826040516131ae929190614f79565b60405180910390a1603754600160a01b900460ff161580156131d25750603b548110155b156131e1576131df612c9e565b505b603c546040516340c10f1960e01b81526001600160a01b03909116906340c10f19906132139033908590600401614f79565b600060405180830381600087803b15801561322d57600080fd5b505af1158015613241573d6000803e3d6000fd5b5061327c9250506001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169050333085614769565b6132846132fa565b50603a548110610cd857610cd861400b565b6000818311156132c6576132bf6132ad8385614eec565b6132b890600a6150b2565b85906147a7565b93506132f0565b818310156132f0576132ed6132db8484614eec565b6132e690600a6150b2565b85906147b3565b93505b50825b9392505050565b60408051608081018252604b546001600160801b03808216808452600160801b92839004821660208501819052604c548084169686019690965292909404166060830152600092839161334c916150be565b6001600160801b03169050806000036133685760009250505090565b6040516370a0823160e01b81523060048201526000907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906370a0823190602401602060405180830381865afa1580156133cf573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906133f39190614f15565b905060008360400151846020015161340b91906150be565b6001600160801b0316905080821161342857600094505050505090565b60006134348284614eec565b90508084106134435780613445565b835b955060008686602001516001600160801b03166134629190614f66565b905061346d81613fa2565b604b80546001600160801b03928316600160801b0292169190911790556040517fee79a0c43d3993055690b54e074b5153e8bae8d1a872b656dedb64aa8f463333906134c59083908a90918252602082015260400190565b60405180910390a150505050505090565b600080604e54116135295760405162461bcd60e51b815260206004820152601d60248201527f4173796e63207769746864726177616c73206e6f7420656e61626c65640000006044820152606401610a68565b6000828152604d6020908152604091829020825160a08101845281546001600160a01b038116825260ff600160a01b82041615158285015264ffffffffff600160a81b90910481168286019081526001909301546001600160801b03808216606080860191909152600160801b92839004821660808087019190915288519081018952604b548084168252849004831697810197909752604c54808316988801989098529190960490951694840194909452604e549151909342926135f092909116614f66565b11156136345760405162461bcd60e51b815260206004820152601360248201527210db185a5b4819195b185e481b9bdd081b595d606a1b6044820152606401610a68565b80602001516001600160801b031682608001516001600160801b0316111561369e5760405162461bcd60e51b815260206004820152601760248201527f51756575652070656e64696e67206c69717569646974790000000000000000006044820152606401610a68565b81516001600160a01b031633146136e75760405162461bcd60e51b815260206004820152600d60248201526c2737ba103932b8bab2b9ba32b960991b6044820152606401610a68565b60208201511561372b5760405162461bcd60e51b815260206004820152600f60248201526e105b1c9958591e4818db185a5b5959608a1b6044820152606401610a68565b6000848152604d60205260409020805460ff60a01b1916600160a01b179055606082015161378b906120a3906001600160801b03167f000000000000000000000000000000000000000000000000000000000000000060ff166012613296565b816040015161379a91906150dd565b604c80546001600160801b0319166001600160801b03928316179055606083015160405191168152849033907f2d43eb174787155132b52ddb6b346e2dca99302eac3df4466dbeff953d3c84d19060200160405180910390a361382f82606001516001600160801b03167f000000000000000000000000000000000000000000000000000000000000000060ff166012613296565b949350505050565b6000603b5482101580156138555750603754600160a01b900460ff16155b1561386957613862612c9e565b9050613874565b61387161445a565b90505b60415415610cd857600081116138cc5760405162461bcd60e51b815260206004820152601d60248201527f546f6f206d616e79206f75747374616e64696e672072657175657374730000006044820152606401610a68565b600061394f82603c60009054906101000a90046001600160a01b03166001600160a01b03166318160ddd6040518163ffffffff1660e01b8152600401602060405180830381865afa158015613925573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906139499190614f15565b906147bf565b9050604154670de0b6b3a7640000821161397a5761397582670de0b6b3a7640000614eec565b61398c565b61398c670de0b6b3a764000083614eec565b11156110dd5760405162461bcd60e51b815260206004820152601e60248201527f4261636b696e6720737570706c79206c6971756964697479206572726f7200006044820152606401610a68565b6001600160a01b03811660009081526035602052604090205460ff16613a425760405162461bcd60e51b815260206004820152601960248201527f5374726174656779206973206e6f7420737570706f72746564000000000000006044820152606401610a68565b6000819050806001600160a01b031663853828b66040518163ffffffff1660e01b8152600401600060405180830381600087803b158015613a8257600080fd5b505af1158015613a96573d6000803e3d6000fd5b505050506110dd6132fa565b6001600160a01b038116613af85760405162461bcd60e51b815260206004820152601a60248201527f4e657720476f7665726e6f7220697320616464726573732830290000000000006044820152606401610a68565b6119b6816147e0565b60007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316826001600160a01b031614613b4457506000919050565b6040516370a0823160e01b815230600482015282906001600160a01b038216906370a0823190602401602060405180830381865afa158015613b8a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613bae9190614f15565b60365490925060005b81811015613cd357600060368281548110613bd457613bd4614ec0565b60009182526020909120015460405163551c457b60e11b81526001600160a01b0388811660048301529091169150819063aa388af690602401602060405180830381865afa158015613c2a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613c4e9190614f44565b15613cca57604051632fa8a91360e11b81526001600160a01b038781166004830152821690635f51522690602401602060405180830381865afa158015613c99573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613cbd9190614f15565b613cc79086614f66565b94505b50600101613bb7565b5060408051608081018252604b546001600160801b03808216808452600160801b9283900482166020850152604c54808316958501869052929092041660608301529091613d219086614f66565b1015613d3257506000949350505050565b805160408201516001600160801b0391821691613d50911686614f66565b613d5a9190614eec565b95945050505050565b6001600160a01b03851660009081526035602052604090205460ff16613dc15760405162461bcd60e51b8152602060048201526013602482015272496e76616c696420746f20537472617465677960681b6044820152606401610a68565b600183148015613dd15750600181145b8015613e3557507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031684846000818110613e1557613e15614ec0565b9050602002016020810190613e2a9190614c05565b6001600160a01b0316145b613e815760405162461bcd60e51b815260206004820152601760248201527f4f6e6c7920617373657420697320737570706f727465640000000000000000006044820152606401610a68565b613e89614847565b82826000818110613e9c57613e9c614ec0565b905060200201351115613ef15760405162461bcd60e51b815260206004820152601b60248201527f4e6f7420656e6f7567682061737365747320617661696c61626c6500000000006044820152606401610a68565b613f488583836000818110613f0857613f08614ec0565b905060200201357f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166130a49092919063ffffffff16565b846001600160a01b031663de5f62686040518163ffffffff1660e01b8152600401600060405180830381600087803b158015613f8357600080fd5b505af1158015613f97573d6000803e3d6000fd5b505050505050505050565b60006001600160801b038211156130a05760405162461bcd60e51b815260206004820152602760248201527f53616665436173743a2076616c756520646f65736e27742066697420696e20316044820152663238206269747360c81b6064820152608401610a68565b6050546001600160a01b03168061401f5750565b6000614029614847565b905080600003614037575050565b603c54604080516318160ddd60e01b815290516000926001600160a01b0316916318160ddd9160048083019260209291908290030181865afa158015614081573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906140a59190614f15565b905060006140ee7f000000000000000000000000000000000000000000000000000000000000000060ff1660126140e76039548661495590919063ffffffff16565b9190613296565b90508083116140fd5750505050565b60006141098285614eec565b9050846141406001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001682846130a4565b6040516311f9fbc960e21b81526001600160a01b038216906347e7ef249061418e907f0000000000000000000000000000000000000000000000000000000000000000908690600401614f79565b600060405180830381600087803b1580156141a857600080fd5b505af11580156141bc573d6000803e3d6000fd5b5050604080516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000811682528a1660208201529081018590527f41b99659f6ba0803f444aff29e5bf6e26dd86a3219aff92119d69710a956ba8d9250606001905060405180910390a1505050505050565b6001600160a01b03851660009081526035602052604090205460ff166142945760405162461bcd60e51b8152602060048201526015602482015274496e76616c69642066726f6d20537472617465677960581b6044820152606401610a68565b8281146142e35760405162461bcd60e51b815260206004820152601960248201527f506172616d65746572206c656e677468206d69736d61746368000000000000006044820152606401610a68565b8260005b818110156143b257866001600160a01b031663d9caed128988888581811061431157614311614ec0565b90506020020160208101906143269190614c05565b87878681811061433857614338614ec0565b6040516001600160e01b031960e088901b1681526001600160a01b03958616600482015294909316602485015250602090910201356044820152606401600060405180830381600087803b15801561438f57600080fd5b505af11580156143a3573d6000803e3d6000fd5b505050508060010190506142e7565b506143bb6132fa565b5050505050505050565b60365460005b8181101561445157603681815481106143e6576143e6614ec0565b60009182526020822001546040805163429c145b60e11b815290516001600160a01b039092169263853828b69260048084019382900301818387803b15801561442e57600080fd5b505af1158015614442573d6000803e3d6000fd5b505050508060010190506143cb565b50610cd86132fa565b6000610c1960127f000000000000000000000000000000000000000000000000000000000000000060ff166140e77f0000000000000000000000000000000000000000000000000000000000000000613b01565b6000806000603c60009054906101000a90046001600160a01b03166001600160a01b031663e696393a6040518163ffffffff1660e01b8152600401602060405180830381865afa158015614506573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061452a9190614f15565b905060006145388287614eec565b604f54909150600090614554906001600160401b031642614eec565b604f54600160c01b90046001600160401b031694509050801580614576575081155b8061458057508587115b8061459257506001600160401b034210155b156145a3576000945050505061467a565b6145ad8787614eec565b604f54909550600160401b90046001600160401b03166001811115614612576145ea856145db836002614fb4565b6145e59089614f92565b61496a565b94506145ff856145fa8389614f92565b614681565b945061460f866145fa8488614fb4565b95505b604f54614653908790670de0b6b3a764000090600160801b90046001600160401b031661463f8688614fb4565b6146499190614fb4565b6145fa9190614f92565b955061467386670de0b6b3a764000061464966470de4df82000087614fb4565b9550505050505b9250929050565b600081831061469057816132f3565b5090919050565b60006146ec826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b03166149799092919063ffffffff16565b8051909150156110dd578080602001905181019061470a9190614f44565b6110dd5760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b6064820152608401610a68565b6040516001600160a01b03808516602483015283166044820152606481018290526147a19085906323b872dd60e01b906084016130c3565b50505050565b60006132f38284614fb4565b60006132f38284614f92565b6000806147d484670de0b6b3a76400006147a7565b905061382f81846147b3565b806001600160a01b03166148006000805160206151908339815191525490565b6001600160a01b03167fc7c0c772add429241571afb3805861fb3cfa2af374534088b76cdb4325a87e9a60405160405180910390a360008051602061519083398151915255565b60408051608081018252604b546001600160801b03808216808452600160801b9283900482166020850152604c5480831695850186905292909204166060830152600092839161489791906150be565b6040516370a0823160e01b81523060048201526001600160801b039190911691506000906001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016906370a0823190602401602060405180830381865afa15801561490c573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906149309190614f15565b9050818111614943576000935050505090565b61494d8282614eec565b935050505090565b60006132f38383670de0b6b3a7640000614988565b600081831161469057816132f3565b606061382f84846000856149a1565b60008061499585856147a7565b9050613d5a81846147b3565b606082471015614a025760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f6044820152651c8818d85b1b60d21b6064820152608401610a68565b843b614a505760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610a68565b600080866001600160a01b03168587604051614a6c9190615120565b60006040518083038185875af1925050503d8060008114614aa9576040519150601f19603f3d011682016040523d82523d6000602084013e614aae565b606091505b5091509150614abe828286614ac9565b979650505050505050565b60608315614ad85750816132f3565b825115614ae85782518084602001fd5b8160405162461bcd60e51b8152600401610a68919061513c565b828054828255906000526020600020908101928215614b57579160200282015b82811115614b5757825182546001600160a01b0319166001600160a01b03909116178255602090920191600190910190614b22565b506130a09291505b808211156130a05760008155600101614b5f565b600060208284031215614b8557600080fd5b5035919050565b80356001600160a01b0381168114614ba357600080fd5b919050565b60008060408385031215614bbb57600080fd5b614bc483614b8c565b946020939093013593505050565b600080600060608486031215614be757600080fd5b614bf084614b8c565b95602085013595506040909401359392505050565b600060208284031215614c1757600080fd5b6132f382614b8c565b602080825282518282018190526000918401906040840190835b81811015614c615783516001600160a01b0316835260209384019390920191600101614c3a565b509095945050505050565b60008083601f840112614c7e57600080fd5b5081356001600160401b03811115614c9557600080fd5b6020830191508360208260051b850101111561467a57600080fd5b60008060208385031215614cc357600080fd5b82356001600160401b03811115614cd957600080fd5b614ce585828601614c6c565b90969095509350505050565b6040808252835190820181905260009060208501906060840190835b81811015614d2b578351835260209384019390920191600101614d0d565b5050602093909301939093525092915050565b600080600080600060608688031215614d5657600080fd5b614d5f86614b8c565b945060208601356001600160401b03811115614d7a57600080fd5b614d8688828901614c6c565b90955093505060408601356001600160401b03811115614da557600080fd5b614db188828901614c6c565b969995985093965092949392505050565b60208082526028908201527f43616c6c6572206973206e6f74207468652053747261746567697374206f722060408201526723b7bb32b93737b960c11b606082015260800190565b6020808252601a908201527f43616c6c6572206973206e6f742074686520476f7665726e6f72000000000000604082015260600190565b6020808252600e908201526d10d85c1a5d185b081c185d5cd95960921b604082015260600190565b6020808252600e908201526d1499595b9d1c985b9d0818d85b1b60921b604082015260600190565b60208082526015908201527414dd1c985d1959de481b9bdd08185c1c1c9bdd9959605a1b604082015260600190565b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052601160045260246000fd5b81810381811115611a6a57611a6a614ed6565b634e487b7160e01b600052603160045260246000fd5b600060208284031215614f2757600080fd5b5051919050565b634e487b7160e01b600052604160045260246000fd5b600060208284031215614f5657600080fd5b815180151581146132f357600080fd5b80820180821115611a6a57611a6a614ed6565b6001600160a01b03929092168252602082015260400190565b600082614faf57634e487b7160e01b600052601260045260246000fd5b500490565b8082028115828204841417611a6a57611a6a614ed6565b6001815b600184111561500657808504811115614fea57614fea614ed6565b6001841615614ff857908102905b60019390931c928002614fcf565b935093915050565b60008261501d57506001611a6a565b8161502a57506000611a6a565b8160018114615040576002811461504a57615066565b6001915050611a6a565b60ff84111561505b5761505b614ed6565b50506001821b611a6a565b5060208310610133831016604e8410600b8410161715615089575081810a611a6a565b6150966000198484614fcb565b80600019048211156150aa576150aa614ed6565b029392505050565b60006132f3838361500e565b6001600160801b038281168282160390811115611a6a57611a6a614ed6565b6001600160801b038181168382160190811115611a6a57611a6a614ed6565b60005b838110156151175781810151838201526020016150ff565b50506000910152565b600082516151328184602087016150fc565b9190910192915050565b602081526000825180602084015261515b8160408501602087016150fc565b601f01601f1916919091016040019291505056fe53bf423e48ed90e97d02ab0ebab13b2a235a6bfbe9c321847d5c175333ac45357bea13895fa79d2831e0a9e28edede30099005a50d652d8957cf8a607ee6ca4aa2646970667358221220cdd4681ab24eb83949cc552c3025347ea514edc98002eccc2e188ec66b9f87e864736f6c634300081c0033", + "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106104075760003560e01c80636217f3ea11610220578063ae69f3cb11610130578063c5f00841116100b8578063d4c3eea011610087578063d4c3eea0146109e6578063e6cc5432146109ee578063ea33b8e414610a02578063f844443614610a0a578063fac5bb9b14610a1d57600080fd5b8063c5f00841146109bb578063c7af3352146109c3578063c9919112146109cb578063d38bfff4146109d357600080fd5b8063b890ebf6116100ff578063b890ebf61461096b578063b9b17f9f1461097e578063bb7a632e14610986578063c3b28864146109a0578063c4d66de8146109a857600080fd5b8063ae69f3cb1461092a578063af14052c1461093d578063b2c9336d14610945578063b4925a201461095857600080fd5b806394828ffd116101b35780639fa1826e116101825780639fa1826e146108ec578063a0712d68146108f5578063a0aead4d14610908578063ab80dafb1461090f578063abaa99161461092257600080fd5b806394828ffd1461086957806395b166bc146108715780639be918e6146108845780639ee679e8146108c457600080fd5b8063840c4c7a116101ef578063840c4c7a146107925780638e510b52146107a55780638ec489a2146107ae578063937b2581146107c157600080fd5b80636217f3ea14610746578063663e64ce14610759578063773540b31461076c57806378f353a11461077f57600080fd5b806339ebf8231161031b578063527e83a8116102ae57806357bee9441161027d57806357bee944146106f45780635802a17214610707578063597c8910146107185780635d36b1901461072b5780635f5152261461073357600080fd5b8063527e83a8146106aa57806352d38e5d146106c457806353ca9f24146106cd578063570d8e1d146106e157600080fd5b806345e4213b116102ea57806345e4213b1461063b57806348e30f541461064457806349c1d54d146106655780634d5f46291461067857600080fd5b806339ebf823146105a95780633b8ae397146105ed5780633dbc911f146106005780634530820a1461060857600080fd5b80631a32aad61161039e5780632acada4d1161036d5780632acada4d146104f35780632da845a81461050857806331e19cfa1461051b578063362bd1a31461052357806338d52e0f1461058257600080fd5b80631a32aad6146104ad5780631cfbe7bc146104c05780631edfe3da146104d3578063207134b0146104ea57600080fd5b80631072cbea116103da5780631072cbea14610461578063156e29f614610474578063175188e8146104875780631816dd4a1461049a57600080fd5b80630493a0fa1461040c57806309f49bf5146104215780630acbda75146104295780630c340a241461043c575b600080fd5b61041f61041a366004614b73565b610a30565b005b61041f610ae6565b61041f610437366004614b73565b610b56565b610444610c01565b6040516001600160a01b0390911681526020015b60405180910390f35b61041f61046f366004614ba8565b610c1e565b61041f610482366004614bd2565b610cdc565b61041f610495366004614c05565b610d4f565b61041f6104a8366004614c05565b6110e2565b603c54610444906001600160a01b031681565b61041f6104ce366004614b73565b6111e9565b6104dc60395481565b604051908152602001610458565b6104dc60435481565b6104fb6112ab565b6040516104589190614c20565b61041f610516366004614c05565b611322565b6036546104dc565b604b54604c5461054f916001600160801b0380821692600160801b928390048216928183169291041684565b604080516001600160801b0395861681529385166020850152918416918301919091529091166060820152608001610458565b6104447f000000000000000000000000000000000000000000000000000000000000000081565b6105d66105b7366004614c05565b6035602052600090815260409020805460019091015460ff9091169082565b604080519215158352602083019190915201610458565b61041f6105fb366004614c05565b611394565b61041f6115a7565b61062b610616366004614c05565b60496020526000908152604090205460ff1681565b6040519015158152602001610458565b6104dc604e5481565b610657610652366004614cb0565b61161d565b604051610458929190614cf1565b604254610444906001600160a01b031681565b604f5461069290600160c01b90046001600160401b031681565b6040516001600160401b039091168152602001610458565b604f5461069290600160801b90046001600160401b031681565b6104dc603b5481565b60375461062b90600160a01b900460ff1681565b603f54610444906001600160a01b031681565b61041f610702366004614c05565b6117c2565b603c546001600160a01b0316610444565b61041f610726366004614c05565b611975565b61041f6119b9565b6104dc610741366004614c05565b611a5f565b61041f610754366004614b73565b611a70565b61041f610767366004614b73565b611bf4565b61041f61077a366004614c05565b611c4d565b604f54610692906001600160401b031681565b61041f6107a0366004614d3e565b611cbf565b6104dc60415481565b61041f6107bc366004614b73565b611d46565b6108226107cf366004614b73565b604d60205260009081526040902080546001909101546001600160a01b03821691600160a01b810460ff1691600160a81b90910464ffffffffff16906001600160801b0380821691600160801b90041685565b604080516001600160a01b039096168652931515602086015264ffffffffff909216928401929092526001600160801b03918216606084015216608082015260a001610458565b61041f611dfb565b61041f61087f366004614c05565b611e6b565b61062b610892366004614c05565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0390811691161490565b6108d76108d2366004614b73565b611f32565b60408051928352602083019190915201610458565b6104dc603a5481565b61041f610903366004614b73565b612273565b60016104dc565b61041f61091d366004614b73565b6122e4565b61041f612433565b61041f610938366004614d3e565b6124ab565b61041f612527565b61041f610953366004614b73565b61256d565b61041f610966366004614b73565b6125c6565b61041f610979366004614b73565b6126e4565b61041f61273d565b604f5461069290600160401b90046001600160401b031681565b6104fb612745565b61041f6109b6366004614c05565b6127a7565b61041f61295f565b61062b6129d5565b61041f612a06565b61041f6109e1366004614c05565b612a46565b6104dc612aea565b60375461062b90600160a81b900460ff1681565b6104dc612af4565b6104dc610a18366004614b73565b612b83565b605054610444906001600160a01b031681565b603f546001600160a01b0316331480610a4c5750610a4c6129d5565b610a715760405162461bcd60e51b8152600401610a6890614dc2565b60405180910390fd5b610a79612c9e565b50610a8381613038565b604f80546001600160401b0392909216600160401b0267ffffffffffffffff60401b199092169190911790556040518181527f406e15fbca1d8ded2dbb06765fea3a54f18395c54125a4c9916dd00ea14ee15e906020015b60405180910390a150565b603f546001600160a01b0316331480610b025750610b026129d5565b610b1e5760405162461bcd60e51b8152600401610a6890614dc2565b6037805460ff60a01b191690556040517fbc044409505c95b6b851433df96e1beae715c909d8e7c1d6d7ab783300d4e3b990600090a1565b610b5e6129d5565b610b7a5760405162461bcd60e51b8152600401610a6890614e0a565b611388811115610bcc5760405162461bcd60e51b815260206004820152601760248201527f62617369732063616e6e6f7420657863656564203530250000000000000000006044820152606401610a68565b60438190556040518181527f56287a45051933ea374811b3d5d165033047be5572cac676f7c28b8be4f746c790602001610adb565b6000610c196000805160206151908339815191525490565b905090565b610c266129d5565b610c425760405162461bcd60e51b8152600401610a6890614e0a565b816001600160a01b03167f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031603610cbc5760405162461bcd60e51b815260206004820152601660248201527513db9b1e481d5b9cdd5c1c1bdc9d195908185cdcd95d60521b6044820152606401610a68565b610cd8610cc7610c01565b6001600160a01b03841690836130a4565b5050565b603754600160a81b900460ff1615610d065760405162461bcd60e51b8152600401610a6890614e41565b60008051602061517083398151915280546001198101610d385760405162461bcd60e51b8152600401610a6890614e69565b60028255610d45846130fa565b5060019055505050565b610d576129d5565b610d735760405162461bcd60e51b8152600401610a6890614e0a565b6001600160a01b03811660009081526035602052604090205460ff16610dab5760405162461bcd60e51b8152600401610a6890614e91565b6050546001600160a01b03808316911603610e085760405162461bcd60e51b815260206004820152601d60248201527f53747261746567792069732064656661756c7420666f722061737365740000006044820152606401610a68565b6036548060005b82811015610e5e57836001600160a01b031660368281548110610e3457610e34614ec0565b6000918252602090912001546001600160a01b031603610e5657809150610e5e565b600101610e0f565b50818110156110dd576036610e74600184614eec565b81548110610e8457610e84614ec0565b600091825260209091200154603680546001600160a01b039092169183908110610eb057610eb0614ec0565b9060005260206000200160006101000a8154816001600160a01b0302191690836001600160a01b031602179055506036805480610eef57610eef614eff565b60008281526020808220830160001990810180546001600160a01b03191690559092019092556001600160a01b038516808352603582526040808420805460ff1990811690915560499093528084208054909316909255815163429c145b60e11b815291518693919263853828b692600480830193919282900301818387803b158015610f7b57600080fd5b505af1158015610f8f573d6000803e3d6000fd5b5060009250610fcd91506509184e72a000905060ff7f0000000000000000000000000000000000000000000000000000000000000000166012613296565b604051632fa8a91360e11b81526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000811660048301529192508291841690635f51522690602401602060405180830381865afa158015611038573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061105c9190614f15565b1061109e5760405162461bcd60e51b81526020600482015260126024820152715374726174656779206861732066756e647360701b6044820152606401610a68565b6040516001600160a01b03861681527f09a1db4b80c32706328728508c941a6b954f31eb5affd32f236c1fd405f8fea49060200160405180910390a150505b505050565b6110ea6129d5565b6111065760405162461bcd60e51b8152600401610a6890614e0a565b6001600160a01b03811660009081526035602052604090205460ff1661113e5760405162461bcd60e51b8152600401610a6890614e91565b6001600160a01b03811660009081526049602052604090205460ff161561119d5760405162461bcd60e51b8152602060048201526013602482015272105b1c9958591e481dda1a5d195b1a5cdd1959606a1b6044820152606401610a68565b6001600160a01b038116600081815260496020526040808220805460ff19166001179055517f47c8c96a5942f094264111c5fe7f6a4fe86efe63254a6fa7afa5fc84f07d58e89190a250565b6111f16129d5565b61120d5760405162461bcd60e51b8152600401610a6890614e0a565b80158061122a5750610258811015801561122a57506213c6808111155b6112765760405162461bcd60e51b815260206004820152601a60248201527f496e76616c696420636c61696d2064656c617920706572696f640000000000006044820152606401610a68565b604e8190556040518181527fc59f5e32049abab44ddea11021f5abb89422a2f550837afcf25df9fc8d0db6b090602001610adb565b60408051600180825281830190925260609160009190602080830190803683370190505090507f00000000000000000000000000000000000000000000000000000000000000008160008151811061130557611305614ec0565b6001600160a01b0390921660209283029190910190910152919050565b61132a6129d5565b6113465760405162461bcd60e51b8152600401610a6890614e0a565b604280546001600160a01b0319166001600160a01b0383169081179091556040519081527f1e4af5ac389e8cde1bdaa6830881b6c987c62a45cfb3b33d27d805cde3b5775090602001610adb565b61139c6129d5565b6113b85760405162461bcd60e51b8152600401610a6890614e0a565b6001600160a01b03811660009081526035602052604090205460ff16156114215760405162461bcd60e51b815260206004820152601960248201527f537472617465677920616c726561647920617070726f766564000000000000006044820152606401610a68565b60405163551c457b60e11b81526001600160a01b037f00000000000000000000000000000000000000000000000000000000000000008116600483015282169063aa388af690602401602060405180830381865afa158015611487573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906114ab9190614f44565b6114f75760405162461bcd60e51b815260206004820152601f60248201527f4173736574206e6f7420737570706f72746564206279205374726174656779006044820152606401610a68565b6040805180820182526001808252600060208084018281526001600160a01b038716808452603583528684209551865460ff19169015151786559051948401949094556036805493840181559091527f4a11f94e20a93c79f6ec743a1954ec4fc2c08429ae2122118bf234b2185c81b890910180546001600160a01b0319168317905591519081527f960dd94cbb79169f09a4e445d58b895df2d9bffa5b31055d0932d801724a20d19101610adb565b603f546001600160a01b03163314806115c357506115c36129d5565b6115df5760405162461bcd60e51b8152600401610a6890614dc2565b6037805460ff60a81b1916600160a81b1790556040517f71f0e5b62f846a22e0b4d159e516e62fa9c2b8eb570be15f83e67d98a2ee51e090600090a1565b603754606090600090600160a81b900460ff161561164d5760405162461bcd60e51b8152600401610a6890614e41565b6000805160206151708339815191528054600119810161167f5760405162461bcd60e51b8152600401610a6890614e69565b6002825561168b6132fa565b50846001600160401b038111156116a4576116a4614f2e565b6040519080825280602002602001820160405280156116cd578160200160208202803683370190505b50935060005b85811015611749576116fc8787838181106116f0576116f0614ec0565b905060200201356134d6565b85828151811061170e5761170e614ec0565b60200260200101818152505084818151811061172c5761172c614ec0565b60200260200101518461173f9190614f66565b93506001016116d3565b5061177e6001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633856130a4565b6117b56117b084601260ff7f000000000000000000000000000000000000000000000000000000000000000016613296565b613837565b6001825550509250929050565b603f546001600160a01b03163314806117de57506117de6129d5565b6117fa5760405162461bcd60e51b8152600401610a6890614dc2565b6040516001600160a01b03821681527f45a9e715f8779ebeab88d3e8d081fb8b484b7730857e7c4478a95a5b1d8f79489060200160405180910390a16001600160a01b03811615611953576001600160a01b03811660009081526035602052604090205460ff1661187d5760405162461bcd60e51b8152600401610a6890614e91565b60405163551c457b60e11b81526001600160a01b037f00000000000000000000000000000000000000000000000000000000000000008116600483015282169063aa388af690602401602060405180830381865afa1580156118e3573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906119079190614f44565b6119535760405162461bcd60e51b815260206004820152601f60248201527f4173736574206e6f7420737570706f72746564206279205374726174656779006044820152606401610a68565b605080546001600160a01b0319166001600160a01b0392909216919091179055565b603f546001600160a01b031633148061199157506119916129d5565b6119ad5760405162461bcd60e51b8152600401610a6890614dc2565b6119b6816139da565b50565b7f44c4d30b2eaad5130ad70c3ba6972730566f3e6359ab83e800d905c61b1c51db546001600160a01b0316336001600160a01b031614611a545760405162461bcd60e51b815260206004820152603060248201527f4f6e6c79207468652070656e64696e6720476f7665726e6f722063616e20636f60448201526f6d706c6574652074686520636c61696d60801b6064820152608401610a68565b611a5d33613aa2565b565b6000611a6a82613b01565b92915050565b603754600160a81b900460ff1615611a9a5760405162461bcd60e51b8152600401610a6890614e41565b3360009081526035602052604090205460ff161515600114611af55760405162461bcd60e51b8152602060048201526014602482015273556e737570706f7274656420737472617465677960601b6044820152606401610a68565b3360009081526049602052604090205460ff161515600114611b545760405162461bcd60e51b81526020600482015260186024820152774e6f742077686974656c697374656420737472617465677960401b6044820152606401610a68565b7f222838db2794d11532d940e8dec38ae307ed0b63cd97c233322e221f998767a63382604051611b85929190614f79565b60405180910390a1603c54604051632770a7eb60e21b81526001600160a01b0390911690639dc29fac90611bbf9033908590600401614f79565b600060405180830381600087803b158015611bd957600080fd5b505af1158015611bed573d6000803e3d6000fd5b5050505050565b611bfc6129d5565b611c185760405162461bcd60e51b8152600401610a6890614e0a565b60418190556040518181527f95201f9c21f26877223b1ff4073936a6484c35495649e60e55730497aeb60d9390602001610adb565b611c556129d5565b611c715760405162461bcd60e51b8152600401610a6890614e0a565b603f80546001600160a01b0319166001600160a01b0383169081179091556040519081527f869e0abd13cc3a975de7b93be3df1cb2255c802b1cead85963cc79d99f131bee90602001610adb565b603f546001600160a01b0316331480611cdb5750611cdb6129d5565b611cf75760405162461bcd60e51b8152600401610a6890614dc2565b60008051602061517083398151915280546001198101611d295760405162461bcd60e51b8152600401610a6890614e69565b60028255611d3a8787878787613d63565b50600190555050505050565b603f546001600160a01b0316331480611d625750611d626129d5565b611d7e5760405162461bcd60e51b8152600401610a6890614dc2565b670de0b6b3a7640000811115611dc65760405162461bcd60e51b815260206004820152600d60248201526c496e76616c69642076616c756560981b6044820152606401610a68565b60398190556040518181527f41ecb23a0e7865b25f38c268b7c3012220d822929e9edff07326e89d5bb822b590602001610adb565b603f546001600160a01b0316331480611e175750611e176129d5565b611e335760405162461bcd60e51b8152600401610a6890614dc2565b6037805460ff60a81b191690556040517f891ebab18da80ebeeea06b1b1cede098329c4c008906a98370c2ac7a80b571cb90600090a1565b611e736129d5565b611e8f5760405162461bcd60e51b8152600401610a6890614e0a565b6001600160a01b03811660009081526049602052604090205460ff16611ee95760405162461bcd60e51b815260206004820152600f60248201526e139bdd081dda1a5d195b1a5cdd1959608a1b6044820152606401610a68565b6001600160a01b038116600081815260496020526040808220805460ff19169055517f0ec40967a61509853550658e51d0e4297f7cba244fe4adc8ba18b5631ac20e2f9190a250565b6037546000908190600160a81b900460ff1615611f615760405162461bcd60e51b8152600401610a6890614e41565b60008051602061517083398151915280546001198101611f935760405162461bcd60e51b8152600401610a6890614e69565b6002825560008511611fe75760405162461bcd60e51b815260206004820152601d60248201527f416d6f756e74206d7573742062652067726561746572207468616e20300000006044820152606401610a68565b6000604e54116120395760405162461bcd60e51b815260206004820152601d60248201527f4173796e63207769746864726177616c73206e6f7420656e61626c65640000006044820152606401610a68565b604c54600160801b90046001600160801b0316935061207d857f000000000000000000000000000000000000000000000000000000000000000060ff166012613296565b604b5461209391906001600160801b0316614f66565b92506120a86120a3856001614f66565b613fa2565b604c80546001600160801b03928316600160801b0292169190911790556120ce83613fa2565b604b80546001600160801b0319166001600160801b03929092169190911790556040805160a081018252338152600060208201524264ffffffffff16918101919091526060810161211e87613fa2565b6001600160801b0316815260200161213585613fa2565b6001600160801b039081169091526000868152604d602090815260409182902084518154928601518685015164ffffffffff16600160a81b0264ffffffffff60a81b19911515600160a01b026001600160a81b03199095166001600160a01b0393841617949094171692909217815560608501516080909501518416600160801b029490931693909317600190920191909155603c549051632770a7eb60e21b8152911690639dc29fac906121f09033908990600401614f79565b600060405180830381600087803b15801561220a57600080fd5b505af115801561221e573d6000803e3d6000fd5b5050505061222b85613837565b6040805186815260208101859052859133917f38e3d972947cfef94205163d483d6287ef27eb312e20cb8e0b13a49989db232e910160405180910390a3600182555050915091565b603754600160a81b900460ff161561229d5760405162461bcd60e51b8152600401610a6890614e41565b600080516020615170833981519152805460011981016122cf5760405162461bcd60e51b8152600401610a6890614e69565b600282556122dc836130fa565b506001905550565b603754600160a81b900460ff161561230e5760405162461bcd60e51b8152600401610a6890614e41565b3360009081526035602052604090205460ff1615156001146123695760405162461bcd60e51b8152602060048201526014602482015273556e737570706f7274656420737472617465677960601b6044820152606401610a68565b3360009081526049602052604090205460ff1615156001146123c85760405162461bcd60e51b81526020600482015260186024820152774e6f742077686974656c697374656420737472617465677960401b6044820152606401610a68565b7f0f6798a560793a54c3bcfe86a93cde1e73087d944c0ea20544137d412139688533826040516123f9929190614f79565b60405180910390a1603c546040516340c10f1960e01b81526001600160a01b03909116906340c10f1990611bbf9033908590600401614f79565b603754600160a81b900460ff161561245d5760405162461bcd60e51b8152600401610a6890614e41565b6000805160206151708339815191528054600119810161248f5760405162461bcd60e51b8152600401610a6890614e69565b6002825561249b6132fa565b506124a461400b565b5060019055565b603f546001600160a01b03163314806124c757506124c76129d5565b6124e35760405162461bcd60e51b8152600401610a6890614dc2565b600080516020615170833981519152805460011981016125155760405162461bcd60e51b8152600401610a6890614e69565b60028255611d3a308888888888614234565b600080516020615170833981519152805460011981016125595760405162461bcd60e51b8152600401610a6890614e69565b60028255612565612c9e565b505060019055565b6125756129d5565b6125915760405162461bcd60e51b8152600401610a6890614e0a565b603a8190556040518181527f2ec5fb5a3d2703edc461252d92ccd2799c3c74f01d97212b20388207fa17ae4590602001610adb565b603f546001600160a01b03163314806125e257506125e26129d5565b6125fe5760405162461bcd60e51b8152600401610a6890614dc2565b612606612c9e565b5060006301e13380612619606484614f92565b6126239190614f92565b90506126396201518066b1a2bc2ec50000614f92565b8111156126785760405162461bcd60e51b815260206004820152600d60248201526c0a4c2e8ca40e8dede40d0d2ced609b1b6044820152606401610a68565b61268181613038565b604f80546001600160401b0392909216600160801b0267ffffffffffffffff60801b199092169190911790556040518181527fef46f143af5fead0010484fe7d6ec2e2972420faa76157f5a6075aa72e614cb59060200160405180910390a15050565b6126ec6129d5565b6127085760405162461bcd60e51b8152600401610a6890614e0a565b603b8190556040518181527f39367850377ac04920a9a670f2180e7a94d83b15ad302e59875ec58fd10bd37d90602001610adb565b6119b66132fa565b6060603680548060200260200160405190810160405280929190818152602001828054801561279d57602002820191906000526020600020905b81546001600160a01b0316815260019091019060200180831161277f575b5050505050905090565b6127af6129d5565b6127cb5760405162461bcd60e51b8152600401610a6890614e0a565b600054610100900460ff16806127e4575060005460ff16155b6128475760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b6064820152608401610a68565b600054610100900460ff16158015612869576000805461ffff19166101011790555b6001600160a01b0382166128b85760405162461bcd60e51b81526020600482015260166024820152756f546f6b656e2061646472657373206973207a65726f60501b6044820152606401610a68565b603c80546001600160a01b0384166001600160a01b03199091161790556037805461ffff60a01b1916600160a81b1790556000603981905569054b40b1f852bda00000603a55683635c9adc5dea00000603b55604080519182526020820190819052905161292891603691614b02565b50604f805467ffffffffffffffff60401b19166a093a8000000000000000001790558015610cd8576000805461ff00191690555050565b603f546001600160a01b031633148061297b575061297b6129d5565b6129975760405162461bcd60e51b8152600401610a6890614dc2565b6037805460ff60a01b1916600160a01b1790556040517f8cff26a5985614b3d30629cc4ab83824bf115aec971b718d8f2f99562032e97290600090a1565b60006129ed6000805160206151908339815191525490565b6001600160a01b0316336001600160a01b031614905090565b603f546001600160a01b0316331480612a225750612a226129d5565b612a3e5760405162461bcd60e51b8152600401610a6890614dc2565b611a5d6143c5565b612a4e6129d5565b612a6a5760405162461bcd60e51b8152600401610a6890614e0a565b612a92817f44c4d30b2eaad5130ad70c3ba6972730566f3e6359ab83e800d905c61b1c51db55565b806001600160a01b0316612ab26000805160206151908339815191525490565b6001600160a01b03167fa39cc5eb22d0f34d8beaefee8a3f17cc229c1a1d1ef87a5ad47313487b1c4f0d60405160405180910390a350565b6000610c1961445a565b6000612b7d603c60009054906101000a90046001600160a01b03166001600160a01b03166318160ddd6040518163ffffffff1660e01b8152600401602060405180830381865afa158015612b4c573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612b709190614f15565b612b7861445a565b6144ae565b50919050565b603754600090600160a81b900460ff1615612bb05760405162461bcd60e51b8152600401610a6890614e41565b60008051602061517083398151915280546001198101612be25760405162461bcd60e51b8152600401610a6890614e69565b60028255604b546000858152604d60205260409020600101546001600160801b03600160801b92839004811692909104161115612c2357612c216132fa565b505b612c2c846134d6565b9250612c626001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633856130a4565b612c946117b084601260ff7f000000000000000000000000000000000000000000000000000000000000000016613296565b5060019055919050565b603754600090600160a01b900460ff1615612ced5760405162461bcd60e51b815260206004820152600f60248201526e149958985cda5b99c81c185d5cd959608a1b6044820152606401610a68565b603c54604080516318160ddd60e01b815290516000926001600160a01b0316916318160ddd9160048083019260209291908290030181865afa158015612d37573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612d5b9190614f15565b90506000612d6761445a565b905081600003612d7a5791506130359050565b600080612d8784846144ae565b90925090506000612d988386614f66565b90508481111580612da857508381115b15612db7575091949350505050565b612dc8826001600160401b03614681565b604f805477ffffffffffffffffffffffffffffffff000000000000000016600160c01b6001600160401b039384160267ffffffffffffffff19161742929092169190911790556042546001600160a01b031660008115612f045761271060435486612e339190614fb4565b612e3d9190614f92565b90508015612f0457848110612e9f5760405162461bcd60e51b815260206004820152602260248201527f466565206d757374206e6f742062652067726561746572207468616e207969656044820152611b1960f21b6064820152608401610a68565b603c546040516340c10f1960e01b81526001600160a01b03909116906340c10f1990612ed19085908590600401614f79565b600060405180830381600087803b158015612eeb57600080fd5b505af1158015612eff573d6000803e3d6000fd5b505050505b604080516001600160a01b0384168152602081018790529081018290527f09516ecf4a8a86e59780a9befc6dee948bc9e60a36e3be68d31ea817ee8d2c809060600160405180910390a1603c60009054906101000a90046001600160a01b03166001600160a01b03166318160ddd6040518163ffffffff1660e01b8152600401602060405180830381865afa158015612fa1573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612fc59190614f15565b83111561302b57603c546040516339a7919f60e01b8152600481018590526001600160a01b03909116906339a7919f90602401600060405180830381600087803b15801561301257600080fd5b505af1158015613026573d6000803e3d6000fd5b505050505b5093955050505050505b90565b60006001600160401b038211156130a05760405162461bcd60e51b815260206004820152602660248201527f53616665436173743a2076616c756520646f65736e27742066697420696e203660448201526534206269747360d01b6064820152608401610a68565b5090565b6110dd8363a9059cbb60e01b84846040516024016130c3929190614f79565b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b031990931692909217909152614697565b6000811161314a5760405162461bcd60e51b815260206004820152601d60248201527f416d6f756e74206d7573742062652067726561746572207468616e20300000006044820152606401610a68565b600061317b82601260ff7f000000000000000000000000000000000000000000000000000000000000000016613296565b90507f0f6798a560793a54c3bcfe86a93cde1e73087d944c0ea20544137d412139688533826040516131ae929190614f79565b60405180910390a1603754600160a01b900460ff161580156131d25750603b548110155b156131e1576131df612c9e565b505b603c546040516340c10f1960e01b81526001600160a01b03909116906340c10f19906132139033908590600401614f79565b600060405180830381600087803b15801561322d57600080fd5b505af1158015613241573d6000803e3d6000fd5b5061327c9250506001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169050333085614769565b6132846132fa565b50603a548110610cd857610cd861400b565b6000818311156132c6576132bf6132ad8385614eec565b6132b890600a6150b2565b85906147a7565b93506132f0565b818310156132f0576132ed6132db8484614eec565b6132e690600a6150b2565b85906147b3565b93505b50825b9392505050565b60408051608081018252604b546001600160801b03808216808452600160801b92839004821660208501819052604c548084169686019690965292909404166060830152600092839161334c916150be565b6001600160801b03169050806000036133685760009250505090565b6040516370a0823160e01b81523060048201526000907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906370a0823190602401602060405180830381865afa1580156133cf573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906133f39190614f15565b905060008360400151846020015161340b91906150be565b6001600160801b0316905080821161342857600094505050505090565b60006134348284614eec565b90508084106134435780613445565b835b955060008686602001516001600160801b03166134629190614f66565b905061346d81613fa2565b604b80546001600160801b03928316600160801b0292169190911790556040517fee79a0c43d3993055690b54e074b5153e8bae8d1a872b656dedb64aa8f463333906134c59083908a90918252602082015260400190565b60405180910390a150505050505090565b600080604e54116135295760405162461bcd60e51b815260206004820152601d60248201527f4173796e63207769746864726177616c73206e6f7420656e61626c65640000006044820152606401610a68565b6000828152604d6020908152604091829020825160a08101845281546001600160a01b038116825260ff600160a01b82041615158285015264ffffffffff600160a81b90910481168286019081526001909301546001600160801b03808216606080860191909152600160801b92839004821660808087019190915288519081018952604b548084168252849004831697810197909752604c54808316988801989098529190960490951694840194909452604e549151909342926135f092909116614f66565b11156136345760405162461bcd60e51b815260206004820152601360248201527210db185a5b4819195b185e481b9bdd081b595d606a1b6044820152606401610a68565b80602001516001600160801b031682608001516001600160801b0316111561369e5760405162461bcd60e51b815260206004820152601760248201527f51756575652070656e64696e67206c69717569646974790000000000000000006044820152606401610a68565b81516001600160a01b031633146136e75760405162461bcd60e51b815260206004820152600d60248201526c2737ba103932b8bab2b9ba32b960991b6044820152606401610a68565b60208201511561372b5760405162461bcd60e51b815260206004820152600f60248201526e105b1c9958591e4818db185a5b5959608a1b6044820152606401610a68565b6000848152604d60205260409020805460ff60a01b1916600160a01b179055606082015161378b906120a3906001600160801b03167f000000000000000000000000000000000000000000000000000000000000000060ff166012613296565b816040015161379a91906150dd565b604c80546001600160801b0319166001600160801b03928316179055606083015160405191168152849033907f2d43eb174787155132b52ddb6b346e2dca99302eac3df4466dbeff953d3c84d19060200160405180910390a361382f82606001516001600160801b03167f000000000000000000000000000000000000000000000000000000000000000060ff166012613296565b949350505050565b6000603b5482101580156138555750603754600160a01b900460ff16155b1561386957613862612c9e565b9050613874565b61387161445a565b90505b60415415610cd857600081116138cc5760405162461bcd60e51b815260206004820152601d60248201527f546f6f206d616e79206f75747374616e64696e672072657175657374730000006044820152606401610a68565b600061394f82603c60009054906101000a90046001600160a01b03166001600160a01b03166318160ddd6040518163ffffffff1660e01b8152600401602060405180830381865afa158015613925573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906139499190614f15565b906147bf565b9050604154670de0b6b3a7640000821161397a5761397582670de0b6b3a7640000614eec565b61398c565b61398c670de0b6b3a764000083614eec565b11156110dd5760405162461bcd60e51b815260206004820152601e60248201527f4261636b696e6720737570706c79206c6971756964697479206572726f7200006044820152606401610a68565b6001600160a01b03811660009081526035602052604090205460ff16613a425760405162461bcd60e51b815260206004820152601960248201527f5374726174656779206973206e6f7420737570706f72746564000000000000006044820152606401610a68565b6000819050806001600160a01b031663853828b66040518163ffffffff1660e01b8152600401600060405180830381600087803b158015613a8257600080fd5b505af1158015613a96573d6000803e3d6000fd5b505050506110dd6132fa565b6001600160a01b038116613af85760405162461bcd60e51b815260206004820152601a60248201527f4e657720476f7665726e6f7220697320616464726573732830290000000000006044820152606401610a68565b6119b6816147e0565b60007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316826001600160a01b031614613b4457506000919050565b6040516370a0823160e01b815230600482015282906001600160a01b038216906370a0823190602401602060405180830381865afa158015613b8a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613bae9190614f15565b60365490925060005b81811015613cd357600060368281548110613bd457613bd4614ec0565b60009182526020909120015460405163551c457b60e11b81526001600160a01b0388811660048301529091169150819063aa388af690602401602060405180830381865afa158015613c2a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613c4e9190614f44565b15613cca57604051632fa8a91360e11b81526001600160a01b038781166004830152821690635f51522690602401602060405180830381865afa158015613c99573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613cbd9190614f15565b613cc79086614f66565b94505b50600101613bb7565b5060408051608081018252604b546001600160801b03808216808452600160801b9283900482166020850152604c54808316958501869052929092041660608301529091613d219086614f66565b1015613d3257506000949350505050565b805160408201516001600160801b0391821691613d50911686614f66565b613d5a9190614eec565b95945050505050565b6001600160a01b03851660009081526035602052604090205460ff16613dc15760405162461bcd60e51b8152602060048201526013602482015272496e76616c696420746f20537472617465677960681b6044820152606401610a68565b600183148015613dd15750600181145b8015613e3557507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031684846000818110613e1557613e15614ec0565b9050602002016020810190613e2a9190614c05565b6001600160a01b0316145b613e815760405162461bcd60e51b815260206004820152601760248201527f4f6e6c7920617373657420697320737570706f727465640000000000000000006044820152606401610a68565b613e89614847565b82826000818110613e9c57613e9c614ec0565b905060200201351115613ef15760405162461bcd60e51b815260206004820152601b60248201527f4e6f7420656e6f7567682061737365747320617661696c61626c6500000000006044820152606401610a68565b613f488583836000818110613f0857613f08614ec0565b905060200201357f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166130a49092919063ffffffff16565b846001600160a01b031663de5f62686040518163ffffffff1660e01b8152600401600060405180830381600087803b158015613f8357600080fd5b505af1158015613f97573d6000803e3d6000fd5b505050505050505050565b60006001600160801b038211156130a05760405162461bcd60e51b815260206004820152602760248201527f53616665436173743a2076616c756520646f65736e27742066697420696e20316044820152663238206269747360c81b6064820152608401610a68565b6050546001600160a01b03168061401f5750565b6000614029614847565b905080600003614037575050565b603c54604080516318160ddd60e01b815290516000926001600160a01b0316916318160ddd9160048083019260209291908290030181865afa158015614081573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906140a59190614f15565b905060006140ee7f000000000000000000000000000000000000000000000000000000000000000060ff1660126140e76039548661495590919063ffffffff16565b9190613296565b90508083116140fd5750505050565b60006141098285614eec565b9050846141406001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001682846130a4565b6040516311f9fbc960e21b81526001600160a01b038216906347e7ef249061418e907f0000000000000000000000000000000000000000000000000000000000000000908690600401614f79565b600060405180830381600087803b1580156141a857600080fd5b505af11580156141bc573d6000803e3d6000fd5b5050604080516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000811682528a1660208201529081018590527f41b99659f6ba0803f444aff29e5bf6e26dd86a3219aff92119d69710a956ba8d9250606001905060405180910390a1505050505050565b6001600160a01b03851660009081526035602052604090205460ff166142945760405162461bcd60e51b8152602060048201526015602482015274496e76616c69642066726f6d20537472617465677960581b6044820152606401610a68565b8281146142e35760405162461bcd60e51b815260206004820152601960248201527f506172616d65746572206c656e677468206d69736d61746368000000000000006044820152606401610a68565b8260005b818110156143b257866001600160a01b031663d9caed128988888581811061431157614311614ec0565b90506020020160208101906143269190614c05565b87878681811061433857614338614ec0565b6040516001600160e01b031960e088901b1681526001600160a01b03958616600482015294909316602485015250602090910201356044820152606401600060405180830381600087803b15801561438f57600080fd5b505af11580156143a3573d6000803e3d6000fd5b505050508060010190506142e7565b506143bb6132fa565b5050505050505050565b60365460005b8181101561445157603681815481106143e6576143e6614ec0565b60009182526020822001546040805163429c145b60e11b815290516001600160a01b039092169263853828b69260048084019382900301818387803b15801561442e57600080fd5b505af1158015614442573d6000803e3d6000fd5b505050508060010190506143cb565b50610cd86132fa565b6000610c1960127f000000000000000000000000000000000000000000000000000000000000000060ff166140e77f0000000000000000000000000000000000000000000000000000000000000000613b01565b6000806000603c60009054906101000a90046001600160a01b03166001600160a01b031663e696393a6040518163ffffffff1660e01b8152600401602060405180830381865afa158015614506573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061452a9190614f15565b905060006145388287614eec565b604f54909150600090614554906001600160401b031642614eec565b604f54600160c01b90046001600160401b031694509050801580614576575081155b8061458057508587115b8061459257506001600160401b034210155b156145a3576000945050505061467a565b6145ad8787614eec565b604f54909550600160401b90046001600160401b03166001811115614612576145ea856145db836002614fb4565b6145e59089614f92565b61496a565b94506145ff856145fa8389614f92565b614681565b945061460f866145fa8488614fb4565b95505b604f54614653908790670de0b6b3a764000090600160801b90046001600160401b031661463f8688614fb4565b6146499190614fb4565b6145fa9190614f92565b955061467386670de0b6b3a764000061464966470de4df82000087614fb4565b9550505050505b9250929050565b600081831061469057816132f3565b5090919050565b60006146ec826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b03166149799092919063ffffffff16565b8051909150156110dd578080602001905181019061470a9190614f44565b6110dd5760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b6064820152608401610a68565b6040516001600160a01b03808516602483015283166044820152606481018290526147a19085906323b872dd60e01b906084016130c3565b50505050565b60006132f38284614fb4565b60006132f38284614f92565b6000806147d484670de0b6b3a76400006147a7565b905061382f81846147b3565b806001600160a01b03166148006000805160206151908339815191525490565b6001600160a01b03167fc7c0c772add429241571afb3805861fb3cfa2af374534088b76cdb4325a87e9a60405160405180910390a360008051602061519083398151915255565b60408051608081018252604b546001600160801b03808216808452600160801b9283900482166020850152604c5480831695850186905292909204166060830152600092839161489791906150be565b6040516370a0823160e01b81523060048201526001600160801b039190911691506000906001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016906370a0823190602401602060405180830381865afa15801561490c573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906149309190614f15565b9050818111614943576000935050505090565b61494d8282614eec565b935050505090565b60006132f38383670de0b6b3a7640000614988565b600081831161469057816132f3565b606061382f84846000856149a1565b60008061499585856147a7565b9050613d5a81846147b3565b606082471015614a025760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f6044820152651c8818d85b1b60d21b6064820152608401610a68565b843b614a505760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610a68565b600080866001600160a01b03168587604051614a6c9190615120565b60006040518083038185875af1925050503d8060008114614aa9576040519150601f19603f3d011682016040523d82523d6000602084013e614aae565b606091505b5091509150614abe828286614ac9565b979650505050505050565b60608315614ad85750816132f3565b825115614ae85782518084602001fd5b8160405162461bcd60e51b8152600401610a68919061513c565b828054828255906000526020600020908101928215614b57579160200282015b82811115614b5757825182546001600160a01b0319166001600160a01b03909116178255602090920191600190910190614b22565b506130a09291505b808211156130a05760008155600101614b5f565b600060208284031215614b8557600080fd5b5035919050565b80356001600160a01b0381168114614ba357600080fd5b919050565b60008060408385031215614bbb57600080fd5b614bc483614b8c565b946020939093013593505050565b600080600060608486031215614be757600080fd5b614bf084614b8c565b95602085013595506040909401359392505050565b600060208284031215614c1757600080fd5b6132f382614b8c565b602080825282518282018190526000918401906040840190835b81811015614c615783516001600160a01b0316835260209384019390920191600101614c3a565b509095945050505050565b60008083601f840112614c7e57600080fd5b5081356001600160401b03811115614c9557600080fd5b6020830191508360208260051b850101111561467a57600080fd5b60008060208385031215614cc357600080fd5b82356001600160401b03811115614cd957600080fd5b614ce585828601614c6c565b90969095509350505050565b6040808252835190820181905260009060208501906060840190835b81811015614d2b578351835260209384019390920191600101614d0d565b5050602093909301939093525092915050565b600080600080600060608688031215614d5657600080fd5b614d5f86614b8c565b945060208601356001600160401b03811115614d7a57600080fd5b614d8688828901614c6c565b90955093505060408601356001600160401b03811115614da557600080fd5b614db188828901614c6c565b969995985093965092949392505050565b60208082526028908201527f43616c6c6572206973206e6f74207468652053747261746567697374206f722060408201526723b7bb32b93737b960c11b606082015260800190565b6020808252601a908201527f43616c6c6572206973206e6f742074686520476f7665726e6f72000000000000604082015260600190565b6020808252600e908201526d10d85c1a5d185b081c185d5cd95960921b604082015260600190565b6020808252600e908201526d1499595b9d1c985b9d0818d85b1b60921b604082015260600190565b60208082526015908201527414dd1c985d1959de481b9bdd08185c1c1c9bdd9959605a1b604082015260600190565b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052601160045260246000fd5b81810381811115611a6a57611a6a614ed6565b634e487b7160e01b600052603160045260246000fd5b600060208284031215614f2757600080fd5b5051919050565b634e487b7160e01b600052604160045260246000fd5b600060208284031215614f5657600080fd5b815180151581146132f357600080fd5b80820180821115611a6a57611a6a614ed6565b6001600160a01b03929092168252602082015260400190565b600082614faf57634e487b7160e01b600052601260045260246000fd5b500490565b8082028115828204841417611a6a57611a6a614ed6565b6001815b600184111561500657808504811115614fea57614fea614ed6565b6001841615614ff857908102905b60019390931c928002614fcf565b935093915050565b60008261501d57506001611a6a565b8161502a57506000611a6a565b8160018114615040576002811461504a57615066565b6001915050611a6a565b60ff84111561505b5761505b614ed6565b50506001821b611a6a565b5060208310610133831016604e8410600b8410161715615089575081810a611a6a565b6150966000198484614fcb565b80600019048211156150aa576150aa614ed6565b029392505050565b60006132f3838361500e565b6001600160801b038281168282160390811115611a6a57611a6a614ed6565b6001600160801b038181168382160190811115611a6a57611a6a614ed6565b60005b838110156151175781810151838201526020016150ff565b50506000910152565b600082516151328184602087016150fc565b9190910192915050565b602081526000825180602084015261515b8160408501602087016150fc565b601f01601f1916919091016040019291505056fe53bf423e48ed90e97d02ab0ebab13b2a235a6bfbe9c321847d5c175333ac45357bea13895fa79d2831e0a9e28edede30099005a50d652d8957cf8a607ee6ca4aa2646970667358221220cdd4681ab24eb83949cc552c3025347ea514edc98002eccc2e188ec66b9f87e864736f6c634300081c0033", + "libraries": {}, + "devdoc": { + "author": "Origin Protocol Inc", + "kind": "dev", + "methods": { + "addStrategyToMintWhitelist(address)": { + "params": { + "strategyAddr": "Strategy address" + } + }, + "addWithdrawalQueueLiquidity()": { + "details": "is called from the Native Staking strategy when validator withdrawals are processed. It also called before any WETH is allocated to a strategy." + }, + "approveStrategy(address)": { + "params": { + "_addr": "Address of the strategy to add" + } + }, + "burnForStrategy(uint256)": { + "details": "Notice: can't use `nonReentrant` modifier since the `redeem` function could require withdrawal on an AMO strategy and that one can call `burnForStrategy` while the execution of the `redeem` has not yet completed -> causing a `nonReentrant` collision. Also important to understand is that this is a limitation imposed by the test suite. Production / mainnet contracts should never be configured in a way where mint/redeem functions that are moving funds between the Vault and end user wallets can influence strategies utilizing this function.", + "params": { + "_amount": "Amount of OToken to burn" + } + }, + "checkBalance(address)": { + "params": { + "_asset": "Address of asset" + }, + "returns": { + "_0": "uint256 Balance of asset in decimals of asset" + } + }, + "claimWithdrawal(uint256)": { + "params": { + "_requestId": "Unique ID for the withdrawal request" + }, + "returns": { + "amount": "Amount of asset transferred to the withdrawer" + } + }, + "claimWithdrawals(uint256[])": { + "params": { + "_requestIds": "Unique ID of each withdrawal request" + }, + "returns": { + "amounts": "Amount of asset received for each request", + "totalAmount": "Total amount of asset transferred to the withdrawer" + } + }, + "depositToStrategy(address,address[],uint256[])": { + "params": { + "_amounts": "Array of amounts of each corresponding asset to deposit.", + "_assets": "Array of asset address that will be deposited into the strategy.", + "_strategyToAddress": "Address of the Strategy to deposit asset into." + } + }, + "isSupportedAsset(address)": { + "params": { + "_asset": "address of the asset" + }, + "returns": { + "_0": "true if supported" + } + }, + "mint(address,uint256,uint256)": { + "details": "Deprecated: use `mint(uint256 _amount)` instead.Deprecated: param _asset Address of the asset being depositedDeprecated: param _minimumOusdAmount Minimum OTokens to mint", + "params": { + "_amount": "Amount of the asset being deposited" + } + }, + "mint(uint256)": { + "params": { + "_amount": "Amount of the asset being deposited" + } + }, + "mintForStrategy(uint256)": { + "params": { + "_amount": "Amount of OToken to mint Notice: can't use `nonReentrant` modifier since the `mint` function can call `allocate`, and that can trigger an AMO strategy to call this function while the execution of the `mint` has not yet completed -> causing a `nonReentrant` collision. Also important to understand is that this is a limitation imposed by the test suite. Production / mainnet contracts should never be configured in a way where mint/redeem functions that are moving funds between the Vault and end user wallets can influence strategies utilizing this function." + } + }, + "previewYield()": { + "returns": { + "yield": "amount of expected yield" + } + }, + "removeStrategy(address)": { + "params": { + "_addr": "Address of the strategy to remove" + } + }, + "removeStrategyFromMintWhitelist(address)": { + "params": { + "strategyAddr": "Strategy address" + } + }, + "requestWithdrawal(uint256)": { + "params": { + "_amount": "Amount of OToken to burn." + }, + "returns": { + "queued": "Cumulative total of all asset queued including already claimed requests.", + "requestId": "Unique ID for the withdrawal request" + } + }, + "setAutoAllocateThreshold(uint256)": { + "params": { + "_threshold": "OToken amount with 18 fixed decimals." + } + }, + "setDefaultStrategy(address)": { + "params": { + "_strategy": "Address of the Strategy" + } + }, + "setDripDuration(uint256)": { + "params": { + "_dripDuration": "Time in seconds to target a constant yield rate" + } + }, + "setRebaseRateMax(uint256)": { + "params": { + "apr": "in 1e18 notation. 3 * 1e18 = 3% APR" + } + }, + "setRebaseThreshold(uint256)": { + "params": { + "_threshold": "OToken amount with 18 fixed decimals." + } + }, + "setStrategistAddr(address)": { + "params": { + "_address": "Address of Strategist" + } + }, + "setVaultBuffer(uint256)": { + "params": { + "_vaultBuffer": "Percentage using 18 decimals. 100% = 1e18." + } + }, + "setWithdrawalClaimDelay(uint256)": { + "params": { + "_delay": "Delay period (should be between 10 mins to 7 days). Set to 0 to disable async withdrawals" + } + }, + "totalValue()": { + "returns": { + "value": "Total value in USD/ETH (1e18)" + } + }, + "transferGovernance(address)": { + "params": { + "_newGovernor": "Address of the new Governor" + } + }, + "transferToken(address,uint256)": { + "params": { + "_amount": "Amount of the asset to transfer", + "_asset": "Address for the asset" + } + }, + "withdrawAllFromStrategy(address)": { + "params": { + "_strategyAddr": "Strategy address." + } + }, + "withdrawFromStrategy(address,address[],uint256[])": { + "params": { + "_amounts": "Array of amounts of each corresponding asset to withdraw.", + "_assets": "Array of asset address that will be withdrawn from the strategy.", + "_strategyFromAddress": "Address of the Strategy to withdraw asset from." + } + } + }, + "title": "OUSD VaultAdmin Contract", + "version": 1 + }, + "userdoc": { + "kind": "user", + "methods": { + "addStrategyToMintWhitelist(address)": { + "notice": "Adds a strategy to the mint whitelist. Reverts if strategy isn't approved on Vault." + }, + "addWithdrawalQueueLiquidity()": { + "notice": "Adds WETH to the withdrawal queue if there is a funding shortfall." + }, + "allocate()": { + "notice": "Allocate unallocated funds on Vault to strategies." + }, + "approveStrategy(address)": { + "notice": "Add a strategy to the Vault." + }, + "autoAllocateThreshold()": { + "notice": "OToken mints over this amount automatically allocate funds. 18 decimals." + }, + "burnForStrategy(uint256)": { + "notice": "Burn OTokens for an allowed Strategy" + }, + "capitalPaused()": { + "notice": "pause operations that change the OToken supply. eg mint, redeem, allocate, mint/burn for strategy" + }, + "checkBalance(address)": { + "notice": "Get the balance of an asset held in Vault and all strategies." + }, + "claimGovernance()": { + "notice": "Claim Governance of the contract to a new account (`newGovernor`). Can only be called by the new Governor." + }, + "claimWithdrawal(uint256)": { + "notice": "Claim a previously requested withdrawal once it is claimable. This request can be claimed once the withdrawal queue's `claimable` amount is greater than or equal this request's `queued` amount and 10 minutes has passed. If the requests is not claimable, the transaction will revert with `Queue pending liquidity`. If the request is not older than 10 minutes, the transaction will revert with `Claim delay not met`. OToken is converted to asset at 1:1." + }, + "claimWithdrawals(uint256[])": { + "notice": "Claim a previously requested withdrawals once they are claimable. This requests can be claimed once the withdrawal queue's `claimable` amount is greater than or equal each request's `queued` amount and 10 minutes has passed. If one of the requests is not claimable, the whole transaction will revert with `Queue pending liquidity`. If one of the requests is not older than 10 minutes, the whole transaction will revert with `Claim delay not met`." + }, + "defaultStrategy()": { + "notice": "Default strategy for asset" + }, + "depositToStrategy(address,address[],uint256[])": { + "notice": "Deposit multiple asset from the vault into the strategy." + }, + "dripDuration()": { + "notice": "Automatic rebase yield calculations. In seconds. Set to 0 or 1 to disable." + }, + "getAllAssets()": { + "notice": "Return all vault asset addresses in order" + }, + "getAllStrategies()": { + "notice": "Return the array of all strategies" + }, + "getAssetCount()": { + "notice": "Return the number of asset supported by the Vault." + }, + "getStrategyCount()": { + "notice": "Return the number of strategies active on the Vault." + }, + "governor()": { + "notice": "Returns the address of the current Governor." + }, + "isGovernor()": { + "notice": "Returns true if the caller is the current Governor." + }, + "isSupportedAsset(address)": { + "notice": "Returns whether the vault supports the asset" + }, + "lastRebase()": { + "notice": "Time in seconds that the vault last rebased yield." + }, + "maxSupplyDiff()": { + "notice": "Max difference between total supply and total value of assets. 18 decimals." + }, + "mint(address,uint256,uint256)": { + "notice": "Deposit a supported asset and mint OTokens." + }, + "mint(uint256)": { + "notice": "Deposit a supported asset and mint OTokens." + }, + "mintForStrategy(uint256)": { + "notice": "Mint OTokens for an allowed Strategy" + }, + "oUSD()": { + "notice": "Deprecated: use `oToken()` instead." + }, + "pauseCapital()": { + "notice": "Set the deposit paused flag to true to prevent capital movement." + }, + "pauseRebase()": { + "notice": "Set the deposit paused flag to true to prevent rebasing." + }, + "previewYield()": { + "notice": "Calculates the amount that would rebase at next rebase. This is before any fees." + }, + "rebase()": { + "notice": "Calculate the total value of asset held by the Vault and all strategies and update the supply of OTokens." + }, + "rebasePaused()": { + "notice": "pause rebasing if true" + }, + "rebasePerSecondMax()": { + "notice": "max rebase percentage per second Can be used to set maximum yield of the protocol, spreading out yield over time" + }, + "rebasePerSecondTarget()": { + "notice": "target rebase rate limit, based on past rates and funds available." + }, + "rebaseThreshold()": { + "notice": "OToken mints over this amount automatically rebase. 18 decimals." + }, + "removeStrategy(address)": { + "notice": "Remove a strategy from the Vault." + }, + "removeStrategyFromMintWhitelist(address)": { + "notice": "Removes a strategy from the mint whitelist." + }, + "requestWithdrawal(uint256)": { + "notice": "Request an asynchronous withdrawal of asset in exchange for OToken. The OToken is burned on request and the asset is transferred to the withdrawer on claim. This request can be claimed once the withdrawal queue's `claimable` amount is greater than or equal this request's `queued` amount. There is a minimum of 10 minutes before a request can be claimed. After that, the request just needs enough asset liquidity in the Vault to satisfy all the outstanding requests to that point in the queue. OToken is converted to asset at 1:1." + }, + "setAutoAllocateThreshold(uint256)": { + "notice": "Sets the minimum amount of OTokens in a mint to trigger an automatic allocation of funds afterwords." + }, + "setDefaultStrategy(address)": { + "notice": "Set the default Strategy for asset, i.e. the one which the asset will be automatically allocated to and withdrawn from" + }, + "setDripDuration(uint256)": { + "notice": "Set the drip duration period" + }, + "setMaxSupplyDiff(uint256)": { + "notice": "Sets the maximum allowable difference between total supply and asset' value." + }, + "setRebaseRateMax(uint256)": { + "notice": "Set a yield streaming max rate. This spreads yield over time if it is above the max rate. This is a per rebase APR which due to compounding differs from the yearly APR. Governance should consider this fact when picking a desired APR" + }, + "setRebaseThreshold(uint256)": { + "notice": "Set a minimum amount of OTokens in a mint or redeem that triggers a rebase" + }, + "setStrategistAddr(address)": { + "notice": "Set address of Strategist" + }, + "setTrusteeAddress(address)": { + "notice": "Sets the trusteeAddress that can receive a portion of yield. Setting to the zero address disables this feature." + }, + "setTrusteeFeeBps(uint256)": { + "notice": "Sets the TrusteeFeeBps to the percentage of yield that should be received in basis points." + }, + "setVaultBuffer(uint256)": { + "notice": "Set a buffer of asset to keep in the Vault to handle most redemptions without needing to spend gas unwinding asset from a Strategy." + }, + "setWithdrawalClaimDelay(uint256)": { + "notice": "Changes the async withdrawal claim period for OETH & superOETHb" + }, + "strategistAddr()": { + "notice": "Address of the Strategist" + }, + "totalValue()": { + "notice": "Determine the total value of asset held by the vault and its strategies." + }, + "transferGovernance(address)": { + "notice": "Transfers Governance of the contract to a new account (`newGovernor`). Can only be called by the current Governor. Must be claimed for this to complete" + }, + "transferToken(address,uint256)": { + "notice": "Transfer token to governor. Intended for recovering tokens stuck in contract, i.e. mistaken sends." + }, + "trusteeAddress()": { + "notice": "Trustee contract that can collect a percentage of yield" + }, + "trusteeFeeBps()": { + "notice": "Amount of yield collected in basis points. eg 2000 = 20%" + }, + "unpauseCapital()": { + "notice": "Set the deposit paused flag to false to enable capital movement." + }, + "unpauseRebase()": { + "notice": "Set the deposit paused flag to true to allow rebasing." + }, + "vaultBuffer()": { + "notice": "Percentage of assets to keep in Vault to handle (most) withdrawals. 100% = 1e18." + }, + "withdrawAllFromStrategies()": { + "notice": "Withdraws all asset from all the strategies and sends asset to the Vault." + }, + "withdrawAllFromStrategy(address)": { + "notice": "Withdraws all asset from the strategy and sends asset to the Vault." + }, + "withdrawFromStrategy(address,address[],uint256[])": { + "notice": "Withdraw multiple asset from the strategy to the vault." + }, + "withdrawalClaimDelay()": { + "notice": "Sets a minimum delay that is required to elapse between requesting async withdrawals and claiming the request. When set to 0 async withdrawals are disabled." + }, + "withdrawalQueueMetadata()": { + "notice": "Global metadata for the withdrawal queue including: queued - cumulative total of all withdrawal requests included the ones that have already been claimed claimable - cumulative total of all the requests that can be claimed including the ones already claimed claimed - total of all the requests that have been claimed nextWithdrawalIndex - index of the next withdrawal request starting at 0" + }, + "withdrawalRequests(uint256)": { + "notice": "Mapping of withdrawal request indices to the user withdrawal request data" + } + }, + "version": 1 + }, + "storageLayout": { + "storage": [ + { + "astId": 59818, + "contract": "contracts/vault/OUSDVault.sol:OUSDVault", + "label": "initialized", + "offset": 0, + "slot": "0", + "type": "t_bool" + }, + { + "astId": 59821, + "contract": "contracts/vault/OUSDVault.sol:OUSDVault", + "label": "initializing", + "offset": 1, + "slot": "0", + "type": "t_bool" + }, + { + "astId": 59861, + "contract": "contracts/vault/OUSDVault.sol:OUSDVault", + "label": "______gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage" + }, + { + "astId": 63565, + "contract": "contracts/vault/OUSDVault.sol:OUSDVault", + "label": "_deprecated_assets", + "offset": 0, + "slot": "51", + "type": "t_uint256" + }, + { + "astId": 63569, + "contract": "contracts/vault/OUSDVault.sol:OUSDVault", + "label": "_deprecated_allAssets", + "offset": 0, + "slot": "52", + "type": "t_array(t_address)dyn_storage" + }, + { + "astId": 63580, + "contract": "contracts/vault/OUSDVault.sol:OUSDVault", + "label": "strategies", + "offset": 0, + "slot": "53", + "type": "t_mapping(t_address,t_struct(Strategy)63574_storage)" + }, + { + "astId": 63584, + "contract": "contracts/vault/OUSDVault.sol:OUSDVault", + "label": "allStrategies", + "offset": 0, + "slot": "54", + "type": "t_array(t_address)dyn_storage" + }, + { + "astId": 63587, + "contract": "contracts/vault/OUSDVault.sol:OUSDVault", + "label": "_deprecated_priceProvider", + "offset": 0, + "slot": "55", + "type": "t_address" + }, + { + "astId": 63590, + "contract": "contracts/vault/OUSDVault.sol:OUSDVault", + "label": "rebasePaused", + "offset": 20, + "slot": "55", + "type": "t_bool" + }, + { + "astId": 63593, + "contract": "contracts/vault/OUSDVault.sol:OUSDVault", + "label": "capitalPaused", + "offset": 21, + "slot": "55", + "type": "t_bool" + }, + { + "astId": 63596, + "contract": "contracts/vault/OUSDVault.sol:OUSDVault", + "label": "_deprecated_redeemFeeBps", + "offset": 0, + "slot": "56", + "type": "t_uint256" + }, + { + "astId": 63599, + "contract": "contracts/vault/OUSDVault.sol:OUSDVault", + "label": "vaultBuffer", + "offset": 0, + "slot": "57", + "type": "t_uint256" + }, + { + "astId": 63602, + "contract": "contracts/vault/OUSDVault.sol:OUSDVault", + "label": "autoAllocateThreshold", + "offset": 0, + "slot": "58", + "type": "t_uint256" + }, + { + "astId": 63605, + "contract": "contracts/vault/OUSDVault.sol:OUSDVault", + "label": "rebaseThreshold", + "offset": 0, + "slot": "59", + "type": "t_uint256" + }, + { + "astId": 63609, + "contract": "contracts/vault/OUSDVault.sol:OUSDVault", + "label": "oToken", + "offset": 0, + "slot": "60", + "type": "t_contract(OUSD)58565" + }, + { + "astId": 63616, + "contract": "contracts/vault/OUSDVault.sol:OUSDVault", + "label": "_deprecated_rebaseHooksAddr", + "offset": 0, + "slot": "61", + "type": "t_address" + }, + { + "astId": 63623, + "contract": "contracts/vault/OUSDVault.sol:OUSDVault", + "label": "_deprecated_uniswapAddr", + "offset": 0, + "slot": "62", + "type": "t_address" + }, + { + "astId": 63630, + "contract": "contracts/vault/OUSDVault.sol:OUSDVault", + "label": "strategistAddr", + "offset": 0, + "slot": "63", + "type": "t_address" + }, + { + "astId": 63633, + "contract": "contracts/vault/OUSDVault.sol:OUSDVault", + "label": "_deprecated_assetDefaultStrategies", + "offset": 0, + "slot": "64", + "type": "t_uint256" + }, + { + "astId": 63636, + "contract": "contracts/vault/OUSDVault.sol:OUSDVault", + "label": "maxSupplyDiff", + "offset": 0, + "slot": "65", + "type": "t_uint256" + }, + { + "astId": 63639, + "contract": "contracts/vault/OUSDVault.sol:OUSDVault", + "label": "trusteeAddress", + "offset": 0, + "slot": "66", + "type": "t_address" + }, + { + "astId": 63642, + "contract": "contracts/vault/OUSDVault.sol:OUSDVault", + "label": "trusteeFeeBps", + "offset": 0, + "slot": "67", + "type": "t_uint256" + }, + { + "astId": 63646, + "contract": "contracts/vault/OUSDVault.sol:OUSDVault", + "label": "_deprecated_swapTokens", + "offset": 0, + "slot": "68", + "type": "t_array(t_address)dyn_storage" + }, + { + "astId": 63649, + "contract": "contracts/vault/OUSDVault.sol:OUSDVault", + "label": "_deprecated_ousdMetaStrategy", + "offset": 0, + "slot": "69", + "type": "t_address" + }, + { + "astId": 63652, + "contract": "contracts/vault/OUSDVault.sol:OUSDVault", + "label": "_deprecated_netOusdMintedForStrategy", + "offset": 0, + "slot": "70", + "type": "t_int256" + }, + { + "astId": 63655, + "contract": "contracts/vault/OUSDVault.sol:OUSDVault", + "label": "_deprecated_netOusdMintForStrategyThreshold", + "offset": 0, + "slot": "71", + "type": "t_uint256" + }, + { + "astId": 63657, + "contract": "contracts/vault/OUSDVault.sol:OUSDVault", + "label": "_deprecated_swapConfig", + "offset": 0, + "slot": "72", + "type": "t_uint256" + }, + { + "astId": 63661, + "contract": "contracts/vault/OUSDVault.sol:OUSDVault", + "label": "isMintWhitelistedStrategy", + "offset": 0, + "slot": "73", + "type": "t_mapping(t_address,t_bool)" + }, + { + "astId": 63664, + "contract": "contracts/vault/OUSDVault.sol:OUSDVault", + "label": "_deprecated_dripper", + "offset": 0, + "slot": "74", + "type": "t_address" + }, + { + "astId": 63678, + "contract": "contracts/vault/OUSDVault.sol:OUSDVault", + "label": "withdrawalQueueMetadata", + "offset": 0, + "slot": "75", + "type": "t_struct(WithdrawalQueueMetadata)63674_storage" + }, + { + "astId": 63695, + "contract": "contracts/vault/OUSDVault.sol:OUSDVault", + "label": "withdrawalRequests", + "offset": 0, + "slot": "77", + "type": "t_mapping(t_uint256,t_struct(WithdrawalRequest)63689_storage)" + }, + { + "astId": 63698, + "contract": "contracts/vault/OUSDVault.sol:OUSDVault", + "label": "withdrawalClaimDelay", + "offset": 0, + "slot": "78", + "type": "t_uint256" + }, + { + "astId": 63701, + "contract": "contracts/vault/OUSDVault.sol:OUSDVault", + "label": "lastRebase", + "offset": 0, + "slot": "79", + "type": "t_uint64" + }, + { + "astId": 63704, + "contract": "contracts/vault/OUSDVault.sol:OUSDVault", + "label": "dripDuration", + "offset": 8, + "slot": "79", + "type": "t_uint64" + }, + { + "astId": 63707, + "contract": "contracts/vault/OUSDVault.sol:OUSDVault", + "label": "rebasePerSecondMax", + "offset": 16, + "slot": "79", + "type": "t_uint64" + }, + { + "astId": 63710, + "contract": "contracts/vault/OUSDVault.sol:OUSDVault", + "label": "rebasePerSecondTarget", + "offset": 24, + "slot": "79", + "type": "t_uint64" + }, + { + "astId": 63724, + "contract": "contracts/vault/OUSDVault.sol:OUSDVault", + "label": "defaultStrategy", + "offset": 0, + "slot": "80", + "type": "t_address" + }, + { + "astId": 63728, + "contract": "contracts/vault/OUSDVault.sol:OUSDVault", + "label": "__gap", + "offset": 0, + "slot": "81", + "type": "t_array(t_uint256)42_storage" + }, + { + "astId": 63731, + "contract": "contracts/vault/OUSDVault.sol:OUSDVault", + "label": "_deprecated_wethAssetIndex", + "offset": 0, + "slot": "123", + "type": "t_uint256" + } + ], + "types": { + "t_address": { + "encoding": "inplace", + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_address)dyn_storage": { + "base": "t_address", + "encoding": "dynamic_array", + "label": "address[]", + "numberOfBytes": "32" + }, + "t_array(t_uint256)42_storage": { + "base": "t_uint256", + "encoding": "inplace", + "label": "uint256[42]", + "numberOfBytes": "1344" + }, + "t_array(t_uint256)50_storage": { + "base": "t_uint256", + "encoding": "inplace", + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "encoding": "inplace", + "label": "bool", + "numberOfBytes": "1" + }, + "t_contract(OUSD)58565": { + "encoding": "inplace", + "label": "contract OUSD", + "numberOfBytes": "20" + }, + "t_int256": { + "encoding": "inplace", + "label": "int256", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_bool)": { + "encoding": "mapping", + "key": "t_address", + "label": "mapping(address => bool)", + "numberOfBytes": "32", + "value": "t_bool" + }, + "t_mapping(t_address,t_struct(Strategy)63574_storage)": { + "encoding": "mapping", + "key": "t_address", + "label": "mapping(address => struct VaultStorage.Strategy)", + "numberOfBytes": "32", + "value": "t_struct(Strategy)63574_storage" + }, + "t_mapping(t_uint256,t_struct(WithdrawalRequest)63689_storage)": { + "encoding": "mapping", + "key": "t_uint256", + "label": "mapping(uint256 => struct VaultStorage.WithdrawalRequest)", + "numberOfBytes": "32", + "value": "t_struct(WithdrawalRequest)63689_storage" + }, + "t_struct(Strategy)63574_storage": { + "encoding": "inplace", + "label": "struct VaultStorage.Strategy", + "members": [ + { + "astId": 63571, + "contract": "contracts/vault/OUSDVault.sol:OUSDVault", + "label": "isSupported", + "offset": 0, + "slot": "0", + "type": "t_bool" + }, + { + "astId": 63573, + "contract": "contracts/vault/OUSDVault.sol:OUSDVault", + "label": "_deprecated", + "offset": 0, + "slot": "1", + "type": "t_uint256" + } + ], + "numberOfBytes": "64" + }, + "t_struct(WithdrawalQueueMetadata)63674_storage": { + "encoding": "inplace", + "label": "struct VaultStorage.WithdrawalQueueMetadata", + "members": [ + { + "astId": 63667, + "contract": "contracts/vault/OUSDVault.sol:OUSDVault", + "label": "queued", + "offset": 0, + "slot": "0", + "type": "t_uint128" + }, + { + "astId": 63669, + "contract": "contracts/vault/OUSDVault.sol:OUSDVault", + "label": "claimable", + "offset": 16, + "slot": "0", + "type": "t_uint128" + }, + { + "astId": 63671, + "contract": "contracts/vault/OUSDVault.sol:OUSDVault", + "label": "claimed", + "offset": 0, + "slot": "1", + "type": "t_uint128" + }, + { + "astId": 63673, + "contract": "contracts/vault/OUSDVault.sol:OUSDVault", + "label": "nextWithdrawalIndex", + "offset": 16, + "slot": "1", + "type": "t_uint128" + } + ], + "numberOfBytes": "64" + }, + "t_struct(WithdrawalRequest)63689_storage": { + "encoding": "inplace", + "label": "struct VaultStorage.WithdrawalRequest", + "members": [ + { + "astId": 63680, + "contract": "contracts/vault/OUSDVault.sol:OUSDVault", + "label": "withdrawer", + "offset": 0, + "slot": "0", + "type": "t_address" + }, + { + "astId": 63682, + "contract": "contracts/vault/OUSDVault.sol:OUSDVault", + "label": "claimed", + "offset": 20, + "slot": "0", + "type": "t_bool" + }, + { + "astId": 63684, + "contract": "contracts/vault/OUSDVault.sol:OUSDVault", + "label": "timestamp", + "offset": 21, + "slot": "0", + "type": "t_uint40" + }, + { + "astId": 63686, + "contract": "contracts/vault/OUSDVault.sol:OUSDVault", + "label": "amount", + "offset": 0, + "slot": "1", + "type": "t_uint128" + }, + { + "astId": 63688, + "contract": "contracts/vault/OUSDVault.sol:OUSDVault", + "label": "queued", + "offset": 16, + "slot": "1", + "type": "t_uint128" + } + ], + "numberOfBytes": "64" + }, + "t_uint128": { + "encoding": "inplace", + "label": "uint128", + "numberOfBytes": "16" + }, + "t_uint256": { + "encoding": "inplace", + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint40": { + "encoding": "inplace", + "label": "uint40", + "numberOfBytes": "5" + }, + "t_uint64": { + "encoding": "inplace", + "label": "uint64", + "numberOfBytes": "8" + } + } + } +} \ No newline at end of file diff --git a/contracts/deployments/mainnet/solcInputs/246907a3fd0d7c6bfd72156e1588273e.json b/contracts/deployments/mainnet/solcInputs/246907a3fd0d7c6bfd72156e1588273e.json new file mode 100644 index 0000000000..ecbbdfc978 --- /dev/null +++ b/contracts/deployments/mainnet/solcInputs/246907a3fd0d7c6bfd72156e1588273e.json @@ -0,0 +1,747 @@ +{ + "language": "Solidity", + "sources": { + "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IRouterClient.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport {Client} from \"../libraries/Client.sol\";\n\ninterface IRouterClient {\n error UnsupportedDestinationChain(uint64 destChainSelector);\n error InsufficientFeeTokenAmount();\n error InvalidMsgValue();\n\n /// @notice Checks if the given chain ID is supported for sending/receiving.\n /// @param chainSelector The chain to check.\n /// @return supported is true if it is supported, false if not.\n function isChainSupported(uint64 chainSelector) external view returns (bool supported);\n\n /// @notice Gets a list of all supported tokens which can be sent or received\n /// to/from a given chain id.\n /// @param chainSelector The chainSelector.\n /// @return tokens The addresses of all tokens that are supported.\n function getSupportedTokens(uint64 chainSelector) external view returns (address[] memory tokens);\n\n /// @param destinationChainSelector The destination chainSelector\n /// @param message The cross-chain CCIP message including data and/or tokens\n /// @return fee returns execution fee for the message\n /// delivery to destination chain, denominated in the feeToken specified in the message.\n /// @dev Reverts with appropriate reason upon invalid message.\n function getFee(\n uint64 destinationChainSelector,\n Client.EVM2AnyMessage memory message\n ) external view returns (uint256 fee);\n\n /// @notice Request a message to be sent to the destination chain\n /// @param destinationChainSelector The destination chain ID\n /// @param message The cross-chain CCIP message including data and/or tokens\n /// @return messageId The message ID\n /// @dev Note if msg.value is larger than the required fee (from getFee) we accept\n /// the overpayment with no refund.\n /// @dev Reverts with appropriate reason upon invalid message.\n function ccipSend(\n uint64 destinationChainSelector,\n Client.EVM2AnyMessage calldata message\n ) external payable returns (bytes32);\n}\n" + }, + "@chainlink/contracts-ccip/src/v0.8/ccip/libraries/Client.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\n// End consumer library.\nlibrary Client {\n /// @dev RMN depends on this struct, if changing, please notify the RMN maintainers.\n struct EVMTokenAmount {\n address token; // token address on the local chain.\n uint256 amount; // Amount of tokens.\n }\n\n struct Any2EVMMessage {\n bytes32 messageId; // MessageId corresponding to ccipSend on source.\n uint64 sourceChainSelector; // Source chain selector.\n bytes sender; // abi.decode(sender) if coming from an EVM chain.\n bytes data; // payload sent in original message.\n EVMTokenAmount[] destTokenAmounts; // Tokens and their amounts in their destination chain representation.\n }\n\n // If extraArgs is empty bytes, the default is 200k gas limit.\n struct EVM2AnyMessage {\n bytes receiver; // abi.encode(receiver address) for dest EVM chains\n bytes data; // Data payload\n EVMTokenAmount[] tokenAmounts; // Token transfers\n address feeToken; // Address of feeToken. address(0) means you will send msg.value.\n bytes extraArgs; // Populate this with _argsToBytes(EVMExtraArgsV1)\n }\n\n // bytes4(keccak256(\"CCIP EVMExtraArgsV1\"));\n bytes4 public constant EVM_EXTRA_ARGS_V1_TAG = 0x97a657c9;\n struct EVMExtraArgsV1 {\n uint256 gasLimit;\n }\n\n function _argsToBytes(EVMExtraArgsV1 memory extraArgs) internal pure returns (bytes memory bts) {\n return abi.encodeWithSelector(EVM_EXTRA_ARGS_V1_TAG, extraArgs);\n }\n}\n" + }, + "@layerzerolabs/lz-evm-messagelib-v2/contracts/libs/ExecutorOptions.sol": { + "content": "// SPDX-License-Identifier: LZBL-1.2\n\npragma solidity ^0.8.20;\n\nimport \"@layerzerolabs/lz-evm-protocol-v2/contracts/libs/CalldataBytesLib.sol\";\n\nlibrary ExecutorOptions {\n using CalldataBytesLib for bytes;\n\n uint8 internal constant WORKER_ID = 1;\n\n uint8 internal constant OPTION_TYPE_LZRECEIVE = 1;\n uint8 internal constant OPTION_TYPE_NATIVE_DROP = 2;\n uint8 internal constant OPTION_TYPE_LZCOMPOSE = 3;\n uint8 internal constant OPTION_TYPE_ORDERED_EXECUTION = 4;\n uint8 internal constant OPTION_TYPE_LZREAD = 5;\n\n error Executor_InvalidLzReceiveOption();\n error Executor_InvalidNativeDropOption();\n error Executor_InvalidLzComposeOption();\n error Executor_InvalidLzReadOption();\n\n /// @dev decode the next executor option from the options starting from the specified cursor\n /// @param _options [executor_id][executor_option][executor_id][executor_option]...\n /// executor_option = [option_size][option_type][option]\n /// option_size = len(option_type) + len(option)\n /// executor_id: uint8, option_size: uint16, option_type: uint8, option: bytes\n /// @param _cursor the cursor to start decoding from\n /// @return optionType the type of the option\n /// @return option the option of the executor\n /// @return cursor the cursor to start decoding the next executor option\n function nextExecutorOption(\n bytes calldata _options,\n uint256 _cursor\n ) internal pure returns (uint8 optionType, bytes calldata option, uint256 cursor) {\n unchecked {\n // skip worker id\n cursor = _cursor + 1;\n\n // read option size\n uint16 size = _options.toU16(cursor);\n cursor += 2;\n\n // read option type\n optionType = _options.toU8(cursor);\n\n // startCursor and endCursor are used to slice the option from _options\n uint256 startCursor = cursor + 1; // skip option type\n uint256 endCursor = cursor + size;\n option = _options[startCursor:endCursor];\n cursor += size;\n }\n }\n\n function decodeLzReceiveOption(bytes calldata _option) internal pure returns (uint128 gas, uint128 value) {\n if (_option.length != 16 && _option.length != 32) revert Executor_InvalidLzReceiveOption();\n gas = _option.toU128(0);\n value = _option.length == 32 ? _option.toU128(16) : 0;\n }\n\n function decodeNativeDropOption(bytes calldata _option) internal pure returns (uint128 amount, bytes32 receiver) {\n if (_option.length != 48) revert Executor_InvalidNativeDropOption();\n amount = _option.toU128(0);\n receiver = _option.toB32(16);\n }\n\n function decodeLzComposeOption(\n bytes calldata _option\n ) internal pure returns (uint16 index, uint128 gas, uint128 value) {\n if (_option.length != 18 && _option.length != 34) revert Executor_InvalidLzComposeOption();\n index = _option.toU16(0);\n gas = _option.toU128(2);\n value = _option.length == 34 ? _option.toU128(18) : 0;\n }\n\n function decodeLzReadOption(\n bytes calldata _option\n ) internal pure returns (uint128 gas, uint32 calldataSize, uint128 value) {\n if (_option.length != 20 && _option.length != 36) revert Executor_InvalidLzReadOption();\n gas = _option.toU128(0);\n calldataSize = _option.toU32(16);\n value = _option.length == 36 ? _option.toU128(20) : 0;\n }\n\n function encodeLzReceiveOption(uint128 _gas, uint128 _value) internal pure returns (bytes memory) {\n return _value == 0 ? abi.encodePacked(_gas) : abi.encodePacked(_gas, _value);\n }\n\n function encodeNativeDropOption(uint128 _amount, bytes32 _receiver) internal pure returns (bytes memory) {\n return abi.encodePacked(_amount, _receiver);\n }\n\n function encodeLzComposeOption(uint16 _index, uint128 _gas, uint128 _value) internal pure returns (bytes memory) {\n return _value == 0 ? abi.encodePacked(_index, _gas) : abi.encodePacked(_index, _gas, _value);\n }\n\n function encodeLzReadOption(\n uint128 _gas,\n uint32 _calldataSize,\n uint128 _value\n ) internal pure returns (bytes memory) {\n return _value == 0 ? abi.encodePacked(_gas, _calldataSize) : abi.encodePacked(_gas, _calldataSize, _value);\n }\n}\n" + }, + "@layerzerolabs/lz-evm-messagelib-v2/contracts/uln/libs/DVNOptions.sol": { + "content": "// SPDX-License-Identifier: LZBL-1.2\n\npragma solidity ^0.8.20;\n\nimport { BytesLib } from \"solidity-bytes-utils/contracts/BytesLib.sol\";\n\nimport { BitMap256 } from \"@layerzerolabs/lz-evm-protocol-v2/contracts/messagelib/libs/BitMaps.sol\";\nimport { CalldataBytesLib } from \"@layerzerolabs/lz-evm-protocol-v2/contracts/libs/CalldataBytesLib.sol\";\n\nlibrary DVNOptions {\n using CalldataBytesLib for bytes;\n using BytesLib for bytes;\n\n uint8 internal constant WORKER_ID = 2;\n uint8 internal constant OPTION_TYPE_PRECRIME = 1;\n\n error DVN_InvalidDVNIdx();\n error DVN_InvalidDVNOptions(uint256 cursor);\n\n /// @dev group dvn options by its idx\n /// @param _options [dvn_id][dvn_option][dvn_id][dvn_option]...\n /// dvn_option = [option_size][dvn_idx][option_type][option]\n /// option_size = len(dvn_idx) + len(option_type) + len(option)\n /// dvn_id: uint8, dvn_idx: uint8, option_size: uint16, option_type: uint8, option: bytes\n /// @return dvnOptions the grouped options, still share the same format of _options\n /// @return dvnIndices the dvn indices\n function groupDVNOptionsByIdx(\n bytes memory _options\n ) internal pure returns (bytes[] memory dvnOptions, uint8[] memory dvnIndices) {\n if (_options.length == 0) return (dvnOptions, dvnIndices);\n\n uint8 numDVNs = getNumDVNs(_options);\n\n // if there is only 1 dvn, we can just return the whole options\n if (numDVNs == 1) {\n dvnOptions = new bytes[](1);\n dvnOptions[0] = _options;\n\n dvnIndices = new uint8[](1);\n dvnIndices[0] = _options.toUint8(3); // dvn idx\n return (dvnOptions, dvnIndices);\n }\n\n // otherwise, we need to group the options by dvn_idx\n dvnIndices = new uint8[](numDVNs);\n dvnOptions = new bytes[](numDVNs);\n unchecked {\n uint256 cursor = 0;\n uint256 start = 0;\n uint8 lastDVNIdx = 255; // 255 is an invalid dvn_idx\n\n while (cursor < _options.length) {\n ++cursor; // skip worker_id\n\n // optionLength asserted in getNumDVNs (skip check)\n uint16 optionLength = _options.toUint16(cursor);\n cursor += 2;\n\n // dvnIdx asserted in getNumDVNs (skip check)\n uint8 dvnIdx = _options.toUint8(cursor);\n\n // dvnIdx must equal to the lastDVNIdx for the first option\n // so it is always skipped in the first option\n // this operation slices out options whenever the scan finds a different lastDVNIdx\n if (lastDVNIdx == 255) {\n lastDVNIdx = dvnIdx;\n } else if (dvnIdx != lastDVNIdx) {\n uint256 len = cursor - start - 3; // 3 is for worker_id and option_length\n bytes memory opt = _options.slice(start, len);\n _insertDVNOptions(dvnOptions, dvnIndices, lastDVNIdx, opt);\n\n // reset the start and lastDVNIdx\n start += len;\n lastDVNIdx = dvnIdx;\n }\n\n cursor += optionLength;\n }\n\n // skip check the cursor here because the cursor is asserted in getNumDVNs\n // if we have reached the end of the options, we need to process the last dvn\n uint256 size = cursor - start;\n bytes memory op = _options.slice(start, size);\n _insertDVNOptions(dvnOptions, dvnIndices, lastDVNIdx, op);\n\n // revert dvnIndices to start from 0\n for (uint8 i = 0; i < numDVNs; ++i) {\n --dvnIndices[i];\n }\n }\n }\n\n function _insertDVNOptions(\n bytes[] memory _dvnOptions,\n uint8[] memory _dvnIndices,\n uint8 _dvnIdx,\n bytes memory _newOptions\n ) internal pure {\n // dvnIdx starts from 0 but default value of dvnIndices is 0,\n // so we tell if the slot is empty by adding 1 to dvnIdx\n if (_dvnIdx == 255) revert DVN_InvalidDVNIdx();\n uint8 dvnIdxAdj = _dvnIdx + 1;\n\n for (uint256 j = 0; j < _dvnIndices.length; ++j) {\n uint8 index = _dvnIndices[j];\n if (dvnIdxAdj == index) {\n _dvnOptions[j] = abi.encodePacked(_dvnOptions[j], _newOptions);\n break;\n } else if (index == 0) {\n // empty slot, that means it is the first time we see this dvn\n _dvnIndices[j] = dvnIdxAdj;\n _dvnOptions[j] = _newOptions;\n break;\n }\n }\n }\n\n /// @dev get the number of unique dvns\n /// @param _options the format is the same as groupDVNOptionsByIdx\n function getNumDVNs(bytes memory _options) internal pure returns (uint8 numDVNs) {\n uint256 cursor = 0;\n BitMap256 bitmap;\n\n // find number of unique dvn_idx\n unchecked {\n while (cursor < _options.length) {\n ++cursor; // skip worker_id\n\n uint16 optionLength = _options.toUint16(cursor);\n cursor += 2;\n if (optionLength < 2) revert DVN_InvalidDVNOptions(cursor); // at least 1 byte for dvn_idx and 1 byte for option_type\n\n uint8 dvnIdx = _options.toUint8(cursor);\n\n // if dvnIdx is not set, increment numDVNs\n // max num of dvns is 255, 255 is an invalid dvn_idx\n // The order of the dvnIdx is not required to be sequential, as enforcing the order may weaken\n // the composability of the options. e.g. if we refrain from enforcing the order, an OApp that has\n // already enforced certain options can append additional options to the end of the enforced\n // ones without restrictions.\n if (dvnIdx == 255) revert DVN_InvalidDVNIdx();\n if (!bitmap.get(dvnIdx)) {\n ++numDVNs;\n bitmap = bitmap.set(dvnIdx);\n }\n\n cursor += optionLength;\n }\n }\n if (cursor != _options.length) revert DVN_InvalidDVNOptions(cursor);\n }\n\n /// @dev decode the next dvn option from _options starting from the specified cursor\n /// @param _options the format is the same as groupDVNOptionsByIdx\n /// @param _cursor the cursor to start decoding\n /// @return optionType the type of the option\n /// @return option the option\n /// @return cursor the cursor to start decoding the next option\n function nextDVNOption(\n bytes calldata _options,\n uint256 _cursor\n ) internal pure returns (uint8 optionType, bytes calldata option, uint256 cursor) {\n unchecked {\n // skip worker id\n cursor = _cursor + 1;\n\n // read option size\n uint16 size = _options.toU16(cursor);\n cursor += 2;\n\n // read option type\n optionType = _options.toU8(cursor + 1); // skip dvn_idx\n\n // startCursor and endCursor are used to slice the option from _options\n uint256 startCursor = cursor + 2; // skip option type and dvn_idx\n uint256 endCursor = cursor + size;\n option = _options[startCursor:endCursor];\n cursor += size;\n }\n }\n}\n" + }, + "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroEndpointV2.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity >=0.8.0;\n\nimport { IMessageLibManager } from \"./IMessageLibManager.sol\";\nimport { IMessagingComposer } from \"./IMessagingComposer.sol\";\nimport { IMessagingChannel } from \"./IMessagingChannel.sol\";\nimport { IMessagingContext } from \"./IMessagingContext.sol\";\n\nstruct MessagingParams {\n uint32 dstEid;\n bytes32 receiver;\n bytes message;\n bytes options;\n bool payInLzToken;\n}\n\nstruct MessagingReceipt {\n bytes32 guid;\n uint64 nonce;\n MessagingFee fee;\n}\n\nstruct MessagingFee {\n uint256 nativeFee;\n uint256 lzTokenFee;\n}\n\nstruct Origin {\n uint32 srcEid;\n bytes32 sender;\n uint64 nonce;\n}\n\ninterface ILayerZeroEndpointV2 is IMessageLibManager, IMessagingComposer, IMessagingChannel, IMessagingContext {\n event PacketSent(bytes encodedPayload, bytes options, address sendLibrary);\n\n event PacketVerified(Origin origin, address receiver, bytes32 payloadHash);\n\n event PacketDelivered(Origin origin, address receiver);\n\n event LzReceiveAlert(\n address indexed receiver,\n address indexed executor,\n Origin origin,\n bytes32 guid,\n uint256 gas,\n uint256 value,\n bytes message,\n bytes extraData,\n bytes reason\n );\n\n event LzTokenSet(address token);\n\n event DelegateSet(address sender, address delegate);\n\n function quote(MessagingParams calldata _params, address _sender) external view returns (MessagingFee memory);\n\n function send(\n MessagingParams calldata _params,\n address _refundAddress\n ) external payable returns (MessagingReceipt memory);\n\n function verify(Origin calldata _origin, address _receiver, bytes32 _payloadHash) external;\n\n function verifiable(Origin calldata _origin, address _receiver) external view returns (bool);\n\n function initializable(Origin calldata _origin, address _receiver) external view returns (bool);\n\n function lzReceive(\n Origin calldata _origin,\n address _receiver,\n bytes32 _guid,\n bytes calldata _message,\n bytes calldata _extraData\n ) external payable;\n\n // oapp can burn messages partially by calling this function with its own business logic if messages are verified in order\n function clear(address _oapp, Origin calldata _origin, bytes32 _guid, bytes calldata _message) external;\n\n function setLzToken(address _lzToken) external;\n\n function lzToken() external view returns (address);\n\n function nativeToken() external view returns (address);\n\n function setDelegate(address _delegate) external;\n}\n" + }, + "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/IMessageLibManager.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity >=0.8.0;\n\nstruct SetConfigParam {\n uint32 eid;\n uint32 configType;\n bytes config;\n}\n\ninterface IMessageLibManager {\n struct Timeout {\n address lib;\n uint256 expiry;\n }\n\n event LibraryRegistered(address newLib);\n event DefaultSendLibrarySet(uint32 eid, address newLib);\n event DefaultReceiveLibrarySet(uint32 eid, address newLib);\n event DefaultReceiveLibraryTimeoutSet(uint32 eid, address oldLib, uint256 expiry);\n event SendLibrarySet(address sender, uint32 eid, address newLib);\n event ReceiveLibrarySet(address receiver, uint32 eid, address newLib);\n event ReceiveLibraryTimeoutSet(address receiver, uint32 eid, address oldLib, uint256 timeout);\n\n function registerLibrary(address _lib) external;\n\n function isRegisteredLibrary(address _lib) external view returns (bool);\n\n function getRegisteredLibraries() external view returns (address[] memory);\n\n function setDefaultSendLibrary(uint32 _eid, address _newLib) external;\n\n function defaultSendLibrary(uint32 _eid) external view returns (address);\n\n function setDefaultReceiveLibrary(uint32 _eid, address _newLib, uint256 _gracePeriod) external;\n\n function defaultReceiveLibrary(uint32 _eid) external view returns (address);\n\n function setDefaultReceiveLibraryTimeout(uint32 _eid, address _lib, uint256 _expiry) external;\n\n function defaultReceiveLibraryTimeout(uint32 _eid) external view returns (address lib, uint256 expiry);\n\n function isSupportedEid(uint32 _eid) external view returns (bool);\n\n function isValidReceiveLibrary(address _receiver, uint32 _eid, address _lib) external view returns (bool);\n\n /// ------------------- OApp interfaces -------------------\n function setSendLibrary(address _oapp, uint32 _eid, address _newLib) external;\n\n function getSendLibrary(address _sender, uint32 _eid) external view returns (address lib);\n\n function isDefaultSendLibrary(address _sender, uint32 _eid) external view returns (bool);\n\n function setReceiveLibrary(address _oapp, uint32 _eid, address _newLib, uint256 _gracePeriod) external;\n\n function getReceiveLibrary(address _receiver, uint32 _eid) external view returns (address lib, bool isDefault);\n\n function setReceiveLibraryTimeout(address _oapp, uint32 _eid, address _lib, uint256 _expiry) external;\n\n function receiveLibraryTimeout(address _receiver, uint32 _eid) external view returns (address lib, uint256 expiry);\n\n function setConfig(address _oapp, address _lib, SetConfigParam[] calldata _params) external;\n\n function getConfig(\n address _oapp,\n address _lib,\n uint32 _eid,\n uint32 _configType\n ) external view returns (bytes memory config);\n}\n" + }, + "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/IMessagingChannel.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity >=0.8.0;\n\ninterface IMessagingChannel {\n event InboundNonceSkipped(uint32 srcEid, bytes32 sender, address receiver, uint64 nonce);\n event PacketNilified(uint32 srcEid, bytes32 sender, address receiver, uint64 nonce, bytes32 payloadHash);\n event PacketBurnt(uint32 srcEid, bytes32 sender, address receiver, uint64 nonce, bytes32 payloadHash);\n\n function eid() external view returns (uint32);\n\n // this is an emergency function if a message cannot be verified for some reasons\n // required to provide _nextNonce to avoid race condition\n function skip(address _oapp, uint32 _srcEid, bytes32 _sender, uint64 _nonce) external;\n\n function nilify(address _oapp, uint32 _srcEid, bytes32 _sender, uint64 _nonce, bytes32 _payloadHash) external;\n\n function burn(address _oapp, uint32 _srcEid, bytes32 _sender, uint64 _nonce, bytes32 _payloadHash) external;\n\n function nextGuid(address _sender, uint32 _dstEid, bytes32 _receiver) external view returns (bytes32);\n\n function inboundNonce(address _receiver, uint32 _srcEid, bytes32 _sender) external view returns (uint64);\n\n function outboundNonce(address _sender, uint32 _dstEid, bytes32 _receiver) external view returns (uint64);\n\n function inboundPayloadHash(\n address _receiver,\n uint32 _srcEid,\n bytes32 _sender,\n uint64 _nonce\n ) external view returns (bytes32);\n\n function lazyInboundNonce(address _receiver, uint32 _srcEid, bytes32 _sender) external view returns (uint64);\n}\n" + }, + "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/IMessagingComposer.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity >=0.8.0;\n\ninterface IMessagingComposer {\n event ComposeSent(address from, address to, bytes32 guid, uint16 index, bytes message);\n event ComposeDelivered(address from, address to, bytes32 guid, uint16 index);\n event LzComposeAlert(\n address indexed from,\n address indexed to,\n address indexed executor,\n bytes32 guid,\n uint16 index,\n uint256 gas,\n uint256 value,\n bytes message,\n bytes extraData,\n bytes reason\n );\n\n function composeQueue(\n address _from,\n address _to,\n bytes32 _guid,\n uint16 _index\n ) external view returns (bytes32 messageHash);\n\n function sendCompose(address _to, bytes32 _guid, uint16 _index, bytes calldata _message) external;\n\n function lzCompose(\n address _from,\n address _to,\n bytes32 _guid,\n uint16 _index,\n bytes calldata _message,\n bytes calldata _extraData\n ) external payable;\n}\n" + }, + "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/IMessagingContext.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity >=0.8.0;\n\ninterface IMessagingContext {\n function isSendingMessage() external view returns (bool);\n\n function getSendContext() external view returns (uint32 dstEid, address sender);\n}\n" + }, + "@layerzerolabs/lz-evm-protocol-v2/contracts/libs/CalldataBytesLib.sol": { + "content": "// SPDX-License-Identifier: LZBL-1.2\n\npragma solidity ^0.8.20;\n\nlibrary CalldataBytesLib {\n function toU8(bytes calldata _bytes, uint256 _start) internal pure returns (uint8) {\n return uint8(_bytes[_start]);\n }\n\n function toU16(bytes calldata _bytes, uint256 _start) internal pure returns (uint16) {\n unchecked {\n uint256 end = _start + 2;\n return uint16(bytes2(_bytes[_start:end]));\n }\n }\n\n function toU32(bytes calldata _bytes, uint256 _start) internal pure returns (uint32) {\n unchecked {\n uint256 end = _start + 4;\n return uint32(bytes4(_bytes[_start:end]));\n }\n }\n\n function toU64(bytes calldata _bytes, uint256 _start) internal pure returns (uint64) {\n unchecked {\n uint256 end = _start + 8;\n return uint64(bytes8(_bytes[_start:end]));\n }\n }\n\n function toU128(bytes calldata _bytes, uint256 _start) internal pure returns (uint128) {\n unchecked {\n uint256 end = _start + 16;\n return uint128(bytes16(_bytes[_start:end]));\n }\n }\n\n function toU256(bytes calldata _bytes, uint256 _start) internal pure returns (uint256) {\n unchecked {\n uint256 end = _start + 32;\n return uint256(bytes32(_bytes[_start:end]));\n }\n }\n\n function toAddr(bytes calldata _bytes, uint256 _start) internal pure returns (address) {\n unchecked {\n uint256 end = _start + 20;\n return address(bytes20(_bytes[_start:end]));\n }\n }\n\n function toB32(bytes calldata _bytes, uint256 _start) internal pure returns (bytes32) {\n unchecked {\n uint256 end = _start + 32;\n return bytes32(_bytes[_start:end]);\n }\n }\n}\n" + }, + "@layerzerolabs/lz-evm-protocol-v2/contracts/messagelib/libs/BitMaps.sol": { + "content": "// SPDX-License-Identifier: MIT\n\n// modified from https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/structs/BitMaps.sol\npragma solidity ^0.8.20;\n\ntype BitMap256 is uint256;\n\nusing BitMaps for BitMap256 global;\n\nlibrary BitMaps {\n /**\n * @dev Returns whether the bit at `index` is set.\n */\n function get(BitMap256 bitmap, uint8 index) internal pure returns (bool) {\n uint256 mask = 1 << index;\n return BitMap256.unwrap(bitmap) & mask != 0;\n }\n\n /**\n * @dev Sets the bit at `index`.\n */\n function set(BitMap256 bitmap, uint8 index) internal pure returns (BitMap256) {\n uint256 mask = 1 << index;\n return BitMap256.wrap(BitMap256.unwrap(bitmap) | mask);\n }\n}\n" + }, + "@layerzerolabs/oapp-evm/contracts/oapp/interfaces/IOAppCore.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\nimport { ILayerZeroEndpointV2 } from \"@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroEndpointV2.sol\";\n\n/**\n * @title IOAppCore\n */\ninterface IOAppCore {\n // Custom error messages\n error OnlyPeer(uint32 eid, bytes32 sender);\n error NoPeer(uint32 eid);\n error InvalidEndpointCall();\n error InvalidDelegate();\n\n // Event emitted when a peer (OApp) is set for a corresponding endpoint\n event PeerSet(uint32 eid, bytes32 peer);\n\n /**\n * @notice Retrieves the OApp version information.\n * @return senderVersion The version of the OAppSender.sol contract.\n * @return receiverVersion The version of the OAppReceiver.sol contract.\n */\n function oAppVersion() external view returns (uint64 senderVersion, uint64 receiverVersion);\n\n /**\n * @notice Retrieves the LayerZero endpoint associated with the OApp.\n * @return iEndpoint The LayerZero endpoint as an interface.\n */\n function endpoint() external view returns (ILayerZeroEndpointV2 iEndpoint);\n\n /**\n * @notice Retrieves the peer (OApp) associated with a corresponding endpoint.\n * @param _eid The endpoint ID.\n * @return peer The peer address (OApp instance) associated with the corresponding endpoint.\n */\n function peers(uint32 _eid) external view returns (bytes32 peer);\n\n /**\n * @notice Sets the peer address (OApp instance) for a corresponding endpoint.\n * @param _eid The endpoint ID.\n * @param _peer The address of the peer to be associated with the corresponding endpoint.\n */\n function setPeer(uint32 _eid, bytes32 _peer) external;\n\n /**\n * @notice Sets the delegate address for the OApp Core.\n * @param _delegate The address of the delegate to be set.\n */\n function setDelegate(address _delegate) external;\n}\n" + }, + "@layerzerolabs/oapp-evm/contracts/oapp/libs/OptionsBuilder.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\nimport { BytesLib } from \"solidity-bytes-utils/contracts/BytesLib.sol\";\nimport { SafeCast } from \"@openzeppelin/contracts/utils/math/SafeCast.sol\";\n\nimport { ExecutorOptions } from \"@layerzerolabs/lz-evm-messagelib-v2/contracts/libs/ExecutorOptions.sol\";\nimport { DVNOptions } from \"@layerzerolabs/lz-evm-messagelib-v2/contracts/uln/libs/DVNOptions.sol\";\n\n/**\n * @title OptionsBuilder\n * @dev Library for building and encoding various message options.\n */\nlibrary OptionsBuilder {\n using SafeCast for uint256;\n using BytesLib for bytes;\n\n // Constants for options types\n uint16 internal constant TYPE_1 = 1; // legacy options type 1\n uint16 internal constant TYPE_2 = 2; // legacy options type 2\n uint16 internal constant TYPE_3 = 3;\n\n // Custom error message\n error InvalidSize(uint256 max, uint256 actual);\n error InvalidOptionType(uint16 optionType);\n\n // Modifier to ensure only options of type 3 are used\n modifier onlyType3(bytes memory _options) {\n if (_options.toUint16(0) != TYPE_3) revert InvalidOptionType(_options.toUint16(0));\n _;\n }\n\n /**\n * @dev Creates a new options container with type 3.\n * @return options The newly created options container.\n */\n function newOptions() internal pure returns (bytes memory) {\n return abi.encodePacked(TYPE_3);\n }\n\n /**\n * @dev Adds an executor LZ receive option to the existing options.\n * @param _options The existing options container.\n * @param _gas The gasLimit used on the lzReceive() function in the OApp.\n * @param _value The msg.value passed to the lzReceive() function in the OApp.\n * @return options The updated options container.\n *\n * @dev When multiples of this option are added, they are summed by the executor\n * eg. if (_gas: 200k, and _value: 1 ether) AND (_gas: 100k, _value: 0.5 ether) are sent in an option to the LayerZeroEndpoint,\n * that becomes (300k, 1.5 ether) when the message is executed on the remote lzReceive() function.\n */\n function addExecutorLzReceiveOption(\n bytes memory _options,\n uint128 _gas,\n uint128 _value\n ) internal pure onlyType3(_options) returns (bytes memory) {\n bytes memory option = ExecutorOptions.encodeLzReceiveOption(_gas, _value);\n return addExecutorOption(_options, ExecutorOptions.OPTION_TYPE_LZRECEIVE, option);\n }\n\n /**\n * @dev Adds an executor native drop option to the existing options.\n * @param _options The existing options container.\n * @param _amount The amount for the native value that is airdropped to the 'receiver'.\n * @param _receiver The receiver address for the native drop option.\n * @return options The updated options container.\n *\n * @dev When multiples of this option are added, they are summed by the executor on the remote chain.\n */\n function addExecutorNativeDropOption(\n bytes memory _options,\n uint128 _amount,\n bytes32 _receiver\n ) internal pure onlyType3(_options) returns (bytes memory) {\n bytes memory option = ExecutorOptions.encodeNativeDropOption(_amount, _receiver);\n return addExecutorOption(_options, ExecutorOptions.OPTION_TYPE_NATIVE_DROP, option);\n }\n\n // /**\n // * @dev Adds an executor native drop option to the existing options.\n // * @param _options The existing options container.\n // * @param _amount The amount for the native value that is airdropped to the 'receiver'.\n // * @param _receiver The receiver address for the native drop option.\n // * @return options The updated options container.\n // *\n // * @dev When multiples of this option are added, they are summed by the executor on the remote chain.\n // */\n function addExecutorLzReadOption(\n bytes memory _options,\n uint128 _gas,\n uint32 _size,\n uint128 _value\n ) internal pure onlyType3(_options) returns (bytes memory) {\n bytes memory option = ExecutorOptions.encodeLzReadOption(_gas, _size, _value);\n return addExecutorOption(_options, ExecutorOptions.OPTION_TYPE_LZREAD, option);\n }\n\n /**\n * @dev Adds an executor LZ compose option to the existing options.\n * @param _options The existing options container.\n * @param _index The index for the lzCompose() function call.\n * @param _gas The gasLimit for the lzCompose() function call.\n * @param _value The msg.value for the lzCompose() function call.\n * @return options The updated options container.\n *\n * @dev When multiples of this option are added, they are summed PER index by the executor on the remote chain.\n * @dev If the OApp sends N lzCompose calls on the remote, you must provide N incremented indexes starting with 0.\n * ie. When your remote OApp composes (N = 3) messages, you must set this option for index 0,1,2\n */\n function addExecutorLzComposeOption(\n bytes memory _options,\n uint16 _index,\n uint128 _gas,\n uint128 _value\n ) internal pure onlyType3(_options) returns (bytes memory) {\n bytes memory option = ExecutorOptions.encodeLzComposeOption(_index, _gas, _value);\n return addExecutorOption(_options, ExecutorOptions.OPTION_TYPE_LZCOMPOSE, option);\n }\n\n /**\n * @dev Adds an executor ordered execution option to the existing options.\n * @param _options The existing options container.\n * @return options The updated options container.\n */\n function addExecutorOrderedExecutionOption(\n bytes memory _options\n ) internal pure onlyType3(_options) returns (bytes memory) {\n return addExecutorOption(_options, ExecutorOptions.OPTION_TYPE_ORDERED_EXECUTION, bytes(\"\"));\n }\n\n /**\n * @dev Adds a DVN pre-crime option to the existing options.\n * @param _options The existing options container.\n * @param _dvnIdx The DVN index for the pre-crime option.\n * @return options The updated options container.\n */\n function addDVNPreCrimeOption(\n bytes memory _options,\n uint8 _dvnIdx\n ) internal pure onlyType3(_options) returns (bytes memory) {\n return addDVNOption(_options, _dvnIdx, DVNOptions.OPTION_TYPE_PRECRIME, bytes(\"\"));\n }\n\n /**\n * @dev Adds an executor option to the existing options.\n * @param _options The existing options container.\n * @param _optionType The type of the executor option.\n * @param _option The encoded data for the executor option.\n * @return options The updated options container.\n */\n function addExecutorOption(\n bytes memory _options,\n uint8 _optionType,\n bytes memory _option\n ) internal pure onlyType3(_options) returns (bytes memory) {\n return\n abi.encodePacked(\n _options,\n ExecutorOptions.WORKER_ID,\n _option.length.toUint16() + 1, // +1 for optionType\n _optionType,\n _option\n );\n }\n\n /**\n * @dev Adds a DVN option to the existing options.\n * @param _options The existing options container.\n * @param _dvnIdx The DVN index for the DVN option.\n * @param _optionType The type of the DVN option.\n * @param _option The encoded data for the DVN option.\n * @return options The updated options container.\n */\n function addDVNOption(\n bytes memory _options,\n uint8 _dvnIdx,\n uint8 _optionType,\n bytes memory _option\n ) internal pure onlyType3(_options) returns (bytes memory) {\n return\n abi.encodePacked(\n _options,\n DVNOptions.WORKER_ID,\n _option.length.toUint16() + 2, // +2 for optionType and dvnIdx\n _dvnIdx,\n _optionType,\n _option\n );\n }\n\n /**\n * @dev Encodes legacy options of type 1.\n * @param _executionGas The gasLimit value passed to lzReceive().\n * @return legacyOptions The encoded legacy options.\n */\n function encodeLegacyOptionsType1(uint256 _executionGas) internal pure returns (bytes memory) {\n if (_executionGas > type(uint128).max) revert InvalidSize(type(uint128).max, _executionGas);\n return abi.encodePacked(TYPE_1, _executionGas);\n }\n\n /**\n * @dev Encodes legacy options of type 2.\n * @param _executionGas The gasLimit value passed to lzReceive().\n * @param _nativeForDst The amount of native air dropped to the receiver.\n * @param _receiver The _nativeForDst receiver address.\n * @return legacyOptions The encoded legacy options of type 2.\n */\n function encodeLegacyOptionsType2(\n uint256 _executionGas,\n uint256 _nativeForDst,\n bytes memory _receiver // @dev Use bytes instead of bytes32 in legacy type 2 for _receiver.\n ) internal pure returns (bytes memory) {\n if (_executionGas > type(uint128).max) revert InvalidSize(type(uint128).max, _executionGas);\n if (_nativeForDst > type(uint128).max) revert InvalidSize(type(uint128).max, _nativeForDst);\n if (_receiver.length > 32) revert InvalidSize(32, _receiver.length);\n return abi.encodePacked(TYPE_2, _executionGas, _nativeForDst, _receiver);\n }\n}\n" + }, + "@layerzerolabs/oapp-evm/contracts/oapp/OAppCore.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\nimport { Ownable } from \"@openzeppelin/contracts/access/Ownable.sol\";\nimport { IOAppCore, ILayerZeroEndpointV2 } from \"./interfaces/IOAppCore.sol\";\n\n/**\n * @title OAppCore\n * @dev Abstract contract implementing the IOAppCore interface with basic OApp configurations.\n */\nabstract contract OAppCore is IOAppCore, Ownable {\n // The LayerZero endpoint associated with the given OApp\n ILayerZeroEndpointV2 public immutable endpoint;\n\n // Mapping to store peers associated with corresponding endpoints\n mapping(uint32 eid => bytes32 peer) public peers;\n\n /**\n * @dev Constructor to initialize the OAppCore with the provided endpoint and delegate.\n * @param _endpoint The address of the LOCAL Layer Zero endpoint.\n * @param _delegate The delegate capable of making OApp configurations inside of the endpoint.\n *\n * @dev The delegate typically should be set as the owner of the contract.\n */\n constructor(address _endpoint, address _delegate) {\n endpoint = ILayerZeroEndpointV2(_endpoint);\n\n if (_delegate == address(0)) revert InvalidDelegate();\n endpoint.setDelegate(_delegate);\n }\n\n /**\n * @notice Sets the peer address (OApp instance) for a corresponding endpoint.\n * @param _eid The endpoint ID.\n * @param _peer The address of the peer to be associated with the corresponding endpoint.\n *\n * @dev Only the owner/admin of the OApp can call this function.\n * @dev Indicates that the peer is trusted to send LayerZero messages to this OApp.\n * @dev Set this to bytes32(0) to remove the peer address.\n * @dev Peer is a bytes32 to accommodate non-evm chains.\n */\n function setPeer(uint32 _eid, bytes32 _peer) public virtual onlyOwner {\n _setPeer(_eid, _peer);\n }\n\n /**\n * @notice Sets the peer address (OApp instance) for a corresponding endpoint.\n * @param _eid The endpoint ID.\n * @param _peer The address of the peer to be associated with the corresponding endpoint.\n *\n * @dev Indicates that the peer is trusted to send LayerZero messages to this OApp.\n * @dev Set this to bytes32(0) to remove the peer address.\n * @dev Peer is a bytes32 to accommodate non-evm chains.\n */\n function _setPeer(uint32 _eid, bytes32 _peer) internal virtual {\n peers[_eid] = _peer;\n emit PeerSet(_eid, _peer);\n }\n\n /**\n * @notice Internal function to get the peer address associated with a specific endpoint; reverts if NOT set.\n * ie. the peer is set to bytes32(0).\n * @param _eid The endpoint ID.\n * @return peer The address of the peer associated with the specified endpoint.\n */\n function _getPeerOrRevert(uint32 _eid) internal view virtual returns (bytes32) {\n bytes32 peer = peers[_eid];\n if (peer == bytes32(0)) revert NoPeer(_eid);\n return peer;\n }\n\n /**\n * @notice Sets the delegate address for the OApp.\n * @param _delegate The address of the delegate to be set.\n *\n * @dev Only the owner/admin of the OApp can call this function.\n * @dev Provides the ability for a delegate to set configs, on behalf of the OApp, directly on the Endpoint contract.\n */\n function setDelegate(address _delegate) public onlyOwner {\n endpoint.setDelegate(_delegate);\n }\n}\n" + }, + "@layerzerolabs/oapp-evm/contracts/oapp/OAppSender.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\nimport { SafeERC20, IERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport { MessagingParams, MessagingFee, MessagingReceipt } from \"@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroEndpointV2.sol\";\nimport { OAppCore } from \"./OAppCore.sol\";\n\n/**\n * @title OAppSender\n * @dev Abstract contract implementing the OAppSender functionality for sending messages to a LayerZero endpoint.\n */\nabstract contract OAppSender is OAppCore {\n using SafeERC20 for IERC20;\n\n // Custom error messages\n error NotEnoughNative(uint256 msgValue);\n error LzTokenUnavailable();\n\n // @dev The version of the OAppSender implementation.\n // @dev Version is bumped when changes are made to this contract.\n uint64 internal constant SENDER_VERSION = 1;\n\n /**\n * @notice Retrieves the OApp version information.\n * @return senderVersion The version of the OAppSender.sol contract.\n * @return receiverVersion The version of the OAppReceiver.sol contract.\n *\n * @dev Providing 0 as the default for OAppReceiver version. Indicates that the OAppReceiver is not implemented.\n * ie. this is a SEND only OApp.\n * @dev If the OApp uses both OAppSender and OAppReceiver, then this needs to be override returning the correct versions\n */\n function oAppVersion() public view virtual returns (uint64 senderVersion, uint64 receiverVersion) {\n return (SENDER_VERSION, 0);\n }\n\n /**\n * @dev Internal function to interact with the LayerZero EndpointV2.quote() for fee calculation.\n * @param _dstEid The destination endpoint ID.\n * @param _message The message payload.\n * @param _options Additional options for the message.\n * @param _payInLzToken Flag indicating whether to pay the fee in LZ tokens.\n * @return fee The calculated MessagingFee for the message.\n * - nativeFee: The native fee for the message.\n * - lzTokenFee: The LZ token fee for the message.\n */\n function _quote(\n uint32 _dstEid,\n bytes memory _message,\n bytes memory _options,\n bool _payInLzToken\n ) internal view virtual returns (MessagingFee memory fee) {\n return\n endpoint.quote(\n MessagingParams(_dstEid, _getPeerOrRevert(_dstEid), _message, _options, _payInLzToken),\n address(this)\n );\n }\n\n /**\n * @dev Internal function to interact with the LayerZero EndpointV2.send() for sending a message.\n * @param _dstEid The destination endpoint ID.\n * @param _message The message payload.\n * @param _options Additional options for the message.\n * @param _fee The calculated LayerZero fee for the message.\n * - nativeFee: The native fee.\n * - lzTokenFee: The lzToken fee.\n * @param _refundAddress The address to receive any excess fee values sent to the endpoint.\n * @return receipt The receipt for the sent message.\n * - guid: The unique identifier for the sent message.\n * - nonce: The nonce of the sent message.\n * - fee: The LayerZero fee incurred for the message.\n */\n function _lzSend(\n uint32 _dstEid,\n bytes memory _message,\n bytes memory _options,\n MessagingFee memory _fee,\n address _refundAddress\n ) internal virtual returns (MessagingReceipt memory receipt) {\n // @dev Push corresponding fees to the endpoint, any excess is sent back to the _refundAddress from the endpoint.\n uint256 messageValue = _payNative(_fee.nativeFee);\n if (_fee.lzTokenFee > 0) _payLzToken(_fee.lzTokenFee);\n\n return\n // solhint-disable-next-line check-send-result\n endpoint.send{ value: messageValue }(\n MessagingParams(_dstEid, _getPeerOrRevert(_dstEid), _message, _options, _fee.lzTokenFee > 0),\n _refundAddress\n );\n }\n\n /**\n * @dev Internal function to pay the native fee associated with the message.\n * @param _nativeFee The native fee to be paid.\n * @return nativeFee The amount of native currency paid.\n *\n * @dev If the OApp needs to initiate MULTIPLE LayerZero messages in a single transaction,\n * this will need to be overridden because msg.value would contain multiple lzFees.\n * @dev Should be overridden in the event the LayerZero endpoint requires a different native currency.\n * @dev Some EVMs use an ERC20 as a method for paying transactions/gasFees.\n * @dev The endpoint is EITHER/OR, ie. it will NOT support both types of native payment at a time.\n */\n function _payNative(uint256 _nativeFee) internal virtual returns (uint256 nativeFee) {\n if (msg.value != _nativeFee) revert NotEnoughNative(msg.value);\n return _nativeFee;\n }\n\n /**\n * @dev Internal function to pay the LZ token fee associated with the message.\n * @param _lzTokenFee The LZ token fee to be paid.\n *\n * @dev If the caller is trying to pay in the specified lzToken, then the lzTokenFee is passed to the endpoint.\n * @dev Any excess sent, is passed back to the specified _refundAddress in the _lzSend().\n */\n function _payLzToken(uint256 _lzTokenFee) internal virtual {\n // @dev Cannot cache the token because it is not immutable in the endpoint.\n address lzToken = endpoint.lzToken();\n if (lzToken == address(0)) revert LzTokenUnavailable();\n\n // Pay LZ token fee by sending tokens to the endpoint.\n IERC20(lzToken).safeTransferFrom(msg.sender, address(endpoint), _lzTokenFee);\n }\n}\n" + }, + "@layerzerolabs/oft-evm/contracts/interfaces/IOFT.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.20;\n\nimport { MessagingReceipt, MessagingFee } from \"@layerzerolabs/oapp-evm/contracts/oapp/OAppSender.sol\";\n\n/**\n * @dev Struct representing token parameters for the OFT send() operation.\n */\nstruct SendParam {\n uint32 dstEid; // Destination endpoint ID.\n bytes32 to; // Recipient address.\n uint256 amountLD; // Amount to send in local decimals.\n uint256 minAmountLD; // Minimum amount to send in local decimals.\n bytes extraOptions; // Additional options supplied by the caller to be used in the LayerZero message.\n bytes composeMsg; // The composed message for the send() operation.\n bytes oftCmd; // The OFT command to be executed, unused in default OFT implementations.\n}\n\n/**\n * @dev Struct representing OFT limit information.\n * @dev These amounts can change dynamically and are up the specific oft implementation.\n */\nstruct OFTLimit {\n uint256 minAmountLD; // Minimum amount in local decimals that can be sent to the recipient.\n uint256 maxAmountLD; // Maximum amount in local decimals that can be sent to the recipient.\n}\n\n/**\n * @dev Struct representing OFT receipt information.\n */\nstruct OFTReceipt {\n uint256 amountSentLD; // Amount of tokens ACTUALLY debited from the sender in local decimals.\n // @dev In non-default implementations, the amountReceivedLD COULD differ from this value.\n uint256 amountReceivedLD; // Amount of tokens to be received on the remote side.\n}\n\n/**\n * @dev Struct representing OFT fee details.\n * @dev Future proof mechanism to provide a standardized way to communicate fees to things like a UI.\n */\nstruct OFTFeeDetail {\n int256 feeAmountLD; // Amount of the fee in local decimals.\n string description; // Description of the fee.\n}\n\n/**\n * @title IOFT\n * @dev Interface for the OftChain (OFT) token.\n * @dev Does not inherit ERC20 to accommodate usage by OFTAdapter as well.\n * @dev This specific interface ID is '0x02e49c2c'.\n */\ninterface IOFT {\n // Custom error messages\n error InvalidLocalDecimals();\n error SlippageExceeded(uint256 amountLD, uint256 minAmountLD);\n\n // Events\n event OFTSent(\n bytes32 indexed guid, // GUID of the OFT message.\n uint32 dstEid, // Destination Endpoint ID.\n address indexed fromAddress, // Address of the sender on the src chain.\n uint256 amountSentLD, // Amount of tokens sent in local decimals.\n uint256 amountReceivedLD // Amount of tokens received in local decimals.\n );\n event OFTReceived(\n bytes32 indexed guid, // GUID of the OFT message.\n uint32 srcEid, // Source Endpoint ID.\n address indexed toAddress, // Address of the recipient on the dst chain.\n uint256 amountReceivedLD // Amount of tokens received in local decimals.\n );\n\n /**\n * @notice Retrieves interfaceID and the version of the OFT.\n * @return interfaceId The interface ID.\n * @return version The version.\n *\n * @dev interfaceId: This specific interface ID is '0x02e49c2c'.\n * @dev version: Indicates a cross-chain compatible msg encoding with other OFTs.\n * @dev If a new feature is added to the OFT cross-chain msg encoding, the version will be incremented.\n * ie. localOFT version(x,1) CAN send messages to remoteOFT version(x,1)\n */\n function oftVersion() external view returns (bytes4 interfaceId, uint64 version);\n\n /**\n * @notice Retrieves the address of the token associated with the OFT.\n * @return token The address of the ERC20 token implementation.\n */\n function token() external view returns (address);\n\n /**\n * @notice Indicates whether the OFT contract requires approval of the 'token()' to send.\n * @return requiresApproval Needs approval of the underlying token implementation.\n *\n * @dev Allows things like wallet implementers to determine integration requirements,\n * without understanding the underlying token implementation.\n */\n function approvalRequired() external view returns (bool);\n\n /**\n * @notice Retrieves the shared decimals of the OFT.\n * @return sharedDecimals The shared decimals of the OFT.\n */\n function sharedDecimals() external view returns (uint8);\n\n /**\n * @notice Provides the fee breakdown and settings data for an OFT. Unused in the default implementation.\n * @param _sendParam The parameters for the send operation.\n * @return limit The OFT limit information.\n * @return oftFeeDetails The details of OFT fees.\n * @return receipt The OFT receipt information.\n */\n function quoteOFT(\n SendParam calldata _sendParam\n ) external view returns (OFTLimit memory, OFTFeeDetail[] memory oftFeeDetails, OFTReceipt memory);\n\n /**\n * @notice Provides a quote for the send() operation.\n * @param _sendParam The parameters for the send() operation.\n * @param _payInLzToken Flag indicating whether the caller is paying in the LZ token.\n * @return fee The calculated LayerZero messaging fee from the send() operation.\n *\n * @dev MessagingFee: LayerZero msg fee\n * - nativeFee: The native fee.\n * - lzTokenFee: The lzToken fee.\n */\n function quoteSend(SendParam calldata _sendParam, bool _payInLzToken) external view returns (MessagingFee memory);\n\n /**\n * @notice Executes the send() operation.\n * @param _sendParam The parameters for the send operation.\n * @param _fee The fee information supplied by the caller.\n * - nativeFee: The native fee.\n * - lzTokenFee: The lzToken fee.\n * @param _refundAddress The address to receive any excess funds from fees etc. on the src.\n * @return receipt The LayerZero messaging receipt from the send() operation.\n * @return oftReceipt The OFT receipt information.\n *\n * @dev MessagingReceipt: LayerZero msg receipt\n * - guid: The unique identifier for the sent message.\n * - nonce: The nonce of the sent message.\n * - fee: The LayerZero fee incurred for the message.\n */\n function send(\n SendParam calldata _sendParam,\n MessagingFee calldata _fee,\n address _refundAddress\n ) external payable returns (MessagingReceipt memory, OFTReceipt memory);\n}\n" + }, + "@openzeppelin/contracts/access/AccessControl.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (access/AccessControl.sol)\n\npragma solidity ^0.8.0;\n\nimport \"./IAccessControl.sol\";\nimport \"../utils/Context.sol\";\nimport \"../utils/Strings.sol\";\nimport \"../utils/introspection/ERC165.sol\";\n\n/**\n * @dev Contract module that allows children to implement role-based access\n * control mechanisms. This is a lightweight version that doesn't allow enumerating role\n * members except through off-chain means by accessing the contract event logs. Some\n * applications may benefit from on-chain enumerability, for those cases see\n * {AccessControlEnumerable}.\n *\n * Roles are referred to by their `bytes32` identifier. These should be exposed\n * in the external API and be unique. The best way to achieve this is by\n * using `public constant` hash digests:\n *\n * ```\n * bytes32 public constant MY_ROLE = keccak256(\"MY_ROLE\");\n * ```\n *\n * Roles can be used to represent a set of permissions. To restrict access to a\n * function call, use {hasRole}:\n *\n * ```\n * function foo() public {\n * require(hasRole(MY_ROLE, msg.sender));\n * ...\n * }\n * ```\n *\n * Roles can be granted and revoked dynamically via the {grantRole} and\n * {revokeRole} functions. Each role has an associated admin role, and only\n * accounts that have a role's admin role can call {grantRole} and {revokeRole}.\n *\n * By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means\n * that only accounts with this role will be able to grant or revoke other\n * roles. More complex role relationships can be created by using\n * {_setRoleAdmin}.\n *\n * WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to\n * grant and revoke this role. Extra precautions should be taken to secure\n * accounts that have been granted it.\n */\nabstract contract AccessControl is Context, IAccessControl, ERC165 {\n struct RoleData {\n mapping(address => bool) members;\n bytes32 adminRole;\n }\n\n mapping(bytes32 => RoleData) private _roles;\n\n bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;\n\n /**\n * @dev Modifier that checks that an account has a specific role. Reverts\n * with a standardized message including the required role.\n *\n * The format of the revert reason is given by the following regular expression:\n *\n * /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/\n *\n * _Available since v4.1._\n */\n modifier onlyRole(bytes32 role) {\n _checkRole(role, _msgSender());\n _;\n }\n\n /**\n * @dev See {IERC165-supportsInterface}.\n */\n function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {\n return interfaceId == type(IAccessControl).interfaceId || super.supportsInterface(interfaceId);\n }\n\n /**\n * @dev Returns `true` if `account` has been granted `role`.\n */\n function hasRole(bytes32 role, address account) public view override returns (bool) {\n return _roles[role].members[account];\n }\n\n /**\n * @dev Revert with a standard message if `account` is missing `role`.\n *\n * The format of the revert reason is given by the following regular expression:\n *\n * /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/\n */\n function _checkRole(bytes32 role, address account) internal view {\n if (!hasRole(role, account)) {\n revert(\n string(\n abi.encodePacked(\n \"AccessControl: account \",\n Strings.toHexString(uint160(account), 20),\n \" is missing role \",\n Strings.toHexString(uint256(role), 32)\n )\n )\n );\n }\n }\n\n /**\n * @dev Returns the admin role that controls `role`. See {grantRole} and\n * {revokeRole}.\n *\n * To change a role's admin, use {_setRoleAdmin}.\n */\n function getRoleAdmin(bytes32 role) public view override returns (bytes32) {\n return _roles[role].adminRole;\n }\n\n /**\n * @dev Grants `role` to `account`.\n *\n * If `account` had not been already granted `role`, emits a {RoleGranted}\n * event.\n *\n * Requirements:\n *\n * - the caller must have ``role``'s admin role.\n */\n function grantRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {\n _grantRole(role, account);\n }\n\n /**\n * @dev Revokes `role` from `account`.\n *\n * If `account` had been granted `role`, emits a {RoleRevoked} event.\n *\n * Requirements:\n *\n * - the caller must have ``role``'s admin role.\n */\n function revokeRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {\n _revokeRole(role, account);\n }\n\n /**\n * @dev Revokes `role` from the calling account.\n *\n * Roles are often managed via {grantRole} and {revokeRole}: this function's\n * purpose is to provide a mechanism for accounts to lose their privileges\n * if they are compromised (such as when a trusted device is misplaced).\n *\n * If the calling account had been revoked `role`, emits a {RoleRevoked}\n * event.\n *\n * Requirements:\n *\n * - the caller must be `account`.\n */\n function renounceRole(bytes32 role, address account) public virtual override {\n require(account == _msgSender(), \"AccessControl: can only renounce roles for self\");\n\n _revokeRole(role, account);\n }\n\n /**\n * @dev Grants `role` to `account`.\n *\n * If `account` had not been already granted `role`, emits a {RoleGranted}\n * event. Note that unlike {grantRole}, this function doesn't perform any\n * checks on the calling account.\n *\n * [WARNING]\n * ====\n * This function should only be called from the constructor when setting\n * up the initial roles for the system.\n *\n * Using this function in any other way is effectively circumventing the admin\n * system imposed by {AccessControl}.\n * ====\n *\n * NOTE: This function is deprecated in favor of {_grantRole}.\n */\n function _setupRole(bytes32 role, address account) internal virtual {\n _grantRole(role, account);\n }\n\n /**\n * @dev Sets `adminRole` as ``role``'s admin role.\n *\n * Emits a {RoleAdminChanged} event.\n */\n function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {\n bytes32 previousAdminRole = getRoleAdmin(role);\n _roles[role].adminRole = adminRole;\n emit RoleAdminChanged(role, previousAdminRole, adminRole);\n }\n\n /**\n * @dev Grants `role` to `account`.\n *\n * Internal function without access restriction.\n */\n function _grantRole(bytes32 role, address account) internal virtual {\n if (!hasRole(role, account)) {\n _roles[role].members[account] = true;\n emit RoleGranted(role, account, _msgSender());\n }\n }\n\n /**\n * @dev Revokes `role` from `account`.\n *\n * Internal function without access restriction.\n */\n function _revokeRole(bytes32 role, address account) internal virtual {\n if (hasRole(role, account)) {\n _roles[role].members[account] = false;\n emit RoleRevoked(role, account, _msgSender());\n }\n }\n}\n" + }, + "@openzeppelin/contracts/access/AccessControlEnumerable.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (access/AccessControlEnumerable.sol)\n\npragma solidity ^0.8.0;\n\nimport \"./IAccessControlEnumerable.sol\";\nimport \"./AccessControl.sol\";\nimport \"../utils/structs/EnumerableSet.sol\";\n\n/**\n * @dev Extension of {AccessControl} that allows enumerating the members of each role.\n */\nabstract contract AccessControlEnumerable is IAccessControlEnumerable, AccessControl {\n using EnumerableSet for EnumerableSet.AddressSet;\n\n mapping(bytes32 => EnumerableSet.AddressSet) private _roleMembers;\n\n /**\n * @dev See {IERC165-supportsInterface}.\n */\n function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {\n return interfaceId == type(IAccessControlEnumerable).interfaceId || super.supportsInterface(interfaceId);\n }\n\n /**\n * @dev Returns one of the accounts that have `role`. `index` must be a\n * value between 0 and {getRoleMemberCount}, non-inclusive.\n *\n * Role bearers are not sorted in any particular way, and their ordering may\n * change at any point.\n *\n * WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure\n * you perform all queries on the same block. See the following\n * https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post]\n * for more information.\n */\n function getRoleMember(bytes32 role, uint256 index) public view override returns (address) {\n return _roleMembers[role].at(index);\n }\n\n /**\n * @dev Returns the number of accounts that have `role`. Can be used\n * together with {getRoleMember} to enumerate all bearers of a role.\n */\n function getRoleMemberCount(bytes32 role) public view override returns (uint256) {\n return _roleMembers[role].length();\n }\n\n /**\n * @dev Overload {_grantRole} to track enumerable memberships\n */\n function _grantRole(bytes32 role, address account) internal virtual override {\n super._grantRole(role, account);\n _roleMembers[role].add(account);\n }\n\n /**\n * @dev Overload {_revokeRole} to track enumerable memberships\n */\n function _revokeRole(bytes32 role, address account) internal virtual override {\n super._revokeRole(role, account);\n _roleMembers[role].remove(account);\n }\n}\n" + }, + "@openzeppelin/contracts/access/IAccessControl.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (access/IAccessControl.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev External interface of AccessControl declared to support ERC165 detection.\n */\ninterface IAccessControl {\n /**\n * @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`\n *\n * `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite\n * {RoleAdminChanged} not being emitted signaling this.\n *\n * _Available since v3.1._\n */\n event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);\n\n /**\n * @dev Emitted when `account` is granted `role`.\n *\n * `sender` is the account that originated the contract call, an admin role\n * bearer except when using {AccessControl-_setupRole}.\n */\n event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);\n\n /**\n * @dev Emitted when `account` is revoked `role`.\n *\n * `sender` is the account that originated the contract call:\n * - if using `revokeRole`, it is the admin role bearer\n * - if using `renounceRole`, it is the role bearer (i.e. `account`)\n */\n event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);\n\n /**\n * @dev Returns `true` if `account` has been granted `role`.\n */\n function hasRole(bytes32 role, address account) external view returns (bool);\n\n /**\n * @dev Returns the admin role that controls `role`. See {grantRole} and\n * {revokeRole}.\n *\n * To change a role's admin, use {AccessControl-_setRoleAdmin}.\n */\n function getRoleAdmin(bytes32 role) external view returns (bytes32);\n\n /**\n * @dev Grants `role` to `account`.\n *\n * If `account` had not been already granted `role`, emits a {RoleGranted}\n * event.\n *\n * Requirements:\n *\n * - the caller must have ``role``'s admin role.\n */\n function grantRole(bytes32 role, address account) external;\n\n /**\n * @dev Revokes `role` from `account`.\n *\n * If `account` had been granted `role`, emits a {RoleRevoked} event.\n *\n * Requirements:\n *\n * - the caller must have ``role``'s admin role.\n */\n function revokeRole(bytes32 role, address account) external;\n\n /**\n * @dev Revokes `role` from the calling account.\n *\n * Roles are often managed via {grantRole} and {revokeRole}: this function's\n * purpose is to provide a mechanism for accounts to lose their privileges\n * if they are compromised (such as when a trusted device is misplaced).\n *\n * If the calling account had been granted `role`, emits a {RoleRevoked}\n * event.\n *\n * Requirements:\n *\n * - the caller must be `account`.\n */\n function renounceRole(bytes32 role, address account) external;\n}\n" + }, + "@openzeppelin/contracts/access/IAccessControlEnumerable.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (access/IAccessControlEnumerable.sol)\n\npragma solidity ^0.8.0;\n\nimport \"./IAccessControl.sol\";\n\n/**\n * @dev External interface of AccessControlEnumerable declared to support ERC165 detection.\n */\ninterface IAccessControlEnumerable is IAccessControl {\n /**\n * @dev Returns one of the accounts that have `role`. `index` must be a\n * value between 0 and {getRoleMemberCount}, non-inclusive.\n *\n * Role bearers are not sorted in any particular way, and their ordering may\n * change at any point.\n *\n * WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure\n * you perform all queries on the same block. See the following\n * https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post]\n * for more information.\n */\n function getRoleMember(bytes32 role, uint256 index) external view returns (address);\n\n /**\n * @dev Returns the number of accounts that have `role`. Can be used\n * together with {getRoleMember} to enumerate all bearers of a role.\n */\n function getRoleMemberCount(bytes32 role) external view returns (uint256);\n}\n" + }, + "@openzeppelin/contracts/access/Ownable.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (access/Ownable.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../utils/Context.sol\";\n\n/**\n * @dev Contract module which provides a basic access control mechanism, where\n * there is an account (an owner) that can be granted exclusive access to\n * specific functions.\n *\n * By default, the owner account will be the one that deploys the contract. This\n * can later be changed with {transferOwnership}.\n *\n * This module is used through inheritance. It will make available the modifier\n * `onlyOwner`, which can be applied to your functions to restrict their use to\n * the owner.\n */\nabstract contract Ownable is Context {\n address private _owner;\n\n event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\n\n /**\n * @dev Initializes the contract setting the deployer as the initial owner.\n */\n constructor() {\n _transferOwnership(_msgSender());\n }\n\n /**\n * @dev Returns the address of the current owner.\n */\n function owner() public view virtual returns (address) {\n return _owner;\n }\n\n /**\n * @dev Throws if called by any account other than the owner.\n */\n modifier onlyOwner() {\n require(owner() == _msgSender(), \"Ownable: caller is not the owner\");\n _;\n }\n\n /**\n * @dev Leaves the contract without owner. It will not be possible to call\n * `onlyOwner` functions anymore. Can only be called by the current owner.\n *\n * NOTE: Renouncing ownership will leave the contract without an owner,\n * thereby removing any functionality that is only available to the owner.\n */\n function renounceOwnership() public virtual onlyOwner {\n _transferOwnership(address(0));\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\n * Can only be called by the current owner.\n */\n function transferOwnership(address newOwner) public virtual onlyOwner {\n require(newOwner != address(0), \"Ownable: new owner is the zero address\");\n _transferOwnership(newOwner);\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\n * Internal function without access restriction.\n */\n function _transferOwnership(address newOwner) internal virtual {\n address oldOwner = _owner;\n _owner = newOwner;\n emit OwnershipTransferred(oldOwner, newOwner);\n }\n}\n" + }, + "@openzeppelin/contracts/security/Pausable.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (security/Pausable.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../utils/Context.sol\";\n\n/**\n * @dev Contract module which allows children to implement an emergency stop\n * mechanism that can be triggered by an authorized account.\n *\n * This module is used through inheritance. It will make available the\n * modifiers `whenNotPaused` and `whenPaused`, which can be applied to\n * the functions of your contract. Note that they will not be pausable by\n * simply including this module, only once the modifiers are put in place.\n */\nabstract contract Pausable is Context {\n /**\n * @dev Emitted when the pause is triggered by `account`.\n */\n event Paused(address account);\n\n /**\n * @dev Emitted when the pause is lifted by `account`.\n */\n event Unpaused(address account);\n\n bool private _paused;\n\n /**\n * @dev Initializes the contract in unpaused state.\n */\n constructor() {\n _paused = false;\n }\n\n /**\n * @dev Returns true if the contract is paused, and false otherwise.\n */\n function paused() public view virtual returns (bool) {\n return _paused;\n }\n\n /**\n * @dev Modifier to make a function callable only when the contract is not paused.\n *\n * Requirements:\n *\n * - The contract must not be paused.\n */\n modifier whenNotPaused() {\n require(!paused(), \"Pausable: paused\");\n _;\n }\n\n /**\n * @dev Modifier to make a function callable only when the contract is paused.\n *\n * Requirements:\n *\n * - The contract must be paused.\n */\n modifier whenPaused() {\n require(paused(), \"Pausable: not paused\");\n _;\n }\n\n /**\n * @dev Triggers stopped state.\n *\n * Requirements:\n *\n * - The contract must not be paused.\n */\n function _pause() internal virtual whenNotPaused {\n _paused = true;\n emit Paused(_msgSender());\n }\n\n /**\n * @dev Returns to normal state.\n *\n * Requirements:\n *\n * - The contract must be paused.\n */\n function _unpause() internal virtual whenPaused {\n _paused = false;\n emit Unpaused(_msgSender());\n }\n}\n" + }, + "@openzeppelin/contracts/token/ERC20/ERC20.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (token/ERC20/ERC20.sol)\n\npragma solidity ^0.8.0;\n\nimport \"./IERC20.sol\";\nimport \"./extensions/IERC20Metadata.sol\";\nimport \"../../utils/Context.sol\";\n\n/**\n * @dev Implementation of the {IERC20} interface.\n *\n * This implementation is agnostic to the way tokens are created. This means\n * that a supply mechanism has to be added in a derived contract using {_mint}.\n * For a generic mechanism see {ERC20PresetMinterPauser}.\n *\n * TIP: For a detailed writeup see our guide\n * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How\n * to implement supply mechanisms].\n *\n * We have followed general OpenZeppelin Contracts guidelines: functions revert\n * instead returning `false` on failure. This behavior is nonetheless\n * conventional and does not conflict with the expectations of ERC20\n * applications.\n *\n * Additionally, an {Approval} event is emitted on calls to {transferFrom}.\n * This allows applications to reconstruct the allowance for all accounts just\n * by listening to said events. Other implementations of the EIP may not emit\n * these events, as it isn't required by the specification.\n *\n * Finally, the non-standard {decreaseAllowance} and {increaseAllowance}\n * functions have been added to mitigate the well-known issues around setting\n * allowances. See {IERC20-approve}.\n */\ncontract ERC20 is Context, IERC20, IERC20Metadata {\n mapping(address => uint256) private _balances;\n\n mapping(address => mapping(address => uint256)) private _allowances;\n\n uint256 private _totalSupply;\n\n string private _name;\n string private _symbol;\n\n /**\n * @dev Sets the values for {name} and {symbol}.\n *\n * The default value of {decimals} is 18. To select a different value for\n * {decimals} you should overload it.\n *\n * All two of these values are immutable: they can only be set once during\n * construction.\n */\n constructor(string memory name_, string memory symbol_) {\n _name = name_;\n _symbol = symbol_;\n }\n\n /**\n * @dev Returns the name of the token.\n */\n function name() public view virtual override returns (string memory) {\n return _name;\n }\n\n /**\n * @dev Returns the symbol of the token, usually a shorter version of the\n * name.\n */\n function symbol() public view virtual override returns (string memory) {\n return _symbol;\n }\n\n /**\n * @dev Returns the number of decimals used to get its user representation.\n * For example, if `decimals` equals `2`, a balance of `505` tokens should\n * be displayed to a user as `5.05` (`505 / 10 ** 2`).\n *\n * Tokens usually opt for a value of 18, imitating the relationship between\n * Ether and Wei. This is the value {ERC20} uses, unless this function is\n * overridden;\n *\n * NOTE: This information is only used for _display_ purposes: it in\n * no way affects any of the arithmetic of the contract, including\n * {IERC20-balanceOf} and {IERC20-transfer}.\n */\n function decimals() public view virtual override returns (uint8) {\n return 18;\n }\n\n /**\n * @dev See {IERC20-totalSupply}.\n */\n function totalSupply() public view virtual override returns (uint256) {\n return _totalSupply;\n }\n\n /**\n * @dev See {IERC20-balanceOf}.\n */\n function balanceOf(address account) public view virtual override returns (uint256) {\n return _balances[account];\n }\n\n /**\n * @dev See {IERC20-transfer}.\n *\n * Requirements:\n *\n * - `recipient` cannot be the zero address.\n * - the caller must have a balance of at least `amount`.\n */\n function transfer(address recipient, uint256 amount) public virtual override returns (bool) {\n _transfer(_msgSender(), recipient, amount);\n return true;\n }\n\n /**\n * @dev See {IERC20-allowance}.\n */\n function allowance(address owner, address spender) public view virtual override returns (uint256) {\n return _allowances[owner][spender];\n }\n\n /**\n * @dev See {IERC20-approve}.\n *\n * Requirements:\n *\n * - `spender` cannot be the zero address.\n */\n function approve(address spender, uint256 amount) public virtual override returns (bool) {\n _approve(_msgSender(), spender, amount);\n return true;\n }\n\n /**\n * @dev See {IERC20-transferFrom}.\n *\n * Emits an {Approval} event indicating the updated allowance. This is not\n * required by the EIP. See the note at the beginning of {ERC20}.\n *\n * Requirements:\n *\n * - `sender` and `recipient` cannot be the zero address.\n * - `sender` must have a balance of at least `amount`.\n * - the caller must have allowance for ``sender``'s tokens of at least\n * `amount`.\n */\n function transferFrom(\n address sender,\n address recipient,\n uint256 amount\n ) public virtual override returns (bool) {\n _transfer(sender, recipient, amount);\n\n uint256 currentAllowance = _allowances[sender][_msgSender()];\n require(currentAllowance >= amount, \"ERC20: transfer amount exceeds allowance\");\n unchecked {\n _approve(sender, _msgSender(), currentAllowance - amount);\n }\n\n return true;\n }\n\n /**\n * @dev Atomically increases the allowance granted to `spender` by the caller.\n *\n * This is an alternative to {approve} that can be used as a mitigation for\n * problems described in {IERC20-approve}.\n *\n * Emits an {Approval} event indicating the updated allowance.\n *\n * Requirements:\n *\n * - `spender` cannot be the zero address.\n */\n function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {\n _approve(_msgSender(), spender, _allowances[_msgSender()][spender] + addedValue);\n return true;\n }\n\n /**\n * @dev Atomically decreases the allowance granted to `spender` by the caller.\n *\n * This is an alternative to {approve} that can be used as a mitigation for\n * problems described in {IERC20-approve}.\n *\n * Emits an {Approval} event indicating the updated allowance.\n *\n * Requirements:\n *\n * - `spender` cannot be the zero address.\n * - `spender` must have allowance for the caller of at least\n * `subtractedValue`.\n */\n function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {\n uint256 currentAllowance = _allowances[_msgSender()][spender];\n require(currentAllowance >= subtractedValue, \"ERC20: decreased allowance below zero\");\n unchecked {\n _approve(_msgSender(), spender, currentAllowance - subtractedValue);\n }\n\n return true;\n }\n\n /**\n * @dev Moves `amount` of tokens from `sender` to `recipient`.\n *\n * This internal function is equivalent to {transfer}, and can be used to\n * e.g. implement automatic token fees, slashing mechanisms, etc.\n *\n * Emits a {Transfer} event.\n *\n * Requirements:\n *\n * - `sender` cannot be the zero address.\n * - `recipient` cannot be the zero address.\n * - `sender` must have a balance of at least `amount`.\n */\n function _transfer(\n address sender,\n address recipient,\n uint256 amount\n ) internal virtual {\n require(sender != address(0), \"ERC20: transfer from the zero address\");\n require(recipient != address(0), \"ERC20: transfer to the zero address\");\n\n _beforeTokenTransfer(sender, recipient, amount);\n\n uint256 senderBalance = _balances[sender];\n require(senderBalance >= amount, \"ERC20: transfer amount exceeds balance\");\n unchecked {\n _balances[sender] = senderBalance - amount;\n }\n _balances[recipient] += amount;\n\n emit Transfer(sender, recipient, amount);\n\n _afterTokenTransfer(sender, recipient, amount);\n }\n\n /** @dev Creates `amount` tokens and assigns them to `account`, increasing\n * the total supply.\n *\n * Emits a {Transfer} event with `from` set to the zero address.\n *\n * Requirements:\n *\n * - `account` cannot be the zero address.\n */\n function _mint(address account, uint256 amount) internal virtual {\n require(account != address(0), \"ERC20: mint to the zero address\");\n\n _beforeTokenTransfer(address(0), account, amount);\n\n _totalSupply += amount;\n _balances[account] += amount;\n emit Transfer(address(0), account, amount);\n\n _afterTokenTransfer(address(0), account, amount);\n }\n\n /**\n * @dev Destroys `amount` tokens from `account`, reducing the\n * total supply.\n *\n * Emits a {Transfer} event with `to` set to the zero address.\n *\n * Requirements:\n *\n * - `account` cannot be the zero address.\n * - `account` must have at least `amount` tokens.\n */\n function _burn(address account, uint256 amount) internal virtual {\n require(account != address(0), \"ERC20: burn from the zero address\");\n\n _beforeTokenTransfer(account, address(0), amount);\n\n uint256 accountBalance = _balances[account];\n require(accountBalance >= amount, \"ERC20: burn amount exceeds balance\");\n unchecked {\n _balances[account] = accountBalance - amount;\n }\n _totalSupply -= amount;\n\n emit Transfer(account, address(0), amount);\n\n _afterTokenTransfer(account, address(0), amount);\n }\n\n /**\n * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.\n *\n * This internal function is equivalent to `approve`, and can be used to\n * e.g. set automatic allowances for certain subsystems, etc.\n *\n * Emits an {Approval} event.\n *\n * Requirements:\n *\n * - `owner` cannot be the zero address.\n * - `spender` cannot be the zero address.\n */\n function _approve(\n address owner,\n address spender,\n uint256 amount\n ) internal virtual {\n require(owner != address(0), \"ERC20: approve from the zero address\");\n require(spender != address(0), \"ERC20: approve to the zero address\");\n\n _allowances[owner][spender] = amount;\n emit Approval(owner, spender, amount);\n }\n\n /**\n * @dev Hook that is called before any transfer of tokens. This includes\n * minting and burning.\n *\n * Calling conditions:\n *\n * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens\n * will be transferred to `to`.\n * - when `from` is zero, `amount` tokens will be minted for `to`.\n * - when `to` is zero, `amount` of ``from``'s tokens will be burned.\n * - `from` and `to` are never both zero.\n *\n * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].\n */\n function _beforeTokenTransfer(\n address from,\n address to,\n uint256 amount\n ) internal virtual {}\n\n /**\n * @dev Hook that is called after any transfer of tokens. This includes\n * minting and burning.\n *\n * Calling conditions:\n *\n * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens\n * has been transferred to `to`.\n * - when `from` is zero, `amount` tokens have been minted for `to`.\n * - when `to` is zero, `amount` of ``from``'s tokens have been burned.\n * - `from` and `to` are never both zero.\n *\n * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].\n */\n function _afterTokenTransfer(\n address from,\n address to,\n uint256 amount\n ) internal virtual {}\n}\n" + }, + "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../IERC20.sol\";\n\n/**\n * @dev Interface for the optional metadata functions from the ERC20 standard.\n *\n * _Available since v4.1._\n */\ninterface IERC20Metadata is IERC20 {\n /**\n * @dev Returns the name of the token.\n */\n function name() external view returns (string memory);\n\n /**\n * @dev Returns the symbol of the token.\n */\n function symbol() external view returns (string memory);\n\n /**\n * @dev Returns the decimals places of the token.\n */\n function decimals() external view returns (uint8);\n}\n" + }, + "@openzeppelin/contracts/token/ERC20/IERC20.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (token/ERC20/IERC20.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Interface of the ERC20 standard as defined in the EIP.\n */\ninterface IERC20 {\n /**\n * @dev Returns the amount of tokens in existence.\n */\n function totalSupply() external view returns (uint256);\n\n /**\n * @dev Returns the amount of tokens owned by `account`.\n */\n function balanceOf(address account) external view returns (uint256);\n\n /**\n * @dev Moves `amount` tokens from the caller's account to `recipient`.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transfer(address recipient, uint256 amount) external returns (bool);\n\n /**\n * @dev Returns the remaining number of tokens that `spender` will be\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\n * zero by default.\n *\n * This value changes when {approve} or {transferFrom} are called.\n */\n function allowance(address owner, address spender) external view returns (uint256);\n\n /**\n * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\n * that someone may use both the old and the new allowance by unfortunate\n * transaction ordering. One possible solution to mitigate this race\n * condition is to first reduce the spender's allowance to 0 and set the\n * desired value afterwards:\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\n *\n * Emits an {Approval} event.\n */\n function approve(address spender, uint256 amount) external returns (bool);\n\n /**\n * @dev Moves `amount` tokens from `sender` to `recipient` using the\n * allowance mechanism. `amount` is then deducted from the caller's\n * allowance.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transferFrom(\n address sender,\n address recipient,\n uint256 amount\n ) external returns (bool);\n\n /**\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\n * another (`to`).\n *\n * Note that `value` may be zero.\n */\n event Transfer(address indexed from, address indexed to, uint256 value);\n\n /**\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\n * a call to {approve}. `value` is the new allowance.\n */\n event Approval(address indexed owner, address indexed spender, uint256 value);\n}\n" + }, + "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (token/ERC20/utils/SafeERC20.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../IERC20.sol\";\nimport \"../../../utils/Address.sol\";\n\n/**\n * @title SafeERC20\n * @dev Wrappers around ERC20 operations that throw on failure (when the token\n * contract returns false). Tokens that return no value (and instead revert or\n * throw on failure) are also supported, non-reverting calls are assumed to be\n * successful.\n * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,\n * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.\n */\nlibrary SafeERC20 {\n using Address for address;\n\n function safeTransfer(\n IERC20 token,\n address to,\n uint256 value\n ) internal {\n _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));\n }\n\n function safeTransferFrom(\n IERC20 token,\n address from,\n address to,\n uint256 value\n ) internal {\n _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));\n }\n\n /**\n * @dev Deprecated. This function has issues similar to the ones found in\n * {IERC20-approve}, and its usage is discouraged.\n *\n * Whenever possible, use {safeIncreaseAllowance} and\n * {safeDecreaseAllowance} instead.\n */\n function safeApprove(\n IERC20 token,\n address spender,\n uint256 value\n ) internal {\n // safeApprove should only be called when setting an initial allowance,\n // or when resetting it to zero. To increase and decrease it, use\n // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'\n require(\n (value == 0) || (token.allowance(address(this), spender) == 0),\n \"SafeERC20: approve from non-zero to non-zero allowance\"\n );\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));\n }\n\n function safeIncreaseAllowance(\n IERC20 token,\n address spender,\n uint256 value\n ) internal {\n uint256 newAllowance = token.allowance(address(this), spender) + value;\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));\n }\n\n function safeDecreaseAllowance(\n IERC20 token,\n address spender,\n uint256 value\n ) internal {\n unchecked {\n uint256 oldAllowance = token.allowance(address(this), spender);\n require(oldAllowance >= value, \"SafeERC20: decreased allowance below zero\");\n uint256 newAllowance = oldAllowance - value;\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));\n }\n }\n\n /**\n * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement\n * on the return value: the return value is optional (but if data is returned, it must not be false).\n * @param token The token targeted by the call.\n * @param data The call data (encoded using abi.encode or one of its variants).\n */\n function _callOptionalReturn(IERC20 token, bytes memory data) private {\n // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since\n // we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that\n // the target address contains contract code and also asserts for success in the low-level call.\n\n bytes memory returndata = address(token).functionCall(data, \"SafeERC20: low-level call failed\");\n if (returndata.length > 0) {\n // Return data is optional\n require(abi.decode(returndata, (bool)), \"SafeERC20: ERC20 operation did not succeed\");\n }\n }\n}\n" + }, + "@openzeppelin/contracts/utils/Address.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (utils/Address.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Collection of functions related to the address type\n */\nlibrary Address {\n /**\n * @dev Returns true if `account` is a contract.\n *\n * [IMPORTANT]\n * ====\n * It is unsafe to assume that an address for which this function returns\n * false is an externally-owned account (EOA) and not a contract.\n *\n * Among others, `isContract` will return false for the following\n * types of addresses:\n *\n * - an externally-owned account\n * - a contract in construction\n * - an address where a contract will be created\n * - an address where a contract lived, but was destroyed\n * ====\n */\n function isContract(address account) internal view returns (bool) {\n // This method relies on extcodesize, which returns 0 for contracts in\n // construction, since the code is only stored at the end of the\n // constructor execution.\n\n uint256 size;\n assembly {\n size := extcodesize(account)\n }\n return size > 0;\n }\n\n /**\n * @dev Replacement for Solidity's `transfer`: sends `amount` wei to\n * `recipient`, forwarding all available gas and reverting on errors.\n *\n * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost\n * of certain opcodes, possibly making contracts go over the 2300 gas limit\n * imposed by `transfer`, making them unable to receive funds via\n * `transfer`. {sendValue} removes this limitation.\n *\n * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].\n *\n * IMPORTANT: because control is transferred to `recipient`, care must be\n * taken to not create reentrancy vulnerabilities. Consider using\n * {ReentrancyGuard} or the\n * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].\n */\n function sendValue(address payable recipient, uint256 amount) internal {\n require(address(this).balance >= amount, \"Address: insufficient balance\");\n\n (bool success, ) = recipient.call{value: amount}(\"\");\n require(success, \"Address: unable to send value, recipient may have reverted\");\n }\n\n /**\n * @dev Performs a Solidity function call using a low level `call`. A\n * plain `call` is an unsafe replacement for a function call: use this\n * function instead.\n *\n * If `target` reverts with a revert reason, it is bubbled up by this\n * function (like regular Solidity function calls).\n *\n * Returns the raw returned data. To convert to the expected return value,\n * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].\n *\n * Requirements:\n *\n * - `target` must be a contract.\n * - calling `target` with `data` must not revert.\n *\n * _Available since v3.1._\n */\n function functionCall(address target, bytes memory data) internal returns (bytes memory) {\n return functionCall(target, data, \"Address: low-level call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with\n * `errorMessage` as a fallback revert reason when `target` reverts.\n *\n * _Available since v3.1._\n */\n function functionCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal returns (bytes memory) {\n return functionCallWithValue(target, data, 0, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but also transferring `value` wei to `target`.\n *\n * Requirements:\n *\n * - the calling contract must have an ETH balance of at least `value`.\n * - the called Solidity function must be `payable`.\n *\n * _Available since v3.1._\n */\n function functionCallWithValue(\n address target,\n bytes memory data,\n uint256 value\n ) internal returns (bytes memory) {\n return functionCallWithValue(target, data, value, \"Address: low-level call with value failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but\n * with `errorMessage` as a fallback revert reason when `target` reverts.\n *\n * _Available since v3.1._\n */\n function functionCallWithValue(\n address target,\n bytes memory data,\n uint256 value,\n string memory errorMessage\n ) internal returns (bytes memory) {\n require(address(this).balance >= value, \"Address: insufficient balance for call\");\n require(isContract(target), \"Address: call to non-contract\");\n\n (bool success, bytes memory returndata) = target.call{value: value}(data);\n return verifyCallResult(success, returndata, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but performing a static call.\n *\n * _Available since v3.3._\n */\n function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {\n return functionStaticCall(target, data, \"Address: low-level static call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\n * but performing a static call.\n *\n * _Available since v3.3._\n */\n function functionStaticCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal view returns (bytes memory) {\n require(isContract(target), \"Address: static call to non-contract\");\n\n (bool success, bytes memory returndata) = target.staticcall(data);\n return verifyCallResult(success, returndata, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but performing a delegate call.\n *\n * _Available since v3.4._\n */\n function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {\n return functionDelegateCall(target, data, \"Address: low-level delegate call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\n * but performing a delegate call.\n *\n * _Available since v3.4._\n */\n function functionDelegateCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal returns (bytes memory) {\n require(isContract(target), \"Address: delegate call to non-contract\");\n\n (bool success, bytes memory returndata) = target.delegatecall(data);\n return verifyCallResult(success, returndata, errorMessage);\n }\n\n /**\n * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the\n * revert reason using the provided one.\n *\n * _Available since v4.3._\n */\n function verifyCallResult(\n bool success,\n bytes memory returndata,\n string memory errorMessage\n ) internal pure returns (bytes memory) {\n if (success) {\n return returndata;\n } else {\n // Look for revert reason and bubble it up if present\n if (returndata.length > 0) {\n // The easiest way to bubble the revert reason is using memory via assembly\n\n assembly {\n let returndata_size := mload(returndata)\n revert(add(32, returndata), returndata_size)\n }\n } else {\n revert(errorMessage);\n }\n }\n }\n}\n" + }, + "@openzeppelin/contracts/utils/Context.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Provides information about the current execution context, including the\n * sender of the transaction and its data. While these are generally available\n * via msg.sender and msg.data, they should not be accessed in such a direct\n * manner, since when dealing with meta-transactions the account sending and\n * paying for execution may not be the actual sender (as far as an application\n * is concerned).\n *\n * This contract is only required for intermediate, library-like contracts.\n */\nabstract contract Context {\n function _msgSender() internal view virtual returns (address) {\n return msg.sender;\n }\n\n function _msgData() internal view virtual returns (bytes calldata) {\n return msg.data;\n }\n}\n" + }, + "@openzeppelin/contracts/utils/introspection/ERC165.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol)\n\npragma solidity ^0.8.0;\n\nimport \"./IERC165.sol\";\n\n/**\n * @dev Implementation of the {IERC165} interface.\n *\n * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check\n * for the additional interface id that will be supported. For example:\n *\n * ```solidity\n * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {\n * return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);\n * }\n * ```\n *\n * Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation.\n */\nabstract contract ERC165 is IERC165 {\n /**\n * @dev See {IERC165-supportsInterface}.\n */\n function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {\n return interfaceId == type(IERC165).interfaceId;\n }\n}\n" + }, + "@openzeppelin/contracts/utils/introspection/IERC165.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Interface of the ERC165 standard, as defined in the\n * https://eips.ethereum.org/EIPS/eip-165[EIP].\n *\n * Implementers can declare support of contract interfaces, which can then be\n * queried by others ({ERC165Checker}).\n *\n * For an implementation, see {ERC165}.\n */\ninterface IERC165 {\n /**\n * @dev Returns true if this contract implements the interface defined by\n * `interfaceId`. See the corresponding\n * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]\n * to learn more about how these ids are created.\n *\n * This function call must use less than 30 000 gas.\n */\n function supportsInterface(bytes4 interfaceId) external view returns (bool);\n}\n" + }, + "@openzeppelin/contracts/utils/math/Math.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (utils/math/Math.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Standard math utilities missing in the Solidity language.\n */\nlibrary Math {\n /**\n * @dev Returns the largest of two numbers.\n */\n function max(uint256 a, uint256 b) internal pure returns (uint256) {\n return a >= b ? a : b;\n }\n\n /**\n * @dev Returns the smallest of two numbers.\n */\n function min(uint256 a, uint256 b) internal pure returns (uint256) {\n return a < b ? a : b;\n }\n\n /**\n * @dev Returns the average of two numbers. The result is rounded towards\n * zero.\n */\n function average(uint256 a, uint256 b) internal pure returns (uint256) {\n // (a + b) / 2 can overflow.\n return (a & b) + (a ^ b) / 2;\n }\n\n /**\n * @dev Returns the ceiling of the division of two numbers.\n *\n * This differs from standard division with `/` in that it rounds up instead\n * of rounding down.\n */\n function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {\n // (a + b - 1) / b can overflow on addition, so we distribute.\n return a / b + (a % b == 0 ? 0 : 1);\n }\n}\n" + }, + "@openzeppelin/contracts/utils/math/SafeCast.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (utils/math/SafeCast.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Wrappers over Solidity's uintXX/intXX casting operators with added overflow\n * checks.\n *\n * Downcasting from uint256/int256 in Solidity does not revert on overflow. This can\n * easily result in undesired exploitation or bugs, since developers usually\n * assume that overflows raise errors. `SafeCast` restores this intuition by\n * reverting the transaction when such an operation overflows.\n *\n * Using this library instead of the unchecked operations eliminates an entire\n * class of bugs, so it's recommended to use it always.\n *\n * Can be combined with {SafeMath} and {SignedSafeMath} to extend it to smaller types, by performing\n * all math on `uint256` and `int256` and then downcasting.\n */\nlibrary SafeCast {\n /**\n * @dev Returns the downcasted uint224 from uint256, reverting on\n * overflow (when the input is greater than largest uint224).\n *\n * Counterpart to Solidity's `uint224` operator.\n *\n * Requirements:\n *\n * - input must fit into 224 bits\n */\n function toUint224(uint256 value) internal pure returns (uint224) {\n require(value <= type(uint224).max, \"SafeCast: value doesn't fit in 224 bits\");\n return uint224(value);\n }\n\n /**\n * @dev Returns the downcasted uint128 from uint256, reverting on\n * overflow (when the input is greater than largest uint128).\n *\n * Counterpart to Solidity's `uint128` operator.\n *\n * Requirements:\n *\n * - input must fit into 128 bits\n */\n function toUint128(uint256 value) internal pure returns (uint128) {\n require(value <= type(uint128).max, \"SafeCast: value doesn't fit in 128 bits\");\n return uint128(value);\n }\n\n /**\n * @dev Returns the downcasted uint96 from uint256, reverting on\n * overflow (when the input is greater than largest uint96).\n *\n * Counterpart to Solidity's `uint96` operator.\n *\n * Requirements:\n *\n * - input must fit into 96 bits\n */\n function toUint96(uint256 value) internal pure returns (uint96) {\n require(value <= type(uint96).max, \"SafeCast: value doesn't fit in 96 bits\");\n return uint96(value);\n }\n\n /**\n * @dev Returns the downcasted uint64 from uint256, reverting on\n * overflow (when the input is greater than largest uint64).\n *\n * Counterpart to Solidity's `uint64` operator.\n *\n * Requirements:\n *\n * - input must fit into 64 bits\n */\n function toUint64(uint256 value) internal pure returns (uint64) {\n require(value <= type(uint64).max, \"SafeCast: value doesn't fit in 64 bits\");\n return uint64(value);\n }\n\n /**\n * @dev Returns the downcasted uint32 from uint256, reverting on\n * overflow (when the input is greater than largest uint32).\n *\n * Counterpart to Solidity's `uint32` operator.\n *\n * Requirements:\n *\n * - input must fit into 32 bits\n */\n function toUint32(uint256 value) internal pure returns (uint32) {\n require(value <= type(uint32).max, \"SafeCast: value doesn't fit in 32 bits\");\n return uint32(value);\n }\n\n /**\n * @dev Returns the downcasted uint16 from uint256, reverting on\n * overflow (when the input is greater than largest uint16).\n *\n * Counterpart to Solidity's `uint16` operator.\n *\n * Requirements:\n *\n * - input must fit into 16 bits\n */\n function toUint16(uint256 value) internal pure returns (uint16) {\n require(value <= type(uint16).max, \"SafeCast: value doesn't fit in 16 bits\");\n return uint16(value);\n }\n\n /**\n * @dev Returns the downcasted uint8 from uint256, reverting on\n * overflow (when the input is greater than largest uint8).\n *\n * Counterpart to Solidity's `uint8` operator.\n *\n * Requirements:\n *\n * - input must fit into 8 bits.\n */\n function toUint8(uint256 value) internal pure returns (uint8) {\n require(value <= type(uint8).max, \"SafeCast: value doesn't fit in 8 bits\");\n return uint8(value);\n }\n\n /**\n * @dev Converts a signed int256 into an unsigned uint256.\n *\n * Requirements:\n *\n * - input must be greater than or equal to 0.\n */\n function toUint256(int256 value) internal pure returns (uint256) {\n require(value >= 0, \"SafeCast: value must be positive\");\n return uint256(value);\n }\n\n /**\n * @dev Returns the downcasted int128 from int256, reverting on\n * overflow (when the input is less than smallest int128 or\n * greater than largest int128).\n *\n * Counterpart to Solidity's `int128` operator.\n *\n * Requirements:\n *\n * - input must fit into 128 bits\n *\n * _Available since v3.1._\n */\n function toInt128(int256 value) internal pure returns (int128) {\n require(value >= type(int128).min && value <= type(int128).max, \"SafeCast: value doesn't fit in 128 bits\");\n return int128(value);\n }\n\n /**\n * @dev Returns the downcasted int64 from int256, reverting on\n * overflow (when the input is less than smallest int64 or\n * greater than largest int64).\n *\n * Counterpart to Solidity's `int64` operator.\n *\n * Requirements:\n *\n * - input must fit into 64 bits\n *\n * _Available since v3.1._\n */\n function toInt64(int256 value) internal pure returns (int64) {\n require(value >= type(int64).min && value <= type(int64).max, \"SafeCast: value doesn't fit in 64 bits\");\n return int64(value);\n }\n\n /**\n * @dev Returns the downcasted int32 from int256, reverting on\n * overflow (when the input is less than smallest int32 or\n * greater than largest int32).\n *\n * Counterpart to Solidity's `int32` operator.\n *\n * Requirements:\n *\n * - input must fit into 32 bits\n *\n * _Available since v3.1._\n */\n function toInt32(int256 value) internal pure returns (int32) {\n require(value >= type(int32).min && value <= type(int32).max, \"SafeCast: value doesn't fit in 32 bits\");\n return int32(value);\n }\n\n /**\n * @dev Returns the downcasted int16 from int256, reverting on\n * overflow (when the input is less than smallest int16 or\n * greater than largest int16).\n *\n * Counterpart to Solidity's `int16` operator.\n *\n * Requirements:\n *\n * - input must fit into 16 bits\n *\n * _Available since v3.1._\n */\n function toInt16(int256 value) internal pure returns (int16) {\n require(value >= type(int16).min && value <= type(int16).max, \"SafeCast: value doesn't fit in 16 bits\");\n return int16(value);\n }\n\n /**\n * @dev Returns the downcasted int8 from int256, reverting on\n * overflow (when the input is less than smallest int8 or\n * greater than largest int8).\n *\n * Counterpart to Solidity's `int8` operator.\n *\n * Requirements:\n *\n * - input must fit into 8 bits.\n *\n * _Available since v3.1._\n */\n function toInt8(int256 value) internal pure returns (int8) {\n require(value >= type(int8).min && value <= type(int8).max, \"SafeCast: value doesn't fit in 8 bits\");\n return int8(value);\n }\n\n /**\n * @dev Converts an unsigned uint256 into a signed int256.\n *\n * Requirements:\n *\n * - input must be less than or equal to maxInt256.\n */\n function toInt256(uint256 value) internal pure returns (int256) {\n // Note: Unsafe cast below is okay because `type(int256).max` is guaranteed to be positive\n require(value <= uint256(type(int256).max), \"SafeCast: value doesn't fit in an int256\");\n return int256(value);\n }\n}\n" + }, + "@openzeppelin/contracts/utils/math/SafeMath.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (utils/math/SafeMath.sol)\n\npragma solidity ^0.8.0;\n\n// CAUTION\n// This version of SafeMath should only be used with Solidity 0.8 or later,\n// because it relies on the compiler's built in overflow checks.\n\n/**\n * @dev Wrappers over Solidity's arithmetic operations.\n *\n * NOTE: `SafeMath` is generally not needed starting with Solidity 0.8, since the compiler\n * now has built in overflow checking.\n */\nlibrary SafeMath {\n /**\n * @dev Returns the addition of two unsigned integers, with an overflow flag.\n *\n * _Available since v3.4._\n */\n function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {\n unchecked {\n uint256 c = a + b;\n if (c < a) return (false, 0);\n return (true, c);\n }\n }\n\n /**\n * @dev Returns the substraction of two unsigned integers, with an overflow flag.\n *\n * _Available since v3.4._\n */\n function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {\n unchecked {\n if (b > a) return (false, 0);\n return (true, a - b);\n }\n }\n\n /**\n * @dev Returns the multiplication of two unsigned integers, with an overflow flag.\n *\n * _Available since v3.4._\n */\n function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {\n unchecked {\n // Gas optimization: this is cheaper than requiring 'a' not being zero, but the\n // benefit is lost if 'b' is also tested.\n // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522\n if (a == 0) return (true, 0);\n uint256 c = a * b;\n if (c / a != b) return (false, 0);\n return (true, c);\n }\n }\n\n /**\n * @dev Returns the division of two unsigned integers, with a division by zero flag.\n *\n * _Available since v3.4._\n */\n function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {\n unchecked {\n if (b == 0) return (false, 0);\n return (true, a / b);\n }\n }\n\n /**\n * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.\n *\n * _Available since v3.4._\n */\n function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {\n unchecked {\n if (b == 0) return (false, 0);\n return (true, a % b);\n }\n }\n\n /**\n * @dev Returns the addition of two unsigned integers, reverting on\n * overflow.\n *\n * Counterpart to Solidity's `+` operator.\n *\n * Requirements:\n *\n * - Addition cannot overflow.\n */\n function add(uint256 a, uint256 b) internal pure returns (uint256) {\n return a + b;\n }\n\n /**\n * @dev Returns the subtraction of two unsigned integers, reverting on\n * overflow (when the result is negative).\n *\n * Counterpart to Solidity's `-` operator.\n *\n * Requirements:\n *\n * - Subtraction cannot overflow.\n */\n function sub(uint256 a, uint256 b) internal pure returns (uint256) {\n return a - b;\n }\n\n /**\n * @dev Returns the multiplication of two unsigned integers, reverting on\n * overflow.\n *\n * Counterpart to Solidity's `*` operator.\n *\n * Requirements:\n *\n * - Multiplication cannot overflow.\n */\n function mul(uint256 a, uint256 b) internal pure returns (uint256) {\n return a * b;\n }\n\n /**\n * @dev Returns the integer division of two unsigned integers, reverting on\n * division by zero. The result is rounded towards zero.\n *\n * Counterpart to Solidity's `/` operator.\n *\n * Requirements:\n *\n * - The divisor cannot be zero.\n */\n function div(uint256 a, uint256 b) internal pure returns (uint256) {\n return a / b;\n }\n\n /**\n * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),\n * reverting when dividing by zero.\n *\n * Counterpart to Solidity's `%` operator. This function uses a `revert`\n * opcode (which leaves remaining gas untouched) while Solidity uses an\n * invalid opcode to revert (consuming all remaining gas).\n *\n * Requirements:\n *\n * - The divisor cannot be zero.\n */\n function mod(uint256 a, uint256 b) internal pure returns (uint256) {\n return a % b;\n }\n\n /**\n * @dev Returns the subtraction of two unsigned integers, reverting with custom message on\n * overflow (when the result is negative).\n *\n * CAUTION: This function is deprecated because it requires allocating memory for the error\n * message unnecessarily. For custom revert reasons use {trySub}.\n *\n * Counterpart to Solidity's `-` operator.\n *\n * Requirements:\n *\n * - Subtraction cannot overflow.\n */\n function sub(\n uint256 a,\n uint256 b,\n string memory errorMessage\n ) internal pure returns (uint256) {\n unchecked {\n require(b <= a, errorMessage);\n return a - b;\n }\n }\n\n /**\n * @dev Returns the integer division of two unsigned integers, reverting with custom message on\n * division by zero. The result is rounded towards zero.\n *\n * Counterpart to Solidity's `/` operator. Note: this function uses a\n * `revert` opcode (which leaves remaining gas untouched) while Solidity\n * uses an invalid opcode to revert (consuming all remaining gas).\n *\n * Requirements:\n *\n * - The divisor cannot be zero.\n */\n function div(\n uint256 a,\n uint256 b,\n string memory errorMessage\n ) internal pure returns (uint256) {\n unchecked {\n require(b > 0, errorMessage);\n return a / b;\n }\n }\n\n /**\n * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),\n * reverting with custom message when dividing by zero.\n *\n * CAUTION: This function is deprecated because it requires allocating memory for the error\n * message unnecessarily. For custom revert reasons use {tryMod}.\n *\n * Counterpart to Solidity's `%` operator. This function uses a `revert`\n * opcode (which leaves remaining gas untouched) while Solidity uses an\n * invalid opcode to revert (consuming all remaining gas).\n *\n * Requirements:\n *\n * - The divisor cannot be zero.\n */\n function mod(\n uint256 a,\n uint256 b,\n string memory errorMessage\n ) internal pure returns (uint256) {\n unchecked {\n require(b > 0, errorMessage);\n return a % b;\n }\n }\n}\n" + }, + "@openzeppelin/contracts/utils/Strings.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (utils/Strings.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev String operations.\n */\nlibrary Strings {\n bytes16 private constant _HEX_SYMBOLS = \"0123456789abcdef\";\n\n /**\n * @dev Converts a `uint256` to its ASCII `string` decimal representation.\n */\n function toString(uint256 value) internal pure returns (string memory) {\n // Inspired by OraclizeAPI's implementation - MIT licence\n // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol\n\n if (value == 0) {\n return \"0\";\n }\n uint256 temp = value;\n uint256 digits;\n while (temp != 0) {\n digits++;\n temp /= 10;\n }\n bytes memory buffer = new bytes(digits);\n while (value != 0) {\n digits -= 1;\n buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));\n value /= 10;\n }\n return string(buffer);\n }\n\n /**\n * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.\n */\n function toHexString(uint256 value) internal pure returns (string memory) {\n if (value == 0) {\n return \"0x00\";\n }\n uint256 temp = value;\n uint256 length = 0;\n while (temp != 0) {\n length++;\n temp >>= 8;\n }\n return toHexString(value, length);\n }\n\n /**\n * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.\n */\n function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {\n bytes memory buffer = new bytes(2 * length + 2);\n buffer[0] = \"0\";\n buffer[1] = \"x\";\n for (uint256 i = 2 * length + 1; i > 1; --i) {\n buffer[i] = _HEX_SYMBOLS[value & 0xf];\n value >>= 4;\n }\n require(value == 0, \"Strings: hex length insufficient\");\n return string(buffer);\n }\n}\n" + }, + "@openzeppelin/contracts/utils/structs/EnumerableSet.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (utils/structs/EnumerableSet.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Library for managing\n * https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive\n * types.\n *\n * Sets have the following properties:\n *\n * - Elements are added, removed, and checked for existence in constant time\n * (O(1)).\n * - Elements are enumerated in O(n). No guarantees are made on the ordering.\n *\n * ```\n * contract Example {\n * // Add the library methods\n * using EnumerableSet for EnumerableSet.AddressSet;\n *\n * // Declare a set state variable\n * EnumerableSet.AddressSet private mySet;\n * }\n * ```\n *\n * As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)\n * and `uint256` (`UintSet`) are supported.\n */\nlibrary EnumerableSet {\n // To implement this library for multiple types with as little code\n // repetition as possible, we write it in terms of a generic Set type with\n // bytes32 values.\n // The Set implementation uses private functions, and user-facing\n // implementations (such as AddressSet) are just wrappers around the\n // underlying Set.\n // This means that we can only create new EnumerableSets for types that fit\n // in bytes32.\n\n struct Set {\n // Storage of set values\n bytes32[] _values;\n // Position of the value in the `values` array, plus 1 because index 0\n // means a value is not in the set.\n mapping(bytes32 => uint256) _indexes;\n }\n\n /**\n * @dev Add a value to a set. O(1).\n *\n * Returns true if the value was added to the set, that is if it was not\n * already present.\n */\n function _add(Set storage set, bytes32 value) private returns (bool) {\n if (!_contains(set, value)) {\n set._values.push(value);\n // The value is stored at length-1, but we add 1 to all indexes\n // and use 0 as a sentinel value\n set._indexes[value] = set._values.length;\n return true;\n } else {\n return false;\n }\n }\n\n /**\n * @dev Removes a value from a set. O(1).\n *\n * Returns true if the value was removed from the set, that is if it was\n * present.\n */\n function _remove(Set storage set, bytes32 value) private returns (bool) {\n // We read and store the value's index to prevent multiple reads from the same storage slot\n uint256 valueIndex = set._indexes[value];\n\n if (valueIndex != 0) {\n // Equivalent to contains(set, value)\n // To delete an element from the _values array in O(1), we swap the element to delete with the last one in\n // the array, and then remove the last element (sometimes called as 'swap and pop').\n // This modifies the order of the array, as noted in {at}.\n\n uint256 toDeleteIndex = valueIndex - 1;\n uint256 lastIndex = set._values.length - 1;\n\n if (lastIndex != toDeleteIndex) {\n bytes32 lastvalue = set._values[lastIndex];\n\n // Move the last value to the index where the value to delete is\n set._values[toDeleteIndex] = lastvalue;\n // Update the index for the moved value\n set._indexes[lastvalue] = valueIndex; // Replace lastvalue's index to valueIndex\n }\n\n // Delete the slot where the moved value was stored\n set._values.pop();\n\n // Delete the index for the deleted slot\n delete set._indexes[value];\n\n return true;\n } else {\n return false;\n }\n }\n\n /**\n * @dev Returns true if the value is in the set. O(1).\n */\n function _contains(Set storage set, bytes32 value) private view returns (bool) {\n return set._indexes[value] != 0;\n }\n\n /**\n * @dev Returns the number of values on the set. O(1).\n */\n function _length(Set storage set) private view returns (uint256) {\n return set._values.length;\n }\n\n /**\n * @dev Returns the value stored at position `index` in the set. O(1).\n *\n * Note that there are no guarantees on the ordering of values inside the\n * array, and it may change when more values are added or removed.\n *\n * Requirements:\n *\n * - `index` must be strictly less than {length}.\n */\n function _at(Set storage set, uint256 index) private view returns (bytes32) {\n return set._values[index];\n }\n\n /**\n * @dev Return the entire set in an array\n *\n * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed\n * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that\n * this function has an unbounded cost, and using it as part of a state-changing function may render the function\n * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.\n */\n function _values(Set storage set) private view returns (bytes32[] memory) {\n return set._values;\n }\n\n // Bytes32Set\n\n struct Bytes32Set {\n Set _inner;\n }\n\n /**\n * @dev Add a value to a set. O(1).\n *\n * Returns true if the value was added to the set, that is if it was not\n * already present.\n */\n function add(Bytes32Set storage set, bytes32 value) internal returns (bool) {\n return _add(set._inner, value);\n }\n\n /**\n * @dev Removes a value from a set. O(1).\n *\n * Returns true if the value was removed from the set, that is if it was\n * present.\n */\n function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) {\n return _remove(set._inner, value);\n }\n\n /**\n * @dev Returns true if the value is in the set. O(1).\n */\n function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {\n return _contains(set._inner, value);\n }\n\n /**\n * @dev Returns the number of values in the set. O(1).\n */\n function length(Bytes32Set storage set) internal view returns (uint256) {\n return _length(set._inner);\n }\n\n /**\n * @dev Returns the value stored at position `index` in the set. O(1).\n *\n * Note that there are no guarantees on the ordering of values inside the\n * array, and it may change when more values are added or removed.\n *\n * Requirements:\n *\n * - `index` must be strictly less than {length}.\n */\n function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {\n return _at(set._inner, index);\n }\n\n /**\n * @dev Return the entire set in an array\n *\n * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed\n * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that\n * this function has an unbounded cost, and using it as part of a state-changing function may render the function\n * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.\n */\n function values(Bytes32Set storage set) internal view returns (bytes32[] memory) {\n return _values(set._inner);\n }\n\n // AddressSet\n\n struct AddressSet {\n Set _inner;\n }\n\n /**\n * @dev Add a value to a set. O(1).\n *\n * Returns true if the value was added to the set, that is if it was not\n * already present.\n */\n function add(AddressSet storage set, address value) internal returns (bool) {\n return _add(set._inner, bytes32(uint256(uint160(value))));\n }\n\n /**\n * @dev Removes a value from a set. O(1).\n *\n * Returns true if the value was removed from the set, that is if it was\n * present.\n */\n function remove(AddressSet storage set, address value) internal returns (bool) {\n return _remove(set._inner, bytes32(uint256(uint160(value))));\n }\n\n /**\n * @dev Returns true if the value is in the set. O(1).\n */\n function contains(AddressSet storage set, address value) internal view returns (bool) {\n return _contains(set._inner, bytes32(uint256(uint160(value))));\n }\n\n /**\n * @dev Returns the number of values in the set. O(1).\n */\n function length(AddressSet storage set) internal view returns (uint256) {\n return _length(set._inner);\n }\n\n /**\n * @dev Returns the value stored at position `index` in the set. O(1).\n *\n * Note that there are no guarantees on the ordering of values inside the\n * array, and it may change when more values are added or removed.\n *\n * Requirements:\n *\n * - `index` must be strictly less than {length}.\n */\n function at(AddressSet storage set, uint256 index) internal view returns (address) {\n return address(uint160(uint256(_at(set._inner, index))));\n }\n\n /**\n * @dev Return the entire set in an array\n *\n * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed\n * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that\n * this function has an unbounded cost, and using it as part of a state-changing function may render the function\n * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.\n */\n function values(AddressSet storage set) internal view returns (address[] memory) {\n bytes32[] memory store = _values(set._inner);\n address[] memory result;\n\n assembly {\n result := store\n }\n\n return result;\n }\n\n // UintSet\n\n struct UintSet {\n Set _inner;\n }\n\n /**\n * @dev Add a value to a set. O(1).\n *\n * Returns true if the value was added to the set, that is if it was not\n * already present.\n */\n function add(UintSet storage set, uint256 value) internal returns (bool) {\n return _add(set._inner, bytes32(value));\n }\n\n /**\n * @dev Removes a value from a set. O(1).\n *\n * Returns true if the value was removed from the set, that is if it was\n * present.\n */\n function remove(UintSet storage set, uint256 value) internal returns (bool) {\n return _remove(set._inner, bytes32(value));\n }\n\n /**\n * @dev Returns true if the value is in the set. O(1).\n */\n function contains(UintSet storage set, uint256 value) internal view returns (bool) {\n return _contains(set._inner, bytes32(value));\n }\n\n /**\n * @dev Returns the number of values on the set. O(1).\n */\n function length(UintSet storage set) internal view returns (uint256) {\n return _length(set._inner);\n }\n\n /**\n * @dev Returns the value stored at position `index` in the set. O(1).\n *\n * Note that there are no guarantees on the ordering of values inside the\n * array, and it may change when more values are added or removed.\n *\n * Requirements:\n *\n * - `index` must be strictly less than {length}.\n */\n function at(UintSet storage set, uint256 index) internal view returns (uint256) {\n return uint256(_at(set._inner, index));\n }\n\n /**\n * @dev Return the entire set in an array\n *\n * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed\n * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that\n * this function has an unbounded cost, and using it as part of a state-changing function may render the function\n * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.\n */\n function values(UintSet storage set) internal view returns (uint256[] memory) {\n bytes32[] memory store = _values(set._inner);\n uint256[] memory result;\n\n assembly {\n result := store\n }\n\n return result;\n }\n}\n" + }, + "contracts/automation/AbstractCCIPBridgeHelperModule.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { AbstractSafeModule } from \"./AbstractSafeModule.sol\";\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\n\nimport { IRouterClient } from \"@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IRouterClient.sol\";\nimport { Client } from \"@chainlink/contracts-ccip/src/v0.8/ccip/libraries/Client.sol\";\n\nabstract contract AbstractCCIPBridgeHelperModule is AbstractSafeModule {\n /**\n * @notice Bridges a token from the source chain to the destination chain using CCIP\n * @param ccipRouter The CCIP router contract\n * @param destinationChainSelector The selector for the destination chain\n * @param token The token to bridge\n * @param amount The amount of token to bridge\n */\n function _bridgeTokenWithCCIP(\n IRouterClient ccipRouter,\n uint64 destinationChainSelector,\n IERC20 token,\n uint256 amount\n ) internal {\n bool success;\n\n // Approve CCIP Router to move the token\n success = safeContract.execTransactionFromModule(\n address(token),\n 0, // Value\n abi.encodeWithSelector(token.approve.selector, ccipRouter, amount),\n 0 // Call\n );\n require(success, \"Failed to approve token\");\n\n Client.EVMTokenAmount[]\n memory tokenAmounts = new Client.EVMTokenAmount[](1);\n Client.EVMTokenAmount memory tokenAmount = Client.EVMTokenAmount({\n token: address(token),\n amount: amount\n });\n tokenAmounts[0] = tokenAmount;\n\n Client.EVM2AnyMessage memory ccipMessage = Client.EVM2AnyMessage({\n receiver: abi.encode(address(safeContract)), // ABI-encoded receiver address\n data: abi.encode(\"\"),\n tokenAmounts: tokenAmounts,\n extraArgs: Client._argsToBytes(\n Client.EVMExtraArgsV1({ gasLimit: 0 })\n ),\n feeToken: address(0)\n });\n\n // Get CCIP fee\n uint256 ccipFee = ccipRouter.getFee(\n destinationChainSelector,\n ccipMessage\n );\n\n // Send CCIP message\n success = safeContract.execTransactionFromModule(\n address(ccipRouter),\n ccipFee, // Value\n abi.encodeWithSelector(\n ccipRouter.ccipSend.selector,\n destinationChainSelector,\n ccipMessage\n ),\n 0 // Call\n );\n require(success, \"Failed to send CCIP message\");\n }\n}\n" + }, + "contracts/automation/AbstractLZBridgeHelperModule.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { AbstractSafeModule } from \"./AbstractSafeModule.sol\";\n\nimport { IOFT, SendParam } from \"@layerzerolabs/oft-evm/contracts/interfaces/IOFT.sol\";\nimport { MessagingFee } from \"@layerzerolabs/oapp-evm/contracts/oapp/OAppSender.sol\";\nimport { OptionsBuilder } from \"@layerzerolabs/oapp-evm/contracts/oapp/libs/OptionsBuilder.sol\";\n\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\n\nabstract contract AbstractLZBridgeHelperModule is AbstractSafeModule {\n using OptionsBuilder for bytes;\n\n /**\n * @dev Bridges token using LayerZero.\n * @param lzEndpointId LayerZero endpoint id.\n * @param token Token to bridge.\n * @param lzAdapter LZ Adapter to use.\n * @param amount Amount of token to bridge.\n * @param slippageBps Slippage in 10^4 basis points.\n * @param isNativeToken Whether the token is native token.\n */\n function _bridgeTokenWithLz(\n uint32 lzEndpointId,\n IERC20 token,\n IOFT lzAdapter,\n uint256 amount,\n uint256 slippageBps,\n bool isNativeToken\n ) internal {\n bool success;\n\n if (!isNativeToken) {\n // Approve LZ Adapter to move the token\n success = safeContract.execTransactionFromModule(\n address(token),\n 0, // Value\n abi.encodeWithSelector(\n token.approve.selector,\n address(lzAdapter),\n amount\n ),\n 0 // Call\n );\n require(success, \"Failed to approve token\");\n }\n\n // Calculate minimum amount to receive\n uint256 minAmount = (amount * (10000 - slippageBps)) / 10000;\n\n // Hardcoded gaslimit of 400k\n bytes memory options = OptionsBuilder\n .newOptions()\n .addExecutorLzReceiveOption(400000, 0);\n\n // Build send param\n SendParam memory sendParam = SendParam({\n dstEid: lzEndpointId,\n to: bytes32(uint256(uint160(address(safeContract)))),\n amountLD: amount,\n minAmountLD: minAmount,\n extraOptions: options,\n composeMsg: bytes(\"\"),\n oftCmd: bytes(\"\")\n });\n\n // Compute fees\n MessagingFee memory msgFee = lzAdapter.quoteSend(sendParam, false);\n\n uint256 value = isNativeToken\n ? amount + msgFee.nativeFee\n : msgFee.nativeFee;\n\n // Execute transaction\n success = safeContract.execTransactionFromModule(\n address(lzAdapter),\n value,\n abi.encodeWithSelector(\n lzAdapter.send.selector,\n sendParam,\n msgFee,\n address(safeContract)\n ),\n 0\n );\n require(success, \"Failed to bridge token\");\n }\n}\n" + }, + "contracts/automation/AbstractSafeModule.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { AccessControlEnumerable } from \"@openzeppelin/contracts/access/AccessControlEnumerable.sol\";\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport { ISafe } from \"../interfaces/ISafe.sol\";\n\nabstract contract AbstractSafeModule is AccessControlEnumerable {\n ISafe public immutable safeContract;\n\n bytes32 public constant OPERATOR_ROLE = keccak256(\"OPERATOR_ROLE\");\n\n modifier onlySafe() {\n require(\n msg.sender == address(safeContract),\n \"Caller is not the safe contract\"\n );\n _;\n }\n\n modifier onlyOperator() {\n require(\n hasRole(OPERATOR_ROLE, msg.sender),\n \"Caller is not an operator\"\n );\n _;\n }\n\n constructor(address _safeContract) {\n safeContract = ISafe(_safeContract);\n _grantRole(DEFAULT_ADMIN_ROLE, address(safeContract));\n _grantRole(OPERATOR_ROLE, address(safeContract));\n }\n\n /**\n * @dev Helps recovering any tokens accidentally sent to this module.\n * @param token Token to transfer. 0x0 to transfer Native token.\n * @param amount Amount to transfer. 0 to transfer all balance.\n */\n function transferTokens(address token, uint256 amount) external onlySafe {\n if (address(token) == address(0)) {\n // Move ETH\n amount = amount > 0 ? amount : address(this).balance;\n payable(address(safeContract)).transfer(amount);\n return;\n }\n\n // Move all balance if amount set to 0\n amount = amount > 0 ? amount : IERC20(token).balanceOf(address(this));\n\n // Transfer to Safe contract\n // slither-disable-next-line unchecked-transfer unused-return\n IERC20(token).transfer(address(safeContract), amount);\n }\n\n receive() external payable {\n // Accept ETH to pay for bridge fees\n }\n}\n" + }, + "contracts/automation/BaseBridgeHelperModule.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n// solhint-disable-next-line max-line-length\nimport { AbstractCCIPBridgeHelperModule, AbstractSafeModule, IRouterClient } from \"./AbstractCCIPBridgeHelperModule.sol\";\n\nimport { AccessControlEnumerable } from \"@openzeppelin/contracts/access/AccessControlEnumerable.sol\";\n\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport { IERC4626 } from \"../../lib/openzeppelin/interfaces/IERC4626.sol\";\nimport { IWETH9 } from \"../interfaces/IWETH9.sol\";\nimport { IVault } from \"../interfaces/IVault.sol\";\n\nimport { BridgedWOETHStrategy } from \"../strategies/BridgedWOETHStrategy.sol\";\n\ncontract BaseBridgeHelperModule is\n AccessControlEnumerable,\n AbstractCCIPBridgeHelperModule\n{\n IVault public constant vault =\n IVault(0x98a0CbeF61bD2D21435f433bE4CD42B56B38CC93);\n IWETH9 public constant weth =\n IWETH9(0x4200000000000000000000000000000000000006);\n IERC20 public constant oethb =\n IERC20(0xDBFeFD2e8460a6Ee4955A68582F85708BAEA60A3);\n IERC4626 public constant bridgedWOETH =\n IERC4626(0xD8724322f44E5c58D7A815F542036fb17DbbF839);\n\n BridgedWOETHStrategy public constant bridgedWOETHStrategy =\n BridgedWOETHStrategy(0x80c864704DD06C3693ed5179190786EE38ACf835);\n\n IRouterClient public constant CCIP_ROUTER =\n IRouterClient(0x881e3A65B4d4a04dD529061dd0071cf975F58bCD);\n\n uint64 public constant CCIP_ETHEREUM_CHAIN_SELECTOR = 5009297550715157269;\n\n constructor(address _safeContract) AbstractSafeModule(_safeContract) {}\n\n /**\n * @dev Bridges wOETH to Ethereum.\n * @param woethAmount Amount of wOETH to bridge.\n */\n function bridgeWOETHToEthereum(uint256 woethAmount)\n public\n payable\n onlyOperator\n {\n _bridgeTokenWithCCIP(\n CCIP_ROUTER,\n CCIP_ETHEREUM_CHAIN_SELECTOR,\n IERC20(address(bridgedWOETH)),\n woethAmount\n );\n }\n\n /**\n * @dev Bridges WETH to Ethereum.\n * @param wethAmount Amount of WETH to bridge.\n */\n function bridgeWETHToEthereum(uint256 wethAmount)\n public\n payable\n onlyOperator\n {\n _bridgeTokenWithCCIP(\n CCIP_ROUTER,\n CCIP_ETHEREUM_CHAIN_SELECTOR,\n IERC20(address(weth)),\n wethAmount\n );\n }\n\n /**\n * @dev Deposits wOETH into the bridgedWOETH strategy.\n * @param woethAmount Amount of wOETH to deposit.\n * @param redeemWithVault Whether to redeem the wOETH for WETH using the Vault.\n * @return Amount of WETH received.\n */\n function depositWOETH(uint256 woethAmount, bool redeemWithVault)\n external\n onlyOperator\n returns (uint256)\n {\n return _depositWOETH(woethAmount, redeemWithVault);\n }\n\n /**\n * @dev Deposits wOETH into the bridgedWOETH strategy and bridges it to Ethereum.\n * @param woethAmount Amount of wOETH to deposit.\n * @return Amount of WETH received.\n */\n function depositWOETHAndBridgeWETH(uint256 woethAmount)\n external\n onlyOperator\n returns (uint256)\n {\n uint256 wethAmount = _depositWOETH(woethAmount, true);\n bridgeWETHToEthereum(wethAmount);\n return wethAmount;\n }\n\n /**\n * @dev Deposits wOETH into the bridgedWOETH strategy.\n * @param woethAmount Amount of wOETH to deposit.\n * @param redeemWithVault Whether to redeem the wOETH for WETH using the Vault.\n * @return Amount of WETH received.\n */\n function _depositWOETH(uint256 woethAmount, bool redeemWithVault)\n internal\n returns (uint256)\n {\n // Update oracle price\n bridgedWOETHStrategy.updateWOETHOraclePrice();\n\n // Rebase to account for any yields from price update\n vault.rebase();\n\n uint256 oethbAmount = oethb.balanceOf(address(safeContract));\n\n // Approve bridgedWOETH strategy to move wOETH\n bool success = safeContract.execTransactionFromModule(\n address(bridgedWOETH),\n 0, // Value\n abi.encodeWithSelector(\n bridgedWOETH.approve.selector,\n address(bridgedWOETHStrategy),\n woethAmount\n ),\n 0 // Call\n );\n\n // Deposit to bridgedWOETH strategy\n success = safeContract.execTransactionFromModule(\n address(bridgedWOETHStrategy),\n 0, // Value\n abi.encodeWithSelector(\n bridgedWOETHStrategy.depositBridgedWOETH.selector,\n woethAmount\n ),\n 0 // Call\n );\n require(success, \"Failed to deposit bridged WOETH\");\n\n oethbAmount = oethb.balanceOf(address(safeContract)) - oethbAmount;\n\n // Rebase to account for any yields from price update\n // and backing asset change from deposit\n vault.rebase();\n\n if (!redeemWithVault) {\n return oethbAmount;\n }\n\n // Redeem for WETH using Vault\n success = safeContract.execTransactionFromModule(\n address(vault),\n 0, // Value\n abi.encodeWithSelector(\n vault.redeem.selector,\n oethbAmount,\n oethbAmount\n ),\n 0 // Call\n );\n require(success, \"Failed to redeem OETHb\");\n\n return oethbAmount;\n }\n\n /**\n * @dev Deposits WETH into the Vault and redeems wOETH from the bridgedWOETH strategy.\n * @param wethAmount Amount of WETH to deposit.\n * @return Amount of wOETH received.\n */\n function depositWETHAndRedeemWOETH(uint256 wethAmount)\n external\n onlyOperator\n returns (uint256)\n {\n return _withdrawWOETH(wethAmount);\n }\n\n function depositWETHAndBridgeWOETH(uint256 wethAmount)\n external\n onlyOperator\n returns (uint256)\n {\n uint256 woethAmount = _withdrawWOETH(wethAmount);\n bridgeWOETHToEthereum(woethAmount);\n return woethAmount;\n }\n\n /**\n * @dev Withdraws wOETH from the bridgedWOETH strategy.\n * @param wethAmount Amount of WETH to use to withdraw.\n * @return Amount of wOETH received.\n */\n function _withdrawWOETH(uint256 wethAmount) internal returns (uint256) {\n // Approve Vault to move WETH\n bool success = safeContract.execTransactionFromModule(\n address(weth),\n 0, // Value\n abi.encodeWithSelector(\n weth.approve.selector,\n address(vault),\n wethAmount\n ),\n 0 // Call\n );\n require(success, \"Failed to approve WETH\");\n\n // Mint OETHb with WETH\n success = safeContract.execTransactionFromModule(\n address(vault),\n 0, // Value\n abi.encodeWithSelector(\n vault.mint.selector,\n address(weth),\n wethAmount,\n wethAmount\n ),\n 0 // Call\n );\n require(success, \"Failed to mint OETHb\");\n\n // Approve bridgedWOETH strategy to move OETHb\n success = safeContract.execTransactionFromModule(\n address(oethb),\n 0, // Value\n abi.encodeWithSelector(\n oethb.approve.selector,\n address(bridgedWOETHStrategy),\n wethAmount\n ),\n 0 // Call\n );\n require(success, \"Failed to approve OETHb\");\n\n uint256 woethAmount = bridgedWOETH.balanceOf(address(safeContract));\n\n // Withdraw from bridgedWOETH strategy\n success = safeContract.execTransactionFromModule(\n address(bridgedWOETHStrategy),\n 0, // Value\n abi.encodeWithSelector(\n bridgedWOETHStrategy.withdrawBridgedWOETH.selector,\n wethAmount\n ),\n 0 // Call\n );\n require(success, \"Failed to withdraw bridged WOETH\");\n\n woethAmount =\n bridgedWOETH.balanceOf(address(safeContract)) -\n woethAmount;\n\n return woethAmount;\n }\n}\n" + }, + "contracts/automation/CurvePoolBoosterBribesModule.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { AbstractSafeModule } from \"./AbstractSafeModule.sol\";\n\ninterface ICurvePoolBooster {\n function manageTotalRewardAmount(\n uint256 bridgeFee,\n uint256 additionalGasLimit\n ) external;\n\n function manageNumberOfPeriods(\n uint8 extraNumberOfPeriods,\n uint256 bridgeFee,\n uint256 additionalGasLimit\n ) external;\n\n function manageRewardPerVote(\n uint256 newMaxRewardPerVote,\n uint256 bridgeFee,\n uint256 additionalGasLimit\n ) external;\n}\n\ncontract CurvePoolBoosterBribesModule is AbstractSafeModule {\n address[] public POOLS;\n\n event PoolBoosterAddressAdded(address pool);\n event PoolBoosterAddressRemoved(address pool);\n\n constructor(\n address _safeContract,\n address _operator,\n address[] memory _pools\n ) AbstractSafeModule(_safeContract) {\n _grantRole(OPERATOR_ROLE, _operator);\n _addPoolBoosterAddress(_pools);\n }\n\n function addPoolBoosterAddress(address[] memory pools)\n external\n onlyOperator\n {\n _addPoolBoosterAddress(pools);\n }\n\n function _addPoolBoosterAddress(address[] memory pools) internal {\n for (uint256 i = 0; i < pools.length; i++) {\n POOLS.push(pools[i]);\n emit PoolBoosterAddressAdded(pools[i]);\n }\n }\n\n function removePoolBoosterAddress(address[] calldata pools)\n external\n onlyOperator\n {\n for (uint256 i = 0; i < pools.length; i++) {\n _removePoolBoosterAddress(pools[i]);\n }\n }\n\n function _removePoolBoosterAddress(address pool) internal {\n uint256 length = POOLS.length;\n for (uint256 i = 0; i < length; i++) {\n if (POOLS[i] == pool) {\n POOLS[i] = POOLS[length - 1];\n POOLS.pop();\n emit PoolBoosterAddressRemoved(pool);\n break;\n }\n }\n }\n\n function manageBribes() external onlyOperator {\n uint256[] memory rewardsPerVote = new uint256[](POOLS.length);\n _manageBribes(rewardsPerVote);\n }\n\n function manageBribes(uint256[] memory rewardsPerVote)\n external\n onlyOperator\n {\n require(POOLS.length == rewardsPerVote.length, \"Length mismatch\");\n _manageBribes(rewardsPerVote);\n }\n\n function _manageBribes(uint256[] memory rewardsPerVote)\n internal\n onlyOperator\n {\n uint256 length = POOLS.length;\n for (uint256 i = 0; i < length; i++) {\n address poolBoosterAddress = POOLS[i];\n\n // PoolBooster need to have a balance of at least 0.003 ether to operate\n // 0.001 ether are used for the bridge fee\n require(\n poolBoosterAddress.balance > 0.003 ether,\n \"Insufficient balance for bribes\"\n );\n\n require(\n safeContract.execTransactionFromModule(\n poolBoosterAddress,\n 0, // Value\n abi.encodeWithSelector(\n ICurvePoolBooster.manageNumberOfPeriods.selector,\n 1, // extraNumberOfPeriods\n 0.001 ether, // bridgeFee\n 1000000 // additionalGasLimit\n ),\n 0\n ),\n \"Manage number of periods failed\"\n );\n\n require(\n safeContract.execTransactionFromModule(\n poolBoosterAddress,\n 0, // Value\n abi.encodeWithSelector(\n ICurvePoolBooster.manageTotalRewardAmount.selector,\n 0.001 ether, // bridgeFee\n 1000000 // additionalGasLimit\n ),\n 0\n ),\n \"Manage total reward failed\"\n );\n\n // Skip setting reward per vote if it's zero\n if (rewardsPerVote[i] == 0) continue;\n require(\n safeContract.execTransactionFromModule(\n poolBoosterAddress,\n 0, // Value\n abi.encodeWithSelector(\n ICurvePoolBooster.manageRewardPerVote.selector,\n rewardsPerVote[i], // newMaxRewardPerVote\n 0.001 ether, // bridgeFee\n 1000000 // additionalGasLimit\n ),\n 0\n ),\n \"Set reward per vote failed\"\n );\n }\n }\n\n function getPools() external view returns (address[] memory) {\n return POOLS;\n }\n}\n" + }, + "contracts/automation/EthereumBridgeHelperModule.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { AbstractLZBridgeHelperModule, AbstractSafeModule } from \"./AbstractLZBridgeHelperModule.sol\";\nimport { AbstractCCIPBridgeHelperModule, IRouterClient } from \"./AbstractCCIPBridgeHelperModule.sol\";\n\nimport { AccessControlEnumerable } from \"@openzeppelin/contracts/access/AccessControlEnumerable.sol\";\n\nimport { IOFT } from \"@layerzerolabs/oft-evm/contracts/interfaces/IOFT.sol\";\n\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport { IERC4626 } from \"../../lib/openzeppelin/interfaces/IERC4626.sol\";\nimport { IWETH9 } from \"../interfaces/IWETH9.sol\";\nimport { IVault } from \"../interfaces/IVault.sol\";\n\ncontract EthereumBridgeHelperModule is\n AccessControlEnumerable,\n AbstractLZBridgeHelperModule,\n AbstractCCIPBridgeHelperModule\n{\n IVault public constant vault =\n IVault(0x39254033945AA2E4809Cc2977E7087BEE48bd7Ab);\n IWETH9 public constant weth =\n IWETH9(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);\n IERC20 public constant oeth =\n IERC20(0x856c4Efb76C1D1AE02e20CEB03A2A6a08b0b8dC3);\n IERC4626 public constant woeth =\n IERC4626(0xDcEe70654261AF21C44c093C300eD3Bb97b78192);\n\n uint32 public constant LZ_PLUME_ENDPOINT_ID = 30370;\n IOFT public constant LZ_WOETH_OMNICHAIN_ADAPTER =\n IOFT(0x7d1bEa5807e6af125826d56ff477745BB89972b8);\n IOFT public constant LZ_ETH_OMNICHAIN_ADAPTER =\n IOFT(0x77b2043768d28E9C9aB44E1aBfC95944bcE57931);\n\n IRouterClient public constant CCIP_ROUTER =\n IRouterClient(0x80226fc0Ee2b096224EeAc085Bb9a8cba1146f7D);\n\n uint64 public constant CCIP_BASE_CHAIN_SELECTOR = 15971525489660198786;\n\n constructor(address _safeContract) AbstractSafeModule(_safeContract) {}\n\n /**\n * @dev Bridges wOETH to Plume.\n * @param woethAmount Amount of wOETH to bridge.\n * @param slippageBps Slippage in 10^4 basis points.\n */\n function bridgeWOETHToPlume(uint256 woethAmount, uint256 slippageBps)\n public\n payable\n onlyOperator\n {\n _bridgeTokenWithLz(\n LZ_PLUME_ENDPOINT_ID,\n woeth,\n LZ_WOETH_OMNICHAIN_ADAPTER,\n woethAmount,\n slippageBps,\n false\n );\n }\n\n /**\n * @dev Bridges wOETH to Base using CCIP.\n * @param woethAmount Amount of wOETH to bridge.\n */\n function bridgeWOETHToBase(uint256 woethAmount)\n public\n payable\n onlyOperator\n {\n _bridgeTokenWithCCIP(\n CCIP_ROUTER,\n CCIP_BASE_CHAIN_SELECTOR,\n woeth,\n woethAmount\n );\n }\n\n /**\n * @dev Bridges wETH to Plume.\n * @param wethAmount Amount of wETH to bridge.\n * @param slippageBps Slippage in 10^4 basis points.\n */\n function bridgeWETHToPlume(uint256 wethAmount, uint256 slippageBps)\n public\n payable\n onlyOperator\n {\n // Unwrap into ETH\n safeContract.execTransactionFromModule(\n address(weth),\n 0, // Value\n abi.encodeWithSelector(weth.withdraw.selector, wethAmount),\n 0 // Call\n );\n\n _bridgeTokenWithLz(\n LZ_PLUME_ENDPOINT_ID,\n IERC20(address(weth)),\n LZ_ETH_OMNICHAIN_ADAPTER,\n wethAmount,\n slippageBps,\n true\n );\n }\n\n /**\n * @dev Bridges wETH to Base using CCIP.\n * @param wethAmount Amount of wETH to bridge.\n */\n function bridgeWETHToBase(uint256 wethAmount) public payable onlyOperator {\n _bridgeTokenWithCCIP(\n CCIP_ROUTER,\n CCIP_BASE_CHAIN_SELECTOR,\n IERC20(address(weth)),\n wethAmount\n );\n }\n\n /**\n * @dev Mints OETH and wraps it into wOETH.\n * @param wethAmount Amount of WETH to mint.\n * @param useNativeToken Whether to use native token to mint.\n * @return Amount of wOETH minted.\n */\n function mintAndWrap(uint256 wethAmount, bool useNativeToken)\n external\n onlyOperator\n returns (uint256)\n {\n if (useNativeToken) {\n wrapETH(wethAmount);\n }\n\n return _mintAndWrap(wethAmount);\n }\n\n function wrapETH(uint256 ethAmount) public payable onlyOperator {\n // Deposit ETH into WETH\n safeContract.execTransactionFromModule(\n address(weth),\n ethAmount, // Value\n abi.encodeWithSelector(weth.deposit.selector),\n 0 // Call\n );\n }\n\n /**\n * @dev Mints OETH and wraps it into wOETH.\n * @param wethAmount Amount of WETH to mint.\n * @return Amount of wOETH minted.\n */\n function _mintAndWrap(uint256 wethAmount) internal returns (uint256) {\n // Approve Vault to move WETH\n bool success = safeContract.execTransactionFromModule(\n address(weth),\n 0, // Value\n abi.encodeWithSelector(\n weth.approve.selector,\n address(vault),\n wethAmount\n ),\n 0 // Call\n );\n require(success, \"Failed to approve WETH\");\n\n // Mint OETH\n success = safeContract.execTransactionFromModule(\n address(vault),\n 0, // Value\n abi.encodeWithSelector(\n vault.mint.selector,\n address(weth),\n wethAmount,\n wethAmount\n ),\n 0 // Call\n );\n require(success, \"Failed to mint OETH\");\n\n // Approve wOETH to move OETH\n success = safeContract.execTransactionFromModule(\n address(oeth),\n 0, // Value\n abi.encodeWithSelector(\n oeth.approve.selector,\n address(woeth),\n wethAmount\n ),\n 0 // Call\n );\n require(success, \"Failed to approve OETH\");\n\n uint256 woethAmount = woeth.balanceOf(address(safeContract));\n\n // Wrap OETH into wOETH\n success = safeContract.execTransactionFromModule(\n address(woeth),\n 0, // Value\n abi.encodeWithSelector(\n woeth.deposit.selector,\n wethAmount,\n address(safeContract)\n ),\n 0 // Call\n );\n require(success, \"Failed to wrap OETH\");\n\n // Compute amount of wOETH minted\n return woeth.balanceOf(address(safeContract)) - woethAmount;\n }\n\n /**\n * @dev Mints OETH and wraps it into wOETH, then bridges it to Plume.\n * @param wethAmount Amount of WETH to mint.\n * @param slippageBps Bridge slippage in 10^4 basis points.\n * @param useNativeToken Whether to use native token to mint.\n */\n function mintWrapAndBridgeToPlume(\n uint256 wethAmount,\n uint256 slippageBps,\n bool useNativeToken\n ) external payable onlyOperator {\n if (useNativeToken) {\n wrapETH(wethAmount);\n }\n\n uint256 woethAmount = _mintAndWrap(wethAmount);\n bridgeWOETHToPlume(woethAmount, slippageBps);\n }\n\n /**\n * @dev Mints OETH and wraps it into wOETH, then bridges it to Base using CCIP.\n * @param wethAmount Amount of WETH to mint.\n * @param useNativeToken Whether to use native token to mint.\n */\n function mintWrapAndBridgeToBase(uint256 wethAmount, bool useNativeToken)\n external\n payable\n onlyOperator\n {\n if (useNativeToken) {\n wrapETH(wethAmount);\n }\n\n uint256 woethAmount = _mintAndWrap(wethAmount);\n bridgeWOETHToBase(woethAmount);\n }\n\n /**\n * @dev Unwraps wOETH and redeems it to get WETH.\n * @param woethAmount Amount of wOETH to unwrap.\n * @return Amount of WETH received.\n */\n function unwrapAndRedeem(uint256 woethAmount)\n external\n onlyOperator\n returns (uint256)\n {\n return _unwrapAndRedeem(woethAmount);\n }\n\n /**\n * @dev Unwraps wOETH and redeems it to get WETH.\n * @param woethAmount Amount of wOETH to unwrap.\n * @return Amount of WETH received.\n */\n function _unwrapAndRedeem(uint256 woethAmount) internal returns (uint256) {\n uint256 oethAmount = oeth.balanceOf(address(safeContract));\n\n // Unwrap wOETH\n bool success = safeContract.execTransactionFromModule(\n address(woeth),\n 0, // Value\n abi.encodeWithSelector(\n woeth.redeem.selector,\n woethAmount,\n address(safeContract),\n address(safeContract)\n ),\n 0 // Call\n );\n require(success, \"Failed to unwrap wOETH\");\n\n oethAmount = oeth.balanceOf(address(safeContract)) - oethAmount;\n\n // Redeem OETH using Vault to get WETH\n success = safeContract.execTransactionFromModule(\n address(vault),\n 0, // Value\n abi.encodeWithSelector(\n vault.redeem.selector,\n oethAmount,\n oethAmount\n ),\n 0 // Call\n );\n require(success, \"Failed to redeem OETH\");\n\n return oethAmount;\n }\n\n /**\n * @dev Unwraps wOETH and redeems it to get WETH, then bridges it to Plume.\n * @param woethAmount Amount of wOETH to unwrap.\n * @param slippageBps Bridge slippage in 10^4 basis points.\n */\n function unwrapRedeemAndBridgeToPlume(\n uint256 woethAmount,\n uint256 slippageBps\n ) external payable onlyOperator {\n uint256 wethAmount = _unwrapAndRedeem(woethAmount);\n // Unwrap into ETH\n safeContract.execTransactionFromModule(\n address(weth),\n 0, // Value\n abi.encodeWithSelector(weth.withdraw.selector, wethAmount),\n 0 // Call\n );\n\n bridgeWETHToPlume(wethAmount, slippageBps);\n }\n\n /**\n * @dev Unwraps wOETH and redeems it to get WETH, then bridges it to Base using CCIP.\n * @param woethAmount Amount of wOETH to unwrap.\n */\n function unwrapRedeemAndBridgeToBase(uint256 woethAmount)\n external\n payable\n onlyOperator\n {\n uint256 wethAmount = _unwrapAndRedeem(woethAmount);\n bridgeWETHToBase(wethAmount);\n }\n}\n" + }, + "contracts/automation/PlumeBridgeHelperModule.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { AbstractSafeModule } from \"./AbstractSafeModule.sol\";\nimport { AbstractLZBridgeHelperModule } from \"./AbstractLZBridgeHelperModule.sol\";\n\nimport { AccessControlEnumerable } from \"@openzeppelin/contracts/access/AccessControlEnumerable.sol\";\n\nimport { IOFT } from \"@layerzerolabs/oft-evm/contracts/interfaces/IOFT.sol\";\n\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport { IERC4626 } from \"../../lib/openzeppelin/interfaces/IERC4626.sol\";\nimport { IWETH9 } from \"../interfaces/IWETH9.sol\";\nimport { IVault } from \"../interfaces/IVault.sol\";\n\nimport { BridgedWOETHStrategy } from \"../strategies/BridgedWOETHStrategy.sol\";\n\ncontract PlumeBridgeHelperModule is\n AccessControlEnumerable,\n AbstractLZBridgeHelperModule\n{\n IVault public constant vault =\n IVault(0xc8c8F8bEA5631A8AF26440AF32a55002138cB76a);\n IWETH9 public constant weth =\n IWETH9(0xca59cA09E5602fAe8B629DeE83FfA819741f14be);\n IERC20 public constant oethp =\n IERC20(0xFCbe50DbE43bF7E5C88C6F6Fb9ef432D4165406E);\n IERC4626 public constant bridgedWOETH =\n IERC4626(0xD8724322f44E5c58D7A815F542036fb17DbbF839);\n\n uint32 public constant LZ_ETHEREUM_ENDPOINT_ID = 30101;\n IOFT public constant LZ_WOETH_OMNICHAIN_ADAPTER =\n IOFT(0x592CB6A596E7919930bF49a27AdAeCA7C055e4DB);\n IOFT public constant LZ_ETH_OMNICHAIN_ADAPTER =\n IOFT(0x4683CE822272CD66CEa73F5F1f9f5cBcaEF4F066);\n\n BridgedWOETHStrategy public constant bridgedWOETHStrategy =\n BridgedWOETHStrategy(0x1E3EdD5e019207D6355Ea77F724b1F1BF639B569);\n\n constructor(address _safeContract) AbstractSafeModule(_safeContract) {}\n\n /**\n * @dev Bridges wOETH to Ethereum.\n * @param woethAmount Amount of wOETH to bridge.\n * @param slippageBps Slippage in 10^4 basis points.\n */\n function bridgeWOETHToEthereum(uint256 woethAmount, uint256 slippageBps)\n public\n payable\n onlyOperator\n {\n _bridgeTokenWithLz(\n LZ_ETHEREUM_ENDPOINT_ID,\n IERC20(address(bridgedWOETH)),\n LZ_WOETH_OMNICHAIN_ADAPTER,\n woethAmount,\n slippageBps,\n false\n );\n }\n\n /**\n * @dev Bridges wETH to Ethereum.\n * @param wethAmount Amount of wETH to bridge.\n * @param slippageBps Slippage in 10^4 basis points.\n */\n function bridgeWETHToEthereum(uint256 wethAmount, uint256 slippageBps)\n public\n payable\n onlyOperator\n {\n _bridgeTokenWithLz(\n LZ_ETHEREUM_ENDPOINT_ID,\n IERC20(address(weth)),\n LZ_ETH_OMNICHAIN_ADAPTER,\n wethAmount,\n slippageBps,\n false\n );\n }\n\n /**\n * @dev Deposits wOETH into the bridgedWOETH strategy.\n * @param woethAmount Amount of wOETH to deposit.\n * @param redeemWithVault Whether to redeem with Vault.\n * @return Amount of OETHp received.\n */\n function depositWOETH(uint256 woethAmount, bool redeemWithVault)\n external\n onlyOperator\n returns (uint256)\n {\n return _depositWOETH(woethAmount, redeemWithVault);\n }\n\n /**\n * @dev Deposits wOETH into the bridgedWOETH strategy and bridges it to Ethereum.\n * @param woethAmount Amount of wOETH to deposit.\n * @param slippageBps Slippage in 10^4 basis points.\n * @return Amount of WETH received.\n */\n function depositWOETHAndBridgeWETH(uint256 woethAmount, uint256 slippageBps)\n external\n payable\n onlyOperator\n returns (uint256)\n {\n uint256 wethAmount = _depositWOETH(woethAmount, true);\n bridgeWETHToEthereum(wethAmount, slippageBps);\n return wethAmount;\n }\n\n /**\n * @dev Deposits wOETH into the bridgedWOETH strategy.\n * @param woethAmount Amount of wOETH to deposit.\n * @param redeemWithVault Whether to redeem with Vault.\n * @return Amount of OETHp received.\n */\n function _depositWOETH(uint256 woethAmount, bool redeemWithVault)\n internal\n returns (uint256)\n {\n // Update oracle price\n bridgedWOETHStrategy.updateWOETHOraclePrice();\n\n // Rebase to account for any yields from price update\n vault.rebase();\n\n uint256 oethpAmount = oethp.balanceOf(address(safeContract));\n\n // Approve bridgedWOETH strategy to move wOETH\n bool success = safeContract.execTransactionFromModule(\n address(bridgedWOETH),\n 0, // Value\n abi.encodeWithSelector(\n bridgedWOETH.approve.selector,\n address(bridgedWOETHStrategy),\n woethAmount\n ),\n 0 // Call\n );\n\n // Deposit to bridgedWOETH strategy\n success = safeContract.execTransactionFromModule(\n address(bridgedWOETHStrategy),\n 0, // Value\n abi.encodeWithSelector(\n bridgedWOETHStrategy.depositBridgedWOETH.selector,\n woethAmount\n ),\n 0 // Call\n );\n require(success, \"Failed to deposit bridged WOETH\");\n\n oethpAmount = oethp.balanceOf(address(safeContract)) - oethpAmount;\n\n // Rebase to account for any yields from price update\n // and backing asset change from deposit\n vault.rebase();\n\n if (!redeemWithVault) {\n return oethpAmount;\n }\n\n // Redeem for WETH using Vault\n success = safeContract.execTransactionFromModule(\n address(vault),\n 0, // Value\n abi.encodeWithSelector(\n vault.redeem.selector,\n oethpAmount,\n oethpAmount\n ),\n 0 // Call\n );\n require(success, \"Failed to redeem OETHp\");\n\n return oethpAmount;\n }\n\n /**\n * @dev Deposits wETH into the vault.\n * @param wethAmount Amount of wETH to deposit.\n * @return Amount of OETHp received.\n */\n function depositWETHAndRedeemWOETH(uint256 wethAmount)\n external\n onlyOperator\n returns (uint256)\n {\n return _withdrawWOETH(wethAmount);\n }\n\n /**\n * @dev Deposits wETH into the vault and bridges it to Ethereum.\n * @param wethAmount Amount of wETH to deposit.\n * @param slippageBps Slippage in 10^4 basis points.\n * @return Amount of WOETH received.\n */\n function depositWETHAndBridgeWOETH(uint256 wethAmount, uint256 slippageBps)\n external\n payable\n onlyOperator\n returns (uint256)\n {\n uint256 woethAmount = _withdrawWOETH(wethAmount);\n bridgeWOETHToEthereum(woethAmount, slippageBps);\n return woethAmount;\n }\n\n /**\n * @dev Withdraws wOETH from the bridgedWOETH strategy.\n * @param wethAmount Amount of WETH to use to withdraw.\n * @return Amount of wOETH received.\n */\n function _withdrawWOETH(uint256 wethAmount) internal returns (uint256) {\n // Approve Vault to move WETH\n bool success = safeContract.execTransactionFromModule(\n address(weth),\n 0, // Value\n abi.encodeWithSelector(\n weth.approve.selector,\n address(vault),\n wethAmount\n ),\n 0 // Call\n );\n require(success, \"Failed to approve WETH\");\n\n // Mint OETHp with WETH\n success = safeContract.execTransactionFromModule(\n address(vault),\n 0, // Value\n abi.encodeWithSelector(\n vault.mint.selector,\n address(weth),\n wethAmount,\n wethAmount\n ),\n 0 // Call\n );\n require(success, \"Failed to mint OETHp\");\n\n // Approve bridgedWOETH strategy to move OETHp\n success = safeContract.execTransactionFromModule(\n address(oethp),\n 0, // Value\n abi.encodeWithSelector(\n oethp.approve.selector,\n address(bridgedWOETHStrategy),\n wethAmount\n ),\n 0 // Call\n );\n require(success, \"Failed to approve OETHp\");\n\n uint256 woethAmount = bridgedWOETH.balanceOf(address(safeContract));\n\n // Withdraw from bridgedWOETH strategy\n success = safeContract.execTransactionFromModule(\n address(bridgedWOETHStrategy),\n 0, // Value\n abi.encodeWithSelector(\n bridgedWOETHStrategy.withdrawBridgedWOETH.selector,\n wethAmount\n ),\n 0 // Call\n );\n require(success, \"Failed to withdraw bridged WOETH\");\n\n woethAmount =\n bridgedWOETH.balanceOf(address(safeContract)) -\n woethAmount;\n\n return woethAmount;\n }\n}\n" + }, + "contracts/beacon/BeaconRoots.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title Library to retrieve beacon block roots.\n * @author Origin Protocol Inc\n */\nlibrary BeaconRoots {\n /// @notice The address of beacon block roots oracle\n /// See https://eips.ethereum.org/EIPS/eip-4788\n address internal constant BEACON_ROOTS_ADDRESS =\n 0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02;\n\n /// @notice Returns the beacon block root for the previous block.\n /// This comes from the Beacon Roots contract defined in EIP-4788.\n /// This will revert if the block is more than 8,191 blocks old as\n /// that is the size of the beacon root's ring buffer.\n /// @param timestamp The timestamp of the block for which to get the parent root.\n /// @return parentRoot The parent block root for the given timestamp.\n function parentBlockRoot(uint64 timestamp)\n internal\n view\n returns (bytes32 parentRoot)\n {\n // Call the Beacon Roots contract to get the parent block root.\n // This does not have a function signature, so we use a staticcall.\n (bool success, bytes memory result) = BEACON_ROOTS_ADDRESS.staticcall(\n abi.encode(timestamp)\n );\n\n require(success && result.length > 0, \"Invalid beacon timestamp\");\n parentRoot = abi.decode(result, (bytes32));\n }\n}\n" + }, + "contracts/beacon/PartialWithdrawal.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title Library to request full or partial withdrawals from validators on the beacon chain.\n * @author Origin Protocol Inc\n */\nlibrary PartialWithdrawal {\n /// @notice The address where the withdrawal request is sent to\n /// See https://eips.ethereum.org/EIPS/eip-7002\n address internal constant WITHDRAWAL_REQUEST_ADDRESS =\n 0x00000961Ef480Eb55e80D19ad83579A64c007002;\n\n /// @notice Requests a partial withdrawal for a given validator public key and amount.\n /// @param validatorPubKey The public key of the validator to withdraw from\n /// @param amount The amount of ETH to withdraw\n function request(bytes calldata validatorPubKey, uint64 amount)\n internal\n returns (uint256 fee_)\n {\n require(validatorPubKey.length == 48, \"Invalid validator byte length\");\n fee_ = fee();\n\n // Call the Withdrawal Request contract with the validator public key\n // and amount to be withdrawn packed together\n\n // This is a general purpose EL to CL request:\n // https://eips.ethereum.org/EIPS/eip-7685\n (bool success, ) = WITHDRAWAL_REQUEST_ADDRESS.call{ value: fee_ }(\n abi.encodePacked(validatorPubKey, amount)\n );\n\n require(success, \"Withdrawal request failed\");\n }\n\n /// @notice Gets fee for withdrawal requests contract on Beacon chain\n function fee() internal view returns (uint256) {\n // Get fee from the withdrawal request contract\n (bool success, bytes memory result) = WITHDRAWAL_REQUEST_ADDRESS\n .staticcall(\"\");\n\n require(success && result.length > 0, \"Failed to get fee\");\n return abi.decode(result, (uint256));\n }\n}\n" + }, + "contracts/buyback/AbstractBuyback.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { Strategizable } from \"../governance/Strategizable.sol\";\nimport \"../interfaces/chainlink/AggregatorV3Interface.sol\";\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport { ICVXLocker } from \"../interfaces/ICVXLocker.sol\";\nimport { ISwapper } from \"../interfaces/ISwapper.sol\";\n\nimport { Initializable } from \"../utils/Initializable.sol\";\n\nabstract contract AbstractBuyback is Initializable, Strategizable {\n using SafeERC20 for IERC20;\n\n event SwapRouterUpdated(address indexed _address);\n\n event RewardsSourceUpdated(address indexed _address);\n event TreasuryManagerUpdated(address indexed _address);\n event CVXShareBpsUpdated(uint256 bps);\n\n // Emitted whenever OUSD/OETH is swapped for OGN/CVX or any other token\n event OTokenBuyback(\n address indexed oToken,\n address indexed swappedFor,\n uint256 swapAmountIn,\n uint256 amountOut\n );\n\n // Address of 1-inch Swap Router\n address public swapRouter;\n\n // slither-disable-next-line constable-states\n address private __deprecated_ousd;\n // slither-disable-next-line constable-states\n address private __deprecated_ogv;\n // slither-disable-next-line constable-states\n address private __deprecated_usdt;\n // slither-disable-next-line constable-states\n address private __deprecated_weth9;\n\n // Address that receives OGN after swaps\n address public rewardsSource;\n\n // Address that receives all other tokens after swaps\n address public treasuryManager;\n\n // slither-disable-next-line constable-states\n uint256 private __deprecated_treasuryBps;\n\n address public immutable oToken;\n address public immutable ogn;\n address public immutable cvx;\n address public immutable cvxLocker;\n\n // Amount of `oToken` balance to use for OGN buyback\n uint256 public balanceForOGN;\n\n // Amount of `oToken` balance to use for CVX buyback\n uint256 public balanceForCVX;\n\n // Percentage of `oToken` balance to be used for CVX\n uint256 public cvxShareBps; // 10000 = 100%\n\n constructor(\n address _oToken,\n address _ogn,\n address _cvx,\n address _cvxLocker\n ) {\n // Make sure nobody owns the implementation contract\n _setGovernor(address(0));\n\n oToken = _oToken;\n ogn = _ogn;\n cvx = _cvx;\n cvxLocker = _cvxLocker;\n }\n\n /**\n * @param _swapRouter Address of Uniswap V3 Router\n * @param _strategistAddr Address of Strategist multi-sig wallet\n * @param _treasuryManagerAddr Address that receives the treasury's share of OUSD\n * @param _rewardsSource Address of RewardsSource contract\n * @param _cvxShareBps Percentage of balance to use for CVX\n */\n function initialize(\n address _swapRouter,\n address _strategistAddr,\n address _treasuryManagerAddr,\n address _rewardsSource,\n uint256 _cvxShareBps\n ) external onlyGovernor initializer {\n _setStrategistAddr(_strategistAddr);\n\n _setSwapRouter(_swapRouter);\n _setRewardsSource(_rewardsSource);\n\n _setTreasuryManager(_treasuryManagerAddr);\n\n _setCVXShareBps(_cvxShareBps);\n }\n\n /**\n * @dev Set address of Uniswap Universal Router for performing liquidation\n * of platform fee tokens. Setting to 0x0 will pause swaps.\n *\n * @param _router Address of the Uniswap Universal router\n */\n function setSwapRouter(address _router) external onlyGovernor {\n _setSwapRouter(_router);\n }\n\n function _setSwapRouter(address _router) internal {\n address oldRouter = swapRouter;\n swapRouter = _router;\n\n if (oldRouter != address(0)) {\n // Remove allowance of old router, if any\n\n if (IERC20(ogn).allowance(address(this), oldRouter) != 0) {\n // slither-disable-next-line unused-return\n IERC20(ogn).safeApprove(oldRouter, 0);\n }\n\n if (IERC20(cvx).allowance(address(this), oldRouter) != 0) {\n // slither-disable-next-line unused-return\n IERC20(cvx).safeApprove(oldRouter, 0);\n }\n }\n\n emit SwapRouterUpdated(_router);\n }\n\n /**\n * @dev Sets the address that receives the OGN buyback rewards\n * @param _address Address\n */\n function setRewardsSource(address _address) external onlyGovernor {\n _setRewardsSource(_address);\n }\n\n function _setRewardsSource(address _address) internal {\n require(_address != address(0), \"Address not set\");\n rewardsSource = _address;\n emit RewardsSourceUpdated(_address);\n }\n\n /**\n * @dev Sets the address that can receive and manage the funds for Treasury\n * @param _address Address\n */\n function setTreasuryManager(address _address) external onlyGovernor {\n _setTreasuryManager(_address);\n }\n\n function _setTreasuryManager(address _address) internal {\n require(_address != address(0), \"Address not set\");\n treasuryManager = _address;\n emit TreasuryManagerUpdated(_address);\n }\n\n /**\n * @dev Sets the percentage of oToken to use for Flywheel tokens\n * @param _bps BPS, 10000 to 100%\n */\n function setCVXShareBps(uint256 _bps) external onlyGovernor {\n _setCVXShareBps(_bps);\n }\n\n function _setCVXShareBps(uint256 _bps) internal {\n require(_bps <= 10000, \"Invalid bps value\");\n cvxShareBps = _bps;\n emit CVXShareBpsUpdated(_bps);\n }\n\n /**\n * @dev Computes the split of oToken balance that can be\n * used for OGN and CVX buybacks.\n */\n function _updateBuybackSplits()\n internal\n returns (uint256 _balanceForOGN, uint256 _balanceForCVX)\n {\n _balanceForOGN = balanceForOGN;\n _balanceForCVX = balanceForCVX;\n\n uint256 totalBalance = IERC20(oToken).balanceOf(address(this));\n uint256 unsplitBalance = totalBalance - _balanceForOGN - _balanceForCVX;\n\n // Check if all balance is accounted for\n if (unsplitBalance != 0) {\n // If not, split unaccounted balance based on `cvxShareBps`\n uint256 addToCVX = (unsplitBalance * cvxShareBps) / 10000;\n _balanceForCVX = _balanceForCVX + addToCVX;\n _balanceForOGN = _balanceForOGN + unsplitBalance - addToCVX;\n\n // Update storage\n balanceForOGN = _balanceForOGN;\n balanceForCVX = _balanceForCVX;\n }\n }\n\n function updateBuybackSplits() external onlyGovernor {\n // slither-disable-next-line unused-return\n _updateBuybackSplits();\n }\n\n function _swapToken(\n address tokenOut,\n uint256 oTokenAmount,\n uint256 minAmountOut,\n bytes calldata swapData\n ) internal returns (uint256 amountOut) {\n require(oTokenAmount > 0, \"Invalid Swap Amount\");\n require(swapRouter != address(0), \"Swap Router not set\");\n require(minAmountOut > 0, \"Invalid minAmount\");\n\n // Transfer OToken to Swapper for swapping\n // slither-disable-next-line unchecked-transfer unused-return\n IERC20(oToken).transfer(swapRouter, oTokenAmount);\n\n // Swap\n amountOut = ISwapper(swapRouter).swap(\n oToken,\n tokenOut,\n oTokenAmount,\n minAmountOut,\n swapData\n );\n\n require(amountOut >= minAmountOut, \"Higher Slippage\");\n\n emit OTokenBuyback(oToken, tokenOut, oTokenAmount, amountOut);\n }\n\n /**\n * @dev Swaps `oTokenAmount` to OGN\n * @param oTokenAmount Amount of OUSD/OETH to swap\n * @param minOGN Minimum OGN to receive for oTokenAmount\n * @param swapData 1inch Swap Data\n */\n function swapForOGN(\n uint256 oTokenAmount,\n uint256 minOGN,\n bytes calldata swapData\n ) external onlyGovernorOrStrategist nonReentrant {\n (uint256 _amountForOGN, ) = _updateBuybackSplits();\n require(_amountForOGN >= oTokenAmount, \"Balance underflow\");\n require(rewardsSource != address(0), \"RewardsSource contract not set\");\n\n unchecked {\n // Subtract the amount to swap from net balance\n balanceForOGN = _amountForOGN - oTokenAmount;\n }\n\n uint256 ognReceived = _swapToken(ogn, oTokenAmount, minOGN, swapData);\n\n // Transfer OGN received to RewardsSource contract\n // slither-disable-next-line unchecked-transfer unused-return\n IERC20(ogn).transfer(rewardsSource, ognReceived);\n }\n\n /**\n * @dev Swaps `oTokenAmount` to CVX\n * @param oTokenAmount Amount of OUSD/OETH to swap\n * @param minCVX Minimum CVX to receive for oTokenAmount\n * @param swapData 1inch Swap Data\n */\n function swapForCVX(\n uint256 oTokenAmount,\n uint256 minCVX,\n bytes calldata swapData\n ) external onlyGovernorOrStrategist nonReentrant {\n (, uint256 _amountForCVX) = _updateBuybackSplits();\n require(_amountForCVX >= oTokenAmount, \"Balance underflow\");\n\n unchecked {\n // Subtract the amount to swap from net balance\n balanceForCVX = _amountForCVX - oTokenAmount;\n }\n\n uint256 cvxReceived = _swapToken(cvx, oTokenAmount, minCVX, swapData);\n\n // Lock all CVX\n _lockAllCVX(cvxReceived);\n }\n\n /**\n * @dev Locks all CVX held by the contract on behalf of the Treasury Manager\n */\n function lockAllCVX() external onlyGovernorOrStrategist {\n _lockAllCVX(IERC20(cvx).balanceOf(address(this)));\n }\n\n function _lockAllCVX(uint256 cvxAmount) internal {\n require(\n treasuryManager != address(0),\n \"Treasury manager address not set\"\n );\n\n // Lock all available CVX on behalf of `treasuryManager`\n ICVXLocker(cvxLocker).lock(treasuryManager, cvxAmount, 0);\n }\n\n /**\n * @dev Approve CVX Locker to move CVX held by this contract\n */\n function safeApproveAllTokens() external onlyGovernorOrStrategist {\n IERC20(cvx).safeApprove(cvxLocker, type(uint256).max);\n }\n\n /**\n * @notice Owner function to withdraw a specific amount of a token\n * @param token token to be transferered\n * @param amount amount of the token to be transferred\n */\n function transferToken(address token, uint256 amount)\n external\n onlyGovernor\n nonReentrant\n {\n IERC20(token).safeTransfer(_governor(), amount);\n }\n}\n" + }, + "contracts/buyback/ARMBuyback.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { AbstractBuyback } from \"./AbstractBuyback.sol\";\n\ncontract ARMBuyback is AbstractBuyback {\n constructor(\n address _oToken,\n address _ogn,\n address _cvx,\n address _cvxLocker\n ) AbstractBuyback(_oToken, _ogn, _cvx, _cvxLocker) {}\n}\n" + }, + "contracts/buyback/OETHBuyback.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { AbstractBuyback } from \"./AbstractBuyback.sol\";\n\ncontract OETHBuyback is AbstractBuyback {\n constructor(\n address _oToken,\n address _ogn,\n address _cvx,\n address _cvxLocker\n ) AbstractBuyback(_oToken, _ogn, _cvx, _cvxLocker) {}\n}\n" + }, + "contracts/buyback/OUSDBuyback.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { AbstractBuyback } from \"./AbstractBuyback.sol\";\n\ncontract OUSDBuyback is AbstractBuyback {\n constructor(\n address _oToken,\n address _ogn,\n address _cvx,\n address _cvxLocker\n ) AbstractBuyback(_oToken, _ogn, _cvx, _cvxLocker) {}\n}\n" + }, + "contracts/echidna/Debugger.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nlibrary Debugger {\n event Debug(string debugString);\n event Debug(string description, string data);\n event Debug(string prefix, string description, string data);\n event Debug(string description, bytes32 data);\n event Debug(string prefix, string description, bytes32 data);\n event Debug(string description, uint256 data);\n event Debug(string prefix, string description, uint256 data);\n event Debug(string description, int256 data);\n event Debug(string prefix, string description, int256 data);\n event Debug(string description, address data);\n event Debug(string prefix, string description, address data);\n event Debug(string description, bool data);\n event Debug(string prefix, string description, bool data);\n\n function log(string memory debugString) internal {\n emit Debug(debugString);\n }\n\n function log(string memory description, string memory data) internal {\n emit Debug(description, data);\n }\n\n function log(\n string memory prefix,\n string memory description,\n string memory data\n ) internal {\n emit Debug(prefix, description, data);\n }\n\n function log(string memory description, bytes32 data) internal {\n emit Debug(description, data);\n }\n\n function log(\n string memory prefix,\n string memory description,\n bytes32 data\n ) internal {\n emit Debug(prefix, description, data);\n }\n\n function log(string memory description, uint256 data) internal {\n emit Debug(description, data);\n }\n\n function log(\n string memory prefix,\n string memory description,\n uint256 data\n ) internal {\n emit Debug(prefix, description, data);\n }\n\n function log(string memory description, int256 data) internal {\n emit Debug(description, data);\n }\n\n function log(\n string memory prefix,\n string memory description,\n int256 data\n ) internal {\n emit Debug(prefix, description, data);\n }\n\n function log(string memory description, address data) internal {\n emit Debug(description, data);\n }\n\n function log(\n string memory prefix,\n string memory description,\n address data\n ) internal {\n emit Debug(prefix, description, data);\n }\n\n function log(string memory description, bool data) internal {\n emit Debug(description, data);\n }\n\n function log(\n string memory prefix,\n string memory description,\n bool data\n ) internal {\n emit Debug(prefix, description, data);\n }\n}\n" + }, + "contracts/echidna/Echidna.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport \"./EchidnaTestApproval.sol\";\n\n/**\n * @title Echidna test contract for OUSD\n * @notice Target contract to be tested, containing all mixins\n * @author Rappie\n */\ncontract Echidna is EchidnaTestApproval {\n\n}\n" + }, + "contracts/echidna/EchidnaConfig.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title Top-level mixin for configuring the desired fuzzing setup\n * @author Rappie\n */\ncontract EchidnaConfig {\n address internal constant ADDRESS_VAULT = address(0x10000);\n address internal constant ADDRESS_OUTSIDER_USER = address(0x20000);\n\n address internal constant ADDRESS_USER0 = address(0x30000);\n address internal constant ADDRESS_USER1 = address(0x40000);\n\n // Will be set in EchidnaSetup constructor\n address internal ADDRESS_OUTSIDER_CONTRACT;\n address internal ADDRESS_CONTRACT0;\n address internal ADDRESS_CONTRACT1;\n\n // Toggle known issues\n //\n // This can be used to skip tests that are known to fail. This is useful\n // when debugging a specific issue, but should be disabled when running\n // the full test suite.\n //\n // True => skip tests that are known to fail\n // False => run all tests\n //\n bool internal constant TOGGLE_KNOWN_ISSUES = false;\n\n // Toggle known issues within limits\n //\n // Same as TOGGLE_KNOWN_ISSUES, but also skip tests that are known to fail\n // within limits set by the variables below.\n //\n bool internal constant TOGGLE_KNOWN_ISSUES_WITHIN_LIMITS = true;\n\n // Starting balance\n //\n // Gives OUSD a non-zero starting supply, which can be useful to ignore\n // certain edge cases.\n //\n // The starting balance is given to outsider accounts that are not used as\n // accounts while fuzzing.\n //\n bool internal constant TOGGLE_STARTING_BALANCE = true;\n uint256 internal constant STARTING_BALANCE = 1_000_000e18;\n\n // Change supply\n //\n // Set a limit to the amount of change per rebase, which can be useful to\n // ignore certain edge cases.\n //\n // True => limit the amount of change to a percentage of total supply\n // False => no limit\n //\n bool internal constant TOGGLE_CHANGESUPPLY_LIMIT = true;\n uint256 internal constant CHANGESUPPLY_DIVISOR = 10; // 10% of total supply\n\n // Mint limit\n //\n // Set a limit the amount minted per mint, which can be useful to\n // ignore certain edge cases.\n //\n // True => limit the amount of minted tokens\n // False => no limit\n //\n bool internal constant TOGGLE_MINT_LIMIT = true;\n uint256 internal constant MINT_MODULO = 1_000_000_000_000e18;\n\n // Known rounding errors\n uint256 internal constant TRANSFER_ROUNDING_ERROR = 1e18 - 1;\n uint256 internal constant OPT_IN_ROUNDING_ERROR = 1e18 - 1;\n uint256 internal constant MINT_ROUNDING_ERROR = 1e18 - 1;\n\n /**\n * @notice Modifier to skip tests that are known to fail\n * @dev see TOGGLE_KNOWN_ISSUES for more information\n */\n modifier hasKnownIssue() {\n if (TOGGLE_KNOWN_ISSUES) return;\n _;\n }\n\n /**\n * @notice Modifier to skip tests that are known to fail within limits\n * @dev see TOGGLE_KNOWN_ISSUES_WITHIN_LIMITS for more information\n */\n modifier hasKnownIssueWithinLimits() {\n if (TOGGLE_KNOWN_ISSUES_WITHIN_LIMITS) return;\n _;\n }\n\n /**\n * @notice Translate an account ID to an address\n * @param accountId The ID of the account\n * @return account The address of the account\n */\n function getAccount(uint8 accountId)\n internal\n view\n returns (address account)\n {\n accountId = accountId / 64;\n if (accountId == 0) return account = ADDRESS_USER0;\n if (accountId == 1) return account = ADDRESS_USER1;\n if (accountId == 2) return account = ADDRESS_CONTRACT0;\n if (accountId == 3) return account = ADDRESS_CONTRACT1;\n require(false, \"Unknown account ID\");\n }\n}\n" + }, + "contracts/echidna/EchidnaDebug.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { Address } from \"@openzeppelin/contracts/utils/Address.sol\";\n\nimport \"./EchidnaHelper.sol\";\nimport \"./Debugger.sol\";\n\nimport \"../token/OUSD.sol\";\n\n/**\n * @title Room for random debugging functions\n * @author Rappie\n */\ncontract EchidnaDebug is EchidnaHelper {\n function debugOUSD() public pure {\n // assert(ousd.balanceOf(ADDRESS_USER0) == 1000);\n // assert(ousd.rebaseState(ADDRESS_USER0) != OUSD.RebaseOptions.OptIn);\n // assert(Address.isContract(ADDRESS_CONTRACT0));\n // Debugger.log(\"nonRebasingSupply\", ousd.nonRebasingSupply());\n // assert(false);\n }\n}\n" + }, + "contracts/echidna/EchidnaHelper.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport \"./EchidnaSetup.sol\";\nimport \"./Debugger.sol\";\n\n/**\n * @title Mixin containing helper functions\n * @author Rappie\n */\ncontract EchidnaHelper is EchidnaSetup {\n /**\n * @notice Mint tokens to an account\n * @param toAcc Account to mint to\n * @param amount Amount to mint\n * @return Amount minted (in case of capped mint with modulo)\n */\n function mint(uint8 toAcc, uint256 amount) public returns (uint256) {\n address to = getAccount(toAcc);\n\n if (TOGGLE_MINT_LIMIT) {\n amount = amount % MINT_MODULO;\n }\n\n hevm.prank(ADDRESS_VAULT);\n ousd.mint(to, amount);\n\n return amount;\n }\n\n /**\n * @notice Burn tokens from an account\n * @param fromAcc Account to burn from\n * @param amount Amount to burn\n */\n function burn(uint8 fromAcc, uint256 amount) public {\n address from = getAccount(fromAcc);\n hevm.prank(ADDRESS_VAULT);\n ousd.burn(from, amount);\n }\n\n /**\n * @notice Change the total supply of OUSD (rebase)\n * @param amount New total supply\n */\n function changeSupply(uint256 amount) public {\n if (TOGGLE_CHANGESUPPLY_LIMIT) {\n amount =\n ousd.totalSupply() +\n (amount % (ousd.totalSupply() / CHANGESUPPLY_DIVISOR));\n }\n\n hevm.prank(ADDRESS_VAULT);\n ousd.changeSupply(amount);\n }\n\n /**\n * @notice Transfer tokens between accounts\n * @param fromAcc Account to transfer from\n * @param toAcc Account to transfer to\n * @param amount Amount to transfer\n */\n function transfer(\n uint8 fromAcc,\n uint8 toAcc,\n uint256 amount\n ) public {\n address from = getAccount(fromAcc);\n address to = getAccount(toAcc);\n hevm.prank(from);\n // slither-disable-next-line unchecked-transfer\n ousd.transfer(to, amount);\n }\n\n /**\n * @notice Transfer approved tokens between accounts\n * @param authorizedAcc Account that is authorized to transfer\n * @param fromAcc Account to transfer from\n * @param toAcc Account to transfer to\n * @param amount Amount to transfer\n */\n function transferFrom(\n uint8 authorizedAcc,\n uint8 fromAcc,\n uint8 toAcc,\n uint256 amount\n ) public {\n address authorized = getAccount(authorizedAcc);\n address from = getAccount(fromAcc);\n address to = getAccount(toAcc);\n hevm.prank(authorized);\n // slither-disable-next-line unchecked-transfer\n ousd.transferFrom(from, to, amount);\n }\n\n /**\n * @notice Opt in to rebasing\n * @param targetAcc Account to opt in\n */\n function optIn(uint8 targetAcc) public {\n address target = getAccount(targetAcc);\n hevm.prank(target);\n ousd.rebaseOptIn();\n }\n\n /**\n * @notice Opt out of rebasing\n * @param targetAcc Account to opt out\n */\n function optOut(uint8 targetAcc) public {\n address target = getAccount(targetAcc);\n hevm.prank(target);\n ousd.rebaseOptOut();\n }\n\n /**\n * @notice Approve an account to spend OUSD\n * @param ownerAcc Account that owns the OUSD\n * @param spenderAcc Account that is approved to spend the OUSD\n * @param amount Amount to approve\n */\n function approve(\n uint8 ownerAcc,\n uint8 spenderAcc,\n uint256 amount\n ) public {\n address owner = getAccount(ownerAcc);\n address spender = getAccount(spenderAcc);\n hevm.prank(owner);\n // slither-disable-next-line unused-return\n ousd.approve(spender, amount);\n }\n\n /**\n * @notice Get the sum of all OUSD balances\n * @return total Total balance\n */\n function getTotalBalance() public view returns (uint256 total) {\n total += ousd.balanceOf(ADDRESS_VAULT);\n total += ousd.balanceOf(ADDRESS_OUTSIDER_USER);\n total += ousd.balanceOf(ADDRESS_OUTSIDER_CONTRACT);\n total += ousd.balanceOf(ADDRESS_USER0);\n total += ousd.balanceOf(ADDRESS_USER1);\n total += ousd.balanceOf(ADDRESS_CONTRACT0);\n total += ousd.balanceOf(ADDRESS_CONTRACT1);\n }\n\n /**\n * @notice Get the sum of all non-rebasing OUSD balances\n * @return total Total balance\n */\n function getTotalNonRebasingBalance() public returns (uint256 total) {\n total += ousd._isNonRebasingAccountEchidna(ADDRESS_VAULT)\n ? ousd.balanceOf(ADDRESS_VAULT)\n : 0;\n total += ousd._isNonRebasingAccountEchidna(ADDRESS_OUTSIDER_USER)\n ? ousd.balanceOf(ADDRESS_OUTSIDER_USER)\n : 0;\n total += ousd._isNonRebasingAccountEchidna(ADDRESS_OUTSIDER_CONTRACT)\n ? ousd.balanceOf(ADDRESS_OUTSIDER_CONTRACT)\n : 0;\n total += ousd._isNonRebasingAccountEchidna(ADDRESS_USER0)\n ? ousd.balanceOf(ADDRESS_USER0)\n : 0;\n total += ousd._isNonRebasingAccountEchidna(ADDRESS_USER1)\n ? ousd.balanceOf(ADDRESS_USER1)\n : 0;\n total += ousd._isNonRebasingAccountEchidna(ADDRESS_CONTRACT0)\n ? ousd.balanceOf(ADDRESS_CONTRACT0)\n : 0;\n total += ousd._isNonRebasingAccountEchidna(ADDRESS_CONTRACT1)\n ? ousd.balanceOf(ADDRESS_CONTRACT1)\n : 0;\n }\n}\n" + }, + "contracts/echidna/EchidnaSetup.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport \"./IHevm.sol\";\nimport \"./EchidnaConfig.sol\";\nimport \"./OUSDEchidna.sol\";\n\ncontract Dummy {}\n\n/**\n * @title Mixin for setup and deployment\n * @author Rappie\n */\ncontract EchidnaSetup is EchidnaConfig {\n IHevm hevm = IHevm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);\n OUSDEchidna ousd = new OUSDEchidna();\n\n /**\n * @notice Deploy the OUSD contract and set up initial state\n */\n constructor() {\n ousd.initialize(ADDRESS_VAULT, 1e18);\n\n // Deploy dummny contracts as users\n Dummy outsider = new Dummy();\n ADDRESS_OUTSIDER_CONTRACT = address(outsider);\n Dummy dummy0 = new Dummy();\n ADDRESS_CONTRACT0 = address(dummy0);\n Dummy dummy1 = new Dummy();\n ADDRESS_CONTRACT1 = address(dummy1);\n\n // Start out with a reasonable amount of OUSD\n if (TOGGLE_STARTING_BALANCE) {\n // Rebasing tokens\n hevm.prank(ADDRESS_VAULT);\n ousd.mint(ADDRESS_OUTSIDER_USER, STARTING_BALANCE / 2);\n\n // Non-rebasing tokens\n hevm.prank(ADDRESS_VAULT);\n ousd.mint(ADDRESS_OUTSIDER_CONTRACT, STARTING_BALANCE / 2);\n }\n }\n}\n" + }, + "contracts/echidna/EchidnaTestAccounting.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport \"./EchidnaDebug.sol\";\nimport \"./EchidnaTestSupply.sol\";\n\n/**\n * @title Mixin for testing accounting functions\n * @author Rappie\n */\ncontract EchidnaTestAccounting is EchidnaTestSupply {\n /**\n * @notice After opting in, balance should not increase. (Ok to lose rounding funds doing this)\n * @param targetAcc Account to opt in\n */\n function testOptInBalance(uint8 targetAcc) public {\n address target = getAccount(targetAcc);\n\n uint256 balanceBefore = ousd.balanceOf(target);\n optIn(targetAcc);\n uint256 balanceAfter = ousd.balanceOf(target);\n\n assert(balanceAfter <= balanceBefore);\n }\n\n /**\n * @notice After opting out, balance should remain the same\n * @param targetAcc Account to opt out\n */\n function testOptOutBalance(uint8 targetAcc) public {\n address target = getAccount(targetAcc);\n\n uint256 balanceBefore = ousd.balanceOf(target);\n optOut(targetAcc);\n uint256 balanceAfter = ousd.balanceOf(target);\n\n assert(balanceAfter == balanceBefore);\n }\n\n /**\n * @notice Account balance should remain the same after opting in minus rounding error\n * @param targetAcc Account to opt in\n */\n function testOptInBalanceRounding(uint8 targetAcc) public {\n address target = getAccount(targetAcc);\n\n uint256 balanceBefore = ousd.balanceOf(target);\n optIn(targetAcc);\n uint256 balanceAfter = ousd.balanceOf(target);\n\n int256 delta = int256(balanceAfter) - int256(balanceBefore);\n Debugger.log(\"delta\", delta);\n\n // slither-disable-next-line tautology\n assert(-1 * delta >= 0);\n assert(-1 * delta <= int256(OPT_IN_ROUNDING_ERROR));\n }\n\n /**\n * @notice After opting in, total supply should remain the same\n * @param targetAcc Account to opt in\n */\n function testOptInTotalSupply(uint8 targetAcc) public {\n uint256 totalSupplyBefore = ousd.totalSupply();\n optIn(targetAcc);\n uint256 totalSupplyAfter = ousd.totalSupply();\n\n assert(totalSupplyAfter == totalSupplyBefore);\n }\n\n /**\n * @notice After opting out, total supply should remain the same\n * @param targetAcc Account to opt out\n */\n function testOptOutTotalSupply(uint8 targetAcc) public {\n uint256 totalSupplyBefore = ousd.totalSupply();\n optOut(targetAcc);\n uint256 totalSupplyAfter = ousd.totalSupply();\n\n assert(totalSupplyAfter == totalSupplyBefore);\n }\n\n /**\n * @notice Account balance should remain the same when a smart contract auto converts\n * @param targetAcc Account to auto convert\n */\n function testAutoConvertBalance(uint8 targetAcc) public {\n address target = getAccount(targetAcc);\n\n uint256 balanceBefore = ousd.balanceOf(target);\n // slither-disable-next-line unused-return\n ousd._isNonRebasingAccountEchidna(target);\n uint256 balanceAfter = ousd.balanceOf(target);\n\n assert(balanceAfter == balanceBefore);\n }\n\n /**\n * @notice The `balanceOf` function should never revert\n * @param targetAcc Account to check balance of\n */\n function testBalanceOfShouldNotRevert(uint8 targetAcc) public {\n address target = getAccount(targetAcc);\n\n // slither-disable-next-line unused-return\n try ousd.balanceOf(target) {\n assert(true);\n } catch {\n assert(false);\n }\n }\n}\n" + }, + "contracts/echidna/EchidnaTestApproval.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport \"./EchidnaTestMintBurn.sol\";\nimport \"./Debugger.sol\";\n\n/**\n * @title Mixin for testing approval related functions\n * @author Rappie\n */\ncontract EchidnaTestApproval is EchidnaTestMintBurn {\n /**\n * @notice Performing `transferFrom` with an amount inside the allowance should not revert\n * @param authorizedAcc The account that is authorized to transfer\n * @param fromAcc The account that is transferring\n * @param toAcc The account that is receiving\n * @param amount The amount to transfer\n */\n function testTransferFromShouldNotRevert(\n uint8 authorizedAcc,\n uint8 fromAcc,\n uint8 toAcc,\n uint256 amount\n ) public {\n address authorized = getAccount(authorizedAcc);\n address from = getAccount(fromAcc);\n address to = getAccount(toAcc);\n\n require(amount <= ousd.balanceOf(from));\n require(amount <= ousd.allowance(from, authorized));\n\n hevm.prank(authorized);\n // slither-disable-next-line unchecked-transfer\n try ousd.transferFrom(from, to, amount) {\n // pass\n } catch {\n assert(false);\n }\n }\n\n /**\n * @notice Performing `transferFrom` with an amount outside the allowance should revert\n * @param authorizedAcc The account that is authorized to transfer\n * @param fromAcc The account that is transferring\n * @param toAcc The account that is receiving\n * @param amount The amount to transfer\n */\n function testTransferFromShouldRevert(\n uint8 authorizedAcc,\n uint8 fromAcc,\n uint8 toAcc,\n uint256 amount\n ) public {\n address authorized = getAccount(authorizedAcc);\n address from = getAccount(fromAcc);\n address to = getAccount(toAcc);\n\n require(amount > 0);\n require(\n !(amount <= ousd.balanceOf(from) &&\n amount <= ousd.allowance(from, authorized))\n );\n\n hevm.prank(authorized);\n // slither-disable-next-line unchecked-transfer\n try ousd.transferFrom(from, to, amount) {\n assert(false);\n } catch {\n // pass\n }\n }\n\n /**\n * @notice Approving an amount should update the allowance and overwrite any previous allowance\n * @param ownerAcc The account that is approving\n * @param spenderAcc The account that is being approved\n * @param amount The amount to approve\n */\n function testApprove(\n uint8 ownerAcc,\n uint8 spenderAcc,\n uint256 amount\n ) public {\n address owner = getAccount(ownerAcc);\n address spender = getAccount(spenderAcc);\n\n approve(ownerAcc, spenderAcc, amount);\n uint256 allowanceAfter1 = ousd.allowance(owner, spender);\n\n assert(allowanceAfter1 == amount);\n\n approve(ownerAcc, spenderAcc, amount / 2);\n uint256 allowanceAfter2 = ousd.allowance(owner, spender);\n\n assert(allowanceAfter2 == amount / 2);\n }\n}\n" + }, + "contracts/echidna/EchidnaTestMintBurn.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport \"./EchidnaDebug.sol\";\nimport \"./EchidnaTestAccounting.sol\";\n\n/**\n * @title Mixin for testing Mint and Burn functions\n * @author Rappie\n */\ncontract EchidnaTestMintBurn is EchidnaTestAccounting {\n /**\n * @notice Minting 0 tokens should not affect account balance\n * @param targetAcc Account to mint to\n */\n function testMintZeroBalance(uint8 targetAcc) public {\n address target = getAccount(targetAcc);\n\n uint256 balanceBefore = ousd.balanceOf(target);\n mint(targetAcc, 0);\n uint256 balanceAfter = ousd.balanceOf(target);\n\n assert(balanceAfter == balanceBefore);\n }\n\n /**\n * @notice Burning 0 tokens should not affect account balance\n * @param targetAcc Account to burn from\n */\n function testBurnZeroBalance(uint8 targetAcc) public {\n address target = getAccount(targetAcc);\n\n uint256 balanceBefore = ousd.balanceOf(target);\n burn(targetAcc, 0);\n uint256 balanceAfter = ousd.balanceOf(target);\n\n assert(balanceAfter == balanceBefore);\n }\n\n /**\n * @notice Minting tokens must increase the account balance by at least amount\n * @param targetAcc Account to mint to\n * @param amount Amount to mint\n * @custom:error testMintBalance(uint8,uint256): failed!💥\n * Call sequence:\n * changeSupply(1)\n * testMintBalance(0,1)\n * Event sequence:\n * Debug(«balanceBefore», 0)\n * Debug(«balanceAfter», 0)\n */\n function testMintBalance(uint8 targetAcc, uint256 amount)\n public\n hasKnownIssue\n hasKnownIssueWithinLimits\n {\n address target = getAccount(targetAcc);\n\n uint256 balanceBefore = ousd.balanceOf(target);\n uint256 amountMinted = mint(targetAcc, amount);\n uint256 balanceAfter = ousd.balanceOf(target);\n\n Debugger.log(\"amountMinted\", amountMinted);\n Debugger.log(\"balanceBefore\", balanceBefore);\n Debugger.log(\"balanceAfter\", balanceAfter);\n\n assert(balanceAfter >= balanceBefore + amountMinted);\n }\n\n /**\n * @notice Burning tokens must decrease the account balance by at least amount\n * @param targetAcc Account to burn from\n * @param amount Amount to burn\n * @custom:error testBurnBalance(uint8,uint256): failed!💥\n * Call sequence:\n * changeSupply(1)\n * mint(0,3)\n * testBurnBalance(0,1)\n * Event sequence:\n * Debug(«balanceBefore», 2)\n * Debug(«balanceAfter», 2)\n */\n function testBurnBalance(uint8 targetAcc, uint256 amount)\n public\n hasKnownIssue\n hasKnownIssueWithinLimits\n {\n address target = getAccount(targetAcc);\n\n uint256 balanceBefore = ousd.balanceOf(target);\n burn(targetAcc, amount);\n uint256 balanceAfter = ousd.balanceOf(target);\n\n Debugger.log(\"balanceBefore\", balanceBefore);\n Debugger.log(\"balanceAfter\", balanceAfter);\n\n assert(balanceAfter <= balanceBefore - amount);\n }\n\n /**\n * @notice Minting tokens should not increase the account balance by less than rounding error above amount\n * @param targetAcc Account to mint to\n * @param amount Amount to mint\n */\n function testMintBalanceRounding(uint8 targetAcc, uint256 amount) public {\n address target = getAccount(targetAcc);\n\n uint256 balanceBefore = ousd.balanceOf(target);\n uint256 amountMinted = mint(targetAcc, amount);\n uint256 balanceAfter = ousd.balanceOf(target);\n\n int256 delta = int256(balanceAfter) - int256(balanceBefore);\n\n // delta == amount, if no error\n // delta < amount, if too little is minted\n // delta > amount, if too much is minted\n int256 error = int256(amountMinted) - delta;\n\n assert(error >= 0);\n assert(error <= int256(MINT_ROUNDING_ERROR));\n }\n\n /**\n * @notice A burn of an account balance must result in a zero balance\n * @param targetAcc Account to burn from\n */\n function testBurnAllBalanceToZero(uint8 targetAcc) public hasKnownIssue {\n address target = getAccount(targetAcc);\n\n burn(targetAcc, ousd.balanceOf(target));\n assert(ousd.balanceOf(target) == 0);\n }\n\n /**\n * @notice You should always be able to burn an account's balance\n * @param targetAcc Account to burn from\n */\n function testBurnAllBalanceShouldNotRevert(uint8 targetAcc)\n public\n hasKnownIssue\n {\n address target = getAccount(targetAcc);\n uint256 balance = ousd.balanceOf(target);\n\n hevm.prank(ADDRESS_VAULT);\n try ousd.burn(target, balance) {\n assert(true);\n } catch {\n assert(false);\n }\n }\n}\n" + }, + "contracts/echidna/EchidnaTestSupply.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport \"./EchidnaDebug.sol\";\nimport \"./EchidnaTestTransfer.sol\";\n\nimport { StableMath } from \"../utils/StableMath.sol\";\n\n/**\n * @title Mixin for testing supply related functions\n * @author Rappie\n */\ncontract EchidnaTestSupply is EchidnaTestTransfer {\n using StableMath for uint256;\n\n uint256 prevRebasingCreditsPerToken = type(uint256).max;\n\n /**\n * @notice After a `changeSupply`, the total supply should exactly\n * match the target total supply. (This is needed to ensure successive\n * rebases are correct).\n * @param supply New total supply\n * @custom:error testChangeSupply(uint256): failed!💥\n * Call sequence:\n * testChangeSupply(1044505275072865171609)\n * Event sequence:\n * TotalSupplyUpdatedHighres(1044505275072865171610, 1000000000000000000000000, 957391048054055578595)\n */\n function testChangeSupply(uint256 supply)\n public\n hasKnownIssue\n hasKnownIssueWithinLimits\n {\n hevm.prank(ADDRESS_VAULT);\n ousd.changeSupply(supply);\n\n assert(ousd.totalSupply() == supply);\n }\n\n /**\n * @notice The total supply must not be less than the sum of account balances.\n * (The difference will go into future rebases)\n * @custom:error testTotalSupplyLessThanTotalBalance(): failed!💥\n * Call sequence:\n * mint(0,1)\n * changeSupply(1)\n * optOut(64)\n * transfer(0,64,1)\n * testTotalSupplyLessThanTotalBalance()\n * Event sequence:\n * Debug(«totalSupply», 1000000000000000001000001)\n * Debug(«totalBalance», 1000000000000000001000002)\n */\n function testTotalSupplyLessThanTotalBalance()\n public\n hasKnownIssue\n hasKnownIssueWithinLimits\n {\n uint256 totalSupply = ousd.totalSupply();\n uint256 totalBalance = getTotalBalance();\n\n Debugger.log(\"totalSupply\", totalSupply);\n Debugger.log(\"totalBalance\", totalBalance);\n\n assert(totalSupply >= totalBalance);\n }\n\n /**\n * @notice Non-rebasing supply should not be larger than total supply\n * @custom:error testNonRebasingSupplyVsTotalSupply(): failed!💥\n * Call sequence:\n * mint(0,2)\n * changeSupply(3)\n * burn(0,1)\n * optOut(0)\n * testNonRebasingSupplyVsTotalSupply()\n */\n function testNonRebasingSupplyVsTotalSupply() public hasKnownIssue {\n uint256 nonRebasingSupply = ousd.nonRebasingSupply();\n uint256 totalSupply = ousd.totalSupply();\n\n assert(nonRebasingSupply <= totalSupply);\n }\n\n /**\n * @notice Global `rebasingCreditsPerToken` should never increase\n * @custom:error testRebasingCreditsPerTokenNotIncreased(): failed!💥\n * Call sequence:\n * testRebasingCreditsPerTokenNotIncreased()\n * changeSupply(1)\n * testRebasingCreditsPerTokenNotIncreased()\n */\n function testRebasingCreditsPerTokenNotIncreased() public hasKnownIssue {\n uint256 curRebasingCreditsPerToken = ousd\n .rebasingCreditsPerTokenHighres();\n\n Debugger.log(\n \"prevRebasingCreditsPerToken\",\n prevRebasingCreditsPerToken\n );\n Debugger.log(\"curRebasingCreditsPerToken\", curRebasingCreditsPerToken);\n\n assert(curRebasingCreditsPerToken <= prevRebasingCreditsPerToken);\n\n prevRebasingCreditsPerToken = curRebasingCreditsPerToken;\n }\n\n /**\n * @notice The rebasing credits per token ratio must greater than zero\n */\n function testRebasingCreditsPerTokenAboveZero() public {\n assert(ousd.rebasingCreditsPerTokenHighres() > 0);\n }\n\n /**\n * @notice The sum of all non-rebasing balances should not be larger than\n * non-rebasing supply\n * @custom:error testTotalNonRebasingSupplyLessThanTotalBalance(): failed!💥\n * Call sequence\n * mint(0,2)\n * changeSupply(1)\n * optOut(0)\n * burn(0,1)\n * testTotalNonRebasingSupplyLessThanTotalBalance()\n * Event sequence:\n * Debug(«totalNonRebasingSupply», 500000000000000000000001)\n * Debug(«totalNonRebasingBalance», 500000000000000000000002)\n */\n function testTotalNonRebasingSupplyLessThanTotalBalance()\n public\n hasKnownIssue\n hasKnownIssueWithinLimits\n {\n uint256 totalNonRebasingSupply = ousd.nonRebasingSupply();\n uint256 totalNonRebasingBalance = getTotalNonRebasingBalance();\n\n Debugger.log(\"totalNonRebasingSupply\", totalNonRebasingSupply);\n Debugger.log(\"totalNonRebasingBalance\", totalNonRebasingBalance);\n\n assert(totalNonRebasingSupply >= totalNonRebasingBalance);\n }\n\n /**\n * @notice An accounts credits / credits per token should not be larger it's balance\n * @param targetAcc The account to check\n */\n function testCreditsPerTokenVsBalance(uint8 targetAcc) public {\n address target = getAccount(targetAcc);\n\n (uint256 credits, uint256 creditsPerToken, ) = ousd\n .creditsBalanceOfHighres(target);\n uint256 expectedBalance = credits.divPrecisely(creditsPerToken);\n\n uint256 balance = ousd.balanceOf(target);\n\n Debugger.log(\"credits\", credits);\n Debugger.log(\"creditsPerToken\", creditsPerToken);\n Debugger.log(\"expectedBalance\", expectedBalance);\n Debugger.log(\"balance\", balance);\n\n assert(expectedBalance == balance);\n }\n}\n" + }, + "contracts/echidna/EchidnaTestTransfer.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport \"./EchidnaDebug.sol\";\nimport \"./Debugger.sol\";\n\n/**\n * @title Mixin for testing transfer related functions\n * @author Rappie\n */\ncontract EchidnaTestTransfer is EchidnaDebug {\n /**\n * @notice The receiving account's balance after a transfer must not increase by\n * less than the amount transferred\n * @param fromAcc Account to transfer from\n * @param toAcc Account to transfer to\n * @param amount Amount to transfer\n * @custom:error testTransferBalanceReceivedLess(uint8,uint8,uint256): failed!💥\n * Call sequence:\n * changeSupply(1)\n * mint(64,2)\n * testTransferBalanceReceivedLess(64,0,1)\n * Event sequence:\n * Debug(«totalSupply», 1000000000000000000500002)\n * Debug(«toBalBefore», 0)\n * Debug(«toBalAfter», 0)\n */\n function testTransferBalanceReceivedLess(\n uint8 fromAcc,\n uint8 toAcc,\n uint256 amount\n ) public hasKnownIssue hasKnownIssueWithinLimits {\n address from = getAccount(fromAcc);\n address to = getAccount(toAcc);\n\n require(from != to);\n\n uint256 toBalBefore = ousd.balanceOf(to);\n transfer(fromAcc, toAcc, amount);\n uint256 toBalAfter = ousd.balanceOf(to);\n\n Debugger.log(\"totalSupply\", ousd.totalSupply());\n Debugger.log(\"toBalBefore\", toBalBefore);\n Debugger.log(\"toBalAfter\", toBalAfter);\n\n assert(toBalAfter >= toBalBefore + amount);\n }\n\n /**\n * @notice The receiving account's balance after a transfer must not\n * increase by more than the amount transferred\n * @param fromAcc Account to transfer from\n * @param toAcc Account to transfer to\n * @param amount Amount to transfer\n */\n function testTransferBalanceReceivedMore(\n uint8 fromAcc,\n uint8 toAcc,\n uint256 amount\n ) public {\n address from = getAccount(fromAcc);\n address to = getAccount(toAcc);\n\n require(from != to);\n\n uint256 toBalBefore = ousd.balanceOf(to);\n transfer(fromAcc, toAcc, amount);\n uint256 toBalAfter = ousd.balanceOf(to);\n\n Debugger.log(\"totalSupply\", ousd.totalSupply());\n Debugger.log(\"toBalBefore\", toBalBefore);\n Debugger.log(\"toBalAfter\", toBalAfter);\n\n assert(toBalAfter <= toBalBefore + amount);\n }\n\n /**\n * @notice The sending account's balance after a transfer must not\n * decrease by less than the amount transferred\n * @param fromAcc Account to transfer from\n * @param toAcc Account to transfer to\n * @param amount Amount to transfer\n * @custom:error testTransferBalanceSentLess(uint8,uint8,uint256): failed!💥\n * Call sequence:\n * mint(0,1)\n * changeSupply(1)\n * testTransferBalanceSentLess(0,64,1)\n * Event sequence:\n * Debug(«totalSupply», 1000000000000000000500001)\n * Debug(«fromBalBefore», 1)\n * Debug(«fromBalAfter», 1)\n */\n function testTransferBalanceSentLess(\n uint8 fromAcc,\n uint8 toAcc,\n uint256 amount\n ) public hasKnownIssue hasKnownIssueWithinLimits {\n address from = getAccount(fromAcc);\n address to = getAccount(toAcc);\n\n require(from != to);\n\n uint256 fromBalBefore = ousd.balanceOf(from);\n transfer(fromAcc, toAcc, amount);\n uint256 fromBalAfter = ousd.balanceOf(from);\n\n Debugger.log(\"totalSupply\", ousd.totalSupply());\n Debugger.log(\"fromBalBefore\", fromBalBefore);\n Debugger.log(\"fromBalAfter\", fromBalAfter);\n\n assert(fromBalAfter <= fromBalBefore - amount);\n }\n\n /**\n * @notice The sending account's balance after a transfer must not\n * decrease by more than the amount transferred\n * @param fromAcc Account to transfer from\n * @param toAcc Account to transfer to\n * @param amount Amount to transfer\n */\n function testTransferBalanceSentMore(\n uint8 fromAcc,\n uint8 toAcc,\n uint256 amount\n ) public {\n address from = getAccount(fromAcc);\n address to = getAccount(toAcc);\n\n require(from != to);\n\n uint256 fromBalBefore = ousd.balanceOf(from);\n transfer(fromAcc, toAcc, amount);\n uint256 fromBalAfter = ousd.balanceOf(from);\n\n Debugger.log(\"totalSupply\", ousd.totalSupply());\n Debugger.log(\"fromBalBefore\", fromBalBefore);\n Debugger.log(\"fromBalAfter\", fromBalAfter);\n\n assert(fromBalAfter >= fromBalBefore - amount);\n }\n\n /**\n * @notice The receiving account's balance after a transfer must not\n * increase by less than the amount transferred (minus rounding error)\n * @param fromAcc Account to transfer from\n * @param toAcc Account to transfer to\n * @param amount Amount to transfer\n */\n function testTransferBalanceReceivedLessRounding(\n uint8 fromAcc,\n uint8 toAcc,\n uint256 amount\n ) public {\n address from = getAccount(fromAcc);\n address to = getAccount(toAcc);\n\n require(from != to);\n\n uint256 toBalBefore = ousd.balanceOf(to);\n transfer(fromAcc, toAcc, amount);\n uint256 toBalAfter = ousd.balanceOf(to);\n\n int256 toDelta = int256(toBalAfter) - int256(toBalBefore);\n\n // delta == amount, if no error\n // delta < amount, if too little is sent\n // delta > amount, if too much is sent\n int256 error = int256(amount) - toDelta;\n\n Debugger.log(\"totalSupply\", ousd.totalSupply());\n Debugger.log(\"toBalBefore\", toBalBefore);\n Debugger.log(\"toBalAfter\", toBalAfter);\n Debugger.log(\"toDelta\", toDelta);\n Debugger.log(\"error\", error);\n\n assert(error >= 0);\n assert(error <= int256(TRANSFER_ROUNDING_ERROR));\n }\n\n /**\n * @notice The sending account's balance after a transfer must\n * not decrease by less than the amount transferred (minus rounding error)\n * @param fromAcc Account to transfer from\n * @param toAcc Account to transfer to\n * @param amount Amount to transfer\n */\n function testTransferBalanceSentLessRounding(\n uint8 fromAcc,\n uint8 toAcc,\n uint256 amount\n ) public {\n address from = getAccount(fromAcc);\n address to = getAccount(toAcc);\n\n require(from != to);\n\n uint256 fromBalBefore = ousd.balanceOf(from);\n transfer(fromAcc, toAcc, amount);\n uint256 fromBalAfter = ousd.balanceOf(from);\n\n int256 fromDelta = int256(fromBalAfter) - int256(fromBalBefore);\n\n // delta == -amount, if no error\n // delta < -amount, if too much is sent\n // delta > -amount, if too little is sent\n int256 error = int256(amount) + fromDelta;\n\n Debugger.log(\"totalSupply\", ousd.totalSupply());\n Debugger.log(\"fromBalBefore\", fromBalBefore);\n Debugger.log(\"fromBalAfter\", fromBalAfter);\n Debugger.log(\"fromDelta\", fromDelta);\n Debugger.log(\"error\", error);\n\n assert(error >= 0);\n assert(error <= int256(TRANSFER_ROUNDING_ERROR));\n }\n\n /**\n * @notice An account should always be able to successfully transfer\n * an amount within its balance.\n * @param fromAcc Account to transfer from\n * @param toAcc Account to transfer to\n * @param amount Amount to transfer\n * @custom:error testTransferWithinBalanceDoesNotRevert(uint8,uint8,uint8): failed!💥\n * Call sequence:\n * mint(0,1)\n * changeSupply(3)\n * optOut(0)\n * testTransferWithinBalanceDoesNotRevert(0,128,2)\n * optIn(0)\n * testTransferWithinBalanceDoesNotRevert(128,0,1)\n * Event sequence:\n * error Revert Panic(17): SafeMath over-/under-flows\n */\n function testTransferWithinBalanceDoesNotRevert(\n uint8 fromAcc,\n uint8 toAcc,\n uint256 amount\n ) public hasKnownIssue {\n address from = getAccount(fromAcc);\n address to = getAccount(toAcc);\n\n require(amount > 0);\n amount = amount % ousd.balanceOf(from);\n\n Debugger.log(\"Total supply\", ousd.totalSupply());\n\n hevm.prank(from);\n // slither-disable-next-line unchecked-transfer\n try ousd.transfer(to, amount) {\n assert(true);\n } catch {\n assert(false);\n }\n }\n\n /**\n * @notice An account should never be able to successfully transfer\n * an amount greater than their balance.\n * @param fromAcc Account to transfer from\n * @param toAcc Account to transfer to\n * @param amount Amount to transfer\n */\n function testTransferExceedingBalanceReverts(\n uint8 fromAcc,\n uint8 toAcc,\n uint256 amount\n ) public {\n address from = getAccount(fromAcc);\n address to = getAccount(toAcc);\n\n amount = ousd.balanceOf(from) + 1 + amount;\n\n hevm.prank(from);\n // slither-disable-next-line unchecked-transfer\n try ousd.transfer(to, amount) {\n assert(false);\n } catch {\n assert(true);\n }\n }\n\n /**\n * @notice A transfer to the same account should not change that account's balance\n * @param targetAcc Account to transfer to\n * @param amount Amount to transfer\n */\n function testTransferSelf(uint8 targetAcc, uint256 amount) public {\n address target = getAccount(targetAcc);\n\n uint256 balanceBefore = ousd.balanceOf(target);\n transfer(targetAcc, targetAcc, amount);\n uint256 balanceAfter = ousd.balanceOf(target);\n\n assert(balanceBefore == balanceAfter);\n }\n\n /**\n * @notice Transfers to the zero account revert\n * @param fromAcc Account to transfer from\n * @param amount Amount to transfer\n */\n function testTransferToZeroAddress(uint8 fromAcc, uint256 amount) public {\n address from = getAccount(fromAcc);\n\n hevm.prank(from);\n // slither-disable-next-line unchecked-transfer\n try ousd.transfer(address(0), amount) {\n assert(false);\n } catch {\n assert(true);\n }\n }\n}\n" + }, + "contracts/echidna/IHevm.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n// https://github.com/ethereum/hevm/blob/main/doc/src/controlling-the-unit-testing-environment.md#cheat-codes\n\ninterface IHevm {\n function warp(uint256 x) external;\n\n function roll(uint256 x) external;\n\n function store(\n address c,\n bytes32 loc,\n bytes32 val\n ) external;\n\n function load(address c, bytes32 loc) external returns (bytes32 val);\n\n function sign(uint256 sk, bytes32 digest)\n external\n returns (\n uint8 v,\n bytes32 r,\n bytes32 s\n );\n\n function addr(uint256 sk) external returns (address addr);\n\n function ffi(string[] calldata) external returns (bytes memory);\n\n function prank(address sender) external;\n}\n" + }, + "contracts/echidna/OUSDEchidna.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport \"../token/OUSD.sol\";\n\ncontract OUSDEchidna is OUSD {\n constructor() OUSD() {}\n\n function _isNonRebasingAccountEchidna(address _account)\n public\n returns (bool)\n {\n _autoMigrate(_account);\n return alternativeCreditsPerToken[_account] > 0;\n }\n}\n" + }, + "contracts/governance/Governable.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title Base for contracts that are managed by the Origin Protocol's Governor.\n * @dev Copy of the openzeppelin Ownable.sol contract with nomenclature change\n * from owner to governor and renounce methods removed. Does not use\n * Context.sol like Ownable.sol does for simplification.\n * @author Origin Protocol Inc\n */\nabstract contract Governable {\n // Storage position of the owner and pendingOwner of the contract\n // keccak256(\"OUSD.governor\");\n bytes32 private constant governorPosition =\n 0x7bea13895fa79d2831e0a9e28edede30099005a50d652d8957cf8a607ee6ca4a;\n\n // keccak256(\"OUSD.pending.governor\");\n bytes32 private constant pendingGovernorPosition =\n 0x44c4d30b2eaad5130ad70c3ba6972730566f3e6359ab83e800d905c61b1c51db;\n\n // keccak256(\"OUSD.reentry.status\");\n bytes32 private constant reentryStatusPosition =\n 0x53bf423e48ed90e97d02ab0ebab13b2a235a6bfbe9c321847d5c175333ac4535;\n\n // See OpenZeppelin ReentrancyGuard implementation\n uint256 constant _NOT_ENTERED = 1;\n uint256 constant _ENTERED = 2;\n\n event PendingGovernorshipTransfer(\n address indexed previousGovernor,\n address indexed newGovernor\n );\n\n event GovernorshipTransferred(\n address indexed previousGovernor,\n address indexed newGovernor\n );\n\n /**\n * @notice Returns the address of the current Governor.\n */\n function governor() public view returns (address) {\n return _governor();\n }\n\n /**\n * @dev Returns the address of the current Governor.\n */\n function _governor() internal view returns (address governorOut) {\n bytes32 position = governorPosition;\n // solhint-disable-next-line no-inline-assembly\n assembly {\n governorOut := sload(position)\n }\n }\n\n /**\n * @dev Returns the address of the pending Governor.\n */\n function _pendingGovernor()\n internal\n view\n returns (address pendingGovernor)\n {\n bytes32 position = pendingGovernorPosition;\n // solhint-disable-next-line no-inline-assembly\n assembly {\n pendingGovernor := sload(position)\n }\n }\n\n /**\n * @dev Throws if called by any account other than the Governor.\n */\n modifier onlyGovernor() {\n require(isGovernor(), \"Caller is not the Governor\");\n _;\n }\n\n /**\n * @notice Returns true if the caller is the current Governor.\n */\n function isGovernor() public view returns (bool) {\n return msg.sender == _governor();\n }\n\n function _setGovernor(address newGovernor) internal {\n emit GovernorshipTransferred(_governor(), newGovernor);\n\n bytes32 position = governorPosition;\n // solhint-disable-next-line no-inline-assembly\n assembly {\n sstore(position, newGovernor)\n }\n }\n\n /**\n * @dev Prevents a contract from calling itself, directly or indirectly.\n * Calling a `nonReentrant` function from another `nonReentrant`\n * function is not supported. It is possible to prevent this from happening\n * by making the `nonReentrant` function external, and make it call a\n * `private` function that does the actual work.\n */\n modifier nonReentrant() {\n bytes32 position = reentryStatusPosition;\n uint256 _reentry_status;\n // solhint-disable-next-line no-inline-assembly\n assembly {\n _reentry_status := sload(position)\n }\n\n // On the first call to nonReentrant, _notEntered will be true\n require(_reentry_status != _ENTERED, \"Reentrant call\");\n\n // Any calls to nonReentrant after this point will fail\n // solhint-disable-next-line no-inline-assembly\n assembly {\n sstore(position, _ENTERED)\n }\n\n _;\n\n // By storing the original value once again, a refund is triggered (see\n // https://eips.ethereum.org/EIPS/eip-2200)\n // solhint-disable-next-line no-inline-assembly\n assembly {\n sstore(position, _NOT_ENTERED)\n }\n }\n\n function _setPendingGovernor(address newGovernor) internal {\n bytes32 position = pendingGovernorPosition;\n // solhint-disable-next-line no-inline-assembly\n assembly {\n sstore(position, newGovernor)\n }\n }\n\n /**\n * @notice Transfers Governance of the contract to a new account (`newGovernor`).\n * Can only be called by the current Governor. Must be claimed for this to complete\n * @param _newGovernor Address of the new Governor\n */\n function transferGovernance(address _newGovernor) external onlyGovernor {\n _setPendingGovernor(_newGovernor);\n emit PendingGovernorshipTransfer(_governor(), _newGovernor);\n }\n\n /**\n * @notice Claim Governance of the contract to a new account (`newGovernor`).\n * Can only be called by the new Governor.\n */\n function claimGovernance() external {\n require(\n msg.sender == _pendingGovernor(),\n \"Only the pending Governor can complete the claim\"\n );\n _changeGovernor(msg.sender);\n }\n\n /**\n * @dev Change Governance of the contract to a new account (`newGovernor`).\n * @param _newGovernor Address of the new Governor\n */\n function _changeGovernor(address _newGovernor) internal {\n require(_newGovernor != address(0), \"New Governor is address(0)\");\n _setGovernor(_newGovernor);\n }\n}\n" + }, + "contracts/governance/Strategizable.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { Governable } from \"./Governable.sol\";\n\ncontract Strategizable is Governable {\n event StrategistUpdated(address _address);\n\n // Address of strategist\n address public strategistAddr;\n\n // For future use\n uint256[50] private __gap;\n\n /**\n * @dev Verifies that the caller is either Governor or Strategist.\n */\n modifier onlyGovernorOrStrategist() virtual {\n require(\n msg.sender == strategistAddr || isGovernor(),\n \"Caller is not the Strategist or Governor\"\n );\n _;\n }\n\n /**\n * @dev Set address of Strategist\n * @param _address Address of Strategist\n */\n function setStrategistAddr(address _address) external onlyGovernor {\n _setStrategistAddr(_address);\n }\n\n /**\n * @dev Set address of Strategist\n * @param _address Address of Strategist\n */\n function _setStrategistAddr(address _address) internal {\n strategistAddr = _address;\n emit StrategistUpdated(_address);\n }\n}\n" + }, + "contracts/harvest/AbstractHarvester.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport { SafeMath } from \"@openzeppelin/contracts/utils/math/SafeMath.sol\";\nimport \"@openzeppelin/contracts/utils/math/Math.sol\";\n\nimport { StableMath } from \"../utils/StableMath.sol\";\nimport { Governable } from \"../governance/Governable.sol\";\nimport { IVault } from \"../interfaces/IVault.sol\";\nimport { IOracle } from \"../interfaces/IOracle.sol\";\nimport { IStrategy } from \"../interfaces/IStrategy.sol\";\nimport { IUniswapV2Router } from \"../interfaces/uniswap/IUniswapV2Router02.sol\";\nimport { IUniswapV3Router } from \"../interfaces/uniswap/IUniswapV3Router.sol\";\nimport { IBalancerVault } from \"../interfaces/balancer/IBalancerVault.sol\";\nimport { ICurvePool } from \"../strategies/ICurvePool.sol\";\nimport \"../utils/Helpers.sol\";\n\nabstract contract AbstractHarvester is Governable {\n using SafeERC20 for IERC20;\n using SafeMath for uint256;\n using StableMath for uint256;\n\n enum SwapPlatform {\n UniswapV2Compatible,\n UniswapV3,\n Balancer,\n Curve\n }\n\n event SupportedStrategyUpdate(address strategyAddress, bool isSupported);\n event RewardTokenConfigUpdated(\n address tokenAddress,\n uint16 allowedSlippageBps,\n uint16 harvestRewardBps,\n SwapPlatform swapPlatform,\n address swapPlatformAddr,\n bytes swapData,\n uint256 liquidationLimit,\n bool doSwapRewardToken\n );\n event RewardTokenSwapped(\n address indexed rewardToken,\n address indexed swappedInto,\n SwapPlatform swapPlatform,\n uint256 amountIn,\n uint256 amountOut\n );\n event RewardProceedsTransferred(\n address indexed token,\n address farmer,\n uint256 protcolYield,\n uint256 farmerFee\n );\n event RewardProceedsAddressChanged(address newProceedsAddress);\n\n error EmptyAddress();\n error InvalidSlippageBps();\n error InvalidHarvestRewardBps();\n\n error InvalidSwapPlatform(SwapPlatform swapPlatform);\n\n error InvalidUniswapV2PathLength();\n error InvalidTokenInSwapPath(address token);\n error EmptyBalancerPoolId();\n error InvalidCurvePoolAssetIndex(address token);\n\n error UnsupportedStrategy(address strategyAddress);\n\n error SlippageError(uint256 actualBalance, uint256 minExpected);\n error BalanceMismatchAfterSwap(uint256 actualBalance, uint256 minExpected);\n\n // Configuration properties for harvesting logic of reward tokens\n struct RewardTokenConfig {\n // Max allowed slippage when swapping reward token for a stablecoin denominated in basis points.\n uint16 allowedSlippageBps;\n // Reward when calling a harvest function denominated in basis points.\n uint16 harvestRewardBps;\n // Address of compatible exchange protocol (Uniswap V2/V3, SushiSwap, Balancer and Curve).\n address swapPlatformAddr;\n /* When true the reward token is being swapped. In a need of (temporarily) disabling the swapping of\n * a reward token this needs to be set to false.\n */\n bool doSwapRewardToken;\n // Platform to use for Swapping\n SwapPlatform swapPlatform;\n /* How much token can be sold per one harvest call. If the balance of rewards tokens\n * exceeds that limit multiple harvest calls are required to harvest all of the tokens.\n * Set it to MAX_INT to effectively disable the limit.\n */\n uint256 liquidationLimit;\n }\n\n mapping(address => RewardTokenConfig) public rewardTokenConfigs;\n mapping(address => bool) public supportedStrategies;\n\n address public immutable vaultAddress;\n\n /**\n * Address receiving rewards proceeds. Initially the Vault contract later will possibly\n * be replaced by another contract that eases out rewards distribution.\n **/\n address public rewardProceedsAddress;\n\n /**\n * All tokens are swapped to this token before it gets transferred\n * to the `rewardProceedsAddress`. USDT for OUSD and WETH for OETH.\n **/\n address public immutable baseTokenAddress;\n // Cached decimals for `baseTokenAddress`\n uint256 public immutable baseTokenDecimals;\n\n // Uniswap V2 path for reward tokens using Uniswap V2 Router\n mapping(address => address[]) public uniswapV2Path;\n // Uniswap V3 path for reward tokens using Uniswap V3 Router\n mapping(address => bytes) public uniswapV3Path;\n // Pool ID to use for reward tokens on Balancer\n mapping(address => bytes32) public balancerPoolId;\n\n struct CurvePoolIndices {\n // Casted into uint128 and stored in a struct to save gas\n uint128 rewardTokenIndex;\n uint128 baseTokenIndex;\n }\n // Packed indices of assets on the Curve pool\n mapping(address => CurvePoolIndices) public curvePoolIndices;\n\n constructor(address _vaultAddress, address _baseTokenAddress) {\n require(_vaultAddress != address(0));\n require(_baseTokenAddress != address(0));\n\n vaultAddress = _vaultAddress;\n baseTokenAddress = _baseTokenAddress;\n\n // Cache decimals as well\n baseTokenDecimals = Helpers.getDecimals(_baseTokenAddress);\n }\n\n /***************************************\n Configuration\n ****************************************/\n\n /**\n * Set the Address receiving rewards proceeds.\n * @param _rewardProceedsAddress Address of the reward token\n */\n function setRewardProceedsAddress(address _rewardProceedsAddress)\n external\n onlyGovernor\n {\n if (_rewardProceedsAddress == address(0)) {\n revert EmptyAddress();\n }\n\n rewardProceedsAddress = _rewardProceedsAddress;\n emit RewardProceedsAddressChanged(_rewardProceedsAddress);\n }\n\n /**\n * @dev Add/update a reward token configuration that holds harvesting config variables\n * @param _tokenAddress Address of the reward token\n * @param tokenConfig.allowedSlippageBps uint16 maximum allowed slippage denominated in basis points.\n * Example: 300 == 3% slippage\n * @param tokenConfig.harvestRewardBps uint16 amount of reward tokens the caller of the function is rewarded.\n * Example: 100 == 1%\n * @param tokenConfig.swapPlatformAddr Address Address of a UniswapV2 compatible contract to perform\n * the exchange from reward tokens to stablecoin (currently hard-coded to USDT)\n * @param tokenConfig.liquidationLimit uint256 Maximum amount of token to be sold per one swap function call.\n * When value is 0 there is no limit.\n * @param tokenConfig.doSwapRewardToken bool Disables swapping of the token when set to true,\n * does not cause it to revert though.\n * @param tokenConfig.swapPlatform SwapPlatform to use for Swapping\n * @param swapData Additional data required for swapping\n */\n function setRewardTokenConfig(\n address _tokenAddress,\n RewardTokenConfig calldata tokenConfig,\n bytes calldata swapData\n ) external onlyGovernor {\n if (tokenConfig.allowedSlippageBps > 1000) {\n revert InvalidSlippageBps();\n }\n\n if (tokenConfig.harvestRewardBps > 1000) {\n revert InvalidHarvestRewardBps();\n }\n\n address newRouterAddress = tokenConfig.swapPlatformAddr;\n if (newRouterAddress == address(0)) {\n // Swap router address should be non zero address\n revert EmptyAddress();\n }\n\n address oldRouterAddress = rewardTokenConfigs[_tokenAddress]\n .swapPlatformAddr;\n rewardTokenConfigs[_tokenAddress] = tokenConfig;\n\n // Revert if feed does not exist\n // slither-disable-next-line unused-return\n\n IERC20 token = IERC20(_tokenAddress);\n // if changing token swap provider cancel existing allowance\n if (\n /* oldRouterAddress == address(0) when there is no pre-existing\n * configuration for said rewards token\n */\n oldRouterAddress != address(0) &&\n oldRouterAddress != newRouterAddress\n ) {\n token.safeApprove(oldRouterAddress, 0);\n }\n\n // Give SwapRouter infinite approval when needed\n if (oldRouterAddress != newRouterAddress) {\n token.safeApprove(newRouterAddress, 0);\n token.safeApprove(newRouterAddress, type(uint256).max);\n }\n\n SwapPlatform _platform = tokenConfig.swapPlatform;\n if (_platform == SwapPlatform.UniswapV2Compatible) {\n uniswapV2Path[_tokenAddress] = _decodeUniswapV2Path(\n swapData,\n _tokenAddress\n );\n } else if (_platform == SwapPlatform.UniswapV3) {\n uniswapV3Path[_tokenAddress] = _decodeUniswapV3Path(\n swapData,\n _tokenAddress\n );\n } else if (_platform == SwapPlatform.Balancer) {\n balancerPoolId[_tokenAddress] = _decodeBalancerPoolId(\n swapData,\n newRouterAddress,\n _tokenAddress\n );\n } else if (_platform == SwapPlatform.Curve) {\n curvePoolIndices[_tokenAddress] = _decodeCurvePoolIndices(\n swapData,\n newRouterAddress,\n _tokenAddress\n );\n } else {\n // Note: This code is unreachable since Solidity reverts when\n // the value is outside the range of defined values of the enum\n // (even if it's under the max length of the base type)\n revert InvalidSwapPlatform(_platform);\n }\n\n emit RewardTokenConfigUpdated(\n _tokenAddress,\n tokenConfig.allowedSlippageBps,\n tokenConfig.harvestRewardBps,\n _platform,\n newRouterAddress,\n swapData,\n tokenConfig.liquidationLimit,\n tokenConfig.doSwapRewardToken\n );\n }\n\n /**\n * @dev Decodes the data passed into Uniswap V2 path and validates\n * it to make sure the path is for `token` to `baseToken`\n *\n * @param data Ecnoded data passed to the `setRewardTokenConfig`\n * @param token The address of the reward token\n * @return path The validated Uniswap V2 path\n */\n function _decodeUniswapV2Path(bytes calldata data, address token)\n internal\n view\n returns (address[] memory path)\n {\n (path) = abi.decode(data, (address[]));\n uint256 len = path.length;\n\n if (len < 2) {\n // Path should have at least two tokens\n revert InvalidUniswapV2PathLength();\n }\n\n // Do some validation\n if (path[0] != token) {\n revert InvalidTokenInSwapPath(path[0]);\n }\n\n if (path[len - 1] != baseTokenAddress) {\n revert InvalidTokenInSwapPath(path[len - 1]);\n }\n }\n\n /**\n * @dev Decodes the data passed into Uniswap V3 path and validates\n * it to make sure the path is for `token` to `baseToken`\n *\n * @param data Ecnoded data passed to the `setRewardTokenConfig`\n * @param token The address of the reward token\n * @return path The validated Uniswap V3 path\n */\n function _decodeUniswapV3Path(bytes calldata data, address token)\n internal\n view\n returns (bytes calldata path)\n {\n path = data;\n\n address decodedAddress = address(uint160(bytes20(data[0:20])));\n\n if (decodedAddress != token) {\n // Invalid Reward Token in swap path\n revert InvalidTokenInSwapPath(decodedAddress);\n }\n\n decodedAddress = address(uint160(bytes20(data[path.length - 20:])));\n if (decodedAddress != baseTokenAddress) {\n // Invalid Base Token in swap path\n revert InvalidTokenInSwapPath(decodedAddress);\n }\n }\n\n /**\n * @dev Decodes the data passed to Balancer Pool ID\n *\n * @param data Ecnoded data passed to the `setRewardTokenConfig`\n * @return poolId The pool ID\n */\n function _decodeBalancerPoolId(\n bytes calldata data,\n address balancerVault,\n address token\n ) internal view returns (bytes32 poolId) {\n (poolId) = abi.decode(data, (bytes32));\n\n if (poolId == bytes32(0)) {\n revert EmptyBalancerPoolId();\n }\n\n IBalancerVault bVault = IBalancerVault(balancerVault);\n\n // Note: this reverts if token is not a pool asset\n // slither-disable-next-line unused-return\n bVault.getPoolTokenInfo(poolId, token);\n\n // slither-disable-next-line unused-return\n bVault.getPoolTokenInfo(poolId, baseTokenAddress);\n }\n\n /**\n * @dev Decodes the data passed to get the pool indices and\n * checks it against the Curve Pool to make sure it's\n * not misconfigured. The indices are packed into a single\n * uint256 for gas savings\n *\n * @param data Ecnoded data passed to the `setRewardTokenConfig`\n * @param poolAddress Curve pool address\n * @param token The address of the reward token\n * @return indices Packed pool asset indices\n */\n function _decodeCurvePoolIndices(\n bytes calldata data,\n address poolAddress,\n address token\n ) internal view returns (CurvePoolIndices memory indices) {\n indices = abi.decode(data, (CurvePoolIndices));\n\n ICurvePool pool = ICurvePool(poolAddress);\n if (token != pool.coins(indices.rewardTokenIndex)) {\n revert InvalidCurvePoolAssetIndex(token);\n }\n if (baseTokenAddress != pool.coins(indices.baseTokenIndex)) {\n revert InvalidCurvePoolAssetIndex(baseTokenAddress);\n }\n }\n\n /**\n * @dev Flags a strategy as supported or not supported one\n * @param _strategyAddress Address of the strategy\n * @param _isSupported Bool marking strategy as supported or not supported\n */\n function setSupportedStrategy(address _strategyAddress, bool _isSupported)\n external\n onlyGovernor\n {\n supportedStrategies[_strategyAddress] = _isSupported;\n emit SupportedStrategyUpdate(_strategyAddress, _isSupported);\n }\n\n /***************************************\n Rewards\n ****************************************/\n\n /**\n * @dev Transfer token to governor. Intended for recovering tokens stuck in\n * contract, i.e. mistaken sends.\n * @param _asset Address for the asset\n * @param _amount Amount of the asset to transfer\n */\n function transferToken(address _asset, uint256 _amount)\n external\n onlyGovernor\n {\n IERC20(_asset).safeTransfer(governor(), _amount);\n }\n\n /**\n * @dev Collect reward tokens from a specific strategy and swap them for\n * base token on the configured swap platform. Can be called by anyone.\n * Rewards incentivizing the caller are sent to the caller of this function.\n * @param _strategyAddr Address of the strategy to collect rewards from\n */\n function harvestAndSwap(address _strategyAddr) external nonReentrant {\n // Remember _harvest function checks for the validity of _strategyAddr\n _harvestAndSwap(_strategyAddr, msg.sender);\n }\n\n /**\n * @dev Collect reward tokens from a specific strategy and swap them for\n * base token on the configured swap platform. Can be called by anyone\n * @param _strategyAddr Address of the strategy to collect rewards from\n * @param _rewardTo Address where to send a share of harvest rewards to as an incentive\n * for executing this function\n */\n function harvestAndSwap(address _strategyAddr, address _rewardTo)\n external\n nonReentrant\n {\n // Remember _harvest function checks for the validity of _strategyAddr\n _harvestAndSwap(_strategyAddr, _rewardTo);\n }\n\n /**\n * @dev Collect reward tokens from a specific strategy and swap them for\n * base token on the configured swap platform\n * @param _strategyAddr Address of the strategy to collect rewards from\n * @param _rewardTo Address where to send a share of harvest rewards to as an incentive\n * for executing this function\n */\n function _harvestAndSwap(address _strategyAddr, address _rewardTo)\n internal\n {\n _harvest(_strategyAddr);\n IStrategy strategy = IStrategy(_strategyAddr);\n address[] memory rewardTokens = strategy.getRewardTokenAddresses();\n uint256 len = rewardTokens.length;\n for (uint256 i = 0; i < len; ++i) {\n // This harvester contract is not used anymore. Keeping the code\n // for passing test deployment. Safe to use address(0x1) as oracle.\n _swap(rewardTokens[i], _rewardTo, IOracle(address(0x1)));\n }\n }\n\n /**\n * @dev Collect reward tokens from a specific strategy and swap them for\n * base token on the configured swap platform\n * @param _strategyAddr Address of the strategy to collect rewards from.\n */\n function _harvest(address _strategyAddr) internal virtual {\n if (!supportedStrategies[_strategyAddr]) {\n revert UnsupportedStrategy(_strategyAddr);\n }\n\n IStrategy strategy = IStrategy(_strategyAddr);\n strategy.collectRewardTokens();\n }\n\n /**\n * @dev Swap a reward token for the base token on the configured\n * swap platform. The token must have a registered price feed\n * with the price provider\n * @param _swapToken Address of the token to swap\n * @param _rewardTo Address where to send the share of harvest rewards to\n * @param _priceProvider Oracle to get prices of the swap token\n */\n function _swap(\n address _swapToken,\n address _rewardTo,\n IOracle _priceProvider\n ) internal virtual {\n uint256 balance = IERC20(_swapToken).balanceOf(address(this));\n\n // No need to swap if the reward token is the base token. eg USDT or WETH.\n // There is also no limit on the transfer. Everything in the harvester will be transferred\n // to the Dripper regardless of the liquidationLimit config.\n if (_swapToken == baseTokenAddress) {\n IERC20(_swapToken).safeTransfer(rewardProceedsAddress, balance);\n // currently not paying the farmer any rewards as there is no swap\n emit RewardProceedsTransferred(\n baseTokenAddress,\n address(0),\n balance,\n 0\n );\n return;\n }\n\n RewardTokenConfig memory tokenConfig = rewardTokenConfigs[_swapToken];\n\n /* This will trigger a return when reward token configuration has not yet been set\n * or we have temporarily disabled swapping of specific reward token via setting\n * doSwapRewardToken to false.\n */\n if (!tokenConfig.doSwapRewardToken) {\n return;\n }\n\n if (balance == 0) {\n return;\n }\n\n if (tokenConfig.liquidationLimit > 0) {\n balance = Math.min(balance, tokenConfig.liquidationLimit);\n }\n\n // This'll revert if there is no price feed\n uint256 oraclePrice = _priceProvider.price(_swapToken);\n\n // Oracle price is 1e18\n uint256 minExpected = (balance *\n (1e4 - tokenConfig.allowedSlippageBps) * // max allowed slippage\n oraclePrice).scaleBy(\n baseTokenDecimals,\n Helpers.getDecimals(_swapToken)\n ) /\n 1e4 / // fix the max slippage decimal position\n 1e18; // and oracle price decimals position\n\n // Do the swap\n uint256 amountReceived = _doSwap(\n tokenConfig.swapPlatform,\n tokenConfig.swapPlatformAddr,\n _swapToken,\n balance,\n minExpected\n );\n\n if (amountReceived < minExpected) {\n revert SlippageError(amountReceived, minExpected);\n }\n\n emit RewardTokenSwapped(\n _swapToken,\n baseTokenAddress,\n tokenConfig.swapPlatform,\n balance,\n amountReceived\n );\n\n IERC20 baseToken = IERC20(baseTokenAddress);\n uint256 baseTokenBalance = baseToken.balanceOf(address(this));\n if (baseTokenBalance < amountReceived) {\n // Note: It's possible to bypass this check by transferring `baseToken`\n // directly to Harvester before calling the `harvestAndSwap`. However,\n // there's no incentive for an attacker to do that. Doing a balance diff\n // will increase the gas cost significantly\n revert BalanceMismatchAfterSwap(baseTokenBalance, amountReceived);\n }\n\n // Farmer only gets fee from the base amount they helped farm,\n // They do not get anything from anything that already was there\n // on the Harvester\n uint256 farmerFee = amountReceived.mulTruncateScale(\n tokenConfig.harvestRewardBps,\n 1e4\n );\n uint256 protocolYield = baseTokenBalance - farmerFee;\n\n baseToken.safeTransfer(rewardProceedsAddress, protocolYield);\n baseToken.safeTransfer(_rewardTo, farmerFee);\n emit RewardProceedsTransferred(\n baseTokenAddress,\n _rewardTo,\n protocolYield,\n farmerFee\n );\n }\n\n function _doSwap(\n SwapPlatform swapPlatform,\n address routerAddress,\n address rewardTokenAddress,\n uint256 amountIn,\n uint256 minAmountOut\n ) internal returns (uint256 amountOut) {\n if (swapPlatform == SwapPlatform.UniswapV2Compatible) {\n return\n _swapWithUniswapV2(\n routerAddress,\n rewardTokenAddress,\n amountIn,\n minAmountOut\n );\n } else if (swapPlatform == SwapPlatform.UniswapV3) {\n return\n _swapWithUniswapV3(\n routerAddress,\n rewardTokenAddress,\n amountIn,\n minAmountOut\n );\n } else if (swapPlatform == SwapPlatform.Balancer) {\n return\n _swapWithBalancer(\n routerAddress,\n rewardTokenAddress,\n amountIn,\n minAmountOut\n );\n } else if (swapPlatform == SwapPlatform.Curve) {\n return\n _swapWithCurve(\n routerAddress,\n rewardTokenAddress,\n amountIn,\n minAmountOut\n );\n } else {\n // Should never be invoked since we catch invalid values\n // in the `setRewardTokenConfig` function before it's set\n revert InvalidSwapPlatform(swapPlatform);\n }\n }\n\n /**\n * @dev Swaps the token to `baseToken` with Uniswap V2\n *\n * @param routerAddress Uniswap V2 Router address\n * @param swapToken Address of the tokenIn\n * @param amountIn Amount of `swapToken` to swap\n * @param minAmountOut Minimum expected amount of `baseToken`\n *\n * @return amountOut Amount of `baseToken` received after the swap\n */\n function _swapWithUniswapV2(\n address routerAddress,\n address swapToken,\n uint256 amountIn,\n uint256 minAmountOut\n ) internal returns (uint256 amountOut) {\n address[] memory path = uniswapV2Path[swapToken];\n\n uint256[] memory amounts = IUniswapV2Router(routerAddress)\n .swapExactTokensForTokens(\n amountIn,\n minAmountOut,\n path,\n address(this),\n block.timestamp\n );\n\n amountOut = amounts[amounts.length - 1];\n }\n\n /**\n * @dev Swaps the token to `baseToken` with Uniswap V3\n *\n * @param routerAddress Uniswap V3 Router address\n * @param swapToken Address of the tokenIn\n * @param amountIn Amount of `swapToken` to swap\n * @param minAmountOut Minimum expected amount of `baseToken`\n *\n * @return amountOut Amount of `baseToken` received after the swap\n */\n function _swapWithUniswapV3(\n address routerAddress,\n address swapToken,\n uint256 amountIn,\n uint256 minAmountOut\n ) internal returns (uint256 amountOut) {\n bytes memory path = uniswapV3Path[swapToken];\n\n IUniswapV3Router.ExactInputParams memory params = IUniswapV3Router\n .ExactInputParams({\n path: path,\n recipient: address(this),\n deadline: block.timestamp,\n amountIn: amountIn,\n amountOutMinimum: minAmountOut\n });\n amountOut = IUniswapV3Router(routerAddress).exactInput(params);\n }\n\n /**\n * @dev Swaps the token to `baseToken` on Balancer\n *\n * @param balancerVaultAddress BalancerVaultAddress\n * @param swapToken Address of the tokenIn\n * @param amountIn Amount of `swapToken` to swap\n * @param minAmountOut Minimum expected amount of `baseToken`\n *\n * @return amountOut Amount of `baseToken` received after the swap\n */\n function _swapWithBalancer(\n address balancerVaultAddress,\n address swapToken,\n uint256 amountIn,\n uint256 minAmountOut\n ) internal returns (uint256 amountOut) {\n bytes32 poolId = balancerPoolId[swapToken];\n\n IBalancerVault.SingleSwap memory singleSwap = IBalancerVault\n .SingleSwap({\n poolId: poolId,\n kind: IBalancerVault.SwapKind.GIVEN_IN,\n assetIn: swapToken,\n assetOut: baseTokenAddress,\n amount: amountIn,\n userData: hex\"\"\n });\n\n IBalancerVault.FundManagement memory fundMgmt = IBalancerVault\n .FundManagement({\n sender: address(this),\n fromInternalBalance: false,\n recipient: payable(address(this)),\n toInternalBalance: false\n });\n\n amountOut = IBalancerVault(balancerVaultAddress).swap(\n singleSwap,\n fundMgmt,\n minAmountOut,\n block.timestamp\n );\n }\n\n /**\n * @dev Swaps the token to `baseToken` on Curve\n *\n * @param poolAddress Curve Pool Address\n * @param swapToken Address of the tokenIn\n * @param amountIn Amount of `swapToken` to swap\n * @param minAmountOut Minimum expected amount of `baseToken`\n *\n * @return amountOut Amount of `baseToken` received after the swap\n */\n function _swapWithCurve(\n address poolAddress,\n address swapToken,\n uint256 amountIn,\n uint256 minAmountOut\n ) internal returns (uint256 amountOut) {\n CurvePoolIndices memory indices = curvePoolIndices[swapToken];\n\n // Note: Not all CurvePools return the `amountOut`, make sure\n // to use only pool that do. Otherwise the swap would revert\n // always\n amountOut = ICurvePool(poolAddress).exchange(\n uint256(indices.rewardTokenIndex),\n uint256(indices.baseTokenIndex),\n amountIn,\n minAmountOut\n );\n }\n}\n" + }, + "contracts/harvest/Dripper.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport { Governable } from \"../governance/Governable.sol\";\nimport { IVault } from \"../interfaces/IVault.sol\";\n\n/**\n * @title OUSD Dripper\n *\n * The dripper contract smooths out the yield from point-in-time yield events\n * and spreads the yield out over a configurable time period. This ensures a\n * continuous per block yield to makes users happy as their next rebase\n * amount is always moving up. Also, this makes historical day to day yields\n * smooth, rather than going from a near zero day, to a large APY day, then\n * back to a near zero day again.\n *\n *\n * Design notes\n * - USDT has a smaller resolution than the number of seconds\n * in a week, which can make per second payouts have a rounding error. However\n * the total effect is not large - cents per day, and this money is\n * not lost, just distributed in the future. While we could use a higher\n * decimal precision for the drip perSecond, we chose simpler code.\n * - By calculating the changing drip rates on collects only, harvests and yield\n * events don't have to call anything on this contract or pay any extra gas.\n * Collect() is already be paying for a single write, since it has to reset\n * the lastCollect time.\n * - By having a collectAndRebase method, and having our external systems call\n * that, the OUSD vault does not need any changes, not even to know the address\n * of the dripper.\n * - A rejected design was to retro-calculate the drip rate on each collect,\n * based on the balance at the time of the collect. While this would have\n * required less state, and would also have made the contract respond more quickly\n * to new income, it would break the predictability that is this contract's entire\n * purpose. If we did this, the amount of fundsAvailable() would make sharp increases\n * when funds were deposited.\n * - When the dripper recalculates the rate, it targets spending the balance over\n * the duration. This means that every time that collect is called, if no\n * new funds have been deposited the duration is being pushed back and the\n * rate decreases. This is expected, and ends up following a smoother but\n * longer curve the more collect() is called without incoming yield.\n *\n */\n\ncontract Dripper is Governable {\n using SafeERC20 for IERC20;\n\n struct Drip {\n uint64 lastCollect; // overflows 262 billion years after the sun dies\n uint192 perSecond; // drip rate per second\n }\n\n address immutable vault; // OUSD vault\n address immutable token; // token to drip out\n uint256 public dripDuration; // in seconds\n Drip public drip; // active drip parameters\n\n constructor(address _vault, address _token) {\n vault = _vault;\n token = _token;\n }\n\n /// @notice How much funds have dripped out already and are currently\n // available to be sent to the vault.\n /// @return The amount that would be sent if a collect was called\n function availableFunds() external view returns (uint256) {\n uint256 balance = IERC20(token).balanceOf(address(this));\n return _availableFunds(balance, drip);\n }\n\n /// @notice Collect all dripped funds and send to vault.\n /// Recalculate new drip rate.\n function collect() external {\n _collect();\n }\n\n /// @notice Collect all dripped funds, send to vault, recalculate new drip\n /// rate, and rebase OUSD.\n function collectAndRebase() external {\n _collect();\n IVault(vault).rebase();\n }\n\n /// @dev Change the drip duration. Governor only.\n /// @param _durationSeconds the number of seconds to drip out the entire\n /// balance over if no collects were called during that time.\n function setDripDuration(uint256 _durationSeconds)\n external\n virtual\n onlyGovernor\n {\n require(_durationSeconds > 0, \"duration must be non-zero\");\n dripDuration = _durationSeconds;\n _collect(); // duration change take immediate effect\n }\n\n /// @dev Transfer out ERC20 tokens held by the contract. Governor only.\n /// @param _asset ERC20 token address\n /// @param _amount amount to transfer\n function transferToken(address _asset, uint256 _amount)\n external\n onlyGovernor\n {\n IERC20(_asset).safeTransfer(governor(), _amount);\n }\n\n /// @dev Calculate available funds by taking the lower of either the\n /// currently dripped out funds or the balance available.\n /// Uses passed in parameters to calculate with for gas savings.\n /// @param _balance current balance in contract\n /// @param _drip current drip parameters\n function _availableFunds(uint256 _balance, Drip memory _drip)\n internal\n view\n returns (uint256)\n {\n uint256 elapsed = block.timestamp - _drip.lastCollect;\n uint256 allowed = (elapsed * _drip.perSecond);\n return (allowed > _balance) ? _balance : allowed;\n }\n\n /// @dev Sends the currently dripped funds to be vault, and sets\n /// the new drip rate based on the new balance.\n function _collect() internal virtual {\n // Calculate send\n uint256 balance = IERC20(token).balanceOf(address(this));\n uint256 amountToSend = _availableFunds(balance, drip);\n uint256 remaining = balance - amountToSend;\n // Calculate new drip perSecond\n // Gas savings by setting entire struct at one time\n drip = Drip({\n perSecond: uint192(remaining / dripDuration),\n lastCollect: uint64(block.timestamp)\n });\n // Send funds\n IERC20(token).safeTransfer(vault, amountToSend);\n }\n\n /// @dev Transfer out all ERC20 held by the contract. Governor only.\n /// @param _asset ERC20 token address\n function transferAllToken(address _asset, address _receiver)\n external\n onlyGovernor\n {\n IERC20(_asset).safeTransfer(\n _receiver,\n IERC20(_asset).balanceOf(address(this))\n );\n }\n}\n" + }, + "contracts/harvest/FixedRateDripper.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport { IVault } from \"../interfaces/IVault.sol\";\nimport { Dripper } from \"./Dripper.sol\";\n\n/**\n * @title Fixed Rate Dripper\n *\n * Similar to the Dripper, Fixed Rate Dripper drips out yield per second.\n * However the Strategist decides the rate and it doesn't change after\n * a drip.\n *\n */\n\ncontract FixedRateDripper is Dripper {\n using SafeERC20 for IERC20;\n\n event DripRateUpdated(uint192 oldDripRate, uint192 newDripRate);\n\n /**\n * @dev Verifies that the caller is the Governor or Strategist.\n */\n modifier onlyGovernorOrStrategist() {\n require(\n isGovernor() || msg.sender == IVault(vault).strategistAddr(),\n \"Caller is not the Strategist or Governor\"\n );\n _;\n }\n\n constructor(address _vault, address _token) Dripper(_vault, _token) {}\n\n /// @inheritdoc Dripper\n function setDripDuration(uint256) external virtual override {\n // Not used in FixedRateDripper\n revert(\"Drip duration disabled\");\n }\n\n /// @inheritdoc Dripper\n function _collect() internal virtual override {\n // Calculate amount to send\n uint256 balance = IERC20(token).balanceOf(address(this));\n uint256 amountToSend = _availableFunds(balance, drip);\n\n // Update timestamp\n drip.lastCollect = uint64(block.timestamp);\n\n // Send funds\n IERC20(token).safeTransfer(vault, amountToSend);\n }\n\n /**\n * @dev Sets the drip rate. Callable by Strategist or Governor.\n * Can be set to zero to stop dripper.\n * @param _perSecond Rate of WETH to drip per second\n */\n function setDripRate(uint192 _perSecond) external onlyGovernorOrStrategist {\n emit DripRateUpdated(_perSecond, drip.perSecond);\n\n /**\n * Note: It's important to call `_collect` before updating\n * the drip rate especially on a new proxy contract.\n * When `lastCollect` is not set/initialized, the elapsed\n * time would be calculated as `block.number` seconds,\n * resulting in a huge yield, if `collect` isn't called first.\n */\n // Collect at existing rate\n _collect();\n\n // Update rate\n drip.perSecond = _perSecond;\n }\n}\n" + }, + "contracts/harvest/Harvester.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { AbstractHarvester } from \"./AbstractHarvester.sol\";\n\ncontract Harvester is AbstractHarvester {\n constructor(address _vault, address _usdtAddress)\n AbstractHarvester(_vault, _usdtAddress)\n {}\n}\n" + }, + "contracts/harvest/OETHBaseHarvester.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { Governable } from \"../governance/Governable.sol\";\n\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\n\nimport { IVault } from \"../interfaces/IVault.sol\";\nimport { IStrategy } from \"../interfaces/IStrategy.sol\";\nimport { ISwapRouter } from \"../interfaces/aerodrome/ISwapRouter.sol\";\n\ncontract OETHBaseHarvester is Governable {\n using SafeERC20 for IERC20;\n\n IVault public immutable vault;\n IStrategy public immutable amoStrategy;\n IERC20 public immutable aero;\n IERC20 public immutable weth;\n ISwapRouter public immutable swapRouter;\n\n address public operatorAddr;\n\n // Similar sig to `AbstractHarvester.RewardTokenSwapped` for\n // future compatibility with monitoring\n event RewardTokenSwapped(\n address indexed rewardToken,\n address indexed swappedInto,\n uint8 swapPlatform,\n uint256 amountIn,\n uint256 amountOut\n );\n\n event OperatorChanged(address oldOperator, address newOperator);\n event YieldSent(address recipient, uint256 yield, uint256 fee);\n\n /**\n * @notice Verifies that the caller is either Governor or Strategist.\n */\n modifier onlyGovernorOrStrategist() {\n require(\n msg.sender == vault.strategistAddr() || isGovernor(),\n \"Caller is not the Strategist or Governor\"\n );\n _;\n }\n\n /**\n * @notice Verifies that the caller is either Governor or Strategist.\n */\n modifier onlyGovernorOrStrategistOrOperator() {\n require(\n msg.sender == operatorAddr ||\n msg.sender == vault.strategistAddr() ||\n isGovernor(),\n \"Caller is not the Operator or Strategist or Governor\"\n );\n _;\n }\n\n constructor(\n address _vault,\n address _amoStrategy,\n address _aero,\n address _weth,\n address _swapRouter\n ) {\n vault = IVault(_vault);\n amoStrategy = IStrategy(_amoStrategy);\n aero = IERC20(_aero);\n weth = IERC20(_weth);\n swapRouter = ISwapRouter(_swapRouter);\n }\n\n /**\n * @dev Changes the operator address which can call `harvest`\n * @param _operatorAddr New operator address\n */\n function setOperatorAddr(address _operatorAddr) external onlyGovernor {\n emit OperatorChanged(operatorAddr, _operatorAddr);\n operatorAddr = _operatorAddr;\n }\n\n /**\n * @notice Collects AERO from AMO strategy and\n * sends it to the Strategist multisig.\n * Anyone can call it.\n */\n function harvest() external onlyGovernorOrStrategistOrOperator {\n address strategistAddr = vault.strategistAddr();\n require(strategistAddr != address(0), \"Guardian address not set\");\n\n // Collect all AERO\n amoStrategy.collectRewardTokens();\n\n uint256 aeroBalance = aero.balanceOf(address(this));\n if (aeroBalance == 0) {\n // Do nothing if there's no AERO to transfer\n return;\n }\n\n // Transfer everything to Strategist\n aero.safeTransfer(strategistAddr, aeroBalance);\n }\n\n /**\n * @notice Harvests AERO from AMO strategy and then swaps some (or all)\n * of it into WETH to distribute yield and fee.\n * When `feeBps` is set to 10000 (100%), all WETH received is\n * sent to strategist.\n *\n * @param aeroToSwap Amount of AERO to swap\n * @param minWETHExpected Min. amount of WETH to expect\n * @param feeBps Performance fee bps (Sent to strategist)\n * @param sendYieldToDripper Sends yield to Dripper, if set to true.\n * Otherwise, to the Guardian\n */\n function harvestAndSwap(\n uint256 aeroToSwap,\n uint256 minWETHExpected,\n uint256 feeBps,\n bool sendYieldToDripper\n ) external onlyGovernorOrStrategist {\n address strategistAddr = vault.strategistAddr();\n require(strategistAddr != address(0), \"Guardian address not set\");\n\n // Yields can either be sent to the Dripper or Strategist\n address yieldRecipient = sendYieldToDripper\n ? vault.dripper()\n : strategistAddr;\n require(yieldRecipient != address(0), \"Yield recipient not set\");\n\n require(feeBps <= 10000, \"Invalid Fee Bps\");\n\n // Collect all AERO\n amoStrategy.collectRewardTokens();\n\n uint256 aeroBalance = aero.balanceOf(address(this));\n if (aeroBalance == 0) {\n // Do nothing if there's no AERO to transfer/swap\n return;\n }\n\n if (aeroToSwap > 0) {\n if (aeroBalance < aeroToSwap) {\n // Transfer in balance from the multisig as needed\n // slither-disable-next-line unchecked-transfer arbitrary-send-erc20\n aero.safeTransferFrom(\n strategistAddr,\n address(this),\n aeroToSwap - aeroBalance\n );\n }\n\n _doSwap(aeroToSwap, minWETHExpected);\n\n // Figure out AERO left in contract after swap\n aeroBalance = aero.balanceOf(address(this));\n }\n\n // Transfer out any leftover AERO after swap\n if (aeroBalance > 0) {\n aero.safeTransfer(strategistAddr, aeroBalance);\n }\n\n // Computes using all balance the contract holds,\n // not just the WETH received from swap. Use `transferToken`\n // if there's any WETH left that needs to be taken out\n uint256 availableWETHBalance = weth.balanceOf(address(this));\n // Computation rounds in favor of protocol\n uint256 fee = (availableWETHBalance * feeBps) / 10000;\n uint256 yield = availableWETHBalance - fee;\n\n // Transfer yield, if any\n if (yield > 0) {\n weth.safeTransfer(yieldRecipient, yield);\n }\n\n // Transfer fee to the Guardian, if any\n if (fee > 0) {\n weth.safeTransfer(strategistAddr, fee);\n }\n\n emit YieldSent(yieldRecipient, yield, fee);\n }\n\n /**\n * @notice Swaps AERO to WETH on Aerodrome\n * @param aeroToSwap Amount of AERO to swap\n * @param minWETHExpected Min. amount of WETH to expect\n */\n function _doSwap(uint256 aeroToSwap, uint256 minWETHExpected) internal {\n // Let the swap router move funds\n aero.approve(address(swapRouter), aeroToSwap);\n\n // Do the swap\n uint256 wethReceived = swapRouter.exactInputSingle(\n ISwapRouter.ExactInputSingleParams({\n tokenIn: address(aero),\n tokenOut: address(weth),\n tickSpacing: 200, // From AERO/WETH pool contract\n recipient: address(this),\n deadline: block.timestamp,\n amountIn: aeroToSwap,\n amountOutMinimum: minWETHExpected,\n sqrtPriceLimitX96: 0\n })\n );\n\n emit RewardTokenSwapped(\n address(aero),\n address(weth),\n 0,\n aeroToSwap,\n wethReceived\n );\n }\n\n /**\n * @notice Transfer token to governor. Intended for recovering tokens stuck in\n * the contract, i.e. mistaken sends.\n * Also, allows to transfer any AERO left in the contract.\n * @param _asset Address for the asset\n * @param _amount Amount of the asset to transfer\n */\n function transferToken(address _asset, uint256 _amount)\n external\n virtual\n onlyGovernor\n {\n IERC20(_asset).safeTransfer(governor(), _amount);\n }\n}\n" + }, + "contracts/harvest/OETHDripper.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { Dripper } from \"./Dripper.sol\";\n\n/**\n * @title OETH Dripper Contract\n * @author Origin Protocol Inc\n */\ncontract OETHDripper is Dripper {\n constructor(address _vault, address _token) Dripper(_vault, _token) {}\n}\n" + }, + "contracts/harvest/OETHFixedRateDripper.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { FixedRateDripper } from \"./FixedRateDripper.sol\";\n\n/**\n * @title OETH FixedRateDripper Contract\n * @author Origin Protocol Inc\n */\ncontract OETHFixedRateDripper is FixedRateDripper {\n constructor(address _vault, address _token)\n FixedRateDripper(_vault, _token)\n {}\n}\n" + }, + "contracts/harvest/OETHHarvester.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { AbstractHarvester } from \"./AbstractHarvester.sol\";\n\ncontract OETHHarvester is AbstractHarvester {\n constructor(address _vault, address _wethAddress)\n AbstractHarvester(_vault, _wethAddress)\n {}\n}\n" + }, + "contracts/harvest/OETHHarvesterSimple.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { Strategizable } from \"../governance/Strategizable.sol\";\nimport { IStrategy } from \"../interfaces/IStrategy.sol\";\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport { Initializable } from \"../utils/Initializable.sol\";\n\n/// @title OETH Harvester Simple Contract\n/// @notice Contract to harvest rewards from strategies\n/// @author Origin Protocol Inc\ncontract OETHHarvesterSimple is Initializable, Strategizable {\n using SafeERC20 for IERC20;\n\n ////////////////////////////////////////////////////\n /// --- CONSTANTS & IMMUTABLES\n ////////////////////////////////////////////////////\n /// @notice wrapped native token address (WETH or wS)\n address public immutable wrappedNativeToken;\n\n ////////////////////////////////////////////////////\n /// --- STORAGE\n ////////////////////////////////////////////////////\n /// @notice Dripper address\n address public dripper;\n\n /// @notice Mapping of supported strategies\n mapping(address => bool) public supportedStrategies;\n\n /// @notice Gap for upgrade safety\n uint256[48] private ___gap;\n\n ////////////////////////////////////////////////////\n /// --- EVENTS\n ////////////////////////////////////////////////////\n event Harvested(\n address indexed strategy,\n address token,\n uint256 amount,\n address indexed receiver\n );\n event SupportedStrategyUpdated(address strategy, bool status);\n event DripperUpdated(address dripper);\n\n ////////////////////////////////////////////////////\n /// --- CONSTRUCTOR\n ////////////////////////////////////////////////////\n constructor(address _wrappedNativeToken) {\n wrappedNativeToken = _wrappedNativeToken;\n\n // prevent implementation contract to be governed\n _setGovernor(address(0));\n }\n\n /// @notice Initialize the contract\n function initialize() external onlyGovernor initializer {\n // Call it to set `initialized` to true and to prevent the implementation\n // from getting initialized in future through the proxy\n }\n\n ////////////////////////////////////////////////////\n /// --- MUTATIVE FUNCTIONS\n ////////////////////////////////////////////////////\n /// @notice Harvest rewards from a strategy and transfer to strategist or dripper\n /// @param _strategy Address of the strategy to harvest\n function harvestAndTransfer(address _strategy) external {\n _harvestAndTransfer(_strategy);\n }\n\n /// @notice Harvest rewards from multiple strategies and transfer to strategist or dripper\n /// @param _strategies Array of strategy addresses to harvest\n function harvestAndTransfer(address[] calldata _strategies) external {\n for (uint256 i = 0; i < _strategies.length; i++) {\n _harvestAndTransfer(_strategies[i]);\n }\n }\n\n /// @notice Internal logic to harvest rewards from a strategy\n function _harvestAndTransfer(address _strategy) internal virtual {\n // Ensure strategy is supported\n require(supportedStrategies[_strategy], \"Strategy not supported\");\n\n // Store locally for some gas savings\n address _strategist = strategistAddr;\n address _dripper = dripper;\n\n // Harvest rewards\n IStrategy(_strategy).collectRewardTokens();\n\n // Cache reward tokens\n address[] memory rewardTokens = IStrategy(_strategy)\n .getRewardTokenAddresses();\n\n uint256 len = rewardTokens.length;\n for (uint256 i = 0; i < len; i++) {\n // Cache balance\n address token = rewardTokens[i];\n uint256 balance = IERC20(token).balanceOf(address(this));\n if (balance > 0) {\n // Determine receiver\n address receiver = token == wrappedNativeToken\n ? _dripper\n : _strategist;\n require(receiver != address(0), \"Invalid receiver\");\n\n // Transfer to the Strategist or the Dripper\n IERC20(token).safeTransfer(receiver, balance);\n emit Harvested(_strategy, token, balance, receiver);\n }\n }\n }\n\n ////////////////////////////////////////////////////\n /// --- GOVERNANCE\n ////////////////////////////////////////////////////\n /// @notice Set supported strategy\n /// @param _strategy Address of the strategy\n /// @param _isSupported Boolean indicating if strategy is supported\n function setSupportedStrategy(address _strategy, bool _isSupported)\n external\n onlyGovernorOrStrategist\n {\n require(_strategy != address(0), \"Invalid strategy\");\n supportedStrategies[_strategy] = _isSupported;\n emit SupportedStrategyUpdated(_strategy, _isSupported);\n }\n\n /// @notice Transfer tokens to strategist\n /// @param _asset Address of the token\n /// @param _amount Amount of tokens to transfer\n function transferToken(address _asset, uint256 _amount)\n external\n onlyGovernorOrStrategist\n {\n IERC20(_asset).safeTransfer(strategistAddr, _amount);\n }\n\n /// @notice Set the dripper address\n /// @param _dripper Address of the dripper\n function setDripper(address _dripper) external onlyGovernor {\n _setDripper(_dripper);\n }\n\n /// @notice Internal logic to set the dripper address\n function _setDripper(address _dripper) internal {\n require(_dripper != address(0), \"Invalid dripper\");\n dripper = _dripper;\n emit DripperUpdated(_dripper);\n }\n}\n" + }, + "contracts/harvest/OSonicHarvester.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { SuperOETHHarvester } from \"./SuperOETHHarvester.sol\";\n\ncontract OSonicHarvester is SuperOETHHarvester {\n /// @param _wrappedNativeToken Address of the native Wrapped S (wS) token\n constructor(address _wrappedNativeToken)\n SuperOETHHarvester(_wrappedNativeToken)\n {}\n}\n" + }, + "contracts/harvest/SuperOETHHarvester.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { OETHHarvesterSimple, IERC20, IStrategy, SafeERC20 } from \"./OETHHarvesterSimple.sol\";\n\ncontract SuperOETHHarvester is OETHHarvesterSimple {\n using SafeERC20 for IERC20;\n\n constructor(address _wrappedNativeToken)\n OETHHarvesterSimple(_wrappedNativeToken)\n {}\n\n /// @inheritdoc OETHHarvesterSimple\n function _harvestAndTransfer(address _strategy) internal virtual override {\n // Ensure strategy is supported\n require(supportedStrategies[_strategy], \"Strategy not supported\");\n\n address receiver = strategistAddr;\n require(receiver != address(0), \"Invalid receiver\");\n\n // Harvest rewards\n IStrategy(_strategy).collectRewardTokens();\n\n // Cache reward tokens\n address[] memory rewardTokens = IStrategy(_strategy)\n .getRewardTokenAddresses();\n\n uint256 len = rewardTokens.length;\n for (uint256 i = 0; i < len; i++) {\n // Cache balance\n address token = rewardTokens[i];\n uint256 balance = IERC20(token).balanceOf(address(this));\n if (balance > 0) {\n // Transfer everything to the strategist\n IERC20(token).safeTransfer(receiver, balance);\n emit Harvested(_strategy, token, balance, receiver);\n }\n }\n }\n}\n" + }, + "contracts/interfaces/aerodrome/ICLGauge.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity >=0.8.0;\n\ninterface ICLGauge {\n /// @notice Returns the claimable rewards for a given account and tokenId\n /// @dev Throws if account is not the position owner\n /// @dev pool.updateRewardsGrowthGlobal() needs to be called first, to return the correct claimable rewards\n /// @param account The address of the user\n /// @param tokenId The tokenId of the position\n /// @return The amount of claimable reward\n function earned(address account, uint256 tokenId)\n external\n view\n returns (uint256);\n\n /// @notice Retrieve rewards for all tokens owned by an account\n /// @dev Throws if not called by the voter\n /// @param account The account of the user\n function getReward(address account) external;\n\n /// @notice Retrieve rewards for a tokenId\n /// @dev Throws if not called by the position owner\n /// @param tokenId The tokenId of the position\n function getReward(uint256 tokenId) external;\n\n /// @notice Notifies gauge of gauge rewards.\n /// @param amount Amount of gauge rewards (emissions) to notify. Must be greater than 604_800.\n function notifyRewardAmount(uint256 amount) external;\n\n /// @dev Notifies gauge of gauge rewards without distributing its fees.\n /// Assumes gauge reward tokens is 18 decimals.\n /// If not 18 decimals, rewardRate may have rounding issues.\n /// @param amount Amount of gauge rewards (emissions) to notify. Must be greater than 604_800.\n function notifyRewardWithoutClaim(uint256 amount) external;\n\n /// @notice Used to deposit a CL position into the gauge\n /// @notice Allows the user to receive emissions instead of fees\n /// @param tokenId The tokenId of the position\n function deposit(uint256 tokenId) external;\n\n /// @notice Used to withdraw a CL position from the gauge\n /// @notice Allows the user to receive fees instead of emissions\n /// @notice Outstanding emissions will be collected on withdrawal\n /// @param tokenId The tokenId of the position\n function withdraw(uint256 tokenId) external;\n\n // /// @notice Fetch all tokenIds staked by a given account\n // /// @param depositor The address of the user\n // /// @return The tokenIds of the staked positions\n // function stakedValues(address depositor) external view returns (uint256[] memory);\n\n // /// @notice Fetch a staked tokenId by index\n // /// @param depositor The address of the user\n // /// @param index The index of the staked tokenId\n // /// @return The tokenId of the staked position\n // function stakedByIndex(address depositor, uint256 index) external view returns (uint256);\n\n // /// @notice Check whether a position is staked in the gauge by a certain user\n // /// @param depositor The address of the user\n // /// @param tokenId The tokenId of the position\n // /// @return Whether the position is staked in the gauge\n // function stakedContains(address depositor, uint256 tokenId) external view returns (bool);\n\n // /// @notice The amount of positions staked in the gauge by a certain user\n // /// @param depositor The address of the user\n // /// @return The amount of positions staked in the gauge\n // function stakedLength(address depositor) external view returns (uint256);\n\n function feesVotingReward() external view returns (address);\n}\n" + }, + "contracts/interfaces/aerodrome/ICLPool.sol": { + "content": "pragma solidity >=0.5.0;\n\n/// @title The interface for a CL Pool\n/// @notice A CL pool facilitates swapping and automated market making between any two assets that strictly conform\n/// to the ERC20 specification\n/// @dev The pool interface is broken up into many smaller pieces\ninterface ICLPool {\n function slot0()\n external\n view\n returns (\n uint160 sqrtPriceX96,\n int24 tick,\n uint16 observationIndex,\n uint16 observationCardinality,\n uint16 observationCardinalityNext,\n bool unlocked\n );\n\n /// @notice The first of the two tokens of the pool, sorted by address\n /// @return The token contract address\n function token0() external view returns (address);\n\n /// @notice The second of the two tokens of the pool, sorted by address\n /// @return The token contract address\n function token1() external view returns (address);\n\n function tickSpacing() external view returns (int24);\n\n /// @notice The gauge corresponding to this pool\n /// @return The gauge contract address\n function gauge() external view returns (address);\n\n /// @notice The currently in range liquidity available to the pool\n /// @dev This value has no relationship to the total liquidity across all ticks\n /// @dev This value includes staked liquidity\n function liquidity() external view returns (uint128);\n\n /// @notice Look up information about a specific tick in the pool\n /// @param tick The tick to look up\n /// @return liquidityGross the total amount of position liquidity that uses the pool either as tick lower or\n /// tick upper,\n /// liquidityNet how much liquidity changes when the pool price crosses the tick,\n /// feeGrowthOutside0X128 the fee growth on the other side of the tick from the current tick in token0,\n /// feeGrowthOutside1X128 the fee growth on the other side of the tick from the current tick in token1,\n /// tickCumulativeOutside the cumulative tick value on the other side of the tick from the current tick\n /// secondsPerLiquidityOutsideX128 the seconds spent per liquidity on the other side of the tick from\n /// the current tick,\n /// secondsOutside the seconds spent on the other side of the tick from the current tick,\n /// initialized Set to true if the tick is initialized, i.e. liquidityGross is greater than 0, otherwise\n /// equal to false.\n /// Outside values can only be used if the tick is initialized, i.e. if liquidityGross is greater than 0.\n /// In addition, these values are only relative and must be used only in comparison to previous snapshots for\n /// a specific position.\n function ticks(int24 tick)\n external\n view\n returns (\n uint128 liquidityGross,\n int128 liquidityNet,\n uint256 feeGrowthOutside0X128,\n uint256 feeGrowthOutside1X128,\n int56 tickCumulativeOutside,\n uint160 secondsPerLiquidityOutsideX128,\n uint32 secondsOutside,\n bool initialized\n );\n}\n" + }, + "contracts/interfaces/aerodrome/INonfungiblePositionManager.sol": { + "content": "// SPDX-License-Identifier: GPL-2.0-or-later\npragma solidity >=0.7.5;\npragma abicoder v2;\n\n/// @title Non-fungible token for positions\n/// @notice Wraps CL positions in a non-fungible token interface which allows for them to be transferred\n/// and authorized.\n// slither-disable-start erc20-interface\ninterface INonfungiblePositionManager {\n /**\n * @dev See {IERC721-approve}.\n */\n function approve(address to, uint256 tokenId) external;\n\n /**\n * @dev See {IERC721-getApproved}.\n */\n function getApproved(uint256 tokenId) external returns (address);\n\n /**\n * @dev See {IERC721-ownerOf}.\n */\n function ownerOf(uint256 tokenId) external view returns (address);\n\n /// @notice Returns the position information associated with a given token ID.\n /// @dev Throws if the token ID is not valid.\n /// @param tokenId The ID of the token that represents the position\n /// @return nonce The nonce for permits\n /// @return operator The address that is approved for spending\n /// @return token0 The address of the token0 for a specific pool\n /// @return token1 The address of the token1 for a specific pool\n /// @return tickSpacing The tick spacing associated with the pool\n /// @return tickLower The lower end of the tick range for the position\n /// @return tickUpper The higher end of the tick range for the position\n /// @return liquidity The liquidity of the position\n /// @return feeGrowthInside0LastX128 The fee growth of token0 as of the last action on the individual position\n /// @return feeGrowthInside1LastX128 The fee growth of token1 as of the last action on the individual position\n /// @return tokensOwed0 The uncollected amount of token0 owed to the position as of the last computation\n /// @return tokensOwed1 The uncollected amount of token1 owed to the position as of the last computation\n function positions(uint256 tokenId)\n external\n view\n returns (\n uint96 nonce,\n address operator,\n address token0,\n address token1,\n int24 tickSpacing,\n int24 tickLower,\n int24 tickUpper,\n uint128 liquidity,\n uint256 feeGrowthInside0LastX128,\n uint256 feeGrowthInside1LastX128,\n uint128 tokensOwed0,\n uint128 tokensOwed1\n );\n\n struct MintParams {\n address token0;\n address token1;\n int24 tickSpacing;\n int24 tickLower;\n int24 tickUpper;\n uint256 amount0Desired;\n uint256 amount1Desired;\n uint256 amount0Min;\n uint256 amount1Min;\n address recipient;\n uint256 deadline;\n uint160 sqrtPriceX96;\n }\n\n /// @notice Creates a new position wrapped in a NFT\n /// @dev Call this when the pool does exist and is initialized. Note that if the pool is created but not initialized\n /// a method does not exist, i.e. the pool is assumed to be initialized.\n /// @param params The params necessary to mint a position, encoded as `MintParams` in calldata\n /// @return tokenId The ID of the token that represents the minted position\n /// @return liquidity The amount of liquidity for this position\n /// @return amount0 The amount of token0\n /// @return amount1 The amount of token1\n function mint(MintParams calldata params)\n external\n payable\n returns (\n uint256 tokenId,\n uint128 liquidity,\n uint256 amount0,\n uint256 amount1\n );\n\n struct IncreaseLiquidityParams {\n uint256 tokenId;\n uint256 amount0Desired;\n uint256 amount1Desired;\n uint256 amount0Min;\n uint256 amount1Min;\n uint256 deadline;\n }\n\n /// @notice Increases the amount of liquidity in a position, with tokens paid by the `msg.sender`\n /// @param params tokenId The ID of the token for which liquidity is being increased,\n /// amount0Desired The desired amount of token0 to be spent,\n /// amount1Desired The desired amount of token1 to be spent,\n /// amount0Min The minimum amount of token0 to spend, which serves as a slippage check,\n /// amount1Min The minimum amount of token1 to spend, which serves as a slippage check,\n /// deadline The time by which the transaction must be included to effect the change\n /// @return liquidity The new liquidity amount as a result of the increase\n /// @return amount0 The amount of token0 to acheive resulting liquidity\n /// @return amount1 The amount of token1 to acheive resulting liquidity\n function increaseLiquidity(IncreaseLiquidityParams calldata params)\n external\n payable\n returns (\n uint128 liquidity,\n uint256 amount0,\n uint256 amount1\n );\n\n struct DecreaseLiquidityParams {\n uint256 tokenId;\n uint128 liquidity;\n uint256 amount0Min;\n uint256 amount1Min;\n uint256 deadline;\n }\n\n /// @notice Decreases the amount of liquidity in a position and accounts it to the position\n /// @param params tokenId The ID of the token for which liquidity is being decreased,\n /// amount The amount by which liquidity will be decreased,\n /// amount0Min The minimum amount of token0 that should be accounted for the burned liquidity,\n /// amount1Min The minimum amount of token1 that should be accounted for the burned liquidity,\n /// deadline The time by which the transaction must be included to effect the change\n /// @return amount0 The amount of token0 accounted to the position's tokens owed\n /// @return amount1 The amount of token1 accounted to the position's tokens owed\n /// @dev The use of this function can cause a loss to users of the NonfungiblePositionManager\n /// @dev for tokens that have very high decimals.\n /// @dev The amount of tokens necessary for the loss is: 3.4028237e+38.\n /// @dev This is equivalent to 1e20 value with 18 decimals.\n function decreaseLiquidity(DecreaseLiquidityParams calldata params)\n external\n payable\n returns (uint256 amount0, uint256 amount1);\n\n struct CollectParams {\n uint256 tokenId;\n address recipient;\n uint128 amount0Max;\n uint128 amount1Max;\n }\n\n /// @notice Collects up to a maximum amount of fees owed to a specific position to the recipient\n /// @notice Used to update staked positions before deposit and withdraw\n /// @param params tokenId The ID of the NFT for which tokens are being collected,\n /// recipient The account that should receive the tokens,\n /// amount0Max The maximum amount of token0 to collect,\n /// amount1Max The maximum amount of token1 to collect\n /// @return amount0 The amount of fees collected in token0\n /// @return amount1 The amount of fees collected in token1\n function collect(CollectParams calldata params)\n external\n payable\n returns (uint256 amount0, uint256 amount1);\n\n /// @notice Burns a token ID, which deletes it from the NFT contract. The token must have 0 liquidity and all tokens\n /// must be collected first.\n /// @param tokenId The ID of the token that is being burned\n function burn(uint256 tokenId) external payable;\n\n /// @notice Sets a new Token Descriptor\n /// @param _tokenDescriptor Address of the new Token Descriptor to be chosen\n function setTokenDescriptor(address _tokenDescriptor) external;\n\n /// @notice Sets a new Owner address\n /// @param _owner Address of the new Owner to be chosen\n function setOwner(address _owner) external;\n}\n// slither-disable-end erc20-interface\n" + }, + "contracts/interfaces/aerodrome/ISugarHelper.sol": { + "content": "// SPDX-License-Identifier: GPL-2.0-or-later\npragma solidity >=0.5.0;\npragma abicoder v2;\n\nimport { INonfungiblePositionManager } from \"./INonfungiblePositionManager.sol\";\n\ninterface ISugarHelper {\n struct PopulatedTick {\n int24 tick;\n uint160 sqrtRatioX96;\n int128 liquidityNet;\n uint128 liquidityGross;\n }\n\n ///\n /// Wrappers for LiquidityAmounts\n ///\n\n function getAmountsForLiquidity(\n uint160 sqrtRatioX96,\n uint160 sqrtRatioAX96,\n uint160 sqrtRatioBX96,\n uint128 liquidity\n ) external pure returns (uint256 amount0, uint256 amount1);\n\n function getLiquidityForAmounts(\n uint256 amount0,\n uint256 amount1,\n uint160 sqrtRatioX96,\n uint160 sqrtRatioAX96,\n uint160 sqrtRatioBX96\n ) external pure returns (uint128 liquidity);\n\n /// @notice Computes the amount of token0 for a given amount of token1 and price range\n /// @param amount1 Amount of token1 to estimate liquidity\n /// @param pool Address of the pool to be used\n /// @param sqrtRatioX96 A sqrt price representing the current pool prices\n /// @param tickLow Lower tick boundary\n /// @param tickLow Upper tick boundary\n /// @dev If the given pool address is not the zero address, will fetch `sqrtRatioX96` from pool\n /// @return amount0 Estimated amount of token0\n function estimateAmount0(\n uint256 amount1,\n address pool,\n uint160 sqrtRatioX96,\n int24 tickLow,\n int24 tickHigh\n ) external view returns (uint256 amount0);\n\n /// @notice Computes the amount of token1 for a given amount of token0 and price range\n /// @param amount0 Amount of token0 to estimate liquidity\n /// @param pool Address of the pool to be used\n /// @param sqrtRatioX96 A sqrt price representing the current pool prices\n /// @param tickLow Lower tick boundary\n /// @param tickLow Upper tick boundary\n /// @dev If the given pool address is not the zero address, will fetch `sqrtRatioX96` from pool\n /// @return amount1 Estimated amount of token1\n function estimateAmount1(\n uint256 amount0,\n address pool,\n uint160 sqrtRatioX96,\n int24 tickLow,\n int24 tickHigh\n ) external view returns (uint256 amount1);\n\n ///\n /// Wrappers for PositionValue\n ///\n\n function principal(\n INonfungiblePositionManager positionManager,\n uint256 tokenId,\n uint160 sqrtRatioX96\n ) external view returns (uint256 amount0, uint256 amount1);\n\n function fees(INonfungiblePositionManager positionManager, uint256 tokenId)\n external\n view\n returns (uint256 amount0, uint256 amount1);\n\n ///\n /// Wrappers for TickMath\n ///\n\n function getSqrtRatioAtTick(int24 tick)\n external\n pure\n returns (uint160 sqrtRatioX96);\n\n function getTickAtSqrtRatio(uint160 sqrtRatioX96)\n external\n pure\n returns (int24 tick);\n\n /// @notice Fetches Tick Data for all populated Ticks in given bitmaps\n /// @param pool Address of the pool from which to fetch data\n /// @param startTick Tick from which the first bitmap will be fetched\n /// @dev The number of bitmaps fetched by this function should always be `MAX_BITMAPS`,\n /// unless there are less than `MAX_BITMAPS` left to iterate through\n /// @return populatedTicks Array of all Populated Ticks in the provided bitmaps\n function getPopulatedTicks(address pool, int24 startTick)\n external\n view\n returns (PopulatedTick[] memory populatedTicks);\n}\n" + }, + "contracts/interfaces/aerodrome/ISwapRouter.sol": { + "content": "// SPDX-License-Identifier: GPL-2.0-or-later\npragma solidity >=0.7.5;\npragma abicoder v2;\n\n/// @title Router token swapping functionality\n/// @notice Functions for swapping tokens via CL\ninterface ISwapRouter {\n struct ExactInputSingleParams {\n address tokenIn;\n address tokenOut;\n int24 tickSpacing;\n address recipient;\n uint256 deadline;\n uint256 amountIn;\n uint256 amountOutMinimum;\n uint160 sqrtPriceLimitX96;\n }\n\n /// @notice Swaps `amountIn` of one token for as much as possible of another token\n /// @param params The parameters necessary for the swap, encoded as `ExactInputSingleParams` in calldata\n /// @return amountOut The amount of the received token\n function exactInputSingle(ExactInputSingleParams calldata params)\n external\n payable\n returns (uint256 amountOut);\n\n struct ExactInputParams {\n bytes path;\n address recipient;\n uint256 deadline;\n uint256 amountIn;\n uint256 amountOutMinimum;\n }\n\n /// @notice Swaps `amountIn` of one token for as much as possible of another along the specified path\n /// @param params The parameters necessary for the multi-hop swap, encoded as `ExactInputParams` in calldata\n /// @return amountOut The amount of the received token\n function exactInput(ExactInputParams calldata params)\n external\n payable\n returns (uint256 amountOut);\n\n struct ExactOutputSingleParams {\n address tokenIn;\n address tokenOut;\n int24 tickSpacing;\n address recipient;\n uint256 deadline;\n uint256 amountOut;\n uint256 amountInMaximum;\n uint160 sqrtPriceLimitX96;\n }\n\n /// @notice Swaps as little as possible of one token for `amountOut` of another token\n /// @param params The parameters necessary for the swap, encoded as `ExactOutputSingleParams` in calldata\n /// @return amountIn The amount of the input token\n function exactOutputSingle(ExactOutputSingleParams calldata params)\n external\n payable\n returns (uint256 amountIn);\n\n struct ExactOutputParams {\n bytes path;\n address recipient;\n uint256 deadline;\n uint256 amountOut;\n uint256 amountInMaximum;\n }\n\n /// @notice Swaps as little as possible of one token for `amountOut` of another along the specified path (reversed)\n /// @param params The parameters necessary for the multi-hop swap, encoded as `ExactOutputParams` in calldata\n /// @return amountIn The amount of the input token\n function exactOutput(ExactOutputParams calldata params)\n external\n payable\n returns (uint256 amountIn);\n}\n" + }, + "contracts/interfaces/balancer/IBalancerVault.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport { IERC20 } from \"../../utils/InitializableAbstractStrategy.sol\";\n\ninterface IBalancerVault {\n enum WeightedPoolJoinKind {\n INIT,\n EXACT_TOKENS_IN_FOR_BPT_OUT,\n TOKEN_IN_FOR_EXACT_BPT_OUT,\n ALL_TOKENS_IN_FOR_EXACT_BPT_OUT,\n ADD_TOKEN\n }\n\n enum WeightedPoolExitKind {\n EXACT_BPT_IN_FOR_ONE_TOKEN_OUT,\n EXACT_BPT_IN_FOR_TOKENS_OUT,\n BPT_IN_FOR_EXACT_TOKENS_OUT,\n REMOVE_TOKEN\n }\n\n /**\n * @dev Called by users to join a Pool, which transfers tokens from `sender` into the Pool's balance. This will\n * trigger custom Pool behavior, which will typically grant something in return to `recipient` - often tokenized\n * Pool shares.\n *\n * If the caller is not `sender`, it must be an authorized relayer for them.\n *\n * The `assets` and `maxAmountsIn` arrays must have the same length, and each entry indicates the maximum amount\n * to send for each asset. The amounts to send are decided by the Pool and not the Vault: it just enforces\n * these maximums.\n *\n * If joining a Pool that holds WETH, it is possible to send ETH directly: the Vault will do the wrapping. To enable\n * this mechanism, the IAsset sentinel value (the zero address) must be passed in the `assets` array instead of the\n * WETH address. Note that it is not possible to combine ETH and WETH in the same join. Any excess ETH will be sent\n * back to the caller (not the sender, which is important for relayers).\n *\n * `assets` must have the same length and order as the array returned by `getPoolTokens`. This prevents issues when\n * interacting with Pools that register and deregister tokens frequently. If sending ETH however, the array must be\n * sorted *before* replacing the WETH address with the ETH sentinel value (the zero address), which means the final\n * `assets` array might not be sorted. Pools with no registered tokens cannot be joined.\n *\n * If `fromInternalBalance` is true, the caller's Internal Balance will be preferred: ERC20 transfers will only\n * be made for the difference between the requested amount and Internal Balance (if any). Note that ETH cannot be\n * withdrawn from Internal Balance: attempting to do so will trigger a revert.\n *\n * This causes the Vault to call the `IBasePool.onJoinPool` hook on the Pool's contract, where Pools implement\n * their own custom logic. This typically requires additional information from the user (such as the expected number\n * of Pool shares). This can be encoded in the `userData` argument, which is ignored by the Vault and passed\n * directly to the Pool's contract, as is `recipient`.\n *\n * Emits a `PoolBalanceChanged` event.\n */\n function joinPool(\n bytes32 poolId,\n address sender,\n address recipient,\n JoinPoolRequest memory request\n ) external payable;\n\n struct JoinPoolRequest {\n address[] assets;\n uint256[] maxAmountsIn;\n bytes userData;\n bool fromInternalBalance;\n }\n\n /**\n * @dev Called by users to exit a Pool, which transfers tokens from the Pool's balance to `recipient`. This will\n * trigger custom Pool behavior, which will typically ask for something in return from `sender` - often tokenized\n * Pool shares. The amount of tokens that can be withdrawn is limited by the Pool's `cash` balance (see\n * `getPoolTokenInfo`).\n *\n * If the caller is not `sender`, it must be an authorized relayer for them.\n *\n * The `tokens` and `minAmountsOut` arrays must have the same length, and each entry in these indicates the minimum\n * token amount to receive for each token contract. The amounts to send are decided by the Pool and not the Vault:\n * it just enforces these minimums.\n *\n * If exiting a Pool that holds WETH, it is possible to receive ETH directly: the Vault will do the unwrapping. To\n * enable this mechanism, the IAsset sentinel value (the zero address) must be passed in the `assets` array instead\n * of the WETH address. Note that it is not possible to combine ETH and WETH in the same exit.\n *\n * `assets` must have the same length and order as the array returned by `getPoolTokens`. This prevents issues when\n * interacting with Pools that register and deregister tokens frequently. If receiving ETH however, the array must\n * be sorted *before* replacing the WETH address with the ETH sentinel value (the zero address), which means the\n * final `assets` array might not be sorted. Pools with no registered tokens cannot be exited.\n *\n * If `toInternalBalance` is true, the tokens will be deposited to `recipient`'s Internal Balance. Otherwise,\n * an ERC20 transfer will be performed. Note that ETH cannot be deposited to Internal Balance: attempting to\n * do so will trigger a revert.\n *\n * `minAmountsOut` is the minimum amount of tokens the user expects to get out of the Pool, for each token in the\n * `tokens` array. This array must match the Pool's registered tokens.\n *\n * This causes the Vault to call the `IBasePool.onExitPool` hook on the Pool's contract, where Pools implement\n * their own custom logic. This typically requires additional information from the user (such as the expected number\n * of Pool shares to return). This can be encoded in the `userData` argument, which is ignored by the Vault and\n * passed directly to the Pool's contract.\n *\n * Emits a `PoolBalanceChanged` event.\n */\n function exitPool(\n bytes32 poolId,\n address sender,\n address payable recipient,\n ExitPoolRequest memory request\n ) external;\n\n struct ExitPoolRequest {\n address[] assets;\n uint256[] minAmountsOut;\n bytes userData;\n bool toInternalBalance;\n }\n\n /**\n * @dev Returns a Pool's registered tokens, the total balance for each, and the latest block when *any* of\n * the tokens' `balances` changed.\n *\n * The order of the `tokens` array is the same order that will be used in `joinPool`, `exitPool`, as well as in all\n * Pool hooks (where applicable). Calls to `registerTokens` and `deregisterTokens` may change this order.\n *\n * If a Pool only registers tokens once, and these are sorted in ascending order, they will be stored in the same\n * order as passed to `registerTokens`.\n *\n * Total balances include both tokens held by the Vault and those withdrawn by the Pool's Asset Managers. These are\n * the amounts used by joins, exits and swaps. For a detailed breakdown of token balances, use `getPoolTokenInfo`\n * instead.\n */\n function getPoolTokens(bytes32 poolId)\n external\n view\n returns (\n IERC20[] memory tokens,\n uint256[] memory balances,\n uint256 lastChangeBlock\n );\n\n /**\n * @dev Performs a set of user balance operations, which involve Internal Balance (deposit, withdraw or transfer)\n * and plain ERC20 transfers using the Vault's allowance. This last feature is particularly useful for relayers, as\n * it lets integrators reuse a user's Vault allowance.\n *\n * For each operation, if the caller is not `sender`, it must be an authorized relayer for them.\n */\n function manageUserBalance(UserBalanceOp[] memory ops) external payable;\n\n struct UserBalanceOp {\n UserBalanceOpKind kind;\n address asset;\n uint256 amount;\n address sender;\n address payable recipient;\n }\n\n enum UserBalanceOpKind {\n DEPOSIT_INTERNAL,\n WITHDRAW_INTERNAL,\n TRANSFER_INTERNAL,\n TRANSFER_EXTERNAL\n }\n\n enum SwapKind {\n GIVEN_IN,\n GIVEN_OUT\n }\n\n struct SingleSwap {\n bytes32 poolId;\n SwapKind kind;\n address assetIn;\n address assetOut;\n uint256 amount;\n bytes userData;\n }\n\n struct FundManagement {\n address sender;\n bool fromInternalBalance;\n address payable recipient;\n bool toInternalBalance;\n }\n\n function swap(\n SingleSwap calldata singleSwap,\n FundManagement calldata funds,\n uint256 limit,\n uint256 deadline\n ) external returns (uint256 amountCalculated);\n\n function getPoolTokenInfo(bytes32 poolId, address token)\n external\n view\n returns (\n uint256 cash,\n uint256 managed,\n uint256 lastChangeBlock,\n address assetManager\n );\n}\n" + }, + "contracts/interfaces/balancer/IMetaStablePool.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport { IRateProvider } from \"./IRateProvider.sol\";\n\ninterface IMetaStablePool {\n function getRateProviders()\n external\n view\n returns (IRateProvider[] memory providers);\n}\n" + }, + "contracts/interfaces/balancer/IRateProvider.sol": { + "content": "// SPDX-License-Identifier: GPL-3.0-or-later\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see .\n\npragma solidity ^0.8.0;\n\ninterface IRateProvider {\n function getRate() external view returns (uint256);\n}\n" + }, + "contracts/interfaces/cctp/ICCTP.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\ninterface ICCTPTokenMessenger {\n function depositForBurn(\n uint256 amount,\n uint32 destinationDomain,\n bytes32 mintRecipient,\n address burnToken,\n bytes32 destinationCaller,\n uint256 maxFee,\n uint32 minFinalityThreshold\n ) external;\n\n function depositForBurnWithHook(\n uint256 amount,\n uint32 destinationDomain,\n bytes32 mintRecipient,\n address burnToken,\n bytes32 destinationCaller,\n uint256 maxFee,\n uint32 minFinalityThreshold,\n bytes memory hookData\n ) external;\n\n function getMinFeeAmount(uint256 amount) external view returns (uint256);\n}\n\ninterface ICCTPMessageTransmitter {\n function sendMessage(\n uint32 destinationDomain,\n bytes32 recipient,\n bytes32 destinationCaller,\n uint32 minFinalityThreshold,\n bytes memory messageBody\n ) external;\n\n function receiveMessage(bytes calldata message, bytes calldata attestation)\n external\n returns (bool);\n}\n\ninterface IMessageHandlerV2 {\n /**\n * @notice Handles an incoming finalized message from an IReceiverV2\n * @dev Finalized messages have finality threshold values greater than or equal to 2000\n * @param sourceDomain The source domain of the message\n * @param sender The sender of the message\n * @param finalityThresholdExecuted the finality threshold at which the message was attested to\n * @param messageBody The raw bytes of the message body\n * @return success True, if successful; false, if not.\n */\n function handleReceiveFinalizedMessage(\n uint32 sourceDomain,\n bytes32 sender,\n uint32 finalityThresholdExecuted,\n bytes calldata messageBody\n ) external returns (bool);\n\n /**\n * @notice Handles an incoming unfinalized message from an IReceiverV2\n * @dev Unfinalized messages have finality threshold values less than 2000\n * @param sourceDomain The source domain of the message\n * @param sender The sender of the message\n * @param finalityThresholdExecuted The finality threshold at which the message was attested to\n * @param messageBody The raw bytes of the message body\n * @return success True, if successful; false, if not.\n */\n function handleReceiveUnfinalizedMessage(\n uint32 sourceDomain,\n bytes32 sender,\n uint32 finalityThresholdExecuted,\n bytes calldata messageBody\n ) external returns (bool);\n}\n" + }, + "contracts/interfaces/chainlink/AggregatorV3Interface.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ninterface AggregatorV3Interface {\n function decimals() external view returns (uint8);\n\n function description() external view returns (string memory);\n\n function version() external view returns (uint256);\n\n // getRoundData and latestRoundData should both raise \"No data present\"\n // if they do not have data to report, instead of returning unset values\n // which could be misinterpreted as actual reported values.\n function getRoundData(uint80 _roundId)\n external\n view\n returns (\n uint80 roundId,\n int256 answer,\n uint256 startedAt,\n uint256 updatedAt,\n uint80 answeredInRound\n );\n\n function latestRoundData()\n external\n view\n returns (\n uint80 roundId,\n int256 answer,\n uint256 startedAt,\n uint256 updatedAt,\n uint80 answeredInRound\n );\n}\n" + }, + "contracts/interfaces/IBasicToken.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ninterface IBasicToken {\n function symbol() external view returns (string memory);\n\n function decimals() external view returns (uint8);\n}\n" + }, + "contracts/interfaces/IBeaconProofs.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\ninterface IBeaconProofs {\n function verifyValidator(\n bytes32 beaconBlockRoot,\n bytes32 pubKeyHash,\n bytes calldata validatorPubKeyProof,\n uint40 validatorIndex,\n bytes32 withdrawalCredentials\n ) external view;\n\n function verifyValidatorWithdrawable(\n bytes32 beaconBlockRoot,\n uint40 validatorIndex,\n uint64 withdrawableEpoch,\n bytes calldata withdrawableEpochProof\n ) external view;\n\n function verifyBalancesContainer(\n bytes32 beaconBlockRoot,\n bytes32 balancesContainerLeaf,\n bytes calldata balancesContainerProof\n ) external view;\n\n function verifyValidatorBalance(\n bytes32 balancesContainerRoot,\n bytes32 validatorBalanceLeaf,\n bytes calldata balanceProof,\n uint40 validatorIndex\n ) external view returns (uint256 validatorBalance);\n\n function verifyPendingDepositsContainer(\n bytes32 beaconBlockRoot,\n bytes32 pendingDepositsContainerRoot,\n bytes calldata proof\n ) external view;\n\n function verifyPendingDeposit(\n bytes32 pendingDepositsContainerRoot,\n bytes32 pendingDepositRoot,\n bytes calldata proof,\n uint32 pendingDepositIndex\n ) external view;\n\n function verifyFirstPendingDeposit(\n bytes32 beaconBlockRoot,\n uint64 slot,\n bytes calldata firstPendingDepositSlotProof\n ) external view returns (bool isEmptyDepositQueue);\n\n function merkleizePendingDeposit(\n bytes32 pubKeyHash,\n bytes calldata withdrawalCredentials,\n uint64 amountGwei,\n bytes calldata signature,\n uint64 slot\n ) external pure returns (bytes32 root);\n\n function merkleizeSignature(bytes calldata signature)\n external\n pure\n returns (bytes32 root);\n}\n" + }, + "contracts/interfaces/ICampaignRemoteManager.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\ninterface ICampaignRemoteManager {\n function createCampaign(\n CampaignCreationParams memory params,\n uint256 destinationChainId,\n uint256 additionalGasLimit,\n address votemarket\n ) external payable;\n\n function manageCampaign(\n CampaignManagementParams memory params,\n uint256 destinationChainId,\n uint256 additionalGasLimit,\n address votemarket\n ) external payable;\n\n function closeCampaign(\n CampaignClosingParams memory params,\n uint256 destinationChainId,\n uint256 additionalGasLimit,\n address votemarket\n ) external payable;\n\n struct CampaignCreationParams {\n uint256 chainId;\n address gauge;\n address manager;\n address rewardToken;\n uint8 numberOfPeriods;\n uint256 maxRewardPerVote;\n uint256 totalRewardAmount;\n address[] addresses;\n address hook;\n bool isWhitelist;\n }\n\n struct CampaignManagementParams {\n uint256 campaignId;\n address rewardToken;\n uint8 numberOfPeriods;\n uint256 totalRewardAmount;\n uint256 maxRewardPerVote;\n }\n\n struct CampaignClosingParams {\n uint256 campaignId;\n }\n}\n" + }, + "contracts/interfaces/IChildLiquidityGaugeFactory.sol": { + "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.8.4;\n\ninterface IChildLiquidityGaugeFactory {\n event DeployedGauge(\n address indexed _implementation,\n address indexed _lp_token,\n address indexed _deployer,\n bytes32 _salt,\n address _gauge\n );\n event Minted(\n address indexed _user,\n address indexed _gauge,\n uint256 _new_total\n );\n event TransferOwnership(address _old_owner, address _new_owner);\n event UpdateCallProxy(address _old_call_proxy, address _new_call_proxy);\n event UpdateImplementation(\n address _old_implementation,\n address _new_implementation\n );\n event UpdateManager(address _manager);\n event UpdateMirrored(address indexed _gauge, bool _mirrored);\n event UpdateRoot(address _factory, address _implementation);\n event UpdateVotingEscrow(\n address _old_voting_escrow,\n address _new_voting_escrow\n );\n\n function accept_transfer_ownership() external;\n\n function call_proxy() external view returns (address);\n\n function commit_transfer_ownership(address _future_owner) external;\n\n function crv() external view returns (address);\n\n function deploy_gauge(address _lp_token, bytes32 _salt)\n external\n returns (address);\n\n function deploy_gauge(\n address _lp_token,\n bytes32 _salt,\n address _manager\n ) external returns (address);\n\n function future_owner() external view returns (address);\n\n function gauge_data(address arg0) external view returns (uint256);\n\n function get_gauge(uint256 arg0) external view returns (address);\n\n function get_gauge_count() external view returns (uint256);\n\n function get_gauge_from_lp_token(address arg0)\n external\n view\n returns (address);\n\n function get_implementation() external view returns (address);\n\n function is_mirrored(address _gauge) external view returns (bool);\n\n function is_valid_gauge(address _gauge) external view returns (bool);\n\n function last_request(address _gauge) external view returns (uint256);\n\n function manager() external view returns (address);\n\n function mint(address _gauge) external;\n\n function mint_many(address[32] memory _gauges) external;\n\n function minted(address arg0, address arg1) external view returns (uint256);\n\n function owner() external view returns (address);\n\n function root_factory() external view returns (address);\n\n function root_implementation() external view returns (address);\n\n function set_call_proxy(address _new_call_proxy) external;\n\n function set_crv(address _crv) external;\n\n function set_implementation(address _implementation) external;\n\n function set_manager(address _new_manager) external;\n\n function set_mirrored(address _gauge, bool _mirrored) external;\n\n function set_root(address _factory, address _implementation) external;\n\n function set_voting_escrow(address _voting_escrow) external;\n\n function version() external view returns (string memory);\n\n function voting_escrow() external view returns (address);\n}\n" + }, + "contracts/interfaces/IComptroller.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ninterface IComptroller {\n // Claim all the COMP accrued by specific holders in specific markets for their supplies and/or borrows\n function claimComp(\n address[] memory holders,\n address[] memory cTokens,\n bool borrowers,\n bool suppliers\n ) external;\n\n function oracle() external view returns (address);\n}\n" + }, + "contracts/interfaces/ICreateX.sol": { + "content": "// SPDX-License-Identifier: AGPL-3.0-only\npragma solidity ^0.8.4;\n\n/**\n * @title CreateX Factory Interface Definition\n * @author pcaversaccio (https://web.archive.org/web/20230921103111/https://pcaversaccio.com/)\n * @custom:coauthor Matt Solomon (https://web.archive.org/web/20230921103335/https://mattsolomon.dev/)\n */\ninterface ICreateX {\n /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/\n /* TYPES */\n /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/\n\n struct Values {\n uint256 constructorAmount;\n uint256 initCallAmount;\n }\n\n /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/\n /* EVENTS */\n /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/\n\n event ContractCreation(address indexed newContract, bytes32 indexed salt);\n event ContractCreation(address indexed newContract);\n event Create3ProxyContractCreation(\n address indexed newContract,\n bytes32 indexed salt\n );\n\n /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/\n /* CUSTOM ERRORS */\n /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/\n\n error FailedContractCreation(address emitter);\n error FailedContractInitialisation(address emitter, bytes revertData);\n error InvalidSalt(address emitter);\n error InvalidNonceValue(address emitter);\n error FailedEtherTransfer(address emitter, bytes revertData);\n\n /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/\n /* CREATE */\n /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/\n\n function deployCreate(bytes memory initCode)\n external\n payable\n returns (address newContract);\n\n function deployCreateAndInit(\n bytes memory initCode,\n bytes memory data,\n Values memory values,\n address refundAddress\n ) external payable returns (address newContract);\n\n function deployCreateAndInit(\n bytes memory initCode,\n bytes memory data,\n Values memory values\n ) external payable returns (address newContract);\n\n function deployCreateClone(address implementation, bytes memory data)\n external\n payable\n returns (address proxy);\n\n function computeCreateAddress(address deployer, uint256 nonce)\n external\n view\n returns (address computedAddress);\n\n function computeCreateAddress(uint256 nonce)\n external\n view\n returns (address computedAddress);\n\n /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/\n /* CREATE2 */\n /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/\n\n function deployCreate2(bytes32 salt, bytes memory initCode)\n external\n payable\n returns (address newContract);\n\n function deployCreate2(bytes memory initCode)\n external\n payable\n returns (address newContract);\n\n function deployCreate2AndInit(\n bytes32 salt,\n bytes memory initCode,\n bytes memory data,\n Values memory values,\n address refundAddress\n ) external payable returns (address newContract);\n\n function deployCreate2AndInit(\n bytes32 salt,\n bytes memory initCode,\n bytes memory data,\n Values memory values\n ) external payable returns (address newContract);\n\n function deployCreate2AndInit(\n bytes memory initCode,\n bytes memory data,\n Values memory values,\n address refundAddress\n ) external payable returns (address newContract);\n\n function deployCreate2AndInit(\n bytes memory initCode,\n bytes memory data,\n Values memory values\n ) external payable returns (address newContract);\n\n function deployCreate2Clone(\n bytes32 salt,\n address implementation,\n bytes memory data\n ) external payable returns (address proxy);\n\n function deployCreate2Clone(address implementation, bytes memory data)\n external\n payable\n returns (address proxy);\n\n function computeCreate2Address(\n bytes32 salt,\n bytes32 initCodeHash,\n address deployer\n ) external pure returns (address computedAddress);\n\n function computeCreate2Address(bytes32 salt, bytes32 initCodeHash)\n external\n view\n returns (address computedAddress);\n\n /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/\n /* CREATE3 */\n /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/\n\n function deployCreate3(bytes32 salt, bytes memory initCode)\n external\n payable\n returns (address newContract);\n\n function deployCreate3(bytes memory initCode)\n external\n payable\n returns (address newContract);\n\n function deployCreate3AndInit(\n bytes32 salt,\n bytes memory initCode,\n bytes memory data,\n Values memory values,\n address refundAddress\n ) external payable returns (address newContract);\n\n function deployCreate3AndInit(\n bytes32 salt,\n bytes memory initCode,\n bytes memory data,\n Values memory values\n ) external payable returns (address newContract);\n\n function deployCreate3AndInit(\n bytes memory initCode,\n bytes memory data,\n Values memory values,\n address refundAddress\n ) external payable returns (address newContract);\n\n function deployCreate3AndInit(\n bytes memory initCode,\n bytes memory data,\n Values memory values\n ) external payable returns (address newContract);\n\n function computeCreate3Address(bytes32 salt, address deployer)\n external\n pure\n returns (address computedAddress);\n\n function computeCreate3Address(bytes32 salt)\n external\n view\n returns (address computedAddress);\n}\n" + }, + "contracts/interfaces/ICurveLiquidityGaugeV6.sol": { + "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.8.4;\n\ninterface ICurveLiquidityGaugeV6 {\n event ApplyOwnership(address admin);\n event Approval(\n address indexed _owner,\n address indexed _spender,\n uint256 _value\n );\n event CommitOwnership(address admin);\n event Deposit(address indexed provider, uint256 value);\n event SetGaugeManager(address _gauge_manager);\n event Transfer(address indexed _from, address indexed _to, uint256 _value);\n event UpdateLiquidityLimit(\n address indexed user,\n uint256 original_balance,\n uint256 original_supply,\n uint256 working_balance,\n uint256 working_supply\n );\n event Withdraw(address indexed provider, uint256 value);\n\n function DOMAIN_SEPARATOR() external view returns (bytes32);\n\n function add_reward(address _reward_token, address _distributor) external;\n\n function allowance(address arg0, address arg1)\n external\n view\n returns (uint256);\n\n function approve(address _spender, uint256 _value) external returns (bool);\n\n function balanceOf(address arg0) external view returns (uint256);\n\n function claim_rewards() external;\n\n function claim_rewards(address _addr) external;\n\n function claim_rewards(address _addr, address _receiver) external;\n\n function claimable_reward(address _user, address _reward_token)\n external\n view\n returns (uint256);\n\n function claimable_tokens(address addr) external returns (uint256);\n\n function claimed_reward(address _addr, address _token)\n external\n view\n returns (uint256);\n\n function decimals() external view returns (uint256);\n\n function decreaseAllowance(address _spender, uint256 _subtracted_value)\n external\n returns (bool);\n\n function deposit(uint256 _value) external;\n\n function deposit(uint256 _value, address _addr) external;\n\n function deposit(\n uint256 _value,\n address _addr,\n bool _claim_rewards\n ) external;\n\n function deposit_reward_token(address _reward_token, uint256 _amount)\n external;\n\n function deposit_reward_token(\n address _reward_token,\n uint256 _amount,\n uint256 _epoch\n ) external;\n\n function factory() external view returns (address);\n\n function future_epoch_time() external view returns (uint256);\n\n function increaseAllowance(address _spender, uint256 _added_value)\n external\n returns (bool);\n\n function inflation_rate() external view returns (uint256);\n\n function integrate_checkpoint() external view returns (uint256);\n\n function integrate_checkpoint_of(address arg0)\n external\n view\n returns (uint256);\n\n function integrate_fraction(address arg0) external view returns (uint256);\n\n function integrate_inv_supply(uint256 arg0) external view returns (uint256);\n\n function integrate_inv_supply_of(address arg0)\n external\n view\n returns (uint256);\n\n function is_killed() external view returns (bool);\n\n function kick(address addr) external;\n\n function lp_token() external view returns (address);\n\n function manager() external view returns (address);\n\n function name() external view returns (string memory);\n\n function nonces(address arg0) external view returns (uint256);\n\n function period() external view returns (int128);\n\n function period_timestamp(uint256 arg0) external view returns (uint256);\n\n function permit(\n address _owner,\n address _spender,\n uint256 _value,\n uint256 _deadline,\n uint8 _v,\n bytes32 _r,\n bytes32 _s\n ) external returns (bool);\n\n function reward_count() external view returns (uint256);\n\n function reward_integral_for(address arg0, address arg1)\n external\n view\n returns (uint256);\n\n function reward_tokens(uint256 arg0) external view returns (address);\n\n function rewards_receiver(address arg0) external view returns (address);\n\n function salt() external view returns (bytes32);\n\n function set_gauge_manager(address _gauge_manager) external;\n\n function set_killed(bool _is_killed) external;\n\n function set_reward_distributor(address _reward_token, address _distributor)\n external;\n\n function set_rewards_receiver(address _receiver) external;\n\n function symbol() external view returns (string memory);\n\n function totalSupply() external view returns (uint256);\n\n function transfer(address _to, uint256 _value) external returns (bool);\n\n function transferFrom(\n address _from,\n address _to,\n uint256 _value\n ) external returns (bool);\n\n function user_checkpoint(address addr) external returns (bool);\n\n function version() external view returns (string memory);\n\n function withdraw(uint256 _value) external;\n\n function withdraw(uint256 _value, bool _claim_rewards) external;\n\n function working_balances(address arg0) external view returns (uint256);\n\n function working_supply() external view returns (uint256);\n}\n" + }, + "contracts/interfaces/ICurveMinter.sol": { + "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.8.4;\n\ninterface ICurveMinter {\n event Minted(address indexed recipient, address gauge, uint256 minted);\n\n function allowed_to_mint_for(address arg0, address arg1)\n external\n view\n returns (bool);\n\n function controller() external view returns (address);\n\n function mint(address gauge_addr) external;\n\n function mint_for(address gauge_addr, address _for) external;\n\n function mint_many(address[8] memory gauge_addrs) external;\n\n function minted(address arg0, address arg1) external view returns (uint256);\n\n function toggle_approve_mint(address minting_user) external;\n\n function token() external view returns (address);\n}\n" + }, + "contracts/interfaces/ICurveStableSwapNG.sol": { + "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.8.4;\n\ninterface ICurveStableSwapNG {\n event AddLiquidity(\n address indexed provider,\n uint256[] token_amounts,\n uint256[] fees,\n uint256 invariant,\n uint256 token_supply\n );\n event ApplyNewFee(uint256 fee, uint256 offpeg_fee_multiplier);\n event Approval(\n address indexed owner,\n address indexed spender,\n uint256 value\n );\n event RampA(\n uint256 old_A,\n uint256 new_A,\n uint256 initial_time,\n uint256 future_time\n );\n event RemoveLiquidity(\n address indexed provider,\n uint256[] token_amounts,\n uint256[] fees,\n uint256 token_supply\n );\n event RemoveLiquidityImbalance(\n address indexed provider,\n uint256[] token_amounts,\n uint256[] fees,\n uint256 invariant,\n uint256 token_supply\n );\n event RemoveLiquidityOne(\n address indexed provider,\n int128 token_id,\n uint256 token_amount,\n uint256 coin_amount,\n uint256 token_supply\n );\n event SetNewMATime(uint256 ma_exp_time, uint256 D_ma_time);\n event StopRampA(uint256 A, uint256 t);\n event TokenExchange(\n address indexed buyer,\n int128 sold_id,\n uint256 tokens_sold,\n int128 bought_id,\n uint256 tokens_bought\n );\n event TokenExchangeUnderlying(\n address indexed buyer,\n int128 sold_id,\n uint256 tokens_sold,\n int128 bought_id,\n uint256 tokens_bought\n );\n event Transfer(\n address indexed sender,\n address indexed receiver,\n uint256 value\n );\n\n function A() external view returns (uint256);\n\n function A_precise() external view returns (uint256);\n\n function DOMAIN_SEPARATOR() external view returns (bytes32);\n\n function D_ma_time() external view returns (uint256);\n\n function D_oracle() external view returns (uint256);\n\n function N_COINS() external view returns (uint256);\n\n function add_liquidity(uint256[] memory _amounts, uint256 _min_mint_amount)\n external\n returns (uint256);\n\n function add_liquidity(\n uint256[] memory _amounts,\n uint256 _min_mint_amount,\n address _receiver\n ) external returns (uint256);\n\n function admin_balances(uint256 arg0) external view returns (uint256);\n\n function admin_fee() external view returns (uint256);\n\n function allowance(address arg0, address arg1)\n external\n view\n returns (uint256);\n\n function approve(address _spender, uint256 _value) external returns (bool);\n\n function balanceOf(address arg0) external view returns (uint256);\n\n function balances(uint256 i) external view returns (uint256);\n\n function calc_token_amount(uint256[] memory _amounts, bool _is_deposit)\n external\n view\n returns (uint256);\n\n function calc_withdraw_one_coin(uint256 _burn_amount, int128 i)\n external\n view\n returns (uint256);\n\n function coins(uint256 arg0) external view returns (address);\n\n function decimals() external view returns (uint8);\n\n function dynamic_fee(int128 i, int128 j) external view returns (uint256);\n\n function ema_price(uint256 i) external view returns (uint256);\n\n function exchange(\n int128 i,\n int128 j,\n uint256 _dx,\n uint256 _min_dy\n ) external returns (uint256);\n\n function exchange(\n int128 i,\n int128 j,\n uint256 _dx,\n uint256 _min_dy,\n address _receiver\n ) external returns (uint256);\n\n function exchange_received(\n int128 i,\n int128 j,\n uint256 _dx,\n uint256 _min_dy\n ) external returns (uint256);\n\n function exchange_received(\n int128 i,\n int128 j,\n uint256 _dx,\n uint256 _min_dy,\n address _receiver\n ) external returns (uint256);\n\n function fee() external view returns (uint256);\n\n function future_A() external view returns (uint256);\n\n function future_A_time() external view returns (uint256);\n\n function get_balances() external view returns (uint256[] memory);\n\n function get_dx(\n int128 i,\n int128 j,\n uint256 dy\n ) external view returns (uint256);\n\n function get_dy(\n int128 i,\n int128 j,\n uint256 dx\n ) external view returns (uint256);\n\n function get_p(uint256 i) external view returns (uint256);\n\n function get_virtual_price() external view returns (uint256);\n\n function initial_A() external view returns (uint256);\n\n function initial_A_time() external view returns (uint256);\n\n function last_price(uint256 i) external view returns (uint256);\n\n function ma_exp_time() external view returns (uint256);\n\n function ma_last_time() external view returns (uint256);\n\n function name() external view returns (string memory);\n\n function nonces(address arg0) external view returns (uint256);\n\n function offpeg_fee_multiplier() external view returns (uint256);\n\n function permit(\n address _owner,\n address _spender,\n uint256 _value,\n uint256 _deadline,\n uint8 _v,\n bytes32 _r,\n bytes32 _s\n ) external returns (bool);\n\n function price_oracle(uint256 i) external view returns (uint256);\n\n function ramp_A(uint256 _future_A, uint256 _future_time) external;\n\n function remove_liquidity(\n uint256 _burn_amount,\n uint256[] memory _min_amounts\n ) external returns (uint256[] memory);\n\n function remove_liquidity(\n uint256 _burn_amount,\n uint256[] memory _min_amounts,\n address _receiver\n ) external returns (uint256[] memory);\n\n function remove_liquidity(\n uint256 _burn_amount,\n uint256[] memory _min_amounts,\n address _receiver,\n bool _claim_admin_fees\n ) external returns (uint256[] memory);\n\n function remove_liquidity_imbalance(\n uint256[] memory _amounts,\n uint256 _max_burn_amount\n ) external returns (uint256);\n\n function remove_liquidity_imbalance(\n uint256[] memory _amounts,\n uint256 _max_burn_amount,\n address _receiver\n ) external returns (uint256);\n\n function remove_liquidity_one_coin(\n uint256 _burn_amount,\n int128 i,\n uint256 _min_received\n ) external returns (uint256);\n\n function remove_liquidity_one_coin(\n uint256 _burn_amount,\n int128 i,\n uint256 _min_received,\n address _receiver\n ) external returns (uint256);\n\n function salt() external view returns (bytes32);\n\n function set_ma_exp_time(uint256 _ma_exp_time, uint256 _D_ma_time) external;\n\n function set_new_fee(uint256 _new_fee, uint256 _new_offpeg_fee_multiplier)\n external;\n\n function stop_ramp_A() external;\n\n function stored_rates() external view returns (uint256[] memory);\n\n function symbol() external view returns (string memory);\n\n function totalSupply() external view returns (uint256);\n\n function transfer(address _to, uint256 _value) external returns (bool);\n\n function transferFrom(\n address _from,\n address _to,\n uint256 _value\n ) external returns (bool);\n\n function version() external view returns (string memory);\n\n function withdraw_admin_fees() external;\n}\n" + }, + "contracts/interfaces/ICurveXChainLiquidityGauge.sol": { + "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.8.4;\n\ninterface ICurveXChainLiquidityGauge {\n event Approval(\n address indexed _owner,\n address indexed _spender,\n uint256 _value\n );\n event Deposit(address indexed provider, uint256 value);\n event SetGaugeManager(address _gauge_manager);\n event Transfer(address indexed _from, address indexed _to, uint256 _value);\n event UpdateLiquidityLimit(\n address indexed user,\n uint256 original_balance,\n uint256 original_supply,\n uint256 working_balance,\n uint256 working_supply\n );\n event Withdraw(address indexed provider, uint256 value);\n\n function DOMAIN_SEPARATOR() external view returns (bytes32);\n\n function add_reward(address _reward_token, address _distributor) external;\n\n function allowance(address arg0, address arg1)\n external\n view\n returns (uint256);\n\n function approve(address _spender, uint256 _value) external returns (bool);\n\n function balanceOf(address arg0) external view returns (uint256);\n\n function claim_rewards() external;\n\n function claim_rewards(address _addr) external;\n\n function claim_rewards(address _addr, address _receiver) external;\n\n function claimable_reward(address _user, address _reward_token)\n external\n view\n returns (uint256);\n\n function claimable_tokens(address addr) external returns (uint256);\n\n function claimed_reward(address _addr, address _token)\n external\n view\n returns (uint256);\n\n function decimals() external view returns (uint256);\n\n function decreaseAllowance(address _spender, uint256 _subtracted_value)\n external\n returns (bool);\n\n function deposit(uint256 _value) external;\n\n function deposit(uint256 _value, address _addr) external;\n\n function deposit(\n uint256 _value,\n address _addr,\n bool _claim_rewards\n ) external;\n\n function deposit_reward_token(address _reward_token, uint256 _amount)\n external;\n\n function deposit_reward_token(\n address _reward_token,\n uint256 _amount,\n uint256 _epoch\n ) external;\n\n function factory() external view returns (address);\n\n function increaseAllowance(address _spender, uint256 _added_value)\n external\n returns (bool);\n\n function inflation_rate(uint256 arg0) external view returns (uint256);\n\n function initialize(\n address _lp_token,\n address _root,\n address _manager\n ) external;\n\n function integrate_checkpoint() external view returns (uint256);\n\n function integrate_checkpoint_of(address arg0)\n external\n view\n returns (uint256);\n\n function integrate_fraction(address arg0) external view returns (uint256);\n\n function integrate_inv_supply(int128 arg0) external view returns (uint256);\n\n function integrate_inv_supply_of(address arg0)\n external\n view\n returns (uint256);\n\n function is_killed() external view returns (bool);\n\n function lp_token() external view returns (address);\n\n function manager() external view returns (address);\n\n function name() external view returns (string memory);\n\n function nonces(address arg0) external view returns (uint256);\n\n function period() external view returns (int128);\n\n function period_timestamp(int128 arg0) external view returns (uint256);\n\n function permit(\n address _owner,\n address _spender,\n uint256 _value,\n uint256 _deadline,\n uint8 _v,\n bytes32 _r,\n bytes32 _s\n ) external returns (bool);\n\n function recover_remaining(address _reward_token) external;\n\n function reward_count() external view returns (uint256);\n\n function reward_integral_for(address arg0, address arg1)\n external\n view\n returns (uint256);\n\n function reward_remaining(address arg0) external view returns (uint256);\n\n function reward_tokens(uint256 arg0) external view returns (address);\n\n function rewards_receiver(address arg0) external view returns (address);\n\n function root_gauge() external view returns (address);\n\n function set_gauge_manager(address _gauge_manager) external;\n\n function set_killed(bool _is_killed) external;\n\n function set_manager(address _gauge_manager) external;\n\n function set_reward_distributor(address _reward_token, address _distributor)\n external;\n\n function set_rewards_receiver(address _receiver) external;\n\n function set_root_gauge(address _root) external;\n\n function symbol() external view returns (string memory);\n\n function totalSupply() external view returns (uint256);\n\n function transfer(address _to, uint256 _value) external returns (bool);\n\n function transferFrom(\n address _from,\n address _to,\n uint256 _value\n ) external returns (bool);\n\n function update_voting_escrow() external;\n\n function user_checkpoint(address addr) external returns (bool);\n\n function version() external view returns (string memory);\n\n function voting_escrow() external view returns (address);\n\n function withdraw(uint256 _value) external;\n\n function withdraw(uint256 _value, bool _claim_rewards) external;\n\n function withdraw(\n uint256 _value,\n bool _claim_rewards,\n address _receiver\n ) external;\n\n function working_balances(address arg0) external view returns (uint256);\n\n function working_supply() external view returns (uint256);\n}\n" + }, + "contracts/interfaces/ICVXLocker.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ninterface ICVXLocker {\n function lock(\n address _account,\n uint256 _amount,\n uint256 _spendRatio\n ) external;\n\n function lockedBalanceOf(address _account) external view returns (uint256);\n}\n" + }, + "contracts/interfaces/IDepositContract.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ninterface IDepositContract {\n /// @notice A processed deposit event.\n event DepositEvent(\n bytes pubkey,\n bytes withdrawal_credentials,\n bytes amount,\n bytes signature,\n bytes index\n );\n\n /// @notice Submit a Phase 0 DepositData object.\n /// @param pubkey A BLS12-381 public key.\n /// @param withdrawal_credentials Commitment to a public key for withdrawals.\n /// @param signature A BLS12-381 signature.\n /// @param deposit_data_root The SHA-256 hash of the SSZ-encoded DepositData object.\n /// Used as a protection against malformed input.\n function deposit(\n bytes calldata pubkey,\n bytes calldata withdrawal_credentials,\n bytes calldata signature,\n bytes32 deposit_data_root\n ) external payable;\n\n /// @notice Query the current deposit root hash.\n /// @return The deposit root hash.\n function get_deposit_root() external view returns (bytes32);\n\n /// @notice Query the current deposit count.\n /// @return The deposit count encoded as a little endian 64-bit number.\n function get_deposit_count() external view returns (bytes memory);\n}\n" + }, + "contracts/interfaces/IMerkl.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\ninterface IDistributor {\n event Claimed(address indexed user, address indexed token, uint256 amount);\n\n function claim(\n address[] calldata users,\n address[] calldata tokens,\n uint256[] calldata amounts,\n bytes32[][] calldata proofs\n ) external;\n}\n" + }, + "contracts/interfaces/IMockVault.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { IVault } from \"./IVault.sol\";\n\ninterface IMockVault is IVault {\n function outstandingWithdrawalsAmount() external view returns (uint256);\n\n function wethAvailable() external view returns (uint256);\n}\n" + }, + "contracts/interfaces/IOracle.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\ninterface IOracle {\n /**\n * @dev returns the asset price in USD, in 8 decimal digits.\n *\n * The version of priceProvider deployed for OETH has 18 decimal digits\n */\n function price(address asset) external view returns (uint256);\n}\n" + }, + "contracts/interfaces/IOUSD.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\ninterface IOUSD {\n event Approval(\n address indexed owner,\n address indexed spender,\n uint256 value\n );\n event GovernorshipTransferred(\n address indexed previousGovernor,\n address indexed newGovernor\n );\n event PendingGovernorshipTransfer(\n address indexed previousGovernor,\n address indexed newGovernor\n );\n event TotalSupplyUpdatedHighres(\n uint256 totalSupply,\n uint256 rebasingCredits,\n uint256 rebasingCreditsPerToken\n );\n event Transfer(address indexed from, address indexed to, uint256 value);\n\n function _totalSupply() external view returns (uint256);\n\n function allowance(address _owner, address _spender)\n external\n view\n returns (uint256);\n\n function approve(address _spender, uint256 _value) external returns (bool);\n\n function balanceOf(address _account) external view returns (uint256);\n\n function burn(address account, uint256 amount) external;\n\n function changeSupply(uint256 _newTotalSupply) external;\n\n function claimGovernance() external;\n\n function creditsBalanceOf(address _account)\n external\n view\n returns (uint256, uint256);\n\n function creditsBalanceOfHighres(address _account)\n external\n view\n returns (\n uint256,\n uint256,\n bool\n );\n\n function decimals() external view returns (uint8);\n\n function decreaseAllowance(address _spender, uint256 _subtractedValue)\n external\n returns (bool);\n\n function governor() external view returns (address);\n\n function increaseAllowance(address _spender, uint256 _addedValue)\n external\n returns (bool);\n\n function initialize(\n string memory _nameArg,\n string memory _symbolArg,\n address _vaultAddress\n ) external;\n\n function isGovernor() external view returns (bool);\n\n function isUpgraded(address) external view returns (uint256);\n\n function mint(address _account, uint256 _amount) external;\n\n function name() external view returns (string memory);\n\n function nonRebasingCreditsPerToken(address)\n external\n view\n returns (uint256);\n\n function nonRebasingSupply() external view returns (uint256);\n\n function rebaseOptIn() external;\n\n function rebaseOptOut() external;\n\n function rebaseState(address) external view returns (uint8);\n\n function rebasingCredits() external view returns (uint256);\n\n function rebasingCreditsHighres() external view returns (uint256);\n\n function rebasingCreditsPerToken() external view returns (uint256);\n\n function rebasingCreditsPerTokenHighres() external view returns (uint256);\n\n function symbol() external view returns (string memory);\n\n function totalSupply() external view returns (uint256);\n\n function transfer(address _to, uint256 _value) external returns (bool);\n\n function transferFrom(\n address _from,\n address _to,\n uint256 _value\n ) external returns (bool);\n\n function transferGovernance(address _newGovernor) external;\n\n function vaultAddress() external view returns (address);\n}\n" + }, + "contracts/interfaces/ISafe.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\ninterface ISafe {\n function execTransactionFromModule(\n address,\n uint256,\n bytes memory,\n uint8\n ) external returns (bool);\n}\n" + }, + "contracts/interfaces/ISSVNetwork.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nstruct Cluster {\n uint32 validatorCount;\n uint64 networkFeeIndex;\n uint64 index;\n bool active;\n uint256 balance;\n}\n\ninterface ISSVNetwork {\n /**********/\n /* Errors */\n /**********/\n\n error CallerNotOwner(); // 0x5cd83192\n error CallerNotWhitelisted(); // 0x8c6e5d71\n error FeeTooLow(); // 0x732f9413\n error FeeExceedsIncreaseLimit(); // 0x958065d9\n error NoFeeDeclared(); // 0x1d226c30\n error ApprovalNotWithinTimeframe(); // 0x97e4b518\n error OperatorDoesNotExist(); // 0x961e3e8c\n error InsufficientBalance(); // 0xf4d678b8\n error ValidatorDoesNotExist(); // 0xe51315d2\n error ClusterNotLiquidatable(); // 0x60300a8d\n error InvalidPublicKeyLength(); // 0x637297a4\n error InvalidOperatorIdsLength(); // 0x38186224\n error ClusterAlreadyEnabled(); // 0x3babafd2\n error ClusterIsLiquidated(); // 0x95a0cf33\n error ClusterDoesNotExists(); // 0x185e2b16\n error IncorrectClusterState(); // 0x12e04c87\n error UnsortedOperatorsList(); // 0xdd020e25\n error NewBlockPeriodIsBelowMinimum(); // 0x6e6c9cac\n error ExceedValidatorLimit(); // 0x6df5ab76\n error TokenTransferFailed(); // 0x045c4b02\n error SameFeeChangeNotAllowed(); // 0xc81272f8\n error FeeIncreaseNotAllowed(); // 0x410a2b6c\n error NotAuthorized(); // 0xea8e4eb5\n error OperatorsListNotUnique(); // 0xa5a1ff5d\n error OperatorAlreadyExists(); // 0x289c9494\n error TargetModuleDoesNotExist(); // 0x8f9195fb\n error MaxValueExceeded(); // 0x91aa3017\n error FeeTooHigh(); // 0xcd4e6167\n error PublicKeysSharesLengthMismatch(); // 0x9ad467b8\n error IncorrectValidatorStateWithData(bytes publicKey); // 0x89307938\n error ValidatorAlreadyExistsWithData(bytes publicKey); // 0x388e7999\n error EmptyPublicKeysList(); // df83e679\n\n // legacy errors\n error ValidatorAlreadyExists(); // 0x8d09a73e\n error IncorrectValidatorState(); // 0x2feda3c1\n\n event AdminChanged(address previousAdmin, address newAdmin);\n event BeaconUpgraded(address indexed beacon);\n event ClusterDeposited(\n address indexed owner,\n uint64[] operatorIds,\n uint256 value,\n Cluster cluster\n );\n event ClusterLiquidated(\n address indexed owner,\n uint64[] operatorIds,\n Cluster cluster\n );\n event ClusterReactivated(\n address indexed owner,\n uint64[] operatorIds,\n Cluster cluster\n );\n event ClusterWithdrawn(\n address indexed owner,\n uint64[] operatorIds,\n uint256 value,\n Cluster cluster\n );\n event DeclareOperatorFeePeriodUpdated(uint64 value);\n event ExecuteOperatorFeePeriodUpdated(uint64 value);\n event FeeRecipientAddressUpdated(\n address indexed owner,\n address recipientAddress\n );\n event Initialized(uint8 version);\n event LiquidationThresholdPeriodUpdated(uint64 value);\n event MinimumLiquidationCollateralUpdated(uint256 value);\n event NetworkEarningsWithdrawn(uint256 value, address recipient);\n event NetworkFeeUpdated(uint256 oldFee, uint256 newFee);\n event OperatorAdded(\n uint64 indexed operatorId,\n address indexed owner,\n bytes publicKey,\n uint256 fee\n );\n event OperatorFeeDeclarationCancelled(\n address indexed owner,\n uint64 indexed operatorId\n );\n event OperatorFeeDeclared(\n address indexed owner,\n uint64 indexed operatorId,\n uint256 blockNumber,\n uint256 fee\n );\n event OperatorFeeExecuted(\n address indexed owner,\n uint64 indexed operatorId,\n uint256 blockNumber,\n uint256 fee\n );\n event OperatorFeeIncreaseLimitUpdated(uint64 value);\n event OperatorMaximumFeeUpdated(uint64 maxFee);\n event OperatorRemoved(uint64 indexed operatorId);\n event OperatorWhitelistUpdated(\n uint64 indexed operatorId,\n address whitelisted\n );\n event OperatorWithdrawn(\n address indexed owner,\n uint64 indexed operatorId,\n uint256 value\n );\n event OwnershipTransferStarted(\n address indexed previousOwner,\n address indexed newOwner\n );\n event OwnershipTransferred(\n address indexed previousOwner,\n address indexed newOwner\n );\n event Upgraded(address indexed implementation);\n event ValidatorAdded(\n address indexed owner,\n uint64[] operatorIds,\n bytes publicKey,\n bytes shares,\n Cluster cluster\n );\n event ValidatorExited(\n address indexed owner,\n uint64[] operatorIds,\n bytes publicKey\n );\n event ValidatorRemoved(\n address indexed owner,\n uint64[] operatorIds,\n bytes publicKey,\n Cluster cluster\n );\n\n fallback() external;\n\n function acceptOwnership() external;\n\n function cancelDeclaredOperatorFee(uint64 operatorId) external;\n\n function declareOperatorFee(uint64 operatorId, uint256 fee) external;\n\n function deposit(\n address clusterOwner,\n uint64[] memory operatorIds,\n uint256 amount,\n Cluster memory cluster\n ) external;\n\n function executeOperatorFee(uint64 operatorId) external;\n\n function exitValidator(bytes memory publicKey, uint64[] memory operatorIds)\n external;\n\n function bulkExitValidator(\n bytes[] calldata publicKeys,\n uint64[] calldata operatorIds\n ) external;\n\n function getVersion() external pure returns (string memory version);\n\n function initialize(\n address token_,\n address ssvOperators_,\n address ssvClusters_,\n address ssvDAO_,\n address ssvViews_,\n uint64 minimumBlocksBeforeLiquidation_,\n uint256 minimumLiquidationCollateral_,\n uint32 validatorsPerOperatorLimit_,\n uint64 declareOperatorFeePeriod_,\n uint64 executeOperatorFeePeriod_,\n uint64 operatorMaxFeeIncrease_\n ) external;\n\n function liquidate(\n address clusterOwner,\n uint64[] memory operatorIds,\n Cluster memory cluster\n ) external;\n\n function owner() external view returns (address);\n\n function pendingOwner() external view returns (address);\n\n function proxiableUUID() external view returns (bytes32);\n\n function reactivate(\n uint64[] memory operatorIds,\n uint256 amount,\n Cluster memory cluster\n ) external;\n\n function reduceOperatorFee(uint64 operatorId, uint256 fee) external;\n\n function registerOperator(bytes memory publicKey, uint256 fee)\n external\n returns (uint64 id);\n\n function registerValidator(\n bytes memory publicKey,\n uint64[] memory operatorIds,\n bytes memory sharesData,\n uint256 amount,\n Cluster memory cluster\n ) external;\n\n function bulkRegisterValidator(\n bytes[] calldata publicKeys,\n uint64[] calldata operatorIds,\n bytes[] calldata sharesData,\n uint256 amount,\n Cluster memory cluster\n ) external;\n\n function removeOperator(uint64 operatorId) external;\n\n function removeValidator(\n bytes memory publicKey,\n uint64[] memory operatorIds,\n Cluster memory cluster\n ) external;\n\n function bulkRemoveValidator(\n bytes[] calldata publicKeys,\n uint64[] calldata operatorIds,\n Cluster memory cluster\n ) external;\n\n function renounceOwnership() external;\n\n function setFeeRecipientAddress(address recipientAddress) external;\n\n function setOperatorWhitelist(uint64 operatorId, address whitelisted)\n external;\n\n function transferOwnership(address newOwner) external;\n\n function updateDeclareOperatorFeePeriod(uint64 timeInSeconds) external;\n\n function updateExecuteOperatorFeePeriod(uint64 timeInSeconds) external;\n\n function updateLiquidationThresholdPeriod(uint64 blocks) external;\n\n function updateMaximumOperatorFee(uint64 maxFee) external;\n\n function updateMinimumLiquidationCollateral(uint256 amount) external;\n\n function updateModule(uint8 moduleId, address moduleAddress) external;\n\n function updateNetworkFee(uint256 fee) external;\n\n function updateOperatorFeeIncreaseLimit(uint64 percentage) external;\n\n function upgradeTo(address newImplementation) external;\n\n function upgradeToAndCall(address newImplementation, bytes memory data)\n external\n payable;\n\n function withdraw(\n uint64[] memory operatorIds,\n uint256 amount,\n Cluster memory cluster\n ) external;\n\n function withdrawAllOperatorEarnings(uint64 operatorId) external;\n\n function withdrawNetworkEarnings(uint256 amount) external;\n\n function withdrawOperatorEarnings(uint64 operatorId, uint256 amount)\n external;\n}\n" + }, + "contracts/interfaces/IStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title Platform interface to integrate with lending platform like Compound, AAVE etc.\n */\ninterface IStrategy {\n /**\n * @dev Deposit the given asset to platform\n * @param _asset asset address\n * @param _amount Amount to deposit\n */\n function deposit(address _asset, uint256 _amount) external;\n\n /**\n * @dev Deposit the entire balance of all supported assets in the Strategy\n * to the platform\n */\n function depositAll() external;\n\n /**\n * @dev Withdraw given asset from Lending platform\n */\n function withdraw(\n address _recipient,\n address _asset,\n uint256 _amount\n ) external;\n\n /**\n * @dev Liquidate all assets in strategy and return them to Vault.\n */\n function withdrawAll() external;\n\n /**\n * @dev Returns the current balance of the given asset.\n */\n function checkBalance(address _asset)\n external\n view\n returns (uint256 balance);\n\n /**\n * @dev Returns bool indicating whether strategy supports asset.\n */\n function supportsAsset(address _asset) external view returns (bool);\n\n /**\n * @dev Collect reward tokens from the Strategy.\n */\n function collectRewardTokens() external;\n\n /**\n * @dev The address array of the reward tokens for the Strategy.\n */\n function getRewardTokenAddresses() external view returns (address[] memory);\n\n function harvesterAddress() external view returns (address);\n\n function transferToken(address token, uint256 amount) external;\n\n function setRewardTokenAddresses(address[] calldata _rewardTokenAddresses)\n external;\n}\n" + }, + "contracts/interfaces/ISwapper.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\ninterface ISwapper {\n /**\n * @param fromAsset The token address of the asset being sold.\n * @param toAsset The token address of the asset being purchased.\n * @param fromAssetAmount The amount of assets being sold.\n * @param minToAssetAmmount The minimum amount of assets to be purchased.\n * @param data tx.data returned from 1Inch's /v5.0/1/swap API\n */\n function swap(\n address fromAsset,\n address toAsset,\n uint256 fromAssetAmount,\n uint256 minToAssetAmmount,\n bytes calldata data\n ) external returns (uint256 toAssetAmount);\n}\n" + }, + "contracts/interfaces/IVault.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { VaultStorage } from \"../vault/VaultStorage.sol\";\n\ninterface IVault {\n // slither-disable-start constable-states\n\n event AssetAllocated(address _asset, address _strategy, uint256 _amount);\n event StrategyApproved(address _addr);\n event StrategyRemoved(address _addr);\n event Mint(address _addr, uint256 _value);\n event Redeem(address _addr, uint256 _value);\n event CapitalPaused();\n event CapitalUnpaused();\n event DefaultStrategyUpdated(address _strategy);\n event RebasePaused();\n event RebaseUnpaused();\n event VaultBufferUpdated(uint256 _vaultBuffer);\n event AllocateThresholdUpdated(uint256 _threshold);\n event RebaseThresholdUpdated(uint256 _threshold);\n event StrategistUpdated(address _address);\n event MaxSupplyDiffChanged(uint256 maxSupplyDiff);\n event YieldDistribution(address _to, uint256 _yield, uint256 _fee);\n event TrusteeFeeBpsChanged(uint256 _basis);\n event TrusteeAddressChanged(address _address);\n event StrategyAddedToMintWhitelist(address indexed strategy);\n event StrategyRemovedFromMintWhitelist(address indexed strategy);\n event RebasePerSecondMaxChanged(uint256 rebaseRatePerSecond);\n event DripDurationChanged(uint256 dripDuration);\n event WithdrawalRequested(\n address indexed _withdrawer,\n uint256 indexed _requestId,\n uint256 _amount,\n uint256 _queued\n );\n event WithdrawalClaimed(\n address indexed _withdrawer,\n uint256 indexed _requestId,\n uint256 _amount\n );\n event WithdrawalClaimable(uint256 _claimable, uint256 _newClaimable);\n event WithdrawalClaimDelayUpdated(uint256 _newDelay);\n\n // Governable.sol\n function transferGovernance(address _newGovernor) external;\n\n function claimGovernance() external;\n\n function governor() external view returns (address);\n\n // VaultAdmin.sol\n function setVaultBuffer(uint256 _vaultBuffer) external;\n\n function vaultBuffer() external view returns (uint256);\n\n function setAutoAllocateThreshold(uint256 _threshold) external;\n\n function autoAllocateThreshold() external view returns (uint256);\n\n function setRebaseThreshold(uint256 _threshold) external;\n\n function rebaseThreshold() external view returns (uint256);\n\n function setStrategistAddr(address _address) external;\n\n function strategistAddr() external view returns (address);\n\n function setMaxSupplyDiff(uint256 _maxSupplyDiff) external;\n\n function maxSupplyDiff() external view returns (uint256);\n\n function setTrusteeAddress(address _address) external;\n\n function trusteeAddress() external view returns (address);\n\n function setTrusteeFeeBps(uint256 _basis) external;\n\n function trusteeFeeBps() external view returns (uint256);\n\n function approveStrategy(address _addr) external;\n\n function removeStrategy(address _addr) external;\n\n function setDefaultStrategy(address _strategy) external;\n\n function defaultStrategy() external view returns (address);\n\n function pauseRebase() external;\n\n function unpauseRebase() external;\n\n function rebasePaused() external view returns (bool);\n\n function pauseCapital() external;\n\n function unpauseCapital() external;\n\n function capitalPaused() external view returns (bool);\n\n function transferToken(address _asset, uint256 _amount) external;\n\n function withdrawAllFromStrategy(address _strategyAddr) external;\n\n function withdrawAllFromStrategies() external;\n\n function withdrawFromStrategy(\n address _strategyFromAddress,\n address[] calldata _assets,\n uint256[] calldata _amounts\n ) external;\n\n function depositToStrategy(\n address _strategyToAddress,\n address[] calldata _assets,\n uint256[] calldata _amounts\n ) external;\n\n // VaultCore.sol\n function mint(\n address _asset,\n uint256 _amount,\n uint256 _minimumOusdAmount\n ) external;\n\n function mintForStrategy(uint256 _amount) external;\n\n function redeem(uint256 _amount, uint256 _minimumUnitAmount) external;\n\n function burnForStrategy(uint256 _amount) external;\n\n function allocate() external;\n\n function rebase() external;\n\n function totalValue() external view returns (uint256 value);\n\n function checkBalance(address _asset) external view returns (uint256);\n\n /// @notice Deprecated: use calculateRedeemOutput\n function calculateRedeemOutputs(uint256 _amount)\n external\n view\n returns (uint256[] memory);\n\n function calculateRedeemOutput(uint256 _amount)\n external\n view\n returns (uint256);\n\n function getAssetCount() external view returns (uint256);\n\n function getAllAssets() external view returns (address[] memory);\n\n function getStrategyCount() external view returns (uint256);\n\n function getAllStrategies() external view returns (address[] memory);\n\n /// @notice Deprecated.\n function isSupportedAsset(address _asset) external view returns (bool);\n\n function dripper() external view returns (address);\n\n function asset() external view returns (address);\n\n function initialize(address) external;\n\n function addWithdrawalQueueLiquidity() external;\n\n function requestWithdrawal(uint256 _amount)\n external\n returns (uint256 requestId, uint256 queued);\n\n function claimWithdrawal(uint256 requestId)\n external\n returns (uint256 amount);\n\n function claimWithdrawals(uint256[] memory requestIds)\n external\n returns (uint256[] memory amounts, uint256 totalAmount);\n\n function withdrawalQueueMetadata()\n external\n view\n returns (VaultStorage.WithdrawalQueueMetadata memory);\n\n function withdrawalRequests(uint256 requestId)\n external\n view\n returns (VaultStorage.WithdrawalRequest memory);\n\n function addStrategyToMintWhitelist(address strategyAddr) external;\n\n function removeStrategyFromMintWhitelist(address strategyAddr) external;\n\n function isMintWhitelistedStrategy(address strategyAddr)\n external\n view\n returns (bool);\n\n function withdrawalClaimDelay() external view returns (uint256);\n\n function setWithdrawalClaimDelay(uint256 newDelay) external;\n\n function lastRebase() external view returns (uint64);\n\n function dripDuration() external view returns (uint64);\n\n function setDripDuration(uint256 _dripDuration) external;\n\n function rebasePerSecondMax() external view returns (uint64);\n\n function setRebaseRateMax(uint256 yearlyApr) external;\n\n function rebasePerSecondTarget() external view returns (uint64);\n\n function previewYield() external view returns (uint256 yield);\n\n function weth() external view returns (address);\n\n // slither-disable-end constable-states\n}\n" + }, + "contracts/interfaces/IWETH9.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ninterface IWETH9 {\n event Approval(address indexed src, address indexed guy, uint256 wad);\n event Deposit(address indexed dst, uint256 wad);\n event Transfer(address indexed src, address indexed dst, uint256 wad);\n event Withdrawal(address indexed src, uint256 wad);\n\n function allowance(address, address) external view returns (uint256);\n\n function approve(address guy, uint256 wad) external returns (bool);\n\n function balanceOf(address) external view returns (uint256);\n\n function decimals() external view returns (uint8);\n\n function deposit() external payable;\n\n function name() external view returns (string memory);\n\n function symbol() external view returns (string memory);\n\n function totalSupply() external view returns (uint256);\n\n function transfer(address dst, uint256 wad) external returns (bool);\n\n function transferFrom(\n address src,\n address dst,\n uint256 wad\n ) external returns (bool);\n\n function withdraw(uint256 wad) external;\n}\n" + }, + "contracts/interfaces/IWstETH.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ninterface IWstETH {\n /**\n * @notice Get amount of wstETH for a given amount of stETH\n * @param _stETHAmount amount of stETH\n * @return Amount of wstETH for a given stETH amount\n */\n function getWstETHByStETH(uint256 _stETHAmount)\n external\n view\n returns (uint256);\n\n /**\n * @notice Get amount of stETH for a given amount of wstETH\n * @param _wstETHAmount amount of wstETH\n * @return Amount of stETH for a given wstETH amount\n */\n function getStETHByWstETH(uint256 _wstETHAmount)\n external\n view\n returns (uint256);\n\n /**\n * @notice Get amount of stETH for a one wstETH\n * @return Amount of stETH for 1 wstETH\n */\n function stEthPerToken() external view returns (uint256);\n\n /**\n * @notice Get amount of wstETH for a one stETH\n * @return Amount of wstETH for a 1 stETH\n */\n function tokensPerStEth() external view returns (uint256);\n\n /**\n * @notice Exchanges stETH to wstETH\n * @param _stETHAmount amount of stETH to wrap in exchange for wstETH\n * @dev Requirements:\n * - `_stETHAmount` must be non-zero\n * - msg.sender must approve at least `_stETHAmount` stETH to this\n * contract.\n * - msg.sender must have at least `_stETHAmount` of stETH.\n * User should first approve _stETHAmount to the WstETH contract\n * @return Amount of wstETH user receives after wrap\n */\n function wrap(uint256 _stETHAmount) external returns (uint256);\n\n /**\n * @notice Exchanges wstETH to stETH\n * @param _wstETHAmount amount of wstETH to uwrap in exchange for stETH\n * @dev Requirements:\n * - `_wstETHAmount` must be non-zero\n * - msg.sender must have at least `_wstETHAmount` wstETH.\n * @return Amount of stETH user receives after unwrap\n */\n function unwrap(uint256 _wstETHAmount) external returns (uint256);\n}\n" + }, + "contracts/interfaces/morpho/compound/ICompoundOracle.sol": { + "content": "// SPDX-License-Identifier: GNU AGPLv3\npragma solidity ^0.8.0;\n\ninterface ICompoundOracle {\n function getUnderlyingPrice(address) external view returns (uint256);\n}\n" + }, + "contracts/interfaces/morpho/ILens.sol": { + "content": "// SPDX-License-Identifier: GNU AGPLv3\npragma solidity ^0.8.0;\n\nimport \"./compound/ICompoundOracle.sol\";\nimport \"./IMorpho.sol\";\n\ninterface ILens {\n /// STORAGE ///\n\n function MAX_BASIS_POINTS() external view returns (uint256);\n\n function WAD() external view returns (uint256);\n\n function morpho() external view returns (IMorpho);\n\n function comptroller() external view returns (IComptroller);\n\n /// GENERAL ///\n\n function getTotalSupply()\n external\n view\n returns (\n uint256 p2pSupplyAmount,\n uint256 poolSupplyAmount,\n uint256 totalSupplyAmount\n );\n\n function getTotalBorrow()\n external\n view\n returns (\n uint256 p2pBorrowAmount,\n uint256 poolBorrowAmount,\n uint256 totalBorrowAmount\n );\n\n /// MARKETS ///\n\n function isMarketCreated(address _poolToken) external view returns (bool);\n\n function isMarketCreatedAndNotPaused(address _poolToken)\n external\n view\n returns (bool);\n\n function isMarketCreatedAndNotPausedNorPartiallyPaused(address _poolToken)\n external\n view\n returns (bool);\n\n function getAllMarkets()\n external\n view\n returns (address[] memory marketsCreated_);\n\n function getMainMarketData(address _poolToken)\n external\n view\n returns (\n uint256 avgSupplyRatePerBlock,\n uint256 avgBorrowRatePerBlock,\n uint256 p2pSupplyAmount,\n uint256 p2pBorrowAmount,\n uint256 poolSupplyAmount,\n uint256 poolBorrowAmount\n );\n\n function getAdvancedMarketData(address _poolToken)\n external\n view\n returns (\n uint256 p2pSupplyIndex,\n uint256 p2pBorrowIndex,\n uint256 poolSupplyIndex,\n uint256 poolBorrowIndex,\n uint32 lastUpdateBlockNumber,\n uint256 p2pSupplyDelta,\n uint256 p2pBorrowDelta\n );\n\n function getMarketConfiguration(address _poolToken)\n external\n view\n returns (\n address underlying,\n bool isCreated,\n bool p2pDisabled,\n bool isPaused,\n bool isPartiallyPaused,\n uint16 reserveFactor,\n uint16 p2pIndexCursor,\n uint256 collateralFactor\n );\n\n function getTotalMarketSupply(address _poolToken)\n external\n view\n returns (uint256 p2pSupplyAmount, uint256 poolSupplyAmount);\n\n function getTotalMarketBorrow(address _poolToken)\n external\n view\n returns (uint256 p2pBorrowAmount, uint256 poolBorrowAmount);\n\n /// INDEXES ///\n\n function getCurrentP2PSupplyIndex(address _poolToken)\n external\n view\n returns (uint256);\n\n function getCurrentP2PBorrowIndex(address _poolToken)\n external\n view\n returns (uint256);\n\n function getCurrentPoolIndexes(address _poolToken)\n external\n view\n returns (\n uint256 currentPoolSupplyIndex,\n uint256 currentPoolBorrowIndex\n );\n\n function getIndexes(address _poolToken, bool _computeUpdatedIndexes)\n external\n view\n returns (\n uint256 p2pSupplyIndex,\n uint256 p2pBorrowIndex,\n uint256 poolSupplyIndex,\n uint256 poolBorrowIndex\n );\n\n /// USERS ///\n\n function getEnteredMarkets(address _user)\n external\n view\n returns (address[] memory enteredMarkets);\n\n function getUserHealthFactor(\n address _user,\n address[] calldata _updatedMarkets\n ) external view returns (uint256);\n\n function getUserBalanceStates(\n address _user,\n address[] calldata _updatedMarkets\n )\n external\n view\n returns (\n uint256 collateralValue,\n uint256 debtValue,\n uint256 maxDebtValue\n );\n\n function getCurrentSupplyBalanceInOf(address _poolToken, address _user)\n external\n view\n returns (\n uint256 balanceOnPool,\n uint256 balanceInP2P,\n uint256 totalBalance\n );\n\n function getCurrentBorrowBalanceInOf(address _poolToken, address _user)\n external\n view\n returns (\n uint256 balanceOnPool,\n uint256 balanceInP2P,\n uint256 totalBalance\n );\n\n function getUserMaxCapacitiesForAsset(address _user, address _poolToken)\n external\n view\n returns (uint256 withdrawable, uint256 borrowable);\n\n function getUserHypotheticalBalanceStates(\n address _user,\n address _poolToken,\n uint256 _withdrawnAmount,\n uint256 _borrowedAmount\n ) external view returns (uint256 debtValue, uint256 maxDebtValue);\n\n function getUserLiquidityDataForAsset(\n address _user,\n address _poolToken,\n bool _computeUpdatedIndexes,\n ICompoundOracle _oracle\n ) external view returns (Types.AssetLiquidityData memory assetData);\n\n function isLiquidatable(address _user, address[] memory _updatedMarkets)\n external\n view\n returns (bool);\n\n function computeLiquidationRepayAmount(\n address _user,\n address _poolTokenBorrowed,\n address _poolTokenCollateral,\n address[] calldata _updatedMarkets\n ) external view returns (uint256 toRepay);\n\n /// RATES ///\n\n function getAverageSupplyRatePerBlock(address _poolToken)\n external\n view\n returns (\n uint256 avgSupplyRatePerBlock,\n uint256 p2pSupplyAmount,\n uint256 poolSupplyAmount\n );\n\n function getAverageBorrowRatePerBlock(address _poolToken)\n external\n view\n returns (\n uint256 avgBorrowRatePerBlock,\n uint256 p2pBorrowAmount,\n uint256 poolBorrowAmount\n );\n\n function getNextUserSupplyRatePerBlock(\n address _poolToken,\n address _user,\n uint256 _amount\n )\n external\n view\n returns (\n uint256 nextSupplyRatePerBlock,\n uint256 balanceOnPool,\n uint256 balanceInP2P,\n uint256 totalBalance\n );\n\n function getNextUserBorrowRatePerBlock(\n address _poolToken,\n address _user,\n uint256 _amount\n )\n external\n view\n returns (\n uint256 nextBorrowRatePerBlock,\n uint256 balanceOnPool,\n uint256 balanceInP2P,\n uint256 totalBalance\n );\n\n function getCurrentUserSupplyRatePerBlock(address _poolToken, address _user)\n external\n view\n returns (uint256);\n\n function getCurrentUserBorrowRatePerBlock(address _poolToken, address _user)\n external\n view\n returns (uint256);\n\n function getRatesPerBlock(address _poolToken)\n external\n view\n returns (\n uint256 p2pSupplyRate,\n uint256 p2pBorrowRate,\n uint256 poolSupplyRate,\n uint256 poolBorrowRate\n );\n\n /// REWARDS ///\n\n function getUserUnclaimedRewards(\n address[] calldata _poolTokens,\n address _user\n ) external view returns (uint256 unclaimedRewards);\n\n function getAccruedSupplierComp(\n address _supplier,\n address _poolToken,\n uint256 _balance\n ) external view returns (uint256);\n\n function getAccruedBorrowerComp(\n address _borrower,\n address _poolToken,\n uint256 _balance\n ) external view returns (uint256);\n\n function getCurrentCompSupplyIndex(address _poolToken)\n external\n view\n returns (uint256);\n\n function getCurrentCompBorrowIndex(address _poolToken)\n external\n view\n returns (uint256);\n}\n" + }, + "contracts/interfaces/morpho/IMorpho.sol": { + "content": "// SPDX-License-Identifier: GNU AGPLv3\npragma solidity ^0.8.0;\n\nimport \"./Types.sol\";\nimport \"../IComptroller.sol\";\nimport \"./compound/ICompoundOracle.sol\";\n\n// prettier-ignore\ninterface IMorpho {\n function comptroller() external view returns (IComptroller);\n function supply(address _poolTokenAddress, address _onBehalf, uint256 _amount) external;\n function supply(address _poolTokenAddress, address _onBehalf, uint256 _amount, uint256 _maxGasForMatching) external;\n function withdraw(address _poolTokenAddress, uint256 _amount) external;\n function claimRewards(\n address[] calldata _cTokenAddresses,\n bool _tradeForMorphoToken\n ) external returns (uint256 claimedAmount);\n}\n" + }, + "contracts/interfaces/morpho/Types.sol": { + "content": "// SPDX-License-Identifier: GNU AGPLv3\npragma solidity ^0.8.0;\n\n/// @title Types.\n/// @author Morpho Labs.\n/// @custom:contact security@morpho.xyz\n/// @dev Common types and structs used in Moprho contracts.\nlibrary Types {\n /// ENUMS ///\n\n enum PositionType {\n SUPPLIERS_IN_P2P,\n SUPPLIERS_ON_POOL,\n BORROWERS_IN_P2P,\n BORROWERS_ON_POOL\n }\n\n /// STRUCTS ///\n\n struct SupplyBalance {\n uint256 inP2P; // In supplier's peer-to-peer unit, a unit that grows in underlying value, to keep track of the interests earned by suppliers in peer-to-peer. Multiply by the peer-to-peer supply index to get the underlying amount.\n uint256 onPool; // In cToken. Multiply by the pool supply index to get the underlying amount.\n }\n\n struct BorrowBalance {\n uint256 inP2P; // In borrower's peer-to-peer unit, a unit that grows in underlying value, to keep track of the interests paid by borrowers in peer-to-peer. Multiply by the peer-to-peer borrow index to get the underlying amount.\n uint256 onPool; // In cdUnit, a unit that grows in value, to keep track of the debt increase when borrowers are on Compound. Multiply by the pool borrow index to get the underlying amount.\n }\n\n // Max gas to consume during the matching process for supply, borrow, withdraw and repay functions.\n struct MaxGasForMatching {\n uint64 supply;\n uint64 borrow;\n uint64 withdraw;\n uint64 repay;\n }\n\n struct Delta {\n uint256 p2pSupplyDelta; // Difference between the stored peer-to-peer supply amount and the real peer-to-peer supply amount (in pool supply unit).\n uint256 p2pBorrowDelta; // Difference between the stored peer-to-peer borrow amount and the real peer-to-peer borrow amount (in pool borrow unit).\n uint256 p2pSupplyAmount; // Sum of all stored peer-to-peer supply (in peer-to-peer supply unit).\n uint256 p2pBorrowAmount; // Sum of all stored peer-to-peer borrow (in peer-to-peer borrow unit).\n }\n\n struct AssetLiquidityData {\n uint256 collateralValue; // The collateral value of the asset.\n uint256 maxDebtValue; // The maximum possible debt value of the asset.\n uint256 debtValue; // The debt value of the asset.\n uint256 underlyingPrice; // The price of the token.\n uint256 collateralFactor; // The liquidation threshold applied on this token.\n }\n\n struct LiquidityData {\n uint256 collateralValue; // The collateral value.\n uint256 maxDebtValue; // The maximum debt value possible.\n uint256 debtValue; // The debt value.\n }\n\n // Variables are packed together to save gas (will not exceed their limit during Morpho's lifetime).\n struct LastPoolIndexes {\n uint32 lastUpdateBlockNumber; // The last time the peer-to-peer indexes were updated.\n uint112 lastSupplyPoolIndex; // Last pool supply index.\n uint112 lastBorrowPoolIndex; // Last pool borrow index.\n }\n\n struct MarketParameters {\n uint16 reserveFactor; // Proportion of the interest earned by users sent to the DAO for each market, in basis point (100% = 10 000). The value is set at market creation.\n uint16 p2pIndexCursor; // Position of the peer-to-peer rate in the pool's spread. Determine the weights of the weighted arithmetic average in the indexes computations ((1 - p2pIndexCursor) * r^S + p2pIndexCursor * r^B) (in basis point).\n }\n\n struct MarketStatus {\n bool isCreated; // Whether or not this market is created.\n bool isPaused; // Whether the market is paused or not (all entry points on Morpho are frozen; supply, borrow, withdraw, repay and liquidate).\n bool isPartiallyPaused; // Whether the market is partially paused or not (only supply and borrow are frozen).\n }\n}\n" + }, + "contracts/interfaces/plume/IFeeRegistry.sol": { + "content": "// SPDX-License-Identifier: GPL-2.0-or-later\n// As the copyright holder of this work, Ubiquity Labs retains\n// the right to distribute, use, and modify this code under any license of\n// their choosing, in addition to the terms of the GPL-v2 or later.\npragma solidity ^0.8.25;\n\ninterface IFeeRegistry {\n function registerFee(\n bool isTokenA,\n uint32 binId,\n uint256 binFeeInQuote\n ) external;\n}\n" + }, + "contracts/interfaces/plume/ILiquidityRegistry.sol": { + "content": "// SPDX-License-Identifier: GPL-2.0-or-later\n// As the copyright holder of this work, Ubiquity Labs retains\n// the right to distribute, use, and modify this code under any license of\n// their choosing, in addition to the terms of the GPL-v2 or later.\npragma solidity ^0.8.25;\n\nimport { IMaverickV2Pool } from \"./IMaverickV2Pool.sol\";\n\ninterface ILiquidityRegistry {\n function notifyBinLiquidity(\n IMaverickV2Pool pool,\n uint256 tokenId,\n uint32 binId,\n uint256 currentBinLpBalance\n ) external;\n}\n" + }, + "contracts/interfaces/plume/IMaverickV2Factory.sol": { + "content": "// SPDX-License-Identifier: GPL-2.0-or-later\n// As the copyright holder of this work, Ubiquity Labs retains\n// the right to distribute, use, and modify this code under any license of\n// their choosing, in addition to the terms of the GPL-v2 or later.\npragma solidity ^0.8.25;\n\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\n\nimport { IFeeRegistry } from \"./IFeeRegistry.sol\";\nimport { IMaverickV2Pool } from \"./IMaverickV2Pool.sol\";\n\ninterface IMaverickV2Factory {\n error FactorAlreadyInitialized();\n error FactorNotInitialized();\n error FactoryInvalidTokenOrder(IERC20 _tokenA, IERC20 _tokenB);\n error FactoryInvalidFee();\n error FactoryInvalidKinds(uint8 kinds);\n error FactoryInvalidTickSpacing(uint256 tickSpacing);\n error FactoryInvalidLookback(uint256 lookback);\n error FactoryInvalidTokenDecimals(uint8 decimalsA, uint8 decimalsB);\n error FactoryPoolAlreadyExists(\n uint256 feeAIn,\n uint256 feeBIn,\n uint256 tickSpacing,\n uint256 lookback,\n IERC20 tokenA,\n IERC20 tokenB,\n uint8 kinds,\n address accessor\n );\n error FactoryAccessorMustBeNonZero();\n error NotImplemented();\n\n event PoolCreated(\n IMaverickV2Pool poolAddress,\n uint8 protocolFeeRatio,\n uint256 feeAIn,\n uint256 feeBIn,\n uint256 tickSpacing,\n uint256 lookback,\n int32 activeTick,\n IERC20 tokenA,\n IERC20 tokenB,\n uint8 kinds,\n address accessor\n );\n event SetFactoryProtocolFeeReceiver(address receiver);\n event SetFactoryProtocolFeeRegistry(IFeeRegistry registry);\n\n struct DeployParameters {\n uint64 feeAIn;\n uint64 feeBIn;\n uint32 lookback;\n int32 activeTick;\n uint64 tokenAScale;\n uint64 tokenBScale;\n // slot\n IERC20 tokenA;\n // slot\n IERC20 tokenB;\n // slot\n uint16 tickSpacing;\n uint8 options;\n address accessor;\n }\n\n /**\n * @notice Called by deployer library to initialize a pool.\n */\n function deployParameters()\n external\n view\n returns (\n uint64 feeAIn,\n uint64 feeBIn,\n uint32 lookback,\n int32 activeTick,\n uint64 tokenAScale,\n uint64 tokenBScale,\n // slot\n IERC20 tokenA,\n // slot\n IERC20 tokenB,\n // slot\n uint16 tickSpacing,\n uint8 options,\n address accessor\n );\n\n /**\n * @notice Create a new MaverickV2Pool with symmetric swap fees.\n * @param fee Fraction of the pool swap amount that is retained as an LP in\n * D18 scale.\n * @param tickSpacing Tick spacing of pool where 1.0001^tickSpacing is the\n * bin width.\n * @param lookback Pool lookback in seconds.\n * @param tokenA Address of tokenA.\n * @param tokenB Address of tokenB.\n * @param activeTick Tick position that contains the active bins.\n * @param kinds 1-15 number to represent the active kinds\n * 0b0001 = static;\n * 0b0010 = right;\n * 0b0100 = left;\n * 0b1000 = both.\n * E.g. a pool with all 4 modes will have kinds = b1111 = 15\n */\n function create(\n uint64 fee,\n uint16 tickSpacing,\n uint32 lookback,\n IERC20 tokenA,\n IERC20 tokenB,\n int32 activeTick,\n uint8 kinds\n ) external returns (IMaverickV2Pool);\n\n /**\n * @notice Create a new MaverickV2Pool.\n * @param feeAIn Fraction of the pool swap amount for tokenA-input swaps\n * that is retained as an LP in D18 scale.\n * @param feeBIn Fraction of the pool swap amount for tokenB-input swaps\n * that is retained as an LP in D18 scale.\n * @param tickSpacing Tick spacing of pool where 1.0001^tickSpacing is the\n * bin width.\n * @param lookback Pool lookback in seconds.\n * @param tokenA Address of tokenA.\n * @param tokenB Address of tokenB.\n * @param activeTick Tick position that contains the active bins.\n * @param kinds 1-15 number to represent the active kinds\n * 0b0001 = static;\n * 0b0010 = right;\n * 0b0100 = left;\n * 0b1000 = both.\n * e.g. a pool with all 4 modes will have kinds = b1111 = 15\n */\n function create(\n uint64 feeAIn,\n uint64 feeBIn,\n uint16 tickSpacing,\n uint32 lookback,\n IERC20 tokenA,\n IERC20 tokenB,\n int32 activeTick,\n uint8 kinds\n ) external returns (IMaverickV2Pool);\n\n /**\n * @notice Bool indicating whether the pool was deployed from this factory.\n */\n function isFactoryPool(IMaverickV2Pool pool) external view returns (bool);\n\n /**\n * @notice Address that receives the protocol fee\n */\n function protocolFeeReceiver() external view returns (address);\n\n /**\n * @notice Address notified on swaps of the protocol fee\n */\n function protocolFeeRegistry() external view returns (IFeeRegistry);\n\n /**\n * @notice Lookup a pool for given parameters.\n */\n function lookup(\n uint256 feeAIn,\n uint256 feeBIn,\n uint256 tickSpacing,\n uint256 lookback,\n IERC20 tokenA,\n IERC20 tokenB,\n uint8 kinds\n ) external view returns (IMaverickV2Pool);\n\n /**\n * @notice Lookup a pool for given parameters.\n */\n function lookup(\n IERC20 _tokenA,\n IERC20 _tokenB,\n uint256 startIndex,\n uint256 endIndex\n ) external view returns (IMaverickV2Pool[] memory pools);\n\n /**\n * @notice Lookup a pool for given parameters.\n */\n function lookup(uint256 startIndex, uint256 endIndex)\n external\n view\n returns (IMaverickV2Pool[] memory pools);\n\n /**\n * @notice Count of permissionless pools.\n */\n function poolCount() external view returns (uint256 _poolCount);\n\n /**\n * @notice Count of pools for a given accessor and token pair. For\n * permissionless pools, pass `accessor = address(0)`.\n */\n function poolByTokenCount(\n IERC20 _tokenA,\n IERC20 _tokenB,\n address accessor\n ) external view returns (uint256 _poolCount);\n\n /**\n * @notice Get the current factory owner.\n */\n function owner() external view returns (address);\n}\n" + }, + "contracts/interfaces/plume/IMaverickV2LiquidityManager.sol": { + "content": "// SPDX-License-Identifier: GPL-2.0-or-later\npragma solidity ^0.8.25;\n\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport { IMaverickV2Pool } from \"./IMaverickV2Pool.sol\";\n\nimport { IMaverickV2Position } from \"./IMaverickV2Position.sol\";\nimport { IMaverickV2PoolLens } from \"./IMaverickV2PoolLens.sol\";\n\ninterface IMaverickV2LiquidityManager {\n error LiquidityManagerNotFactoryPool();\n error LiquidityManagerNotTokenIdOwner();\n\n /**\n * @notice Maverick V2 NFT position contract that tracks NFT-based\n * liquditiy positions.\n */\n function position() external view returns (IMaverickV2Position);\n\n /**\n * @notice Create Maverick V2 pool. Function is a pass through to the pool\n * factory and is provided here so that is can be assembled as part of a\n * multicall transaction.\n */\n function createPool(\n uint64 fee,\n uint16 tickSpacing,\n uint32 lookback,\n IERC20 tokenA,\n IERC20 tokenB,\n int32 activeTick,\n uint8 kinds\n ) external payable returns (IMaverickV2Pool pool);\n\n /**\n * @notice Add Liquidity position NFT for msg.sender by specifying\n * msg.sender's token index.\n * @dev Token index is different from tokenId.\n * On the Position NFT contract a user can own multiple NFT tokenIds and\n * these are indexes by an enumeration index which is the `index` input\n * here.\n *\n * See addLiquidity for a description of the add params.\n */\n function addPositionLiquidityToSenderByTokenIndex(\n IMaverickV2Pool pool,\n uint256 index,\n bytes memory packedSqrtPriceBreaks,\n bytes[] memory packedArgs\n )\n external\n payable\n returns (\n uint256 tokenAAmount,\n uint256 tokenBAmount,\n uint32[] memory binIds\n );\n\n /**\n * @notice Mint new tokenId in the Position NFt contract to msg.sender.\n * Both mints an NFT and adds liquidity to the pool that is held by the\n * NFT.\n */\n function mintPositionNftToSender(\n IMaverickV2Pool pool,\n bytes calldata packedSqrtPriceBreaks,\n bytes[] calldata packedArgs\n )\n external\n payable\n returns (\n uint256 tokenAAmount,\n uint256 tokenBAmount,\n uint32[] memory binIds,\n uint256 tokenId\n );\n\n /**\n * @notice Donates liqudity to a pool that is held by the position contract\n * and will never be retrievable. Can be used to start a pool and ensure\n * there will always be a base level of liquditiy in the pool.\n */\n function donateLiquidity(\n IMaverickV2Pool pool,\n IMaverickV2Pool.AddLiquidityParams memory args\n ) external payable;\n\n /**\n * @notice Packs sqrtPrice breaks array with this format: [length,\n * array[0], array[1],..., array[length-1]] where length is 1 byte.\n */\n function packUint88Array(uint88[] memory fullArray)\n external\n pure\n returns (bytes memory packedArray);\n\n /**\n * @notice Packs addLiquidity paramters array element-wise.\n */\n function packAddLiquidityArgsArray(\n IMaverickV2Pool.AddLiquidityParams[] memory args\n ) external pure returns (bytes[] memory argsPacked);\n}\n" + }, + "contracts/interfaces/plume/IMaverickV2Pool.sol": { + "content": "// SPDX-License-Identifier: GPL-2.0-or-later\n// As the copyright holder of this work, Ubiquity Labs retains\n// the right to distribute, use, and modify this code under any license of\n// their choosing, in addition to the terms of the GPL-v2 or later.\npragma solidity ^0.8.0;\n\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport { IMaverickV2Factory } from \"./IMaverickV2Factory.sol\";\n\ninterface IMaverickV2Pool {\n error PoolZeroLiquidityAdded();\n error PoolMinimumLiquidityNotMet();\n error PoolLocked();\n error PoolInvalidFee();\n error PoolTicksNotSorted(uint256 index, int256 previousTick, int256 tick);\n error PoolTicksAmountsLengthMismatch(\n uint256 ticksLength,\n uint256 amountsLength\n );\n error PoolBinIdsAmountsLengthMismatch(\n uint256 binIdsLength,\n uint256 amountsLength\n );\n error PoolKindNotSupported(uint256 kinds, uint256 kind);\n error PoolInsufficientBalance(\n uint256 deltaLpAmount,\n uint256 accountBalance\n );\n error PoolReservesExceedMaximum(uint256 amount);\n error PoolValueExceedsBits(uint256 amount, uint256 bits);\n error PoolTickMaxExceeded(uint256 tick);\n error PoolMigrateBinFirst();\n error PoolCurrentTickBeyondSwapLimit(int32 startingTick);\n error PoolSenderNotAccessor(address sender_, address accessor);\n error PoolSenderNotFactory(address sender_, address accessor);\n error PoolFunctionNotImplemented();\n error PoolTokenNotSolvent(\n uint256 internalReserve,\n uint256 tokenBalance,\n IERC20 token\n );\n error PoolNoProtocolFeeReceiverSet();\n\n event PoolSwap(\n address sender,\n address recipient,\n SwapParams params,\n uint256 amountIn,\n uint256 amountOut\n );\n event PoolFlashLoan(\n address sender,\n address recipient,\n uint256 amountA,\n uint256 amountB\n );\n event PoolProtocolFeeCollected(IERC20 token, uint256 protocolFee);\n\n event PoolAddLiquidity(\n address sender,\n address recipient,\n uint256 subaccount,\n AddLiquidityParams params,\n uint256 tokenAAmount,\n uint256 tokenBAmount,\n uint32[] binIds\n );\n\n event PoolMigrateBinsUpStack(\n address sender,\n uint32 binId,\n uint32 maxRecursion\n );\n\n event PoolRemoveLiquidity(\n address sender,\n address recipient,\n uint256 subaccount,\n RemoveLiquidityParams params,\n uint256 tokenAOut,\n uint256 tokenBOut\n );\n\n event PoolTickState(int32 tick, uint256 reserveA, uint256 reserveB);\n event PoolTickBinUpdate(int32 tick, uint8 kind, uint32 binId);\n event PoolSqrtPrice(uint256 sqrtPrice);\n\n /**\n * @notice Tick state parameters.\n */\n struct TickState {\n uint128 reserveA;\n uint128 reserveB;\n uint128 totalSupply;\n uint32[4] binIdsByTick;\n }\n\n /**\n * @notice Tick data parameters.\n * @param currentReserveA Current reserve of token A.\n * @param currentReserveB Current reserve of token B.\n * @param currentLiquidity Current liquidity amount.\n */\n struct TickData {\n uint256 currentReserveA;\n uint256 currentReserveB;\n uint256 currentLiquidity;\n }\n\n /**\n * @notice Bin state parameters.\n * @param mergeBinBalance LP token balance that this bin possesses of the merge bin.\n * @param mergeId Bin ID of the bin that this bin has merged into.\n * @param totalSupply Total amount of LP tokens in this bin.\n * @param kind One of the 4 kinds (0=static, 1=right, 2=left, 3=both).\n * @param tick The lower price tick of the bin in its current state.\n * @param tickBalance Balance of the tick.\n */\n struct BinState {\n uint128 mergeBinBalance;\n uint128 tickBalance;\n uint128 totalSupply;\n uint8 kind;\n int32 tick;\n uint32 mergeId;\n }\n\n /**\n * @notice Parameters for swap.\n * @param amount Amount of the token that is either the input if exactOutput is false\n * or the output if exactOutput is true.\n * @param tokenAIn Boolean indicating whether tokenA is the input.\n * @param exactOutput Boolean indicating whether the amount specified is\n * the exact output amount (true).\n * @param tickLimit The furthest tick a swap will execute in. If no limit\n * is desired, value should be set to type(int32).max for a tokenAIn swap\n * and type(int32).min for a swap where tokenB is the input.\n */\n struct SwapParams {\n uint256 amount;\n bool tokenAIn;\n bool exactOutput;\n int32 tickLimit;\n }\n\n /**\n * @notice Parameters associated with adding liquidity.\n * @param kind One of the 4 kinds (0=static, 1=right, 2=left, 3=both).\n * @param ticks Array of ticks to add liquidity to.\n * @param amounts Array of bin LP amounts to add.\n */\n struct AddLiquidityParams {\n uint8 kind;\n int32[] ticks;\n uint128[] amounts;\n }\n\n /**\n * @notice Parameters for each bin that will have liquidity removed.\n * @param binIds Index array of the bins losing liquidity.\n * @param amounts Array of bin LP amounts to remove.\n */\n struct RemoveLiquidityParams {\n uint32[] binIds;\n uint128[] amounts;\n }\n\n /**\n * @notice State of the pool.\n * @param reserveA Pool tokenA balanceOf at end of last operation\n * @param reserveB Pool tokenB balanceOf at end of last operation\n * @param lastTwaD8 Value of log time weighted average price at last block.\n * Value is 8-decimal scale and is in the fractional tick domain. E.g. a\n * value of 12.3e8 indicates the TWAP was 3/10ths of the way into the 12th\n * tick.\n * @param lastLogPriceD8 Value of log price at last block. Value is\n * 8-decimal scale and is in the fractional tick domain. E.g. a value of\n * 12.3e8 indicates the price was 3/10ths of the way into the 12th tick.\n * @param lastTimestamp Last block.timestamp value in seconds for latest\n * swap transaction.\n * @param activeTick Current tick position that contains the active bins.\n * @param isLocked Pool isLocked, E.g., locked or unlocked; isLocked values\n * defined in Pool.sol.\n * @param binCounter Index of the last bin created.\n * @param protocolFeeRatioD3 Ratio of the swap fee that is kept for the\n * protocol.\n */\n struct State {\n uint128 reserveA;\n uint128 reserveB;\n int64 lastTwaD8;\n int64 lastLogPriceD8;\n uint40 lastTimestamp;\n int32 activeTick;\n bool isLocked;\n uint32 binCounter;\n uint8 protocolFeeRatioD3;\n }\n\n /**\n * @notice Internal data used for data passing between Pool and Bin code.\n */\n struct BinDelta {\n uint128 deltaA;\n uint128 deltaB;\n }\n\n /**\n * @notice 1-15 number to represent the active kinds.\n * @notice 0b0001 = static;\n * @notice 0b0010 = right;\n * @notice 0b0100 = left;\n * @notice 0b1000 = both;\n *\n * E.g. a pool with all 4 modes will have kinds = b1111 = 15\n */\n function kinds() external view returns (uint8 _kinds);\n\n /**\n * @notice Pool swap fee for the given direction (A-in or B-in swap) in\n * 18-decimal format. E.g. 0.01e18 is a 1% swap fee.\n */\n function fee(bool tokenAIn) external view returns (uint256);\n\n /**\n * @notice TickSpacing of pool where 1.0001^tickSpacing is the bin width.\n */\n function tickSpacing() external view returns (uint256);\n\n /**\n * @notice Lookback period of pool in seconds.\n */\n function lookback() external view returns (uint256);\n\n /**\n * @notice Address of Pool accessor. This is Zero address for\n * permissionless pools.\n */\n function accessor() external view returns (address);\n\n /**\n * @notice Pool tokenA. Address of tokenA is such that tokenA < tokenB.\n */\n function tokenA() external view returns (IERC20);\n\n /**\n * @notice Pool tokenB.\n */\n function tokenB() external view returns (IERC20);\n\n /**\n * @notice Deploying factory of the pool and also contract that has ability\n * to set and collect protocol fees for the pool.\n */\n function factory() external view returns (IMaverickV2Factory);\n\n /**\n * @notice Most significant bit of scale value is a flag to indicate whether\n * tokenA has more or less than 18 decimals. Scale is used in conjuction\n * with Math.toScale/Math.fromScale functions to convert from token amounts\n * to D18 scale internal pool accounting.\n */\n function tokenAScale() external view returns (uint256);\n\n /**\n * @notice Most significant bit of scale value is a flag to indicate whether\n * tokenA has more or less than 18 decimals. Scale is used in conjuction\n * with Math.toScale/Math.fromScale functions to convert from token amounts\n * to D18 scale internal pool accounting.\n */\n function tokenBScale() external view returns (uint256);\n\n /**\n * @notice ID of bin at input tick position and kind.\n */\n function binIdByTickKind(int32 tick, uint256 kind)\n external\n view\n returns (uint32);\n\n /**\n * @notice Accumulated tokenA protocol fee.\n */\n function protocolFeeA() external view returns (uint128);\n\n /**\n * @notice Accumulated tokenB protocol fee.\n */\n function protocolFeeB() external view returns (uint128);\n\n /**\n * @notice Lending fee rate on flash loans.\n */\n function lendingFeeRateD18() external view returns (uint256);\n\n /**\n * @notice External function to get the current time-weighted average price.\n */\n function getCurrentTwa() external view returns (int256);\n\n /**\n * @notice External function to get the state of the pool.\n */\n function getState() external view returns (State memory);\n\n /**\n * @notice Return state of Bin at input binId.\n */\n function getBin(uint32 binId) external view returns (BinState memory bin);\n\n /**\n * @notice Return state of Tick at input tick position.\n */\n function getTick(int32 tick)\n external\n view\n returns (TickState memory tickState);\n\n /**\n * @notice Retrieves the balance of a user within a bin.\n * @param user The user's address.\n * @param subaccount The subaccount for the user.\n * @param binId The ID of the bin.\n */\n function balanceOf(\n address user,\n uint256 subaccount,\n uint32 binId\n ) external view returns (uint128 lpToken);\n\n /**\n * @notice Add liquidity to a pool. This function allows users to deposit\n * tokens into a liquidity pool.\n * @dev This function will call `maverickV2AddLiquidityCallback` on the\n * calling contract to collect the tokenA/tokenB payment.\n * @param recipient The account that will receive credit for the added liquidity.\n * @param subaccount The account that will receive credit for the added liquidity.\n * @param params Parameters containing the details for adding liquidity,\n * such as token types and amounts.\n * @param data Bytes information that gets passed to the callback.\n * @return tokenAAmount The amount of token A added to the pool.\n * @return tokenBAmount The amount of token B added to the pool.\n * @return binIds An array of bin IDs where the liquidity is stored.\n */\n function addLiquidity(\n address recipient,\n uint256 subaccount,\n AddLiquidityParams calldata params,\n bytes calldata data\n )\n external\n returns (\n uint256 tokenAAmount,\n uint256 tokenBAmount,\n uint32[] memory binIds\n );\n\n /**\n * @notice Removes liquidity from the pool.\n * @dev Liquidy can only be removed from a bin that is either unmerged or\n * has a mergeId of an unmerged bin. If a bin is merged more than one\n * level deep, it must be migrated up the merge stack to the root bin\n * before liquidity removal.\n * @param recipient The address to receive the tokens.\n * @param subaccount The subaccount for the recipient.\n * @param params The parameters for removing liquidity.\n * @return tokenAOut The amount of token A received.\n * @return tokenBOut The amount of token B received.\n */\n function removeLiquidity(\n address recipient,\n uint256 subaccount,\n RemoveLiquidityParams calldata params\n ) external returns (uint256 tokenAOut, uint256 tokenBOut);\n\n /**\n * @notice Migrate bins up the linked list of merged bins so that its\n * mergeId is the currrent active bin.\n * @dev Liquidy can only be removed from a bin that is either unmerged or\n * has a mergeId of an unmerged bin. If a bin is merged more than one\n * level deep, it must be migrated up the merge stack to the root bin\n * before liquidity removal.\n * @param binId The ID of the bin to migrate.\n * @param maxRecursion The maximum recursion depth for the migration.\n */\n function migrateBinUpStack(uint32 binId, uint32 maxRecursion) external;\n\n /**\n * @notice Swap tokenA/tokenB assets in the pool. The swap user has two\n * options for funding their swap.\n * - The user can push the input token amount to the pool before calling\n * the swap function. In order to avoid having the pool call the callback,\n * the user should pass a zero-length `data` bytes object with the swap\n * call.\n * - The user can send the input token amount to the pool when the pool\n * calls the `maverickV2SwapCallback` function on the calling contract.\n * That callback has input parameters that specify the token address of the\n * input token, the input and output amounts, and the bytes data sent to\n * the swap function.\n * @dev If the users elects to do a callback-based swap, the output\n * assets will be sent before the callback is called, allowing the user to\n * execute flash swaps. However, the pool does have reentrancy protection,\n * so a swapper will not be able to interact with the same pool again\n * while they are in the callback function.\n * @param recipient The address to receive the output tokens.\n * @param params Parameters containing the details of the swap\n * @param data Bytes information that gets passed to the callback.\n */\n function swap(\n address recipient,\n SwapParams memory params,\n bytes calldata data\n ) external returns (uint256 amountIn, uint256 amountOut);\n\n /**\n * @notice Loan tokenA/tokenB assets from the pool to recipient. The fee\n * rate of a loan is determined by `lendingFeeRateD18`, which is set at the\n * protocol level by the factory. This function calls\n * `maverickV2FlashLoanCallback` on the calling contract. At the end of\n * the callback, the caller must pay back the loan with fee (if there is a\n * fee).\n * @param recipient The address to receive the loaned tokens.\n * @param amountB Loan amount of tokenA sent to recipient.\n * @param amountB Loan amount of tokenB sent to recipient.\n * @param data Bytes information that gets passed to the callback.\n */\n function flashLoan(\n address recipient,\n uint256 amountA,\n uint256 amountB,\n bytes calldata data\n ) external;\n\n /**\n * @notice Distributes accumulated protocol fee to factory protocolFeeReceiver\n */\n function distributeFees(bool isTokenA)\n external\n returns (uint256 protocolFee, IERC20 token);\n}\n" + }, + "contracts/interfaces/plume/IMaverickV2PoolLens.sol": { + "content": "// SPDX-License-Identifier: GPL-2.0-or-later\npragma solidity ^0.8.25;\n\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport { IMaverickV2Factory } from \"./IMaverickV2Factory.sol\";\nimport { IMaverickV2Pool } from \"./IMaverickV2Pool.sol\";\n\ninterface IMaverickV2PoolLens {\n error LensTargetPriceOutOfBounds(\n uint256 targetSqrtPrice,\n uint256 sqrtLowerTickPrice,\n uint256 sqrtUpperTickPrice\n );\n error LensTooLittleLiquidity(\n uint256 relativeLiquidityAmount,\n uint256 deltaA,\n uint256 deltaB\n );\n error LensTargetingTokenWithNoDelta(\n bool targetIsA,\n uint256 deltaA,\n uint256 deltaB\n );\n\n /**\n * @notice Add liquidity slippage parameters for a distribution of liquidity.\n * @param pool Pool where liquidity is being added.\n * @param kind Bin kind; all bins must have the same kind in a given call\n * to addLiquidity.\n * @param ticks Array of tick values to add liquidity to.\n * @param relativeLiquidityAmounts Relative liquidity amounts for the\n * specified ticks. Liquidity in this case is not bin LP balance, it is\n * the bin liquidity as defined by liquidity = deltaA / (sqrt(upper) -\n * sqrt(lower)) or deltaB = liquidity / sqrt(lower) - liquidity /\n * sqrt(upper).\n * @param addSpec Slippage specification.\n */\n struct AddParamsViewInputs {\n IMaverickV2Pool pool;\n uint8 kind;\n int32[] ticks;\n uint128[] relativeLiquidityAmounts;\n AddParamsSpecification addSpec;\n }\n\n /**\n * @notice Multi-price add param specification.\n * @param slippageFactorD18 Max slippage allowed as a percent in D18 scale. e.g. 1% slippage is 0.01e18\n * @param numberOfPriceBreaksPerSide Number of price break values on either\n * side of current price.\n * @param targetAmount Target token contribution amount in tokenA if\n * targetIsA is true, otherwise this is the target amount for tokenB.\n * @param targetIsA Indicates if the target amount is for tokenA or tokenB\n */\n struct AddParamsSpecification {\n uint256 slippageFactorD18;\n uint256 numberOfPriceBreaksPerSide;\n uint256 targetAmount;\n bool targetIsA;\n }\n\n /**\n * @notice Specification for deriving create pool parameters. Creating a\n * pool in the liquidity manager has several steps:\n *\n * - Deploy pool\n * - Donate a small amount of initial liquidity in the activeTick\n * - Execute a small swap to set the pool price to the desired value\n * - Add liquidity\n *\n * In order to execute these steps, the caller must specify the parameters\n * of each step. The PoolLens has helper function to derive the values\n * used by the LiquidityManager, but this struct is the input to that\n * helper function and represents the core intent of the pool creator.\n *\n * @param fee Fraction of the pool swap amount that is retained as an LP in\n * D18 scale.\n * @param tickSpacing Tick spacing of pool where 1.0001^tickSpacing is the\n * bin width.\n * @param lookback Pool lookback in seconds.\n * @param tokenA Address of tokenA.\n * @param tokenB Address of tokenB.\n * @param activeTick Tick position that contains the active bins.\n * @param kinds 1-15 number to represent the active kinds\n * 0b0001 = static;\n * 0b0010 = right;\n * 0b0100 = left;\n * 0b1000 = both.\n * e.g. a pool with all 4 modes will have kinds = b1111 = 15\n * @param initialTargetB Amount of B to be donated to the pool after pool\n * create. This amount needs to be big enough to meet the minimum bin\n * liquidity.\n * @param sqrtPrice Target sqrt price of the pool.\n * @param kind Bin kind; all bins must have the same kind in a given call\n * to addLiquidity.\n * @param ticks Array of tick values to add liquidity to.\n * @param relativeLiquidityAmounts Relative liquidity amounts for the\n * specified ticks. Liquidity in this case is not bin LP balance, it is\n * the bin liquidity as defined by liquidity = deltaA / (sqrt(upper) -\n * sqrt(lower)) or deltaB = liquidity / sqrt(lower) - liquidity /\n * sqrt(upper).\n * @param targetAmount Target token contribution amount in tokenA if\n * targetIsA is true, otherwise this is the target amount for tokenB.\n * @param targetIsA Indicates if the target amount is for tokenA or tokenB\n */\n struct CreateAndAddParamsViewInputs {\n uint64 feeAIn;\n uint64 feeBIn;\n uint16 tickSpacing;\n uint32 lookback;\n IERC20 tokenA;\n IERC20 tokenB;\n int32 activeTick;\n uint8 kinds;\n // donate params\n uint256 initialTargetB;\n uint256 sqrtPrice;\n // add target\n uint8 kind;\n int32[] ticks;\n uint128[] relativeLiquidityAmounts;\n uint256 targetAmount;\n bool targetIsA;\n }\n\n struct Output {\n uint256 deltaAOut;\n uint256 deltaBOut;\n uint256[] deltaAs;\n uint256[] deltaBs;\n uint128[] deltaLpBalances;\n }\n\n struct Reserves {\n uint256 amountA;\n uint256 amountB;\n }\n\n struct BinPositionKinds {\n uint128[4] values;\n }\n\n struct PoolState {\n IMaverickV2Pool.TickState[] tickStateMapping;\n IMaverickV2Pool.BinState[] binStateMapping;\n BinPositionKinds[] binIdByTickKindMapping;\n IMaverickV2Pool.State state;\n Reserves protocolFees;\n }\n\n struct BoostedPositionSpecification {\n IMaverickV2Pool pool;\n uint32[] binIds;\n uint128[] ratios;\n uint8 kind;\n }\n\n struct CreateAndAddParamsInputs {\n uint64 feeAIn;\n uint64 feeBIn;\n uint16 tickSpacing;\n uint32 lookback;\n IERC20 tokenA;\n IERC20 tokenB;\n int32 activeTick;\n uint8 kinds;\n // donate params\n IMaverickV2Pool.AddLiquidityParams donateParams;\n // swap params\n uint256 swapAmount;\n // add params\n IMaverickV2Pool.AddLiquidityParams addParams;\n bytes[] packedAddParams;\n uint256 deltaAOut;\n uint256 deltaBOut;\n uint256 preAddReserveA;\n uint256 preAddReserveB;\n }\n\n struct TickDeltas {\n uint256 deltaAOut;\n uint256 deltaBOut;\n uint256[] deltaAs;\n uint256[] deltaBs;\n }\n\n /**\n * @notice Converts add parameter slippage specification into add\n * parameters. The return values are given in both raw format and as packed\n * values that can be used in the LiquidityManager contract.\n */\n function getAddLiquidityParams(AddParamsViewInputs memory params)\n external\n view\n returns (\n bytes memory packedSqrtPriceBreaks,\n bytes[] memory packedArgs,\n uint88[] memory sqrtPriceBreaks,\n IMaverickV2Pool.AddLiquidityParams[] memory addParams,\n IMaverickV2PoolLens.TickDeltas[] memory tickDeltas\n );\n\n /**\n * @notice Converts add parameter slippage specification and new pool\n * specification into CreateAndAddParamsInputs parameters that can be used in the\n * LiquidityManager contract.\n */\n function getCreatePoolAtPriceAndAddLiquidityParams(\n CreateAndAddParamsViewInputs memory params\n ) external view returns (CreateAndAddParamsInputs memory output);\n\n /**\n * @notice View function that provides information about pool ticks within\n * a tick radius from the activeTick. Ticks with no reserves are not\n * included in part o f the return array.\n */\n function getTicksAroundActive(IMaverickV2Pool pool, int32 tickRadius)\n external\n view\n returns (\n int32[] memory ticks,\n IMaverickV2Pool.TickState[] memory tickStates\n );\n\n /**\n * @notice View function that provides information about pool ticks within\n * a range. Ticks with no reserves are not included in part o f the return\n * array.\n */\n function getTicks(\n IMaverickV2Pool pool,\n int32 tickStart,\n int32 tickEnd\n )\n external\n view\n returns (\n int32[] memory ticks,\n IMaverickV2Pool.TickState[] memory tickStates\n );\n\n /**\n * @notice View function that provides information about pool ticks within\n * a range. Information returned includes all pool state needed to emulate\n * a swap off chain. Ticks with no reserves are not included in part o f\n * the return array.\n */\n function getTicksAroundActiveWLiquidity(\n IMaverickV2Pool pool,\n int32 tickRadius\n )\n external\n view\n returns (\n int32[] memory ticks,\n IMaverickV2Pool.TickState[] memory tickStates,\n uint256[] memory liquidities,\n uint256[] memory sqrtLowerTickPrices,\n uint256[] memory sqrtUpperTickPrices,\n IMaverickV2Pool.State memory poolState,\n uint256 sqrtPrice,\n uint256 feeAIn,\n uint256 feeBIn\n );\n\n /**\n * @notice View function that provides pool state information.\n */\n function getFullPoolState(\n IMaverickV2Pool pool,\n uint32 binStart,\n uint32 binEnd\n ) external view returns (PoolState memory poolState);\n\n /**\n * @notice View function that provides price and liquidity of a given tick.\n */\n function getTickSqrtPriceAndL(IMaverickV2Pool pool, int32 tick)\n external\n view\n returns (uint256 sqrtPrice, uint256 liquidity);\n\n /**\n * @notice Pool sqrt price.\n */\n function getPoolSqrtPrice(IMaverickV2Pool pool)\n external\n view\n returns (uint256 sqrtPrice);\n\n /**\n * @notice Pool price.\n */\n function getPoolPrice(IMaverickV2Pool pool)\n external\n view\n returns (uint256 price);\n\n /**\n * @notice Token scale of two tokens in a pool.\n */\n function tokenScales(IMaverickV2Pool pool)\n external\n view\n returns (uint256 tokenAScale, uint256 tokenBScale);\n}\n" + }, + "contracts/interfaces/plume/IMaverickV2Position.sol": { + "content": "// SPDX-License-Identifier: GPL-2.0-or-later\npragma solidity ^0.8.25;\n\nimport { IMaverickV2Factory } from \"./IMaverickV2Factory.sol\";\nimport { IMaverickV2Pool } from \"./IMaverickV2Pool.sol\";\nimport { ILiquidityRegistry } from \"./ILiquidityRegistry.sol\";\n\ninterface IMaverickV2Position {\n event PositionClearData(uint256 indexed tokenId);\n event PositionSetData(\n uint256 indexed tokenId,\n uint256 index,\n PositionPoolBinIds newData\n );\n event SetLpReward(ILiquidityRegistry lpReward);\n\n error PositionDuplicatePool(uint256 index, IMaverickV2Pool pool);\n error PositionNotFactoryPool();\n error PositionPermissionedLiquidityPool();\n\n struct PositionPoolBinIds {\n IMaverickV2Pool pool;\n uint32[] binIds;\n }\n\n struct PositionFullInformation {\n PositionPoolBinIds poolBinIds;\n uint256 amountA;\n uint256 amountB;\n uint256[] binAAmounts;\n uint256[] binBAmounts;\n int32[] ticks;\n uint256[] liquidities;\n }\n\n /**\n * @notice Factory that tracks lp rewards.\n */\n function lpReward() external view returns (ILiquidityRegistry);\n\n /**\n * @notice Pool factory.\n */\n function factory() external view returns (IMaverickV2Factory);\n\n /**\n * @notice Mint NFT that holds liquidity in a Maverick V2 Pool. To mint\n * liquidity to an NFT, add liquidity to bins in a pool where the\n * add liquidity recipient is this contract and the subaccount is the\n * tokenId. LiquidityManager can be used to simplify minting Position NFTs.\n */\n function mint(\n address recipient,\n IMaverickV2Pool pool,\n uint32[] memory binIds\n ) external returns (uint256 tokenId);\n\n /**\n * @notice Overwrites tokenId pool/binId information for a given data index.\n */\n function setTokenIdData(\n uint256 tokenId,\n uint256 index,\n IMaverickV2Pool pool,\n uint32[] memory binIds\n ) external;\n\n /**\n * @notice Overwrites entire pool/binId data set for a given tokenId.\n */\n function setTokenIdData(uint256 tokenId, PositionPoolBinIds[] memory data)\n external;\n\n /**\n * @notice Append new pool/binIds data array to tokenId.\n */\n function appendTokenIdData(\n uint256 tokenId,\n IMaverickV2Pool pool,\n uint32[] memory binIds\n ) external;\n\n /**\n * @notice Get array pool/binIds data for a given tokenId.\n */\n function getTokenIdData(uint256 tokenId)\n external\n view\n returns (PositionPoolBinIds[] memory);\n\n /**\n * @notice Get value from array of pool/binIds data for a given tokenId.\n */\n function getTokenIdData(uint256 tokenId, uint256 index)\n external\n view\n returns (PositionPoolBinIds memory);\n\n /**\n * @notice Length of array of pool/binIds data for a given tokenId.\n */\n function tokenIdDataLength(uint256 tokenId)\n external\n view\n returns (uint256 length);\n\n /**\n * @notice Remove liquidity from tokenId for a given pool. User can\n * specify arbitrary bins to remove from for their subaccount in the pool\n * even if those bins are not in the tokenIdData set.\n */\n function removeLiquidity(\n uint256 tokenId,\n address recipient,\n IMaverickV2Pool pool,\n IMaverickV2Pool.RemoveLiquidityParams memory params\n ) external returns (uint256 tokenAAmount, uint256 tokenBAmount);\n\n /**\n * @notice Remove liquidity from tokenId for a given pool to sender. User\n * can specify arbitrary bins to remove from for their subaccount in the\n * pool even if those bins are not in the tokenIdData set.\n */\n function removeLiquidityToSender(\n uint256 tokenId,\n IMaverickV2Pool pool,\n IMaverickV2Pool.RemoveLiquidityParams memory params\n ) external returns (uint256 tokenAAmount, uint256 tokenBAmount);\n\n /**\n * @notice NFT asset information for a given range of pool/binIds indexes.\n * This function only returns the liquidity in the pools/binIds stored as\n * part of the tokenIdData, but it is possible that the NFT has additional\n * liquidity in pools/binIds that have not been recorded.\n */\n function tokenIdPositionInformation(\n uint256 tokenId,\n uint256 startIndex,\n uint256 stopIndex\n ) external view returns (PositionFullInformation[] memory output);\n\n /**\n * @notice NFT asset information for a given pool/binIds index. This\n * function only returns the liquidity in the pools/binIds stored as part\n * of the tokenIdData, but it is possible that the NFT has additional\n * liquidity in pools/binIds that have not been recorded.\n */\n function tokenIdPositionInformation(uint256 tokenId, uint256 index)\n external\n view\n returns (PositionFullInformation memory output);\n\n /**\n * @notice Get remove parameters for removing a fractional part of the\n * liquidity owned by a given tokenId. The fractional factor to remove is\n * given by proporationD18 in 18-decimal scale.\n */\n function getRemoveParams(\n uint256 tokenId,\n uint256 index,\n uint256 proportionD18\n )\n external\n view\n returns (IMaverickV2Pool.RemoveLiquidityParams memory params);\n\n /**\n * @notice Register the bin balances in the nft with the LpReward contract.\n */\n function checkpointBinLpBalance(\n uint256 tokenId,\n IMaverickV2Pool pool,\n uint32[] memory binIds\n ) external;\n}\n" + }, + "contracts/interfaces/plume/IMaverickV2Quoter.sol": { + "content": "// SPDX-License-Identifier: GPL-2.0-or-later\npragma solidity ^0.8.25;\n\nimport { IMaverickV2Pool } from \"./IMaverickV2Pool.sol\";\n\ninterface IMaverickV2Quoter {\n error QuoterInvalidSwap();\n error QuoterInvalidAddLiquidity();\n\n /**\n * @notice Calculates a swap on a MaverickV2Pool and returns the resulting\n * amount and estimated gas. The gas estimate is only a rough estimate and\n * may not match a swap's gas.\n * @param pool The MaverickV2Pool to swap on.\n * @param amount The input amount.\n * @param tokenAIn Indicates if token A is the input token.\n * @param exactOutput Indicates if the amount is the output amount (true)\n * or input amount (false). If the tickLimit is reached, the full value of\n * the exactOutput may not be returned because the pool will stop swapping\n * before the whole order is filled.\n * @param tickLimit The tick limit for the swap. Once the swap lands in\n * this tick, it will stop and return the output amount swapped up to that\n * tick.\n */\n function calculateSwap(\n IMaverickV2Pool pool,\n uint128 amount,\n bool tokenAIn,\n bool exactOutput,\n int32 tickLimit\n )\n external\n returns (\n uint256 amountIn,\n uint256 amountOut,\n uint256 gasEstimate\n );\n\n /**\n * @notice Calculates a multihop swap and returns the resulting amount and\n * estimated gas. The gas estimate is only a rough estimate and\n * may not match a swap's gas.\n * @param path The path of pools to swap through. Path is given by an\n * packed array of (pool, tokenAIn) tuples. So each step in the path is 160\n * + 8 = 168 bits of data. e.g. path = abi.encodePacked(pool1, true, pool2, false);\n * @param amount The input amount.\n * @param exactOutput A boolean indicating if exact output is required.\n */\n function calculateMultiHopSwap(\n bytes memory path,\n uint256 amount,\n bool exactOutput\n ) external returns (uint256 returnAmount, uint256 gasEstimate);\n\n /**\n * @notice Computes the token amounts required for a given set of\n * addLiquidity parameters. The gas estimate is only a rough estimate and\n * may not match a add's gas.\n */\n function calculateAddLiquidity(\n IMaverickV2Pool pool,\n IMaverickV2Pool.AddLiquidityParams calldata params\n )\n external\n returns (\n uint256 amountA,\n uint256 amountB,\n uint256 gasEstimate\n );\n\n /**\n * @notice Pool's sqrt price.\n */\n function poolSqrtPrice(IMaverickV2Pool pool)\n external\n view\n returns (uint256 sqrtPrice);\n}\n" + }, + "contracts/interfaces/plume/IPoolDistributor.sol": { + "content": "// SPDX-License-Identifier: GPL-2.0-or-later\npragma solidity ^0.8.0;\n\nimport { IMaverickV2Pool } from \"./IMaverickV2Pool.sol\";\n\ninterface IPoolDistributor {\n function rewardToken() external view returns (address);\n\n function claimLp(\n address recipient,\n uint256 tokenId,\n IMaverickV2Pool pool,\n uint32[] memory binIds,\n uint256 epoch\n ) external returns (uint256 amount);\n}\n" + }, + "contracts/interfaces/plume/IVotingDistributor.sol": { + "content": "// SPDX-License-Identifier: GPL-2.0-or-later\npragma solidity ^0.8.0;\n\ninterface IVotingDistributor {\n function lastEpoch() external view returns (uint256);\n}\n" + }, + "contracts/interfaces/poolBooster/IPoolBoostCentralRegistry.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\ninterface IPoolBoostCentralRegistry {\n /**\n * @dev all the supported pool booster types are listed here. It is possible\n * to have multiple versions of the factory that supports the same type of\n * pool booster. Factories are immutable and this can happen when a factory\n * or related pool booster required code update.\n * e.g. \"PoolBoosterSwapxDouble\" & \"PoolBoosterSwapxDouble_v2\"\n */\n enum PoolBoosterType {\n // Supports bribing 2 contracts per pool. Appropriate for Ichi vault concentrated\n // liquidity pools where (which is expected in most/all cases) both pool gauges\n // require bribing.\n SwapXDoubleBooster,\n // Supports bribing a single contract per pool. Appropriate for Classic Stable &\n // Classic Volatile pools and Ichi vaults where only 1 side (1 of the 2 gauges)\n // needs bribing\n SwapXSingleBooster,\n // Supports bribing a single contract per pool. Appropriate for Metropolis pools\n MetropolisBooster,\n // Supports creating a Merkl campaign.\n MerklBooster,\n // Supports creating a plain Curve pool booster\n CurvePoolBoosterPlain\n }\n\n struct PoolBoosterEntry {\n address boosterAddress;\n address ammPoolAddress;\n PoolBoosterType boosterType;\n }\n\n event PoolBoosterCreated(\n address poolBoosterAddress,\n address ammPoolAddress,\n PoolBoosterType poolBoosterType,\n address factoryAddress\n );\n event PoolBoosterRemoved(address poolBoosterAddress);\n\n function emitPoolBoosterCreated(\n address _poolBoosterAddress,\n address _ammPoolAddress,\n PoolBoosterType _boosterType\n ) external;\n\n function emitPoolBoosterRemoved(address _poolBoosterAddress) external;\n}\n" + }, + "contracts/interfaces/sonic/ISFC.sol": { + "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.8.0;\n\n/**\n * @title Special Fee Contract for Sonic network\n * @notice The SFC maintains a list of validators and delegators and distributes rewards to them.\n * @custom:security-contact security@fantom.foundation\n */\ninterface ISFC {\n error StakeIsFullySlashed();\n\n event CreatedValidator(\n uint256 indexed validatorID,\n address indexed auth,\n uint256 createdEpoch,\n uint256 createdTime\n );\n event Delegated(\n address indexed delegator,\n uint256 indexed validatorID,\n uint256 amount\n );\n event Undelegated(\n address indexed delegator,\n uint256 indexed validatorID,\n uint256 indexed wrID,\n uint256 amount\n );\n event Withdrawn(\n address indexed delegator,\n uint256 indexed validatorID,\n uint256 indexed wrID,\n uint256 amount,\n uint256 penalty\n );\n event ClaimedRewards(\n address indexed delegator,\n uint256 indexed validatorID,\n uint256 rewards\n );\n event RestakedRewards(\n address indexed delegator,\n uint256 indexed validatorID,\n uint256 rewards\n );\n event BurntFTM(uint256 amount);\n event UpdatedSlashingRefundRatio(\n uint256 indexed validatorID,\n uint256 refundRatio\n );\n event RefundedSlashedLegacyDelegation(\n address indexed delegator,\n uint256 indexed validatorID,\n uint256 amount\n );\n\n event DeactivatedValidator(\n uint256 indexed validatorID,\n uint256 deactivatedEpoch,\n uint256 deactivatedTime\n );\n event ChangedValidatorStatus(uint256 indexed validatorID, uint256 status);\n event AnnouncedRedirection(address indexed from, address indexed to);\n\n function currentSealedEpoch() external view returns (uint256);\n\n function getEpochSnapshot(uint256 epoch)\n external\n view\n returns (\n uint256 endTime,\n uint256 endBlock,\n uint256 epochFee,\n uint256 baseRewardPerSecond,\n uint256 totalStake,\n uint256 totalSupply\n );\n\n function getStake(address delegator, uint256 validatorID)\n external\n view\n returns (uint256);\n\n function getValidator(uint256 validatorID)\n external\n view\n returns (\n uint256 status,\n uint256 receivedStake,\n address auth,\n uint256 createdEpoch,\n uint256 createdTime,\n uint256 deactivatedTime,\n uint256 deactivatedEpoch\n );\n\n function getValidatorID(address auth) external view returns (uint256);\n\n function getValidatorPubkey(uint256 validatorID)\n external\n view\n returns (bytes memory);\n\n function pubkeyAddressvalidatorID(address pubkeyAddress)\n external\n view\n returns (uint256);\n\n function getWithdrawalRequest(\n address delegator,\n uint256 validatorID,\n uint256 wrID\n )\n external\n view\n returns (\n uint256 epoch,\n uint256 time,\n uint256 amount\n );\n\n function isOwner() external view returns (bool);\n\n function lastValidatorID() external view returns (uint256);\n\n function minGasPrice() external view returns (uint256);\n\n function owner() external view returns (address);\n\n function renounceOwnership() external;\n\n function slashingRefundRatio(uint256 validatorID)\n external\n view\n returns (uint256);\n\n function stashedRewardsUntilEpoch(address delegator, uint256 validatorID)\n external\n view\n returns (uint256);\n\n function totalActiveStake() external view returns (uint256);\n\n function totalStake() external view returns (uint256);\n\n function totalSupply() external view returns (uint256);\n\n function transferOwnership(address newOwner) external;\n\n function treasuryAddress() external view returns (address);\n\n function version() external pure returns (bytes3);\n\n function currentEpoch() external view returns (uint256);\n\n function updateConstsAddress(address v) external;\n\n function constsAddress() external view returns (address);\n\n function getEpochValidatorIDs(uint256 epoch)\n external\n view\n returns (uint256[] memory);\n\n function getEpochReceivedStake(uint256 epoch, uint256 validatorID)\n external\n view\n returns (uint256);\n\n function getEpochAccumulatedRewardPerToken(\n uint256 epoch,\n uint256 validatorID\n ) external view returns (uint256);\n\n function getEpochAccumulatedUptime(uint256 epoch, uint256 validatorID)\n external\n view\n returns (uint256);\n\n function getEpochAverageUptime(uint256 epoch, uint256 validatorID)\n external\n view\n returns (uint32);\n\n function getEpochAccumulatedOriginatedTxsFee(\n uint256 epoch,\n uint256 validatorID\n ) external view returns (uint256);\n\n function getEpochOfflineTime(uint256 epoch, uint256 validatorID)\n external\n view\n returns (uint256);\n\n function getEpochOfflineBlocks(uint256 epoch, uint256 validatorID)\n external\n view\n returns (uint256);\n\n function getEpochEndBlock(uint256 epoch) external view returns (uint256);\n\n function rewardsStash(address delegator, uint256 validatorID)\n external\n view\n returns (uint256);\n\n function createValidator(bytes calldata pubkey) external payable;\n\n function getSelfStake(uint256 validatorID) external view returns (uint256);\n\n function delegate(uint256 validatorID) external payable;\n\n function undelegate(\n uint256 validatorID,\n uint256 wrID,\n uint256 amount\n ) external;\n\n function isSlashed(uint256 validatorID) external view returns (bool);\n\n function withdraw(uint256 validatorID, uint256 wrID) external;\n\n function deactivateValidator(uint256 validatorID, uint256 status) external;\n\n function pendingRewards(address delegator, uint256 validatorID)\n external\n view\n returns (uint256);\n\n function stashRewards(address delegator, uint256 validatorID) external;\n\n function claimRewards(uint256 validatorID) external;\n\n function restakeRewards(uint256 validatorID) external;\n\n function updateSlashingRefundRatio(uint256 validatorID, uint256 refundRatio)\n external;\n\n function updateTreasuryAddress(address v) external;\n\n function burnFTM(uint256 amount) external;\n\n function sealEpoch(\n uint256[] calldata offlineTime,\n uint256[] calldata offlineBlocks,\n uint256[] calldata uptimes,\n uint256[] calldata originatedTxsFee\n ) external;\n\n function sealEpochValidators(uint256[] calldata nextValidatorIDs) external;\n\n function initialize(\n uint256 sealedEpoch,\n uint256 _totalSupply,\n address nodeDriver,\n address consts,\n address _owner\n ) external;\n\n function setGenesisValidator(\n address auth,\n uint256 validatorID,\n bytes calldata pubkey,\n uint256 createdTime\n ) external;\n\n function setGenesisDelegation(\n address delegator,\n uint256 validatorID,\n uint256 stake\n ) external;\n\n function updateStakeSubscriberAddress(address v) external;\n\n function stakeSubscriberAddress() external view returns (address);\n\n function setRedirectionAuthorizer(address v) external;\n\n function announceRedirection(address to) external;\n\n function initiateRedirection(address from, address to) external;\n\n function redirect(address to) external;\n}\n" + }, + "contracts/interfaces/sonic/ISwapXGauge.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ninterface IGauge {\n function owner() external view returns (address);\n\n function TOKEN() external view returns (address);\n\n function DISTRIBUTION() external view returns (address);\n\n function balanceOf(address account) external view returns (uint256);\n\n function claimFees() external returns (uint256 claimed0, uint256 claimed1);\n\n function deposit(uint256 amount) external;\n\n function depositAll() external;\n\n function earned(address account) external view returns (uint256);\n\n function getReward() external;\n\n function getReward(address _user) external;\n\n function isForPair() external view returns (bool);\n\n function lastTimeRewardApplicable() external view returns (uint256);\n\n function lastUpdateTime() external view returns (uint256);\n\n function notifyRewardAmount(address token, uint256 reward) external;\n\n function periodFinish() external view returns (uint256);\n\n function rewardForDuration() external view returns (uint256);\n\n function rewardPerToken() external view returns (uint256);\n\n function rewardPerTokenStored() external view returns (uint256);\n\n function rewardRate() external view returns (uint256);\n\n function rewardToken() external view returns (address);\n\n function rewards(address) external view returns (uint256);\n\n function totalSupply() external view returns (uint256);\n\n function userRewardPerTokenPaid(address) external view returns (uint256);\n\n function withdraw(uint256 amount) external;\n\n function withdrawAll() external;\n\n function withdrawAllAndHarvest() external;\n\n function withdrawExcess(address token, uint256 amount) external;\n\n function emergency() external returns (bool);\n\n function emergencyWithdraw() external;\n\n function activateEmergencyMode() external;\n\n function stopEmergencyMode() external;\n}\n" + }, + "contracts/interfaces/sonic/ISwapXPair.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ninterface IPair {\n event Approval(address indexed src, address indexed guy, uint256 wad);\n event Transfer(address indexed src, address indexed dst, uint256 wad);\n\n event Mint(address indexed sender, uint256 amount0, uint256 amount1);\n event Burn(\n address indexed sender,\n uint256 amount0,\n uint256 amount1,\n address indexed to\n );\n event Swap(\n address indexed sender,\n uint256 amount0In,\n uint256 amount1In,\n uint256 amount0Out,\n uint256 amount1Out,\n address indexed to\n );\n event Claim(\n address indexed sender,\n address indexed recipient,\n uint256 amount0,\n uint256 amount1\n );\n\n function metadata()\n external\n view\n returns (\n uint256 dec0,\n uint256 dec1,\n uint256 r0,\n uint256 r1,\n bool st,\n address t0,\n address t1\n );\n\n function claimFees() external returns (uint256, uint256);\n\n function tokens() external view returns (address, address);\n\n function token0() external view returns (address);\n\n function token1() external view returns (address);\n\n function permit(\n address owner,\n address spender,\n uint256 value,\n uint256 deadline,\n uint8 v,\n bytes32 r,\n bytes32 s\n ) external;\n\n function swap(\n uint256 amount0Out,\n uint256 amount1Out,\n address to,\n bytes calldata data\n ) external;\n\n function burn(address to)\n external\n returns (uint256 amount0, uint256 amount1);\n\n function mint(address to) external returns (uint256 liquidity);\n\n function getReserves()\n external\n view\n returns (\n uint256 _reserve0,\n uint256 _reserve1,\n uint256 _blockTimestampLast\n );\n\n function getAmountOut(uint256, address) external view returns (uint256);\n\n // ERC20 methods\n function name() external view returns (string memory);\n\n function symbol() external view returns (string memory);\n\n function decimals() external view returns (uint8);\n\n function totalSupply() external view returns (uint256);\n\n function balanceOf(address) external view returns (uint256);\n\n function transfer(address recipient, uint256 amount)\n external\n returns (bool);\n\n function transferFrom(\n address sender,\n address recipient,\n uint256 amount\n ) external returns (bool);\n\n function allowance(address owner, address spender)\n external\n view\n returns (uint256);\n\n function approve(address spender, uint256 value) external returns (bool);\n\n function claimable0(address _user) external view returns (uint256);\n\n function claimable1(address _user) external view returns (uint256);\n\n function isStable() external view returns (bool);\n\n function skim(address to) external;\n\n function sync() external;\n}\n" + }, + "contracts/interfaces/sonic/IWrappedSonic.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ninterface IWrappedSonic {\n event Deposit(address indexed account, uint256 value);\n event Withdrawal(address indexed account, uint256 value);\n event Transfer(address indexed from, address indexed to, uint256 value);\n event Approval(\n address indexed owner,\n address indexed spender,\n uint256 value\n );\n\n function allowance(address owner, address spender)\n external\n view\n returns (uint256);\n\n function approve(address spender, uint256 value) external returns (bool);\n\n function balanceOf(address account) external view returns (uint256);\n\n function decimals() external view returns (uint8);\n\n function deposit() external payable;\n\n function depositFor(address account) external payable returns (bool);\n\n function totalSupply() external view returns (uint256);\n\n function transfer(address to, uint256 value) external returns (bool);\n\n function transferFrom(\n address from,\n address to,\n uint256 value\n ) external returns (bool);\n\n function withdraw(uint256 value) external;\n\n function withdrawTo(address account, uint256 value) external returns (bool);\n}\n" + }, + "contracts/interfaces/uniswap/IUniswapV2Router02.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ninterface IUniswapV2Router {\n function WETH() external pure returns (address);\n\n function swapExactTokensForTokens(\n uint256 amountIn,\n uint256 amountOutMin,\n address[] calldata path,\n address to,\n uint256 deadline\n ) external returns (uint256[] memory amounts);\n\n function addLiquidity(\n address tokenA,\n address tokenB,\n uint256 amountADesired,\n uint256 amountBDesired,\n uint256 amountAMin,\n uint256 amountBMin,\n address to,\n uint256 deadline\n )\n external\n returns (\n uint256 amountA,\n uint256 amountB,\n uint256 liquidity\n );\n}\n" + }, + "contracts/interfaces/uniswap/IUniswapV3Router.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\n// -- Solididy v0.5.x compatible interface\ninterface IUniswapV3Router {\n struct ExactInputParams {\n bytes path;\n address recipient;\n uint256 deadline;\n uint256 amountIn;\n uint256 amountOutMinimum;\n }\n\n /// @notice Swaps `amountIn` of one token for as much as possible of another along the specified path\n /// @param params The parameters necessary for the multi-hop swap, encoded as `ExactInputParams` in calldata\n /// @return amountOut The amount of the received token\n function exactInput(ExactInputParams calldata params)\n external\n payable\n returns (uint256 amountOut);\n}\n" + }, + "contracts/mocks/crosschain/CCTPMessageTransmitterMock.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { ICCTPMessageTransmitter } from \"../../interfaces/cctp/ICCTP.sol\";\nimport { IERC20 } from \"../../utils/InitializableAbstractStrategy.sol\";\nimport { BytesHelper } from \"../../utils/BytesHelper.sol\";\nimport { AbstractCCTPIntegrator } from \"../../strategies/crosschain/AbstractCCTPIntegrator.sol\";\n\n/**\n * @title Mock conctract simulating the functionality of the CCTPTokenMessenger contract\n * for the porposes of unit testing.\n * @author Origin Protocol Inc\n */\n\ncontract CCTPMessageTransmitterMock is ICCTPMessageTransmitter {\n using BytesHelper for bytes;\n\n IERC20 public usdc;\n uint256 public nonce = 0;\n // Sender index in the burn message v2\n // Ref: https://github.com/circlefin/evm-cctp-contracts/blob/master/src/messages/v2/BurnMessageV2.sol\n uint8 constant BURN_MESSAGE_V2_MESSAGE_SENDER_INDEX = 100;\n uint8 constant BURN_MESSAGE_V2_HOOK_DATA_INDEX = 228;\n\n uint16 public messageFinality = 2000;\n address public messageSender;\n uint32 public sourceDomain = 4294967295; // 0xFFFFFFFF\n\n // Full message with header\n struct Message {\n uint32 version;\n uint32 sourceDomain;\n uint32 destinationDomain;\n bytes32 recipient;\n bytes32 messageHeaderRecipient;\n bytes32 sender;\n bytes32 destinationCaller;\n uint32 minFinalityThreshold;\n bool isTokenTransfer;\n uint256 tokenAmount;\n bytes messageBody;\n }\n\n Message[] public messages;\n // map of encoded messages to the corresponding message structs\n mapping(bytes32 => Message) public encodedMessages;\n\n constructor(address _usdc) {\n usdc = IERC20(_usdc);\n }\n\n // @dev for the porposes of unit tests queues the message to be mock-sent using\n // the cctp bridge.\n function sendMessage(\n uint32 destinationDomain,\n bytes32 recipient,\n bytes32 destinationCaller,\n uint32 minFinalityThreshold,\n bytes memory messageBody\n ) external virtual override {\n bytes32 nonceHash = keccak256(abi.encodePacked(nonce));\n nonce++;\n\n // If destination is mainnet, source is base and vice versa\n uint32 sourceDomain = destinationDomain == 0 ? 6 : 0;\n\n Message memory message = Message({\n version: 1,\n sourceDomain: sourceDomain,\n destinationDomain: destinationDomain,\n recipient: recipient,\n messageHeaderRecipient: recipient,\n sender: bytes32(uint256(uint160(msg.sender))),\n destinationCaller: destinationCaller,\n minFinalityThreshold: minFinalityThreshold,\n isTokenTransfer: false,\n tokenAmount: 0,\n messageBody: messageBody\n });\n\n messages.push(message);\n }\n\n // @dev for the porposes of unit tests queues the USDC burn/mint to be executed\n // using the cctp bridge.\n function sendTokenTransferMessage(\n uint32 destinationDomain,\n bytes32 recipient,\n bytes32 destinationCaller,\n uint32 minFinalityThreshold,\n uint256 tokenAmount,\n bytes memory messageBody\n ) external {\n bytes32 nonceHash = keccak256(abi.encodePacked(nonce));\n nonce++;\n\n // If destination is mainnet, source is base and vice versa\n uint32 sourceDomain = destinationDomain == 0 ? 6 : 0;\n\n Message memory message = Message({\n version: 1,\n sourceDomain: sourceDomain,\n destinationDomain: destinationDomain,\n recipient: recipient,\n messageHeaderRecipient: recipient,\n sender: bytes32(uint256(uint160(msg.sender))),\n destinationCaller: destinationCaller,\n minFinalityThreshold: minFinalityThreshold,\n isTokenTransfer: true,\n tokenAmount: tokenAmount,\n messageBody: messageBody\n });\n\n messages.push(message);\n }\n\n function receiveMessage(bytes memory message, bytes memory attestation)\n public\n virtual\n override\n returns (bool)\n {\n Message memory storedMsg = encodedMessages[keccak256(message)];\n AbstractCCTPIntegrator recipient = AbstractCCTPIntegrator(\n address(uint160(uint256(storedMsg.recipient)))\n );\n\n bytes32 sender = storedMsg.sender;\n bytes memory messageBody = storedMsg.messageBody;\n\n // Credit USDC in this step as it is done in the live cctp contracts\n if (storedMsg.isTokenTransfer) {\n usdc.transfer(address(recipient), storedMsg.tokenAmount);\n // override the sender with the one stored in the Burn message as the sender int he\n // message header is the TokenMessenger.\n sender = bytes32(\n uint256(\n uint160(\n storedMsg.messageBody.extractAddress(\n BURN_MESSAGE_V2_MESSAGE_SENDER_INDEX\n )\n )\n )\n );\n messageBody = storedMsg.messageBody.extractSlice(\n BURN_MESSAGE_V2_HOOK_DATA_INDEX,\n storedMsg.messageBody.length\n );\n } else {\n bytes32 overrideSenderBytes = bytes32(\n uint256(uint160(messageSender))\n );\n if (messageFinality >= 2000) {\n recipient.handleReceiveFinalizedMessage(\n sourceDomain == 4294967295\n ? storedMsg.sourceDomain\n : sourceDomain,\n messageSender == address(0) ? sender : overrideSenderBytes,\n messageFinality,\n messageBody\n );\n } else {\n recipient.handleReceiveUnfinalizedMessage(\n sourceDomain == 4294967295\n ? storedMsg.sourceDomain\n : sourceDomain,\n messageSender == address(0) ? sender : overrideSenderBytes,\n messageFinality,\n messageBody\n );\n }\n }\n\n return true;\n }\n\n function addMessage(Message memory storedMsg) external {\n messages.push(storedMsg);\n }\n\n function _encodeMessageHeader(\n uint32 version,\n uint32 sourceDomain,\n bytes32 sender,\n bytes32 recipient,\n bytes memory messageBody\n ) internal pure returns (bytes memory) {\n bytes memory header = abi.encodePacked(\n version, // 0-3\n sourceDomain, // 4-7\n bytes32(0), // 8-39 destinationDomain\n bytes4(0), // 40-43 nonce\n sender, // 44-75 sender\n recipient, // 76-107 recipient\n bytes32(0), // other stuff\n bytes8(0) // other stuff\n );\n return abi.encodePacked(header, messageBody);\n }\n\n function _removeFront() internal returns (Message memory) {\n require(messages.length > 0, \"No messages\");\n Message memory removed = messages[0];\n // Shift array\n for (uint256 i = 0; i < messages.length - 1; i++) {\n messages[i] = messages[i + 1];\n }\n messages.pop();\n return removed;\n }\n\n function _processMessage(Message memory storedMsg) internal {\n bytes memory encodedMessage = _encodeMessageHeader(\n storedMsg.version,\n storedMsg.sourceDomain,\n storedMsg.sender,\n storedMsg.messageHeaderRecipient,\n storedMsg.messageBody\n );\n\n encodedMessages[keccak256(encodedMessage)] = storedMsg;\n\n address recipient = address(uint160(uint256(storedMsg.recipient)));\n\n AbstractCCTPIntegrator(recipient).relay(encodedMessage, bytes(\"\"));\n }\n\n function _removeBack() internal returns (Message memory) {\n require(messages.length > 0, \"No messages\");\n Message memory last = messages[messages.length - 1];\n messages.pop();\n return last;\n }\n\n function messagesInQueue() external view returns (uint256) {\n return messages.length;\n }\n\n function processFrontOverrideHeader(bytes4 customHeader) external {\n Message memory storedMsg = _removeFront();\n\n bytes memory modifiedBody = abi.encodePacked(\n customHeader,\n storedMsg.messageBody.extractSlice(4, storedMsg.messageBody.length)\n );\n\n storedMsg.messageBody = modifiedBody;\n\n _processMessage(storedMsg);\n }\n\n function processFrontOverrideVersion(uint32 customVersion) external {\n Message memory storedMsg = _removeFront();\n storedMsg.version = customVersion;\n _processMessage(storedMsg);\n }\n\n function processFrontOverrideSender(address customSender) external {\n Message memory storedMsg = _removeFront();\n storedMsg.sender = bytes32(uint256(uint160(customSender)));\n _processMessage(storedMsg);\n }\n\n function processFrontOverrideRecipient(address customRecipient) external {\n Message memory storedMsg = _removeFront();\n storedMsg.messageHeaderRecipient = bytes32(\n uint256(uint160(customRecipient))\n );\n _processMessage(storedMsg);\n }\n\n function processFrontOverrideMessageBody(bytes memory customMessageBody)\n external\n {\n Message memory storedMsg = _removeFront();\n storedMsg.messageBody = customMessageBody;\n _processMessage(storedMsg);\n }\n\n function processFront() external {\n Message memory storedMsg = _removeFront();\n _processMessage(storedMsg);\n }\n\n function processBack() external {\n Message memory storedMsg = _removeBack();\n _processMessage(storedMsg);\n }\n\n function getMessagesLength() external view returns (uint256) {\n return messages.length;\n }\n\n function overrideMessageFinality(uint16 _finality) external {\n messageFinality = _finality;\n }\n\n function overrideSender(address _sender) external {\n messageSender = _sender;\n }\n\n function overrideSourceDomain(uint32 _sourceDomain) external {\n sourceDomain = _sourceDomain;\n }\n}\n" + }, + "contracts/mocks/crosschain/CCTPMessageTransmitterMock2.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { IMessageHandlerV2 } from \"../../interfaces/cctp/ICCTP.sol\";\nimport { BytesHelper } from \"../../utils/BytesHelper.sol\";\nimport { CCTPMessageTransmitterMock } from \"./CCTPMessageTransmitterMock.sol\";\n\nuint8 constant SOURCE_DOMAIN_INDEX = 4;\nuint8 constant RECIPIENT_INDEX = 76;\nuint8 constant SENDER_INDEX = 44;\nuint8 constant MESSAGE_BODY_INDEX = 148;\n\n/**\n * @title Mock conctract simulating the functionality of the CCTPTokenMessenger contract\n * for the porposes of unit testing.\n * @author Origin Protocol Inc\n */\n\ncontract CCTPMessageTransmitterMock2 is CCTPMessageTransmitterMock {\n using BytesHelper for bytes;\n\n address public cctpTokenMessenger;\n\n event MessageReceivedInMockTransmitter(bytes message);\n event MessageSent(bytes message);\n\n constructor(address _usdc) CCTPMessageTransmitterMock(_usdc) {}\n\n function setCCTPTokenMessenger(address _cctpTokenMessenger) external {\n cctpTokenMessenger = _cctpTokenMessenger;\n }\n\n function sendMessage(\n uint32 destinationDomain,\n bytes32 recipient,\n bytes32 destinationCaller,\n uint32 minFinalityThreshold,\n bytes memory messageBody\n ) external virtual override {\n bytes memory message = abi.encodePacked(\n uint32(1), // version\n uint32(destinationDomain == 0 ? 6 : 0), // source domain\n uint32(destinationDomain), // destination domain\n uint256(0),\n bytes32(uint256(uint160(msg.sender))), // sender\n recipient, // recipient\n destinationCaller, // destination caller\n minFinalityThreshold, // min finality threshold\n uint32(0),\n messageBody // message body\n );\n emit MessageSent(message);\n }\n\n function receiveMessage(bytes memory message, bytes memory attestation)\n public\n virtual\n override\n returns (bool)\n {\n uint32 sourceDomain = message.extractUint32(SOURCE_DOMAIN_INDEX);\n address recipient = message.extractAddress(RECIPIENT_INDEX);\n address sender = message.extractAddress(SENDER_INDEX);\n\n bytes memory messageBody = message.extractSlice(\n MESSAGE_BODY_INDEX,\n message.length\n );\n\n bool isBurnMessage = recipient == cctpTokenMessenger;\n\n if (isBurnMessage) {\n // recipient = messageBody.extractAddress(BURN_MESSAGE_V2_RECIPIENT_INDEX);\n // This step won't mint USDC, transfer it to the recipient address\n // in your tests\n } else {\n IMessageHandlerV2(recipient).handleReceiveFinalizedMessage(\n sourceDomain,\n bytes32(uint256(uint160(sender))),\n 2000,\n messageBody\n );\n }\n\n // This step won't mint USDC, transfer it to the recipient address\n // in your tests\n emit MessageReceivedInMockTransmitter(message);\n\n return true;\n }\n}\n" + }, + "contracts/mocks/crosschain/CCTPTokenMessengerMock.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { ICCTPTokenMessenger } from \"../../interfaces/cctp/ICCTP.sol\";\nimport { CCTPMessageTransmitterMock } from \"./CCTPMessageTransmitterMock.sol\";\nimport { IERC20 } from \"../../utils/InitializableAbstractStrategy.sol\";\n\n/**\n * @title Mock conctract simulating the functionality of the CCTPTokenMessenger contract\n * for the porposes of unit testing.\n * @author Origin Protocol Inc\n */\n\ncontract CCTPTokenMessengerMock is ICCTPTokenMessenger {\n IERC20 public usdc;\n CCTPMessageTransmitterMock public cctpMessageTransmitterMock;\n\n constructor(address _usdc, address _cctpMessageTransmitterMock) {\n usdc = IERC20(_usdc);\n cctpMessageTransmitterMock = CCTPMessageTransmitterMock(\n _cctpMessageTransmitterMock\n );\n }\n\n function depositForBurn(\n uint256 amount,\n uint32 destinationDomain,\n bytes32 mintRecipient,\n address burnToken,\n bytes32 destinationCaller,\n uint256 maxFee,\n uint32 minFinalityThreshold\n ) external override {\n revert(\"Not implemented\");\n }\n\n /**\n * @dev mocks the depositForBurnWithHook function by sending the USDC to the CCTPMessageTransmitterMock\n * called by the AbstractCCTPIntegrator contract.\n */\n function depositForBurnWithHook(\n uint256 amount,\n uint32 destinationDomain,\n bytes32 mintRecipient,\n address burnToken,\n bytes32 destinationCaller,\n uint256 maxFee,\n uint32 minFinalityThreshold,\n bytes memory hookData\n ) external override {\n require(burnToken == address(usdc), \"Invalid burn token\");\n\n usdc.transferFrom(msg.sender, address(this), maxFee);\n uint256 destinationAmount = amount - maxFee;\n usdc.transferFrom(\n msg.sender,\n address(cctpMessageTransmitterMock),\n destinationAmount\n );\n\n bytes memory burnMessage = _encodeBurnMessageV2(\n mintRecipient,\n amount,\n msg.sender,\n maxFee,\n maxFee,\n hookData\n );\n\n cctpMessageTransmitterMock.sendTokenTransferMessage(\n destinationDomain,\n mintRecipient,\n destinationCaller,\n minFinalityThreshold,\n destinationAmount,\n burnMessage\n );\n }\n\n function _encodeBurnMessageV2(\n bytes32 mintRecipient,\n uint256 amount,\n address messageSender,\n uint256 maxFee,\n uint256 feeExecuted,\n bytes memory hookData\n ) internal view returns (bytes memory) {\n bytes32 burnTokenBytes32 = bytes32(\n abi.encodePacked(bytes12(0), bytes20(uint160(address(usdc))))\n );\n bytes32 messageSenderBytes32 = bytes32(\n abi.encodePacked(bytes12(0), bytes20(uint160(messageSender)))\n );\n bytes32 expirationBlock = bytes32(0);\n\n // Ref: https://developers.circle.com/cctp/technical-guide#message-body\n return\n abi.encodePacked(\n uint32(1), // 0-3: version\n burnTokenBytes32, // 4-35: burnToken (bytes32 left-padded address)\n mintRecipient, // 36-67: mintRecipient (bytes32 left-padded address)\n amount, // 68-99: uint256 amount\n messageSenderBytes32, // 100-131: messageSender (bytes32 left-padded address)\n maxFee, // 132-163: uint256 maxFee\n feeExecuted, // 164-195: uint256 feeExecuted\n expirationBlock, // 196-227: bytes32 expirationBlock\n hookData // 228+: dynamic hookData\n );\n }\n\n function getMinFeeAmount(uint256 amount)\n external\n view\n override\n returns (uint256)\n {\n return 0;\n }\n}\n" + }, + "contracts/mocks/MintableERC20.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { ERC20 } from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\n\ninterface IMintableERC20 {\n function mint(uint256 value) external;\n\n function mintTo(address to, uint256 value) external;\n}\n\n/**\n * @title MintableERC20\n * @dev Exposes the mint function of ERC20 for tests\n */\nabstract contract MintableERC20 is IMintableERC20, ERC20 {\n /**\n * @dev Function to mint tokens\n * @param _value The amount of tokens to mint.\n */\n function mint(uint256 _value) public virtual override {\n _mint(msg.sender, _value);\n }\n\n /**\n * @dev Function to mint tokens\n * @param _to Address to mint to.\n * @param _value The amount of tokens to mint.\n */\n function mintTo(address _to, uint256 _value) public virtual override {\n _mint(_to, _value);\n }\n}\n" + }, + "contracts/mocks/MockBalancerVault.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport { IBalancerVault } from \"../interfaces/balancer/IBalancerVault.sol\";\nimport { MintableERC20 } from \"./MintableERC20.sol\";\nimport { StableMath } from \"../utils/StableMath.sol\";\n\n// import \"hardhat/console.sol\";\n\ncontract MockBalancerVault {\n using StableMath for uint256;\n uint256 public slippage = 1 ether;\n bool public transferDisabled = false;\n bool public slippageErrorDisabled = false;\n\n function swap(\n IBalancerVault.SingleSwap calldata singleSwap,\n IBalancerVault.FundManagement calldata funds,\n uint256 minAmountOut,\n uint256\n ) external returns (uint256 amountCalculated) {\n amountCalculated = (minAmountOut * slippage) / 1 ether;\n if (!slippageErrorDisabled) {\n require(amountCalculated >= minAmountOut, \"Slippage error\");\n }\n IERC20(singleSwap.assetIn).transferFrom(\n funds.sender,\n address(this),\n singleSwap.amount\n );\n if (!transferDisabled) {\n MintableERC20(singleSwap.assetOut).mintTo(\n funds.recipient,\n amountCalculated\n );\n }\n }\n\n function setSlippage(uint256 _slippage) external {\n slippage = _slippage;\n }\n\n function getPoolTokenInfo(bytes32 poolId, address token)\n external\n view\n returns (\n uint256,\n uint256,\n uint256,\n address\n )\n {}\n\n function disableTransfer() external {\n transferDisabled = true;\n }\n\n function disableSlippageError() external {\n slippageErrorDisabled = true;\n }\n}\n" + }, + "contracts/mocks/MockERC4626Vault.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport { IERC4626 } from \"../../lib/openzeppelin/interfaces/IERC4626.sol\";\nimport { ERC20 } from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\n\ncontract MockERC4626Vault is IERC4626, ERC20 {\n using SafeERC20 for IERC20;\n\n address public asset;\n uint8 public constant DECIMALS = 18;\n\n constructor(address _asset) ERC20(\"Mock Vault Share\", \"MVS\") {\n asset = _asset;\n }\n\n // ERC20 totalSupply is inherited\n\n // ERC20 balanceOf is inherited\n\n function deposit(uint256 assets, address receiver)\n public\n override\n returns (uint256 shares)\n {\n shares = previewDeposit(assets);\n IERC20(asset).safeTransferFrom(msg.sender, address(this), assets);\n _mint(receiver, shares);\n return shares;\n }\n\n function mint(uint256 shares, address receiver)\n public\n override\n returns (uint256 assets)\n {\n assets = previewMint(shares);\n IERC20(asset).safeTransferFrom(msg.sender, address(this), assets);\n _mint(receiver, shares);\n return assets;\n }\n\n function withdraw(\n uint256 assets,\n address receiver,\n address owner\n ) public override returns (uint256 shares) {\n shares = previewWithdraw(assets);\n if (msg.sender != owner) {\n // No approval check for mock\n }\n _burn(owner, shares);\n IERC20(asset).safeTransfer(receiver, assets);\n return shares;\n }\n\n function redeem(\n uint256 shares,\n address receiver,\n address owner\n ) public override returns (uint256 assets) {\n assets = previewRedeem(shares);\n if (msg.sender != owner) {\n // No approval check for mock\n }\n _burn(owner, shares);\n IERC20(asset).safeTransfer(receiver, assets);\n return assets;\n }\n\n function totalAssets() public view override returns (uint256) {\n return IERC20(asset).balanceOf(address(this));\n }\n\n function convertToShares(uint256 assets)\n public\n view\n override\n returns (uint256 shares)\n {\n uint256 supply = totalSupply(); // Use ERC20 totalSupply\n return\n supply == 0 || assets == 0\n ? assets\n : (assets * supply) / totalAssets();\n }\n\n function convertToAssets(uint256 shares)\n public\n view\n override\n returns (uint256 assets)\n {\n uint256 supply = totalSupply(); // Use ERC20 totalSupply\n return supply == 0 ? shares : (shares * totalAssets()) / supply;\n }\n\n function maxDeposit(address receiver)\n public\n view\n override\n returns (uint256)\n {\n return type(uint256).max;\n }\n\n function maxMint(address receiver) public view override returns (uint256) {\n return type(uint256).max;\n }\n\n function maxWithdraw(address owner) public view override returns (uint256) {\n return convertToAssets(balanceOf(owner));\n }\n\n function maxRedeem(address owner) public view override returns (uint256) {\n return balanceOf(owner);\n }\n\n function previewDeposit(uint256 assets)\n public\n view\n override\n returns (uint256 shares)\n {\n return convertToShares(assets);\n }\n\n function previewMint(uint256 shares)\n public\n view\n override\n returns (uint256 assets)\n {\n return convertToAssets(shares);\n }\n\n function previewWithdraw(uint256 assets)\n public\n view\n override\n returns (uint256 shares)\n {\n return convertToShares(assets);\n }\n\n function previewRedeem(uint256 shares)\n public\n view\n override\n returns (uint256 assets)\n {\n return convertToAssets(shares);\n }\n\n function _mint(address account, uint256 amount) internal override {\n super._mint(account, amount);\n }\n\n function _burn(address account, uint256 amount) internal override {\n super._burn(account, amount);\n }\n\n // Inherited from ERC20\n}\n" + }, + "contracts/mocks/MockEvilDAI.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport \"./MintableERC20.sol\";\nimport { IVault } from \"../interfaces/IVault.sol\";\n\ncontract MockEvilDAI is MintableERC20 {\n address host;\n address realCoin;\n\n constructor(address _host, address _realCoin) ERC20(\"DAI\", \"DAI\") {\n host = _host;\n realCoin = _realCoin;\n }\n\n function transferFrom(\n // solhint-disable-next-line no-unused-vars\n address _from,\n // solhint-disable-next-line no-unused-vars\n address _to,\n uint256 _amount\n ) public override returns (bool) {\n // call mint again!\n if (_amount != 69) {\n IVault(host).mint(address(this), 69, 0);\n }\n return true;\n }\n}\n" + }, + "contracts/mocks/MockEvilReentrantContract.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { IVault } from \"../interfaces/IVault.sol\";\nimport { IOracle } from \"../interfaces/IOracle.sol\";\nimport { IRateProvider } from \"../interfaces/balancer/IRateProvider.sol\";\n\nimport { IBalancerVault } from \"../interfaces/balancer/IBalancerVault.sol\";\nimport { IERC20 } from \"../utils/InitializableAbstractStrategy.sol\";\n\nimport { StableMath } from \"../utils/StableMath.sol\";\n\ncontract MockEvilReentrantContract {\n using StableMath for uint256;\n\n IBalancerVault public immutable balancerVault;\n IERC20 public immutable reth;\n IERC20 public immutable weth;\n IVault public immutable oethVault;\n address public immutable poolAddress;\n bytes32 public immutable balancerPoolId;\n address public immutable priceProvider;\n\n constructor(\n address _balancerVault,\n address _oethVault,\n address _reth,\n address _weth,\n address _poolAddress,\n bytes32 _poolId\n ) {\n balancerVault = IBalancerVault(_balancerVault);\n oethVault = IVault(_oethVault);\n reth = IERC20(_reth);\n weth = IERC20(_weth);\n poolAddress = _poolAddress;\n balancerPoolId = _poolId;\n }\n\n function doEvilStuff() public {\n uint256 rethPrice = IOracle(priceProvider).price(address(reth));\n\n // 1. Join pool\n uint256[] memory amounts = new uint256[](2);\n amounts[0] = uint256(10 ether);\n amounts[1] = rethPrice * 10;\n\n address[] memory assets = new address[](2);\n assets[0] = address(reth);\n assets[1] = address(weth);\n\n uint256 minBPT = getBPTExpected(assets, amounts).mulTruncate(\n 0.99 ether\n );\n\n bytes memory joinUserData = abi.encode(\n IBalancerVault.WeightedPoolJoinKind.EXACT_TOKENS_IN_FOR_BPT_OUT,\n amounts,\n minBPT\n );\n\n IBalancerVault.JoinPoolRequest memory joinRequest = IBalancerVault\n .JoinPoolRequest(assets, amounts, joinUserData, false);\n\n balancerVault.joinPool(\n balancerPoolId,\n address(this),\n address(this),\n joinRequest\n );\n\n uint256 bptTokenBalance = IERC20(poolAddress).balanceOf(address(this));\n\n // 2. Redeem as ETH\n bytes memory exitUserData = abi.encode(\n IBalancerVault.WeightedPoolExitKind.EXACT_BPT_IN_FOR_ONE_TOKEN_OUT,\n bptTokenBalance,\n 1\n );\n\n assets[1] = address(0); // Receive ETH instead of WETH\n uint256[] memory exitAmounts = new uint256[](2);\n exitAmounts[1] = 15 ether;\n IBalancerVault.ExitPoolRequest memory exitRequest = IBalancerVault\n .ExitPoolRequest(assets, exitAmounts, exitUserData, false);\n\n balancerVault.exitPool(\n balancerPoolId,\n address(this),\n payable(address(this)),\n exitRequest\n );\n bptTokenBalance = IERC20(poolAddress).balanceOf(address(this));\n }\n\n function getBPTExpected(address[] memory _assets, uint256[] memory _amounts)\n internal\n view\n virtual\n returns (uint256 bptExpected)\n {\n for (uint256 i = 0; i < _assets.length; ++i) {\n uint256 strategyAssetMarketPrice = IOracle(priceProvider).price(\n _assets[i]\n );\n // convert asset amount to ETH amount\n bptExpected =\n bptExpected +\n _amounts[i].mulTruncate(strategyAssetMarketPrice);\n }\n\n uint256 bptRate = IRateProvider(poolAddress).getRate();\n // Convert ETH amount to BPT amount\n bptExpected = bptExpected.divPrecisely(bptRate);\n }\n\n function approveAllTokens() public {\n // Approve all tokens\n weth.approve(address(oethVault), type(uint256).max);\n reth.approve(poolAddress, type(uint256).max);\n weth.approve(poolAddress, type(uint256).max);\n reth.approve(address(balancerVault), type(uint256).max);\n weth.approve(address(balancerVault), type(uint256).max);\n }\n\n receive() external payable {\n // 3. Try to mint OETH\n oethVault.mint(address(weth), 1 ether, 0.9 ether);\n }\n}\n" + }, + "contracts/mocks/MockLimitedWrappedOusd.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { WrappedOusd } from \"../token/WrappedOusd.sol\";\nimport { ERC20 } from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\n\ncontract MockLimitedWrappedOusd is WrappedOusd {\n constructor(ERC20 underlying_) WrappedOusd(underlying_) {}\n\n function maxDeposit(address)\n public\n view\n virtual\n override\n returns (uint256)\n {\n return 1e18;\n }\n}\n" + }, + "contracts/mocks/MockNonRebasing.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\n\nimport { IVault } from \"../interfaces/IVault.sol\";\n\nimport { OUSD } from \"../token/OUSD.sol\";\n\ncontract MockNonRebasing {\n OUSD oUSD;\n\n function setOUSD(address _oUSDAddress) public {\n oUSD = OUSD(_oUSDAddress);\n }\n\n function rebaseOptIn() public {\n oUSD.rebaseOptIn();\n }\n\n function rebaseOptOut() public {\n oUSD.rebaseOptOut();\n }\n\n function transfer(address _to, uint256 _value) public {\n oUSD.transfer(_to, _value);\n }\n\n function transferFrom(\n address _from,\n address _to,\n uint256 _value\n ) public {\n oUSD.transferFrom(_from, _to, _value);\n }\n\n function approve(address _spender, uint256 _addedValue) public {\n oUSD.approve(_spender, _addedValue);\n }\n\n function mintOusd(\n address _vaultContract,\n address _asset,\n uint256 _amount\n ) public {\n IVault(_vaultContract).mint(_asset, _amount, 0);\n }\n\n function redeemOusd(address _vaultContract, uint256 _amount) public {\n IVault(_vaultContract).requestWithdrawal(_amount);\n }\n\n function approveFor(\n address _contract,\n address _spender,\n uint256 _addedValue\n ) public {\n IERC20(_contract).approve(_spender, _addedValue);\n }\n}\n" + }, + "contracts/mocks/MockOETHVault.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { OETHVault } from \"../vault/OETHVault.sol\";\nimport { StableMath } from \"../utils/StableMath.sol\";\nimport \"../utils/Helpers.sol\";\n\ncontract MockOETHVault is OETHVault {\n using StableMath for uint256;\n\n constructor(address _weth) OETHVault(_weth) {\n _setGovernor(msg.sender);\n }\n\n function supportAsset(address asset) external {\n require(asset == asset, \"Only asset supported\");\n }\n}\n" + }, + "contracts/mocks/MockOETHVaultAdmin.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { OETHVault } from \"../vault/OETHVault.sol\";\n\ncontract MockOETHVault is OETHVault {\n constructor(address _weth) OETHVault(_weth) {}\n\n // fetches the WETH amount in outstanding withdrawals\n function outstandingWithdrawalsAmount()\n external\n view\n returns (uint256 wethAmount)\n {\n WithdrawalQueueMetadata memory queue = withdrawalQueueMetadata;\n\n // The amount of WETH that is still to be claimed in the withdrawal queue\n wethAmount = queue.queued - queue.claimed;\n }\n\n function wethAvailable() external view returns (uint256) {\n return _assetAvailable();\n }\n}\n" + }, + "contracts/mocks/MockRebornMinter.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { IVault } from \"../interfaces/IVault.sol\";\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\n// solhint-disable-next-line no-console\nimport \"hardhat/console.sol\";\n\ncontract Sanctum {\n address public asset;\n address public vault;\n address public reborner;\n bool public shouldAttack = false;\n // should selfdestruct in the constructor\n bool public shouldDestruct = false;\n uint256 public targetMethod;\n address public ousdContract;\n\n constructor(address _asset, address _vault) {\n asset = _asset;\n vault = _vault;\n }\n\n function deploy(uint256 salt, bytes memory bytecode)\n public\n returns (address addr)\n {\n // solhint-disable-next-line no-inline-assembly\n assembly {\n addr := create2(0, add(bytecode, 0x20), mload(bytecode), salt)\n }\n require(addr != address(0), \"Create2: Failed on deploy\");\n }\n\n function computeAddress(uint256 salt, bytes memory bytecode)\n public\n view\n returns (address)\n {\n bytes32 bytecodeHashHash = keccak256(bytecode);\n bytes32 _data = keccak256(\n abi.encodePacked(\n bytes1(0xff),\n address(this),\n salt,\n bytecodeHashHash\n )\n );\n return address(bytes20(_data << 96));\n }\n\n function setShouldAttack(bool _shouldAttack) public {\n shouldAttack = _shouldAttack;\n }\n\n // should call selfdestruct in the constructor\n function setShouldDesctruct(bool _shouldDestruct) public {\n shouldDestruct = _shouldDestruct;\n }\n\n function setTargetMethod(uint256 target) public {\n targetMethod = target;\n }\n\n function setOUSDAddress(address _ousdContract) public {\n ousdContract = _ousdContract;\n }\n}\n\ncontract Reborner {\n Sanctum sanctum;\n bool logging = false;\n\n constructor(address _sanctum) {\n log(\"We are created...\");\n sanctum = Sanctum(_sanctum);\n if (sanctum.shouldAttack()) {\n log(\"We are attacking now...\");\n\n uint256 target = sanctum.targetMethod();\n\n if (target == 1) {\n redeem();\n } else if (target == 2) {\n transfer();\n } else {\n mint();\n }\n }\n\n if (sanctum.shouldDestruct()) {\n bye();\n }\n }\n\n function mint() public {\n log(\"We are attempting to mint..\");\n address asset = sanctum.asset();\n address vault = sanctum.vault();\n IERC20(asset).approve(vault, 1e6);\n IVault(vault).mint(asset, 1e6, 0);\n log(\"We are now minting..\");\n }\n\n function redeem() public {\n log(\"We are attempting to redeem..\");\n address vault = sanctum.vault();\n IVault(vault).redeem(1e18, 1e18);\n log(\"We are now redeeming..\");\n }\n\n function transfer() public {\n log(\"We are attempting to transfer..\");\n address ousd = sanctum.ousdContract();\n require(IERC20(ousd).transfer(address(1), 1e18), \"transfer failed\");\n log(\"We are now transfering..\");\n }\n\n function bye() public {\n log(\"We are now destructing..\");\n selfdestruct(payable(msg.sender));\n }\n\n function log(string memory message) internal view {\n if (logging) {\n // solhint-disable-next-line no-console\n console.log(message);\n }\n }\n}\n" + }, + "contracts/mocks/MockRoosterAMOStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title Rooster AMO strategy exposing extra functionality\n * @author Origin Protocol Inc\n */\n\nimport { RoosterAMOStrategy } from \"../strategies/plume/RoosterAMOStrategy.sol\";\nimport { IMaverickV2Pool } from \"../interfaces/plume/IMaverickV2Pool.sol\";\n\ncontract MockRoosterAMOStrategy is RoosterAMOStrategy {\n constructor(\n BaseStrategyConfig memory _stratConfig,\n address _wethAddress,\n address _oethpAddress,\n address _liquidityManager,\n address _poolLens,\n address _maverickPosition,\n address _maverickQuoter,\n address _mPool,\n bool _upperTickAtParity,\n address _votingDistributor,\n address _poolDistributor\n )\n RoosterAMOStrategy(\n _stratConfig,\n _wethAddress,\n _oethpAddress,\n _liquidityManager,\n _poolLens,\n _maverickPosition,\n _maverickQuoter,\n _mPool,\n _upperTickAtParity,\n _votingDistributor,\n _poolDistributor\n )\n {}\n\n function getCurrentWethShare() external view returns (uint256) {\n uint256 _currentPrice = getPoolSqrtPrice();\n\n return _getWethShare(_currentPrice);\n }\n}\n" + }, + "contracts/mocks/MockStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\n\ncontract MockStrategy {\n address[] public assets;\n\n address public withdrawAllAsset;\n address public withdrawAllRecipient;\n\n bool public shouldSupportAsset;\n\n constructor() {\n shouldSupportAsset = true;\n }\n\n function deposit(address asset, uint256 amount) external {}\n\n function depositAll() external {}\n\n function withdraw(\n address recipient,\n address asset,\n uint256 amount\n ) external {\n IERC20(asset).transfer(recipient, amount);\n }\n\n function withdrawAll() external {\n IERC20(withdrawAllAsset).transfer(\n withdrawAllRecipient,\n IERC20(withdrawAllAsset).balanceOf(address(this))\n );\n }\n\n function checkBalance(address asset)\n external\n view\n returns (uint256 balance)\n {\n balance = IERC20(asset).balanceOf(address(this));\n }\n\n function supportsAsset(address) external view returns (bool) {\n return shouldSupportAsset;\n }\n\n function setShouldSupportAsset(bool _shouldSupportAsset) external {\n shouldSupportAsset = _shouldSupportAsset;\n }\n\n function collectRewardTokens() external {}\n\n function getRewardTokenAddresses()\n external\n view\n returns (address[] memory)\n {\n return new address[](0);\n }\n\n function setWithdrawAll(address asset, address recipient) external {\n withdrawAllAsset = asset;\n withdrawAllRecipient = recipient;\n }\n}\n" + }, + "contracts/mocks/MockVault.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { VaultAdmin } from \"../vault/VaultAdmin.sol\";\nimport { StableMath } from \"../utils/StableMath.sol\";\nimport \"../utils/Helpers.sol\";\n\ncontract MockVault is VaultAdmin {\n using StableMath for uint256;\n\n uint256 storedTotalValue;\n\n constructor(address _asset) VaultAdmin(_asset) {}\n\n function setTotalValue(uint256 _value) public {\n storedTotalValue = _value;\n }\n\n function totalValue() external view override returns (uint256) {\n return storedTotalValue;\n }\n\n function _totalValue() internal view override returns (uint256) {\n return storedTotalValue;\n }\n\n function _checkBalance(address _asset)\n internal\n view\n override\n returns (uint256 balance)\n {\n // Avoids rounding errors by returning the total value\n // in a single currency\n if (asset == _asset) {\n uint256 decimals = Helpers.getDecimals(_asset);\n return storedTotalValue.scaleBy(decimals, 18);\n } else {\n return 0;\n }\n }\n}\n" + }, + "contracts/mocks/MockVaultCoreInstantRebase.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { VaultCore } from \"../vault/VaultCore.sol\";\n\ncontract MockVaultCoreInstantRebase is VaultCore {\n constructor(address _asset) VaultCore(_asset) {}\n\n function _nextYield(uint256 supply, uint256 vaultValue)\n internal\n view\n override\n returns (uint256 yield, uint256 targetRate)\n {\n if (vaultValue <= supply) {\n return (0, 0);\n }\n yield = vaultValue - supply;\n return (yield, 0);\n }\n}\n" + }, + "contracts/mocks/TestUpgradedOUSD.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport \"../token/OUSD.sol\";\n\n// used to alter internal state of OUSD contract\ncontract TestUpgradedOUSD is OUSD {\n constructor() OUSD() {}\n\n function overwriteCreditBalances(address _account, uint256 _creditBalance)\n public\n {\n creditBalances[_account] = _creditBalance;\n }\n\n function overwriteAlternativeCPT(address _account, uint256 _acpt) public {\n alternativeCreditsPerToken[_account] = _acpt;\n }\n\n function overwriteRebaseState(address _account, RebaseOptions _rebaseOption)\n public\n {\n rebaseState[_account] = _rebaseOption;\n }\n}\n" + }, + "contracts/poolBooster/curve/CurvePoolBooster.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport { Initializable } from \"../../utils/Initializable.sol\";\nimport { Strategizable } from \"../../governance/Strategizable.sol\";\nimport { ICampaignRemoteManager } from \"../../interfaces/ICampaignRemoteManager.sol\";\n\n/// @title CurvePoolBooster\n/// @author Origin Protocol\n/// @notice Contract to manage interactions with VotemarketV2 for a dedicated Curve pool/gauge.\ncontract CurvePoolBooster is Initializable, Strategizable {\n using SafeERC20 for IERC20;\n\n ////////////////////////////////////////////////////\n /// --- CONSTANTS && IMMUTABLES\n ////////////////////////////////////////////////////\n /// @notice Base fee for the contract, 100%\n uint16 public constant FEE_BASE = 10_000;\n\n /// @notice Arbitrum where the votemarket is running\n uint256 public constant targetChainId = 42161;\n\n /// @notice Address of the gauge to manage\n address public immutable gauge;\n\n /// @notice Address of the reward token\n address public immutable rewardToken;\n\n ////////////////////////////////////////////////////\n /// --- STORAGE\n ////////////////////////////////////////////////////\n\n /// @notice Fee in FEE_BASE unit payed when managing campaign.\n uint16 public fee;\n\n /// @notice Address of the fee collector\n address public feeCollector;\n\n /// @notice Address of the campaignRemoteManager linked to VotemarketV2\n address public campaignRemoteManager;\n\n /// @notice Address of votemarket in L2\n address public votemarket;\n\n /// @notice Id of the campaign created\n uint256 public campaignId;\n\n ////////////////////////////////////////////////////\n /// --- EVENTS\n ////////////////////////////////////////////////////\n event FeeUpdated(uint16 newFee);\n event FeeCollected(address feeCollector, uint256 feeAmount);\n event FeeCollectorUpdated(address newFeeCollector);\n event VotemarketUpdated(address newVotemarket);\n event CampaignRemoteManagerUpdated(address newCampaignRemoteManager);\n event CampaignCreated(\n address gauge,\n address rewardToken,\n uint256 maxRewardPerVote,\n uint256 totalRewardAmount\n );\n event CampaignIdUpdated(uint256 newId);\n event CampaignClosed(uint256 campaignId);\n event TotalRewardAmountUpdated(uint256 extraTotalRewardAmount);\n event NumberOfPeriodsUpdated(uint8 extraNumberOfPeriods);\n event RewardPerVoteUpdated(uint256 newMaxRewardPerVote);\n event TokensRescued(address token, uint256 amount, address receiver);\n\n ////////////////////////////////////////////////////\n /// --- CONSTRUCTOR && INITIALIZATION\n ////////////////////////////////////////////////////\n constructor(address _rewardToken, address _gauge) {\n rewardToken = _rewardToken;\n gauge = _gauge;\n\n // Prevent implementation contract to be governed\n _setGovernor(address(0));\n }\n\n /// @notice initialize function, to set up initial internal state\n /// @param _strategist Address of the strategist\n /// @param _fee Fee in FEE_BASE unit payed when managing campaign\n /// @param _feeCollector Address of the fee collector\n function initialize(\n address _strategist,\n uint16 _fee,\n address _feeCollector,\n address _campaignRemoteManager,\n address _votemarket\n ) external onlyGovernor initializer {\n _setStrategistAddr(_strategist);\n _setFee(_fee);\n _setFeeCollector(_feeCollector);\n _setCampaignRemoteManager(_campaignRemoteManager);\n _setVotemarket(_votemarket);\n }\n\n ////////////////////////////////////////////////////\n /// --- MUTATIVE FUNCTIONS\n ////////////////////////////////////////////////////\n /// @notice Create a new campaign on VotemarketV2\n /// @dev This will use all token available in this contract\n /// @dev Caller must send ETH to pay for the bridge fee\n /// @param numberOfPeriods Duration of the campaign in weeks\n /// @param maxRewardPerVote Maximum reward per vote to distribute, to avoid overspending\n /// @param blacklist List of addresses to exclude from the campaign\n /// @param additionalGasLimit Additional gas limit for the bridge\n function createCampaign(\n uint8 numberOfPeriods,\n uint256 maxRewardPerVote,\n address[] calldata blacklist,\n uint256 additionalGasLimit\n ) external payable nonReentrant onlyGovernorOrStrategist {\n require(campaignId == 0, \"Campaign already created\");\n require(numberOfPeriods > 1, \"Invalid number of periods\");\n require(maxRewardPerVote > 0, \"Invalid reward per vote\");\n\n // Handle fee (if any)\n uint256 balanceSubFee = _handleFee();\n\n // Approve the balanceSubFee to the campaign manager\n IERC20(rewardToken).safeApprove(campaignRemoteManager, 0);\n IERC20(rewardToken).safeApprove(campaignRemoteManager, balanceSubFee);\n\n // Create a new campaign\n ICampaignRemoteManager(campaignRemoteManager).createCampaign{\n value: msg.value\n }(\n ICampaignRemoteManager.CampaignCreationParams({\n chainId: targetChainId,\n gauge: gauge,\n manager: address(this),\n rewardToken: rewardToken,\n numberOfPeriods: numberOfPeriods,\n maxRewardPerVote: maxRewardPerVote,\n totalRewardAmount: balanceSubFee,\n addresses: blacklist,\n hook: address(0),\n isWhitelist: false\n }),\n targetChainId,\n additionalGasLimit,\n votemarket\n );\n\n emit CampaignCreated(\n gauge,\n rewardToken,\n maxRewardPerVote,\n balanceSubFee\n );\n }\n\n /// @notice Manage campaign parameters in a single call\n /// @dev This function should be called after the campaign is created\n /// @dev Caller must send ETH to pay for the bridge fee\n /// @param totalRewardAmount Amount of reward tokens to add:\n /// - 0: no update\n /// - type(uint256).max: use all tokens in contract\n /// - other: use specific amount\n /// @param numberOfPeriods Number of additional periods (0 = no update)\n /// @param maxRewardPerVote New maximum reward per vote (0 = no update)\n /// @param additionalGasLimit Additional gas limit for the bridge\n function manageCampaign(\n uint256 totalRewardAmount,\n uint8 numberOfPeriods,\n uint256 maxRewardPerVote,\n uint256 additionalGasLimit\n ) external payable nonReentrant onlyGovernorOrStrategist {\n require(campaignId != 0, \"Campaign not created\");\n\n uint256 rewardAmount;\n\n if (totalRewardAmount != 0) {\n uint256 amount = min(\n IERC20(rewardToken).balanceOf(address(this)),\n totalRewardAmount\n );\n\n // Handle fee\n rewardAmount = _handleFee(amount);\n require(rewardAmount > 0, \"No reward to add\");\n\n // Approve the reward amount to the campaign manager\n IERC20(rewardToken).safeApprove(campaignRemoteManager, 0);\n IERC20(rewardToken).safeApprove(\n campaignRemoteManager,\n rewardAmount\n );\n }\n\n // Call remote manager\n ICampaignRemoteManager(campaignRemoteManager).manageCampaign{\n value: msg.value\n }(\n ICampaignRemoteManager.CampaignManagementParams({\n campaignId: campaignId,\n rewardToken: rewardToken,\n numberOfPeriods: numberOfPeriods,\n totalRewardAmount: rewardAmount,\n maxRewardPerVote: maxRewardPerVote\n }),\n targetChainId,\n additionalGasLimit,\n votemarket\n );\n\n // Emit relevant events\n if (rewardAmount > 0) {\n emit TotalRewardAmountUpdated(rewardAmount);\n }\n if (numberOfPeriods > 0) {\n emit NumberOfPeriodsUpdated(numberOfPeriods);\n }\n if (maxRewardPerVote > 0) {\n emit RewardPerVoteUpdated(maxRewardPerVote);\n }\n }\n\n /// @notice Close the campaign.\n /// @dev This function only work on the L2 chain. Not on mainnet.\n /// @dev Caller must send ETH to pay for the bridge fee\n /// @dev The _campaignId parameter is not related to the campaignId of this contract, allowing greater flexibility.\n /// @param _campaignId Id of the campaign to close\n /// @param additionalGasLimit Additional gas limit for the bridge\n // slither-disable-start reentrancy-eth\n function closeCampaign(uint256 _campaignId, uint256 additionalGasLimit)\n external\n payable\n nonReentrant\n onlyGovernorOrStrategist\n {\n ICampaignRemoteManager(campaignRemoteManager).closeCampaign{\n value: msg.value\n }(\n ICampaignRemoteManager.CampaignClosingParams({\n campaignId: campaignId\n }),\n targetChainId,\n additionalGasLimit,\n votemarket\n );\n campaignId = 0;\n emit CampaignClosed(_campaignId);\n }\n\n // slither-disable-end reentrancy-eth\n\n /// @notice Calculate the fee amount and transfer it to the feeCollector\n /// @dev Uses full contract balance\n /// @return Balance after fee\n function _handleFee() internal returns (uint256) {\n uint256 balance = IERC20(rewardToken).balanceOf(address(this));\n\n // This is not a problem if balance is 0, feeAmount will be 0 as well\n // We don't want to make the whole function revert just because of that.\n return _handleFee(balance);\n }\n\n /// @notice Calculate the fee amount and transfer it to the feeCollector\n /// @param amount Amount to take fee from\n /// @return Amount after fee\n function _handleFee(uint256 amount) internal returns (uint256) {\n uint256 feeAmount = (amount * fee) / FEE_BASE;\n\n // If there is a fee, transfer it to the feeCollector\n if (feeAmount > 0) {\n IERC20(rewardToken).safeTransfer(feeCollector, feeAmount);\n emit FeeCollected(feeCollector, feeAmount);\n }\n\n // Fetch balance again to avoid rounding issues\n return IERC20(rewardToken).balanceOf(address(this));\n }\n\n ////////////////////////////////////////////////////\n /// --- GOVERNANCE && OPERATION\n ////////////////////////////////////////////////////\n /// @notice Set the campaign id\n /// @dev Only callable by the governor or strategist\n /// @param _campaignId New campaign id\n function setCampaignId(uint256 _campaignId)\n external\n onlyGovernorOrStrategist\n {\n campaignId = _campaignId;\n emit CampaignIdUpdated(_campaignId);\n }\n\n /// @notice Rescue ETH from the contract\n /// @dev Only callable by the governor or strategist\n /// @param receiver Address to receive the ETH\n function rescueETH(address receiver)\n external\n nonReentrant\n onlyGovernorOrStrategist\n {\n require(receiver != address(0), \"Invalid receiver\");\n uint256 balance = address(this).balance;\n (bool success, ) = receiver.call{ value: balance }(\"\");\n require(success, \"Transfer failed\");\n emit TokensRescued(address(0), balance, receiver);\n }\n\n /// @notice Rescue ERC20 tokens from the contract\n /// @dev Only callable by the governor or strategist\n /// @param token Address of the token to rescue\n function rescueToken(address token, address receiver)\n external\n nonReentrant\n onlyGovernor\n {\n require(receiver != address(0), \"Invalid receiver\");\n uint256 balance = IERC20(token).balanceOf(address(this));\n IERC20(token).safeTransfer(receiver, balance);\n emit TokensRescued(token, balance, receiver);\n }\n\n /// @notice Set the fee\n /// @dev Only callable by the governor\n /// @param _fee New fee\n function setFee(uint16 _fee) external onlyGovernor {\n _setFee(_fee);\n }\n\n /// @notice Internal logic to set the fee\n function _setFee(uint16 _fee) internal {\n require(_fee <= FEE_BASE / 2, \"Fee too high\");\n fee = _fee;\n emit FeeUpdated(_fee);\n }\n\n /// @notice Set the fee collector\n /// @dev Only callable by the governor\n /// @param _feeCollector New fee collector\n function setFeeCollector(address _feeCollector) external onlyGovernor {\n _setFeeCollector(_feeCollector);\n }\n\n /// @notice Internal logic to set the fee collector\n function _setFeeCollector(address _feeCollector) internal {\n require(_feeCollector != address(0), \"Invalid fee collector\");\n feeCollector = _feeCollector;\n emit FeeCollectorUpdated(_feeCollector);\n }\n\n /// @notice Set the campaignRemoteManager\n /// @param _campaignRemoteManager New campaignRemoteManager address\n function setCampaignRemoteManager(address _campaignRemoteManager)\n external\n onlyGovernor\n {\n _setCampaignRemoteManager(_campaignRemoteManager);\n }\n\n /// @notice Internal logic to set the campaignRemoteManager\n /// @param _campaignRemoteManager New campaignRemoteManager address\n function _setCampaignRemoteManager(address _campaignRemoteManager)\n internal\n {\n require(\n _campaignRemoteManager != address(0),\n \"Invalid campaignRemoteManager\"\n );\n campaignRemoteManager = _campaignRemoteManager;\n emit CampaignRemoteManagerUpdated(_campaignRemoteManager);\n }\n\n /// @notice Set the votemarket address\n /// @param _votemarket New votemarket address\n function setVotemarket(address _votemarket) external onlyGovernor {\n _setVotemarket(_votemarket);\n }\n\n /// @notice Internal logic to set the votemarket address\n function _setVotemarket(address _votemarket) internal {\n require(_votemarket != address(0), \"Invalid votemarket\");\n votemarket = _votemarket;\n emit VotemarketUpdated(_votemarket);\n }\n\n /// @notice Return the minimum of two uint256 numbers\n function min(uint256 a, uint256 b) internal pure returns (uint256) {\n return a < b ? a : b;\n }\n\n receive() external payable {}\n}\n" + }, + "contracts/poolBooster/curve/CurvePoolBoosterFactory.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { ICreateX } from \"../../interfaces/ICreateX.sol\";\nimport { Initializable } from \"../../utils/Initializable.sol\";\nimport { Strategizable } from \"../../governance/Strategizable.sol\";\nimport { CurvePoolBoosterPlain } from \"./CurvePoolBoosterPlain.sol\";\nimport { IPoolBoostCentralRegistry } from \"../../interfaces/poolBooster/IPoolBoostCentralRegistry.sol\";\n\n/// @title CurvePoolBoosterFactory\n/// @author Origin Protocol\n/// @notice Factory contract to create CurvePoolBoosterPlain instances\ncontract CurvePoolBoosterFactory is Initializable, Strategizable {\n ////////////////////////////////////////////////////\n /// --- Structs\n ////////////////////////////////////////////////////\n struct PoolBoosterEntry {\n address boosterAddress;\n address ammPoolAddress;\n IPoolBoostCentralRegistry.PoolBoosterType boosterType;\n }\n\n ////////////////////////////////////////////////////\n /// --- Constants\n ////////////////////////////////////////////////////\n\n /// @notice Address of the CreateX contract\n ICreateX public constant CREATEX =\n ICreateX(0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed);\n\n ////////////////////////////////////////////////////\n /// --- Storage\n ////////////////////////////////////////////////////\n\n /// @notice list of all the pool boosters created by this factory\n PoolBoosterEntry[] public poolBoosters;\n /// @notice Central registry contract\n IPoolBoostCentralRegistry public centralRegistry;\n /// @notice mapping of AMM pool to pool booster\n mapping(address => PoolBoosterEntry) public poolBoosterFromPool;\n\n ////////////////////////////////////////////////////\n /// --- Initialize\n ////////////////////////////////////////////////////\n\n /// @notice Initialize the contract. Normally we'd rather have the governor and strategist set in the constructor,\n /// but since this contract is deployed by CreateX we need to set them in the initialize function because\n /// the constructor's parameters influence the address of the contract when deployed using CreateX.\n /// And having different governor and strategist on the same address on different chains would\n /// cause issues.\n /// @param _governor Address of the governor\n /// @param _strategist Address of the strategist\n /// @param _centralRegistry Address of the central registry\n function initialize(\n address _governor,\n address _strategist,\n address _centralRegistry\n ) external initializer {\n _setGovernor(_governor);\n _setStrategistAddr(_strategist);\n centralRegistry = IPoolBoostCentralRegistry(_centralRegistry);\n }\n\n ////////////////////////////////////////////////////\n /// --- External Mutative Functions\n ////////////////////////////////////////////////////\n\n /// @notice Create a new CurvePoolBoosterPlain instance\n /// @param _rewardToken Address of the reward token (OETH or OUSD)\n /// @param _gauge Address of the gauge (e.g. Curve OETH/WETH Gauge)\n /// @param _feeCollector Address of the fee collector (e.g. MultichainStrategist)\n /// @param _fee Fee in FEE_BASE unit payed when managing campaign\n /// @param _campaignRemoteManager Address of the campaign remote manager\n /// @param _votemarket Address of the votemarket\n /// @param _salt A unique number that affects the address of the pool booster created. Note: this number\n /// should match the one from `computePoolBoosterAddress` in order for the final deployed address\n /// and pre-computed address to match\n /// @param _expectedAddress The expected address of the pool booster. This is used to verify that the pool booster\n /// was deployed at the expected address, otherwise the transaction batch will revert. If set to 0 then the\n /// address verification is skipped.\n function createCurvePoolBoosterPlain(\n address _rewardToken,\n address _gauge,\n address _feeCollector,\n uint16 _fee,\n address _campaignRemoteManager,\n address _votemarket,\n bytes32 _salt,\n address _expectedAddress\n ) external onlyGovernorOrStrategist returns (address) {\n require(governor() != address(0), \"Governor not set\");\n require(strategistAddr != address(0), \"Strategist not set\");\n // salt encoded sender\n address senderAddress = address(bytes20(_salt));\n // the contract that calls the CreateX should be encoded in the salt to protect against front-running\n require(senderAddress == address(this), \"Front-run protection failed\");\n\n address poolBoosterAddress = CREATEX.deployCreate2(\n _salt,\n _getInitCode(_rewardToken, _gauge)\n );\n\n require(\n _expectedAddress == address(0) ||\n poolBoosterAddress == _expectedAddress,\n \"Pool booster deployed at unexpected address\"\n );\n\n CurvePoolBoosterPlain(payable(poolBoosterAddress)).initialize(\n governor(),\n strategistAddr,\n _fee,\n _feeCollector,\n _campaignRemoteManager,\n _votemarket\n );\n\n _storePoolBoosterEntry(poolBoosterAddress, _gauge);\n return poolBoosterAddress;\n }\n\n /// @notice Removes the pool booster from the internal list of pool boosters.\n /// @dev This action does not destroy the pool booster contract nor does it\n /// stop the yield delegation to it.\n /// @param _poolBoosterAddress address of the pool booster\n function removePoolBooster(address _poolBoosterAddress)\n external\n onlyGovernor\n {\n uint256 boostersLen = poolBoosters.length;\n for (uint256 i = 0; i < boostersLen; ++i) {\n if (poolBoosters[i].boosterAddress == _poolBoosterAddress) {\n // erase mapping\n delete poolBoosterFromPool[poolBoosters[i].ammPoolAddress];\n\n // overwrite current pool booster with the last entry in the list\n poolBoosters[i] = poolBoosters[boostersLen - 1];\n // drop the last entry\n poolBoosters.pop();\n\n // centralRegistry can be address(0) on some chains\n if (address(centralRegistry) != address(0)) {\n centralRegistry.emitPoolBoosterRemoved(_poolBoosterAddress);\n }\n break;\n }\n }\n }\n\n ////////////////////////////////////////////////////\n /// --- Internal Mutative Functions\n ////////////////////////////////////////////////////\n\n /// @notice Stores the pool booster entry in the internal list and mapping\n /// @param _poolBoosterAddress address of the pool booster\n /// @param _ammPoolAddress address of the AMM pool\n function _storePoolBoosterEntry(\n address _poolBoosterAddress,\n address _ammPoolAddress\n ) internal {\n IPoolBoostCentralRegistry.PoolBoosterType _boosterType = IPoolBoostCentralRegistry\n .PoolBoosterType\n .CurvePoolBoosterPlain;\n PoolBoosterEntry memory entry = PoolBoosterEntry(\n _poolBoosterAddress,\n _ammPoolAddress,\n _boosterType\n );\n\n poolBoosters.push(entry);\n poolBoosterFromPool[_ammPoolAddress] = entry;\n\n // emit the events of the pool booster created\n // centralRegistry can be address(0) on some chains\n if (address(centralRegistry) != address(0)) {\n centralRegistry.emitPoolBoosterCreated(\n _poolBoosterAddress,\n _ammPoolAddress,\n _boosterType\n );\n }\n }\n\n ////////////////////////////////////////////////////\n /// --- External View Functions\n ////////////////////////////////////////////////////\n\n /// @notice Create a new CurvePoolBoosterPlain instance (address computation version)\n /// @param _rewardToken Address of the reward token (OETH or OUSD)\n /// @param _gauge Address of the gauge (e.g. Curve OETH/WETH Gauge)\n /// @param _salt A unique number that affects the address of the pool booster created. Note: this number\n /// should match the one from `createCurvePoolBoosterPlain` in order for the final deployed address\n /// and pre-computed address to match\n function computePoolBoosterAddress(\n address _rewardToken,\n address _gauge,\n bytes32 _salt\n ) external view returns (address) {\n bytes32 guardedSalt = _computeGuardedSalt(_salt);\n return\n CREATEX.computeCreate2Address(\n guardedSalt,\n keccak256(_getInitCode(_rewardToken, _gauge)),\n address(CREATEX)\n );\n }\n\n /// @notice Encodes a salt for CreateX by concatenating deployer address (bytes20), cross-chain protection flag\n /// (bytes1), and the first 11 bytes of the provided salt (most significant bytes). This function is exposed\n /// for easier operations. For the salt value itself just use the epoch time when the operation is performed.\n /// @param salt The raw salt as uint256; converted to bytes32, then only the first 11 bytes (MSB) are used.\n /// @return encodedSalt The resulting 32-byte encoded salt.\n function encodeSaltForCreateX(uint256 salt)\n external\n view\n returns (bytes32 encodedSalt)\n {\n // only the right most 11 bytes are considered when encoding salt. Which is limited by the number in the below\n // require. If salt were higher, the higher bytes would need to be set to 0 to not affect the \"or\" way of\n // encoding the salt.\n require(salt <= 309485009821345068724781055, \"Invalid salt\");\n\n // prepare encoded salt guarded by this factory address. When the deployer part of the salt is the same as the\n // caller of CreateX the salt is re-hashed and thus guarded from front-running.\n address deployer = address(this);\n\n // Flag as uint8 (0)\n uint8 flag = 0;\n\n // Precompute parts\n uint256 deployerPart = uint256(uint160(deployer)) << 96; // 20 bytes shifted left 96 bits (12 bytes)\n uint256 flagPart = uint256(flag) << 88; // 1 byte shifted left 88 bits (11 bytes)\n\n // Concat via nested OR\n // solhint-disable-next-line no-inline-assembly\n assembly {\n encodedSalt := or(or(deployerPart, flagPart), salt)\n }\n }\n\n /// @notice Get the number of pool boosters created by this factory\n function poolBoosterLength() external view returns (uint256) {\n return poolBoosters.length;\n }\n\n /// @notice Get the list of all pool boosters created by this factory\n function getPoolBoosters()\n external\n view\n returns (PoolBoosterEntry[] memory)\n {\n return poolBoosters;\n }\n\n ////////////////////////////////////////////////////\n /// --- Internal View/Pure Functions\n ////////////////////////////////////////////////////\n\n /// @notice Get the init code for the CurvePoolBoosterPlain contract\n function _getInitCode(address _rewardToken, address _gauge)\n internal\n pure\n returns (bytes memory)\n {\n return\n abi.encodePacked(\n type(CurvePoolBoosterPlain).creationCode,\n abi.encode(_rewardToken, _gauge)\n );\n }\n\n /// @notice Compute the guarded salt for CreateX protections. This version of guarded\n /// salt expects that this factory contract is the one doing calls to the CreateX contract.\n function _computeGuardedSalt(bytes32 _salt)\n internal\n view\n returns (bytes32)\n {\n return\n _efficientHash({\n a: bytes32(uint256(uint160(address(this)))),\n b: _salt\n });\n }\n\n /// @notice Efficiently hash two bytes32 values together\n function _efficientHash(bytes32 a, bytes32 b)\n internal\n pure\n returns (bytes32 hash)\n {\n // solhint-disable-next-line no-inline-assembly\n assembly {\n mstore(0x00, a)\n mstore(0x20, b)\n hash := keccak256(0x00, 0x40)\n }\n }\n}\n" + }, + "contracts/poolBooster/curve/CurvePoolBoosterPlain.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { CurvePoolBooster } from \"./CurvePoolBooster.sol\";\n\n/// @title CurvePoolBoosterPlain\n/// @author Origin Protocol\n/// @notice Contract to manage interactions with VotemarketV2 for a dedicated Curve pool/gauge. It differs from the\n/// CurvePoolBooster in that it is not proxied.\n/// @dev Governor is not set in the constructor so that the same contract can be deployed on the same address on\n/// multiple chains. Governor is set in the initialize function.\ncontract CurvePoolBoosterPlain is CurvePoolBooster {\n constructor(address _rewardToken, address _gauge)\n CurvePoolBooster(_rewardToken, _gauge)\n {\n rewardToken = _rewardToken;\n gauge = _gauge;\n }\n\n /// @notice initialize function, to set up initial internal state\n /// @param _strategist Address of the strategist\n /// @param _fee Fee in FEE_BASE unit payed when managing campaign\n /// @param _feeCollector Address of the fee collector\n /// @dev Since this function is initialized in the same transaction as it is created the initialize function\n /// doesn't need role protection.\n /// Because the governor is only set in the initialisation function the base class initialize can not be\n /// called as it is not the governor who is issueing this call.\n function initialize(\n address _govenor,\n address _strategist,\n uint16 _fee,\n address _feeCollector,\n address _campaignRemoteManager,\n address _votemarket\n ) external initializer {\n _setStrategistAddr(_strategist);\n _setFee(_fee);\n _setFeeCollector(_feeCollector);\n _setCampaignRemoteManager(_campaignRemoteManager);\n _setVotemarket(_votemarket);\n\n // Set the governor to the provided governor\n _setGovernor(_govenor);\n }\n}\n" + }, + "contracts/proxies/create2/CrossChainStrategyProxy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { InitializeGovernedUpgradeabilityProxy2 } from \"../InitializeGovernedUpgradeabilityProxy2.sol\";\n\n// ********************************************************\n// ********************************************************\n// IMPORTANT: DO NOT CHANGE ANYTHING IN THIS FILE.\n// Any changes to this file (even whitespaces) will\n// affect the create2 address of the proxy\n// ********************************************************\n// ********************************************************\n\n/**\n * @notice CrossChainStrategyProxy delegates calls to a\n * CrossChainMasterStrategy or CrossChainRemoteStrategy\n * implementation contract.\n */\ncontract CrossChainStrategyProxy is InitializeGovernedUpgradeabilityProxy2 {\n constructor(address governor)\n InitializeGovernedUpgradeabilityProxy2(governor)\n {}\n}\n" + }, + "contracts/proxies/InitializeGovernedUpgradeabilityProxy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { Address } from \"@openzeppelin/contracts/utils/Address.sol\";\n\nimport { Governable } from \"../governance/Governable.sol\";\n\n/**\n * @title BaseGovernedUpgradeabilityProxy\n * @dev This contract combines an upgradeability proxy with our governor system.\n * It is based on an older version of OpenZeppelins BaseUpgradeabilityProxy\n * with Solidity ^0.8.0.\n * @author Origin Protocol Inc\n */\ncontract InitializeGovernedUpgradeabilityProxy is Governable {\n /**\n * @dev Emitted when the implementation is upgraded.\n * @param implementation Address of the new implementation.\n */\n event Upgraded(address indexed implementation);\n\n constructor() {\n _setGovernor(msg.sender);\n }\n\n /**\n * @dev Contract initializer with Governor enforcement\n * @param _logic Address of the initial implementation.\n * @param _initGovernor Address of the initial Governor.\n * @param _data Data to send as msg.data to the implementation to initialize\n * the proxied contract.\n * It should include the signature and the parameters of the function to be\n * called, as described in\n * https://solidity.readthedocs.io/en/v0.4.24/abi-spec.html#function-selector-and-argument-encoding.\n * This parameter is optional, if no data is given the initialization call\n * to proxied contract will be skipped.\n */\n function initialize(\n address _logic,\n address _initGovernor,\n bytes calldata _data\n ) public payable onlyGovernor {\n require(_implementation() == address(0));\n require(_logic != address(0), \"Implementation not set\");\n assert(\n IMPLEMENTATION_SLOT ==\n bytes32(uint256(keccak256(\"eip1967.proxy.implementation\")) - 1)\n );\n _setImplementation(_logic);\n if (_data.length > 0) {\n (bool success, ) = _logic.delegatecall(_data);\n require(success);\n }\n _changeGovernor(_initGovernor);\n }\n\n /**\n * @return The address of the proxy admin/it's also the governor.\n */\n function admin() external view returns (address) {\n return _governor();\n }\n\n /**\n * @return The address of the implementation.\n */\n function implementation() external view returns (address) {\n return _implementation();\n }\n\n /**\n * @dev Upgrade the backing implementation of the proxy.\n * Only the admin can call this function.\n * @param _newImplementation Address of the new implementation.\n */\n function upgradeTo(address _newImplementation) external onlyGovernor {\n _upgradeTo(_newImplementation);\n }\n\n /**\n * @dev Upgrade the backing implementation of the proxy and call a function\n * on the new implementation.\n * This is useful to initialize the proxied contract.\n * @param newImplementation Address of the new implementation.\n * @param data Data to send as msg.data in the low level call.\n * It should include the signature and the parameters of the function to be called, as described in\n * https://solidity.readthedocs.io/en/v0.4.24/abi-spec.html#function-selector-and-argument-encoding.\n */\n function upgradeToAndCall(address newImplementation, bytes calldata data)\n external\n payable\n onlyGovernor\n {\n _upgradeTo(newImplementation);\n (bool success, ) = newImplementation.delegatecall(data);\n require(success);\n }\n\n /**\n * @dev Fallback function.\n * Implemented entirely in `_fallback`.\n */\n fallback() external payable {\n _fallback();\n }\n\n /**\n * @dev Delegates execution to an implementation contract.\n * This is a low level function that doesn't return to its internal call site.\n * It will return to the external caller whatever the implementation returns.\n * @param _impl Address to delegate.\n */\n function _delegate(address _impl) internal {\n // solhint-disable-next-line no-inline-assembly\n assembly {\n // Copy msg.data. We take full control of memory in this inline assembly\n // block because it will not return to Solidity code. We overwrite the\n // Solidity scratch pad at memory position 0.\n calldatacopy(0, 0, calldatasize())\n\n // Call the implementation.\n // out and outsize are 0 because we don't know the size yet.\n let result := delegatecall(gas(), _impl, 0, calldatasize(), 0, 0)\n\n // Copy the returned data.\n returndatacopy(0, 0, returndatasize())\n\n switch result\n // delegatecall returns 0 on error.\n case 0 {\n revert(0, returndatasize())\n }\n default {\n return(0, returndatasize())\n }\n }\n }\n\n /**\n * @dev Function that is run as the first thing in the fallback function.\n * Can be redefined in derived contracts to add functionality.\n * Redefinitions must call super._willFallback().\n */\n function _willFallback() internal {}\n\n /**\n * @dev fallback implementation.\n * Extracted to enable manual triggering.\n */\n function _fallback() internal {\n _willFallback();\n _delegate(_implementation());\n }\n\n /**\n * @dev Storage slot with the address of the current implementation.\n * This is the keccak-256 hash of \"eip1967.proxy.implementation\" subtracted by 1, and is\n * validated in the constructor.\n */\n bytes32 internal constant IMPLEMENTATION_SLOT =\n 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;\n\n /**\n * @dev Returns the current implementation.\n * @return impl Address of the current implementation\n */\n function _implementation() internal view returns (address impl) {\n bytes32 slot = IMPLEMENTATION_SLOT;\n // solhint-disable-next-line no-inline-assembly\n assembly {\n impl := sload(slot)\n }\n }\n\n /**\n * @dev Upgrades the proxy to a new implementation.\n * @param newImplementation Address of the new implementation.\n */\n function _upgradeTo(address newImplementation) internal {\n _setImplementation(newImplementation);\n emit Upgraded(newImplementation);\n }\n\n /**\n * @dev Sets the implementation address of the proxy.\n * @param newImplementation Address of the new implementation.\n */\n function _setImplementation(address newImplementation) internal {\n require(\n Address.isContract(newImplementation),\n \"Cannot set a proxy implementation to a non-contract address\"\n );\n\n bytes32 slot = IMPLEMENTATION_SLOT;\n\n // solhint-disable-next-line no-inline-assembly\n assembly {\n sstore(slot, newImplementation)\n }\n }\n}\n" + }, + "contracts/proxies/InitializeGovernedUpgradeabilityProxy2.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { InitializeGovernedUpgradeabilityProxy } from \"./InitializeGovernedUpgradeabilityProxy.sol\";\n\n/**\n * @title BaseGovernedUpgradeabilityProxy2\n * @dev This is the same as InitializeGovernedUpgradeabilityProxy except that the\n * governor is defined in the constructor.\n * @author Origin Protocol Inc\n */\ncontract InitializeGovernedUpgradeabilityProxy2 is\n InitializeGovernedUpgradeabilityProxy\n{\n /**\n * This is used when the msg.sender can not be the governor. E.g. when the proxy is\n * deployed via CreateX\n */\n constructor(address governor) InitializeGovernedUpgradeabilityProxy() {\n _setGovernor(governor);\n }\n}\n" + }, + "contracts/proxies/Proxies.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { InitializeGovernedUpgradeabilityProxy } from \"./InitializeGovernedUpgradeabilityProxy.sol\";\nimport { InitializeGovernedUpgradeabilityProxy2 } from \"./InitializeGovernedUpgradeabilityProxy2.sol\";\n\n/**\n * @notice OUSDProxy delegates calls to an OUSD implementation\n */\ncontract OUSDProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice WrappedOUSDProxy delegates calls to a WrappedOUSD implementation\n */\ncontract WrappedOUSDProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice VaultProxy delegates calls to a Vault implementation\n */\ncontract VaultProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice CompoundStrategyProxy delegates calls to a CompoundStrategy implementation\n */\ncontract CompoundStrategyProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice AaveStrategyProxy delegates calls to a AaveStrategy implementation\n */\ncontract AaveStrategyProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice ConvexStrategyProxy delegates calls to a ConvexStrategy implementation\n */\ncontract ConvexStrategyProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice HarvesterProxy delegates calls to a Harvester implementation\n */\ncontract HarvesterProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice DripperProxy delegates calls to a Dripper implementation\n */\ncontract DripperProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice MorphoCompoundStrategyProxy delegates calls to a MorphoCompoundStrategy implementation\n */\ncontract MorphoCompoundStrategyProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice ConvexOUSDMetaStrategyProxy delegates calls to a ConvexOUSDMetaStrategy implementation\n */\ncontract ConvexOUSDMetaStrategyProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice MorphoAaveStrategyProxy delegates calls to a MorphoCompoundStrategy implementation\n */\ncontract MorphoAaveStrategyProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice OETHProxy delegates calls to nowhere for now\n */\ncontract OETHProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice WOETHProxy delegates calls to nowhere for now\n */\ncontract WOETHProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice OETHVaultProxy delegates calls to a Vault implementation\n */\ncontract OETHVaultProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice OETHDripperProxy delegates calls to a OETHDripper implementation\n */\ncontract OETHDripperProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice OETHHarvesterProxy delegates calls to a Harvester implementation\n */\ncontract OETHHarvesterProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice CurveEthStrategyProxy delegates calls to a CurveEthStrategy implementation\n */\ncontract ConvexEthMetaStrategyProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice BuybackProxy delegates calls to Buyback implementation\n */\ncontract BuybackProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice OETHMorphoAaveStrategyProxy delegates calls to a MorphoAaveStrategy implementation\n */\ncontract OETHMorphoAaveStrategyProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice OETHBalancerMetaPoolrEthStrategyProxy delegates calls to a BalancerMetaPoolStrategy implementation\n */\ncontract OETHBalancerMetaPoolrEthStrategyProxy is\n InitializeGovernedUpgradeabilityProxy\n{\n\n}\n\n/**\n * @notice OETHBalancerMetaPoolwstEthStrategyProxy delegates calls to a BalancerMetaPoolStrategy implementation\n */\ncontract OETHBalancerMetaPoolwstEthStrategyProxy is\n InitializeGovernedUpgradeabilityProxy\n{\n\n}\n\n/**\n * @notice MakerDsrStrategyProxy delegates calls to a Generalized4626Strategy implementation\n */\ncontract MakerDsrStrategyProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice OETHBuybackProxy delegates calls to Buyback implementation\n */\ncontract OETHBuybackProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice BridgedWOETHProxy delegates calls to BridgedWOETH implementation\n */\ncontract BridgedWOETHProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice NativeStakingSSVStrategyProxy delegates calls to NativeStakingSSVStrategy implementation\n */\ncontract NativeStakingSSVStrategyProxy is\n InitializeGovernedUpgradeabilityProxy\n{\n\n}\n\n/**\n * @notice NativeStakingFeeAccumulatorProxy delegates calls to FeeAccumulator implementation\n */\ncontract NativeStakingFeeAccumulatorProxy is\n InitializeGovernedUpgradeabilityProxy\n{\n\n}\n\n/**\n * @notice NativeStakingSSVStrategy2Proxy delegates calls to NativeStakingSSVStrategy implementation\n */\ncontract NativeStakingSSVStrategy2Proxy is\n InitializeGovernedUpgradeabilityProxy\n{\n\n}\n\n/**\n * @notice NativeStakingFeeAccumulator2Proxy delegates calls to FeeAccumulator implementation\n */\ncontract NativeStakingFeeAccumulator2Proxy is\n InitializeGovernedUpgradeabilityProxy\n{\n\n}\n\n/**\n * @notice NativeStakingSSVStrategy3Proxy delegates calls to NativeStakingSSVStrategy implementation\n */\ncontract NativeStakingSSVStrategy3Proxy is\n InitializeGovernedUpgradeabilityProxy\n{\n\n}\n\n/**\n * @notice NativeStakingFeeAccumulator3Proxy delegates calls to FeeAccumulator implementation\n */\ncontract NativeStakingFeeAccumulator3Proxy is\n InitializeGovernedUpgradeabilityProxy\n{\n\n}\n\n/**\n * @notice MetaMorphoStrategyProxy delegates calls to a Generalized4626Strategy implementation\n */\ncontract MetaMorphoStrategyProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice ARMBuybackProxy delegates calls to Buyback implementation\n */\ncontract ARMBuybackProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice MorphoGauntletPrimeUSDCStrategyProxy delegates calls to a Generalized4626Strategy implementation\n */\ncontract MorphoGauntletPrimeUSDCStrategyProxy is\n InitializeGovernedUpgradeabilityProxy\n{\n\n}\n\n/**\n * @notice MorphoGauntletPrimeUSDTStrategyProxy delegates calls to a Generalized4626USDTStrategy implementation\n */\ncontract MorphoGauntletPrimeUSDTStrategyProxy is\n InitializeGovernedUpgradeabilityProxy\n{\n\n}\n\n/**\n * @notice CurvePoolBoosterProxy delegates calls to a CurvePoolBooster implementation\n */\ncontract CurvePoolBoosterProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice OETHFixedRateDripperProxy delegates calls to a OETHFixedRateDripper implementation\n */\ncontract OETHFixedRateDripperProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice OETHSimpleHarvesterProxy delegates calls to a OETHSimpleHarvester implementation\n */\ncontract OETHSimpleHarvesterProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice PoolBoostCentralRegistryProxy delegates calls to the PoolBoostCentralRegistry implementation\n */\ncontract PoolBoostCentralRegistryProxy is\n InitializeGovernedUpgradeabilityProxy\n{\n\n}\n\n/**\n * @notice MakerSSRStrategyProxy delegates calls to a Generalized4626Strategy implementation\n */\ncontract MakerSSRStrategyProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice OUSDCurveAMOProxy delegates calls to a CurveAMOStrategy implementation\n */\ncontract OUSDCurveAMOProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice OETHCurveAMOProxy delegates calls to a CurveAMOStrategy implementation\n */\ncontract OETHCurveAMOProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n\n/**\n * @notice CompoundingStakingSSVStrategyProxy delegates calls to a CompoundingStakingSSVStrategy implementation\n */\ncontract CompoundingStakingSSVStrategyProxy is\n InitializeGovernedUpgradeabilityProxy\n{\n\n}\n\n/**\n * @notice OUSDMorphoV2StrategyProxy delegates calls to a Generalized4626Strategy implementation\n */\ncontract OUSDMorphoV2StrategyProxy is InitializeGovernedUpgradeabilityProxy {\n\n}\n" + }, + "contracts/strategies/AaveStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title OUSD Aave Strategy\n * @notice Investment strategy for investing stablecoins via Aave\n * @author Origin Protocol Inc\n */\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\n\nimport \"./IAave.sol\";\nimport { IERC20, InitializableAbstractStrategy } from \"../utils/InitializableAbstractStrategy.sol\";\n\nimport { IAaveStakedToken } from \"./IAaveStakeToken.sol\";\nimport { IAaveIncentivesController } from \"./IAaveIncentivesController.sol\";\n\ncontract AaveStrategy is InitializableAbstractStrategy {\n using SafeERC20 for IERC20;\n\n uint16 constant referralCode = 92;\n\n IAaveIncentivesController public incentivesController;\n IAaveStakedToken public stkAave;\n\n /**\n * @param _stratConfig The platform and OToken vault addresses\n */\n constructor(BaseStrategyConfig memory _stratConfig)\n InitializableAbstractStrategy(_stratConfig)\n {}\n\n /**\n * Initializer for setting up strategy internal state. This overrides the\n * InitializableAbstractStrategy initializer as AAVE needs several extra\n * addresses for the rewards program.\n * @param _rewardTokenAddresses Address of the AAVE token\n * @param _assets Addresses of supported assets\n * @param _pTokens Platform Token corresponding addresses\n * @param _incentivesAddress Address of the AAVE incentives controller\n * @param _stkAaveAddress Address of the stkAave contract\n */\n function initialize(\n address[] calldata _rewardTokenAddresses, // AAVE\n address[] calldata _assets,\n address[] calldata _pTokens,\n address _incentivesAddress,\n address _stkAaveAddress\n ) external onlyGovernor initializer {\n incentivesController = IAaveIncentivesController(_incentivesAddress);\n stkAave = IAaveStakedToken(_stkAaveAddress);\n InitializableAbstractStrategy._initialize(\n _rewardTokenAddresses,\n _assets,\n _pTokens\n );\n }\n\n /**\n * @dev Deposit asset into Aave\n * @param _asset Address of asset to deposit\n * @param _amount Amount of asset to deposit\n */\n function deposit(address _asset, uint256 _amount)\n external\n override\n onlyVault\n nonReentrant\n {\n _deposit(_asset, _amount);\n }\n\n /**\n * @dev Deposit asset into Aave\n * @param _asset Address of asset to deposit\n * @param _amount Amount of asset to deposit\n */\n function _deposit(address _asset, uint256 _amount) internal {\n require(_amount > 0, \"Must deposit something\");\n // Following line also doubles as a check that we are depositing\n // an asset that we support.\n emit Deposit(_asset, _getATokenFor(_asset), _amount);\n _getLendingPool().deposit(_asset, _amount, address(this), referralCode);\n }\n\n /**\n * @dev Deposit the entire balance of any supported asset into Aave\n */\n function depositAll() external override onlyVault nonReentrant {\n for (uint256 i = 0; i < assetsMapped.length; i++) {\n uint256 balance = IERC20(assetsMapped[i]).balanceOf(address(this));\n if (balance > 0) {\n _deposit(assetsMapped[i], balance);\n }\n }\n }\n\n /**\n * @dev Withdraw asset from Aave\n * @param _recipient Address to receive withdrawn asset\n * @param _asset Address of asset to withdraw\n * @param _amount Amount of asset to withdraw\n */\n function withdraw(\n address _recipient,\n address _asset,\n uint256 _amount\n ) external override onlyVault nonReentrant {\n require(_amount > 0, \"Must withdraw something\");\n require(_recipient != address(0), \"Must specify recipient\");\n\n emit Withdrawal(_asset, _getATokenFor(_asset), _amount);\n uint256 actual = _getLendingPool().withdraw(\n _asset,\n _amount,\n address(this)\n );\n require(actual == _amount, \"Did not withdraw enough\");\n IERC20(_asset).safeTransfer(_recipient, _amount);\n }\n\n /**\n * @dev Remove all assets from platform and send them to Vault contract.\n */\n function withdrawAll() external override onlyVaultOrGovernor nonReentrant {\n for (uint256 i = 0; i < assetsMapped.length; i++) {\n // Redeem entire balance of aToken\n IERC20 asset = IERC20(assetsMapped[i]);\n address aToken = _getATokenFor(assetsMapped[i]);\n uint256 balance = IERC20(aToken).balanceOf(address(this));\n if (balance > 0) {\n uint256 actual = _getLendingPool().withdraw(\n address(asset),\n balance,\n address(this)\n );\n require(actual == balance, \"Did not withdraw enough\");\n\n uint256 assetBalance = asset.balanceOf(address(this));\n // Transfer entire balance to Vault\n asset.safeTransfer(vaultAddress, assetBalance);\n\n emit Withdrawal(address(asset), aToken, assetBalance);\n }\n }\n }\n\n /**\n * @dev Get the total asset value held in the platform\n * @param _asset Address of the asset\n * @return balance Total value of the asset in the platform\n */\n function checkBalance(address _asset)\n external\n view\n override\n returns (uint256 balance)\n {\n // Balance is always with token aToken decimals\n address aToken = _getATokenFor(_asset);\n balance = IERC20(aToken).balanceOf(address(this));\n }\n\n /**\n * @dev Returns bool indicating whether asset is supported by strategy\n * @param _asset Address of the asset\n */\n function supportsAsset(address _asset) public view override returns (bool) {\n return assetToPToken[_asset] != address(0);\n }\n\n /**\n * @dev Approve the spending of all assets by their corresponding aToken,\n * if for some reason is it necessary.\n */\n function safeApproveAllTokens()\n external\n override\n onlyGovernor\n nonReentrant\n {\n address lendingPool = address(_getLendingPool());\n // approve the pool to spend the Asset\n for (uint256 i = 0; i < assetsMapped.length; i++) {\n address asset = assetsMapped[i];\n // Safe approval\n IERC20(asset).safeApprove(lendingPool, 0);\n IERC20(asset).safeApprove(lendingPool, type(uint256).max);\n }\n }\n\n /**\n * @dev Internal method to respond to the addition of new asset / aTokens\n We need to give the AAVE lending pool approval to transfer the\n asset.\n * @param _asset Address of the asset to approve\n * @param _aToken Address of the aToken\n */\n // solhint-disable-next-line no-unused-vars\n function _abstractSetPToken(address _asset, address _aToken)\n internal\n override\n {\n address lendingPool = address(_getLendingPool());\n IERC20(_asset).safeApprove(lendingPool, 0);\n IERC20(_asset).safeApprove(lendingPool, type(uint256).max);\n }\n\n /**\n * @dev Get the aToken wrapped in the IERC20 interface for this asset.\n * Fails if the pToken doesn't exist in our mappings.\n * @param _asset Address of the asset\n * @return Corresponding aToken to this asset\n */\n function _getATokenFor(address _asset) internal view returns (address) {\n address aToken = assetToPToken[_asset];\n require(aToken != address(0), \"aToken does not exist\");\n return aToken;\n }\n\n /**\n * @dev Get the current address of the Aave lending pool, which is the gateway to\n * depositing.\n * @return Current lending pool implementation\n */\n function _getLendingPool() internal view returns (IAaveLendingPool) {\n address lendingPool = ILendingPoolAddressesProvider(platformAddress)\n .getLendingPool();\n require(lendingPool != address(0), \"Lending pool does not exist\");\n return IAaveLendingPool(lendingPool);\n }\n\n /**\n * @dev Collect stkAave, convert it to AAVE send to Vault.\n */\n function collectRewardTokens()\n external\n override\n onlyHarvester\n nonReentrant\n {\n if (address(stkAave) == address(0)) {\n return;\n }\n\n // Check staked AAVE cooldown timer\n uint256 cooldown = stkAave.stakersCooldowns(address(this));\n uint256 windowStart = cooldown + stkAave.COOLDOWN_SECONDS();\n uint256 windowEnd = windowStart + stkAave.UNSTAKE_WINDOW();\n\n // If inside the unlock window, then we can redeem stkAave\n // for AAVE and send it to the vault.\n if (block.timestamp > windowStart && block.timestamp <= windowEnd) {\n // Redeem to AAVE\n uint256 stkAaveBalance = stkAave.balanceOf(address(this));\n stkAave.redeem(address(this), stkAaveBalance);\n\n // Transfer AAVE to harvesterAddress\n uint256 aaveBalance = IERC20(rewardTokenAddresses[0]).balanceOf(\n address(this)\n );\n if (aaveBalance > 0) {\n IERC20(rewardTokenAddresses[0]).safeTransfer(\n harvesterAddress,\n aaveBalance\n );\n }\n }\n\n // Collect available rewards and restart the cooldown timer, if either of\n // those should be run.\n if (block.timestamp > windowStart || cooldown == 0) {\n uint256 assetsLen = assetsMapped.length;\n // aToken addresses for incentives controller\n address[] memory aTokens = new address[](assetsLen);\n for (uint256 i = 0; i < assetsLen; ++i) {\n aTokens[i] = _getATokenFor(assetsMapped[i]);\n }\n\n // 1. If we have rewards availabile, collect them\n uint256 pendingRewards = incentivesController.getRewardsBalance(\n aTokens,\n address(this)\n );\n if (pendingRewards > 0) {\n // Because getting more stkAAVE from the incentives controller\n // with claimRewards() may push the stkAAVE cooldown time\n // forward, it is called after stakedAAVE has been turned into\n // AAVE.\n uint256 collected = incentivesController.claimRewards(\n aTokens,\n pendingRewards,\n address(this)\n );\n require(collected == pendingRewards, \"AAVE reward difference\");\n }\n\n // 2. Start cooldown counting down.\n if (stkAave.balanceOf(address(this)) > 0) {\n // Protected with if since cooldown call would revert\n // if no stkAave balance.\n stkAave.cooldown();\n }\n }\n }\n}\n" + }, + "contracts/strategies/AbstractCompoundStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title Base Compound Abstract Strategy\n * @author Origin Protocol Inc\n */\n\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport { ICERC20 } from \"./ICompound.sol\";\nimport { IComptroller } from \"../interfaces/IComptroller.sol\";\nimport { IERC20, InitializableAbstractStrategy } from \"../utils/InitializableAbstractStrategy.sol\";\n\nabstract contract AbstractCompoundStrategy is InitializableAbstractStrategy {\n using SafeERC20 for IERC20;\n\n int256[50] private __reserved;\n\n /**\n * @dev Retuns bool indicating whether asset is supported by strategy\n * @param _asset Address of the asset\n */\n function supportsAsset(address _asset) public view override returns (bool) {\n return assetToPToken[_asset] != address(0);\n }\n\n /**\n * @dev Get the cToken wrapped in the ICERC20 interface for this asset.\n * Fails if the pToken doesn't exist in our mappings.\n * @param _asset Address of the asset\n * @return Corresponding cToken to this asset\n */\n function _getCTokenFor(address _asset) internal view returns (ICERC20) {\n address cToken = assetToPToken[_asset];\n require(cToken != address(0), \"cToken does not exist\");\n return ICERC20(cToken);\n }\n\n /**\n * @dev Converts an underlying amount into cToken amount\n * cTokenAmt = (underlying * 1e18) / exchangeRate\n * @param _cToken cToken for which to change\n * @param _underlying Amount of underlying to convert\n * @return amount Equivalent amount of cTokens\n */\n function _convertUnderlyingToCToken(ICERC20 _cToken, uint256 _underlying)\n internal\n view\n returns (uint256 amount)\n {\n // e.g. 1e18*1e18 / 205316390724364402565641705 = 50e8\n // e.g. 1e8*1e18 / 205316390724364402565641705 = 0.45 or 0\n amount = (_underlying * 1e18) / _cToken.exchangeRateStored();\n }\n}\n" + }, + "contracts/strategies/AbstractConvexMetaStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title Curve Convex Strategy\n * @notice Investment strategy for investing stablecoins via Curve 3Pool\n * @author Origin Protocol Inc\n */\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\n\nimport { IRewardStaking } from \"./IRewardStaking.sol\";\nimport { ICurvePool } from \"./ICurvePool.sol\";\nimport { ICurveMetaPool } from \"./ICurveMetaPool.sol\";\nimport { IERC20, AbstractCurveStrategy, InitializableAbstractStrategy } from \"./AbstractCurveStrategy.sol\";\nimport { StableMath } from \"../utils/StableMath.sol\";\nimport { Helpers } from \"../utils/Helpers.sol\";\n\nabstract contract AbstractConvexMetaStrategy is AbstractCurveStrategy {\n using StableMath for uint256;\n using SafeERC20 for IERC20;\n\n event MaxWithdrawalSlippageUpdated(\n uint256 _prevMaxSlippagePercentage,\n uint256 _newMaxSlippagePercentage\n );\n\n // used to circumvent the stack too deep issue\n struct InitConfig {\n address cvxDepositorAddress; //Address of the Convex depositor(AKA booster) for this pool\n address metapoolAddress; //Address of the Curve MetaPool\n address metapoolMainToken; //Address of Main metapool token\n address cvxRewardStakerAddress; //Address of the CVX rewards staker\n address metapoolLPToken; //Address of metapool LP token\n uint256 cvxDepositorPTokenId; //Pid of the pool referred to by Depositor and staker\n }\n\n address internal cvxDepositorAddress;\n address internal cvxRewardStakerAddress;\n uint256 internal cvxDepositorPTokenId;\n ICurveMetaPool internal metapool;\n IERC20 internal metapoolMainToken;\n IERC20 internal metapoolLPToken;\n // Ordered list of metapool assets\n address[] internal metapoolAssets;\n // Max withdrawal slippage denominated in 1e18 (1e18 == 100%)\n uint256 public maxWithdrawalSlippage;\n uint128 internal crvCoinIndex;\n uint128 internal mainCoinIndex;\n\n int256[41] private ___reserved;\n\n /**\n * Initializer for setting up strategy internal state. This overrides the\n * InitializableAbstractStrategy initializer as Curve strategies don't fit\n * well within that abstraction.\n * @param _rewardTokenAddresses Address of CRV & CVX\n * @param _assets Addresses of supported assets. MUST be passed in the same\n * order as returned by coins on the pool contract, i.e.\n * DAI, USDC, USDT\n * @param _pTokens Platform Token corresponding addresses\n * @param initConfig Various addresses and info for initialization state\n */\n function initialize(\n address[] calldata _rewardTokenAddresses, // CRV + CVX\n address[] calldata _assets,\n address[] calldata _pTokens,\n InitConfig calldata initConfig\n ) external onlyGovernor initializer {\n require(_assets.length == 3, \"Must have exactly three assets\");\n // Should be set prior to abstract initialize call otherwise\n // abstractSetPToken calls will fail\n cvxDepositorAddress = initConfig.cvxDepositorAddress;\n pTokenAddress = _pTokens[0];\n metapool = ICurveMetaPool(initConfig.metapoolAddress);\n metapoolMainToken = IERC20(initConfig.metapoolMainToken);\n cvxRewardStakerAddress = initConfig.cvxRewardStakerAddress;\n metapoolLPToken = IERC20(initConfig.metapoolLPToken);\n cvxDepositorPTokenId = initConfig.cvxDepositorPTokenId;\n maxWithdrawalSlippage = 1e16;\n\n metapoolAssets = [metapool.coins(0), metapool.coins(1)];\n crvCoinIndex = _getMetapoolCoinIndex(pTokenAddress);\n mainCoinIndex = _getMetapoolCoinIndex(initConfig.metapoolMainToken);\n InitializableAbstractStrategy._initialize(\n _rewardTokenAddresses,\n _assets,\n _pTokens\n );\n _approveBase();\n }\n\n /**\n * @dev Get the total asset value held in the platform\n * @param _asset Address of the asset\n * @return balance Total value of the asset in the platform\n */\n function checkBalance(address _asset)\n public\n view\n virtual\n override\n returns (uint256 balance)\n {\n require(assetToPToken[_asset] != address(0), \"Unsupported asset\");\n balance = 0;\n\n // LP tokens in this contract. This should generally be nothing as we\n // should always stake the full balance in the Gauge, but include for\n // safety\n uint256 contractPTokens = IERC20(pTokenAddress).balanceOf(\n address(this)\n );\n ICurvePool curvePool = ICurvePool(platformAddress);\n if (contractPTokens > 0) {\n uint256 virtual_price = curvePool.get_virtual_price();\n uint256 value = contractPTokens.mulTruncate(virtual_price);\n balance += value;\n }\n\n /* We intentionally omit the metapoolLp tokens held by the metastrategyContract\n * since the contract should never (except in the middle of deposit/withdrawal\n * transaction) hold any amount of those tokens in normal operation. There\n * could be tokens sent to it by a 3rd party and we decide to actively ignore\n * those.\n */\n uint256 metapoolGaugePTokens = IRewardStaking(cvxRewardStakerAddress)\n .balanceOf(address(this));\n\n if (metapoolGaugePTokens > 0) {\n uint256 value = metapoolGaugePTokens.mulTruncate(\n metapool.get_virtual_price()\n );\n balance += value;\n }\n\n uint256 assetDecimals = Helpers.getDecimals(_asset);\n balance = balance.scaleBy(assetDecimals, 18) / THREEPOOL_ASSET_COUNT;\n }\n\n /**\n * @dev This function is completely analogous to _calcCurveTokenAmount[AbstractCurveStrategy]\n * and just utilizes different Curve (meta)pool API\n */\n function _calcCurveMetaTokenAmount(uint128 _coinIndex, uint256 _amount)\n internal\n returns (uint256 requiredMetapoolLP)\n {\n uint256[2] memory _amounts = [uint256(0), uint256(0)];\n _amounts[uint256(_coinIndex)] = _amount;\n\n // LP required when removing required asset ignoring fees\n uint256 lpRequiredNoFees = metapool.calc_token_amount(_amounts, false);\n /* LP required if fees would apply to entirety of removed amount\n *\n * fee is 1e10 denominated number: https://curve.readthedocs.io/exchange-pools.html#StableSwap.fee\n */\n uint256 lpRequiredFullFees = lpRequiredNoFees.mulTruncateScale(\n 1e10 + metapool.fee(),\n 1e10\n );\n\n /* asset received when withdrawing full fee applicable LP accounting for\n * slippage and fees\n */\n uint256 assetReceivedForFullLPFees = metapool.calc_withdraw_one_coin(\n lpRequiredFullFees,\n int128(_coinIndex)\n );\n\n // exact amount of LP required\n requiredMetapoolLP =\n (lpRequiredFullFees * _amount) /\n assetReceivedForFullLPFees;\n }\n\n function _approveBase() internal override {\n IERC20 pToken = IERC20(pTokenAddress);\n // 3Pool for LP token (required for removing liquidity)\n pToken.safeApprove(platformAddress, 0);\n pToken.safeApprove(platformAddress, type(uint256).max);\n // Gauge for LP token\n metapoolLPToken.safeApprove(cvxDepositorAddress, 0);\n metapoolLPToken.safeApprove(cvxDepositorAddress, type(uint256).max);\n // Metapool for LP token\n pToken.safeApprove(address(metapool), 0);\n pToken.safeApprove(address(metapool), type(uint256).max);\n // Metapool for Metapool main token\n metapoolMainToken.safeApprove(address(metapool), 0);\n metapoolMainToken.safeApprove(address(metapool), type(uint256).max);\n }\n\n /**\n * @dev Get the index of the coin\n */\n function _getMetapoolCoinIndex(address _asset)\n internal\n view\n returns (uint128)\n {\n for (uint128 i = 0; i < 2; i++) {\n if (metapoolAssets[i] == _asset) return i;\n }\n revert(\"Invalid Metapool asset\");\n }\n\n /**\n * @dev Sets max withdrawal slippage that is considered when removing\n * liquidity from Metapools.\n * @param _maxWithdrawalSlippage Max withdrawal slippage denominated in\n * wad (number with 18 decimals): 1e18 == 100%, 1e16 == 1%\n *\n * IMPORTANT Minimum maxWithdrawalSlippage should actually be 0.1% (1e15)\n * for production usage. Contract allows as low value as 0% for confirming\n * correct behavior in test suite.\n */\n function setMaxWithdrawalSlippage(uint256 _maxWithdrawalSlippage)\n external\n onlyVaultOrGovernorOrStrategist\n {\n require(\n _maxWithdrawalSlippage <= 1e18,\n \"Max withdrawal slippage needs to be between 0% - 100%\"\n );\n emit MaxWithdrawalSlippageUpdated(\n maxWithdrawalSlippage,\n _maxWithdrawalSlippage\n );\n maxWithdrawalSlippage = _maxWithdrawalSlippage;\n }\n\n /**\n * @dev Collect accumulated CRV and CVX and send to Harvester.\n */\n function collectRewardTokens()\n external\n override\n onlyHarvester\n nonReentrant\n {\n // Collect CRV and CVX\n IRewardStaking(cvxRewardStakerAddress).getReward();\n _collectRewardTokens();\n }\n\n /**\n * @dev Returns the largest of two numbers int256 version\n */\n function _max(int256 a, int256 b) internal pure returns (int256) {\n return a >= b ? a : b;\n }\n}\n" + }, + "contracts/strategies/AbstractCurveStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title Curve 3Pool Strategy\n * @notice Investment strategy for investing stablecoins via Curve 3Pool\n * @author Origin Protocol Inc\n */\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\n\nimport { ICurvePool } from \"./ICurvePool.sol\";\nimport { IERC20, InitializableAbstractStrategy } from \"../utils/InitializableAbstractStrategy.sol\";\nimport { StableMath } from \"../utils/StableMath.sol\";\nimport { Helpers } from \"../utils/Helpers.sol\";\n\nabstract contract AbstractCurveStrategy is InitializableAbstractStrategy {\n using StableMath for uint256;\n using SafeERC20 for IERC20;\n\n uint256 internal constant MAX_SLIPPAGE = 1e16; // 1%, same as the Curve UI\n // number of assets in Curve 3Pool (USDC, DAI, USDT)\n uint256 internal constant THREEPOOL_ASSET_COUNT = 3;\n address internal pTokenAddress;\n\n int256[49] private __reserved;\n\n /**\n * @dev Deposit asset into the Curve 3Pool\n * @param _asset Address of asset to deposit\n * @param _amount Amount of asset to deposit\n */\n function deposit(address _asset, uint256 _amount)\n external\n override\n onlyVault\n nonReentrant\n {\n require(_amount > 0, \"Must deposit something\");\n emit Deposit(_asset, pTokenAddress, _amount);\n\n // 3Pool requires passing deposit amounts for all 3 assets, set to 0 for\n // all\n uint256[3] memory _amounts;\n uint256 poolCoinIndex = _getCoinIndex(_asset);\n // Set the amount on the asset we want to deposit\n _amounts[poolCoinIndex] = _amount;\n ICurvePool curvePool = ICurvePool(platformAddress);\n uint256 assetDecimals = Helpers.getDecimals(_asset);\n uint256 depositValue = _amount.scaleBy(18, assetDecimals).divPrecisely(\n curvePool.get_virtual_price()\n );\n uint256 minMintAmount = depositValue.mulTruncate(\n uint256(1e18) - MAX_SLIPPAGE\n );\n // Do the deposit to 3pool\n curvePool.add_liquidity(_amounts, minMintAmount);\n _lpDepositAll();\n }\n\n function _lpDepositAll() internal virtual;\n\n /**\n * @dev Deposit the entire balance of any supported asset into the Curve 3pool\n */\n function depositAll() external override onlyVault nonReentrant {\n uint256[3] memory _amounts = [uint256(0), uint256(0), uint256(0)];\n uint256 depositValue = 0;\n ICurvePool curvePool = ICurvePool(platformAddress);\n uint256 curveVirtualPrice = curvePool.get_virtual_price();\n\n uint256 assetCount = assetsMapped.length;\n for (uint256 i = 0; i < assetCount; i++) {\n address assetAddress = assetsMapped[i];\n uint256 balance = IERC20(assetAddress).balanceOf(address(this));\n if (balance > 0) {\n uint256 poolCoinIndex = _getCoinIndex(assetAddress);\n // Set the amount on the asset we want to deposit\n _amounts[poolCoinIndex] = balance;\n uint256 assetDecimals = Helpers.getDecimals(assetAddress);\n // Get value of deposit in Curve LP token to later determine\n // the minMintAmount argument for add_liquidity\n depositValue =\n depositValue +\n balance.scaleBy(18, assetDecimals).divPrecisely(\n curveVirtualPrice\n );\n emit Deposit(assetAddress, pTokenAddress, balance);\n }\n }\n\n uint256 minMintAmount = depositValue.mulTruncate(\n uint256(1e18) - MAX_SLIPPAGE\n );\n // Do the deposit to 3pool\n curvePool.add_liquidity(_amounts, minMintAmount);\n\n /* In case of Curve Strategy all assets are mapped to the same pToken (3CrvLP). Let\n * descendants further handle the pToken. By either deploying it to the metapool and\n * resulting tokens in Gauge. Or deploying pTokens directly to the Gauge.\n */\n _lpDepositAll();\n }\n\n function _lpWithdraw(uint256 numCrvTokens) internal virtual;\n\n function _lpWithdrawAll() internal virtual;\n\n /**\n * @dev Withdraw asset from Curve 3Pool\n * @param _recipient Address to receive withdrawn asset\n * @param _asset Address of asset to withdraw\n * @param _amount Amount of asset to withdraw\n */\n function withdraw(\n address _recipient,\n address _asset,\n uint256 _amount\n ) external override onlyVault nonReentrant {\n require(_amount > 0, \"Invalid amount\");\n\n emit Withdrawal(_asset, pTokenAddress, _amount);\n\n uint256 contractCrv3Tokens = IERC20(pTokenAddress).balanceOf(\n address(this)\n );\n\n uint256 coinIndex = _getCoinIndex(_asset);\n ICurvePool curvePool = ICurvePool(platformAddress);\n\n uint256 requiredCrv3Tokens = _calcCurveTokenAmount(coinIndex, _amount);\n\n // We have enough LP tokens, make sure they are all on this contract\n if (contractCrv3Tokens < requiredCrv3Tokens) {\n _lpWithdraw(requiredCrv3Tokens - contractCrv3Tokens);\n }\n\n uint256[3] memory _amounts = [uint256(0), uint256(0), uint256(0)];\n _amounts[coinIndex] = _amount;\n\n curvePool.remove_liquidity_imbalance(_amounts, requiredCrv3Tokens);\n IERC20(_asset).safeTransfer(_recipient, _amount);\n }\n\n /**\n * @dev Calculate amount of LP required when withdrawing specific amount of one\n * of the underlying assets accounting for fees and slippage.\n *\n * Curve pools unfortunately do not contain a calculation function for\n * amount of LP required when withdrawing a specific amount of one of the\n * underlying tokens and also accounting for fees (Curve's calc_token_amount\n * does account for slippage but not fees).\n *\n * Steps taken to calculate the metric:\n * - get amount of LP required if fees wouldn't apply\n * - increase the LP amount as if fees would apply to the entirety of the underlying\n * asset withdrawal. (when withdrawing only one coin fees apply only to amounts\n * of other assets pool would return in case of balanced removal - since those need\n * to be swapped for the single underlying asset being withdrawn)\n * - get amount of underlying asset withdrawn (this Curve function does consider slippage\n * and fees) when using the increased LP amount. As LP amount is slightly over-increased\n * so is amount of underlying assets returned.\n * - since we know exactly how much asset we require take the rate of LP required for asset\n * withdrawn to get the exact amount of LP.\n */\n function _calcCurveTokenAmount(uint256 _coinIndex, uint256 _amount)\n internal\n returns (uint256 required3Crv)\n {\n ICurvePool curvePool = ICurvePool(platformAddress);\n\n uint256[3] memory _amounts = [uint256(0), uint256(0), uint256(0)];\n _amounts[_coinIndex] = _amount;\n\n // LP required when removing required asset ignoring fees\n uint256 lpRequiredNoFees = curvePool.calc_token_amount(_amounts, false);\n /* LP required if fees would apply to entirety of removed amount\n *\n * fee is 1e10 denominated number: https://curve.readthedocs.io/exchange-pools.html#StableSwap.fee\n */\n uint256 lpRequiredFullFees = lpRequiredNoFees.mulTruncateScale(\n 1e10 + curvePool.fee(),\n 1e10\n );\n\n /* asset received when withdrawing full fee applicable LP accounting for\n * slippage and fees\n */\n uint256 assetReceivedForFullLPFees = curvePool.calc_withdraw_one_coin(\n lpRequiredFullFees,\n int128(uint128(_coinIndex))\n );\n\n // exact amount of LP required\n required3Crv =\n (lpRequiredFullFees * _amount) /\n assetReceivedForFullLPFees;\n }\n\n /**\n * @dev Remove all assets from platform and send them to Vault contract.\n */\n function withdrawAll() external override onlyVaultOrGovernor nonReentrant {\n _lpWithdrawAll();\n // Withdraws are proportional to assets held by 3Pool\n uint256[3] memory minWithdrawAmounts = [\n uint256(0),\n uint256(0),\n uint256(0)\n ];\n\n // Remove liquidity\n ICurvePool threePool = ICurvePool(platformAddress);\n threePool.remove_liquidity(\n IERC20(pTokenAddress).balanceOf(address(this)),\n minWithdrawAmounts\n );\n // Transfer assets out of Vault\n // Note that Curve will provide all 3 of the assets in 3pool even if\n // we have not set PToken addresses for all of them in this strategy\n for (uint256 i = 0; i < assetsMapped.length; i++) {\n IERC20 asset = IERC20(threePool.coins(i));\n asset.safeTransfer(vaultAddress, asset.balanceOf(address(this)));\n }\n }\n\n /**\n * @dev Get the total asset value held in the platform\n * @param _asset Address of the asset\n * @return balance Total value of the asset in the platform\n */\n function checkBalance(address _asset)\n public\n view\n virtual\n override\n returns (uint256 balance)\n {\n require(assetToPToken[_asset] != address(0), \"Unsupported asset\");\n // LP tokens in this contract. This should generally be nothing as we\n // should always stake the full balance in the Gauge, but include for\n // safety\n uint256 totalPTokens = IERC20(pTokenAddress).balanceOf(address(this));\n ICurvePool curvePool = ICurvePool(platformAddress);\n if (totalPTokens > 0) {\n uint256 virtual_price = curvePool.get_virtual_price();\n uint256 value = (totalPTokens * virtual_price) / 1e18;\n uint256 assetDecimals = Helpers.getDecimals(_asset);\n balance = value.scaleBy(assetDecimals, 18) / THREEPOOL_ASSET_COUNT;\n }\n }\n\n /**\n * @dev Retuns bool indicating whether asset is supported by strategy\n * @param _asset Address of the asset\n */\n function supportsAsset(address _asset) public view override returns (bool) {\n return assetToPToken[_asset] != address(0);\n }\n\n /**\n * @dev Approve the spending of all assets by their corresponding pool tokens,\n * if for some reason is it necessary.\n */\n function safeApproveAllTokens()\n external\n override\n onlyGovernor\n nonReentrant\n {\n _approveBase();\n // This strategy is a special case since it only supports one asset\n for (uint256 i = 0; i < assetsMapped.length; i++) {\n _approveAsset(assetsMapped[i]);\n }\n }\n\n /**\n * @dev Call the necessary approvals for the Curve pool and gauge\n * @param _asset Address of the asset\n */\n function _abstractSetPToken(address _asset, address) internal override {\n _approveAsset(_asset);\n }\n\n function _approveAsset(address _asset) internal {\n IERC20 asset = IERC20(_asset);\n // 3Pool for asset (required for adding liquidity)\n asset.safeApprove(platformAddress, 0);\n asset.safeApprove(platformAddress, type(uint256).max);\n }\n\n function _approveBase() internal virtual;\n\n /**\n * @dev Get the index of the coin\n */\n function _getCoinIndex(address _asset) internal view returns (uint256) {\n for (uint256 i = 0; i < 3; i++) {\n if (assetsMapped[i] == _asset) return i;\n }\n revert(\"Invalid 3pool asset\");\n }\n}\n" + }, + "contracts/strategies/aerodrome/AerodromeAMOStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title Aerodrome AMO strategy\n * @author Origin Protocol Inc\n */\nimport { Math } from \"@openzeppelin/contracts/utils/math/Math.sol\";\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport { SafeCast } from \"@openzeppelin/contracts/utils/math/SafeCast.sol\";\n\nimport { IERC20, InitializableAbstractStrategy } from \"../../utils/InitializableAbstractStrategy.sol\";\nimport { StableMath } from \"../../utils/StableMath.sol\";\n\nimport { ISugarHelper } from \"../../interfaces/aerodrome/ISugarHelper.sol\";\nimport { INonfungiblePositionManager } from \"../../interfaces/aerodrome/INonfungiblePositionManager.sol\";\nimport { ISwapRouter } from \"../../interfaces/aerodrome/ISwapRouter.sol\";\nimport { ICLPool } from \"../../interfaces/aerodrome/ICLPool.sol\";\nimport { ICLGauge } from \"../../interfaces/aerodrome/ICLGauge.sol\";\nimport { IVault } from \"../../interfaces/IVault.sol\";\n\ncontract AerodromeAMOStrategy is InitializableAbstractStrategy {\n using StableMath for uint256;\n using SafeERC20 for IERC20;\n using SafeCast for uint256;\n\n /************************************************\n Important (!) setup configuration\n *************************************************/\n\n /**\n * In order to be able to remove a reasonable amount of complexity from the contract one of the\n * preconditions for this contract to function correctly is to have an outside account mint a small\n * amount of liquidity in the tick space where the contract will deploy's its liquidity and then send\n * that NFT LP position to a dead address (transfer to zero address not allowed.) See example of such\n * NFT LP token:\n * https://basescan.org/token/0x827922686190790b37229fd06084350e74485b72?a=413296#inventory\n */\n\n /***************************************\n Storage slot members\n ****************************************/\n\n /// @notice tokenId of the liquidity position\n uint256 public tokenId;\n /// @dev Minimum amount of tokens the strategy would be able to withdraw from the pool.\n /// minimum amount of tokens are withdrawn at a 1:1 price\n uint256 public underlyingAssets;\n /// @notice Marks the start of the interval that defines the allowed range of WETH share in\n /// the pre-configured pool's liquidity ticker\n uint256 public allowedWethShareStart;\n /// @notice Marks the end of the interval that defines the allowed range of WETH share in\n /// the pre-configured pool's liquidity ticker\n uint256 public allowedWethShareEnd;\n /// @dev reserved for inheritance\n int256[46] private __reserved;\n\n /***************************************\n Constants, structs and events\n ****************************************/\n\n /// @notice The address of the Wrapped ETH (WETH) token contract\n address public immutable WETH;\n /// @notice The address of the OETHb token contract\n address public immutable OETHb;\n /// @notice lower tick set to -1 representing the price of 1.0001 of WETH for 1 OETHb.\n int24 public immutable lowerTick;\n /// @notice lower tick set to 0 representing the price of 1.0000 of WETH for 1 OETHb.\n int24 public immutable upperTick;\n /// @notice tick spacing of the pool (set to 1)\n int24 public immutable tickSpacing;\n /// @notice the swapRouter for performing swaps\n ISwapRouter public immutable swapRouter;\n /// @notice the underlying AMO Slipstream pool\n ICLPool public immutable clPool;\n /// @notice the gauge for the corresponding Slipstream pool (clPool)\n /// @dev can become an immutable once the gauge is created on the base main-net\n ICLGauge public immutable clGauge;\n /// @notice the Position manager contract that is used to manage the pool's position\n INonfungiblePositionManager public immutable positionManager;\n /// @notice helper contract for liquidity and ticker math\n ISugarHelper public immutable helper;\n /// @notice sqrtRatioX96TickLower\n /// @dev tick lower has value -1 and represents the lowest price of WETH priced in OETHb. Meaning the pool\n /// offers less than 1 OETHb for 1 WETH. In other terms to get 1 OETHB the swap needs to offer 1.0001 WETH\n /// this is where purchasing OETHb with WETH within the liquidity position is most expensive\n uint160 public immutable sqrtRatioX96TickLower;\n /// @notice sqrtRatioX96TickHigher\n /// @dev tick higher has value 0 and represents 1:1 price parity of WETH to OETHb\n uint160 public immutable sqrtRatioX96TickHigher;\n /// @dev tick closest to 1:1 price parity\n /// Correctly assessing which tick is closer to 1:1 price parity is important since it affects\n /// the way we calculate the underlying assets in check Balance. The underlying aerodrome pool\n /// orders the tokens depending on the values of their addresses. If OETH token is token0 in the pool\n /// then sqrtRatioX96TickClosestToParity=sqrtRatioX96TickLower. If it is token1 in the pool then\n /// sqrtRatioX96TickClosestToParity=sqrtRatioX96TickHigher\n uint160 public immutable sqrtRatioX96TickClosestToParity;\n\n /// @dev a threshold under which the contract no longer allows for the protocol to rebalance. Guarding\n /// against a strategist / guardian being taken over and with multiple transactions draining the\n /// protocol funds.\n uint256 public constant SOLVENCY_THRESHOLD = 0.998 ether;\n\n error NotEnoughWethForSwap(uint256 wethBalance, uint256 requiredWeth); // 0x989e5ca8\n error NotEnoughWethLiquidity(uint256 wethBalance, uint256 requiredWeth); // 0xa6737d87\n error PoolRebalanceOutOfBounds(\n uint256 currentPoolWethShare,\n uint256 allowedWethShareStart,\n uint256 allowedWethShareEnd\n ); // 0x3681e8e0\n error OutsideExpectedTickRange(int24 currentTick); // 0x5a2eba75\n\n event PoolRebalanced(uint256 currentPoolWethShare);\n\n event PoolWethShareIntervalUpdated(\n uint256 allowedWethShareStart,\n uint256 allowedWethShareEnd\n );\n\n event LiquidityRemoved(\n uint256 withdrawLiquidityShare,\n uint256 removedWETHAmount,\n uint256 removedOETHbAmount,\n uint256 wethAmountCollected,\n uint256 oethbAmountCollected,\n uint256 underlyingAssets\n );\n\n event LiquidityAdded(\n uint256 wethAmountDesired,\n uint256 oethbAmountDesired,\n uint256 wethAmountSupplied,\n uint256 oethbAmountSupplied,\n uint256 tokenId,\n uint256 underlyingAssets\n );\n\n event UnderlyingAssetsUpdated(uint256 underlyingAssets);\n\n /**\n * @dev Un-stakes the token from the gauge for the execution duration of\n * the function and after that re-stakes it back in.\n *\n * It is important that the token is unstaked and owned by the strategy contract\n * during any liquidity altering operations and that it is re-staked back into the\n * gauge after liquidity changes. If the token fails to re-stake back to the\n * gauge it is not earning incentives.\n */\n // all functions using this modifier are used by functions with reentrancy check\n // slither-disable-start reentrancy-no-eth\n modifier gaugeUnstakeAndRestake() {\n // because of solidity short-circuit _isLpTokenStakedInGauge doesn't get called\n // when tokenId == 0\n if (tokenId != 0 && _isLpTokenStakedInGauge()) {\n clGauge.withdraw(tokenId);\n }\n _;\n // because of solidity short-circuit _isLpTokenStakedInGauge doesn't get called\n // when tokenId == 0\n if (tokenId != 0 && !_isLpTokenStakedInGauge()) {\n /**\n * It can happen that a withdrawal (or a full withdrawal) transactions would\n * remove all of the liquidity from the token with a NFT token still existing.\n * In that case the token can not be staked into the gauge, as some liquidity\n * needs to be added to it first.\n */\n if (_getLiquidity() > 0) {\n // if token liquidity changes the positionManager requires re-approval.\n // to any contract pre-approved to handle the token.\n positionManager.approve(address(clGauge), tokenId);\n clGauge.deposit(tokenId);\n }\n }\n }\n\n // slither-disable-end reentrancy-no-eth\n\n /// @notice the constructor\n /// @dev This contract is intended to be used as a proxy. To prevent the\n /// potential confusion of having a functional implementation contract\n /// the constructor has the `initializer` modifier. This way the\n /// `initialize` function can not be called on the implementation contract.\n /// For the same reason the implementation contract also has the governor\n /// set to a zero address.\n /// @param _stratConfig the basic strategy configuration\n /// @param _wethAddress Address of the Erc20 WETH Token contract\n /// @param _oethbAddress Address of the Erc20 OETHb Token contract\n /// @param _swapRouter Address of the Aerodrome Universal Swap Router\n /// @param _nonfungiblePositionManager Address of position manager to add/remove\n /// the liquidity\n /// @param _clPool Address of the Aerodrome concentrated liquidity pool\n /// @param _clGauge Address of the Aerodrome slipstream pool gauge\n /// @param _sugarHelper Address of the Aerodrome Sugar helper contract\n /// @param _lowerBoundingTick Smaller bounding tick of our liquidity position\n /// @param _upperBoundingTick Larger bounding tick of our liquidity position\n /// @param _tickClosestToParity Tick that is closer to 1:1 price parity\n constructor(\n BaseStrategyConfig memory _stratConfig,\n address _wethAddress,\n address _oethbAddress,\n address _swapRouter,\n address _nonfungiblePositionManager,\n address _clPool,\n address _clGauge,\n address _sugarHelper,\n int24 _lowerBoundingTick,\n int24 _upperBoundingTick,\n int24 _tickClosestToParity\n ) initializer InitializableAbstractStrategy(_stratConfig) {\n require(\n _lowerBoundingTick == _tickClosestToParity ||\n _upperBoundingTick == _tickClosestToParity,\n \"Misconfigured tickClosestToParity\"\n );\n require(\n ICLPool(_clPool).token0() == _wethAddress,\n \"Only WETH supported as token0\"\n );\n require(\n ICLPool(_clPool).token1() == _oethbAddress,\n \"Only OETHb supported as token1\"\n );\n int24 _tickSpacing = ICLPool(_clPool).tickSpacing();\n // when we generalize AMO we might support other tick spacings\n require(_tickSpacing == 1, \"Unsupported tickSpacing\");\n\n WETH = _wethAddress;\n OETHb = _oethbAddress;\n swapRouter = ISwapRouter(_swapRouter);\n positionManager = INonfungiblePositionManager(\n _nonfungiblePositionManager\n );\n clPool = ICLPool(_clPool);\n clGauge = ICLGauge(_clGauge);\n helper = ISugarHelper(_sugarHelper);\n sqrtRatioX96TickLower = ISugarHelper(_sugarHelper).getSqrtRatioAtTick(\n _lowerBoundingTick\n );\n sqrtRatioX96TickHigher = ISugarHelper(_sugarHelper).getSqrtRatioAtTick(\n _upperBoundingTick\n );\n sqrtRatioX96TickClosestToParity = ISugarHelper(_sugarHelper)\n .getSqrtRatioAtTick(_tickClosestToParity);\n\n lowerTick = _lowerBoundingTick;\n upperTick = _upperBoundingTick;\n tickSpacing = _tickSpacing;\n\n // prevent implementation contract to be governed\n _setGovernor(address(0));\n }\n\n /**\n * @notice initialize function, to set up initial internal state\n * @param _rewardTokenAddresses Address of reward token for platform\n */\n function initialize(address[] memory _rewardTokenAddresses)\n external\n onlyGovernor\n initializer\n {\n InitializableAbstractStrategy._initialize(\n _rewardTokenAddresses,\n new address[](0),\n new address[](0)\n );\n }\n\n /***************************************\n Configuration \n ****************************************/\n\n /**\n * @notice Set allowed pool weth share interval. After the rebalance happens\n * the share of WETH token in the ticker needs to be withing the specifications\n * of the interval.\n *\n * @param _allowedWethShareStart Start of WETH share interval expressed as 18 decimal amount\n * @param _allowedWethShareEnd End of WETH share interval expressed as 18 decimal amount\n */\n function setAllowedPoolWethShareInterval(\n uint256 _allowedWethShareStart,\n uint256 _allowedWethShareEnd\n ) external onlyGovernor {\n require(\n _allowedWethShareStart < _allowedWethShareEnd,\n \"Invalid interval\"\n );\n // can not go below 1% weth share\n require(_allowedWethShareStart > 0.01 ether, \"Invalid interval start\");\n // can not go above 95% weth share\n require(_allowedWethShareEnd < 0.95 ether, \"Invalid interval end\");\n\n allowedWethShareStart = _allowedWethShareStart;\n allowedWethShareEnd = _allowedWethShareEnd;\n emit PoolWethShareIntervalUpdated(\n allowedWethShareStart,\n allowedWethShareEnd\n );\n }\n\n /***************************************\n Periphery utils\n ****************************************/\n\n function _isLpTokenStakedInGauge() internal view returns (bool) {\n require(tokenId != 0, \"Missing NFT LP token\");\n\n address owner = positionManager.ownerOf(tokenId);\n require(\n owner == address(clGauge) || owner == address(this),\n \"Unexpected token owner\"\n );\n return owner == address(clGauge);\n }\n\n /***************************************\n Strategy overrides \n ****************************************/\n\n /**\n * @notice Deposit an amount of assets into the strategy contract. Calling deposit doesn't\n * automatically deposit funds into the underlying Aerodrome pool\n * @param _asset Address for the asset\n * @param _amount Units of asset to deposit\n */\n function deposit(address _asset, uint256 _amount)\n external\n override\n onlyVault\n nonReentrant\n {\n _deposit(_asset, _amount);\n }\n\n /**\n * @notice Deposit WETH to the strategy contract. This function does not add liquidity to the\n * underlying Aerodrome pool.\n */\n function depositAll() external override onlyVault nonReentrant {\n uint256 _wethBalance = IERC20(WETH).balanceOf(address(this));\n if (_wethBalance > 1e12) {\n _deposit(WETH, _wethBalance);\n }\n }\n\n /**\n * @dev Deposit WETH to the contract. This function doesn't deposit the liquidity to the\n * pool, that is done via the rebalance call.\n * @param _asset Address of the asset to deposit\n * @param _amount Amount of assets to deposit\n */\n function _deposit(address _asset, uint256 _amount) internal {\n require(_asset == WETH, \"Unsupported asset\");\n require(_amount > 0, \"Must deposit something\");\n emit Deposit(_asset, address(0), _amount);\n\n // if the pool price is not within the expected interval leave the WETH on the contract\n // as to not break the mints\n (bool _isExpectedRange, ) = _checkForExpectedPoolPrice(false);\n if (_isExpectedRange) {\n // deposit funds into the underlying pool\n _rebalance(0, false, 0);\n }\n }\n\n /**\n * @notice Rebalance the pool to the desired token split and Deposit any WETH on the contract to the\n * underlying aerodrome pool. Print the required amount of corresponding OETHb. After the rebalancing is\n * done burn any potentially remaining OETHb tokens still on the strategy contract.\n *\n * This function has a slightly different behaviour depending on the status of the underlying Aerodrome\n * slipstream pool. The function consists of the following 3 steps:\n * 1. withdrawPartialLiquidity -> so that moving the activeTrading price via a swap is cheaper\n * 2. swapToDesiredPosition -> move active trading price in the pool to be able to deposit WETH & OETHb\n * tokens with the desired pre-configured shares\n * 3. addLiquidity -> add liquidity into the pool respecting share split configuration\n *\n * Scenario 1: When there is no liquidity in the pool from the strategy but there is from other LPs then\n * only step 1 is skipped. (It is important to note that liquidity needs to exist in the configured\n * strategy tick ranges in order for the swap to be possible) Step 3 mints new liquidity position\n * instead of adding to an existing one.\n * Scenario 2: When there is strategy's liquidity in the pool all 3 steps are taken\n *\n *\n * Exact _amountToSwap, _swapWeth & _minTokenReceived parameters shall be determined by simulating the\n * transaction off-chain. The strategy checks that after the swap the share of the tokens is in the\n * expected ranges.\n *\n * @param _amountToSwap The amount of the token to swap\n * @param _swapWeth Swap using WETH when true, use OETHb when false\n * @param _minTokenReceived Slippage check -> minimum amount of token expected in return\n */\n function rebalance(\n uint256 _amountToSwap,\n bool _swapWeth,\n uint256 _minTokenReceived\n ) external nonReentrant onlyGovernorOrStrategist {\n _rebalance(_amountToSwap, _swapWeth, _minTokenReceived);\n }\n\n function _rebalance(\n uint256 _amountToSwap,\n bool _swapWeth,\n uint256 _minTokenReceived\n ) internal {\n /**\n * Would be nice to check if there is any total liquidity in the pool before performing this swap\n * but there is no easy way to do that in UniswapV3:\n * - clPool.liquidity() -> only liquidity in the active tick\n * - asset[1&2].balanceOf(address(clPool)) -> will include uncollected tokens of LP providers\n * after their liquidity position has been decreased\n */\n\n /**\n * When rebalance is called for the first time there is no strategy\n * liquidity in the pool yet. The liquidity removal is thus skipped.\n * Also execute this function when WETH is required for the swap.\n */\n if (tokenId != 0 && _swapWeth && _amountToSwap > 0) {\n _ensureWETHBalance(_amountToSwap);\n }\n\n // in some cases we will just want to add liquidity and not issue a swap to move the\n // active trading position within the pool\n if (_amountToSwap > 0) {\n _swapToDesiredPosition(_amountToSwap, _swapWeth, _minTokenReceived);\n }\n // calling check liquidity early so we don't get unexpected errors when adding liquidity\n // in the later stages of this function\n _checkForExpectedPoolPrice(true);\n\n _addLiquidity();\n\n // this call shouldn't be necessary, since adding liquidity shouldn't affect the active\n // trading price. It is a defensive programming measure.\n (, uint256 _wethSharePct) = _checkForExpectedPoolPrice(true);\n\n // revert if protocol insolvent\n _solvencyAssert();\n\n emit PoolRebalanced(_wethSharePct);\n }\n\n /**\n * Checks that the protocol is solvent, protecting from a rogue Strategist / Guardian that can\n * keep rebalancing the pool in both directions making the protocol lose a tiny amount of\n * funds each time.\n *\n * Protocol must be at least SOLVENCY_THRESHOLD (99,8 %) backed in order for the rebalances to\n * function.\n */\n function _solvencyAssert() internal view {\n uint256 _totalVaultValue = IVault(vaultAddress).totalValue();\n uint256 _totalOethbSupply = IERC20(OETHb).totalSupply();\n\n if (\n _totalVaultValue.divPrecisely(_totalOethbSupply) <\n SOLVENCY_THRESHOLD\n ) {\n revert(\"Protocol insolvent\");\n }\n }\n\n /**\n * @dev Decrease partial or all liquidity from the pool.\n * @param _liquidityToDecrease The amount of liquidity to remove expressed in 18 decimal point\n */\n function _removeLiquidity(uint256 _liquidityToDecrease)\n internal\n gaugeUnstakeAndRestake\n {\n require(_liquidityToDecrease > 0, \"Must remove some liquidity\");\n\n uint128 _liquidity = _getLiquidity();\n // need to convert to uint256 since intermittent result is to big for uint128 to handle\n uint128 _liquidityToRemove = uint256(_liquidity)\n .mulTruncate(_liquidityToDecrease)\n .toUint128();\n\n /**\n * There is no liquidity to remove -> exit function early. This can happen after a\n * withdraw/withdrawAll removes all of the liquidity while retaining the NFT token.\n */\n if (_liquidity == 0 || _liquidityToRemove == 0) {\n return;\n }\n\n (uint256 _amountWeth, uint256 _amountOethb) = positionManager\n .decreaseLiquidity(\n // Both expected amounts can be 0 since we don't really care if any swaps\n // happen just before the liquidity removal.\n INonfungiblePositionManager.DecreaseLiquidityParams({\n tokenId: tokenId,\n liquidity: _liquidityToRemove,\n amount0Min: 0,\n amount1Min: 0,\n deadline: block.timestamp\n })\n );\n\n (\n uint256 _amountWethCollected,\n uint256 _amountOethbCollected\n ) = positionManager.collect(\n INonfungiblePositionManager.CollectParams({\n tokenId: tokenId,\n recipient: address(this),\n amount0Max: type(uint128).max, // defaults to all tokens owed\n amount1Max: type(uint128).max // defaults to all tokens owed\n })\n );\n\n _updateUnderlyingAssets();\n\n emit LiquidityRemoved(\n _liquidityToDecrease,\n _amountWeth, //removedWethAmount\n _amountOethb, //removedOethbAmount\n _amountWethCollected,\n _amountOethbCollected,\n underlyingAssets\n );\n\n _burnOethbOnTheContract();\n }\n\n /**\n * @dev Perform a swap so that after the swap the ticker has the desired WETH to OETHb token share.\n */\n function _swapToDesiredPosition(\n uint256 _amountToSwap,\n bool _swapWeth,\n uint256 _minTokenReceived\n ) internal {\n IERC20 _tokenToSwap = IERC20(_swapWeth ? WETH : OETHb);\n uint256 _balance = _tokenToSwap.balanceOf(address(this));\n\n if (_balance < _amountToSwap) {\n // This should never trigger since _ensureWETHBalance will already\n // throw an error if there is not enough WETH\n if (_swapWeth) {\n revert NotEnoughWethForSwap(_balance, _amountToSwap);\n }\n // if swapping OETHb\n uint256 mintForSwap = _amountToSwap - _balance;\n IVault(vaultAddress).mintForStrategy(mintForSwap);\n }\n\n // approve the specific amount of WETH required\n if (_swapWeth) {\n IERC20(WETH).approve(address(swapRouter), _amountToSwap);\n }\n\n // Swap it\n swapRouter.exactInputSingle(\n // sqrtPriceLimitX96 is just a rough sanity check that we are within 0 -> 1 tick\n // a more fine check is performed in _checkForExpectedPoolPrice\n // Note: this needs further work if we want to generalize this approach\n ISwapRouter.ExactInputSingleParams({\n tokenIn: address(_tokenToSwap),\n tokenOut: _swapWeth ? OETHb : WETH,\n tickSpacing: tickSpacing, // set to 1\n recipient: address(this),\n deadline: block.timestamp,\n amountIn: _amountToSwap,\n amountOutMinimum: _minTokenReceived, // slippage check\n sqrtPriceLimitX96: _swapWeth\n ? sqrtRatioX96TickLower\n : sqrtRatioX96TickHigher\n })\n );\n\n /**\n * In the interest of each function in _rebalance to leave the contract state as\n * clean as possible the OETHb tokens here are burned. This decreases the\n * dependence where `_swapToDesiredPosition` function relies on later functions\n * (`addLiquidity`) to burn the OETHb. Reducing the risk of error introduction.\n */\n _burnOethbOnTheContract();\n }\n\n /**\n * @dev Add liquidity into the pool in the pre-configured WETH to OETHb share ratios\n * defined by the allowedPoolWethShareStart|End interval. This function will respect\n * liquidity ratios when there is no liquidity yet in the pool. If liquidity is already\n * present then it relies on the `_swapToDesiredPosition` function in a step before\n * to already move the trading price to desired position (with some tolerance).\n */\n // rebalance already has re-entrency checks\n // slither-disable-start reentrancy-no-eth\n function _addLiquidity() internal gaugeUnstakeAndRestake {\n uint256 _wethBalance = IERC20(WETH).balanceOf(address(this));\n uint256 _oethbBalance = IERC20(OETHb).balanceOf(address(this));\n // don't deposit small liquidity amounts\n if (_wethBalance <= 1e12) {\n return;\n }\n\n uint160 _currentPrice = getPoolX96Price();\n /**\n * Sanity check active trading price is positioned within our desired tick.\n *\n * We revert when price is equal to the lower tick even though that is still\n * a valid amount in regards to ticker position by Sugar.estimateAmount call.\n * Current price equaling tick bound at the 1:1 price parity results in\n * uint overfow when calculating the OETHb balance to deposit.\n */\n if (\n _currentPrice <= sqrtRatioX96TickLower ||\n _currentPrice >= sqrtRatioX96TickHigher\n ) {\n revert OutsideExpectedTickRange(getCurrentTradingTick());\n }\n\n /**\n * If estimateAmount1 call fails it could be due to _currentPrice being really\n * close to a tick and amount1 is a larger number than the sugar helper is able\n * to compute.\n *\n * If token addresses were reversed estimateAmount0 would be required here\n */\n uint256 _oethbRequired = helper.estimateAmount1(\n _wethBalance,\n address(0), // no need to pass pool address when current price is specified\n _currentPrice,\n lowerTick,\n upperTick\n );\n\n if (_oethbRequired > _oethbBalance) {\n IVault(vaultAddress).mintForStrategy(\n _oethbRequired - _oethbBalance\n );\n }\n\n // approve the specific amount of WETH required\n IERC20(WETH).approve(address(positionManager), _wethBalance);\n\n uint256 _wethAmountSupplied;\n uint256 _oethbAmountSupplied;\n if (tokenId == 0) {\n (\n tokenId,\n ,\n _wethAmountSupplied,\n _oethbAmountSupplied\n ) = positionManager.mint(\n /** amount0Min & amount1Min are left at 0 because slippage protection is ensured by the\n * _checkForExpectedPoolPrice\n *›\n * Also sqrtPriceX96 is 0 because the pool is already created\n * non zero amount attempts to create a new instance of the pool\n */\n INonfungiblePositionManager.MintParams({\n token0: WETH,\n token1: OETHb,\n tickSpacing: tickSpacing,\n tickLower: lowerTick,\n tickUpper: upperTick,\n amount0Desired: _wethBalance,\n amount1Desired: _oethbRequired,\n amount0Min: 0,\n amount1Min: 0,\n recipient: address(this),\n deadline: block.timestamp,\n sqrtPriceX96: 0\n })\n );\n } else {\n (, _wethAmountSupplied, _oethbAmountSupplied) = positionManager\n .increaseLiquidity(\n /** amount0Min & amount1Min are left at 0 because slippage protection is ensured by the\n * _checkForExpectedPoolPrice\n */\n INonfungiblePositionManager.IncreaseLiquidityParams({\n tokenId: tokenId,\n amount0Desired: _wethBalance,\n amount1Desired: _oethbRequired,\n amount0Min: 0,\n amount1Min: 0,\n deadline: block.timestamp\n })\n );\n }\n\n _updateUnderlyingAssets();\n emit LiquidityAdded(\n _wethBalance, // wethAmountDesired\n _oethbRequired, // oethbAmountDesired\n _wethAmountSupplied, // wethAmountSupplied\n _oethbAmountSupplied, // oethbAmountSupplied\n tokenId, // tokenId\n underlyingAssets\n );\n\n // burn remaining OETHb\n _burnOethbOnTheContract();\n }\n\n // slither-disable-end reentrancy-no-eth\n\n /**\n * @dev Check that the Aerodrome pool price is within the expected\n * parameters.\n * This function works whether the strategy contract has liquidity\n * position in the pool or not. The function returns _wethSharePct\n * as a gas optimization measure.\n * @param throwException when set to true the function throws an exception\n * when pool's price is not within expected range.\n * @return _isExpectedRange Bool expressing price is within expected range\n * @return _wethSharePct Share of WETH owned by this strategy contract in the\n * configured ticker.\n */\n function _checkForExpectedPoolPrice(bool throwException)\n internal\n view\n returns (bool _isExpectedRange, uint256 _wethSharePct)\n {\n require(\n allowedWethShareStart != 0 && allowedWethShareEnd != 0,\n \"Weth share interval not set\"\n );\n\n uint160 _currentPrice = getPoolX96Price();\n\n /**\n * First check we are in expected tick range\n *\n * We revert even though price being equal to the lower tick would still\n * count being within lower tick for the purpose of Sugar.estimateAmount calls\n */\n if (\n _currentPrice <= sqrtRatioX96TickLower ||\n _currentPrice >= sqrtRatioX96TickHigher\n ) {\n if (throwException) {\n revert OutsideExpectedTickRange(getCurrentTradingTick());\n }\n return (false, 0);\n }\n\n // 18 decimal number expressed WETH tick share\n _wethSharePct = _getWethShare(_currentPrice);\n\n if (\n _wethSharePct < allowedWethShareStart ||\n _wethSharePct > allowedWethShareEnd\n ) {\n if (throwException) {\n revert PoolRebalanceOutOfBounds(\n _wethSharePct,\n allowedWethShareStart,\n allowedWethShareEnd\n );\n }\n return (false, _wethSharePct);\n }\n\n return (true, _wethSharePct);\n }\n\n /**\n * Burns any OETHb tokens remaining on the strategy contract\n */\n function _burnOethbOnTheContract() internal {\n uint256 _oethbBalance = IERC20(OETHb).balanceOf(address(this));\n if (_oethbBalance > 1e12) {\n IVault(vaultAddress).burnForStrategy(_oethbBalance);\n }\n }\n\n /// @dev This function assumes there are no uncollected tokens in the clPool owned by the strategy contract.\n /// For that reason any liquidity withdrawals must also collect the tokens.\n function _updateUnderlyingAssets() internal {\n if (tokenId == 0) {\n underlyingAssets = 0;\n emit UnderlyingAssetsUpdated(underlyingAssets);\n return;\n }\n\n uint128 _liquidity = _getLiquidity();\n\n /**\n * Our net value represent the smallest amount of tokens we are able to extract from the position\n * given our liquidity.\n *\n * The least amount of tokens extraditable from the position is where the active trading price is\n * at the ticker 0 meaning the pool is offering 1:1 trades between WETH & OETHb. At that moment the pool\n * consists completely of OETHb and no WETH.\n *\n * The more swaps from WETH -> OETHb happen on the pool the more the price starts to move towards the -1\n * ticker making OETHb (priced in WETH) more expensive.\n *\n * An additional note: when liquidity is 0 then the helper returns 0 for both token amounts. And the\n * function set underlying assets to 0.\n */\n (uint256 _wethAmount, uint256 _oethbAmount) = helper\n .getAmountsForLiquidity(\n sqrtRatioX96TickClosestToParity, // sqrtRatioX96\n sqrtRatioX96TickLower, // sqrtRatioAX96\n sqrtRatioX96TickHigher, // sqrtRatioBX96\n _liquidity\n );\n\n require(_wethAmount == 0, \"Non zero wethAmount\");\n underlyingAssets = _oethbAmount;\n emit UnderlyingAssetsUpdated(underlyingAssets);\n }\n\n /**\n * @dev This function removes the appropriate amount of liquidity to assure that the required\n * amount of WETH is available on the contract\n *\n * @param _amount WETH balance required on the contract\n */\n function _ensureWETHBalance(uint256 _amount) internal {\n uint256 _wethBalance = IERC20(WETH).balanceOf(address(this));\n if (_wethBalance >= _amount) {\n return;\n }\n\n require(tokenId != 0, \"No liquidity available\");\n uint256 _additionalWethRequired = _amount - _wethBalance;\n (uint256 _wethInThePool, ) = getPositionPrincipal();\n\n if (_wethInThePool < _additionalWethRequired) {\n revert NotEnoughWethLiquidity(\n _wethInThePool,\n _additionalWethRequired\n );\n }\n\n uint256 shareOfWethToRemove = Math.min(\n _additionalWethRequired.divPrecisely(_wethInThePool) + 1,\n 1e18\n );\n _removeLiquidity(shareOfWethToRemove);\n }\n\n /**\n * @notice Withdraw an `amount` of assets from the platform and\n * send to the `_recipient`.\n * @param _recipient Address to which the asset should be sent\n * @param _asset WETH address\n * @param _amount Amount of WETH to withdraw\n */\n function withdraw(\n address _recipient,\n address _asset,\n uint256 _amount\n ) external override onlyVault nonReentrant {\n require(_asset == WETH, \"Unsupported asset\");\n require(_recipient == vaultAddress, \"Only withdraw to vault allowed\");\n\n _ensureWETHBalance(_amount);\n\n _withdraw(_recipient, _amount);\n }\n\n /**\n * @notice Withdraw WETH and sends it to the Vault.\n */\n function withdrawAll() external override onlyVault nonReentrant {\n if (tokenId != 0) {\n _removeLiquidity(1e18);\n }\n\n uint256 _balance = IERC20(WETH).balanceOf(address(this));\n if (_balance > 0) {\n _withdraw(vaultAddress, _balance);\n }\n }\n\n function _withdraw(address _recipient, uint256 _amount) internal {\n require(_amount > 0, \"Must withdraw something\");\n require(_recipient == vaultAddress, \"Only withdraw to vault allowed\");\n\n IERC20(WETH).safeTransfer(_recipient, _amount);\n emit Withdrawal(WETH, address(0), _amount);\n }\n\n /**\n * @dev Collect the AERO token from the gauge\n */\n function _collectRewardTokens() internal override {\n if (tokenId != 0 && _isLpTokenStakedInGauge()) {\n clGauge.getReward(tokenId);\n }\n super._collectRewardTokens();\n }\n\n /**\n * @dev Retuns bool indicating whether asset is supported by strategy\n * @param _asset Address of the asset\n */\n function supportsAsset(address _asset) public view override returns (bool) {\n return _asset == WETH;\n }\n\n /**\n * @dev Approve the spending of all assets\n */\n function safeApproveAllTokens()\n external\n override\n onlyGovernor\n nonReentrant\n {\n // to add liquidity to the clPool\n IERC20(OETHb).approve(address(positionManager), type(uint256).max);\n // to be able to rebalance using the swapRouter\n IERC20(OETHb).approve(address(swapRouter), type(uint256).max);\n\n /* the behaviour of this strategy has slightly changed and WETH could be\n * present on the contract between the transactions. For that reason we are\n * un-approving WETH to the swapRouter & positionManager and only approving\n * the required amount before a transaction\n */\n IERC20(WETH).approve(address(swapRouter), 0);\n IERC20(WETH).approve(address(positionManager), 0);\n }\n\n /***************************************\n Balances and Fees\n ****************************************/\n\n /**\n * @dev Get the total asset value held in the platform\n * @param _asset Address of the asset\n * @return balance Total value of the asset in the platform\n */\n function checkBalance(address _asset)\n external\n view\n override\n returns (uint256)\n {\n require(_asset == WETH, \"Only WETH supported\");\n\n // we could in theory deposit to the strategy and forget to call rebalance in the same\n // governance transaction batch. In that case the WETH that is on the strategy contract\n // also needs to be accounted for.\n uint256 _wethBalance = IERC20(WETH).balanceOf(address(this));\n // just paranoia check, in case there is OETHb in the strategy that for some reason hasn't\n // been burned yet.\n uint256 _oethbBalance = IERC20(OETHb).balanceOf(address(this));\n return underlyingAssets + _wethBalance + _oethbBalance;\n }\n\n /**\n * @dev Returns the balance of both tokens in a given position (excluding fees)\n * @return _amountWeth Amount of WETH in position\n * @return _amountOethb Amount of OETHb in position\n */\n function getPositionPrincipal()\n public\n view\n returns (uint256 _amountWeth, uint256 _amountOethb)\n {\n if (tokenId == 0) {\n return (0, 0);\n }\n\n uint160 _sqrtRatioX96 = getPoolX96Price();\n (_amountWeth, _amountOethb) = helper.principal(\n positionManager,\n tokenId,\n _sqrtRatioX96\n );\n }\n\n /**\n * @notice Returns the current pool price in X96 format\n * @return _sqrtRatioX96 Pool price\n */\n function getPoolX96Price() public view returns (uint160 _sqrtRatioX96) {\n (_sqrtRatioX96, , , , , ) = clPool.slot0();\n }\n\n /**\n * @notice Returns the current active trading tick of the underlying pool\n * @return _currentTick Current pool trading tick\n */\n function getCurrentTradingTick() public view returns (int24 _currentTick) {\n (, _currentTick, , , , ) = clPool.slot0();\n }\n\n /**\n * @notice Returns the percentage of WETH liquidity in the configured ticker\n * owned by this strategy contract.\n * @return uint256 1e18 denominated percentage expressing the share\n */\n function getWETHShare() external view returns (uint256) {\n uint160 _currentPrice = getPoolX96Price();\n return _getWethShare(_currentPrice);\n }\n\n /**\n * @notice Returns the amount of liquidity in the contract's LP position\n * @return _liquidity Amount of liquidity in the position\n */\n function _getLiquidity() internal view returns (uint128 _liquidity) {\n if (tokenId == 0) {\n revert(\"No LP position\");\n }\n\n (, , , , , , , _liquidity, , , , ) = positionManager.positions(tokenId);\n }\n\n function _getWethShare(uint160 _currentPrice)\n internal\n view\n returns (uint256)\n {\n /**\n * If estimateAmount1 call fails it could be due to _currentPrice being really\n * close to a tick and amount1 too big to compute.\n *\n * If token addresses were reversed estimateAmount0 would be required here\n */\n uint256 _normalizedWethAmount = 1 ether;\n uint256 _correspondingOethAmount = helper.estimateAmount1(\n _normalizedWethAmount,\n address(0), // no need to pass pool address when current price is specified\n _currentPrice,\n lowerTick,\n upperTick\n );\n\n // 18 decimal number expressed weth tick share\n return\n _normalizedWethAmount.divPrecisely(\n _normalizedWethAmount + _correspondingOethAmount\n );\n }\n\n /***************************************\n Hidden functions\n ****************************************/\n /// @inheritdoc InitializableAbstractStrategy\n function setPTokenAddress(address, address) external override {\n // The pool tokens can never change.\n revert(\"Unsupported method\");\n }\n\n /// @inheritdoc InitializableAbstractStrategy\n function removePToken(uint256) external override {\n // The pool tokens can never change.\n revert(\"Unsupported method\");\n }\n\n /**\n * @dev Not supported\n */\n function _abstractSetPToken(address, address) internal override {\n // the deployer shall call safeApproveAllTokens() to set necessary approvals\n revert(\"Unsupported method\");\n }\n\n /***************************************\n ERC721 management\n ****************************************/\n\n /// @notice Callback function for whenever a NFT is transferred to this contract\n // solhint-disable-next-line max-line-length\n /// Ref: https://docs.openzeppelin.com/contracts/3.x/api/token/erc721#IERC721Receiver-onERC721Received-address-address-uint256-bytes-\n function onERC721Received(\n address,\n address,\n uint256,\n bytes calldata\n ) external returns (bytes4) {\n return this.onERC721Received.selector;\n }\n}\n" + }, + "contracts/strategies/balancer/AbstractAuraStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title OETH Base Balancer Abstract Strategy\n * @author Origin Protocol Inc\n */\n\nimport { AbstractBalancerStrategy } from \"./AbstractBalancerStrategy.sol\";\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport { IERC20 } from \"../../utils/InitializableAbstractStrategy.sol\";\nimport { IERC4626 } from \"../../../lib/openzeppelin/interfaces/IERC4626.sol\";\nimport { StableMath } from \"../../utils/StableMath.sol\";\nimport { IRewardStaking } from \"../IRewardStaking.sol\";\n\nabstract contract AbstractAuraStrategy is AbstractBalancerStrategy {\n using SafeERC20 for IERC20;\n using StableMath for uint256;\n\n /// @notice Address of the Aura rewards pool\n address public immutable auraRewardPoolAddress;\n\n // renamed from __reserved to not shadow AbstractBalancerStrategy.__reserved,\n int256[50] private __reserved_baseAuraStrategy;\n\n constructor(address _auraRewardPoolAddress) {\n auraRewardPoolAddress = _auraRewardPoolAddress;\n }\n\n /**\n * @dev Deposit all Balancer Pool Tokens (BPT) in this strategy contract\n * to the Aura rewards pool.\n */\n function _lpDepositAll() internal virtual override {\n uint256 bptBalance = IERC20(platformAddress).balanceOf(address(this));\n uint256 auraLp = IERC4626(auraRewardPoolAddress).deposit(\n bptBalance,\n address(this)\n );\n require(bptBalance == auraLp, \"Aura LP != BPT\");\n }\n\n /**\n * @dev Withdraw `numBPTTokens` Balancer Pool Tokens (BPT) from\n * the Aura rewards pool to this strategy contract.\n * @param numBPTTokens Number of Balancer Pool Tokens (BPT) to withdraw\n */\n function _lpWithdraw(uint256 numBPTTokens) internal virtual override {\n IRewardStaking(auraRewardPoolAddress).withdrawAndUnwrap(\n numBPTTokens,\n true // also claim reward tokens\n );\n }\n\n /**\n * @dev Withdraw all Balancer Pool Tokens (BPT) from\n * the Aura rewards pool to this strategy contract.\n */\n function _lpWithdrawAll() internal virtual override {\n // Get all the strategy's BPTs in Aura\n // maxRedeem is implemented as balanceOf(address) in Aura\n uint256 bptBalance = IERC4626(auraRewardPoolAddress).maxRedeem(\n address(this)\n );\n\n IRewardStaking(auraRewardPoolAddress).withdrawAndUnwrap(\n bptBalance,\n true // also claim reward tokens\n );\n }\n\n /**\n * @notice Collects BAL and AURA tokens from the rewards pool.\n */\n function collectRewardTokens()\n external\n virtual\n override\n onlyHarvester\n nonReentrant\n {\n /* Similar to Convex, calling this function collects both of the\n * accrued BAL and AURA tokens.\n */\n IRewardStaking(auraRewardPoolAddress).getReward();\n _collectRewardTokens();\n }\n\n /// @notice Balancer Pool Tokens (BPT) in the Balancer pool and the Aura rewards pool.\n function _getBalancerPoolTokens()\n internal\n view\n override\n returns (uint256 balancerPoolTokens)\n {\n balancerPoolTokens =\n IERC20(platformAddress).balanceOf(address(this)) +\n // maxRedeem is implemented as balanceOf(address) in Aura\n IERC4626(auraRewardPoolAddress).maxRedeem(address(this));\n }\n\n function _approveBase() internal virtual override {\n super._approveBase();\n\n IERC20 pToken = IERC20(platformAddress);\n pToken.safeApprove(auraRewardPoolAddress, type(uint256).max);\n }\n}\n" + }, + "contracts/strategies/balancer/AbstractBalancerStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title OETH Balancer Abstract Strategy\n * @author Origin Protocol Inc\n */\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport { IERC20, InitializableAbstractStrategy } from \"../../utils/InitializableAbstractStrategy.sol\";\nimport { IBalancerVault } from \"../../interfaces/balancer/IBalancerVault.sol\";\nimport { IRateProvider } from \"../../interfaces/balancer/IRateProvider.sol\";\nimport { VaultReentrancyLib } from \"./VaultReentrancyLib.sol\";\nimport { IOracle } from \"../../interfaces/IOracle.sol\";\nimport { IWstETH } from \"../../interfaces/IWstETH.sol\";\nimport { IERC4626 } from \"../../../lib/openzeppelin/interfaces/IERC4626.sol\";\nimport { StableMath } from \"../../utils/StableMath.sol\";\n\nabstract contract AbstractBalancerStrategy is InitializableAbstractStrategy {\n using SafeERC20 for IERC20;\n using StableMath for uint256;\n\n address public immutable rETH;\n address public immutable stETH;\n address public immutable wstETH;\n address public immutable frxETH;\n address public immutable sfrxETH;\n\n /// @notice Address of the Balancer vault\n IBalancerVault public immutable balancerVault;\n /// @notice Balancer pool identifier\n bytes32 public immutable balancerPoolId;\n\n // Max withdrawal deviation denominated in 1e18 (1e18 == 100%)\n uint256 public maxWithdrawalDeviation;\n // Max deposit deviation denominated in 1e18 (1e18 == 100%)\n uint256 public maxDepositDeviation;\n\n int256[48] private __reserved;\n\n struct BaseBalancerConfig {\n address rEthAddress; // Address of the rETH token\n address stEthAddress; // Address of the stETH token\n address wstEthAddress; // Address of the wstETH token\n address frxEthAddress; // Address of the frxEth token\n address sfrxEthAddress; // Address of the sfrxEth token\n address balancerVaultAddress; // Address of the Balancer vault\n bytes32 balancerPoolId; // Balancer pool identifier\n }\n\n event MaxWithdrawalDeviationUpdated(\n uint256 _prevMaxDeviationPercentage,\n uint256 _newMaxDeviationPercentage\n );\n event MaxDepositDeviationUpdated(\n uint256 _prevMaxDeviationPercentage,\n uint256 _newMaxDeviationPercentage\n );\n\n /**\n * @dev Ensure we are not in a Vault context when this function is called, by attempting a no-op internal\n * balance operation. If we are already in a Vault transaction (e.g., a swap, join, or exit), the Vault's\n * reentrancy protection will cause this function to revert.\n *\n * Use this modifier with any function that can cause a state change in a pool and is either public itself,\n * or called by a public function *outside* a Vault operation (e.g., join, exit, or swap).\n *\n * This is to protect against Balancer's read-only re-entrancy vulnerability:\n * https://www.notion.so/originprotocol/Balancer-read-only-reentrancy-c686e72c82414ef18fa34312bb02e11b\n */\n modifier whenNotInBalancerVaultContext() {\n VaultReentrancyLib.ensureNotInVaultContext(balancerVault);\n _;\n }\n\n constructor(BaseBalancerConfig memory _balancerConfig) {\n rETH = _balancerConfig.rEthAddress;\n stETH = _balancerConfig.stEthAddress;\n wstETH = _balancerConfig.wstEthAddress;\n frxETH = _balancerConfig.frxEthAddress;\n sfrxETH = _balancerConfig.sfrxEthAddress;\n\n balancerVault = IBalancerVault(_balancerConfig.balancerVaultAddress);\n balancerPoolId = _balancerConfig.balancerPoolId;\n }\n\n /**\n * Initializer for setting up strategy internal state. This overrides the\n * InitializableAbstractStrategy initializer as Balancer's strategies don't fit\n * well within that abstraction.\n * @param _rewardTokenAddresses Address of BAL & AURA\n * @param _assets Addresses of supported assets. MUST be passed in the same\n * order as returned by coins on the pool contract, i.e.\n * WETH, stETH\n * @param _pTokens Platform Token corresponding addresses\n */\n function initialize(\n address[] calldata _rewardTokenAddresses, // BAL & AURA\n address[] calldata _assets,\n address[] calldata _pTokens\n ) external onlyGovernor initializer {\n maxWithdrawalDeviation = 1e16;\n maxDepositDeviation = 1e16;\n\n emit MaxWithdrawalDeviationUpdated(0, maxWithdrawalDeviation);\n emit MaxDepositDeviationUpdated(0, maxDepositDeviation);\n\n IERC20[] memory poolAssets = _getPoolAssets();\n require(\n poolAssets.length == _assets.length,\n \"Pool assets length mismatch\"\n );\n for (uint256 i = 0; i < _assets.length; ++i) {\n address asset = _fromPoolAsset(address(poolAssets[i]));\n require(_assets[i] == asset, \"Pool assets mismatch\");\n }\n\n InitializableAbstractStrategy._initialize(\n _rewardTokenAddresses,\n _assets,\n _pTokens\n );\n _approveBase();\n }\n\n /**\n * @notice Returns bool indicating whether asset is supported by strategy\n * @param _asset Address of the asset\n */\n function supportsAsset(address _asset) public view override returns (bool) {\n return assetToPToken[_asset] != address(0);\n }\n\n /**\n * @notice Get strategy's share of an assets in the Balancer pool.\n * This is not denominated in OUSD/ETH value of the assets in the Balancer pool.\n * @param _asset Address of the Vault collateral asset\n * @return amount the amount of vault collateral assets\n *\n * IMPORTANT if this function is overridden it needs to have a whenNotInBalancerVaultContext\n * modifier on it or it is susceptible to read-only re-entrancy attack\n *\n * @dev it is important that this function is not affected by reporting inflated\n * values of assets in case of any pool manipulation. Such a manipulation could easily\n * exploit the protocol by:\n * - minting OETH\n * - tilting Balancer pool to report higher balances of assets\n * - rebasing() -> all that extra token balances get distributed to OETH holders\n * - tilting pool back\n * - redeeming OETH\n */\n function checkBalance(address _asset)\n external\n view\n virtual\n override\n whenNotInBalancerVaultContext\n returns (uint256 amount)\n {\n require(assetToPToken[_asset] != address(0), \"Unsupported asset\");\n\n uint256 bptBalance = _getBalancerPoolTokens();\n\n /* To calculate the worth of queried asset:\n * - assume that all tokens normalized to their ETH value have an equal split balance\n * in the pool when it is balanced\n * - multiply the BPT amount with the bpt rate to get the ETH denominated amount\n * of strategy's holdings\n * - divide that by the number of tokens we support in the pool to get ETH denominated\n * amount that is applicable to each supported token in the pool.\n *\n * It would be possible to support only 1 asset in the pool (and be exposed to all\n * the assets while holding BPT tokens) and deposit/withdraw/checkBalance using only\n * that asset. TBD: changes to other functions still required if we ever decide to\n * go with such configuration.\n */\n amount = (bptBalance.mulTruncate(\n IRateProvider(platformAddress).getRate()\n ) / assetsMapped.length);\n\n /* If the pool asset is equal to (strategy )_asset it means that a rate\n * provider for that asset exists and that asset is not necessarily\n * pegged to a unit (ETH).\n *\n * Because this function returns the balance of the asset and is not denominated in\n * ETH units we need to convert the ETH denominated amount to asset amount.\n */\n if (_toPoolAsset(_asset) == _asset) {\n amount = amount.divPrecisely(_getRateProviderRate(_asset));\n }\n }\n\n /**\n * @notice Returns the value of all assets managed by this strategy.\n * Uses the Balancer pool's rate (virtual price) to convert the strategy's\n * Balancer Pool Tokens (BPT) to ETH value.\n * @return value The ETH value\n *\n * IMPORTANT if this function is overridden it needs to have a whenNotInBalancerVaultContext\n * modifier on it or it is susceptible to read-only re-entrancy attack\n */\n function checkBalance()\n external\n view\n virtual\n whenNotInBalancerVaultContext\n returns (uint256 value)\n {\n uint256 bptBalance = _getBalancerPoolTokens();\n\n // Convert BPT to ETH value\n value = bptBalance.mulTruncate(\n IRateProvider(platformAddress).getRate()\n );\n }\n\n /// @notice Balancer Pool Tokens (BPT) in the Balancer pool.\n function _getBalancerPoolTokens()\n internal\n view\n virtual\n returns (uint256 balancerPoolTokens)\n {\n balancerPoolTokens = IERC20(platformAddress).balanceOf(address(this));\n }\n\n /* solhint-disable max-line-length */\n /**\n * @notice BPT price is calculated by taking the rate from the rateProvider of the asset in\n * question. If one does not exist it defaults to 1e18. To get the final BPT expected that\n * is multiplied by the underlying asset amount divided by BPT token rate. BPT token rate is\n * similar to Curve's virtual_price and expresses how much has the price of BPT appreciated\n * (e.g. due to swap fees) in relation to the underlying assets\n *\n * Using the above approach makes the strategy vulnerable to a possible MEV attack using\n * flash loan to manipulate the pool before a deposit/withdrawal since the function ignores\n * market values of the assets being priced in BPT.\n *\n * At the time of writing there is no safe on-chain approach to pricing BPT in a way that it\n * would make it invulnerable to MEV pool manipulation. See recent Balancer exploit:\n * https://www.notion.so/originprotocol/Balancer-OETH-strategy-9becdea132704e588782a919d7d471eb?pvs=4#1cf07de12fc64f1888072321e0644348\n *\n * To mitigate MEV possibilities during deposits and withdraws, the VaultValueChecker will use checkBalance before and after the move\n * to ensure the expected changes took place.\n *\n * @param _asset Address of the Balancer pool asset\n * @param _amount Amount of the Balancer pool asset\n * @return bptExpected of BPT expected in exchange for the asset\n *\n * @dev\n * bptAssetPrice = 1e18 (asset peg) * pool_asset_rate\n *\n * bptExpected = bptAssetPrice * asset_amount / BPT_token_rate\n *\n * bptExpected = 1e18 (asset peg) * pool_asset_rate * asset_amount / BPT_token_rate\n * bptExpected = asset_amount * pool_asset_rate / BPT_token_rate\n *\n * further information available here:\n * https://www.notion.so/originprotocol/Balancer-OETH-strategy-9becdea132704e588782a919d7d471eb?pvs=4#ce01495ae70346d8971f5dced809fb83\n */\n /* solhint-enable max-line-length */\n function _getBPTExpected(address _asset, uint256 _amount)\n internal\n view\n virtual\n returns (uint256 bptExpected)\n {\n uint256 bptRate = IRateProvider(platformAddress).getRate();\n uint256 poolAssetRate = _getRateProviderRate(_asset);\n bptExpected = _amount.mulTruncate(poolAssetRate).divPrecisely(bptRate);\n }\n\n function _getBPTExpected(\n address[] memory _assets,\n uint256[] memory _amounts\n ) internal view virtual returns (uint256 bptExpected) {\n require(_assets.length == _amounts.length, \"Assets & amounts mismatch\");\n\n for (uint256 i = 0; i < _assets.length; ++i) {\n uint256 poolAssetRate = _getRateProviderRate(_assets[i]);\n // convert asset amount to ETH amount\n bptExpected += _amounts[i].mulTruncate(poolAssetRate);\n }\n\n uint256 bptRate = IRateProvider(platformAddress).getRate();\n // Convert ETH amount to BPT amount\n bptExpected = bptExpected.divPrecisely(bptRate);\n }\n\n function _lpDepositAll() internal virtual;\n\n function _lpWithdraw(uint256 numBPTTokens) internal virtual;\n\n function _lpWithdrawAll() internal virtual;\n\n /**\n * @notice Balancer returns assets and rateProviders for corresponding assets ordered\n * by numerical order.\n */\n function _getPoolAssets() internal view returns (IERC20[] memory assets) {\n // slither-disable-next-line unused-return\n (assets, , ) = balancerVault.getPoolTokens(balancerPoolId);\n }\n\n /**\n * @dev If an asset is rebasing the Balancer pools have a wrapped versions of assets\n * that the strategy supports. This function converts the pool(wrapped) asset\n * and corresponding amount to strategy asset.\n */\n function _toPoolAsset(address asset, uint256 amount)\n internal\n view\n returns (address poolAsset, uint256 poolAmount)\n {\n if (asset == stETH) {\n poolAsset = wstETH;\n if (amount > 0) {\n poolAmount = IWstETH(wstETH).getWstETHByStETH(amount);\n }\n } else if (asset == frxETH) {\n poolAsset = sfrxETH;\n if (amount > 0) {\n poolAmount = IERC4626(sfrxETH).convertToShares(amount);\n }\n } else {\n poolAsset = asset;\n poolAmount = amount;\n }\n }\n\n /**\n * @dev Converts a Vault collateral asset to a Balancer pool asset.\n * stETH becomes wstETH, frxETH becomes sfrxETH and everything else stays the same.\n * @param asset Address of the Vault collateral asset.\n * @return Address of the Balancer pool asset.\n */\n function _toPoolAsset(address asset) internal view returns (address) {\n if (asset == stETH) {\n return wstETH;\n } else if (asset == frxETH) {\n return sfrxETH;\n }\n return asset;\n }\n\n /**\n * @dev Converts rebasing asset to its wrapped counterpart.\n */\n function _wrapPoolAsset(address asset, uint256 amount)\n internal\n returns (address wrappedAsset, uint256 wrappedAmount)\n {\n if (asset == stETH) {\n wrappedAsset = wstETH;\n if (amount > 0) {\n wrappedAmount = IWstETH(wstETH).wrap(amount);\n }\n } else if (asset == frxETH) {\n wrappedAsset = sfrxETH;\n if (amount > 0) {\n wrappedAmount = IERC4626(sfrxETH).deposit(\n amount,\n address(this)\n );\n }\n } else {\n wrappedAsset = asset;\n wrappedAmount = amount;\n }\n }\n\n /**\n * @dev Converts wrapped asset to its rebasing counterpart.\n */\n function _unwrapPoolAsset(address asset, uint256 amount)\n internal\n returns (uint256 unwrappedAmount)\n {\n if (asset == stETH) {\n unwrappedAmount = IWstETH(wstETH).unwrap(amount);\n } else if (asset == frxETH) {\n unwrappedAmount = IERC4626(sfrxETH).withdraw(\n amount,\n address(this),\n address(this)\n );\n } else {\n unwrappedAmount = amount;\n }\n }\n\n /**\n * @dev If an asset is rebasing the Balancer pools have a wrapped versions of assets\n * that the strategy supports. This function converts the rebasing strategy asset\n * and corresponding amount to wrapped(pool) asset.\n */\n function _fromPoolAsset(address poolAsset, uint256 poolAmount)\n internal\n view\n returns (address asset, uint256 amount)\n {\n if (poolAsset == wstETH) {\n asset = stETH;\n if (poolAmount > 0) {\n amount = IWstETH(wstETH).getStETHByWstETH(poolAmount);\n }\n } else if (poolAsset == sfrxETH) {\n asset = frxETH;\n if (poolAmount > 0) {\n amount = IERC4626(sfrxETH).convertToAssets(poolAmount);\n }\n } else {\n asset = poolAsset;\n amount = poolAmount;\n }\n }\n\n function _fromPoolAsset(address poolAsset)\n internal\n view\n returns (address asset)\n {\n if (poolAsset == wstETH) {\n asset = stETH;\n } else if (poolAsset == sfrxETH) {\n asset = frxETH;\n } else {\n asset = poolAsset;\n }\n }\n\n /**\n * @notice Sets max withdrawal deviation that is considered when removing\n * liquidity from Balancer pools.\n * @param _maxWithdrawalDeviation Max withdrawal deviation denominated in\n * wad (number with 18 decimals): 1e18 == 100%, 1e16 == 1%\n *\n * IMPORTANT Minimum maxWithdrawalDeviation will be 1% (1e16) for production\n * usage. Vault value checker in combination with checkBalance will\n * catch any unexpected manipulation.\n */\n function setMaxWithdrawalDeviation(uint256 _maxWithdrawalDeviation)\n external\n onlyVaultOrGovernorOrStrategist\n {\n require(\n _maxWithdrawalDeviation <= 1e18,\n \"Withdrawal dev. out of bounds\"\n );\n emit MaxWithdrawalDeviationUpdated(\n maxWithdrawalDeviation,\n _maxWithdrawalDeviation\n );\n maxWithdrawalDeviation = _maxWithdrawalDeviation;\n }\n\n /**\n * @notice Sets max deposit deviation that is considered when adding\n * liquidity to Balancer pools.\n * @param _maxDepositDeviation Max deposit deviation denominated in\n * wad (number with 18 decimals): 1e18 == 100%, 1e16 == 1%\n *\n * IMPORTANT Minimum maxDepositDeviation will default to 1% (1e16)\n * for production usage. Vault value checker in combination with\n * checkBalance will catch any unexpected manipulation.\n */\n function setMaxDepositDeviation(uint256 _maxDepositDeviation)\n external\n onlyVaultOrGovernorOrStrategist\n {\n require(_maxDepositDeviation <= 1e18, \"Deposit dev. out of bounds\");\n emit MaxDepositDeviationUpdated(\n maxDepositDeviation,\n _maxDepositDeviation\n );\n maxDepositDeviation = _maxDepositDeviation;\n }\n\n function _approveBase() internal virtual {\n IERC20 pToken = IERC20(platformAddress);\n // Balancer vault for BPT token (required for removing liquidity)\n pToken.safeApprove(address(balancerVault), type(uint256).max);\n }\n\n function _getRateProviderRate(address _asset)\n internal\n view\n virtual\n returns (uint256);\n}\n" + }, + "contracts/strategies/balancer/BalancerMetaPoolStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title OETH Balancer MetaStablePool Strategy\n * @author Origin Protocol Inc\n */\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport { AbstractAuraStrategy, AbstractBalancerStrategy } from \"./AbstractAuraStrategy.sol\";\nimport { IBalancerVault } from \"../../interfaces/balancer/IBalancerVault.sol\";\nimport { IRateProvider } from \"../../interfaces/balancer/IRateProvider.sol\";\nimport { IMetaStablePool } from \"../../interfaces/balancer/IMetaStablePool.sol\";\nimport { IERC20, InitializableAbstractStrategy } from \"../../utils/InitializableAbstractStrategy.sol\";\nimport { StableMath } from \"../../utils/StableMath.sol\";\n\ncontract BalancerMetaPoolStrategy is AbstractAuraStrategy {\n using SafeERC20 for IERC20;\n using StableMath for uint256;\n\n constructor(\n BaseStrategyConfig memory _stratConfig,\n BaseBalancerConfig memory _balancerConfig,\n address _auraRewardPoolAddress\n )\n InitializableAbstractStrategy(_stratConfig)\n AbstractBalancerStrategy(_balancerConfig)\n AbstractAuraStrategy(_auraRewardPoolAddress)\n {}\n\n /**\n * @notice There are no plans to configure BalancerMetaPool as a default\n * asset strategy. For that reason there is no need to support this\n * functionality.\n */\n function deposit(address, uint256)\n external\n override\n onlyVault\n nonReentrant\n {\n revert(\"Not supported\");\n }\n\n /**\n * @notice There are no plans to configure BalancerMetaPool as a default\n * asset strategy. For that reason there is no need to support this\n * functionality.\n */\n function deposit(address[] calldata, uint256[] calldata)\n external\n onlyVault\n nonReentrant\n {\n revert(\"Not supported\");\n }\n\n /**\n * @notice Deposits all supported assets in this strategy contract to the Balancer pool.\n */\n function depositAll() external override onlyVault nonReentrant {\n uint256 assetsLength = assetsMapped.length;\n address[] memory strategyAssets = new address[](assetsLength);\n uint256[] memory strategyAmounts = new uint256[](assetsLength);\n\n // For each vault collateral asset\n for (uint256 i = 0; i < assetsLength; ++i) {\n strategyAssets[i] = assetsMapped[i];\n // Get the asset balance in this strategy contract\n strategyAmounts[i] = IERC20(strategyAssets[i]).balanceOf(\n address(this)\n );\n }\n _deposit(strategyAssets, strategyAmounts);\n }\n\n /*\n * _deposit doesn't require a read-only re-entrancy protection since during the deposit\n * the function enters the Balancer Vault Context. If this function were called as part of\n * the attacking contract (while intercepting execution flow upon receiving ETH) the read-only\n * protection of the Balancer Vault would be triggered. Since the attacking contract would\n * already be in the Balancer Vault context and wouldn't be able to enter it again.\n */\n function _deposit(\n address[] memory _strategyAssets,\n uint256[] memory _strategyAmounts\n ) internal {\n require(\n _strategyAssets.length == _strategyAmounts.length,\n \"Array length missmatch\"\n );\n\n (IERC20[] memory tokens, , ) = balancerVault.getPoolTokens(\n balancerPoolId\n );\n\n uint256[] memory strategyAssetAmountsToPoolAssetAmounts = new uint256[](\n _strategyAssets.length\n );\n address[] memory strategyAssetsToPoolAssets = new address[](\n _strategyAssets.length\n );\n\n for (uint256 i = 0; i < _strategyAssets.length; ++i) {\n address strategyAsset = _strategyAssets[i];\n uint256 strategyAmount = _strategyAmounts[i];\n\n require(\n assetToPToken[strategyAsset] != address(0),\n \"Unsupported asset\"\n );\n strategyAssetsToPoolAssets[i] = _toPoolAsset(strategyAsset);\n\n if (strategyAmount > 0) {\n emit Deposit(strategyAsset, platformAddress, strategyAmount);\n\n // wrap rebasing assets like stETH and frxETH to wstETH and sfrxETH\n (, strategyAssetAmountsToPoolAssetAmounts[i]) = _wrapPoolAsset(\n strategyAsset,\n strategyAmount\n );\n }\n }\n\n uint256[] memory amountsIn = new uint256[](tokens.length);\n address[] memory poolAssets = new address[](tokens.length);\n for (uint256 i = 0; i < tokens.length; ++i) {\n // Convert IERC20 type to address\n poolAssets[i] = address(tokens[i]);\n\n // For each of the mapped assets\n for (uint256 j = 0; j < strategyAssetsToPoolAssets.length; ++j) {\n // If the pool asset is the same as the mapped asset\n if (poolAssets[i] == strategyAssetsToPoolAssets[j]) {\n amountsIn[i] = strategyAssetAmountsToPoolAssetAmounts[j];\n }\n }\n }\n\n uint256 minBPT = _getBPTExpected(\n strategyAssetsToPoolAssets,\n strategyAssetAmountsToPoolAssetAmounts\n );\n uint256 minBPTwDeviation = minBPT.mulTruncate(\n 1e18 - maxDepositDeviation\n );\n\n /* EXACT_TOKENS_IN_FOR_BPT_OUT:\n * User sends precise quantities of tokens, and receives an\n * estimated but unknown (computed at run time) quantity of BPT.\n *\n * ['uint256', 'uint256[]', 'uint256']\n * [EXACT_TOKENS_IN_FOR_BPT_OUT, amountsIn, minimumBPT]\n */\n bytes memory userData = abi.encode(\n IBalancerVault.WeightedPoolJoinKind.EXACT_TOKENS_IN_FOR_BPT_OUT,\n amountsIn,\n minBPTwDeviation\n );\n\n IBalancerVault.JoinPoolRequest memory request = IBalancerVault\n .JoinPoolRequest(poolAssets, amountsIn, userData, false);\n\n // Add the pool assets in this strategy to the balancer pool\n balancerVault.joinPool(\n balancerPoolId,\n address(this),\n address(this),\n request\n );\n\n // Deposit the Balancer Pool Tokens (BPT) into Aura\n _lpDepositAll();\n }\n\n /**\n * @notice Withdraw a Vault collateral asset from the Balancer pool.\n * @param _recipient Address to receive the Vault collateral assets. Typically is the Vault.\n * @param _strategyAsset Address of the Vault collateral asset\n * @param _strategyAmount The amount of Vault collateral assets to withdraw\n */\n function withdraw(\n address _recipient,\n address _strategyAsset,\n uint256 _strategyAmount\n ) external override onlyVault nonReentrant {\n address[] memory strategyAssets = new address[](1);\n uint256[] memory strategyAmounts = new uint256[](1);\n strategyAssets[0] = _strategyAsset;\n strategyAmounts[0] = _strategyAmount;\n\n _withdraw(_recipient, strategyAssets, strategyAmounts);\n }\n\n /**\n * @notice Withdraw multiple Vault collateral asset from the Balancer pool.\n * @param _recipient Address to receive the Vault collateral assets. Typically is the Vault.\n * @param _strategyAssets Addresses of the Vault collateral assets\n * @param _strategyAmounts The amounts of Vault collateral assets to withdraw\n */\n function withdraw(\n address _recipient,\n address[] calldata _strategyAssets,\n uint256[] calldata _strategyAmounts\n ) external onlyVault nonReentrant {\n _withdraw(_recipient, _strategyAssets, _strategyAmounts);\n }\n\n /**\n * @dev Withdraw multiple Vault collateral asset from the Balancer pool.\n * @param _recipient Address to receive the Vault collateral assets. Typically is the Vault.\n * @param _strategyAssets Addresses of the Vault collateral assets\n * @param _strategyAmounts The amounts of Vault collateral assets to withdraw\n *\n * _withdrawal doesn't require a read-only re-entrancy protection since during the withdrawal\n * the function enters the Balancer Vault Context. If this function were called as part of\n * the attacking contract (while intercepting execution flow upon receiving ETH) the read-only\n * protection of the Balancer Vault would be triggered. Since the attacking contract would\n * already be in the Balancer Vault context and wouldn't be able to enter it again.\n */\n function _withdraw(\n address _recipient,\n address[] memory _strategyAssets,\n uint256[] memory _strategyAmounts\n ) internal {\n require(\n _strategyAssets.length == _strategyAmounts.length,\n \"Invalid input arrays\"\n );\n\n for (uint256 i = 0; i < _strategyAssets.length; ++i) {\n require(\n assetToPToken[_strategyAssets[i]] != address(0),\n \"Unsupported asset\"\n );\n }\n\n // STEP 1 - Calculate the Balancer pool assets and amounts from the vault collateral assets\n\n // Get all the supported balancer pool assets\n (IERC20[] memory tokens, , ) = balancerVault.getPoolTokens(\n balancerPoolId\n );\n // Calculate the balancer pool assets and amounts to withdraw\n uint256[] memory poolAssetsAmountsOut = new uint256[](tokens.length);\n address[] memory poolAssets = new address[](tokens.length);\n // Is the wrapped asset amount indexed by the assets array, not the order of the Balancer pool tokens\n // eg wstETH and sfrxETH amounts, not the stETH and frxETH amounts\n uint256[] memory strategyAssetsToPoolAssetsAmounts = new uint256[](\n _strategyAssets.length\n );\n\n // For each of the Balancer pool assets\n for (uint256 i = 0; i < tokens.length; ++i) {\n poolAssets[i] = address(tokens[i]);\n\n // Convert the Balancer pool asset back to a vault collateral asset\n address strategyAsset = _fromPoolAsset(poolAssets[i]);\n\n // for each of the vault assets\n for (uint256 j = 0; j < _strategyAssets.length; ++j) {\n // If the vault asset equals the vault asset mapped from the Balancer pool asset\n if (_strategyAssets[j] == strategyAsset) {\n (, poolAssetsAmountsOut[i]) = _toPoolAsset(\n strategyAsset,\n _strategyAmounts[j]\n );\n strategyAssetsToPoolAssetsAmounts[j] = poolAssetsAmountsOut[\n i\n ];\n\n /* Because of the potential Balancer rounding error mentioned below\n * the contract might receive 1-2 WEI smaller amount than required\n * in the withdraw user data encoding. If slightly lesser token amount\n * is received the strategy can not unwrap the pool asset as it is\n * smaller than expected.\n *\n * For that reason we `overshoot` the required tokens expected to\n * circumvent the error\n */\n if (poolAssetsAmountsOut[i] > 0) {\n poolAssetsAmountsOut[i] += 2;\n }\n }\n }\n }\n\n // STEP 2 - Calculate the max about of Balancer Pool Tokens (BPT) to withdraw\n\n // Estimate the required amount of Balancer Pool Tokens (BPT) for the assets\n uint256 maxBPTtoWithdraw = _getBPTExpected(\n poolAssets,\n /* all non 0 values are overshot by 2 WEI and with the expected mainnet\n * ~1% withdrawal deviation, the 2 WEI aren't important\n */\n poolAssetsAmountsOut\n );\n // Increase BPTs by the max allowed deviation\n // Any excess BPTs will be left in this strategy contract\n maxBPTtoWithdraw = maxBPTtoWithdraw.mulTruncate(\n 1e18 + maxWithdrawalDeviation\n );\n\n // STEP 3 - Withdraw the Balancer Pool Tokens (BPT) from Aura to this strategy contract\n\n // Withdraw BPT from Aura allowing for BPTs left in this strategy contract from previous withdrawals\n _lpWithdraw(\n maxBPTtoWithdraw - IERC20(platformAddress).balanceOf(address(this))\n );\n\n // STEP 4 - Withdraw the balancer pool assets from the pool\n\n /* Custom asset exit: BPT_IN_FOR_EXACT_TOKENS_OUT:\n * User sends an estimated but unknown (computed at run time) quantity of BPT,\n * and receives precise quantities of specified tokens.\n *\n * ['uint256', 'uint256[]', 'uint256']\n * [BPT_IN_FOR_EXACT_TOKENS_OUT, amountsOut, maxBPTAmountIn]\n */\n bytes memory userData = abi.encode(\n IBalancerVault.WeightedPoolExitKind.BPT_IN_FOR_EXACT_TOKENS_OUT,\n poolAssetsAmountsOut,\n maxBPTtoWithdraw\n );\n\n IBalancerVault.ExitPoolRequest memory request = IBalancerVault\n .ExitPoolRequest(\n poolAssets,\n /* We specify the exact amount of a tokens we are expecting in the encoded\n * userData, for that reason we don't need to specify the amountsOut here.\n *\n * Also Balancer has a rounding issue that can make a transaction fail:\n * https://github.com/balancer/balancer-v2-monorepo/issues/2541\n * which is an extra reason why this field is empty.\n */\n new uint256[](tokens.length),\n userData,\n false\n );\n\n balancerVault.exitPool(\n balancerPoolId,\n address(this),\n /* Payable keyword is required because of the IBalancerVault interface even though\n * this strategy shall never be receiving native ETH\n */\n payable(address(this)),\n request\n );\n\n // STEP 5 - Re-deposit any left over BPT tokens back into Aura\n /* When concluding how much of BPT we need to withdraw from Aura we overshoot by\n * roughly around 1% (initial mainnet setting of maxWithdrawalDeviation). After exiting\n * the pool strategy could have left over BPT tokens that are not earning boosted yield.\n * We re-deploy those back in.\n */\n _lpDepositAll();\n\n // STEP 6 - Unswap balancer pool assets to vault collateral assets and send to the vault.\n\n // For each of the specified assets\n for (uint256 i = 0; i < _strategyAssets.length; ++i) {\n // Unwrap assets like wstETH and sfrxETH to rebasing assets stETH and frxETH\n if (strategyAssetsToPoolAssetsAmounts[i] > 0) {\n _unwrapPoolAsset(\n _strategyAssets[i],\n strategyAssetsToPoolAssetsAmounts[i]\n );\n }\n\n // Transfer the vault collateral assets to the recipient, which is typically the vault\n if (_strategyAmounts[i] > 0) {\n IERC20(_strategyAssets[i]).safeTransfer(\n _recipient,\n _strategyAmounts[i]\n );\n\n emit Withdrawal(\n _strategyAssets[i],\n platformAddress,\n _strategyAmounts[i]\n );\n }\n }\n }\n\n /**\n * @notice Withdraws all supported Vault collateral assets from the Balancer pool\n * and send to the OToken's Vault.\n *\n * Is only executable by the OToken's Vault or the Governor.\n */\n function withdrawAll() external override onlyVaultOrGovernor nonReentrant {\n // STEP 1 - Withdraw all Balancer Pool Tokens (BPT) from Aura to this strategy contract\n\n _lpWithdrawAll();\n // Get the BPTs withdrawn from Aura plus any that were already in this strategy contract\n uint256 BPTtoWithdraw = IERC20(platformAddress).balanceOf(\n address(this)\n );\n // Get the balancer pool assets and their total balances\n (IERC20[] memory tokens, , ) = balancerVault.getPoolTokens(\n balancerPoolId\n );\n uint256[] memory minAmountsOut = new uint256[](tokens.length);\n address[] memory poolAssets = new address[](tokens.length);\n for (uint256 i = 0; i < tokens.length; ++i) {\n poolAssets[i] = address(tokens[i]);\n }\n\n // STEP 2 - Withdraw the Balancer pool assets from the pool\n /* Proportional exit: EXACT_BPT_IN_FOR_TOKENS_OUT:\n * User sends a precise quantity of BPT, and receives an estimated but unknown\n * (computed at run time) quantity of a single token\n *\n * ['uint256', 'uint256']\n * [EXACT_BPT_IN_FOR_TOKENS_OUT, bptAmountIn]\n *\n * It is ok to pass an empty minAmountsOut since tilting the pool in any direction\n * when doing a proportional exit can only be beneficial to the strategy. Since\n * it will receive more of the underlying tokens for the BPT traded in.\n */\n bytes memory userData = abi.encode(\n IBalancerVault.WeightedPoolExitKind.EXACT_BPT_IN_FOR_TOKENS_OUT,\n BPTtoWithdraw\n );\n\n IBalancerVault.ExitPoolRequest memory request = IBalancerVault\n .ExitPoolRequest(poolAssets, minAmountsOut, userData, false);\n\n balancerVault.exitPool(\n balancerPoolId,\n address(this),\n /* Payable keyword is required because of the IBalancerVault interface even though\n * this strategy shall never be receiving native ETH\n */\n payable(address(this)),\n request\n );\n\n // STEP 3 - Convert the balancer pool assets to the vault collateral assets and send to the vault\n // For each of the Balancer pool assets\n for (uint256 i = 0; i < tokens.length; ++i) {\n address poolAsset = address(tokens[i]);\n // Convert the balancer pool asset to the strategy asset\n address strategyAsset = _fromPoolAsset(poolAsset);\n // Get the balancer pool assets withdraw from the pool plus any that were already in this strategy contract\n uint256 poolAssetAmount = IERC20(poolAsset).balanceOf(\n address(this)\n );\n\n // Unwrap assets like wstETH and sfrxETH to rebasing assets stETH and frxETH\n uint256 unwrappedAmount = 0;\n if (poolAssetAmount > 0) {\n unwrappedAmount = _unwrapPoolAsset(\n strategyAsset,\n poolAssetAmount\n );\n }\n\n // Transfer the vault collateral assets to the vault\n if (unwrappedAmount > 0) {\n IERC20(strategyAsset).safeTransfer(\n vaultAddress,\n unwrappedAmount\n );\n emit Withdrawal(\n strategyAsset,\n platformAddress,\n unwrappedAmount\n );\n }\n }\n }\n\n /**\n * @notice Approves the Balancer Vault to transfer poolAsset counterparts\n * of all of the supported assets from this strategy. E.g. stETH is a supported\n * strategy and Balancer Vault gets unlimited approval to transfer wstETH.\n *\n * If Balancer pool uses a wrapped version of a supported asset then also approve\n * unlimited usage of an asset to the contract responsible for wrapping.\n *\n * Approve unlimited spending by Balancer Vault and Aura reward pool of the\n * pool BPT tokens.\n *\n * Is only executable by the Governor.\n */\n function safeApproveAllTokens()\n external\n override\n onlyGovernor\n nonReentrant\n {\n uint256 assetCount = assetsMapped.length;\n for (uint256 i = 0; i < assetCount; ++i) {\n _abstractSetPToken(assetsMapped[i], platformAddress);\n }\n _approveBase();\n }\n\n // solhint-disable-next-line no-unused-vars\n function _abstractSetPToken(address _asset, address) internal override {\n address poolAsset = _toPoolAsset(_asset);\n if (_asset == stETH) {\n // slither-disable-next-line unused-return\n IERC20(stETH).approve(wstETH, type(uint256).max);\n } else if (_asset == frxETH) {\n // slither-disable-next-line unused-return\n IERC20(frxETH).approve(sfrxETH, type(uint256).max);\n }\n _approveAsset(poolAsset);\n }\n\n /**\n * @dev Approves the Balancer Vault to transfer an asset from\n * this strategy. The assets could be a Vault collateral asset\n * like WETH or rETH; or a Balancer pool asset that wraps the vault asset\n * like wstETH or sfrxETH.\n */\n function _approveAsset(address _asset) internal {\n IERC20 asset = IERC20(_asset);\n // slither-disable-next-line unused-return\n asset.approve(address(balancerVault), type(uint256).max);\n }\n\n /**\n * @notice Returns the rate supplied by the Balancer configured rate\n * provider. Rate is used to normalize the token to common underlying\n * pool denominator. (ETH for ETH Liquid staking derivatives)\n *\n * @param _asset Address of the Balancer pool asset\n * @return rate of the corresponding asset\n */\n function _getRateProviderRate(address _asset)\n internal\n view\n override\n returns (uint256)\n {\n IMetaStablePool pool = IMetaStablePool(platformAddress);\n IRateProvider[] memory providers = pool.getRateProviders();\n (IERC20[] memory tokens, , ) = balancerVault.getPoolTokens(\n balancerPoolId\n );\n\n uint256 providersLength = providers.length;\n for (uint256 i = 0; i < providersLength; ++i) {\n // _assets and corresponding rate providers are all in the same order\n if (address(tokens[i]) == _asset) {\n // rate provider doesn't exist, defaults to 1e18\n if (address(providers[i]) == address(0)) {\n return 1e18;\n }\n return providers[i].getRate();\n }\n }\n\n // should never happen\n assert(false);\n }\n}\n" + }, + "contracts/strategies/balancer/VaultReentrancyLib.sol": { + "content": "// SPDX-License-Identifier: GPL-3.0-or-later\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see .\n\npragma solidity >=0.7.0 <0.9.0;\n\nimport \"../../utils/BalancerErrors.sol\";\nimport { IBalancerVault } from \"../../interfaces/balancer/IBalancerVault.sol\";\n\nlibrary VaultReentrancyLib {\n /**\n * @dev Ensure we are not in a Vault context when this function is called, by attempting a no-op internal\n * balance operation. If we are already in a Vault transaction (e.g., a swap, join, or exit), the Vault's\n * reentrancy protection will cause this function to revert.\n *\n * The exact function call doesn't really matter: we're just trying to trigger the Vault reentrancy check\n * (and not hurt anything in case it works). An empty operation array with no specific operation at all works\n * for that purpose, and is also the least expensive in terms of gas and bytecode size.\n *\n * Call this at the top of any function that can cause a state change in a pool and is either public itself,\n * or called by a public function *outside* a Vault operation (e.g., join, exit, or swap).\n *\n * If this is *not* called in functions that are vulnerable to the read-only reentrancy issue described\n * here (https://forum.balancer.fi/t/reentrancy-vulnerability-scope-expanded/4345), those functions are unsafe,\n * and subject to manipulation that may result in loss of funds.\n */\n function ensureNotInVaultContext(IBalancerVault vault) internal view {\n // Perform the following operation to trigger the Vault's reentrancy guard:\n //\n // IBalancerVault.UserBalanceOp[] memory noop = new IBalancerVault.UserBalanceOp[](0);\n // _vault.manageUserBalance(noop);\n //\n // However, use a static call so that it can be a view function (even though the function is non-view).\n // This allows the library to be used more widely, as some functions that need to be protected might be\n // view.\n //\n // This staticcall always reverts, but we need to make sure it doesn't fail due to a re-entrancy attack.\n // Staticcalls consume all gas forwarded to them on a revert caused by storage modification.\n // By default, almost the entire available gas is forwarded to the staticcall,\n // causing the entire call to revert with an 'out of gas' error.\n //\n // We set the gas limit to 10k for the staticcall to\n // avoid wasting gas when it reverts due to storage modification.\n // `manageUserBalance` is a non-reentrant function in the Vault, so calling it invokes `_enterNonReentrant`\n // in the `ReentrancyGuard` contract, reproduced here:\n //\n // function _enterNonReentrant() private {\n // // If the Vault is actually being reentered, it will revert in the first line, at the `_require` that\n // // checks the reentrancy flag, with \"BAL#400\" (corresponding to Errors.REENTRANCY) in the revertData.\n // // The full revertData will be: `abi.encodeWithSignature(\"Error(string)\", \"BAL#400\")`.\n // _require(_status != _ENTERED, Errors.REENTRANCY);\n //\n // // If the Vault is not being reentered, the check above will pass: but it will *still* revert,\n // // because the next line attempts to modify storage during a staticcall. However, this type of\n // // failure results in empty revertData.\n // _status = _ENTERED;\n // }\n //\n // So based on this analysis, there are only two possible revertData values: empty, or abi.encoded BAL#400.\n //\n // It is of course much more bytecode and gas efficient to check for zero-length revertData than to compare it\n // to the encoded REENTRANCY revertData.\n //\n // While it should be impossible for the call to fail in any other way (especially since it reverts before\n // `manageUserBalance` even gets called), any other error would generate non-zero revertData, so checking for\n // empty data guards against this case too.\n\n (, bytes memory revertData) = address(vault).staticcall{ gas: 10_000 }(\n abi.encodeWithSelector(vault.manageUserBalance.selector, 0)\n );\n\n _require(revertData.length == 0, Errors.REENTRANCY);\n }\n}\n" + }, + "contracts/strategies/BaseCurveAMOStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title Curve Automated Market Maker (AMO) Strategy\n * @notice AMO strategy for the Curve OETH/WETH pool\n * @author Origin Protocol Inc\n */\nimport { Math } from \"@openzeppelin/contracts/utils/math/Math.sol\";\nimport { SafeCast } from \"@openzeppelin/contracts/utils/math/SafeCast.sol\";\n\nimport { IERC20, InitializableAbstractStrategy } from \"../utils/InitializableAbstractStrategy.sol\";\nimport { StableMath } from \"../utils/StableMath.sol\";\nimport { IVault } from \"../interfaces/IVault.sol\";\nimport { IWETH9 } from \"../interfaces/IWETH9.sol\";\nimport { ICurveStableSwapNG } from \"../interfaces/ICurveStableSwapNG.sol\";\nimport { ICurveXChainLiquidityGauge } from \"../interfaces/ICurveXChainLiquidityGauge.sol\";\nimport { IChildLiquidityGaugeFactory } from \"../interfaces/IChildLiquidityGaugeFactory.sol\";\n\ncontract BaseCurveAMOStrategy is InitializableAbstractStrategy {\n using StableMath for uint256;\n using SafeCast for uint256;\n\n /**\n * @dev a threshold under which the contract no longer allows for the protocol to manually rebalance.\n * Guarding against a strategist / guardian being taken over and with multiple transactions\n * draining the protocol funds.\n */\n uint256 public constant SOLVENCY_THRESHOLD = 0.998 ether;\n\n // New immutable variables that must be set in the constructor\n /**\n * @notice Address of the Wrapped ETH (WETH) contract.\n */\n IWETH9 public immutable weth;\n\n /**\n * @notice Address of the OETH token contract.\n */\n IERC20 public immutable oeth;\n\n /**\n * @notice Address of the LP (Liquidity Provider) token contract.\n */\n IERC20 public immutable lpToken;\n\n /**\n * @notice Address of the Curve StableSwap NG pool contract.\n */\n ICurveStableSwapNG public immutable curvePool;\n\n /**\n * @notice Address of the Curve X-Chain Liquidity Gauge contract.\n */\n ICurveXChainLiquidityGauge public immutable gauge;\n\n /**\n * @notice Address of the Child Liquidity Gauge Factory contract.\n */\n IChildLiquidityGaugeFactory public immutable gaugeFactory;\n\n // Ordered list of pool assets\n uint128 public immutable oethCoinIndex;\n uint128 public immutable wethCoinIndex;\n\n /**\n * @notice Maximum slippage allowed for adding/removing liquidity from the Curve pool.\n */\n uint256 public maxSlippage;\n\n event MaxSlippageUpdated(uint256 newMaxSlippage);\n\n /**\n * @dev Verifies that the caller is the Strategist.\n */\n modifier onlyStrategist() {\n require(\n msg.sender == IVault(vaultAddress).strategistAddr(),\n \"Caller is not the Strategist\"\n );\n _;\n }\n\n /**\n * @dev Checks the Curve pool's balances have improved and the balances\n * have not tipped to the other side.\n * This modifier is only applied to functions that do a single sided add or remove.\n * The standard deposit function adds to both sides of the pool in a way that\n * the pool's balance is not worsened.\n * Withdrawals are proportional so doesn't change the pools asset balance.\n */\n modifier improvePoolBalance() {\n // Get the asset and OToken balances in the Curve pool\n uint256[] memory balancesBefore = curvePool.get_balances();\n // diff = ETH balance - OETH balance\n int256 diffBefore = balancesBefore[wethCoinIndex].toInt256() -\n balancesBefore[oethCoinIndex].toInt256();\n\n _;\n\n // Get the asset and OToken balances in the Curve pool\n uint256[] memory balancesAfter = curvePool.get_balances();\n // diff = ETH balance - OETH balance\n int256 diffAfter = balancesAfter[wethCoinIndex].toInt256() -\n balancesAfter[oethCoinIndex].toInt256();\n\n if (diffBefore == 0) {\n require(diffAfter == 0, \"Position balance is worsened\");\n } else if (diffBefore < 0) {\n // If the pool was originally imbalanced in favor of OETH, then\n // we want to check that the pool is now more balanced\n require(diffAfter <= 0, \"OTokens overshot peg\");\n require(diffBefore < diffAfter, \"OTokens balance worse\");\n } else if (diffBefore > 0) {\n // If the pool was originally imbalanced in favor of ETH, then\n // we want to check that the pool is now more balanced\n require(diffAfter >= 0, \"Assets overshot peg\");\n require(diffAfter < diffBefore, \"Assets balance worse\");\n }\n }\n\n constructor(\n BaseStrategyConfig memory _baseConfig,\n address _oeth,\n address _weth,\n address _gauge,\n address _gaugeFactory,\n uint128 _oethCoinIndex,\n uint128 _wethCoinIndex\n ) InitializableAbstractStrategy(_baseConfig) {\n oethCoinIndex = _oethCoinIndex;\n wethCoinIndex = _wethCoinIndex;\n\n lpToken = IERC20(_baseConfig.platformAddress);\n curvePool = ICurveStableSwapNG(_baseConfig.platformAddress);\n\n oeth = IERC20(_oeth);\n weth = IWETH9(_weth);\n gauge = ICurveXChainLiquidityGauge(_gauge);\n gaugeFactory = IChildLiquidityGaugeFactory(_gaugeFactory);\n\n _setGovernor(address(0));\n }\n\n /**\n * Initializer for setting up strategy internal state. This overrides the\n * InitializableAbstractStrategy initializer as Curve strategies don't fit\n * well within that abstraction.\n * @param _rewardTokenAddresses Address of CRV\n * @param _maxSlippage Maximum slippage allowed for adding/removing liquidity from the Curve pool.\n */\n function initialize(\n address[] calldata _rewardTokenAddresses, // CRV\n uint256 _maxSlippage\n ) external onlyGovernor initializer {\n address[] memory pTokens = new address[](1);\n pTokens[0] = address(curvePool);\n\n address[] memory _assets = new address[](1);\n _assets[0] = address(weth);\n\n InitializableAbstractStrategy._initialize(\n _rewardTokenAddresses,\n _assets,\n pTokens\n );\n\n _approveBase();\n _setMaxSlippage(_maxSlippage);\n }\n\n /***************************************\n Deposit\n ****************************************/\n\n /**\n * @notice Deposit WETH into the Curve pool\n * @param _weth Address of Wrapped ETH (WETH) contract.\n * @param _amount Amount of WETH to deposit.\n */\n function deposit(address _weth, uint256 _amount)\n external\n override\n onlyVault\n nonReentrant\n {\n _deposit(_weth, _amount);\n }\n\n function _deposit(address _weth, uint256 _wethAmount) internal {\n require(_wethAmount > 0, \"Must deposit something\");\n require(_weth == address(weth), \"Can only deposit WETH\");\n\n emit Deposit(_weth, address(lpToken), _wethAmount);\n\n // Get the asset and OToken balances in the Curve pool\n uint256[] memory balances = curvePool.get_balances();\n // safe to cast since min value is at least 0\n uint256 oethToAdd = uint256(\n _max(\n 0,\n balances[wethCoinIndex].toInt256() +\n _wethAmount.toInt256() -\n balances[oethCoinIndex].toInt256()\n )\n );\n\n /* Add so much OETH so that the pool ends up being balanced. And at minimum\n * add as much OETH as WETH and at maximum twice as much OETH.\n */\n oethToAdd = Math.max(oethToAdd, _wethAmount);\n oethToAdd = Math.min(oethToAdd, _wethAmount * 2);\n\n /* Mint OETH with a strategy that attempts to contribute to stability of OETH/WETH pool. Try\n * to mint so much OETH that after deployment of liquidity pool ends up being balanced.\n *\n * To manage unpredictability minimal OETH minted will always be at least equal or greater\n * to WETH amount deployed. And never larger than twice the WETH amount deployed even if\n * it would have a further beneficial effect on pool stability.\n */\n IVault(vaultAddress).mintForStrategy(oethToAdd);\n\n emit Deposit(address(oeth), address(lpToken), oethToAdd);\n\n uint256[] memory _amounts = new uint256[](2);\n _amounts[wethCoinIndex] = _wethAmount;\n _amounts[oethCoinIndex] = oethToAdd;\n\n uint256 valueInLpTokens = (_wethAmount + oethToAdd).divPrecisely(\n curvePool.get_virtual_price()\n );\n uint256 minMintAmount = valueInLpTokens.mulTruncate(\n uint256(1e18) - maxSlippage\n );\n\n // Do the deposit to the Curve pool\n uint256 lpDeposited = curvePool.add_liquidity(_amounts, minMintAmount);\n require(lpDeposited >= minMintAmount, \"Min LP amount error\");\n\n // Deposit the Curve pool's LP tokens into the Curve gauge\n gauge.deposit(lpDeposited);\n\n // Ensure solvency of the vault\n _solvencyAssert();\n }\n\n /**\n * @notice Deposit the strategy's entire balance of WETH into the Curve pool\n */\n function depositAll() external override onlyVault nonReentrant {\n uint256 balance = weth.balanceOf(address(this));\n if (balance > 0) {\n _deposit(address(weth), balance);\n }\n }\n\n /***************************************\n Withdraw\n ****************************************/\n\n /**\n * @notice Withdraw ETH and OETH from the Curve pool, burn the OETH,\n * convert the ETH to WETH and transfer to the recipient.\n * @param _recipient Address to receive withdrawn asset which is normally the Vault.\n * @param _weth Address of the Wrapped ETH (WETH) contract.\n * @param _amount Amount of WETH to withdraw.\n */\n function withdraw(\n address _recipient,\n address _weth,\n uint256 _amount\n ) external override onlyVault nonReentrant {\n require(_amount > 0, \"Must withdraw something\");\n require(_weth == address(weth), \"Can only withdraw WETH\");\n\n emit Withdrawal(_weth, address(lpToken), _amount);\n\n uint256 requiredLpTokens = calcTokenToBurn(_amount);\n\n _lpWithdraw(requiredLpTokens);\n\n /* math in requiredLpTokens should correctly calculate the amount of LP to remove\n * in that the strategy receives enough WETH on balanced removal\n */\n uint256[] memory _minWithdrawalAmounts = new uint256[](2);\n _minWithdrawalAmounts[wethCoinIndex] = _amount;\n // slither-disable-next-line unused-return\n curvePool.remove_liquidity(requiredLpTokens, _minWithdrawalAmounts);\n\n // Burn all the removed OETH and any that was left in the strategy\n uint256 oethToBurn = oeth.balanceOf(address(this));\n IVault(vaultAddress).burnForStrategy(oethToBurn);\n\n emit Withdrawal(address(oeth), address(lpToken), oethToBurn);\n\n // Transfer WETH to the recipient\n require(\n weth.transfer(_recipient, _amount),\n \"Transfer of WETH not successful\"\n );\n\n // Ensure solvency of the vault\n _solvencyAssert();\n }\n\n function calcTokenToBurn(uint256 _wethAmount)\n internal\n view\n returns (uint256 lpToBurn)\n {\n /* The rate between coins in the pool determines the rate at which pool returns\n * tokens when doing balanced removal (remove_liquidity call). And by knowing how much WETH\n * we want we can determine how much of OETH we receive by removing liquidity.\n *\n * Because we are doing balanced removal we should be making profit when removing liquidity in a\n * pool tilted to either side.\n *\n * Important: A downside is that the Strategist / Governor needs to be\n * cognisant of not removing too much liquidity. And while the proposal to remove liquidity\n * is being voted on the pool tilt might change so much that the proposal that has been valid while\n * created is no longer valid.\n */\n\n uint256 poolWETHBalance = curvePool.balances(wethCoinIndex);\n /* K is multiplied by 1e36 which is used for higher precision calculation of required\n * pool LP tokens. Without it the end value can have rounding errors up to precision of\n * 10 digits. This way we move the decimal point by 36 places when doing the calculation\n * and again by 36 places when we are done with it.\n */\n uint256 k = (1e36 * lpToken.totalSupply()) / poolWETHBalance;\n // prettier-ignore\n // slither-disable-next-line divide-before-multiply\n uint256 diff = (_wethAmount + 1) * k;\n lpToBurn = diff / 1e36;\n }\n\n /**\n * @notice Remove all ETH and OETH from the Curve pool, burn the OETH,\n * convert the ETH to WETH and transfer to the Vault contract.\n */\n function withdrawAll() external override onlyVaultOrGovernor nonReentrant {\n uint256 gaugeTokens = gauge.balanceOf(address(this));\n // Can not withdraw zero LP tokens from the gauge\n if (gaugeTokens == 0) return;\n _lpWithdraw(gaugeTokens);\n\n // Withdraws are proportional to assets held by 3Pool\n uint256[] memory minWithdrawAmounts = new uint256[](2);\n\n // Remove liquidity\n // slither-disable-next-line unused-return\n curvePool.remove_liquidity(\n lpToken.balanceOf(address(this)),\n minWithdrawAmounts\n );\n\n // Burn all OETH\n uint256 oethToBurn = oeth.balanceOf(address(this));\n IVault(vaultAddress).burnForStrategy(oethToBurn);\n\n // Get the strategy contract's WETH balance.\n // This includes all that was removed from the Curve pool and\n // any ether that was sitting in the strategy contract before the removal.\n uint256 ethBalance = weth.balanceOf(address(this));\n require(\n weth.transfer(vaultAddress, ethBalance),\n \"Transfer of WETH not successful\"\n );\n\n emit Withdrawal(address(weth), address(lpToken), ethBalance);\n emit Withdrawal(address(oeth), address(lpToken), oethToBurn);\n }\n\n /***************************************\n Curve pool Rebalancing\n ****************************************/\n\n /**\n * @notice Mint OTokens and one-sided add to the Curve pool.\n * This is used when the Curve pool does not have enough OTokens and too many ETH.\n * The OToken/Asset, eg OETH/ETH, price with increase.\n * The amount of assets in the vault is unchanged.\n * The total supply of OTokens is increased.\n * The asset value of the strategy and vault is increased.\n * @param _oTokens The amount of OTokens to be minted and added to the pool.\n */\n function mintAndAddOTokens(uint256 _oTokens)\n external\n onlyStrategist\n nonReentrant\n improvePoolBalance\n {\n IVault(vaultAddress).mintForStrategy(_oTokens);\n\n uint256[] memory amounts = new uint256[](2);\n amounts[oethCoinIndex] = _oTokens;\n\n // Convert OETH to Curve pool LP tokens\n uint256 valueInLpTokens = (_oTokens).divPrecisely(\n curvePool.get_virtual_price()\n );\n // Apply slippage to LP tokens\n uint256 minMintAmount = valueInLpTokens.mulTruncate(\n uint256(1e18) - maxSlippage\n );\n\n // Add the minted OTokens to the Curve pool\n uint256 lpDeposited = curvePool.add_liquidity(amounts, minMintAmount);\n require(lpDeposited >= minMintAmount, \"Min LP amount error\");\n\n // Deposit the Curve pool LP tokens to the Curve gauge\n gauge.deposit(lpDeposited);\n\n // Ensure solvency of the vault\n _solvencyAssert();\n\n emit Deposit(address(oeth), address(lpToken), _oTokens);\n }\n\n /**\n * @notice One-sided remove of OTokens from the Curve pool which are then burned.\n * This is used when the Curve pool has too many OTokens and not enough ETH.\n * The amount of assets in the vault is unchanged.\n * The total supply of OTokens is reduced.\n * The asset value of the strategy and vault is reduced.\n * @param _lpTokens The amount of Curve pool LP tokens to be burned for OTokens.\n */\n function removeAndBurnOTokens(uint256 _lpTokens)\n external\n onlyStrategist\n nonReentrant\n improvePoolBalance\n {\n // Withdraw Curve pool LP tokens from Convex and remove OTokens from the Curve pool\n uint256 oethToBurn = _withdrawAndRemoveFromPool(\n _lpTokens,\n oethCoinIndex\n );\n\n // The vault burns the OTokens from this strategy\n IVault(vaultAddress).burnForStrategy(oethToBurn);\n\n // Ensure solvency of the vault\n _solvencyAssert();\n\n emit Withdrawal(address(oeth), address(lpToken), oethToBurn);\n }\n\n /**\n * @notice One-sided remove of ETH from the Curve pool, convert to WETH\n * and transfer to the vault.\n * This is used when the Curve pool does not have enough OTokens and too many ETH.\n * The OToken/Asset, eg OETH/ETH, price with decrease.\n * The amount of assets in the vault increases.\n * The total supply of OTokens does not change.\n * The asset value of the strategy reduces.\n * The asset value of the vault should be close to the same.\n * @param _lpTokens The amount of Curve pool LP tokens to be burned for ETH.\n * @dev Curve pool LP tokens is used rather than WETH assets as Curve does not\n * have a way to accurately calculate the amount of LP tokens for a required\n * amount of ETH. Curve's `calc_token_amount` functioun does not include fees.\n * A 3rd party libary can be used that takes into account the fees, but this\n * is a gas intensive process. It's easier for the trusted strategist to\n * caclulate the amount of Curve pool LP tokens required off-chain.\n */\n function removeOnlyAssets(uint256 _lpTokens)\n external\n onlyStrategist\n nonReentrant\n improvePoolBalance\n {\n // Withdraw Curve pool LP tokens from Curve gauge and remove ETH from the Curve pool\n uint256 ethAmount = _withdrawAndRemoveFromPool(\n _lpTokens,\n wethCoinIndex\n );\n\n // Transfer WETH to the vault\n require(\n weth.transfer(vaultAddress, ethAmount),\n \"Transfer of WETH not successful\"\n );\n\n // Ensure solvency of the vault\n _solvencyAssert();\n\n emit Withdrawal(address(weth), address(lpToken), ethAmount);\n }\n\n /**\n * @dev Remove Curve pool LP tokens from the Convex pool and\n * do a one-sided remove of ETH or OETH from the Curve pool.\n * @param _lpTokens The amount of Curve pool LP tokens to be removed from the Convex pool.\n * @param coinIndex The index of the coin to be removed from the Curve pool. 0 = ETH, 1 = OETH.\n * @return coinsRemoved The amount of ETH or OETH removed from the Curve pool.\n */\n function _withdrawAndRemoveFromPool(uint256 _lpTokens, uint128 coinIndex)\n internal\n returns (uint256 coinsRemoved)\n {\n // Withdraw Curve pool LP tokens from Curve gauge\n _lpWithdraw(_lpTokens);\n\n // Convert Curve pool LP tokens to ETH value\n uint256 valueInEth = _lpTokens.mulTruncate(\n curvePool.get_virtual_price()\n );\n // Apply slippage to ETH value\n uint256 minAmount = valueInEth.mulTruncate(uint256(1e18) - maxSlippage);\n\n // Remove just the ETH from the Curve pool\n coinsRemoved = curvePool.remove_liquidity_one_coin(\n _lpTokens,\n int128(coinIndex),\n minAmount,\n address(this)\n );\n }\n\n /**\n * Checks that the protocol is solvent, protecting from a rogue Strategist / Guardian that can\n * keep rebalancing the pool in both directions making the protocol lose a tiny amount of\n * funds each time.\n *\n * Protocol must be at least SOLVENCY_THRESHOLD (99,8 %) backed in order for the rebalances to\n * function.\n */\n function _solvencyAssert() internal view {\n uint256 _totalVaultValue = IVault(vaultAddress).totalValue();\n uint256 _totalOethbSupply = oeth.totalSupply();\n\n if (\n _totalVaultValue.divPrecisely(_totalOethbSupply) <\n SOLVENCY_THRESHOLD\n ) {\n revert(\"Protocol insolvent\");\n }\n }\n\n /***************************************\n Assets and Rewards\n ****************************************/\n\n /**\n * @notice Collect accumulated CRV (and other) rewards and send to the Harvester.\n */\n function collectRewardTokens()\n external\n override\n onlyHarvester\n nonReentrant\n {\n // CRV rewards flow.\n //---\n // CRV inflation:\n // Gauge receive CRV rewards from inflation.\n // Each checkpoint on the gauge send this CRV inflation to gauge factory.\n // This strategy should call mint on the gauge factory to collect the CRV rewards.\n // ---\n // Extra rewards:\n // Calling claim_rewards on the gauge will only claim extra rewards (outside of CRV).\n // ---\n\n // Mint CRV on Child Liquidity gauge factory\n gaugeFactory.mint(address(gauge));\n // Collect extra gauge rewards (outside of CRV)\n gauge.claim_rewards();\n\n _collectRewardTokens();\n }\n\n function _lpWithdraw(uint256 _lpAmount) internal {\n // withdraw lp tokens from the gauge without claiming rewards\n gauge.withdraw(_lpAmount);\n }\n\n /**\n * @notice Get the total asset value held in the platform\n * @param _asset Address of the asset\n * @return balance Total value of the asset in the platform\n */\n function checkBalance(address _asset)\n external\n view\n override\n returns (uint256 balance)\n {\n require(_asset == address(weth), \"Unsupported asset\");\n\n // WETH balance needed here for the balance check that happens from vault during depositing.\n balance = weth.balanceOf(address(this));\n uint256 lpTokens = gauge.balanceOf(address(this));\n if (lpTokens > 0) {\n balance += (lpTokens * curvePool.get_virtual_price()) / 1e18;\n }\n }\n\n /**\n * @notice Returns bool indicating whether asset is supported by strategy\n * @param _asset Address of the asset\n */\n function supportsAsset(address _asset) public view override returns (bool) {\n return _asset == address(weth);\n }\n\n /***************************************\n Approvals\n ****************************************/\n\n /**\n * @notice Sets the maximum slippage allowed for any swap/liquidity operation\n * @param _maxSlippage Maximum slippage allowed, 1e18 = 100%.\n */\n function setMaxSlippage(uint256 _maxSlippage) external onlyGovernor {\n _setMaxSlippage(_maxSlippage);\n }\n\n function _setMaxSlippage(uint256 _maxSlippage) internal {\n require(_maxSlippage <= 5e16, \"Slippage must be less than 100%\");\n maxSlippage = _maxSlippage;\n emit MaxSlippageUpdated(_maxSlippage);\n }\n\n /**\n * @notice Approve the spending of all assets by their corresponding pool tokens,\n * if for some reason is it necessary.\n */\n function safeApproveAllTokens()\n external\n override\n onlyGovernor\n nonReentrant\n {\n _approveBase();\n }\n\n /**\n * @dev Since we are unwrapping WETH before depositing it to Curve\n * there is no need to set an approval for WETH on the Curve\n * pool\n * @param _asset Address of the asset\n * @param _pToken Address of the Curve LP token\n */\n // solhint-disable-next-line no-unused-vars\n function _abstractSetPToken(address _asset, address _pToken)\n internal\n override\n {}\n\n function _approveBase() internal {\n // Approve Curve pool for OETH (required for adding liquidity)\n // slither-disable-next-line unused-return\n oeth.approve(platformAddress, type(uint256).max);\n\n // Approve Curve pool for WETH (required for adding liquidity)\n // slither-disable-next-line unused-return\n weth.approve(platformAddress, type(uint256).max);\n\n // Approve Curve gauge contract to transfer Curve pool LP tokens\n // This is needed for deposits if Curve pool LP tokens into the Curve gauge.\n // slither-disable-next-line unused-return\n lpToken.approve(address(gauge), type(uint256).max);\n }\n\n /**\n * @dev Returns the largest of two numbers int256 version\n */\n function _max(int256 a, int256 b) internal pure returns (int256) {\n return a >= b ? a : b;\n }\n}\n" + }, + "contracts/strategies/BridgedWOETHStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { IERC20, SafeERC20, InitializableAbstractStrategy } from \"../utils/InitializableAbstractStrategy.sol\";\nimport { IWETH9 } from \"../interfaces/IWETH9.sol\";\nimport { IVault } from \"../interfaces/IVault.sol\";\nimport { AggregatorV3Interface } from \"../interfaces/chainlink/AggregatorV3Interface.sol\";\nimport { StableMath } from \"../utils/StableMath.sol\";\nimport { SafeCast } from \"@openzeppelin/contracts/utils/math/SafeCast.sol\";\nimport { IOracle } from \"../interfaces/IOracle.sol\";\n\ncontract BridgedWOETHStrategy is InitializableAbstractStrategy {\n using StableMath for uint256;\n using StableMath for uint128;\n using SafeCast for uint256;\n using SafeERC20 for IERC20;\n\n event MaxPriceDiffBpsUpdated(uint128 oldValue, uint128 newValue);\n event WOETHPriceUpdated(uint128 oldValue, uint128 newValue);\n\n IWETH9 public immutable weth;\n IERC20 public immutable bridgedWOETH;\n IERC20 public immutable oethb;\n IOracle public immutable oracle;\n\n uint128 public lastOraclePrice;\n uint128 public maxPriceDiffBps;\n\n constructor(\n BaseStrategyConfig memory _stratConfig,\n address _weth,\n address _bridgedWOETH,\n address _oethb,\n address _oracle\n ) InitializableAbstractStrategy(_stratConfig) {\n weth = IWETH9(_weth);\n bridgedWOETH = IERC20(_bridgedWOETH);\n oethb = IERC20(_oethb);\n oracle = IOracle(_oracle);\n }\n\n function initialize(uint128 _maxPriceDiffBps)\n external\n onlyGovernor\n initializer\n {\n InitializableAbstractStrategy._initialize(\n new address[](0), // No reward tokens\n new address[](0), // No assets\n new address[](0) // No pTokens\n );\n\n _setMaxPriceDiffBps(_maxPriceDiffBps);\n }\n\n /**\n * @dev Sets the max price diff bps for the wOETH value appreciation\n * @param _maxPriceDiffBps Bps value, 10k == 100%\n */\n function setMaxPriceDiffBps(uint128 _maxPriceDiffBps)\n external\n onlyGovernor\n {\n _setMaxPriceDiffBps(_maxPriceDiffBps);\n }\n\n /**\n * @dev Sets the max price diff bps for the wOETH value appreciation\n * @param _maxPriceDiffBps Bps value, 10k == 100%\n */\n function _setMaxPriceDiffBps(uint128 _maxPriceDiffBps) internal {\n require(\n _maxPriceDiffBps > 0 && _maxPriceDiffBps <= 10000,\n \"Invalid bps value\"\n );\n\n emit MaxPriceDiffBpsUpdated(maxPriceDiffBps, _maxPriceDiffBps);\n\n maxPriceDiffBps = _maxPriceDiffBps;\n }\n\n /**\n * @dev Wrapper for _updateWOETHOraclePrice with nonReentrant flag\n * @return The latest price of wOETH from Oracle\n */\n function updateWOETHOraclePrice() external nonReentrant returns (uint256) {\n return _updateWOETHOraclePrice();\n }\n\n /**\n * @dev Finds the value of bridged wOETH from the Oracle.\n * Ensures that it's within the bounds and reasonable.\n * And stores it.\n *\n * NOTE: Intentionally not caching `Vault.priceProvider` here,\n * since doing so would mean that we also have to update this\n * strategy every time there's a change in oracle router.\n * Besides on L2, the gas is considerably cheaper than mainnet.\n *\n * @return Latest price from oracle\n */\n function _updateWOETHOraclePrice() internal returns (uint256) {\n // WETH price per unit of bridged wOETH\n uint256 oraclePrice = oracle.price(address(bridgedWOETH));\n\n // 1 wOETH > 1 WETH, always\n require(oraclePrice > 1 ether, \"Invalid wOETH value\");\n\n uint128 oraclePrice128 = oraclePrice.toUint128();\n\n // Do some checks\n if (lastOraclePrice > 0) {\n // Make sure the value only goes up\n require(oraclePrice128 >= lastOraclePrice, \"Negative wOETH yield\");\n\n // lastOraclePrice * (1 + maxPriceDiffBps)\n uint256 maxPrice = (lastOraclePrice * (1e4 + maxPriceDiffBps)) /\n 1e4;\n\n // And that it's within the bounds.\n require(oraclePrice128 <= maxPrice, \"Price diff beyond threshold\");\n }\n\n emit WOETHPriceUpdated(lastOraclePrice, oraclePrice128);\n\n // Store the price\n lastOraclePrice = oraclePrice128;\n\n return oraclePrice;\n }\n\n /**\n * @dev Computes & returns the value of given wOETH in WETH\n * @param woethAmount Amount of wOETH\n * @return Value of wOETH in WETH (using the last stored oracle price)\n */\n function getBridgedWOETHValue(uint256 woethAmount)\n public\n view\n returns (uint256)\n {\n return (woethAmount * lastOraclePrice) / 1 ether;\n }\n\n /**\n * @dev Takes in bridged wOETH and mints & returns\n * equivalent amount of OETHb.\n * @param woethAmount Amount of bridged wOETH to transfer in\n */\n function depositBridgedWOETH(uint256 woethAmount)\n external\n onlyGovernorOrStrategist\n nonReentrant\n {\n // Update wOETH price\n uint256 oraclePrice = _updateWOETHOraclePrice();\n\n // Figure out how much they are worth\n uint256 oethToMint = (woethAmount * oraclePrice) / 1 ether;\n\n require(oethToMint > 0, \"Invalid deposit amount\");\n\n // There's no pToken, however, it just uses WOETH address in the event\n emit Deposit(address(weth), address(bridgedWOETH), oethToMint);\n\n // Mint OETHb tokens and transfer it to the caller\n IVault(vaultAddress).mintForStrategy(oethToMint);\n\n // Transfer out minted OETHb\n // slither-disable-next-line unchecked-transfer unused-return\n oethb.transfer(msg.sender, oethToMint);\n\n // Transfer in all bridged wOETH tokens\n // slither-disable-next-line unchecked-transfer unused-return\n bridgedWOETH.transferFrom(msg.sender, address(this), woethAmount);\n }\n\n /**\n * @dev Takes in OETHb and burns it and returns\n * equivalent amount of bridged wOETH.\n * @param oethToBurn Amount of OETHb to burn\n */\n function withdrawBridgedWOETH(uint256 oethToBurn)\n external\n onlyGovernorOrStrategist\n nonReentrant\n {\n // Update wOETH price\n uint256 oraclePrice = _updateWOETHOraclePrice();\n\n // Figure out how much they are worth\n uint256 woethAmount = (oethToBurn * 1 ether) / oraclePrice;\n\n require(woethAmount > 0, \"Invalid withdraw amount\");\n\n // There's no pToken, however, it just uses WOETH address in the event\n emit Withdrawal(address(weth), address(bridgedWOETH), oethToBurn);\n\n // Transfer WOETH back\n // slither-disable-next-line unchecked-transfer unused-return\n bridgedWOETH.transfer(msg.sender, woethAmount);\n\n // Transfer in OETHb\n // slither-disable-next-line unchecked-transfer unused-return\n oethb.transferFrom(msg.sender, address(this), oethToBurn);\n\n // Burn OETHb\n IVault(vaultAddress).burnForStrategy(oethToBurn);\n }\n\n /**\n * @notice Returns the amount of backing WETH the strategy holds\n * @param _asset Address of the asset\n * @return balance Total value of the asset in the platform\n */\n function checkBalance(address _asset)\n external\n view\n override\n returns (uint256 balance)\n {\n require(_asset == address(weth), \"Unsupported asset\");\n\n // Figure out how much wOETH is worth at the time.\n // Always uses the last stored oracle price.\n // Call updateWOETHOraclePrice manually to pull in latest yields.\n\n // NOTE: If the contract has been deployed but the call to\n // `updateWOETHOraclePrice()` has never been made, then this\n // will return zero. It should be fine because the strategy\n // should update the price whenever a deposit/withdraw happens.\n\n // If `updateWOETHOraclePrice()` hasn't been called in a while,\n // the strategy will underreport its holdings but never overreport it.\n\n balance =\n (bridgedWOETH.balanceOf(address(this)) * lastOraclePrice) /\n 1 ether;\n }\n\n /**\n * @notice Check if an asset is supported.\n * @param _asset Address of the asset\n * @return bool Whether asset is supported\n */\n function supportsAsset(address _asset) public view override returns (bool) {\n // Strategist deposits bridged wOETH but the contract only\n // reports the balance in WETH. As far as Vault is concerned,\n // it isn't aware of bridged wOETH token\n return _asset == address(weth);\n }\n\n /***************************************\n Overridden methods\n ****************************************/\n /**\n * @inheritdoc InitializableAbstractStrategy\n */\n function transferToken(address _asset, uint256 _amount)\n public\n override\n onlyGovernor\n {\n require(\n _asset != address(bridgedWOETH) && _asset != address(weth),\n \"Cannot transfer supported asset\"\n );\n // Use SafeERC20 only for rescuing unknown assets; core tokens are standard.\n IERC20(_asset).safeTransfer(governor(), _amount);\n }\n\n /**\n * @notice deposit() function not used for this strategy\n */\n function deposit(address, uint256)\n external\n override\n onlyVault\n nonReentrant\n {\n // Use depositBridgedWOETH() instead\n require(false, \"Deposit disabled\");\n }\n\n /**\n * @notice depositAll() function not used for this strategy\n */\n function depositAll() external override onlyVault nonReentrant {\n // Use depositBridgedWOETH() instead\n require(false, \"Deposit disabled\");\n }\n\n /**\n * @notice withdraw() function not used for this strategy\n */\n function withdraw(\n // solhint-disable-next-line no-unused-vars\n address _recipient,\n // solhint-disable-next-line no-unused-vars\n address _asset,\n // solhint-disable-next-line no-unused-vars\n uint256 _amount\n ) external override onlyVault nonReentrant {\n require(false, \"Withdrawal disabled\");\n }\n\n /**\n * @notice withdrawAll() function not used for this strategy\n */\n function withdrawAll() external override onlyVaultOrGovernor nonReentrant {\n // Withdrawal disabled\n }\n\n function _abstractSetPToken(address, address) internal override {\n revert(\"No pTokens are used\");\n }\n\n function safeApproveAllTokens() external override {}\n\n /**\n * @inheritdoc InitializableAbstractStrategy\n */\n function removePToken(uint256) external override {\n revert(\"No pTokens are used\");\n }\n\n /**\n * @inheritdoc InitializableAbstractStrategy\n */\n function collectRewardTokens() external override {}\n}\n" + }, + "contracts/strategies/CompoundStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title Compound Strategy\n * @notice Investment strategy for Compound like lending platforms. eg Compound and Flux\n * @author Origin Protocol Inc\n */\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\n\nimport { ICERC20 } from \"./ICompound.sol\";\nimport { AbstractCompoundStrategy, InitializableAbstractStrategy } from \"./AbstractCompoundStrategy.sol\";\nimport { IComptroller } from \"../interfaces/IComptroller.sol\";\nimport { IERC20 } from \"../utils/InitializableAbstractStrategy.sol\";\n\ncontract CompoundStrategy is AbstractCompoundStrategy {\n using SafeERC20 for IERC20;\n event SkippedWithdrawal(address asset, uint256 amount);\n\n constructor(BaseStrategyConfig memory _stratConfig)\n InitializableAbstractStrategy(_stratConfig)\n {}\n\n /**\n * @notice initialize function, to set up initial internal state\n * @param _rewardTokenAddresses Address of reward token for platform\n * @param _assets Addresses of initial supported assets\n * @param _pTokens Platform Token corresponding addresses\n */\n function initialize(\n address[] memory _rewardTokenAddresses,\n address[] memory _assets,\n address[] memory _pTokens\n ) external onlyGovernor initializer {\n InitializableAbstractStrategy._initialize(\n _rewardTokenAddresses,\n _assets,\n _pTokens\n );\n }\n\n /**\n * @notice Collect accumulated COMP and send to Harvester.\n */\n function collectRewardTokens()\n external\n virtual\n override\n onlyHarvester\n nonReentrant\n {\n // Claim COMP from Comptroller\n ICERC20 cToken = _getCTokenFor(assetsMapped[0]);\n IComptroller comptroller = IComptroller(cToken.comptroller());\n // Only collect from active cTokens, saves gas\n address[] memory ctokensToCollect = new address[](assetsMapped.length);\n uint256 assetCount = assetsMapped.length;\n for (uint256 i = 0; i < assetCount; ++i) {\n ctokensToCollect[i] = address(_getCTokenFor(assetsMapped[i]));\n }\n // Claim only for this strategy\n address[] memory claimers = new address[](1);\n claimers[0] = address(this);\n // Claim COMP from Comptroller. Only collect for supply, saves gas\n comptroller.claimComp(claimers, ctokensToCollect, false, true);\n // Transfer COMP to Harvester\n IERC20 rewardToken = IERC20(rewardTokenAddresses[0]);\n uint256 balance = rewardToken.balanceOf(address(this));\n emit RewardTokenCollected(\n harvesterAddress,\n rewardTokenAddresses[0],\n balance\n );\n rewardToken.safeTransfer(harvesterAddress, balance);\n }\n\n /**\n * @notice Deposit asset into the underlying platform\n * @param _asset Address of asset to deposit\n * @param _amount Amount of assets to deposit\n */\n function deposit(address _asset, uint256 _amount)\n external\n override\n onlyVault\n nonReentrant\n {\n _deposit(_asset, _amount);\n }\n\n /**\n * @dev Deposit an asset into the underlying platform\n * @param _asset Address of the asset to deposit\n * @param _amount Amount of assets to deposit\n */\n function _deposit(address _asset, uint256 _amount) internal {\n require(_amount > 0, \"Must deposit something\");\n ICERC20 cToken = _getCTokenFor(_asset);\n emit Deposit(_asset, address(cToken), _amount);\n require(cToken.mint(_amount) == 0, \"cToken mint failed\");\n }\n\n /**\n * @notice Deposit the entire balance of any supported asset in the strategy into the underlying platform\n */\n function depositAll() external override onlyVault nonReentrant {\n uint256 assetCount = assetsMapped.length;\n for (uint256 i = 0; i < assetCount; ++i) {\n IERC20 asset = IERC20(assetsMapped[i]);\n uint256 assetBalance = asset.balanceOf(address(this));\n if (assetBalance > 0) {\n _deposit(address(asset), assetBalance);\n }\n }\n }\n\n /**\n * @notice Withdraw an asset from the underlying platform\n * @param _recipient Address to receive withdrawn assets\n * @param _asset Address of the asset to withdraw\n * @param _amount Amount of assets to withdraw\n */\n function withdraw(\n address _recipient,\n address _asset,\n uint256 _amount\n ) external override onlyVault nonReentrant {\n require(_amount > 0, \"Must withdraw something\");\n require(_recipient != address(0), \"Must specify recipient\");\n\n ICERC20 cToken = _getCTokenFor(_asset);\n // If redeeming 0 cTokens, just skip, else COMP will revert\n uint256 cTokensToRedeem = _convertUnderlyingToCToken(cToken, _amount);\n if (cTokensToRedeem == 0) {\n emit SkippedWithdrawal(_asset, _amount);\n return;\n }\n\n emit Withdrawal(_asset, address(cToken), _amount);\n require(cToken.redeemUnderlying(_amount) == 0, \"Redeem failed\");\n IERC20(_asset).safeTransfer(_recipient, _amount);\n }\n\n /**\n * @dev Internal method to respond to the addition of new asset / cTokens\n * We need to approve the cToken and give it permission to spend the asset\n * @param _asset Address of the asset to approve. eg DAI\n * @param _pToken The pToken for the approval. eg cDAI or fDAI\n */\n function _abstractSetPToken(address _asset, address _pToken)\n internal\n override\n {\n // Safe approval\n IERC20(_asset).safeApprove(_pToken, 0);\n IERC20(_asset).safeApprove(_pToken, type(uint256).max);\n }\n\n /**\n * @notice Remove all supported assets from the underlying platform and send them to Vault contract.\n */\n function withdrawAll() external override onlyVaultOrGovernor nonReentrant {\n uint256 assetCount = assetsMapped.length;\n for (uint256 i = 0; i < assetCount; ++i) {\n IERC20 asset = IERC20(assetsMapped[i]);\n // Redeem entire balance of cToken\n ICERC20 cToken = _getCTokenFor(address(asset));\n uint256 cTokenBalance = cToken.balanceOf(address(this));\n if (cTokenBalance > 0) {\n require(cToken.redeem(cTokenBalance) == 0, \"Redeem failed\");\n uint256 assetBalance = asset.balanceOf(address(this));\n // Transfer entire balance to Vault\n asset.safeTransfer(vaultAddress, assetBalance);\n\n emit Withdrawal(address(asset), address(cToken), assetBalance);\n }\n }\n }\n\n /**\n * @notice Get the total asset value held in the underlying platform\n * This includes any interest that was generated since depositing.\n * The exchange rate between the cToken and asset gradually increases,\n * causing the cToken to be worth more corresponding asset.\n * @param _asset Address of the asset\n * @return balance Total value of the asset in the platform\n */\n function checkBalance(address _asset)\n external\n view\n override\n returns (uint256 balance)\n {\n // Balance is always with token cToken decimals\n ICERC20 cToken = _getCTokenFor(_asset);\n balance = _checkBalance(cToken);\n }\n\n /**\n * @dev Get the total asset value held in the platform\n * underlying = (cTokenAmt * exchangeRate) / 1e18\n * @param _cToken cToken for which to check balance\n * @return balance Total value of the asset in the platform\n */\n function _checkBalance(ICERC20 _cToken)\n internal\n view\n returns (uint256 balance)\n {\n // e.g. 50e8*205316390724364402565641705 / 1e18 = 1.0265..e18\n balance =\n (_cToken.balanceOf(address(this)) * _cToken.exchangeRateStored()) /\n 1e18;\n }\n\n /**\n * @notice Approve the spending of all assets by their corresponding cToken,\n * if for some reason is it necessary.\n */\n function safeApproveAllTokens() external override {\n uint256 assetCount = assetsMapped.length;\n for (uint256 i = 0; i < assetCount; ++i) {\n IERC20 asset = IERC20(assetsMapped[i]);\n address cToken = assetToPToken[address(asset)];\n // Safe approval\n asset.safeApprove(cToken, 0);\n asset.safeApprove(cToken, type(uint256).max);\n }\n }\n}\n" + }, + "contracts/strategies/ConvexEthMetaStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title Convex Automated Market Maker (AMO) Strategy\n * @notice AMO strategy for the Curve OETH/ETH pool\n * @author Origin Protocol Inc\n */\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport \"@openzeppelin/contracts/utils/math/Math.sol\";\n\nimport { ICurveETHPoolV1 } from \"./ICurveETHPoolV1.sol\";\nimport { IERC20, InitializableAbstractStrategy } from \"../utils/InitializableAbstractStrategy.sol\";\nimport { StableMath } from \"../utils/StableMath.sol\";\nimport { IVault } from \"../interfaces/IVault.sol\";\nimport { IWETH9 } from \"../interfaces/IWETH9.sol\";\nimport { IConvexDeposits } from \"./IConvexDeposits.sol\";\nimport { IRewardStaking } from \"./IRewardStaking.sol\";\n\ncontract ConvexEthMetaStrategy is InitializableAbstractStrategy {\n using StableMath for uint256;\n using SafeERC20 for IERC20;\n\n uint256 public constant MAX_SLIPPAGE = 1e16; // 1%, same as the Curve UI\n address public constant ETH_ADDRESS =\n 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;\n\n // The following slots have been deprecated with immutable variables\n // slither-disable-next-line constable-states\n address private _deprecated_cvxDepositorAddress;\n // slither-disable-next-line constable-states\n address private _deprecated_cvxRewardStaker;\n // slither-disable-next-line constable-states\n uint256 private _deprecated_cvxDepositorPTokenId;\n // slither-disable-next-line constable-states\n address private _deprecated_curvePool;\n // slither-disable-next-line constable-states\n address private _deprecated_lpToken;\n // slither-disable-next-line constable-states\n address private _deprecated_oeth;\n // slither-disable-next-line constable-states\n address private _deprecated_weth;\n\n // Ordered list of pool assets\n // slither-disable-next-line constable-states\n uint128 private _deprecated_oethCoinIndex;\n // slither-disable-next-line constable-states\n uint128 private _deprecated_ethCoinIndex;\n\n // New immutable variables that must be set in the constructor\n address public immutable cvxDepositorAddress;\n IRewardStaking public immutable cvxRewardStaker;\n uint256 public immutable cvxDepositorPTokenId;\n ICurveETHPoolV1 public immutable curvePool;\n IERC20 public immutable lpToken;\n IERC20 public immutable oeth;\n IWETH9 public immutable weth;\n\n // Ordered list of pool assets\n uint128 public constant oethCoinIndex = 1;\n uint128 public constant ethCoinIndex = 0;\n\n /**\n * @dev Verifies that the caller is the Strategist.\n */\n modifier onlyStrategist() {\n require(\n msg.sender == IVault(vaultAddress).strategistAddr(),\n \"Caller is not the Strategist\"\n );\n _;\n }\n\n /**\n * @dev Checks the Curve pool's balances have improved and the balances\n * have not tipped to the other side.\n * This modifier only works on functions that do a single sided add or remove.\n * The standard deposit function adds to both sides of the pool in a way that\n * the pool's balance is not worsened.\n * Withdrawals are proportional so doesn't change the pools asset balance.\n */\n modifier improvePoolBalance() {\n // Get the asset and OToken balances in the Curve pool\n uint256[2] memory balancesBefore = curvePool.get_balances();\n // diff = ETH balance - OETH balance\n int256 diffBefore = int256(balancesBefore[ethCoinIndex]) -\n int256(balancesBefore[oethCoinIndex]);\n\n _;\n\n // Get the asset and OToken balances in the Curve pool\n uint256[2] memory balancesAfter = curvePool.get_balances();\n // diff = ETH balance - OETH balance\n int256 diffAfter = int256(balancesAfter[ethCoinIndex]) -\n int256(balancesAfter[oethCoinIndex]);\n\n if (diffBefore <= 0) {\n // If the pool was originally imbalanced in favor of OETH, then\n // we want to check that the pool is now more balanced\n require(diffAfter <= 0, \"OTokens overshot peg\");\n require(diffBefore < diffAfter, \"OTokens balance worse\");\n }\n if (diffBefore >= 0) {\n // If the pool was originally imbalanced in favor of ETH, then\n // we want to check that the pool is now more balanced\n require(diffAfter >= 0, \"Assets overshot peg\");\n require(diffAfter < diffBefore, \"Assets balance worse\");\n }\n }\n\n // Used to circumvent the stack too deep issue\n struct ConvexEthMetaConfig {\n address cvxDepositorAddress; //Address of the Convex depositor(AKA booster) for this pool\n address cvxRewardStakerAddress; //Address of the CVX rewards staker\n uint256 cvxDepositorPTokenId; //Pid of the pool referred to by Depositor and staker\n address oethAddress; //Address of OETH token\n address wethAddress; //Address of WETH\n }\n\n constructor(\n BaseStrategyConfig memory _baseConfig,\n ConvexEthMetaConfig memory _convexConfig\n ) InitializableAbstractStrategy(_baseConfig) {\n lpToken = IERC20(_baseConfig.platformAddress);\n curvePool = ICurveETHPoolV1(_baseConfig.platformAddress);\n\n cvxDepositorAddress = _convexConfig.cvxDepositorAddress;\n cvxRewardStaker = IRewardStaking(_convexConfig.cvxRewardStakerAddress);\n cvxDepositorPTokenId = _convexConfig.cvxDepositorPTokenId;\n oeth = IERC20(_convexConfig.oethAddress);\n weth = IWETH9(_convexConfig.wethAddress);\n }\n\n /**\n * Initializer for setting up strategy internal state. This overrides the\n * InitializableAbstractStrategy initializer as Curve strategies don't fit\n * well within that abstraction.\n * @param _rewardTokenAddresses Address of CRV & CVX\n * @param _assets Addresses of supported assets. eg WETH\n */\n function initialize(\n address[] calldata _rewardTokenAddresses, // CRV + CVX\n address[] calldata _assets // WETH\n ) external onlyGovernor initializer {\n require(_assets.length == 1, \"Must have exactly one asset\");\n require(_assets[0] == address(weth), \"Asset not WETH\");\n\n address[] memory pTokens = new address[](1);\n pTokens[0] = address(curvePool);\n\n InitializableAbstractStrategy._initialize(\n _rewardTokenAddresses,\n _assets,\n pTokens\n );\n\n _approveBase();\n }\n\n /***************************************\n Deposit\n ****************************************/\n\n /**\n * @notice Deposit WETH into the Curve pool\n * @param _weth Address of Wrapped ETH (WETH) contract.\n * @param _amount Amount of WETH to deposit.\n */\n function deposit(address _weth, uint256 _amount)\n external\n override\n onlyVault\n nonReentrant\n {\n _deposit(_weth, _amount);\n }\n\n function _deposit(address _weth, uint256 _wethAmount) internal {\n require(_wethAmount > 0, \"Must deposit something\");\n require(_weth == address(weth), \"Can only deposit WETH\");\n weth.withdraw(_wethAmount);\n\n emit Deposit(_weth, address(lpToken), _wethAmount);\n\n // Get the asset and OToken balances in the Curve pool\n uint256[2] memory balances = curvePool.get_balances();\n // safe to cast since min value is at least 0\n uint256 oethToAdd = uint256(\n _max(\n 0,\n int256(balances[ethCoinIndex]) +\n int256(_wethAmount) -\n int256(balances[oethCoinIndex])\n )\n );\n\n /* Add so much OETH so that the pool ends up being balanced. And at minimum\n * add as much OETH as WETH and at maximum twice as much OETH.\n */\n oethToAdd = Math.max(oethToAdd, _wethAmount);\n oethToAdd = Math.min(oethToAdd, _wethAmount * 2);\n\n /* Mint OETH with a strategy that attempts to contribute to stability of OETH/WETH pool. Try\n * to mint so much OETH that after deployment of liquidity pool ends up being balanced.\n *\n * To manage unpredictability minimal OETH minted will always be at least equal or greater\n * to WETH amount deployed. And never larger than twice the WETH amount deployed even if\n * it would have a further beneficial effect on pool stability.\n */\n IVault(vaultAddress).mintForStrategy(oethToAdd);\n\n emit Deposit(address(oeth), address(lpToken), oethToAdd);\n\n uint256[2] memory _amounts;\n _amounts[ethCoinIndex] = _wethAmount;\n _amounts[oethCoinIndex] = oethToAdd;\n\n uint256 valueInLpTokens = (_wethAmount + oethToAdd).divPrecisely(\n curvePool.get_virtual_price()\n );\n uint256 minMintAmount = valueInLpTokens.mulTruncate(\n uint256(1e18) - MAX_SLIPPAGE\n );\n\n // Do the deposit to the Curve pool\n // slither-disable-next-line arbitrary-send\n uint256 lpDeposited = curvePool.add_liquidity{ value: _wethAmount }(\n _amounts,\n minMintAmount\n );\n\n // Deposit the Curve pool's LP tokens into the Convex rewards pool\n require(\n IConvexDeposits(cvxDepositorAddress).deposit(\n cvxDepositorPTokenId,\n lpDeposited,\n true // Deposit with staking\n ),\n \"Depositing LP to Convex not successful\"\n );\n }\n\n /**\n * @notice Deposit the strategy's entire balance of WETH into the Curve pool\n */\n function depositAll() external override onlyVault nonReentrant {\n uint256 balance = weth.balanceOf(address(this));\n if (balance > 0) {\n _deposit(address(weth), balance);\n }\n }\n\n /***************************************\n Withdraw\n ****************************************/\n\n /**\n * @notice Withdraw ETH and OETH from the Curve pool, burn the OETH,\n * convert the ETH to WETH and transfer to the recipient.\n * @param _recipient Address to receive withdrawn asset which is normally the Vault.\n * @param _weth Address of the Wrapped ETH (WETH) contract.\n * @param _amount Amount of WETH to withdraw.\n */\n function withdraw(\n address _recipient,\n address _weth,\n uint256 _amount\n ) external override onlyVault nonReentrant {\n require(_amount > 0, \"Invalid amount\");\n require(_weth == address(weth), \"Can only withdraw WETH\");\n\n emit Withdrawal(_weth, address(lpToken), _amount);\n\n uint256 requiredLpTokens = calcTokenToBurn(_amount);\n\n _lpWithdraw(requiredLpTokens);\n\n /* math in requiredLpTokens should correctly calculate the amount of LP to remove\n * in that the strategy receives enough WETH on balanced removal\n */\n uint256[2] memory _minWithdrawalAmounts = [uint256(0), uint256(0)];\n _minWithdrawalAmounts[ethCoinIndex] = _amount;\n // slither-disable-next-line unused-return\n curvePool.remove_liquidity(requiredLpTokens, _minWithdrawalAmounts);\n\n // Burn all the removed OETH and any that was left in the strategy\n uint256 oethToBurn = oeth.balanceOf(address(this));\n IVault(vaultAddress).burnForStrategy(oethToBurn);\n\n emit Withdrawal(address(oeth), address(lpToken), oethToBurn);\n\n // Transfer WETH to the recipient\n weth.deposit{ value: _amount }();\n require(\n weth.transfer(_recipient, _amount),\n \"Transfer of WETH not successful\"\n );\n }\n\n function calcTokenToBurn(uint256 _wethAmount)\n internal\n view\n returns (uint256 lpToBurn)\n {\n /* The rate between coins in the pool determines the rate at which pool returns\n * tokens when doing balanced removal (remove_liquidity call). And by knowing how much WETH\n * we want we can determine how much of OETH we receive by removing liquidity.\n *\n * Because we are doing balanced removal we should be making profit when removing liquidity in a\n * pool tilted to either side.\n *\n * Important: A downside is that the Strategist / Governor needs to be\n * cognisant of not removing too much liquidity. And while the proposal to remove liquidity\n * is being voted on the pool tilt might change so much that the proposal that has been valid while\n * created is no longer valid.\n */\n\n uint256 poolWETHBalance = curvePool.balances(ethCoinIndex);\n /* K is multiplied by 1e36 which is used for higher precision calculation of required\n * pool LP tokens. Without it the end value can have rounding errors up to precision of\n * 10 digits. This way we move the decimal point by 36 places when doing the calculation\n * and again by 36 places when we are done with it.\n */\n uint256 k = (1e36 * lpToken.totalSupply()) / poolWETHBalance;\n // prettier-ignore\n // slither-disable-next-line divide-before-multiply\n uint256 diff = (_wethAmount + 1) * k;\n lpToBurn = diff / 1e36;\n }\n\n /**\n * @notice Remove all ETH and OETH from the Curve pool, burn the OETH,\n * convert the ETH to WETH and transfer to the Vault contract.\n */\n function withdrawAll() external override onlyVaultOrGovernor nonReentrant {\n uint256 gaugeTokens = cvxRewardStaker.balanceOf(address(this));\n _lpWithdraw(gaugeTokens);\n\n // Withdraws are proportional to assets held by 3Pool\n uint256[2] memory minWithdrawAmounts = [uint256(0), uint256(0)];\n\n // Remove liquidity\n // slither-disable-next-line unused-return\n curvePool.remove_liquidity(\n lpToken.balanceOf(address(this)),\n minWithdrawAmounts\n );\n\n // Burn all OETH\n uint256 oethToBurn = oeth.balanceOf(address(this));\n IVault(vaultAddress).burnForStrategy(oethToBurn);\n\n // Get the strategy contract's ether balance.\n // This includes all that was removed from the Curve pool and\n // any ether that was sitting in the strategy contract before the removal.\n uint256 ethBalance = address(this).balance;\n // Convert all the strategy contract's ether to WETH and transfer to the vault.\n weth.deposit{ value: ethBalance }();\n require(\n weth.transfer(vaultAddress, ethBalance),\n \"Transfer of WETH not successful\"\n );\n\n emit Withdrawal(address(weth), address(lpToken), ethBalance);\n emit Withdrawal(address(oeth), address(lpToken), oethToBurn);\n }\n\n /***************************************\n Curve pool Rebalancing\n ****************************************/\n\n /**\n * @notice Mint OTokens and one-sided add to the Curve pool.\n * This is used when the Curve pool does not have enough OTokens and too many ETH.\n * The OToken/Asset, eg OETH/ETH, price with increase.\n * The amount of assets in the vault is unchanged.\n * The total supply of OTokens is increased.\n * The asset value of the strategy and vault is increased.\n * @param _oTokens The amount of OTokens to be minted and added to the pool.\n */\n function mintAndAddOTokens(uint256 _oTokens)\n external\n onlyStrategist\n nonReentrant\n improvePoolBalance\n {\n IVault(vaultAddress).mintForStrategy(_oTokens);\n\n uint256[2] memory amounts = [uint256(0), uint256(0)];\n amounts[oethCoinIndex] = _oTokens;\n\n // Convert OETH to Curve pool LP tokens\n uint256 valueInLpTokens = (_oTokens).divPrecisely(\n curvePool.get_virtual_price()\n );\n // Apply slippage to LP tokens\n uint256 minMintAmount = valueInLpTokens.mulTruncate(\n uint256(1e18) - MAX_SLIPPAGE\n );\n\n // Add the minted OTokens to the Curve pool\n uint256 lpDeposited = curvePool.add_liquidity(amounts, minMintAmount);\n\n // Deposit the Curve pool LP tokens to the Convex rewards pool\n require(\n IConvexDeposits(cvxDepositorAddress).deposit(\n cvxDepositorPTokenId,\n lpDeposited,\n true // Deposit with staking\n ),\n \"Failed to Deposit LP to Convex\"\n );\n\n emit Deposit(address(oeth), address(lpToken), _oTokens);\n }\n\n /**\n * @notice One-sided remove of OTokens from the Curve pool which are then burned.\n * This is used when the Curve pool has too many OTokens and not enough ETH.\n * The amount of assets in the vault is unchanged.\n * The total supply of OTokens is reduced.\n * The asset value of the strategy and vault is reduced.\n * @param _lpTokens The amount of Curve pool LP tokens to be burned for OTokens.\n */\n function removeAndBurnOTokens(uint256 _lpTokens)\n external\n onlyStrategist\n nonReentrant\n improvePoolBalance\n {\n // Withdraw Curve pool LP tokens from Convex and remove OTokens from the Curve pool\n uint256 oethToBurn = _withdrawAndRemoveFromPool(\n _lpTokens,\n oethCoinIndex\n );\n\n // The vault burns the OTokens from this strategy\n IVault(vaultAddress).burnForStrategy(oethToBurn);\n\n emit Withdrawal(address(oeth), address(lpToken), oethToBurn);\n }\n\n /**\n * @notice One-sided remove of ETH from the Curve pool, convert to WETH\n * and transfer to the vault.\n * This is used when the Curve pool does not have enough OTokens and too many ETH.\n * The OToken/Asset, eg OETH/ETH, price with decrease.\n * The amount of assets in the vault increases.\n * The total supply of OTokens does not change.\n * The asset value of the strategy reduces.\n * The asset value of the vault should be close to the same.\n * @param _lpTokens The amount of Curve pool LP tokens to be burned for ETH.\n * @dev Curve pool LP tokens is used rather than WETH assets as Curve does not\n * have a way to accurately calculate the amount of LP tokens for a required\n * amount of ETH. Curve's `calc_token_amount` functioun does not include fees.\n * A 3rd party libary can be used that takes into account the fees, but this\n * is a gas intensive process. It's easier for the trusted strategist to\n * caclulate the amount of Curve pool LP tokens required off-chain.\n */\n function removeOnlyAssets(uint256 _lpTokens)\n external\n onlyStrategist\n nonReentrant\n improvePoolBalance\n {\n // Withdraw Curve pool LP tokens from Convex and remove ETH from the Curve pool\n uint256 ethAmount = _withdrawAndRemoveFromPool(_lpTokens, ethCoinIndex);\n\n // Convert ETH to WETH and transfer to the vault\n weth.deposit{ value: ethAmount }();\n require(\n weth.transfer(vaultAddress, ethAmount),\n \"Transfer of WETH not successful\"\n );\n\n emit Withdrawal(address(weth), address(lpToken), ethAmount);\n }\n\n /**\n * @dev Remove Curve pool LP tokens from the Convex pool and\n * do a one-sided remove of ETH or OETH from the Curve pool.\n * @param _lpTokens The amount of Curve pool LP tokens to be removed from the Convex pool.\n * @param coinIndex The index of the coin to be removed from the Curve pool. 0 = ETH, 1 = OETH.\n * @return coinsRemoved The amount of ETH or OETH removed from the Curve pool.\n */\n function _withdrawAndRemoveFromPool(uint256 _lpTokens, uint128 coinIndex)\n internal\n returns (uint256 coinsRemoved)\n {\n // Withdraw Curve pool LP tokens from Convex pool\n _lpWithdraw(_lpTokens);\n\n // Convert Curve pool LP tokens to ETH value\n uint256 valueInEth = _lpTokens.mulTruncate(\n curvePool.get_virtual_price()\n );\n // Apply slippage to ETH value\n uint256 minAmount = valueInEth.mulTruncate(\n uint256(1e18) - MAX_SLIPPAGE\n );\n\n // Remove just the ETH from the Curve pool\n coinsRemoved = curvePool.remove_liquidity_one_coin(\n _lpTokens,\n int128(coinIndex),\n minAmount,\n address(this)\n );\n }\n\n /***************************************\n Assets and Rewards\n ****************************************/\n\n /**\n * @notice Collect accumulated CRV and CVX rewards and send to the Harvester.\n */\n function collectRewardTokens()\n external\n override\n onlyHarvester\n nonReentrant\n {\n // Collect CRV and CVX\n cvxRewardStaker.getReward();\n _collectRewardTokens();\n }\n\n function _lpWithdraw(uint256 _wethAmount) internal {\n // withdraw and unwrap with claim takes back the lpTokens\n // and also collects the rewards for deposit\n cvxRewardStaker.withdrawAndUnwrap(_wethAmount, true);\n }\n\n /**\n * @notice Get the total asset value held in the platform\n * @param _asset Address of the asset\n * @return balance Total value of the asset in the platform\n */\n function checkBalance(address _asset)\n public\n view\n override\n returns (uint256 balance)\n {\n require(_asset == address(weth), \"Unsupported asset\");\n\n // Eth balance needed here for the balance check that happens from vault during depositing.\n balance = address(this).balance;\n uint256 lpTokens = cvxRewardStaker.balanceOf(address(this));\n if (lpTokens > 0) {\n balance += (lpTokens * curvePool.get_virtual_price()) / 1e18;\n }\n }\n\n /**\n * @notice Returns bool indicating whether asset is supported by strategy\n * @param _asset Address of the asset\n */\n function supportsAsset(address _asset) public view override returns (bool) {\n return _asset == address(weth);\n }\n\n /***************************************\n Approvals\n ****************************************/\n\n /**\n * @notice Approve the spending of all assets by their corresponding pool tokens,\n * if for some reason is it necessary.\n */\n function safeApproveAllTokens()\n external\n override\n onlyGovernor\n nonReentrant\n {\n _approveBase();\n }\n\n /**\n * @notice Accept unwrapped WETH\n */\n receive() external payable {}\n\n /**\n * @dev Since we are unwrapping WETH before depositing it to Curve\n * there is no need to set an approval for WETH on the Curve\n * pool\n * @param _asset Address of the asset\n * @param _pToken Address of the Curve LP token\n */\n // solhint-disable-next-line no-unused-vars\n function _abstractSetPToken(address _asset, address _pToken)\n internal\n override\n {}\n\n function _approveBase() internal {\n // Approve Curve pool for OETH (required for adding liquidity)\n // No approval is needed for ETH\n // slither-disable-next-line unused-return\n oeth.approve(platformAddress, type(uint256).max);\n\n // Approve Convex deposit contract to transfer Curve pool LP tokens\n // This is needed for deposits if Curve pool LP tokens into the Convex rewards pool\n // slither-disable-next-line unused-return\n lpToken.approve(cvxDepositorAddress, type(uint256).max);\n }\n\n /**\n * @dev Returns the largest of two numbers int256 version\n */\n function _max(int256 a, int256 b) internal pure returns (int256) {\n return a >= b ? a : b;\n }\n}\n" + }, + "contracts/strategies/ConvexGeneralizedMetaStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title Curve Convex Strategy\n * @notice Investment strategy for investing stablecoins via Curve 3Pool\n * @author Origin Protocol Inc\n */\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport \"@openzeppelin/contracts/utils/Strings.sol\";\n\nimport { IRewardStaking } from \"./IRewardStaking.sol\";\nimport { IConvexDeposits } from \"./IConvexDeposits.sol\";\nimport { ICurvePool } from \"./ICurvePool.sol\";\nimport { IERC20, InitializableAbstractStrategy } from \"./AbstractCurveStrategy.sol\";\nimport { AbstractConvexMetaStrategy } from \"./AbstractConvexMetaStrategy.sol\";\nimport { StableMath } from \"../utils/StableMath.sol\";\n\ncontract ConvexGeneralizedMetaStrategy is AbstractConvexMetaStrategy {\n using StableMath for uint256;\n using SafeERC20 for IERC20;\n\n constructor(BaseStrategyConfig memory _stratConfig)\n InitializableAbstractStrategy(_stratConfig)\n {}\n\n /* Take 3pool LP and deposit it to metapool. Take the LP from metapool\n * and deposit them to Convex.\n */\n function _lpDepositAll() internal override {\n IERC20 threePoolLp = IERC20(pTokenAddress);\n ICurvePool curvePool = ICurvePool(platformAddress);\n\n uint256 threePoolLpBalance = threePoolLp.balanceOf(address(this));\n uint256 curve3PoolVirtualPrice = curvePool.get_virtual_price();\n uint256 threePoolLpDollarValue = threePoolLpBalance.mulTruncate(\n curve3PoolVirtualPrice\n );\n\n uint256[2] memory _amounts = [0, threePoolLpBalance];\n\n uint256 metapoolVirtualPrice = metapool.get_virtual_price();\n /**\n * First convert all the deposited tokens to dollar values,\n * then divide by virtual price to convert to metapool LP tokens\n * and apply the max slippage\n */\n uint256 minReceived = threePoolLpDollarValue\n .divPrecisely(metapoolVirtualPrice)\n .mulTruncate(uint256(1e18) - MAX_SLIPPAGE);\n\n uint256 metapoolLp = metapool.add_liquidity(_amounts, minReceived);\n\n bool success = IConvexDeposits(cvxDepositorAddress).deposit(\n cvxDepositorPTokenId,\n metapoolLp,\n true // Deposit with staking\n );\n\n require(success, \"Failed to deposit to Convex\");\n }\n\n /**\n * Withdraw the specified amount of tokens from the gauge. And use all the resulting tokens\n * to remove liquidity from metapool\n * @param num3CrvTokens Number of Convex 3pool LP tokens to withdraw from metapool\n */\n function _lpWithdraw(uint256 num3CrvTokens) internal override {\n uint256 gaugeTokens = IRewardStaking(cvxRewardStakerAddress).balanceOf(\n address(this)\n );\n\n uint256 requiredMetapoolLpTokens = _calcCurveMetaTokenAmount(\n crvCoinIndex,\n num3CrvTokens\n );\n\n require(\n requiredMetapoolLpTokens <= gaugeTokens,\n string(\n bytes.concat(\n bytes(\"Attempting to withdraw \"),\n bytes(Strings.toString(requiredMetapoolLpTokens)),\n bytes(\", metapoolLP but only \"),\n bytes(Strings.toString(gaugeTokens)),\n bytes(\" available.\")\n )\n )\n );\n\n // withdraw and unwrap with claim takes back the lpTokens and also collects the rewards for deposit\n IRewardStaking(cvxRewardStakerAddress).withdrawAndUnwrap(\n requiredMetapoolLpTokens,\n true\n );\n\n if (requiredMetapoolLpTokens > 0) {\n // slither-disable-next-line unused-return\n metapool.remove_liquidity_one_coin(\n requiredMetapoolLpTokens,\n int128(crvCoinIndex),\n num3CrvTokens\n );\n }\n }\n\n function _lpWithdrawAll() internal override {\n uint256 gaugeTokens = IRewardStaking(cvxRewardStakerAddress).balanceOf(\n address(this)\n );\n IRewardStaking(cvxRewardStakerAddress).withdrawAndUnwrap(\n gaugeTokens,\n true\n );\n\n if (gaugeTokens > 0) {\n uint256 burnDollarAmount = gaugeTokens.mulTruncate(\n metapool.get_virtual_price()\n );\n uint256 curve3PoolExpected = burnDollarAmount.divPrecisely(\n ICurvePool(platformAddress).get_virtual_price()\n );\n\n // Always withdraw all of the available metapool LP tokens (similar to how we always deposit all)\n // slither-disable-next-line unused-return\n metapool.remove_liquidity_one_coin(\n gaugeTokens,\n int128(crvCoinIndex),\n curve3PoolExpected -\n curve3PoolExpected.mulTruncate(maxWithdrawalSlippage)\n );\n }\n }\n}\n" + }, + "contracts/strategies/ConvexOUSDMetaStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title Curve Convex Strategy\n * @notice Investment strategy for investing stablecoins via Curve 3Pool\n * @author Origin Protocol Inc\n */\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport \"@openzeppelin/contracts/utils/Strings.sol\";\nimport \"@openzeppelin/contracts/utils/math/Math.sol\";\n\nimport { IRewardStaking } from \"./IRewardStaking.sol\";\nimport { IConvexDeposits } from \"./IConvexDeposits.sol\";\nimport { ICurvePool } from \"./ICurvePool.sol\";\nimport { IERC20, InitializableAbstractStrategy } from \"./AbstractCurveStrategy.sol\";\nimport { AbstractConvexMetaStrategy } from \"./AbstractConvexMetaStrategy.sol\";\nimport { StableMath } from \"../utils/StableMath.sol\";\nimport { IVault } from \"../interfaces/IVault.sol\";\n\ncontract ConvexOUSDMetaStrategy is AbstractConvexMetaStrategy {\n using StableMath for uint256;\n using SafeERC20 for IERC20;\n\n constructor(BaseStrategyConfig memory _stratConfig)\n InitializableAbstractStrategy(_stratConfig)\n {}\n\n /* Take 3pool LP and mint the corresponding amount of ousd. Deposit and stake that to\n * ousd Curve Metapool. Take the LP from metapool and deposit them to Convex.\n */\n function _lpDepositAll() internal override {\n ICurvePool curvePool = ICurvePool(platformAddress);\n\n uint256 threePoolLpBalance = IERC20(pTokenAddress).balanceOf(\n address(this)\n );\n uint256 curve3PoolVirtualPrice = curvePool.get_virtual_price();\n uint256 threePoolLpDollarValue = threePoolLpBalance.mulTruncate(\n curve3PoolVirtualPrice\n );\n\n // safe to cast since min value is at least 0\n uint256 ousdToAdd = uint256(\n _max(\n 0,\n int256(\n metapool.balances(crvCoinIndex).mulTruncate(\n curve3PoolVirtualPrice\n )\n ) -\n int256(metapool.balances(mainCoinIndex)) +\n int256(threePoolLpDollarValue)\n )\n );\n\n /* Add so much OUSD so that the pool ends up being balanced. And at minimum\n * add twice as much OUSD as 3poolLP and at maximum at twice as\n * much OUSD.\n */\n ousdToAdd = Math.max(ousdToAdd, threePoolLpDollarValue);\n ousdToAdd = Math.min(ousdToAdd, threePoolLpDollarValue * 2);\n\n /* Mint OUSD with a strategy that attempts to contribute to stability of OUSD metapool. Try\n * to mint so much OUSD that after deployment of liquidity pool ends up being balanced.\n *\n * To manage unpredictability minimal OUSD minted will always be at least equal or greater\n * to stablecoin(DAI, USDC, USDT) amount of 3CRVLP deployed. And never larger than twice the\n * stablecoin amount of 3CRVLP deployed even if it would have a further beneficial effect\n * on pool stability.\n */\n if (ousdToAdd > 0) {\n IVault(vaultAddress).mintForStrategy(ousdToAdd);\n }\n\n uint256[2] memory _amounts = [ousdToAdd, threePoolLpBalance];\n\n uint256 metapoolVirtualPrice = metapool.get_virtual_price();\n /**\n * First convert all the deposited tokens to dollar values,\n * then divide by virtual price to convert to metapool LP tokens\n * and apply the max slippage\n */\n uint256 minReceived = (ousdToAdd + threePoolLpDollarValue)\n .divPrecisely(metapoolVirtualPrice)\n .mulTruncate(uint256(1e18) - MAX_SLIPPAGE);\n\n uint256 metapoolLp = metapool.add_liquidity(_amounts, minReceived);\n\n bool success = IConvexDeposits(cvxDepositorAddress).deposit(\n cvxDepositorPTokenId,\n metapoolLp,\n true // Deposit with staking\n );\n\n require(success, \"Failed to deposit to Convex\");\n }\n\n /**\n * Withdraw the specified amount of tokens from the gauge. And use all the resulting tokens\n * to remove liquidity from metapool\n * @param num3CrvTokens Number of 3CRV tokens to withdraw from metapool\n */\n function _lpWithdraw(uint256 num3CrvTokens) internal override {\n ICurvePool curvePool = ICurvePool(platformAddress);\n /* The rate between coins in the metapool determines the rate at which metapool returns\n * tokens when doing balanced removal (remove_liquidity call). And by knowing how much 3crvLp\n * we want we can determine how much of OUSD we receive by removing liquidity.\n *\n * Because we are doing balanced removal we should be making profit when removing liquidity in a\n * pool tilted to either side.\n *\n * Important: A downside is that the Strategist / Governor needs to be\n * cognisant of not removing too much liquidity. And while the proposal to remove liquidity\n * is being voted on the pool tilt might change so much that the proposal that has been valid while\n * created is no longer valid.\n */\n\n uint256 crvPoolBalance = metapool.balances(crvCoinIndex);\n /* K is multiplied by 1e36 which is used for higher precision calculation of required\n * metapool LP tokens. Without it the end value can have rounding errors up to precision of\n * 10 digits. This way we move the decimal point by 36 places when doing the calculation\n * and again by 36 places when we are done with it.\n */\n uint256 k = (1e36 * metapoolLPToken.totalSupply()) / crvPoolBalance;\n // simplifying below to: `uint256 diff = (num3CrvTokens - 1) * k` causes loss of precision\n // prettier-ignore\n // slither-disable-next-line divide-before-multiply\n uint256 diff = crvPoolBalance * k -\n (crvPoolBalance - num3CrvTokens - 1) * k;\n uint256 lpToBurn = diff / 1e36;\n\n uint256 gaugeTokens = IRewardStaking(cvxRewardStakerAddress).balanceOf(\n address(this)\n );\n\n require(\n lpToBurn <= gaugeTokens,\n string(\n bytes.concat(\n bytes(\"Attempting to withdraw \"),\n bytes(Strings.toString(lpToBurn)),\n bytes(\", metapoolLP but only \"),\n bytes(Strings.toString(gaugeTokens)),\n bytes(\" available.\")\n )\n )\n );\n\n // withdraw and unwrap with claim takes back the lpTokens and also collects the rewards for deposit\n IRewardStaking(cvxRewardStakerAddress).withdrawAndUnwrap(\n lpToBurn,\n true\n );\n\n // calculate the min amount of OUSD expected for the specified amount of LP tokens\n uint256 minOUSDAmount = lpToBurn.mulTruncate(\n metapool.get_virtual_price()\n ) -\n num3CrvTokens.mulTruncate(curvePool.get_virtual_price()) -\n 1;\n\n // withdraw the liquidity from metapool\n uint256[2] memory _removedAmounts = metapool.remove_liquidity(\n lpToBurn,\n [minOUSDAmount, num3CrvTokens]\n );\n\n IVault(vaultAddress).burnForStrategy(_removedAmounts[mainCoinIndex]);\n }\n\n function _lpWithdrawAll() internal override {\n IERC20 metapoolErc20 = IERC20(address(metapool));\n uint256 gaugeTokens = IRewardStaking(cvxRewardStakerAddress).balanceOf(\n address(this)\n );\n IRewardStaking(cvxRewardStakerAddress).withdrawAndUnwrap(\n gaugeTokens,\n true\n );\n\n uint256[2] memory _minAmounts = [uint256(0), uint256(0)];\n uint256[2] memory _removedAmounts = metapool.remove_liquidity(\n metapoolErc20.balanceOf(address(this)),\n _minAmounts\n );\n\n IVault(vaultAddress).burnForStrategy(_removedAmounts[mainCoinIndex]);\n }\n}\n" + }, + "contracts/strategies/ConvexStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title Curve Convex Strategy\n * @notice Investment strategy for investing stablecoins via Curve 3Pool\n * @author Origin Protocol Inc\n */\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\n\nimport { ICurvePool } from \"./ICurvePool.sol\";\nimport { IRewardStaking } from \"./IRewardStaking.sol\";\nimport { IConvexDeposits } from \"./IConvexDeposits.sol\";\nimport { IERC20, AbstractCurveStrategy, InitializableAbstractStrategy } from \"./AbstractCurveStrategy.sol\";\nimport { StableMath } from \"../utils/StableMath.sol\";\nimport { Helpers } from \"../utils/Helpers.sol\";\n\n/*\n * IMPORTANT(!) If ConvexStrategy needs to be re-deployed, it requires new\n * proxy contract with fresh storage slots. Changes in `AbstractCurveStrategy`\n * storage slots would break existing implementation.\n *\n * Remove this notice if ConvexStrategy is re-deployed\n */\ncontract ConvexStrategy is AbstractCurveStrategy {\n using StableMath for uint256;\n using SafeERC20 for IERC20;\n\n address internal cvxDepositorAddress;\n address internal cvxRewardStakerAddress;\n // slither-disable-next-line constable-states\n address private _deprecated_cvxRewardTokenAddress;\n uint256 internal cvxDepositorPTokenId;\n\n constructor(BaseStrategyConfig memory _stratConfig)\n InitializableAbstractStrategy(_stratConfig)\n {}\n\n /**\n * Initializer for setting up strategy internal state. This overrides the\n * InitializableAbstractStrategy initializer as Curve strategies don't fit\n * well within that abstraction.\n * @param _rewardTokenAddresses Address of CRV & CVX\n * @param _assets Addresses of supported assets. MUST be passed in the same\n * order as returned by coins on the pool contract, i.e.\n * DAI, USDC, USDT\n * @param _pTokens Platform Token corresponding addresses\n * @param _cvxDepositorAddress Address of the Convex depositor(AKA booster) for this pool\n * @param _cvxRewardStakerAddress Address of the CVX rewards staker\n * @param _cvxDepositorPTokenId Pid of the pool referred to by Depositor and staker\n */\n function initialize(\n address[] calldata _rewardTokenAddresses, // CRV + CVX\n address[] calldata _assets,\n address[] calldata _pTokens,\n address _cvxDepositorAddress,\n address _cvxRewardStakerAddress,\n uint256 _cvxDepositorPTokenId\n ) external onlyGovernor initializer {\n require(_assets.length == 3, \"Must have exactly three assets\");\n // Should be set prior to abstract initialize call otherwise\n // abstractSetPToken calls will fail\n cvxDepositorAddress = _cvxDepositorAddress;\n cvxRewardStakerAddress = _cvxRewardStakerAddress;\n cvxDepositorPTokenId = _cvxDepositorPTokenId;\n pTokenAddress = _pTokens[0];\n\n InitializableAbstractStrategy._initialize(\n _rewardTokenAddresses,\n _assets,\n _pTokens\n );\n _approveBase();\n }\n\n function _lpDepositAll() internal override {\n IERC20 pToken = IERC20(pTokenAddress);\n // Deposit with staking\n bool success = IConvexDeposits(cvxDepositorAddress).deposit(\n cvxDepositorPTokenId,\n pToken.balanceOf(address(this)),\n true\n );\n require(success, \"Failed to deposit to Convex\");\n }\n\n function _lpWithdraw(uint256 numCrvTokens) internal override {\n uint256 gaugePTokens = IRewardStaking(cvxRewardStakerAddress).balanceOf(\n address(this)\n );\n\n // Not enough in this contract or in the Gauge, can't proceed\n require(numCrvTokens > gaugePTokens, \"Insufficient 3CRV balance\");\n\n // withdraw and unwrap with claim takes back the lpTokens and also collects the rewards to this\n IRewardStaking(cvxRewardStakerAddress).withdrawAndUnwrap(\n numCrvTokens,\n true\n );\n }\n\n function _lpWithdrawAll() internal override {\n // withdraw and unwrap with claim takes back the lpTokens and also collects the rewards to this\n IRewardStaking(cvxRewardStakerAddress).withdrawAndUnwrap(\n IRewardStaking(cvxRewardStakerAddress).balanceOf(address(this)),\n true\n );\n }\n\n function _approveBase() internal override {\n IERC20 pToken = IERC20(pTokenAddress);\n // 3Pool for LP token (required for removing liquidity)\n pToken.safeApprove(platformAddress, 0);\n pToken.safeApprove(platformAddress, type(uint256).max);\n // Gauge for LP token\n pToken.safeApprove(cvxDepositorAddress, 0);\n pToken.safeApprove(cvxDepositorAddress, type(uint256).max);\n }\n\n /**\n * @dev Get the total asset value held in the platform\n * @param _asset Address of the asset\n * @return balance Total value of the asset in the platform\n */\n function checkBalance(address _asset)\n public\n view\n override\n returns (uint256 balance)\n {\n require(assetToPToken[_asset] != address(0), \"Unsupported asset\");\n // LP tokens in this contract. This should generally be nothing as we\n // should always stake the full balance in the Gauge, but include for\n // safety\n uint256 contractPTokens = IERC20(pTokenAddress).balanceOf(\n address(this)\n );\n uint256 gaugePTokens = IRewardStaking(cvxRewardStakerAddress).balanceOf(\n address(this)\n );\n uint256 totalPTokens = contractPTokens + gaugePTokens;\n\n ICurvePool curvePool = ICurvePool(platformAddress);\n if (totalPTokens > 0) {\n uint256 virtual_price = curvePool.get_virtual_price();\n uint256 value = (totalPTokens * virtual_price) / 1e18;\n uint256 assetDecimals = Helpers.getDecimals(_asset);\n balance = value.scaleBy(assetDecimals, 18) / 3;\n }\n }\n\n /**\n * @dev Collect accumulated CRV and CVX and send to Vault.\n */\n function collectRewardTokens()\n external\n override\n onlyHarvester\n nonReentrant\n {\n // Collect CRV and CVX\n IRewardStaking(cvxRewardStakerAddress).getReward();\n _collectRewardTokens();\n }\n}\n" + }, + "contracts/strategies/crosschain/AbstractCCTPIntegrator.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title AbstractCCTPIntegrator\n * @author Origin Protocol Inc\n *\n * @dev Abstract contract that contains all the logic used to integrate with CCTP.\n */\n\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport { IERC20 } from \"../../utils/InitializableAbstractStrategy.sol\";\n\nimport { ICCTPTokenMessenger, ICCTPMessageTransmitter, IMessageHandlerV2 } from \"../../interfaces/cctp/ICCTP.sol\";\n\nimport { CrossChainStrategyHelper } from \"./CrossChainStrategyHelper.sol\";\nimport { Governable } from \"../../governance/Governable.sol\";\nimport { BytesHelper } from \"../../utils/BytesHelper.sol\";\nimport \"../../utils/Helpers.sol\";\n\nabstract contract AbstractCCTPIntegrator is Governable, IMessageHandlerV2 {\n using SafeERC20 for IERC20;\n\n using BytesHelper for bytes;\n using CrossChainStrategyHelper for bytes;\n\n event LastTransferNonceUpdated(uint64 lastTransferNonce);\n event NonceProcessed(uint64 nonce);\n\n event CCTPMinFinalityThresholdSet(uint16 minFinalityThreshold);\n event CCTPFeePremiumBpsSet(uint16 feePremiumBps);\n event OperatorChanged(address operator);\n event TokensBridged(\n uint32 destinationDomain,\n address peerStrategy,\n address tokenAddress,\n uint256 tokenAmount,\n uint256 maxFee,\n uint32 minFinalityThreshold,\n bytes hookData\n );\n event MessageTransmitted(\n uint32 destinationDomain,\n address peerStrategy,\n uint32 minFinalityThreshold,\n bytes message\n );\n\n // Message body V2 fields\n // Ref: https://developers.circle.com/cctp/technical-guide#message-body\n // Ref: https://github.com/circlefin/evm-cctp-contracts/blob/master/src/messages/v2/BurnMessageV2.sol\n uint8 private constant BURN_MESSAGE_V2_VERSION_INDEX = 0;\n uint8 private constant BURN_MESSAGE_V2_BURN_TOKEN_INDEX = 4;\n uint8 private constant BURN_MESSAGE_V2_RECIPIENT_INDEX = 36;\n uint8 private constant BURN_MESSAGE_V2_AMOUNT_INDEX = 68;\n uint8 private constant BURN_MESSAGE_V2_MESSAGE_SENDER_INDEX = 100;\n uint8 private constant BURN_MESSAGE_V2_FEE_EXECUTED_INDEX = 164;\n uint8 private constant BURN_MESSAGE_V2_HOOK_DATA_INDEX = 228;\n\n /**\n * @notice Max transfer threshold imposed by the CCTP\n * Ref: https://developers.circle.com/cctp/evm-smart-contracts#depositforburn\n * @dev 10M USDC limit applies to both standard and fast transfer modes. The fast transfer mode has\n * an additional limitation that is not present on-chain and Circle may alter that amount off-chain\n * at their preference. The amount available for fast transfer can be queried here:\n * https://iris-api.circle.com/v2/fastBurn/USDC/allowance .\n * If a fast transfer token transaction has been issued and there is not enough allowance for it\n * the off-chain Iris component will re-attempt the transaction and if it fails it will fallback\n * to a standard transfer. Reference section 4.3 in the whitepaper:\n * https://6778953.fs1.hubspotusercontent-na1.net/hubfs/6778953/PDFs/Whitepapers/CCTPV2_White_Paper.pdf\n */\n uint256 public constant MAX_TRANSFER_AMOUNT = 10_000_000 * 10**6; // 10M USDC\n\n /// @notice Minimum transfer amount to avoid zero or dust transfers\n uint256 public constant MIN_TRANSFER_AMOUNT = 10**6;\n\n // CCTP contracts\n // This implementation assumes that remote and local chains have these contracts\n // deployed on the same addresses.\n /// @notice CCTP message transmitter contract\n ICCTPMessageTransmitter public immutable cctpMessageTransmitter;\n /// @notice CCTP token messenger contract\n ICCTPTokenMessenger public immutable cctpTokenMessenger;\n\n /// @notice USDC address on local chain\n address public immutable usdcToken;\n\n /// @notice USDC address on remote chain\n address public immutable peerUsdcToken;\n\n /// @notice Domain ID of the chain from which messages are accepted\n uint32 public immutable peerDomainID;\n\n /// @notice Strategy address on other chain\n address public immutable peerStrategy;\n\n /**\n * @notice Minimum finality threshold\n * Can be 1000 (safe, after 1 epoch) or 2000 (finalized, after 2 epochs).\n * Ref: https://developers.circle.com/cctp/technical-guide#finality-thresholds\n * @dev When configuring the contract for fast transfer we should check the available\n * allowance of USDC that can be bridged using fast mode:\n * wget https://iris-api.circle.com/v2/fastBurn/USDC/allowance\n */\n uint16 public minFinalityThreshold;\n\n /// @notice Fee premium in basis points\n uint16 public feePremiumBps;\n\n /// @notice Nonce of the last known deposit or withdrawal\n uint64 public lastTransferNonce;\n\n /// @notice Operator address: Can relay CCTP messages\n address public operator;\n\n /// @notice Mapping of processed nonces\n mapping(uint64 => bool) private nonceProcessed;\n\n // For future use\n uint256[48] private __gap;\n\n modifier onlyCCTPMessageTransmitter() {\n require(\n msg.sender == address(cctpMessageTransmitter),\n \"Caller is not CCTP transmitter\"\n );\n _;\n }\n\n modifier onlyOperator() {\n require(msg.sender == operator, \"Caller is not the Operator\");\n _;\n }\n\n /**\n * @notice Configuration for CCTP integration\n * @param cctpTokenMessenger Address of the CCTP token messenger contract\n * @param cctpMessageTransmitter Address of the CCTP message transmitter contract\n * @param peerDomainID Domain ID of the chain from which messages are accepted.\n * 0 for Ethereum, 6 for Base, etc.\n * Ref: https://developers.circle.com/cctp/cctp-supported-blockchains\n * @param peerStrategy Address of the master or remote strategy on the other chain\n * @param usdcToken USDC address on local chain\n */\n struct CCTPIntegrationConfig {\n address cctpTokenMessenger;\n address cctpMessageTransmitter;\n uint32 peerDomainID;\n address peerStrategy;\n address usdcToken;\n address peerUsdcToken;\n }\n\n constructor(CCTPIntegrationConfig memory _config) {\n require(_config.usdcToken != address(0), \"Invalid USDC address\");\n require(\n _config.peerUsdcToken != address(0),\n \"Invalid peer USDC address\"\n );\n require(\n _config.cctpTokenMessenger != address(0),\n \"Invalid CCTP config\"\n );\n require(\n _config.cctpMessageTransmitter != address(0),\n \"Invalid CCTP config\"\n );\n require(\n _config.peerStrategy != address(0),\n \"Invalid peer strategy address\"\n );\n\n cctpMessageTransmitter = ICCTPMessageTransmitter(\n _config.cctpMessageTransmitter\n );\n cctpTokenMessenger = ICCTPTokenMessenger(_config.cctpTokenMessenger);\n\n // Domain ID of the chain from which messages are accepted\n peerDomainID = _config.peerDomainID;\n\n // Strategy address on other chain, should\n // always be same as the proxy of this strategy\n peerStrategy = _config.peerStrategy;\n\n // USDC address on local chain\n usdcToken = _config.usdcToken;\n\n // Just a sanity check to ensure the base token is USDC\n uint256 _usdcTokenDecimals = Helpers.getDecimals(_config.usdcToken);\n string memory _usdcTokenSymbol = Helpers.getSymbol(_config.usdcToken);\n require(_usdcTokenDecimals == 6, \"Base token decimals must be 6\");\n require(\n keccak256(abi.encodePacked(_usdcTokenSymbol)) ==\n keccak256(abi.encodePacked(\"USDC\")),\n \"Token symbol must be USDC\"\n );\n\n // USDC address on remote chain\n peerUsdcToken = _config.peerUsdcToken;\n }\n\n /**\n * @dev Initialize the implementation contract\n * @param _operator Operator address\n * @param _minFinalityThreshold Minimum finality threshold\n * @param _feePremiumBps Fee premium in basis points\n */\n function _initialize(\n address _operator,\n uint16 _minFinalityThreshold,\n uint16 _feePremiumBps\n ) internal {\n _setOperator(_operator);\n _setMinFinalityThreshold(_minFinalityThreshold);\n _setFeePremiumBps(_feePremiumBps);\n\n // Nonce starts at 1, so assume nonce 0 as processed.\n // NOTE: This will cause the deposit/withdraw to fail if the\n // strategy is not initialized properly (which is expected).\n nonceProcessed[0] = true;\n }\n\n /***************************************\n Settings\n ****************************************/\n /**\n * @dev Set the operator address\n * @param _operator Operator address\n */\n function setOperator(address _operator) external onlyGovernor {\n _setOperator(_operator);\n }\n\n /**\n * @dev Set the operator address\n * @param _operator Operator address\n */\n function _setOperator(address _operator) internal {\n operator = _operator;\n emit OperatorChanged(_operator);\n }\n\n /**\n * @dev Set the minimum finality threshold at which\n * the message is considered to be finalized to relay.\n * Only accepts a value of 1000 (Safe, after 1 epoch) or\n * 2000 (Finalized, after 2 epochs).\n * @param _minFinalityThreshold Minimum finality threshold\n */\n function setMinFinalityThreshold(uint16 _minFinalityThreshold)\n external\n onlyGovernor\n {\n _setMinFinalityThreshold(_minFinalityThreshold);\n }\n\n /**\n * @dev Set the minimum finality threshold\n * @param _minFinalityThreshold Minimum finality threshold\n */\n function _setMinFinalityThreshold(uint16 _minFinalityThreshold) internal {\n // 1000 for fast transfer and 2000 for standard transfer\n require(\n _minFinalityThreshold == 1000 || _minFinalityThreshold == 2000,\n \"Invalid threshold\"\n );\n\n minFinalityThreshold = _minFinalityThreshold;\n emit CCTPMinFinalityThresholdSet(_minFinalityThreshold);\n }\n\n /**\n * @dev Set the fee premium in basis points.\n * Cannot be higher than 30% (3000 basis points).\n * @param _feePremiumBps Fee premium in basis points\n */\n function setFeePremiumBps(uint16 _feePremiumBps) external onlyGovernor {\n _setFeePremiumBps(_feePremiumBps);\n }\n\n /**\n * @dev Set the fee premium in basis points\n * Cannot be higher than 30% (3000 basis points).\n * Ref: https://developers.circle.com/cctp/technical-guide#fees\n * @param _feePremiumBps Fee premium in basis points\n */\n function _setFeePremiumBps(uint16 _feePremiumBps) internal {\n require(_feePremiumBps <= 3000, \"Fee premium too high\"); // 30%\n\n feePremiumBps = _feePremiumBps;\n emit CCTPFeePremiumBpsSet(_feePremiumBps);\n }\n\n /***************************************\n CCTP message handling\n ****************************************/\n\n /**\n * @dev Handles a finalized CCTP message\n * @param sourceDomain Source domain of the message\n * @param sender Sender of the message\n * @param finalityThresholdExecuted Fidelity threshold executed\n * @param messageBody Message body\n */\n function handleReceiveFinalizedMessage(\n uint32 sourceDomain,\n bytes32 sender,\n uint32 finalityThresholdExecuted,\n bytes memory messageBody\n ) external override onlyCCTPMessageTransmitter returns (bool) {\n // Make sure the finality threshold at execution is at least 2000\n require(\n finalityThresholdExecuted >= 2000,\n \"Finality threshold too low\"\n );\n\n return _handleReceivedMessage(sourceDomain, sender, messageBody);\n }\n\n /**\n * @dev Handles an unfinalized but safe CCTP message\n * @param sourceDomain Source domain of the message\n * @param sender Sender of the message\n * @param finalityThresholdExecuted Fidelity threshold executed\n * @param messageBody Message body\n */\n function handleReceiveUnfinalizedMessage(\n uint32 sourceDomain,\n bytes32 sender,\n uint32 finalityThresholdExecuted,\n bytes memory messageBody\n ) external override onlyCCTPMessageTransmitter returns (bool) {\n // Make sure the contract is configured to handle unfinalized messages\n require(\n minFinalityThreshold == 1000,\n \"Unfinalized messages are not supported\"\n );\n // Make sure the finality threshold at execution is at least 1000\n require(\n finalityThresholdExecuted >= 1000,\n \"Finality threshold too low\"\n );\n\n return _handleReceivedMessage(sourceDomain, sender, messageBody);\n }\n\n /**\n * @dev Handles a CCTP message\n * @param sourceDomain Source domain of the message\n * @param sender Sender of the message\n * @param messageBody Message body\n */\n function _handleReceivedMessage(\n uint32 sourceDomain,\n bytes32 sender,\n bytes memory messageBody\n ) internal returns (bool) {\n require(sourceDomain == peerDomainID, \"Unknown Source Domain\");\n\n // Extract address from bytes32 (CCTP stores addresses as right-padded bytes32)\n address senderAddress = address(uint160(uint256(sender)));\n require(senderAddress == peerStrategy, \"Unknown Sender\");\n\n _onMessageReceived(messageBody);\n\n return true;\n }\n\n /**\n * @dev Sends tokens to the peer strategy using CCTP Token Messenger\n * @param tokenAmount Amount of tokens to send\n * @param hookData Hook data\n */\n function _sendTokens(uint256 tokenAmount, bytes memory hookData)\n internal\n virtual\n {\n // CCTP has a maximum transfer amount of 10M USDC per tx\n require(tokenAmount <= MAX_TRANSFER_AMOUNT, \"Token amount too high\");\n\n // Approve only what needs to be transferred\n IERC20(usdcToken).safeApprove(address(cctpTokenMessenger), tokenAmount);\n\n // Compute the max fee to be paid.\n // Ref: https://developers.circle.com/cctp/evm-smart-contracts#getminfeeamount\n // The right way to compute fees would be to use CCTP's getMinFeeAmount function.\n // The issue is that the getMinFeeAmount is not present on v2.0 contracts, but is on\n // v2.1. Some of CCTP's deployed contracts are v2.0, some are v2.1.\n // We will only be using standard transfers and fee on those is 0 for now. If they\n // ever start implementing fee for standard transfers or if we decide to use fast\n // trasnfer, we can use feePremiumBps as a workaround.\n uint256 maxFee = feePremiumBps > 0\n ? (tokenAmount * feePremiumBps) / 10000\n : 0;\n\n // Send tokens to the peer strategy using CCTP Token Messenger\n cctpTokenMessenger.depositForBurnWithHook(\n tokenAmount,\n peerDomainID,\n bytes32(uint256(uint160(peerStrategy))),\n address(usdcToken),\n bytes32(uint256(uint160(peerStrategy))),\n maxFee,\n uint32(minFinalityThreshold),\n hookData\n );\n\n emit TokensBridged(\n peerDomainID,\n peerStrategy,\n usdcToken,\n tokenAmount,\n maxFee,\n uint32(minFinalityThreshold),\n hookData\n );\n }\n\n /**\n * @dev Sends a message to the peer strategy using CCTP Message Transmitter\n * @param message Payload of the message to send\n */\n function _sendMessage(bytes memory message) internal virtual {\n cctpMessageTransmitter.sendMessage(\n peerDomainID,\n bytes32(uint256(uint160(peerStrategy))),\n bytes32(uint256(uint160(peerStrategy))),\n uint32(minFinalityThreshold),\n message\n );\n\n emit MessageTransmitted(\n peerDomainID,\n peerStrategy,\n uint32(minFinalityThreshold),\n message\n );\n }\n\n /**\n * @dev Receives a message from the peer strategy on the other chain,\n * does some basic checks and relays it to the local MessageTransmitterV2.\n * If the message is a burn message, it will also handle the hook data\n * and call the _onTokenReceived function.\n * @param message Payload of the message to send\n * @param attestation Attestation of the message\n */\n function relay(bytes memory message, bytes memory attestation)\n external\n onlyOperator\n {\n (\n uint32 version,\n uint32 sourceDomainID,\n address sender,\n address recipient,\n bytes memory messageBody\n ) = message.decodeMessageHeader();\n\n // Ensure that it's a CCTP message\n require(\n version == CrossChainStrategyHelper.CCTP_MESSAGE_VERSION,\n \"Invalid CCTP message version\"\n );\n\n // Ensure that the source domain is the peer domain\n require(sourceDomainID == peerDomainID, \"Unknown Source Domain\");\n\n // Ensure message body version\n version = messageBody.extractUint32(BURN_MESSAGE_V2_VERSION_INDEX);\n\n // NOTE: There's a possibility that the CCTP Token Messenger might\n // send other types of messages in future, not just the burn message.\n // If it ever comes to that, this shouldn't cause us any problems\n // because it has to still go through the followign checks:\n // - version check\n // - message body length check\n // - sender and recipient (which should be in the same slots and same as address(this))\n // - hook data handling (which will revert even if all the above checks pass)\n bool isBurnMessageV1 = sender == address(cctpTokenMessenger);\n\n if (isBurnMessageV1) {\n // Handle burn message\n require(\n version == 1 &&\n messageBody.length >= BURN_MESSAGE_V2_HOOK_DATA_INDEX,\n \"Invalid burn message\"\n );\n\n // Ensure the burn token is USDC\n address burnToken = messageBody.extractAddress(\n BURN_MESSAGE_V2_BURN_TOKEN_INDEX\n );\n require(burnToken == peerUsdcToken, \"Invalid burn token\");\n\n // Address of caller of depositForBurn (or depositForBurnWithCaller) on source domain\n sender = messageBody.extractAddress(\n BURN_MESSAGE_V2_MESSAGE_SENDER_INDEX\n );\n\n recipient = messageBody.extractAddress(\n BURN_MESSAGE_V2_RECIPIENT_INDEX\n );\n } else {\n // We handle only Burn message or our custom messagee\n require(\n version == CrossChainStrategyHelper.ORIGIN_MESSAGE_VERSION,\n \"Unsupported message version\"\n );\n }\n\n // Ensure the recipient is this contract\n // Both sender and recipient should be deployed to same address on both chains.\n require(address(this) == recipient, \"Unexpected recipient address\");\n require(sender == peerStrategy, \"Incorrect sender/recipient address\");\n\n // Relay the message\n // This step also mints USDC and transfers it to the recipient wallet\n bool relaySuccess = cctpMessageTransmitter.receiveMessage(\n message,\n attestation\n );\n require(relaySuccess, \"Receive message failed\");\n\n if (isBurnMessageV1) {\n // Extract the hook data from the message body\n bytes memory hookData = messageBody.extractSlice(\n BURN_MESSAGE_V2_HOOK_DATA_INDEX,\n messageBody.length\n );\n\n // Extract the token amount from the message body\n uint256 tokenAmount = messageBody.extractUint256(\n BURN_MESSAGE_V2_AMOUNT_INDEX\n );\n\n // Extract the fee executed from the message body\n uint256 feeExecuted = messageBody.extractUint256(\n BURN_MESSAGE_V2_FEE_EXECUTED_INDEX\n );\n\n // Call the _onTokenReceived function\n _onTokenReceived(tokenAmount - feeExecuted, feeExecuted, hookData);\n }\n }\n\n /***************************************\n Message utils\n ****************************************/\n\n /***************************************\n Nonce Handling\n ****************************************/\n /**\n * @dev Checks if the last known transfer is pending.\n * Nonce starts at 1, so 0 is disregarded.\n * @return True if a transfer is pending, false otherwise\n */\n function isTransferPending() public view returns (bool) {\n return !nonceProcessed[lastTransferNonce];\n }\n\n /**\n * @dev Checks if a given nonce is processed.\n * Nonce starts at 1, so 0 is disregarded.\n * @param nonce Nonce to check\n * @return True if the nonce is processed, false otherwise\n */\n function isNonceProcessed(uint64 nonce) public view returns (bool) {\n return nonceProcessed[nonce];\n }\n\n /**\n * @dev Marks a given nonce as processed.\n * Can only mark nonce as processed once. New nonce should\n * always be greater than the last known nonce. Also updates\n * the last known nonce.\n * @param nonce Nonce to mark as processed\n */\n function _markNonceAsProcessed(uint64 nonce) internal {\n uint64 lastNonce = lastTransferNonce;\n\n // Can only mark latest nonce as processed\n // Master strategy when receiving a message from the remote strategy\n // will have lastNone == nonce, as the nonce is increase at the start\n // of deposit / withdrawal flow.\n // Remote strategy will have lastNonce < nonce, as a new nonce initiated\n // from master will be greater than the last one.\n require(nonce >= lastNonce, \"Nonce too low\");\n // Can only mark nonce as processed once\n require(!nonceProcessed[nonce], \"Nonce already processed\");\n\n nonceProcessed[nonce] = true;\n emit NonceProcessed(nonce);\n\n if (nonce != lastNonce) {\n // Update last known nonce\n lastTransferNonce = nonce;\n emit LastTransferNonceUpdated(nonce);\n }\n }\n\n /**\n * @dev Gets the next nonce to use.\n * Nonce starts at 1, so 0 is disregarded.\n * Reverts if last nonce hasn't been processed yet.\n * @return Next nonce\n */\n function _getNextNonce() internal returns (uint64) {\n uint64 nonce = lastTransferNonce;\n\n require(nonceProcessed[nonce], \"Pending token transfer\");\n\n nonce = nonce + 1;\n lastTransferNonce = nonce;\n emit LastTransferNonceUpdated(nonce);\n\n return nonce;\n }\n\n /***************************************\n Inheritence overrides\n ****************************************/\n\n /**\n * @dev Called when the USDC is received from the CCTP\n * @param tokenAmount The actual amount of USDC received (amount sent - fee executed)\n * @param feeExecuted The fee executed\n * @param payload The payload of the message (hook data)\n */\n function _onTokenReceived(\n uint256 tokenAmount,\n uint256 feeExecuted,\n bytes memory payload\n ) internal virtual;\n\n /**\n * @dev Called when the message is received\n * @param payload The payload of the message\n */\n function _onMessageReceived(bytes memory payload) internal virtual;\n}\n" + }, + "contracts/strategies/crosschain/CrossChainMasterStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title OUSD Yearn V3 Master Strategy - the Mainnet part\n * @author Origin Protocol Inc\n *\n * @dev This strategy can only perform 1 deposit or withdrawal at a time. For that\n * reason it shouldn't be configured as an asset default strategy.\n */\n\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport { IERC20, InitializableAbstractStrategy } from \"../../utils/InitializableAbstractStrategy.sol\";\nimport { AbstractCCTPIntegrator } from \"./AbstractCCTPIntegrator.sol\";\nimport { CrossChainStrategyHelper } from \"./CrossChainStrategyHelper.sol\";\n\ncontract CrossChainMasterStrategy is\n AbstractCCTPIntegrator,\n InitializableAbstractStrategy\n{\n using SafeERC20 for IERC20;\n using CrossChainStrategyHelper for bytes;\n\n /**\n * @notice Remote strategy balance\n * @dev The remote balance is cached and might not reflect the actual\n * real-time balance of the remote strategy.\n */\n uint256 public remoteStrategyBalance;\n\n /// @notice Amount that's bridged due to a pending Deposit process\n /// but with no acknowledgement from the remote strategy yet\n uint256 public pendingAmount;\n\n uint256 internal constant MAX_BALANCE_CHECK_AGE = 1 days;\n\n event RemoteStrategyBalanceUpdated(uint256 balance);\n event WithdrawRequested(address indexed asset, uint256 amount);\n event WithdrawAllSkipped();\n event BalanceCheckIgnored(uint64 nonce, uint256 timestamp, bool isTooOld);\n\n /**\n * @param _stratConfig The platform and OToken vault addresses\n */\n constructor(\n BaseStrategyConfig memory _stratConfig,\n CCTPIntegrationConfig memory _cctpConfig\n )\n InitializableAbstractStrategy(_stratConfig)\n AbstractCCTPIntegrator(_cctpConfig)\n {\n require(\n _stratConfig.platformAddress == address(0),\n \"Invalid platform address\"\n );\n require(\n _stratConfig.vaultAddress != address(0),\n \"Invalid Vault address\"\n );\n }\n\n /**\n * @dev Initialize the strategy implementation\n * @param _operator Address of the operator\n * @param _minFinalityThreshold Minimum finality threshold\n * @param _feePremiumBps Fee premium in basis points\n */\n function initialize(\n address _operator,\n uint16 _minFinalityThreshold,\n uint16 _feePremiumBps\n ) external virtual onlyGovernor initializer {\n _initialize(_operator, _minFinalityThreshold, _feePremiumBps);\n\n address[] memory rewardTokens = new address[](0);\n address[] memory assets = new address[](0);\n address[] memory pTokens = new address[](0);\n\n InitializableAbstractStrategy._initialize(\n rewardTokens,\n assets,\n pTokens\n );\n }\n\n /// @inheritdoc InitializableAbstractStrategy\n function deposit(address _asset, uint256 _amount)\n external\n override\n onlyVault\n nonReentrant\n {\n _deposit(_asset, _amount);\n }\n\n /// @inheritdoc InitializableAbstractStrategy\n function depositAll() external override onlyVault nonReentrant {\n uint256 balance = IERC20(usdcToken).balanceOf(address(this));\n // Deposit if balance is greater than 1 USDC\n if (balance >= MIN_TRANSFER_AMOUNT) {\n _deposit(usdcToken, balance);\n }\n }\n\n /// @inheritdoc InitializableAbstractStrategy\n function withdraw(\n address _recipient,\n address _asset,\n uint256 _amount\n ) external override onlyVault nonReentrant {\n require(_recipient == vaultAddress, \"Only Vault can withdraw\");\n _withdraw(_asset, _amount);\n }\n\n /// @inheritdoc InitializableAbstractStrategy\n function withdrawAll() external override onlyVaultOrGovernor nonReentrant {\n if (isTransferPending()) {\n // Do nothing if there is a pending transfer\n // Note: We never want withdrawAll to fail, so\n // emit an event to indicate that the withdrawal was skipped\n emit WithdrawAllSkipped();\n return;\n }\n\n // Withdraw everything in Remote strategy\n uint256 _remoteBalance = remoteStrategyBalance;\n if (_remoteBalance < MIN_TRANSFER_AMOUNT) {\n // Do nothing if there is less than 1 USDC in the Remote strategy\n return;\n }\n\n _withdraw(\n usdcToken,\n _remoteBalance > MAX_TRANSFER_AMOUNT\n ? MAX_TRANSFER_AMOUNT\n : _remoteBalance\n );\n }\n\n /**\n * @notice Check the balance of the strategy that includes\n * the balance of the asset on this contract,\n * the amount of the asset being bridged,\n * and the balance reported by the Remote strategy.\n * @param _asset Address of the asset to check\n * @return balance Total balance of the asset\n */\n function checkBalance(address _asset)\n public\n view\n override\n returns (uint256 balance)\n {\n require(_asset == usdcToken, \"Unsupported asset\");\n\n // USDC balance on this contract\n // + USDC being bridged\n // + USDC cached in the corresponding Remote part of this contract\n return\n IERC20(usdcToken).balanceOf(address(this)) +\n pendingAmount +\n remoteStrategyBalance;\n }\n\n /// @inheritdoc InitializableAbstractStrategy\n function supportsAsset(address _asset) public view override returns (bool) {\n return _asset == usdcToken;\n }\n\n /// @inheritdoc InitializableAbstractStrategy\n function safeApproveAllTokens()\n external\n override\n onlyGovernor\n nonReentrant\n {}\n\n /// @inheritdoc InitializableAbstractStrategy\n function _abstractSetPToken(address, address) internal override {}\n\n /// @inheritdoc InitializableAbstractStrategy\n function collectRewardTokens()\n external\n override\n onlyHarvester\n nonReentrant\n {}\n\n /// @inheritdoc AbstractCCTPIntegrator\n function _onMessageReceived(bytes memory payload) internal override {\n if (\n payload.getMessageType() ==\n CrossChainStrategyHelper.BALANCE_CHECK_MESSAGE\n ) {\n // Received when Remote strategy checks the balance\n _processBalanceCheckMessage(payload);\n return;\n }\n\n revert(\"Unknown message type\");\n }\n\n /// @inheritdoc AbstractCCTPIntegrator\n function _onTokenReceived(\n uint256 tokenAmount,\n // solhint-disable-next-line no-unused-vars\n uint256 feeExecuted,\n bytes memory payload\n ) internal override {\n uint64 _nonce = lastTransferNonce;\n\n // Should be expecting an acknowledgement\n require(!isNonceProcessed(_nonce), \"Nonce already processed\");\n\n // Now relay to the regular flow\n // NOTE: Calling _onMessageReceived would mean that we are bypassing a\n // few checks that the regular flow does (like sourceDomainID check\n // and sender check in `handleReceiveFinalizedMessage`). However,\n // CCTPMessageRelayer relays the message first (which will go through\n // all the checks) and not update balance and then finally calls this\n // `_onTokenReceived` which will update the balance.\n // So, if any of the checks fail during the first no-balance-update flow,\n // this won't happen either, since the tx would revert.\n _onMessageReceived(payload);\n\n // Send any tokens in the contract to the Vault\n uint256 usdcBalance = IERC20(usdcToken).balanceOf(address(this));\n // Should always have enough tokens\n require(usdcBalance >= tokenAmount, \"Insufficient balance\");\n // Transfer all tokens to the Vault to not leave any dust\n IERC20(usdcToken).safeTransfer(vaultAddress, usdcBalance);\n\n // Emit withdrawal amount\n emit Withdrawal(usdcToken, usdcToken, usdcBalance);\n }\n\n /**\n * @dev Bridge and deposit asset into the remote strategy\n * @param _asset Address of the asset to deposit\n * @param depositAmount Amount of the asset to deposit\n */\n function _deposit(address _asset, uint256 depositAmount) internal virtual {\n require(_asset == usdcToken, \"Unsupported asset\");\n require(pendingAmount == 0, \"Unexpected pending amount\");\n // Deposit at least 1 USDC\n require(\n depositAmount >= MIN_TRANSFER_AMOUNT,\n \"Deposit amount too small\"\n );\n require(\n depositAmount <= MAX_TRANSFER_AMOUNT,\n \"Deposit amount too high\"\n );\n\n // Get the next nonce\n // Note: reverts if a transfer is pending\n uint64 nonce = _getNextNonce();\n\n // Set pending amount\n pendingAmount = depositAmount;\n\n // Build deposit message payload\n bytes memory message = CrossChainStrategyHelper.encodeDepositMessage(\n nonce,\n depositAmount\n );\n\n // Send deposit message to the remote strategy\n _sendTokens(depositAmount, message);\n\n // Emit deposit event\n emit Deposit(_asset, _asset, depositAmount);\n }\n\n /**\n * @dev Send a withdraw request to the remote strategy\n * @param _asset Address of the asset to withdraw\n * @param _amount Amount of the asset to withdraw\n */\n function _withdraw(address _asset, uint256 _amount) internal virtual {\n require(_asset == usdcToken, \"Unsupported asset\");\n // Withdraw at least 1 USDC\n require(_amount >= MIN_TRANSFER_AMOUNT, \"Withdraw amount too small\");\n require(\n _amount <= remoteStrategyBalance,\n \"Withdraw amount exceeds remote strategy balance\"\n );\n require(\n _amount <= MAX_TRANSFER_AMOUNT,\n \"Withdraw amount exceeds max transfer amount\"\n );\n\n // Get the next nonce\n // Note: reverts if a transfer is pending\n uint64 nonce = _getNextNonce();\n\n // Build and send withdrawal message with payload\n bytes memory message = CrossChainStrategyHelper.encodeWithdrawMessage(\n nonce,\n _amount\n );\n _sendMessage(message);\n\n // Emit WithdrawRequested event here,\n // Withdraw will be emitted in _onTokenReceived\n emit WithdrawRequested(usdcToken, _amount);\n }\n\n /**\n * @dev Process balance check:\n * - Confirms a deposit to the remote strategy\n * - Skips balance update if there's a pending withdrawal\n * - Updates the remote strategy balance\n * @param message The message containing the nonce and balance\n */\n function _processBalanceCheckMessage(bytes memory message)\n internal\n virtual\n {\n // Decode the message\n // When transferConfirmation is true, it means that the message is a result of a deposit or a withdrawal\n // process.\n (\n uint64 nonce,\n uint256 balance,\n bool transferConfirmation,\n uint256 timestamp\n ) = message.decodeBalanceCheckMessage();\n // Get the last cached nonce\n uint64 _lastCachedNonce = lastTransferNonce;\n\n if (nonce != _lastCachedNonce) {\n // If nonce is not the last cached nonce, it is an outdated message\n // Ignore it\n return;\n }\n\n // A received message nonce not yet processed indicates there is a\n // deposit or withdrawal in progress.\n bool transferInProgress = isTransferPending();\n\n if (transferInProgress) {\n if (transferConfirmation) {\n // Apply the effects of the deposit / withdrawal completion\n _markNonceAsProcessed(nonce);\n pendingAmount = 0;\n } else {\n // A balanceCheck arrived that is not part of the deposit / withdrawal process\n // that has been generated on the Remote contract after the deposit / withdrawal which is\n // still pending. This can happen when the CCTP bridge delivers the messages out of order.\n // Ignore it, since the pending deposit / withdrawal must first be cofirmed.\n emit BalanceCheckIgnored(nonce, timestamp, false);\n return;\n }\n } else {\n if (block.timestamp > timestamp + MAX_BALANCE_CHECK_AGE) {\n // Balance check is too old, ignore it\n emit BalanceCheckIgnored(nonce, timestamp, true);\n return;\n }\n }\n\n // At this point update the strategy balance the balanceCheck message is either:\n // - a confirmation of a deposit / withdrawal\n // - a message that updates balances when no deposit / withdrawal is in progress\n remoteStrategyBalance = balance;\n emit RemoteStrategyBalanceUpdated(balance);\n }\n}\n" + }, + "contracts/strategies/crosschain/CrossChainRemoteStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title CrossChainRemoteStrategy\n * @author Origin Protocol Inc\n *\n * @dev Part of the cross-chain strategy that lives on the remote chain.\n * Handles deposits and withdrawals from the master strategy on peer chain\n * and locally deposits the funds to a 4626 compatible vault.\n */\n\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport { IERC20 } from \"../../utils/InitializableAbstractStrategy.sol\";\nimport { IERC4626 } from \"../../../lib/openzeppelin/interfaces/IERC4626.sol\";\nimport { Generalized4626Strategy } from \"../Generalized4626Strategy.sol\";\nimport { AbstractCCTPIntegrator } from \"./AbstractCCTPIntegrator.sol\";\nimport { CrossChainStrategyHelper } from \"./CrossChainStrategyHelper.sol\";\nimport { InitializableAbstractStrategy } from \"../../utils/InitializableAbstractStrategy.sol\";\nimport { Strategizable } from \"../../governance/Strategizable.sol\";\n\ncontract CrossChainRemoteStrategy is\n AbstractCCTPIntegrator,\n Generalized4626Strategy,\n Strategizable\n{\n using SafeERC20 for IERC20;\n using CrossChainStrategyHelper for bytes;\n\n event DepositUnderlyingFailed(string reason);\n event WithdrawalFailed(uint256 amountRequested, uint256 amountAvailable);\n event WithdrawUnderlyingFailed(string reason);\n\n modifier onlyOperatorOrStrategistOrGovernor() {\n require(\n msg.sender == operator ||\n msg.sender == strategistAddr ||\n isGovernor(),\n \"Caller is not the Operator, Strategist or the Governor\"\n );\n _;\n }\n\n modifier onlyGovernorOrStrategist()\n override(InitializableAbstractStrategy, Strategizable) {\n require(\n msg.sender == strategistAddr || isGovernor(),\n \"Caller is not the Strategist or Governor\"\n );\n _;\n }\n\n constructor(\n BaseStrategyConfig memory _baseConfig,\n CCTPIntegrationConfig memory _cctpConfig\n )\n AbstractCCTPIntegrator(_cctpConfig)\n Generalized4626Strategy(_baseConfig, _cctpConfig.usdcToken)\n {\n require(usdcToken == address(assetToken), \"Token mismatch\");\n require(\n _baseConfig.platformAddress != address(0),\n \"Invalid platform address\"\n );\n // Vault address must always be address(0) for the remote strategy\n require(\n _baseConfig.vaultAddress == address(0),\n \"Invalid vault address\"\n );\n }\n\n /**\n * @dev Initialize the strategy implementation\n * @param _strategist Address of the strategist\n * @param _operator Address of the operator\n * @param _minFinalityThreshold Minimum finality threshold\n * @param _feePremiumBps Fee premium in basis points\n */\n function initialize(\n address _strategist,\n address _operator,\n uint16 _minFinalityThreshold,\n uint16 _feePremiumBps\n ) external virtual onlyGovernor initializer {\n _initialize(_operator, _minFinalityThreshold, _feePremiumBps);\n _setStrategistAddr(_strategist);\n\n address[] memory rewardTokens = new address[](0);\n address[] memory assets = new address[](1);\n address[] memory pTokens = new address[](1);\n\n assets[0] = address(usdcToken);\n pTokens[0] = address(platformAddress);\n\n InitializableAbstractStrategy._initialize(\n rewardTokens,\n assets,\n pTokens\n );\n }\n\n /// @inheritdoc Generalized4626Strategy\n function deposit(address _asset, uint256 _amount)\n external\n virtual\n override\n onlyGovernorOrStrategist\n nonReentrant\n {\n _deposit(_asset, _amount);\n }\n\n /// @inheritdoc Generalized4626Strategy\n function depositAll()\n external\n virtual\n override\n onlyGovernorOrStrategist\n nonReentrant\n {\n _deposit(usdcToken, IERC20(usdcToken).balanceOf(address(this)));\n }\n\n /// @inheritdoc Generalized4626Strategy\n /// @dev Interface requires a recipient, but for compatibility it must be address(this).\n function withdraw(\n address _recipient,\n address _asset,\n uint256 _amount\n ) external virtual override onlyGovernorOrStrategist nonReentrant {\n _withdraw(_recipient, _asset, _amount);\n }\n\n /// @inheritdoc Generalized4626Strategy\n function withdrawAll()\n external\n virtual\n override\n onlyGovernorOrStrategist\n nonReentrant\n {\n IERC4626 platform = IERC4626(platformAddress);\n _withdraw(\n address(this),\n usdcToken,\n platform.previewRedeem(platform.balanceOf(address(this)))\n );\n }\n\n /// @inheritdoc AbstractCCTPIntegrator\n function _onMessageReceived(bytes memory payload) internal override {\n uint32 messageType = payload.getMessageType();\n if (messageType == CrossChainStrategyHelper.DEPOSIT_MESSAGE) {\n // Received when Master strategy sends tokens to the remote strategy\n // Do nothing because we receive acknowledgement with token transfer,\n // so _onTokenReceived will handle it\n } else if (messageType == CrossChainStrategyHelper.WITHDRAW_MESSAGE) {\n // Received when Master strategy requests a withdrawal\n _processWithdrawMessage(payload);\n } else {\n revert(\"Unknown message type\");\n }\n }\n\n /**\n * @dev Process deposit message from peer strategy\n * @param tokenAmount Amount of tokens received\n * @param feeExecuted Fee executed\n * @param payload Payload of the message\n */\n function _processDepositMessage(\n // solhint-disable-next-line no-unused-vars\n uint256 tokenAmount,\n // solhint-disable-next-line no-unused-vars\n uint256 feeExecuted,\n bytes memory payload\n ) internal virtual {\n (uint64 nonce, ) = payload.decodeDepositMessage();\n\n // Replay protection is part of the _markNonceAsProcessed function\n _markNonceAsProcessed(nonce);\n\n // Deposit everything we got, not just what was bridged\n uint256 balance = IERC20(usdcToken).balanceOf(address(this));\n\n // Underlying call to deposit funds can fail. It mustn't affect the overall\n // flow as confirmation message should still be sent.\n if (balance >= MIN_TRANSFER_AMOUNT) {\n _deposit(usdcToken, balance);\n }\n\n // Send balance check message to the peer strategy\n bytes memory message = CrossChainStrategyHelper\n .encodeBalanceCheckMessage(\n lastTransferNonce,\n checkBalance(usdcToken),\n true,\n block.timestamp\n );\n _sendMessage(message);\n }\n\n /**\n * @dev Deposit assets by converting them to shares\n * @param _asset Address of asset to deposit\n * @param _amount Amount of asset to deposit\n */\n function _deposit(address _asset, uint256 _amount) internal override {\n // By design, this function should not revert. Otherwise, it'd\n // not be able to process messages and might freeze the contracts\n // state. However these two require statements would never fail\n // in every function invoking this. The same kind of checks should\n // be enforced in all the calling functions for these two and any\n // other require statements added to this function.\n require(_amount > 0, \"Must deposit something\");\n require(_asset == address(usdcToken), \"Unexpected asset address\");\n\n // This call can fail, and the failure doesn't need to bubble up to the _processDepositMessage function\n // as the flow is not affected by the failure.\n\n try IERC4626(platformAddress).deposit(_amount, address(this)) {\n emit Deposit(_asset, address(shareToken), _amount);\n } catch Error(string memory reason) {\n emit DepositUnderlyingFailed(\n string(abi.encodePacked(\"Deposit failed: \", reason))\n );\n } catch (bytes memory lowLevelData) {\n emit DepositUnderlyingFailed(\n string(\n abi.encodePacked(\n \"Deposit failed: low-level call failed with data \",\n lowLevelData\n )\n )\n );\n }\n }\n\n /**\n * @dev Process withdrawal message from peer strategy\n * @param payload Payload of the message\n */\n function _processWithdrawMessage(bytes memory payload) internal virtual {\n (uint64 nonce, uint256 withdrawAmount) = payload\n .decodeWithdrawMessage();\n\n // Replay protection is part of the _markNonceAsProcessed function\n _markNonceAsProcessed(nonce);\n\n uint256 usdcBalance = IERC20(usdcToken).balanceOf(address(this));\n\n if (usdcBalance < withdrawAmount) {\n // Withdraw the missing funds from the remote strategy. This call can fail and\n // the failure doesn't bubble up to the _processWithdrawMessage function\n _withdraw(address(this), usdcToken, withdrawAmount - usdcBalance);\n\n // Update the possible increase in the balance on the contract.\n usdcBalance = IERC20(usdcToken).balanceOf(address(this));\n }\n\n // Check balance after withdrawal\n uint256 strategyBalance = checkBalance(usdcToken);\n\n // If there are some tokens to be sent AND the balance is sufficient\n // to satisfy the withdrawal request then send the funds to the peer strategy.\n // In case a direct withdraw(All) has previously been called\n // there is a possibility of USDC funds remaining on the contract.\n // A separate withdraw to extract or deposit to the Morpho vault needs to be\n // initiated from the peer Master strategy to utilise USDC funds.\n if (\n withdrawAmount >= MIN_TRANSFER_AMOUNT &&\n usdcBalance >= withdrawAmount\n ) {\n // The new balance on the contract needs to have USDC subtracted from it as\n // that will be withdrawn in the next step\n bytes memory message = CrossChainStrategyHelper\n .encodeBalanceCheckMessage(\n lastTransferNonce,\n strategyBalance - withdrawAmount,\n true,\n block.timestamp\n );\n _sendTokens(withdrawAmount, message);\n } else {\n // Contract either:\n // - only has small dust amount of USDC\n // - doesn't have sufficient funds to satisfy the withdrawal request\n // In both cases send the balance update message to the peer strategy.\n bytes memory message = CrossChainStrategyHelper\n .encodeBalanceCheckMessage(\n lastTransferNonce,\n strategyBalance,\n true,\n block.timestamp\n );\n _sendMessage(message);\n emit WithdrawalFailed(withdrawAmount, usdcBalance);\n }\n }\n\n /**\n * @dev Withdraw asset by burning shares\n * @param _recipient Address to receive withdrawn asset\n * @param _asset Address of asset to withdraw\n * @param _amount Amount of asset to withdraw\n */\n function _withdraw(\n address _recipient,\n address _asset,\n uint256 _amount\n ) internal override {\n require(_amount > 0, \"Must withdraw something\");\n require(_recipient == address(this), \"Invalid recipient\");\n require(_asset == address(usdcToken), \"Unexpected asset address\");\n\n // This call can fail, and the failure doesn't need to bubble up to the _processWithdrawMessage function\n // as the flow is not affected by the failure.\n try\n // slither-disable-next-line unused-return\n IERC4626(platformAddress).withdraw(\n _amount,\n address(this),\n address(this)\n )\n {\n emit Withdrawal(_asset, address(shareToken), _amount);\n } catch Error(string memory reason) {\n emit WithdrawUnderlyingFailed(\n string(abi.encodePacked(\"Withdrawal failed: \", reason))\n );\n } catch (bytes memory lowLevelData) {\n emit WithdrawUnderlyingFailed(\n string(\n abi.encodePacked(\n \"Withdrawal failed: low-level call failed with data \",\n lowLevelData\n )\n )\n );\n }\n }\n\n /**\n * @dev Process token received message from peer strategy\n * @param tokenAmount Amount of tokens received\n * @param feeExecuted Fee executed\n * @param payload Payload of the message\n */\n function _onTokenReceived(\n uint256 tokenAmount,\n uint256 feeExecuted,\n bytes memory payload\n ) internal override {\n uint32 messageType = payload.getMessageType();\n\n require(\n messageType == CrossChainStrategyHelper.DEPOSIT_MESSAGE,\n \"Invalid message type\"\n );\n\n _processDepositMessage(tokenAmount, feeExecuted, payload);\n }\n\n /**\n * @dev Send balance update message to the peer strategy\n */\n function sendBalanceUpdate()\n external\n virtual\n onlyOperatorOrStrategistOrGovernor\n {\n uint256 balance = checkBalance(usdcToken);\n bytes memory message = CrossChainStrategyHelper\n .encodeBalanceCheckMessage(\n lastTransferNonce,\n balance,\n false,\n block.timestamp\n );\n _sendMessage(message);\n }\n\n /**\n * @notice Get the total asset value held in the platform and contract\n * @param _asset Address of the asset\n * @return balance Total value of the asset in the platform and contract\n */\n function checkBalance(address _asset)\n public\n view\n override\n returns (uint256)\n {\n require(_asset == usdcToken, \"Unexpected asset address\");\n /**\n * Balance of USDC on the contract is counted towards the total balance, since a deposit\n * to the Morpho V2 might fail and the USDC might remain on this contract as a result of a\n * bridged transfer.\n */\n uint256 balanceOnContract = IERC20(usdcToken).balanceOf(address(this));\n\n IERC4626 platform = IERC4626(platformAddress);\n return\n platform.previewRedeem(platform.balanceOf(address(this))) +\n balanceOnContract;\n }\n}\n" + }, + "contracts/strategies/crosschain/CrossChainStrategyHelper.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title CrossChainStrategyHelper\n * @author Origin Protocol Inc\n * @dev This library is used to encode and decode the messages for the cross-chain strategy.\n * It is used to ensure that the messages are valid and to get the message version and type.\n */\n\nimport { BytesHelper } from \"../../utils/BytesHelper.sol\";\n\nlibrary CrossChainStrategyHelper {\n using BytesHelper for bytes;\n\n uint32 public constant DEPOSIT_MESSAGE = 1;\n uint32 public constant WITHDRAW_MESSAGE = 2;\n uint32 public constant BALANCE_CHECK_MESSAGE = 3;\n\n uint32 public constant CCTP_MESSAGE_VERSION = 1;\n uint32 public constant ORIGIN_MESSAGE_VERSION = 1010;\n\n // CCTP Message Header fields\n // Ref: https://developers.circle.com/cctp/technical-guide#message-header\n uint8 private constant VERSION_INDEX = 0;\n uint8 private constant SOURCE_DOMAIN_INDEX = 4;\n uint8 private constant SENDER_INDEX = 44;\n uint8 private constant RECIPIENT_INDEX = 76;\n uint8 private constant MESSAGE_BODY_INDEX = 148;\n\n /**\n * @dev Get the message version from the message.\n * It should always be 4 bytes long,\n * starting from the 0th index.\n * @param message The message to get the version from\n * @return The message version\n */\n function getMessageVersion(bytes memory message)\n internal\n pure\n returns (uint32)\n {\n // uint32 bytes 0 to 4 is Origin message version\n // uint32 bytes 4 to 8 is Message type\n return message.extractUint32(0);\n }\n\n /**\n * @dev Get the message type from the message.\n * It should always be 4 bytes long,\n * starting from the 4th index.\n * @param message The message to get the type from\n * @return The message type\n */\n function getMessageType(bytes memory message)\n internal\n pure\n returns (uint32)\n {\n // uint32 bytes 0 to 4 is Origin message version\n // uint32 bytes 4 to 8 is Message type\n return message.extractUint32(4);\n }\n\n /**\n * @dev Verify the message version and type.\n * The message version should be the same as the Origin message version,\n * and the message type should be the same as the expected message type.\n * @param _message The message to verify\n * @param _type The expected message type\n */\n function verifyMessageVersionAndType(bytes memory _message, uint32 _type)\n internal\n pure\n {\n require(\n getMessageVersion(_message) == ORIGIN_MESSAGE_VERSION,\n \"Invalid Origin Message Version\"\n );\n require(getMessageType(_message) == _type, \"Invalid Message type\");\n }\n\n /**\n * @dev Get the message payload from the message.\n * The payload starts at the 8th byte.\n * @param message The message to get the payload from\n * @return The message payload\n */\n function getMessagePayload(bytes memory message)\n internal\n pure\n returns (bytes memory)\n {\n // uint32 bytes 0 to 4 is Origin message version\n // uint32 bytes 4 to 8 is Message type\n // Payload starts at byte 8\n return message.extractSlice(8, message.length);\n }\n\n /**\n * @dev Encode the deposit message.\n * The message version and type are always encoded in the message.\n * @param nonce The nonce of the deposit\n * @param depositAmount The amount of the deposit\n * @return The encoded deposit message\n */\n function encodeDepositMessage(uint64 nonce, uint256 depositAmount)\n internal\n pure\n returns (bytes memory)\n {\n return\n abi.encodePacked(\n ORIGIN_MESSAGE_VERSION,\n DEPOSIT_MESSAGE,\n abi.encode(nonce, depositAmount)\n );\n }\n\n /**\n * @dev Decode the deposit message.\n * The message version and type are verified in the message.\n * @param message The message to decode\n * @return The nonce and the amount of the deposit\n */\n function decodeDepositMessage(bytes memory message)\n internal\n pure\n returns (uint64, uint256)\n {\n verifyMessageVersionAndType(message, DEPOSIT_MESSAGE);\n\n (uint64 nonce, uint256 depositAmount) = abi.decode(\n getMessagePayload(message),\n (uint64, uint256)\n );\n return (nonce, depositAmount);\n }\n\n /**\n * @dev Encode the withdrawal message.\n * The message version and type are always encoded in the message.\n * @param nonce The nonce of the withdrawal\n * @param withdrawAmount The amount of the withdrawal\n * @return The encoded withdrawal message\n */\n function encodeWithdrawMessage(uint64 nonce, uint256 withdrawAmount)\n internal\n pure\n returns (bytes memory)\n {\n return\n abi.encodePacked(\n ORIGIN_MESSAGE_VERSION,\n WITHDRAW_MESSAGE,\n abi.encode(nonce, withdrawAmount)\n );\n }\n\n /**\n * @dev Decode the withdrawal message.\n * The message version and type are verified in the message.\n * @param message The message to decode\n * @return The nonce and the amount of the withdrawal\n */\n function decodeWithdrawMessage(bytes memory message)\n internal\n pure\n returns (uint64, uint256)\n {\n verifyMessageVersionAndType(message, WITHDRAW_MESSAGE);\n\n (uint64 nonce, uint256 withdrawAmount) = abi.decode(\n getMessagePayload(message),\n (uint64, uint256)\n );\n return (nonce, withdrawAmount);\n }\n\n /**\n * @dev Encode the balance check message.\n * The message version and type are always encoded in the message.\n * @param nonce The nonce of the balance check\n * @param balance The balance to check\n * @param transferConfirmation Indicates if the message is a transfer confirmation. This is true\n * when the message is a result of a deposit or a withdrawal.\n * @return The encoded balance check message\n */\n function encodeBalanceCheckMessage(\n uint64 nonce,\n uint256 balance,\n bool transferConfirmation,\n uint256 timestamp\n ) internal pure returns (bytes memory) {\n return\n abi.encodePacked(\n ORIGIN_MESSAGE_VERSION,\n BALANCE_CHECK_MESSAGE,\n abi.encode(nonce, balance, transferConfirmation, timestamp)\n );\n }\n\n /**\n * @dev Decode the balance check message.\n * The message version and type are verified in the message.\n * @param message The message to decode\n * @return The nonce, the balance and indicates if the message is a transfer confirmation\n */\n function decodeBalanceCheckMessage(bytes memory message)\n internal\n pure\n returns (\n uint64,\n uint256,\n bool,\n uint256\n )\n {\n verifyMessageVersionAndType(message, BALANCE_CHECK_MESSAGE);\n\n (\n uint64 nonce,\n uint256 balance,\n bool transferConfirmation,\n uint256 timestamp\n ) = abi.decode(\n getMessagePayload(message),\n (uint64, uint256, bool, uint256)\n );\n return (nonce, balance, transferConfirmation, timestamp);\n }\n\n /**\n * @dev Decode the CCTP message header\n * @param message Message to decode\n * @return version Version of the message\n * @return sourceDomainID Source domain ID\n * @return sender Sender of the message\n * @return recipient Recipient of the message\n * @return messageBody Message body\n */\n function decodeMessageHeader(bytes memory message)\n internal\n pure\n returns (\n uint32 version,\n uint32 sourceDomainID,\n address sender,\n address recipient,\n bytes memory messageBody\n )\n {\n version = message.extractUint32(VERSION_INDEX);\n sourceDomainID = message.extractUint32(SOURCE_DOMAIN_INDEX);\n // Address of MessageTransmitterV2 caller on source domain\n sender = message.extractAddress(SENDER_INDEX);\n // Address to handle message body on destination domain\n recipient = message.extractAddress(RECIPIENT_INDEX);\n messageBody = message.extractSlice(MESSAGE_BODY_INDEX, message.length);\n }\n}\n" + }, + "contracts/strategies/CurveAMOStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title Curve Automated Market Maker (AMO) Strategy\n * @notice AMO strategy for a Curve pool using an OToken.\n * @author Origin Protocol Inc\n */\nimport { Math } from \"@openzeppelin/contracts/utils/math/Math.sol\";\nimport { SafeCast } from \"@openzeppelin/contracts/utils/math/SafeCast.sol\";\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\n\nimport { IERC20, InitializableAbstractStrategy } from \"../utils/InitializableAbstractStrategy.sol\";\nimport { StableMath } from \"../utils/StableMath.sol\";\nimport { IVault } from \"../interfaces/IVault.sol\";\nimport { ICurveStableSwapNG } from \"../interfaces/ICurveStableSwapNG.sol\";\nimport { ICurveLiquidityGaugeV6 } from \"../interfaces/ICurveLiquidityGaugeV6.sol\";\nimport { IBasicToken } from \"../interfaces/IBasicToken.sol\";\nimport { ICurveMinter } from \"../interfaces/ICurveMinter.sol\";\n\ncontract CurveAMOStrategy is InitializableAbstractStrategy {\n using SafeCast for uint256;\n using SafeERC20 for IERC20;\n using StableMath for uint256;\n\n /**\n * @dev a threshold under which the contract no longer allows for the protocol to manually rebalance.\n * Guarding against a strategist / guardian being taken over and with multiple transactions\n * draining the protocol funds.\n */\n uint256 public constant SOLVENCY_THRESHOLD = 0.998 ether;\n\n // New immutable variables that must be set in the constructor\n /**\n * @notice Address of the hard asset (weth, usdt, usdc).\n */\n IERC20 public immutable hardAsset;\n\n /**\n * @notice Address of the OTOKEN token contract.\n */\n IERC20 public immutable oToken;\n\n /**\n * @notice Address of the LP (Liquidity Provider) token contract.\n */\n IERC20 public immutable lpToken;\n\n /**\n * @notice Address of the Curve StableSwap NG pool contract.\n */\n ICurveStableSwapNG public immutable curvePool;\n\n /**\n * @notice Address of the Curve X-Chain Liquidity Gauge contract.\n */\n ICurveLiquidityGaugeV6 public immutable gauge;\n\n /**\n * @notice Address of the Curve Minter contract.\n */\n ICurveMinter public immutable minter;\n\n /**\n * @notice Index of the OTOKEN and hardAsset in the Curve pool.\n */\n uint128 public immutable otokenCoinIndex;\n uint128 public immutable hardAssetCoinIndex;\n\n /**\n * @notice Decimals of the OTOKEN and hardAsset.\n */\n uint8 public immutable decimalsOToken;\n uint8 public immutable decimalsHardAsset;\n\n /**\n * @notice Maximum slippage allowed for adding/removing liquidity from the Curve pool.\n */\n uint256 public maxSlippage;\n\n event MaxSlippageUpdated(uint256 newMaxSlippage);\n\n /**\n * @dev Verifies that the caller is the Strategist.\n */\n modifier onlyStrategist() {\n require(\n msg.sender == IVault(vaultAddress).strategistAddr(),\n \"Caller is not the Strategist\"\n );\n _;\n }\n\n /**\n * @dev Checks the Curve pool's balances have improved and the balances\n * have not tipped to the other side.\n * This modifier is only applied to functions that do a single sided add or remove.\n * The standard deposit function adds to both sides of the pool in a way that\n * the pool's balance is not worsened.\n * Withdrawals are proportional so doesn't change the pools asset balance.\n */\n modifier improvePoolBalance() {\n // Get the hard asset and OToken balances in the Curve pool\n uint256[] memory balancesBefore = curvePool.get_balances();\n // diff = hardAsset balance - OTOKEN balance\n int256 diffBefore = (\n balancesBefore[hardAssetCoinIndex].scaleBy(\n decimalsOToken,\n decimalsHardAsset\n )\n ).toInt256() - balancesBefore[otokenCoinIndex].toInt256();\n\n _;\n\n // Get the hard asset and OToken balances in the Curve pool\n uint256[] memory balancesAfter = curvePool.get_balances();\n // diff = hardAsset balance - OTOKEN balance\n int256 diffAfter = (\n balancesAfter[hardAssetCoinIndex].scaleBy(\n decimalsOToken,\n decimalsHardAsset\n )\n ).toInt256() - balancesAfter[otokenCoinIndex].toInt256();\n\n if (diffBefore == 0) {\n require(diffAfter == 0, \"Position balance is worsened\");\n } else if (diffBefore < 0) {\n // If the pool was originally imbalanced in favor of OTOKEN, then\n // we want to check that the pool is now more balanced\n require(diffAfter <= 0, \"OTokens overshot peg\");\n require(diffBefore < diffAfter, \"OTokens balance worse\");\n } else if (diffBefore > 0) {\n // If the pool was originally imbalanced in favor of hardAsset, then\n // we want to check that the pool is now more balanced\n require(diffAfter >= 0, \"Assets overshot peg\");\n require(diffAfter < diffBefore, \"Assets balance worse\");\n }\n }\n\n constructor(\n BaseStrategyConfig memory _baseConfig,\n address _otoken,\n address _hardAsset,\n address _gauge,\n address _minter\n ) InitializableAbstractStrategy(_baseConfig) {\n lpToken = IERC20(_baseConfig.platformAddress);\n curvePool = ICurveStableSwapNG(_baseConfig.platformAddress);\n minter = ICurveMinter(_minter);\n\n oToken = IERC20(_otoken);\n hardAsset = IERC20(_hardAsset);\n gauge = ICurveLiquidityGaugeV6(_gauge);\n decimalsHardAsset = IBasicToken(_hardAsset).decimals();\n decimalsOToken = IBasicToken(_otoken).decimals();\n\n (hardAssetCoinIndex, otokenCoinIndex) = curvePool.coins(0) == _hardAsset\n ? (0, 1)\n : (1, 0);\n require(\n curvePool.coins(otokenCoinIndex) == _otoken &&\n curvePool.coins(hardAssetCoinIndex) == _hardAsset,\n \"Invalid coin indexes\"\n );\n require(gauge.lp_token() == address(curvePool), \"Invalid pool\");\n }\n\n /**\n * Initializer for setting up strategy internal state. This overrides the\n * InitializableAbstractStrategy initializer as Curve strategies don't fit\n * well within that abstraction.\n * @param _rewardTokenAddresses Address of CRV\n * @param _maxSlippage Maximum slippage allowed for adding/removing liquidity from the Curve pool.\n */\n function initialize(\n address[] calldata _rewardTokenAddresses, // CRV\n uint256 _maxSlippage\n ) external onlyGovernor initializer {\n address[] memory pTokens = new address[](1);\n pTokens[0] = address(curvePool);\n\n address[] memory _assets = new address[](1);\n _assets[0] = address(hardAsset);\n\n InitializableAbstractStrategy._initialize(\n _rewardTokenAddresses,\n _assets,\n pTokens\n );\n\n _approveBase();\n _setMaxSlippage(_maxSlippage);\n }\n\n /***************************************\n Deposit\n ****************************************/\n\n /**\n * @notice Deposit hard asset into the Curve pool\n * @param _hardAsset Address of hard asset contract.\n * @param _amount Amount of hard asset to deposit.\n */\n function deposit(address _hardAsset, uint256 _amount)\n external\n override\n onlyVault\n nonReentrant\n {\n _deposit(_hardAsset, _amount);\n }\n\n function _deposit(address _hardAsset, uint256 _hardAssetAmount) internal {\n require(_hardAssetAmount > 0, \"Must deposit something\");\n require(_hardAsset == address(hardAsset), \"Unsupported asset\");\n\n emit Deposit(_hardAsset, address(lpToken), _hardAssetAmount);\n uint256 scaledHardAssetAmount = _hardAssetAmount.scaleBy(\n decimalsOToken,\n decimalsHardAsset\n );\n\n // Get the asset and OToken balances in the Curve pool\n uint256[] memory balances = curvePool.get_balances();\n // safe to cast since min value is at least 0\n uint256 otokenToAdd = uint256(\n _max(\n 0,\n (\n balances[hardAssetCoinIndex].scaleBy(\n decimalsOToken,\n decimalsHardAsset\n )\n ).toInt256() +\n scaledHardAssetAmount.toInt256() -\n balances[otokenCoinIndex].toInt256()\n )\n );\n\n /* Add so much OTOKEN so that the pool ends up being balanced. And at minimum\n * add as much OTOKEN as hard asset and at maximum twice as much OTOKEN.\n */\n otokenToAdd = Math.max(otokenToAdd, scaledHardAssetAmount);\n otokenToAdd = Math.min(otokenToAdd, scaledHardAssetAmount * 2);\n\n /* Mint OTOKEN with a strategy that attempts to contribute to stability of OTOKEN/hardAsset pool. Try\n * to mint so much OTOKEN that after deployment of liquidity pool ends up being balanced.\n *\n * To manage unpredictability minimal OTOKEN minted will always be at least equal or greater\n * to hardAsset amount deployed. And never larger than twice the hardAsset amount deployed even if\n * it would have a further beneficial effect on pool stability.\n */\n IVault(vaultAddress).mintForStrategy(otokenToAdd);\n\n emit Deposit(address(oToken), address(lpToken), otokenToAdd);\n\n uint256[] memory _amounts = new uint256[](2);\n _amounts[hardAssetCoinIndex] = _hardAssetAmount;\n _amounts[otokenCoinIndex] = otokenToAdd;\n\n uint256 valueInLpTokens = (scaledHardAssetAmount + otokenToAdd)\n .divPrecisely(curvePool.get_virtual_price());\n uint256 minMintAmount = valueInLpTokens.mulTruncate(\n uint256(1e18) - maxSlippage\n );\n\n // Do the deposit to the Curve pool\n uint256 lpDeposited = curvePool.add_liquidity(_amounts, minMintAmount);\n require(lpDeposited >= minMintAmount, \"Min LP amount error\");\n\n // Deposit the Curve pool's LP tokens into the Curve gauge\n gauge.deposit(lpDeposited);\n\n // Ensure solvency of the vault\n _solvencyAssert();\n }\n\n /**\n * @notice Deposit the strategy's entire balance of hardAsset into the Curve pool\n */\n function depositAll() external override onlyVault nonReentrant {\n uint256 balance = hardAsset.balanceOf(address(this));\n if (balance > 0) {\n _deposit(address(hardAsset), balance);\n }\n }\n\n /***************************************\n Withdraw\n ****************************************/\n\n /**\n * @notice Withdraw hardAsset and OTOKEN from the Curve pool, burn the OTOKEN,\n * transfer hardAsset to the recipient.\n * @param _recipient Address to receive withdrawn asset which is normally the Vault.\n * @param _hardAsset Address of the hardAsset contract.\n * @param _amount Amount of hardAsset to withdraw.\n */\n function withdraw(\n address _recipient,\n address _hardAsset,\n uint256 _amount\n ) external override onlyVault nonReentrant {\n require(_amount > 0, \"Must withdraw something\");\n require(\n _hardAsset == address(hardAsset),\n \"Can only withdraw hard asset\"\n );\n\n emit Withdrawal(_hardAsset, address(lpToken), _amount);\n\n uint256 requiredLpTokens = calcTokenToBurn(\n _amount.scaleBy(decimalsOToken, decimalsHardAsset)\n );\n\n _lpWithdraw(requiredLpTokens);\n\n /* math in requiredLpTokens should correctly calculate the amount of LP to remove\n * in that the strategy receives enough hardAsset on balanced removal\n */\n uint256[] memory _minWithdrawalAmounts = new uint256[](2);\n _minWithdrawalAmounts[hardAssetCoinIndex] = _amount;\n // slither-disable-next-line unused-return\n curvePool.remove_liquidity(requiredLpTokens, _minWithdrawalAmounts);\n\n // Burn all the removed OTOKEN and any that was left in the strategy\n uint256 otokenToBurn = oToken.balanceOf(address(this));\n IVault(vaultAddress).burnForStrategy(otokenToBurn);\n\n emit Withdrawal(address(oToken), address(lpToken), otokenToBurn);\n\n // Transfer hardAsset to the recipient\n hardAsset.safeTransfer(_recipient, _amount);\n\n // Ensure solvency of the vault\n _solvencyAssert();\n }\n\n function calcTokenToBurn(uint256 _hardAssetAmount)\n internal\n view\n returns (uint256 lpToBurn)\n {\n /* The rate between coins in the pool determines the rate at which pool returns\n * tokens when doing balanced removal (remove_liquidity call). And by knowing how much hardAsset\n * we want we can determine how much of OTOKEN we receive by removing liquidity.\n *\n * Because we are doing balanced removal we should be making profit when removing liquidity in a\n * pool tilted to either side.\n *\n * Important: A downside is that the Strategist / Governor needs to be\n * cognisant of not removing too much liquidity. And while the proposal to remove liquidity\n * is being voted on the pool tilt might change so much that the proposal that has been valid while\n * created is no longer valid.\n */\n\n uint256 poolHardAssetBalance = curvePool\n .balances(hardAssetCoinIndex)\n .scaleBy(decimalsOToken, decimalsHardAsset);\n /* K is multiplied by 1e36 which is used for higher precision calculation of required\n * pool LP tokens. Without it the end value can have rounding errors up to precision of\n * 10 digits. This way we move the decimal point by 36 places when doing the calculation\n * and again by 36 places when we are done with it.\n */\n uint256 k = (1e36 * lpToken.totalSupply()) / poolHardAssetBalance;\n // prettier-ignore\n // slither-disable-next-line divide-before-multiply\n uint256 diff = (_hardAssetAmount + 1) * k;\n lpToBurn = diff / 1e36;\n }\n\n /**\n * @notice Remove all hardAsset and OTOKEN from the Curve pool, burn the OTOKEN,\n * transfer hardAsset to the Vault contract.\n */\n function withdrawAll() external override onlyVaultOrGovernor nonReentrant {\n uint256 gaugeTokens = gauge.balanceOf(address(this));\n _lpWithdraw(gaugeTokens);\n\n // Withdraws are proportional to assets held by 3Pool\n uint256[] memory minWithdrawAmounts = new uint256[](2);\n\n // Check balance of LP tokens in the strategy, if 0 return\n uint256 lpBalance = lpToken.balanceOf(address(this));\n\n // Remove liquidity\n // slither-disable-next-line unused-return\n if (lpBalance > 0) {\n curvePool.remove_liquidity(lpBalance, minWithdrawAmounts);\n }\n\n // Burn all OTOKEN\n uint256 otokenToBurn = oToken.balanceOf(address(this));\n IVault(vaultAddress).burnForStrategy(otokenToBurn);\n\n // Get the strategy contract's hardAsset balance.\n // This includes all that was removed from the Curve pool and\n // any hardAsset that was sitting in the strategy contract before the removal.\n uint256 hardAssetBalance = hardAsset.balanceOf(address(this));\n hardAsset.safeTransfer(vaultAddress, hardAssetBalance);\n\n if (hardAssetBalance > 0)\n emit Withdrawal(\n address(hardAsset),\n address(lpToken),\n hardAssetBalance\n );\n if (otokenToBurn > 0)\n emit Withdrawal(address(oToken), address(lpToken), otokenToBurn);\n }\n\n /***************************************\n Curve pool Rebalancing\n ****************************************/\n\n /**\n * @notice Mint OTokens and one-sided add to the Curve pool.\n * This is used when the Curve pool does not have enough OTokens and too many hardAsset.\n * The OToken/Asset, eg OTOKEN/hardAsset, price with increase.\n * The amount of assets in the vault is unchanged.\n * The total supply of OTokens is increased.\n * The asset value of the strategy and vault is increased.\n * @param _oTokens The amount of OTokens to be minted and added to the pool.\n */\n function mintAndAddOTokens(uint256 _oTokens)\n external\n onlyStrategist\n nonReentrant\n improvePoolBalance\n {\n IVault(vaultAddress).mintForStrategy(_oTokens);\n\n uint256[] memory amounts = new uint256[](2);\n amounts[otokenCoinIndex] = _oTokens;\n\n // Convert OTOKEN to Curve pool LP tokens\n uint256 valueInLpTokens = (_oTokens).divPrecisely(\n curvePool.get_virtual_price()\n );\n // Apply slippage to LP tokens\n uint256 minMintAmount = valueInLpTokens.mulTruncate(\n uint256(1e18) - maxSlippage\n );\n\n // Add the minted OTokens to the Curve pool\n uint256 lpDeposited = curvePool.add_liquidity(amounts, minMintAmount);\n require(lpDeposited >= minMintAmount, \"Min LP amount error\");\n\n // Deposit the Curve pool LP tokens to the Curve gauge\n gauge.deposit(lpDeposited);\n\n // Ensure solvency of the vault\n _solvencyAssert();\n\n emit Deposit(address(oToken), address(lpToken), _oTokens);\n }\n\n /**\n * @notice One-sided remove of OTokens from the Curve pool which are then burned.\n * This is used when the Curve pool has too many OTokens and not enough hardAsset.\n * The amount of assets in the vault is unchanged.\n * The total supply of OTokens is reduced.\n * The asset value of the strategy and vault is reduced.\n * @param _lpTokens The amount of Curve pool LP tokens to be burned for OTokens.\n */\n function removeAndBurnOTokens(uint256 _lpTokens)\n external\n onlyStrategist\n nonReentrant\n improvePoolBalance\n {\n // Withdraw Curve pool LP tokens from gauge and remove OTokens from the Curve pool\n uint256 otokenToBurn = _withdrawAndRemoveFromPool(\n _lpTokens,\n otokenCoinIndex\n );\n\n // The vault burns the OTokens from this strategy\n IVault(vaultAddress).burnForStrategy(otokenToBurn);\n\n // Ensure solvency of the vault\n _solvencyAssert();\n\n emit Withdrawal(address(oToken), address(lpToken), otokenToBurn);\n }\n\n /**\n * @notice One-sided remove of hardAsset from the Curve pool and transfer to the vault.\n * This is used when the Curve pool does not have enough OTokens and too many hardAsset.\n * The OToken/Asset, eg OTOKEN/hardAsset, price with decrease.\n * The amount of assets in the vault increases.\n * The total supply of OTokens does not change.\n * The asset value of the strategy reduces.\n * The asset value of the vault should be close to the same.\n * @param _lpTokens The amount of Curve pool LP tokens to be burned for hardAsset.\n * @dev Curve pool LP tokens is used rather than hardAsset assets as Curve does not\n * have a way to accurately calculate the amount of LP tokens for a required\n * amount of hardAsset. Curve's `calc_token_amount` function does not include fees.\n * A 3rd party library can be used that takes into account the fees, but this\n * is a gas intensive process. It's easier for the trusted strategist to\n * calculate the amount of Curve pool LP tokens required off-chain.\n */\n function removeOnlyAssets(uint256 _lpTokens)\n external\n onlyStrategist\n nonReentrant\n improvePoolBalance\n {\n // Withdraw Curve pool LP tokens from Curve gauge and remove hardAsset from the Curve pool\n uint256 hardAssetAmount = _withdrawAndRemoveFromPool(\n _lpTokens,\n hardAssetCoinIndex\n );\n\n // Transfer hardAsset to the vault\n hardAsset.safeTransfer(vaultAddress, hardAssetAmount);\n\n // Ensure solvency of the vault\n _solvencyAssert();\n\n emit Withdrawal(address(hardAsset), address(lpToken), hardAssetAmount);\n }\n\n /**\n * @dev Remove Curve pool LP tokens from the gauge and\n * do a one-sided remove of hardAsset or OTOKEN from the Curve pool.\n * @param _lpTokens The amount of Curve pool LP tokens to be removed from the gauge\n * @param coinIndex The index of the coin to be removed from the Curve pool. 0 = hardAsset, 1 = OTOKEN.\n * @return coinsRemoved The amount of hardAsset or OTOKEN removed from the Curve pool.\n */\n function _withdrawAndRemoveFromPool(uint256 _lpTokens, uint128 coinIndex)\n internal\n returns (uint256 coinsRemoved)\n {\n // Withdraw Curve pool LP tokens from Curve gauge\n _lpWithdraw(_lpTokens);\n\n // Convert Curve pool LP tokens to hardAsset value\n uint256 valueInEth = _lpTokens.mulTruncate(\n curvePool.get_virtual_price()\n );\n\n if (coinIndex == hardAssetCoinIndex) {\n valueInEth = valueInEth.scaleBy(decimalsHardAsset, decimalsOToken);\n }\n\n // Apply slippage to hardAsset value\n uint256 minAmount = valueInEth.mulTruncate(uint256(1e18) - maxSlippage);\n\n // Remove just the hardAsset from the Curve pool\n coinsRemoved = curvePool.remove_liquidity_one_coin(\n _lpTokens,\n int128(coinIndex),\n minAmount,\n address(this)\n );\n }\n\n /**\n * Checks that the protocol is solvent, protecting from a rogue Strategist / Guardian that can\n * keep rebalancing the pool in both directions making the protocol lose a tiny amount of\n * funds each time.\n *\n * Protocol must be at least SOLVENCY_THRESHOLD (99,8 %) backed in order for the rebalances to\n * function.\n */\n function _solvencyAssert() internal view {\n uint256 _totalVaultValue = IVault(vaultAddress).totalValue();\n uint256 _totalOtokenSupply = oToken.totalSupply();\n\n if (\n _totalVaultValue.divPrecisely(_totalOtokenSupply) <\n SOLVENCY_THRESHOLD\n ) {\n revert(\"Protocol insolvent\");\n }\n }\n\n /***************************************\n Assets and Rewards\n ****************************************/\n\n /**\n * @notice Collect accumulated CRV (and other) rewards and send to the Harvester.\n */\n function collectRewardTokens()\n external\n override\n onlyHarvester\n nonReentrant\n {\n // Collect CRV rewards from inflation\n minter.mint(address(gauge));\n\n // Collect extra gauge rewards (outside of CRV)\n gauge.claim_rewards();\n\n _collectRewardTokens();\n }\n\n function _lpWithdraw(uint256 _lpAmount) internal {\n require(\n gauge.balanceOf(address(this)) >= _lpAmount,\n \"Insufficient LP tokens\"\n );\n // withdraw lp tokens from the gauge without claiming rewards\n gauge.withdraw(_lpAmount);\n }\n\n /**\n * @notice Get the total asset value held in the platform\n * @param _asset Address of the asset\n * @return balance Total value of the asset in the platform\n */\n function checkBalance(address _asset)\n external\n view\n override\n returns (uint256 balance)\n {\n require(_asset == address(hardAsset), \"Unsupported asset\");\n\n // hardAsset balance needed here for the balance check that happens from vault during depositing.\n balance = hardAsset.balanceOf(address(this));\n uint256 lpTokens = gauge.balanceOf(address(this));\n if (lpTokens > 0) {\n balance += ((lpTokens * curvePool.get_virtual_price()) / 1e18)\n .scaleBy(decimalsHardAsset, decimalsOToken);\n }\n }\n\n /**\n * @notice Returns bool indicating whether asset is supported by strategy\n * @param _asset Address of the asset\n */\n function supportsAsset(address _asset) public view override returns (bool) {\n return _asset == address(hardAsset);\n }\n\n /***************************************\n Approvals\n ****************************************/\n\n /**\n * @notice Sets the maximum slippage allowed for any swap/liquidity operation\n * @param _maxSlippage Maximum slippage allowed, 1e18 = 100%.\n */\n function setMaxSlippage(uint256 _maxSlippage) external onlyGovernor {\n _setMaxSlippage(_maxSlippage);\n }\n\n function _setMaxSlippage(uint256 _maxSlippage) internal {\n require(_maxSlippage <= 5e16, \"Slippage must be less than 100%\");\n maxSlippage = _maxSlippage;\n emit MaxSlippageUpdated(_maxSlippage);\n }\n\n /**\n * @notice Approve the spending of all assets by their corresponding pool tokens,\n * if for some reason is it necessary.\n */\n function safeApproveAllTokens()\n external\n override\n onlyGovernor\n nonReentrant\n {\n _approveBase();\n }\n\n /**\n * @dev Since we are unwrapping WETH before depositing it to Curve\n * there is no need to set an approval for WETH on the Curve\n * pool\n * @param _asset Address of the asset\n * @param _pToken Address of the Curve LP token\n */\n // solhint-disable-next-line no-unused-vars\n function _abstractSetPToken(address _asset, address _pToken)\n internal\n override\n {}\n\n function _approveBase() internal {\n // Approve Curve pool for OTOKEN (required for adding liquidity)\n // slither-disable-next-line unused-return\n oToken.approve(platformAddress, type(uint256).max);\n\n // Approve Curve pool for hardAsset (required for adding liquidity)\n // slither-disable-next-line unused-return\n hardAsset.safeApprove(platformAddress, type(uint256).max);\n\n // Approve Curve gauge contract to transfer Curve pool LP tokens\n // This is needed for deposits if Curve pool LP tokens into the Curve gauge.\n // slither-disable-next-line unused-return\n lpToken.approve(address(gauge), type(uint256).max);\n }\n\n /**\n * @dev Returns the largest of two numbers int256 version\n */\n function _max(int256 a, int256 b) internal pure returns (int256) {\n return a >= b ? a : b;\n }\n}\n" + }, + "contracts/strategies/Generalized4626Strategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title Generalized 4626 Strategy\n * @notice Investment strategy for ERC-4626 Tokenized Vaults\n * @author Origin Protocol Inc\n */\nimport { IERC4626 } from \"../../lib/openzeppelin/interfaces/IERC4626.sol\";\nimport { IERC20, InitializableAbstractStrategy } from \"../utils/InitializableAbstractStrategy.sol\";\nimport { IDistributor } from \"../interfaces/IMerkl.sol\";\n\ncontract Generalized4626Strategy is InitializableAbstractStrategy {\n /// @notice The address of the Merkle Distributor contract.\n IDistributor public constant merkleDistributor =\n IDistributor(0x3Ef3D8bA38EBe18DB133cEc108f4D14CE00Dd9Ae);\n\n /// @dev Replaced with an immutable variable\n // slither-disable-next-line constable-states\n address private _deprecate_shareToken;\n /// @dev Replaced with an immutable variable\n // slither-disable-next-line constable-states\n address private _deprecate_assetToken;\n\n IERC20 public immutable shareToken;\n IERC20 public immutable assetToken;\n\n // For future use\n uint256[50] private __gap;\n\n event ClaimedRewards(address indexed token, uint256 amount);\n\n /**\n * @param _baseConfig Base strategy config with platformAddress (ERC-4626 Vault contract), eg sfrxETH or sDAI,\n * and vaultAddress (OToken Vault contract), eg VaultProxy or OETHVaultProxy\n * @param _assetToken Address of the ERC-4626 asset token. eg frxETH or DAI\n */\n constructor(BaseStrategyConfig memory _baseConfig, address _assetToken)\n InitializableAbstractStrategy(_baseConfig)\n {\n shareToken = IERC20(_baseConfig.platformAddress);\n assetToken = IERC20(_assetToken);\n }\n\n function initialize() external virtual onlyGovernor initializer {\n address[] memory rewardTokens = new address[](0);\n address[] memory assets = new address[](1);\n address[] memory pTokens = new address[](1);\n\n assets[0] = address(assetToken);\n pTokens[0] = address(platformAddress);\n\n InitializableAbstractStrategy._initialize(\n rewardTokens,\n assets,\n pTokens\n );\n }\n\n /**\n * @dev Deposit assets by converting them to shares\n * @param _asset Address of asset to deposit\n * @param _amount Amount of asset to deposit\n */\n function deposit(address _asset, uint256 _amount)\n external\n virtual\n override\n onlyVault\n nonReentrant\n {\n _deposit(_asset, _amount);\n }\n\n /**\n * @dev Deposit assets by converting them to shares\n * @param _asset Address of asset to deposit\n * @param _amount Amount of asset to deposit\n */\n function _deposit(address _asset, uint256 _amount) internal virtual {\n require(_amount > 0, \"Must deposit something\");\n require(_asset == address(assetToken), \"Unexpected asset address\");\n\n // slither-disable-next-line unused-return\n IERC4626(platformAddress).deposit(_amount, address(this));\n emit Deposit(_asset, address(shareToken), _amount);\n }\n\n /**\n * @dev Deposit the entire balance of assetToken to gain shareToken\n */\n function depositAll() external virtual override onlyVault nonReentrant {\n uint256 balance = assetToken.balanceOf(address(this));\n if (balance > 0) {\n _deposit(address(assetToken), balance);\n }\n }\n\n /**\n * @dev Withdraw asset by burning shares\n * @param _recipient Address to receive withdrawn asset\n * @param _asset Address of asset to withdraw\n * @param _amount Amount of asset to withdraw\n */\n function withdraw(\n address _recipient,\n address _asset,\n uint256 _amount\n ) external virtual override onlyVault nonReentrant {\n _withdraw(_recipient, _asset, _amount);\n }\n\n function _withdraw(\n address _recipient,\n address _asset,\n uint256 _amount\n ) internal virtual {\n require(_amount > 0, \"Must withdraw something\");\n require(_recipient != address(0), \"Must specify recipient\");\n require(_asset == address(assetToken), \"Unexpected asset address\");\n\n // slither-disable-next-line unused-return\n IERC4626(platformAddress).withdraw(_amount, _recipient, address(this));\n emit Withdrawal(_asset, address(shareToken), _amount);\n }\n\n /**\n * @dev Internal method to respond to the addition of new asset / share tokens\n */\n function _abstractSetPToken(address, address) internal virtual override {\n _approveBase();\n }\n\n /**\n * @dev Remove all assets from platform and send them to Vault contract.\n */\n function withdrawAll()\n external\n virtual\n override\n onlyVaultOrGovernor\n nonReentrant\n {\n uint256 shareBalance = shareToken.balanceOf(address(this));\n uint256 assetAmount = 0;\n if (shareBalance > 0) {\n assetAmount = IERC4626(platformAddress).redeem(\n shareBalance,\n vaultAddress,\n address(this)\n );\n emit Withdrawal(\n address(assetToken),\n address(shareToken),\n assetAmount\n );\n }\n }\n\n /**\n * @notice Get the total asset value held in the platform\n * @param _asset Address of the asset\n * @return balance Total value of the asset in the platform\n */\n function checkBalance(address _asset)\n public\n view\n virtual\n override\n returns (uint256 balance)\n {\n require(_asset == address(assetToken), \"Unexpected asset address\");\n /* We are intentionally not counting the amount of assetToken parked on the\n * contract toward the checkBalance. The deposit and withdraw functions\n * should not result in assetToken being unused and owned by this strategy\n * contract.\n */\n IERC4626 platform = IERC4626(platformAddress);\n return platform.previewRedeem(platform.balanceOf(address(this)));\n }\n\n /**\n * @notice Governor approves the ERC-4626 Tokenized Vault to spend the asset.\n */\n function safeApproveAllTokens() external override onlyGovernor {\n _approveBase();\n }\n\n function _approveBase() internal virtual {\n // Approval the asset to be transferred to the ERC-4626 Tokenized Vault.\n // Used by the ERC-4626 deposit() and mint() functions\n // slither-disable-next-line unused-return\n assetToken.approve(platformAddress, type(uint256).max);\n }\n\n /**\n * @dev Returns bool indicating whether asset is supported by strategy\n * @param _asset Address of the asset\n */\n function supportsAsset(address _asset)\n public\n view\n virtual\n override\n returns (bool)\n {\n return _asset == address(assetToken);\n }\n\n /**\n * @notice is not supported for this strategy as the asset and\n * ERC-4626 Tokenized Vault are set at deploy time.\n * @dev If the ERC-4626 Tokenized Vault needed to be changed, a new\n * contract would need to be deployed and the proxy updated.\n */\n function setPTokenAddress(address, address) external override onlyGovernor {\n revert(\"unsupported function\");\n }\n\n /**\n * @notice is not supported for this strategy as the asset and\n * ERC-4626 Tokenized Vault are set at deploy time.\n * @dev If the ERC-4626 Tokenized Vault needed to be changed, a new\n * contract would need to be deployed and the proxy updated.\n */\n function removePToken(uint256) external override onlyGovernor {\n revert(\"unsupported function\");\n }\n\n /// @notice Claim tokens from the Merkle Distributor\n /// @param token The address of the token to claim.\n /// @param amount The amount of tokens to claim.\n /// @param proof The Merkle proof to validate the claim.\n function merkleClaim(\n address token,\n uint256 amount,\n bytes32[] calldata proof\n ) external {\n address[] memory users = new address[](1);\n users[0] = address(this);\n\n address[] memory tokens = new address[](1);\n tokens[0] = token;\n\n uint256[] memory amounts = new uint256[](1);\n amounts[0] = amount;\n\n bytes32[][] memory proofs = new bytes32[][](1);\n proofs[0] = proof;\n\n merkleDistributor.claim(users, tokens, amounts, proofs);\n\n emit ClaimedRewards(token, amount);\n }\n}\n" + }, + "contracts/strategies/Generalized4626USDTStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\ninterface IUSDT {\n // Tether's approve does not return a bool like standard IERC20 contracts\n // slither-disable-next-line erc20-interface\n function approve(address _spender, uint256 _value) external;\n}\n\n/**\n * @title Generalized 4626 Strategy when asset is Tether USD (USDT)\n * @notice Investment strategy for ERC-4626 Tokenized Vaults for the USDT asset.\n * @author Origin Protocol Inc\n */\nimport { Generalized4626Strategy } from \"./Generalized4626Strategy.sol\";\n\ncontract Generalized4626USDTStrategy is Generalized4626Strategy {\n /**\n * @param _baseConfig Base strategy config with platformAddress (ERC-4626 Vault contract), eg sfrxETH or sDAI,\n * and vaultAddress (OToken Vault contract), eg VaultProxy or OETHVaultProxy\n * @param _assetToken Address of the ERC-4626 asset token. eg frxETH or DAI\n */\n constructor(BaseStrategyConfig memory _baseConfig, address _assetToken)\n Generalized4626Strategy(_baseConfig, _assetToken)\n {}\n\n /// @dev Override for Tether as USDT does not return a bool on approve.\n /// Using assetToken.approve will fail as it expects a bool return value\n function _approveBase() internal virtual override {\n // Approval the asset to be transferred to the ERC-4626 Tokenized Vault.\n // Used by the ERC-4626 deposit() and mint() functions\n // slither-disable-next-line unused-return\n IUSDT(address(assetToken)).approve(platformAddress, type(uint256).max);\n }\n}\n" + }, + "contracts/strategies/IAave.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @dev Interface for Aaves Lending Pool\n * Documentation: https://developers.aave.com/#lendingpool\n */\ninterface IAaveLendingPool {\n /**\n * @dev Deposits an `amount` of underlying asset into the reserve, receiving in return overlying aTokens.\n * - E.g. User deposits 100 USDC and gets in return 100 aUSDC\n * @param asset The address of the underlying asset to deposit\n * @param amount The amount to be deposited\n * @param onBehalfOf The address that will receive the aTokens, same as msg.sender if the user\n * wants to receive them on his own wallet, or a different address if the beneficiary of aTokens\n * is a different wallet\n * @param referralCode Code used to register the integrator originating the operation, for potential rewards.\n * 0 if the action is executed directly by the user, without any middle-man\n **/\n function deposit(\n address asset,\n uint256 amount,\n address onBehalfOf,\n uint16 referralCode\n ) external;\n\n /**\n * @dev Withdraws an `amount` of underlying asset from the reserve, burning the equivalent aTokens owned\n * E.g. User has 100 aUSDC, calls withdraw() and receives 100 USDC, burning the 100 aUSDC\n * @param asset The address of the underlying asset to withdraw\n * @param amount The underlying amount to be withdrawn\n * - Send the value type(uint256).max in order to withdraw the whole aToken balance\n * @param to Address that will receive the underlying, same as msg.sender if the user\n * wants to receive it on his own wallet, or a different address if the beneficiary is a\n * different wallet\n * @return The final amount withdrawn\n **/\n function withdraw(\n address asset,\n uint256 amount,\n address to\n ) external returns (uint256);\n}\n\n/**\n * @dev Interface for Aaves Lending Pool\n * Documentation: https://developers.aave.com/#lendingpooladdressesprovider\n */\ninterface ILendingPoolAddressesProvider {\n /**\n * @notice Get the current address for Aave LendingPool\n * @dev Lending pool is the core contract on which to call deposit\n */\n function getLendingPool() external view returns (address);\n}\n" + }, + "contracts/strategies/IAaveIncentivesController.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\ninterface IAaveIncentivesController {\n event RewardsAccrued(address indexed user, uint256 amount);\n\n event RewardsClaimed(\n address indexed user,\n address indexed to,\n uint256 amount\n );\n\n event RewardsClaimed(\n address indexed user,\n address indexed to,\n address indexed claimer,\n uint256 amount\n );\n\n event ClaimerSet(address indexed user, address indexed claimer);\n\n /*\n * @dev Returns the configuration of the distribution for a certain asset\n * @param asset The address of the reference asset of the distribution\n * @return The asset index, the emission per second and the last updated timestamp\n **/\n function getAssetData(address asset)\n external\n view\n returns (\n uint256,\n uint256,\n uint256\n );\n\n /**\n * @dev Whitelists an address to claim the rewards on behalf of another address\n * @param user The address of the user\n * @param claimer The address of the claimer\n */\n function setClaimer(address user, address claimer) external;\n\n /**\n * @dev Returns the whitelisted claimer for a certain address (0x0 if not set)\n * @param user The address of the user\n * @return The claimer address\n */\n function getClaimer(address user) external view returns (address);\n\n /**\n * @dev Configure assets for a certain rewards emission\n * @param assets The assets to incentivize\n * @param emissionsPerSecond The emission for each asset\n */\n function configureAssets(\n address[] calldata assets,\n uint256[] calldata emissionsPerSecond\n ) external;\n\n /**\n * @dev Called by the corresponding asset on any update that affects the rewards distribution\n * @param asset The address of the user\n * @param userBalance The balance of the user of the asset in the lending pool\n * @param totalSupply The total supply of the asset in the lending pool\n **/\n function handleAction(\n address asset,\n uint256 userBalance,\n uint256 totalSupply\n ) external;\n\n /**\n * @dev Returns the total of rewards of an user, already accrued + not yet accrued\n * @param user The address of the user\n * @return The rewards\n **/\n function getRewardsBalance(address[] calldata assets, address user)\n external\n view\n returns (uint256);\n\n /**\n * @dev Claims reward for an user, on all the assets of the lending pool,\n * accumulating the pending rewards\n * @param amount Amount of rewards to claim\n * @param to Address that will be receiving the rewards\n * @return Rewards claimed\n **/\n function claimRewards(\n address[] calldata assets,\n uint256 amount,\n address to\n ) external returns (uint256);\n\n /**\n * @dev Claims reward for an user on behalf, on all the assets of the\n * lending pool, accumulating the pending rewards. The caller must\n * be whitelisted via \"allowClaimOnBehalf\" function by the RewardsAdmin role manager\n * @param amount Amount of rewards to claim\n * @param user Address to check and claim rewards\n * @param to Address that will be receiving the rewards\n * @return Rewards claimed\n **/\n function claimRewardsOnBehalf(\n address[] calldata assets,\n uint256 amount,\n address user,\n address to\n ) external returns (uint256);\n\n /**\n * @dev returns the unclaimed rewards of the user\n * @param user the address of the user\n * @return the unclaimed user rewards\n */\n function getUserUnclaimedRewards(address user)\n external\n view\n returns (uint256);\n\n /**\n * @dev returns the unclaimed rewards of the user\n * @param user the address of the user\n * @param asset The asset to incentivize\n * @return the user index for the asset\n */\n function getUserAssetData(address user, address asset)\n external\n view\n returns (uint256);\n\n /**\n * @dev for backward compatibility with previous implementation of the Incentives controller\n */\n function REWARD_TOKEN() external view returns (address);\n\n /**\n * @dev for backward compatibility with previous implementation of the Incentives controller\n */\n function PRECISION() external view returns (uint8);\n\n /**\n * @dev Gets the distribution end timestamp of the emissions\n */\n function DISTRIBUTION_END() external view returns (uint256);\n}\n" + }, + "contracts/strategies/IAaveStakeToken.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\ninterface IAaveStakedToken {\n function COOLDOWN_SECONDS() external returns (uint256);\n\n function UNSTAKE_WINDOW() external returns (uint256);\n\n function balanceOf(address addr) external returns (uint256);\n\n function redeem(address to, uint256 amount) external;\n\n function stakersCooldowns(address addr) external returns (uint256);\n\n function cooldown() external;\n}\n" + }, + "contracts/strategies/ICompound.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @dev Compound C Token interface\n * Documentation: https://compound.finance/developers/ctokens\n */\ninterface ICERC20 {\n /**\n * @notice The mint function transfers an asset into the protocol, which begins accumulating\n * interest based on the current Supply Rate for the asset. The user receives a quantity of\n * cTokens equal to the underlying tokens supplied, divided by the current Exchange Rate.\n * @param mintAmount The amount of the asset to be supplied, in units of the underlying asset.\n * @return 0 on success, otherwise an Error codes\n */\n function mint(uint256 mintAmount) external returns (uint256);\n\n /**\n * @notice Sender redeems cTokens in exchange for the underlying asset\n * @dev Accrues interest whether or not the operation succeeds, unless reverted\n * @param redeemTokens The number of cTokens to redeem into underlying\n * @return uint 0=success, otherwise an error code.\n */\n function redeem(uint256 redeemTokens) external returns (uint256);\n\n /**\n * @notice The redeem underlying function converts cTokens into a specified quantity of the underlying\n * asset, and returns them to the user. The amount of cTokens redeemed is equal to the quantity of\n * underlying tokens received, divided by the current Exchange Rate. The amount redeemed must be less\n * than the user's Account Liquidity and the market's available liquidity.\n * @param redeemAmount The amount of underlying to be redeemed.\n * @return 0 on success, otherwise an error code.\n */\n function redeemUnderlying(uint256 redeemAmount) external returns (uint256);\n\n /**\n * @notice The user's underlying balance, representing their assets in the protocol, is equal to\n * the user's cToken balance multiplied by the Exchange Rate.\n * @param owner The account to get the underlying balance of.\n * @return The amount of underlying currently owned by the account.\n */\n function balanceOfUnderlying(address owner) external returns (uint256);\n\n /**\n * @notice Calculates the exchange rate from the underlying to the CToken\n * @dev This function does not accrue interest before calculating the exchange rate\n * @return Calculated exchange rate scaled by 1e18\n */\n function exchangeRateStored() external view returns (uint256);\n\n /**\n * @notice Get the token balance of the `owner`\n * @param owner The address of the account to query\n * @return The number of tokens owned by `owner`\n */\n function balanceOf(address owner) external view returns (uint256);\n\n /**\n * @notice Get the supply rate per block for supplying the token to Compound.\n */\n function supplyRatePerBlock() external view returns (uint256);\n\n /**\n * @notice Address of the Compound Comptroller.\n */\n function comptroller() external view returns (address);\n}\n" + }, + "contracts/strategies/IConvexDeposits.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\ninterface IConvexDeposits {\n function deposit(\n uint256 _pid,\n uint256 _amount,\n bool _stake\n ) external returns (bool);\n\n function deposit(\n uint256 _amount,\n bool _lock,\n address _stakeAddress\n ) external;\n}\n" + }, + "contracts/strategies/ICurveETHPoolV1.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\ninterface ICurveETHPoolV1 {\n event AddLiquidity(\n address indexed provider,\n uint256[2] token_amounts,\n uint256[2] fees,\n uint256 invariant,\n uint256 token_supply\n );\n event ApplyNewFee(uint256 fee);\n event Approval(\n address indexed owner,\n address indexed spender,\n uint256 value\n );\n event CommitNewFee(uint256 new_fee);\n event RampA(\n uint256 old_A,\n uint256 new_A,\n uint256 initial_time,\n uint256 future_time\n );\n event RemoveLiquidity(\n address indexed provider,\n uint256[2] token_amounts,\n uint256[2] fees,\n uint256 token_supply\n );\n event RemoveLiquidityImbalance(\n address indexed provider,\n uint256[2] token_amounts,\n uint256[2] fees,\n uint256 invariant,\n uint256 token_supply\n );\n event RemoveLiquidityOne(\n address indexed provider,\n uint256 token_amount,\n uint256 coin_amount,\n uint256 token_supply\n );\n event StopRampA(uint256 A, uint256 t);\n event TokenExchange(\n address indexed buyer,\n int128 sold_id,\n uint256 tokens_sold,\n int128 bought_id,\n uint256 tokens_bought\n );\n event Transfer(\n address indexed sender,\n address indexed receiver,\n uint256 value\n );\n\n function A() external view returns (uint256);\n\n function A_precise() external view returns (uint256);\n\n function DOMAIN_SEPARATOR() external view returns (bytes32);\n\n function add_liquidity(uint256[2] memory _amounts, uint256 _min_mint_amount)\n external\n payable\n returns (uint256);\n\n function add_liquidity(\n uint256[2] memory _amounts,\n uint256 _min_mint_amount,\n address _receiver\n ) external payable returns (uint256);\n\n function admin_action_deadline() external view returns (uint256);\n\n function admin_balances(uint256 i) external view returns (uint256);\n\n function admin_fee() external view returns (uint256);\n\n function allowance(address arg0, address arg1)\n external\n view\n returns (uint256);\n\n function apply_new_fee() external;\n\n function approve(address _spender, uint256 _value) external returns (bool);\n\n function balanceOf(address arg0) external view returns (uint256);\n\n function balances(uint256 arg0) external view returns (uint256);\n\n function calc_token_amount(uint256[2] memory _amounts, bool _is_deposit)\n external\n view\n returns (uint256);\n\n function calc_withdraw_one_coin(uint256 _burn_amount, int128 i)\n external\n view\n returns (uint256);\n\n function coins(uint256 arg0) external view returns (address);\n\n function commit_new_fee(uint256 _new_fee) external;\n\n function decimals() external view returns (uint256);\n\n function ema_price() external view returns (uint256);\n\n function exchange(\n int128 i,\n int128 j,\n uint256 _dx,\n uint256 _min_dy\n ) external payable returns (uint256);\n\n function exchange(\n int128 i,\n int128 j,\n uint256 _dx,\n uint256 _min_dy,\n address _receiver\n ) external payable returns (uint256);\n\n function fee() external view returns (uint256);\n\n function future_A() external view returns (uint256);\n\n function future_A_time() external view returns (uint256);\n\n function future_fee() external view returns (uint256);\n\n function get_balances() external view returns (uint256[2] memory);\n\n function get_dy(\n int128 i,\n int128 j,\n uint256 dx\n ) external view returns (uint256);\n\n function get_p() external view returns (uint256);\n\n function get_virtual_price() external view returns (uint256);\n\n function initial_A() external view returns (uint256);\n\n function initial_A_time() external view returns (uint256);\n\n function initialize(\n string memory _name,\n string memory _symbol,\n address[4] memory _coins,\n uint256[4] memory _rate_multipliers,\n uint256 _A,\n uint256 _fee\n ) external;\n\n function last_price() external view returns (uint256);\n\n function ma_exp_time() external view returns (uint256);\n\n function ma_last_time() external view returns (uint256);\n\n function name() external view returns (string memory);\n\n function nonces(address arg0) external view returns (uint256);\n\n function permit(\n address _owner,\n address _spender,\n uint256 _value,\n uint256 _deadline,\n uint8 _v,\n bytes32 _r,\n bytes32 _s\n ) external returns (bool);\n\n function price_oracle() external view returns (uint256);\n\n function ramp_A(uint256 _future_A, uint256 _future_time) external;\n\n function remove_liquidity(\n uint256 _burn_amount,\n uint256[2] memory _min_amounts\n ) external returns (uint256[2] memory);\n\n function remove_liquidity(\n uint256 _burn_amount,\n uint256[2] memory _min_amounts,\n address _receiver\n ) external returns (uint256[2] memory);\n\n function remove_liquidity_imbalance(\n uint256[2] memory _amounts,\n uint256 _max_burn_amount\n ) external returns (uint256);\n\n function remove_liquidity_imbalance(\n uint256[2] memory _amounts,\n uint256 _max_burn_amount,\n address _receiver\n ) external returns (uint256);\n\n function remove_liquidity_one_coin(\n uint256 _burn_amount,\n int128 i,\n uint256 _min_received\n ) external returns (uint256);\n\n function remove_liquidity_one_coin(\n uint256 _burn_amount,\n int128 i,\n uint256 _min_received,\n address _receiver\n ) external returns (uint256);\n\n function set_ma_exp_time(uint256 _ma_exp_time) external;\n\n function stop_ramp_A() external;\n\n function symbol() external view returns (string memory);\n\n function totalSupply() external view returns (uint256);\n\n function transfer(address _to, uint256 _value) external returns (bool);\n\n function transferFrom(\n address _from,\n address _to,\n uint256 _value\n ) external returns (bool);\n\n function version() external view returns (string memory);\n\n function withdraw_admin_fees() external;\n}\n" + }, + "contracts/strategies/ICurveMetaPool.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.4;\n\ninterface ICurveMetaPool {\n function add_liquidity(uint256[2] calldata amounts, uint256 min_mint_amount)\n external\n returns (uint256);\n\n function get_virtual_price() external view returns (uint256);\n\n function remove_liquidity(uint256 _amount, uint256[2] calldata min_amounts)\n external\n returns (uint256[2] calldata);\n\n function remove_liquidity_one_coin(\n uint256 _token_amount,\n int128 i,\n uint256 min_amount\n ) external returns (uint256);\n\n function remove_liquidity_imbalance(\n uint256[2] calldata amounts,\n uint256 max_burn_amount\n ) external returns (uint256);\n\n function calc_withdraw_one_coin(uint256 _token_amount, int128 i)\n external\n view\n returns (uint256);\n\n function balances(uint256 i) external view returns (uint256);\n\n function calc_token_amount(uint256[2] calldata amounts, bool deposit)\n external\n view\n returns (uint256);\n\n function base_pool() external view returns (address);\n\n function fee() external view returns (uint256);\n\n function coins(uint256 i) external view returns (address);\n\n function exchange(\n int128 i,\n int128 j,\n uint256 dx,\n uint256 min_dy\n ) external returns (uint256);\n\n function get_dy(\n int128 i,\n int128 j,\n uint256 dx\n ) external view returns (uint256);\n}\n" + }, + "contracts/strategies/ICurvePool.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\ninterface ICurvePool {\n function get_virtual_price() external view returns (uint256);\n\n function add_liquidity(uint256[3] calldata _amounts, uint256 _min) external;\n\n function balances(uint256) external view returns (uint256);\n\n function calc_token_amount(uint256[3] calldata _amounts, bool _deposit)\n external\n returns (uint256);\n\n function fee() external view returns (uint256);\n\n function remove_liquidity_one_coin(\n uint256 _amount,\n int128 _index,\n uint256 _minAmount\n ) external;\n\n function remove_liquidity(\n uint256 _amount,\n uint256[3] calldata _minWithdrawAmounts\n ) external;\n\n function calc_withdraw_one_coin(uint256 _amount, int128 _index)\n external\n view\n returns (uint256);\n\n function exchange(\n uint256 i,\n uint256 j,\n uint256 dx,\n uint256 min_dy\n ) external returns (uint256);\n\n function coins(uint256 _index) external view returns (address);\n\n function remove_liquidity_imbalance(\n uint256[3] calldata _amounts,\n uint256 maxBurnAmount\n ) external;\n}\n" + }, + "contracts/strategies/IRewardStaking.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\ninterface IRewardStaking {\n function stakeFor(address, uint256) external;\n\n function stake(uint256) external;\n\n function withdraw(uint256 amount, bool claim) external;\n\n function withdrawAndUnwrap(uint256 amount, bool claim) external;\n\n function earned(address account) external view returns (uint256);\n\n function getReward() external;\n\n function getReward(address _account, bool _claimExtras) external;\n\n function extraRewardsLength() external returns (uint256);\n\n function extraRewards(uint256 _pid) external returns (address);\n\n function rewardToken() external returns (address);\n\n function balanceOf(address account) external view returns (uint256);\n}\n" + }, + "contracts/strategies/MorphoAaveStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title OUSD Morpho Aave Strategy\n * @notice Investment strategy for investing stablecoins via Morpho (Aave)\n * @author Origin Protocol Inc\n */\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport { IERC20, InitializableAbstractStrategy } from \"../utils/InitializableAbstractStrategy.sol\";\nimport { IMorpho } from \"../interfaces/morpho/IMorpho.sol\";\nimport { ILens } from \"../interfaces/morpho/ILens.sol\";\nimport { StableMath } from \"../utils/StableMath.sol\";\n\ncontract MorphoAaveStrategy is InitializableAbstractStrategy {\n address public constant MORPHO = 0x777777c9898D384F785Ee44Acfe945efDFf5f3E0;\n address public constant LENS = 0x507fA343d0A90786d86C7cd885f5C49263A91FF4;\n\n using SafeERC20 for IERC20;\n using StableMath for uint256;\n\n constructor(BaseStrategyConfig memory _stratConfig)\n InitializableAbstractStrategy(_stratConfig)\n {}\n\n /**\n * @dev Initialize function, to set up initial internal state\n * @param _rewardTokenAddresses Address of reward token for platform\n * @param _assets Addresses of initial supported assets\n * @param _pTokens Platform Token corresponding addresses\n */\n function initialize(\n address[] calldata _rewardTokenAddresses,\n address[] calldata _assets,\n address[] calldata _pTokens\n ) external onlyGovernor initializer {\n InitializableAbstractStrategy._initialize(\n _rewardTokenAddresses,\n _assets,\n _pTokens\n );\n }\n\n /**\n * @dev Approve the spending of all assets by main Morpho contract,\n * if for some reason is it necessary.\n */\n function safeApproveAllTokens()\n external\n override\n onlyGovernor\n nonReentrant\n {\n uint256 assetCount = assetsMapped.length;\n for (uint256 i = 0; i < assetCount; i++) {\n address asset = assetsMapped[i];\n\n // Safe approval\n IERC20(asset).safeApprove(MORPHO, 0);\n IERC20(asset).safeApprove(MORPHO, type(uint256).max);\n }\n }\n\n /**\n * @dev Internal method to respond to the addition of new asset\n * We need to approve and allow Morpho to move them\n * @param _asset Address of the asset to approve\n * @param _pToken The pToken for the approval\n */\n // solhint-disable-next-line no-unused-vars\n function _abstractSetPToken(address _asset, address _pToken)\n internal\n override\n {\n IERC20(_asset).safeApprove(MORPHO, 0);\n IERC20(_asset).safeApprove(MORPHO, type(uint256).max);\n }\n\n /**\n * @dev Collect accumulated rewards and send them to Harvester.\n */\n function collectRewardTokens()\n external\n override\n onlyHarvester\n nonReentrant\n {\n // Morpho Aave-v2 doesn't distribute reward tokens\n // solhint-disable-next-line max-line-length\n // Ref: https://developers.morpho.xyz/interact-with-morpho/get-started/interact-with-morpho/claim-rewards#morpho-aave-v2\n }\n\n /**\n * @dev Get the amount of rewards pending to be collected from the protocol\n */\n function getPendingRewards() external view returns (uint256 balance) {\n // Morpho Aave-v2 doesn't distribute reward tokens\n // solhint-disable-next-line max-line-length\n // Ref: https://developers.morpho.xyz/interact-with-morpho/get-started/interact-with-morpho/claim-rewards#morpho-aave-v2\n return 0;\n }\n\n /**\n * @dev Deposit asset into Morpho\n * @param _asset Address of asset to deposit\n * @param _amount Amount of asset to deposit\n */\n function deposit(address _asset, uint256 _amount)\n external\n override\n onlyVault\n nonReentrant\n {\n _deposit(_asset, _amount);\n }\n\n /**\n * @dev Deposit asset into Morpho\n * @param _asset Address of asset to deposit\n * @param _amount Amount of asset to deposit\n */\n function _deposit(address _asset, uint256 _amount) internal {\n require(_amount > 0, \"Must deposit something\");\n\n address pToken = address(_getPTokenFor(_asset));\n\n IMorpho(MORPHO).supply(\n pToken,\n address(this), // the address of the user you want to supply on behalf of\n _amount\n );\n emit Deposit(_asset, pToken, _amount);\n }\n\n /**\n * @dev Deposit the entire balance of any supported asset into Morpho\n */\n function depositAll() external override onlyVault nonReentrant {\n for (uint256 i = 0; i < assetsMapped.length; i++) {\n uint256 balance = IERC20(assetsMapped[i]).balanceOf(address(this));\n if (balance > 0) {\n _deposit(assetsMapped[i], balance);\n }\n }\n }\n\n /**\n * @dev Withdraw asset from Morpho\n * @param _recipient Address to receive withdrawn asset\n * @param _asset Address of asset to withdraw\n * @param _amount Amount of asset to withdraw\n */\n function withdraw(\n address _recipient,\n address _asset,\n uint256 _amount\n ) external override onlyVault nonReentrant {\n _withdraw(_recipient, _asset, _amount);\n }\n\n function _withdraw(\n address _recipient,\n address _asset,\n uint256 _amount\n ) internal {\n require(_amount > 0, \"Must withdraw something\");\n require(_recipient != address(0), \"Must specify recipient\");\n\n address pToken = address(_getPTokenFor(_asset));\n\n IMorpho(MORPHO).withdraw(pToken, _amount);\n emit Withdrawal(_asset, pToken, _amount);\n IERC20(_asset).safeTransfer(_recipient, _amount);\n }\n\n /**\n * @dev Remove all assets from platform and send them to Vault contract.\n */\n function withdrawAll() external override onlyVaultOrGovernor nonReentrant {\n for (uint256 i = 0; i < assetsMapped.length; i++) {\n uint256 balance = _checkBalance(assetsMapped[i]);\n if (balance > 0) {\n _withdraw(vaultAddress, assetsMapped[i], balance);\n }\n }\n }\n\n /**\n * @dev Return total value of an asset held in the platform\n * @param _asset Address of the asset\n * @return balance Total value of the asset in the platform\n */\n function checkBalance(address _asset)\n external\n view\n override\n returns (uint256 balance)\n {\n return _checkBalance(_asset);\n }\n\n function _checkBalance(address _asset)\n internal\n view\n returns (uint256 balance)\n {\n address pToken = address(_getPTokenFor(_asset));\n\n // Total value represented by decimal position of underlying token\n (, , balance) = ILens(LENS).getCurrentSupplyBalanceInOf(\n pToken,\n address(this)\n );\n }\n\n /**\n * @dev Retuns bool indicating whether asset is supported by strategy\n * @param _asset Address of the asset\n */\n function supportsAsset(address _asset) public view override returns (bool) {\n return assetToPToken[_asset] != address(0);\n }\n\n /**\n * @dev Get the pToken wrapped in the IERC20 interface for this asset.\n * Fails if the pToken doesn't exist in our mappings.\n * @param _asset Address of the asset\n * @return pToken Corresponding pToken to this asset\n */\n function _getPTokenFor(address _asset) internal view returns (IERC20) {\n address pToken = assetToPToken[_asset];\n require(pToken != address(0), \"pToken does not exist\");\n return IERC20(pToken);\n }\n}\n" + }, + "contracts/strategies/MorphoCompoundStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title OUSD Morpho Compound Strategy\n * @notice Investment strategy for investing stablecoins via Morpho (Compound)\n * @author Origin Protocol Inc\n */\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport { IERC20, AbstractCompoundStrategy, InitializableAbstractStrategy } from \"./AbstractCompoundStrategy.sol\";\nimport { IMorpho } from \"../interfaces/morpho/IMorpho.sol\";\nimport { ILens } from \"../interfaces/morpho/ILens.sol\";\nimport { StableMath } from \"../utils/StableMath.sol\";\nimport \"../utils/Helpers.sol\";\n\ncontract MorphoCompoundStrategy is AbstractCompoundStrategy {\n address public constant MORPHO = 0x8888882f8f843896699869179fB6E4f7e3B58888;\n address public constant LENS = 0x930f1b46e1D081Ec1524efD95752bE3eCe51EF67;\n using SafeERC20 for IERC20;\n using StableMath for uint256;\n\n constructor(BaseStrategyConfig memory _stratConfig)\n InitializableAbstractStrategy(_stratConfig)\n {}\n\n /**\n * @dev Initialize function, to set up initial internal state\n * @param _rewardTokenAddresses Address of reward token for platform\n * @param _assets Addresses of initial supported assets\n * @param _pTokens Platform Token corresponding addresses\n */\n function initialize(\n address[] calldata _rewardTokenAddresses,\n address[] calldata _assets,\n address[] calldata _pTokens\n ) external onlyGovernor initializer {\n InitializableAbstractStrategy._initialize(\n _rewardTokenAddresses,\n _assets,\n _pTokens\n );\n }\n\n /**\n * @dev Approve the spending of all assets by main Morpho contract,\n * if for some reason is it necessary.\n */\n function safeApproveAllTokens()\n external\n override\n onlyGovernor\n nonReentrant\n {\n uint256 assetCount = assetsMapped.length;\n for (uint256 i = 0; i < assetCount; i++) {\n address asset = assetsMapped[i];\n\n // Safe approval\n IERC20(asset).safeApprove(MORPHO, 0);\n IERC20(asset).safeApprove(MORPHO, type(uint256).max);\n }\n }\n\n /**\n * @dev Internal method to respond to the addition of new asset\n * We need to approve and allow Morpho to move them\n * @param _asset Address of the asset to approve\n * @param _pToken The pToken for the approval\n */\n // solhint-disable-next-line no-unused-vars\n function _abstractSetPToken(address _asset, address _pToken)\n internal\n override\n {\n IERC20(_asset).safeApprove(MORPHO, 0);\n IERC20(_asset).safeApprove(MORPHO, type(uint256).max);\n }\n\n /**\n * @dev Collect accumulated rewards and send them to Harvester.\n */\n function collectRewardTokens()\n external\n override\n onlyHarvester\n nonReentrant\n {\n /**\n * Gas considerations. We could query Morpho LENS's `getUserUnclaimedRewards` for each\n * cToken separately and only claimRewards where it is economically feasible. Each call\n * (out of 3) costs ~60k gas extra.\n *\n * Each extra cToken in the `poolTokens` of `claimRewards` function makes that call\n * 89-120k more expensive gas wise.\n *\n * With Lens query in case where:\n * - there is only 1 reward token to collect. Net gas usage in best case would be\n * 3*60 - 2*120 = -60k -> saving 60k gas\n * - there are 2 reward tokens to collect. Net gas usage in best case would be\n * 3*60 - 120 = 60k -> more expensive for 60k gas\n * - there are 3 reward tokens to collect. Net gas usage in best case would be\n * 3*60 = 180k -> more expensive for 180k gas\n *\n * For the above reasoning such \"optimization\" is not implemented\n */\n\n address[] memory poolTokens = new address[](assetsMapped.length);\n for (uint256 i = 0; i < assetsMapped.length; i++) {\n poolTokens[i] = assetToPToken[assetsMapped[i]];\n }\n\n // slither-disable-next-line unused-return\n IMorpho(MORPHO).claimRewards(\n poolTokens, // The addresses of the underlying protocol's pools to claim rewards from\n false // Whether to trade the accrued rewards for MORPHO token, with a premium\n );\n\n // Transfer COMP to Harvester\n IERC20 rewardToken = IERC20(rewardTokenAddresses[0]);\n uint256 balance = rewardToken.balanceOf(address(this));\n emit RewardTokenCollected(\n harvesterAddress,\n rewardTokenAddresses[0],\n balance\n );\n rewardToken.safeTransfer(harvesterAddress, balance);\n }\n\n /**\n * @dev Get the amount of rewards pending to be collected from the protocol\n */\n function getPendingRewards() external view returns (uint256 balance) {\n address[] memory poolTokens = new address[](assetsMapped.length);\n for (uint256 i = 0; i < assetsMapped.length; i++) {\n poolTokens[i] = assetToPToken[assetsMapped[i]];\n }\n\n return ILens(LENS).getUserUnclaimedRewards(poolTokens, address(this));\n }\n\n /**\n * @dev Deposit asset into Morpho\n * @param _asset Address of asset to deposit\n * @param _amount Amount of asset to deposit\n */\n function deposit(address _asset, uint256 _amount)\n external\n override\n onlyVault\n nonReentrant\n {\n _deposit(_asset, _amount);\n }\n\n /**\n * @dev Deposit asset into Morpho\n * @param _asset Address of asset to deposit\n * @param _amount Amount of asset to deposit\n */\n function _deposit(address _asset, uint256 _amount) internal {\n require(_amount > 0, \"Must deposit something\");\n\n IMorpho(MORPHO).supply(\n address(_getCTokenFor(_asset)),\n address(this), // the address of the user you want to supply on behalf of\n _amount\n );\n emit Deposit(_asset, address(_getCTokenFor(_asset)), _amount);\n }\n\n /**\n * @dev Deposit the entire balance of any supported asset into Morpho\n */\n function depositAll() external override onlyVault nonReentrant {\n for (uint256 i = 0; i < assetsMapped.length; i++) {\n uint256 balance = IERC20(assetsMapped[i]).balanceOf(address(this));\n if (balance > 0) {\n _deposit(assetsMapped[i], balance);\n }\n }\n }\n\n /**\n * @dev Withdraw asset from Morpho\n * @param _recipient Address to receive withdrawn asset\n * @param _asset Address of asset to withdraw\n * @param _amount Amount of asset to withdraw\n */\n function withdraw(\n address _recipient,\n address _asset,\n uint256 _amount\n ) external override onlyVault nonReentrant {\n _withdraw(_recipient, _asset, _amount);\n }\n\n function _withdraw(\n address _recipient,\n address _asset,\n uint256 _amount\n ) internal {\n require(_amount > 0, \"Must withdraw something\");\n require(_recipient != address(0), \"Must specify recipient\");\n\n address pToken = assetToPToken[_asset];\n\n IMorpho(MORPHO).withdraw(pToken, _amount);\n emit Withdrawal(_asset, address(_getCTokenFor(_asset)), _amount);\n IERC20(_asset).safeTransfer(_recipient, _amount);\n }\n\n /**\n * @dev Remove all assets from platform and send them to Vault contract.\n */\n function withdrawAll() external override onlyVaultOrGovernor nonReentrant {\n for (uint256 i = 0; i < assetsMapped.length; i++) {\n uint256 balance = _checkBalance(assetsMapped[i]);\n if (balance > 0) {\n _withdraw(vaultAddress, assetsMapped[i], balance);\n }\n }\n }\n\n /**\n * @dev Return total value of an asset held in the platform\n * @param _asset Address of the asset\n * @return balance Total value of the asset in the platform\n */\n function checkBalance(address _asset)\n external\n view\n override\n returns (uint256 balance)\n {\n return _checkBalance(_asset);\n }\n\n function _checkBalance(address _asset)\n internal\n view\n returns (uint256 balance)\n {\n address pToken = assetToPToken[_asset];\n\n // Total value represented by decimal position of underlying token\n (, , balance) = ILens(LENS).getCurrentSupplyBalanceInOf(\n pToken,\n address(this)\n );\n }\n}\n" + }, + "contracts/strategies/NativeStaking/CompoundingStakingSSVStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\n\nimport { InitializableAbstractStrategy } from \"../../utils/InitializableAbstractStrategy.sol\";\nimport { IWETH9 } from \"../../interfaces/IWETH9.sol\";\nimport { CompoundingValidatorManager } from \"./CompoundingValidatorManager.sol\";\n\n/// @title Compounding Staking SSV Strategy\n/// @notice Strategy to deploy funds into DVT validators powered by the SSV Network\n/// @author Origin Protocol Inc\ncontract CompoundingStakingSSVStrategy is\n CompoundingValidatorManager,\n InitializableAbstractStrategy\n{\n /// @notice SSV ERC20 token that serves as a payment for operating SSV validators\n address public immutable SSV_TOKEN;\n\n // For future use\n uint256[50] private __gap;\n\n /// @param _baseConfig Base strategy config with\n /// `platformAddress` not used so empty address\n /// `vaultAddress` the address of the OETH Vault contract\n /// @param _wethAddress Address of the WETH Token contract\n /// @param _ssvToken Address of the SSV Token contract\n /// @param _ssvNetwork Address of the SSV Network contract\n /// @param _beaconChainDepositContract Address of the beacon chain deposit contract\n /// @param _beaconProofs Address of the Beacon Proofs contract that verifies beacon chain data\n /// @param _beaconGenesisTimestamp The timestamp of the Beacon chain's genesis.\n constructor(\n BaseStrategyConfig memory _baseConfig,\n address _wethAddress,\n address _ssvToken,\n address _ssvNetwork,\n address _beaconChainDepositContract,\n address _beaconProofs,\n uint64 _beaconGenesisTimestamp\n )\n InitializableAbstractStrategy(_baseConfig)\n CompoundingValidatorManager(\n _wethAddress,\n _baseConfig.vaultAddress,\n _beaconChainDepositContract,\n _ssvNetwork,\n _beaconProofs,\n _beaconGenesisTimestamp\n )\n {\n SSV_TOKEN = _ssvToken;\n\n // Make sure nobody owns the implementation contract\n _setGovernor(address(0));\n }\n\n /// @notice Set up initial internal state including\n /// 1. approving the SSVNetwork to transfer SSV tokens from this strategy contract\n /// @param _rewardTokenAddresses Not used so empty array\n /// @param _assets Not used so empty array\n /// @param _pTokens Not used so empty array\n function initialize(\n address[] memory _rewardTokenAddresses,\n address[] memory _assets,\n address[] memory _pTokens\n ) external onlyGovernor initializer {\n InitializableAbstractStrategy._initialize(\n _rewardTokenAddresses,\n _assets,\n _pTokens\n );\n\n safeApproveAllTokens();\n }\n\n /// @notice Unlike other strategies, this does not deposit assets into the underlying platform.\n /// It just checks the asset is WETH and emits the Deposit event.\n /// To deposit WETH into validators, `registerSsvValidator` and `stakeEth` must be used.\n /// @param _asset Address of the WETH token.\n /// @param _amount Amount of WETH that was transferred to the strategy by the vault.\n function deposit(address _asset, uint256 _amount)\n external\n override\n onlyVault\n nonReentrant\n {\n require(_asset == WETH, \"Unsupported asset\");\n require(_amount > 0, \"Must deposit something\");\n\n // Account for the new WETH\n depositedWethAccountedFor += _amount;\n\n emit Deposit(_asset, address(0), _amount);\n }\n\n /// @notice Unlike other strategies, this does not deposit assets into the underlying platform.\n /// It just emits the Deposit event.\n /// To deposit WETH into validators `registerSsvValidator` and `stakeEth` must be used.\n function depositAll() external override onlyVault nonReentrant {\n uint256 wethBalance = IERC20(WETH).balanceOf(address(this));\n uint256 newWeth = wethBalance - depositedWethAccountedFor;\n\n if (newWeth > 0) {\n // Account for the new WETH\n depositedWethAccountedFor = wethBalance;\n\n emit Deposit(WETH, address(0), newWeth);\n }\n }\n\n /// @notice Withdraw ETH and WETH from this strategy contract.\n /// @param _recipient Address to receive withdrawn assets.\n /// @param _asset Address of the WETH token.\n /// @param _amount Amount of WETH to withdraw.\n function withdraw(\n address _recipient,\n address _asset,\n uint256 _amount\n ) external override nonReentrant {\n require(_asset == WETH, \"Unsupported asset\");\n require(\n msg.sender == vaultAddress || msg.sender == validatorRegistrator,\n \"Caller not Vault or Registrator\"\n );\n\n _withdraw(_recipient, _amount, address(this).balance);\n }\n\n function _withdraw(\n address _recipient,\n uint256 _withdrawAmount,\n uint256 _ethBalance\n ) internal {\n require(_withdrawAmount > 0, \"Must withdraw something\");\n require(_recipient == vaultAddress, \"Recipient not Vault\");\n\n // Convert any ETH from validator partial withdrawals, exits\n // or execution rewards to WETH and do the necessary accounting.\n if (_ethBalance > 0) _convertEthToWeth(_ethBalance);\n\n // Transfer WETH to the recipient and do the necessary accounting.\n _transferWeth(_withdrawAmount, _recipient);\n\n emit Withdrawal(WETH, address(0), _withdrawAmount);\n }\n\n /// @notice Transfer all WETH deposits, ETH from validator withdrawals and ETH from\n /// execution rewards in this strategy to the vault.\n /// This does not withdraw from the validators. That has to be done separately with the\n /// `validatorWithdrawal` operation.\n function withdrawAll() external override onlyVaultOrGovernor nonReentrant {\n uint256 ethBalance = address(this).balance;\n uint256 withdrawAmount = IERC20(WETH).balanceOf(address(this)) +\n ethBalance;\n\n if (withdrawAmount > 0) {\n _withdraw(vaultAddress, withdrawAmount, ethBalance);\n }\n }\n\n /// @notice Accounts for all the assets managed by this strategy which includes:\n /// 1. The current WETH in this strategy contract\n /// 2. The last verified ETH balance, total deposits and total validator balances\n /// @param _asset Address of WETH asset.\n /// @return balance Total value in ETH\n function checkBalance(address _asset)\n external\n view\n override\n returns (uint256 balance)\n {\n require(_asset == WETH, \"Unsupported asset\");\n\n // Load the last verified balance from the storage\n // and add to the latest WETH balance of this strategy.\n balance =\n lastVerifiedEthBalance +\n IWETH9(WETH).balanceOf(address(this));\n }\n\n /// @notice Returns bool indicating whether asset is supported by the strategy.\n /// @param _asset The address of the WETH token.\n function supportsAsset(address _asset) public view override returns (bool) {\n return _asset == WETH;\n }\n\n /// @notice Approves the SSV Network contract to transfer SSV tokens for validator registration.\n function safeApproveAllTokens() public override {\n // Approves the SSV Network contract to transfer SSV tokens when validators are registered\n IERC20(SSV_TOKEN).approve(SSV_NETWORK, type(uint256).max);\n }\n\n /**\n * @notice We can accept ETH directly to this contract from anyone as it does not impact our accounting\n * like it did in the legacy NativeStakingStrategy.\n * The new ETH will be accounted for in `checkBalance` after the next snapBalances and verifyBalances txs.\n */\n receive() external payable {}\n\n /***************************************\n Internal functions\n ****************************************/\n\n /// @notice is not supported for this strategy as there is no platform token.\n function setPTokenAddress(address, address) external pure override {\n revert(\"Unsupported function\");\n }\n\n /// @notice is not supported for this strategy as there is no platform token.\n function removePToken(uint256) external pure override {\n revert(\"Unsupported function\");\n }\n\n /// @dev This strategy does not use a platform token like the old Aave and Compound strategies.\n function _abstractSetPToken(address _asset, address) internal override {}\n\n /// @dev Consensus rewards are compounded to the validator's balance instead of being\n /// swept to this strategy contract.\n /// Execution rewards from MEV and tx priority accumulate as ETH in this strategy contract.\n /// Withdrawals from validators also accumulate as ETH in this strategy contract.\n /// It's too complex to separate the rewards from withdrawals so this function is not implemented.\n /// Besides, ETH rewards are not sent to the Dripper any more. The Vault can now regulate\n /// the increase in assets.\n function _collectRewardTokens() internal pure override {\n revert(\"Unsupported function\");\n }\n}\n" + }, + "contracts/strategies/NativeStaking/CompoundingValidatorManager.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport { SafeCast } from \"@openzeppelin/contracts/utils/math/SafeCast.sol\";\nimport { Math } from \"@openzeppelin/contracts/utils/math/Math.sol\";\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport { Pausable } from \"@openzeppelin/contracts/security/Pausable.sol\";\nimport { Governable } from \"../../governance/Governable.sol\";\nimport { IDepositContract } from \"../../interfaces/IDepositContract.sol\";\nimport { IWETH9 } from \"../../interfaces/IWETH9.sol\";\nimport { ISSVNetwork, Cluster } from \"../../interfaces/ISSVNetwork.sol\";\nimport { BeaconRoots } from \"../../beacon/BeaconRoots.sol\";\nimport { PartialWithdrawal } from \"../../beacon/PartialWithdrawal.sol\";\nimport { IBeaconProofs } from \"../../interfaces/IBeaconProofs.sol\";\n\n/**\n * @title Validator lifecycle management contract\n * @notice This contract implements all the required functionality to\n * register, deposit, withdraw, exit and remove validators.\n * @author Origin Protocol Inc\n */\nabstract contract CompoundingValidatorManager is Governable, Pausable {\n using SafeERC20 for IERC20;\n\n /// @dev The amount of ETH in wei that is required for a deposit to a new validator.\n uint256 internal constant DEPOSIT_AMOUNT_WEI = 1 ether;\n /// @dev Validator balances over this amount will eventually become active on the beacon chain.\n /// Due to hysteresis, if the effective balance is 31 ETH, the actual balance\n /// must rise to 32.25 ETH to trigger an effective balance update to 32 ETH.\n /// https://eth2book.info/capella/part2/incentives/balances/#hysteresis\n uint256 internal constant MIN_ACTIVATION_BALANCE_GWEI = 32.25 ether / 1e9;\n /// @dev The maximum number of deposits that are waiting to be verified as processed on the beacon chain.\n uint256 internal constant MAX_DEPOSITS = 32;\n /// @dev The maximum number of validators that can be verified.\n uint256 internal constant MAX_VERIFIED_VALIDATORS = 48;\n /// @dev The default withdrawable epoch value on the Beacon chain.\n /// A value in the far future means the validator is not exiting.\n uint64 internal constant FAR_FUTURE_EPOCH = type(uint64).max;\n /// @dev The number of seconds between each beacon chain slot.\n uint64 internal constant SLOT_DURATION = 12;\n /// @dev The number of slots in each beacon chain epoch.\n uint64 internal constant SLOTS_PER_EPOCH = 32;\n /// @dev Minimum time in seconds to allow snapped balances to be verified.\n /// Set to 35 slots which is 3 slots more than 1 epoch (32 slots). Deposits get processed\n /// once per epoch. This larger than 1 epoch delay should achieve that `snapBalances` sometimes\n /// get called in the middle (or towards the end) of the epoch. Giving the off-chain script\n /// sufficient time after the end of the epoch to prepare the proofs and call `verifyBalances`.\n /// This is considering a malicious actor would keep calling `snapBalances` as frequent as possible\n /// to disturb our operations.\n uint64 internal constant SNAP_BALANCES_DELAY = 35 * SLOT_DURATION;\n\n /// @notice The address of the Wrapped ETH (WETH) token contract\n address internal immutable WETH;\n /// @notice The address of the beacon chain deposit contract\n address internal immutable BEACON_CHAIN_DEPOSIT_CONTRACT;\n /// @notice The address of the SSV Network contract used to interface with\n address internal immutable SSV_NETWORK;\n /// @notice Address of the OETH Vault proxy contract\n address internal immutable VAULT_ADDRESS;\n /// @notice Address of the Beacon Proofs contract that verifies beacon chain data\n address public immutable BEACON_PROOFS;\n /// @notice The timestamp of the Beacon chain genesis.\n /// @dev this is different on Testnets like Hoodi so is set at deployment time.\n uint64 internal immutable BEACON_GENESIS_TIMESTAMP;\n\n /// @notice Address of the registrator - allowed to register, withdraw, exit and remove validators\n address public validatorRegistrator;\n\n /// @notice Deposit data for new compounding validators.\n /// @dev A `VERIFIED` deposit can mean 3 separate things:\n /// - a deposit has been processed by the beacon chain and shall be included in the\n /// balance of the next verifyBalances call\n /// - a deposit has been done to a slashed validator and has probably been recovered\n /// back to this strategy. Probably because we can not know for certain. This contract\n /// only detects when the validator has passed its withdrawal epoch. It is close to impossible\n /// to prove with Merkle Proofs that the postponed deposit this contract is responsible for\n /// creating is not present anymore in BeaconChain.state.pending_deposits. This in effect\n /// means that there might be a period where this contract thinks the deposit has been already\n /// returned as ETH balance before it happens. This will result in some days (or weeks)\n /// -> depending on the size of deposit queue of showing a deficit when calling `checkBalance`.\n /// As this only offsets the yield and doesn't cause a critical double-counting we are not addressing\n /// this issue.\n /// - A deposit has been done to the validator, but our deposit has been front run by a malicious\n /// actor. Funds in the deposit this contract makes are not recoverable.\n enum DepositStatus {\n UNKNOWN, // default value\n PENDING, // deposit is pending and waiting to be verified\n VERIFIED // deposit has been verified\n }\n\n /// @param pubKeyHash Hash of validator's public key using the Beacon Chain's format\n /// @param amountGwei Amount of ETH in gwei that has been deposited to the beacon chain deposit contract\n /// @param slot The beacon chain slot number when the deposit has been made\n /// @param depositIndex The index of the deposit in the list of active deposits\n /// @param status The status of the deposit, either UNKNOWN, PENDING or VERIFIED\n struct DepositData {\n bytes32 pubKeyHash;\n uint64 amountGwei;\n uint64 slot;\n uint32 depositIndex;\n DepositStatus status;\n }\n /// @notice Restricts to only one deposit to an unverified validator at a time.\n /// This is to limit front-running attacks of deposits to the beacon chain contract.\n ///\n /// @dev The value is set to true when a deposit to a new validator has been done that has\n /// not yet be verified.\n bool public firstDeposit;\n /// @notice Mapping of the pending deposit roots to the deposit data\n mapping(bytes32 => DepositData) public deposits;\n /// @notice List of strategy deposit IDs to a validator.\n /// The ID is the merkle root of the pending deposit data which is unique for each validator, amount and block.\n /// Duplicate pending deposit roots are prevented so can be used as an identifier to each strategy deposit.\n /// The list can be for deposits waiting to be verified as processed on the beacon chain,\n /// or deposits that have been verified to an exiting validator and is now waiting for the\n /// validator's balance to be swept.\n /// The list may not be ordered by time of deposit.\n /// Removed deposits will move the last deposit to the removed index.\n bytes32[] public depositList;\n\n enum ValidatorState {\n NON_REGISTERED, // validator is not registered on the SSV network\n REGISTERED, // validator is registered on the SSV network\n STAKED, // validator has funds staked\n VERIFIED, // validator has been verified to exist on the beacon chain\n ACTIVE, // The validator balance is at least 32 ETH. The validator may not yet be active on the beacon chain.\n EXITING, // The validator has been requested to exit\n EXITED, // The validator has been verified to have a zero balance\n REMOVED, // validator has funds withdrawn to this strategy contract and is removed from the SSV\n INVALID // The validator has been front-run and the withdrawal address is not this strategy\n }\n\n // Validator data\n struct ValidatorData {\n ValidatorState state; // The state of the validator known to this contract\n uint40 index; // The index of the validator on the beacon chain\n }\n /// @notice List of validator public key hashes that have been verified to exist on the beacon chain.\n /// These have had a deposit processed and the validator's balance increased.\n /// Validators will be removed from this list when its verified they have a zero balance.\n bytes32[] public verifiedValidators;\n /// @notice Mapping of the hash of the validator's public key to the validator state and index.\n /// Uses the Beacon chain hashing for BLSPubkey which is sha256(abi.encodePacked(validator.pubkey, bytes16(0)))\n mapping(bytes32 => ValidatorData) public validator;\n\n /// @param blockRoot Beacon chain block root of the snapshot\n /// @param timestamp Timestamp of the snapshot\n /// @param ethBalance The balance of ETH in the strategy contract at the snapshot\n struct Balances {\n bytes32 blockRoot;\n uint64 timestamp;\n uint128 ethBalance;\n }\n /// @notice Mapping of the block root to the balances at that slot\n Balances public snappedBalance;\n /// @notice The last verified ETH balance of the strategy\n uint256 public lastVerifiedEthBalance;\n\n /// @dev This contract receives WETH as the deposit asset, but unlike other strategies doesn't immediately\n /// deposit it to an underlying platform. Rather a special privilege account stakes it to the validators.\n /// For that reason calling WETH.balanceOf(this) in a deposit function can contain WETH that has just been\n /// deposited and also WETH that has previously been deposited. To keep a correct count we need to keep track\n /// of WETH that has already been accounted for.\n /// This value represents the amount of WETH balance of this contract that has already been accounted for by the\n /// deposit events.\n /// It is important to note that this variable is not concerned with WETH that is a result of full/partial\n /// withdrawal of the validators. It is strictly concerned with WETH that has been deposited and is waiting to\n /// be staked.\n uint256 public depositedWethAccountedFor;\n\n // For future use\n uint256[41] private __gap;\n\n event RegistratorChanged(address indexed newAddress);\n event FirstDepositReset();\n event SSVValidatorRegistered(\n bytes32 indexed pubKeyHash,\n uint64[] operatorIds\n );\n event SSVValidatorRemoved(bytes32 indexed pubKeyHash, uint64[] operatorIds);\n event ETHStaked(\n bytes32 indexed pubKeyHash,\n bytes32 indexed pendingDepositRoot,\n bytes pubKey,\n uint256 amountWei\n );\n event ValidatorVerified(\n bytes32 indexed pubKeyHash,\n uint40 indexed validatorIndex\n );\n event ValidatorInvalid(bytes32 indexed pubKeyHash);\n event DepositVerified(\n bytes32 indexed pendingDepositRoot,\n uint256 amountWei\n );\n event ValidatorWithdraw(bytes32 indexed pubKeyHash, uint256 amountWei);\n event BalancesSnapped(bytes32 indexed blockRoot, uint256 ethBalance);\n event BalancesVerified(\n uint64 indexed timestamp,\n uint256 totalDepositsWei,\n uint256 totalValidatorBalance,\n uint256 ethBalance\n );\n\n /// @dev Throws if called by any account other than the Registrator\n modifier onlyRegistrator() {\n require(msg.sender == validatorRegistrator, \"Not Registrator\");\n _;\n }\n\n /// @dev Throws if called by any account other than the Registrator or Governor\n modifier onlyRegistratorOrGovernor() {\n require(\n msg.sender == validatorRegistrator || isGovernor(),\n \"Not Registrator or Governor\"\n );\n _;\n }\n\n /// @param _wethAddress Address of the Erc20 WETH Token contract\n /// @param _vaultAddress Address of the Vault\n /// @param _beaconChainDepositContract Address of the beacon chain deposit contract\n /// @param _ssvNetwork Address of the SSV Network contract\n /// @param _beaconProofs Address of the Beacon Proofs contract that verifies beacon chain data\n /// @param _beaconGenesisTimestamp The timestamp of the Beacon chain's genesis.\n constructor(\n address _wethAddress,\n address _vaultAddress,\n address _beaconChainDepositContract,\n address _ssvNetwork,\n address _beaconProofs,\n uint64 _beaconGenesisTimestamp\n ) {\n WETH = _wethAddress;\n BEACON_CHAIN_DEPOSIT_CONTRACT = _beaconChainDepositContract;\n SSV_NETWORK = _ssvNetwork;\n VAULT_ADDRESS = _vaultAddress;\n BEACON_PROOFS = _beaconProofs;\n BEACON_GENESIS_TIMESTAMP = _beaconGenesisTimestamp;\n\n require(\n block.timestamp > _beaconGenesisTimestamp,\n \"Invalid genesis timestamp\"\n );\n }\n\n /**\n *\n * Admin Functions\n *\n */\n\n /// @notice Set the address of the registrator which can register, exit and remove validators\n function setRegistrator(address _address) external onlyGovernor {\n validatorRegistrator = _address;\n emit RegistratorChanged(_address);\n }\n\n /// @notice Reset the `firstDeposit` flag to false so deposits to unverified validators can be made again.\n function resetFirstDeposit() external onlyGovernor {\n require(firstDeposit, \"No first deposit\");\n\n firstDeposit = false;\n\n emit FirstDepositReset();\n }\n\n function pause() external onlyRegistratorOrGovernor {\n _pause();\n }\n\n function unPause() external onlyGovernor {\n _unpause();\n }\n\n /**\n *\n * Validator Management\n *\n */\n\n /// @notice Registers a single validator in a SSV Cluster.\n /// Only the Registrator can call this function.\n /// @param publicKey The public key of the validator\n /// @param operatorIds The operator IDs of the SSV Cluster\n /// @param sharesData The shares data for the validator\n /// @param ssvAmount The amount of SSV tokens to be deposited to the SSV cluster\n /// @param cluster The SSV cluster details including the validator count and SSV balance\n // slither-disable-start reentrancy-no-eth\n function registerSsvValidator(\n bytes calldata publicKey,\n uint64[] calldata operatorIds,\n bytes calldata sharesData,\n uint256 ssvAmount,\n Cluster calldata cluster\n ) external onlyRegistrator whenNotPaused {\n // Hash the public key using the Beacon Chain's format\n bytes32 pubKeyHash = _hashPubKey(publicKey);\n // Check each public key has not already been used\n require(\n validator[pubKeyHash].state == ValidatorState.NON_REGISTERED,\n \"Validator already registered\"\n );\n\n // Store the validator state as registered\n validator[pubKeyHash].state = ValidatorState.REGISTERED;\n\n ISSVNetwork(SSV_NETWORK).registerValidator(\n publicKey,\n operatorIds,\n sharesData,\n ssvAmount,\n cluster\n );\n\n emit SSVValidatorRegistered(pubKeyHash, operatorIds);\n }\n\n // slither-disable-end reentrancy-no-eth\n\n struct ValidatorStakeData {\n bytes pubkey;\n bytes signature;\n bytes32 depositDataRoot;\n }\n\n /// @notice Stakes WETH in this strategy to a compounding validator.\n /// The first deposit to a new validator, the amount must be 1 ETH.\n /// Another deposit of at least 31 ETH is required for the validator to be activated.\n /// This second deposit has to be done after the validator has been verified.\n /// Does not convert any ETH sitting in this strategy to WETH.\n /// There can not be two deposits to the same validator in the same block for the same amount.\n /// Function is pausable so in case a run-away Registrator can be prevented from continuing\n /// to deposit funds to slashed or undesired validators.\n /// @param validatorStakeData validator data needed to stake.\n /// The `ValidatorStakeData` struct contains the pubkey, signature and depositDataRoot.\n /// Only the registrator can call this function.\n /// @param depositAmountGwei The amount of WETH to stake to the validator in Gwei.\n // slither-disable-start reentrancy-eth,reentrancy-no-eth\n function stakeEth(\n ValidatorStakeData calldata validatorStakeData,\n uint64 depositAmountGwei\n ) external onlyRegistrator whenNotPaused {\n uint256 depositAmountWei = uint256(depositAmountGwei) * 1 gwei;\n // Check there is enough WETH from the deposits sitting in this strategy contract\n // There could be ETH from withdrawals but we'll ignore that. If it's really needed\n // the ETH can be withdrawn and then deposited back to the strategy.\n require(\n depositAmountWei <= IWETH9(WETH).balanceOf(address(this)),\n \"Insufficient WETH\"\n );\n require(depositList.length < MAX_DEPOSITS, \"Max deposits\");\n\n // Convert required ETH from WETH and do the necessary accounting\n _convertWethToEth(depositAmountWei);\n\n // Hash the public key using the Beacon Chain's hashing for BLSPubkey\n bytes32 pubKeyHash = _hashPubKey(validatorStakeData.pubkey);\n ValidatorState currentState = validator[pubKeyHash].state;\n // Can only stake to a validator that has been registered, verified or active.\n // Can not stake to a validator that has been staked but not yet verified.\n require(\n (currentState == ValidatorState.REGISTERED ||\n currentState == ValidatorState.VERIFIED ||\n currentState == ValidatorState.ACTIVE),\n \"Not registered or verified\"\n );\n require(depositAmountWei >= 1 ether, \"Deposit too small\");\n if (currentState == ValidatorState.REGISTERED) {\n // Can only have one pending deposit to an unverified validator at a time.\n // This is to limit front-running deposit attacks to a single deposit.\n // The exiting deposit needs to be verified before another deposit can be made.\n // If there was a front-running attack, the validator needs to be verified as invalid\n // and the Governor calls `resetFirstDeposit` to set `firstDeposit` to false.\n require(!firstDeposit, \"Existing first deposit\");\n // Limits the amount of ETH that can be at risk from a front-running deposit attack.\n require(\n depositAmountWei == DEPOSIT_AMOUNT_WEI,\n \"Invalid first deposit amount\"\n );\n // Limits the number of validator balance proofs to verifyBalances\n require(\n verifiedValidators.length + 1 <= MAX_VERIFIED_VALIDATORS,\n \"Max validators\"\n );\n\n // Flag a deposit to an unverified validator so no other deposits can be made\n // to an unverified validator.\n firstDeposit = true;\n validator[pubKeyHash].state = ValidatorState.STAKED;\n }\n\n /* 0x02 to indicate that withdrawal credentials are for a compounding validator\n * that was introduced with the Pectra upgrade.\n * bytes11(0) to fill up the required zeros\n * remaining bytes20 are for the address\n */\n bytes memory withdrawalCredentials = abi.encodePacked(\n bytes1(0x02),\n bytes11(0),\n address(this)\n );\n\n /// After the Pectra upgrade the validators have a new restriction when proposing\n /// blocks. The timestamps are at strict intervals of 12 seconds from the genesis block\n /// forward. Each slot is created at strict 12 second intervals and those slots can\n /// either have blocks attached to them or not. This way using the block.timestamp\n /// the slot number can easily be calculated.\n uint64 depositSlot = (SafeCast.toUint64(block.timestamp) -\n BEACON_GENESIS_TIMESTAMP) / SLOT_DURATION;\n\n // Calculate the merkle root of the beacon chain pending deposit data.\n // This is used as the unique ID of the deposit.\n bytes32 pendingDepositRoot = IBeaconProofs(BEACON_PROOFS)\n .merkleizePendingDeposit(\n pubKeyHash,\n withdrawalCredentials,\n depositAmountGwei,\n validatorStakeData.signature,\n depositSlot\n );\n require(\n deposits[pendingDepositRoot].status == DepositStatus.UNKNOWN,\n \"Duplicate deposit\"\n );\n\n // Store the deposit data for verifyDeposit and verifyBalances\n deposits[pendingDepositRoot] = DepositData({\n pubKeyHash: pubKeyHash,\n amountGwei: depositAmountGwei,\n slot: depositSlot,\n depositIndex: SafeCast.toUint32(depositList.length),\n status: DepositStatus.PENDING\n });\n depositList.push(pendingDepositRoot);\n\n // Deposit to the Beacon Chain deposit contract.\n // This will create a deposit in the beacon chain's pending deposit queue.\n IDepositContract(BEACON_CHAIN_DEPOSIT_CONTRACT).deposit{\n value: depositAmountWei\n }(\n validatorStakeData.pubkey,\n withdrawalCredentials,\n validatorStakeData.signature,\n validatorStakeData.depositDataRoot\n );\n\n emit ETHStaked(\n pubKeyHash,\n pendingDepositRoot,\n validatorStakeData.pubkey,\n depositAmountWei\n );\n }\n\n // slither-disable-end reentrancy-eth,reentrancy-no-eth\n\n /// @notice Request a full or partial withdrawal from a validator.\n /// A zero amount will trigger a full withdrawal.\n /// If the remaining balance is < 32 ETH then only the amount in excess of 32 ETH will be withdrawn.\n /// Only the Registrator can call this function.\n /// 1 wei of value should be sent with the tx to pay for the withdrawal request fee.\n /// If no value sent, 1 wei will be taken from the strategy's ETH balance if it has any.\n /// If no ETH balance, the tx will revert.\n /// @param publicKey The public key of the validator\n /// @param amountGwei The amount of ETH to be withdrawn from the validator in Gwei.\n /// A zero amount will trigger a full withdrawal.\n // slither-disable-start reentrancy-no-eth\n function validatorWithdrawal(bytes calldata publicKey, uint64 amountGwei)\n external\n payable\n onlyRegistrator\n {\n // Hash the public key using the Beacon Chain's format\n bytes32 pubKeyHash = _hashPubKey(publicKey);\n ValidatorData memory validatorDataMem = validator[pubKeyHash];\n // Validator full withdrawal could be denied due to multiple reasons:\n // - the validator has not been activated or active long enough\n // (current_epoch < activation_epoch + SHARD_COMMITTEE_PERIOD)\n // - the validator has pending balance to withdraw from a previous partial withdrawal request\n //\n // Meaning that the on-chain to beacon chain full withdrawal request could fail. Instead\n // of adding complexity of verifying if a validator is eligible for a full exit, we allow\n // multiple full withdrawal requests per validator.\n require(\n validatorDataMem.state == ValidatorState.ACTIVE ||\n validatorDataMem.state == ValidatorState.EXITING,\n \"Validator not active/exiting\"\n );\n\n // If a full withdrawal (validator exit)\n if (amountGwei == 0) {\n // For each staking strategy's deposits\n uint256 depositsCount = depositList.length;\n for (uint256 i = 0; i < depositsCount; ++i) {\n bytes32 pendingDepositRoot = depositList[i];\n // Check there is no pending deposits to the exiting validator\n require(\n pubKeyHash != deposits[pendingDepositRoot].pubKeyHash,\n \"Pending deposit\"\n );\n }\n\n // Store the validator state as exiting so no more deposits can be made to it.\n // This may already be EXITING if the previous exit request failed. eg the validator\n // was not active long enough.\n validator[pubKeyHash].state = ValidatorState.EXITING;\n }\n\n // Do not remove from the list of verified validators.\n // This is done in the verifyBalances function once the validator's balance has been verified to be zero.\n // The validator state will be set to EXITED in the verifyBalances function.\n\n PartialWithdrawal.request(publicKey, amountGwei);\n\n emit ValidatorWithdraw(pubKeyHash, uint256(amountGwei) * 1 gwei);\n }\n\n // slither-disable-end reentrancy-no-eth\n\n /// @notice Remove the validator from the SSV Cluster after:\n /// - the validator has been exited from `validatorWithdrawal` or slashed\n /// - the validator has incorrectly registered and can not be staked to\n /// - the initial deposit was front-run and the withdrawal address is not this strategy's address.\n /// Make sure `validatorWithdrawal` is called with a zero amount and the validator has exited the Beacon chain.\n /// If removed before the validator has exited the beacon chain will result in the validator being slashed.\n /// Only the registrator can call this function.\n /// @param publicKey The public key of the validator\n /// @param operatorIds The operator IDs of the SSV Cluster\n /// @param cluster The SSV cluster details including the validator count and SSV balance\n // slither-disable-start reentrancy-no-eth\n function removeSsvValidator(\n bytes calldata publicKey,\n uint64[] calldata operatorIds,\n Cluster calldata cluster\n ) external onlyRegistrator {\n // Hash the public key using the Beacon Chain's format\n bytes32 pubKeyHash = _hashPubKey(publicKey);\n ValidatorState currentState = validator[pubKeyHash].state;\n // Can remove SSV validators that were incorrectly registered and can not be deposited to.\n require(\n currentState == ValidatorState.REGISTERED ||\n currentState == ValidatorState.EXITED ||\n currentState == ValidatorState.INVALID,\n \"Validator not regd or exited\"\n );\n\n validator[pubKeyHash].state = ValidatorState.REMOVED;\n\n ISSVNetwork(SSV_NETWORK).removeValidator(\n publicKey,\n operatorIds,\n cluster\n );\n\n emit SSVValidatorRemoved(pubKeyHash, operatorIds);\n }\n\n /**\n *\n * SSV Management\n *\n */\n\n // slither-disable-end reentrancy-no-eth\n\n /// `depositSSV` has been removed as `deposit` on the SSVNetwork contract can be called directly\n /// by the Strategist which is already holding SSV tokens.\n\n /// @notice Withdraws excess SSV Tokens from the SSV Network contract which was used to pay the SSV Operators.\n /// @dev A SSV cluster is defined by the SSVOwnerAddress and the set of operatorIds.\n /// @param operatorIds The operator IDs of the SSV Cluster\n /// @param ssvAmount The amount of SSV tokens to be withdrawn from the SSV cluster\n /// @param cluster The SSV cluster details including the validator count and SSV balance\n function withdrawSSV(\n uint64[] memory operatorIds,\n uint256 ssvAmount,\n Cluster memory cluster\n ) external onlyGovernor {\n ISSVNetwork(SSV_NETWORK).withdraw(operatorIds, ssvAmount, cluster);\n }\n\n /**\n *\n * Beacon Chain Proofs\n *\n */\n\n /// @notice Verifies a validator's index to its public key.\n /// Adds to the list of verified validators if the validator's withdrawal address is this strategy's address.\n /// Marks the validator as invalid and removes the deposit if the withdrawal address is not this strategy's address.\n /// @param nextBlockTimestamp The timestamp of the execution layer block after the beacon chain slot\n /// we are verifying.\n /// The next one is needed as the Beacon Oracle returns the parent beacon block root for a block timestamp,\n /// which is the beacon block root of the previous block.\n /// @param validatorIndex The index of the validator on the beacon chain.\n /// @param pubKeyHash The hash of the validator's public key using the Beacon Chain's format\n /// @param withdrawalCredentials contain the validator type and withdrawal address. These can be incorrect and/or\n /// malformed. In case of incorrect withdrawalCredentials the validator deposit has been front run\n /// @param validatorPubKeyProof The merkle proof for the validator public key to the beacon block root.\n /// This is 53 witness hashes of 32 bytes each concatenated together starting from the leaf node.\n /// BeaconBlock.state.validators[validatorIndex].pubkey\n function verifyValidator(\n uint64 nextBlockTimestamp,\n uint40 validatorIndex,\n bytes32 pubKeyHash,\n bytes32 withdrawalCredentials,\n bytes calldata validatorPubKeyProof\n ) external {\n require(\n validator[pubKeyHash].state == ValidatorState.STAKED,\n \"Validator not staked\"\n );\n\n // Get the beacon block root of the slot we are verifying the validator in.\n // The parent beacon block root of the next block is the beacon block root of the slot we are verifying.\n bytes32 blockRoot = BeaconRoots.parentBlockRoot(nextBlockTimestamp);\n\n // Verify the validator index is for the validator with the given public key.\n // Also verify the validator's withdrawal credentials\n IBeaconProofs(BEACON_PROOFS).verifyValidator(\n blockRoot,\n pubKeyHash,\n validatorPubKeyProof,\n validatorIndex,\n withdrawalCredentials\n );\n\n // Store the validator state as verified\n validator[pubKeyHash] = ValidatorData({\n state: ValidatorState.VERIFIED,\n index: validatorIndex\n });\n\n bytes32 expectedWithdrawalCredentials = bytes32(\n abi.encodePacked(bytes1(0x02), bytes11(0), address(this))\n );\n\n // If the initial deposit was front-run and the withdrawal address is not this strategy\n // or the validator type is not a compounding validator (0x02)\n if (expectedWithdrawalCredentials != withdrawalCredentials) {\n // override the validator state\n validator[pubKeyHash].state = ValidatorState.INVALID;\n\n // Find and remove the deposit as the funds can not be recovered\n uint256 depositCount = depositList.length;\n for (uint256 i = 0; i < depositCount; i++) {\n DepositData memory deposit = deposits[depositList[i]];\n if (deposit.pubKeyHash == pubKeyHash) {\n // next verifyBalances will correctly account for the loss of a front-run\n // deposit. Doing it here accounts for the loss as soon as possible\n lastVerifiedEthBalance -= Math.min(\n lastVerifiedEthBalance,\n uint256(deposit.amountGwei) * 1 gwei\n );\n _removeDeposit(depositList[i], deposit);\n break;\n }\n }\n\n // Leave the `firstDeposit` flag as true so no more deposits to unverified validators can be made.\n // The Governor has to reset the `firstDeposit` to false before another deposit to\n // an unverified validator can be made.\n // The Governor can set a new `validatorRegistrator` if they suspect it has been compromised.\n\n emit ValidatorInvalid(pubKeyHash);\n return;\n }\n\n // Add the new validator to the list of verified validators\n verifiedValidators.push(pubKeyHash);\n\n // Reset the firstDeposit flag as the first deposit to an unverified validator has been verified.\n firstDeposit = false;\n\n emit ValidatorVerified(pubKeyHash, validatorIndex);\n }\n\n struct FirstPendingDepositSlotProofData {\n uint64 slot;\n bytes proof;\n }\n\n struct StrategyValidatorProofData {\n uint64 withdrawableEpoch;\n bytes withdrawableEpochProof;\n }\n\n /// @notice Verifies a deposit on the execution layer has been processed by the beacon chain.\n /// This means the accounting of the strategy's ETH moves from a pending deposit to a validator balance.\n ///\n /// Important: this function has a limitation where `depositProcessedSlot` that is passed by the off-chain\n /// verifier requires a slot immediately after it to propose a block otherwise the `BeaconRoots.parentBlockRoot`\n /// will fail. This shouldn't be a problem, since by the current behaviour of beacon chain only 1%-3% slots\n /// don't propose a block.\n /// @param pendingDepositRoot The unique identifier of the deposit emitted in `ETHStaked` from\n /// the `stakeEth` function.\n /// @param depositProcessedSlot Any slot on or after the strategy's deposit was processed on the beacon chain.\n /// Can not be a slot with pending deposits with the same slot as the deposit being verified.\n /// Can not be a slot before a missed slot as the Beacon Root contract will have the parent block root\n /// set for the next block timestamp in 12 seconds time.\n /// @param firstPendingDeposit a `FirstPendingDepositSlotProofData` struct containing:\n /// - slot: The beacon chain slot of the first deposit in the beacon chain's deposit queue.\n /// Can be any non-zero value if the deposit queue is empty.\n /// - proof: The merkle proof of the first pending deposit's slot to the beacon block root.\n /// Can be either:\n /// * 40 witness hashes for BeaconBlock.state.PendingDeposits[0].slot when the deposit queue is not empty.\n /// * 37 witness hashes for BeaconBlock.state.PendingDeposits[0] when the deposit queue is empty.\n /// The 32 byte witness hashes are concatenated together starting from the leaf node.\n /// @param strategyValidatorData a `StrategyValidatorProofData` struct containing:\n /// - withdrawableEpoch: The withdrawable epoch of the validator the strategy is depositing to.\n /// - withdrawableEpochProof: The merkle proof for the withdrawable epoch of the validator the strategy\n /// is depositing to, to the beacon block root.\n /// This is 53 witness hashes of 32 bytes each concatenated together starting from the leaf node.\n // slither-disable-start reentrancy-no-eth\n function verifyDeposit(\n bytes32 pendingDepositRoot,\n uint64 depositProcessedSlot,\n FirstPendingDepositSlotProofData calldata firstPendingDeposit,\n StrategyValidatorProofData calldata strategyValidatorData\n ) external {\n // Load into memory the previously saved deposit data\n DepositData memory deposit = deposits[pendingDepositRoot];\n ValidatorData memory strategyValidator = validator[deposit.pubKeyHash];\n require(deposit.status == DepositStatus.PENDING, \"Deposit not pending\");\n require(firstPendingDeposit.slot != 0, \"Zero 1st pending deposit slot\");\n\n // We should allow the verification of deposits for validators that have been marked as exiting\n // to cover this situation:\n // - there are 2 pending deposits\n // - beacon chain has slashed the validator\n // - when verifyDeposit is called for the first deposit it sets the Validator state to EXITING\n // - verifyDeposit should allow a secondary call for the other deposit to a slashed validator\n require(\n strategyValidator.state == ValidatorState.VERIFIED ||\n strategyValidator.state == ValidatorState.ACTIVE ||\n strategyValidator.state == ValidatorState.EXITING,\n \"Not verified/active/exiting\"\n );\n // The verification slot must be after the deposit's slot.\n // This is needed for when the deposit queue is empty.\n require(deposit.slot < depositProcessedSlot, \"Slot not after deposit\");\n\n uint64 snapTimestamp = snappedBalance.timestamp;\n\n // This check prevents an accounting error that can happen if:\n // - snapBalances are snapped at the time of T\n // - deposit is processed on the beacon chain after time T and before verifyBalances()\n // - verifyDeposit is called before verifyBalances which removes a deposit from depositList\n // and deposit balance from totalDepositsWei\n // - verifyBalances is called under-reporting the strategy's balance\n require(\n (_calcNextBlockTimestamp(depositProcessedSlot) <= snapTimestamp) ||\n snapTimestamp == 0,\n \"Deposit after balance snapshot\"\n );\n\n // Get the parent beacon block root of the next block which is the block root of the deposit verification slot.\n // This will revert if the slot after the verification slot was missed.\n bytes32 depositBlockRoot = BeaconRoots.parentBlockRoot(\n _calcNextBlockTimestamp(depositProcessedSlot)\n );\n\n // Verify the slot of the first pending deposit matches the beacon chain\n bool isDepositQueueEmpty = IBeaconProofs(BEACON_PROOFS)\n .verifyFirstPendingDeposit(\n depositBlockRoot,\n firstPendingDeposit.slot,\n firstPendingDeposit.proof\n );\n\n // Verify the withdrawableEpoch on the validator of the strategy's deposit\n IBeaconProofs(BEACON_PROOFS).verifyValidatorWithdrawable(\n depositBlockRoot,\n strategyValidator.index,\n strategyValidatorData.withdrawableEpoch,\n strategyValidatorData.withdrawableEpochProof\n );\n\n uint64 firstPendingDepositEpoch = firstPendingDeposit.slot /\n SLOTS_PER_EPOCH;\n\n // If deposit queue is empty all deposits have certainly been processed. If not\n // a validator can either be not exiting and no further checks are required.\n // Or a validator is exiting then this function needs to make sure that the\n // pending deposit to an exited validator has certainly been processed. The\n // slot/epoch of first pending deposit is the one that contains the transaction\n // where the deposit to the ETH Deposit Contract has been made.\n //\n // Once the firstPendingDepositEpoch becomes greater than the withdrawableEpoch of\n // the slashed validator then the deposit has certainly been processed. When the beacon\n // chain reaches the withdrawableEpoch of the validator the deposit will no longer be\n // postponed. And any new deposits created (and present in the deposit queue)\n // will have an equal or larger withdrawableEpoch.\n require(\n strategyValidatorData.withdrawableEpoch == FAR_FUTURE_EPOCH ||\n strategyValidatorData.withdrawableEpoch <=\n firstPendingDepositEpoch ||\n isDepositQueueEmpty,\n \"Exit Deposit likely not proc.\"\n );\n\n // solhint-disable max-line-length\n // Check the deposit slot is before the first pending deposit's slot on the beacon chain.\n // If this is not true then we can't guarantee the deposit has been processed by the beacon chain.\n // The deposit's slot can not be the same slot as the first pending deposit as there could be\n // many deposits in the same block, hence have the same pending deposit slot.\n // If the deposit queue is empty then our deposit must have been processed on the beacon chain.\n // The deposit slot can be zero for validators consolidating to a compounding validator or 0x01 validator\n // being promoted to a compounding one. Reference:\n // - [switch_to_compounding_validator](https://ethereum.github.io/consensus-specs/specs/electra/beacon-chain/#new-switch_to_compounding_validator\n // - [queue_excess_active_balance](https://ethereum.github.io/consensus-specs/specs/electra/beacon-chain/#new-queue_excess_active_balance)\n // - [process_consolidation_request](https://ethereum.github.io/consensus-specs/specs/electra/beacon-chain/#new-process_consolidation_request)\n // We can not guarantee that the deposit has been processed in that case.\n // solhint-enable max-line-length\n require(\n deposit.slot < firstPendingDeposit.slot || isDepositQueueEmpty,\n \"Deposit likely not processed\"\n );\n\n // Remove the deposit now it has been verified as processed on the beacon chain.\n _removeDeposit(pendingDepositRoot, deposit);\n\n emit DepositVerified(\n pendingDepositRoot,\n uint256(deposit.amountGwei) * 1 gwei\n );\n }\n\n function _removeDeposit(\n bytes32 pendingDepositRoot,\n DepositData memory deposit\n ) internal {\n // After verifying the proof, update the contract storage\n deposits[pendingDepositRoot].status = DepositStatus.VERIFIED;\n // Move the last deposit to the index of the verified deposit\n bytes32 lastDeposit = depositList[depositList.length - 1];\n depositList[deposit.depositIndex] = lastDeposit;\n deposits[lastDeposit].depositIndex = deposit.depositIndex;\n // Delete the last deposit from the list\n depositList.pop();\n }\n\n /// @dev Calculates the timestamp of the next execution block from the given slot.\n /// @param slot The beacon chain slot number used for merkle proof verification.\n function _calcNextBlockTimestamp(uint64 slot)\n internal\n view\n returns (uint64)\n {\n // Calculate the next block timestamp from the slot.\n return SLOT_DURATION * slot + BEACON_GENESIS_TIMESTAMP + SLOT_DURATION;\n }\n\n // slither-disable-end reentrancy-no-eth\n\n /// @notice Stores the current ETH balance at the current block and beacon block root\n /// of the slot that is associated with the previous block.\n ///\n /// When snapping / verifying balance it is of a high importance that there is no\n /// miss-match in respect to ETH that is held by the contract and balances that are\n /// verified on the validators.\n ///\n /// First some context on the beacon-chain block building behaviour. Relevant parts of\n /// constructing a block on the beacon chain consist of:\n /// - process_withdrawals: ETH is deducted from the validator's balance\n /// - process_execution_payload: immediately after the previous step executing all the\n /// transactions\n /// - apply the withdrawals: adding ETH to the recipient which is the withdrawal address\n /// contained in the withdrawal credentials of the exited validators\n ///\n /// That means that balance increases which are part of the post-block execution state are\n /// done within the block, but the transaction that are contained within that block can not\n /// see / interact with the balance from the exited validators. Only transactions in the\n /// next block can do that.\n ///\n /// When snap balances is performed the state of the chain is snapped across 2 separate\n /// chain states:\n /// - ETH balance of the contract is recorded on block X -> and corresponding slot Y\n /// - beacon chain block root is recorded of block X - 1 -> and corresponding slot Y - 1\n /// given there were no missed slots. It could also be Y - 2, Y - 3 depending on how\n /// many slots have not managed to propose a block. For the sake of simplicity this slot\n /// will be referred to as Y - 1 as it makes no difference in the argument\n ///\n /// Given these 2 separate chain states it is paramount that verify balances can not experience\n /// miss-counting ETH or much more dangerous double counting of the ETH.\n ///\n /// When verifyBalances is called it is performed on the current block Z where Z > X. Verify\n /// balances adds up all the ETH (omitting WETH) controlled by this contract:\n /// - ETH balance in the contract on block X\n /// - ETH balance in Deposits on block Z that haven't been yet processed in slot Y - 1\n /// - ETH balance in validators that are active in slot Y - 1\n /// - skips the ETH balance in validators that have withdrawn in slot Y - 1 (or sooner)\n /// and have their balance visible to transactions in slot Y and corresponding block X\n /// (or sooner)\n ///\n /// Lets verify the correctness of ETH accounting given the above described behaviour.\n ///\n /// *ETH balance in the contract on block X*\n ///\n /// This is an ETH balance of the contract on a non current X block. Any ETH leaving the\n /// contract as a result of a withdrawal subtracts from the ETH accounted for on block X\n /// if `verifyBalances` has already been called. It also invalidates a `snapBalances` in\n /// case `verifyBalances` has not been called yet. Not performing this would result in not\n /// accounting for the withdrawn ETH that has happened anywhere in the block interval [X + 1, Z].\n ///\n /// Similarly to withdrawals any `stakeEth` deposits to the deposit contract adds to the ETH\n /// accounted for since the last `verifyBalances` has been called. And it invalidates the\n /// `snapBalances` in case `verifyBalances` hasn't been yet called. Not performing this\n /// would result in double counting the `stakedEth` since it would be present once in the\n /// snapped contract balance and the second time in deposit storage variables.\n ///\n /// This behaviour is correct.\n ///\n /// *ETH balance in Deposits on block Z that haven't been yet processed in slot Y - 1*\n ///\n /// The contract sums up all the ETH that has been deposited to the Beacon chain deposit\n /// contract at block Z. The execution layer doesn't have direct access to the state of\n /// deposits on the beacon chain. And if it is to sum up all the ETH that is marked to be\n /// deposited it needs to be sure to not double count ETH that is in deposits (storage vars)\n /// and could also be part of the validator balances. It does that by verifying that at\n /// slot Y - 1 none of the deposits visible on block Z have been processed. Meaning since\n /// the last snap till now all are still in queue. Which ensures they can not be part of\n /// the validator balances in later steps.\n ///\n /// This behaviour is correct.\n ///\n /// *ETH balance in validators that are active in slot Y - 1*\n ///\n /// The contract is verifying none of the deposits on Y - 1 slot have been processed and\n /// for that reason it checks the validator balances in the same slot. Ensuring accounting\n /// correctness.\n ///\n /// This behaviour is correct.\n ///\n /// *The withdrawn validators*\n ///\n /// The withdrawn validators could have their balances deducted in any slot before slot\n /// Y - 1 and the execution layer sees the balance increase in the subsequent slot. Lets\n /// look at the \"worst case scenario\" where the validator withdrawal is processed in the\n /// slot Y - 1 (snapped slot) and see their balance increase (in execution layer) in slot\n /// Y -> block X. The ETH balance on the contract is snapped at block X meaning that\n /// even if the validator exits at the latest possible time it is paramount that the ETH\n /// balance on the execution layer is recorded in the next block. Correctly accounting\n /// for the withdrawn ETH.\n ///\n /// Worth mentioning if the validator exit is processed by the slot Y and balance increase\n /// seen on the execution layer on block X + 1 the withdrawal is ignored by both the\n /// validator balance verification as well as execution layer contract balance snap.\n ///\n /// This behaviour is correct.\n ///\n /// The validator balances on the beacon chain can then be proved with `verifyBalances`.\n function snapBalances() external {\n uint64 currentTimestamp = SafeCast.toUint64(block.timestamp);\n require(\n snappedBalance.timestamp + SNAP_BALANCES_DELAY < currentTimestamp,\n \"Snap too soon\"\n );\n\n bytes32 blockRoot = BeaconRoots.parentBlockRoot(currentTimestamp);\n // Get the current ETH balance\n uint256 ethBalance = address(this).balance;\n\n // Store the snapped balance\n snappedBalance = Balances({\n blockRoot: blockRoot,\n timestamp: currentTimestamp,\n ethBalance: SafeCast.toUint128(ethBalance)\n });\n\n emit BalancesSnapped(blockRoot, ethBalance);\n }\n\n // A struct is used to avoid stack too deep errors\n struct BalanceProofs {\n // BeaconBlock.state.balances\n bytes32 balancesContainerRoot;\n bytes balancesContainerProof;\n // BeaconBlock.state.balances[validatorIndex]\n bytes32[] validatorBalanceLeaves;\n bytes[] validatorBalanceProofs;\n }\n\n struct PendingDepositProofs {\n bytes32 pendingDepositContainerRoot;\n bytes pendingDepositContainerProof;\n uint32[] pendingDepositIndexes;\n bytes[] pendingDepositProofs;\n }\n\n /// @notice Verifies the balances of all active validators on the beacon chain\n /// and checks each of the strategy's deposits are still to be processed by the beacon chain.\n /// @param balanceProofs a `BalanceProofs` struct containing the following:\n /// - balancesContainerRoot: The merkle root of the balances container\n /// - balancesContainerProof: The merkle proof for the balances container to the beacon block root.\n /// This is 9 witness hashes of 32 bytes each concatenated together starting from the leaf node.\n /// - validatorBalanceLeaves: Array of leaf nodes containing the validator balance with three other balances.\n /// - validatorBalanceProofs: Array of merkle proofs for the validator balance to the Balances container root.\n /// This is 39 witness hashes of 32 bytes each concatenated together starting from the leaf node.\n /// @param pendingDepositProofs a `PendingDepositProofs` struct containing the following:\n /// - pendingDepositContainerRoot: The merkle root of the pending deposits list container\n /// - pendingDepositContainerProof: The merkle proof from the pending deposits list container\n /// to the beacon block root.\n /// This is 9 witness hashes of 32 bytes each concatenated together starting from the leaf node.\n /// - pendingDepositIndexes: Array of indexes in the pending deposits list container for each\n /// of the strategy's deposits.\n /// - pendingDepositProofs: Array of merkle proofs for each strategy deposit in the\n /// beacon chain's pending deposit list container to the pending deposits list container root.\n /// These are 28 witness hashes of 32 bytes each concatenated together starting from the leaf node.\n // slither-disable-start reentrancy-no-eth\n function verifyBalances(\n BalanceProofs calldata balanceProofs,\n PendingDepositProofs calldata pendingDepositProofs\n ) external {\n // Load previously snapped balances for the given block root\n Balances memory balancesMem = snappedBalance;\n // Check the balances are the latest\n require(balancesMem.timestamp > 0, \"No snapped balances\");\n\n uint256 verifiedValidatorsCount = verifiedValidators.length;\n uint256 totalValidatorBalance = 0;\n uint256 depositsCount = depositList.length;\n\n // If there are no verified validators then we can skip the balance verification\n if (verifiedValidatorsCount > 0) {\n require(\n balanceProofs.validatorBalanceProofs.length ==\n verifiedValidatorsCount,\n \"Invalid balance proofs\"\n );\n require(\n balanceProofs.validatorBalanceLeaves.length ==\n verifiedValidatorsCount,\n \"Invalid balance leaves\"\n );\n // verify beaconBlock.state.balances root to beacon block root\n IBeaconProofs(BEACON_PROOFS).verifyBalancesContainer(\n balancesMem.blockRoot,\n balanceProofs.balancesContainerRoot,\n balanceProofs.balancesContainerProof\n );\n\n bytes32[]\n memory validatorHashesMem = _getPendingDepositValidatorHashes(\n depositsCount\n );\n\n // for each validator in reverse order so we can pop off exited validators at the end\n for (uint256 i = verifiedValidatorsCount; i > 0; ) {\n --i;\n ValidatorData memory validatorDataMem = validator[\n verifiedValidators[i]\n ];\n // verify validator's balance in beaconBlock.state.balances to the\n // beaconBlock.state.balances container root\n uint256 validatorBalanceGwei = IBeaconProofs(BEACON_PROOFS)\n .verifyValidatorBalance(\n balanceProofs.balancesContainerRoot,\n balanceProofs.validatorBalanceLeaves[i],\n balanceProofs.validatorBalanceProofs[i],\n validatorDataMem.index\n );\n\n // If the validator has exited and the balance is now zero\n if (validatorBalanceGwei == 0) {\n // Check if there are any pending deposits to this validator\n bool depositPending = false;\n for (uint256 j = 0; j < validatorHashesMem.length; j++) {\n if (validatorHashesMem[j] == verifiedValidators[i]) {\n depositPending = true;\n break;\n }\n }\n\n // If validator has a pending deposit we can not remove due to\n // the following situation:\n // - validator has a pending deposit\n // - validator has been slashed\n // - sweep cycle has withdrawn all ETH from the validator. Balance is 0\n // - beacon chain has processed the deposit and set the validator balance\n // to deposit amount\n // - if validator is no longer in the list of verifiedValidators its\n // balance will not be considered and be under-counted.\n if (!depositPending) {\n // Store the validator state as exited\n // This could have been in VERIFIED, ACTIVE or EXITING state\n validator[verifiedValidators[i]].state = ValidatorState\n .EXITED;\n\n // Remove the validator with a zero balance from the list of verified validators\n\n // Reduce the count of verified validators which is the last index before the pop removes it.\n verifiedValidatorsCount -= 1;\n\n // Move the last validator that has already been verified to the current index.\n // There's an extra SSTORE if i is the last active validator but that's fine,\n // It's not a common case and the code is simpler this way.\n verifiedValidators[i] = verifiedValidators[\n verifiedValidatorsCount\n ];\n // Delete the last validator from the list\n verifiedValidators.pop();\n }\n\n // The validator balance is zero so not need to add to totalValidatorBalance\n continue;\n } else if (\n validatorDataMem.state == ValidatorState.VERIFIED &&\n validatorBalanceGwei > MIN_ACTIVATION_BALANCE_GWEI\n ) {\n // Store the validator state as active. This does not necessarily mean the\n // validator is active on the beacon chain yet. It just means the validator has\n // enough balance that it can become active.\n validator[verifiedValidators[i]].state = ValidatorState\n .ACTIVE;\n }\n\n // convert Gwei balance to Wei and add to the total validator balance\n totalValidatorBalance += validatorBalanceGwei * 1 gwei;\n }\n }\n\n uint256 totalDepositsWei = 0;\n\n // If there are no deposits then we can skip the deposit verification.\n // This section is after the validator balance verifications so an exited validator will be marked\n // as EXITED before the deposits are verified. If there was a deposit to an exited validator\n // then the deposit can only be removed once the validator is fully exited.\n // It is possible that validator fully exits and a postponed deposit to an exited validator increases\n // its balance again. In such case the contract will erroneously consider a deposit applied before it\n // has been applied on the beacon chain showing a smaller than real `totalValidatorBalance`.\n if (depositsCount > 0) {\n require(\n pendingDepositProofs.pendingDepositProofs.length ==\n depositsCount,\n \"Invalid deposit proofs\"\n );\n require(\n pendingDepositProofs.pendingDepositIndexes.length ==\n depositsCount,\n \"Invalid deposit indexes\"\n );\n\n // Verify from the root of the pending deposit list container to the beacon block root\n IBeaconProofs(BEACON_PROOFS).verifyPendingDepositsContainer(\n balancesMem.blockRoot,\n pendingDepositProofs.pendingDepositContainerRoot,\n pendingDepositProofs.pendingDepositContainerProof\n );\n\n // For each staking strategy's deposit.\n for (uint256 i = 0; i < depositsCount; ++i) {\n bytes32 pendingDepositRoot = depositList[i];\n\n // Verify the strategy's deposit is still pending on the beacon chain.\n IBeaconProofs(BEACON_PROOFS).verifyPendingDeposit(\n pendingDepositProofs.pendingDepositContainerRoot,\n pendingDepositRoot,\n pendingDepositProofs.pendingDepositProofs[i],\n pendingDepositProofs.pendingDepositIndexes[i]\n );\n\n // Convert the deposit amount from Gwei to Wei and add to the total\n totalDepositsWei +=\n uint256(deposits[pendingDepositRoot].amountGwei) *\n 1 gwei;\n }\n }\n\n // Store the verified balance in storage\n lastVerifiedEthBalance =\n totalDepositsWei +\n totalValidatorBalance +\n balancesMem.ethBalance;\n // Reset the last snap timestamp so a new snapBalances has to be made\n snappedBalance.timestamp = 0;\n\n emit BalancesVerified(\n balancesMem.timestamp,\n totalDepositsWei,\n totalValidatorBalance,\n balancesMem.ethBalance\n );\n }\n\n // slither-disable-end reentrancy-no-eth\n\n /// @notice get a list of all validator hashes present in the pending deposits\n /// list can have duplicate entries\n function _getPendingDepositValidatorHashes(uint256 depositsCount)\n internal\n view\n returns (bytes32[] memory validatorHashes)\n {\n validatorHashes = new bytes32[](depositsCount);\n for (uint256 i = 0; i < depositsCount; i++) {\n validatorHashes[i] = deposits[depositList[i]].pubKeyHash;\n }\n }\n\n /// @notice Hash a validator public key using the Beacon Chain's format\n function _hashPubKey(bytes memory pubKey) internal pure returns (bytes32) {\n require(pubKey.length == 48, \"Invalid public key\");\n return sha256(abi.encodePacked(pubKey, bytes16(0)));\n }\n\n /**\n *\n * WETH and ETH Accounting\n *\n */\n\n /// @dev Called when WETH is transferred out of the strategy so\n /// the strategy knows how much WETH it has on deposit.\n /// This is so it can emit the correct amount in the Deposit event in depositAll().\n function _transferWeth(uint256 _amount, address _recipient) internal {\n IERC20(WETH).safeTransfer(_recipient, _amount);\n\n // The min is required as more WETH can be withdrawn than deposited\n // as the strategy earns consensus and execution rewards.\n uint256 deductAmount = Math.min(_amount, depositedWethAccountedFor);\n depositedWethAccountedFor -= deductAmount;\n\n // No change in ETH balance so no need to snapshot the balances\n }\n\n /// @dev Converts ETH to WETH and updates the accounting.\n /// @param _ethAmount The amount of ETH in wei.\n function _convertEthToWeth(uint256 _ethAmount) internal {\n // slither-disable-next-line arbitrary-send-eth\n IWETH9(WETH).deposit{ value: _ethAmount }();\n\n depositedWethAccountedFor += _ethAmount;\n\n // Store the reduced ETH balance.\n // The ETH balance in this strategy contract can be more than the last verified ETH balance\n // due to partial withdrawals or full exits being processed by the beacon chain since the last snapBalances.\n // It can also happen from execution rewards (MEV) or ETH donations.\n lastVerifiedEthBalance -= Math.min(lastVerifiedEthBalance, _ethAmount);\n\n // The ETH balance was decreased to WETH so we need to invalidate the last balances snap.\n snappedBalance.timestamp = 0;\n }\n\n /// @dev Converts WETH to ETH and updates the accounting.\n /// @param _wethAmount The amount of WETH in wei.\n function _convertWethToEth(uint256 _wethAmount) internal {\n IWETH9(WETH).withdraw(_wethAmount);\n\n uint256 deductAmount = Math.min(_wethAmount, depositedWethAccountedFor);\n depositedWethAccountedFor -= deductAmount;\n\n // Store the increased ETH balance\n lastVerifiedEthBalance += _wethAmount;\n\n // The ETH balance was increased from WETH so we need to invalidate the last balances snap.\n snappedBalance.timestamp = 0;\n }\n\n /**\n *\n * View Functions\n *\n */\n\n /// @notice Returns the number of deposits waiting to be verified as processed on the beacon chain,\n /// or deposits that have been verified to an exiting validator and is now waiting for the\n /// validator's balance to be swept.\n function depositListLength() external view returns (uint256) {\n return depositList.length;\n }\n\n /// @notice Returns the number of verified validators.\n function verifiedValidatorsLength() external view returns (uint256) {\n return verifiedValidators.length;\n }\n}\n" + }, + "contracts/strategies/NativeStaking/FeeAccumulator.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { Address } from \"@openzeppelin/contracts/utils/Address.sol\";\n\n/**\n * @title Fee Accumulator for Native Staking SSV Strategy\n * @notice Receives execution rewards which includes tx fees and\n * MEV rewards like tx priority and tx ordering.\n * It does NOT include swept ETH from beacon chain consensus rewards or full validator withdrawals.\n * @author Origin Protocol Inc\n */\ncontract FeeAccumulator {\n /// @notice The address of the Native Staking Strategy\n address public immutable STRATEGY;\n\n event ExecutionRewardsCollected(address indexed strategy, uint256 amount);\n\n /**\n * @param _strategy Address of the Native Staking Strategy\n */\n constructor(address _strategy) {\n STRATEGY = _strategy;\n }\n\n /**\n * @notice sends all ETH in this FeeAccumulator contract to the Native Staking Strategy.\n * @return eth The amount of execution rewards that were sent to the Native Staking Strategy\n */\n function collect() external returns (uint256 eth) {\n require(msg.sender == STRATEGY, \"Caller is not the Strategy\");\n\n eth = address(this).balance;\n if (eth > 0) {\n // Send the ETH to the Native Staking Strategy\n Address.sendValue(payable(STRATEGY), eth);\n\n emit ExecutionRewardsCollected(STRATEGY, eth);\n }\n }\n\n /**\n * @dev Accept ETH\n */\n receive() external payable {}\n}\n" + }, + "contracts/strategies/NativeStaking/NativeStakingSSVStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport \"@openzeppelin/contracts/utils/math/Math.sol\";\n\nimport { InitializableAbstractStrategy } from \"../../utils/InitializableAbstractStrategy.sol\";\nimport { IWETH9 } from \"../../interfaces/IWETH9.sol\";\nimport { FeeAccumulator } from \"./FeeAccumulator.sol\";\nimport { ValidatorAccountant } from \"./ValidatorAccountant.sol\";\nimport { ISSVNetwork } from \"../../interfaces/ISSVNetwork.sol\";\n\nstruct ValidatorStakeData {\n bytes pubkey;\n bytes signature;\n bytes32 depositDataRoot;\n}\n\n/// @title Native Staking SSV Strategy\n/// @notice Strategy to deploy funds into DVT validators powered by the SSV Network\n/// @author Origin Protocol Inc\n/// @dev This contract handles WETH and ETH and in some operations interchanges between the two. Any WETH that\n/// is on the contract across multiple blocks (and not just transitory within a transaction) is considered an\n/// asset. Meaning deposits increase the balance of the asset and withdrawal decrease it. As opposed to all\n/// our other strategies the WETH doesn't immediately get deposited into an underlying strategy and can be present\n/// across multiple blocks waiting to be unwrapped to ETH and staked to validators. This separation of WETH and ETH is\n/// required since the rewards (reward token) is also in ETH.\n///\n/// To simplify the accounting of WETH there is another difference in behavior compared to the other strategies.\n/// To withdraw WETH asset - exit message is posted to validators and the ETH hits this contract with multiple days\n/// delay. In order to simplify the WETH accounting upon detection of such an event the ValidatorAccountant\n/// immediately wraps ETH to WETH and sends it to the Vault.\n///\n/// On the other hand any ETH on the contract (across multiple blocks) is there either:\n/// - as a result of already accounted for consensus rewards\n/// - as a result of not yet accounted for consensus rewards\n/// - as a results of not yet accounted for full validator withdrawals (or validator slashes)\n///\n/// Even though the strategy assets and rewards are a very similar asset the consensus layer rewards and the\n/// execution layer rewards are considered rewards and those are dripped to the Vault over a configurable time\n/// interval and not immediately.\ncontract NativeStakingSSVStrategy is\n ValidatorAccountant,\n InitializableAbstractStrategy\n{\n using SafeERC20 for IERC20;\n\n /// @notice SSV ERC20 token that serves as a payment for operating SSV validators\n address public immutable SSV_TOKEN;\n /// @notice Fee collector address\n /// @dev this address will receive maximal extractable value (MEV) rewards. These are\n /// rewards for arranging transactions in a way that benefits the validator.\n address payable public immutable FEE_ACCUMULATOR_ADDRESS;\n\n /// @dev This contract receives WETH as the deposit asset, but unlike other strategies doesn't immediately\n /// deposit it to an underlying platform. Rather a special privilege account stakes it to the validators.\n /// For that reason calling WETH.balanceOf(this) in a deposit function can contain WETH that has just been\n /// deposited and also WETH that has previously been deposited. To keep a correct count we need to keep track\n /// of WETH that has already been accounted for.\n /// This value represents the amount of WETH balance of this contract that has already been accounted for by the\n /// deposit events.\n /// It is important to note that this variable is not concerned with WETH that is a result of full/partial\n /// withdrawal of the validators. It is strictly concerned with WETH that has been deposited and is waiting to\n /// be staked.\n uint256 public depositedWethAccountedFor;\n\n // For future use\n uint256[49] private __gap;\n\n /// @param _baseConfig Base strategy config with platformAddress (ERC-4626 Vault contract), eg sfrxETH or sDAI,\n /// and vaultAddress (OToken Vault contract), eg VaultProxy or OETHVaultProxy\n /// @param _wethAddress Address of the Erc20 WETH Token contract\n /// @param _ssvToken Address of the Erc20 SSV Token contract\n /// @param _ssvNetwork Address of the SSV Network contract\n /// @param _maxValidators Maximum number of validators that can be registered in the strategy\n /// @param _feeAccumulator Address of the fee accumulator receiving execution layer validator rewards\n /// @param _beaconChainDepositContract Address of the beacon chain deposit contract\n constructor(\n BaseStrategyConfig memory _baseConfig,\n address _wethAddress,\n address _ssvToken,\n address _ssvNetwork,\n uint256 _maxValidators,\n address _feeAccumulator,\n address _beaconChainDepositContract\n )\n InitializableAbstractStrategy(_baseConfig)\n ValidatorAccountant(\n _wethAddress,\n _baseConfig.vaultAddress,\n _beaconChainDepositContract,\n _ssvNetwork,\n _maxValidators\n )\n {\n SSV_TOKEN = _ssvToken;\n FEE_ACCUMULATOR_ADDRESS = payable(_feeAccumulator);\n }\n\n /// @notice Set up initial internal state including\n /// 1. approving the SSVNetwork to transfer SSV tokens from this strategy contract\n /// 2. setting the recipient of SSV validator MEV rewards to the FeeAccumulator contract.\n /// @param _rewardTokenAddresses Address of reward token for platform\n /// @param _assets Addresses of initial supported assets\n /// @param _pTokens Platform Token corresponding addresses\n function initialize(\n address[] memory _rewardTokenAddresses,\n address[] memory _assets,\n address[] memory _pTokens\n ) external onlyGovernor initializer {\n InitializableAbstractStrategy._initialize(\n _rewardTokenAddresses,\n _assets,\n _pTokens\n );\n\n // Approves the SSV Network contract to transfer SSV tokens for deposits\n IERC20(SSV_TOKEN).approve(SSV_NETWORK, type(uint256).max);\n\n // Set the FeeAccumulator as the address for SSV validators to send MEV rewards to\n ISSVNetwork(SSV_NETWORK).setFeeRecipientAddress(\n FEE_ACCUMULATOR_ADDRESS\n );\n }\n\n /// @notice Unlike other strategies, this does not deposit assets into the underlying platform.\n /// It just checks the asset is WETH and emits the Deposit event.\n /// To deposit WETH into validators `registerSsvValidator` and `stakeEth` must be used.\n /// Will NOT revert if the strategy is paused from an accounting failure.\n /// @param _asset Address of asset to deposit. Has to be WETH.\n /// @param _amount Amount of assets that were transferred to the strategy by the vault.\n function deposit(address _asset, uint256 _amount)\n external\n override\n onlyVault\n nonReentrant\n {\n require(_asset == WETH, \"Unsupported asset\");\n depositedWethAccountedFor += _amount;\n _deposit(_asset, _amount);\n }\n\n /// @dev Deposit WETH to this strategy so it can later be staked into a validator.\n /// @param _asset Address of WETH\n /// @param _amount Amount of WETH to deposit\n function _deposit(address _asset, uint256 _amount) internal {\n require(_amount > 0, \"Must deposit something\");\n /*\n * We could do a check here that would revert when \"_amount % 32 ether != 0\". With the idea of\n * not allowing deposits that will result in WETH sitting on the strategy after all the possible batches\n * of 32ETH have been staked.\n * But someone could mess with our strategy by sending some WETH to it. And we might want to deposit just\n * enough WETH to add it up to 32 so it can be staked. For that reason the check is left out.\n *\n * WETH sitting on the strategy won't interfere with the accounting since accounting only operates on ETH.\n */\n emit Deposit(_asset, address(0), _amount);\n }\n\n /// @notice Unlike other strategies, this does not deposit assets into the underlying platform.\n /// It just emits the Deposit event.\n /// To deposit WETH into validators `registerSsvValidator` and `stakeEth` must be used.\n /// Will NOT revert if the strategy is paused from an accounting failure.\n function depositAll() external override onlyVault nonReentrant {\n uint256 wethBalance = IERC20(WETH).balanceOf(address(this));\n uint256 newWeth = wethBalance - depositedWethAccountedFor;\n\n if (newWeth > 0) {\n depositedWethAccountedFor = wethBalance;\n\n _deposit(WETH, newWeth);\n }\n }\n\n /// @notice Withdraw WETH from this contract. Used only if some WETH for is lingering on the contract.\n /// That can happen when:\n /// - after mints if the strategy is the default\n /// - time between depositToStrategy and stakeEth\n /// - the deposit was not a multiple of 32 WETH\n /// - someone sent WETH directly to this contract\n /// Will NOT revert if the strategy is paused from an accounting failure.\n /// @param _recipient Address to receive withdrawn assets\n /// @param _asset WETH to withdraw\n /// @param _amount Amount of WETH to withdraw\n function withdraw(\n address _recipient,\n address _asset,\n uint256 _amount\n ) external override onlyVault nonReentrant {\n require(_asset == WETH, \"Unsupported asset\");\n _withdraw(_recipient, _asset, _amount);\n }\n\n function _withdraw(\n address _recipient,\n address _asset,\n uint256 _amount\n ) internal {\n require(_amount > 0, \"Must withdraw something\");\n require(_recipient != address(0), \"Must specify recipient\");\n\n _wethWithdrawn(_amount);\n\n IERC20(_asset).safeTransfer(_recipient, _amount);\n emit Withdrawal(_asset, address(0), _amount);\n }\n\n /// @notice transfer all WETH deposits back to the vault.\n /// This does not withdraw from the validators. That has to be done separately with the\n /// `exitSsvValidator` and `removeSsvValidator` operations.\n /// This does not withdraw any execution rewards from the FeeAccumulator or\n /// consensus rewards in this strategy.\n /// Any ETH in this strategy that was swept from a full validator withdrawal will not be withdrawn.\n /// ETH from full validator withdrawals is sent to the Vault using `doAccounting`.\n /// Will NOT revert if the strategy is paused from an accounting failure.\n function withdrawAll() external override onlyVaultOrGovernor nonReentrant {\n uint256 wethBalance = IERC20(WETH).balanceOf(address(this));\n if (wethBalance > 0) {\n _withdraw(vaultAddress, WETH, wethBalance);\n }\n }\n\n /// @notice Returns the total value of (W)ETH that is staked to the validators\n /// and WETH deposits that are still to be staked.\n /// This does not include ETH from consensus rewards sitting in this strategy\n /// or ETH from MEV rewards in the FeeAccumulator. These rewards are harvested\n /// and sent to the Dripper so will eventually be sent to the Vault as WETH.\n /// @param _asset Address of weth asset\n /// @return balance Total value of (W)ETH\n function checkBalance(address _asset)\n external\n view\n override\n returns (uint256 balance)\n {\n require(_asset == WETH, \"Unsupported asset\");\n\n balance =\n // add the ETH that has been staked in validators\n activeDepositedValidators *\n FULL_STAKE +\n // add the WETH in the strategy from deposits that are still to be staked\n IERC20(WETH).balanceOf(address(this));\n }\n\n function pause() external onlyStrategist {\n _pause();\n }\n\n /// @notice Returns bool indicating whether asset is supported by strategy.\n /// @param _asset The address of the asset token.\n function supportsAsset(address _asset) public view override returns (bool) {\n return _asset == WETH;\n }\n\n /// @notice Approves the SSV Network contract to transfer SSV tokens for deposits\n function safeApproveAllTokens() external override {\n // Approves the SSV Network contract to transfer SSV tokens for deposits\n IERC20(SSV_TOKEN).approve(SSV_NETWORK, type(uint256).max);\n }\n\n /// @notice Set the FeeAccumulator as the address for SSV validators to send MEV rewards to\n function setFeeRecipient() external {\n ISSVNetwork(SSV_NETWORK).setFeeRecipientAddress(\n FEE_ACCUMULATOR_ADDRESS\n );\n }\n\n /**\n * @notice Only accept ETH from the FeeAccumulator and the WETH contract - required when\n * unwrapping WETH just before staking it to the validator.\n * The strategy will also receive ETH from the priority fees of transactions when producing blocks\n * as defined in EIP-1559.\n * The tx fees come from the Beacon chain so do not need any EVM level permissions to receive ETH.\n * The tx fees are paid with each block produced. They are not included in the consensus rewards\n * which are periodically swept from the validators to this strategy.\n * For accounting purposes, the priority fees of transactions will be considered consensus rewards\n * and will be included in the AccountingConsensusRewards event.\n * @dev don't want to receive donations from anyone else as donations over the fuse limits will\n * mess with the accounting of the consensus rewards and validator full withdrawals.\n */\n receive() external payable {\n require(\n msg.sender == FEE_ACCUMULATOR_ADDRESS || msg.sender == WETH,\n \"Eth not from allowed contracts\"\n );\n }\n\n /***************************************\n Internal functions\n ****************************************/\n\n function _abstractSetPToken(address _asset, address) internal override {}\n\n /// @dev Convert accumulated ETH to WETH and send to the Harvester.\n /// Will revert if the strategy is paused for accounting.\n function _collectRewardTokens() internal override whenNotPaused {\n // collect ETH from execution rewards from the fee accumulator\n uint256 executionRewards = FeeAccumulator(FEE_ACCUMULATOR_ADDRESS)\n .collect();\n\n // total ETH rewards to be harvested = execution rewards + consensus rewards\n uint256 ethRewards = executionRewards + consensusRewards;\n\n require(\n address(this).balance >= ethRewards,\n \"Insufficient eth balance\"\n );\n\n if (ethRewards > 0) {\n // reset the counter keeping track of beacon chain consensus rewards\n consensusRewards = 0;\n\n // Convert ETH rewards to WETH\n IWETH9(WETH).deposit{ value: ethRewards }();\n\n IERC20(WETH).safeTransfer(harvesterAddress, ethRewards);\n emit RewardTokenCollected(harvesterAddress, WETH, ethRewards);\n }\n }\n\n /// @dev emits Withdrawal event from NativeStakingSSVStrategy\n function _wethWithdrawnToVault(uint256 _amount) internal override {\n emit Withdrawal(WETH, address(0), _amount);\n }\n\n /// @dev Called when WETH is withdrawn from the strategy or staked to a validator so\n /// the strategy knows how much WETH it has on deposit.\n /// This is so it can emit the correct amount in the Deposit event in depositAll().\n function _wethWithdrawn(uint256 _amount) internal override {\n /* In an ideal world we wouldn't need to reduce the deduction amount when the\n * depositedWethAccountedFor is smaller than the _amount.\n *\n * The reason this is required is that a malicious actor could sent WETH directly\n * to this contract and that would circumvent the increase of depositedWethAccountedFor\n * property. When the ETH would be staked the depositedWethAccountedFor amount could\n * be deducted so much that it would be negative.\n */\n uint256 deductAmount = Math.min(_amount, depositedWethAccountedFor);\n depositedWethAccountedFor -= deductAmount;\n }\n}\n" + }, + "contracts/strategies/NativeStaking/ValidatorAccountant.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { ValidatorRegistrator } from \"./ValidatorRegistrator.sol\";\nimport { IWETH9 } from \"../../interfaces/IWETH9.sol\";\n\n/// @title Validator Accountant\n/// @notice Attributes the ETH swept from beacon chain validators to this strategy contract\n/// as either full or partial withdrawals. Partial withdrawals being consensus rewards.\n/// Full withdrawals are from exited validators.\n/// @author Origin Protocol Inc\nabstract contract ValidatorAccountant is ValidatorRegistrator {\n /// @notice The minimum amount of blocks that need to pass between two calls to manuallyFixAccounting\n uint256 public constant MIN_FIX_ACCOUNTING_CADENCE = 7200; // 1 day\n\n /// @notice Keeps track of the total consensus rewards swept from the beacon chain\n uint256 public consensusRewards;\n\n /// @notice start of fuse interval\n uint256 public fuseIntervalStart;\n /// @notice end of fuse interval\n uint256 public fuseIntervalEnd;\n /// @notice last block number manuallyFixAccounting has been called\n uint256 public lastFixAccountingBlockNumber;\n\n uint256[49] private __gap;\n\n event FuseIntervalUpdated(uint256 start, uint256 end);\n event AccountingFullyWithdrawnValidator(\n uint256 noOfValidators,\n uint256 remainingValidators,\n uint256 wethSentToVault\n );\n event AccountingValidatorSlashed(\n uint256 remainingValidators,\n uint256 wethSentToVault\n );\n event AccountingConsensusRewards(uint256 amount);\n\n event AccountingManuallyFixed(\n int256 validatorsDelta,\n int256 consensusRewardsDelta,\n uint256 wethToVault\n );\n\n /// @param _wethAddress Address of the Erc20 WETH Token contract\n /// @param _vaultAddress Address of the Vault\n /// @param _beaconChainDepositContract Address of the beacon chain deposit contract\n /// @param _ssvNetwork Address of the SSV Network contract\n /// @param _maxValidators Maximum number of validators that can be registered in the strategy\n constructor(\n address _wethAddress,\n address _vaultAddress,\n address _beaconChainDepositContract,\n address _ssvNetwork,\n uint256 _maxValidators\n )\n ValidatorRegistrator(\n _wethAddress,\n _vaultAddress,\n _beaconChainDepositContract,\n _ssvNetwork,\n _maxValidators\n )\n {}\n\n /// @notice set fuse interval values\n function setFuseInterval(\n uint256 _fuseIntervalStart,\n uint256 _fuseIntervalEnd\n ) external onlyGovernor {\n require(\n _fuseIntervalStart < _fuseIntervalEnd &&\n _fuseIntervalEnd < 32 ether &&\n _fuseIntervalEnd - _fuseIntervalStart >= 4 ether,\n \"Incorrect fuse interval\"\n );\n\n fuseIntervalStart = _fuseIntervalStart;\n fuseIntervalEnd = _fuseIntervalEnd;\n\n emit FuseIntervalUpdated(_fuseIntervalStart, _fuseIntervalEnd);\n }\n\n /* solhint-disable max-line-length */\n /// This notion page offers a good explanation of how the accounting functions\n /// https://www.notion.so/originprotocol/Limited-simplified-native-staking-accounting-67a217c8420d40678eb943b9da0ee77d\n /// In short, after dividing by 32, if the ETH remaining on the contract falls between 0 and fuseIntervalStart,\n /// the accounting function will treat that ETH as Beacon chain consensus rewards.\n /// On the contrary, if after dividing by 32, the ETH remaining on the contract falls between fuseIntervalEnd and 32,\n /// the accounting function will treat that as a validator slashing.\n /// @notice Perform the accounting attributing beacon chain ETH to either full or partial withdrawals. Returns true when\n /// accounting is valid and fuse isn't \"blown\". Returns false when fuse is blown.\n /// @dev This function could in theory be permission-less but lets allow only the Registrator (Defender Action) to call it\n /// for now.\n /// @return accountingValid true if accounting was successful, false if fuse is blown\n /* solhint-enable max-line-length */\n function doAccounting()\n external\n onlyRegistrator\n whenNotPaused\n nonReentrant\n returns (bool accountingValid)\n {\n // pause the accounting on failure\n accountingValid = _doAccounting(true);\n }\n\n // slither-disable-start reentrancy-eth\n function _doAccounting(bool pauseOnFail)\n internal\n returns (bool accountingValid)\n {\n if (address(this).balance < consensusRewards) {\n return _failAccounting(pauseOnFail);\n }\n\n // Calculate all the new ETH that has been swept to the contract since the last accounting\n uint256 newSweptETH = address(this).balance - consensusRewards;\n accountingValid = true;\n\n // send the ETH that is from fully withdrawn validators to the Vault\n if (newSweptETH >= FULL_STAKE) {\n uint256 fullyWithdrawnValidators;\n // explicitly cast to uint256 as we want to round to a whole number of validators\n fullyWithdrawnValidators = uint256(newSweptETH / FULL_STAKE);\n activeDepositedValidators -= fullyWithdrawnValidators;\n\n uint256 wethToVault = FULL_STAKE * fullyWithdrawnValidators;\n IWETH9(WETH).deposit{ value: wethToVault }();\n // slither-disable-next-line unchecked-transfer\n IWETH9(WETH).transfer(VAULT_ADDRESS, wethToVault);\n _wethWithdrawnToVault(wethToVault);\n\n emit AccountingFullyWithdrawnValidator(\n fullyWithdrawnValidators,\n activeDepositedValidators,\n wethToVault\n );\n }\n\n uint256 ethRemaining = address(this).balance - consensusRewards;\n // should be less than a whole validator stake\n require(ethRemaining < FULL_STAKE, \"Unexpected accounting\");\n\n // If no Beacon chain consensus rewards swept\n if (ethRemaining == 0) {\n // do nothing\n return accountingValid;\n } else if (ethRemaining < fuseIntervalStart) {\n // Beacon chain consensus rewards swept (partial validator withdrawals)\n // solhint-disable-next-line reentrancy\n consensusRewards += ethRemaining;\n emit AccountingConsensusRewards(ethRemaining);\n } else if (ethRemaining > fuseIntervalEnd) {\n // Beacon chain consensus rewards swept but also a slashed validator fully exited\n IWETH9(WETH).deposit{ value: ethRemaining }();\n // slither-disable-next-line unchecked-transfer\n IWETH9(WETH).transfer(VAULT_ADDRESS, ethRemaining);\n activeDepositedValidators -= 1;\n\n _wethWithdrawnToVault(ethRemaining);\n\n emit AccountingValidatorSlashed(\n activeDepositedValidators,\n ethRemaining\n );\n }\n // Oh no... Fuse is blown. The Strategist needs to adjust the accounting values.\n else {\n return _failAccounting(pauseOnFail);\n }\n }\n\n // slither-disable-end reentrancy-eth\n\n /// @dev pause any further accounting if required and return false\n function _failAccounting(bool pauseOnFail)\n internal\n returns (bool accountingValid)\n {\n // pause if not already\n if (pauseOnFail) {\n _pause();\n }\n // fail the accounting\n accountingValid = false;\n }\n\n /// @notice Allow the Strategist to fix the accounting of this strategy and unpause.\n /// @param _validatorsDelta adjust the active validators by up to plus three or minus three\n /// @param _consensusRewardsDelta adjust the accounted for consensus rewards up or down\n /// @param _ethToVaultAmount the amount of ETH that gets wrapped into WETH and sent to the Vault\n /// @dev There is a case when a validator(s) gets slashed so much that the eth swept from\n /// the beacon chain enters the fuse area and there are no consensus rewards on the contract\n /// to \"dip into\"/use. To increase the amount of unaccounted ETH over the fuse end interval\n /// we need to reduce the amount of active deposited validators and immediately send WETH\n /// to the vault, so it doesn't interfere with further accounting.\n function manuallyFixAccounting(\n int256 _validatorsDelta,\n int256 _consensusRewardsDelta,\n uint256 _ethToVaultAmount\n ) external onlyStrategist whenPaused nonReentrant {\n require(\n lastFixAccountingBlockNumber + MIN_FIX_ACCOUNTING_CADENCE <\n block.number,\n \"Fix accounting called too soon\"\n );\n require(\n _validatorsDelta >= -3 &&\n _validatorsDelta <= 3 &&\n // new value must be positive\n int256(activeDepositedValidators) + _validatorsDelta >= 0,\n \"Invalid validatorsDelta\"\n );\n require(\n _consensusRewardsDelta >= -332 ether &&\n _consensusRewardsDelta <= 332 ether &&\n // new value must be positive\n int256(consensusRewards) + _consensusRewardsDelta >= 0,\n \"Invalid consensusRewardsDelta\"\n );\n require(_ethToVaultAmount <= 32 ether * 3, \"Invalid wethToVaultAmount\");\n\n activeDepositedValidators = uint256(\n int256(activeDepositedValidators) + _validatorsDelta\n );\n consensusRewards = uint256(\n int256(consensusRewards) + _consensusRewardsDelta\n );\n lastFixAccountingBlockNumber = block.number;\n if (_ethToVaultAmount > 0) {\n IWETH9(WETH).deposit{ value: _ethToVaultAmount }();\n // slither-disable-next-line unchecked-transfer\n IWETH9(WETH).transfer(VAULT_ADDRESS, _ethToVaultAmount);\n _wethWithdrawnToVault(_ethToVaultAmount);\n }\n\n emit AccountingManuallyFixed(\n _validatorsDelta,\n _consensusRewardsDelta,\n _ethToVaultAmount\n );\n\n // rerun the accounting to see if it has now been fixed.\n // Do not pause the accounting on failure as it is already paused\n require(_doAccounting(false), \"Fuse still blown\");\n\n // unpause since doAccounting was successful\n _unpause();\n }\n\n /***************************************\n Abstract\n ****************************************/\n\n /// @dev allows for NativeStakingSSVStrategy contract to emit the Withdrawal event\n function _wethWithdrawnToVault(uint256 _amount) internal virtual;\n}\n" + }, + "contracts/strategies/NativeStaking/ValidatorRegistrator.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { Pausable } from \"@openzeppelin/contracts/security/Pausable.sol\";\nimport { Governable } from \"../../governance/Governable.sol\";\nimport { IDepositContract } from \"../../interfaces/IDepositContract.sol\";\nimport { IVault } from \"../../interfaces/IVault.sol\";\nimport { IWETH9 } from \"../../interfaces/IWETH9.sol\";\nimport { ISSVNetwork, Cluster } from \"../../interfaces/ISSVNetwork.sol\";\n\nstruct ValidatorStakeData {\n bytes pubkey;\n bytes signature;\n bytes32 depositDataRoot;\n}\n\n/**\n * @title Registrator of the validators\n * @notice This contract implements all the required functionality to register, exit and remove validators.\n * @author Origin Protocol Inc\n */\nabstract contract ValidatorRegistrator is Governable, Pausable {\n /// @notice The maximum amount of ETH that can be staked by a validator\n /// @dev this can change in the future with EIP-7251, Increase the MAX_EFFECTIVE_BALANCE\n uint256 public constant FULL_STAKE = 32 ether;\n\n /// @notice The address of the Wrapped ETH (WETH) token contract\n address public immutable WETH;\n /// @notice The address of the beacon chain deposit contract\n address public immutable BEACON_CHAIN_DEPOSIT_CONTRACT;\n /// @notice The address of the SSV Network contract used to interface with\n address public immutable SSV_NETWORK;\n /// @notice Address of the OETH Vault proxy contract\n address public immutable VAULT_ADDRESS;\n /// @notice Maximum number of validators that can be registered in this strategy\n uint256 public immutable MAX_VALIDATORS;\n\n /// @notice Address of the registrator - allowed to register, exit and remove validators\n address public validatorRegistrator;\n /// @notice The number of validators that have 32 (!) ETH actively deposited. When a new deposit\n /// to a validator happens this number increases, when a validator exit is detected this number\n /// decreases.\n uint256 public activeDepositedValidators;\n /// @notice State of the validators keccak256(pubKey) => state\n mapping(bytes32 => VALIDATOR_STATE) public validatorsStates;\n /// @notice The account that is allowed to modify stakeETHThreshold and reset stakeETHTally\n address public stakingMonitor;\n /// @notice Amount of ETH that can be staked before staking on the contract is suspended\n /// and the `stakingMonitor` needs to approve further staking by calling `resetStakeETHTally`\n uint256 public stakeETHThreshold;\n /// @notice Amount of ETH that has been staked since the `stakingMonitor` last called `resetStakeETHTally`.\n /// This can not go above `stakeETHThreshold`.\n uint256 public stakeETHTally;\n // For future use\n uint256[47] private __gap;\n\n enum VALIDATOR_STATE {\n NON_REGISTERED, // validator is not registered on the SSV network\n REGISTERED, // validator is registered on the SSV network\n STAKED, // validator has funds staked\n EXITING, // exit message has been posted and validator is in the process of exiting\n EXIT_COMPLETE // validator has funds withdrawn to the EigenPod and is removed from the SSV\n }\n\n event RegistratorChanged(address indexed newAddress);\n event StakingMonitorChanged(address indexed newAddress);\n event ETHStaked(bytes32 indexed pubKeyHash, bytes pubKey, uint256 amount);\n event SSVValidatorRegistered(\n bytes32 indexed pubKeyHash,\n bytes pubKey,\n uint64[] operatorIds\n );\n event SSVValidatorExitInitiated(\n bytes32 indexed pubKeyHash,\n bytes pubKey,\n uint64[] operatorIds\n );\n event SSVValidatorExitCompleted(\n bytes32 indexed pubKeyHash,\n bytes pubKey,\n uint64[] operatorIds\n );\n event StakeETHThresholdChanged(uint256 amount);\n event StakeETHTallyReset();\n\n /// @dev Throws if called by any account other than the Registrator\n modifier onlyRegistrator() {\n require(\n msg.sender == validatorRegistrator,\n \"Caller is not the Registrator\"\n );\n _;\n }\n\n /// @dev Throws if called by any account other than the Staking monitor\n modifier onlyStakingMonitor() {\n require(msg.sender == stakingMonitor, \"Caller is not the Monitor\");\n _;\n }\n\n /// @dev Throws if called by any account other than the Strategist\n modifier onlyStrategist() {\n require(\n msg.sender == IVault(VAULT_ADDRESS).strategistAddr(),\n \"Caller is not the Strategist\"\n );\n _;\n }\n\n /// @param _wethAddress Address of the Erc20 WETH Token contract\n /// @param _vaultAddress Address of the Vault\n /// @param _beaconChainDepositContract Address of the beacon chain deposit contract\n /// @param _ssvNetwork Address of the SSV Network contract\n /// @param _maxValidators Maximum number of validators that can be registered in the strategy\n constructor(\n address _wethAddress,\n address _vaultAddress,\n address _beaconChainDepositContract,\n address _ssvNetwork,\n uint256 _maxValidators\n ) {\n WETH = _wethAddress;\n BEACON_CHAIN_DEPOSIT_CONTRACT = _beaconChainDepositContract;\n SSV_NETWORK = _ssvNetwork;\n VAULT_ADDRESS = _vaultAddress;\n MAX_VALIDATORS = _maxValidators;\n }\n\n /// @notice Set the address of the registrator which can register, exit and remove validators\n function setRegistrator(address _address) external onlyGovernor {\n validatorRegistrator = _address;\n emit RegistratorChanged(_address);\n }\n\n /// @notice Set the address of the staking monitor that is allowed to reset stakeETHTally\n function setStakingMonitor(address _address) external onlyGovernor {\n stakingMonitor = _address;\n emit StakingMonitorChanged(_address);\n }\n\n /// @notice Set the amount of ETH that can be staked before staking monitor\n // needs to a approve further staking by resetting the stake ETH tally\n function setStakeETHThreshold(uint256 _amount) external onlyGovernor {\n stakeETHThreshold = _amount;\n emit StakeETHThresholdChanged(_amount);\n }\n\n /// @notice Reset the stakeETHTally\n function resetStakeETHTally() external onlyStakingMonitor {\n stakeETHTally = 0;\n emit StakeETHTallyReset();\n }\n\n /// @notice Stakes WETH to the node validators\n /// @param validators A list of validator data needed to stake.\n /// The `ValidatorStakeData` struct contains the pubkey, signature and depositDataRoot.\n /// Only the registrator can call this function.\n // slither-disable-start reentrancy-eth\n function stakeEth(ValidatorStakeData[] calldata validators)\n external\n onlyRegistrator\n whenNotPaused\n nonReentrant\n {\n uint256 requiredETH = validators.length * FULL_STAKE;\n\n // Check there is enough WETH from the deposits sitting in this strategy contract\n require(\n requiredETH <= IWETH9(WETH).balanceOf(address(this)),\n \"Insufficient WETH\"\n );\n require(\n activeDepositedValidators + validators.length <= MAX_VALIDATORS,\n \"Max validators reached\"\n );\n\n require(\n stakeETHTally + requiredETH <= stakeETHThreshold,\n \"Staking ETH over threshold\"\n );\n stakeETHTally += requiredETH;\n\n // Convert required ETH from WETH\n IWETH9(WETH).withdraw(requiredETH);\n _wethWithdrawn(requiredETH);\n\n /* 0x01 to indicate that withdrawal credentials will contain an EOA address that the sweeping function\n * can sweep funds to.\n * bytes11(0) to fill up the required zeros\n * remaining bytes20 are for the address\n */\n bytes memory withdrawalCredentials = abi.encodePacked(\n bytes1(0x01),\n bytes11(0),\n address(this)\n );\n\n // For each validator\n for (uint256 i = 0; i < validators.length; ++i) {\n bytes32 pubKeyHash = keccak256(validators[i].pubkey);\n\n require(\n validatorsStates[pubKeyHash] == VALIDATOR_STATE.REGISTERED,\n \"Validator not registered\"\n );\n\n IDepositContract(BEACON_CHAIN_DEPOSIT_CONTRACT).deposit{\n value: FULL_STAKE\n }(\n validators[i].pubkey,\n withdrawalCredentials,\n validators[i].signature,\n validators[i].depositDataRoot\n );\n\n validatorsStates[pubKeyHash] = VALIDATOR_STATE.STAKED;\n\n emit ETHStaked(pubKeyHash, validators[i].pubkey, FULL_STAKE);\n }\n // save gas by changing this storage variable only once rather each time in the loop.\n activeDepositedValidators += validators.length;\n }\n\n // slither-disable-end reentrancy-eth\n\n /// @notice Registers a new validator in the SSV Cluster.\n /// Only the registrator can call this function.\n /// @param publicKeys The public keys of the validators\n /// @param operatorIds The operator IDs of the SSV Cluster\n /// @param sharesData The shares data for each validator\n /// @param ssvAmount The amount of SSV tokens to be deposited to the SSV cluster\n /// @param cluster The SSV cluster details including the validator count and SSV balance\n // slither-disable-start reentrancy-no-eth\n function registerSsvValidators(\n bytes[] calldata publicKeys,\n uint64[] calldata operatorIds,\n bytes[] calldata sharesData,\n uint256 ssvAmount,\n Cluster calldata cluster\n ) external onlyRegistrator whenNotPaused {\n require(\n publicKeys.length == sharesData.length,\n \"Pubkey sharesData mismatch\"\n );\n // Check each public key has not already been used\n bytes32 pubKeyHash;\n VALIDATOR_STATE currentState;\n for (uint256 i = 0; i < publicKeys.length; ++i) {\n pubKeyHash = keccak256(publicKeys[i]);\n currentState = validatorsStates[pubKeyHash];\n require(\n currentState == VALIDATOR_STATE.NON_REGISTERED,\n \"Validator already registered\"\n );\n\n validatorsStates[pubKeyHash] = VALIDATOR_STATE.REGISTERED;\n\n emit SSVValidatorRegistered(pubKeyHash, publicKeys[i], operatorIds);\n }\n\n ISSVNetwork(SSV_NETWORK).bulkRegisterValidator(\n publicKeys,\n operatorIds,\n sharesData,\n ssvAmount,\n cluster\n );\n }\n\n // slither-disable-end reentrancy-no-eth\n\n /// @notice Exit a validator from the Beacon chain.\n /// The staked ETH will eventually swept to this native staking strategy.\n /// Only the registrator can call this function.\n /// @param publicKey The public key of the validator\n /// @param operatorIds The operator IDs of the SSV Cluster\n // slither-disable-start reentrancy-no-eth\n function exitSsvValidator(\n bytes calldata publicKey,\n uint64[] calldata operatorIds\n ) external onlyRegistrator whenNotPaused {\n bytes32 pubKeyHash = keccak256(publicKey);\n VALIDATOR_STATE currentState = validatorsStates[pubKeyHash];\n require(currentState == VALIDATOR_STATE.STAKED, \"Validator not staked\");\n\n ISSVNetwork(SSV_NETWORK).exitValidator(publicKey, operatorIds);\n\n validatorsStates[pubKeyHash] = VALIDATOR_STATE.EXITING;\n\n emit SSVValidatorExitInitiated(pubKeyHash, publicKey, operatorIds);\n }\n\n // slither-disable-end reentrancy-no-eth\n\n /// @notice Remove a validator from the SSV Cluster.\n /// Make sure `exitSsvValidator` is called before and the validate has exited the Beacon chain.\n /// If removed before the validator has exited the beacon chain will result in the validator being slashed.\n /// Only the registrator can call this function.\n /// @param publicKey The public key of the validator\n /// @param operatorIds The operator IDs of the SSV Cluster\n /// @param cluster The SSV cluster details including the validator count and SSV balance\n // slither-disable-start reentrancy-no-eth\n function removeSsvValidator(\n bytes calldata publicKey,\n uint64[] calldata operatorIds,\n Cluster calldata cluster\n ) external onlyRegistrator whenNotPaused {\n bytes32 pubKeyHash = keccak256(publicKey);\n VALIDATOR_STATE currentState = validatorsStates[pubKeyHash];\n // Can remove SSV validators that were incorrectly registered and can not be deposited to.\n require(\n currentState == VALIDATOR_STATE.EXITING ||\n currentState == VALIDATOR_STATE.REGISTERED,\n \"Validator not regd or exiting\"\n );\n\n ISSVNetwork(SSV_NETWORK).removeValidator(\n publicKey,\n operatorIds,\n cluster\n );\n\n validatorsStates[pubKeyHash] = VALIDATOR_STATE.EXIT_COMPLETE;\n\n emit SSVValidatorExitCompleted(pubKeyHash, publicKey, operatorIds);\n }\n\n // slither-disable-end reentrancy-no-eth\n\n /// @notice Deposits more SSV Tokens to the SSV Network contract which is used to pay the SSV Operators.\n /// @dev A SSV cluster is defined by the SSVOwnerAddress and the set of operatorIds.\n /// uses \"onlyStrategist\" modifier so continuous front-running can't DOS our maintenance service\n /// that tries to top up SSV tokens.\n /// @param operatorIds The operator IDs of the SSV Cluster\n /// @param ssvAmount The amount of SSV tokens to be deposited to the SSV cluster\n /// @param cluster The SSV cluster details including the validator count and SSV balance\n function depositSSV(\n uint64[] memory operatorIds,\n uint256 ssvAmount,\n Cluster memory cluster\n ) external onlyStrategist {\n ISSVNetwork(SSV_NETWORK).deposit(\n address(this),\n operatorIds,\n ssvAmount,\n cluster\n );\n }\n\n /// @notice Withdraws excess SSV Tokens from the SSV Network contract which was used to pay the SSV Operators.\n /// @dev A SSV cluster is defined by the SSVOwnerAddress and the set of operatorIds.\n /// @param operatorIds The operator IDs of the SSV Cluster\n /// @param ssvAmount The amount of SSV tokens to be deposited to the SSV cluster\n /// @param cluster The SSV cluster details including the validator count and SSV balance\n function withdrawSSV(\n uint64[] memory operatorIds,\n uint256 ssvAmount,\n Cluster memory cluster\n ) external onlyGovernor {\n ISSVNetwork(SSV_NETWORK).withdraw(operatorIds, ssvAmount, cluster);\n }\n\n /***************************************\n Abstract\n ****************************************/\n\n /// @dev Called when WETH is withdrawn from the strategy or staked to a validator so\n /// the strategy knows how much WETH it has on deposit.\n /// This is so it can emit the correct amount in the Deposit event in depositAll().\n function _wethWithdrawn(uint256 _amount) internal virtual;\n}\n" + }, + "contracts/strategies/plume/RoosterAMOStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title Rooster AMO strategy\n * @author Origin Protocol Inc\n * @custom:security-contact security@originprotocol.com\n */\nimport { Math as MathRooster } from \"../../../lib/rooster/v2-common/libraries/Math.sol\";\nimport { Math as Math_v5 } from \"../../../lib/rooster/openzeppelin-custom/contracts/utils/math/Math.sol\";\nimport { StableMath } from \"../../utils/StableMath.sol\";\n\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport { SafeCast } from \"@openzeppelin/contracts/utils/math/SafeCast.sol\";\n\nimport { IERC20, InitializableAbstractStrategy } from \"../../utils/InitializableAbstractStrategy.sol\";\nimport { IVault } from \"../../interfaces/IVault.sol\";\nimport { IMaverickV2Pool } from \"../../interfaces/plume/IMaverickV2Pool.sol\";\nimport { IMaverickV2Quoter } from \"../../interfaces/plume/IMaverickV2Quoter.sol\";\nimport { IMaverickV2LiquidityManager } from \"../../interfaces/plume/IMaverickV2LiquidityManager.sol\";\nimport { IMaverickV2PoolLens } from \"../../interfaces/plume/IMaverickV2PoolLens.sol\";\nimport { IMaverickV2Position } from \"../../interfaces/plume/IMaverickV2Position.sol\";\nimport { IVotingDistributor } from \"../../interfaces/plume/IVotingDistributor.sol\";\nimport { IPoolDistributor } from \"../../interfaces/plume/IPoolDistributor.sol\";\n// importing custom version of rooster TickMath because of dependency collision. Maverick uses\n// a newer OpenZepplin Math library with functionality that is not present in 4.4.2 (the one we use)\nimport { TickMath } from \"../../../lib/rooster/v2-common/libraries/TickMath.sol\";\n\ncontract RoosterAMOStrategy is InitializableAbstractStrategy {\n using StableMath for uint256;\n using SafeERC20 for IERC20;\n using SafeCast for uint256;\n\n /***************************************\n Storage slot members\n ****************************************/\n\n /// @notice NFT tokenId of the liquidity position\n ///\n /// @dev starts with value of 1 and can not be 0\n // solhint-disable-next-line max-line-length\n /// https://github.com/rooster-protocol/rooster-contracts/blob/fbfecbc519e4495b12598024a42630b4a8ea4489/v2-common/contracts/base/Nft.sol#L14\n uint256 public tokenId;\n /// @dev Minimum amount of tokens the strategy would be able to withdraw from the pool.\n /// minimum amount of tokens are withdrawn at a 1:1 price\n /// Important: Underlying assets contains only assets that are deposited in the underlying Rooster pool.\n /// WETH or OETH held by this contract is not accounted for in underlying assets\n uint256 public underlyingAssets;\n /// @notice Marks the start of the interval that defines the allowed range of WETH share in\n /// the pre-configured pool's liquidity ticker\n uint256 public allowedWethShareStart;\n /// @notice Marks the end of the interval that defines the allowed range of WETH share in\n /// the pre-configured pool's liquidity ticker\n uint256 public allowedWethShareEnd;\n /// @dev reserved for inheritance\n int256[46] private __reserved;\n\n /***************************************\n Constants, structs and events\n ****************************************/\n\n /// @notice The address of the Wrapped ETH (WETH) token contract\n address public immutable WETH;\n /// @notice The address of the OETH token contract\n address public immutable OETH;\n /// @notice the underlying AMO Maverick (Rooster) pool\n IMaverickV2Pool public immutable mPool;\n /// @notice the Liquidity manager used to add liquidity to the pool\n IMaverickV2LiquidityManager public immutable liquidityManager;\n /// @notice the Maverick V2 poolLens\n ///\n /// @dev only used to provide the pool's current sqrtPrice\n IMaverickV2PoolLens public immutable poolLens;\n /// @notice the Maverick V2 position\n ///\n /// @dev provides details of the NFT LP position and offers functions to\n /// remove the liquidity.\n IMaverickV2Position public immutable maverickPosition;\n /// @notice the Maverick Quoter\n IMaverickV2Quoter public immutable quoter;\n /// @notice the Maverick Voting Distributor\n IVotingDistributor public immutable votingDistributor;\n /// @notice the Maverick Pool Distributor\n IPoolDistributor public immutable poolDistributor;\n\n /// @notice sqrtPriceTickLower\n /// @dev tick lower represents the lower price of OETH priced in WETH. Meaning the pool\n /// offers more than 1 OETH for 1 WETH. In other terms to get 1 OETH the swap needs to offer 0.9999 WETH\n /// this is where purchasing OETH with WETH within the liquidity position is the cheapest.\n ///\n /// _____________________\n /// | | |\n /// | WETH | OETH |\n /// | | |\n /// | | |\n /// --------- * ---- * ---------- * ---------\n /// currentPrice\n /// sqrtPriceHigher-(1:1 parity)\n /// sqrtPriceLower\n ///\n ///\n /// Price is defined as price of token1 in terms of token0. (token1 / token0)\n /// @notice sqrtPriceTickLower - OETH is priced 0.9999 WETH\n uint256 public immutable sqrtPriceTickLower;\n /// @notice sqrtPriceTickHigher\n /// @dev tick higher represents 1:1 price parity of WETH to OETH\n uint256 public immutable sqrtPriceTickHigher;\n /// @dev price at parity (in OETH this is equal to sqrtPriceTickHigher)\n uint256 public immutable sqrtPriceAtParity;\n /// @notice The tick where the strategy deploys the liquidity to\n int32 public constant TICK_NUMBER = -1;\n /// @notice Minimum liquidity that must be exceeded to continue with the action\n /// e.g. deposit, add liquidity\n uint256 public constant ACTION_THRESHOLD = 1e12;\n /// @notice Maverick pool static liquidity bin type\n uint8 public constant MAV_STATIC_BIN_KIND = 0;\n /// @dev a threshold under which the contract no longer allows for the protocol to rebalance. Guarding\n /// against a strategist / guardian being taken over and with multiple transactions draining the\n /// protocol funds.\n uint256 public constant SOLVENCY_THRESHOLD = 0.998 ether;\n /// @notice Emitted when the allowed interval within which the strategy contract is allowed to deposit\n /// liquidity to the underlying pool is updated.\n /// @param allowedWethShareStart The start of the interval\n /// @param allowedWethShareEnd The end of the interval\n event PoolWethShareIntervalUpdated(\n uint256 allowedWethShareStart,\n uint256 allowedWethShareEnd\n );\n /// @notice Emitted when liquidity is removed from the underlying pool\n /// @param withdrawLiquidityShare Share of strategy's liquidity that has been removed\n /// @param removedWETHAmount The amount of WETH removed\n /// @param removedOETHAmount The amount of OETH removed\n /// @param underlyingAssets Updated amount of strategy's underlying assets\n event LiquidityRemoved(\n uint256 withdrawLiquidityShare,\n uint256 removedWETHAmount,\n uint256 removedOETHAmount,\n uint256 underlyingAssets\n );\n\n /// @notice Emitted when the underlying pool is rebalanced\n /// @param currentPoolWethShare The resulting share of strategy's liquidity\n /// in the TICK_NUMBER\n event PoolRebalanced(uint256 currentPoolWethShare);\n\n /// @notice Emitted when the amount of underlying assets the strategy hold as\n /// liquidity in the pool is updated.\n /// @param underlyingAssets Updated amount of strategy's underlying assets\n event UnderlyingAssetsUpdated(uint256 underlyingAssets);\n\n /// @notice Emitted when liquidity is added to the underlying pool\n /// @param wethAmountDesired Amount of WETH desired to be deposited\n /// @param oethAmountDesired Amount of OETH desired to be deposited\n /// @param wethAmountSupplied Amount of WETH deposited\n /// @param oethAmountSupplied Amount of OETH deposited\n /// @param tokenId NFT liquidity token id\n /// @param underlyingAssets Updated amount of underlying assets\n event LiquidityAdded(\n uint256 wethAmountDesired,\n uint256 oethAmountDesired,\n uint256 wethAmountSupplied,\n uint256 oethAmountSupplied,\n uint256 tokenId,\n uint256 underlyingAssets\n ); // 0x1530ec74\n\n error PoolRebalanceOutOfBounds(\n uint256 currentPoolWethShare,\n uint256 allowedWethShareStart,\n uint256 allowedWethShareEnd\n ); // 0x3681e8e0\n\n error NotEnoughWethForSwap(uint256 wethBalance, uint256 requiredWeth); // 0x989e5ca8\n error NotEnoughWethLiquidity(uint256 wethBalance, uint256 requiredWeth); // 0xa6737d87\n error OutsideExpectedTickRange(); // 0xa6e1bad2\n error SlippageCheck(uint256 tokenReceived); // 0x355cdb78\n\n /// @notice the constructor\n /// @dev This contract is intended to be used as a proxy. To prevent the\n /// potential confusion of having a functional implementation contract\n /// the constructor has the `initializer` modifier. This way the\n /// `initialize` function can not be called on the implementation contract.\n /// For the same reason the implementation contract also has the governor\n /// set to a zero address.\n /// @param _stratConfig the basic strategy configuration\n /// @param _wethAddress Address of the Erc20 WETH Token contract\n /// @param _oethAddress Address of the Erc20 OETH Token contract\n /// @param _liquidityManager Address of liquidity manager to add\n /// the liquidity\n /// @param _poolLens Address of the pool lens contract\n /// @param _maverickPosition Address of the Maverick's position contract\n /// @param _maverickQuoter Address of the Maverick's Quoter contract\n /// @param _mPool Address of the Rooster concentrated liquidity pool\n /// @param _upperTickAtParity Bool when true upperTick is the one where the\n /// price of OETH and WETH are at parity\n constructor(\n BaseStrategyConfig memory _stratConfig,\n address _wethAddress,\n address _oethAddress,\n address _liquidityManager,\n address _poolLens,\n address _maverickPosition,\n address _maverickQuoter,\n address _mPool,\n bool _upperTickAtParity,\n address _votingDistributor,\n address _poolDistributor\n ) initializer InitializableAbstractStrategy(_stratConfig) {\n require(\n address(IMaverickV2Pool(_mPool).tokenA()) == _wethAddress,\n \"WETH not TokenA\"\n );\n require(\n address(IMaverickV2Pool(_mPool).tokenB()) == _oethAddress,\n \"OETH not TokenB\"\n );\n require(\n _liquidityManager != address(0),\n \"LiquidityManager zero address not allowed\"\n );\n require(\n _maverickQuoter != address(0),\n \"Quoter zero address not allowed\"\n );\n require(_poolLens != address(0), \"PoolLens zero address not allowed\");\n require(\n _maverickPosition != address(0),\n \"Position zero address not allowed\"\n );\n require(\n _votingDistributor != address(0),\n \"Voting distributor zero address not allowed\"\n );\n require(\n _poolDistributor != address(0),\n \"Pool distributor zero address not allowed\"\n );\n\n uint256 _tickSpacing = IMaverickV2Pool(_mPool).tickSpacing();\n require(_tickSpacing == 1, \"Unsupported tickSpacing\");\n\n // tickSpacing == 1\n (sqrtPriceTickLower, sqrtPriceTickHigher) = TickMath.tickSqrtPrices(\n _tickSpacing,\n TICK_NUMBER\n );\n sqrtPriceAtParity = _upperTickAtParity\n ? sqrtPriceTickHigher\n : sqrtPriceTickLower;\n\n WETH = _wethAddress;\n OETH = _oethAddress;\n liquidityManager = IMaverickV2LiquidityManager(_liquidityManager);\n poolLens = IMaverickV2PoolLens(_poolLens);\n maverickPosition = IMaverickV2Position(_maverickPosition);\n quoter = IMaverickV2Quoter(_maverickQuoter);\n mPool = IMaverickV2Pool(_mPool);\n votingDistributor = IVotingDistributor(_votingDistributor);\n poolDistributor = IPoolDistributor(_poolDistributor);\n\n // prevent implementation contract to be governed\n _setGovernor(address(0));\n }\n\n /**\n * @notice initialize function, to set up initial internal state\n */\n function initialize() external onlyGovernor initializer {\n // Read reward\n address[] memory _rewardTokens = new address[](1);\n _rewardTokens[0] = poolDistributor.rewardToken();\n\n require(_rewardTokens[0] != address(0), \"No reward token configured\");\n\n InitializableAbstractStrategy._initialize(\n _rewardTokens,\n new address[](0),\n new address[](0)\n );\n }\n\n /***************************************\n Configuration \n ****************************************/\n\n /**\n * @notice Set allowed pool weth share interval. After the rebalance happens\n * the share of WETH token in the ticker needs to be within the specifications\n * of the interval.\n *\n * @param _allowedWethShareStart Start of WETH share interval expressed as 18 decimal amount\n * @param _allowedWethShareEnd End of WETH share interval expressed as 18 decimal amount\n */\n function setAllowedPoolWethShareInterval(\n uint256 _allowedWethShareStart,\n uint256 _allowedWethShareEnd\n ) external onlyGovernor {\n require(\n _allowedWethShareStart < _allowedWethShareEnd,\n \"Invalid interval\"\n );\n // can not go below 1% weth share\n require(_allowedWethShareStart > 0.01 ether, \"Invalid interval start\");\n // can not go above 95% weth share\n require(_allowedWethShareEnd < 0.95 ether, \"Invalid interval end\");\n\n allowedWethShareStart = _allowedWethShareStart;\n allowedWethShareEnd = _allowedWethShareEnd;\n emit PoolWethShareIntervalUpdated(\n _allowedWethShareStart,\n _allowedWethShareEnd\n );\n }\n\n /***************************************\n Strategy overrides \n ****************************************/\n\n /**\n * @notice Deposits funds to the strategy which deposits them to the\n * underlying Rooster pool if the pool price is within the expected interval.\n * @param _asset Address for the asset\n * @param _amount Units of asset to deposit\n */\n function deposit(address _asset, uint256 _amount)\n external\n override\n onlyVault\n nonReentrant\n {\n _deposit(_asset, _amount);\n }\n\n /**\n * @notice Deposits all the funds to the strategy which deposits them to the\n * underlying Rooster pool if the pool price is within the expected interval.\n */\n function depositAll() external override onlyVault nonReentrant {\n uint256 _wethBalance = IERC20(WETH).balanceOf(address(this));\n _deposit(WETH, _wethBalance);\n }\n\n /**\n * @dev Deposits funds to the strategy which deposits them to the\n * underlying Rooster pool if the pool price is within the expected interval.\n * Before this function can be called the initial pool position needs to already\n * be minted.\n * @param _asset Address of the asset to deposit\n * @param _amount Amount of assets to deposit\n */\n function _deposit(address _asset, uint256 _amount) internal {\n require(_asset == WETH, \"Unsupported asset\");\n require(_amount > 0, \"Must deposit something\");\n require(tokenId > 0, \"Initial position not minted\");\n emit Deposit(_asset, address(0), _amount);\n\n // if the pool price is not within the expected interval leave the WETH on the contract\n // as to not break the mints - in case it would be configured as a default asset strategy\n (bool _isExpectedRange, ) = _checkForExpectedPoolPrice(false);\n if (_isExpectedRange) {\n // deposit funds into the underlying pool. Because no swap is performed there is no\n // need to remove any of the liquidity beforehand.\n _rebalance(0, false, 0, 0);\n }\n }\n\n /**\n * @notice Withdraw an `amount` of WETH from the platform and\n * send to the `_recipient`.\n * @param _recipient Address to which the asset should be sent\n * @param _asset WETH address\n * @param _amount Amount of WETH to withdraw\n */\n function withdraw(\n address _recipient,\n address _asset,\n uint256 _amount\n ) external override onlyVault nonReentrant {\n require(_asset == WETH, \"Unsupported asset\");\n require(_amount > 0, \"Must withdraw something\");\n require(_recipient == vaultAddress, \"Only withdraw to vault allowed\");\n\n _ensureWETHBalance(_amount);\n\n _withdraw(_recipient, _amount);\n }\n\n /**\n * @notice Withdraw WETH and sends it to the Vault.\n */\n function withdrawAll() external override onlyVault nonReentrant {\n if (tokenId != 0) {\n _removeLiquidity(1e18);\n }\n\n uint256 _balance = IERC20(WETH).balanceOf(address(this));\n if (_balance > 0) {\n _withdraw(vaultAddress, _balance);\n }\n }\n\n function _withdraw(address _recipient, uint256 _amount) internal {\n IERC20(WETH).safeTransfer(_recipient, _amount);\n emit Withdrawal(WETH, address(0), _amount);\n }\n\n /**\n * @dev Retuns bool indicating whether asset is supported by strategy\n * @param _asset Address of the asset\n * @return bool True when the _asset is WETH\n */\n function supportsAsset(address _asset) public view override returns (bool) {\n return _asset == WETH;\n }\n\n /**\n * @dev Approve the spending amounts for the assets\n */\n function _approveTokenAmounts(\n uint256 _wethAllowance,\n uint256 _oethAllowance\n ) internal {\n IERC20(WETH).approve(address(liquidityManager), _wethAllowance);\n IERC20(OETH).approve(address(liquidityManager), _oethAllowance);\n }\n\n /***************************************\n Liquidity management\n ****************************************/\n /**\n * @dev Add liquidity into the pool in the pre-configured WETH to OETH share ratios\n * defined by the allowedPoolWethShareStart|End interval.\n *\n * Normally a PoolLens contract is used to prepare the parameters to add liquidity to the\n * Rooster pools. It has some errors when doing those calculation and for that reason a\n * much more accurate Quoter contract is used. This is possible due to our requirement of\n * adding liquidity only to one tick - PoolLens supports adding liquidity into multiple ticks\n * using different distribution ratios.\n */\n function _addLiquidity() internal {\n uint256 _wethBalance = IERC20(WETH).balanceOf(address(this));\n uint256 _oethBalance = IERC20(OETH).balanceOf(address(this));\n // don't deposit small liquidity amounts\n if (_wethBalance <= ACTION_THRESHOLD) {\n return;\n }\n\n (\n bytes memory packedSqrtPriceBreaks,\n bytes[] memory packedArgs,\n uint256 WETHRequired,\n uint256 OETHRequired\n ) = _getAddLiquidityParams(_wethBalance, 1e30);\n\n if (OETHRequired > _oethBalance) {\n IVault(vaultAddress).mintForStrategy(OETHRequired - _oethBalance);\n }\n\n _approveTokenAmounts(WETHRequired, OETHRequired);\n\n (\n uint256 _wethAmount,\n uint256 _oethAmount,\n uint32[] memory binIds\n ) = liquidityManager.addPositionLiquidityToSenderByTokenIndex(\n mPool,\n 0, // NFT token index\n packedSqrtPriceBreaks,\n packedArgs\n );\n\n require(binIds.length == 1, \"Unexpected binIds length\");\n\n // burn remaining OETH\n _burnOethOnTheContract();\n _updateUnderlyingAssets();\n\n // needs to be called after _updateUnderlyingAssets so the updated amount\n // is reflected in the event\n emit LiquidityAdded(\n _wethBalance, // wethAmountDesired\n OETHRequired, // oethAmountDesired\n _wethAmount, // wethAmountSupplied\n _oethAmount, // oethAmountSupplied\n tokenId, // tokenId\n underlyingAssets\n );\n }\n\n /**\n * @dev The function creates liquidity parameters required to be able to add liquidity to the pool.\n * The function needs to handle the 3 different cases of the way liquidity is added:\n * - only WETH present in the tick\n * - only OETH present in the tick\n * - both tokens present in the tick\n *\n */\n function _getAddLiquidityParams(uint256 _maxWETH, uint256 _maxOETH)\n internal\n returns (\n bytes memory packedSqrtPriceBreaks,\n bytes[] memory packedArgs,\n uint256 WETHRequired,\n uint256 OETHRequired\n )\n {\n IMaverickV2Pool.AddLiquidityParams[]\n memory addParams = new IMaverickV2Pool.AddLiquidityParams[](1);\n int32[] memory ticks = new int32[](1);\n uint128[] memory amounts = new uint128[](1);\n ticks[0] = TICK_NUMBER;\n // arbitrary LP amount\n amounts[0] = 1e24;\n\n // construct value for Quoter with arbitrary LP amount\n IMaverickV2Pool.AddLiquidityParams memory addParam = IMaverickV2Pool\n .AddLiquidityParams({\n kind: MAV_STATIC_BIN_KIND,\n ticks: ticks,\n amounts: amounts\n });\n\n // get the WETH and OETH required to get the proportion of tokens required\n // given the arbitrary liquidity\n (WETHRequired, OETHRequired, ) = quoter.calculateAddLiquidity(\n mPool,\n addParam\n );\n\n /**\n * If either token required is 0 then the tick consists only of the other token. In that\n * case the liquidity calculations need to be done using the non 0 token. By setting the\n * tokenRequired from 0 to 1 the `min` in next step will ignore that (the bigger) value.\n */\n WETHRequired = WETHRequired == 0 ? 1 : WETHRequired;\n OETHRequired = OETHRequired == 0 ? 1 : OETHRequired;\n\n addParam.amounts[0] = Math_v5\n .min(\n ((_maxWETH - 1) * 1e24) / WETHRequired,\n ((_maxOETH - 1) * 1e24) / OETHRequired\n )\n .toUint128();\n\n // update the quotes with the actual amounts\n (WETHRequired, OETHRequired, ) = quoter.calculateAddLiquidity(\n mPool,\n addParam\n );\n\n require(_maxWETH >= WETHRequired, \"More WETH required than specified\");\n require(_maxOETH >= OETHRequired, \"More OETH required than specified\");\n\n // organize values to be used by manager\n addParams[0] = addParam;\n packedArgs = liquidityManager.packAddLiquidityArgsArray(addParams);\n // price can stay 0 if array only has one element\n packedSqrtPriceBreaks = liquidityManager.packUint88Array(\n new uint88[](1)\n );\n }\n\n /**\n * @dev Check that the Rooster pool price is within the expected\n * parameters.\n * This function works whether the strategy contract has liquidity\n * position in the pool or not. The function returns _wethSharePct\n * as a gas optimization measure.\n * @param _throwException when set to true the function throws an exception\n * when pool's price is not within expected range.\n * @return _isExpectedRange Bool expressing price is within expected range\n * @return _wethSharePct Share of WETH owned by this strategy contract in the\n * configured ticker.\n */\n function _checkForExpectedPoolPrice(bool _throwException)\n internal\n view\n returns (bool _isExpectedRange, uint256 _wethSharePct)\n {\n require(\n allowedWethShareStart != 0 && allowedWethShareEnd != 0,\n \"Weth share interval not set\"\n );\n\n uint256 _currentPrice = getPoolSqrtPrice();\n\n /**\n * First check pool price is in expected tick range\n *\n * A revert is issued even though price being equal to the lower bound as that can not\n * be within the approved tick range.\n */\n if (\n _currentPrice <= sqrtPriceTickLower ||\n _currentPrice >= sqrtPriceTickHigher\n ) {\n if (_throwException) {\n revert OutsideExpectedTickRange();\n }\n\n return (false, _currentPrice <= sqrtPriceTickLower ? 0 : 1e18);\n }\n\n // 18 decimal number expressed WETH tick share\n _wethSharePct = _getWethShare(_currentPrice);\n\n if (\n _wethSharePct < allowedWethShareStart ||\n _wethSharePct > allowedWethShareEnd\n ) {\n if (_throwException) {\n revert PoolRebalanceOutOfBounds(\n _wethSharePct,\n allowedWethShareStart,\n allowedWethShareEnd\n );\n }\n return (false, _wethSharePct);\n }\n\n return (true, _wethSharePct);\n }\n\n /**\n * @notice Rebalance the pool to the desired token split and Deposit any WETH on the contract to the\n * underlying rooster pool. Print the required amount of corresponding OETH. After the rebalancing is\n * done burn any potentially remaining OETH tokens still on the strategy contract.\n *\n * This function has a slightly different behaviour depending on the status of the underlying Rooster\n * pool. The function consists of the following 3 steps:\n * 1. withdrawLiquidityOption -> this is a configurable option where either only part of the liquidity\n * necessary for the swap is removed, or all of it. This way the rebalance\n * is able to optimize for volume, for efficiency or anything in between\n * 2. swapToDesiredPosition -> move active trading price in the pool to be able to deposit WETH & OETH\n * tokens with the desired pre-configured ratios\n * 3. addLiquidity -> add liquidity into the pool respecting ratio split configuration\n *\n *\n * Exact _amountToSwap, _swapWeth & _minTokenReceived parameters shall be determined by simulating the\n * transaction off-chain. The strategy checks that after the swap the share of the tokens is in the\n * expected ranges.\n *\n * @param _amountToSwap The amount of the token to swap\n * @param _swapWeth Swap using WETH when true, use OETH when false\n * @param _minTokenReceived Slippage check -> minimum amount of token expected in return\n * @param _liquidityToRemovePct Percentage of liquidity to remove -> the percentage amount of liquidity to\n * remove before performing the swap. 1e18 denominated\n */\n function rebalance(\n uint256 _amountToSwap,\n bool _swapWeth,\n uint256 _minTokenReceived,\n uint256 _liquidityToRemovePct\n ) external nonReentrant onlyGovernorOrStrategist {\n _rebalance(\n _amountToSwap,\n _swapWeth,\n _minTokenReceived,\n _liquidityToRemovePct\n );\n }\n\n // slither-disable-start reentrancy-no-eth\n function _rebalance(\n uint256 _amountToSwap,\n bool _swapWeth,\n uint256 _minTokenReceived,\n uint256 _liquidityToRemovePct\n ) internal {\n // Remove the required amount of liquidity\n if (_liquidityToRemovePct > 0) {\n _removeLiquidity(_liquidityToRemovePct);\n }\n\n // in some cases (e.g. deposits) we will just want to add liquidity and not\n // issue a swap to move the active trading position within the pool. Before or after a\n // deposit or as a standalone call the strategist might issue a rebalance to move the\n // active trading price to a more desired position.\n if (_amountToSwap > 0) {\n // In case liquidity has been removed and there is still not enough WETH owned by the\n // strategy contract remove additional required amount of WETH.\n if (_swapWeth) _ensureWETHBalance(_amountToSwap);\n\n _swapToDesiredPosition(_amountToSwap, _swapWeth, _minTokenReceived);\n }\n\n // calling check liquidity early so we don't get unexpected errors when adding liquidity\n // in the later stages of this function\n _checkForExpectedPoolPrice(true);\n\n _addLiquidity();\n\n // this call shouldn't be necessary, since adding liquidity shouldn't affect the active\n // trading price. It is a defensive programming measure.\n (, uint256 _wethSharePct) = _checkForExpectedPoolPrice(true);\n\n // revert if protocol insolvent\n _solvencyAssert();\n\n emit PoolRebalanced(_wethSharePct);\n }\n\n // slither-disable-end reentrancy-no-eth\n\n /**\n * @dev Perform a swap so that after the swap the tick has the desired WETH to OETH token share.\n */\n function _swapToDesiredPosition(\n uint256 _amountToSwap,\n bool _swapWeth,\n uint256 _minTokenReceived\n ) internal {\n IERC20 _tokenToSwap = IERC20(_swapWeth ? WETH : OETH);\n uint256 _balance = _tokenToSwap.balanceOf(address(this));\n\n if (_balance < _amountToSwap) {\n // This should never trigger since _ensureWETHBalance will already\n // throw an error if there is not enough WETH\n if (_swapWeth) {\n revert NotEnoughWethForSwap(_balance, _amountToSwap);\n }\n // if swapping OETH\n uint256 mintForSwap = _amountToSwap - _balance;\n IVault(vaultAddress).mintForStrategy(mintForSwap);\n }\n\n // SafeERC20 is used for IERC20 transfers. Not sure why slither complains\n // slither-disable-next-line unchecked-transfer\n _tokenToSwap.transfer(address(mPool), _amountToSwap);\n\n // tickLimit: the furthest tick a swap will execute in. If no limit is desired,\n // value should be set to type(int32).max for a tokenAIn (WETH) swap\n // and type(int32).min for a swap where tokenB (OETH) is the input\n\n IMaverickV2Pool.SwapParams memory swapParams = IMaverickV2Pool\n // exactOutput defines whether the amount specified is the output\n // or the input amount of the swap\n .SwapParams({\n amount: _amountToSwap,\n tokenAIn: _swapWeth,\n exactOutput: false,\n tickLimit: TICK_NUMBER\n });\n\n // swaps without a callback as the assets are already sent to the pool\n (, uint256 amountOut) = mPool.swap(\n address(this),\n swapParams,\n bytes(\"\")\n );\n\n /**\n * There could be additional checks here for validating minTokenReceived is within the\n * expected range (e.g. 99% - 101% of the token sent in). Though that doesn't provide\n * any additional security. After the swap the `_checkForExpectedPoolPrice` validates\n * that the swap has moved the price into the expected tick (# -1).\n *\n * If the guardian forgets to set a `_minTokenReceived` and a sandwich attack bends\n * the pool before the swap the `_checkForExpectedPoolPrice` will fail the transaction.\n *\n * A check would not prevent a compromised guardian from stealing funds as multiple\n * transactions each loosing smaller amount of funds are still possible.\n */\n if (amountOut < _minTokenReceived) {\n revert SlippageCheck(amountOut);\n }\n\n /**\n * In the interest of each function in `_rebalance` to leave the contract state as\n * clean as possible the OETH tokens here are burned. This decreases the\n * dependence where `_swapToDesiredPosition` function relies on later functions\n * (`addLiquidity`) to burn the OETH. Reducing the risk of error introduction.\n */\n _burnOethOnTheContract();\n }\n\n /**\n * @dev This function removes the appropriate amount of liquidity to ensure that the required\n * amount of WETH is available on the contract\n *\n * @param _amount WETH balance required on the contract\n */\n function _ensureWETHBalance(uint256 _amount) internal {\n uint256 _wethBalance = IERC20(WETH).balanceOf(address(this));\n if (_wethBalance >= _amount) {\n return;\n }\n\n require(tokenId != 0, \"No liquidity available\");\n uint256 _additionalWethRequired = _amount - _wethBalance;\n (uint256 _wethInThePool, ) = getPositionPrincipal();\n\n if (_wethInThePool < _additionalWethRequired) {\n revert NotEnoughWethLiquidity(\n _wethInThePool,\n _additionalWethRequired\n );\n }\n\n uint256 shareOfWethToRemove = _wethInThePool <= 1\n ? 1e18\n : Math_v5.min(\n /**\n * When dealing with shares of liquidity to remove there is always some\n * rounding involved. After extensive fuzz testing the below approach\n * yielded the best results where the strategy overdraws the least and\n * never removes insufficient amount of WETH.\n */\n (_additionalWethRequired + 2).divPrecisely(_wethInThePool - 1) +\n 2,\n 1e18\n );\n\n _removeLiquidity(shareOfWethToRemove);\n }\n\n /**\n * @dev Decrease partial or all liquidity from the pool.\n * @param _liquidityToDecrease The amount of liquidity to remove denominated in 1e18\n */\n function _removeLiquidity(uint256 _liquidityToDecrease) internal {\n require(_liquidityToDecrease > 0, \"Must remove some liquidity\");\n require(\n _liquidityToDecrease <= 1e18,\n \"Can not remove more than 100% of liquidity\"\n );\n\n // 0 indicates the first (and only) bin in the NFT LP position.\n IMaverickV2Pool.RemoveLiquidityParams memory params = maverickPosition\n .getRemoveParams(tokenId, 0, _liquidityToDecrease);\n (uint256 _amountWeth, uint256 _amountOeth) = maverickPosition\n .removeLiquidityToSender(tokenId, mPool, params);\n\n _burnOethOnTheContract();\n _updateUnderlyingAssets();\n\n // needs to be called after the _updateUnderlyingAssets so the updated amount is reflected\n // in the event\n emit LiquidityRemoved(\n _liquidityToDecrease,\n _amountWeth,\n _amountOeth,\n underlyingAssets\n );\n }\n\n /**\n * @dev Burns any OETH tokens remaining on the strategy contract if the balance is\n * above the action threshold.\n */\n function _burnOethOnTheContract() internal {\n uint256 _oethBalance = IERC20(OETH).balanceOf(address(this));\n IVault(vaultAddress).burnForStrategy(_oethBalance);\n }\n\n /**\n * @notice Returns the percentage of WETH liquidity in the configured ticker\n * owned by this strategy contract.\n * @return uint256 1e18 denominated percentage expressing the share\n */\n function getWETHShare() external view returns (uint256) {\n uint256 _currentPrice = getPoolSqrtPrice();\n return _getWethShare(_currentPrice);\n }\n\n /**\n * @dev Returns the share of WETH in tick denominated in 1e18\n */\n function _getWethShare(uint256 _currentPrice)\n internal\n view\n returns (uint256)\n {\n (\n uint256 wethAmount,\n uint256 oethAmount\n ) = _reservesInTickForGivenPriceAndLiquidity(\n sqrtPriceTickLower,\n sqrtPriceTickHigher,\n _currentPrice,\n 1e24\n );\n\n return wethAmount.divPrecisely(wethAmount + oethAmount);\n }\n\n /**\n * @notice Returns the current pool price in square root\n * @return Square root of the pool price\n */\n function getPoolSqrtPrice() public view returns (uint256) {\n return poolLens.getPoolSqrtPrice(mPool);\n }\n\n /**\n * @notice Returns the current active trading tick of the underlying pool\n * @return _currentTick Current pool trading tick\n */\n function getCurrentTradingTick() public view returns (int32 _currentTick) {\n _currentTick = mPool.getState().activeTick;\n }\n\n /**\n * @notice Mint the initial NFT position\n *\n * @dev This amount is \"gifted\" to the strategy contract and will count as a yield\n * surplus.\n */\n // slither-disable-start reentrancy-no-eth\n function mintInitialPosition() external onlyGovernor nonReentrant {\n require(tokenId == 0, \"Initial position already minted\");\n (\n bytes memory packedSqrtPriceBreaks,\n bytes[] memory packedArgs,\n uint256 WETHRequired,\n uint256 OETHRequired\n ) = _getAddLiquidityParams(1e16, 1e16);\n\n // Mint rounded up OETH amount\n if (OETHRequired > 0) {\n IVault(vaultAddress).mintForStrategy(OETHRequired);\n }\n\n _approveTokenAmounts(WETHRequired, OETHRequired);\n\n // Store the tokenId before calling updateUnderlyingAssets as it relies on the tokenId\n // not being 0\n (, , , tokenId) = liquidityManager.mintPositionNftToSender(\n mPool,\n packedSqrtPriceBreaks,\n packedArgs\n );\n\n // burn remaining OETH\n _burnOethOnTheContract();\n _updateUnderlyingAssets();\n }\n\n // slither-disable-end reentrancy-no-eth\n\n /**\n * @notice Returns the balance of tokens the strategy holds in the LP position\n * @return _amountWeth Amount of WETH in position\n * @return _amountOeth Amount of OETH in position\n */\n function getPositionPrincipal()\n public\n view\n returns (uint256 _amountWeth, uint256 _amountOeth)\n {\n if (tokenId == 0) {\n return (0, 0);\n }\n\n (_amountWeth, _amountOeth, ) = _getPositionInformation();\n }\n\n /**\n * @dev Returns the balance of tokens the strategy holds in the LP position\n * @return _amountWeth Amount of WETH in position\n * @return _amountOeth Amount of OETH in position\n * @return liquidity Amount of liquidity in the position\n */\n function _getPositionInformation()\n internal\n view\n returns (\n uint256 _amountWeth,\n uint256 _amountOeth,\n uint256 liquidity\n )\n {\n IMaverickV2Position.PositionFullInformation\n memory positionInfo = maverickPosition.tokenIdPositionInformation(\n tokenId,\n 0\n );\n\n require(\n positionInfo.liquidities.length == 1,\n \"Unexpected liquidities length\"\n );\n require(positionInfo.ticks.length == 1, \"Unexpected ticks length\");\n\n _amountWeth = positionInfo.amountA;\n _amountOeth = positionInfo.amountB;\n liquidity = positionInfo.liquidities[0];\n }\n\n /**\n * Checks that the protocol is solvent, protecting from a rogue Strategist / Guardian that can\n * keep rebalancing the pool in both directions making the protocol lose a tiny amount of\n * funds each time.\n *\n * Protocol must be at least SOLVENCY_THRESHOLD (99.8%) backed in order for the rebalances to\n * function.\n */\n function _solvencyAssert() internal view {\n uint256 _totalVaultValue = IVault(vaultAddress).totalValue();\n uint256 _totalOethSupply = IERC20(OETH).totalSupply();\n\n if (\n _totalVaultValue.divPrecisely(_totalOethSupply) < SOLVENCY_THRESHOLD\n ) {\n revert(\"Protocol insolvent\");\n }\n }\n\n /**\n * @dev Collect Rooster reward token, and send it to the harvesterAddress\n */\n function collectRewardTokens()\n external\n override\n onlyHarvester\n nonReentrant\n {\n // Do nothing if there's no position minted\n if (tokenId > 0) {\n uint32[] memory binIds = new uint32[](1);\n IMaverickV2Pool.TickState memory tickState = mPool.getTick(\n TICK_NUMBER\n );\n // get the binId for the MAV_STATIC_BIN_KIND in tick TICK_NUMBER (-1)\n binIds[0] = tickState.binIdsByTick[0];\n\n uint256 lastEpoch = votingDistributor.lastEpoch();\n\n poolDistributor.claimLp(\n address(this),\n tokenId,\n mPool,\n binIds,\n lastEpoch\n );\n }\n\n // Run the internal inherited function\n _collectRewardTokens();\n }\n\n /***************************************\n Balances and Fees\n ****************************************/\n\n /**\n * @dev Get the total asset value held in the platform\n * @param _asset Address of the asset\n * @return balance Total value of the asset in the platform\n */\n function checkBalance(address _asset)\n external\n view\n override\n returns (uint256)\n {\n require(_asset == WETH, \"Only WETH supported\");\n\n // because of PoolLens inaccuracy there is usually some dust WETH left on the contract\n uint256 _wethBalance = IERC20(WETH).balanceOf(address(this));\n // just paranoia check, in case there is OETH in the strategy that for some reason hasn't\n // been burned yet. This should always be 0.\n uint256 _oethBalance = IERC20(OETH).balanceOf(address(this));\n return underlyingAssets + _wethBalance + _oethBalance;\n }\n\n /// @dev This function updates the amount of underlying assets with the approach of the least possible\n /// total tokens extracted for the current liquidity in the pool.\n function _updateUnderlyingAssets() internal {\n /**\n * Our net value represent the smallest amount of tokens we are able to extract from the position\n * given our liquidity.\n *\n * The least amount of tokens ex-tractable from the position is where the active trading price is\n * at the edge between tick -1 & tick 0. There the pool is offering 1:1 trades between WETH & OETH.\n * At that moment the pool consists completely of WETH and no OETH.\n *\n * The more swaps from OETH -> WETH happen on the pool the more the price starts to move away from the tick 0\n * towards the middle of tick -1 making OETH (priced in WETH) cheaper.\n */\n\n uint256 _wethAmount = tokenId == 0 ? 0 : _balanceInPosition();\n\n underlyingAssets = _wethAmount;\n emit UnderlyingAssetsUpdated(_wethAmount);\n }\n\n /**\n * @dev Strategy reserves (which consist only of WETH in case of Rooster - Plume pool)\n * when the tick price is closest to parity - assuring the lowest amount of tokens\n * returned for the current position liquidity.\n */\n function _balanceInPosition() internal view returns (uint256 _wethBalance) {\n (, , uint256 liquidity) = _getPositionInformation();\n\n uint256 _oethBalance;\n\n (_wethBalance, _oethBalance) = _reservesInTickForGivenPriceAndLiquidity(\n sqrtPriceTickLower,\n sqrtPriceTickHigher,\n sqrtPriceAtParity,\n liquidity\n );\n\n require(_oethBalance == 0, \"Non zero oethBalance\");\n }\n\n /**\n * @notice Tick dominance denominated in 1e18\n * @return _tickDominance The share of liquidity in TICK_NUMBER tick owned\n * by the strategy contract denominated in 1e18\n */\n function tickDominance() public view returns (uint256 _tickDominance) {\n IMaverickV2Pool.TickState memory tickState = mPool.getTick(TICK_NUMBER);\n\n uint256 wethReserve = tickState.reserveA;\n uint256 oethReserve = tickState.reserveB;\n\n // prettier-ignore\n (uint256 _amountWeth, uint256 _amountOeth, ) = _getPositionInformation();\n\n if (wethReserve + oethReserve == 0) {\n return 0;\n }\n\n _tickDominance = (_amountWeth + _amountOeth).divPrecisely(\n wethReserve + oethReserve\n );\n }\n\n /***************************************\n Hidden functions\n ****************************************/\n /**\n * @dev Unsupported\n */\n function setPTokenAddress(address, address) external pure override {\n // The pool tokens can never change.\n revert(\"Unsupported method\");\n }\n\n /**\n * @dev Unsupported\n */\n function removePToken(uint256) external pure override {\n // The pool tokens can never change.\n revert(\"Unsupported method\");\n }\n\n /**\n * @dev Unsupported\n */\n function _abstractSetPToken(address, address) internal pure override {\n revert(\"Unsupported method\");\n }\n\n /**\n * @dev Unsupported\n */\n function safeApproveAllTokens() external pure override {\n // all the amounts are approved at the time required\n revert(\"Unsupported method\");\n }\n\n /***************************************\n Maverick liquidity utilities\n ****************************************/\n\n /// @notice Calculates deltaA = liquidity * (sqrt(upper) - sqrt(lower))\n /// Calculates deltaB = liquidity / sqrt(lower) - liquidity / sqrt(upper),\n /// i.e. liquidity * (sqrt(upper) - sqrt(lower)) / (sqrt(upper) * sqrt(lower))\n ///\n /// @dev refactored from here:\n // solhint-disable-next-line max-line-length\n /// https://github.com/rooster-protocol/rooster-contracts/blob/main/v2-supplemental/contracts/libraries/LiquidityUtilities.sol#L665-L695\n function _reservesInTickForGivenPriceAndLiquidity(\n uint256 _lowerSqrtPrice,\n uint256 _upperSqrtPrice,\n uint256 _newSqrtPrice,\n uint256 _liquidity\n ) internal pure returns (uint128 reserveA, uint128 reserveB) {\n if (_liquidity == 0) {\n (reserveA, reserveB) = (0, 0);\n } else {\n uint256 lowerEdge = MathRooster.max(_lowerSqrtPrice, _newSqrtPrice);\n\n reserveA = MathRooster\n .mulCeil(\n _liquidity,\n MathRooster.clip(\n MathRooster.min(_upperSqrtPrice, _newSqrtPrice),\n _lowerSqrtPrice\n )\n )\n .toUint128();\n reserveB = MathRooster\n .mulDivCeil(\n _liquidity,\n 1e18 * MathRooster.clip(_upperSqrtPrice, lowerEdge),\n _upperSqrtPrice * lowerEdge\n )\n .toUint128();\n }\n }\n}\n" + }, + "contracts/strategies/sonic/SonicStakingStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { Math } from \"@openzeppelin/contracts/utils/math/Math.sol\";\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport { SonicValidatorDelegator } from \"./SonicValidatorDelegator.sol\";\nimport { IWrappedSonic } from \"../../interfaces/sonic/IWrappedSonic.sol\";\n\n/**\n * @title Staking Strategy for Sonic's native S currency\n * @author Origin Protocol Inc\n */\ncontract SonicStakingStrategy is SonicValidatorDelegator {\n // For future use\n uint256[50] private __gap;\n\n constructor(\n BaseStrategyConfig memory _baseConfig,\n address _wrappedSonic,\n address _sfc\n ) SonicValidatorDelegator(_baseConfig, _wrappedSonic, _sfc) {}\n\n /// @notice Deposit wrapped S asset into the underlying platform.\n /// @param _asset Address of asset to deposit. Has to be Wrapped Sonic (wS).\n /// @param _amount Amount of assets that were transferred to the strategy by the vault.\n function deposit(address _asset, uint256 _amount)\n external\n override\n onlyVault\n nonReentrant\n {\n require(_asset == wrappedSonic, \"Unsupported asset\");\n _deposit(_asset, _amount);\n }\n\n /**\n * @notice Deposit Wrapped Sonic (wS) to this strategy and delegate to a validator.\n * @param _asset Address of Wrapped Sonic (wS) token\n * @param _amount Amount of Wrapped Sonic (wS) to deposit\n */\n function _deposit(address _asset, uint256 _amount) internal virtual {\n require(_amount > 0, \"Must deposit something\");\n\n _delegate(_amount);\n emit Deposit(_asset, address(0), _amount);\n }\n\n /**\n * @notice Deposit the entire balance of wrapped S in this strategy contract into\n * the underlying platform.\n */\n function depositAll() external virtual override onlyVault nonReentrant {\n uint256 wSBalance = IERC20(wrappedSonic).balanceOf(address(this));\n\n if (wSBalance > 0) {\n _deposit(wrappedSonic, wSBalance);\n }\n }\n\n /// @notice Withdraw Wrapped Sonic (wS) from this strategy contract.\n /// Used only if some wS is lingering on the contract.\n /// That can happen only when someone sends wS directly to this contract\n /// @param _recipient Address to receive withdrawn assets\n /// @param _asset Address of the Wrapped Sonic (wS) token\n /// @param _amount Amount of Wrapped Sonic (wS) to withdraw\n function withdraw(\n address _recipient,\n address _asset,\n uint256 _amount\n ) external override onlyVault nonReentrant {\n require(_asset == wrappedSonic, \"Unsupported asset\");\n _withdraw(_recipient, _asset, _amount);\n }\n\n function _withdraw(\n address _recipient,\n address _asset,\n uint256 _amount\n ) internal override {\n require(_amount > 0, \"Must withdraw something\");\n require(_recipient != address(0), \"Must specify recipient\");\n\n // slither-disable-next-line unchecked-transfer unused-return\n IERC20(_asset).transfer(_recipient, _amount);\n\n emit Withdrawal(wrappedSonic, address(0), _amount);\n }\n\n /// @notice Transfer all Wrapped Sonic (wS) deposits back to the vault.\n /// This does not withdraw from delegated validators. That has to be done separately with `undelegate`.\n /// Any native S in this strategy will be withdrawn.\n function withdrawAll() external override onlyVaultOrGovernor nonReentrant {\n uint256 balance = address(this).balance;\n if (balance > 0) {\n IWrappedSonic(wrappedSonic).deposit{ value: balance }();\n }\n uint256 wSBalance = IERC20(wrappedSonic).balanceOf(address(this));\n if (wSBalance > 0) {\n _withdraw(vaultAddress, wrappedSonic, wSBalance);\n }\n }\n\n /**\n * @dev Returns bool indicating whether asset is supported by strategy\n * @param _asset Address of the asset token\n */\n function supportsAsset(address _asset)\n public\n view\n virtual\n override\n returns (bool)\n {\n return _asset == wrappedSonic;\n }\n\n /**\n * @notice is not supported for this strategy as the\n * Wrapped Sonic (wS) token is set at deploy time.\n */\n function setPTokenAddress(address, address)\n external\n view\n override\n onlyGovernor\n {\n revert(\"unsupported function\");\n }\n\n /// @notice is not used by this strategy as all staking rewards are restaked\n function collectRewardTokens() external override nonReentrant {\n revert(\"unsupported function\");\n }\n\n /**\n * @notice is not supported for this strategy as the\n * Wrapped Sonic (wS) token is set at deploy time.\n */\n function removePToken(uint256) external view override onlyGovernor {\n revert(\"unsupported function\");\n }\n\n /// @dev is not used by this strategy but must be implemented as it's abstract\n /// in the inherited `InitializableAbstractStrategy` contract.\n function _abstractSetPToken(address, address) internal virtual override {}\n\n /// @notice is not used by this strategy\n function safeApproveAllTokens() external override onlyGovernor {}\n}\n" + }, + "contracts/strategies/sonic/SonicSwapXAMOStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title SwapX Algorithmic Market Maker (AMO) Strategy\n * @notice AMO strategy for the SwapX OS/wS stable pool\n * @author Origin Protocol Inc\n */\nimport { SafeCast } from \"@openzeppelin/contracts/utils/math/SafeCast.sol\";\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\n\nimport { IERC20, InitializableAbstractStrategy } from \"../../utils/InitializableAbstractStrategy.sol\";\nimport { StableMath } from \"../../utils/StableMath.sol\";\nimport { sqrt } from \"../../utils/PRBMath.sol\";\nimport { IBasicToken } from \"../../interfaces/IBasicToken.sol\";\nimport { IPair } from \"../../interfaces/sonic/ISwapXPair.sol\";\nimport { IGauge } from \"../../interfaces/sonic/ISwapXGauge.sol\";\nimport { IVault } from \"../../interfaces/IVault.sol\";\n\ncontract SonicSwapXAMOStrategy is InitializableAbstractStrategy {\n using SafeERC20 for IERC20;\n using StableMath for uint256;\n using SafeCast for uint256;\n\n /**\n * @notice a threshold under which the contract no longer allows for the protocol to manually rebalance.\n * Guarding against a strategist / guardian being taken over and with multiple transactions\n * draining the protocol funds.\n */\n uint256 public constant SOLVENCY_THRESHOLD = 0.998 ether;\n\n /// @notice Precision for the SwapX Stable AMM (sAMM) invariant k.\n uint256 public constant PRECISION = 1e18;\n\n /// @notice Address of the Wrapped S (wS) token.\n address public immutable ws;\n\n /// @notice Address of the OS token contract.\n address public immutable os;\n\n /// @notice Address of the SwapX Stable pool contract.\n address public immutable pool;\n\n /// @notice Address of the SwapX Gauge contract.\n address public immutable gauge;\n\n /// @notice The max amount the OS/wS price can deviate from peg (1e18)\n /// before deposits are reverted scaled to 18 decimals.\n /// eg 0.01e18 or 1e16 is 1% which is 100 basis points.\n /// This is the amount below and above peg so a 50 basis point deviation (0.005e18)\n /// allows a price range from 0.995 to 1.005.\n uint256 public maxDepeg;\n\n event SwapOTokensToPool(\n uint256 osMinted,\n uint256 wsDepositAmount,\n uint256 osDepositAmount,\n uint256 lpTokens\n );\n event SwapAssetsToPool(\n uint256 wsSwapped,\n uint256 lpTokens,\n uint256 osBurnt\n );\n event MaxDepegUpdated(uint256 maxDepeg);\n\n /**\n * @dev Verifies that the caller is the Strategist of the Vault.\n */\n modifier onlyStrategist() {\n require(\n msg.sender == IVault(vaultAddress).strategistAddr(),\n \"Caller is not the Strategist\"\n );\n _;\n }\n\n /**\n * @dev Skim the SwapX pool in case any extra wS or OS tokens were added\n */\n modifier skimPool() {\n IPair(pool).skim(address(this));\n _;\n }\n\n /**\n * @dev Checks the pool is balanced enough to allow deposits.\n */\n modifier nearBalancedPool() {\n // OS/wS price = wS / OS\n // Get the OS/wS price for selling 1 OS for wS\n // As OS is 1, the wS amount is the OS/wS price\n uint256 sellPrice = IPair(pool).getAmountOut(1e18, os);\n\n // Get the amount of OS received from selling 1 wS. This is buying OS.\n uint256 osAmount = IPair(pool).getAmountOut(1e18, ws);\n // Convert to a OS/wS price = wS / OS\n uint256 buyPrice = 1e36 / osAmount;\n\n uint256 pegPrice = 1e18;\n\n require(\n sellPrice >= pegPrice - maxDepeg && buyPrice <= pegPrice + maxDepeg,\n \"price out of range\"\n );\n _;\n }\n\n /**\n * @dev Checks the pool's balances have improved and the balances\n * have not tipped to the other side.\n * This modifier is only applied to functions that do swaps against the pool.\n * Deposits and withdrawals are proportional to the pool's balances hence don't need this check.\n */\n modifier improvePoolBalance() {\n // Get the asset and OToken balances in the pool\n (uint256 wsReservesBefore, uint256 osReservesBefore, ) = IPair(pool)\n .getReserves();\n // diff = wS balance - OS balance\n int256 diffBefore = wsReservesBefore.toInt256() -\n osReservesBefore.toInt256();\n\n _;\n\n // Get the asset and OToken balances in the pool\n (uint256 wsReservesAfter, uint256 osReservesAfter, ) = IPair(pool)\n .getReserves();\n // diff = wS balance - OS balance\n int256 diffAfter = wsReservesAfter.toInt256() -\n osReservesAfter.toInt256();\n\n if (diffBefore == 0) {\n require(diffAfter == 0, \"Position balance is worsened\");\n } else if (diffBefore < 0) {\n // If the pool was originally imbalanced in favor of OS, then\n // we want to check that the pool is now more balanced\n require(diffAfter <= 0, \"Assets overshot peg\");\n require(diffBefore < diffAfter, \"OTokens balance worse\");\n } else if (diffBefore > 0) {\n // If the pool was originally imbalanced in favor of wS, then\n // we want to check that the pool is now more balanced\n require(diffAfter >= 0, \"OTokens overshot peg\");\n require(diffAfter < diffBefore, \"Assets balance worse\");\n }\n }\n\n /**\n * @param _baseConfig The `platformAddress` is the address of the SwapX pool.\n * The `vaultAddress` is the address of the Origin Sonic Vault.\n * @param _os Address of the OS token.\n * @param _ws Address of the Wrapped S (wS) token.\n * @param _gauge Address of the SwapX gauge for the pool.\n */\n constructor(\n BaseStrategyConfig memory _baseConfig,\n address _os,\n address _ws,\n address _gauge\n ) InitializableAbstractStrategy(_baseConfig) {\n // Check the pool tokens are correct\n require(\n IPair(_baseConfig.platformAddress).token0() == _ws &&\n IPair(_baseConfig.platformAddress).token1() == _os,\n \"Incorrect pool tokens\"\n );\n // Checked both tokens are to 18 decimals\n require(\n IBasicToken(_ws).decimals() == 18 &&\n IBasicToken(_os).decimals() == 18,\n \"Incorrect token decimals\"\n );\n // Check the SwapX pool is a Stable AMM (sAMM)\n require(\n IPair(_baseConfig.platformAddress).isStable() == true,\n \"Pool not stable\"\n );\n // Check the gauge is for the pool\n require(\n IGauge(_gauge).TOKEN() == _baseConfig.platformAddress,\n \"Incorrect gauge\"\n );\n\n // Set the immutable variables\n os = _os;\n ws = _ws;\n pool = _baseConfig.platformAddress;\n gauge = _gauge;\n\n // This is an implementation contract. The governor is set in the proxy contract.\n _setGovernor(address(0));\n }\n\n /**\n * Initializer for setting up strategy internal state. This overrides the\n * InitializableAbstractStrategy initializer as SwapX strategies don't fit\n * well within that abstraction.\n * @param _rewardTokenAddresses Array containing SWPx token address\n * @param _maxDepeg The max amount the OS/wS price can deviate from peg (1e18) before deposits are reverted.\n */\n function initialize(\n address[] calldata _rewardTokenAddresses,\n uint256 _maxDepeg\n ) external onlyGovernor initializer {\n address[] memory pTokens = new address[](1);\n pTokens[0] = pool;\n\n address[] memory _assets = new address[](1);\n _assets[0] = ws;\n\n InitializableAbstractStrategy._initialize(\n _rewardTokenAddresses,\n _assets,\n pTokens\n );\n\n maxDepeg = _maxDepeg;\n\n _approveBase();\n }\n\n /***************************************\n Deposit\n ****************************************/\n\n /**\n * @notice Deposit an amount of Wrapped S (wS) into the SwapX pool.\n * Mint OS in proportion to the pool's wS and OS reserves,\n * transfer Wrapped S (wS) and OS to the pool,\n * mint the pool's LP token and deposit in the gauge.\n * @dev This tx must be wrapped by the VaultValueChecker.\n * To minimize loses, the pool should be rebalanced before depositing.\n * The pool's OS/wS price must be within the maxDepeg range.\n * @param _asset Address of Wrapped S (wS) token.\n * @param _wsAmount Amount of Wrapped S (wS) tokens to deposit.\n */\n function deposit(address _asset, uint256 _wsAmount)\n external\n override\n onlyVault\n nonReentrant\n skimPool\n nearBalancedPool\n {\n require(_asset == ws, \"Unsupported asset\");\n require(_wsAmount > 0, \"Must deposit something\");\n\n (uint256 osDepositAmount, ) = _deposit(_wsAmount);\n\n // Ensure solvency of the vault\n _solvencyAssert();\n\n // Emit event for the deposited wS tokens\n emit Deposit(ws, pool, _wsAmount);\n // Emit event for the minted OS tokens\n emit Deposit(os, pool, osDepositAmount);\n }\n\n /**\n * @notice Deposit all the strategy's Wrapped S (wS) tokens into the SwapX pool.\n * Mint OS in proportion to the pool's wS and OS reserves,\n * transfer Wrapped S (wS) and OS to the pool,\n * mint the pool's LP token and deposit in the gauge.\n * @dev This tx must be wrapped by the VaultValueChecker.\n * To minimize loses, the pool should be rebalanced before depositing.\n * The pool's OS/wS price must be within the maxDepeg range.\n */\n function depositAll()\n external\n override\n onlyVault\n nonReentrant\n skimPool\n nearBalancedPool\n {\n uint256 wsBalance = IERC20(ws).balanceOf(address(this));\n if (wsBalance > 0) {\n (uint256 osDepositAmount, ) = _deposit(wsBalance);\n\n // Ensure solvency of the vault\n _solvencyAssert();\n\n // Emit event for the deposited wS tokens\n emit Deposit(ws, pool, wsBalance);\n // Emit event for the minted OS tokens\n emit Deposit(os, pool, osDepositAmount);\n }\n }\n\n /**\n * @dev Mint OS in proportion to the pool's wS and OS reserves,\n * transfer Wrapped S (wS) and OS to the pool,\n * mint the pool's LP token and deposit in the gauge.\n * @param _wsAmount Amount of Wrapped S (wS) tokens to deposit.\n * @return osDepositAmount Amount of OS tokens minted and deposited into the pool.\n * @return lpTokens Amount of SwapX pool LP tokens minted and deposited into the gauge.\n */\n function _deposit(uint256 _wsAmount)\n internal\n returns (uint256 osDepositAmount, uint256 lpTokens)\n {\n // Calculate the required amount of OS to mint based on the wS amount.\n osDepositAmount = _calcTokensToMint(_wsAmount);\n\n // Mint the required OS tokens to this strategy\n IVault(vaultAddress).mintForStrategy(osDepositAmount);\n\n // Add wS and OS liquidity to the pool and stake in gauge\n lpTokens = _depositToPoolAndGauge(_wsAmount, osDepositAmount);\n }\n\n /***************************************\n Withdraw\n ****************************************/\n\n /**\n * @notice Withdraw wS and OS from the SwapX pool, burn the OS,\n * and transfer the wS to the recipient.\n * @param _recipient Address of the Vault.\n * @param _asset Address of the Wrapped S (wS) contract.\n * @param _wsAmount Amount of Wrapped S (wS) to withdraw.\n */\n function withdraw(\n address _recipient,\n address _asset,\n uint256 _wsAmount\n ) external override onlyVault nonReentrant skimPool {\n require(_wsAmount > 0, \"Must withdraw something\");\n require(_asset == ws, \"Unsupported asset\");\n // This strategy can't be set as a default strategy for wS in the Vault.\n // This means the recipient must always be the Vault.\n require(_recipient == vaultAddress, \"Only withdraw to vault allowed\");\n\n // Calculate how much pool LP tokens to burn to get the required amount of wS tokens back\n uint256 lpTokens = _calcTokensToBurn(_wsAmount);\n\n // Withdraw pool LP tokens from the gauge and remove assets from from the pool\n _withdrawFromGaugeAndPool(lpTokens);\n\n // Burn all the removed OS and any that was left in the strategy\n uint256 osToBurn = IERC20(os).balanceOf(address(this));\n IVault(vaultAddress).burnForStrategy(osToBurn);\n\n // Transfer wS to the recipient\n // Note there can be a dust amount of wS left in the strategy as\n // the burn of the pool's LP tokens is rounded up\n require(\n IERC20(ws).balanceOf(address(this)) >= _wsAmount,\n \"Not enough wS removed from pool\"\n );\n IERC20(ws).safeTransfer(_recipient, _wsAmount);\n\n // Ensure solvency of the vault\n _solvencyAssert();\n\n // Emit event for the withdrawn wS tokens\n emit Withdrawal(ws, pool, _wsAmount);\n // Emit event for the burnt OS tokens\n emit Withdrawal(os, pool, osToBurn);\n }\n\n /**\n * @notice Withdraw all pool LP tokens from the gauge,\n * remove all wS and OS from the SwapX pool,\n * burn all the OS tokens,\n * and transfer all the wS to the Vault contract.\n * @dev There is no solvency check here as withdrawAll can be called to\n * quickly secure assets to the Vault in emergencies.\n */\n function withdrawAll()\n external\n override\n onlyVaultOrGovernor\n nonReentrant\n skimPool\n {\n // Get all the pool LP tokens the strategy has staked in the gauge\n uint256 lpTokens = IGauge(gauge).balanceOf(address(this));\n // Can not withdraw zero LP tokens from the gauge\n if (lpTokens == 0) return;\n\n if (IGauge(gauge).emergency()) {\n // The gauge is in emergency mode\n _emergencyWithdrawFromGaugeAndPool();\n } else {\n // Withdraw pool LP tokens from the gauge and remove assets from from the pool\n _withdrawFromGaugeAndPool(lpTokens);\n }\n\n // Burn all OS in this strategy contract\n uint256 osToBurn = IERC20(os).balanceOf(address(this));\n IVault(vaultAddress).burnForStrategy(osToBurn);\n\n // Get the strategy contract's wS balance.\n // This includes all that was removed from the SwapX pool and\n // any that was sitting in the strategy contract before the removal.\n uint256 wsBalance = IERC20(ws).balanceOf(address(this));\n IERC20(ws).safeTransfer(vaultAddress, wsBalance);\n\n // Emit event for the withdrawn wS tokens\n emit Withdrawal(ws, pool, wsBalance);\n // Emit event for the burnt OS tokens\n emit Withdrawal(os, pool, osToBurn);\n }\n\n /***************************************\n Pool Rebalancing\n ****************************************/\n\n /** @notice Used when there is more OS than wS in the pool.\n * wS and OS is removed from the pool, the received wS is swapped for OS\n * and the left over OS in the strategy is burnt.\n * The OS/wS price is < 1.0 so OS is being bought at a discount.\n * @param _wsAmount Amount of Wrapped S (wS) to swap into the pool.\n */\n function swapAssetsToPool(uint256 _wsAmount)\n external\n onlyStrategist\n nonReentrant\n improvePoolBalance\n skimPool\n {\n require(_wsAmount > 0, \"Must swap something\");\n\n // 1. Partially remove liquidity so there’s enough wS for the swap\n\n // Calculate how much pool LP tokens to burn to get the required amount of wS tokens back\n uint256 lpTokens = _calcTokensToBurn(_wsAmount);\n require(lpTokens > 0, \"No LP tokens to burn\");\n\n _withdrawFromGaugeAndPool(lpTokens);\n\n // 2. Swap wS for OS against the pool\n // Swap exact amount of wS for OS against the pool\n // There can be a dust amount of wS left in the strategy as the burn of the pool's LP tokens is rounded up\n _swapExactTokensForTokens(_wsAmount, ws, os);\n\n // 3. Burn all the OS left in the strategy from the remove liquidity and swap\n uint256 osToBurn = IERC20(os).balanceOf(address(this));\n IVault(vaultAddress).burnForStrategy(osToBurn);\n\n // Ensure solvency of the vault\n _solvencyAssert();\n\n // Emit event for the burnt OS tokens\n emit Withdrawal(os, pool, osToBurn);\n // Emit event for the swap\n emit SwapAssetsToPool(_wsAmount, lpTokens, osToBurn);\n }\n\n /**\n * @notice Used when there is more wS than OS in the pool.\n * OS is minted and swapped for wS against the pool,\n * more OS is minted and added back into the pool with the swapped out wS.\n * The OS/wS price is > 1.0 so OS is being sold at a premium.\n * @param _osAmount Amount of OS to swap into the pool.\n */\n function swapOTokensToPool(uint256 _osAmount)\n external\n onlyStrategist\n nonReentrant\n improvePoolBalance\n skimPool\n {\n require(_osAmount > 0, \"Must swap something\");\n\n // 1. Mint OS so it can be swapped into the pool\n\n // There can be OS in the strategy from skimming the pool\n uint256 osInStrategy = IERC20(os).balanceOf(address(this));\n require(_osAmount >= osInStrategy, \"Too much OS in strategy\");\n uint256 osToMint = _osAmount - osInStrategy;\n\n // Mint the required OS tokens to this strategy\n IVault(vaultAddress).mintForStrategy(osToMint);\n\n // 2. Swap OS for wS against the pool\n _swapExactTokensForTokens(_osAmount, os, ws);\n\n // The wS is from the swap and any wS that was sitting in the strategy\n uint256 wsDepositAmount = IERC20(ws).balanceOf(address(this));\n\n // 3. Add wS and OS back to the pool in proportion to the pool's reserves\n (uint256 osDepositAmount, uint256 lpTokens) = _deposit(wsDepositAmount);\n\n // Ensure solvency of the vault\n _solvencyAssert();\n\n // Emit event for the minted OS tokens\n emit Deposit(os, pool, osToMint + osDepositAmount);\n // Emit event for the swap\n emit SwapOTokensToPool(\n osToMint,\n wsDepositAmount,\n osDepositAmount,\n lpTokens\n );\n }\n\n /***************************************\n Assets and Rewards\n ****************************************/\n\n /**\n * @notice Get the wS value of assets in the strategy and SwapX pool.\n * The value of the assets in the pool is calculated assuming the pool is balanced.\n * This way the value can not be manipulated by changing the pool's token balances.\n * @param _asset Address of the Wrapped S (wS) token\n * @return balance Total value in wS.\n */\n function checkBalance(address _asset)\n external\n view\n override\n returns (uint256 balance)\n {\n require(_asset == ws, \"Unsupported asset\");\n\n // wS balance needed here for the balance check that happens from vault during depositing.\n balance = IERC20(ws).balanceOf(address(this));\n\n // This assumes 1 gauge LP token = 1 pool LP token\n uint256 lpTokens = IGauge(gauge).balanceOf(address(this));\n if (lpTokens == 0) return balance;\n\n // Add the strategy’s share of the wS and OS tokens in the SwapX pool if the pool was balanced.\n balance += _lpValue(lpTokens);\n }\n\n /**\n * @notice Returns bool indicating whether asset is supported by strategy\n * @param _asset Address of the asset\n */\n function supportsAsset(address _asset) public view override returns (bool) {\n return _asset == ws;\n }\n\n /**\n * @notice Collect accumulated SWPx (and other) rewards and send to the Harvester.\n */\n function collectRewardTokens()\n external\n override\n onlyHarvester\n nonReentrant\n {\n // Collect SWPx rewards from the gauge\n IGauge(gauge).getReward();\n\n _collectRewardTokens();\n }\n\n /***************************************\n Internal SwapX Pool and Gauge Functions\n ****************************************/\n\n /**\n * @dev Calculate the required amount of OS to mint based on the wS amount.\n * This ensures the proportion of OS tokens being added to the pool matches the proportion of wS tokens.\n * For example, if the added wS tokens is 10% of existing wS tokens in the pool,\n * then the OS tokens being added should also be 10% of the OS tokens in the pool.\n * @param _wsAmount Amount of Wrapped S (wS) to be added to the pool.\n * @return osAmount Amount of OS to be minted and added to the pool.\n */\n function _calcTokensToMint(uint256 _wsAmount)\n internal\n view\n returns (uint256 osAmount)\n {\n (uint256 wsReserves, uint256 osReserves, ) = IPair(pool).getReserves();\n require(wsReserves > 0, \"Empty pool\");\n\n // OS to add = (wS being added * OS in pool) / wS in pool\n osAmount = (_wsAmount * osReserves) / wsReserves;\n }\n\n /**\n * @dev Calculate how much pool LP tokens to burn to get the required amount of wS tokens back\n * from the pool.\n * @param _wsAmount Amount of Wrapped S (wS) to be removed from the pool.\n * @return lpTokens Amount of SwapX pool LP tokens to burn.\n */\n function _calcTokensToBurn(uint256 _wsAmount)\n internal\n view\n returns (uint256 lpTokens)\n {\n /* The SwapX pool proportionally returns the reserve tokens when removing liquidity.\n * First, calculate the proportion of required wS tokens against the pools wS reserves.\n * That same proportion is used to calculate the required amount of pool LP tokens.\n * For example, if the required wS tokens is 10% of the pool's wS reserves,\n * then 10% of the pool's LP supply needs to be burned.\n *\n * Because we are doing balanced removal we should be making profit when removing liquidity in a\n * pool tilted to either side.\n *\n * Important: A downside is that the Strategist / Governor needs to be\n * cognizant of not removing too much liquidity. And while the proposal to remove liquidity\n * is being voted on, the pool tilt might change so much that the proposal that has been valid while\n * created is no longer valid.\n */\n\n (uint256 wsReserves, , ) = IPair(pool).getReserves();\n require(wsReserves > 0, \"Empty pool\");\n\n lpTokens = (_wsAmount * IPair(pool).totalSupply()) / wsReserves;\n lpTokens += 1; // Add 1 to ensure we get enough LP tokens with rounding\n }\n\n /**\n * @dev Deposit Wrapped S (wS) and OS liquidity to the SwapX pool\n * and stake the pool's LP token in the gauge.\n * @param _wsAmount Amount of Wrapped S (wS) to deposit.\n * @param _osAmount Amount of OS to deposit.\n * @return lpTokens Amount of SwapX pool LP tokens minted.\n */\n function _depositToPoolAndGauge(uint256 _wsAmount, uint256 _osAmount)\n internal\n returns (uint256 lpTokens)\n {\n // Transfer wS to the pool\n IERC20(ws).safeTransfer(pool, _wsAmount);\n // Transfer OS to the pool\n IERC20(os).safeTransfer(pool, _osAmount);\n\n // Mint LP tokens from the pool\n lpTokens = IPair(pool).mint(address(this));\n\n // Deposit the pool's LP tokens into the gauge\n IGauge(gauge).deposit(lpTokens);\n }\n\n /**\n * @dev Withdraw pool LP tokens from the gauge and remove wS and OS from the pool.\n * @param _lpTokens Amount of SwapX pool LP tokens to withdraw from the gauge\n */\n function _withdrawFromGaugeAndPool(uint256 _lpTokens) internal {\n require(\n IGauge(gauge).balanceOf(address(this)) >= _lpTokens,\n \"Not enough LP tokens in gauge\"\n );\n\n // Withdraw pool LP tokens from the gauge\n IGauge(gauge).withdraw(_lpTokens);\n\n // Transfer the pool LP tokens to the pool\n IERC20(pool).safeTransfer(pool, _lpTokens);\n\n // Burn the LP tokens and transfer the wS and OS back to the strategy\n IPair(pool).burn(address(this));\n }\n\n /**\n * @dev Withdraw all pool LP tokens from the gauge when it's in emergency mode\n * and remove wS and OS from the pool.\n */\n function _emergencyWithdrawFromGaugeAndPool() internal {\n // Withdraw all pool LP tokens from the gauge\n IGauge(gauge).emergencyWithdraw();\n\n // Get the pool LP tokens in strategy\n uint256 _lpTokens = IERC20(pool).balanceOf(address(this));\n\n // Transfer the pool LP tokens to the pool\n IERC20(pool).safeTransfer(pool, _lpTokens);\n\n // Burn the LP tokens and transfer the wS and OS back to the strategy\n IPair(pool).burn(address(this));\n }\n\n /**\n * @dev Swap exact amount of tokens for another token against the pool.\n * @param _amountIn Amount of tokens to swap into the pool.\n * @param _tokenIn Address of the token going into the pool.\n * @param _tokenOut Address of the token being swapped out of the pool.\n */\n function _swapExactTokensForTokens(\n uint256 _amountIn,\n address _tokenIn,\n address _tokenOut\n ) internal {\n // Transfer in tokens to the pool\n IERC20(_tokenIn).safeTransfer(pool, _amountIn);\n\n // Calculate how much out tokens we get from the swap\n uint256 amountOut = IPair(pool).getAmountOut(_amountIn, _tokenIn);\n\n // Safety check that we are dealing with the correct pool tokens\n require(\n (_tokenIn == ws && _tokenOut == os) ||\n (_tokenIn == os && _tokenOut == ws),\n \"Unsupported swap\"\n );\n\n // Work out the correct order of the amounts for the pool\n (uint256 amount0, uint256 amount1) = _tokenIn == ws\n ? (uint256(0), amountOut)\n : (amountOut, 0);\n\n // Perform the swap on the pool\n IPair(pool).swap(amount0, amount1, address(this), new bytes(0));\n\n // The slippage protection against the amount out is indirectly done\n // via the improvePoolBalance\n }\n\n /// @dev Calculate the value of a LP position in a SwapX stable pool\n /// if the pool was balanced.\n /// @param _lpTokens Amount of LP tokens in the SwapX pool\n /// @return value The wS value of the LP tokens when the pool is balanced\n function _lpValue(uint256 _lpTokens) internal view returns (uint256 value) {\n // Get total supply of LP tokens\n uint256 totalSupply = IPair(pool).totalSupply();\n if (totalSupply == 0) return 0;\n\n // Get the current reserves of the pool\n (uint256 wsReserves, uint256 osReserves, ) = IPair(pool).getReserves();\n\n // Calculate the invariant of the pool assuming both tokens have 18 decimals.\n // k is scaled to 18 decimals.\n uint256 k = _invariant(wsReserves, osReserves);\n\n // If x = y, let’s denote x = y = z (where z is the common reserve value)\n // Substitute z into the invariant:\n // k = z^3 * z + z * z^3\n // k = 2 * z^4\n // Going back the other way to calculate the common reserve value z\n // z = (k / 2) ^ (1/4)\n // the total value of the pool when x = y is 2 * z, which is 2 * (k / 2) ^ (1/4)\n uint256 zSquared = sqrt((k * 1e18) / 2); // 18 + 18 = 36 decimals becomes 18 decimals after sqrt\n uint256 z = sqrt(zSquared * 1e18); // 18 + 18 = 36 decimals becomes 18 decimals after sqrt\n uint256 totalValueOfPool = 2 * z;\n\n // lp value = lp tokens * value of pool / total supply\n value = (_lpTokens * totalValueOfPool) / totalSupply;\n }\n\n /**\n * @dev Compute the invariant for a SwapX stable pool.\n * This assumed both x and y tokens are to 18 decimals which is checked in the constructor.\n * invariant: k = x^3 * y + x * y^3\n * @dev This implementation is copied from SwapX's Pair contract.\n * @param _x The amount of Wrapped S (wS) tokens in the pool\n * @param _y The amount of the OS tokens in the pool\n * @return k The invariant of the SwapX stable pool\n */\n function _invariant(uint256 _x, uint256 _y)\n internal\n pure\n returns (uint256 k)\n {\n uint256 _a = (_x * _y) / PRECISION;\n uint256 _b = ((_x * _x) / PRECISION + (_y * _y) / PRECISION);\n // slither-disable-next-line divide-before-multiply\n k = (_a * _b) / PRECISION;\n }\n\n /**\n * @dev Checks that the protocol is solvent, protecting from a rogue Strategist / Guardian that can\n * keep rebalancing the pool in both directions making the protocol lose a tiny amount of\n * funds each time.\n *\n * Protocol must be at least SOLVENCY_THRESHOLD (99,8 %) backed in order for the rebalances to\n * function.\n */\n function _solvencyAssert() internal view {\n uint256 _totalVaultValue = IVault(vaultAddress).totalValue();\n uint256 _totalSupply = IERC20(os).totalSupply();\n\n if (\n _totalSupply > 0 &&\n _totalVaultValue.divPrecisely(_totalSupply) < SOLVENCY_THRESHOLD\n ) {\n revert(\"Protocol insolvent\");\n }\n }\n\n /***************************************\n Setters\n ****************************************/\n\n /**\n * @notice Set the maximum deviation from the OS/wS peg (1e18) before deposits are reverted.\n * @param _maxDepeg the OS/wS price from peg (1e18) in 18 decimals.\n * eg 0.01e18 or 1e16 is 1% which is 100 basis points.\n */\n function setMaxDepeg(uint256 _maxDepeg) external onlyGovernor {\n maxDepeg = _maxDepeg;\n\n emit MaxDepegUpdated(_maxDepeg);\n }\n\n /***************************************\n Approvals\n ****************************************/\n\n /**\n * @notice Approve the spending of all assets by their corresponding pool tokens,\n * if for some reason is it necessary.\n */\n function safeApproveAllTokens()\n external\n override\n onlyGovernor\n nonReentrant\n {\n _approveBase();\n }\n\n // solhint-disable-next-line no-unused-vars\n function _abstractSetPToken(address _asset, address _pToken)\n internal\n override\n {}\n\n function _approveBase() internal {\n // Approve SwapX gauge contract to transfer SwapX pool LP tokens\n // This is needed for deposits of SwapX pool LP tokens into the gauge.\n // slither-disable-next-line unused-return\n IPair(pool).approve(address(gauge), type(uint256).max);\n }\n}\n" + }, + "contracts/strategies/sonic/SonicValidatorDelegator.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { IERC20, InitializableAbstractStrategy } from \"../../utils/InitializableAbstractStrategy.sol\";\nimport { IVault } from \"../../interfaces/IVault.sol\";\nimport { ISFC } from \"../../interfaces/sonic/ISFC.sol\";\nimport { IWrappedSonic } from \"../../interfaces/sonic/IWrappedSonic.sol\";\n\n/**\n * @title Manages delegation to Sonic validators\n * @notice This contract implements all the required functionality to delegate to,\n undelegate from and withdraw from validators.\n * @author Origin Protocol Inc\n */\nabstract contract SonicValidatorDelegator is InitializableAbstractStrategy {\n /// @notice Address of Sonic's wrapped S token\n address public immutable wrappedSonic;\n /// @notice Sonic's Special Fee Contract (SFC)\n ISFC public immutable sfc;\n\n /// @notice a unique ID for each withdrawal request\n uint256 public nextWithdrawId;\n /// @notice Sonic (S) that is pending withdrawal after undelegating\n uint256 public pendingWithdrawals;\n\n /// @notice List of supported validator IDs that can be delegated to\n uint256[] public supportedValidators;\n\n /// @notice Default validator id to deposit to\n uint256 public defaultValidatorId;\n\n struct WithdrawRequest {\n uint256 validatorId;\n uint256 undelegatedAmount;\n uint256 timestamp;\n }\n /// @notice Mapping of withdrawIds to validatorIds and undelegatedAmounts\n mapping(uint256 => WithdrawRequest) public withdrawals;\n\n /// @notice Address of the registrator - allowed to register, exit and remove validators\n address public validatorRegistrator;\n\n // For future use\n uint256[44] private __gap;\n\n event Delegated(uint256 indexed validatorId, uint256 delegatedAmount);\n event Undelegated(\n uint256 indexed withdrawId,\n uint256 indexed validatorId,\n uint256 undelegatedAmount\n );\n event Withdrawn(\n uint256 indexed withdrawId,\n uint256 indexed validatorId,\n uint256 undelegatedAmount,\n uint256 withdrawnAmount\n );\n event RegistratorChanged(address indexed newAddress);\n event SupportedValidator(uint256 indexed validatorId);\n event UnsupportedValidator(uint256 indexed validatorId);\n event DefaultValidatorIdChanged(uint256 indexed validatorId);\n\n /// @dev Throws if called by any account other than the Registrator or Strategist\n modifier onlyRegistratorOrStrategist() {\n require(\n msg.sender == validatorRegistrator ||\n msg.sender == IVault(vaultAddress).strategistAddr(),\n \"Caller is not the Registrator or Strategist\"\n );\n _;\n }\n\n constructor(\n BaseStrategyConfig memory _baseConfig,\n address _wrappedSonic,\n address _sfc\n ) InitializableAbstractStrategy(_baseConfig) {\n wrappedSonic = _wrappedSonic;\n sfc = ISFC(_sfc);\n }\n\n function initialize() external virtual onlyGovernor initializer {\n address[] memory rewardTokens = new address[](0);\n address[] memory assets = new address[](1);\n address[] memory pTokens = new address[](1);\n\n assets[0] = address(wrappedSonic);\n pTokens[0] = address(platformAddress);\n\n InitializableAbstractStrategy._initialize(\n rewardTokens,\n assets,\n pTokens\n );\n }\n\n /// @notice Returns the total value of Sonic (S) that is delegated validators.\n /// Wrapped Sonic (wS) deposits that are still to be delegated and any undelegated amounts\n /// still pending a withdrawal.\n /// @param _asset Address of Wrapped Sonic (wS) token\n /// @return balance Total value managed by the strategy\n function checkBalance(address _asset)\n external\n view\n virtual\n override\n returns (uint256 balance)\n {\n require(_asset == wrappedSonic, \"Unsupported asset\");\n\n // add the Wrapped Sonic (wS) in the strategy from deposits that are still to be delegated\n // and any undelegated amounts still pending a withdrawal\n balance =\n IERC20(wrappedSonic).balanceOf(address(this)) +\n pendingWithdrawals;\n\n // For each supported validator, get the staked amount and pending rewards\n uint256 validatorLen = supportedValidators.length;\n for (uint256 i = 0; i < validatorLen; i++) {\n uint256 validator = supportedValidators[i];\n balance += sfc.getStake(address(this), validator);\n balance += sfc.pendingRewards(address(this), validator);\n }\n }\n\n /**\n * @dev Delegate from this strategy to a specific Sonic validator. Called\n * automatically on asset deposit\n * @param _amount the amount of Sonic (S) to delegate.\n */\n function _delegate(uint256 _amount) internal {\n require(\n isSupportedValidator(defaultValidatorId),\n \"Validator not supported\"\n );\n\n // unwrap Wrapped Sonic (wS) to native Sonic (S)\n IWrappedSonic(wrappedSonic).withdraw(_amount);\n\n //slither-disable-next-line arbitrary-send-eth\n sfc.delegate{ value: _amount }(defaultValidatorId);\n\n emit Delegated(defaultValidatorId, _amount);\n }\n\n /**\n * @notice Undelegate from a specific Sonic validator.\n * This needs to be followed by a `withdrawFromSFC` two weeks later.\n * @param _validatorId The Sonic validator ID to undelegate from.\n * @param _undelegateAmount the amount of Sonic (S) to undelegate.\n * @return withdrawId The unique ID of the withdrawal request.\n */\n function undelegate(uint256 _validatorId, uint256 _undelegateAmount)\n external\n onlyRegistratorOrStrategist\n nonReentrant\n returns (uint256 withdrawId)\n {\n withdrawId = _undelegate(_validatorId, _undelegateAmount);\n }\n\n function _undelegate(uint256 _validatorId, uint256 _undelegateAmount)\n internal\n returns (uint256 withdrawId)\n {\n // Can still undelegate even if the validator is no longer supported\n require(_undelegateAmount > 0, \"Must undelegate something\");\n\n uint256 amountDelegated = sfc.getStake(address(this), _validatorId);\n require(\n _undelegateAmount <= amountDelegated,\n \"Insufficient delegation\"\n );\n\n withdrawId = nextWithdrawId++;\n\n withdrawals[withdrawId] = WithdrawRequest(\n _validatorId,\n _undelegateAmount,\n block.timestamp\n );\n pendingWithdrawals += _undelegateAmount;\n\n sfc.undelegate(_validatorId, withdrawId, _undelegateAmount);\n\n emit Undelegated(withdrawId, _validatorId, _undelegateAmount);\n }\n\n /**\n * @notice Withdraw native S from a previously undelegated validator.\n * The native S is wrapped wS and transferred to the Vault.\n * @param _withdrawId The unique withdraw ID used to `undelegate`\n * @return withdrawnAmount The amount of Sonic (S) withdrawn.\n * This can be less than the undelegated amount in the event of slashing.\n */\n function withdrawFromSFC(uint256 _withdrawId)\n external\n onlyRegistratorOrStrategist\n nonReentrant\n returns (uint256 withdrawnAmount)\n {\n require(_withdrawId < nextWithdrawId, \"Invalid withdrawId\");\n\n // Can still withdraw even if the validator is no longer supported\n // Load the withdrawal from storage into memory\n WithdrawRequest memory withdrawal = withdrawals[_withdrawId];\n require(!isWithdrawnFromSFC(_withdrawId), \"Already withdrawn\");\n\n withdrawals[_withdrawId].undelegatedAmount = 0;\n pendingWithdrawals -= withdrawal.undelegatedAmount;\n\n uint256 sBalanceBefore = address(this).balance;\n\n // Try to withdraw from SFC\n try sfc.withdraw(withdrawal.validatorId, _withdrawId) {\n // continue below\n } catch (bytes memory err) {\n bytes4 errorSelector = bytes4(err);\n\n // If the validator has been fully slashed, SFC's withdraw function will\n // revert with a StakeIsFullySlashed custom error.\n if (errorSelector == ISFC.StakeIsFullySlashed.selector) {\n // The validator was fully slashed, so all the delegated amounts were lost.\n // Will swallow the error as we still want to update the\n // withdrawals and pendingWithdrawals storage variables.\n\n // The return param defaults to zero but lets set it explicitly so it's clear\n withdrawnAmount = 0;\n\n emit Withdrawn(\n _withdrawId,\n withdrawal.validatorId,\n withdrawal.undelegatedAmount,\n withdrawnAmount\n );\n\n // Exit here as there is nothing to transfer to the Vault\n return withdrawnAmount;\n } else {\n // Bubble up any other SFC custom errors.\n // Inline assembly is currently the only way to generically rethrow the exact same custom error\n // from the raw bytes err in a catch block while preserving its original selector and parameters.\n // solhint-disable-next-line no-inline-assembly\n assembly {\n revert(add(32, err), mload(err))\n }\n }\n }\n\n // Set return parameter\n withdrawnAmount = address(this).balance - sBalanceBefore;\n\n // Wrap Sonic (S) to Wrapped Sonic (wS)\n IWrappedSonic(wrappedSonic).deposit{ value: withdrawnAmount }();\n\n // Transfer the Wrapped Sonic (wS) to the Vault\n _withdraw(vaultAddress, wrappedSonic, withdrawnAmount);\n\n // withdrawal.undelegatedAmount & withdrawnAmount can differ in case of slashing\n emit Withdrawn(\n _withdrawId,\n withdrawal.validatorId,\n withdrawal.undelegatedAmount,\n withdrawnAmount\n );\n }\n\n /// @notice returns a bool whether a withdrawalId has already been withdrawn or not\n /// @param _withdrawId The unique withdraw ID used to `undelegate`\n function isWithdrawnFromSFC(uint256 _withdrawId)\n public\n view\n returns (bool)\n {\n WithdrawRequest memory withdrawal = withdrawals[_withdrawId];\n require(withdrawal.validatorId > 0, \"Invalid withdrawId\");\n return withdrawal.undelegatedAmount == 0;\n }\n\n /**\n * @notice Restake any pending validator rewards for all supported validators\n * @param _validatorIds List of Sonic validator IDs to restake rewards\n */\n function restakeRewards(uint256[] calldata _validatorIds)\n external\n nonReentrant\n {\n for (uint256 i = 0; i < _validatorIds.length; ++i) {\n require(\n isSupportedValidator(_validatorIds[i]),\n \"Validator not supported\"\n );\n\n uint256 rewards = sfc.pendingRewards(\n address(this),\n _validatorIds[i]\n );\n\n if (rewards > 0) {\n sfc.restakeRewards(_validatorIds[i]);\n }\n }\n\n // The SFC contract will emit Delegated and RestakedRewards events.\n // The checkBalance function should not change as the pending rewards will moved to the staked amount.\n }\n\n /**\n * @notice Claim any pending rewards from validators\n * @param _validatorIds List of Sonic validator IDs to claim rewards\n */\n function collectRewards(uint256[] calldata _validatorIds)\n external\n onlyRegistratorOrStrategist\n nonReentrant\n {\n uint256 sBalanceBefore = address(this).balance;\n\n for (uint256 i = 0; i < _validatorIds.length; ++i) {\n uint256 rewards = sfc.pendingRewards(\n address(this),\n _validatorIds[i]\n );\n\n if (rewards > 0) {\n // The SFC contract will emit ClaimedRewards(delegator (this), validatorId, rewards)\n sfc.claimRewards(_validatorIds[i]);\n }\n }\n\n uint256 rewardsAmount = address(this).balance - sBalanceBefore;\n\n // Wrap Sonic (S) to Wrapped Sonic (wS)\n IWrappedSonic(wrappedSonic).deposit{ value: rewardsAmount }();\n\n // Transfer the Wrapped Sonic (wS) to the Vault\n _withdraw(vaultAddress, wrappedSonic, rewardsAmount);\n }\n\n /**\n * @notice To receive native S from SFC and Wrapped Sonic (wS)\n *\n * @dev This does not prevent donating S tokens to the contract\n * as wrappedSonic has a `withdrawTo` function where a third party\n * owner of wrappedSonic can withdraw to this contract.\n */\n receive() external payable {\n require(\n msg.sender == address(sfc) || msg.sender == wrappedSonic,\n \"S not from allowed contracts\"\n );\n }\n\n /***************************************\n Admin functions\n ****************************************/\n\n /// @notice Set the address of the Registrator which can undelegate, withdraw and collect rewards\n /// @param _validatorRegistrator The address of the Registrator\n function setRegistrator(address _validatorRegistrator)\n external\n onlyGovernor\n {\n validatorRegistrator = _validatorRegistrator;\n emit RegistratorChanged(_validatorRegistrator);\n }\n\n /// @notice Set the default validatorId to delegate to on deposit\n /// @param _validatorId The validator identifier. eg 18\n function setDefaultValidatorId(uint256 _validatorId)\n external\n onlyRegistratorOrStrategist\n {\n require(isSupportedValidator(_validatorId), \"Validator not supported\");\n defaultValidatorId = _validatorId;\n emit DefaultValidatorIdChanged(_validatorId);\n }\n\n /// @notice Allows a validator to be delegated to by the Registrator\n /// @param _validatorId The validator identifier. eg 18\n function supportValidator(uint256 _validatorId) external onlyGovernor {\n require(\n !isSupportedValidator(_validatorId),\n \"Validator already supported\"\n );\n\n supportedValidators.push(_validatorId);\n\n emit SupportedValidator(_validatorId);\n }\n\n /// @notice Removes a validator from the supported list.\n /// Unsupported validators can still be undelegated from, withdrawn from and rewards collected.\n /// @param _validatorId The validator identifier. eg 18\n function unsupportValidator(uint256 _validatorId) external onlyGovernor {\n require(isSupportedValidator(_validatorId), \"Validator not supported\");\n\n uint256 validatorLen = supportedValidators.length;\n for (uint256 i = 0; i < validatorLen; ++i) {\n if (supportedValidators[i] == _validatorId) {\n supportedValidators[i] = supportedValidators[validatorLen - 1];\n supportedValidators.pop();\n break;\n }\n }\n\n uint256 stake = sfc.getStake(address(this), _validatorId);\n\n // undelegate if validator still has funds staked\n if (stake > 0) {\n _undelegate(_validatorId, stake);\n }\n emit UnsupportedValidator(_validatorId);\n }\n\n /// @notice Returns the length of the supportedValidators array\n function supportedValidatorsLength() external view returns (uint256) {\n return supportedValidators.length;\n }\n\n /// @notice Returns whether a validator is supported by this strategy\n /// @param _validatorId The validator identifier\n function isSupportedValidator(uint256 _validatorId)\n public\n view\n returns (bool)\n {\n uint256 validatorLen = supportedValidators.length;\n for (uint256 i = 0; i < validatorLen; ++i) {\n if (supportedValidators[i] == _validatorId) {\n return true;\n }\n }\n return false;\n }\n\n function _withdraw(\n address _recipient,\n address _asset,\n uint256 _amount\n ) internal virtual;\n}\n" + }, + "contracts/strategies/VaultValueChecker.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { IOUSD } from \"../interfaces/IOUSD.sol\";\nimport { IVault } from \"../interfaces/IVault.sol\";\n\ncontract VaultValueChecker {\n IVault public immutable vault;\n IOUSD public immutable ousd;\n // Snapshot expiration time in seconds.\n // Used to prevent accidental use of an old snapshot, but\n // is not zero to allow easy testing of strategist actions in fork testing\n uint256 constant SNAPSHOT_EXPIRES = 5 * 60;\n\n struct Snapshot {\n uint256 vaultValue;\n uint256 totalSupply;\n uint256 time;\n }\n // By doing per user snapshots, we prevent a reentrancy attack\n // from a third party that updates the snapshot in the middle\n // of an allocation process\n\n mapping(address => Snapshot) public snapshots;\n\n constructor(address _vault, address _ousd) {\n vault = IVault(_vault);\n ousd = IOUSD(_ousd);\n }\n\n function takeSnapshot() external {\n snapshots[msg.sender] = Snapshot({\n vaultValue: vault.totalValue(),\n totalSupply: ousd.totalSupply(),\n time: block.timestamp\n });\n }\n\n function checkDelta(\n int256 expectedProfit,\n int256 profitVariance,\n int256 expectedVaultChange,\n int256 vaultChangeVariance\n ) external {\n // Intentionaly not view so that this method shows up in TX builders\n Snapshot memory snapshot = snapshots[msg.sender];\n int256 vaultChange = toInt256(vault.totalValue()) -\n toInt256(snapshot.vaultValue);\n int256 supplyChange = toInt256(ousd.totalSupply()) -\n toInt256(snapshot.totalSupply);\n int256 profit = vaultChange - supplyChange;\n\n require(\n snapshot.time >= block.timestamp - SNAPSHOT_EXPIRES,\n \"Snapshot too old\"\n );\n require(snapshot.time <= block.timestamp, \"Snapshot too new\");\n require(profit >= expectedProfit - profitVariance, \"Profit too low\");\n require(profit <= expectedProfit + profitVariance, \"Profit too high\");\n require(\n vaultChange >= expectedVaultChange - vaultChangeVariance,\n \"Vault value change too low\"\n );\n require(\n vaultChange <= expectedVaultChange + vaultChangeVariance,\n \"Vault value change too high\"\n );\n }\n\n function toInt256(uint256 value) internal pure returns (int256) {\n // From openzeppelin math/SafeCast.sol\n // Note: Unsafe cast below is okay because `type(int256).max` is guaranteed to be positive\n require(\n value <= uint256(type(int256).max),\n \"SafeCast: value doesn't fit in an int256\"\n );\n return int256(value);\n }\n}\n\ncontract OETHVaultValueChecker is VaultValueChecker {\n constructor(address _vault, address _ousd)\n VaultValueChecker(_vault, _ousd)\n {}\n}\n" + }, + "contracts/token/OETH.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { OUSD } from \"./OUSD.sol\";\n\n/**\n * @title OETH Token Contract\n * @author Origin Protocol Inc\n */\ncontract OETH is OUSD {\n function symbol() external pure override returns (string memory) {\n return \"OETH\";\n }\n\n function name() external pure override returns (string memory) {\n return \"Origin Ether\";\n }\n\n function decimals() external pure override returns (uint8) {\n return 18;\n }\n}\n" + }, + "contracts/token/OETHBase.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { OUSD } from \"./OUSD.sol\";\n\n/**\n * @title OETH Token Contract\n * @author Origin Protocol Inc\n */\ncontract OETHBase is OUSD {\n constructor() {\n // Nobody owns the implementation contract\n _setGovernor(address(0));\n }\n\n function symbol() external pure override returns (string memory) {\n return \"superOETHb\";\n }\n\n function name() external pure override returns (string memory) {\n return \"Super OETH\";\n }\n\n function decimals() external pure override returns (uint8) {\n return 18;\n }\n}\n" + }, + "contracts/token/OETHPlume.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { OUSD } from \"./OUSD.sol\";\n\n/**\n * @title Super OETH (Plume) Token Contract\n * @author Origin Protocol Inc\n */\ncontract OETHPlume is OUSD {\n constructor() {\n // Nobody owns the implementation contract\n _setGovernor(address(0));\n }\n\n function symbol() external pure override returns (string memory) {\n return \"superOETHp\";\n }\n\n function name() external pure override returns (string memory) {\n return \"Super OETH\";\n }\n\n function decimals() external pure override returns (uint8) {\n return 18;\n }\n}\n" + }, + "contracts/token/OSonic.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { OUSD } from \"./OUSD.sol\";\n\n/**\n * @title Origin Sonic (OS) token on Sonic\n * @author Origin Protocol Inc\n */\ncontract OSonic is OUSD {\n function symbol() external pure override returns (string memory) {\n return \"OS\";\n }\n\n function name() external pure override returns (string memory) {\n return \"Origin Sonic\";\n }\n}\n" + }, + "contracts/token/OUSD.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title OUSD Token Contract\n * @dev ERC20 compatible contract for OUSD\n * @dev Implements an elastic supply\n * @author Origin Protocol Inc\n */\nimport { IVault } from \"../interfaces/IVault.sol\";\nimport { Governable } from \"../governance/Governable.sol\";\nimport { SafeCast } from \"@openzeppelin/contracts/utils/math/SafeCast.sol\";\n\ncontract OUSD is Governable {\n using SafeCast for int256;\n using SafeCast for uint256;\n\n /// @dev Event triggered when the supply changes\n /// @param totalSupply Updated token total supply\n /// @param rebasingCredits Updated token rebasing credits\n /// @param rebasingCreditsPerToken Updated token rebasing credits per token\n event TotalSupplyUpdatedHighres(\n uint256 totalSupply,\n uint256 rebasingCredits,\n uint256 rebasingCreditsPerToken\n );\n /// @dev Event triggered when an account opts in for rebasing\n /// @param account Address of the account\n event AccountRebasingEnabled(address account);\n /// @dev Event triggered when an account opts out of rebasing\n /// @param account Address of the account\n event AccountRebasingDisabled(address account);\n /// @dev Emitted when `value` tokens are moved from one account `from` to\n /// another `to`.\n /// @param from Address of the account tokens are moved from\n /// @param to Address of the account tokens are moved to\n /// @param value Amount of tokens transferred\n event Transfer(address indexed from, address indexed to, uint256 value);\n /// @dev Emitted when the allowance of a `spender` for an `owner` is set by\n /// a call to {approve}. `value` is the new allowance.\n /// @param owner Address of the owner approving allowance\n /// @param spender Address of the spender allowance is granted to\n /// @param value Amount of tokens spender can transfer\n event Approval(\n address indexed owner,\n address indexed spender,\n uint256 value\n );\n /// @dev Yield resulting from {changeSupply} that a `source` account would\n /// receive is directed to `target` account.\n /// @param source Address of the source forwarding the yield\n /// @param target Address of the target receiving the yield\n event YieldDelegated(address source, address target);\n /// @dev Yield delegation from `source` account to the `target` account is\n /// suspended.\n /// @param source Address of the source suspending yield forwarding\n /// @param target Address of the target no longer receiving yield from `source`\n /// account\n event YieldUndelegated(address source, address target);\n\n enum RebaseOptions {\n NotSet,\n StdNonRebasing,\n StdRebasing,\n YieldDelegationSource,\n YieldDelegationTarget\n }\n\n uint256[154] private _gap; // Slots to align with deployed contract\n uint256 private constant MAX_SUPPLY = type(uint128).max;\n /// @dev The amount of tokens in existence\n uint256 public totalSupply;\n mapping(address => mapping(address => uint256)) private allowances;\n /// @dev The vault with privileges to execute {mint}, {burn}\n /// and {changeSupply}\n address public vaultAddress;\n mapping(address => uint256) internal creditBalances;\n // the 2 storage variables below need trailing underscores to not name collide with public functions\n uint256 private rebasingCredits_; // Sum of all rebasing credits (creditBalances for rebasing accounts)\n uint256 private rebasingCreditsPerToken_;\n /// @dev The amount of tokens that are not rebasing - receiving yield\n uint256 public nonRebasingSupply;\n mapping(address => uint256) internal alternativeCreditsPerToken;\n /// @dev A map of all addresses and their respective RebaseOptions\n mapping(address => RebaseOptions) public rebaseState;\n mapping(address => uint256) private __deprecated_isUpgraded;\n /// @dev A map of addresses that have yields forwarded to. This is an\n /// inverse mapping of {yieldFrom}\n /// Key Account forwarding yield\n /// Value Account receiving yield\n mapping(address => address) public yieldTo;\n /// @dev A map of addresses that are receiving the yield. This is an\n /// inverse mapping of {yieldTo}\n /// Key Account receiving yield\n /// Value Account forwarding yield\n mapping(address => address) public yieldFrom;\n\n uint256 private constant RESOLUTION_INCREASE = 1e9;\n uint256[34] private __gap; // including below gap totals up to 200\n\n /// @dev Verifies that the caller is the Governor or Strategist.\n modifier onlyGovernorOrStrategist() {\n require(\n isGovernor() || msg.sender == IVault(vaultAddress).strategistAddr(),\n \"Caller is not the Strategist or Governor\"\n );\n _;\n }\n\n /// @dev Initializes the contract and sets necessary variables.\n /// @param _vaultAddress Address of the vault contract\n /// @param _initialCreditsPerToken The starting rebasing credits per token.\n function initialize(address _vaultAddress, uint256 _initialCreditsPerToken)\n external\n onlyGovernor\n {\n require(_vaultAddress != address(0), \"Zero vault address\");\n require(vaultAddress == address(0), \"Already initialized\");\n\n rebasingCreditsPerToken_ = _initialCreditsPerToken;\n vaultAddress = _vaultAddress;\n }\n\n /// @dev Returns the symbol of the token, a shorter version\n /// of the name.\n function symbol() external pure virtual returns (string memory) {\n return \"OUSD\";\n }\n\n /// @dev Returns the name of the token.\n function name() external pure virtual returns (string memory) {\n return \"Origin Dollar\";\n }\n\n /// @dev Returns the number of decimals used to get its user representation.\n function decimals() external pure virtual returns (uint8) {\n return 18;\n }\n\n /**\n * @dev Verifies that the caller is the Vault contract\n */\n modifier onlyVault() {\n require(vaultAddress == msg.sender, \"Caller is not the Vault\");\n _;\n }\n\n /**\n * @return High resolution rebasingCreditsPerToken\n */\n function rebasingCreditsPerTokenHighres() external view returns (uint256) {\n return rebasingCreditsPerToken_;\n }\n\n /**\n * @return Low resolution rebasingCreditsPerToken\n */\n function rebasingCreditsPerToken() external view returns (uint256) {\n return rebasingCreditsPerToken_ / RESOLUTION_INCREASE;\n }\n\n /**\n * @return High resolution total number of rebasing credits\n */\n function rebasingCreditsHighres() external view returns (uint256) {\n return rebasingCredits_;\n }\n\n /**\n * @return Low resolution total number of rebasing credits\n */\n function rebasingCredits() external view returns (uint256) {\n return rebasingCredits_ / RESOLUTION_INCREASE;\n }\n\n /**\n * @notice Gets the balance of the specified address.\n * @param _account Address to query the balance of.\n * @return A uint256 representing the amount of base units owned by the\n * specified address.\n */\n function balanceOf(address _account) public view returns (uint256) {\n RebaseOptions state = rebaseState[_account];\n if (state == RebaseOptions.YieldDelegationSource) {\n // Saves a slot read when transferring to or from a yield delegating source\n // since we know creditBalances equals the balance.\n return creditBalances[_account];\n }\n uint256 baseBalance = (creditBalances[_account] * 1e18) /\n _creditsPerToken(_account);\n if (state == RebaseOptions.YieldDelegationTarget) {\n // creditBalances of yieldFrom accounts equals token balances\n return baseBalance - creditBalances[yieldFrom[_account]];\n }\n return baseBalance;\n }\n\n /**\n * @notice Gets the credits balance of the specified address.\n * @dev Backwards compatible with old low res credits per token.\n * @param _account The address to query the balance of.\n * @return (uint256, uint256) Credit balance and credits per token of the\n * address\n */\n function creditsBalanceOf(address _account)\n external\n view\n returns (uint256, uint256)\n {\n uint256 cpt = _creditsPerToken(_account);\n if (cpt == 1e27) {\n // For a period before the resolution upgrade, we created all new\n // contract accounts at high resolution. Since they are not changing\n // as a result of this upgrade, we will return their true values\n return (creditBalances[_account], cpt);\n } else {\n return (\n creditBalances[_account] / RESOLUTION_INCREASE,\n cpt / RESOLUTION_INCREASE\n );\n }\n }\n\n /**\n * @notice Gets the credits balance of the specified address.\n * @param _account The address to query the balance of.\n * @return (uint256, uint256, bool) Credit balance, credits per token of the\n * address, and isUpgraded\n */\n function creditsBalanceOfHighres(address _account)\n external\n view\n returns (\n uint256,\n uint256,\n bool\n )\n {\n return (\n creditBalances[_account],\n _creditsPerToken(_account),\n true // all accounts have their resolution \"upgraded\"\n );\n }\n\n // Backwards compatible view\n function nonRebasingCreditsPerToken(address _account)\n external\n view\n returns (uint256)\n {\n return alternativeCreditsPerToken[_account];\n }\n\n /**\n * @notice Transfer tokens to a specified address.\n * @param _to the address to transfer to.\n * @param _value the amount to be transferred.\n * @return true on success.\n */\n function transfer(address _to, uint256 _value) external returns (bool) {\n require(_to != address(0), \"Transfer to zero address\");\n\n _executeTransfer(msg.sender, _to, _value);\n\n emit Transfer(msg.sender, _to, _value);\n return true;\n }\n\n /**\n * @notice Transfer tokens from one address to another.\n * @param _from The address you want to send tokens from.\n * @param _to The address you want to transfer to.\n * @param _value The amount of tokens to be transferred.\n * @return true on success.\n */\n function transferFrom(\n address _from,\n address _to,\n uint256 _value\n ) external returns (bool) {\n require(_to != address(0), \"Transfer to zero address\");\n uint256 userAllowance = allowances[_from][msg.sender];\n require(_value <= userAllowance, \"Allowance exceeded\");\n\n unchecked {\n allowances[_from][msg.sender] = userAllowance - _value;\n }\n\n _executeTransfer(_from, _to, _value);\n\n emit Transfer(_from, _to, _value);\n return true;\n }\n\n function _executeTransfer(\n address _from,\n address _to,\n uint256 _value\n ) internal {\n (\n int256 fromRebasingCreditsDiff,\n int256 fromNonRebasingSupplyDiff\n ) = _adjustAccount(_from, -_value.toInt256());\n (\n int256 toRebasingCreditsDiff,\n int256 toNonRebasingSupplyDiff\n ) = _adjustAccount(_to, _value.toInt256());\n\n _adjustGlobals(\n fromRebasingCreditsDiff + toRebasingCreditsDiff,\n fromNonRebasingSupplyDiff + toNonRebasingSupplyDiff\n );\n }\n\n function _adjustAccount(address _account, int256 _balanceChange)\n internal\n returns (int256 rebasingCreditsDiff, int256 nonRebasingSupplyDiff)\n {\n RebaseOptions state = rebaseState[_account];\n int256 currentBalance = balanceOf(_account).toInt256();\n if (currentBalance + _balanceChange < 0) {\n revert(\"Transfer amount exceeds balance\");\n }\n uint256 newBalance = (currentBalance + _balanceChange).toUint256();\n\n if (state == RebaseOptions.YieldDelegationSource) {\n address target = yieldTo[_account];\n uint256 targetOldBalance = balanceOf(target);\n uint256 targetNewCredits = _balanceToRebasingCredits(\n targetOldBalance + newBalance\n );\n rebasingCreditsDiff =\n targetNewCredits.toInt256() -\n creditBalances[target].toInt256();\n\n creditBalances[_account] = newBalance;\n creditBalances[target] = targetNewCredits;\n } else if (state == RebaseOptions.YieldDelegationTarget) {\n uint256 newCredits = _balanceToRebasingCredits(\n newBalance + creditBalances[yieldFrom[_account]]\n );\n rebasingCreditsDiff =\n newCredits.toInt256() -\n creditBalances[_account].toInt256();\n creditBalances[_account] = newCredits;\n } else {\n _autoMigrate(_account);\n uint256 alternativeCreditsPerTokenMem = alternativeCreditsPerToken[\n _account\n ];\n if (alternativeCreditsPerTokenMem > 0) {\n nonRebasingSupplyDiff = _balanceChange;\n if (alternativeCreditsPerTokenMem != 1e18) {\n alternativeCreditsPerToken[_account] = 1e18;\n }\n creditBalances[_account] = newBalance;\n } else {\n uint256 newCredits = _balanceToRebasingCredits(newBalance);\n rebasingCreditsDiff =\n newCredits.toInt256() -\n creditBalances[_account].toInt256();\n creditBalances[_account] = newCredits;\n }\n }\n }\n\n function _adjustGlobals(\n int256 _rebasingCreditsDiff,\n int256 _nonRebasingSupplyDiff\n ) internal {\n if (_rebasingCreditsDiff != 0) {\n rebasingCredits_ = (rebasingCredits_.toInt256() +\n _rebasingCreditsDiff).toUint256();\n }\n if (_nonRebasingSupplyDiff != 0) {\n nonRebasingSupply = (nonRebasingSupply.toInt256() +\n _nonRebasingSupplyDiff).toUint256();\n }\n }\n\n /**\n * @notice Function to check the amount of tokens that _owner has allowed\n * to `_spender`.\n * @param _owner The address which owns the funds.\n * @param _spender The address which will spend the funds.\n * @return The number of tokens still available for the _spender.\n */\n function allowance(address _owner, address _spender)\n external\n view\n returns (uint256)\n {\n return allowances[_owner][_spender];\n }\n\n /**\n * @notice Approve the passed address to spend the specified amount of\n * tokens on behalf of msg.sender.\n * @param _spender The address which will spend the funds.\n * @param _value The amount of tokens to be spent.\n * @return true on success.\n */\n function approve(address _spender, uint256 _value) external returns (bool) {\n allowances[msg.sender][_spender] = _value;\n emit Approval(msg.sender, _spender, _value);\n return true;\n }\n\n /**\n * @notice Creates `_amount` tokens and assigns them to `_account`,\n * increasing the total supply.\n */\n function mint(address _account, uint256 _amount) external onlyVault {\n require(_account != address(0), \"Mint to the zero address\");\n\n // Account\n (\n int256 toRebasingCreditsDiff,\n int256 toNonRebasingSupplyDiff\n ) = _adjustAccount(_account, _amount.toInt256());\n // Globals\n _adjustGlobals(toRebasingCreditsDiff, toNonRebasingSupplyDiff);\n totalSupply = totalSupply + _amount;\n\n require(totalSupply < MAX_SUPPLY, \"Max supply\");\n emit Transfer(address(0), _account, _amount);\n }\n\n /**\n * @notice Destroys `_amount` tokens from `_account`,\n * reducing the total supply.\n */\n function burn(address _account, uint256 _amount) external onlyVault {\n require(_account != address(0), \"Burn from the zero address\");\n if (_amount == 0) {\n return;\n }\n\n // Account\n (\n int256 toRebasingCreditsDiff,\n int256 toNonRebasingSupplyDiff\n ) = _adjustAccount(_account, -_amount.toInt256());\n // Globals\n _adjustGlobals(toRebasingCreditsDiff, toNonRebasingSupplyDiff);\n totalSupply = totalSupply - _amount;\n\n emit Transfer(_account, address(0), _amount);\n }\n\n /**\n * @dev Get the credits per token for an account. Returns a fixed amount\n * if the account is non-rebasing.\n * @param _account Address of the account.\n */\n function _creditsPerToken(address _account)\n internal\n view\n returns (uint256)\n {\n uint256 alternativeCreditsPerTokenMem = alternativeCreditsPerToken[\n _account\n ];\n if (alternativeCreditsPerTokenMem != 0) {\n return alternativeCreditsPerTokenMem;\n } else {\n return rebasingCreditsPerToken_;\n }\n }\n\n /**\n * @dev Auto migrate contracts to be non rebasing,\n * unless they have opted into yield.\n * @param _account Address of the account.\n */\n function _autoMigrate(address _account) internal {\n uint256 codeLen = _account.code.length;\n bool isEOA = (codeLen == 0) ||\n (codeLen == 23 && bytes3(_account.code) == 0xef0100);\n // In previous code versions, contracts would not have had their\n // rebaseState[_account] set to RebaseOptions.NonRebasing when migrated\n // therefore we check the actual accounting used on the account as well.\n if (\n (!isEOA) &&\n rebaseState[_account] == RebaseOptions.NotSet &&\n alternativeCreditsPerToken[_account] == 0\n ) {\n _rebaseOptOut(_account);\n }\n }\n\n /**\n * @dev Calculates credits from contract's global rebasingCreditsPerToken_, and\n * also balance that corresponds to those credits. The latter is important\n * when adjusting the contract's global nonRebasingSupply to circumvent any\n * possible rounding errors.\n *\n * @param _balance Balance of the account.\n */\n function _balanceToRebasingCredits(uint256 _balance)\n internal\n view\n returns (uint256 rebasingCredits)\n {\n // Rounds up, because we need to ensure that accounts always have\n // at least the balance that they should have.\n // Note this should always be used on an absolute account value,\n // not on a possibly negative diff, because then the rounding would be wrong.\n return ((_balance) * rebasingCreditsPerToken_ + 1e18 - 1) / 1e18;\n }\n\n /**\n * @notice The calling account will start receiving yield after a successful call.\n * @param _account Address of the account.\n */\n function governanceRebaseOptIn(address _account) external onlyGovernor {\n require(_account != address(0), \"Zero address not allowed\");\n _rebaseOptIn(_account);\n }\n\n /**\n * @notice The calling account will start receiving yield after a successful call.\n */\n function rebaseOptIn() external {\n _rebaseOptIn(msg.sender);\n }\n\n function _rebaseOptIn(address _account) internal {\n uint256 balance = balanceOf(_account);\n\n // prettier-ignore\n require(\n alternativeCreditsPerToken[_account] > 0 ||\n // Accounts may explicitly `rebaseOptIn` regardless of\n // accounting if they have a 0 balance.\n creditBalances[_account] == 0\n ,\n \"Account must be non-rebasing\"\n );\n RebaseOptions state = rebaseState[_account];\n // prettier-ignore\n require(\n state == RebaseOptions.StdNonRebasing ||\n state == RebaseOptions.NotSet,\n \"Only standard non-rebasing accounts can opt in\"\n );\n\n uint256 newCredits = _balanceToRebasingCredits(balance);\n\n // Account\n rebaseState[_account] = RebaseOptions.StdRebasing;\n alternativeCreditsPerToken[_account] = 0;\n creditBalances[_account] = newCredits;\n // Globals\n _adjustGlobals(newCredits.toInt256(), -balance.toInt256());\n\n emit AccountRebasingEnabled(_account);\n }\n\n /**\n * @notice The calling account will no longer receive yield\n */\n function rebaseOptOut() external {\n _rebaseOptOut(msg.sender);\n }\n\n function _rebaseOptOut(address _account) internal {\n require(\n alternativeCreditsPerToken[_account] == 0,\n \"Account must be rebasing\"\n );\n RebaseOptions state = rebaseState[_account];\n require(\n state == RebaseOptions.StdRebasing || state == RebaseOptions.NotSet,\n \"Only standard rebasing accounts can opt out\"\n );\n\n uint256 oldCredits = creditBalances[_account];\n uint256 balance = balanceOf(_account);\n\n // Account\n rebaseState[_account] = RebaseOptions.StdNonRebasing;\n alternativeCreditsPerToken[_account] = 1e18;\n creditBalances[_account] = balance;\n // Globals\n _adjustGlobals(-oldCredits.toInt256(), balance.toInt256());\n\n emit AccountRebasingDisabled(_account);\n }\n\n /**\n * @notice Distribute yield to users. This changes the exchange rate\n * between \"credits\" and OUSD tokens to change rebasing user's balances.\n * @param _newTotalSupply New total supply of OUSD.\n */\n function changeSupply(uint256 _newTotalSupply) external onlyVault {\n require(totalSupply > 0, \"Cannot increase 0 supply\");\n\n if (totalSupply == _newTotalSupply) {\n emit TotalSupplyUpdatedHighres(\n totalSupply,\n rebasingCredits_,\n rebasingCreditsPerToken_\n );\n return;\n }\n\n totalSupply = _newTotalSupply > MAX_SUPPLY\n ? MAX_SUPPLY\n : _newTotalSupply;\n\n uint256 rebasingSupply = totalSupply - nonRebasingSupply;\n // round up in the favour of the protocol\n rebasingCreditsPerToken_ =\n (rebasingCredits_ * 1e18 + rebasingSupply - 1) /\n rebasingSupply;\n\n require(rebasingCreditsPerToken_ > 0, \"Invalid change in supply\");\n\n emit TotalSupplyUpdatedHighres(\n totalSupply,\n rebasingCredits_,\n rebasingCreditsPerToken_\n );\n }\n\n /*\n * @notice Send the yield from one account to another account.\n * Each account keeps its own balances.\n */\n function delegateYield(address _from, address _to)\n external\n onlyGovernorOrStrategist\n {\n require(_from != address(0), \"Zero from address not allowed\");\n require(_to != address(0), \"Zero to address not allowed\");\n\n require(_from != _to, \"Cannot delegate to self\");\n require(\n yieldFrom[_to] == address(0) &&\n yieldTo[_to] == address(0) &&\n yieldFrom[_from] == address(0) &&\n yieldTo[_from] == address(0),\n \"Blocked by existing yield delegation\"\n );\n RebaseOptions stateFrom = rebaseState[_from];\n RebaseOptions stateTo = rebaseState[_to];\n\n require(\n stateFrom == RebaseOptions.NotSet ||\n stateFrom == RebaseOptions.StdNonRebasing ||\n stateFrom == RebaseOptions.StdRebasing,\n \"Invalid rebaseState from\"\n );\n\n require(\n stateTo == RebaseOptions.NotSet ||\n stateTo == RebaseOptions.StdNonRebasing ||\n stateTo == RebaseOptions.StdRebasing,\n \"Invalid rebaseState to\"\n );\n\n if (alternativeCreditsPerToken[_from] == 0) {\n _rebaseOptOut(_from);\n }\n if (alternativeCreditsPerToken[_to] > 0) {\n _rebaseOptIn(_to);\n }\n\n uint256 fromBalance = balanceOf(_from);\n uint256 toBalance = balanceOf(_to);\n uint256 oldToCredits = creditBalances[_to];\n uint256 newToCredits = _balanceToRebasingCredits(\n fromBalance + toBalance\n );\n\n // Set up the bidirectional links\n yieldTo[_from] = _to;\n yieldFrom[_to] = _from;\n\n // Local\n rebaseState[_from] = RebaseOptions.YieldDelegationSource;\n alternativeCreditsPerToken[_from] = 1e18;\n creditBalances[_from] = fromBalance;\n rebaseState[_to] = RebaseOptions.YieldDelegationTarget;\n creditBalances[_to] = newToCredits;\n\n // Global\n int256 creditsChange = newToCredits.toInt256() -\n oldToCredits.toInt256();\n _adjustGlobals(creditsChange, -(fromBalance).toInt256());\n emit YieldDelegated(_from, _to);\n }\n\n /*\n * @notice Stop sending the yield from one account to another account.\n */\n function undelegateYield(address _from) external onlyGovernorOrStrategist {\n // Require a delegation, which will also ensure a valid delegation\n require(yieldTo[_from] != address(0), \"Zero address not allowed\");\n\n address to = yieldTo[_from];\n uint256 fromBalance = balanceOf(_from);\n uint256 toBalance = balanceOf(to);\n uint256 oldToCredits = creditBalances[to];\n uint256 newToCredits = _balanceToRebasingCredits(toBalance);\n\n // Remove the bidirectional links\n yieldFrom[to] = address(0);\n yieldTo[_from] = address(0);\n\n // Local\n rebaseState[_from] = RebaseOptions.StdNonRebasing;\n // alternativeCreditsPerToken[from] already 1e18 from `delegateYield()`\n creditBalances[_from] = fromBalance;\n rebaseState[to] = RebaseOptions.StdRebasing;\n // alternativeCreditsPerToken[to] already 0 from `delegateYield()`\n creditBalances[to] = newToCredits;\n\n // Global\n int256 creditsChange = newToCredits.toInt256() -\n oldToCredits.toInt256();\n _adjustGlobals(creditsChange, fromBalance.toInt256());\n emit YieldUndelegated(_from, to);\n }\n}\n" + }, + "contracts/token/WOETH.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { ERC4626 } from \"../../lib/openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol\";\nimport { ERC20 } from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport { IERC20Metadata } from \"@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol\";\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\n\nimport { Governable } from \"../governance/Governable.sol\";\nimport { Initializable } from \"../utils/Initializable.sol\";\nimport { OETH } from \"./OETH.sol\";\n\n/**\n * @title Wrapped OETH Token Contract\n * @author Origin Protocol Inc\n *\n * @dev An important capability of this contract is that it isn't susceptible to changes of the\n * exchange rate of WOETH/OETH if/when someone sends the underlying asset (OETH) to the contract.\n * If OETH weren't rebasing this could be achieved by solely tracking the ERC20 transfers of the OETH\n * token on mint, deposit, redeem, withdraw. The issue is that OETH is rebasing and OETH balances\n * will change when the token rebases.\n * For that reason the contract logic checks the actual underlying OETH token balance only once\n * (either on a fresh contract creation or upgrade) and considering the WOETH supply and\n * rebasingCreditsPerToken calculates the _adjuster. Once the adjuster is calculated any donations\n * to the contract are ignored. The totalSupply (instead of querying OETH balance) works off of\n * adjuster the current WOETH supply and rebasingCreditsPerToken. This makes WOETH value accrual\n * completely follow OETH's value accrual.\n * WOETH is safe to use in lending markets as the VualtCore's _rebase contains safeguards preventing\n * any sudden large rebases.\n */\n\ncontract WOETH is ERC4626, Governable, Initializable {\n using SafeERC20 for IERC20;\n /* This is a 1e27 adjustment constant that expresses the difference in exchange rate between\n * OETH's rebase since inception (expressed with rebasingCreditsPerToken) and WOETH to OETH\n * conversion.\n *\n * If WOETH and OETH are deployed at the same time, the value of adjuster is a neutral 1e27\n */\n uint256 public adjuster;\n uint256[49] private __gap;\n\n // no need to set ERC20 name and symbol since they are overridden in WOETH & WOETHBase\n constructor(ERC20 underlying_) ERC20(\"\", \"\") ERC4626(underlying_) {}\n\n /**\n * @notice Enable OETH rebasing for this contract\n */\n function initialize() external onlyGovernor initializer {\n OETH(address(asset())).rebaseOptIn();\n\n initialize2();\n }\n\n /**\n * @notice secondary initializer that newly deployed contracts will execute as part\n * of primary initialize function and the existing contracts will have it called\n * as a governance operation.\n */\n function initialize2() public onlyGovernor {\n require(adjuster == 0, \"Initialize2 already called\");\n\n if (totalSupply() == 0) {\n adjuster = 1e27;\n } else {\n adjuster =\n (rebasingCreditsPerTokenHighres() *\n ERC20(asset()).balanceOf(address(this))) /\n totalSupply();\n }\n }\n\n function name()\n public\n view\n virtual\n override(ERC20, IERC20Metadata)\n returns (string memory)\n {\n return \"Wrapped OETH\";\n }\n\n function symbol()\n public\n view\n virtual\n override(ERC20, IERC20Metadata)\n returns (string memory)\n {\n return \"wOETH\";\n }\n\n /**\n * @notice Transfer token to governor. Intended for recovering tokens stuck in\n * contract, i.e. mistaken sends. Cannot transfer OETH\n * @param asset_ Address for the asset\n * @param amount_ Amount of the asset to transfer\n */\n function transferToken(address asset_, uint256 amount_)\n external\n onlyGovernor\n {\n require(asset_ != address(asset()), \"Cannot collect core asset\");\n IERC20(asset_).safeTransfer(governor(), amount_);\n }\n\n /// @inheritdoc ERC4626\n function convertToShares(uint256 assets)\n public\n view\n virtual\n override\n returns (uint256 shares)\n {\n return (assets * rebasingCreditsPerTokenHighres()) / adjuster;\n }\n\n /// @inheritdoc ERC4626\n function convertToAssets(uint256 shares)\n public\n view\n virtual\n override\n returns (uint256 assets)\n {\n return (shares * adjuster) / rebasingCreditsPerTokenHighres();\n }\n\n /// @inheritdoc ERC4626\n function totalAssets() public view override returns (uint256) {\n return (totalSupply() * adjuster) / rebasingCreditsPerTokenHighres();\n }\n\n function rebasingCreditsPerTokenHighres() internal view returns (uint256) {\n return OETH(asset()).rebasingCreditsPerTokenHighres();\n }\n}\n" + }, + "contracts/token/WOETHBase.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { WOETH } from \"./WOETH.sol\";\nimport { ERC20 } from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\n\n/**\n * @title OETH Token Contract\n * @author Origin Protocol Inc\n */\n\ncontract WOETHBase is WOETH {\n constructor(ERC20 underlying_) WOETH(underlying_) {}\n\n function name() public view virtual override returns (string memory) {\n return \"Wrapped Super OETH\";\n }\n\n function symbol() public view virtual override returns (string memory) {\n return \"wsuperOETHb\";\n }\n}\n" + }, + "contracts/token/WOETHPlume.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { WOETH } from \"./WOETH.sol\";\nimport { ERC20 } from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\n\n/**\n * @title wOETH (Plume) Token Contract\n * @author Origin Protocol Inc\n */\n\ncontract WOETHPlume is WOETH {\n constructor(ERC20 underlying_) WOETH(underlying_) {}\n\n function name() public view virtual override returns (string memory) {\n return \"Wrapped Super OETH\";\n }\n\n function symbol() public view virtual override returns (string memory) {\n return \"wsuperOETHp\";\n }\n}\n" + }, + "contracts/token/WOSonic.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { ERC20 } from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\n\nimport { WOETH } from \"./WOETH.sol\";\n\n/**\n * @title Wrapped Origin Sonic (wOS) token on Sonic\n * @author Origin Protocol Inc\n */\ncontract WOSonic is WOETH {\n constructor(ERC20 underlying_) WOETH(underlying_) {}\n\n function name()\n public\n view\n virtual\n override(WOETH)\n returns (string memory)\n {\n return \"Wrapped OS\";\n }\n\n function symbol()\n public\n view\n virtual\n override(WOETH)\n returns (string memory)\n {\n return \"wOS\";\n }\n}\n" + }, + "contracts/token/WrappedOusd.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { ERC20 } from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\n\nimport { WOETH } from \"./WOETH.sol\";\n\n/**\n * @title Wrapped OUSD Token Contract\n * @author Origin Protocol Inc\n */\ncontract WrappedOusd is WOETH {\n constructor(ERC20 underlying_) WOETH(underlying_) {}\n\n function name()\n public\n view\n virtual\n override(WOETH)\n returns (string memory)\n {\n return \"Wrapped OUSD\";\n }\n\n function symbol()\n public\n view\n virtual\n override(WOETH)\n returns (string memory)\n {\n return \"WOUSD\";\n }\n}\n" + }, + "contracts/utils/BalancerErrors.sol": { + "content": "// SPDX-License-Identifier: GPL-3.0-or-later\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see .\n\npragma solidity >=0.7.4 <0.9.0;\n\n// solhint-disable\n\n/**\n * @dev Reverts if `condition` is false, with a revert reason containing `errorCode`. Only codes up to 999 are\n * supported.\n * Uses the default 'BAL' prefix for the error code\n */\nfunction _require(bool condition, uint256 errorCode) pure {\n if (!condition) _revert(errorCode);\n}\n\n/**\n * @dev Reverts if `condition` is false, with a revert reason containing `errorCode`. Only codes up to 999 are\n * supported.\n */\nfunction _require(\n bool condition,\n uint256 errorCode,\n bytes3 prefix\n) pure {\n if (!condition) _revert(errorCode, prefix);\n}\n\n/**\n * @dev Reverts with a revert reason containing `errorCode`. Only codes up to 999 are supported.\n * Uses the default 'BAL' prefix for the error code\n */\nfunction _revert(uint256 errorCode) pure {\n _revert(errorCode, 0x42414c); // This is the raw byte representation of \"BAL\"\n}\n\n/**\n * @dev Reverts with a revert reason containing `errorCode`. Only codes up to 999 are supported.\n */\nfunction _revert(uint256 errorCode, bytes3 prefix) pure {\n uint256 prefixUint = uint256(uint24(prefix));\n // We're going to dynamically create a revert string based on the error code, with the following format:\n // 'BAL#{errorCode}'\n // where the code is left-padded with zeroes to three digits (so they range from 000 to 999).\n //\n // We don't have revert strings embedded in the contract to save bytecode size: it takes much less space to store a\n // number (8 to 16 bits) than the individual string characters.\n //\n // The dynamic string creation algorithm that follows could be implemented in Solidity, but assembly allows for a\n // much denser implementation, again saving bytecode size. Given this function unconditionally reverts, this is a\n // safe place to rely on it without worrying about how its usage might affect e.g. memory contents.\n assembly {\n // First, we need to compute the ASCII representation of the error code. We assume that it is in the 0-999\n // range, so we only need to convert three digits. To convert the digits to ASCII, we add 0x30, the value for\n // the '0' character.\n\n let units := add(mod(errorCode, 10), 0x30)\n\n errorCode := div(errorCode, 10)\n let tenths := add(mod(errorCode, 10), 0x30)\n\n errorCode := div(errorCode, 10)\n let hundreds := add(mod(errorCode, 10), 0x30)\n\n // With the individual characters, we can now construct the full string.\n // We first append the '#' character (0x23) to the prefix. In the case of 'BAL', it results in 0x42414c23 ('BAL#')\n // Then, we shift this by 24 (to provide space for the 3 bytes of the error code), and add the\n // characters to it, each shifted by a multiple of 8.\n // The revert reason is then shifted left by 200 bits (256 minus the length of the string, 7 characters * 8 bits\n // per character = 56) to locate it in the most significant part of the 256 slot (the beginning of a byte\n // array).\n let formattedPrefix := shl(24, add(0x23, shl(8, prefixUint)))\n\n let revertReason := shl(\n 200,\n add(\n formattedPrefix,\n add(add(units, shl(8, tenths)), shl(16, hundreds))\n )\n )\n\n // We can now encode the reason in memory, which can be safely overwritten as we're about to revert. The encoded\n // message will have the following layout:\n // [ revert reason identifier ] [ string location offset ] [ string length ] [ string contents ]\n\n // The Solidity revert reason identifier is 0x08c739a0, the function selector of the Error(string) function. We\n // also write zeroes to the next 28 bytes of memory, but those are about to be overwritten.\n mstore(\n 0x0,\n 0x08c379a000000000000000000000000000000000000000000000000000000000\n )\n // Next is the offset to the location of the string, which will be placed immediately after (20 bytes away).\n mstore(\n 0x04,\n 0x0000000000000000000000000000000000000000000000000000000000000020\n )\n // The string length is fixed: 7 characters.\n mstore(0x24, 7)\n // Finally, the string itself is stored.\n mstore(0x44, revertReason)\n\n // Even if the string is only 7 bytes long, we need to return a full 32 byte slot containing it. The length of\n // the encoded message is therefore 4 + 32 + 32 + 32 = 100.\n revert(0, 100)\n }\n}\n\nlibrary Errors {\n // Math\n uint256 internal constant ADD_OVERFLOW = 0;\n uint256 internal constant SUB_OVERFLOW = 1;\n uint256 internal constant SUB_UNDERFLOW = 2;\n uint256 internal constant MUL_OVERFLOW = 3;\n uint256 internal constant ZERO_DIVISION = 4;\n uint256 internal constant DIV_INTERNAL = 5;\n uint256 internal constant X_OUT_OF_BOUNDS = 6;\n uint256 internal constant Y_OUT_OF_BOUNDS = 7;\n uint256 internal constant PRODUCT_OUT_OF_BOUNDS = 8;\n uint256 internal constant INVALID_EXPONENT = 9;\n\n // Input\n uint256 internal constant OUT_OF_BOUNDS = 100;\n uint256 internal constant UNSORTED_ARRAY = 101;\n uint256 internal constant UNSORTED_TOKENS = 102;\n uint256 internal constant INPUT_LENGTH_MISMATCH = 103;\n uint256 internal constant ZERO_TOKEN = 104;\n uint256 internal constant INSUFFICIENT_DATA = 105;\n\n // Shared pools\n uint256 internal constant MIN_TOKENS = 200;\n uint256 internal constant MAX_TOKENS = 201;\n uint256 internal constant MAX_SWAP_FEE_PERCENTAGE = 202;\n uint256 internal constant MIN_SWAP_FEE_PERCENTAGE = 203;\n uint256 internal constant MINIMUM_BPT = 204;\n uint256 internal constant CALLER_NOT_VAULT = 205;\n uint256 internal constant UNINITIALIZED = 206;\n uint256 internal constant BPT_IN_MAX_AMOUNT = 207;\n uint256 internal constant BPT_OUT_MIN_AMOUNT = 208;\n uint256 internal constant EXPIRED_PERMIT = 209;\n uint256 internal constant NOT_TWO_TOKENS = 210;\n uint256 internal constant DISABLED = 211;\n\n // Pools\n uint256 internal constant MIN_AMP = 300;\n uint256 internal constant MAX_AMP = 301;\n uint256 internal constant MIN_WEIGHT = 302;\n uint256 internal constant MAX_STABLE_TOKENS = 303;\n uint256 internal constant MAX_IN_RATIO = 304;\n uint256 internal constant MAX_OUT_RATIO = 305;\n uint256 internal constant MIN_BPT_IN_FOR_TOKEN_OUT = 306;\n uint256 internal constant MAX_OUT_BPT_FOR_TOKEN_IN = 307;\n uint256 internal constant NORMALIZED_WEIGHT_INVARIANT = 308;\n uint256 internal constant INVALID_TOKEN = 309;\n uint256 internal constant UNHANDLED_JOIN_KIND = 310;\n uint256 internal constant ZERO_INVARIANT = 311;\n uint256 internal constant ORACLE_INVALID_SECONDS_QUERY = 312;\n uint256 internal constant ORACLE_NOT_INITIALIZED = 313;\n uint256 internal constant ORACLE_QUERY_TOO_OLD = 314;\n uint256 internal constant ORACLE_INVALID_INDEX = 315;\n uint256 internal constant ORACLE_BAD_SECS = 316;\n uint256 internal constant AMP_END_TIME_TOO_CLOSE = 317;\n uint256 internal constant AMP_ONGOING_UPDATE = 318;\n uint256 internal constant AMP_RATE_TOO_HIGH = 319;\n uint256 internal constant AMP_NO_ONGOING_UPDATE = 320;\n uint256 internal constant STABLE_INVARIANT_DIDNT_CONVERGE = 321;\n uint256 internal constant STABLE_GET_BALANCE_DIDNT_CONVERGE = 322;\n uint256 internal constant RELAYER_NOT_CONTRACT = 323;\n uint256 internal constant BASE_POOL_RELAYER_NOT_CALLED = 324;\n uint256 internal constant REBALANCING_RELAYER_REENTERED = 325;\n uint256 internal constant GRADUAL_UPDATE_TIME_TRAVEL = 326;\n uint256 internal constant SWAPS_DISABLED = 327;\n uint256 internal constant CALLER_IS_NOT_LBP_OWNER = 328;\n uint256 internal constant PRICE_RATE_OVERFLOW = 329;\n uint256 internal constant INVALID_JOIN_EXIT_KIND_WHILE_SWAPS_DISABLED = 330;\n uint256 internal constant WEIGHT_CHANGE_TOO_FAST = 331;\n uint256 internal constant LOWER_GREATER_THAN_UPPER_TARGET = 332;\n uint256 internal constant UPPER_TARGET_TOO_HIGH = 333;\n uint256 internal constant UNHANDLED_BY_LINEAR_POOL = 334;\n uint256 internal constant OUT_OF_TARGET_RANGE = 335;\n uint256 internal constant UNHANDLED_EXIT_KIND = 336;\n uint256 internal constant UNAUTHORIZED_EXIT = 337;\n uint256 internal constant MAX_MANAGEMENT_SWAP_FEE_PERCENTAGE = 338;\n uint256 internal constant UNHANDLED_BY_MANAGED_POOL = 339;\n uint256 internal constant UNHANDLED_BY_PHANTOM_POOL = 340;\n uint256 internal constant TOKEN_DOES_NOT_HAVE_RATE_PROVIDER = 341;\n uint256 internal constant INVALID_INITIALIZATION = 342;\n uint256 internal constant OUT_OF_NEW_TARGET_RANGE = 343;\n uint256 internal constant FEATURE_DISABLED = 344;\n uint256 internal constant UNINITIALIZED_POOL_CONTROLLER = 345;\n uint256 internal constant SET_SWAP_FEE_DURING_FEE_CHANGE = 346;\n uint256 internal constant SET_SWAP_FEE_PENDING_FEE_CHANGE = 347;\n uint256 internal constant CHANGE_TOKENS_DURING_WEIGHT_CHANGE = 348;\n uint256 internal constant CHANGE_TOKENS_PENDING_WEIGHT_CHANGE = 349;\n uint256 internal constant MAX_WEIGHT = 350;\n uint256 internal constant UNAUTHORIZED_JOIN = 351;\n uint256 internal constant MAX_MANAGEMENT_AUM_FEE_PERCENTAGE = 352;\n uint256 internal constant FRACTIONAL_TARGET = 353;\n uint256 internal constant ADD_OR_REMOVE_BPT = 354;\n uint256 internal constant INVALID_CIRCUIT_BREAKER_BOUNDS = 355;\n uint256 internal constant CIRCUIT_BREAKER_TRIPPED = 356;\n uint256 internal constant MALICIOUS_QUERY_REVERT = 357;\n uint256 internal constant JOINS_EXITS_DISABLED = 358;\n\n // Lib\n uint256 internal constant REENTRANCY = 400;\n uint256 internal constant SENDER_NOT_ALLOWED = 401;\n uint256 internal constant PAUSED = 402;\n uint256 internal constant PAUSE_WINDOW_EXPIRED = 403;\n uint256 internal constant MAX_PAUSE_WINDOW_DURATION = 404;\n uint256 internal constant MAX_BUFFER_PERIOD_DURATION = 405;\n uint256 internal constant INSUFFICIENT_BALANCE = 406;\n uint256 internal constant INSUFFICIENT_ALLOWANCE = 407;\n uint256 internal constant ERC20_TRANSFER_FROM_ZERO_ADDRESS = 408;\n uint256 internal constant ERC20_TRANSFER_TO_ZERO_ADDRESS = 409;\n uint256 internal constant ERC20_MINT_TO_ZERO_ADDRESS = 410;\n uint256 internal constant ERC20_BURN_FROM_ZERO_ADDRESS = 411;\n uint256 internal constant ERC20_APPROVE_FROM_ZERO_ADDRESS = 412;\n uint256 internal constant ERC20_APPROVE_TO_ZERO_ADDRESS = 413;\n uint256 internal constant ERC20_TRANSFER_EXCEEDS_ALLOWANCE = 414;\n uint256 internal constant ERC20_DECREASED_ALLOWANCE_BELOW_ZERO = 415;\n uint256 internal constant ERC20_TRANSFER_EXCEEDS_BALANCE = 416;\n uint256 internal constant ERC20_BURN_EXCEEDS_ALLOWANCE = 417;\n uint256 internal constant SAFE_ERC20_CALL_FAILED = 418;\n uint256 internal constant ADDRESS_INSUFFICIENT_BALANCE = 419;\n uint256 internal constant ADDRESS_CANNOT_SEND_VALUE = 420;\n uint256 internal constant SAFE_CAST_VALUE_CANT_FIT_INT256 = 421;\n uint256 internal constant GRANT_SENDER_NOT_ADMIN = 422;\n uint256 internal constant REVOKE_SENDER_NOT_ADMIN = 423;\n uint256 internal constant RENOUNCE_SENDER_NOT_ALLOWED = 424;\n uint256 internal constant BUFFER_PERIOD_EXPIRED = 425;\n uint256 internal constant CALLER_IS_NOT_OWNER = 426;\n uint256 internal constant NEW_OWNER_IS_ZERO = 427;\n uint256 internal constant CODE_DEPLOYMENT_FAILED = 428;\n uint256 internal constant CALL_TO_NON_CONTRACT = 429;\n uint256 internal constant LOW_LEVEL_CALL_FAILED = 430;\n uint256 internal constant NOT_PAUSED = 431;\n uint256 internal constant ADDRESS_ALREADY_ALLOWLISTED = 432;\n uint256 internal constant ADDRESS_NOT_ALLOWLISTED = 433;\n uint256 internal constant ERC20_BURN_EXCEEDS_BALANCE = 434;\n uint256 internal constant INVALID_OPERATION = 435;\n uint256 internal constant CODEC_OVERFLOW = 436;\n uint256 internal constant IN_RECOVERY_MODE = 437;\n uint256 internal constant NOT_IN_RECOVERY_MODE = 438;\n uint256 internal constant INDUCED_FAILURE = 439;\n uint256 internal constant EXPIRED_SIGNATURE = 440;\n uint256 internal constant MALFORMED_SIGNATURE = 441;\n uint256 internal constant SAFE_CAST_VALUE_CANT_FIT_UINT64 = 442;\n uint256 internal constant UNHANDLED_FEE_TYPE = 443;\n uint256 internal constant BURN_FROM_ZERO = 444;\n\n // Vault\n uint256 internal constant INVALID_POOL_ID = 500;\n uint256 internal constant CALLER_NOT_POOL = 501;\n uint256 internal constant SENDER_NOT_ASSET_MANAGER = 502;\n uint256 internal constant USER_DOESNT_ALLOW_RELAYER = 503;\n uint256 internal constant INVALID_SIGNATURE = 504;\n uint256 internal constant EXIT_BELOW_MIN = 505;\n uint256 internal constant JOIN_ABOVE_MAX = 506;\n uint256 internal constant SWAP_LIMIT = 507;\n uint256 internal constant SWAP_DEADLINE = 508;\n uint256 internal constant CANNOT_SWAP_SAME_TOKEN = 509;\n uint256 internal constant UNKNOWN_AMOUNT_IN_FIRST_SWAP = 510;\n uint256 internal constant MALCONSTRUCTED_MULTIHOP_SWAP = 511;\n uint256 internal constant INTERNAL_BALANCE_OVERFLOW = 512;\n uint256 internal constant INSUFFICIENT_INTERNAL_BALANCE = 513;\n uint256 internal constant INVALID_ETH_INTERNAL_BALANCE = 514;\n uint256 internal constant INVALID_POST_LOAN_BALANCE = 515;\n uint256 internal constant INSUFFICIENT_ETH = 516;\n uint256 internal constant UNALLOCATED_ETH = 517;\n uint256 internal constant ETH_TRANSFER = 518;\n uint256 internal constant CANNOT_USE_ETH_SENTINEL = 519;\n uint256 internal constant TOKENS_MISMATCH = 520;\n uint256 internal constant TOKEN_NOT_REGISTERED = 521;\n uint256 internal constant TOKEN_ALREADY_REGISTERED = 522;\n uint256 internal constant TOKENS_ALREADY_SET = 523;\n uint256 internal constant TOKENS_LENGTH_MUST_BE_2 = 524;\n uint256 internal constant NONZERO_TOKEN_BALANCE = 525;\n uint256 internal constant BALANCE_TOTAL_OVERFLOW = 526;\n uint256 internal constant POOL_NO_TOKENS = 527;\n uint256 internal constant INSUFFICIENT_FLASH_LOAN_BALANCE = 528;\n\n // Fees\n uint256 internal constant SWAP_FEE_PERCENTAGE_TOO_HIGH = 600;\n uint256 internal constant FLASH_LOAN_FEE_PERCENTAGE_TOO_HIGH = 601;\n uint256 internal constant INSUFFICIENT_FLASH_LOAN_FEE_AMOUNT = 602;\n uint256 internal constant AUM_FEE_PERCENTAGE_TOO_HIGH = 603;\n\n // FeeSplitter\n uint256 internal constant SPLITTER_FEE_PERCENTAGE_TOO_HIGH = 700;\n\n // Misc\n uint256 internal constant UNIMPLEMENTED = 998;\n uint256 internal constant SHOULD_NOT_HAPPEN = 999;\n}\n" + }, + "contracts/utils/BytesHelper.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nuint256 constant UINT32_LENGTH = 4;\nuint256 constant UINT64_LENGTH = 8;\nuint256 constant UINT256_LENGTH = 32;\n// Address is 20 bytes, but we expect the data to be padded with 0s to 32 bytes\nuint256 constant ADDRESS_LENGTH = 32;\n\nlibrary BytesHelper {\n /**\n * @dev Extract a slice from bytes memory\n * @param data The bytes memory to slice\n * @param start The start index (inclusive)\n * @param end The end index (exclusive)\n * @return result A new bytes memory containing the slice\n */\n function extractSlice(\n bytes memory data,\n uint256 start,\n uint256 end\n ) internal pure returns (bytes memory) {\n require(end >= start, \"Invalid slice range\");\n require(end <= data.length, \"Slice end exceeds data length\");\n\n uint256 length = end - start;\n bytes memory result = new bytes(length);\n\n // Simple byte-by-byte copy\n for (uint256 i = 0; i < length; i++) {\n result[i] = data[start + i];\n }\n\n return result;\n }\n\n /**\n * @dev Decode a uint32 from a bytes memory\n * @param data The bytes memory to decode\n * @return uint32 The decoded uint32\n */\n function decodeUint32(bytes memory data) internal pure returns (uint32) {\n require(data.length == 4, \"Invalid data length\");\n return uint32(uint256(bytes32(data)) >> 224);\n }\n\n /**\n * @dev Extract a uint32 from a bytes memory\n * @param data The bytes memory to extract from\n * @param start The start index (inclusive)\n * @return uint32 The extracted uint32\n */\n function extractUint32(bytes memory data, uint256 start)\n internal\n pure\n returns (uint32)\n {\n return decodeUint32(extractSlice(data, start, start + UINT32_LENGTH));\n }\n\n /**\n * @dev Decode an address from a bytes memory.\n * Expects the data to be padded with 0s to 32 bytes.\n * @param data The bytes memory to decode\n * @return address The decoded address\n */\n function decodeAddress(bytes memory data) internal pure returns (address) {\n // We expect the data to be padded with 0s, so length is 32 not 20\n require(data.length == 32, \"Invalid data length\");\n return abi.decode(data, (address));\n }\n\n /**\n * @dev Extract an address from a bytes memory\n * @param data The bytes memory to extract from\n * @param start The start index (inclusive)\n * @return address The extracted address\n */\n function extractAddress(bytes memory data, uint256 start)\n internal\n pure\n returns (address)\n {\n return decodeAddress(extractSlice(data, start, start + ADDRESS_LENGTH));\n }\n\n /**\n * @dev Decode a uint256 from a bytes memory\n * @param data The bytes memory to decode\n * @return uint256 The decoded uint256\n */\n function decodeUint256(bytes memory data) internal pure returns (uint256) {\n require(data.length == 32, \"Invalid data length\");\n return abi.decode(data, (uint256));\n }\n\n /**\n * @dev Extract a uint256 from a bytes memory\n * @param data The bytes memory to extract from\n * @param start The start index (inclusive)\n * @return uint256 The extracted uint256\n */\n function extractUint256(bytes memory data, uint256 start)\n internal\n pure\n returns (uint256)\n {\n return decodeUint256(extractSlice(data, start, start + UINT256_LENGTH));\n }\n}\n" + }, + "contracts/utils/Helpers.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { IBasicToken } from \"../interfaces/IBasicToken.sol\";\n\nlibrary Helpers {\n /**\n * @notice Fetch the `symbol()` from an ERC20 token\n * @dev Grabs the `symbol()` from a contract\n * @param _token Address of the ERC20 token\n * @return string Symbol of the ERC20 token\n */\n function getSymbol(address _token) internal view returns (string memory) {\n string memory symbol = IBasicToken(_token).symbol();\n return symbol;\n }\n\n /**\n * @notice Fetch the `decimals()` from an ERC20 token\n * @dev Grabs the `decimals()` from a contract and fails if\n * the decimal value does not live within a certain range\n * @param _token Address of the ERC20 token\n * @return uint256 Decimals of the ERC20 token\n */\n function getDecimals(address _token) internal view returns (uint256) {\n uint256 decimals = IBasicToken(_token).decimals();\n require(\n decimals >= 4 && decimals <= 18,\n \"Token must have sufficient decimal places\"\n );\n\n return decimals;\n }\n}\n" + }, + "contracts/utils/Initializable.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title Base contract any contracts that need to initialize state after deployment.\n * @author Origin Protocol Inc\n */\nabstract contract Initializable {\n /**\n * @dev Indicates that the contract has been initialized.\n */\n bool private initialized;\n\n /**\n * @dev Indicates that the contract is in the process of being initialized.\n */\n bool private initializing;\n\n /**\n * @dev Modifier to protect an initializer function from being invoked twice.\n */\n modifier initializer() {\n require(\n initializing || !initialized,\n \"Initializable: contract is already initialized\"\n );\n\n bool isTopLevelCall = !initializing;\n if (isTopLevelCall) {\n initializing = true;\n initialized = true;\n }\n\n _;\n\n if (isTopLevelCall) {\n initializing = false;\n }\n }\n\n uint256[50] private ______gap;\n}\n" + }, + "contracts/utils/InitializableAbstractStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title Base contract for vault strategies.\n * @author Origin Protocol Inc\n */\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\n\nimport { Initializable } from \"../utils/Initializable.sol\";\nimport { Governable } from \"../governance/Governable.sol\";\nimport { IVault } from \"../interfaces/IVault.sol\";\n\nabstract contract InitializableAbstractStrategy is Initializable, Governable {\n using SafeERC20 for IERC20;\n\n event PTokenAdded(address indexed _asset, address _pToken);\n event PTokenRemoved(address indexed _asset, address _pToken);\n event Deposit(address indexed _asset, address _pToken, uint256 _amount);\n event Withdrawal(address indexed _asset, address _pToken, uint256 _amount);\n event RewardTokenCollected(\n address recipient,\n address rewardToken,\n uint256 amount\n );\n event RewardTokenAddressesUpdated(\n address[] _oldAddresses,\n address[] _newAddresses\n );\n event HarvesterAddressesUpdated(\n address _oldHarvesterAddress,\n address _newHarvesterAddress\n );\n\n /// @notice Address of the underlying platform\n address public immutable platformAddress;\n /// @notice Address of the OToken vault\n address public immutable vaultAddress;\n\n /// @dev Replaced with an immutable variable\n // slither-disable-next-line constable-states\n address private _deprecated_platformAddress;\n\n /// @dev Replaced with an immutable\n // slither-disable-next-line constable-states\n address private _deprecated_vaultAddress;\n\n /// @notice asset => pToken (Platform Specific Token Address)\n mapping(address => address) public assetToPToken;\n\n /// @notice Full list of all assets supported by the strategy\n address[] internal assetsMapped;\n\n // Deprecated: Reward token address\n // slither-disable-next-line constable-states\n address private _deprecated_rewardTokenAddress;\n\n // Deprecated: now resides in Harvester's rewardTokenConfigs\n // slither-disable-next-line constable-states\n uint256 private _deprecated_rewardLiquidationThreshold;\n\n /// @notice Address of the Harvester contract allowed to collect reward tokens\n address public harvesterAddress;\n\n /// @notice Address of the reward tokens. eg CRV, BAL, CVX, AURA\n address[] public rewardTokenAddresses;\n\n /* Reserved for future expansion. Used to be 100 storage slots\n * and has decreased to accommodate:\n * - harvesterAddress\n * - rewardTokenAddresses\n */\n int256[98] private _reserved;\n\n struct BaseStrategyConfig {\n address platformAddress; // Address of the underlying platform\n address vaultAddress; // Address of the OToken's Vault\n }\n\n /**\n * @dev Verifies that the caller is the Governor or Strategist.\n */\n modifier onlyGovernorOrStrategist() virtual {\n require(\n isGovernor() || msg.sender == IVault(vaultAddress).strategistAddr(),\n \"Caller is not the Strategist or Governor\"\n );\n _;\n }\n\n /**\n * @param _config The platform and OToken vault addresses\n */\n constructor(BaseStrategyConfig memory _config) {\n platformAddress = _config.platformAddress;\n vaultAddress = _config.vaultAddress;\n }\n\n /**\n * @dev Internal initialize function, to set up initial internal state\n * @param _rewardTokenAddresses Address of reward token for platform\n * @param _assets Addresses of initial supported assets\n * @param _pTokens Platform Token corresponding addresses\n */\n function _initialize(\n address[] memory _rewardTokenAddresses,\n address[] memory _assets,\n address[] memory _pTokens\n ) internal {\n rewardTokenAddresses = _rewardTokenAddresses;\n\n uint256 assetCount = _assets.length;\n require(assetCount == _pTokens.length, \"Invalid input arrays\");\n for (uint256 i = 0; i < assetCount; ++i) {\n _setPTokenAddress(_assets[i], _pTokens[i]);\n }\n }\n\n /**\n * @notice Collect accumulated reward token and send to Vault.\n */\n function collectRewardTokens() external virtual onlyHarvester nonReentrant {\n _collectRewardTokens();\n }\n\n /**\n * @dev Default implementation that transfers reward tokens to the Harvester\n * Implementing strategies need to add custom logic to collect the rewards.\n */\n function _collectRewardTokens() internal virtual {\n uint256 rewardTokenCount = rewardTokenAddresses.length;\n for (uint256 i = 0; i < rewardTokenCount; ++i) {\n IERC20 rewardToken = IERC20(rewardTokenAddresses[i]);\n uint256 balance = rewardToken.balanceOf(address(this));\n if (balance > 0) {\n emit RewardTokenCollected(\n harvesterAddress,\n address(rewardToken),\n balance\n );\n rewardToken.safeTransfer(harvesterAddress, balance);\n }\n }\n }\n\n /**\n * @dev Verifies that the caller is the Vault.\n */\n modifier onlyVault() {\n require(msg.sender == vaultAddress, \"Caller is not the Vault\");\n _;\n }\n\n /**\n * @dev Verifies that the caller is the Harvester.\n */\n modifier onlyHarvester() {\n require(msg.sender == harvesterAddress, \"Caller is not the Harvester\");\n _;\n }\n\n /**\n * @dev Verifies that the caller is the Vault or Governor.\n */\n modifier onlyVaultOrGovernor() {\n require(\n msg.sender == vaultAddress || msg.sender == governor(),\n \"Caller is not the Vault or Governor\"\n );\n _;\n }\n\n /**\n * @dev Verifies that the caller is the Vault, Governor, or Strategist.\n */\n modifier onlyVaultOrGovernorOrStrategist() {\n require(\n msg.sender == vaultAddress ||\n msg.sender == governor() ||\n msg.sender == IVault(vaultAddress).strategistAddr(),\n \"Caller is not the Vault, Governor, or Strategist\"\n );\n _;\n }\n\n /**\n * @notice Set the reward token addresses. Any old addresses will be overwritten.\n * @param _rewardTokenAddresses Array of reward token addresses\n */\n function setRewardTokenAddresses(address[] calldata _rewardTokenAddresses)\n external\n onlyGovernor\n {\n uint256 rewardTokenCount = _rewardTokenAddresses.length;\n for (uint256 i = 0; i < rewardTokenCount; ++i) {\n require(\n _rewardTokenAddresses[i] != address(0),\n \"Can not set an empty address as a reward token\"\n );\n }\n\n emit RewardTokenAddressesUpdated(\n rewardTokenAddresses,\n _rewardTokenAddresses\n );\n rewardTokenAddresses = _rewardTokenAddresses;\n }\n\n /**\n * @notice Get the reward token addresses.\n * @return address[] the reward token addresses.\n */\n function getRewardTokenAddresses()\n external\n view\n returns (address[] memory)\n {\n return rewardTokenAddresses;\n }\n\n /**\n * @notice Provide support for asset by passing its pToken address.\n * This method can only be called by the system Governor\n * @param _asset Address for the asset\n * @param _pToken Address for the corresponding platform token\n */\n function setPTokenAddress(address _asset, address _pToken)\n external\n virtual\n onlyGovernor\n {\n _setPTokenAddress(_asset, _pToken);\n }\n\n /**\n * @notice Remove a supported asset by passing its index.\n * This method can only be called by the system Governor\n * @param _assetIndex Index of the asset to be removed\n */\n function removePToken(uint256 _assetIndex) external virtual onlyGovernor {\n require(_assetIndex < assetsMapped.length, \"Invalid index\");\n address asset = assetsMapped[_assetIndex];\n address pToken = assetToPToken[asset];\n\n if (_assetIndex < assetsMapped.length - 1) {\n assetsMapped[_assetIndex] = assetsMapped[assetsMapped.length - 1];\n }\n assetsMapped.pop();\n assetToPToken[asset] = address(0);\n\n emit PTokenRemoved(asset, pToken);\n }\n\n /**\n * @notice Provide support for asset by passing its pToken address.\n * Add to internal mappings and execute the platform specific,\n * abstract method `_abstractSetPToken`\n * @param _asset Address for the asset\n * @param _pToken Address for the corresponding platform token\n */\n function _setPTokenAddress(address _asset, address _pToken) internal {\n require(assetToPToken[_asset] == address(0), \"pToken already set\");\n require(\n _asset != address(0) && _pToken != address(0),\n \"Invalid addresses\"\n );\n\n assetToPToken[_asset] = _pToken;\n assetsMapped.push(_asset);\n\n emit PTokenAdded(_asset, _pToken);\n\n _abstractSetPToken(_asset, _pToken);\n }\n\n /**\n * @notice Transfer token to governor. Intended for recovering tokens stuck in\n * strategy contracts, i.e. mistaken sends.\n * @param _asset Address for the asset\n * @param _amount Amount of the asset to transfer\n */\n function transferToken(address _asset, uint256 _amount)\n public\n virtual\n onlyGovernor\n {\n require(!supportsAsset(_asset), \"Cannot transfer supported asset\");\n IERC20(_asset).safeTransfer(governor(), _amount);\n }\n\n /**\n * @notice Set the Harvester contract that can collect rewards.\n * @param _harvesterAddress Address of the harvester contract.\n */\n function setHarvesterAddress(address _harvesterAddress)\n external\n onlyGovernor\n {\n emit HarvesterAddressesUpdated(harvesterAddress, _harvesterAddress);\n harvesterAddress = _harvesterAddress;\n }\n\n /***************************************\n Abstract\n ****************************************/\n\n function _abstractSetPToken(address _asset, address _pToken)\n internal\n virtual;\n\n function safeApproveAllTokens() external virtual;\n\n /**\n * @notice Deposit an amount of assets into the platform\n * @param _asset Address for the asset\n * @param _amount Units of asset to deposit\n */\n function deposit(address _asset, uint256 _amount) external virtual;\n\n /**\n * @notice Deposit all supported assets in this strategy contract to the platform\n */\n function depositAll() external virtual;\n\n /**\n * @notice Withdraw an `amount` of assets from the platform and\n * send to the `_recipient`.\n * @param _recipient Address to which the asset should be sent\n * @param _asset Address of the asset\n * @param _amount Units of asset to withdraw\n */\n function withdraw(\n address _recipient,\n address _asset,\n uint256 _amount\n ) external virtual;\n\n /**\n * @notice Withdraw all supported assets from platform and\n * sends to the OToken's Vault.\n */\n function withdrawAll() external virtual;\n\n /**\n * @notice Get the total asset value held in the platform.\n * This includes any interest that was generated since depositing.\n * @param _asset Address of the asset\n * @return balance Total value of the asset in the platform\n */\n function checkBalance(address _asset)\n external\n view\n virtual\n returns (uint256 balance);\n\n /**\n * @notice Check if an asset is supported.\n * @param _asset Address of the asset\n * @return bool Whether asset is supported\n */\n function supportsAsset(address _asset) public view virtual returns (bool);\n}\n" + }, + "contracts/utils/PRBMath.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n// Copied from the PRBMath library\n// https://github.com/PaulRBerg/prb-math/blob/main/src/Common.sol\n\n/// @notice Calculates the square root of x using the Babylonian method.\n///\n/// @dev See https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method.\n///\n/// Notes:\n/// - If x is not a perfect square, the result is rounded down.\n/// - Credits to OpenZeppelin for the explanations in comments below.\n///\n/// @param x The uint256 number for which to calculate the square root.\n/// @return result The result as a uint256.\n/// @custom:smtchecker abstract-function-nondet\nfunction sqrt(uint256 x) pure returns (uint256 result) {\n if (x == 0) {\n return 0;\n }\n\n // For our first guess, we calculate the biggest power of 2 which is smaller than the square root of x.\n //\n // We know that the \"msb\" (most significant bit) of x is a power of 2 such that we have:\n //\n // $$\n // msb(x) <= x <= 2*msb(x)$\n // $$\n //\n // We write $msb(x)$ as $2^k$, and we get:\n //\n // $$\n // k = log_2(x)\n // $$\n //\n // Thus, we can write the initial inequality as:\n //\n // $$\n // 2^{log_2(x)} <= x <= 2*2^{log_2(x)+1} \\\\\n // sqrt(2^k) <= sqrt(x) < sqrt(2^{k+1}) \\\\\n // 2^{k/2} <= sqrt(x) < 2^{(k+1)/2} <= 2^{(k/2)+1}\n // $$\n //\n // Consequently, $2^{log_2(x) /2} is a good first approximation of sqrt(x) with at least one correct bit.\n uint256 xAux = uint256(x);\n result = 1;\n if (xAux >= 2**128) {\n xAux >>= 128;\n result <<= 64;\n }\n if (xAux >= 2**64) {\n xAux >>= 64;\n result <<= 32;\n }\n if (xAux >= 2**32) {\n xAux >>= 32;\n result <<= 16;\n }\n if (xAux >= 2**16) {\n xAux >>= 16;\n result <<= 8;\n }\n if (xAux >= 2**8) {\n xAux >>= 8;\n result <<= 4;\n }\n if (xAux >= 2**4) {\n xAux >>= 4;\n result <<= 2;\n }\n if (xAux >= 2**2) {\n result <<= 1;\n }\n\n // At this point, `result` is an estimation with at least one bit of precision. We know the true value has at\n // most 128 bits, since it is the square root of a uint256. Newton's method converges quadratically (precision\n // doubles at every iteration). We thus need at most 7 iteration to turn our partial result with one bit of\n // precision into the expected uint128 result.\n unchecked {\n result = (result + x / result) >> 1;\n result = (result + x / result) >> 1;\n result = (result + x / result) >> 1;\n result = (result + x / result) >> 1;\n result = (result + x / result) >> 1;\n result = (result + x / result) >> 1;\n result = (result + x / result) >> 1;\n\n // If x is not a perfect square, round the result toward zero.\n uint256 roundedResult = x / result;\n if (result >= roundedResult) {\n result = roundedResult;\n }\n }\n}\n" + }, + "contracts/utils/StableMath.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { SafeMath } from \"@openzeppelin/contracts/utils/math/SafeMath.sol\";\n\n// Based on StableMath from Stability Labs Pty. Ltd.\n// https://github.com/mstable/mStable-contracts/blob/master/contracts/shared/StableMath.sol\n\nlibrary StableMath {\n using SafeMath for uint256;\n\n /**\n * @dev Scaling unit for use in specific calculations,\n * where 1 * 10**18, or 1e18 represents a unit '1'\n */\n uint256 private constant FULL_SCALE = 1e18;\n\n /***************************************\n Helpers\n ****************************************/\n\n /**\n * @dev Adjust the scale of an integer\n * @param to Decimals to scale to\n * @param from Decimals to scale from\n */\n function scaleBy(\n uint256 x,\n uint256 to,\n uint256 from\n ) internal pure returns (uint256) {\n if (to > from) {\n x = x.mul(10**(to - from));\n } else if (to < from) {\n // slither-disable-next-line divide-before-multiply\n x = x.div(10**(from - to));\n }\n return x;\n }\n\n /***************************************\n Precise Arithmetic\n ****************************************/\n\n /**\n * @dev Multiplies two precise units, and then truncates by the full scale\n * @param x Left hand input to multiplication\n * @param y Right hand input to multiplication\n * @return Result after multiplying the two inputs and then dividing by the shared\n * scale unit\n */\n function mulTruncate(uint256 x, uint256 y) internal pure returns (uint256) {\n return mulTruncateScale(x, y, FULL_SCALE);\n }\n\n /**\n * @dev Multiplies two precise units, and then truncates by the given scale. For example,\n * when calculating 90% of 10e18, (10e18 * 9e17) / 1e18 = (9e36) / 1e18 = 9e18\n * @param x Left hand input to multiplication\n * @param y Right hand input to multiplication\n * @param scale Scale unit\n * @return Result after multiplying the two inputs and then dividing by the shared\n * scale unit\n */\n function mulTruncateScale(\n uint256 x,\n uint256 y,\n uint256 scale\n ) internal pure returns (uint256) {\n // e.g. assume scale = fullScale\n // z = 10e18 * 9e17 = 9e36\n uint256 z = x.mul(y);\n // return 9e36 / 1e18 = 9e18\n return z.div(scale);\n }\n\n /**\n * @dev Multiplies two precise units, and then truncates by the full scale, rounding up the result\n * @param x Left hand input to multiplication\n * @param y Right hand input to multiplication\n * @return Result after multiplying the two inputs and then dividing by the shared\n * scale unit, rounded up to the closest base unit.\n */\n function mulTruncateCeil(uint256 x, uint256 y)\n internal\n pure\n returns (uint256)\n {\n // e.g. 8e17 * 17268172638 = 138145381104e17\n uint256 scaled = x.mul(y);\n // e.g. 138145381104e17 + 9.99...e17 = 138145381113.99...e17\n uint256 ceil = scaled.add(FULL_SCALE.sub(1));\n // e.g. 13814538111.399...e18 / 1e18 = 13814538111\n return ceil.div(FULL_SCALE);\n }\n\n /**\n * @dev Precisely divides two units, by first scaling the left hand operand. Useful\n * for finding percentage weightings, i.e. 8e18/10e18 = 80% (or 8e17)\n * @param x Left hand input to division\n * @param y Right hand input to division\n * @return Result after multiplying the left operand by the scale, and\n * executing the division on the right hand input.\n */\n function divPrecisely(uint256 x, uint256 y)\n internal\n pure\n returns (uint256)\n {\n // e.g. 8e18 * 1e18 = 8e36\n uint256 z = x.mul(FULL_SCALE);\n // e.g. 8e36 / 10e18 = 8e17\n return z.div(y);\n }\n}\n" + }, + "contracts/vault/OETHBaseVault.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { VaultAdmin } from \"./VaultAdmin.sol\";\n\n/**\n * @title OETH Base VaultAdmin Contract\n * @author Origin Protocol Inc\n */\ncontract OETHBaseVault is VaultAdmin {\n constructor(address _weth) VaultAdmin(_weth) {}\n}\n" + }, + "contracts/vault/OETHPlumeVault.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { VaultAdmin } from \"./VaultAdmin.sol\";\n\n/**\n * @title OETH Plume VaultAdmin Contract\n * @author Origin Protocol Inc\n */\ncontract OETHPlumeVault is VaultAdmin {\n constructor(address _weth) VaultAdmin(_weth) {}\n\n // @inheritdoc VaultAdmin\n function _mint(\n address,\n uint256 _amount,\n uint256\n ) internal virtual {\n // Only Strategist or Governor can mint using the Vault for now.\n // This allows the strateigst to fund the Vault with WETH when\n // removing liquidi from wOETH strategy.\n require(\n msg.sender == strategistAddr || isGovernor(),\n \"Caller is not the Strategist or Governor\"\n );\n\n super._mint(_amount);\n }\n}\n" + }, + "contracts/vault/OETHVault.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { VaultAdmin } from \"./VaultAdmin.sol\";\n\n/**\n * @title OETH VaultAdmin Contract\n * @author Origin Protocol Inc\n */\ncontract OETHVault is VaultAdmin {\n constructor(address _weth) VaultAdmin(_weth) {}\n}\n" + }, + "contracts/vault/OSVault.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { VaultAdmin } from \"./VaultAdmin.sol\";\n\n/**\n * @title Origin Sonic VaultAdmin contract on Sonic\n * @author Origin Protocol Inc\n */\ncontract OSVault is VaultAdmin {\n constructor(address _wS) VaultAdmin(_wS) {}\n}\n" + }, + "contracts/vault/OUSDVault.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { VaultAdmin } from \"./VaultAdmin.sol\";\n\n/**\n * @title OUSD VaultAdmin Contract\n * @author Origin Protocol Inc\n */\ncontract OUSDVault is VaultAdmin {\n constructor(address _usdc) VaultAdmin(_usdc) {}\n}\n" + }, + "contracts/vault/VaultAdmin.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title OToken VaultAdmin contract\n * @notice The VaultAdmin contract makes configuration and admin calls on the vault.\n * @author Origin Protocol Inc\n */\n\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\n\nimport { IVault } from \"../interfaces/IVault.sol\";\nimport { StableMath } from \"../utils/StableMath.sol\";\nimport { SafeCast } from \"@openzeppelin/contracts/utils/math/SafeCast.sol\";\n\nimport \"./VaultCore.sol\";\n\nabstract contract VaultAdmin is VaultCore {\n using SafeERC20 for IERC20;\n using StableMath for uint256;\n using SafeCast for uint256;\n\n /**\n * @dev Verifies that the caller is the Governor or Strategist.\n */\n modifier onlyGovernorOrStrategist() {\n require(\n msg.sender == strategistAddr || isGovernor(),\n \"Caller is not the Strategist or Governor\"\n );\n _;\n }\n\n constructor(address _asset) VaultCore(_asset) {}\n\n /***************************************\n Configuration\n ****************************************/\n /**\n * @notice Set a buffer of asset to keep in the Vault to handle most\n * redemptions without needing to spend gas unwinding asset from a Strategy.\n * @param _vaultBuffer Percentage using 18 decimals. 100% = 1e18.\n */\n function setVaultBuffer(uint256 _vaultBuffer)\n external\n onlyGovernorOrStrategist\n {\n require(_vaultBuffer <= 1e18, \"Invalid value\");\n vaultBuffer = _vaultBuffer;\n emit VaultBufferUpdated(_vaultBuffer);\n }\n\n /**\n * @notice Sets the minimum amount of OTokens in a mint to trigger an\n * automatic allocation of funds afterwords.\n * @param _threshold OToken amount with 18 fixed decimals.\n */\n function setAutoAllocateThreshold(uint256 _threshold)\n external\n onlyGovernor\n {\n autoAllocateThreshold = _threshold;\n emit AllocateThresholdUpdated(_threshold);\n }\n\n /**\n * @notice Set a minimum amount of OTokens in a mint or redeem that triggers a\n * rebase\n * @param _threshold OToken amount with 18 fixed decimals.\n */\n function setRebaseThreshold(uint256 _threshold) external onlyGovernor {\n rebaseThreshold = _threshold;\n emit RebaseThresholdUpdated(_threshold);\n }\n\n /**\n * @notice Set address of Strategist\n * @param _address Address of Strategist\n */\n function setStrategistAddr(address _address) external onlyGovernor {\n strategistAddr = _address;\n emit StrategistUpdated(_address);\n }\n\n /**\n * @notice Set the default Strategy for asset, i.e. the one which\n * the asset will be automatically allocated to and withdrawn from\n * @param _strategy Address of the Strategy\n */\n function setDefaultStrategy(address _strategy)\n external\n onlyGovernorOrStrategist\n {\n emit DefaultStrategyUpdated(_strategy);\n // If its a zero address being passed for the strategy we are removing\n // the default strategy\n if (_strategy != address(0)) {\n // Make sure the strategy meets some criteria\n require(strategies[_strategy].isSupported, \"Strategy not approved\");\n require(\n IStrategy(_strategy).supportsAsset(asset),\n \"Asset not supported by Strategy\"\n );\n }\n defaultStrategy = _strategy;\n }\n\n /**\n * @notice Changes the async withdrawal claim period for OETH & superOETHb\n * @param _delay Delay period (should be between 10 mins to 7 days).\n * Set to 0 to disable async withdrawals\n */\n function setWithdrawalClaimDelay(uint256 _delay) external onlyGovernor {\n require(\n _delay == 0 || (_delay >= 10 minutes && _delay <= 15 days),\n \"Invalid claim delay period\"\n );\n withdrawalClaimDelay = _delay;\n emit WithdrawalClaimDelayUpdated(_delay);\n }\n\n // slither-disable-start reentrancy-no-eth\n /**\n * @notice Set a yield streaming max rate. This spreads yield over\n * time if it is above the max rate. This is a per rebase APR which\n * due to compounding differs from the yearly APR. Governance should\n * consider this fact when picking a desired APR\n * @param apr in 1e18 notation. 3 * 1e18 = 3% APR\n */\n function setRebaseRateMax(uint256 apr) external onlyGovernorOrStrategist {\n // The old yield will be at the old rate\n _rebase();\n // Change the rate\n uint256 newPerSecond = apr / 100 / 365 days;\n require(newPerSecond <= MAX_REBASE_PER_SECOND, \"Rate too high\");\n rebasePerSecondMax = newPerSecond.toUint64();\n emit RebasePerSecondMaxChanged(newPerSecond);\n }\n\n // slither-disable-end reentrancy-no-eth\n\n // slither-disable-start reentrancy-no-eth\n /**\n * @notice Set the drip duration period\n * @param _dripDuration Time in seconds to target a constant yield rate\n */\n function setDripDuration(uint256 _dripDuration)\n external\n onlyGovernorOrStrategist\n {\n // The old yield will be at the old rate\n _rebase();\n dripDuration = _dripDuration.toUint64();\n emit DripDurationChanged(_dripDuration);\n }\n\n // slither-disable-end reentrancy-no-eth\n\n /***************************************\n Strategy Config\n ****************************************/\n\n /**\n * @notice Add a strategy to the Vault.\n * @param _addr Address of the strategy to add\n */\n function approveStrategy(address _addr) external onlyGovernor {\n require(!strategies[_addr].isSupported, \"Strategy already approved\");\n require(\n IStrategy(_addr).supportsAsset(asset),\n \"Asset not supported by Strategy\"\n );\n strategies[_addr] = Strategy({ isSupported: true, _deprecated: 0 });\n allStrategies.push(_addr);\n emit StrategyApproved(_addr);\n }\n\n /**\n * @notice Remove a strategy from the Vault.\n * @param _addr Address of the strategy to remove\n */\n\n function removeStrategy(address _addr) external onlyGovernor {\n require(strategies[_addr].isSupported, \"Strategy not approved\");\n require(defaultStrategy != _addr, \"Strategy is default for asset\");\n\n // Initialize strategyIndex with out of bounds result so function will\n // revert if no valid index found\n uint256 stratCount = allStrategies.length;\n uint256 strategyIndex = stratCount;\n for (uint256 i = 0; i < stratCount; ++i) {\n if (allStrategies[i] == _addr) {\n strategyIndex = i;\n break;\n }\n }\n\n if (strategyIndex < stratCount) {\n allStrategies[strategyIndex] = allStrategies[stratCount - 1];\n allStrategies.pop();\n\n // Mark the strategy as not supported\n strategies[_addr].isSupported = false;\n isMintWhitelistedStrategy[_addr] = false;\n\n // Withdraw all asset\n IStrategy strategy = IStrategy(_addr);\n strategy.withdrawAll();\n\n // 1e13 for 18 decimals. And 1e1(10) for 6 decimals\n uint256 maxDustBalance = uint256(1e13).scaleBy(assetDecimals, 18);\n\n /*\n * Some strategies are not able to withdraw all of their funds in a synchronous call.\n * Prevent the possible accidental removal of such strategies before their funds are withdrawn.\n */\n require(\n strategy.checkBalance(asset) < maxDustBalance,\n \"Strategy has funds\"\n );\n emit StrategyRemoved(_addr);\n }\n }\n\n /**\n * @notice Adds a strategy to the mint whitelist.\n * Reverts if strategy isn't approved on Vault.\n * @param strategyAddr Strategy address\n */\n function addStrategyToMintWhitelist(address strategyAddr)\n external\n onlyGovernor\n {\n require(strategies[strategyAddr].isSupported, \"Strategy not approved\");\n\n require(\n !isMintWhitelistedStrategy[strategyAddr],\n \"Already whitelisted\"\n );\n\n isMintWhitelistedStrategy[strategyAddr] = true;\n\n emit StrategyAddedToMintWhitelist(strategyAddr);\n }\n\n /**\n * @notice Removes a strategy from the mint whitelist.\n * @param strategyAddr Strategy address\n */\n function removeStrategyFromMintWhitelist(address strategyAddr)\n external\n onlyGovernor\n {\n // Intentionally skipping `strategies.isSupported` check since\n // we may wanna remove an address even after removing the strategy\n\n require(isMintWhitelistedStrategy[strategyAddr], \"Not whitelisted\");\n\n isMintWhitelistedStrategy[strategyAddr] = false;\n\n emit StrategyRemovedFromMintWhitelist(strategyAddr);\n }\n\n /***************************************\n Strategies\n ****************************************/\n\n /**\n * @notice Deposit multiple asset from the vault into the strategy.\n * @param _strategyToAddress Address of the Strategy to deposit asset into.\n * @param _assets Array of asset address that will be deposited into the strategy.\n * @param _amounts Array of amounts of each corresponding asset to deposit.\n */\n function depositToStrategy(\n address _strategyToAddress,\n address[] calldata _assets,\n uint256[] calldata _amounts\n ) external onlyGovernorOrStrategist nonReentrant {\n _depositToStrategy(_strategyToAddress, _assets, _amounts);\n }\n\n function _depositToStrategy(\n address _strategyToAddress,\n address[] calldata _assets,\n uint256[] calldata _amounts\n ) internal virtual {\n require(\n strategies[_strategyToAddress].isSupported,\n \"Invalid to Strategy\"\n );\n require(\n _assets.length == 1 && _amounts.length == 1 && _assets[0] == asset,\n \"Only asset is supported\"\n );\n\n // Check the there is enough asset to transfer once the backing\n // asset reserved for the withdrawal queue is accounted for\n require(\n _amounts[0] <= _assetAvailable(),\n \"Not enough assets available\"\n );\n\n // Send required amount of funds to the strategy\n IERC20(asset).safeTransfer(_strategyToAddress, _amounts[0]);\n\n // Deposit all the funds that have been sent to the strategy\n IStrategy(_strategyToAddress).depositAll();\n }\n\n /**\n * @notice Withdraw multiple asset from the strategy to the vault.\n * @param _strategyFromAddress Address of the Strategy to withdraw asset from.\n * @param _assets Array of asset address that will be withdrawn from the strategy.\n * @param _amounts Array of amounts of each corresponding asset to withdraw.\n */\n function withdrawFromStrategy(\n address _strategyFromAddress,\n address[] calldata _assets,\n uint256[] calldata _amounts\n ) external onlyGovernorOrStrategist nonReentrant {\n _withdrawFromStrategy(\n address(this),\n _strategyFromAddress,\n _assets,\n _amounts\n );\n }\n\n /**\n * @param _recipient can either be a strategy or the Vault\n */\n function _withdrawFromStrategy(\n address _recipient,\n address _strategyFromAddress,\n address[] calldata _assets,\n uint256[] calldata _amounts\n ) internal virtual {\n require(\n strategies[_strategyFromAddress].isSupported,\n \"Invalid from Strategy\"\n );\n require(_assets.length == _amounts.length, \"Parameter length mismatch\");\n\n uint256 assetCount = _assets.length;\n for (uint256 i = 0; i < assetCount; ++i) {\n // Withdraw from Strategy to the recipient\n IStrategy(_strategyFromAddress).withdraw(\n _recipient,\n _assets[i],\n _amounts[i]\n );\n }\n\n _addWithdrawalQueueLiquidity();\n }\n\n /**\n * @notice Sets the maximum allowable difference between\n * total supply and asset' value.\n */\n function setMaxSupplyDiff(uint256 _maxSupplyDiff) external onlyGovernor {\n maxSupplyDiff = _maxSupplyDiff;\n emit MaxSupplyDiffChanged(_maxSupplyDiff);\n }\n\n /**\n * @notice Sets the trusteeAddress that can receive a portion of yield.\n * Setting to the zero address disables this feature.\n */\n function setTrusteeAddress(address _address) external onlyGovernor {\n trusteeAddress = _address;\n emit TrusteeAddressChanged(_address);\n }\n\n /**\n * @notice Sets the TrusteeFeeBps to the percentage of yield that should be\n * received in basis points.\n */\n function setTrusteeFeeBps(uint256 _basis) external onlyGovernor {\n require(_basis <= 5000, \"basis cannot exceed 50%\");\n trusteeFeeBps = _basis;\n emit TrusteeFeeBpsChanged(_basis);\n }\n\n /***************************************\n Pause\n ****************************************/\n\n /**\n * @notice Set the deposit paused flag to true to prevent rebasing.\n */\n function pauseRebase() external onlyGovernorOrStrategist {\n rebasePaused = true;\n emit RebasePaused();\n }\n\n /**\n * @notice Set the deposit paused flag to true to allow rebasing.\n */\n function unpauseRebase() external onlyGovernorOrStrategist {\n rebasePaused = false;\n emit RebaseUnpaused();\n }\n\n /**\n * @notice Set the deposit paused flag to true to prevent capital movement.\n */\n function pauseCapital() external onlyGovernorOrStrategist {\n capitalPaused = true;\n emit CapitalPaused();\n }\n\n /**\n * @notice Set the deposit paused flag to false to enable capital movement.\n */\n function unpauseCapital() external onlyGovernorOrStrategist {\n capitalPaused = false;\n emit CapitalUnpaused();\n }\n\n /***************************************\n Utils\n ****************************************/\n\n /**\n * @notice Transfer token to governor. Intended for recovering tokens stuck in\n * contract, i.e. mistaken sends.\n * @param _asset Address for the asset\n * @param _amount Amount of the asset to transfer\n */\n function transferToken(address _asset, uint256 _amount)\n external\n onlyGovernor\n {\n require(asset != _asset, \"Only unsupported asset\");\n IERC20(_asset).safeTransfer(governor(), _amount);\n }\n\n /***************************************\n Strategies Admin\n ****************************************/\n\n /**\n * @notice Withdraws all asset from the strategy and sends asset to the Vault.\n * @param _strategyAddr Strategy address.\n */\n function withdrawAllFromStrategy(address _strategyAddr)\n external\n onlyGovernorOrStrategist\n {\n _withdrawAllFromStrategy(_strategyAddr);\n }\n\n function _withdrawAllFromStrategy(address _strategyAddr) internal virtual {\n require(\n strategies[_strategyAddr].isSupported,\n \"Strategy is not supported\"\n );\n IStrategy strategy = IStrategy(_strategyAddr);\n strategy.withdrawAll();\n _addWithdrawalQueueLiquidity();\n }\n\n /**\n * @notice Withdraws all asset from all the strategies and sends asset to the Vault.\n */\n function withdrawAllFromStrategies() external onlyGovernorOrStrategist {\n _withdrawAllFromStrategies();\n }\n\n function _withdrawAllFromStrategies() internal virtual {\n uint256 stratCount = allStrategies.length;\n for (uint256 i = 0; i < stratCount; ++i) {\n IStrategy(allStrategies[i]).withdrawAll();\n }\n _addWithdrawalQueueLiquidity();\n }\n}\n" + }, + "contracts/vault/VaultCore.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title OToken VaultCore contract\n * @notice The Vault contract stores asset. On a deposit, OTokens will be minted\n and sent to the depositor. On a withdrawal, OTokens will be burned and\n asset will be sent to the withdrawer. The Vault accepts deposits of\n interest from yield bearing strategies which will modify the supply\n of OTokens.\n * @author Origin Protocol Inc\n */\n\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport { SafeCast } from \"@openzeppelin/contracts/utils/math/SafeCast.sol\";\n\nimport { StableMath } from \"../utils/StableMath.sol\";\n\nimport \"./VaultInitializer.sol\";\n\nabstract contract VaultCore is VaultInitializer {\n using SafeERC20 for IERC20;\n using StableMath for uint256;\n\n /**\n * @dev Verifies that the rebasing is not paused.\n */\n modifier whenNotRebasePaused() {\n require(!rebasePaused, \"Rebasing paused\");\n _;\n }\n\n /**\n * @dev Verifies that the deposits are not paused.\n */\n modifier whenNotCapitalPaused() {\n require(!capitalPaused, \"Capital paused\");\n _;\n }\n\n constructor(address _asset) VaultInitializer(_asset) {}\n\n ////////////////////////////////////////////////////\n /// MINT / BURN ///\n ////////////////////////////////////////////////////\n /**\n * @notice Deposit a supported asset and mint OTokens.\n * @dev Deprecated: use `mint(uint256 _amount)` instead.\n * @dev Deprecated: param _asset Address of the asset being deposited\n * @param _amount Amount of the asset being deposited\n * @dev Deprecated: param _minimumOusdAmount Minimum OTokens to mint\n */\n function mint(\n address,\n uint256 _amount,\n uint256\n ) external whenNotCapitalPaused nonReentrant {\n _mint(_amount);\n }\n\n /**\n * @notice Deposit a supported asset and mint OTokens.\n * @param _amount Amount of the asset being deposited\n */\n function mint(uint256 _amount) external whenNotCapitalPaused nonReentrant {\n _mint(_amount);\n }\n\n // slither-disable-start reentrancy-no-eth\n /**\n * @dev Deposit a supported asset and mint OTokens.\n * @param _amount Amount of the asset being deposited\n */\n function _mint(uint256 _amount) internal virtual {\n require(_amount > 0, \"Amount must be greater than 0\");\n\n // Scale amount to 18 decimals\n uint256 scaledAmount = _amount.scaleBy(18, assetDecimals);\n\n emit Mint(msg.sender, scaledAmount);\n\n // Rebase must happen before any transfers occur.\n if (!rebasePaused && scaledAmount >= rebaseThreshold) {\n _rebase();\n }\n\n // Mint oTokens\n oToken.mint(msg.sender, scaledAmount);\n\n IERC20(asset).safeTransferFrom(msg.sender, address(this), _amount);\n\n // Give priority to the withdrawal queue for the new asset liquidity\n _addWithdrawalQueueLiquidity();\n\n // Auto-allocate if necessary\n if (scaledAmount >= autoAllocateThreshold) {\n _allocate();\n }\n }\n\n // slither-disable-end reentrancy-no-eth\n\n /**\n * @notice Mint OTokens for an allowed Strategy\n * @param _amount Amount of OToken to mint\n *\n * Notice: can't use `nonReentrant` modifier since the `mint` function can\n * call `allocate`, and that can trigger an AMO strategy to call this function\n * while the execution of the `mint` has not yet completed -> causing a `nonReentrant` collision.\n *\n * Also important to understand is that this is a limitation imposed by the test suite.\n * Production / mainnet contracts should never be configured in a way where mint/redeem functions\n * that are moving funds between the Vault and end user wallets can influence strategies\n * utilizing this function.\n */\n function mintForStrategy(uint256 _amount)\n external\n virtual\n whenNotCapitalPaused\n {\n require(\n strategies[msg.sender].isSupported == true,\n \"Unsupported strategy\"\n );\n require(\n isMintWhitelistedStrategy[msg.sender] == true,\n \"Not whitelisted strategy\"\n );\n\n emit Mint(msg.sender, _amount);\n // Mint matching amount of OTokens\n oToken.mint(msg.sender, _amount);\n }\n\n /**\n * @notice Burn OTokens for an allowed Strategy\n * @param _amount Amount of OToken to burn\n *\n * @dev Notice: can't use `nonReentrant` modifier since the `redeem` function could\n * require withdrawal on an AMO strategy and that one can call `burnForStrategy`\n * while the execution of the `redeem` has not yet completed -> causing a `nonReentrant` collision.\n *\n * Also important to understand is that this is a limitation imposed by the test suite.\n * Production / mainnet contracts should never be configured in a way where mint/redeem functions\n * that are moving funds between the Vault and end user wallets can influence strategies\n * utilizing this function.\n */\n function burnForStrategy(uint256 _amount)\n external\n virtual\n whenNotCapitalPaused\n {\n require(\n strategies[msg.sender].isSupported == true,\n \"Unsupported strategy\"\n );\n require(\n isMintWhitelistedStrategy[msg.sender] == true,\n \"Not whitelisted strategy\"\n );\n\n emit Redeem(msg.sender, _amount);\n\n // Burn OTokens\n oToken.burn(msg.sender, _amount);\n }\n\n ////////////////////////////////////////////////////\n /// ASYNC WITHDRAWALS ///\n ////////////////////////////////////////////////////\n /**\n * @notice Request an asynchronous withdrawal of asset in exchange for OToken.\n * The OToken is burned on request and the asset is transferred to the withdrawer on claim.\n * This request can be claimed once the withdrawal queue's `claimable` amount\n * is greater than or equal this request's `queued` amount.\n * There is a minimum of 10 minutes before a request can be claimed. After that, the request just needs\n * enough asset liquidity in the Vault to satisfy all the outstanding requests to that point in the queue.\n * OToken is converted to asset at 1:1.\n * @param _amount Amount of OToken to burn.\n * @return requestId Unique ID for the withdrawal request\n * @return queued Cumulative total of all asset queued including already claimed requests.\n */\n function requestWithdrawal(uint256 _amount)\n external\n virtual\n whenNotCapitalPaused\n nonReentrant\n returns (uint256 requestId, uint256 queued)\n {\n require(_amount > 0, \"Amount must be greater than 0\");\n require(withdrawalClaimDelay > 0, \"Async withdrawals not enabled\");\n\n // The check that the requester has enough OToken is done in to later burn call\n\n requestId = withdrawalQueueMetadata.nextWithdrawalIndex;\n queued =\n withdrawalQueueMetadata.queued +\n _amount.scaleBy(assetDecimals, 18);\n\n // Store the next withdrawal request\n withdrawalQueueMetadata.nextWithdrawalIndex = SafeCast.toUint128(\n requestId + 1\n );\n // Store the updated queued amount which reserves asset in the withdrawal queue\n // and reduces the vault's total asset\n withdrawalQueueMetadata.queued = SafeCast.toUint128(queued);\n // Store the user's withdrawal request\n // `queued` is in asset decimals, while `amount` is in OToken decimals (18)\n withdrawalRequests[requestId] = WithdrawalRequest({\n withdrawer: msg.sender,\n claimed: false,\n timestamp: uint40(block.timestamp),\n amount: SafeCast.toUint128(_amount),\n queued: SafeCast.toUint128(queued)\n });\n\n // Burn the user's OToken\n oToken.burn(msg.sender, _amount);\n\n // Prevent withdrawal if the vault is solvent by more than the allowed percentage\n _postRedeem(_amount);\n\n emit WithdrawalRequested(msg.sender, requestId, _amount, queued);\n }\n\n // slither-disable-start reentrancy-no-eth\n /**\n * @notice Claim a previously requested withdrawal once it is claimable.\n * This request can be claimed once the withdrawal queue's `claimable` amount\n * is greater than or equal this request's `queued` amount and 10 minutes has passed.\n * If the requests is not claimable, the transaction will revert with `Queue pending liquidity`.\n * If the request is not older than 10 minutes, the transaction will revert with `Claim delay not met`.\n * OToken is converted to asset at 1:1.\n * @param _requestId Unique ID for the withdrawal request\n * @return amount Amount of asset transferred to the withdrawer\n */\n function claimWithdrawal(uint256 _requestId)\n external\n virtual\n whenNotCapitalPaused\n nonReentrant\n returns (uint256 amount)\n {\n // Try and get more liquidity if there is not enough available\n if (\n withdrawalRequests[_requestId].queued >\n withdrawalQueueMetadata.claimable\n ) {\n // Add any asset to the withdrawal queue\n // this needs to remain here as:\n // - Vault can be funded and `addWithdrawalQueueLiquidity` is not externally called\n // - funds can be withdrawn from a strategy\n //\n // Those funds need to be added to withdrawal queue liquidity\n _addWithdrawalQueueLiquidity();\n }\n\n // Scale amount to asset decimals\n amount = _claimWithdrawal(_requestId);\n\n // transfer asset from the vault to the withdrawer\n IERC20(asset).safeTransfer(msg.sender, amount);\n\n // Prevent insolvency\n _postRedeem(amount.scaleBy(18, assetDecimals));\n }\n\n // slither-disable-end reentrancy-no-eth\n /**\n * @notice Claim a previously requested withdrawals once they are claimable.\n * This requests can be claimed once the withdrawal queue's `claimable` amount\n * is greater than or equal each request's `queued` amount and 10 minutes has passed.\n * If one of the requests is not claimable, the whole transaction will revert with `Queue pending liquidity`.\n * If one of the requests is not older than 10 minutes,\n * the whole transaction will revert with `Claim delay not met`.\n * @param _requestIds Unique ID of each withdrawal request\n * @return amounts Amount of asset received for each request\n * @return totalAmount Total amount of asset transferred to the withdrawer\n */\n function claimWithdrawals(uint256[] calldata _requestIds)\n external\n virtual\n whenNotCapitalPaused\n nonReentrant\n returns (uint256[] memory amounts, uint256 totalAmount)\n {\n // Add any asset to the withdrawal queue\n // this needs to remain here as:\n // - Vault can be funded and `addWithdrawalQueueLiquidity` is not externally called\n // - funds can be withdrawn from a strategy\n //\n // Those funds need to be added to withdrawal queue liquidity\n _addWithdrawalQueueLiquidity();\n\n amounts = new uint256[](_requestIds.length);\n for (uint256 i; i < _requestIds.length; ++i) {\n // Scale all amounts to asset decimals, thus totalAmount is also in asset decimals\n amounts[i] = _claimWithdrawal(_requestIds[i]);\n totalAmount += amounts[i];\n }\n\n // transfer all the claimed asset from the vault to the withdrawer\n IERC20(asset).safeTransfer(msg.sender, totalAmount);\n\n // Prevent insolvency\n _postRedeem(totalAmount.scaleBy(18, assetDecimals));\n\n return (amounts, totalAmount);\n }\n\n function _claimWithdrawal(uint256 requestId)\n internal\n returns (uint256 amount)\n {\n require(withdrawalClaimDelay > 0, \"Async withdrawals not enabled\");\n\n // Load the structs from storage into memory\n WithdrawalRequest memory request = withdrawalRequests[requestId];\n WithdrawalQueueMetadata memory queue = withdrawalQueueMetadata;\n\n require(\n request.timestamp + withdrawalClaimDelay <= block.timestamp,\n \"Claim delay not met\"\n );\n // If there isn't enough reserved liquidity in the queue to claim\n require(request.queued <= queue.claimable, \"Queue pending liquidity\");\n require(request.withdrawer == msg.sender, \"Not requester\");\n require(request.claimed == false, \"Already claimed\");\n\n // Store the request as claimed\n withdrawalRequests[requestId].claimed = true;\n // Store the updated claimed amount\n withdrawalQueueMetadata.claimed =\n queue.claimed +\n SafeCast.toUint128(\n StableMath.scaleBy(request.amount, assetDecimals, 18)\n );\n\n emit WithdrawalClaimed(msg.sender, requestId, request.amount);\n\n return StableMath.scaleBy(request.amount, assetDecimals, 18);\n }\n\n function _postRedeem(uint256 _amount) internal {\n // Until we can prove that we won't affect the prices of our asset\n // by withdrawing them, this should be here.\n // It's possible that a strategy was off on its asset total, perhaps\n // a reward token sold for more or for less than anticipated.\n uint256 totalUnits = 0;\n if (_amount >= rebaseThreshold && !rebasePaused) {\n totalUnits = _rebase();\n } else {\n totalUnits = _totalValue();\n }\n\n // Check that the OTokens are backed by enough asset\n if (maxSupplyDiff > 0) {\n // If there are more outstanding withdrawal requests than asset in the vault and strategies\n // then the available asset will be negative and totalUnits will be rounded up to zero.\n // As we don't know the exact shortfall amount, we will reject all redeem and withdrawals\n require(totalUnits > 0, \"Too many outstanding requests\");\n\n // Allow a max difference of maxSupplyDiff% between\n // asset value and OUSD total supply\n uint256 diff = oToken.totalSupply().divPrecisely(totalUnits);\n require(\n (diff > 1e18 ? diff - 1e18 : 1e18 - diff) <= maxSupplyDiff,\n \"Backing supply liquidity error\"\n );\n }\n }\n\n /**\n * @notice Allocate unallocated funds on Vault to strategies.\n */\n function allocate() external virtual whenNotCapitalPaused nonReentrant {\n // Add any unallocated asset to the withdrawal queue first\n _addWithdrawalQueueLiquidity();\n\n _allocate();\n }\n\n /**\n * @dev Allocate asset (eg. WETH or USDC) to the default asset strategy\n * if there is excess to the Vault buffer.\n * This is called from either `mint` or `allocate` and assumes `_addWithdrawalQueueLiquidity`\n * has been called before this function.\n */\n function _allocate() internal virtual {\n // No need to do anything if no default strategy for asset\n address depositStrategyAddr = defaultStrategy;\n if (depositStrategyAddr == address(0)) return;\n\n uint256 assetAvailableInVault = _assetAvailable();\n // No need to do anything if there isn't any asset in the vault to allocate\n if (assetAvailableInVault == 0) return;\n\n // Calculate the target buffer for the vault using the total supply\n uint256 totalSupply = oToken.totalSupply();\n // Scaled to asset decimals\n uint256 targetBuffer = totalSupply.mulTruncate(vaultBuffer).scaleBy(\n assetDecimals,\n 18\n );\n\n // If available asset in the Vault is below or equal the target buffer then there's nothing to allocate\n if (assetAvailableInVault <= targetBuffer) return;\n\n // The amount of asset to allocate to the default strategy\n uint256 allocateAmount = assetAvailableInVault - targetBuffer;\n\n IStrategy strategy = IStrategy(depositStrategyAddr);\n // Transfer asset to the strategy and call the strategy's deposit function\n IERC20(asset).safeTransfer(address(strategy), allocateAmount);\n strategy.deposit(asset, allocateAmount);\n\n emit AssetAllocated(asset, depositStrategyAddr, allocateAmount);\n }\n\n /**\n * @notice Calculate the total value of asset held by the Vault and all\n * strategies and update the supply of OTokens.\n */\n function rebase() external virtual nonReentrant {\n _rebase();\n }\n\n /**\n * @dev Calculate the total value of asset held by the Vault and all\n * strategies and update the supply of OTokens, optionally sending a\n * portion of the yield to the trustee.\n * @return totalUnits Total balance of Vault in units\n */\n function _rebase() internal whenNotRebasePaused returns (uint256) {\n uint256 supply = oToken.totalSupply();\n uint256 vaultValue = _totalValue();\n // If no supply yet, do not rebase\n if (supply == 0) {\n return vaultValue;\n }\n\n // Calculate yield and new supply\n (uint256 yield, uint256 targetRate) = _nextYield(supply, vaultValue);\n uint256 newSupply = supply + yield;\n // Only rebase upwards and if we have enough backing funds\n if (newSupply <= supply || newSupply > vaultValue) {\n return vaultValue;\n }\n\n rebasePerSecondTarget = uint64(_min(targetRate, type(uint64).max));\n lastRebase = uint64(block.timestamp); // Intentional cast\n\n // Fee collection on yield\n address _trusteeAddress = trusteeAddress; // gas savings\n uint256 fee = 0;\n if (_trusteeAddress != address(0)) {\n fee = (yield * trusteeFeeBps) / 1e4;\n if (fee > 0) {\n require(fee < yield, \"Fee must not be greater than yield\");\n oToken.mint(_trusteeAddress, fee);\n }\n }\n emit YieldDistribution(_trusteeAddress, yield, fee);\n\n // Only ratchet OToken supply upwards\n // Final check uses latest totalSupply\n if (newSupply > oToken.totalSupply()) {\n oToken.changeSupply(newSupply);\n }\n return vaultValue;\n }\n\n /**\n * @notice Calculates the amount that would rebase at next rebase.\n * This is before any fees.\n * @return yield amount of expected yield\n */\n function previewYield() external view returns (uint256 yield) {\n (yield, ) = _nextYield(oToken.totalSupply(), _totalValue());\n return yield;\n }\n\n /**\n * @dev Calculates the amount that would rebase at next rebase.\n * See this Readme for detailed explanation:\n * contracts/contracts/vault/README - Yield Limits.md\n */\n function _nextYield(uint256 supply, uint256 vaultValue)\n internal\n view\n virtual\n returns (uint256 yield, uint256 targetRate)\n {\n uint256 nonRebasing = oToken.nonRebasingSupply();\n uint256 rebasing = supply - nonRebasing;\n uint256 elapsed = block.timestamp - lastRebase;\n targetRate = rebasePerSecondTarget;\n\n if (\n elapsed == 0 || // Yield only once per block.\n rebasing == 0 || // No yield if there are no rebasing tokens to give it to.\n supply > vaultValue || // No yield if we do not have yield to give.\n block.timestamp >= type(uint64).max // No yield if we are too far in the future to calculate it correctly.\n ) {\n return (0, targetRate);\n }\n\n // Start with the full difference available\n yield = vaultValue - supply;\n\n // Cap via optional automatic duration smoothing\n uint256 _dripDuration = dripDuration;\n if (_dripDuration > 1) {\n // If we are able to sustain an increased drip rate for\n // double the duration, then increase the target drip rate\n targetRate = _max(targetRate, yield / (_dripDuration * 2));\n // If we cannot sustain the target rate any more,\n // then rebase what we can, and reduce the target\n targetRate = _min(targetRate, yield / _dripDuration);\n // drip at the new target rate\n yield = _min(yield, targetRate * elapsed);\n }\n\n // Cap per second. elapsed is not 1e18 denominated\n yield = _min(yield, (rebasing * elapsed * rebasePerSecondMax) / 1e18);\n\n // Cap at a hard max per rebase, to avoid long durations resulting in huge rebases\n yield = _min(yield, (rebasing * MAX_REBASE) / 1e18);\n\n return (yield, targetRate);\n }\n\n /**\n * @notice Determine the total value of asset held by the vault and its\n * strategies.\n * @return value Total value in USD/ETH (1e18)\n */\n function totalValue() external view virtual returns (uint256 value) {\n value = _totalValue();\n }\n\n /**\n * @dev Internal Calculate the total value of the asset held by the\n * vault and its strategies.\n * @dev The total value of all WETH held by the vault and all its strategies\n * less any WETH that is reserved for the withdrawal queue.\n * If there is not enough WETH in the vault and all strategies to cover\n * all outstanding withdrawal requests then return a total value of 0.\n * @return value Total value in USD/ETH (1e18)\n */\n function _totalValue() internal view virtual returns (uint256 value) {\n // As asset is the only asset, just return the asset balance\n value = _checkBalance(asset).scaleBy(18, assetDecimals);\n }\n\n /**\n * @notice Get the balance of an asset held in Vault and all strategies.\n * @param _asset Address of asset\n * @return uint256 Balance of asset in decimals of asset\n */\n function checkBalance(address _asset) external view returns (uint256) {\n return _checkBalance(_asset);\n }\n\n /**\n * @notice Get the balance of an asset held in Vault and all strategies.\n * @dev Get the balance of an asset held in Vault and all strategies\n * less any asset that is reserved for the withdrawal queue.\n * BaseAsset is the only asset that can return a non-zero balance.\n * All other asset will return 0 even if there is some dust amounts left in the Vault.\n * For example, there is 1 wei left of stETH (or USDC) in the OETH (or OUSD) Vault but\n * will return 0 in this function.\n *\n * If there is not enough asset in the vault and all strategies to cover all outstanding\n * withdrawal requests then return a asset balance of 0\n * @param _asset Address of asset\n * @return balance Balance of asset in decimals of asset\n */\n function _checkBalance(address _asset)\n internal\n view\n virtual\n returns (uint256 balance)\n {\n if (_asset != asset) return 0;\n\n // Get the asset in the vault and the strategies\n IERC20 asset = IERC20(_asset);\n balance = asset.balanceOf(address(this));\n uint256 stratCount = allStrategies.length;\n for (uint256 i = 0; i < stratCount; ++i) {\n IStrategy strategy = IStrategy(allStrategies[i]);\n if (strategy.supportsAsset(_asset)) {\n balance = balance + strategy.checkBalance(_asset);\n }\n }\n\n WithdrawalQueueMetadata memory queue = withdrawalQueueMetadata;\n\n // If the vault becomes insolvent enough that the total value in the vault and all strategies\n // is less than the outstanding withdrawals.\n // For example, there was a mass slashing event and most users request a withdrawal.\n if (balance + queue.claimed < queue.queued) {\n return 0;\n }\n\n // Need to remove asset that is reserved for the withdrawal queue\n return balance + queue.claimed - queue.queued;\n }\n\n /**\n * @notice Adds WETH to the withdrawal queue if there is a funding shortfall.\n * @dev is called from the Native Staking strategy when validator withdrawals are processed.\n * It also called before any WETH is allocated to a strategy.\n */\n function addWithdrawalQueueLiquidity() external {\n _addWithdrawalQueueLiquidity();\n }\n\n /**\n * @dev Adds asset (eg. WETH or USDC) to the withdrawal queue if there is a funding shortfall.\n * This assumes 1 asset equal 1 corresponding OToken.\n */\n function _addWithdrawalQueueLiquidity()\n internal\n returns (uint256 addedClaimable)\n {\n WithdrawalQueueMetadata memory queue = withdrawalQueueMetadata;\n\n // Check if the claimable asset is less than the queued amount\n uint256 queueShortfall = queue.queued - queue.claimable;\n\n // No need to do anything is the withdrawal queue is full funded\n if (queueShortfall == 0) {\n return 0;\n }\n\n uint256 assetBalance = IERC20(asset).balanceOf(address(this));\n\n // Of the claimable withdrawal requests, how much is unclaimed?\n // That is, the amount of asset that is currently allocated for the withdrawal queue\n uint256 allocatedBaseAsset = queue.claimable - queue.claimed;\n\n // If there is no unallocated asset then there is nothing to add to the queue\n if (assetBalance <= allocatedBaseAsset) {\n return 0;\n }\n\n uint256 unallocatedBaseAsset = assetBalance - allocatedBaseAsset;\n // the new claimable amount is the smaller of the queue shortfall or unallocated asset\n addedClaimable = queueShortfall < unallocatedBaseAsset\n ? queueShortfall\n : unallocatedBaseAsset;\n uint256 newClaimable = queue.claimable + addedClaimable;\n\n // Store the new claimable amount back to storage\n withdrawalQueueMetadata.claimable = SafeCast.toUint128(newClaimable);\n\n // emit a WithdrawalClaimable event\n emit WithdrawalClaimable(newClaimable, addedClaimable);\n }\n\n /**\n * @dev Calculate how much asset (eg. WETH or USDC) in the vault is not reserved for the withdrawal queue.\n * That is, it is available to be redeemed or deposited into a strategy.\n */\n function _assetAvailable() internal view returns (uint256 assetAvailable) {\n WithdrawalQueueMetadata memory queue = withdrawalQueueMetadata;\n\n // The amount of asset that is still to be claimed in the withdrawal queue\n uint256 outstandingWithdrawals = queue.queued - queue.claimed;\n\n // The amount of sitting in asset in the vault\n uint256 assetBalance = IERC20(asset).balanceOf(address(this));\n // If there is not enough asset in the vault to cover the outstanding withdrawals\n if (assetBalance <= outstandingWithdrawals) return 0;\n\n return assetBalance - outstandingWithdrawals;\n }\n\n /***************************************\n Utils\n ****************************************/\n\n /**\n * @notice Return the number of asset supported by the Vault.\n */\n function getAssetCount() public view returns (uint256) {\n return 1;\n }\n\n /**\n * @notice Return all vault asset addresses in order\n */\n function getAllAssets() external view returns (address[] memory) {\n address[] memory a = new address[](1);\n a[0] = asset;\n return a;\n }\n\n /**\n * @notice Return the number of strategies active on the Vault.\n */\n function getStrategyCount() external view returns (uint256) {\n return allStrategies.length;\n }\n\n /**\n * @notice Return the array of all strategies\n */\n function getAllStrategies() external view returns (address[] memory) {\n return allStrategies;\n }\n\n /**\n * @notice Returns whether the vault supports the asset\n * @param _asset address of the asset\n * @return true if supported\n */\n function isSupportedAsset(address _asset) external view returns (bool) {\n return asset == _asset;\n }\n\n function _min(uint256 a, uint256 b) internal pure returns (uint256) {\n return a < b ? a : b;\n }\n\n function _max(uint256 a, uint256 b) internal pure returns (uint256) {\n return a > b ? a : b;\n }\n}\n" + }, + "contracts/vault/VaultInitializer.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title OToken VaultInitializer contract\n * @notice The Vault contract initializes the vault.\n * @author Origin Protocol Inc\n */\n\nimport \"./VaultStorage.sol\";\n\nabstract contract VaultInitializer is VaultStorage {\n constructor(address _asset) VaultStorage(_asset) {}\n\n function initialize(address _oToken) external onlyGovernor initializer {\n require(_oToken != address(0), \"oToken address is zero\");\n\n oToken = OUSD(_oToken);\n\n rebasePaused = false;\n capitalPaused = true;\n\n // Initial Vault buffer of 0%\n vaultBuffer = 0;\n // Initial allocate threshold of 25,000 OUSD\n autoAllocateThreshold = 25000e18;\n // Threshold for rebasing\n rebaseThreshold = 1000e18;\n // Initialize all strategies\n allStrategies = new address[](0);\n // Start with drip duration: 7 days\n dripDuration = 604800;\n }\n}\n" + }, + "contracts/vault/VaultStorage.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title OToken VaultStorage contract\n * @notice The VaultStorage contract defines the storage for the Vault contracts\n * @author Origin Protocol Inc\n */\n\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport { Address } from \"@openzeppelin/contracts/utils/Address.sol\";\n\nimport { IStrategy } from \"../interfaces/IStrategy.sol\";\nimport { IERC20Metadata } from \"@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol\";\nimport { Governable } from \"../governance/Governable.sol\";\nimport { OUSD } from \"../token/OUSD.sol\";\nimport { Initializable } from \"../utils/Initializable.sol\";\nimport \"../utils/Helpers.sol\";\n\nabstract contract VaultStorage is Initializable, Governable {\n using SafeERC20 for IERC20;\n\n event AssetAllocated(address _asset, address _strategy, uint256 _amount);\n event StrategyApproved(address _addr);\n event StrategyRemoved(address _addr);\n event Mint(address _addr, uint256 _value);\n event Redeem(address _addr, uint256 _value);\n event CapitalPaused();\n event CapitalUnpaused();\n event DefaultStrategyUpdated(address _strategy);\n event RebasePaused();\n event RebaseUnpaused();\n event VaultBufferUpdated(uint256 _vaultBuffer);\n event AllocateThresholdUpdated(uint256 _threshold);\n event RebaseThresholdUpdated(uint256 _threshold);\n event StrategistUpdated(address _address);\n event MaxSupplyDiffChanged(uint256 maxSupplyDiff);\n event YieldDistribution(address _to, uint256 _yield, uint256 _fee);\n event TrusteeFeeBpsChanged(uint256 _basis);\n event TrusteeAddressChanged(address _address);\n event StrategyAddedToMintWhitelist(address indexed strategy);\n event StrategyRemovedFromMintWhitelist(address indexed strategy);\n event RebasePerSecondMaxChanged(uint256 rebaseRatePerSecond);\n event DripDurationChanged(uint256 dripDuration);\n event WithdrawalRequested(\n address indexed _withdrawer,\n uint256 indexed _requestId,\n uint256 _amount,\n uint256 _queued\n );\n event WithdrawalClaimed(\n address indexed _withdrawer,\n uint256 indexed _requestId,\n uint256 _amount\n );\n event WithdrawalClaimable(uint256 _claimable, uint256 _newClaimable);\n event WithdrawalClaimDelayUpdated(uint256 _newDelay);\n\n // Since we are proxy, all state should be uninitalized.\n // Since this storage contract does not have logic directly on it\n // we should not be checking for to see if these variables can be constant.\n // slither-disable-start uninitialized-state\n // slither-disable-start constable-states\n\n /// @dev mapping of supported vault assets to their configuration\n uint256 private _deprecated_assets;\n /// @dev list of all assets supported by the vault.\n address[] private _deprecated_allAssets;\n\n // Strategies approved for use by the Vault\n struct Strategy {\n bool isSupported;\n uint256 _deprecated; // Deprecated storage slot\n }\n /// @dev mapping of strategy contracts to their configuration\n mapping(address => Strategy) public strategies;\n /// @dev list of all vault strategies\n address[] internal allStrategies;\n\n /// @notice Address of the Oracle price provider contract\n address private _deprecated_priceProvider;\n /// @notice pause rebasing if true\n bool public rebasePaused;\n /// @notice pause operations that change the OToken supply.\n /// eg mint, redeem, allocate, mint/burn for strategy\n bool public capitalPaused;\n /// @notice Redemption fee in basis points. eg 50 = 0.5%\n uint256 private _deprecated_redeemFeeBps;\n /// @notice Percentage of assets to keep in Vault to handle (most) withdrawals. 100% = 1e18.\n uint256 public vaultBuffer;\n /// @notice OToken mints over this amount automatically allocate funds. 18 decimals.\n uint256 public autoAllocateThreshold;\n /// @notice OToken mints over this amount automatically rebase. 18 decimals.\n uint256 public rebaseThreshold;\n\n /// @dev Address of the OToken token. eg OUSD or OETH.\n OUSD public oToken;\n\n /// @dev Address of the contract responsible for post rebase syncs with AMMs\n address private _deprecated_rebaseHooksAddr = address(0);\n\n /// @dev Deprecated: Address of Uniswap\n address private _deprecated_uniswapAddr = address(0);\n\n /// @notice Address of the Strategist\n address public strategistAddr = address(0);\n\n /// @notice Mapping of asset address to the Strategy that they should automatically\n // be allocated to\n uint256 private _deprecated_assetDefaultStrategies;\n\n /// @notice Max difference between total supply and total value of assets. 18 decimals.\n uint256 public maxSupplyDiff;\n\n /// @notice Trustee contract that can collect a percentage of yield\n address public trusteeAddress;\n\n /// @notice Amount of yield collected in basis points. eg 2000 = 20%\n uint256 public trusteeFeeBps;\n\n /// @dev Deprecated: Tokens that should be swapped for stablecoins\n address[] private _deprecated_swapTokens;\n\n /// @notice Metapool strategy that is allowed to mint/burn OTokens without changing collateral\n\n address private _deprecated_ousdMetaStrategy;\n\n /// @notice How much OTokens are currently minted by the strategy\n int256 private _deprecated_netOusdMintedForStrategy;\n\n /// @notice How much net total OTokens are allowed to be minted by all strategies\n uint256 private _deprecated_netOusdMintForStrategyThreshold;\n\n uint256 private _deprecated_swapConfig;\n\n // List of strategies that can mint oTokens directly\n // Used in OETHBaseVaultCore\n mapping(address => bool) public isMintWhitelistedStrategy;\n\n /// @notice Address of the Dripper contract that streams harvested rewards to the Vault\n /// @dev The vault is proxied so needs to be set with setDripper against the proxy contract.\n address private _deprecated_dripper;\n\n /// Withdrawal Queue Storage /////\n\n struct WithdrawalQueueMetadata {\n // cumulative total of all withdrawal requests included the ones that have already been claimed\n uint128 queued;\n // cumulative total of all the requests that can be claimed including the ones that have already been claimed\n uint128 claimable;\n // total of all the requests that have been claimed\n uint128 claimed;\n // index of the next withdrawal request starting at 0\n uint128 nextWithdrawalIndex;\n }\n\n /// @notice Global metadata for the withdrawal queue including:\n /// queued - cumulative total of all withdrawal requests included the ones that have already been claimed\n /// claimable - cumulative total of all the requests that can be claimed including the ones already claimed\n /// claimed - total of all the requests that have been claimed\n /// nextWithdrawalIndex - index of the next withdrawal request starting at 0\n WithdrawalQueueMetadata public withdrawalQueueMetadata;\n\n struct WithdrawalRequest {\n address withdrawer;\n bool claimed;\n uint40 timestamp; // timestamp of the withdrawal request\n // Amount of oTokens to redeem. eg OETH\n uint128 amount;\n // cumulative total of all withdrawal requests including this one.\n // this request can be claimed when this queued amount is less than or equal to the queue's claimable amount.\n uint128 queued;\n }\n\n /// @notice Mapping of withdrawal request indices to the user withdrawal request data\n mapping(uint256 => WithdrawalRequest) public withdrawalRequests;\n\n /// @notice Sets a minimum delay that is required to elapse between\n /// requesting async withdrawals and claiming the request.\n /// When set to 0 async withdrawals are disabled.\n uint256 public withdrawalClaimDelay;\n\n /// @notice Time in seconds that the vault last rebased yield.\n uint64 public lastRebase;\n\n /// @notice Automatic rebase yield calculations. In seconds. Set to 0 or 1 to disable.\n uint64 public dripDuration;\n\n /// @notice max rebase percentage per second\n /// Can be used to set maximum yield of the protocol,\n /// spreading out yield over time\n uint64 public rebasePerSecondMax;\n\n /// @notice target rebase rate limit, based on past rates and funds available.\n uint64 public rebasePerSecondTarget;\n\n uint256 internal constant MAX_REBASE = 0.02 ether;\n uint256 internal constant MAX_REBASE_PER_SECOND =\n uint256(0.05 ether) / 1 days;\n\n /// @notice Default strategy for asset\n address public defaultStrategy;\n\n // For future use\n uint256[42] private __gap;\n\n /// @notice Index of WETH asset in allAssets array\n /// Legacy OETHVaultCore code, relocated here for vault consistency.\n uint256 private _deprecated_wethAssetIndex;\n\n /// @dev Address of the asset (eg. WETH or USDC)\n address public immutable asset;\n uint8 internal immutable assetDecimals;\n\n // slither-disable-end constable-states\n // slither-disable-end uninitialized-state\n\n constructor(address _asset) {\n uint8 _decimals = IERC20Metadata(_asset).decimals();\n require(_decimals <= 18, \"invalid asset decimals\");\n asset = _asset;\n assetDecimals = _decimals;\n }\n\n /// @notice Deprecated: use `oToken()` instead.\n function oUSD() external view returns (OUSD) {\n return oToken;\n }\n}\n" + }, + "contracts/zapper/AbstractOTokenZapper.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport { IVault } from \"../interfaces/IVault.sol\";\nimport { IWETH9 } from \"../interfaces/IWETH9.sol\";\nimport { IERC4626 } from \"../../lib/openzeppelin/interfaces/IERC4626.sol\";\n\nabstract contract AbstractOTokenZapper {\n IERC20 public immutable oToken;\n IERC4626 public immutable wOToken;\n IVault public immutable vault;\n\n IWETH9 public immutable weth;\n\n address private constant ETH_MARKER =\n 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;\n\n event Zap(address indexed minter, address indexed asset, uint256 amount);\n\n constructor(\n address _oToken,\n address _wOToken,\n address _vault,\n address _weth\n ) {\n oToken = IERC20(_oToken);\n wOToken = IERC4626(_wOToken);\n vault = IVault(_vault);\n weth = IWETH9(_weth);\n\n IWETH9(_weth).approve(address(_vault), type(uint256).max);\n IERC20(_oToken).approve(_wOToken, type(uint256).max);\n }\n\n /**\n * @dev Deposit ETH and receive OToken in return.\n * Will verify that the user is sent 1:1 for ETH.\n */\n receive() external payable {\n deposit();\n }\n\n /**\n * @dev Deposit ETH and receive OToken in return\n * Will verify that the user is sent 1:1 for ETH.\n * @return Amount of OETH sent to user\n */\n function deposit() public payable returns (uint256) {\n uint256 balance = address(this).balance;\n\n emit Zap(msg.sender, ETH_MARKER, balance);\n\n // Wrap ETH\n weth.deposit{ value: balance }();\n\n // Mint with WETH\n return _mint(balance, msg.sender);\n }\n\n /**\n * @dev Deposit ETH and receive superOETHb in return\n * @param minReceived min amount of wsuperOETHb to receive\n * @return Amount of wsuperOETHb sent to user\n */\n function depositETHForWrappedTokens(uint256 minReceived)\n external\n payable\n returns (uint256)\n {\n uint256 balance = address(this).balance;\n\n emit Zap(msg.sender, ETH_MARKER, balance);\n\n // Wrap ETH\n weth.deposit{ value: balance }();\n\n // Mint with WETH\n uint256 mintedOToken = _mint(balance, address(this));\n\n // Wrap OToken into wOToken\n uint256 mintedWOToken = wOToken.deposit(mintedOToken, msg.sender);\n\n require(mintedWOToken >= minReceived, \"Zapper: not enough minted\");\n\n return mintedWOToken;\n }\n\n /**\n * @dev Deposit WETH and receive OToken in return\n * @param wethAmount Amount of WETH to deposit\n * @param minReceived min amount of wsuperOETHb to receive\n * @return Amount of wsuperOETHb sent to user\n */\n function depositWETHForWrappedTokens(\n uint256 wethAmount,\n uint256 minReceived\n ) external returns (uint256) {\n // slither-disable-next-line unchecked-transfer unused-return\n weth.transferFrom(msg.sender, address(this), wethAmount);\n\n emit Zap(msg.sender, address(weth), wethAmount);\n\n // Mint with WETH\n uint256 mintedOToken = _mint(wethAmount, address(this));\n\n // Wrap OToken into wOToken\n uint256 mintedWOToken = wOToken.deposit(mintedOToken, msg.sender);\n\n require(mintedWOToken >= minReceived, \"Zapper: not enough minted\");\n\n return mintedWOToken;\n }\n\n /**\n * @dev Internal function to mint superOETHb with WETH\n * @param minOToken Minimum amount of OToken to for user to receive\n * @param recipient Address that receives the tokens\n * @return Amount of OToken sent to user\n */\n function _mint(uint256 minOToken, address recipient)\n internal\n returns (uint256)\n {\n uint256 toMint = weth.balanceOf(address(this));\n vault.mint(address(weth), toMint, minOToken);\n uint256 mintedAmount = oToken.balanceOf(address(this));\n require(mintedAmount >= minOToken, \"Zapper: not enough minted\");\n\n if (recipient != address(this)) {\n require(oToken.transfer(recipient, mintedAmount));\n }\n\n return mintedAmount;\n }\n}\n" + }, + "contracts/zapper/OETHBaseZapper.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { AbstractOTokenZapper } from \"./AbstractOTokenZapper.sol\";\n\ncontract OETHBaseZapper is AbstractOTokenZapper {\n constructor(\n address _oethb,\n address _woethb,\n address _vault\n )\n AbstractOTokenZapper(\n _oethb,\n _woethb,\n _vault,\n 0x4200000000000000000000000000000000000006\n )\n {}\n}\n" + }, + "contracts/zapper/OETHZapper.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { AbstractOTokenZapper } from \"./AbstractOTokenZapper.sol\";\n\ncontract OETHZapper is AbstractOTokenZapper {\n constructor(\n address _oeth,\n address _woeth,\n address _vault,\n address _weth\n ) AbstractOTokenZapper(_oeth, _woeth, _vault, _weth) {}\n}\n" + }, + "contracts/zapper/OSonicZapper.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport { IVault } from \"../interfaces/IVault.sol\";\nimport { IWrappedSonic } from \"../interfaces/sonic/IWrappedSonic.sol\";\nimport { IERC4626 } from \"../../lib/openzeppelin/interfaces/IERC4626.sol\";\n\n/**\n * @title Zapper for Origin Sonic (OS) tokens\n * @author Origin Protocol Inc\n */\ncontract OSonicZapper {\n IERC20 public immutable OS;\n IERC4626 public immutable wOS;\n IVault public immutable vault;\n\n IWrappedSonic public constant wS =\n IWrappedSonic(0x039e2fB66102314Ce7b64Ce5Ce3E5183bc94aD38);\n address private constant ETH_MARKER =\n 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;\n\n event Zap(address indexed minter, address indexed asset, uint256 amount);\n\n constructor(\n address _OS,\n address _wOS,\n address _vault\n ) {\n OS = IERC20(_OS);\n wOS = IERC4626(_wOS);\n vault = IVault(_vault);\n\n wS.approve(address(_vault), type(uint256).max);\n IERC20(_OS).approve(_wOS, type(uint256).max);\n }\n\n /**\n * @dev Deposit native S currency and receive Origin Sonic (OS) tokens in return.\n * Will verify that the user is sent 1:1 for S.\n */\n receive() external payable {\n deposit();\n }\n\n /**\n * @dev Deposit native S currency and receive Origin Sonic (OS) tokens in return.\n * Will verify that the user is sent 1:1 for S.\n * @return Amount of Origin Sonic (OS) tokens sent to user\n */\n function deposit() public payable returns (uint256) {\n uint256 balance = address(this).balance;\n\n emit Zap(msg.sender, ETH_MARKER, balance);\n\n // Wrap native S\n wS.deposit{ value: balance }();\n\n // Mint Origin Sonic (OS) with Wrapped Sonic (wS)\n return _mint(balance, msg.sender);\n }\n\n /**\n * @dev Deposit S and receive Wrapped Origin Sonic (wOS) in return\n * @param minReceived min amount of Wrapped Origin Sonic (wOS) to receive\n * @return Amount of Wrapped Origin Sonic (wOS) tokens sent to user\n */\n function depositSForWrappedTokens(uint256 minReceived)\n external\n payable\n returns (uint256)\n {\n uint256 balance = address(this).balance;\n\n emit Zap(msg.sender, ETH_MARKER, balance);\n\n // Wrap S\n wS.deposit{ value: balance }();\n\n // Mint with Wrapped Sonic\n uint256 mintOS = _mint(balance, address(this));\n\n // Wrap Origin Sonic (OS) into Wrapped Origin Sonic (wOS)\n uint256 mintedWOS = wOS.deposit(mintOS, msg.sender);\n\n require(mintedWOS >= minReceived, \"Zapper: not enough minted\");\n\n return mintedWOS;\n }\n\n /**\n * @dev Deposit Wrapped Sonic (wS) tokens and receive Wrapped Origin Sonic (wOS) tokens in return\n * @param wSAmount Amount of Wrapped Sonic (wS) to deposit\n * @param minReceived min amount of Wrapped Origin Sonic (wOS) token to receive\n * @return Amount of Wrapped Origin Sonic (wOS) tokens sent to user\n */\n function depositWSForWrappedTokens(uint256 wSAmount, uint256 minReceived)\n external\n returns (uint256)\n {\n // slither-disable-next-line unchecked-transfer unused-return\n wS.transferFrom(msg.sender, address(this), wSAmount);\n\n emit Zap(msg.sender, address(wS), wSAmount);\n\n // Mint with Wrapped Sonic (wS)\n uint256 mintedOS = _mint(wSAmount, address(this));\n\n // Wrap Origin Sonic (OS) tokens into Wrapped Origin Sonic (wOS) tokens\n uint256 mintedWOS = wOS.deposit(mintedOS, msg.sender);\n\n require(mintedWOS >= minReceived, \"Zapper: not enough minted\");\n\n return mintedWOS;\n }\n\n /**\n * @dev Internal function to mint Origin Sonic (OS) with Wrapped S (wS)\n * @param minOS Minimum amount of Origin Sonic (OS) tokens the user can receive\n * @param recipient Address that receives the tokens\n * @return Amount of Origin Sonic (OS) tokens sent to the recipient\n */\n function _mint(uint256 minOS, address recipient)\n internal\n returns (uint256)\n {\n uint256 toMint = wS.balanceOf(address(this));\n vault.mint(address(wS), toMint, minOS);\n uint256 mintedAmount = OS.balanceOf(address(this));\n require(mintedAmount >= minOS, \"Zapper: not enough minted\");\n\n if (recipient != address(this)) {\n require(OS.transfer(recipient, mintedAmount));\n }\n\n return mintedAmount;\n }\n}\n" + }, + "hardhat/console.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity >=0.4.22 <0.9.0;\n\nlibrary console {\n address constant CONSOLE_ADDRESS =\n 0x000000000000000000636F6e736F6c652e6c6f67;\n\n function _sendLogPayloadImplementation(bytes memory payload) internal view {\n address consoleAddress = CONSOLE_ADDRESS;\n /// @solidity memory-safe-assembly\n assembly {\n pop(\n staticcall(\n gas(),\n consoleAddress,\n add(payload, 32),\n mload(payload),\n 0,\n 0\n )\n )\n }\n }\n\n function _castToPure(\n function(bytes memory) internal view fnIn\n ) internal pure returns (function(bytes memory) pure fnOut) {\n assembly {\n fnOut := fnIn\n }\n }\n\n function _sendLogPayload(bytes memory payload) internal pure {\n _castToPure(_sendLogPayloadImplementation)(payload);\n }\n\n function log() internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log()\"));\n }\n\n function logInt(int256 p0) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(int256)\", p0));\n }\n\n function logUint(uint256 p0) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256)\", p0));\n }\n\n function logString(string memory p0) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string)\", p0));\n }\n\n function logBool(bool p0) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool)\", p0));\n }\n\n function logAddress(address p0) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address)\", p0));\n }\n\n function logBytes(bytes memory p0) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bytes)\", p0));\n }\n\n function logBytes1(bytes1 p0) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bytes1)\", p0));\n }\n\n function logBytes2(bytes2 p0) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bytes2)\", p0));\n }\n\n function logBytes3(bytes3 p0) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bytes3)\", p0));\n }\n\n function logBytes4(bytes4 p0) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bytes4)\", p0));\n }\n\n function logBytes5(bytes5 p0) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bytes5)\", p0));\n }\n\n function logBytes6(bytes6 p0) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bytes6)\", p0));\n }\n\n function logBytes7(bytes7 p0) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bytes7)\", p0));\n }\n\n function logBytes8(bytes8 p0) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bytes8)\", p0));\n }\n\n function logBytes9(bytes9 p0) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bytes9)\", p0));\n }\n\n function logBytes10(bytes10 p0) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bytes10)\", p0));\n }\n\n function logBytes11(bytes11 p0) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bytes11)\", p0));\n }\n\n function logBytes12(bytes12 p0) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bytes12)\", p0));\n }\n\n function logBytes13(bytes13 p0) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bytes13)\", p0));\n }\n\n function logBytes14(bytes14 p0) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bytes14)\", p0));\n }\n\n function logBytes15(bytes15 p0) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bytes15)\", p0));\n }\n\n function logBytes16(bytes16 p0) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bytes16)\", p0));\n }\n\n function logBytes17(bytes17 p0) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bytes17)\", p0));\n }\n\n function logBytes18(bytes18 p0) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bytes18)\", p0));\n }\n\n function logBytes19(bytes19 p0) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bytes19)\", p0));\n }\n\n function logBytes20(bytes20 p0) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bytes20)\", p0));\n }\n\n function logBytes21(bytes21 p0) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bytes21)\", p0));\n }\n\n function logBytes22(bytes22 p0) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bytes22)\", p0));\n }\n\n function logBytes23(bytes23 p0) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bytes23)\", p0));\n }\n\n function logBytes24(bytes24 p0) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bytes24)\", p0));\n }\n\n function logBytes25(bytes25 p0) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bytes25)\", p0));\n }\n\n function logBytes26(bytes26 p0) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bytes26)\", p0));\n }\n\n function logBytes27(bytes27 p0) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bytes27)\", p0));\n }\n\n function logBytes28(bytes28 p0) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bytes28)\", p0));\n }\n\n function logBytes29(bytes29 p0) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bytes29)\", p0));\n }\n\n function logBytes30(bytes30 p0) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bytes30)\", p0));\n }\n\n function logBytes31(bytes31 p0) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bytes31)\", p0));\n }\n\n function logBytes32(bytes32 p0) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bytes32)\", p0));\n }\n\n function log(uint256 p0) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256)\", p0));\n }\n\n function log(string memory p0) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string)\", p0));\n }\n\n function log(bool p0) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool)\", p0));\n }\n\n function log(address p0) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address)\", p0));\n }\n\n function log(uint256 p0, uint256 p1) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,uint256)\", p0, p1));\n }\n\n function log(uint256 p0, string memory p1) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,string)\", p0, p1));\n }\n\n function log(uint256 p0, bool p1) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,bool)\", p0, p1));\n }\n\n function log(uint256 p0, address p1) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,address)\", p0, p1));\n }\n\n function log(string memory p0, uint256 p1) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,uint256)\", p0, p1));\n }\n\n function log(string memory p0, string memory p1) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,string)\", p0, p1));\n }\n\n function log(string memory p0, bool p1) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,bool)\", p0, p1));\n }\n\n function log(string memory p0, address p1) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,address)\", p0, p1));\n }\n\n function log(bool p0, uint256 p1) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,uint256)\", p0, p1));\n }\n\n function log(bool p0, string memory p1) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,string)\", p0, p1));\n }\n\n function log(bool p0, bool p1) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,bool)\", p0, p1));\n }\n\n function log(bool p0, address p1) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,address)\", p0, p1));\n }\n\n function log(address p0, uint256 p1) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,uint256)\", p0, p1));\n }\n\n function log(address p0, string memory p1) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,string)\", p0, p1));\n }\n\n function log(address p0, bool p1) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,bool)\", p0, p1));\n }\n\n function log(address p0, address p1) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,address)\", p0, p1));\n }\n\n function log(uint256 p0, uint256 p1, uint256 p2) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,uint256,uint256)\", p0, p1, p2));\n }\n\n function log(uint256 p0, uint256 p1, string memory p2) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,uint256,string)\", p0, p1, p2));\n }\n\n function log(uint256 p0, uint256 p1, bool p2) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,uint256,bool)\", p0, p1, p2));\n }\n\n function log(uint256 p0, uint256 p1, address p2) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,uint256,address)\", p0, p1, p2));\n }\n\n function log(uint256 p0, string memory p1, uint256 p2) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,string,uint256)\", p0, p1, p2));\n }\n\n function log(uint256 p0, string memory p1, string memory p2) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,string,string)\", p0, p1, p2));\n }\n\n function log(uint256 p0, string memory p1, bool p2) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,string,bool)\", p0, p1, p2));\n }\n\n function log(uint256 p0, string memory p1, address p2) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,string,address)\", p0, p1, p2));\n }\n\n function log(uint256 p0, bool p1, uint256 p2) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,bool,uint256)\", p0, p1, p2));\n }\n\n function log(uint256 p0, bool p1, string memory p2) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,bool,string)\", p0, p1, p2));\n }\n\n function log(uint256 p0, bool p1, bool p2) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,bool,bool)\", p0, p1, p2));\n }\n\n function log(uint256 p0, bool p1, address p2) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,bool,address)\", p0, p1, p2));\n }\n\n function log(uint256 p0, address p1, uint256 p2) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,address,uint256)\", p0, p1, p2));\n }\n\n function log(uint256 p0, address p1, string memory p2) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,address,string)\", p0, p1, p2));\n }\n\n function log(uint256 p0, address p1, bool p2) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,address,bool)\", p0, p1, p2));\n }\n\n function log(uint256 p0, address p1, address p2) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,address,address)\", p0, p1, p2));\n }\n\n function log(string memory p0, uint256 p1, uint256 p2) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,uint256,uint256)\", p0, p1, p2));\n }\n\n function log(string memory p0, uint256 p1, string memory p2) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,uint256,string)\", p0, p1, p2));\n }\n\n function log(string memory p0, uint256 p1, bool p2) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,uint256,bool)\", p0, p1, p2));\n }\n\n function log(string memory p0, uint256 p1, address p2) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,uint256,address)\", p0, p1, p2));\n }\n\n function log(string memory p0, string memory p1, uint256 p2) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,string,uint256)\", p0, p1, p2));\n }\n\n function log(string memory p0, string memory p1, string memory p2) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,string,string)\", p0, p1, p2));\n }\n\n function log(string memory p0, string memory p1, bool p2) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,string,bool)\", p0, p1, p2));\n }\n\n function log(string memory p0, string memory p1, address p2) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,string,address)\", p0, p1, p2));\n }\n\n function log(string memory p0, bool p1, uint256 p2) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,bool,uint256)\", p0, p1, p2));\n }\n\n function log(string memory p0, bool p1, string memory p2) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,bool,string)\", p0, p1, p2));\n }\n\n function log(string memory p0, bool p1, bool p2) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,bool,bool)\", p0, p1, p2));\n }\n\n function log(string memory p0, bool p1, address p2) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,bool,address)\", p0, p1, p2));\n }\n\n function log(string memory p0, address p1, uint256 p2) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,address,uint256)\", p0, p1, p2));\n }\n\n function log(string memory p0, address p1, string memory p2) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,address,string)\", p0, p1, p2));\n }\n\n function log(string memory p0, address p1, bool p2) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,address,bool)\", p0, p1, p2));\n }\n\n function log(string memory p0, address p1, address p2) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,address,address)\", p0, p1, p2));\n }\n\n function log(bool p0, uint256 p1, uint256 p2) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,uint256,uint256)\", p0, p1, p2));\n }\n\n function log(bool p0, uint256 p1, string memory p2) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,uint256,string)\", p0, p1, p2));\n }\n\n function log(bool p0, uint256 p1, bool p2) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,uint256,bool)\", p0, p1, p2));\n }\n\n function log(bool p0, uint256 p1, address p2) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,uint256,address)\", p0, p1, p2));\n }\n\n function log(bool p0, string memory p1, uint256 p2) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,string,uint256)\", p0, p1, p2));\n }\n\n function log(bool p0, string memory p1, string memory p2) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,string,string)\", p0, p1, p2));\n }\n\n function log(bool p0, string memory p1, bool p2) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,string,bool)\", p0, p1, p2));\n }\n\n function log(bool p0, string memory p1, address p2) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,string,address)\", p0, p1, p2));\n }\n\n function log(bool p0, bool p1, uint256 p2) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,bool,uint256)\", p0, p1, p2));\n }\n\n function log(bool p0, bool p1, string memory p2) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,bool,string)\", p0, p1, p2));\n }\n\n function log(bool p0, bool p1, bool p2) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,bool,bool)\", p0, p1, p2));\n }\n\n function log(bool p0, bool p1, address p2) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,bool,address)\", p0, p1, p2));\n }\n\n function log(bool p0, address p1, uint256 p2) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,address,uint256)\", p0, p1, p2));\n }\n\n function log(bool p0, address p1, string memory p2) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,address,string)\", p0, p1, p2));\n }\n\n function log(bool p0, address p1, bool p2) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,address,bool)\", p0, p1, p2));\n }\n\n function log(bool p0, address p1, address p2) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,address,address)\", p0, p1, p2));\n }\n\n function log(address p0, uint256 p1, uint256 p2) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,uint256,uint256)\", p0, p1, p2));\n }\n\n function log(address p0, uint256 p1, string memory p2) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,uint256,string)\", p0, p1, p2));\n }\n\n function log(address p0, uint256 p1, bool p2) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,uint256,bool)\", p0, p1, p2));\n }\n\n function log(address p0, uint256 p1, address p2) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,uint256,address)\", p0, p1, p2));\n }\n\n function log(address p0, string memory p1, uint256 p2) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,string,uint256)\", p0, p1, p2));\n }\n\n function log(address p0, string memory p1, string memory p2) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,string,string)\", p0, p1, p2));\n }\n\n function log(address p0, string memory p1, bool p2) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,string,bool)\", p0, p1, p2));\n }\n\n function log(address p0, string memory p1, address p2) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,string,address)\", p0, p1, p2));\n }\n\n function log(address p0, bool p1, uint256 p2) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,bool,uint256)\", p0, p1, p2));\n }\n\n function log(address p0, bool p1, string memory p2) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,bool,string)\", p0, p1, p2));\n }\n\n function log(address p0, bool p1, bool p2) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,bool,bool)\", p0, p1, p2));\n }\n\n function log(address p0, bool p1, address p2) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,bool,address)\", p0, p1, p2));\n }\n\n function log(address p0, address p1, uint256 p2) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,address,uint256)\", p0, p1, p2));\n }\n\n function log(address p0, address p1, string memory p2) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,address,string)\", p0, p1, p2));\n }\n\n function log(address p0, address p1, bool p2) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,address,bool)\", p0, p1, p2));\n }\n\n function log(address p0, address p1, address p2) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,address,address)\", p0, p1, p2));\n }\n\n function log(uint256 p0, uint256 p1, uint256 p2, uint256 p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,uint256,uint256,uint256)\", p0, p1, p2, p3));\n }\n\n function log(uint256 p0, uint256 p1, uint256 p2, string memory p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,uint256,uint256,string)\", p0, p1, p2, p3));\n }\n\n function log(uint256 p0, uint256 p1, uint256 p2, bool p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,uint256,uint256,bool)\", p0, p1, p2, p3));\n }\n\n function log(uint256 p0, uint256 p1, uint256 p2, address p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,uint256,uint256,address)\", p0, p1, p2, p3));\n }\n\n function log(uint256 p0, uint256 p1, string memory p2, uint256 p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,uint256,string,uint256)\", p0, p1, p2, p3));\n }\n\n function log(uint256 p0, uint256 p1, string memory p2, string memory p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,uint256,string,string)\", p0, p1, p2, p3));\n }\n\n function log(uint256 p0, uint256 p1, string memory p2, bool p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,uint256,string,bool)\", p0, p1, p2, p3));\n }\n\n function log(uint256 p0, uint256 p1, string memory p2, address p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,uint256,string,address)\", p0, p1, p2, p3));\n }\n\n function log(uint256 p0, uint256 p1, bool p2, uint256 p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,uint256,bool,uint256)\", p0, p1, p2, p3));\n }\n\n function log(uint256 p0, uint256 p1, bool p2, string memory p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,uint256,bool,string)\", p0, p1, p2, p3));\n }\n\n function log(uint256 p0, uint256 p1, bool p2, bool p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,uint256,bool,bool)\", p0, p1, p2, p3));\n }\n\n function log(uint256 p0, uint256 p1, bool p2, address p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,uint256,bool,address)\", p0, p1, p2, p3));\n }\n\n function log(uint256 p0, uint256 p1, address p2, uint256 p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,uint256,address,uint256)\", p0, p1, p2, p3));\n }\n\n function log(uint256 p0, uint256 p1, address p2, string memory p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,uint256,address,string)\", p0, p1, p2, p3));\n }\n\n function log(uint256 p0, uint256 p1, address p2, bool p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,uint256,address,bool)\", p0, p1, p2, p3));\n }\n\n function log(uint256 p0, uint256 p1, address p2, address p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,uint256,address,address)\", p0, p1, p2, p3));\n }\n\n function log(uint256 p0, string memory p1, uint256 p2, uint256 p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,string,uint256,uint256)\", p0, p1, p2, p3));\n }\n\n function log(uint256 p0, string memory p1, uint256 p2, string memory p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,string,uint256,string)\", p0, p1, p2, p3));\n }\n\n function log(uint256 p0, string memory p1, uint256 p2, bool p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,string,uint256,bool)\", p0, p1, p2, p3));\n }\n\n function log(uint256 p0, string memory p1, uint256 p2, address p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,string,uint256,address)\", p0, p1, p2, p3));\n }\n\n function log(uint256 p0, string memory p1, string memory p2, uint256 p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,string,string,uint256)\", p0, p1, p2, p3));\n }\n\n function log(uint256 p0, string memory p1, string memory p2, string memory p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,string,string,string)\", p0, p1, p2, p3));\n }\n\n function log(uint256 p0, string memory p1, string memory p2, bool p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,string,string,bool)\", p0, p1, p2, p3));\n }\n\n function log(uint256 p0, string memory p1, string memory p2, address p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,string,string,address)\", p0, p1, p2, p3));\n }\n\n function log(uint256 p0, string memory p1, bool p2, uint256 p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,string,bool,uint256)\", p0, p1, p2, p3));\n }\n\n function log(uint256 p0, string memory p1, bool p2, string memory p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,string,bool,string)\", p0, p1, p2, p3));\n }\n\n function log(uint256 p0, string memory p1, bool p2, bool p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,string,bool,bool)\", p0, p1, p2, p3));\n }\n\n function log(uint256 p0, string memory p1, bool p2, address p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,string,bool,address)\", p0, p1, p2, p3));\n }\n\n function log(uint256 p0, string memory p1, address p2, uint256 p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,string,address,uint256)\", p0, p1, p2, p3));\n }\n\n function log(uint256 p0, string memory p1, address p2, string memory p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,string,address,string)\", p0, p1, p2, p3));\n }\n\n function log(uint256 p0, string memory p1, address p2, bool p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,string,address,bool)\", p0, p1, p2, p3));\n }\n\n function log(uint256 p0, string memory p1, address p2, address p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,string,address,address)\", p0, p1, p2, p3));\n }\n\n function log(uint256 p0, bool p1, uint256 p2, uint256 p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,bool,uint256,uint256)\", p0, p1, p2, p3));\n }\n\n function log(uint256 p0, bool p1, uint256 p2, string memory p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,bool,uint256,string)\", p0, p1, p2, p3));\n }\n\n function log(uint256 p0, bool p1, uint256 p2, bool p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,bool,uint256,bool)\", p0, p1, p2, p3));\n }\n\n function log(uint256 p0, bool p1, uint256 p2, address p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,bool,uint256,address)\", p0, p1, p2, p3));\n }\n\n function log(uint256 p0, bool p1, string memory p2, uint256 p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,bool,string,uint256)\", p0, p1, p2, p3));\n }\n\n function log(uint256 p0, bool p1, string memory p2, string memory p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,bool,string,string)\", p0, p1, p2, p3));\n }\n\n function log(uint256 p0, bool p1, string memory p2, bool p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,bool,string,bool)\", p0, p1, p2, p3));\n }\n\n function log(uint256 p0, bool p1, string memory p2, address p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,bool,string,address)\", p0, p1, p2, p3));\n }\n\n function log(uint256 p0, bool p1, bool p2, uint256 p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,bool,bool,uint256)\", p0, p1, p2, p3));\n }\n\n function log(uint256 p0, bool p1, bool p2, string memory p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,bool,bool,string)\", p0, p1, p2, p3));\n }\n\n function log(uint256 p0, bool p1, bool p2, bool p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,bool,bool,bool)\", p0, p1, p2, p3));\n }\n\n function log(uint256 p0, bool p1, bool p2, address p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,bool,bool,address)\", p0, p1, p2, p3));\n }\n\n function log(uint256 p0, bool p1, address p2, uint256 p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,bool,address,uint256)\", p0, p1, p2, p3));\n }\n\n function log(uint256 p0, bool p1, address p2, string memory p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,bool,address,string)\", p0, p1, p2, p3));\n }\n\n function log(uint256 p0, bool p1, address p2, bool p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,bool,address,bool)\", p0, p1, p2, p3));\n }\n\n function log(uint256 p0, bool p1, address p2, address p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,bool,address,address)\", p0, p1, p2, p3));\n }\n\n function log(uint256 p0, address p1, uint256 p2, uint256 p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,address,uint256,uint256)\", p0, p1, p2, p3));\n }\n\n function log(uint256 p0, address p1, uint256 p2, string memory p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,address,uint256,string)\", p0, p1, p2, p3));\n }\n\n function log(uint256 p0, address p1, uint256 p2, bool p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,address,uint256,bool)\", p0, p1, p2, p3));\n }\n\n function log(uint256 p0, address p1, uint256 p2, address p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,address,uint256,address)\", p0, p1, p2, p3));\n }\n\n function log(uint256 p0, address p1, string memory p2, uint256 p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,address,string,uint256)\", p0, p1, p2, p3));\n }\n\n function log(uint256 p0, address p1, string memory p2, string memory p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,address,string,string)\", p0, p1, p2, p3));\n }\n\n function log(uint256 p0, address p1, string memory p2, bool p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,address,string,bool)\", p0, p1, p2, p3));\n }\n\n function log(uint256 p0, address p1, string memory p2, address p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,address,string,address)\", p0, p1, p2, p3));\n }\n\n function log(uint256 p0, address p1, bool p2, uint256 p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,address,bool,uint256)\", p0, p1, p2, p3));\n }\n\n function log(uint256 p0, address p1, bool p2, string memory p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,address,bool,string)\", p0, p1, p2, p3));\n }\n\n function log(uint256 p0, address p1, bool p2, bool p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,address,bool,bool)\", p0, p1, p2, p3));\n }\n\n function log(uint256 p0, address p1, bool p2, address p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,address,bool,address)\", p0, p1, p2, p3));\n }\n\n function log(uint256 p0, address p1, address p2, uint256 p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,address,address,uint256)\", p0, p1, p2, p3));\n }\n\n function log(uint256 p0, address p1, address p2, string memory p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,address,address,string)\", p0, p1, p2, p3));\n }\n\n function log(uint256 p0, address p1, address p2, bool p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,address,address,bool)\", p0, p1, p2, p3));\n }\n\n function log(uint256 p0, address p1, address p2, address p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(uint256,address,address,address)\", p0, p1, p2, p3));\n }\n\n function log(string memory p0, uint256 p1, uint256 p2, uint256 p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,uint256,uint256,uint256)\", p0, p1, p2, p3));\n }\n\n function log(string memory p0, uint256 p1, uint256 p2, string memory p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,uint256,uint256,string)\", p0, p1, p2, p3));\n }\n\n function log(string memory p0, uint256 p1, uint256 p2, bool p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,uint256,uint256,bool)\", p0, p1, p2, p3));\n }\n\n function log(string memory p0, uint256 p1, uint256 p2, address p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,uint256,uint256,address)\", p0, p1, p2, p3));\n }\n\n function log(string memory p0, uint256 p1, string memory p2, uint256 p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,uint256,string,uint256)\", p0, p1, p2, p3));\n }\n\n function log(string memory p0, uint256 p1, string memory p2, string memory p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,uint256,string,string)\", p0, p1, p2, p3));\n }\n\n function log(string memory p0, uint256 p1, string memory p2, bool p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,uint256,string,bool)\", p0, p1, p2, p3));\n }\n\n function log(string memory p0, uint256 p1, string memory p2, address p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,uint256,string,address)\", p0, p1, p2, p3));\n }\n\n function log(string memory p0, uint256 p1, bool p2, uint256 p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,uint256,bool,uint256)\", p0, p1, p2, p3));\n }\n\n function log(string memory p0, uint256 p1, bool p2, string memory p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,uint256,bool,string)\", p0, p1, p2, p3));\n }\n\n function log(string memory p0, uint256 p1, bool p2, bool p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,uint256,bool,bool)\", p0, p1, p2, p3));\n }\n\n function log(string memory p0, uint256 p1, bool p2, address p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,uint256,bool,address)\", p0, p1, p2, p3));\n }\n\n function log(string memory p0, uint256 p1, address p2, uint256 p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,uint256,address,uint256)\", p0, p1, p2, p3));\n }\n\n function log(string memory p0, uint256 p1, address p2, string memory p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,uint256,address,string)\", p0, p1, p2, p3));\n }\n\n function log(string memory p0, uint256 p1, address p2, bool p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,uint256,address,bool)\", p0, p1, p2, p3));\n }\n\n function log(string memory p0, uint256 p1, address p2, address p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,uint256,address,address)\", p0, p1, p2, p3));\n }\n\n function log(string memory p0, string memory p1, uint256 p2, uint256 p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,string,uint256,uint256)\", p0, p1, p2, p3));\n }\n\n function log(string memory p0, string memory p1, uint256 p2, string memory p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,string,uint256,string)\", p0, p1, p2, p3));\n }\n\n function log(string memory p0, string memory p1, uint256 p2, bool p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,string,uint256,bool)\", p0, p1, p2, p3));\n }\n\n function log(string memory p0, string memory p1, uint256 p2, address p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,string,uint256,address)\", p0, p1, p2, p3));\n }\n\n function log(string memory p0, string memory p1, string memory p2, uint256 p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,string,string,uint256)\", p0, p1, p2, p3));\n }\n\n function log(string memory p0, string memory p1, string memory p2, string memory p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,string,string,string)\", p0, p1, p2, p3));\n }\n\n function log(string memory p0, string memory p1, string memory p2, bool p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,string,string,bool)\", p0, p1, p2, p3));\n }\n\n function log(string memory p0, string memory p1, string memory p2, address p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,string,string,address)\", p0, p1, p2, p3));\n }\n\n function log(string memory p0, string memory p1, bool p2, uint256 p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,string,bool,uint256)\", p0, p1, p2, p3));\n }\n\n function log(string memory p0, string memory p1, bool p2, string memory p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,string,bool,string)\", p0, p1, p2, p3));\n }\n\n function log(string memory p0, string memory p1, bool p2, bool p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,string,bool,bool)\", p0, p1, p2, p3));\n }\n\n function log(string memory p0, string memory p1, bool p2, address p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,string,bool,address)\", p0, p1, p2, p3));\n }\n\n function log(string memory p0, string memory p1, address p2, uint256 p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,string,address,uint256)\", p0, p1, p2, p3));\n }\n\n function log(string memory p0, string memory p1, address p2, string memory p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,string,address,string)\", p0, p1, p2, p3));\n }\n\n function log(string memory p0, string memory p1, address p2, bool p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,string,address,bool)\", p0, p1, p2, p3));\n }\n\n function log(string memory p0, string memory p1, address p2, address p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,string,address,address)\", p0, p1, p2, p3));\n }\n\n function log(string memory p0, bool p1, uint256 p2, uint256 p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,bool,uint256,uint256)\", p0, p1, p2, p3));\n }\n\n function log(string memory p0, bool p1, uint256 p2, string memory p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,bool,uint256,string)\", p0, p1, p2, p3));\n }\n\n function log(string memory p0, bool p1, uint256 p2, bool p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,bool,uint256,bool)\", p0, p1, p2, p3));\n }\n\n function log(string memory p0, bool p1, uint256 p2, address p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,bool,uint256,address)\", p0, p1, p2, p3));\n }\n\n function log(string memory p0, bool p1, string memory p2, uint256 p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,bool,string,uint256)\", p0, p1, p2, p3));\n }\n\n function log(string memory p0, bool p1, string memory p2, string memory p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,bool,string,string)\", p0, p1, p2, p3));\n }\n\n function log(string memory p0, bool p1, string memory p2, bool p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,bool,string,bool)\", p0, p1, p2, p3));\n }\n\n function log(string memory p0, bool p1, string memory p2, address p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,bool,string,address)\", p0, p1, p2, p3));\n }\n\n function log(string memory p0, bool p1, bool p2, uint256 p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,bool,bool,uint256)\", p0, p1, p2, p3));\n }\n\n function log(string memory p0, bool p1, bool p2, string memory p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,bool,bool,string)\", p0, p1, p2, p3));\n }\n\n function log(string memory p0, bool p1, bool p2, bool p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,bool,bool,bool)\", p0, p1, p2, p3));\n }\n\n function log(string memory p0, bool p1, bool p2, address p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,bool,bool,address)\", p0, p1, p2, p3));\n }\n\n function log(string memory p0, bool p1, address p2, uint256 p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,bool,address,uint256)\", p0, p1, p2, p3));\n }\n\n function log(string memory p0, bool p1, address p2, string memory p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,bool,address,string)\", p0, p1, p2, p3));\n }\n\n function log(string memory p0, bool p1, address p2, bool p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,bool,address,bool)\", p0, p1, p2, p3));\n }\n\n function log(string memory p0, bool p1, address p2, address p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,bool,address,address)\", p0, p1, p2, p3));\n }\n\n function log(string memory p0, address p1, uint256 p2, uint256 p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,address,uint256,uint256)\", p0, p1, p2, p3));\n }\n\n function log(string memory p0, address p1, uint256 p2, string memory p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,address,uint256,string)\", p0, p1, p2, p3));\n }\n\n function log(string memory p0, address p1, uint256 p2, bool p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,address,uint256,bool)\", p0, p1, p2, p3));\n }\n\n function log(string memory p0, address p1, uint256 p2, address p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,address,uint256,address)\", p0, p1, p2, p3));\n }\n\n function log(string memory p0, address p1, string memory p2, uint256 p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,address,string,uint256)\", p0, p1, p2, p3));\n }\n\n function log(string memory p0, address p1, string memory p2, string memory p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,address,string,string)\", p0, p1, p2, p3));\n }\n\n function log(string memory p0, address p1, string memory p2, bool p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,address,string,bool)\", p0, p1, p2, p3));\n }\n\n function log(string memory p0, address p1, string memory p2, address p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,address,string,address)\", p0, p1, p2, p3));\n }\n\n function log(string memory p0, address p1, bool p2, uint256 p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,address,bool,uint256)\", p0, p1, p2, p3));\n }\n\n function log(string memory p0, address p1, bool p2, string memory p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,address,bool,string)\", p0, p1, p2, p3));\n }\n\n function log(string memory p0, address p1, bool p2, bool p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,address,bool,bool)\", p0, p1, p2, p3));\n }\n\n function log(string memory p0, address p1, bool p2, address p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,address,bool,address)\", p0, p1, p2, p3));\n }\n\n function log(string memory p0, address p1, address p2, uint256 p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,address,address,uint256)\", p0, p1, p2, p3));\n }\n\n function log(string memory p0, address p1, address p2, string memory p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,address,address,string)\", p0, p1, p2, p3));\n }\n\n function log(string memory p0, address p1, address p2, bool p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,address,address,bool)\", p0, p1, p2, p3));\n }\n\n function log(string memory p0, address p1, address p2, address p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(string,address,address,address)\", p0, p1, p2, p3));\n }\n\n function log(bool p0, uint256 p1, uint256 p2, uint256 p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,uint256,uint256,uint256)\", p0, p1, p2, p3));\n }\n\n function log(bool p0, uint256 p1, uint256 p2, string memory p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,uint256,uint256,string)\", p0, p1, p2, p3));\n }\n\n function log(bool p0, uint256 p1, uint256 p2, bool p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,uint256,uint256,bool)\", p0, p1, p2, p3));\n }\n\n function log(bool p0, uint256 p1, uint256 p2, address p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,uint256,uint256,address)\", p0, p1, p2, p3));\n }\n\n function log(bool p0, uint256 p1, string memory p2, uint256 p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,uint256,string,uint256)\", p0, p1, p2, p3));\n }\n\n function log(bool p0, uint256 p1, string memory p2, string memory p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,uint256,string,string)\", p0, p1, p2, p3));\n }\n\n function log(bool p0, uint256 p1, string memory p2, bool p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,uint256,string,bool)\", p0, p1, p2, p3));\n }\n\n function log(bool p0, uint256 p1, string memory p2, address p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,uint256,string,address)\", p0, p1, p2, p3));\n }\n\n function log(bool p0, uint256 p1, bool p2, uint256 p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,uint256,bool,uint256)\", p0, p1, p2, p3));\n }\n\n function log(bool p0, uint256 p1, bool p2, string memory p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,uint256,bool,string)\", p0, p1, p2, p3));\n }\n\n function log(bool p0, uint256 p1, bool p2, bool p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,uint256,bool,bool)\", p0, p1, p2, p3));\n }\n\n function log(bool p0, uint256 p1, bool p2, address p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,uint256,bool,address)\", p0, p1, p2, p3));\n }\n\n function log(bool p0, uint256 p1, address p2, uint256 p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,uint256,address,uint256)\", p0, p1, p2, p3));\n }\n\n function log(bool p0, uint256 p1, address p2, string memory p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,uint256,address,string)\", p0, p1, p2, p3));\n }\n\n function log(bool p0, uint256 p1, address p2, bool p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,uint256,address,bool)\", p0, p1, p2, p3));\n }\n\n function log(bool p0, uint256 p1, address p2, address p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,uint256,address,address)\", p0, p1, p2, p3));\n }\n\n function log(bool p0, string memory p1, uint256 p2, uint256 p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,string,uint256,uint256)\", p0, p1, p2, p3));\n }\n\n function log(bool p0, string memory p1, uint256 p2, string memory p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,string,uint256,string)\", p0, p1, p2, p3));\n }\n\n function log(bool p0, string memory p1, uint256 p2, bool p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,string,uint256,bool)\", p0, p1, p2, p3));\n }\n\n function log(bool p0, string memory p1, uint256 p2, address p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,string,uint256,address)\", p0, p1, p2, p3));\n }\n\n function log(bool p0, string memory p1, string memory p2, uint256 p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,string,string,uint256)\", p0, p1, p2, p3));\n }\n\n function log(bool p0, string memory p1, string memory p2, string memory p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,string,string,string)\", p0, p1, p2, p3));\n }\n\n function log(bool p0, string memory p1, string memory p2, bool p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,string,string,bool)\", p0, p1, p2, p3));\n }\n\n function log(bool p0, string memory p1, string memory p2, address p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,string,string,address)\", p0, p1, p2, p3));\n }\n\n function log(bool p0, string memory p1, bool p2, uint256 p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,string,bool,uint256)\", p0, p1, p2, p3));\n }\n\n function log(bool p0, string memory p1, bool p2, string memory p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,string,bool,string)\", p0, p1, p2, p3));\n }\n\n function log(bool p0, string memory p1, bool p2, bool p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,string,bool,bool)\", p0, p1, p2, p3));\n }\n\n function log(bool p0, string memory p1, bool p2, address p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,string,bool,address)\", p0, p1, p2, p3));\n }\n\n function log(bool p0, string memory p1, address p2, uint256 p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,string,address,uint256)\", p0, p1, p2, p3));\n }\n\n function log(bool p0, string memory p1, address p2, string memory p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,string,address,string)\", p0, p1, p2, p3));\n }\n\n function log(bool p0, string memory p1, address p2, bool p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,string,address,bool)\", p0, p1, p2, p3));\n }\n\n function log(bool p0, string memory p1, address p2, address p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,string,address,address)\", p0, p1, p2, p3));\n }\n\n function log(bool p0, bool p1, uint256 p2, uint256 p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,bool,uint256,uint256)\", p0, p1, p2, p3));\n }\n\n function log(bool p0, bool p1, uint256 p2, string memory p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,bool,uint256,string)\", p0, p1, p2, p3));\n }\n\n function log(bool p0, bool p1, uint256 p2, bool p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,bool,uint256,bool)\", p0, p1, p2, p3));\n }\n\n function log(bool p0, bool p1, uint256 p2, address p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,bool,uint256,address)\", p0, p1, p2, p3));\n }\n\n function log(bool p0, bool p1, string memory p2, uint256 p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,bool,string,uint256)\", p0, p1, p2, p3));\n }\n\n function log(bool p0, bool p1, string memory p2, string memory p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,bool,string,string)\", p0, p1, p2, p3));\n }\n\n function log(bool p0, bool p1, string memory p2, bool p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,bool,string,bool)\", p0, p1, p2, p3));\n }\n\n function log(bool p0, bool p1, string memory p2, address p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,bool,string,address)\", p0, p1, p2, p3));\n }\n\n function log(bool p0, bool p1, bool p2, uint256 p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,bool,bool,uint256)\", p0, p1, p2, p3));\n }\n\n function log(bool p0, bool p1, bool p2, string memory p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,bool,bool,string)\", p0, p1, p2, p3));\n }\n\n function log(bool p0, bool p1, bool p2, bool p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,bool,bool,bool)\", p0, p1, p2, p3));\n }\n\n function log(bool p0, bool p1, bool p2, address p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,bool,bool,address)\", p0, p1, p2, p3));\n }\n\n function log(bool p0, bool p1, address p2, uint256 p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,bool,address,uint256)\", p0, p1, p2, p3));\n }\n\n function log(bool p0, bool p1, address p2, string memory p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,bool,address,string)\", p0, p1, p2, p3));\n }\n\n function log(bool p0, bool p1, address p2, bool p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,bool,address,bool)\", p0, p1, p2, p3));\n }\n\n function log(bool p0, bool p1, address p2, address p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,bool,address,address)\", p0, p1, p2, p3));\n }\n\n function log(bool p0, address p1, uint256 p2, uint256 p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,address,uint256,uint256)\", p0, p1, p2, p3));\n }\n\n function log(bool p0, address p1, uint256 p2, string memory p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,address,uint256,string)\", p0, p1, p2, p3));\n }\n\n function log(bool p0, address p1, uint256 p2, bool p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,address,uint256,bool)\", p0, p1, p2, p3));\n }\n\n function log(bool p0, address p1, uint256 p2, address p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,address,uint256,address)\", p0, p1, p2, p3));\n }\n\n function log(bool p0, address p1, string memory p2, uint256 p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,address,string,uint256)\", p0, p1, p2, p3));\n }\n\n function log(bool p0, address p1, string memory p2, string memory p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,address,string,string)\", p0, p1, p2, p3));\n }\n\n function log(bool p0, address p1, string memory p2, bool p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,address,string,bool)\", p0, p1, p2, p3));\n }\n\n function log(bool p0, address p1, string memory p2, address p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,address,string,address)\", p0, p1, p2, p3));\n }\n\n function log(bool p0, address p1, bool p2, uint256 p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,address,bool,uint256)\", p0, p1, p2, p3));\n }\n\n function log(bool p0, address p1, bool p2, string memory p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,address,bool,string)\", p0, p1, p2, p3));\n }\n\n function log(bool p0, address p1, bool p2, bool p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,address,bool,bool)\", p0, p1, p2, p3));\n }\n\n function log(bool p0, address p1, bool p2, address p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,address,bool,address)\", p0, p1, p2, p3));\n }\n\n function log(bool p0, address p1, address p2, uint256 p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,address,address,uint256)\", p0, p1, p2, p3));\n }\n\n function log(bool p0, address p1, address p2, string memory p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,address,address,string)\", p0, p1, p2, p3));\n }\n\n function log(bool p0, address p1, address p2, bool p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,address,address,bool)\", p0, p1, p2, p3));\n }\n\n function log(bool p0, address p1, address p2, address p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(bool,address,address,address)\", p0, p1, p2, p3));\n }\n\n function log(address p0, uint256 p1, uint256 p2, uint256 p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,uint256,uint256,uint256)\", p0, p1, p2, p3));\n }\n\n function log(address p0, uint256 p1, uint256 p2, string memory p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,uint256,uint256,string)\", p0, p1, p2, p3));\n }\n\n function log(address p0, uint256 p1, uint256 p2, bool p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,uint256,uint256,bool)\", p0, p1, p2, p3));\n }\n\n function log(address p0, uint256 p1, uint256 p2, address p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,uint256,uint256,address)\", p0, p1, p2, p3));\n }\n\n function log(address p0, uint256 p1, string memory p2, uint256 p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,uint256,string,uint256)\", p0, p1, p2, p3));\n }\n\n function log(address p0, uint256 p1, string memory p2, string memory p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,uint256,string,string)\", p0, p1, p2, p3));\n }\n\n function log(address p0, uint256 p1, string memory p2, bool p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,uint256,string,bool)\", p0, p1, p2, p3));\n }\n\n function log(address p0, uint256 p1, string memory p2, address p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,uint256,string,address)\", p0, p1, p2, p3));\n }\n\n function log(address p0, uint256 p1, bool p2, uint256 p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,uint256,bool,uint256)\", p0, p1, p2, p3));\n }\n\n function log(address p0, uint256 p1, bool p2, string memory p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,uint256,bool,string)\", p0, p1, p2, p3));\n }\n\n function log(address p0, uint256 p1, bool p2, bool p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,uint256,bool,bool)\", p0, p1, p2, p3));\n }\n\n function log(address p0, uint256 p1, bool p2, address p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,uint256,bool,address)\", p0, p1, p2, p3));\n }\n\n function log(address p0, uint256 p1, address p2, uint256 p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,uint256,address,uint256)\", p0, p1, p2, p3));\n }\n\n function log(address p0, uint256 p1, address p2, string memory p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,uint256,address,string)\", p0, p1, p2, p3));\n }\n\n function log(address p0, uint256 p1, address p2, bool p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,uint256,address,bool)\", p0, p1, p2, p3));\n }\n\n function log(address p0, uint256 p1, address p2, address p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,uint256,address,address)\", p0, p1, p2, p3));\n }\n\n function log(address p0, string memory p1, uint256 p2, uint256 p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,string,uint256,uint256)\", p0, p1, p2, p3));\n }\n\n function log(address p0, string memory p1, uint256 p2, string memory p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,string,uint256,string)\", p0, p1, p2, p3));\n }\n\n function log(address p0, string memory p1, uint256 p2, bool p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,string,uint256,bool)\", p0, p1, p2, p3));\n }\n\n function log(address p0, string memory p1, uint256 p2, address p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,string,uint256,address)\", p0, p1, p2, p3));\n }\n\n function log(address p0, string memory p1, string memory p2, uint256 p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,string,string,uint256)\", p0, p1, p2, p3));\n }\n\n function log(address p0, string memory p1, string memory p2, string memory p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,string,string,string)\", p0, p1, p2, p3));\n }\n\n function log(address p0, string memory p1, string memory p2, bool p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,string,string,bool)\", p0, p1, p2, p3));\n }\n\n function log(address p0, string memory p1, string memory p2, address p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,string,string,address)\", p0, p1, p2, p3));\n }\n\n function log(address p0, string memory p1, bool p2, uint256 p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,string,bool,uint256)\", p0, p1, p2, p3));\n }\n\n function log(address p0, string memory p1, bool p2, string memory p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,string,bool,string)\", p0, p1, p2, p3));\n }\n\n function log(address p0, string memory p1, bool p2, bool p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,string,bool,bool)\", p0, p1, p2, p3));\n }\n\n function log(address p0, string memory p1, bool p2, address p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,string,bool,address)\", p0, p1, p2, p3));\n }\n\n function log(address p0, string memory p1, address p2, uint256 p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,string,address,uint256)\", p0, p1, p2, p3));\n }\n\n function log(address p0, string memory p1, address p2, string memory p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,string,address,string)\", p0, p1, p2, p3));\n }\n\n function log(address p0, string memory p1, address p2, bool p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,string,address,bool)\", p0, p1, p2, p3));\n }\n\n function log(address p0, string memory p1, address p2, address p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,string,address,address)\", p0, p1, p2, p3));\n }\n\n function log(address p0, bool p1, uint256 p2, uint256 p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,bool,uint256,uint256)\", p0, p1, p2, p3));\n }\n\n function log(address p0, bool p1, uint256 p2, string memory p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,bool,uint256,string)\", p0, p1, p2, p3));\n }\n\n function log(address p0, bool p1, uint256 p2, bool p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,bool,uint256,bool)\", p0, p1, p2, p3));\n }\n\n function log(address p0, bool p1, uint256 p2, address p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,bool,uint256,address)\", p0, p1, p2, p3));\n }\n\n function log(address p0, bool p1, string memory p2, uint256 p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,bool,string,uint256)\", p0, p1, p2, p3));\n }\n\n function log(address p0, bool p1, string memory p2, string memory p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,bool,string,string)\", p0, p1, p2, p3));\n }\n\n function log(address p0, bool p1, string memory p2, bool p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,bool,string,bool)\", p0, p1, p2, p3));\n }\n\n function log(address p0, bool p1, string memory p2, address p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,bool,string,address)\", p0, p1, p2, p3));\n }\n\n function log(address p0, bool p1, bool p2, uint256 p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,bool,bool,uint256)\", p0, p1, p2, p3));\n }\n\n function log(address p0, bool p1, bool p2, string memory p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,bool,bool,string)\", p0, p1, p2, p3));\n }\n\n function log(address p0, bool p1, bool p2, bool p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,bool,bool,bool)\", p0, p1, p2, p3));\n }\n\n function log(address p0, bool p1, bool p2, address p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,bool,bool,address)\", p0, p1, p2, p3));\n }\n\n function log(address p0, bool p1, address p2, uint256 p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,bool,address,uint256)\", p0, p1, p2, p3));\n }\n\n function log(address p0, bool p1, address p2, string memory p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,bool,address,string)\", p0, p1, p2, p3));\n }\n\n function log(address p0, bool p1, address p2, bool p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,bool,address,bool)\", p0, p1, p2, p3));\n }\n\n function log(address p0, bool p1, address p2, address p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,bool,address,address)\", p0, p1, p2, p3));\n }\n\n function log(address p0, address p1, uint256 p2, uint256 p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,address,uint256,uint256)\", p0, p1, p2, p3));\n }\n\n function log(address p0, address p1, uint256 p2, string memory p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,address,uint256,string)\", p0, p1, p2, p3));\n }\n\n function log(address p0, address p1, uint256 p2, bool p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,address,uint256,bool)\", p0, p1, p2, p3));\n }\n\n function log(address p0, address p1, uint256 p2, address p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,address,uint256,address)\", p0, p1, p2, p3));\n }\n\n function log(address p0, address p1, string memory p2, uint256 p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,address,string,uint256)\", p0, p1, p2, p3));\n }\n\n function log(address p0, address p1, string memory p2, string memory p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,address,string,string)\", p0, p1, p2, p3));\n }\n\n function log(address p0, address p1, string memory p2, bool p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,address,string,bool)\", p0, p1, p2, p3));\n }\n\n function log(address p0, address p1, string memory p2, address p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,address,string,address)\", p0, p1, p2, p3));\n }\n\n function log(address p0, address p1, bool p2, uint256 p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,address,bool,uint256)\", p0, p1, p2, p3));\n }\n\n function log(address p0, address p1, bool p2, string memory p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,address,bool,string)\", p0, p1, p2, p3));\n }\n\n function log(address p0, address p1, bool p2, bool p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,address,bool,bool)\", p0, p1, p2, p3));\n }\n\n function log(address p0, address p1, bool p2, address p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,address,bool,address)\", p0, p1, p2, p3));\n }\n\n function log(address p0, address p1, address p2, uint256 p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,address,address,uint256)\", p0, p1, p2, p3));\n }\n\n function log(address p0, address p1, address p2, string memory p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,address,address,string)\", p0, p1, p2, p3));\n }\n\n function log(address p0, address p1, address p2, bool p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,address,address,bool)\", p0, p1, p2, p3));\n }\n\n function log(address p0, address p1, address p2, address p3) internal pure {\n _sendLogPayload(abi.encodeWithSignature(\"log(address,address,address,address)\", p0, p1, p2, p3));\n }\n}\n" + }, + "lib/openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\nimport { IERC4626 } from \"../../../../interfaces/IERC4626.sol\";\nimport { ERC20 } from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport { IERC20Metadata } from \"@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol\";\n\n// From Open Zeppelin draft PR commit:\n// fac43034dca85ff539db3fc8aa2a7084b843d454\n// https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3171\n\nabstract contract ERC4626 is ERC20, IERC4626 {\n IERC20Metadata private immutable _asset;\n\n constructor(IERC20Metadata __asset) {\n _asset = __asset;\n }\n\n /** @dev See {IERC4262-asset} */\n function asset() public view virtual override returns (address) {\n return address(_asset);\n }\n\n /** @dev See {IERC4262-totalAssets} */\n function totalAssets() public view virtual override returns (uint256) {\n return _asset.balanceOf(address(this));\n }\n\n /**\n * @dev See {IERC4262-convertToShares}\n *\n * Will revert if asserts > 0, totalSupply > 0 and totalAssets = 0. That corresponds to a case where any asset\n * would represent an infinite amout of shares.\n */\n function convertToShares(uint256 assets) public view virtual override returns (uint256 shares) {\n uint256 supply = totalSupply();\n\n return\n (assets == 0 || supply == 0)\n ? (assets * 10**decimals()) / 10**_asset.decimals()\n : (assets * supply) / totalAssets();\n }\n\n /** @dev See {IERC4262-convertToAssets} */\n function convertToAssets(uint256 shares) public view virtual override returns (uint256 assets) {\n uint256 supply = totalSupply();\n\n return (supply == 0) ? (shares * 10**_asset.decimals()) / 10**decimals() : (shares * totalAssets()) / supply;\n }\n\n /** @dev See {IERC4262-maxDeposit} */\n function maxDeposit(address) public view virtual override returns (uint256) {\n return type(uint256).max;\n }\n\n /** @dev See {IERC4262-maxMint} */\n function maxMint(address) public view virtual override returns (uint256) {\n return type(uint256).max;\n }\n\n /** @dev See {IERC4262-maxWithdraw} */\n function maxWithdraw(address owner) public view virtual override returns (uint256) {\n return convertToAssets(balanceOf(owner));\n }\n\n /** @dev See {IERC4262-maxRedeem} */\n function maxRedeem(address owner) public view virtual override returns (uint256) {\n return balanceOf(owner);\n }\n\n /** @dev See {IERC4262-previewDeposit} */\n function previewDeposit(uint256 assets) public view virtual override returns (uint256) {\n return convertToShares(assets);\n }\n\n /** @dev See {IERC4262-previewMint} */\n function previewMint(uint256 shares) public view virtual override returns (uint256) {\n uint256 assets = convertToAssets(shares);\n return assets + (convertToShares(assets) < shares ? 1 : 0);\n }\n\n /** @dev See {IERC4262-previewWithdraw} */\n function previewWithdraw(uint256 assets) public view virtual override returns (uint256) {\n uint256 shares = convertToShares(assets);\n return shares + (convertToAssets(shares) < assets ? 1 : 0);\n }\n\n /** @dev See {IERC4262-previewRedeem} */\n function previewRedeem(uint256 shares) public view virtual override returns (uint256) {\n return convertToAssets(shares);\n }\n\n /** @dev See {IERC4262-deposit} */\n function deposit(uint256 assets, address receiver) public virtual override returns (uint256) {\n require(assets <= maxDeposit(receiver), \"ERC4626: deposit more then max\");\n\n address caller = _msgSender();\n uint256 shares = previewDeposit(assets);\n\n // if _asset is ERC777, transferFrom can call reenter BEFORE the transfer happens through\n // the tokensToSend hook, so we need to transfer before we mint to keep the invariants.\n SafeERC20.safeTransferFrom(_asset, caller, address(this), assets);\n _mint(receiver, shares);\n\n emit Deposit(caller, receiver, assets, shares);\n\n return shares;\n }\n\n /** @dev See {IERC4262-mint} */\n function mint(uint256 shares, address receiver) public virtual override returns (uint256) {\n require(shares <= maxMint(receiver), \"ERC4626: mint more then max\");\n\n address caller = _msgSender();\n uint256 assets = previewMint(shares);\n\n // if _asset is ERC777, transferFrom can call reenter BEFORE the transfer happens through\n // the tokensToSend hook, so we need to transfer before we mint to keep the invariants.\n SafeERC20.safeTransferFrom(_asset, caller, address(this), assets);\n _mint(receiver, shares);\n\n emit Deposit(caller, receiver, assets, shares);\n\n return assets;\n }\n\n /** @dev See {IERC4262-withdraw} */\n function withdraw(\n uint256 assets,\n address receiver,\n address owner\n ) public virtual override returns (uint256) {\n require(assets <= maxWithdraw(owner), \"ERC4626: withdraw more then max\");\n\n address caller = _msgSender();\n uint256 shares = previewWithdraw(assets);\n\n if (caller != owner) {\n _spendAllowance(owner, caller, shares);\n }\n\n // if _asset is ERC777, transfer can call reenter AFTER the transfer happens through\n // the tokensReceived hook, so we need to transfer after we burn to keep the invariants.\n _burn(owner, shares);\n SafeERC20.safeTransfer(_asset, receiver, assets);\n\n emit Withdraw(caller, receiver, owner, assets, shares);\n\n return shares;\n }\n\n /** @dev See {IERC4262-redeem} */\n function redeem(\n uint256 shares,\n address receiver,\n address owner\n ) public virtual override returns (uint256) {\n require(shares <= maxRedeem(owner), \"ERC4626: redeem more then max\");\n\n address caller = _msgSender();\n uint256 assets = previewRedeem(shares);\n\n if (caller != owner) {\n _spendAllowance(owner, caller, shares);\n }\n\n // if _asset is ERC777, transfer can call reenter AFTER the transfer happens through\n // the tokensReceived hook, so we need to transfer after we burn to keep the invariants.\n _burn(owner, shares);\n SafeERC20.safeTransfer(_asset, receiver, assets);\n\n emit Withdraw(caller, receiver, owner, assets, shares);\n\n return assets;\n }\n\n // Included here, since this method was not yet present in\n // the version of Open Zeppelin ERC20 code we use.\n function _spendAllowance(\n address owner,\n address spender,\n uint256 amount\n ) internal virtual {\n uint256 currentAllowance = allowance(owner, spender);\n if (currentAllowance != type(uint256).max) {\n require(currentAllowance >= amount, \"ERC20: insufficient allowance\");\n unchecked {\n _approve(owner, spender, currentAllowance - amount);\n }\n }\n }\n}" + }, + "lib/openzeppelin/interfaces/IERC4626.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\nimport { IERC20Metadata } from \"@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol\";\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\n\ninterface IERC4626 is IERC20, IERC20Metadata {\n event Deposit(address indexed caller, address indexed owner, uint256 assets, uint256 shares);\n\n event Withdraw(\n address indexed caller,\n address indexed receiver,\n address indexed owner,\n uint256 assets,\n uint256 shares\n );\n\n /**\n * @dev Returns the address of the underlying token used for the Vault for accounting, depositing, and withdrawing.\n *\n * - MUST be an ERC-20 token contract.\n * - MUST NOT revert.\n */\n function asset() external view returns (address assetTokenAddress);\n\n /**\n * @dev Returns the total amount of the underlying asset that is “managed” by Vault.\n *\n * - SHOULD include any compounding that occurs from yield.\n * - MUST be inclusive of any fees that are charged against assets in the Vault.\n * - MUST NOT revert.\n */\n function totalAssets() external view returns (uint256 totalManagedAssets);\n\n /**\n * @dev Returns the amount of shares that the Vault would exchange for the amount of assets provided, in an ideal\n * scenario where all the conditions are met.\n *\n * - MUST NOT be inclusive of any fees that are charged against assets in the Vault.\n * - MUST NOT show any variations depending on the caller.\n * - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange.\n * - MUST NOT revert.\n *\n * NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the\n * “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and\n * from.\n */\n function convertToShares(uint256 assets) external view returns (uint256 shares);\n\n /**\n * @dev Returns the amount of assets that the Vault would exchange for the amount of shares provided, in an ideal\n * scenario where all the conditions are met.\n *\n * - MUST NOT be inclusive of any fees that are charged against assets in the Vault.\n * - MUST NOT show any variations depending on the caller.\n * - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange.\n * - MUST NOT revert.\n *\n * NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the\n * “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and\n * from.\n */\n function convertToAssets(uint256 shares) external view returns (uint256 assets);\n\n /**\n * @dev Returns the maximum amount of the underlying asset that can be deposited into the Vault for the receiver,\n * through a deposit call.\n *\n * - MUST return a limited value if receiver is subject to some deposit limit.\n * - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of assets that may be deposited.\n * - MUST NOT revert.\n */\n function maxDeposit(address receiver) external view returns (uint256 maxAssets);\n\n /**\n * @dev Allows an on-chain or off-chain user to simulate the effects of their deposit at the current block, given\n * current on-chain conditions.\n *\n * - MUST return as close to and no more than the exact amount of Vault shares that would be minted in a deposit\n * call in the same transaction. I.e. deposit should return the same or more shares as previewDeposit if called\n * in the same transaction.\n * - MUST NOT account for deposit limits like those returned from maxDeposit and should always act as though the\n * deposit would be accepted, regardless if the user has enough tokens approved, etc.\n * - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees.\n * - MUST NOT revert.\n *\n * NOTE: any unfavorable discrepancy between convertToShares and previewDeposit SHOULD be considered slippage in\n * share price or some other type of condition, meaning the depositor will lose assets by depositing.\n */\n function previewDeposit(uint256 assets) external view returns (uint256 shares);\n\n /**\n * @dev Mints shares Vault shares to receiver by depositing exactly amount of underlying tokens.\n *\n * - MUST emit the Deposit event.\n * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the\n * deposit execution, and are accounted for during deposit.\n * - MUST revert if all of assets cannot be deposited (due to deposit limit being reached, slippage, the user not\n * approving enough underlying tokens to the Vault contract, etc).\n *\n * NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token.\n */\n function deposit(uint256 assets, address receiver) external returns (uint256 shares);\n\n /**\n * @dev Returns the maximum amount of the Vault shares that can be minted for the receiver, through a mint call.\n * - MUST return a limited value if receiver is subject to some mint limit.\n * - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of shares that may be minted.\n * - MUST NOT revert.\n */\n function maxMint(address receiver) external view returns (uint256 maxShares);\n\n /**\n * @dev Allows an on-chain or off-chain user to simulate the effects of their mint at the current block, given\n * current on-chain conditions.\n *\n * - MUST return as close to and no fewer than the exact amount of assets that would be deposited in a mint call\n * in the same transaction. I.e. mint should return the same or fewer assets as previewMint if called in the\n * same transaction.\n * - MUST NOT account for mint limits like those returned from maxMint and should always act as though the mint\n * would be accepted, regardless if the user has enough tokens approved, etc.\n * - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees.\n * - MUST NOT revert.\n *\n * NOTE: any unfavorable discrepancy between convertToAssets and previewMint SHOULD be considered slippage in\n * share price or some other type of condition, meaning the depositor will lose assets by minting.\n */\n function previewMint(uint256 shares) external view returns (uint256 assets);\n\n /**\n * @dev Mints exactly shares Vault shares to receiver by depositing amount of underlying tokens.\n *\n * - MUST emit the Deposit event.\n * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the mint\n * execution, and are accounted for during mint.\n * - MUST revert if all of shares cannot be minted (due to deposit limit being reached, slippage, the user not\n * approving enough underlying tokens to the Vault contract, etc).\n *\n * NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token.\n */\n function mint(uint256 shares, address receiver) external returns (uint256 assets);\n\n /**\n * @dev Returns the maximum amount of the underlying asset that can be withdrawn from the owner balance in the\n * Vault, through a withdraw call.\n *\n * - MUST return a limited value if owner is subject to some withdrawal limit or timelock.\n * - MUST NOT revert.\n */\n function maxWithdraw(address owner) external view returns (uint256 maxAssets);\n\n /**\n * @dev Allows an on-chain or off-chain user to simulate the effects of their withdrawal at the current block,\n * given current on-chain conditions.\n *\n * - MUST return as close to and no fewer than the exact amount of Vault shares that would be burned in a withdraw\n * call in the same transaction. I.e. withdraw should return the same or fewer shares as previewWithdraw if\n * called\n * in the same transaction.\n * - MUST NOT account for withdrawal limits like those returned from maxWithdraw and should always act as though\n * the withdrawal would be accepted, regardless if the user has enough shares, etc.\n * - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees.\n * - MUST NOT revert.\n *\n * NOTE: any unfavorable discrepancy between convertToShares and previewWithdraw SHOULD be considered slippage in\n * share price or some other type of condition, meaning the depositor will lose assets by depositing.\n */\n function previewWithdraw(uint256 assets) external view returns (uint256 shares);\n\n /**\n * @dev Burns shares from owner and sends exactly assets of underlying tokens to receiver.\n *\n * - MUST emit the Withdraw event.\n * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the\n * withdraw execution, and are accounted for during withdraw.\n * - MUST revert if all of assets cannot be withdrawn (due to withdrawal limit being reached, slippage, the owner\n * not having enough shares, etc).\n *\n * Note that some implementations will require pre-requesting to the Vault before a withdrawal may be performed.\n * Those methods should be performed separately.\n */\n function withdraw(\n uint256 assets,\n address receiver,\n address owner\n ) external returns (uint256 shares);\n\n /**\n * @dev Returns the maximum amount of Vault shares that can be redeemed from the owner balance in the Vault,\n * through a redeem call.\n *\n * - MUST return a limited value if owner is subject to some withdrawal limit or timelock.\n * - MUST return balanceOf(owner) if owner is not subject to any withdrawal limit or timelock.\n * - MUST NOT revert.\n */\n function maxRedeem(address owner) external view returns (uint256 maxShares);\n\n /**\n * @dev Allows an on-chain or off-chain user to simulate the effects of their redeemption at the current block,\n * given current on-chain conditions.\n *\n * - MUST return as close to and no more than the exact amount of assets that would be withdrawn in a redeem call\n * in the same transaction. I.e. redeem should return the same or more assets as previewRedeem if called in the\n * same transaction.\n * - MUST NOT account for redemption limits like those returned from maxRedeem and should always act as though the\n * redemption would be accepted, regardless if the user has enough shares, etc.\n * - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees.\n * - MUST NOT revert.\n *\n * NOTE: any unfavorable discrepancy between convertToAssets and previewRedeem SHOULD be considered slippage in\n * share price or some other type of condition, meaning the depositor will lose assets by redeeming.\n */\n function previewRedeem(uint256 shares) external view returns (uint256 assets);\n\n /**\n * @dev Burns exactly shares from owner and sends assets of underlying tokens to receiver.\n *\n * - MUST emit the Withdraw event.\n * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the\n * redeem execution, and are accounted for during redeem.\n * - MUST revert if all of shares cannot be redeemed (due to withdrawal limit being reached, slippage, the owner\n * not having enough shares, etc).\n *\n * NOTE: some implementations will require pre-requesting to the Vault before a withdrawal may be performed.\n * Those methods should be performed separately.\n */\n function redeem(\n uint256 shares,\n address receiver,\n address owner\n ) external returns (uint256 assets);\n}" + }, + "lib/rooster/openzeppelin-custom/contracts/utils/math/Math.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.1.0) (utils/math/Math.sol)\n\npragma solidity ^0.8.20;\n\nimport {Panic} from \"../Panic.sol\";\nimport {SafeCast} from \"./SafeCast.sol\";\n\n/**\n * @dev Standard math utilities missing in the Solidity language.\n */\nlibrary Math {\n enum Rounding {\n Floor, // Toward negative infinity\n Ceil, // Toward positive infinity\n Trunc, // Toward zero\n Expand // Away from zero\n }\n\n /**\n * @dev Return the 512-bit addition of two uint256.\n *\n * The result is stored in two 256 variables such that sum = high * 2²⁵⁶ + low.\n */\n function add512(uint256 a, uint256 b) internal pure returns (uint256 high, uint256 low) {\n assembly (\"memory-safe\") {\n low := add(a, b)\n high := lt(low, a)\n }\n }\n\n /**\n * @dev Return the 512-bit multiplication of two uint256.\n *\n * The result is stored in two 256 variables such that product = high * 2²⁵⁶ + low.\n */\n function mul512(uint256 a, uint256 b) internal pure returns (uint256 high, uint256 low) {\n // 512-bit multiply [high low] = x * y. Compute the product mod 2²⁵⁶ and mod 2²⁵⁶ - 1, then use\n // the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256\n // variables such that product = high * 2²⁵⁶ + low.\n assembly (\"memory-safe\") {\n let mm := mulmod(a, b, not(0))\n low := mul(a, b)\n high := sub(sub(mm, low), lt(mm, low))\n }\n }\n\n /**\n * @dev Returns the addition of two unsigned integers, with a success flag (no overflow).\n */\n function tryAdd(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {\n unchecked {\n uint256 c = a + b;\n success = c >= a;\n result = c * SafeCast.toUint(success);\n }\n }\n\n /**\n * @dev Returns the subtraction of two unsigned integers, with a success flag (no overflow).\n */\n function trySub(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {\n unchecked {\n uint256 c = a - b;\n success = c <= a;\n result = c * SafeCast.toUint(success);\n }\n }\n\n /**\n * @dev Returns the multiplication of two unsigned integers, with a success flag (no overflow).\n */\n function tryMul(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {\n unchecked {\n uint256 c = a * b;\n assembly (\"memory-safe\") {\n // Only true when the multiplication doesn't overflow\n // (c / a == b) || (a == 0)\n success := or(eq(div(c, a), b), iszero(a))\n }\n // equivalent to: success ? c : 0\n result = c * SafeCast.toUint(success);\n }\n }\n\n /**\n * @dev Returns the division of two unsigned integers, with a success flag (no division by zero).\n */\n function tryDiv(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {\n unchecked {\n success = b > 0;\n assembly (\"memory-safe\") {\n // The `DIV` opcode returns zero when the denominator is 0.\n result := div(a, b)\n }\n }\n }\n\n /**\n * @dev Returns the remainder of dividing two unsigned integers, with a success flag (no division by zero).\n */\n function tryMod(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {\n unchecked {\n success = b > 0;\n assembly (\"memory-safe\") {\n // The `MOD` opcode returns zero when the denominator is 0.\n result := mod(a, b)\n }\n }\n }\n\n /**\n * @dev Unsigned saturating addition, bounds to `2²⁵⁶ - 1` instead of overflowing.\n */\n function saturatingAdd(uint256 a, uint256 b) internal pure returns (uint256) {\n (bool success, uint256 result) = tryAdd(a, b);\n return ternary(success, result, type(uint256).max);\n }\n\n /**\n * @dev Unsigned saturating subtraction, bounds to zero instead of overflowing.\n */\n function saturatingSub(uint256 a, uint256 b) internal pure returns (uint256) {\n (, uint256 result) = trySub(a, b);\n return result;\n }\n\n /**\n * @dev Unsigned saturating multiplication, bounds to `2²⁵⁶ - 1` instead of overflowing.\n */\n function saturatingMul(uint256 a, uint256 b) internal pure returns (uint256) {\n (bool success, uint256 result) = tryMul(a, b);\n return ternary(success, result, type(uint256).max);\n }\n\n /**\n * @dev Branchless ternary evaluation for `a ? b : c`. Gas costs are constant.\n *\n * IMPORTANT: This function may reduce bytecode size and consume less gas when used standalone.\n * However, the compiler may optimize Solidity ternary operations (i.e. `a ? b : c`) to only compute\n * one branch when needed, making this function more expensive.\n */\n function ternary(bool condition, uint256 a, uint256 b) internal pure returns (uint256) {\n unchecked {\n // branchless ternary works because:\n // b ^ (a ^ b) == a\n // b ^ 0 == b\n return b ^ ((a ^ b) * SafeCast.toUint(condition));\n }\n }\n\n /**\n * @dev Returns the largest of two numbers.\n */\n function max(uint256 a, uint256 b) internal pure returns (uint256) {\n return ternary(a > b, a, b);\n }\n\n /**\n * @dev Returns the smallest of two numbers.\n */\n function min(uint256 a, uint256 b) internal pure returns (uint256) {\n return ternary(a < b, a, b);\n }\n\n /**\n * @dev Returns the average of two numbers. The result is rounded towards\n * zero.\n */\n function average(uint256 a, uint256 b) internal pure returns (uint256) {\n // (a + b) / 2 can overflow.\n return (a & b) + (a ^ b) / 2;\n }\n\n /**\n * @dev Returns the ceiling of the division of two numbers.\n *\n * This differs from standard division with `/` in that it rounds towards infinity instead\n * of rounding towards zero.\n */\n function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {\n if (b == 0) {\n // Guarantee the same behavior as in a regular Solidity division.\n Panic.panic(Panic.DIVISION_BY_ZERO);\n }\n\n // The following calculation ensures accurate ceiling division without overflow.\n // Since a is non-zero, (a - 1) / b will not overflow.\n // The largest possible result occurs when (a - 1) / b is type(uint256).max,\n // but the largest value we can obtain is type(uint256).max - 1, which happens\n // when a = type(uint256).max and b = 1.\n unchecked {\n return SafeCast.toUint(a > 0) * ((a - 1) / b + 1);\n }\n }\n\n /**\n * @dev Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or\n * denominator == 0.\n *\n * Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv) with further edits by\n * Uniswap Labs also under MIT license.\n */\n function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {\n unchecked {\n (uint256 high, uint256 low) = mul512(x, y);\n\n // Handle non-overflow cases, 256 by 256 division.\n if (high == 0) {\n // Solidity will revert if denominator == 0, unlike the div opcode on its own.\n // The surrounding unchecked block does not change this fact.\n // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.\n return low / denominator;\n }\n\n // Make sure the result is less than 2²⁵⁶. Also prevents denominator == 0.\n if (denominator <= high) {\n Panic.panic(ternary(denominator == 0, Panic.DIVISION_BY_ZERO, Panic.UNDER_OVERFLOW));\n }\n\n ///////////////////////////////////////////////\n // 512 by 256 division.\n ///////////////////////////////////////////////\n\n // Make division exact by subtracting the remainder from [high low].\n uint256 remainder;\n assembly (\"memory-safe\") {\n // Compute remainder using mulmod.\n remainder := mulmod(x, y, denominator)\n\n // Subtract 256 bit number from 512 bit number.\n high := sub(high, gt(remainder, low))\n low := sub(low, remainder)\n }\n\n // Factor powers of two out of denominator and compute largest power of two divisor of denominator.\n // Always >= 1. See https://cs.stackexchange.com/q/138556/92363.\n\n uint256 twos = denominator & (0 - denominator);\n assembly (\"memory-safe\") {\n // Divide denominator by twos.\n denominator := div(denominator, twos)\n\n // Divide [high low] by twos.\n low := div(low, twos)\n\n // Flip twos such that it is 2²⁵⁶ / twos. If twos is zero, then it becomes one.\n twos := add(div(sub(0, twos), twos), 1)\n }\n\n // Shift in bits from high into low.\n low |= high * twos;\n\n // Invert denominator mod 2²⁵⁶. Now that denominator is an odd number, it has an inverse modulo 2²⁵⁶ such\n // that denominator * inv ≡ 1 mod 2²⁵⁶. Compute the inverse by starting with a seed that is correct for\n // four bits. That is, denominator * inv ≡ 1 mod 2⁴.\n uint256 inverse = (3 * denominator) ^ 2;\n\n // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also\n // works in modular arithmetic, doubling the correct bits in each step.\n inverse *= 2 - denominator * inverse; // inverse mod 2⁸\n inverse *= 2 - denominator * inverse; // inverse mod 2¹⁶\n inverse *= 2 - denominator * inverse; // inverse mod 2³²\n inverse *= 2 - denominator * inverse; // inverse mod 2⁶⁴\n inverse *= 2 - denominator * inverse; // inverse mod 2¹²⁸\n inverse *= 2 - denominator * inverse; // inverse mod 2²⁵⁶\n\n // Because the division is now exact we can divide by multiplying with the modular inverse of denominator.\n // This will give us the correct result modulo 2²⁵⁶. Since the preconditions guarantee that the outcome is\n // less than 2²⁵⁶, this is the final result. We don't need to compute the high bits of the result and high\n // is no longer required.\n result = low * inverse;\n return result;\n }\n }\n\n /**\n * @dev Calculates x * y / denominator with full precision, following the selected rounding direction.\n */\n function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {\n return mulDiv(x, y, denominator) + SafeCast.toUint(unsignedRoundsUp(rounding) && mulmod(x, y, denominator) > 0);\n }\n\n /**\n * @dev Calculates floor(x * y >> n) with full precision. Throws if result overflows a uint256.\n */\n function mulShr(uint256 x, uint256 y, uint8 n) internal pure returns (uint256 result) {\n unchecked {\n (uint256 high, uint256 low) = mul512(x, y);\n if (high >= 1 << n) {\n Panic.panic(Panic.UNDER_OVERFLOW);\n }\n return (high << (256 - n)) | (low >> n);\n }\n }\n\n /**\n * @dev Calculates x * y >> n with full precision, following the selected rounding direction.\n */\n function mulShr(uint256 x, uint256 y, uint8 n, Rounding rounding) internal pure returns (uint256) {\n return mulShr(x, y, n) + SafeCast.toUint(unsignedRoundsUp(rounding) && mulmod(x, y, 1 << n) > 0);\n }\n\n /**\n * @dev Calculate the modular multiplicative inverse of a number in Z/nZ.\n *\n * If n is a prime, then Z/nZ is a field. In that case all elements are inversible, except 0.\n * If n is not a prime, then Z/nZ is not a field, and some elements might not be inversible.\n *\n * If the input value is not inversible, 0 is returned.\n *\n * NOTE: If you know for sure that n is (big) a prime, it may be cheaper to use Fermat's little theorem and get the\n * inverse using `Math.modExp(a, n - 2, n)`. See {invModPrime}.\n */\n function invMod(uint256 a, uint256 n) internal pure returns (uint256) {\n unchecked {\n if (n == 0) return 0;\n\n // The inverse modulo is calculated using the Extended Euclidean Algorithm (iterative version)\n // Used to compute integers x and y such that: ax + ny = gcd(a, n).\n // When the gcd is 1, then the inverse of a modulo n exists and it's x.\n // ax + ny = 1\n // ax = 1 + (-y)n\n // ax ≡ 1 (mod n) # x is the inverse of a modulo n\n\n // If the remainder is 0 the gcd is n right away.\n uint256 remainder = a % n;\n uint256 gcd = n;\n\n // Therefore the initial coefficients are:\n // ax + ny = gcd(a, n) = n\n // 0a + 1n = n\n int256 x = 0;\n int256 y = 1;\n\n while (remainder != 0) {\n uint256 quotient = gcd / remainder;\n\n (gcd, remainder) = (\n // The old remainder is the next gcd to try.\n remainder,\n // Compute the next remainder.\n // Can't overflow given that (a % gcd) * (gcd // (a % gcd)) <= gcd\n // where gcd is at most n (capped to type(uint256).max)\n gcd - remainder * quotient\n );\n\n (x, y) = (\n // Increment the coefficient of a.\n y,\n // Decrement the coefficient of n.\n // Can overflow, but the result is casted to uint256 so that the\n // next value of y is \"wrapped around\" to a value between 0 and n - 1.\n x - y * int256(quotient)\n );\n }\n\n if (gcd != 1) return 0; // No inverse exists.\n return ternary(x < 0, n - uint256(-x), uint256(x)); // Wrap the result if it's negative.\n }\n }\n\n /**\n * @dev Variant of {invMod}. More efficient, but only works if `p` is known to be a prime greater than `2`.\n *\n * From https://en.wikipedia.org/wiki/Fermat%27s_little_theorem[Fermat's little theorem], we know that if p is\n * prime, then `a**(p-1) ≡ 1 mod p`. As a consequence, we have `a * a**(p-2) ≡ 1 mod p`, which means that\n * `a**(p-2)` is the modular multiplicative inverse of a in Fp.\n *\n * NOTE: this function does NOT check that `p` is a prime greater than `2`.\n */\n function invModPrime(uint256 a, uint256 p) internal view returns (uint256) {\n unchecked {\n return Math.modExp(a, p - 2, p);\n }\n }\n\n /**\n * @dev Returns the modular exponentiation of the specified base, exponent and modulus (b ** e % m)\n *\n * Requirements:\n * - modulus can't be zero\n * - underlying staticcall to precompile must succeed\n *\n * IMPORTANT: The result is only valid if the underlying call succeeds. When using this function, make\n * sure the chain you're using it on supports the precompiled contract for modular exponentiation\n * at address 0x05 as specified in https://eips.ethereum.org/EIPS/eip-198[EIP-198]. Otherwise,\n * the underlying function will succeed given the lack of a revert, but the result may be incorrectly\n * interpreted as 0.\n */\n function modExp(uint256 b, uint256 e, uint256 m) internal view returns (uint256) {\n (bool success, uint256 result) = tryModExp(b, e, m);\n if (!success) {\n Panic.panic(Panic.DIVISION_BY_ZERO);\n }\n return result;\n }\n\n /**\n * @dev Returns the modular exponentiation of the specified base, exponent and modulus (b ** e % m).\n * It includes a success flag indicating if the operation succeeded. Operation will be marked as failed if trying\n * to operate modulo 0 or if the underlying precompile reverted.\n *\n * IMPORTANT: The result is only valid if the success flag is true. When using this function, make sure the chain\n * you're using it on supports the precompiled contract for modular exponentiation at address 0x05 as specified in\n * https://eips.ethereum.org/EIPS/eip-198[EIP-198]. Otherwise, the underlying function will succeed given the lack\n * of a revert, but the result may be incorrectly interpreted as 0.\n */\n function tryModExp(uint256 b, uint256 e, uint256 m) internal view returns (bool success, uint256 result) {\n if (m == 0) return (false, 0);\n assembly (\"memory-safe\") {\n let ptr := mload(0x40)\n // | Offset | Content | Content (Hex) |\n // |-----------|------------|--------------------------------------------------------------------|\n // | 0x00:0x1f | size of b | 0x0000000000000000000000000000000000000000000000000000000000000020 |\n // | 0x20:0x3f | size of e | 0x0000000000000000000000000000000000000000000000000000000000000020 |\n // | 0x40:0x5f | size of m | 0x0000000000000000000000000000000000000000000000000000000000000020 |\n // | 0x60:0x7f | value of b | 0x<.............................................................b> |\n // | 0x80:0x9f | value of e | 0x<.............................................................e> |\n // | 0xa0:0xbf | value of m | 0x<.............................................................m> |\n mstore(ptr, 0x20)\n mstore(add(ptr, 0x20), 0x20)\n mstore(add(ptr, 0x40), 0x20)\n mstore(add(ptr, 0x60), b)\n mstore(add(ptr, 0x80), e)\n mstore(add(ptr, 0xa0), m)\n\n // Given the result < m, it's guaranteed to fit in 32 bytes,\n // so we can use the memory scratch space located at offset 0.\n success := staticcall(gas(), 0x05, ptr, 0xc0, 0x00, 0x20)\n result := mload(0x00)\n }\n }\n\n /**\n * @dev Variant of {modExp} that supports inputs of arbitrary length.\n */\n function modExp(bytes memory b, bytes memory e, bytes memory m) internal view returns (bytes memory) {\n (bool success, bytes memory result) = tryModExp(b, e, m);\n if (!success) {\n Panic.panic(Panic.DIVISION_BY_ZERO);\n }\n return result;\n }\n\n /**\n * @dev Variant of {tryModExp} that supports inputs of arbitrary length.\n */\n function tryModExp(\n bytes memory b,\n bytes memory e,\n bytes memory m\n ) internal view returns (bool success, bytes memory result) {\n if (_zeroBytes(m)) return (false, new bytes(0));\n\n uint256 mLen = m.length;\n\n // Encode call args in result and move the free memory pointer\n result = abi.encodePacked(b.length, e.length, mLen, b, e, m);\n\n assembly (\"memory-safe\") {\n let dataPtr := add(result, 0x20)\n // Write result on top of args to avoid allocating extra memory.\n success := staticcall(gas(), 0x05, dataPtr, mload(result), dataPtr, mLen)\n // Overwrite the length.\n // result.length > returndatasize() is guaranteed because returndatasize() == m.length\n mstore(result, mLen)\n // Set the memory pointer after the returned data.\n mstore(0x40, add(dataPtr, mLen))\n }\n }\n\n /**\n * @dev Returns whether the provided byte array is zero.\n */\n function _zeroBytes(bytes memory byteArray) private pure returns (bool) {\n for (uint256 i = 0; i < byteArray.length; ++i) {\n if (byteArray[i] != 0) {\n return false;\n }\n }\n return true;\n }\n\n /**\n * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded\n * towards zero.\n *\n * This method is based on Newton's method for computing square roots; the algorithm is restricted to only\n * using integer operations.\n */\n function sqrt(uint256 a) internal pure returns (uint256) {\n unchecked {\n // Take care of easy edge cases when a == 0 or a == 1\n if (a <= 1) {\n return a;\n }\n\n // In this function, we use Newton's method to get a root of `f(x) := x² - a`. It involves building a\n // sequence x_n that converges toward sqrt(a). For each iteration x_n, we also define the error between\n // the current value as `ε_n = | x_n - sqrt(a) |`.\n //\n // For our first estimation, we consider `e` the smallest power of 2 which is bigger than the square root\n // of the target. (i.e. `2**(e-1) ≤ sqrt(a) < 2**e`). We know that `e ≤ 128` because `(2¹²⁸)² = 2²⁵⁶` is\n // bigger than any uint256.\n //\n // By noticing that\n // `2**(e-1) ≤ sqrt(a) < 2**e → (2**(e-1))² ≤ a < (2**e)² → 2**(2*e-2) ≤ a < 2**(2*e)`\n // we can deduce that `e - 1` is `log2(a) / 2`. We can thus compute `x_n = 2**(e-1)` using a method similar\n // to the msb function.\n uint256 aa = a;\n uint256 xn = 1;\n\n if (aa >= (1 << 128)) {\n aa >>= 128;\n xn <<= 64;\n }\n if (aa >= (1 << 64)) {\n aa >>= 64;\n xn <<= 32;\n }\n if (aa >= (1 << 32)) {\n aa >>= 32;\n xn <<= 16;\n }\n if (aa >= (1 << 16)) {\n aa >>= 16;\n xn <<= 8;\n }\n if (aa >= (1 << 8)) {\n aa >>= 8;\n xn <<= 4;\n }\n if (aa >= (1 << 4)) {\n aa >>= 4;\n xn <<= 2;\n }\n if (aa >= (1 << 2)) {\n xn <<= 1;\n }\n\n // We now have x_n such that `x_n = 2**(e-1) ≤ sqrt(a) < 2**e = 2 * x_n`. This implies ε_n ≤ 2**(e-1).\n //\n // We can refine our estimation by noticing that the middle of that interval minimizes the error.\n // If we move x_n to equal 2**(e-1) + 2**(e-2), then we reduce the error to ε_n ≤ 2**(e-2).\n // This is going to be our x_0 (and ε_0)\n xn = (3 * xn) >> 1; // ε_0 := | x_0 - sqrt(a) | ≤ 2**(e-2)\n\n // From here, Newton's method give us:\n // x_{n+1} = (x_n + a / x_n) / 2\n //\n // One should note that:\n // x_{n+1}² - a = ((x_n + a / x_n) / 2)² - a\n // = ((x_n² + a) / (2 * x_n))² - a\n // = (x_n⁴ + 2 * a * x_n² + a²) / (4 * x_n²) - a\n // = (x_n⁴ + 2 * a * x_n² + a² - 4 * a * x_n²) / (4 * x_n²)\n // = (x_n⁴ - 2 * a * x_n² + a²) / (4 * x_n²)\n // = (x_n² - a)² / (2 * x_n)²\n // = ((x_n² - a) / (2 * x_n))²\n // ≥ 0\n // Which proves that for all n ≥ 1, sqrt(a) ≤ x_n\n //\n // This gives us the proof of quadratic convergence of the sequence:\n // ε_{n+1} = | x_{n+1} - sqrt(a) |\n // = | (x_n + a / x_n) / 2 - sqrt(a) |\n // = | (x_n² + a - 2*x_n*sqrt(a)) / (2 * x_n) |\n // = | (x_n - sqrt(a))² / (2 * x_n) |\n // = | ε_n² / (2 * x_n) |\n // = ε_n² / | (2 * x_n) |\n //\n // For the first iteration, we have a special case where x_0 is known:\n // ε_1 = ε_0² / | (2 * x_0) |\n // ≤ (2**(e-2))² / (2 * (2**(e-1) + 2**(e-2)))\n // ≤ 2**(2*e-4) / (3 * 2**(e-1))\n // ≤ 2**(e-3) / 3\n // ≤ 2**(e-3-log2(3))\n // ≤ 2**(e-4.5)\n //\n // For the following iterations, we use the fact that, 2**(e-1) ≤ sqrt(a) ≤ x_n:\n // ε_{n+1} = ε_n² / | (2 * x_n) |\n // ≤ (2**(e-k))² / (2 * 2**(e-1))\n // ≤ 2**(2*e-2*k) / 2**e\n // ≤ 2**(e-2*k)\n xn = (xn + a / xn) >> 1; // ε_1 := | x_1 - sqrt(a) | ≤ 2**(e-4.5) -- special case, see above\n xn = (xn + a / xn) >> 1; // ε_2 := | x_2 - sqrt(a) | ≤ 2**(e-9) -- general case with k = 4.5\n xn = (xn + a / xn) >> 1; // ε_3 := | x_3 - sqrt(a) | ≤ 2**(e-18) -- general case with k = 9\n xn = (xn + a / xn) >> 1; // ε_4 := | x_4 - sqrt(a) | ≤ 2**(e-36) -- general case with k = 18\n xn = (xn + a / xn) >> 1; // ε_5 := | x_5 - sqrt(a) | ≤ 2**(e-72) -- general case with k = 36\n xn = (xn + a / xn) >> 1; // ε_6 := | x_6 - sqrt(a) | ≤ 2**(e-144) -- general case with k = 72\n\n // Because e ≤ 128 (as discussed during the first estimation phase), we know have reached a precision\n // ε_6 ≤ 2**(e-144) < 1. Given we're operating on integers, then we can ensure that xn is now either\n // sqrt(a) or sqrt(a) + 1.\n return xn - SafeCast.toUint(xn > a / xn);\n }\n }\n\n /**\n * @dev Calculates sqrt(a), following the selected rounding direction.\n */\n function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {\n unchecked {\n uint256 result = sqrt(a);\n return result + SafeCast.toUint(unsignedRoundsUp(rounding) && result * result < a);\n }\n }\n\n /**\n * @dev Return the log in base 2 of a positive value rounded towards zero.\n * Returns 0 if given 0.\n */\n function log2(uint256 x) internal pure returns (uint256 r) {\n // If value has upper 128 bits set, log2 result is at least 128\n r = SafeCast.toUint(x > 0xffffffffffffffffffffffffffffffff) << 7;\n // If upper 64 bits of 128-bit half set, add 64 to result\n r |= SafeCast.toUint((x >> r) > 0xffffffffffffffff) << 6;\n // If upper 32 bits of 64-bit half set, add 32 to result\n r |= SafeCast.toUint((x >> r) > 0xffffffff) << 5;\n // If upper 16 bits of 32-bit half set, add 16 to result\n r |= SafeCast.toUint((x >> r) > 0xffff) << 4;\n // If upper 8 bits of 16-bit half set, add 8 to result\n r |= SafeCast.toUint((x >> r) > 0xff) << 3;\n // If upper 4 bits of 8-bit half set, add 4 to result\n r |= SafeCast.toUint((x >> r) > 0xf) << 2;\n\n // Shifts value right by the current result and use it as an index into this lookup table:\n //\n // | x (4 bits) | index | table[index] = MSB position |\n // |------------|---------|-----------------------------|\n // | 0000 | 0 | table[0] = 0 |\n // | 0001 | 1 | table[1] = 0 |\n // | 0010 | 2 | table[2] = 1 |\n // | 0011 | 3 | table[3] = 1 |\n // | 0100 | 4 | table[4] = 2 |\n // | 0101 | 5 | table[5] = 2 |\n // | 0110 | 6 | table[6] = 2 |\n // | 0111 | 7 | table[7] = 2 |\n // | 1000 | 8 | table[8] = 3 |\n // | 1001 | 9 | table[9] = 3 |\n // | 1010 | 10 | table[10] = 3 |\n // | 1011 | 11 | table[11] = 3 |\n // | 1100 | 12 | table[12] = 3 |\n // | 1101 | 13 | table[13] = 3 |\n // | 1110 | 14 | table[14] = 3 |\n // | 1111 | 15 | table[15] = 3 |\n //\n // The lookup table is represented as a 32-byte value with the MSB positions for 0-15 in the last 16 bytes.\n assembly (\"memory-safe\") {\n r := or(r, byte(shr(r, x), 0x0000010102020202030303030303030300000000000000000000000000000000))\n }\n }\n\n /**\n * @dev Return the log in base 2, following the selected rounding direction, of a positive value.\n * Returns 0 if given 0.\n */\n function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {\n unchecked {\n uint256 result = log2(value);\n return result + SafeCast.toUint(unsignedRoundsUp(rounding) && 1 << result < value);\n }\n }\n\n /**\n * @dev Return the log in base 10 of a positive value rounded towards zero.\n * Returns 0 if given 0.\n */\n function log10(uint256 value) internal pure returns (uint256) {\n uint256 result = 0;\n unchecked {\n if (value >= 10 ** 64) {\n value /= 10 ** 64;\n result += 64;\n }\n if (value >= 10 ** 32) {\n value /= 10 ** 32;\n result += 32;\n }\n if (value >= 10 ** 16) {\n value /= 10 ** 16;\n result += 16;\n }\n if (value >= 10 ** 8) {\n value /= 10 ** 8;\n result += 8;\n }\n if (value >= 10 ** 4) {\n value /= 10 ** 4;\n result += 4;\n }\n if (value >= 10 ** 2) {\n value /= 10 ** 2;\n result += 2;\n }\n if (value >= 10 ** 1) {\n result += 1;\n }\n }\n return result;\n }\n\n /**\n * @dev Return the log in base 10, following the selected rounding direction, of a positive value.\n * Returns 0 if given 0.\n */\n function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {\n unchecked {\n uint256 result = log10(value);\n return result + SafeCast.toUint(unsignedRoundsUp(rounding) && 10 ** result < value);\n }\n }\n\n /**\n * @dev Return the log in base 256 of a positive value rounded towards zero.\n * Returns 0 if given 0.\n *\n * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.\n */\n function log256(uint256 x) internal pure returns (uint256 r) {\n // If value has upper 128 bits set, log2 result is at least 128\n r = SafeCast.toUint(x > 0xffffffffffffffffffffffffffffffff) << 7;\n // If upper 64 bits of 128-bit half set, add 64 to result\n r |= SafeCast.toUint((x >> r) > 0xffffffffffffffff) << 6;\n // If upper 32 bits of 64-bit half set, add 32 to result\n r |= SafeCast.toUint((x >> r) > 0xffffffff) << 5;\n // If upper 16 bits of 32-bit half set, add 16 to result\n r |= SafeCast.toUint((x >> r) > 0xffff) << 4;\n // Add 1 if upper 8 bits of 16-bit half set, and divide accumulated result by 8\n return (r >> 3) | SafeCast.toUint((x >> r) > 0xff);\n }\n\n /**\n * @dev Return the log in base 256, following the selected rounding direction, of a positive value.\n * Returns 0 if given 0.\n */\n function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {\n unchecked {\n uint256 result = log256(value);\n return result + SafeCast.toUint(unsignedRoundsUp(rounding) && 1 << (result << 3) < value);\n }\n }\n\n /**\n * @dev Returns whether a provided rounding mode is considered rounding up for unsigned integers.\n */\n function unsignedRoundsUp(Rounding rounding) internal pure returns (bool) {\n return uint8(rounding) % 2 == 1;\n }\n}" + }, + "lib/rooster/openzeppelin-custom/contracts/utils/math/SafeCast.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.1.0) (utils/math/SafeCast.sol)\n// This file was procedurally generated from scripts/generate/templates/SafeCast.js.\n\npragma solidity ^0.8.20;\n\n/**\n * @dev Wrappers over Solidity's uintXX/intXX/bool casting operators with added overflow\n * checks.\n *\n * Downcasting from uint256/int256 in Solidity does not revert on overflow. This can\n * easily result in undesired exploitation or bugs, since developers usually\n * assume that overflows raise errors. `SafeCast` restores this intuition by\n * reverting the transaction when such an operation overflows.\n *\n * Using this library instead of the unchecked operations eliminates an entire\n * class of bugs, so it's recommended to use it always.\n */\nlibrary SafeCast {\n /**\n * @dev Value doesn't fit in an uint of `bits` size.\n */\n error SafeCastOverflowedUintDowncast(uint8 bits, uint256 value);\n\n /**\n * @dev An int value doesn't fit in an uint of `bits` size.\n */\n error SafeCastOverflowedIntToUint(int256 value);\n\n /**\n * @dev Value doesn't fit in an int of `bits` size.\n */\n error SafeCastOverflowedIntDowncast(uint8 bits, int256 value);\n\n /**\n * @dev An uint value doesn't fit in an int of `bits` size.\n */\n error SafeCastOverflowedUintToInt(uint256 value);\n\n /**\n * @dev Returns the downcasted uint248 from uint256, reverting on\n * overflow (when the input is greater than largest uint248).\n *\n * Counterpart to Solidity's `uint248` operator.\n *\n * Requirements:\n *\n * - input must fit into 248 bits\n */\n function toUint248(uint256 value) internal pure returns (uint248) {\n if (value > type(uint248).max) {\n revert SafeCastOverflowedUintDowncast(248, value);\n }\n return uint248(value);\n }\n\n /**\n * @dev Returns the downcasted uint240 from uint256, reverting on\n * overflow (when the input is greater than largest uint240).\n *\n * Counterpart to Solidity's `uint240` operator.\n *\n * Requirements:\n *\n * - input must fit into 240 bits\n */\n function toUint240(uint256 value) internal pure returns (uint240) {\n if (value > type(uint240).max) {\n revert SafeCastOverflowedUintDowncast(240, value);\n }\n return uint240(value);\n }\n\n /**\n * @dev Returns the downcasted uint232 from uint256, reverting on\n * overflow (when the input is greater than largest uint232).\n *\n * Counterpart to Solidity's `uint232` operator.\n *\n * Requirements:\n *\n * - input must fit into 232 bits\n */\n function toUint232(uint256 value) internal pure returns (uint232) {\n if (value > type(uint232).max) {\n revert SafeCastOverflowedUintDowncast(232, value);\n }\n return uint232(value);\n }\n\n /**\n * @dev Returns the downcasted uint224 from uint256, reverting on\n * overflow (when the input is greater than largest uint224).\n *\n * Counterpart to Solidity's `uint224` operator.\n *\n * Requirements:\n *\n * - input must fit into 224 bits\n */\n function toUint224(uint256 value) internal pure returns (uint224) {\n if (value > type(uint224).max) {\n revert SafeCastOverflowedUintDowncast(224, value);\n }\n return uint224(value);\n }\n\n /**\n * @dev Returns the downcasted uint216 from uint256, reverting on\n * overflow (when the input is greater than largest uint216).\n *\n * Counterpart to Solidity's `uint216` operator.\n *\n * Requirements:\n *\n * - input must fit into 216 bits\n */\n function toUint216(uint256 value) internal pure returns (uint216) {\n if (value > type(uint216).max) {\n revert SafeCastOverflowedUintDowncast(216, value);\n }\n return uint216(value);\n }\n\n /**\n * @dev Returns the downcasted uint208 from uint256, reverting on\n * overflow (when the input is greater than largest uint208).\n *\n * Counterpart to Solidity's `uint208` operator.\n *\n * Requirements:\n *\n * - input must fit into 208 bits\n */\n function toUint208(uint256 value) internal pure returns (uint208) {\n if (value > type(uint208).max) {\n revert SafeCastOverflowedUintDowncast(208, value);\n }\n return uint208(value);\n }\n\n /**\n * @dev Returns the downcasted uint200 from uint256, reverting on\n * overflow (when the input is greater than largest uint200).\n *\n * Counterpart to Solidity's `uint200` operator.\n *\n * Requirements:\n *\n * - input must fit into 200 bits\n */\n function toUint200(uint256 value) internal pure returns (uint200) {\n if (value > type(uint200).max) {\n revert SafeCastOverflowedUintDowncast(200, value);\n }\n return uint200(value);\n }\n\n /**\n * @dev Returns the downcasted uint192 from uint256, reverting on\n * overflow (when the input is greater than largest uint192).\n *\n * Counterpart to Solidity's `uint192` operator.\n *\n * Requirements:\n *\n * - input must fit into 192 bits\n */\n function toUint192(uint256 value) internal pure returns (uint192) {\n if (value > type(uint192).max) {\n revert SafeCastOverflowedUintDowncast(192, value);\n }\n return uint192(value);\n }\n\n /**\n * @dev Returns the downcasted uint184 from uint256, reverting on\n * overflow (when the input is greater than largest uint184).\n *\n * Counterpart to Solidity's `uint184` operator.\n *\n * Requirements:\n *\n * - input must fit into 184 bits\n */\n function toUint184(uint256 value) internal pure returns (uint184) {\n if (value > type(uint184).max) {\n revert SafeCastOverflowedUintDowncast(184, value);\n }\n return uint184(value);\n }\n\n /**\n * @dev Returns the downcasted uint176 from uint256, reverting on\n * overflow (when the input is greater than largest uint176).\n *\n * Counterpart to Solidity's `uint176` operator.\n *\n * Requirements:\n *\n * - input must fit into 176 bits\n */\n function toUint176(uint256 value) internal pure returns (uint176) {\n if (value > type(uint176).max) {\n revert SafeCastOverflowedUintDowncast(176, value);\n }\n return uint176(value);\n }\n\n /**\n * @dev Returns the downcasted uint168 from uint256, reverting on\n * overflow (when the input is greater than largest uint168).\n *\n * Counterpart to Solidity's `uint168` operator.\n *\n * Requirements:\n *\n * - input must fit into 168 bits\n */\n function toUint168(uint256 value) internal pure returns (uint168) {\n if (value > type(uint168).max) {\n revert SafeCastOverflowedUintDowncast(168, value);\n }\n return uint168(value);\n }\n\n /**\n * @dev Returns the downcasted uint160 from uint256, reverting on\n * overflow (when the input is greater than largest uint160).\n *\n * Counterpart to Solidity's `uint160` operator.\n *\n * Requirements:\n *\n * - input must fit into 160 bits\n */\n function toUint160(uint256 value) internal pure returns (uint160) {\n if (value > type(uint160).max) {\n revert SafeCastOverflowedUintDowncast(160, value);\n }\n return uint160(value);\n }\n\n /**\n * @dev Returns the downcasted uint152 from uint256, reverting on\n * overflow (when the input is greater than largest uint152).\n *\n * Counterpart to Solidity's `uint152` operator.\n *\n * Requirements:\n *\n * - input must fit into 152 bits\n */\n function toUint152(uint256 value) internal pure returns (uint152) {\n if (value > type(uint152).max) {\n revert SafeCastOverflowedUintDowncast(152, value);\n }\n return uint152(value);\n }\n\n /**\n * @dev Returns the downcasted uint144 from uint256, reverting on\n * overflow (when the input is greater than largest uint144).\n *\n * Counterpart to Solidity's `uint144` operator.\n *\n * Requirements:\n *\n * - input must fit into 144 bits\n */\n function toUint144(uint256 value) internal pure returns (uint144) {\n if (value > type(uint144).max) {\n revert SafeCastOverflowedUintDowncast(144, value);\n }\n return uint144(value);\n }\n\n /**\n * @dev Returns the downcasted uint136 from uint256, reverting on\n * overflow (when the input is greater than largest uint136).\n *\n * Counterpart to Solidity's `uint136` operator.\n *\n * Requirements:\n *\n * - input must fit into 136 bits\n */\n function toUint136(uint256 value) internal pure returns (uint136) {\n if (value > type(uint136).max) {\n revert SafeCastOverflowedUintDowncast(136, value);\n }\n return uint136(value);\n }\n\n /**\n * @dev Returns the downcasted uint128 from uint256, reverting on\n * overflow (when the input is greater than largest uint128).\n *\n * Counterpart to Solidity's `uint128` operator.\n *\n * Requirements:\n *\n * - input must fit into 128 bits\n */\n function toUint128(uint256 value) internal pure returns (uint128) {\n if (value > type(uint128).max) {\n revert SafeCastOverflowedUintDowncast(128, value);\n }\n return uint128(value);\n }\n\n /**\n * @dev Returns the downcasted uint120 from uint256, reverting on\n * overflow (when the input is greater than largest uint120).\n *\n * Counterpart to Solidity's `uint120` operator.\n *\n * Requirements:\n *\n * - input must fit into 120 bits\n */\n function toUint120(uint256 value) internal pure returns (uint120) {\n if (value > type(uint120).max) {\n revert SafeCastOverflowedUintDowncast(120, value);\n }\n return uint120(value);\n }\n\n /**\n * @dev Returns the downcasted uint112 from uint256, reverting on\n * overflow (when the input is greater than largest uint112).\n *\n * Counterpart to Solidity's `uint112` operator.\n *\n * Requirements:\n *\n * - input must fit into 112 bits\n */\n function toUint112(uint256 value) internal pure returns (uint112) {\n if (value > type(uint112).max) {\n revert SafeCastOverflowedUintDowncast(112, value);\n }\n return uint112(value);\n }\n\n /**\n * @dev Returns the downcasted uint104 from uint256, reverting on\n * overflow (when the input is greater than largest uint104).\n *\n * Counterpart to Solidity's `uint104` operator.\n *\n * Requirements:\n *\n * - input must fit into 104 bits\n */\n function toUint104(uint256 value) internal pure returns (uint104) {\n if (value > type(uint104).max) {\n revert SafeCastOverflowedUintDowncast(104, value);\n }\n return uint104(value);\n }\n\n /**\n * @dev Returns the downcasted uint96 from uint256, reverting on\n * overflow (when the input is greater than largest uint96).\n *\n * Counterpart to Solidity's `uint96` operator.\n *\n * Requirements:\n *\n * - input must fit into 96 bits\n */\n function toUint96(uint256 value) internal pure returns (uint96) {\n if (value > type(uint96).max) {\n revert SafeCastOverflowedUintDowncast(96, value);\n }\n return uint96(value);\n }\n\n /**\n * @dev Returns the downcasted uint88 from uint256, reverting on\n * overflow (when the input is greater than largest uint88).\n *\n * Counterpart to Solidity's `uint88` operator.\n *\n * Requirements:\n *\n * - input must fit into 88 bits\n */\n function toUint88(uint256 value) internal pure returns (uint88) {\n if (value > type(uint88).max) {\n revert SafeCastOverflowedUintDowncast(88, value);\n }\n return uint88(value);\n }\n\n /**\n * @dev Returns the downcasted uint80 from uint256, reverting on\n * overflow (when the input is greater than largest uint80).\n *\n * Counterpart to Solidity's `uint80` operator.\n *\n * Requirements:\n *\n * - input must fit into 80 bits\n */\n function toUint80(uint256 value) internal pure returns (uint80) {\n if (value > type(uint80).max) {\n revert SafeCastOverflowedUintDowncast(80, value);\n }\n return uint80(value);\n }\n\n /**\n * @dev Returns the downcasted uint72 from uint256, reverting on\n * overflow (when the input is greater than largest uint72).\n *\n * Counterpart to Solidity's `uint72` operator.\n *\n * Requirements:\n *\n * - input must fit into 72 bits\n */\n function toUint72(uint256 value) internal pure returns (uint72) {\n if (value > type(uint72).max) {\n revert SafeCastOverflowedUintDowncast(72, value);\n }\n return uint72(value);\n }\n\n /**\n * @dev Returns the downcasted uint64 from uint256, reverting on\n * overflow (when the input is greater than largest uint64).\n *\n * Counterpart to Solidity's `uint64` operator.\n *\n * Requirements:\n *\n * - input must fit into 64 bits\n */\n function toUint64(uint256 value) internal pure returns (uint64) {\n if (value > type(uint64).max) {\n revert SafeCastOverflowedUintDowncast(64, value);\n }\n return uint64(value);\n }\n\n /**\n * @dev Returns the downcasted uint56 from uint256, reverting on\n * overflow (when the input is greater than largest uint56).\n *\n * Counterpart to Solidity's `uint56` operator.\n *\n * Requirements:\n *\n * - input must fit into 56 bits\n */\n function toUint56(uint256 value) internal pure returns (uint56) {\n if (value > type(uint56).max) {\n revert SafeCastOverflowedUintDowncast(56, value);\n }\n return uint56(value);\n }\n\n /**\n * @dev Returns the downcasted uint48 from uint256, reverting on\n * overflow (when the input is greater than largest uint48).\n *\n * Counterpart to Solidity's `uint48` operator.\n *\n * Requirements:\n *\n * - input must fit into 48 bits\n */\n function toUint48(uint256 value) internal pure returns (uint48) {\n if (value > type(uint48).max) {\n revert SafeCastOverflowedUintDowncast(48, value);\n }\n return uint48(value);\n }\n\n /**\n * @dev Returns the downcasted uint40 from uint256, reverting on\n * overflow (when the input is greater than largest uint40).\n *\n * Counterpart to Solidity's `uint40` operator.\n *\n * Requirements:\n *\n * - input must fit into 40 bits\n */\n function toUint40(uint256 value) internal pure returns (uint40) {\n if (value > type(uint40).max) {\n revert SafeCastOverflowedUintDowncast(40, value);\n }\n return uint40(value);\n }\n\n /**\n * @dev Returns the downcasted uint32 from uint256, reverting on\n * overflow (when the input is greater than largest uint32).\n *\n * Counterpart to Solidity's `uint32` operator.\n *\n * Requirements:\n *\n * - input must fit into 32 bits\n */\n function toUint32(uint256 value) internal pure returns (uint32) {\n if (value > type(uint32).max) {\n revert SafeCastOverflowedUintDowncast(32, value);\n }\n return uint32(value);\n }\n\n /**\n * @dev Returns the downcasted uint24 from uint256, reverting on\n * overflow (when the input is greater than largest uint24).\n *\n * Counterpart to Solidity's `uint24` operator.\n *\n * Requirements:\n *\n * - input must fit into 24 bits\n */\n function toUint24(uint256 value) internal pure returns (uint24) {\n if (value > type(uint24).max) {\n revert SafeCastOverflowedUintDowncast(24, value);\n }\n return uint24(value);\n }\n\n /**\n * @dev Returns the downcasted uint16 from uint256, reverting on\n * overflow (when the input is greater than largest uint16).\n *\n * Counterpart to Solidity's `uint16` operator.\n *\n * Requirements:\n *\n * - input must fit into 16 bits\n */\n function toUint16(uint256 value) internal pure returns (uint16) {\n if (value > type(uint16).max) {\n revert SafeCastOverflowedUintDowncast(16, value);\n }\n return uint16(value);\n }\n\n /**\n * @dev Returns the downcasted uint8 from uint256, reverting on\n * overflow (when the input is greater than largest uint8).\n *\n * Counterpart to Solidity's `uint8` operator.\n *\n * Requirements:\n *\n * - input must fit into 8 bits\n */\n function toUint8(uint256 value) internal pure returns (uint8) {\n if (value > type(uint8).max) {\n revert SafeCastOverflowedUintDowncast(8, value);\n }\n return uint8(value);\n }\n\n /**\n * @dev Converts a signed int256 into an unsigned uint256.\n *\n * Requirements:\n *\n * - input must be greater than or equal to 0.\n */\n function toUint256(int256 value) internal pure returns (uint256) {\n if (value < 0) {\n revert SafeCastOverflowedIntToUint(value);\n }\n return uint256(value);\n }\n\n /**\n * @dev Returns the downcasted int248 from int256, reverting on\n * overflow (when the input is less than smallest int248 or\n * greater than largest int248).\n *\n * Counterpart to Solidity's `int248` operator.\n *\n * Requirements:\n *\n * - input must fit into 248 bits\n */\n function toInt248(int256 value) internal pure returns (int248 downcasted) {\n downcasted = int248(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(248, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int240 from int256, reverting on\n * overflow (when the input is less than smallest int240 or\n * greater than largest int240).\n *\n * Counterpart to Solidity's `int240` operator.\n *\n * Requirements:\n *\n * - input must fit into 240 bits\n */\n function toInt240(int256 value) internal pure returns (int240 downcasted) {\n downcasted = int240(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(240, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int232 from int256, reverting on\n * overflow (when the input is less than smallest int232 or\n * greater than largest int232).\n *\n * Counterpart to Solidity's `int232` operator.\n *\n * Requirements:\n *\n * - input must fit into 232 bits\n */\n function toInt232(int256 value) internal pure returns (int232 downcasted) {\n downcasted = int232(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(232, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int224 from int256, reverting on\n * overflow (when the input is less than smallest int224 or\n * greater than largest int224).\n *\n * Counterpart to Solidity's `int224` operator.\n *\n * Requirements:\n *\n * - input must fit into 224 bits\n */\n function toInt224(int256 value) internal pure returns (int224 downcasted) {\n downcasted = int224(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(224, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int216 from int256, reverting on\n * overflow (when the input is less than smallest int216 or\n * greater than largest int216).\n *\n * Counterpart to Solidity's `int216` operator.\n *\n * Requirements:\n *\n * - input must fit into 216 bits\n */\n function toInt216(int256 value) internal pure returns (int216 downcasted) {\n downcasted = int216(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(216, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int208 from int256, reverting on\n * overflow (when the input is less than smallest int208 or\n * greater than largest int208).\n *\n * Counterpart to Solidity's `int208` operator.\n *\n * Requirements:\n *\n * - input must fit into 208 bits\n */\n function toInt208(int256 value) internal pure returns (int208 downcasted) {\n downcasted = int208(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(208, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int200 from int256, reverting on\n * overflow (when the input is less than smallest int200 or\n * greater than largest int200).\n *\n * Counterpart to Solidity's `int200` operator.\n *\n * Requirements:\n *\n * - input must fit into 200 bits\n */\n function toInt200(int256 value) internal pure returns (int200 downcasted) {\n downcasted = int200(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(200, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int192 from int256, reverting on\n * overflow (when the input is less than smallest int192 or\n * greater than largest int192).\n *\n * Counterpart to Solidity's `int192` operator.\n *\n * Requirements:\n *\n * - input must fit into 192 bits\n */\n function toInt192(int256 value) internal pure returns (int192 downcasted) {\n downcasted = int192(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(192, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int184 from int256, reverting on\n * overflow (when the input is less than smallest int184 or\n * greater than largest int184).\n *\n * Counterpart to Solidity's `int184` operator.\n *\n * Requirements:\n *\n * - input must fit into 184 bits\n */\n function toInt184(int256 value) internal pure returns (int184 downcasted) {\n downcasted = int184(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(184, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int176 from int256, reverting on\n * overflow (when the input is less than smallest int176 or\n * greater than largest int176).\n *\n * Counterpart to Solidity's `int176` operator.\n *\n * Requirements:\n *\n * - input must fit into 176 bits\n */\n function toInt176(int256 value) internal pure returns (int176 downcasted) {\n downcasted = int176(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(176, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int168 from int256, reverting on\n * overflow (when the input is less than smallest int168 or\n * greater than largest int168).\n *\n * Counterpart to Solidity's `int168` operator.\n *\n * Requirements:\n *\n * - input must fit into 168 bits\n */\n function toInt168(int256 value) internal pure returns (int168 downcasted) {\n downcasted = int168(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(168, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int160 from int256, reverting on\n * overflow (when the input is less than smallest int160 or\n * greater than largest int160).\n *\n * Counterpart to Solidity's `int160` operator.\n *\n * Requirements:\n *\n * - input must fit into 160 bits\n */\n function toInt160(int256 value) internal pure returns (int160 downcasted) {\n downcasted = int160(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(160, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int152 from int256, reverting on\n * overflow (when the input is less than smallest int152 or\n * greater than largest int152).\n *\n * Counterpart to Solidity's `int152` operator.\n *\n * Requirements:\n *\n * - input must fit into 152 bits\n */\n function toInt152(int256 value) internal pure returns (int152 downcasted) {\n downcasted = int152(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(152, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int144 from int256, reverting on\n * overflow (when the input is less than smallest int144 or\n * greater than largest int144).\n *\n * Counterpart to Solidity's `int144` operator.\n *\n * Requirements:\n *\n * - input must fit into 144 bits\n */\n function toInt144(int256 value) internal pure returns (int144 downcasted) {\n downcasted = int144(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(144, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int136 from int256, reverting on\n * overflow (when the input is less than smallest int136 or\n * greater than largest int136).\n *\n * Counterpart to Solidity's `int136` operator.\n *\n * Requirements:\n *\n * - input must fit into 136 bits\n */\n function toInt136(int256 value) internal pure returns (int136 downcasted) {\n downcasted = int136(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(136, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int128 from int256, reverting on\n * overflow (when the input is less than smallest int128 or\n * greater than largest int128).\n *\n * Counterpart to Solidity's `int128` operator.\n *\n * Requirements:\n *\n * - input must fit into 128 bits\n */\n function toInt128(int256 value) internal pure returns (int128 downcasted) {\n downcasted = int128(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(128, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int120 from int256, reverting on\n * overflow (when the input is less than smallest int120 or\n * greater than largest int120).\n *\n * Counterpart to Solidity's `int120` operator.\n *\n * Requirements:\n *\n * - input must fit into 120 bits\n */\n function toInt120(int256 value) internal pure returns (int120 downcasted) {\n downcasted = int120(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(120, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int112 from int256, reverting on\n * overflow (when the input is less than smallest int112 or\n * greater than largest int112).\n *\n * Counterpart to Solidity's `int112` operator.\n *\n * Requirements:\n *\n * - input must fit into 112 bits\n */\n function toInt112(int256 value) internal pure returns (int112 downcasted) {\n downcasted = int112(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(112, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int104 from int256, reverting on\n * overflow (when the input is less than smallest int104 or\n * greater than largest int104).\n *\n * Counterpart to Solidity's `int104` operator.\n *\n * Requirements:\n *\n * - input must fit into 104 bits\n */\n function toInt104(int256 value) internal pure returns (int104 downcasted) {\n downcasted = int104(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(104, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int96 from int256, reverting on\n * overflow (when the input is less than smallest int96 or\n * greater than largest int96).\n *\n * Counterpart to Solidity's `int96` operator.\n *\n * Requirements:\n *\n * - input must fit into 96 bits\n */\n function toInt96(int256 value) internal pure returns (int96 downcasted) {\n downcasted = int96(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(96, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int88 from int256, reverting on\n * overflow (when the input is less than smallest int88 or\n * greater than largest int88).\n *\n * Counterpart to Solidity's `int88` operator.\n *\n * Requirements:\n *\n * - input must fit into 88 bits\n */\n function toInt88(int256 value) internal pure returns (int88 downcasted) {\n downcasted = int88(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(88, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int80 from int256, reverting on\n * overflow (when the input is less than smallest int80 or\n * greater than largest int80).\n *\n * Counterpart to Solidity's `int80` operator.\n *\n * Requirements:\n *\n * - input must fit into 80 bits\n */\n function toInt80(int256 value) internal pure returns (int80 downcasted) {\n downcasted = int80(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(80, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int72 from int256, reverting on\n * overflow (when the input is less than smallest int72 or\n * greater than largest int72).\n *\n * Counterpart to Solidity's `int72` operator.\n *\n * Requirements:\n *\n * - input must fit into 72 bits\n */\n function toInt72(int256 value) internal pure returns (int72 downcasted) {\n downcasted = int72(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(72, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int64 from int256, reverting on\n * overflow (when the input is less than smallest int64 or\n * greater than largest int64).\n *\n * Counterpart to Solidity's `int64` operator.\n *\n * Requirements:\n *\n * - input must fit into 64 bits\n */\n function toInt64(int256 value) internal pure returns (int64 downcasted) {\n downcasted = int64(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(64, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int56 from int256, reverting on\n * overflow (when the input is less than smallest int56 or\n * greater than largest int56).\n *\n * Counterpart to Solidity's `int56` operator.\n *\n * Requirements:\n *\n * - input must fit into 56 bits\n */\n function toInt56(int256 value) internal pure returns (int56 downcasted) {\n downcasted = int56(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(56, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int48 from int256, reverting on\n * overflow (when the input is less than smallest int48 or\n * greater than largest int48).\n *\n * Counterpart to Solidity's `int48` operator.\n *\n * Requirements:\n *\n * - input must fit into 48 bits\n */\n function toInt48(int256 value) internal pure returns (int48 downcasted) {\n downcasted = int48(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(48, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int40 from int256, reverting on\n * overflow (when the input is less than smallest int40 or\n * greater than largest int40).\n *\n * Counterpart to Solidity's `int40` operator.\n *\n * Requirements:\n *\n * - input must fit into 40 bits\n */\n function toInt40(int256 value) internal pure returns (int40 downcasted) {\n downcasted = int40(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(40, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int32 from int256, reverting on\n * overflow (when the input is less than smallest int32 or\n * greater than largest int32).\n *\n * Counterpart to Solidity's `int32` operator.\n *\n * Requirements:\n *\n * - input must fit into 32 bits\n */\n function toInt32(int256 value) internal pure returns (int32 downcasted) {\n downcasted = int32(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(32, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int24 from int256, reverting on\n * overflow (when the input is less than smallest int24 or\n * greater than largest int24).\n *\n * Counterpart to Solidity's `int24` operator.\n *\n * Requirements:\n *\n * - input must fit into 24 bits\n */\n function toInt24(int256 value) internal pure returns (int24 downcasted) {\n downcasted = int24(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(24, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int16 from int256, reverting on\n * overflow (when the input is less than smallest int16 or\n * greater than largest int16).\n *\n * Counterpart to Solidity's `int16` operator.\n *\n * Requirements:\n *\n * - input must fit into 16 bits\n */\n function toInt16(int256 value) internal pure returns (int16 downcasted) {\n downcasted = int16(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(16, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int8 from int256, reverting on\n * overflow (when the input is less than smallest int8 or\n * greater than largest int8).\n *\n * Counterpart to Solidity's `int8` operator.\n *\n * Requirements:\n *\n * - input must fit into 8 bits\n */\n function toInt8(int256 value) internal pure returns (int8 downcasted) {\n downcasted = int8(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(8, value);\n }\n }\n\n /**\n * @dev Converts an unsigned uint256 into a signed int256.\n *\n * Requirements:\n *\n * - input must be less than or equal to maxInt256.\n */\n function toInt256(uint256 value) internal pure returns (int256) {\n // Note: Unsafe cast below is okay because `type(int256).max` is guaranteed to be positive\n if (value > uint256(type(int256).max)) {\n revert SafeCastOverflowedUintToInt(value);\n }\n return int256(value);\n }\n\n /**\n * @dev Cast a boolean (false or true) to a uint256 (0 or 1) with no jump.\n */\n function toUint(bool b) internal pure returns (uint256 u) {\n assembly (\"memory-safe\") {\n u := iszero(iszero(b))\n }\n }\n}" + }, + "lib/rooster/openzeppelin-custom/contracts/utils/Panic.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.1.0) (utils/Panic.sol)\n\npragma solidity ^0.8.20;\n\n/**\n * @dev Helper library for emitting standardized panic codes.\n *\n * ```solidity\n * contract Example {\n * using Panic for uint256;\n *\n * // Use any of the declared internal constants\n * function foo() { Panic.GENERIC.panic(); }\n *\n * // Alternatively\n * function foo() { Panic.panic(Panic.GENERIC); }\n * }\n * ```\n *\n * Follows the list from https://github.com/ethereum/solidity/blob/v0.8.24/libsolutil/ErrorCodes.h[libsolutil].\n *\n * _Available since v5.1._\n */\n// slither-disable-next-line unused-state\nlibrary Panic {\n /// @dev generic / unspecified error\n uint256 internal constant GENERIC = 0x00;\n /// @dev used by the assert() builtin\n uint256 internal constant ASSERT = 0x01;\n /// @dev arithmetic underflow or overflow\n uint256 internal constant UNDER_OVERFLOW = 0x11;\n /// @dev division or modulo by zero\n uint256 internal constant DIVISION_BY_ZERO = 0x12;\n /// @dev enum conversion error\n uint256 internal constant ENUM_CONVERSION_ERROR = 0x21;\n /// @dev invalid encoding in storage\n uint256 internal constant STORAGE_ENCODING_ERROR = 0x22;\n /// @dev empty array pop\n uint256 internal constant EMPTY_ARRAY_POP = 0x31;\n /// @dev array out of bounds access\n uint256 internal constant ARRAY_OUT_OF_BOUNDS = 0x32;\n /// @dev resource error (too large allocation or too large array)\n uint256 internal constant RESOURCE_ERROR = 0x41;\n /// @dev calling invalid internal function\n uint256 internal constant INVALID_INTERNAL_FUNCTION = 0x51;\n\n /// @dev Reverts with a panic code. Recommended to use with\n /// the internal constants with predefined codes.\n function panic(uint256 code) internal pure {\n assembly (\"memory-safe\") {\n mstore(0x00, 0x4e487b71)\n mstore(0x20, code)\n revert(0x1c, 0x24)\n }\n }\n}" + }, + "lib/rooster/v2-common/libraries/Constants.sol": { + "content": "// SPDX-License-Identifier: GPL-2.0-or-later\n// As the copyright holder of this work, Ubiquity Labs retains\n// the right to distribute, use, and modify this code under any license of\n// their choosing, in addition to the terms of the GPL-v2 or later.\npragma solidity ^0.8.25;\n\n// factory contraints on pools\nuint8 constant MAX_PROTOCOL_FEE_RATIO_D3 = 0.25e3; // 25%\nuint256 constant MAX_PROTOCOL_LENDING_FEE_RATE_D18 = 0.02e18; // 2%\nuint64 constant MAX_POOL_FEE_D18 = 0.9e18; // 90%\nuint64 constant MIN_LOOKBACK = 1 seconds;\n\n// pool constraints\nuint8 constant NUMBER_OF_KINDS = 4;\nint32 constant NUMBER_OF_KINDS_32 = int32(int8(NUMBER_OF_KINDS));\nuint256 constant MAX_TICK = 322_378; // max price 1e14 in D18 scale\nint32 constant MAX_TICK_32 = int32(int256(MAX_TICK));\nint32 constant MIN_TICK_32 = int32(-int256(MAX_TICK));\nuint256 constant MAX_BINS_TO_MERGE = 3;\nuint128 constant MINIMUM_LIQUIDITY = 1e8;\n\n// accessor named constants\nuint8 constant ALL_KINDS_MASK = 0xF; // 0b1111\nuint8 constant PERMISSIONED_LIQUIDITY_MASK = 0x10; // 0b010000\nuint8 constant PERMISSIONED_SWAP_MASK = 0x20; // 0b100000\nuint8 constant OPTIONS_MASK = ALL_KINDS_MASK | PERMISSIONED_LIQUIDITY_MASK | PERMISSIONED_SWAP_MASK; // 0b111111\n\n// named values\naddress constant MERGED_LP_BALANCE_ADDRESS = address(0);\nuint256 constant MERGED_LP_BALANCE_SUBACCOUNT = 0;\nuint128 constant ONE = 1e18;\nuint128 constant ONE_SQUARED = 1e36;\nint256 constant INT256_ONE = 1e18;\nuint256 constant ONE_D8 = 1e8;\nuint256 constant ONE_D3 = 1e3;\nint40 constant INT_ONE_D8 = 1e8;\nint40 constant HALF_TICK_D8 = 0.5e8;\nuint8 constant DEFAULT_DECIMALS = 18;\nuint256 constant DEFAULT_SCALE = 1;\nbytes constant EMPTY_PRICE_BREAKS = hex\"010000000000000000000000\";" + }, + "lib/rooster/v2-common/libraries/Math.sol": { + "content": "// SPDX-License-Identifier: GPL-2.0-or-later\n// As the copyright holder of this work, Ubiquity Labs retains\n// the right to distribute, use, and modify this code under any license of\n// their choosing, in addition to the terms of the GPL-v2 or later.\npragma solidity ^0.8.25;\n\nimport {Math as OzMath} from \"../../openzeppelin-custom/contracts/utils/math/Math.sol\";\n\nimport {ONE, DEFAULT_SCALE, DEFAULT_DECIMALS, INT_ONE_D8, ONE_SQUARED} from \"./Constants.sol\";\n\n/**\n * @notice Math functions.\n */\nlibrary Math {\n /**\n * @notice Returns the lesser of two values.\n * @param x First uint256 value.\n * @param y Second uint256 value.\n */\n function min(uint256 x, uint256 y) internal pure returns (uint256 z) {\n assembly (\"memory-safe\") {\n z := xor(x, mul(xor(x, y), lt(y, x)))\n }\n }\n\n /**\n * @notice Returns the lesser of two uint128 values.\n * @param x First uint128 value.\n * @param y Second uint128 value.\n */\n function min128(uint128 x, uint128 y) internal pure returns (uint128 z) {\n assembly (\"memory-safe\") {\n z := xor(x, mul(xor(x, y), lt(y, x)))\n }\n }\n\n /**\n * @notice Returns the lesser of two int256 values.\n * @param x First int256 value.\n * @param y Second int256 value.\n */\n function min(int256 x, int256 y) internal pure returns (int256 z) {\n assembly (\"memory-safe\") {\n z := xor(x, mul(xor(x, y), slt(y, x)))\n }\n }\n\n /**\n * @notice Returns the greater of two uint256 values.\n * @param x First uint256 value.\n * @param y Second uint256 value.\n */\n function max(uint256 x, uint256 y) internal pure returns (uint256 z) {\n assembly (\"memory-safe\") {\n z := xor(x, mul(xor(x, y), gt(y, x)))\n }\n }\n\n /**\n * @notice Returns the greater of two int256 values.\n * @param x First int256 value.\n * @param y Second int256 value.\n */\n function max(int256 x, int256 y) internal pure returns (int256 z) {\n assembly (\"memory-safe\") {\n z := xor(x, mul(xor(x, y), sgt(y, x)))\n }\n }\n\n /**\n * @notice Returns the greater of two uint128 values.\n * @param x First uint128 value.\n * @param y Second uint128 value.\n */\n function max128(uint128 x, uint128 y) internal pure returns (uint128 z) {\n assembly (\"memory-safe\") {\n z := xor(x, mul(xor(x, y), gt(y, x)))\n }\n }\n\n /**\n * @notice Thresholds a value to be within the specified bounds.\n * @param value The value to bound.\n * @param lowerLimit The minimum allowable value.\n * @param upperLimit The maximum allowable value.\n */\n function boundValue(\n uint256 value,\n uint256 lowerLimit,\n uint256 upperLimit\n ) internal pure returns (uint256 outputValue) {\n outputValue = min(max(value, lowerLimit), upperLimit);\n }\n\n /**\n * @notice Returns the difference between two uint128 values or zero if the result would be negative.\n * @param x The minuend.\n * @param y The subtrahend.\n */\n function clip128(uint128 x, uint128 y) internal pure returns (uint128) {\n unchecked {\n return x < y ? 0 : x - y;\n }\n }\n\n /**\n * @notice Returns the difference between two uint256 values or zero if the result would be negative.\n * @param x The minuend.\n * @param y The subtrahend.\n */\n function clip(uint256 x, uint256 y) internal pure returns (uint256) {\n unchecked {\n return x < y ? 0 : x - y;\n }\n }\n\n /**\n * @notice Divides one uint256 by another, rounding down to the nearest\n * integer.\n * @param x The dividend.\n * @param y The divisor.\n */\n function divFloor(uint256 x, uint256 y) internal pure returns (uint256) {\n return mulDivFloor(x, ONE, y);\n }\n\n /**\n * @notice Divides one uint256 by another, rounding up to the nearest integer.\n * @param x The dividend.\n * @param y The divisor.\n */\n function divCeil(uint256 x, uint256 y) internal pure returns (uint256) {\n return mulDivCeil(x, ONE, y);\n }\n\n /**\n * @notice Multiplies two uint256 values and then divides by ONE, rounding down.\n * @param x The multiplicand.\n * @param y The multiplier.\n */\n function mulFloor(uint256 x, uint256 y) internal pure returns (uint256) {\n return OzMath.mulDiv(x, y, ONE);\n }\n\n /**\n * @notice Multiplies two uint256 values and then divides by ONE, rounding up.\n * @param x The multiplicand.\n * @param y The multiplier.\n */\n function mulCeil(uint256 x, uint256 y) internal pure returns (uint256) {\n return mulDivCeil(x, y, ONE);\n }\n\n /**\n * @notice Calculates the multiplicative inverse of a uint256, rounding down.\n * @param x The value to invert.\n */\n function invFloor(uint256 x) internal pure returns (uint256) {\n unchecked {\n return ONE_SQUARED / x;\n }\n }\n\n /**\n * @notice Calculates the multiplicative inverse of a uint256, rounding up.\n * @param denominator The value to invert.\n */\n function invCeil(uint256 denominator) internal pure returns (uint256 z) {\n assembly (\"memory-safe\") {\n // divide z - 1 by the denominator and add 1.\n z := add(div(sub(ONE_SQUARED, 1), denominator), 1)\n }\n }\n\n /**\n * @notice Multiplies two uint256 values and divides by a third, rounding down.\n * @param x The multiplicand.\n * @param y The multiplier.\n * @param k The divisor.\n */\n function mulDivFloor(uint256 x, uint256 y, uint256 k) internal pure returns (uint256 result) {\n result = OzMath.mulDiv(x, y, max(1, k));\n }\n\n /**\n * @notice Multiplies two uint256 values and divides by a third, rounding up if there's a remainder.\n * @param x The multiplicand.\n * @param y The multiplier.\n * @param k The divisor.\n */\n function mulDivCeil(uint256 x, uint256 y, uint256 k) internal pure returns (uint256 result) {\n result = mulDivFloor(x, y, k);\n if (mulmod(x, y, max(1, k)) != 0) result = result + 1;\n }\n\n /**\n * @notice Multiplies two uint256 values and divides by a third, rounding\n * down. Will revert if `x * y` is larger than `type(uint256).max`.\n * @param x The first operand for multiplication.\n * @param y The second operand for multiplication.\n * @param denominator The divisor after multiplication.\n */\n function mulDivDown(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 z) {\n assembly (\"memory-safe\") {\n // Store x * y in z for now.\n z := mul(x, y)\n if iszero(denominator) {\n denominator := 1\n }\n\n if iszero(or(iszero(x), eq(div(z, x), y))) {\n revert(0, 0)\n }\n\n // Divide z by the denominator.\n z := div(z, denominator)\n }\n }\n\n /**\n * @notice Multiplies two uint256 values and divides by a third, rounding\n * up. Will revert if `x * y` is larger than `type(uint256).max`.\n * @param x The first operand for multiplication.\n * @param y The second operand for multiplication.\n * @param denominator The divisor after multiplication.\n */\n function mulDivUp(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 z) {\n assembly (\"memory-safe\") {\n // Store x * y in z for now.\n z := mul(x, y)\n if iszero(denominator) {\n denominator := 1\n }\n\n if iszero(or(iszero(x), eq(div(z, x), y))) {\n revert(0, 0)\n }\n\n // First, divide z - 1 by the denominator and add 1.\n // We allow z - 1 to underflow if z is 0, because we multiply the\n // end result by 0 if z is zero, ensuring we return 0 if z is zero.\n z := mul(iszero(iszero(z)), add(div(sub(z, 1), denominator), 1))\n }\n }\n\n /**\n * @notice Multiplies a uint256 by another and divides by a constant,\n * rounding down. Will revert if `x * y` is larger than\n * `type(uint256).max`.\n * @param x The multiplicand.\n * @param y The multiplier.\n */\n function mulDown(uint256 x, uint256 y) internal pure returns (uint256) {\n return mulDivDown(x, y, ONE);\n }\n\n /**\n * @notice Divides a uint256 by another, rounding down the result. Will\n * revert if `x * 1e18` is larger than `type(uint256).max`.\n * @param x The dividend.\n * @param y The divisor.\n */\n function divDown(uint256 x, uint256 y) internal pure returns (uint256) {\n return mulDivDown(x, ONE, y);\n }\n\n /**\n * @notice Divides a uint256 by another, rounding up the result. Will\n * revert if `x * 1e18` is larger than `type(uint256).max`.\n * @param x The dividend.\n * @param y The divisor.\n */\n function divUp(uint256 x, uint256 y) internal pure returns (uint256) {\n return mulDivUp(x, ONE, y);\n }\n\n /**\n * @notice Scales a number based on a difference in decimals from a default.\n * @param decimals The new decimal precision.\n */\n function scale(uint8 decimals) internal pure returns (uint256) {\n unchecked {\n if (decimals == DEFAULT_DECIMALS) {\n return DEFAULT_SCALE;\n } else {\n return 10 ** (DEFAULT_DECIMALS - decimals);\n }\n }\n }\n\n /**\n * @notice Adjusts a scaled amount to the token decimal scale.\n * @param amount The scaled amount.\n * @param scaleFactor The scaling factor to adjust by.\n * @param ceil Whether to round up (true) or down (false).\n */\n function ammScaleToTokenScale(uint256 amount, uint256 scaleFactor, bool ceil) internal pure returns (uint256 z) {\n unchecked {\n if (scaleFactor == DEFAULT_SCALE || amount == 0) {\n return amount;\n } else {\n if (!ceil) return amount / scaleFactor;\n assembly (\"memory-safe\") {\n z := add(div(sub(amount, 1), scaleFactor), 1)\n }\n }\n }\n }\n\n /**\n * @notice Adjusts a token amount to the D18 AMM scale.\n * @param amount The amount in token scale.\n * @param scaleFactor The scale factor for adjustment.\n */\n function tokenScaleToAmmScale(uint256 amount, uint256 scaleFactor) internal pure returns (uint256) {\n if (scaleFactor == DEFAULT_SCALE) {\n return amount;\n } else {\n return amount * scaleFactor;\n }\n }\n\n /**\n * @notice Returns the absolute value of a signed 32-bit integer.\n * @param x The integer to take the absolute value of.\n */\n function abs32(int32 x) internal pure returns (uint32) {\n unchecked {\n return uint32(x < 0 ? -x : x);\n }\n }\n\n /**\n * @notice Returns the absolute value of a signed 256-bit integer.\n * @param x The integer to take the absolute value of.\n */\n function abs(int256 x) internal pure returns (uint256) {\n unchecked {\n return uint256(x < 0 ? -x : x);\n }\n }\n\n /**\n * @notice Calculates the integer square root of a uint256 rounded down.\n * @param x The number to take the square root of.\n */\n function sqrt(uint256 x) internal pure returns (uint256 z) {\n // from https://github.com/transmissions11/solmate/blob/e8f96f25d48fe702117ce76c79228ca4f20206cb/src/utils/FixedPointMathLib.sol\n assembly (\"memory-safe\") {\n let y := x\n z := 181\n\n if iszero(lt(y, 0x10000000000000000000000000000000000)) {\n y := shr(128, y)\n z := shl(64, z)\n }\n if iszero(lt(y, 0x1000000000000000000)) {\n y := shr(64, y)\n z := shl(32, z)\n }\n if iszero(lt(y, 0x10000000000)) {\n y := shr(32, y)\n z := shl(16, z)\n }\n if iszero(lt(y, 0x1000000)) {\n y := shr(16, y)\n z := shl(8, z)\n }\n\n z := shr(18, mul(z, add(y, 65536)))\n\n z := shr(1, add(z, div(x, z)))\n z := shr(1, add(z, div(x, z)))\n z := shr(1, add(z, div(x, z)))\n z := shr(1, add(z, div(x, z)))\n z := shr(1, add(z, div(x, z)))\n z := shr(1, add(z, div(x, z)))\n z := shr(1, add(z, div(x, z)))\n\n z := sub(z, lt(div(x, z), z))\n }\n }\n\n /**\n * @notice Computes the floor of a D8-scaled number as an int32, ignoring\n * potential overflow in the cast.\n * @param val The D8-scaled number.\n */\n function floorD8Unchecked(int256 val) internal pure returns (int32) {\n int32 val32;\n bool check;\n unchecked {\n val32 = int32(val / INT_ONE_D8);\n check = (val < 0 && val % INT_ONE_D8 != 0);\n }\n return check ? val32 - 1 : val32;\n }\n}" + }, + "lib/rooster/v2-common/libraries/TickMath.sol": { + "content": "// SPDX-License-Identifier: GPL-2.0-or-later\n// As the copyright holder of this work, Ubiquity Labs retains\n// the right to distribute, use, and modify this code under any license of\n// their choosing, in addition to the terms of the GPL-v2 or later.\npragma solidity ^0.8.25;\n\nimport {Math as OzMath} from \"../../openzeppelin-custom/contracts/utils/math/Math.sol\";\nimport {Math} from \"./Math.sol\";\nimport {MAX_TICK, ONE} from \"./Constants.sol\";\n\n/**\n * @notice Math functions related to tick operations.\n */\n// slither-disable-start divide-before-multiply\nlibrary TickMath {\n using Math for uint256;\n\n error TickMaxExceeded(int256 tick);\n\n /**\n * @notice Compute the lower and upper sqrtPrice of a tick.\n * @param tickSpacing The tick spacing used for calculations.\n * @param _tick The input tick value.\n */\n function tickSqrtPrices(\n uint256 tickSpacing,\n int32 _tick\n ) internal pure returns (uint256 sqrtLowerPrice, uint256 sqrtUpperPrice) {\n unchecked {\n sqrtLowerPrice = tickSqrtPrice(tickSpacing, _tick);\n sqrtUpperPrice = tickSqrtPrice(tickSpacing, _tick + 1);\n }\n }\n\n /**\n * @notice Compute the base tick value from the pool tick and the\n * tickSpacing. Revert if base tick is beyond the max tick boundary.\n * @param tickSpacing The tick spacing used for calculations.\n * @param _tick The input tick value.\n */\n function subTickIndex(uint256 tickSpacing, int32 _tick) internal pure returns (uint32 subTick) {\n subTick = Math.abs32(_tick);\n subTick *= uint32(tickSpacing);\n if (subTick > MAX_TICK) {\n revert TickMaxExceeded(_tick);\n }\n }\n\n /**\n * @notice Calculate the square root price for a given tick and tick spacing.\n * @param tickSpacing The tick spacing used for calculations.\n * @param _tick The input tick value.\n * @return _result The square root price.\n */\n function tickSqrtPrice(uint256 tickSpacing, int32 _tick) internal pure returns (uint256 _result) {\n unchecked {\n uint256 tick = subTickIndex(tickSpacing, _tick);\n\n uint256 ratio = tick & 0x1 != 0 ? 0xfffcb933bd6fad9d3af5f0b9f25db4d6 : 0x100000000000000000000000000000000;\n if (tick & 0x2 != 0) ratio = (ratio * 0xfff97272373d41fd789c8cb37ffcaa1c) >> 128;\n if (tick & 0x4 != 0) ratio = (ratio * 0xfff2e50f5f656ac9229c67059486f389) >> 128;\n if (tick & 0x8 != 0) ratio = (ratio * 0xffe5caca7e10e81259b3cddc7a064941) >> 128;\n if (tick & 0x10 != 0) ratio = (ratio * 0xffcb9843d60f67b19e8887e0bd251eb7) >> 128;\n if (tick & 0x20 != 0) ratio = (ratio * 0xff973b41fa98cd2e57b660be99eb2c4a) >> 128;\n if (tick & 0x40 != 0) ratio = (ratio * 0xff2ea16466c9838804e327cb417cafcb) >> 128;\n if (tick & 0x80 != 0) ratio = (ratio * 0xfe5dee046a99d51e2cc356c2f617dbe0) >> 128;\n if (tick & 0x100 != 0) ratio = (ratio * 0xfcbe86c7900aecf64236ab31f1f9dcb5) >> 128;\n if (tick & 0x200 != 0) ratio = (ratio * 0xf987a7253ac4d9194200696907cf2e37) >> 128;\n if (tick & 0x400 != 0) ratio = (ratio * 0xf3392b0822b88206f8abe8a3b44dd9be) >> 128;\n if (tick & 0x800 != 0) ratio = (ratio * 0xe7159475a2c578ef4f1d17b2b235d480) >> 128;\n if (tick & 0x1000 != 0) ratio = (ratio * 0xd097f3bdfd254ee83bdd3f248e7e785e) >> 128;\n if (tick & 0x2000 != 0) ratio = (ratio * 0xa9f746462d8f7dd10e744d913d033333) >> 128;\n if (tick & 0x4000 != 0) ratio = (ratio * 0x70d869a156ddd32a39e257bc3f50aa9b) >> 128;\n if (tick & 0x8000 != 0) ratio = (ratio * 0x31be135f97da6e09a19dc367e3b6da40) >> 128;\n if (tick & 0x10000 != 0) ratio = (ratio * 0x9aa508b5b7e5a9780b0cc4e25d61a56) >> 128;\n if (tick & 0x20000 != 0) ratio = (ratio * 0x5d6af8dedbcb3a6ccb7ce618d14225) >> 128;\n if (tick & 0x40000 != 0) ratio = (ratio * 0x2216e584f630389b2052b8db590e) >> 128;\n if (_tick > 0) ratio = type(uint256).max / ratio;\n _result = (ratio * ONE) >> 128;\n }\n }\n\n /**\n * @notice Calculate liquidity of a tick.\n * @param reserveA Tick reserve of token A.\n * @param reserveB Tick reserve of token B.\n * @param sqrtLowerTickPrice The square root price of the lower tick edge.\n * @param sqrtUpperTickPrice The square root price of the upper tick edge.\n */\n function getTickL(\n uint256 reserveA,\n uint256 reserveB,\n uint256 sqrtLowerTickPrice,\n uint256 sqrtUpperTickPrice\n ) internal pure returns (uint256 liquidity) {\n // known:\n // - sqrt price values are different\n // - reserveA and reserveB fit in 128 bit\n // - sqrt price is in (1e-7, 1e7)\n // - D18 max for uint256 is 1.15e59\n // - D18 min is 1e-18\n\n unchecked {\n // diff is in (5e-12, 4e6); max tick spacing is 10_000\n uint256 diff = sqrtUpperTickPrice - sqrtLowerTickPrice;\n\n // Need to maximize precision by shifting small values A and B up so\n // that they use more of the available bit range. Two constraints to\n // consider: we need A * B * diff / sqrtPrice to be bigger than 1e-18\n // when the bump is not in play. This constrains the threshold for\n // bumping to be at least 77 bit; ie, either a or b needs 2^77 which\n // means that term A * B * diff / sqrtPrice > 1e-18.\n //\n // At the other end, the second constraint is that b^2 needs to fit in\n // a 256-bit number, so, post bump, the max reserve value needs to be\n // less than 6e22. With a 78-bit threshold and a 57-bit bump, we have A\n // and B are in (1.4e-1, 4.4e22 (2^(78+57))) with bump, and one of A or\n // B is at least 2^78 without the bump, but the other reserve value may\n // be as small as 1 wei.\n uint256 precisionBump = 0;\n if ((reserveA >> 78) == 0 && (reserveB >> 78) == 0) {\n precisionBump = 57;\n reserveA <<= precisionBump;\n reserveB <<= precisionBump;\n }\n\n if (reserveB == 0) return Math.divDown(reserveA, diff) >> precisionBump;\n if (reserveA == 0)\n return Math.mulDivDown(reserveB.mulDown(sqrtLowerTickPrice), sqrtUpperTickPrice, diff) >> precisionBump;\n\n // b is in (7.2e-9 (2^57 / 1e7 / 2), 2.8e29 (2^(78+57) * 1e7 / 2)) with bump\n // b is in a subset of the same range without bump\n uint256 b = (reserveA.divDown(sqrtUpperTickPrice) + reserveB.mulDown(sqrtLowerTickPrice)) >> 1;\n\n // b^2 is in (5.1e-17, 4.8e58); and will not overflow on either end;\n // A*B is in (3e-13 (2^78 / 1e18 * 1e-18), 1.9e45) without bump and is in a subset range with bump\n // A*B*diff/sqrtUpper is in (1.5e-17 (3e-13 * 5e-12 * 1e7), 7.6e58);\n\n // Since b^2 is at the upper edge of the precision range, we are not\n // able to multiply the argument of the sqrt by 1e18, instead, we move\n // this factor outside of the sqrt. The resulting loss of precision\n // means that this liquidity value is a lower bound on the tick\n // liquidity\n return\n OzMath.mulDiv(\n b +\n Math.sqrt(\n (OzMath.mulDiv(b, b, ONE) +\n OzMath.mulDiv(reserveB.mulFloor(reserveA), diff, sqrtUpperTickPrice))\n ) *\n 1e9,\n sqrtUpperTickPrice,\n diff\n ) >> precisionBump;\n }\n }\n\n /**\n * @notice Calculate square root price of a tick. Returns left edge of the\n * tick if the tick has no reserves.\n * @param reserveA Tick reserve of token A.\n * @param reserveB Tick reserve of token B.\n * @param sqrtLowerTickPrice The square root price of the lower tick edge.\n * @param sqrtUpperTickPrice The square root price of the upper tick edge.\n * @return sqrtPrice The calculated square root price.\n */\n function getSqrtPrice(\n uint256 reserveA,\n uint256 reserveB,\n uint256 sqrtLowerTickPrice,\n uint256 sqrtUpperTickPrice,\n uint256 liquidity\n ) internal pure returns (uint256 sqrtPrice) {\n unchecked {\n if (reserveA == 0) {\n return sqrtLowerTickPrice;\n }\n if (reserveB == 0) {\n return sqrtUpperTickPrice;\n }\n sqrtPrice = Math.sqrt(\n ONE *\n (reserveA + liquidity.mulDown(sqrtLowerTickPrice)).divDown(\n reserveB + liquidity.divDown(sqrtUpperTickPrice)\n )\n );\n sqrtPrice = Math.boundValue(sqrtPrice, sqrtLowerTickPrice, sqrtUpperTickPrice);\n }\n }\n\n /**\n * @notice Calculate square root price of a tick. Returns left edge of the\n * tick if the tick has no reserves.\n * @param reserveA Tick reserve of token A.\n * @param reserveB Tick reserve of token B.\n * @param sqrtLowerTickPrice The square root price of the lower tick edge.\n * @param sqrtUpperTickPrice The square root price of the upper tick edge.\n * @return sqrtPrice The calculated square root price.\n * @return liquidity The calculated liquidity.\n */\n function getTickSqrtPriceAndL(\n uint256 reserveA,\n uint256 reserveB,\n uint256 sqrtLowerTickPrice,\n uint256 sqrtUpperTickPrice\n ) internal pure returns (uint256 sqrtPrice, uint256 liquidity) {\n liquidity = getTickL(reserveA, reserveB, sqrtLowerTickPrice, sqrtUpperTickPrice);\n sqrtPrice = getSqrtPrice(reserveA, reserveB, sqrtLowerTickPrice, sqrtUpperTickPrice, liquidity);\n }\n}\n// slither-disable-end divide-before-multiply" + }, + "solidity-bytes-utils/contracts/BytesLib.sol": { + "content": "// SPDX-License-Identifier: Unlicense\n/*\n * @title Solidity Bytes Arrays Utils\n * @author Gonçalo Sá \n *\n * @dev Bytes tightly packed arrays utility library for ethereum contracts written in Solidity.\n * The library lets you concatenate, slice and type cast bytes arrays both in memory and storage.\n */\npragma solidity >=0.8.0 <0.9.0;\n\n\nlibrary BytesLib {\n function concat(\n bytes memory _preBytes,\n bytes memory _postBytes\n )\n internal\n pure\n returns (bytes memory)\n {\n bytes memory tempBytes;\n\n assembly {\n // Get a location of some free memory and store it in tempBytes as\n // Solidity does for memory variables.\n tempBytes := mload(0x40)\n\n // Store the length of the first bytes array at the beginning of\n // the memory for tempBytes.\n let length := mload(_preBytes)\n mstore(tempBytes, length)\n\n // Maintain a memory counter for the current write location in the\n // temp bytes array by adding the 32 bytes for the array length to\n // the starting location.\n let mc := add(tempBytes, 0x20)\n // Stop copying when the memory counter reaches the length of the\n // first bytes array.\n let end := add(mc, length)\n\n for {\n // Initialize a copy counter to the start of the _preBytes data,\n // 32 bytes into its memory.\n let cc := add(_preBytes, 0x20)\n } lt(mc, end) {\n // Increase both counters by 32 bytes each iteration.\n mc := add(mc, 0x20)\n cc := add(cc, 0x20)\n } {\n // Write the _preBytes data into the tempBytes memory 32 bytes\n // at a time.\n mstore(mc, mload(cc))\n }\n\n // Add the length of _postBytes to the current length of tempBytes\n // and store it as the new length in the first 32 bytes of the\n // tempBytes memory.\n length := mload(_postBytes)\n mstore(tempBytes, add(length, mload(tempBytes)))\n\n // Move the memory counter back from a multiple of 0x20 to the\n // actual end of the _preBytes data.\n mc := end\n // Stop copying when the memory counter reaches the new combined\n // length of the arrays.\n end := add(mc, length)\n\n for {\n let cc := add(_postBytes, 0x20)\n } lt(mc, end) {\n mc := add(mc, 0x20)\n cc := add(cc, 0x20)\n } {\n mstore(mc, mload(cc))\n }\n\n // Update the free-memory pointer by padding our last write location\n // to 32 bytes: add 31 bytes to the end of tempBytes to move to the\n // next 32 byte block, then round down to the nearest multiple of\n // 32. If the sum of the length of the two arrays is zero then add\n // one before rounding down to leave a blank 32 bytes (the length block with 0).\n mstore(0x40, and(\n add(add(end, iszero(add(length, mload(_preBytes)))), 31),\n not(31) // Round down to the nearest 32 bytes.\n ))\n }\n\n return tempBytes;\n }\n\n function concatStorage(bytes storage _preBytes, bytes memory _postBytes) internal {\n assembly {\n // Read the first 32 bytes of _preBytes storage, which is the length\n // of the array. (We don't need to use the offset into the slot\n // because arrays use the entire slot.)\n let fslot := sload(_preBytes.slot)\n // Arrays of 31 bytes or less have an even value in their slot,\n // while longer arrays have an odd value. The actual length is\n // the slot divided by two for odd values, and the lowest order\n // byte divided by two for even values.\n // If the slot is even, bitwise and the slot with 255 and divide by\n // two to get the length. If the slot is odd, bitwise and the slot\n // with -1 and divide by two.\n let slength := div(and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)), 2)\n let mlength := mload(_postBytes)\n let newlength := add(slength, mlength)\n // slength can contain both the length and contents of the array\n // if length < 32 bytes so let's prepare for that\n // v. http://solidity.readthedocs.io/en/latest/miscellaneous.html#layout-of-state-variables-in-storage\n switch add(lt(slength, 32), lt(newlength, 32))\n case 2 {\n // Since the new array still fits in the slot, we just need to\n // update the contents of the slot.\n // uint256(bytes_storage) = uint256(bytes_storage) + uint256(bytes_memory) + new_length\n sstore(\n _preBytes.slot,\n // all the modifications to the slot are inside this\n // next block\n add(\n // we can just add to the slot contents because the\n // bytes we want to change are the LSBs\n fslot,\n add(\n mul(\n div(\n // load the bytes from memory\n mload(add(_postBytes, 0x20)),\n // zero all bytes to the right\n exp(0x100, sub(32, mlength))\n ),\n // and now shift left the number of bytes to\n // leave space for the length in the slot\n exp(0x100, sub(32, newlength))\n ),\n // increase length by the double of the memory\n // bytes length\n mul(mlength, 2)\n )\n )\n )\n }\n case 1 {\n // The stored value fits in the slot, but the combined value\n // will exceed it.\n // get the keccak hash to get the contents of the array\n mstore(0x0, _preBytes.slot)\n let sc := add(keccak256(0x0, 0x20), div(slength, 32))\n\n // save new length\n sstore(_preBytes.slot, add(mul(newlength, 2), 1))\n\n // The contents of the _postBytes array start 32 bytes into\n // the structure. Our first read should obtain the `submod`\n // bytes that can fit into the unused space in the last word\n // of the stored array. To get this, we read 32 bytes starting\n // from `submod`, so the data we read overlaps with the array\n // contents by `submod` bytes. Masking the lowest-order\n // `submod` bytes allows us to add that value directly to the\n // stored value.\n\n let submod := sub(32, slength)\n let mc := add(_postBytes, submod)\n let end := add(_postBytes, mlength)\n let mask := sub(exp(0x100, submod), 1)\n\n sstore(\n sc,\n add(\n and(\n fslot,\n 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00\n ),\n and(mload(mc), mask)\n )\n )\n\n for {\n mc := add(mc, 0x20)\n sc := add(sc, 1)\n } lt(mc, end) {\n sc := add(sc, 1)\n mc := add(mc, 0x20)\n } {\n sstore(sc, mload(mc))\n }\n\n mask := exp(0x100, sub(mc, end))\n\n sstore(sc, mul(div(mload(mc), mask), mask))\n }\n default {\n // get the keccak hash to get the contents of the array\n mstore(0x0, _preBytes.slot)\n // Start copying to the last used word of the stored array.\n let sc := add(keccak256(0x0, 0x20), div(slength, 32))\n\n // save new length\n sstore(_preBytes.slot, add(mul(newlength, 2), 1))\n\n // Copy over the first `submod` bytes of the new data as in\n // case 1 above.\n let slengthmod := mod(slength, 32)\n let mlengthmod := mod(mlength, 32)\n let submod := sub(32, slengthmod)\n let mc := add(_postBytes, submod)\n let end := add(_postBytes, mlength)\n let mask := sub(exp(0x100, submod), 1)\n\n sstore(sc, add(sload(sc), and(mload(mc), mask)))\n\n for {\n sc := add(sc, 1)\n mc := add(mc, 0x20)\n } lt(mc, end) {\n sc := add(sc, 1)\n mc := add(mc, 0x20)\n } {\n sstore(sc, mload(mc))\n }\n\n mask := exp(0x100, sub(mc, end))\n\n sstore(sc, mul(div(mload(mc), mask), mask))\n }\n }\n }\n\n function slice(\n bytes memory _bytes,\n uint256 _start,\n uint256 _length\n )\n internal\n pure\n returns (bytes memory)\n {\n // We're using the unchecked block below because otherwise execution ends \n // with the native overflow error code.\n unchecked {\n require(_length + 31 >= _length, \"slice_overflow\");\n }\n require(_bytes.length >= _start + _length, \"slice_outOfBounds\");\n\n bytes memory tempBytes;\n\n assembly {\n switch iszero(_length)\n case 0 {\n // Get a location of some free memory and store it in tempBytes as\n // Solidity does for memory variables.\n tempBytes := mload(0x40)\n\n // The first word of the slice result is potentially a partial\n // word read from the original array. To read it, we calculate\n // the length of that partial word and start copying that many\n // bytes into the array. The first word we copy will start with\n // data we don't care about, but the last `lengthmod` bytes will\n // land at the beginning of the contents of the new array. When\n // we're done copying, we overwrite the full first word with\n // the actual length of the slice.\n let lengthmod := and(_length, 31)\n\n // The multiplication in the next line is necessary\n // because when slicing multiples of 32 bytes (lengthmod == 0)\n // the following copy loop was copying the origin's length\n // and then ending prematurely not copying everything it should.\n let mc := add(add(tempBytes, lengthmod), mul(0x20, iszero(lengthmod)))\n let end := add(mc, _length)\n\n for {\n // The multiplication in the next line has the same exact purpose\n // as the one above.\n let cc := add(add(add(_bytes, lengthmod), mul(0x20, iszero(lengthmod))), _start)\n } lt(mc, end) {\n mc := add(mc, 0x20)\n cc := add(cc, 0x20)\n } {\n mstore(mc, mload(cc))\n }\n\n mstore(tempBytes, _length)\n\n //update free-memory pointer\n //allocating the array padded to 32 bytes like the compiler does now\n mstore(0x40, and(add(mc, 31), not(31)))\n }\n //if we want a zero-length slice let's just return a zero-length array\n default {\n tempBytes := mload(0x40)\n //zero out the 32 bytes slice we are about to return\n //we need to do it because Solidity does not garbage collect\n mstore(tempBytes, 0)\n\n mstore(0x40, add(tempBytes, 0x20))\n }\n }\n\n return tempBytes;\n }\n\n function toAddress(bytes memory _bytes, uint256 _start) internal pure returns (address) {\n require(_bytes.length >= _start + 20, \"toAddress_outOfBounds\");\n address tempAddress;\n\n assembly {\n tempAddress := div(mload(add(add(_bytes, 0x20), _start)), 0x1000000000000000000000000)\n }\n\n return tempAddress;\n }\n\n function toUint8(bytes memory _bytes, uint256 _start) internal pure returns (uint8) {\n require(_bytes.length >= _start + 1 , \"toUint8_outOfBounds\");\n uint8 tempUint;\n\n assembly {\n tempUint := mload(add(add(_bytes, 0x1), _start))\n }\n\n return tempUint;\n }\n\n function toUint16(bytes memory _bytes, uint256 _start) internal pure returns (uint16) {\n require(_bytes.length >= _start + 2, \"toUint16_outOfBounds\");\n uint16 tempUint;\n\n assembly {\n tempUint := mload(add(add(_bytes, 0x2), _start))\n }\n\n return tempUint;\n }\n\n function toUint32(bytes memory _bytes, uint256 _start) internal pure returns (uint32) {\n require(_bytes.length >= _start + 4, \"toUint32_outOfBounds\");\n uint32 tempUint;\n\n assembly {\n tempUint := mload(add(add(_bytes, 0x4), _start))\n }\n\n return tempUint;\n }\n\n function toUint64(bytes memory _bytes, uint256 _start) internal pure returns (uint64) {\n require(_bytes.length >= _start + 8, \"toUint64_outOfBounds\");\n uint64 tempUint;\n\n assembly {\n tempUint := mload(add(add(_bytes, 0x8), _start))\n }\n\n return tempUint;\n }\n\n function toUint96(bytes memory _bytes, uint256 _start) internal pure returns (uint96) {\n require(_bytes.length >= _start + 12, \"toUint96_outOfBounds\");\n uint96 tempUint;\n\n assembly {\n tempUint := mload(add(add(_bytes, 0xc), _start))\n }\n\n return tempUint;\n }\n\n function toUint128(bytes memory _bytes, uint256 _start) internal pure returns (uint128) {\n require(_bytes.length >= _start + 16, \"toUint128_outOfBounds\");\n uint128 tempUint;\n\n assembly {\n tempUint := mload(add(add(_bytes, 0x10), _start))\n }\n\n return tempUint;\n }\n\n function toUint256(bytes memory _bytes, uint256 _start) internal pure returns (uint256) {\n require(_bytes.length >= _start + 32, \"toUint256_outOfBounds\");\n uint256 tempUint;\n\n assembly {\n tempUint := mload(add(add(_bytes, 0x20), _start))\n }\n\n return tempUint;\n }\n\n function toBytes32(bytes memory _bytes, uint256 _start) internal pure returns (bytes32) {\n require(_bytes.length >= _start + 32, \"toBytes32_outOfBounds\");\n bytes32 tempBytes32;\n\n assembly {\n tempBytes32 := mload(add(add(_bytes, 0x20), _start))\n }\n\n return tempBytes32;\n }\n\n function equal(bytes memory _preBytes, bytes memory _postBytes) internal pure returns (bool) {\n bool success = true;\n\n assembly {\n let length := mload(_preBytes)\n\n // if lengths don't match the arrays are not equal\n switch eq(length, mload(_postBytes))\n case 1 {\n // cb is a circuit breaker in the for loop since there's\n // no said feature for inline assembly loops\n // cb = 1 - don't breaker\n // cb = 0 - break\n let cb := 1\n\n let mc := add(_preBytes, 0x20)\n let end := add(mc, length)\n\n for {\n let cc := add(_postBytes, 0x20)\n // the next line is the loop condition:\n // while(uint256(mc < end) + cb == 2)\n } eq(add(lt(mc, end), cb), 2) {\n mc := add(mc, 0x20)\n cc := add(cc, 0x20)\n } {\n // if any of these checks fails then arrays are not equal\n if iszero(eq(mload(mc), mload(cc))) {\n // unsuccess:\n success := 0\n cb := 0\n }\n }\n }\n default {\n // unsuccess:\n success := 0\n }\n }\n\n return success;\n }\n\n function equalStorage(\n bytes storage _preBytes,\n bytes memory _postBytes\n )\n internal\n view\n returns (bool)\n {\n bool success = true;\n\n assembly {\n // we know _preBytes_offset is 0\n let fslot := sload(_preBytes.slot)\n // Decode the length of the stored array like in concatStorage().\n let slength := div(and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)), 2)\n let mlength := mload(_postBytes)\n\n // if lengths don't match the arrays are not equal\n switch eq(slength, mlength)\n case 1 {\n // slength can contain both the length and contents of the array\n // if length < 32 bytes so let's prepare for that\n // v. http://solidity.readthedocs.io/en/latest/miscellaneous.html#layout-of-state-variables-in-storage\n if iszero(iszero(slength)) {\n switch lt(slength, 32)\n case 1 {\n // blank the last byte which is the length\n fslot := mul(div(fslot, 0x100), 0x100)\n\n if iszero(eq(fslot, mload(add(_postBytes, 0x20)))) {\n // unsuccess:\n success := 0\n }\n }\n default {\n // cb is a circuit breaker in the for loop since there's\n // no said feature for inline assembly loops\n // cb = 1 - don't breaker\n // cb = 0 - break\n let cb := 1\n\n // get the keccak hash to get the contents of the array\n mstore(0x0, _preBytes.slot)\n let sc := keccak256(0x0, 0x20)\n\n let mc := add(_postBytes, 0x20)\n let end := add(mc, mlength)\n\n // the next line is the loop condition:\n // while(uint256(mc < end) + cb == 2)\n for {} eq(add(lt(mc, end), cb), 2) {\n sc := add(sc, 1)\n mc := add(mc, 0x20)\n } {\n if iszero(eq(sload(sc), mload(mc))) {\n // unsuccess:\n success := 0\n cb := 0\n }\n }\n }\n }\n }\n default {\n // unsuccess:\n success := 0\n }\n }\n\n return success;\n }\n}\n" + } + }, + "settings": { + "optimizer": { + "enabled": true, + "runs": 200 + }, + "evmVersion": "paris", + "outputSelection": { + "*": { + "*": [ + "abi", + "evm.bytecode", + "evm.deployedBytecode", + "evm.methodIdentifiers", + "metadata", + "devdoc", + "userdoc", + "storageLayout", + "evm.gasEstimates" + ], + "": [ + "ast" + ] + } + }, + "metadata": { + "useLiteralContent": true + } + } +} \ No newline at end of file diff --git a/contracts/storageLayout/mainnet/OUSDVault.json b/contracts/storageLayout/mainnet/OUSDVault.json new file mode 100644 index 0000000000..56c38a08b5 --- /dev/null +++ b/contracts/storageLayout/mainnet/OUSDVault.json @@ -0,0 +1,460 @@ +{ + "solcVersion": "0.8.28", + "storage": [ + { + "label": "initialized", + "offset": 0, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "contracts/utils/Initializable.sol:12" + }, + { + "label": "initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "contracts/utils/Initializable.sol:17" + }, + { + "label": "______gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "Initializable", + "src": "contracts/utils/Initializable.sol:41" + }, + { + "label": "_deprecated_assets", + "offset": 0, + "slot": "51", + "type": "t_uint256", + "contract": "VaultStorage", + "src": "contracts/vault/VaultStorage.sol:67" + }, + { + "label": "_deprecated_allAssets", + "offset": 0, + "slot": "52", + "type": "t_array(t_address)dyn_storage", + "contract": "VaultStorage", + "src": "contracts/vault/VaultStorage.sol:69" + }, + { + "label": "strategies", + "offset": 0, + "slot": "53", + "type": "t_mapping(t_address,t_struct(Strategy)63574_storage)", + "contract": "VaultStorage", + "src": "contracts/vault/VaultStorage.sol:77" + }, + { + "label": "allStrategies", + "offset": 0, + "slot": "54", + "type": "t_array(t_address)dyn_storage", + "contract": "VaultStorage", + "src": "contracts/vault/VaultStorage.sol:79" + }, + { + "label": "_deprecated_priceProvider", + "offset": 0, + "slot": "55", + "type": "t_address", + "contract": "VaultStorage", + "src": "contracts/vault/VaultStorage.sol:82" + }, + { + "label": "rebasePaused", + "offset": 20, + "slot": "55", + "type": "t_bool", + "contract": "VaultStorage", + "src": "contracts/vault/VaultStorage.sol:84" + }, + { + "label": "capitalPaused", + "offset": 21, + "slot": "55", + "type": "t_bool", + "contract": "VaultStorage", + "src": "contracts/vault/VaultStorage.sol:87" + }, + { + "label": "_deprecated_redeemFeeBps", + "offset": 0, + "slot": "56", + "type": "t_uint256", + "contract": "VaultStorage", + "src": "contracts/vault/VaultStorage.sol:89" + }, + { + "label": "vaultBuffer", + "offset": 0, + "slot": "57", + "type": "t_uint256", + "contract": "VaultStorage", + "src": "contracts/vault/VaultStorage.sol:91" + }, + { + "label": "autoAllocateThreshold", + "offset": 0, + "slot": "58", + "type": "t_uint256", + "contract": "VaultStorage", + "src": "contracts/vault/VaultStorage.sol:93" + }, + { + "label": "rebaseThreshold", + "offset": 0, + "slot": "59", + "type": "t_uint256", + "contract": "VaultStorage", + "src": "contracts/vault/VaultStorage.sol:95" + }, + { + "label": "oToken", + "offset": 0, + "slot": "60", + "type": "t_contract(OUSD)58565", + "contract": "VaultStorage", + "src": "contracts/vault/VaultStorage.sol:98" + }, + { + "label": "_deprecated_rebaseHooksAddr", + "offset": 0, + "slot": "61", + "type": "t_address", + "contract": "VaultStorage", + "src": "contracts/vault/VaultStorage.sol:101" + }, + { + "label": "_deprecated_uniswapAddr", + "offset": 0, + "slot": "62", + "type": "t_address", + "contract": "VaultStorage", + "src": "contracts/vault/VaultStorage.sol:104" + }, + { + "label": "strategistAddr", + "offset": 0, + "slot": "63", + "type": "t_address", + "contract": "VaultStorage", + "src": "contracts/vault/VaultStorage.sol:107" + }, + { + "label": "_deprecated_assetDefaultStrategies", + "offset": 0, + "slot": "64", + "type": "t_uint256", + "contract": "VaultStorage", + "src": "contracts/vault/VaultStorage.sol:111" + }, + { + "label": "maxSupplyDiff", + "offset": 0, + "slot": "65", + "type": "t_uint256", + "contract": "VaultStorage", + "src": "contracts/vault/VaultStorage.sol:114" + }, + { + "label": "trusteeAddress", + "offset": 0, + "slot": "66", + "type": "t_address", + "contract": "VaultStorage", + "src": "contracts/vault/VaultStorage.sol:117" + }, + { + "label": "trusteeFeeBps", + "offset": 0, + "slot": "67", + "type": "t_uint256", + "contract": "VaultStorage", + "src": "contracts/vault/VaultStorage.sol:120" + }, + { + "label": "_deprecated_swapTokens", + "offset": 0, + "slot": "68", + "type": "t_array(t_address)dyn_storage", + "contract": "VaultStorage", + "src": "contracts/vault/VaultStorage.sol:123" + }, + { + "label": "_deprecated_ousdMetaStrategy", + "offset": 0, + "slot": "69", + "type": "t_address", + "contract": "VaultStorage", + "src": "contracts/vault/VaultStorage.sol:127" + }, + { + "label": "_deprecated_netOusdMintedForStrategy", + "offset": 0, + "slot": "70", + "type": "t_int256", + "contract": "VaultStorage", + "src": "contracts/vault/VaultStorage.sol:130" + }, + { + "label": "_deprecated_netOusdMintForStrategyThreshold", + "offset": 0, + "slot": "71", + "type": "t_uint256", + "contract": "VaultStorage", + "src": "contracts/vault/VaultStorage.sol:133" + }, + { + "label": "_deprecated_swapConfig", + "offset": 0, + "slot": "72", + "type": "t_uint256", + "contract": "VaultStorage", + "src": "contracts/vault/VaultStorage.sol:135" + }, + { + "label": "isMintWhitelistedStrategy", + "offset": 0, + "slot": "73", + "type": "t_mapping(t_address,t_bool)", + "contract": "VaultStorage", + "src": "contracts/vault/VaultStorage.sol:139" + }, + { + "label": "_deprecated_dripper", + "offset": 0, + "slot": "74", + "type": "t_address", + "contract": "VaultStorage", + "src": "contracts/vault/VaultStorage.sol:143" + }, + { + "label": "withdrawalQueueMetadata", + "offset": 0, + "slot": "75", + "type": "t_struct(WithdrawalQueueMetadata)63674_storage", + "contract": "VaultStorage", + "src": "contracts/vault/VaultStorage.sol:163" + }, + { + "label": "withdrawalRequests", + "offset": 0, + "slot": "77", + "type": "t_mapping(t_uint256,t_struct(WithdrawalRequest)63689_storage)", + "contract": "VaultStorage", + "src": "contracts/vault/VaultStorage.sol:177" + }, + { + "label": "withdrawalClaimDelay", + "offset": 0, + "slot": "78", + "type": "t_uint256", + "contract": "VaultStorage", + "src": "contracts/vault/VaultStorage.sol:182" + }, + { + "label": "lastRebase", + "offset": 0, + "slot": "79", + "type": "t_uint64", + "contract": "VaultStorage", + "src": "contracts/vault/VaultStorage.sol:185" + }, + { + "label": "dripDuration", + "offset": 8, + "slot": "79", + "type": "t_uint64", + "contract": "VaultStorage", + "src": "contracts/vault/VaultStorage.sol:188" + }, + { + "label": "rebasePerSecondMax", + "offset": 16, + "slot": "79", + "type": "t_uint64", + "contract": "VaultStorage", + "src": "contracts/vault/VaultStorage.sol:193" + }, + { + "label": "rebasePerSecondTarget", + "offset": 24, + "slot": "79", + "type": "t_uint64", + "contract": "VaultStorage", + "src": "contracts/vault/VaultStorage.sol:196" + }, + { + "label": "defaultStrategy", + "offset": 0, + "slot": "80", + "type": "t_address", + "contract": "VaultStorage", + "src": "contracts/vault/VaultStorage.sol:203" + }, + { + "label": "__gap", + "offset": 0, + "slot": "81", + "type": "t_array(t_uint256)42_storage", + "contract": "VaultStorage", + "src": "contracts/vault/VaultStorage.sol:206" + }, + { + "label": "_deprecated_wethAssetIndex", + "offset": 0, + "slot": "123", + "type": "t_uint256", + "contract": "VaultStorage", + "src": "contracts/vault/VaultStorage.sol:210" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_address)dyn_storage": { + "label": "address[]", + "numberOfBytes": "32" + }, + "t_array(t_uint256)42_storage": { + "label": "uint256[42]", + "numberOfBytes": "1344" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_contract(OUSD)58565": { + "label": "contract OUSD", + "numberOfBytes": "20" + }, + "t_int256": { + "label": "int256", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_bool)": { + "label": "mapping(address => bool)", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_struct(Strategy)63574_storage)": { + "label": "mapping(address => struct VaultStorage.Strategy)", + "numberOfBytes": "32" + }, + "t_mapping(t_uint256,t_struct(WithdrawalRequest)63689_storage)": { + "label": "mapping(uint256 => struct VaultStorage.WithdrawalRequest)", + "numberOfBytes": "32" + }, + "t_struct(Strategy)63574_storage": { + "label": "struct VaultStorage.Strategy", + "members": [ + { + "label": "isSupported", + "type": "t_bool", + "offset": 0, + "slot": "0" + }, + { + "label": "_deprecated", + "type": "t_uint256", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_struct(WithdrawalQueueMetadata)63674_storage": { + "label": "struct VaultStorage.WithdrawalQueueMetadata", + "members": [ + { + "label": "queued", + "type": "t_uint128", + "offset": 0, + "slot": "0" + }, + { + "label": "claimable", + "type": "t_uint128", + "offset": 16, + "slot": "0" + }, + { + "label": "claimed", + "type": "t_uint128", + "offset": 0, + "slot": "1" + }, + { + "label": "nextWithdrawalIndex", + "type": "t_uint128", + "offset": 16, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_struct(WithdrawalRequest)63689_storage": { + "label": "struct VaultStorage.WithdrawalRequest", + "members": [ + { + "label": "withdrawer", + "type": "t_address", + "offset": 0, + "slot": "0" + }, + { + "label": "claimed", + "type": "t_bool", + "offset": 20, + "slot": "0" + }, + { + "label": "timestamp", + "type": "t_uint40", + "offset": 21, + "slot": "0" + }, + { + "label": "amount", + "type": "t_uint128", + "offset": 0, + "slot": "1" + }, + { + "label": "queued", + "type": "t_uint128", + "offset": 16, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_uint128": { + "label": "uint128", + "numberOfBytes": "16" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint40": { + "label": "uint40", + "numberOfBytes": "5" + }, + "t_uint64": { + "label": "uint64", + "numberOfBytes": "8" + } + }, + "namespaces": {} +} \ No newline at end of file From 5c6bd49ba055dc314cc694ac0443c5a314b39621 Mon Sep 17 00:00:00 2001 From: Shah <10547529+shahthepro@users.noreply.github.com> Date: Mon, 16 Feb 2026 15:06:24 +0400 Subject: [PATCH 17/36] Update scripts and runlog (#2801) --- brownie/addresses.py | 6 ++++- brownie/runlogs/2026_02_strategist.py | 24 +++++++++++++++++++ .../mainnet/174_ousd_enable_async_withdraw.js | 1 - contracts/utils/addresses.js | 6 +++-- 4 files changed, 33 insertions(+), 4 deletions(-) create mode 100644 brownie/runlogs/2026_02_strategist.py diff --git a/brownie/addresses.py b/brownie/addresses.py index e722d88d4e..2e2e92fe1e 100644 --- a/brownie/addresses.py +++ b/brownie/addresses.py @@ -213,4 +213,8 @@ ## Safe Modules ETHEREUM_BRIDGE_HELPER_MODULE = "0x630C1763D38AbE76301F58909fa174E7B84A7ECD" BASE_BRIDGE_HELPER_MODULE = "0x362DBD4Ff662b2E2b05b9cEDC91da2Dd2c655b26" -PLUME_BRIDGE_HELPER_MODULE = "0xAc58C88349e00509FEc216E1B61d13b43315E18D" \ No newline at end of file +PLUME_BRIDGE_HELPER_MODULE = "0xAc58C88349e00509FEc216E1B61d13b43315E18D" + +## Morpho V2 (Base) Crosschain strategy +CROSSCHAIN_MORPHO_V2_BASE_MASTER_STRATEGY = "0xB1d624fc40824683e2bFBEfd19eB208DbBE00866" +CROSSCHAIN_MORPHO_V2_BASE_REMOTE_STRATEGY = "0xB1d624fc40824683e2bFBEfd19eB208DbBE00866" \ No newline at end of file diff --git a/brownie/runlogs/2026_02_strategist.py b/brownie/runlogs/2026_02_strategist.py new file mode 100644 index 0000000000..fdc8162a4f --- /dev/null +++ b/brownie/runlogs/2026_02_strategist.py @@ -0,0 +1,24 @@ +# ------------------------------------------------------------- +# Feb 16, 2026 - Allocate 100 USDC to the Crosschain strategy +# ------------------------------------------------------------- +from world import * +def main(): + with TemporaryForkForReallocations() as txs: + txs.append(vault_core.rebase({'from': MULTICHAIN_STRATEGIST})) + txs.append(vault_value_checker.takeSnapshot({'from': MULTICHAIN_STRATEGIST})) + + txs.append(vault_admin.depositToStrategy( + CROSSCHAIN_MORPHO_V2_BASE_MASTER_STRATEGY, + [usdc], + [100 * 10**6], + {'from': MULTICHAIN_STRATEGIST} + )) + vault_change = vault_core.totalValue() - vault_value_checker.snapshots(MULTICHAIN_STRATEGIST)[0] + supply_change = ousd.totalSupply() - vault_value_checker.snapshots(MULTICHAIN_STRATEGIST)[1] + profit = vault_change - supply_change + txs.append(vault_value_checker.checkDelta(profit, (1 * 10**18), vault_change, (1 * 10**18), {'from': MULTICHAIN_STRATEGIST})) + print("-----") + print("Profit", "{:.6f}".format(profit / 10**18), profit) + print("USDC supply change", "{:.6f}".format(supply_change / 10**18), supply_change) + print("Vault Change", "{:.6f}".format(vault_change / 10**18), vault_change) + print("-----") \ No newline at end of file diff --git a/contracts/deploy/mainnet/174_ousd_enable_async_withdraw.js b/contracts/deploy/mainnet/174_ousd_enable_async_withdraw.js index 5190c77d1a..d9b77d32b5 100644 --- a/contracts/deploy/mainnet/174_ousd_enable_async_withdraw.js +++ b/contracts/deploy/mainnet/174_ousd_enable_async_withdraw.js @@ -1,4 +1,3 @@ -const addresses = require("../../utils/addresses"); const { deploymentWithGovernanceProposal } = require("../../utils/deploy"); module.exports = deploymentWithGovernanceProposal( diff --git a/contracts/utils/addresses.js b/contracts/utils/addresses.js index e1250dabd1..0b7bc4fccd 100644 --- a/contracts/utils/addresses.js +++ b/contracts/utils/addresses.js @@ -708,8 +708,10 @@ addresses.hoodi.mockBeaconRoots = "0xdCfcAE4A084AA843eE446f400B23aA7B6340484b"; // Crosschain Strategy // TODO delete: master - remote test address: 0x1743658b284a843b47f555343dbb628d46d0c254 -addresses.base.CrossChainRemoteStrategy = "TODO"; -addresses.mainnet.CrossChainMasterStrategy = "TODO"; +addresses.base.CrossChainRemoteStrategy = + "0xB1d624fc40824683e2bFBEfd19eB208DbBE00866"; +addresses.mainnet.CrossChainMasterStrategy = + "0xB1d624fc40824683e2bFBEfd19eB208DbBE00866"; // CCTP Circle Contract addresses: https://developers.circle.com/cctp/references/contract-addresses addresses.CCTPTokenMessengerV2 = "0x28b5a0e9c621a5badaa536219b3a228c8168cf5d"; addresses.CCTPMessageTransmitterV2 = From 428371699e745fae6b34ad05e1052c85ddc17bf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment?= <55331875+clement-ux@users.noreply.github.com> Date: Mon, 16 Feb 2026 12:07:21 +0100 Subject: [PATCH 18/36] [CurvePB] Update CurvePoolBoosterBribesModule address (#2797) * [CurvePB] Update CurvePoolBoosterBribesModule address Update the CurvePoolBoosterBribesModule contract address to the new deployment. * lint * [CurvePB] Refactor bribes module contract initialization and use constants from ethers --- contracts/tasks/poolBooster.js | 8 +++----- contracts/utils/addresses.js | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/contracts/tasks/poolBooster.js b/contracts/tasks/poolBooster.js index 4b66d66c7a..0da2a4cc28 100644 --- a/contracts/tasks/poolBooster.js +++ b/contracts/tasks/poolBooster.js @@ -1,5 +1,5 @@ const { formatUnits, parseUnits } = require("ethers/lib/utils"); -const { Contract } = require("ethers"); +const { Contract, constants } = require("ethers"); const addresses = require("../utils/addresses"); const { logTxDetails } = require("../utils/txLogger"); @@ -384,10 +384,8 @@ async function manageBribes({ ); } - const bribesModuleContract = new ethers.Contract( + const bribesModuleContract = new Contract( BRIBES_MODULE, - // old pool booster bribes module - //"0x12856b1944a6a8c86c61D0F8B6e44C37726e86D7", bribesModuleAbi, signer ); @@ -413,7 +411,7 @@ async function manageBribes({ tx = await bribesModuleContract["manageBribes()"](); } else { // Build default arrays for totalRewardAmounts and extraDuration - const totalRewardAmounts = pools.map(() => ethers.constants.MaxUint256); + const totalRewardAmounts = pools.map(() => constants.MaxUint256); const extraDuration = pools.map(() => 1); log( diff --git a/contracts/utils/addresses.js b/contracts/utils/addresses.js index 0b7bc4fccd..924006d4f1 100644 --- a/contracts/utils/addresses.js +++ b/contracts/utils/addresses.js @@ -329,7 +329,7 @@ addresses.mainnet.CurveGaugeController = addresses.mainnet.CurvePoolBoosterOETH = "0x7B5e7aDEBC2da89912BffE55c86675CeCE59803E"; addresses.mainnet.CurvePoolBoosterBribesModule = - "0xfA01FE78bBD1ade8064C34FE537BA2E2670B2198"; + "0x82447F7C3eF0a628B0c614A3eA0898a5bb7c18fe"; // SSV network addresses.mainnet.SSV = "0x9D65fF81a3c488d585bBfb0Bfe3c7707c7917f54"; From d2fd6e96dfe3dc162ad659a1f78d689ff2496d80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment?= <55331875+clement-ux@users.noreply.github.com> Date: Mon, 16 Feb 2026 12:07:56 +0100 Subject: [PATCH 19/36] [MerklPB] Transfer PoolBoostCentralRegistry governance to strategist (#2798) --- ...transfer_pool_boost_registry_governance.js | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 contracts/deploy/mainnet/176_transfer_pool_boost_registry_governance.js diff --git a/contracts/deploy/mainnet/176_transfer_pool_boost_registry_governance.js b/contracts/deploy/mainnet/176_transfer_pool_boost_registry_governance.js new file mode 100644 index 0000000000..7994c0ca42 --- /dev/null +++ b/contracts/deploy/mainnet/176_transfer_pool_boost_registry_governance.js @@ -0,0 +1,33 @@ +const addresses = require("../../utils/addresses"); +const { deploymentWithGovernanceProposal } = require("../../utils/deploy"); + +module.exports = deploymentWithGovernanceProposal( + { + deployName: "176_transfer_pool_boost_registry_governance", + forceDeploy: false, + reduceQueueTime: true, + deployerIsProposer: false, + proposalId: + "82732039039728467243283754654134444302286420496150398783369816761252174679817", + }, + async () => { + const cPoolBoostCentralRegistryProxy = await ethers.getContract( + "PoolBoostCentralRegistryProxy" + ); + const cPoolBoostCentralRegistry = await ethers.getContractAt( + "PoolBoostCentralRegistry", + cPoolBoostCentralRegistryProxy.address + ); + + return { + name: "Transfer PoolBoostCentralRegistry governance to strategist", + actions: [ + { + contract: cPoolBoostCentralRegistry, + signature: "transferGovernance(address)", + args: [addresses.multichainStrategist], + }, + ], + }; + } +); From fb5dfd3fc5c67564e311097f357a13810d8bf9cb Mon Sep 17 00:00:00 2001 From: Nick Addison Date: Wed, 18 Feb 2026 21:36:16 +1100 Subject: [PATCH 20/36] Run log 17 feb 26 (#2803) * Run log 17 Feb - deposit SSV to clusters * Added native_staking_3_strat contract --- brownie/runlogs/2026_02_strategist.py | 58 ++++++++++++++++++++++++++- brownie/world.py | 1 + 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/brownie/runlogs/2026_02_strategist.py b/brownie/runlogs/2026_02_strategist.py index fdc8162a4f..fa6073b293 100644 --- a/brownie/runlogs/2026_02_strategist.py +++ b/brownie/runlogs/2026_02_strategist.py @@ -21,4 +21,60 @@ def main(): print("Profit", "{:.6f}".format(profit / 10**18), profit) print("USDC supply change", "{:.6f}".format(supply_change / 10**18), supply_change) print("Vault Change", "{:.6f}".format(vault_change / 10**18), vault_change) - print("-----") \ No newline at end of file + print("-----") + +# ------------------------------------------- +# Feb 17 2026 - Add SSV to second and third Native Staking SSV Clusters +# ------------------------------------------- + +from world import * + +def main(): + with TemporaryForkForReallocations() as txs: + amount = 228 * 10**18 + txs.append( + ssv.transfer( + OETH_NATIVE_STAKING_2_STRAT, + amount, + {'from': STRATEGIST} + ) + ) + + # use the following command to get cluster info: + # pnpm hardhat getClusterInfo --operatorids 752,753,754,755 --network mainnet --owner 0x4685dB8bF2Df743c861d71E6cFb5347222992076 + + txs.append( + native_staking_2_strat.depositSSV( + # SSV Operator Ids + [752, 753, 754, 755], + amount, + # SSV Cluster details: + # validatorCount, networkFeeIndex, index, active, balance + [485, 416695837505, 9585132, True, 433293212143542776597], + {'from': STRATEGIST} + ) + ) + + amount = 48 * 10**18 + txs.append( + ssv.transfer( + OETH_NATIVE_STAKING_3_STRAT, + amount, + {'from': STRATEGIST} + ) + ) + + # use the following command to get cluster info: + # pnpm hardhat getClusterInfo --operatorids 338,339,340,341 --network mainnet --owner 0xE98538A0e8C2871C2482e1Be8cC6bd9F8E8fFD63 + + txs.append( + native_staking_3_strat.depositSSV( + # SSV Operator Ids + [338, 339, 340, 341], + amount, + # SSV Cluster details: + # validatorCount, networkFeeIndex, index, active, balance + [88, 406033866602, 0, True, 93251662533120000000], + {'from': STRATEGIST} + ) + ) diff --git a/brownie/world.py b/brownie/world.py index 408f6cc640..6fa66032c5 100644 --- a/brownie/world.py +++ b/brownie/world.py @@ -63,6 +63,7 @@ frxeth_redeem_strat = load_contract('frxeth_redeem_strat', OETH_FRAX_ETH_REDEEM_STRAT) native_staking_strat = load_contract('native_staking_strat', OETH_NATIVE_STAKING_STRAT) native_staking_2_strat = load_contract('native_staking_strat', OETH_NATIVE_STAKING_2_STRAT) +native_staking_3_strat = load_contract('native_staking_strat', OETH_NATIVE_STAKING_3_STRAT) lido_withdrawal_strat = load_contract('lido_withdrawal_strat', OETH_LIDO_WITHDRAWAL_STRAT) ousd_metapool = load_contract("ousd_metapool", OUSD_METAPOOL) From bdffa61c623d9c635c4c851aac4cca351ec1fe06 Mon Sep 17 00:00:00 2001 From: Shah <10547529+shahthepro@users.noreply.github.com> Date: Wed, 18 Feb 2026 21:34:14 +0400 Subject: [PATCH 21/36] Add scripts to change Crosschain strategy operator (#2802) * Add scripts to change Crosschain strategy operator * Run base deployment --- ...043_change_crosschain_strategy_operator.js | 24 ++++++++ ...177_change_crosschain_strategy_operator.js | 29 ++++++++++ contracts/deployments/base/.migrations.json | 3 +- ..._crosschain_strategy_operator.execute.json | 52 +++++++++++++++++ ...crosschain_strategy_operator.schedule.json | 57 +++++++++++++++++++ 5 files changed, 164 insertions(+), 1 deletion(-) create mode 100644 contracts/deploy/base/043_change_crosschain_strategy_operator.js create mode 100644 contracts/deploy/mainnet/177_change_crosschain_strategy_operator.js create mode 100644 contracts/deployments/base/operations/043_change_crosschain_strategy_operator.execute.json create mode 100644 contracts/deployments/base/operations/043_change_crosschain_strategy_operator.schedule.json diff --git a/contracts/deploy/base/043_change_crosschain_strategy_operator.js b/contracts/deploy/base/043_change_crosschain_strategy_operator.js new file mode 100644 index 0000000000..72c99b83fc --- /dev/null +++ b/contracts/deploy/base/043_change_crosschain_strategy_operator.js @@ -0,0 +1,24 @@ +const { deployOnBase } = require("../../utils/deploy-l2"); +const addresses = require("../../utils/addresses"); + +module.exports = deployOnBase( + { + deployName: "043_change_crosschain_strategy_operator", + }, + async () => { + const cCrossChainRemoteStrategy = await ethers.getContractAt( + "CrossChainRemoteStrategy", + addresses.base.CrossChainRemoteStrategy + ); + + return { + actions: [ + { + contract: cCrossChainRemoteStrategy, + signature: "setOperator(address)", + args: [addresses.base.OZRelayerAddress], + }, + ], + }; + } +); diff --git a/contracts/deploy/mainnet/177_change_crosschain_strategy_operator.js b/contracts/deploy/mainnet/177_change_crosschain_strategy_operator.js new file mode 100644 index 0000000000..395fab1981 --- /dev/null +++ b/contracts/deploy/mainnet/177_change_crosschain_strategy_operator.js @@ -0,0 +1,29 @@ +const addresses = require("../../utils/addresses"); +const { deploymentWithGovernanceProposal } = require("../../utils/deploy"); + +module.exports = deploymentWithGovernanceProposal( + { + deployName: "177_change_crosschain_strategy_operator", + forceDeploy: false, + reduceQueueTime: true, + deployerIsProposer: false, + proposalId: "", + }, + async () => { + const cCrossChainMasterStrategy = await ethers.getContractAt( + "CrossChainMasterStrategy", + addresses.mainnet.CrossChainMasterStrategy + ); + + return { + name: "Change Operator of Crosschain Strategy", + actions: [ + { + contract: cCrossChainMasterStrategy, + signature: "setOperator(address)", + args: [addresses.mainnet.validatorRegistrator], + }, + ], + }; + } +); diff --git a/contracts/deployments/base/.migrations.json b/contracts/deployments/base/.migrations.json index 8474d4765b..60e274c336 100644 --- a/contracts/deployments/base/.migrations.json +++ b/contracts/deployments/base/.migrations.json @@ -38,5 +38,6 @@ "038_vault_upgrade": 1752579215, "039_pool_booster_factory": 1756293140, "041_crosschain_strategy_proxies": 1770737618, - "042_crosschain_strategy": 1770807435 + "042_crosschain_strategy": 1770807435, + "043_change_crosschain_strategy_operator": 1771403173 } \ No newline at end of file diff --git a/contracts/deployments/base/operations/043_change_crosschain_strategy_operator.execute.json b/contracts/deployments/base/operations/043_change_crosschain_strategy_operator.execute.json new file mode 100644 index 0000000000..0bfdd568dd --- /dev/null +++ b/contracts/deployments/base/operations/043_change_crosschain_strategy_operator.execute.json @@ -0,0 +1,52 @@ +{ + "version": "1.0", + "chainId": "8453", + "createdAt": 1771403173, + "meta": { + "name": "Transaction Batch", + "description": "", + "txBuilderVersion": "1.16.1", + "createdFromSafeAddress": "0x92A19381444A001d62cE67BaFF066fA1111d7202", + "createdFromOwnerAddress": "" + }, + "transactions": [ + { + "to": "0xf817cb3092179083c48c014688D98B72fB61464f", + "value": "0", + "data": null, + "contractMethod": { + "inputs": [ + { + "type": "address[]", + "name": "targets" + }, + { + "type": "uint256[]", + "name": "values" + }, + { + "type": "bytes[]", + "name": "payloads" + }, + { + "type": "bytes32", + "name": "predecessor" + }, + { + "type": "bytes32", + "name": "salt" + } + ], + "name": "executeBatch", + "payable": true + }, + "contractInputsValues": { + "targets": "[\"0xB1d624fc40824683e2bFBEfd19eB208DbBE00866\"]", + "values": "[\"0\"]", + "payloads": "[\"0xb3ab15fb000000000000000000000000c0d6fa24d135c006de5b8b2955935466a03d920a\"]", + "predecessor": "0x0000000000000000000000000000000000000000000000000000000000000000", + "salt": "0x0fd63a88f7398435976aba72cf7d151579669202dba834daf3a63b09cab78132" + } + } + ] +} \ No newline at end of file diff --git a/contracts/deployments/base/operations/043_change_crosschain_strategy_operator.schedule.json b/contracts/deployments/base/operations/043_change_crosschain_strategy_operator.schedule.json new file mode 100644 index 0000000000..b3b25ed9b9 --- /dev/null +++ b/contracts/deployments/base/operations/043_change_crosschain_strategy_operator.schedule.json @@ -0,0 +1,57 @@ +{ + "version": "1.0", + "chainId": "8453", + "createdAt": 1771403173, + "meta": { + "name": "Transaction Batch", + "description": "", + "txBuilderVersion": "1.16.1", + "createdFromSafeAddress": "0x92A19381444A001d62cE67BaFF066fA1111d7202", + "createdFromOwnerAddress": "" + }, + "transactions": [ + { + "to": "0xf817cb3092179083c48c014688D98B72fB61464f", + "value": "0", + "data": null, + "contractMethod": { + "inputs": [ + { + "type": "address[]", + "name": "targets" + }, + { + "type": "uint256[]", + "name": "values" + }, + { + "type": "bytes[]", + "name": "payloads" + }, + { + "type": "bytes32", + "name": "predecessor" + }, + { + "type": "bytes32", + "name": "salt" + }, + { + "type": "uint256", + "name": "delay" + } + ], + "name": "scheduleBatch", + "payable": false + }, + "contractInputsValues": { + "targets": "[\"0xB1d624fc40824683e2bFBEfd19eB208DbBE00866\"]", + "values": "[\"0\"]", + "payloads": "[\"0xb3ab15fb000000000000000000000000c0d6fa24d135c006de5b8b2955935466a03d920a\"]", + "predecessor": "0x0000000000000000000000000000000000000000000000000000000000000000", + "salt": "0x0fd63a88f7398435976aba72cf7d151579669202dba834daf3a63b09cab78132", + "delay": "172800" + } + } + ] +} \ No newline at end of file From 8925265be6afea288baaffa75cabea1e46a1da04 Mon Sep 17 00:00:00 2001 From: Nick Addison Date: Thu, 19 Feb 2026 13:55:24 +1100 Subject: [PATCH 22/36] Fix the Native Staking Strategy fork tests for the second SSV cluster (#2757) --- contracts/test/strategies/nativeSsvStaking.mainnet.fork-test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/test/strategies/nativeSsvStaking.mainnet.fork-test.js b/contracts/test/strategies/nativeSsvStaking.mainnet.fork-test.js index 07addc1e97..0ee20afcb7 100644 --- a/contracts/test/strategies/nativeSsvStaking.mainnet.fork-test.js +++ b/contracts/test/strategies/nativeSsvStaking.mainnet.fork-test.js @@ -74,7 +74,7 @@ describe("ForkTest: Second Native SSV Staking Strategy", function () { testValidator: { publicKey: "0xae24289bd670bfbdd3bc904596b475d080dde3415506f1abe1fb76ff292ff6bd743d710061b9e2b16fd8541a76fe53ee", - operatorIds: [348, 352, 361, 377], + operatorIds: [752, 753, 754, 755], sharesData: "0xb2cff426a8898f801feed0e3efb1d036def14590809426995df98ad243c0927987c0f207a3c2d9b48d47a0ceec80eb2d0b1839d84ab1a2d75fd48aaf4a859ab8d2ae776b55f64e2c40733a44697c924882a5a8688790ec4203b8847a61e84803a8252f2a2812ec9854b381fc12a222ea8764e07084c8a7873426f62a43ca1b88dfa258713a5ff7749290add650843533a5b2a8430c1cf5e476d5498736b384464db057b05f8a120c4a08b84dfb8a9c2c6adfdcb5660386cd582c610eb06422628dfc08496bf0edfffc6e1e05964c710a104ed6c2d700c823243fce8a3c76575ca1618113e036498f839830c5d24d604ab13769367f9467b8f3771082a29a8ce96194da370a0550ce5d09975590ba5e1fa154382ba0bc2d7ebd7fd2192978998d53d845103bbfa2f8f3680245b005bc802109ea6a8449fce0fffcfa712cc8bbf6672eda7bbfd209644190a1c383faac861aad1534f50acd7c58104c4ad27e0b6d4b44c80e52ede1b0f066cae285e193f356f193872d40586020c75a68c011d2ca172126139d1728985c9ca9b76db5639ec0d265b9bf239ad3ed94a55709442031b18db6fd430b25138b1d7a17484cde1433e8d5837c3c806630135187d27261991e94f84d3ceb1fc2eaa44042cc09f10cea84b0ef6a00cb07aebdd7df6a4e14fd6efce5954d19219efd3419c338a6fa9644db5fdeb5b226cc008c0599f0c02bf3c99c74ff80e5ea2c2d0e47304ee21dfdf870599288dedd0977711af5cc467179765df58b0a489c906d85f5855c1d7359cab73e22229f354d9f9e0a1e623d9264988df14da8b710dfe42a895cbdc10ac25bd0d3412e9c90a632c8f1890b3d412bf6756367893f800b8895f000645fb56bf1b956cca68ee19238e64047b4b75be13c0316c5a220af0e28f0d9948fd74b7e261cddb0e79f80349686a0089a9d50baa79bdccd4ac9392ec857530456a9f7302ca091a640feb4a6f1c31c1c6fd1847e20986d2e87f84a01522d3004ddf002c56d5e9549eca04ce3738b8bc5a7e239c967906305d820f6a3b8f1f6a61af7fcdfabd935d068f8cd0cc58c84dc120ef20df1ea492c70937282a9e5a0857511ab7c6d6300947da3f0f7fa4d022453163c1d82e78b15182d9a2878fb96ba0f08a71288772249f52a34dff4b7ae106bb76055e05309c4701abdf685d68163d0a705b162e91c409c7d8c386dc24f2d7c01c150017b365c6d72304f082d4030057917fb55a927ed5a6150e9e70a8b12cfadca1bfba0e85f694c946ef781fb8344285c28adc2e358513ed9ec2a1fb80935de88ec2cdac6e0d538e25043716ec8b29c157fb41a3d887c2025ffc71b414b977f9b81c497ee8bd9db042d4121dc4a8c5220a0f438dbdcde55580fb8c8b3aec3a53ffe958056653fd9bf58aa3b060a99c38c94035a27a6bfd66767965090526f1f403f7332914d2726d2f2bdd979895031a1afad4e112d4471193080e13a301e7a6ad24a217d94a5c964a6118dbcc9b2dfd3a0180189c0ca4dee3e8d24a18b904e826e324256d478deb66b9b47cdb65de2a2b951787dad3536a839b230313d6fd202364a8a3a0ce033fb8bf6a32d4b7c94af54f5ca7d861497d50a593606437f7420485ccda17977eb495967f700ef4bcc9f8d2c2ed4933b26418768b31ae02a0ca2fbaa7b63f349619278bac3f3ef5796c669c3ecb9ed19f2ffa453b4801f4ac78938a11c8e7a778a7ae8e5813dd93414b1b9912e4466519216ac58a6b538d03128feee6235adef2ecc57d9b2d9fec719fb8c8aac0bd7f491860658e8f32ee6285c264c843c6142d578abfc9bab330355bed41a12862669f0b88f894cce277bcdbd94", // This sig isn't correct but will do for testing From c2fe983cda3ad3a0866bf0600f0aba4bd7524ff1 Mon Sep 17 00:00:00 2001 From: Shah <10547529+shahthepro@users.noreply.github.com> Date: Thu, 19 Feb 2026 15:15:35 +0400 Subject: [PATCH 23/36] Repo Cleanup (#2799) * Remove stuff * Remove stuff * Remove more stuff * Fix unit tests * Fix tests * remove more stuff * revert mock files * few more tweaks * more delete * remove older deployment files * Restore mock contract * Fix fixtures * Repo cleanup additions (#2804) * Removed old generated contract docs * Added Curve AMO Strategy docs * Fixed amoStrat Hardhat task for new OETH Curve AMO * Removed ConvexEthMetaStrategy and Generalized4626USDTStrategy * Added new Cross-chain Strategy to OUSD Vault fork test * Fix OUSD vault fork test * Fix Should claim Morpho rewards fork test Removed old Morpho Strategies from fixture * Correct operator ids for second SSV cluster in fork tests * prettier * Fix Curve AMO fork tests * Add proposal id to 177_change_crosschain_strategy_operator * Fix CrossChainMasterStrategy fork tests * Fixed claim CRV rewards fork tests * Fix Merkl Pool Booster fork test * Fix claiming rewards on Yearn's Morpho OUSD v2 Strategy * morphoOUSDv2Fixture to handle outstanding withdrawal requests * fix ForkTest: CrossChainRemoteStrategy fork tests * prettier --------- Co-authored-by: Nick Addison --- brownie/contracts/MockOracle.sol | 10 - contracts/abi/crvMinter.json | 94 + contracts/contracts/buyback/ARMBuyback.sol | 13 - .../contracts/buyback/AbstractBuyback.sol | 325 ---- contracts/contracts/buyback/OETHBuyback.sol | 13 - contracts/contracts/buyback/OUSDBuyback.sol | 13 - contracts/contracts/harvest/Harvester.sol | 10 - .../contracts/harvest/OETHBaseHarvester.sol | 233 --- contracts/contracts/harvest/OETHDripper.sol | 12 - contracts/contracts/harvest/OETHHarvester.sol | 10 - contracts/contracts/harvest/README.md | 16 - contracts/contracts/interfaces/IBuyback.sol | 6 - .../contracts/interfaces/IComptroller.sol | 14 - .../contracts/interfaces/IFraxETHMinter.sol | 9 - contracts/contracts/interfaces/IOneInch.sol | 48 - contracts/contracts/interfaces/IRETH.sol | 37 - contracts/contracts/interfaces/ISfrxETH.sol | 127 -- contracts/contracts/interfaces/ISwapper.sol | 19 - .../interfaces/balancer/IMetaStablePool.sol | 11 - .../balancer/IOracleWeightedPool.sol | 44 - .../contracts/interfaces/morpho/ILens.sol | 305 --- .../contracts/interfaces/morpho/IMorpho.sol | 18 - .../contracts/interfaces/morpho/Types.sol | 76 - .../morpho/compound/ICompoundOracle.sol | 6 - .../contracts/mocks/Mock1InchSwapRouter.sol | 109 -- contracts/contracts/mocks/MockAAVEToken.sol | 8 - contracts/contracts/mocks/MockAave.sol | 100 - .../mocks/MockAaveIncentivesController.sol | 52 - contracts/contracts/mocks/MockAura.sol | 8 - contracts/contracts/mocks/MockBAL.sol | 8 - .../contracts/mocks/MockBalancerVault.sol | 62 - contracts/contracts/mocks/MockCOMP.sol | 8 - contracts/contracts/mocks/MockCToken.sol | 105 - contracts/contracts/mocks/MockCVXLocker.sol | 27 - contracts/contracts/mocks/MockComptroller.sol | 12 - contracts/contracts/mocks/MockEvilDAI.sol | 29 - .../mocks/MockEvilReentrantContract.sol | 130 -- .../contracts/mocks/MockFrxETHMinter.sol | 24 - .../mocks/MockMaverickDistributor.sol | 35 - .../contracts/mocks/MockMetadataToken.sol | 6 - .../mocks/MockMintableUniswapPair.sol | 21 - contracts/contracts/mocks/MockOETHVault.sol | 18 - .../contracts/mocks/MockOETHVaultAdmin.sol | 24 - contracts/contracts/mocks/MockOGV.sol | 8 - contracts/contracts/mocks/MockOracle.sol | 88 - .../mocks/MockOracleRouterNoStale.sol | 40 - .../mocks/MockOracleWeightedPool.sol | 25 - contracts/contracts/mocks/MockRETH.sol | 19 - .../mocks/MockRoosterAMOStrategy.sol | 46 - contracts/contracts/mocks/MockStkAave.sol | 69 - contracts/contracts/mocks/MockSwapper.sol | 29 - contracts/contracts/mocks/MockTUSD.sol | 12 - contracts/contracts/mocks/MockUniswapPair.sol | 98 - contracts/contracts/mocks/MockfrxETH.sol | 8 - contracts/contracts/mocks/MocksfrxETH.sol | 75 - contracts/contracts/mocks/MockstETH.sol | 8 - contracts/contracts/mocks/curve/Mock3CRV.sol | 13 - .../contracts/mocks/curve/MockBooster.sol | 152 -- contracts/contracts/mocks/curve/MockCRV.sol | 12 - .../contracts/mocks/curve/MockCRVMinter.sol | 20 - contracts/contracts/mocks/curve/MockCVX.sol | 8 - .../mocks/curve/MockCurveAbstractMetapool.sol | 151 -- .../contracts/mocks/curve/MockCurveGauge.sol | 30 - .../mocks/curve/MockCurveMetapool.sol | 13 - .../contracts/mocks/curve/MockCurvePool.sol | 148 -- .../contracts/mocks/curve/MockRewardPool.sol | 127 -- contracts/contracts/proxies/Proxies.sol | 86 - .../contracts/strategies/AaveStrategy.sol | 305 --- .../strategies/AbstractCompoundStrategy.sol | 55 - .../strategies/AbstractConvexMetaStrategy.sol | 251 --- .../strategies/AbstractCurveStrategy.sol | 303 --- .../contracts/strategies/CompoundStrategy.sol | 230 --- .../strategies/ConvexEthMetaStrategy.sol | 617 ------ .../ConvexGeneralizedMetaStrategy.sol | 134 -- .../strategies/ConvexOUSDMetaStrategy.sol | 190 -- .../contracts/strategies/ConvexStrategy.sol | 165 -- .../Generalized4626USDTStrategy.sol | 35 - contracts/contracts/strategies/ICompound.sol | 67 - .../contracts/strategies/ICurveMetaPool.sol | 56 - .../strategies/MorphoAaveStrategy.sol | 235 --- .../strategies/MorphoCompoundStrategy.sol | 251 --- contracts/contracts/strategies/README.md | 58 +- .../balancer/AbstractAuraStrategy.sol | 108 -- .../balancer/AbstractBalancerStrategy.sol | 489 ----- .../balancer/BalancerMetaPoolStrategy.sol | 561 ------ .../contracts/strategies/balancer/README.md | 15 - .../balancer/VaultReentrancyLib.sol | 84 - .../strategies/plume/RoosterAMOStrategy.sol | 1205 ------------ .../contracts/strategies/sonic/README.md | 14 - contracts/contracts/swapper/README.md | 11 - .../contracts/swapper/Swapper1InchV5.sol | 107 -- contracts/contracts/utils/BalancerErrors.sol | 305 --- contracts/deploy/base/001_woeth_on_base.js | 45 - contracts/deploy/base/002_base_oracles.js | 29 - .../deploy/base/003_base_vault_and_token.js | 168 -- contracts/deploy/base/004_super_oeth.js | 45 - .../deploy/base/005_mutlisig_harvester.js | 70 - .../deploy/base/006_base_amo_strategy.js | 144 -- .../deploy/base/007_bridged_woeth_strategy.js | 103 - contracts/deploy/base/008_oethb_zapper.js | 27 - contracts/deploy/base/009_upgrade_vault.js | 77 - .../deploy/base/010_upgrade_vault_core.js | 28 - .../deploy/base/011_transfer_governance.js | 87 - contracts/deploy/base/012_claim_governance.js | 74 - .../deploy/base/013_revoke_admin_role.js | 31 - .../deploy/base/014_fixed_rate_dripper.js | 30 - contracts/deploy/base/015_harvester.js | 73 - .../deploy/base/016_timelock_2d_delay.js | 25 - contracts/deploy/base/017_upgrade_amo.js | 71 - .../deploy/base/018_strategist_as_executor.js | 27 - .../deploy/base/019_async_withdrawals.js | 45 - contracts/deploy/base/020_upgrade_amo.js | 28 - .../deploy/base/021_multichain_strategist.js | 50 - contracts/deploy/base/022_upgrade_oeth.js | 30 - .../deploy/base/023_update_weth_share.js | 31 - .../deploy/base/024_multisig_as_canceller.js | 27 - contracts/deploy/base/025_base_curve_amo.js | 84 - contracts/deploy/base/026_harvester_v2.js | 73 - .../deploy/base/027_base_curve_amo_upgrade.js | 69 - .../deploy/base/028_vault_woeth_upgrade.js | 83 - .../base/030_claimbribes_safe_module.js | 125 -- .../base/031_enable_buyback_operator.js | 26 - contracts/deploy/base/032_vault_perf_fee.js | 25 - contracts/deploy/base/033_bridge_module.js | 39 - .../deploy/base/034_claimbribes_module_v2.js | 53 - .../035_claimbribes_module_old_guardian.js | 34 - .../deploy/base/036_oethb_upgrade_EIP7702.js | 30 - contracts/deploy/base/037_deploy_harvester.js | 99 - contracts/deploy/base/038_vault_upgrade.js | 37 - .../deploy/base/039_pool_booster_factory.js | 79 - contracts/deploy/deployActions.js | 840 +------- contracts/deploy/mainnet/000_mock.js | 353 +--- contracts/deploy/mainnet/001_core.js | 25 +- .../mainnet/102_2nd_native_ssv_staking.js | 256 --- .../deploy/mainnet/103_oeth_withdraw_queue.js | 142 -- .../mainnet/104_upgrade_staking_strategies.js | 125 -- .../mainnet/105_ousd_remove_flux_strat.js | 36 - .../mainnet/106_ousd_metamorpho_usdc.js | 98 - contracts/deploy/mainnet/107_arm_buyback.js | 63 - contracts/deploy/mainnet/108_vault_upgrade.js | 66 - .../mainnet/109_3rd_native_ssv_staking.js | 213 --- .../deploy/mainnet/110_transfer_morpho.js | 69 - .../mainnet/111_morpho_wrap_and_transfer.js | 52 - .../mainnet/112_ousd_morpho_gauntlet_usdc.js | 96 - .../mainnet/113_ousd_morpho_gauntlet_usdt.js | 96 - .../deploy/mainnet/114_simple_harvester.js | 48 - contracts/deploy/mainnet/115_ousd_upgrade.js | 35 - contracts/deploy/mainnet/116_oeth_upgrade.js | 39 - .../mainnet/117_oeth_fixed_rate_dripper.js | 100 - .../mainnet/118_multichain_strategist.js | 77 - .../mainnet/119_multisig_as_canceller.js | 37 - .../deploy/mainnet/120_remove_ousd_amo.js | 43 - .../deploy/mainnet/121_pool_booster_curve.js | 123 -- .../mainnet/122_delegate_yield_curve_pool.js | 31 - .../deploy/mainnet/123_simple_harvester_v2.js | 176 -- .../mainnet/124_remove_ousd_aave_strat.js | 50 - .../mainnet/125_buyback_treasury_address.js | 40 - .../mainnet/126_update_amo_mint_threshold.js | 28 - .../mainnet/127_replace_dai_with_usds.js | 238 --- .../mainnet/128_remove_ousd_morpho_aave.js | 40 - .../deploy/mainnet/129_oeth_vault_upgrade.js | 60 - .../130_update_votemarket_addresses.js | 48 - .../deploy/mainnet/131_ousd_usdc_curve_amo.js | 101 - .../deploy/mainnet/133_omnichain_adapter.js | 30 - .../deploy/mainnet/134_vault_woeth_upgrade.js | 83 - .../deploy/mainnet/135_vault_wousd_upgrade.js | 81 - .../mainnet/136_upgrade_morpho_strategies.js | 83 - .../deploy/mainnet/137_oeth_weth_curve_amo.js | 97 - .../deploy/mainnet/138_buyback_deprecation.js | 93 - .../mainnet/139_deprecate_ousd_harvester.js | 72 - .../140_remove_1st_native_ssv_staking.js | 75 - .../deploy/mainnet/141_claimrewards_module.js | 74 - .../mainnet/142_bridge_helper_module.js | 49 - .../143_bridge_helper_module_upgrade.js | 49 - .../mainnet/144_ousd_oeth_upgrade_EIP7702.js | 42 - .../mainnet/145_deploy_xogn_rewards_module.js | 45 - .../146_morpho_strategies_reward_tokens.js | 75 - .../147_claim_rewards_module_upgrade.js | 51 - .../148_xogn_module_for_5_of_8_multisig.js | 37 - contracts/deploy/mainnet/149_xogn_module_7.js | 36 - contracts/deploy/mainnet/150_vault_upgrade.js | 54 - .../deploy/mainnet/151_curve_pb_module.js | 41 - .../deploy/mainnet/152_pool_booster_setup.js | 96 - .../deploy/mainnet/153_beacon_root_testing.js | 24 - .../mainnet/154_upgrade_native_staking.js | 62 - contracts/deploy/mainnet/155_oeth_zapper.js | 44 - .../deploy/mainnet/169_crosschain_strategy.js | 3 +- ...177_change_crosschain_strategy_operator.js | 3 +- .../deploy/mainnet/999_fork_test_setup.js | 25 +- contracts/deploy/plume/008_rooster_amo.js | 146 -- contracts/deploy/sonic/001_vault_and_token.js | 220 --- contracts/deploy/sonic/002_oracle_router.js | 32 - .../sonic/003_sonic_staking_strategy.js | 154 -- .../deploy/sonic/004_timelock_1d_delay.js | 25 - .../deploy/sonic/005_multisig_as_canceller.js | 27 - contracts/deploy/sonic/006_yf_swpx_os_pool.js | 26 - .../sonic/007_strategist_as_executor.js | 27 - .../deploy/sonic/008_swapx_yield_forward.js | 33 - contracts/deploy/sonic/009_swapx_amo.js | 120 -- .../deploy/sonic/010_swapx_yield_forward.js | 35 - .../deploy/sonic/011_pool_booster_factory.js | 115 -- contracts/deploy/sonic/012_tb_yf_batch_1.js | 157 -- contracts/deploy/sonic/013_vault_config.js | 35 - contracts/deploy/sonic/014_wrapped_sonic.js | 38 - contracts/deploy/sonic/015_redeem_fee.js | 22 - .../016_sonic_staking_strategy_upgrade.js | 45 - .../sonic/017_pool_booster_metropolis.js | 81 - .../deploy/sonic/018_merkl_pool_booster.js | 81 - .../deploy/sonic/018_pool_booster_batch.js | 84 - .../sonic/019_os_vault_based_dripper.js | 64 - .../sonic/020_enable_buyback_operator.js | 26 - contracts/deploy/sonic/021_add_validator.js | 43 - .../deploy/sonic/022_os_upgrade_EIP7702.js | 29 - .../023_transfer_pbfactory_governance.js | 38 - .../sonic/024_increase_timelock_delay.js | 28 - contracts/deploy/sonic/025_vault_upgrade.js | 37 - .../deployments/mainnet/.migrations.json | 5 +- contracts/dev.env | 3 - contracts/docs/AaveStrategyHierarchy.svg | 61 - contracts/docs/AaveStrategySquashed.svg | 99 - contracts/docs/AuraWETHPriceFeedHierarchy.svg | 74 - contracts/docs/AuraWETHPriceFeedSquashed.svg | 72 - contracts/docs/AuraWETHPriceFeedStorage.svg | 79 - .../BalancerMetaPoolStrategyHierarchy.svg | 89 - .../docs/BalancerMetaPoolStrategySquashed.svg | 134 -- .../docs/BalancerMetaPoolStrategyStorage.svg | 141 -- ....svg => BaseCurveAMOStrategyHierarchy.svg} | 45 +- ...d.svg => BaseCurveAMOStrategySquashed.svg} | 203 +- ...ge.svg => BaseCurveAMOStrategyStorage.svg} | 160 +- .../docs/ConvexEthMetaStrategyStorage.svg | 171 -- .../docs/ConvexOUSDMetaStrategyHierarchy.svg | 89 - ...rchy.svg => CurveAMOStrategyHierarchy.svg} | 45 +- ...ashed.svg => CurveAMOStrategySquashed.svg} | 213 +-- contracts/docs/CurveAMOStrategyStorage.svg | 129 ++ contracts/docs/FluxStrategyHierarchy.svg | 88 - contracts/docs/FluxStrategySquashed.svg | 99 - contracts/docs/FluxStrategyStorage.svg | 129 -- contracts/docs/FraxETHStrategyHierarchy.svg | 74 - contracts/docs/FraxETHStrategySquashed.svg | 104 - contracts/docs/FraxETHStrategyStorage.svg | 249 --- contracts/docs/HarvesterHierarchy.svg | 47 - contracts/docs/HarvesterSquashed.svg | 80 - contracts/docs/HarvesterStorage.svg | 109 -- contracts/docs/MorphoAaveStrategySquashed.svg | 100 - contracts/docs/MorphoAaveStrategyStorage.svg | 125 -- .../docs/MorphoCompStrategyHierarchy.svg | 75 - contracts/docs/MorphoCompStrategySquashed.svg | 102 - contracts/docs/MorphoCompStrategyStorage.svg | 129 -- contracts/docs/OETHBaseHarvesterHierarchy.svg | 33 - contracts/docs/OETHBaseHarvesterStorage.svg | 29 - contracts/docs/OETHBaseHierarchy.svg | 47 + contracts/docs/OETHBuybackHierarchy.svg | 116 -- contracts/docs/OETHBuybackSquashed.svg | 89 - contracts/docs/OETHBuybackStorage.svg | 163 -- contracts/docs/OUSDBuybackHierarchy.svg | 116 -- contracts/docs/OUSDBuybackSquashed.svg | 89 - contracts/docs/OUSDBuybackStorage.svg | 163 -- ...PoolBoosterFactorySwapxDoubleHierarchy.svg | 134 ++ ...PoolBoosterFactorySwapxDoubleSquashed.svg} | 96 +- .../PoolBoosterFactorySwapxDoubleStorage.svg | 113 ++ ...PoolBoosterFactorySwapxSingleHierarchy.svg | 134 ++ .../PoolBoosterFactorySwapxSingleSquashed.svg | 59 + .../PoolBoosterFactorySwapxSingleStorage.svg | 113 ++ contracts/docs/Swapper1InchV5Hierarchy.svg | 88 - contracts/docs/Swapper1InchV5Squashed.svg | 31 - contracts/docs/Swapper1InchV5Storage.svg | 23 - contracts/docs/generate.sh | 70 +- contracts/scripts/governor/README.md | 7 - contracts/scripts/governor/propose.js | 1058 ----------- contracts/tasks/amoStrategy.js | 43 +- contracts/tasks/curve.js | 11 +- contracts/tasks/debug.js | 365 ---- contracts/tasks/dripper.js | 40 - contracts/tasks/governance.js | 22 - contracts/tasks/tasks.js | 47 +- contracts/test/_fixture-base.js | 39 +- contracts/test/_fixture-plume.js | 43 - contracts/test/_fixture.js | 1685 ++--------------- contracts/test/_fund.js | 6 - contracts/test/_hot-deploy.js | 172 +- contracts/test/_metastrategies-fixtures.js | 304 --- contracts/test/abi/aTusd.json | 833 -------- contracts/test/abi/aUsdt.json | 833 -------- contracts/test/abi/aave.json | 603 ------ contracts/test/abi/cDai.json | 1466 -------------- contracts/test/abi/cUsdc.json | 1175 ------------ contracts/test/abi/comp.json | 532 ------ contracts/test/abi/crvMinter.json | 1 - contracts/test/abi/morphoLens.json | 1182 ------------ contracts/test/abi/oethMetapool.json | 578 ------ contracts/test/abi/ousdMetapool.json | 628 ------ contracts/test/abi/sUSDS.json | 477 ----- contracts/test/abi/sfrxETH.json | 1 - contracts/test/abi/threepoolLP.json | 1 - contracts/test/abi/threepoolSwap.json | 1 - contracts/test/behaviour/harvest.fork.js | 93 - contracts/test/behaviour/harvestable.js | 2 +- contracts/test/behaviour/harvester.js | 972 ---------- contracts/test/behaviour/strategy.js | 78 +- contracts/test/buyback/buyback.js | 516 ----- .../test/buyback/buyback.mainnet.fork-test.js | 260 --- .../fixed-rate-dripper.base.fork-test.js | 108 -- .../ousd-harvest-crv.mainnet.fork-test.js | 125 -- .../simple-harvester.mainnet.fork-test.js | 323 ---- contracts/test/helpers.js | 185 -- .../test/oracle/oracle.mainnet.fork-test.js | 34 - .../poolBooster.mainnet.fork-test.js | 2 +- .../claim-rewards.mainnet.fork-test.js | 12 +- contracts/test/strategies/aave.js | 340 ---- .../test/strategies/aave.mainnet.fork-test.js | 187 -- ...alancerMetaStablePool.mainnet.fork-test.js | 738 -------- ...alancerPoolReentrancy.mainnet.fork-test.js | 54 - .../bridged-woeth-strategy.plume.fork-test.js | 239 --- .../base/curve-amo.base.fork-test.js | 1 + contracts/test/strategies/compound.js | 124 -- contracts/test/strategies/convex.js | 106 -- ...chain-master-strategy.mainnet.fork-test.js | 34 +- ...osschain-remote-strategy.base.fork-test.js | 15 +- .../curve-amo-oeth.mainnet.fork-test.js | 11 +- .../curve-amo-ousd.mainnet.fork-test.js | 13 +- contracts/test/strategies/dripper.js | 137 -- .../morpho-comp.mainnet.fork-test.js | 229 --- .../oeth-metapool.mainnet.fork-test.js | 958 ---------- .../oeth-morpho-aave.mainnet.fork-test.js | 186 -- ...pool-3crv-tilted-pool.mainnet.fork-test.js | 130 -- ...etapool-balanced-pool.mainnet.fork-test.js | 143 -- ...pool-ousd-tilted-pool.mainnet.fork-test.js | 139 -- ...o-guantlet-prime-usdc.mainnet.fork-test.js | 443 ----- ...orpho-steakhouse-usdc.mainnet.fork-test.js | 373 ---- .../ousd-v2-morpho.mainnet.fork-test.js | 11 +- .../plume/rooster-amo.plume.fork-test.js | 1140 ----------- contracts/test/vault/compound.js | 468 ----- contracts/test/vault/harvester.js | 28 - .../test/vault/harvester.mainnet.fork-test.js | 307 --- contracts/test/vault/index.js | 62 +- .../vault/oeth-vault.mainnet.fork-test.js | 40 +- contracts/test/vault/rebase.js | 62 +- .../test/vault/vault.mainnet.fork-test.js | 21 +- contracts/utils/1Inch.js | 148 -- contracts/utils/addresses.js | 49 +- contracts/utils/balancerStrategyDeployment.js | 125 -- contracts/utils/constants.js | 149 -- contracts/utils/deploy.js | 7 +- contracts/utils/oracle.js | 53 - 344 files changed, 1523 insertions(+), 44286 deletions(-) delete mode 100644 brownie/contracts/MockOracle.sol create mode 100644 contracts/abi/crvMinter.json delete mode 100644 contracts/contracts/buyback/ARMBuyback.sol delete mode 100644 contracts/contracts/buyback/AbstractBuyback.sol delete mode 100644 contracts/contracts/buyback/OETHBuyback.sol delete mode 100644 contracts/contracts/buyback/OUSDBuyback.sol delete mode 100644 contracts/contracts/harvest/Harvester.sol delete mode 100644 contracts/contracts/harvest/OETHBaseHarvester.sol delete mode 100644 contracts/contracts/harvest/OETHDripper.sol delete mode 100644 contracts/contracts/harvest/OETHHarvester.sol delete mode 100644 contracts/contracts/interfaces/IBuyback.sol delete mode 100644 contracts/contracts/interfaces/IComptroller.sol delete mode 100644 contracts/contracts/interfaces/IFraxETHMinter.sol delete mode 100644 contracts/contracts/interfaces/IOneInch.sol delete mode 100644 contracts/contracts/interfaces/IRETH.sol delete mode 100644 contracts/contracts/interfaces/ISfrxETH.sol delete mode 100644 contracts/contracts/interfaces/ISwapper.sol delete mode 100644 contracts/contracts/interfaces/balancer/IMetaStablePool.sol delete mode 100644 contracts/contracts/interfaces/balancer/IOracleWeightedPool.sol delete mode 100644 contracts/contracts/interfaces/morpho/ILens.sol delete mode 100644 contracts/contracts/interfaces/morpho/IMorpho.sol delete mode 100644 contracts/contracts/interfaces/morpho/Types.sol delete mode 100644 contracts/contracts/interfaces/morpho/compound/ICompoundOracle.sol delete mode 100644 contracts/contracts/mocks/Mock1InchSwapRouter.sol delete mode 100644 contracts/contracts/mocks/MockAAVEToken.sol delete mode 100644 contracts/contracts/mocks/MockAave.sol delete mode 100644 contracts/contracts/mocks/MockAaveIncentivesController.sol delete mode 100644 contracts/contracts/mocks/MockAura.sol delete mode 100644 contracts/contracts/mocks/MockBAL.sol delete mode 100644 contracts/contracts/mocks/MockBalancerVault.sol delete mode 100644 contracts/contracts/mocks/MockCOMP.sol delete mode 100644 contracts/contracts/mocks/MockCToken.sol delete mode 100644 contracts/contracts/mocks/MockCVXLocker.sol delete mode 100644 contracts/contracts/mocks/MockComptroller.sol delete mode 100644 contracts/contracts/mocks/MockEvilDAI.sol delete mode 100644 contracts/contracts/mocks/MockEvilReentrantContract.sol delete mode 100644 contracts/contracts/mocks/MockFrxETHMinter.sol delete mode 100644 contracts/contracts/mocks/MockMaverickDistributor.sol delete mode 100644 contracts/contracts/mocks/MockMetadataToken.sol delete mode 100644 contracts/contracts/mocks/MockMintableUniswapPair.sol delete mode 100644 contracts/contracts/mocks/MockOETHVault.sol delete mode 100644 contracts/contracts/mocks/MockOETHVaultAdmin.sol delete mode 100644 contracts/contracts/mocks/MockOGV.sol delete mode 100644 contracts/contracts/mocks/MockOracle.sol delete mode 100644 contracts/contracts/mocks/MockOracleRouterNoStale.sol delete mode 100644 contracts/contracts/mocks/MockOracleWeightedPool.sol delete mode 100644 contracts/contracts/mocks/MockRETH.sol delete mode 100644 contracts/contracts/mocks/MockRoosterAMOStrategy.sol delete mode 100644 contracts/contracts/mocks/MockStkAave.sol delete mode 100644 contracts/contracts/mocks/MockSwapper.sol delete mode 100644 contracts/contracts/mocks/MockTUSD.sol delete mode 100644 contracts/contracts/mocks/MockUniswapPair.sol delete mode 100644 contracts/contracts/mocks/MockfrxETH.sol delete mode 100644 contracts/contracts/mocks/MocksfrxETH.sol delete mode 100644 contracts/contracts/mocks/MockstETH.sol delete mode 100644 contracts/contracts/mocks/curve/Mock3CRV.sol delete mode 100644 contracts/contracts/mocks/curve/MockBooster.sol delete mode 100644 contracts/contracts/mocks/curve/MockCRV.sol delete mode 100644 contracts/contracts/mocks/curve/MockCRVMinter.sol delete mode 100644 contracts/contracts/mocks/curve/MockCVX.sol delete mode 100644 contracts/contracts/mocks/curve/MockCurveAbstractMetapool.sol delete mode 100644 contracts/contracts/mocks/curve/MockCurveGauge.sol delete mode 100644 contracts/contracts/mocks/curve/MockCurveMetapool.sol delete mode 100644 contracts/contracts/mocks/curve/MockCurvePool.sol delete mode 100644 contracts/contracts/mocks/curve/MockRewardPool.sol delete mode 100644 contracts/contracts/strategies/AaveStrategy.sol delete mode 100644 contracts/contracts/strategies/AbstractCompoundStrategy.sol delete mode 100644 contracts/contracts/strategies/AbstractConvexMetaStrategy.sol delete mode 100644 contracts/contracts/strategies/AbstractCurveStrategy.sol delete mode 100644 contracts/contracts/strategies/CompoundStrategy.sol delete mode 100644 contracts/contracts/strategies/ConvexEthMetaStrategy.sol delete mode 100644 contracts/contracts/strategies/ConvexGeneralizedMetaStrategy.sol delete mode 100644 contracts/contracts/strategies/ConvexOUSDMetaStrategy.sol delete mode 100644 contracts/contracts/strategies/ConvexStrategy.sol delete mode 100644 contracts/contracts/strategies/Generalized4626USDTStrategy.sol delete mode 100644 contracts/contracts/strategies/ICompound.sol delete mode 100644 contracts/contracts/strategies/ICurveMetaPool.sol delete mode 100644 contracts/contracts/strategies/MorphoAaveStrategy.sol delete mode 100644 contracts/contracts/strategies/MorphoCompoundStrategy.sol delete mode 100644 contracts/contracts/strategies/balancer/AbstractAuraStrategy.sol delete mode 100644 contracts/contracts/strategies/balancer/AbstractBalancerStrategy.sol delete mode 100644 contracts/contracts/strategies/balancer/BalancerMetaPoolStrategy.sol delete mode 100644 contracts/contracts/strategies/balancer/README.md delete mode 100644 contracts/contracts/strategies/balancer/VaultReentrancyLib.sol delete mode 100644 contracts/contracts/strategies/plume/RoosterAMOStrategy.sol delete mode 100644 contracts/contracts/swapper/README.md delete mode 100644 contracts/contracts/swapper/Swapper1InchV5.sol delete mode 100644 contracts/contracts/utils/BalancerErrors.sol delete mode 100644 contracts/deploy/base/001_woeth_on_base.js delete mode 100644 contracts/deploy/base/002_base_oracles.js delete mode 100644 contracts/deploy/base/003_base_vault_and_token.js delete mode 100644 contracts/deploy/base/004_super_oeth.js delete mode 100644 contracts/deploy/base/005_mutlisig_harvester.js delete mode 100644 contracts/deploy/base/006_base_amo_strategy.js delete mode 100644 contracts/deploy/base/007_bridged_woeth_strategy.js delete mode 100644 contracts/deploy/base/008_oethb_zapper.js delete mode 100644 contracts/deploy/base/009_upgrade_vault.js delete mode 100644 contracts/deploy/base/010_upgrade_vault_core.js delete mode 100644 contracts/deploy/base/011_transfer_governance.js delete mode 100644 contracts/deploy/base/012_claim_governance.js delete mode 100644 contracts/deploy/base/013_revoke_admin_role.js delete mode 100644 contracts/deploy/base/014_fixed_rate_dripper.js delete mode 100644 contracts/deploy/base/015_harvester.js delete mode 100644 contracts/deploy/base/016_timelock_2d_delay.js delete mode 100644 contracts/deploy/base/017_upgrade_amo.js delete mode 100644 contracts/deploy/base/018_strategist_as_executor.js delete mode 100644 contracts/deploy/base/019_async_withdrawals.js delete mode 100644 contracts/deploy/base/020_upgrade_amo.js delete mode 100644 contracts/deploy/base/021_multichain_strategist.js delete mode 100644 contracts/deploy/base/022_upgrade_oeth.js delete mode 100644 contracts/deploy/base/023_update_weth_share.js delete mode 100644 contracts/deploy/base/024_multisig_as_canceller.js delete mode 100644 contracts/deploy/base/025_base_curve_amo.js delete mode 100644 contracts/deploy/base/026_harvester_v2.js delete mode 100644 contracts/deploy/base/027_base_curve_amo_upgrade.js delete mode 100644 contracts/deploy/base/028_vault_woeth_upgrade.js delete mode 100644 contracts/deploy/base/030_claimbribes_safe_module.js delete mode 100644 contracts/deploy/base/031_enable_buyback_operator.js delete mode 100644 contracts/deploy/base/032_vault_perf_fee.js delete mode 100644 contracts/deploy/base/033_bridge_module.js delete mode 100644 contracts/deploy/base/034_claimbribes_module_v2.js delete mode 100644 contracts/deploy/base/035_claimbribes_module_old_guardian.js delete mode 100644 contracts/deploy/base/036_oethb_upgrade_EIP7702.js delete mode 100644 contracts/deploy/base/037_deploy_harvester.js delete mode 100644 contracts/deploy/base/038_vault_upgrade.js delete mode 100644 contracts/deploy/base/039_pool_booster_factory.js delete mode 100644 contracts/deploy/mainnet/102_2nd_native_ssv_staking.js delete mode 100644 contracts/deploy/mainnet/103_oeth_withdraw_queue.js delete mode 100644 contracts/deploy/mainnet/104_upgrade_staking_strategies.js delete mode 100644 contracts/deploy/mainnet/105_ousd_remove_flux_strat.js delete mode 100644 contracts/deploy/mainnet/106_ousd_metamorpho_usdc.js delete mode 100644 contracts/deploy/mainnet/107_arm_buyback.js delete mode 100644 contracts/deploy/mainnet/108_vault_upgrade.js delete mode 100644 contracts/deploy/mainnet/109_3rd_native_ssv_staking.js delete mode 100644 contracts/deploy/mainnet/110_transfer_morpho.js delete mode 100644 contracts/deploy/mainnet/111_morpho_wrap_and_transfer.js delete mode 100644 contracts/deploy/mainnet/112_ousd_morpho_gauntlet_usdc.js delete mode 100644 contracts/deploy/mainnet/113_ousd_morpho_gauntlet_usdt.js delete mode 100644 contracts/deploy/mainnet/114_simple_harvester.js delete mode 100644 contracts/deploy/mainnet/115_ousd_upgrade.js delete mode 100644 contracts/deploy/mainnet/116_oeth_upgrade.js delete mode 100644 contracts/deploy/mainnet/117_oeth_fixed_rate_dripper.js delete mode 100644 contracts/deploy/mainnet/118_multichain_strategist.js delete mode 100644 contracts/deploy/mainnet/119_multisig_as_canceller.js delete mode 100644 contracts/deploy/mainnet/120_remove_ousd_amo.js delete mode 100644 contracts/deploy/mainnet/121_pool_booster_curve.js delete mode 100644 contracts/deploy/mainnet/122_delegate_yield_curve_pool.js delete mode 100644 contracts/deploy/mainnet/123_simple_harvester_v2.js delete mode 100644 contracts/deploy/mainnet/124_remove_ousd_aave_strat.js delete mode 100644 contracts/deploy/mainnet/125_buyback_treasury_address.js delete mode 100644 contracts/deploy/mainnet/126_update_amo_mint_threshold.js delete mode 100644 contracts/deploy/mainnet/127_replace_dai_with_usds.js delete mode 100644 contracts/deploy/mainnet/128_remove_ousd_morpho_aave.js delete mode 100644 contracts/deploy/mainnet/129_oeth_vault_upgrade.js delete mode 100644 contracts/deploy/mainnet/130_update_votemarket_addresses.js delete mode 100644 contracts/deploy/mainnet/131_ousd_usdc_curve_amo.js delete mode 100644 contracts/deploy/mainnet/133_omnichain_adapter.js delete mode 100644 contracts/deploy/mainnet/134_vault_woeth_upgrade.js delete mode 100644 contracts/deploy/mainnet/135_vault_wousd_upgrade.js delete mode 100644 contracts/deploy/mainnet/136_upgrade_morpho_strategies.js delete mode 100644 contracts/deploy/mainnet/137_oeth_weth_curve_amo.js delete mode 100644 contracts/deploy/mainnet/138_buyback_deprecation.js delete mode 100644 contracts/deploy/mainnet/139_deprecate_ousd_harvester.js delete mode 100644 contracts/deploy/mainnet/140_remove_1st_native_ssv_staking.js delete mode 100644 contracts/deploy/mainnet/141_claimrewards_module.js delete mode 100644 contracts/deploy/mainnet/142_bridge_helper_module.js delete mode 100644 contracts/deploy/mainnet/143_bridge_helper_module_upgrade.js delete mode 100644 contracts/deploy/mainnet/144_ousd_oeth_upgrade_EIP7702.js delete mode 100644 contracts/deploy/mainnet/145_deploy_xogn_rewards_module.js delete mode 100644 contracts/deploy/mainnet/146_morpho_strategies_reward_tokens.js delete mode 100644 contracts/deploy/mainnet/147_claim_rewards_module_upgrade.js delete mode 100644 contracts/deploy/mainnet/148_xogn_module_for_5_of_8_multisig.js delete mode 100644 contracts/deploy/mainnet/149_xogn_module_7.js delete mode 100644 contracts/deploy/mainnet/150_vault_upgrade.js delete mode 100644 contracts/deploy/mainnet/151_curve_pb_module.js delete mode 100644 contracts/deploy/mainnet/152_pool_booster_setup.js delete mode 100644 contracts/deploy/mainnet/153_beacon_root_testing.js delete mode 100644 contracts/deploy/mainnet/154_upgrade_native_staking.js delete mode 100644 contracts/deploy/mainnet/155_oeth_zapper.js delete mode 100644 contracts/deploy/plume/008_rooster_amo.js delete mode 100644 contracts/deploy/sonic/001_vault_and_token.js delete mode 100644 contracts/deploy/sonic/002_oracle_router.js delete mode 100644 contracts/deploy/sonic/003_sonic_staking_strategy.js delete mode 100644 contracts/deploy/sonic/004_timelock_1d_delay.js delete mode 100644 contracts/deploy/sonic/005_multisig_as_canceller.js delete mode 100644 contracts/deploy/sonic/006_yf_swpx_os_pool.js delete mode 100644 contracts/deploy/sonic/007_strategist_as_executor.js delete mode 100644 contracts/deploy/sonic/008_swapx_yield_forward.js delete mode 100644 contracts/deploy/sonic/009_swapx_amo.js delete mode 100644 contracts/deploy/sonic/010_swapx_yield_forward.js delete mode 100644 contracts/deploy/sonic/011_pool_booster_factory.js delete mode 100644 contracts/deploy/sonic/012_tb_yf_batch_1.js delete mode 100644 contracts/deploy/sonic/013_vault_config.js delete mode 100644 contracts/deploy/sonic/014_wrapped_sonic.js delete mode 100644 contracts/deploy/sonic/015_redeem_fee.js delete mode 100644 contracts/deploy/sonic/016_sonic_staking_strategy_upgrade.js delete mode 100644 contracts/deploy/sonic/017_pool_booster_metropolis.js delete mode 100644 contracts/deploy/sonic/018_merkl_pool_booster.js delete mode 100644 contracts/deploy/sonic/018_pool_booster_batch.js delete mode 100644 contracts/deploy/sonic/019_os_vault_based_dripper.js delete mode 100644 contracts/deploy/sonic/020_enable_buyback_operator.js delete mode 100644 contracts/deploy/sonic/021_add_validator.js delete mode 100644 contracts/deploy/sonic/022_os_upgrade_EIP7702.js delete mode 100644 contracts/deploy/sonic/023_transfer_pbfactory_governance.js delete mode 100644 contracts/deploy/sonic/024_increase_timelock_delay.js delete mode 100644 contracts/deploy/sonic/025_vault_upgrade.js delete mode 100644 contracts/docs/AaveStrategyHierarchy.svg delete mode 100644 contracts/docs/AaveStrategySquashed.svg delete mode 100644 contracts/docs/AuraWETHPriceFeedHierarchy.svg delete mode 100644 contracts/docs/AuraWETHPriceFeedSquashed.svg delete mode 100644 contracts/docs/AuraWETHPriceFeedStorage.svg delete mode 100644 contracts/docs/BalancerMetaPoolStrategyHierarchy.svg delete mode 100644 contracts/docs/BalancerMetaPoolStrategySquashed.svg delete mode 100644 contracts/docs/BalancerMetaPoolStrategyStorage.svg rename contracts/docs/{ConvexEthMetaStrategyHierarchy.svg => BaseCurveAMOStrategyHierarchy.svg} (69%) rename contracts/docs/{ConvexEthMetaStrategySquashed.svg => BaseCurveAMOStrategySquashed.svg} (72%) rename contracts/docs/{AaveStrategyStorage.svg => BaseCurveAMOStrategyStorage.svg} (51%) delete mode 100644 contracts/docs/ConvexEthMetaStrategyStorage.svg delete mode 100644 contracts/docs/ConvexOUSDMetaStrategyHierarchy.svg rename contracts/docs/{MorphoAaveStrategyHierarchy.svg => CurveAMOStrategyHierarchy.svg} (69%) rename contracts/docs/{ConvexOUSDMetaStrategySquashed.svg => CurveAMOStrategySquashed.svg} (69%) create mode 100644 contracts/docs/CurveAMOStrategyStorage.svg delete mode 100644 contracts/docs/FluxStrategyHierarchy.svg delete mode 100644 contracts/docs/FluxStrategySquashed.svg delete mode 100644 contracts/docs/FluxStrategyStorage.svg delete mode 100644 contracts/docs/FraxETHStrategyHierarchy.svg delete mode 100644 contracts/docs/FraxETHStrategySquashed.svg delete mode 100644 contracts/docs/FraxETHStrategyStorage.svg delete mode 100644 contracts/docs/HarvesterHierarchy.svg delete mode 100644 contracts/docs/HarvesterSquashed.svg delete mode 100644 contracts/docs/HarvesterStorage.svg delete mode 100644 contracts/docs/MorphoAaveStrategySquashed.svg delete mode 100644 contracts/docs/MorphoAaveStrategyStorage.svg delete mode 100644 contracts/docs/MorphoCompStrategyHierarchy.svg delete mode 100644 contracts/docs/MorphoCompStrategySquashed.svg delete mode 100644 contracts/docs/MorphoCompStrategyStorage.svg delete mode 100644 contracts/docs/OETHBaseHarvesterHierarchy.svg delete mode 100644 contracts/docs/OETHBaseHarvesterStorage.svg create mode 100644 contracts/docs/OETHBaseHierarchy.svg delete mode 100644 contracts/docs/OETHBuybackHierarchy.svg delete mode 100644 contracts/docs/OETHBuybackSquashed.svg delete mode 100644 contracts/docs/OETHBuybackStorage.svg delete mode 100644 contracts/docs/OUSDBuybackHierarchy.svg delete mode 100644 contracts/docs/OUSDBuybackSquashed.svg delete mode 100644 contracts/docs/OUSDBuybackStorage.svg create mode 100644 contracts/docs/PoolBoosterFactorySwapxDoubleHierarchy.svg rename contracts/docs/{OETHBaseHarvesterSquashed.svg => PoolBoosterFactorySwapxDoubleSquashed.svg} (53%) create mode 100644 contracts/docs/PoolBoosterFactorySwapxDoubleStorage.svg create mode 100644 contracts/docs/PoolBoosterFactorySwapxSingleHierarchy.svg create mode 100644 contracts/docs/PoolBoosterFactorySwapxSingleSquashed.svg create mode 100644 contracts/docs/PoolBoosterFactorySwapxSingleStorage.svg delete mode 100644 contracts/docs/Swapper1InchV5Hierarchy.svg delete mode 100644 contracts/docs/Swapper1InchV5Squashed.svg delete mode 100644 contracts/docs/Swapper1InchV5Storage.svg delete mode 100644 contracts/scripts/governor/README.md delete mode 100644 contracts/scripts/governor/propose.js delete mode 100644 contracts/tasks/debug.js delete mode 100644 contracts/tasks/dripper.js delete mode 100644 contracts/test/_metastrategies-fixtures.js delete mode 100644 contracts/test/abi/aTusd.json delete mode 100644 contracts/test/abi/aUsdt.json delete mode 100644 contracts/test/abi/aave.json delete mode 100644 contracts/test/abi/cDai.json delete mode 100644 contracts/test/abi/cUsdc.json delete mode 100644 contracts/test/abi/comp.json delete mode 100644 contracts/test/abi/crvMinter.json delete mode 100644 contracts/test/abi/morphoLens.json delete mode 100644 contracts/test/abi/oethMetapool.json delete mode 100644 contracts/test/abi/ousdMetapool.json delete mode 100644 contracts/test/abi/sUSDS.json delete mode 100644 contracts/test/abi/sfrxETH.json delete mode 100644 contracts/test/abi/threepoolLP.json delete mode 100644 contracts/test/abi/threepoolSwap.json delete mode 100644 contracts/test/behaviour/harvest.fork.js delete mode 100644 contracts/test/behaviour/harvester.js delete mode 100644 contracts/test/buyback/buyback.js delete mode 100644 contracts/test/buyback/buyback.mainnet.fork-test.js delete mode 100644 contracts/test/dripper/fixed-rate-dripper.base.fork-test.js delete mode 100644 contracts/test/harvest/ousd-harvest-crv.mainnet.fork-test.js delete mode 100644 contracts/test/harvest/simple-harvester.mainnet.fork-test.js delete mode 100644 contracts/test/oracle/oracle.mainnet.fork-test.js delete mode 100644 contracts/test/strategies/aave.js delete mode 100644 contracts/test/strategies/aave.mainnet.fork-test.js delete mode 100644 contracts/test/strategies/balancerMetaStablePool.mainnet.fork-test.js delete mode 100644 contracts/test/strategies/balancerPoolReentrancy.mainnet.fork-test.js delete mode 100644 contracts/test/strategies/base/bridged-woeth-strategy.plume.fork-test.js delete mode 100644 contracts/test/strategies/compound.js delete mode 100644 contracts/test/strategies/convex.js delete mode 100644 contracts/test/strategies/dripper.js delete mode 100644 contracts/test/strategies/morpho-comp.mainnet.fork-test.js delete mode 100644 contracts/test/strategies/oeth-metapool.mainnet.fork-test.js delete mode 100644 contracts/test/strategies/oeth-morpho-aave.mainnet.fork-test.js delete mode 100644 contracts/test/strategies/ousd-metapool-3crv-tilted-pool.mainnet.fork-test.js delete mode 100644 contracts/test/strategies/ousd-metapool-balanced-pool.mainnet.fork-test.js delete mode 100644 contracts/test/strategies/ousd-metapool-ousd-tilted-pool.mainnet.fork-test.js delete mode 100644 contracts/test/strategies/ousd-morpho-guantlet-prime-usdc.mainnet.fork-test.js delete mode 100644 contracts/test/strategies/ousd-morpho-steakhouse-usdc.mainnet.fork-test.js delete mode 100644 contracts/test/strategies/plume/rooster-amo.plume.fork-test.js delete mode 100644 contracts/test/vault/compound.js delete mode 100644 contracts/test/vault/harvester.js delete mode 100644 contracts/test/vault/harvester.mainnet.fork-test.js delete mode 100644 contracts/utils/1Inch.js delete mode 100644 contracts/utils/balancerStrategyDeployment.js delete mode 100644 contracts/utils/oracle.js diff --git a/brownie/contracts/MockOracle.sol b/brownie/contracts/MockOracle.sol deleted file mode 100644 index 43dafe9714..0000000000 --- a/brownie/contracts/MockOracle.sol +++ /dev/null @@ -1,10 +0,0 @@ -pragma solidity ^0.8.0; - -// For testing OUSD's response to oracle price changes -contract MockOracle { - mapping(address => uint256) public price; - - function setPrice(address asset, uint256 price_) external { - price[asset] = price_; - } -} diff --git a/contracts/abi/crvMinter.json b/contracts/abi/crvMinter.json new file mode 100644 index 0000000000..ab4b4dd00f --- /dev/null +++ b/contracts/abi/crvMinter.json @@ -0,0 +1,94 @@ +[ + { + "name": "Minted", + "inputs": [ + { "type": "address", "name": "recipient", "indexed": true }, + { "type": "address", "name": "gauge", "indexed": false }, + { "type": "uint256", "name": "minted", "indexed": false } + ], + "anonymous": false, + "type": "event" + }, + { + "outputs": [], + "inputs": [ + { "type": "address", "name": "_token" }, + { "type": "address", "name": "_controller" } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "name": "mint", + "outputs": [], + "inputs": [{ "type": "address", "name": "gauge_addr" }], + "stateMutability": "nonpayable", + "type": "function", + "gas": 100038 + }, + { + "name": "mint_many", + "outputs": [], + "inputs": [{ "type": "address[8]", "name": "gauge_addrs" }], + "stateMutability": "nonpayable", + "type": "function", + "gas": 408502 + }, + { + "name": "mint_for", + "outputs": [], + "inputs": [ + { "type": "address", "name": "gauge_addr" }, + { "type": "address", "ame": "_for" } + ], + "stateMutability": "nonpayable", + "type": "function", + "gas": 101219 + }, + { + "name": "toggle_approve_mint", + "outputs": [], + "inputs": [{ "type": "address", "name": "minting_user" }], + "stateMutability": "nonpayable", + "type": "function", + "gas": 36726 + }, + { + "name": "token", + "outputs": [{ "type": "address", "name": "" }], + "inputs": [], + "stateMutability": "view", + "type": "function", + "gas": 1301 + }, + { + "name": "controller", + "outputs": [{ "type": "address", "name": "" }], + "inputs": [], + "stateMutability": "view", + "type": "function", + "gas": 1331 + }, + { + "name": "minted", + "outputs": [{ "type": "uint256", "name": "" }], + "inputs": [ + { "type": "address", "name": "arg0" }, + { "type": "address", "name": "arg1" } + ], + "stateMutability": "view", + "type": "function", + "gas": 1669 + }, + { + "name": "allowed_to_mint_for", + "outputs": [{ "type": "bool", "name": "" }], + "inputs": [ + { "type": "address", "name": "arg0" }, + { "type": "address", "name": "arg1" } + ], + "stateMutability": "view", + "type": "function", + "gas": 1699 + } +] diff --git a/contracts/contracts/buyback/ARMBuyback.sol b/contracts/contracts/buyback/ARMBuyback.sol deleted file mode 100644 index 62c821e91e..0000000000 --- a/contracts/contracts/buyback/ARMBuyback.sol +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -import { AbstractBuyback } from "./AbstractBuyback.sol"; - -contract ARMBuyback is AbstractBuyback { - constructor( - address _oToken, - address _ogn, - address _cvx, - address _cvxLocker - ) AbstractBuyback(_oToken, _ogn, _cvx, _cvxLocker) {} -} diff --git a/contracts/contracts/buyback/AbstractBuyback.sol b/contracts/contracts/buyback/AbstractBuyback.sol deleted file mode 100644 index 8cece82053..0000000000 --- a/contracts/contracts/buyback/AbstractBuyback.sol +++ /dev/null @@ -1,325 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -import { Strategizable } from "../governance/Strategizable.sol"; -import "../interfaces/chainlink/AggregatorV3Interface.sol"; -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import { ICVXLocker } from "../interfaces/ICVXLocker.sol"; -import { ISwapper } from "../interfaces/ISwapper.sol"; - -import { Initializable } from "../utils/Initializable.sol"; - -abstract contract AbstractBuyback is Initializable, Strategizable { - using SafeERC20 for IERC20; - - event SwapRouterUpdated(address indexed _address); - - event RewardsSourceUpdated(address indexed _address); - event TreasuryManagerUpdated(address indexed _address); - event CVXShareBpsUpdated(uint256 bps); - - // Emitted whenever OUSD/OETH is swapped for OGN/CVX or any other token - event OTokenBuyback( - address indexed oToken, - address indexed swappedFor, - uint256 swapAmountIn, - uint256 amountOut - ); - - // Address of 1-inch Swap Router - address public swapRouter; - - // slither-disable-next-line constable-states - address private __deprecated_ousd; - // slither-disable-next-line constable-states - address private __deprecated_ogv; - // slither-disable-next-line constable-states - address private __deprecated_usdt; - // slither-disable-next-line constable-states - address private __deprecated_weth9; - - // Address that receives OGN after swaps - address public rewardsSource; - - // Address that receives all other tokens after swaps - address public treasuryManager; - - // slither-disable-next-line constable-states - uint256 private __deprecated_treasuryBps; - - address public immutable oToken; - address public immutable ogn; - address public immutable cvx; - address public immutable cvxLocker; - - // Amount of `oToken` balance to use for OGN buyback - uint256 public balanceForOGN; - - // Amount of `oToken` balance to use for CVX buyback - uint256 public balanceForCVX; - - // Percentage of `oToken` balance to be used for CVX - uint256 public cvxShareBps; // 10000 = 100% - - constructor( - address _oToken, - address _ogn, - address _cvx, - address _cvxLocker - ) { - // Make sure nobody owns the implementation contract - _setGovernor(address(0)); - - oToken = _oToken; - ogn = _ogn; - cvx = _cvx; - cvxLocker = _cvxLocker; - } - - /** - * @param _swapRouter Address of Uniswap V3 Router - * @param _strategistAddr Address of Strategist multi-sig wallet - * @param _treasuryManagerAddr Address that receives the treasury's share of OUSD - * @param _rewardsSource Address of RewardsSource contract - * @param _cvxShareBps Percentage of balance to use for CVX - */ - function initialize( - address _swapRouter, - address _strategistAddr, - address _treasuryManagerAddr, - address _rewardsSource, - uint256 _cvxShareBps - ) external onlyGovernor initializer { - _setStrategistAddr(_strategistAddr); - - _setSwapRouter(_swapRouter); - _setRewardsSource(_rewardsSource); - - _setTreasuryManager(_treasuryManagerAddr); - - _setCVXShareBps(_cvxShareBps); - } - - /** - * @dev Set address of Uniswap Universal Router for performing liquidation - * of platform fee tokens. Setting to 0x0 will pause swaps. - * - * @param _router Address of the Uniswap Universal router - */ - function setSwapRouter(address _router) external onlyGovernor { - _setSwapRouter(_router); - } - - function _setSwapRouter(address _router) internal { - address oldRouter = swapRouter; - swapRouter = _router; - - if (oldRouter != address(0)) { - // Remove allowance of old router, if any - - if (IERC20(ogn).allowance(address(this), oldRouter) != 0) { - // slither-disable-next-line unused-return - IERC20(ogn).safeApprove(oldRouter, 0); - } - - if (IERC20(cvx).allowance(address(this), oldRouter) != 0) { - // slither-disable-next-line unused-return - IERC20(cvx).safeApprove(oldRouter, 0); - } - } - - emit SwapRouterUpdated(_router); - } - - /** - * @dev Sets the address that receives the OGN buyback rewards - * @param _address Address - */ - function setRewardsSource(address _address) external onlyGovernor { - _setRewardsSource(_address); - } - - function _setRewardsSource(address _address) internal { - require(_address != address(0), "Address not set"); - rewardsSource = _address; - emit RewardsSourceUpdated(_address); - } - - /** - * @dev Sets the address that can receive and manage the funds for Treasury - * @param _address Address - */ - function setTreasuryManager(address _address) external onlyGovernor { - _setTreasuryManager(_address); - } - - function _setTreasuryManager(address _address) internal { - require(_address != address(0), "Address not set"); - treasuryManager = _address; - emit TreasuryManagerUpdated(_address); - } - - /** - * @dev Sets the percentage of oToken to use for Flywheel tokens - * @param _bps BPS, 10000 to 100% - */ - function setCVXShareBps(uint256 _bps) external onlyGovernor { - _setCVXShareBps(_bps); - } - - function _setCVXShareBps(uint256 _bps) internal { - require(_bps <= 10000, "Invalid bps value"); - cvxShareBps = _bps; - emit CVXShareBpsUpdated(_bps); - } - - /** - * @dev Computes the split of oToken balance that can be - * used for OGN and CVX buybacks. - */ - function _updateBuybackSplits() - internal - returns (uint256 _balanceForOGN, uint256 _balanceForCVX) - { - _balanceForOGN = balanceForOGN; - _balanceForCVX = balanceForCVX; - - uint256 totalBalance = IERC20(oToken).balanceOf(address(this)); - uint256 unsplitBalance = totalBalance - _balanceForOGN - _balanceForCVX; - - // Check if all balance is accounted for - if (unsplitBalance != 0) { - // If not, split unaccounted balance based on `cvxShareBps` - uint256 addToCVX = (unsplitBalance * cvxShareBps) / 10000; - _balanceForCVX = _balanceForCVX + addToCVX; - _balanceForOGN = _balanceForOGN + unsplitBalance - addToCVX; - - // Update storage - balanceForOGN = _balanceForOGN; - balanceForCVX = _balanceForCVX; - } - } - - function updateBuybackSplits() external onlyGovernor { - // slither-disable-next-line unused-return - _updateBuybackSplits(); - } - - function _swapToken( - address tokenOut, - uint256 oTokenAmount, - uint256 minAmountOut, - bytes calldata swapData - ) internal returns (uint256 amountOut) { - require(oTokenAmount > 0, "Invalid Swap Amount"); - require(swapRouter != address(0), "Swap Router not set"); - require(minAmountOut > 0, "Invalid minAmount"); - - // Transfer OToken to Swapper for swapping - // slither-disable-next-line unchecked-transfer unused-return - IERC20(oToken).transfer(swapRouter, oTokenAmount); - - // Swap - amountOut = ISwapper(swapRouter).swap( - oToken, - tokenOut, - oTokenAmount, - minAmountOut, - swapData - ); - - require(amountOut >= minAmountOut, "Higher Slippage"); - - emit OTokenBuyback(oToken, tokenOut, oTokenAmount, amountOut); - } - - /** - * @dev Swaps `oTokenAmount` to OGN - * @param oTokenAmount Amount of OUSD/OETH to swap - * @param minOGN Minimum OGN to receive for oTokenAmount - * @param swapData 1inch Swap Data - */ - function swapForOGN( - uint256 oTokenAmount, - uint256 minOGN, - bytes calldata swapData - ) external onlyGovernorOrStrategist nonReentrant { - (uint256 _amountForOGN, ) = _updateBuybackSplits(); - require(_amountForOGN >= oTokenAmount, "Balance underflow"); - require(rewardsSource != address(0), "RewardsSource contract not set"); - - unchecked { - // Subtract the amount to swap from net balance - balanceForOGN = _amountForOGN - oTokenAmount; - } - - uint256 ognReceived = _swapToken(ogn, oTokenAmount, minOGN, swapData); - - // Transfer OGN received to RewardsSource contract - // slither-disable-next-line unchecked-transfer unused-return - IERC20(ogn).transfer(rewardsSource, ognReceived); - } - - /** - * @dev Swaps `oTokenAmount` to CVX - * @param oTokenAmount Amount of OUSD/OETH to swap - * @param minCVX Minimum CVX to receive for oTokenAmount - * @param swapData 1inch Swap Data - */ - function swapForCVX( - uint256 oTokenAmount, - uint256 minCVX, - bytes calldata swapData - ) external onlyGovernorOrStrategist nonReentrant { - (, uint256 _amountForCVX) = _updateBuybackSplits(); - require(_amountForCVX >= oTokenAmount, "Balance underflow"); - - unchecked { - // Subtract the amount to swap from net balance - balanceForCVX = _amountForCVX - oTokenAmount; - } - - uint256 cvxReceived = _swapToken(cvx, oTokenAmount, minCVX, swapData); - - // Lock all CVX - _lockAllCVX(cvxReceived); - } - - /** - * @dev Locks all CVX held by the contract on behalf of the Treasury Manager - */ - function lockAllCVX() external onlyGovernorOrStrategist { - _lockAllCVX(IERC20(cvx).balanceOf(address(this))); - } - - function _lockAllCVX(uint256 cvxAmount) internal { - require( - treasuryManager != address(0), - "Treasury manager address not set" - ); - - // Lock all available CVX on behalf of `treasuryManager` - ICVXLocker(cvxLocker).lock(treasuryManager, cvxAmount, 0); - } - - /** - * @dev Approve CVX Locker to move CVX held by this contract - */ - function safeApproveAllTokens() external onlyGovernorOrStrategist { - IERC20(cvx).safeApprove(cvxLocker, type(uint256).max); - } - - /** - * @notice Owner function to withdraw a specific amount of a token - * @param token token to be transferered - * @param amount amount of the token to be transferred - */ - function transferToken(address token, uint256 amount) - external - onlyGovernor - nonReentrant - { - IERC20(token).safeTransfer(_governor(), amount); - } -} diff --git a/contracts/contracts/buyback/OETHBuyback.sol b/contracts/contracts/buyback/OETHBuyback.sol deleted file mode 100644 index cafb833998..0000000000 --- a/contracts/contracts/buyback/OETHBuyback.sol +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -import { AbstractBuyback } from "./AbstractBuyback.sol"; - -contract OETHBuyback is AbstractBuyback { - constructor( - address _oToken, - address _ogn, - address _cvx, - address _cvxLocker - ) AbstractBuyback(_oToken, _ogn, _cvx, _cvxLocker) {} -} diff --git a/contracts/contracts/buyback/OUSDBuyback.sol b/contracts/contracts/buyback/OUSDBuyback.sol deleted file mode 100644 index ee724c1d42..0000000000 --- a/contracts/contracts/buyback/OUSDBuyback.sol +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -import { AbstractBuyback } from "./AbstractBuyback.sol"; - -contract OUSDBuyback is AbstractBuyback { - constructor( - address _oToken, - address _ogn, - address _cvx, - address _cvxLocker - ) AbstractBuyback(_oToken, _ogn, _cvx, _cvxLocker) {} -} diff --git a/contracts/contracts/harvest/Harvester.sol b/contracts/contracts/harvest/Harvester.sol deleted file mode 100644 index 76495730eb..0000000000 --- a/contracts/contracts/harvest/Harvester.sol +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -import { AbstractHarvester } from "./AbstractHarvester.sol"; - -contract Harvester is AbstractHarvester { - constructor(address _vault, address _usdtAddress) - AbstractHarvester(_vault, _usdtAddress) - {} -} diff --git a/contracts/contracts/harvest/OETHBaseHarvester.sol b/contracts/contracts/harvest/OETHBaseHarvester.sol deleted file mode 100644 index 441287470f..0000000000 --- a/contracts/contracts/harvest/OETHBaseHarvester.sol +++ /dev/null @@ -1,233 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -import { Governable } from "../governance/Governable.sol"; - -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; - -import { IVault } from "../interfaces/IVault.sol"; -import { IStrategy } from "../interfaces/IStrategy.sol"; -import { ISwapRouter } from "../interfaces/aerodrome/ISwapRouter.sol"; - -contract OETHBaseHarvester is Governable { - using SafeERC20 for IERC20; - - IVault public immutable vault; - IStrategy public immutable amoStrategy; - IERC20 public immutable aero; - IERC20 public immutable weth; - ISwapRouter public immutable swapRouter; - - address public operatorAddr; - - // Similar sig to `AbstractHarvester.RewardTokenSwapped` for - // future compatibility with monitoring - event RewardTokenSwapped( - address indexed rewardToken, - address indexed swappedInto, - uint8 swapPlatform, - uint256 amountIn, - uint256 amountOut - ); - - event OperatorChanged(address oldOperator, address newOperator); - event YieldSent(address recipient, uint256 yield, uint256 fee); - - /** - * @notice Verifies that the caller is either Governor or Strategist. - */ - modifier onlyGovernorOrStrategist() { - require( - msg.sender == vault.strategistAddr() || isGovernor(), - "Caller is not the Strategist or Governor" - ); - _; - } - - /** - * @notice Verifies that the caller is either Governor or Strategist. - */ - modifier onlyGovernorOrStrategistOrOperator() { - require( - msg.sender == operatorAddr || - msg.sender == vault.strategistAddr() || - isGovernor(), - "Caller is not the Operator or Strategist or Governor" - ); - _; - } - - constructor( - address _vault, - address _amoStrategy, - address _aero, - address _weth, - address _swapRouter - ) { - vault = IVault(_vault); - amoStrategy = IStrategy(_amoStrategy); - aero = IERC20(_aero); - weth = IERC20(_weth); - swapRouter = ISwapRouter(_swapRouter); - } - - /** - * @dev Changes the operator address which can call `harvest` - * @param _operatorAddr New operator address - */ - function setOperatorAddr(address _operatorAddr) external onlyGovernor { - emit OperatorChanged(operatorAddr, _operatorAddr); - operatorAddr = _operatorAddr; - } - - /** - * @notice Collects AERO from AMO strategy and - * sends it to the Strategist multisig. - * Anyone can call it. - */ - function harvest() external onlyGovernorOrStrategistOrOperator { - address strategistAddr = vault.strategistAddr(); - require(strategistAddr != address(0), "Guardian address not set"); - - // Collect all AERO - amoStrategy.collectRewardTokens(); - - uint256 aeroBalance = aero.balanceOf(address(this)); - if (aeroBalance == 0) { - // Do nothing if there's no AERO to transfer - return; - } - - // Transfer everything to Strategist - aero.safeTransfer(strategistAddr, aeroBalance); - } - - /** - * @notice Harvests AERO from AMO strategy and then swaps some (or all) - * of it into WETH to distribute yield and fee. - * When `feeBps` is set to 10000 (100%), all WETH received is - * sent to strategist. - * - * @param aeroToSwap Amount of AERO to swap - * @param minWETHExpected Min. amount of WETH to expect - * @param feeBps Performance fee bps (Sent to strategist) - * @param sendYieldToDripper Sends yield to Dripper, if set to true. - * Otherwise, to the Guardian - */ - function harvestAndSwap( - uint256 aeroToSwap, - uint256 minWETHExpected, - uint256 feeBps, - bool sendYieldToDripper - ) external onlyGovernorOrStrategist { - address strategistAddr = vault.strategistAddr(); - require(strategistAddr != address(0), "Guardian address not set"); - - // Yields can either be sent to the Dripper or Strategist - address yieldRecipient = sendYieldToDripper - ? vault.dripper() - : strategistAddr; - require(yieldRecipient != address(0), "Yield recipient not set"); - - require(feeBps <= 10000, "Invalid Fee Bps"); - - // Collect all AERO - amoStrategy.collectRewardTokens(); - - uint256 aeroBalance = aero.balanceOf(address(this)); - if (aeroBalance == 0) { - // Do nothing if there's no AERO to transfer/swap - return; - } - - if (aeroToSwap > 0) { - if (aeroBalance < aeroToSwap) { - // Transfer in balance from the multisig as needed - // slither-disable-next-line unchecked-transfer arbitrary-send-erc20 - aero.safeTransferFrom( - strategistAddr, - address(this), - aeroToSwap - aeroBalance - ); - } - - _doSwap(aeroToSwap, minWETHExpected); - - // Figure out AERO left in contract after swap - aeroBalance = aero.balanceOf(address(this)); - } - - // Transfer out any leftover AERO after swap - if (aeroBalance > 0) { - aero.safeTransfer(strategistAddr, aeroBalance); - } - - // Computes using all balance the contract holds, - // not just the WETH received from swap. Use `transferToken` - // if there's any WETH left that needs to be taken out - uint256 availableWETHBalance = weth.balanceOf(address(this)); - // Computation rounds in favor of protocol - uint256 fee = (availableWETHBalance * feeBps) / 10000; - uint256 yield = availableWETHBalance - fee; - - // Transfer yield, if any - if (yield > 0) { - weth.safeTransfer(yieldRecipient, yield); - } - - // Transfer fee to the Guardian, if any - if (fee > 0) { - weth.safeTransfer(strategistAddr, fee); - } - - emit YieldSent(yieldRecipient, yield, fee); - } - - /** - * @notice Swaps AERO to WETH on Aerodrome - * @param aeroToSwap Amount of AERO to swap - * @param minWETHExpected Min. amount of WETH to expect - */ - function _doSwap(uint256 aeroToSwap, uint256 minWETHExpected) internal { - // Let the swap router move funds - aero.approve(address(swapRouter), aeroToSwap); - - // Do the swap - uint256 wethReceived = swapRouter.exactInputSingle( - ISwapRouter.ExactInputSingleParams({ - tokenIn: address(aero), - tokenOut: address(weth), - tickSpacing: 200, // From AERO/WETH pool contract - recipient: address(this), - deadline: block.timestamp, - amountIn: aeroToSwap, - amountOutMinimum: minWETHExpected, - sqrtPriceLimitX96: 0 - }) - ); - - emit RewardTokenSwapped( - address(aero), - address(weth), - 0, - aeroToSwap, - wethReceived - ); - } - - /** - * @notice Transfer token to governor. Intended for recovering tokens stuck in - * the contract, i.e. mistaken sends. - * Also, allows to transfer any AERO left in the contract. - * @param _asset Address for the asset - * @param _amount Amount of the asset to transfer - */ - function transferToken(address _asset, uint256 _amount) - external - virtual - onlyGovernor - { - IERC20(_asset).safeTransfer(governor(), _amount); - } -} diff --git a/contracts/contracts/harvest/OETHDripper.sol b/contracts/contracts/harvest/OETHDripper.sol deleted file mode 100644 index a96179a370..0000000000 --- a/contracts/contracts/harvest/OETHDripper.sol +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -import { Dripper } from "./Dripper.sol"; - -/** - * @title OETH Dripper Contract - * @author Origin Protocol Inc - */ -contract OETHDripper is Dripper { - constructor(address _vault, address _token) Dripper(_vault, _token) {} -} diff --git a/contracts/contracts/harvest/OETHHarvester.sol b/contracts/contracts/harvest/OETHHarvester.sol deleted file mode 100644 index 5d9dc8b480..0000000000 --- a/contracts/contracts/harvest/OETHHarvester.sol +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -import { AbstractHarvester } from "./AbstractHarvester.sol"; - -contract OETHHarvester is AbstractHarvester { - constructor(address _vault, address _wethAddress) - AbstractHarvester(_vault, _wethAddress) - {} -} diff --git a/contracts/contracts/harvest/README.md b/contracts/contracts/harvest/README.md index 82389cebb2..9d6fd7924f 100644 --- a/contracts/contracts/harvest/README.md +++ b/contracts/contracts/harvest/README.md @@ -32,22 +32,6 @@ Used on Mainnet for OETH, Base and Sonic. ![Fixed Rate Dripper Storage](../../docs/OETHFixedRateDripperStorage.svg) -## Harvester - -Used on Mainnet for OUSD. - -### Hierarchy - -![Harvester Hierarchy](../../docs/HarvesterHierarchy.svg) - -### Squashed - -![Harvester Squashed](../../docs/HarvesterSquashed.svg) - -### Storage - -![Harvester Storage](../../docs/HarvesterStorage.svg) - ## OETH Simple Harvester Used on Mainnet for OETH. diff --git a/contracts/contracts/interfaces/IBuyback.sol b/contracts/contracts/interfaces/IBuyback.sol deleted file mode 100644 index 3bcb71c3a5..0000000000 --- a/contracts/contracts/interfaces/IBuyback.sol +++ /dev/null @@ -1,6 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -interface IBuyback { - function swap() external; -} diff --git a/contracts/contracts/interfaces/IComptroller.sol b/contracts/contracts/interfaces/IComptroller.sol deleted file mode 100644 index 9156b04281..0000000000 --- a/contracts/contracts/interfaces/IComptroller.sol +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -interface IComptroller { - // Claim all the COMP accrued by specific holders in specific markets for their supplies and/or borrows - function claimComp( - address[] memory holders, - address[] memory cTokens, - bool borrowers, - bool suppliers - ) external; - - function oracle() external view returns (address); -} diff --git a/contracts/contracts/interfaces/IFraxETHMinter.sol b/contracts/contracts/interfaces/IFraxETHMinter.sol deleted file mode 100644 index 66c0130788..0000000000 --- a/contracts/contracts/interfaces/IFraxETHMinter.sol +++ /dev/null @@ -1,9 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -interface IFraxETHMinter { - function submitAndDeposit(address recipient) - external - payable - returns (uint256 shares); -} diff --git a/contracts/contracts/interfaces/IOneInch.sol b/contracts/contracts/interfaces/IOneInch.sol deleted file mode 100644 index 93e732219e..0000000000 --- a/contracts/contracts/interfaces/IOneInch.sol +++ /dev/null @@ -1,48 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; - -/// 1Inch swap data -struct SwapDescription { - IERC20 srcToken; // contract address of a token to sell - IERC20 dstToken; // contract address of a token to buy - address payable srcReceiver; - address payable dstReceiver; // Receiver of destination currency. default: fromAddress - uint256 amount; - uint256 minReturnAmount; - uint256 flags; -} - -/// @title Interface for making arbitrary calls during swap -interface IAggregationExecutor { - /// @notice propagates information about original msg.sender and executes arbitrary data - function execute(address msgSender) external payable; // 0x4b64e492 -} - -interface IOneInchRouter { - /// @notice Performs a swap, delegating all calls encoded in `data` to `executor`. - function swap( - IAggregationExecutor executor, - SwapDescription calldata desc, - bytes calldata permit, - bytes calldata data - ) external returns (uint256 returnAmount, uint256 spentAmount); - - /// @notice Performs swap using Uniswap exchange. Wraps and unwraps ETH if required. - function unoswapTo( - address payable recipient, - IERC20 srcToken, - uint256 amount, - uint256 minReturn, - uint256[] calldata pools - ) external payable returns (uint256 returnAmount); - - /// @notice Performs swap using Uniswap V3 exchange. Wraps and unwraps ETH if required. - function uniswapV3SwapTo( - address payable recipient, - uint256 amount, - uint256 minReturn, - uint256[] calldata pools - ) external payable returns (uint256 returnAmount); -} diff --git a/contracts/contracts/interfaces/IRETH.sol b/contracts/contracts/interfaces/IRETH.sol deleted file mode 100644 index ea70d6e516..0000000000 --- a/contracts/contracts/interfaces/IRETH.sol +++ /dev/null @@ -1,37 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -interface IRETH { - function getEthValue(uint256 _rethAmount) external view returns (uint256); - - function getRethValue(uint256 _ethAmount) external view returns (uint256); - - function getExchangeRate() external view returns (uint256); - - function totalSupply() external view returns (uint256); - - function balanceOf(address account) external view returns (uint256); - - function transfer(address recipient, uint256 amount) - external - returns (bool); - - function allowance(address owner, address spender) - external - view - returns (uint256); - - function approve(address spender, uint256 amount) external returns (bool); - - function transferFrom( - address sender, - address recipient, - uint256 amount - ) external returns (bool); - - function name() external view returns (string memory); - - function symbol() external view returns (string memory); - - function decimals() external view returns (uint8); -} diff --git a/contracts/contracts/interfaces/ISfrxETH.sol b/contracts/contracts/interfaces/ISfrxETH.sol deleted file mode 100644 index 4ff0324ff9..0000000000 --- a/contracts/contracts/interfaces/ISfrxETH.sol +++ /dev/null @@ -1,127 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -interface ISfrxETH { - event Approval( - address indexed owner, - address indexed spender, - uint256 amount - ); - event Deposit( - address indexed caller, - address indexed owner, - uint256 assets, - uint256 shares - ); - event NewRewardsCycle(uint32 indexed cycleEnd, uint256 rewardAmount); - event Transfer(address indexed from, address indexed to, uint256 amount); - event Withdraw( - address indexed caller, - address indexed receiver, - address indexed owner, - uint256 assets, - uint256 shares - ); - - function DOMAIN_SEPARATOR() external view returns (bytes32); - - function allowance(address, address) external view returns (uint256); - - function approve(address spender, uint256 amount) external returns (bool); - - function asset() external view returns (address); - - function balanceOf(address) external view returns (uint256); - - function convertToAssets(uint256 shares) external view returns (uint256); - - function convertToShares(uint256 assets) external view returns (uint256); - - function decimals() external view returns (uint8); - - function deposit(uint256 assets, address receiver) - external - returns (uint256 shares); - - function depositWithSignature( - uint256 assets, - address receiver, - uint256 deadline, - bool approveMax, - uint8 v, - bytes32 r, - bytes32 s - ) external returns (uint256 shares); - - function lastRewardAmount() external view returns (uint192); - - function lastSync() external view returns (uint32); - - function maxDeposit(address) external view returns (uint256); - - function maxMint(address) external view returns (uint256); - - function maxRedeem(address owner) external view returns (uint256); - - function maxWithdraw(address owner) external view returns (uint256); - - function mint(uint256 shares, address receiver) - external - returns (uint256 assets); - - function name() external view returns (string memory); - - function nonces(address) external view returns (uint256); - - function permit( - address owner, - address spender, - uint256 value, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) external; - - function previewDeposit(uint256 assets) external view returns (uint256); - - function previewMint(uint256 shares) external view returns (uint256); - - function previewRedeem(uint256 shares) external view returns (uint256); - - function previewWithdraw(uint256 assets) external view returns (uint256); - - function pricePerShare() external view returns (uint256); - - function redeem( - uint256 shares, - address receiver, - address owner - ) external returns (uint256 assets); - - function rewardsCycleEnd() external view returns (uint32); - - function rewardsCycleLength() external view returns (uint32); - - function symbol() external view returns (string memory); - - function syncRewards() external; - - function totalAssets() external view returns (uint256); - - function totalSupply() external view returns (uint256); - - function transfer(address to, uint256 amount) external returns (bool); - - function transferFrom( - address from, - address to, - uint256 amount - ) external returns (bool); - - function withdraw( - uint256 assets, - address receiver, - address owner - ) external returns (uint256 shares); -} diff --git a/contracts/contracts/interfaces/ISwapper.sol b/contracts/contracts/interfaces/ISwapper.sol deleted file mode 100644 index 77f7100885..0000000000 --- a/contracts/contracts/interfaces/ISwapper.sol +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -interface ISwapper { - /** - * @param fromAsset The token address of the asset being sold. - * @param toAsset The token address of the asset being purchased. - * @param fromAssetAmount The amount of assets being sold. - * @param minToAssetAmmount The minimum amount of assets to be purchased. - * @param data tx.data returned from 1Inch's /v5.0/1/swap API - */ - function swap( - address fromAsset, - address toAsset, - uint256 fromAssetAmount, - uint256 minToAssetAmmount, - bytes calldata data - ) external returns (uint256 toAssetAmount); -} diff --git a/contracts/contracts/interfaces/balancer/IMetaStablePool.sol b/contracts/contracts/interfaces/balancer/IMetaStablePool.sol deleted file mode 100644 index 760737359f..0000000000 --- a/contracts/contracts/interfaces/balancer/IMetaStablePool.sol +++ /dev/null @@ -1,11 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import { IRateProvider } from "./IRateProvider.sol"; - -interface IMetaStablePool { - function getRateProviders() - external - view - returns (IRateProvider[] memory providers); -} diff --git a/contracts/contracts/interfaces/balancer/IOracleWeightedPool.sol b/contracts/contracts/interfaces/balancer/IOracleWeightedPool.sol deleted file mode 100644 index d58d6b89d4..0000000000 --- a/contracts/contracts/interfaces/balancer/IOracleWeightedPool.sol +++ /dev/null @@ -1,44 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -// The three values that can be queried: -// -// - PAIR_PRICE: the price of the tokens in the Pool, expressed as the price of the second token in units of the -// first token. For example, if token A is worth $2, and token B is worth $4, the pair price will be 2.0. -// Note that the price is computed *including* the tokens decimals. This means that the pair price of a Pool with -// DAI and USDC will be close to 1.0, despite DAI having 18 decimals and USDC 6. -// -// - BPT_PRICE: the price of the Pool share token (BPT), in units of the first token. -// Note that the price is computed *including* the tokens decimals. This means that the BPT price of a Pool with -// USDC in which BPT is worth $5 will be 5.0, despite the BPT having 18 decimals and USDC 6. -// -// - INVARIANT: the value of the Pool's invariant, which serves as a measure of its liquidity. -enum Variable { - PAIR_PRICE, - BPT_PRICE, - INVARIANT -} - -/** - * @dev Information for a Time Weighted Average query. - * - * Each query computes the average over a window of duration `secs` seconds that ended `ago` seconds ago. For - * example, the average over the past 30 minutes is computed by settings secs to 1800 and ago to 0. If secs is 1800 - * and ago is 1800 as well, the average between 60 and 30 minutes ago is computed instead. - */ -struct OracleAverageQuery { - Variable variable; - uint256 secs; - uint256 ago; -} - -interface IOracleWeightedPool { - /** - * @dev Returns the time average weighted price corresponding to each of `queries`. Prices are represented as 18 - * decimal fixed point values. - */ - function getTimeWeightedAverage(OracleAverageQuery[] memory queries) - external - view - returns (uint256[] memory results); -} diff --git a/contracts/contracts/interfaces/morpho/ILens.sol b/contracts/contracts/interfaces/morpho/ILens.sol deleted file mode 100644 index 6e8fc2ab9f..0000000000 --- a/contracts/contracts/interfaces/morpho/ILens.sol +++ /dev/null @@ -1,305 +0,0 @@ -// SPDX-License-Identifier: GNU AGPLv3 -pragma solidity ^0.8.0; - -import "./compound/ICompoundOracle.sol"; -import "./IMorpho.sol"; - -interface ILens { - /// STORAGE /// - - function MAX_BASIS_POINTS() external view returns (uint256); - - function WAD() external view returns (uint256); - - function morpho() external view returns (IMorpho); - - function comptroller() external view returns (IComptroller); - - /// GENERAL /// - - function getTotalSupply() - external - view - returns ( - uint256 p2pSupplyAmount, - uint256 poolSupplyAmount, - uint256 totalSupplyAmount - ); - - function getTotalBorrow() - external - view - returns ( - uint256 p2pBorrowAmount, - uint256 poolBorrowAmount, - uint256 totalBorrowAmount - ); - - /// MARKETS /// - - function isMarketCreated(address _poolToken) external view returns (bool); - - function isMarketCreatedAndNotPaused(address _poolToken) - external - view - returns (bool); - - function isMarketCreatedAndNotPausedNorPartiallyPaused(address _poolToken) - external - view - returns (bool); - - function getAllMarkets() - external - view - returns (address[] memory marketsCreated_); - - function getMainMarketData(address _poolToken) - external - view - returns ( - uint256 avgSupplyRatePerBlock, - uint256 avgBorrowRatePerBlock, - uint256 p2pSupplyAmount, - uint256 p2pBorrowAmount, - uint256 poolSupplyAmount, - uint256 poolBorrowAmount - ); - - function getAdvancedMarketData(address _poolToken) - external - view - returns ( - uint256 p2pSupplyIndex, - uint256 p2pBorrowIndex, - uint256 poolSupplyIndex, - uint256 poolBorrowIndex, - uint32 lastUpdateBlockNumber, - uint256 p2pSupplyDelta, - uint256 p2pBorrowDelta - ); - - function getMarketConfiguration(address _poolToken) - external - view - returns ( - address underlying, - bool isCreated, - bool p2pDisabled, - bool isPaused, - bool isPartiallyPaused, - uint16 reserveFactor, - uint16 p2pIndexCursor, - uint256 collateralFactor - ); - - function getTotalMarketSupply(address _poolToken) - external - view - returns (uint256 p2pSupplyAmount, uint256 poolSupplyAmount); - - function getTotalMarketBorrow(address _poolToken) - external - view - returns (uint256 p2pBorrowAmount, uint256 poolBorrowAmount); - - /// INDEXES /// - - function getCurrentP2PSupplyIndex(address _poolToken) - external - view - returns (uint256); - - function getCurrentP2PBorrowIndex(address _poolToken) - external - view - returns (uint256); - - function getCurrentPoolIndexes(address _poolToken) - external - view - returns ( - uint256 currentPoolSupplyIndex, - uint256 currentPoolBorrowIndex - ); - - function getIndexes(address _poolToken, bool _computeUpdatedIndexes) - external - view - returns ( - uint256 p2pSupplyIndex, - uint256 p2pBorrowIndex, - uint256 poolSupplyIndex, - uint256 poolBorrowIndex - ); - - /// USERS /// - - function getEnteredMarkets(address _user) - external - view - returns (address[] memory enteredMarkets); - - function getUserHealthFactor( - address _user, - address[] calldata _updatedMarkets - ) external view returns (uint256); - - function getUserBalanceStates( - address _user, - address[] calldata _updatedMarkets - ) - external - view - returns ( - uint256 collateralValue, - uint256 debtValue, - uint256 maxDebtValue - ); - - function getCurrentSupplyBalanceInOf(address _poolToken, address _user) - external - view - returns ( - uint256 balanceOnPool, - uint256 balanceInP2P, - uint256 totalBalance - ); - - function getCurrentBorrowBalanceInOf(address _poolToken, address _user) - external - view - returns ( - uint256 balanceOnPool, - uint256 balanceInP2P, - uint256 totalBalance - ); - - function getUserMaxCapacitiesForAsset(address _user, address _poolToken) - external - view - returns (uint256 withdrawable, uint256 borrowable); - - function getUserHypotheticalBalanceStates( - address _user, - address _poolToken, - uint256 _withdrawnAmount, - uint256 _borrowedAmount - ) external view returns (uint256 debtValue, uint256 maxDebtValue); - - function getUserLiquidityDataForAsset( - address _user, - address _poolToken, - bool _computeUpdatedIndexes, - ICompoundOracle _oracle - ) external view returns (Types.AssetLiquidityData memory assetData); - - function isLiquidatable(address _user, address[] memory _updatedMarkets) - external - view - returns (bool); - - function computeLiquidationRepayAmount( - address _user, - address _poolTokenBorrowed, - address _poolTokenCollateral, - address[] calldata _updatedMarkets - ) external view returns (uint256 toRepay); - - /// RATES /// - - function getAverageSupplyRatePerBlock(address _poolToken) - external - view - returns ( - uint256 avgSupplyRatePerBlock, - uint256 p2pSupplyAmount, - uint256 poolSupplyAmount - ); - - function getAverageBorrowRatePerBlock(address _poolToken) - external - view - returns ( - uint256 avgBorrowRatePerBlock, - uint256 p2pBorrowAmount, - uint256 poolBorrowAmount - ); - - function getNextUserSupplyRatePerBlock( - address _poolToken, - address _user, - uint256 _amount - ) - external - view - returns ( - uint256 nextSupplyRatePerBlock, - uint256 balanceOnPool, - uint256 balanceInP2P, - uint256 totalBalance - ); - - function getNextUserBorrowRatePerBlock( - address _poolToken, - address _user, - uint256 _amount - ) - external - view - returns ( - uint256 nextBorrowRatePerBlock, - uint256 balanceOnPool, - uint256 balanceInP2P, - uint256 totalBalance - ); - - function getCurrentUserSupplyRatePerBlock(address _poolToken, address _user) - external - view - returns (uint256); - - function getCurrentUserBorrowRatePerBlock(address _poolToken, address _user) - external - view - returns (uint256); - - function getRatesPerBlock(address _poolToken) - external - view - returns ( - uint256 p2pSupplyRate, - uint256 p2pBorrowRate, - uint256 poolSupplyRate, - uint256 poolBorrowRate - ); - - /// REWARDS /// - - function getUserUnclaimedRewards( - address[] calldata _poolTokens, - address _user - ) external view returns (uint256 unclaimedRewards); - - function getAccruedSupplierComp( - address _supplier, - address _poolToken, - uint256 _balance - ) external view returns (uint256); - - function getAccruedBorrowerComp( - address _borrower, - address _poolToken, - uint256 _balance - ) external view returns (uint256); - - function getCurrentCompSupplyIndex(address _poolToken) - external - view - returns (uint256); - - function getCurrentCompBorrowIndex(address _poolToken) - external - view - returns (uint256); -} diff --git a/contracts/contracts/interfaces/morpho/IMorpho.sol b/contracts/contracts/interfaces/morpho/IMorpho.sol deleted file mode 100644 index 60ec3d376c..0000000000 --- a/contracts/contracts/interfaces/morpho/IMorpho.sol +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-License-Identifier: GNU AGPLv3 -pragma solidity ^0.8.0; - -import "./Types.sol"; -import "../IComptroller.sol"; -import "./compound/ICompoundOracle.sol"; - -// prettier-ignore -interface IMorpho { - function comptroller() external view returns (IComptroller); - function supply(address _poolTokenAddress, address _onBehalf, uint256 _amount) external; - function supply(address _poolTokenAddress, address _onBehalf, uint256 _amount, uint256 _maxGasForMatching) external; - function withdraw(address _poolTokenAddress, uint256 _amount) external; - function claimRewards( - address[] calldata _cTokenAddresses, - bool _tradeForMorphoToken - ) external returns (uint256 claimedAmount); -} diff --git a/contracts/contracts/interfaces/morpho/Types.sol b/contracts/contracts/interfaces/morpho/Types.sol deleted file mode 100644 index 2929acde4b..0000000000 --- a/contracts/contracts/interfaces/morpho/Types.sol +++ /dev/null @@ -1,76 +0,0 @@ -// SPDX-License-Identifier: GNU AGPLv3 -pragma solidity ^0.8.0; - -/// @title Types. -/// @author Morpho Labs. -/// @custom:contact security@morpho.xyz -/// @dev Common types and structs used in Moprho contracts. -library Types { - /// ENUMS /// - - enum PositionType { - SUPPLIERS_IN_P2P, - SUPPLIERS_ON_POOL, - BORROWERS_IN_P2P, - BORROWERS_ON_POOL - } - - /// STRUCTS /// - - struct SupplyBalance { - uint256 inP2P; // In supplier's peer-to-peer unit, a unit that grows in underlying value, to keep track of the interests earned by suppliers in peer-to-peer. Multiply by the peer-to-peer supply index to get the underlying amount. - uint256 onPool; // In cToken. Multiply by the pool supply index to get the underlying amount. - } - - struct BorrowBalance { - uint256 inP2P; // In borrower's peer-to-peer unit, a unit that grows in underlying value, to keep track of the interests paid by borrowers in peer-to-peer. Multiply by the peer-to-peer borrow index to get the underlying amount. - uint256 onPool; // In cdUnit, a unit that grows in value, to keep track of the debt increase when borrowers are on Compound. Multiply by the pool borrow index to get the underlying amount. - } - - // Max gas to consume during the matching process for supply, borrow, withdraw and repay functions. - struct MaxGasForMatching { - uint64 supply; - uint64 borrow; - uint64 withdraw; - uint64 repay; - } - - struct Delta { - uint256 p2pSupplyDelta; // Difference between the stored peer-to-peer supply amount and the real peer-to-peer supply amount (in pool supply unit). - uint256 p2pBorrowDelta; // Difference between the stored peer-to-peer borrow amount and the real peer-to-peer borrow amount (in pool borrow unit). - uint256 p2pSupplyAmount; // Sum of all stored peer-to-peer supply (in peer-to-peer supply unit). - uint256 p2pBorrowAmount; // Sum of all stored peer-to-peer borrow (in peer-to-peer borrow unit). - } - - struct AssetLiquidityData { - uint256 collateralValue; // The collateral value of the asset. - uint256 maxDebtValue; // The maximum possible debt value of the asset. - uint256 debtValue; // The debt value of the asset. - uint256 underlyingPrice; // The price of the token. - uint256 collateralFactor; // The liquidation threshold applied on this token. - } - - struct LiquidityData { - uint256 collateralValue; // The collateral value. - uint256 maxDebtValue; // The maximum debt value possible. - uint256 debtValue; // The debt value. - } - - // Variables are packed together to save gas (will not exceed their limit during Morpho's lifetime). - struct LastPoolIndexes { - uint32 lastUpdateBlockNumber; // The last time the peer-to-peer indexes were updated. - uint112 lastSupplyPoolIndex; // Last pool supply index. - uint112 lastBorrowPoolIndex; // Last pool borrow index. - } - - struct MarketParameters { - uint16 reserveFactor; // Proportion of the interest earned by users sent to the DAO for each market, in basis point (100% = 10 000). The value is set at market creation. - uint16 p2pIndexCursor; // Position of the peer-to-peer rate in the pool's spread. Determine the weights of the weighted arithmetic average in the indexes computations ((1 - p2pIndexCursor) * r^S + p2pIndexCursor * r^B) (in basis point). - } - - struct MarketStatus { - bool isCreated; // Whether or not this market is created. - bool isPaused; // Whether the market is paused or not (all entry points on Morpho are frozen; supply, borrow, withdraw, repay and liquidate). - bool isPartiallyPaused; // Whether the market is partially paused or not (only supply and borrow are frozen). - } -} diff --git a/contracts/contracts/interfaces/morpho/compound/ICompoundOracle.sol b/contracts/contracts/interfaces/morpho/compound/ICompoundOracle.sol deleted file mode 100644 index c684ed2c6d..0000000000 --- a/contracts/contracts/interfaces/morpho/compound/ICompoundOracle.sol +++ /dev/null @@ -1,6 +0,0 @@ -// SPDX-License-Identifier: GNU AGPLv3 -pragma solidity ^0.8.0; - -interface ICompoundOracle { - function getUnderlyingPrice(address) external view returns (uint256); -} diff --git a/contracts/contracts/mocks/Mock1InchSwapRouter.sol b/contracts/contracts/mocks/Mock1InchSwapRouter.sol deleted file mode 100644 index dcbd8f242a..0000000000 --- a/contracts/contracts/mocks/Mock1InchSwapRouter.sol +++ /dev/null @@ -1,109 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; - -import { SwapDescription } from "../interfaces/IOneInch.sol"; - -contract Mock1InchSwapRouter { - using SafeERC20 for IERC20; - - event MockSwap(address executor, bytes permitData, bytes executorData); - - event MockSwapDesc( - address srcToken, - address dstToken, - address srcReceiver, - address dstReceiver, - uint256 amount, - uint256 minReturnAmount, - uint256 flags - ); - - event MockUnoswapTo( - address recipient, - address srcToken, - uint256 amount, - uint256 minReturn, - uint256[] pools - ); - - event MockUniswapV3SwapTo( - address recipient, - uint256 amount, - uint256 minReturn, - uint256[] pools - ); - - /** - * @dev transfers the shource asset and returns the minReturnAmount of the destination asset. - */ - function swap( - address executor, - SwapDescription calldata desc, - bytes calldata permitData, - bytes calldata executorData - ) public returns (uint256 returnAmount, uint256 spentAmount) { - // Transfer the source tokens to the receiver contract - IERC20(desc.srcToken).safeTransferFrom( - msg.sender, - desc.srcReceiver, - desc.amount - ); - - // Transfer the destination tokens to the recipient - IERC20(desc.dstToken).safeTransfer( - desc.dstReceiver, - desc.minReturnAmount - ); - - emit MockSwap(executor, permitData, executorData); - _swapDesc(desc); - returnAmount = 0; - spentAmount = 0; - } - - function _swapDesc(SwapDescription calldata desc) public { - emit MockSwapDesc( - address(desc.srcToken), - address(desc.dstToken), - desc.srcReceiver, - desc.dstReceiver, - desc.amount, - desc.minReturnAmount, - desc.flags - ); - } - - /** - * @dev only transfers the source asset to this contract. - * Ideally it would return the destination asset but that's encoded in the pools array. - */ - function unoswapTo( - address payable recipient, - address srcToken, - uint256 amount, - uint256 minReturn, - uint256[] calldata pools - ) public returns (uint256 returnAmount) { - // transfer the from asset from the caller - IERC20(srcToken).safeTransferFrom(msg.sender, address(this), amount); - - emit MockUnoswapTo(recipient, srcToken, amount, minReturn, pools); - returnAmount = 0; - } - - /** - * @dev does not do any transfers. Just emits MockUniswapV3SwapTo. - */ - function uniswapV3SwapTo( - address payable recipient, - uint256 amount, - uint256 minReturn, - uint256[] calldata pools - ) public returns (uint256 returnAmount) { - emit MockUniswapV3SwapTo(recipient, amount, minReturn, pools); - returnAmount = 0; - } -} diff --git a/contracts/contracts/mocks/MockAAVEToken.sol b/contracts/contracts/mocks/MockAAVEToken.sol deleted file mode 100644 index fbb3af48fc..0000000000 --- a/contracts/contracts/mocks/MockAAVEToken.sol +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -import "./MintableERC20.sol"; - -contract MockAAVEToken is MintableERC20 { - constructor() ERC20("AAVE", "AAVE") {} -} diff --git a/contracts/contracts/mocks/MockAave.sol b/contracts/contracts/mocks/MockAave.sol deleted file mode 100644 index d887b083a9..0000000000 --- a/contracts/contracts/mocks/MockAave.sol +++ /dev/null @@ -1,100 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import { IERC20, ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; - -import { MintableERC20 } from "./MintableERC20.sol"; -import { IAaveLendingPool, ILendingPoolAddressesProvider } from "../strategies/IAave.sol"; -import { StableMath } from "../utils/StableMath.sol"; - -// 1. User calls 'getLendingPool' -// 2. User calls 'deposit' (Aave) -// - Deposit their underlying -// - Mint aToken to them -// 3. User calls redeem (aToken) -// - Retrieve their aToken -// - Return equal amount of underlying - -contract MockAToken is MintableERC20 { - address public lendingPool; - IERC20 public underlyingToken; - using SafeERC20 for IERC20; - - constructor( - address _lendingPool, - string memory _name, - string memory _symbol, - IERC20 _underlyingToken - ) ERC20(_name, _symbol) { - lendingPool = _lendingPool; - underlyingToken = _underlyingToken; - // addMinter(_lendingPool); - } - - function decimals() public view override returns (uint8) { - return ERC20(address(underlyingToken)).decimals(); - } - - function poolRedeem(uint256 _amount, address _to) external { - require(msg.sender == lendingPool, "pool only"); - // Redeem these a Tokens - _burn(_to, _amount); - // For the underlying - underlyingToken.safeTransferFrom(lendingPool, _to, _amount); - } -} - -contract MockAave is IAaveLendingPool, ILendingPoolAddressesProvider { - using SafeERC20 for IERC20; - using StableMath for uint256; - - mapping(address => address) reserveToAToken; - address pool = address(this); - address payable core = payable(address(this)); - uint256 factor; - - function addAToken(address _aToken, address _underlying) public { - IERC20(_underlying).safeApprove(_aToken, 0); - IERC20(_underlying).safeApprove(_aToken, type(uint256).max); - reserveToAToken[_underlying] = _aToken; - } - - // set the reserve factor / basically the interest on deposit - // in 18 precision - // so 0.5% would be 5 * 10 ^ 15 - function setFactor(uint256 factor_) public { - factor = factor_; - } - - function deposit( - address _reserve, - uint256 _amount, - address _to, - uint16 /*_referralCode*/ - ) external override { - uint256 previousBal = IERC20(reserveToAToken[_reserve]).balanceOf( - msg.sender - ); - uint256 interest = previousBal.mulTruncate(factor); - MintableERC20(reserveToAToken[_reserve]).mintTo(msg.sender, interest); - // Take their reserve - IERC20(_reserve).safeTransferFrom(msg.sender, address(this), _amount); - // Credit them with aToken - MintableERC20(reserveToAToken[_reserve]).mintTo(_to, _amount); - } - - function withdraw( - address asset, - uint256 amount, - address to - ) external override returns (uint256) { - MockAToken atoken = MockAToken(reserveToAToken[asset]); - atoken.poolRedeem(amount, to); - return amount; - } - - function getLendingPool() external view override returns (address) { - return pool; - } -} diff --git a/contracts/contracts/mocks/MockAaveIncentivesController.sol b/contracts/contracts/mocks/MockAaveIncentivesController.sol deleted file mode 100644 index 705ded336a..0000000000 --- a/contracts/contracts/mocks/MockAaveIncentivesController.sol +++ /dev/null @@ -1,52 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -import { MockStkAave } from "./MockStkAave.sol"; - -contract MockAaveIncentivesController { - mapping(address => uint256) private rewards; - MockStkAave public REWARD_TOKEN; - - constructor(address _reward_token) { - REWARD_TOKEN = MockStkAave(_reward_token); - } - - function setRewardsBalance(address user, uint256 amount) external { - rewards[user] = amount; - } - - /** - * @dev Returns the total of rewards of an user, already accrued + not yet accrued - * @param user The address of the user - * @return The rewards - **/ - // solhint-disable-next-line no-unused-vars - function getRewardsBalance(address[] calldata assets, address user) - external - view - returns (uint256) - { - return rewards[user]; - } - - /** - * @dev Claims reward for an user, on all the assets of the lending pool, accumulating the pending rewards - * @param amount Amount of rewards to claim - * @param to Address that will be receiving the rewards - * @return Rewards claimed - **/ - function claimRewards( - // solhint-disable-next-line no-unused-vars - address[] calldata assets, - uint256 amount, - address to - ) external returns (uint256) { - require(amount > 0); - require(rewards[to] == amount); - REWARD_TOKEN.mint(amount); - require(REWARD_TOKEN.transfer(to, amount)); - // solhint-disable-next-line reentrancy - rewards[to] = 0; - return amount; - } -} diff --git a/contracts/contracts/mocks/MockAura.sol b/contracts/contracts/mocks/MockAura.sol deleted file mode 100644 index 51fed93352..0000000000 --- a/contracts/contracts/mocks/MockAura.sol +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -import "./MintableERC20.sol"; - -contract MockAura is MintableERC20 { - constructor() ERC20("Aura", "AURA") {} -} diff --git a/contracts/contracts/mocks/MockBAL.sol b/contracts/contracts/mocks/MockBAL.sol deleted file mode 100644 index 498692ee16..0000000000 --- a/contracts/contracts/mocks/MockBAL.sol +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -import "./MintableERC20.sol"; - -contract MockBAL is MintableERC20 { - constructor() ERC20("Balancer", "BAL") {} -} diff --git a/contracts/contracts/mocks/MockBalancerVault.sol b/contracts/contracts/mocks/MockBalancerVault.sol deleted file mode 100644 index 00470bd0f4..0000000000 --- a/contracts/contracts/mocks/MockBalancerVault.sol +++ /dev/null @@ -1,62 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { IBalancerVault } from "../interfaces/balancer/IBalancerVault.sol"; -import { MintableERC20 } from "./MintableERC20.sol"; -import { StableMath } from "../utils/StableMath.sol"; - -// import "hardhat/console.sol"; - -contract MockBalancerVault { - using StableMath for uint256; - uint256 public slippage = 1 ether; - bool public transferDisabled = false; - bool public slippageErrorDisabled = false; - - function swap( - IBalancerVault.SingleSwap calldata singleSwap, - IBalancerVault.FundManagement calldata funds, - uint256 minAmountOut, - uint256 - ) external returns (uint256 amountCalculated) { - amountCalculated = (minAmountOut * slippage) / 1 ether; - if (!slippageErrorDisabled) { - require(amountCalculated >= minAmountOut, "Slippage error"); - } - IERC20(singleSwap.assetIn).transferFrom( - funds.sender, - address(this), - singleSwap.amount - ); - if (!transferDisabled) { - MintableERC20(singleSwap.assetOut).mintTo( - funds.recipient, - amountCalculated - ); - } - } - - function setSlippage(uint256 _slippage) external { - slippage = _slippage; - } - - function getPoolTokenInfo(bytes32 poolId, address token) - external - view - returns ( - uint256, - uint256, - uint256, - address - ) - {} - - function disableTransfer() external { - transferDisabled = true; - } - - function disableSlippageError() external { - slippageErrorDisabled = true; - } -} diff --git a/contracts/contracts/mocks/MockCOMP.sol b/contracts/contracts/mocks/MockCOMP.sol deleted file mode 100644 index 99fc95efc0..0000000000 --- a/contracts/contracts/mocks/MockCOMP.sol +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -import "./MintableERC20.sol"; - -contract MockCOMP is MintableERC20 { - constructor() ERC20("COMP", "COMP") {} -} diff --git a/contracts/contracts/mocks/MockCToken.sol b/contracts/contracts/mocks/MockCToken.sol deleted file mode 100644 index 47fa14d0ac..0000000000 --- a/contracts/contracts/mocks/MockCToken.sol +++ /dev/null @@ -1,105 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -import { IERC20, ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; - -import { ICERC20 } from "../strategies/ICompound.sol"; -import { StableMath } from "../utils/StableMath.sol"; - -contract MockCToken is ICERC20, ERC20 { - using StableMath for uint256; - - IERC20 public underlyingToken; - // underlying = cToken * exchangeRate - // cToken = underlying / exchangeRate - uint256 exchangeRate; - address public override comptroller; - - constructor(ERC20 _underlyingToken, address _comptroller) - ERC20("cMock", "cMK") - { - uint8 underlyingDecimals = _underlyingToken.decimals(); - // if has 18 dp, exchange rate should be 1e26 - // if has 8 dp, exchange rate should be 1e18 - if (underlyingDecimals > 8) { - exchangeRate = 10**uint256(18 + underlyingDecimals - 10); - } else if (underlyingDecimals < 8) { - // e.g. 18-8+6 = 16 - exchangeRate = 10**uint256(18 - 8 + underlyingDecimals); - } else { - exchangeRate = 1e18; - } - underlyingToken = _underlyingToken; - comptroller = _comptroller; - } - - function decimals() public pure override returns (uint8) { - return 8; - } - - function mint(uint256 mintAmount) public override returns (uint256) { - // Credit them with cToken - _mint(msg.sender, mintAmount.divPrecisely(exchangeRate)); - // Take their reserve - underlyingToken.transferFrom(msg.sender, address(this), mintAmount); - return 0; - } - - function redeem(uint256 redeemAmount) external override returns (uint256) { - uint256 tokenAmount = redeemAmount.mulTruncate(exchangeRate); - // Burn the cToken - _burn(msg.sender, redeemAmount); - // Transfer underlying to caller - underlyingToken.transfer(msg.sender, tokenAmount); - return 0; - } - - function redeemUnderlying(uint256 redeemAmount) - external - override - returns (uint256) - { - uint256 cTokens = redeemAmount.divPrecisely(exchangeRate); - // Burn the cToken - _burn(msg.sender, cTokens); - // Transfer underlying to caller - underlyingToken.transfer(msg.sender, redeemAmount); - return 0; - } - - function balanceOfUnderlying(address owner) - external - view - override - returns (uint256) - { - uint256 cTokenBal = this.balanceOf(owner); - return cTokenBal.mulTruncate(exchangeRate); - } - - function balanceOf(address owner) - public - view - override(ICERC20, ERC20) - returns (uint256) - { - return ERC20.balanceOf(owner); - } - - function updateExchangeRate() - internal - view - returns (uint256 newExchangeRate) - { - uint256 factor = 100002 * (10**13); // 0.002% - newExchangeRate = exchangeRate.mulTruncate(factor); - } - - function exchangeRateStored() external view override returns (uint256) { - return exchangeRate; - } - - function supplyRatePerBlock() external pure override returns (uint256) { - return 141 * (10**8); - } -} diff --git a/contracts/contracts/mocks/MockCVXLocker.sol b/contracts/contracts/mocks/MockCVXLocker.sol deleted file mode 100644 index e94adee0a8..0000000000 --- a/contracts/contracts/mocks/MockCVXLocker.sol +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; - -contract MockCVXLocker { - address public immutable cvx; - mapping(address => uint256) public lockedBalanceOf; - - constructor(address _cvx) { - cvx = _cvx; - } - - function lock( - address _account, - uint256 _amount, - uint256 - ) external { - lockedBalanceOf[_account] += _amount; - ERC20(cvx).transferFrom(msg.sender, address(this), _amount); - } - - function unlockAllTokens(address _account) external { - lockedBalanceOf[_account] = 0; - ERC20(cvx).transfer(_account, lockedBalanceOf[_account]); - } -} diff --git a/contracts/contracts/mocks/MockComptroller.sol b/contracts/contracts/mocks/MockComptroller.sol deleted file mode 100644 index 1e9898f713..0000000000 --- a/contracts/contracts/mocks/MockComptroller.sol +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -contract MockComptroller { - // Claim all the COMP accrued by specific holders in specific markets for their supplies and/or borrows - function claimComp( - address[] memory holders, - address[] memory cTokens, - bool borrowers, - bool suppliers - ) external {} -} diff --git a/contracts/contracts/mocks/MockEvilDAI.sol b/contracts/contracts/mocks/MockEvilDAI.sol deleted file mode 100644 index 1342c8c183..0000000000 --- a/contracts/contracts/mocks/MockEvilDAI.sol +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -import "./MintableERC20.sol"; -import { IVault } from "../interfaces/IVault.sol"; - -contract MockEvilDAI is MintableERC20 { - address host; - address realCoin; - - constructor(address _host, address _realCoin) ERC20("DAI", "DAI") { - host = _host; - realCoin = _realCoin; - } - - function transferFrom( - // solhint-disable-next-line no-unused-vars - address _from, - // solhint-disable-next-line no-unused-vars - address _to, - uint256 _amount - ) public override returns (bool) { - // call mint again! - if (_amount != 69) { - IVault(host).mint(address(this), 69, 0); - } - return true; - } -} diff --git a/contracts/contracts/mocks/MockEvilReentrantContract.sol b/contracts/contracts/mocks/MockEvilReentrantContract.sol deleted file mode 100644 index fea2c81bb1..0000000000 --- a/contracts/contracts/mocks/MockEvilReentrantContract.sol +++ /dev/null @@ -1,130 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -import { IVault } from "../interfaces/IVault.sol"; -import { IOracle } from "../interfaces/IOracle.sol"; -import { IRateProvider } from "../interfaces/balancer/IRateProvider.sol"; - -import { IBalancerVault } from "../interfaces/balancer/IBalancerVault.sol"; -import { IERC20 } from "../utils/InitializableAbstractStrategy.sol"; - -import { StableMath } from "../utils/StableMath.sol"; - -contract MockEvilReentrantContract { - using StableMath for uint256; - - IBalancerVault public immutable balancerVault; - IERC20 public immutable reth; - IERC20 public immutable weth; - IVault public immutable oethVault; - address public immutable poolAddress; - bytes32 public immutable balancerPoolId; - address public immutable priceProvider; - - constructor( - address _balancerVault, - address _oethVault, - address _reth, - address _weth, - address _poolAddress, - bytes32 _poolId - ) { - balancerVault = IBalancerVault(_balancerVault); - oethVault = IVault(_oethVault); - reth = IERC20(_reth); - weth = IERC20(_weth); - poolAddress = _poolAddress; - balancerPoolId = _poolId; - } - - function doEvilStuff() public { - uint256 rethPrice = IOracle(priceProvider).price(address(reth)); - - // 1. Join pool - uint256[] memory amounts = new uint256[](2); - amounts[0] = uint256(10 ether); - amounts[1] = rethPrice * 10; - - address[] memory assets = new address[](2); - assets[0] = address(reth); - assets[1] = address(weth); - - uint256 minBPT = getBPTExpected(assets, amounts).mulTruncate( - 0.99 ether - ); - - bytes memory joinUserData = abi.encode( - IBalancerVault.WeightedPoolJoinKind.EXACT_TOKENS_IN_FOR_BPT_OUT, - amounts, - minBPT - ); - - IBalancerVault.JoinPoolRequest memory joinRequest = IBalancerVault - .JoinPoolRequest(assets, amounts, joinUserData, false); - - balancerVault.joinPool( - balancerPoolId, - address(this), - address(this), - joinRequest - ); - - uint256 bptTokenBalance = IERC20(poolAddress).balanceOf(address(this)); - - // 2. Redeem as ETH - bytes memory exitUserData = abi.encode( - IBalancerVault.WeightedPoolExitKind.EXACT_BPT_IN_FOR_ONE_TOKEN_OUT, - bptTokenBalance, - 1 - ); - - assets[1] = address(0); // Receive ETH instead of WETH - uint256[] memory exitAmounts = new uint256[](2); - exitAmounts[1] = 15 ether; - IBalancerVault.ExitPoolRequest memory exitRequest = IBalancerVault - .ExitPoolRequest(assets, exitAmounts, exitUserData, false); - - balancerVault.exitPool( - balancerPoolId, - address(this), - payable(address(this)), - exitRequest - ); - bptTokenBalance = IERC20(poolAddress).balanceOf(address(this)); - } - - function getBPTExpected(address[] memory _assets, uint256[] memory _amounts) - internal - view - virtual - returns (uint256 bptExpected) - { - for (uint256 i = 0; i < _assets.length; ++i) { - uint256 strategyAssetMarketPrice = IOracle(priceProvider).price( - _assets[i] - ); - // convert asset amount to ETH amount - bptExpected = - bptExpected + - _amounts[i].mulTruncate(strategyAssetMarketPrice); - } - - uint256 bptRate = IRateProvider(poolAddress).getRate(); - // Convert ETH amount to BPT amount - bptExpected = bptExpected.divPrecisely(bptRate); - } - - function approveAllTokens() public { - // Approve all tokens - weth.approve(address(oethVault), type(uint256).max); - reth.approve(poolAddress, type(uint256).max); - weth.approve(poolAddress, type(uint256).max); - reth.approve(address(balancerVault), type(uint256).max); - weth.approve(address(balancerVault), type(uint256).max); - } - - receive() external payable { - // 3. Try to mint OETH - oethVault.mint(address(weth), 1 ether, 0.9 ether); - } -} diff --git a/contracts/contracts/mocks/MockFrxETHMinter.sol b/contracts/contracts/mocks/MockFrxETHMinter.sol deleted file mode 100644 index 0cb1ac91a6..0000000000 --- a/contracts/contracts/mocks/MockFrxETHMinter.sol +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -import "./MintableERC20.sol"; - -contract MockFrxETHMinter { - address public immutable frxETH; - address public immutable sfrxETH; - - constructor(address _frxETH, address _sfrxETH) { - frxETH = _frxETH; - sfrxETH = _sfrxETH; - } - - function submitAndDeposit(address recipient) - external - payable - returns (uint256 shares) - { - IMintableERC20(frxETH).mintTo(sfrxETH, msg.value); - IMintableERC20(sfrxETH).mintTo(recipient, msg.value); - shares = msg.value; - } -} diff --git a/contracts/contracts/mocks/MockMaverickDistributor.sol b/contracts/contracts/mocks/MockMaverickDistributor.sol deleted file mode 100644 index e2d5f2df37..0000000000 --- a/contracts/contracts/mocks/MockMaverickDistributor.sol +++ /dev/null @@ -1,35 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { IMaverickV2Pool } from "../interfaces/plume/IMaverickV2Pool.sol"; - -contract MockMaverickDistributor { - IERC20 public immutable rewardToken; - - uint256 public lastEpoch; - uint256 public rewardAmount; - - constructor(address _rewardToken) { - rewardToken = IERC20(_rewardToken); - } - - function setLastEpoch(uint256 _epoch) external { - lastEpoch = _epoch; - } - - function setRewardTokenAmount(uint256 _amount) external { - rewardAmount = _amount; - } - - function claimLp( - address recipient, - uint256, - IMaverickV2Pool, - uint32[] memory, - uint256 - ) external returns (uint256 amount) { - rewardToken.transfer(recipient, rewardAmount); - return rewardAmount; - } -} diff --git a/contracts/contracts/mocks/MockMetadataToken.sol b/contracts/contracts/mocks/MockMetadataToken.sol deleted file mode 100644 index 0bc8654d7b..0000000000 --- a/contracts/contracts/mocks/MockMetadataToken.sol +++ /dev/null @@ -1,6 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -// IERC20Metadata is used in the resolveAsset function in contracts/utils/assets.js -// We just need to import it here to make its ABI available to Hardhat -import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; diff --git a/contracts/contracts/mocks/MockMintableUniswapPair.sol b/contracts/contracts/mocks/MockMintableUniswapPair.sol deleted file mode 100644 index 26d206e2e9..0000000000 --- a/contracts/contracts/mocks/MockMintableUniswapPair.sol +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -import "./MintableERC20.sol"; -import "./MockUniswapPair.sol"; - -contract MockMintableUniswapPair is MockUniswapPair, MintableERC20 { - constructor( - address _token0, - address _token1, - uint112 _reserve0, - uint112 _reserve1 - ) - MockUniswapPair(_token0, _token1, _reserve0, _reserve1) - ERC20("Uniswap V2", "UNI-v2") - {} - - function decimals() public pure override returns (uint8) { - return 18; - } -} diff --git a/contracts/contracts/mocks/MockOETHVault.sol b/contracts/contracts/mocks/MockOETHVault.sol deleted file mode 100644 index 7b72c90189..0000000000 --- a/contracts/contracts/mocks/MockOETHVault.sol +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -import { OETHVault } from "../vault/OETHVault.sol"; -import { StableMath } from "../utils/StableMath.sol"; -import "../utils/Helpers.sol"; - -contract MockOETHVault is OETHVault { - using StableMath for uint256; - - constructor(address _weth) OETHVault(_weth) { - _setGovernor(msg.sender); - } - - function supportAsset(address asset) external { - require(asset == asset, "Only asset supported"); - } -} diff --git a/contracts/contracts/mocks/MockOETHVaultAdmin.sol b/contracts/contracts/mocks/MockOETHVaultAdmin.sol deleted file mode 100644 index 43c9689bd8..0000000000 --- a/contracts/contracts/mocks/MockOETHVaultAdmin.sol +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -import { OETHVault } from "../vault/OETHVault.sol"; - -contract MockOETHVault is OETHVault { - constructor(address _weth) OETHVault(_weth) {} - - // fetches the WETH amount in outstanding withdrawals - function outstandingWithdrawalsAmount() - external - view - returns (uint256 wethAmount) - { - WithdrawalQueueMetadata memory queue = withdrawalQueueMetadata; - - // The amount of WETH that is still to be claimed in the withdrawal queue - wethAmount = queue.queued - queue.claimed; - } - - function wethAvailable() external view returns (uint256) { - return _assetAvailable(); - } -} diff --git a/contracts/contracts/mocks/MockOGV.sol b/contracts/contracts/mocks/MockOGV.sol deleted file mode 100644 index f1231434fa..0000000000 --- a/contracts/contracts/mocks/MockOGV.sol +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -import "./MintableERC20.sol"; - -contract MockOGV is MintableERC20 { - constructor() ERC20("OGV", "OGV") {} -} diff --git a/contracts/contracts/mocks/MockOracle.sol b/contracts/contracts/mocks/MockOracle.sol deleted file mode 100644 index 2f401f302b..0000000000 --- a/contracts/contracts/mocks/MockOracle.sol +++ /dev/null @@ -1,88 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -import "../interfaces/IPriceOracle.sol"; -import "../interfaces/IMinMaxOracle.sol"; - -/** - * Mock of both price Oracle and min max oracles - */ -contract MockOracle is IPriceOracle, IMinMaxOracle { - mapping(bytes32 => uint256) prices; - mapping(bytes32 => uint256[]) pricesMinMax; - uint256 ethMin; - uint256 ethMax; - - /** - * @dev returns the asset price in USD, 6 decimal digits. - * Compatible with the Open Price Feed. - */ - function price(string calldata symbol) - external - view - override - returns (uint256) - { - return prices[keccak256(abi.encodePacked(symbol))]; - } - - /** - * @dev sets the price of the asset in USD, 6 decimal digits - * - */ - function setPrice(string calldata symbol, uint256 _price) external { - prices[keccak256(abi.encodePacked(symbol))] = _price; - } - - /** - * @dev sets the min and max price of ETH in USD, 6 decimal digits - * - */ - function setEthPriceMinMax(uint256 _min, uint256 _max) external { - ethMin = _min; - ethMax = _max; - } - - /** - * @dev sets the prices Min Max for a specific symbol in ETH, 8 decimal digits - * - */ - function setTokPriceMinMax( - string calldata symbol, - uint256 _min, - uint256 _max - ) external { - pricesMinMax[keccak256(abi.encodePacked(symbol))] = [_min, _max]; - } - - /** - * @dev get the price of asset in ETH, 8 decimal digits. - */ - function priceMin(string calldata symbol) - external - view - override - returns (uint256) - { - uint256[] storage pMinMax = pricesMinMax[ - keccak256(abi.encodePacked(symbol)) - ]; - return (pMinMax[0] * ethMin) / 1e6; - } - - /** - * @dev get the price of asset in USD, 8 decimal digits. - * Not needed for now - */ - function priceMax(string calldata symbol) - external - view - override - returns (uint256) - { - uint256[] storage pMinMax = pricesMinMax[ - keccak256(abi.encodePacked(symbol)) - ]; - return (pMinMax[1] * ethMax) / 1e6; - } -} diff --git a/contracts/contracts/mocks/MockOracleRouterNoStale.sol b/contracts/contracts/mocks/MockOracleRouterNoStale.sol deleted file mode 100644 index 448c688aec..0000000000 --- a/contracts/contracts/mocks/MockOracleRouterNoStale.sol +++ /dev/null @@ -1,40 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -import "../interfaces/chainlink/AggregatorV3Interface.sol"; -import { IOracle } from "../interfaces/IOracle.sol"; -import { Helpers } from "../utils/Helpers.sol"; -import { StableMath } from "../utils/StableMath.sol"; -import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; -import { OracleRouter } from "../oracle/OracleRouter.sol"; -import { OETHOracleRouter } from "../oracle/OETHOracleRouter.sol"; - -// @notice Oracle Router used to bypass staleness -contract MockOracleRouterNoStale is OracleRouter { - function feedMetadata(address asset) - internal - pure - virtual - override - returns (address feedAddress, uint256 maxStaleness) - { - (feedAddress, ) = super.feedMetadata(asset); - maxStaleness = 365 days; - } -} - -// @notice Oracle Router used to bypass staleness -contract MockOETHOracleRouterNoStale is OETHOracleRouter { - constructor() OETHOracleRouter() {} - - function feedMetadata(address asset) - internal - view - virtual - override - returns (address feedAddress, uint256 maxStaleness) - { - (feedAddress, ) = super.feedMetadata(asset); - maxStaleness = 365 days; - } -} diff --git a/contracts/contracts/mocks/MockOracleWeightedPool.sol b/contracts/contracts/mocks/MockOracleWeightedPool.sol deleted file mode 100644 index 0632b19c7d..0000000000 --- a/contracts/contracts/mocks/MockOracleWeightedPool.sol +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -import { Variable, OracleAverageQuery, IOracleWeightedPool } from "../interfaces/balancer/IOracleWeightedPool.sol"; - -contract MockOracleWeightedPool is IOracleWeightedPool { - uint256[] public nextResults; - - constructor() { - nextResults = [1 ether, 1 ether]; - } - - function getTimeWeightedAverage(OracleAverageQuery[] memory) - external - view - override - returns (uint256[] memory results) - { - return nextResults; - } - - function setNextResults(uint256[] calldata results) external { - nextResults = results; - } -} diff --git a/contracts/contracts/mocks/MockRETH.sol b/contracts/contracts/mocks/MockRETH.sol deleted file mode 100644 index 818dd73b9d..0000000000 --- a/contracts/contracts/mocks/MockRETH.sol +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -import "./MintableERC20.sol"; -import "../interfaces/IGetExchangeRateToken.sol"; - -contract MockRETH is MintableERC20, IGetExchangeRateToken { - uint256 private exchangeRate = 12e17; - - constructor() ERC20("Rocket Pool ETH", "rETH") {} - - function getExchangeRate() external view override returns (uint256) { - return exchangeRate; - } - - function setExchangeRate(uint256 _rate) external { - exchangeRate = _rate; - } -} diff --git a/contracts/contracts/mocks/MockRoosterAMOStrategy.sol b/contracts/contracts/mocks/MockRoosterAMOStrategy.sol deleted file mode 100644 index 46627a7342..0000000000 --- a/contracts/contracts/mocks/MockRoosterAMOStrategy.sol +++ /dev/null @@ -1,46 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -/** - * @title Rooster AMO strategy exposing extra functionality - * @author Origin Protocol Inc - */ - -import { RoosterAMOStrategy } from "../strategies/plume/RoosterAMOStrategy.sol"; -import { IMaverickV2Pool } from "../interfaces/plume/IMaverickV2Pool.sol"; - -contract MockRoosterAMOStrategy is RoosterAMOStrategy { - constructor( - BaseStrategyConfig memory _stratConfig, - address _wethAddress, - address _oethpAddress, - address _liquidityManager, - address _poolLens, - address _maverickPosition, - address _maverickQuoter, - address _mPool, - bool _upperTickAtParity, - address _votingDistributor, - address _poolDistributor - ) - RoosterAMOStrategy( - _stratConfig, - _wethAddress, - _oethpAddress, - _liquidityManager, - _poolLens, - _maverickPosition, - _maverickQuoter, - _mPool, - _upperTickAtParity, - _votingDistributor, - _poolDistributor - ) - {} - - function getCurrentWethShare() external view returns (uint256) { - uint256 _currentPrice = getPoolSqrtPrice(); - - return _getWethShare(_currentPrice); - } -} diff --git a/contracts/contracts/mocks/MockStkAave.sol b/contracts/contracts/mocks/MockStkAave.sol deleted file mode 100644 index 4ab309e700..0000000000 --- a/contracts/contracts/mocks/MockStkAave.sol +++ /dev/null @@ -1,69 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import "./MintableERC20.sol"; - -contract MockStkAave is MintableERC20 { - uint256 public COOLDOWN_SECONDS = 864000; - uint256 public UNSTAKE_WINDOW = 172800; - address public STAKED_TOKEN; - - mapping(address => uint256) public stakerRewardsToClaim; - mapping(address => uint256) public stakersCooldowns; - - using SafeERC20 for IERC20; - - constructor(address _stakedToken) ERC20("Staked Aave", "stkAAVE") { - STAKED_TOKEN = _stakedToken; - } - - function decimals() public pure override returns (uint8) { - return 18; - } - - function setStakedToken(address _stakedToken) external { - STAKED_TOKEN = _stakedToken; - } - - /** - * @dev Redeems staked tokens, and stop earning rewards - * @param to Address to redeem to - * @param amount Amount to redeem - **/ - function redeem(address to, uint256 amount) external { - uint256 cooldownStartTimestamp = stakersCooldowns[msg.sender]; - uint256 windowStart = cooldownStartTimestamp + COOLDOWN_SECONDS; - require(amount != 0, "INVALID_ZERO_AMOUNT"); - require(block.timestamp > windowStart, "INSUFFICIENT_COOLDOWN"); - require( - block.timestamp - windowStart <= UNSTAKE_WINDOW, - "UNSTAKE_WINDOW_FINISHED" - ); - uint256 balanceOfMessageSender = balanceOf(msg.sender); - uint256 amountToRedeem = (amount > balanceOfMessageSender) - ? balanceOfMessageSender - : amount; - - stakersCooldowns[msg.sender] = 0; - _burn(msg.sender, amountToRedeem); - IERC20(STAKED_TOKEN).safeTransfer(to, amountToRedeem); - } - - /** - * @dev Activates the cooldown period to unstake - * - It can't be called if the user is not staking - **/ - function cooldown() external { - require(balanceOf(msg.sender) != 0, "INVALID_BALANCE_ON_COOLDOWN"); - stakersCooldowns[msg.sender] = block.timestamp; - } - - /** - * @dev Test helper function to allow changing the cooldown - **/ - function setCooldown(address account, uint256 _cooldown) external { - stakersCooldowns[account] = _cooldown; - } -} diff --git a/contracts/contracts/mocks/MockSwapper.sol b/contracts/contracts/mocks/MockSwapper.sol deleted file mode 100644 index a9866795ec..0000000000 --- a/contracts/contracts/mocks/MockSwapper.sol +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -import { IMintableERC20 } from "./MintableERC20.sol"; -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; - -contract MockSwapper { - uint256 public nextOutAmount; - - function swap( - // solhint-disable-next-line no-unused-vars - address _fromAsset, - address _toAsset, - // solhint-disable-next-line no-unused-vars - uint256 _fromAssetAmount, - uint256 _minToAssetAmount, - // solhint-disable-next-line no-unused-vars - bytes calldata _data - ) external returns (uint256 toAssetAmount) { - toAssetAmount = (nextOutAmount > 0) ? nextOutAmount : _minToAssetAmount; - nextOutAmount = 0; - IMintableERC20(_toAsset).mint(toAssetAmount); - IERC20(_toAsset).transfer(msg.sender, toAssetAmount); - } - - function setNextOutAmount(uint256 _nextOutAmount) public { - nextOutAmount = _nextOutAmount; - } -} diff --git a/contracts/contracts/mocks/MockTUSD.sol b/contracts/contracts/mocks/MockTUSD.sol deleted file mode 100644 index 7fea8b5b05..0000000000 --- a/contracts/contracts/mocks/MockTUSD.sol +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -import "./MintableERC20.sol"; - -contract MockTUSD is MintableERC20 { - constructor() ERC20("TrueUSD", "TUSD") {} - - function decimals() public pure override returns (uint8) { - return 18; - } -} diff --git a/contracts/contracts/mocks/MockUniswapPair.sol b/contracts/contracts/mocks/MockUniswapPair.sol deleted file mode 100644 index fab1cbf3ef..0000000000 --- a/contracts/contracts/mocks/MockUniswapPair.sol +++ /dev/null @@ -1,98 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -import { IUniswapV2Pair } from "../interfaces/uniswap/IUniswapV2Pair.sol"; - -contract MockUniswapPair is IUniswapV2Pair { - address tok0; - address tok1; - uint112 reserve0; - uint112 reserve1; - uint256 blockTimestampLast; - - bool public hasSynced = false; - - constructor( - address _token0, - address _token1, - uint112 _reserve0, - uint112 _reserve1 - ) { - tok0 = _token0; - tok1 = _token1; - reserve0 = _reserve0; - reserve1 = _reserve1; - blockTimestampLast = block.timestamp; - } - - function token0() external view override returns (address) { - return tok0; - } - - function token1() external view override returns (address) { - return tok1; - } - - function getReserves() - external - view - override - returns ( - uint112, - uint112, - uint32 - ) - { - return (reserve0, reserve1, uint32(blockTimestampLast)); - } - - function setReserves(uint112 _reserve0, uint112 _reserve1) public { - reserve0 = _reserve0; - reserve1 = _reserve1; - blockTimestampLast = block.timestamp; - } - - // CAUTION This will not work if you setReserves multiple times over - // multiple different blocks because then it wouldn't be a continuous - // reserve factor over that blockTimestamp, this assumes an even reserve - // ratio all the way through - function price0CumulativeLast() external view override returns (uint256) { - return - uint256(FixedPoint.fraction(reserve1, reserve0)._x) * - blockTimestampLast; - } - - function price1CumulativeLast() external view override returns (uint256) { - return - uint256(FixedPoint.fraction(reserve0, reserve1)._x) * - blockTimestampLast; - } - - function sync() external override { - hasSynced = true; - } - - function checkHasSynced() external view { - require(hasSynced, "Not synced"); - } -} - -// a library for handling binary fixed point numbers (https://en.wikipedia.org/wiki/Q_(number_format)) -library FixedPoint { - // range: [0, 2**112 - 1] - // resolution: 1 / 2**112 - struct uq112x112 { - uint224 _x; - } - - // returns a uq112x112 which represents the ratio of the numerator to the denominator - // equivalent to encode(numerator).div(denominator) - function fraction(uint112 numerator, uint112 denominator) - internal - pure - returns (uq112x112 memory) - { - require(denominator > 0, "FixedPoint: DIV_BY_ZERO"); - return uq112x112((uint224(numerator) << 112) / denominator); - } -} diff --git a/contracts/contracts/mocks/MockfrxETH.sol b/contracts/contracts/mocks/MockfrxETH.sol deleted file mode 100644 index 8e665af0e6..0000000000 --- a/contracts/contracts/mocks/MockfrxETH.sol +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -import "./MintableERC20.sol"; - -contract MockfrxETH is MintableERC20 { - constructor() ERC20("frxETH", "frxETH") {} -} diff --git a/contracts/contracts/mocks/MocksfrxETH.sol b/contracts/contracts/mocks/MocksfrxETH.sol deleted file mode 100644 index a88710df6d..0000000000 --- a/contracts/contracts/mocks/MocksfrxETH.sol +++ /dev/null @@ -1,75 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -import "./MintableERC20.sol"; - -contract MocksfrxETH is MintableERC20 { - address public frxETH; - - constructor(address _frxETH) ERC20("sfrxETH", "sfrxETH") { - frxETH = _frxETH; - } - - function setMockfrxETHAddress(address _frxETH) external { - frxETH = _frxETH; - } - - function deposit(uint256 assets, address receiver) - external - returns (uint256 shares) - { - ERC20(frxETH).transferFrom(msg.sender, address(this), assets); - - _mint(receiver, assets); - - return assets; - } - - function maxWithdraw(address owner) external view returns (uint256) { - return balanceOf(owner); - } - - function setMaxWithdrawableBalance(address owner, uint256 balance) - external - { - uint256 currentBalance = balanceOf(owner); - if (currentBalance > balance) { - _burn(owner, currentBalance - balance); - } else if (balance > currentBalance) { - _mint(owner, balance - currentBalance); - } - } - - function redeem( - uint256 shares, - address receiver, - address owner - ) external returns (uint256 assets) { - _burn(owner, shares); - - ERC20(frxETH).transfer(receiver, shares); - - assets = shares; - } - - function withdraw( - uint256 assets, - address receiver, - address owner - ) external returns (uint256 shares) { - _burn(owner, assets); - - ERC20(frxETH).transfer(receiver, assets); - - shares = assets; - } - - function submitAndDeposit(address recipient) - external - payable - returns (uint256 shares) - { - _mint(recipient, msg.value); - shares = msg.value; - } -} diff --git a/contracts/contracts/mocks/MockstETH.sol b/contracts/contracts/mocks/MockstETH.sol deleted file mode 100644 index ae16691242..0000000000 --- a/contracts/contracts/mocks/MockstETH.sol +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -import "./MintableERC20.sol"; - -contract MockstETH is MintableERC20 { - constructor() ERC20("stETH", "stETH") {} -} diff --git a/contracts/contracts/mocks/curve/Mock3CRV.sol b/contracts/contracts/mocks/curve/Mock3CRV.sol deleted file mode 100644 index f93adad386..0000000000 --- a/contracts/contracts/mocks/curve/Mock3CRV.sol +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -import { ERC20, MintableERC20 } from "../MintableERC20.sol"; -import { BurnableERC20 } from "../BurnableERC20.sol"; - -contract Mock3CRV is MintableERC20, BurnableERC20 { - constructor() ERC20("Curve.fi DAI/USDC/USDT", "3Crv") {} - - function decimals() public pure override returns (uint8) { - return 18; - } -} diff --git a/contracts/contracts/mocks/curve/MockBooster.sol b/contracts/contracts/mocks/curve/MockBooster.sol deleted file mode 100644 index 14a0c72b63..0000000000 --- a/contracts/contracts/mocks/curve/MockBooster.sol +++ /dev/null @@ -1,152 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import { MockRewardPool } from "./MockRewardPool.sol"; - -import { IRewardStaking } from "../../strategies/IRewardStaking.sol"; -import { IMintableERC20, MintableERC20, ERC20 } from "../MintableERC20.sol"; -import { IBurnableERC20, BurnableERC20 } from "../BurnableERC20.sol"; - -contract MockDepositToken is MintableERC20, BurnableERC20 { - constructor() ERC20("DCVX", "CVX Deposit Token") {} -} - -contract MockBooster { - using SafeERC20 for IERC20; - - struct PoolInfo { - address lptoken; - address token; - address crvRewards; - } - - address public minter; // this is CVx for the booster on live - address public crv; // Curve rewards token - address public cvx; // Convex rewards token - mapping(uint256 => PoolInfo) public poolInfo; - - constructor( - address _rewardsMinter, - address _crv, - address _cvx - ) public { - minter = _rewardsMinter; - crv = _crv; - cvx = _cvx; - } - - function setPool(uint256 pid, address _lpToken) - external - returns (address rewards) - { - address token = address(new MockDepositToken()); - // Deploy a new Convex Rewards Pool - rewards = address( - new MockRewardPool(pid, token, crv, cvx, address(this)) - ); - - poolInfo[pid] = PoolInfo({ - lptoken: _lpToken, - token: token, - crvRewards: rewards - }); - } - - function deposit( - uint256 _pid, - uint256 _amount, - bool _stake - ) public returns (bool) { - PoolInfo storage pool = poolInfo[_pid]; - - address lptoken = pool.lptoken; - - // hold on to the Curve LP tokens - IERC20(lptoken).safeTransferFrom(msg.sender, address(this), _amount); - - address token = pool.token; - if (_stake) { - // mint Convex pool LP tokens and stake in rewards contract on user behalf - IMintableERC20(token).mint(_amount); - address rewardContract = pool.crvRewards; - IERC20(token).safeApprove(rewardContract, 0); - IERC20(token).safeApprove(rewardContract, _amount); - IRewardStaking(rewardContract).stakeFor(msg.sender, _amount); - } else { - // mint Convex pool LP tokens and send to user - IMintableERC20(token).mint(_amount); - IERC20(token).transfer(msg.sender, _amount); - } - return true; - } - - // Deposit all Curve LP tokens and stake - function depositAll(uint256 _pid, bool _stake) external returns (bool) { - address lptoken = poolInfo[_pid].lptoken; - uint256 balance = IERC20(lptoken).balanceOf(msg.sender); - deposit(_pid, balance, _stake); - return true; - } - - // withdraw Curve LP tokens - function _withdraw( - uint256 _pid, - uint256 _amount, - address _from, - address _to - ) internal { - PoolInfo storage pool = poolInfo[_pid]; - - // burn the Convex pool LP tokens - IBurnableERC20(pool.token).burnFrom(_from, _amount); - - // return the Curve LP tokens - IERC20(pool.lptoken).safeTransfer(_to, _amount); - } - - // withdraw Curve LP tokens - function withdraw(uint256 _pid, uint256 _amount) public returns (bool) { - _withdraw(_pid, _amount, msg.sender, msg.sender); - return true; - } - - // withdraw all Curve LP tokens - function withdrawAll(uint256 _pid) public returns (bool) { - address token = poolInfo[_pid].token; - uint256 userBal = IERC20(token).balanceOf(msg.sender); - withdraw(_pid, userBal); - return true; - } - - // allow reward contracts to send here and withdraw to user - function withdrawTo( - uint256 _pid, - uint256 _amount, - address _to - ) external returns (bool) { - address rewardContract = poolInfo[_pid].crvRewards; - require(msg.sender == rewardContract, "!auth"); - - _withdraw(_pid, _amount, msg.sender, _to); - return true; - } - - // callback from reward contract when crv is received. - function rewardClaimed( - uint256 _pid, - // solhint-disable-next-line no-unused-vars - address _address, - uint256 _amount - ) external returns (bool) { - address rewardContract = poolInfo[_pid].crvRewards; - require(msg.sender == rewardContract, "!auth"); - - //mint reward tokens - // and transfer it - IMintableERC20(minter).mint(_amount); - IERC20(minter).transfer(msg.sender, _amount); - return true; - } -} diff --git a/contracts/contracts/mocks/curve/MockCRV.sol b/contracts/contracts/mocks/curve/MockCRV.sol deleted file mode 100644 index 71c88a86dd..0000000000 --- a/contracts/contracts/mocks/curve/MockCRV.sol +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -import "../MintableERC20.sol"; - -contract MockCRV is MintableERC20 { - constructor() ERC20("Curve DAO Token", "CRV") {} - - function decimals() public pure override returns (uint8) { - return 18; - } -} diff --git a/contracts/contracts/mocks/curve/MockCRVMinter.sol b/contracts/contracts/mocks/curve/MockCRVMinter.sol deleted file mode 100644 index 70fc5d973e..0000000000 --- a/contracts/contracts/mocks/curve/MockCRVMinter.sol +++ /dev/null @@ -1,20 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; - -import { IMintableERC20 } from "../MintableERC20.sol"; - -contract MockCRVMinter { - address crv; - - constructor(address _crv) { - crv = _crv; - } - - function mint(address _address) external { - uint256 amount = 2e18; - IMintableERC20(crv).mint(amount); - IERC20(crv).transfer(_address, amount); - } -} diff --git a/contracts/contracts/mocks/curve/MockCVX.sol b/contracts/contracts/mocks/curve/MockCVX.sol deleted file mode 100644 index 7f9cde2009..0000000000 --- a/contracts/contracts/mocks/curve/MockCVX.sol +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -import "../MintableERC20.sol"; - -contract MockCVX is MintableERC20 { - constructor() ERC20("CVX", "CVX DAO Token") {} -} diff --git a/contracts/contracts/mocks/curve/MockCurveAbstractMetapool.sol b/contracts/contracts/mocks/curve/MockCurveAbstractMetapool.sol deleted file mode 100644 index 7fb0f78560..0000000000 --- a/contracts/contracts/mocks/curve/MockCurveAbstractMetapool.sol +++ /dev/null @@ -1,151 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; - -import { MintableERC20, IMintableERC20 } from "../MintableERC20.sol"; -import { StableMath } from "../../utils/StableMath.sol"; -import "../../utils/Helpers.sol"; - -abstract contract MockCurveAbstractMetapool is MintableERC20 { - using StableMath for uint256; - - address[] public coins; - uint256[2] public balances; - - // Returns the same amount of LP tokens in 1e18 decimals - function add_liquidity(uint256[2] calldata _amounts, uint256 _minAmount) - external - returns (uint256 lpAmount) - { - for (uint256 i = 0; i < _amounts.length; i++) { - if (_amounts[i] > 0) { - IERC20(coins[i]).transferFrom( - msg.sender, - address(this), - _amounts[i] - ); - uint256 assetDecimals = Helpers.getDecimals(coins[i]); - // Convert to 1e18 and add to sum - lpAmount += _amounts[i].scaleBy(18, assetDecimals); - balances[i] = balances[i] + _amounts[i]; - } - } - // Hacky way of simulating slippage to check _minAmount - if (lpAmount == 29000e18) lpAmount = 14500e18; - require(lpAmount >= _minAmount, "Slippage ruined your day"); - // Send LP token to sender, e.g. 3CRV - _mint(msg.sender, lpAmount); - } - - // Dumb implementation that returns the same amount - function calc_withdraw_one_coin(uint256 _amount, int128 _index) - public - view - returns (uint256 lpAmount) - { - uint256 assetDecimals = Helpers.getDecimals(coins[uint128(_index)]); - lpAmount = _amount.scaleBy(assetDecimals, 18); - } - - function remove_liquidity_one_coin( - uint256 _lpAmount, - int128 _index, - // solhint-disable-next-line no-unused-vars - uint256 _minAmount - ) external returns (uint256 amount) { - _burn(msg.sender, _lpAmount); - uint256[] memory amounts = new uint256[](coins.length); - amounts[uint128(_index)] = _lpAmount; - amount = calc_withdraw_one_coin(_lpAmount, _index); - balances[uint128(_index)] -= amount; - IERC20(coins[uint128(_index)]).transfer(msg.sender, amount); - } - - function get_virtual_price() external pure returns (uint256) { - return 1e18; - } - - // solhint-disable-next-line no-unused-vars - function remove_liquidity(uint256 _amount, uint256[2] memory _min_amounts) - public - returns (uint256[2] memory amounts) - { - _burn(msg.sender, _amount); - uint256 totalSupply = totalSupply(); - for (uint256 i = 0; i < 2; i++) { - amounts[i] = totalSupply > 0 - ? (_amount * IERC20(coins[i]).balanceOf(address(this))) / - totalSupply - : IERC20(coins[i]).balanceOf(address(this)); - balances[i] -= amounts[i]; - IERC20(coins[i]).transfer(msg.sender, amounts[i]); - } - } - - function remove_liquidity_imbalance( - uint256[2] memory _amounts, - uint256 _max_burned_tokens - ) public returns (uint256) { - return - _remove_liquidity_imbalance( - _amounts, - _max_burned_tokens, - msg.sender - ); - } - - function remove_liquidity_imbalance( - uint256[2] memory _amounts, - uint256 _max_burned_tokens, - address _reveiver - ) public returns (uint256) { - return - _remove_liquidity_imbalance( - _amounts, - _max_burned_tokens, - _reveiver - ); - } - - function _remove_liquidity_imbalance( - uint256[2] memory _amounts, - uint256 _max_burned_tokens, - address _reveiver - ) internal returns (uint256 lpTokens) { - lpTokens = _max_burned_tokens; - _burn(msg.sender, lpTokens); - for (uint256 i = 0; i < _amounts.length; i++) { - balances[i] -= _amounts[i]; - if (_amounts[i] > 0) { - IERC20(coins[i]).transfer(_reveiver, _amounts[i]); - } - } - } - - // Dumb implementation that sums the scaled amounts - function calc_token_amount(uint256[2] memory _amounts, bool) - public - view - returns (uint256 lpTokens) - { - for (uint256 i = 0; i < _amounts.length; i++) { - uint256 assetDecimals = Helpers.getDecimals(coins[i]); - // Convert to 1e18 and add to lpTokens - lpTokens += _amounts[i].scaleBy(18, assetDecimals); - } - } - - /// @notice 0.02% fee - function fee() external pure returns (uint256) { - return 2000000; - } - - function decimals() public pure override returns (uint8) { - return 18; - } - - function burnFrom(address from, uint256 value) public { - _burn(from, value); - } -} diff --git a/contracts/contracts/mocks/curve/MockCurveGauge.sol b/contracts/contracts/mocks/curve/MockCurveGauge.sol deleted file mode 100644 index e55298eeb7..0000000000 --- a/contracts/contracts/mocks/curve/MockCurveGauge.sol +++ /dev/null @@ -1,30 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; - -import { ICurveGauge } from "../../strategies/ICurveGauge.sol"; - -contract MockCurveGauge is ICurveGauge { - mapping(address => uint256) private _balances; - address lpToken; - - constructor(address _lpToken) { - lpToken = _lpToken; - } - - function balanceOf(address account) public view override returns (uint256) { - return _balances[account]; - } - - function deposit(uint256 _value, address _account) external override { - IERC20(lpToken).transferFrom(msg.sender, address(this), _value); - _balances[_account] += _value; - } - - function withdraw(uint256 _value) external override { - IERC20(lpToken).transfer(msg.sender, _value); - // solhint-disable-next-line reentrancy - _balances[msg.sender] -= _value; - } -} diff --git a/contracts/contracts/mocks/curve/MockCurveMetapool.sol b/contracts/contracts/mocks/curve/MockCurveMetapool.sol deleted file mode 100644 index c9bbee33b0..0000000000 --- a/contracts/contracts/mocks/curve/MockCurveMetapool.sol +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -import { MockCurveAbstractMetapool } from "./MockCurveAbstractMetapool.sol"; -import "../MintableERC20.sol"; - -contract MockCurveMetapool is MockCurveAbstractMetapool { - constructor(address[2] memory _coins) - ERC20("Curve.fi 3pool/OUSD metapool", "3crv_OUSD") - { - coins = _coins; - } -} diff --git a/contracts/contracts/mocks/curve/MockCurvePool.sol b/contracts/contracts/mocks/curve/MockCurvePool.sol deleted file mode 100644 index 067be8939f..0000000000 --- a/contracts/contracts/mocks/curve/MockCurvePool.sol +++ /dev/null @@ -1,148 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { IBurnableERC20 } from "../BurnableERC20.sol"; - -import { IMintableERC20 } from "../MintableERC20.sol"; -import { ICurvePool } from "../../strategies/ICurvePool.sol"; -import { StableMath } from "../../utils/StableMath.sol"; -import "../../utils/Helpers.sol"; - -contract MockCurvePool { - using StableMath for uint256; - - address[] public coins; - uint256[3] public balances; - address lpToken; - uint256 public slippage = 1 ether; - - constructor(address[3] memory _coins, address _lpToken) { - coins = _coins; - lpToken = _lpToken; - } - - function setCoins(address[] memory _coins) external { - coins = _coins; - } - - // Returns the same amount of LP tokens in 1e18 decimals - function add_liquidity(uint256[3] calldata _amounts, uint256 _minAmount) - external - { - uint256 sum = 0; - for (uint256 i = 0; i < _amounts.length; i++) { - if (_amounts[i] > 0) { - IERC20(coins[i]).transferFrom( - msg.sender, - address(this), - _amounts[i] - ); - uint256 assetDecimals = Helpers.getDecimals(coins[i]); - // Convert to 1e18 and add to sum - sum += _amounts[i].scaleBy(18, assetDecimals); - balances[i] = balances[i] + _amounts[i]; - } - } - // Hacky way of simulating slippage to check _minAmount - if (sum == 29000e18) sum = 14500e18; - require(sum >= _minAmount, "Slippage ruined your day"); - // Send LP token to sender, e.g. 3CRV - IMintableERC20(lpToken).mint(sum); - IERC20(lpToken).transfer(msg.sender, sum); - } - - // Dumb implementation that returns the same amount - function calc_withdraw_one_coin(uint256 _amount, int128 _index) - public - view - returns (uint256) - { - uint256 assetDecimals = Helpers.getDecimals(coins[uint128(_index)]); - return _amount.scaleBy(assetDecimals, 18); - } - - function remove_liquidity_one_coin( - uint256 _amount, - int128 _index, - // solhint-disable-next-line no-unused-vars - uint256 _minAmount - ) external { - // Burn the Curve LP tokens - IBurnableERC20(lpToken).burnFrom(msg.sender, _amount); - uint256[] memory amounts = new uint256[](coins.length); - amounts[uint128(_index)] = _amount; - uint256 coinAmount = calc_withdraw_one_coin(_amount, _index); - balances[uint128(_index)] -= coinAmount; - IERC20(coins[uint128(_index)]).transfer(msg.sender, coinAmount); - } - - function get_virtual_price() external pure returns (uint256) { - return 1e18; - } - - // solhint-disable-next-line no-unused-vars - function remove_liquidity(uint256 _lpAmount, uint256[3] memory _min_amounts) - public - { - // Burn the Curve LP tokens - IBurnableERC20(lpToken).burnFrom(msg.sender, _lpAmount); - uint256 totalSupply = IERC20(lpToken).totalSupply(); - for (uint256 i = 0; i < 3; i++) { - uint256 coinAmount = totalSupply > 0 - ? (_lpAmount * IERC20(coins[i]).balanceOf(address(this))) / - totalSupply - : IERC20(coins[i]).balanceOf(address(this)); - balances[i] -= coinAmount; - IERC20(coins[i]).transfer(msg.sender, coinAmount); - } - } - - function remove_liquidity_imbalance( - uint256[3] memory _amounts, - uint256 _max_burned_tokens - ) public { - // Burn the Curve LP tokens - IBurnableERC20(lpToken).burnFrom(msg.sender, _max_burned_tokens); - // For each coin, transfer to the caller - for (uint256 i = 0; i < _amounts.length; i++) { - balances[i] -= _amounts[i]; - if (_amounts[i] > 0) { - IERC20(coins[i]).transfer(msg.sender, _amounts[i]); - } - } - } - - // Dumb implementation that sums the scaled amounts - function calc_token_amount(uint256[3] memory _amounts, bool) - public - view - returns (uint256 lpTokens) - { - for (uint256 i = 0; i < _amounts.length; i++) { - uint256 assetDecimals = Helpers.getDecimals(coins[i]); - // Convert to 1e18 and add to lpTokens - lpTokens += _amounts[i].scaleBy(18, assetDecimals); - } - } - - function fee() external pure returns (uint256) { - return 1000000; - } - - function exchange( - uint256 coin0, - uint256 coin1, - uint256 amountIn, - uint256 minAmountOut - ) external returns (uint256 amountOut) { - IERC20(coins[coin0]).transferFrom(msg.sender, address(this), amountIn); - amountOut = (minAmountOut * slippage) / 1 ether; - require(amountOut >= minAmountOut, "Slippage error"); - IMintableERC20(coins[coin1]).mintTo(msg.sender, amountOut); - } - - function setSlippage(uint256 _slippage) external { - slippage = _slippage; - } -} diff --git a/contracts/contracts/mocks/curve/MockRewardPool.sol b/contracts/contracts/mocks/curve/MockRewardPool.sol deleted file mode 100644 index de7d46bf8c..0000000000 --- a/contracts/contracts/mocks/curve/MockRewardPool.sol +++ /dev/null @@ -1,127 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import { IMintableERC20 } from "../MintableERC20.sol"; -import "@openzeppelin/contracts/utils/math/SafeMath.sol"; - -interface IDeposit { - function poolInfo(uint256) - external - view - returns ( - address, - address, - address, - address, - address, - bool - ); - - function rewardClaimed( - uint256, - address, - uint256 - ) external; - - function withdrawTo( - uint256, - uint256, - address - ) external; -} - -contract MockRewardPool { - using SafeMath for uint256; - using SafeERC20 for IERC20; - - uint256 public pid; - address public stakingToken; - address public rewardTokenA; - address public rewardTokenB; - address public operator; - - uint256 private _totalSupply; - - mapping(address => uint256) private _balances; - mapping(address => uint256) public rewards; - - constructor( - uint256 _pid, - address _stakingToken, - address _rewardTokenA, - address _rewardTokenB, - address _operator - ) public { - pid = _pid; - stakingToken = _stakingToken; - rewardTokenA = _rewardTokenA; - rewardTokenB = _rewardTokenB; - operator = _operator; - } - - function totalSupply() public view returns (uint256) { - return _totalSupply; - } - - function balanceOf(address account) public view returns (uint256) { - return _balances[account]; - } - - function stakeFor(address _for, uint256 _amount) public returns (bool) { - require(_amount > 0, "RewardPool : Cannot stake 0"); - - //give to _for - _totalSupply = _totalSupply.add(_amount); - _balances[_for] = _balances[_for].add(_amount); - - //take away from sender - IERC20(stakingToken).safeTransferFrom( - msg.sender, - address(this), - _amount - ); - - return true; - } - - function withdrawAndUnwrap(uint256 amount, bool claim) - public - returns (bool) - { - _totalSupply = _totalSupply.sub(amount); - _balances[msg.sender] = _balances[msg.sender].sub(amount); - - //tell operator to withdraw from here directly to user - IDeposit(operator).withdrawTo(pid, amount, msg.sender); - - //get rewards too - if (claim) { - getReward(msg.sender, true); - } - return true; - } - - function withdrawAllAndUnwrap(bool claim) external { - withdrawAndUnwrap(_balances[msg.sender], claim); - } - - // solhint-disable-next-line no-unused-vars - function getReward(address _account, bool _claimExtras) - public - returns (bool) - { - IMintableERC20(rewardTokenA).mint(2 * 1e18); - IERC20(rewardTokenA).transfer(_account, 2 * 1e18); - - IMintableERC20(rewardTokenB).mint(3 * 1e18); - IERC20(rewardTokenB).transfer(_account, 3 * 1e18); - - return true; - } - - function getReward() public returns (bool) { - getReward(msg.sender, true); - } -} diff --git a/contracts/contracts/proxies/Proxies.sol b/contracts/contracts/proxies/Proxies.sol index 03227c25a8..2dc8e89832 100644 --- a/contracts/contracts/proxies/Proxies.sol +++ b/contracts/contracts/proxies/Proxies.sol @@ -25,27 +25,6 @@ contract VaultProxy is InitializeGovernedUpgradeabilityProxy { } -/** - * @notice CompoundStrategyProxy delegates calls to a CompoundStrategy implementation - */ -contract CompoundStrategyProxy is InitializeGovernedUpgradeabilityProxy { - -} - -/** - * @notice AaveStrategyProxy delegates calls to a AaveStrategy implementation - */ -contract AaveStrategyProxy is InitializeGovernedUpgradeabilityProxy { - -} - -/** - * @notice ConvexStrategyProxy delegates calls to a ConvexStrategy implementation - */ -contract ConvexStrategyProxy is InitializeGovernedUpgradeabilityProxy { - -} - /** * @notice HarvesterProxy delegates calls to a Harvester implementation */ @@ -60,27 +39,6 @@ contract DripperProxy is InitializeGovernedUpgradeabilityProxy { } -/** - * @notice MorphoCompoundStrategyProxy delegates calls to a MorphoCompoundStrategy implementation - */ -contract MorphoCompoundStrategyProxy is InitializeGovernedUpgradeabilityProxy { - -} - -/** - * @notice ConvexOUSDMetaStrategyProxy delegates calls to a ConvexOUSDMetaStrategy implementation - */ -contract ConvexOUSDMetaStrategyProxy is InitializeGovernedUpgradeabilityProxy { - -} - -/** - * @notice MorphoAaveStrategyProxy delegates calls to a MorphoCompoundStrategy implementation - */ -contract MorphoAaveStrategyProxy is InitializeGovernedUpgradeabilityProxy { - -} - /** * @notice OETHProxy delegates calls to nowhere for now */ @@ -116,20 +74,6 @@ contract OETHHarvesterProxy is InitializeGovernedUpgradeabilityProxy { } -/** - * @notice CurveEthStrategyProxy delegates calls to a CurveEthStrategy implementation - */ -contract ConvexEthMetaStrategyProxy is InitializeGovernedUpgradeabilityProxy { - -} - -/** - * @notice BuybackProxy delegates calls to Buyback implementation - */ -contract BuybackProxy is InitializeGovernedUpgradeabilityProxy { - -} - /** * @notice OETHMorphoAaveStrategyProxy delegates calls to a MorphoAaveStrategy implementation */ @@ -162,13 +106,6 @@ contract MakerDsrStrategyProxy is InitializeGovernedUpgradeabilityProxy { } -/** - * @notice OETHBuybackProxy delegates calls to Buyback implementation - */ -contract OETHBuybackProxy is InitializeGovernedUpgradeabilityProxy { - -} - /** * @notice BridgedWOETHProxy delegates calls to BridgedWOETH implementation */ @@ -237,13 +174,6 @@ contract MetaMorphoStrategyProxy is InitializeGovernedUpgradeabilityProxy { } -/** - * @notice ARMBuybackProxy delegates calls to Buyback implementation - */ -contract ARMBuybackProxy is InitializeGovernedUpgradeabilityProxy { - -} - /** * @notice MorphoGauntletPrimeUSDCStrategyProxy delegates calls to a Generalized4626Strategy implementation */ @@ -253,15 +183,6 @@ contract MorphoGauntletPrimeUSDCStrategyProxy is } -/** - * @notice MorphoGauntletPrimeUSDTStrategyProxy delegates calls to a Generalized4626USDTStrategy implementation - */ -contract MorphoGauntletPrimeUSDTStrategyProxy is - InitializeGovernedUpgradeabilityProxy -{ - -} - /** * @notice CurvePoolBoosterProxy delegates calls to a CurvePoolBooster implementation */ @@ -292,13 +213,6 @@ contract PoolBoostCentralRegistryProxy is } -/** - * @notice MakerSSRStrategyProxy delegates calls to a Generalized4626Strategy implementation - */ -contract MakerSSRStrategyProxy is InitializeGovernedUpgradeabilityProxy { - -} - /** * @notice OUSDCurveAMOProxy delegates calls to a CurveAMOStrategy implementation */ diff --git a/contracts/contracts/strategies/AaveStrategy.sol b/contracts/contracts/strategies/AaveStrategy.sol deleted file mode 100644 index d72a58e4e3..0000000000 --- a/contracts/contracts/strategies/AaveStrategy.sol +++ /dev/null @@ -1,305 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -/** - * @title OUSD Aave Strategy - * @notice Investment strategy for investing stablecoins via Aave - * @author Origin Protocol Inc - */ -import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; - -import "./IAave.sol"; -import { IERC20, InitializableAbstractStrategy } from "../utils/InitializableAbstractStrategy.sol"; - -import { IAaveStakedToken } from "./IAaveStakeToken.sol"; -import { IAaveIncentivesController } from "./IAaveIncentivesController.sol"; - -contract AaveStrategy is InitializableAbstractStrategy { - using SafeERC20 for IERC20; - - uint16 constant referralCode = 92; - - IAaveIncentivesController public incentivesController; - IAaveStakedToken public stkAave; - - /** - * @param _stratConfig The platform and OToken vault addresses - */ - constructor(BaseStrategyConfig memory _stratConfig) - InitializableAbstractStrategy(_stratConfig) - {} - - /** - * Initializer for setting up strategy internal state. This overrides the - * InitializableAbstractStrategy initializer as AAVE needs several extra - * addresses for the rewards program. - * @param _rewardTokenAddresses Address of the AAVE token - * @param _assets Addresses of supported assets - * @param _pTokens Platform Token corresponding addresses - * @param _incentivesAddress Address of the AAVE incentives controller - * @param _stkAaveAddress Address of the stkAave contract - */ - function initialize( - address[] calldata _rewardTokenAddresses, // AAVE - address[] calldata _assets, - address[] calldata _pTokens, - address _incentivesAddress, - address _stkAaveAddress - ) external onlyGovernor initializer { - incentivesController = IAaveIncentivesController(_incentivesAddress); - stkAave = IAaveStakedToken(_stkAaveAddress); - InitializableAbstractStrategy._initialize( - _rewardTokenAddresses, - _assets, - _pTokens - ); - } - - /** - * @dev Deposit asset into Aave - * @param _asset Address of asset to deposit - * @param _amount Amount of asset to deposit - */ - function deposit(address _asset, uint256 _amount) - external - override - onlyVault - nonReentrant - { - _deposit(_asset, _amount); - } - - /** - * @dev Deposit asset into Aave - * @param _asset Address of asset to deposit - * @param _amount Amount of asset to deposit - */ - function _deposit(address _asset, uint256 _amount) internal { - require(_amount > 0, "Must deposit something"); - // Following line also doubles as a check that we are depositing - // an asset that we support. - emit Deposit(_asset, _getATokenFor(_asset), _amount); - _getLendingPool().deposit(_asset, _amount, address(this), referralCode); - } - - /** - * @dev Deposit the entire balance of any supported asset into Aave - */ - function depositAll() external override onlyVault nonReentrant { - for (uint256 i = 0; i < assetsMapped.length; i++) { - uint256 balance = IERC20(assetsMapped[i]).balanceOf(address(this)); - if (balance > 0) { - _deposit(assetsMapped[i], balance); - } - } - } - - /** - * @dev Withdraw asset from Aave - * @param _recipient Address to receive withdrawn asset - * @param _asset Address of asset to withdraw - * @param _amount Amount of asset to withdraw - */ - function withdraw( - address _recipient, - address _asset, - uint256 _amount - ) external override onlyVault nonReentrant { - require(_amount > 0, "Must withdraw something"); - require(_recipient != address(0), "Must specify recipient"); - - emit Withdrawal(_asset, _getATokenFor(_asset), _amount); - uint256 actual = _getLendingPool().withdraw( - _asset, - _amount, - address(this) - ); - require(actual == _amount, "Did not withdraw enough"); - IERC20(_asset).safeTransfer(_recipient, _amount); - } - - /** - * @dev Remove all assets from platform and send them to Vault contract. - */ - function withdrawAll() external override onlyVaultOrGovernor nonReentrant { - for (uint256 i = 0; i < assetsMapped.length; i++) { - // Redeem entire balance of aToken - IERC20 asset = IERC20(assetsMapped[i]); - address aToken = _getATokenFor(assetsMapped[i]); - uint256 balance = IERC20(aToken).balanceOf(address(this)); - if (balance > 0) { - uint256 actual = _getLendingPool().withdraw( - address(asset), - balance, - address(this) - ); - require(actual == balance, "Did not withdraw enough"); - - uint256 assetBalance = asset.balanceOf(address(this)); - // Transfer entire balance to Vault - asset.safeTransfer(vaultAddress, assetBalance); - - emit Withdrawal(address(asset), aToken, assetBalance); - } - } - } - - /** - * @dev Get the total asset value held in the platform - * @param _asset Address of the asset - * @return balance Total value of the asset in the platform - */ - function checkBalance(address _asset) - external - view - override - returns (uint256 balance) - { - // Balance is always with token aToken decimals - address aToken = _getATokenFor(_asset); - balance = IERC20(aToken).balanceOf(address(this)); - } - - /** - * @dev Returns bool indicating whether asset is supported by strategy - * @param _asset Address of the asset - */ - function supportsAsset(address _asset) public view override returns (bool) { - return assetToPToken[_asset] != address(0); - } - - /** - * @dev Approve the spending of all assets by their corresponding aToken, - * if for some reason is it necessary. - */ - function safeApproveAllTokens() - external - override - onlyGovernor - nonReentrant - { - address lendingPool = address(_getLendingPool()); - // approve the pool to spend the Asset - for (uint256 i = 0; i < assetsMapped.length; i++) { - address asset = assetsMapped[i]; - // Safe approval - IERC20(asset).safeApprove(lendingPool, 0); - IERC20(asset).safeApprove(lendingPool, type(uint256).max); - } - } - - /** - * @dev Internal method to respond to the addition of new asset / aTokens - We need to give the AAVE lending pool approval to transfer the - asset. - * @param _asset Address of the asset to approve - * @param _aToken Address of the aToken - */ - // solhint-disable-next-line no-unused-vars - function _abstractSetPToken(address _asset, address _aToken) - internal - override - { - address lendingPool = address(_getLendingPool()); - IERC20(_asset).safeApprove(lendingPool, 0); - IERC20(_asset).safeApprove(lendingPool, type(uint256).max); - } - - /** - * @dev Get the aToken wrapped in the IERC20 interface for this asset. - * Fails if the pToken doesn't exist in our mappings. - * @param _asset Address of the asset - * @return Corresponding aToken to this asset - */ - function _getATokenFor(address _asset) internal view returns (address) { - address aToken = assetToPToken[_asset]; - require(aToken != address(0), "aToken does not exist"); - return aToken; - } - - /** - * @dev Get the current address of the Aave lending pool, which is the gateway to - * depositing. - * @return Current lending pool implementation - */ - function _getLendingPool() internal view returns (IAaveLendingPool) { - address lendingPool = ILendingPoolAddressesProvider(platformAddress) - .getLendingPool(); - require(lendingPool != address(0), "Lending pool does not exist"); - return IAaveLendingPool(lendingPool); - } - - /** - * @dev Collect stkAave, convert it to AAVE send to Vault. - */ - function collectRewardTokens() - external - override - onlyHarvester - nonReentrant - { - if (address(stkAave) == address(0)) { - return; - } - - // Check staked AAVE cooldown timer - uint256 cooldown = stkAave.stakersCooldowns(address(this)); - uint256 windowStart = cooldown + stkAave.COOLDOWN_SECONDS(); - uint256 windowEnd = windowStart + stkAave.UNSTAKE_WINDOW(); - - // If inside the unlock window, then we can redeem stkAave - // for AAVE and send it to the vault. - if (block.timestamp > windowStart && block.timestamp <= windowEnd) { - // Redeem to AAVE - uint256 stkAaveBalance = stkAave.balanceOf(address(this)); - stkAave.redeem(address(this), stkAaveBalance); - - // Transfer AAVE to harvesterAddress - uint256 aaveBalance = IERC20(rewardTokenAddresses[0]).balanceOf( - address(this) - ); - if (aaveBalance > 0) { - IERC20(rewardTokenAddresses[0]).safeTransfer( - harvesterAddress, - aaveBalance - ); - } - } - - // Collect available rewards and restart the cooldown timer, if either of - // those should be run. - if (block.timestamp > windowStart || cooldown == 0) { - uint256 assetsLen = assetsMapped.length; - // aToken addresses for incentives controller - address[] memory aTokens = new address[](assetsLen); - for (uint256 i = 0; i < assetsLen; ++i) { - aTokens[i] = _getATokenFor(assetsMapped[i]); - } - - // 1. If we have rewards availabile, collect them - uint256 pendingRewards = incentivesController.getRewardsBalance( - aTokens, - address(this) - ); - if (pendingRewards > 0) { - // Because getting more stkAAVE from the incentives controller - // with claimRewards() may push the stkAAVE cooldown time - // forward, it is called after stakedAAVE has been turned into - // AAVE. - uint256 collected = incentivesController.claimRewards( - aTokens, - pendingRewards, - address(this) - ); - require(collected == pendingRewards, "AAVE reward difference"); - } - - // 2. Start cooldown counting down. - if (stkAave.balanceOf(address(this)) > 0) { - // Protected with if since cooldown call would revert - // if no stkAave balance. - stkAave.cooldown(); - } - } - } -} diff --git a/contracts/contracts/strategies/AbstractCompoundStrategy.sol b/contracts/contracts/strategies/AbstractCompoundStrategy.sol deleted file mode 100644 index d2e447c1ad..0000000000 --- a/contracts/contracts/strategies/AbstractCompoundStrategy.sol +++ /dev/null @@ -1,55 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -/** - * @title Base Compound Abstract Strategy - * @author Origin Protocol Inc - */ - -import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import { ICERC20 } from "./ICompound.sol"; -import { IComptroller } from "../interfaces/IComptroller.sol"; -import { IERC20, InitializableAbstractStrategy } from "../utils/InitializableAbstractStrategy.sol"; - -abstract contract AbstractCompoundStrategy is InitializableAbstractStrategy { - using SafeERC20 for IERC20; - - int256[50] private __reserved; - - /** - * @dev Retuns bool indicating whether asset is supported by strategy - * @param _asset Address of the asset - */ - function supportsAsset(address _asset) public view override returns (bool) { - return assetToPToken[_asset] != address(0); - } - - /** - * @dev Get the cToken wrapped in the ICERC20 interface for this asset. - * Fails if the pToken doesn't exist in our mappings. - * @param _asset Address of the asset - * @return Corresponding cToken to this asset - */ - function _getCTokenFor(address _asset) internal view returns (ICERC20) { - address cToken = assetToPToken[_asset]; - require(cToken != address(0), "cToken does not exist"); - return ICERC20(cToken); - } - - /** - * @dev Converts an underlying amount into cToken amount - * cTokenAmt = (underlying * 1e18) / exchangeRate - * @param _cToken cToken for which to change - * @param _underlying Amount of underlying to convert - * @return amount Equivalent amount of cTokens - */ - function _convertUnderlyingToCToken(ICERC20 _cToken, uint256 _underlying) - internal - view - returns (uint256 amount) - { - // e.g. 1e18*1e18 / 205316390724364402565641705 = 50e8 - // e.g. 1e8*1e18 / 205316390724364402565641705 = 0.45 or 0 - amount = (_underlying * 1e18) / _cToken.exchangeRateStored(); - } -} diff --git a/contracts/contracts/strategies/AbstractConvexMetaStrategy.sol b/contracts/contracts/strategies/AbstractConvexMetaStrategy.sol deleted file mode 100644 index 8692c4d491..0000000000 --- a/contracts/contracts/strategies/AbstractConvexMetaStrategy.sol +++ /dev/null @@ -1,251 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -/** - * @title Curve Convex Strategy - * @notice Investment strategy for investing stablecoins via Curve 3Pool - * @author Origin Protocol Inc - */ -import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; - -import { IRewardStaking } from "./IRewardStaking.sol"; -import { ICurvePool } from "./ICurvePool.sol"; -import { ICurveMetaPool } from "./ICurveMetaPool.sol"; -import { IERC20, AbstractCurveStrategy, InitializableAbstractStrategy } from "./AbstractCurveStrategy.sol"; -import { StableMath } from "../utils/StableMath.sol"; -import { Helpers } from "../utils/Helpers.sol"; - -abstract contract AbstractConvexMetaStrategy is AbstractCurveStrategy { - using StableMath for uint256; - using SafeERC20 for IERC20; - - event MaxWithdrawalSlippageUpdated( - uint256 _prevMaxSlippagePercentage, - uint256 _newMaxSlippagePercentage - ); - - // used to circumvent the stack too deep issue - struct InitConfig { - address cvxDepositorAddress; //Address of the Convex depositor(AKA booster) for this pool - address metapoolAddress; //Address of the Curve MetaPool - address metapoolMainToken; //Address of Main metapool token - address cvxRewardStakerAddress; //Address of the CVX rewards staker - address metapoolLPToken; //Address of metapool LP token - uint256 cvxDepositorPTokenId; //Pid of the pool referred to by Depositor and staker - } - - address internal cvxDepositorAddress; - address internal cvxRewardStakerAddress; - uint256 internal cvxDepositorPTokenId; - ICurveMetaPool internal metapool; - IERC20 internal metapoolMainToken; - IERC20 internal metapoolLPToken; - // Ordered list of metapool assets - address[] internal metapoolAssets; - // Max withdrawal slippage denominated in 1e18 (1e18 == 100%) - uint256 public maxWithdrawalSlippage; - uint128 internal crvCoinIndex; - uint128 internal mainCoinIndex; - - int256[41] private ___reserved; - - /** - * Initializer for setting up strategy internal state. This overrides the - * InitializableAbstractStrategy initializer as Curve strategies don't fit - * well within that abstraction. - * @param _rewardTokenAddresses Address of CRV & CVX - * @param _assets Addresses of supported assets. MUST be passed in the same - * order as returned by coins on the pool contract, i.e. - * DAI, USDC, USDT - * @param _pTokens Platform Token corresponding addresses - * @param initConfig Various addresses and info for initialization state - */ - function initialize( - address[] calldata _rewardTokenAddresses, // CRV + CVX - address[] calldata _assets, - address[] calldata _pTokens, - InitConfig calldata initConfig - ) external onlyGovernor initializer { - require(_assets.length == 3, "Must have exactly three assets"); - // Should be set prior to abstract initialize call otherwise - // abstractSetPToken calls will fail - cvxDepositorAddress = initConfig.cvxDepositorAddress; - pTokenAddress = _pTokens[0]; - metapool = ICurveMetaPool(initConfig.metapoolAddress); - metapoolMainToken = IERC20(initConfig.metapoolMainToken); - cvxRewardStakerAddress = initConfig.cvxRewardStakerAddress; - metapoolLPToken = IERC20(initConfig.metapoolLPToken); - cvxDepositorPTokenId = initConfig.cvxDepositorPTokenId; - maxWithdrawalSlippage = 1e16; - - metapoolAssets = [metapool.coins(0), metapool.coins(1)]; - crvCoinIndex = _getMetapoolCoinIndex(pTokenAddress); - mainCoinIndex = _getMetapoolCoinIndex(initConfig.metapoolMainToken); - InitializableAbstractStrategy._initialize( - _rewardTokenAddresses, - _assets, - _pTokens - ); - _approveBase(); - } - - /** - * @dev Get the total asset value held in the platform - * @param _asset Address of the asset - * @return balance Total value of the asset in the platform - */ - function checkBalance(address _asset) - public - view - virtual - override - returns (uint256 balance) - { - require(assetToPToken[_asset] != address(0), "Unsupported asset"); - balance = 0; - - // LP tokens in this contract. This should generally be nothing as we - // should always stake the full balance in the Gauge, but include for - // safety - uint256 contractPTokens = IERC20(pTokenAddress).balanceOf( - address(this) - ); - ICurvePool curvePool = ICurvePool(platformAddress); - if (contractPTokens > 0) { - uint256 virtual_price = curvePool.get_virtual_price(); - uint256 value = contractPTokens.mulTruncate(virtual_price); - balance += value; - } - - /* We intentionally omit the metapoolLp tokens held by the metastrategyContract - * since the contract should never (except in the middle of deposit/withdrawal - * transaction) hold any amount of those tokens in normal operation. There - * could be tokens sent to it by a 3rd party and we decide to actively ignore - * those. - */ - uint256 metapoolGaugePTokens = IRewardStaking(cvxRewardStakerAddress) - .balanceOf(address(this)); - - if (metapoolGaugePTokens > 0) { - uint256 value = metapoolGaugePTokens.mulTruncate( - metapool.get_virtual_price() - ); - balance += value; - } - - uint256 assetDecimals = Helpers.getDecimals(_asset); - balance = balance.scaleBy(assetDecimals, 18) / THREEPOOL_ASSET_COUNT; - } - - /** - * @dev This function is completely analogous to _calcCurveTokenAmount[AbstractCurveStrategy] - * and just utilizes different Curve (meta)pool API - */ - function _calcCurveMetaTokenAmount(uint128 _coinIndex, uint256 _amount) - internal - returns (uint256 requiredMetapoolLP) - { - uint256[2] memory _amounts = [uint256(0), uint256(0)]; - _amounts[uint256(_coinIndex)] = _amount; - - // LP required when removing required asset ignoring fees - uint256 lpRequiredNoFees = metapool.calc_token_amount(_amounts, false); - /* LP required if fees would apply to entirety of removed amount - * - * fee is 1e10 denominated number: https://curve.readthedocs.io/exchange-pools.html#StableSwap.fee - */ - uint256 lpRequiredFullFees = lpRequiredNoFees.mulTruncateScale( - 1e10 + metapool.fee(), - 1e10 - ); - - /* asset received when withdrawing full fee applicable LP accounting for - * slippage and fees - */ - uint256 assetReceivedForFullLPFees = metapool.calc_withdraw_one_coin( - lpRequiredFullFees, - int128(_coinIndex) - ); - - // exact amount of LP required - requiredMetapoolLP = - (lpRequiredFullFees * _amount) / - assetReceivedForFullLPFees; - } - - function _approveBase() internal override { - IERC20 pToken = IERC20(pTokenAddress); - // 3Pool for LP token (required for removing liquidity) - pToken.safeApprove(platformAddress, 0); - pToken.safeApprove(platformAddress, type(uint256).max); - // Gauge for LP token - metapoolLPToken.safeApprove(cvxDepositorAddress, 0); - metapoolLPToken.safeApprove(cvxDepositorAddress, type(uint256).max); - // Metapool for LP token - pToken.safeApprove(address(metapool), 0); - pToken.safeApprove(address(metapool), type(uint256).max); - // Metapool for Metapool main token - metapoolMainToken.safeApprove(address(metapool), 0); - metapoolMainToken.safeApprove(address(metapool), type(uint256).max); - } - - /** - * @dev Get the index of the coin - */ - function _getMetapoolCoinIndex(address _asset) - internal - view - returns (uint128) - { - for (uint128 i = 0; i < 2; i++) { - if (metapoolAssets[i] == _asset) return i; - } - revert("Invalid Metapool asset"); - } - - /** - * @dev Sets max withdrawal slippage that is considered when removing - * liquidity from Metapools. - * @param _maxWithdrawalSlippage Max withdrawal slippage denominated in - * wad (number with 18 decimals): 1e18 == 100%, 1e16 == 1% - * - * IMPORTANT Minimum maxWithdrawalSlippage should actually be 0.1% (1e15) - * for production usage. Contract allows as low value as 0% for confirming - * correct behavior in test suite. - */ - function setMaxWithdrawalSlippage(uint256 _maxWithdrawalSlippage) - external - onlyVaultOrGovernorOrStrategist - { - require( - _maxWithdrawalSlippage <= 1e18, - "Max withdrawal slippage needs to be between 0% - 100%" - ); - emit MaxWithdrawalSlippageUpdated( - maxWithdrawalSlippage, - _maxWithdrawalSlippage - ); - maxWithdrawalSlippage = _maxWithdrawalSlippage; - } - - /** - * @dev Collect accumulated CRV and CVX and send to Harvester. - */ - function collectRewardTokens() - external - override - onlyHarvester - nonReentrant - { - // Collect CRV and CVX - IRewardStaking(cvxRewardStakerAddress).getReward(); - _collectRewardTokens(); - } - - /** - * @dev Returns the largest of two numbers int256 version - */ - function _max(int256 a, int256 b) internal pure returns (int256) { - return a >= b ? a : b; - } -} diff --git a/contracts/contracts/strategies/AbstractCurveStrategy.sol b/contracts/contracts/strategies/AbstractCurveStrategy.sol deleted file mode 100644 index 3ee7dd50a9..0000000000 --- a/contracts/contracts/strategies/AbstractCurveStrategy.sol +++ /dev/null @@ -1,303 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -/** - * @title Curve 3Pool Strategy - * @notice Investment strategy for investing stablecoins via Curve 3Pool - * @author Origin Protocol Inc - */ -import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; - -import { ICurvePool } from "./ICurvePool.sol"; -import { IERC20, InitializableAbstractStrategy } from "../utils/InitializableAbstractStrategy.sol"; -import { StableMath } from "../utils/StableMath.sol"; -import { Helpers } from "../utils/Helpers.sol"; - -abstract contract AbstractCurveStrategy is InitializableAbstractStrategy { - using StableMath for uint256; - using SafeERC20 for IERC20; - - uint256 internal constant MAX_SLIPPAGE = 1e16; // 1%, same as the Curve UI - // number of assets in Curve 3Pool (USDC, DAI, USDT) - uint256 internal constant THREEPOOL_ASSET_COUNT = 3; - address internal pTokenAddress; - - int256[49] private __reserved; - - /** - * @dev Deposit asset into the Curve 3Pool - * @param _asset Address of asset to deposit - * @param _amount Amount of asset to deposit - */ - function deposit(address _asset, uint256 _amount) - external - override - onlyVault - nonReentrant - { - require(_amount > 0, "Must deposit something"); - emit Deposit(_asset, pTokenAddress, _amount); - - // 3Pool requires passing deposit amounts for all 3 assets, set to 0 for - // all - uint256[3] memory _amounts; - uint256 poolCoinIndex = _getCoinIndex(_asset); - // Set the amount on the asset we want to deposit - _amounts[poolCoinIndex] = _amount; - ICurvePool curvePool = ICurvePool(platformAddress); - uint256 assetDecimals = Helpers.getDecimals(_asset); - uint256 depositValue = _amount.scaleBy(18, assetDecimals).divPrecisely( - curvePool.get_virtual_price() - ); - uint256 minMintAmount = depositValue.mulTruncate( - uint256(1e18) - MAX_SLIPPAGE - ); - // Do the deposit to 3pool - curvePool.add_liquidity(_amounts, minMintAmount); - _lpDepositAll(); - } - - function _lpDepositAll() internal virtual; - - /** - * @dev Deposit the entire balance of any supported asset into the Curve 3pool - */ - function depositAll() external override onlyVault nonReentrant { - uint256[3] memory _amounts = [uint256(0), uint256(0), uint256(0)]; - uint256 depositValue = 0; - ICurvePool curvePool = ICurvePool(platformAddress); - uint256 curveVirtualPrice = curvePool.get_virtual_price(); - - uint256 assetCount = assetsMapped.length; - for (uint256 i = 0; i < assetCount; i++) { - address assetAddress = assetsMapped[i]; - uint256 balance = IERC20(assetAddress).balanceOf(address(this)); - if (balance > 0) { - uint256 poolCoinIndex = _getCoinIndex(assetAddress); - // Set the amount on the asset we want to deposit - _amounts[poolCoinIndex] = balance; - uint256 assetDecimals = Helpers.getDecimals(assetAddress); - // Get value of deposit in Curve LP token to later determine - // the minMintAmount argument for add_liquidity - depositValue = - depositValue + - balance.scaleBy(18, assetDecimals).divPrecisely( - curveVirtualPrice - ); - emit Deposit(assetAddress, pTokenAddress, balance); - } - } - - uint256 minMintAmount = depositValue.mulTruncate( - uint256(1e18) - MAX_SLIPPAGE - ); - // Do the deposit to 3pool - curvePool.add_liquidity(_amounts, minMintAmount); - - /* In case of Curve Strategy all assets are mapped to the same pToken (3CrvLP). Let - * descendants further handle the pToken. By either deploying it to the metapool and - * resulting tokens in Gauge. Or deploying pTokens directly to the Gauge. - */ - _lpDepositAll(); - } - - function _lpWithdraw(uint256 numCrvTokens) internal virtual; - - function _lpWithdrawAll() internal virtual; - - /** - * @dev Withdraw asset from Curve 3Pool - * @param _recipient Address to receive withdrawn asset - * @param _asset Address of asset to withdraw - * @param _amount Amount of asset to withdraw - */ - function withdraw( - address _recipient, - address _asset, - uint256 _amount - ) external override onlyVault nonReentrant { - require(_amount > 0, "Invalid amount"); - - emit Withdrawal(_asset, pTokenAddress, _amount); - - uint256 contractCrv3Tokens = IERC20(pTokenAddress).balanceOf( - address(this) - ); - - uint256 coinIndex = _getCoinIndex(_asset); - ICurvePool curvePool = ICurvePool(platformAddress); - - uint256 requiredCrv3Tokens = _calcCurveTokenAmount(coinIndex, _amount); - - // We have enough LP tokens, make sure they are all on this contract - if (contractCrv3Tokens < requiredCrv3Tokens) { - _lpWithdraw(requiredCrv3Tokens - contractCrv3Tokens); - } - - uint256[3] memory _amounts = [uint256(0), uint256(0), uint256(0)]; - _amounts[coinIndex] = _amount; - - curvePool.remove_liquidity_imbalance(_amounts, requiredCrv3Tokens); - IERC20(_asset).safeTransfer(_recipient, _amount); - } - - /** - * @dev Calculate amount of LP required when withdrawing specific amount of one - * of the underlying assets accounting for fees and slippage. - * - * Curve pools unfortunately do not contain a calculation function for - * amount of LP required when withdrawing a specific amount of one of the - * underlying tokens and also accounting for fees (Curve's calc_token_amount - * does account for slippage but not fees). - * - * Steps taken to calculate the metric: - * - get amount of LP required if fees wouldn't apply - * - increase the LP amount as if fees would apply to the entirety of the underlying - * asset withdrawal. (when withdrawing only one coin fees apply only to amounts - * of other assets pool would return in case of balanced removal - since those need - * to be swapped for the single underlying asset being withdrawn) - * - get amount of underlying asset withdrawn (this Curve function does consider slippage - * and fees) when using the increased LP amount. As LP amount is slightly over-increased - * so is amount of underlying assets returned. - * - since we know exactly how much asset we require take the rate of LP required for asset - * withdrawn to get the exact amount of LP. - */ - function _calcCurveTokenAmount(uint256 _coinIndex, uint256 _amount) - internal - returns (uint256 required3Crv) - { - ICurvePool curvePool = ICurvePool(platformAddress); - - uint256[3] memory _amounts = [uint256(0), uint256(0), uint256(0)]; - _amounts[_coinIndex] = _amount; - - // LP required when removing required asset ignoring fees - uint256 lpRequiredNoFees = curvePool.calc_token_amount(_amounts, false); - /* LP required if fees would apply to entirety of removed amount - * - * fee is 1e10 denominated number: https://curve.readthedocs.io/exchange-pools.html#StableSwap.fee - */ - uint256 lpRequiredFullFees = lpRequiredNoFees.mulTruncateScale( - 1e10 + curvePool.fee(), - 1e10 - ); - - /* asset received when withdrawing full fee applicable LP accounting for - * slippage and fees - */ - uint256 assetReceivedForFullLPFees = curvePool.calc_withdraw_one_coin( - lpRequiredFullFees, - int128(uint128(_coinIndex)) - ); - - // exact amount of LP required - required3Crv = - (lpRequiredFullFees * _amount) / - assetReceivedForFullLPFees; - } - - /** - * @dev Remove all assets from platform and send them to Vault contract. - */ - function withdrawAll() external override onlyVaultOrGovernor nonReentrant { - _lpWithdrawAll(); - // Withdraws are proportional to assets held by 3Pool - uint256[3] memory minWithdrawAmounts = [ - uint256(0), - uint256(0), - uint256(0) - ]; - - // Remove liquidity - ICurvePool threePool = ICurvePool(platformAddress); - threePool.remove_liquidity( - IERC20(pTokenAddress).balanceOf(address(this)), - minWithdrawAmounts - ); - // Transfer assets out of Vault - // Note that Curve will provide all 3 of the assets in 3pool even if - // we have not set PToken addresses for all of them in this strategy - for (uint256 i = 0; i < assetsMapped.length; i++) { - IERC20 asset = IERC20(threePool.coins(i)); - asset.safeTransfer(vaultAddress, asset.balanceOf(address(this))); - } - } - - /** - * @dev Get the total asset value held in the platform - * @param _asset Address of the asset - * @return balance Total value of the asset in the platform - */ - function checkBalance(address _asset) - public - view - virtual - override - returns (uint256 balance) - { - require(assetToPToken[_asset] != address(0), "Unsupported asset"); - // LP tokens in this contract. This should generally be nothing as we - // should always stake the full balance in the Gauge, but include for - // safety - uint256 totalPTokens = IERC20(pTokenAddress).balanceOf(address(this)); - ICurvePool curvePool = ICurvePool(platformAddress); - if (totalPTokens > 0) { - uint256 virtual_price = curvePool.get_virtual_price(); - uint256 value = (totalPTokens * virtual_price) / 1e18; - uint256 assetDecimals = Helpers.getDecimals(_asset); - balance = value.scaleBy(assetDecimals, 18) / THREEPOOL_ASSET_COUNT; - } - } - - /** - * @dev Retuns bool indicating whether asset is supported by strategy - * @param _asset Address of the asset - */ - function supportsAsset(address _asset) public view override returns (bool) { - return assetToPToken[_asset] != address(0); - } - - /** - * @dev Approve the spending of all assets by their corresponding pool tokens, - * if for some reason is it necessary. - */ - function safeApproveAllTokens() - external - override - onlyGovernor - nonReentrant - { - _approveBase(); - // This strategy is a special case since it only supports one asset - for (uint256 i = 0; i < assetsMapped.length; i++) { - _approveAsset(assetsMapped[i]); - } - } - - /** - * @dev Call the necessary approvals for the Curve pool and gauge - * @param _asset Address of the asset - */ - function _abstractSetPToken(address _asset, address) internal override { - _approveAsset(_asset); - } - - function _approveAsset(address _asset) internal { - IERC20 asset = IERC20(_asset); - // 3Pool for asset (required for adding liquidity) - asset.safeApprove(platformAddress, 0); - asset.safeApprove(platformAddress, type(uint256).max); - } - - function _approveBase() internal virtual; - - /** - * @dev Get the index of the coin - */ - function _getCoinIndex(address _asset) internal view returns (uint256) { - for (uint256 i = 0; i < 3; i++) { - if (assetsMapped[i] == _asset) return i; - } - revert("Invalid 3pool asset"); - } -} diff --git a/contracts/contracts/strategies/CompoundStrategy.sol b/contracts/contracts/strategies/CompoundStrategy.sol deleted file mode 100644 index 83c5d12310..0000000000 --- a/contracts/contracts/strategies/CompoundStrategy.sol +++ /dev/null @@ -1,230 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -/** - * @title Compound Strategy - * @notice Investment strategy for Compound like lending platforms. eg Compound and Flux - * @author Origin Protocol Inc - */ -import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; - -import { ICERC20 } from "./ICompound.sol"; -import { AbstractCompoundStrategy, InitializableAbstractStrategy } from "./AbstractCompoundStrategy.sol"; -import { IComptroller } from "../interfaces/IComptroller.sol"; -import { IERC20 } from "../utils/InitializableAbstractStrategy.sol"; - -contract CompoundStrategy is AbstractCompoundStrategy { - using SafeERC20 for IERC20; - event SkippedWithdrawal(address asset, uint256 amount); - - constructor(BaseStrategyConfig memory _stratConfig) - InitializableAbstractStrategy(_stratConfig) - {} - - /** - * @notice initialize function, to set up initial internal state - * @param _rewardTokenAddresses Address of reward token for platform - * @param _assets Addresses of initial supported assets - * @param _pTokens Platform Token corresponding addresses - */ - function initialize( - address[] memory _rewardTokenAddresses, - address[] memory _assets, - address[] memory _pTokens - ) external onlyGovernor initializer { - InitializableAbstractStrategy._initialize( - _rewardTokenAddresses, - _assets, - _pTokens - ); - } - - /** - * @notice Collect accumulated COMP and send to Harvester. - */ - function collectRewardTokens() - external - virtual - override - onlyHarvester - nonReentrant - { - // Claim COMP from Comptroller - ICERC20 cToken = _getCTokenFor(assetsMapped[0]); - IComptroller comptroller = IComptroller(cToken.comptroller()); - // Only collect from active cTokens, saves gas - address[] memory ctokensToCollect = new address[](assetsMapped.length); - uint256 assetCount = assetsMapped.length; - for (uint256 i = 0; i < assetCount; ++i) { - ctokensToCollect[i] = address(_getCTokenFor(assetsMapped[i])); - } - // Claim only for this strategy - address[] memory claimers = new address[](1); - claimers[0] = address(this); - // Claim COMP from Comptroller. Only collect for supply, saves gas - comptroller.claimComp(claimers, ctokensToCollect, false, true); - // Transfer COMP to Harvester - IERC20 rewardToken = IERC20(rewardTokenAddresses[0]); - uint256 balance = rewardToken.balanceOf(address(this)); - emit RewardTokenCollected( - harvesterAddress, - rewardTokenAddresses[0], - balance - ); - rewardToken.safeTransfer(harvesterAddress, balance); - } - - /** - * @notice Deposit asset into the underlying platform - * @param _asset Address of asset to deposit - * @param _amount Amount of assets to deposit - */ - function deposit(address _asset, uint256 _amount) - external - override - onlyVault - nonReentrant - { - _deposit(_asset, _amount); - } - - /** - * @dev Deposit an asset into the underlying platform - * @param _asset Address of the asset to deposit - * @param _amount Amount of assets to deposit - */ - function _deposit(address _asset, uint256 _amount) internal { - require(_amount > 0, "Must deposit something"); - ICERC20 cToken = _getCTokenFor(_asset); - emit Deposit(_asset, address(cToken), _amount); - require(cToken.mint(_amount) == 0, "cToken mint failed"); - } - - /** - * @notice Deposit the entire balance of any supported asset in the strategy into the underlying platform - */ - function depositAll() external override onlyVault nonReentrant { - uint256 assetCount = assetsMapped.length; - for (uint256 i = 0; i < assetCount; ++i) { - IERC20 asset = IERC20(assetsMapped[i]); - uint256 assetBalance = asset.balanceOf(address(this)); - if (assetBalance > 0) { - _deposit(address(asset), assetBalance); - } - } - } - - /** - * @notice Withdraw an asset from the underlying platform - * @param _recipient Address to receive withdrawn assets - * @param _asset Address of the asset to withdraw - * @param _amount Amount of assets to withdraw - */ - function withdraw( - address _recipient, - address _asset, - uint256 _amount - ) external override onlyVault nonReentrant { - require(_amount > 0, "Must withdraw something"); - require(_recipient != address(0), "Must specify recipient"); - - ICERC20 cToken = _getCTokenFor(_asset); - // If redeeming 0 cTokens, just skip, else COMP will revert - uint256 cTokensToRedeem = _convertUnderlyingToCToken(cToken, _amount); - if (cTokensToRedeem == 0) { - emit SkippedWithdrawal(_asset, _amount); - return; - } - - emit Withdrawal(_asset, address(cToken), _amount); - require(cToken.redeemUnderlying(_amount) == 0, "Redeem failed"); - IERC20(_asset).safeTransfer(_recipient, _amount); - } - - /** - * @dev Internal method to respond to the addition of new asset / cTokens - * We need to approve the cToken and give it permission to spend the asset - * @param _asset Address of the asset to approve. eg DAI - * @param _pToken The pToken for the approval. eg cDAI or fDAI - */ - function _abstractSetPToken(address _asset, address _pToken) - internal - override - { - // Safe approval - IERC20(_asset).safeApprove(_pToken, 0); - IERC20(_asset).safeApprove(_pToken, type(uint256).max); - } - - /** - * @notice Remove all supported assets from the underlying platform and send them to Vault contract. - */ - function withdrawAll() external override onlyVaultOrGovernor nonReentrant { - uint256 assetCount = assetsMapped.length; - for (uint256 i = 0; i < assetCount; ++i) { - IERC20 asset = IERC20(assetsMapped[i]); - // Redeem entire balance of cToken - ICERC20 cToken = _getCTokenFor(address(asset)); - uint256 cTokenBalance = cToken.balanceOf(address(this)); - if (cTokenBalance > 0) { - require(cToken.redeem(cTokenBalance) == 0, "Redeem failed"); - uint256 assetBalance = asset.balanceOf(address(this)); - // Transfer entire balance to Vault - asset.safeTransfer(vaultAddress, assetBalance); - - emit Withdrawal(address(asset), address(cToken), assetBalance); - } - } - } - - /** - * @notice Get the total asset value held in the underlying platform - * This includes any interest that was generated since depositing. - * The exchange rate between the cToken and asset gradually increases, - * causing the cToken to be worth more corresponding asset. - * @param _asset Address of the asset - * @return balance Total value of the asset in the platform - */ - function checkBalance(address _asset) - external - view - override - returns (uint256 balance) - { - // Balance is always with token cToken decimals - ICERC20 cToken = _getCTokenFor(_asset); - balance = _checkBalance(cToken); - } - - /** - * @dev Get the total asset value held in the platform - * underlying = (cTokenAmt * exchangeRate) / 1e18 - * @param _cToken cToken for which to check balance - * @return balance Total value of the asset in the platform - */ - function _checkBalance(ICERC20 _cToken) - internal - view - returns (uint256 balance) - { - // e.g. 50e8*205316390724364402565641705 / 1e18 = 1.0265..e18 - balance = - (_cToken.balanceOf(address(this)) * _cToken.exchangeRateStored()) / - 1e18; - } - - /** - * @notice Approve the spending of all assets by their corresponding cToken, - * if for some reason is it necessary. - */ - function safeApproveAllTokens() external override { - uint256 assetCount = assetsMapped.length; - for (uint256 i = 0; i < assetCount; ++i) { - IERC20 asset = IERC20(assetsMapped[i]); - address cToken = assetToPToken[address(asset)]; - // Safe approval - asset.safeApprove(cToken, 0); - asset.safeApprove(cToken, type(uint256).max); - } - } -} diff --git a/contracts/contracts/strategies/ConvexEthMetaStrategy.sol b/contracts/contracts/strategies/ConvexEthMetaStrategy.sol deleted file mode 100644 index 2343cf3a5e..0000000000 --- a/contracts/contracts/strategies/ConvexEthMetaStrategy.sol +++ /dev/null @@ -1,617 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -/** - * @title Convex Automated Market Maker (AMO) Strategy - * @notice AMO strategy for the Curve OETH/ETH pool - * @author Origin Protocol Inc - */ -import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import "@openzeppelin/contracts/utils/math/Math.sol"; - -import { ICurveETHPoolV1 } from "./ICurveETHPoolV1.sol"; -import { IERC20, InitializableAbstractStrategy } from "../utils/InitializableAbstractStrategy.sol"; -import { StableMath } from "../utils/StableMath.sol"; -import { IVault } from "../interfaces/IVault.sol"; -import { IWETH9 } from "../interfaces/IWETH9.sol"; -import { IConvexDeposits } from "./IConvexDeposits.sol"; -import { IRewardStaking } from "./IRewardStaking.sol"; - -contract ConvexEthMetaStrategy is InitializableAbstractStrategy { - using StableMath for uint256; - using SafeERC20 for IERC20; - - uint256 public constant MAX_SLIPPAGE = 1e16; // 1%, same as the Curve UI - address public constant ETH_ADDRESS = - 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; - - // The following slots have been deprecated with immutable variables - // slither-disable-next-line constable-states - address private _deprecated_cvxDepositorAddress; - // slither-disable-next-line constable-states - address private _deprecated_cvxRewardStaker; - // slither-disable-next-line constable-states - uint256 private _deprecated_cvxDepositorPTokenId; - // slither-disable-next-line constable-states - address private _deprecated_curvePool; - // slither-disable-next-line constable-states - address private _deprecated_lpToken; - // slither-disable-next-line constable-states - address private _deprecated_oeth; - // slither-disable-next-line constable-states - address private _deprecated_weth; - - // Ordered list of pool assets - // slither-disable-next-line constable-states - uint128 private _deprecated_oethCoinIndex; - // slither-disable-next-line constable-states - uint128 private _deprecated_ethCoinIndex; - - // New immutable variables that must be set in the constructor - address public immutable cvxDepositorAddress; - IRewardStaking public immutable cvxRewardStaker; - uint256 public immutable cvxDepositorPTokenId; - ICurveETHPoolV1 public immutable curvePool; - IERC20 public immutable lpToken; - IERC20 public immutable oeth; - IWETH9 public immutable weth; - - // Ordered list of pool assets - uint128 public constant oethCoinIndex = 1; - uint128 public constant ethCoinIndex = 0; - - /** - * @dev Verifies that the caller is the Strategist. - */ - modifier onlyStrategist() { - require( - msg.sender == IVault(vaultAddress).strategistAddr(), - "Caller is not the Strategist" - ); - _; - } - - /** - * @dev Checks the Curve pool's balances have improved and the balances - * have not tipped to the other side. - * This modifier only works on functions that do a single sided add or remove. - * The standard deposit function adds to both sides of the pool in a way that - * the pool's balance is not worsened. - * Withdrawals are proportional so doesn't change the pools asset balance. - */ - modifier improvePoolBalance() { - // Get the asset and OToken balances in the Curve pool - uint256[2] memory balancesBefore = curvePool.get_balances(); - // diff = ETH balance - OETH balance - int256 diffBefore = int256(balancesBefore[ethCoinIndex]) - - int256(balancesBefore[oethCoinIndex]); - - _; - - // Get the asset and OToken balances in the Curve pool - uint256[2] memory balancesAfter = curvePool.get_balances(); - // diff = ETH balance - OETH balance - int256 diffAfter = int256(balancesAfter[ethCoinIndex]) - - int256(balancesAfter[oethCoinIndex]); - - if (diffBefore <= 0) { - // If the pool was originally imbalanced in favor of OETH, then - // we want to check that the pool is now more balanced - require(diffAfter <= 0, "OTokens overshot peg"); - require(diffBefore < diffAfter, "OTokens balance worse"); - } - if (diffBefore >= 0) { - // If the pool was originally imbalanced in favor of ETH, then - // we want to check that the pool is now more balanced - require(diffAfter >= 0, "Assets overshot peg"); - require(diffAfter < diffBefore, "Assets balance worse"); - } - } - - // Used to circumvent the stack too deep issue - struct ConvexEthMetaConfig { - address cvxDepositorAddress; //Address of the Convex depositor(AKA booster) for this pool - address cvxRewardStakerAddress; //Address of the CVX rewards staker - uint256 cvxDepositorPTokenId; //Pid of the pool referred to by Depositor and staker - address oethAddress; //Address of OETH token - address wethAddress; //Address of WETH - } - - constructor( - BaseStrategyConfig memory _baseConfig, - ConvexEthMetaConfig memory _convexConfig - ) InitializableAbstractStrategy(_baseConfig) { - lpToken = IERC20(_baseConfig.platformAddress); - curvePool = ICurveETHPoolV1(_baseConfig.platformAddress); - - cvxDepositorAddress = _convexConfig.cvxDepositorAddress; - cvxRewardStaker = IRewardStaking(_convexConfig.cvxRewardStakerAddress); - cvxDepositorPTokenId = _convexConfig.cvxDepositorPTokenId; - oeth = IERC20(_convexConfig.oethAddress); - weth = IWETH9(_convexConfig.wethAddress); - } - - /** - * Initializer for setting up strategy internal state. This overrides the - * InitializableAbstractStrategy initializer as Curve strategies don't fit - * well within that abstraction. - * @param _rewardTokenAddresses Address of CRV & CVX - * @param _assets Addresses of supported assets. eg WETH - */ - function initialize( - address[] calldata _rewardTokenAddresses, // CRV + CVX - address[] calldata _assets // WETH - ) external onlyGovernor initializer { - require(_assets.length == 1, "Must have exactly one asset"); - require(_assets[0] == address(weth), "Asset not WETH"); - - address[] memory pTokens = new address[](1); - pTokens[0] = address(curvePool); - - InitializableAbstractStrategy._initialize( - _rewardTokenAddresses, - _assets, - pTokens - ); - - _approveBase(); - } - - /*************************************** - Deposit - ****************************************/ - - /** - * @notice Deposit WETH into the Curve pool - * @param _weth Address of Wrapped ETH (WETH) contract. - * @param _amount Amount of WETH to deposit. - */ - function deposit(address _weth, uint256 _amount) - external - override - onlyVault - nonReentrant - { - _deposit(_weth, _amount); - } - - function _deposit(address _weth, uint256 _wethAmount) internal { - require(_wethAmount > 0, "Must deposit something"); - require(_weth == address(weth), "Can only deposit WETH"); - weth.withdraw(_wethAmount); - - emit Deposit(_weth, address(lpToken), _wethAmount); - - // Get the asset and OToken balances in the Curve pool - uint256[2] memory balances = curvePool.get_balances(); - // safe to cast since min value is at least 0 - uint256 oethToAdd = uint256( - _max( - 0, - int256(balances[ethCoinIndex]) + - int256(_wethAmount) - - int256(balances[oethCoinIndex]) - ) - ); - - /* Add so much OETH so that the pool ends up being balanced. And at minimum - * add as much OETH as WETH and at maximum twice as much OETH. - */ - oethToAdd = Math.max(oethToAdd, _wethAmount); - oethToAdd = Math.min(oethToAdd, _wethAmount * 2); - - /* Mint OETH with a strategy that attempts to contribute to stability of OETH/WETH pool. Try - * to mint so much OETH that after deployment of liquidity pool ends up being balanced. - * - * To manage unpredictability minimal OETH minted will always be at least equal or greater - * to WETH amount deployed. And never larger than twice the WETH amount deployed even if - * it would have a further beneficial effect on pool stability. - */ - IVault(vaultAddress).mintForStrategy(oethToAdd); - - emit Deposit(address(oeth), address(lpToken), oethToAdd); - - uint256[2] memory _amounts; - _amounts[ethCoinIndex] = _wethAmount; - _amounts[oethCoinIndex] = oethToAdd; - - uint256 valueInLpTokens = (_wethAmount + oethToAdd).divPrecisely( - curvePool.get_virtual_price() - ); - uint256 minMintAmount = valueInLpTokens.mulTruncate( - uint256(1e18) - MAX_SLIPPAGE - ); - - // Do the deposit to the Curve pool - // slither-disable-next-line arbitrary-send - uint256 lpDeposited = curvePool.add_liquidity{ value: _wethAmount }( - _amounts, - minMintAmount - ); - - // Deposit the Curve pool's LP tokens into the Convex rewards pool - require( - IConvexDeposits(cvxDepositorAddress).deposit( - cvxDepositorPTokenId, - lpDeposited, - true // Deposit with staking - ), - "Depositing LP to Convex not successful" - ); - } - - /** - * @notice Deposit the strategy's entire balance of WETH into the Curve pool - */ - function depositAll() external override onlyVault nonReentrant { - uint256 balance = weth.balanceOf(address(this)); - if (balance > 0) { - _deposit(address(weth), balance); - } - } - - /*************************************** - Withdraw - ****************************************/ - - /** - * @notice Withdraw ETH and OETH from the Curve pool, burn the OETH, - * convert the ETH to WETH and transfer to the recipient. - * @param _recipient Address to receive withdrawn asset which is normally the Vault. - * @param _weth Address of the Wrapped ETH (WETH) contract. - * @param _amount Amount of WETH to withdraw. - */ - function withdraw( - address _recipient, - address _weth, - uint256 _amount - ) external override onlyVault nonReentrant { - require(_amount > 0, "Invalid amount"); - require(_weth == address(weth), "Can only withdraw WETH"); - - emit Withdrawal(_weth, address(lpToken), _amount); - - uint256 requiredLpTokens = calcTokenToBurn(_amount); - - _lpWithdraw(requiredLpTokens); - - /* math in requiredLpTokens should correctly calculate the amount of LP to remove - * in that the strategy receives enough WETH on balanced removal - */ - uint256[2] memory _minWithdrawalAmounts = [uint256(0), uint256(0)]; - _minWithdrawalAmounts[ethCoinIndex] = _amount; - // slither-disable-next-line unused-return - curvePool.remove_liquidity(requiredLpTokens, _minWithdrawalAmounts); - - // Burn all the removed OETH and any that was left in the strategy - uint256 oethToBurn = oeth.balanceOf(address(this)); - IVault(vaultAddress).burnForStrategy(oethToBurn); - - emit Withdrawal(address(oeth), address(lpToken), oethToBurn); - - // Transfer WETH to the recipient - weth.deposit{ value: _amount }(); - require( - weth.transfer(_recipient, _amount), - "Transfer of WETH not successful" - ); - } - - function calcTokenToBurn(uint256 _wethAmount) - internal - view - returns (uint256 lpToBurn) - { - /* The rate between coins in the pool determines the rate at which pool returns - * tokens when doing balanced removal (remove_liquidity call). And by knowing how much WETH - * we want we can determine how much of OETH we receive by removing liquidity. - * - * Because we are doing balanced removal we should be making profit when removing liquidity in a - * pool tilted to either side. - * - * Important: A downside is that the Strategist / Governor needs to be - * cognisant of not removing too much liquidity. And while the proposal to remove liquidity - * is being voted on the pool tilt might change so much that the proposal that has been valid while - * created is no longer valid. - */ - - uint256 poolWETHBalance = curvePool.balances(ethCoinIndex); - /* K is multiplied by 1e36 which is used for higher precision calculation of required - * pool LP tokens. Without it the end value can have rounding errors up to precision of - * 10 digits. This way we move the decimal point by 36 places when doing the calculation - * and again by 36 places when we are done with it. - */ - uint256 k = (1e36 * lpToken.totalSupply()) / poolWETHBalance; - // prettier-ignore - // slither-disable-next-line divide-before-multiply - uint256 diff = (_wethAmount + 1) * k; - lpToBurn = diff / 1e36; - } - - /** - * @notice Remove all ETH and OETH from the Curve pool, burn the OETH, - * convert the ETH to WETH and transfer to the Vault contract. - */ - function withdrawAll() external override onlyVaultOrGovernor nonReentrant { - uint256 gaugeTokens = cvxRewardStaker.balanceOf(address(this)); - _lpWithdraw(gaugeTokens); - - // Withdraws are proportional to assets held by 3Pool - uint256[2] memory minWithdrawAmounts = [uint256(0), uint256(0)]; - - // Remove liquidity - // slither-disable-next-line unused-return - curvePool.remove_liquidity( - lpToken.balanceOf(address(this)), - minWithdrawAmounts - ); - - // Burn all OETH - uint256 oethToBurn = oeth.balanceOf(address(this)); - IVault(vaultAddress).burnForStrategy(oethToBurn); - - // Get the strategy contract's ether balance. - // This includes all that was removed from the Curve pool and - // any ether that was sitting in the strategy contract before the removal. - uint256 ethBalance = address(this).balance; - // Convert all the strategy contract's ether to WETH and transfer to the vault. - weth.deposit{ value: ethBalance }(); - require( - weth.transfer(vaultAddress, ethBalance), - "Transfer of WETH not successful" - ); - - emit Withdrawal(address(weth), address(lpToken), ethBalance); - emit Withdrawal(address(oeth), address(lpToken), oethToBurn); - } - - /*************************************** - Curve pool Rebalancing - ****************************************/ - - /** - * @notice Mint OTokens and one-sided add to the Curve pool. - * This is used when the Curve pool does not have enough OTokens and too many ETH. - * The OToken/Asset, eg OETH/ETH, price with increase. - * The amount of assets in the vault is unchanged. - * The total supply of OTokens is increased. - * The asset value of the strategy and vault is increased. - * @param _oTokens The amount of OTokens to be minted and added to the pool. - */ - function mintAndAddOTokens(uint256 _oTokens) - external - onlyStrategist - nonReentrant - improvePoolBalance - { - IVault(vaultAddress).mintForStrategy(_oTokens); - - uint256[2] memory amounts = [uint256(0), uint256(0)]; - amounts[oethCoinIndex] = _oTokens; - - // Convert OETH to Curve pool LP tokens - uint256 valueInLpTokens = (_oTokens).divPrecisely( - curvePool.get_virtual_price() - ); - // Apply slippage to LP tokens - uint256 minMintAmount = valueInLpTokens.mulTruncate( - uint256(1e18) - MAX_SLIPPAGE - ); - - // Add the minted OTokens to the Curve pool - uint256 lpDeposited = curvePool.add_liquidity(amounts, minMintAmount); - - // Deposit the Curve pool LP tokens to the Convex rewards pool - require( - IConvexDeposits(cvxDepositorAddress).deposit( - cvxDepositorPTokenId, - lpDeposited, - true // Deposit with staking - ), - "Failed to Deposit LP to Convex" - ); - - emit Deposit(address(oeth), address(lpToken), _oTokens); - } - - /** - * @notice One-sided remove of OTokens from the Curve pool which are then burned. - * This is used when the Curve pool has too many OTokens and not enough ETH. - * The amount of assets in the vault is unchanged. - * The total supply of OTokens is reduced. - * The asset value of the strategy and vault is reduced. - * @param _lpTokens The amount of Curve pool LP tokens to be burned for OTokens. - */ - function removeAndBurnOTokens(uint256 _lpTokens) - external - onlyStrategist - nonReentrant - improvePoolBalance - { - // Withdraw Curve pool LP tokens from Convex and remove OTokens from the Curve pool - uint256 oethToBurn = _withdrawAndRemoveFromPool( - _lpTokens, - oethCoinIndex - ); - - // The vault burns the OTokens from this strategy - IVault(vaultAddress).burnForStrategy(oethToBurn); - - emit Withdrawal(address(oeth), address(lpToken), oethToBurn); - } - - /** - * @notice One-sided remove of ETH from the Curve pool, convert to WETH - * and transfer to the vault. - * This is used when the Curve pool does not have enough OTokens and too many ETH. - * The OToken/Asset, eg OETH/ETH, price with decrease. - * The amount of assets in the vault increases. - * The total supply of OTokens does not change. - * The asset value of the strategy reduces. - * The asset value of the vault should be close to the same. - * @param _lpTokens The amount of Curve pool LP tokens to be burned for ETH. - * @dev Curve pool LP tokens is used rather than WETH assets as Curve does not - * have a way to accurately calculate the amount of LP tokens for a required - * amount of ETH. Curve's `calc_token_amount` functioun does not include fees. - * A 3rd party libary can be used that takes into account the fees, but this - * is a gas intensive process. It's easier for the trusted strategist to - * caclulate the amount of Curve pool LP tokens required off-chain. - */ - function removeOnlyAssets(uint256 _lpTokens) - external - onlyStrategist - nonReentrant - improvePoolBalance - { - // Withdraw Curve pool LP tokens from Convex and remove ETH from the Curve pool - uint256 ethAmount = _withdrawAndRemoveFromPool(_lpTokens, ethCoinIndex); - - // Convert ETH to WETH and transfer to the vault - weth.deposit{ value: ethAmount }(); - require( - weth.transfer(vaultAddress, ethAmount), - "Transfer of WETH not successful" - ); - - emit Withdrawal(address(weth), address(lpToken), ethAmount); - } - - /** - * @dev Remove Curve pool LP tokens from the Convex pool and - * do a one-sided remove of ETH or OETH from the Curve pool. - * @param _lpTokens The amount of Curve pool LP tokens to be removed from the Convex pool. - * @param coinIndex The index of the coin to be removed from the Curve pool. 0 = ETH, 1 = OETH. - * @return coinsRemoved The amount of ETH or OETH removed from the Curve pool. - */ - function _withdrawAndRemoveFromPool(uint256 _lpTokens, uint128 coinIndex) - internal - returns (uint256 coinsRemoved) - { - // Withdraw Curve pool LP tokens from Convex pool - _lpWithdraw(_lpTokens); - - // Convert Curve pool LP tokens to ETH value - uint256 valueInEth = _lpTokens.mulTruncate( - curvePool.get_virtual_price() - ); - // Apply slippage to ETH value - uint256 minAmount = valueInEth.mulTruncate( - uint256(1e18) - MAX_SLIPPAGE - ); - - // Remove just the ETH from the Curve pool - coinsRemoved = curvePool.remove_liquidity_one_coin( - _lpTokens, - int128(coinIndex), - minAmount, - address(this) - ); - } - - /*************************************** - Assets and Rewards - ****************************************/ - - /** - * @notice Collect accumulated CRV and CVX rewards and send to the Harvester. - */ - function collectRewardTokens() - external - override - onlyHarvester - nonReentrant - { - // Collect CRV and CVX - cvxRewardStaker.getReward(); - _collectRewardTokens(); - } - - function _lpWithdraw(uint256 _wethAmount) internal { - // withdraw and unwrap with claim takes back the lpTokens - // and also collects the rewards for deposit - cvxRewardStaker.withdrawAndUnwrap(_wethAmount, true); - } - - /** - * @notice Get the total asset value held in the platform - * @param _asset Address of the asset - * @return balance Total value of the asset in the platform - */ - function checkBalance(address _asset) - public - view - override - returns (uint256 balance) - { - require(_asset == address(weth), "Unsupported asset"); - - // Eth balance needed here for the balance check that happens from vault during depositing. - balance = address(this).balance; - uint256 lpTokens = cvxRewardStaker.balanceOf(address(this)); - if (lpTokens > 0) { - balance += (lpTokens * curvePool.get_virtual_price()) / 1e18; - } - } - - /** - * @notice Returns bool indicating whether asset is supported by strategy - * @param _asset Address of the asset - */ - function supportsAsset(address _asset) public view override returns (bool) { - return _asset == address(weth); - } - - /*************************************** - Approvals - ****************************************/ - - /** - * @notice Approve the spending of all assets by their corresponding pool tokens, - * if for some reason is it necessary. - */ - function safeApproveAllTokens() - external - override - onlyGovernor - nonReentrant - { - _approveBase(); - } - - /** - * @notice Accept unwrapped WETH - */ - receive() external payable {} - - /** - * @dev Since we are unwrapping WETH before depositing it to Curve - * there is no need to set an approval for WETH on the Curve - * pool - * @param _asset Address of the asset - * @param _pToken Address of the Curve LP token - */ - // solhint-disable-next-line no-unused-vars - function _abstractSetPToken(address _asset, address _pToken) - internal - override - {} - - function _approveBase() internal { - // Approve Curve pool for OETH (required for adding liquidity) - // No approval is needed for ETH - // slither-disable-next-line unused-return - oeth.approve(platformAddress, type(uint256).max); - - // Approve Convex deposit contract to transfer Curve pool LP tokens - // This is needed for deposits if Curve pool LP tokens into the Convex rewards pool - // slither-disable-next-line unused-return - lpToken.approve(cvxDepositorAddress, type(uint256).max); - } - - /** - * @dev Returns the largest of two numbers int256 version - */ - function _max(int256 a, int256 b) internal pure returns (int256) { - return a >= b ? a : b; - } -} diff --git a/contracts/contracts/strategies/ConvexGeneralizedMetaStrategy.sol b/contracts/contracts/strategies/ConvexGeneralizedMetaStrategy.sol deleted file mode 100644 index ee5916842b..0000000000 --- a/contracts/contracts/strategies/ConvexGeneralizedMetaStrategy.sol +++ /dev/null @@ -1,134 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -/** - * @title Curve Convex Strategy - * @notice Investment strategy for investing stablecoins via Curve 3Pool - * @author Origin Protocol Inc - */ -import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import "@openzeppelin/contracts/utils/Strings.sol"; - -import { IRewardStaking } from "./IRewardStaking.sol"; -import { IConvexDeposits } from "./IConvexDeposits.sol"; -import { ICurvePool } from "./ICurvePool.sol"; -import { IERC20, InitializableAbstractStrategy } from "./AbstractCurveStrategy.sol"; -import { AbstractConvexMetaStrategy } from "./AbstractConvexMetaStrategy.sol"; -import { StableMath } from "../utils/StableMath.sol"; - -contract ConvexGeneralizedMetaStrategy is AbstractConvexMetaStrategy { - using StableMath for uint256; - using SafeERC20 for IERC20; - - constructor(BaseStrategyConfig memory _stratConfig) - InitializableAbstractStrategy(_stratConfig) - {} - - /* Take 3pool LP and deposit it to metapool. Take the LP from metapool - * and deposit them to Convex. - */ - function _lpDepositAll() internal override { - IERC20 threePoolLp = IERC20(pTokenAddress); - ICurvePool curvePool = ICurvePool(platformAddress); - - uint256 threePoolLpBalance = threePoolLp.balanceOf(address(this)); - uint256 curve3PoolVirtualPrice = curvePool.get_virtual_price(); - uint256 threePoolLpDollarValue = threePoolLpBalance.mulTruncate( - curve3PoolVirtualPrice - ); - - uint256[2] memory _amounts = [0, threePoolLpBalance]; - - uint256 metapoolVirtualPrice = metapool.get_virtual_price(); - /** - * First convert all the deposited tokens to dollar values, - * then divide by virtual price to convert to metapool LP tokens - * and apply the max slippage - */ - uint256 minReceived = threePoolLpDollarValue - .divPrecisely(metapoolVirtualPrice) - .mulTruncate(uint256(1e18) - MAX_SLIPPAGE); - - uint256 metapoolLp = metapool.add_liquidity(_amounts, minReceived); - - bool success = IConvexDeposits(cvxDepositorAddress).deposit( - cvxDepositorPTokenId, - metapoolLp, - true // Deposit with staking - ); - - require(success, "Failed to deposit to Convex"); - } - - /** - * Withdraw the specified amount of tokens from the gauge. And use all the resulting tokens - * to remove liquidity from metapool - * @param num3CrvTokens Number of Convex 3pool LP tokens to withdraw from metapool - */ - function _lpWithdraw(uint256 num3CrvTokens) internal override { - uint256 gaugeTokens = IRewardStaking(cvxRewardStakerAddress).balanceOf( - address(this) - ); - - uint256 requiredMetapoolLpTokens = _calcCurveMetaTokenAmount( - crvCoinIndex, - num3CrvTokens - ); - - require( - requiredMetapoolLpTokens <= gaugeTokens, - string( - bytes.concat( - bytes("Attempting to withdraw "), - bytes(Strings.toString(requiredMetapoolLpTokens)), - bytes(", metapoolLP but only "), - bytes(Strings.toString(gaugeTokens)), - bytes(" available.") - ) - ) - ); - - // withdraw and unwrap with claim takes back the lpTokens and also collects the rewards for deposit - IRewardStaking(cvxRewardStakerAddress).withdrawAndUnwrap( - requiredMetapoolLpTokens, - true - ); - - if (requiredMetapoolLpTokens > 0) { - // slither-disable-next-line unused-return - metapool.remove_liquidity_one_coin( - requiredMetapoolLpTokens, - int128(crvCoinIndex), - num3CrvTokens - ); - } - } - - function _lpWithdrawAll() internal override { - uint256 gaugeTokens = IRewardStaking(cvxRewardStakerAddress).balanceOf( - address(this) - ); - IRewardStaking(cvxRewardStakerAddress).withdrawAndUnwrap( - gaugeTokens, - true - ); - - if (gaugeTokens > 0) { - uint256 burnDollarAmount = gaugeTokens.mulTruncate( - metapool.get_virtual_price() - ); - uint256 curve3PoolExpected = burnDollarAmount.divPrecisely( - ICurvePool(platformAddress).get_virtual_price() - ); - - // Always withdraw all of the available metapool LP tokens (similar to how we always deposit all) - // slither-disable-next-line unused-return - metapool.remove_liquidity_one_coin( - gaugeTokens, - int128(crvCoinIndex), - curve3PoolExpected - - curve3PoolExpected.mulTruncate(maxWithdrawalSlippage) - ); - } - } -} diff --git a/contracts/contracts/strategies/ConvexOUSDMetaStrategy.sol b/contracts/contracts/strategies/ConvexOUSDMetaStrategy.sol deleted file mode 100644 index afc86f1086..0000000000 --- a/contracts/contracts/strategies/ConvexOUSDMetaStrategy.sol +++ /dev/null @@ -1,190 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -/** - * @title Curve Convex Strategy - * @notice Investment strategy for investing stablecoins via Curve 3Pool - * @author Origin Protocol Inc - */ -import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import "@openzeppelin/contracts/utils/Strings.sol"; -import "@openzeppelin/contracts/utils/math/Math.sol"; - -import { IRewardStaking } from "./IRewardStaking.sol"; -import { IConvexDeposits } from "./IConvexDeposits.sol"; -import { ICurvePool } from "./ICurvePool.sol"; -import { IERC20, InitializableAbstractStrategy } from "./AbstractCurveStrategy.sol"; -import { AbstractConvexMetaStrategy } from "./AbstractConvexMetaStrategy.sol"; -import { StableMath } from "../utils/StableMath.sol"; -import { IVault } from "../interfaces/IVault.sol"; - -contract ConvexOUSDMetaStrategy is AbstractConvexMetaStrategy { - using StableMath for uint256; - using SafeERC20 for IERC20; - - constructor(BaseStrategyConfig memory _stratConfig) - InitializableAbstractStrategy(_stratConfig) - {} - - /* Take 3pool LP and mint the corresponding amount of ousd. Deposit and stake that to - * ousd Curve Metapool. Take the LP from metapool and deposit them to Convex. - */ - function _lpDepositAll() internal override { - ICurvePool curvePool = ICurvePool(platformAddress); - - uint256 threePoolLpBalance = IERC20(pTokenAddress).balanceOf( - address(this) - ); - uint256 curve3PoolVirtualPrice = curvePool.get_virtual_price(); - uint256 threePoolLpDollarValue = threePoolLpBalance.mulTruncate( - curve3PoolVirtualPrice - ); - - // safe to cast since min value is at least 0 - uint256 ousdToAdd = uint256( - _max( - 0, - int256( - metapool.balances(crvCoinIndex).mulTruncate( - curve3PoolVirtualPrice - ) - ) - - int256(metapool.balances(mainCoinIndex)) + - int256(threePoolLpDollarValue) - ) - ); - - /* Add so much OUSD so that the pool ends up being balanced. And at minimum - * add twice as much OUSD as 3poolLP and at maximum at twice as - * much OUSD. - */ - ousdToAdd = Math.max(ousdToAdd, threePoolLpDollarValue); - ousdToAdd = Math.min(ousdToAdd, threePoolLpDollarValue * 2); - - /* Mint OUSD with a strategy that attempts to contribute to stability of OUSD metapool. Try - * to mint so much OUSD that after deployment of liquidity pool ends up being balanced. - * - * To manage unpredictability minimal OUSD minted will always be at least equal or greater - * to stablecoin(DAI, USDC, USDT) amount of 3CRVLP deployed. And never larger than twice the - * stablecoin amount of 3CRVLP deployed even if it would have a further beneficial effect - * on pool stability. - */ - if (ousdToAdd > 0) { - IVault(vaultAddress).mintForStrategy(ousdToAdd); - } - - uint256[2] memory _amounts = [ousdToAdd, threePoolLpBalance]; - - uint256 metapoolVirtualPrice = metapool.get_virtual_price(); - /** - * First convert all the deposited tokens to dollar values, - * then divide by virtual price to convert to metapool LP tokens - * and apply the max slippage - */ - uint256 minReceived = (ousdToAdd + threePoolLpDollarValue) - .divPrecisely(metapoolVirtualPrice) - .mulTruncate(uint256(1e18) - MAX_SLIPPAGE); - - uint256 metapoolLp = metapool.add_liquidity(_amounts, minReceived); - - bool success = IConvexDeposits(cvxDepositorAddress).deposit( - cvxDepositorPTokenId, - metapoolLp, - true // Deposit with staking - ); - - require(success, "Failed to deposit to Convex"); - } - - /** - * Withdraw the specified amount of tokens from the gauge. And use all the resulting tokens - * to remove liquidity from metapool - * @param num3CrvTokens Number of 3CRV tokens to withdraw from metapool - */ - function _lpWithdraw(uint256 num3CrvTokens) internal override { - ICurvePool curvePool = ICurvePool(platformAddress); - /* The rate between coins in the metapool determines the rate at which metapool returns - * tokens when doing balanced removal (remove_liquidity call). And by knowing how much 3crvLp - * we want we can determine how much of OUSD we receive by removing liquidity. - * - * Because we are doing balanced removal we should be making profit when removing liquidity in a - * pool tilted to either side. - * - * Important: A downside is that the Strategist / Governor needs to be - * cognisant of not removing too much liquidity. And while the proposal to remove liquidity - * is being voted on the pool tilt might change so much that the proposal that has been valid while - * created is no longer valid. - */ - - uint256 crvPoolBalance = metapool.balances(crvCoinIndex); - /* K is multiplied by 1e36 which is used for higher precision calculation of required - * metapool LP tokens. Without it the end value can have rounding errors up to precision of - * 10 digits. This way we move the decimal point by 36 places when doing the calculation - * and again by 36 places when we are done with it. - */ - uint256 k = (1e36 * metapoolLPToken.totalSupply()) / crvPoolBalance; - // simplifying below to: `uint256 diff = (num3CrvTokens - 1) * k` causes loss of precision - // prettier-ignore - // slither-disable-next-line divide-before-multiply - uint256 diff = crvPoolBalance * k - - (crvPoolBalance - num3CrvTokens - 1) * k; - uint256 lpToBurn = diff / 1e36; - - uint256 gaugeTokens = IRewardStaking(cvxRewardStakerAddress).balanceOf( - address(this) - ); - - require( - lpToBurn <= gaugeTokens, - string( - bytes.concat( - bytes("Attempting to withdraw "), - bytes(Strings.toString(lpToBurn)), - bytes(", metapoolLP but only "), - bytes(Strings.toString(gaugeTokens)), - bytes(" available.") - ) - ) - ); - - // withdraw and unwrap with claim takes back the lpTokens and also collects the rewards for deposit - IRewardStaking(cvxRewardStakerAddress).withdrawAndUnwrap( - lpToBurn, - true - ); - - // calculate the min amount of OUSD expected for the specified amount of LP tokens - uint256 minOUSDAmount = lpToBurn.mulTruncate( - metapool.get_virtual_price() - ) - - num3CrvTokens.mulTruncate(curvePool.get_virtual_price()) - - 1; - - // withdraw the liquidity from metapool - uint256[2] memory _removedAmounts = metapool.remove_liquidity( - lpToBurn, - [minOUSDAmount, num3CrvTokens] - ); - - IVault(vaultAddress).burnForStrategy(_removedAmounts[mainCoinIndex]); - } - - function _lpWithdrawAll() internal override { - IERC20 metapoolErc20 = IERC20(address(metapool)); - uint256 gaugeTokens = IRewardStaking(cvxRewardStakerAddress).balanceOf( - address(this) - ); - IRewardStaking(cvxRewardStakerAddress).withdrawAndUnwrap( - gaugeTokens, - true - ); - - uint256[2] memory _minAmounts = [uint256(0), uint256(0)]; - uint256[2] memory _removedAmounts = metapool.remove_liquidity( - metapoolErc20.balanceOf(address(this)), - _minAmounts - ); - - IVault(vaultAddress).burnForStrategy(_removedAmounts[mainCoinIndex]); - } -} diff --git a/contracts/contracts/strategies/ConvexStrategy.sol b/contracts/contracts/strategies/ConvexStrategy.sol deleted file mode 100644 index 01398dc9e3..0000000000 --- a/contracts/contracts/strategies/ConvexStrategy.sol +++ /dev/null @@ -1,165 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -/** - * @title Curve Convex Strategy - * @notice Investment strategy for investing stablecoins via Curve 3Pool - * @author Origin Protocol Inc - */ -import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; - -import { ICurvePool } from "./ICurvePool.sol"; -import { IRewardStaking } from "./IRewardStaking.sol"; -import { IConvexDeposits } from "./IConvexDeposits.sol"; -import { IERC20, AbstractCurveStrategy, InitializableAbstractStrategy } from "./AbstractCurveStrategy.sol"; -import { StableMath } from "../utils/StableMath.sol"; -import { Helpers } from "../utils/Helpers.sol"; - -/* - * IMPORTANT(!) If ConvexStrategy needs to be re-deployed, it requires new - * proxy contract with fresh storage slots. Changes in `AbstractCurveStrategy` - * storage slots would break existing implementation. - * - * Remove this notice if ConvexStrategy is re-deployed - */ -contract ConvexStrategy is AbstractCurveStrategy { - using StableMath for uint256; - using SafeERC20 for IERC20; - - address internal cvxDepositorAddress; - address internal cvxRewardStakerAddress; - // slither-disable-next-line constable-states - address private _deprecated_cvxRewardTokenAddress; - uint256 internal cvxDepositorPTokenId; - - constructor(BaseStrategyConfig memory _stratConfig) - InitializableAbstractStrategy(_stratConfig) - {} - - /** - * Initializer for setting up strategy internal state. This overrides the - * InitializableAbstractStrategy initializer as Curve strategies don't fit - * well within that abstraction. - * @param _rewardTokenAddresses Address of CRV & CVX - * @param _assets Addresses of supported assets. MUST be passed in the same - * order as returned by coins on the pool contract, i.e. - * DAI, USDC, USDT - * @param _pTokens Platform Token corresponding addresses - * @param _cvxDepositorAddress Address of the Convex depositor(AKA booster) for this pool - * @param _cvxRewardStakerAddress Address of the CVX rewards staker - * @param _cvxDepositorPTokenId Pid of the pool referred to by Depositor and staker - */ - function initialize( - address[] calldata _rewardTokenAddresses, // CRV + CVX - address[] calldata _assets, - address[] calldata _pTokens, - address _cvxDepositorAddress, - address _cvxRewardStakerAddress, - uint256 _cvxDepositorPTokenId - ) external onlyGovernor initializer { - require(_assets.length == 3, "Must have exactly three assets"); - // Should be set prior to abstract initialize call otherwise - // abstractSetPToken calls will fail - cvxDepositorAddress = _cvxDepositorAddress; - cvxRewardStakerAddress = _cvxRewardStakerAddress; - cvxDepositorPTokenId = _cvxDepositorPTokenId; - pTokenAddress = _pTokens[0]; - - InitializableAbstractStrategy._initialize( - _rewardTokenAddresses, - _assets, - _pTokens - ); - _approveBase(); - } - - function _lpDepositAll() internal override { - IERC20 pToken = IERC20(pTokenAddress); - // Deposit with staking - bool success = IConvexDeposits(cvxDepositorAddress).deposit( - cvxDepositorPTokenId, - pToken.balanceOf(address(this)), - true - ); - require(success, "Failed to deposit to Convex"); - } - - function _lpWithdraw(uint256 numCrvTokens) internal override { - uint256 gaugePTokens = IRewardStaking(cvxRewardStakerAddress).balanceOf( - address(this) - ); - - // Not enough in this contract or in the Gauge, can't proceed - require(numCrvTokens > gaugePTokens, "Insufficient 3CRV balance"); - - // withdraw and unwrap with claim takes back the lpTokens and also collects the rewards to this - IRewardStaking(cvxRewardStakerAddress).withdrawAndUnwrap( - numCrvTokens, - true - ); - } - - function _lpWithdrawAll() internal override { - // withdraw and unwrap with claim takes back the lpTokens and also collects the rewards to this - IRewardStaking(cvxRewardStakerAddress).withdrawAndUnwrap( - IRewardStaking(cvxRewardStakerAddress).balanceOf(address(this)), - true - ); - } - - function _approveBase() internal override { - IERC20 pToken = IERC20(pTokenAddress); - // 3Pool for LP token (required for removing liquidity) - pToken.safeApprove(platformAddress, 0); - pToken.safeApprove(platformAddress, type(uint256).max); - // Gauge for LP token - pToken.safeApprove(cvxDepositorAddress, 0); - pToken.safeApprove(cvxDepositorAddress, type(uint256).max); - } - - /** - * @dev Get the total asset value held in the platform - * @param _asset Address of the asset - * @return balance Total value of the asset in the platform - */ - function checkBalance(address _asset) - public - view - override - returns (uint256 balance) - { - require(assetToPToken[_asset] != address(0), "Unsupported asset"); - // LP tokens in this contract. This should generally be nothing as we - // should always stake the full balance in the Gauge, but include for - // safety - uint256 contractPTokens = IERC20(pTokenAddress).balanceOf( - address(this) - ); - uint256 gaugePTokens = IRewardStaking(cvxRewardStakerAddress).balanceOf( - address(this) - ); - uint256 totalPTokens = contractPTokens + gaugePTokens; - - ICurvePool curvePool = ICurvePool(platformAddress); - if (totalPTokens > 0) { - uint256 virtual_price = curvePool.get_virtual_price(); - uint256 value = (totalPTokens * virtual_price) / 1e18; - uint256 assetDecimals = Helpers.getDecimals(_asset); - balance = value.scaleBy(assetDecimals, 18) / 3; - } - } - - /** - * @dev Collect accumulated CRV and CVX and send to Vault. - */ - function collectRewardTokens() - external - override - onlyHarvester - nonReentrant - { - // Collect CRV and CVX - IRewardStaking(cvxRewardStakerAddress).getReward(); - _collectRewardTokens(); - } -} diff --git a/contracts/contracts/strategies/Generalized4626USDTStrategy.sol b/contracts/contracts/strategies/Generalized4626USDTStrategy.sol deleted file mode 100644 index d6d576a542..0000000000 --- a/contracts/contracts/strategies/Generalized4626USDTStrategy.sol +++ /dev/null @@ -1,35 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -interface IUSDT { - // Tether's approve does not return a bool like standard IERC20 contracts - // slither-disable-next-line erc20-interface - function approve(address _spender, uint256 _value) external; -} - -/** - * @title Generalized 4626 Strategy when asset is Tether USD (USDT) - * @notice Investment strategy for ERC-4626 Tokenized Vaults for the USDT asset. - * @author Origin Protocol Inc - */ -import { Generalized4626Strategy } from "./Generalized4626Strategy.sol"; - -contract Generalized4626USDTStrategy is Generalized4626Strategy { - /** - * @param _baseConfig Base strategy config with platformAddress (ERC-4626 Vault contract), eg sfrxETH or sDAI, - * and vaultAddress (OToken Vault contract), eg VaultProxy or OETHVaultProxy - * @param _assetToken Address of the ERC-4626 asset token. eg frxETH or DAI - */ - constructor(BaseStrategyConfig memory _baseConfig, address _assetToken) - Generalized4626Strategy(_baseConfig, _assetToken) - {} - - /// @dev Override for Tether as USDT does not return a bool on approve. - /// Using assetToken.approve will fail as it expects a bool return value - function _approveBase() internal virtual override { - // Approval the asset to be transferred to the ERC-4626 Tokenized Vault. - // Used by the ERC-4626 deposit() and mint() functions - // slither-disable-next-line unused-return - IUSDT(address(assetToken)).approve(platformAddress, type(uint256).max); - } -} diff --git a/contracts/contracts/strategies/ICompound.sol b/contracts/contracts/strategies/ICompound.sol deleted file mode 100644 index 160e259b7f..0000000000 --- a/contracts/contracts/strategies/ICompound.sol +++ /dev/null @@ -1,67 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -/** - * @dev Compound C Token interface - * Documentation: https://compound.finance/developers/ctokens - */ -interface ICERC20 { - /** - * @notice The mint function transfers an asset into the protocol, which begins accumulating - * interest based on the current Supply Rate for the asset. The user receives a quantity of - * cTokens equal to the underlying tokens supplied, divided by the current Exchange Rate. - * @param mintAmount The amount of the asset to be supplied, in units of the underlying asset. - * @return 0 on success, otherwise an Error codes - */ - function mint(uint256 mintAmount) external returns (uint256); - - /** - * @notice Sender redeems cTokens in exchange for the underlying asset - * @dev Accrues interest whether or not the operation succeeds, unless reverted - * @param redeemTokens The number of cTokens to redeem into underlying - * @return uint 0=success, otherwise an error code. - */ - function redeem(uint256 redeemTokens) external returns (uint256); - - /** - * @notice The redeem underlying function converts cTokens into a specified quantity of the underlying - * asset, and returns them to the user. The amount of cTokens redeemed is equal to the quantity of - * underlying tokens received, divided by the current Exchange Rate. The amount redeemed must be less - * than the user's Account Liquidity and the market's available liquidity. - * @param redeemAmount The amount of underlying to be redeemed. - * @return 0 on success, otherwise an error code. - */ - function redeemUnderlying(uint256 redeemAmount) external returns (uint256); - - /** - * @notice The user's underlying balance, representing their assets in the protocol, is equal to - * the user's cToken balance multiplied by the Exchange Rate. - * @param owner The account to get the underlying balance of. - * @return The amount of underlying currently owned by the account. - */ - function balanceOfUnderlying(address owner) external returns (uint256); - - /** - * @notice Calculates the exchange rate from the underlying to the CToken - * @dev This function does not accrue interest before calculating the exchange rate - * @return Calculated exchange rate scaled by 1e18 - */ - function exchangeRateStored() external view returns (uint256); - - /** - * @notice Get the token balance of the `owner` - * @param owner The address of the account to query - * @return The number of tokens owned by `owner` - */ - function balanceOf(address owner) external view returns (uint256); - - /** - * @notice Get the supply rate per block for supplying the token to Compound. - */ - function supplyRatePerBlock() external view returns (uint256); - - /** - * @notice Address of the Compound Comptroller. - */ - function comptroller() external view returns (address); -} diff --git a/contracts/contracts/strategies/ICurveMetaPool.sol b/contracts/contracts/strategies/ICurveMetaPool.sol deleted file mode 100644 index 4a782c7376..0000000000 --- a/contracts/contracts/strategies/ICurveMetaPool.sol +++ /dev/null @@ -1,56 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.4; - -interface ICurveMetaPool { - function add_liquidity(uint256[2] calldata amounts, uint256 min_mint_amount) - external - returns (uint256); - - function get_virtual_price() external view returns (uint256); - - function remove_liquidity(uint256 _amount, uint256[2] calldata min_amounts) - external - returns (uint256[2] calldata); - - function remove_liquidity_one_coin( - uint256 _token_amount, - int128 i, - uint256 min_amount - ) external returns (uint256); - - function remove_liquidity_imbalance( - uint256[2] calldata amounts, - uint256 max_burn_amount - ) external returns (uint256); - - function calc_withdraw_one_coin(uint256 _token_amount, int128 i) - external - view - returns (uint256); - - function balances(uint256 i) external view returns (uint256); - - function calc_token_amount(uint256[2] calldata amounts, bool deposit) - external - view - returns (uint256); - - function base_pool() external view returns (address); - - function fee() external view returns (uint256); - - function coins(uint256 i) external view returns (address); - - function exchange( - int128 i, - int128 j, - uint256 dx, - uint256 min_dy - ) external returns (uint256); - - function get_dy( - int128 i, - int128 j, - uint256 dx - ) external view returns (uint256); -} diff --git a/contracts/contracts/strategies/MorphoAaveStrategy.sol b/contracts/contracts/strategies/MorphoAaveStrategy.sol deleted file mode 100644 index 9fe6ab0a4b..0000000000 --- a/contracts/contracts/strategies/MorphoAaveStrategy.sol +++ /dev/null @@ -1,235 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -/** - * @title OUSD Morpho Aave Strategy - * @notice Investment strategy for investing stablecoins via Morpho (Aave) - * @author Origin Protocol Inc - */ -import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import { IERC20, InitializableAbstractStrategy } from "../utils/InitializableAbstractStrategy.sol"; -import { IMorpho } from "../interfaces/morpho/IMorpho.sol"; -import { ILens } from "../interfaces/morpho/ILens.sol"; -import { StableMath } from "../utils/StableMath.sol"; - -contract MorphoAaveStrategy is InitializableAbstractStrategy { - address public constant MORPHO = 0x777777c9898D384F785Ee44Acfe945efDFf5f3E0; - address public constant LENS = 0x507fA343d0A90786d86C7cd885f5C49263A91FF4; - - using SafeERC20 for IERC20; - using StableMath for uint256; - - constructor(BaseStrategyConfig memory _stratConfig) - InitializableAbstractStrategy(_stratConfig) - {} - - /** - * @dev Initialize function, to set up initial internal state - * @param _rewardTokenAddresses Address of reward token for platform - * @param _assets Addresses of initial supported assets - * @param _pTokens Platform Token corresponding addresses - */ - function initialize( - address[] calldata _rewardTokenAddresses, - address[] calldata _assets, - address[] calldata _pTokens - ) external onlyGovernor initializer { - InitializableAbstractStrategy._initialize( - _rewardTokenAddresses, - _assets, - _pTokens - ); - } - - /** - * @dev Approve the spending of all assets by main Morpho contract, - * if for some reason is it necessary. - */ - function safeApproveAllTokens() - external - override - onlyGovernor - nonReentrant - { - uint256 assetCount = assetsMapped.length; - for (uint256 i = 0; i < assetCount; i++) { - address asset = assetsMapped[i]; - - // Safe approval - IERC20(asset).safeApprove(MORPHO, 0); - IERC20(asset).safeApprove(MORPHO, type(uint256).max); - } - } - - /** - * @dev Internal method to respond to the addition of new asset - * We need to approve and allow Morpho to move them - * @param _asset Address of the asset to approve - * @param _pToken The pToken for the approval - */ - // solhint-disable-next-line no-unused-vars - function _abstractSetPToken(address _asset, address _pToken) - internal - override - { - IERC20(_asset).safeApprove(MORPHO, 0); - IERC20(_asset).safeApprove(MORPHO, type(uint256).max); - } - - /** - * @dev Collect accumulated rewards and send them to Harvester. - */ - function collectRewardTokens() - external - override - onlyHarvester - nonReentrant - { - // Morpho Aave-v2 doesn't distribute reward tokens - // solhint-disable-next-line max-line-length - // Ref: https://developers.morpho.xyz/interact-with-morpho/get-started/interact-with-morpho/claim-rewards#morpho-aave-v2 - } - - /** - * @dev Get the amount of rewards pending to be collected from the protocol - */ - function getPendingRewards() external view returns (uint256 balance) { - // Morpho Aave-v2 doesn't distribute reward tokens - // solhint-disable-next-line max-line-length - // Ref: https://developers.morpho.xyz/interact-with-morpho/get-started/interact-with-morpho/claim-rewards#morpho-aave-v2 - return 0; - } - - /** - * @dev Deposit asset into Morpho - * @param _asset Address of asset to deposit - * @param _amount Amount of asset to deposit - */ - function deposit(address _asset, uint256 _amount) - external - override - onlyVault - nonReentrant - { - _deposit(_asset, _amount); - } - - /** - * @dev Deposit asset into Morpho - * @param _asset Address of asset to deposit - * @param _amount Amount of asset to deposit - */ - function _deposit(address _asset, uint256 _amount) internal { - require(_amount > 0, "Must deposit something"); - - address pToken = address(_getPTokenFor(_asset)); - - IMorpho(MORPHO).supply( - pToken, - address(this), // the address of the user you want to supply on behalf of - _amount - ); - emit Deposit(_asset, pToken, _amount); - } - - /** - * @dev Deposit the entire balance of any supported asset into Morpho - */ - function depositAll() external override onlyVault nonReentrant { - for (uint256 i = 0; i < assetsMapped.length; i++) { - uint256 balance = IERC20(assetsMapped[i]).balanceOf(address(this)); - if (balance > 0) { - _deposit(assetsMapped[i], balance); - } - } - } - - /** - * @dev Withdraw asset from Morpho - * @param _recipient Address to receive withdrawn asset - * @param _asset Address of asset to withdraw - * @param _amount Amount of asset to withdraw - */ - function withdraw( - address _recipient, - address _asset, - uint256 _amount - ) external override onlyVault nonReentrant { - _withdraw(_recipient, _asset, _amount); - } - - function _withdraw( - address _recipient, - address _asset, - uint256 _amount - ) internal { - require(_amount > 0, "Must withdraw something"); - require(_recipient != address(0), "Must specify recipient"); - - address pToken = address(_getPTokenFor(_asset)); - - IMorpho(MORPHO).withdraw(pToken, _amount); - emit Withdrawal(_asset, pToken, _amount); - IERC20(_asset).safeTransfer(_recipient, _amount); - } - - /** - * @dev Remove all assets from platform and send them to Vault contract. - */ - function withdrawAll() external override onlyVaultOrGovernor nonReentrant { - for (uint256 i = 0; i < assetsMapped.length; i++) { - uint256 balance = _checkBalance(assetsMapped[i]); - if (balance > 0) { - _withdraw(vaultAddress, assetsMapped[i], balance); - } - } - } - - /** - * @dev Return total value of an asset held in the platform - * @param _asset Address of the asset - * @return balance Total value of the asset in the platform - */ - function checkBalance(address _asset) - external - view - override - returns (uint256 balance) - { - return _checkBalance(_asset); - } - - function _checkBalance(address _asset) - internal - view - returns (uint256 balance) - { - address pToken = address(_getPTokenFor(_asset)); - - // Total value represented by decimal position of underlying token - (, , balance) = ILens(LENS).getCurrentSupplyBalanceInOf( - pToken, - address(this) - ); - } - - /** - * @dev Retuns bool indicating whether asset is supported by strategy - * @param _asset Address of the asset - */ - function supportsAsset(address _asset) public view override returns (bool) { - return assetToPToken[_asset] != address(0); - } - - /** - * @dev Get the pToken wrapped in the IERC20 interface for this asset. - * Fails if the pToken doesn't exist in our mappings. - * @param _asset Address of the asset - * @return pToken Corresponding pToken to this asset - */ - function _getPTokenFor(address _asset) internal view returns (IERC20) { - address pToken = assetToPToken[_asset]; - require(pToken != address(0), "pToken does not exist"); - return IERC20(pToken); - } -} diff --git a/contracts/contracts/strategies/MorphoCompoundStrategy.sol b/contracts/contracts/strategies/MorphoCompoundStrategy.sol deleted file mode 100644 index 32d7a1c325..0000000000 --- a/contracts/contracts/strategies/MorphoCompoundStrategy.sol +++ /dev/null @@ -1,251 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -/** - * @title OUSD Morpho Compound Strategy - * @notice Investment strategy for investing stablecoins via Morpho (Compound) - * @author Origin Protocol Inc - */ -import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import { IERC20, AbstractCompoundStrategy, InitializableAbstractStrategy } from "./AbstractCompoundStrategy.sol"; -import { IMorpho } from "../interfaces/morpho/IMorpho.sol"; -import { ILens } from "../interfaces/morpho/ILens.sol"; -import { StableMath } from "../utils/StableMath.sol"; -import "../utils/Helpers.sol"; - -contract MorphoCompoundStrategy is AbstractCompoundStrategy { - address public constant MORPHO = 0x8888882f8f843896699869179fB6E4f7e3B58888; - address public constant LENS = 0x930f1b46e1D081Ec1524efD95752bE3eCe51EF67; - using SafeERC20 for IERC20; - using StableMath for uint256; - - constructor(BaseStrategyConfig memory _stratConfig) - InitializableAbstractStrategy(_stratConfig) - {} - - /** - * @dev Initialize function, to set up initial internal state - * @param _rewardTokenAddresses Address of reward token for platform - * @param _assets Addresses of initial supported assets - * @param _pTokens Platform Token corresponding addresses - */ - function initialize( - address[] calldata _rewardTokenAddresses, - address[] calldata _assets, - address[] calldata _pTokens - ) external onlyGovernor initializer { - InitializableAbstractStrategy._initialize( - _rewardTokenAddresses, - _assets, - _pTokens - ); - } - - /** - * @dev Approve the spending of all assets by main Morpho contract, - * if for some reason is it necessary. - */ - function safeApproveAllTokens() - external - override - onlyGovernor - nonReentrant - { - uint256 assetCount = assetsMapped.length; - for (uint256 i = 0; i < assetCount; i++) { - address asset = assetsMapped[i]; - - // Safe approval - IERC20(asset).safeApprove(MORPHO, 0); - IERC20(asset).safeApprove(MORPHO, type(uint256).max); - } - } - - /** - * @dev Internal method to respond to the addition of new asset - * We need to approve and allow Morpho to move them - * @param _asset Address of the asset to approve - * @param _pToken The pToken for the approval - */ - // solhint-disable-next-line no-unused-vars - function _abstractSetPToken(address _asset, address _pToken) - internal - override - { - IERC20(_asset).safeApprove(MORPHO, 0); - IERC20(_asset).safeApprove(MORPHO, type(uint256).max); - } - - /** - * @dev Collect accumulated rewards and send them to Harvester. - */ - function collectRewardTokens() - external - override - onlyHarvester - nonReentrant - { - /** - * Gas considerations. We could query Morpho LENS's `getUserUnclaimedRewards` for each - * cToken separately and only claimRewards where it is economically feasible. Each call - * (out of 3) costs ~60k gas extra. - * - * Each extra cToken in the `poolTokens` of `claimRewards` function makes that call - * 89-120k more expensive gas wise. - * - * With Lens query in case where: - * - there is only 1 reward token to collect. Net gas usage in best case would be - * 3*60 - 2*120 = -60k -> saving 60k gas - * - there are 2 reward tokens to collect. Net gas usage in best case would be - * 3*60 - 120 = 60k -> more expensive for 60k gas - * - there are 3 reward tokens to collect. Net gas usage in best case would be - * 3*60 = 180k -> more expensive for 180k gas - * - * For the above reasoning such "optimization" is not implemented - */ - - address[] memory poolTokens = new address[](assetsMapped.length); - for (uint256 i = 0; i < assetsMapped.length; i++) { - poolTokens[i] = assetToPToken[assetsMapped[i]]; - } - - // slither-disable-next-line unused-return - IMorpho(MORPHO).claimRewards( - poolTokens, // The addresses of the underlying protocol's pools to claim rewards from - false // Whether to trade the accrued rewards for MORPHO token, with a premium - ); - - // Transfer COMP to Harvester - IERC20 rewardToken = IERC20(rewardTokenAddresses[0]); - uint256 balance = rewardToken.balanceOf(address(this)); - emit RewardTokenCollected( - harvesterAddress, - rewardTokenAddresses[0], - balance - ); - rewardToken.safeTransfer(harvesterAddress, balance); - } - - /** - * @dev Get the amount of rewards pending to be collected from the protocol - */ - function getPendingRewards() external view returns (uint256 balance) { - address[] memory poolTokens = new address[](assetsMapped.length); - for (uint256 i = 0; i < assetsMapped.length; i++) { - poolTokens[i] = assetToPToken[assetsMapped[i]]; - } - - return ILens(LENS).getUserUnclaimedRewards(poolTokens, address(this)); - } - - /** - * @dev Deposit asset into Morpho - * @param _asset Address of asset to deposit - * @param _amount Amount of asset to deposit - */ - function deposit(address _asset, uint256 _amount) - external - override - onlyVault - nonReentrant - { - _deposit(_asset, _amount); - } - - /** - * @dev Deposit asset into Morpho - * @param _asset Address of asset to deposit - * @param _amount Amount of asset to deposit - */ - function _deposit(address _asset, uint256 _amount) internal { - require(_amount > 0, "Must deposit something"); - - IMorpho(MORPHO).supply( - address(_getCTokenFor(_asset)), - address(this), // the address of the user you want to supply on behalf of - _amount - ); - emit Deposit(_asset, address(_getCTokenFor(_asset)), _amount); - } - - /** - * @dev Deposit the entire balance of any supported asset into Morpho - */ - function depositAll() external override onlyVault nonReentrant { - for (uint256 i = 0; i < assetsMapped.length; i++) { - uint256 balance = IERC20(assetsMapped[i]).balanceOf(address(this)); - if (balance > 0) { - _deposit(assetsMapped[i], balance); - } - } - } - - /** - * @dev Withdraw asset from Morpho - * @param _recipient Address to receive withdrawn asset - * @param _asset Address of asset to withdraw - * @param _amount Amount of asset to withdraw - */ - function withdraw( - address _recipient, - address _asset, - uint256 _amount - ) external override onlyVault nonReentrant { - _withdraw(_recipient, _asset, _amount); - } - - function _withdraw( - address _recipient, - address _asset, - uint256 _amount - ) internal { - require(_amount > 0, "Must withdraw something"); - require(_recipient != address(0), "Must specify recipient"); - - address pToken = assetToPToken[_asset]; - - IMorpho(MORPHO).withdraw(pToken, _amount); - emit Withdrawal(_asset, address(_getCTokenFor(_asset)), _amount); - IERC20(_asset).safeTransfer(_recipient, _amount); - } - - /** - * @dev Remove all assets from platform and send them to Vault contract. - */ - function withdrawAll() external override onlyVaultOrGovernor nonReentrant { - for (uint256 i = 0; i < assetsMapped.length; i++) { - uint256 balance = _checkBalance(assetsMapped[i]); - if (balance > 0) { - _withdraw(vaultAddress, assetsMapped[i], balance); - } - } - } - - /** - * @dev Return total value of an asset held in the platform - * @param _asset Address of the asset - * @return balance Total value of the asset in the platform - */ - function checkBalance(address _asset) - external - view - override - returns (uint256 balance) - { - return _checkBalance(_asset); - } - - function _checkBalance(address _asset) - internal - view - returns (uint256 balance) - { - address pToken = assetToPToken[_asset]; - - // Total value represented by decimal position of underlying token - (, , balance) = ILens(LENS).getCurrentSupplyBalanceInOf( - pToken, - address(this) - ); - } -} diff --git a/contracts/contracts/strategies/README.md b/contracts/contracts/strategies/README.md index 0bbea8a9c2..fc8d716777 100644 --- a/contracts/contracts/strategies/README.md +++ b/contracts/contracts/strategies/README.md @@ -1,47 +1,5 @@ # Diagrams -## Aave Strategy - -### Hierarchy - -![Aave Strategy Hierarchy](../../docs/AaveStrategyHierarchy.svg) - -### Squashed - -![Aave Strategy Squashed](../../docs/AaveStrategySquashed.svg) - -### Storage - -![Aave Strategy Storage](../../docs/AaveStrategyStorage.svg) - -## Convex ETH AMO Strategy - -### Hierarchy - -![Convex ETH AMO Strategy Hierarchy](../../docs/ConvexEthMetaStrategyHierarchy.svg) - -### Squashed - -![Convex ETH AMO Strategy Squashed](../../docs/ConvexEthMetaStrategySquashed.svg) - -### Storage - -![Convex ETH AMO Strategy Storage](../../docs/ConvexEthMetaStrategyStorage.svg) - -## Convex OUSD AMO Strategy - -### Hierarchy - -![Convex OUSD AMO Strategy Hierarchy](../../docs/ConvexOUSDMetaStrategyHierarchy.svg) - -### Squashed - -![Convex OUSD AMO Strategy Squashed](../../docs/ConvexOUSDMetaStrategySquashed.svg) - - - ## Generalized ERC-4626 Strategy ### Hierarchy @@ -56,30 +14,30 @@ ![Generalized ERC-4626 Strategy Storage](../../docs/Generalized4626StrategyStorage.svg) -## Morpho Aave Strategy +## Curve AMO Strategy ### Hierarchy -![Morpho Aave Strategy Hierarchy](../../docs/MorphoAaveStrategyHierarchy.svg) +![Curve AMO Strategy Hierarchy](../../docs/CurveAMOStrategyHierarchy.svg) ### Squashed -![Morpho Aave Strategy Squashed](../../docs/MorphoAaveStrategySquashed.svg) +![Curve AMO Strategy Squashed](../../docs/CurveAMOStrategySquashed.svg) ### Storage -![Morpho Aave Strategy Storage](../../docs/MorphoAaveStrategyStorage.svg) +![Curve AMO Strategy Storage](../../docs/CurveAMOStrategyStorage.svg) -## Morpho Compound Strategy +## Base Curve AMO Strategy ### Hierarchy -![Morpho Compound Strategy Hierarchy](../../docs/MorphoCompStrategyHierarchy.svg) +![Base Curve AMO Strategy Hierarchy](../../docs/BaseCurveAMOStrategyHierarchy.svg) ### Squashed -![Morpho Compound Strategy Squashed](../../docs/MorphoCompStrategySquashed.svg) +![Base Curve AMO Strategy Squashed](../../docs/BaseCurveAMOStrategySquashed.svg) ### Storage -![Morpho Compound Strategy Storage](../../docs/MorphoCompStrategyStorage.svg) +![Base Curve AMO Strategy Storage](../../docs/BaseCurveAMOStrategyStorage.svg) diff --git a/contracts/contracts/strategies/balancer/AbstractAuraStrategy.sol b/contracts/contracts/strategies/balancer/AbstractAuraStrategy.sol deleted file mode 100644 index 22c227d8fc..0000000000 --- a/contracts/contracts/strategies/balancer/AbstractAuraStrategy.sol +++ /dev/null @@ -1,108 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -/** - * @title OETH Base Balancer Abstract Strategy - * @author Origin Protocol Inc - */ - -import { AbstractBalancerStrategy } from "./AbstractBalancerStrategy.sol"; -import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import { IERC20 } from "../../utils/InitializableAbstractStrategy.sol"; -import { IERC4626 } from "../../../lib/openzeppelin/interfaces/IERC4626.sol"; -import { StableMath } from "../../utils/StableMath.sol"; -import { IRewardStaking } from "../IRewardStaking.sol"; - -abstract contract AbstractAuraStrategy is AbstractBalancerStrategy { - using SafeERC20 for IERC20; - using StableMath for uint256; - - /// @notice Address of the Aura rewards pool - address public immutable auraRewardPoolAddress; - - // renamed from __reserved to not shadow AbstractBalancerStrategy.__reserved, - int256[50] private __reserved_baseAuraStrategy; - - constructor(address _auraRewardPoolAddress) { - auraRewardPoolAddress = _auraRewardPoolAddress; - } - - /** - * @dev Deposit all Balancer Pool Tokens (BPT) in this strategy contract - * to the Aura rewards pool. - */ - function _lpDepositAll() internal virtual override { - uint256 bptBalance = IERC20(platformAddress).balanceOf(address(this)); - uint256 auraLp = IERC4626(auraRewardPoolAddress).deposit( - bptBalance, - address(this) - ); - require(bptBalance == auraLp, "Aura LP != BPT"); - } - - /** - * @dev Withdraw `numBPTTokens` Balancer Pool Tokens (BPT) from - * the Aura rewards pool to this strategy contract. - * @param numBPTTokens Number of Balancer Pool Tokens (BPT) to withdraw - */ - function _lpWithdraw(uint256 numBPTTokens) internal virtual override { - IRewardStaking(auraRewardPoolAddress).withdrawAndUnwrap( - numBPTTokens, - true // also claim reward tokens - ); - } - - /** - * @dev Withdraw all Balancer Pool Tokens (BPT) from - * the Aura rewards pool to this strategy contract. - */ - function _lpWithdrawAll() internal virtual override { - // Get all the strategy's BPTs in Aura - // maxRedeem is implemented as balanceOf(address) in Aura - uint256 bptBalance = IERC4626(auraRewardPoolAddress).maxRedeem( - address(this) - ); - - IRewardStaking(auraRewardPoolAddress).withdrawAndUnwrap( - bptBalance, - true // also claim reward tokens - ); - } - - /** - * @notice Collects BAL and AURA tokens from the rewards pool. - */ - function collectRewardTokens() - external - virtual - override - onlyHarvester - nonReentrant - { - /* Similar to Convex, calling this function collects both of the - * accrued BAL and AURA tokens. - */ - IRewardStaking(auraRewardPoolAddress).getReward(); - _collectRewardTokens(); - } - - /// @notice Balancer Pool Tokens (BPT) in the Balancer pool and the Aura rewards pool. - function _getBalancerPoolTokens() - internal - view - override - returns (uint256 balancerPoolTokens) - { - balancerPoolTokens = - IERC20(platformAddress).balanceOf(address(this)) + - // maxRedeem is implemented as balanceOf(address) in Aura - IERC4626(auraRewardPoolAddress).maxRedeem(address(this)); - } - - function _approveBase() internal virtual override { - super._approveBase(); - - IERC20 pToken = IERC20(platformAddress); - pToken.safeApprove(auraRewardPoolAddress, type(uint256).max); - } -} diff --git a/contracts/contracts/strategies/balancer/AbstractBalancerStrategy.sol b/contracts/contracts/strategies/balancer/AbstractBalancerStrategy.sol deleted file mode 100644 index 7c013076c9..0000000000 --- a/contracts/contracts/strategies/balancer/AbstractBalancerStrategy.sol +++ /dev/null @@ -1,489 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -/** - * @title OETH Balancer Abstract Strategy - * @author Origin Protocol Inc - */ -import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import { IERC20, InitializableAbstractStrategy } from "../../utils/InitializableAbstractStrategy.sol"; -import { IBalancerVault } from "../../interfaces/balancer/IBalancerVault.sol"; -import { IRateProvider } from "../../interfaces/balancer/IRateProvider.sol"; -import { VaultReentrancyLib } from "./VaultReentrancyLib.sol"; -import { IOracle } from "../../interfaces/IOracle.sol"; -import { IWstETH } from "../../interfaces/IWstETH.sol"; -import { IERC4626 } from "../../../lib/openzeppelin/interfaces/IERC4626.sol"; -import { StableMath } from "../../utils/StableMath.sol"; - -abstract contract AbstractBalancerStrategy is InitializableAbstractStrategy { - using SafeERC20 for IERC20; - using StableMath for uint256; - - address public immutable rETH; - address public immutable stETH; - address public immutable wstETH; - address public immutable frxETH; - address public immutable sfrxETH; - - /// @notice Address of the Balancer vault - IBalancerVault public immutable balancerVault; - /// @notice Balancer pool identifier - bytes32 public immutable balancerPoolId; - - // Max withdrawal deviation denominated in 1e18 (1e18 == 100%) - uint256 public maxWithdrawalDeviation; - // Max deposit deviation denominated in 1e18 (1e18 == 100%) - uint256 public maxDepositDeviation; - - int256[48] private __reserved; - - struct BaseBalancerConfig { - address rEthAddress; // Address of the rETH token - address stEthAddress; // Address of the stETH token - address wstEthAddress; // Address of the wstETH token - address frxEthAddress; // Address of the frxEth token - address sfrxEthAddress; // Address of the sfrxEth token - address balancerVaultAddress; // Address of the Balancer vault - bytes32 balancerPoolId; // Balancer pool identifier - } - - event MaxWithdrawalDeviationUpdated( - uint256 _prevMaxDeviationPercentage, - uint256 _newMaxDeviationPercentage - ); - event MaxDepositDeviationUpdated( - uint256 _prevMaxDeviationPercentage, - uint256 _newMaxDeviationPercentage - ); - - /** - * @dev Ensure we are not in a Vault context when this function is called, by attempting a no-op internal - * balance operation. If we are already in a Vault transaction (e.g., a swap, join, or exit), the Vault's - * reentrancy protection will cause this function to revert. - * - * Use this modifier with any function that can cause a state change in a pool and is either public itself, - * or called by a public function *outside* a Vault operation (e.g., join, exit, or swap). - * - * This is to protect against Balancer's read-only re-entrancy vulnerability: - * https://www.notion.so/originprotocol/Balancer-read-only-reentrancy-c686e72c82414ef18fa34312bb02e11b - */ - modifier whenNotInBalancerVaultContext() { - VaultReentrancyLib.ensureNotInVaultContext(balancerVault); - _; - } - - constructor(BaseBalancerConfig memory _balancerConfig) { - rETH = _balancerConfig.rEthAddress; - stETH = _balancerConfig.stEthAddress; - wstETH = _balancerConfig.wstEthAddress; - frxETH = _balancerConfig.frxEthAddress; - sfrxETH = _balancerConfig.sfrxEthAddress; - - balancerVault = IBalancerVault(_balancerConfig.balancerVaultAddress); - balancerPoolId = _balancerConfig.balancerPoolId; - } - - /** - * Initializer for setting up strategy internal state. This overrides the - * InitializableAbstractStrategy initializer as Balancer's strategies don't fit - * well within that abstraction. - * @param _rewardTokenAddresses Address of BAL & AURA - * @param _assets Addresses of supported assets. MUST be passed in the same - * order as returned by coins on the pool contract, i.e. - * WETH, stETH - * @param _pTokens Platform Token corresponding addresses - */ - function initialize( - address[] calldata _rewardTokenAddresses, // BAL & AURA - address[] calldata _assets, - address[] calldata _pTokens - ) external onlyGovernor initializer { - maxWithdrawalDeviation = 1e16; - maxDepositDeviation = 1e16; - - emit MaxWithdrawalDeviationUpdated(0, maxWithdrawalDeviation); - emit MaxDepositDeviationUpdated(0, maxDepositDeviation); - - IERC20[] memory poolAssets = _getPoolAssets(); - require( - poolAssets.length == _assets.length, - "Pool assets length mismatch" - ); - for (uint256 i = 0; i < _assets.length; ++i) { - address asset = _fromPoolAsset(address(poolAssets[i])); - require(_assets[i] == asset, "Pool assets mismatch"); - } - - InitializableAbstractStrategy._initialize( - _rewardTokenAddresses, - _assets, - _pTokens - ); - _approveBase(); - } - - /** - * @notice Returns bool indicating whether asset is supported by strategy - * @param _asset Address of the asset - */ - function supportsAsset(address _asset) public view override returns (bool) { - return assetToPToken[_asset] != address(0); - } - - /** - * @notice Get strategy's share of an assets in the Balancer pool. - * This is not denominated in OUSD/ETH value of the assets in the Balancer pool. - * @param _asset Address of the Vault collateral asset - * @return amount the amount of vault collateral assets - * - * IMPORTANT if this function is overridden it needs to have a whenNotInBalancerVaultContext - * modifier on it or it is susceptible to read-only re-entrancy attack - * - * @dev it is important that this function is not affected by reporting inflated - * values of assets in case of any pool manipulation. Such a manipulation could easily - * exploit the protocol by: - * - minting OETH - * - tilting Balancer pool to report higher balances of assets - * - rebasing() -> all that extra token balances get distributed to OETH holders - * - tilting pool back - * - redeeming OETH - */ - function checkBalance(address _asset) - external - view - virtual - override - whenNotInBalancerVaultContext - returns (uint256 amount) - { - require(assetToPToken[_asset] != address(0), "Unsupported asset"); - - uint256 bptBalance = _getBalancerPoolTokens(); - - /* To calculate the worth of queried asset: - * - assume that all tokens normalized to their ETH value have an equal split balance - * in the pool when it is balanced - * - multiply the BPT amount with the bpt rate to get the ETH denominated amount - * of strategy's holdings - * - divide that by the number of tokens we support in the pool to get ETH denominated - * amount that is applicable to each supported token in the pool. - * - * It would be possible to support only 1 asset in the pool (and be exposed to all - * the assets while holding BPT tokens) and deposit/withdraw/checkBalance using only - * that asset. TBD: changes to other functions still required if we ever decide to - * go with such configuration. - */ - amount = (bptBalance.mulTruncate( - IRateProvider(platformAddress).getRate() - ) / assetsMapped.length); - - /* If the pool asset is equal to (strategy )_asset it means that a rate - * provider for that asset exists and that asset is not necessarily - * pegged to a unit (ETH). - * - * Because this function returns the balance of the asset and is not denominated in - * ETH units we need to convert the ETH denominated amount to asset amount. - */ - if (_toPoolAsset(_asset) == _asset) { - amount = amount.divPrecisely(_getRateProviderRate(_asset)); - } - } - - /** - * @notice Returns the value of all assets managed by this strategy. - * Uses the Balancer pool's rate (virtual price) to convert the strategy's - * Balancer Pool Tokens (BPT) to ETH value. - * @return value The ETH value - * - * IMPORTANT if this function is overridden it needs to have a whenNotInBalancerVaultContext - * modifier on it or it is susceptible to read-only re-entrancy attack - */ - function checkBalance() - external - view - virtual - whenNotInBalancerVaultContext - returns (uint256 value) - { - uint256 bptBalance = _getBalancerPoolTokens(); - - // Convert BPT to ETH value - value = bptBalance.mulTruncate( - IRateProvider(platformAddress).getRate() - ); - } - - /// @notice Balancer Pool Tokens (BPT) in the Balancer pool. - function _getBalancerPoolTokens() - internal - view - virtual - returns (uint256 balancerPoolTokens) - { - balancerPoolTokens = IERC20(platformAddress).balanceOf(address(this)); - } - - /* solhint-disable max-line-length */ - /** - * @notice BPT price is calculated by taking the rate from the rateProvider of the asset in - * question. If one does not exist it defaults to 1e18. To get the final BPT expected that - * is multiplied by the underlying asset amount divided by BPT token rate. BPT token rate is - * similar to Curve's virtual_price and expresses how much has the price of BPT appreciated - * (e.g. due to swap fees) in relation to the underlying assets - * - * Using the above approach makes the strategy vulnerable to a possible MEV attack using - * flash loan to manipulate the pool before a deposit/withdrawal since the function ignores - * market values of the assets being priced in BPT. - * - * At the time of writing there is no safe on-chain approach to pricing BPT in a way that it - * would make it invulnerable to MEV pool manipulation. See recent Balancer exploit: - * https://www.notion.so/originprotocol/Balancer-OETH-strategy-9becdea132704e588782a919d7d471eb?pvs=4#1cf07de12fc64f1888072321e0644348 - * - * To mitigate MEV possibilities during deposits and withdraws, the VaultValueChecker will use checkBalance before and after the move - * to ensure the expected changes took place. - * - * @param _asset Address of the Balancer pool asset - * @param _amount Amount of the Balancer pool asset - * @return bptExpected of BPT expected in exchange for the asset - * - * @dev - * bptAssetPrice = 1e18 (asset peg) * pool_asset_rate - * - * bptExpected = bptAssetPrice * asset_amount / BPT_token_rate - * - * bptExpected = 1e18 (asset peg) * pool_asset_rate * asset_amount / BPT_token_rate - * bptExpected = asset_amount * pool_asset_rate / BPT_token_rate - * - * further information available here: - * https://www.notion.so/originprotocol/Balancer-OETH-strategy-9becdea132704e588782a919d7d471eb?pvs=4#ce01495ae70346d8971f5dced809fb83 - */ - /* solhint-enable max-line-length */ - function _getBPTExpected(address _asset, uint256 _amount) - internal - view - virtual - returns (uint256 bptExpected) - { - uint256 bptRate = IRateProvider(platformAddress).getRate(); - uint256 poolAssetRate = _getRateProviderRate(_asset); - bptExpected = _amount.mulTruncate(poolAssetRate).divPrecisely(bptRate); - } - - function _getBPTExpected( - address[] memory _assets, - uint256[] memory _amounts - ) internal view virtual returns (uint256 bptExpected) { - require(_assets.length == _amounts.length, "Assets & amounts mismatch"); - - for (uint256 i = 0; i < _assets.length; ++i) { - uint256 poolAssetRate = _getRateProviderRate(_assets[i]); - // convert asset amount to ETH amount - bptExpected += _amounts[i].mulTruncate(poolAssetRate); - } - - uint256 bptRate = IRateProvider(platformAddress).getRate(); - // Convert ETH amount to BPT amount - bptExpected = bptExpected.divPrecisely(bptRate); - } - - function _lpDepositAll() internal virtual; - - function _lpWithdraw(uint256 numBPTTokens) internal virtual; - - function _lpWithdrawAll() internal virtual; - - /** - * @notice Balancer returns assets and rateProviders for corresponding assets ordered - * by numerical order. - */ - function _getPoolAssets() internal view returns (IERC20[] memory assets) { - // slither-disable-next-line unused-return - (assets, , ) = balancerVault.getPoolTokens(balancerPoolId); - } - - /** - * @dev If an asset is rebasing the Balancer pools have a wrapped versions of assets - * that the strategy supports. This function converts the pool(wrapped) asset - * and corresponding amount to strategy asset. - */ - function _toPoolAsset(address asset, uint256 amount) - internal - view - returns (address poolAsset, uint256 poolAmount) - { - if (asset == stETH) { - poolAsset = wstETH; - if (amount > 0) { - poolAmount = IWstETH(wstETH).getWstETHByStETH(amount); - } - } else if (asset == frxETH) { - poolAsset = sfrxETH; - if (amount > 0) { - poolAmount = IERC4626(sfrxETH).convertToShares(amount); - } - } else { - poolAsset = asset; - poolAmount = amount; - } - } - - /** - * @dev Converts a Vault collateral asset to a Balancer pool asset. - * stETH becomes wstETH, frxETH becomes sfrxETH and everything else stays the same. - * @param asset Address of the Vault collateral asset. - * @return Address of the Balancer pool asset. - */ - function _toPoolAsset(address asset) internal view returns (address) { - if (asset == stETH) { - return wstETH; - } else if (asset == frxETH) { - return sfrxETH; - } - return asset; - } - - /** - * @dev Converts rebasing asset to its wrapped counterpart. - */ - function _wrapPoolAsset(address asset, uint256 amount) - internal - returns (address wrappedAsset, uint256 wrappedAmount) - { - if (asset == stETH) { - wrappedAsset = wstETH; - if (amount > 0) { - wrappedAmount = IWstETH(wstETH).wrap(amount); - } - } else if (asset == frxETH) { - wrappedAsset = sfrxETH; - if (amount > 0) { - wrappedAmount = IERC4626(sfrxETH).deposit( - amount, - address(this) - ); - } - } else { - wrappedAsset = asset; - wrappedAmount = amount; - } - } - - /** - * @dev Converts wrapped asset to its rebasing counterpart. - */ - function _unwrapPoolAsset(address asset, uint256 amount) - internal - returns (uint256 unwrappedAmount) - { - if (asset == stETH) { - unwrappedAmount = IWstETH(wstETH).unwrap(amount); - } else if (asset == frxETH) { - unwrappedAmount = IERC4626(sfrxETH).withdraw( - amount, - address(this), - address(this) - ); - } else { - unwrappedAmount = amount; - } - } - - /** - * @dev If an asset is rebasing the Balancer pools have a wrapped versions of assets - * that the strategy supports. This function converts the rebasing strategy asset - * and corresponding amount to wrapped(pool) asset. - */ - function _fromPoolAsset(address poolAsset, uint256 poolAmount) - internal - view - returns (address asset, uint256 amount) - { - if (poolAsset == wstETH) { - asset = stETH; - if (poolAmount > 0) { - amount = IWstETH(wstETH).getStETHByWstETH(poolAmount); - } - } else if (poolAsset == sfrxETH) { - asset = frxETH; - if (poolAmount > 0) { - amount = IERC4626(sfrxETH).convertToAssets(poolAmount); - } - } else { - asset = poolAsset; - amount = poolAmount; - } - } - - function _fromPoolAsset(address poolAsset) - internal - view - returns (address asset) - { - if (poolAsset == wstETH) { - asset = stETH; - } else if (poolAsset == sfrxETH) { - asset = frxETH; - } else { - asset = poolAsset; - } - } - - /** - * @notice Sets max withdrawal deviation that is considered when removing - * liquidity from Balancer pools. - * @param _maxWithdrawalDeviation Max withdrawal deviation denominated in - * wad (number with 18 decimals): 1e18 == 100%, 1e16 == 1% - * - * IMPORTANT Minimum maxWithdrawalDeviation will be 1% (1e16) for production - * usage. Vault value checker in combination with checkBalance will - * catch any unexpected manipulation. - */ - function setMaxWithdrawalDeviation(uint256 _maxWithdrawalDeviation) - external - onlyVaultOrGovernorOrStrategist - { - require( - _maxWithdrawalDeviation <= 1e18, - "Withdrawal dev. out of bounds" - ); - emit MaxWithdrawalDeviationUpdated( - maxWithdrawalDeviation, - _maxWithdrawalDeviation - ); - maxWithdrawalDeviation = _maxWithdrawalDeviation; - } - - /** - * @notice Sets max deposit deviation that is considered when adding - * liquidity to Balancer pools. - * @param _maxDepositDeviation Max deposit deviation denominated in - * wad (number with 18 decimals): 1e18 == 100%, 1e16 == 1% - * - * IMPORTANT Minimum maxDepositDeviation will default to 1% (1e16) - * for production usage. Vault value checker in combination with - * checkBalance will catch any unexpected manipulation. - */ - function setMaxDepositDeviation(uint256 _maxDepositDeviation) - external - onlyVaultOrGovernorOrStrategist - { - require(_maxDepositDeviation <= 1e18, "Deposit dev. out of bounds"); - emit MaxDepositDeviationUpdated( - maxDepositDeviation, - _maxDepositDeviation - ); - maxDepositDeviation = _maxDepositDeviation; - } - - function _approveBase() internal virtual { - IERC20 pToken = IERC20(platformAddress); - // Balancer vault for BPT token (required for removing liquidity) - pToken.safeApprove(address(balancerVault), type(uint256).max); - } - - function _getRateProviderRate(address _asset) - internal - view - virtual - returns (uint256); -} diff --git a/contracts/contracts/strategies/balancer/BalancerMetaPoolStrategy.sol b/contracts/contracts/strategies/balancer/BalancerMetaPoolStrategy.sol deleted file mode 100644 index e63097d33d..0000000000 --- a/contracts/contracts/strategies/balancer/BalancerMetaPoolStrategy.sol +++ /dev/null @@ -1,561 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -/** - * @title OETH Balancer MetaStablePool Strategy - * @author Origin Protocol Inc - */ -import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import { AbstractAuraStrategy, AbstractBalancerStrategy } from "./AbstractAuraStrategy.sol"; -import { IBalancerVault } from "../../interfaces/balancer/IBalancerVault.sol"; -import { IRateProvider } from "../../interfaces/balancer/IRateProvider.sol"; -import { IMetaStablePool } from "../../interfaces/balancer/IMetaStablePool.sol"; -import { IERC20, InitializableAbstractStrategy } from "../../utils/InitializableAbstractStrategy.sol"; -import { StableMath } from "../../utils/StableMath.sol"; - -contract BalancerMetaPoolStrategy is AbstractAuraStrategy { - using SafeERC20 for IERC20; - using StableMath for uint256; - - constructor( - BaseStrategyConfig memory _stratConfig, - BaseBalancerConfig memory _balancerConfig, - address _auraRewardPoolAddress - ) - InitializableAbstractStrategy(_stratConfig) - AbstractBalancerStrategy(_balancerConfig) - AbstractAuraStrategy(_auraRewardPoolAddress) - {} - - /** - * @notice There are no plans to configure BalancerMetaPool as a default - * asset strategy. For that reason there is no need to support this - * functionality. - */ - function deposit(address, uint256) - external - override - onlyVault - nonReentrant - { - revert("Not supported"); - } - - /** - * @notice There are no plans to configure BalancerMetaPool as a default - * asset strategy. For that reason there is no need to support this - * functionality. - */ - function deposit(address[] calldata, uint256[] calldata) - external - onlyVault - nonReentrant - { - revert("Not supported"); - } - - /** - * @notice Deposits all supported assets in this strategy contract to the Balancer pool. - */ - function depositAll() external override onlyVault nonReentrant { - uint256 assetsLength = assetsMapped.length; - address[] memory strategyAssets = new address[](assetsLength); - uint256[] memory strategyAmounts = new uint256[](assetsLength); - - // For each vault collateral asset - for (uint256 i = 0; i < assetsLength; ++i) { - strategyAssets[i] = assetsMapped[i]; - // Get the asset balance in this strategy contract - strategyAmounts[i] = IERC20(strategyAssets[i]).balanceOf( - address(this) - ); - } - _deposit(strategyAssets, strategyAmounts); - } - - /* - * _deposit doesn't require a read-only re-entrancy protection since during the deposit - * the function enters the Balancer Vault Context. If this function were called as part of - * the attacking contract (while intercepting execution flow upon receiving ETH) the read-only - * protection of the Balancer Vault would be triggered. Since the attacking contract would - * already be in the Balancer Vault context and wouldn't be able to enter it again. - */ - function _deposit( - address[] memory _strategyAssets, - uint256[] memory _strategyAmounts - ) internal { - require( - _strategyAssets.length == _strategyAmounts.length, - "Array length missmatch" - ); - - (IERC20[] memory tokens, , ) = balancerVault.getPoolTokens( - balancerPoolId - ); - - uint256[] memory strategyAssetAmountsToPoolAssetAmounts = new uint256[]( - _strategyAssets.length - ); - address[] memory strategyAssetsToPoolAssets = new address[]( - _strategyAssets.length - ); - - for (uint256 i = 0; i < _strategyAssets.length; ++i) { - address strategyAsset = _strategyAssets[i]; - uint256 strategyAmount = _strategyAmounts[i]; - - require( - assetToPToken[strategyAsset] != address(0), - "Unsupported asset" - ); - strategyAssetsToPoolAssets[i] = _toPoolAsset(strategyAsset); - - if (strategyAmount > 0) { - emit Deposit(strategyAsset, platformAddress, strategyAmount); - - // wrap rebasing assets like stETH and frxETH to wstETH and sfrxETH - (, strategyAssetAmountsToPoolAssetAmounts[i]) = _wrapPoolAsset( - strategyAsset, - strategyAmount - ); - } - } - - uint256[] memory amountsIn = new uint256[](tokens.length); - address[] memory poolAssets = new address[](tokens.length); - for (uint256 i = 0; i < tokens.length; ++i) { - // Convert IERC20 type to address - poolAssets[i] = address(tokens[i]); - - // For each of the mapped assets - for (uint256 j = 0; j < strategyAssetsToPoolAssets.length; ++j) { - // If the pool asset is the same as the mapped asset - if (poolAssets[i] == strategyAssetsToPoolAssets[j]) { - amountsIn[i] = strategyAssetAmountsToPoolAssetAmounts[j]; - } - } - } - - uint256 minBPT = _getBPTExpected( - strategyAssetsToPoolAssets, - strategyAssetAmountsToPoolAssetAmounts - ); - uint256 minBPTwDeviation = minBPT.mulTruncate( - 1e18 - maxDepositDeviation - ); - - /* EXACT_TOKENS_IN_FOR_BPT_OUT: - * User sends precise quantities of tokens, and receives an - * estimated but unknown (computed at run time) quantity of BPT. - * - * ['uint256', 'uint256[]', 'uint256'] - * [EXACT_TOKENS_IN_FOR_BPT_OUT, amountsIn, minimumBPT] - */ - bytes memory userData = abi.encode( - IBalancerVault.WeightedPoolJoinKind.EXACT_TOKENS_IN_FOR_BPT_OUT, - amountsIn, - minBPTwDeviation - ); - - IBalancerVault.JoinPoolRequest memory request = IBalancerVault - .JoinPoolRequest(poolAssets, amountsIn, userData, false); - - // Add the pool assets in this strategy to the balancer pool - balancerVault.joinPool( - balancerPoolId, - address(this), - address(this), - request - ); - - // Deposit the Balancer Pool Tokens (BPT) into Aura - _lpDepositAll(); - } - - /** - * @notice Withdraw a Vault collateral asset from the Balancer pool. - * @param _recipient Address to receive the Vault collateral assets. Typically is the Vault. - * @param _strategyAsset Address of the Vault collateral asset - * @param _strategyAmount The amount of Vault collateral assets to withdraw - */ - function withdraw( - address _recipient, - address _strategyAsset, - uint256 _strategyAmount - ) external override onlyVault nonReentrant { - address[] memory strategyAssets = new address[](1); - uint256[] memory strategyAmounts = new uint256[](1); - strategyAssets[0] = _strategyAsset; - strategyAmounts[0] = _strategyAmount; - - _withdraw(_recipient, strategyAssets, strategyAmounts); - } - - /** - * @notice Withdraw multiple Vault collateral asset from the Balancer pool. - * @param _recipient Address to receive the Vault collateral assets. Typically is the Vault. - * @param _strategyAssets Addresses of the Vault collateral assets - * @param _strategyAmounts The amounts of Vault collateral assets to withdraw - */ - function withdraw( - address _recipient, - address[] calldata _strategyAssets, - uint256[] calldata _strategyAmounts - ) external onlyVault nonReentrant { - _withdraw(_recipient, _strategyAssets, _strategyAmounts); - } - - /** - * @dev Withdraw multiple Vault collateral asset from the Balancer pool. - * @param _recipient Address to receive the Vault collateral assets. Typically is the Vault. - * @param _strategyAssets Addresses of the Vault collateral assets - * @param _strategyAmounts The amounts of Vault collateral assets to withdraw - * - * _withdrawal doesn't require a read-only re-entrancy protection since during the withdrawal - * the function enters the Balancer Vault Context. If this function were called as part of - * the attacking contract (while intercepting execution flow upon receiving ETH) the read-only - * protection of the Balancer Vault would be triggered. Since the attacking contract would - * already be in the Balancer Vault context and wouldn't be able to enter it again. - */ - function _withdraw( - address _recipient, - address[] memory _strategyAssets, - uint256[] memory _strategyAmounts - ) internal { - require( - _strategyAssets.length == _strategyAmounts.length, - "Invalid input arrays" - ); - - for (uint256 i = 0; i < _strategyAssets.length; ++i) { - require( - assetToPToken[_strategyAssets[i]] != address(0), - "Unsupported asset" - ); - } - - // STEP 1 - Calculate the Balancer pool assets and amounts from the vault collateral assets - - // Get all the supported balancer pool assets - (IERC20[] memory tokens, , ) = balancerVault.getPoolTokens( - balancerPoolId - ); - // Calculate the balancer pool assets and amounts to withdraw - uint256[] memory poolAssetsAmountsOut = new uint256[](tokens.length); - address[] memory poolAssets = new address[](tokens.length); - // Is the wrapped asset amount indexed by the assets array, not the order of the Balancer pool tokens - // eg wstETH and sfrxETH amounts, not the stETH and frxETH amounts - uint256[] memory strategyAssetsToPoolAssetsAmounts = new uint256[]( - _strategyAssets.length - ); - - // For each of the Balancer pool assets - for (uint256 i = 0; i < tokens.length; ++i) { - poolAssets[i] = address(tokens[i]); - - // Convert the Balancer pool asset back to a vault collateral asset - address strategyAsset = _fromPoolAsset(poolAssets[i]); - - // for each of the vault assets - for (uint256 j = 0; j < _strategyAssets.length; ++j) { - // If the vault asset equals the vault asset mapped from the Balancer pool asset - if (_strategyAssets[j] == strategyAsset) { - (, poolAssetsAmountsOut[i]) = _toPoolAsset( - strategyAsset, - _strategyAmounts[j] - ); - strategyAssetsToPoolAssetsAmounts[j] = poolAssetsAmountsOut[ - i - ]; - - /* Because of the potential Balancer rounding error mentioned below - * the contract might receive 1-2 WEI smaller amount than required - * in the withdraw user data encoding. If slightly lesser token amount - * is received the strategy can not unwrap the pool asset as it is - * smaller than expected. - * - * For that reason we `overshoot` the required tokens expected to - * circumvent the error - */ - if (poolAssetsAmountsOut[i] > 0) { - poolAssetsAmountsOut[i] += 2; - } - } - } - } - - // STEP 2 - Calculate the max about of Balancer Pool Tokens (BPT) to withdraw - - // Estimate the required amount of Balancer Pool Tokens (BPT) for the assets - uint256 maxBPTtoWithdraw = _getBPTExpected( - poolAssets, - /* all non 0 values are overshot by 2 WEI and with the expected mainnet - * ~1% withdrawal deviation, the 2 WEI aren't important - */ - poolAssetsAmountsOut - ); - // Increase BPTs by the max allowed deviation - // Any excess BPTs will be left in this strategy contract - maxBPTtoWithdraw = maxBPTtoWithdraw.mulTruncate( - 1e18 + maxWithdrawalDeviation - ); - - // STEP 3 - Withdraw the Balancer Pool Tokens (BPT) from Aura to this strategy contract - - // Withdraw BPT from Aura allowing for BPTs left in this strategy contract from previous withdrawals - _lpWithdraw( - maxBPTtoWithdraw - IERC20(platformAddress).balanceOf(address(this)) - ); - - // STEP 4 - Withdraw the balancer pool assets from the pool - - /* Custom asset exit: BPT_IN_FOR_EXACT_TOKENS_OUT: - * User sends an estimated but unknown (computed at run time) quantity of BPT, - * and receives precise quantities of specified tokens. - * - * ['uint256', 'uint256[]', 'uint256'] - * [BPT_IN_FOR_EXACT_TOKENS_OUT, amountsOut, maxBPTAmountIn] - */ - bytes memory userData = abi.encode( - IBalancerVault.WeightedPoolExitKind.BPT_IN_FOR_EXACT_TOKENS_OUT, - poolAssetsAmountsOut, - maxBPTtoWithdraw - ); - - IBalancerVault.ExitPoolRequest memory request = IBalancerVault - .ExitPoolRequest( - poolAssets, - /* We specify the exact amount of a tokens we are expecting in the encoded - * userData, for that reason we don't need to specify the amountsOut here. - * - * Also Balancer has a rounding issue that can make a transaction fail: - * https://github.com/balancer/balancer-v2-monorepo/issues/2541 - * which is an extra reason why this field is empty. - */ - new uint256[](tokens.length), - userData, - false - ); - - balancerVault.exitPool( - balancerPoolId, - address(this), - /* Payable keyword is required because of the IBalancerVault interface even though - * this strategy shall never be receiving native ETH - */ - payable(address(this)), - request - ); - - // STEP 5 - Re-deposit any left over BPT tokens back into Aura - /* When concluding how much of BPT we need to withdraw from Aura we overshoot by - * roughly around 1% (initial mainnet setting of maxWithdrawalDeviation). After exiting - * the pool strategy could have left over BPT tokens that are not earning boosted yield. - * We re-deploy those back in. - */ - _lpDepositAll(); - - // STEP 6 - Unswap balancer pool assets to vault collateral assets and send to the vault. - - // For each of the specified assets - for (uint256 i = 0; i < _strategyAssets.length; ++i) { - // Unwrap assets like wstETH and sfrxETH to rebasing assets stETH and frxETH - if (strategyAssetsToPoolAssetsAmounts[i] > 0) { - _unwrapPoolAsset( - _strategyAssets[i], - strategyAssetsToPoolAssetsAmounts[i] - ); - } - - // Transfer the vault collateral assets to the recipient, which is typically the vault - if (_strategyAmounts[i] > 0) { - IERC20(_strategyAssets[i]).safeTransfer( - _recipient, - _strategyAmounts[i] - ); - - emit Withdrawal( - _strategyAssets[i], - platformAddress, - _strategyAmounts[i] - ); - } - } - } - - /** - * @notice Withdraws all supported Vault collateral assets from the Balancer pool - * and send to the OToken's Vault. - * - * Is only executable by the OToken's Vault or the Governor. - */ - function withdrawAll() external override onlyVaultOrGovernor nonReentrant { - // STEP 1 - Withdraw all Balancer Pool Tokens (BPT) from Aura to this strategy contract - - _lpWithdrawAll(); - // Get the BPTs withdrawn from Aura plus any that were already in this strategy contract - uint256 BPTtoWithdraw = IERC20(platformAddress).balanceOf( - address(this) - ); - // Get the balancer pool assets and their total balances - (IERC20[] memory tokens, , ) = balancerVault.getPoolTokens( - balancerPoolId - ); - uint256[] memory minAmountsOut = new uint256[](tokens.length); - address[] memory poolAssets = new address[](tokens.length); - for (uint256 i = 0; i < tokens.length; ++i) { - poolAssets[i] = address(tokens[i]); - } - - // STEP 2 - Withdraw the Balancer pool assets from the pool - /* Proportional exit: EXACT_BPT_IN_FOR_TOKENS_OUT: - * User sends a precise quantity of BPT, and receives an estimated but unknown - * (computed at run time) quantity of a single token - * - * ['uint256', 'uint256'] - * [EXACT_BPT_IN_FOR_TOKENS_OUT, bptAmountIn] - * - * It is ok to pass an empty minAmountsOut since tilting the pool in any direction - * when doing a proportional exit can only be beneficial to the strategy. Since - * it will receive more of the underlying tokens for the BPT traded in. - */ - bytes memory userData = abi.encode( - IBalancerVault.WeightedPoolExitKind.EXACT_BPT_IN_FOR_TOKENS_OUT, - BPTtoWithdraw - ); - - IBalancerVault.ExitPoolRequest memory request = IBalancerVault - .ExitPoolRequest(poolAssets, minAmountsOut, userData, false); - - balancerVault.exitPool( - balancerPoolId, - address(this), - /* Payable keyword is required because of the IBalancerVault interface even though - * this strategy shall never be receiving native ETH - */ - payable(address(this)), - request - ); - - // STEP 3 - Convert the balancer pool assets to the vault collateral assets and send to the vault - // For each of the Balancer pool assets - for (uint256 i = 0; i < tokens.length; ++i) { - address poolAsset = address(tokens[i]); - // Convert the balancer pool asset to the strategy asset - address strategyAsset = _fromPoolAsset(poolAsset); - // Get the balancer pool assets withdraw from the pool plus any that were already in this strategy contract - uint256 poolAssetAmount = IERC20(poolAsset).balanceOf( - address(this) - ); - - // Unwrap assets like wstETH and sfrxETH to rebasing assets stETH and frxETH - uint256 unwrappedAmount = 0; - if (poolAssetAmount > 0) { - unwrappedAmount = _unwrapPoolAsset( - strategyAsset, - poolAssetAmount - ); - } - - // Transfer the vault collateral assets to the vault - if (unwrappedAmount > 0) { - IERC20(strategyAsset).safeTransfer( - vaultAddress, - unwrappedAmount - ); - emit Withdrawal( - strategyAsset, - platformAddress, - unwrappedAmount - ); - } - } - } - - /** - * @notice Approves the Balancer Vault to transfer poolAsset counterparts - * of all of the supported assets from this strategy. E.g. stETH is a supported - * strategy and Balancer Vault gets unlimited approval to transfer wstETH. - * - * If Balancer pool uses a wrapped version of a supported asset then also approve - * unlimited usage of an asset to the contract responsible for wrapping. - * - * Approve unlimited spending by Balancer Vault and Aura reward pool of the - * pool BPT tokens. - * - * Is only executable by the Governor. - */ - function safeApproveAllTokens() - external - override - onlyGovernor - nonReentrant - { - uint256 assetCount = assetsMapped.length; - for (uint256 i = 0; i < assetCount; ++i) { - _abstractSetPToken(assetsMapped[i], platformAddress); - } - _approveBase(); - } - - // solhint-disable-next-line no-unused-vars - function _abstractSetPToken(address _asset, address) internal override { - address poolAsset = _toPoolAsset(_asset); - if (_asset == stETH) { - // slither-disable-next-line unused-return - IERC20(stETH).approve(wstETH, type(uint256).max); - } else if (_asset == frxETH) { - // slither-disable-next-line unused-return - IERC20(frxETH).approve(sfrxETH, type(uint256).max); - } - _approveAsset(poolAsset); - } - - /** - * @dev Approves the Balancer Vault to transfer an asset from - * this strategy. The assets could be a Vault collateral asset - * like WETH or rETH; or a Balancer pool asset that wraps the vault asset - * like wstETH or sfrxETH. - */ - function _approveAsset(address _asset) internal { - IERC20 asset = IERC20(_asset); - // slither-disable-next-line unused-return - asset.approve(address(balancerVault), type(uint256).max); - } - - /** - * @notice Returns the rate supplied by the Balancer configured rate - * provider. Rate is used to normalize the token to common underlying - * pool denominator. (ETH for ETH Liquid staking derivatives) - * - * @param _asset Address of the Balancer pool asset - * @return rate of the corresponding asset - */ - function _getRateProviderRate(address _asset) - internal - view - override - returns (uint256) - { - IMetaStablePool pool = IMetaStablePool(platformAddress); - IRateProvider[] memory providers = pool.getRateProviders(); - (IERC20[] memory tokens, , ) = balancerVault.getPoolTokens( - balancerPoolId - ); - - uint256 providersLength = providers.length; - for (uint256 i = 0; i < providersLength; ++i) { - // _assets and corresponding rate providers are all in the same order - if (address(tokens[i]) == _asset) { - // rate provider doesn't exist, defaults to 1e18 - if (address(providers[i]) == address(0)) { - return 1e18; - } - return providers[i].getRate(); - } - } - - // should never happen - assert(false); - } -} diff --git a/contracts/contracts/strategies/balancer/README.md b/contracts/contracts/strategies/balancer/README.md deleted file mode 100644 index 8e5aa3468e..0000000000 --- a/contracts/contracts/strategies/balancer/README.md +++ /dev/null @@ -1,15 +0,0 @@ -# Diagrams - -## Balancer Metapool Strategy - -### Hierarchy - -![Balancer Metapool Strategy Hierarchy](../../../docs/BalancerMetaPoolStrategyHierarchy.svg) - -### Squashed - -![Balancer Metapool Strategy Squashed](../../../docs/BalancerMetaPoolStrategySquashed.svg) - -### Storage - -![Balancer Metapool Strategy Storage](../../../docs/BalancerMetaPoolStrategyStorage.svg) diff --git a/contracts/contracts/strategies/balancer/VaultReentrancyLib.sol b/contracts/contracts/strategies/balancer/VaultReentrancyLib.sol deleted file mode 100644 index c579047cf8..0000000000 --- a/contracts/contracts/strategies/balancer/VaultReentrancyLib.sol +++ /dev/null @@ -1,84 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -pragma solidity >=0.7.0 <0.9.0; - -import "../../utils/BalancerErrors.sol"; -import { IBalancerVault } from "../../interfaces/balancer/IBalancerVault.sol"; - -library VaultReentrancyLib { - /** - * @dev Ensure we are not in a Vault context when this function is called, by attempting a no-op internal - * balance operation. If we are already in a Vault transaction (e.g., a swap, join, or exit), the Vault's - * reentrancy protection will cause this function to revert. - * - * The exact function call doesn't really matter: we're just trying to trigger the Vault reentrancy check - * (and not hurt anything in case it works). An empty operation array with no specific operation at all works - * for that purpose, and is also the least expensive in terms of gas and bytecode size. - * - * Call this at the top of any function that can cause a state change in a pool and is either public itself, - * or called by a public function *outside* a Vault operation (e.g., join, exit, or swap). - * - * If this is *not* called in functions that are vulnerable to the read-only reentrancy issue described - * here (https://forum.balancer.fi/t/reentrancy-vulnerability-scope-expanded/4345), those functions are unsafe, - * and subject to manipulation that may result in loss of funds. - */ - function ensureNotInVaultContext(IBalancerVault vault) internal view { - // Perform the following operation to trigger the Vault's reentrancy guard: - // - // IBalancerVault.UserBalanceOp[] memory noop = new IBalancerVault.UserBalanceOp[](0); - // _vault.manageUserBalance(noop); - // - // However, use a static call so that it can be a view function (even though the function is non-view). - // This allows the library to be used more widely, as some functions that need to be protected might be - // view. - // - // This staticcall always reverts, but we need to make sure it doesn't fail due to a re-entrancy attack. - // Staticcalls consume all gas forwarded to them on a revert caused by storage modification. - // By default, almost the entire available gas is forwarded to the staticcall, - // causing the entire call to revert with an 'out of gas' error. - // - // We set the gas limit to 10k for the staticcall to - // avoid wasting gas when it reverts due to storage modification. - // `manageUserBalance` is a non-reentrant function in the Vault, so calling it invokes `_enterNonReentrant` - // in the `ReentrancyGuard` contract, reproduced here: - // - // function _enterNonReentrant() private { - // // If the Vault is actually being reentered, it will revert in the first line, at the `_require` that - // // checks the reentrancy flag, with "BAL#400" (corresponding to Errors.REENTRANCY) in the revertData. - // // The full revertData will be: `abi.encodeWithSignature("Error(string)", "BAL#400")`. - // _require(_status != _ENTERED, Errors.REENTRANCY); - // - // // If the Vault is not being reentered, the check above will pass: but it will *still* revert, - // // because the next line attempts to modify storage during a staticcall. However, this type of - // // failure results in empty revertData. - // _status = _ENTERED; - // } - // - // So based on this analysis, there are only two possible revertData values: empty, or abi.encoded BAL#400. - // - // It is of course much more bytecode and gas efficient to check for zero-length revertData than to compare it - // to the encoded REENTRANCY revertData. - // - // While it should be impossible for the call to fail in any other way (especially since it reverts before - // `manageUserBalance` even gets called), any other error would generate non-zero revertData, so checking for - // empty data guards against this case too. - - (, bytes memory revertData) = address(vault).staticcall{ gas: 10_000 }( - abi.encodeWithSelector(vault.manageUserBalance.selector, 0) - ); - - _require(revertData.length == 0, Errors.REENTRANCY); - } -} diff --git a/contracts/contracts/strategies/plume/RoosterAMOStrategy.sol b/contracts/contracts/strategies/plume/RoosterAMOStrategy.sol deleted file mode 100644 index 18ffc071ed..0000000000 --- a/contracts/contracts/strategies/plume/RoosterAMOStrategy.sol +++ /dev/null @@ -1,1205 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -/** - * @title Rooster AMO strategy - * @author Origin Protocol Inc - * @custom:security-contact security@originprotocol.com - */ -import { Math as MathRooster } from "../../../lib/rooster/v2-common/libraries/Math.sol"; -import { Math as Math_v5 } from "../../../lib/rooster/openzeppelin-custom/contracts/utils/math/Math.sol"; -import { StableMath } from "../../utils/StableMath.sol"; - -import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; - -import { IERC20, InitializableAbstractStrategy } from "../../utils/InitializableAbstractStrategy.sol"; -import { IVault } from "../../interfaces/IVault.sol"; -import { IMaverickV2Pool } from "../../interfaces/plume/IMaverickV2Pool.sol"; -import { IMaverickV2Quoter } from "../../interfaces/plume/IMaverickV2Quoter.sol"; -import { IMaverickV2LiquidityManager } from "../../interfaces/plume/IMaverickV2LiquidityManager.sol"; -import { IMaverickV2PoolLens } from "../../interfaces/plume/IMaverickV2PoolLens.sol"; -import { IMaverickV2Position } from "../../interfaces/plume/IMaverickV2Position.sol"; -import { IVotingDistributor } from "../../interfaces/plume/IVotingDistributor.sol"; -import { IPoolDistributor } from "../../interfaces/plume/IPoolDistributor.sol"; -// importing custom version of rooster TickMath because of dependency collision. Maverick uses -// a newer OpenZepplin Math library with functionality that is not present in 4.4.2 (the one we use) -import { TickMath } from "../../../lib/rooster/v2-common/libraries/TickMath.sol"; - -contract RoosterAMOStrategy is InitializableAbstractStrategy { - using StableMath for uint256; - using SafeERC20 for IERC20; - using SafeCast for uint256; - - /*************************************** - Storage slot members - ****************************************/ - - /// @notice NFT tokenId of the liquidity position - /// - /// @dev starts with value of 1 and can not be 0 - // solhint-disable-next-line max-line-length - /// https://github.com/rooster-protocol/rooster-contracts/blob/fbfecbc519e4495b12598024a42630b4a8ea4489/v2-common/contracts/base/Nft.sol#L14 - uint256 public tokenId; - /// @dev Minimum amount of tokens the strategy would be able to withdraw from the pool. - /// minimum amount of tokens are withdrawn at a 1:1 price - /// Important: Underlying assets contains only assets that are deposited in the underlying Rooster pool. - /// WETH or OETH held by this contract is not accounted for in underlying assets - uint256 public underlyingAssets; - /// @notice Marks the start of the interval that defines the allowed range of WETH share in - /// the pre-configured pool's liquidity ticker - uint256 public allowedWethShareStart; - /// @notice Marks the end of the interval that defines the allowed range of WETH share in - /// the pre-configured pool's liquidity ticker - uint256 public allowedWethShareEnd; - /// @dev reserved for inheritance - int256[46] private __reserved; - - /*************************************** - Constants, structs and events - ****************************************/ - - /// @notice The address of the Wrapped ETH (WETH) token contract - address public immutable WETH; - /// @notice The address of the OETH token contract - address public immutable OETH; - /// @notice the underlying AMO Maverick (Rooster) pool - IMaverickV2Pool public immutable mPool; - /// @notice the Liquidity manager used to add liquidity to the pool - IMaverickV2LiquidityManager public immutable liquidityManager; - /// @notice the Maverick V2 poolLens - /// - /// @dev only used to provide the pool's current sqrtPrice - IMaverickV2PoolLens public immutable poolLens; - /// @notice the Maverick V2 position - /// - /// @dev provides details of the NFT LP position and offers functions to - /// remove the liquidity. - IMaverickV2Position public immutable maverickPosition; - /// @notice the Maverick Quoter - IMaverickV2Quoter public immutable quoter; - /// @notice the Maverick Voting Distributor - IVotingDistributor public immutable votingDistributor; - /// @notice the Maverick Pool Distributor - IPoolDistributor public immutable poolDistributor; - - /// @notice sqrtPriceTickLower - /// @dev tick lower represents the lower price of OETH priced in WETH. Meaning the pool - /// offers more than 1 OETH for 1 WETH. In other terms to get 1 OETH the swap needs to offer 0.9999 WETH - /// this is where purchasing OETH with WETH within the liquidity position is the cheapest. - /// - /// _____________________ - /// | | | - /// | WETH | OETH | - /// | | | - /// | | | - /// --------- * ---- * ---------- * --------- - /// currentPrice - /// sqrtPriceHigher-(1:1 parity) - /// sqrtPriceLower - /// - /// - /// Price is defined as price of token1 in terms of token0. (token1 / token0) - /// @notice sqrtPriceTickLower - OETH is priced 0.9999 WETH - uint256 public immutable sqrtPriceTickLower; - /// @notice sqrtPriceTickHigher - /// @dev tick higher represents 1:1 price parity of WETH to OETH - uint256 public immutable sqrtPriceTickHigher; - /// @dev price at parity (in OETH this is equal to sqrtPriceTickHigher) - uint256 public immutable sqrtPriceAtParity; - /// @notice The tick where the strategy deploys the liquidity to - int32 public constant TICK_NUMBER = -1; - /// @notice Minimum liquidity that must be exceeded to continue with the action - /// e.g. deposit, add liquidity - uint256 public constant ACTION_THRESHOLD = 1e12; - /// @notice Maverick pool static liquidity bin type - uint8 public constant MAV_STATIC_BIN_KIND = 0; - /// @dev a threshold under which the contract no longer allows for the protocol to rebalance. Guarding - /// against a strategist / guardian being taken over and with multiple transactions draining the - /// protocol funds. - uint256 public constant SOLVENCY_THRESHOLD = 0.998 ether; - /// @notice Emitted when the allowed interval within which the strategy contract is allowed to deposit - /// liquidity to the underlying pool is updated. - /// @param allowedWethShareStart The start of the interval - /// @param allowedWethShareEnd The end of the interval - event PoolWethShareIntervalUpdated( - uint256 allowedWethShareStart, - uint256 allowedWethShareEnd - ); - /// @notice Emitted when liquidity is removed from the underlying pool - /// @param withdrawLiquidityShare Share of strategy's liquidity that has been removed - /// @param removedWETHAmount The amount of WETH removed - /// @param removedOETHAmount The amount of OETH removed - /// @param underlyingAssets Updated amount of strategy's underlying assets - event LiquidityRemoved( - uint256 withdrawLiquidityShare, - uint256 removedWETHAmount, - uint256 removedOETHAmount, - uint256 underlyingAssets - ); - - /// @notice Emitted when the underlying pool is rebalanced - /// @param currentPoolWethShare The resulting share of strategy's liquidity - /// in the TICK_NUMBER - event PoolRebalanced(uint256 currentPoolWethShare); - - /// @notice Emitted when the amount of underlying assets the strategy hold as - /// liquidity in the pool is updated. - /// @param underlyingAssets Updated amount of strategy's underlying assets - event UnderlyingAssetsUpdated(uint256 underlyingAssets); - - /// @notice Emitted when liquidity is added to the underlying pool - /// @param wethAmountDesired Amount of WETH desired to be deposited - /// @param oethAmountDesired Amount of OETH desired to be deposited - /// @param wethAmountSupplied Amount of WETH deposited - /// @param oethAmountSupplied Amount of OETH deposited - /// @param tokenId NFT liquidity token id - /// @param underlyingAssets Updated amount of underlying assets - event LiquidityAdded( - uint256 wethAmountDesired, - uint256 oethAmountDesired, - uint256 wethAmountSupplied, - uint256 oethAmountSupplied, - uint256 tokenId, - uint256 underlyingAssets - ); // 0x1530ec74 - - error PoolRebalanceOutOfBounds( - uint256 currentPoolWethShare, - uint256 allowedWethShareStart, - uint256 allowedWethShareEnd - ); // 0x3681e8e0 - - error NotEnoughWethForSwap(uint256 wethBalance, uint256 requiredWeth); // 0x989e5ca8 - error NotEnoughWethLiquidity(uint256 wethBalance, uint256 requiredWeth); // 0xa6737d87 - error OutsideExpectedTickRange(); // 0xa6e1bad2 - error SlippageCheck(uint256 tokenReceived); // 0x355cdb78 - - /// @notice the constructor - /// @dev This contract is intended to be used as a proxy. To prevent the - /// potential confusion of having a functional implementation contract - /// the constructor has the `initializer` modifier. This way the - /// `initialize` function can not be called on the implementation contract. - /// For the same reason the implementation contract also has the governor - /// set to a zero address. - /// @param _stratConfig the basic strategy configuration - /// @param _wethAddress Address of the Erc20 WETH Token contract - /// @param _oethAddress Address of the Erc20 OETH Token contract - /// @param _liquidityManager Address of liquidity manager to add - /// the liquidity - /// @param _poolLens Address of the pool lens contract - /// @param _maverickPosition Address of the Maverick's position contract - /// @param _maverickQuoter Address of the Maverick's Quoter contract - /// @param _mPool Address of the Rooster concentrated liquidity pool - /// @param _upperTickAtParity Bool when true upperTick is the one where the - /// price of OETH and WETH are at parity - constructor( - BaseStrategyConfig memory _stratConfig, - address _wethAddress, - address _oethAddress, - address _liquidityManager, - address _poolLens, - address _maverickPosition, - address _maverickQuoter, - address _mPool, - bool _upperTickAtParity, - address _votingDistributor, - address _poolDistributor - ) initializer InitializableAbstractStrategy(_stratConfig) { - require( - address(IMaverickV2Pool(_mPool).tokenA()) == _wethAddress, - "WETH not TokenA" - ); - require( - address(IMaverickV2Pool(_mPool).tokenB()) == _oethAddress, - "OETH not TokenB" - ); - require( - _liquidityManager != address(0), - "LiquidityManager zero address not allowed" - ); - require( - _maverickQuoter != address(0), - "Quoter zero address not allowed" - ); - require(_poolLens != address(0), "PoolLens zero address not allowed"); - require( - _maverickPosition != address(0), - "Position zero address not allowed" - ); - require( - _votingDistributor != address(0), - "Voting distributor zero address not allowed" - ); - require( - _poolDistributor != address(0), - "Pool distributor zero address not allowed" - ); - - uint256 _tickSpacing = IMaverickV2Pool(_mPool).tickSpacing(); - require(_tickSpacing == 1, "Unsupported tickSpacing"); - - // tickSpacing == 1 - (sqrtPriceTickLower, sqrtPriceTickHigher) = TickMath.tickSqrtPrices( - _tickSpacing, - TICK_NUMBER - ); - sqrtPriceAtParity = _upperTickAtParity - ? sqrtPriceTickHigher - : sqrtPriceTickLower; - - WETH = _wethAddress; - OETH = _oethAddress; - liquidityManager = IMaverickV2LiquidityManager(_liquidityManager); - poolLens = IMaverickV2PoolLens(_poolLens); - maverickPosition = IMaverickV2Position(_maverickPosition); - quoter = IMaverickV2Quoter(_maverickQuoter); - mPool = IMaverickV2Pool(_mPool); - votingDistributor = IVotingDistributor(_votingDistributor); - poolDistributor = IPoolDistributor(_poolDistributor); - - // prevent implementation contract to be governed - _setGovernor(address(0)); - } - - /** - * @notice initialize function, to set up initial internal state - */ - function initialize() external onlyGovernor initializer { - // Read reward - address[] memory _rewardTokens = new address[](1); - _rewardTokens[0] = poolDistributor.rewardToken(); - - require(_rewardTokens[0] != address(0), "No reward token configured"); - - InitializableAbstractStrategy._initialize( - _rewardTokens, - new address[](0), - new address[](0) - ); - } - - /*************************************** - Configuration - ****************************************/ - - /** - * @notice Set allowed pool weth share interval. After the rebalance happens - * the share of WETH token in the ticker needs to be within the specifications - * of the interval. - * - * @param _allowedWethShareStart Start of WETH share interval expressed as 18 decimal amount - * @param _allowedWethShareEnd End of WETH share interval expressed as 18 decimal amount - */ - function setAllowedPoolWethShareInterval( - uint256 _allowedWethShareStart, - uint256 _allowedWethShareEnd - ) external onlyGovernor { - require( - _allowedWethShareStart < _allowedWethShareEnd, - "Invalid interval" - ); - // can not go below 1% weth share - require(_allowedWethShareStart > 0.01 ether, "Invalid interval start"); - // can not go above 95% weth share - require(_allowedWethShareEnd < 0.95 ether, "Invalid interval end"); - - allowedWethShareStart = _allowedWethShareStart; - allowedWethShareEnd = _allowedWethShareEnd; - emit PoolWethShareIntervalUpdated( - _allowedWethShareStart, - _allowedWethShareEnd - ); - } - - /*************************************** - Strategy overrides - ****************************************/ - - /** - * @notice Deposits funds to the strategy which deposits them to the - * underlying Rooster pool if the pool price is within the expected interval. - * @param _asset Address for the asset - * @param _amount Units of asset to deposit - */ - function deposit(address _asset, uint256 _amount) - external - override - onlyVault - nonReentrant - { - _deposit(_asset, _amount); - } - - /** - * @notice Deposits all the funds to the strategy which deposits them to the - * underlying Rooster pool if the pool price is within the expected interval. - */ - function depositAll() external override onlyVault nonReentrant { - uint256 _wethBalance = IERC20(WETH).balanceOf(address(this)); - _deposit(WETH, _wethBalance); - } - - /** - * @dev Deposits funds to the strategy which deposits them to the - * underlying Rooster pool if the pool price is within the expected interval. - * Before this function can be called the initial pool position needs to already - * be minted. - * @param _asset Address of the asset to deposit - * @param _amount Amount of assets to deposit - */ - function _deposit(address _asset, uint256 _amount) internal { - require(_asset == WETH, "Unsupported asset"); - require(_amount > 0, "Must deposit something"); - require(tokenId > 0, "Initial position not minted"); - emit Deposit(_asset, address(0), _amount); - - // if the pool price is not within the expected interval leave the WETH on the contract - // as to not break the mints - in case it would be configured as a default asset strategy - (bool _isExpectedRange, ) = _checkForExpectedPoolPrice(false); - if (_isExpectedRange) { - // deposit funds into the underlying pool. Because no swap is performed there is no - // need to remove any of the liquidity beforehand. - _rebalance(0, false, 0, 0); - } - } - - /** - * @notice Withdraw an `amount` of WETH from the platform and - * send to the `_recipient`. - * @param _recipient Address to which the asset should be sent - * @param _asset WETH address - * @param _amount Amount of WETH to withdraw - */ - function withdraw( - address _recipient, - address _asset, - uint256 _amount - ) external override onlyVault nonReentrant { - require(_asset == WETH, "Unsupported asset"); - require(_amount > 0, "Must withdraw something"); - require(_recipient == vaultAddress, "Only withdraw to vault allowed"); - - _ensureWETHBalance(_amount); - - _withdraw(_recipient, _amount); - } - - /** - * @notice Withdraw WETH and sends it to the Vault. - */ - function withdrawAll() external override onlyVault nonReentrant { - if (tokenId != 0) { - _removeLiquidity(1e18); - } - - uint256 _balance = IERC20(WETH).balanceOf(address(this)); - if (_balance > 0) { - _withdraw(vaultAddress, _balance); - } - } - - function _withdraw(address _recipient, uint256 _amount) internal { - IERC20(WETH).safeTransfer(_recipient, _amount); - emit Withdrawal(WETH, address(0), _amount); - } - - /** - * @dev Retuns bool indicating whether asset is supported by strategy - * @param _asset Address of the asset - * @return bool True when the _asset is WETH - */ - function supportsAsset(address _asset) public view override returns (bool) { - return _asset == WETH; - } - - /** - * @dev Approve the spending amounts for the assets - */ - function _approveTokenAmounts( - uint256 _wethAllowance, - uint256 _oethAllowance - ) internal { - IERC20(WETH).approve(address(liquidityManager), _wethAllowance); - IERC20(OETH).approve(address(liquidityManager), _oethAllowance); - } - - /*************************************** - Liquidity management - ****************************************/ - /** - * @dev Add liquidity into the pool in the pre-configured WETH to OETH share ratios - * defined by the allowedPoolWethShareStart|End interval. - * - * Normally a PoolLens contract is used to prepare the parameters to add liquidity to the - * Rooster pools. It has some errors when doing those calculation and for that reason a - * much more accurate Quoter contract is used. This is possible due to our requirement of - * adding liquidity only to one tick - PoolLens supports adding liquidity into multiple ticks - * using different distribution ratios. - */ - function _addLiquidity() internal { - uint256 _wethBalance = IERC20(WETH).balanceOf(address(this)); - uint256 _oethBalance = IERC20(OETH).balanceOf(address(this)); - // don't deposit small liquidity amounts - if (_wethBalance <= ACTION_THRESHOLD) { - return; - } - - ( - bytes memory packedSqrtPriceBreaks, - bytes[] memory packedArgs, - uint256 WETHRequired, - uint256 OETHRequired - ) = _getAddLiquidityParams(_wethBalance, 1e30); - - if (OETHRequired > _oethBalance) { - IVault(vaultAddress).mintForStrategy(OETHRequired - _oethBalance); - } - - _approveTokenAmounts(WETHRequired, OETHRequired); - - ( - uint256 _wethAmount, - uint256 _oethAmount, - uint32[] memory binIds - ) = liquidityManager.addPositionLiquidityToSenderByTokenIndex( - mPool, - 0, // NFT token index - packedSqrtPriceBreaks, - packedArgs - ); - - require(binIds.length == 1, "Unexpected binIds length"); - - // burn remaining OETH - _burnOethOnTheContract(); - _updateUnderlyingAssets(); - - // needs to be called after _updateUnderlyingAssets so the updated amount - // is reflected in the event - emit LiquidityAdded( - _wethBalance, // wethAmountDesired - OETHRequired, // oethAmountDesired - _wethAmount, // wethAmountSupplied - _oethAmount, // oethAmountSupplied - tokenId, // tokenId - underlyingAssets - ); - } - - /** - * @dev The function creates liquidity parameters required to be able to add liquidity to the pool. - * The function needs to handle the 3 different cases of the way liquidity is added: - * - only WETH present in the tick - * - only OETH present in the tick - * - both tokens present in the tick - * - */ - function _getAddLiquidityParams(uint256 _maxWETH, uint256 _maxOETH) - internal - returns ( - bytes memory packedSqrtPriceBreaks, - bytes[] memory packedArgs, - uint256 WETHRequired, - uint256 OETHRequired - ) - { - IMaverickV2Pool.AddLiquidityParams[] - memory addParams = new IMaverickV2Pool.AddLiquidityParams[](1); - int32[] memory ticks = new int32[](1); - uint128[] memory amounts = new uint128[](1); - ticks[0] = TICK_NUMBER; - // arbitrary LP amount - amounts[0] = 1e24; - - // construct value for Quoter with arbitrary LP amount - IMaverickV2Pool.AddLiquidityParams memory addParam = IMaverickV2Pool - .AddLiquidityParams({ - kind: MAV_STATIC_BIN_KIND, - ticks: ticks, - amounts: amounts - }); - - // get the WETH and OETH required to get the proportion of tokens required - // given the arbitrary liquidity - (WETHRequired, OETHRequired, ) = quoter.calculateAddLiquidity( - mPool, - addParam - ); - - /** - * If either token required is 0 then the tick consists only of the other token. In that - * case the liquidity calculations need to be done using the non 0 token. By setting the - * tokenRequired from 0 to 1 the `min` in next step will ignore that (the bigger) value. - */ - WETHRequired = WETHRequired == 0 ? 1 : WETHRequired; - OETHRequired = OETHRequired == 0 ? 1 : OETHRequired; - - addParam.amounts[0] = Math_v5 - .min( - ((_maxWETH - 1) * 1e24) / WETHRequired, - ((_maxOETH - 1) * 1e24) / OETHRequired - ) - .toUint128(); - - // update the quotes with the actual amounts - (WETHRequired, OETHRequired, ) = quoter.calculateAddLiquidity( - mPool, - addParam - ); - - require(_maxWETH >= WETHRequired, "More WETH required than specified"); - require(_maxOETH >= OETHRequired, "More OETH required than specified"); - - // organize values to be used by manager - addParams[0] = addParam; - packedArgs = liquidityManager.packAddLiquidityArgsArray(addParams); - // price can stay 0 if array only has one element - packedSqrtPriceBreaks = liquidityManager.packUint88Array( - new uint88[](1) - ); - } - - /** - * @dev Check that the Rooster pool price is within the expected - * parameters. - * This function works whether the strategy contract has liquidity - * position in the pool or not. The function returns _wethSharePct - * as a gas optimization measure. - * @param _throwException when set to true the function throws an exception - * when pool's price is not within expected range. - * @return _isExpectedRange Bool expressing price is within expected range - * @return _wethSharePct Share of WETH owned by this strategy contract in the - * configured ticker. - */ - function _checkForExpectedPoolPrice(bool _throwException) - internal - view - returns (bool _isExpectedRange, uint256 _wethSharePct) - { - require( - allowedWethShareStart != 0 && allowedWethShareEnd != 0, - "Weth share interval not set" - ); - - uint256 _currentPrice = getPoolSqrtPrice(); - - /** - * First check pool price is in expected tick range - * - * A revert is issued even though price being equal to the lower bound as that can not - * be within the approved tick range. - */ - if ( - _currentPrice <= sqrtPriceTickLower || - _currentPrice >= sqrtPriceTickHigher - ) { - if (_throwException) { - revert OutsideExpectedTickRange(); - } - - return (false, _currentPrice <= sqrtPriceTickLower ? 0 : 1e18); - } - - // 18 decimal number expressed WETH tick share - _wethSharePct = _getWethShare(_currentPrice); - - if ( - _wethSharePct < allowedWethShareStart || - _wethSharePct > allowedWethShareEnd - ) { - if (_throwException) { - revert PoolRebalanceOutOfBounds( - _wethSharePct, - allowedWethShareStart, - allowedWethShareEnd - ); - } - return (false, _wethSharePct); - } - - return (true, _wethSharePct); - } - - /** - * @notice Rebalance the pool to the desired token split and Deposit any WETH on the contract to the - * underlying rooster pool. Print the required amount of corresponding OETH. After the rebalancing is - * done burn any potentially remaining OETH tokens still on the strategy contract. - * - * This function has a slightly different behaviour depending on the status of the underlying Rooster - * pool. The function consists of the following 3 steps: - * 1. withdrawLiquidityOption -> this is a configurable option where either only part of the liquidity - * necessary for the swap is removed, or all of it. This way the rebalance - * is able to optimize for volume, for efficiency or anything in between - * 2. swapToDesiredPosition -> move active trading price in the pool to be able to deposit WETH & OETH - * tokens with the desired pre-configured ratios - * 3. addLiquidity -> add liquidity into the pool respecting ratio split configuration - * - * - * Exact _amountToSwap, _swapWeth & _minTokenReceived parameters shall be determined by simulating the - * transaction off-chain. The strategy checks that after the swap the share of the tokens is in the - * expected ranges. - * - * @param _amountToSwap The amount of the token to swap - * @param _swapWeth Swap using WETH when true, use OETH when false - * @param _minTokenReceived Slippage check -> minimum amount of token expected in return - * @param _liquidityToRemovePct Percentage of liquidity to remove -> the percentage amount of liquidity to - * remove before performing the swap. 1e18 denominated - */ - function rebalance( - uint256 _amountToSwap, - bool _swapWeth, - uint256 _minTokenReceived, - uint256 _liquidityToRemovePct - ) external nonReentrant onlyGovernorOrStrategist { - _rebalance( - _amountToSwap, - _swapWeth, - _minTokenReceived, - _liquidityToRemovePct - ); - } - - // slither-disable-start reentrancy-no-eth - function _rebalance( - uint256 _amountToSwap, - bool _swapWeth, - uint256 _minTokenReceived, - uint256 _liquidityToRemovePct - ) internal { - // Remove the required amount of liquidity - if (_liquidityToRemovePct > 0) { - _removeLiquidity(_liquidityToRemovePct); - } - - // in some cases (e.g. deposits) we will just want to add liquidity and not - // issue a swap to move the active trading position within the pool. Before or after a - // deposit or as a standalone call the strategist might issue a rebalance to move the - // active trading price to a more desired position. - if (_amountToSwap > 0) { - // In case liquidity has been removed and there is still not enough WETH owned by the - // strategy contract remove additional required amount of WETH. - if (_swapWeth) _ensureWETHBalance(_amountToSwap); - - _swapToDesiredPosition(_amountToSwap, _swapWeth, _minTokenReceived); - } - - // calling check liquidity early so we don't get unexpected errors when adding liquidity - // in the later stages of this function - _checkForExpectedPoolPrice(true); - - _addLiquidity(); - - // this call shouldn't be necessary, since adding liquidity shouldn't affect the active - // trading price. It is a defensive programming measure. - (, uint256 _wethSharePct) = _checkForExpectedPoolPrice(true); - - // revert if protocol insolvent - _solvencyAssert(); - - emit PoolRebalanced(_wethSharePct); - } - - // slither-disable-end reentrancy-no-eth - - /** - * @dev Perform a swap so that after the swap the tick has the desired WETH to OETH token share. - */ - function _swapToDesiredPosition( - uint256 _amountToSwap, - bool _swapWeth, - uint256 _minTokenReceived - ) internal { - IERC20 _tokenToSwap = IERC20(_swapWeth ? WETH : OETH); - uint256 _balance = _tokenToSwap.balanceOf(address(this)); - - if (_balance < _amountToSwap) { - // This should never trigger since _ensureWETHBalance will already - // throw an error if there is not enough WETH - if (_swapWeth) { - revert NotEnoughWethForSwap(_balance, _amountToSwap); - } - // if swapping OETH - uint256 mintForSwap = _amountToSwap - _balance; - IVault(vaultAddress).mintForStrategy(mintForSwap); - } - - // SafeERC20 is used for IERC20 transfers. Not sure why slither complains - // slither-disable-next-line unchecked-transfer - _tokenToSwap.transfer(address(mPool), _amountToSwap); - - // tickLimit: the furthest tick a swap will execute in. If no limit is desired, - // value should be set to type(int32).max for a tokenAIn (WETH) swap - // and type(int32).min for a swap where tokenB (OETH) is the input - - IMaverickV2Pool.SwapParams memory swapParams = IMaverickV2Pool - // exactOutput defines whether the amount specified is the output - // or the input amount of the swap - .SwapParams({ - amount: _amountToSwap, - tokenAIn: _swapWeth, - exactOutput: false, - tickLimit: TICK_NUMBER - }); - - // swaps without a callback as the assets are already sent to the pool - (, uint256 amountOut) = mPool.swap( - address(this), - swapParams, - bytes("") - ); - - /** - * There could be additional checks here for validating minTokenReceived is within the - * expected range (e.g. 99% - 101% of the token sent in). Though that doesn't provide - * any additional security. After the swap the `_checkForExpectedPoolPrice` validates - * that the swap has moved the price into the expected tick (# -1). - * - * If the guardian forgets to set a `_minTokenReceived` and a sandwich attack bends - * the pool before the swap the `_checkForExpectedPoolPrice` will fail the transaction. - * - * A check would not prevent a compromised guardian from stealing funds as multiple - * transactions each loosing smaller amount of funds are still possible. - */ - if (amountOut < _minTokenReceived) { - revert SlippageCheck(amountOut); - } - - /** - * In the interest of each function in `_rebalance` to leave the contract state as - * clean as possible the OETH tokens here are burned. This decreases the - * dependence where `_swapToDesiredPosition` function relies on later functions - * (`addLiquidity`) to burn the OETH. Reducing the risk of error introduction. - */ - _burnOethOnTheContract(); - } - - /** - * @dev This function removes the appropriate amount of liquidity to ensure that the required - * amount of WETH is available on the contract - * - * @param _amount WETH balance required on the contract - */ - function _ensureWETHBalance(uint256 _amount) internal { - uint256 _wethBalance = IERC20(WETH).balanceOf(address(this)); - if (_wethBalance >= _amount) { - return; - } - - require(tokenId != 0, "No liquidity available"); - uint256 _additionalWethRequired = _amount - _wethBalance; - (uint256 _wethInThePool, ) = getPositionPrincipal(); - - if (_wethInThePool < _additionalWethRequired) { - revert NotEnoughWethLiquidity( - _wethInThePool, - _additionalWethRequired - ); - } - - uint256 shareOfWethToRemove = _wethInThePool <= 1 - ? 1e18 - : Math_v5.min( - /** - * When dealing with shares of liquidity to remove there is always some - * rounding involved. After extensive fuzz testing the below approach - * yielded the best results where the strategy overdraws the least and - * never removes insufficient amount of WETH. - */ - (_additionalWethRequired + 2).divPrecisely(_wethInThePool - 1) + - 2, - 1e18 - ); - - _removeLiquidity(shareOfWethToRemove); - } - - /** - * @dev Decrease partial or all liquidity from the pool. - * @param _liquidityToDecrease The amount of liquidity to remove denominated in 1e18 - */ - function _removeLiquidity(uint256 _liquidityToDecrease) internal { - require(_liquidityToDecrease > 0, "Must remove some liquidity"); - require( - _liquidityToDecrease <= 1e18, - "Can not remove more than 100% of liquidity" - ); - - // 0 indicates the first (and only) bin in the NFT LP position. - IMaverickV2Pool.RemoveLiquidityParams memory params = maverickPosition - .getRemoveParams(tokenId, 0, _liquidityToDecrease); - (uint256 _amountWeth, uint256 _amountOeth) = maverickPosition - .removeLiquidityToSender(tokenId, mPool, params); - - _burnOethOnTheContract(); - _updateUnderlyingAssets(); - - // needs to be called after the _updateUnderlyingAssets so the updated amount is reflected - // in the event - emit LiquidityRemoved( - _liquidityToDecrease, - _amountWeth, - _amountOeth, - underlyingAssets - ); - } - - /** - * @dev Burns any OETH tokens remaining on the strategy contract if the balance is - * above the action threshold. - */ - function _burnOethOnTheContract() internal { - uint256 _oethBalance = IERC20(OETH).balanceOf(address(this)); - IVault(vaultAddress).burnForStrategy(_oethBalance); - } - - /** - * @notice Returns the percentage of WETH liquidity in the configured ticker - * owned by this strategy contract. - * @return uint256 1e18 denominated percentage expressing the share - */ - function getWETHShare() external view returns (uint256) { - uint256 _currentPrice = getPoolSqrtPrice(); - return _getWethShare(_currentPrice); - } - - /** - * @dev Returns the share of WETH in tick denominated in 1e18 - */ - function _getWethShare(uint256 _currentPrice) - internal - view - returns (uint256) - { - ( - uint256 wethAmount, - uint256 oethAmount - ) = _reservesInTickForGivenPriceAndLiquidity( - sqrtPriceTickLower, - sqrtPriceTickHigher, - _currentPrice, - 1e24 - ); - - return wethAmount.divPrecisely(wethAmount + oethAmount); - } - - /** - * @notice Returns the current pool price in square root - * @return Square root of the pool price - */ - function getPoolSqrtPrice() public view returns (uint256) { - return poolLens.getPoolSqrtPrice(mPool); - } - - /** - * @notice Returns the current active trading tick of the underlying pool - * @return _currentTick Current pool trading tick - */ - function getCurrentTradingTick() public view returns (int32 _currentTick) { - _currentTick = mPool.getState().activeTick; - } - - /** - * @notice Mint the initial NFT position - * - * @dev This amount is "gifted" to the strategy contract and will count as a yield - * surplus. - */ - // slither-disable-start reentrancy-no-eth - function mintInitialPosition() external onlyGovernor nonReentrant { - require(tokenId == 0, "Initial position already minted"); - ( - bytes memory packedSqrtPriceBreaks, - bytes[] memory packedArgs, - uint256 WETHRequired, - uint256 OETHRequired - ) = _getAddLiquidityParams(1e16, 1e16); - - // Mint rounded up OETH amount - if (OETHRequired > 0) { - IVault(vaultAddress).mintForStrategy(OETHRequired); - } - - _approveTokenAmounts(WETHRequired, OETHRequired); - - // Store the tokenId before calling updateUnderlyingAssets as it relies on the tokenId - // not being 0 - (, , , tokenId) = liquidityManager.mintPositionNftToSender( - mPool, - packedSqrtPriceBreaks, - packedArgs - ); - - // burn remaining OETH - _burnOethOnTheContract(); - _updateUnderlyingAssets(); - } - - // slither-disable-end reentrancy-no-eth - - /** - * @notice Returns the balance of tokens the strategy holds in the LP position - * @return _amountWeth Amount of WETH in position - * @return _amountOeth Amount of OETH in position - */ - function getPositionPrincipal() - public - view - returns (uint256 _amountWeth, uint256 _amountOeth) - { - if (tokenId == 0) { - return (0, 0); - } - - (_amountWeth, _amountOeth, ) = _getPositionInformation(); - } - - /** - * @dev Returns the balance of tokens the strategy holds in the LP position - * @return _amountWeth Amount of WETH in position - * @return _amountOeth Amount of OETH in position - * @return liquidity Amount of liquidity in the position - */ - function _getPositionInformation() - internal - view - returns ( - uint256 _amountWeth, - uint256 _amountOeth, - uint256 liquidity - ) - { - IMaverickV2Position.PositionFullInformation - memory positionInfo = maverickPosition.tokenIdPositionInformation( - tokenId, - 0 - ); - - require( - positionInfo.liquidities.length == 1, - "Unexpected liquidities length" - ); - require(positionInfo.ticks.length == 1, "Unexpected ticks length"); - - _amountWeth = positionInfo.amountA; - _amountOeth = positionInfo.amountB; - liquidity = positionInfo.liquidities[0]; - } - - /** - * Checks that the protocol is solvent, protecting from a rogue Strategist / Guardian that can - * keep rebalancing the pool in both directions making the protocol lose a tiny amount of - * funds each time. - * - * Protocol must be at least SOLVENCY_THRESHOLD (99.8%) backed in order for the rebalances to - * function. - */ - function _solvencyAssert() internal view { - uint256 _totalVaultValue = IVault(vaultAddress).totalValue(); - uint256 _totalOethSupply = IERC20(OETH).totalSupply(); - - if ( - _totalVaultValue.divPrecisely(_totalOethSupply) < SOLVENCY_THRESHOLD - ) { - revert("Protocol insolvent"); - } - } - - /** - * @dev Collect Rooster reward token, and send it to the harvesterAddress - */ - function collectRewardTokens() - external - override - onlyHarvester - nonReentrant - { - // Do nothing if there's no position minted - if (tokenId > 0) { - uint32[] memory binIds = new uint32[](1); - IMaverickV2Pool.TickState memory tickState = mPool.getTick( - TICK_NUMBER - ); - // get the binId for the MAV_STATIC_BIN_KIND in tick TICK_NUMBER (-1) - binIds[0] = tickState.binIdsByTick[0]; - - uint256 lastEpoch = votingDistributor.lastEpoch(); - - poolDistributor.claimLp( - address(this), - tokenId, - mPool, - binIds, - lastEpoch - ); - } - - // Run the internal inherited function - _collectRewardTokens(); - } - - /*************************************** - Balances and Fees - ****************************************/ - - /** - * @dev Get the total asset value held in the platform - * @param _asset Address of the asset - * @return balance Total value of the asset in the platform - */ - function checkBalance(address _asset) - external - view - override - returns (uint256) - { - require(_asset == WETH, "Only WETH supported"); - - // because of PoolLens inaccuracy there is usually some dust WETH left on the contract - uint256 _wethBalance = IERC20(WETH).balanceOf(address(this)); - // just paranoia check, in case there is OETH in the strategy that for some reason hasn't - // been burned yet. This should always be 0. - uint256 _oethBalance = IERC20(OETH).balanceOf(address(this)); - return underlyingAssets + _wethBalance + _oethBalance; - } - - /// @dev This function updates the amount of underlying assets with the approach of the least possible - /// total tokens extracted for the current liquidity in the pool. - function _updateUnderlyingAssets() internal { - /** - * Our net value represent the smallest amount of tokens we are able to extract from the position - * given our liquidity. - * - * The least amount of tokens ex-tractable from the position is where the active trading price is - * at the edge between tick -1 & tick 0. There the pool is offering 1:1 trades between WETH & OETH. - * At that moment the pool consists completely of WETH and no OETH. - * - * The more swaps from OETH -> WETH happen on the pool the more the price starts to move away from the tick 0 - * towards the middle of tick -1 making OETH (priced in WETH) cheaper. - */ - - uint256 _wethAmount = tokenId == 0 ? 0 : _balanceInPosition(); - - underlyingAssets = _wethAmount; - emit UnderlyingAssetsUpdated(_wethAmount); - } - - /** - * @dev Strategy reserves (which consist only of WETH in case of Rooster - Plume pool) - * when the tick price is closest to parity - assuring the lowest amount of tokens - * returned for the current position liquidity. - */ - function _balanceInPosition() internal view returns (uint256 _wethBalance) { - (, , uint256 liquidity) = _getPositionInformation(); - - uint256 _oethBalance; - - (_wethBalance, _oethBalance) = _reservesInTickForGivenPriceAndLiquidity( - sqrtPriceTickLower, - sqrtPriceTickHigher, - sqrtPriceAtParity, - liquidity - ); - - require(_oethBalance == 0, "Non zero oethBalance"); - } - - /** - * @notice Tick dominance denominated in 1e18 - * @return _tickDominance The share of liquidity in TICK_NUMBER tick owned - * by the strategy contract denominated in 1e18 - */ - function tickDominance() public view returns (uint256 _tickDominance) { - IMaverickV2Pool.TickState memory tickState = mPool.getTick(TICK_NUMBER); - - uint256 wethReserve = tickState.reserveA; - uint256 oethReserve = tickState.reserveB; - - // prettier-ignore - (uint256 _amountWeth, uint256 _amountOeth, ) = _getPositionInformation(); - - if (wethReserve + oethReserve == 0) { - return 0; - } - - _tickDominance = (_amountWeth + _amountOeth).divPrecisely( - wethReserve + oethReserve - ); - } - - /*************************************** - Hidden functions - ****************************************/ - /** - * @dev Unsupported - */ - function setPTokenAddress(address, address) external pure override { - // The pool tokens can never change. - revert("Unsupported method"); - } - - /** - * @dev Unsupported - */ - function removePToken(uint256) external pure override { - // The pool tokens can never change. - revert("Unsupported method"); - } - - /** - * @dev Unsupported - */ - function _abstractSetPToken(address, address) internal pure override { - revert("Unsupported method"); - } - - /** - * @dev Unsupported - */ - function safeApproveAllTokens() external pure override { - // all the amounts are approved at the time required - revert("Unsupported method"); - } - - /*************************************** - Maverick liquidity utilities - ****************************************/ - - /// @notice Calculates deltaA = liquidity * (sqrt(upper) - sqrt(lower)) - /// Calculates deltaB = liquidity / sqrt(lower) - liquidity / sqrt(upper), - /// i.e. liquidity * (sqrt(upper) - sqrt(lower)) / (sqrt(upper) * sqrt(lower)) - /// - /// @dev refactored from here: - // solhint-disable-next-line max-line-length - /// https://github.com/rooster-protocol/rooster-contracts/blob/main/v2-supplemental/contracts/libraries/LiquidityUtilities.sol#L665-L695 - function _reservesInTickForGivenPriceAndLiquidity( - uint256 _lowerSqrtPrice, - uint256 _upperSqrtPrice, - uint256 _newSqrtPrice, - uint256 _liquidity - ) internal pure returns (uint128 reserveA, uint128 reserveB) { - if (_liquidity == 0) { - (reserveA, reserveB) = (0, 0); - } else { - uint256 lowerEdge = MathRooster.max(_lowerSqrtPrice, _newSqrtPrice); - - reserveA = MathRooster - .mulCeil( - _liquidity, - MathRooster.clip( - MathRooster.min(_upperSqrtPrice, _newSqrtPrice), - _lowerSqrtPrice - ) - ) - .toUint128(); - reserveB = MathRooster - .mulDivCeil( - _liquidity, - 1e18 * MathRooster.clip(_upperSqrtPrice, lowerEdge), - _upperSqrtPrice * lowerEdge - ) - .toUint128(); - } - } -} diff --git a/contracts/contracts/strategies/sonic/README.md b/contracts/contracts/strategies/sonic/README.md index 3cd7f3b743..93ecd51a2f 100644 --- a/contracts/contracts/strategies/sonic/README.md +++ b/contracts/contracts/strategies/sonic/README.md @@ -14,20 +14,6 @@ ![Sonic Staking Strategy Storage](../../../docs/SonicStakingStrategyStorage.svg) -## Curve AMO Strategy - -### Hierarchy - -![Curve AMO Strategy Hierarchy](../../../docs/SonicCurveAMOStrategyHierarchy.svg) - -### Squashed - -![Curve AMO Strategy Squashed](../../../docs/SonicCurveAMOStrategySquashed.svg) - -### Storage - -![Curve AMO Strategy Storage](../../../docs/SonicCurveAMOStrategyStorage.svg) - ## SwapX AMO Strategy ### Hierarchy diff --git a/contracts/contracts/swapper/README.md b/contracts/contracts/swapper/README.md deleted file mode 100644 index 4a2d3ef7a5..0000000000 --- a/contracts/contracts/swapper/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# Diagrams - -## 1Inch V5 Swapper - -### Hierarchy - -![1Inch V5 Swapper Hierarchy](../../docs/Swapper1InchV5Hierarchy.svg) - -### Squashed - -![1Inch V5 Swapper Squashed](../../docs/Swapper1InchV5Squashed.svg) diff --git a/contracts/contracts/swapper/Swapper1InchV5.sol b/contracts/contracts/swapper/Swapper1InchV5.sol deleted file mode 100644 index 1ca4afb3e0..0000000000 --- a/contracts/contracts/swapper/Swapper1InchV5.sol +++ /dev/null @@ -1,107 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -/** - * @notice 1Inch Pathfinder V5 implementation of the general ISwapper interface. - * @author Origin Protocol Inc - * @dev It is possible that dust token amounts are left in this contract after a swap. - * This can happen with some tokens that don't send the full transfer amount. - * These dust amounts can build up over time and be used by anyone who calls the `swap` function. - */ - -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; - -import { IAggregationExecutor, IOneInchRouter, SwapDescription } from "../interfaces/IOneInch.sol"; -import { ISwapper } from "../interfaces/ISwapper.sol"; - -contract Swapper1InchV5 is ISwapper { - using SafeERC20 for IERC20; - - /// @notice 1Inch router contract to give allowance to perform swaps - address public constant SWAP_ROUTER = - 0x1111111254EEB25477B68fb85Ed929f73A960582; - - // swap(address,(address,address,address,address,uint256,uint256,uint256),bytes,bytes) - bytes4 internal constant SWAP_SELECTOR = 0x12aa3caf; - // unoswapTo(address,address,uint256,uint256,uint256[]) - bytes4 internal constant UNISWAP_SELECTOR = 0xf78dc253; - // uniswapV3SwapTo(address,uint256,uint256,uint256[]) - bytes4 internal constant UNISWAPV3_SELECTOR = 0xbc80f1a8; - - /** - * @notice Strategist swaps assets sitting in the contract of the `assetHolder`. - * @param _fromAsset The token address of the asset being sold by the vault. - * @param _toAsset The token address of the asset being purchased by the vault. - * @param _fromAssetAmount The amount of assets being sold by the vault. - * @param _minToAssetAmount The minimum amount of assets to be purchased. - * @param _data RLP encoded executer address and bytes data. This is re-encoded tx.data from 1Inch swap API - */ - function swap( - address _fromAsset, - address _toAsset, - uint256 _fromAssetAmount, - uint256 _minToAssetAmount, - bytes calldata _data - ) external override returns (uint256 toAssetAmount) { - // Decode the function selector from the RLP encoded _data param - bytes4 swapSelector = bytes4(_data[:4]); - - if (swapSelector == SWAP_SELECTOR) { - // Decode the executer address and data from the RLP encoded _data param - (, address executer, bytes memory executerData) = abi.decode( - _data, - (bytes4, address, bytes) - ); - SwapDescription memory swapDesc = SwapDescription({ - srcToken: IERC20(_fromAsset), - dstToken: IERC20(_toAsset), - srcReceiver: payable(executer), - dstReceiver: payable(msg.sender), - amount: _fromAssetAmount, - minReturnAmount: _minToAssetAmount, - flags: 4 // 1st bit _PARTIAL_FILL, 2nd bit _REQUIRES_EXTRA_ETH, 3rd bit _SHOULD_CLAIM - }); - (toAssetAmount, ) = IOneInchRouter(SWAP_ROUTER).swap( - IAggregationExecutor(executer), - swapDesc, - hex"", - executerData - ); - } else if (swapSelector == UNISWAP_SELECTOR) { - // Need to get the Uniswap pools data from the _data param - (, uint256[] memory pools) = abi.decode(_data, (bytes4, uint256[])); - toAssetAmount = IOneInchRouter(SWAP_ROUTER).unoswapTo( - payable(msg.sender), - IERC20(_fromAsset), - _fromAssetAmount, - _minToAssetAmount, - pools - ); - } else if (swapSelector == UNISWAPV3_SELECTOR) { - // Need to get the Uniswap pools data from the _data param - // slither-disable-next-line uninitialized-storage - (, uint256[] memory pools) = abi.decode(_data, (bytes4, uint256[])); - toAssetAmount = IOneInchRouter(SWAP_ROUTER).uniswapV3SwapTo( - payable(msg.sender), - _fromAssetAmount, - _minToAssetAmount, - pools - ); - } else { - revert("Unsupported swap function"); - } - } - - /** - * @notice Approve assets for swapping. - * @param _assets Array of token addresses to approve. - * @dev unlimited approval is used as no tokens sit in this contract outside a transaction. - */ - function approveAssets(address[] memory _assets) external { - for (uint256 i = 0; i < _assets.length; ++i) { - // Give the 1Inch router approval to transfer unlimited assets - IERC20(_assets[i]).safeApprove(SWAP_ROUTER, type(uint256).max); - } - } -} diff --git a/contracts/contracts/utils/BalancerErrors.sol b/contracts/contracts/utils/BalancerErrors.sol deleted file mode 100644 index 0d3f2d591f..0000000000 --- a/contracts/contracts/utils/BalancerErrors.sol +++ /dev/null @@ -1,305 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -pragma solidity >=0.7.4 <0.9.0; - -// solhint-disable - -/** - * @dev Reverts if `condition` is false, with a revert reason containing `errorCode`. Only codes up to 999 are - * supported. - * Uses the default 'BAL' prefix for the error code - */ -function _require(bool condition, uint256 errorCode) pure { - if (!condition) _revert(errorCode); -} - -/** - * @dev Reverts if `condition` is false, with a revert reason containing `errorCode`. Only codes up to 999 are - * supported. - */ -function _require( - bool condition, - uint256 errorCode, - bytes3 prefix -) pure { - if (!condition) _revert(errorCode, prefix); -} - -/** - * @dev Reverts with a revert reason containing `errorCode`. Only codes up to 999 are supported. - * Uses the default 'BAL' prefix for the error code - */ -function _revert(uint256 errorCode) pure { - _revert(errorCode, 0x42414c); // This is the raw byte representation of "BAL" -} - -/** - * @dev Reverts with a revert reason containing `errorCode`. Only codes up to 999 are supported. - */ -function _revert(uint256 errorCode, bytes3 prefix) pure { - uint256 prefixUint = uint256(uint24(prefix)); - // We're going to dynamically create a revert string based on the error code, with the following format: - // 'BAL#{errorCode}' - // where the code is left-padded with zeroes to three digits (so they range from 000 to 999). - // - // We don't have revert strings embedded in the contract to save bytecode size: it takes much less space to store a - // number (8 to 16 bits) than the individual string characters. - // - // The dynamic string creation algorithm that follows could be implemented in Solidity, but assembly allows for a - // much denser implementation, again saving bytecode size. Given this function unconditionally reverts, this is a - // safe place to rely on it without worrying about how its usage might affect e.g. memory contents. - assembly { - // First, we need to compute the ASCII representation of the error code. We assume that it is in the 0-999 - // range, so we only need to convert three digits. To convert the digits to ASCII, we add 0x30, the value for - // the '0' character. - - let units := add(mod(errorCode, 10), 0x30) - - errorCode := div(errorCode, 10) - let tenths := add(mod(errorCode, 10), 0x30) - - errorCode := div(errorCode, 10) - let hundreds := add(mod(errorCode, 10), 0x30) - - // With the individual characters, we can now construct the full string. - // We first append the '#' character (0x23) to the prefix. In the case of 'BAL', it results in 0x42414c23 ('BAL#') - // Then, we shift this by 24 (to provide space for the 3 bytes of the error code), and add the - // characters to it, each shifted by a multiple of 8. - // The revert reason is then shifted left by 200 bits (256 minus the length of the string, 7 characters * 8 bits - // per character = 56) to locate it in the most significant part of the 256 slot (the beginning of a byte - // array). - let formattedPrefix := shl(24, add(0x23, shl(8, prefixUint))) - - let revertReason := shl( - 200, - add( - formattedPrefix, - add(add(units, shl(8, tenths)), shl(16, hundreds)) - ) - ) - - // We can now encode the reason in memory, which can be safely overwritten as we're about to revert. The encoded - // message will have the following layout: - // [ revert reason identifier ] [ string location offset ] [ string length ] [ string contents ] - - // The Solidity revert reason identifier is 0x08c739a0, the function selector of the Error(string) function. We - // also write zeroes to the next 28 bytes of memory, but those are about to be overwritten. - mstore( - 0x0, - 0x08c379a000000000000000000000000000000000000000000000000000000000 - ) - // Next is the offset to the location of the string, which will be placed immediately after (20 bytes away). - mstore( - 0x04, - 0x0000000000000000000000000000000000000000000000000000000000000020 - ) - // The string length is fixed: 7 characters. - mstore(0x24, 7) - // Finally, the string itself is stored. - mstore(0x44, revertReason) - - // Even if the string is only 7 bytes long, we need to return a full 32 byte slot containing it. The length of - // the encoded message is therefore 4 + 32 + 32 + 32 = 100. - revert(0, 100) - } -} - -library Errors { - // Math - uint256 internal constant ADD_OVERFLOW = 0; - uint256 internal constant SUB_OVERFLOW = 1; - uint256 internal constant SUB_UNDERFLOW = 2; - uint256 internal constant MUL_OVERFLOW = 3; - uint256 internal constant ZERO_DIVISION = 4; - uint256 internal constant DIV_INTERNAL = 5; - uint256 internal constant X_OUT_OF_BOUNDS = 6; - uint256 internal constant Y_OUT_OF_BOUNDS = 7; - uint256 internal constant PRODUCT_OUT_OF_BOUNDS = 8; - uint256 internal constant INVALID_EXPONENT = 9; - - // Input - uint256 internal constant OUT_OF_BOUNDS = 100; - uint256 internal constant UNSORTED_ARRAY = 101; - uint256 internal constant UNSORTED_TOKENS = 102; - uint256 internal constant INPUT_LENGTH_MISMATCH = 103; - uint256 internal constant ZERO_TOKEN = 104; - uint256 internal constant INSUFFICIENT_DATA = 105; - - // Shared pools - uint256 internal constant MIN_TOKENS = 200; - uint256 internal constant MAX_TOKENS = 201; - uint256 internal constant MAX_SWAP_FEE_PERCENTAGE = 202; - uint256 internal constant MIN_SWAP_FEE_PERCENTAGE = 203; - uint256 internal constant MINIMUM_BPT = 204; - uint256 internal constant CALLER_NOT_VAULT = 205; - uint256 internal constant UNINITIALIZED = 206; - uint256 internal constant BPT_IN_MAX_AMOUNT = 207; - uint256 internal constant BPT_OUT_MIN_AMOUNT = 208; - uint256 internal constant EXPIRED_PERMIT = 209; - uint256 internal constant NOT_TWO_TOKENS = 210; - uint256 internal constant DISABLED = 211; - - // Pools - uint256 internal constant MIN_AMP = 300; - uint256 internal constant MAX_AMP = 301; - uint256 internal constant MIN_WEIGHT = 302; - uint256 internal constant MAX_STABLE_TOKENS = 303; - uint256 internal constant MAX_IN_RATIO = 304; - uint256 internal constant MAX_OUT_RATIO = 305; - uint256 internal constant MIN_BPT_IN_FOR_TOKEN_OUT = 306; - uint256 internal constant MAX_OUT_BPT_FOR_TOKEN_IN = 307; - uint256 internal constant NORMALIZED_WEIGHT_INVARIANT = 308; - uint256 internal constant INVALID_TOKEN = 309; - uint256 internal constant UNHANDLED_JOIN_KIND = 310; - uint256 internal constant ZERO_INVARIANT = 311; - uint256 internal constant ORACLE_INVALID_SECONDS_QUERY = 312; - uint256 internal constant ORACLE_NOT_INITIALIZED = 313; - uint256 internal constant ORACLE_QUERY_TOO_OLD = 314; - uint256 internal constant ORACLE_INVALID_INDEX = 315; - uint256 internal constant ORACLE_BAD_SECS = 316; - uint256 internal constant AMP_END_TIME_TOO_CLOSE = 317; - uint256 internal constant AMP_ONGOING_UPDATE = 318; - uint256 internal constant AMP_RATE_TOO_HIGH = 319; - uint256 internal constant AMP_NO_ONGOING_UPDATE = 320; - uint256 internal constant STABLE_INVARIANT_DIDNT_CONVERGE = 321; - uint256 internal constant STABLE_GET_BALANCE_DIDNT_CONVERGE = 322; - uint256 internal constant RELAYER_NOT_CONTRACT = 323; - uint256 internal constant BASE_POOL_RELAYER_NOT_CALLED = 324; - uint256 internal constant REBALANCING_RELAYER_REENTERED = 325; - uint256 internal constant GRADUAL_UPDATE_TIME_TRAVEL = 326; - uint256 internal constant SWAPS_DISABLED = 327; - uint256 internal constant CALLER_IS_NOT_LBP_OWNER = 328; - uint256 internal constant PRICE_RATE_OVERFLOW = 329; - uint256 internal constant INVALID_JOIN_EXIT_KIND_WHILE_SWAPS_DISABLED = 330; - uint256 internal constant WEIGHT_CHANGE_TOO_FAST = 331; - uint256 internal constant LOWER_GREATER_THAN_UPPER_TARGET = 332; - uint256 internal constant UPPER_TARGET_TOO_HIGH = 333; - uint256 internal constant UNHANDLED_BY_LINEAR_POOL = 334; - uint256 internal constant OUT_OF_TARGET_RANGE = 335; - uint256 internal constant UNHANDLED_EXIT_KIND = 336; - uint256 internal constant UNAUTHORIZED_EXIT = 337; - uint256 internal constant MAX_MANAGEMENT_SWAP_FEE_PERCENTAGE = 338; - uint256 internal constant UNHANDLED_BY_MANAGED_POOL = 339; - uint256 internal constant UNHANDLED_BY_PHANTOM_POOL = 340; - uint256 internal constant TOKEN_DOES_NOT_HAVE_RATE_PROVIDER = 341; - uint256 internal constant INVALID_INITIALIZATION = 342; - uint256 internal constant OUT_OF_NEW_TARGET_RANGE = 343; - uint256 internal constant FEATURE_DISABLED = 344; - uint256 internal constant UNINITIALIZED_POOL_CONTROLLER = 345; - uint256 internal constant SET_SWAP_FEE_DURING_FEE_CHANGE = 346; - uint256 internal constant SET_SWAP_FEE_PENDING_FEE_CHANGE = 347; - uint256 internal constant CHANGE_TOKENS_DURING_WEIGHT_CHANGE = 348; - uint256 internal constant CHANGE_TOKENS_PENDING_WEIGHT_CHANGE = 349; - uint256 internal constant MAX_WEIGHT = 350; - uint256 internal constant UNAUTHORIZED_JOIN = 351; - uint256 internal constant MAX_MANAGEMENT_AUM_FEE_PERCENTAGE = 352; - uint256 internal constant FRACTIONAL_TARGET = 353; - uint256 internal constant ADD_OR_REMOVE_BPT = 354; - uint256 internal constant INVALID_CIRCUIT_BREAKER_BOUNDS = 355; - uint256 internal constant CIRCUIT_BREAKER_TRIPPED = 356; - uint256 internal constant MALICIOUS_QUERY_REVERT = 357; - uint256 internal constant JOINS_EXITS_DISABLED = 358; - - // Lib - uint256 internal constant REENTRANCY = 400; - uint256 internal constant SENDER_NOT_ALLOWED = 401; - uint256 internal constant PAUSED = 402; - uint256 internal constant PAUSE_WINDOW_EXPIRED = 403; - uint256 internal constant MAX_PAUSE_WINDOW_DURATION = 404; - uint256 internal constant MAX_BUFFER_PERIOD_DURATION = 405; - uint256 internal constant INSUFFICIENT_BALANCE = 406; - uint256 internal constant INSUFFICIENT_ALLOWANCE = 407; - uint256 internal constant ERC20_TRANSFER_FROM_ZERO_ADDRESS = 408; - uint256 internal constant ERC20_TRANSFER_TO_ZERO_ADDRESS = 409; - uint256 internal constant ERC20_MINT_TO_ZERO_ADDRESS = 410; - uint256 internal constant ERC20_BURN_FROM_ZERO_ADDRESS = 411; - uint256 internal constant ERC20_APPROVE_FROM_ZERO_ADDRESS = 412; - uint256 internal constant ERC20_APPROVE_TO_ZERO_ADDRESS = 413; - uint256 internal constant ERC20_TRANSFER_EXCEEDS_ALLOWANCE = 414; - uint256 internal constant ERC20_DECREASED_ALLOWANCE_BELOW_ZERO = 415; - uint256 internal constant ERC20_TRANSFER_EXCEEDS_BALANCE = 416; - uint256 internal constant ERC20_BURN_EXCEEDS_ALLOWANCE = 417; - uint256 internal constant SAFE_ERC20_CALL_FAILED = 418; - uint256 internal constant ADDRESS_INSUFFICIENT_BALANCE = 419; - uint256 internal constant ADDRESS_CANNOT_SEND_VALUE = 420; - uint256 internal constant SAFE_CAST_VALUE_CANT_FIT_INT256 = 421; - uint256 internal constant GRANT_SENDER_NOT_ADMIN = 422; - uint256 internal constant REVOKE_SENDER_NOT_ADMIN = 423; - uint256 internal constant RENOUNCE_SENDER_NOT_ALLOWED = 424; - uint256 internal constant BUFFER_PERIOD_EXPIRED = 425; - uint256 internal constant CALLER_IS_NOT_OWNER = 426; - uint256 internal constant NEW_OWNER_IS_ZERO = 427; - uint256 internal constant CODE_DEPLOYMENT_FAILED = 428; - uint256 internal constant CALL_TO_NON_CONTRACT = 429; - uint256 internal constant LOW_LEVEL_CALL_FAILED = 430; - uint256 internal constant NOT_PAUSED = 431; - uint256 internal constant ADDRESS_ALREADY_ALLOWLISTED = 432; - uint256 internal constant ADDRESS_NOT_ALLOWLISTED = 433; - uint256 internal constant ERC20_BURN_EXCEEDS_BALANCE = 434; - uint256 internal constant INVALID_OPERATION = 435; - uint256 internal constant CODEC_OVERFLOW = 436; - uint256 internal constant IN_RECOVERY_MODE = 437; - uint256 internal constant NOT_IN_RECOVERY_MODE = 438; - uint256 internal constant INDUCED_FAILURE = 439; - uint256 internal constant EXPIRED_SIGNATURE = 440; - uint256 internal constant MALFORMED_SIGNATURE = 441; - uint256 internal constant SAFE_CAST_VALUE_CANT_FIT_UINT64 = 442; - uint256 internal constant UNHANDLED_FEE_TYPE = 443; - uint256 internal constant BURN_FROM_ZERO = 444; - - // Vault - uint256 internal constant INVALID_POOL_ID = 500; - uint256 internal constant CALLER_NOT_POOL = 501; - uint256 internal constant SENDER_NOT_ASSET_MANAGER = 502; - uint256 internal constant USER_DOESNT_ALLOW_RELAYER = 503; - uint256 internal constant INVALID_SIGNATURE = 504; - uint256 internal constant EXIT_BELOW_MIN = 505; - uint256 internal constant JOIN_ABOVE_MAX = 506; - uint256 internal constant SWAP_LIMIT = 507; - uint256 internal constant SWAP_DEADLINE = 508; - uint256 internal constant CANNOT_SWAP_SAME_TOKEN = 509; - uint256 internal constant UNKNOWN_AMOUNT_IN_FIRST_SWAP = 510; - uint256 internal constant MALCONSTRUCTED_MULTIHOP_SWAP = 511; - uint256 internal constant INTERNAL_BALANCE_OVERFLOW = 512; - uint256 internal constant INSUFFICIENT_INTERNAL_BALANCE = 513; - uint256 internal constant INVALID_ETH_INTERNAL_BALANCE = 514; - uint256 internal constant INVALID_POST_LOAN_BALANCE = 515; - uint256 internal constant INSUFFICIENT_ETH = 516; - uint256 internal constant UNALLOCATED_ETH = 517; - uint256 internal constant ETH_TRANSFER = 518; - uint256 internal constant CANNOT_USE_ETH_SENTINEL = 519; - uint256 internal constant TOKENS_MISMATCH = 520; - uint256 internal constant TOKEN_NOT_REGISTERED = 521; - uint256 internal constant TOKEN_ALREADY_REGISTERED = 522; - uint256 internal constant TOKENS_ALREADY_SET = 523; - uint256 internal constant TOKENS_LENGTH_MUST_BE_2 = 524; - uint256 internal constant NONZERO_TOKEN_BALANCE = 525; - uint256 internal constant BALANCE_TOTAL_OVERFLOW = 526; - uint256 internal constant POOL_NO_TOKENS = 527; - uint256 internal constant INSUFFICIENT_FLASH_LOAN_BALANCE = 528; - - // Fees - uint256 internal constant SWAP_FEE_PERCENTAGE_TOO_HIGH = 600; - uint256 internal constant FLASH_LOAN_FEE_PERCENTAGE_TOO_HIGH = 601; - uint256 internal constant INSUFFICIENT_FLASH_LOAN_FEE_AMOUNT = 602; - uint256 internal constant AUM_FEE_PERCENTAGE_TOO_HIGH = 603; - - // FeeSplitter - uint256 internal constant SPLITTER_FEE_PERCENTAGE_TOO_HIGH = 700; - - // Misc - uint256 internal constant UNIMPLEMENTED = 998; - uint256 internal constant SHOULD_NOT_HAPPEN = 999; -} diff --git a/contracts/deploy/base/001_woeth_on_base.js b/contracts/deploy/base/001_woeth_on_base.js deleted file mode 100644 index 3782b69b39..0000000000 --- a/contracts/deploy/base/001_woeth_on_base.js +++ /dev/null @@ -1,45 +0,0 @@ -const { deployOnBaseWithEOA } = require("../../utils/deploy-l2"); -const { - deployWithConfirmation, - withConfirmation, -} = require("../../utils/deploy"); -const { getTxOpts } = require("../../utils/tx"); - -module.exports = deployOnBaseWithEOA( - { - deployName: "001_woeth_on_base", - }, - async ({ ethers }) => { - const { deployerAddr } = await getNamedAccounts(); - const sDeployer = await ethers.provider.getSigner(deployerAddr); - - // Deploy Proxy - await deployWithConfirmation("BridgedBaseWOETHProxy", []); - const cWOETHProxy = await ethers.getContract("BridgedBaseWOETHProxy"); - console.log("BridgedBaseWOETHProxy address:", cWOETHProxy.address); - - // Deploy Bridged WOETH Token implementation - await deployWithConfirmation("BridgedWOETH", []); - const cWOETHImpl = await ethers.getContract("BridgedWOETH"); - console.log("BridgedWOETH address:", cWOETHImpl.address); - - // Build implementation initialization tx data - const initData = cWOETHImpl.interface.encodeFunctionData( - "initialize()", - [] - ); - - // Initialize the proxy - // prettier-ignore - await withConfirmation( - cWOETHProxy - .connect(sDeployer)["initialize(address,address,bytes)"]( - cWOETHImpl.address, - deployerAddr, // Pretend Deployer is Governor for now - initData, - await getTxOpts() - ) - ); - console.log("Initialized BridgedBaseWOETHProxy"); - } -); diff --git a/contracts/deploy/base/002_base_oracles.js b/contracts/deploy/base/002_base_oracles.js deleted file mode 100644 index 7c8ca52133..0000000000 --- a/contracts/deploy/base/002_base_oracles.js +++ /dev/null @@ -1,29 +0,0 @@ -const { deployOnBase } = require("../../utils/deploy-l2"); -const { - deployWithConfirmation, - withConfirmation, -} = require("../../utils/deploy"); -const addresses = require("../../utils/addresses"); - -module.exports = deployOnBase( - { - deployName: "002_base_oracles", - }, - async ({ ethers }) => { - // TODO: Proxy for OracleRouter? - - // Deploy OETHb Oracle Router - await deployWithConfirmation("OETHBaseOracleRouter", []); - - const cOracleRouter = await ethers.getContract("OETHBaseOracleRouter"); - - // Cache decimals - await withConfirmation( - cOracleRouter.cacheDecimals(addresses.base.BridgedWOETH) - ); - - return { - actions: [], - }; - } -); diff --git a/contracts/deploy/base/003_base_vault_and_token.js b/contracts/deploy/base/003_base_vault_and_token.js deleted file mode 100644 index 4a10ca60f6..0000000000 --- a/contracts/deploy/base/003_base_vault_and_token.js +++ /dev/null @@ -1,168 +0,0 @@ -const { deployOnBase } = require("../../utils/deploy-l2"); -const { - deployWithConfirmation, - withConfirmation, -} = require("../../utils/deploy"); -const addresses = require("../../utils/addresses"); - -module.exports = deployOnBase( - { - deployName: "003_base_vault_and_token", - }, - async ({ ethers }) => { - const { deployerAddr, governorAddr } = await getNamedAccounts(); - const sDeployer = await ethers.provider.getSigner(deployerAddr); - - // Proxies - await deployWithConfirmation("OETHBaseProxy"); - await deployWithConfirmation("WOETHBaseProxy"); - await deployWithConfirmation("OETHBaseVaultProxy"); - - const cOETHbProxy = await ethers.getContract("OETHBaseProxy"); - const cwOETHbProxy = await ethers.getContract("WOETHBaseProxy"); - const cOETHbVaultProxy = await ethers.getContract("OETHBaseVaultProxy"); - - // Core contracts - const dOETHb = await deployWithConfirmation("OETH"); - const dwOETHb = await deployWithConfirmation("WOETHBase", [ - cOETHbProxy.address, // Base token - "Wrapped OETH Base", - "wOETHb", - ]); - const dOETHbVault = await deployWithConfirmation("OETHVault"); - const dOETHbVaultCore = await deployWithConfirmation("OETHBaseVaultCore", [ - addresses.base.WETH, - ]); - const dOETHbVaultAdmin = await deployWithConfirmation("OETHBaseVaultAdmin"); - - // Get contract instances - const cOETHb = await ethers.getContractAt("OETH", cOETHbProxy.address); - const cwOETHb = await ethers.getContractAt( - "WOETHBase", - cwOETHbProxy.address - ); - const cOETHbVault = await ethers.getContractAt( - "IVault", - cOETHbVaultProxy.address - ); - const cOracleRouter = await ethers.getContract("OETHBaseOracleRouter"); - - // Init OETHb - const resolution = ethers.utils.parseUnits("1", 27); - const initDataOETHb = cOETHb.interface.encodeFunctionData( - "initialize(string,string,address,uint256)", - [ - "OETH Base", - "OETHb", // Token Symbol - cOETHbVaultProxy.address, // OETHb Vault - resolution, // HighRes - ] - ); - // prettier-ignore - await withConfirmation( - cOETHbProxy - .connect(sDeployer)["initialize(address,address,bytes)"]( - dOETHb.address, - deployerAddr, - initDataOETHb - ) - ); - console.log("Initialized OETHBaseProxy and OETHBase implementation"); - - // Init OETHbVault - const initDataOETHbVault = cOETHbVault.interface.encodeFunctionData( - "initialize(address,address)", - [ - cOracleRouter.address, // OracleRouter - cOETHbProxy.address, // OETHb - ] - ); - // prettier-ignore - await withConfirmation( - cOETHbVaultProxy - .connect(sDeployer)["initialize(address,address,bytes)"]( - dOETHbVault.address, - deployerAddr, - initDataOETHbVault - ) - ); - console.log("Initialized OETHBaseVaultProxy and implementation"); - - // Init wOETHb - const initDatawOETHb = cwOETHb.interface.encodeFunctionData( - "initialize()", - [] - ); - // prettier-ignore - await withConfirmation( - cwOETHbProxy - .connect(sDeployer)["initialize(address,address,bytes)"]( - dwOETHb.address, - // No need for additional governance transfer, - // since deployer doesn't have to configure anything - governorAddr, - initDatawOETHb - ) - ); - console.log("Initialized WOETHBaseProxy and implementation"); - - // Set Core Impl - await withConfirmation( - cOETHbVaultProxy.connect(sDeployer).upgradeTo(dOETHbVaultCore.address) - ); - console.log("Set OETHBaseVaultCore implementation"); - - // Set Admin Impl - await withConfirmation( - cOETHbVault.connect(sDeployer).setAdminImpl(dOETHbVaultAdmin.address) - ); - console.log("Set OETHBaseVaultAdmin implementation"); - - // Transfer ownership - await withConfirmation( - cOETHbVaultProxy.connect(sDeployer).transferGovernance(governorAddr) - ); - await withConfirmation( - cOETHbProxy.connect(sDeployer).transferGovernance(governorAddr) - ); - console.log("Transferred Governance"); - - return { - actions: [ - { - // 1. Claim Governance on OETHb - contract: cOETHbProxy, - signature: "claimGovernance()", - args: [], - }, - { - // 2. Claim Governance on OETHbVault - contract: cOETHbVaultProxy, - signature: "claimGovernance()", - args: [], - }, - { - // 3. Allow minting with WETH - contract: cOETHbVault, - signature: "supportAsset(address,uint8)", - args: [ - addresses.base.WETH, - 0, // Decimal - ], - }, - { - // 4. Unpause Capital - contract: cOETHbVault, - signature: "unpauseCapital()", - args: [], - }, - { - // 5. Upgrade wOETHb - contract: cwOETHbProxy, - signature: "upgradeTo(address)", - args: [dwOETHb.address], - }, - ], - }; - } -); diff --git a/contracts/deploy/base/004_super_oeth.js b/contracts/deploy/base/004_super_oeth.js deleted file mode 100644 index a051f55b18..0000000000 --- a/contracts/deploy/base/004_super_oeth.js +++ /dev/null @@ -1,45 +0,0 @@ -const { deployOnBase } = require("../../utils/deploy-l2"); -const { deployWithConfirmation } = require("../../utils/deploy"); - -module.exports = deployOnBase( - { - deployName: "004_super_oeth", - }, - async ({ ethers }) => { - // Proxies - const cOETHbProxy = await ethers.getContract("OETHBaseProxy"); - const cwOETHbProxy = await ethers.getContract("WOETHBaseProxy"); - - // Core contracts - const dOETHb = await deployWithConfirmation("OETHBase"); - const dwOETHb = await deployWithConfirmation("WOETHBase", [ - cOETHbProxy.address, // Base token - ]); - - // Get contract instances - const cOETHb = await ethers.getContractAt("OETHBase", cOETHbProxy.address); - - return { - actions: [ - { - // 1. Upgrade OETHb proxy - contract: cOETHbProxy, - signature: "upgradeTo(address)", - args: [dOETHb.address], - }, - { - // 2. Initialize OETHb's new impl - contract: cOETHb, - signature: "initialize2()", - args: [], - }, - { - // 3. Upgrade wOETHb proxy - contract: cwOETHbProxy, - signature: "upgradeTo(address)", - args: [dwOETHb.address], - }, - ], - }; - } -); diff --git a/contracts/deploy/base/005_mutlisig_harvester.js b/contracts/deploy/base/005_mutlisig_harvester.js deleted file mode 100644 index 780ed426cf..0000000000 --- a/contracts/deploy/base/005_mutlisig_harvester.js +++ /dev/null @@ -1,70 +0,0 @@ -const { deployOnBase } = require("../../utils/deploy-l2"); -const { - deployWithConfirmation, - withConfirmation, -} = require("../../utils/deploy"); -const addresses = require("../../utils/addresses"); - -module.exports = deployOnBase( - { - deployName: "005_mutlisig_harvester", - }, - async ({ ethers }) => { - const { deployerAddr, governorAddr } = await getNamedAccounts(); - const sDeployer = await ethers.provider.getSigner(deployerAddr); - - // Proxies - await deployWithConfirmation("OETHBaseDripperProxy"); - - const oethbDripperProxy = await ethers.getContract("OETHBaseDripperProxy"); - const oethbProxy = await ethers.getContract("OETHBaseProxy"); - const oethbVaultProxy = await ethers.getContract("OETHBaseVaultProxy"); - const oethbVault = await ethers.getContractAt( - "IVault", - oethbVaultProxy.address - ); - - // Contracts - await deployWithConfirmation("OETHDripper", [ - oethbVault.address, // OETHb Vault - addresses.base.WETH, // WETH - ]); - const dripperImpl = await ethers.getContract("OETHDripper"); - console.log("OETHDripper implementation deployed at", dripperImpl.address); - - await deployWithConfirmation("OETHVaultValueChecker", [ - oethbVault.address, // OETHb Vault - oethbProxy.address, // OETHb - ]); - const vaultValueChecker = await ethers.getContract("OETHVaultValueChecker"); - console.log("VaultValueChecker deployed at", vaultValueChecker.address); - - // Initialize Dripper Proxy - // prettier-ignore - await withConfirmation( - oethbDripperProxy - .connect(sDeployer)["initialize(address,address,bytes)"]( - dripperImpl.address, // Implementation - governorAddr, // Governor multisig - "0x" // No init data - ) - ); - console.log("Initialized OETHBaseDripperProxy"); - - const cDripper = await ethers.getContractAt( - "OETHDripper", - oethbDripperProxy.address - ); - - return { - actions: [ - { - // 1. Configure Dripper to 7 days - contract: cDripper, - signature: "setDripDuration(uint256)", - args: [3 * 24 * 60 * 60], - }, - ], - }; - } -); diff --git a/contracts/deploy/base/006_base_amo_strategy.js b/contracts/deploy/base/006_base_amo_strategy.js deleted file mode 100644 index e2269c9cea..0000000000 --- a/contracts/deploy/base/006_base_amo_strategy.js +++ /dev/null @@ -1,144 +0,0 @@ -const { deployOnBase } = require("../../utils/deploy-l2"); -const { - deployWithConfirmation, - withConfirmation, -} = require("../../utils/deploy"); -const addresses = require("../../utils/addresses"); -const { oethUnits } = require("../../test/helpers"); -const { - deployBaseAerodromeAMOStrategyImplementation, -} = require("../deployActions"); - -//const aeroVoterAbi = require("../../test/abi/aerodromeVoter.json"); -//const slipstreamPoolAbi = require("../../test/abi/aerodromeSlipstreamPool.json") -//const { impersonateAndFund } = require("../../utils/signers.js"); - -/** - * This is needed only as long as the gauge isn't created on the base mainnet - */ -// const setupAerodromeOEthbWETHGauge = async (oethbAddress) => { -// const voter = await ethers.getContractAt(aeroVoterAbi, addresses.base.aeroVoterAddress); -// const amoPool = await ethers.getContractAt(slipstreamPoolAbi, addresses.base.aerodromeOETHbWETHClPool); - -// const aeroGaugeSigner = await impersonateAndFund(addresses.base.aeroGaugeGovernorAddress); - -// // whitelist OETHb -// await voter -// .connect(aeroGaugeSigner) -// .whitelistToken( -// oethbAddress, -// true -// ); - -// // create a gauge -// await voter -// .connect(aeroGaugeSigner) -// .createGauge( -// // 0x5e7BB104d84c7CB9B682AaC2F3d509f5F406809A -// addresses.base.slipstreamPoolFactory, -// // 0x6446021F4E396dA3df4235C62537431372195D38 -// addresses.base.aerodromeOETHbWETHClPool -// ); - -// return await amoPool.gauge(); -// }; - -module.exports = deployOnBase( - { - deployName: "006_base_amo_strategy", - }, - async ({ ethers }) => { - const { deployerAddr, governorAddr } = await getNamedAccounts(); - const sDeployer = await ethers.provider.getSigner(deployerAddr); - const cOETHbVaultProxy = await ethers.getContract("OETHBaseVaultProxy"); - const cOETHbVault = await ethers.getContractAt( - "IVault", - cOETHbVaultProxy.address - ); - - const cAMOStrategyImpl = - await deployBaseAerodromeAMOStrategyImplementation(); - await deployWithConfirmation("AerodromeAMOStrategyProxy"); - const cAMOStrategyProxy = await ethers.getContract( - "AerodromeAMOStrategyProxy" - ); - const cAMOStrategy = await ethers.getContractAt( - "AerodromeAMOStrategy", - cAMOStrategyProxy.address - ); - - console.log("Deployed AMO strategy and proxy contracts"); - - // Init the AMO strategy - const initData = cAMOStrategyImpl.interface.encodeFunctionData( - "initialize(address[])", - [ - [addresses.base.AERO], // rewardTokenAddresses - ] - ); - // prettier-ignore - await withConfirmation( - cAMOStrategyProxy - .connect(sDeployer)["initialize(address,address,bytes)"]( - cAMOStrategyImpl.address, - deployerAddr, - initData - ) - ); - console.log("Initialized cAMOStrategyProxy and implementation"); - - await withConfirmation( - cAMOStrategy.connect(sDeployer).setAllowedPoolWethShareInterval( - oethUnits("0.18"), // 18% - oethUnits("0.22") // 22% - ) - ); - - await withConfirmation( - cAMOStrategy.connect(sDeployer).safeApproveAllTokens() - ); - - console.log("AMOStrategy configured"); - - // Transfer ownership - await withConfirmation( - cAMOStrategyProxy.connect(sDeployer).transferGovernance(governorAddr) - ); - console.log("Transferred Governance"); - - return { - actions: [ - { - // 1. Claim Governance on the AMO strategy - contract: cAMOStrategyProxy, - signature: "claimGovernance()", - args: [], - }, - { - // 2. Approve the AMO strategy on the Vault - contract: cOETHbVault, - signature: "approveStrategy(address)", - args: [cAMOStrategyProxy.address], - }, - { - // 3. Set strategist address - contract: cOETHbVault, - signature: "setStrategistAddr(address)", - args: [addresses.base.strategist], - }, - { - // 4. Set strategy as whitelisted one to mint OETHb tokens - contract: cOETHbVault, - signature: "addStrategyToMintWhitelist(address)", - args: [cAMOStrategyProxy.address], - }, - { - // 5. Set harvester address - contract: cAMOStrategy, - signature: "setHarvesterAddress(address)", - args: [addresses.base.strategist], - }, - ], - }; - } -); diff --git a/contracts/deploy/base/007_bridged_woeth_strategy.js b/contracts/deploy/base/007_bridged_woeth_strategy.js deleted file mode 100644 index bb7f31c8aa..0000000000 --- a/contracts/deploy/base/007_bridged_woeth_strategy.js +++ /dev/null @@ -1,103 +0,0 @@ -const { deployOnBase } = require("../../utils/deploy-l2"); -const { - deployWithConfirmation, - withConfirmation, -} = require("../../utils/deploy"); -const addresses = require("../../utils/addresses"); - -module.exports = deployOnBase( - { - deployName: "007_bridged_woeth_strategy", - }, - async ({ ethers }) => { - const { deployerAddr, governorAddr } = await getNamedAccounts(); - const sDeployer = await ethers.provider.getSigner(deployerAddr); - - const cOETHbVaultProxy = await ethers.getContract("OETHBaseVaultProxy"); - const cOETHbProxy = await ethers.getContract("OETHBaseProxy"); - const cOETHbVault = await ethers.getContractAt( - "IVault", - cOETHbVaultProxy.address - ); - - // Deploy oracle router - await deployWithConfirmation("OETHBaseOracleRouter"); - const cOracleRouter = await ethers.getContract("OETHBaseOracleRouter"); - - // Cache decimals - await withConfirmation( - cOracleRouter.cacheDecimals(addresses.base.BridgedWOETH) - ); - - // Deploy proxy - await deployWithConfirmation("BridgedWOETHStrategyProxy"); - const cStrategyProxy = await ethers.getContract( - "BridgedWOETHStrategyProxy" - ); - - // Deploy implementation - const dStrategyImpl = await deployWithConfirmation("BridgedWOETHStrategy", [ - [addresses.zero, cOETHbVaultProxy.address], - addresses.base.WETH, - addresses.base.BridgedWOETH, - cOETHbProxy.address, - ]); - const cStrategy = await ethers.getContractAt( - "BridgedWOETHStrategy", - cStrategyProxy.address - ); - - // Init Strategy - const initData = cStrategy.interface.encodeFunctionData( - "initialize(uint128)", - [ - 100, // 1% maxPriceDiffBps - ] - ); - // prettier-ignore - await withConfirmation( - cStrategyProxy - .connect(sDeployer)["initialize(address,address,bytes)"]( - dStrategyImpl.address, - governorAddr, - initData - ) - ); - console.log("Initialized BridgedWOETHStrategyProxy"); - - return { - actions: [ - { - // 1. Approve strategy - contract: cOETHbVault, - signature: "approveStrategy(address)", - args: [cStrategyProxy.address], - }, - { - // 2. Add to mint whitelist - contract: cOETHbVault, - signature: "addStrategyToMintWhitelist(address)", - args: [cStrategyProxy.address], - }, - { - // 3. Update oracle price - contract: cStrategy, - signature: "updateWOETHOraclePrice()", - args: [], - }, - { - // 4. Update oracle router - contract: cOETHbVault, - signature: "setPriceProvider(address)", - args: [cOracleRouter.address], - }, - { - // 5. Set strategist as Harvester - contract: cStrategy, - signature: "setHarvesterAddress(address)", - args: [addresses.base.strategist], - }, - ], - }; - } -); diff --git a/contracts/deploy/base/008_oethb_zapper.js b/contracts/deploy/base/008_oethb_zapper.js deleted file mode 100644 index b5bf203b89..0000000000 --- a/contracts/deploy/base/008_oethb_zapper.js +++ /dev/null @@ -1,27 +0,0 @@ -const { deployOnBase } = require("../../utils/deploy-l2"); - -module.exports = deployOnBase( - { deployName: "008_oethb_zapper", forceDeploy: false }, - async ({ deployWithConfirmation }) => { - // Deployer Actions - // ---------------- - - const cOETHbProxy = await ethers.getContract("OETHBaseProxy"); - const cWOETHbProxy = await ethers.getContract("WOETHBaseProxy"); - const cOETHbVaultProxy = await ethers.getContract("OETHBaseVaultProxy"); - - // Deploy new Zapper - await deployWithConfirmation( - "OETHBaseZapper", - [cOETHbProxy.address, cWOETHbProxy.address, cOETHbVaultProxy.address], - undefined, - true - ); - - // Governance Actions - // ---------------- - return { - actions: [], - }; - } -); diff --git a/contracts/deploy/base/009_upgrade_vault.js b/contracts/deploy/base/009_upgrade_vault.js deleted file mode 100644 index bf331296b6..0000000000 --- a/contracts/deploy/base/009_upgrade_vault.js +++ /dev/null @@ -1,77 +0,0 @@ -const { deployOnBase } = require("../../utils/deploy-l2"); -const { deployWithConfirmation } = require("../../utils/deploy"); -const addresses = require("../../utils/addresses"); -const { utils } = require("ethers"); - -module.exports = deployOnBase( - { - deployName: "009_upgrade_vault", - }, - async ({ ethers }) => { - const cOETHbVaultProxy = await ethers.getContract("OETHBaseVaultProxy"); - const cOETHbVault = await ethers.getContractAt( - "IVault", - cOETHbVaultProxy.address - ); - const cOETHbDripperProxy = await ethers.getContract("OETHBaseDripperProxy"); - - // Deploy new implementation - const dOETHbVaultCore = await deployWithConfirmation("OETHBaseVaultCore", [ - addresses.base.WETH, - ]); - const dOETHbVaultAdmin = await deployWithConfirmation("OETHBaseVaultAdmin"); - - return { - actions: [ - { - // 1. Upgrade VaultCore - contract: cOETHbVaultProxy, - signature: "upgradeTo(address)", - args: [dOETHbVaultCore.address], - }, - { - // 2. Upgrade VaultAdmin - contract: cOETHbVault, - signature: "setAdminImpl(address)", - args: [dOETHbVaultAdmin.address], - }, - { - // 3. Set allocate threshold - contract: cOETHbVault, - signature: "setAutoAllocateThreshold(uint256)", - args: [utils.parseEther("10")], // 10 OETH - }, - { - // 4. Set rebase threshold - contract: cOETHbVault, - signature: "setRebaseThreshold(uint256)", - args: [utils.parseEther("1")], // 1 OETH - }, - { - // 5. Max supply diff - contract: cOETHbVault, - signature: "setMaxSupplyDiff(uint256)", - args: [utils.parseUnits("0.03", 18)], // 0.03 OETH - }, - { - // 6. Set trustee address - contract: cOETHbVault, - signature: "setTrusteeAddress(address)", - args: [addresses.base.strategist], - }, - { - // 7. Set trustee fee - contract: cOETHbVault, - signature: "setTrusteeFeeBps(uint256)", - args: [2000], // 20% - }, - { - // 8. Set Dripper - contract: cOETHbVault, - signature: "setDripper(address)", - args: [cOETHbDripperProxy.address], - }, - ], - }; - } -); diff --git a/contracts/deploy/base/010_upgrade_vault_core.js b/contracts/deploy/base/010_upgrade_vault_core.js deleted file mode 100644 index 5ee83ec733..0000000000 --- a/contracts/deploy/base/010_upgrade_vault_core.js +++ /dev/null @@ -1,28 +0,0 @@ -const { deployOnBase } = require("../../utils/deploy-l2"); -const { deployWithConfirmation } = require("../../utils/deploy"); -const addresses = require("../../utils/addresses"); - -module.exports = deployOnBase( - { - deployName: "010_upgrade_vault_core", - }, - async ({ ethers }) => { - const cOETHbVaultProxy = await ethers.getContract("OETHBaseVaultProxy"); - - // Deploy new implementation - const dOETHbVaultCore = await deployWithConfirmation("OETHBaseVaultCore", [ - addresses.base.WETH, - ]); - - return { - actions: [ - { - // 1. Upgrade VaultCore - contract: cOETHbVaultProxy, - signature: "upgradeTo(address)", - args: [dOETHbVaultCore.address], - }, - ], - }; - } -); diff --git a/contracts/deploy/base/011_transfer_governance.js b/contracts/deploy/base/011_transfer_governance.js deleted file mode 100644 index 9a3b15d339..0000000000 --- a/contracts/deploy/base/011_transfer_governance.js +++ /dev/null @@ -1,87 +0,0 @@ -const { deployOnBase } = require("../../utils/deploy-l2"); -const addresses = require("../../utils/addresses"); - -const ADMIN_ROLE = - "0x0000000000000000000000000000000000000000000000000000000000000000"; - -module.exports = deployOnBase( - { - deployName: "011_transfer_governance", - }, - async ({ ethers }) => { - const cBridgedWOETHProxy = await ethers.getContract( - "BridgedBaseWOETHProxy" - ); - const cBridgedWOETH = await ethers.getContractAt( - "BridgedWOETH", - cBridgedWOETHProxy.address - ); - - const cOETHbVaultProxy = await ethers.getContract("OETHBaseVaultProxy"); - const cOETHbProxy = await ethers.getContract("OETHBaseProxy"); - const cWOETHbProxy = await ethers.getContract("WOETHBaseProxy"); - - const cDripperProxy = await ethers.getContract("OETHBaseDripperProxy"); - - const cAMOStrategyProxy = await ethers.getContract( - "AerodromeAMOStrategyProxy" - ); - const cWOETHStrategyProxy = await ethers.getContract( - "BridgedWOETHStrategyProxy" - ); - - return { - actions: [ - { - // 1. Grant admin role to Timelock on Bridged wOETH - // TODO: Revoke role later when everything works fine - contract: cBridgedWOETH, - signature: "grantRole(bytes32,address)", - args: [ADMIN_ROLE, addresses.base.timelock], - }, - { - // 2. Bridged wOETH proxy - contract: cBridgedWOETHProxy, - signature: "transferGovernance(address)", - args: [addresses.base.timelock], - }, - { - // 3. Vault proxy - contract: cOETHbVaultProxy, - signature: "transferGovernance(address)", - args: [addresses.base.timelock], - }, - { - // 4. OETHb proxy - contract: cOETHbProxy, - signature: "transferGovernance(address)", - args: [addresses.base.timelock], - }, - { - // 5. WOETHb proxy - contract: cWOETHbProxy, - signature: "transferGovernance(address)", - args: [addresses.base.timelock], - }, - { - // 6. Dripper proxy - contract: cDripperProxy, - signature: "transferGovernance(address)", - args: [addresses.base.timelock], - }, - { - // 7. AMO Strategy proxy - contract: cAMOStrategyProxy, - signature: "transferGovernance(address)", - args: [addresses.base.timelock], - }, - { - // 8. Bridged WOETH Strategy Proxy - contract: cWOETHStrategyProxy, - signature: "transferGovernance(address)", - args: [addresses.base.timelock], - }, - ], - }; - } -); diff --git a/contracts/deploy/base/012_claim_governance.js b/contracts/deploy/base/012_claim_governance.js deleted file mode 100644 index c1804b2abd..0000000000 --- a/contracts/deploy/base/012_claim_governance.js +++ /dev/null @@ -1,74 +0,0 @@ -const { deployOnBase } = require("../../utils/deploy-l2"); - -module.exports = deployOnBase( - { - deployName: "012_claim_governance", - useTimelock: true, - }, - async ({ ethers }) => { - const cBridgedWOETHProxy = await ethers.getContract( - "BridgedBaseWOETHProxy" - ); - - const cOETHbVaultProxy = await ethers.getContract("OETHBaseVaultProxy"); - const cOETHbProxy = await ethers.getContract("OETHBaseProxy"); - const cWOETHbProxy = await ethers.getContract("WOETHBaseProxy"); - - const cDripperProxy = await ethers.getContract("OETHBaseDripperProxy"); - - const cAMOStrategyProxy = await ethers.getContract( - "AerodromeAMOStrategyProxy" - ); - const cWOETHStrategyProxy = await ethers.getContract( - "BridgedWOETHStrategyProxy" - ); - - return { - name: "Claim Governance on superOETHb contracts", - actions: [ - { - // 1. Bridged wOETH proxy - contract: cBridgedWOETHProxy, - signature: "claimGovernance()", - args: [], - }, - { - // 2. Vault proxy - contract: cOETHbVaultProxy, - signature: "claimGovernance()", - args: [], - }, - { - // 3. OETHb proxy - contract: cOETHbProxy, - signature: "claimGovernance()", - args: [], - }, - { - // 4. WOETHb proxy - contract: cWOETHbProxy, - signature: "claimGovernance()", - args: [], - }, - { - // 5. Dripper proxy - contract: cDripperProxy, - signature: "claimGovernance()", - args: [], - }, - { - // 6. AMO Strategy proxy - contract: cAMOStrategyProxy, - signature: "claimGovernance()", - args: [], - }, - { - // 7. Bridged WOETH Strategy Proxy - contract: cWOETHStrategyProxy, - signature: "claimGovernance()", - args: [], - }, - ], - }; - } -); diff --git a/contracts/deploy/base/013_revoke_admin_role.js b/contracts/deploy/base/013_revoke_admin_role.js deleted file mode 100644 index 3c1fd34f67..0000000000 --- a/contracts/deploy/base/013_revoke_admin_role.js +++ /dev/null @@ -1,31 +0,0 @@ -const { deployOnBase } = require("../../utils/deploy-l2"); -const addresses = require("../../utils/addresses"); - -const ADMIN_ROLE = - "0x0000000000000000000000000000000000000000000000000000000000000000"; - -module.exports = deployOnBase( - { - deployName: "013_revoke_admin_role", - }, - async ({ ethers }) => { - const cBridgedWOETHProxy = await ethers.getContract( - "BridgedBaseWOETHProxy" - ); - const cBridgedWOETH = await ethers.getContractAt( - "BridgedWOETH", - cBridgedWOETHProxy.address - ); - - return { - actions: [ - { - // 1. Revoke admin role from multisig on Bridged wOETH - contract: cBridgedWOETH, - signature: "revokeRole(bytes32,address)", - args: [ADMIN_ROLE, addresses.base.governor], - }, - ], - }; - } -); diff --git a/contracts/deploy/base/014_fixed_rate_dripper.js b/contracts/deploy/base/014_fixed_rate_dripper.js deleted file mode 100644 index 7735b2ea04..0000000000 --- a/contracts/deploy/base/014_fixed_rate_dripper.js +++ /dev/null @@ -1,30 +0,0 @@ -const { deployOnBase } = require("../../utils/deploy-l2"); -const { deployWithConfirmation } = require("../../utils/deploy"); -const addresses = require("../../utils/addresses"); - -module.exports = deployOnBase( - { - deployName: "014_fixed_rate_dripper", - }, - async ({ ethers }) => { - const cOETHbDripperProxy = await ethers.getContract("OETHBaseDripperProxy"); - const cOETHbVaultProxy = await ethers.getContract("OETHBaseVaultProxy"); - - // Deploy new implementation - const dOETHbDripper = await deployWithConfirmation("FixedRateDripper", [ - cOETHbVaultProxy.address, - addresses.base.WETH, - ]); - - return { - actions: [ - { - // 1. Upgrade Dripper - contract: cOETHbDripperProxy, - signature: "upgradeTo(address)", - args: [dOETHbDripper.address], - }, - ], - }; - } -); diff --git a/contracts/deploy/base/015_harvester.js b/contracts/deploy/base/015_harvester.js deleted file mode 100644 index 568959b535..0000000000 --- a/contracts/deploy/base/015_harvester.js +++ /dev/null @@ -1,73 +0,0 @@ -const { deployOnBase } = require("../../utils/deploy-l2"); -const { - deployWithConfirmation, - withConfirmation, -} = require("../../utils/deploy"); -const addresses = require("../../utils/addresses"); - -module.exports = deployOnBase( - { - deployName: "015_harvester", - useTimelock: true, - }, - async ({ ethers }) => { - const { deployerAddr } = await getNamedAccounts(); - const sDeployer = await ethers.provider.getSigner(deployerAddr); - - const cOETHbVaultProxy = await ethers.getContract("OETHBaseVaultProxy"); - const cAMOStrategyProxy = await ethers.getContract( - "AerodromeAMOStrategyProxy" - ); - - // Deploy proxy - await deployWithConfirmation("OETHBaseHarvesterProxy"); - const cHarvesterProxy = await ethers.getContract("OETHBaseHarvesterProxy"); - - // Deploy implementation - const dHarvesterImpl = await deployWithConfirmation("OETHBaseHarvester", [ - cOETHbVaultProxy.address, - cAMOStrategyProxy.address, - addresses.base.AERO, - addresses.base.WETH, - addresses.base.swapRouter, - ]); - - const cAMOStrategy = await ethers.getContractAt( - "AerodromeAMOStrategy", - cAMOStrategyProxy.address - ); - - // prettier-ignore - await withConfirmation( - cHarvesterProxy - .connect(sDeployer)["initialize(address,address,bytes)"]( - dHarvesterImpl.address, - addresses.base.timelock, - "0x" - ) - ); - console.log("Initialized OETHBaseHarvesterProxy"); - - const cHarvester = await ethers.getContractAt( - "OETHBaseHarvester", - cHarvesterProxy.address - ); - - return { - actions: [ - { - // 1. Set as harvester address on the strategy - contract: cAMOStrategy, - signature: "setHarvesterAddress(address)", - args: [cHarvesterProxy.address], - }, - { - // 2. Set Operator address - contract: cHarvester, - signature: "setOperatorAddr(address)", - args: [addresses.base.OZRelayerAddress], - }, - ], - }; - } -); diff --git a/contracts/deploy/base/016_timelock_2d_delay.js b/contracts/deploy/base/016_timelock_2d_delay.js deleted file mode 100644 index 67958f9200..0000000000 --- a/contracts/deploy/base/016_timelock_2d_delay.js +++ /dev/null @@ -1,25 +0,0 @@ -const { deployOnBase } = require("../../utils/deploy-l2"); -const addresses = require("../../utils/addresses"); - -module.exports = deployOnBase( - { - deployName: "016_timelock_2d_delay", - }, - async ({ ethers }) => { - const cTimelock = await ethers.getContractAt( - "ITimelockController", - addresses.base.timelock - ); - - return { - actions: [ - { - // 1. Update delay to 2d - contract: cTimelock, - signature: "updateDelay(uint256)", - args: [2 * 24 * 60 * 60], - }, - ], - }; - } -); diff --git a/contracts/deploy/base/017_upgrade_amo.js b/contracts/deploy/base/017_upgrade_amo.js deleted file mode 100644 index daf8e3ce2a..0000000000 --- a/contracts/deploy/base/017_upgrade_amo.js +++ /dev/null @@ -1,71 +0,0 @@ -const { deployOnBase } = require("../../utils/deploy-l2"); -const addresses = require("../../utils/addresses"); -const { utils } = require("ethers"); -const { - deployBaseAerodromeAMOStrategyImplementation, -} = require("../deployActions"); - -module.exports = deployOnBase( - { - deployName: "017_upgrade_amo", - }, - async ({ ethers }) => { - const cOETHbVaultProxy = await ethers.getContract("OETHBaseVaultProxy"); - const cOETHbVault = await ethers.getContractAt( - "IVault", - cOETHbVaultProxy.address - ); - - const cAMOStrategyProxy = await ethers.getContract( - "AerodromeAMOStrategyProxy" - ); - const cAMOStrategyImpl = - await deployBaseAerodromeAMOStrategyImplementation(); - const cAMOStrategy = await ethers.getContractAt( - "AerodromeAMOStrategy", - cAMOStrategyProxy.address - ); - - return { - actions: [ - { - // 1. Upgrade AMO - contract: cAMOStrategyProxy, - signature: "upgradeTo(address)", - args: [cAMOStrategyImpl.address], - }, - { - // 2. Reset WETH approvals to 0 on swapRouter and positionManager - contract: cAMOStrategy, - signature: "safeApproveAllTokens()", - args: [], - }, - { - // 2. set auto allocate threshold to 0.1 - gas is cheap - contract: cOETHbVault, - signature: "setAutoAllocateThreshold(uint256)", - args: [utils.parseUnits("0.1", 18)], - }, - // { - // // 3. set that 0.04% (4 basis points) of Vualt TVL triggers the allocation. - // // At the time of writing this is ~53 ETH - // contract: cOETHbVault, - // signature: "setVaultBuffer(uint256)", - // args: [utils.parseUnits("4", 14)], - // }, - { - // 3. for now disable allocating weth - contract: cOETHbVault, - signature: "setVaultBuffer(uint256)", - args: [utils.parseUnits("1", 18)], - }, - { - // 4. set aerodrome AMO as WETH asset default strategy - contract: cOETHbVault, - signature: "setAssetDefaultStrategy(address,address)", - args: [addresses.base.WETH, cAMOStrategyProxy.address], - }, - ], - }; - } -); diff --git a/contracts/deploy/base/018_strategist_as_executor.js b/contracts/deploy/base/018_strategist_as_executor.js deleted file mode 100644 index 6303046dac..0000000000 --- a/contracts/deploy/base/018_strategist_as_executor.js +++ /dev/null @@ -1,27 +0,0 @@ -const { deployOnBase } = require("../../utils/deploy-l2"); -const addresses = require("../../utils/addresses"); - -const EXECUTOR_ROLE = - "0xd8aa0f3194971a2a116679f7c2090f6939c8d4e01a2a8d7e41d55e5351469e63"; - -module.exports = deployOnBase( - { - deployName: "018_strategist_as_executor", - }, - async ({ ethers }) => { - const cTimelock = await ethers.getContractAt( - "ITimelockController", - addresses.base.timelock - ); - - return { - actions: [ - { - contract: cTimelock, - signature: "grantRole(bytes32,address)", - args: [EXECUTOR_ROLE, addresses.base.strategist], - }, - ], - }; - } -); diff --git a/contracts/deploy/base/019_async_withdrawals.js b/contracts/deploy/base/019_async_withdrawals.js deleted file mode 100644 index ccc87bc8df..0000000000 --- a/contracts/deploy/base/019_async_withdrawals.js +++ /dev/null @@ -1,45 +0,0 @@ -const { deployOnBase } = require("../../utils/deploy-l2"); -const { deployWithConfirmation } = require("../../utils/deploy"); -const addresses = require("../../utils/addresses"); - -module.exports = deployOnBase( - { - deployName: "019_async_withdrawals", - }, - async ({ ethers }) => { - const cOETHbVaultProxy = await ethers.getContract("OETHBaseVaultProxy"); - const cOETHbVault = await ethers.getContractAt( - "IVault", - cOETHbVaultProxy.address - ); - - // Deploy new implementation - const dOETHbVaultCore = await deployWithConfirmation("OETHBaseVaultCore", [ - addresses.base.WETH, - ]); - const dOETHbVaultAdmin = await deployWithConfirmation("OETHBaseVaultAdmin"); - - return { - actions: [ - { - // 1. Upgrade VaultCore - contract: cOETHbVaultProxy, - signature: "upgradeTo(address)", - args: [dOETHbVaultCore.address], - }, - { - // 2. Upgrade VaultAdmin - contract: cOETHbVault, - signature: "setAdminImpl(address)", - args: [dOETHbVaultAdmin.address], - }, - { - // 3. Set async claim delay to 1 day - contract: cOETHbVault, - signature: "setWithdrawalClaimDelay(uint256)", - args: [24 * 60 * 60], // 1d - }, - ], - }; - } -); diff --git a/contracts/deploy/base/020_upgrade_amo.js b/contracts/deploy/base/020_upgrade_amo.js deleted file mode 100644 index 84b561d667..0000000000 --- a/contracts/deploy/base/020_upgrade_amo.js +++ /dev/null @@ -1,28 +0,0 @@ -const { deployOnBase } = require("../../utils/deploy-l2"); -const { - deployBaseAerodromeAMOStrategyImplementation, -} = require("../deployActions"); - -module.exports = deployOnBase( - { - deployName: "020_upgrade_amo", - }, - async ({ ethers }) => { - const cAMOStrategyProxy = await ethers.getContract( - "AerodromeAMOStrategyProxy" - ); - const cAMOStrategyImpl = - await deployBaseAerodromeAMOStrategyImplementation(); - - return { - actions: [ - { - // 1. Upgrade AMO - contract: cAMOStrategyProxy, - signature: "upgradeTo(address)", - args: [cAMOStrategyImpl.address], - }, - ], - }; - } -); diff --git a/contracts/deploy/base/021_multichain_strategist.js b/contracts/deploy/base/021_multichain_strategist.js deleted file mode 100644 index 37835ae164..0000000000 --- a/contracts/deploy/base/021_multichain_strategist.js +++ /dev/null @@ -1,50 +0,0 @@ -const { deployOnBase } = require("../../utils/deploy-l2"); -const addresses = require("../../utils/addresses"); - -const EXECUTOR_ROLE = - "0xd8aa0f3194971a2a116679f7c2090f6939c8d4e01a2a8d7e41d55e5351469e63"; - -module.exports = deployOnBase( - { - deployName: "021_multichain_strategist", - // useTimelock: false, - }, - async ({ ethers }) => { - const cOETHbVaultProxy = await ethers.getContract("OETHBaseVaultProxy"); - const cOETHbVault = await ethers.getContractAt( - "IVault", - cOETHbVaultProxy.address - ); - - const cTimelock = await ethers.getContractAt( - "ITimelockController", - addresses.base.timelock - ); - - return { - name: "Switch to Multichain Strategist", - actions: [ - { - contract: cOETHbVault, - signature: "setStrategistAddr(address)", - args: [addresses.base.multichainStrategist], - }, - { - contract: cOETHbVault, - signature: "setTrusteeAddress(address)", - args: [addresses.base.multichainStrategist], - }, - { - contract: cTimelock, - signature: "grantRole(bytes32,address)", - args: [EXECUTOR_ROLE, addresses.base.multichainStrategist], - }, - { - contract: cTimelock, - signature: "revokeRole(bytes32,address)", - args: [EXECUTOR_ROLE, addresses.base.strategist], - }, - ], - }; - } -); diff --git a/contracts/deploy/base/022_upgrade_oeth.js b/contracts/deploy/base/022_upgrade_oeth.js deleted file mode 100644 index 8ca45a1a97..0000000000 --- a/contracts/deploy/base/022_upgrade_oeth.js +++ /dev/null @@ -1,30 +0,0 @@ -const { deployOnBase } = require("../../utils/deploy-l2"); -const { deployWithConfirmation } = require("../../utils/deploy"); - -module.exports = deployOnBase( - { - deployName: "022_upgrade_oeth", - // forceSkip: true, - }, - async ({ ethers }) => { - const dOETHb = await deployWithConfirmation( - "OETHBase", - [], - undefined, - true - ); - - const cOETHbProxy = await ethers.getContract("OETHBaseProxy"); - - return { - actions: [ - { - // 1. Upgrade OETH - contract: cOETHbProxy, - signature: "upgradeTo(address)", - args: [dOETHb.address], - }, - ], - }; - } -); diff --git a/contracts/deploy/base/023_update_weth_share.js b/contracts/deploy/base/023_update_weth_share.js deleted file mode 100644 index 42f11469b5..0000000000 --- a/contracts/deploy/base/023_update_weth_share.js +++ /dev/null @@ -1,31 +0,0 @@ -const { deployOnBase } = require("../../utils/deploy-l2"); -const { utils } = require("ethers"); - -module.exports = deployOnBase( - { - deployName: "023_update_weth_share", - }, - async ({ ethers }) => { - const cAMOStrategyProxy = await ethers.getContract( - "AerodromeAMOStrategyProxy" - ); - const cAMOStrategy = await ethers.getContractAt( - "AerodromeAMOStrategy", - cAMOStrategyProxy.address - ); - - return { - actions: [ - { - // 1. Set WETH share to be 1% to 15% - contract: cAMOStrategy, - signature: "setAllowedPoolWethShareInterval(uint256,uint256)", - args: [ - utils.parseUnits("0.010000001", 18), - utils.parseUnits("0.15", 18), - ], - }, - ], - }; - } -); diff --git a/contracts/deploy/base/024_multisig_as_canceller.js b/contracts/deploy/base/024_multisig_as_canceller.js deleted file mode 100644 index 427c4a5983..0000000000 --- a/contracts/deploy/base/024_multisig_as_canceller.js +++ /dev/null @@ -1,27 +0,0 @@ -const { deployOnBase } = require("../../utils/deploy-l2"); -const addresses = require("../../utils/addresses"); - -module.exports = deployOnBase( - { - deployName: "024_multisig_as_canceller", - }, - async ({ ethers }) => { - const cTimelock = await ethers.getContractAt( - "ITimelockController", - addresses.base.timelock - ); - - const timelockCancellerRole = await cTimelock.CANCELLER_ROLE(); - - return { - name: "Grant canceller role to 5/8 Multisig", - actions: [ - { - contract: cTimelock, - signature: "grantRole(bytes32,address)", - args: [timelockCancellerRole, addresses.base.governor], - }, - ], - }; - } -); diff --git a/contracts/deploy/base/025_base_curve_amo.js b/contracts/deploy/base/025_base_curve_amo.js deleted file mode 100644 index 15666dd398..0000000000 --- a/contracts/deploy/base/025_base_curve_amo.js +++ /dev/null @@ -1,84 +0,0 @@ -const { deployOnBase } = require("../../utils/deploy-l2"); -const { - deployWithConfirmation, - withConfirmation, -} = require("../../utils/deploy"); -const addresses = require("../../utils/addresses"); -const { oethUnits } = require("../../test/helpers"); - -module.exports = deployOnBase( - { - deployName: "025_base_curve_amo", - }, - async ({ ethers }) => { - const { deployerAddr } = await getNamedAccounts(); - const sDeployer = await ethers.provider.getSigner(deployerAddr); - - // Deploy Base Curve AMO proxy - const cOETHbProxy = await ethers.getContract("OETHBaseProxy"); - const cOETHbVaultProxy = await ethers.getContract("OETHBaseVaultProxy"); - const cOETHbVaultAdmin = await ethers.getContractAt( - "OETHBaseVaultAdmin", - cOETHbVaultProxy.address - ); - - const dOETHBaseCurveAMOProxy = await deployWithConfirmation( - "OETHBaseCurveAMOProxy", - [] - ); - - const cOETHBaseCurveAMOProxy = await ethers.getContract( - "OETHBaseCurveAMOProxy" - ); - - // Deploy Base Curve AMO implementation - const dOETHBaseCurveAMO = await deployWithConfirmation( - "BaseCurveAMOStrategy", - [ - [addresses.base.OETHb_WETH.pool, cOETHbVaultProxy.address], - cOETHbProxy.address, - addresses.base.WETH, - addresses.base.OETHb_WETH.gauge, - addresses.base.childLiquidityGaugeFactory, - 1, // SuperOETH is coin 1 of the Curve WETH/SuperOETH pool - 0, // WETH is coin 0 of the Curve WETH/SuperOETH pool - ] - ); - const cOETHBaseCurveAMO = await ethers.getContractAt( - "BaseCurveAMOStrategy", - dOETHBaseCurveAMOProxy.address - ); - - // Initialize Base Curve AMO implementation - const initData = cOETHBaseCurveAMO.interface.encodeFunctionData( - "initialize(address[],uint256)", - [[addresses.base.CRV], oethUnits("0.002")] - ); - await withConfirmation( - // prettier-ignore - cOETHBaseCurveAMOProxy - .connect(sDeployer)["initialize(address,address,bytes)"]( - dOETHBaseCurveAMO.address, - addresses.base.timelock, - initData - ) - ); - - return { - actions: [ - // Approve strategy on vault - { - contract: cOETHbVaultAdmin, - signature: "approveStrategy(address)", - args: [cOETHBaseCurveAMOProxy.address], - }, - // Add strategy to mint whitelist - { - contract: cOETHbVaultAdmin, - signature: "addStrategyToMintWhitelist(address)", - args: [cOETHBaseCurveAMOProxy.address], - }, - ], - }; - } -); diff --git a/contracts/deploy/base/026_harvester_v2.js b/contracts/deploy/base/026_harvester_v2.js deleted file mode 100644 index 748f6f2003..0000000000 --- a/contracts/deploy/base/026_harvester_v2.js +++ /dev/null @@ -1,73 +0,0 @@ -const addresses = require("../../utils/addresses"); -const { deployWithConfirmation } = require("../../utils/deploy"); -const { deployOnBase } = require("../../utils/deploy-l2"); - -module.exports = deployOnBase( - { - deployName: "026_harvester_v2", - }, - async ({ ethers }) => { - const cHarvesterProxy = await ethers.getContract("OETHBaseHarvesterProxy"); - const cAMOStrategyProxy = await ethers.getContract( - "AerodromeAMOStrategyProxy" - ); - const cCurveAMOStrategyProxy = await ethers.getContract( - "OETHBaseCurveAMOProxy" - ); - const cCurveAMOStrategy = await ethers.getContractAt( - "BaseCurveAMOStrategy", - cCurveAMOStrategyProxy.address - ); - - const dHarvester = await deployWithConfirmation("SuperOETHHarvester", [ - addresses.base.WETH, - ]); - console.log("SuperOETHHarvester deployed at", dHarvester.address); - - const cHarvester = await ethers.getContractAt( - "SuperOETHHarvester", - cHarvesterProxy.address - ); - - const cDripperProxy = await ethers.getContract("OETHBaseDripperProxy"); - - return { - actions: [ - { - // Upgrade the harvester - contract: cHarvesterProxy, - signature: "upgradeTo(address)", - args: [dHarvester.address], - }, - { - // Upgrade the harvester - contract: cHarvester, - signature: "initialize(address,address,address)", - args: [ - addresses.base.timelock, - addresses.multichainStrategist, - cDripperProxy.address, - ], - }, - { - // Mark Aerodome AMO strategy as supported - contract: cHarvester, - signature: "setSupportedStrategy(address,bool)", - args: [cAMOStrategyProxy.address, true], - }, - { - // Mark Curve AMO strategy as supported - contract: cHarvester, - signature: "setSupportedStrategy(address,bool)", - args: [cCurveAMOStrategyProxy.address, true], - }, - { - // Set the harvester address on the Curve AMO strategy - contract: cCurveAMOStrategy, - signature: "setHarvesterAddress(address)", - args: [cHarvesterProxy.address], - }, - ], - }; - } -); diff --git a/contracts/deploy/base/027_base_curve_amo_upgrade.js b/contracts/deploy/base/027_base_curve_amo_upgrade.js deleted file mode 100644 index fa838d7642..0000000000 --- a/contracts/deploy/base/027_base_curve_amo_upgrade.js +++ /dev/null @@ -1,69 +0,0 @@ -const { deployOnBase } = require("../../utils/deploy-l2"); -const { deployWithConfirmation } = require("../../utils/deploy"); -const addresses = require("../../utils/addresses"); - -module.exports = deployOnBase( - { - deployName: "027_base_curve_amo_upgrade", - }, - async ({ ethers }) => { - // Get the SuperOETH, Vault and Curve AMO contracts - const cOETHbProxy = await ethers.getContract("OETHBaseProxy"); - const cOETHbVaultProxy = await ethers.getContract("OETHBaseVaultProxy"); - const cOETHbVault = await ethers.getContractAt( - "IVault", - cOETHbVaultProxy.address - ); - const cOETHBaseCurveAMOProxy = await ethers.getContract( - "OETHBaseCurveAMOProxy" - ); - - // Deploy Base Curve AMO implementation - const dOETHBaseCurveAMOImpl = await deployWithConfirmation( - "BaseCurveAMOStrategy", - [ - [addresses.base.OETHb_WETH.pool, cOETHbVaultProxy.address], - cOETHbProxy.address, - addresses.base.WETH, - addresses.base.OETHb_WETH.gauge, - addresses.base.childLiquidityGaugeFactory, - 1, // SuperOETH is coin 1 of the Curve WETH/SuperOETH pool - 0, // WETH is coin 0 of the Curve WETH/SuperOETH pool - ] - ); - - // Deploy new Vault Core implementation - const dOETHbVaultCore = await deployWithConfirmation("OETHBaseVaultCore", [ - addresses.base.WETH, - ]); - - // Deploy new Vault Admin implementation - const dOETHbVaultAdmin = await deployWithConfirmation( - "OETHBaseVaultAdmin", - [addresses.base.WETH] - ); - - return { - actions: [ - // 1. Upgrade the Base Curve AMO Strategy implementation - { - contract: cOETHBaseCurveAMOProxy, - signature: "upgradeTo(address)", - args: [dOETHBaseCurveAMOImpl.address], - }, - // 2. Upgrade VaultCore - { - contract: cOETHbVaultProxy, - signature: "upgradeTo(address)", - args: [dOETHbVaultCore.address], - }, - // 3. Upgrade VaultAdmin - { - contract: cOETHbVault, - signature: "setAdminImpl(address)", - args: [dOETHbVaultAdmin.address], - }, - ], - }; - } -); diff --git a/contracts/deploy/base/028_vault_woeth_upgrade.js b/contracts/deploy/base/028_vault_woeth_upgrade.js deleted file mode 100644 index 76c11003e7..0000000000 --- a/contracts/deploy/base/028_vault_woeth_upgrade.js +++ /dev/null @@ -1,83 +0,0 @@ -const { deployOnBase } = require("../../utils/deploy-l2"); -const { deployWithConfirmation } = require("../../utils/deploy"); -const { parseUnits } = require("ethers/lib/utils.js"); -const addresses = require("../../utils/addresses"); - -module.exports = deployOnBase( - { - deployName: "028_vault_woeth_upgrade", - }, - async ({ ethers }) => { - const cOETHbVaultProxy = await ethers.getContract("OETHBaseVaultProxy"); - const cwOETHbProxy = await ethers.getContract("WOETHBaseProxy"); - const cOETHbProxy = await ethers.getContract("OETHBaseProxy"); - - const cOETHbVault = await ethers.getContractAt( - "IVault", - cOETHbVaultProxy.address - ); - - // Deploy new implementation - const dOETHbVaultCore = await deployWithConfirmation("OETHBaseVaultCore", [ - addresses.base.WETH, - ]); - const dOETHbVaultAdmin = await deployWithConfirmation( - "OETHBaseVaultAdmin", - [addresses.base.WETH] - ); - - const dwOETHb = await deployWithConfirmation("WOETHBase", [ - cOETHbProxy.address, // Base OETH token - ]); - - const cwOETHb = await ethers.getContractAt( - "WOETHBase", - cwOETHbProxy.address - ); - - // ---------------- - // Governance Actions - // ---------------- - return { - name: "Add rate limiting to Origin Base Vault", - actions: [ - // 1. Upgrade Vault proxy to VaultCore - { - contract: cOETHbVaultProxy, - signature: "upgradeTo(address)", - args: [dOETHbVaultCore.address], - }, - // 2. Set the VaultAdmin - { - contract: cOETHbVault, - signature: "setAdminImpl(address)", - args: [dOETHbVaultAdmin.address], - }, - // 3. Default to a short dripper, since currently we are running zero dripper. - { - contract: cOETHbVault, - signature: "setDripDuration(uint256)", - args: [4 * 60 * 60], - }, - // 4. Default to a 20% APR rebase rate cap - { - contract: cOETHbVault, - signature: "setRebaseRateMax(uint256)", - args: [parseUnits("20", 18)], - }, - { - // 5. Upgrade wOETHb proxy - contract: cwOETHbProxy, - signature: "upgradeTo(address)", - args: [dwOETHb.address], - }, - // 6. Run the second initializer - { - contract: cwOETHb, - signature: "initialize2()", - args: [], - }, - ], - }; - } -); diff --git a/contracts/deploy/base/030_claimbribes_safe_module.js b/contracts/deploy/base/030_claimbribes_safe_module.js deleted file mode 100644 index 2384574354..0000000000 --- a/contracts/deploy/base/030_claimbribes_safe_module.js +++ /dev/null @@ -1,125 +0,0 @@ -const { deployOnBase } = require("../../utils/deploy-l2"); -const { - deployWithConfirmation, - withConfirmation, -} = require("../../utils/deploy"); - -const addresses = require("../../utils/addresses"); -const { isFork } = require("../../utils/hardhat-helpers"); -const { impersonateAndFund } = require("../../utils/signers"); - -module.exports = deployOnBase( - { - deployName: "030_claimbribes_safe_module", - }, - async ({ ethers }) => { - const safeAddress = "0xb6D85Ce798660076152d6FD3a484129668839c95"; - const voter = "0x16613524e02ad97eDfeF371bC883F2F5d6C480A5"; - const veAero = "0xebf418fe2512e7e6bd9b87a8f0f294acdc67e6b4"; - const usdcAddr = "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913"; - - await deployWithConfirmation("ClaimBribesSafeModule", [ - safeAddress, - voter, - veAero, - ]); - const cClaimBribesSafeModule = await ethers.getContract( - "ClaimBribesSafeModule" - ); - - if (isFork) { - const OETHb = await ethers.getContract("OETHBaseProxy"); - const safeSigner = await impersonateAndFund(safeAddress); - - const cSafe = await ethers.getContractAt( - ["function enableModule(address module) external"], - safeAddress - ); - - await withConfirmation( - cSafe.connect(safeSigner).enableModule(cClaimBribesSafeModule.address) - ); - console.log("Enabled module"); - - // await withConfirmation( - // cClaimBribesSafeModule.connect(cSafe).grantRole( - // ethers.utils.keccak256("EXECUTOR_ROLE"), - // safeAddress - // ) - // ); - - // OETHb/WETH - await withConfirmation( - cClaimBribesSafeModule - .connect(safeSigner) - .addBribePool(addresses.base.aerodromeOETHbWETHClPool) - ); - // WETH/USDC - await withConfirmation( - cClaimBribesSafeModule - .connect(safeSigner) - .addBribePool("0xb2cc224c1c9feE385f8ad6a55b4d94E92359DC59") - ); - console.log("Added bribe pools"); - - const nftIdsToTest = [ - 72538, 72539, 72540, 72541, 72542, 72543, 72544, 72545, 72546, 72547, - 72548, 72549, 72550, 72551, 72552, 72553, 72554, 72555, 72556, 72557, - 72558, 72559, 72560, 72561, 72562, 72563, 72564, 72565, 72566, 72567, - 72568, 72569, 72570, 72571, 72572, 72573, 72574, 72575, 72576, 72577, - 72578, 72579, 72580, 72581, 72582, 72583, 72584, 72585, 72586, 72587, - ]; - - await withConfirmation( - cClaimBribesSafeModule.connect(safeSigner).addNFTIds(nftIdsToTest) - ); - console.log("Added NFTs"); - - const weth = await ethers.getContractAt("IERC20", addresses.base.WETH); - const aero = await ethers.getContractAt("IERC20", addresses.base.AERO); - const oethb = await ethers.getContractAt("IERC20", OETHb.address); - const usdc = await ethers.getContractAt("IERC20", usdcAddr); - const wethBalanceBefore = await weth.balanceOf(safeAddress); - const aeroBalanceBefore = await aero.balanceOf(safeAddress); - const oethbBalanceBefore = await oethb.balanceOf(safeAddress); - const usdcBalanceBefore = await usdc.balanceOf(safeAddress); - - for (let i = 0; i < Math.ceil(nftIdsToTest.length / 9); i += 9) { - console.log(i, i + 9); - await withConfirmation( - cClaimBribesSafeModule - .connect(safeSigner) - .claimBribes(i, i + 9, false) - ); - } - console.log("Claimed bribes"); - - const wethBalanceAfter = await weth.balanceOf(safeAddress); - const aeroBalanceAfter = await aero.balanceOf(safeAddress); - const oethbBalanceAfter = await oethb.balanceOf(safeAddress); - const usdcBalanceAfter = await usdc.balanceOf(safeAddress); - console.log( - "WETH balance", - wethBalanceBefore.toString(), - wethBalanceAfter.toString() - ); - console.log( - "AERO balance", - aeroBalanceBefore.toString(), - aeroBalanceAfter.toString() - ); - console.log( - "OETHb balance", - oethbBalanceBefore.toString(), - oethbBalanceAfter.toString() - ); - console.log( - "USDC balance", - usdcBalanceBefore.toString(), - usdcBalanceAfter.toString() - ); - } - - return {}; - } -); diff --git a/contracts/deploy/base/031_enable_buyback_operator.js b/contracts/deploy/base/031_enable_buyback_operator.js deleted file mode 100644 index a668244a24..0000000000 --- a/contracts/deploy/base/031_enable_buyback_operator.js +++ /dev/null @@ -1,26 +0,0 @@ -const { deployOnBase } = require("../../utils/deploy-l2.js"); -const addresses = require("../../utils/addresses"); - -module.exports = deployOnBase( - { - deployName: "031_enable_buyback_operator", - }, - async () => { - const cOETHBaseVaultProxy = await ethers.getContract("OETHBaseVaultProxy"); - const cOETHBaseVault = await ethers.getContractAt( - "IVault", - cOETHBaseVaultProxy.address - ); - - return { - name: "Enable Buyback Operator", - actions: [ - { - contract: cOETHBaseVault, - signature: "setTrusteeAddress(address)", - args: [addresses.multichainBuybackOperator], - }, - ], - }; - } -); diff --git a/contracts/deploy/base/032_vault_perf_fee.js b/contracts/deploy/base/032_vault_perf_fee.js deleted file mode 100644 index 6980620ba6..0000000000 --- a/contracts/deploy/base/032_vault_perf_fee.js +++ /dev/null @@ -1,25 +0,0 @@ -const { deployOnBase } = require("../../utils/deploy-l2.js"); - -module.exports = deployOnBase( - { - deployName: "032_vault_perf_fee", - }, - async () => { - const cOETHBaseVaultProxy = await ethers.getContract("OETHBaseVaultProxy"); - const cOETHBaseVault = await ethers.getContractAt( - "IVault", - cOETHBaseVaultProxy.address - ); - - return { - name: "Enable Buyback Operator", - actions: [ - { - contract: cOETHBaseVault, - signature: "setTrusteeFeeBps(uint256)", - args: [2000], // 20% - }, - ], - }; - } -); diff --git a/contracts/deploy/base/033_bridge_module.js b/contracts/deploy/base/033_bridge_module.js deleted file mode 100644 index 76d2a1ecca..0000000000 --- a/contracts/deploy/base/033_bridge_module.js +++ /dev/null @@ -1,39 +0,0 @@ -const { deployOnBase } = require("../../utils/deploy-l2.js"); -const addresses = require("../../utils/addresses"); -const { isFork } = require("../../utils/hardhat-helpers"); -const { impersonateAndFund } = require("../../utils/signers"); - -module.exports = deployOnBase( - { - deployName: "033_bridge_helper_module", - }, - async ({ deployWithConfirmation, withConfirmation }) => { - await deployWithConfirmation("BaseBridgeHelperModule", [ - addresses.multichainStrategist, - ]); - const cBaseBridgeHelperModule = await ethers.getContract( - "BaseBridgeHelperModule" - ); - - if (isFork) { - const safeSigner = await impersonateAndFund( - addresses.multichainStrategist - ); - - const cSafe = await ethers.getContractAt( - ["function enableModule(address module) external"], - addresses.multichainStrategist - ); - - await withConfirmation( - cSafe.connect(safeSigner).enableModule(cBaseBridgeHelperModule.address) - ); - - console.log("Enabled module on fork"); - } - - return { - actions: [], - }; - } -); diff --git a/contracts/deploy/base/034_claimbribes_module_v2.js b/contracts/deploy/base/034_claimbribes_module_v2.js deleted file mode 100644 index e84f69c060..0000000000 --- a/contracts/deploy/base/034_claimbribes_module_v2.js +++ /dev/null @@ -1,53 +0,0 @@ -const { deployOnBase } = require("../../utils/deploy-l2"); -const { deployWithConfirmation } = require("../../utils/deploy"); -const addresses = require("../../utils/addresses"); - -module.exports = deployOnBase( - { - deployName: "034_claimbribes_module_v2", - }, - async ({ ethers }) => { - const voter = "0x16613524e02ad97eDfeF371bC883F2F5d6C480A5"; - const veAero = "0xebf418fe2512e7e6bd9b87a8f0f294acdc67e6b4"; - - await deployWithConfirmation( - "ClaimBribesSafeModule1", - [ - // AERO locker Safe - "0xb6D85Ce798660076152d6FD3a484129668839c95", - voter, - veAero, - ], - "ClaimBribesSafeModule" - ); - - const cClaimBribesSafeModule1 = await ethers.getContract( - "ClaimBribesSafeModule1" - ); - console.log( - "ClaimBribesSafeModule1 deployed at", - cClaimBribesSafeModule1.address - ); - - await deployWithConfirmation( - "ClaimBribesSafeModule2", - [ - // Multichain Guardian Safe - addresses.multichainStrategist, - voter, - veAero, - ], - "ClaimBribesSafeModule" - ); - - const cClaimBribesSafeModule2 = await ethers.getContract( - "ClaimBribesSafeModule2" - ); - console.log( - "ClaimBribesSafeModule2 deployed at", - cClaimBribesSafeModule2.address - ); - - return {}; - } -); diff --git a/contracts/deploy/base/035_claimbribes_module_old_guardian.js b/contracts/deploy/base/035_claimbribes_module_old_guardian.js deleted file mode 100644 index 2670733493..0000000000 --- a/contracts/deploy/base/035_claimbribes_module_old_guardian.js +++ /dev/null @@ -1,34 +0,0 @@ -const { deployOnBase } = require("../../utils/deploy-l2"); -const { deployWithConfirmation } = require("../../utils/deploy"); -const addresses = require("../../utils/addresses"); - -module.exports = deployOnBase( - { - deployName: "035_claimbribes_module_old_guardian", - }, - async ({ ethers }) => { - const voter = "0x16613524e02ad97eDfeF371bC883F2F5d6C480A5"; - const veAero = "0xebf418fe2512e7e6bd9b87a8f0f294acdc67e6b4"; - - await deployWithConfirmation( - "ClaimBribesSafeModule3", - [ - // Old Guardian Safe - addresses.base.strategist, - voter, - veAero, - ], - "ClaimBribesSafeModule" - ); - - const cClaimBribesSafeModule3 = await ethers.getContract( - "ClaimBribesSafeModule3" - ); - console.log( - "ClaimBribesSafeModule3 deployed at", - cClaimBribesSafeModule3.address - ); - - return {}; - } -); diff --git a/contracts/deploy/base/036_oethb_upgrade_EIP7702.js b/contracts/deploy/base/036_oethb_upgrade_EIP7702.js deleted file mode 100644 index 3143047983..0000000000 --- a/contracts/deploy/base/036_oethb_upgrade_EIP7702.js +++ /dev/null @@ -1,30 +0,0 @@ -const { deployOnBase } = require("../../utils/deploy-l2"); -const { deployWithConfirmation } = require("../../utils/deploy"); - -module.exports = deployOnBase( - { - deployName: "036_oethb_upgrade_EIP7702", - // forceSkip: true, - }, - async ({ ethers }) => { - const dOETHb = await deployWithConfirmation( - "OETHBase", - [], - undefined, - true - ); - - const cOETHbProxy = await ethers.getContract("OETHBaseProxy"); - - return { - actions: [ - { - // 1. Upgrade OETH - contract: cOETHbProxy, - signature: "upgradeTo(address)", - args: [dOETHb.address], - }, - ], - }; - } -); diff --git a/contracts/deploy/base/037_deploy_harvester.js b/contracts/deploy/base/037_deploy_harvester.js deleted file mode 100644 index 379032e7da..0000000000 --- a/contracts/deploy/base/037_deploy_harvester.js +++ /dev/null @@ -1,99 +0,0 @@ -const addresses = require("../../utils/addresses"); -const { deployWithConfirmation } = require("../../utils/deploy"); -const { deployOnBase } = require("../../utils/deploy-l2"); - -module.exports = deployOnBase( - { - deployName: "037_deploy_harvester", - }, - async ({ ethers, withConfirmation }) => { - const { deployerAddr } = await getNamedAccounts(); - const sDeployer = await ethers.provider.getSigner(deployerAddr); - - await deployWithConfirmation("OETHBaseHarvesterProxy"); - const cHarvesterProxy = await ethers.getContract("OETHBaseHarvesterProxy"); - const cAerodromeAMOStrategyProxy = await ethers.getContract( - "AerodromeAMOStrategyProxy" - ); - const cAerodromeAMOStrategy = await ethers.getContractAt( - "AerodromeAMOStrategy", - cAerodromeAMOStrategyProxy.address - ); - const cCurveAMOStrategyProxy = await ethers.getContract( - "OETHBaseCurveAMOProxy" - ); - const cCurveAMOStrategy = await ethers.getContractAt( - "BaseCurveAMOStrategy", - cCurveAMOStrategyProxy.address - ); - - const dHarvester = await deployWithConfirmation("SuperOETHHarvester", [ - addresses.base.WETH, - ]); - console.log("SuperOETHHarvester deployed at", dHarvester.address); - - const cHarvester = await ethers.getContractAt( - "SuperOETHHarvester", - cHarvesterProxy.address - ); - - const initData = cHarvester.interface.encodeFunctionData( - "initialize()", - [] - ); - - // prettier-ignore - await withConfirmation( - cHarvesterProxy - .connect(sDeployer)["initialize(address,address,bytes)"]( - dHarvester.address, - addresses.base.timelock, - initData - ) - ); - console.log("Initialized OETHBaseHarvesterProxy"); - - const cOETHbVaultProxy = await ethers.getContract("OETHBaseVaultProxy"); - - return { - actions: [ - { - // Set Vault as Dripper - contract: cHarvester, - signature: "setDripper(address)", - args: [cOETHbVaultProxy.address], - }, - { - // Set Multi-chain Guardian as Strategist - contract: cHarvester, - signature: "setStrategistAddr(address)", - args: [addresses.base.multichainStrategist], - }, - { - // Mark Aerodome AMO strategy as supported - contract: cHarvester, - signature: "setSupportedStrategy(address,bool)", - args: [cAerodromeAMOStrategyProxy.address, true], - }, - { - // Mark Curve AMO strategy as supported - contract: cHarvester, - signature: "setSupportedStrategy(address,bool)", - args: [cCurveAMOStrategyProxy.address, true], - }, - { - // Set the harvester address on the Aerodrome AMO strategy - contract: cAerodromeAMOStrategy, - signature: "setHarvesterAddress(address)", - args: [cHarvesterProxy.address], - }, - { - // Set the harvester address on the Curve AMO strategy - contract: cCurveAMOStrategy, - signature: "setHarvesterAddress(address)", - args: [cHarvesterProxy.address], - }, - ], - }; - } -); diff --git a/contracts/deploy/base/038_vault_upgrade.js b/contracts/deploy/base/038_vault_upgrade.js deleted file mode 100644 index 67ed5a9919..0000000000 --- a/contracts/deploy/base/038_vault_upgrade.js +++ /dev/null @@ -1,37 +0,0 @@ -const { deployOnBase } = require("../../utils/deploy-l2"); -const { deployWithConfirmation } = require("../../utils/deploy"); -const addresses = require("../../utils/addresses"); - -module.exports = deployOnBase( - { - deployName: "038_vault_upgrade", - //proposalId: "", - }, - async ({ ethers }) => { - const cOETHbVaultProxy = await ethers.getContract("OETHBaseVaultProxy"); - - // Deploy new implementation without storage slot checks because of the: - // - Renamed `dripper` to `_deprecated_dripper` - const dOETHbVaultCore = await deployWithConfirmation( - "OETHBaseVaultCore", - [addresses.base.WETH], - "OETHBaseVaultCore", - true - ); - - // ---------------- - // Governance Actions - // ---------------- - return { - name: "Upgrade VaultCore", - actions: [ - // 1. Upgrade VaultCore implementation - { - contract: cOETHbVaultProxy, - signature: "upgradeTo(address)", - args: [dOETHbVaultCore.address], - }, - ], - }; - } -); diff --git a/contracts/deploy/base/039_pool_booster_factory.js b/contracts/deploy/base/039_pool_booster_factory.js deleted file mode 100644 index b96f44929c..0000000000 --- a/contracts/deploy/base/039_pool_booster_factory.js +++ /dev/null @@ -1,79 +0,0 @@ -const { deployOnBase } = require("../../utils/deploy-l2"); -const addresses = require("../../utils/addresses"); -const { - deployWithConfirmation, - withConfirmation, -} = require("../../utils/deploy.js"); - -module.exports = deployOnBase( - { - deployName: "039_pool_booster_factory", - }, - async ({ ethers }) => { - const { deployerAddr } = await getNamedAccounts(); - const sDeployer = await ethers.provider.getSigner(deployerAddr); - - // 1. Fetch contracts - const cOETHbProxy = await ethers.getContract("OETHBaseProxy"); - - // 2. Deploy PoolBooster Central Registry (proxy + implementation) - await deployWithConfirmation("PoolBoostCentralRegistryProxy", []); - const cPoolBoostCentralRegistryProxy = await ethers.getContract( - "PoolBoostCentralRegistryProxy" - ); - console.log( - `Pool boost central registry proxy deployed: ${cPoolBoostCentralRegistryProxy.address}` - ); - - const dPoolBoostCentralRegistry = await deployWithConfirmation( - "PoolBoostCentralRegistry", - [] - ); - console.log( - `Deployed Pool Boost Central Registry to ${dPoolBoostCentralRegistry.address}` - ); - const cPoolBoostCentralRegistry = await ethers.getContractAt( - "PoolBoostCentralRegistry", - cPoolBoostCentralRegistryProxy.address - ); - - // prettier-ignore - await withConfirmation( - cPoolBoostCentralRegistryProxy - .connect(sDeployer)["initialize(address,address,bytes)"]( - dPoolBoostCentralRegistry.address, - addresses.base.timelock, - "0x" - ) - ); - console.log( - "Initialized PoolBoostCentralRegistry proxy and implementation" - ); - - // 3. Deploy PoolBoosterFactory for Merkl - const dPoolBoosterFactory = await deployWithConfirmation( - "PoolBoosterFactoryMerkl_v1", - [ - cOETHbProxy.address, - addresses.base.multichainStrategist, - cPoolBoostCentralRegistryProxy.address, - addresses.base.MerklDistributor, - ], - "PoolBoosterFactoryMerkl" - ); - console.log( - `Pool Booster Merkl Factory deployed to ${dPoolBoosterFactory.address}` - ); - - return { - actions: [ - { - // set the factory as an approved one - contract: cPoolBoostCentralRegistry, - signature: "approveFactory(address)", - args: [dPoolBoosterFactory.address], - }, - ], - }; - } -); diff --git a/contracts/deploy/deployActions.js b/contracts/deploy/deployActions.js index 7203da9f6a..79afc82e29 100644 --- a/contracts/deploy/deployActions.js +++ b/contracts/deploy/deployActions.js @@ -8,17 +8,10 @@ const { parseUnits } = require("ethers/lib/utils.js"); const addresses = require("../utils/addresses"); const { getAssetAddresses, - getOracleAddresses, - isMainnet, - isHolesky, - isHoleskyOrFork, - isSonicOrFork, isTest, isFork, isForkTest, isCI, - isPlume, - isHoodi, isHoodiOrFork, } = require("../test/helpers.js"); const { @@ -27,7 +20,6 @@ const { withConfirmation, encodeSaltForCreateX, } = require("../utils/deploy"); -const { metapoolLPCRVPid } = require("../utils/constants"); const { replaceContractAt } = require("../utils/hardhat"); const { resolveContract } = require("../utils/resolvers"); const { impersonateAccount, getSigner } = require("../utils/signers"); @@ -42,254 +34,6 @@ const { const log = require("../utils/logger")("deploy:core"); -/** - * Deploy AAVE Strategy which only supports USDC. - * Deploys a proxy, the actual strategy, initializes the proxy and initializes - * the strategy. - */ -const deployAaveStrategy = async () => { - const assetAddresses = await getAssetAddresses(hre.deployments); - const { governorAddr } = await getNamedAccounts(); - - const cVaultProxy = await ethers.getContract("VaultProxy"); - - const dAaveStrategyProxy = await deployWithConfirmation( - "AaveStrategyProxy", - [], - "InitializeGovernedUpgradeabilityProxy" - ); - const cAaveStrategyProxy = await ethers.getContract("AaveStrategyProxy"); - const dAaveStrategy = await deployWithConfirmation("AaveStrategy", [ - [assetAddresses.AAVE_ADDRESS_PROVIDER, cVaultProxy.address], - ]); - const cAaveStrategy = await ethers.getContractAt( - "AaveStrategy", - dAaveStrategyProxy.address - ); - - const cAaveIncentivesController = await ethers.getContract( - "MockAaveIncentivesController" - ); - - const initData = cAaveStrategy.interface.encodeFunctionData( - "initialize(address[],address[],address[],address,address)", - [ - [assetAddresses.AAVE_TOKEN], - [assetAddresses.USDC], - [assetAddresses.aUSDC], - cAaveIncentivesController.address, - assetAddresses.STKAAVE, - ] - ); - - await withConfirmation( - cAaveStrategyProxy["initialize(address,address,bytes)"]( - dAaveStrategy.address, - governorAddr, - initData - ) - ); - - log("Initialized AaveStrategyProxy"); - - return cAaveStrategy; -}; - -/** - * Deploy Compound Strategy which only supports USDS. - * Deploys a proxy, the actual strategy, initializes the proxy and initializes - * the strategy. - */ -const deployCompoundStrategy = async () => { - const assetAddresses = await getAssetAddresses(deployments); - const { governorAddr } = await getNamedAccounts(); - - const cVaultProxy = await ethers.getContract("VaultProxy"); - - const dCompoundStrategyProxy = await deployWithConfirmation( - "CompoundStrategyProxy" - ); - const cCompoundStrategyProxy = await ethers.getContract( - "CompoundStrategyProxy" - ); - const dCompoundStrategy = await deployWithConfirmation("CompoundStrategy", [ - [addresses.dead, cVaultProxy.address], - ]); - const cCompoundStrategy = await ethers.getContractAt( - "CompoundStrategy", - dCompoundStrategyProxy.address - ); - - const initData = cCompoundStrategy.interface.encodeFunctionData( - "initialize(address[],address[],address[])", - [[assetAddresses.COMP], [assetAddresses.USDS], [assetAddresses.cUSDS]] - ); - - await withConfirmation( - cCompoundStrategyProxy["initialize(address,address,bytes)"]( - dCompoundStrategy.address, - governorAddr, - initData - ) - ); - - return cCompoundStrategy; -}; - -/** - * Deploys a Convex Strategy which supports USDC, USDT and USDS. - */ -const deployConvexStrategy = async () => { - const assetAddresses = await getAssetAddresses(deployments); - const { deployerAddr, governorAddr } = await getNamedAccounts(); - // Signers - const sDeployer = await ethers.provider.getSigner(deployerAddr); - const sGovernor = await ethers.provider.getSigner(governorAddr); - - const cVaultProxy = await ethers.getContract("VaultProxy"); - - await deployWithConfirmation("ConvexStrategyProxy"); - const cConvexStrategyProxy = await ethers.getContract("ConvexStrategyProxy"); - - const dConvexStrategy = await deployWithConfirmation("ConvexStrategy", [ - [assetAddresses.ThreePool, cVaultProxy.address], - ]); - const cConvexStrategy = await ethers.getContractAt( - "ConvexStrategy", - cConvexStrategyProxy.address - ); - - await withConfirmation( - cConvexStrategyProxy["initialize(address,address,bytes)"]( - dConvexStrategy.address, - deployerAddr, - [] - ) - ); - log("Initialized ConvexStrategyProxy"); - - // Initialize Strategies - const mockBooster = await ethers.getContract("MockBooster"); - const mockRewardPool = await ethers.getContract("MockRewardPool"); - await withConfirmation( - cConvexStrategy.connect(sDeployer)[ - // eslint-disable-next-line no-unexpected-multiline - "initialize(address[],address[],address[],address,address,uint256)" - ]( - [assetAddresses.CRV, assetAddresses.CVX], - [assetAddresses.USDS, assetAddresses.USDC, assetAddresses.USDT], - [ - assetAddresses.ThreePoolToken, - assetAddresses.ThreePoolToken, - assetAddresses.ThreePoolToken, - ], - mockBooster.address, // _cvxDepositorAddress, - mockRewardPool.address, // _cvxRewardStakerAddress, - 9 // _cvxDepositorPTokenId - ) - ); - log("Initialized ConvexStrategy"); - - await withConfirmation( - cConvexStrategy.connect(sDeployer).transferGovernance(governorAddr) - ); - log(`ConvexStrategy transferGovernance(${governorAddr}) called`); - // On Mainnet the governance transfer gets executed separately, via the - // multi-sig wallet. On other networks, this migration script can claim - // governance by the governor. - if (!isMainnet) { - await withConfirmation( - cConvexStrategy - .connect(sGovernor) // Claim governance with governor - .claimGovernance() - ); - log("Claimed governance for ConvexStrategy"); - } - return cConvexStrategy; -}; - -/** - * Deploys a Convex Meta Strategy which supports OUSD / 3Crv - */ -const deployConvexOUSDMetaStrategy = async () => { - const assetAddresses = await getAssetAddresses(deployments); - const { deployerAddr, governorAddr } = await getNamedAccounts(); - // Signers - const sDeployer = await ethers.provider.getSigner(deployerAddr); - const sGovernor = await ethers.provider.getSigner(governorAddr); - - const cVaultProxy = await ethers.getContract("VaultProxy"); - - await deployWithConfirmation("ConvexOUSDMetaStrategyProxy"); - const cConvexOUSDMetaStrategyProxy = await ethers.getContract( - "ConvexOUSDMetaStrategyProxy" - ); - - const dConvexOUSDMetaStrategy = await deployWithConfirmation( - "ConvexOUSDMetaStrategy", - [[assetAddresses.ThreePool, cVaultProxy.address]] - ); - const cConvexOUSDMetaStrategy = await ethers.getContractAt( - "ConvexOUSDMetaStrategy", - cConvexOUSDMetaStrategyProxy.address - ); - - await withConfirmation( - cConvexOUSDMetaStrategyProxy["initialize(address,address,bytes)"]( - dConvexOUSDMetaStrategy.address, - deployerAddr, - [] - ) - ); - log("Initialized ConvexOUSDMetaStrategyProxy"); - - // Initialize Strategies - const mockBooster = await ethers.getContract("MockBooster"); - const mockRewardPool = await ethers.getContract("MockRewardPool"); - const ousd = await ethers.getContract("OUSDProxy"); - - await withConfirmation( - cConvexOUSDMetaStrategy.connect(sDeployer)[ - // eslint-disable-next-line no-unexpected-multiline - "initialize(address[],address[],address[],(address,address,address,address,address,uint256))" - ]( - [assetAddresses.CVX, assetAddresses.CRV], - [assetAddresses.USDS, assetAddresses.USDC, assetAddresses.USDT], - [ - assetAddresses.ThreePoolToken, - assetAddresses.ThreePoolToken, - assetAddresses.ThreePoolToken, - ], - [ - mockBooster.address, // _cvxDepositorAddress, - assetAddresses.ThreePoolOUSDMetapool, // metapool address, - ousd.address, // _ousdAddress, - mockRewardPool.address, // _cvxRewardStakerAddress, - assetAddresses.ThreePoolOUSDMetapool, // metapoolLpToken (metapool address), - metapoolLPCRVPid, // _cvxDepositorPTokenId - ] - ) - ); - log("Initialized ConvexOUSDMetaStrategy"); - - await withConfirmation( - cConvexOUSDMetaStrategy.connect(sDeployer).transferGovernance(governorAddr) - ); - log(`ConvexOUSDMetaStrategy transferGovernance(${governorAddr}) called`); - // On Mainnet the governance transfer gets executed separately, via the - // multi-sig wallet. On other networks, this migration script can claim - // governance by the governor. - if (!isMainnet) { - await withConfirmation( - cConvexOUSDMetaStrategy - .connect(sGovernor) // Claim governance with governor - .claimGovernance() - ); - log("Claimed governance for ConvexOUSDMetaStrategy"); - } - return cConvexOUSDMetaStrategy; -}; - /** * Configure Vault by adding supported assets and Strategies. */ @@ -367,268 +111,33 @@ const configureOETHVault = async () => { ); }; -const deployOUSDHarvester = async (ousdDripper) => { - const assetAddresses = await getAssetAddresses(deployments); - const { governorAddr } = await getNamedAccounts(); - const sGovernor = await ethers.provider.getSigner(governorAddr); - - const cVaultProxy = await ethers.getContract("VaultProxy"); - - const dHarvesterProxy = await deployWithConfirmation( - "HarvesterProxy", - [], - "InitializeGovernedUpgradeabilityProxy" - ); - const cHarvesterProxy = await ethers.getContract("HarvesterProxy"); - - const dHarvester = await deployWithConfirmation("Harvester", [ - cVaultProxy.address, - assetAddresses.USDT, - ]); - - const cHarvester = await ethers.getContractAt( - "Harvester", - dHarvesterProxy.address - ); - - await withConfirmation( - cHarvesterProxy["initialize(address,address,bytes)"]( - dHarvester.address, - governorAddr, - [] - ) - ); - - log("Initialized HarvesterProxy"); - - await withConfirmation( - cHarvester - .connect(sGovernor) - .setRewardProceedsAddress( - isMainnet || isHolesky || isHoodi - ? ousdDripper.address - : cVaultProxy.address - ) - ); - - return dHarvesterProxy; -}; - -const upgradeOETHHarvester = async () => { - const assetAddresses = await getAssetAddresses(deployments); - const cOETHVaultProxy = await ethers.getContract("OETHVaultProxy"); - const cOETHHarvesterProxy = await ethers.getContract("OETHHarvesterProxy"); - - const dOETHHarvester = await deployWithConfirmation("OETHHarvester", [ - cOETHVaultProxy.address, - assetAddresses.WETH, - ]); - - await withConfirmation(cOETHHarvesterProxy.upgradeTo(dOETHHarvester.address)); - - log("Upgraded OETHHarvesterProxy"); - return cOETHHarvesterProxy; -}; - -const deployOETHHarvester = async (oethDripper) => { - const assetAddresses = await getAssetAddresses(deployments); - const { governorAddr } = await getNamedAccounts(); - const sGovernor = await ethers.provider.getSigner(governorAddr); - const cOETHVaultProxy = await ethers.getContract("OETHVaultProxy"); - - const dOETHHarvesterProxy = await deployWithConfirmation( - "OETHHarvesterProxy", - [], - "InitializeGovernedUpgradeabilityProxy" - ); - const cOETHHarvesterProxy = await ethers.getContract("OETHHarvesterProxy"); - - const dOETHHarvester = await deployWithConfirmation("OETHHarvester", [ - cOETHVaultProxy.address, - assetAddresses.WETH, - ]); - - const cOETHHarvester = await ethers.getContractAt( - "OETHHarvester", - dOETHHarvesterProxy.address - ); - - await withConfirmation( - // prettier-ignore - cOETHHarvesterProxy["initialize(address,address,bytes)"]( - dOETHHarvester.address, - governorAddr, - [] - ) - ); - - log("Initialized OETHHarvesterProxy"); - - await withConfirmation( - cOETHHarvester - .connect(sGovernor) - .setRewardProceedsAddress( - isMainnet || isHolesky || isHoodi - ? oethDripper.address - : cOETHVaultProxy.address - ) - ); - - return cOETHHarvester; -}; - -/** - * Deploy Harvester - */ -const deployHarvesters = async (ousdDripper, oethDripper) => { - const dHarvesterProxy = await deployOUSDHarvester(ousdDripper); - const dOETHHarvesterProxy = await deployOETHHarvester(oethDripper); - - return [dHarvesterProxy, dOETHHarvesterProxy]; -}; - -/** - * Configure Strategies by setting the Harvester address - */ -const configureStrategies = async (harvesterProxy, oethHarvesterProxy) => { - const { governorAddr } = await getNamedAccounts(); - // Signers - const sGovernor = await ethers.provider.getSigner(governorAddr); - - // Configure Compound Strategy if deployed - const compoundDeployment = await hre.deployments - .get("CompoundStrategyProxy") - .catch(() => null); - if (compoundDeployment) { - const compound = await ethers.getContractAt( - "CompoundStrategy", - compoundDeployment.address - ); - await withConfirmation( - compound.connect(sGovernor).setHarvesterAddress(harvesterProxy.address) - ); - } - - // Configure Aave Strategy if deployed - const aaveDeployment = await hre.deployments - .get("AaveStrategyProxy") - .catch(() => null); - if (aaveDeployment) { - const aave = await ethers.getContractAt( - "AaveStrategy", - aaveDeployment.address - ); - await withConfirmation( - aave.connect(sGovernor).setHarvesterAddress(harvesterProxy.address) - ); - } - - // Configure Convex Strategy if deployed - const convexDeployment = await hre.deployments - .get("ConvexStrategyProxy") - .catch(() => null); - if (convexDeployment) { - const convex = await ethers.getContractAt( - "ConvexStrategy", - convexDeployment.address - ); - await withConfirmation( - convex.connect(sGovernor).setHarvesterAddress(harvesterProxy.address) - ); - } - - const nativeStakingSSVStrategyProxy = await ethers.getContract( - "NativeStakingSSVStrategyProxy" - ); - const nativeStakingSSVStrategy = await ethers.getContractAt( - "NativeStakingSSVStrategy", - nativeStakingSSVStrategyProxy.address - ); - - await withConfirmation( - nativeStakingSSVStrategy - .connect(sGovernor) - .setHarvesterAddress(oethHarvesterProxy.address) - ); -}; - -const deployOUSDDripper = async () => { - const { governorAddr } = await getNamedAccounts(); - - const assetAddresses = await getAssetAddresses(deployments); - const cVaultProxy = await ethers.getContract("VaultProxy"); - - // Deploy Dripper Impl - const dDripper = await deployWithConfirmation("Dripper", [ - cVaultProxy.address, - assetAddresses.USDT, - ]); - await deployWithConfirmation("DripperProxy"); - // Deploy Dripper Proxy - const cDripperProxy = await ethers.getContract("DripperProxy"); - await withConfirmation( - cDripperProxy["initialize(address,address,bytes)"]( - dDripper.address, - governorAddr, - [] - ) - ); - const cDripper = await ethers.getContractAt( - "OETHDripper", - cDripperProxy.address - ); - +const deploySimpleOETHHarvester = async () => { + const { governorAddr, strategistAddr } = await getNamedAccounts(); const sGovernor = await ethers.provider.getSigner(governorAddr); - // duration of 14 days - await withConfirmation( - cDripper.connect(sGovernor).setDripDuration(14 * 24 * 60 * 60) - ); - - return cDripper; -}; - -const deployOETHDripper = async () => { - const { governorAddr } = await getNamedAccounts(); - const assetAddresses = await getAssetAddresses(deployments); - const cVaultProxy = await ethers.getContract("OETHVaultProxy"); - // Deploy Dripper Impl - const dDripper = await deployWithConfirmation("OETHDripper", [ - cVaultProxy.address, + // Deploy OETHHarvesterSimple implementation + const dHarvester = await deployWithConfirmation("OETHHarvesterSimple", [ assetAddresses.WETH, ]); - - await deployWithConfirmation("OETHDripperProxy"); - // Deploy Dripper Proxy - const cDripperProxy = await ethers.getContract("OETHDripperProxy"); + await deployWithConfirmation("OETHSimpleHarvesterProxy"); + const cHarvesterProxy = await ethers.getContract("OETHSimpleHarvesterProxy"); await withConfirmation( - cDripperProxy["initialize(address,address,bytes)"]( - dDripper.address, + cHarvesterProxy["initialize(address,address,bytes)"]( + dHarvester.address, governorAddr, [] ) ); - const cDripper = await ethers.getContractAt( - "OETHDripper", - cDripperProxy.address + const cHarvester = await ethers.getContractAt( + "OETHHarvesterSimple", + cHarvesterProxy.address ); - - const sGovernor = await ethers.provider.getSigner(governorAddr); - - // duration of 14 days await withConfirmation( - cDripper.connect(sGovernor).setDripDuration(14 * 24 * 60 * 60) + cHarvester.connect(sGovernor).setStrategistAddr(strategistAddr) ); - return cDripper; -}; - -const deployDrippers = async () => { - const ousdDripper = await deployOUSDDripper(); - const oethDripper = await deployOETHDripper(); - - return [ousdDripper, oethDripper]; + return cHarvester; }; /** @@ -990,78 +499,10 @@ const deployCompoundingStakingSSVStrategy = async () => { }; /** - * Deploy the OracleRouter and initialize it with Chainlink sources. + * Deploy the OracleRouter. + * Deprecated */ -const deployOracles = async () => { - const { deployerAddr } = await getNamedAccounts(); - // Signers - const sDeployer = await ethers.provider.getSigner(deployerAddr); - - let oracleContract = "MockOracleRouter"; - let contractName = "OracleRouter"; - let args = []; - if (isMainnet) { - oracleContract = "OracleRouter"; - } else if (isHoleskyOrFork || isHoodiOrFork) { - oracleContract = "OETHFixedOracle"; - contractName = "OETHOracleRouter"; - args = []; - } else if (isSonicOrFork) { - oracleContract = "OSonicOracleRouter"; - contractName = "OSonicOracleRouter"; - args = [addresses.zero]; - } - - await deployWithConfirmation(contractName, args, oracleContract); - if (isHoleskyOrFork || isHoodiOrFork || isSonicOrFork) { - // no need to configure any feeds since they are hardcoded to a fixed feed - // TODO: further deployments will require more intelligent separation of different - // chains / environment oracle deployments - return; - } - - const oracleRouter = await ethers.getContract("OracleRouter"); - log("Deployed OracleRouter"); - - const assetAddresses = await getAssetAddresses(deployments); - - // Register feeds - // Not needed in production - const oracleAddresses = await getOracleAddresses(deployments); - /* Mock oracle feeds report 0 for updatedAt data point. Set - * maxStaleness to 100 years from epoch to make the Oracle - * feeds valid - */ - const maxStaleness = 24 * 60 * 60 * 365 * 100; - - const oracleFeeds = [ - [assetAddresses.USDS, oracleAddresses.chainlink.USDS_USD], - [assetAddresses.USDT, oracleAddresses.chainlink.USDT_USD], - [assetAddresses.USDC, oracleAddresses.chainlink.USDC_USD], - [assetAddresses.TUSD, oracleAddresses.chainlink.TUSD_USD], - [assetAddresses.COMP, oracleAddresses.chainlink.COMP_USD], - [assetAddresses.AAVE, oracleAddresses.chainlink.AAVE_USD], - [assetAddresses.AAVE_TOKEN, oracleAddresses.chainlink.AAVE_USD], - [assetAddresses.CRV, oracleAddresses.chainlink.CRV_USD], - [assetAddresses.CVX, oracleAddresses.chainlink.CVX_USD], - [assetAddresses.RETH, oracleAddresses.chainlink.RETH_ETH], - [assetAddresses.WETH, oracleAddresses.chainlink.WETH_ETH], - [addresses.mainnet.WETH, oracleAddresses.chainlink.WETH_ETH], - [assetAddresses.stETH, oracleAddresses.chainlink.STETH_ETH], - [assetAddresses.frxETH, oracleAddresses.chainlink.FRXETH_ETH], - [ - assetAddresses.NonStandardToken, - oracleAddresses.chainlink.NonStandardToken_USD, - ], - [assetAddresses.BAL, oracleAddresses.chainlink.BAL_ETH], - ]; - - for (const [asset, oracle] of oracleFeeds) { - await withConfirmation( - oracleRouter.connect(sDeployer).setFeed(asset, oracle, maxStaleness) - ); - } -}; +const deployOracles = async () => {}; const deployOETHCore = async () => { let { governorAddr, deployerAddr } = await hre.getNamedAccounts(); @@ -1254,22 +695,6 @@ const deployCore = async () => { await deployOETHCore(); }; -// deploy curve metapool mocks -const deployCurveMetapoolMocks = async () => { - const ousd = await ethers.getContract("OUSDProxy"); - const { deployerAddr } = await hre.getNamedAccounts(); - const assetAddresses = await getAssetAddresses(deployments); - - await hre.deployments.deploy("MockCurveMetapool", { - from: deployerAddr, - args: [[ousd.address, assetAddresses.ThreePoolToken]], - }); - - const metapoolToken = await ethers.getContract("MockCurveMetapool"); - const mockBooster = await ethers.getContract("MockBooster"); - await mockBooster.setPool(metapoolLPCRVPid, metapoolToken.address); -}; - // create Uniswap V3 OUSD - USDT pool const deployUniswapV3Pool = async () => { const ousd = await ethers.getContract("OUSDProxy"); @@ -1291,135 +716,6 @@ const deployUniswapV3Pool = async () => { ); }; -const deployBuyback = async () => { - const { deployerAddr, governorAddr, strategistAddr } = - await getNamedAccounts(); - const sDeployer = await ethers.provider.getSigner(deployerAddr); - const sGovernor = await ethers.provider.getSigner(governorAddr); - - const assetAddresses = await getAssetAddresses(deployments); - const ousd = await ethers.getContract("OUSDProxy"); - const oeth = await ethers.getContract("OETHProxy"); - const cOUSDVault = await ethers.getContractAt( - "VaultAdmin", - ( - await ethers.getContract("VaultProxy") - ).address - ); - const cOETHVault = await ethers.getContractAt( - "VaultAdmin", - ( - await ethers.getContract("OETHVaultProxy") - ).address - ); - - // Deploy proxy and implementation - const dOUSDBuybackProxy = await deployWithConfirmation("BuybackProxy"); - const dOETHBuybackProxy = await deployWithConfirmation("OETHBuybackProxy"); - const ousdContractName = "OUSDBuyback"; - const oethContractName = "OETHBuyback"; - const dOUSDBuybackImpl = await deployWithConfirmation(ousdContractName, [ - ousd.address, - assetAddresses.OGN, - assetAddresses.CVX, - assetAddresses.CVXLocker, - ]); - const dOETHBuybackImpl = await deployWithConfirmation(oethContractName, [ - oeth.address, - assetAddresses.OGN, - assetAddresses.CVX, - assetAddresses.CVXLocker, - ]); - - const cOUSDBuybackProxy = await ethers.getContractAt( - "BuybackProxy", - dOUSDBuybackProxy.address - ); - - const cOETHBuybackProxy = await ethers.getContractAt( - "OETHBuybackProxy", - dOETHBuybackProxy.address - ); - - const mockSwapper = await ethers.getContract("MockSwapper"); - - // Init proxy to implementation - await withConfirmation( - cOUSDBuybackProxy.connect(sDeployer)[ - // eslint-disable-next-line no-unexpected-multiline - "initialize(address,address,bytes)" - ](dOUSDBuybackImpl.address, deployerAddr, []) - ); - await withConfirmation( - cOETHBuybackProxy.connect(sDeployer)[ - // eslint-disable-next-line no-unexpected-multiline - "initialize(address,address,bytes)" - ](dOETHBuybackImpl.address, deployerAddr, []) - ); - - const cOUSDBuyback = await ethers.getContractAt( - ousdContractName, - cOUSDBuybackProxy.address - ); - const cOETHBuyback = await ethers.getContractAt( - oethContractName, - cOETHBuybackProxy.address - ); - - // Initialize implementation contract - const initFunction = "initialize(address,address,address,address,uint256)"; - await withConfirmation( - cOUSDBuyback.connect(sDeployer)[initFunction]( - mockSwapper.address, - strategistAddr, - strategistAddr, // Treasury manager - assetAddresses.RewardsSource, - 5000 // 50% - ) - ); - await withConfirmation( - cOETHBuyback.connect(sDeployer)[initFunction]( - mockSwapper.address, - strategistAddr, - strategistAddr, // Treasury manager - assetAddresses.RewardsSource, - 5000 // 50% - ) - ); - - // Init proxy to implementation - await withConfirmation( - cOUSDBuyback.connect(sDeployer).transferGovernance(governorAddr) - ); - await withConfirmation( - cOETHBuyback.connect(sDeployer).transferGovernance(governorAddr) - ); - - await cOUSDBuyback.connect(sDeployer).safeApproveAllTokens(); - await cOETHBuyback.connect(sDeployer).safeApproveAllTokens(); - - // On Mainnet the governance transfer gets executed separately, via the - // multi-sig wallet. On other networks, this migration script can claim - // governance by the governor. - if (!isMainnet) { - await withConfirmation( - cOUSDBuyback - .connect(sGovernor) // Claim governance with governor - .claimGovernance() - ); - await withConfirmation( - cOETHBuyback - .connect(sGovernor) // Claim governance with governor - .claimGovernance() - ); - log("Claimed governance for Buyback"); - - await cOUSDVault.connect(sGovernor).setTrusteeAddress(cOUSDBuyback.address); - await cOETHVault.connect(sGovernor).setTrusteeAddress(cOETHBuyback.address); - log("Buyback set as Vault trustee"); - } -}; - const deployVaultValueChecker = async () => { const vault = await ethers.getContract("VaultProxy"); const ousd = await ethers.getContract("OUSDProxy"); @@ -1470,42 +766,6 @@ const deployWOeth = async () => { ](dWrappedOethImpl.address, governorAddr, initData); }; -const deployOETHSwapper = async () => { - const { deployerAddr } = await getNamedAccounts(); - const sDeployer = await ethers.provider.getSigner(deployerAddr); - - const assetAddresses = await getAssetAddresses(deployments); - - await deployWithConfirmation("Swapper1InchV5"); - const cSwapper = await ethers.getContract("Swapper1InchV5"); - - cSwapper - .connect(sDeployer) - .approveAssets([ - assetAddresses.RETH, - assetAddresses.stETH, - assetAddresses.WETH, - assetAddresses.frxETH, - ]); -}; - -const deployOUSDSwapper = async () => { - const { deployerAddr } = await getNamedAccounts(); - const sDeployer = await ethers.provider.getSigner(deployerAddr); - - const assetAddresses = await getAssetAddresses(deployments); - // Assumes deployOETHSwapper has already been run - const cSwapper = await ethers.getContract("Swapper1InchV5"); - - cSwapper - .connect(sDeployer) - .approveAssets([ - assetAddresses.USDS, - assetAddresses.USDC, - assetAddresses.USDT, - ]); -}; - const deployBaseAerodromeAMOStrategyImplementation = async () => { const cOETHbProxy = await ethers.getContract("OETHBaseProxy"); const cOETHbVaultProxy = await ethers.getContract("OETHBaseVaultProxy"); @@ -1529,55 +789,6 @@ const deployBaseAerodromeAMOStrategyImplementation = async () => { return await ethers.getContract("AerodromeAMOStrategy"); }; -const deployPlumeRoosterAMOStrategyImplementation = async (poolAddress) => { - return _deployPlumeRoosterAMOImplementationConfigurable( - poolAddress, - "RoosterAMOStrategy" - ); -}; - -const deployPlumeMockRoosterAMOStrategyImplementation = async (poolAddress) => { - return _deployPlumeRoosterAMOImplementationConfigurable( - poolAddress, - "MockRoosterAMOStrategy" - ); -}; - -const _deployPlumeRoosterAMOImplementationConfigurable = async ( - poolAddress, - stratContractName -) => { - const cOETHpProxy = await ethers.getContract("OETHPlumeProxy"); - const cOETHpVaultProxy = await ethers.getContract("OETHPlumeVaultProxy"); - - if (!isFork && isPlume) { - throw new Error("You cannot deploy this strategy yet"); - } - - const cMockMaverickDistributor = await ethers.getContract( - "MockMaverickDistributor" - ); - - await deployWithConfirmation(stratContractName, [ - /* Used first by the 002_rooster_amo_ deploy file - */ - [addresses.zero, cOETHpVaultProxy.address], // platformAddress, VaultAddress - addresses.plume.WETH, // weth address - cOETHpProxy.address, // OETHp address - addresses.plume.MaverickV2LiquidityManager, // liquidity manager - addresses.plume.MaverickV2PoolLens, // pool lens - addresses.plume.MaverickV2Position, // position - addresses.plume.MaverickV2Quoter, // quoter - poolAddress, // superOETHp/WPLUME pool - true, // uppperTickAtParity - // TODO: change these to actual addresses - cMockMaverickDistributor.address, // votingDistributor - cMockMaverickDistributor.address, // poolDistributor - ]); - - return await ethers.getContract(stratContractName); -}; - const getPlumeContracts = async () => { const maverickV2LiquidityManager = await ethers.getContractAt( "IMaverickV2LiquidityManager", @@ -1967,36 +1178,19 @@ module.exports = { deployCore, deployOETHCore, deployOUSDCore, - deployCurveMetapoolMocks, - deployCompoundStrategy, - deployAaveStrategy, - deployConvexStrategy, - deployConvexOUSDMetaStrategy, deployNativeStakingSSVStrategy, deployCompoundingStakingSSVStrategy, - deployDrippers, - deployOETHDripper, - deployOUSDDripper, - deployHarvesters, - deployOETHHarvester, - deployOUSDHarvester, - upgradeOETHHarvester, + deploySimpleOETHHarvester, configureVault, configureOETHVault, - configureStrategies, - deployBuyback, deployUniswapV3Pool, deployVaultValueChecker, deployWOusd, deployWOeth, - deployOETHSwapper, - deployOUSDSwapper, upgradeNativeStakingSSVStrategy, upgradeNativeStakingFeeAccumulator, upgradeCompoundingStakingSSVStrategy, deployBaseAerodromeAMOStrategyImplementation, - deployPlumeRoosterAMOStrategyImplementation, - deployPlumeMockRoosterAMOStrategyImplementation, getPlumeContracts, deploySonicSwapXAMOStrategyImplementation, deployProxyWithCreateX, diff --git a/contracts/deploy/mainnet/000_mock.js b/contracts/deploy/mainnet/000_mock.js index 166253a416..829f20f16e 100644 --- a/contracts/deploy/mainnet/000_mock.js +++ b/contracts/deploy/mainnet/000_mock.js @@ -1,7 +1,6 @@ const { parseUnits } = require("ethers").utils; const { isMainnetOrFork } = require("../../test/helpers"); const addresses = require("../../utils/addresses"); -const { threeCRVPid } = require("../../utils/constants"); const { replaceContractAt } = require("../../utils/hardhat"); const { hardhatSetBalance } = require("../../test/_fund"); @@ -10,21 +9,6 @@ const { bytecode: FACTORY_BYTECODE, } = require("@uniswap/v3-core/artifacts/contracts/UniswapV3Factory.sol/UniswapV3Factory.json"); -const { - abi: ROUTER_ABI, - bytecode: ROUTER_BYTECODE, -} = require("@uniswap/v3-periphery/artifacts/contracts/SwapRouter.sol/SwapRouter.json"); - -const { - abi: MANAGER_ABI, - bytecode: MANAGER_BYTECODE, -} = require("@uniswap/v3-periphery/artifacts/contracts/NonfungiblePositionManager.sol/NonfungiblePositionManager.json"); - -const { - abi: QUOTER_ABI, - bytecode: QUOTER_BYTECODE, -} = require("@uniswap/v3-periphery/artifacts/contracts/lens/Quoter.sol/Quoter.json"); - const deployMocks = async ({ getNamedAccounts, deployments }) => { const { deploy } = deployments; const { deployerAddr, governorAddr } = await getNamedAccounts(); @@ -37,16 +21,10 @@ const deployMocks = async ({ getNamedAccounts, deployments }) => { // Deploy mock coins (assets) const assetContracts = [ "MockUSDT", - "MockTUSD", "MockUSDC", "MockUSDS", "MockDAI", "MockNonStandardToken", - "MockOGV", - "MockAave", - "MockRETH", - "MockstETH", - "MockfrxETH", ]; for (const contract of assetContracts) { await deploy(contract, { from: deployerAddr }); @@ -57,81 +35,18 @@ const deployMocks = async ({ getNamedAccounts, deployments }) => { const mockWETH = await ethers.getContract("MockWETH"); await replaceContractAt(addresses.mainnet.WETH, mockWETH); await hardhatSetBalance(addresses.mainnet.WETH, "999999999999999"); - const weth = await ethers.getContractAt("MockWETH", addresses.mainnet.WETH); - - await deploy("MocksfrxETH", { - from: deployerAddr, - args: [(await ethers.getContract("MockfrxETH")).address], - }); await deploy("MockOGN", { from: deployerAddr, args: [parseUnits("1000000000", 18)], }); - // Mock Comptroller - await deploy("MockComptroller", { - from: deployerAddr, - }); - - // Deploy mock cTokens (Compound) - await deploy("MockCDAI", { - args: [ - (await ethers.getContract("MockDAI")).address, - (await ethers.getContract("MockComptroller")).address, - ], - contract: "MockCToken", - from: deployerAddr, - }); - - await deploy("MockCUSDS", { - args: [ - (await ethers.getContract("MockUSDS")).address, - (await ethers.getContract("MockComptroller")).address, - ], - contract: "MockCToken", - from: deployerAddr, - }); - await deploy("DepositContractUtils", { args: [], contract: "DepositContractUtils", from: deployerAddr, }); - await deploy("MockCUSDC", { - args: [ - (await ethers.getContract("MockUSDC")).address, - (await ethers.getContract("MockComptroller")).address, - ], - contract: "MockCToken", - from: deployerAddr, - }); - - await deploy("MockCUSDT", { - args: [ - (await ethers.getContract("MockUSDT")).address, - (await ethers.getContract("MockComptroller")).address, - ], - contract: "MockCToken", - from: deployerAddr, - }); - - // Mock COMP token - await deploy("MockCOMP", { - from: deployerAddr, - }); - - // Mock BAL token - await deploy("MockBAL", { - from: deployerAddr, - }); - - // Mock AURA token - await deploy("MockAura", { - from: deployerAddr, - }); - // Deploy a mock Vault with additional functions for tests await deploy("MockVault", { args: [(await ethers.getContract("MockUSDC")).address], @@ -153,222 +68,13 @@ const deployMocks = async ({ getNamedAccounts, deployments }) => { from: deployerAddr, }); - const usds = await ethers.getContract("MockUSDS"); const usdc = await ethers.getContract("MockUSDC"); - const usdt = await ethers.getContract("MockUSDT"); - const dai = await ethers.getContract("MockDAI"); - - // Deploy mock aTokens (Aave) - // MockAave is the mock lendingPool - const lendingPool = await ethers.getContract("MockAave"); - await deploy("MockADAI", { - args: [lendingPool.address, "Mock Aave DAI", "aDAI", dai.address], - contract: "MockAToken", - from: deployerAddr, - }); - lendingPool.addAToken( - (await ethers.getContract("MockADAI")).address, - dai.address - ); - - await deploy("MockAUSDC", { - args: [lendingPool.address, "Mock Aave USDC", "aUSDC", usdc.address], - contract: "MockAToken", - from: deployerAddr, - }); - lendingPool.addAToken( - (await ethers.getContract("MockAUSDC")).address, - usdc.address - ); - - await deploy("MockAUSDT", { - args: [lendingPool.address, "Mock Aave USDT", "aUSDT", usdt.address], - contract: "MockAToken", - from: deployerAddr, - }); - lendingPool.addAToken( - (await ethers.getContract("MockAUSDT")).address, - usdt.address - ); - - // Deploy mock chainlink oracle price feeds. - await deploy("MockChainlinkOracleFeedDAI", { - from: deployerAddr, - contract: "MockChainlinkOracleFeed", - args: [parseUnits("1", 8).toString(), 8], // 1 DAI = 1 USD, 8 digits decimal. - }); - await deploy("MockChainlinkOracleFeedUSDS", { - from: deployerAddr, - contract: "MockChainlinkOracleFeed", - args: [parseUnits("1", 8).toString(), 8], // 1 USDS = 1 USD, 8 digits decimal. - }); - await deploy("MockChainlinkOracleFeedUSDT", { - from: deployerAddr, - contract: "MockChainlinkOracleFeed", - args: [parseUnits("1", 8).toString(), 8], // 1 USDT = 1 USD, 8 digits decimal. - }); - await deploy("MockChainlinkOracleFeedUSDC", { - from: deployerAddr, - contract: "MockChainlinkOracleFeed", - args: [parseUnits("1", 8).toString(), 8], // 1 USDC = 1 USD, 8 digits decimal. - }); - await deploy("MockChainlinkOracleFeedTUSD", { - from: deployerAddr, - contract: "MockChainlinkOracleFeed", - args: [parseUnits("1", 8).toString(), 8], // 1 TUSD = 1 USD, 8 digits decimal. - }); - await deploy("MockChainlinkOracleFeedCOMP", { - from: deployerAddr, - contract: "MockChainlinkOracleFeed", - args: [parseUnits("1", 8).toString(), 8], // 1 COMP = 1 USD, 8 digits decimal. - }); - await deploy("MockChainlinkOracleFeedAAVE", { - from: deployerAddr, - contract: "MockChainlinkOracleFeed", - args: [parseUnits("1", 8).toString(), 8], // 1 AAVE = 1 USD, 8 digits decimal. - }); - await deploy("MockChainlinkOracleFeedCRV", { - from: deployerAddr, - contract: "MockChainlinkOracleFeed", - args: [parseUnits("1", 8).toString(), 8], // 1 CRV = 1 USD, 8 digits decimal. - }); - await deploy("MockChainlinkOracleFeedCVX", { - from: deployerAddr, - contract: "MockChainlinkOracleFeed", - args: [parseUnits("1", 8).toString(), 8], // 1 CVX = 1 USD, 8 digits decimal. - }); - await deploy("MockChainlinkOracleFeedNonStandardToken", { - from: deployerAddr, - contract: "MockChainlinkOracleFeed", - args: [parseUnits("1", 8).toString(), 8], // 1 = 1 USD, 8 digits decimal. - }); - await deploy("MockChainlinkOracleFeedETH", { - from: deployerAddr, - contract: "MockChainlinkOracleFeed", - args: [parseUnits("4000", 8).toString(), 8], // 1 ETH = 4000 USD, 8 digits decimal. - }); - await deploy("MockChainlinkOracleFeedOGNETH", { - from: deployerAddr, - contract: "MockChainlinkOracleFeed", - args: [parseUnits("0.1", 18).toString(), 18], // 10 OGN = 1 ETH, 18 digits decimal. - }); - await deploy("MockChainlinkOracleFeedRETHETH", { - from: deployerAddr, - contract: "MockChainlinkOracleFeed", - args: [parseUnits("1.2", 18).toString(), 18], // 1 RETH = 1.2 ETH , 18 digits decimal. - }); - await deploy("MockChainlinkOracleFeedstETHETH", { - from: deployerAddr, - contract: "MockChainlinkOracleFeed", - args: [parseUnits("0.998", 18).toString(), 18], // 1 stETH = 0.998 ETH , 18 digits decimal. - }); - await deploy("MockChainlinkOracleFeedfrxETHETH", { - from: deployerAddr, - contract: "MockChainlinkOracleFeed", - args: [parseUnits("1", 18).toString(), 18], // 1 frxETH = 1 ETH , 18 digits decimal. - }); - await deploy("MockChainlinkOracleFeedWETHETH", { - from: deployerAddr, - contract: "MockChainlinkOracleFeed", - args: [parseUnits("1", 18).toString(), 18], // 1 WETH = 1 ETH , 18 digits decimal. - }); - await deploy("MockChainlinkOracleFeedfrxETHETH", { - from: deployerAddr, - contract: "MockChainlinkOracleFeed", - args: [parseUnits("1", 18).toString(), 18], // 1 frxETH = 1 ETH , 18 digits decimal. - }); - await deploy("MockChainlinkOracleFeedBALETH", { - from: deployerAddr, - contract: "MockChainlinkOracleFeed", - args: [parseUnits("0.002", 18).toString(), 18], // 500 BAL = 1 ETH , 18 digits decimal. - }); // Deploy mock Uniswap router await deploy("MockUniswapRouter", { from: deployerAddr, }); - // Deploy mock Uniswap router - await deploy("MockBalancerVault", { - from: deployerAddr, - }); - - // Deploy 3pool mocks - await deploy("Mock3CRV", { - from: deployerAddr, - }); - - // Mock CRV token - await deploy("MockCRV", { - from: deployerAddr, - }); - - // Mock Curve minter for minting CRV - const mockCRV = await ethers.getContract("MockCRV"); - await deploy("MockCRVMinter", { - from: deployerAddr, - args: [mockCRV.address], - }); - - const threePoolToken = await ethers.getContract("Mock3CRV"); - - // Mock Curve gauge for depositing LP tokens from pool - await deploy("MockCurveGauge", { - from: deployerAddr, - args: [threePoolToken.address], - }); - - await deploy("MockCurvePool", { - from: deployerAddr, - args: [[usds.address, usdc.address, usdt.address], threePoolToken.address], - }); - // Mock CVX token - await deploy("MockCVX", { - from: deployerAddr, - }); - const mockCVX = await ethers.getContract("MockCVX"); - await deploy("MockCVXLocker", { - from: deployerAddr, - args: [mockCVX.address], - }); - - await deploy("MockBooster", { - from: deployerAddr, - args: [mockCVX.address, mockCRV.address, mockCVX.address], - }); - const mockBooster = await ethers.getContract("MockBooster"); - await mockBooster.setPool(threeCRVPid, threePoolToken.address); - - await deploy("MockRewardPool", { - from: deployerAddr, - args: [ - threeCRVPid, - threePoolToken.address, - mockCRV.address, - mockCVX.address, - mockCRV.address, - ], - }); - - await deploy("MockAAVEToken", { - from: deployerAddr, - args: [], - }); - - const mockAaveToken = await ethers.getContract("MockAAVEToken"); - - await deploy("MockStkAave", { - from: deployerAddr, - args: [mockAaveToken.address], - }); - - const mockStkAave = await ethers.getContract("MockStkAave"); - - await deploy("MockAaveIncentivesController", { - from: deployerAddr, - args: [mockStkAave.address], - }); - await deploy("MockNonRebasing", { from: deployerAddr, }); @@ -378,7 +84,7 @@ const deployMocks = async ({ getNamedAccounts, deployments }) => { contract: "MockNonRebasing", }); - const factory = await deploy("MockUniswapV3Factory", { + await deploy("MockUniswapV3Factory", { from: deployerAddr, contract: { abi: FACTORY_ABI, @@ -386,63 +92,6 @@ const deployMocks = async ({ getNamedAccounts, deployments }) => { }, }); - await deploy("MockUniswapV3Router", { - from: deployerAddr, - contract: { - abi: ROUTER_ABI, - bytecode: ROUTER_BYTECODE, - }, - args: [factory.address, weth.address], - }); - - await deploy("MockUniswapV3NonfungiblePositionManager", { - from: deployerAddr, - contract: { - abi: MANAGER_ABI, - bytecode: MANAGER_BYTECODE, - }, - /* - * The last constructor argument should be of type "NonfungibleTokenPositionDescriptor", but - * the bytecode of that seems to be corrupt - hardhat doesn't want to deploy it. Shouldn't be a - * problem as long as we don't call the `tokenUri` function: - * https://github.com/Uniswap/uniswap-v3-periphery/blob/79c708f357df69f7b3a494467e0f501810a11146/contracts/NonfungiblePositionManager.sol#L189-L192 - * - */ - args: [factory.address, weth.address, factory.address], - }); - - await deploy("MockUniswapV3Quoter", { - from: deployerAddr, - contract: { - abi: QUOTER_ABI, - bytecode: QUOTER_BYTECODE, - }, - args: [factory.address, weth.address], - }); - - const frxETH = await ethers.getContract("MockfrxETH"); - const sfrxETH = await ethers.getContract("MocksfrxETH"); - await deploy("MockFrxETHMinter", { - from: deployerAddr, - args: [frxETH.address, sfrxETH.address], - }); - // Replace frxETHMinter - await replaceContractAt( - addresses.mainnet.FraxETHMinter, - await ethers.getContract("MockFrxETHMinter") - ); - - await deploy("MockSwapper", { - from: deployerAddr, - }); - await deploy("Mock1InchSwapRouter", { - from: deployerAddr, - }); - - await deploy("MockOracleWeightedPool", { - from: deployerAddr, - }); - await deploy("MockBeaconProofs", { from: deployerAddr }); await deploy("EnhancedBeaconProofs", { from: deployerAddr }); await deploy("MockBeaconRoots", { from: deployerAddr }); diff --git a/contracts/deploy/mainnet/001_core.js b/contracts/deploy/mainnet/001_core.js index 7d6dc08841..d6c811f63d 100644 --- a/contracts/deploy/mainnet/001_core.js +++ b/contracts/deploy/mainnet/001_core.js @@ -3,24 +3,15 @@ const { isFork } = require("../../test/helpers.js"); const { deployOracles, deployCore, - deployCurveMetapoolMocks, - deployCompoundStrategy, - deployAaveStrategy, - deployConvexStrategy, deployNativeStakingSSVStrategy, deployCompoundingStakingSSVStrategy, - deployDrippers, - deployHarvesters, + deploySimpleOETHHarvester, configureVault, configureOETHVault, - configureStrategies, - deployBuyback, deployUniswapV3Pool, deployVaultValueChecker, deployWOusd, deployWOeth, - deployOETHSwapper, - deployOUSDSwapper, deployCrossChainUnitTestStrategy, } = require("../deployActions"); @@ -30,27 +21,15 @@ const main = async () => { await deployOracles(); await deployCore(); - await deployCurveMetapoolMocks(); - await deployCompoundStrategy(); - await deployAaveStrategy(); - await deployConvexStrategy(); await deployNativeStakingSSVStrategy(); await deployCompoundingStakingSSVStrategy(); - const [ousdDripper, oethDripper] = await deployDrippers(); - const [harvesterProxy, oethHarvesterProxy] = await deployHarvesters( - ousdDripper, - oethDripper - ); + await deploySimpleOETHHarvester(); await configureVault(); await configureOETHVault(); - await configureStrategies(harvesterProxy, oethHarvesterProxy); - await deployBuyback(); await deployUniswapV3Pool(); await deployVaultValueChecker(); await deployWOusd(); await deployWOeth(); - await deployOETHSwapper(); - await deployOUSDSwapper(); await deployCrossChainUnitTestStrategy(usdc.address); console.log("001_core deploy done."); return true; diff --git a/contracts/deploy/mainnet/102_2nd_native_ssv_staking.js b/contracts/deploy/mainnet/102_2nd_native_ssv_staking.js deleted file mode 100644 index beabd164e8..0000000000 --- a/contracts/deploy/mainnet/102_2nd_native_ssv_staking.js +++ /dev/null @@ -1,256 +0,0 @@ -const { deploymentWithGovernanceProposal } = require("../../utils/deploy"); -const addresses = require("../../utils/addresses"); -const { isFork } = require("../../test/helpers.js"); -const { impersonateAccount } = require("../../utils/signers"); - -module.exports = deploymentWithGovernanceProposal( - { - deployName: "102_2nd_native_ssv_staking", - forceDeploy: false, - //forceSkip: true, - reduceQueueTime: true, - deployerIsProposer: false, - proposalId: - "28472247901961988076478290503619170282582190552945502651596812949787910054542", - }, - async ({ deployWithConfirmation, ethers, getTxOpts, withConfirmation }) => { - const { deployerAddr } = await getNamedAccounts(); - const sDeployer = await ethers.provider.getSigner(deployerAddr); - console.log(`Using deployer account: ${deployerAddr}`); - - // Current contracts - const cVaultProxy = await ethers.getContract("OETHVaultProxy"); - const cVaultAdmin = await ethers.getContractAt( - "OETHVaultAdmin", - cVaultProxy.address - ); - - const cHarvesterProxy = await ethers.getContract("OETHHarvesterProxy"); - const cHarvester = await ethers.getContractAt( - "OETHHarvester", - cHarvesterProxy.address - ); - - // Deployer Actions - // ---------------- - - // 1. Fetch the strategy proxy deployed by Defender Relayer - const cNativeStakingStrategy2Proxy = await ethers.getContract( - "NativeStakingSSVStrategy2Proxy" - ); - console.log( - `Native Staking Strategy 2 Proxy: ${cNativeStakingStrategy2Proxy.address}` - ); - - // 2. Deploy the new FeeAccumulator proxy - console.log(`About to deploy FeeAccumulator proxy`); - const dFeeAccumulator2Proxy = await deployWithConfirmation( - "NativeStakingFeeAccumulator2Proxy" - ); - const cFeeAccumulator2Proxy = await ethers.getContractAt( - "NativeStakingFeeAccumulator2Proxy", - dFeeAccumulator2Proxy.address - ); - - // 3. Deploy the new FeeAccumulator implementation - console.log(`About to deploy FeeAccumulator implementation`); - const dFeeAccumulator2 = await deployWithConfirmation("FeeAccumulator", [ - cNativeStakingStrategy2Proxy.address, // _collector - ]); - const cFeeAccumulator2 = await ethers.getContractAt( - "FeeAccumulator", - dFeeAccumulator2.address - ); - - // 4. Init the Second FeeAccumulator proxy to point at the implementation, set the governor - console.log(`About to initialize FeeAccumulator`); - const proxyInitFunction = "initialize(address,address,bytes)"; - await withConfirmation( - cFeeAccumulator2Proxy.connect(sDeployer)[proxyInitFunction]( - cFeeAccumulator2.address, // implementation address - addresses.mainnet.Timelock, // governance - "0x", // do not call any initialize functions - await getTxOpts() - ) - ); - - // 5. Deploy the new Native Staking Strategy implementation - console.log(`About to deploy NativeStakingSSVStrategy implementation`); - const dNativeStakingStrategy2Impl = await deployWithConfirmation( - "NativeStakingSSVStrategy", - [ - [addresses.zero, cVaultProxy.address], //_baseConfig - addresses.mainnet.WETH, // wethAddress - addresses.mainnet.SSV, // ssvToken - addresses.mainnet.SSVNetwork, // ssvNetwork - 500, // maxValidators - dFeeAccumulator2Proxy.address, // feeAccumulator - addresses.mainnet.beaconChainDepositContract, // beacon chain deposit contract - ] - ); - const cNativeStakingStrategy2Impl = await ethers.getContractAt( - "NativeStakingSSVStrategy", - dNativeStakingStrategy2Impl.address - ); - - const cNativeStakingStrategy2 = await ethers.getContractAt( - "NativeStakingSSVStrategy", - cNativeStakingStrategy2Proxy.address - ); - - // 6. Initialize Native Staking Proxy with new implementation and strategy initialization - console.log(`About to initialize NativeStakingSSVStrategy`); - const initData = cNativeStakingStrategy2Impl.interface.encodeFunctionData( - "initialize(address[],address[],address[])", - [ - [addresses.mainnet.WETH], // reward token addresses - /* no need to specify WETH as an asset, since we have that overriden in the "supportsAsset" - * function on the strategy - */ - [], // asset token addresses - [], // platform tokens addresses - ] - ); - - const proxyGovernor = await cNativeStakingStrategy2Proxy.governor(); - if (isFork && proxyGovernor != deployerAddr) { - const relayerSigner = await impersonateAccount( - addresses.mainnet.validatorRegistrator - ); - await withConfirmation( - cNativeStakingStrategy2Proxy - .connect(relayerSigner) - .transferGovernance(deployerAddr, await getTxOpts()) - ); - } else { - /* Before kicking off the deploy script make sure the Defender relayer transfers the governance - * of the proxy to the deployer account that shall be deploying this script so it will be able - * to initialize the proxy contract - * - * Run the following to make it happen, and comment this error block out: - * yarn run hardhat transferGovernanceNativeStakingProxy --address 0xdeployerAddress --network mainnet - */ - if (proxyGovernor != deployerAddr) { - throw new Error( - `Native Staking Strategy proxy's governor: ${proxyGovernor} does not match current deployer ${deployerAddr}` - ); - } - } - - // 7. Transfer governance of the Native Staking Strategy 2 proxy to the deployer - console.log(`About to claimGovernance of NativeStakingStrategyProxy`); - await withConfirmation( - cNativeStakingStrategy2Proxy - .connect(sDeployer) - .claimGovernance(await getTxOpts()) - ); - - // 8. Init the proxy to point at the implementation, set the governor, and call initialize - console.log(`About to initialize of NativeStakingStrategy2`); - await withConfirmation( - cNativeStakingStrategy2Proxy.connect(sDeployer)[proxyInitFunction]( - cNativeStakingStrategy2Impl.address, // implementation address - addresses.mainnet.Timelock, // governance - initData, // data for call to the initialize function on the strategy - await getTxOpts() - ) - ); - - // 9. Safe approve SSV token spending - console.log(`About to safeApproveAllTokens of NativeStakingStrategy2`); - await cNativeStakingStrategy2.connect(sDeployer).safeApproveAllTokens(); - - // 10. Fetch the first Native Staking Strategy proxy - const cNativeStakingStrategyProxy = await ethers.getContract( - "NativeStakingSSVStrategyProxy" - ); - console.log( - `cNativeStakingStrategyProxy ${cNativeStakingStrategyProxy.address}` - ); - - // 11. Fetch the first Fee Accumulator proxy - const cFeeAccumulatorProxy = await ethers.getContract( - "NativeStakingFeeAccumulatorProxy" - ); - console.log(`cFeeAccumulatorProxy ${cFeeAccumulatorProxy.address}`); - - // 12. Deploy new implementation for the first Native Staking Strategy - console.log(`About to deploy NativeStakingSSVStrategy implementation`); - const dNativeStakingStrategyImpl = await deployWithConfirmation( - "NativeStakingSSVStrategy", - [ - [addresses.zero, cVaultProxy.address], //_baseConfig - addresses.mainnet.WETH, // wethAddress - addresses.mainnet.SSV, // ssvToken - addresses.mainnet.SSVNetwork, // ssvNetwork - 500, // maxValidators - cFeeAccumulatorProxy.address, // feeAccumulator - addresses.mainnet.beaconChainDepositContract, // beacon chain deposit contract - ] - ); - - // Governance Actions - // ---------------- - return { - name: `Deploy a second OETH Native Staking Strategy. - -Upgrade the first OETH Native Staking Strategy`, - actions: [ - // 1. Add new strategy to vault - { - contract: cVaultAdmin, - signature: "approveStrategy(address)", - args: [cNativeStakingStrategy2Proxy.address], - }, - // 2. configure Harvester to support the strategy - { - contract: cHarvester, - signature: "setSupportedStrategy(address,bool)", - args: [cNativeStakingStrategy2Proxy.address, true], - }, - // 3. set harvester to the strategy - { - contract: cNativeStakingStrategy2, - signature: "setHarvesterAddress(address)", - args: [cHarvesterProxy.address], - }, - // 4. configure the fuse interval - { - contract: cNativeStakingStrategy2, - signature: "setFuseInterval(uint256,uint256)", - args: [ - ethers.utils.parseEther("21.6"), - ethers.utils.parseEther("25.6"), - ], - }, - // 5. set validator registrator to the Defender Relayer - { - contract: cNativeStakingStrategy2, - signature: "setRegistrator(address)", - // The Defender Relayer - args: [addresses.mainnet.validatorRegistrator], - }, - // 6. set staking threshold - { - contract: cNativeStakingStrategy2, - signature: "setStakeETHThreshold(uint256)", - // 16 validators before the 5/8 multisig has to call resetStakeETHTally - args: [ethers.utils.parseEther("512")], // 16 * 32ETH - }, - // 7. set staking monitor - { - contract: cNativeStakingStrategy2, - signature: "setStakingMonitor(address)", - // The 5/8 multisig - args: [addresses.mainnet.Guardian], - }, - // 8. Upgrade the first Native Staking Strategy - { - contract: cNativeStakingStrategyProxy, - signature: "upgradeTo(address)", - args: [dNativeStakingStrategyImpl.address], - }, - ], - }; - } -); diff --git a/contracts/deploy/mainnet/103_oeth_withdraw_queue.js b/contracts/deploy/mainnet/103_oeth_withdraw_queue.js deleted file mode 100644 index 980ac470e5..0000000000 --- a/contracts/deploy/mainnet/103_oeth_withdraw_queue.js +++ /dev/null @@ -1,142 +0,0 @@ -const { formatUnits, parseEther } = require("ethers/lib/utils"); - -const addresses = require("../../utils/addresses"); -const { deploymentWithGovernanceProposal } = require("../../utils/deploy"); - -module.exports = deploymentWithGovernanceProposal( - { - deployName: "103_oeth_withdraw_queue", - forceDeploy: false, - //forceSkip: true, - reduceQueueTime: true, - deployerIsProposer: false, - proposalId: - "4952583864870422832882752992396486341874887833414208719999363086677469346971", - }, - async ({ deployWithConfirmation }) => { - // Deployer Actions - // ---------------- - - // 1. Deploy new OETH Vault Core and Admin implementations - // Need to override the storage safety check as we are repacking the - // internal assets mapping to just use 1 storage slot - const dVaultCore = await deployWithConfirmation( - "OETHVaultCore", - [addresses.mainnet.WETH], - null, - true, - {}, // libraries - 3800000, // gasLimit - false // useFeeData - ); - const dVaultAdmin = await deployWithConfirmation( - "OETHVaultAdmin", - [addresses.mainnet.WETH], - null, - true, - {}, // libraries - 3200000, // gasLimit - false // useFeeData - ); - - // 2. Connect to the OETH Vault as its governor via the proxy - const cVaultProxy = await ethers.getContract("OETHVaultProxy"); - const cVault = await ethers.getContractAt("IVault", cVaultProxy.address); - const cDripperProxy = await ethers.getContract("OETHDripperProxy"); - - const cLidoWithdrawStrategyProxy = await ethers.getContract( - "LidoWithdrawalStrategyProxy" - ); - const cOETHMorphoAaveStrategyProxy = await ethers.getContract( - "OETHMorphoAaveStrategyProxy" - ); - - const stETH = await ethers.getContractAt("IERC20", addresses.mainnet.stETH); - const stEthInVault = await stETH.balanceOf(cVault.address); - console.log( - `There is ${formatUnits(stEthInVault)} stETH in the OETH Vault` - ); - - const rETH = await ethers.getContractAt("IERC20", addresses.mainnet.rETH); - const rEthInVault = await rETH.balanceOf(cVault.address); - console.log(`There is ${formatUnits(rEthInVault)} rETH in the OETH Vault`); - - const cNativeStakingStrategy2Proxy = await ethers.getContract( - "NativeStakingSSVStrategy2Proxy" - ); - - // Governance Actions - // ---------------- - return { - name: "Upgrade OETH Vault to support queued withdrawals", - actions: [ - // 1. Remove the Lido Withdraw Strategy - { - contract: cVault, - signature: "removeStrategy(address)", - args: [cLidoWithdrawStrategyProxy.address], - }, - // 2. Remove the Morpho Strategy - { - contract: cVault, - signature: "removeStrategy(address)", - args: [cOETHMorphoAaveStrategyProxy.address], - }, - // 3. Remove stETH from the OETH Vault - { - contract: cVault, - signature: "removeAsset(address)", - args: [stETH.address], - }, - // 4. Remove rETH from the OETH Vault - { - contract: cVault, - signature: "removeAsset(address)", - args: [rETH.address], - }, - // 5. Cache WETH asset index now stETH has been removed - // the WETH index should go from 1 to 0 - { - contract: cVault, - signature: "cacheWETHAssetIndex()", - args: [], - }, - // 6. Upgrade the OETH Vault proxy to the new core vault implementation - { - contract: cVaultProxy, - signature: "upgradeTo(address)", - args: [dVaultCore.address], - }, - // 7. set OETH Vault proxy to the new admin vault implementation - { - contract: cVault, - signature: "setAdminImpl(address)", - args: [dVaultAdmin.address], - }, - // 8. Set the Dripper contract - { - contract: cVault, - signature: "setDripper(address)", - args: [cDripperProxy.address], - }, - // 9. Set the second Native Staking Strategy as the default for WETH - { - contract: cVault, - signature: "setAssetDefaultStrategy(address,address)", - args: [addresses.mainnet.WETH, cNativeStakingStrategy2Proxy.address], - }, - // 10. Set Vault buffer to 0.2% - { - contract: cVault, - signature: "setVaultBuffer(uint256)", - args: [parseEther("0.002")], - }, - // 11. Allocate WETH to the second Native Staking Strategy - { - contract: cVault, - signature: "allocate()", - }, - ], - }; - } -); diff --git a/contracts/deploy/mainnet/104_upgrade_staking_strategies.js b/contracts/deploy/mainnet/104_upgrade_staking_strategies.js deleted file mode 100644 index f2335b4ba1..0000000000 --- a/contracts/deploy/mainnet/104_upgrade_staking_strategies.js +++ /dev/null @@ -1,125 +0,0 @@ -const { deploymentWithGovernanceProposal } = require("../../utils/deploy.js"); -const addresses = require("../../utils/addresses.js"); - -module.exports = deploymentWithGovernanceProposal( - { - deployName: "104_upgrade_staking_strategies", - forceDeploy: false, - //forceSkip: true, - reduceQueueTime: true, - deployerIsProposer: false, - proposalId: - "71647907232135467087600722567288045171884251235529308950820063762029046155177", - }, - async ({ deployWithConfirmation, ethers }) => { - // Current contracts - const cVaultProxy = await ethers.getContract("OETHVaultProxy"); - - // Deployer Actions - // ---------------- - - // 1. Fetch the first strategy proxy deployed by Defender Relayer - const cNativeStakingStrategyProxy = await ethers.getContract( - "NativeStakingSSVStrategyProxy" - ); - const cNativeStakingStrategy = await ethers.getContractAt( - "NativeStakingSSVStrategy", - cNativeStakingStrategyProxy.address - ); - console.log( - `cNativeStakingStrategyProxy ${cNativeStakingStrategyProxy.address}` - ); - - // 2. Fetch the first Fee Accumulator proxy - const cFeeAccumulatorProxy = await ethers.getContract( - "NativeStakingFeeAccumulatorProxy" - ); - console.log(`cFeeAccumulatorProxy ${cFeeAccumulatorProxy.address}`); - - // 3. Deploy new implementation for the first Native Staking Strategy - console.log( - `About to deploy the first NativeStakingSSVStrategy implementation` - ); - const dNativeStakingStrategyImpl = await deployWithConfirmation( - "NativeStakingSSVStrategy", - [ - [addresses.zero, cVaultProxy.address], //_baseConfig - addresses.mainnet.WETH, // wethAddress - addresses.mainnet.SSV, // ssvToken - addresses.mainnet.SSVNetwork, // ssvNetwork - 500, // maxValidators - cFeeAccumulatorProxy.address, // feeAccumulator - addresses.mainnet.beaconChainDepositContract, // beacon chain deposit contract - ] - ); - - // 4. Fetch the second strategy proxy deployed by Defender Relayer - const cNativeStakingStrategy2Proxy = await ethers.getContract( - "NativeStakingSSVStrategy2Proxy" - ); - const cNativeStakingStrategy2 = await ethers.getContractAt( - "NativeStakingSSVStrategy", - cNativeStakingStrategy2Proxy.address - ); - console.log( - `Native Staking Strategy 2 Proxy: ${cNativeStakingStrategy2Proxy.address}` - ); - - // 5. Fetch the second Fee Accumulator proxy - const cFeeAccumulator2Proxy = await ethers.getContract( - "NativeStakingFeeAccumulator2Proxy" - ); - console.log(`cFeeAccumulator2Proxy ${cFeeAccumulator2Proxy.address}`); - - // 3. Deploy new implementation for the second Native Staking Strategy - console.log( - `About to deploy the second NativeStakingSSVStrategy implementation` - ); - const dNativeStakingStrategy2Impl = await deployWithConfirmation( - "NativeStakingSSVStrategy", - [ - [addresses.zero, cVaultProxy.address], //_baseConfig - addresses.mainnet.WETH, // wethAddress - addresses.mainnet.SSV, // ssvToken - addresses.mainnet.SSVNetwork, // ssvNetwork - 500, // maxValidators - cFeeAccumulator2Proxy.address, // feeAccumulator - addresses.mainnet.beaconChainDepositContract, // beacon chain deposit contract - ] - ); - - // Governance Actions - // ---------------- - return { - name: `Upgrade both OETH Native Staking Strategies. - -Set the FeeAccumulator contracts to receive MEV rewards from the SSV validators`, - actions: [ - // 1. Upgrade the first Native Staking Strategy - { - contract: cNativeStakingStrategyProxy, - signature: "upgradeTo(address)", - args: [dNativeStakingStrategyImpl.address], - }, - // 2. Set the first FeeAccumulator to receive MEV rewards - { - contract: cNativeStakingStrategy, - signature: "setFeeRecipient()", - args: [], - }, - // 3. Upgrade the second Native Staking Strategy - { - contract: cNativeStakingStrategy2Proxy, - signature: "upgradeTo(address)", - args: [dNativeStakingStrategy2Impl.address], - }, - // 4. Set the second FeeAccumulator to receive MEV rewards - { - contract: cNativeStakingStrategy2, - signature: "setFeeRecipient()", - args: [], - }, - ], - }; - } -); diff --git a/contracts/deploy/mainnet/105_ousd_remove_flux_strat.js b/contracts/deploy/mainnet/105_ousd_remove_flux_strat.js deleted file mode 100644 index 98a9b7712b..0000000000 --- a/contracts/deploy/mainnet/105_ousd_remove_flux_strat.js +++ /dev/null @@ -1,36 +0,0 @@ -const { deploymentWithGovernanceProposal } = require("../../utils/deploy"); - -module.exports = deploymentWithGovernanceProposal( - { - deployName: "105_ousd_remove_flux_strat", - forceDeploy: false, - // forceSkip: true, - reduceQueueTime: false, - deployerIsProposer: false, - proposalId: - "34347268131529952700500536280019071045711956672151609598093869968201379596367", - }, - async () => { - // Current OUSD Vault contracts - const cVaultProxy = await ethers.getContract("VaultProxy"); - const cVaultAdmin = await ethers.getContractAt( - "VaultAdmin", - cVaultProxy.address - ); - - const cFluxStrategy = await ethers.getContract("FluxStrategyProxy"); - - // Governance Actions - // ---------------- - return { - name: "Remove Flux strategy from the OUSD Vault", - actions: [ - { - contract: cVaultAdmin, - signature: "removeStrategy(address)", - args: [cFluxStrategy.address], - }, - ], - }; - } -); diff --git a/contracts/deploy/mainnet/106_ousd_metamorpho_usdc.js b/contracts/deploy/mainnet/106_ousd_metamorpho_usdc.js deleted file mode 100644 index 0cdbc1daff..0000000000 --- a/contracts/deploy/mainnet/106_ousd_metamorpho_usdc.js +++ /dev/null @@ -1,98 +0,0 @@ -const addresses = require("../../utils/addresses"); -const { deploymentWithGovernanceProposal } = require("../../utils/deploy"); - -module.exports = deploymentWithGovernanceProposal( - { - deployName: "106_ousd_metamorpho_usdc", - forceDeploy: false, - // forceSkip: true, - // reduceQueueTime: true, - deployerIsProposer: false, - proposalId: - "18625164866922300754601338202643973145672922743793130890170553774294234651247", - }, - async ({ deployWithConfirmation, getTxOpts, withConfirmation }) => { - // Current OUSD Vault contracts - const cVaultProxy = await ethers.getContract("VaultProxy"); - const cVaultAdmin = await ethers.getContractAt( - "VaultAdmin", - cVaultProxy.address - ); - const cHarvesterProxy = await ethers.getContract("HarvesterProxy"); - const cHarvester = await ethers.getContractAt( - "Harvester", - cHarvesterProxy.address - ); - - // Deployer Actions - // ---------------- - const { deployerAddr } = await getNamedAccounts(); - const sDeployer = await ethers.provider.getSigner(deployerAddr); - - // 1. Deploy new MetaMorpho Strategy proxy - const dMetaMorphoProxy = await deployWithConfirmation( - "MetaMorphoStrategyProxy" - ); - const cMetaMorphoProxy = await ethers.getContract( - "MetaMorphoStrategyProxy" - ); - - // 2. Deploy new Generalized4626Strategy contract as there has been a number of gas optimizations since it was first deployed - const dMetaMorphoStrategyImpl = await deployWithConfirmation( - "Generalized4626Strategy", - [ - [addresses.mainnet.MorphoSteakhouseUSDCVault, cVaultProxy.address], - addresses.mainnet.USDC, - ], - undefined, - true // storage slots have changed since FRAX strategy deployment so force a new deployment - ); - const cMetaMorpho = await ethers.getContractAt( - "Generalized4626Strategy", - dMetaMorphoProxy.address - ); - - // 3. Construct initialize call data to initialize and configure the new strategy - const initData = cMetaMorpho.interface.encodeFunctionData( - "initialize()", - [] - ); - - // 4. Init the proxy to point at the implementation, set the governor, and call initialize - const initFunction = "initialize(address,address,bytes)"; - await withConfirmation( - cMetaMorphoProxy.connect(sDeployer)[initFunction]( - dMetaMorphoStrategyImpl.address, - addresses.mainnet.Timelock, // governor - initData, // data for delegate call to the initialize function on the strategy - await getTxOpts() - ) - ); - - // Governance Actions - // ---------------- - return { - name: "Add MetaMorpho Strategy to the OUSD Vault", - actions: [ - { - // Add the new strategy to the vault - contract: cVaultAdmin, - signature: "approveStrategy(address)", - args: [cMetaMorpho.address], - }, - { - // Add the new strategy to the Harvester - contract: cHarvester, - signature: "setSupportedStrategy(address,bool)", - args: [cMetaMorpho.address, true], - }, - { - // Set the Harvester in the new strategy - contract: cMetaMorpho, - signature: "setHarvesterAddress(address)", - args: [cHarvesterProxy.address], - }, - ], - }; - } -); diff --git a/contracts/deploy/mainnet/107_arm_buyback.js b/contracts/deploy/mainnet/107_arm_buyback.js deleted file mode 100644 index f58c4ada24..0000000000 --- a/contracts/deploy/mainnet/107_arm_buyback.js +++ /dev/null @@ -1,63 +0,0 @@ -const addresses = require("../../utils/addresses"); -const { deploymentWithGovernanceProposal } = require("../../utils/deploy"); - -module.exports = deploymentWithGovernanceProposal( - { - deployName: "107_arm_buyback", - // forceSkip: true, - // onlyOnFork: true, // this is only executed in forked environment - deployerIsProposer: false, // just to solve the issue of later active proposals failing - proposalId: "", - }, - async ({ deployWithConfirmation, getTxOpts, withConfirmation }) => { - const { strategistAddr } = await getNamedAccounts(); - - const cSwapper = await ethers.getContract("Swapper1InchV5"); - - // Deployer Actions - // ---------------- - const { deployerAddr } = await getNamedAccounts(); - const sDeployer = await ethers.provider.getSigner(deployerAddr); - - // 1. Deploy new ARM Buyback proxy - const dARMBuybackProxy = await deployWithConfirmation("ARMBuybackProxy"); - const cARMBuybackProxy = await ethers.getContract("ARMBuybackProxy"); - - // 2. Deploy new ARM Buyback implementation - const dARMBuybackImpl = await deployWithConfirmation("ARMBuyback", [ - addresses.mainnet.WETH, - addresses.mainnet.OGN, - addresses.mainnet.CVX, - addresses.mainnet.CVXLocker, - ]); - const cARMBuyback = await ethers.getContractAt( - "ARMBuyback", - dARMBuybackProxy.address - ); - - // 3. Construct initialize call data to initialize and configure the new buyback - const initData = cARMBuyback.interface.encodeFunctionData( - "initialize(address,address,address,address,uint256)", - [ - cSwapper.address, // Swapper1InchV5 - strategistAddr, // MS 2 - strategistAddr, // MS 2 - addresses.mainnet.OGNRewardsSource, // FixedRateRewardsSourceProxy - 0, - ] - ); - - // 4. Init the proxy to point at the implementation and call initialize - const initFunction = "initialize(address,address,bytes)"; - await withConfirmation( - cARMBuybackProxy.connect(sDeployer)[initFunction]( - dARMBuybackImpl.address, - addresses.mainnet.Timelock, // governor - initData, // data for delegate call to the initialize function on the strategy - await getTxOpts() - ) - ); - - return {}; - } -); diff --git a/contracts/deploy/mainnet/108_vault_upgrade.js b/contracts/deploy/mainnet/108_vault_upgrade.js deleted file mode 100644 index 582acca2a0..0000000000 --- a/contracts/deploy/mainnet/108_vault_upgrade.js +++ /dev/null @@ -1,66 +0,0 @@ -const addresses = require("../../utils/addresses"); -const { deploymentWithGovernanceProposal } = require("../../utils/deploy"); - -module.exports = deploymentWithGovernanceProposal( - { - deployName: "108_vault_upgrade", - forceDeploy: false, - //forceSkip: true, - reduceQueueTime: true, - deployerIsProposer: false, - proposalId: - "65756708910242452493001228142964504430010766487241550114316648937729891405136", - }, - async ({ deployWithConfirmation }) => { - // Deployer Actions - // ---------------- - - // 1. Deploy new OETH Vault Core and Admin implementations - const dVaultCore = await deployWithConfirmation("OETHVaultCore", [ - addresses.mainnet.WETH, - ]); - const dVaultAdmin = await deployWithConfirmation("OETHVaultAdmin", [ - addresses.mainnet.WETH, - ]); - - // 2. Connect to the OETH Vault as its governor via the proxy - const cVaultProxy = await ethers.getContract("OETHVaultProxy"); - const cVault = await ethers.getContractAt("IVault", cVaultProxy.address); - - // 3. Deploy new OETH implementation without storage slot checks - const dOETH = await deployWithConfirmation("OETH", [], "OETH", true); - const cOETHProxy = await ethers.getContract("OETHProxy"); - - // Governance Actions - // ---------------- - return { - name: "Upgrade OETH Vault", - actions: [ - // 1. Upgrade the OETH Vault proxy to the new core vault implementation - { - contract: cVaultProxy, - signature: "upgradeTo(address)", - args: [dVaultCore.address], - }, - // 2. Set OETH Vault proxy to the new admin vault implementation - { - contract: cVault, - signature: "setAdminImpl(address)", - args: [dVaultAdmin.address], - }, - // 3. Set async claim delay to 10 minutes - { - contract: cVault, - signature: "setWithdrawalClaimDelay(uint256)", - args: [10 * 60], // 10 mins - }, - // 4. Upgrade the OETH proxy to the new implementation - { - contract: cOETHProxy, - signature: "upgradeTo(address)", - args: [dOETH.address], - }, - ], - }; - } -); diff --git a/contracts/deploy/mainnet/109_3rd_native_ssv_staking.js b/contracts/deploy/mainnet/109_3rd_native_ssv_staking.js deleted file mode 100644 index 24a22957e0..0000000000 --- a/contracts/deploy/mainnet/109_3rd_native_ssv_staking.js +++ /dev/null @@ -1,213 +0,0 @@ -const { deploymentWithGovernanceProposal } = require("../../utils/deploy"); -const addresses = require("../../utils/addresses"); -const { isFork } = require("../../test/helpers.js"); -const { impersonateAccount } = require("../../utils/signers"); - -module.exports = deploymentWithGovernanceProposal( - { - deployName: "109_3rd_native_ssv_staking", - forceDeploy: false, - //forceSkip: true, - reduceQueueTime: true, - deployerIsProposer: false, - proposalId: - "105762082509768776202240604338417692927766202901146189706379006167687855789299", - }, - async ({ deployWithConfirmation, ethers, getTxOpts, withConfirmation }) => { - const { deployerAddr } = await getNamedAccounts(); - const sDeployer = await ethers.provider.getSigner(deployerAddr); - console.log(`Using deployer account: ${deployerAddr}`); - - // Current contracts - const cVaultProxy = await ethers.getContract("OETHVaultProxy"); - const cVaultAdmin = await ethers.getContractAt( - "OETHVaultAdmin", - cVaultProxy.address - ); - - const cHarvesterProxy = await ethers.getContract("OETHHarvesterProxy"); - const cHarvester = await ethers.getContractAt( - "OETHHarvester", - cHarvesterProxy.address - ); - - // Deployer Actions - // ---------------- - - // 1. Fetch the strategy proxy deployed by Defender Relayer - const cNativeStakingStrategyProxy = await ethers.getContract( - "NativeStakingSSVStrategy3Proxy" - ); - console.log( - `Native Staking Strategy 3 Proxy: ${cNativeStakingStrategyProxy.address}` - ); - - // 2. Deploy the new FeeAccumulator proxy - console.log(`About to deploy FeeAccumulator proxy`); - const dFeeAccumulatorProxy = await deployWithConfirmation( - "NativeStakingFeeAccumulator3Proxy" - ); - const cFeeAccumulatorProxy = await ethers.getContractAt( - "NativeStakingFeeAccumulator3Proxy", - dFeeAccumulatorProxy.address - ); - - // 3. Deploy the new FeeAccumulator implementation - console.log(`About to deploy FeeAccumulator implementation`); - const dFeeAccumulator = await deployWithConfirmation("FeeAccumulator", [ - cNativeStakingStrategyProxy.address, // _collector - ]); - const cFeeAccumulator = await ethers.getContractAt( - "FeeAccumulator", - dFeeAccumulator.address - ); - - // 4. Init the third FeeAccumulator proxy to point at the implementation, set the governor - console.log(`About to initialize FeeAccumulator`); - const proxyInitFunction = "initialize(address,address,bytes)"; - await withConfirmation( - cFeeAccumulatorProxy.connect(sDeployer)[proxyInitFunction]( - cFeeAccumulator.address, // implementation address - addresses.mainnet.Timelock, // governance - "0x", // do not call any initialize functions - await getTxOpts() - ) - ); - - // 5. Deploy the new Native Staking Strategy implementation - console.log(`About to deploy NativeStakingSSVStrategy implementation`); - const dNativeStakingStrategyImpl = await deployWithConfirmation( - "NativeStakingSSVStrategy", - [ - [addresses.zero, cVaultProxy.address], //_baseConfig - addresses.mainnet.WETH, // wethAddress - addresses.mainnet.SSV, // ssvToken - addresses.mainnet.SSVNetwork, // ssvNetwork - 500, // maxValidators - dFeeAccumulatorProxy.address, // feeAccumulator - addresses.mainnet.beaconChainDepositContract, // beacon chain deposit contract - ] - ); - const cNativeStakingStrategyImpl = await ethers.getContractAt( - "NativeStakingSSVStrategy", - dNativeStakingStrategyImpl.address - ); - - const cNativeStakingStrategy = await ethers.getContractAt( - "NativeStakingSSVStrategy", - cNativeStakingStrategyProxy.address - ); - - // 6. Initialize Native Staking Proxy with new implementation and strategy initialization - console.log(`About to initialize NativeStakingSSVStrategy`); - const initData = cNativeStakingStrategyImpl.interface.encodeFunctionData( - "initialize(address[],address[],address[])", - [ - [addresses.mainnet.WETH], // reward token addresses - /* no need to specify WETH as an asset, since we have that overriden in the "supportsAsset" - * function on the strategy - */ - [], // asset token addresses - [], // platform tokens addresses - ] - ); - - /* Before kicking off the deploy script make sure the Defender relayer transfers the governance - * of the proxy to the deployer account that shall be deploying this script so it will be able - * to initialize the proxy contract - * - * Run the following to make it happen, and comment this error block out: - * yarn run hardhat transferGovernanceNativeStakingProxy --index 3 --deployer 0xdeployerAddress --network mainnet - */ - const proxyGovernor = await cNativeStakingStrategyProxy.governor(); - if (isFork && proxyGovernor != deployerAddr) { - const relayerSigner = await impersonateAccount( - addresses.mainnet.validatorRegistrator - ); - await withConfirmation( - cNativeStakingStrategyProxy - .connect(relayerSigner) - .transferGovernance(deployerAddr, await getTxOpts()) - ); - } - - // 7. Transfer governance of the Native Staking Strategy proxy to the deployer - console.log(`About to claimGovernance of NativeStakingStrategyProxy`); - await withConfirmation( - cNativeStakingStrategyProxy - .connect(sDeployer) - .claimGovernance(await getTxOpts()) - ); - - // 8. Init the proxy to point at the implementation, set the governor, and call initialize - console.log(`About to initialize of NativeStakingStrategy`); - await withConfirmation( - cNativeStakingStrategyProxy.connect(sDeployer)[proxyInitFunction]( - cNativeStakingStrategyImpl.address, // implementation address - addresses.mainnet.Timelock, // governance - initData, // data for call to the initialize function on the strategy - await getTxOpts() - ) - ); - - // 9. Safe approve SSV token spending - console.log(`About to safeApproveAllTokens of NativeStakingStrategy`); - await cNativeStakingStrategy.connect(sDeployer).safeApproveAllTokens(); - - // Governance Actions - // ---------------- - return { - name: `Deploy a third OETH Native Staking Strategy.`, - actions: [ - // 1. Add new strategy to vault - { - contract: cVaultAdmin, - signature: "approveStrategy(address)", - args: [cNativeStakingStrategyProxy.address], - }, - // 2. configure Harvester to support the strategy - { - contract: cHarvester, - signature: "setSupportedStrategy(address,bool)", - args: [cNativeStakingStrategyProxy.address, true], - }, - // 3. set harvester to the strategy - { - contract: cNativeStakingStrategy, - signature: "setHarvesterAddress(address)", - args: [cHarvesterProxy.address], - }, - // 4. configure the fuse interval - { - contract: cNativeStakingStrategy, - signature: "setFuseInterval(uint256,uint256)", - args: [ - ethers.utils.parseEther("21.6"), - ethers.utils.parseEther("25.6"), - ], - }, - // 5. set validator registrator to the Defender Relayer - { - contract: cNativeStakingStrategy, - signature: "setRegistrator(address)", - // The Defender Relayer - args: [addresses.mainnet.validatorRegistrator], - }, - // 6. set staking threshold - { - contract: cNativeStakingStrategy, - signature: "setStakeETHThreshold(uint256)", - // 16 validators before the 5/8 multisig has to call resetStakeETHTally - args: [ethers.utils.parseEther("512")], // 16 * 32ETH - }, - // 7. set staking monitor - { - contract: cNativeStakingStrategy, - signature: "setStakingMonitor(address)", - // The 5/8 multisig - args: [addresses.mainnet.Guardian], - }, - ], - }; - } -); diff --git a/contracts/deploy/mainnet/110_transfer_morpho.js b/contracts/deploy/mainnet/110_transfer_morpho.js deleted file mode 100644 index 42e554e5dc..0000000000 --- a/contracts/deploy/mainnet/110_transfer_morpho.js +++ /dev/null @@ -1,69 +0,0 @@ -const { deploymentWithGovernanceProposal } = require("../../utils/deploy"); - -module.exports = deploymentWithGovernanceProposal( - { - deployName: "110_transfer_morpho", - forceDeploy: false, - forceSkip: true, - skipSimulation: true, - reduceQueueTime: true, - deployerIsProposer: false, - proposalId: "", - }, - async () => { - const cOUSDMorphoAaveProxy = await ethers.getContract( - "MorphoAaveStrategyProxy" - ); - const cOUSDMorphoCompoundProxy = await ethers.getContract( - "MorphoCompoundStrategyProxy" - ); - const cOETHMorphoAaveProxy = await ethers.getContract( - "OETHMorphoAaveStrategyProxy" - ); - - const cOUSDMorphoAave = await ethers.getContractAt( - "InitializableAbstractStrategy", - cOUSDMorphoAaveProxy.address - ); - const cOUSDMorphoCompound = await ethers.getContractAt( - "InitializableAbstractStrategy", - cOUSDMorphoCompoundProxy.address - ); - const cOETHMorphoAave = await ethers.getContractAt( - "InitializableAbstractStrategy", - cOETHMorphoAaveProxy.address - ); - - const morpho = await ethers.getContractAt( - "IERC20", - "0x9994e35db50125e0df82e4c2dde62496ce330999" - ); - - const ousdAaveBalance = await morpho.balanceOf(cOUSDMorphoAave.address); - const ousdCompBalance = await morpho.balanceOf(cOUSDMorphoCompound.address); - const oethAaveBalance = await morpho.balanceOf(cOETHMorphoAave.address); - - // Governance Actions - // ---------------- - return { - name: "Transfer Morpho Tokens from Strategies to the Guardian", - actions: [ - { - contract: cOUSDMorphoAave, - signature: "transferToken(address,uint256)", - args: [morpho.address, ousdAaveBalance], - }, - { - contract: cOUSDMorphoCompound, - signature: "transferToken(address,uint256)", - args: [morpho.address, ousdCompBalance], - }, - { - contract: cOETHMorphoAave, - signature: "transferToken(address,uint256)", - args: [morpho.address, oethAaveBalance], - }, - ], - }; - } -); diff --git a/contracts/deploy/mainnet/111_morpho_wrap_and_transfer.js b/contracts/deploy/mainnet/111_morpho_wrap_and_transfer.js deleted file mode 100644 index b4d1481fff..0000000000 --- a/contracts/deploy/mainnet/111_morpho_wrap_and_transfer.js +++ /dev/null @@ -1,52 +0,0 @@ -const addresses = require("../../utils/addresses"); -const { deploymentWithGovernanceProposal } = require("../../utils/deploy"); - -module.exports = deploymentWithGovernanceProposal( - { - deployName: "111_morpho_wrap_and_transfer", - forceDeploy: false, - // forceSkip: true, - reduceQueueTime: true, - deployerIsProposer: false, - proposalId: - "52303668283507532886105041405617076369748861896782994594114630508874108983718", - }, - async () => { - const { strategistAddr } = await getNamedAccounts(); - - const cLegacyMorpho = await ethers.getContractAt( - [ - "function approve(address,uint256) external", - "function balanceOf(address) external view returns(uint256)", - ], - "0x9994E35Db50125E0DF82e4c2dde62496CE330999" - ); - - const cWrapperContract = await ethers.getContractAt( - ["function depositFor(address,uint256) external"], - "0x9D03bb2092270648d7480049d0E58d2FcF0E5123" - ); - - const currentBalance = await cLegacyMorpho.balanceOf( - addresses.mainnet.Timelock - ); - - // Governance Actions - // ---------------- - return { - name: "Transfer Morpho tokens from Timelock to the Guardian", - actions: [ - { - contract: cLegacyMorpho, - signature: "approve(address,uint256)", - args: [cWrapperContract.address, currentBalance], - }, - { - contract: cWrapperContract, - signature: "depositFor(address,uint256)", - args: [strategistAddr, currentBalance], - }, - ], - }; - } -); diff --git a/contracts/deploy/mainnet/112_ousd_morpho_gauntlet_usdc.js b/contracts/deploy/mainnet/112_ousd_morpho_gauntlet_usdc.js deleted file mode 100644 index 77a7b80512..0000000000 --- a/contracts/deploy/mainnet/112_ousd_morpho_gauntlet_usdc.js +++ /dev/null @@ -1,96 +0,0 @@ -const addresses = require("../../utils/addresses"); -const { deploymentWithGovernanceProposal } = require("../../utils/deploy"); - -module.exports = deploymentWithGovernanceProposal( - { - deployName: "112_ousd_morpho_gauntlet_usdc", - forceDeploy: false, - // forceSkip: true, - reduceQueueTime: true, - deployerIsProposer: false, - proposalId: - "75087811221559915489997248701865604408180819987973892712738892811928200381194", - }, - async ({ deployWithConfirmation, getTxOpts, withConfirmation }) => { - // Current OUSD Vault contracts - const cVaultProxy = await ethers.getContract("VaultProxy"); - const cVaultAdmin = await ethers.getContractAt( - "VaultAdmin", - cVaultProxy.address - ); - const cHarvesterProxy = await ethers.getContract("HarvesterProxy"); - const cHarvester = await ethers.getContractAt( - "Harvester", - cHarvesterProxy.address - ); - - // Deployer Actions - // ---------------- - const { deployerAddr } = await getNamedAccounts(); - const sDeployer = await ethers.provider.getSigner(deployerAddr); - - // 1. Deploy new Morpho Gauntlet Prime USDC Strategy proxy - const dMorphoGauntletPrimeUSDCProxy = await deployWithConfirmation( - "MorphoGauntletPrimeUSDCStrategyProxy" - ); - const cMorphoGauntletPrimeUSDCProxy = await ethers.getContract( - "MorphoGauntletPrimeUSDCStrategyProxy" - ); - - // 2. Deploy new Generalized4626Strategy contract as it has an immutable to the Morpho Vault contract - const dGeneralized4626Strategy = await deployWithConfirmation( - "Generalized4626Strategy", - [ - [addresses.mainnet.MorphoGauntletPrimeUSDCVault, cVaultProxy.address], - addresses.mainnet.USDC, - ] - ); - const cMorphoGauntletPrimeUSDC = await ethers.getContractAt( - "Generalized4626Strategy", - dMorphoGauntletPrimeUSDCProxy.address - ); - - // 3. Construct initialize call data to initialize and configure the new strategy - const initData = cMorphoGauntletPrimeUSDC.interface.encodeFunctionData( - "initialize()", - [] - ); - - // 4. Init the proxy to point at the implementation, set the governor, and call initialize - const initFunction = "initialize(address,address,bytes)"; - await withConfirmation( - cMorphoGauntletPrimeUSDCProxy.connect(sDeployer)[initFunction]( - dGeneralized4626Strategy.address, - addresses.mainnet.Timelock, // governor - initData, // data for delegate call to the initialize function on the strategy - await getTxOpts() - ) - ); - - // Governance Actions - // ---------------- - return { - name: "Add Morpho Gauntlet Prime USDC Strategy to the OUSD Vault", - actions: [ - { - // Add the new strategy to the vault - contract: cVaultAdmin, - signature: "approveStrategy(address)", - args: [cMorphoGauntletPrimeUSDC.address], - }, - { - // Add the new strategy to the Harvester - contract: cHarvester, - signature: "setSupportedStrategy(address,bool)", - args: [cMorphoGauntletPrimeUSDC.address, true], - }, - { - // Set the Harvester in the new strategy - contract: cMorphoGauntletPrimeUSDC, - signature: "setHarvesterAddress(address)", - args: [cHarvesterProxy.address], - }, - ], - }; - } -); diff --git a/contracts/deploy/mainnet/113_ousd_morpho_gauntlet_usdt.js b/contracts/deploy/mainnet/113_ousd_morpho_gauntlet_usdt.js deleted file mode 100644 index 2a120feaba..0000000000 --- a/contracts/deploy/mainnet/113_ousd_morpho_gauntlet_usdt.js +++ /dev/null @@ -1,96 +0,0 @@ -const addresses = require("../../utils/addresses"); -const { deploymentWithGovernanceProposal } = require("../../utils/deploy"); - -module.exports = deploymentWithGovernanceProposal( - { - deployName: "113_ousd_morpho_gauntlet_usdt", - forceDeploy: false, - // forceSkip: true, - reduceQueueTime: true, - deployerIsProposer: false, - proposalId: - "81633492285999845572989946154472567378435839827604776411913516896058378421073", - }, - async ({ deployWithConfirmation, getTxOpts, withConfirmation }) => { - // Current OUSD Vault contracts - const cVaultProxy = await ethers.getContract("VaultProxy"); - const cVaultAdmin = await ethers.getContractAt( - "VaultAdmin", - cVaultProxy.address - ); - const cHarvesterProxy = await ethers.getContract("HarvesterProxy"); - const cHarvester = await ethers.getContractAt( - "Harvester", - cHarvesterProxy.address - ); - - // Deployer Actions - // ---------------- - const { deployerAddr } = await getNamedAccounts(); - const sDeployer = await ethers.provider.getSigner(deployerAddr); - - // 1. Deploy new Morpho Gauntlet Prime USDT Strategy proxy - const dMorphoGauntletPrimeUSDTProxy = await deployWithConfirmation( - "MorphoGauntletPrimeUSDTStrategyProxy" - ); - const cMorphoGauntletPrimeUSDTProxy = await ethers.getContract( - "MorphoGauntletPrimeUSDTStrategyProxy" - ); - - // 2. Deploy new Generalized4626USDTStrategy contract as it has an immutable to the Morpho Vault contract - const dGeneralized4626USDTStrategy = await deployWithConfirmation( - "Generalized4626USDTStrategy", - [ - [addresses.mainnet.MorphoGauntletPrimeUSDTVault, cVaultProxy.address], - addresses.mainnet.USDT, - ] - ); - const cMorphoGauntletPrimeUSDT = await ethers.getContractAt( - "Generalized4626USDTStrategy", - dMorphoGauntletPrimeUSDTProxy.address - ); - - // 3. Construct initialize call data to initialize and configure the new strategy - const initData = cMorphoGauntletPrimeUSDT.interface.encodeFunctionData( - "initialize()", - [] - ); - - // 4. Init the proxy to point at the implementation, set the governor, and call initialize - const initFunction = "initialize(address,address,bytes)"; - await withConfirmation( - cMorphoGauntletPrimeUSDTProxy.connect(sDeployer)[initFunction]( - dGeneralized4626USDTStrategy.address, - addresses.mainnet.Timelock, // governor - initData, // data for delegate call to the initialize function on the strategy - await getTxOpts() - ) - ); - - // Governance Actions - // ---------------- - return { - name: "Add Morpho Gauntlet Prime USDT Strategy to the OUSD Vault", - actions: [ - { - // Add the new strategy to the vault - contract: cVaultAdmin, - signature: "approveStrategy(address)", - args: [cMorphoGauntletPrimeUSDT.address], - }, - { - // Add the new strategy to the Harvester - contract: cHarvester, - signature: "setSupportedStrategy(address,bool)", - args: [cMorphoGauntletPrimeUSDT.address, true], - }, - { - // Set the Harvester in the new strategy - contract: cMorphoGauntletPrimeUSDT, - signature: "setHarvesterAddress(address)", - args: [cHarvesterProxy.address], - }, - ], - }; - } -); diff --git a/contracts/deploy/mainnet/114_simple_harvester.js b/contracts/deploy/mainnet/114_simple_harvester.js deleted file mode 100644 index de1460835e..0000000000 --- a/contracts/deploy/mainnet/114_simple_harvester.js +++ /dev/null @@ -1,48 +0,0 @@ -const addresses = require("../../utils/addresses"); -const { deploymentWithGovernanceProposal } = require("../../utils/deploy"); - -module.exports = deploymentWithGovernanceProposal( - { - deployName: "114_simple_harvester", - forceDeploy: false, - // forceSkip: true, - reduceQueueTime: true, - deployerIsProposer: false, - proposalId: - "24744476590264145931226505760829226060179710806211802903308139275399713774496", - }, - async ({ deployWithConfirmation }) => { - const { strategistAddr } = await getNamedAccounts(); - - // 1. Deploy contract - const dOETHHarvesterSimple = await deployWithConfirmation( - "OETHHarvesterSimple", - [addresses.mainnet.Timelock, strategistAddr] - ); - - console.log("strategistAddr: ", strategistAddr); - const cOETHHarvesterSimple = await ethers.getContractAt( - "OETHHarvesterSimple", - dOETHHarvesterSimple.address - ); - - // Get AMO contract - const cAMO = await ethers.getContractAt( - "ConvexEthMetaStrategy", - addresses.mainnet.ConvexOETHAMOStrategy - ); - - // Governance Actions - // ---------------- - return { - name: "Change harvester in OETH AMO", - actions: [ - { - contract: cAMO, - signature: "setHarvesterAddress(address)", - args: [cOETHHarvesterSimple.address], - }, - ], - }; - } -); diff --git a/contracts/deploy/mainnet/115_ousd_upgrade.js b/contracts/deploy/mainnet/115_ousd_upgrade.js deleted file mode 100644 index e2b528bab1..0000000000 --- a/contracts/deploy/mainnet/115_ousd_upgrade.js +++ /dev/null @@ -1,35 +0,0 @@ -const { deploymentWithGovernanceProposal } = require("../../utils/deploy"); - -module.exports = deploymentWithGovernanceProposal( - { - deployName: "115_ousd_upgrade", - forceDeploy: false, - //forceSkip: true, - reduceQueueTime: true, - deployerIsProposer: false, - proposalId: - "81330106807897532209861932892553497229066239651440694079747414212484833485320", - }, - async ({ deployWithConfirmation }) => { - // Deployer Actions - // ---------------- - - // 1. Deploy new OUSD implementation without storage slot checks - const dOUSD = await deployWithConfirmation("OUSD", [], "OUSD", true); - const cOUSDProxy = await ethers.getContract("OUSDProxy"); - - // Governance Actions - // ---------------- - return { - name: "Upgrade OUSD token contract", - actions: [ - // 1. Upgrade the OUSD proxy to the new implementation - { - contract: cOUSDProxy, - signature: "upgradeTo(address)", - args: [dOUSD.address], - }, - ], - }; - } -); diff --git a/contracts/deploy/mainnet/116_oeth_upgrade.js b/contracts/deploy/mainnet/116_oeth_upgrade.js deleted file mode 100644 index 2ec9ccedbd..0000000000 --- a/contracts/deploy/mainnet/116_oeth_upgrade.js +++ /dev/null @@ -1,39 +0,0 @@ -const { deploymentWithGovernanceProposal } = require("../../utils/deploy"); - -module.exports = deploymentWithGovernanceProposal( - { - deployName: "116_oeth_upgrade", - forceDeploy: false, - //forceSkip: true, - reduceQueueTime: true, - deployerIsProposer: false, - proposalId: - "52429102495712506451181434062412649974462865869819985125168484368682758799461", - }, - async ({ deployWithConfirmation }) => { - // Deployer Actions - // ---------------- - const cOETHProxy = await ethers.getContract("OETHProxy"); - - // Deploy new version of OETH contract - const dOETHImpl = await deployWithConfirmation("OETH", [], undefined, true); - - // Governance Actions - // ---------------- - return { - name: "Upgrade OETH token contract\n\ - \n\ - This upgrade enabled yield delegation controlled by xOGN governance \n\ - \n\ - ", - actions: [ - // Upgrade the OETH token proxy contract to the new implementation - { - contract: cOETHProxy, - signature: "upgradeTo(address)", - args: [dOETHImpl.address], - }, - ], - }; - } -); diff --git a/contracts/deploy/mainnet/117_oeth_fixed_rate_dripper.js b/contracts/deploy/mainnet/117_oeth_fixed_rate_dripper.js deleted file mode 100644 index a33e4be44c..0000000000 --- a/contracts/deploy/mainnet/117_oeth_fixed_rate_dripper.js +++ /dev/null @@ -1,100 +0,0 @@ -const addresses = require("../../utils/addresses"); -const { deploymentWithGovernanceProposal } = require("../../utils/deploy"); - -module.exports = deploymentWithGovernanceProposal( - { - deployName: "117_oeth_fixed_rate_dripper", - forceDeploy: false, - //forceSkip: true, - reduceQueueTime: true, - deployerIsProposer: false, - proposalId: - "27194273192096049001033521868815029294031516460891881333743928574609945488001", - }, - async ({ deployWithConfirmation, withConfirmation }) => { - const cOETHVaultProxy = await ethers.getContractAt( - "VaultAdmin", - addresses.mainnet.OETHVaultProxy - ); - - // Deployer Actions - // ---------------- - const { deployerAddr } = await getNamedAccounts(); - const sDeployer = await ethers.provider.getSigner(deployerAddr); - - // 1. Deploy new implementation of OETH Dripper (with transferAllToken function) - // 2. Deploy new OETH fixed rate dripper (proxy + implementation) - // 3. Upgrade Dripper to the new version (with transferAll token function) - // 4. Transfer all funds from old dripper to new dripper - // 5. Set new dripper on the vault - - // --- 1 --- - // 1.a. Get the current OETH Dripper Proxy - const cOETHDripperProxy = await ethers.getContract("OETHDripperProxy"); - - // 1.b. Deploy the new OETH Dripper implementation (with transferAllToken function) - const dOETHDripper = await deployWithConfirmation( - "OETHDripper", - [addresses.mainnet.OETHVaultProxy, addresses.mainnet.WETH], - undefined, - true // due to changing name from `perBlock` to `perSecond` - ); - - const cOETHDripper = await ethers.getContractAt( - "OETHDripper", - cOETHDripperProxy.address - ); - - // --- 2 --- - // 2.a Deploy the Fixed Rate Dripper Proxy - const dOETHFixedRateDripperProxy = await deployWithConfirmation( - "OETHFixedRateDripperProxy" - ); - - const cOETHFixedRateDripperProxy = await ethers.getContract( - "OETHFixedRateDripperProxy" - ); - - // 2.b. Deploy the OETH Fixed Rate Dripper implementation - const dOETHFixedRateDripper = await deployWithConfirmation( - "OETHFixedRateDripper", - [addresses.mainnet.OETHVaultProxy, addresses.mainnet.WETH] - ); - - // 2.c. Initialize the Fixed Rate Dripper Proxy - const initFunction = "initialize(address,address,bytes)"; - await withConfirmation( - cOETHFixedRateDripperProxy.connect(sDeployer)[initFunction]( - dOETHFixedRateDripper.address, - addresses.mainnet.Timelock, // governor - "0x" // no init data - ) - ); - // --- 3 & 4 & 5 --- - // Governance Actions - // ---------------- - return { - name: "Migrate OETH Dripper to Fixed Rate Dripper", - actions: [ - // 3. Upgrade the Dripper to the new version - { - contract: cOETHDripperProxy, - signature: "upgradeTo(address)", - args: [dOETHDripper.address], - }, - // 4. Transfer all funds from the old dripper to the new dripper - { - contract: cOETHDripper, - signature: "transferAllToken(address,address)", - args: [addresses.mainnet.WETH, cOETHFixedRateDripperProxy.address], - }, - // 5. Set new dripper address on the vault - { - contract: cOETHVaultProxy, - signature: "setDripper(address)", - args: [dOETHFixedRateDripperProxy.address], - }, - ], - }; - } -); diff --git a/contracts/deploy/mainnet/118_multichain_strategist.js b/contracts/deploy/mainnet/118_multichain_strategist.js deleted file mode 100644 index 0aab665be8..0000000000 --- a/contracts/deploy/mainnet/118_multichain_strategist.js +++ /dev/null @@ -1,77 +0,0 @@ -const { deploymentWithGovernanceProposal } = require("../../utils/deploy"); - -module.exports = deploymentWithGovernanceProposal( - { - deployName: "118_multichain_strategist", - forceDeploy: false, - //forceSkip: true, - reduceQueueTime: true, - deployerIsProposer: false, - proposalId: - "29029286887383246507190904901480289164471194993284788972189545670391461770154", - }, - async () => { - const { multichainStrategistAddr } = await getNamedAccounts(); - - const cOETHVaultProxy = await ethers.getContract("OETHVaultProxy"); - const cOETHVault = await ethers.getContractAt( - "IVault", - cOETHVaultProxy.address - ); - - const cOUSDVaultProxy = await ethers.getContract("VaultProxy"); - const cOUSDVault = await ethers.getContractAt( - "IVault", - cOUSDVaultProxy.address - ); - - const cOETHBuybackProxy = await ethers.getContract("OETHBuybackProxy"); - const cOETHBuyback = await ethers.getContractAt( - "OETHBuyback", - cOETHBuybackProxy.address - ); - - const cOUSDBuybackProxy = await ethers.getContract("BuybackProxy"); - const cOUSDBuyback = await ethers.getContractAt( - "OUSDBuyback", - cOUSDBuybackProxy.address - ); - - const cOETHHarvesterSimple = await ethers.getContract( - "OETHHarvesterSimple" - ); - - // Governance Actions - // ---------------- - return { - name: "Switch to multichain guardian", - actions: [ - { - contract: cOETHVault, - signature: "setStrategistAddr(address)", - args: [multichainStrategistAddr], - }, - { - contract: cOUSDVault, - signature: "setStrategistAddr(address)", - args: [multichainStrategistAddr], - }, - { - contract: cOETHBuyback, - signature: "setStrategistAddr(address)", - args: [multichainStrategistAddr], - }, - { - contract: cOUSDBuyback, - signature: "setStrategistAddr(address)", - args: [multichainStrategistAddr], - }, - { - contract: cOETHHarvesterSimple, - signature: "setStrategistAddr(address)", - args: [multichainStrategistAddr], - }, - ], - }; - } -); diff --git a/contracts/deploy/mainnet/119_multisig_as_canceller.js b/contracts/deploy/mainnet/119_multisig_as_canceller.js deleted file mode 100644 index 502b09e553..0000000000 --- a/contracts/deploy/mainnet/119_multisig_as_canceller.js +++ /dev/null @@ -1,37 +0,0 @@ -const { deploymentWithGovernanceProposal } = require("../../utils/deploy"); -const addresses = require("../../utils/addresses"); - -module.exports = deploymentWithGovernanceProposal( - { - deployName: "119_multisig_as_canceller", - forceDeploy: false, - //forceSkip: true, - reduceQueueTime: true, - deployerIsProposer: false, - proposalId: - "68528526132026080998168122859635399048147530736160306492805070199180314362200", - }, - async () => { - const { timelockAddr } = await getNamedAccounts(); - - const cTimelock = await ethers.getContractAt( - "ITimelockController", - timelockAddr - ); - - const timelockCancellerRole = await cTimelock.CANCELLER_ROLE(); - - // Governance Actions - // ---------------- - return { - name: "Grant canceller role to 5/8 Multisig", - actions: [ - { - contract: cTimelock, - signature: "grantRole(bytes32,address)", - args: [timelockCancellerRole, addresses.mainnet.Guardian], - }, - ], - }; - } -); diff --git a/contracts/deploy/mainnet/120_remove_ousd_amo.js b/contracts/deploy/mainnet/120_remove_ousd_amo.js deleted file mode 100644 index bbf7abda2b..0000000000 --- a/contracts/deploy/mainnet/120_remove_ousd_amo.js +++ /dev/null @@ -1,43 +0,0 @@ -const { deploymentWithGovernanceProposal } = require("../../utils/deploy"); -const addresses = require("../../utils/addresses"); - -module.exports = deploymentWithGovernanceProposal( - { - deployName: "120_remove_ousd_amo", - forceDeploy: false, - //forceSkip: true, - reduceQueueTime: true, - deployerIsProposer: false, - proposalId: - "47377301530901645877668147419124102540503539821842750844128770769774878595548", - }, - async () => { - const cOUSDVaultProxy = await ethers.getContract("VaultProxy"); - const cOUSDVault = await ethers.getContractAt( - "IVault", - cOUSDVaultProxy.address - ); - - const cOUSDMetaStrategyProxy = await ethers.getContract( - "ConvexOUSDMetaStrategyProxy" - ); - - // Governance Actions - // ---------------- - return { - name: "Remove OUSD AMO Strategy", - actions: [ - { - contract: cOUSDVault, - signature: "removeStrategy(address)", - args: [cOUSDMetaStrategyProxy.address], - }, - { - contract: cOUSDVault, - signature: "setOusdMetaStrategy(address)", - args: [addresses.zero], - }, - ], - }; - } -); diff --git a/contracts/deploy/mainnet/121_pool_booster_curve.js b/contracts/deploy/mainnet/121_pool_booster_curve.js deleted file mode 100644 index 339dcece5d..0000000000 --- a/contracts/deploy/mainnet/121_pool_booster_curve.js +++ /dev/null @@ -1,123 +0,0 @@ -const addresses = require("../../utils/addresses"); -const { - deploymentWithGovernanceProposal, - encodeSaltForCreateX, -} = require("../../utils/deploy"); -const createxAbi = require("../../abi/createx.json"); -const PoolBoosterBytecode = require("../../artifacts/contracts/poolBooster/curve/CurvePoolBooster.sol/CurvePoolBooster.json"); -const ProxyBytecode = require("../../artifacts/contracts/proxies/Proxies.sol/CurvePoolBoosterProxy.json"); - -module.exports = deploymentWithGovernanceProposal( - { - deployName: "121_pool_booster_curve", - forceDeploy: false, - //forceSkip: true, - reduceQueueTime: true, - deployerIsProposer: false, - proposalId: "", - }, - async ({ withConfirmation }) => { - const { deployerAddr } = await getNamedAccounts(); - const sDeployer = await ethers.provider.getSigner(deployerAddr); - console.log(`Deployer address: ${deployerAddr}`); - - const cCurvePoolBooster = await ethers.getContractAt( - "CurvePoolBooster", - addresses.zero - ); - const cCurvePoolBoosterProxy = await ethers.getContractAt( - "CurvePoolBoosterProxy", - addresses.zero - ); - - console.log("\nStarting deployment using CreateX"); - const rewardToken = addresses.mainnet.OUSDProxy; - const gauge = addresses.mainnet.CurveOUSDUSDTGauge; - const targetedChainId = 42161; // arbitrum - const fee = 0; - - // Get CreateX contract - const cCreateX = await ethers.getContractAt(createxAbi, addresses.createX); - - // Generate salt - const salt = ethers.utils.keccak256( - ethers.utils.concat([ - ethers.utils.arrayify(rewardToken), - ethers.utils.arrayify(gauge), - ethers.utils.arrayify(1), - ]) - ); - - const encodedSalt = encodeSaltForCreateX(deployerAddr, false, salt); - console.log(`Encoded salt: ${encodedSalt}`); - - // --- Deploy implementation --- // - const cachedInitCodeImpl = ethers.utils.concat([ - PoolBoosterBytecode.bytecode, - ethers.utils.defaultAbiCoder.encode( - ["uint256", "address", "address"], - [targetedChainId, rewardToken, gauge] - ), - ]); - const txResponse = await withConfirmation( - cCreateX.connect(sDeployer).deployCreate2(encodedSalt, cachedInitCodeImpl) - ); - const txReceipt = await txResponse.wait(); - // event 0 is GovernorshipTransferred - // event 1 is ContractCreation, topics[1] is the address of the deployed contract, topics[2] is the salt - const implementationAddress = ethers.utils.getAddress( - `0x${txReceipt.events[1].topics[1].slice(26)}` - ); - console.log( - `Curve Booster Implementation deployed at: ${implementationAddress}` - ); - - // --- Deploy and init proxy --- // - const cachedInitCodeProxy = ProxyBytecode.bytecode; // No constructor arguments - const initializeImplem = cCurvePoolBooster.interface.encodeFunctionData( - "initialize(address,uint16,address,address,address)", - [ - addresses.multichainStrategist, // strategist - fee, // fee - addresses.multichainStrategist, // feeCollector - addresses.mainnet.CampaignRemoteManager, // campaignRemoteManager - addresses.votemarket, // votemarket - ] - ); - const initializeProxy = cCurvePoolBoosterProxy.interface.encodeFunctionData( - "initialize(address,address,bytes)", - [ - implementationAddress, // implementation - addresses.mainnet.Timelock, // governor - initializeImplem, // init data - ] - ); - const txResponseProxy = await withConfirmation( - cCreateX - .connect(sDeployer) - .deployCreate2AndInit( - encodedSalt, - cachedInitCodeProxy, - initializeProxy, - ["0x00", "0x00"], - deployerAddr - ) - ); - const txReceiptProxy = await txResponseProxy.wait(); - // event 0 is GovernorshipTransferred - // event 1 is ContractCreation, topics[1] is the address of the deployed contract, topics[2] is the salt - // event 2 is StrategistUpdated - // event 3 is FeeUpdated - // event 4 is FeeCollectorUpdated - // event 5 is CampaignRemoteManagerUpdated - // event 6 is GovernorshipTransferred - const proxyAddress = ethers.utils.getAddress( - `0x${txReceiptProxy.events[1].topics[1].slice(26)}` - ); - console.log(`Curve Booster Proxy deployed at: ${proxyAddress}`); - - await ethers.getContractAt("CurvePoolBooster", proxyAddress); - await ethers.getContractAt("CurvePoolBoosterProxy", proxyAddress); - return {}; - } -); diff --git a/contracts/deploy/mainnet/122_delegate_yield_curve_pool.js b/contracts/deploy/mainnet/122_delegate_yield_curve_pool.js deleted file mode 100644 index d671376074..0000000000 --- a/contracts/deploy/mainnet/122_delegate_yield_curve_pool.js +++ /dev/null @@ -1,31 +0,0 @@ -const addresses = require("../../utils/addresses"); -const { deploymentWithGovernanceProposal } = require("../../utils/deploy"); - -module.exports = deploymentWithGovernanceProposal( - { - deployName: "122_delegate_yield_curve_pool", - forceDeploy: false, - //forceSkip: true, - reduceQueueTime: true, - deployerIsProposer: false, - proposalId: "", - }, - async () => { - const OUSD = await ethers.getContractAt( - "OUSD", - addresses.mainnet.OUSDProxy - ); - const OUSDUSDTCurvePoolBooster = - "0xF4c001dfe53C584425d7943395C7E57b10BD1DC8"; - return { - name: "Delegate Yield from OUSD/USDT Curve Pool to PoolBooster", - actions: [ - { - contract: OUSD, - signature: "delegateYield(address,address)", - args: [addresses.mainnet.CurveOUSDUSDTPool, OUSDUSDTCurvePoolBooster], - }, - ], - }; - } -); diff --git a/contracts/deploy/mainnet/123_simple_harvester_v2.js b/contracts/deploy/mainnet/123_simple_harvester_v2.js deleted file mode 100644 index bbaeb35287..0000000000 --- a/contracts/deploy/mainnet/123_simple_harvester_v2.js +++ /dev/null @@ -1,176 +0,0 @@ -const addresses = require("../../utils/addresses"); -const { deploymentWithGovernanceProposal } = require("../../utils/deploy"); - -module.exports = deploymentWithGovernanceProposal( - { - deployName: "123_simple_harvester_v2", - forceDeploy: false, - //forceSkip: true, - reduceQueueTime: true, - deployerIsProposer: false, - proposalId: - "26550610486664057138305516943450169995813254080724548319324256884874259231291", - }, - async ({ deployWithConfirmation, withConfirmation }) => { - const { multichainStrategistAddr, deployerAddr } = await getNamedAccounts(); - const sDeployer = await ethers.provider.getSigner(deployerAddr); - - // ---- Menu --- - // 1. Deploy new simple Harvester - // 2. Change harvester on all SSV strategies - // 3. Support strategies on the harvester - // 4. Sending all WETH from the OldDripper to FixedRateDripper - // 5. Update harvester on OETH AMO - // 6. Support AMO strategy on new harvester - // -------------- - - // 1. Deploy new simple Harvester - const cOETHFixedRateDripperProxy = await ethers.getContract( - "OETHFixedRateDripperProxy" - ); - - // 1.a Deploy proxy - const dOETHSimpleHarvesterProxy = await deployWithConfirmation( - "OETHSimpleHarvesterProxy" - ); - - const cOETHSimpleHarvesterProxy = await ethers.getContract( - "OETHSimpleHarvesterProxy" - ); - - // 1.b Deploy implementation - await deployWithConfirmation( - "OETHHarvesterSimple", - [addresses.mainnet.WETH], - undefined, - true - ); - - const cOETHHarvesterSimpleImpl = await ethers.getContract( - "OETHHarvesterSimple" - ); - - // 1.c Initialize the proxy - const initData = cOETHHarvesterSimpleImpl.interface.encodeFunctionData( - "initialize(address,address,address)", - [ - addresses.mainnet.Timelock, - multichainStrategistAddr, - cOETHFixedRateDripperProxy.address, - ] - ); - - const proxyInitFunction = "initialize(address,address,bytes)"; - // prettier-ignore - await withConfirmation( - cOETHSimpleHarvesterProxy.connect(sDeployer)[proxyInitFunction]( - cOETHHarvesterSimpleImpl.address, - addresses.mainnet.Timelock, - initData - ) - ); - - const cOETHHarvesterSimple = await ethers.getContractAt( - "OETHHarvesterSimple", - dOETHSimpleHarvesterProxy.address - ); - - // 2. Change harvester on all SSV strategies - const cNativeStakingStrategyProxy_1 = await ethers.getContract( - "NativeStakingSSVStrategyProxy" - ); - const cNativeStakingStrategy_1 = await ethers.getContractAt( - "NativeStakingSSVStrategy", - cNativeStakingStrategyProxy_1.address - ); - - const cNativeStakingStrategyProxy_2 = await ethers.getContract( - "NativeStakingSSVStrategy2Proxy" - ); - const cNativeStakingStrategy_2 = await ethers.getContractAt( - "NativeStakingSSVStrategy", - cNativeStakingStrategyProxy_2.address - ); - - const cNativeStakingStrategyProxy_3 = await ethers.getContract( - "NativeStakingSSVStrategy3Proxy" - ); - const cNativeStakingStrategy_3 = await ethers.getContractAt( - "NativeStakingSSVStrategy", - cNativeStakingStrategyProxy_3.address - ); - - // 4. Sending all WETH from the OldDripper to FixedRateDripper - const cOETHDripperProxy = await ethers.getContract("OETHDripperProxy"); - - const cOETHDripper = await ethers.getContractAt( - "OETHDripper", - cOETHDripperProxy.address - ); - - // 5. Update harvester on OETH AMO - const cAMO = await ethers.getContractAt( - "ConvexEthMetaStrategy", - addresses.mainnet.ConvexOETHAMOStrategy - ); - - // Governance Actions - // ---------------- - return { - name: "Move all strategies to new simple harvester", - actions: [ - // 2. Change harvester on all SSV strategies - { - contract: cNativeStakingStrategy_1, - signature: "setHarvesterAddress(address)", - args: [cOETHHarvesterSimple.address], - }, - { - contract: cNativeStakingStrategy_2, - signature: "setHarvesterAddress(address)", - args: [cOETHHarvesterSimple.address], - }, - { - contract: cNativeStakingStrategy_3, - signature: "setHarvesterAddress(address)", - args: [cOETHHarvesterSimple.address], - }, - // 3. Support strategies on the harvester - { - contract: cOETHHarvesterSimple, - signature: "setSupportedStrategy(address,bool)", - args: [cNativeStakingStrategy_1.address, true], - }, - { - contract: cOETHHarvesterSimple, - signature: "setSupportedStrategy(address,bool)", - args: [cNativeStakingStrategy_2.address, true], - }, - { - contract: cOETHHarvesterSimple, - signature: "setSupportedStrategy(address,bool)", - args: [cNativeStakingStrategy_3.address, true], - }, - // 4. Sending all WETH from the OldDripper to FixedRateDripper - { - contract: cOETHDripper, - signature: "transferAllToken(address,address)", - args: [addresses.mainnet.WETH, cOETHFixedRateDripperProxy.address], - }, - // 5. Update harvester on OETH AMO - { - contract: cAMO, - signature: "setHarvesterAddress(address)", - args: [cOETHHarvesterSimple.address], - }, - - // 6. Support AMO strategy on new harvester - { - contract: cOETHHarvesterSimple, - signature: "setSupportedStrategy(address,bool)", - args: [cAMO.address, true], - }, - ], - }; - } -); diff --git a/contracts/deploy/mainnet/124_remove_ousd_aave_strat.js b/contracts/deploy/mainnet/124_remove_ousd_aave_strat.js deleted file mode 100644 index b55162e5fb..0000000000 --- a/contracts/deploy/mainnet/124_remove_ousd_aave_strat.js +++ /dev/null @@ -1,50 +0,0 @@ -const addresses = require("../../utils/addresses"); -const { deploymentWithGovernanceProposal } = require("../../utils/deploy"); - -module.exports = deploymentWithGovernanceProposal( - { - deployName: "124_remove_ousd_aave_strat", - forceDeploy: false, - reduceQueueTime: true, - proposalId: - "88299129181157806559379214304016095228552624275043325103321115382026366391938", - }, - async () => { - const cVaultAdmin = await ethers.getContractAt( - "VaultAdmin", - addresses.mainnet.VaultProxy - ); - - const cAaveStrategyProxy = await ethers.getContract("AaveStrategyProxy"); - const cMorphoGauntletPrimeUSDTProxy = await ethers.getContract( - "MorphoGauntletPrimeUSDTStrategyProxy" - ); - - const cHarvesterProxy = await ethers.getContract("HarvesterProxy"); - const cHarvester = await ethers.getContractAt( - "Harvester", - cHarvesterProxy.address - ); - - return { - name: "Remove OUSD Aave Strategy", - actions: [ - { - contract: cVaultAdmin, - signature: "setAssetDefaultStrategy(address,address)", - args: [addresses.mainnet.USDT, cMorphoGauntletPrimeUSDTProxy.address], - }, - { - contract: cVaultAdmin, - signature: "removeStrategy(address)", - args: [cAaveStrategyProxy.address], - }, - { - contract: cHarvester, - signature: "setSupportedStrategy(address,bool)", - args: [cAaveStrategyProxy.address, false], - }, - ], - }; - } -); diff --git a/contracts/deploy/mainnet/125_buyback_treasury_address.js b/contracts/deploy/mainnet/125_buyback_treasury_address.js deleted file mode 100644 index 08df234744..0000000000 --- a/contracts/deploy/mainnet/125_buyback_treasury_address.js +++ /dev/null @@ -1,40 +0,0 @@ -const addresses = require("../../utils/addresses"); -const { deploymentWithGovernanceProposal } = require("../../utils/deploy"); - -module.exports = deploymentWithGovernanceProposal( - { - deployName: "125_buyback_treasury_address", - forceDeploy: false, - reduceQueueTime: true, - proposalId: - "56651372254281448265344353866131308080772476253698569576053764086501989665213", - }, - async () => { - const cBuybackProxy = await ethers.getContract("BuybackProxy"); - const cBuyback = await ethers.getContractAt( - "OUSDBuyback", - cBuybackProxy.address - ); - const cOETHBuybackProxy = await ethers.getContract("OETHBuybackProxy"); - const cOETHBuyback = await ethers.getContractAt( - "OETHBuyback", - cOETHBuybackProxy.address - ); - - return { - name: "Update Treasury Address in OUSD & OETH Buyback contracts", - actions: [ - { - contract: cBuyback, - signature: "setTreasuryManager(address)", - args: [addresses.multichainStrategist], - }, - { - contract: cOETHBuyback, - signature: "setTreasuryManager(address)", - args: [addresses.multichainStrategist], - }, - ], - }; - } -); diff --git a/contracts/deploy/mainnet/126_update_amo_mint_threshold.js b/contracts/deploy/mainnet/126_update_amo_mint_threshold.js deleted file mode 100644 index 6b1c9ac447..0000000000 --- a/contracts/deploy/mainnet/126_update_amo_mint_threshold.js +++ /dev/null @@ -1,28 +0,0 @@ -const { oethUnits } = require("../../test/helpers"); -const { deploymentWithGovernanceProposal } = require("../../utils/deploy"); -const { resolveContract } = require("../../utils/resolvers"); - -module.exports = deploymentWithGovernanceProposal( - { - deployName: "126_update_amo_mint_threshold", - forceDeploy: false, - reduceQueueTime: true, - proposalId: - "30965832335636924298210825232841816133670499253182025520679897537320067618960", - }, - async () => { - // Reference the OETH Vault - const cOETHVault = await resolveContract("OETHVaultProxy", "IVault"); - - return { - name: "Update OETH Curve AMO mint threshold", - actions: [ - { - contract: cOETHVault, - signature: "setNetOusdMintForStrategyThreshold(uint256)", - args: [oethUnits("50000")], - }, - ], - }; - } -); diff --git a/contracts/deploy/mainnet/127_replace_dai_with_usds.js b/contracts/deploy/mainnet/127_replace_dai_with_usds.js deleted file mode 100644 index 04d6b5a819..0000000000 --- a/contracts/deploy/mainnet/127_replace_dai_with_usds.js +++ /dev/null @@ -1,238 +0,0 @@ -const { deploymentWithGovernanceProposal } = require("../../utils/deploy"); -const addresses = require("../../utils/addresses"); -const { isFork } = require("../../utils/hardhat-helpers"); - -module.exports = deploymentWithGovernanceProposal( - { - deployName: "127_replace_dai_with_usds", - reduceQueueTime: true, - // forceSkip: true, - deployerIsProposer: false, - proposalId: - "114954970394844320178182896169894131762293708671906355250905818532387580494958", - // TODO: Temporary hack to test it on CI - simulateDirectlyOnTimelock: isFork, - // executionRetries: 2, - }, - async ({ deployWithConfirmation, getTxOpts, withConfirmation, ethers }) => { - const DAI = addresses.mainnet.DAI; - const USDS = addresses.mainnet.USDS; - const sUSDS = addresses.mainnet.sUSDS; - - const { deployerAddr, timelockAddr } = await getNamedAccounts(); - const sDeployer = await ethers.provider.getSigner(deployerAddr); - - const cVaultProxy = await ethers.getContract("VaultProxy"); - const cVault = await ethers.getContractAt("IVault", cVaultProxy.address); - const cDSRStrategyProxy = await ethers.getContract("MakerDsrStrategyProxy"); - - const cHarvesterProxy = await ethers.getContract("HarvesterProxy"); - const cHarvester = await ethers.getContractAt( - "Harvester", - cHarvesterProxy.address - ); - - const dVaultAdmin = await deployWithConfirmation("VaultAdmin"); - - // 1. Deploy OracleRouter - const dOracleRouter = await deployWithConfirmation("OracleRouter"); - const cOracleRouter = await ethers.getContractAt( - "OracleRouter", - dOracleRouter.address - ); - - const assetsToCache = [ - USDS, - DAI, - addresses.mainnet.USDT, - addresses.mainnet.USDC, - addresses.mainnet.COMP, - addresses.mainnet.Aave, - addresses.mainnet.CRV, - addresses.mainnet.CVX, - ]; - - for (const asset of assetsToCache) { - console.log(`Caching decimals for ${asset}`); - await withConfirmation( - cOracleRouter.connect(sDeployer).cacheDecimals(asset) - ); - } - - // 2. Deploy Migration Strategy - const dMigrationStrategy = await deployWithConfirmation( - "DAIMigrationStrategy", - [ - { - vaultAddress: cVaultProxy.address, - platformAddress: addresses.mainnet.DaiUsdsMigrationContract, - }, - DAI, - USDS, - timelockAddr, - ] - ); - - const migrationStrategy = await ethers.getContractAt( - "DAIMigrationStrategy", - dMigrationStrategy.address - ); - - // 3. Deploy SSR Strategy - const dMakerSSRStrategyProxy = await deployWithConfirmation( - "MakerSSRStrategyProxy" - ); - const dMakerSSRStrategy = await deployWithConfirmation( - "Generalized4626Strategy", - [[sUSDS, cVaultProxy.address], USDS] - ); - const cMakerSSRStrategyProxy = await ethers.getContract( - "MakerSSRStrategyProxy" - ); - const cMakerSSRStrategy = await ethers.getContractAt( - "Generalized4626Strategy", - dMakerSSRStrategyProxy.address - ); - - // 4. Initialize the SSR Strategy - const initData = cMakerSSRStrategy.interface.encodeFunctionData( - "initialize()", - [] - ); - const initFunction = "initialize(address,address,bytes)"; - await withConfirmation( - cMakerSSRStrategyProxy.connect(sDeployer)[initFunction]( - dMakerSSRStrategy.address, - timelockAddr, // governor - initData, // data for delegate call to the initialize function on the strategy - await getTxOpts() - ) - ); - console.log(`Initialized SSR Strategy Proxy`); - - return { - name: `Migrate DAI to USDS - -This governance proposal, once executed, will migrate all the Vault's DAI holdings to USDS. - -It adds a temporary strategy to migrate DAI to USDS using the DAI-USDS Migration Contract from Maker/Sky. - -It also adds a new strategy for USDS, the Sky Saving Rate (SSR) Strategy, which is a 4626 Vault that earns yield by compounding USDS in the Sky protocol. - -Post this governance proposal, DAI cannot be used to mint OUSD on the Vault. Any redeems will include a basket of USDT, USDC and USDS, and not DAI. -`, - actions: [ - { - // Upgrade VaultAdmin implementation - contract: cVault, - signature: "setAdminImpl(address)", - args: [dVaultAdmin.address], - }, - { - // Set OracleRouter on Vault - contract: cVault, - signature: "setPriceProvider(address)", - args: [cOracleRouter.address], - }, - { - // Support USDS on Vault - contract: cVault, - signature: "supportAsset(address,uint8)", - args: [USDS, 0], - }, - - { - // Approve Migration Strategy on the Vault - contract: cVault, - signature: "approveStrategy(address)", - args: [migrationStrategy.address], - }, - { - // Approve SSR Strategy on the Vault - contract: cVault, - signature: "approveStrategy(address)", - args: [cMakerSSRStrategy.address], - }, - { - // Set Migration Strategy as default for DAI - contract: cVault, - signature: "setAssetDefaultStrategy(address,address)", - args: [DAI, migrationStrategy.address], - }, - { - // Set Maker SSR Strategy as default for USDS - contract: cVault, - signature: "setAssetDefaultStrategy(address,address)", - args: [USDS, cMakerSSRStrategy.address], - }, - { - // Remove DSR strategy - // This would move all DAI to vault - contract: cVault, - signature: "removeStrategy(address)", - args: [cDSRStrategyProxy.address], - }, - { - // Allocate DAI from the Vault to the Migration Strategy - contract: cVault, - signature: "allocate()", - args: [], - }, - { - // Reset default strategy for DAI so that we can remove it - contract: cVault, - signature: "setAssetDefaultStrategy(address,address)", - args: [DAI, addresses.zero], - }, - { - // Remove Migration Strategy - // This will remove all USDS and move it to the Vault - contract: cVault, - signature: "removeStrategy(address)", - args: [migrationStrategy.address], - }, - - // { - // // Optional: Allocate USDS from the Vault to the SSR Strategy - // contract: cVault, - // signature: "allocate()", - // args: [], - // }, - - { - // Remove DAI - contract: cVault, - signature: "removeAsset(address)", - args: [DAI], - }, - - // Harvester related things - { - // Set Harvester in the SSR Strategy - contract: cMakerSSRStrategy, - signature: "setHarvesterAddress(address)", - args: [cHarvesterProxy.address], - }, - { - // Add SSR Strategy to the Harvester - contract: cHarvester, - signature: "setSupportedStrategy(address,bool)", - args: [cMakerSSRStrategy.address, true], - }, - { - // Remove DSR Strategy from the Harvester - contract: cHarvester, - signature: "setSupportedStrategy(address,bool)", - args: [cDSRStrategyProxy.address, false], - }, - - { - // Set Oracle Slippage for USDS - contract: cVault, - signature: "setOracleSlippage(address,uint16)", - args: [USDS, 25], - }, - ], - }; - } -); diff --git a/contracts/deploy/mainnet/128_remove_ousd_morpho_aave.js b/contracts/deploy/mainnet/128_remove_ousd_morpho_aave.js deleted file mode 100644 index f457865106..0000000000 --- a/contracts/deploy/mainnet/128_remove_ousd_morpho_aave.js +++ /dev/null @@ -1,40 +0,0 @@ -const { deploymentWithGovernanceProposal } = require("../../utils/deploy"); - -module.exports = deploymentWithGovernanceProposal( - { - deployName: "128_remove_ousd_morpho_aave", - forceDeploy: false, - reduceQueueTime: true, - proposalId: - "47150374744617854837268881905932350474964620527004713806544167947184108941396", - }, - async () => { - const cVaultProxy = await ethers.getContract("VaultProxy"); - const cVault = await ethers.getContractAt("IVault", cVaultProxy.address); - - const cMorphoAaveStrategyProxy = await ethers.getContract( - "MorphoAaveStrategyProxy" - ); - const cHarvesterProxy = await ethers.getContract("HarvesterProxy"); - const cHarvester = await ethers.getContractAt( - "Harvester", - cHarvesterProxy.address - ); - - return { - name: "Remove Morpho Aave Strategy from the Vault", - actions: [ - { - contract: cVault, - signature: "removeStrategy(address)", - args: [cMorphoAaveStrategyProxy.address], - }, - { - contract: cHarvester, - signature: "setSupportedStrategy(address,bool)", - args: [cMorphoAaveStrategyProxy.address, false], - }, - ], - }; - } -); diff --git a/contracts/deploy/mainnet/129_oeth_vault_upgrade.js b/contracts/deploy/mainnet/129_oeth_vault_upgrade.js deleted file mode 100644 index 0557665fd7..0000000000 --- a/contracts/deploy/mainnet/129_oeth_vault_upgrade.js +++ /dev/null @@ -1,60 +0,0 @@ -const addresses = require("../../utils/addresses"); -const { deploymentWithGovernanceProposal } = require("../../utils/deploy"); - -module.exports = deploymentWithGovernanceProposal( - { - deployName: "129_oeth_vault_upgrade", - forceDeploy: false, - //forceSkip: true, - reduceQueueTime: true, - deployerIsProposer: false, - proposalId: - "88718652422371169609565969051527989757314494318421619545684851109270814057874", - }, - async ({ deployWithConfirmation }) => { - // Deployer Actions - // ---------------- - - // 1. Deploy new OETH Vault Core and Admin implementations - const dVaultCore = await deployWithConfirmation("OETHVaultCore", [ - addresses.mainnet.WETH, - ]); - const dVaultAdmin = await deployWithConfirmation("OETHVaultAdmin", [ - addresses.mainnet.WETH, - ]); - - // 2. Connect to the OETH Vault as its governor via the proxy - const cVaultProxy = await ethers.getContract("OETHVaultProxy"); - const cVault = await ethers.getContractAt("IVault", cVaultProxy.address); - - const cCurveAMOStrategyProxy = await ethers.getContract( - "ConvexEthMetaStrategyProxy" - ); - - // Governance Actions - // ---------------- - return { - name: "Upgrade OETH Vault to support multiple AMOs", - actions: [ - // 1. Upgrade the OETH Vault proxy to the new core vault implementation - { - contract: cVaultProxy, - signature: "upgradeTo(address)", - args: [dVaultCore.address], - }, - // 2. Set OETH Vault proxy to the new admin vault implementation - { - contract: cVault, - signature: "setAdminImpl(address)", - args: [dVaultAdmin.address], - }, - // 3. Add the Curve AMO as a whitelisted address - { - contract: cVault, - signature: "addStrategyToMintWhitelist(address)", - args: [cCurveAMOStrategyProxy.address], - }, - ], - }; - } -); diff --git a/contracts/deploy/mainnet/130_update_votemarket_addresses.js b/contracts/deploy/mainnet/130_update_votemarket_addresses.js deleted file mode 100644 index c93db1d8b9..0000000000 --- a/contracts/deploy/mainnet/130_update_votemarket_addresses.js +++ /dev/null @@ -1,48 +0,0 @@ -const addresses = require("../../utils/addresses"); -const { deploymentWithGovernanceProposal } = require("../../utils/deploy"); - -module.exports = deploymentWithGovernanceProposal( - { - deployName: "130_update_votemarket_addresses", - forceDeploy: false, - //forceSkip: true, - reduceQueueTime: true, - deployerIsProposer: false, - proposalId: "", - }, - async () => { - const PoolBooster_OUSD = await ethers.getContractAt( - "CurvePoolBooster", - "0x514447A1Ef103f3cF4B0fE92A947F071239f2809" - ); - const PoolBooster_OETH = await ethers.getContractAt( - "CurvePoolBooster", - "0x7B5e7aDEBC2da89912BffE55c86675CeCE59803E" - ); - return { - name: "Adjust CampaignRemoteManager and VoteMarket addresses for TriOGN PoolBoosters", - actions: [ - { - contract: PoolBooster_OUSD, - signature: "setCampaignRemoteManager(address)", - args: [addresses.mainnet.CampaignRemoteManager], - }, - { - contract: PoolBooster_OETH, - signature: "setCampaignRemoteManager(address)", - args: [addresses.mainnet.CampaignRemoteManager], - }, - { - contract: PoolBooster_OUSD, - signature: "setVotemarket(address)", - args: [addresses.votemarket], - }, - { - contract: PoolBooster_OETH, - signature: "setVotemarket(address)", - args: [addresses.votemarket], - }, - ], - }; - } -); diff --git a/contracts/deploy/mainnet/131_ousd_usdc_curve_amo.js b/contracts/deploy/mainnet/131_ousd_usdc_curve_amo.js deleted file mode 100644 index 7d2bbbfb9d..0000000000 --- a/contracts/deploy/mainnet/131_ousd_usdc_curve_amo.js +++ /dev/null @@ -1,101 +0,0 @@ -const { deploymentWithGovernanceProposal } = require("../../utils/deploy"); - -const { - deployWithConfirmation, - withConfirmation, -} = require("../../utils/deploy"); -const addresses = require("../../utils/addresses"); -const { oethUnits } = require("../../test/helpers"); - -module.exports = deploymentWithGovernanceProposal( - { - deployName: "131_ousd_usdc_curve_amo", - forceDeploy: false, - //forceSkip: true, - reduceQueueTime: true, - deployerIsProposer: false, - proposalId: - "25500112647750821066059988048824352945507970558859648817105366239943827337543", - }, - async ({ ethers }) => { - const { deployerAddr, strategistAddr } = await getNamedAccounts(); - const sDeployer = await ethers.provider.getSigner(deployerAddr); - - const cOracleRouter = await ethers.getContract("OracleRouter"); - await cOracleRouter.cacheDecimals(addresses.mainnet.USDT); - await cOracleRouter.cacheDecimals(addresses.mainnet.USDC); - - // Deploy Base Curve AMO proxy - const cOUSDProxy = await ethers.getContract("OUSDProxy"); - const cOUSDVaultProxy = await ethers.getContract("VaultProxy"); - const cOUSDVaultAdmin = await ethers.getContractAt( - "VaultAdmin", - cOUSDVaultProxy.address - ); - - const dOUSDCurveAMOProxy = await deployWithConfirmation( - "OUSDCurveAMOProxy", - [] - ); - - const cOUSDCurveAMOProxy = await ethers.getContractAt( - "OUSDCurveAMOProxy", - dOUSDCurveAMOProxy.address - ); - - // Deploy Base Curve AMO implementation - const dOUSDCurveAMO = await deployWithConfirmation("CurveAMOStrategy", [ - [addresses.mainnet.curve.OUSD_USDC.pool, cOUSDVaultProxy.address], - cOUSDProxy.address, - addresses.mainnet.USDC, - addresses.mainnet.curve.OUSD_USDC.gauge, - addresses.mainnet.CRVMinter, - ]); - const cOUSDCurveAMOImpl = await ethers.getContract("CurveAMOStrategy"); - - // Initialize Base Curve AMO implementation - const initData = cOUSDCurveAMOImpl.interface.encodeFunctionData( - "initialize(address[],uint256)", - [[addresses.mainnet.CRV], oethUnits("0.002")] - ); - - await withConfirmation( - // prettier-ignore - cOUSDCurveAMOProxy - .connect(sDeployer)["initialize(address,address,bytes)"]( - dOUSDCurveAMO.address, - addresses.mainnet.Timelock, - initData - ) - ); - - const cOUSDCurveAMO = await ethers.getContractAt( - "CurveAMOStrategy", - cOUSDCurveAMOProxy.address - ); - - return { - name: "Add Curve AMO Strategy to OUSD Vault", - actions: [ - // Approve strategy on vault - { - contract: cOUSDVaultAdmin, - signature: "approveStrategy(address)", - args: [cOUSDCurveAMOProxy.address], - }, - // Add strategy to mint whitelist - { - contract: cOUSDVaultAdmin, - signature: "setOusdMetaStrategy(address)", - args: [cOUSDCurveAMOProxy.address], - }, - // Set strategist as harvester - { - contract: cOUSDCurveAMO, - signature: "setHarvesterAddress(address)", - args: [strategistAddr], - }, - ], - }; - } -); diff --git a/contracts/deploy/mainnet/133_omnichain_adapter.js b/contracts/deploy/mainnet/133_omnichain_adapter.js deleted file mode 100644 index 69ae05c433..0000000000 --- a/contracts/deploy/mainnet/133_omnichain_adapter.js +++ /dev/null @@ -1,30 +0,0 @@ -const { - deploymentWithGovernanceProposal, - deployWithConfirmation, -} = require("../../utils/deploy"); - -const addresses = require("../../utils/addresses"); - -module.exports = deploymentWithGovernanceProposal( - { - deployName: "133_omnichain_adapter", - }, - async ({ ethers }) => { - const { deployerAddr } = await getNamedAccounts(); - - const cWOETHProxy = await ethers.getContract("WOETHProxy"); - - // NOTE: For now, deployer is the governor to test things out - const dOmnichainMainnetAdapter = await deployWithConfirmation( - "OmnichainMainnetAdapter", - [cWOETHProxy.address, addresses.mainnet.LayerZeroEndpointV2, deployerAddr] - ); - - console.log( - "OmnichainMainnetAdapter address:", - dOmnichainMainnetAdapter.address - ); - - return {}; - } -); diff --git a/contracts/deploy/mainnet/134_vault_woeth_upgrade.js b/contracts/deploy/mainnet/134_vault_woeth_upgrade.js deleted file mode 100644 index 850b9d8267..0000000000 --- a/contracts/deploy/mainnet/134_vault_woeth_upgrade.js +++ /dev/null @@ -1,83 +0,0 @@ -const { - deploymentWithGovernanceProposal, - deployWithConfirmation, -} = require("../../utils/deploy"); -const { parseUnits } = require("ethers/lib/utils.js"); -const addresses = require("../../utils/addresses"); - -module.exports = deploymentWithGovernanceProposal( - { - deployName: "134_vault_woeth_upgrade", - proposalId: - "49172722703369984622112561793798089212723378488321724356488307408039828925801", - }, - async ({ ethers }) => { - const cOETHVaultProxy = await ethers.getContract("OETHVaultProxy"); - const cwOETHProxy = await ethers.getContract("WOETHProxy"); - const cOETHProxy = await ethers.getContract("OETHProxy"); - - const cOETHVault = await ethers.getContractAt( - "IVault", - cOETHVaultProxy.address - ); - - // Deploy new implementation - const dOETHVaultCore = await deployWithConfirmation("OETHVaultCore", [ - addresses.mainnet.WETH, - ]); - const dOETHVaultAdmin = await deployWithConfirmation("OETHVaultAdmin", [ - addresses.mainnet.WETH, - ]); - - const dwOETH = await deployWithConfirmation("WOETH", [ - cOETHProxy.address, // OETH token - ]); - - const cwOETH = await ethers.getContractAt("WOETH", cwOETHProxy.address); - - // ---------------- - // Governance Actions - // ---------------- - return { - name: "Add rate limiting to Origin Vault and upgrade WOETH", - actions: [ - // 1. Upgrade Vault proxy to VaultCore - { - contract: cOETHVaultProxy, - signature: "upgradeTo(address)", - args: [dOETHVaultCore.address], - }, - // 2. Set the VaultAdmin - { - contract: cOETHVault, - signature: "setAdminImpl(address)", - args: [dOETHVaultAdmin.address], - }, - // 3. Default to a short dripper, since currently we are running zero dripper. - { - contract: cOETHVault, - signature: "setDripDuration(uint256)", - args: [4 * 60 * 60], - }, - // 4. Default to a 20% APR rebase rate cap - { - contract: cOETHVault, - signature: "setRebaseRateMax(uint256)", - args: [parseUnits("20", 18)], - }, - { - // 5. Upgrade wOETHb proxy - contract: cwOETHProxy, - signature: "upgradeTo(address)", - args: [dwOETH.address], - }, - // 6. Run the second initializer - { - contract: cwOETH, - signature: "initialize2()", - args: [], - }, - ], - }; - } -); diff --git a/contracts/deploy/mainnet/135_vault_wousd_upgrade.js b/contracts/deploy/mainnet/135_vault_wousd_upgrade.js deleted file mode 100644 index 6dc8a0ab9b..0000000000 --- a/contracts/deploy/mainnet/135_vault_wousd_upgrade.js +++ /dev/null @@ -1,81 +0,0 @@ -const { - deploymentWithGovernanceProposal, - deployWithConfirmation, -} = require("../../utils/deploy"); -const { parseUnits } = require("ethers/lib/utils.js"); - -module.exports = deploymentWithGovernanceProposal( - { - deployName: "135_vault_wousd_upgrade", - proposalId: - "62736766423374672768580940310861921323283907575465980811344596309308068769219", - }, - async ({ ethers }) => { - const cVaultProxy = await ethers.getContract("VaultProxy"); - const cwOUSDProxy = await ethers.getContract("WrappedOUSDProxy"); - const cOUSDProxy = await ethers.getContract("OUSDProxy"); - - const cOUSDVault = await ethers.getContractAt( - "IVault", - cVaultProxy.address - ); - - // Deploy new implementation - const dOUSDVaultCore = await deployWithConfirmation("VaultCore", []); - const dVaultAdmin = await deployWithConfirmation("VaultAdmin", []); - - const dwOUSD = await deployWithConfirmation("WrappedOusd", [ - cOUSDProxy.address, // OUSD token - ]); - - const cwOUSD = await ethers.getContractAt( - "WrappedOusd", - cwOUSDProxy.address - ); - - // ---------------- - // Governance Actions - // ---------------- - return { - name: "Add rate limiting to Origin Vault and upgrade Wrapped OUSD", - actions: [ - // 1. Upgrade Vault proxy to VaultCore - { - contract: cVaultProxy, - signature: "upgradeTo(address)", - args: [dOUSDVaultCore.address], - }, - // 2. Set the VaultAdmin - { - contract: cOUSDVault, - signature: "setAdminImpl(address)", - args: [dVaultAdmin.address], - }, - // 3. Default to a short dripper, since currently we are running zero dripper. - { - contract: cOUSDVault, - signature: "setDripDuration(uint256)", - args: [4 * 60 * 60], - }, - // 4. Default to a 20% APR rebase rate cap - { - contract: cOUSDVault, - signature: "setRebaseRateMax(uint256)", - args: [parseUnits("20", 18)], - }, - { - // 5. Upgrade wOUSD proxy - contract: cwOUSDProxy, - signature: "upgradeTo(address)", - args: [dwOUSD.address], - }, - // 6. Run the second initializer - { - contract: cwOUSD, - signature: "initialize2()", - args: [], - }, - ], - }; - } -); diff --git a/contracts/deploy/mainnet/136_upgrade_morpho_strategies.js b/contracts/deploy/mainnet/136_upgrade_morpho_strategies.js deleted file mode 100644 index 7d73ec221a..0000000000 --- a/contracts/deploy/mainnet/136_upgrade_morpho_strategies.js +++ /dev/null @@ -1,83 +0,0 @@ -const addresses = require("../../utils/addresses"); -const { deploymentWithGovernanceProposal } = require("../../utils/deploy"); - -module.exports = deploymentWithGovernanceProposal( - { - deployName: "136_upgrade_morpho_strategies", - forceDeploy: false, - // forceSkip: true, - // reduceQueueTime: true, - deployerIsProposer: false, - proposalId: - "102952869103112185385289294460650789441590856926098847130687971800657890659280", - }, - async ({ deployWithConfirmation }) => { - // Current OUSD Vault contracts - const cVaultProxy = await ethers.getContract("VaultProxy"); - const dMorphoSteakhouseUSDCStrategyProxy = await ethers.getContract( - "MetaMorphoStrategyProxy" - ); - const dMorphoGauntletPrimeUSDCStrategyProxy = await ethers.getContract( - "MorphoGauntletPrimeUSDCStrategyProxy" - ); - const dMorphoGauntletPrimeUSDTStrategyProxy = await ethers.getContract( - "MorphoGauntletPrimeUSDTStrategyProxy" - ); - - // Deployer Actions - // ---------------- - - // 1. Deploy new Generalized4626Strategy contract for Morpho Steakhouse USDC - const dMorphoSteakhouseUSDCStrategyImpl = await deployWithConfirmation( - "Generalized4626Strategy", - [ - [addresses.mainnet.MorphoSteakhouseUSDCVault, cVaultProxy.address], - addresses.mainnet.USDC, - ] - ); - - // 2. Deploy new Generalized4626Strategy contract for Morpho Gauntlet Prime USDC - const dMorphoGauntletPrimeUSDCStrategyImpl = await deployWithConfirmation( - "Generalized4626Strategy", - [ - [addresses.mainnet.MorphoGauntletPrimeUSDCVault, cVaultProxy.address], - addresses.mainnet.USDC, - ] - ); - - // 2. Deploy new Generalized4626Strategy contract for Morpho Gauntlet Prime USDT - const dMorphoGauntletPrimeUSDTStrategyImpl = await deployWithConfirmation( - "Generalized4626USDTStrategy", - [ - [addresses.mainnet.MorphoGauntletPrimeUSDTVault, cVaultProxy.address], - addresses.mainnet.USDT, - ] - ); - - // Governance Actions - // ---------------- - return { - name: "Upgrade Morpho Steakhouse and Gauntlet Prime Strategies", - actions: [ - { - // 1. Upgrade Morpho Steakhouse USDC Strategy - contract: dMorphoSteakhouseUSDCStrategyProxy, - signature: "upgradeTo(address)", - args: [dMorphoSteakhouseUSDCStrategyImpl.address], - }, - { - // 1. Upgrade Morpho Steakhouse USDC Strategy - contract: dMorphoGauntletPrimeUSDCStrategyProxy, - signature: "upgradeTo(address)", - args: [dMorphoGauntletPrimeUSDCStrategyImpl.address], - }, - { - // 1. Upgrade Morpho Steakhouse USDC Strategy - contract: dMorphoGauntletPrimeUSDTStrategyProxy, - signature: "upgradeTo(address)", - args: [dMorphoGauntletPrimeUSDTStrategyImpl.address], - }, - ], - }; - } -); diff --git a/contracts/deploy/mainnet/137_oeth_weth_curve_amo.js b/contracts/deploy/mainnet/137_oeth_weth_curve_amo.js deleted file mode 100644 index 0e4fe0024d..0000000000 --- a/contracts/deploy/mainnet/137_oeth_weth_curve_amo.js +++ /dev/null @@ -1,97 +0,0 @@ -const { deploymentWithGovernanceProposal } = require("../../utils/deploy"); - -const { - deployWithConfirmation, - withConfirmation, -} = require("../../utils/deploy"); -const addresses = require("../../utils/addresses"); -const { oethUnits } = require("../../test/helpers"); - -module.exports = deploymentWithGovernanceProposal( - { - deployName: "137_oeth_weth_curve_amo", - forceDeploy: false, - //forceSkip: true, - reduceQueueTime: true, - deployerIsProposer: false, - proposalId: - "56250259650123444799492076755994707271232704725960146040626637955232036241797", - }, - async ({ ethers }) => { - const { deployerAddr, strategistAddr } = await getNamedAccounts(); - const sDeployer = await ethers.provider.getSigner(deployerAddr); - - const cOETHProxy = await ethers.getContract("OETHProxy"); - const cOETHVaultProxy = await ethers.getContract("OETHVaultProxy"); - const cOETHVaultAdmin = await ethers.getContractAt( - "OETHVaultAdmin", - cOETHVaultProxy.address - ); - - // Deploy Base Curve AMO proxy - const dOETHCurveAMOProxy = await deployWithConfirmation( - "OETHCurveAMOProxy", - [] - ); - - const cOETHCurveAMOProxy = await ethers.getContractAt( - "OETHCurveAMOProxy", - dOETHCurveAMOProxy.address - ); - - // Deploy Base Curve AMO implementation - const dOETHCurveAMO = await deployWithConfirmation("CurveAMOStrategy", [ - [addresses.mainnet.curve.OETH_WETH.pool, cOETHVaultProxy.address], - cOETHProxy.address, - addresses.mainnet.WETH, - addresses.mainnet.curve.OETH_WETH.gauge, - addresses.mainnet.CRVMinter, - ]); - const cOETHCurveAMOImpl = await ethers.getContract("CurveAMOStrategy"); - - // Initialize Base Curve AMO implementation - const initData = cOETHCurveAMOImpl.interface.encodeFunctionData( - "initialize(address[],uint256)", - [[addresses.mainnet.CRV], oethUnits("0.002")] - ); - - await withConfirmation( - // prettier-ignore - cOETHCurveAMOProxy - .connect(sDeployer)["initialize(address,address,bytes)"]( - dOETHCurveAMO.address, - addresses.mainnet.Timelock, - initData - ) - ); - - const cOETHCurveAMO = await ethers.getContractAt( - "CurveAMOStrategy", - cOETHCurveAMOProxy.address - ); - - return { - name: "Add new Curve AMO Strategy to OETH Vault", - actions: [ - // Approve strategy on vault - { - contract: cOETHVaultAdmin, - signature: "approveStrategy(address)", - args: [cOETHCurveAMOProxy.address], - }, - // Add strategy to mint whitelist - { - contract: cOETHVaultAdmin, - signature: "addStrategyToMintWhitelist(address)", - args: [cOETHCurveAMOProxy.address], - }, - // Set strategist as harvester - { - contract: cOETHCurveAMO, - signature: "setHarvesterAddress(address)", - args: [strategistAddr], - }, - ], - }; - } -); diff --git a/contracts/deploy/mainnet/138_buyback_deprecation.js b/contracts/deploy/mainnet/138_buyback_deprecation.js deleted file mode 100644 index 1553f987ab..0000000000 --- a/contracts/deploy/mainnet/138_buyback_deprecation.js +++ /dev/null @@ -1,93 +0,0 @@ -const addresses = require("../../utils/addresses"); -const { deploymentWithGovernanceProposal } = require("../../utils/deploy"); - -module.exports = deploymentWithGovernanceProposal( - { - deployName: "138_buyback_deprecation", - forceDeploy: false, - // forceSkip: true, - // reduceQueueTime: true, - deployerIsProposer: false, - proposalId: - "95818130106833559554842628131764707258168638288997879437261600658785882386480", - }, - async () => { - const cLegacyBuyback = await ethers.getContractAt( - "OUSDBuyback", - "0x77314eb392b2be47c014cde0706908b3307ad6a9" - ); - const ogv = await ethers.getContractAt("IERC20", addresses.mainnet.OGV); - const ogvInLegacyBuyback = await ogv.balanceOf(cLegacyBuyback.address); - - console.log("OGV in Legacy Buyback:", ogvInLegacyBuyback.toString()); - - const cOUSDBuybackProxy = await ethers.getContract("BuybackProxy"); - const cOUSDBuyback = await ethers.getContractAt( - "OUSDBuyback", - cOUSDBuybackProxy.address - ); - const cOETHBuybackProxy = await ethers.getContract("OETHBuybackProxy"); - const cOETHBuyback = await ethers.getContractAt( - "OETHBuyback", - cOETHBuybackProxy.address - ); - const cARMBuybackProxy = await ethers.getContract("ARMBuybackProxy"); - const cARMBuyback = await ethers.getContractAt( - "ARMBuyback", - cARMBuybackProxy.address - ); - - const cOUSDVaultProxy = await ethers.getContract("VaultProxy"); - const cOUSDVault = await ethers.getContractAt( - "IVault", - cOUSDVaultProxy.address - ); - const cOETHVaultProxy = await ethers.getContract("OETHVaultProxy"); - const cOETHVault = await ethers.getContractAt( - "IVault", - cOETHVaultProxy.address - ); - - return { - name: `Update fee recipient and change ownership of buyback contracts - -- Sets the trustee address to the multichain Guardian for OUSD and OETH vaults -- Changes the governor of existing and legacy buyback contracts to the multichain Guardian to enable updated buyback operations`, - actions: [ - { - // Send all OUSD fees to the Multichain Strategist - contract: cOUSDVault, - signature: "setTrusteeAddress(address)", - args: [addresses.multichainBuybackOperator], - }, - { - // Send all OETH fees to the Multichain Strategist - contract: cOETHVault, - signature: "setTrusteeAddress(address)", - args: [addresses.multichainBuybackOperator], - }, - // Transfer governance from all buyback contracts to the Multichain Strategist - { - contract: cOUSDBuyback, - signature: "transferGovernance(address)", - args: [addresses.multichainStrategist], - }, - { - contract: cOETHBuyback, - signature: "transferGovernance(address)", - args: [addresses.multichainStrategist], - }, - { - contract: cARMBuyback, - signature: "transferGovernance(address)", - args: [addresses.multichainStrategist], - }, - { - contract: cLegacyBuyback, - signature: "transferGovernance(address)", - args: [addresses.multichainStrategist], - }, - ], - }; - } -); diff --git a/contracts/deploy/mainnet/139_deprecate_ousd_harvester.js b/contracts/deploy/mainnet/139_deprecate_ousd_harvester.js deleted file mode 100644 index 279ce7839c..0000000000 --- a/contracts/deploy/mainnet/139_deprecate_ousd_harvester.js +++ /dev/null @@ -1,72 +0,0 @@ -const { deploymentWithGovernanceProposal } = require("../../utils/deploy"); - -const addresses = require("../../utils/addresses"); - -module.exports = deploymentWithGovernanceProposal( - { - deployName: "139_deprecate_ousd_harvester", - forceDeploy: false, - forceSkip: false, - proposalId: - "47645042373197517899120935227572898626533275576180817805400930508873896963074", - }, - async ({ ethers }) => { - const cHarvesterProxy = await ethers.getContract("HarvesterProxy"); - const cHarvester = await ethers.getContractAt( - "Harvester", - cHarvesterProxy.address - ); - - const cMetaMorphoStrategyProxy = await ethers.getContract( - "MetaMorphoStrategyProxy" - ); - const cMetaMorphoStrategy = await ethers.getContractAt( - "Generalized4626Strategy", - cMetaMorphoStrategyProxy.address - ); - - const cMorphoGauntletPrimeUSDCStrategyProxy = await ethers.getContract( - "MorphoGauntletPrimeUSDCStrategyProxy" - ); - const cMorphoGauntletPrimeUSDCStrategy = await ethers.getContractAt( - "Generalized4626Strategy", - cMorphoGauntletPrimeUSDCStrategyProxy.address - ); - - const cMorphoGauntletPrimeUSDTStrategyProxy = await ethers.getContract( - "MorphoGauntletPrimeUSDTStrategyProxy" - ); - const cMorphoGauntletPrimeUSDTStrategy = await ethers.getContractAt( - "Generalized4626USDTStrategy", - cMorphoGauntletPrimeUSDTStrategyProxy.address - ); - - return { - name: `Deprecate OUSD Harvester -- Makes the multichain Gaurdian the harvester for OUSD strategies. -- Transfers governance of harvester contract to the multichain strategist to move out funds.`, - actions: [ - { - contract: cMetaMorphoStrategy, - signature: "setHarvesterAddress(address)", - args: [addresses.multichainStrategist], - }, - { - contract: cMorphoGauntletPrimeUSDCStrategy, - signature: "setHarvesterAddress(address)", - args: [addresses.multichainStrategist], - }, - { - contract: cMorphoGauntletPrimeUSDTStrategy, - signature: "setHarvesterAddress(address)", - args: [addresses.multichainStrategist], - }, - { - contract: cHarvester, - signature: "transferGovernance(address)", - args: [addresses.multichainStrategist], - }, - ], - }; - } -); diff --git a/contracts/deploy/mainnet/140_remove_1st_native_ssv_staking.js b/contracts/deploy/mainnet/140_remove_1st_native_ssv_staking.js deleted file mode 100644 index 3416cdd800..0000000000 --- a/contracts/deploy/mainnet/140_remove_1st_native_ssv_staking.js +++ /dev/null @@ -1,75 +0,0 @@ -const { deploymentWithGovernanceProposal } = require("../../utils/deploy"); -const addresses = require("../../utils/addresses"); - -module.exports = deploymentWithGovernanceProposal( - { - deployName: "140_remove_1st_native_ssv_staking", - forceDeploy: false, - //forceSkip: true, - reduceQueueTime: true, - deployerIsProposer: false, - proposalId: - "58072692907258896292503342211403681374811708763269462372642180263959505348028", - }, - async ({ deployWithConfirmation, ethers }) => { - // Current contracts - const cVaultProxy = await ethers.getContract("OETHVaultProxy"); - const cVaultAdmin = await ethers.getContractAt( - "OETHVaultAdmin", - cVaultProxy.address - ); - - // Deployer Actions - // ---------------- - - // 1. Fetch the first native strategy proxy - const cNativeStakingStrategyProxy = await ethers.getContract( - "NativeStakingSSVStrategyProxy" - ); - - const cFeeAccumulatorProxy = await ethers.getContract( - "NativeStakingFeeAccumulatorProxy" - ); - - // 2. Deploy the new Native Staking Strategy implementation - const dNativeStakingStrategyImpl = await deployWithConfirmation( - "NativeStakingSSVStrategy", - [ - [addresses.zero, cVaultProxy.address], //_baseConfig - addresses.mainnet.WETH, // wethAddress - addresses.mainnet.SSV, // ssvToken - addresses.mainnet.SSVNetwork, // ssvNetwork - 500, // maxValidators - cFeeAccumulatorProxy.address, // feeAccumulator - addresses.mainnet.beaconChainDepositContract, // beacon chain deposit contract - ] - ); - - // Governance Actions - // ---------------- - return { - name: `Remove the first native SSV staking strategy from the OETH Vault and claim the remaining SSV from the cluster`, - actions: [ - // 1. Remove strategy from vault - { - contract: cVaultAdmin, - signature: "removeStrategy(address)", - args: [cNativeStakingStrategyProxy.address], - }, - // 2. Upgrade the Native Staking Strategy - { - contract: cNativeStakingStrategyProxy, - signature: "upgradeTo(address)", - args: [dNativeStakingStrategyImpl.address], - }, - // 3. Transfer governance to the Guardian - { - contract: cNativeStakingStrategyProxy, - signature: "transferGovernance(address)", - args: [addresses.multichainStrategist], - }, - // The Guardian can then retrieve the SSV left in the cluster by calling claimGovernance, withdrawSSV and transferToken - ], - }; - } -); diff --git a/contracts/deploy/mainnet/141_claimrewards_module.js b/contracts/deploy/mainnet/141_claimrewards_module.js deleted file mode 100644 index 19a7b63b48..0000000000 --- a/contracts/deploy/mainnet/141_claimrewards_module.js +++ /dev/null @@ -1,74 +0,0 @@ -const { deploymentWithGovernanceProposal } = require("../../utils/deploy"); -const addresses = require("../../utils/addresses"); -const { isFork } = require("../../utils/hardhat-helpers"); -const { impersonateAndFund } = require("../../utils/signers"); - -module.exports = deploymentWithGovernanceProposal( - { - deployName: "141_claimrewards_module", - forceDeploy: false, - reduceQueueTime: true, - proposalId: "", - }, - async ({ deployWithConfirmation, withConfirmation }) => { - const cOUSDCurveAMOProxy = await ethers.getContract("OUSDCurveAMOProxy"); - const cOETHCurveAMOProxy = await ethers.getContract("OETHCurveAMOProxy"); - const cMorphoSteakhouseUSDCStrategyProxy = await ethers.getContract( - "MetaMorphoStrategyProxy" - ); - const cMorphoGauntletPrimeUSDCStrategyProxy = await ethers.getContract( - "MorphoGauntletPrimeUSDCStrategyProxy" - ); - const cMorphoGauntletPrimeUSDTStrategyProxy = await ethers.getContract( - "MorphoGauntletPrimeUSDTStrategyProxy" - ); - - await deployWithConfirmation("ClaimStrategyRewardsSafeModule", [ - addresses.multichainStrategist, - [ - cOUSDCurveAMOProxy.address, - cOETHCurveAMOProxy.address, - cMorphoSteakhouseUSDCStrategyProxy.address, - cMorphoGauntletPrimeUSDCStrategyProxy.address, - cMorphoGauntletPrimeUSDTStrategyProxy.address, - ], - ]); - const cClaimStrategyRewardsSafeModule = await ethers.getContract( - "ClaimStrategyRewardsSafeModule" - ); - - console.log( - "ClaimStrategyRewardsSafeModule deployed to", - cClaimStrategyRewardsSafeModule.address - ); - - if (isFork) { - const safeSigner = await impersonateAndFund( - addresses.multichainStrategist - ); - - const cSafe = await ethers.getContractAt( - ["function enableModule(address module) external"], - addresses.multichainStrategist - ); - - await withConfirmation( - cSafe - .connect(safeSigner) - .enableModule(cClaimStrategyRewardsSafeModule.address) - ); - - console.log("Enabled module"); - - await withConfirmation( - cClaimStrategyRewardsSafeModule.connect(safeSigner).claimRewards(true) - ); - - console.log("Claimed rewards"); - } - - return { - actions: [], - }; - } -); diff --git a/contracts/deploy/mainnet/142_bridge_helper_module.js b/contracts/deploy/mainnet/142_bridge_helper_module.js deleted file mode 100644 index db2cfd71d4..0000000000 --- a/contracts/deploy/mainnet/142_bridge_helper_module.js +++ /dev/null @@ -1,49 +0,0 @@ -const { deploymentWithGovernanceProposal } = require("../../utils/deploy"); -const addresses = require("../../utils/addresses"); -const { isFork } = require("../../utils/hardhat-helpers"); -const { impersonateAndFund } = require("../../utils/signers"); - -module.exports = deploymentWithGovernanceProposal( - { - deployName: "142_bridge_helper_module", - forceDeploy: false, - reduceQueueTime: true, - proposalId: "", - }, - async ({ deployWithConfirmation, withConfirmation }) => { - await deployWithConfirmation("EthereumBridgeHelperModule", [ - addresses.multichainStrategist, - ]); - const cEthereumBridgeHelperModule = await ethers.getContract( - "EthereumBridgeHelperModule" - ); - - console.log( - "EthereumBridgeHelperModule deployed to", - cEthereumBridgeHelperModule.address - ); - - if (isFork) { - const safeSigner = await impersonateAndFund( - addresses.multichainStrategist - ); - - const cSafe = await ethers.getContractAt( - ["function enableModule(address module) external"], - addresses.multichainStrategist - ); - - await withConfirmation( - cSafe - .connect(safeSigner) - .enableModule(cEthereumBridgeHelperModule.address) - ); - - console.log("Enabled module on fork"); - } - - return { - actions: [], - }; - } -); diff --git a/contracts/deploy/mainnet/143_bridge_helper_module_upgrade.js b/contracts/deploy/mainnet/143_bridge_helper_module_upgrade.js deleted file mode 100644 index c619386117..0000000000 --- a/contracts/deploy/mainnet/143_bridge_helper_module_upgrade.js +++ /dev/null @@ -1,49 +0,0 @@ -const { deploymentWithGovernanceProposal } = require("../../utils/deploy"); -const addresses = require("../../utils/addresses"); -const { isFork } = require("../../utils/hardhat-helpers"); -const { impersonateAndFund } = require("../../utils/signers"); - -module.exports = deploymentWithGovernanceProposal( - { - deployName: "143_bridge_helper_module_upgrade", - forceDeploy: false, - reduceQueueTime: true, - proposalId: "", - }, - async ({ deployWithConfirmation, withConfirmation }) => { - await deployWithConfirmation("EthereumBridgeHelperModule", [ - addresses.multichainStrategist, - ]); - const cEthereumBridgeHelperModule = await ethers.getContract( - "EthereumBridgeHelperModule" - ); - - console.log( - "EthereumBridgeHelperModule deployed to", - cEthereumBridgeHelperModule.address - ); - - if (isFork) { - const safeSigner = await impersonateAndFund( - addresses.multichainStrategist - ); - - const cSafe = await ethers.getContractAt( - ["function enableModule(address module) external"], - addresses.multichainStrategist - ); - - await withConfirmation( - cSafe - .connect(safeSigner) - .enableModule(cEthereumBridgeHelperModule.address) - ); - - console.log("Enabled module on fork"); - } - - return { - actions: [], - }; - } -); diff --git a/contracts/deploy/mainnet/144_ousd_oeth_upgrade_EIP7702.js b/contracts/deploy/mainnet/144_ousd_oeth_upgrade_EIP7702.js deleted file mode 100644 index 14db6eef4c..0000000000 --- a/contracts/deploy/mainnet/144_ousd_oeth_upgrade_EIP7702.js +++ /dev/null @@ -1,42 +0,0 @@ -const { deploymentWithGovernanceProposal } = require("../../utils/deploy"); - -module.exports = deploymentWithGovernanceProposal( - { - deployName: "144_ousd_oeth_upgrade_EIP7702", - forceDeploy: false, - reduceQueueTime: true, - deployerIsProposer: false, - proposalId: "", - }, - async ({ deployWithConfirmation }) => { - // Deployer Actions - // ---------------- - - // 1. Deploy new OUSD implementation without storage slot checks - const dOUSD = await deployWithConfirmation("OUSD", [], "OUSD", true); - const cOUSDProxy = await ethers.getContract("OUSDProxy"); - - // 2. Deploy new OETH implementation without storage slot checks - const dOETH = await deployWithConfirmation("OETH", [], "OETH", true); - const cOETHProxy = await ethers.getContract("OETHProxy"); - - // Governance Actions - // ---------------- - return { - name: "Upgrade OUSD/OETH token contract", - actions: [ - // 1. Upgrade the OUSD proxy to the new implementation - { - contract: cOUSDProxy, - signature: "upgradeTo(address)", - args: [dOUSD.address], - }, - { - contract: cOETHProxy, - signature: "upgradeTo(address)", - args: [dOETH.address], - }, - ], - }; - } -); diff --git a/contracts/deploy/mainnet/145_deploy_xogn_rewards_module.js b/contracts/deploy/mainnet/145_deploy_xogn_rewards_module.js deleted file mode 100644 index efc2855f22..0000000000 --- a/contracts/deploy/mainnet/145_deploy_xogn_rewards_module.js +++ /dev/null @@ -1,45 +0,0 @@ -const { deploymentWithGovernanceProposal } = require("../../utils/deploy"); - -module.exports = deploymentWithGovernanceProposal( - { - deployName: "145_deploy_xogn_rewards_module", - forceDeploy: false, - reduceQueueTime: true, - deployerIsProposer: false, - proposalId: "", - }, - async ({ deployWithConfirmation }) => { - const safeAddresses = [ - "0x5c8228e709D7F91209DE898F6a7B8c6035A7B78f", - "0x69497A2A170c138876F05Df01bFfDd5C4b651CF2", - "0x684b38997afbBBC055e0BEB6d536686Ebd171bdB", - "0xe555EFA16d38747F9e496926b576FD1ebD31DeCa", - "0x6E75645EeDCCCAA0f472323Afce8f82B875C8CB9", - ]; - - let i = 0; - for (const safeAddress of safeAddresses) { - i++; - const moduleName = `CollectXOGNRewardsModule${i}`; - await deployWithConfirmation( - moduleName, - [ - safeAddress, - // Defender Relayer - "0x4b91827516f79d6F6a1F292eD99671663b09169a", - ], - "CollectXOGNRewardsModule" - ); - const cCollectXOGNRewardsModule = await ethers.getContract(moduleName); - - console.log( - `CollectXOGNRewardsModule${i} (for ${safeAddress}) deployed to`, - cCollectXOGNRewardsModule.address - ); - } - - return { - actions: [], - }; - } -); diff --git a/contracts/deploy/mainnet/146_morpho_strategies_reward_tokens.js b/contracts/deploy/mainnet/146_morpho_strategies_reward_tokens.js deleted file mode 100644 index 525ef161f0..0000000000 --- a/contracts/deploy/mainnet/146_morpho_strategies_reward_tokens.js +++ /dev/null @@ -1,75 +0,0 @@ -const { deploymentWithGovernanceProposal } = require("../../utils/deploy"); -const addresses = require("../../utils/addresses"); - -module.exports = deploymentWithGovernanceProposal( - { - deployName: "146_morpho_strategies_reward_tokens", - forceDeploy: false, - reduceQueueTime: true, - proposalId: "", - }, - async () => { - const cGauntletUSDCStrategyProxy = await ethers.getContract( - "MorphoGauntletPrimeUSDCStrategyProxy" - ); - - const cGauntletUSDTStrategyProxy = await ethers.getContract( - "MorphoGauntletPrimeUSDTStrategyProxy" - ); - - const cMetaMorphoStrategyProxy = await ethers.getContract( - "MetaMorphoStrategyProxy" - ); - - const cGauntletUSDCStrategy = await ethers.getContractAt( - "IStrategy", - cGauntletUSDCStrategyProxy.address - ); - - const cGauntletUSDTStrategy = await ethers.getContractAt( - "IStrategy", - cGauntletUSDTStrategyProxy.address - ); - - const cMetaMorphoStrategy = await ethers.getContractAt( - "IStrategy", - cMetaMorphoStrategyProxy.address - ); - - return { - name: "Change reward token addresses for Morpho Strategies", - actions: [ - { - contract: cGauntletUSDCStrategy, - signature: "setRewardTokenAddresses(address[])", - args: [ - [ - addresses.mainnet.MorphoToken, - addresses.mainnet.LegacyMorphoToken, - ], - ], - }, - { - contract: cGauntletUSDTStrategy, - signature: "setRewardTokenAddresses(address[])", - args: [ - [ - addresses.mainnet.MorphoToken, - addresses.mainnet.LegacyMorphoToken, - ], - ], - }, - { - contract: cMetaMorphoStrategy, - signature: "setRewardTokenAddresses(address[])", - args: [ - [ - addresses.mainnet.MorphoToken, - addresses.mainnet.LegacyMorphoToken, - ], - ], - }, - ], - }; - } -); diff --git a/contracts/deploy/mainnet/147_claim_rewards_module_upgrade.js b/contracts/deploy/mainnet/147_claim_rewards_module_upgrade.js deleted file mode 100644 index 648a49b099..0000000000 --- a/contracts/deploy/mainnet/147_claim_rewards_module_upgrade.js +++ /dev/null @@ -1,51 +0,0 @@ -const { deploymentWithGovernanceProposal } = require("../../utils/deploy"); -const addresses = require("../../utils/addresses"); - -module.exports = deploymentWithGovernanceProposal( - { - deployName: "147_claim_rewards_module_upgrade", - forceDeploy: false, - forceSkip: false, - reduceQueueTime: true, - proposalId: "", - }, - async ({ deployWithConfirmation }) => { - const cOUSDCurveAMOProxy = await ethers.getContract("OUSDCurveAMOProxy"); - const cOETHCurveAMOProxy = await ethers.getContract("OETHCurveAMOProxy"); - const cGauntletUSDCStrategyProxy = await ethers.getContract( - "MorphoGauntletPrimeUSDCStrategyProxy" - ); - const cGauntletUSDTStrategyProxy = await ethers.getContract( - "MorphoGauntletPrimeUSDTStrategyProxy" - ); - const cMetaMorphoStrategyProxy = await ethers.getContract( - "MetaMorphoStrategyProxy" - ); - - await deployWithConfirmation("ClaimStrategyRewardsSafeModule", [ - addresses.multichainStrategist, - // Defender Relayer - addresses.mainnet.validatorRegistrator, - [ - cOUSDCurveAMOProxy.address, - cOETHCurveAMOProxy.address, - cGauntletUSDCStrategyProxy.address, - cGauntletUSDTStrategyProxy.address, - cMetaMorphoStrategyProxy.address, - ], - ]); - - const cClaimStrategyRewardsSafeModule = await ethers.getContract( - "ClaimStrategyRewardsSafeModule" - ); - - console.log( - "cClaimStrategyRewardsSafeModule deployed to", - cClaimStrategyRewardsSafeModule.address - ); - - return { - actions: [], - }; - } -); diff --git a/contracts/deploy/mainnet/148_xogn_module_for_5_of_8_multisig.js b/contracts/deploy/mainnet/148_xogn_module_for_5_of_8_multisig.js deleted file mode 100644 index f9ceab3112..0000000000 --- a/contracts/deploy/mainnet/148_xogn_module_for_5_of_8_multisig.js +++ /dev/null @@ -1,37 +0,0 @@ -const addresses = require("../../utils/addresses"); -const { deploymentWithGovernanceProposal } = require("../../utils/deploy"); - -module.exports = deploymentWithGovernanceProposal( - { - deployName: "148_xogn_module_for_5_of_8_multisig", - forceDeploy: false, - reduceQueueTime: true, - deployerIsProposer: false, - proposalId: "", - }, - async ({ deployWithConfirmation }) => { - // 5 of 8 multisig - const safeAddress = addresses.mainnet.Guardian; - - const moduleName = `CollectXOGNRewardsModule6`; - await deployWithConfirmation( - moduleName, - [ - safeAddress, - // Defender Relayer - addresses.mainnet.validatorRegistrator, - ], - "CollectXOGNRewardsModule" - ); - const cCollectXOGNRewardsModule = await ethers.getContract(moduleName); - - console.log( - `${moduleName} (for ${safeAddress}) deployed to`, - cCollectXOGNRewardsModule.address - ); - - return { - actions: [], - }; - } -); diff --git a/contracts/deploy/mainnet/149_xogn_module_7.js b/contracts/deploy/mainnet/149_xogn_module_7.js deleted file mode 100644 index 5357f0215d..0000000000 --- a/contracts/deploy/mainnet/149_xogn_module_7.js +++ /dev/null @@ -1,36 +0,0 @@ -const addresses = require("../../utils/addresses"); -const { deploymentWithGovernanceProposal } = require("../../utils/deploy"); - -module.exports = deploymentWithGovernanceProposal( - { - deployName: "149_xogn_module_7", - forceDeploy: false, - reduceQueueTime: true, - deployerIsProposer: false, - proposalId: "", - }, - async ({ deployWithConfirmation }) => { - const safeAddress = "0xA2Cc2eAE69cBf04a3D5660bc3E689B035324Fc3F"; - - const moduleName = `CollectXOGNRewardsModule7`; - await deployWithConfirmation( - moduleName, - [ - safeAddress, - // Defender Relayer - addresses.mainnet.validatorRegistrator, - ], - "CollectXOGNRewardsModule" - ); - const cCollectXOGNRewardsModule = await ethers.getContract(moduleName); - - console.log( - `${moduleName} (for ${safeAddress}) deployed to`, - cCollectXOGNRewardsModule.address - ); - - return { - actions: [], - }; - } -); diff --git a/contracts/deploy/mainnet/150_vault_upgrade.js b/contracts/deploy/mainnet/150_vault_upgrade.js deleted file mode 100644 index 55679d00c0..0000000000 --- a/contracts/deploy/mainnet/150_vault_upgrade.js +++ /dev/null @@ -1,54 +0,0 @@ -const { - deploymentWithGovernanceProposal, - deployWithConfirmation, -} = require("../../utils/deploy"); -const addresses = require("../../utils/addresses"); - -module.exports = deploymentWithGovernanceProposal( - { - deployName: "150_vault_upgrade", - forceSkip: true, - //proposalId: "", - }, - async ({ ethers }) => { - const cOETHVaultProxy = await ethers.getContract("OETHVaultProxy"); - const cOETHHarvesterSimpleProxy = await ethers.getContract( - "OETHSimpleHarvesterProxy" - ); - - const cOETHHarvesterSimple = await ethers.getContractAt( - "OETHHarvesterSimple", - cOETHHarvesterSimpleProxy.address - ); - - // Deploy new implementation without storage slot checks because of the: - // - Renamed `dripper` to `_deprecated_dripper` - const dOETHVaultCore = await deployWithConfirmation( - "OETHVaultCore", - [addresses.mainnet.WETH], - "OETHVaultCore", - true - ); - - // ---------------- - // Governance Actions - // ---------------- - return { - name: "Upgrade VaultCore and set Vault as the recipient of the WETH rewards on the Simple harvester", - actions: [ - // 1. Upgrade VaultCore implementation - { - contract: cOETHVaultProxy, - signature: "upgradeTo(address)", - args: [dOETHVaultCore.address], - }, - // 2. Move the WETH rewards from the Dripper directly to the Vault - { - contract: cOETHHarvesterSimple, - signature: "setDripper(address)", - args: [cOETHVaultProxy.address], - }, - ], - }; - } -); diff --git a/contracts/deploy/mainnet/151_curve_pb_module.js b/contracts/deploy/mainnet/151_curve_pb_module.js deleted file mode 100644 index bef198acaa..0000000000 --- a/contracts/deploy/mainnet/151_curve_pb_module.js +++ /dev/null @@ -1,41 +0,0 @@ -const addresses = require("../../utils/addresses"); -const { deploymentWithGovernanceProposal } = require("../../utils/deploy"); - -module.exports = deploymentWithGovernanceProposal( - { - deployName: "151_curve_pb_module", - forceDeploy: false, - reduceQueueTime: true, - deployerIsProposer: false, - proposalId: "", - }, - async ({ deployWithConfirmation }) => { - const safeAddress = addresses.multichainStrategist; - - const moduleName = `CurvePoolBoosterBribesModule`; - await deployWithConfirmation( - moduleName, - [ - safeAddress, - // Defender Relayer - addresses.mainnet.validatorRegistrator, - [ - "0x1bc53929fF517531Da09EAa281c2375dE3a0AD2C", - "0x7B5e7aDEBC2da89912BffE55c86675CeCE59803E", - "0x514447A1Ef103f3cF4B0fE92A947F071239f2809", - ], - ], - "CurvePoolBoosterBribesModule" - ); - const cCurvePoolBoosterBribesModule = await ethers.getContract(moduleName); - - console.log( - `${moduleName} (for ${safeAddress}) deployed to`, - cCurvePoolBoosterBribesModule.address - ); - - return { - actions: [], - }; - } -); diff --git a/contracts/deploy/mainnet/152_pool_booster_setup.js b/contracts/deploy/mainnet/152_pool_booster_setup.js deleted file mode 100644 index aeccb223c8..0000000000 --- a/contracts/deploy/mainnet/152_pool_booster_setup.js +++ /dev/null @@ -1,96 +0,0 @@ -const addresses = require("../../utils/addresses"); -const { deploymentWithGovernanceProposal } = require("../../utils/deploy"); - -module.exports = deploymentWithGovernanceProposal( - { - deployName: "152_pool_booster_setup", - forceDeploy: false, - reduceQueueTime: true, - deployerIsProposer: false, - proposalId: - "88571309263461999046223385877946752282612145620560563525414048647990244142989", - }, - async ({ deployWithConfirmation, withConfirmation }) => { - const { deployerAddr } = await getNamedAccounts(); - const sDeployer = await ethers.provider.getSigner(deployerAddr); - - const oethProxy = await ethers.getContract("OETHProxy"); - const oeth = await ethers.getContractAt("OETH", oethProxy.address); - - // --------------------------------------------------------------------------------------------------------- - // --- - // --- Deploy PoolBoostCentralRegistry - // --- - // --------------------------------------------------------------------------------------------------------- - - await deployWithConfirmation("PoolBoostCentralRegistryProxy"); - const cPoolBoostCentralRegistryProxy = await ethers.getContract( - "PoolBoostCentralRegistryProxy" - ); - - console.log( - `Pool boost central registry proxy deployed: ${cPoolBoostCentralRegistryProxy.address}` - ); - - const dPoolBoostCentralRegistry = await deployWithConfirmation( - "PoolBoostCentralRegistry", - [] - ); - console.log( - `Deployed Pool Boost Central Registry to ${dPoolBoostCentralRegistry.address}` - ); - - const cPoolBoostCentralRegistry = await ethers.getContractAt( - "PoolBoostCentralRegistry", - cPoolBoostCentralRegistryProxy.address - ); - - // prettier-ignore - await withConfirmation( - cPoolBoostCentralRegistryProxy - .connect(sDeployer)["initialize(address,address,bytes)"]( - dPoolBoostCentralRegistry.address, - addresses.mainnet.Timelock, - "0x" - ) - ); - console.log( - "Initialized PoolBoostCentralRegistry proxy and implementation" - ); - - // --------------------------------------------------------------------------------------------------------- - // --- - // --- Deploy PoolBoosterFactoryMerkl - // --- - // --------------------------------------------------------------------------------------------------------- - const dPoolBoosterFactoryMerkl = await deployWithConfirmation( - "PoolBoosterFactoryMerkl", - [ - oeth.address, - // so we can create a Merkl pool booster fast via a multichain strategist and kick off yield forwarding - addresses.multichainStrategist, - cPoolBoostCentralRegistryProxy.address, - addresses.mainnet.CampaignCreator, - ] - ); - const cPoolBoosterMerklFactory = await ethers.getContract( - "PoolBoosterFactoryMerkl" - ); - - console.log( - `Pool Booster Merkl Factory deployed to ${cPoolBoosterMerklFactory.address}` - ); - - return { - name: "Approve PoolBoosterFactoryMerkl", - actions: [ - { - // set the factory as an approved one - contract: cPoolBoostCentralRegistry, - signature: "approveFactory(address)", - args: [dPoolBoosterFactoryMerkl.address], - }, - ], - }; - } -); diff --git a/contracts/deploy/mainnet/153_beacon_root_testing.js b/contracts/deploy/mainnet/153_beacon_root_testing.js deleted file mode 100644 index f0368be016..0000000000 --- a/contracts/deploy/mainnet/153_beacon_root_testing.js +++ /dev/null @@ -1,24 +0,0 @@ -const { deploymentWithGovernanceProposal } = require("../../utils/deploy"); - -module.exports = deploymentWithGovernanceProposal( - { - deployName: "153_beacon_root_testing", - forceDeploy: false, - //forceSkip: true, - reduceQueueTime: true, - deployerIsProposer: false, - // proposalId: "", - }, - async ({ deployWithConfirmation }) => { - // 1. Deploy the MockBeaconRoots - const dMockBeaconRoots = await deployWithConfirmation("MockBeaconRoots"); - console.log(`Deployed MockBeaconRoots ${dMockBeaconRoots.address}`); - - // Governance Actions - // ---------------- - return { - name: "", - actions: [], - }; - } -); diff --git a/contracts/deploy/mainnet/154_upgrade_native_staking.js b/contracts/deploy/mainnet/154_upgrade_native_staking.js deleted file mode 100644 index 4a19eb5e0c..0000000000 --- a/contracts/deploy/mainnet/154_upgrade_native_staking.js +++ /dev/null @@ -1,62 +0,0 @@ -const { deploymentWithGovernanceProposal } = require("../../utils/deploy"); -const { deployCompoundingStakingSSVStrategy } = require("../deployActions"); -const addresses = require("../../utils/addresses"); - -module.exports = deploymentWithGovernanceProposal( - { - deployName: "154_upgrade_native_staking", - forceDeploy: false, - //forceSkip: true, - reduceQueueTime: true, - deployerIsProposer: false, - proposalId: - "99121594404373353367959271787303330322964429439529781330823574079193280812345", - }, - async ({ ethers }) => { - // Current contracts - const cVaultProxy = await ethers.getContract("OETHVaultProxy"); - const cVaultAdmin = await ethers.getContractAt( - "OETHVaultAdmin", - cVaultProxy.address - ); - // Deployer Actions - // ---------------- - - // 1. Deploy the new Compounding Staking Strategy contracts - const cCompoundingStakingStrategy = - await deployCompoundingStakingSSVStrategy(); - - // Governance Actions - // ---------------- - return { - name: `Deploy new Compounding Staking Strategy that uses compounding validators and Beacon Chain merkle proofs`, - actions: [ - // 1. Add new strategy to vault - { - contract: cVaultAdmin, - signature: "approveStrategy(address)", - args: [cCompoundingStakingStrategy.address], - }, - // 2. set harvester to the Defender Relayer - { - contract: cCompoundingStakingStrategy, - signature: "setHarvesterAddress(address)", - args: [addresses.mainnet.validatorRegistrator], - }, - // 3. set validator registrator to the Defender Relayer - { - contract: cCompoundingStakingStrategy, - signature: "setRegistrator(address)", - // The Defender Relayer - args: [addresses.mainnet.validatorRegistrator], - }, - // 4. set new Staking Strategy as the default strategy for WETH - { - contract: cVaultAdmin, - signature: "setAssetDefaultStrategy(address,address)", - args: [addresses.mainnet.WETH, cCompoundingStakingStrategy.address], - }, - ], - }; - } -); diff --git a/contracts/deploy/mainnet/155_oeth_zapper.js b/contracts/deploy/mainnet/155_oeth_zapper.js deleted file mode 100644 index 10e690b276..0000000000 --- a/contracts/deploy/mainnet/155_oeth_zapper.js +++ /dev/null @@ -1,44 +0,0 @@ -const { deploymentWithGovernanceProposal } = require("../../utils/deploy"); -const addresses = require("../../utils/addresses"); - -module.exports = deploymentWithGovernanceProposal( - { - deployName: "155_oeth_zapper", - forceDeploy: false, - //forceSkip: true, - reduceQueueTime: true, - deployerIsProposer: false, - proposalId: "", - }, - async ({ ethers, deployWithConfirmation }) => { - // Current contracts - const cVaultProxy = await ethers.getContract("OETHVaultProxy"); - const cOETH = await ethers.getContract("OETHProxy"); - const cwOETH = await ethers.getContract("WOETHProxy"); - - // Deployer Actions - // ---------------- - - // 1. Deploy the new Compounding Staking Strategy contracts - const dZapper = await deployWithConfirmation( - "OETHZapper", - [ - cOETH.address, - cwOETH.address, - cVaultProxy.address, - addresses.mainnet.WETH, - ], - undefined, - true - ); - const cZapper = await ethers.getContractAt("OETHZapper", dZapper.address); - console.log(`OETHZapper deployed to ${cZapper.address}`); - - // Governance Actions - // ---------------- - return { - name: "", - actions: [], - }; - } -); diff --git a/contracts/deploy/mainnet/169_crosschain_strategy.js b/contracts/deploy/mainnet/169_crosschain_strategy.js index d2a1cd1984..76b4356b5f 100644 --- a/contracts/deploy/mainnet/169_crosschain_strategy.js +++ b/contracts/deploy/mainnet/169_crosschain_strategy.js @@ -12,7 +12,8 @@ module.exports = deploymentWithGovernanceProposal( forceDeploy: false, reduceQueueTime: true, deployerIsProposer: false, - proposalId: "", + proposalId: + "82859201447218338287719298348503030894203878448854317959506610360592214196029", }, async () => { const crossChainStrategyProxyAddress = await getCreate2ProxyAddress( diff --git a/contracts/deploy/mainnet/177_change_crosschain_strategy_operator.js b/contracts/deploy/mainnet/177_change_crosschain_strategy_operator.js index 395fab1981..7332d703ff 100644 --- a/contracts/deploy/mainnet/177_change_crosschain_strategy_operator.js +++ b/contracts/deploy/mainnet/177_change_crosschain_strategy_operator.js @@ -7,7 +7,8 @@ module.exports = deploymentWithGovernanceProposal( forceDeploy: false, reduceQueueTime: true, deployerIsProposer: false, - proposalId: "", + proposalId: + "44576067657297086005253513834508683316408185013245459259067058344639958414003", }, async () => { const cCrossChainMasterStrategy = await ethers.getContractAt( diff --git a/contracts/deploy/mainnet/999_fork_test_setup.js b/contracts/deploy/mainnet/999_fork_test_setup.js index ac8abcaee0..e050e7d4b8 100644 --- a/contracts/deploy/mainnet/999_fork_test_setup.js +++ b/contracts/deploy/mainnet/999_fork_test_setup.js @@ -1,8 +1,6 @@ const { isFork, isForkWithLocalNode } = require("../../test/helpers"); -const { deployWithConfirmation } = require("../../utils/deploy"); const { fundAccounts } = require("../../utils/funding"); const addresses = require("../../utils/addresses"); -const { replaceContractAt } = require("../../utils/hardhat"); const { impersonateAndFund } = require("../../utils/signers"); const { hardhatSetBalance } = require("../../test/_fund"); @@ -33,27 +31,6 @@ const main = async (hre) => { await hardhatSetBalance(deployerAddr, "1000000"); - const oracleRouter = await ethers.getContract("OracleRouter"); - const oethOracleRouter = await ethers.getContract( - isFork ? "OETHOracleRouter" : "OracleRouter" - ); - - // Replace OracleRouter to disable staleness - const dMockOracleRouterNoStale = await deployWithConfirmation( - "MockOracleRouterNoStale" - ); - const dMockOETHOracleRouterNoStale = await deployWithConfirmation( - "MockOETHOracleRouterNoStale" - ); - log("Deployed MockOracleRouterNoStale and MockOETHOracleRouterNoStale"); - await replaceContractAt(oracleRouter.address, dMockOracleRouterNoStale); - await replaceContractAt( - oethOracleRouter.address, - dMockOETHOracleRouterNoStale - ); - - log("Replaced Oracle contracts for fork test"); - const signers = await hre.ethers.getSigners(); for (const signer of signers.slice(0, 4)) { @@ -107,7 +84,7 @@ const main = async (hre) => { log(`999_fork_test_setup deployment done!`); }; -main.id = "999_no_stale_oracles"; +main.id = "999_fork_test_setup"; main.skip = () => isForkWithLocalNode || !isFork; module.exports = main; diff --git a/contracts/deploy/plume/008_rooster_amo.js b/contracts/deploy/plume/008_rooster_amo.js deleted file mode 100644 index c81c135d5e..0000000000 --- a/contracts/deploy/plume/008_rooster_amo.js +++ /dev/null @@ -1,146 +0,0 @@ -const hre = require("hardhat"); -const { deployOnPlume } = require("../../utils/deploy-l2"); -const { - deployWithConfirmation, - withConfirmation, -} = require("../../utils/deploy"); -const addresses = require("../../utils/addresses"); -const { - deployPlumeRoosterAMOStrategyImplementation, -} = require("../deployActions"); -const { isFork, oethUnits } = require("../../test/helpers"); -const { setERC20TokenBalance } = require("../../test/_fund"); -const { utils } = require("ethers"); - -module.exports = deployOnPlume( - { - deployName: "008_rooster_amo", - forceSkip: true, - }, - async () => { - const { deployerAddr } = await getNamedAccounts(); - const sDeployer = await ethers.getSigner(deployerAddr); - const cOETHpVaultProxy = await ethers.getContract("OETHPlumeVaultProxy"); - const weth = await ethers.getContractAt("IWETH9", addresses.plume.WETH); - const deployerWethBalance = await weth - .connect(sDeployer) - .balanceOf(sDeployer.address); - - console.log("Deployer WETH balance", deployerWethBalance.toString()); - if (!isFork) { - if (deployerWethBalance.lt(oethUnits("1"))) { - throw new Error( - "Deployer needs at least 1e18 of WETH to mint the initial balance" - ); - } - } - - const cOETHpVault = await ethers.getContractAt( - "IVault", - cOETHpVaultProxy.address - ); - - await deployWithConfirmation("RoosterAMOStrategyProxy"); - const cAMOStrategyProxy = await ethers.getContract( - "RoosterAMOStrategyProxy" - ); - - if (isFork) { - // Just pretend wPlume is the reward token for testing - await deployWithConfirmation("MockMaverickDistributor", [ - addresses.plume.WPLUME, - ]); - const cMockMaverickDistributor = await ethers.getContract( - "MockMaverickDistributor" - ); - - await withConfirmation( - cMockMaverickDistributor - .connect(sDeployer) - .setRewardTokenAmount(oethUnits("1")) - ); - - // Fund the mock contract - const wPlume = await ethers.getContractAt( - "IWETH9", - addresses.plume.WPLUME - ); - await withConfirmation( - wPlume.connect(sDeployer).deposit({ value: oethUnits("10") }) - ); - await withConfirmation( - wPlume - .connect(sDeployer) - .transfer(cMockMaverickDistributor.address, oethUnits("10")) - ); - } - - const cAMOStrategyImpl = await deployPlumeRoosterAMOStrategyImplementation( - addresses.plume.OethpWETHRoosterPool - ); - const strategyImplInitData = cAMOStrategyImpl.interface.encodeFunctionData( - "initialize()", - [] - ); - - // prettier-ignore - await withConfirmation( - cAMOStrategyProxy - .connect(sDeployer)["initialize(address,address,bytes)"]( - cAMOStrategyImpl.address, - addresses.plume.timelock, - strategyImplInitData - ) - ); - - const cAMOStrategy = await ethers.getContractAt( - "RoosterAMOStrategy", - cAMOStrategyProxy.address - ); - - if (isFork) { - // 1 WETH - await setERC20TokenBalance(sDeployer.address, weth, "1", hre); - } - - // transfer 1e16 of WETH to the strategy to mint the initial position - await weth - .connect(sDeployer) - .transfer(cAMOStrategy.address, oethUnits("0.01")); - - return { - actions: [ - { - // Approve the AMO strategy on the Vault - contract: cOETHpVault, - signature: "approveStrategy(address)", - args: [cAMOStrategy.address], - }, - { - // Set strategy as whitelisted one to mint OETHp tokens - contract: cOETHpVault, - signature: "addStrategyToMintWhitelist(address)", - args: [cAMOStrategy.address], - }, - { - // Safe approve tokens - contract: cAMOStrategy, - signature: "mintInitialPosition()", - args: [], - }, - { - // Safe approve tokens - contract: cAMOStrategy, - signature: "setAllowedPoolWethShareInterval(uint256,uint256)", - args: [utils.parseUnits("0.10", 18), utils.parseUnits("0.25", 18)], - }, - { - // Set Harvester address to the multisig - contract: cAMOStrategy, - signature: "setHarvesterAddress(address)", - args: [addresses.multichainStrategist], - }, - ], - }; - } -); diff --git a/contracts/deploy/sonic/001_vault_and_token.js b/contracts/deploy/sonic/001_vault_and_token.js deleted file mode 100644 index 73b4ec9c81..0000000000 --- a/contracts/deploy/sonic/001_vault_and_token.js +++ /dev/null @@ -1,220 +0,0 @@ -const { parseEther } = require("ethers/lib/utils"); - -const { deployOnSonic } = require("../../utils/deploy-l2.js"); -const { - deployWithConfirmation, - withConfirmation, -} = require("../../utils/deploy.js"); -const addresses = require("../../utils/addresses.js"); - -module.exports = deployOnSonic( - { - deployName: "001_vault_and_token", - forceSkip: false, - }, - async ({ ethers }) => { - const { deployerAddr, strategistAddr } = await getNamedAccounts(); - console.log(`Deployer: ${deployerAddr}`); - const sDeployer = await ethers.provider.getSigner(deployerAddr); - - const cWS = await ethers.getContractAt("IWrappedSonic", addresses.sonic.wS); - - // Proxies - const dOSonicProxy = await deployWithConfirmation("OSonicProxy"); - console.log(`Deployed Origin S proxy to ${dOSonicProxy.address}`); - - const dWOSonicProxy = await deployWithConfirmation("WOSonicProxy"); - console.log(`Deployed Wrapped Origin S proxy to ${dWOSonicProxy.address}`); - - const dOSonicVaultProxy = await deployWithConfirmation("OSonicVaultProxy"); - console.log(`Deployed Vault proxy to ${dOSonicVaultProxy.address}`); - - const cOSonicProxy = await ethers.getContract("OSonicProxy"); - const cWOSonicProxy = await ethers.getContract("WOSonicProxy"); - const cOSonicVaultProxy = await ethers.getContract("OSonicVaultProxy"); - - // Core contracts - const dOSonic = await deployWithConfirmation("OSonic"); - console.log(`Deployed Origin S to ${dOSonic.address}`); - - const dWOSonic = await deployWithConfirmation("WOSonic", [ - cOSonicProxy.address, // Base token - "Wrapped Origin S", // Token Name - "wOS", // Token Symbol - ]); - console.log(`Deployed Wrapped Origin S to ${dWOSonic.address}`); - - const dOSonicVaultCore = await deployWithConfirmation("OSonicVaultCore", [ - cWS.address, - ]); - console.log(`Deployed Vault Core to ${dOSonicVaultCore.address}`); - - const dOSonicVault = await deployWithConfirmation("OSonicVault", [ - cWS.address, - ]); - console.log(`Deployed Vault Admin to ${dOSonicVault.address}`); - - // Get contract instances - const cOSonic = await ethers.getContractAt("OSonic", cOSonicProxy.address); - const cWOSonic = await ethers.getContractAt( - "WOSonic", - cWOSonicProxy.address - ); - const cOSonicVault = await ethers.getContractAt( - "IVault", - cOSonicVaultProxy.address - ); - - // Init OSonic - const resolution = ethers.utils.parseUnits("1", 27); - const initDataOSonic = cOSonic.interface.encodeFunctionData( - "initialize(address,uint256)", - [ - cOSonicVaultProxy.address, // Origin Sonic Vault - resolution, // HighRes - ] - ); - - // prettier-ignore - await cOSonicProxy - .connect(sDeployer)["initialize(address,address,bytes)"]( - dOSonic.address, - addresses.sonic.timelock, - initDataOSonic - ); - console.log("Initialized Origin S"); - - // Init OSonicVault - const initDataOSonicVault = cOSonicVault.interface.encodeFunctionData( - "initialize(address,address)", - [ - addresses.dead, // OracleRouter - cOSonicProxy.address, // OSonic - ] - ); - // prettier-ignore - await cOSonicVaultProxy - .connect(sDeployer)["initialize(address,address,bytes)"]( - dOSonicVaultCore.address, - addresses.sonic.timelock, - initDataOSonicVault - ); - console.log("Initialized Origin S Vault"); - - // Init WOSonic - const initDataWOSonic = cWOSonic.interface.encodeFunctionData( - "initialize()", - [] - ); - // prettier-ignore - await cWOSonicProxy - .connect(sDeployer)["initialize(address,address,bytes)"]( - dWOSonic.address, - addresses.sonic.timelock, - initDataWOSonic - ) - console.log("Initialized Wrapper Origin S"); - - // Deploy the Dripper - const dOSonicDripperProxy = await deployWithConfirmation( - "OSonicDripperProxy" - ); - console.log( - `Deployed Origin Sonic Dripper Proxy to ${dOSonicDripperProxy.address}` - ); - const cOSonicDripperProxy = await ethers.getContract("OSonicDripperProxy"); - - const dFixedRateDripper = await deployWithConfirmation("FixedRateDripper", [ - cOSonicVaultProxy.address, // VaultAddress - cWS.address, - ]); - console.log(`Deployed Fixed Rate Dripper to ${dFixedRateDripper.address}`); - - // Init the Dripper proxy - // prettier-ignore - await withConfirmation( - cOSonicDripperProxy - .connect(sDeployer)["initialize(address,address,bytes)"]( - dFixedRateDripper.address, - addresses.sonic.timelock, - "0x" - ) - ); - - // Deploy the Zapper - const dOSonicZapper = await deployWithConfirmation("OSonicZapper", [ - cOSonic.address, - cWOSonic.address, - cOSonicVault.address, - ]); - console.log(`Deployed Origin Sonic Zapper to ${dOSonicZapper.address}`); - - // Deploy the VaultValueChecker - await deployWithConfirmation("VaultValueChecker", [ - cOSonicVault.address, // Origin Sonic Vault - cOSonic.address, // Origin Sonic token - ]); - const vaultValueChecker = await ethers.getContract("VaultValueChecker"); - console.log(`Deployed Vault Value Checker to ${vaultValueChecker.address}`); - - return { - name: "Config Tokens and Vault", - actions: [ - // 1. Upgrade Vault proxy to VaultCore - { - contract: cOSonicVaultProxy, - signature: "upgradeTo(address)", - args: [dOSonicVaultCore.address], - }, - // 2. Set the VaultAdmin - { - contract: cOSonicVault, - signature: "setAdminImpl(address)", - args: [dOSonicVault.address], - }, - // 3. Support wrapped S - { - contract: cOSonicVault, - signature: "supportAsset(address,uint8)", - args: [cWS.address, 0], // 0 -> UnitConversion.DECIMALS - }, - // 4. Unpause capital - { - contract: cOSonicVault, - signature: "unpauseCapital()", - args: [], - }, - // 5. withdrawal claim delay set to 1 day - { - contract: cOSonicVault, - signature: "setWithdrawalClaimDelay(uint256)", - args: [86400], - }, - // 6. Configure the Vault - { - contract: cOSonicVault, - signature: "setRebaseThreshold(uint256)", - args: [parseEther("10")], // 10 OS - }, - // 7. set setAutoAllocateThreshold - { - contract: cOSonicVault, - signature: "setMaxSupplyDiff(uint256)", - args: [parseEther("1")], // 1 OS - }, - // 8. set guardian / strategist - { - contract: cOSonicVault, - signature: "setStrategistAddr(address)", - args: [strategistAddr], - }, - // 9. Set the Dripper on the Vault - { - contract: cOSonicVault, - signature: "setDripper(address)", - args: [cOSonicDripperProxy.address], - }, - ], - }; - } -); diff --git a/contracts/deploy/sonic/002_oracle_router.js b/contracts/deploy/sonic/002_oracle_router.js deleted file mode 100644 index 3fa746c583..0000000000 --- a/contracts/deploy/sonic/002_oracle_router.js +++ /dev/null @@ -1,32 +0,0 @@ -const { deployOnSonic } = require("../../utils/deploy-l2.js"); -const { deployOracles } = require("../deployActions"); - -module.exports = deployOnSonic( - { - deployName: "002_oracle_router", - forceSkip: false, - }, - async ({ ethers }) => { - const cOSonicVaultProxy = await ethers.getContract("OSonicVaultProxy"); - const cOSonicVault = await ethers.getContractAt( - "IVault", - cOSonicVaultProxy.address - ); - - await deployOracles(); - const oracleRouter = await ethers.getContract("OSonicOracleRouter"); - console.log(`Deployed Oracle Router at: ${oracleRouter.address}`); - - return { - name: "Configure Oracle Router as Price provider", - actions: [ - // 1. Approve Sonic Staking Strategy on the Vault - { - contract: cOSonicVault, - signature: "setPriceProvider(address)", - args: [oracleRouter.address], - }, - ], - }; - } -); diff --git a/contracts/deploy/sonic/003_sonic_staking_strategy.js b/contracts/deploy/sonic/003_sonic_staking_strategy.js deleted file mode 100644 index 3114e3ebef..0000000000 --- a/contracts/deploy/sonic/003_sonic_staking_strategy.js +++ /dev/null @@ -1,154 +0,0 @@ -const { parseUnits } = require("ethers/lib/utils.js"); -const { deployOnSonic } = require("../../utils/deploy-l2.js"); -const { - deployWithConfirmation, - withConfirmation, -} = require("../../utils/deploy.js"); -const addresses = require("../../utils/addresses.js"); - -module.exports = deployOnSonic( - { - deployName: "003_sonic_staking_strategy", - forceSkip: false, - }, - async ({ ethers }) => { - const { deployerAddr, strategistAddr } = await getNamedAccounts(); - console.log(`Deployer: ${deployerAddr}`); - console.log(`Strategist: ${strategistAddr}`); - const sDeployer = await ethers.provider.getSigner(deployerAddr); - - const cOSonicVaultProxy = await ethers.getContract("OSonicVaultProxy"); - const cOSonicVault = await ethers.getContractAt( - "IVault", - cOSonicVaultProxy.address - ); - - // Staking Strategy - const dSonicStakingStrategyProxy = await deployWithConfirmation( - "SonicStakingStrategyProxy" - ); - console.log( - `Deployed Sonic Staking Strategy proxy to ${dSonicStakingStrategyProxy.address}` - ); - - const cSonicStakingStrategyProxy = await ethers.getContract( - "SonicStakingStrategyProxy" - ); - const dSonicStakingStrategy = await deployWithConfirmation( - "SonicStakingStrategy", - [ - [addresses.sonic.SFC, cOSonicVaultProxy.address], // platformAddress, VaultAddress - addresses.sonic.wS, - addresses.sonic.SFC, - ] - ); - console.log( - `Deployed Sonic Staking Strategy to ${dSonicStakingStrategy.address}` - ); - const cSonicStakingStrategy = await ethers.getContractAt( - "SonicStakingStrategy", - cSonicStakingStrategyProxy.address - ); - - // Init the Sonic Staking Strategy - const initSonicStakingStrategy = - cSonicStakingStrategy.interface.encodeFunctionData("initialize()", []); - // prettier-ignore - await withConfirmation( - cSonicStakingStrategyProxy - .connect(sDeployer)["initialize(address,address,bytes)"]( - dSonicStakingStrategy.address, - addresses.sonic.timelock, - initSonicStakingStrategy - ) - ); - console.log("Initialized SonicStakingStrategy proxy and implementation"); - - // Deploy a new Wrapped Origin Sonic contract - - const cOSonicProxy = await ethers.getContract("OSonicProxy"); - const cWOSonicProxy = await ethers.getContract("WOSonicProxy"); - - const dWOSonic = await deployWithConfirmation("WOSonic", [ - cOSonicProxy.address, // Base token - "Wrapped OS", // Token Name - "wOS", // Token Symbol - ]); - console.log(`Deployed Wrapped OS to ${dWOSonic.address}`); - - return { - name: "Config Sonic Staking Strategy", - actions: [ - // 1. Approve Sonic Staking Strategy on the Vault - { - contract: cOSonicVault, - signature: "approveStrategy(address)", - args: [cSonicStakingStrategy.address], - }, - // 2. Add supported validators - { - contract: cSonicStakingStrategy, - signature: "supportValidator(uint256)", - args: [15], - }, - { - contract: cSonicStakingStrategy, - signature: "supportValidator(uint256)", - args: [16], - }, - { - contract: cSonicStakingStrategy, - signature: "supportValidator(uint256)", - args: [17], - }, - { - contract: cSonicStakingStrategy, - signature: "supportValidator(uint256)", - args: [18], - }, - // 3. Set Defender Relayer for Sonic validator controls - { - contract: cSonicStakingStrategy, - signature: "setRegistrator(address)", - args: [addresses.sonic.validatorRegistrator], - }, - // 4. Set the Sonic Staking Strategy as the default strategy for wS - { - contract: cOSonicVault, - signature: "setAssetDefaultStrategy(address,address)", - args: [addresses.sonic.wS, cSonicStakingStrategy.address], - }, - // 5. Set 10% performance fee - { - contract: cOSonicVault, - signature: "setTrusteeFeeBps(uint256)", - args: [1000], - }, - // 6. set the trustee address - { - contract: cOSonicVault, - signature: "setTrusteeAddress(address)", - args: [strategistAddr], - }, - // 7. Set the Vault buffer to 1% - { - contract: cOSonicVault, - signature: "setVaultBuffer(uint256)", - args: [parseUnits("1", 16)], - }, - // 8. Set the auto allocation to 1,000 wS - { - contract: cOSonicVault, - signature: "setAutoAllocateThreshold(uint256)", - args: [parseUnits("1000", 18)], - }, - // 9. Upgrade the Wrapped Origin Sonic contract with new name - { - contract: cWOSonicProxy, - signature: "upgradeTo(address)", - args: [dWOSonic.address], - }, - ], - }; - } -); diff --git a/contracts/deploy/sonic/004_timelock_1d_delay.js b/contracts/deploy/sonic/004_timelock_1d_delay.js deleted file mode 100644 index f145e1d921..0000000000 --- a/contracts/deploy/sonic/004_timelock_1d_delay.js +++ /dev/null @@ -1,25 +0,0 @@ -const { deployOnSonic } = require("../../utils/deploy-l2"); -const addresses = require("../../utils/addresses"); - -module.exports = deployOnSonic( - { - deployName: "004_timelock_1d_delay", - }, - async ({ ethers }) => { - const cTimelock = await ethers.getContractAt( - "ITimelockController", - addresses.sonic.timelock - ); - - return { - actions: [ - { - // 1. Update delay to 1d - contract: cTimelock, - signature: "updateDelay(uint256)", - args: [24 * 60 * 60], - }, - ], - }; - } -); diff --git a/contracts/deploy/sonic/005_multisig_as_canceller.js b/contracts/deploy/sonic/005_multisig_as_canceller.js deleted file mode 100644 index 4215430791..0000000000 --- a/contracts/deploy/sonic/005_multisig_as_canceller.js +++ /dev/null @@ -1,27 +0,0 @@ -const { deployOnSonic } = require("../../utils/deploy-l2"); -const addresses = require("../../utils/addresses"); - -module.exports = deployOnSonic( - { - deployName: "005_multisig_as_canceller", - }, - async ({ ethers }) => { - const cTimelock = await ethers.getContractAt( - "ITimelockController", - addresses.sonic.timelock - ); - - const timelockCancellerRole = await cTimelock.CANCELLER_ROLE(); - - return { - actions: [ - { - // 1. Grant canceller role to 5/8 Multisig - contract: cTimelock, - signature: "grantRole(bytes32,address)", - args: [timelockCancellerRole, addresses.sonic.admin], - }, - ], - }; - } -); diff --git a/contracts/deploy/sonic/006_yf_swpx_os_pool.js b/contracts/deploy/sonic/006_yf_swpx_os_pool.js deleted file mode 100644 index 7a2842559f..0000000000 --- a/contracts/deploy/sonic/006_yf_swpx_os_pool.js +++ /dev/null @@ -1,26 +0,0 @@ -const { deployOnSonic } = require("../../utils/deploy-l2"); -const addresses = require("../../utils/addresses"); - -module.exports = deployOnSonic( - { - deployName: "006_yf_swpx_os_pool", - }, - async ({ ethers }) => { - const cOSonicProxy = await ethers.getContract("OSonicProxy"); - const cOSonic = await ethers.getContractAt("OSonic", cOSonicProxy.address); - - return { - actions: [ - { - // 1. Delegate the yield from the SwapX SWPx/OS pool to SwapX Treasury - contract: cOSonic, - signature: "delegateYield(address,address)", - args: [ - addresses.sonic.SwapXSWPxOSPool, - addresses.sonic.SwapXTreasury, - ], - }, - ], - }; - } -); diff --git a/contracts/deploy/sonic/007_strategist_as_executor.js b/contracts/deploy/sonic/007_strategist_as_executor.js deleted file mode 100644 index 7a6d32b2a7..0000000000 --- a/contracts/deploy/sonic/007_strategist_as_executor.js +++ /dev/null @@ -1,27 +0,0 @@ -const { deployOnSonic } = require("../../utils/deploy-l2"); -const addresses = require("../../utils/addresses"); - -const EXECUTOR_ROLE = - "0xd8aa0f3194971a2a116679f7c2090f6939c8d4e01a2a8d7e41d55e5351469e63"; - -module.exports = deployOnSonic( - { - deployName: "007_strategist_as_executor", - }, - async ({ ethers }) => { - const cTimelock = await ethers.getContractAt( - "ITimelockController", - addresses.sonic.timelock - ); - - return { - actions: [ - { - contract: cTimelock, - signature: "grantRole(bytes32,address)", - args: [EXECUTOR_ROLE, addresses.sonic.guardian], - }, - ], - }; - } -); diff --git a/contracts/deploy/sonic/008_swapx_yield_forward.js b/contracts/deploy/sonic/008_swapx_yield_forward.js deleted file mode 100644 index 5ddb9c8ed7..0000000000 --- a/contracts/deploy/sonic/008_swapx_yield_forward.js +++ /dev/null @@ -1,33 +0,0 @@ -const { deployOnSonic } = require("../../utils/deploy-l2"); -const addresses = require("../../utils/addresses"); - -module.exports = deployOnSonic( - { - deployName: "008_swapx_yield_forward", - }, - async ({ ethers }) => { - const cOSonicProxy = await ethers.getContract("OSonicProxy"); - const cOSonic = await ethers.getContractAt("OSonic", cOSonicProxy.address); - - return { - actions: [ - { - contract: cOSonic, - signature: "delegateYield(address,address)", - args: [ - addresses.sonic.SwapXOsUSDCe.pool, - addresses.sonic.SwapXOsUSDCeMultisigBooster, - ], - }, - { - contract: cOSonic, - signature: "delegateYield(address,address)", - args: [ - addresses.sonic.SwapXOsGEMSx.pool, - addresses.sonic.SwapXOsGEMSxMultisigBooster, - ], - }, - ], - }; - } -); diff --git a/contracts/deploy/sonic/009_swapx_amo.js b/contracts/deploy/sonic/009_swapx_amo.js deleted file mode 100644 index c718d51a95..0000000000 --- a/contracts/deploy/sonic/009_swapx_amo.js +++ /dev/null @@ -1,120 +0,0 @@ -const { deployOnSonic } = require("../../utils/deploy-l2"); -const { - deployWithConfirmation, - withConfirmation, -} = require("../../utils/deploy"); -const { - deploySonicSwapXAMOStrategyImplementation, -} = require("../deployActions"); -const addresses = require("../../utils/addresses"); - -module.exports = deployOnSonic( - { - deployName: "009_swapx_amo", - }, - async ({ ethers }) => { - const { deployerAddr, strategistAddr } = await getNamedAccounts(); - const sDeployer = await ethers.provider.getSigner(deployerAddr); - - // Deploy a new Vault Core implementation - const dOSonicVaultCore = await deployWithConfirmation("OSonicVaultCore", [ - addresses.sonic.wS, - ]); - console.log(`Deployed Vault Core to ${dOSonicVaultCore.address}`); - - // Deploy a new Vault Admin implementation - const dOSonicVaultAdmin = await deployWithConfirmation("OSonicVaultAdmin", [ - addresses.sonic.wS, - ]); - console.log( - `Deployed Origin Sonic Vault Admin to ${dOSonicVaultAdmin.address}` - ); - - // Deploy the Harvester proxy - await deployWithConfirmation("OSonicHarvesterProxy"); - - // Deploy the Harvester implementation - await deployWithConfirmation("OETHHarvesterSimple", [addresses.sonic.wS]); - const dHarvester = await ethers.getContract("OETHHarvesterSimple"); - - const cHarvesterProxy = await ethers.getContract("OSonicHarvesterProxy"); - const cHarvester = await ethers.getContractAt( - "OETHHarvesterSimple", - cHarvesterProxy.address - ); - - const cDripperProxy = await ethers.getContract("OSonicDripperProxy"); - - const initHarvester = cHarvester.interface.encodeFunctionData( - "initialize(address,address,address)", - [addresses.sonic.timelock, strategistAddr, cDripperProxy.address] - ); - - // Initialize the Harvester - // prettier-ignore - await withConfirmation( - cHarvesterProxy - .connect(sDeployer)["initialize(address,address,bytes)"]( - dHarvester.address, - addresses.sonic.timelock, - initHarvester - ) - ); - - // Deploy Sonic SwapX AMO Strategy proxy - const cOSonicVaultProxy = await ethers.getContract("OSonicVaultProxy"); - const cOSonicVaultAdmin = await ethers.getContractAt( - "OSonicVaultAdmin", - cOSonicVaultProxy.address - ); - await deployWithConfirmation("SonicSwapXAMOStrategyProxy", []); - const cSonicSwapXAMOStrategyProxy = await ethers.getContract( - "SonicSwapXAMOStrategyProxy" - ); - - // Deploy Sonic SwapX AMO Strategy implementation - const cSonicSwapXAMOStrategy = - await deploySonicSwapXAMOStrategyImplementation(); - - return { - actions: [ - // 1. Upgrade Vault proxy to new VaultCore - { - contract: cOSonicVaultProxy, - signature: "upgradeTo(address)", - args: [dOSonicVaultCore.address], - }, - // 2. Upgrade the VaultAdmin - { - contract: cOSonicVaultAdmin, - signature: "setAdminImpl(address)", - args: [dOSonicVaultAdmin.address], - }, - // 3. Approve new strategy on the Vault - { - contract: cOSonicVaultAdmin, - signature: "approveStrategy(address)", - args: [cSonicSwapXAMOStrategyProxy.address], - }, - // 4. Add strategy to mint whitelist - { - contract: cOSonicVaultAdmin, - signature: "addStrategyToMintWhitelist(address)", - args: [cSonicSwapXAMOStrategyProxy.address], - }, - // 5. Enable for SwapX AMO after it has been deployed - { - contract: cHarvester, - signature: "setSupportedStrategy(address,bool)", - args: [cSonicSwapXAMOStrategyProxy.address, true], - }, - // 6. Set the Harvester on the SwapX AMO strategy - { - contract: cSonicSwapXAMOStrategy, - signature: "setHarvesterAddress(address)", - args: [cHarvesterProxy.address], - }, - ], - }; - } -); diff --git a/contracts/deploy/sonic/010_swapx_yield_forward.js b/contracts/deploy/sonic/010_swapx_yield_forward.js deleted file mode 100644 index 3debca53e9..0000000000 --- a/contracts/deploy/sonic/010_swapx_yield_forward.js +++ /dev/null @@ -1,35 +0,0 @@ -const { deployOnSonic } = require("../../utils/deploy-l2"); -const addresses = require("../../utils/addresses"); - -module.exports = deployOnSonic( - { - deployName: "010_swapx_yield_forward", - }, - async ({ ethers }) => { - const cOSonicProxy = await ethers.getContract("OSonicProxy"); - const cOSonic = await ethers.getContractAt("OSonic", cOSonicProxy.address); - - return { - actions: [ - { - // https://www.notion.so/originprotocol/TB-YieldForwarding-19a84d46f53c806f9fd3c0921f10d940?pvs=25 - contract: cOSonic, - signature: "delegateYield(address,address)", - args: [ - addresses.sonic.Shadow.OsEco.pool, - addresses.sonic.Shadow.OsEco.yf_treasury, - ], - }, - { - // https://www.notion.so/originprotocol/TB-YieldForwarding-19984d46f53c80f1a913cdb199432ecd - contract: cOSonic, - signature: "delegateYield(address,address)", - args: [ - addresses.sonic.SwapX.OsHedgy.pool, - addresses.sonic.SwapX.OsHedgy.yf_treasury, - ], - }, - ], - }; - } -); diff --git a/contracts/deploy/sonic/011_pool_booster_factory.js b/contracts/deploy/sonic/011_pool_booster_factory.js deleted file mode 100644 index d256870fa6..0000000000 --- a/contracts/deploy/sonic/011_pool_booster_factory.js +++ /dev/null @@ -1,115 +0,0 @@ -const { deployOnSonic } = require("../../utils/deploy-l2"); -const addresses = require("../../utils/addresses"); -const { - deployWithConfirmation, - withConfirmation, -} = require("../../utils/deploy.js"); -const { oethUnits } = require("../../test/helpers"); - -module.exports = deployOnSonic( - { - deployName: "011_pool_booster_factory", - }, - async ({ ethers }) => { - const { deployerAddr } = await getNamedAccounts(); - const sDeployer = await ethers.provider.getSigner(deployerAddr); - - const cOSonic = await ethers.getContractAt( - "OSonic", - addresses.sonic.OSonicProxy - ); - - await deployWithConfirmation("PoolBoostCentralRegistryProxy", []); - const cPoolBoostCentralRegistryProxy = await ethers.getContract( - "PoolBoostCentralRegistryProxy" - ); - console.log( - `Pool boost central registry proxy deployed: ${cPoolBoostCentralRegistryProxy.address}` - ); - - const dPoolBoostCentralRegistry = await deployWithConfirmation( - "PoolBoostCentralRegistry", - [] - ); - console.log( - `Deployed Pool Boost Central Registry to ${dPoolBoostCentralRegistry.address}` - ); - const cPoolBoostCentralRegistry = await ethers.getContractAt( - "PoolBoostCentralRegistry", - cPoolBoostCentralRegistryProxy.address - ); - - // prettier-ignore - await withConfirmation( - cPoolBoostCentralRegistryProxy - .connect(sDeployer)["initialize(address,address,bytes)"]( - dPoolBoostCentralRegistry.address, - addresses.sonic.timelock, - "0x" - ) - ); - console.log( - "Initialized PoolBoostCentralRegistry proxy and implementation" - ); - - const dPoolBoosterFactory = await deployWithConfirmation( - "PoolBoosterFactorySwapxDouble_v1", - [ - addresses.sonic.OSonicProxy, - addresses.sonic.timelock, - cPoolBoostCentralRegistryProxy.address, - ], - "PoolBoosterFactorySwapxDouble" - ); - const cPoolBoosterFactory = await ethers.getContract( - "PoolBoosterFactorySwapxDouble_v1" - ); - console.log( - `Pool Booster Ichi Factory deployed to ${dPoolBoosterFactory.address}` - ); - - const poolBoosterCreationArgs = [ - addresses.sonic.SwapXOsUSDCe.extBribeOS, - addresses.sonic.SwapXOsUSDCe.extBribeUSDC, - addresses.sonic.SwapXOsUSDCe.pool, - oethUnits("0.7"), // 70% - ethers.BigNumber.from("1740052983"), // epoch as Thu, 20 Feb 2025 12:03:03 GMT - ]; - - const poolBoosterAddress = - await cPoolBoosterFactory.computePoolBoosterAddress( - ...poolBoosterCreationArgs - ); - - return { - actions: [ - { - // set the factory as an approved one - contract: cPoolBoostCentralRegistry, - signature: "approveFactory(address)", - args: [dPoolBoosterFactory.address], - }, - { - // crate a pool booster for the concentrated liquidity pool - contract: cPoolBoosterFactory, - signature: - "createPoolBoosterSwapxDouble(address,address,address,uint256,uint256)", - args: poolBoosterCreationArgs, - }, - { - // Undelegate yield from the OS/USDC.e pool - contract: cOSonic, - signature: "undelegateYield(address)", - args: [addresses.sonic.SwapXOsUSDCe.pool], - }, - { - // Delegate yield from the OS/USDC.e pool - // to the pool booster - contract: cOSonic, - signature: "delegateYield(address,address)", - args: [addresses.sonic.SwapXOsUSDCe.pool, poolBoosterAddress], - }, - ], - }; - } -); diff --git a/contracts/deploy/sonic/012_tb_yf_batch_1.js b/contracts/deploy/sonic/012_tb_yf_batch_1.js deleted file mode 100644 index 397d0db3ab..0000000000 --- a/contracts/deploy/sonic/012_tb_yf_batch_1.js +++ /dev/null @@ -1,157 +0,0 @@ -const { deployOnSonic } = require("../../utils/deploy-l2"); -const addresses = require("../../utils/addresses"); -const { - deployWithConfirmation, - createPoolBoosterSonic, -} = require("../../utils/deploy.js"); -const { oethUnits } = require("../../test/helpers"); - -module.exports = deployOnSonic( - { - deployName: "012_tb_yf_batch_1", - }, - async ({ ethers }) => { - // --------------------------------------------------------------------------------------------------------- - // --- - // --- Contracts - // --- - // --------------------------------------------------------------------------------------------------------- - const cOSonic = await ethers.getContractAt( - "OSonic", - addresses.sonic.OSonicProxy - ); - - const cPoolBoostCentralRegistryProxy = await ethers.getContract( - "PoolBoostCentralRegistryProxy" - ); - - const cPoolBoostCentralRegistry = await ethers.getContractAt( - "PoolBoostCentralRegistry", - cPoolBoostCentralRegistryProxy.address - ); - - const cPoolBoosterFactorySwapxDouble = await ethers.getContract( - "PoolBoosterFactorySwapxDouble_v1" - ); - - const SALT = ethers.BigNumber.from("1741009056"); // epoch as Friday 28th Feb 2025 4PM UTC - - // --------------------------------------------------------------------------------------------------------- - // --- - // --- PoolBoosterFactory SwapxSingle Deployment - // --- - // --------------------------------------------------------------------------------------------------------- - const dPoolBoosterSwapxSingleFactory = await deployWithConfirmation( - "PoolBoosterFactorySwapxSingle", - [ - addresses.sonic.OSonicProxy, - addresses.sonic.timelock, - cPoolBoostCentralRegistryProxy.address, - ] - ); - const cPoolBoosterFactorySwapxSingle = await ethers.getContract( - "PoolBoosterFactorySwapxSingle" - ); - console.log( - `Pool Booster Swapx Single deployed to ${dPoolBoosterSwapxSingleFactory.address}` - ); - - // --------------------------------------------------------------------------------------------------------- - // --- - // --- PoolBooster SwapxSingle - // --- - // --------------------------------------------------------------------------------------------------------- - const { actions: actionsSingle } = await createPoolBoosterSonic({ - cOSonic, - factoryContract: cPoolBoosterFactorySwapxSingle, - pools: ["Equalizer.WsOs", "Equalizer.ThcOs", "SwapX.OsFiery"], - salt: SALT, - type: "Single", - }); - // --------------------------------------------------------------------------------------------------------- - // --- - // --- PoolBooster SwapxDouble - // --- - // --------------------------------------------------------------------------------------------------------- - const { actions: actionsDouble } = await createPoolBoosterSonic({ - cOSonic, - factoryContract: cPoolBoosterFactorySwapxDouble, - pools: ["SwapX.OsSfrxUSD", "SwapX.OsScUSD", "SwapX.OsSilo"], - salt: SALT, - split: oethUnits("0.7"), - type: "Double", - }); - - // --------------------------------------------------------------------------------------------------------- - // --- - // --- Governance Actions - // --- - // --------------------------------------------------------------------------------------------------------- - return { - actions: [ - { - // set the factory as an approved one - contract: cPoolBoostCentralRegistry, - signature: "approveFactory(address)", - args: [dPoolBoosterSwapxSingleFactory.address], - }, - { - // Plateform: SwapX - // Protocol: Moon Bay - // From: VolatileV1 AMM - MOON/OS --> To: EOA - contract: cOSonic, - signature: "delegateYield(address,address)", - args: [ - "0x51caf8b6d184e46aeb606472258242aacee3e23b", - "0xa9d3b1408353d05064d47daf0dc98e104eb9c98a", - ], - }, - { - // Plateform: SwapX - // Protocol: BOL - // From: VolatileV1 AMM - BOL/OS --> To: EOA - contract: cOSonic, - signature: "delegateYield(address,address)", - args: [ - "0x0666b11a59f02781854e778687ce312d6b306ce4", - "0x3ef000Bae3e8105be55F76FDa784fD7d69CFf30e", - ], - }, - { - // Plateform: SwapX - // Protocol: EGGS - // From: VolatileV1 AMM - OS/EGGS --> To: EOA - contract: cOSonic, - signature: "delegateYield(address,address)", - args: [ - "0x6feae13b486a225fb2247ccfda40bf8f1dd9d4b1", - "0x98Fc4CE3dFf1d0D7c9dF94f7d9b4E6E6468D5EfF", - ], - }, - { - // Plateform: Metropolis - // Protocol: Paintswap - // From: BRUSH/OS --> To: EOA - contract: cOSonic, - signature: "delegateYield(address,address)", - args: [ - "0xbb9e9f35e5eda1eeed3d811366501d940866268f", - "0x3b99636439FBA6314C0F52D35FEd2fF442191407", - ], - }, - { - // Protocol: HOG - // From: HogGenesisRewardPool --> To: Safe Contract - contract: cOSonic, - signature: "delegateYield(address,address)", - args: [ - "0x2e585b96a2ef1661508110e41c005be86b63fc34", - "0xF0E3E07e11bFA26AEB0C0693824Eb0BF1653AE77", - ], - }, - ...actionsSingle, - ...actionsDouble, - ], - }; - } -); diff --git a/contracts/deploy/sonic/013_vault_config.js b/contracts/deploy/sonic/013_vault_config.js deleted file mode 100644 index 2015554246..0000000000 --- a/contracts/deploy/sonic/013_vault_config.js +++ /dev/null @@ -1,35 +0,0 @@ -const { deployOnSonic } = require("../../utils/deploy-l2"); -const { oethUnits } = require("../../test/helpers"); -const { resolveContract } = require("../../utils/resolvers"); - -module.exports = deployOnSonic( - { - deployName: "013_vault_config", - }, - async () => { - const cOSonicVault = await resolveContract("OSonicVaultProxy", "IVault"); - - return { - actions: [ - { - // Increase the rebase threshold to 50k wS - contract: cOSonicVault, - signature: "setRebaseThreshold(uint256)", - args: [oethUnits("20000", 18)], - }, - { - // Increase the auto-allocation threshold to 20k wS - contract: cOSonicVault, - signature: "setAutoAllocateThreshold(uint256)", - args: [oethUnits("20000", 18)], - }, - { - // Reduce the vault buffer to 0.5% - contract: cOSonicVault, - signature: "setVaultBuffer(uint256)", - args: [oethUnits("0.005", 18)], - }, - ], - }; - } -); diff --git a/contracts/deploy/sonic/014_wrapped_sonic.js b/contracts/deploy/sonic/014_wrapped_sonic.js deleted file mode 100644 index f1ca26bff0..0000000000 --- a/contracts/deploy/sonic/014_wrapped_sonic.js +++ /dev/null @@ -1,38 +0,0 @@ -const { deployOnSonic } = require("../../utils/deploy-l2"); -const { deployWithConfirmation } = require("../../utils/deploy.js"); - -module.exports = deployOnSonic( - { - deployName: "014_wrapped_sonic", - }, - async ({ ethers }) => { - const cOSonicProxy = await ethers.getContract("OSonicProxy"); - const dWOSonicProxy = await ethers.getContract("WOSonicProxy"); - - const dWSonicImpl = await deployWithConfirmation("WOSonic", [ - cOSonicProxy.address, - ]); - - const cWOSonic = await ethers.getContractAt( - "WOSonic", - dWOSonicProxy.address - ); - - return { - actions: [ - // 1. Upgrade WOSonic - { - contract: dWOSonicProxy, - signature: "upgradeTo(address)", - args: [dWSonicImpl.address], - }, - // 2. Run the second initializer - { - contract: cWOSonic, - signature: "initialize2()", - args: [], - }, - ], - }; - } -); diff --git a/contracts/deploy/sonic/015_redeem_fee.js b/contracts/deploy/sonic/015_redeem_fee.js deleted file mode 100644 index dc2a708643..0000000000 --- a/contracts/deploy/sonic/015_redeem_fee.js +++ /dev/null @@ -1,22 +0,0 @@ -const { deployOnSonic } = require("../../utils/deploy-l2"); -const { resolveContract } = require("../../utils/resolvers"); - -module.exports = deployOnSonic( - { - deployName: "015_redeem_fee", - }, - async () => { - const cOSonicVault = await resolveContract("OSonicVaultProxy", "IVault"); - - return { - actions: [ - { - // Set redeem fee to 0.1% - contract: cOSonicVault, - signature: "setRedeemFeeBps(uint256)", - args: [ethers.BigNumber.from("10")], - }, - ], - }; - } -); diff --git a/contracts/deploy/sonic/016_sonic_staking_strategy_upgrade.js b/contracts/deploy/sonic/016_sonic_staking_strategy_upgrade.js deleted file mode 100644 index cc3dab366c..0000000000 --- a/contracts/deploy/sonic/016_sonic_staking_strategy_upgrade.js +++ /dev/null @@ -1,45 +0,0 @@ -const { deployOnSonic } = require("../../utils/deploy-l2.js"); -const { deployWithConfirmation } = require("../../utils/deploy.js"); -const addresses = require("../../utils/addresses.js"); - -module.exports = deployOnSonic( - { - deployName: "016_sonic_staking_strategy_upgrade", - forceSkip: false, - }, - async ({ ethers }) => { - const { deployerAddr, strategistAddr } = await getNamedAccounts(); - console.log(`Deployer: ${deployerAddr}`); - console.log(`Strategist: ${strategistAddr}`); - - const cOSonicVaultProxy = await ethers.getContract("OSonicVaultProxy"); - - // Staking Strategy - const cSonicStakingStrategyProxy = await ethers.getContract( - "SonicStakingStrategyProxy" - ); - const dSonicStakingStrategy = await deployWithConfirmation( - "SonicStakingStrategy", - [ - [addresses.sonic.SFC, cOSonicVaultProxy.address], // platformAddress, VaultAddress - addresses.sonic.wS, - addresses.sonic.SFC, - ] - ); - console.log( - `Deployed Sonic Staking Strategy to ${dSonicStakingStrategy.address}` - ); - - return { - name: "Upgrade the Sonic Staking Strategy", - actions: [ - // 1. Upgrade the Sonic Staking Strategy - { - contract: cSonicStakingStrategyProxy, - signature: "upgradeTo(address)", - args: [dSonicStakingStrategy.address], - }, - ], - }; - } -); diff --git a/contracts/deploy/sonic/017_pool_booster_metropolis.js b/contracts/deploy/sonic/017_pool_booster_metropolis.js deleted file mode 100644 index 86f5960260..0000000000 --- a/contracts/deploy/sonic/017_pool_booster_metropolis.js +++ /dev/null @@ -1,81 +0,0 @@ -const { deployOnSonic } = require("../../utils/deploy-l2"); -const addresses = require("../../utils/addresses"); -const { deployWithConfirmation } = require("../../utils/deploy.js"); - -// 0x03A9896A464C515d13f2679df337bF95bc891fdA: Voter -// 0xd9db92613867FE0d290CE64Fe737E2F8B80CADc3: Rewarder Factory -// 0x161A72027D83DA46329ed64A4EDfd0B717b7f8a7: Rewarder Implem -module.exports = deployOnSonic( - { - deployName: "017_pool_booster_metropolis", - }, - async ({ ethers }) => { - // --------------------------------------------------------------------------------------------------------- - // --- - // --- Contracts - // --- - // --------------------------------------------------------------------------------------------------------- - const cOSonic = await ethers.getContractAt( - "OSonic", - addresses.sonic.OSonicProxy - ); - - const cPoolBoostCentralRegistryProxy = await ethers.getContract( - "PoolBoostCentralRegistryProxy" - ); - - // --------------------------------------------------------------------------------------------------------- - // --- - // --- Upgrade PoolBoosterCentralRegistry - // --- - // --------------------------------------------------------------------------------------------------------- - const dPoolBoosterCentralRegistry = await deployWithConfirmation( - "PoolBoostCentralRegistry", - [] - ); - console.log( - `Deployed new Pool Boost Central Registry to ${dPoolBoosterCentralRegistry.address}` - ); - - const cPoolBoostCentralRegistry = await ethers.getContractAt( - "PoolBoostCentralRegistry", - cPoolBoostCentralRegistryProxy.address - ); - - // --------------------------------------------------------------------------------------------------------- - // --- - // --- Deploy PoolBoosterFactoryMetropolis - // --- - // --------------------------------------------------------------------------------------------------------- - const dPoolBoosterFactoryMetropolis = await deployWithConfirmation( - "PoolBoosterFactoryMetropolis", - [ - cOSonic.address, - addresses.sonic.timelock, - cPoolBoostCentralRegistryProxy.address, - addresses.sonic.Metropolis.RewarderFactory, // Rewarder Factory - addresses.sonic.Metropolis.Voter, // Voter - ] - ); - console.log( - `Pool Booster Factory Metropolis deployed to ${dPoolBoosterFactoryMetropolis.address}` - ); - - return { - name: "Upgrade PoolBoosterCentralRegistry and deploy PoolBoosterFactoryMetropolis", - actions: [ - { - contract: cPoolBoostCentralRegistryProxy, - signature: "upgradeTo(address)", - args: [dPoolBoosterCentralRegistry.address], - }, - { - // set the factory as an approved one - contract: cPoolBoostCentralRegistry, - signature: "approveFactory(address)", - args: [dPoolBoosterFactoryMetropolis.address], - }, - ], - }; - } -); diff --git a/contracts/deploy/sonic/018_merkl_pool_booster.js b/contracts/deploy/sonic/018_merkl_pool_booster.js deleted file mode 100644 index d9295955a0..0000000000 --- a/contracts/deploy/sonic/018_merkl_pool_booster.js +++ /dev/null @@ -1,81 +0,0 @@ -const { deployOnSonic } = require("../../utils/deploy-l2"); -const addresses = require("../../utils/addresses"); -const { deployWithConfirmation } = require("../../utils/deploy.js"); - -module.exports = deployOnSonic( - { - deployName: "018_merkl_pool_booster", - }, - async ({ ethers }) => { - // --------------------------------------------------------------------------------------------------------- - // --- - // --- Contracts - // --- - // --------------------------------------------------------------------------------------------------------- - const cOSonic = await ethers.getContractAt( - "OSonic", - addresses.sonic.OSonicProxy - ); - - const cPoolBoostCentralRegistryProxy = await ethers.getContract( - "PoolBoostCentralRegistryProxy" - ); - - // --------------------------------------------------------------------------------------------------------- - // --- - // --- Upgrade PoolBoosterCentralRegistry - // --- - // --------------------------------------------------------------------------------------------------------- - const dPoolBoosterCentralRegistry = await deployWithConfirmation( - "PoolBoostCentralRegistry", - [] - ); - console.log( - `Deployed new Pool Boost Central Registry to ${dPoolBoosterCentralRegistry.address}` - ); - - const cPoolBoostCentralRegistry = await ethers.getContractAt( - "PoolBoostCentralRegistry", - cPoolBoostCentralRegistryProxy.address - ); - - // --------------------------------------------------------------------------------------------------------- - // --- - // --- Deploy PoolBoosterFactoryMerkl - // --- - // --------------------------------------------------------------------------------------------------------- - const dPoolBoosterFactoryMerkl = await deployWithConfirmation( - "PoolBoosterFactoryMerkl", - [ - cOSonic.address, - addresses.sonic.timelock, - cPoolBoostCentralRegistryProxy.address, - addresses.sonic.MerklDistributor, - ] - ); - const cPoolBoosterMerklFactory = await ethers.getContract( - "PoolBoosterFactoryMerkl" - ); - - console.log( - `Pool Booster Merkl Factory deployed to ${cPoolBoosterMerklFactory.address}` - ); - - return { - name: "Upgrade PoolBoosterCentralRegistry and deploy PoolBoosterFactoryMerkl", - actions: [ - { - contract: cPoolBoostCentralRegistryProxy, - signature: "upgradeTo(address)", - args: [dPoolBoosterCentralRegistry.address], - }, - { - // set the factory as an approved one - contract: cPoolBoostCentralRegistry, - signature: "approveFactory(address)", - args: [dPoolBoosterFactoryMerkl.address], - }, - ], - }; - } -); diff --git a/contracts/deploy/sonic/018_pool_booster_batch.js b/contracts/deploy/sonic/018_pool_booster_batch.js deleted file mode 100644 index 65fb5f2605..0000000000 --- a/contracts/deploy/sonic/018_pool_booster_batch.js +++ /dev/null @@ -1,84 +0,0 @@ -const { deployOnSonic } = require("../../utils/deploy-l2"); -const addresses = require("../../utils/addresses"); -const { createPoolBoosterSonic } = require("../../utils/deploy.js"); -const { oethUnits } = require("../../test/helpers"); - -module.exports = deployOnSonic( - { - deployName: "018_pool_booster_batch", - }, - async ({ ethers }) => { - // --------------------------------------------------------------------------------------------------------- - // --- - // --- Contracts - // --- - // --------------------------------------------------------------------------------------------------------- - const cOSonic = await ethers.getContractAt( - "OSonic", - addresses.sonic.OSonicProxy - ); - - const SALT = ethers.BigNumber.from("1744352622"); - - const cPoolBoosterFactorySwapxSingle = await ethers.getContract( - "PoolBoosterFactorySwapxSingle" - ); - - const cPoolBoosterFactorySwapxDouble = await ethers.getContract( - "PoolBoosterFactorySwapxDouble_v1" - ); - - const cPoolBoosterFactoryMetropolis = await ethers.getContract( - "PoolBoosterFactoryMetropolis" - ); - - // --------------------------------------------------------------------------------------------------------- - // --- - // --- PoolBooster SwapxSingle - // --- - // --------------------------------------------------------------------------------------------------------- - const { actions: actionsSingle } = await createPoolBoosterSonic({ - cOSonic, - factoryContract: cPoolBoosterFactorySwapxSingle, - pools: ["SwapX.OsMYRD"], - salt: SALT, - type: "Single", - }); - - // --------------------------------------------------------------------------------------------------------- - // --- - // --- PoolBooster SwapxDouble - // --- - // --------------------------------------------------------------------------------------------------------- - const { actions: actionsDouble } = await createPoolBoosterSonic({ - cOSonic, - factoryContract: cPoolBoosterFactorySwapxDouble, - pools: ["SwapX.OsBes", "SwapX.OsBRNx"], - salt: SALT, - split: oethUnits("0.7"), - type: "Double", - }); - - // --------------------------------------------------------------------------------------------------------- - // --- - // --- PoolBooster Metropolis - // --- - // --------------------------------------------------------------------------------------------------------- - const { actions: actionsMetropolis } = await createPoolBoosterSonic({ - cOSonic, - factoryContract: cPoolBoosterFactoryMetropolis, - pools: ["Metropolis.OsWs"], - salt: SALT, - type: "Metropolis", - }); - - // --------------------------------------------------------------------------------------------------------- - // --- - // --- Governance Actions - // --- - // --------------------------------------------------------------------------------------------------------- - return { - actions: [...actionsSingle, ...actionsDouble, ...actionsMetropolis], - }; - } -); diff --git a/contracts/deploy/sonic/019_os_vault_based_dripper.js b/contracts/deploy/sonic/019_os_vault_based_dripper.js deleted file mode 100644 index 67366a8b98..0000000000 --- a/contracts/deploy/sonic/019_os_vault_based_dripper.js +++ /dev/null @@ -1,64 +0,0 @@ -const { parseUnits } = require("ethers/lib/utils.js"); -const addresses = require("../../utils/addresses"); -const { deployOnSonic } = require("../../utils/deploy-l2.js"); - -module.exports = deployOnSonic( - { - deployName: "019_os_vault_based_dripper", - }, - async ({ deployWithConfirmation }) => { - // Contract addresses to use - const cOSonicVaultProxy = await ethers.getContract("OSonicVaultProxy"); - const cWS = await ethers.getContractAt("IWrappedSonic", addresses.sonic.wS); - - // Deploy - const dOSonicVaultCore = await deployWithConfirmation("OSonicVaultCore", [ - cWS.address, - ]); - console.log(`Deployed Vault Core to ${dOSonicVaultCore.address}`); - - const dOSonicVaultAdmin = await deployWithConfirmation("OSonicVaultAdmin", [ - cWS.address, - ]); - console.log(`Deployed Vault Admin to ${dOSonicVaultAdmin.address}`); - - // Get vault contract instance - const cOSonicVault = await ethers.getContractAt( - "IVault", - cOSonicVaultProxy.address - ); - - // ---------------- - // Governance Actions - // ---------------- - return { - name: "Add rate limiting to Origin Sonic Vault", - actions: [ - // 1. Upgrade Vault proxy to VaultCore - { - contract: cOSonicVaultProxy, - signature: "upgradeTo(address)", - args: [dOSonicVaultCore.address], - }, - // 2. Set the VaultAdmin - { - contract: cOSonicVault, - signature: "setAdminImpl(address)", - args: [dOSonicVaultAdmin.address], - }, - // 3. Default to a short dripper, since currently we are running zero dripper. - { - contract: cOSonicVault, - signature: "setDripDuration(uint256)", - args: [4 * 60 * 60], - }, - // 4. Default to a 20% APR rebase rate cap - { - contract: cOSonicVault, - signature: "setRebaseRateMax(uint256)", - args: [parseUnits("20", 18)], - }, - ], - }; - } -); diff --git a/contracts/deploy/sonic/020_enable_buyback_operator.js b/contracts/deploy/sonic/020_enable_buyback_operator.js deleted file mode 100644 index c6d296be44..0000000000 --- a/contracts/deploy/sonic/020_enable_buyback_operator.js +++ /dev/null @@ -1,26 +0,0 @@ -const { deployOnSonic } = require("../../utils/deploy-l2.js"); -const addresses = require("../../utils/addresses"); - -module.exports = deployOnSonic( - { - deployName: "020_enable_buyback_operator", - }, - async () => { - const cOSonicVaultProxy = await ethers.getContract("OSonicVaultProxy"); - const cOSonicVault = await ethers.getContractAt( - "IVault", - cOSonicVaultProxy.address - ); - - return { - name: "Enable Buyback Operator", - actions: [ - { - contract: cOSonicVault, - signature: "setTrusteeAddress(address)", - args: [addresses.multichainBuybackOperator], - }, - ], - }; - } -); diff --git a/contracts/deploy/sonic/021_add_validator.js b/contracts/deploy/sonic/021_add_validator.js deleted file mode 100644 index 6301e2a617..0000000000 --- a/contracts/deploy/sonic/021_add_validator.js +++ /dev/null @@ -1,43 +0,0 @@ -const { deployOnSonic } = require("../../utils/deploy-l2.js"); -const addresses = require("../../utils/addresses.js"); - -module.exports = deployOnSonic( - { - deployName: "021_add_validator", - forceSkip: false, - }, - async ({ ethers }) => { - const cOSonicVaultProxy = await ethers.getContract("OSonicVaultProxy"); - const cOSonicVault = await ethers.getContractAt( - "IVault", - cOSonicVaultProxy.address - ); - - // Staking Strategy - const cSonicStakingStrategyProxy = await ethers.getContract( - "SonicStakingStrategyProxy" - ); - const cSonicStakingStrategy = await ethers.getContractAt( - "SonicStakingStrategy", - cSonicStakingStrategyProxy.address - ); - - return { - name: "Config Sonic Staking Strategy", - actions: [ - // 1. Add support for new validator - { - contract: cSonicStakingStrategy, - signature: "supportValidator(uint256)", - args: [45], - }, - // 2. Remove default strategy for wS - { - contract: cOSonicVault, - signature: "setAssetDefaultStrategy(address,address)", - args: [addresses.sonic.wS, addresses.zero], - }, - ], - }; - } -); diff --git a/contracts/deploy/sonic/022_os_upgrade_EIP7702.js b/contracts/deploy/sonic/022_os_upgrade_EIP7702.js deleted file mode 100644 index 8dab5a48a8..0000000000 --- a/contracts/deploy/sonic/022_os_upgrade_EIP7702.js +++ /dev/null @@ -1,29 +0,0 @@ -const { deployOnSonic } = require("../../utils/deploy-l2.js"); -const { deployWithConfirmation } = require("../../utils/deploy.js"); - -module.exports = deployOnSonic( - { - deployName: "022_os_upgrade_EIP7702", - forceSkip: false, - }, - async ({ ethers }) => { - // 1. Deploy new OS implementation - const dOSonic = await deployWithConfirmation("OSonic"); - const cOSonicProxy = await ethers.getContract("OSonicProxy"); - - console.log(`Deployed Origin S to ${dOSonic.address}`); - console.log(`OSonic Proxy at ${cOSonicProxy.address}`); - - return { - name: "Upgrade OS token contract", - actions: [ - // 1. Upgrade the OSonic proxy to the new implementation - { - contract: cOSonicProxy, - signature: "upgradeTo(address)", - args: [dOSonic.address], - }, - ], - }; - } -); diff --git a/contracts/deploy/sonic/023_transfer_pbfactory_governance.js b/contracts/deploy/sonic/023_transfer_pbfactory_governance.js deleted file mode 100644 index e782e5e551..0000000000 --- a/contracts/deploy/sonic/023_transfer_pbfactory_governance.js +++ /dev/null @@ -1,38 +0,0 @@ -const { deployOnSonic } = require("../../utils/deploy-l2.js"); -const addresses = require("../../utils/addresses"); - -module.exports = deployOnSonic( - { - deployName: "023_transfer_pbfactory_governance", - forceSkip: false, - }, - async ({ ethers }) => { - const cPoolBoostCentralRegistryProxy = await ethers.getContract( - "PoolBoostCentralRegistryProxy" - ); - - const cPoolBoostCentralRegistry = await ethers.getContractAt( - "PoolBoostCentralRegistry", - cPoolBoostCentralRegistryProxy.address - ); - - const factories = await cPoolBoostCentralRegistry.getAllFactories(); - - const actions = []; - for (const factory of factories) { - actions.push({ - contract: await ethers.getContractAt( - "AbstractPoolBoosterFactory", - factory - ), - signature: "transferGovernance(address)", - args: [addresses.multichainStrategist], - }); - } - - return { - name: "Transfer PoolBoost Factories governance", - actions, - }; - } -); diff --git a/contracts/deploy/sonic/024_increase_timelock_delay.js b/contracts/deploy/sonic/024_increase_timelock_delay.js deleted file mode 100644 index 8d6f7e506d..0000000000 --- a/contracts/deploy/sonic/024_increase_timelock_delay.js +++ /dev/null @@ -1,28 +0,0 @@ -const addresses = require("../../utils/addresses.js"); -const { deployOnSonic } = require("../../utils/deploy-l2.js"); - -module.exports = deployOnSonic( - { - deployName: "024_increase_timelock_delay", - forceSkip: false, - }, - async ({ ethers }) => { - // 1. Deploy new OS implementation - const cTimelock = await ethers.getContractAt( - "ITimelockController", - addresses.sonic.timelock - ); - - return { - name: "Upgrade OS token contract", - actions: [ - // 1. Update delay to 2d - { - contract: cTimelock, - signature: "updateDelay(uint256)", - args: [24 * 60 * 60 * 2], - }, - ], - }; - } -); diff --git a/contracts/deploy/sonic/025_vault_upgrade.js b/contracts/deploy/sonic/025_vault_upgrade.js deleted file mode 100644 index 0eb82269cb..0000000000 --- a/contracts/deploy/sonic/025_vault_upgrade.js +++ /dev/null @@ -1,37 +0,0 @@ -const { deployOnSonic } = require("../../utils/deploy-l2"); -const { deployWithConfirmation } = require("../../utils/deploy"); -const addresses = require("../../utils/addresses"); - -module.exports = deployOnSonic( - { - deployName: "025_vault_upgrade", - //proposalId: "", - }, - async ({ ethers }) => { - const cOSonicVaultProxy = await ethers.getContract("OSonicVaultProxy"); - - // Deploy new implementation without storage slot checks because of the: - // - Renamed `dripper` to `_deprecated_dripper` - const dOSonicVaultCore = await deployWithConfirmation( - "OSonicVaultCore", - [addresses.sonic.wS], - "OSonicVaultCore", - true - ); - - // ---------------- - // Governance Actions - // ---------------- - return { - name: "Upgrade VaultCore", - actions: [ - // 1. Upgrade VaultCore implementation - { - contract: cOSonicVaultProxy, - signature: "upgradeTo(address)", - args: [dOSonicVaultCore.address], - }, - ], - }; - } -); diff --git a/contracts/deployments/mainnet/.migrations.json b/contracts/deployments/mainnet/.migrations.json index 2c7ea56107..8ff70c6c46 100644 --- a/contracts/deployments/mainnet/.migrations.json +++ b/contracts/deployments/mainnet/.migrations.json @@ -63,5 +63,6 @@ "166_curve_pool_booster_factory": 1769174027, "168_crosschain_strategy_proxies": 1770738208, "169_crosschain_strategy": 1770738961, - "173_improve_curve_pb_module": 1770818413 -} \ No newline at end of file + "173_improve_curve_pb_module": 1770818413, + "177_change_crosschain_strategy_operator": 1771489463 +} diff --git a/contracts/dev.env b/contracts/dev.env index d2515dd8ea..77f3895f7d 100644 --- a/contracts/dev.env +++ b/contracts/dev.env @@ -33,9 +33,6 @@ ACCOUNTS_TO_FUND= # BASESCAN_API_KEY= # SONICSCAN_API_KEY= -# The 1Inch enterprise API -# ONEINCH_API=https://[SET DOMAIN HERE]/v5.0/1/swap - # Test timeout in milliseconds # MOCHA_TIMEOUT=40000 diff --git a/contracts/docs/AaveStrategyHierarchy.svg b/contracts/docs/AaveStrategyHierarchy.svg deleted file mode 100644 index 74f116b8ab..0000000000 --- a/contracts/docs/AaveStrategyHierarchy.svg +++ /dev/null @@ -1,61 +0,0 @@ - - - - - - -UmlClassDiagram - - - -20 - -Governable -../contracts/governance/Governable.sol - - - -150 - -AaveStrategy -../contracts/strategies/AaveStrategy.sol - - - -195 - -<<Abstract>> -InitializableAbstractStrategy -../contracts/utils/InitializableAbstractStrategy.sol - - - -150->195 - - - - - -194 - -<<Abstract>> -Initializable -../contracts/utils/Initializable.sol - - - -195->20 - - - - - -195->194 - - - - - diff --git a/contracts/docs/AaveStrategySquashed.svg b/contracts/docs/AaveStrategySquashed.svg deleted file mode 100644 index 21d2e01374..0000000000 --- a/contracts/docs/AaveStrategySquashed.svg +++ /dev/null @@ -1,99 +0,0 @@ - - - - - - -UmlClassDiagram - - - -150 - -AaveStrategy -../contracts/strategies/AaveStrategy.sol - -Private: -   initialized: bool <<Initializable>> -   initializing: bool <<Initializable>> -   ______gap: uint256[50] <<Initializable>> -   governorPosition: bytes32 <<Governable>> -   pendingGovernorPosition: bytes32 <<Governable>> -   reentryStatusPosition: bytes32 <<Governable>> -   _deprecated_platformAddress: address <<InitializableAbstractStrategy>> -   _deprecated_vaultAddress: address <<InitializableAbstractStrategy>> -   _deprecated_rewardTokenAddress: address <<InitializableAbstractStrategy>> -   _deprecated_rewardLiquidationThreshold: uint256 <<InitializableAbstractStrategy>> -   _reserved: int256[98] <<InitializableAbstractStrategy>> -Internal: -   assetsMapped: address[] <<InitializableAbstractStrategy>> -Public: -   _NOT_ENTERED: uint256 <<Governable>> -   _ENTERED: uint256 <<Governable>> -   platformAddress: address <<InitializableAbstractStrategy>> -   vaultAddress: address <<InitializableAbstractStrategy>> -   assetToPToken: mapping(address=>address) <<InitializableAbstractStrategy>> -   harvesterAddress: address <<InitializableAbstractStrategy>> -   rewardTokenAddresses: address[] <<InitializableAbstractStrategy>> -   referralCode: uint16 <<AaveStrategy>> -   incentivesController: IAaveIncentivesController <<AaveStrategy>> -   stkAave: IAaveStakedToken <<AaveStrategy>> - -Internal: -    _governor(): (governorOut: address) <<Governable>> -    _pendingGovernor(): (pendingGovernor: address) <<Governable>> -    _setGovernor(newGovernor: address) <<Governable>> -    _setPendingGovernor(newGovernor: address) <<Governable>> -    _changeGovernor(_newGovernor: address) <<Governable>> -    _initialize(_rewardTokenAddresses: address[], _assets: address[], _pTokens: address[]) <<InitializableAbstractStrategy>> -    _collectRewardTokens() <<InitializableAbstractStrategy>> -    _setPTokenAddress(_asset: address, _pToken: address) <<InitializableAbstractStrategy>> -    _abstractSetPToken(_asset: address, _aToken: address) <<AaveStrategy>> -    _deposit(_asset: address, _amount: uint256) <<AaveStrategy>> -    _getATokenFor(_asset: address): address <<AaveStrategy>> -    _getLendingPool(): IAaveLendingPool <<AaveStrategy>> -External: -    transferGovernance(_newGovernor: address) <<onlyGovernor>> <<Governable>> -    claimGovernance() <<Governable>> -    collectRewardTokens() <<onlyHarvester, nonReentrant>> <<AaveStrategy>> -    setRewardTokenAddresses(_rewardTokenAddresses: address[]) <<onlyGovernor>> <<InitializableAbstractStrategy>> -    getRewardTokenAddresses(): address[] <<InitializableAbstractStrategy>> -    setPTokenAddress(_asset: address, _pToken: address) <<onlyGovernor>> <<InitializableAbstractStrategy>> -    removePToken(_assetIndex: uint256) <<onlyGovernor>> <<InitializableAbstractStrategy>> -    setHarvesterAddress(_harvesterAddress: address) <<onlyGovernor>> <<InitializableAbstractStrategy>> -    safeApproveAllTokens() <<onlyGovernor, nonReentrant>> <<AaveStrategy>> -    deposit(_asset: address, _amount: uint256) <<onlyVault, nonReentrant>> <<AaveStrategy>> -    depositAll() <<onlyVault, nonReentrant>> <<AaveStrategy>> -    withdraw(_recipient: address, _asset: address, _amount: uint256) <<onlyVault, nonReentrant>> <<AaveStrategy>> -    withdrawAll() <<onlyVaultOrGovernor, nonReentrant>> <<AaveStrategy>> -    checkBalance(_asset: address): (balance: uint256) <<AaveStrategy>> -    initialize(_rewardTokenAddresses: address[], _assets: address[], _pTokens: address[], _incentivesAddress: address, _stkAaveAddress: address) <<onlyGovernor, initializer>> <<AaveStrategy>> -Public: -    <<event>> PendingGovernorshipTransfer(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> GovernorshipTransferred(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> PTokenAdded(_asset: address, _pToken: address) <<InitializableAbstractStrategy>> -    <<event>> PTokenRemoved(_asset: address, _pToken: address) <<InitializableAbstractStrategy>> -    <<event>> Deposit(_asset: address, _pToken: address, _amount: uint256) <<InitializableAbstractStrategy>> -    <<event>> Withdrawal(_asset: address, _pToken: address, _amount: uint256) <<InitializableAbstractStrategy>> -    <<event>> RewardTokenCollected(recipient: address, rewardToken: address, amount: uint256) <<InitializableAbstractStrategy>> -    <<event>> RewardTokenAddressesUpdated(_oldAddresses: address[], _newAddresses: address[]) <<InitializableAbstractStrategy>> -    <<event>> HarvesterAddressesUpdated(_oldHarvesterAddress: address, _newHarvesterAddress: address) <<InitializableAbstractStrategy>> -    <<modifier>> initializer() <<Initializable>> -    <<modifier>> onlyGovernor() <<Governable>> -    <<modifier>> nonReentrant() <<Governable>> -    <<modifier>> onlyVault() <<InitializableAbstractStrategy>> -    <<modifier>> onlyHarvester() <<InitializableAbstractStrategy>> -    <<modifier>> onlyVaultOrGovernor() <<InitializableAbstractStrategy>> -    <<modifier>> onlyVaultOrGovernorOrStrategist() <<InitializableAbstractStrategy>> -    constructor() <<Governable>> -    governor(): address <<Governable>> -    isGovernor(): bool <<Governable>> -    constructor(_stratConfig: BaseStrategyConfig) <<AaveStrategy>> -    transferToken(_asset: address, _amount: uint256) <<onlyGovernor>> <<InitializableAbstractStrategy>> -    supportsAsset(_asset: address): bool <<AaveStrategy>> - - - diff --git a/contracts/docs/AuraWETHPriceFeedHierarchy.svg b/contracts/docs/AuraWETHPriceFeedHierarchy.svg deleted file mode 100644 index 5e6482f5c0..0000000000 --- a/contracts/docs/AuraWETHPriceFeedHierarchy.svg +++ /dev/null @@ -1,74 +0,0 @@ - - - - - - -UmlClassDiagram - - - -20 - -Governable -../contracts/governance/Governable.sol - - - -25 - -Strategizable -../contracts/governance/Strategizable.sol - - - -25->20 - - - - - -227 - -<<Interface>> -IOracleWeightedPool -../contracts/interfaces/balancer/IOracleWeightedPool.sol - - - -229 - -<<Interface>> -AggregatorV3Interface -../contracts/interfaces/chainlink/AggregatorV3Interface.sol - - - -115 - -AuraWETHPriceFeed -../contracts/oracle/AuraWETHPriceFeed.sol - - - -115->25 - - - - - -115->227 - - - - - -115->229 - - - - - diff --git a/contracts/docs/AuraWETHPriceFeedSquashed.svg b/contracts/docs/AuraWETHPriceFeedSquashed.svg deleted file mode 100644 index 6046878f10..0000000000 --- a/contracts/docs/AuraWETHPriceFeedSquashed.svg +++ /dev/null @@ -1,72 +0,0 @@ - - - - - - -UmlClassDiagram - - - -115 - -AuraWETHPriceFeed -../contracts/oracle/AuraWETHPriceFeed.sol - -Private: -   governorPosition: bytes32 <<Governable>> -   pendingGovernorPosition: bytes32 <<Governable>> -   reentryStatusPosition: bytes32 <<Governable>> -   __gap: uint256[50] <<Strategizable>> -Public: -   _NOT_ENTERED: uint256 <<Governable>> -   _ENTERED: uint256 <<Governable>> -   strategistAddr: address <<Strategizable>> -   paused: bool <<AuraWETHPriceFeed>> -   tolerance: uint256 <<AuraWETHPriceFeed>> -   decimals: uint8 <<AuraWETHPriceFeed>> -   description: string <<AuraWETHPriceFeed>> -   version: uint256 <<AuraWETHPriceFeed>> -   auraOracleWeightedPool: IOracleWeightedPool <<AuraWETHPriceFeed>> - -Internal: -    _governor(): (governorOut: address) <<Governable>> -    _pendingGovernor(): (pendingGovernor: address) <<Governable>> -    _setGovernor(newGovernor: address) <<Governable>> -    _setPendingGovernor(newGovernor: address) <<Governable>> -    _changeGovernor(_newGovernor: address) <<Governable>> -    _setStrategistAddr(_address: address) <<Strategizable>> -    _price(): int256 <<AuraWETHPriceFeed>> -External: -     decimals(): uint8 <<AggregatorV3Interface>> -     description(): string <<AggregatorV3Interface>> -     version(): uint256 <<AggregatorV3Interface>> -    getRoundData(uint80): (uint80, int256, uint256, uint256, uint80) <<AuraWETHPriceFeed>> -    latestRoundData(): (uint80, answer: int256, uint256, updatedAt: uint256, uint80) <<AuraWETHPriceFeed>> -    transferGovernance(_newGovernor: address) <<onlyGovernor>> <<Governable>> -    claimGovernance() <<Governable>> -    setStrategistAddr(_address: address) <<onlyGovernor>> <<Strategizable>> -    price(): int256 <<AuraWETHPriceFeed>> -    pause() <<onlyGovernorOrStrategist>> <<AuraWETHPriceFeed>> -    unpause() <<onlyGovernor>> <<AuraWETHPriceFeed>> -    setTolerance(_tolerance: uint256) <<onlyGovernor>> <<AuraWETHPriceFeed>> -Public: -    <<event>> PendingGovernorshipTransfer(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> GovernorshipTransferred(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> StrategistUpdated(_address: address) <<Strategizable>> -    <<event>> PriceFeedPaused() <<AuraWETHPriceFeed>> -    <<event>> PriceFeedUnpaused() <<AuraWETHPriceFeed>> -    <<event>> ToleranceChanged(oldTolerance: uint256, newTolerance: uint256) <<AuraWETHPriceFeed>> -    <<modifier>> onlyGovernor() <<Governable>> -    <<modifier>> nonReentrant() <<Governable>> -    <<modifier>> onlyGovernorOrStrategist() <<Strategizable>> -    constructor() <<Governable>> -    governor(): address <<Governable>> -    isGovernor(): bool <<Governable>> -    constructor(_auraOracleWeightedPool: address, _governor: address) <<AuraWETHPriceFeed>> - - - diff --git a/contracts/docs/AuraWETHPriceFeedStorage.svg b/contracts/docs/AuraWETHPriceFeedStorage.svg deleted file mode 100644 index b991cd1c99..0000000000 --- a/contracts/docs/AuraWETHPriceFeedStorage.svg +++ /dev/null @@ -1,79 +0,0 @@ - - - - - - -StorageDiagram - - - -2 - -AuraWETHPriceFeed <<Contract>> - -slot - -0 - -1-50 - -51 - -52 - -type: <inherited contract>.variable (bytes) - -unallocated (12) - -address: Strategizable.strategistAddr (20) - -uint256[50]: Strategizable.__gap (1600) - -unallocated (31) - -bool: paused (1) - -uint256: tolerance (32) - - - -1 - -uint256[50]: __gap <<Array>> - -slot - -1 - -2 - -3-48 - -49 - -50 - -type: variable (bytes) - -uint256 (32) - -uint256 (32) - ----- (1472) - -uint256 (32) - -uint256 (32) - - - -2:7->1 - - - - - diff --git a/contracts/docs/BalancerMetaPoolStrategyHierarchy.svg b/contracts/docs/BalancerMetaPoolStrategyHierarchy.svg deleted file mode 100644 index fc8cfaa8c3..0000000000 --- a/contracts/docs/BalancerMetaPoolStrategyHierarchy.svg +++ /dev/null @@ -1,89 +0,0 @@ - - - - - - -UmlClassDiagram - - - -20 - -Governable -../contracts/governance/Governable.sol - - - -263 - -BalancerMetaPoolStrategy -../contracts/strategies/balancer/BalancerMetaPoolStrategy.sol - - - -264 - -<<Abstract>> -BaseAuraStrategy -../contracts/strategies/balancer/BaseAuraStrategy.sol - - - -263->264 - - - - - -265 - -<<Abstract>> -BaseBalancerStrategy -../contracts/strategies/balancer/BaseBalancerStrategy.sol - - - -264->265 - - - - - -195 - -<<Abstract>> -InitializableAbstractStrategy -../contracts/utils/InitializableAbstractStrategy.sol - - - -265->195 - - - - - -194 - -<<Abstract>> -Initializable -../contracts/utils/Initializable.sol - - - -195->20 - - - - - -195->194 - - - - - diff --git a/contracts/docs/BalancerMetaPoolStrategySquashed.svg b/contracts/docs/BalancerMetaPoolStrategySquashed.svg deleted file mode 100644 index 0feadd9c97..0000000000 --- a/contracts/docs/BalancerMetaPoolStrategySquashed.svg +++ /dev/null @@ -1,134 +0,0 @@ - - - - - - -UmlClassDiagram - - - -263 - -BalancerMetaPoolStrategy -../contracts/strategies/balancer/BalancerMetaPoolStrategy.sol - -Private: -   initialized: bool <<Initializable>> -   initializing: bool <<Initializable>> -   ______gap: uint256[50] <<Initializable>> -   governorPosition: bytes32 <<Governable>> -   pendingGovernorPosition: bytes32 <<Governable>> -   reentryStatusPosition: bytes32 <<Governable>> -   _deprecated_platformAddress: address <<InitializableAbstractStrategy>> -   _deprecated_vaultAddress: address <<InitializableAbstractStrategy>> -   _deprecated_rewardTokenAddress: address <<InitializableAbstractStrategy>> -   _deprecated_rewardLiquidationThreshold: uint256 <<InitializableAbstractStrategy>> -   _reserved: int256[98] <<InitializableAbstractStrategy>> -   __reserved: int256[48] <<BaseBalancerStrategy>> -   __reserved_baseAuraStrategy: int256[50] <<BaseAuraStrategy>> -Internal: -   assetsMapped: address[] <<InitializableAbstractStrategy>> -Public: -   _NOT_ENTERED: uint256 <<Governable>> -   _ENTERED: uint256 <<Governable>> -   platformAddress: address <<InitializableAbstractStrategy>> -   vaultAddress: address <<InitializableAbstractStrategy>> -   assetToPToken: mapping(address=>address) <<InitializableAbstractStrategy>> -   harvesterAddress: address <<InitializableAbstractStrategy>> -   rewardTokenAddresses: address[] <<InitializableAbstractStrategy>> -   rETH: address <<BaseBalancerStrategy>> -   stETH: address <<BaseBalancerStrategy>> -   wstETH: address <<BaseBalancerStrategy>> -   frxETH: address <<BaseBalancerStrategy>> -   sfrxETH: address <<BaseBalancerStrategy>> -   balancerVault: IBalancerVault <<BaseBalancerStrategy>> -   balancerPoolId: bytes32 <<BaseBalancerStrategy>> -   maxWithdrawalDeviation: uint256 <<BaseBalancerStrategy>> -   maxDepositDeviation: uint256 <<BaseBalancerStrategy>> -   auraRewardPoolAddress: address <<BaseAuraStrategy>> - -Internal: -    _governor(): (governorOut: address) <<Governable>> -    _pendingGovernor(): (pendingGovernor: address) <<Governable>> -    _setGovernor(newGovernor: address) <<Governable>> -    _setPendingGovernor(newGovernor: address) <<Governable>> -    _changeGovernor(_newGovernor: address) <<Governable>> -    _initialize(_rewardTokenAddresses: address[], _assets: address[], _pTokens: address[]) <<InitializableAbstractStrategy>> -    _collectRewardTokens() <<InitializableAbstractStrategy>> -    _setPTokenAddress(_asset: address, _pToken: address) <<InitializableAbstractStrategy>> -    _abstractSetPToken(_asset: address, address) <<BalancerMetaPoolStrategy>> -    _getBalancerPoolTokens(): (balancerPoolTokens: uint256) <<BaseAuraStrategy>> -    _getBPTExpected(_asset: address, _amount: uint256): (bptExpected: uint256) <<BaseBalancerStrategy>> -    _getBPTExpected(_assets: address[], _amounts: uint256[]): (bptExpected: uint256) <<BaseBalancerStrategy>> -    _lpDepositAll() <<BaseAuraStrategy>> -    _lpWithdraw(numBPTTokens: uint256) <<BaseAuraStrategy>> -    _lpWithdrawAll() <<BaseAuraStrategy>> -    _getPoolAssets(): (assets: IERC20[]) <<BaseBalancerStrategy>> -    _toPoolAsset(asset: address, amount: uint256): (poolAsset: address, poolAmount: uint256) <<BaseBalancerStrategy>> -    _toPoolAsset(asset: address): address <<BaseBalancerStrategy>> -    _wrapPoolAsset(asset: address, amount: uint256): (wrappedAsset: address, wrappedAmount: uint256) <<BaseBalancerStrategy>> -    _unwrapPoolAsset(asset: address, amount: uint256): (unwrappedAmount: uint256) <<BaseBalancerStrategy>> -    _fromPoolAsset(poolAsset: address, poolAmount: uint256): (asset: address, amount: uint256) <<BaseBalancerStrategy>> -    _fromPoolAsset(poolAsset: address): (asset: address) <<BaseBalancerStrategy>> -    _approveBase() <<BaseAuraStrategy>> -    _getRateProviderRate(_asset: address): uint256 <<BalancerMetaPoolStrategy>> -    _deposit(_strategyAssets: address[], _strategyAmounts: uint256[]) <<BalancerMetaPoolStrategy>> -    _withdraw(_recipient: address, _strategyAssets: address[], _strategyAmounts: uint256[]) <<BalancerMetaPoolStrategy>> -    _approveAsset(_asset: address) <<BalancerMetaPoolStrategy>> -External: -    transferGovernance(_newGovernor: address) <<onlyGovernor>> <<Governable>> -    claimGovernance() <<Governable>> -    collectRewardTokens() <<onlyHarvester, nonReentrant>> <<BaseAuraStrategy>> -    setRewardTokenAddresses(_rewardTokenAddresses: address[]) <<onlyGovernor>> <<InitializableAbstractStrategy>> -    getRewardTokenAddresses(): address[] <<InitializableAbstractStrategy>> -    setPTokenAddress(_asset: address, _pToken: address) <<onlyGovernor>> <<InitializableAbstractStrategy>> -    removePToken(_assetIndex: uint256) <<onlyGovernor>> <<InitializableAbstractStrategy>> -    setHarvesterAddress(_harvesterAddress: address) <<onlyGovernor>> <<InitializableAbstractStrategy>> -    safeApproveAllTokens() <<onlyGovernor, nonReentrant>> <<BalancerMetaPoolStrategy>> -    deposit(address, uint256) <<onlyVault, nonReentrant>> <<BalancerMetaPoolStrategy>> -    depositAll() <<onlyVault, nonReentrant>> <<BalancerMetaPoolStrategy>> -    withdraw(_recipient: address, _strategyAsset: address, _strategyAmount: uint256) <<onlyVault, nonReentrant>> <<BalancerMetaPoolStrategy>> -    withdrawAll() <<onlyVaultOrGovernor, nonReentrant>> <<BalancerMetaPoolStrategy>> -    checkBalance(_asset: address): (amount: uint256) <<whenNotInBalancerVaultContext>> <<BaseBalancerStrategy>> -    initialize(_rewardTokenAddresses: address[], _assets: address[], _pTokens: address[]) <<onlyGovernor, initializer>> <<BaseBalancerStrategy>> -    checkBalance(): (value: uint256) <<whenNotInBalancerVaultContext>> <<BaseBalancerStrategy>> -    setMaxWithdrawalDeviation(_maxWithdrawalDeviation: uint256) <<onlyVaultOrGovernorOrStrategist>> <<BaseBalancerStrategy>> -    setMaxDepositDeviation(_maxDepositDeviation: uint256) <<onlyVaultOrGovernorOrStrategist>> <<BaseBalancerStrategy>> -    deposit(address[], uint256[]) <<onlyVault, nonReentrant>> <<BalancerMetaPoolStrategy>> -    withdraw(_recipient: address, _strategyAssets: address[], _strategyAmounts: uint256[]) <<onlyVault, nonReentrant>> <<BalancerMetaPoolStrategy>> -Public: -    <<event>> PendingGovernorshipTransfer(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> GovernorshipTransferred(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> PTokenAdded(_asset: address, _pToken: address) <<InitializableAbstractStrategy>> -    <<event>> PTokenRemoved(_asset: address, _pToken: address) <<InitializableAbstractStrategy>> -    <<event>> Deposit(_asset: address, _pToken: address, _amount: uint256) <<InitializableAbstractStrategy>> -    <<event>> Withdrawal(_asset: address, _pToken: address, _amount: uint256) <<InitializableAbstractStrategy>> -    <<event>> RewardTokenCollected(recipient: address, rewardToken: address, amount: uint256) <<InitializableAbstractStrategy>> -    <<event>> RewardTokenAddressesUpdated(_oldAddresses: address[], _newAddresses: address[]) <<InitializableAbstractStrategy>> -    <<event>> HarvesterAddressesUpdated(_oldHarvesterAddress: address, _newHarvesterAddress: address) <<InitializableAbstractStrategy>> -    <<event>> MaxWithdrawalDeviationUpdated(_prevMaxDeviationPercentage: uint256, _newMaxDeviationPercentage: uint256) <<BaseBalancerStrategy>> -    <<event>> MaxDepositDeviationUpdated(_prevMaxDeviationPercentage: uint256, _newMaxDeviationPercentage: uint256) <<BaseBalancerStrategy>> -    <<modifier>> initializer() <<Initializable>> -    <<modifier>> onlyGovernor() <<Governable>> -    <<modifier>> nonReentrant() <<Governable>> -    <<modifier>> onlyVault() <<InitializableAbstractStrategy>> -    <<modifier>> onlyHarvester() <<InitializableAbstractStrategy>> -    <<modifier>> onlyVaultOrGovernor() <<InitializableAbstractStrategy>> -    <<modifier>> onlyVaultOrGovernorOrStrategist() <<InitializableAbstractStrategy>> -    <<modifier>> whenNotInBalancerVaultContext() <<BaseBalancerStrategy>> -    constructor() <<Governable>> -    governor(): address <<Governable>> -    isGovernor(): bool <<Governable>> -    constructor(_config: BaseStrategyConfig) <<InitializableAbstractStrategy>> -    transferToken(_asset: address, _amount: uint256) <<onlyGovernor>> <<InitializableAbstractStrategy>> -    supportsAsset(_asset: address): bool <<BaseBalancerStrategy>> -    constructor(_balancerConfig: BaseBalancerConfig) <<BaseBalancerStrategy>> -    constructor(_auraRewardPoolAddress: address) <<BaseAuraStrategy>> -    constructor(_stratConfig: BaseStrategyConfig, _balancerConfig: BaseBalancerConfig, _auraRewardPoolAddress: address) <<BalancerMetaPoolStrategy>> - - - diff --git a/contracts/docs/BalancerMetaPoolStrategyStorage.svg b/contracts/docs/BalancerMetaPoolStrategyStorage.svg deleted file mode 100644 index 4d93eb2ddb..0000000000 --- a/contracts/docs/BalancerMetaPoolStrategyStorage.svg +++ /dev/null @@ -1,141 +0,0 @@ - - - - - - -StorageDiagram - - - -3 - -BalancerMetaPoolStrategy <<Contract>> - -slot - -0 - -1-50 - -51 - -52 - -53 - -54 - -55 - -56 - -57 - -58 - -59-156 - -157 - -158 - -159-206 - -207-256 - -type: <inherited contract>.variable (bytes) - -unallocated (30) - -bool: Initializable.initializing (1) - -bool: Initializable.initialized (1) - -uint256[50]: Initializable.______gap (1600) - -unallocated (12) - -address: InitializableAbstractStrategy._deprecated_platformAddress (20) - -unallocated (12) - -address: InitializableAbstractStrategy._deprecated_vaultAddress (20) - -mapping(address=>address): InitializableAbstractStrategy.assetToPToken (32) - -address[]: InitializableAbstractStrategy.assetsMapped (32) - -unallocated (12) - -address: InitializableAbstractStrategy._deprecated_rewardTokenAddress (20) - -uint256: InitializableAbstractStrategy._deprecated_rewardLiquidationThreshold (32) - -unallocated (12) - -address: InitializableAbstractStrategy.harvesterAddress (20) - -address[]: InitializableAbstractStrategy.rewardTokenAddresses (32) - -int256[98]: InitializableAbstractStrategy._reserved (3136) - -uint256: BaseBalancerStrategy.maxWithdrawalDeviation (32) - -uint256: BaseBalancerStrategy.maxDepositDeviation (32) - -int256[48]: BaseBalancerStrategy.__reserved (1536) - -int256[50]: BaseAuraStrategy.__reserved_baseAuraStrategy (1600) - - - -1 - -address[]: assetsMapped <<Array>> -0x4a11f94e20a93c79f6ec743a1954ec4fc2c08429ae2122118bf234b2185c81b8 - -offset - -0 - -type: variable (bytes) - -unallocated (12) - -address (20) - - - -3:8->1 - - - - - -2 - -address[]: rewardTokenAddresses <<Array>> -0xa2999d817b6757290b50e8ecf3fa939673403dd35c97de392fdb343b4015ce9e - -offset - -0 - -type: variable (bytes) - -unallocated (12) - -address (20) - - - -3:13->2 - - - - - diff --git a/contracts/docs/ConvexEthMetaStrategyHierarchy.svg b/contracts/docs/BaseCurveAMOStrategyHierarchy.svg similarity index 69% rename from contracts/docs/ConvexEthMetaStrategyHierarchy.svg rename to contracts/docs/BaseCurveAMOStrategyHierarchy.svg index cce9738332..0e8f6f1670 100644 --- a/contracts/docs/ConvexEthMetaStrategyHierarchy.svg +++ b/contracts/docs/BaseCurveAMOStrategyHierarchy.svg @@ -9,51 +9,52 @@ UmlClassDiagram - + -20 - -Governable -../contracts/governance/Governable.sol +40 + +<<Abstract>> +Governable +../contracts/governance/Governable.sol - + -156 - -ConvexEthMetaStrategy -../contracts/strategies/ConvexEthMetaStrategy.sol +203 + +BaseCurveAMOStrategy +../contracts/strategies/BaseCurveAMOStrategy.sol - + -195 +251 <<Abstract>> InitializableAbstractStrategy ../contracts/utils/InitializableAbstractStrategy.sol - + -156->195 +203->251 - + -194 +250 <<Abstract>> Initializable ../contracts/utils/Initializable.sol - + -195->20 - - +251->40 + + - + -195->194 +251->250 diff --git a/contracts/docs/ConvexEthMetaStrategySquashed.svg b/contracts/docs/BaseCurveAMOStrategySquashed.svg similarity index 72% rename from contracts/docs/ConvexEthMetaStrategySquashed.svg rename to contracts/docs/BaseCurveAMOStrategySquashed.svg index 94c4f6c787..ded99cf0a0 100644 --- a/contracts/docs/ConvexEthMetaStrategySquashed.svg +++ b/contracts/docs/BaseCurveAMOStrategySquashed.svg @@ -4,96 +4,89 @@ - - + + UmlClassDiagram - - + + -156 - -ConvexEthMetaStrategy -../contracts/strategies/ConvexEthMetaStrategy.sol - -Private: -   initialized: bool <<Initializable>> -   initializing: bool <<Initializable>> -   ______gap: uint256[50] <<Initializable>> -   governorPosition: bytes32 <<Governable>> -   pendingGovernorPosition: bytes32 <<Governable>> -   reentryStatusPosition: bytes32 <<Governable>> -   _deprecated_platformAddress: address <<InitializableAbstractStrategy>> -   _deprecated_vaultAddress: address <<InitializableAbstractStrategy>> -   _deprecated_rewardTokenAddress: address <<InitializableAbstractStrategy>> -   _deprecated_rewardLiquidationThreshold: uint256 <<InitializableAbstractStrategy>> -   _reserved: int256[98] <<InitializableAbstractStrategy>> -   _deprecated_cvxDepositorAddress: address <<ConvexEthMetaStrategy>> -   _deprecated_cvxRewardStaker: address <<ConvexEthMetaStrategy>> -   _deprecated_cvxDepositorPTokenId: uint256 <<ConvexEthMetaStrategy>> -   _deprecated_curvePool: address <<ConvexEthMetaStrategy>> -   _deprecated_lpToken: address <<ConvexEthMetaStrategy>> -   _deprecated_oeth: address <<ConvexEthMetaStrategy>> -   _deprecated_weth: address <<ConvexEthMetaStrategy>> -   _deprecated_oethCoinIndex: uint128 <<ConvexEthMetaStrategy>> -   _deprecated_ethCoinIndex: uint128 <<ConvexEthMetaStrategy>> -Internal: -   assetsMapped: address[] <<InitializableAbstractStrategy>> -Public: -   _NOT_ENTERED: uint256 <<Governable>> -   _ENTERED: uint256 <<Governable>> -   platformAddress: address <<InitializableAbstractStrategy>> -   vaultAddress: address <<InitializableAbstractStrategy>> -   assetToPToken: mapping(address=>address) <<InitializableAbstractStrategy>> -   harvesterAddress: address <<InitializableAbstractStrategy>> -   rewardTokenAddresses: address[] <<InitializableAbstractStrategy>> -   MAX_SLIPPAGE: uint256 <<ConvexEthMetaStrategy>> -   ETH_ADDRESS: address <<ConvexEthMetaStrategy>> -   cvxDepositorAddress: address <<ConvexEthMetaStrategy>> -   cvxRewardStaker: IRewardStaking <<ConvexEthMetaStrategy>> -   cvxDepositorPTokenId: uint256 <<ConvexEthMetaStrategy>> -   curvePool: ICurveETHPoolV1 <<ConvexEthMetaStrategy>> -   lpToken: IERC20 <<ConvexEthMetaStrategy>> -   oeth: IERC20 <<ConvexEthMetaStrategy>> -   weth: IWETH9 <<ConvexEthMetaStrategy>> -   oethCoinIndex: uint128 <<ConvexEthMetaStrategy>> -   ethCoinIndex: uint128 <<ConvexEthMetaStrategy>> - -Internal: -    _governor(): (governorOut: address) <<Governable>> -    _pendingGovernor(): (pendingGovernor: address) <<Governable>> -    _setGovernor(newGovernor: address) <<Governable>> -    _setPendingGovernor(newGovernor: address) <<Governable>> -    _changeGovernor(_newGovernor: address) <<Governable>> -    _initialize(_rewardTokenAddresses: address[], _assets: address[], _pTokens: address[]) <<InitializableAbstractStrategy>> -    _collectRewardTokens() <<InitializableAbstractStrategy>> -    _setPTokenAddress(_asset: address, _pToken: address) <<InitializableAbstractStrategy>> -    _abstractSetPToken(_asset: address, _pToken: address) <<ConvexEthMetaStrategy>> -    _deposit(_weth: address, _wethAmount: uint256) <<ConvexEthMetaStrategy>> -    calcTokenToBurn(_wethAmount: uint256): (lpToBurn: uint256) <<ConvexEthMetaStrategy>> -    _withdrawAndRemoveFromPool(_lpTokens: uint256, coinIndex: uint128): (coinsRemoved: uint256) <<ConvexEthMetaStrategy>> -    _lpWithdraw(_wethAmount: uint256) <<ConvexEthMetaStrategy>> -    _approveBase() <<ConvexEthMetaStrategy>> -    _max(a: int256, b: int256): int256 <<ConvexEthMetaStrategy>> -External: -    <<payable>> null() <<ConvexEthMetaStrategy>> -    transferGovernance(_newGovernor: address) <<onlyGovernor>> <<Governable>> -    claimGovernance() <<Governable>> -    collectRewardTokens() <<onlyHarvester, nonReentrant>> <<ConvexEthMetaStrategy>> -    setRewardTokenAddresses(_rewardTokenAddresses: address[]) <<onlyGovernor>> <<InitializableAbstractStrategy>> -    getRewardTokenAddresses(): address[] <<InitializableAbstractStrategy>> -    setPTokenAddress(_asset: address, _pToken: address) <<onlyGovernor>> <<InitializableAbstractStrategy>> -    removePToken(_assetIndex: uint256) <<onlyGovernor>> <<InitializableAbstractStrategy>> -    setHarvesterAddress(_harvesterAddress: address) <<onlyGovernor>> <<InitializableAbstractStrategy>> -    safeApproveAllTokens() <<onlyGovernor, nonReentrant>> <<ConvexEthMetaStrategy>> -    deposit(_weth: address, _amount: uint256) <<onlyVault, nonReentrant>> <<ConvexEthMetaStrategy>> -    depositAll() <<onlyVault, nonReentrant>> <<ConvexEthMetaStrategy>> -    withdraw(_recipient: address, _weth: address, _amount: uint256) <<onlyVault, nonReentrant>> <<ConvexEthMetaStrategy>> -    withdrawAll() <<onlyVaultOrGovernor, nonReentrant>> <<ConvexEthMetaStrategy>> -    initialize(_rewardTokenAddresses: address[], _assets: address[]) <<onlyGovernor, initializer>> <<ConvexEthMetaStrategy>> -    mintAndAddOTokens(_oTokens: uint256) <<onlyStrategist, nonReentrant, improvePoolBalance>> <<ConvexEthMetaStrategy>> -    removeAndBurnOTokens(_lpTokens: uint256) <<onlyStrategist, nonReentrant, improvePoolBalance>> <<ConvexEthMetaStrategy>> -    removeOnlyAssets(_lpTokens: uint256) <<onlyStrategist, nonReentrant, improvePoolBalance>> <<ConvexEthMetaStrategy>> +203 + +BaseCurveAMOStrategy +../contracts/strategies/BaseCurveAMOStrategy.sol + +Private: +   initialized: bool <<Initializable>> +   initializing: bool <<Initializable>> +   ______gap: uint256[50] <<Initializable>> +   governorPosition: bytes32 <<Governable>> +   pendingGovernorPosition: bytes32 <<Governable>> +   reentryStatusPosition: bytes32 <<Governable>> +   _deprecated_platformAddress: address <<InitializableAbstractStrategy>> +   _deprecated_vaultAddress: address <<InitializableAbstractStrategy>> +   _deprecated_rewardTokenAddress: address <<InitializableAbstractStrategy>> +   _deprecated_rewardLiquidationThreshold: uint256 <<InitializableAbstractStrategy>> +   _reserved: int256[98] <<InitializableAbstractStrategy>> +Internal: +   assetsMapped: address[] <<InitializableAbstractStrategy>> +Public: +   _NOT_ENTERED: uint256 <<Governable>> +   _ENTERED: uint256 <<Governable>> +   platformAddress: address <<InitializableAbstractStrategy>> +   vaultAddress: address <<InitializableAbstractStrategy>> +   assetToPToken: mapping(address=>address) <<InitializableAbstractStrategy>> +   harvesterAddress: address <<InitializableAbstractStrategy>> +   rewardTokenAddresses: address[] <<InitializableAbstractStrategy>> +   SOLVENCY_THRESHOLD: uint256 <<BaseCurveAMOStrategy>> +   weth: IWETH9 <<BaseCurveAMOStrategy>> +   oeth: IERC20 <<BaseCurveAMOStrategy>> +   lpToken: IERC20 <<BaseCurveAMOStrategy>> +   curvePool: ICurveStableSwapNG <<BaseCurveAMOStrategy>> +   gauge: ICurveXChainLiquidityGauge <<BaseCurveAMOStrategy>> +   gaugeFactory: IChildLiquidityGaugeFactory <<BaseCurveAMOStrategy>> +   oethCoinIndex: uint128 <<BaseCurveAMOStrategy>> +   wethCoinIndex: uint128 <<BaseCurveAMOStrategy>> +   maxSlippage: uint256 <<BaseCurveAMOStrategy>> + +Internal: +    _governor(): (governorOut: address) <<Governable>> +    _pendingGovernor(): (pendingGovernor: address) <<Governable>> +    _setGovernor(newGovernor: address) <<Governable>> +    _setPendingGovernor(newGovernor: address) <<Governable>> +    _changeGovernor(_newGovernor: address) <<Governable>> +    _initialize(_rewardTokenAddresses: address[], _assets: address[], _pTokens: address[]) <<InitializableAbstractStrategy>> +    _collectRewardTokens() <<InitializableAbstractStrategy>> +    _setPTokenAddress(_asset: address, _pToken: address) <<InitializableAbstractStrategy>> +    _abstractSetPToken(_asset: address, _pToken: address) <<BaseCurveAMOStrategy>> +    _deposit(_weth: address, _wethAmount: uint256) <<BaseCurveAMOStrategy>> +    calcTokenToBurn(_wethAmount: uint256): (lpToBurn: uint256) <<BaseCurveAMOStrategy>> +    _withdrawAndRemoveFromPool(_lpTokens: uint256, coinIndex: uint128): (coinsRemoved: uint256) <<BaseCurveAMOStrategy>> +    _solvencyAssert() <<BaseCurveAMOStrategy>> +    _lpWithdraw(_lpAmount: uint256) <<BaseCurveAMOStrategy>> +    _setMaxSlippage(_maxSlippage: uint256) <<BaseCurveAMOStrategy>> +    _approveBase() <<BaseCurveAMOStrategy>> +    _max(a: int256, b: int256): int256 <<BaseCurveAMOStrategy>> +External: +    transferGovernance(_newGovernor: address) <<onlyGovernor>> <<Governable>> +    claimGovernance() <<Governable>> +    collectRewardTokens() <<onlyHarvester, nonReentrant>> <<BaseCurveAMOStrategy>> +    setRewardTokenAddresses(_rewardTokenAddresses: address[]) <<onlyGovernor>> <<InitializableAbstractStrategy>> +    getRewardTokenAddresses(): address[] <<InitializableAbstractStrategy>> +    setPTokenAddress(_asset: address, _pToken: address) <<onlyGovernor>> <<InitializableAbstractStrategy>> +    removePToken(_assetIndex: uint256) <<onlyGovernor>> <<InitializableAbstractStrategy>> +    setHarvesterAddress(_harvesterAddress: address) <<onlyGovernor>> <<InitializableAbstractStrategy>> +    safeApproveAllTokens() <<onlyGovernor, nonReentrant>> <<BaseCurveAMOStrategy>> +    deposit(_weth: address, _amount: uint256) <<onlyVault, nonReentrant>> <<BaseCurveAMOStrategy>> +    depositAll() <<onlyVault, nonReentrant>> <<BaseCurveAMOStrategy>> +    withdraw(_recipient: address, _weth: address, _amount: uint256) <<onlyVault, nonReentrant>> <<BaseCurveAMOStrategy>> +    withdrawAll() <<onlyVaultOrGovernor, nonReentrant>> <<BaseCurveAMOStrategy>> +    checkBalance(_asset: address): (balance: uint256) <<BaseCurveAMOStrategy>> +    initialize(_rewardTokenAddresses: address[], _maxSlippage: uint256) <<onlyGovernor, initializer>> <<BaseCurveAMOStrategy>> +    mintAndAddOTokens(_oTokens: uint256) <<onlyStrategist, nonReentrant, improvePoolBalance>> <<BaseCurveAMOStrategy>> +    removeAndBurnOTokens(_lpTokens: uint256) <<onlyStrategist, nonReentrant, improvePoolBalance>> <<BaseCurveAMOStrategy>> +    removeOnlyAssets(_lpTokens: uint256) <<onlyStrategist, nonReentrant, improvePoolBalance>> <<BaseCurveAMOStrategy>> +    setMaxSlippage(_maxSlippage: uint256) <<onlyGovernor>> <<BaseCurveAMOStrategy>> Public:    <<event>> PendingGovernorshipTransfer(previousGovernor: address, newGovernor: address) <<Governable>>    <<event>> GovernorshipTransferred(previousGovernor: address, newGovernor: address) <<Governable>> @@ -104,23 +97,23 @@    <<event>> RewardTokenCollected(recipient: address, rewardToken: address, amount: uint256) <<InitializableAbstractStrategy>>    <<event>> RewardTokenAddressesUpdated(_oldAddresses: address[], _newAddresses: address[]) <<InitializableAbstractStrategy>>    <<event>> HarvesterAddressesUpdated(_oldHarvesterAddress: address, _newHarvesterAddress: address) <<InitializableAbstractStrategy>> -    <<modifier>> initializer() <<Initializable>> -    <<modifier>> onlyGovernor() <<Governable>> -    <<modifier>> nonReentrant() <<Governable>> -    <<modifier>> onlyVault() <<InitializableAbstractStrategy>> -    <<modifier>> onlyHarvester() <<InitializableAbstractStrategy>> -    <<modifier>> onlyVaultOrGovernor() <<InitializableAbstractStrategy>> -    <<modifier>> onlyVaultOrGovernorOrStrategist() <<InitializableAbstractStrategy>> -    <<modifier>> onlyStrategist() <<ConvexEthMetaStrategy>> -    <<modifier>> improvePoolBalance() <<ConvexEthMetaStrategy>> -    constructor() <<Governable>> -    governor(): address <<Governable>> -    isGovernor(): bool <<Governable>> -    constructor(_config: BaseStrategyConfig) <<InitializableAbstractStrategy>> -    transferToken(_asset: address, _amount: uint256) <<onlyGovernor>> <<InitializableAbstractStrategy>> -    checkBalance(_asset: address): (balance: uint256) <<ConvexEthMetaStrategy>> -    supportsAsset(_asset: address): bool <<ConvexEthMetaStrategy>> -    constructor(_baseConfig: BaseStrategyConfig, _convexConfig: ConvexEthMetaConfig) <<ConvexEthMetaStrategy>> +    <<event>> MaxSlippageUpdated(newMaxSlippage: uint256) <<BaseCurveAMOStrategy>> +    <<modifier>> initializer() <<Initializable>> +    <<modifier>> onlyGovernor() <<Governable>> +    <<modifier>> nonReentrant() <<Governable>> +    <<modifier>> onlyGovernorOrStrategist() <<InitializableAbstractStrategy>> +    <<modifier>> onlyVault() <<InitializableAbstractStrategy>> +    <<modifier>> onlyHarvester() <<InitializableAbstractStrategy>> +    <<modifier>> onlyVaultOrGovernor() <<InitializableAbstractStrategy>> +    <<modifier>> onlyVaultOrGovernorOrStrategist() <<InitializableAbstractStrategy>> +    <<modifier>> onlyStrategist() <<BaseCurveAMOStrategy>> +    <<modifier>> improvePoolBalance() <<BaseCurveAMOStrategy>> +    governor(): address <<Governable>> +    isGovernor(): bool <<Governable>> +    constructor(_config: BaseStrategyConfig) <<InitializableAbstractStrategy>> +    transferToken(_asset: address, _amount: uint256) <<onlyGovernor>> <<InitializableAbstractStrategy>> +    supportsAsset(_asset: address): bool <<BaseCurveAMOStrategy>> +    constructor(_baseConfig: BaseStrategyConfig, _oeth: address, _weth: address, _gauge: address, _gaugeFactory: address, _oethCoinIndex: uint128, _wethCoinIndex: uint128) <<BaseCurveAMOStrategy>> diff --git a/contracts/docs/AaveStrategyStorage.svg b/contracts/docs/BaseCurveAMOStrategyStorage.svg similarity index 51% rename from contracts/docs/AaveStrategyStorage.svg rename to contracts/docs/BaseCurveAMOStrategyStorage.svg index 30c3825b0b..9e1d4aef87 100644 --- a/contracts/docs/AaveStrategyStorage.svg +++ b/contracts/docs/BaseCurveAMOStrategyStorage.svg @@ -4,134 +4,126 @@ - - + + StorageDiagram - + 3 - -AaveStrategy <<Contract>> - -slot - -0 + +BaseCurveAMOStrategy <<Contract>> + +slot -1-50 +0 -51 +1-50 -52 +51 -53 +52 -54 +53 -55 +54 -56 +55 -57 +56 -58 +57 -59-156 +58 -157 +59-156 -158 - -type: <inherited contract>.variable (bytes) - -unallocated (30) - -bool: Initializable.initializing (1) - -bool: Initializable.initialized (1) +157 + +type: <inherited contract>.variable (bytes) -uint256[50]: Initializable.______gap (1600) +unallocated (30) + +bool: Initializable.initializing (1) + +bool: Initializable.initialized (1) -unallocated (12) - -address: InitializableAbstractStrategy._deprecated_platformAddress (20) +uint256[50]: Initializable.______gap (1600) -unallocated (12) - -address: InitializableAbstractStrategy._deprecated_vaultAddress (20) +unallocated (12) + +address: InitializableAbstractStrategy._deprecated_platformAddress (20) -mapping(address=>address): InitializableAbstractStrategy.assetToPToken (32) +unallocated (12) + +address: InitializableAbstractStrategy._deprecated_vaultAddress (20) -address[]: InitializableAbstractStrategy.assetsMapped (32) +mapping(address=>address): InitializableAbstractStrategy.assetToPToken (32) -unallocated (12) - -address: InitializableAbstractStrategy._deprecated_rewardTokenAddress (20) +address[]: InitializableAbstractStrategy.assetsMapped (32) -uint256: InitializableAbstractStrategy._deprecated_rewardLiquidationThreshold (32) +unallocated (12) + +address: InitializableAbstractStrategy._deprecated_rewardTokenAddress (20) -unallocated (12) - -address: InitializableAbstractStrategy.harvesterAddress (20) +uint256: InitializableAbstractStrategy._deprecated_rewardLiquidationThreshold (32) -address[]: InitializableAbstractStrategy.rewardTokenAddresses (32) +unallocated (12) + +address: InitializableAbstractStrategy.harvesterAddress (20) -int256[98]: InitializableAbstractStrategy._reserved (3136) +address[]: InitializableAbstractStrategy.rewardTokenAddresses (32) -unallocated (12) - -IAaveIncentivesController: incentivesController (20) +int256[98]: InitializableAbstractStrategy._reserved (3136) -unallocated (12) - -IAaveStakedToken: stkAave (20) +uint256: maxSlippage (32) 1 - -address[]: assetsMapped <<Array>> -0x4a11f94e20a93c79f6ec743a1954ec4fc2c08429ae2122118bf234b2185c81b8 - -offset - -0 - -type: variable (bytes) - -unallocated (12) - -address (20) + +address[]: assetsMapped <<Array>> +0x4a11f94e20a93c79f6ec743a1954ec4fc2c08429ae2122118bf234b2185c81b8 + +offset + +0 + +type: variable (bytes) + +unallocated (12) + +address (20) 3:8->1 - - + + 2 - -address[]: rewardTokenAddresses <<Array>> -0xa2999d817b6757290b50e8ecf3fa939673403dd35c97de392fdb343b4015ce9e - -offset - -0 - -type: variable (bytes) - -unallocated (12) - -address (20) + +address[]: rewardTokenAddresses <<Array>> +0xa2999d817b6757290b50e8ecf3fa939673403dd35c97de392fdb343b4015ce9e + +offset + +0 + +type: variable (bytes) + +unallocated (12) + +address (20) 3:13->2 - - + + diff --git a/contracts/docs/ConvexEthMetaStrategyStorage.svg b/contracts/docs/ConvexEthMetaStrategyStorage.svg deleted file mode 100644 index 1b7fc49370..0000000000 --- a/contracts/docs/ConvexEthMetaStrategyStorage.svg +++ /dev/null @@ -1,171 +0,0 @@ - - - - - - -StorageDiagram - - - -3 - -ConvexEthMetaStrategy <<Contract>> - -slot - -0 - -1-50 - -51 - -52 - -53 - -54 - -55 - -56 - -57 - -58 - -59-156 - -157 - -158 - -159 - -160 - -161 - -162 - -163 - -164 - -type: <inherited contract>.variable (bytes) - -unallocated (30) - -bool: Initializable.initializing (1) - -bool: Initializable.initialized (1) - -uint256[50]: Initializable.______gap (1600) - -unallocated (12) - -address: InitializableAbstractStrategy._deprecated_platformAddress (20) - -unallocated (12) - -address: InitializableAbstractStrategy._deprecated_vaultAddress (20) - -mapping(address=>address): InitializableAbstractStrategy.assetToPToken (32) - -address[]: InitializableAbstractStrategy.assetsMapped (32) - -unallocated (12) - -address: InitializableAbstractStrategy._deprecated_rewardTokenAddress (20) - -uint256: InitializableAbstractStrategy._deprecated_rewardLiquidationThreshold (32) - -unallocated (12) - -address: InitializableAbstractStrategy.harvesterAddress (20) - -address[]: InitializableAbstractStrategy.rewardTokenAddresses (32) - -int256[98]: InitializableAbstractStrategy._reserved (3136) - -unallocated (12) - -address: _deprecated_cvxDepositorAddress (20) - -unallocated (12) - -address: _deprecated_cvxRewardStaker (20) - -uint256: _deprecated_cvxDepositorPTokenId (32) - -unallocated (12) - -address: _deprecated_curvePool (20) - -unallocated (12) - -address: _deprecated_lpToken (20) - -unallocated (12) - -address: _deprecated_oeth (20) - -unallocated (12) - -address: _deprecated_weth (20) - -uint128: _deprecated_ethCoinIndex (16) - -uint128: _deprecated_oethCoinIndex (16) - - - -1 - -address[]: assetsMapped <<Array>> -0x4a11f94e20a93c79f6ec743a1954ec4fc2c08429ae2122118bf234b2185c81b8 - -offset - -0 - -type: variable (bytes) - -unallocated (12) - -address (20) - - - -3:8->1 - - - - - -2 - -address[]: rewardTokenAddresses <<Array>> -0xa2999d817b6757290b50e8ecf3fa939673403dd35c97de392fdb343b4015ce9e - -offset - -0 - -type: variable (bytes) - -unallocated (12) - -address (20) - - - -3:13->2 - - - - - diff --git a/contracts/docs/ConvexOUSDMetaStrategyHierarchy.svg b/contracts/docs/ConvexOUSDMetaStrategyHierarchy.svg deleted file mode 100644 index 71041626d5..0000000000 --- a/contracts/docs/ConvexOUSDMetaStrategyHierarchy.svg +++ /dev/null @@ -1,89 +0,0 @@ - - - - - - -UmlClassDiagram - - - -20 - -Governable -../contracts/governance/Governable.sol - - - -152 - -<<Abstract>> -BaseConvexMetaStrategy -../contracts/strategies/BaseConvexMetaStrategy.sol - - - -154 - -<<Abstract>> -BaseCurveStrategy -../contracts/strategies/BaseCurveStrategy.sol - - - -152->154 - - - - - -195 - -<<Abstract>> -InitializableAbstractStrategy -../contracts/utils/InitializableAbstractStrategy.sol - - - -154->195 - - - - - -159 - -ConvexOUSDMetaStrategy -../contracts/strategies/ConvexOUSDMetaStrategy.sol - - - -159->152 - - - - - -194 - -<<Abstract>> -Initializable -../contracts/utils/Initializable.sol - - - -195->20 - - - - - -195->194 - - - - - diff --git a/contracts/docs/MorphoAaveStrategyHierarchy.svg b/contracts/docs/CurveAMOStrategyHierarchy.svg similarity index 69% rename from contracts/docs/MorphoAaveStrategyHierarchy.svg rename to contracts/docs/CurveAMOStrategyHierarchy.svg index 4705321bfe..c73c101a90 100644 --- a/contracts/docs/MorphoAaveStrategyHierarchy.svg +++ b/contracts/docs/CurveAMOStrategyHierarchy.svg @@ -9,51 +9,52 @@ UmlClassDiagram - + -20 - -Governable -../contracts/governance/Governable.sol +40 + +<<Abstract>> +Governable +../contracts/governance/Governable.sol - + -176 - -MorphoAaveStrategy -../contracts/strategies/MorphoAaveStrategy.sol +207 + +CurveAMOStrategy +../contracts/strategies/CurveAMOStrategy.sol - + -195 +251 <<Abstract>> InitializableAbstractStrategy ../contracts/utils/InitializableAbstractStrategy.sol - + -176->195 +207->251 - + -194 +250 <<Abstract>> Initializable ../contracts/utils/Initializable.sol - + -195->20 - - +251->40 + + - + -195->194 +251->250 diff --git a/contracts/docs/ConvexOUSDMetaStrategySquashed.svg b/contracts/docs/CurveAMOStrategySquashed.svg similarity index 69% rename from contracts/docs/ConvexOUSDMetaStrategySquashed.svg rename to contracts/docs/CurveAMOStrategySquashed.svg index 7555cc03c6..1acee301f0 100644 --- a/contracts/docs/ConvexOUSDMetaStrategySquashed.svg +++ b/contracts/docs/CurveAMOStrategySquashed.svg @@ -4,117 +4,118 @@ - - + + UmlClassDiagram - - + + -159 - -ConvexOUSDMetaStrategy -../contracts/strategies/ConvexOUSDMetaStrategy.sol - -Private: -   initialized: bool <<Initializable>> -   initializing: bool <<Initializable>> -   ______gap: uint256[50] <<Initializable>> -   governorPosition: bytes32 <<Governable>> -   pendingGovernorPosition: bytes32 <<Governable>> -   reentryStatusPosition: bytes32 <<Governable>> -   _deprecated_platformAddress: address <<InitializableAbstractStrategy>> -   _deprecated_vaultAddress: address <<InitializableAbstractStrategy>> -   _deprecated_rewardTokenAddress: address <<InitializableAbstractStrategy>> -   _deprecated_rewardLiquidationThreshold: uint256 <<InitializableAbstractStrategy>> -   _reserved: int256[98] <<InitializableAbstractStrategy>> -   __reserved: int256[49] <<BaseCurveStrategy>> -   ___reserved: int256[41] <<BaseConvexMetaStrategy>> -Internal: -   assetsMapped: address[] <<InitializableAbstractStrategy>> -   MAX_SLIPPAGE: uint256 <<BaseCurveStrategy>> -   THREEPOOL_ASSET_COUNT: uint256 <<BaseCurveStrategy>> -   pTokenAddress: address <<BaseCurveStrategy>> -   cvxDepositorAddress: address <<BaseConvexMetaStrategy>> -   cvxRewardStakerAddress: address <<BaseConvexMetaStrategy>> -   cvxDepositorPTokenId: uint256 <<BaseConvexMetaStrategy>> -   metapool: ICurveMetaPool <<BaseConvexMetaStrategy>> -   metapoolMainToken: IERC20 <<BaseConvexMetaStrategy>> -   metapoolLPToken: IERC20 <<BaseConvexMetaStrategy>> -   metapoolAssets: address[] <<BaseConvexMetaStrategy>> -   crvCoinIndex: uint128 <<BaseConvexMetaStrategy>> -   mainCoinIndex: uint128 <<BaseConvexMetaStrategy>> -Public: -   _NOT_ENTERED: uint256 <<Governable>> -   _ENTERED: uint256 <<Governable>> -   platformAddress: address <<InitializableAbstractStrategy>> -   vaultAddress: address <<InitializableAbstractStrategy>> -   assetToPToken: mapping(address=>address) <<InitializableAbstractStrategy>> -   harvesterAddress: address <<InitializableAbstractStrategy>> -   rewardTokenAddresses: address[] <<InitializableAbstractStrategy>> -   maxWithdrawalSlippage: uint256 <<BaseConvexMetaStrategy>> - -Internal: -    _governor(): (governorOut: address) <<Governable>> -    _pendingGovernor(): (pendingGovernor: address) <<Governable>> -    _setGovernor(newGovernor: address) <<Governable>> -    _setPendingGovernor(newGovernor: address) <<Governable>> -    _changeGovernor(_newGovernor: address) <<Governable>> -    _initialize(_rewardTokenAddresses: address[], _assets: address[], _pTokens: address[]) <<InitializableAbstractStrategy>> -    _collectRewardTokens() <<InitializableAbstractStrategy>> -    _setPTokenAddress(_asset: address, _pToken: address) <<InitializableAbstractStrategy>> -    _abstractSetPToken(_asset: address, address) <<BaseCurveStrategy>> -    _lpDepositAll() <<ConvexOUSDMetaStrategy>> -    _lpWithdraw(num3CrvTokens: uint256) <<ConvexOUSDMetaStrategy>> -    _lpWithdrawAll() <<ConvexOUSDMetaStrategy>> -    _calcCurveTokenAmount(_coinIndex: uint256, _amount: uint256): (required3Crv: uint256) <<BaseCurveStrategy>> -    _approveAsset(_asset: address) <<BaseCurveStrategy>> -    _approveBase() <<BaseConvexMetaStrategy>> -    _getCoinIndex(_asset: address): uint256 <<BaseCurveStrategy>> -    _calcCurveMetaTokenAmount(_coinIndex: uint128, _amount: uint256): (requiredMetapoolLP: uint256) <<BaseConvexMetaStrategy>> -    _getMetapoolCoinIndex(_asset: address): uint128 <<BaseConvexMetaStrategy>> -    _max(a: int256, b: int256): int256 <<BaseConvexMetaStrategy>> -External: -    transferGovernance(_newGovernor: address) <<onlyGovernor>> <<Governable>> -    claimGovernance() <<Governable>> -    collectRewardTokens() <<onlyHarvester, nonReentrant>> <<BaseConvexMetaStrategy>> -    setRewardTokenAddresses(_rewardTokenAddresses: address[]) <<onlyGovernor>> <<InitializableAbstractStrategy>> -    getRewardTokenAddresses(): address[] <<InitializableAbstractStrategy>> -    setPTokenAddress(_asset: address, _pToken: address) <<onlyGovernor>> <<InitializableAbstractStrategy>> -    removePToken(_assetIndex: uint256) <<onlyGovernor>> <<InitializableAbstractStrategy>> -    setHarvesterAddress(_harvesterAddress: address) <<onlyGovernor>> <<InitializableAbstractStrategy>> -    safeApproveAllTokens() <<onlyGovernor, nonReentrant>> <<BaseCurveStrategy>> -    deposit(_asset: address, _amount: uint256) <<onlyVault, nonReentrant>> <<BaseCurveStrategy>> -    depositAll() <<onlyVault, nonReentrant>> <<BaseCurveStrategy>> -    withdraw(_recipient: address, _asset: address, _amount: uint256) <<onlyVault, nonReentrant>> <<BaseCurveStrategy>> -    withdrawAll() <<onlyVaultOrGovernor, nonReentrant>> <<BaseCurveStrategy>> -    initialize(_rewardTokenAddresses: address[], _assets: address[], _pTokens: address[], initConfig: InitConfig) <<onlyGovernor, initializer>> <<BaseConvexMetaStrategy>> -    setMaxWithdrawalSlippage(_maxWithdrawalSlippage: uint256) <<onlyVaultOrGovernorOrStrategist>> <<BaseConvexMetaStrategy>> -Public: -    <<event>> PendingGovernorshipTransfer(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> GovernorshipTransferred(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> PTokenAdded(_asset: address, _pToken: address) <<InitializableAbstractStrategy>> -    <<event>> PTokenRemoved(_asset: address, _pToken: address) <<InitializableAbstractStrategy>> -    <<event>> Deposit(_asset: address, _pToken: address, _amount: uint256) <<InitializableAbstractStrategy>> -    <<event>> Withdrawal(_asset: address, _pToken: address, _amount: uint256) <<InitializableAbstractStrategy>> -    <<event>> RewardTokenCollected(recipient: address, rewardToken: address, amount: uint256) <<InitializableAbstractStrategy>> -    <<event>> RewardTokenAddressesUpdated(_oldAddresses: address[], _newAddresses: address[]) <<InitializableAbstractStrategy>> -    <<event>> HarvesterAddressesUpdated(_oldHarvesterAddress: address, _newHarvesterAddress: address) <<InitializableAbstractStrategy>> -    <<event>> MaxWithdrawalSlippageUpdated(_prevMaxSlippagePercentage: uint256, _newMaxSlippagePercentage: uint256) <<BaseConvexMetaStrategy>> -    <<modifier>> initializer() <<Initializable>> -    <<modifier>> onlyGovernor() <<Governable>> -    <<modifier>> nonReentrant() <<Governable>> -    <<modifier>> onlyVault() <<InitializableAbstractStrategy>> -    <<modifier>> onlyHarvester() <<InitializableAbstractStrategy>> -    <<modifier>> onlyVaultOrGovernor() <<InitializableAbstractStrategy>> -    <<modifier>> onlyVaultOrGovernorOrStrategist() <<InitializableAbstractStrategy>> -    constructor() <<Governable>> +207 + +CurveAMOStrategy +../contracts/strategies/CurveAMOStrategy.sol + +Private: +   initialized: bool <<Initializable>> +   initializing: bool <<Initializable>> +   ______gap: uint256[50] <<Initializable>> +   governorPosition: bytes32 <<Governable>> +   pendingGovernorPosition: bytes32 <<Governable>> +   reentryStatusPosition: bytes32 <<Governable>> +   _deprecated_platformAddress: address <<InitializableAbstractStrategy>> +   _deprecated_vaultAddress: address <<InitializableAbstractStrategy>> +   _deprecated_rewardTokenAddress: address <<InitializableAbstractStrategy>> +   _deprecated_rewardLiquidationThreshold: uint256 <<InitializableAbstractStrategy>> +   _reserved: int256[98] <<InitializableAbstractStrategy>> +Internal: +   assetsMapped: address[] <<InitializableAbstractStrategy>> +Public: +   _NOT_ENTERED: uint256 <<Governable>> +   _ENTERED: uint256 <<Governable>> +   platformAddress: address <<InitializableAbstractStrategy>> +   vaultAddress: address <<InitializableAbstractStrategy>> +   assetToPToken: mapping(address=>address) <<InitializableAbstractStrategy>> +   harvesterAddress: address <<InitializableAbstractStrategy>> +   rewardTokenAddresses: address[] <<InitializableAbstractStrategy>> +   SOLVENCY_THRESHOLD: uint256 <<CurveAMOStrategy>> +   hardAsset: IERC20 <<CurveAMOStrategy>> +   oToken: IERC20 <<CurveAMOStrategy>> +   lpToken: IERC20 <<CurveAMOStrategy>> +   curvePool: ICurveStableSwapNG <<CurveAMOStrategy>> +   gauge: ICurveLiquidityGaugeV6 <<CurveAMOStrategy>> +   minter: ICurveMinter <<CurveAMOStrategy>> +   otokenCoinIndex: uint128 <<CurveAMOStrategy>> +   hardAssetCoinIndex: uint128 <<CurveAMOStrategy>> +   decimalsOToken: uint8 <<CurveAMOStrategy>> +   decimalsHardAsset: uint8 <<CurveAMOStrategy>> +   maxSlippage: uint256 <<CurveAMOStrategy>> + +Internal: +    _governor(): (governorOut: address) <<Governable>> +    _pendingGovernor(): (pendingGovernor: address) <<Governable>> +    _setGovernor(newGovernor: address) <<Governable>> +    _setPendingGovernor(newGovernor: address) <<Governable>> +    _changeGovernor(_newGovernor: address) <<Governable>> +    _initialize(_rewardTokenAddresses: address[], _assets: address[], _pTokens: address[]) <<InitializableAbstractStrategy>> +    _collectRewardTokens() <<InitializableAbstractStrategy>> +    _setPTokenAddress(_asset: address, _pToken: address) <<InitializableAbstractStrategy>> +    _abstractSetPToken(_asset: address, _pToken: address) <<CurveAMOStrategy>> +    _deposit(_hardAsset: address, _hardAssetAmount: uint256) <<CurveAMOStrategy>> +    calcTokenToBurn(_hardAssetAmount: uint256): (lpToBurn: uint256) <<CurveAMOStrategy>> +    _withdrawAndRemoveFromPool(_lpTokens: uint256, coinIndex: uint128): (coinsRemoved: uint256) <<CurveAMOStrategy>> +    _solvencyAssert() <<CurveAMOStrategy>> +    _lpWithdraw(_lpAmount: uint256) <<CurveAMOStrategy>> +    _setMaxSlippage(_maxSlippage: uint256) <<CurveAMOStrategy>> +    _approveBase() <<CurveAMOStrategy>> +    _max(a: int256, b: int256): int256 <<CurveAMOStrategy>> +External: +    transferGovernance(_newGovernor: address) <<onlyGovernor>> <<Governable>> +    claimGovernance() <<Governable>> +    collectRewardTokens() <<onlyHarvester, nonReentrant>> <<CurveAMOStrategy>> +    setRewardTokenAddresses(_rewardTokenAddresses: address[]) <<onlyGovernor>> <<InitializableAbstractStrategy>> +    getRewardTokenAddresses(): address[] <<InitializableAbstractStrategy>> +    setPTokenAddress(_asset: address, _pToken: address) <<onlyGovernor>> <<InitializableAbstractStrategy>> +    removePToken(_assetIndex: uint256) <<onlyGovernor>> <<InitializableAbstractStrategy>> +    setHarvesterAddress(_harvesterAddress: address) <<onlyGovernor>> <<InitializableAbstractStrategy>> +    safeApproveAllTokens() <<onlyGovernor, nonReentrant>> <<CurveAMOStrategy>> +    deposit(_hardAsset: address, _amount: uint256) <<onlyVault, nonReentrant>> <<CurveAMOStrategy>> +    depositAll() <<onlyVault, nonReentrant>> <<CurveAMOStrategy>> +    withdraw(_recipient: address, _hardAsset: address, _amount: uint256) <<onlyVault, nonReentrant>> <<CurveAMOStrategy>> +    withdrawAll() <<onlyVaultOrGovernor, nonReentrant>> <<CurveAMOStrategy>> +    checkBalance(_asset: address): (balance: uint256) <<CurveAMOStrategy>> +    initialize(_rewardTokenAddresses: address[], _maxSlippage: uint256) <<onlyGovernor, initializer>> <<CurveAMOStrategy>> +    mintAndAddOTokens(_oTokens: uint256) <<onlyStrategist, nonReentrant, improvePoolBalance>> <<CurveAMOStrategy>> +    removeAndBurnOTokens(_lpTokens: uint256) <<onlyStrategist, nonReentrant, improvePoolBalance>> <<CurveAMOStrategy>> +    removeOnlyAssets(_lpTokens: uint256) <<onlyStrategist, nonReentrant, improvePoolBalance>> <<CurveAMOStrategy>> +    setMaxSlippage(_maxSlippage: uint256) <<onlyGovernor>> <<CurveAMOStrategy>> +Public: +    <<event>> PendingGovernorshipTransfer(previousGovernor: address, newGovernor: address) <<Governable>> +    <<event>> GovernorshipTransferred(previousGovernor: address, newGovernor: address) <<Governable>> +    <<event>> PTokenAdded(_asset: address, _pToken: address) <<InitializableAbstractStrategy>> +    <<event>> PTokenRemoved(_asset: address, _pToken: address) <<InitializableAbstractStrategy>> +    <<event>> Deposit(_asset: address, _pToken: address, _amount: uint256) <<InitializableAbstractStrategy>> +    <<event>> Withdrawal(_asset: address, _pToken: address, _amount: uint256) <<InitializableAbstractStrategy>> +    <<event>> RewardTokenCollected(recipient: address, rewardToken: address, amount: uint256) <<InitializableAbstractStrategy>> +    <<event>> RewardTokenAddressesUpdated(_oldAddresses: address[], _newAddresses: address[]) <<InitializableAbstractStrategy>> +    <<event>> HarvesterAddressesUpdated(_oldHarvesterAddress: address, _newHarvesterAddress: address) <<InitializableAbstractStrategy>> +    <<event>> MaxSlippageUpdated(newMaxSlippage: uint256) <<CurveAMOStrategy>> +    <<modifier>> initializer() <<Initializable>> +    <<modifier>> onlyGovernor() <<Governable>> +    <<modifier>> nonReentrant() <<Governable>> +    <<modifier>> onlyGovernorOrStrategist() <<InitializableAbstractStrategy>> +    <<modifier>> onlyVault() <<InitializableAbstractStrategy>> +    <<modifier>> onlyHarvester() <<InitializableAbstractStrategy>> +    <<modifier>> onlyVaultOrGovernor() <<InitializableAbstractStrategy>> +    <<modifier>> onlyVaultOrGovernorOrStrategist() <<InitializableAbstractStrategy>> +    <<modifier>> onlyStrategist() <<CurveAMOStrategy>> +    <<modifier>> improvePoolBalance() <<CurveAMOStrategy>>    governor(): address <<Governable>>    isGovernor(): bool <<Governable>> -    constructor(_stratConfig: BaseStrategyConfig) <<ConvexOUSDMetaStrategy>> +    constructor(_config: BaseStrategyConfig) <<InitializableAbstractStrategy>>    transferToken(_asset: address, _amount: uint256) <<onlyGovernor>> <<InitializableAbstractStrategy>> -    checkBalance(_asset: address): (balance: uint256) <<BaseConvexMetaStrategy>> -    supportsAsset(_asset: address): bool <<BaseCurveStrategy>> +    supportsAsset(_asset: address): bool <<CurveAMOStrategy>> +    constructor(_baseConfig: BaseStrategyConfig, _otoken: address, _hardAsset: address, _gauge: address, _minter: address) <<CurveAMOStrategy>> diff --git a/contracts/docs/CurveAMOStrategyStorage.svg b/contracts/docs/CurveAMOStrategyStorage.svg new file mode 100644 index 0000000000..5b67bf27ec --- /dev/null +++ b/contracts/docs/CurveAMOStrategyStorage.svg @@ -0,0 +1,129 @@ + + + + + + +StorageDiagram + + + +3 + +CurveAMOStrategy <<Contract>> + +slot + +0 + +1-50 + +51 + +52 + +53 + +54 + +55 + +56 + +57 + +58 + +59-156 + +157 + +type: <inherited contract>.variable (bytes) + +unallocated (30) + +bool: Initializable.initializing (1) + +bool: Initializable.initialized (1) + +uint256[50]: Initializable.______gap (1600) + +unallocated (12) + +address: InitializableAbstractStrategy._deprecated_platformAddress (20) + +unallocated (12) + +address: InitializableAbstractStrategy._deprecated_vaultAddress (20) + +mapping(address=>address): InitializableAbstractStrategy.assetToPToken (32) + +address[]: InitializableAbstractStrategy.assetsMapped (32) + +unallocated (12) + +address: InitializableAbstractStrategy._deprecated_rewardTokenAddress (20) + +uint256: InitializableAbstractStrategy._deprecated_rewardLiquidationThreshold (32) + +unallocated (12) + +address: InitializableAbstractStrategy.harvesterAddress (20) + +address[]: InitializableAbstractStrategy.rewardTokenAddresses (32) + +int256[98]: InitializableAbstractStrategy._reserved (3136) + +uint256: maxSlippage (32) + + + +1 + +address[]: assetsMapped <<Array>> +0x4a11f94e20a93c79f6ec743a1954ec4fc2c08429ae2122118bf234b2185c81b8 + +offset + +0 + +type: variable (bytes) + +unallocated (12) + +address (20) + + + +3:8->1 + + + + + +2 + +address[]: rewardTokenAddresses <<Array>> +0xa2999d817b6757290b50e8ecf3fa939673403dd35c97de392fdb343b4015ce9e + +offset + +0 + +type: variable (bytes) + +unallocated (12) + +address (20) + + + +3:13->2 + + + + + diff --git a/contracts/docs/FluxStrategyHierarchy.svg b/contracts/docs/FluxStrategyHierarchy.svg deleted file mode 100644 index fec3306738..0000000000 --- a/contracts/docs/FluxStrategyHierarchy.svg +++ /dev/null @@ -1,88 +0,0 @@ - - - - - - -UmlClassDiagram - - - -20 - -Governable -../contracts/governance/Governable.sol - - - -151 - -<<Abstract>> -BaseCompoundStrategy -../contracts/strategies/BaseCompoundStrategy.sol - - - -195 - -<<Abstract>> -InitializableAbstractStrategy -../contracts/utils/InitializableAbstractStrategy.sol - - - -151->195 - - - - - -155 - -CompoundStrategy -../contracts/strategies/CompoundStrategy.sol - - - -155->151 - - - - - -161 - -FluxStrategy -../contracts/strategies/FluxStrategy.sol - - - -161->155 - - - - - -194 - -<<Abstract>> -Initializable -../contracts/utils/Initializable.sol - - - -195->20 - - - - - -195->194 - - - - - diff --git a/contracts/docs/FluxStrategySquashed.svg b/contracts/docs/FluxStrategySquashed.svg deleted file mode 100644 index 39b26dd5dd..0000000000 --- a/contracts/docs/FluxStrategySquashed.svg +++ /dev/null @@ -1,99 +0,0 @@ - - - - - - -UmlClassDiagram - - - -161 - -FluxStrategy -../contracts/strategies/FluxStrategy.sol - -Private: -   initialized: bool <<Initializable>> -   initializing: bool <<Initializable>> -   ______gap: uint256[50] <<Initializable>> -   governorPosition: bytes32 <<Governable>> -   pendingGovernorPosition: bytes32 <<Governable>> -   reentryStatusPosition: bytes32 <<Governable>> -   _deprecated_platformAddress: address <<InitializableAbstractStrategy>> -   _deprecated_vaultAddress: address <<InitializableAbstractStrategy>> -   _deprecated_rewardTokenAddress: address <<InitializableAbstractStrategy>> -   _deprecated_rewardLiquidationThreshold: uint256 <<InitializableAbstractStrategy>> -   _reserved: int256[98] <<InitializableAbstractStrategy>> -   __reserved: int256[50] <<BaseCompoundStrategy>> -Internal: -   assetsMapped: address[] <<InitializableAbstractStrategy>> -Public: -   _NOT_ENTERED: uint256 <<Governable>> -   _ENTERED: uint256 <<Governable>> -   platformAddress: address <<InitializableAbstractStrategy>> -   vaultAddress: address <<InitializableAbstractStrategy>> -   assetToPToken: mapping(address=>address) <<InitializableAbstractStrategy>> -   harvesterAddress: address <<InitializableAbstractStrategy>> -   rewardTokenAddresses: address[] <<InitializableAbstractStrategy>> - -Internal: -    _governor(): (governorOut: address) <<Governable>> -    _pendingGovernor(): (pendingGovernor: address) <<Governable>> -    _setGovernor(newGovernor: address) <<Governable>> -    _setPendingGovernor(newGovernor: address) <<Governable>> -    _changeGovernor(_newGovernor: address) <<Governable>> -    _initialize(_rewardTokenAddresses: address[], _assets: address[], _pTokens: address[]) <<InitializableAbstractStrategy>> -    _collectRewardTokens() <<InitializableAbstractStrategy>> -    _setPTokenAddress(_asset: address, _pToken: address) <<InitializableAbstractStrategy>> -    _abstractSetPToken(_asset: address, _pToken: address) <<CompoundStrategy>> -    _getCTokenFor(_asset: address): ICERC20 <<BaseCompoundStrategy>> -    _convertUnderlyingToCToken(_cToken: ICERC20, _underlying: uint256): (amount: uint256) <<BaseCompoundStrategy>> -    _deposit(_asset: address, _amount: uint256) <<CompoundStrategy>> -    _checkBalance(_cToken: ICERC20): (balance: uint256) <<CompoundStrategy>> -External: -    transferGovernance(_newGovernor: address) <<onlyGovernor>> <<Governable>> -    claimGovernance() <<Governable>> -    collectRewardTokens() <<FluxStrategy>> -    setRewardTokenAddresses(_rewardTokenAddresses: address[]) <<onlyGovernor>> <<InitializableAbstractStrategy>> -    getRewardTokenAddresses(): address[] <<InitializableAbstractStrategy>> -    setPTokenAddress(_asset: address, _pToken: address) <<onlyGovernor>> <<InitializableAbstractStrategy>> -    removePToken(_assetIndex: uint256) <<onlyGovernor>> <<InitializableAbstractStrategy>> -    setHarvesterAddress(_harvesterAddress: address) <<onlyGovernor>> <<InitializableAbstractStrategy>> -    safeApproveAllTokens() <<CompoundStrategy>> -    deposit(_asset: address, _amount: uint256) <<onlyVault, nonReentrant>> <<CompoundStrategy>> -    depositAll() <<onlyVault, nonReentrant>> <<CompoundStrategy>> -    withdraw(_recipient: address, _asset: address, _amount: uint256) <<onlyVault, nonReentrant>> <<CompoundStrategy>> -    withdrawAll() <<onlyVaultOrGovernor, nonReentrant>> <<CompoundStrategy>> -    checkBalance(_asset: address): (balance: uint256) <<CompoundStrategy>> -    initialize(_rewardTokenAddresses: address[], _assets: address[], _pTokens: address[]) <<onlyGovernor, initializer>> <<CompoundStrategy>> -Public: -    <<event>> PendingGovernorshipTransfer(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> GovernorshipTransferred(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> PTokenAdded(_asset: address, _pToken: address) <<InitializableAbstractStrategy>> -    <<event>> PTokenRemoved(_asset: address, _pToken: address) <<InitializableAbstractStrategy>> -    <<event>> Deposit(_asset: address, _pToken: address, _amount: uint256) <<InitializableAbstractStrategy>> -    <<event>> Withdrawal(_asset: address, _pToken: address, _amount: uint256) <<InitializableAbstractStrategy>> -    <<event>> RewardTokenCollected(recipient: address, rewardToken: address, amount: uint256) <<InitializableAbstractStrategy>> -    <<event>> RewardTokenAddressesUpdated(_oldAddresses: address[], _newAddresses: address[]) <<InitializableAbstractStrategy>> -    <<event>> HarvesterAddressesUpdated(_oldHarvesterAddress: address, _newHarvesterAddress: address) <<InitializableAbstractStrategy>> -    <<event>> SkippedWithdrawal(asset: address, amount: uint256) <<CompoundStrategy>> -    <<modifier>> initializer() <<Initializable>> -    <<modifier>> onlyGovernor() <<Governable>> -    <<modifier>> nonReentrant() <<Governable>> -    <<modifier>> onlyVault() <<InitializableAbstractStrategy>> -    <<modifier>> onlyHarvester() <<InitializableAbstractStrategy>> -    <<modifier>> onlyVaultOrGovernor() <<InitializableAbstractStrategy>> -    <<modifier>> onlyVaultOrGovernorOrStrategist() <<InitializableAbstractStrategy>> -    constructor() <<Governable>> -    governor(): address <<Governable>> -    isGovernor(): bool <<Governable>> -    constructor(_stratConfig: BaseStrategyConfig) <<FluxStrategy>> -    transferToken(_asset: address, _amount: uint256) <<onlyGovernor>> <<InitializableAbstractStrategy>> -    supportsAsset(_asset: address): bool <<BaseCompoundStrategy>> - - - diff --git a/contracts/docs/FluxStrategyStorage.svg b/contracts/docs/FluxStrategyStorage.svg deleted file mode 100644 index 474d87b066..0000000000 --- a/contracts/docs/FluxStrategyStorage.svg +++ /dev/null @@ -1,129 +0,0 @@ - - - - - - -StorageDiagram - - - -3 - -FluxStrategy <<Contract>> - -slot - -0 - -1-50 - -51 - -52 - -53 - -54 - -55 - -56 - -57 - -58 - -59-156 - -157-206 - -type: <inherited contract>.variable (bytes) - -unallocated (30) - -bool: Initializable.initializing (1) - -bool: Initializable.initialized (1) - -uint256[50]: Initializable.______gap (1600) - -unallocated (12) - -address: InitializableAbstractStrategy._deprecated_platformAddress (20) - -unallocated (12) - -address: InitializableAbstractStrategy._deprecated_vaultAddress (20) - -mapping(address=>address): InitializableAbstractStrategy.assetToPToken (32) - -address[]: InitializableAbstractStrategy.assetsMapped (32) - -unallocated (12) - -address: InitializableAbstractStrategy._deprecated_rewardTokenAddress (20) - -uint256: InitializableAbstractStrategy._deprecated_rewardLiquidationThreshold (32) - -unallocated (12) - -address: InitializableAbstractStrategy.harvesterAddress (20) - -address[]: InitializableAbstractStrategy.rewardTokenAddresses (32) - -int256[98]: InitializableAbstractStrategy._reserved (3136) - -int256[50]: BaseCompoundStrategy.__reserved (1600) - - - -1 - -address[]: assetsMapped <<Array>> -0x4a11f94e20a93c79f6ec743a1954ec4fc2c08429ae2122118bf234b2185c81b8 - -offset - -0 - -type: variable (bytes) - -unallocated (12) - -address (20) - - - -3:8->1 - - - - - -2 - -address[]: rewardTokenAddresses <<Array>> -0xa2999d817b6757290b50e8ecf3fa939673403dd35c97de392fdb343b4015ce9e - -offset - -0 - -type: variable (bytes) - -unallocated (12) - -address (20) - - - -3:13->2 - - - - - diff --git a/contracts/docs/FraxETHStrategyHierarchy.svg b/contracts/docs/FraxETHStrategyHierarchy.svg deleted file mode 100644 index 6ed96c287b..0000000000 --- a/contracts/docs/FraxETHStrategyHierarchy.svg +++ /dev/null @@ -1,74 +0,0 @@ - - - - - - -UmlClassDiagram - - - -20 - -Governable -../contracts/governance/Governable.sol - - - -162 - -FraxETHStrategy -../contracts/strategies/FraxETHStrategy.sol - - - -163 - -Generalized4626Strategy -../contracts/strategies/Generalized4626Strategy.sol - - - -162->163 - - - - - -195 - -<<Abstract>> -InitializableAbstractStrategy -../contracts/utils/InitializableAbstractStrategy.sol - - - -163->195 - - - - - -194 - -<<Abstract>> -Initializable -../contracts/utils/Initializable.sol - - - -195->20 - - - - - -195->194 - - - - - diff --git a/contracts/docs/FraxETHStrategySquashed.svg b/contracts/docs/FraxETHStrategySquashed.svg deleted file mode 100644 index a7415eca6c..0000000000 --- a/contracts/docs/FraxETHStrategySquashed.svg +++ /dev/null @@ -1,104 +0,0 @@ - - - - - - -UmlClassDiagram - - - -162 - -FraxETHStrategy -../contracts/strategies/FraxETHStrategy.sol - -Private: -   initialized: bool <<Initializable>> -   initializing: bool <<Initializable>> -   ______gap: uint256[50] <<Initializable>> -   governorPosition: bytes32 <<Governable>> -   pendingGovernorPosition: bytes32 <<Governable>> -   reentryStatusPosition: bytes32 <<Governable>> -   _deprecated_platformAddress: address <<InitializableAbstractStrategy>> -   _deprecated_vaultAddress: address <<InitializableAbstractStrategy>> -   _deprecated_rewardTokenAddress: address <<InitializableAbstractStrategy>> -   _deprecated_rewardLiquidationThreshold: uint256 <<InitializableAbstractStrategy>> -   _reserved: int256[98] <<InitializableAbstractStrategy>> -   _deprecate_shareToken: address <<Generalized4626Strategy>> -   _deprecate_assetToken: address <<Generalized4626Strategy>> -   __gap: uint256[50] <<Generalized4626Strategy>> -Internal: -   assetsMapped: address[] <<InitializableAbstractStrategy>> -Public: -   _NOT_ENTERED: uint256 <<Governable>> -   _ENTERED: uint256 <<Governable>> -   platformAddress: address <<InitializableAbstractStrategy>> -   vaultAddress: address <<InitializableAbstractStrategy>> -   assetToPToken: mapping(address=>address) <<InitializableAbstractStrategy>> -   harvesterAddress: address <<InitializableAbstractStrategy>> -   rewardTokenAddresses: address[] <<InitializableAbstractStrategy>> -   shareToken: IERC20 <<Generalized4626Strategy>> -   assetToken: IERC20 <<Generalized4626Strategy>> -   weth: address <<FraxETHStrategy>> -   fraxETHMinter: IFraxETHMinter <<FraxETHStrategy>> - -Internal: -    _governor(): (governorOut: address) <<Governable>> -    _pendingGovernor(): (pendingGovernor: address) <<Governable>> -    _setGovernor(newGovernor: address) <<Governable>> -    _setPendingGovernor(newGovernor: address) <<Governable>> -    _changeGovernor(_newGovernor: address) <<Governable>> -    _initialize(_rewardTokenAddresses: address[], _assets: address[], _pTokens: address[]) <<InitializableAbstractStrategy>> -    _collectRewardTokens() <<InitializableAbstractStrategy>> -    _setPTokenAddress(_asset: address, _pToken: address) <<InitializableAbstractStrategy>> -    _abstractSetPToken(address, address) <<Generalized4626Strategy>> -    _deposit(_asset: address, _amount: uint256) <<FraxETHStrategy>> -    _approveBase() <<Generalized4626Strategy>> -External: -    <<payable>> null() <<FraxETHStrategy>> -    transferGovernance(_newGovernor: address) <<onlyGovernor>> <<Governable>> -    claimGovernance() <<Governable>> -    collectRewardTokens() <<onlyHarvester, nonReentrant>> <<InitializableAbstractStrategy>> -    setRewardTokenAddresses(_rewardTokenAddresses: address[]) <<onlyGovernor>> <<InitializableAbstractStrategy>> -    getRewardTokenAddresses(): address[] <<InitializableAbstractStrategy>> -    setPTokenAddress(address, address) <<onlyGovernor>> <<Generalized4626Strategy>> -    removePToken(uint256) <<onlyGovernor>> <<Generalized4626Strategy>> -    setHarvesterAddress(_harvesterAddress: address) <<onlyGovernor>> <<InitializableAbstractStrategy>> -    safeApproveAllTokens() <<onlyGovernor>> <<Generalized4626Strategy>> -    deposit(_asset: address, _amount: uint256) <<onlyVault, nonReentrant>> <<Generalized4626Strategy>> -    depositAll() <<onlyVault, nonReentrant>> <<FraxETHStrategy>> -    withdraw(_recipient: address, _asset: address, _amount: uint256) <<onlyVault, nonReentrant>> <<Generalized4626Strategy>> -    withdrawAll() <<onlyVaultOrGovernor, nonReentrant>> <<Generalized4626Strategy>> -    checkBalance(_asset: address): (balance: uint256) <<FraxETHStrategy>> -    initialize() <<onlyGovernor, initializer>> <<FraxETHStrategy>> -Public: -    <<event>> PendingGovernorshipTransfer(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> GovernorshipTransferred(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> PTokenAdded(_asset: address, _pToken: address) <<InitializableAbstractStrategy>> -    <<event>> PTokenRemoved(_asset: address, _pToken: address) <<InitializableAbstractStrategy>> -    <<event>> Deposit(_asset: address, _pToken: address, _amount: uint256) <<InitializableAbstractStrategy>> -    <<event>> Withdrawal(_asset: address, _pToken: address, _amount: uint256) <<InitializableAbstractStrategy>> -    <<event>> RewardTokenCollected(recipient: address, rewardToken: address, amount: uint256) <<InitializableAbstractStrategy>> -    <<event>> RewardTokenAddressesUpdated(_oldAddresses: address[], _newAddresses: address[]) <<InitializableAbstractStrategy>> -    <<event>> HarvesterAddressesUpdated(_oldHarvesterAddress: address, _newHarvesterAddress: address) <<InitializableAbstractStrategy>> -    <<modifier>> initializer() <<Initializable>> -    <<modifier>> onlyGovernor() <<Governable>> -    <<modifier>> nonReentrant() <<Governable>> -    <<modifier>> onlyVault() <<InitializableAbstractStrategy>> -    <<modifier>> onlyHarvester() <<InitializableAbstractStrategy>> -    <<modifier>> onlyVaultOrGovernor() <<InitializableAbstractStrategy>> -    <<modifier>> onlyVaultOrGovernorOrStrategist() <<InitializableAbstractStrategy>> -    constructor() <<Governable>> -    governor(): address <<Governable>> -    isGovernor(): bool <<Governable>> -    constructor(_config: BaseStrategyConfig) <<InitializableAbstractStrategy>> -    transferToken(_asset: address, _amount: uint256) <<onlyGovernor>> <<InitializableAbstractStrategy>> -    supportsAsset(_asset: address): bool <<FraxETHStrategy>> -    constructor(_baseConfig: BaseStrategyConfig, _assetToken: address) <<FraxETHStrategy>> - - - diff --git a/contracts/docs/FraxETHStrategyStorage.svg b/contracts/docs/FraxETHStrategyStorage.svg deleted file mode 100644 index 29e3982d71..0000000000 --- a/contracts/docs/FraxETHStrategyStorage.svg +++ /dev/null @@ -1,249 +0,0 @@ - - - - - - -StorageDiagram - - - -6 - -FraxETHStrategy <<Contract>> - -slot - -0 - -1-50 - -51 - -52 - -53 - -54 - -55 - -56 - -57 - -58 - -59-156 - -157 - -158 - -159-208 - -type: <inherited contract>.variable (bytes) - -unallocated (30) - -bool: Initializable.initializing (1) - -bool: Initializable.initialized (1) - -uint256[50]: Initializable.______gap (1600) - -unallocated (12) - -address: InitializableAbstractStrategy._deprecated_platformAddress (20) - -unallocated (12) - -address: InitializableAbstractStrategy._deprecated_vaultAddress (20) - -mapping(address=>address): InitializableAbstractStrategy.assetToPToken (32) - -address[]: InitializableAbstractStrategy.assetsMapped (32) - -unallocated (12) - -address: InitializableAbstractStrategy._deprecated_rewardTokenAddress (20) - -uint256: InitializableAbstractStrategy._deprecated_rewardLiquidationThreshold (32) - -unallocated (12) - -address: InitializableAbstractStrategy.harvesterAddress (20) - -address[]: InitializableAbstractStrategy.rewardTokenAddresses (32) - -int256[98]: InitializableAbstractStrategy._reserved (3136) - -unallocated (12) - -address: Generalized4626Strategy._deprecate_shareToken (20) - -unallocated (12) - -address: Generalized4626Strategy._deprecate_assetToken (20) - -uint256[50]: Generalized4626Strategy.__gap (1600) - - - -1 - -uint256[50]: ______gap <<Array>> - -slot - -1 - -2 - -3-48 - -49 - -50 - -type: variable (bytes) - -uint256 (32) - -uint256 (32) - ----- (1472) - -uint256 (32) - -uint256 (32) - - - -6:8->1 - - - - - -2 - -address[]: assetsMapped <<Array>> -0x4a11f94e20a93c79f6ec743a1954ec4fc2c08429ae2122118bf234b2185c81b8 - -offset - -0 - -type: variable (bytes) - -unallocated (12) - -address (20) - - - -6:13->2 - - - - - -3 - -address[]: rewardTokenAddresses <<Array>> -0xa2999d817b6757290b50e8ecf3fa939673403dd35c97de392fdb343b4015ce9e - -offset - -0 - -type: variable (bytes) - -unallocated (12) - -address (20) - - - -6:18->3 - - - - - -4 - -int256[98]: _reserved <<Array>> - -slot - -59 - -60 - -61-154 - -155 - -156 - -type: variable (bytes) - -int256 (32) - -int256 (32) - ----- (3008) - -int256 (32) - -int256 (32) - - - -6:24->4 - - - - - -5 - -uint256[50]: __gap <<Array>> - -slot - -159 - -160 - -161-206 - -207 - -208 - -type: variable (bytes) - -uint256 (32) - -uint256 (32) - ----- (1472) - -uint256 (32) - -uint256 (32) - - - -6:32->5 - - - - - diff --git a/contracts/docs/HarvesterHierarchy.svg b/contracts/docs/HarvesterHierarchy.svg deleted file mode 100644 index 1791e1aa00..0000000000 --- a/contracts/docs/HarvesterHierarchy.svg +++ /dev/null @@ -1,47 +0,0 @@ - - - - - - -UmlClassDiagram - - - -21 - -Governable -../contracts/governance/Governable.sol - - - -27 - -<<Abstract>> -AbstractHarvester -../contracts/harvest/AbstractHarvester.sol - - - -27->21 - - - - - -34 - -Harvester -../contracts/harvest/Harvester.sol - - - -34->27 - - - - - diff --git a/contracts/docs/HarvesterSquashed.svg b/contracts/docs/HarvesterSquashed.svg deleted file mode 100644 index 1803a1e1fb..0000000000 --- a/contracts/docs/HarvesterSquashed.svg +++ /dev/null @@ -1,80 +0,0 @@ - - - - - - -UmlClassDiagram - - - -32 - -Harvester -../contracts/harvest/Harvester.sol - -Private: -   governorPosition: bytes32 <<Governable>> -   pendingGovernorPosition: bytes32 <<Governable>> -   reentryStatusPosition: bytes32 <<Governable>> -Public: -   _NOT_ENTERED: uint256 <<Governable>> -   _ENTERED: uint256 <<Governable>> -   rewardTokenConfigs: mapping(address=>RewardTokenConfig) <<AbstractHarvester>> -   supportedStrategies: mapping(address=>bool) <<AbstractHarvester>> -   vaultAddress: address <<AbstractHarvester>> -   rewardProceedsAddress: address <<AbstractHarvester>> -   baseTokenAddress: address <<AbstractHarvester>> -   baseTokenDecimals: uint256 <<AbstractHarvester>> -   uniswapV2Path: mapping(address=>address[]) <<AbstractHarvester>> -   uniswapV3Path: mapping(address=>bytes) <<AbstractHarvester>> -   balancerPoolId: mapping(address=>bytes32) <<AbstractHarvester>> -   curvePoolIndices: mapping(address=>CurvePoolIndices) <<AbstractHarvester>> - -Internal: -    _governor(): (governorOut: address) <<Governable>> -    _pendingGovernor(): (pendingGovernor: address) <<Governable>> -    _setGovernor(newGovernor: address) <<Governable>> -    _setPendingGovernor(newGovernor: address) <<Governable>> -    _changeGovernor(_newGovernor: address) <<Governable>> -    _decodeUniswapV2Path(data: bytes, token: address): (path: address[]) <<AbstractHarvester>> -    _decodeUniswapV3Path(data: bytes, token: address): (path: bytes) <<AbstractHarvester>> -    _decodeBalancerPoolId(data: bytes, balancerVault: address, token: address): (poolId: bytes32) <<AbstractHarvester>> -    _decodeCurvePoolIndices(data: bytes, poolAddress: address, token: address): (indices: CurvePoolIndices) <<AbstractHarvester>> -    _harvestAndSwap(_strategyAddr: address, _rewardTo: address) <<AbstractHarvester>> -    _harvest(_strategyAddr: address) <<AbstractHarvester>> -    _swap(_swapToken: address, _rewardTo: address, _priceProvider: IOracle) <<AbstractHarvester>> -    _doSwap(swapPlatform: SwapPlatform, routerAddress: address, rewardTokenAddress: address, amountIn: uint256, minAmountOut: uint256): (amountOut: uint256) <<AbstractHarvester>> -    _swapWithUniswapV2(routerAddress: address, swapToken: address, amountIn: uint256, minAmountOut: uint256): (amountOut: uint256) <<AbstractHarvester>> -    _swapWithUniswapV3(routerAddress: address, swapToken: address, amountIn: uint256, minAmountOut: uint256): (amountOut: uint256) <<AbstractHarvester>> -    _swapWithBalancer(balancerVaultAddress: address, swapToken: address, amountIn: uint256, minAmountOut: uint256): (amountOut: uint256) <<AbstractHarvester>> -    _swapWithCurve(poolAddress: address, swapToken: address, amountIn: uint256, minAmountOut: uint256): (amountOut: uint256) <<AbstractHarvester>> -External: -    transferGovernance(_newGovernor: address) <<onlyGovernor>> <<Governable>> -    claimGovernance() <<Governable>> -    setRewardProceedsAddress(_rewardProceedsAddress: address) <<onlyGovernor>> <<AbstractHarvester>> -    setRewardTokenConfig(_tokenAddress: address, tokenConfig: RewardTokenConfig, swapData: bytes) <<onlyGovernor>> <<AbstractHarvester>> -    setSupportedStrategy(_strategyAddress: address, _isSupported: bool) <<onlyGovernor>> <<AbstractHarvester>> -    transferToken(_asset: address, _amount: uint256) <<onlyGovernor>> <<AbstractHarvester>> -    harvestAndSwap(_strategyAddr: address) <<nonReentrant>> <<AbstractHarvester>> -    harvestAndSwap(_strategyAddr: address, _rewardTo: address) <<nonReentrant>> <<AbstractHarvester>> -Public: -    <<event>> PendingGovernorshipTransfer(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> GovernorshipTransferred(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> SupportedStrategyUpdate(strategyAddress: address, isSupported: bool) <<AbstractHarvester>> -    <<event>> RewardTokenConfigUpdated(tokenAddress: address, allowedSlippageBps: uint16, harvestRewardBps: uint16, swapPlatform: SwapPlatform, swapPlatformAddr: address, swapData: bytes, liquidationLimit: uint256, doSwapRewardToken: bool) <<AbstractHarvester>> -    <<event>> RewardTokenSwapped(rewardToken: address, swappedInto: address, swapPlatform: SwapPlatform, amountIn: uint256, amountOut: uint256) <<AbstractHarvester>> -    <<event>> RewardProceedsTransferred(token: address, farmer: address, protcolYield: uint256, farmerFee: uint256) <<AbstractHarvester>> -    <<event>> RewardProceedsAddressChanged(newProceedsAddress: address) <<AbstractHarvester>> -    <<modifier>> onlyGovernor() <<Governable>> -    <<modifier>> nonReentrant() <<Governable>> -    constructor() <<Governable>> -    governor(): address <<Governable>> -    isGovernor(): bool <<Governable>> -    constructor(_vault: address, _usdtAddress: address) <<Harvester>> - - - diff --git a/contracts/docs/HarvesterStorage.svg b/contracts/docs/HarvesterStorage.svg deleted file mode 100644 index 0196f73335..0000000000 --- a/contracts/docs/HarvesterStorage.svg +++ /dev/null @@ -1,109 +0,0 @@ - - - - - - -StorageDiagram - - - -3 - -Harvester <<Contract>> - -slot - -0 - -1 - -2 - -3 - -4 - -5 - -6 - -type: <inherited contract>.variable (bytes) - -mapping(address=>RewardTokenConfig): AbstractHarvester.rewardTokenConfigs (32) - -mapping(address=>bool): AbstractHarvester.supportedStrategies (32) - -unallocated (12) - -address: AbstractHarvester.rewardProceedsAddress (20) - -mapping(address=>address[]): AbstractHarvester.uniswapV2Path (32) - -mapping(address=>bytes): AbstractHarvester.uniswapV3Path (32) - -mapping(address=>bytes32): AbstractHarvester.balancerPoolId (32) - -mapping(address=>CurvePoolIndices): AbstractHarvester.curvePoolIndices (32) - - - -1 - -RewardTokenConfig <<Struct>> - -offset - -0 - -1 - -type: variable (bytes) - -unallocated (6) - -SwapPlatform: swapPlatform (1) - -bool: doSwapRewardToken (1) - -address: swapPlatformAddr (20) - -uint16: harvestRewardBps (2) - -uint16: allowedSlippageBps (2) - -uint256: liquidationLimit (32) - - - -3:7->1 - - - - - -2 - -CurvePoolIndices <<Struct>> - -offset - -0 - -type: variable (bytes) - -uint128: baseTokenIndex (16) - -uint128: rewardTokenIndex (16) - - - -3:15->2 - - - - - diff --git a/contracts/docs/MorphoAaveStrategySquashed.svg b/contracts/docs/MorphoAaveStrategySquashed.svg deleted file mode 100644 index 3b1982d39c..0000000000 --- a/contracts/docs/MorphoAaveStrategySquashed.svg +++ /dev/null @@ -1,100 +0,0 @@ - - - - - - -UmlClassDiagram - - - -176 - -MorphoAaveStrategy -../contracts/strategies/MorphoAaveStrategy.sol - -Private: -   initialized: bool <<Initializable>> -   initializing: bool <<Initializable>> -   ______gap: uint256[50] <<Initializable>> -   governorPosition: bytes32 <<Governable>> -   pendingGovernorPosition: bytes32 <<Governable>> -   reentryStatusPosition: bytes32 <<Governable>> -   _deprecated_platformAddress: address <<InitializableAbstractStrategy>> -   _deprecated_vaultAddress: address <<InitializableAbstractStrategy>> -   _deprecated_rewardTokenAddress: address <<InitializableAbstractStrategy>> -   _deprecated_rewardLiquidationThreshold: uint256 <<InitializableAbstractStrategy>> -   _reserved: int256[98] <<InitializableAbstractStrategy>> -Internal: -   assetsMapped: address[] <<InitializableAbstractStrategy>> -Public: -   _NOT_ENTERED: uint256 <<Governable>> -   _ENTERED: uint256 <<Governable>> -   platformAddress: address <<InitializableAbstractStrategy>> -   vaultAddress: address <<InitializableAbstractStrategy>> -   assetToPToken: mapping(address=>address) <<InitializableAbstractStrategy>> -   harvesterAddress: address <<InitializableAbstractStrategy>> -   rewardTokenAddresses: address[] <<InitializableAbstractStrategy>> -   MORPHO: address <<MorphoAaveStrategy>> -   LENS: address <<MorphoAaveStrategy>> - -Internal: -    _governor(): (governorOut: address) <<Governable>> -    _pendingGovernor(): (pendingGovernor: address) <<Governable>> -    _setGovernor(newGovernor: address) <<Governable>> -    _setPendingGovernor(newGovernor: address) <<Governable>> -    _changeGovernor(_newGovernor: address) <<Governable>> -    _initialize(_rewardTokenAddresses: address[], _assets: address[], _pTokens: address[]) <<InitializableAbstractStrategy>> -    _collectRewardTokens() <<InitializableAbstractStrategy>> -    _setPTokenAddress(_asset: address, _pToken: address) <<InitializableAbstractStrategy>> -    _abstractSetPToken(_asset: address, _pToken: address) <<MorphoAaveStrategy>> -    _deposit(_asset: address, _amount: uint256) <<MorphoAaveStrategy>> -    _withdraw(_recipient: address, _asset: address, _amount: uint256) <<MorphoAaveStrategy>> -    _checkBalance(_asset: address): (balance: uint256) <<MorphoAaveStrategy>> -    _getPTokenFor(_asset: address): IERC20 <<MorphoAaveStrategy>> -External: -    transferGovernance(_newGovernor: address) <<onlyGovernor>> <<Governable>> -    claimGovernance() <<Governable>> -    collectRewardTokens() <<onlyHarvester, nonReentrant>> <<MorphoAaveStrategy>> -    setRewardTokenAddresses(_rewardTokenAddresses: address[]) <<onlyGovernor>> <<InitializableAbstractStrategy>> -    getRewardTokenAddresses(): address[] <<InitializableAbstractStrategy>> -    setPTokenAddress(_asset: address, _pToken: address) <<onlyGovernor>> <<InitializableAbstractStrategy>> -    removePToken(_assetIndex: uint256) <<onlyGovernor>> <<InitializableAbstractStrategy>> -    setHarvesterAddress(_harvesterAddress: address) <<onlyGovernor>> <<InitializableAbstractStrategy>> -    safeApproveAllTokens() <<onlyGovernor, nonReentrant>> <<MorphoAaveStrategy>> -    deposit(_asset: address, _amount: uint256) <<onlyVault, nonReentrant>> <<MorphoAaveStrategy>> -    depositAll() <<onlyVault, nonReentrant>> <<MorphoAaveStrategy>> -    withdraw(_recipient: address, _asset: address, _amount: uint256) <<onlyVault, nonReentrant>> <<MorphoAaveStrategy>> -    withdrawAll() <<onlyVaultOrGovernor, nonReentrant>> <<MorphoAaveStrategy>> -    checkBalance(_asset: address): (balance: uint256) <<MorphoAaveStrategy>> -    initialize(_rewardTokenAddresses: address[], _assets: address[], _pTokens: address[]) <<onlyGovernor, initializer>> <<MorphoAaveStrategy>> -    getPendingRewards(): (balance: uint256) <<MorphoAaveStrategy>> -Public: -    <<event>> PendingGovernorshipTransfer(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> GovernorshipTransferred(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> PTokenAdded(_asset: address, _pToken: address) <<InitializableAbstractStrategy>> -    <<event>> PTokenRemoved(_asset: address, _pToken: address) <<InitializableAbstractStrategy>> -    <<event>> Deposit(_asset: address, _pToken: address, _amount: uint256) <<InitializableAbstractStrategy>> -    <<event>> Withdrawal(_asset: address, _pToken: address, _amount: uint256) <<InitializableAbstractStrategy>> -    <<event>> RewardTokenCollected(recipient: address, rewardToken: address, amount: uint256) <<InitializableAbstractStrategy>> -    <<event>> RewardTokenAddressesUpdated(_oldAddresses: address[], _newAddresses: address[]) <<InitializableAbstractStrategy>> -    <<event>> HarvesterAddressesUpdated(_oldHarvesterAddress: address, _newHarvesterAddress: address) <<InitializableAbstractStrategy>> -    <<modifier>> initializer() <<Initializable>> -    <<modifier>> onlyGovernor() <<Governable>> -    <<modifier>> nonReentrant() <<Governable>> -    <<modifier>> onlyVault() <<InitializableAbstractStrategy>> -    <<modifier>> onlyHarvester() <<InitializableAbstractStrategy>> -    <<modifier>> onlyVaultOrGovernor() <<InitializableAbstractStrategy>> -    <<modifier>> onlyVaultOrGovernorOrStrategist() <<InitializableAbstractStrategy>> -    constructor() <<Governable>> -    governor(): address <<Governable>> -    isGovernor(): bool <<Governable>> -    constructor(_stratConfig: BaseStrategyConfig) <<MorphoAaveStrategy>> -    transferToken(_asset: address, _amount: uint256) <<onlyGovernor>> <<InitializableAbstractStrategy>> -    supportsAsset(_asset: address): bool <<MorphoAaveStrategy>> - - - diff --git a/contracts/docs/MorphoAaveStrategyStorage.svg b/contracts/docs/MorphoAaveStrategyStorage.svg deleted file mode 100644 index 6b29aa1889..0000000000 --- a/contracts/docs/MorphoAaveStrategyStorage.svg +++ /dev/null @@ -1,125 +0,0 @@ - - - - - - -StorageDiagram - - - -3 - -MorphoAaveStrategy <<Contract>> - -slot - -0 - -1-50 - -51 - -52 - -53 - -54 - -55 - -56 - -57 - -58 - -59-156 - -type: <inherited contract>.variable (bytes) - -unallocated (30) - -bool: Initializable.initializing (1) - -bool: Initializable.initialized (1) - -uint256[50]: Initializable.______gap (1600) - -unallocated (12) - -address: InitializableAbstractStrategy._deprecated_platformAddress (20) - -unallocated (12) - -address: InitializableAbstractStrategy._deprecated_vaultAddress (20) - -mapping(address=>address): InitializableAbstractStrategy.assetToPToken (32) - -address[]: InitializableAbstractStrategy.assetsMapped (32) - -unallocated (12) - -address: InitializableAbstractStrategy._deprecated_rewardTokenAddress (20) - -uint256: InitializableAbstractStrategy._deprecated_rewardLiquidationThreshold (32) - -unallocated (12) - -address: InitializableAbstractStrategy.harvesterAddress (20) - -address[]: InitializableAbstractStrategy.rewardTokenAddresses (32) - -int256[98]: InitializableAbstractStrategy._reserved (3136) - - - -1 - -address[]: assetsMapped <<Array>> -0x4a11f94e20a93c79f6ec743a1954ec4fc2c08429ae2122118bf234b2185c81b8 - -offset - -0 - -type: variable (bytes) - -unallocated (12) - -address (20) - - - -3:8->1 - - - - - -2 - -address[]: rewardTokenAddresses <<Array>> -0xa2999d817b6757290b50e8ecf3fa939673403dd35c97de392fdb343b4015ce9e - -offset - -0 - -type: variable (bytes) - -unallocated (12) - -address (20) - - - -3:13->2 - - - - - diff --git a/contracts/docs/MorphoCompStrategyHierarchy.svg b/contracts/docs/MorphoCompStrategyHierarchy.svg deleted file mode 100644 index 2800787262..0000000000 --- a/contracts/docs/MorphoCompStrategyHierarchy.svg +++ /dev/null @@ -1,75 +0,0 @@ - - - - - - -UmlClassDiagram - - - -20 - -Governable -../contracts/governance/Governable.sol - - - -151 - -<<Abstract>> -BaseCompoundStrategy -../contracts/strategies/BaseCompoundStrategy.sol - - - -195 - -<<Abstract>> -InitializableAbstractStrategy -../contracts/utils/InitializableAbstractStrategy.sol - - - -151->195 - - - - - -177 - -MorphoCompoundStrategy -../contracts/strategies/MorphoCompoundStrategy.sol - - - -177->151 - - - - - -194 - -<<Abstract>> -Initializable -../contracts/utils/Initializable.sol - - - -195->20 - - - - - -195->194 - - - - - diff --git a/contracts/docs/MorphoCompStrategySquashed.svg b/contracts/docs/MorphoCompStrategySquashed.svg deleted file mode 100644 index f862df9dd0..0000000000 --- a/contracts/docs/MorphoCompStrategySquashed.svg +++ /dev/null @@ -1,102 +0,0 @@ - - - - - - -UmlClassDiagram - - - -177 - -MorphoCompoundStrategy -../contracts/strategies/MorphoCompoundStrategy.sol - -Private: -   initialized: bool <<Initializable>> -   initializing: bool <<Initializable>> -   ______gap: uint256[50] <<Initializable>> -   governorPosition: bytes32 <<Governable>> -   pendingGovernorPosition: bytes32 <<Governable>> -   reentryStatusPosition: bytes32 <<Governable>> -   _deprecated_platformAddress: address <<InitializableAbstractStrategy>> -   _deprecated_vaultAddress: address <<InitializableAbstractStrategy>> -   _deprecated_rewardTokenAddress: address <<InitializableAbstractStrategy>> -   _deprecated_rewardLiquidationThreshold: uint256 <<InitializableAbstractStrategy>> -   _reserved: int256[98] <<InitializableAbstractStrategy>> -   __reserved: int256[50] <<BaseCompoundStrategy>> -Internal: -   assetsMapped: address[] <<InitializableAbstractStrategy>> -Public: -   _NOT_ENTERED: uint256 <<Governable>> -   _ENTERED: uint256 <<Governable>> -   platformAddress: address <<InitializableAbstractStrategy>> -   vaultAddress: address <<InitializableAbstractStrategy>> -   assetToPToken: mapping(address=>address) <<InitializableAbstractStrategy>> -   harvesterAddress: address <<InitializableAbstractStrategy>> -   rewardTokenAddresses: address[] <<InitializableAbstractStrategy>> -   MORPHO: address <<MorphoCompoundStrategy>> -   LENS: address <<MorphoCompoundStrategy>> - -Internal: -    _governor(): (governorOut: address) <<Governable>> -    _pendingGovernor(): (pendingGovernor: address) <<Governable>> -    _setGovernor(newGovernor: address) <<Governable>> -    _setPendingGovernor(newGovernor: address) <<Governable>> -    _changeGovernor(_newGovernor: address) <<Governable>> -    _initialize(_rewardTokenAddresses: address[], _assets: address[], _pTokens: address[]) <<InitializableAbstractStrategy>> -    _collectRewardTokens() <<InitializableAbstractStrategy>> -    _setPTokenAddress(_asset: address, _pToken: address) <<InitializableAbstractStrategy>> -    _abstractSetPToken(_asset: address, _pToken: address) <<MorphoCompoundStrategy>> -    _getCTokenFor(_asset: address): ICERC20 <<BaseCompoundStrategy>> -    _convertUnderlyingToCToken(_cToken: ICERC20, _underlying: uint256): (amount: uint256) <<BaseCompoundStrategy>> -    _deposit(_asset: address, _amount: uint256) <<MorphoCompoundStrategy>> -    _withdraw(_recipient: address, _asset: address, _amount: uint256) <<MorphoCompoundStrategy>> -    _checkBalance(_asset: address): (balance: uint256) <<MorphoCompoundStrategy>> -External: -    transferGovernance(_newGovernor: address) <<onlyGovernor>> <<Governable>> -    claimGovernance() <<Governable>> -    collectRewardTokens() <<onlyHarvester, nonReentrant>> <<MorphoCompoundStrategy>> -    setRewardTokenAddresses(_rewardTokenAddresses: address[]) <<onlyGovernor>> <<InitializableAbstractStrategy>> -    getRewardTokenAddresses(): address[] <<InitializableAbstractStrategy>> -    setPTokenAddress(_asset: address, _pToken: address) <<onlyGovernor>> <<InitializableAbstractStrategy>> -    removePToken(_assetIndex: uint256) <<onlyGovernor>> <<InitializableAbstractStrategy>> -    setHarvesterAddress(_harvesterAddress: address) <<onlyGovernor>> <<InitializableAbstractStrategy>> -    safeApproveAllTokens() <<onlyGovernor, nonReentrant>> <<MorphoCompoundStrategy>> -    deposit(_asset: address, _amount: uint256) <<onlyVault, nonReentrant>> <<MorphoCompoundStrategy>> -    depositAll() <<onlyVault, nonReentrant>> <<MorphoCompoundStrategy>> -    withdraw(_recipient: address, _asset: address, _amount: uint256) <<onlyVault, nonReentrant>> <<MorphoCompoundStrategy>> -    withdrawAll() <<onlyVaultOrGovernor, nonReentrant>> <<MorphoCompoundStrategy>> -    checkBalance(_asset: address): (balance: uint256) <<MorphoCompoundStrategy>> -    initialize(_rewardTokenAddresses: address[], _assets: address[], _pTokens: address[]) <<onlyGovernor, initializer>> <<MorphoCompoundStrategy>> -    getPendingRewards(): (balance: uint256) <<MorphoCompoundStrategy>> -Public: -    <<event>> PendingGovernorshipTransfer(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> GovernorshipTransferred(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> PTokenAdded(_asset: address, _pToken: address) <<InitializableAbstractStrategy>> -    <<event>> PTokenRemoved(_asset: address, _pToken: address) <<InitializableAbstractStrategy>> -    <<event>> Deposit(_asset: address, _pToken: address, _amount: uint256) <<InitializableAbstractStrategy>> -    <<event>> Withdrawal(_asset: address, _pToken: address, _amount: uint256) <<InitializableAbstractStrategy>> -    <<event>> RewardTokenCollected(recipient: address, rewardToken: address, amount: uint256) <<InitializableAbstractStrategy>> -    <<event>> RewardTokenAddressesUpdated(_oldAddresses: address[], _newAddresses: address[]) <<InitializableAbstractStrategy>> -    <<event>> HarvesterAddressesUpdated(_oldHarvesterAddress: address, _newHarvesterAddress: address) <<InitializableAbstractStrategy>> -    <<modifier>> initializer() <<Initializable>> -    <<modifier>> onlyGovernor() <<Governable>> -    <<modifier>> nonReentrant() <<Governable>> -    <<modifier>> onlyVault() <<InitializableAbstractStrategy>> -    <<modifier>> onlyHarvester() <<InitializableAbstractStrategy>> -    <<modifier>> onlyVaultOrGovernor() <<InitializableAbstractStrategy>> -    <<modifier>> onlyVaultOrGovernorOrStrategist() <<InitializableAbstractStrategy>> -    constructor() <<Governable>> -    governor(): address <<Governable>> -    isGovernor(): bool <<Governable>> -    constructor(_stratConfig: BaseStrategyConfig) <<MorphoCompoundStrategy>> -    transferToken(_asset: address, _amount: uint256) <<onlyGovernor>> <<InitializableAbstractStrategy>> -    supportsAsset(_asset: address): bool <<BaseCompoundStrategy>> - - - diff --git a/contracts/docs/MorphoCompStrategyStorage.svg b/contracts/docs/MorphoCompStrategyStorage.svg deleted file mode 100644 index 9f3488e5ef..0000000000 --- a/contracts/docs/MorphoCompStrategyStorage.svg +++ /dev/null @@ -1,129 +0,0 @@ - - - - - - -StorageDiagram - - - -3 - -MorphoCompoundStrategy <<Contract>> - -slot - -0 - -1-50 - -51 - -52 - -53 - -54 - -55 - -56 - -57 - -58 - -59-156 - -157-206 - -type: <inherited contract>.variable (bytes) - -unallocated (30) - -bool: Initializable.initializing (1) - -bool: Initializable.initialized (1) - -uint256[50]: Initializable.______gap (1600) - -unallocated (12) - -address: InitializableAbstractStrategy._deprecated_platformAddress (20) - -unallocated (12) - -address: InitializableAbstractStrategy._deprecated_vaultAddress (20) - -mapping(address=>address): InitializableAbstractStrategy.assetToPToken (32) - -address[]: InitializableAbstractStrategy.assetsMapped (32) - -unallocated (12) - -address: InitializableAbstractStrategy._deprecated_rewardTokenAddress (20) - -uint256: InitializableAbstractStrategy._deprecated_rewardLiquidationThreshold (32) - -unallocated (12) - -address: InitializableAbstractStrategy.harvesterAddress (20) - -address[]: InitializableAbstractStrategy.rewardTokenAddresses (32) - -int256[98]: InitializableAbstractStrategy._reserved (3136) - -int256[50]: BaseCompoundStrategy.__reserved (1600) - - - -1 - -address[]: assetsMapped <<Array>> -0x4a11f94e20a93c79f6ec743a1954ec4fc2c08429ae2122118bf234b2185c81b8 - -offset - -0 - -type: variable (bytes) - -unallocated (12) - -address (20) - - - -3:8->1 - - - - - -2 - -address[]: rewardTokenAddresses <<Array>> -0xa2999d817b6757290b50e8ecf3fa939673403dd35c97de392fdb343b4015ce9e - -offset - -0 - -type: variable (bytes) - -unallocated (12) - -address (20) - - - -3:13->2 - - - - - diff --git a/contracts/docs/OETHBaseHarvesterHierarchy.svg b/contracts/docs/OETHBaseHarvesterHierarchy.svg deleted file mode 100644 index 83e0824f5f..0000000000 --- a/contracts/docs/OETHBaseHarvesterHierarchy.svg +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - -UmlClassDiagram - - - -21 - -Governable -../contracts/governance/Governable.sol - - - -35 - -OETHBaseHarvester -../contracts/harvest/OETHBaseHarvester.sol - - - -35->21 - - - - - diff --git a/contracts/docs/OETHBaseHarvesterStorage.svg b/contracts/docs/OETHBaseHarvesterStorage.svg deleted file mode 100644 index efc29089ec..0000000000 --- a/contracts/docs/OETHBaseHarvesterStorage.svg +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - -StorageDiagram - - - -1 - -OETHBaseHarvester <<Contract>> - -slot - -0 - -type: <inherited contract>.variable (bytes) - -unallocated (12) - -address: operatorAddr (20) - - - diff --git a/contracts/docs/OETHBaseHierarchy.svg b/contracts/docs/OETHBaseHierarchy.svg new file mode 100644 index 0000000000..343aecb4f1 --- /dev/null +++ b/contracts/docs/OETHBaseHierarchy.svg @@ -0,0 +1,47 @@ + + + + + + +UmlClassDiagram + + + +40 + +<<Abstract>> +Governable +../contracts/governance/Governable.sol + + + +226 + +OETHBase +../contracts/token/OETHBase.sol + + + +229 + +OUSD +../contracts/token/OUSD.sol + + + +226->229 + + + + + +229->40 + + + + + diff --git a/contracts/docs/OETHBuybackHierarchy.svg b/contracts/docs/OETHBuybackHierarchy.svg deleted file mode 100644 index b94f9b0376..0000000000 --- a/contracts/docs/OETHBuybackHierarchy.svg +++ /dev/null @@ -1,116 +0,0 @@ - - - - - - -UmlClassDiagram - - - -0 - -<<Abstract>> -BaseBuyback -../contracts/buyback/BaseBuyback.sol - - - -25 - -Strategizable -../contracts/governance/Strategizable.sol - - - -0->25 - - - - - -37 - -<<Interface>> -ICVXLocker -../contracts/interfaces/ICVXLocker.sol - - - -0->37 - - - - - -243 - -<<Interface>> -IUniswapUniversalRouter -../contracts/interfaces/uniswap/IUniswapUniversalRouter.sol - - - -0->243 - - - - - -194 - -<<Abstract>> -Initializable -../contracts/utils/Initializable.sol - - - -0->194 - - - - - -392 - -<<Interface>> -IERC20 -../node_modules/@openzeppelin/contracts/token/ERC20/IERC20.sol - - - -0->392 - - - - - -1 - -OETHBuyback -../contracts/buyback/OETHBuyback.sol - - - -1->0 - - - - - -20 - -Governable -../contracts/governance/Governable.sol - - - -25->20 - - - - - diff --git a/contracts/docs/OETHBuybackSquashed.svg b/contracts/docs/OETHBuybackSquashed.svg deleted file mode 100644 index b2e5cf037d..0000000000 --- a/contracts/docs/OETHBuybackSquashed.svg +++ /dev/null @@ -1,89 +0,0 @@ - - - - - - -UmlClassDiagram - - - -1 - -OETHBuyback -../contracts/buyback/OETHBuyback.sol - -Private: -   initialized: bool <<Initializable>> -   initializing: bool <<Initializable>> -   ______gap: uint256[50] <<Initializable>> -   governorPosition: bytes32 <<Governable>> -   pendingGovernorPosition: bytes32 <<Governable>> -   reentryStatusPosition: bytes32 <<Governable>> -   __gap: uint256[50] <<Strategizable>> -   __deprecated_ousd: address <<BaseBuyback>> -   __deprecated_ogv: address <<BaseBuyback>> -   __deprecated_usdt: address <<BaseBuyback>> -   __deprecated_weth9: address <<BaseBuyback>> -   __deprecated_treasuryBps: uint256 <<BaseBuyback>> -   swapCommand: bytes <<BaseBuyback>> -Public: -   _NOT_ENTERED: uint256 <<Governable>> -   _ENTERED: uint256 <<Governable>> -   strategistAddr: address <<Strategizable>> -   universalRouter: address <<BaseBuyback>> -   rewardsSource: address <<BaseBuyback>> -   treasuryManager: address <<BaseBuyback>> -   oToken: address <<BaseBuyback>> -   ogv: address <<BaseBuyback>> -   cvx: address <<BaseBuyback>> -   cvxLocker: address <<BaseBuyback>> -   ogvPath: bytes <<OETHBuyback>> -   cvxPath: bytes <<OETHBuyback>> - -Internal: -    _governor(): (governorOut: address) <<Governable>> -    _pendingGovernor(): (pendingGovernor: address) <<Governable>> -    _setGovernor(newGovernor: address) <<Governable>> -    _setPendingGovernor(newGovernor: address) <<Governable>> -    _changeGovernor(_newGovernor: address) <<Governable>> -    _setStrategistAddr(_address: address) <<Strategizable>> -    _setUniswapUniversalRouter(_router: address) <<BaseBuyback>> -    _setRewardsSource(_address: address) <<BaseBuyback>> -    _setTreasuryManager(_address: address) <<BaseBuyback>> -    _lockAllCVX() <<BaseBuyback>> -    _getSwapPath(toToken: address): (path: bytes) <<OETHBuyback>> -External: -    transferGovernance(_newGovernor: address) <<onlyGovernor>> <<Governable>> -    claimGovernance() <<Governable>> -    setStrategistAddr(_address: address) <<onlyGovernor>> <<Strategizable>> -    initialize(_uniswapUniversalRouter: address, _strategistAddr: address, _treasuryManagerAddr: address, _rewardsSource: address) <<onlyGovernor, initializer>> <<BaseBuyback>> -    setUniswapUniversalRouter(_router: address) <<onlyGovernor>> <<BaseBuyback>> -    setRewardsSource(_address: address) <<onlyGovernor>> <<BaseBuyback>> -    setTreasuryManager(_address: address) <<onlyGovernor>> <<BaseBuyback>> -    swap(oTokenAmount: uint256, minOGV: uint256, minCVX: uint256) <<onlyGovernorOrStrategist, nonReentrant>> <<BaseBuyback>> -    lockAllCVX() <<onlyGovernorOrStrategist>> <<BaseBuyback>> -    safeApproveAllTokens() <<onlyGovernorOrStrategist>> <<BaseBuyback>> -    transferToken(token: address, amount: uint256) <<onlyGovernor, nonReentrant>> <<BaseBuyback>> -Public: -    <<event>> PendingGovernorshipTransfer(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> GovernorshipTransferred(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> StrategistUpdated(_address: address) <<Strategizable>> -    <<event>> UniswapUniversalRouterUpdated(_address: address) <<BaseBuyback>> -    <<event>> RewardsSourceUpdated(_address: address) <<BaseBuyback>> -    <<event>> TreasuryManagerUpdated(_address: address) <<BaseBuyback>> -    <<event>> OTokenBuyback(oToken: address, swappedFor: address, swapAmountIn: uint256, minExpected: uint256) <<BaseBuyback>> -    <<modifier>> initializer() <<Initializable>> -    <<modifier>> onlyGovernor() <<Governable>> -    <<modifier>> nonReentrant() <<Governable>> -    <<modifier>> onlyGovernorOrStrategist() <<Strategizable>> -    constructor() <<Governable>> -    governor(): address <<Governable>> -    isGovernor(): bool <<Governable>> -    constructor(_oToken: address, _ogv: address, _cvx: address, _cvxLocker: address) <<OETHBuyback>> - - - diff --git a/contracts/docs/OETHBuybackStorage.svg b/contracts/docs/OETHBuybackStorage.svg deleted file mode 100644 index c9ec88a950..0000000000 --- a/contracts/docs/OETHBuybackStorage.svg +++ /dev/null @@ -1,163 +0,0 @@ - - - - - - -StorageDiagram - - - -3 - -OETHBuyback <<Contract>> - -slot - -0 - -1-50 - -51 - -52-101 - -102 - -103 - -104 - -105 - -106 - -107 - -108 - -109 - -type: <inherited contract>.variable (bytes) - -unallocated (30) - -bool: Initializable.initializing (1) - -bool: Initializable.initialized (1) - -uint256[50]: Initializable.______gap (1600) - -unallocated (12) - -address: Strategizable.strategistAddr (20) - -uint256[50]: Strategizable.__gap (1600) - -unallocated (12) - -address: BaseBuyback.universalRouter (20) - -unallocated (12) - -address: BaseBuyback.__deprecated_ousd (20) - -unallocated (12) - -address: BaseBuyback.__deprecated_ogv (20) - -unallocated (12) - -address: BaseBuyback.__deprecated_usdt (20) - -unallocated (12) - -address: BaseBuyback.__deprecated_weth9 (20) - -unallocated (12) - -address: BaseBuyback.rewardsSource (20) - -unallocated (12) - -address: BaseBuyback.treasuryManager (20) - -uint256: BaseBuyback.__deprecated_treasuryBps (32) - - - -1 - -uint256[50]: ______gap <<Array>> - -slot - -1 - -2 - -3-48 - -49 - -50 - -type: variable (bytes) - -uint256 (32) - -uint256 (32) - ----- (1472) - -uint256 (32) - -uint256 (32) - - - -3:8->1 - - - - - -2 - -uint256[50]: __gap <<Array>> - -slot - -52 - -53 - -54-99 - -100 - -101 - -type: variable (bytes) - -uint256 (32) - -uint256 (32) - ----- (1472) - -uint256 (32) - -uint256 (32) - - - -3:15->2 - - - - - diff --git a/contracts/docs/OUSDBuybackHierarchy.svg b/contracts/docs/OUSDBuybackHierarchy.svg deleted file mode 100644 index f086c7dc28..0000000000 --- a/contracts/docs/OUSDBuybackHierarchy.svg +++ /dev/null @@ -1,116 +0,0 @@ - - - - - - -UmlClassDiagram - - - -0 - -<<Abstract>> -BaseBuyback -../contracts/buyback/BaseBuyback.sol - - - -25 - -Strategizable -../contracts/governance/Strategizable.sol - - - -0->25 - - - - - -37 - -<<Interface>> -ICVXLocker -../contracts/interfaces/ICVXLocker.sol - - - -0->37 - - - - - -243 - -<<Interface>> -IUniswapUniversalRouter -../contracts/interfaces/uniswap/IUniswapUniversalRouter.sol - - - -0->243 - - - - - -194 - -<<Abstract>> -Initializable -../contracts/utils/Initializable.sol - - - -0->194 - - - - - -392 - -<<Interface>> -IERC20 -../node_modules/@openzeppelin/contracts/token/ERC20/IERC20.sol - - - -0->392 - - - - - -2 - -OUSDBuyback -../contracts/buyback/OUSDBuyback.sol - - - -2->0 - - - - - -20 - -Governable -../contracts/governance/Governable.sol - - - -25->20 - - - - - diff --git a/contracts/docs/OUSDBuybackSquashed.svg b/contracts/docs/OUSDBuybackSquashed.svg deleted file mode 100644 index 4922f4a525..0000000000 --- a/contracts/docs/OUSDBuybackSquashed.svg +++ /dev/null @@ -1,89 +0,0 @@ - - - - - - -UmlClassDiagram - - - -2 - -OUSDBuyback -../contracts/buyback/OUSDBuyback.sol - -Private: -   initialized: bool <<Initializable>> -   initializing: bool <<Initializable>> -   ______gap: uint256[50] <<Initializable>> -   governorPosition: bytes32 <<Governable>> -   pendingGovernorPosition: bytes32 <<Governable>> -   reentryStatusPosition: bytes32 <<Governable>> -   __gap: uint256[50] <<Strategizable>> -   __deprecated_ousd: address <<BaseBuyback>> -   __deprecated_ogv: address <<BaseBuyback>> -   __deprecated_usdt: address <<BaseBuyback>> -   __deprecated_weth9: address <<BaseBuyback>> -   __deprecated_treasuryBps: uint256 <<BaseBuyback>> -   swapCommand: bytes <<BaseBuyback>> -Public: -   _NOT_ENTERED: uint256 <<Governable>> -   _ENTERED: uint256 <<Governable>> -   strategistAddr: address <<Strategizable>> -   universalRouter: address <<BaseBuyback>> -   rewardsSource: address <<BaseBuyback>> -   treasuryManager: address <<BaseBuyback>> -   oToken: address <<BaseBuyback>> -   ogv: address <<BaseBuyback>> -   cvx: address <<BaseBuyback>> -   cvxLocker: address <<BaseBuyback>> -   ogvPath: bytes <<OUSDBuyback>> -   cvxPath: bytes <<OUSDBuyback>> - -Internal: -    _governor(): (governorOut: address) <<Governable>> -    _pendingGovernor(): (pendingGovernor: address) <<Governable>> -    _setGovernor(newGovernor: address) <<Governable>> -    _setPendingGovernor(newGovernor: address) <<Governable>> -    _changeGovernor(_newGovernor: address) <<Governable>> -    _setStrategistAddr(_address: address) <<Strategizable>> -    _setUniswapUniversalRouter(_router: address) <<BaseBuyback>> -    _setRewardsSource(_address: address) <<BaseBuyback>> -    _setTreasuryManager(_address: address) <<BaseBuyback>> -    _lockAllCVX() <<BaseBuyback>> -    _getSwapPath(toToken: address): (path: bytes) <<OUSDBuyback>> -External: -    transferGovernance(_newGovernor: address) <<onlyGovernor>> <<Governable>> -    claimGovernance() <<Governable>> -    setStrategistAddr(_address: address) <<onlyGovernor>> <<Strategizable>> -    initialize(_uniswapUniversalRouter: address, _strategistAddr: address, _treasuryManagerAddr: address, _rewardsSource: address) <<onlyGovernor, initializer>> <<BaseBuyback>> -    setUniswapUniversalRouter(_router: address) <<onlyGovernor>> <<BaseBuyback>> -    setRewardsSource(_address: address) <<onlyGovernor>> <<BaseBuyback>> -    setTreasuryManager(_address: address) <<onlyGovernor>> <<BaseBuyback>> -    swap(oTokenAmount: uint256, minOGV: uint256, minCVX: uint256) <<onlyGovernorOrStrategist, nonReentrant>> <<BaseBuyback>> -    lockAllCVX() <<onlyGovernorOrStrategist>> <<BaseBuyback>> -    safeApproveAllTokens() <<onlyGovernorOrStrategist>> <<BaseBuyback>> -    transferToken(token: address, amount: uint256) <<onlyGovernor, nonReentrant>> <<BaseBuyback>> -Public: -    <<event>> PendingGovernorshipTransfer(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> GovernorshipTransferred(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> StrategistUpdated(_address: address) <<Strategizable>> -    <<event>> UniswapUniversalRouterUpdated(_address: address) <<BaseBuyback>> -    <<event>> RewardsSourceUpdated(_address: address) <<BaseBuyback>> -    <<event>> TreasuryManagerUpdated(_address: address) <<BaseBuyback>> -    <<event>> OTokenBuyback(oToken: address, swappedFor: address, swapAmountIn: uint256, minExpected: uint256) <<BaseBuyback>> -    <<modifier>> initializer() <<Initializable>> -    <<modifier>> onlyGovernor() <<Governable>> -    <<modifier>> nonReentrant() <<Governable>> -    <<modifier>> onlyGovernorOrStrategist() <<Strategizable>> -    constructor() <<Governable>> -    governor(): address <<Governable>> -    isGovernor(): bool <<Governable>> -    constructor(_oToken: address, _ogv: address, _cvx: address, _cvxLocker: address) <<OUSDBuyback>> - - - diff --git a/contracts/docs/OUSDBuybackStorage.svg b/contracts/docs/OUSDBuybackStorage.svg deleted file mode 100644 index b89da451e7..0000000000 --- a/contracts/docs/OUSDBuybackStorage.svg +++ /dev/null @@ -1,163 +0,0 @@ - - - - - - -StorageDiagram - - - -3 - -OUSDBuyback <<Contract>> - -slot - -0 - -1-50 - -51 - -52-101 - -102 - -103 - -104 - -105 - -106 - -107 - -108 - -109 - -type: <inherited contract>.variable (bytes) - -unallocated (30) - -bool: Initializable.initializing (1) - -bool: Initializable.initialized (1) - -uint256[50]: Initializable.______gap (1600) - -unallocated (12) - -address: Strategizable.strategistAddr (20) - -uint256[50]: Strategizable.__gap (1600) - -unallocated (12) - -address: BaseBuyback.universalRouter (20) - -unallocated (12) - -address: BaseBuyback.__deprecated_ousd (20) - -unallocated (12) - -address: BaseBuyback.__deprecated_ogv (20) - -unallocated (12) - -address: BaseBuyback.__deprecated_usdt (20) - -unallocated (12) - -address: BaseBuyback.__deprecated_weth9 (20) - -unallocated (12) - -address: BaseBuyback.rewardsSource (20) - -unallocated (12) - -address: BaseBuyback.treasuryManager (20) - -uint256: BaseBuyback.__deprecated_treasuryBps (32) - - - -1 - -uint256[50]: ______gap <<Array>> - -slot - -1 - -2 - -3-48 - -49 - -50 - -type: variable (bytes) - -uint256 (32) - -uint256 (32) - ----- (1472) - -uint256 (32) - -uint256 (32) - - - -3:8->1 - - - - - -2 - -uint256[50]: __gap <<Array>> - -slot - -52 - -53 - -54-99 - -100 - -101 - -type: variable (bytes) - -uint256 (32) - -uint256 (32) - ----- (1472) - -uint256 (32) - -uint256 (32) - - - -3:15->2 - - - - - diff --git a/contracts/docs/PoolBoosterFactorySwapxDoubleHierarchy.svg b/contracts/docs/PoolBoosterFactorySwapxDoubleHierarchy.svg new file mode 100644 index 0000000000..33fb13dbe9 --- /dev/null +++ b/contracts/docs/PoolBoosterFactorySwapxDoubleHierarchy.svg @@ -0,0 +1,134 @@ + + + + + + +UmlClassDiagram + + + +40 + +<<Abstract>> +Governable +../contracts/governance/Governable.sol + + + +339 + +<<Interface>> +IPoolBoostCentralRegistry +../contracts/interfaces/poolBooster/IPoolBoostCentralRegistry.sol + + + +342 + +<<Interface>> +IPoolBooster +../contracts/interfaces/poolBooster/IPoolBooster.sol + + + +343 + +<<Interface>> +IBribe +../contracts/interfaces/poolBooster/ISwapXAlgebraBribe.sol + + + +133 + +AbstractPoolBoosterFactory +../contracts/poolBooster/AbstractPoolBoosterFactory.sol + + + +133->40 + + + + + +133->339 + + +emitPoolBoosterCreated +emitPoolBoosterRemoved + + + +133->342 + + +bribe + + + +138 + +PoolBoosterFactorySwapxDouble +../contracts/poolBooster/PoolBoosterFactorySwapxDouble.sol + + + +138->339 + + + + + +138->133 + + + + + +146 + +PoolBoosterSwapxDouble +../contracts/poolBooster/PoolBoosterSwapxDouble.sol + + + +138->146 + + + + + +146->342 + + + + + +146->343 + + +notifyRewardAmount + + + +1369 + +<<Interface>> +IERC20 +../node_modules/.pnpm/@openzeppelin+contracts@4.4.2/node_modules/@openzeppelin/contracts/token/ERC20/IERC20.sol + + + +146->1369 + + +approve +balanceOf + + + diff --git a/contracts/docs/OETHBaseHarvesterSquashed.svg b/contracts/docs/PoolBoosterFactorySwapxDoubleSquashed.svg similarity index 53% rename from contracts/docs/OETHBaseHarvesterSquashed.svg rename to contracts/docs/PoolBoosterFactorySwapxDoubleSquashed.svg index cf200cb47c..4fe294ef3b 100644 --- a/contracts/docs/OETHBaseHarvesterSquashed.svg +++ b/contracts/docs/PoolBoosterFactorySwapxDoubleSquashed.svg @@ -4,60 +4,56 @@ - - + + UmlClassDiagram - - + + -35 - -OETHBaseHarvester -../contracts/harvest/OETHBaseHarvester.sol - -Private: -   governorPosition: bytes32 <<Governable>> -   pendingGovernorPosition: bytes32 <<Governable>> -   reentryStatusPosition: bytes32 <<Governable>> -Public: -   _NOT_ENTERED: uint256 <<Governable>> -   _ENTERED: uint256 <<Governable>> -   vault: IVault <<OETHBaseHarvester>> -   amoStrategy: IStrategy <<OETHBaseHarvester>> -   aero: IERC20 <<OETHBaseHarvester>> -   weth: IERC20 <<OETHBaseHarvester>> -   swapRouter: ISwapRouter <<OETHBaseHarvester>> -   operatorAddr: address <<OETHBaseHarvester>> - -Internal: -    _governor(): (governorOut: address) <<Governable>> -    _pendingGovernor(): (pendingGovernor: address) <<Governable>> -    _setGovernor(newGovernor: address) <<Governable>> -    _setPendingGovernor(newGovernor: address) <<Governable>> -    _changeGovernor(_newGovernor: address) <<Governable>> -    _doSwap(aeroToSwap: uint256, minWETHExpected: uint256) <<OETHBaseHarvester>> -External: -    transferGovernance(_newGovernor: address) <<onlyGovernor>> <<Governable>> -    claimGovernance() <<Governable>> -    setOperatorAddr(_operatorAddr: address) <<onlyGovernor>> <<OETHBaseHarvester>> -    harvest() <<onlyGovernorOrStrategistOrOperator>> <<OETHBaseHarvester>> -    harvestAndSwap(aeroToSwap: uint256, minWETHExpected: uint256, feeBps: uint256, sendYieldToDripper: bool) <<onlyGovernorOrStrategist>> <<OETHBaseHarvester>> -    transferToken(_asset: address, _amount: uint256) <<onlyGovernor>> <<OETHBaseHarvester>> -Public: -    <<event>> PendingGovernorshipTransfer(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> GovernorshipTransferred(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> RewardTokenSwapped(rewardToken: address, swappedInto: address, swapPlatform: uint8, amountIn: uint256, amountOut: uint256) <<OETHBaseHarvester>> -    <<event>> OperatorChanged(oldOperator: address, newOperator: address) <<OETHBaseHarvester>> -    <<event>> YieldSent(recipient: address, yield: uint256, fee: uint256) <<OETHBaseHarvester>> -    <<modifier>> onlyGovernor() <<Governable>> -    <<modifier>> nonReentrant() <<Governable>> -    <<modifier>> onlyGovernorOrStrategist() <<OETHBaseHarvester>> -    <<modifier>> onlyGovernorOrStrategistOrOperator() <<OETHBaseHarvester>> -    constructor() <<Governable>> +138 + +PoolBoosterFactorySwapxDouble +../contracts/poolBooster/PoolBoosterFactorySwapxDouble.sol + +Private: +   governorPosition: bytes32 <<Governable>> +   pendingGovernorPosition: bytes32 <<Governable>> +   reentryStatusPosition: bytes32 <<Governable>> +Public: +   _NOT_ENTERED: uint256 <<Governable>> +   _ENTERED: uint256 <<Governable>> +   oToken: address <<AbstractPoolBoosterFactory>> +   centralRegistry: IPoolBoostCentralRegistry <<AbstractPoolBoosterFactory>> +   poolBoosters: PoolBoosterEntry[] <<AbstractPoolBoosterFactory>> +   poolBoosterFromPool: mapping(address=>PoolBoosterEntry) <<AbstractPoolBoosterFactory>> +   version: uint256 <<PoolBoosterFactorySwapxDouble>> + +Internal: +    _governor(): (governorOut: address) <<Governable>> +    _pendingGovernor(): (pendingGovernor: address) <<Governable>> +    _setGovernor(newGovernor: address) <<Governable>> +    _setPendingGovernor(newGovernor: address) <<Governable>> +    _changeGovernor(_newGovernor: address) <<Governable>> +    _storePoolBoosterEntry(_poolBoosterAddress: address, _ammPoolAddress: address, _boosterType: IPoolBoostCentralRegistry.PoolBoosterType) <<AbstractPoolBoosterFactory>> +    _deployContract(_bytecode: bytes, _salt: uint256): (_address: address) <<AbstractPoolBoosterFactory>> +    _computeAddress(_bytecode: bytes, _salt: uint256): address <<AbstractPoolBoosterFactory>> +External: +    transferGovernance(_newGovernor: address) <<onlyGovernor>> <<Governable>> +    claimGovernance() <<Governable>> +    bribeAll(_exclusionList: address[]) <<AbstractPoolBoosterFactory>> +    removePoolBooster(_poolBoosterAddress: address) <<onlyGovernor>> <<AbstractPoolBoosterFactory>> +    poolBoosterLength(): uint256 <<AbstractPoolBoosterFactory>> +    createPoolBoosterSwapxDouble(_bribeAddressOS: address, _bribeAddressOther: address, _ammPoolAddress: address, _split: uint256, _salt: uint256) <<onlyGovernor>> <<PoolBoosterFactorySwapxDouble>> +    computePoolBoosterAddress(_bribeAddressOS: address, _bribeAddressOther: address, _ammPoolAddress: address, _split: uint256, _salt: uint256): address <<PoolBoosterFactorySwapxDouble>> +Public: +    <<event>> PendingGovernorshipTransfer(previousGovernor: address, newGovernor: address) <<Governable>> +    <<event>> GovernorshipTransferred(previousGovernor: address, newGovernor: address) <<Governable>> +    <<modifier>> onlyGovernor() <<Governable>> +    <<modifier>> nonReentrant() <<Governable>>    governor(): address <<Governable>>    isGovernor(): bool <<Governable>> -    constructor(_vault: address, _amoStrategy: address, _aero: address, _weth: address, _swapRouter: address) <<OETHBaseHarvester>> +    constructor(_oToken: address, _governor: address, _centralRegistry: address) <<PoolBoosterFactorySwapxDouble>> diff --git a/contracts/docs/PoolBoosterFactorySwapxDoubleStorage.svg b/contracts/docs/PoolBoosterFactorySwapxDoubleStorage.svg new file mode 100644 index 0000000000..c36fe9c566 --- /dev/null +++ b/contracts/docs/PoolBoosterFactorySwapxDoubleStorage.svg @@ -0,0 +1,113 @@ + + + + + + +StorageDiagram + + + +4 + +PoolBoosterFactorySwapxDouble <<Contract>> + +slot + +0 + +1 + +type: <inherited contract>.variable (bytes) + +PoolBoosterEntry[]: AbstractPoolBoosterFactory.poolBoosters (32) + +mapping(address=>PoolBoosterEntry): AbstractPoolBoosterFactory.poolBoosterFromPool (32) + + + +2 + +PoolBoosterEntry[]: poolBoosters <<Array>> +0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563 + +offset + +0-1 + +type: variable (bytes) + +PoolBoosterEntry (64) + + + +4:5->2 + + + + + +3 + +PoolBoosterEntry <<Struct>> + +offset + +0 + +1 + +type: variable (bytes) + +unallocated (12) + +address: boosterAddress (20) + +unallocated (11) + +IPoolBoostCentralRegistry.PoolBoosterType: boosterType (1) + +address: ammPoolAddress (20) + + + +4:9->3 + + + + + +1 + +PoolBoosterEntry <<Struct>> +0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563 + +offset + +0 + +1 + +type: variable (bytes) + +unallocated (12) + +address: boosterAddress (20) + +unallocated (11) + +IPoolBoostCentralRegistry.PoolBoosterType: boosterType (1) + +address: ammPoolAddress (20) + + + +2:4->1 + + + + + diff --git a/contracts/docs/PoolBoosterFactorySwapxSingleHierarchy.svg b/contracts/docs/PoolBoosterFactorySwapxSingleHierarchy.svg new file mode 100644 index 0000000000..c53940953c --- /dev/null +++ b/contracts/docs/PoolBoosterFactorySwapxSingleHierarchy.svg @@ -0,0 +1,134 @@ + + + + + + +UmlClassDiagram + + + +40 + +<<Abstract>> +Governable +../contracts/governance/Governable.sol + + + +339 + +<<Interface>> +IPoolBoostCentralRegistry +../contracts/interfaces/poolBooster/IPoolBoostCentralRegistry.sol + + + +342 + +<<Interface>> +IPoolBooster +../contracts/interfaces/poolBooster/IPoolBooster.sol + + + +343 + +<<Interface>> +IBribe +../contracts/interfaces/poolBooster/ISwapXAlgebraBribe.sol + + + +133 + +AbstractPoolBoosterFactory +../contracts/poolBooster/AbstractPoolBoosterFactory.sol + + + +133->40 + + + + + +133->339 + + +emitPoolBoosterCreated +emitPoolBoosterRemoved + + + +133->342 + + +bribe + + + +139 + +PoolBoosterFactorySwapxSingle +../contracts/poolBooster/PoolBoosterFactorySwapxSingle.sol + + + +139->339 + + + + + +139->133 + + + + + +147 + +PoolBoosterSwapxSingle +../contracts/poolBooster/PoolBoosterSwapxSingle.sol + + + +139->147 + + + + + +147->342 + + + + + +147->343 + + +notifyRewardAmount + + + +1369 + +<<Interface>> +IERC20 +../node_modules/.pnpm/@openzeppelin+contracts@4.4.2/node_modules/@openzeppelin/contracts/token/ERC20/IERC20.sol + + + +147->1369 + + +approve +balanceOf + + + diff --git a/contracts/docs/PoolBoosterFactorySwapxSingleSquashed.svg b/contracts/docs/PoolBoosterFactorySwapxSingleSquashed.svg new file mode 100644 index 0000000000..52f3c7a9d5 --- /dev/null +++ b/contracts/docs/PoolBoosterFactorySwapxSingleSquashed.svg @@ -0,0 +1,59 @@ + + + + + + +UmlClassDiagram + + + +139 + +PoolBoosterFactorySwapxSingle +../contracts/poolBooster/PoolBoosterFactorySwapxSingle.sol + +Private: +   governorPosition: bytes32 <<Governable>> +   pendingGovernorPosition: bytes32 <<Governable>> +   reentryStatusPosition: bytes32 <<Governable>> +Public: +   _NOT_ENTERED: uint256 <<Governable>> +   _ENTERED: uint256 <<Governable>> +   oToken: address <<AbstractPoolBoosterFactory>> +   centralRegistry: IPoolBoostCentralRegistry <<AbstractPoolBoosterFactory>> +   poolBoosters: PoolBoosterEntry[] <<AbstractPoolBoosterFactory>> +   poolBoosterFromPool: mapping(address=>PoolBoosterEntry) <<AbstractPoolBoosterFactory>> +   version: uint256 <<PoolBoosterFactorySwapxSingle>> + +Internal: +    _governor(): (governorOut: address) <<Governable>> +    _pendingGovernor(): (pendingGovernor: address) <<Governable>> +    _setGovernor(newGovernor: address) <<Governable>> +    _setPendingGovernor(newGovernor: address) <<Governable>> +    _changeGovernor(_newGovernor: address) <<Governable>> +    _storePoolBoosterEntry(_poolBoosterAddress: address, _ammPoolAddress: address, _boosterType: IPoolBoostCentralRegistry.PoolBoosterType) <<AbstractPoolBoosterFactory>> +    _deployContract(_bytecode: bytes, _salt: uint256): (_address: address) <<AbstractPoolBoosterFactory>> +    _computeAddress(_bytecode: bytes, _salt: uint256): address <<AbstractPoolBoosterFactory>> +External: +    transferGovernance(_newGovernor: address) <<onlyGovernor>> <<Governable>> +    claimGovernance() <<Governable>> +    bribeAll(_exclusionList: address[]) <<AbstractPoolBoosterFactory>> +    removePoolBooster(_poolBoosterAddress: address) <<onlyGovernor>> <<AbstractPoolBoosterFactory>> +    poolBoosterLength(): uint256 <<AbstractPoolBoosterFactory>> +    createPoolBoosterSwapxSingle(_bribeAddress: address, _ammPoolAddress: address, _salt: uint256) <<onlyGovernor>> <<PoolBoosterFactorySwapxSingle>> +    computePoolBoosterAddress(_bribeAddress: address, _ammPoolAddress: address, _salt: uint256): address <<PoolBoosterFactorySwapxSingle>> +Public: +    <<event>> PendingGovernorshipTransfer(previousGovernor: address, newGovernor: address) <<Governable>> +    <<event>> GovernorshipTransferred(previousGovernor: address, newGovernor: address) <<Governable>> +    <<modifier>> onlyGovernor() <<Governable>> +    <<modifier>> nonReentrant() <<Governable>> +    governor(): address <<Governable>> +    isGovernor(): bool <<Governable>> +    constructor(_oToken: address, _governor: address, _centralRegistry: address) <<PoolBoosterFactorySwapxSingle>> + + + diff --git a/contracts/docs/PoolBoosterFactorySwapxSingleStorage.svg b/contracts/docs/PoolBoosterFactorySwapxSingleStorage.svg new file mode 100644 index 0000000000..1e6d7d5f69 --- /dev/null +++ b/contracts/docs/PoolBoosterFactorySwapxSingleStorage.svg @@ -0,0 +1,113 @@ + + + + + + +StorageDiagram + + + +4 + +PoolBoosterFactorySwapxSingle <<Contract>> + +slot + +0 + +1 + +type: <inherited contract>.variable (bytes) + +PoolBoosterEntry[]: AbstractPoolBoosterFactory.poolBoosters (32) + +mapping(address=>PoolBoosterEntry): AbstractPoolBoosterFactory.poolBoosterFromPool (32) + + + +2 + +PoolBoosterEntry[]: poolBoosters <<Array>> +0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563 + +offset + +0-1 + +type: variable (bytes) + +PoolBoosterEntry (64) + + + +4:5->2 + + + + + +3 + +PoolBoosterEntry <<Struct>> + +offset + +0 + +1 + +type: variable (bytes) + +unallocated (12) + +address: boosterAddress (20) + +unallocated (11) + +IPoolBoostCentralRegistry.PoolBoosterType: boosterType (1) + +address: ammPoolAddress (20) + + + +4:9->3 + + + + + +1 + +PoolBoosterEntry <<Struct>> +0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563 + +offset + +0 + +1 + +type: variable (bytes) + +unallocated (12) + +address: boosterAddress (20) + +unallocated (11) + +IPoolBoostCentralRegistry.PoolBoosterType: boosterType (1) + +address: ammPoolAddress (20) + + + +2:4->1 + + + + + diff --git a/contracts/docs/Swapper1InchV5Hierarchy.svg b/contracts/docs/Swapper1InchV5Hierarchy.svg deleted file mode 100644 index 3dd6ab0860..0000000000 --- a/contracts/docs/Swapper1InchV5Hierarchy.svg +++ /dev/null @@ -1,88 +0,0 @@ - - - - - - -UmlClassDiagram - - - -46 - -<<Interface>> -IAggregationExecutor -../contracts/interfaces/IOneInch.sol - - - -47 - -<<Interface>> -IOneInchRouter -../contracts/interfaces/IOneInch.sol - - - -47->46 - - - - - -392 - -<<Interface>> -IERC20 -../node_modules/@openzeppelin/contracts/token/ERC20/IERC20.sol - - - -47->392 - - - - - -53 - -<<Interface>> -ISwapper -../contracts/interfaces/ISwapper.sol - - - -182 - -Swapper1InchV5 -../contracts/swapper/Swapper1InchV5.sol - - - -182->46 - - - - - -182->47 - - - - - -182->53 - - - - - -182->392 - - - - - diff --git a/contracts/docs/Swapper1InchV5Squashed.svg b/contracts/docs/Swapper1InchV5Squashed.svg deleted file mode 100644 index 88a3764ea3..0000000000 --- a/contracts/docs/Swapper1InchV5Squashed.svg +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - -UmlClassDiagram - - - -182 - -Swapper1InchV5 -../contracts/swapper/Swapper1InchV5.sol - -Internal: -   SWAP_SELECTOR: bytes4 <<Swapper1InchV5>> -   UNISWAP_SELECTOR: bytes4 <<Swapper1InchV5>> -   UNISWAPV3_SELECTOR: bytes4 <<Swapper1InchV5>> -Public: -   SWAP_ROUTER: address <<Swapper1InchV5>> - -External: -    swap(_fromAsset: address, _toAsset: address, _fromAssetAmount: uint256, _minToAssetAmount: uint256, _data: bytes): (toAssetAmount: uint256) <<Swapper1InchV5>> -    approveAssets(_assets: address[]) <<Swapper1InchV5>> - - - diff --git a/contracts/docs/Swapper1InchV5Storage.svg b/contracts/docs/Swapper1InchV5Storage.svg deleted file mode 100644 index 5ecf1912d3..0000000000 --- a/contracts/docs/Swapper1InchV5Storage.svg +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - -StorageDiagram - - - -1 - -Swapper1InchV5 <<Contract>> - -slot - -type: <inherited contract>.variable (bytes) - - - diff --git a/contracts/docs/generate.sh b/contracts/docs/generate.sh index 5af8c5867b..2420f4610b 100644 --- a/contracts/docs/generate.sh +++ b/contracts/docs/generate.sh @@ -1,12 +1,3 @@ -# contracts/buyback -sol2uml .. -v -hv -hf -he -hs -hl -b OUSDBuyback -o OUSDBuybackHierarchy.svg -sol2uml .. -s -d 0 -b OUSDBuyback -o OUSDBuybackSquashed.svg -sol2uml storage .. -c OUSDBuyback -o OUSDBuybackStorage.svg - -sol2uml .. -v -hv -hf -he -hs -hl -b OETHBuyback -o OETHBuybackHierarchy.svg -sol2uml .. -s -d 0 -b OETHBuyback -o OETHBuybackSquashed.svg -sol2uml storage .. -c OETHBuyback -o OETHBuybackStorage.svg - # contracts/harvest sol2uml .. -v -hv -hf -he -hs -hl -hi -b Dripper -o DripperHierarchy.svg sol2uml .. -s -d 0 -b Dripper -o DripperSquashed.svg @@ -16,18 +7,10 @@ sol2uml .. -v -hv -hf -he -hs -hl -hi -b OETHFixedRateDripper -o OETHFixedRateDr sol2uml .. -s -d 0 -b OETHFixedRateDripper -o OETHFixedRateDripperSquashed.svg sol2uml storage .. -c OETHFixedRateDripper -o OETHFixedRateDripperStorage.svg -sol2uml .. -v -hv -hf -he -hs -hl -hi -b Harvester -o HarvesterHierarchy.svg -sol2uml .. -s -d 0 -b Harvester -o HarvesterSquashed.svg -sol2uml storage .. -c Harvester -o HarvesterStorage.svg - sol2uml .. -v -hv -hf -he -hs -hl -hi -b OETHHarvesterSimple -o OETHHarvesterSimpleHierarchy.svg sol2uml .. -s -d 0 -b OETHHarvesterSimple -o OETHHarvesterSimpleSquashed.svg sol2uml storage .. -c OETHHarvesterSimple -o OETHHarvesterSimpleStorage.svg --hideExpand __gap,___gap,______gap -sol2uml .. -v -hv -hf -he -hs -hl -hi -b OETHBaseHarvester -o OETHBaseHarvesterHierarchy.svg -sol2uml .. -s -d 0 -b OETHBaseHarvester -o OETHBaseHarvesterSquashed.svg -sol2uml storage .. -c OETHBaseHarvester -o OETHBaseHarvesterStorage.svg --hideExpand __gap,___gap,______gap - sol2uml .. -v -hv -hf -he -hs -hl -hi -b OSonicHarvester -o OSonicHarvesterHierarchy.svg sol2uml .. -s -d 0 -b OSonicHarvester -o OSonicHarvesterSquashed.svg sol2uml storage .. -c OSonicHarvester -o OSonicHarvesterStorage.svg --hideExpand __gap,___gap,______gap @@ -64,15 +47,6 @@ sol2uml .. -v -hv -hf -he -hs -hl -hi -b BridgedWOETHStrategy -o BridgedWOETHStr sol2uml .. -s -d 0 -b BridgedWOETHStrategy -o BridgedWOETHStrategySquashed.svg sol2uml storage .. -c BridgedWOETHStrategy -o BridgedWOETHStrategyStorage.svg --hideExpand ______gap,_reserved,__reserved -sol2uml .. -v -hv -hf -he -hs -hl -hi -b ConvexEthMetaStrategy -o ConvexEthMetaStrategyHierarchy.svg -sol2uml .. -s -d 0 -b ConvexEthMetaStrategy -o ConvexEthMetaStrategySquashed.svg -sol2uml storage .. -c ConvexEthMetaStrategy -o ConvexEthMetaStrategyStorage.svg --hideExpand ______gap,_reserved,__reserved - -sol2uml .. -v -hv -hf -he -hs -hl -hi -b ConvexOUSDMetaStrategy -o ConvexOUSDMetaStrategyHierarchy.svg -sol2uml .. -s -d 0 -b ConvexOUSDMetaStrategy -o ConvexOUSDMetaStrategySquashed.svg -# Failed to find user defined type "IERC20" in attribute "metapoolMainToken" of type "1"" -# sol2uml storage .. -c ConvexOUSDMetaStrategy -o ConvexOUSDMetaStrategyStorage.svg - sol2uml .. -v -hv -hf -he -hs -hl -hi -b Generalized4626Strategy -o Generalized4626StrategyHierarchy.svg sol2uml .. -s -d 0 -b Generalized4626Strategy -o Generalized4626StrategySquashed.svg sol2uml storage .. -c Generalized4626Strategy -o Generalized4626StrategyStorage.svg --hideExpand ______gap,_reserved,__gap @@ -83,18 +57,13 @@ sol2uml storage .. -c NativeStakingSSVStrategy -o NativeStakingSSVStrategyStorag sol2uml .. -v -hv -hf -he -hs -hl -hi -b FeeAccumulator -o FeeAccumulatorHierarchy.svg sol2uml .. -s -d 0 -b FeeAccumulator -o FeeAccumulatorSquashed.svg -sol2uml .. -v -hv -hf -he -hs -hl -hi -b MorphoAaveStrategy -o MorphoAaveStrategyHierarchy.svg -sol2uml .. -s -d 0 -b MorphoAaveStrategy -o MorphoAaveStrategySquashed.svg -sol2uml storage .. -c MorphoAaveStrategy -o MorphoAaveStrategyStorage.svg --hideExpand ______gap,_reserved - -sol2uml .. -v -hv -hf -he -hs -hl -hi -b MorphoCompoundStrategy -o MorphoCompStrategyHierarchy.svg -sol2uml .. -s -d 0 -b MorphoCompoundStrategy -o MorphoCompStrategySquashed.svg -sol2uml storage .. -c MorphoCompoundStrategy -o MorphoCompStrategyStorage.svg --hideExpand ______gap,_reserved,__reserved +sol2uml .. -v -hv -hf -he -hs -hl -hi -i prettier-plugin-solidity -b CurveAMOStrategy -o CurveAMOStrategyHierarchy.svg +sol2uml .. -s -d 0 -b CurveAMOStrategy -i prettier-plugin-solidity -o CurveAMOStrategySquashed.svg +sol2uml storage .. -c CurveAMOStrategy -i prettier-plugin-solidity -o CurveAMOStrategyStorage.svg --hideExpand ______gap,_reserved,__gap -# contracts/strategies/balancer -sol2uml .. -v -hv -hf -he -hs -hl -hi -b BalancerMetaPoolStrategy -o BalancerMetaPoolStrategyHierarchy.svg -sol2uml .. -s -d 0 -b BalancerMetaPoolStrategy -o BalancerMetaPoolStrategySquashed.svg -sol2uml storage .. -c BalancerMetaPoolStrategy -o BalancerMetaPoolStrategyStorage.svg --hideExpand ______gap,_reserved,__reserved,__reserved_baseAuraStrategy +sol2uml .. -v -hv -hf -he -hs -hl -hi -i prettier-plugin-solidity -b BaseCurveAMOStrategy -o BaseCurveAMOStrategyHierarchy.svg +sol2uml .. -s -d 0 -b BaseCurveAMOStrategy -i prettier-plugin-solidity -o BaseCurveAMOStrategySquashed.svg +sol2uml storage .. -c BaseCurveAMOStrategy -i prettier-plugin-solidity -o BaseCurveAMOStrategyStorage.svg --hideExpand ______gap,_reserved,__gap # contracts/strategies/sonic sol2uml .. -v -hv -hf -he -hs -hl -hi -b SonicStakingStrategy -o SonicStakingStrategyHierarchy.svg @@ -105,11 +74,6 @@ sol2uml .. -v -hv -hf -he -hs -hl -hi -b SonicSwapXAMOStrategy -o SonicSwapXAMOS sol2uml .. -s -d 0 -b SonicSwapXAMOStrategy -o SonicSwapXAMOStrategySquashed.svg sol2uml storage .. -c SonicSwapXAMOStrategy -o SonicSwapXAMOStrategyStorage.svg --hideExpand __gap,______gap,_reserved -# contracts/swapper -sol2uml .. -v -hv -hf -he -hs -hl -b Swapper1InchV5 -o Swapper1InchV5Hierarchy.svg -sol2uml .. -s -d 0 -b Swapper1InchV5 -o Swapper1InchV5Squashed.svg -sol2uml storage .. -c Swapper1InchV5 -o Swapper1InchV5Storage.svg - # contracts/token sol2uml .. -v -hv -hf -he -hs -hl -hi -b OUSD -o OUSDHierarchy.svg sol2uml .. -s -d 0 -b OUSD -o OUSDSquashed.svg @@ -128,9 +92,9 @@ sol2uml .. -s -d 0 -b WOETH -o WOETHSquashed.svg sol2uml storage .. -c WOETH -o WOETHStorage.svg --hideExpand ______gap # Base tokens -sol2uml .. -v -hv -hf -he -hs -hl -hi -b OETHBase -o OETHBaseHierarchy.svg -sol2uml .. -s -d 0 -b OETHBase -o OETHBaseSquashed.svg -sol2uml storage .. -c OETHBase -o OETHBaseStorage.svg --hideExpand _gap,__gap +sol2uml .. -v -hv -hf -he -hs -hl -hi -i prettier-plugin-solidity -b OETHBase -o OETHBaseHierarchy.svg +sol2uml .. -s -d 0 -b OETHBase -i prettier-plugin-solidity -o OETHBaseSquashed.svg +sol2uml storage .. -c OETHBase -i prettier-plugin-solidity -o OETHBaseStorage.svg --hideExpand _gap,__gap sol2uml .. -v -hv -hf -he -hs -hl -hi -b WOETHBase -o WOETHBaseHierarchy.svg sol2uml .. -s -d 0 -b WOETHBase -o WOETHBaseSquashed.svg @@ -162,15 +126,13 @@ sol2uml storage .. -i prettier-plugin-solidity -c OUSDVault -o VaultStorage.svg # sol2uml storage .. -i prettier-plugin-solidity -c OSonicVault -o OSonicVaultStorage.svg --hideExpand __gap,______gap,_deprecated_swapTokens # contracts/poolBooster -# call stack size exceeded -#sol2uml .. -v -hv -hf -he -hs -hl -b PoolBoosterFactorySwapxSingle -o PoolBoosterFactorySwapxSingleHierarchy.svg -#sol2uml .. -s -d 0 -b PoolBoosterFactorySwapxSingle -o PoolBoosterFactorySwapxSingleSquashed.svg -sol2uml storage .. -c PoolBoosterFactorySwapxSingle -o PoolBoosterFactorySwapxSingleStorage.svg - -# call stack size exceeded -#sol2uml .. -v -hv -hf -he -hs -hl -b PoolBoosterFactorySwapxDouble -o PoolBoosterFactorySwapxDoubleHierarchy.svg -#sol2uml .. -s -d 0 -b PoolBoosterFactorySwapxDouble -o PoolBoosterFactorySwapxDoubleSquashed.svg -sol2uml storage .. -c PoolBoosterFactorySwapxDouble -o PoolBoosterFactorySwapxDoubleStorage.svg +sol2uml .. -v -hv -hf -he -hs -hl `-i prettier-plugin-solidity` -b PoolBoosterFactorySwapxSingle -o PoolBoosterFactorySwapxSingleHierarchy.svg +sol2uml .. -s -d 0 -i prettier-plugin-solidity -b PoolBoosterFactorySwapxSingle -o PoolBoosterFactorySwapxSingleSquashed.svg +sol2uml storage .. -i prettier-plugin-solidity -c PoolBoosterFactorySwapxSingle -o PoolBoosterFactorySwapxSingleStorage.svg + +sol2uml .. -v -hv -hf -he -hs -hl -i prettier-plugin-solidity -b PoolBoosterFactorySwapxDouble -o PoolBoosterFactorySwapxDoubleHierarchy.svg +sol2uml .. -s -d 0 -i prettier-plugin-solidity -b PoolBoosterFactorySwapxDouble -o PoolBoosterFactorySwapxDoubleSquashed.svg +sol2uml storage .. -i prettier-plugin-solidity -c PoolBoosterFactorySwapxDouble -o PoolBoosterFactorySwapxDoubleStorage.svg # contracts/utils sol2uml .. -v -hv -hf -he -hs -hl -b InitializableAbstractStrategy -o InitializableAbstractStrategyHierarchy.svg diff --git a/contracts/scripts/governor/README.md b/contracts/scripts/governor/README.md deleted file mode 100644 index 8ce3fc74e6..0000000000 --- a/contracts/scripts/governor/README.md +++ /dev/null @@ -1,7 +0,0 @@ -Scripts for OUSD governance. - -The lifecycle of a governance proposal is as follow: - 1. A proposal for executing some action(s) is submitted by calling the propose(args) method on the governor contract. A proposalId is returned. Anyone can submit a proposal. - 1. After it is reviewed and if approved, the proposal gets queued by calling the queue(proposalId) method on the governor contract. Only the guardian (currently Origin's multisig) can do so. - 1. After the timelock on the proposal expires, the proposal can be executed by calling the method execute(proposalId) on the governor contract. Only the guardian can do so. - diff --git a/contracts/scripts/governor/propose.js b/contracts/scripts/governor/propose.js deleted file mode 100644 index 8bfdddbd50..0000000000 --- a/contracts/scripts/governor/propose.js +++ /dev/null @@ -1,1058 +0,0 @@ -// Script for sending a governance proposal. -// This can be sent by any account, but the script uses the deployer account -// for simplicity since it is already configured in Hardhat. -// -// Usage: -// - Setup your environment -// export HARDHAT_NETWORK=mainnet -// export DEPLOYER_PK= -// export GAS_PRICE_MULTIPLIER= e.g. 1.1 -// export PROVIDER_URL= -// - Run: -// node propose.js -- -// - -const { ethers, getNamedAccounts } = require("hardhat"); -const { utils } = require("ethers"); - -const { isMainnet } = require("../../test/helpers.js"); -const { proposeArgs } = require("../../utils/governor"); -const { getTxOpts } = require("../../utils/tx"); -const addresses = require("../../utils/addresses"); - -// Wait for 3 blocks confirmation on Mainnet. -const NUM_CONFIRMATIONS = isMainnet ? 3 : 0; - -// Proposal for setting the timelock delay to 48hrs -async function proposeGovernorSetDelayArgs() { - const governor = await ethers.getContract("Governor"); - - const description = "Set timelock to 48hrs"; - const args = await proposeArgs([ - { - contract: governor, - signature: "setDelay(uint256)", - args: [48 * 60 * 60], // 48hrs - }, - ]); - return { args, description }; -} - -async function proposeVaultv2GovernanceArgs() { - const mixOracle = await ethers.getContract("MixOracle"); - const chainlinkOracle = await ethers.getContract("ChainlinkOracle"); - const cAaveStrategyProxy = await ethers.getContract("AaveStrategyProxy"); - const cAaveStrategy = await ethers.getContractAt( - "AaveStrategy", - cAaveStrategyProxy.address - ); - const cCompoundStrategyProxy = await ethers.getContract( - "CompoundStrategyProxy" - ); - const cCompoundStrategy = await ethers.getContractAt( - "CompoundStrategy", - cCompoundStrategyProxy.address - ); - const cVaultProxy = await ethers.getContract("VaultProxy"); - const cVault = await ethers.getContractAt("Vault", cVaultProxy.address); - - const description = "Vault V2 governance"; - const args = await proposeArgs([ - { - contract: mixOracle, - signature: "claimGovernance()", - }, - { - contract: chainlinkOracle, - signature: "claimGovernance()", - }, - { - contract: cAaveStrategy, - signature: "claimGovernance()", - }, - { - contract: cCompoundStrategy, - signature: "claimGovernance()", - }, - { - contract: cVault, - signature: "claimGovernance()", - }, - ]); - return { args, description }; -} - -// Transfer governance of the OUSD contract from old to new governor. -// IMPORTANT: must be executed against the old governor. -async function proposeOusdNewGovernorArgs() { - const { governorAddr } = await getNamedAccounts(); - const cOUSDProxy = await ethers.getContract("OUSDProxy"); - const cOUSD = await ethers.getContractAt("OUSD", cOUSDProxy.address); - - const description = "OUSD governance transfer"; - const args = await proposeArgs([ - { - contract: cOUSD, - signature: "transferGovernance(address)", - args: [governorAddr], - }, - ]); - return { args, description }; -} - -// - claimGovernance -// - upgradeTo OUSDReset -// - call reset() -// - call setVaultAddress() -// - upgradeTo OUSD -async function proposeOusdv2ResetArgs() { - const dOUSD = await ethers.getContract("OUSD"); - const dOUSDReset = await ethers.getContract("OUSDReset"); - const cOUSDProxy = await ethers.getContract("OUSDProxy"); - const cOUSDReset = await ethers.getContractAt( - "OUSDReset", - cOUSDProxy.address - ); - const cVaultProxy = await ethers.getContract("VaultProxy"); - - const description = "OUSD Reset"; - const args = await proposeArgs([ - { - contract: cOUSDProxy, - signature: "claimGovernance()", - }, - { - contract: cOUSDProxy, - signature: "upgradeTo(address)", - args: [dOUSDReset.address], - }, - { - contract: cOUSDReset, - signature: "reset()", - }, - { - contract: cOUSDReset, - signature: "setVaultAddress(address)", - args: [cVaultProxy.address], - }, - { - contract: cOUSDProxy, - signature: "upgradeTo(address)", - args: [dOUSD.address], - }, - ]); - return { args, description }; -} - -// Returns the argument to use for sending a proposal to upgrade OUSD. -async function proposeUpgradeStakingArgs() { - const stakingProxy = await ethers.getContract("OGNStakingProxy"); - const staking = await ethers.getContract("SingleAssetStaking"); - - const args = await proposeArgs([ - { - contract: stakingProxy, - signature: "upgradeTo(address)", - args: [staking.address], - }, - ]); - const description = "Upgrade OGNStaking"; - return { args, description }; -} - -async function proposeClaimOGNStakingGovernance() { - const proxy = await ethers.getContract("OGNStakingProxy"); - - const args = await proposeArgs([ - { - contract: proxy, - signature: "claimGovernance()", - }, - ]); - const description = "Claim OGNStaking"; - return { args, description }; -} - -async function proposeSetMaxSupplyDiffArgs() { - const vaultProxy = await ethers.getContract("VaultProxy"); - const vaultAdmin = await ethers.getContractAt( - "VaultAdmin", - vaultProxy.address - ); - - const args = await proposeArgs([ - { - contract: vaultAdmin, - signature: "setMaxSupplyDiff(uint256)", - args: [utils.parseUnits("5", 16)], // 5% - }, - ]); - const description = "Set maxSupplyDiff"; - return { args, description }; -} - -async function proposeUnpauseRebaseArgs() { - const vaultProxy = await ethers.getContract("VaultProxy"); - const vaultAdmin = await ethers.getContractAt( - "VaultAdmin", - vaultProxy.address - ); - - const args = await proposeArgs([ - { - contract: vaultAdmin, - signature: "unpauseRebase()", - }, - ]); - const description = "Unpause rebase"; - return { args, description }; -} - -async function proposeUnpauseCapitalArgs() { - const vaultProxy = await ethers.getContract("VaultProxy"); - const vaultAdmin = await ethers.getContractAt( - "VaultAdmin", - vaultProxy.address - ); - - const args = await proposeArgs([ - { - contract: vaultAdmin, - signature: "unpauseCapital()", - }, - ]); - const description = "Unpause capital"; - return { args, description }; -} - -async function proposePauseCapitalArgs() { - const vaultProxy = await ethers.getContract("VaultProxy"); - const vaultAdmin = await ethers.getContractAt( - "VaultAdmin", - vaultProxy.address - ); - - const args = await proposeArgs([ - { - contract: vaultAdmin, - signature: "pauseCapital()", - }, - ]); - const description = "Pause capital"; - return { args, description }; -} - -// Returns the arguments to use for sending a proposal to call harvest() on the vault. -async function proposeHarvestArgs() { - const vaultProxy = await ethers.getContract("VaultProxy"); - const vaultAdmin = await ethers.getContractAt( - "VaultAdmin", - vaultProxy.address - ); - - const args = await proposeArgs([ - { - contract: vaultAdmin, - signature: "harvest()", - }, - ]); - const description = "Call harvest"; - return { args, description }; -} - -// Call setRebaseHooksAddr on the vault. -async function proposeSetRebaseHookAddrArgs() { - const vaultProxy = await ethers.getContract("VaultProxy"); - const vaultAdmin = await ethers.getContractAt( - "VaultAdmin", - vaultProxy.address - ); - - const args = await proposeArgs([ - { - contract: vaultAdmin, - signature: "setRebaseHooksAddr(address)", - args: [config.address], - }, - ]); - const description = "setRebaseHooksAddr"; - return { args, description }; -} - -// Returns the arguments to use for sending a proposal to call setUniswapAddr(address) on the vault. -async function proposeSetUniswapAddrArgs(config) { - const vaultProxy = await ethers.getContract("VaultProxy"); - const vaultAdmin = await ethers.getContractAt( - "VaultAdmin", - vaultProxy.address - ); - - const args = await proposeArgs([ - { - contract: vaultAdmin, - signature: "setUniswapAddr(address)", - args: [config.address], - }, - ]); - const description = "Call setUniswapAddr"; - return { args, description }; -} - -// Returns the arguments to use for sending a proposal to call setUniswapAddr(address) on the vault. -async function proposeSetBuybackUniswapAddrArgs(config) { - const buyback = await ethers.getContract("Buyback"); - - const args = await proposeArgs([ - { - contract: buyback, - signature: "setUniswapAddr(address)", - args: [config.address], - }, - ]); - const description = "Call setUniswapAddr on buyback"; - return { args, description }; -} - -// Returns the arguments to use for sending a proposal to call setTrusteeFeeBps(bps) on the vault. -async function proposeSetTrusteeFeeBpsArgs(config) { - const vaultProxy = await ethers.getContract("VaultProxy"); - const vaultAdmin = await ethers.getContractAt( - "VaultAdmin", - vaultProxy.address - ); - - const args = await proposeArgs([ - { - contract: vaultAdmin, - signature: "setTrusteeFeeBps(uint256)", - args: [config.bps], - }, - ]); - const description = "Call setTrusteeFeeBps"; - return { args, description }; -} - -// Returns the argument to use for sending a proposal to upgrade OUSD. -async function proposeUpgradeOusdArgs() { - const ousdProxy = await ethers.getContract("OUSDProxy"); - const ousd = await ethers.getContract("OUSD"); - - const args = await proposeArgs([ - { - contract: ousdProxy, - signature: "upgradeTo(address)", - args: [ousd.address], - }, - ]); - const description = "Upgrade OUSD"; - return { args, description }; -} - -// Returns the arguments to use for sending a proposal call to upgrade to a new MicOracle. -// See migration 11_new_mix_oracle for reference. -async function proposeUpgradeOracleArgs() { - const vaultProxy = await ethers.getContract("VaultProxy"); - const vaultAdmin = await ethers.getContractAt( - "VaultAdmin", - vaultProxy.address - ); - const mixOracle = await ethers.getContract("MixOracle"); - const uniswapOracle = await ethers.getContract("OpenUniswapOracle"); - - const args = await proposeArgs([ - { - contract: mixOracle, - signature: "claimGovernance()", - }, - { - contract: uniswapOracle, - signature: "claimGovernance()", - }, - { - contract: vaultAdmin, - signature: "setPriceProvider(address)", - args: [mixOracle.address], - }, - ]); - const description = "New MixOracle"; - return { args, description }; -} - -// Args to send a proposal to claim governance on new strategies. -// See migration 13_three_pool_strategies and 14_compound_dai_strategy for reference. -async function proposeClaimStrategiesArgs() { - const curveUSDCStrategyProxy = await ethers.getContract( - "CurveUSDCStrategyProxy" - ); - const curveUSDCStrategy = await ethers.getContractAt( - "ThreePoolStrategy", - curveUSDCStrategyProxy.address - ); - const curveUSDTStrategyProxy = await ethers.getContract( - "CurveUSDTStrategyProxy" - ); - const curveUSDTStrategy = await ethers.getContractAt( - "ThreePoolStrategy", - curveUSDTStrategyProxy.address - ); - const compoundStrategyProxy = await ethers.getContract( - "CompoundStrategyProxy" - ); - const compoundStrategy = await ethers.getContractAt( - "CompoundStrategy", - compoundStrategyProxy.address - ); - - const args = await proposeArgs([ - { - contract: curveUSDCStrategy, - signature: "claimGovernance()", - }, - { - contract: curveUSDTStrategy, - signature: "claimGovernance()", - }, - { - contract: compoundStrategy, - signature: "claimGovernance()", - }, - ]); - const description = "Claim strategies"; - return { args, description }; -} - -// Args to send a proposal to remove (and liquidate) a strategy. -async function proposeRemoveStrategyArgs(config) { - const vaultProxy = await ethers.getContract("VaultProxy"); - const vaultAdmin = await ethers.getContractAt( - "VaultAdmin", - vaultProxy.address - ); - - // Harvest the strategy first, then remove it. - // Note: in the future we'll modify the vault's removeStrategy() implementation - // to call harvest but for now we have to handle it as a separate step. - const args = await proposeArgs([ - { - contract: vaultAdmin, - signature: "harvest()", - }, - { - contract: vaultAdmin, - signature: "removeStrategy(address)", - args: [config.address], - }, - ]); - const description = "Remove strategy"; - return { args, description }; -} - -// Args to send a proposal to add strategies. -async function proposeAddStrategiesArgs() { - const vaultProxy = await ethers.getContract("VaultProxy"); - const vaultAdmin = await ethers.getContractAt( - "VaultAdmin", - vaultProxy.address - ); - const curveUSDCStrategyProxy = await ethers.getContract( - "CurveUSDCStrategyProxy" - ); - const curveUSDTStrategyProxy = await ethers.getContract( - "CurveUSDTStrategyProxy" - ); - const compoundStrategyProxy = await ethers.getContract( - "CompoundStrategyProxy" - ); - - // Note: Set strategies weight to 100% for USDC and USDT. 50% for DAI since we plan on - // adding another DAI strategy soon and we'll split the funds between the two. - const args = await proposeArgs([ - { - contract: vaultAdmin, - signature: "addStrategy(address,uint256)", - args: [curveUSDTStrategyProxy.address, utils.parseUnits("1", 18)], - }, - { - contract: vaultAdmin, - signature: "addStrategy(address,uint256)", - args: [curveUSDCStrategyProxy.address, utils.parseUnits("1", 18)], - }, - { - contract: vaultAdmin, - signature: "addStrategy(address,uint256)", - args: [compoundStrategyProxy.address, utils.parseUnits("5", 17)], - }, - ]); - const description = "Add strategies"; - return { args, description }; -} - -// Returns the argument to use for sending a proposal to upgrade the USDC and USDT Curve strategies. -async function proposeUpgradeCurveStrategiesArgs() { - const vaultProxy = await ethers.getContract("VaultProxy"); - const vaultAdmin = await ethers.getContractAt( - "VaultAdmin", - vaultProxy.address - ); - const cCurveUSDCStrategyProxy = await ethers.getContract( - "CurveUSDCStrategyProxy" - ); - const cCurveUSDTStrategyProxy = await ethers.getContract( - "CurveUSDTStrategyProxy" - ); - const cCurveUSDCStrategy = await ethers.getContract("CurveUSDCStrategy"); - const cCurveUSDTStrategy = await ethers.getContract("CurveUSDTStrategy"); - - const args = await proposeArgs([ - { - contract: vaultAdmin, - signature: "setVaultBuffer(uint256)", - args: [utils.parseUnits("999", 15)], // set buffer to 99.9% using precision 18 - }, - { - contract: cCurveUSDCStrategyProxy, - signature: "upgradeTo(address)", - args: [cCurveUSDCStrategy.address], - }, - { - contract: cCurveUSDTStrategyProxy, - signature: "upgradeTo(address)", - args: [cCurveUSDTStrategy.address], - }, - ]); - const description = "Upgrade Curve strategies"; - return { args, description }; -} - -// Args to send a proposal to: -// 1. add the aave strategy -// 2. upgrade the curve USDT strategy to fix a bug -async function proposeAddAaveStrategyAndUpgradeCurveUsdtArgs() { - const vaultProxy = await ethers.getContract("VaultProxy"); - const vaultAdmin = await ethers.getContractAt( - "VaultAdmin", - vaultProxy.address - ); - const aaveStrategyProxy = await ethers.getContract("AaveStrategyProxy"); - - const cCurveUSDTStrategyProxy = await ethers.getContract( - "CurveUSDTStrategyProxy" - ); - const cCurveUSDTStrategy = await ethers.getContract("CurveUSDTStrategy"); - - // Note: set Aave strategy weight to a 50% to split DAI funds evenly between Aave and Compound. - const args = await proposeArgs([ - { - contract: vaultAdmin, - signature: "addStrategy(address,uint256)", - args: [aaveStrategyProxy.address, utils.parseUnits("5", 17)], // 50% in 18 digits precision. - }, - { - contract: cCurveUSDTStrategyProxy, - signature: "upgradeTo(address)", - args: [cCurveUSDTStrategy.address], - }, - ]); - const description = "Add aave strategy"; - return { args, description }; -} - -// Returns the argument to use for sending a proposal to set the Vault's buffer -async function proposeSetVaultBufferArgs() { - const vaultProxy = await ethers.getContract("VaultProxy"); - const vaultAdmin = await ethers.getContractAt( - "VaultAdmin", - vaultProxy.address - ); - - const args = await proposeArgs([ - { - contract: vaultAdmin, - signature: "setVaultBuffer(uint256)", - args: [utils.parseUnits("1", 18)], - }, - ]); - const description = "Set vault buffer to 100%"; - return { args, description }; -} - -// Args to send a proposal to claim governance on the Aave strategy. -async function proposeClaimAaveStrategyArgs() { - const aaveStrategyProxy = await ethers.getContract("AaveStrategyProxy"); - - const args = await proposeArgs([ - { - contract: aaveStrategyProxy, - signature: "claimGovernance()", - }, - ]); - const description = "Claim aave"; - return { args, description }; -} - -// Args to send a proposal to disable the uniswap oracle for stablecoins. -async function proposeProp14Args() { - const mixOracle = await ethers.getContract("MixOracle"); - const chainlinkOracle = await ethers.getContract("ChainlinkOracle"); - - const args = await proposeArgs([ - { - contract: mixOracle, - signature: "registerTokenOracles(string,address[],address[])", - args: ["USDC", [chainlinkOracle.address], [addresses.mainnet.openOracle]], - }, - { - contract: mixOracle, - signature: "registerTokenOracles(string,address[],address[])", - args: ["USDT", [chainlinkOracle.address], [addresses.mainnet.openOracle]], - }, - { - contract: mixOracle, - signature: "registerTokenOracles(string,address[],address[])", - args: ["DAI", [chainlinkOracle.address], [addresses.mainnet.openOracle]], - }, - ]); - const description = "Disable uniswap oracle"; - return { args, description }; -} - -async function proposeSetRewardLiquidationThresholdArgs() { - const cCompoundStrategyProxy = await ethers.getContract( - "CompoundStrategyProxy" - ); - const cCompoundStrategy = await ethers.getContractAt( - "CompoundStrategy", - cCompoundStrategyProxy.address - ); - - const args = await proposeArgs([ - { - contract: cCompoundStrategy, - signature: "setRewardLiquidationThreshold(uint256)", - args: [utils.parseUnits("1", 18)], // 1 COMP with precision 18 - }, - ]); - const description = "Set rewardLiquidationThreshold to 1 COMP"; - return { args, description }; -} - -async function proposeLockAdjusterArgs() { - const cCompensationClaims = await ethers.getContract("CompensationClaims"); - - const args = await proposeArgs([ - { - contract: cCompensationClaims, - signature: "lockAdjuster()", - }, - ]); - const description = "Lock the adjuster"; - return { args, description }; -} - -async function proposeUnlockAdjusterArgs() { - const cCompensationClaims = await ethers.getContract("CompensationClaims"); - - const args = await proposeArgs([ - { - contract: cCompensationClaims, - signature: "unlockAdjuster()", - }, - ]); - const description = "Unlock the adjuster"; - return { args, description }; -} - -async function proposeStartClaimsArgs() { - if (!config.duration) { - throw new Error("A duration in sec must be specified"); - } - const cCompensationClaims = await ethers.getContract("CompensationClaims"); - - const args = await proposeArgs([ - { - contract: cCompensationClaims, - signature: "start(uint256)", - args: [config.duration], - }, - ]); - const description = "Start compensation claims"; - return { args, description }; -} - -// Configure the OGN Staking contract for the compensation Airdrop. -async function proposeSetAirDropRootArgs() { - const cStakingProxy = await ethers.getContract("OGNStakingProxy"); - const cStaking = await ethers.getContractAt( - "SingleAssetStaking", - cStakingProxy.address - ); - - const dropStakeType = 1; - const dropRootHash = process.env.DROP_ROOT_HASH; - const dropProofDepth = process.env.DROP_PROOF_DEPTH; - if (!dropRootHash) { - throw new Error("DROP_ROOT_HASH not set"); - } - if (!dropProofDepth) { - throw new Error("DROP_PROOF_DEPTH not set"); - } - - const args = await proposeArgs([ - { - contract: cStaking, - signature: "setAirDropRoot(uint8,bytes32,uint256)", - args: [dropStakeType, dropRootHash, dropProofDepth], - }, - ]); - const description = "Call setAirDropRoot on OGN Staking"; - return { args, description }; -} - -// Returns the argument to use for sending a proposal to set the Vault's buffer to 0.5% -// and the Compound strategy liquidation threshold to zero. -async function proposeSettingUpdatesArgs() { - const vaultProxy = await ethers.getContract("VaultProxy"); - const vaultAdmin = await ethers.getContractAt( - "VaultAdmin", - vaultProxy.address - ); - const cCompoundStrategyProxy = await ethers.getContract( - "CompoundStrategyProxy" - ); - const cCompoundStrategy = await ethers.getContractAt( - "CompoundStrategy", - cCompoundStrategyProxy.address - ); - - const args = await proposeArgs([ - { - contract: vaultAdmin, - signature: "setVaultBuffer(uint256)", - args: [utils.parseUnits("5", 15)], // set buffer to 0.5% at precision 18 - }, - { - contract: cCompoundStrategy, - signature: "setRewardLiquidationThreshold(uint256)", - args: [0], - }, - ]); - const description = "Update Vault and Compound strategy settings"; - return { args, description }; -} - -async function proposeCompoundDAIArgs() { - const cCompoundStrategyProxy = await ethers.getContract( - "CompoundStrategyProxy" - ); - const cCompoundStrategy = await ethers.getContractAt( - "CompoundStrategy", - cCompoundStrategyProxy.address - ); - - const args = await proposeArgs([ - { - contract: cCompoundStrategy, - signature: "setPTokenAddress(address,address)", - args: [addresses.mainnet.DAI, addresses.mainnet.cDAI], - }, - ]); - const description = "Enable DAI on Compound strategy"; - return { args, description }; -} - -async function proposeWithdrawAllArgs() { - const cAaveStrategyProxy = await ethers.getContract("AaveStrategyProxy"); - const cAaveStrategy = await ethers.getContractAt( - "AaveStrategy", - cAaveStrategyProxy.address - ); - - const cCompoundStrategyProxy = await ethers.getContract( - "CompoundStrategyProxy" - ); - const cCompoundStrategy = await ethers.getContractAt( - "CompoundStrategy", - cCompoundStrategyProxy.address - ); - - const args = await proposeArgs([ - { - contract: cAaveStrategy, - signature: "withdrawAll()", - }, - { - contract: cCompoundStrategy, - signature: "withdrawAll()", - }, - ]); - const description = "Withdraw funds from Aave and Compound"; - return { args, description }; -} - -async function proposeCompRewardTokenZero() { - const cCompoundStrategyProxy = await ethers.getContract( - "CompoundStrategyProxy" - ); - const cCompoundStrategy = await ethers.getContractAt( - "CompoundStrategy", - cCompoundStrategyProxy.address - ); - - const args = await proposeArgs([ - { - contract: cCompoundStrategy, - signature: "setRewardTokenAddress(address)", - args: [addresses.zero], - }, - ]); - const description = "Set Compound reward token addresss to zero"; - return { args, description }; -} - -async function main(config) { - let governor; - if (config.governorV1) { - // V1 governor contract has a slightly different interface for the propose method which - // takes an extra uint256[] argument compared to V2. - const v1GovernorAddr = "0x8a5fF78BFe0de04F5dc1B57d2e1095bE697Be76E"; - const v1GovernorAbi = [ - "function propose(address[],uint256[],string[],bytes[],string) returns (uint256)", - "function proposalCount() view returns (uint256)", - "function queue(uint256)", - "function execute(uint256)", - ]; - governor = new ethers.Contract( - v1GovernorAddr, - v1GovernorAbi, - ethers.provider - ); - console.log(`Using V1 governor contract at ${v1GovernorAddr}`); - } else { - governor = await ethers.getContract("Governor"); - } - - const { deployerAddr } = await getNamedAccounts(); - const sDeployer = ethers.provider.getSigner(deployerAddr); - - let proposalCount = await governor.proposalCount(); - console.log("Current proposal count=", proposalCount.toString()); - - let argsMethod; - if (config.harvest) { - console.log("Harvest proposal"); - argsMethod = proposeHarvestArgs; - } else if (config.setUniswapAddr) { - console.log("setUniswapAddr proposal"); - argsMethod = proposeSetUniswapAddrArgs; - } else if (config.setBuybackUniswapAddr) { - console.log("setBuybackUniswapAddr proposal"); - argsMethod = proposeSetBuybackUniswapAddrArgs; - } else if (config.setTrusteeFeeBps) { - console.log("setTrusteeFeeBps proposal"); - argsMethod = proposeSetTrusteeFeeBpsArgs; - } else if (config.setRebaseHookAddr) { - console.log("setRebaseHookAddr proposal"); - argsMethod = proposeSetRebaseHookAddrArgs; - } else if (config.upgradeOusd) { - console.log("upgradeOusd proposal"); - argsMethod = proposeUpgradeOusdArgs; - } else if (config.upgradeOracle) { - console.log("upgradeOracle proposal"); - argsMethod = proposeUpgradeOracleArgs; - } else if (config.claimStrategies) { - console.log("claimStrategies proposal"); - argsMethod = proposeClaimStrategiesArgs; - } else if (config.removeStrategy) { - console.log("removeStrategy proposal"); - argsMethod = proposeRemoveStrategyArgs; - } else if (config.addStrategies) { - console.log("addStrategies proposal"); - argsMethod = proposeAddStrategiesArgs; - } else if (config.upgradeCurveStrategies) { - console.log("upgradeCurveStrategies proposal"); - argsMethod = proposeUpgradeCurveStrategiesArgs; - } else if (config.setVaultBuffer) { - console.log("setVaultBuffer proposal"); - argsMethod = proposeSetVaultBufferArgs; - } else if (config.addAaveStrategyAndUpgradeCurveUsdt) { - console.log("addAaveStrategyAndUpgradeCurveUsdt proposal"); - argsMethod = proposeAddAaveStrategyAndUpgradeCurveUsdtArgs; - } else if (config.claimAaveStrategy) { - console.log("claimAaveStrategy proposal"); - argsMethod = proposeClaimAaveStrategyArgs; - } else if (config.prop14) { - console.log("prop14 proposal"); - argsMethod = proposeProp14Args; - } else if (config.pauseCapital) { - console.log("pauseCapital"); - argsMethod = proposePauseCapitalArgs; - } else if (config.unpauseCapital) { - console.log("unpauseCapital"); - argsMethod = proposeUnpauseCapitalArgs; - } else if (config.unpauseRebase) { - console.log("unpauseRebase"); - argsMethod = proposeUnpauseRebaseArgs; - } else if (config.claimOGNStakingGovernance) { - console.log("proposeClaimOGNStakingGovernance"); - argsMethod = proposeClaimOGNStakingGovernance; - } else if (config.upgradeStaking) { - console.log("upgradeStaking"); - argsMethod = proposeUpgradeStakingArgs; - } else if (config.vaultv2Governance) { - console.log("VaultV2Governance"); - argsMethod = proposeVaultv2GovernanceArgs; - } else if (config.ousdNewGovernor) { - console.log("OusdNewGovernor"); - argsMethod = proposeOusdNewGovernorArgs; - } else if (config.ousdv2Reset) { - console.log("Ousdv2Reset"); - argsMethod = proposeOusdv2ResetArgs; - } else if (config.setRewardLiquidationThreshold) { - console.log("Set Compound reward liquidation threshold"); - argsMethod = proposeSetRewardLiquidationThresholdArgs; - } else if (config.lockAdjuster) { - console.log("Lock adjuster on CompensationClaims"); - argsMethod = proposeLockAdjusterArgs; - } else if (config.unlockAdjuster) { - console.log("Unlock adjuster on CompensationClaims"); - argsMethod = proposeUnlockAdjusterArgs; - } else if (config.startClaims) { - console.log("Start claims on CompensationClaims"); - argsMethod = proposeStartClaimsArgs; - } else if (config.setMaxSupplyDiff) { - console.log("setMaxSupplyDiff"); - argsMethod = proposeSetMaxSupplyDiffArgs; - } else if (config.setAirDropRoot) { - console.log("setAirDropRoot"); - argsMethod = proposeSetAirDropRootArgs; - } else if (config.proposeSettingUpdates) { - console.log("proposeSettingUpdates"); - argsMethod = proposeSettingUpdatesArgs; - } else if (config.withdrawAll) { - console.log("proposeWithdrawAll"); - argsMethod = proposeWithdrawAllArgs; - } else if (config.compoundDAI) { - console.log("proposeCompoundDAI"); - argsMethod = proposeCompoundDAIArgs; - } else if (config.compRewardTokenZero) { - argsMethod = proposeCompRewardTokenZero; - } else if (config.governorSetDelay) { - argsMethod = proposeGovernorSetDelayArgs; - } else { - console.error("An action must be specified on the command line."); - return; - } - - const { args, description } = await argsMethod(config); - - let propArgs; - if (config.governorV1) { - // The V1 governor requires an extra arg compared to v2 since it is payable. - propArgs = [args[0], [0], args[1], args[2]]; - } else { - propArgs = args; - } - - if (config.doIt) { - console.log("Sending a tx calling propose() on", governor.address); - console.log("args:", propArgs); - let transaction; - transaction = await governor - .connect(sDeployer) - .propose(...propArgs, description, await getTxOpts()); - console.log("Sent. tx hash:", transaction.hash); - console.log("Waiting for confirmation..."); - await ethers.provider.waitForTransaction( - transaction.hash, - NUM_CONFIRMATIONS - ); - console.log("Propose tx confirmed"); - } else { - console.log("Would send a tx to call propose() on", governor.address); - console.log("args:", propArgs); - } - - const newProposalId = await governor.proposalCount(); - console.log("New proposal count=", newProposalId.toString()); - console.log( - `Next step: call the following method on the governor at ${governor.address} via multi-sig` - ); - console.log(` queue(${newProposalId.toString()})`); - console.log("Done"); -} - -// Util to parse command line args. -function parseArgv() { - const args = {}; - for (const arg of process.argv) { - const elems = arg.split("="); - const key = elems[0]; - const val = elems.length > 1 ? elems[1] : true; - args[key] = val; - } - return args; -} - -// Parse config. -const args = parseArgv(); -const config = { - // dry run mode vs for real. - doIt: args["--doIt"] === "true" || false, - duration: args["--duration"], - address: args["--address"], - bps: args["--bps"], - governorV1: args["--governorV1"], - harvest: args["--harvest"], - setUniswapAddr: args["--setUniswapAddr"], - setBuybackUniswapAddr: args["--setBuybackUniswapAddr"], - setTrusteeFeeBps: args["--setTrusteeFeeBps"], - setRebaseHookAddr: args["--setRebaseHookAddr"], - upgradeOusd: args["--upgradeOusd"], - upgradeVaultCore: args["--upgradeVaultCore"], - upgradeVaultCoreAndAdmin: args["--upgradeVaultCoreAndAdmin"], - upgradeOracle: args["--upgradeOracle"], - claimStrategies: args["--claimStrategies"], - removeStrategy: args["--removeStrategy"], - addStrategies: args["--addStrategies"], - upgradeCurveStrategies: args["--upgradeCurveStrategies"], - setVaultBuffer: args["--setVaultBuffer"], - addAaveStrategyAndUpgradeCurveUsdt: - args["--addAaveStrategyAndUpgradeCurveUsdt"], - claimAaveStrategy: args["--claimAaveStrategy"], - prop14: args["--prop14"], - prop17: args["--prop17"], - pauseCapital: args["--pauseCapital"], - unpauseCapital: args["--unpauseCapital"], - unpauseRebase: args["--unpauseRebase"], - claimOGNStakingGovernance: args["--claimOGNStakingGovernance"], - upgradeStaking: args["--upgradeStaking"], - vaultv2Governance: args["--vaultv2Governance"], - ousdNewGovernor: args["--ousdNewGovernor"], - ousdv2Reset: args["--ousdv2Reset"], - setRewardLiquidationThreshold: args["--setRewardLiquidationThreshold"], - lockAdjuster: args["--lockAdjuster"], - unlockAdjuster: args["--unlockAdjuster"], - startClaims: args["--startClaims"], - setMaxSupplyDiff: args["--setMaxSupplyDiff"], - setAirDropRoot: args["--setAirDropRoot"], - proposeSettingUpdates: args["--proposeSettingUpdates"], - withdrawAll: args["--withdrawAll"], - compoundDAI: args["--compoundDAI"], - compRewardTokenZero: args["--compRewardTokenZero"], - governorSetDelay: args["--governorSetDelay"], -}; - -// Validate arguments. -if (config.address) { - if (!utils.isAddress(config.address)) { - throw new Error(`Invalid Ethereum address ${config.address}`); - } -} - -// Run the job. -main(config) - .then(() => process.exit(0)) - .catch((error) => { - console.error(error); - process.exit(1); - }); diff --git a/contracts/tasks/amoStrategy.js b/contracts/tasks/amoStrategy.js index 33c26ce6b4..558d7ca4da 100644 --- a/contracts/tasks/amoStrategy.js +++ b/contracts/tasks/amoStrategy.js @@ -280,37 +280,6 @@ async function amoStrategyTask(taskArguments) { vaultAdjustedTotalSupplyBefore )}` ); - - // Strategy's net minted and threshold - const netMintedForStrategy = await vault.netOusdMintedForStrategy({ - blockTag, - }); - const netMintedForStrategyThreshold = - await vault.netOusdMintForStrategyThreshold({ blockTag }); - const netMintedForStrategyDiff = - netMintedForStrategyThreshold.sub(netMintedForStrategy); - - output( - displayProperty( - "\nNet minted for strategy ", - assetSymbol, - netMintedForStrategy - ) - ); - output( - displayProperty( - "Net minted threshold", - assetSymbol, - netMintedForStrategyThreshold - ) - ); - output( - displayProperty( - "Net minted for strat diff", - assetSymbol, - netMintedForStrategyDiff - ) - ); } /************************************ @@ -323,10 +292,10 @@ async function mintAndAddOTokensTask(taskArguments) { // Get symbols and contracts const amoStrategyAddr = symbol === "OETH" - ? addresses.mainnet.ConvexOETHAMOStrategy + ? addresses.mainnet.CurveOETHAMOStrategy : addresses.mainnet.ConvexOUSDAMOStrategy; const amoStrategy = await ethers.getContractAt( - "ConvexEthMetaStrategy", + "CurveAMOStrategy", amoStrategyAddr ); @@ -345,10 +314,10 @@ async function removeAndBurnOTokensTask(taskArguments) { // Get symbols and contracts const amoStrategyAddr = symbol === "OETH" - ? addresses.mainnet.ConvexOETHAMOStrategy + ? addresses.mainnet.CurveOETHAMOStrategy : addresses.mainnet.ConvexOUSDAMOStrategy; const amoStrategy = await ethers.getContractAt( - "ConvexEthMetaStrategy", + "CurveAMOStrategy", amoStrategyAddr ); @@ -373,10 +342,10 @@ async function removeOnlyAssetsTask(taskArguments) { // Get symbols and contracts const amoStrategyAddr = symbol === "OETH" - ? addresses.mainnet.ConvexOETHAMOStrategy + ? addresses.mainnet.CurveOETHAMOStrategy : addresses.mainnet.ConvexOUSDAMOStrategy; const amoStrategy = await ethers.getContractAt( - "ConvexEthMetaStrategy", + "CurveAMOStrategy", amoStrategyAddr ); diff --git a/contracts/tasks/curve.js b/contracts/tasks/curve.js index ed0c634f2b..669a42aac9 100644 --- a/contracts/tasks/curve.js +++ b/contracts/tasks/curve.js @@ -2,7 +2,6 @@ const { BigNumber } = require("ethers"); const { formatUnits, parseUnits } = require("ethers/lib/utils"); const curveNGPoolAbi = require("../test/abi/curveStableSwapNG.json"); -const oethPoolAbi = require("../test/abi/oethMetapool.json"); const addresses = require("../utils/addresses"); const { resolveAsset } = require("../utils/resolvers"); const { getDiffBlocks } = require("./block"); @@ -378,18 +377,18 @@ async function curveContracts(oTokenSymbol) { // Get the contract addresses const poolAddr = oTokenSymbol === "OETH" - ? addresses.mainnet.CurveOETHMetaPool + ? addresses.mainnet.curve.OETH_WETH.pool : addresses.mainnet.curve.OUSD_USDC.pool; log(`Resolved ${oTokenSymbol} Curve pool to ${poolAddr}`); const strategyAddr = oTokenSymbol === "OETH" - ? addresses.mainnet.ConvexOETHAMOStrategy + ? addresses.mainnet.CurveOETHAMOStrategy : addresses.mainnet.CurveOUSDAMOStrategy; const convexRewardsPoolAddr = oTokenSymbol === "OETH" - ? addresses.mainnet.CVXETHRewardsPool + ? addresses.mainnet.curve.OETH_WETH.gauge : addresses.mainnet.curve.OUSD_USDC.gauge; - const poolLPSymbol = oTokenSymbol === "OETH" ? "OETHCRV-f" : "OUSD/USDC"; + const poolLPSymbol = oTokenSymbol === "OETH" ? "OETH/WETH" : "OUSD/USDC"; const vaultAddr = oTokenSymbol === "OETH" ? addresses.mainnet.OETHVaultProxy @@ -406,7 +405,7 @@ async function curveContracts(oTokenSymbol) { : await resolveAsset("USDC"); const pool = oTokenSymbol === "OETH" - ? await hre.ethers.getContractAt(oethPoolAbi, poolAddr) + ? await hre.ethers.getContractAt(curveNGPoolAbi, poolAddr) : await hre.ethers.getContractAt(curveNGPoolAbi, poolAddr); const cvxRewardPool = await ethers.getContractAt( "IRewardStaking", diff --git a/contracts/tasks/debug.js b/contracts/tasks/debug.js deleted file mode 100644 index d085b5aacd..0000000000 --- a/contracts/tasks/debug.js +++ /dev/null @@ -1,365 +0,0 @@ -const { utils } = require("ethers"); -const { formatUnits } = utils; - -const erc20Abi = require("../test/abi/erc20.json"); -const addresses = require("../utils/addresses"); - -/** - * Prints information about deployed contracts and their config. - */ -async function debug(taskArguments, hre) { - // - // Get all contracts to operate on. - const vaultProxy = await hre.ethers.getContract("VaultProxy"); - const ousdProxy = await hre.ethers.getContract("OUSDProxy"); - const aaveProxy = await hre.ethers.getContract("AaveStrategyProxy"); - const compoundProxy = await hre.ethers.getContract("CompoundStrategyProxy"); - const vault = await hre.ethers.getContractAt("IVault", vaultProxy.address); - const cVault = await hre.ethers.getContract("Vault"); - const vaultAdmin = await hre.ethers.getContract("VaultAdmin"); - const vaultCore = await hre.ethers.getContract("VaultCore"); - const ousd = await hre.ethers.getContractAt("OUSD", ousdProxy.address); - const cOusd = await hre.ethers.getContract("OUSD"); - const aaveStrategy = await hre.ethers.getContractAt( - "AaveStrategy", - aaveProxy.address - ); - const cAaveStrategy = await hre.ethers.getContract("AaveStrategy"); - const compoundStrategy = await hre.ethers.getContractAt( - "CompoundStrategy", - compoundProxy.address - ); - const cCompoundStrategy = await hre.ethers.getContract("CompoundStrategy"); - const threePoolStrategyProxy = await hre.ethers.getContract( - "ConvexStrategyProxy" - ); - const threePoolStrategy = await hre.ethers.getContractAt( - "ConvexStrategy", - threePoolStrategyProxy.address - ); - const cThreePoolStrategy = await hre.ethers.getContract("ConvexStrategy"); - - const oracleRouter = await hre.ethers.getContract("OracleRouter"); - - const governor = await hre.ethers.getContract("Governor"); - - const ognStakingProxy = await hre.ethers.getContract("OGNStakingProxy"); - const ognStaking = await hre.ethers.getContract("SingleAssetStaking"); - - const cBuyback = await hre.ethers.getContract("Buyback"); - - // - // Addresses - // - console.log("\nContract addresses"); - console.log("===================="); - console.log(`OUSD proxy: ${ousdProxy.address}`); - console.log(`OUSD impl: ${await ousdProxy.implementation()}`); - console.log(`OUSD: ${cOusd.address}`); - console.log(`Vault proxy: ${vaultProxy.address}`); - console.log(`Vault impl: ${await vaultProxy.implementation()}`); - console.log(`Vault: ${cVault.address}`); - console.log(`VaultCore: ${vaultCore.address}`); - console.log(`VaultAdmin: ${vaultAdmin.address}`); - console.log(`OracleRouter: ${oracleRouter.address}`); - console.log(`AaveStrategy proxy: ${aaveProxy.address}`); - console.log(`AaveStrategy impl: ${await aaveProxy.implementation()}`); - console.log(`AaveStrategy: ${cAaveStrategy.address}`); - console.log(`CompoundStrategy proxy: ${compoundProxy.address}`); - console.log( - `CompoundStrategy impl: ${await compoundProxy.implementation()}` - ); - console.log(`CompoundStrategy: ${cCompoundStrategy.address}`); - console.log(`ThreePoolStrategy proxy: ${threePoolStrategyProxy.address}`); - console.log( - `ThreePoolStrategy impl: ${await threePoolStrategyProxy.implementation()}` - ); - console.log(`ThreePoolStrategy: ${cThreePoolStrategy.address}`); - console.log(`Governor: ${governor.address}`); - console.log(`Buyback: ${cBuyback.address}`); - console.log(`OGNStaking proxy: ${ognStakingProxy.address}`); - console.log( - `OGNStaking proxy impl: ${await ognStakingProxy.implementation()}` - ); - console.log(`OGNStaking: ${ognStaking.address}`); - - // - // Governor - // - const govAdmin = await governor.admin(); - const govPendingAdmin = await governor.pendingAdmin(); - const govDelay = await governor.delay(); - const govPropCount = await governor.proposalCount(); - console.log("\nGovernor"); - console.log("===================="); - console.log("Admin: ", govAdmin); - console.log("PendingAdmin: ", govPendingAdmin); - console.log("Delay (seconds): ", govDelay.toString()); - console.log("ProposalCount: ", govPropCount.toString()); - - // - // Governance - // - - // Read the current governor address on all the contracts. - const ousdGovernorAddr = await ousd.governor(); - const vaultGovernorAddr = await vault.governor(); - const aaveStrategyGovernorAddr = await aaveStrategy.governor(); - const compoundStrategyGovernorAddr = await compoundStrategy.governor(); - const threePoolStrategyGovernorAddr = await threePoolStrategy.governor(); - - console.log("\nGovernor addresses"); - console.log("===================="); - console.log("OUSD: ", ousdGovernorAddr); - console.log("Vault: ", vaultGovernorAddr); - console.log("AaveStrategy: ", aaveStrategyGovernorAddr); - console.log("CompoundStrategy: ", compoundStrategyGovernorAddr); - console.log("ThreePoolStrategy: ", threePoolStrategyGovernorAddr); - - // - // OUSD - // - const name = await ousd.name(); - const decimals = await ousd.decimals(); - const symbol = await ousd.symbol(); - const totalSupply = await ousd.totalSupply(); - const vaultAddress = await ousd.vaultAddress(); - const nonRebasingSupply = await ousd.nonRebasingSupply(); - const rebasingSupply = totalSupply.sub(nonRebasingSupply); - const rebasingCreditsPerToken = await ousd.rebasingCreditsPerToken(); - const rebasingCredits = await ousd.rebasingCredits(); - - console.log("\nOUSD"); - console.log("======="); - console.log(`name: ${name}`); - console.log(`symbol: ${symbol}`); - console.log(`decimals: ${decimals}`); - console.log(`totalSupply: ${formatUnits(totalSupply, 18)}`); - console.log(`vaultAddress: ${vaultAddress}`); - console.log(`nonRebasingSupply: ${formatUnits(nonRebasingSupply, 18)}`); - console.log(`rebasingSupply: ${formatUnits(rebasingSupply, 18)}`); - console.log(`rebasingCreditsPerToken: ${rebasingCreditsPerToken}`); - console.log(`rebasingCredits: ${rebasingCredits}`); - - // - // Oracle - // - console.log("\nOracle"); - console.log("========"); - const priceDAI = await oracleRouter.price(addresses.mainnet.DAI); - const priceUSDC = await oracleRouter.price(addresses.mainnet.USDC); - const priceUSDT = await oracleRouter.price(addresses.mainnet.USDT); - console.log(`DAI price : ${formatUnits(priceDAI, 8)} USD`); - console.log(`USDC price: ${formatUnits(priceUSDC, 8)} USD`); - console.log(`USDT price: ${formatUnits(priceUSDT, 8)} USD`); - - // - // Vault - // - const rebasePaused = await vault.rebasePaused(); - const capitalPaused = await vault.capitalPaused(); - const redeemFeeBps = Number(await vault.redeemFeeBps()); - const trusteeFeeBps = Number(await vault.trusteeFeeBps()); - const vaultBuffer = Number( - formatUnits((await vault.vaultBuffer()).toString(), 18) - ); - const autoAllocateThreshold = await vault.autoAllocateThreshold(); - const rebaseThreshold = await vault.rebaseThreshold(); - const maxSupplyDiff = await vault.maxSupplyDiff(); - const strategyCount = await vault.getStrategyCount(); - const assetCount = await vault.getAssetCount(); - const strategistAddress = await vault.strategistAddr(); - const trusteeAddress = await vault.trusteeAddress(); - const priceProvider = await vault.priceProvider(); - - console.log("\nVault Settings"); - console.log("================"); - console.log("rebasePaused:\t\t\t", rebasePaused); - console.log("capitalPaused:\t\t\t", capitalPaused); - console.log(`redeemFeeBps:\t\t\t ${redeemFeeBps} (${redeemFeeBps / 100}%)`); - console.log( - `trusteeFeeBps:\t\t\t ${trusteeFeeBps} (${trusteeFeeBps / 100}%)` - ); - console.log(`vaultBuffer:\t\t\t ${vaultBuffer} (${vaultBuffer * 100}%)`); - console.log( - "autoAllocateThreshold (USD):\t", - formatUnits(autoAllocateThreshold.toString(), 18) - ); - console.log( - "rebaseThreshold (USD):\t\t", - formatUnits(rebaseThreshold.toString(), 18) - ); - - console.log( - `maxSupplyDiff:\t\t\t ${formatUnits(maxSupplyDiff.toString(), 16)}%` - ); - - console.log("Price provider address:\t\t", priceProvider); - console.log("Strategy count:\t\t\t", Number(strategyCount)); - console.log("Asset count:\t\t\t", Number(assetCount)); - console.log("Strategist address:\t\t", strategistAddress); - console.log("Trustee address:\t\t", trusteeAddress); - - const assets = [ - { - symbol: "DAI", - address: addresses.mainnet.DAI, - decimals: 18, - }, - { - symbol: "USDC", - address: addresses.mainnet.USDC, - decimals: 6, - }, - { - symbol: "USDT", - address: addresses.mainnet.USDT, - decimals: 6, - }, - ]; - - const totalValue = await vault.totalValue(); - const balances = {}; - for (const asset of assets) { - const balance = await vault["checkBalance(address)"](asset.address); - balances[asset.symbol] = formatUnits(balance.toString(), asset.decimals); - } - - console.log("\nVault balances"); - console.log("================"); - console.log( - `totalValue (USD):\t $${Number( - formatUnits(totalValue.toString(), 18) - ).toFixed(2)}` - ); - for (const [symbol, balance] of Object.entries(balances)) { - console.log(` ${symbol}:\t\t\t ${Number(balance).toFixed(2)}`); - } - - console.log("\nVault buffer balances"); - console.log("================"); - - const vaultBufferBalances = {}; - for (const asset of assets) { - vaultBufferBalances[asset.symbol] = - (await ( - await hre.ethers.getContractAt(erc20Abi, asset.address) - ).balanceOf(vault.address)) / - (1 * 10 ** asset.decimals); - } - for (const [symbol, balance] of Object.entries(vaultBufferBalances)) { - console.log(`${symbol}:\t\t\t ${balance}`); - } - - console.log("\nStrategies balances"); - console.log("====================="); - // - // Aave Strategy - // - let asset = assets[0]; // Aave only holds DAI - let balanceRaw = await aaveStrategy.checkBalance(asset.address); - let balance = formatUnits(balanceRaw.toString(), asset.decimals); - console.log(`Aave ${asset.symbol}:\t balance=${balance}`); - - // - // Compound Strategy - // - let compoundsAssets = [assets[0], assets[1], assets[2]]; // Compound only holds USDC and USDT - for (asset of compoundsAssets) { - balanceRaw = await compoundStrategy.checkBalance(asset.address); - balance = formatUnits(balanceRaw.toString(), asset.decimals); - console.log(`Compound ${asset.symbol}:\t balance=${balance}`); - } - - // - // ThreePool Strategy - // Supports all stablecoins - // - for (asset of assets) { - balanceRaw = await threePoolStrategy.checkBalance(asset.address); - balance = formatUnits(balanceRaw.toString(), asset.decimals); - console.log(`ThreePool ${asset.symbol}:\t balance=${balance}`); - } - - // - // Strategies settings - // - - console.log("\nDefault strategies"); - console.log("============================"); - for (const asset of assets) { - console.log( - asset.symbol, - `\t${await vault.assetDefaultStrategies(asset.address)}` - ); - } - - console.log("\nAave strategy settings"); - console.log("============================"); - console.log("vaultAddress:\t\t\t", await aaveStrategy.vaultAddress()); - console.log("platformAddress:\t\t", await aaveStrategy.platformAddress()); - console.log( - "rewardTokenAddress:\t\t", - await aaveStrategy.rewardTokenAddress() - ); - console.log( - "rewardLiquidationThreshold:\t", - (await aaveStrategy.rewardLiquidationThreshold()).toString() - ); - for (const asset of assets) { - console.log( - `supportsAsset(${asset.symbol}):\t\t`, - await aaveStrategy.supportsAsset(asset.address) - ); - } - - console.log("\nCompound strategy settings"); - console.log("============================"); - console.log("vaultAddress:\t\t\t", await compoundStrategy.vaultAddress()); - console.log("platformAddress:\t\t", await compoundStrategy.platformAddress()); - console.log( - "rewardTokenAddress:\t\t", - await compoundStrategy.rewardTokenAddress() - ); - console.log( - "rewardLiquidationThreshold:\t", - (await compoundStrategy.rewardLiquidationThreshold()).toString() - ); - for (const asset of assets) { - console.log( - `supportsAsset(${asset.symbol}):\t\t`, - await compoundStrategy.supportsAsset(asset.address) - ); - } - - console.log("\n3pool strategy settings"); - console.log("=============================="); - console.log("vaultAddress:\t\t\t", await threePoolStrategy.vaultAddress()); - console.log( - "platformAddress:\t\t", - await threePoolStrategy.platformAddress() - ); - console.log( - "rewardTokenAddress:\t\t", - await threePoolStrategy.rewardTokenAddress() - ); - console.log( - "cvxRewardTokenAddress:\t\t", - await threePoolStrategy.cvxRewardTokenAddress() - ); - console.log( - "rewardLiquidationThreshold:\t", - (await threePoolStrategy.rewardLiquidationThreshold()).toString() - ); - - for (const asset of assets) { - console.log( - `supportsAsset(${asset.symbol}):\t\t`, - await threePoolStrategy.supportsAsset(asset.address) - ); - } -} - -module.exports = { - debug, -}; diff --git a/contracts/tasks/dripper.js b/contracts/tasks/dripper.js deleted file mode 100644 index c6a7859db8..0000000000 --- a/contracts/tasks/dripper.js +++ /dev/null @@ -1,40 +0,0 @@ -const { resolveContract } = require("../utils/resolvers"); -const { getSigner } = require("../utils/signers"); -const { logTxDetails } = require("../utils/txLogger"); - -const log = require("../utils/logger")("task:vault"); - -async function getContract(symbol) { - const contractPrefix = symbol === "OUSD" ? "" : symbol; - const dripper = await resolveContract( - `${contractPrefix}DripperProxy`, - "IDripper" - ); - - return dripper; -} - -async function setDripDuration({ symbol, duration }) { - const signer = await getSigner(); - - const dripper = await getContract(symbol); - - log(`About setDripDuration to ${duration} seconds on the ${symbol} Dripper`); - const tx = await dripper.connect(signer).setDripDuration(duration); - await logTxDetails(tx, "setDripDuration"); -} - -async function collect({ symbol }) { - const signer = await getSigner(); - - const dripper = await getContract(symbol); - - log(`About collect from the ${symbol} Dripper`); - const tx = await dripper.connect(signer).collect(); - await logTxDetails(tx, "collect"); -} - -module.exports = { - collect, - setDripDuration, -}; diff --git a/contracts/tasks/governance.js b/contracts/tasks/governance.js index 36f94f82be..268811fed8 100644 --- a/contracts/tasks/governance.js +++ b/contracts/tasks/governance.js @@ -96,33 +96,11 @@ async function proposal(taskArguments, hre) { async function governors() { const cOUSDProxy = await ethers.getContract("OUSDProxy"); const cVaultProxy = await ethers.getContract("VaultProxy"); - const cCompoundStrategyProxy = await ethers.getContract( - "CompoundStrategyProxy" - ); - const cThreePoolStrategyProxy = await ethers.getContract( - "ThreePoolStrategyProxy" - ); - const cAaveStrategyProxy = await ethers.getContract("AaveStrategyProxy"); - const cBuyback = await ethers.getContract("Buyback"); - const cOGNStakingProxy = await ethers.getContract("OGNStakingProxy"); - const cCompensationClaim = await ethers.getContract("CompensationClaims"); console.log("Governor addresses:"); console.log("==================="); console.log("OUSDProxy: ", await cOUSDProxy.governor()); console.log("VaultProxy: ", await cVaultProxy.governor()); - console.log( - "CompoundStrategyProxy: ", - await cCompoundStrategyProxy.governor() - ); - console.log( - "ThreePoolStrategyProxy: ", - await cThreePoolStrategyProxy.governor() - ); - console.log("AaveStrategyProxy: ", await cAaveStrategyProxy.governor()); - console.log("Buyback: ", await cBuyback.governor()); - console.log("OGNSTakingProxy: ", await cOGNStakingProxy.governor()); - console.log("CompensationClaim: ", await cCompensationClaim.governor()); } module.exports = { diff --git a/contracts/tasks/tasks.js b/contracts/tasks/tasks.js index 8275c3513d..e24c96526a 100644 --- a/contracts/tasks/tasks.js +++ b/contracts/tasks/tasks.js @@ -1,5 +1,4 @@ const { subtask, task, types } = require("hardhat/config"); -const { debug } = require("./debug"); const { env } = require("./env"); const { setActionVars, updateAction } = require("./defender"); const { execute, executeOnFork, proposal, governors } = require("./governance"); @@ -17,7 +16,6 @@ const { encryptMasterPrivateKey, decryptMasterPrivateKey, } = require("./amazon"); -const { collect, setDripDuration } = require("./dripper"); const { getSigner, getDefenderSigner } = require("../utils/signers"); const { snapMorpho } = require("../utils/morpho"); const { snapAero } = require("./aero"); @@ -151,9 +149,6 @@ const log = require("../utils/logger")("tasks"); // Environment tasks. task("env", "Check env vars are properly set for a Mainnet deployment", env); -// Debug tasks. -task("debug", "Print info about contracts and their configs", debug); - // Token tasks. subtask("allowance", "Get the token allowance an owner has given to a spender") .addParam( @@ -511,38 +506,6 @@ task("claimWithdrawal").setAction(async (_, __, runSuper) => { return runSuper(); }); -// Dripper - -subtask("collect", "Collect harvested rewards from the Dripper to the Vault") - .addOptionalParam( - "symbol", - "Symbol of the OToken. eg OETH or OUSD", - "OETH", - types.string - ) - .setAction(collect); -task("collect").setAction(async (_, __, runSuper) => { - return runSuper(); -}); - -subtask("setDripDuration", "Set the Dripper duration") - .addParam( - "duration", - "The number of seconds to drip harvested rewards", - undefined, - types.int - ) - .addOptionalParam( - "symbol", - "Symbol of the OToken. eg OETH or OUSD", - "OETH", - types.string - ) - .setAction(setDripDuration); -task("setDripDuration").setAction(async (_, __, runSuper) => { - return runSuper(); -}); - // Governance tasks subtask("execute", "Execute a governance proposal") .addParam("id", "Proposal ID") @@ -771,7 +734,7 @@ subtask("curveAdd", "Add liquidity to Curve Metapool") types.string ) .addParam("otokens", "Amount of OTokens. eg OETH or OUSD", 0, types.float) - .addParam("assets", "Amount of assets. eg ETH or 3CRV", 0, types.float) + .addParam("assets", "Amount of assets. eg ETH", 0, types.float) .addOptionalParam( "slippage", "Max allowed slippage as a percentage to 2 decimal places.", @@ -780,7 +743,7 @@ subtask("curveAdd", "Add liquidity to Curve Metapool") ) .addOptionalParam( "min", - "Min Metapool LP tokens to be minted.", + "Min Curve pool LP tokens to be minted.", undefined, types.float ) @@ -797,7 +760,7 @@ subtask("curveRemove", "Remove liquidity from Curve Metapool") types.string ) .addParam("otokens", "Amount of OTokens. eg OETH or OUSD", 0, types.float) - .addParam("assets", "Amount of assets. eg ETH or 3CRV", 0, types.float) + .addParam("assets", "Amount of assets. eg ETH", 0, types.float) .addOptionalParam( "slippage", "Max allowed slippage as a percentage to 2 decimal places.", @@ -818,7 +781,7 @@ subtask("curveSwap", "Swap Metapool tokens") ) .addParam( "from", - "Symbol of the from token. eg OETH, ETH, 3CRV, OUSD", + "Symbol of the from token. eg OETH, ETH, OUSD", undefined, types.string ) @@ -879,7 +842,7 @@ subtask( ) .addParam( "amount", - "Amount of Metapool LP tokens to burn for removed assets", + "Amount of Curve pool LP tokens to burn for removed assets", 0, types.float ) diff --git a/contracts/test/_fixture-base.js b/contracts/test/_fixture-base.js index c17e0180cf..3024f13a60 100644 --- a/contracts/test/_fixture-base.js +++ b/contracts/test/_fixture-base.js @@ -8,7 +8,6 @@ const { deployWithConfirmation } = require("../utils/deploy"); const addresses = require("../utils/addresses"); const erc20Abi = require("./abi/erc20.json"); const hhHelpers = require("@nomicfoundation/hardhat-network-helpers"); -const { getCreate2ProxyAddress } = require("../deploy/deployActions"); const log = require("../utils/logger")("test:fixtures-base"); @@ -26,17 +25,6 @@ const BURNER_ROLE = let snapshotId; -const baseFixtureWithMockedVaultAdminConfig = async () => { - const fixture = await defaultFixture(); - await deployWithConfirmation("MockOETHVault", [fixture.weth.address]); - - fixture.oethbVault = await ethers.getContractAt( - "IMockVault", - fixture.oethbVault.address - ); - return fixture; -}; - const defaultFixture = async () => { if (!snapshotId && !isFork) { snapshotId = await nodeSnapshot(); @@ -78,7 +66,7 @@ const defaultFixture = async () => { oethbVaultProxy.address ); - let aerodromeAmoStrategy, dripper, harvester, quoter, sugar, curveAMOStrategy; + let aerodromeAmoStrategy, harvester, quoter, sugar, curveAMOStrategy; if (isFork) { // Aerodrome AMO Strategy const aerodromeAmoStrategyProxy = await ethers.getContract( @@ -109,13 +97,6 @@ const defaultFixture = async () => { quoter = await hre.ethers.getContract("AerodromeAMOQuoter"); - // Dripper - const dripperProxy = await ethers.getContract("OETHBaseDripperProxy"); - dripper = await ethers.getContractAt( - "FixedRateDripper", - dripperProxy.address - ); - const curveAMOProxy = await ethers.getContract("OETHBaseCurveAMOProxy"); curveAMOStrategy = await ethers.getContractAt( "BaseCurveAMOStrategy", @@ -134,11 +115,6 @@ const defaultFixture = async () => { "BridgedWOETHStrategy", woethStrategyProxy.address ); - - const oracleRouter = await ethers.getContract( - isFork ? "OETHBaseOracleRouter" : "MockOracleRouter" - ); - const mockStrategy = isFork ? undefined : await ethers.getContract("MockStrategy"); @@ -258,13 +234,11 @@ const defaultFixture = async () => { wOETHb, zapper, harvester, - dripper, // Bridged WOETH woeth, woethProxy, woethStrategy, - oracleRouter, // Strategies aerodromeAmoStrategy, @@ -295,9 +269,6 @@ const defaultFixture = async () => { }; const defaultBaseFixture = deployments.createFixture(defaultFixture); -const baseFixtureWithMockedVaultAdmin = deployments.createFixture( - baseFixtureWithMockedVaultAdminConfig -); const bridgeHelperModuleFixture = deployments.createFixture(async () => { const fixture = await defaultBaseFixture(); @@ -334,9 +305,8 @@ const bridgeHelperModuleFixture = deployments.createFixture(async () => { const crossChainFixture = deployments.createFixture(async () => { const fixture = await defaultBaseFixture(); - const crossChainStrategyProxyAddress = await getCreate2ProxyAddress( - "CrossChainStrategyProxy" - ); + const crossChainStrategyProxyAddress = + addresses.base.CrossChainRemoteStrategy; const crossChainRemoteStrategy = await ethers.getContractAt( "CrossChainRemoteStrategy", crossChainStrategyProxyAddress @@ -376,6 +346,8 @@ const crossChainFixture = deployments.createFixture(async () => { .connect(fixture.rafael) .mint(fixture.rafael.address, usdcUnits("1000000")); + fixture.relayer = await impersonateAndFund(addresses.base.OZRelayerAddress); + return { ...fixture, crossChainRemoteStrategy, @@ -392,7 +364,6 @@ mocha.after(async () => { module.exports = { defaultBaseFixture, - baseFixtureWithMockedVaultAdmin, MINTER_ROLE, BURNER_ROLE, bridgeHelperModuleFixture, diff --git a/contracts/test/_fixture-plume.js b/contracts/test/_fixture-plume.js index 91581ff216..ece30ef133 100644 --- a/contracts/test/_fixture-plume.js +++ b/contracts/test/_fixture-plume.js @@ -4,10 +4,6 @@ const mocha = require("mocha"); const { isFork, isPlumeFork, oethUnits } = require("./helpers"); const { impersonateAndFund } = require("../utils/signers"); const { nodeRevert, nodeSnapshot } = require("./_fixture"); -const { deployWithConfirmation } = require("../utils/deploy"); -const { - deployPlumeMockRoosterAMOStrategyImplementation, -} = require("../deploy/deployActions.js"); const addresses = require("../utils/addresses"); const hhHelpers = require("@nomicfoundation/hardhat-network-helpers"); const log = require("../utils/logger")("test:fixtures-plume"); @@ -19,36 +15,6 @@ const BURNER_ROLE = let snapshotId; -const baseFixtureWithMockedVaultAdminConfig = async () => { - const fixture = await defaultFixture(); - await deployWithConfirmation("MockOETHVault", [fixture.weth.address]); - - fixture.oethpVault = await ethers.getContractAt( - "IMockVault", - fixture.oethpVault.address - ); - - const mockImplementation = - await deployPlumeMockRoosterAMOStrategyImplementation( - addresses.plume.OethpWETHRoosterPool - ); - - const roosterAmoStrategyProxy = await ethers.getContract( - "RoosterAMOStrategyProxy" - ); - - await roosterAmoStrategyProxy - .connect(fixture.governor) - .upgradeTo(mockImplementation.address); - - fixture.roosterAmoStrategy = await ethers.getContractAt( - "MockRoosterAMOStrategy", - roosterAmoStrategyProxy.address - ); - - return fixture; -}; - const defaultFixture = async () => { if (!snapshotId && !isFork) { snapshotId = await nodeSnapshot(); @@ -150,10 +116,6 @@ const defaultFixture = async () => { addresses.plume.WETH ); - const oracleRouter = await ethers.getContract( - isFork ? "OETHPlumeOracleRouter" : "MockOracleRouter" - ); - const _mintWETH = async (signer, amount) => { if (isFork) { await wethMintableContract.connect(governor).mint(signer.address, amount); @@ -222,7 +184,6 @@ const defaultFixture = async () => { woeth, woethProxy, woethStrategy, - oracleRouter, // Helpers _mintWETH, @@ -230,9 +191,6 @@ const defaultFixture = async () => { }; const defaultPlumeFixture = deployments.createFixture(defaultFixture); -const plumeFixtureWithMockedVaultAdmin = deployments.createFixture( - baseFixtureWithMockedVaultAdminConfig -); const bridgeHelperModuleFixture = deployments.createFixture(async () => { const fixture = await defaultPlumeFixture(); @@ -270,6 +228,5 @@ mocha.after(async () => { module.exports = { defaultPlumeFixture, - plumeFixtureWithMockedVaultAdmin, bridgeHelperModuleFixture, }; diff --git a/contracts/test/_fixture.js b/contracts/test/_fixture.js index 68f8196d01..accaf235e8 100644 --- a/contracts/test/_fixture.js +++ b/contracts/test/_fixture.js @@ -1,6 +1,5 @@ const hre = require("hardhat"); const { ethers } = hre; -const { formatUnits } = require("ethers/lib/utils"); const mocha = require("mocha"); require("./_global-hooks"); @@ -8,9 +7,7 @@ require("./_global-hooks"); const { hotDeployOption } = require("./_hot-deploy.js"); const addresses = require("../utils/addresses"); const { resolveContract } = require("../utils/resolvers"); -//const { setChainlinkOraclePrice } = require("../utils/oracle"); -const { balancer_rETH_WETH_PID } = require("../utils/constants"); const { fundAccounts, fundAccountsForOETHUnitTests, @@ -20,38 +17,24 @@ const { deployWithConfirmation } = require("../utils/deploy"); const { replaceContractAt } = require("../utils/hardhat"); const { getAssetAddresses, - getOracleAddresses, oethUnits, ousdUnits, usdcUnits, - units, isTest, isFork, isHolesky, isHoleskyFork, } = require("./helpers"); const { hardhatSetBalance, setERC20TokenBalance } = require("./_fund"); -const { getCreate2ProxyAddress } = require("../deploy/deployActions"); const usdsAbi = require("./abi/usds.json").abi; const usdtAbi = require("./abi/usdt.json").abi; const erc20Abi = require("./abi/erc20.json"); -const morphoAbi = require("./abi/morpho.json"); -const morphoLensAbi = require("./abi/morphoLens.json"); -const crvMinterAbi = require("./abi/crvMinter.json"); -const susdsAbi = require("./abi/sUSDS.json"); const metamorphoAbi = require("./abi/metamorpho.json"); const merklDistributorAbi = require("./abi/merklDistributor.json"); -// const curveFactoryAbi = require("./abi/curveFactory.json") -const ousdMetapoolAbi = require("./abi/ousdMetapool.json"); -const oethMetapoolAbi = require("./abi/oethMetapool.json"); -const threepoolLPAbi = require("./abi/threepoolLP.json"); -const threepoolSwapAbi = require("./abi/threepoolSwap.json"); const curveXChainLiquidityGaugeAbi = require("./abi/curveXChainLiquidityGauge.json"); const curveStableSwapNGAbi = require("./abi/curveStableSwapNG.json"); - -const sfrxETHAbi = require("./abi/sfrxETH.json"); const { defaultAbiCoder, parseUnits } = require("ethers/lib/utils"); const { impersonateAndFund } = require("../utils/signers"); @@ -87,20 +70,9 @@ const simpleOETHFixture = deployments.createFixture(async () => { const cWOETHProxy = await ethers.getContract("WOETHProxy"); const woeth = await ethers.getContractAt("WOETH", cWOETHProxy.address); - const oethHarvesterProxy = await ethers.getContract("OETHHarvesterProxy"); - const oethHarvester = await ethers.getContractAt( - "OETHHarvester", - oethHarvesterProxy.address - ); - - const oethOracleRouter = await ethers.getContract( - isFork ? "OETHOracleRouter" : "OracleRouter" - ); - let weth, ssv, nativeStakingSSVStrategy, - oethDripper, oethFixedRateDripper, simpleOETHHarvester; @@ -113,12 +85,6 @@ const simpleOETHFixture = deployments.createFixture(async () => { weth = await ethers.getContractAt("IWETH9", addressContext.WETH); ssv = await ethers.getContractAt(erc20Abi, addressContext.SSV); - const oethDripperProxy = await ethers.getContract("OETHDripperProxy"); - oethDripper = await ethers.getContractAt( - "OETHDripper", - oethDripperProxy.address - ); - const oethFixedRateDripperProxy = await ethers.getContract( "OETHFixedRateDripperProxy" ); @@ -154,6 +120,14 @@ const simpleOETHFixture = deployments.createFixture(async () => { "NativeStakingSSVStrategy", nativeStakingStrategyProxy.address ); + + const simpleOETHHarvesterProxy = await ethers.getContract( + "OETHSimpleHarvesterProxy" + ); + simpleOETHHarvester = await ethers.getContractAt( + "OETHHarvesterSimple", + simpleOETHHarvesterProxy.address + ); } if (!isFork) { @@ -200,8 +174,6 @@ const simpleOETHFixture = deployments.createFixture(async () => { domen, daniel, franck, - // Contracts - oethOracleRouter, // Assets ssv, weth, @@ -210,10 +182,9 @@ const simpleOETHFixture = deployments.createFixture(async () => { oeth, woeth, nativeStakingSSVStrategy, - oethDripper, oethFixedRateDripper, - oethHarvester, simpleOETHHarvester, + oethHarvester: simpleOETHHarvester, }; }); @@ -568,52 +539,16 @@ const defaultFixture = deployments.createFixture(async () => { const { governorAddr, multichainStrategistAddr, timelockAddr } = await getNamedAccounts(); - const vaultAndTokenConracts = await getVaultAndTokenContracts(); - - const harvesterProxy = await ethers.getContract("HarvesterProxy"); - const harvester = await ethers.getContractAt( - "Harvester", - harvesterProxy.address - ); - const oethHarvesterProxy = await ethers.getContract("OETHHarvesterProxy"); - const oethHarvester = await ethers.getContractAt( - "OETHHarvester", - oethHarvesterProxy.address - ); + const vaultAndTokenContracts = await getVaultAndTokenContracts(); - const dripperProxy = await ethers.getContract("DripperProxy"); - const dripper = await ethers.getContractAt("Dripper", dripperProxy.address); + const dripperProxy = isFork + ? await ethers.getContract("DripperProxy") + : undefined; + const dripper = isFork + ? await ethers.getContractAt("Dripper", dripperProxy.address) + : undefined; const wousdProxy = await ethers.getContract("WrappedOUSDProxy"); const wousd = await ethers.getContractAt("WrappedOusd", wousdProxy.address); - const CompoundStrategyFactory = await ethers.getContractFactory( - "CompoundStrategy" - ); - - const compoundStrategyProxy = await ethers.getContract( - "CompoundStrategyProxy" - ); - - const compoundStrategy = await ethers.getContractAt( - "CompoundStrategy", - compoundStrategyProxy.address - ); - - const convexStrategyProxy = await ethers.getContract("ConvexStrategyProxy"); - const convexStrategy = await ethers.getContractAt( - "ConvexStrategy", - convexStrategyProxy.address - ); - - const aaveStrategyProxy = await ethers.getContract("AaveStrategyProxy"); - const aaveStrategy = await ethers.getContractAt( - "AaveStrategy", - aaveStrategyProxy.address - ); - - const oracleRouter = await ethers.getContract("OracleRouter"); - const oethOracleRouter = await ethers.getContract( - isFork ? "OETHOracleRouter" : "OracleRouter" - ); const nativeStakingStrategyProxy = await ethers.getContract( "NativeStakingSSVStrategyProxy" @@ -631,36 +566,6 @@ const defaultFixture = deployments.createFixture(async () => { nativeStakingFeeAccumulatorProxy.address ); - const morphoSteakhouseUSDCStrategyProxy = !isFork - ? undefined - : await ethers.getContract("MetaMorphoStrategyProxy"); - const morphoSteakhouseUSDCStrategy = !isFork - ? undefined - : await ethers.getContractAt( - "Generalized4626Strategy", - morphoSteakhouseUSDCStrategyProxy.address - ); - - const morphoGauntletPrimeUSDCStrategyProxy = !isFork - ? undefined - : await ethers.getContract("MorphoGauntletPrimeUSDCStrategyProxy"); - const morphoGauntletPrimeUSDCStrategy = !isFork - ? undefined - : await ethers.getContractAt( - "Generalized4626Strategy", - morphoGauntletPrimeUSDCStrategyProxy.address - ); - - const morphoGauntletPrimeUSDTStrategyProxy = !isFork - ? undefined - : await ethers.getContract("MorphoGauntletPrimeUSDTStrategyProxy"); - const morphoGauntletPrimeUSDTStrategy = !isFork - ? undefined - : await ethers.getContractAt( - "Generalized4626USDTStrategy", - morphoGauntletPrimeUSDTStrategyProxy.address - ); - const morphoOUSDv2StrategyProxy = !isFork ? undefined : await ethers.getContract("OUSDMorphoV2StrategyProxy"); @@ -678,16 +583,13 @@ const defaultFixture = deployments.createFixture(async () => { ) : undefined; - const simpleHarvesterProxy = isFork - ? await ethers.getContract("OETHSimpleHarvesterProxy") - : undefined; - - const simpleOETHHarvester = isFork - ? await ethers.getContractAt( - "OETHHarvesterSimple", - simpleHarvesterProxy.address - ) - : undefined; + const simpleHarvesterProxy = await ethers.getContract( + "OETHSimpleHarvesterProxy" + ); + const simpleOETHHarvester = await ethers.getContractAt( + "OETHHarvesterSimple", + simpleHarvesterProxy.address + ); const oethFixedRateDripperProxy = !isFork ? undefined @@ -735,72 +637,10 @@ const defaultFixture = deployments.createFixture(async () => { ? undefined : await ethers.getContract("MockStrategy"); - let usdt, - usds, - tusd, - usdc, - weth, - ogn, - ogv, - nonStandardToken, - cusdt, - cusdc, - comp, - adai, - ausdt, - ausdc, - aave, - aaveToken, - stkAave, - aaveIncentivesController, - reth, - stETH, - frxETH, - sfrxETH, - sUSDS, - morphoSteakHouseUSDCVault, - morphoGauntletPrimeUSDCVault, - morphoGauntletPrimeUSDTVault, - morphoOUSDv2Vault, - ssv; - - let chainlinkOracleFeedDAI, - chainlinkOracleFeedUSDT, - chainlinkOracleFeedUSDC, - chainlinkOracleFeedUSDS, - chainlinkOracleFeedOGNETH, - chainlinkOracleFeedETH, - crv, - crvMinter, - aura, - bal, - threePool, - threePoolToken, - metapoolToken, - morpho, - morphoToken, - legacyMorphoToken, - morphoCompoundStrategy, - balancerREthStrategy, - makerSSRStrategy, - morphoAaveStrategy, - oethMorphoAaveStrategy, - morphoLens, - threePoolGauge, - aaveAddressProvider, - cvx, - cvxBooster, - cvxRewardPool, - depositContractUtils, - oethDripper, + let usdt, usds, usdc, weth, ogn, morphoOUSDv2Vault, ssv; + + let depositContractUtils, oethZapper, - swapper, - mockSwapper, - swapper1Inch, - mock1InchSwapRouter, - convexEthMetaStrategy, - vaultValueChecker, - oethVaultValueChecker, poolBoosterCentralRegistry, poolBoosterMerklFactory, merklDistributor; @@ -810,133 +650,16 @@ const defaultFixture = deployments.createFixture(async () => { usds = await ethers.getContractAt(usdsAbi, addresses.mainnet.USDS); usdc = await ethers.getContractAt(erc20Abi, addresses.mainnet.USDC); weth = await ethers.getContractAt("IWETH9", addresses.mainnet.WETH); - cusdt = await ethers.getContractAt(erc20Abi, addresses.mainnet.cUSDT); - cusdc = await ethers.getContractAt(erc20Abi, addresses.mainnet.cUSDC); - comp = await ethers.getContractAt(erc20Abi, addresses.mainnet.COMP); - crv = await ethers.getContractAt(erc20Abi, addresses.mainnet.CRV); - cvx = await ethers.getContractAt(erc20Abi, addresses.mainnet.CVX); ogn = await ethers.getContractAt(erc20Abi, addresses.mainnet.OGN); - aave = await ethers.getContractAt(erc20Abi, addresses.mainnet.Aave); - ausdt = await ethers.getContractAt(erc20Abi, addresses.mainnet.aUSDT); - ausdc = await ethers.getContractAt(erc20Abi, addresses.mainnet.aUSDC); - adai = await ethers.getContractAt(erc20Abi, addresses.mainnet.aDAI); - reth = await ethers.getContractAt("IRETH", addresses.mainnet.rETH); - frxETH = await ethers.getContractAt(erc20Abi, addresses.mainnet.frxETH); - sfrxETH = await ethers.getContractAt(sfrxETHAbi, addresses.mainnet.sfrxETH); - stETH = await ethers.getContractAt(erc20Abi, addresses.mainnet.stETH); - sUSDS = await ethers.getContractAt(susdsAbi, addresses.mainnet.sUSDS); - morpho = await ethers.getContractAt(morphoAbi, addresses.mainnet.Morpho); - morphoLens = await ethers.getContractAt( - morphoLensAbi, - addresses.mainnet.MorphoLens - ); - morphoSteakHouseUSDCVault = await ethers.getContractAt( - metamorphoAbi, - addresses.mainnet.MorphoSteakhouseUSDCVault - ); - morphoGauntletPrimeUSDCVault = await ethers.getContractAt( - metamorphoAbi, - addresses.mainnet.MorphoGauntletPrimeUSDCVault - ); - morphoGauntletPrimeUSDTVault = await ethers.getContractAt( - metamorphoAbi, - addresses.mainnet.MorphoGauntletPrimeUSDTVault - ); + morphoOUSDv2Vault = await ethers.getContractAt( metamorphoAbi, addresses.mainnet.MorphoOUSDv2Vault ); - morphoToken = await ethers.getContractAt( - erc20Abi, - addresses.mainnet.MorphoToken - ); - legacyMorphoToken = await ethers.getContractAt( - erc20Abi, - addresses.mainnet.LegacyMorphoToken - ); - aura = await ethers.getContractAt(erc20Abi, addresses.mainnet.AURA); - bal = await ethers.getContractAt(erc20Abi, addresses.mainnet.BAL); - ogv = await ethers.getContractAt(erc20Abi, addresses.mainnet.OGV); ssv = await ethers.getContractAt(erc20Abi, addresses.mainnet.SSV); - crvMinter = await ethers.getContractAt( - crvMinterAbi, - addresses.mainnet.CRVMinter - ); - aaveAddressProvider = await ethers.getContractAt( - "ILendingPoolAddressesProvider", - addresses.mainnet.AAVE_ADDRESS_PROVIDER - ); - cvxBooster = await ethers.getContractAt( - "MockBooster", - addresses.mainnet.CVXBooster - ); - cvxRewardPool = await ethers.getContractAt( - "IRewardStaking", - addresses.mainnet.CVXRewardsPool - ); - - const makerSSRStrategyProxy = await ethers.getContract( - "MakerSSRStrategyProxy" - ); - makerSSRStrategy = await ethers.getContractAt( - "Generalized4626Strategy", - makerSSRStrategyProxy.address - ); - - const morphoCompoundStrategyProxy = await ethers.getContract( - "MorphoCompoundStrategyProxy" - ); - morphoCompoundStrategy = await ethers.getContractAt( - "MorphoCompoundStrategy", - morphoCompoundStrategyProxy.address - ); - - const morphoAaveStrategyProxy = await ethers.getContract( - "MorphoAaveStrategyProxy" - ); - morphoAaveStrategy = await ethers.getContractAt( - "MorphoAaveStrategy", - morphoAaveStrategyProxy.address - ); - - const oethMorphoAaveStrategyProxy = await ethers.getContract( - "OETHMorphoAaveStrategyProxy" - ); - oethMorphoAaveStrategy = await ethers.getContractAt( - "MorphoAaveStrategy", - oethMorphoAaveStrategyProxy.address - ); - - const balancerRethStrategyProxy = await ethers.getContract( - "OETHBalancerMetaPoolrEthStrategyProxy" - ); - balancerREthStrategy = await ethers.getContractAt( - "BalancerMetaPoolStrategy", - balancerRethStrategyProxy.address - ); - - const convexEthMetaStrategyProxy = await ethers.getContract( - "ConvexEthMetaStrategyProxy" - ); - convexEthMetaStrategy = await ethers.getContractAt( - "ConvexEthMetaStrategy", - convexEthMetaStrategyProxy.address - ); - - const oethDripperProxy = await ethers.getContract("OETHDripperProxy"); - oethDripper = await ethers.getContractAt( - "OETHDripper", - oethDripperProxy.address - ); - oethZapper = await ethers.getContract("OETHZapper"); - swapper = await ethers.getContract("Swapper1InchV5"); - - vaultValueChecker = await ethers.getContract("VaultValueChecker"); - oethVaultValueChecker = await ethers.getContract("OETHVaultValueChecker"); - poolBoosterCentralRegistry = await ethers.getContractAt( "PoolBoostCentralRegistry", ( @@ -954,86 +677,23 @@ const defaultFixture = deployments.createFixture(async () => { } else { usdt = await ethers.getContract("MockUSDT"); usds = await ethers.getContract("MockUSDS"); - tusd = await ethers.getContract("MockTUSD"); usdc = await ethers.getContract("MockUSDC"); weth = await ethers.getContractAt("MockWETH", addresses.mainnet.WETH); ogn = await ethers.getContract("MockOGN"); - ogv = await ethers.getContract("MockOGV"); - reth = await ethers.getContract("MockRETH"); - frxETH = await ethers.getContract("MockfrxETH"); - sfrxETH = await ethers.getContract("MocksfrxETH"); - // // Note: Not used anywhere in unit tests - sUSDS = undefined; - stETH = await ethers.getContract("MockstETH"); - nonStandardToken = await ethers.getContract("MockNonStandardToken"); ssv = await ethers.getContract("MockSSV"); - - cusdt = await ethers.getContract("MockCUSDT"); - cusdc = await ethers.getContract("MockCUSDC"); - comp = await ethers.getContract("MockCOMP"); - bal = await ethers.getContract("MockBAL"); - aura = await ethers.getContract("MockAura"); - - crv = await ethers.getContract("MockCRV"); - cvx = await ethers.getContract("MockCVX"); - crvMinter = await ethers.getContract("MockCRVMinter"); - threePool = await ethers.getContract("MockCurvePool"); - threePoolToken = await ethers.getContract("Mock3CRV"); - metapoolToken = await ethers.getContract("MockCurveMetapool"); - threePoolGauge = await ethers.getContract("MockCurveGauge"); - cvxBooster = await ethers.getContract("MockBooster"); - cvxRewardPool = await ethers.getContract("MockRewardPool"); depositContractUtils = await ethers.getContract("DepositContractUtils"); - - adai = await ethers.getContract("MockADAI"); - aaveToken = await ethers.getContract("MockAAVEToken"); - aave = await ethers.getContract("MockAave"); - // currently in test the mockAave is itself the address provider - aaveAddressProvider = await ethers.getContractAt( - "ILendingPoolAddressesProvider", - aave.address - ); - stkAave = await ethers.getContract("MockStkAave"); - aaveIncentivesController = await ethers.getContract( - "MockAaveIncentivesController" - ); - - chainlinkOracleFeedDAI = await ethers.getContract( - "MockChainlinkOracleFeedDAI" - ); - chainlinkOracleFeedUSDS = await ethers.getContract( - "MockChainlinkOracleFeedUSDS" - ); - chainlinkOracleFeedUSDT = await ethers.getContract( - "MockChainlinkOracleFeedUSDT" - ); - chainlinkOracleFeedUSDC = await ethers.getContract( - "MockChainlinkOracleFeedUSDC" - ); - chainlinkOracleFeedOGNETH = await ethers.getContract( - "MockChainlinkOracleFeedOGNETH" - ); - chainlinkOracleFeedETH = await ethers.getContract( - "MockChainlinkOracleFeedETH" - ); - - swapper = await ethers.getContract("MockSwapper"); - mockSwapper = await ethers.getContract("MockSwapper"); - swapper1Inch = await ethers.getContract("Swapper1InchV5"); - mock1InchSwapRouter = await ethers.getContract("Mock1InchSwapRouter"); } if (!isFork) { const sGovernor = await ethers.provider.getSigner(governorAddr); // Enable capital movement - await vaultAndTokenConracts.vault.connect(sGovernor).unpauseCapital(); + await vaultAndTokenContracts.vault.connect(sGovernor).unpauseCapital(); } const signers = await hre.ethers.getSigners(); let governor = signers[1]; let strategist = signers[0]; - const adjuster = signers[0]; let timelock; let oldTimelock; @@ -1061,8 +721,8 @@ const defaultFixture = deployments.createFixture(async () => { for (const user of [matt, josh]) { await usdc .connect(user) - .approve(vaultAndTokenConracts.vault.address, usdcUnits("100")); - await vaultAndTokenConracts.vault + .approve(vaultAndTokenContracts.vault.address, usdcUnits("100")); + await vaultAndTokenContracts.vault .connect(user) .mint(usdc.address, usdcUnits("100"), 0); @@ -1071,97 +731,39 @@ const defaultFixture = deployments.createFixture(async () => { await weth.connect(user).deposit({ value: oethUnits("10000") }); await weth .connect(user) - .approve(vaultAndTokenConracts.oethVault.address, oethUnits("100")); + .approve(vaultAndTokenContracts.oethVault.address, oethUnits("100")); } } return { - ...vaultAndTokenConracts, + ...vaultAndTokenContracts, // Accounts matt, josh, anna, governor, strategist, - adjuster, domen, daniel, franck, timelock, oldTimelock, // Contracts - vaultValueChecker, - harvester, dripper, - // Oracle - chainlinkOracleFeedDAI, - chainlinkOracleFeedUSDT, - chainlinkOracleFeedUSDC, - chainlinkOracleFeedUSDS, - chainlinkOracleFeedOGNETH, - chainlinkOracleFeedETH, - compoundStrategy, - oracleRouter, - oethOracleRouter, // Assets usdt, usds, - sUSDS, - tusd, usdc, ogn, ssv, weth, - ogv, - reth, - stETH, - nonStandardToken, - // cTokens - cusdc, - cusdt, - comp, - // aTokens, - adai, - ausdt, - ausdc, - // CompoundStrategy contract factory to deploy - CompoundStrategyFactory, - crv, - crvMinter, - threePool, - threePoolGauge, - threePoolToken, - metapoolToken, - morpho, - morphoLens, - convexStrategy, - makerSSRStrategy, - morphoCompoundStrategy, - morphoAaveStrategy, - cvx, - cvxBooster, - cvxRewardPool, depositContractUtils, - - aaveStrategy, - aaveToken, - aaveAddressProvider, - aaveIncentivesController, - aave, - stkAave, - // uniswapPairOUSD_USDT, - // liquidityRewardOUSD_USDT, wousd, - morphoSteakhouseUSDCStrategy, - morphoSteakHouseUSDCVault, - morphoGauntletPrimeUSDCStrategy, - morphoGauntletPrimeUSDCVault, - morphoGauntletPrimeUSDTStrategy, - morphoGauntletPrimeUSDTVault, morphoOUSDv2Strategy, morphoOUSDv2Vault, curvePoolBooster, simpleOETHHarvester, + oethHarvester: simpleOETHHarvester, oethFixedRateDripper, OUSDCurveAMO, curvePoolOusdUsdc, @@ -1171,27 +773,11 @@ const defaultFixture = deployments.createFixture(async () => { curveGaugeOETHWETH, // OETH - oethVaultValueChecker, - frxETH, - sfrxETH, nativeStakingSSVStrategy, nativeStakingFeeAccumulator, - balancerREthStrategy, - oethMorphoAaveStrategy, - convexEthMetaStrategy, - oethDripper, oethFixedRateDripperProxy, - oethHarvester, oethZapper, - swapper, - mockSwapper, - swapper1Inch, - mock1InchSwapRouter, - aura, - bal, - - morphoToken, - legacyMorphoToken, + poolBoosterCentralRegistry, poolBoosterMerklFactory, merklDistributor, @@ -1230,29 +816,17 @@ async function poolBoosterCodeUpdatedFixture() { } async function oethDefaultFixture() { - // TODO: Trim it down to only do OETH things const fixture = await defaultFixture(); - const { weth, reth, stETH, frxETH, sfrxETH } = fixture; + const { weth } = fixture; const { matt, josh, domen, daniel, franck, oethVault } = fixture; if (isFork) { for (const user of [matt, josh, domen, daniel, franck]) { - // Everyone gets free tokens - for (const token of [weth, reth, stETH, frxETH, sfrxETH]) { - await setERC20TokenBalance(user.address, token, "1000000", hre); - - // And vault can rug them all - await resetAllowance(token, user, oethVault.address); - } + await setERC20TokenBalance(user.address, weth, "1000000", hre); + await resetAllowance(weth, user, oethVault.address); } } else { - // Replace frxETHMinter - await replaceContractAt( - addresses.mainnet.FraxETHMinter, - await ethers.getContract("MockFrxETHMinter") - ); - // Fund WETH contract await hardhatSetBalance(weth.address, "999999999999999"); @@ -1261,107 +835,13 @@ async function oethDefaultFixture() { // Reset allowances for (const user of [matt, josh, domen, daniel, franck]) { - for (const asset of [weth, reth, stETH, frxETH, sfrxETH]) { - await resetAllowance(asset, user, oethVault.address); - } + await resetAllowance(weth, user, oethVault.address); } } return fixture; } -async function oethCollateralSwapFixture() { - const fixture = await oethDefaultFixture(); - - return fixture; - - // const { reth, stETH, matt, strategist, timelock, oethVault } = fixture; - - // const bufferBps = await oethVault.vaultBuffer(); - // const shouldChangeBuffer = bufferBps.lt(oethUnits("1")); - - // if (shouldChangeBuffer) { - // // If it's not 100% already, set it to 100% - // await oethVault.connect(strategist).setVaultBuffer( - // oethUnits("1") // 100% - // ); - // } - - // for (const token of [reth, stETH]) { - // await token - // .connect(matt) - // .approve( - // oethVault.address, - // parseEther("100000000000000000000000000000000000") - // ); - - // // Transfer some tokens to the Vault so they can be swapped out - // await token.connect(matt).transfer(oethVault.address, parseEther("200")); - // } - - // if (shouldChangeBuffer) { - // // Set it back - // await oethVault.connect(strategist).setVaultBuffer(bufferBps); - // } - - // // Withdraw all from strategies so we have assets to swap - // await oethVault.connect(timelock).withdrawAllFromStrategies(); - - // return fixture; -} - -async function ousdCollateralSwapFixture() { - const fixture = await defaultFixture(); - - const { usds, usdc, usdt, matt, strategist, timelock, vault } = fixture; - - const bufferBps = await vault.vaultBuffer(); - const shouldChangeBuffer = bufferBps.lt(ousdUnits("1")); - - if (shouldChangeBuffer) { - // If it's not 100% already, set it to 100% - await vault.connect(strategist).setVaultBuffer( - ousdUnits("1") // 100% - ); - } - - await usdt.connect(matt).approve(vault.address, 0); - for (const token of [usds, usdc, usdt]) { - await token - .connect(matt) - .approve(vault.address, await units("10000", token)); - - // Mint some tokens, so it ends up in Vault - await vault.connect(matt).mint(token.address, await units("500", token), 0); - } - - if (shouldChangeBuffer) { - // Set it back - await vault.connect(strategist).setVaultBuffer(bufferBps); - } - - // Withdraw all from strategies so we have assets to swap - await vault.connect(timelock).withdrawAllFromStrategies(); - - return fixture; -} - -async function oeth1InchSwapperFixture() { - const fixture = await oethDefaultFixture(); - const { mock1InchSwapRouter } = fixture; - - const swapPlatformAddr = "0x1111111254EEB25477B68fb85Ed929f73A960582"; - await replaceContractAt(swapPlatformAddr, mock1InchSwapRouter); - - const stubbedRouterContract = await hre.ethers.getContractAt( - "Mock1InchSwapRouter", - swapPlatformAddr - ); - fixture.mock1InchSwapRouter = stubbedRouterContract; - - return fixture; -} - /** * Configure the MockVault contract by initializing it and setting supported * assets and then upgrade the Vault implementation via VaultProxy. @@ -1439,6 +919,11 @@ async function claimRewardsModuleFixture() { await cSafe.connect(safeSigner).enableModule(claimRewardsModule.address); } + fixture.morphoToken = await ethers.getContractAt( + "MintableERC20", + addresses.mainnet.MorphoToken + ); + return { ...fixture, claimRewardsModule, @@ -1447,239 +932,75 @@ async function claimRewardsModuleFixture() { } /** - * Configure a Vault with only the Compound strategy. - */ -async function compoundVaultFixture() { - const fixture = await defaultFixture(); - - const { governorAddr } = await getNamedAccounts(); - const sGovernor = await ethers.provider.getSigner(governorAddr); - - const assetAddresses = await getAssetAddresses(deployments); - - // Approve in Vault - await fixture.vault - .connect(sGovernor) - .approveStrategy(fixture.compoundStrategy.address); - - await fixture.harvester - .connect(sGovernor) - .setSupportedStrategy(fixture.compoundStrategy.address, true); - - // Add USDT - await fixture.compoundStrategy - .connect(sGovernor) - .setPTokenAddress(assetAddresses.USDT, assetAddresses.cUSDT); - // Add USDC - await fixture.compoundStrategy - .connect(sGovernor) - .setPTokenAddress(assetAddresses.USDC, assetAddresses.cUSDC); - - return fixture; -} - -/** - * Configure a Vault with only the Convex strategy. - */ -async function convexVaultFixture() { - const fixture = await defaultFixture(); - - const { governorAddr } = await getNamedAccounts(); - const sGovernor = await ethers.provider.getSigner(governorAddr); - // Add Convex - await fixture.vault - .connect(sGovernor) - .approveStrategy(fixture.convexStrategy.address); - - await fixture.harvester - .connect(sGovernor) - .setSupportedStrategy(fixture.convexStrategy.address, true); - - await fixture.vault - .connect(sGovernor) - .setDefaultStrategy(fixture.convexStrategy.address); - return fixture; -} - -/** - * Configure a Vault with the balancerREthStrategy - */ -async function balancerREthFixture(config = { defaultStrategy: true }) { - const fixture = await defaultFixture(); - await hotDeployOption(fixture, "balancerREthFixture", { - isOethFixture: true, - }); - - const { oethVault, timelock, weth, reth, balancerREthStrategy, josh } = - fixture; - - if (config.defaultStrategy) { - await oethVault - .connect(timelock) - .setDefaultStrategy(reth.address, balancerREthStrategy.address); - await oethVault - .connect(timelock) - .setDefaultStrategy(weth.address, balancerREthStrategy.address); - } - - fixture.rEthBPT = await ethers.getContractAt( - "IERC20Metadata", - addresses.mainnet.rETH_WETH_BPT, - josh - ); - fixture.balancerREthPID = balancer_rETH_WETH_PID; - - fixture.auraPool = await ethers.getContractAt( - "IERC4626", - addresses.mainnet.rETH_WETH_AuraRewards - ); - - fixture.balancerVault = await ethers.getContractAt( - "IBalancerVault", - addresses.mainnet.balancerVault, - josh - ); - - // completely peg the rETH price - // await setChainlinkOraclePrice(addresses.mainnet.rETH, await reth.getExchangeRate()); - - await setERC20TokenBalance(josh.address, reth, "1000000", hre); - await hardhatSetBalance(josh.address, "1000000"); - - return fixture; -} - -/** - * Configure a Vault with only the Meta strategy. - */ -async function convexMetaVaultFixture() { - throw new Error("Fix fixtures"); - // const fixture = await defaultFixture(); - - // if (isFork) { - // const { josh, matt, anna, domen, daniel, franck, ousd } = fixture; - - // // const curveFactoryAddress = '0xB9fC157394Af804a3578134A6585C0dc9cc990d4' - - // const threepoolLP = await ethers.getContractAt( - // threepoolLPAbi, - // addresses.mainnet.ThreePoolToken - // ); - // const ousdMetaPool = await ethers.getContractAt( - // ousdMetapoolAbi, - // addresses.mainnet.CurveOUSDMetaPool - // ); - // const threepoolSwap = await ethers.getContractAt( - // threepoolSwapAbi, - // addresses.mainnet.ThreePool - // ); - // // const curveFactory = await ethers.getContractAt(curveFactoryAbi, curveFactoryAddress) - - // const balances = await ousdMetaPool.get_balances(); - // log(`Metapool balance 0: ${formatUnits(balances[0])}`); - // log(`Metapool balance 1: ${formatUnits(balances[1])}`); - - // // Domen is loaded with 3CRV - // await hardhatSetBalance(domen.address, "1000000"); - // await setERC20TokenBalance(domen.address, threepoolLP, "1000000", hre); - - // for (const user of [josh, matt, anna, domen, daniel, franck]) { - // // Approve OUSD MetaPool contract to move funds - // await resetAllowance(threepoolLP, user, ousdMetaPool.address); - // await resetAllowance(ousd, user, ousdMetaPool.address); - // } - - // fixture.ousdMetaPool = ousdMetaPool; - // fixture.threePoolToken = threepoolLP; - // fixture.threepoolSwap = threepoolSwap; - // } else { - // // Migrations should do these on fork - // const { governorAddr } = await getNamedAccounts(); - // const sGovernor = await ethers.provider.getSigner(governorAddr); - - // // Add Convex Meta strategy - // await fixture.vault - // .connect(sGovernor) - // .approveStrategy(fixture.OUSDmetaStrategy.address); - - // // set meta strategy on vault so meta strategy is allowed to mint OUSD - // await fixture.vault - // .connect(sGovernor) - // .setOusdMetaStrategy(fixture.OUSDmetaStrategy.address); - - // // set OUSD mint threshold to 50 million - // await fixture.vault - // .connect(sGovernor) - // .setNetOusdMintForStrategyThreshold(parseUnits("50", 24)); - - // await fixture.harvester - // .connect(sGovernor) - // .setSupportedStrategy(fixture.OUSDmetaStrategy.address, true); - - // await fixture.vault - // .connect(sGovernor) - // .setAssetDefaultStrategy( - // fixture.usdt.address, - // fixture.OUSDmetaStrategy.address - // ); - - // await fixture.vault - // .connect(sGovernor) - // .setAssetDefaultStrategy( - // fixture.usdc.address, - // fixture.OUSDmetaStrategy.address - // ); - // } - - // return fixture; -} - -/** - * Configure a Vault with default DAI strategy to the Maker DSR strategy. + * Configure a Vault with default USDC strategy to Yearn's Morpho OUSD v2 Vault. */ - -async function makerSSRFixture( +async function morphoOUSDv2Fixture( config = { - usdsMintAmount: 0, + usdcMintAmount: 0, depositToStrategy: false, } ) { const fixture = await defaultFixture(); if (isFork) { - const { usds, josh, makerSSRStrategy, strategist, vault } = fixture; + const { usdc, josh, morphoOUSDv2Strategy, strategist, vault } = fixture; + + // TODO remove once Yearn has done this on mainnet + // // Whitelist the strategy + // const gateOwner = await impersonateAndFund( + // "0x50B75d586929Ab2F75dC15f07E1B921b7C4Ba8fA" + // ); + // const gate = await ethers.getContractAt( + // ["function setIsWhitelisted(address,bool) external"], + // "0x6704aB7aF6787930c60DFa422104E899E823e657" + // ); + // await gate + // .connect(gateOwner) + // .setIsWhitelisted(morphoOUSDv2Strategy.address, true); // Impersonate the OUSD Vault fixture.vaultSigner = await impersonateAndFund(vault.address); - // mint some OUSD using USDS if configured - if (config?.usdsMintAmount > 0) { - const usdsMintAmount = parseUnits(config.usdsMintAmount.toString()); + const morphoToken = await ethers.getContractAt( + "MintableERC20", + addresses.mainnet.MorphoToken + ); + fixture.morphoToken = morphoToken; + + // mint some OUSD using USDC if configured + if (config?.usdcMintAmount > 0) { + const usdcMintAmount = parseUnits(config.usdcMintAmount.toString(), 6); await vault.connect(josh).rebase(); await vault.connect(josh).allocate(); - // Approve the Vault to transfer USDS - await usds.connect(josh).approve(vault.address, usdsMintAmount); + // Approve the Vault to transfer USDC + await usdc.connect(josh).approve(vault.address, usdcMintAmount); - // Mint OUSD with USDS + // Mint OUSD with USDC // This will sit in the vault, not the strategy - await vault.connect(josh).mint(usds.address, usdsMintAmount, 0); + await vault.connect(josh).mint(usdc.address, usdcMintAmount, 0); - // Add DAI to the Maker DSR Strategy + // Add USDC to the strategy if (config?.depositToStrategy) { - // The strategist deposits the WETH to the AMO strategy + // Calculate how much can be deposited to the strategy + // given outstanding withdrawal requests. + const usdcBalance = await usdc.balanceOf(vault.address); + const queue = await vault.withdrawalQueueMetadata(); + const available = usdcBalance.add(queue.claimed).sub(queue.queued); + + // The strategist deposits the USDC to the strategy await vault .connect(strategist) .depositToStrategy( - makerSSRStrategy.address, - [usds.address], - [usdsMintAmount] + morphoOUSDv2Strategy.address, + [usdc.address], + [available] ); } } } else { throw new Error( - "Maker SSR strategy only supported in forked test environment" + "Yearn's Morpho OUSD v2 strategy only supported in forked test environment" ); } @@ -1687,329 +1008,7 @@ async function makerSSRFixture( } /** - * Configure a Vault with default USDC strategy to the Morpho Steakhouse USDC Vault. - */ -async function morphoSteakhouseUSDCFixture( - config = { - usdcMintAmount: 0, - depositToStrategy: false, - } -) { - const fixture = await defaultFixture(); - - if (isFork) { - const { usdc, josh, morphoSteakhouseUSDCStrategy, strategist, vault } = - fixture; - - // Impersonate the OUSD Vault - fixture.vaultSigner = await impersonateAndFund(vault.address); - - // mint some OUSD using USDC if configured - if (config?.usdcMintAmount > 0) { - const usdcMintAmount = parseUnits(config.usdcMintAmount.toString(), 6); - await vault.connect(josh).rebase(); - await vault.connect(josh).allocate(); - - // Approve the Vault to transfer USDC - await usdc.connect(josh).approve(vault.address, usdcMintAmount); - - // Mint OUSD with USDC - // This will sit in the vault, not the strategy - await vault.connect(josh).mint(usdc.address, usdcMintAmount, 0); - - // Add USDC to the strategy - if (config?.depositToStrategy) { - // The strategist deposits the USDC to the strategy - await vault - .connect(strategist) - .depositToStrategy( - morphoSteakhouseUSDCStrategy.address, - [usdc.address], - [usdcMintAmount] - ); - } - } - } else { - throw new Error( - "Morpho Steakhouse USDC strategy only supported in forked test environment" - ); - } - - return fixture; -} - -/** - * Configure a Vault with default USDC strategy to the Morpho Gauntlet Prime USDC Vault. - */ -async function morphoGauntletPrimeUSDCFixture( - config = { - usdcMintAmount: 0, - depositToStrategy: false, - } -) { - const fixture = await defaultFixture(); - - if (isFork) { - const { usdc, josh, morphoGauntletPrimeUSDCStrategy, strategist, vault } = - fixture; - - // Impersonate the OUSD Vault - fixture.vaultSigner = await impersonateAndFund(vault.address); - - // mint some OUSD using USDC if configured - if (config?.usdcMintAmount > 0) { - const usdcMintAmount = parseUnits(config.usdcMintAmount.toString(), 6); - await vault.connect(josh).rebase(); - await vault.connect(josh).allocate(); - - // Approve the Vault to transfer USDC - await usdc.connect(josh).approve(vault.address, usdcMintAmount); - - // Mint OUSD with USDC - // This will sit in the vault, not the strategy - await vault.connect(josh).mint(usdc.address, usdcMintAmount, 0); - - // Add USDC to the strategy - if (config?.depositToStrategy) { - // The strategist deposits the USDC to the strategy - await vault - .connect(strategist) - .depositToStrategy( - morphoGauntletPrimeUSDCStrategy.address, - [usdc.address], - [usdcMintAmount] - ); - } - } - } else { - throw new Error( - "Morpho Gauntlet Prime USDC strategy only supported in forked test environment" - ); - } - - return fixture; -} - -/** - * Configure a Vault with default USDT strategy to the Morpho Gauntlet Prime USDT Vault. - */ -async function morphoGauntletPrimeUSDTFixture( - config = { - usdtMintAmount: 0, - depositToStrategy: false, - } -) { - const fixture = await defaultFixture(); - - if (isFork) { - const { usdt, josh, morphoGauntletPrimeUSDTStrategy, strategist, vault } = - fixture; - - // Impersonate the OUSD Vault - fixture.vaultSigner = await impersonateAndFund(vault.address); - - // mint some OUSD using USDT if configured - if (config?.usdtMintAmount > 0) { - const usdtMintAmount = parseUnits(config.usdtMintAmount.toString(), 6); - await vault.connect(josh).rebase(); - await vault.connect(josh).allocate(); - - // Approve the Vault to transfer USDT - await usdt.connect(josh).approve(vault.address, 0); - await usdt.connect(josh).approve(vault.address, usdtMintAmount); - - // Mint OUSD with USDT - // This will sit in the vault, not the strategy - await vault.connect(josh).mint(usdt.address, usdtMintAmount, 0); - - // Add USDT to the strategy - if (config?.depositToStrategy) { - // The strategist deposits the USDT to the strategy - await vault - .connect(strategist) - .depositToStrategy( - morphoGauntletPrimeUSDTStrategy.address, - [usdt.address], - [usdtMintAmount] - ); - } - } - } else { - throw new Error( - "Morpho Gauntlet Prime USDT strategy only supported in forked test environment" - ); - } - - return fixture; -} - -/** - * Configure a Vault with default USDC strategy to Yearn's Morpho OUSD v2 Vault. - */ -async function morphoOUSDv2Fixture( - config = { - usdcMintAmount: 0, - depositToStrategy: false, - } -) { - const fixture = await defaultFixture(); - - if (isFork) { - const { usdc, josh, morphoOUSDv2Strategy, strategist, vault } = fixture; - - // TODO remove once Yearn has done this on mainnet - // // Whitelist the strategy - // const gateOwner = await impersonateAndFund( - // "0x50B75d586929Ab2F75dC15f07E1B921b7C4Ba8fA" - // ); - // const gate = await ethers.getContractAt( - // ["function setIsWhitelisted(address,bool) external"], - // "0x6704aB7aF6787930c60DFa422104E899E823e657" - // ); - // await gate - // .connect(gateOwner) - // .setIsWhitelisted(morphoOUSDv2Strategy.address, true); - - // Impersonate the OUSD Vault - fixture.vaultSigner = await impersonateAndFund(vault.address); - - fixture.buyBackSigner = await impersonateAndFund( - addresses.multichainBuybackOperator - ); - - // mint some OUSD using USDC if configured - if (config?.usdcMintAmount > 0) { - const usdcMintAmount = parseUnits(config.usdcMintAmount.toString(), 6); - await vault.connect(josh).rebase(); - await vault.connect(josh).allocate(); - - // Approve the Vault to transfer USDC - await usdc.connect(josh).approve(vault.address, usdcMintAmount); - - // Mint OUSD with USDC - // This will sit in the vault, not the strategy - await vault.connect(josh).mint(usdc.address, usdcMintAmount, 0); - - // Add USDC to the strategy - if (config?.depositToStrategy) { - // The strategist deposits the USDC to the strategy - await vault - .connect(strategist) - .depositToStrategy( - morphoOUSDv2Strategy.address, - [usdc.address], - [usdcMintAmount] - ); - } - } - } else { - throw new Error( - "Yearn's Morpho OUSD v2 strategy only supported in forked test environment" - ); - } - - return fixture; -} - -/** - * Configure a Vault with only the Morpho strategy. - */ -async function morphoCompoundFixture() { - const fixture = await defaultFixture(); - await hotDeployOption(fixture, "morphoCompoundFixture"); - - const { timelock } = fixture; - - if (isFork) { - await fixture.vault - .connect(timelock) - .setDefaultStrategy(fixture.morphoCompoundStrategy.address); - - await fixture.vault - .connect(timelock) - .setDefaultStrategy(fixture.morphoCompoundStrategy.address); - - await fixture.vault - .connect(timelock) - .setDefaultStrategy(fixture.morphoCompoundStrategy.address); - } else { - throw new Error( - "Morpho strategy only supported in forked test environment" - ); - } - - return fixture; -} - -/** - * Configure a Vault with only the Aave strategy for USDT. - */ -async function aaveFixture() { - const fixture = await defaultFixture(); - - const { timelock } = fixture; - - if (isFork) { - await fixture.vault - .connect(timelock) - .setDefaultStrategy(fixture.aaveStrategy.address); - } else { - throw new Error( - "Aave strategy supported for USDT in forked test environment" - ); - } - - return fixture; -} - -/** - * Configure a Vault with only the Morpho strategy. - */ -async function morphoAaveFixture() { - const fixture = await defaultFixture(); - - const { timelock } = fixture; - - if (isFork) { - // The supply of DAI and USDT has been paused for Morpho Aave V2 so no default strategy - await fixture.vault.connect(timelock).setDefaultStrategy(addresses.zero); - await fixture.vault.connect(timelock).setDefaultStrategy(addresses.zero); - - await fixture.vault - .connect(timelock) - .setDefaultStrategy(fixture.morphoAaveStrategy.address); - } else { - throw new Error( - "Morpho strategy only supported in forked test environment" - ); - } - - return fixture; -} - -/** - * Configure a Vault with only the Morpho strategy. - */ -async function oethMorphoAaveFixture() { - const fixture = await oethDefaultFixture(); - - if (isFork) { - const { oethVault, timelock, oethMorphoAaveStrategy } = fixture; - - await oethVault - .connect(timelock) - .setDefaultStrategy(oethMorphoAaveStrategy.address); - } else { - throw new Error( - "Morpho strategy only supported in forked test environment" - ); - } - - return fixture; -} - -/** - * NativeStakingSSVStrategy fixture + * NativeStakingSSVStrategy fixture */ async function nativeStakingSSVStrategyFixture() { const fixture = await oethDefaultFixture(); @@ -2064,6 +1063,11 @@ async function nativeStakingSSVStrategyFixture() { .connect(sGovernor) .setRegistrator(governorAddr); + // Set harvester address on the strategy + await nativeStakingSSVStrategy + .connect(sGovernor) + .setHarvesterAddress(fixture.simpleOETHHarvester.address); + fixture.validatorRegistrator = sGovernor; } @@ -2147,7 +1151,7 @@ async function compoundingStakingSSVStrategyFixture() { await compoundingStakingSSVStrategy .connect(sGovernor) - .setHarvesterAddress(fixture.oethHarvester.address); + .setHarvesterAddress(fixture.simpleOETHHarvester.address); fixture.validatorRegistrator = sRegistrator; } @@ -2174,88 +1178,6 @@ async function compoundingStakingSSVStrategyMerkleProofsMockedFixture() { return fixture; } -/** - * Generalized strategy fixture that works only in forked environment - * - * @param metapoolAddress -> the address of the metapool - * @param rewardPoolAddress -> address of the reward staker contract - * @param metastrategyProxyName -> name of the generalizedMetastrategy proxy contract - */ -async function convexGeneralizedMetaForkedFixture( - config = { - metapoolAddress: addresses.mainnet.CurveOUSDMetaPool, - rewardPoolAddress: addresses.mainnet.CVXRewardsPool, - metastrategyProxyName: addresses.mainnet.ConvexOUSDAMOStrategy, - lpTokenAddress: addresses.mainnet.ThreePoolToken, - } -) { - const { - metapoolAddress, - rewardPoolAddress, - metastrategyProxyName, - lpTokenAddress, - } = config; - const fixture = await defaultFixture(); - - const { timelockAddr } = await getNamedAccounts(); - const sGovernor = await ethers.provider.getSigner(timelockAddr); - const { josh, matt, anna, domen, daniel, franck } = fixture; - - const threepoolLP = await ethers.getContractAt( - threepoolLPAbi, - addresses.mainnet.ThreePoolToken - ); - const metapool = await ethers.getContractAt(ousdMetapoolAbi, metapoolAddress); - - const primaryCoin = await ethers.getContractAt( - erc20Abi, - await metapool.coins(0) - ); - - const threepoolSwap = await ethers.getContractAt( - threepoolSwapAbi, - addresses.mainnet.ThreePool - ); - - const lpToken = await ethers.getContractAt(erc20Abi, lpTokenAddress); - - for (const user of [josh, matt, anna, domen, daniel, franck]) { - // Approve Metapool contract to move funds - await resetAllowance(threepoolLP, user, metapoolAddress); - await resetAllowance(primaryCoin, user, metapoolAddress); - } - - await impersonateAndFund(domen.address, "1000000"); - await setERC20TokenBalance(domen.address, threepoolLP, "1000000", hre); - - fixture.metapoolCoin = primaryCoin; - fixture.metapool = metapool; - fixture.metapoolLpToken = lpToken; - fixture.threePoolToken = threepoolLP; - fixture.threepoolSwap = threepoolSwap; - - fixture.metaStrategyProxy = await ethers.getContract(metastrategyProxyName); - fixture.metaStrategy = await ethers.getContractAt( - "ConvexGeneralizedMetaStrategy", - fixture.metaStrategyProxy.address - ); - - fixture.rewardPool = await ethers.getContractAt( - "IRewardStaking", - rewardPoolAddress - ); - - await fixture.vault - .connect(sGovernor) - .setDefaultStrategy(fixture.metaStrategy.address); - - await fixture.vault - .connect(sGovernor) - .setDefaultStrategy(fixture.metaStrategy.address); - - return fixture; -} - async function nodeSnapshot() { return await hre.network.provider.request({ method: "evm_snapshot", @@ -2280,257 +1202,6 @@ async function resetAllowance( await tokenContract.connect(signer).approve(toAddress, allowance); } -/** - * Configure a Vault with only the OETH/(W)ETH Curve Metastrategy. - */ -async function convexOETHMetaVaultFixture( - config = { - wethMintAmount: 0, - depositToStrategy: false, - poolAddEthAmount: 0, - poolAddOethAmount: 0, - balancePool: false, - } -) { - const fixture = await oethDefaultFixture(); - await hotDeployOption(fixture, "convexOETHMetaVaultFixture", { - isOethFixture: true, - }); - - const { - convexEthMetaStrategy, - oeth, - oethVault, - josh, - strategist, - timelock, - weth, - crv, - } = fixture; - - await impersonateAndFund(josh.address); - await setERC20TokenBalance(josh.address, weth, "10000000", hre); - await setERC20TokenBalance(josh.address, crv, "10000000", hre); - - // Update the strategy threshold to 500k ETH - await oethVault - .connect(timelock) - .setNetOusdMintForStrategyThreshold(parseUnits("500", 21)); - - await oethVault.connect(timelock).setDefaultStrategy(addresses.zero); - - // Impersonate the OETH Vault - fixture.oethVaultSigner = await impersonateAndFund(oethVault.address); - // Impersonate the Curve gauge that holds all the LP tokens - fixture.oethGaugeSigner = await impersonateAndFund( - addresses.mainnet.CurveOETHGauge - ); - - // Convex pool that records the deposited balances - fixture.cvxRewardPool = await ethers.getContractAt( - "IRewardStaking", - addresses.mainnet.CVXETHRewardsPool - ); - - fixture.oethMetaPool = await ethers.getContractAt( - oethMetapoolAbi, - addresses.mainnet.CurveOETHMetaPool - ); - - // mint some OETH using WETH is configured - if (config?.wethMintAmount > 0) { - const wethAmount = parseUnits(config.wethMintAmount.toString()); - await oethVault.connect(josh).rebase(); - await oethVault.connect(josh).allocate(); - - // Calculate how much to mint based on the WETH in the vault, - // the withdrawal queue, and the WETH to be sent to the strategy - const wethBalance = await weth.balanceOf(oethVault.address); - const queue = await oethVault.withdrawalQueueMetadata(); - const available = wethBalance.add(queue.claimed).sub(queue.queued); - const mintAmount = wethAmount.sub(available); - - if (mintAmount.gt(0)) { - // Approve the Vault to transfer WETH - await weth.connect(josh).approve(oethVault.address, mintAmount); - - // Mint OETH with WETH - // This will sit in the vault, not the strategy - await oethVault.connect(josh).mint(weth.address, mintAmount, 0); - } - - // Add ETH to the Metapool - if (config?.depositToStrategy) { - // The strategist deposits the WETH to the AMO strategy - await oethVault - .connect(strategist) - .depositToStrategy( - convexEthMetaStrategy.address, - [weth.address], - [wethAmount] - ); - } - } - - if (config?.balancePool) { - const ethBalance = await fixture.oethMetaPool.balances(0); - const oethBalance = await fixture.oethMetaPool.balances(1); - - const diff = parseInt( - ethBalance.sub(oethBalance).div(oethUnits("1")).toString() - ); - - if (diff > 0) { - config.poolAddOethAmount = (config.poolAddOethAmount || 0) + diff; - } else if (diff < 0) { - config.poolAddEthAmount = (config.poolAddEthAmount || 0) - diff; - } - } - - // Add ETH to the Metapool - if (config?.poolAddEthAmount > 0) { - // Fund Josh with ETH plus some extra for gas fees - const fundAmount = config.poolAddEthAmount + 1; - await hardhatSetBalance(josh.address, fundAmount.toString()); - - const ethAmount = parseUnits(config.poolAddEthAmount.toString(), 18); - // prettier-ignore - await fixture.oethMetaPool - .connect(josh)["add_liquidity(uint256[2],uint256)"]([ethAmount, 0], 0, { - value: ethAmount, - }); - } - - const { oethWhaleAddress } = addresses.mainnet; - fixture.oethWhale = await impersonateAndFund(oethWhaleAddress); - - // Add OETH to the Metapool - if (config?.poolAddOethAmount > 0) { - const poolAddOethAmountUnits = parseUnits( - config.poolAddOethAmount.toString() - ); - - const { oethWhale } = fixture; - - // Load with WETH - await setERC20TokenBalance( - oethWhaleAddress, - weth, - (config.poolAddOethAmount * 2).toString() - ); - - // Approve the Vault to transfer WETH - await weth - .connect(oethWhale) - .approve(oethVault.address, poolAddOethAmountUnits); - - // Mint OETH with WETH - await oethVault - .connect(oethWhale) - .mint(weth.address, poolAddOethAmountUnits, 0); - - const oethAmount = await oeth.balanceOf(oethWhaleAddress); - log(`OETH whale balance : ${formatUnits(oethAmount)}`); - log(`OETH to add to Metapool: ${formatUnits(poolAddOethAmountUnits)}`); - - await oeth - .connect(fixture.oethWhale) - .approve(fixture.oethMetaPool.address, poolAddOethAmountUnits); - - // prettier-ignore - await fixture.oethMetaPool - .connect(fixture.oethWhale)["add_liquidity(uint256[2],uint256)"]([0, poolAddOethAmountUnits], 0); - } - - return fixture; -} - -/** - * Configure a compound fixture with a false vault for testing - */ -async function compoundFixture() { - throw new Error("Update fixture to remove usage of DAI"); - // const fixture = await defaultFixture(); - - // const assetAddresses = await getAssetAddresses(deployments); - // const { deploy } = deployments; - // const { governorAddr } = await getNamedAccounts(); - // const sGovernor = await ethers.provider.getSigner(governorAddr); - - // await deploy("StandaloneCompound", { - // from: governorAddr, - // contract: "CompoundStrategy", - // args: [[addresses.dead, fixture.vault.address]], - // }); - - // fixture.cStandalone = await ethers.getContract("StandaloneCompound"); - - // // Set governor as vault - // await fixture.cStandalone - // .connect(sGovernor) - // .initialize( - // [assetAddresses.COMP], - // [assetAddresses.DAI, assetAddresses.USDC], - // [assetAddresses.cDAI, assetAddresses.cUSDC] - // ); - - // await fixture.cStandalone - // .connect(sGovernor) - // .setHarvesterAddress(fixture.harvester.address); - - // // impersonate the vault and strategy - // fixture.vaultSigner = await impersonateAndFund(fixture.vault.address); - // fixture.strategySigner = await impersonateAndFund( - // fixture.cStandalone.address - // ); - - // await fixture.usdc.transfer( - // await fixture.matt.getAddress(), - // parseUnits("1000", 6) - // ); - - // return fixture; -} - -/** - * Configure a hacked Vault - */ -async function hackedVaultFixture() { - const fixture = await defaultFixture(); - - const assetAddresses = await getAssetAddresses(deployments); - const { deploy } = deployments; - const { vault, oracleRouter } = fixture; - const { governorAddr } = await getNamedAccounts(); - const sGovernor = await ethers.provider.getSigner(governorAddr); - const oracleAddresses = await getOracleAddresses(hre.deployments); - - await deploy("MockEvilDAI", { - from: governorAddr, - args: [vault.address, assetAddresses.USDS], - }); - - const evilDAI = await ethers.getContract("MockEvilDAI"); - /* Mock oracle feeds report 0 for updatedAt data point. Set - * maxStaleness to 100 years from epoch to make the Oracle - * feeds valid - */ - const maxStaleness = 24 * 60 * 60 * 365 * 100; - - await oracleRouter.setFeed( - evilDAI.address, - oracleAddresses.chainlink.DAI_USD, - maxStaleness - ); - await oracleRouter.cacheDecimals(evilDAI.address); - - await fixture.vault.connect(sGovernor).supportAsset(evilDAI.address, 0); - - fixture.evilDAI = evilDAI; - - return fixture; -} - /** * Instant rebase vault, for testing systems external to the vault */ @@ -2613,6 +1284,14 @@ async function crossChainFixtureUnit() { // Impersonate the OUSD Vault fixture.vaultSigner = await impersonateAndFund(vault.address); + // Fund extra USDC for cross-chain tests that require large mints + if (!isFork) { + const usdc = await ethers.getContract("MockUSDC"); + for (const user of [fixture.josh, fixture.matt]) { + await usdc.connect(user).mint(usdcUnits("100000")); + } + } + return { ...fixture, crossChainMasterStrategy: cCrossChainMasterStrategy, @@ -2670,140 +1349,6 @@ async function rebornFixture() { return fixture; } -async function buybackFixture() { - const fixture = await defaultFixture(); - - const { ousd, oeth, oethVault, vault, weth, usdc, josh, governor, timelock } = - fixture; - - const ousdBuybackProxy = await ethers.getContract("BuybackProxy"); - const ousdBuyback = await ethers.getContractAt( - "OUSDBuyback", - ousdBuybackProxy.address - ); - - const oethBuybackProxy = await ethers.getContract("OETHBuybackProxy"); - const oethBuyback = await ethers.getContractAt( - "OETHBuyback", - oethBuybackProxy.address - ); - - let armBuyback; - if (isFork) { - const armBuybackProxy = await ethers.getContract("ARMBuybackProxy"); - armBuyback = await ethers.getContractAt( - "ARMBuyback", - armBuybackProxy.address - ); - fixture.armBuyback = armBuyback; - } - - fixture.ousdBuyback = ousdBuyback; - fixture.oethBuyback = oethBuyback; - - const rewardsSourceAddress = await ousdBuyback.connect(josh).rewardsSource(); - fixture.rewardsSource = await ethers.getContractAt([], rewardsSourceAddress); - - if (isFork) { - fixture.cvxLocker = await ethers.getContractAt( - "ICVXLocker", - addresses.mainnet.CVXLocker - ); - fixture.uniswapRouter = await ethers.getContractAt( - "IUniswapUniversalRouter", - addresses.mainnet.uniswapUniversalRouter - ); - - // Load with funds to test swaps - await setERC20TokenBalance(josh.address, weth, "10000"); - await setERC20TokenBalance(josh.address, usdc, "10000"); - await weth.connect(josh).approve(oethVault.address, oethUnits("10000")); - await usdc.connect(josh).approve(vault.address, usdcUnits("10000")); - - // Mint & transfer oToken - await oethVault.connect(josh).mint(weth.address, oethUnits("1.23"), "0"); - await oeth.connect(josh).transfer(oethBuyback.address, oethUnits("1.1")); - - await vault.connect(josh).mint(usdc.address, usdcUnits("1231"), "0"); - await ousd.connect(josh).transfer(ousdBuyback.address, oethUnits("1100")); - await setERC20TokenBalance(armBuyback.address, weth, "100"); - - // Compute splits - await oethBuyback.connect(timelock).updateBuybackSplits(); - await ousdBuyback.connect(timelock).updateBuybackSplits(); - await armBuyback.connect(timelock).updateBuybackSplits(); - } else { - fixture.mockSwapper = await ethers.getContract("MockSwapper"); - fixture.cvxLocker = await ethers.getContract("MockCVXLocker"); - - // Mint some OUSD - await usdc.connect(josh).mint(usdcUnits("3000")); - await usdc.connect(josh).approve(vault.address, usdcUnits("3000")); - await vault.connect(josh).mint(usdc.address, usdcUnits("3000"), "0"); - - // Mint some OETH - await weth.connect(josh).mint(oethUnits("3")); - await weth.connect(josh).approve(oethVault.address, oethUnits("3")); - await oethVault.connect(josh).mint(weth.address, oethUnits("3"), "0"); - - // Transfer those to the buyback contract - await oeth.connect(josh).transfer(oethBuyback.address, oethUnits("3")); - await ousd.connect(josh).transfer(ousdBuyback.address, ousdUnits("3000")); - //await weth.connect(josh).transfer(armBuyback.address, oethUnits("3")); - - // Compute splits - await oethBuyback.connect(governor).updateBuybackSplits(); - await ousdBuyback.connect(governor).updateBuybackSplits(); - //await armBuyback.connect(governor).updateBuybackSplits(); - } - - return fixture; -} - -async function harvesterFixture() { - let fixture; - - if (isFork) { - fixture = await defaultFixture(); - } else { - fixture = await compoundVaultFixture(); - - const { - vault, - governor, - harvester, - aaveStrategy, - comp, - aaveToken, - strategist, - compoundStrategy, - } = fixture; - - // Add Aave which only supports USDC - await vault.connect(governor).approveStrategy(aaveStrategy.address); - - await harvester - .connect(governor) - .setSupportedStrategy(aaveStrategy.address, true); - - // Add direct allocation of USDC to Aave - await vault.connect(governor).setDefaultStrategy(aaveStrategy.address); - - // Let strategies hold some reward tokens - await comp - .connect(strategist) - .mintTo(compoundStrategy.address, ousdUnits("120")); - await aaveToken - .connect(strategist) - .mintTo(aaveStrategy.address, ousdUnits("150")); - - fixture.uniswapRouter = await ethers.getContract("MockUniswapRouter"); - fixture.balancerVault = await ethers.getContract("MockBalancerVault"); - } - - return fixture; -} - async function woethCcipZapperFixture() { const fixture = await defaultFixture(); @@ -2956,9 +1501,8 @@ async function enableExecutionLayerGeneralPurposeRequests() { async function crossChainFixture() { const fixture = await defaultFixture(); - const crossChainStrategyProxyAddress = await getCreate2ProxyAddress( - "CrossChainStrategyProxy" - ); + const crossChainStrategyProxyAddress = + addresses.mainnet.CrossChainMasterStrategy; const cCrossChainMasterStrategy = await ethers.getContractAt( "CrossChainMasterStrategy", crossChainStrategyProxyAddress @@ -2979,6 +1523,10 @@ async function crossChainFixture() { addresses.CCTPTokenMessengerV2 ); + fixture.relayer = await impersonateAndFund( + addresses.mainnet.validatorRegistrator + ); + await setERC20TokenBalance( fixture.matt.address, fixture.usdc, @@ -3053,33 +1601,12 @@ module.exports = { poolBoosterCodeUpdatedFixture, loadTokenTransferFixture, mockVaultFixture, - compoundFixture, - compoundVaultFixture, - convexVaultFixture, - convexMetaVaultFixture, - convexOETHMetaVaultFixture, - convexGeneralizedMetaForkedFixture, - makerSSRFixture, - morphoSteakhouseUSDCFixture, - morphoGauntletPrimeUSDCFixture, - morphoGauntletPrimeUSDTFixture, morphoOUSDv2Fixture, - morphoCompoundFixture, - aaveFixture, - morphoAaveFixture, - hackedVaultFixture, instantRebaseVaultFixture, rebornFixture, - balancerREthFixture, nativeStakingSSVStrategyFixture, compoundingStakingSSVStrategyFixture, compoundingStakingSSVStrategyMerkleProofsMockedFixture, - oethMorphoAaveFixture, - oeth1InchSwapperFixture, - oethCollateralSwapFixture, - ousdCollateralSwapFixture, - buybackFixture, - harvesterFixture, nodeSnapshot, nodeRevert, woethCcipZapperFixture, diff --git a/contracts/test/_fund.js b/contracts/test/_fund.js index 49ea93558b..3db3144cf4 100644 --- a/contracts/test/_fund.js +++ b/contracts/test/_fund.js @@ -20,16 +20,10 @@ const log = require("../utils/logger")("test:_fund"); const mappedFundingSlots = {}; const balancesContractSlotCache = { - [addresses.mainnet.stETH.toLowerCase()]: [0, false], - [addresses.mainnet.frxETH.toLowerCase()]: [0, false], - [addresses.mainnet.rETH.toLowerCase()]: [1, false], - [addresses.mainnet.sfrxETH.toLowerCase()]: [3, false], - [addresses.mainnet.ThreePoolToken.toLowerCase()]: [3, true], [addresses.mainnet.DAI.toLowerCase()]: [2, false], [addresses.mainnet.USDS.toLowerCase()]: [2, false], [addresses.mainnet.USDC.toLowerCase()]: [9, false], [addresses.mainnet.USDT.toLowerCase()]: [2, false], - [addresses.mainnet.TUSD.toLowerCase()]: [14, false], [addresses.mainnet.OGN.toLowerCase()]: [0, true], [addresses.mainnet.OETHProxy.toLowerCase()]: [157, false], [addresses.mainnet.OUSDProxy.toLowerCase()]: [157, false], diff --git a/contracts/test/_hot-deploy.js b/contracts/test/_hot-deploy.js index 59e7c94151..c92ac3711b 100644 --- a/contracts/test/_hot-deploy.js +++ b/contracts/test/_hot-deploy.js @@ -6,11 +6,6 @@ const { ethers } = hre; const { isFork, isCI } = require("./helpers"); const addresses = require("../utils/addresses"); -const { - balancer_rETH_WETH_PID, - balancer_wstETH_sfrxETH_rETH_PID, - oethPoolLpPID, -} = require("../utils/constants"); const { replaceContractAt } = require("../utils/hardhat"); const log = require("../utils/logger")("test:fixtures:hot-deploy"); @@ -24,60 +19,7 @@ async function constructNewContract( const { deploy } = deployments; const getConstructorArguments = async () => { - if ( - ["BalancerMetaPoolTestStrategy", "BalancerMetaPoolStrategy"].includes( - implContractName - ) - ) { - return [ - [addresses.mainnet.rETH_WETH_BPT, addresses.mainnet.OETHVaultProxy], - [ - addresses.mainnet.rETH, - addresses.mainnet.stETH, - addresses.mainnet.wstETH, - addresses.mainnet.frxETH, - addresses.mainnet.sfrxETH, - addresses.mainnet.balancerVault, // Address of the Balancer vault - balancer_rETH_WETH_PID, // Pool ID of the Balancer pool - ], - addresses.mainnet.rETH_WETH_AuraRewards, // Address of the Aura rewards contract - ]; - } else if (implContractName === "BalancerComposablePoolTestStrategy") { - return [ - [ - addresses.mainnet.wstETH_sfrxETH_rETH_BPT, - addresses.mainnet.OETHVaultProxy, - ], - [ - addresses.mainnet.rETH, - addresses.mainnet.stETH, - addresses.mainnet.wstETH, - addresses.mainnet.frxETH, - addresses.mainnet.sfrxETH, - addresses.mainnet.balancerVault, // Address of the Balancer vault - balancer_wstETH_sfrxETH_rETH_PID, // Pool ID of the Balancer pool - ], - addresses.mainnet.wstETH_sfrxETH_rETH_AuraRewards, // Address of the Aura rewards contract - ]; - } else if (implContractName === "MorphoCompoundStrategy") { - return [ - [ - addresses.zero, // platformAddres not used by the strategy - addresses.mainnet.VaultProxy, - ], - ]; - } else if (implContractName === "ConvexEthMetaStrategy") { - return [ - [addresses.mainnet.CurveOETHMetaPool, addresses.mainnet.OETHVaultProxy], - [ - addresses.mainnet.CVXBooster, - addresses.mainnet.CVXETHRewardsPool, - oethPoolLpPID, - addresses.mainnet.OETHProxy, - addresses.mainnet.WETH, - ], - ]; - } else if (implContractName === "NativeStakingSSVStrategy") { + if (implContractName === "NativeStakingSSVStrategy") { const feeAccumulatorAddress = await fixture[ fixtureStrategyVarName ].FEE_ACCUMULATOR_ADDRESS(); @@ -125,33 +67,12 @@ async function hotDeployOption( const { isOethFixture } = config; const deployStrat = hotDeployOptions.includes("strategy"); const deployVault = hotDeployOptions.includes("vault"); - const deployHarvester = hotDeployOptions.includes("harvester"); - const deployOracleRouter = hotDeployOptions.includes("oracleRouter"); - const deployBuyback = hotDeployOptions.includes("buyback"); log(`Running fixture hot deployment w/ config; isOethFixture:${isOethFixture} strategy:${!!deployStrat} - vault:${!!deployVault} harvester:${!!deployHarvester}`); + vault:${!!deployVault}`); if (deployStrat) { - if (fixtureName === "balancerREthFixture") { - await hotDeployFixture( - fixture, // fixture - "balancerREthStrategy", // fixtureStrategyVarName - "BalancerMetaPoolStrategy" // implContractName - ); - } else if (fixtureName === "morphoCompoundFixture") { - await hotDeployFixture( - fixture, // fixture - "morphoCompoundStrategy", // fixtureStrategyVarName - "MorphoCompoundStrategy" // implContractName - ); - } else if (fixtureName === "convexOETHMetaVaultFixture") { - await hotDeployFixture( - fixture, // fixture - "convexEthMetaStrategy", // fixtureStrategyVarName - "ConvexEthMetaStrategy" // implContractName - ); - } else if (fixtureName === "nativeStakingSSVStrategyFixture") { + if (fixtureName === "nativeStakingSSVStrategyFixture") { await hotDeployFixture( fixture, // fixture "nativeStakingSSVStrategy", // fixtureStrategyVarName @@ -163,16 +84,6 @@ async function hotDeployOption( if (deployVault) { await hotDeployVault(fixture, isOethFixture); } - if (deployHarvester) { - await hotDeployHarvester(fixture, isOethFixture); - } - if (deployOracleRouter) { - await hotDeployOracleRouter(fixture, isOethFixture); - } - - if (deployBuyback) { - await hotDeployBuyback(fixture, isOethFixture); - } } async function hotDeployVault(fixture, isOeth) { @@ -204,83 +115,6 @@ async function hotDeployVault(fixture, isOeth) { await replaceContractAt(liveImplContractAddress, implementation); } -async function hotDeployHarvester(fixture, forOETH) { - const { deploy } = deployments; - const harvesterName = `${forOETH ? "OETH" : ""}Harvester`; - const harvesterProxyName = `${forOETH ? "OETH" : ""}HarvesterProxy`; - const vault = forOETH ? fixture.oethVault : fixture.vault; - const baseToken = forOETH ? fixture.weth : fixture.usdt; - - const cHarvesterProxy = await ethers.getContract(harvesterProxyName); - - log(`Deploying new ${harvesterName} implementation`); - await deploy(harvesterName, { - from: addresses.mainnet.Timelock, - contract: harvesterName, - args: [vault.address, baseToken.address], - }); - const implementation = await ethers.getContract(harvesterName); - const liveImplContractAddress = await cHarvesterProxy.implementation(); - log( - `Replacing implementation at ${liveImplContractAddress} with the fresh bytecode` - ); - await replaceContractAt(liveImplContractAddress, implementation); -} - -async function hotDeployOracleRouter(fixture, forOETH) { - const { deploy } = deployments; - const routerName = `${forOETH ? "OETH" : ""}OracleRouter`; - - const cRouter = await ethers.getContract(routerName); - - if (forOETH) { - await deploy("AuraWETHPriceFeed", { - from: await fixture.strategist.getAddress(), - args: [addresses.mainnet.AuraWeightedOraclePool], - }); - const auraPriceFeed = await ethers.getContract("AuraWETHPriceFeed"); - - await deploy(routerName, { - from: await fixture.strategist.getAddress(), - args: [auraPriceFeed.address], - }); - } else { - await deploy(routerName, { - from: await fixture.strategist.getAddress(), - args: [], - }); - } - - const implementation = await ethers.getContract(routerName); - log(`Replacing implementation at ${cRouter.address} with the fresh bytecode`); - await replaceContractAt(cRouter.address, implementation); -} - -async function hotDeployBuyback(fixture, forOETH) { - const { deploy } = deployments; - const proxyName = `${forOETH ? "OETH" : ""}BuybackProxy`; - const contractName = `${forOETH ? "OETH" : "OUSD"}Buyback`; - - const proxy = await ethers.getContract(proxyName); - const oldImplAddr = await proxy.implementation(); - - await deploy(contractName, { - from: await fixture.strategist.getAddress(), - args: [ - forOETH ? addresses.mainnet.OETHProxy : addresses.mainnet.OUSDProxy, - addresses.mainnet.OGV, - addresses.mainnet.CVX, - addresses.mainnet.CVXLocker, - ], - }); - - const newImpl = await ethers.getContract(contractName); - log( - `Replacing ${proxyName} implementation at ${oldImplAddr} with the fresh bytecode` - ); - await replaceContractAt(oldImplAddr, newImpl); -} - /* Run the fixture and replace the main strategy contract(s) of the fixture * with the freshly compiled implementation. */ diff --git a/contracts/test/_metastrategies-fixtures.js b/contracts/test/_metastrategies-fixtures.js deleted file mode 100644 index 2f1bf7bb85..0000000000 --- a/contracts/test/_metastrategies-fixtures.js +++ /dev/null @@ -1,304 +0,0 @@ -const hre = require("hardhat"); -const { ethers } = hre; -const { formatUnits } = require("ethers/lib/utils"); - -const { ousdUnits, units } = require("./helpers"); -const { convexMetaVaultFixture, resetAllowance } = require("./_fixture"); -const addresses = require("../utils/addresses"); -const erc20Abi = require("./abi/erc20.json"); -const { impersonateAndFund } = require("../utils/signers"); -const { setERC20TokenBalance } = require("./_fund"); - -const log = require("../utils/logger")("test:fixtures:strategies:meta"); - -// NOTE: This can cause a change in setup from mainnet. -// However, mint/redeem tests, without any changes, are tested -// in vault.fork-test.js, so this should be fine. - -async function withDefaultOUSDMetapoolStrategiesSet() { - const fixture = await convexMetaVaultFixture(); - - const { vault, timelock, dai, usdt, usdc, OUSDmetaStrategy, daniel } = - fixture; - - await vault.connect(timelock).setDefaultStrategy(OUSDmetaStrategy.address); - - await vault.connect(timelock).setDefaultStrategy(OUSDmetaStrategy.address); - await vault.connect(timelock).setDefaultStrategy(OUSDmetaStrategy.address); - - fixture.cvxRewardPool = await ethers.getContractAt( - "IRewardStaking", - addresses.mainnet.CVXRewardsPool - ); - - // Also, mint some OUSD so that there's some in the pool - const amount = "500000"; - for (const asset of [usdt, usdc, dai]) { - await setERC20TokenBalance(daniel.address, asset, amount); - await vault - .connect(daniel) - .mint(asset.address, await units(amount, asset), 0); - } - - return fixture; -} - -async function withBalancedOUSDMetaPool() { - const fixture = await withDefaultOUSDMetapoolStrategiesSet(); - - await balanceOUSDMetaPool(fixture); - - return fixture; -} - -async function balanceOUSDMetaPool(fixture) { - const { ousdMetaPool } = fixture; - - log(`Metapool balances before being balanced`); - const balancesBefore = await fixture.ousdMetaPool.get_balances(); - const coinOne3CrvValueBefore = await get3CRVLiquidity( - fixture, - balancesBefore[0] - ); - log( - `Metapool balance 0: ${formatUnits( - coinOne3CrvValueBefore - )} 3CRV (${formatUnits(balancesBefore[0])} OUSD)` - ); - log(`Metapool balance 1: ${formatUnits(balancesBefore[1])} 3CRV`); - - await _balanceMetaPool(fixture, ousdMetaPool); - - log(`Metapool balances after being balanced`); - const balancesAfter = await fixture.ousdMetaPool.get_balances(); - const coinOne3CrvValueAfter = await get3CRVLiquidity( - fixture, - balancesAfter[0] - ); - log( - `Metapool balance 0: ${formatUnits( - coinOne3CrvValueAfter - )} 3CRV (${formatUnits(balancesAfter[0])} OUSD)` - ); - log(`Metapool balance 1: ${formatUnits(balancesAfter[1])} 3CRV`); -} - -async function _balanceMetaPool(fixture, metapool) { - const { vault, domen } = fixture; - - // Balance metapool - const ousdBalance = await metapool.balances(0); - const crv3Balance = await metapool.balances(1); - - // 3Crv value of coins - const coinOne3CrvValue = await get3CRVLiquidity(fixture, ousdBalance); - const coinTwo3CrvValue = crv3Balance; - - const coinOneContract = await ethers.getContractAt( - erc20Abi, - await metapool.coins(0) - ); - const coinTwoContract = await ethers.getContractAt( - erc20Abi, - await metapool.coins(1) - ); - - const exchangeSign = "exchange(int128,int128,uint256,uint256)"; - const metapoolSigner = await impersonateAndFund(metapool.address); - /* let metapool perform the exchange on itself. This is somewhat dirty, but is also the - * best assurance that the liquidity of both coins for balancing are going to be - * available. - */ - const exchangeMethod = await metapool.connect(metapoolSigner)[exchangeSign]; - await resetAllowance(coinOneContract, metapoolSigner, metapool.address); - await resetAllowance(coinTwoContract, metapoolSigner, metapool.address); - - if (coinOne3CrvValue.gt(coinTwo3CrvValue)) { - // There is more OUSD than 3CRV - const crvAmount = coinOne3CrvValue.sub(coinTwo3CrvValue); - await impersonateAndFund(domen.address, "1000000"); - await setERC20TokenBalance(domen.address, coinTwoContract, crvAmount, hre); - - log(`About to add ${formatUnits(crvAmount)} 3CRV to the OUSD Metapool`); - // prettier-ignore - await metapool - .connect(domen)["add_liquidity(uint256[2],uint256)"]([0, crvAmount], 0); - } else if (coinTwo3CrvValue.gt(coinOne3CrvValue)) { - const diffInDollars = coinTwo3CrvValue.sub(coinOne3CrvValue); - const liquidityDiff = await get3CRVLiquidity(fixture, diffInDollars.div(2)); - - if (liquidityDiff > 0) { - // Tilt to Main Token - log(`About to exchange ${formatUnits(liquidityDiff)} OUSD for 3CRV`); - await exchangeMethod(0, 1, liquidityDiff, 0); - } - } - - await vault.connect(domen).allocate(); - await vault.connect(domen).rebase(); -} - -async function withCRV3TitledOUSDMetapool() { - const fixture = await withDefaultOUSDMetapoolStrategiesSet(); - - fixture.metapool = fixture.ousdMetaPool; - - await tiltTo3CRV_Metapool_automatic(fixture); - - return fixture; -} - -async function tiltTo3CRV_OUSDMetapool(fixture, amount) { - const { ousdMetaPool } = fixture; - - await tiltTo3CRV_Metapool(fixture, ousdMetaPool, amount); -} - -/* Tilt towards 3CRV by checking liquidity - */ -async function tiltTo3CRV_Metapool_automatic(fixture) { - const { metapool, threePoolToken } = fixture; - - const metapoolSigner = await impersonateAndFund(metapool.address); - await resetAllowance(threePoolToken, metapoolSigner, metapool.address); - - // 90% of main coin pool liquidity - const shareOfThreePoolCoinBalance = ( - await threePoolToken.balanceOf(metapool.address) - ) - .mul(ousdUnits("0.9")) - .div(ousdUnits("1")); - - let acc = ethers.BigNumber.from("0"); - /* self deploy 90% of threepool coin liquidity until pool has at least five times - * the 3crvLP liquidity comparing to main coin. - */ - while (acc.lt((await metapool.balances(0)).mul(5))) { - // Tilt to main token - await metapool.connect(metapoolSigner)[ - // eslint-disable-next-line - "add_liquidity(uint256[2],uint256)" - ]([0, shareOfThreePoolCoinBalance], 0); - acc = acc.add(shareOfThreePoolCoinBalance); - } -} - -/* Just triple the main token liquidity in a flaky manner where the pool - * re-deploys its own liquidity - */ -async function tiltToMainToken(fixture) { - const { metapool, metapoolCoin } = fixture; - - const metapoolSigner = await impersonateAndFund(metapool.address); - await resetAllowance(metapoolCoin, metapoolSigner, metapool.address); - // 90% of main coin pool liquidity - const shareOfMainCoinBalance = ( - await metapoolCoin.balanceOf(metapool.address) - ) - .mul(ousdUnits("0.9")) - .div(ousdUnits("1")); - - let acc = ethers.BigNumber.from("0"); - - /* self deploy 90% of main coin liquidity until at least five times the main coin liquidity - * comparing to 3crv is deployed to the pool. - */ - while (acc.lt((await metapool.balances(1)).mul(5))) { - // Tilt to main token - await metapool.connect(metapoolSigner)[ - // eslint-disable-next-line - "add_liquidity(uint256[2],uint256)" - ]([shareOfMainCoinBalance, 0], 0); - - acc = acc.add(shareOfMainCoinBalance); - } -} - -async function tiltTo3CRV_Metapool(fixture, metapool, amount) { - const { vault, domen, ousdMetaPool } = fixture; - - // Balance metapool - await _balanceMetaPool(fixture, metapool); - amount = amount || ousdUnits("1000000"); - - // Tilt to 3CRV by a million - const exchangeSign = "exchange(int128,int128,uint256,uint256)"; - // make metapool make exchange on itself. It should always have enough OUSD/3crv to do this - const metapoolSigner = await impersonateAndFund(ousdMetaPool.address); - - await metapool.connect(metapoolSigner)[exchangeSign](1, 0, amount.div(2), 0); - - await vault.connect(domen).allocate(); - await vault.connect(domen).rebase(); -} - -async function withOUSDTitledMetapool() { - const fixture = await withDefaultOUSDMetapoolStrategiesSet(); - - fixture.metapool = fixture.ousdMetaPool; - await tiltToOUSD_OUSDMetapool(fixture); - - return fixture; -} - -async function tiltToOUSD_OUSDMetapool(fixture, amount) { - const { vault, domen, ousdMetaPool } = fixture; - - // Balance metapool - await balanceOUSDMetaPool(fixture); - - amount = amount || ousdUnits("1000000"); - - // Tilt to 3CRV by a million - const exchangeSign = "exchange(int128,int128,uint256,uint256)"; - // make metapool make exchange on itself. It should always have enough OUSD/3crv to do this - const metapoolSigner = await impersonateAndFund(ousdMetaPool.address); - - await ousdMetaPool - .connect(metapoolSigner) - // eslint-disable-next-line - [exchangeSign](0, 1, amount.div(2), 0); - - await vault.connect(domen).allocate(); - await vault.connect(domen).rebase(); -} - -// Convert 3CRV value to OUSD/3CRV Metapool LP tokens -async function getOUSDLiquidity(fixture, crv3Amount) { - const { ousdMetaPool } = fixture; - return _getCoinLiquidity(ousdMetaPool, crv3Amount); -} - -// Convert USD value to 3Pool LP tokens -async function get3CRVLiquidity(fixture, usdAmount) { - const { threepoolSwap } = fixture; - return _getCoinLiquidity(threepoolSwap, usdAmount); -} - -// Convert pool value, eg USD or 3CRV, to Pool LP tokens -async function _getCoinLiquidity(pool, value) { - const vPrice = await pool.get_virtual_price(); - // LP tokens = value / virtual price * 1e18 - return value.div(vPrice).mul(ousdUnits("1")); -} - -module.exports = { - convexMetaVaultFixture, - withDefaultOUSDMetapoolStrategiesSet, - - withBalancedOUSDMetaPool, - balanceOUSDMetaPool, - - withCRV3TitledOUSDMetapool, - tiltTo3CRV_OUSDMetapool, - - withOUSDTitledMetapool, - tiltToOUSD_OUSDMetapool, - - tiltTo3CRV_Metapool, - tiltTo3CRV_Metapool_automatic, - tiltToMainToken, - - getOUSDLiquidity, - get3CRVLiquidity, -}; diff --git a/contracts/test/abi/aTusd.json b/contracts/test/abi/aTusd.json deleted file mode 100644 index e65f02a51d..0000000000 --- a/contracts/test/abi/aTusd.json +++ /dev/null @@ -1,833 +0,0 @@ -{ - "abi": [ - { - "inputs": [ - { - "internalType": "contract LendingPoolAddressesProvider", - "name": "_addressesProvider", - "type": "address" - }, - { - "internalType": "address", - "name": "_underlyingAsset", - "type": "address" - }, - { - "internalType": "uint8", - "name": "_underlyingAssetDecimals", - "type": "uint8" - }, - { - "internalType": "string", - "name": "_name", - "type": "string" - }, - { - "internalType": "string", - "name": "_symbol", - "type": "string" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "value", - "type": "uint256" - } - ], - "name": "Approval", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "_from", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "_to", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "_value", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "_fromBalanceIncrease", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "_toBalanceIncrease", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "_fromIndex", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "_toIndex", - "type": "uint256" - } - ], - "name": "BalanceTransfer", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "_from", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "_value", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "_fromBalanceIncrease", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "_fromIndex", - "type": "uint256" - } - ], - "name": "BurnOnLiquidation", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "_from", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "_to", - "type": "address" - } - ], - "name": "InterestRedirectionAllowanceChanged", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "_from", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "_to", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "_redirectedBalance", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "_fromBalanceIncrease", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "_fromIndex", - "type": "uint256" - } - ], - "name": "InterestStreamRedirected", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "_from", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "_value", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "_fromBalanceIncrease", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "_fromIndex", - "type": "uint256" - } - ], - "name": "MintOnDeposit", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "_from", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "_value", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "_fromBalanceIncrease", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "_fromIndex", - "type": "uint256" - } - ], - "name": "Redeem", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "_targetAddress", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "_targetBalanceIncrease", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "_targetIndex", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "_redirectedBalanceAdded", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "_redirectedBalanceRemoved", - "type": "uint256" - } - ], - "name": "RedirectedBalanceUpdated", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "value", - "type": "uint256" - } - ], - "name": "Transfer", - "type": "event" - }, - { - "constant": true, - "inputs": [], - "name": "UINT_MAX_VALUE", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "_to", - "type": "address" - } - ], - "name": "allowInterestRedirectionTo", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "internalType": "address", - "name": "spender", - "type": "address" - } - ], - "name": "allowance", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "internalType": "uint256", - "name": "value", - "type": "uint256" - } - ], - "name": "approve", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "internalType": "address", - "name": "_user", - "type": "address" - } - ], - "name": "balanceOf", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "_account", - "type": "address" - }, - { - "internalType": "uint256", - "name": "_value", - "type": "uint256" - } - ], - "name": "burnOnLiquidation", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "decimals", - "outputs": [ - { - "internalType": "uint8", - "name": "", - "type": "uint8" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "internalType": "uint256", - "name": "subtractedValue", - "type": "uint256" - } - ], - "name": "decreaseAllowance", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "internalType": "address", - "name": "_user", - "type": "address" - } - ], - "name": "getInterestRedirectionAddress", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "internalType": "address", - "name": "_user", - "type": "address" - } - ], - "name": "getRedirectedBalance", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "internalType": "address", - "name": "_user", - "type": "address" - } - ], - "name": "getUserIndex", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "internalType": "uint256", - "name": "addedValue", - "type": "uint256" - } - ], - "name": "increaseAllowance", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "internalType": "address", - "name": "_user", - "type": "address" - }, - { - "internalType": "uint256", - "name": "_amount", - "type": "uint256" - } - ], - "name": "isTransferAllowed", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "_account", - "type": "address" - }, - { - "internalType": "uint256", - "name": "_amount", - "type": "uint256" - } - ], - "name": "mintOnDeposit", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "name", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "internalType": "address", - "name": "_user", - "type": "address" - } - ], - "name": "principalBalanceOf", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "uint256", - "name": "_amount", - "type": "uint256" - } - ], - "name": "redeem", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "_to", - "type": "address" - } - ], - "name": "redirectInterestStream", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "_from", - "type": "address" - }, - { - "internalType": "address", - "name": "_to", - "type": "address" - } - ], - "name": "redirectInterestStreamOf", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "symbol", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "totalSupply", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "recipient", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "transfer", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "sender", - "type": "address" - }, - { - "internalType": "address", - "name": "recipient", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "transferFrom", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "_from", - "type": "address" - }, - { - "internalType": "address", - "name": "_to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "_value", - "type": "uint256" - } - ], - "name": "transferOnLiquidation", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "underlyingAssetAddress", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - } - ] -} \ No newline at end of file diff --git a/contracts/test/abi/aUsdt.json b/contracts/test/abi/aUsdt.json deleted file mode 100644 index e65f02a51d..0000000000 --- a/contracts/test/abi/aUsdt.json +++ /dev/null @@ -1,833 +0,0 @@ -{ - "abi": [ - { - "inputs": [ - { - "internalType": "contract LendingPoolAddressesProvider", - "name": "_addressesProvider", - "type": "address" - }, - { - "internalType": "address", - "name": "_underlyingAsset", - "type": "address" - }, - { - "internalType": "uint8", - "name": "_underlyingAssetDecimals", - "type": "uint8" - }, - { - "internalType": "string", - "name": "_name", - "type": "string" - }, - { - "internalType": "string", - "name": "_symbol", - "type": "string" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "value", - "type": "uint256" - } - ], - "name": "Approval", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "_from", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "_to", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "_value", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "_fromBalanceIncrease", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "_toBalanceIncrease", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "_fromIndex", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "_toIndex", - "type": "uint256" - } - ], - "name": "BalanceTransfer", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "_from", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "_value", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "_fromBalanceIncrease", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "_fromIndex", - "type": "uint256" - } - ], - "name": "BurnOnLiquidation", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "_from", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "_to", - "type": "address" - } - ], - "name": "InterestRedirectionAllowanceChanged", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "_from", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "_to", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "_redirectedBalance", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "_fromBalanceIncrease", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "_fromIndex", - "type": "uint256" - } - ], - "name": "InterestStreamRedirected", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "_from", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "_value", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "_fromBalanceIncrease", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "_fromIndex", - "type": "uint256" - } - ], - "name": "MintOnDeposit", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "_from", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "_value", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "_fromBalanceIncrease", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "_fromIndex", - "type": "uint256" - } - ], - "name": "Redeem", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "_targetAddress", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "_targetBalanceIncrease", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "_targetIndex", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "_redirectedBalanceAdded", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "_redirectedBalanceRemoved", - "type": "uint256" - } - ], - "name": "RedirectedBalanceUpdated", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "value", - "type": "uint256" - } - ], - "name": "Transfer", - "type": "event" - }, - { - "constant": true, - "inputs": [], - "name": "UINT_MAX_VALUE", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "_to", - "type": "address" - } - ], - "name": "allowInterestRedirectionTo", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "internalType": "address", - "name": "spender", - "type": "address" - } - ], - "name": "allowance", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "internalType": "uint256", - "name": "value", - "type": "uint256" - } - ], - "name": "approve", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "internalType": "address", - "name": "_user", - "type": "address" - } - ], - "name": "balanceOf", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "_account", - "type": "address" - }, - { - "internalType": "uint256", - "name": "_value", - "type": "uint256" - } - ], - "name": "burnOnLiquidation", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "decimals", - "outputs": [ - { - "internalType": "uint8", - "name": "", - "type": "uint8" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "internalType": "uint256", - "name": "subtractedValue", - "type": "uint256" - } - ], - "name": "decreaseAllowance", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "internalType": "address", - "name": "_user", - "type": "address" - } - ], - "name": "getInterestRedirectionAddress", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "internalType": "address", - "name": "_user", - "type": "address" - } - ], - "name": "getRedirectedBalance", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "internalType": "address", - "name": "_user", - "type": "address" - } - ], - "name": "getUserIndex", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "internalType": "uint256", - "name": "addedValue", - "type": "uint256" - } - ], - "name": "increaseAllowance", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "internalType": "address", - "name": "_user", - "type": "address" - }, - { - "internalType": "uint256", - "name": "_amount", - "type": "uint256" - } - ], - "name": "isTransferAllowed", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "_account", - "type": "address" - }, - { - "internalType": "uint256", - "name": "_amount", - "type": "uint256" - } - ], - "name": "mintOnDeposit", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "name", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "internalType": "address", - "name": "_user", - "type": "address" - } - ], - "name": "principalBalanceOf", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "uint256", - "name": "_amount", - "type": "uint256" - } - ], - "name": "redeem", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "_to", - "type": "address" - } - ], - "name": "redirectInterestStream", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "_from", - "type": "address" - }, - { - "internalType": "address", - "name": "_to", - "type": "address" - } - ], - "name": "redirectInterestStreamOf", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "symbol", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "totalSupply", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "recipient", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "transfer", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "sender", - "type": "address" - }, - { - "internalType": "address", - "name": "recipient", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "transferFrom", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "_from", - "type": "address" - }, - { - "internalType": "address", - "name": "_to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "_value", - "type": "uint256" - } - ], - "name": "transferOnLiquidation", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "underlyingAssetAddress", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - } - ] -} \ No newline at end of file diff --git a/contracts/test/abi/aave.json b/contracts/test/abi/aave.json deleted file mode 100644 index ec821155b2..0000000000 --- a/contracts/test/abi/aave.json +++ /dev/null @@ -1,603 +0,0 @@ -{ - "abi": [ - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "newAddress", - "type": "address" - } - ], - "name": "EthereumAddressUpdated", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "newAddress", - "type": "address" - } - ], - "name": "FeeProviderUpdated", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "newAddress", - "type": "address" - } - ], - "name": "LendingPoolConfiguratorUpdated", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "newAddress", - "type": "address" - } - ], - "name": "LendingPoolCoreUpdated", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "newAddress", - "type": "address" - } - ], - "name": "LendingPoolDataProviderUpdated", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "newAddress", - "type": "address" - } - ], - "name": "LendingPoolLiquidationManagerUpdated", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "newAddress", - "type": "address" - } - ], - "name": "LendingPoolManagerUpdated", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "newAddress", - "type": "address" - } - ], - "name": "LendingPoolParametersProviderUpdated", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "newAddress", - "type": "address" - } - ], - "name": "LendingPoolUpdated", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "newAddress", - "type": "address" - } - ], - "name": "LendingRateOracleUpdated", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "previousOwner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "OwnershipTransferred", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "newAddress", - "type": "address" - } - ], - "name": "PriceOracleUpdated", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "bytes32", - "name": "id", - "type": "bytes32" - }, - { - "indexed": true, - "internalType": "address", - "name": "newAddress", - "type": "address" - } - ], - "name": "ProxyCreated", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "newAddress", - "type": "address" - } - ], - "name": "TokenDistributorUpdated", - "type": "event" - }, - { - "constant": true, - "inputs": [ - { - "internalType": "bytes32", - "name": "_key", - "type": "bytes32" - } - ], - "name": "getAddress", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "getFeeProvider", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "getLendingPool", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "getLendingPoolConfigurator", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "getLendingPoolCore", - "outputs": [ - { - "internalType": "address payable", - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "getLendingPoolDataProvider", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "getLendingPoolLiquidationManager", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "getLendingPoolManager", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "getLendingPoolParametersProvider", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "getLendingRateOracle", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "getPriceOracle", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "getTokenDistributor", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "isOwner", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "owner", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [], - "name": "renounceOwnership", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "_feeProvider", - "type": "address" - } - ], - "name": "setFeeProviderImpl", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "_configurator", - "type": "address" - } - ], - "name": "setLendingPoolConfiguratorImpl", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "_lendingPoolCore", - "type": "address" - } - ], - "name": "setLendingPoolCoreImpl", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "_provider", - "type": "address" - } - ], - "name": "setLendingPoolDataProviderImpl", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "_pool", - "type": "address" - } - ], - "name": "setLendingPoolImpl", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "_manager", - "type": "address" - } - ], - "name": "setLendingPoolLiquidationManager", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "_lendingPoolManager", - "type": "address" - } - ], - "name": "setLendingPoolManager", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "_parametersProvider", - "type": "address" - } - ], - "name": "setLendingPoolParametersProviderImpl", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "_lendingRateOracle", - "type": "address" - } - ], - "name": "setLendingRateOracle", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "_priceOracle", - "type": "address" - } - ], - "name": "setPriceOracle", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "_tokenDistributor", - "type": "address" - } - ], - "name": "setTokenDistributor", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "transferOwnership", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - } - ] -} \ No newline at end of file diff --git a/contracts/test/abi/cDai.json b/contracts/test/abi/cDai.json deleted file mode 100644 index ad1e3e7791..0000000000 --- a/contracts/test/abi/cDai.json +++ /dev/null @@ -1,1466 +0,0 @@ -{ - "abi": [ - { - "inputs": [ - { - "internalType": "address", - "name": "underlying_", - "type": "address" - }, - { - "internalType": "contract ComptrollerInterface", - "name": "comptroller_", - "type": "address" - }, - { - "internalType": "contract InterestRateModel", - "name": "interestRateModel_", - "type": "address" - }, - { - "internalType": "uint256", - "name": "initialExchangeRateMantissa_", - "type": "uint256" - }, - { - "internalType": "string", - "name": "name_", - "type": "string" - }, - { - "internalType": "string", - "name": "symbol_", - "type": "string" - }, - { - "internalType": "uint8", - "name": "decimals_", - "type": "uint8" - }, - { - "internalType": "address payable", - "name": "admin_", - "type": "address" - }, - { - "internalType": "address", - "name": "implementation_", - "type": "address" - }, - { - "internalType": "bytes", - "name": "becomeImplementationData", - "type": "bytes" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "uint256", - "name": "cashPrior", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "interestAccumulated", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "borrowIndex", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "totalBorrows", - "type": "uint256" - } - ], - "name": "AccrueInterest", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "Approval", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "address", - "name": "borrower", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "borrowAmount", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "accountBorrows", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "totalBorrows", - "type": "uint256" - } - ], - "name": "Borrow", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "uint256", - "name": "error", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "info", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "detail", - "type": "uint256" - } - ], - "name": "Failure", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "address", - "name": "liquidator", - "type": "address" - }, - { - "indexed": false, - "internalType": "address", - "name": "borrower", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "repayAmount", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "address", - "name": "cTokenCollateral", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "seizeTokens", - "type": "uint256" - } - ], - "name": "LiquidateBorrow", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "address", - "name": "minter", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "mintAmount", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "mintTokens", - "type": "uint256" - } - ], - "name": "Mint", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "address", - "name": "oldAdmin", - "type": "address" - }, - { - "indexed": false, - "internalType": "address", - "name": "newAdmin", - "type": "address" - } - ], - "name": "NewAdmin", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "contract ComptrollerInterface", - "name": "oldComptroller", - "type": "address" - }, - { - "indexed": false, - "internalType": "contract ComptrollerInterface", - "name": "newComptroller", - "type": "address" - } - ], - "name": "NewComptroller", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "address", - "name": "oldImplementation", - "type": "address" - }, - { - "indexed": false, - "internalType": "address", - "name": "newImplementation", - "type": "address" - } - ], - "name": "NewImplementation", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "contract InterestRateModel", - "name": "oldInterestRateModel", - "type": "address" - }, - { - "indexed": false, - "internalType": "contract InterestRateModel", - "name": "newInterestRateModel", - "type": "address" - } - ], - "name": "NewMarketInterestRateModel", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "address", - "name": "oldPendingAdmin", - "type": "address" - }, - { - "indexed": false, - "internalType": "address", - "name": "newPendingAdmin", - "type": "address" - } - ], - "name": "NewPendingAdmin", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "uint256", - "name": "oldReserveFactorMantissa", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "newReserveFactorMantissa", - "type": "uint256" - } - ], - "name": "NewReserveFactor", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "address", - "name": "redeemer", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "redeemAmount", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "redeemTokens", - "type": "uint256" - } - ], - "name": "Redeem", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "address", - "name": "payer", - "type": "address" - }, - { - "indexed": false, - "internalType": "address", - "name": "borrower", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "repayAmount", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "accountBorrows", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "totalBorrows", - "type": "uint256" - } - ], - "name": "RepayBorrow", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "address", - "name": "benefactor", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "addAmount", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "newTotalReserves", - "type": "uint256" - } - ], - "name": "ReservesAdded", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "address", - "name": "admin", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "reduceAmount", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "newTotalReserves", - "type": "uint256" - } - ], - "name": "ReservesReduced", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "Transfer", - "type": "event" - }, - { - "payable": true, - "stateMutability": "payable", - "type": "fallback" - }, - { - "constant": false, - "inputs": [], - "name": "_acceptAdmin", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "uint256", - "name": "addAmount", - "type": "uint256" - } - ], - "name": "_addReserves", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "uint256", - "name": "reduceAmount", - "type": "uint256" - } - ], - "name": "_reduceReserves", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "contract ComptrollerInterface", - "name": "newComptroller", - "type": "address" - } - ], - "name": "_setComptroller", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "implementation_", - "type": "address" - }, - { - "internalType": "bool", - "name": "allowResign", - "type": "bool" - }, - { - "internalType": "bytes", - "name": "becomeImplementationData", - "type": "bytes" - } - ], - "name": "_setImplementation", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "contract InterestRateModel", - "name": "newInterestRateModel", - "type": "address" - } - ], - "name": "_setInterestRateModel", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "address payable", - "name": "newPendingAdmin", - "type": "address" - } - ], - "name": "_setPendingAdmin", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "uint256", - "name": "newReserveFactorMantissa", - "type": "uint256" - } - ], - "name": "_setReserveFactor", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "accrualBlockNumber", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [], - "name": "accrueInterest", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "admin", - "outputs": [ - { - "internalType": "address payable", - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "internalType": "address", - "name": "spender", - "type": "address" - } - ], - "name": "allowance", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "approve", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "internalType": "address", - "name": "owner", - "type": "address" - } - ], - "name": "balanceOf", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "owner", - "type": "address" - } - ], - "name": "balanceOfUnderlying", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "uint256", - "name": "borrowAmount", - "type": "uint256" - } - ], - "name": "borrow", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "account", - "type": "address" - } - ], - "name": "borrowBalanceCurrent", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "internalType": "address", - "name": "account", - "type": "address" - } - ], - "name": "borrowBalanceStored", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "borrowIndex", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "borrowRatePerBlock", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "comptroller", - "outputs": [ - { - "internalType": "contract ComptrollerInterface", - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "decimals", - "outputs": [ - { - "internalType": "uint8", - "name": "", - "type": "uint8" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "bytes", - "name": "data", - "type": "bytes" - } - ], - "name": "delegateToImplementation", - "outputs": [ - { - "internalType": "bytes", - "name": "", - "type": "bytes" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "internalType": "bytes", - "name": "data", - "type": "bytes" - } - ], - "name": "delegateToViewImplementation", - "outputs": [ - { - "internalType": "bytes", - "name": "", - "type": "bytes" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [], - "name": "exchangeRateCurrent", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "exchangeRateStored", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "internalType": "address", - "name": "account", - "type": "address" - } - ], - "name": "getAccountSnapshot", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "getCash", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "implementation", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "interestRateModel", - "outputs": [ - { - "internalType": "contract InterestRateModel", - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "isCToken", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "borrower", - "type": "address" - }, - { - "internalType": "uint256", - "name": "repayAmount", - "type": "uint256" - }, - { - "internalType": "contract CTokenInterface", - "name": "cTokenCollateral", - "type": "address" - } - ], - "name": "liquidateBorrow", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "uint256", - "name": "mintAmount", - "type": "uint256" - } - ], - "name": "mint", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "name", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "pendingAdmin", - "outputs": [ - { - "internalType": "address payable", - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "uint256", - "name": "redeemTokens", - "type": "uint256" - } - ], - "name": "redeem", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "uint256", - "name": "redeemAmount", - "type": "uint256" - } - ], - "name": "redeemUnderlying", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "uint256", - "name": "repayAmount", - "type": "uint256" - } - ], - "name": "repayBorrow", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "borrower", - "type": "address" - }, - { - "internalType": "uint256", - "name": "repayAmount", - "type": "uint256" - } - ], - "name": "repayBorrowBehalf", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "reserveFactorMantissa", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "liquidator", - "type": "address" - }, - { - "internalType": "address", - "name": "borrower", - "type": "address" - }, - { - "internalType": "uint256", - "name": "seizeTokens", - "type": "uint256" - } - ], - "name": "seize", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "supplyRatePerBlock", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "symbol", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "totalBorrows", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [], - "name": "totalBorrowsCurrent", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "totalReserves", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "totalSupply", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "dst", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "transfer", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "src", - "type": "address" - }, - { - "internalType": "address", - "name": "dst", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "transferFrom", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "underlying", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - } - ] -} \ No newline at end of file diff --git a/contracts/test/abi/cUsdc.json b/contracts/test/abi/cUsdc.json deleted file mode 100644 index 6cbbc39eaa..0000000000 --- a/contracts/test/abi/cUsdc.json +++ /dev/null @@ -1,1175 +0,0 @@ -{ - "abi": [ - { - "constant": true, - "inputs": [], - "name": "name", - "outputs": [ - { - "name": "", - "type": "string" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "spender", - "type": "address" - }, - { - "name": "amount", - "type": "uint256" - } - ], - "name": "approve", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "repayAmount", - "type": "uint256" - } - ], - "name": "repayBorrow", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "reserveFactorMantissa", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "account", - "type": "address" - } - ], - "name": "borrowBalanceCurrent", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "totalSupply", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "exchangeRateStored", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "src", - "type": "address" - }, - { - "name": "dst", - "type": "address" - }, - { - "name": "amount", - "type": "uint256" - } - ], - "name": "transferFrom", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "borrower", - "type": "address" - }, - { - "name": "repayAmount", - "type": "uint256" - } - ], - "name": "repayBorrowBehalf", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "pendingAdmin", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "decimals", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "owner", - "type": "address" - } - ], - "name": "balanceOfUnderlying", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "getCash", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "newComptroller", - "type": "address" - } - ], - "name": "_setComptroller", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "totalBorrows", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "comptroller", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "reduceAmount", - "type": "uint256" - } - ], - "name": "_reduceReserves", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "initialExchangeRateMantissa", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "accrualBlockNumber", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "underlying", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "owner", - "type": "address" - } - ], - "name": "balanceOf", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [], - "name": "totalBorrowsCurrent", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "redeemAmount", - "type": "uint256" - } - ], - "name": "redeemUnderlying", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "totalReserves", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "symbol", - "outputs": [ - { - "name": "", - "type": "string" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "account", - "type": "address" - } - ], - "name": "borrowBalanceStored", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "mintAmount", - "type": "uint256" - } - ], - "name": "mint", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [], - "name": "accrueInterest", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "dst", - "type": "address" - }, - { - "name": "amount", - "type": "uint256" - } - ], - "name": "transfer", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "borrowIndex", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "supplyRatePerBlock", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "liquidator", - "type": "address" - }, - { - "name": "borrower", - "type": "address" - }, - { - "name": "seizeTokens", - "type": "uint256" - } - ], - "name": "seize", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "newPendingAdmin", - "type": "address" - } - ], - "name": "_setPendingAdmin", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [], - "name": "exchangeRateCurrent", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "account", - "type": "address" - } - ], - "name": "getAccountSnapshot", - "outputs": [ - { - "name": "", - "type": "uint256" - }, - { - "name": "", - "type": "uint256" - }, - { - "name": "", - "type": "uint256" - }, - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "borrowAmount", - "type": "uint256" - } - ], - "name": "borrow", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "redeemTokens", - "type": "uint256" - } - ], - "name": "redeem", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "owner", - "type": "address" - }, - { - "name": "spender", - "type": "address" - } - ], - "name": "allowance", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [], - "name": "_acceptAdmin", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "newInterestRateModel", - "type": "address" - } - ], - "name": "_setInterestRateModel", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "interestRateModel", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "borrower", - "type": "address" - }, - { - "name": "repayAmount", - "type": "uint256" - }, - { - "name": "cTokenCollateral", - "type": "address" - } - ], - "name": "liquidateBorrow", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "admin", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "borrowRatePerBlock", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "newReserveFactorMantissa", - "type": "uint256" - } - ], - "name": "_setReserveFactor", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "isCToken", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "name": "underlying_", - "type": "address" - }, - { - "name": "comptroller_", - "type": "address" - }, - { - "name": "interestRateModel_", - "type": "address" - }, - { - "name": "initialExchangeRateMantissa_", - "type": "uint256" - }, - { - "name": "name_", - "type": "string" - }, - { - "name": "symbol_", - "type": "string" - }, - { - "name": "decimals_", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "name": "interestAccumulated", - "type": "uint256" - }, - { - "indexed": false, - "name": "borrowIndex", - "type": "uint256" - }, - { - "indexed": false, - "name": "totalBorrows", - "type": "uint256" - } - ], - "name": "AccrueInterest", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "name": "minter", - "type": "address" - }, - { - "indexed": false, - "name": "mintAmount", - "type": "uint256" - }, - { - "indexed": false, - "name": "mintTokens", - "type": "uint256" - } - ], - "name": "Mint", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "name": "redeemer", - "type": "address" - }, - { - "indexed": false, - "name": "redeemAmount", - "type": "uint256" - }, - { - "indexed": false, - "name": "redeemTokens", - "type": "uint256" - } - ], - "name": "Redeem", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "name": "borrower", - "type": "address" - }, - { - "indexed": false, - "name": "borrowAmount", - "type": "uint256" - }, - { - "indexed": false, - "name": "accountBorrows", - "type": "uint256" - }, - { - "indexed": false, - "name": "totalBorrows", - "type": "uint256" - } - ], - "name": "Borrow", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "name": "payer", - "type": "address" - }, - { - "indexed": false, - "name": "borrower", - "type": "address" - }, - { - "indexed": false, - "name": "repayAmount", - "type": "uint256" - }, - { - "indexed": false, - "name": "accountBorrows", - "type": "uint256" - }, - { - "indexed": false, - "name": "totalBorrows", - "type": "uint256" - } - ], - "name": "RepayBorrow", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "name": "liquidator", - "type": "address" - }, - { - "indexed": false, - "name": "borrower", - "type": "address" - }, - { - "indexed": false, - "name": "repayAmount", - "type": "uint256" - }, - { - "indexed": false, - "name": "cTokenCollateral", - "type": "address" - }, - { - "indexed": false, - "name": "seizeTokens", - "type": "uint256" - } - ], - "name": "LiquidateBorrow", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "name": "oldPendingAdmin", - "type": "address" - }, - { - "indexed": false, - "name": "newPendingAdmin", - "type": "address" - } - ], - "name": "NewPendingAdmin", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "name": "oldAdmin", - "type": "address" - }, - { - "indexed": false, - "name": "newAdmin", - "type": "address" - } - ], - "name": "NewAdmin", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "name": "oldComptroller", - "type": "address" - }, - { - "indexed": false, - "name": "newComptroller", - "type": "address" - } - ], - "name": "NewComptroller", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "name": "oldInterestRateModel", - "type": "address" - }, - { - "indexed": false, - "name": "newInterestRateModel", - "type": "address" - } - ], - "name": "NewMarketInterestRateModel", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "name": "oldReserveFactorMantissa", - "type": "uint256" - }, - { - "indexed": false, - "name": "newReserveFactorMantissa", - "type": "uint256" - } - ], - "name": "NewReserveFactor", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "name": "admin", - "type": "address" - }, - { - "indexed": false, - "name": "reduceAmount", - "type": "uint256" - }, - { - "indexed": false, - "name": "newTotalReserves", - "type": "uint256" - } - ], - "name": "ReservesReduced", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "name": "error", - "type": "uint256" - }, - { - "indexed": false, - "name": "info", - "type": "uint256" - }, - { - "indexed": false, - "name": "detail", - "type": "uint256" - } - ], - "name": "Failure", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "from", - "type": "address" - }, - { - "indexed": true, - "name": "to", - "type": "address" - }, - { - "indexed": false, - "name": "amount", - "type": "uint256" - } - ], - "name": "Transfer", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "owner", - "type": "address" - }, - { - "indexed": true, - "name": "spender", - "type": "address" - }, - { - "indexed": false, - "name": "amount", - "type": "uint256" - } - ], - "name": "Approval", - "type": "event" - } - ] -} \ No newline at end of file diff --git a/contracts/test/abi/comp.json b/contracts/test/abi/comp.json deleted file mode 100644 index dc20baa89f..0000000000 --- a/contracts/test/abi/comp.json +++ /dev/null @@ -1,532 +0,0 @@ -{ - "abi": [ - { - "inputs": [ - { - "internalType": "address", - "name": "account", - "type": "address" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "Approval", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "delegator", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "fromDelegate", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "toDelegate", - "type": "address" - } - ], - "name": "DelegateChanged", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "delegate", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "previousBalance", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "newBalance", - "type": "uint256" - } - ], - "name": "DelegateVotesChanged", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "Transfer", - "type": "event" - }, - { - "constant": true, - "inputs": [], - "name": "DELEGATION_TYPEHASH", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "DOMAIN_TYPEHASH", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "internalType": "address", - "name": "account", - "type": "address" - }, - { - "internalType": "address", - "name": "spender", - "type": "address" - } - ], - "name": "allowance", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "internalType": "uint256", - "name": "rawAmount", - "type": "uint256" - } - ], - "name": "approve", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "internalType": "address", - "name": "account", - "type": "address" - } - ], - "name": "balanceOf", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - }, - { - "internalType": "uint32", - "name": "", - "type": "uint32" - } - ], - "name": "checkpoints", - "outputs": [ - { - "internalType": "uint32", - "name": "fromBlock", - "type": "uint32" - }, - { - "internalType": "uint96", - "name": "votes", - "type": "uint96" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "decimals", - "outputs": [ - { - "internalType": "uint8", - "name": "", - "type": "uint8" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "delegatee", - "type": "address" - } - ], - "name": "delegate", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "delegatee", - "type": "address" - }, - { - "internalType": "uint256", - "name": "nonce", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "expiry", - "type": "uint256" - }, - { - "internalType": "uint8", - "name": "v", - "type": "uint8" - }, - { - "internalType": "bytes32", - "name": "r", - "type": "bytes32" - }, - { - "internalType": "bytes32", - "name": "s", - "type": "bytes32" - } - ], - "name": "delegateBySig", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "name": "delegates", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "internalType": "address", - "name": "account", - "type": "address" - } - ], - "name": "getCurrentVotes", - "outputs": [ - { - "internalType": "uint96", - "name": "", - "type": "uint96" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "internalType": "address", - "name": "account", - "type": "address" - }, - { - "internalType": "uint256", - "name": "blockNumber", - "type": "uint256" - } - ], - "name": "getPriorVotes", - "outputs": [ - { - "internalType": "uint96", - "name": "", - "type": "uint96" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "name", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "name": "nonces", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "name": "numCheckpoints", - "outputs": [ - { - "internalType": "uint32", - "name": "", - "type": "uint32" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "symbol", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "totalSupply", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "dst", - "type": "address" - }, - { - "internalType": "uint256", - "name": "rawAmount", - "type": "uint256" - } - ], - "name": "transfer", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "src", - "type": "address" - }, - { - "internalType": "address", - "name": "dst", - "type": "address" - }, - { - "internalType": "uint256", - "name": "rawAmount", - "type": "uint256" - } - ], - "name": "transferFrom", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - } - ] -} \ No newline at end of file diff --git a/contracts/test/abi/crvMinter.json b/contracts/test/abi/crvMinter.json deleted file mode 100644 index 18f8aadfbd..0000000000 --- a/contracts/test/abi/crvMinter.json +++ /dev/null @@ -1 +0,0 @@ -[{"name":"Minted","inputs":[{"type":"address","name":"recipient","indexed":true},{"type":"address","name":"gauge","indexed":false},{"type":"uint256","name":"minted","indexed":false}],"anonymous":false,"type":"event"},{"outputs":[],"inputs":[{"type":"address","name":"_token"},{"type":"address","name":"_controller"}],"stateMutability":"nonpayable","type":"constructor"},{"name":"mint","outputs":[],"inputs":[{"type":"address","name":"gauge_addr"}],"stateMutability":"nonpayable","type":"function","gas":100038},{"name":"mint_many","outputs":[],"inputs":[{"type":"address[8]","name":"gauge_addrs"}],"stateMutability":"nonpayable","type":"function","gas":408502},{"name":"mint_for","outputs":[],"inputs":[{"type":"address","name":"gauge_addr"},{"type":"address","ame":"_for"}],"stateMutability":"nonpayable","type":"function","gas":101219},{"name":"toggle_approve_mint","outputs":[],"inputs":[{"type":"address","name":"minting_user"}],"stateMutability":"nonpayable","type":"function","gas":36726},{"name":"token","outputs":[{"type":"address","name":""}],"inputs":[],"stateMutability":"view","type":"function","gas":1301},{"name":"controller","outputs":[{"type":"address","name":""}],"inputs":[],"stateMutability":"view","type":"function","gas":1331},{"name":"minted","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"address","name":"arg0"},{"type":"address","name":"arg1"}],"stateMutability":"view","type":"function","gas":1669},{"name":"allowed_to_mint_for","outputs":[{"type":"bool","name":""}],"inputs":[{"type":"address","name":"arg0"},{"type":"address","name":"arg1"}],"stateMutability":"view","type":"function","gas":1699}] diff --git a/contracts/test/abi/morphoLens.json b/contracts/test/abi/morphoLens.json deleted file mode 100644 index ce6af80edb..0000000000 --- a/contracts/test/abi/morphoLens.json +++ /dev/null @@ -1,1182 +0,0 @@ -[ - { - "inputs": [], - "name": "CompoundOracleFailed", - "type": "error" - }, - { - "inputs": [], - "name": "InvalidPoolToken", - "type": "error" - }, - { - "inputs": [], - "name": "MAX_BASIS_POINTS", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "WAD", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "comptroller", - "outputs": [ - { - "internalType": "contract IComptroller", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_user", - "type": "address" - }, - { - "internalType": "address", - "name": "_poolTokenBorrowedAddress", - "type": "address" - }, - { - "internalType": "address", - "name": "_poolTokenCollateralAddress", - "type": "address" - }, - { - "internalType": "address[]", - "name": "_updatedMarkets", - "type": "address[]" - } - ], - "name": "computeLiquidationRepayAmount", - "outputs": [ - { - "internalType": "uint256", - "name": "toRepay", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_borrower", - "type": "address" - }, - { - "internalType": "address", - "name": "_poolTokenAddress", - "type": "address" - }, - { - "internalType": "uint256", - "name": "_balance", - "type": "uint256" - } - ], - "name": "getAccruedBorrowerComp", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_supplier", - "type": "address" - }, - { - "internalType": "address", - "name": "_poolTokenAddress", - "type": "address" - }, - { - "internalType": "uint256", - "name": "_balance", - "type": "uint256" - } - ], - "name": "getAccruedSupplierComp", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_poolTokenAddress", - "type": "address" - } - ], - "name": "getAdvancedMarketData", - "outputs": [ - { - "internalType": "uint256", - "name": "p2pSupplyIndex", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "p2pBorrowIndex", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "poolSupplyIndex", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "poolBorrowIndex", - "type": "uint256" - }, - { - "internalType": "uint32", - "name": "lastUpdateBlockNumber", - "type": "uint32" - }, - { - "internalType": "uint256", - "name": "p2pSupplyDelta", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "p2pBorrowDelta", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "getAllMarkets", - "outputs": [ - { - "internalType": "address[]", - "name": "marketsCreated", - "type": "address[]" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_poolTokenAddress", - "type": "address" - } - ], - "name": "getAverageBorrowRatePerBlock", - "outputs": [ - { - "internalType": "uint256", - "name": "avgBorrowRatePerBlock", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "p2pBorrowAmount", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "poolBorrowAmount", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_poolTokenAddress", - "type": "address" - } - ], - "name": "getAverageSupplyRatePerBlock", - "outputs": [ - { - "internalType": "uint256", - "name": "avgSupplyRatePerBlock", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "p2pSupplyAmount", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "poolSupplyAmount", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_poolTokenAddress", - "type": "address" - }, - { - "internalType": "address", - "name": "_user", - "type": "address" - } - ], - "name": "getCurrentBorrowBalanceInOf", - "outputs": [ - { - "internalType": "uint256", - "name": "balanceOnPool", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "balanceInP2P", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "totalBalance", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_poolTokenAddress", - "type": "address" - } - ], - "name": "getCurrentCompBorrowIndex", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_poolTokenAddress", - "type": "address" - } - ], - "name": "getCurrentCompSupplyIndex", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_poolTokenAddress", - "type": "address" - } - ], - "name": "getCurrentP2PBorrowIndex", - "outputs": [ - { - "internalType": "uint256", - "name": "currentP2PBorrowIndex", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_poolTokenAddress", - "type": "address" - } - ], - "name": "getCurrentP2PSupplyIndex", - "outputs": [ - { - "internalType": "uint256", - "name": "currentP2PSupplyIndex", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_poolTokenAddress", - "type": "address" - } - ], - "name": "getCurrentPoolIndexes", - "outputs": [ - { - "internalType": "uint256", - "name": "currentPoolSupplyIndex", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "currentPoolBorrowIndex", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_poolTokenAddress", - "type": "address" - }, - { - "internalType": "address", - "name": "_user", - "type": "address" - } - ], - "name": "getCurrentSupplyBalanceInOf", - "outputs": [ - { - "internalType": "uint256", - "name": "balanceOnPool", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "balanceInP2P", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "totalBalance", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_poolTokenAddress", - "type": "address" - }, - { - "internalType": "address", - "name": "_user", - "type": "address" - } - ], - "name": "getCurrentUserBorrowRatePerBlock", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_poolTokenAddress", - "type": "address" - }, - { - "internalType": "address", - "name": "_user", - "type": "address" - } - ], - "name": "getCurrentUserSupplyRatePerBlock", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_user", - "type": "address" - } - ], - "name": "getEnteredMarkets", - "outputs": [ - { - "internalType": "address[]", - "name": "enteredMarkets", - "type": "address[]" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_poolTokenAddress", - "type": "address" - }, - { - "internalType": "bool", - "name": "_getUpdatedIndexes", - "type": "bool" - } - ], - "name": "getIndexes", - "outputs": [ - { - "internalType": "uint256", - "name": "newP2PSupplyIndex", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "newP2PBorrowIndex", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "newPoolSupplyIndex", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "newPoolBorrowIndex", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_poolTokenAddress", - "type": "address" - } - ], - "name": "getMainMarketData", - "outputs": [ - { - "internalType": "uint256", - "name": "avgSupplyRatePerBlock", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "avgBorrowRatePerBlock", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "p2pSupplyAmount", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "p2pBorrowAmount", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "poolSupplyAmount", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "poolBorrowAmount", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_poolTokenAddress", - "type": "address" - } - ], - "name": "getMarketConfiguration", - "outputs": [ - { - "internalType": "address", - "name": "underlying", - "type": "address" - }, - { - "internalType": "bool", - "name": "isCreated", - "type": "bool" - }, - { - "internalType": "bool", - "name": "p2pDisabled", - "type": "bool" - }, - { - "internalType": "bool", - "name": "isPaused", - "type": "bool" - }, - { - "internalType": "bool", - "name": "isPartiallyPaused", - "type": "bool" - }, - { - "internalType": "uint16", - "name": "reserveFactor", - "type": "uint16" - }, - { - "internalType": "uint16", - "name": "p2pIndexCursor", - "type": "uint16" - }, - { - "internalType": "uint256", - "name": "collateralFactor", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_poolTokenAddress", - "type": "address" - }, - { - "internalType": "address", - "name": "_user", - "type": "address" - }, - { - "internalType": "uint256", - "name": "_amount", - "type": "uint256" - } - ], - "name": "getNextUserBorrowRatePerBlock", - "outputs": [ - { - "internalType": "uint256", - "name": "nextBorrowRatePerBlock", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "balanceOnPool", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "balanceInP2P", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "totalBalance", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_poolTokenAddress", - "type": "address" - }, - { - "internalType": "address", - "name": "_user", - "type": "address" - }, - { - "internalType": "uint256", - "name": "_amount", - "type": "uint256" - } - ], - "name": "getNextUserSupplyRatePerBlock", - "outputs": [ - { - "internalType": "uint256", - "name": "nextSupplyRatePerBlock", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "balanceOnPool", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "balanceInP2P", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "totalBalance", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_poolTokenAddress", - "type": "address" - } - ], - "name": "getRatesPerBlock", - "outputs": [ - { - "internalType": "uint256", - "name": "p2pSupplyRate", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "p2pBorrowRate", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "poolSupplyRate", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "poolBorrowRate", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "getTotalBorrow", - "outputs": [ - { - "internalType": "uint256", - "name": "p2pBorrowAmount", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "poolBorrowAmount", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "totalBorrowAmount", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_poolTokenAddress", - "type": "address" - } - ], - "name": "getTotalMarketBorrow", - "outputs": [ - { - "internalType": "uint256", - "name": "p2pBorrowAmount", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "poolBorrowAmount", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_poolTokenAddress", - "type": "address" - } - ], - "name": "getTotalMarketSupply", - "outputs": [ - { - "internalType": "uint256", - "name": "p2pSupplyAmount", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "poolSupplyAmount", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "getTotalSupply", - "outputs": [ - { - "internalType": "uint256", - "name": "p2pSupplyAmount", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "poolSupplyAmount", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "totalSupplyAmount", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_user", - "type": "address" - }, - { - "internalType": "address[]", - "name": "_updatedMarkets", - "type": "address[]" - } - ], - "name": "getUserBalanceStates", - "outputs": [ - { - "internalType": "uint256", - "name": "collateralValue", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "debtValue", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "maxDebtValue", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_user", - "type": "address" - }, - { - "internalType": "address[]", - "name": "_updatedMarkets", - "type": "address[]" - } - ], - "name": "getUserHealthFactor", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_user", - "type": "address" - }, - { - "internalType": "address", - "name": "_poolTokenAddress", - "type": "address" - }, - { - "internalType": "uint256", - "name": "_withdrawnAmount", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "_borrowedAmount", - "type": "uint256" - } - ], - "name": "getUserHypotheticalBalanceStates", - "outputs": [ - { - "internalType": "uint256", - "name": "debtValue", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "maxDebtValue", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_user", - "type": "address" - }, - { - "internalType": "address", - "name": "_poolTokenAddress", - "type": "address" - }, - { - "internalType": "bool", - "name": "_getUpdatedIndexes", - "type": "bool" - }, - { - "internalType": "contract ICompoundOracle", - "name": "_oracle", - "type": "address" - } - ], - "name": "getUserLiquidityDataForAsset", - "outputs": [ - { - "components": [ - { - "internalType": "uint256", - "name": "collateralValue", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "maxDebtValue", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "debtValue", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "underlyingPrice", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "collateralFactor", - "type": "uint256" - } - ], - "internalType": "struct Types.AssetLiquidityData", - "name": "assetData", - "type": "tuple" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_user", - "type": "address" - }, - { - "internalType": "address", - "name": "_poolTokenAddress", - "type": "address" - } - ], - "name": "getUserMaxCapacitiesForAsset", - "outputs": [ - { - "internalType": "uint256", - "name": "withdrawable", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "borrowable", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address[]", - "name": "_poolTokenAddresses", - "type": "address[]" - }, - { - "internalType": "address", - "name": "_user", - "type": "address" - } - ], - "name": "getUserUnclaimedRewards", - "outputs": [ - { - "internalType": "uint256", - "name": "unclaimedRewards", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_morphoAddress", - "type": "address" - } - ], - "name": "initialize", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_user", - "type": "address" - }, - { - "internalType": "address[]", - "name": "_updatedMarkets", - "type": "address[]" - } - ], - "name": "isLiquidatable", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_poolTokenAddress", - "type": "address" - } - ], - "name": "isMarketCreated", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_poolTokenAddress", - "type": "address" - } - ], - "name": "isMarketCreatedAndNotPaused", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_poolTokenAddress", - "type": "address" - } - ], - "name": "isMarketCreatedAndNotPausedNorPartiallyPaused", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "morpho", - "outputs": [ - { - "internalType": "contract IMorpho", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "rewardsManager", - "outputs": [ - { - "internalType": "contract IRewardsManager", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - } -] \ No newline at end of file diff --git a/contracts/test/abi/oethMetapool.json b/contracts/test/abi/oethMetapool.json deleted file mode 100644 index 343e425d91..0000000000 --- a/contracts/test/abi/oethMetapool.json +++ /dev/null @@ -1,578 +0,0 @@ -[ - { - "name": "Transfer", - "inputs": [ - { "name": "sender", "type": "address", "indexed": true }, - { "name": "receiver", "type": "address", "indexed": true }, - { "name": "value", "type": "uint256", "indexed": false } - ], - "anonymous": false, - "type": "event" - }, - { - "name": "Approval", - "inputs": [ - { "name": "owner", "type": "address", "indexed": true }, - { "name": "spender", "type": "address", "indexed": true }, - { "name": "value", "type": "uint256", "indexed": false } - ], - "anonymous": false, - "type": "event" - }, - { - "name": "TokenExchange", - "inputs": [ - { "name": "buyer", "type": "address", "indexed": true }, - { "name": "sold_id", "type": "int128", "indexed": false }, - { "name": "tokens_sold", "type": "uint256", "indexed": false }, - { "name": "bought_id", "type": "int128", "indexed": false }, - { "name": "tokens_bought", "type": "uint256", "indexed": false } - ], - "anonymous": false, - "type": "event" - }, - { - "name": "AddLiquidity", - "inputs": [ - { "name": "provider", "type": "address", "indexed": true }, - { "name": "token_amounts", "type": "uint256[2]", "indexed": false }, - { "name": "fees", "type": "uint256[2]", "indexed": false }, - { "name": "invariant", "type": "uint256", "indexed": false }, - { "name": "token_supply", "type": "uint256", "indexed": false } - ], - "anonymous": false, - "type": "event" - }, - { - "name": "RemoveLiquidity", - "inputs": [ - { "name": "provider", "type": "address", "indexed": true }, - { "name": "token_amounts", "type": "uint256[2]", "indexed": false }, - { "name": "fees", "type": "uint256[2]", "indexed": false }, - { "name": "token_supply", "type": "uint256", "indexed": false } - ], - "anonymous": false, - "type": "event" - }, - { - "name": "RemoveLiquidityOne", - "inputs": [ - { "name": "provider", "type": "address", "indexed": true }, - { "name": "token_amount", "type": "uint256", "indexed": false }, - { "name": "coin_amount", "type": "uint256", "indexed": false }, - { "name": "token_supply", "type": "uint256", "indexed": false } - ], - "anonymous": false, - "type": "event" - }, - { - "name": "RemoveLiquidityImbalance", - "inputs": [ - { "name": "provider", "type": "address", "indexed": true }, - { "name": "token_amounts", "type": "uint256[2]", "indexed": false }, - { "name": "fees", "type": "uint256[2]", "indexed": false }, - { "name": "invariant", "type": "uint256", "indexed": false }, - { "name": "token_supply", "type": "uint256", "indexed": false } - ], - "anonymous": false, - "type": "event" - }, - { - "name": "RampA", - "inputs": [ - { "name": "old_A", "type": "uint256", "indexed": false }, - { "name": "new_A", "type": "uint256", "indexed": false }, - { "name": "initial_time", "type": "uint256", "indexed": false }, - { "name": "future_time", "type": "uint256", "indexed": false } - ], - "anonymous": false, - "type": "event" - }, - { - "name": "StopRampA", - "inputs": [ - { "name": "A", "type": "uint256", "indexed": false }, - { "name": "t", "type": "uint256", "indexed": false } - ], - "anonymous": false, - "type": "event" - }, - { - "name": "CommitNewFee", - "inputs": [{ "name": "new_fee", "type": "uint256", "indexed": false }], - "anonymous": false, - "type": "event" - }, - { - "name": "ApplyNewFee", - "inputs": [{ "name": "fee", "type": "uint256", "indexed": false }], - "anonymous": false, - "type": "event" - }, - { - "stateMutability": "nonpayable", - "type": "constructor", - "inputs": [], - "outputs": [] - }, - { - "stateMutability": "nonpayable", - "type": "function", - "name": "initialize", - "inputs": [ - { "name": "_name", "type": "string" }, - { "name": "_symbol", "type": "string" }, - { "name": "_coins", "type": "address[4]" }, - { "name": "_rate_multipliers", "type": "uint256[4]" }, - { "name": "_A", "type": "uint256" }, - { "name": "_fee", "type": "uint256" } - ], - "outputs": [] - }, - { - "stateMutability": "view", - "type": "function", - "name": "decimals", - "inputs": [], - "outputs": [{ "name": "", "type": "uint256" }] - }, - { - "stateMutability": "nonpayable", - "type": "function", - "name": "transfer", - "inputs": [ - { "name": "_to", "type": "address" }, - { "name": "_value", "type": "uint256" } - ], - "outputs": [{ "name": "", "type": "bool" }] - }, - { - "stateMutability": "nonpayable", - "type": "function", - "name": "transferFrom", - "inputs": [ - { "name": "_from", "type": "address" }, - { "name": "_to", "type": "address" }, - { "name": "_value", "type": "uint256" } - ], - "outputs": [{ "name": "", "type": "bool" }] - }, - { - "stateMutability": "nonpayable", - "type": "function", - "name": "approve", - "inputs": [ - { "name": "_spender", "type": "address" }, - { "name": "_value", "type": "uint256" } - ], - "outputs": [{ "name": "", "type": "bool" }] - }, - { - "stateMutability": "nonpayable", - "type": "function", - "name": "permit", - "inputs": [ - { "name": "_owner", "type": "address" }, - { "name": "_spender", "type": "address" }, - { "name": "_value", "type": "uint256" }, - { "name": "_deadline", "type": "uint256" }, - { "name": "_v", "type": "uint8" }, - { "name": "_r", "type": "bytes32" }, - { "name": "_s", "type": "bytes32" } - ], - "outputs": [{ "name": "", "type": "bool" }] - }, - { - "stateMutability": "view", - "type": "function", - "name": "last_price", - "inputs": [], - "outputs": [{ "name": "", "type": "uint256" }] - }, - { - "stateMutability": "view", - "type": "function", - "name": "ema_price", - "inputs": [], - "outputs": [{ "name": "", "type": "uint256" }] - }, - { - "stateMutability": "view", - "type": "function", - "name": "get_balances", - "inputs": [], - "outputs": [{ "name": "", "type": "uint256[2]" }] - }, - { - "stateMutability": "view", - "type": "function", - "name": "admin_fee", - "inputs": [], - "outputs": [{ "name": "", "type": "uint256" }] - }, - { - "stateMutability": "view", - "type": "function", - "name": "A", - "inputs": [], - "outputs": [{ "name": "", "type": "uint256" }] - }, - { - "stateMutability": "view", - "type": "function", - "name": "A_precise", - "inputs": [], - "outputs": [{ "name": "", "type": "uint256" }] - }, - { - "stateMutability": "view", - "type": "function", - "name": "get_p", - "inputs": [], - "outputs": [{ "name": "", "type": "uint256" }] - }, - { - "stateMutability": "view", - "type": "function", - "name": "price_oracle", - "inputs": [], - "outputs": [{ "name": "", "type": "uint256" }] - }, - { - "stateMutability": "view", - "type": "function", - "name": "get_virtual_price", - "inputs": [], - "outputs": [{ "name": "", "type": "uint256" }] - }, - { - "stateMutability": "view", - "type": "function", - "name": "calc_token_amount", - "inputs": [ - { "name": "_amounts", "type": "uint256[2]" }, - { "name": "_is_deposit", "type": "bool" } - ], - "outputs": [{ "name": "", "type": "uint256" }] - }, - { - "stateMutability": "payable", - "type": "function", - "name": "add_liquidity", - "inputs": [ - { "name": "_amounts", "type": "uint256[2]" }, - { "name": "_min_mint_amount", "type": "uint256" } - ], - "outputs": [{ "name": "", "type": "uint256" }] - }, - { - "stateMutability": "payable", - "type": "function", - "name": "add_liquidity", - "inputs": [ - { "name": "_amounts", "type": "uint256[2]" }, - { "name": "_min_mint_amount", "type": "uint256" }, - { "name": "_receiver", "type": "address" } - ], - "outputs": [{ "name": "", "type": "uint256" }] - }, - { - "stateMutability": "view", - "type": "function", - "name": "get_dy", - "inputs": [ - { "name": "i", "type": "int128" }, - { "name": "j", "type": "int128" }, - { "name": "dx", "type": "uint256" } - ], - "outputs": [{ "name": "", "type": "uint256" }] - }, - { - "stateMutability": "payable", - "type": "function", - "name": "exchange", - "inputs": [ - { "name": "i", "type": "int128" }, - { "name": "j", "type": "int128" }, - { "name": "_dx", "type": "uint256" }, - { "name": "_min_dy", "type": "uint256" } - ], - "outputs": [{ "name": "", "type": "uint256" }] - }, - { - "stateMutability": "payable", - "type": "function", - "name": "exchange", - "inputs": [ - { "name": "i", "type": "int128" }, - { "name": "j", "type": "int128" }, - { "name": "_dx", "type": "uint256" }, - { "name": "_min_dy", "type": "uint256" }, - { "name": "_receiver", "type": "address" } - ], - "outputs": [{ "name": "", "type": "uint256" }] - }, - { - "stateMutability": "nonpayable", - "type": "function", - "name": "remove_liquidity", - "inputs": [ - { "name": "_burn_amount", "type": "uint256" }, - { "name": "_min_amounts", "type": "uint256[2]" } - ], - "outputs": [{ "name": "", "type": "uint256[2]" }] - }, - { - "stateMutability": "nonpayable", - "type": "function", - "name": "remove_liquidity", - "inputs": [ - { "name": "_burn_amount", "type": "uint256" }, - { "name": "_min_amounts", "type": "uint256[2]" }, - { "name": "_receiver", "type": "address" } - ], - "outputs": [{ "name": "", "type": "uint256[2]" }] - }, - { - "stateMutability": "nonpayable", - "type": "function", - "name": "remove_liquidity_imbalance", - "inputs": [ - { "name": "_amounts", "type": "uint256[2]" }, - { "name": "_max_burn_amount", "type": "uint256" } - ], - "outputs": [{ "name": "", "type": "uint256" }] - }, - { - "stateMutability": "nonpayable", - "type": "function", - "name": "remove_liquidity_imbalance", - "inputs": [ - { "name": "_amounts", "type": "uint256[2]" }, - { "name": "_max_burn_amount", "type": "uint256" }, - { "name": "_receiver", "type": "address" } - ], - "outputs": [{ "name": "", "type": "uint256" }] - }, - { - "stateMutability": "view", - "type": "function", - "name": "calc_withdraw_one_coin", - "inputs": [ - { "name": "_burn_amount", "type": "uint256" }, - { "name": "i", "type": "int128" } - ], - "outputs": [{ "name": "", "type": "uint256" }] - }, - { - "stateMutability": "nonpayable", - "type": "function", - "name": "remove_liquidity_one_coin", - "inputs": [ - { "name": "_burn_amount", "type": "uint256" }, - { "name": "i", "type": "int128" }, - { "name": "_min_received", "type": "uint256" } - ], - "outputs": [{ "name": "", "type": "uint256" }] - }, - { - "stateMutability": "nonpayable", - "type": "function", - "name": "remove_liquidity_one_coin", - "inputs": [ - { "name": "_burn_amount", "type": "uint256" }, - { "name": "i", "type": "int128" }, - { "name": "_min_received", "type": "uint256" }, - { "name": "_receiver", "type": "address" } - ], - "outputs": [{ "name": "", "type": "uint256" }] - }, - { - "stateMutability": "nonpayable", - "type": "function", - "name": "ramp_A", - "inputs": [ - { "name": "_future_A", "type": "uint256" }, - { "name": "_future_time", "type": "uint256" } - ], - "outputs": [] - }, - { - "stateMutability": "nonpayable", - "type": "function", - "name": "stop_ramp_A", - "inputs": [], - "outputs": [] - }, - { - "stateMutability": "view", - "type": "function", - "name": "admin_balances", - "inputs": [{ "name": "i", "type": "uint256" }], - "outputs": [{ "name": "", "type": "uint256" }] - }, - { - "stateMutability": "nonpayable", - "type": "function", - "name": "withdraw_admin_fees", - "inputs": [], - "outputs": [] - }, - { - "stateMutability": "nonpayable", - "type": "function", - "name": "commit_new_fee", - "inputs": [{ "name": "_new_fee", "type": "uint256" }], - "outputs": [] - }, - { - "stateMutability": "nonpayable", - "type": "function", - "name": "apply_new_fee", - "inputs": [], - "outputs": [] - }, - { - "stateMutability": "nonpayable", - "type": "function", - "name": "set_ma_exp_time", - "inputs": [{ "name": "_ma_exp_time", "type": "uint256" }], - "outputs": [] - }, - { - "stateMutability": "view", - "type": "function", - "name": "version", - "inputs": [], - "outputs": [{ "name": "", "type": "string" }] - }, - { - "stateMutability": "view", - "type": "function", - "name": "coins", - "inputs": [{ "name": "arg0", "type": "uint256" }], - "outputs": [{ "name": "", "type": "address" }] - }, - { - "stateMutability": "view", - "type": "function", - "name": "balances", - "inputs": [{ "name": "arg0", "type": "uint256" }], - "outputs": [{ "name": "", "type": "uint256" }] - }, - { - "stateMutability": "view", - "type": "function", - "name": "fee", - "inputs": [], - "outputs": [{ "name": "", "type": "uint256" }] - }, - { - "stateMutability": "view", - "type": "function", - "name": "future_fee", - "inputs": [], - "outputs": [{ "name": "", "type": "uint256" }] - }, - { - "stateMutability": "view", - "type": "function", - "name": "admin_action_deadline", - "inputs": [], - "outputs": [{ "name": "", "type": "uint256" }] - }, - { - "stateMutability": "view", - "type": "function", - "name": "initial_A", - "inputs": [], - "outputs": [{ "name": "", "type": "uint256" }] - }, - { - "stateMutability": "view", - "type": "function", - "name": "future_A", - "inputs": [], - "outputs": [{ "name": "", "type": "uint256" }] - }, - { - "stateMutability": "view", - "type": "function", - "name": "initial_A_time", - "inputs": [], - "outputs": [{ "name": "", "type": "uint256" }] - }, - { - "stateMutability": "view", - "type": "function", - "name": "future_A_time", - "inputs": [], - "outputs": [{ "name": "", "type": "uint256" }] - }, - { - "stateMutability": "view", - "type": "function", - "name": "name", - "inputs": [], - "outputs": [{ "name": "", "type": "string" }] - }, - { - "stateMutability": "view", - "type": "function", - "name": "symbol", - "inputs": [], - "outputs": [{ "name": "", "type": "string" }] - }, - { - "stateMutability": "view", - "type": "function", - "name": "balanceOf", - "inputs": [{ "name": "arg0", "type": "address" }], - "outputs": [{ "name": "", "type": "uint256" }] - }, - { - "stateMutability": "view", - "type": "function", - "name": "allowance", - "inputs": [ - { "name": "arg0", "type": "address" }, - { "name": "arg1", "type": "address" } - ], - "outputs": [{ "name": "", "type": "uint256" }] - }, - { - "stateMutability": "view", - "type": "function", - "name": "totalSupply", - "inputs": [], - "outputs": [{ "name": "", "type": "uint256" }] - }, - { - "stateMutability": "view", - "type": "function", - "name": "DOMAIN_SEPARATOR", - "inputs": [], - "outputs": [{ "name": "", "type": "bytes32" }] - }, - { - "stateMutability": "view", - "type": "function", - "name": "nonces", - "inputs": [{ "name": "arg0", "type": "address" }], - "outputs": [{ "name": "", "type": "uint256" }] - }, - { - "stateMutability": "view", - "type": "function", - "name": "ma_exp_time", - "inputs": [], - "outputs": [{ "name": "", "type": "uint256" }] - }, - { - "stateMutability": "view", - "type": "function", - "name": "ma_last_time", - "inputs": [], - "outputs": [{ "name": "", "type": "uint256" }] - } -] diff --git a/contracts/test/abi/ousdMetapool.json b/contracts/test/abi/ousdMetapool.json deleted file mode 100644 index 2c85f9e279..0000000000 --- a/contracts/test/abi/ousdMetapool.json +++ /dev/null @@ -1,628 +0,0 @@ -[ - { - "anonymous": false, - "inputs": [ - { "indexed": true, "name": "sender", "type": "address" }, - { "indexed": true, "name": "receiver", "type": "address" }, - { "indexed": false, "name": "value", "type": "uint256" } - ], - "name": "Transfer", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { "indexed": true, "name": "owner", "type": "address" }, - { "indexed": true, "name": "spender", "type": "address" }, - { "indexed": false, "name": "value", "type": "uint256" } - ], - "name": "Approval", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { "indexed": true, "name": "buyer", "type": "address" }, - { "indexed": false, "name": "sold_id", "type": "int128" }, - { "indexed": false, "name": "tokens_sold", "type": "uint256" }, - { "indexed": false, "name": "bought_id", "type": "int128" }, - { "indexed": false, "name": "tokens_bought", "type": "uint256" } - ], - "name": "TokenExchange", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { "indexed": true, "name": "buyer", "type": "address" }, - { "indexed": false, "name": "sold_id", "type": "int128" }, - { "indexed": false, "name": "tokens_sold", "type": "uint256" }, - { "indexed": false, "name": "bought_id", "type": "int128" }, - { "indexed": false, "name": "tokens_bought", "type": "uint256" } - ], - "name": "TokenExchangeUnderlying", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { "indexed": true, "name": "provider", "type": "address" }, - { "indexed": false, "name": "token_amounts", "type": "uint256[2]" }, - { "indexed": false, "name": "fees", "type": "uint256[2]" }, - { "indexed": false, "name": "invariant", "type": "uint256" }, - { "indexed": false, "name": "token_supply", "type": "uint256" } - ], - "name": "AddLiquidity", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { "indexed": true, "name": "provider", "type": "address" }, - { "indexed": false, "name": "token_amounts", "type": "uint256[2]" }, - { "indexed": false, "name": "fees", "type": "uint256[2]" }, - { "indexed": false, "name": "token_supply", "type": "uint256" } - ], - "name": "RemoveLiquidity", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { "indexed": true, "name": "provider", "type": "address" }, - { "indexed": false, "name": "token_amount", "type": "uint256" }, - { "indexed": false, "name": "coin_amount", "type": "uint256" }, - { "indexed": false, "name": "token_supply", "type": "uint256" } - ], - "name": "RemoveLiquidityOne", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { "indexed": true, "name": "provider", "type": "address" }, - { "indexed": false, "name": "token_amounts", "type": "uint256[2]" }, - { "indexed": false, "name": "fees", "type": "uint256[2]" }, - { "indexed": false, "name": "invariant", "type": "uint256" }, - { "indexed": false, "name": "token_supply", "type": "uint256" } - ], - "name": "RemoveLiquidityImbalance", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { "indexed": true, "name": "deadline", "type": "uint256" }, - { "indexed": true, "name": "admin", "type": "address" } - ], - "name": "CommitNewAdmin", - "type": "event" - }, - { - "anonymous": false, - "inputs": [{ "indexed": true, "name": "admin", "type": "address" }], - "name": "NewAdmin", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { "indexed": true, "name": "deadline", "type": "uint256" }, - { "indexed": false, "name": "fee", "type": "uint256" }, - { "indexed": false, "name": "admin_fee", "type": "uint256" } - ], - "name": "CommitNewFee", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { "indexed": false, "name": "fee", "type": "uint256" }, - { "indexed": false, "name": "admin_fee", "type": "uint256" } - ], - "name": "NewFee", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { "indexed": false, "name": "old_A", "type": "uint256" }, - { "indexed": false, "name": "new_A", "type": "uint256" }, - { "indexed": false, "name": "initial_time", "type": "uint256" }, - { "indexed": false, "name": "future_time", "type": "uint256" } - ], - "name": "RampA", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { "indexed": false, "name": "A", "type": "uint256" }, - { "indexed": false, "name": "t", "type": "uint256" } - ], - "name": "StopRampA", - "type": "event" - }, - { - "inputs": [], - "outputs": [], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "gas": 470049, - "inputs": [ - { "name": "_name", "type": "string" }, - { "name": "_symbol", "type": "string" }, - { "name": "_coin", "type": "address" }, - { "name": "_decimals", "type": "uint256" }, - { "name": "_A", "type": "uint256" }, - { "name": "_fee", "type": "uint256" }, - { "name": "_admin", "type": "address" } - ], - "name": "initialize", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "decimals", - "outputs": [{ "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "gas": 75402, - "inputs": [ - { "name": "_to", "type": "address" }, - { "name": "_value", "type": "uint256" } - ], - "name": "transfer", - "outputs": [{ "name": "", "type": "bool" }], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "gas": 112037, - "inputs": [ - { "name": "_from", "type": "address" }, - { "name": "_to", "type": "address" }, - { "name": "_value", "type": "uint256" } - ], - "name": "transferFrom", - "outputs": [{ "name": "", "type": "bool" }], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "gas": 37854, - "inputs": [ - { "name": "_spender", "type": "address" }, - { "name": "_value", "type": "uint256" } - ], - "name": "approve", - "outputs": [{ "name": "", "type": "bool" }], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "get_previous_balances", - "outputs": [{ "name": "", "type": "uint256[2]" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "get_balances", - "outputs": [{ "name": "", "type": "uint256[2]" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "name": "_first_balances", "type": "uint256[2]" }, - { "name": "_last_balances", "type": "uint256[2]" }, - { "name": "_time_elapsed", "type": "uint256" } - ], - "name": "get_twap_balances", - "outputs": [{ "name": "", "type": "uint256[2]" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "get_price_cumulative_last", - "outputs": [{ "name": "", "type": "uint256[2]" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "admin_fee", - "outputs": [{ "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "A", - "outputs": [{ "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "A_precise", - "outputs": [{ "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "get_virtual_price", - "outputs": [{ "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "name": "_amounts", "type": "uint256[2]" }, - { "name": "_is_deposit", "type": "bool" } - ], - "name": "calc_token_amount", - "outputs": [{ "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "name": "_amounts", "type": "uint256[2]" }, - { "name": "_is_deposit", "type": "bool" }, - { "name": "_previous", "type": "bool" } - ], - "name": "calc_token_amount", - "outputs": [{ "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "name": "_amounts", "type": "uint256[2]" }, - { "name": "_min_mint_amount", "type": "uint256" } - ], - "name": "add_liquidity", - "outputs": [{ "name": "", "type": "uint256" }], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "name": "_amounts", "type": "uint256[2]" }, - { "name": "_min_mint_amount", "type": "uint256" }, - { "name": "_receiver", "type": "address" } - ], - "name": "add_liquidity", - "outputs": [{ "name": "", "type": "uint256" }], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "name": "i", "type": "int128" }, - { "name": "j", "type": "int128" }, - { "name": "dx", "type": "uint256" } - ], - "name": "get_dy", - "outputs": [{ "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "name": "i", "type": "int128" }, - { "name": "j", "type": "int128" }, - { "name": "dx", "type": "uint256" }, - { "name": "_balances", "type": "uint256[2]" } - ], - "name": "get_dy", - "outputs": [{ "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "name": "i", "type": "int128" }, - { "name": "j", "type": "int128" }, - { "name": "dx", "type": "uint256" } - ], - "name": "get_dy_underlying", - "outputs": [{ "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "name": "i", "type": "int128" }, - { "name": "j", "type": "int128" }, - { "name": "dx", "type": "uint256" }, - { "name": "_balances", "type": "uint256[2]" } - ], - "name": "get_dy_underlying", - "outputs": [{ "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "name": "i", "type": "int128" }, - { "name": "j", "type": "int128" }, - { "name": "dx", "type": "uint256" }, - { "name": "min_dy", "type": "uint256" } - ], - "name": "exchange", - "outputs": [{ "name": "", "type": "uint256" }], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "name": "i", "type": "int128" }, - { "name": "j", "type": "int128" }, - { "name": "dx", "type": "uint256" }, - { "name": "min_dy", "type": "uint256" }, - { "name": "_receiver", "type": "address" } - ], - "name": "exchange", - "outputs": [{ "name": "", "type": "uint256" }], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "name": "i", "type": "int128" }, - { "name": "j", "type": "int128" }, - { "name": "dx", "type": "uint256" }, - { "name": "min_dy", "type": "uint256" } - ], - "name": "exchange_underlying", - "outputs": [{ "name": "", "type": "uint256" }], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "name": "i", "type": "int128" }, - { "name": "j", "type": "int128" }, - { "name": "dx", "type": "uint256" }, - { "name": "min_dy", "type": "uint256" }, - { "name": "_receiver", "type": "address" } - ], - "name": "exchange_underlying", - "outputs": [{ "name": "", "type": "uint256" }], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "name": "_burn_amount", "type": "uint256" }, - { "name": "_min_amounts", "type": "uint256[2]" } - ], - "name": "remove_liquidity", - "outputs": [{ "name": "", "type": "uint256[2]" }], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "name": "_burn_amount", "type": "uint256" }, - { "name": "_min_amounts", "type": "uint256[2]" }, - { "name": "_receiver", "type": "address" } - ], - "name": "remove_liquidity", - "outputs": [{ "name": "", "type": "uint256[2]" }], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "name": "_amounts", "type": "uint256[2]" }, - { "name": "_max_burn_amount", "type": "uint256" } - ], - "name": "remove_liquidity_imbalance", - "outputs": [{ "name": "", "type": "uint256" }], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "name": "_amounts", "type": "uint256[2]" }, - { "name": "_max_burn_amount", "type": "uint256" }, - { "name": "_receiver", "type": "address" } - ], - "name": "remove_liquidity_imbalance", - "outputs": [{ "name": "", "type": "uint256" }], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "name": "_burn_amount", "type": "uint256" }, - { "name": "i", "type": "int128" } - ], - "name": "calc_withdraw_one_coin", - "outputs": [{ "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "name": "_burn_amount", "type": "uint256" }, - { "name": "i", "type": "int128" }, - { "name": "_previous", "type": "bool" } - ], - "name": "calc_withdraw_one_coin", - "outputs": [{ "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "name": "_burn_amount", "type": "uint256" }, - { "name": "i", "type": "int128" }, - { "name": "_min_received", "type": "uint256" } - ], - "name": "remove_liquidity_one_coin", - "outputs": [{ "name": "", "type": "uint256" }], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "name": "_burn_amount", "type": "uint256" }, - { "name": "i", "type": "int128" }, - { "name": "_min_received", "type": "uint256" }, - { "name": "_receiver", "type": "address" } - ], - "name": "remove_liquidity_one_coin", - "outputs": [{ "name": "", "type": "uint256" }], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "gas": 152464, - "inputs": [ - { "name": "_future_A", "type": "uint256" }, - { "name": "_future_time", "type": "uint256" } - ], - "name": "ramp_A", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "gas": 149225, - "inputs": [], - "name": "stop_ramp_A", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [{ "name": "i", "type": "uint256" }], - "name": "admin_balances", - "outputs": [{ "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "gas": 11347, - "inputs": [], - "name": "withdraw_admin_fees", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "admin", - "outputs": [{ "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [{ "name": "arg0", "type": "uint256" }], - "name": "coins", - "outputs": [{ "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [{ "name": "arg0", "type": "uint256" }], - "name": "balances", - "outputs": [{ "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "fee", - "outputs": [{ "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "block_timestamp_last", - "outputs": [{ "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "initial_A", - "outputs": [{ "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "future_A", - "outputs": [{ "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "initial_A_time", - "outputs": [{ "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "future_A_time", - "outputs": [{ "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "name", - "outputs": [{ "name": "", "type": "string" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "symbol", - "outputs": [{ "name": "", "type": "string" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [{ "name": "arg0", "type": "address" }], - "name": "balanceOf", - "outputs": [{ "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "name": "arg0", "type": "address" }, - { "name": "arg1", "type": "address" } - ], - "name": "allowance", - "outputs": [{ "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "totalSupply", - "outputs": [{ "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - } -] diff --git a/contracts/test/abi/sUSDS.json b/contracts/test/abi/sUSDS.json deleted file mode 100644 index 32355f936e..0000000000 --- a/contracts/test/abi/sUSDS.json +++ /dev/null @@ -1,477 +0,0 @@ -[ - { - "inputs": [ - { "internalType": "address", "name": "_daiJoin", "type": "address" }, - { "internalType": "address", "name": "_pot", "type": "address" } - ], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "value", - "type": "uint256" - } - ], - "name": "Approval", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "sender", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "assets", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "shares", - "type": "uint256" - } - ], - "name": "Deposit", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "value", - "type": "uint256" - } - ], - "name": "Transfer", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "sender", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "receiver", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "assets", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "shares", - "type": "uint256" - } - ], - "name": "Withdraw", - "type": "event" - }, - { - "inputs": [], - "name": "DOMAIN_SEPARATOR", - "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "PERMIT_TYPEHASH", - "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "", "type": "address" }, - { "internalType": "address", "name": "", "type": "address" } - ], - "name": "allowance", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "spender", "type": "address" }, - { "internalType": "uint256", "name": "value", "type": "uint256" } - ], - "name": "approve", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "asset", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [{ "internalType": "address", "name": "", "type": "address" }], - "name": "balanceOf", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "shares", "type": "uint256" } - ], - "name": "convertToAssets", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "assets", "type": "uint256" } - ], - "name": "convertToShares", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "dai", - "outputs": [ - { "internalType": "contract DaiLike", "name": "", "type": "address" } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "daiJoin", - "outputs": [ - { "internalType": "contract DaiJoinLike", "name": "", "type": "address" } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "decimals", - "outputs": [{ "internalType": "uint8", "name": "", "type": "uint8" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "spender", "type": "address" }, - { - "internalType": "uint256", - "name": "subtractedValue", - "type": "uint256" - } - ], - "name": "decreaseAllowance", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "deploymentChainId", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "assets", "type": "uint256" }, - { "internalType": "address", "name": "receiver", "type": "address" } - ], - "name": "deposit", - "outputs": [ - { "internalType": "uint256", "name": "shares", "type": "uint256" } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "spender", "type": "address" }, - { "internalType": "uint256", "name": "addedValue", "type": "uint256" } - ], - "name": "increaseAllowance", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [{ "internalType": "address", "name": "", "type": "address" }], - "name": "maxDeposit", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [{ "internalType": "address", "name": "", "type": "address" }], - "name": "maxMint", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "owner", "type": "address" } - ], - "name": "maxRedeem", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "owner", "type": "address" } - ], - "name": "maxWithdraw", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "shares", "type": "uint256" }, - { "internalType": "address", "name": "receiver", "type": "address" } - ], - "name": "mint", - "outputs": [ - { "internalType": "uint256", "name": "assets", "type": "uint256" } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "name", - "outputs": [{ "internalType": "string", "name": "", "type": "string" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [{ "internalType": "address", "name": "", "type": "address" }], - "name": "nonces", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "owner", "type": "address" }, - { "internalType": "address", "name": "spender", "type": "address" }, - { "internalType": "uint256", "name": "value", "type": "uint256" }, - { "internalType": "uint256", "name": "deadline", "type": "uint256" }, - { "internalType": "bytes", "name": "signature", "type": "bytes" } - ], - "name": "permit", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "owner", "type": "address" }, - { "internalType": "address", "name": "spender", "type": "address" }, - { "internalType": "uint256", "name": "value", "type": "uint256" }, - { "internalType": "uint256", "name": "deadline", "type": "uint256" }, - { "internalType": "uint8", "name": "v", "type": "uint8" }, - { "internalType": "bytes32", "name": "r", "type": "bytes32" }, - { "internalType": "bytes32", "name": "s", "type": "bytes32" } - ], - "name": "permit", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "pot", - "outputs": [ - { "internalType": "contract PotLike", "name": "", "type": "address" } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "assets", "type": "uint256" } - ], - "name": "previewDeposit", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "shares", "type": "uint256" } - ], - "name": "previewMint", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "shares", "type": "uint256" } - ], - "name": "previewRedeem", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "assets", "type": "uint256" } - ], - "name": "previewWithdraw", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "shares", "type": "uint256" }, - { "internalType": "address", "name": "receiver", "type": "address" }, - { "internalType": "address", "name": "owner", "type": "address" } - ], - "name": "redeem", - "outputs": [ - { "internalType": "uint256", "name": "assets", "type": "uint256" } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "symbol", - "outputs": [{ "internalType": "string", "name": "", "type": "string" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "totalAssets", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "totalSupply", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "to", "type": "address" }, - { "internalType": "uint256", "name": "value", "type": "uint256" } - ], - "name": "transfer", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "from", "type": "address" }, - { "internalType": "address", "name": "to", "type": "address" }, - { "internalType": "uint256", "name": "value", "type": "uint256" } - ], - "name": "transferFrom", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "vat", - "outputs": [ - { "internalType": "contract VatLike", "name": "", "type": "address" } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "version", - "outputs": [{ "internalType": "string", "name": "", "type": "string" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "assets", "type": "uint256" }, - { "internalType": "address", "name": "receiver", "type": "address" }, - { "internalType": "address", "name": "owner", "type": "address" } - ], - "name": "withdraw", - "outputs": [ - { "internalType": "uint256", "name": "shares", "type": "uint256" } - ], - "stateMutability": "nonpayable", - "type": "function" - } -] diff --git a/contracts/test/abi/sfrxETH.json b/contracts/test/abi/sfrxETH.json deleted file mode 100644 index 1d237e59df..0000000000 --- a/contracts/test/abi/sfrxETH.json +++ /dev/null @@ -1 +0,0 @@ -[{"inputs":[{"internalType":"contract ERC20","name":"_underlying","type":"address"},{"internalType":"uint32","name":"_rewardsCycleLength","type":"uint32"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"SyncError","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"caller","type":"address"},{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":false,"internalType":"uint256","name":"assets","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"shares","type":"uint256"}],"name":"Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint32","name":"cycleEnd","type":"uint32"},{"indexed":false,"internalType":"uint256","name":"rewardAmount","type":"uint256"}],"name":"NewRewardsCycle","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"caller","type":"address"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":false,"internalType":"uint256","name":"assets","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"shares","type":"uint256"}],"name":"Withdraw","type":"event"},{"inputs":[],"name":"DOMAIN_SEPARATOR","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"asset","outputs":[{"internalType":"contract ERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"name":"convertToAssets","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"name":"convertToShares","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"assets","type":"uint256"},{"internalType":"address","name":"receiver","type":"address"}],"name":"deposit","outputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"assets","type":"uint256"},{"internalType":"address","name":"receiver","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bool","name":"approveMax","type":"bool"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"depositWithSignature","outputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"lastRewardAmount","outputs":[{"internalType":"uint192","name":"","type":"uint192"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"lastSync","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"maxDeposit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"maxMint","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"maxRedeem","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"maxWithdraw","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"shares","type":"uint256"},{"internalType":"address","name":"receiver","type":"address"}],"name":"mint","outputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"nonces","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"permit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"name":"previewDeposit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"name":"previewMint","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"name":"previewRedeem","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"name":"previewWithdraw","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pricePerShare","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"shares","type":"uint256"},{"internalType":"address","name":"receiver","type":"address"},{"internalType":"address","name":"owner","type":"address"}],"name":"redeem","outputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"rewardsCycleEnd","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"rewardsCycleLength","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"syncRewards","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"totalAssets","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"assets","type":"uint256"},{"internalType":"address","name":"receiver","type":"address"},{"internalType":"address","name":"owner","type":"address"}],"name":"withdraw","outputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/contracts/test/abi/threepoolLP.json b/contracts/test/abi/threepoolLP.json deleted file mode 100644 index 807091021d..0000000000 --- a/contracts/test/abi/threepoolLP.json +++ /dev/null @@ -1 +0,0 @@ -[{"anonymous": false, "inputs": [{"indexed": true, "name": "_from", "type": "address"}, {"indexed": true, "name": "_to", "type": "address"}, {"indexed": false, "name": "_value", "type": "uint256"}], "name": "Transfer", "type": "event"}, {"anonymous": false, "inputs": [{"indexed": true, "name": "_owner", "type": "address"}, {"indexed": true, "name": "_spender", "type": "address"}, {"indexed": false, "name": "_value", "type": "uint256"}], "name": "Approval", "type": "event"}, {"inputs": [{"name": "_name", "type": "string"}, {"name": "_symbol", "type": "string"}, {"name": "_decimals", "type": "uint256"}, {"name": "_supply", "type": "uint256"}], "outputs": [], "stateMutability": "nonpayable", "type": "constructor"}, {"gas": 36247, "inputs": [{"name": "_minter", "type": "address"}], "name": "set_minter", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"gas": 178069, "inputs": [{"name": "_name", "type": "string"}, {"name": "_symbol", "type": "string"}], "name": "set_name", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"gas": 1211, "inputs": [], "name": "totalSupply", "outputs": [{"name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"}, {"gas": 1549, "inputs": [{"name": "_owner", "type": "address"}, {"name": "_spender", "type": "address"}], "name": "allowance", "outputs": [{"name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"}, {"gas": 74832, "inputs": [{"name": "_to", "type": "address"}, {"name": "_value", "type": "uint256"}], "name": "transfer", "outputs": [{"name": "", "type": "bool"}], "stateMutability": "nonpayable", "type": "function"}, {"gas": 111983, "inputs": [{"name": "_from", "type": "address"}, {"name": "_to", "type": "address"}, {"name": "_value", "type": "uint256"}], "name": "transferFrom", "outputs": [{"name": "", "type": "bool"}], "stateMutability": "nonpayable", "type": "function"}, {"gas": 39078, "inputs": [{"name": "_spender", "type": "address"}, {"name": "_value", "type": "uint256"}], "name": "approve", "outputs": [{"name": "", "type": "bool"}], "stateMutability": "nonpayable", "type": "function"}, {"gas": 75808, "inputs": [{"name": "_to", "type": "address"}, {"name": "_value", "type": "uint256"}], "name": "mint", "outputs": [{"name": "", "type": "bool"}], "stateMutability": "nonpayable", "type": "function"}, {"gas": 75826, "inputs": [{"name": "_to", "type": "address"}, {"name": "_value", "type": "uint256"}], "name": "burnFrom", "outputs": [{"name": "", "type": "bool"}], "stateMutability": "nonpayable", "type": "function"}, {"gas": 7823, "inputs": [], "name": "name", "outputs": [{"name": "", "type": "string"}], "stateMutability": "view", "type": "function"}, {"gas": 6876, "inputs": [], "name": "symbol", "outputs": [{"name": "", "type": "string"}], "stateMutability": "view", "type": "function"}, {"gas": 1481, "inputs": [], "name": "decimals", "outputs": [{"name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"}, {"gas": 1665, "inputs": [{"name": "arg0", "type": "address"}], "name": "balanceOf", "outputs": [{"name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"}] \ No newline at end of file diff --git a/contracts/test/abi/threepoolSwap.json b/contracts/test/abi/threepoolSwap.json deleted file mode 100644 index ae01ae0e53..0000000000 --- a/contracts/test/abi/threepoolSwap.json +++ /dev/null @@ -1 +0,0 @@ -[{"anonymous": false, "inputs": [{"indexed": true, "name": "buyer", "type": "address"}, {"indexed": false, "name": "sold_id", "type": "int128"}, {"indexed": false, "name": "tokens_sold", "type": "uint256"}, {"indexed": false, "name": "bought_id", "type": "int128"}, {"indexed": false, "name": "tokens_bought", "type": "uint256"}], "name": "TokenExchange", "type": "event"}, {"anonymous": false, "inputs": [{"indexed": true, "name": "provider", "type": "address"}, {"indexed": false, "name": "token_amounts", "type": "uint256[3]"}, {"indexed": false, "name": "fees", "type": "uint256[3]"}, {"indexed": false, "name": "invariant", "type": "uint256"}, {"indexed": false, "name": "token_supply", "type": "uint256"}], "name": "AddLiquidity", "type": "event"}, {"anonymous": false, "inputs": [{"indexed": true, "name": "provider", "type": "address"}, {"indexed": false, "name": "token_amounts", "type": "uint256[3]"}, {"indexed": false, "name": "fees", "type": "uint256[3]"}, {"indexed": false, "name": "token_supply", "type": "uint256"}], "name": "RemoveLiquidity", "type": "event"}, {"anonymous": false, "inputs": [{"indexed": true, "name": "provider", "type": "address"}, {"indexed": false, "name": "token_amount", "type": "uint256"}, {"indexed": false, "name": "coin_amount", "type": "uint256"}], "name": "RemoveLiquidityOne", "type": "event"}, {"anonymous": false, "inputs": [{"indexed": true, "name": "provider", "type": "address"}, {"indexed": false, "name": "token_amounts", "type": "uint256[3]"}, {"indexed": false, "name": "fees", "type": "uint256[3]"}, {"indexed": false, "name": "invariant", "type": "uint256"}, {"indexed": false, "name": "token_supply", "type": "uint256"}], "name": "RemoveLiquidityImbalance", "type": "event"}, {"anonymous": false, "inputs": [{"indexed": true, "name": "deadline", "type": "uint256"}, {"indexed": true, "name": "admin", "type": "address"}], "name": "CommitNewAdmin", "type": "event"}, {"anonymous": false, "inputs": [{"indexed": true, "name": "admin", "type": "address"}], "name": "NewAdmin", "type": "event"}, {"anonymous": false, "inputs": [{"indexed": true, "name": "deadline", "type": "uint256"}, {"indexed": false, "name": "fee", "type": "uint256"}, {"indexed": false, "name": "admin_fee", "type": "uint256"}], "name": "CommitNewFee", "type": "event"}, {"anonymous": false, "inputs": [{"indexed": false, "name": "fee", "type": "uint256"}, {"indexed": false, "name": "admin_fee", "type": "uint256"}], "name": "NewFee", "type": "event"}, {"anonymous": false, "inputs": [{"indexed": false, "name": "old_A", "type": "uint256"}, {"indexed": false, "name": "new_A", "type": "uint256"}, {"indexed": false, "name": "initial_time", "type": "uint256"}, {"indexed": false, "name": "future_time", "type": "uint256"}], "name": "RampA", "type": "event"}, {"anonymous": false, "inputs": [{"indexed": false, "name": "A", "type": "uint256"}, {"indexed": false, "name": "t", "type": "uint256"}], "name": "StopRampA", "type": "event"}, {"inputs": [{"name": "_owner", "type": "address"}, {"name": "_coins", "type": "address[3]"}, {"name": "_pool_token", "type": "address"}, {"name": "_A", "type": "uint256"}, {"name": "_fee", "type": "uint256"}, {"name": "_admin_fee", "type": "uint256"}], "outputs": [], "stateMutability": "nonpayable", "type": "constructor"}, {"gas": 5227, "inputs": [], "name": "A", "outputs": [{"name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"}, {"gas": 1133537, "inputs": [], "name": "get_virtual_price", "outputs": [{"name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"}, {"gas": 4508776, "inputs": [{"name": "amounts", "type": "uint256[3]"}, {"name": "deposit", "type": "bool"}], "name": "calc_token_amount", "outputs": [{"name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"}, {"gas": 6954858, "inputs": [{"name": "amounts", "type": "uint256[3]"}, {"name": "min_mint_amount", "type": "uint256"}], "name": "add_liquidity", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"gas": 2673791, "inputs": [{"name": "i", "type": "int128"}, {"name": "j", "type": "int128"}, {"name": "dx", "type": "uint256"}], "name": "get_dy", "outputs": [{"name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"}, {"gas": 2673474, "inputs": [{"name": "i", "type": "int128"}, {"name": "j", "type": "int128"}, {"name": "dx", "type": "uint256"}], "name": "get_dy_underlying", "outputs": [{"name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"}, {"gas": 2818066, "inputs": [{"name": "i", "type": "int128"}, {"name": "j", "type": "int128"}, {"name": "dx", "type": "uint256"}, {"name": "min_dy", "type": "uint256"}], "name": "exchange", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"gas": 192846, "inputs": [{"name": "_amount", "type": "uint256"}, {"name": "min_amounts", "type": "uint256[3]"}], "name": "remove_liquidity", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"gas": 6951851, "inputs": [{"name": "amounts", "type": "uint256[3]"}, {"name": "max_burn_amount", "type": "uint256"}], "name": "remove_liquidity_imbalance", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"gas": 1102, "inputs": [{"name": "_token_amount", "type": "uint256"}, {"name": "i", "type": "int128"}], "name": "calc_withdraw_one_coin", "outputs": [{"name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"}, {"gas": 4025523, "inputs": [{"name": "_token_amount", "type": "uint256"}, {"name": "i", "type": "int128"}, {"name": "min_amount", "type": "uint256"}], "name": "remove_liquidity_one_coin", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"gas": 151919, "inputs": [{"name": "_future_A", "type": "uint256"}, {"name": "_future_time", "type": "uint256"}], "name": "ramp_A", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"gas": 148637, "inputs": [], "name": "stop_ramp_A", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"gas": 110461, "inputs": [{"name": "new_fee", "type": "uint256"}, {"name": "new_admin_fee", "type": "uint256"}], "name": "commit_new_fee", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"gas": 97242, "inputs": [], "name": "apply_new_fee", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"gas": 21895, "inputs": [], "name": "revert_new_parameters", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"gas": 74572, "inputs": [{"name": "_owner", "type": "address"}], "name": "commit_transfer_ownership", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"gas": 60710, "inputs": [], "name": "apply_transfer_ownership", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"gas": 21985, "inputs": [], "name": "revert_transfer_ownership", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"gas": 3481, "inputs": [{"name": "i", "type": "uint256"}], "name": "admin_balances", "outputs": [{"name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"}, {"gas": 21502, "inputs": [], "name": "withdraw_admin_fees", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"gas": 111389, "inputs": [], "name": "donate_admin_fees", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"gas": 37998, "inputs": [], "name": "kill_me", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"gas": 22135, "inputs": [], "name": "unkill_me", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"gas": 2220, "inputs": [{"name": "arg0", "type": "uint256"}], "name": "coins", "outputs": [{"name": "", "type": "address"}], "stateMutability": "view", "type": "function"}, {"gas": 2250, "inputs": [{"name": "arg0", "type": "uint256"}], "name": "balances", "outputs": [{"name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"}, {"gas": 2171, "inputs": [], "name": "fee", "outputs": [{"name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"}, {"gas": 2201, "inputs": [], "name": "admin_fee", "outputs": [{"name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"}, {"gas": 2231, "inputs": [], "name": "owner", "outputs": [{"name": "", "type": "address"}], "stateMutability": "view", "type": "function"}, {"gas": 2261, "inputs": [], "name": "initial_A", "outputs": [{"name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"}, {"gas": 2291, "inputs": [], "name": "future_A", "outputs": [{"name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"}, {"gas": 2321, "inputs": [], "name": "initial_A_time", "outputs": [{"name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"}, {"gas": 2351, "inputs": [], "name": "future_A_time", "outputs": [{"name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"}, {"gas": 2381, "inputs": [], "name": "admin_actions_deadline", "outputs": [{"name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"}, {"gas": 2411, "inputs": [], "name": "transfer_ownership_deadline", "outputs": [{"name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"}, {"gas": 2441, "inputs": [], "name": "future_fee", "outputs": [{"name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"}, {"gas": 2471, "inputs": [], "name": "future_admin_fee", "outputs": [{"name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"}, {"gas": 2501, "inputs": [], "name": "future_owner", "outputs": [{"name": "", "type": "address"}], "stateMutability": "view", "type": "function"}] \ No newline at end of file diff --git a/contracts/test/behaviour/harvest.fork.js b/contracts/test/behaviour/harvest.fork.js deleted file mode 100644 index 2497398d50..0000000000 --- a/contracts/test/behaviour/harvest.fork.js +++ /dev/null @@ -1,93 +0,0 @@ -const { expect } = require("chai"); - -const { isFork, units } = require("../helpers"); -const { setERC20TokenBalance } = require("../_fund"); -const { impersonateAndFund } = require("../../utils/signers"); - -/** - * - * @param {*} context a function that returns: - * - strategy: Strategy to test against - * - harvester: OUSD Harvester or OETH Harvester - * - dripper: Dripper - * - fixture: The actual fixture - * @example - * shouldHarvestRewardTokens(() => ({ - * fixture, - * harvester, - * dripper, - * strategy - * })); - */ -const shouldHarvestRewardTokens = (context) => { - if (!isFork) { - // Only meant to be used on fork - return; - } - - describe("Reward Tokens", () => { - it("Should collect and send reward tokens to harvester", async () => { - const { strategy, harvester } = context(); - - const harvesterSigner = await impersonateAndFund(harvester.address); - - const rewardTokens = await strategy.getRewardTokenAddresses(); - const harvesterBalBefore = []; - - for (let i = 0; i < rewardTokens.length; i++) { - const token = await ethers.getContractAt("IERC20", rewardTokens[i]); - // Send some rewards to the strategy - await setERC20TokenBalance(strategy.address, token, "1000"); - harvesterBalBefore[i] = await token.balanceOf(harvester.address); - rewardTokens[i] = token; - } - - const tx = await strategy.connect(harvesterSigner).collectRewardTokens(); - await expect(tx).to.emit(strategy, "RewardTokenCollected"); - - // Should've transferred everything to Harvester - for (let i = 0; i < rewardTokens.length; i++) { - const token = rewardTokens[i]; - expect(await token.balanceOf(strategy.address)).to.eq("0"); - expect(await token.balanceOf(harvester.address)).to.be.gte( - harvesterBalBefore[i].add(await units("1000", token)) - ); - } - }); - - it("Should swap reward tokens", async () => { - const { strategy, harvester, fixture } = context(); - const { strategist } = fixture; - - const rewardTokens = await strategy.getRewardTokenAddresses(); - - let i = 0; - for (const tokenAddr of rewardTokens) { - const token = await ethers.getContractAt("IERC20", tokenAddr); - // Send some rewards to the strategy - await setERC20TokenBalance(strategy.address, token); - - rewardTokens[i++] = token; - } - - // Trigger the swap - // prettier-ignore - const tx = await harvester - .connect(strategist)["harvestAndSwap(address)"](strategy.address); - - // Make sure the events have been emitted - await expect(tx).to.emit(strategy, "RewardTokenCollected"); - await expect(tx).to.emit(harvester, "RewardTokenSwapped"); - await expect(tx).to.emit(harvester, "RewardProceedsTransferred"); - - // Should've transferred everything to Harvester - for (const token of rewardTokens) { - expect(await token.balanceOf(strategy.address)).to.eq("0"); - } - }); - }); -}; - -module.exports = { - shouldHarvestRewardTokens, -}; diff --git a/contracts/test/behaviour/harvestable.js b/contracts/test/behaviour/harvestable.js index f80fb4fd6d..714f611961 100644 --- a/contracts/test/behaviour/harvestable.js +++ b/contracts/test/behaviour/harvestable.js @@ -11,7 +11,7 @@ const { impersonateAndFund } = require("../../utils/signers"); shouldBehaveLikeHarvester(() => ({ ...fixture, harvester: fixture.oethHarvester - strategy: fixture.convexEthMetaStrategy, + strategy: fixture.nativeStakingSSVStrategy, })); */ const shouldBehaveLikeHarvestable = (context) => { diff --git a/contracts/test/behaviour/harvester.js b/contracts/test/behaviour/harvester.js deleted file mode 100644 index 12c080e32d..0000000000 --- a/contracts/test/behaviour/harvester.js +++ /dev/null @@ -1,972 +0,0 @@ -const { expect } = require("chai"); - -const { - changeInMultipleBalances, - setOracleTokenPriceUsd, - usdsUnits, - ousdUnits, - usdtUnits, - oethUnits, -} = require("../helpers"); -const { impersonateAndFund } = require("../../utils/signers"); -const addresses = require("../../utils/addresses"); -const { utils } = require("ethers"); -const { MAX_UINT256 } = require("../../utils/constants"); - -/** - * - * @param {*} context a function that returns a fixture with the additional properties: - * - harvester: OUSD Harvester or OETH Harvester - * - strategies: an array of objects with the following properties: - * - strategy: strategy contract - * - rewardTokens: an array of reward tokens. - * - rewardProceedsAddress: address to send the rewards to. eg vault - * @example - shouldBehaveLikeHarvester(() => ({ - ...fixture, - harvester: fixture.harvester, - strategies: [ - { - strategy: fixture.compoundStrategy, - rewardTokens: [fixture.comp], - }, - { - strategy: fixture.aaveStrategy, - rewardTokens: [fixture.aaveToken], - }, - ], - rewardProceedsAddress: fixture.vault.address, - })); - */ -const shouldBehaveLikeHarvester = (context) => { - describe.skip("Harvest behaviour", () => { - async function _checkBalancesPostHarvesting(harvestFn, strategies) { - const { harvester } = context(); - - if (!Array.isArray(strategies)) { - strategies = [strategies]; - } - - const rewardTokens = strategies.reduce( - (all, s) => [...all, ...s.rewardTokens], - [] - ); - - const balanceDiff = await changeInMultipleBalances( - async () => { - await harvestFn(); - }, - rewardTokens, - [...strategies.map((s) => s.strategy.address), harvester.address] - ); - - for (const { strategy, rewardTokens } of strategies) { - for (const token of rewardTokens) { - expect(balanceDiff[harvester.address][token.address]).to.equal( - -1 * balanceDiff[strategy.address][token.address], - `Balance mismatch for rewardToken: ${await token.symbol()}` - ); - } - } - } - - it("Should allow rewards to be collect from the strategy by the harvester", async () => { - const { harvester, strategies } = context(); - const { strategy } = strategies[0]; - - const harvesterSigner = await impersonateAndFund(harvester.address); - - await _checkBalancesPostHarvesting( - () => strategy.connect(harvesterSigner).collectRewardTokens(), - strategies[0] - ); - }); - - it("Should NOT allow rewards to be collected by non-harvester", async () => { - const { anna, governor, strategist, strategies } = context(); - const { strategy } = strategies[0]; - - for (const signer of [anna, governor, strategist]) { - await expect( - strategy.connect(signer).collectRewardTokens() - ).to.be.revertedWith("Caller is not the Harvester"); - } - }); - }); - - describe.skip("RewardTokenConfig", () => { - it("Should only allow valid Uniswap V2 path", async () => { - const { harvester, crv, usdt, governor, uniswapRouter } = context(); - - const config = { - allowedSlippageBps: 200, - harvestRewardBps: 500, - swapPlatformAddr: uniswapRouter.address, - doSwapRewardToken: true, - swapPlatform: 0, - liquidationLimit: 0, - }; - - let uniV2Path = utils.defaultAbiCoder.encode( - ["address[]"], - [[usdt.address, crv.address]] - ); - - await expect( - harvester - .connect(governor) - .setRewardTokenConfig(crv.address, config, uniV2Path) - ).to.be.revertedWith("InvalidTokenInSwapPath"); - - uniV2Path = utils.defaultAbiCoder.encode( - ["address[]"], - [[crv.address, crv.address]] - ); - - await expect( - harvester - .connect(governor) - .setRewardTokenConfig(crv.address, config, uniV2Path) - ).to.be.revertedWith("InvalidTokenInSwapPath"); - - uniV2Path = utils.defaultAbiCoder.encode(["address[]"], [[usdt.address]]); - await expect( - harvester - .connect(governor) - .setRewardTokenConfig(crv.address, config, uniV2Path) - ).to.be.revertedWith("InvalidUniswapV2PathLength"); - - uniV2Path = utils.defaultAbiCoder.encode( - ["address[]"], - [[crv.address, usdt.address]] - ); - - await expect( - harvester - .connect(governor) - .setRewardTokenConfig(crv.address, config, uniV2Path) - ).to.not.be.reverted; - }); - - it("Should only allow valid Uniswap V3 path", async () => { - const { harvester, crv, usdt, governor, uniswapRouter } = context(); - - const config = { - allowedSlippageBps: 200, - harvestRewardBps: 500, - swapPlatformAddr: uniswapRouter.address, - doSwapRewardToken: true, - swapPlatform: 1, - liquidationLimit: 0, - }; - - let uniV3Path = utils.solidityPack( - ["address", "uint24", "address"], - [usdt.address, 24, crv.address] - ); - await expect( - harvester - .connect(governor) - .setRewardTokenConfig(crv.address, config, uniV3Path) - ).to.be.revertedWith("InvalidTokenInSwapPath"); - - uniV3Path = utils.solidityPack( - ["address", "uint24", "address"], - [crv.address, 24, crv.address] - ); - await expect( - harvester - .connect(governor) - .setRewardTokenConfig(crv.address, config, uniV3Path) - ).to.be.revertedWith("InvalidTokenInSwapPath"); - - uniV3Path = utils.solidityPack( - ["address", "uint24", "address"], - [crv.address, 24, usdt.address] - ); - await expect( - harvester - .connect(governor) - .setRewardTokenConfig(crv.address, config, uniV3Path) - ).to.not.be.reverted; - }); - - it("Should only allow valid balancer config", async () => { - const { harvester, crv, governor, balancerVault } = context(); - - const config = { - allowedSlippageBps: 200, - harvestRewardBps: 500, - swapPlatformAddr: balancerVault.address, - doSwapRewardToken: true, - swapPlatform: 2, - liquidationLimit: 0, - }; - - await expect( - harvester - .connect(governor) - .setRewardTokenConfig( - crv.address, - config, - utils.defaultAbiCoder.encode( - ["bytes32"], - [ - "0x0000000000000000000000000000000000000000000000000000000000000000", - ] - ) - ) - ).to.be.revertedWith("EmptyBalancerPoolId"); - - await expect( - harvester - .connect(governor) - .setRewardTokenConfig( - crv.address, - config, - utils.defaultAbiCoder.encode( - ["bytes32"], - [ - "0x000000000000000000000000000000000000000000000000000000000000dead", - ] - ) - ) - ).to.not.be.reverted; - }); - - it("Should only allow valid Curve config", async () => { - const { harvester, crv, usdt, governor, threePool } = context(); - - const config = { - allowedSlippageBps: 200, - harvestRewardBps: 500, - swapPlatformAddr: threePool.address, - doSwapRewardToken: true, - swapPlatform: 3, - liquidationLimit: 0, - }; - - await threePool - .connect(governor) - .setCoins([crv.address, crv.address, usdt.address]); - - await expect( - harvester - .connect(governor) - .setRewardTokenConfig( - crv.address, - config, - utils.defaultAbiCoder.encode(["uint256", "uint256"], ["1", "0"]) - ) - ).to.be.revertedWith("InvalidCurvePoolAssetIndex"); - - await expect( - harvester - .connect(governor) - .setRewardTokenConfig( - crv.address, - config, - utils.defaultAbiCoder.encode(["uint256", "uint256"], ["2", "2"]) - ) - ).to.be.revertedWith("InvalidCurvePoolAssetIndex"); - - await expect( - harvester - .connect(governor) - .setRewardTokenConfig( - crv.address, - config, - utils.defaultAbiCoder.encode(["uint256", "uint256"], ["0", "2"]) - ) - ).to.not.be.reverted; - }); - - it("Should revert on unsupported platform", async () => { - const { harvester, crv, governor, uniswapRouter } = context(); - - const config = { - allowedSlippageBps: 200, - harvestRewardBps: 500, - swapPlatformAddr: uniswapRouter.address, - doSwapRewardToken: true, - swapPlatform: 4, - liquidationLimit: 0, - }; - - await expect( - harvester - .connect(governor) - .setRewardTokenConfig( - crv.address, - config, - utils.defaultAbiCoder.encode(["uint256"], ["0"]) - ) - ).to.be.reverted; - }); - - it("Should reset allowance on older router", async () => { - const { harvester, crv, usdt, governor, vault, uniswapRouter } = - context(); - - const oldRouter = vault; // Pretend vault is a router - - const config = { - allowedSlippageBps: 200, - harvestRewardBps: 500, - swapPlatformAddr: oldRouter.address, - doSwapRewardToken: true, - swapPlatform: 0, - liquidationLimit: 0, - }; - - const uniV2Path = utils.defaultAbiCoder.encode( - ["address[]"], - [[crv.address, usdt.address]] - ); - - await harvester - .connect(governor) - .setRewardTokenConfig(crv.address, config, uniV2Path); - - // Check allowance on old router - expect(await crv.allowance(harvester.address, oldRouter.address)).to.eq( - MAX_UINT256 - ); - - // Change router - await harvester.connect(governor).setRewardTokenConfig( - crv.address, - { - ...config, - swapPlatformAddr: uniswapRouter.address, - }, - uniV2Path - ); - - // Check allowance on old & new router - expect( - await crv.allowance(harvester.address, uniswapRouter.address) - ).to.eq(MAX_UINT256); - expect(await crv.allowance(harvester.address, oldRouter.address)).to.eq( - 0 - ); - }); - - it("Should not allow setting a valid router address", async () => { - const { harvester, crv, governor } = context(); - - const config = { - allowedSlippageBps: 200, - harvestRewardBps: 500, - swapPlatformAddr: addresses.zero, - doSwapRewardToken: true, - swapPlatform: 0, - liquidationLimit: 0, - }; - - await expect( - harvester - .connect(governor) - .setRewardTokenConfig(crv.address, config, []) - ).to.be.revertedWith("EmptyAddress"); - }); - - it("Should not allow higher slippage", async () => { - const { harvester, crv, governor, uniswapRouter } = context(); - - const config = { - allowedSlippageBps: 1001, - harvestRewardBps: 500, - swapPlatformAddr: uniswapRouter.address, - doSwapRewardToken: true, - swapPlatform: 0, - liquidationLimit: 0, - }; - - await expect( - harvester - .connect(governor) - .setRewardTokenConfig(crv.address, config, []) - ).to.be.revertedWith("InvalidSlippageBps"); - }); - - it("Should not allow higher reward fee", async () => { - const { harvester, crv, governor, uniswapRouter } = context(); - - const config = { - allowedSlippageBps: 133, - harvestRewardBps: 1001, - swapPlatformAddr: uniswapRouter.address, - doSwapRewardToken: true, - swapPlatform: 0, - liquidationLimit: 0, - }; - - await expect( - harvester - .connect(governor) - .setRewardTokenConfig(crv.address, config, []) - ).to.be.revertedWith("InvalidHarvestRewardBps"); - }); - - it.skip("Should revert for unsupported tokens", async () => { - const { harvester, governor, uniswapRouter, usdc } = context(); - - const config = { - allowedSlippageBps: 133, - harvestRewardBps: 344, - swapPlatformAddr: uniswapRouter.address, - doSwapRewardToken: true, - swapPlatform: 0, - liquidationLimit: 0, - }; - - await expect( - harvester - .connect(governor) - .setRewardTokenConfig(usdc.address, config, []) - ).to.be.revertedWith("Asset not available"); - }); - - it("Should revert when it's not Governor", async () => { - const { harvester, ousd, strategist, uniswapRouter } = context(); - - const config = { - allowedSlippageBps: 133, - harvestRewardBps: 344, - swapPlatformAddr: uniswapRouter.address, - doSwapRewardToken: true, - swapPlatform: 0, - liquidationLimit: 0, - }; - - await expect( - harvester - .connect(strategist) - .setRewardTokenConfig(ousd.address, config, []) - ).to.be.revertedWith("Caller is not the Governor"); - }); - }); - - describe.skip("Swap", () => { - async function _swapWithRouter(swapRouterConfig, swapData) { - const { - harvester, - strategies, - rewardProceedsAddress, - governor, - uniswapRouter, - domen, - } = context(); - - const { strategy, rewardTokens } = strategies[0]; - - const baseToken = await ethers.getContractAt( - "MockUSDT", - await harvester.baseTokenAddress() - ); - const swapToken = rewardTokens[0]; - - // Configure to use Uniswap V3 - const config = { - allowedSlippageBps: 200, - harvestRewardBps: 500, - swapPlatformAddr: uniswapRouter.address, - doSwapRewardToken: true, - swapPlatform: 1, - liquidationLimit: 0, - ...swapRouterConfig, - }; - - await harvester - .connect(governor) - .setRewardTokenConfig(swapToken.address, config, swapData); - - await setOracleTokenPriceUsd(await swapToken.symbol(), "1"); - - let swapTx; - const balanceDiff = await changeInMultipleBalances( - async () => { - // Do the swap - // prettier-ignore - swapTx = await harvester.connect(domen)["harvestAndSwap(address)"]( - strategy.address - ) - }, - [baseToken, swapToken], - [ - harvester.address, - domen.address, - strategy.address, - rewardProceedsAddress, - ] - ); - - const balanceSwapped = - -1 * - (balanceDiff[strategy.address][swapToken.address] + - balanceDiff[harvester.address][swapToken.address]); - const tokensReceived = - balanceDiff[rewardProceedsAddress][baseToken.address] + - balanceDiff[domen.address][baseToken.address]; - - const protocolYield = (tokensReceived * 9500) / 10000; - const farmerFee = (tokensReceived * 500) / 10000; - - await expect(swapTx) - .to.emit(harvester, "RewardTokenSwapped") - .withArgs( - swapToken.address, - baseToken.address, - config.swapPlatform, - balanceSwapped.toString(), - tokensReceived.toString() - ); - - await expect(swapTx) - .to.emit(harvester, "RewardProceedsTransferred") - .withArgs(baseToken.address, domen.address, protocolYield, farmerFee); - - expect(balanceDiff[domen.address][baseToken.address]).to.equal(farmerFee); - expect(balanceDiff[rewardProceedsAddress][baseToken.address]).to.equal( - protocolYield - ); - } - - it("Should harvest and swap with Uniswap V2", async () => { - const { harvester, strategies, uniswapRouter } = context(); - const { rewardTokens } = strategies[0]; - - const baseToken = await ethers.getContractAt( - "MockUSDT", - await harvester.baseTokenAddress() - ); - const swapToken = rewardTokens[0]; - - await _swapWithRouter( - { - swapPlatform: 0, - swapPlatformAddr: uniswapRouter.address, - }, - utils.defaultAbiCoder.encode( - ["address[]"], - [[swapToken.address, baseToken.address]] - ) - ); - }); - - it("Should harvest and swap with Uniswap V3", async () => { - const { uniswapRouter, domen, daniel, governor, harvester, strategies } = - context(); - const { strategy, rewardTokens } = strategies[0]; - - const baseToken = await ethers.getContractAt( - "MockUSDT", - await harvester.baseTokenAddress() - ); - const swapToken = rewardTokens[0]; - - // Configure to use Uniswap V3 - const config = { - allowedSlippageBps: 200, - harvestRewardBps: 500, - swapPlatformAddr: uniswapRouter.address, - doSwapRewardToken: true, - swapPlatform: 1, - liquidationLimit: 0, - }; - - await harvester - .connect(governor) - .setRewardTokenConfig( - swapToken.address, - config, - utils.solidityPack( - ["address", "uint24", "address"], - [swapToken.address, 500, baseToken.address] - ) - ); - - await setOracleTokenPriceUsd(await swapToken.symbol(), "1"); - - const balBefore = await baseToken.balanceOf(daniel.address); - - // prettier-ignore - await harvester.connect(domen)["harvestAndSwap(address,address)"]( - strategy.address, - daniel.address - ) - - expect(await baseToken.balanceOf(daniel.address)).to.be.gt(balBefore); - }); - - it("Should harvest and swap with Balancer", async () => { - const { balancerVault } = context(); - await _swapWithRouter( - { - swapPlatform: 2, - swapPlatformAddr: balancerVault.address, - }, - utils.defaultAbiCoder.encode( - ["bytes32"], - ["0x000000000000000000000000000000000000000000000000000000000000dead"] - ) - ); - }); - - it("Should harvest and swap with Curve", async () => { - const { threePool, governor, harvester, strategies } = context(); - const { rewardTokens } = strategies[0]; - - const baseToken = await ethers.getContractAt( - "MockUSDT", - await harvester.baseTokenAddress() - ); - const swapToken = rewardTokens[0]; - - await threePool - .connect(governor) - .setCoins([swapToken.address, baseToken.address, baseToken.address]); - - await _swapWithRouter( - { - swapPlatform: 3, - swapPlatformAddr: threePool.address, - }, - utils.defaultAbiCoder.encode(["uint256", "uint256"], ["0", "2"]) - ); - }); - - it("Should not swap when disabled", async () => { - const { harvester, strategies, governor, balancerVault, domen } = - context(); - - const { strategy, rewardTokens } = strategies[0]; - - const swapToken = rewardTokens[0]; - - // Configure to use Uniswap V3 - const config = { - allowedSlippageBps: 200, - harvestRewardBps: 500, - swapPlatformAddr: balancerVault.address, - doSwapRewardToken: false, - swapPlatform: 2, - liquidationLimit: 0, - }; - - await harvester - .connect(governor) - .setRewardTokenConfig( - swapToken.address, - config, - utils.defaultAbiCoder.encode( - ["bytes32"], - [ - "0x000000000000000000000000000000000000000000000000000000000000dead", - ] - ) - ); - - await setOracleTokenPriceUsd(await swapToken.symbol(), "1"); - - // prettier-ignore - const swapTx = await harvester - .connect(domen)["harvestAndSwap(address)"](strategy.address); - - await expect(swapTx).to.not.emit(harvester, "RewardTokenSwapped"); - await expect(swapTx).to.not.emit(harvester, "RewardProceedsTransferred"); - }); - - it("Should harvest and swap and send rewards to external address", async () => { - const { harvester, strategies, uniswapRouter } = context(); - const { rewardTokens } = strategies[0]; - - const baseToken = await ethers.getContractAt( - "MockUSDT", - await harvester.baseTokenAddress() - ); - const swapToken = rewardTokens[0]; - - await _swapWithRouter( - { - swapPlatform: 0, - swapPlatformAddr: uniswapRouter.address, - }, - utils.defaultAbiCoder.encode( - ["address[]"], - [[swapToken.address, baseToken.address]] - ) - ); - }); - - it("Should not swap when balance is zero", async () => { - const { harvester, strategies, governor, balancerVault } = context(); - - const { rewardTokens, strategy } = strategies[0]; - - const swapToken = rewardTokens[0]; - - // Configure to use Uniswap V3 - const config = { - allowedSlippageBps: 200, - harvestRewardBps: 500, - swapPlatformAddr: balancerVault.address, - doSwapRewardToken: true, - swapPlatform: 2, - liquidationLimit: 0, - }; - - await harvester - .connect(governor) - .setRewardTokenConfig( - swapToken.address, - config, - utils.defaultAbiCoder.encode( - ["bytes32"], - [ - "0x000000000000000000000000000000000000000000000000000000000000dead", - ] - ) - ); - - await setOracleTokenPriceUsd(await swapToken.symbol(), "1"); - - // Remove everything from strategy - await swapToken - .connect(await impersonateAndFund(strategy.address)) - .transfer( - governor.address, - await swapToken.balanceOf(strategy.address) - ); - - // prettier-ignore - const swapTx = await harvester - .connect(governor)["harvestAndSwap(address)"](strategy.address); - - await expect(swapTx).to.not.emit(harvester, "RewardTokenSwapped"); - await expect(swapTx).to.not.emit(harvester, "RewardProceedsTransferred"); - }); - - it("Should revert when swap platform doesn't return enough tokens", async () => { - const { harvester, strategies, governor, balancerVault } = context(); - - const { rewardTokens, strategy } = strategies[0]; - - const swapToken = rewardTokens[0]; - - // Configure to use Uniswap V3 - const config = { - allowedSlippageBps: 200, - harvestRewardBps: 500, - swapPlatformAddr: balancerVault.address, - doSwapRewardToken: true, - swapPlatform: 2, - liquidationLimit: 0, - }; - - await harvester - .connect(governor) - .setRewardTokenConfig( - swapToken.address, - config, - utils.defaultAbiCoder.encode( - ["bytes32"], - [ - "0x000000000000000000000000000000000000000000000000000000000000dead", - ] - ) - ); - - await setOracleTokenPriceUsd(await swapToken.symbol(), "1"); - - // Disable transfer on mock balancerVault - await balancerVault.connect(governor).disableTransfer(); - - // prettier-ignore - const swapTx = harvester - .connect(governor)["harvestAndSwap(address)"](strategy.address); - - await expect(swapTx).to.be.revertedWith("BalanceMismatchAfterSwap"); - }); - - it("Should revert when slippage is high", async () => { - const { harvester, strategies, governor, balancerVault } = context(); - - const { rewardTokens, strategy } = strategies[0]; - - const swapToken = rewardTokens[0]; - - // Configure to use Uniswap V3 - const config = { - allowedSlippageBps: 200, - harvestRewardBps: 500, - swapPlatformAddr: balancerVault.address, - doSwapRewardToken: true, - swapPlatform: 2, - liquidationLimit: 0, - }; - - await harvester - .connect(governor) - .setRewardTokenConfig( - swapToken.address, - config, - utils.defaultAbiCoder.encode( - ["bytes32"], - [ - "0x000000000000000000000000000000000000000000000000000000000000dead", - ] - ) - ); - - await setOracleTokenPriceUsd(await swapToken.symbol(), "1"); - - // Disable transfer on mock balancerVault - await balancerVault.connect(governor).disableSlippageError(); - await balancerVault.connect(governor).setSlippage(oethUnits("0.1")); - - // prettier-ignore - const swapTx = harvester - .connect(governor)["harvestAndSwap(address)"](strategy.address); - - await expect(swapTx).to.be.revertedWith("SlippageError"); - }); - - it("Should use liquidation limit", async () => { - const { harvester, strategies, governor, balancerVault, domen } = - context(); - - const { strategy, rewardTokens } = strategies[0]; - - const swapToken = rewardTokens[0]; - const baseToken = await ethers.getContractAt( - "MockUSDT", - await harvester.baseTokenAddress() - ); - - // Configure to use Balancer - const config = { - allowedSlippageBps: 0, - harvestRewardBps: 0, - swapPlatformAddr: balancerVault.address, - doSwapRewardToken: true, - swapPlatform: 2, - liquidationLimit: ousdUnits("100"), - }; - - await harvester - .connect(governor) - .setRewardTokenConfig( - swapToken.address, - config, - utils.defaultAbiCoder.encode( - ["bytes32"], - [ - "0x000000000000000000000000000000000000000000000000000000000000dead", - ] - ) - ); - - await setOracleTokenPriceUsd(await swapToken.symbol(), "1"); - - await swapToken - .connect(domen) - .mintTo(harvester.address, ousdUnits("1000")); - - // prettier-ignore - const swapTx = await harvester - .connect(domen)["harvestAndSwap(address)"](strategy.address); - - await expect(swapTx) - .to.emit(harvester, "RewardTokenSwapped") - .withArgs( - swapToken.address, - baseToken.address, - 2, - ousdUnits("100"), - usdtUnits("100") - ); - }); - - it("Should not harvest from unsupported strategies", async () => { - const { harvester, domen } = context(); - - // prettier-ignore - await expect( - harvester - .connect(domen)["harvestAndSwap(address)"](domen.address) - ).to.be.revertedWith("UnsupportedStrategy"); - }); - }); - - describe.skip("Admin function", () => { - it("Should only allow governor to change RewardProceedsAddress", async () => { - const { harvester, governor, daniel, strategist } = context(); - - const tx = await harvester - .connect(governor) - .setRewardProceedsAddress(strategist.address); - - await expect(tx) - .to.emit(harvester, "RewardProceedsAddressChanged") - .withArgs(strategist.address); - - expect(await harvester.rewardProceedsAddress()).to.equal( - strategist.address - ); - - for (const signer of [daniel, strategist]) { - await expect( - harvester.connect(signer).setRewardProceedsAddress(governor.address) - ).to.be.revertedWith("Caller is not the Governor"); - } - }); - - it("Should not allow to set invalid rewardProceedsAddress", async () => { - const { harvester, governor } = context(); - - await expect( - harvester.connect(governor).setRewardProceedsAddress(addresses.zero) - ).to.be.revertedWith("EmptyAddress"); - }); - - it("Should allow governor to set supported strategies", async () => { - const { harvester, governor, strategist } = context(); - - expect(await harvester.supportedStrategies(strategist.address)).to.be - .false; - await harvester - .connect(governor) - .setSupportedStrategy(strategist.address, true); - expect(await harvester.supportedStrategies(strategist.address)).to.be - .true; - - await expect( - harvester - .connect(strategist) - .setSupportedStrategy(strategist.address, false) - ).to.be.revertedWith("Caller is not the Governor"); - }); - - it("Should allow governor to transfer any token", async () => { - const { governor, strategist, usds, harvester } = context(); - - await usds.connect(governor).mintTo(harvester.address, usdsUnits("1000")); - - await expect( - harvester.connect(strategist).transferToken(usds.address, "1") - ).to.be.revertedWith("Caller is not the Governor"); - - await harvester - .connect(governor) - .transferToken(usds.address, usdsUnits("1000")); - - expect(await usds.balanceOf(harvester.address)).to.eq("0"); - }); - }); -}; - -module.exports = { - shouldBehaveLikeHarvester, -}; diff --git a/contracts/test/behaviour/strategy.js b/contracts/test/behaviour/strategy.js index 1fcdba8d1d..23880954cb 100644 --- a/contracts/test/behaviour/strategy.js +++ b/contracts/test/behaviour/strategy.js @@ -19,9 +19,9 @@ const { parseUnits } = require("ethers/lib/utils"); * @example shouldBehaveLikeStrategy(() => ({ ...fixture, - strategy: fixture.fraxEthStrategy, - assets: [fixture.frxETH, fixture.weth], - valueAssets: [fixture.frxETH], + strategy: fixture.nativeStakingSSVStrategy, + assets: [fixture.weth], + valueAssets: [], harvester: fixture.oethHarvester, vault: fixture.oethVault, checkWithdrawAmounts: true, @@ -47,31 +47,9 @@ const shouldBehaveLikeStrategy = (context) => { } }); it("Should NOT be a supported asset", async () => { - const { - assets, - strategy, - usdt, - usdc, - usds, - weth, - reth, - stETH, - frxETH, - crv, - cvx, - } = await context(); - - const randomAssets = [ - usdt, - usdc, - usds, - weth, - reth, - stETH, - frxETH, - cvx, - crv, - ]; + const { assets, strategy, usdt, usdc, usds, weth } = await context(); + + const randomAssets = [usdt, usdc, usds, weth]; for (const asset of randomAssets) { if (assets.includes(asset)) { continue; // Since `assets` already has a list of supported assets @@ -358,28 +336,27 @@ const shouldBehaveLikeStrategy = (context) => { }); }); it("Should allow transfer of arbitrary token by Governor", async () => { - const { governor, crv, strategy } = context(); - const governorCRVBalanceBefore = await crv.balanceOf(governor.address); - const strategyCRVBalanceBefore = await crv.balanceOf(strategy.address); + const { governor, ssv, strategy } = context(); + const governorBalanceBefore = await ssv.balanceOf(governor.address); - // Anna accidentally sends CRV to strategy + // Someone accidentally sends SSV to strategy const recoveryAmount = parseUnits("2"); - await setERC20TokenBalance(strategy.address, crv, recoveryAmount, hre); + await setERC20TokenBalance(strategy.address, ssv, recoveryAmount, hre); - // Anna asks Governor for help + // Governor recovers the token const tx = await strategy .connect(governor) - .transferToken(crv.address, recoveryAmount); + .transferToken(ssv.address, recoveryAmount); await expect(tx) - .to.emit(crv, "Transfer") + .to.emit(ssv, "Transfer") .withArgs(strategy.address, governor.address, recoveryAmount); await expect(governor).has.a.balanceOf( - governorCRVBalanceBefore.add(recoveryAmount), - crv + governorBalanceBefore.add(recoveryAmount), + ssv ); - await expect(strategy).has.a.balanceOf(strategyCRVBalanceBefore, crv); + await expect(strategy).has.a.balanceOf("0", ssv); }); it("Should not transfer supported assets from strategy", async () => { const { assets, governor, strategy } = context(); @@ -439,9 +416,13 @@ const shouldBehaveLikeStrategy = (context) => { } }); it("Should allow reward tokens to be set by the governor", async () => { - const { governor, strategy, comp, crv, bal } = context(); + const { governor, strategy } = context(); - const newRewardTokens = [comp.address, crv.address, bal.address]; + // Use random addresses as reward tokens for testing + const rewardToken1 = Wallet.createRandom().address; + const rewardToken2 = Wallet.createRandom().address; + const rewardToken3 = Wallet.createRandom().address; + const newRewardTokens = [rewardToken1, rewardToken2, rewardToken3]; const oldRewardTokens = await strategy.getRewardTokenAddresses(); const tx = await strategy @@ -452,18 +433,21 @@ const shouldBehaveLikeStrategy = (context) => { .to.emit(strategy, "RewardTokenAddressesUpdated") .withArgs(oldRewardTokens, newRewardTokens); - expect(await strategy.rewardTokenAddresses(0)).to.equal(comp.address); - expect(await strategy.rewardTokenAddresses(1)).to.equal(crv.address); - expect(await strategy.rewardTokenAddresses(2)).to.equal(bal.address); + expect(await strategy.rewardTokenAddresses(0)).to.equal(rewardToken1); + expect(await strategy.rewardTokenAddresses(1)).to.equal(rewardToken2); + expect(await strategy.rewardTokenAddresses(2)).to.equal(rewardToken3); }); it("Should not allow reward tokens to be set by non-governor", async () => { - const { comp, crv, bal, strategy, strategist, matt, harvester, vault } = - context(); + const { strategy, strategist, matt, harvester, vault } = context(); const vaultSigner = await impersonateAndFund(vault.address); const harvesterSigner = await impersonateAndFund(harvester.address); - const newRewardTokens = [comp.address, crv.address, bal.address]; + const newRewardTokens = [ + Wallet.createRandom().address, + Wallet.createRandom().address, + Wallet.createRandom().address, + ]; for (const signer of [strategist, matt, harvesterSigner, vaultSigner]) { await expect( diff --git a/contracts/test/buyback/buyback.js b/contracts/test/buyback/buyback.js deleted file mode 100644 index 94d4ebd970..0000000000 --- a/contracts/test/buyback/buyback.js +++ /dev/null @@ -1,516 +0,0 @@ -const { expect } = require("chai"); - -const { createFixtureLoader, buybackFixture } = require("../_fixture"); -const { ousdUnits, usdcUnits, oethUnits, isCI } = require("../helpers"); -const addresses = require("../../utils/addresses"); -const { impersonateAndFund } = require("../../utils/signers"); -const { setERC20TokenBalance } = require("../_fund"); -const { setStorageAt } = require("@nomicfoundation/hardhat-network-helpers"); - -const loadFixture = createFixtureLoader(buybackFixture); - -describe("Buyback", function () { - let fixture; - - // Retry up to 3 times on CI - this.retries(isCI ? 3 : 0); - - beforeEach(async () => { - fixture = await loadFixture(); - }); - - it("Should swap OETH for OGN", async () => { - const { oeth, ogn, oethBuyback, strategist, rewardsSource } = fixture; - - await oethBuyback - .connect(strategist) - .swapForOGN(oethUnits("1"), oethUnits("100000"), "0x00000000"); - - // Check balance after swap - await expect(oethBuyback).to.have.a.balanceOf(oethUnits("2"), oeth); - - expect(await oethBuyback.balanceForOGN()).to.equal(oethUnits("0.5")); - expect(await oethBuyback.balanceForCVX()).to.equal(oethUnits("1.5")); - - // Ensure OGN went to RewardsSource contract - await expect(rewardsSource).to.have.balanceOf(oethUnits("100000"), ogn); - }); - - it("Should swap OETH for CVX", async () => { - const { oeth, oethBuyback, strategist, cvx, cvxLocker } = fixture; - - await oethBuyback - .connect(strategist) - .swapForCVX(oethUnits("1"), oethUnits("100"), "0x00000000"); - - // Check balance after swap - await expect(oethBuyback).to.have.a.balanceOf(oethUnits("2"), oeth); - - expect(await oethBuyback.balanceForOGN()).to.equal(oethUnits("1.5")); - - expect(await oethBuyback.balanceForCVX()).to.equal(oethUnits("0.5")); - - // Ensure it locked CVX - expect(await cvxLocker.lockedBalanceOf(strategist.address)).to.equal( - oethUnits("100") - ); - await expect(cvxLocker).to.have.balanceOf(oethUnits("100"), cvx); - }); - - it("Should swap OUSD for OGN", async () => { - const { ousd, ogn, ousdBuyback, strategist, rewardsSource } = fixture; - - await ousdBuyback - .connect(strategist) - .swapForOGN(ousdUnits("1250"), ousdUnits("100000"), "0x00000000"); - - // Check balance after swap - await expect(ousdBuyback).to.have.a.balanceOf(ousdUnits("1750"), ousd); - - expect(await ousdBuyback.balanceForOGN()).to.equal(ousdUnits("250")); - expect(await ousdBuyback.balanceForCVX()).to.equal(ousdUnits("1500")); - - // Ensure OGN went to RewardsSource contract - await expect(rewardsSource).to.have.balanceOf(ousdUnits("100000"), ogn); - }); - - it("Should swap OUSD for CVX", async () => { - const { ousd, ousdBuyback, strategist, cvx, cvxLocker } = fixture; - - await ousdBuyback - .connect(strategist) - .swapForCVX(ousdUnits("750"), ousdUnits("100"), "0x00000000"); - - // Check balance after swap - await expect(ousdBuyback).to.have.a.balanceOf(ousdUnits("2250"), ousd); - - expect(await ousdBuyback.balanceForOGN()).to.equal(ousdUnits("1500")); - - expect(await ousdBuyback.balanceForCVX()).to.equal(ousdUnits("750")); - - // Ensure it locked CVX - expect(await cvxLocker.lockedBalanceOf(strategist.address)).to.equal( - ousdUnits("100") - ); - await expect(cvxLocker).to.have.balanceOf(ousdUnits("100"), cvx); - }); - - it("Should NOT swap OGN when called by someone else", async () => { - const { anna, ousdBuyback } = fixture; - - const ousdAmount = ousdUnits("1000"); - - await expect( - ousdBuyback.connect(anna).swapForOGN(ousdAmount, 1, "0x00000000") - ).to.be.revertedWith("Caller is not the Strategist or Governor"); - }); - - it("Should NOT swap CVX when called by someone else", async () => { - const { anna, ousdBuyback } = fixture; - - const ousdAmount = ousdUnits("1000"); - - await expect( - ousdBuyback.connect(anna).swapForCVX(ousdAmount, 1, "0x00000000") - ).to.be.revertedWith("Caller is not the Strategist or Governor"); - }); - - it("Should NOT swap when swap amount is invalid", async () => { - const { ousdBuyback, strategist } = fixture; - - await expect( - ousdBuyback.connect(strategist).swapForCVX(0, 0, "0x00000000") - ).to.be.revertedWith("Invalid Swap Amount"); - }); - - it("Should NOT swap when swapper isn't set", async () => { - const { governor, ousdBuyback } = fixture; - - // Set Swap Router Address to 0x0 - await ousdBuyback.connect(governor).setSwapRouter(addresses.zero); - expect(await ousdBuyback.swapRouter()).to.be.equal(addresses.zero); - - const ousdAmount = ousdUnits("1000"); - - await expect( - ousdBuyback.connect(governor).swapForOGN(ousdAmount, 10, "0x00000000") - ).to.be.revertedWith("Swap Router not set"); - }); - - it("Should NOT swap when min expected is zero", async () => { - const { governor, ousdBuyback } = fixture; - await expect( - ousdBuyback.connect(governor).swapForOGN(10, 0, "0x00000000") - ).to.be.revertedWith("Invalid minAmount"); - - await expect( - ousdBuyback.connect(governor).swapForCVX(10, 0, "0x00000000") - ).to.be.revertedWith("Invalid minAmount"); - }); - - it("Should NOT swap OGN when RewardsSource isn't set", async () => { - const { governor, ousdBuyback } = fixture; - - // Set RewardsSource to zero - await setStorageAt( - ousdBuyback.address, - "0x6b", - "0x0000000000000000000000000000000000000000000000000000000000000000" - ); - - await expect( - ousdBuyback.connect(governor).swapForOGN(10, 10, "0x00000000") - ).to.be.revertedWith("RewardsSource contract not set"); - }); - - it("Should NOT swap OGN/CVX when balance underflow", async () => { - const { governor, ousdBuyback } = fixture; - - await expect( - ousdBuyback - .connect(governor) - .swapForOGN(ousdUnits("2000"), 10, "0x00000000") - ).to.be.revertedWith("Balance underflow"); - - await expect( - ousdBuyback - .connect(governor) - .swapForCVX(ousdUnits("2000"), 10, "0x00000000") - ).to.be.revertedWith("Balance underflow"); - }); - - it("Should revert when slippage is higher", async () => { - const { governor, ousdBuyback, mockSwapper } = fixture; - - await mockSwapper.setNextOutAmount(ousdUnits("100")); - - await expect( - ousdBuyback - .connect(governor) - .swapForOGN(ousdUnits("100"), ousdUnits("1000"), "0x00000000") - ).to.be.revertedWith("Higher Slippage"); - }); - - it("Should allow Governor to set Trustee address on Vault", async () => { - const { vault, governor, ousd } = fixture; - // Pretend OUSD is Treasury Manager - await vault.connect(governor).setTrusteeAddress(ousd.address); - - expect(await vault.trusteeAddress()).to.equal(ousd.address); - }); - - it("Should not allow non-Governor to set Trustee address on Vault", async () => { - const { vault, anna, ousd } = fixture; - // Pretend OUSD is Treasury Manager - await expect( - vault.connect(anna).setTrusteeAddress(ousd.address) - ).to.be.revertedWith("Caller is not the Governor"); - }); - - it("Should allow Governor to set Swap Router address", async () => { - const { ousdBuyback, governor, ousd } = fixture; - // Pretend OUSD is Swap Router - await ousdBuyback.connect(governor).setSwapRouter(ousd.address); - - expect(await ousdBuyback.swapRouter()).to.equal(ousd.address); - }); - - it("Should revoke allowance on older Swap Router", async () => { - const { ousdBuyback, governor, mockSwapper, ousd, ogn, cvx } = fixture; - - const mockSigner = await impersonateAndFund(ousdBuyback.address); - - await cvx - .connect(mockSigner) - .approve(mockSwapper.address, ousdUnits("10000")); - - await ogn - .connect(mockSigner) - .approve(mockSwapper.address, ousdUnits("12300")); - - // Pretend OUSD is Swap Router - await ousdBuyback.connect(governor).setSwapRouter(ousd.address); - - expect(await ousdBuyback.swapRouter()).to.equal(ousd.address); - - // Ensure allowance is removed - expect( - await ogn.allowance(ousdBuyback.address, mockSwapper.address) - ).to.equal(0); - - expect( - await cvx.allowance(ousdBuyback.address, mockSwapper.address) - ).to.equal(0); - }); - - it("Should not allow non-Governor to set Swap Router address", async () => { - const { ousdBuyback, anna, ousd } = fixture; - // Pretend OUSD is Swap Router - await expect( - ousdBuyback.connect(anna).setSwapRouter(ousd.address) - ).to.be.revertedWith("Caller is not the Governor"); - }); - - it("Should allow Governor to set Strategist address", async () => { - const { ousdBuyback, governor, ousd } = fixture; - // Pretend OUSD is a Strategist - await ousdBuyback.connect(governor).setStrategistAddr(ousd.address); - expect(await ousdBuyback.strategistAddr()).to.be.equal(ousd.address); - }); - - it("Should not allow non-Governor to set Strategist address", async () => { - const { ousdBuyback, anna, ousd } = fixture; - // Pretend OUSD is Strategist - await expect( - ousdBuyback.connect(anna).setStrategistAddr(ousd.address) - ).to.be.revertedWith("Caller is not the Governor"); - }); - - it("Should allow withdrawal of arbitrary token by Governor", async () => { - const { vault, ousd, usdc, matt, governor, ousdBuyback } = fixture; - // Matt deposits USDC, 6 decimals - await usdc.connect(matt).approve(vault.address, usdcUnits("8.0")); - await vault.connect(matt).mint(usdc.address, usdcUnits("8.0"), 0); - // Matt sends his OUSD directly to Vault - await ousd.connect(matt).transfer(ousdBuyback.address, ousdUnits("8.0")); - // Matt asks Governor for help - await ousdBuyback - .connect(governor) - .transferToken(ousd.address, ousdUnits("8.0")); - await expect(governor).has.a.balanceOf("8.0", ousd); - }); - - it("Should not allow withdrawal of arbitrary token by non-Governor", async () => { - const { ousdBuyback, ousd, matt, strategist } = fixture; - // Naughty Matt - await expect( - ousdBuyback.connect(matt).transferToken(ousd.address, ousdUnits("8.0")) - ).to.be.revertedWith("Caller is not the Governor"); - - // Make sure strategist can't do that either - await expect( - ousdBuyback - .connect(strategist) - .transferToken(ousd.address, ousdUnits("8.0")) - ).to.be.revertedWith("Caller is not the Governor"); - }); - - it("Should allow Governor to change RewardsSource address", async () => { - const { ousdBuyback, governor, matt } = fixture; - - await ousdBuyback.connect(governor).setRewardsSource(matt.address); - - expect(await ousdBuyback.rewardsSource()).to.equal(matt.address); - }); - - it("Should not allow anyone else to change RewardsSource address", async () => { - const { ousdBuyback, strategist, matt, josh } = fixture; - - for (const user of [strategist, josh]) { - await expect( - ousdBuyback.connect(user).setRewardsSource(matt.address) - ).to.be.revertedWith("Caller is not the Governor"); - } - }); - - it("Should not allow setting RewardsSource address to address(0)", async () => { - const { ousdBuyback, governor } = fixture; - - await expect( - ousdBuyback - .connect(governor) - .setRewardsSource("0x0000000000000000000000000000000000000000") - ).to.be.revertedWith("Address not set"); - }); - - it("Should allow Governor to change Treasury manager address", async () => { - const { ousdBuyback, governor, matt } = fixture; - - await ousdBuyback.connect(governor).setTreasuryManager(matt.address); - - expect(await ousdBuyback.treasuryManager()).to.equal(matt.address); - }); - - it("Should not allow setting Treasury manager address to address(0)", async () => { - const { ousdBuyback, governor } = fixture; - - expect( - ousdBuyback - .connect(governor) - .setTreasuryManager("0x0000000000000000000000000000000000000000") - ).to.be.revertedWith("Address not set"); - }); - - it("Should not allow anyone else to change Treasury manager address", async () => { - const { ousdBuyback, strategist, matt, josh } = fixture; - - for (const user of [strategist, josh]) { - await expect( - ousdBuyback.connect(user).setTreasuryManager(matt.address) - ).to.be.revertedWith("Caller is not the Governor"); - } - }); - - it("Should lock all CVX", async () => { - const { ousdBuyback, cvx, cvxLocker, governor } = fixture; - - await setERC20TokenBalance(ousdBuyback.address, cvx, "1000"); - - await ousdBuyback.connect(governor).lockAllCVX(); - - await expect(cvxLocker).to.have.balanceOf(oethUnits("1000"), cvx); - }); - - it("Should not allow anyone else to lock CVX", async () => { - const { ousdBuyback, matt, josh, domen } = fixture; - - for (const signer of [matt, josh, domen]) { - await expect(ousdBuyback.connect(signer).lockAllCVX()).to.be.revertedWith( - "Caller is not the Strategist or Governor" - ); - } - }); - - it("Should not lock if treasury manager isn't set", async () => { - const { ousdBuyback, cvx, governor } = fixture; - - await setERC20TokenBalance(ousdBuyback.address, cvx, "1000"); - - // Set treasury manager to zero - await setStorageAt( - ousdBuyback.address, - "0x6c", - "0x0000000000000000000000000000000000000000000000000000000000000000" - ); - - await expect(ousdBuyback.connect(governor).lockAllCVX()).to.be.revertedWith( - "Treasury manager address not set" - ); - }); - - it("Should allow governance to update CVX bps", async () => { - const { ousdBuyback, governor } = fixture; - - await ousdBuyback.connect(governor).setCVXShareBps(1000); - - expect(await ousdBuyback.cvxShareBps()).to.equal(1000); - }); - - it("Should not allow anyone else to update CVX bps", async () => { - const { ousdBuyback, matt, josh, domen } = fixture; - - for (const signer of [matt, josh, domen]) { - await expect( - ousdBuyback.connect(signer).setCVXShareBps(3000) - ).to.be.revertedWith("Caller is not the Governor"); - } - }); - - it("Should not allow invalid value", async () => { - const { ousdBuyback, governor } = fixture; - - await expect( - ousdBuyback.connect(governor).setCVXShareBps(10001) - ).to.be.revertedWith("Invalid bps value"); - }); - - it("Should handle splits correctly (with 50% for CVX)", async () => { - const { ousdBuyback, ousd, josh, governor } = fixture; - - // Should have equal shares - expect(await ousdBuyback.balanceForOGN()).to.equal(ousdUnits("1500")); - expect(await ousdBuyback.balanceForCVX()).to.equal(ousdUnits("1500")); - - // Set OGN share to zero - await setStorageAt( - ousdBuyback.address, - "0x6e", - "0x0000000000000000000000000000000000000000000000000000000000000000" - ); - - // Now contract has 1500 to be split between OGN and CVX - await ousdBuyback.connect(governor).updateBuybackSplits(); - - // Ensure distribution - expect(await ousdBuyback.balanceForOGN()).to.equal(ousdUnits("750")); - expect(await ousdBuyback.balanceForCVX()).to.equal(ousdUnits("2250")); - - // When there's no unsplit balance, let it do its thing - await ousdBuyback.connect(governor).updateBuybackSplits(); - - // Ensure no change distribution - expect(await ousdBuyback.balanceForOGN()).to.equal(ousdUnits("750")); - expect(await ousdBuyback.balanceForCVX()).to.equal(ousdUnits("2250")); - - // When there's only one wei extra - await ousd.connect(josh).transfer(ousdBuyback.address, 1); - await ousdBuyback.connect(governor).updateBuybackSplits(); - - // Ensure OGN raised by 1 wei - expect(await ousdBuyback.balanceForOGN()).to.equal( - ousdUnits("750").add("1") - ); - expect(await ousdBuyback.balanceForCVX()).to.equal(ousdUnits("2250")); - }); - - it("Should handle splits correctly (with 0% for CVX)", async () => { - const { ousdBuyback, ousd, josh, governor } = fixture; - - // Should have equal shares - expect(await ousdBuyback.balanceForOGN()).to.equal(ousdUnits("1500")); - expect(await ousdBuyback.balanceForCVX()).to.equal(ousdUnits("1500")); - - expect( - await ousdBuyback.connect(governor).setCVXShareBps(0) // 0% for CVX - ); - - // Set OGN share to zero (to mimic some unaccounted balance) - await setStorageAt( - ousdBuyback.address, - "0x6e", - "0x0000000000000000000000000000000000000000000000000000000000000000" - ); - - // Now contract has 1500 unaccounted oToken - // and should allocate it all to OGN - await ousdBuyback.connect(governor).updateBuybackSplits(); - - // Ensure distribution - expect(await ousdBuyback.balanceForOGN()).to.equal(ousdUnits("1500")); - expect(await ousdBuyback.balanceForCVX()).to.equal(ousdUnits("1500")); - - // When there's no unsplit balance, let it do its thing - await ousdBuyback.connect(governor).updateBuybackSplits(); - - // Ensure no change distribution - expect(await ousdBuyback.balanceForOGN()).to.equal(ousdUnits("1500")); - expect(await ousdBuyback.balanceForCVX()).to.equal(ousdUnits("1500")); - - // When there's only one wei extra - await ousd.connect(josh).transfer(ousdBuyback.address, 1); - await ousdBuyback.connect(governor).updateBuybackSplits(); - - // Ensure OGN raised by 1 wei - expect(await ousdBuyback.balanceForOGN()).to.equal( - ousdUnits("1500").add("1") - ); - expect(await ousdBuyback.balanceForCVX()).to.equal(ousdUnits("1500")); - - // Set CVX share to zero (to mimic some unaccounted balance) - await setStorageAt( - ousdBuyback.address, - "0x6f", - "0x0000000000000000000000000000000000000000000000000000000000000000" - ); - - // Now contract has 1500 unaccounted oToken - // and should allocate it all to OGN - await ousdBuyback.connect(governor).updateBuybackSplits(); - - // Ensure distribution - expect(await ousdBuyback.balanceForOGN()).to.equal( - ousdUnits("3000").add("1") - ); - expect(await ousdBuyback.balanceForCVX()).to.equal(ousdUnits("0")); - }); -}); diff --git a/contracts/test/buyback/buyback.mainnet.fork-test.js b/contracts/test/buyback/buyback.mainnet.fork-test.js deleted file mode 100644 index d11e47fc2e..0000000000 --- a/contracts/test/buyback/buyback.mainnet.fork-test.js +++ /dev/null @@ -1,260 +0,0 @@ -const { expect } = require("chai"); -const { createFixtureLoader, buybackFixture } = require("../_fixture"); -const { ousdUnits, oethUnits } = require("../helpers"); -const { getIInchSwapData, recodeSwapData } = require("../../utils/1Inch"); -const { hotDeployOption } = require("../_hot-deploy"); - -const loadFixture = createFixtureLoader(buybackFixture); - -// Skipping buyback tests since they seem to randomly fail on CI -// and it's a user-facing function. It's callable only by the strategist, -// so we would know if it's broken. -describe.skip("ForkTest: OETH Buyback", function () { - this.timeout(0); - - let fixture; - beforeEach(async () => { - fixture = await loadFixture(); - - await hotDeployOption(fixture, null, { - isOethFixture: true, - }); - }); - - it("Should swap OETH for OGN", async () => { - const { oethBuyback, oeth, oethVault, ogn, rewardsSource, strategist } = - fixture; - - const oethBalanceBefore = await oeth.balanceOf(oethBuyback.address); - const ognShareBefore = await oethBuyback.balanceForOGN(); - const cvxShareBefore = await oethBuyback.balanceForCVX(); - const rewardsBalanceBefore = await ogn.balanceOf(rewardsSource.address); - - const ognAmount = ognShareBefore.lte(oethUnits("1")) - ? ognShareBefore - : oethUnits("1"); - - let data = await getIInchSwapData({ - vault: oethVault, - fromAsset: oeth, - toAsset: ogn, - fromAmount: ognAmount, - // 5%, just so that fork-tests don't fail on - // CI randomly due to price volatility. - slippage: 5, - protocols: ["UNISWAP", "UNISWAP_V3"], - }); - data = await recodeSwapData(data); - - await oethBuyback - .connect(strategist) - .swapForOGN(ognAmount, oethUnits("100"), data); - - const oethBalanceAfter = await oeth.balanceOf(oethBuyback.address); - const ognShareAfter = await oethBuyback.balanceForOGN(); - const cvxShareAfter = await oethBuyback.balanceForCVX(); - const rewardsBalanceAfter = await ogn.balanceOf(rewardsSource.address); - - expect(ognShareAfter).to.eq(ognShareBefore.sub(ognAmount)); - expect(oethBalanceAfter).to.eq(oethBalanceBefore.sub(ognAmount)); - expect(cvxShareAfter).to.eq(cvxShareBefore); - expect(rewardsBalanceAfter).to.be.gt(rewardsBalanceBefore); - }); - - it("Should swap OETH for CVX and lock it", async () => { - const { oethBuyback, oeth, oethVault, cvx, cvxLocker, strategist } = - fixture; - - const oethBalanceBefore = await oeth.balanceOf(oethBuyback.address); - const ognShareBefore = await oethBuyback.balanceForOGN(); - const cvxShareBefore = await oethBuyback.balanceForCVX(); - const strategistAddr = await strategist.getAddress(); - const lockedCVXBalanceBefore = await cvxLocker.lockedBalanceOf( - strategistAddr - ); - - let data = await getIInchSwapData({ - vault: oethVault, - fromAsset: oeth, - toAsset: cvx, - fromAmount: cvxShareBefore, - // 5%, just so that fork-tests don't fail on - // CI randomly due to price volatility. - slippage: 5, - protocols: ["UNISWAP", "UNISWAP_V3"], - }); - data = await recodeSwapData(data); - - await oethBuyback - .connect(strategist) - .swapForCVX(cvxShareBefore, oethUnits("1"), data); - - const oethBalanceAfter = await oeth.balanceOf(oethBuyback.address); - const ognShareAfter = await oethBuyback.balanceForOGN(); - const cvxShareAfter = await oethBuyback.balanceForCVX(); - - expect(cvxShareAfter).to.eq(0); - expect(oethBalanceAfter).to.eq(oethBalanceBefore.sub(cvxShareBefore)); - expect(ognShareAfter).to.eq(ognShareBefore); - - expect(await cvxLocker.lockedBalanceOf(strategistAddr)).to.be.gte( - lockedCVXBalanceBefore - ); - }); -}); - -describe.skip("ForkTest: OUSD Buyback", function () { - this.timeout(0); - - let fixture; - beforeEach(async () => { - fixture = await loadFixture(); - - await hotDeployOption(fixture, null, { - isOethFixture: false, - }); - }); - - it("Should swap OUSD for OGN", async () => { - const { ousdBuyback, ousd, vault, ogn, rewardsSource, strategist } = - fixture; - - const ousdBalanceBefore = await ousd.balanceOf(ousdBuyback.address); - const ognShareBefore = await ousdBuyback.balanceForOGN(); - const cvxShareBefore = await ousdBuyback.balanceForCVX(); - const rewardsBalanceBefore = await ogn.balanceOf(rewardsSource.address); - - let data = await getIInchSwapData({ - vault: vault, - fromAsset: ousd, - toAsset: ogn, - fromAmount: ognShareBefore, - // 20%, just so that fork-tests don't fail on - // CI randomly due to price volatility. - slippage: 20, - protocols: ["UNISWAP", "UNISWAP_V3"], - }); - data = await recodeSwapData(data); - - await ousdBuyback - .connect(strategist) - .swapForOGN(ognShareBefore, ousdUnits("1"), data); - - const ousdBalanceAfter = await ousd.balanceOf(ousdBuyback.address); - const ognShareAfter = await ousdBuyback.balanceForOGN(); - const cvxShareAfter = await ousdBuyback.balanceForCVX(); - const rewardsBalanceAfter = await ogn.balanceOf(rewardsSource.address); - - expect(ognShareAfter).to.eq(0); - expect(ousdBalanceAfter).to.eq(ousdBalanceBefore.sub(ognShareBefore)); - expect(cvxShareAfter).to.eq(cvxShareBefore); - expect(rewardsBalanceAfter).to.be.gt(rewardsBalanceBefore); - }); - - it("Should swap OUSD for CVX and lock it", async () => { - const { ousdBuyback, ousd, vault, cvx, cvxLocker, strategist } = fixture; - - const ousdBalanceBefore = await ousd.balanceOf(ousdBuyback.address); - const ognShareBefore = await ousdBuyback.balanceForOGN(); - const cvxShareBefore = await ousdBuyback.balanceForCVX(); - const strategistAddr = await strategist.getAddress(); - const lockedCVXBalanceBefore = await cvxLocker.lockedBalanceOf( - strategistAddr - ); - - let data = await getIInchSwapData({ - vault: vault, - fromAsset: ousd, - toAsset: cvx, - fromAmount: cvxShareBefore, - // 20%, just so that fork-tests don't fail on - // CI randomly due to price volatility. - slippage: 20, - protocols: ["UNISWAP", "UNISWAP_V3"], - }); - data = await recodeSwapData(data); - - await ousdBuyback - .connect(strategist) - .swapForCVX(cvxShareBefore, ousdUnits("0.01"), data); - - const ousdBalanceAfter = await ousd.balanceOf(ousdBuyback.address); - const ognShareAfter = await ousdBuyback.balanceForOGN(); - const cvxShareAfter = await ousdBuyback.balanceForCVX(); - - expect(cvxShareAfter).to.eq(0); - expect(ousdBalanceAfter).to.eq(ousdBalanceBefore.sub(cvxShareBefore)); - expect(ognShareAfter).to.eq(ognShareBefore); - - expect(await cvxLocker.lockedBalanceOf(strategistAddr)).to.be.gte( - lockedCVXBalanceBefore - ); - }); -}); - -describe.skip("ForkTest: ARM Buyback", function () { - this.timeout(0); - - let fixture; - beforeEach(async () => { - fixture = await loadFixture(); - - await hotDeployOption(fixture, null, { - isOethFixture: true, - }); - }); - - it("Should check the configuration", async () => { - const { armBuyback, weth, ogn, rewardsSource } = fixture; - const cSwapper = await ethers.getContract("Swapper1InchV5"); - - expect(await armBuyback.oToken()).to.be.eq(weth.address); - expect(await armBuyback.ogn()).to.be.eq( - ethers.utils.getAddress(ogn.address) - ); - expect(await armBuyback.rewardsSource()).to.be.eq(rewardsSource.address); - expect(await armBuyback.swapRouter()).to.be.eq(cSwapper.address); - expect(await armBuyback.cvxShareBps()).to.be.eq(0); - }); - - it("Should swap WETH for OGN", async () => { - const { armBuyback, weth, oethVault, ogn, rewardsSource, strategist } = - fixture; - - const oethBalanceBefore = await weth.balanceOf(armBuyback.address); - const ognShareBefore = await armBuyback.balanceForOGN(); - const cvxShareBefore = await armBuyback.balanceForCVX(); - const rewardsBalanceBefore = await ogn.balanceOf(rewardsSource.address); - - const ognAmount = ognShareBefore.lte(oethUnits("1")) - ? ognShareBefore - : oethUnits("1"); - - let data = await getIInchSwapData({ - vault: oethVault, - fromAsset: weth, - toAsset: ogn, - fromAmount: ognAmount, - // 5%, just so that fork-tests don't fail on - // CI randomly due to price volatility. - slippage: 5, - protocols: ["UNISWAP", "UNISWAP_V3"], - }); - - data = recodeSwapData(data); - - await armBuyback - .connect(strategist) - .swapForOGN(ognAmount, oethUnits("100"), data); - - const oethBalanceAfter = await weth.balanceOf(armBuyback.address); - const ognShareAfter = await armBuyback.balanceForOGN(); - const cvxShareAfter = await armBuyback.balanceForCVX(); - const rewardsBalanceAfter = await ogn.balanceOf(rewardsSource.address); - - expect(ognShareAfter).to.eq(ognShareBefore.sub(ognAmount)); - expect(oethBalanceAfter).to.eq(oethBalanceBefore.sub(ognAmount)); - expect(cvxShareAfter).to.eq(cvxShareBefore); - expect(rewardsBalanceAfter).to.be.gt(rewardsBalanceBefore); - }); -}); diff --git a/contracts/test/dripper/fixed-rate-dripper.base.fork-test.js b/contracts/test/dripper/fixed-rate-dripper.base.fork-test.js deleted file mode 100644 index cfce4e1677..0000000000 --- a/contracts/test/dripper/fixed-rate-dripper.base.fork-test.js +++ /dev/null @@ -1,108 +0,0 @@ -const { createFixtureLoader } = require("../_fixture"); -const { defaultBaseFixture } = require("../_fixture-base"); -const { expect } = require("chai"); -const { oethUnits, getBlockTimestamp, advanceTime } = require("../helpers"); -const { impersonateAndFund } = require("../../utils/signers"); -const { BigNumber } = require("ethers"); - -const baseFixture = createFixtureLoader(defaultBaseFixture); - -describe("ForkTest: OETHb FixedRateDripper", function () { - let fixture; - beforeEach(async () => { - fixture = await baseFixture(); - }); - - it("Should collect reward at fixed rate", async () => { - const { dripper, weth, oethbVault } = fixture; - - // Fund dripper with some WETH - const dripperSigner = await impersonateAndFund(dripper.address); - await weth.connect(dripperSigner).deposit({ value: oethUnits("50") }); - - const dripperBalanceBefore = await weth.balanceOf(dripper.address); - const vaultBalanceBefore = await weth.balanceOf(oethbVault.address); - - // Get current rate - const drip = await dripper.drip(); - const currTimestamp = await getBlockTimestamp(); - - // Fast forward time - const oneDay = 24 * 60 * 60; - await advanceTime(oneDay); // 1d - - const elapsedTime = BigNumber.from(currTimestamp) - .sub(drip.lastCollect) - .add(oneDay); - const expectedRewards = elapsedTime.mul(drip.perSecond); - - // Do a collect - await dripper.collect(); - - // Check state - const dripperBalanceAfter = await weth.balanceOf(dripper.address); - const vaultBalanceAfter = await weth.balanceOf(oethbVault.address); - - expect(dripperBalanceAfter).to.approxEqual( - dripperBalanceBefore.sub(expectedRewards) - ); - expect(vaultBalanceAfter).to.approxEqual( - vaultBalanceBefore.add(expectedRewards) - ); - - // Make sure drip rate hasn't changed - const dripAfter = await dripper.drip(); - expect(dripAfter.perSecond).to.eq(drip.perSecond); - // ... and lastCollect has been updated - expect(dripAfter.lastCollect).to.gte(drip.lastCollect.add(elapsedTime)); - }); - - it("Should allow strategist/governor to change rate", async () => { - const { dripper, strategist, governor } = fixture; - - let tx = await dripper.connect(strategist).setDripRate( - oethUnits("1") // 1 WETH per second - ); - await expect(tx).to.emit(dripper, "DripRateUpdated"); - - let rate = (await dripper.drip()).perSecond; - expect(rate).to.eq(oethUnits("1")); - - tx = await dripper.connect(governor).setDripRate( - oethUnits("2") // 2 WETH per second - ); - await expect(tx).to.emit(dripper, "DripRateUpdated"); - - rate = (await dripper.drip()).perSecond; - expect(rate).to.eq(oethUnits("2")); - }); - - it("Should not allow anyone else to change rate", async () => { - const { dripper, nick } = fixture; - - const tx = dripper.connect(nick).setDripRate( - oethUnits("1") // 1 WETH per second - ); - await expect(tx).to.be.revertedWith( - "Caller is not the Strategist or Governor" - ); - }); - - it("Should allow to disable rate", async () => { - const { dripper, strategist } = fixture; - - const tx = await dripper.connect(strategist).setDripRate( - "0" // Disable dripping - ); - await expect(tx).to.emit(dripper, "DripRateUpdated"); - - expect((await dripper.drip()).perSecond).to.eq("0"); - }); - - it("Should have disabled drip duration", async () => { - const { dripper, governor } = fixture; - - const tx = dripper.connect(governor).setDripDuration("7"); - await expect(tx).to.be.revertedWith("Drip duration disabled"); - }); -}); diff --git a/contracts/test/harvest/ousd-harvest-crv.mainnet.fork-test.js b/contracts/test/harvest/ousd-harvest-crv.mainnet.fork-test.js deleted file mode 100644 index f79dbd1f72..0000000000 --- a/contracts/test/harvest/ousd-harvest-crv.mainnet.fork-test.js +++ /dev/null @@ -1,125 +0,0 @@ -const { expect } = require("chai"); -const { parseUnits } = require("ethers").utils; - -const { usdtUnits } = require("../helpers"); -const { loadDefaultFixture } = require("../_fixture"); -const { MAX_UINT256 } = require("../../utils/constants"); - -describe("ForkTest: Harvest OUSD", function () { - this.timeout(0); - - let fixture; - beforeEach(async () => { - fixture = await loadDefaultFixture(); - }); - - describe.skip("deploy script CRV liquidation limit", function () { - it("config", async function () { - const { crv, harvester } = fixture; - const crvTokenConfig = await harvester.rewardTokenConfigs(crv.address); - expect(crvTokenConfig.liquidationLimit).to.be.eq(parseUnits("4000", 18)); - }); - it("should harvest", async function () { - const { crv, timelock, harvester, OUSDmetaStrategy } = fixture; - - const balanceBeforeHarvest = await crv.balanceOf(harvester.address); - - // prettier-ignore - await harvester - .connect(timelock)["harvest(address)"](OUSDmetaStrategy.address); - - const balanceAfterHarvest = await crv.balanceOf(harvester.address); - - const crvHarvested = balanceAfterHarvest.sub(balanceBeforeHarvest); - expect(crvHarvested).to.be.gt(parseUnits("20000", 18)); - }); - it("should harvest and swap", async function () { - const { anna, OUSDmetaStrategy, dripper, harvester, usdt } = fixture; - - const usdtBalanceBeforeDripper = await usdt.balanceOf(dripper.address); - - // prettier-ignore - await harvester - .connect(anna)["harvestAndSwap(address)"](OUSDmetaStrategy.address); - - const usdtBalanceAfterDripper = await usdt.balanceOf(dripper.address); - const usdtSwapped = usdtBalanceAfterDripper.sub(usdtBalanceBeforeDripper); - await expect(usdtSwapped).to.be.gt(usdtUnits("3100")); - }); - }); - describe.skip("no CRV liquidation limit", function () { - beforeEach(async () => { - const { crv, harvester, timelock } = fixture; - - const oldCrvTokenConfig = await harvester.rewardTokenConfigs(crv.address); - await harvester.connect(timelock).setRewardTokenConfig( - crv.address, - oldCrvTokenConfig.allowedSlippageBps, - oldCrvTokenConfig.harvestRewardBps, - 0, // Uniswap V2 compatible - oldCrvTokenConfig.swapPlatformAddr, - MAX_UINT256, - oldCrvTokenConfig.doSwapRewardToken - ); - }); - /* - * Skipping this test as it should only fail on a specific block number, where - * there is: - * - no liquidation limit - * - strategy has accrued a lot of CRV rewards - * - depth of the SushiSwap pool is not deep enough to handle the swap without - * hitting the slippage limit. - */ - it("should not harvest and swap", async function () { - const { anna, OUSDmetaStrategy, harvester } = fixture; - - // prettier-ignore - const tx = harvester - .connect(anna)["harvestAndSwap(address)"](OUSDmetaStrategy.address); - await expect(tx).to.be.revertedWith( - "UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT" - ); - }); - }); - describe.skip("CRV liquidation limit", function () { - const crvLimit = 4000; - beforeEach(async () => { - const { crv, harvester, timelock } = fixture; - - const oldCrvTokenConfig = await harvester.rewardTokenConfigs(crv.address); - - await harvester.connect(timelock).setRewardTokenConfig( - crv.address, - oldCrvTokenConfig.allowedSlippageBps, - oldCrvTokenConfig.harvestRewardBps, - 0, // Uniswap V2 compatible - oldCrvTokenConfig.swapPlatformAddr, - parseUnits(crvLimit.toString(), 18), - oldCrvTokenConfig.doSwapRewardToken - ); - }); - /* - * Skipping this test as it will only succeed again on a specific block number. - * If strategy doesn't have enough CRV not nearly enough rewards are going to be - * harvested for the test to pass. - */ - it("should harvest and swap", async function () { - const { crv, OUSDmetaStrategy, dripper, harvester, timelock, usdt } = - fixture; - - const balanceBeforeDripper = await usdt.balanceOf(dripper.address); - - // prettier-ignore - await harvester - .connect(timelock)["harvest(address)"](OUSDmetaStrategy.address); - await harvester.connect(timelock).swapRewardToken(crv.address); - - const balanceAfterDripper = await usdt.balanceOf(dripper.address); - const usdtSwapped = balanceAfterDripper.sub(balanceBeforeDripper); - - await expect(usdtSwapped, "USDT received").to.be.gt( - usdtUnits((crvLimit * 0.79).toString()) - ); - }); - }); -}); diff --git a/contracts/test/harvest/simple-harvester.mainnet.fork-test.js b/contracts/test/harvest/simple-harvester.mainnet.fork-test.js deleted file mode 100644 index 5975ebce6e..0000000000 --- a/contracts/test/harvest/simple-harvester.mainnet.fork-test.js +++ /dev/null @@ -1,323 +0,0 @@ -const { expect } = require("chai"); - -const addresses = require("../../utils/addresses"); -const { isCI, oethUnits } = require("../helpers"); -const { setERC20TokenBalance } = require("../_fund"); - -const { loadDefaultFixture } = require("../_fixture"); - -describe.skip("ForkTest: SimpleHarvester", function () { - this.timeout(0); - - // Retry up to 3 times on CI - this.retries(isCI ? 3 : 0); - - let fixture; - beforeEach(async () => { - fixture = await loadDefaultFixture(); - - // As convexEthMetaStrategy is no longer used, it does not have any rewards. - // We will set a balance to simulate rewards. - await setERC20TokenBalance( - fixture.convexEthMetaStrategy.address, - fixture.crv, - "1000" - ); - }); - - // --- Initial Parameters --- - it("Should have correct parameters", async () => { - const { simpleOETHHarvester } = fixture; - const { multichainStrategistAddr } = await getNamedAccounts(); - - expect(await simpleOETHHarvester.governor()).to.be.equal( - addresses.mainnet.Timelock - ); - - expect(await simpleOETHHarvester.strategistAddr()).to.be.equal( - multichainStrategistAddr - ); - }); - - // --- Harvest and Transfer Rewards --- - it("Should Harvest and transfer rewards (out of WETH) as strategist", async () => { - const { simpleOETHHarvester, convexEthMetaStrategy, crv, strategist } = - fixture; - - await ensureStrategyIsSupported( - simpleOETHHarvester, - convexEthMetaStrategy.address, - strategist - ); - - const balanceBeforeCRV = await crv.balanceOf(strategist.address); - // prettier-ignore - await simpleOETHHarvester - .connect(strategist)["harvestAndTransfer(address)"](convexEthMetaStrategy.address); - - const balanceAfterCRV = await crv.balanceOf(strategist.address); - expect(balanceAfterCRV).to.be.gt(balanceBeforeCRV); - }); - - it("Should Harvest and transfer rewards (only of WETH) as strategist", async () => { - const { - simpleOETHHarvester, - nativeStakingSSVStrategy, - weth, - strategist, - oethVault, - josh, - nativeStakingFeeAccumulator, - } = fixture; - - // Send ETH to the FeeAccumulator to simulate yield. - await josh.sendTransaction({ - to: nativeStakingFeeAccumulator.address, - value: oethUnits("1"), - }); - - const balanceBeforeWETH = await weth.balanceOf(oethVault.address); - // prettier-ignore - await simpleOETHHarvester - .connect(strategist)["harvestAndTransfer(address)"](nativeStakingSSVStrategy.address); - - const balanceAfterWETH = await weth.balanceOf(oethVault.address); - expect(balanceAfterWETH).to.be.gte(balanceBeforeWETH.add(oethUnits("1"))); - }); - - it("Should Harvest and transfer rewards (out of WETH) as governor", async () => { - const { - simpleOETHHarvester, - convexEthMetaStrategy, - strategist, - timelock, - crv, - } = fixture; - - const balanceBeforeCRV = await crv.balanceOf(strategist.address); - // prettier-ignore - await simpleOETHHarvester - .connect(timelock)["harvestAndTransfer(address)"](convexEthMetaStrategy.address); - - const balanceAfterCRV = await crv.balanceOf(strategist.address); - expect(balanceAfterCRV).to.be.gt(balanceBeforeCRV); - }); - - it("Should Harvest and transfer rewards (only of WETH) as governor", async () => { - const { - simpleOETHHarvester, - nativeStakingSSVStrategy, - weth, - timelock, - oethVault, - josh, - nativeStakingFeeAccumulator, - } = fixture; - - // Send ETH to the FeeAccumulator to simulate yield. - await josh.sendTransaction({ - to: nativeStakingFeeAccumulator.address, - value: oethUnits("1"), - }); - - const balanceBeforeWETH = await weth.balanceOf(oethVault.address); - // prettier-ignore - await simpleOETHHarvester - .connect(timelock)["harvestAndTransfer(address)"](nativeStakingSSVStrategy.address); - - const balanceAfterWETH = await weth.balanceOf(oethVault.address); - expect(balanceAfterWETH).to.be.gte(balanceBeforeWETH.add(oethUnits("1"))); - }); - - it("Should revert if strategy is not authorized", async () => { - const { simpleOETHHarvester, convexEthMetaStrategy, timelock } = fixture; - - await simpleOETHHarvester - .connect(timelock) - .setSupportedStrategy(addresses.mainnet.ConvexOETHAMOStrategy, false); - - await expect( - // prettier-ignore - simpleOETHHarvester - .connect(timelock)["harvestAndTransfer(address)"](convexEthMetaStrategy.address) - ).to.be.revertedWith("Strategy not supported"); - }); - - // --- Support Strategies --- - it("Should unsupport Strategy as governor", async () => { - const { simpleOETHHarvester, timelock } = fixture; - await simpleOETHHarvester - .connect(timelock) - .setSupportedStrategy(addresses.mainnet.ConvexOETHAMOStrategy, false); - expect( - await simpleOETHHarvester.supportedStrategies( - addresses.mainnet.ConvexOETHAMOStrategy - ) - ).to.be.equal(false); - }); - - it("Should unsupport Strategy as strategist", async () => { - const { simpleOETHHarvester, strategist } = fixture; - - await simpleOETHHarvester - .connect(strategist) - .setSupportedStrategy(addresses.mainnet.ConvexOETHAMOStrategy, false); - expect( - await simpleOETHHarvester - .connect(strategist) - .supportedStrategies(addresses.mainnet.ConvexOETHAMOStrategy) - ).to.be.equal(false); - }); - - it("Should revert if support strategy is not governor or strategist", async () => { - const { simpleOETHHarvester, josh } = fixture; - - await expect( - // prettier-ignore - simpleOETHHarvester - .connect(josh) - .setSupportedStrategy(addresses.mainnet.ConvexOETHAMOStrategy, true) - ).to.be.revertedWith("Caller is not the Strategist or Governor"); - }); - - it("Should revert if strategy is address 0", async () => { - const { simpleOETHHarvester, timelock } = fixture; - - await expect( - // prettier-ignore - simpleOETHHarvester - .connect(timelock).setSupportedStrategy(addresses.zero, true) - ).to.be.revertedWith("Invalid strategy"); - }); - - // --- Set Strategist --- - it("Should revert when setting strategist is not governor", async () => { - const { simpleOETHHarvester, josh } = fixture; - - await expect( - // prettier-ignore - simpleOETHHarvester - .connect(josh) - .setStrategistAddr(josh.address) - ).to.be.revertedWith("Caller is not the Governor"); - }); - - it("Should Set strategist", async () => { - const { simpleOETHHarvester, timelock, josh } = fixture; - - expect(await simpleOETHHarvester.strategistAddr()).not.to.equal( - josh.address - ); - await simpleOETHHarvester.connect(timelock).setStrategistAddr(josh.address); - expect(await simpleOETHHarvester.strategistAddr()).to.equal(josh.address); - }); - - it("Should Harvest and transfer rewards as strategist", async () => { - const { simpleOETHHarvester, convexEthMetaStrategy, crv, strategist } = - fixture; - - const balanceBeforeCRV = await crv.balanceOf(strategist.address); - await simpleOETHHarvester - .connect(strategist) - .setSupportedStrategy(convexEthMetaStrategy.address, true); - // prettier-ignore - await simpleOETHHarvester - .connect(strategist)["harvestAndTransfer(address)"](convexEthMetaStrategy.address); - - const balanceAfterCRV = await crv.balanceOf(strategist.address); - expect(balanceAfterCRV).to.be.gt(balanceBeforeCRV); - }); - - it("Should Harvest and transfer rewards as governor", async () => { - const { - simpleOETHHarvester, - convexEthMetaStrategy, - strategist, - timelock, - crv, - } = fixture; - - const balanceBeforeCRV = await crv.balanceOf(strategist.address); - - await simpleOETHHarvester - .connect(timelock) - .setSupportedStrategy(convexEthMetaStrategy.address, true); - // prettier-ignore - await simpleOETHHarvester - .connect(timelock)["harvestAndTransfer(address)"](convexEthMetaStrategy.address); - - const balanceAfterCRV = await crv.balanceOf(strategist.address); - expect(balanceAfterCRV).to.be.gt(balanceBeforeCRV); - }); - - it("Should revert if strategy is not authorized", async () => { - const { simpleOETHHarvester, convexEthMetaStrategy, timelock } = fixture; - - await simpleOETHHarvester - .connect(timelock) - .setSupportedStrategy(addresses.mainnet.ConvexOETHAMOStrategy, false); - - await expect( - // prettier-ignore - simpleOETHHarvester - .connect(timelock)["harvestAndTransfer(address)"](convexEthMetaStrategy.address) - ).to.be.revertedWith("Strategy not supported"); - }); - - it("Should revert if strategy is address 0", async () => { - const { simpleOETHHarvester, timelock } = fixture; - - await expect( - // prettier-ignore - simpleOETHHarvester - .connect(timelock).setSupportedStrategy(addresses.zero, true) - ).to.be.revertedWith("Invalid strategy"); - }); - - it("Should test to rescue tokens as governor", async () => { - const { simpleOETHHarvester, timelock, crv } = fixture; - - await setERC20TokenBalance(simpleOETHHarvester.address, crv, "1000"); - const balanceBeforeCRV = await crv.balanceOf(simpleOETHHarvester.address); - await simpleOETHHarvester - .connect(timelock) - .transferToken(crv.address, "1000"); - const balanceAfterCRV = await crv.balanceOf(simpleOETHHarvester.address); - expect(balanceAfterCRV).to.be.lt(balanceBeforeCRV); - }); - - it("Should test to rescue tokens as strategist", async () => { - const { simpleOETHHarvester, strategist, crv } = fixture; - - await setERC20TokenBalance(simpleOETHHarvester.address, crv, "1000"); - const balanceBeforeCRV = await crv.balanceOf(simpleOETHHarvester.address); - await simpleOETHHarvester - .connect(strategist) - .transferToken(crv.address, "1000"); - const balanceAfterCRV = await crv.balanceOf(simpleOETHHarvester.address); - expect(balanceAfterCRV).to.be.lt(balanceBeforeCRV); - }); - - // --- Set Dripper --- - it("Should Set Dripper as governor", async () => { - const { simpleOETHHarvester, timelock, josh } = fixture; - - await simpleOETHHarvester.connect(timelock).setDripper(josh.address); - - expect(await simpleOETHHarvester.dripper()).to.equal(josh.address); - }); - - it("Should revert when setting dripper due to address 0", async () => { - const { simpleOETHHarvester, timelock } = fixture; - - await expect( - simpleOETHHarvester.connect(timelock).setDripper(addresses.zero) - ).to.be.revertedWith("Invalid dripper"); - }); - - const ensureStrategyIsSupported = async (harvester, strategy, strategist) => { - if (!(await harvester.supportedStrategies(strategy))) { - await harvester.connect(strategist).setSupportedStrategy(strategy, true); - } - }; -}); diff --git a/contracts/test/helpers.js b/contracts/test/helpers.js index 2a3b20f876..bc2a25c130 100644 --- a/contracts/test/helpers.js +++ b/contracts/test/helpers.js @@ -248,10 +248,6 @@ function usdcUnitsFormat(amount) { return formatUnits(amount, 6); } -function tusdUnits(amount) { - return parseUnits(amount, 18); -} - function usdsUnits(amount) { return parseUnits(amount, 18); } @@ -268,14 +264,6 @@ function oracleUnits(amount) { return parseUnits(amount, 6); } -function cDaiUnits(amount) { - return parseUnits(amount, 8); -} - -function cUsdcUnits(amount) { - return parseUnits(amount, 8); -} - /** * Asserts that the total supply of a contract is approximately equal to an expected value, with a tolerance of 0.1%. * @param {Contract} contract - The token contract to check the total supply of. @@ -363,154 +351,19 @@ const advanceBlocks = async (numBlocks) => { await hre.network.provider.send("hardhat_mine", [blocksHex]); }; -const getOracleAddress = async (deployments) => { - return (await deployments.get("OracleRouter")).address; -}; - -/** - * Sets the price in USD the mix oracle will return for a specific token. - * This first sets the ETH price in USD, then token price in ETH - * - * @param {string} tokenSymbol: "DAI", USDC", etc... - * @param {number} usdPrice: price of the token in USD. - * @returns {Promise} - */ -const setOracleTokenPriceUsd = async (tokenSymbol, usdPrice) => { - const symbolMap = { - USDC: 6, - USDT: 6, - DAI: 6, - USDS: 18, - COMP: 6, - CVX: 6, - CRV: 6, - }; - - if (isMainnetOrFork) { - throw new Error( - `setOracleTokenPriceUsd not supported on network ${hre.network.name}` - ); - } - // Set the chainlink token price in USD, with 8 decimals. - const tokenFeed = await ethers.getContract( - "MockChainlinkOracleFeed" + tokenSymbol - ); - - const decimals = Object.keys(symbolMap).includes(tokenSymbol) - ? symbolMap[tokenSymbol] - : 18; - await tokenFeed.setDecimals(decimals); - await tokenFeed.setPrice(parseUnits(usdPrice, decimals)); -}; - -const getOracleAddresses = async (deployments) => { - if (isMainnetOrFork) { - // On mainnet or fork, return mainnet addresses. - return { - chainlink: { - ETH_USD: addresses.mainnet.chainlinkETH_USD, - DAI_USD: addresses.mainnet.chainlinkDAI_USD, - // Use same Oracle as DAI for USDS - USDS_USD: addresses.mainnet.chainlinkDAI_USD, - USDC_USD: addresses.mainnet.chainlinkUSDC_USD, - USDT_USD: addresses.mainnet.chainlinkUSDT_USD, - COMP_USD: addresses.mainnet.chainlinkCOMP_USD, - AAVE_USD: addresses.mainnet.chainlinkAAVE_USD, - CRV_USD: addresses.mainnet.chainlinkCRV_USD, - CVX_USD: addresses.mainnet.chainlinkCVX_USD, - OGN_ETH: addresses.mainnet.chainlinkOGN_ETH, - RETH_ETH: addresses.mainnet.chainlinkRETH_ETH, - stETH_ETH: addresses.mainnet.chainlinkstETH_ETH, - BAL_ETH: addresses.mainnet.chainlinkBAL_ETH, - // TODO: Update with deployed address - // AURA_ETH: addresses.mainnet.chainlinkAURA_ETH, - }, - openOracle: addresses.mainnet.openOracle, // Deprecated - }; - } else { - // On other environments, return mock feeds. - return { - chainlink: { - ETH_USD: (await deployments.get("MockChainlinkOracleFeedETH")).address, - DAI_USD: (await deployments.get("MockChainlinkOracleFeedDAI")).address, - USDS_USD: (await deployments.get("MockChainlinkOracleFeedUSDS")) - .address, - USDC_USD: (await deployments.get("MockChainlinkOracleFeedUSDC")) - .address, - USDT_USD: (await deployments.get("MockChainlinkOracleFeedUSDT")) - .address, - TUSD_USD: (await deployments.get("MockChainlinkOracleFeedTUSD")) - .address, - COMP_USD: (await deployments.get("MockChainlinkOracleFeedCOMP")) - .address, - AAVE_USD: (await deployments.get("MockChainlinkOracleFeedAAVE")) - .address, - CRV_USD: (await deployments.get("MockChainlinkOracleFeedCRV")).address, - CVX_USD: (await deployments.get("MockChainlinkOracleFeedCVX")).address, - OGN_ETH: (await deployments.get("MockChainlinkOracleFeedOGNETH")) - .address, - RETH_ETH: (await deployments.get("MockChainlinkOracleFeedRETHETH")) - .address, - STETH_ETH: (await deployments.get("MockChainlinkOracleFeedstETHETH")) - .address, - FRXETH_ETH: (await deployments.get("MockChainlinkOracleFeedfrxETHETH")) - .address, - WETH_ETH: (await deployments.get("MockChainlinkOracleFeedWETHETH")) - .address, - BAL_ETH: (await deployments.get("MockChainlinkOracleFeedBALETH")) - .address, - NonStandardToken_USD: ( - await deployments.get("MockChainlinkOracleFeedNonStandardToken") - ).address, - }, - }; - } -}; - const getAssetAddresses = async (deployments) => { if (isMainnetOrFork) { return { USDT: addresses.mainnet.USDT, USDC: addresses.mainnet.USDC, - TUSD: addresses.mainnet.TUSD, DAI: addresses.mainnet.DAI, USDS: addresses.mainnet.USDS, - cUSDC: addresses.mainnet.cUSDC, - cUSDT: addresses.mainnet.cUSDT, WETH: addresses.mainnet.WETH, - COMP: addresses.mainnet.COMP, - ThreePool: addresses.mainnet.ThreePool, - ThreePoolToken: addresses.mainnet.ThreePoolToken, - ThreePoolGauge: addresses.mainnet.ThreePoolGauge, - CRV: addresses.mainnet.CRV, - CVX: addresses.mainnet.CVX, - CVXLocker: addresses.mainnet.CVXLocker, - CRVMinter: addresses.mainnet.CRVMinter, - aDAI: addresses.mainnet.aDAI, - aDAI_v2: addresses.mainnet.aDAI_v2, - aUSDC: addresses.mainnet.aUSDC, - aUSDT: addresses.mainnet.aUSDT, - aWETH: addresses.mainnet.aWETH, - cDAI: addresses.mainnet.cDAI, - AAVE: addresses.mainnet.Aave, - AAVE_TOKEN: addresses.mainnet.Aave, - AAVE_ADDRESS_PROVIDER: addresses.mainnet.AAVE_ADDRESS_PROVIDER, - AAVE_INCENTIVES_CONTROLLER: addresses.mainnet.AAVE_INCENTIVES_CONTROLLER, - STKAAVE: addresses.mainnet.STKAAVE, OGN: addresses.mainnet.OGN, - OGV: addresses.mainnet.OGV, - RewardsSource: addresses.mainnet.RewardsSource, - RETH: addresses.mainnet.rETH, - frxETH: addresses.mainnet.frxETH, - stETH: addresses.mainnet.stETH, - sfrxETH: addresses.mainnet.sfrxETH, uniswapRouter: addresses.mainnet.uniswapRouter, uniswapV3Router: addresses.mainnet.uniswapV3Router, uniswapUniversalRouter: addresses.mainnet.uniswapUniversalRouter, sushiswapRouter: addresses.mainnet.sushiswapRouter, - auraWeightedOraclePool: addresses.mainnet.AuraWeightedOraclePool, - AURA: addresses.mainnet.AURA, - BAL: addresses.mainnet.BAL, SSV: addresses.mainnet.SSV, SSVNetwork: addresses.mainnet.SSVNetwork, beaconChainDepositContract: addresses.mainnet.beaconChainDepositContract, @@ -533,47 +386,15 @@ const getAssetAddresses = async (deployments) => { const addressMap = { USDT: (await deployments.get("MockUSDT")).address, USDC: (await deployments.get("MockUSDC")).address, - TUSD: (await deployments.get("MockTUSD")).address, USDS: (await deployments.get("MockUSDS")).address, - cUSDC: (await deployments.get("MockCUSDC")).address, - cUSDT: (await deployments.get("MockCUSDT")).address, - cDAI: (await deployments.get("MockCDAI")).address, - cUSDS: (await deployments.get("MockCUSDS")).address, NonStandardToken: (await deployments.get("MockNonStandardToken")).address, WETH: addresses.mainnet.WETH, - COMP: (await deployments.get("MockCOMP")).address, - ThreePool: (await deployments.get("MockCurvePool")).address, - ThreePoolToken: (await deployments.get("Mock3CRV")).address, - ThreePoolGauge: (await deployments.get("MockCurveGauge")).address, - CRV: (await deployments.get("MockCRV")).address, - CVX: (await deployments.get("MockCVX")).address, - CVXLocker: (await deployments.get("MockCVXLocker")).address, - CRVMinter: (await deployments.get("MockCRVMinter")).address, - aDAI: (await deployments.get("MockADAI")).address, - aUSDC: (await deployments.get("MockAUSDC")).address, - aUSDT: (await deployments.get("MockAUSDT")).address, - AAVE: (await deployments.get("MockAave")).address, - AAVE_TOKEN: (await deployments.get("MockAAVEToken")).address, - AAVE_ADDRESS_PROVIDER: (await deployments.get("MockAave")).address, - STKAAVE: (await deployments.get("MockStkAave")).address, OGN: (await deployments.get("MockOGN")).address, - OGV: (await deployments.get("MockOGV")).address, - RETH: (await deployments.get("MockRETH")).address, - stETH: (await deployments.get("MockstETH")).address, - frxETH: (await deployments.get("MockfrxETH")).address, - sfrxETH: (await deployments.get("MocksfrxETH")).address, - // Note: This is only used to transfer the swapped OGV in `Buyback` contract. - // So, as long as this is a valid address, it should be fine. - RewardsSource: addresses.dead, uniswapRouter: (await deployments.get("MockUniswapRouter")).address, uniswapV3Router: (await deployments.get("MockUniswapRouter")).address, uniswapUniversalRouter: (await deployments.get("MockUniswapRouter")) .address, sushiswapRouter: (await deployments.get("MockUniswapRouter")).address, - auraWeightedOraclePool: (await deployments.get("MockOracleWeightedPool")) - .address, - AURA: (await deployments.get("MockAura")).address, - BAL: (await deployments.get("MockBAL")).address, SSV: (await deployments.get("MockSSV")).address, SSVNetwork: (await deployments.get("MockSSVNetwork")).address, beaconChainDepositContract: (await deployments.get("MockDepositContract")) @@ -793,14 +614,11 @@ module.exports = { oethUnits, usdtUnits, usdcUnits, - tusdUnits, usdsUnits, ognUnits, ethUnits, fraxUnits, oracleUnits, - cDaiUnits, - cUsdcUnits, frxETHUnits, units, ousdUnitsFormat, @@ -844,9 +662,6 @@ module.exports = { isHoodi, isHoodiFork, isHoodiOrFork, - getOracleAddress, - setOracleTokenPriceUsd, - getOracleAddresses, getAssetAddresses, governorArgs, proposeArgs, diff --git a/contracts/test/oracle/oracle.mainnet.fork-test.js b/contracts/test/oracle/oracle.mainnet.fork-test.js deleted file mode 100644 index bf41cd822a..0000000000 --- a/contracts/test/oracle/oracle.mainnet.fork-test.js +++ /dev/null @@ -1,34 +0,0 @@ -const { expect } = require("chai"); -const { parseUnits } = require("ethers/lib/utils"); - -const { loadDefaultFixture } = require("../_fixture"); -const { isCI } = require("../helpers"); - -describe("ForkTest: OETH Oracle Routers", function () { - this.timeout(0); - - // Retry up to 3 times on CI - this.retries(isCI ? 3 : 0); - - let fixture, oethOracleRouter; - beforeEach(async () => { - fixture = await loadDefaultFixture(); - oethOracleRouter = await ethers.getContract("OETHOracleRouter"); - }); - - it("should get WETH price", async () => { - const { weth } = fixture; - - const price = await oethOracleRouter.price(weth.address); - expect(price).to.eq(parseUnits("1", 18)); - }); - - it("should get gas costs of weth", async () => { - const { weth, josh } = fixture; - - const tx = await oethOracleRouter - .connect(josh) - .populateTransaction.price(weth.address); - await josh.sendTransaction(tx); - }); -}); diff --git a/contracts/test/poolBooster/poolBooster.mainnet.fork-test.js b/contracts/test/poolBooster/poolBooster.mainnet.fork-test.js index 4487f2c0d1..8825b7a2fc 100644 --- a/contracts/test/poolBooster/poolBooster.mainnet.fork-test.js +++ b/contracts/test/poolBooster/poolBooster.mainnet.fork-test.js @@ -42,7 +42,7 @@ describe("ForkTest: Merkl Pool Booster", function () { }); it("Should have OETH token supported by Merkl Distributor", async () => { - expect(await merklDistributor.rewardTokenMinAmounts(oeth.address)).to.equal( + expect(await merklDistributor.rewardTokenMinAmounts(oeth.address)).to.gt( oethUnits("0.00001") ); }); diff --git a/contracts/test/safe-modules/claim-rewards.mainnet.fork-test.js b/contracts/test/safe-modules/claim-rewards.mainnet.fork-test.js index 940044984a..c9d49ef12b 100644 --- a/contracts/test/safe-modules/claim-rewards.mainnet.fork-test.js +++ b/contracts/test/safe-modules/claim-rewards.mainnet.fork-test.js @@ -1,21 +1,27 @@ +const { expect } = require("chai"); +const { ethers } = require("hardhat"); const { createFixtureLoader, claimRewardsModuleFixture, } = require("../_fixture"); -const { expect } = require("chai"); -const { ethers } = require("hardhat"); +const addresses = require("../../utils/addresses"); + +const erc20Abi = require("../../abi/erc20.json"); const mainnetFixture = createFixtureLoader(claimRewardsModuleFixture); describe("ForkTest: Claim Strategy Rewards Safe Module", function () { let fixture; + let crv; beforeEach(async () => { fixture = await mainnetFixture(); + + crv = await ethers.getContractAt(erc20Abi, addresses.mainnet.CRV); }); it("Should claim CRV rewards", async () => { - const { crv, safeSigner, claimRewardsModule } = fixture; + const { safeSigner, claimRewardsModule } = fixture; const cOUSDCurveAMOProxy = await ethers.getContract("OUSDCurveAMOProxy"); const cOETHCurveAMOProxy = await ethers.getContract("OETHCurveAMOProxy"); diff --git a/contracts/test/strategies/aave.js b/contracts/test/strategies/aave.js deleted file mode 100644 index 5abd9c98d7..0000000000 --- a/contracts/test/strategies/aave.js +++ /dev/null @@ -1,340 +0,0 @@ -const { expect } = require("chai"); -const { utils } = require("ethers"); - -const { createFixtureLoader, aaveVaultFixture } = require("../_fixture"); -const { - ousdUnits, - units, - expectApproxSupply, - getBlockTimestamp, - isFork, -} = require("../helpers"); -const { shouldBehaveLikeGovernable } = require("../behaviour/governable"); -const { shouldBehaveLikeHarvestable } = require("../behaviour/harvestable"); -const { shouldBehaveLikeStrategy } = require("../behaviour/strategy"); - -describe.skip("Aave Strategy", function () { - if (isFork) { - this.timeout(0); - } - - let anna, - matt, - josh, - ousd, - vault, - harvester, - governor, - adai, - aaveStrategy, - usdt, - usdc, - dai, - aaveAddressProvider, - aaveCoreAddress; - - const emptyVault = async () => { - await vault.connect(matt).redeem(ousd.balanceOf(matt.address), 0); - await vault.connect(josh).redeem(ousd.balanceOf(matt.address), 0); - }; - - const mint = async (amount, asset) => { - await asset.connect(anna).mint(await units(amount, asset)); - await asset - .connect(anna) - .approve(vault.address, await units(amount, asset)); - await vault - .connect(anna) - .mint(asset.address, await units(amount, asset), 0); - }; - - let fixture; - const loadFixture = createFixtureLoader(aaveVaultFixture); - beforeEach(async function () { - fixture = await loadFixture(); - anna = fixture.anna; - matt = fixture.matt; - josh = fixture.josh; - vault = fixture.vault; - harvester = fixture.harvester; - ousd = fixture.ousd; - governor = fixture.governor; - aaveStrategy = fixture.aaveStrategy; - adai = fixture.adai; - usdt = fixture.usdt; - usdc = fixture.usdc; - dai = fixture.dai; - aaveAddressProvider = fixture.aaveAddressProvider; - aaveCoreAddress = await aaveAddressProvider.getLendingPool(); - }); - - shouldBehaveLikeGovernable(() => ({ - ...fixture, - strategy: fixture.aaveStrategy, - })); - - shouldBehaveLikeHarvestable(() => ({ - ...fixture, - harvester: fixture.harvester, - strategy: fixture.aaveStrategy, - })); - - shouldBehaveLikeStrategy(() => ({ - ...fixture, - strategy: fixture.aaveStrategy, - assets: [fixture.dai], - vault: fixture.vault, - })); - - describe("Mint", function () { - it("Should be able to mint DAI and it should show up in the Aave core", async function () { - await expectApproxSupply(ousd, ousdUnits("200")); - // we already have 200 dai in vault - await expect(vault).has.an.approxBalanceOf("200", dai); - await mint("30000.00", dai); - await expectApproxSupply(ousd, ousdUnits("30200")); - // should allocate all of it to strategy - await expect(aaveStrategy).has.an.approxBalanceOf("30200", adai); - await expect(anna).to.have.a.balanceOf("30000", ousd); - expect(await dai.balanceOf(aaveCoreAddress)).to.be.equal( - utils.parseUnits("30200", 18) - ); - }); - - it("Should not send USDC to Aave strategy", async function () { - await emptyVault(); - // should be all empty - await expectApproxSupply(ousd, ousdUnits("0")); - await mint("30000.00", usdc); - await expectApproxSupply(ousd, ousdUnits("30000")); - await expect(aaveStrategy).has.an.approxBalanceOf("0", dai); - await vault.connect(anna).redeem(ousdUnits("30000.00"), 0); - }); - - it("Should be able to mint and redeem DAI", async function () { - await expectApproxSupply(ousd, ousdUnits("200")); - await mint("30000.00", dai); - await vault.connect(anna).redeem(ousdUnits("20000"), 0); - await expectApproxSupply(ousd, ousdUnits("10200")); - // Anna started with 1000 DAI - await expect(anna).to.have.a.balanceOf("21000", dai); - await expect(anna).to.have.a.balanceOf("10000", ousd); - }); - - it("Should be able to withdrawAll", async function () { - await expectApproxSupply(ousd, ousdUnits("200")); - await mint("30000.00", dai); - await vault - .connect(governor) - .withdrawAllFromStrategy(aaveStrategy.address); - await expect(aaveStrategy).to.have.a.balanceOf("0", dai); - }); - - it("Should be able to redeem and return assets after multiple mints", async function () { - await mint("30000.00", usdt); - await mint("30000.00", usdc); - await mint("30000.00", dai); - await vault.connect(anna).redeem(ousdUnits("60000.00"), 0); - // Anna had 1000 of each asset before the mints - // 200 DAI was already in the Vault - // 30200 DAI, 30000 USDT, 30000 USDC - // 30200 / 90200 * 30000 + 1000 DAI - // 30000 / 90200 * 30000 + 1000 USDC and USDT - await expect(anna).to.have.an.approxBalanceOf("21088.69", dai); - await expect(anna).to.have.an.approxBalanceOf("20955.65", usdc); - await expect(anna).to.have.an.approxBalanceOf("20955.65", usdt); - await expectApproxSupply(ousd, ousdUnits("30200")); - }); - }); - - describe("Rewards", function () { - const STAKE_AMOUNT = "10000000000"; - const REWARD_AMOUNT = "70000000000"; - const ZERO_COOLDOWN = -1; - const DAY = 24 * 60 * 60; - - const collectRewards = function (setupOpts, verificationOpts) { - return async function () { - let currentTimestamp; - - const aaveStrategy = fixture.aaveStrategy; - const aaveIncentives = fixture.aaveIncentivesController; - const aave = fixture.aaveToken; - const stkAave = fixture.stkAave; - const governor = fixture.governor; - - let { cooldownAgo, hasStkAave, hasRewards } = setupOpts; - // Options - let stkAaveAmount = hasStkAave ? STAKE_AMOUNT : 0; - cooldownAgo = cooldownAgo == ZERO_COOLDOWN ? 0 : cooldownAgo; - let rewardsAmount = hasRewards ? REWARD_AMOUNT : 0; - - // Configure - // ---- - // - Give some AAVE to stkAAVE so that we can redeem the stkAAVE - await aave.connect(governor).mint(stkAaveAmount); - await aave.connect(governor).transfer(stkAave.address, stkAaveAmount); - - // Setup for test - // ---- - if (cooldownAgo > 0) { - currentTimestamp = await getBlockTimestamp(); - let cooldown = currentTimestamp - cooldownAgo; - await stkAave.setCooldown(aaveStrategy.address, cooldown); - } - if (stkAaveAmount > 0) { - await stkAave.connect(governor).mint(stkAaveAmount); - await stkAave - .connect(governor) - .transfer(aaveStrategy.address, stkAaveAmount); - } - if (rewardsAmount > 0) { - await aaveIncentives.setRewardsBalance( - aaveStrategy.address, - rewardsAmount - ); - } - - // Disable swap - await harvester.connect(governor).setRewardTokenConfig( - aave.address, - { - allowedSlippageBps: 200, - harvestRewardBps: 0, - swapPlatformAddr: aave.address, - doSwapRewardToken: false, - swapPlatform: 0, - liquidationLimit: 0, - }, - utils.defaultAbiCoder.encode( - ["address[]"], - [[aave.address, fixture.usdt.address]] - ) - ); - - // Run - // ---- - // prettier-ignore - await harvester - .connect(governor)["harvestAndSwap(address)"](aaveStrategy.address); - currentTimestamp = await getBlockTimestamp(); - - // Verification - // ---- - const { - shouldConvertStkAAVEToAAVE, - shouldResetCooldown, - shouldClaimRewards, - } = verificationOpts; - if (shouldConvertStkAAVEToAAVE) { - const stratAave = await aave.balanceOf(aaveStrategy.address); - expect(stratAave).to.equal("0", "AAVE:Strategy"); - const harvesterAave = await aave.balanceOf(harvester.address); - expect(harvesterAave).to.equal(STAKE_AMOUNT, "AAVE:Vault"); - } else { - const stratAave = await aave.balanceOf(aaveStrategy.address); - expect(stratAave).to.equal("0", "AAVE:Strategy"); - const harvesterAave = await aave.balanceOf(harvester.address); - expect(harvesterAave).to.equal("0", "AAVE:Vault"); - } - - if (shouldResetCooldown) { - const cooldown = await stkAave.stakersCooldowns(aaveStrategy.address); - expect(currentTimestamp).to.equal(cooldown, "Cooldown should reset"); - } else { - const cooldown = await stkAave.stakersCooldowns(aaveStrategy.address); - expect(currentTimestamp).to.not.equal(cooldown, "Cooldown not reset"); - } - - if (shouldClaimRewards === true) { - const stratStkAave = await stkAave.balanceOf(aaveStrategy.address); - expect(stratStkAave).to.be.at.least( - REWARD_AMOUNT, - "StkAAVE:Strategy" - ); - } else if (shouldClaimRewards === false) { - const stratStkAave = await stkAave.balanceOf(aaveStrategy.address); - expect(stratStkAave).to.be.below(REWARD_AMOUNT, "StkAAVE:Strategy"); - } else { - expect(false).to.be.true("shouldclaimRewards is not defined"); - } - }; - }; - - it( - "In cooldown window", - collectRewards( - { - cooldownAgo: 11 * DAY, - hasStkAave: true, - hasRewards: true, - }, - { - shouldConvertStkAAVEToAAVE: true, - shouldResetCooldown: true, - shouldClaimRewards: true, - } - ) - ); - it( - "Before cooldown window", - collectRewards( - { - cooldownAgo: 2 * DAY, - hasStkAave: true, - hasRewards: true, - }, - { - shouldConvertStkAAVEToAAVE: false, - shouldResetCooldown: false, - shouldClaimRewards: false, - } - ) - ); - it( - "No cooldown set", - collectRewards( - { - cooldownAgo: ZERO_COOLDOWN, - hasStkAave: true, - hasRewards: true, - }, - { - shouldConvertStkAAVEToAAVE: false, - shouldResetCooldown: true, - shouldClaimRewards: true, - } - ) - ); - it( - "After window", - collectRewards( - { - cooldownAgo: 13 * DAY, - hasStkAave: true, - hasRewards: true, - }, - { - shouldConvertStkAAVEToAAVE: false, - shouldResetCooldown: true, - shouldClaimRewards: true, - } - ) - ); - it( - "No pending rewards", - collectRewards( - { - cooldownAgo: 11 * DAY, - hasStkAave: true, - hasRewards: false, - }, - { - shouldConvertStkAAVEToAAVE: true, - shouldResetCooldown: false, - shouldClaimRewards: false, - } - ) - ); - }); -}); diff --git a/contracts/test/strategies/aave.mainnet.fork-test.js b/contracts/test/strategies/aave.mainnet.fork-test.js deleted file mode 100644 index a3c473941c..0000000000 --- a/contracts/test/strategies/aave.mainnet.fork-test.js +++ /dev/null @@ -1,187 +0,0 @@ -const { expect } = require("chai"); - -const { - units, - ousdUnits, - advanceBlocks, - advanceTime, - isCI, -} = require("../helpers"); -const { createFixtureLoader, aaveFixture } = require("../_fixture"); -const { impersonateAndFund } = require("../../utils/signers"); - -describe.skip("ForkTest: Aave Strategy", function () { - this.timeout(0); - - // Retry up to 3 times on CI - this.retries(isCI ? 3 : 0); - - let fixture; - const loadFixture = createFixtureLoader(aaveFixture); - beforeEach(async () => { - fixture = await loadFixture(); - }); - - describe("Mint", function () { - it("Should deploy USDT in Aave", async function () { - const { josh, usdt } = fixture; - await mintTest(fixture, josh, usdt, "200000"); - }); - }); - - describe("Redeem", function () { - it("Should redeem from Aave", async () => { - const { vault, ousd, usdt, anna } = fixture; - - await vault.connect(anna).rebase(); - - const supplyBeforeMint = await ousd.totalSupply(); - - const amount = "30100"; - - // Mint with USDT - await vault - .connect(anna) - .mint(usdt.address, await units(amount, usdt), 0); - - const currentSupply = await ousd.totalSupply(); - const supplyAdded = currentSupply.sub(supplyBeforeMint); - expect(supplyAdded).to.approxEqualTolerance(ousdUnits("30000"), 1); - - const currentBalance = await ousd.connect(anna).balanceOf(anna.address); - - // Now try to redeem 30k - await vault.connect(anna).redeem(ousdUnits("30000"), 0); - - // User balance should be down by 30k - const newBalance = await ousd.connect(anna).balanceOf(anna.address); - expect(newBalance).to.approxEqualTolerance( - currentBalance.sub(ousdUnits("30000")), - 1 - ); - - const newSupply = await ousd.totalSupply(); - const supplyDiff = currentSupply.sub(newSupply); - - expect(supplyDiff).to.approxEqualTolerance(ousdUnits("30000"), 1); - }); - }); - - describe("Withdraw", function () { - it("Should be able to withdraw USDT from strategy", async function () { - const { franck, usdt } = fixture; - await withdrawTest(fixture, franck, usdt, "5000"); - }); - - it("Should be able to withdrawAll from strategy", async function () { - const { matt, usdc, vault, usdt, aaveStrategy } = fixture; - const vaultSigner = await impersonateAndFund(vault.address); - const amount = "110000"; - - const usdcUnits = await units(amount, usdc); - const usdtUnits = await units(amount, usdt); - - await vault.connect(matt).mint(usdt.address, usdtUnits, 0); - await vault.connect(matt).mint(usdc.address, usdcUnits, 0); - - await vault.connect(matt).rebase(); - await vault.connect(matt).allocate(); - - const vaultUsdtBefore = await usdt.balanceOf(vault.address); - const vaultUsdcBefore = await usdc.balanceOf(vault.address); - - const stratBalUsdc = await aaveStrategy.checkBalance(usdc.address); - const stratBalUsdt = await aaveStrategy.checkBalance(usdt.address); - - await aaveStrategy.connect(vaultSigner).withdrawAll(); - - const vaultUsdtDiff = - (await usdt.balanceOf(vault.address)) - vaultUsdtBefore; - const vaultUsdcDiff = - (await usdc.balanceOf(vault.address)) - vaultUsdcBefore; - - expect(vaultUsdcDiff).to.approxEqualTolerance(stratBalUsdc, 1); - expect(vaultUsdtDiff).to.approxEqualTolerance(stratBalUsdt, 1); - - expect(await aaveStrategy.checkBalance(usdc.address)).to.equal("0"); - expect(await aaveStrategy.checkBalance(usdt.address)).to.equal("0"); - }); - }); - - // set it as a last test that executes because we advance time and theat - // messes with recency of oracle prices - describe("Supply Revenue", function () { - it("Should get supply interest", async function () { - const { anna, usdt, aaveStrategy } = fixture; - await mintTest(fixture, anna, usdt, "110000"); - - const currentBalance = await aaveStrategy.checkBalance(usdt.address); - - await advanceTime(60 * 60 * 24 * 365); - await advanceBlocks(10000); - - const balanceAfter1Y = await aaveStrategy.checkBalance(usdt.address); - - const diff = balanceAfter1Y.sub(currentBalance); - expect(diff).to.be.gt(0); - }); - }); -}); - -async function mintTest(fixture, user, asset, amount = "30000") { - const { vault, ousd, aaveStrategy } = fixture; - - await vault.connect(user).rebase(); - await vault.connect(user).allocate(); - - const unitAmount = await units(amount, asset); - - const currentSupply = await ousd.totalSupply(); - const currentBalance = await ousd.connect(user).balanceOf(user.address); - const currentStrategyBalance = await aaveStrategy.checkBalance(asset.address); - - // Mint OUSD w/ asset - await vault.connect(user).mint(asset.address, unitAmount, 0); - await vault.connect(user).allocate(); - - const newBalance = await ousd.connect(user).balanceOf(user.address); - const newSupply = await ousd.totalSupply(); - const newStrategyBalance = await aaveStrategy.checkBalance(asset.address); - - const balanceDiff = newBalance.sub(currentBalance); - // Ensure user has correct balance (w/ 1% slippage tolerance) - expect(balanceDiff).to.approxEqualTolerance(ousdUnits(amount), 1); - - // Supply checks - const supplyDiff = newSupply.sub(currentSupply); - const ousdUnitAmount = ousdUnits(amount); - - expect(supplyDiff).to.approxEqualTolerance(ousdUnitAmount, 1); - - const liquidityDiff = newStrategyBalance.sub(currentStrategyBalance); - - // Should have liquidity in Aave - expect(liquidityDiff).to.approxEqualTolerance(await units(amount, asset), 1); -} - -async function withdrawTest(fixture, user, asset, amount) { - const { vault, aaveStrategy } = fixture; - if (amount) { - await mintTest(fixture, user, asset, amount); - } - - const assetUnits = amount - ? await units(amount, asset) - : await aaveStrategy.checkBalance(asset.address); - const vaultAssetBalBefore = await asset.balanceOf(vault.address); - const vaultSigner = await impersonateAndFund(vault.address); - - await aaveStrategy - .connect(vaultSigner) - .withdraw(vault.address, asset.address, assetUnits); - const vaultAssetBalDiff = (await asset.balanceOf(vault.address)).sub( - vaultAssetBalBefore - ); - - expect(vaultAssetBalDiff).to.approxEqualTolerance(assetUnits, 1); -} diff --git a/contracts/test/strategies/balancerMetaStablePool.mainnet.fork-test.js b/contracts/test/strategies/balancerMetaStablePool.mainnet.fork-test.js deleted file mode 100644 index 72f293d820..0000000000 --- a/contracts/test/strategies/balancerMetaStablePool.mainnet.fork-test.js +++ /dev/null @@ -1,738 +0,0 @@ -const { expect } = require("chai"); -const { formatUnits } = require("ethers").utils; -const { BigNumber } = require("ethers"); - -const addresses = require("../../utils/addresses"); -const { balancer_rETH_WETH_PID } = require("../../utils/constants"); -const { units, oethUnits, isCI } = require("../helpers"); -const { balancerREthFixture, createFixtureLoader } = require("../_fixture"); - -const { impersonateAndFund } = require("../../utils/signers"); -const { setERC20TokenBalance } = require("../_fund"); - -const log = require("../../utils/logger")("test:fork:strategy:balancer"); - -const loadBalancerREthFixtureDefault = createFixtureLoader( - balancerREthFixture, - { - defaultStrategy: true, - } -); - -const loadBalancerREthFixtureNotDefault = createFixtureLoader( - balancerREthFixture, - { - defaultStrategy: false, - } -); - -describe.skip("ForkTest: Balancer MetaStablePool rETH/WETH Strategy", function () { - this.timeout(0); - this.retries(isCI ? 3 : 0); - - let fixture; - - describe("Post deployment", () => { - beforeEach(async () => { - fixture = await loadBalancerREthFixtureDefault(); - }); - it("Should have the correct initial state", async function () { - const { balancerREthStrategy, oethVault } = fixture; - - // Platform and OToken Vault - expect(await balancerREthStrategy.platformAddress()).to.equal( - addresses.mainnet.rETH_WETH_BPT - ); - expect(await balancerREthStrategy.vaultAddress()).to.equal( - oethVault.address - ); - - // Balancer and Aura config - expect(await balancerREthStrategy.balancerVault()).to.equal( - addresses.mainnet.balancerVault - ); - expect(await balancerREthStrategy.balancerPoolId()).to.equal( - balancer_rETH_WETH_PID - ); - expect(await balancerREthStrategy.auraRewardPoolAddress()).to.equal( - addresses.mainnet.rETH_WETH_AuraRewards - ); - - // Check deviation values - expect(await balancerREthStrategy.maxDepositDeviation()).to.equal( - oethUnits("0.01") - ); - expect(await balancerREthStrategy.maxWithdrawalDeviation()).to.equal( - oethUnits("0.01") - ); - - // Check addresses - expect(await balancerREthStrategy.rETH()).to.equal( - addresses.mainnet.rETH - ); - expect(await balancerREthStrategy.wstETH()).to.equal( - addresses.mainnet.wstETH - ); - expect(await balancerREthStrategy.stETH()).to.equal( - addresses.mainnet.stETH - ); - expect(await balancerREthStrategy.sfrxETH()).to.equal( - addresses.mainnet.sfrxETH - ); - expect(await balancerREthStrategy.frxETH()).to.equal( - addresses.mainnet.frxETH - ); - }); - - it("Should safeApproveAllTokens", async function () { - const { reth, rEthBPT, weth, balancerREthStrategy, timelock } = fixture; - const balancerVault = await balancerREthStrategy.balancerVault(); - const auraRewardPool = await balancerREthStrategy.auraRewardPoolAddress(); - - const MAX = BigNumber.from( - "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" - ); - const ZERO = BigNumber.from(0); - const expectAllowanceRaw = async (expected, asset, owner, spender) => { - const allowance = await asset.allowance(owner, spender); - await expect(allowance).to.eq(expected); - }; - - const resetAllowance = async (asset, spender) => { - // strategy needs some ETH so it can execute the transactions - const strategySigner = await impersonateAndFund( - balancerREthStrategy.address, - "10" - ); - await asset.connect(strategySigner).approve(spender, ZERO); - }; - - await resetAllowance(reth, balancerVault); - await resetAllowance(weth, balancerVault); - await resetAllowance(rEthBPT, balancerVault); - await resetAllowance(rEthBPT, auraRewardPool); - - await expectAllowanceRaw( - ZERO, - reth, - balancerREthStrategy.address, - balancerVault - ); - await expectAllowanceRaw( - ZERO, - weth, - balancerREthStrategy.address, - balancerVault - ); - await expectAllowanceRaw( - ZERO, - rEthBPT, - balancerREthStrategy.address, - balancerVault - ); - await expectAllowanceRaw( - ZERO, - rEthBPT, - balancerREthStrategy.address, - auraRewardPool - ); - - await balancerREthStrategy.connect(timelock).safeApproveAllTokens(); - - await expectAllowanceRaw( - MAX, - reth, - balancerREthStrategy.address, - balancerVault - ); - await expectAllowanceRaw( - MAX, - weth, - balancerREthStrategy.address, - balancerVault - ); - await expectAllowanceRaw( - MAX, - rEthBPT, - balancerREthStrategy.address, - balancerVault - ); - await expectAllowanceRaw( - MAX, - rEthBPT, - balancerREthStrategy.address, - auraRewardPool - ); - }); - }); - - describe.skip("Deposit", function () { - beforeEach(async () => { - fixture = await loadBalancerREthFixtureNotDefault(); - const { oethVault, josh, reth } = fixture; - - await reth.connect(josh).transfer(oethVault.address, oethUnits("32")); - }); - it("Should deposit 5 WETH and 5 rETH in Balancer MetaStablePool strategy", async function () { - const { reth, rEthBPT, weth } = fixture; - await depositTest(fixture, [5, 5], [weth, reth], rEthBPT); - }); - it("Should deposit 12 WETH in Balancer MetaStablePool strategy", async function () { - const { reth, rEthBPT, weth } = fixture; - await depositTest(fixture, [12, 0], [weth, reth], rEthBPT); - }); - it("Should deposit 30 rETH in Balancer MetaStablePool strategy", async function () { - const { reth, rEthBPT, weth } = fixture; - await depositTest(fixture, [0, 30], [weth, reth], rEthBPT); - }); - it("Should deposit all WETH and rETH in strategy to pool", async function () { - const { balancerREthStrategy, oethVault, reth, weth } = fixture; - - const rethInVaultBefore = await reth.balanceOf(oethVault.address); - const wethInVaultBefore = await weth.balanceOf(oethVault.address); - const strategyValueBefore = await balancerREthStrategy[ - "checkBalance()" - ](); - - const oethVaultSigner = await impersonateAndFund(oethVault.address); - - const rethUnits = oethUnits("7"); - const rethValue = await reth.getEthValue(rethUnits); - await reth - .connect(oethVaultSigner) - .transfer(balancerREthStrategy.address, rethUnits); - const wethUnits = oethUnits("8"); - await weth - .connect(oethVaultSigner) - .transfer(balancerREthStrategy.address, wethUnits); - - await balancerREthStrategy.connect(oethVaultSigner).depositAll(); - - const rethInVaultAfter = await reth.balanceOf(oethVault.address); - const wethInVaultAfter = await weth.balanceOf(oethVault.address); - const strategyValueAfter = await balancerREthStrategy["checkBalance()"](); - - expect(rethInVaultBefore.sub(rethInVaultAfter)).to.equal(rethUnits); - expect(wethInVaultBefore.sub(wethInVaultAfter)).to.equal(wethUnits); - expect( - strategyValueAfter.sub(strategyValueBefore) - /* can in theory be up to ~2% off when calculating rETH value since the - * chainlink oracle allows for 2% deviation: https://data.chain.link/ethereum/mainnet/crypto-eth/reth-eth - * - * Since we are also depositing WETH that 2% deviation should be diluted to - * roughly ~1% when pricing value in the strategy. We are choosing 0.5% here for now - * and will adjust to more if needed. - */ - ).to.approxEqualTolerance(rethValue.add(wethUnits), 2); - }); - - it("Should be able to deposit with higher deposit deviation", async function () {}); - - it("Should revert when read-only re-entrancy is triggered", async function () { - /* - needs to be an asset default strategy - * - needs pool that supports native ETH - * - attacker needs to try to deposit to Balancer pool and withdraw - * - while withdrawing and receiving ETH attacker should take over the execution flow - * and try calling mint/redeem with the strategy default asset on the OethVault - * - transaction should revert because of the `whenNotInVaultContext` modifier - */ - }); - - it("Should check balance for gas usage", async () => { - const { balancerREthStrategy, josh, weth } = fixture; - - // Check balance in a transaction so the gas usage can be measured - await balancerREthStrategy["checkBalance(address)"](weth.address); - const tx = await balancerREthStrategy - .connect(josh) - .populateTransaction["checkBalance(address)"](weth.address); - await josh.sendTransaction(tx); - }); - - it("Should not return invalid balance of unsupported asset", async () => { - // Deposit something - const { reth, rEthBPT, weth, balancerREthStrategy, frxETH, stETH } = - fixture; - await depositTest(fixture, [5, 5], [weth, reth], rEthBPT); - - // Check balance - for (const unsupportedAsset of [frxETH, stETH]) { - await expect( - balancerREthStrategy["checkBalance(address)"]( - unsupportedAsset.address - ) - ).to.be.revertedWith("Unsupported asset"); - } - }); - }); - - describe("Withdraw", function () { - beforeEach(async () => { - fixture = await loadBalancerREthFixtureNotDefault(); - }); - - // a list of WETH/RETH pairs - const withdrawalTestCases = [ - ["10", "0"], - ["0", "8"], - ["11", "14"], - ["2.9543", "9.234"], - ["1.0001", "0"], - ["9.99998", "0"], - ["0", "7.00123"], - ["0", "0.210002"], - ["38.432", "12.5643"], - ["5.123452", "29.00123"], - ["22.1232", "30.12342"], - ]; - - for (const [wethAmount, rethAmount] of withdrawalTestCases) { - it(`Should be able to withdraw ${wethAmount} WETH and ${rethAmount} RETH from the pool`, async function () { - const { reth, balancerREthStrategy, oethVault, weth } = fixture; - - const vaultWethBalanceBefore = await weth.balanceOf(oethVault.address); - const vaultRethBalanceBefore = await reth.balanceOf(oethVault.address); - const wethWithdrawAmount = await units(wethAmount, weth); - const rethWithdrawAmount = await units(rethAmount, reth); - - const oethVaultSigner = await impersonateAndFund(oethVault.address); - - // prettier-ignore - await balancerREthStrategy - .connect(oethVaultSigner)["withdraw(address,address[],uint256[])"]( - oethVault.address, - [weth.address, reth.address], - [wethWithdrawAmount, rethWithdrawAmount] - ); - - expect( - (await weth.balanceOf(oethVault.address)).sub(vaultWethBalanceBefore) - ).to.approxEqualTolerance(wethWithdrawAmount, 0.01); - expect( - (await reth.balanceOf(oethVault.address)).sub(vaultRethBalanceBefore) - ).to.approxEqualTolerance(rethWithdrawAmount, 0.01); - }); - } - - it("Should be able to withdraw all of pool liquidity", async function () { - const { oethVault, weth, reth, balancerREthStrategy } = fixture; - - const wethBalanceBefore = await balancerREthStrategy[ - "checkBalance(address)" - ](weth.address); - const stEthBalanceBefore = await balancerREthStrategy[ - "checkBalance(address)" - ](reth.address); - - const oethVaultSigner = await impersonateAndFund(oethVault.address); - - await balancerREthStrategy.connect(oethVaultSigner).withdrawAll(); - - const wethBalanceDiff = wethBalanceBefore.sub( - await balancerREthStrategy["checkBalance(address)"](weth.address) - ); - const stEthBalanceDiff = stEthBalanceBefore.sub( - await balancerREthStrategy["checkBalance(address)"](reth.address) - ); - - expect(wethBalanceDiff).to.be.gte(await units("15", weth), 1); - expect(stEthBalanceDiff).to.be.gte(await units("15", reth), 1); - }); - - it("Should be able to withdraw with higher withdrawal deviation", async function () {}); - }); - - // Minting with rETH is unsupported - describe.skip("Large withdraw", function () { - const depositAmount = 30000; - let depositAmountUnits, oethVaultSigner; - beforeEach(async () => { - fixture = await loadBalancerREthFixtureNotDefault(); - const { - balancerREthStrategy, - balancerREthPID, - balancerVault, - josh, - oethVault, - oethZapper, - strategist, - reth, - weth, - } = fixture; - - oethVaultSigner = await impersonateAndFund(oethVault.address); - - await getPoolBalances(balancerVault, balancerREthPID); - - // Mint 100k oETH using WETH - depositAmountUnits = oethUnits(depositAmount.toString()); - await oethZapper.connect(josh).deposit({ value: depositAmountUnits }); - - // Mint 100k of oETH using RETH - await reth.connect(josh).approve(oethVault.address, depositAmountUnits); - await oethVault.connect(josh).mint(reth.address, depositAmountUnits, 0); - - await oethVault - .connect(strategist) - .depositToStrategy( - balancerREthStrategy.address, - [weth.address, reth.address], - [depositAmountUnits, depositAmountUnits] - ); - - log( - `Vault deposited ${depositAmount} WETH and ${depositAmount} RETH to Balancer strategy` - ); - }); - it(`withdraw all ${depositAmount} of both assets together using withdrawAll`, async () => { - const { balancerREthStrategy, oethVault } = fixture; - - const stratValueBefore = await oethVault.totalValue(); - log(`Vault total value before: ${formatUnits(stratValueBefore)}`); - - // Withdraw all - await balancerREthStrategy.connect(oethVaultSigner).withdrawAll(); - log(`Vault withdraws all WETH and RETH`); - - const stratValueAfter = await oethVault.totalValue(); - log(`Vault total value after: ${formatUnits(stratValueAfter)}`); - - const diff = stratValueBefore.sub(stratValueAfter); - const baseUnits = depositAmountUnits.mul(2); - const diffPercent = diff.mul(100000000).div(baseUnits); - log( - `Vault's ETH value change: ${formatUnits(diff)} ETH ${formatUnits( - diffPercent, - 6 - )}%` - ); - }); - it(`withdraw close to ${depositAmount} of both assets using multi asset withdraw`, async () => { - const { auraPool, balancerREthStrategy, rEthBPT, oethVault, reth, weth } = - fixture; - - const withdrawAmount = 29690; - const withdrawAmountUnits = oethUnits(withdrawAmount.toString(), 18); - - const stratValueBefore = await oethVault.totalValue(); - log(`Vault total value before: ${formatUnits(stratValueBefore)}`); - - // Withdraw all - // prettier-ignore - await balancerREthStrategy - .connect(oethVaultSigner)["withdraw(address,address[],uint256[])"]( - oethVault.address, - [weth.address, reth.address], - [withdrawAmountUnits, withdrawAmountUnits] - ); - log( - `Vault withdraws ${withdrawAmount} WETH and ${withdrawAmount} RETH together` - ); - - const bptAfterReth = await auraPool.balanceOf( - balancerREthStrategy.address - ); - log(`Aura BPTs after withdraw: ${formatUnits(bptAfterReth)}`); - log( - `Strategy BPTs after withdraw: ${formatUnits( - await rEthBPT.balanceOf(balancerREthStrategy.address) - )}` - ); - - const stratValueAfter = await oethVault.totalValue(); - log(`Vault total value after: ${formatUnits(stratValueAfter)}`); - - const diff = stratValueBefore.sub(stratValueAfter); - const baseUnits = withdrawAmountUnits.mul(2); - const diffPercent = diff.mul(100000000).div(baseUnits); - log( - `Vault's ETH value change: ${formatUnits(diff)} ETH ${formatUnits( - diffPercent, - 6 - )}%` - ); - }); - it(`withdraw ${depositAmount} of each asset in separate calls`, async () => { - const { balancerREthStrategy, rEthBPT, oethVault, reth, weth, auraPool } = - fixture; - - const stratValueBefore = await oethVault.totalValue(); - log(`Vault total value before: ${formatUnits(stratValueBefore)}`); - - const bptBefore = await auraPool.balanceOf(balancerREthStrategy.address); - log(`Aura BPTs before: ${formatUnits(bptBefore)}`); - - const withdrawAmount = 29700; - const withdrawAmountUnits = oethUnits(withdrawAmount.toString(), 18); - - // Withdraw WETH - // prettier-ignore - await balancerREthStrategy - .connect(oethVaultSigner)["withdraw(address,address,uint256)"]( - oethVault.address, - weth.address, - withdrawAmountUnits - ); - - log(`Vault withdraws ${withdrawAmount} WETH`); - - const stratValueAfterWeth = await oethVault.totalValue(); - log( - `Vault total value after WETH withdraw: ${formatUnits( - stratValueAfterWeth - )}` - ); - const bptAfterWeth = await auraPool.balanceOf( - balancerREthStrategy.address - ); - log(`Aura BPTs after WETH withdraw: ${formatUnits(bptAfterWeth)}`); - log( - `Strategy BPTs after WETH withdraw: ${formatUnits( - await rEthBPT.balanceOf(balancerREthStrategy.address) - )}` - ); - - // Withdraw RETH - // prettier-ignore - await balancerREthStrategy - .connect(oethVaultSigner)["withdraw(address,address,uint256)"]( - oethVault.address, - reth.address, - withdrawAmountUnits - ); - - log(`Vault withdraws ${withdrawAmount} RETH`); - - const bptAfterReth = await auraPool.balanceOf( - balancerREthStrategy.address - ); - log(`Aura BPTs after RETH withdraw: ${formatUnits(bptAfterReth)}`); - log( - `Strategy BPTs after RETH withdraw: ${formatUnits( - await rEthBPT.balanceOf(balancerREthStrategy.address) - )}` - ); - - const stratValueAfterReth = await oethVault.totalValue(); - log( - `Vault total value after RETH withdraw: ${formatUnits( - stratValueAfterReth - )}` - ); - - const diff = stratValueBefore.sub(stratValueAfterReth); - const baseUnits = withdrawAmountUnits.mul(2); - const diffPercent = diff.mul(100000000).div(baseUnits); - log( - `Vault's ETH value change: ${formatUnits(diff)} ETH ${formatUnits( - diffPercent, - 6 - )}%` - ); - }); - }); - - describe("Harvest rewards", function () { - beforeEach(async () => { - fixture = await loadBalancerREthFixtureDefault(); - - const { oethVault, josh, reth } = fixture; - - await reth.connect(josh).transfer(oethVault.address, oethUnits("32")); - }); - it("Should be able to collect reward tokens", async function () { - const { balancerREthStrategy, oethHarvester, bal, aura } = fixture; - - const sHarvester = await impersonateAndFund(oethHarvester.address); - const balBefore = await bal.balanceOf(oethHarvester.address); - const auraBefore = await aura.balanceOf(oethHarvester.address); - - await balancerREthStrategy.connect(sHarvester).collectRewardTokens(); - - expect(await bal.balanceOf(oethHarvester.address)).to.be.gt( - balBefore.add(oethUnits("0.1")) - ); - expect(await aura.balanceOf(oethHarvester.address)).to.be.gt( - auraBefore.add(oethUnits("0.1")) - ); - }); - - it("Should be able to collect and swap reward tokens", async function () { - const { - josh, - balancerREthStrategy, - weth, - oethHarvester, - oethDripper, - bal, - aura, - } = fixture; - - // Let the strategy have some tokens it can send to Harvester - await setERC20TokenBalance( - balancerREthStrategy.address, - bal, - oethUnits("50") - ); - await setERC20TokenBalance( - balancerREthStrategy.address, - aura, - oethUnits("50") - ); - - const wethBalanceBefore = await weth.balanceOf(oethDripper.address); - await oethHarvester.connect(josh)[ - // eslint-disable-next-line - "harvestAndSwap(address)" - ](balancerREthStrategy.address); - - const wethBalanceDiff = (await weth.balanceOf(oethDripper.address)).sub( - wethBalanceBefore - ); - - expect(wethBalanceDiff).to.be.gt(oethUnits("0")); - }); - }); -}); - -async function getPoolValues(strategy, allAssets, reth) { - const result = { - sum: BigNumber.from(0), - }; - - for (const asset of allAssets) { - const assetSymbol = await asset.symbol(); - const strategyAssetBalance = await strategy["checkBalance(address)"]( - asset.address - ); - log( - `Balancer ${assetSymbol} balance: ${formatUnits(strategyAssetBalance)}` - ); - const strategyAssetValue = - asset.address === reth.address - ? await reth.getEthValue(strategyAssetBalance) - : strategyAssetBalance; - result.sum = result.sum.add(strategyAssetValue); - log(`Balancer ${assetSymbol} value: ${formatUnits(strategyAssetValue)}`); - result[assetSymbol] = strategyAssetBalance; - } - log(`Balancer sum values: ${formatUnits(result.sum)}`); - - result.value = await strategy["checkBalance()"](); - log(`Balancer value: ${formatUnits(result.value)}`); - - return result; -} - -async function getPoolBalances(balancerVault, pid) { - const result = {}; - const { tokens, balances } = await balancerVault.getPoolTokens(pid); - let i = 0; - for (const balance of balances) { - const assetAddr = tokens[i++]; - log(`${assetAddr} pool balance: ${formatUnits(balance)}`); - result[assetAddr] = balance; - } - return result; -} - -async function depositTest( - fixture, - amounts, - allAssets, - bpt, - strategyValueDiffPct = 3 -) { - const { - oethVault, - oeth, - balancerREthStrategy, - balancerVault, - balancerREthPID, - reth, - strategist, - } = fixture; - const logParams = { - oeth, - oethVault, - bpt, - balancerVault, - strategy: balancerREthStrategy, - allAssets, - pid: balancerREthPID, - reth, - }; - - const unitAmounts = amounts.map((amount) => oethUnits(amount.toString())); - const ethAmounts = await Promise.all( - allAssets.map((asset, i) => - asset.address === reth.address - ? reth.getEthValue(unitAmounts[i]) - : unitAmounts[i] - ) - ); - const sumEthAmounts = ethAmounts.reduce( - (a, b) => a.add(b), - BigNumber.from(0) - ); - - const before = await logBalances(logParams); - - await oethVault.connect(strategist).depositToStrategy( - balancerREthStrategy.address, - allAssets.map((asset) => asset.address), - unitAmounts - ); - - const after = await logBalances(logParams); - - // Should have liquidity in Balancer - const strategyValuesDiff = after.strategyValues.sum.sub( - before.strategyValues.sum - ); - expect(strategyValuesDiff).to.approxEqualTolerance( - sumEthAmounts, - strategyValueDiffPct - ); - expect( - after.strategyValues.value, - "strategy total value = sum of asset values" - ).to.approxEqualTolerance(after.strategyValues.sum, 0.01); -} - -async function logBalances({ - oeth, - oethVault, - bpt, - balancerVault, - pid, - strategy, - allAssets, - reth, -}) { - const oethSupply = await oeth.totalSupply(); - const bptSupply = await bpt.totalSupply(); - - log(`\nOETH total supply: ${formatUnits(oethSupply)}`); - log(`BPT total supply : ${formatUnits(bptSupply)}`); - - const vaultAssets = {}; - for (const asset of allAssets) { - const vaultAssetAmount = await asset.balanceOf(oethVault.address); - const symbol = await asset.symbol(); - log(`${symbol} in vault ${formatUnits(vaultAssetAmount)}`); - vaultAssets[symbol] = vaultAssetAmount; - } - - const strategyValues = await getPoolValues(strategy, allAssets, reth); - - const poolBalances = await getPoolBalances(balancerVault, pid); - - return { - oethSupply, - bptSupply, - strategyValues, - poolBalances, - vaultAssets, - }; -} diff --git a/contracts/test/strategies/balancerPoolReentrancy.mainnet.fork-test.js b/contracts/test/strategies/balancerPoolReentrancy.mainnet.fork-test.js deleted file mode 100644 index b0e6fa6d55..0000000000 --- a/contracts/test/strategies/balancerPoolReentrancy.mainnet.fork-test.js +++ /dev/null @@ -1,54 +0,0 @@ -const hre = require("hardhat"); -const { ethers } = hre; -const { expect } = require("chai"); -const { isCI } = require("../helpers"); -const { balancerREthFixture, createFixtureLoader } = require("../_fixture"); -const { deployWithConfirmation } = require("../../utils/deploy"); -const addresses = require("../../utils/addresses"); -const { setERC20TokenBalance } = require("../_fund"); - -describe.skip("ForkTest: Balancer MetaStablePool - Read-only Reentrancy", function () { - this.timeout(0); - // Retry up to 3 times on CI - this.retries(isCI ? 3 : 0); - - let fixture; - const loadFixture = createFixtureLoader(balancerREthFixture, { - defaultStrategy: true, - }); - - beforeEach(async () => { - fixture = await loadFixture(); - }); - - it("Should not allow read-only reentrancy", async () => { - const { weth, reth, oethVault, rEthBPT, balancerREthPID, daniel } = fixture; - - // Deploy the attacking contract - const dEvilContract = await deployWithConfirmation( - "MockEvilReentrantContract", - [ - addresses.mainnet.balancerVault, - oethVault.address, - reth.address, - weth.address, - rEthBPT.address, - balancerREthPID, - ] - ); - const cEvilContract = await ethers.getContractAt( - "MockEvilReentrantContract", - dEvilContract.address - ); - - // Approve movement of tokens - await cEvilContract.connect(daniel).approveAllTokens(); - - // Fund attacking contract with WETH and rETH - await setERC20TokenBalance(cEvilContract.address, weth, "1000000", hre); - await setERC20TokenBalance(cEvilContract.address, reth, "1000000", hre); - - // Do Evil Stuff - await expect(cEvilContract.doEvilStuff()).to.be.reverted; - }); -}); diff --git a/contracts/test/strategies/base/bridged-woeth-strategy.plume.fork-test.js b/contracts/test/strategies/base/bridged-woeth-strategy.plume.fork-test.js deleted file mode 100644 index b155d6c861..0000000000 --- a/contracts/test/strategies/base/bridged-woeth-strategy.plume.fork-test.js +++ /dev/null @@ -1,239 +0,0 @@ -const { createFixtureLoader } = require("../../_fixture"); -const { defaultPlumeFixture } = require("../../_fixture-plume"); -const { expect } = require("chai"); -const { oethUnits } = require("../../helpers"); -const { deployWithConfirmation } = require("../../../utils/deploy"); -const { replaceContractAt } = require("../../../utils/hardhat"); -const addresses = require("../../../utils/addresses"); - -const plumeFixture = createFixtureLoader(defaultPlumeFixture); - -describe("Plume Fork Test: Bridged WOETH Strategy", function () { - let fixture; - beforeEach(async () => { - fixture = await plumeFixture(); - }); - - it("Should allow governor/strategist to mint with bridged WOETH", async () => { - const { woeth, oethp, oethpVault, weth, woethStrategy, governor } = fixture; - - await oethpVault.rebase(); - await woethStrategy.updateWOETHOraclePrice(); - - const supplyBefore = await oethp.totalSupply(); - const userOETHpBalanceBefore = await oethp.balanceOf(governor.address); - const userWOETHBalanceBefore = await woeth.balanceOf(governor.address); - const stratBalanceBefore = await woethStrategy.checkBalance(weth.address); - const stratWOETHBalanceBefore = await woeth.balanceOf( - woethStrategy.address - ); - - const depositAmount = oethUnits("1"); - const expectedAmount = await woethStrategy.getBridgedWOETHValue( - depositAmount - ); - - // Governor mints OETHp with wOETH - await woeth.connect(governor).approve(woethStrategy.address, depositAmount); - await woethStrategy.connect(governor).depositBridgedWOETH(depositAmount); - - const supplyDiff = (await oethp.totalSupply()).sub(supplyBefore); - expect(supplyDiff).to.approxEqualTolerance( - expectedAmount, - 1, - "Incorrect supply change" - ); - - const userOETHpBalanceDiff = (await oethp.balanceOf(governor.address)).sub( - userOETHpBalanceBefore - ); - expect(userOETHpBalanceDiff).to.approxEqualTolerance( - expectedAmount, - 1, - "OETHp balance didn't increase" - ); - - const userWOETHBalanceDiff = userWOETHBalanceBefore.sub( - await woeth.balanceOf(governor.address) - ); - expect(userWOETHBalanceDiff).to.approxEqualTolerance( - depositAmount, - 1, - "User has more WOETH" - ); - - const stratBalanceDiff = ( - await woethStrategy.checkBalance(weth.address) - ).sub(stratBalanceBefore); - expect(stratBalanceDiff).to.approxEqualTolerance( - expectedAmount, - 1, - "Strategy reports more balance" - ); - - const stratWOETHBalanceDiff = ( - await woeth.balanceOf(woethStrategy.address) - ).sub(stratWOETHBalanceBefore); - expect(stratWOETHBalanceDiff).to.approxEqualTolerance( - depositAmount, - 1, - "Strategy has less WOETH" - ); - }); - - it("Should allow governor/strategist to get back bridged WOETH", async () => { - const { woeth, oethp, oethpVault, weth, woethStrategy, governor } = fixture; - - await oethpVault.rebase(); - await woethStrategy.updateWOETHOraclePrice(); - - const depositWOETHAmount = oethUnits("1"); - - // Governor mints OETHp with wOETH - await woeth - .connect(governor) - .approve(woethStrategy.address, depositWOETHAmount); - await woethStrategy - .connect(governor) - .depositBridgedWOETH(depositWOETHAmount); - - const supplyBefore = await oethp.totalSupply(); - const userOETHpBalanceBefore = await oethp.balanceOf(governor.address); - const userWOETHBalanceBefore = await woeth.balanceOf(governor.address); - const stratBalanceBefore = await woethStrategy.checkBalance(weth.address); - const stratWOETHBalanceBefore = await woeth.balanceOf( - woethStrategy.address - ); - - const expectedOETHpAmount = await woethStrategy.getBridgedWOETHValue( - depositWOETHAmount.sub(1) - ); - - // Approve strategy to move OETHp - await oethp - .connect(governor) - .approve(woethStrategy.address, oethUnits("1000000")); - - // Governor tries to withdraw - await woethStrategy - .connect(governor) - .withdrawBridgedWOETH(expectedOETHpAmount); - - const supplyDiff = supplyBefore.sub(await oethp.totalSupply()); - expect(supplyDiff).to.approxEqualTolerance( - expectedOETHpAmount, - 1, - "Incorrect supply change" - ); - - const userOETHpBalanceDiff = userOETHpBalanceBefore.sub( - await oethp.balanceOf(governor.address) - ); - expect(userOETHpBalanceDiff).to.approxEqualTolerance( - expectedOETHpAmount, - 1, - "OETHp balance didn't go down" - ); - - const userWOETHBalanceDiff = (await woeth.balanceOf(governor.address)).sub( - userWOETHBalanceBefore - ); - expect(userWOETHBalanceDiff).to.approxEqualTolerance( - depositWOETHAmount, - 1, - "User has less WOETH" - ); - - const stratBalanceDiff = stratBalanceBefore.sub( - await woethStrategy.checkBalance(weth.address) - ); - expect(stratBalanceDiff).to.approxEqualTolerance( - expectedOETHpAmount, - 1, - "Strategy reports incorrect balance" - ); - - const stratWOETHBalanceDiff = stratWOETHBalanceBefore.sub( - await woeth.balanceOf(woethStrategy.address) - ); - expect(stratWOETHBalanceDiff).to.approxEqualTolerance( - depositWOETHAmount, - 1, - "Strategy has more WOETH" - ); - }); - - it("Should handle yields from appreciation of WOETH value", async () => { - const { woeth, oethp, oethpVault, weth, woethStrategy, governor } = fixture; - - await oethpVault.rebase(); - - const depositAmount = oethUnits("1"); - const oracleFeed = await ethers.getContractAt( - "AggregatorV3Interface", - addresses.plume.BridgedWOETHOracleFeed - ); - const roundData = await oracleFeed.latestRoundData(); - - // Governor mints OETHp with wOETH - await woeth.connect(governor).approve(woethStrategy.address, depositAmount); - await woethStrategy.connect(governor).depositBridgedWOETH(depositAmount); - - const supplyBefore = await oethp.totalSupply(); - // const userOETHpBalanceBefore = await oethp.balanceOf(governor.address); - // const userWOETHBalanceBefore = await woeth.balanceOf(governor.address); - const stratBalanceBefore = await woethStrategy.checkBalance(weth.address); - const stratWOETHBalanceBefore = await woeth.balanceOf( - woethStrategy.address - ); - - // Deploy a mock oracle feed - const dMockOracleFeed = await deployWithConfirmation( - "MockChainlinkOracleFeed", - [ - roundData.answer, // Initial price - 18, // Decimals - ] - ); - // Replace the feed with mock - await replaceContractAt( - addresses.base.BridgedWOETHOracleFeed, - dMockOracleFeed - ); - - const cMockOracleFeed = await ethers.getContractAt( - "MockChainlinkOracleFeed", - addresses.base.BridgedWOETHOracleFeed - ); - // Set price - await cMockOracleFeed.connect(governor).setPrice(roundData.answer); - await cMockOracleFeed.connect(governor).setDecimals(18); - - // Update price on strategy - await woethStrategy.updateWOETHOraclePrice(); - - expect(await woethStrategy.checkBalance(weth.address)).to.equal( - stratBalanceBefore - ); - - // Increase the price by 0.5% - await cMockOracleFeed.setPrice(roundData.answer.mul(1005).div(1000)); - - // Strategy balance should've increased by 0.5% - expect( - await woethStrategy.checkBalance(weth.address) - ).to.approxEqualTolerance(stratBalanceBefore.mul(1005).div(1000), 1); - expect(await woeth.balanceOf(woethStrategy.address)).to.equal( - stratWOETHBalanceBefore - ); - - // Should increase supply on rebase - await oethpVault.rebase(); - - // Should've increased supply - expect(await oethp.totalSupply()).to.be.approxEqualTolerance( - supplyBefore.add(stratBalanceBefore.mul(5).div(1000)), - 1 - ); - }); -}); diff --git a/contracts/test/strategies/base/curve-amo.base.fork-test.js b/contracts/test/strategies/base/curve-amo.base.fork-test.js index 656b99753c..799d4cd040 100644 --- a/contracts/test/strategies/base/curve-amo.base.fork-test.js +++ b/contracts/test/strategies/base/curve-amo.base.fork-test.js @@ -757,6 +757,7 @@ describe("Base Fork Test: Curve AMO strategy", function () { cvx: crv, comp: crv, bal: crv, + ssv: crv, // Users anna: rafael, matt: clement, diff --git a/contracts/test/strategies/compound.js b/contracts/test/strategies/compound.js deleted file mode 100644 index 6cea9c8fc0..0000000000 --- a/contracts/test/strategies/compound.js +++ /dev/null @@ -1,124 +0,0 @@ -const { expect } = require("chai"); - -const { impersonateAndFund } = require("../../utils/signers"); -const { createFixtureLoader, compoundFixture } = require("../_fixture"); -const { usdcUnits, isFork } = require("../helpers"); -const { shouldBehaveLikeGovernable } = require("../behaviour/governable"); -const { shouldBehaveLikeHarvestable } = require("../behaviour/harvestable"); -const { shouldBehaveLikeStrategy } = require("../behaviour/strategy"); - -describe.skip("Compound strategy", function () { - if (isFork) { - this.timeout(0); - } - - let fixture; - const loadFixture = createFixtureLoader(compoundFixture); - beforeEach(async function () { - fixture = await loadFixture(); - }); - - shouldBehaveLikeGovernable(() => ({ - ...fixture, - strategy: fixture.cStandalone, - })); - - shouldBehaveLikeHarvestable(() => ({ - ...fixture, - harvester: fixture.harvester, - strategy: fixture.cStandalone, - })); - - shouldBehaveLikeStrategy(() => ({ - ...fixture, - strategy: fixture.cStandalone, - assets: [fixture.dai, fixture.usdc], - vault: fixture.vault, - })); - - it("Should allow a withdraw", async () => { - const { cStandalone, usdc, cusdc, vault, vaultSigner } = fixture; - const stratCUSDBalance = await cusdc.balanceOf(cStandalone.address); - // Fund the strategy - const strategySigner = await impersonateAndFund( - fixture.cStandalone.address - ); - usdc.connect(strategySigner).mint(usdcUnits("1000")); - // Run deposit() - await cStandalone - .connect(vaultSigner) - .deposit(usdc.address, usdcUnits("1000")); - const exchangeRate = await cusdc.exchangeRateStored(); - // 0xde0b6b3a7640000 == 1e18 - const expectedCusd = usdcUnits("1000") - .mul("0xde0b6b3a7640000") - .div(exchangeRate); - // Make sure we have cUSD now - await expect(await cusdc.balanceOf(cStandalone.address)).to.be.above( - stratCUSDBalance - ); - await expect(await cusdc.balanceOf(cStandalone.address)).to.be.equal( - expectedCusd - ); - await expect(await usdc.balanceOf(cStandalone.address)).to.be.equal( - usdcUnits("0") - ); - // event Withdrawal(address indexed _asset, address _pToken, uint256 _amount); - await expect( - cStandalone - .connect(vaultSigner) - .withdraw(vault.address, usdc.address, usdcUnits("1000")) - ) - .to.emit(cStandalone, "Withdrawal") - .withArgs(usdc.address, cusdc.address, usdcUnits("1000")); - await expect(await cusdc.balanceOf(cStandalone.address)).to.be.equal( - stratCUSDBalance - ); - }); - - it("Should allow Governor to set reward token address", async () => { - const { cStandalone, governor, comp } = fixture; - - await expect( - cStandalone - .connect(governor) - .setRewardTokenAddresses([cStandalone.address]) - ) - .to.emit(cStandalone, "RewardTokenAddressesUpdated") - .withArgs([comp.address], [cStandalone.address]); - expect(await cStandalone.rewardTokenAddresses(0)).to.equal( - cStandalone.address - ); - }); - - it("Should block Governor from adding more reward token address with zero address", async () => { - const { cStandalone, governor } = fixture; - - await expect( - cStandalone - .connect(governor) - .setRewardTokenAddresses([ - cStandalone.address, - "0x0000000000000000000000000000000000000000", - ]) - ).to.be.revertedWith("Can not set an empty address as a reward token"); - }); - - it("Should allow Governor to remove reward token addresses", async () => { - const { cStandalone, governor, comp } = fixture; - - // Add so we can remove - await cStandalone - .connect(governor) - .setRewardTokenAddresses([cStandalone.address, comp.address]); - // Remove all - await cStandalone.connect(governor).setRewardTokenAddresses([]); - }); - - it("Should not allow non-Governor to set reward token address", async () => { - const { cStandalone, anna } = fixture; - await expect( - cStandalone.connect(anna).setRewardTokenAddresses([cStandalone.address]) - ).to.be.revertedWith("Caller is not the Governor"); - }); -}); diff --git a/contracts/test/strategies/convex.js b/contracts/test/strategies/convex.js deleted file mode 100644 index 9f1da88d7d..0000000000 --- a/contracts/test/strategies/convex.js +++ /dev/null @@ -1,106 +0,0 @@ -const { expect } = require("chai"); - -const { createFixtureLoader, convexVaultFixture } = require("../_fixture"); -const { - usdcUnits, - ousdUnits, - units, - expectApproxSupply, - isFork, -} = require("../helpers"); - -describe("Convex Strategy", function () { - if (isFork) { - this.timeout(0); - } - - let ousd, - vault, - governor, - strategist, - crv, - threePoolToken, - convexStrategy, - cvxBooster, - usdc; - - const mint = async (amount, asset) => { - await asset.connect(strategist).mint(await units(amount, asset)); - await asset - .connect(strategist) - .approve(vault.address, await units(amount, asset)); - return await vault - .connect(strategist) - .mint(asset.address, await units(amount, asset), 0); - }; - - const loadFixture = createFixtureLoader(convexVaultFixture); - beforeEach(async function () { - const fixture = await loadFixture(); - vault = fixture.vault; - ousd = fixture.ousd; - governor = fixture.governor; - crv = fixture.crv; - threePoolToken = fixture.threePoolToken; - convexStrategy = fixture.convexStrategy; - cvxBooster = fixture.cvxBooster; - usdc = fixture.usdc; - strategist = fixture.strategist; - }); - - describe("Mint", function () { - it("Should stake USDC in Curve gauge via 3pool", async function () { - await expectApproxSupply(ousd, ousdUnits("200")); - await mint("50000.00", usdc); - await expectApproxSupply(ousd, ousdUnits("50200")); - await expect(strategist).to.have.a.balanceOf("50000", ousd); - await expect(cvxBooster).has.an.approxBalanceOf("50200", threePoolToken); - }); - }); - - describe("Redeem", function () { - it("Should be able to unstake from gauge and return USDT", async function () { - await expectApproxSupply(ousd, ousdUnits("200")); - await mint("10000.00", usdc); - await vault.connect(governor).set; - await vault.connect(strategist).requestWithdrawal(ousdUnits("10000")); - await expectApproxSupply(ousd, ousdUnits("200")); - }); - }); - - describe("Utilities", function () { - it("Should allow transfer of arbitrary token by Governor", async () => { - await usdc.connect(strategist).approve(vault.address, usdcUnits("8.0")); - await vault.connect(strategist).mint(usdc.address, usdcUnits("8.0"), 0); - // Strategist sends her OUSD directly to Strategy - await ousd - .connect(strategist) - .transfer(convexStrategy.address, ousdUnits("8.0")); - // Strategist asks Governor for help - await convexStrategy - .connect(governor) - .transferToken(ousd.address, ousdUnits("8.0")); - await expect(governor).has.a.balanceOf("8.0", ousd); - }); - - it("Should not allow transfer of arbitrary token by non-Governor", async () => { - // Naughty Strategist - await expect( - convexStrategy - .connect(strategist) - .transferToken(ousd.address, ousdUnits("8.0")) - ).to.be.revertedWith("Caller is not the Governor"); - }); - - it("Should revert when zero address attempts to be set as reward token address", async () => { - await expect( - convexStrategy - .connect(governor) - .setRewardTokenAddresses([ - crv.address, - "0x0000000000000000000000000000000000000000", - ]) - ).to.be.revertedWith("Can not set an empty address as a reward token"); - }); - }); -}); diff --git a/contracts/test/strategies/crosschain/crosschain-master-strategy.mainnet.fork-test.js b/contracts/test/strategies/crosschain/crosschain-master-strategy.mainnet.fork-test.js index 5cb3eb150a..9a96415eca 100644 --- a/contracts/test/strategies/crosschain/crosschain-master-strategy.mainnet.fork-test.js +++ b/contracts/test/strategies/crosschain/crosschain-master-strategy.mainnet.fork-test.js @@ -110,7 +110,7 @@ describe("ForkTest: CrossChainMasterStrategy", function () { burnEventData.hookData ); expect(messageType).to.eq(1); - expect(nonce).to.eq(1); + expect(nonce).to.gt(2); expect(amount).to.eq(usdcUnits("1000")); }); @@ -171,14 +171,14 @@ describe("ForkTest: CrossChainMasterStrategy", function () { const { messageType, nonce, amount } = decodeDepositOrWithdrawMessage(payload); expect(messageType).to.eq(2); - expect(nonce).to.eq(1); + expect(nonce).to.gt(2); expect(amount).to.eq(usdcUnits("1000")); }); }); describe("Message receiving", function () { it("Should handle balance check message", async function () { - const { crossChainMasterStrategy, strategist } = fixture; + const { crossChainMasterStrategy, relayer } = fixture; if (await crossChainMasterStrategy.isTransferPending()) { // Skip if there's a pending transfer @@ -209,7 +209,7 @@ describe("ForkTest: CrossChainMasterStrategy", function () { ); // Relay the message with fake attestation - await crossChainMasterStrategy.connect(strategist).relay(message, "0x"); + await crossChainMasterStrategy.connect(relayer).relay(message, "0x"); const remoteStrategyBalance = await crossChainMasterStrategy.remoteStrategyBalance(); @@ -217,7 +217,7 @@ describe("ForkTest: CrossChainMasterStrategy", function () { }); it("Should handle balance check message for a pending deposit", async function () { - const { crossChainMasterStrategy, strategist, usdc, matt } = fixture; + const { crossChainMasterStrategy, relayer, usdc, matt } = fixture; if (await crossChainMasterStrategy.isTransferPending()) { // Skip if there's a pending transfer @@ -263,7 +263,7 @@ describe("ForkTest: CrossChainMasterStrategy", function () { ); // Relay the message with fake attestation - await crossChainMasterStrategy.connect(strategist).relay(message, "0x"); + await crossChainMasterStrategy.connect(relayer).relay(message, "0x"); const remoteStrategyBalance = await crossChainMasterStrategy.remoteStrategyBalance(); @@ -276,7 +276,7 @@ describe("ForkTest: CrossChainMasterStrategy", function () { }); it("Should accept tokens for a pending withdrawal", async function () { - const { crossChainMasterStrategy, strategist, matt, usdc } = fixture; + const { crossChainMasterStrategy, relayer, matt, usdc } = fixture; if (await crossChainMasterStrategy.isTransferPending()) { // Skip if there's a pending transfer @@ -333,7 +333,7 @@ describe("ForkTest: CrossChainMasterStrategy", function () { .transfer(crossChainMasterStrategy.address, usdcUnits("2342")); // Relay the message with fake attestation - await crossChainMasterStrategy.connect(strategist).relay(message, "0x"); + await crossChainMasterStrategy.connect(relayer).relay(message, "0x"); const remoteStrategyBalance = await crossChainMasterStrategy.remoteStrategyBalance(); @@ -341,7 +341,7 @@ describe("ForkTest: CrossChainMasterStrategy", function () { }); it("Should ignore balance check message for a pending withdrawal", async function () { - const { crossChainMasterStrategy, strategist, usdc } = fixture; + const { crossChainMasterStrategy, relayer, usdc } = fixture; if (await crossChainMasterStrategy.isTransferPending()) { // Skip if there's a pending transfer @@ -389,7 +389,7 @@ describe("ForkTest: CrossChainMasterStrategy", function () { ); // Relay the message with fake attestation - await crossChainMasterStrategy.connect(strategist).relay(message, "0x"); + await crossChainMasterStrategy.connect(relayer).relay(message, "0x"); // Should've ignore the message const remoteStrategyBalance = @@ -398,7 +398,7 @@ describe("ForkTest: CrossChainMasterStrategy", function () { }); it("Should ignore balance check message with older nonce", async function () { - const { crossChainMasterStrategy, strategist, matt, usdc } = fixture; + const { crossChainMasterStrategy, relayer, matt, usdc } = fixture; if (await crossChainMasterStrategy.isTransferPending()) { // Skip if there's a pending transfer @@ -447,7 +447,7 @@ describe("ForkTest: CrossChainMasterStrategy", function () { ); // Relay the message with fake attestation - await crossChainMasterStrategy.connect(strategist).relay(message, "0x"); + await crossChainMasterStrategy.connect(relayer).relay(message, "0x"); const remoteStrategyBalance = await crossChainMasterStrategy.remoteStrategyBalance(); @@ -455,7 +455,7 @@ describe("ForkTest: CrossChainMasterStrategy", function () { }); it("Should ignore if nonce is higher", async function () { - const { crossChainMasterStrategy, strategist } = fixture; + const { crossChainMasterStrategy, relayer } = fixture; if (await crossChainMasterStrategy.isTransferPending()) { // Skip if there's a pending transfer @@ -489,14 +489,14 @@ describe("ForkTest: CrossChainMasterStrategy", function () { ); // Relay the message with fake attestation - await crossChainMasterStrategy.connect(strategist).relay(message, "0x"); + await crossChainMasterStrategy.connect(relayer).relay(message, "0x"); const remoteStrategyBalanceAfter = await crossChainMasterStrategy.remoteStrategyBalance(); expect(remoteStrategyBalanceAfter).to.eq(remoteStrategyBalanceBefore); }); it("Should revert if the burn token is not peer USDC", async function () { - const { crossChainMasterStrategy, strategist } = fixture; + const { crossChainMasterStrategy, relayer } = fixture; if (await crossChainMasterStrategy.isTransferPending()) { // Skip if there's a pending transfer @@ -540,9 +540,7 @@ describe("ForkTest: CrossChainMasterStrategy", function () { ); // Relay the message with fake attestation - const tx = crossChainMasterStrategy - .connect(strategist) - .relay(message, "0x"); + const tx = crossChainMasterStrategy.connect(relayer).relay(message, "0x"); await expect(tx).to.be.revertedWith("Invalid burn token"); }); diff --git a/contracts/test/strategies/crosschain/crosschain-remote-strategy.base.fork-test.js b/contracts/test/strategies/crosschain/crosschain-remote-strategy.base.fork-test.js index ea8c2dfac2..d6c754a06e 100644 --- a/contracts/test/strategies/crosschain/crosschain-remote-strategy.base.fork-test.js +++ b/contracts/test/strategies/crosschain/crosschain-remote-strategy.base.fork-test.js @@ -123,7 +123,7 @@ describe("ForkTest: CrossChainRemoteStrategy", function () { }); it("Should handle deposits", async function () { - const { crossChainRemoteStrategy, strategist, rafael, usdc } = fixture; + const { crossChainRemoteStrategy, relayer, rafael, usdc } = fixture; // snapshot state const balanceBefore = await crossChainRemoteStrategy.checkBalance( @@ -161,7 +161,7 @@ describe("ForkTest: CrossChainRemoteStrategy", function () { // Relay the message const tx = await crossChainRemoteStrategy - .connect(strategist) + .connect(relayer) .relay(message, "0x"); // Check if it sent the check balance message @@ -184,7 +184,8 @@ describe("ForkTest: CrossChainRemoteStrategy", function () { }); it("Should handle withdrawals", async function () { - const { crossChainRemoteStrategy, strategist, rafael, usdc } = fixture; + const { crossChainRemoteStrategy, strategist, relayer, rafael, usdc } = + fixture; const withdrawalAmount = usdcUnits("1234.56"); @@ -221,7 +222,7 @@ describe("ForkTest: CrossChainRemoteStrategy", function () { // Relay the message const tx = await crossChainRemoteStrategy - .connect(strategist) + .connect(relayer) .relay(message, "0x"); // Check if it sent the check balance message @@ -249,7 +250,7 @@ describe("ForkTest: CrossChainRemoteStrategy", function () { }); it("Should revert if the burn token is not peer USDC", async function () { - const { crossChainRemoteStrategy, strategist } = fixture; + const { crossChainRemoteStrategy, relayer } = fixture; const nonceBefore = await crossChainRemoteStrategy.lastTransferNonce(); @@ -277,9 +278,7 @@ describe("ForkTest: CrossChainRemoteStrategy", function () { ); // Relay the message - const tx = crossChainRemoteStrategy - .connect(strategist) - .relay(message, "0x"); + const tx = crossChainRemoteStrategy.connect(relayer).relay(message, "0x"); await expect(tx).to.be.revertedWith("Invalid burn token"); }); diff --git a/contracts/test/strategies/curve-amo-oeth.mainnet.fork-test.js b/contracts/test/strategies/curve-amo-oeth.mainnet.fork-test.js index 4accfb546a..bbcf6340b1 100644 --- a/contracts/test/strategies/curve-amo-oeth.mainnet.fork-test.js +++ b/contracts/test/strategies/curve-amo-oeth.mainnet.fork-test.js @@ -10,6 +10,8 @@ const { advanceTime } = require("../helpers"); const { shouldBehaveLikeGovernable } = require("../behaviour/governable"); const { shouldBehaveLikeHarvestable } = require("../behaviour/harvestable"); const { shouldBehaveLikeStrategy } = require("../behaviour/strategy"); +const erc20Abi = require("../../abi/erc20.json"); +const crvMinterAbi = require("../../abi/crvMinter.json"); const { loadDefaultFixture } = require("../_fixture"); @@ -37,6 +39,7 @@ describe("Curve AMO OETH strategy", function () { impersonatedCurveStrategy, impersonatedTimelock, crv, + crvMinter, harvester; let defaultDepositor; @@ -55,7 +58,11 @@ describe("Curve AMO OETH strategy", function () { timelock = fixture.timelock; curvePool = fixture.curvePoolOETHWETH; curveGauge = fixture.curveGaugeOETHWETH; - crv = fixture.crv; + crv = await ethers.getContractAt(erc20Abi, addresses.mainnet.CRV); + crvMinter = await ethers.getContractAt( + crvMinterAbi, + addresses.mainnet.CRVMinter + ); harvester = fixture.strategist; governor = await ethers.getSigner(addresses.mainnet.Timelock); @@ -614,7 +621,7 @@ describe("Curve AMO OETH strategy", function () { const integrate_fraction = await curveGauge.integrate_fraction( curveAMOStrategy.address ); - const alreadyMinted = await fixture.crvMinter.minted( + const alreadyMinted = await crvMinter.minted( curveAMOStrategy.address, curveGauge.address ); diff --git a/contracts/test/strategies/curve-amo-ousd.mainnet.fork-test.js b/contracts/test/strategies/curve-amo-ousd.mainnet.fork-test.js index f713fb8453..0b4accf29f 100644 --- a/contracts/test/strategies/curve-amo-ousd.mainnet.fork-test.js +++ b/contracts/test/strategies/curve-amo-ousd.mainnet.fork-test.js @@ -10,12 +10,14 @@ const { advanceTime } = require("../helpers"); const { shouldBehaveLikeGovernable } = require("../behaviour/governable"); const { shouldBehaveLikeHarvestable } = require("../behaviour/harvestable"); const { shouldBehaveLikeStrategy } = require("../behaviour/strategy"); +const erc20Abi = require("../../abi/erc20.json"); +const crvMinterAbi = require("../../abi/crvMinter.json"); const { loadDefaultFixture } = require("../_fixture"); const log = require("../../utils/logger")("test:fork:ousd:curve:amo"); -describe("Curve AMO OUSD strategy", function () { +describe("Fork Test: Curve AMO OUSD strategy", function () { this.timeout(0); let fixture, @@ -37,6 +39,7 @@ describe("Curve AMO OUSD strategy", function () { impersonatedCurveStrategy, impersonatedTimelock, crv, + crvMinter, harvester; let defaultDepositor; @@ -55,7 +58,11 @@ describe("Curve AMO OUSD strategy", function () { timelock = fixture.timelock; curvePool = fixture.curvePoolOusdUsdc; curveGauge = fixture.curveGaugeOusdUsdc; - crv = fixture.crv; + crv = await ethers.getContractAt(erc20Abi, addresses.mainnet.CRV); + crvMinter = await ethers.getContractAt( + crvMinterAbi, + addresses.mainnet.CRVMinter + ); harvester = fixture.strategist; governor = await ethers.getSigner(addresses.mainnet.Timelock); @@ -609,7 +616,7 @@ describe("Curve AMO OUSD strategy", function () { const integrate_fraction = await curveGauge.integrate_fraction( curveAMOStrategy.address ); - const alreadyMinted = await fixture.crvMinter.minted( + const alreadyMinted = await crvMinter.minted( curveAMOStrategy.address, curveGauge.address ); diff --git a/contracts/test/strategies/dripper.js b/contracts/test/strategies/dripper.js deleted file mode 100644 index 9a6209c713..0000000000 --- a/contracts/test/strategies/dripper.js +++ /dev/null @@ -1,137 +0,0 @@ -const { expect } = require("chai"); -const { - createFixtureLoader, - instantRebaseVaultFixture, -} = require("../_fixture"); - -const { usdtUnits, advanceTime } = require("../helpers"); - -describe("Dripper", async () => { - let dripper, usdt, vault, governor, josh; - const loadFixture = createFixtureLoader(instantRebaseVaultFixture); - - beforeEach(async () => { - const fixture = await loadFixture(); - dripper = fixture.dripper; - usdt = fixture.usdt; - vault = fixture.vault; - governor = fixture.governor; - josh = fixture.josh; - - await usdt.mintTo(dripper.address, usdtUnits("1000")); - }); - - async function emptyDripper() { - const balance = await usdt.balanceOf(dripper.address); - await dripper.connect(governor).transferToken(usdt.address, balance); - } - - async function expectApproxCollectOf(amount, fn) { - const before = await usdt.balanceOf(vault.address); - await fn(); - const after = await usdt.balanceOf(vault.address); - const collected = after.sub(before); - expect(collected).gte(usdtUnits(amount).mul(998).div(1000)); - expect(collected).lte(usdtUnits(amount).mul(1002).div(1000)); - } - - describe("availableFunds()", async () => { - it("shows zero available before any duration has been set", async () => { - await advanceTime(1000); - expect(await dripper.availableFunds()).to.equal(0); - }); - it("returns a number after a duration has been set", async () => { - await dripper.connect(governor).setDripDuration("2000"); - await advanceTime(1000); - /* sometimes this test fails with 0.1% deviation from the expected value. Somehow - * another second slips in between Dripper's setDripDuration and 1000 seconds of - * advance time. - * - * Assumption is that in some cases the setDripDuration advances the time for 1 second - * so the total advanced time in the end equals 1001 seconds. Either that or hardhat - * isn't completely precise when advancing time using `evm_increaseTime`. - * - * It is hard to test this because the failure is intermittent. - */ - - // 0.1% out of 1000 seconds is 1 second - expect(await dripper.availableFunds()).to.approxEqualTolerance( - usdtUnits("500"), - 0.1 - ); - }); - it("returns zero if no balance", async () => { - await dripper.connect(governor).setDripDuration("2000"); - await advanceTime(1000); - await emptyDripper(); - expect(await dripper.availableFunds()).to.equal(usdtUnits("0")); - }); - }); - describe("collect()", async () => { - it("transfers funds to the vault", async () => { - await dripper.connect(governor).setDripDuration("20000"); - await advanceTime(1000); - await expectApproxCollectOf("50", dripper.collect); - }); - it("collects what is reported by availableFunds()", async () => { - await dripper.connect(governor).setDripDuration("20000"); - await advanceTime(17890); - const expected = ((await dripper.availableFunds()) / 1e6).toString(); - await expectApproxCollectOf(expected, dripper.collect); - }); - }); - describe("Drip math", async () => { - it("gives all funds if collect is after the duration end", async () => { - await dripper.connect(governor).setDripDuration("20000"); - await advanceTime(20001); - await expectApproxCollectOf("1000", dripper.collect); - }); - it("gives 98% of funds if the collect is 98% to the duration", async () => { - await dripper.connect(governor).setDripDuration("20000"); - await advanceTime(19600); - await expectApproxCollectOf("980", dripper.collect); - }); - it("adding funds does not change the current drip rate", async () => { - await dripper.connect(governor).setDripDuration("20000"); - await usdt.mintTo(dripper.address, usdtUnits("3000")); - await advanceTime(19600); - await expectApproxCollectOf("980", dripper.collect); - }); - it("rounds down the rate", async () => { - await emptyDripper(); - await usdt.mintTo(dripper.address, 999); // 1/1000 of a USDC - await dripper.connect(governor).setDripDuration("1000"); - await advanceTime(500); - // Per block rate should be zero - await expectApproxCollectOf("0", dripper.collect); - }); - }); - describe("collectTokens()", async () => { - it("transfers funds to governor", async () => { - await expect(governor).to.have.balanceOf("1000", usdt); - await expect(dripper).to.have.balanceOf("1000", usdt); - const balance = usdt.balanceOf(dripper.address); - await dripper.connect(governor).transferToken(usdt.address, balance); - await expect(dripper).to.have.balanceOf("0", usdt); - await expect(governor).to.have.balanceOf("2000", usdt); - }); - it("cannot be called by the public", async () => { - await expect(dripper.connect(josh).transferToken(usdt.address, 1)).to.be - .reverted; - }); - }); - describe("setDripDuration()", async () => { - it("transfers funds to governor", async () => { - await dripper.connect(governor).setDripDuration(1000); - expect(await dripper.dripDuration()).to.equal(1000); - }); - it("cannot be called by the public", async () => { - await expect(dripper.connect(josh).setDripDuration(1000)).to.be.reverted; - }); - it("cannot be set to zero by the public", async () => { - await expect( - dripper.connect(governor).setDripDuration(0) - ).to.be.revertedWith("duration must be non-zero"); - }); - }); -}); diff --git a/contracts/test/strategies/morpho-comp.mainnet.fork-test.js b/contracts/test/strategies/morpho-comp.mainnet.fork-test.js deleted file mode 100644 index 0995ce45cf..0000000000 --- a/contracts/test/strategies/morpho-comp.mainnet.fork-test.js +++ /dev/null @@ -1,229 +0,0 @@ -const { expect } = require("chai"); - -const { - units, - ousdUnits, - differenceInErc20TokenBalance, - advanceBlocks, - advanceTime, - isCI, -} = require("../helpers"); -const { createFixtureLoader, morphoCompoundFixture } = require("../_fixture"); -const { impersonateAndFund } = require("../../utils/signers"); - -describe.skip("ForkTest: Morpho Compound Strategy", function () { - this.timeout(0); - - // Retry up to 3 times on CI - this.retries(isCI ? 3 : 0); - - let fixture; - const loadFixture = createFixtureLoader(morphoCompoundFixture); - beforeEach(async () => { - fixture = await loadFixture(); - }); - - describe("Mint", function () { - it("Should deploy USDC in Morpho Compound", async function () { - const { matt, usdc } = fixture; - await mintTest(fixture, matt, usdc, "110000"); - }); - - it("Should deploy USDT in Morpho Compound", async function () { - const { josh, usdt } = fixture; - await mintTest(fixture, josh, usdt, "200000"); - }); - - it("Should deploy USDS in Morpho Compound", async function () { - const { anna, usds } = fixture; - await mintTest(fixture, anna, usds, "110000"); - }); - }); - - describe("Redeem", function () { - it("Should redeem from Morpho", async () => { - const { vault, ousd, usdt, usdc, usds, domen } = fixture; - - await vault.connect(domen).rebase(); - - const supplyBeforeMint = await ousd.totalSupply(); - - const amount = "20020"; - - // Mint with all three assets - for (const asset of [usdt, usdc, usds]) { - await vault - .connect(domen) - .mint(asset.address, await units(amount, asset), 0); - } - - const currentSupply = await ousd.totalSupply(); - const supplyAdded = currentSupply.sub(supplyBeforeMint); - expect(supplyAdded).to.approxEqualTolerance(ousdUnits("60000"), 1); - - const currentBalance = await ousd.connect(domen).balanceOf(domen.address); - - // Now try to redeem 30k - await vault.connect(domen).redeem(ousdUnits("30000"), 0); - - // User balance should be down by 30k - const newBalance = await ousd.connect(domen).balanceOf(domen.address); - expect(newBalance).to.approxEqualTolerance( - currentBalance.sub(ousdUnits("30000")), - 1 - ); - - const newSupply = await ousd.totalSupply(); - const supplyDiff = currentSupply.sub(newSupply); - - expect(supplyDiff).to.approxEqualTolerance(ousdUnits("30000"), 1); - }); - }); - - describe("Withdraw", function () { - it("Should be able to withdraw from strategy", async function () { - const { matt, usdc, vault, morphoCompoundStrategy } = fixture; - const amount = "110000"; - await mintTest(fixture, matt, usdc, amount); - - const usdcUnits = await units(amount, usdc); - const vaultUsdcBefore = await usdc.balanceOf(vault.address); - const vaultSigner = await impersonateAndFund(vault.address); - - await morphoCompoundStrategy - .connect(vaultSigner) - .withdraw(vault.address, usdc.address, usdcUnits); - const vaultUsdcDiff = - (await usdc.balanceOf(vault.address)) - vaultUsdcBefore; - - expect(vaultUsdcDiff).to.approxEqualTolerance(usdcUnits, 1); - }); - - it("Should be able to withdrawAll from strategy", async function () { - const { matt, usdc, vault, usdt, morphoCompoundStrategy } = fixture; - const vaultSigner = await impersonateAndFund(vault.address); - const amount = "110000"; - - const usdcUnits = await units(amount, usdc); - const usdtUnits = await units(amount, usdt); - - await vault.connect(matt).mint(usdt.address, usdtUnits, 0); - await vault.connect(matt).mint(usdc.address, usdcUnits, 0); - - await vault.connect(matt).rebase(); - await vault.connect(matt).allocate(); - - const vaultUsdtBefore = await usdt.balanceOf(vault.address); - const vaultUsdcBefore = await usdc.balanceOf(vault.address); - - const stratBalUsdc = await morphoCompoundStrategy.checkBalance( - usdc.address - ); - const stratBalUsdt = await morphoCompoundStrategy.checkBalance( - usdt.address - ); - - await morphoCompoundStrategy.connect(vaultSigner).withdrawAll(); - - const vaultUsdtDiff = - (await usdt.balanceOf(vault.address)) - vaultUsdtBefore; - const vaultUsdcDiff = - (await usdc.balanceOf(vault.address)) - vaultUsdcBefore; - - expect(vaultUsdcDiff).to.approxEqualTolerance(stratBalUsdc, 1); - expect(vaultUsdtDiff).to.approxEqualTolerance(stratBalUsdt, 1); - - expect(await morphoCompoundStrategy.checkBalance(usdc.address)).to.equal( - "0" - ); - expect(await morphoCompoundStrategy.checkBalance(usdt.address)).to.equal( - "0" - ); - }); - }); - - // set it as a last test that executes because we advance time and theat - // messes with recency of oracle prices - describe("Rewards", function () { - it("Should be able to harvest rewards", async function () { - const { - harvester, - daniel, - anna, - usdc, - cusdc, - usdt, - morphoLens, - morphoCompoundStrategy, - dripper, - } = fixture; - await mintTest(fixture, anna, usdc, "110000"); - - // harvester always exchanges for USDT and parks the funds in the dripper - const usdtBalanceDiff = await differenceInErc20TokenBalance( - dripper.address, - usdt, - async () => { - // advance time so that some rewards accrue - await advanceTime(3600 * 24 * 1); - await advanceBlocks(100); - // check that rewards are there - await expect( - await morphoLens.getUserUnclaimedRewards( - [cusdc.address], - morphoCompoundStrategy.address - ) - ).to.be.gte(0); - // prettier-ignore - await harvester - .connect(daniel)["harvestAndSwap(address)"](morphoCompoundStrategy.address); - } - ); - - expect(usdtBalanceDiff).to.be.gte(0); - }); - }); -}); - -async function mintTest(fixture, user, asset, amount = "30000") { - const { vault, ousd, morphoCompoundStrategy } = fixture; - - await vault.connect(user).rebase(); - await vault.connect(user).allocate(); - - const unitAmount = await units(amount, asset); - - const currentSupply = await ousd.totalSupply(); - const currentBalance = await ousd.connect(user).balanceOf(user.address); - const currentMorphoBalance = await morphoCompoundStrategy.checkBalance( - asset.address - ); - - // Mint OUSD w/ asset - await vault.connect(user).mint(asset.address, unitAmount, 0); - await vault.connect(user).allocate(); - - const newBalance = await ousd.connect(user).balanceOf(user.address); - const newSupply = await ousd.totalSupply(); - const newMorphoBalance = await morphoCompoundStrategy.checkBalance( - asset.address - ); - - const balanceDiff = newBalance.sub(currentBalance); - // Ensure user has correct balance (w/ 1% slippage tolerance) - expect(balanceDiff).to.approxEqualTolerance(ousdUnits(amount), 2); - - // Supply checks - const supplyDiff = newSupply.sub(currentSupply); - const ousdUnitAmount = ousdUnits(amount); - - expect(supplyDiff).to.approxEqualTolerance(ousdUnitAmount, 1); - - const morphoLiquidityDiff = newMorphoBalance.sub(currentMorphoBalance); - - // Should have liquidity in Morpho - expect(morphoLiquidityDiff).to.approxEqualTolerance( - await units(amount, asset), - 1 - ); -} diff --git a/contracts/test/strategies/oeth-metapool.mainnet.fork-test.js b/contracts/test/strategies/oeth-metapool.mainnet.fork-test.js deleted file mode 100644 index 9542973c22..0000000000 --- a/contracts/test/strategies/oeth-metapool.mainnet.fork-test.js +++ /dev/null @@ -1,958 +0,0 @@ -const { expect } = require("chai"); -const { formatUnits, parseUnits } = require("ethers/lib/utils"); -const { run } = require("hardhat"); - -const addresses = require("../../utils/addresses"); -const { oethPoolLpPID, ONE } = require("../../utils/constants"); -const { units, oethUnits, isCI } = require("../helpers"); -const { - createFixtureLoader, - convexOETHMetaVaultFixture, -} = require("../_fixture"); - -const log = require("../../utils/logger")("test:fork:oeth:metapool"); - -describe.skip("ForkTest: OETH AMO Curve Metapool Strategy", function () { - this.timeout(0); - // Retry up to 3 times on CI - this.retries(isCI ? 3 : 0); - - let fixture; - - const min = (bn1, bn2) => { - if (bn1.gt(bn2)) return bn2; - return bn1; - }; - - describe("with mainnet data", () => { - const loadFixture = createFixtureLoader(convexOETHMetaVaultFixture); - beforeEach(async () => { - fixture = await loadFixture(); - }); - it("Should have constants and immutables set", async () => { - const { convexEthMetaStrategy } = fixture; - - expect(await convexEthMetaStrategy.MAX_SLIPPAGE()).to.equal( - parseUnits("0.01", 18) - ); - expect(await convexEthMetaStrategy.ETH_ADDRESS()).to.equal(addresses.ETH); - - expect(await convexEthMetaStrategy.cvxDepositorAddress()).to.equal( - addresses.mainnet.CVXBooster - ); - expect(await convexEthMetaStrategy.cvxRewardStaker()).to.equal( - addresses.mainnet.CVXETHRewardsPool - ); - expect(await convexEthMetaStrategy.cvxDepositorPTokenId()).to.equal( - oethPoolLpPID - ); - expect(await convexEthMetaStrategy.curvePool()).to.equal( - addresses.mainnet.CurveOETHMetaPool - ); - expect(await convexEthMetaStrategy.lpToken()).to.equal( - addresses.mainnet.CurveOETHMetaPool - ); - expect(await convexEthMetaStrategy.oeth()).to.equal( - addresses.mainnet.OETHProxy - ); - expect(await convexEthMetaStrategy.weth()).to.equal( - addresses.mainnet.WETH - ); - }); - it("Should be able to check balance", async () => { - const { weth, josh, convexEthMetaStrategy } = fixture; - - const balance = await convexEthMetaStrategy.checkBalance(weth.address); - log(`check balance ${balance}`); - expect(balance).gt(0); - - // This uses a transaction to call a view function so the gas usage can be reported. - const tx = await convexEthMetaStrategy - .connect(josh) - .populateTransaction.checkBalance(weth.address); - await josh.sendTransaction(tx); - }); - // Skipping this since we switched to simple harvester - it.skip("Should be able to harvest the rewards", async function () { - const { - josh, - weth, - oethHarvester, - oethDripper, - oethVault, - convexEthMetaStrategy, - crv, - } = fixture; - - // send some CRV to the strategy to partly simulate reward harvesting - await crv - .connect(josh) - .transfer(convexEthMetaStrategy.address, parseUnits("2000")); - - const wethBefore = await weth.balanceOf(oethDripper.address); - - // prettier-ignore - await oethHarvester - .connect(josh)["harvestAndSwap(address)"](convexEthMetaStrategy.address); - - const wethDiff = (await weth.balanceOf(oethDripper.address)).sub( - wethBefore - ); - await oethVault.connect(josh).rebase(); - - await expect(wethDiff).to.be.gte(parseUnits("0.1")); - }); - it("Only Governor can approve all tokens", async () => { - const { - timelock, - strategist, - josh, - oethVaultSigner, - convexEthMetaStrategy, - weth, - oeth, - oethMetaPool, - } = fixture; - - // Governor can approve all tokens - const tx = await convexEthMetaStrategy - .connect(timelock) - .safeApproveAllTokens(); - await expect(tx).to.not.emit(weth, "Approval"); - await expect(tx).to.emit(oeth, "Approval"); - await expect(tx).to.emit(oethMetaPool, "Approval"); - - for (const signer of [strategist, josh, oethVaultSigner]) { - const tx = convexEthMetaStrategy.connect(signer).safeApproveAllTokens(); - await expect(tx).to.be.revertedWith("Caller is not the Governor"); - } - }); - }); - - describe("with some WETH in the vault", () => { - const loadFixture = createFixtureLoader(convexOETHMetaVaultFixture, { - wethMintAmount: 5000, - depositToStrategy: false, - balancePool: true, - }); - beforeEach(async () => { - fixture = await loadFixture(); - }); - it("Vault should deposit some WETH to AMO strategy", async function () { - const { - convexEthMetaStrategy, - oeth, - oethMetaPool, - oethVaultSigner, - weth, - } = fixture; - - const wethDepositAmount = await units("5000", weth); - - // Vault transfers WETH to strategy - await weth - .connect(oethVaultSigner) - .transfer(convexEthMetaStrategy.address, wethDepositAmount); - - const { oethMintAmount, curveBalances: curveBalancesBefore } = - await calcOethMintAmount(fixture, wethDepositAmount); - const oethSupplyBefore = await oeth.totalSupply(); - - log("Before deposit to strategy"); - await run("amoStrat", { - pool: "OETH", - output: false, - }); - - const tx = await convexEthMetaStrategy - .connect(oethVaultSigner) - .deposit(weth.address, wethDepositAmount); - - const receipt = await tx.wait(); - - log("After deposit to strategy"); - await run("amoStrat", { - pool: "OETH", - output: false, - fromBlock: receipt.blockNumber - 1, - }); - - // Check emitted events - await expect(tx) - .to.emit(convexEthMetaStrategy, "Deposit") - .withArgs(weth.address, oethMetaPool.address, wethDepositAmount); - await expect(tx) - .to.emit(convexEthMetaStrategy, "Deposit") - .withArgs(oeth.address, oethMetaPool.address, oethMintAmount); - - // Check the ETH and OETH balances in the Curve Metapool - const curveBalancesAfter = await oethMetaPool.get_balances(); - expect(curveBalancesAfter[0]).to.approxEqualTolerance( - curveBalancesBefore[0].add(wethDepositAmount), - 0.01 // 0.01% or 1 basis point - ); - expect(curveBalancesAfter[1]).to.approxEqualTolerance( - curveBalancesBefore[1].add(oethMintAmount), - 0.01 // 0.01% or 1 basis point - ); - - // Check the OETH total supply increase - const oethSupplyAfter = await oeth.totalSupply(); - expect(oethSupplyAfter).to.approxEqualTolerance( - oethSupplyBefore.add(oethMintAmount), - 0.01 // 0.01% or 1 basis point - ); - }); - it("Only vault can deposit some WETH to AMO strategy", async function () { - const { - convexEthMetaStrategy, - oethVaultSigner, - strategist, - timelock, - josh, - weth, - } = fixture; - - const depositAmount = parseUnits("50"); - await weth - .connect(oethVaultSigner) - .transfer(convexEthMetaStrategy.address, depositAmount); - - for (const signer of [strategist, timelock, josh]) { - const tx = convexEthMetaStrategy - .connect(signer) - .deposit(weth.address, depositAmount); - - await expect(tx).to.revertedWith("Caller is not the Vault"); - } - }); - it("Only vault can deposit all WETH to AMO strategy", async function () { - const { - convexEthMetaStrategy, - oethMetaPool, - oethVaultSigner, - strategist, - timelock, - josh, - weth, - } = fixture; - - const depositAmount = parseUnits("50"); - await weth - .connect(oethVaultSigner) - .transfer(convexEthMetaStrategy.address, depositAmount); - - for (const signer of [strategist, timelock, josh]) { - const tx = convexEthMetaStrategy.connect(signer).depositAll(); - - await expect(tx).to.revertedWith("Caller is not the Vault"); - } - - const tx = await convexEthMetaStrategy - .connect(oethVaultSigner) - .depositAll(); - await expect(tx) - .to.emit(convexEthMetaStrategy, "Deposit") - .withNamedArgs({ _asset: weth.address, _pToken: oethMetaPool.address }); - }); - }); - - describe("with the strategy having some OETH and ETH in the Metapool", () => { - const loadFixture = createFixtureLoader(convexOETHMetaVaultFixture, { - wethMintAmount: 5000, - depositToStrategy: true, - balancePool: true, - }); - beforeEach(async () => { - fixture = await loadFixture(); - }); - it("Vault should be able to withdraw all", async () => { - const { - convexEthMetaStrategy, - oethMetaPool, - oeth, - oethVaultSigner, - weth, - } = fixture; - - const { - oethBurnAmount, - ethWithdrawAmount, - curveBalances: curveBalancesBefore, - } = await calcWithdrawAllAmounts(fixture); - - const oethSupplyBefore = await oeth.totalSupply(); - - log("Before withdraw all from strategy"); - await run("amoStrat", { - pool: "OETH", - output: false, - }); - - // Now try to withdraw all the WETH from the strategy - const tx = await convexEthMetaStrategy - .connect(oethVaultSigner) - .withdrawAll(); - - const receipt = await tx.wait(); - - log("After withdraw all from strategy"); - await run("amoStrat", { - pool: "OETH", - output: false, - fromBlock: receipt.blockNumber - 1, - }); - - // Check emitted events - await expect(tx) - .to.emit(convexEthMetaStrategy, "Withdrawal") - .withArgs(weth.address, oethMetaPool.address, ethWithdrawAmount); - await expect(tx) - .to.emit(convexEthMetaStrategy, "Withdrawal") - .withArgs(oeth.address, oethMetaPool.address, oethBurnAmount); - - // Check the ETH and OETH balances in the Curve Metapool - const curveBalancesAfter = await oethMetaPool.get_balances(); - expect(curveBalancesAfter[0]).to.approxEqualTolerance( - curveBalancesBefore[0].sub(ethWithdrawAmount), - 0.05 // 0.05% or 5 basis point - ); - expect(curveBalancesAfter[1]).to.approxEqualTolerance( - curveBalancesBefore[1].sub(oethBurnAmount), - 0.05 // 0.05% - ); - - // Check the OETH total supply decrease - const oethSupplyAfter = await oeth.totalSupply(); - expect(oethSupplyAfter).to.approxEqualTolerance( - oethSupplyBefore.sub(oethBurnAmount), - 0.05 // 0.01% or 5 basis point - ); - }); - it("Vault should be able to withdraw some", async () => { - const { - convexEthMetaStrategy, - oeth, - oethMetaPool, - oethVault, - oethVaultSigner, - weth, - } = fixture; - - const withdrawAmount = oethUnits("1000"); - - const { oethBurnAmount, curveBalances: curveBalancesBefore } = - await calcOethWithdrawAmount(fixture, withdrawAmount); - const oethSupplyBefore = await oeth.totalSupply(); - const vaultWethBalanceBefore = await weth.balanceOf(oethVault.address); - - log("Before withdraw from strategy"); - await run("amoStrat", { - pool: "OETH", - output: false, - }); - - // Now try to withdraw the WETH from the strategy - const tx = await convexEthMetaStrategy - .connect(oethVaultSigner) - .withdraw(oethVault.address, weth.address, withdrawAmount); - - const receipt = await tx.wait(); - - log("After withdraw from strategy"); - await run("amoStrat", { - pool: "OETH", - output: false, - fromBlock: receipt.blockNumber - 1, - }); - - // Check emitted events - await expect(tx) - .to.emit(convexEthMetaStrategy, "Withdrawal") - .withArgs(weth.address, oethMetaPool.address, withdrawAmount); - await expect(tx) - .to.emit(convexEthMetaStrategy, "Withdrawal") - .withNamedArgs({ _asset: oeth.address, _pToken: oethMetaPool.address }); - - // Check the ETH and OETH balances in the Curve Metapool - const curveBalancesAfter = await oethMetaPool.get_balances(); - expect(curveBalancesAfter[0]).to.approxEqualTolerance( - curveBalancesBefore[0].sub(withdrawAmount), - 0.05 // 0.05% or 5 basis point - ); - expect(curveBalancesAfter[1]).to.approxEqualTolerance( - curveBalancesBefore[1].sub(oethBurnAmount), - 0.05 // 0.05% - ); - - // Check the OETH total supply decrease - const oethSupplyAfter = await oeth.totalSupply(); - expect(oethSupplyAfter).to.approxEqualTolerance( - oethSupplyBefore.sub(oethBurnAmount), - 0.05 // 0.05% or 5 basis point - ); - - // Check the WETH balance in the Vault - expect(await weth.balanceOf(oethVault.address)).to.equal( - vaultWethBalanceBefore.add(withdrawAmount) - ); - }); - it("Only vault can withdraw some WETH from AMO strategy", async function () { - const { - convexEthMetaStrategy, - oethVault, - strategist, - timelock, - josh, - weth, - } = fixture; - - for (const signer of [strategist, timelock, josh]) { - const tx = convexEthMetaStrategy - .connect(signer) - .withdraw(oethVault.address, weth.address, parseUnits("50")); - - await expect(tx).to.revertedWith("Caller is not the Vault"); - } - }); - it("Only vault and governor can withdraw all WETH from AMO strategy", async function () { - const { convexEthMetaStrategy, strategist, timelock, josh } = fixture; - - for (const signer of [strategist, josh]) { - const tx = convexEthMetaStrategy.connect(signer).withdrawAll(); - - await expect(tx).to.revertedWith("Caller is not the Vault or Governor"); - } - - // Governor can withdraw all - const tx = convexEthMetaStrategy.connect(timelock).withdrawAll(); - await expect(tx).to.emit(convexEthMetaStrategy, "Withdrawal"); - }); - }); - - describe("with a lot more OETH in the Metapool", () => { - const loadFixture = createFixtureLoader(convexOETHMetaVaultFixture, { - wethMintAmount: 5000, - depositToStrategy: false, - poolAddOethAmount: 20000, - balancePool: true, - }); - beforeEach(async () => { - fixture = await loadFixture(); - }); - - it("Strategist should remove a little OETH from the Metapool", async () => { - await assertRemoveAndBurn(parseUnits("3"), fixture); - }); - - it("Strategist should remove as much OETH from the Metapool while still not crossing the 1:1 peg", async () => { - const { cvxRewardPool, convexEthMetaStrategy, oethMetaPool } = fixture; - const lpBalance = await cvxRewardPool.balanceOf( - convexEthMetaStrategy.address - ); - const virtualPrice = await oethMetaPool.get_virtual_price(); - const [ethBalance, oethBalance] = await oethMetaPool.get_balances(); - - // removing OETH tokens will only worsen the pool balance. Nothing to test here. - if (ethBalance > oethBalance) { - return; - } - - const balanceDiff = oethBalance.sub(ethBalance); - const balanceDiffInLp = balanceDiff.mul(ONE).div(virtualPrice); - - // Max amount removable on the pool while the price doesn't cross the peg - const balanceDiffInLpAdjusted = balanceDiffInLp - // reduce by 2% - .mul(98) - .div(100); - - const lpAmount = min(balanceDiffInLpAdjusted, lpBalance); - - await assertRemoveAndBurn(lpAmount, fixture); - }); - it("Strategist should fail to add even more OETH to the Metapool", async () => { - const { convexEthMetaStrategy, strategist } = fixture; - - log("Before mintAndAddOTokens"); - await run("amoStrat", { - pool: "OETH", - output: false, - }); - - // Mint and add OETH to the Metapool - const tx = convexEthMetaStrategy - .connect(strategist) - .mintAndAddOTokens(parseUnits("1")); - - await expect(tx).to.be.revertedWith("OTokens balance worse"); - }); - }); - - describe("with a lot more OETH in the Metapool", () => { - const loadFixture = createFixtureLoader(convexOETHMetaVaultFixture, { - wethMintAmount: 5000, - depositToStrategy: false, - poolAddOethAmount: 6000, - balancePool: true, - }); - beforeEach(async () => { - fixture = await loadFixture(); - }); - it("Strategist should fail to remove the little ETH from the Metapool", async () => { - const { convexEthMetaStrategy, strategist } = fixture; - - // Remove ETH form the Metapool - const tx = convexEthMetaStrategy - .connect(strategist) - .removeOnlyAssets(parseUnits("1")); - - await expect(tx).to.be.revertedWith("OTokens balance worse"); - }); - }); - - describe("with a lot more ETH in the Metapool", () => { - const loadFixture = createFixtureLoader(convexOETHMetaVaultFixture, { - wethMintAmount: 5000, - depositToStrategy: false, - poolAddEthAmount: 200000, - balancePool: true, - }); - beforeEach(async () => { - fixture = await loadFixture(); - }); - it("Strategist should add a little OETH to the Metapool", async () => { - const oethMintAmount = oethUnits("3"); - await assertMintAndAddOTokens(oethMintAmount, fixture); - }); - it("Strategist should add a lot of OETH to the Metapool", async () => { - const oethMintAmount = oethUnits("150000"); - await assertMintAndAddOTokens(oethMintAmount, fixture); - }); - it("Strategist should add OETH to balance the Metapool", async () => { - const { oethMetaPool } = fixture; - const curveBalances = await oethMetaPool.get_balances(); - const oethMintAmount = curveBalances[0] - .sub(curveBalances[1]) - // reduce by 0.001% - .mul(99999) - .div(100000); - - await assertMintAndAddOTokens(oethMintAmount, fixture); - }); - it("Strategist should remove a little ETH from the Metapool", async () => { - const lpAmount = parseUnits("2"); - await assertRemoveOnlyAssets(lpAmount, fixture); - }); - it("Strategist should remove a lot ETH from the Metapool", async () => { - const { cvxRewardPool, convexEthMetaStrategy } = fixture; - const lpBalance = await cvxRewardPool.balanceOf( - convexEthMetaStrategy.address - ); - const lpAmount = lpBalance - // reduce by 1% - .mul(99) - .div(100); - - await assertRemoveOnlyAssets(lpAmount, fixture); - }); - }); - - describe("with a little more ETH in the Metapool", () => { - const loadFixture = createFixtureLoader(convexOETHMetaVaultFixture, { - wethMintAmount: 20000, - depositToStrategy: false, - poolAddEthAmount: 22000, - balancePool: true, - }); - beforeEach(async () => { - fixture = await loadFixture(); - }); - it("Strategist should remove ETH to balance the Metapool", async () => { - const { cvxRewardPool, convexEthMetaStrategy, oethMetaPool } = fixture; - const lpBalance = await cvxRewardPool.balanceOf( - convexEthMetaStrategy.address - ); - const virtualPrice = await oethMetaPool.get_virtual_price(); - const [ethBalance, oethBalance] = await oethMetaPool.get_balances(); - - // removing WETH tokens will only worsen the pool balance. Nothing to test here. - if (oethBalance > ethBalance) { - return; - } - - const balanceDiff = ethBalance.sub(oethBalance); - const balanceDiffInLp = balanceDiff.mul(ONE).div(virtualPrice); - - // Max amount removable on the pool while the price doesn't cross the peg - const balanceDiffInLpAdjusted = balanceDiffInLp - // reduce by 2% - .mul(98) - .div(100); - - const lpAmount = min(balanceDiffInLpAdjusted, lpBalance); - - await assertRemoveOnlyAssets(lpAmount, fixture); - }); - }); - - describe("with a little more ETH in the Metapool", () => { - const loadFixture = createFixtureLoader(convexOETHMetaVaultFixture, { - wethMintAmount: 20000, - depositToStrategy: true, - poolAddEthAmount: 8000, - balancePool: true, - }); - beforeEach(async () => { - fixture = await loadFixture(); - }); - it("Strategist should fail to add too much OETH to the Metapool", async () => { - const { convexEthMetaStrategy, strategist } = fixture; - - // Add OETH to the Metapool - const tx = convexEthMetaStrategy - .connect(strategist) - .mintAndAddOTokens(parseUnits("10000")); - - await expect(tx).to.be.revertedWith("Assets overshot peg"); - }); - it("Strategist should fail to remove too much ETH from the Metapool", async () => { - const { convexEthMetaStrategy, strategist } = fixture; - - // Remove ETH from the Metapool - const tx = convexEthMetaStrategy - .connect(strategist) - .removeOnlyAssets(parseUnits("8000")); - - await expect(tx).to.be.revertedWith("Assets overshot peg"); - }); - it("Strategist should fail to remove the little OETH from the Metapool", async () => { - const { convexEthMetaStrategy, strategist } = fixture; - - // Remove ETH from the Metapool - const tx = convexEthMetaStrategy - .connect(strategist) - .removeAndBurnOTokens(parseUnits("1")); - - await expect(tx).to.be.revertedWith("Assets balance worse"); - }); - }); - - describe("with a little more OETH in the Metapool", () => { - const loadFixture = createFixtureLoader(convexOETHMetaVaultFixture, { - wethMintAmount: 20000, - depositToStrategy: false, - poolAddOethAmount: 2000, - balancePool: true, - }); - beforeEach(async () => { - fixture = await loadFixture(); - }); - it("Strategist should fail to remove too much OETH from the Metapool", async () => { - const { cvxRewardPool, convexEthMetaStrategy, strategist } = fixture; - - log("Before removeAndBurnOTokens"); - await run("amoStrat", { - pool: "OETH", - output: false, - }); - - const lpBalance = await cvxRewardPool.balanceOf( - convexEthMetaStrategy.address - ); - const lpAmount = lpBalance - // reduce by 40% - .mul(60) - .div(100); - - // Remove OETH from the Metapool - const tx = convexEthMetaStrategy - .connect(strategist) - .removeAndBurnOTokens(lpAmount); - - await expect(tx).to.be.revertedWith("OTokens overshot peg"); - }); - }); -}); - -async function assertRemoveAndBurn(lpAmount, fixture) { - const { convexEthMetaStrategy, oethMetaPool, oeth, strategist } = fixture; - - const oethBurnAmount = await calcOethRemoveAmount(fixture, lpAmount); - const curveBalancesBefore = await oethMetaPool.get_balances(); - const oethSupplyBefore = await oeth.totalSupply(); - - log( - `Before remove and burn of ${formatUnits(lpAmount)} OETH from the Metapool` - ); - await run("amoStrat", { - pool: "OETH", - output: false, - }); - - // Remove and burn OETH from the Metapool - const tx = await convexEthMetaStrategy - .connect(strategist) - .removeAndBurnOTokens(lpAmount); - - const receipt = await tx.wait(); - - log("After remove and burn of OETH from Metapool"); - await run("amoStrat", { - pool: "OETH", - output: false, - fromBlock: receipt.blockNumber - 1, - }); - - // Check emitted event - await expect(tx) - .to.emit(convexEthMetaStrategy, "Withdrawal") - .withArgs(oeth.address, oethMetaPool.address, oethBurnAmount); - - // Check the ETH and OETH balances in the Curve Metapool - const curveBalancesAfter = await oethMetaPool.get_balances(); - expect(curveBalancesAfter[0]).to.equal(curveBalancesBefore[0]); - expect(curveBalancesAfter[1]).to.approxEqualTolerance( - curveBalancesBefore[1].sub(oethBurnAmount), - 0.01 // 0.01% - ); - - // Check the OETH total supply decrease - const oethSupplyAfter = await oeth.totalSupply(); - expect(oethSupplyAfter).to.approxEqualTolerance( - oethSupplyBefore.sub(oethBurnAmount), - 0.01 // 0.01% or 1 basis point - ); -} - -async function assertMintAndAddOTokens(oethMintAmount, fixture) { - const { convexEthMetaStrategy, oethMetaPool, oeth, strategist } = fixture; - - const curveBalancesBefore = await oethMetaPool.get_balances(); - const oethSupplyBefore = await oeth.totalSupply(); - - log( - `Before mint and add ${formatUnits(oethMintAmount)} OETH to the Metapool` - ); - await run("amoStrat", { - pool: "OETH", - output: false, - }); - - // Mint and add OETH to the Metapool - const tx = await convexEthMetaStrategy - .connect(strategist) - .mintAndAddOTokens(oethMintAmount); - - const receipt = await tx.wait(); - - // Check emitted event - await expect(tx) - .emit(convexEthMetaStrategy, "Deposit") - .withArgs(oeth.address, oethMetaPool.address, oethMintAmount); - - log("After mint and add of OETH to the Metapool"); - await run("amoStrat", { - pool: "OETH", - output: false, - fromBlock: receipt.blockNumber - 1, - }); - - // Check the ETH and OETH balances in the Curve Metapool - const curveBalancesAfter = await oethMetaPool.get_balances(); - expect(curveBalancesAfter[0]).to.approxEqualTolerance( - curveBalancesBefore[0], - 0.01 // 0.01% or 1 basis point - ); - expect(curveBalancesAfter[1]).to.approxEqualTolerance( - curveBalancesBefore[1].add(oethMintAmount), - 0.01 // 0.01% - ); - - // Check the OETH total supply decrease - const oethSupplyAfter = await oeth.totalSupply(); - expect(oethSupplyAfter).to.approxEqualTolerance( - oethSupplyBefore.add(oethMintAmount), - 0.01 // 0.01% or 1 basis point - ); -} - -async function assertRemoveOnlyAssets(lpAmount, fixture) { - const { - convexEthMetaStrategy, - cvxRewardPool, - oethMetaPool, - oethVault, - oeth, - strategist, - weth, - } = fixture; - - log(`Removing ${formatUnits(lpAmount)} ETH from the Metapool`); - const ethRemoveAmount = await calcEthRemoveAmount(fixture, lpAmount); - log("After calc ETH remove amount"); - const curveBalancesBefore = await oethMetaPool.get_balances(); - const oethSupplyBefore = await oeth.totalSupply(); - const vaultWethBalanceBefore = await weth.balanceOf(oethVault.address); - const strategyLpBalanceBefore = await cvxRewardPool.balanceOf( - convexEthMetaStrategy.address - ); - const vaultValueBefore = await oethVault.totalValue(); - - log( - `Before remove and burn of ${formatUnits(lpAmount)} ETH from the Metapool` - ); - await run("amoStrat", { - pool: "OETH", - output: false, - }); - - // Remove ETH from the Metapool and transfer to the Vault as WETH - const tx = await convexEthMetaStrategy - .connect(strategist) - .removeOnlyAssets(lpAmount); - - const receipt = await tx.wait(); - - log("After remove and burn of ETH from Metapool"); - await run("amoStrat", { - pool: "OETH", - output: false, - fromBlock: receipt.blockNumber - 1, - }); - - // Check emitted event - await expect(tx) - .to.emit(convexEthMetaStrategy, "Withdrawal") - .withArgs(weth.address, oethMetaPool.address, ethRemoveAmount); - - // Check the ETH and OETH balances in the Curve Metapool - const curveBalancesAfter = await oethMetaPool.get_balances(); - expect(curveBalancesAfter[0]).to.approxEqualTolerance( - curveBalancesBefore[0].sub(ethRemoveAmount), - 0.01 // 0.01% or 1 basis point - ); - expect(curveBalancesAfter[1]).to.equal(curveBalancesBefore[1]); - - // Check the OETH total supply is the same - const oethSupplyAfter = await oeth.totalSupply(); - expect(oethSupplyAfter).to.approxEqualTolerance( - oethSupplyBefore, - 0.01 // 0.01% or 1 basis point - ); - - // Check the WETH balance in the Vault - expect(await weth.balanceOf(oethVault.address)).to.equal( - vaultWethBalanceBefore.add(ethRemoveAmount) - ); - - // Check the vault made money - const vaultValueAfter = await oethVault.totalValue(); - expect(vaultValueAfter.sub(vaultValueBefore)).to.gt(parseUnits("-1")); - - // Check the strategy LP balance decreased - const strategyLpBalanceAfter = await cvxRewardPool.balanceOf( - convexEthMetaStrategy.address - ); - expect(strategyLpBalanceBefore.sub(strategyLpBalanceAfter)).to.eq(lpAmount); -} - -// Calculate the minted OETH amount for a deposit -async function calcOethMintAmount(fixture, wethDepositAmount) { - const { oethMetaPool } = fixture; - - // Get the ETH and OETH balances in the Curve Metapool - const curveBalances = await oethMetaPool.get_balances(); - // ETH balance - OETH balance - const balanceDiff = curveBalances[0].sub(curveBalances[1]); - - let oethMintAmount = balanceDiff.lte(0) - ? // If more OETH than ETH then mint same amount of OETH as ETH - wethDepositAmount - : // If less OETH than ETH then mint the difference - balanceDiff.add(wethDepositAmount); - // Cap the minting to twice the ETH deposit amount - const doubleWethDepositAmount = wethDepositAmount.mul(2); - oethMintAmount = oethMintAmount.lte(doubleWethDepositAmount) - ? oethMintAmount - : doubleWethDepositAmount; - log(`OETH mint amount : ${formatUnits(oethMintAmount)}`); - - return { oethMintAmount, curveBalances }; -} - -// Calculate the amount of OETH burnt from a withdraw -async function calcOethWithdrawAmount(fixture, wethWithdrawAmount) { - const { oethMetaPool } = fixture; - - // Get the ETH and OETH balances in the Curve Metapool - const curveBalances = await oethMetaPool.get_balances(); - - // OETH to burn = WETH withdrawn * OETH pool balance / ETH pool balance - const oethBurnAmount = wethWithdrawAmount - .mul(curveBalances[1]) - .div(curveBalances[0]); - - log(`OETH burn amount : ${formatUnits(oethBurnAmount)}`); - - return { oethBurnAmount, curveBalances }; -} - -// Calculate the OETH and ETH amounts from a withdrawAll -async function calcWithdrawAllAmounts(fixture) { - const { convexEthMetaStrategy, cvxRewardPool, oethMetaPool } = fixture; - - // Get the ETH and OETH balances in the Curve Metapool - const curveBalances = await oethMetaPool.get_balances(); - const strategyLpAmount = await cvxRewardPool.balanceOf( - convexEthMetaStrategy.address - ); - const totalLpSupply = await oethMetaPool.totalSupply(); - - // OETH to burn = OETH pool balance * strategy LP amount / total pool LP amount - const oethBurnAmount = curveBalances[1] - .mul(strategyLpAmount) - .div(totalLpSupply); - // ETH to withdraw = ETH pool balance * strategy LP amount / total pool LP amount - const ethWithdrawAmount = curveBalances[0] - .mul(strategyLpAmount) - .div(totalLpSupply); - - log(`OETH burn amount : ${formatUnits(oethBurnAmount)}`); - log(`ETH withdraw amount : ${formatUnits(ethWithdrawAmount)}`); - - return { oethBurnAmount, ethWithdrawAmount, curveBalances }; -} - -// Calculate the amount of OETH burned from a removeAndBurnOTokens -async function calcOethRemoveAmount(fixture, lpAmount) { - const { oethGaugeSigner, oethMetaPool } = fixture; - - // Static call to get the OETH removed from the Metapool for a given amount of LP tokens - const oethBurnAmount = await oethMetaPool - .connect(oethGaugeSigner) - .callStatic["remove_liquidity_one_coin(uint256,int128,uint256)"]( - lpAmount, - 1, - 0 - ); - - log(`OETH burn amount : ${formatUnits(oethBurnAmount)}`); - - return oethBurnAmount; -} - -// Calculate the amount of ETH burned from a removeOnlyAssets -async function calcEthRemoveAmount(fixture, lpAmount) { - const { oethMetaPool } = fixture; - - // Get the ETH removed from the Metapool for a given amount of LP tokens - const ethRemoveAmount = await oethMetaPool.calc_withdraw_one_coin( - lpAmount, - 0 - ); - - log(`ETH burn amount : ${formatUnits(ethRemoveAmount)}`); - - return ethRemoveAmount; -} diff --git a/contracts/test/strategies/oeth-morpho-aave.mainnet.fork-test.js b/contracts/test/strategies/oeth-morpho-aave.mainnet.fork-test.js deleted file mode 100644 index 3ad0e5660f..0000000000 --- a/contracts/test/strategies/oeth-morpho-aave.mainnet.fork-test.js +++ /dev/null @@ -1,186 +0,0 @@ -const { expect } = require("chai"); - -const { - units, - oethUnits, - advanceBlocks, - advanceTime, - isCI, -} = require("../helpers"); -const { createFixtureLoader, oethMorphoAaveFixture } = require("../_fixture"); -const { impersonateAndFund } = require("../../utils/signers"); - -describe.skip("ForkTest: Morpho Aave OETH Strategy", function () { - this.timeout(0); - - // Retry up to 3 times on CI - this.retries(isCI ? 3 : 0); - - let fixture; - const loadFixture = createFixtureLoader(oethMorphoAaveFixture); - beforeEach(async () => { - fixture = await loadFixture(); - }); - - describe("Mint", function () { - it("Should deploy WETH in Morpho Aave", async function () { - const { domen, weth } = fixture; - await mintTest(fixture, domen, weth, "3.56"); - }); - }); - - describe("Redeem", function () { - it("Should redeem from Morpho", async () => { - const { oethVault, oeth, weth, daniel } = fixture; - - // Mint some ETH - const amount = "1.23"; - const amountUnits = oethUnits(amount); - await oethVault.connect(daniel).mint(weth.address, amountUnits, 0); - await oethVault.connect(daniel).allocate(); - await oethVault.connect(daniel).rebase(); - - const currentSupply = await oeth.totalSupply(); - const currentBalance = await oeth - .connect(daniel) - .balanceOf(daniel.address); - - // Now try to redeem 1.23 OETH - await oethVault.connect(daniel).redeem(amountUnits, 0); - - await oethVault.connect(daniel).allocate(); - await oethVault.connect(daniel).rebase(); - - // User balance should be down by 1.23 OETH - const newBalance = await oeth.connect(daniel).balanceOf(daniel.address); - expect(newBalance).to.approxEqualTolerance( - currentBalance.sub(amountUnits), - 1 - ); - - const newSupply = await oeth.totalSupply(); - const supplyDiff = currentSupply.sub(newSupply); - - expect(supplyDiff).to.approxEqualTolerance(amountUnits, 1); - }); - }); - - describe("Supply Revenue", function () { - it("Should get supply interest", async function () { - const { josh, weth, oethMorphoAaveStrategy } = fixture; - await mintTest(fixture, josh, weth, "2.3333444"); - - const currentBalance = await oethMorphoAaveStrategy.checkBalance( - weth.address - ); - - await advanceTime(60 * 60 * 24 * 365); - await advanceBlocks(10000); - - const balanceAfter1Y = await oethMorphoAaveStrategy.checkBalance( - weth.address - ); - - const diff = balanceAfter1Y.sub(currentBalance); - expect(diff).to.be.gt(0); - }); - }); - - describe("Withdraw", function () { - it("Should be able to withdraw WETH from strategy", async function () { - const { matt, weth } = fixture; - await withdrawTest(fixture, matt, weth, "2.7655"); - }); - - it("Should be able to withdrawAll from strategy", async function () { - const { weth, oethVault, oethMorphoAaveStrategy } = fixture; - const oethVaultSigner = await impersonateAndFund(oethVault.address); - - // The strategy already has some funds allocated on Mainnet, - // so the following lines are unnecessary - - // // Remove funds so no residual funds get allocated - // await weth - // .connect(oethVaultSigner) - // .transfer(franck.address, await weth.balanceOf(oethVault.address)); - - // // Mint some OETH - // await mintTest(fixture, franck, weth, amount); - - const existingBalance = await oethMorphoAaveStrategy.checkBalance( - weth.address - ); - const oethVaultWETHBefore = (await weth.balanceOf(oethVault.address)).add( - existingBalance - ); - - await oethMorphoAaveStrategy.connect(oethVaultSigner).withdrawAll(); - - const oethVaultWETHAfter = await weth.balanceOf(oethVault.address); - expect(oethVaultWETHAfter).to.approxEqualTolerance( - oethVaultWETHBefore, - 1 - ); - }); - }); -}); - -async function mintTest(fixture, user, asset, amount = "0.34") { - const { oethVault, oeth, oethMorphoAaveStrategy } = fixture; - - await oethVault.connect(user).allocate(); - await oethVault.connect(user).rebase(); - - const unitAmount = await units(amount, asset); - - const currentSupply = await oeth.totalSupply(); - const currentBalance = await oeth.connect(user).balanceOf(user.address); - const currentMorphoBalance = await oethMorphoAaveStrategy.checkBalance( - asset.address - ); - - // Mint OETH w/ asset - await oethVault.connect(user).mint(asset.address, unitAmount, 0); - await oethVault.connect(user).allocate(); - - const newBalance = await oeth.connect(user).balanceOf(user.address); - const newSupply = await oeth.totalSupply(); - const newMorphoBalance = await oethMorphoAaveStrategy.checkBalance( - asset.address - ); - - const balanceDiff = newBalance.sub(currentBalance); - // Ensure user has correct balance (w/ 1% slippage tolerance) - expect(balanceDiff).to.approxEqualTolerance(oethUnits(amount), 2); - - // Supply checks - const supplyDiff = newSupply.sub(currentSupply); - const oethUnitAmount = oethUnits(amount); - expect(supplyDiff).to.approxEqualTolerance(oethUnitAmount, 1); - - const morphoLiquidityDiff = newMorphoBalance.sub(currentMorphoBalance); - - // Should have liquidity in Morpho - expect(morphoLiquidityDiff).to.approxEqualTolerance( - await units(amount, asset), - 1 - ); -} - -async function withdrawTest(fixture, user, asset, amount = "3.876") { - const { oethVault, oethMorphoAaveStrategy } = fixture; - await mintTest(fixture, user, asset, amount); - - const assetUnits = await units(amount, asset); - const oethVaultAssetBalBefore = await asset.balanceOf(oethVault.address); - const oethVaultSigner = await impersonateAndFund(oethVault.address); - - await oethMorphoAaveStrategy - .connect(oethVaultSigner) - .withdraw(oethVault.address, asset.address, assetUnits); - const oethVaultAssetBalDiff = (await asset.balanceOf(oethVault.address)).sub( - oethVaultAssetBalBefore - ); - - expect(oethVaultAssetBalDiff).to.approxEqualTolerance(assetUnits, 1); -} diff --git a/contracts/test/strategies/ousd-metapool-3crv-tilted-pool.mainnet.fork-test.js b/contracts/test/strategies/ousd-metapool-3crv-tilted-pool.mainnet.fork-test.js deleted file mode 100644 index c343536c15..0000000000 --- a/contracts/test/strategies/ousd-metapool-3crv-tilted-pool.mainnet.fork-test.js +++ /dev/null @@ -1,130 +0,0 @@ -const { expect } = require("chai"); - -const { units, ousdUnits, isCI } = require("../helpers"); -const { createFixtureLoader } = require("../_fixture"); -const { withCRV3TitledOUSDMetapool } = require("../_metastrategies-fixtures"); - -// The OUSD AMO has been removed -describe.skip("ForkTest: Convex 3pool/OUSD Meta Strategy - Titled to 3CRV", function () { - this.timeout(0); - - // Retry up to 3 times on CI - this.retries(isCI ? 3 : 0); - - let fixture; - const loadFixture = createFixtureLoader(withCRV3TitledOUSDMetapool); - beforeEach(async () => { - fixture = await loadFixture(); - }); - - describe("Mint", function () { - it("Should stake USDT in Curve gauge via metapool", async function () { - const { josh, usdt } = fixture; - await mintTest(fixture, josh, usdt, "200000"); - }); - - it("Should stake USDC in Curve gauge via metapool", async function () { - const { matt, usdc } = fixture; - await mintTest(fixture, matt, usdc, "110000"); - }); - - it("Should stake DAI in Curve gauge via metapool", async function () { - const { anna, dai } = fixture; - await mintTest(fixture, anna, dai, "110000"); - }); - }); - - describe("Redeem", function () { - it("Should redeem", async () => { - const { vault, ousd, usdt, usdc, dai, anna, OUSDmetaStrategy } = fixture; - - await vault.connect(anna).allocate(); - - const supplyBeforeMint = await ousd.totalSupply(); - - const amount = "10000"; - - // Mint with all three assets - for (const asset of [usdt, usdc, dai]) { - await vault - .connect(anna) - .mint(asset.address, await units(amount, asset), 0); - } - - await vault.connect(anna).allocate(); - - // we multiply it by 3 because 1/3 of balance is represented by each of the assets - const strategyBalance = ( - await OUSDmetaStrategy.checkBalance(dai.address) - ).mul(3); - - // min 1x 3crv + 1x printed OUSD: (10k + 10k) * (usdt + usdc) = 40k - expect(strategyBalance).to.be.gte(ousdUnits("40000")); - - // Total supply should be up by at least (10k x 2) + (10k x 2) + 10k = 50k - const currentSupply = await ousd.totalSupply(); - const supplyAdded = currentSupply.sub(supplyBeforeMint); - expect(supplyAdded).to.be.gte(ousdUnits("49999")); - - const currentBalance = await ousd.connect(anna).balanceOf(anna.address); - - // Now try to redeem the amount - await vault.connect(anna).redeem(ousdUnits("29900"), 0); - - // User balance should be down by 30k - const newBalance = await ousd.connect(anna).balanceOf(anna.address); - expect(newBalance).to.approxEqualTolerance( - currentBalance.sub(ousdUnits("29900")), - 1 - ); - - const newSupply = await ousd.totalSupply(); - const supplyDiff = currentSupply.sub(newSupply); - - expect(supplyDiff).to.be.gte(ousdUnits("29900")); - }); - }); -}); - -async function mintTest(fixture, user, asset, amount = "30000") { - const { vault, ousd, OUSDmetaStrategy, cvxRewardPool } = fixture; - - await vault.connect(user).allocate(); - await vault.connect(user).rebase(); - - const unitAmount = await units(amount, asset); - - const currentSupply = await ousd.totalSupply(); - const currentBalance = await ousd.connect(user).balanceOf(user.address); - const currentRewardPoolBalance = await cvxRewardPool - .connect(user) - .balanceOf(OUSDmetaStrategy.address); - - // Mint OUSD w/ asset - await vault.connect(user).mint(asset.address, unitAmount, 0); - await vault.connect(user).allocate(); - - // Ensure user has correct balance (w/ 1% slippage tolerance) - const newBalance = await ousd.connect(user).balanceOf(user.address); - const balanceDiff = newBalance.sub(currentBalance); - expect(balanceDiff).to.approxEqualTolerance(ousdUnits(amount), 2); - - // Supply checks - const newSupply = await ousd.totalSupply(); - const supplyDiff = newSupply.sub(currentSupply); - const ousdUnitAmount = ousdUnits(amount); - - // The pool is titled to 3CRV by a million - // It should have added amount*3 supply - expect(supplyDiff).to.approxEqualTolerance(ousdUnitAmount.mul(3), 5); - - // Ensure some LP tokens got staked under OUSDMetaStrategy address - const newRewardPoolBalance = await cvxRewardPool - .connect(user) - .balanceOf(OUSDmetaStrategy.address); - const rewardPoolBalanceDiff = newRewardPoolBalance.sub( - currentRewardPoolBalance - ); - // Should have staked the LP tokens for USDT and USDC - expect(rewardPoolBalanceDiff).to.be.gte(ousdUnits(amount).mul(3).div(2)); -} diff --git a/contracts/test/strategies/ousd-metapool-balanced-pool.mainnet.fork-test.js b/contracts/test/strategies/ousd-metapool-balanced-pool.mainnet.fork-test.js deleted file mode 100644 index 3f4e921356..0000000000 --- a/contracts/test/strategies/ousd-metapool-balanced-pool.mainnet.fork-test.js +++ /dev/null @@ -1,143 +0,0 @@ -const { expect } = require("chai"); -const { run } = require("hardhat"); - -const { units, ousdUnits, isCI } = require("../helpers"); -const { createFixtureLoader } = require("../_fixture"); -const { withBalancedOUSDMetaPool } = require("../_metastrategies-fixtures"); - -const log = require("../../utils/logger")("test:fork:ousd:metapool"); - -// The OUSD AMO has been removed -describe.skip("ForkTest: Convex 3pool/OUSD Meta Strategy - Balanced Metapool", function () { - this.timeout(0); - - // Retry up to 3 times on CI - this.retries(isCI ? 3 : 0); - - let fixture; - const loadFixture = createFixtureLoader(withBalancedOUSDMetaPool); - beforeEach(async () => { - fixture = await loadFixture(); - }); - - describe("Mint", function () { - it("Should stake USDT in Curve gauge via metapool", async function () { - const { josh, usdt } = fixture; - await mintTest(fixture, josh, usdt, "100000"); - }); - - it("Should stake USDC in Curve gauge via metapool", async function () { - const { matt, usdc } = fixture; - await mintTest(fixture, matt, usdc, "120000"); - }); - - it("Should stake USDS in Curve gauge via metapool", async function () { - const { anna, usds } = fixture; - await mintTest(fixture, anna, usds, "110000"); - }); - }); - - describe("Redeem", function () { - it("Should redeem", async () => { - const { vault, ousd, usdt, usdc, usds, anna, OUSDmetaStrategy } = fixture; - - await vault.connect(anna).allocate(); - - const supplyBeforeMint = await ousd.totalSupply(); - - const amount = "10000"; - - const beforeMintBlock = await ethers.provider.getBlockNumber(); - - // Mint with all three assets - for (const asset of [usdt, usdc, usds]) { - await vault - .connect(anna) - .mint(asset.address, await units(amount, asset), 0); - } - - await vault.connect(anna).allocate(); - - log("After mints and allocate to strategy"); - await run("amoStrat", { - pool: "OUSD", - output: false, - fromBlock: beforeMintBlock, - }); - - // we multiply it by 3 because 1/3 of balance is represented by each of the assets - const strategyBalance = ( - await OUSDmetaStrategy.checkBalance(usds.address) - ).mul(3); - - // 3x 10k assets + 3x 10k OUSD = 60k - await expect(strategyBalance).to.be.gte(ousdUnits("59990")); - - // Total supply should be up by at least (10k x 2) + (10k x 2) + 10k = 50k - const currentSupply = await ousd.totalSupply(); - const supplyAdded = currentSupply.sub(supplyBeforeMint); - expect(supplyAdded).to.be.gte(ousdUnits("49995")); - - const currentBalance = await ousd.connect(anna).balanceOf(anna.address); - - // Now try to redeem the amount - const redeemAmount = ousdUnits("29980"); - await vault.connect(anna).redeem(redeemAmount, 0); - - // User balance should be down by 30k - const newBalance = await ousd.connect(anna).balanceOf(anna.address); - expect(newBalance).to.approxEqualTolerance( - currentBalance.sub(redeemAmount), - 1 - ); - - const newSupply = await ousd.totalSupply(); - const supplyDiff = currentSupply.sub(newSupply); - - expect(supplyDiff).to.be.gte(redeemAmount); - }); - }); -}); - -async function mintTest(fixture, user, asset, amount = "30000") { - const { vault, ousd, OUSDmetaStrategy, cvxRewardPool } = fixture; - - await vault.connect(user).allocate(); - await vault.connect(user).rebase(); - - const unitAmount = await units(amount, asset); - - const currentSupply = await ousd.totalSupply(); - const currentBalance = await ousd.connect(user).balanceOf(user.address); - const currentRewardPoolBalance = await cvxRewardPool - .connect(user) - .balanceOf(OUSDmetaStrategy.address); - - // Mint OUSD w/ asset - await vault.connect(user).mint(asset.address, unitAmount, 0); - await vault.connect(user).allocate(); - - // Ensure user has correct balance (w/ 1% slippage tolerance) - const newBalance = await ousd.connect(user).balanceOf(user.address); - const balanceDiff = newBalance.sub(currentBalance); - expect(balanceDiff).to.approxEqualTolerance(ousdUnits(amount), 2); - - // Supply checks - const newSupply = await ousd.totalSupply(); - const supplyDiff = newSupply.sub(currentSupply); - // Ensure 2x OUSD has been added to supply - expect(supplyDiff).to.approxEqualTolerance(ousdUnits(amount).mul(2), 1); - - // Ensure some LP tokens got staked under OUSDMetaStrategy address - const newRewardPoolBalance = await cvxRewardPool - .connect(user) - .balanceOf(OUSDmetaStrategy.address); - const rewardPoolBalanceDiff = newRewardPoolBalance.sub( - currentRewardPoolBalance - ); - // Should have staked the LP tokens - expect(rewardPoolBalanceDiff).to.approxEqualTolerance( - ousdUnits(amount).mul(2), - 5 - ); -} diff --git a/contracts/test/strategies/ousd-metapool-ousd-tilted-pool.mainnet.fork-test.js b/contracts/test/strategies/ousd-metapool-ousd-tilted-pool.mainnet.fork-test.js deleted file mode 100644 index 5adcfbc264..0000000000 --- a/contracts/test/strategies/ousd-metapool-ousd-tilted-pool.mainnet.fork-test.js +++ /dev/null @@ -1,139 +0,0 @@ -const { expect } = require("chai"); - -const { units, ousdUnits, isCI } = require("../helpers"); -const { createFixtureLoader } = require("../_fixture"); -const { withOUSDTitledMetapool } = require("../_metastrategies-fixtures"); - -// The OUSD AMO has been removed -describe.skip("ForkTest: Convex 3pool/OUSD Meta Strategy - Titled to OUSD", function () { - this.timeout(0); - - // Retry up to 3 times on CI - this.retries(isCI ? 3 : 0); - - let fixture; - const loadFixture = createFixtureLoader(withOUSDTitledMetapool); - beforeEach(async () => { - fixture = await loadFixture(); - }); - - describe("Mint", function () { - it("Should stake USDT in Curve gauge via metapool", async function () { - const { josh, usdt } = fixture; - await mintTest(fixture, josh, usdt, "100000"); - }); - - it("Should stake USDC in Curve gauge via metapool", async function () { - const { matt, usdc } = fixture; - await mintTest(fixture, matt, usdc, "120000"); - }); - - it("Should stake USDS in Curve gauge via metapool", async function () { - const { anna, usds } = fixture; - await mintTest(fixture, anna, usds, "110000"); - }); - }); - - describe("Redeem", function () { - it("Should redeem", async () => { - const { vault, ousd, usdt, usdc, usds, anna, OUSDmetaStrategy } = fixture; - - await vault.connect(anna).allocate(); - - const supplyBeforeMint = await ousd.totalSupply(); - const strategyBalanceBeforeMint = ( - await OUSDmetaStrategy.checkBalance(usds.address) - ).mul(3); - - const amount = "10000"; - - // Mint with all three assets - for (const asset of [usdt, usdc, usds]) { - await vault - .connect(anna) - .mint(asset.address, await units(amount, asset), 0); - } - - await vault.connect(anna).allocate(); - - // we multiply it by 3 because 1/3 of balance is represented by each of the assets - const strategyBalance = ( - await OUSDmetaStrategy.checkBalance(usds.address) - ).mul(3); - const strategyBalanceChange = strategyBalance.sub( - strategyBalanceBeforeMint - ); - - // min 1x 3crv + 1x printed OUSD: (10k + 10k + 10k) * (usdt + usdc + usds) = 60k - expect(strategyBalanceChange).to.be.gte(ousdUnits("59500")); - - // Total supply should be up by at least (10k x 2) + (10k x 2) + (10k x 2) = 60k - const currentSupply = await ousd.totalSupply(); - const supplyAdded = currentSupply.sub(supplyBeforeMint); - expect(supplyAdded).to.be.gte(ousdUnits("59500")); - - const currentBalance = await ousd.connect(anna).balanceOf(anna.address); - - // Now try to redeem the amount - const redeemAmount = ousdUnits("10000"); - await vault.connect(anna).redeem(redeemAmount, 0); - - // User balance should be down by 10k - const newBalance = await ousd.connect(anna).balanceOf(anna.address); - expect(newBalance).to.approxEqualTolerance( - currentBalance.sub(redeemAmount), - 1 - ); - - const newSupply = await ousd.totalSupply(); - const supplyDiff = currentSupply.sub(newSupply); - - expect(supplyDiff).to.be.gte(redeemAmount); - }); - }); -}); - -async function mintTest(fixture, user, asset, amount = "30000") { - const { vault, ousd, OUSDmetaStrategy, cvxRewardPool } = fixture; - await vault.connect(user).allocate(); - await vault.connect(user).rebase(); - - const unitAmount = await units(amount, asset); - - const currentSupply = await ousd.totalSupply(); - const currentBalance = await ousd.connect(user).balanceOf(user.address); - const currentRewardPoolBalance = await cvxRewardPool - .connect(user) - .balanceOf(OUSDmetaStrategy.address); - - // Mint OUSD w/ asset - await vault.connect(user).mint(asset.address, unitAmount, 0); - await vault.connect(user).allocate(); - - // Ensure user has correct balance (w/ 1% slippage tolerance) - const newBalance = await ousd.connect(user).balanceOf(user.address); - const balanceDiff = newBalance.sub(currentBalance); - expect(balanceDiff).to.approxEqualTolerance(ousdUnits(amount), 2); - - // Supply checks - const newSupply = await ousd.totalSupply(); - const supplyDiff = newSupply.sub(currentSupply); - - // The pool is titled to 3CRV by a millions - // It should have added 2 times the OUSD amount. - // 1x for 3poolLp tokens and 1x for minimum amount of OUSD printed - expect(supplyDiff).to.approxEqualTolerance(ousdUnits(amount).mul(2), 5); - - // Ensure some LP tokens got staked under OUSDMetaStrategy address - const newRewardPoolBalance = await cvxRewardPool - .connect(user) - .balanceOf(OUSDmetaStrategy.address); - const rewardPoolBalanceDiff = newRewardPoolBalance.sub( - currentRewardPoolBalance - ); - // Should have staked the LP tokens for USDT and USDC - expect(rewardPoolBalanceDiff).to.approxEqualTolerance( - ousdUnits(amount).mul(2), - 5 - ); -} diff --git a/contracts/test/strategies/ousd-morpho-guantlet-prime-usdc.mainnet.fork-test.js b/contracts/test/strategies/ousd-morpho-guantlet-prime-usdc.mainnet.fork-test.js deleted file mode 100644 index d384715dd3..0000000000 --- a/contracts/test/strategies/ousd-morpho-guantlet-prime-usdc.mainnet.fork-test.js +++ /dev/null @@ -1,443 +0,0 @@ -const { expect } = require("chai"); -const { formatUnits, parseUnits } = require("ethers/lib/utils"); - -const { getMerklRewards } = require("../../tasks/merkl"); -const addresses = require("../../utils/addresses"); -const { units, isCI } = require("../helpers"); - -const { - createFixtureLoader, - morphoGauntletPrimeUSDCFixture, -} = require("../_fixture"); - -const log = require("../../utils/logger")( - "test:fork:ousd-morpho-gauntlet-usdc" -); - -describe.skip("ForkTest: Morpho Gauntlet Prime USDC Strategy", function () { - this.timeout(0); - - // Retry up to 3 times on CI - this.retries(isCI ? 3 : 0); - - let fixture; - - describe("post deployment", () => { - const loadFixture = createFixtureLoader(morphoGauntletPrimeUSDCFixture); - beforeEach(async () => { - fixture = await loadFixture(); - }); - it("Should have constants and immutables set", async () => { - const { vault, morphoGauntletPrimeUSDCStrategy } = fixture; - - expect(await morphoGauntletPrimeUSDCStrategy.platformAddress()).to.equal( - addresses.mainnet.MorphoGauntletPrimeUSDCVault - ); - expect(await morphoGauntletPrimeUSDCStrategy.vaultAddress()).to.equal( - vault.address - ); - expect(await morphoGauntletPrimeUSDCStrategy.shareToken()).to.equal( - addresses.mainnet.MorphoGauntletPrimeUSDCVault - ); - expect(await morphoGauntletPrimeUSDCStrategy.assetToken()).to.equal( - addresses.mainnet.USDC - ); - expect( - await morphoGauntletPrimeUSDCStrategy.supportsAsset( - addresses.mainnet.USDC - ) - ).to.equal(true); - expect( - await morphoGauntletPrimeUSDCStrategy.assetToPToken( - addresses.mainnet.USDC - ) - ).to.equal(addresses.mainnet.MorphoGauntletPrimeUSDCVault); - expect(await morphoGauntletPrimeUSDCStrategy.governor()).to.equal( - addresses.mainnet.Timelock - ); - }); - it("Should be able to check balance", async () => { - const { usdc, josh, morphoGauntletPrimeUSDCStrategy } = fixture; - - // This uses a transaction to call a view function so the gas usage can be reported. - const tx = await morphoGauntletPrimeUSDCStrategy - .connect(josh) - .populateTransaction.checkBalance(usdc.address); - await josh.sendTransaction(tx); - }); - it("Only Governor can approve all tokens", async () => { - const { - timelock, - oldTimelock, - strategist, - josh, - daniel, - domen, - morphoGauntletPrimeUSDCStrategy, - usdc, - vaultSigner, - } = fixture; - - // Governor can approve all tokens - const tx = await morphoGauntletPrimeUSDCStrategy - .connect(timelock) - .safeApproveAllTokens(); - await expect(tx).to.emit(usdc, "Approval"); - - for (const signer of [ - daniel, - domen, - josh, - strategist, - oldTimelock, - vaultSigner, - ]) { - const tx = morphoGauntletPrimeUSDCStrategy - .connect(signer) - .safeApproveAllTokens(); - await expect(tx).to.be.revertedWith("Caller is not the Governor"); - } - }); - }); - - describe("with some USDC in the vault", () => { - const loadFixture = createFixtureLoader(morphoGauntletPrimeUSDCFixture, { - usdcMintAmount: 12000, - depositToStrategy: false, - }); - beforeEach(async () => { - fixture = await loadFixture(); - }); - - it("Vault should deposit some USDC to strategy", async function () { - const { - usdc, - ousd, - morphoGauntletPrimeUSDCStrategy, - vault, - strategist, - vaultSigner, - } = fixture; - - const checkBalanceBefore = - await morphoGauntletPrimeUSDCStrategy.checkBalance(usdc.address); - - const usdcDepositAmount = await units("1000", usdc); - - // Vault transfers USDC to strategy - await usdc - .connect(vaultSigner) - .transfer(morphoGauntletPrimeUSDCStrategy.address, usdcDepositAmount); - - await vault.connect(strategist).rebase(); - - const ousdSupplyBefore = await ousd.totalSupply(); - - const tx = await morphoGauntletPrimeUSDCStrategy - .connect(vaultSigner) - .deposit(usdc.address, usdcDepositAmount); - - // Check emitted event - await expect(tx) - .to.emit(morphoGauntletPrimeUSDCStrategy, "Deposit") - .withArgs( - usdc.address, - addresses.mainnet.MorphoGauntletPrimeUSDCVault, - usdcDepositAmount - ); - - // Check the OUSD total supply increase - const ousdSupplyAfter = await ousd.totalSupply(); - expect(ousdSupplyAfter).to.approxEqualTolerance( - ousdSupplyBefore.add(usdcDepositAmount), - 0.1 // 0.1% or 10 basis point - ); - expect( - await morphoGauntletPrimeUSDCStrategy.checkBalance(usdc.address) - ).to.approxEqualTolerance( - checkBalanceBefore.add(usdcDepositAmount), - 0.01 - ); // 0.01% or 1 basis point - }); - it("Only vault can deposit some USDC to the strategy", async function () { - const { - usdc, - morphoGauntletPrimeUSDCStrategy, - vaultSigner, - strategist, - timelock, - oldTimelock, - josh, - } = fixture; - - const depositAmount = await units("50", usdc); - await usdc - .connect(vaultSigner) - .transfer(morphoGauntletPrimeUSDCStrategy.address, depositAmount); - - for (const signer of [strategist, oldTimelock, timelock, josh]) { - const tx = morphoGauntletPrimeUSDCStrategy - .connect(signer) - .deposit(usdc.address, depositAmount); - - await expect(tx).to.revertedWith("Caller is not the Vault"); - } - }); - it("Only vault can deposit all USDC to strategy", async function () { - const { - usdc, - morphoGauntletPrimeUSDCStrategy, - vaultSigner, - strategist, - timelock, - oldTimelock, - josh, - } = fixture; - - const depositAmount = await units("50", usdc); - await usdc - .connect(vaultSigner) - .transfer(morphoGauntletPrimeUSDCStrategy.address, depositAmount); - - for (const signer of [strategist, oldTimelock, timelock, josh]) { - const tx = morphoGauntletPrimeUSDCStrategy.connect(signer).depositAll(); - - await expect(tx).to.revertedWith("Caller is not the Vault"); - } - - const tx = await morphoGauntletPrimeUSDCStrategy - .connect(vaultSigner) - .depositAll(); - await expect(tx).to.emit(morphoGauntletPrimeUSDCStrategy, "Deposit"); - }); - }); - - describe("with the strategy having some USDC in MetaMorpho Strategy", () => { - const loadFixture = createFixtureLoader(morphoGauntletPrimeUSDCFixture, { - usdcMintAmount: 12000, - depositToStrategy: true, - }); - beforeEach(async () => { - fixture = await loadFixture(); - }); - - it("Vault should be able to withdraw all", async () => { - const { - usdc, - morphoGauntletPrimeUSDCVault, - morphoGauntletPrimeUSDCStrategy, - ousd, - vault, - vaultSigner, - } = fixture; - - const usdcWithdrawAmountExpected = - await morphoGauntletPrimeUSDCVault.maxWithdraw( - morphoGauntletPrimeUSDCStrategy.address - ); - - log( - `Expected to withdraw ${formatUnits(usdcWithdrawAmountExpected)} USDC` - ); - - const ousdSupplyBefore = await ousd.totalSupply(); - const vaultUSDCBalanceBefore = await usdc.balanceOf(vault.address); - - log("Before withdraw all from strategy"); - - // Now try to withdraw all the WETH from the strategy - const tx = await morphoGauntletPrimeUSDCStrategy - .connect(vaultSigner) - .withdrawAll(); - - log("After withdraw all from strategy"); - - // Check emitted event - await expect(tx).to.emittedEvent("Withdrawal", [ - usdc.address, - morphoGauntletPrimeUSDCVault.address, - (amount) => - expect(amount).approxEqualTolerance( - usdcWithdrawAmountExpected, - 0.01, - "Withdrawal amount" - ), - ]); - - // Check the OUSD total supply stays the same - expect(await ousd.totalSupply()).to.approxEqualTolerance( - ousdSupplyBefore, - 0.01 // 0.01% or 1 basis point - ); - - // Check the USDC amount in the vault increases - expect(await usdc.balanceOf(vault.address)).to.approxEqualTolerance( - vaultUSDCBalanceBefore.add(usdcWithdrawAmountExpected), - 0.01 - ); - }); - it("Vault should be able to withdraw some USDC", async () => { - const { - usdc, - morphoGauntletPrimeUSDCVault, - morphoGauntletPrimeUSDCStrategy, - ousd, - vault, - vaultSigner, - } = fixture; - - const withdrawAmount = await units("1000", usdc); - - const ousdSupplyBefore = await ousd.totalSupply(); - const vaultUSDCBalanceBefore = await usdc.balanceOf(vault.address); - - log(`Before withdraw of ${formatUnits(withdrawAmount)} from strategy`); - - // Now try to withdraw the USDC from the strategy - const tx = await morphoGauntletPrimeUSDCStrategy - .connect(vaultSigner) - .withdraw(vault.address, usdc.address, withdrawAmount); - - log("After withdraw from strategy"); - - // Check emitted event - await expect(tx) - .to.emit(morphoGauntletPrimeUSDCStrategy, "Withdrawal") - .withArgs( - usdc.address, - morphoGauntletPrimeUSDCVault.address, - withdrawAmount - ); - - // Check the OUSD total supply stays the same - const ousdSupplyAfter = await ousd.totalSupply(); - expect(ousdSupplyAfter).to.approxEqualTolerance( - ousdSupplyBefore, - 0.01 // 0.01% or 1 basis point - ); - - // Check the USDC balance in the Vault - expect(await usdc.balanceOf(vault.address)).to.equal( - vaultUSDCBalanceBefore.add(withdrawAmount) - ); - }); - it("Only vault can withdraw some USDC from strategy", async function () { - const { - morphoGauntletPrimeUSDCStrategy, - oethVault, - strategist, - timelock, - oldTimelock, - josh, - weth, - } = fixture; - - for (const signer of [strategist, timelock, oldTimelock, josh]) { - const tx = morphoGauntletPrimeUSDCStrategy - .connect(signer) - .withdraw(oethVault.address, weth.address, parseUnits("50")); - - await expect(tx).to.revertedWith("Caller is not the Vault"); - } - }); - it("Only vault and governor can withdraw all USDC from Maker DSR strategy", async function () { - const { morphoGauntletPrimeUSDCStrategy, strategist, timelock, josh } = - fixture; - - for (const signer of [strategist, josh]) { - const tx = morphoGauntletPrimeUSDCStrategy - .connect(signer) - .withdrawAll(); - - await expect(tx).to.revertedWith("Caller is not the Vault or Governor"); - } - - // Governor can withdraw all - const tx = morphoGauntletPrimeUSDCStrategy - .connect(timelock) - .withdrawAll(); - await expect(tx).to.emit(morphoGauntletPrimeUSDCStrategy, "Withdrawal"); - }); - }); - - describe("claim and collect MORPHO rewards", () => { - const loadFixture = createFixtureLoader(morphoGauntletPrimeUSDCFixture); - beforeEach(async () => { - fixture = await loadFixture(); - }); - it("Should claim MORPHO rewards", async () => { - const { josh, morphoGauntletPrimeUSDCStrategy, morphoToken } = fixture; - - const { amount, proofs } = await getMerklRewards({ - userAddress: morphoGauntletPrimeUSDCStrategy.address, - chainId: 1, - }); - log(`MORPHO rewards available to claim: ${formatUnits(amount, 18)}`); - - const tx = await morphoGauntletPrimeUSDCStrategy - .connect(josh) - .merkleClaim(morphoToken.address, amount, proofs); - await expect(tx) - .to.emit(morphoGauntletPrimeUSDCStrategy, "ClaimedRewards") - .withArgs(morphoToken.address, amount); - }); - - it("Should be able to collect MORPHO rewards", async () => { - const { strategist, josh, morphoGauntletPrimeUSDCStrategy, morphoToken } = - fixture; - - const { amount, proofs } = await getMerklRewards({ - userAddress: morphoGauntletPrimeUSDCStrategy.address, - chainId: 1, - }); - log(`MORPHO rewards available to claim: ${formatUnits(amount, 18)}`); - - await morphoGauntletPrimeUSDCStrategy - .connect(josh) - .merkleClaim(morphoToken.address, amount, proofs); - - const expectMorphoTransfer = await morphoToken.balanceOf( - morphoGauntletPrimeUSDCStrategy.address - ); - - const tx = await morphoGauntletPrimeUSDCStrategy - .connect(strategist) - .collectRewardTokens(); - - if (expectMorphoTransfer.gt(0)) { - // The amount is total claimed over time and not the amount of rewards claimed in this tx - await expect(tx) - .to.emit(morphoToken, "Transfer") - .withArgs( - morphoGauntletPrimeUSDCStrategy.address, - strategist.address, - expectMorphoTransfer - ); - } - }); - }); - - describe("administration", () => { - const loadFixture = createFixtureLoader(morphoGauntletPrimeUSDCFixture); - beforeEach(async () => { - fixture = await loadFixture(); - }); - it("Governor should not be able to set the platform token", () => { - const { frxETH, sfrxETH, morphoGauntletPrimeUSDCStrategy, timelock } = - fixture; - - const tx = morphoGauntletPrimeUSDCStrategy - .connect(timelock) - .setPTokenAddress(frxETH.address, sfrxETH.address); - expect(tx).to.be.revertedWith("unsupported function"); - }); - it("Governor should not be able to remove the platform token", () => { - const { morphoGauntletPrimeUSDCStrategy, timelock } = fixture; - - const tx = morphoGauntletPrimeUSDCStrategy - .connect(timelock) - .removePToken(0); - expect(tx).to.be.revertedWith("unsupported function"); - }); - }); -}); diff --git a/contracts/test/strategies/ousd-morpho-steakhouse-usdc.mainnet.fork-test.js b/contracts/test/strategies/ousd-morpho-steakhouse-usdc.mainnet.fork-test.js deleted file mode 100644 index 040af33616..0000000000 --- a/contracts/test/strategies/ousd-morpho-steakhouse-usdc.mainnet.fork-test.js +++ /dev/null @@ -1,373 +0,0 @@ -const { expect } = require("chai"); -const { formatUnits, parseUnits } = require("ethers/lib/utils"); - -const addresses = require("../../utils/addresses"); -const { units, isCI } = require("../helpers"); - -const { - createFixtureLoader, - morphoSteakhouseUSDCFixture, -} = require("../_fixture"); - -const log = require("../../utils/logger"); - -describe.skip("ForkTest: Morpho Steakhouse USDC Strategy", function () { - this.timeout(0); - - // Retry up to 3 times on CI - this.retries(isCI ? 3 : 0); - - let fixture; - - describe("post deployment", () => { - const loadFixture = createFixtureLoader(morphoSteakhouseUSDCFixture); - beforeEach(async () => { - fixture = await loadFixture(); - }); - it("Should have constants and immutables set", async () => { - const { vault, morphoSteakhouseUSDCStrategy } = fixture; - - expect(await morphoSteakhouseUSDCStrategy.platformAddress()).to.equal( - addresses.mainnet.MorphoSteakhouseUSDCVault - ); - expect(await morphoSteakhouseUSDCStrategy.vaultAddress()).to.equal( - vault.address - ); - expect(await morphoSteakhouseUSDCStrategy.shareToken()).to.equal( - addresses.mainnet.MorphoSteakhouseUSDCVault - ); - expect(await morphoSteakhouseUSDCStrategy.assetToken()).to.equal( - addresses.mainnet.USDC - ); - expect( - await morphoSteakhouseUSDCStrategy.supportsAsset(addresses.mainnet.USDC) - ).to.equal(true); - expect( - await morphoSteakhouseUSDCStrategy.assetToPToken(addresses.mainnet.USDC) - ).to.equal(addresses.mainnet.MorphoSteakhouseUSDCVault); - expect(await morphoSteakhouseUSDCStrategy.governor()).to.equal( - addresses.mainnet.Timelock - ); - }); - it("Should be able to check balance", async () => { - const { usdc, josh, morphoSteakhouseUSDCStrategy } = fixture; - - // This uses a transaction to call a view function so the gas usage can be reported. - const tx = await morphoSteakhouseUSDCStrategy - .connect(josh) - .populateTransaction.checkBalance(usdc.address); - await josh.sendTransaction(tx); - }); - it("Only Governor can approve all tokens", async () => { - const { - timelock, - oldTimelock, - strategist, - josh, - daniel, - domen, - morphoSteakhouseUSDCStrategy, - usdc, - vaultSigner, - } = fixture; - - // Governor can approve all tokens - const tx = await morphoSteakhouseUSDCStrategy - .connect(timelock) - .safeApproveAllTokens(); - await expect(tx).to.emit(usdc, "Approval"); - - for (const signer of [ - daniel, - domen, - josh, - strategist, - oldTimelock, - vaultSigner, - ]) { - const tx = morphoSteakhouseUSDCStrategy - .connect(signer) - .safeApproveAllTokens(); - await expect(tx).to.be.revertedWith("Caller is not the Governor"); - } - }); - }); - - describe("with some USDC in the vault", () => { - const loadFixture = createFixtureLoader(morphoSteakhouseUSDCFixture, { - usdcMintAmount: 12000, - depositToStrategy: false, - }); - beforeEach(async () => { - fixture = await loadFixture(); - }); - - it("Vault should deposit some USDC to strategy", async function () { - const { - usdc, - ousd, - morphoSteakhouseUSDCStrategy, - vault, - strategist, - vaultSigner, - } = fixture; - - const checkBalanceBefore = - await morphoSteakhouseUSDCStrategy.checkBalance(usdc.address); - - const usdcDepositAmount = await units("1000", usdc); - - // Vault transfers USDC to strategy - await usdc - .connect(vaultSigner) - .transfer(morphoSteakhouseUSDCStrategy.address, usdcDepositAmount); - - await vault.connect(strategist).rebase(); - - const ousdSupplyBefore = await ousd.totalSupply(); - - const tx = await morphoSteakhouseUSDCStrategy - .connect(vaultSigner) - .deposit(usdc.address, usdcDepositAmount); - - // Check emitted event - await expect(tx) - .to.emit(morphoSteakhouseUSDCStrategy, "Deposit") - .withArgs( - usdc.address, - addresses.mainnet.MorphoSteakhouseUSDCVault, - usdcDepositAmount - ); - - // Check the OUSD total supply increase - const ousdSupplyAfter = await ousd.totalSupply(); - expect(ousdSupplyAfter).to.approxEqualTolerance( - ousdSupplyBefore.add(usdcDepositAmount), - 0.1 // 0.1% or 10 basis point - ); - expect( - await morphoSteakhouseUSDCStrategy.checkBalance(usdc.address) - ).to.approxEqualTolerance( - checkBalanceBefore.add(usdcDepositAmount), - 0.01 - ); // 0.01% or 1 basis point - }); - it("Only vault can deposit some USDC to the strategy", async function () { - const { - usdc, - morphoSteakhouseUSDCStrategy, - vaultSigner, - strategist, - timelock, - oldTimelock, - josh, - } = fixture; - - const depositAmount = await units("50", usdc); - await usdc - .connect(vaultSigner) - .transfer(morphoSteakhouseUSDCStrategy.address, depositAmount); - - for (const signer of [strategist, oldTimelock, timelock, josh]) { - const tx = morphoSteakhouseUSDCStrategy - .connect(signer) - .deposit(usdc.address, depositAmount); - - await expect(tx).to.revertedWith("Caller is not the Vault"); - } - }); - it("Only vault can deposit all USDC to strategy", async function () { - const { - usdc, - morphoSteakhouseUSDCStrategy, - vaultSigner, - strategist, - timelock, - oldTimelock, - josh, - } = fixture; - - const depositAmount = await units("50", usdc); - await usdc - .connect(vaultSigner) - .transfer(morphoSteakhouseUSDCStrategy.address, depositAmount); - - for (const signer of [strategist, oldTimelock, timelock, josh]) { - const tx = morphoSteakhouseUSDCStrategy.connect(signer).depositAll(); - - await expect(tx).to.revertedWith("Caller is not the Vault"); - } - - const tx = await morphoSteakhouseUSDCStrategy - .connect(vaultSigner) - .depositAll(); - await expect(tx).to.emit(morphoSteakhouseUSDCStrategy, "Deposit"); - }); - }); - - describe("with the strategy having some USDC in MetaMorpho Strategy", () => { - const loadFixture = createFixtureLoader(morphoSteakhouseUSDCFixture, { - usdcMintAmount: 12000, - depositToStrategy: true, - }); - beforeEach(async () => { - fixture = await loadFixture(); - }); - - it("Vault should be able to withdraw all", async () => { - const { - usdc, - morphoSteakHouseUSDCVault, - morphoSteakhouseUSDCStrategy, - ousd, - vault, - vaultSigner, - } = fixture; - - const usdcWithdrawAmountExpected = - await morphoSteakHouseUSDCVault.maxWithdraw( - morphoSteakhouseUSDCStrategy.address - ); - - log( - `Expected to withdraw ${formatUnits(usdcWithdrawAmountExpected)} USDC` - ); - - const ousdSupplyBefore = await ousd.totalSupply(); - const vaultUSDCBalanceBefore = await usdc.balanceOf(vault.address); - - log("Before withdraw all from strategy"); - - // Now try to withdraw all the WETH from the strategy - const tx = await morphoSteakhouseUSDCStrategy - .connect(vaultSigner) - .withdrawAll(); - - log("After withdraw all from strategy"); - - // Check emitted event - await expect(tx).to.emittedEvent("Withdrawal", [ - usdc.address, - morphoSteakHouseUSDCVault.address, - (amount) => - expect(amount).approxEqualTolerance( - usdcWithdrawAmountExpected, - 0.01, - "Withdrawal amount" - ), - ]); - - // Check the OUSD total supply stays the same - expect(await ousd.totalSupply()).to.approxEqualTolerance( - ousdSupplyBefore, - 0.01 // 0.01% or 1 basis point - ); - - // Check the USDC amount in the vault increases - expect(await usdc.balanceOf(vault.address)).to.approxEqualTolerance( - vaultUSDCBalanceBefore.add(usdcWithdrawAmountExpected), - 0.01 - ); - }); - it("Vault should be able to withdraw some USDC", async () => { - const { - usdc, - morphoSteakHouseUSDCVault, - morphoSteakhouseUSDCStrategy, - ousd, - vault, - vaultSigner, - } = fixture; - - const withdrawAmount = await units("1000", usdc); - - const ousdSupplyBefore = await ousd.totalSupply(); - const vaultUSDCBalanceBefore = await usdc.balanceOf(vault.address); - - log(`Before withdraw of ${formatUnits(withdrawAmount)} from strategy`); - - // Now try to withdraw the USDC from the strategy - const tx = await morphoSteakhouseUSDCStrategy - .connect(vaultSigner) - .withdraw(vault.address, usdc.address, withdrawAmount); - - log("After withdraw from strategy"); - - // Check emitted event - await expect(tx) - .to.emit(morphoSteakhouseUSDCStrategy, "Withdrawal") - .withArgs( - usdc.address, - morphoSteakHouseUSDCVault.address, - withdrawAmount - ); - - // Check the OUSD total supply stays the same - const ousdSupplyAfter = await ousd.totalSupply(); - expect(ousdSupplyAfter).to.approxEqualTolerance( - ousdSupplyBefore, - 0.01 // 0.01% or 1 basis point - ); - - // Check the USDC balance in the Vault - expect(await usdc.balanceOf(vault.address)).to.equal( - vaultUSDCBalanceBefore.add(withdrawAmount) - ); - }); - it("Only vault can withdraw some USDC from strategy", async function () { - const { - morphoSteakhouseUSDCStrategy, - oethVault, - strategist, - timelock, - oldTimelock, - josh, - weth, - } = fixture; - - for (const signer of [strategist, timelock, oldTimelock, josh]) { - const tx = morphoSteakhouseUSDCStrategy - .connect(signer) - .withdraw(oethVault.address, weth.address, parseUnits("50")); - - await expect(tx).to.revertedWith("Caller is not the Vault"); - } - }); - it("Only vault and governor can withdraw all USDC from Maker DSR strategy", async function () { - const { morphoSteakhouseUSDCStrategy, strategist, timelock, josh } = - fixture; - - for (const signer of [strategist, josh]) { - const tx = morphoSteakhouseUSDCStrategy.connect(signer).withdrawAll(); - - await expect(tx).to.revertedWith("Caller is not the Vault or Governor"); - } - - // Governor can withdraw all - const tx = morphoSteakhouseUSDCStrategy.connect(timelock).withdrawAll(); - await expect(tx).to.emit(morphoSteakhouseUSDCStrategy, "Withdrawal"); - }); - }); - - describe("administration", () => { - const loadFixture = createFixtureLoader(morphoSteakhouseUSDCFixture); - beforeEach(async () => { - fixture = await loadFixture(); - }); - it("Governor should not be able to set the platform token", () => { - const { frxETH, sfrxETH, morphoSteakhouseUSDCStrategy, timelock } = - fixture; - - const tx = morphoSteakhouseUSDCStrategy - .connect(timelock) - .setPTokenAddress(frxETH.address, sfrxETH.address); - expect(tx).to.be.revertedWith("unsupported function"); - }); - it("Governor should not be able to remove the platform token", () => { - const { morphoSteakhouseUSDCStrategy, timelock } = fixture; - - const tx = morphoSteakhouseUSDCStrategy.connect(timelock).removePToken(0); - expect(tx).to.be.revertedWith("unsupported function"); - }); - }); -}); diff --git a/contracts/test/strategies/ousd-v2-morpho.mainnet.fork-test.js b/contracts/test/strategies/ousd-v2-morpho.mainnet.fork-test.js index 3add96409a..27b91bc6fd 100644 --- a/contracts/test/strategies/ousd-v2-morpho.mainnet.fork-test.js +++ b/contracts/test/strategies/ousd-v2-morpho.mainnet.fork-test.js @@ -7,6 +7,7 @@ const { getMerklRewards } = require("../../tasks/merkl"); const { units, isCI } = require("../helpers"); const { createFixtureLoader, morphoOUSDv2Fixture } = require("../_fixture"); +const { impersonateAndFund } = require("../../utils/signers"); const log = require("../../utils/logger")("test:fork:ousd-v2-morpho"); @@ -201,7 +202,7 @@ describe("ForkTest: Yearn's Morpho OUSD v2 Strategy", function () { describe("with the strategy having some USDC in Morpho Strategy", () => { const loadFixture = createFixtureLoader(morphoOUSDv2Fixture, { - usdcMintAmount: 12000, + usdcMintAmount: 120000, depositToStrategy: true, }); beforeEach(async () => { @@ -218,7 +219,7 @@ describe("ForkTest: Yearn's Morpho OUSD v2 Strategy", function () { vaultSigner, } = fixture; - const minBalance = await units("12000", usdc); + const minBalance = await units("120000", usdc); const strategyVaultShares = await morphoOUSDv2Vault.balanceOf( morphoOUSDv2Strategy.address ); @@ -373,8 +374,7 @@ describe("ForkTest: Yearn's Morpho OUSD v2 Strategy", function () { }); it("Should be able to collect MORPHO rewards", async () => { - const { buyBackSigner, josh, morphoOUSDv2Strategy, morphoToken } = - fixture; + const { josh, morphoOUSDv2Strategy, morphoToken } = fixture; const { amount, proofs } = await getMerklRewards({ userAddress: morphoOUSDv2Strategy.address, @@ -392,6 +392,9 @@ describe("ForkTest: Yearn's Morpho OUSD v2 Strategy", function () { morphoOUSDv2Strategy.address ); + const buyBackSigner = await impersonateAndFund( + addresses.multichainBuybackOperator + ); const tx = await morphoOUSDv2Strategy .connect(buyBackSigner) .collectRewardTokens(); diff --git a/contracts/test/strategies/plume/rooster-amo.plume.fork-test.js b/contracts/test/strategies/plume/rooster-amo.plume.fork-test.js deleted file mode 100644 index c66da5fe6c..0000000000 --- a/contracts/test/strategies/plume/rooster-amo.plume.fork-test.js +++ /dev/null @@ -1,1140 +0,0 @@ -const hre = require("hardhat"); -const { createFixtureLoader } = require("../../_fixture"); - -const addresses = require("../../../utils/addresses"); -const { plumeFixtureWithMockedVaultAdmin } = require("../../_fixture-plume"); -const { expect } = require("chai"); -const { oethUnits } = require("../../helpers"); -const ethers = hre.ethers; -const { impersonateAndFund } = require("../../../utils/signers"); -const { BigNumber } = ethers; - -const plumeFixtureWithMockedVault = createFixtureLoader( - plumeFixtureWithMockedVaultAdmin -); - -const { setERC20TokenBalance } = require("../../_fund"); - -describe.skip("ForkTest: Rooster AMO Strategy (Plume)", async function () { - let fixture, - oethpVault, - oethVaultSigner, - oethp, - weth, - roosterAmoStrategy, - roosterOETHpWETHpool, - governor, - strategist, - rafael; - - beforeEach(async () => { - fixture = await plumeFixtureWithMockedVault(); - weth = fixture.weth; - oethp = fixture.oethp; - oethpVault = fixture.oethpVault; - roosterAmoStrategy = fixture.roosterAmoStrategy; - roosterOETHpWETHpool = fixture.roosterOETHpWETHpool; - governor = fixture.governor; - strategist = fixture.strategist; - rafael = fixture.rafael; - oethVaultSigner = await impersonateAndFund(oethpVault.address); - - await setup(); - }); - - const configureAutomaticDepositOnMint = async (vaultBuffer) => { - await oethpVault.connect(governor).setVaultBuffer(vaultBuffer); - - const totalValue = await oethpVault.totalValue(); - - // min mint to trigger deposits - return totalValue.mul(vaultBuffer).div(oethUnits("1")); - }; - - describe("ForkTest: Initial state (Plume)", function () { - it("Should have the correct initial state", async function () { - // correct pool weth share interval - expect(await roosterAmoStrategy.allowedWethShareStart()).to.equal( - oethUnits("0.10") - ); - - expect(await roosterAmoStrategy.allowedWethShareEnd()).to.equal( - oethUnits("0.25") - ); - - expect(await roosterAmoStrategy.tickDominance()).to.gt(0); - expect(await roosterAmoStrategy.tickDominance()).to.lte(oethUnits("1")); - - await verifyEndConditions(); - }); - - it("Should revert setting ptoken address", async function () { - await expect( - roosterAmoStrategy - .connect(governor) - .setPTokenAddress(weth.address, weth.address) - ).to.be.revertedWith("Unsupported method"); - }); - - it("Should revert setting ptoken address", async function () { - await expect( - roosterAmoStrategy.connect(governor).removePToken(weth.address) - ).to.be.revertedWith("Unsupported method"); - }); - - it("Should revert calling safe approve all tokens", async function () { - await expect( - roosterAmoStrategy.connect(governor).safeApproveAllTokens() - ).to.be.revertedWith("Unsupported method"); - }); - - it("Should allow calling getWethShare", async function () { - await expect( - await roosterAmoStrategy.connect(governor).getWETHShare() - ).to.be.lte(oethUnits("1")); - }); - - it("Should support WETH", async function () { - await expect( - await roosterAmoStrategy.supportsAsset(weth.address) - ).to.equal(true); - - await expect( - await roosterAmoStrategy.supportsAsset(oethp.address) - ).to.equal(false); - }); - - it("Should not revert calling public views", async function () { - await roosterAmoStrategy.getPoolSqrtPrice(); - await roosterAmoStrategy.getCurrentTradingTick(); - await roosterAmoStrategy.getPositionPrincipal(); - await roosterAmoStrategy.tickDominance(); - }); - }); - - describe("Configuration", function () { - it("Governor can set the allowed pool weth share interval", async () => { - const { roosterAmoStrategy } = fixture; - const gov = await roosterAmoStrategy.governor(); - - await roosterAmoStrategy - .connect(await impersonateAndFund(gov)) - .setAllowedPoolWethShareInterval(oethUnits("0.19"), oethUnits("0.23")); - - expect(await roosterAmoStrategy.allowedWethShareStart()).to.equal( - oethUnits("0.19") - ); - - expect(await roosterAmoStrategy.allowedWethShareEnd()).to.equal( - oethUnits("0.23") - ); - }); - - it("Only the governor can set the pool weth share", async () => { - const { rafael, roosterAmoStrategy } = fixture; - - await expect( - roosterAmoStrategy - .connect(rafael) - .setAllowedPoolWethShareInterval(oethUnits("0.19"), oethUnits("0.23")) - ).to.be.revertedWith("Caller is not the Governor"); - }); - - it("Can not mint initial position twice", async () => { - const { governor, roosterAmoStrategy } = fixture; - - await expect( - roosterAmoStrategy.connect(governor).mintInitialPosition() - ).to.be.revertedWith("Initial position already minted"); - }); - - it("Only the governor can mint the initial position", async () => { - const { rafael, roosterAmoStrategy } = fixture; - - await expect( - roosterAmoStrategy.connect(rafael).mintInitialPosition() - ).to.be.revertedWith("Caller is not the Governor"); - }); - - it("Can not set incorrect pool WETH share intervals", async () => { - const { roosterAmoStrategy } = fixture; - const gov = await roosterAmoStrategy.governor(); - - await expect( - roosterAmoStrategy - .connect(await impersonateAndFund(gov)) - .setAllowedPoolWethShareInterval(oethUnits("0.5"), oethUnits("0.4")) - ).to.be.revertedWith("Invalid interval"); - - await expect( - roosterAmoStrategy - .connect(await impersonateAndFund(gov)) - .setAllowedPoolWethShareInterval( - oethUnits("0.0001"), - oethUnits("0.5") - ) - ).to.be.revertedWith("Invalid interval start"); - - await expect( - roosterAmoStrategy - .connect(await impersonateAndFund(gov)) - .setAllowedPoolWethShareInterval(oethUnits("0.2"), oethUnits("0.96")) - ).to.be.revertedWith("Invalid interval end"); - }); - }); - - describe("Withdraw", function () { - it("Should allow withdraw when the pool is 80:20 balanced", async () => { - const { oethpVault, roosterAmoStrategy, weth } = fixture; - - const impersonatedVaultSigner = await impersonateAndFund( - oethpVault.address - ); - - const balanceBefore = await weth.balanceOf(oethpVault.address); - - const poolPrice = await roosterAmoStrategy.getPoolSqrtPrice(); - - // setup() moves the pool closer to 80:20 - const [amountWETH, amountOETHb] = - await roosterAmoStrategy.getPositionPrincipal(); - - // Try withdrawing an amount - await roosterAmoStrategy - .connect(impersonatedVaultSigner) - .withdraw(oethpVault.address, weth.address, oethUnits("1")); - - // Make sure that 1 WETH and 4 OETHb were burned - const [amountWETHAfter, amountOETHbAfter] = - await roosterAmoStrategy.getPositionPrincipal(); - - expect(amountWETHAfter).to.approxEqualTolerance( - amountWETH.sub(oethUnits("1")) - ); - - expect(amountOETHbAfter).to.approxEqualTolerance( - amountOETHb.sub(oethUnits("4")), - 3 - ); - - // Make sure there's no price movement - expect(await roosterAmoStrategy.getPoolSqrtPrice()).to.eq(poolPrice); - - // And recipient has got it - expect(await weth.balanceOf(oethpVault.address)).to.eq( - balanceBefore.add(oethUnits("1")) - ); - - // There may remain some WETH left on the strategy contract because of the rounding when - // removing the liquidity - expect(await weth.balanceOf(roosterAmoStrategy.address)).to.lte( - BigNumber.from("1000") - ); - - await verifyEndConditions(); - }); - - it("Should allow withdrawAll when the pool is 80:20 balanced", async () => { - const { oethpVault, roosterAmoStrategy, weth, oethp } = fixture; - - const impersonatedVaultSigner = await impersonateAndFund( - oethpVault.address - ); - - const balanceBefore = await weth.balanceOf(oethpVault.address); - const supplyBefore = await oethp.totalSupply(); - - // setup() moves the pool closer to 80:20 - const [amountWETHBefore, amountOETHpBefore] = - await roosterAmoStrategy.getPositionPrincipal(); - - // Try withdrawing an amount - await roosterAmoStrategy.connect(impersonatedVaultSigner).withdrawAll(); - - // // Make sure pool is empty - const [amountWETH, amountOETHb] = - await roosterAmoStrategy.getPositionPrincipal(); - expect(amountOETHb).to.eq(0); - expect(amountWETH).to.eq(0); - - // And recipient has got it - expect(await weth.balanceOf(oethpVault.address)).to.approxEqualTolerance( - balanceBefore.add(amountWETHBefore) - ); - - // And supply has gone down - expect(await oethp.totalSupply()).to.eq( - supplyBefore.sub(amountOETHpBefore) - ); - - // There should be no WETH on the strategy contract - expect(await weth.balanceOf(roosterAmoStrategy.address)).to.eq( - oethUnits("0") - ); - }); - - it("Should allow double withdrawAll", async () => { - const { oethpVault, roosterAmoStrategy } = fixture; - - const impersonatedVaultSigner = await impersonateAndFund( - oethpVault.address - ); - - // Try withdrawing an amount - await roosterAmoStrategy.connect(impersonatedVaultSigner).withdrawAll(); - await roosterAmoStrategy.connect(impersonatedVaultSigner).withdrawAll(); - }); - - it("Should withdraw when there's little WETH in the pool", async () => { - const { oethpVault, roosterAmoStrategy, weth } = fixture; - - const impersonatedVaultSigner = await impersonateAndFund( - oethpVault.address - ); - - // Drain out most of WETH - await rebalanceThePoolToWETHRatio("0.01"); - - const balanceBefore = await weth.balanceOf(oethpVault.address); - - const [amountWETH, amountOETHb] = - await roosterAmoStrategy.getPositionPrincipal(); - - // Try withdrawing an amount - await roosterAmoStrategy - .connect(impersonatedVaultSigner) - .withdraw(oethpVault.address, weth.address, oethUnits("0.01")); - - // Make sure that 1 WETH was burned and pool composition remains the same - const [amountWETHAfter, amountOETHbAfter] = - await roosterAmoStrategy.getPositionPrincipal(); - expect(amountWETHAfter).to.approxEqualTolerance( - amountWETH.sub(oethUnits("0.01")) - ); - expect(amountOETHbAfter.div(amountWETHAfter)).to.approxEqualTolerance( - amountOETHb.div(amountWETH) - ); - - // And recipient has got it - expect(await weth.balanceOf(oethpVault.address)).to.approxEqualTolerance( - balanceBefore.add(oethUnits("0.01")) - ); - - // There may remain some WETH left on the strategy contract because of the rounding - // when removing the liquidity - expect(await weth.balanceOf(roosterAmoStrategy.address)).to.lte( - BigNumber.from("1000") - ); - - await verifyEndConditions(); - }); - - it("Should withdrawAll when there's little WETH in the pool", async () => { - const { oethpVault, roosterAmoStrategy, weth } = fixture; - - const impersonatedVaultSigner = await impersonateAndFund( - oethpVault.address - ); - - // setup() moves the pool closer to 80:20 - - // Drain out most of WETH - await rebalanceThePoolToWETHRatio("0.01"); - - const balanceBefore = await weth.balanceOf(oethpVault.address); - - const [amountWETH] = await roosterAmoStrategy.getPositionPrincipal(); - - // Try withdrawing an amount - await roosterAmoStrategy.connect(impersonatedVaultSigner).withdrawAll(); - - // And recipient has got it - expect(await weth.balanceOf(oethpVault.address)).to.approxEqualTolerance( - balanceBefore.add(amountWETH) - ); - - // There should be no WETH on the strategy contract - expect(await weth.balanceOf(roosterAmoStrategy.address)).to.eq( - oethUnits("0") - ); - - await verifyEndConditions(); - }); - - it("Should withdraw when there's little OETHp in the pool", async () => { - const { oethpVault, roosterAmoStrategy, weth } = fixture; - - const impersonatedVaultSigner = await impersonateAndFund( - oethpVault.address - ); - - // setup() moves the pool closer to 80:20 - await rebalanceThePoolToWETHRatio("0.97"); - - const balanceBefore = await weth.balanceOf(oethpVault.address); - - const [amountWETH, amountOETHb] = - await roosterAmoStrategy.getPositionPrincipal(); - - // Try withdrawing an amount - await roosterAmoStrategy - .connect(impersonatedVaultSigner) - .withdraw(oethpVault.address, weth.address, oethUnits("1")); - - // Make sure that 1 WETH was burned and pool composition remains the same - const [amountWETHAfter, amountOETHbAfter] = - await roosterAmoStrategy.getPositionPrincipal(); - expect(amountWETHAfter).to.approxEqualTolerance( - amountWETH.sub(oethUnits("1")) - ); - expect(amountOETHbAfter.div(amountWETHAfter)).to.approxEqualTolerance( - amountOETHb.div(amountWETH) - ); - - // And recipient has got it - expect(await weth.balanceOf(oethpVault.address)).to.approxEqualTolerance( - balanceBefore.add(oethUnits("1")) - ); - - // There may remain some WETH left on the strategy contract because of the rounding - // when removing the liquidity - expect(await weth.balanceOf(roosterAmoStrategy.address)).to.lte( - BigNumber.from("10000") - ); - - await verifyEndConditions(); - }); - - it("Should withdrawAll when there's little OETHp in the pool", async () => { - const { oethpVault, roosterAmoStrategy, weth } = fixture; - - const impersonatedVaultSigner = await impersonateAndFund( - oethpVault.address - ); - - // setup() moves the pool closer to 80:20 - await rebalanceThePoolToWETHRatio("0.97"); - - const balanceBefore = await weth.balanceOf(oethpVault.address); - - const [amountWETH] = await roosterAmoStrategy.getPositionPrincipal(); - - // Try withdrawing an amount - await roosterAmoStrategy.connect(impersonatedVaultSigner).withdrawAll(); - - // And recipient has got it - expect(await weth.balanceOf(oethpVault.address)).to.approxEqualTolerance( - balanceBefore.add(amountWETH) - ); - - // There should be no WETH on the strategy contract - expect(await weth.balanceOf(roosterAmoStrategy.address)).to.eq( - oethUnits("0") - ); - - await verifyEndConditions(); - }); - }); - - describe("Deposit and rebalance", function () { - it("Should be able to deposit to the strategy", async () => { - await mintAndDepositToStrategy(); - - await verifyEndConditions(); - }); - - it("Should revert when not depositing WETH or amount is 0, or withdrawing WETH", async () => { - await expect( - roosterAmoStrategy - .connect(oethVaultSigner) - .deposit(oethp.address, BigNumber.from("1")) - ).to.be.revertedWith("Unsupported asset"); - - await expect( - roosterAmoStrategy - .connect(oethVaultSigner) - .withdraw(oethpVault.address, oethp.address, oethUnits("1")) - ).to.be.revertedWith("Unsupported asset"); - - await expect( - roosterAmoStrategy - .connect(oethVaultSigner) - .withdraw(oethpVault.address, weth.address, 0) - ).to.be.revertedWith("Must withdraw something"); - - await expect( - roosterAmoStrategy - .connect(oethVaultSigner) - .withdraw(weth.address, weth.address, oethUnits("1")) - ).to.be.revertedWith("Only withdraw to vault allowed"); - - await expect( - roosterAmoStrategy - .connect(oethVaultSigner) - .deposit(weth.address, BigNumber.from("0")) - ).to.be.revertedWith("Must deposit something"); - }); - - it("Should check that add liquidity in difference cases leaves little weth on the contract", async () => { - const amount = oethUnits("5"); - - await weth.connect(rafael).approve(oethpVault.address, amount); - await oethpVault.connect(rafael).mint(weth.address, amount, amount); - - const gov = await roosterAmoStrategy.governor(); - await oethpVault - .connect(await impersonateAndFund(gov)) - .depositToStrategy( - roosterAmoStrategy.address, - [weth.address], - [amount] - ); - - // Rooster LENS contracts have issues where they over-calculate the amount of tokens that need to be - // deposited - expect(await weth.balanceOf(roosterAmoStrategy.address)).to.lte( - oethUnits("10000000000") - ); - - await verifyEndConditions(); - }); - - it("Should revert when there is not enough WETH to perform a swap", async () => { - await rebalanceThePoolToWETHRatio("0.02"); - await expect( - rebalance( - oethUnits("1000000000"), - true, // _swapWETH - oethUnits("0.009") - ) - ).to.be.revertedWithCustomError( - "NotEnoughWethLiquidity(uint256,uint256)" - ); - }); - - it("Should have the correct balance within some tolerance", async () => { - const balance = await roosterAmoStrategy.checkBalance(weth.address); - const amountToDeposit = oethUnits("6"); - await mintAndDepositToStrategy({ amount: amountToDeposit }); - - // just add liquidity don't move the active trading position - await rebalance(BigNumber.from("0"), true, BigNumber.from("0")); - - const wethShare = await roosterAmoStrategy.getCurrentWethShare(); - - await expect( - await roosterAmoStrategy.checkBalance(weth.address) - ).to.approxEqualTolerance( - balance.add(amountToDeposit.div(wethShare).mul(oethUnits("1"))), - 1.5 - ); - - await verifyEndConditions(); - }); - - it("Current trading price shouldn't affect checkBalance", async () => { - const amountToDeposit = oethUnits("6"); - await mintAndDepositToStrategy({ amount: amountToDeposit }); - const balanceBefore = await roosterAmoStrategy.checkBalance(weth.address); - - // perform a swap - await swap({ - amount: oethUnits("1"), - swapWeth: false, - }); - - expect(await roosterAmoStrategy.checkBalance(weth.address)).to.equal( - balanceBefore - ); - - await verifyEndConditions(); - }); - - it("Should be able to rebalance removing half of liquidity", async () => { - const balance = await roosterAmoStrategy.checkBalance(weth.address); - const amountToDeposit = oethUnits("6"); - await mintAndDepositToStrategy({ amount: amountToDeposit }); - - // just add liquidity don't move the active trading position - await rebalance(BigNumber.from("0"), true, BigNumber.from("0"), "0.5"); - - const wethShare = await roosterAmoStrategy.getCurrentWethShare(); - - await expect( - await roosterAmoStrategy.checkBalance(weth.address) - ).to.approxEqualTolerance( - balance.add(amountToDeposit.div(wethShare).mul(oethUnits("1"))), - 1.5 - ); - - await verifyEndConditions(); - }); - - it("Should be able to rebalance removing all of liquidity", async () => { - const balance = await roosterAmoStrategy.checkBalance(weth.address); - const amountToDeposit = oethUnits("6"); - await mintAndDepositToStrategy({ amount: amountToDeposit }); - - // just add liquidity don't move the active trading position - await rebalance(BigNumber.from("0"), true, BigNumber.from("0"), "1"); - - const wethShare = await roosterAmoStrategy.getCurrentWethShare(); - - await expect( - await roosterAmoStrategy.checkBalance(weth.address) - ).to.approxEqualTolerance( - balance.add(amountToDeposit.div(wethShare).mul(oethUnits("1"))), - 1.5 - ); - - await verifyEndConditions(); - }); - - it("Should revert when it fails the slippage check", async () => { - const amountToDeposit = oethUnits("6"); - await mintAndDepositToStrategy({ amount: amountToDeposit }); - - await expect( - rebalance(oethUnits("1"), true, oethUnits("1.1")) - ).to.be.revertedWithCustomError("SlippageCheck(uint256)"); - - await verifyEndConditions(); - }); - - it("Should revert on non WETH balance", async () => { - await expect( - roosterAmoStrategy.checkBalance(oethp.address) - ).to.be.revertedWith("Only WETH supported"); - }); - - it("Should throw an exception if not enough WETH on rebalance to perform a swap", async () => { - // swap out most of the weth - await swap({ - // Pool has 5 WETH - amount: oethUnits("4.99"), - swapWeth: false, - }); - - await expect( - rebalance( - (await weth.balanceOf(await roosterAmoStrategy.mPool())).mul("2"), - true, - oethUnits("4") - ) - ).to.be.revertedWithCustomError( - "NotEnoughWethLiquidity(uint256,uint256)" - ); - - await verifyEndConditions(); - }); - - it("Should not be able to rebalance when protocol is insolvent", async () => { - await mintAndDepositToStrategy({ amount: oethUnits("1000") }); - await roosterAmoStrategy.connect(oethVaultSigner).withdrawAll(); - - // ensure there is a LP position - await mintAndDepositToStrategy({ amount: oethUnits("1") }); - - // transfer WETH out making the protocol insolvent - const swapBal = oethUnits("0.00001"); - const addLiquidityBal = oethUnits("1"); - const balRemaining = (await weth.balanceOf(oethpVault.address)) - .sub(swapBal) - .sub(addLiquidityBal); - - await weth - .connect(oethVaultSigner) - .transfer(roosterAmoStrategy.address, swapBal.add(addLiquidityBal)); - await weth - .connect(oethVaultSigner) - .transfer(addresses.dead, balRemaining); - - await expect( - rebalance( - swapBal, - true, // _swapWETHs - oethUnits("0.000009"), - "0" - ) - ).to.be.revertedWith("Protocol insolvent"); - }); - }); - - describe("Perform multiple actions", function () { - it("LP token should stay staked with multiple deposit/withdraw actions", async () => { - // deposit into pool once - await mintAndDepositToStrategy({ amount: oethUnits("5") }); - // prettier-ignore - const tx = await rebalance( - oethUnits("0.00001"), - true, // _swapWETHs - oethUnits("0.000009") - ); - await expect(tx).to.emit(roosterAmoStrategy, "PoolRebalanced"); - await verifyEndConditions(); - - // deposit into pool again - await mintAndDepositToStrategy({ amount: oethUnits("5") }); - // prettier-ignore - const tx1 = await rebalance( - oethUnits("0"), - true, // _swapWETHs - oethUnits("0") - ); - await expect(tx1).to.emit(roosterAmoStrategy, "PoolRebalanced"); - await verifyEndConditions(); - - // Withdraw from the pool - await roosterAmoStrategy - .connect(oethVaultSigner) - .withdraw(oethpVault.address, weth.address, oethUnits("1")); - await verifyEndConditions(); - - // deposit into pool again - await mintAndDepositToStrategy({ amount: oethUnits("5") }); - // prettier-ignore - const tx2 = await rebalance( - oethUnits("0"), - true, // _swapWETHs - oethUnits("0") - ); - await expect(tx2).to.emit(roosterAmoStrategy, "PoolRebalanced"); - await verifyEndConditions(); - - // Withdraw from the pool - await roosterAmoStrategy - .connect(oethVaultSigner) - .withdraw(oethpVault.address, weth.address, oethUnits("1")); - await verifyEndConditions(); - - // Withdraw from the pool - await roosterAmoStrategy.connect(oethVaultSigner).withdrawAll(); - - // deposit into pool again - await mintAndDepositToStrategy({ amount: oethUnits("5") }); - // prettier-ignore - const tx3 = await rebalance( - oethUnits("0"), - true, // _swapWETHs - oethUnits("0") - ); - await expect(tx3).to.emit(roosterAmoStrategy, "PoolRebalanced"); - await verifyEndConditions(); - }); - }); - - describe("Deposit and rebalance with mocked Vault", async () => { - beforeEach(async () => { - fixture = await plumeFixtureWithMockedVault(); - weth = fixture.weth; - oethpVault = fixture.oethpVault; - roosterAmoStrategy = fixture.roosterAmoStrategy; - governor = fixture.governor; - strategist = fixture.strategist; - - await setup(); - }); - - const depositAllVaultWeth = async ({ returnTransaction } = {}) => { - const wethAvailable = await oethpVault.wethAvailable(); - const gov = await oethpVault.governor(); - const tx = await oethpVault - .connect(await impersonateAndFund(gov)) - .depositToStrategy( - roosterAmoStrategy.address, - [weth.address], - [wethAvailable] - ); - - if (returnTransaction) { - return tx; - } - - await expect(tx).to.emit(roosterAmoStrategy, "PoolRebalanced"); - }; - - const depositAllWethAndConfigure1pct = async () => { - // configure to leave no WETH on the vault - await configureAutomaticDepositOnMint(oethUnits("0")); - const outstandingWeth = await oethpVault.outstandingWithdrawalsAmount(); - - // send WETH to the vault that is outstanding to be claimed - await weth - .connect(fixture.clement) - .transfer(oethpVault.address, outstandingWeth); - - await depositAllVaultWeth(); - - // configure to only keep 1bp of the Vault's totalValue in the Vault; - const minAmountReserved = await configureAutomaticDepositOnMint( - oethUnits("0.01") - ); - - return minAmountReserved; - }; - - it("Should not automatically deposit to strategy when below vault buffer threshold", async () => { - const minAmountReserved = await depositAllWethAndConfigure1pct(); - - const amountBelowThreshold = minAmountReserved.div(BigNumber.from("2")); - - await mint({ amount: amountBelowThreshold }); - // There is some WETH usually on the strategy contract because liquidity manager doesn't consume all. - // Also the strategy contract adjusts WETH supplied to pool down, to mitigate the PoolLens liquidity - // calculation. - await expect(await weth.balanceOf(roosterAmoStrategy.address)).to.lte( - BigNumber.from("1000") - ); - - await expect(await oethpVault.wethAvailable()).to.approxEqualTolerance( - amountBelowThreshold - ); - - await verifyEndConditions(); - }); - - it("Should revert when pool rebalance is off target", async () => { - const { amount, swapWeth } = await estimateSwapAmountsToReachWethRatio( - oethUnits("0.91") - ); - await swap({ amount, swapWeth }); - await mintAndDepositToStrategy({ amount: oethUnits("1000") }, false); - const { amount: amount2, swapWeth: swapWeth2 } = - await estimateSwapAmountsToReachWethRatio(oethUnits("0.91")); - - await expect( - rebalance(amount2, swapWeth2, 0, "0") - ).to.be.revertedWithCustomError( - "PoolRebalanceOutOfBounds(uint256,uint256,uint256)" - ); - - await expect( - rebalance(amount.add(amount), swapWeth2, 0, "0") - ).to.be.revertedWithCustomError("OutsideExpectedTickRange()"); - }); - - it("Should be able to rebalance the pool when price pushed very close to 1:1", async () => { - const { amount: amountToSwap, swapWeth: swapWethToSwap } = - await estimateSwapAmountsToReachWethRatio(oethUnits("0.99")); - await swap({ - amount: amountToSwap, - swapWeth: swapWethToSwap, - }); - - const { amount, swapWeth } = - await estimateSwapAmountsToGetToConfiguredInterval(); - - await rebalance(amount, swapWeth, 0, "0"); - await verifyEndConditions(); - }); - - it("Should be able to rebalance the pool when price pushed very close to OETHb costing 0.9999 WETH", async () => { - const { amount: amountToSwap, swapWeth: swapWethToSwap } = - await estimateSwapAmountsToReachWethRatio(oethUnits("0.01")); - await swap({ - amount: amountToSwap, - swapWeth: swapWethToSwap, - }); - - const { amount, swapWeth } = - await estimateSwapAmountsToGetToConfiguredInterval(); - await mintAndDepositToStrategy({ amount: oethUnits("1000") }, false); - - await rebalance(amount, swapWeth, 0, "0"); - await verifyEndConditions(); - }); - - it("Should be able to deposit to the pool & rebalance", async () => { - await mintAndDepositToStrategy({ amount: oethUnits("5") }); - let { amount, swapWeth } = - await estimateSwapAmountsToGetToConfiguredInterval(); - - const tx = await rebalance(amount, swapWeth, 0, "0"); - - await expect(tx).to.emit(roosterAmoStrategy, "PoolRebalanced"); - await verifyEndConditions(); - }); - - it("Should be able to rebalance when small amount of WETH needs to be removed as swap liquidity", async () => { - await mintAndDepositToStrategy({ amount: oethUnits("5") }); - - const { amount, swapWeth } = - await estimateSwapAmountsToGetToConfiguredInterval(); - // rebalance to use up any WETH liquidity on the contract - await rebalance(amount, swapWeth, 0, "0"); - // rebalance requiring small amount of WETH (increasing) - await rebalance(BigNumber.from("100"), true, 0, "0"); - await rebalance(BigNumber.from("10000"), true, 0, "0"); - await rebalance(BigNumber.from("1000000"), true, 0, "0"); - await rebalance(BigNumber.from("100000000"), true, 0, "0"); - await rebalance(BigNumber.from("10000000000"), true, 0, "0"); - await rebalance(BigNumber.from("1000000000000"), true, 0, "0"); - - await verifyEndConditions(); - }); - - it("Should be able to deposit to the pool & rebalance multiple times", async () => { - await mintAndDepositToStrategy({ amount: oethUnits("5") }); - let { amount, swapWeth } = - await estimateSwapAmountsToGetToConfiguredInterval(); - const tx = await rebalance(amount, swapWeth, 0, "0"); - await expect(tx).to.emit(roosterAmoStrategy, "PoolRebalanced"); - await verifyEndConditions(); - - await mintAndDepositToStrategy({ amount: oethUnits("5") }); - - let { amount: amount1, swapWeth: swapWeth1 } = - await estimateSwapAmountsToGetToConfiguredInterval(); - const tx1 = await rebalance(amount1, swapWeth1, 0, "0"); - await expect(tx1).to.emit(roosterAmoStrategy, "PoolRebalanced"); - await verifyEndConditions(); - }); - - const depositValues = [ - "0.5", - "2.5", - "3.5", - "5", - "9", - "50", - "250", - "500", - "1500", - "2500", - ]; - for (const depositValue of depositValues) { - it(`Should be able to deposit to the pool & rebalance multiple times with ${depositValue} deposit`, async () => { - await mintAndDepositToStrategy({ amount: oethUnits("5") }); - let { amount, swapWeth } = - await estimateSwapAmountsToGetToConfiguredInterval(); - const tx = await rebalance(amount, swapWeth, 0, "0"); - await expect(tx).to.emit(roosterAmoStrategy, "PoolRebalanced"); - await verifyEndConditions(); - - await mintAndDepositToStrategy({ amount: oethUnits("5") }); - - let { amount: amount1, swapWeth: swapWeth1 } = - await estimateSwapAmountsToGetToConfiguredInterval(); - const tx1 = await rebalance(amount1, swapWeth1, 0, "0"); - await expect(tx1).to.emit(roosterAmoStrategy, "PoolRebalanced"); - await verifyEndConditions(); - }); - } - }); - - describe("Rewards", function () { - it("Should be able to claim rewards when available", async () => { - // Make sure the strategy has something - await mintAndDepositToStrategy({ amount: oethUnits("5") }); - - const wPlume = await ethers.getContractAt( - "IWETH9", - addresses.plume.WPLUME - ); - const balanceBefore = await wPlume.balanceOf(strategist.address); - await roosterAmoStrategy.connect(strategist).collectRewardTokens(); - const balanceAfter = await wPlume.balanceOf(strategist.address); - - const balanceDiff = balanceAfter.sub(balanceBefore); - expect(balanceDiff).to.eq(oethUnits("1")); - }); - }); - - const setup = async () => { - await mintAndDepositToStrategy({ - amount: oethUnits("100"), - returnTransaction: true, - }); - - await oethpVault.connect(rafael).rebase(); - - let { amount, swapWeth } = - await estimateSwapAmountsToGetToConfiguredInterval(); - - await rebalance(amount, swapWeth, 0, "0"); - }; - - const swap = async ({ amount, swapWeth }) => { - // Check if rafael as enough token to perform swap - // If not, mint some - const balanceOETHp = await oethp.balanceOf(rafael.address); - if (!swapWeth && balanceOETHp.lt(amount)) { - await weth.connect(rafael).approve(oethpVault.address, amount); - await oethpVault.connect(rafael).mint(weth.address, amount, amount); - } - - if (swapWeth) { - await weth.connect(rafael).transfer(roosterOETHpWETHpool.address, amount); - } else { - await oethp - .connect(rafael) - .transfer(roosterOETHpWETHpool.address, amount); - } - - await roosterOETHpWETHpool.connect(rafael).swap( - rafael.address, - { - amount: amount, - tokenAIn: swapWeth, - exactOutput: false, - tickLimit: swapWeth ? 2147483647 : -2147483648, - }, - "0x" - ); - }; - - // get the middle point of configured weth share interval - const getConfiguredWethShare = async () => { - const wethShareStart = await roosterAmoStrategy.allowedWethShareStart(); - const wethShareEnd = await roosterAmoStrategy.allowedWethShareEnd(); - - return wethShareStart.add(wethShareEnd).div(BigNumber.from("2")); - }; - - const rebalanceThePoolToWETHRatio = async (wethRatio) => { - const { amount, swapWeth } = await estimateSwapAmountsToReachWethRatio( - oethUnits(wethRatio) - ); - await swap({ amount, swapWeth }); - }; - - const estimateSwapAmountsToGetToConfiguredInterval = async () => { - const configuredWethShare = await getConfiguredWethShare(); - return await estimateSwapAmountsToReachWethRatio(configuredWethShare); - }; - - // the amount to swap (and token type) to reach desired WETH ratio - // Notice: this only works if the pools is already in the tick -1 where - // all the liquidity is deployed - const estimateSwapAmountsToReachWethRatio = async (wethRatio) => { - let currentTradingTick = parseInt( - (await roosterAmoStrategy.getCurrentTradingTick()).toString() - ); - let totalAmount = BigNumber.from("0"); - - while (currentTradingTick < -1) { - totalAmount = totalAmount.add(await _getOETHInTick(currentTradingTick)); - currentTradingTick += 1; - } - - while (currentTradingTick > -1) { - totalAmount = totalAmount.add(await _getWETHInTick(currentTradingTick)); - currentTradingTick -= 1; - } - - let { amount, swapWeth } = await _estimateAmountsWithinTheAMOTick( - wethRatio - ); - amount = amount.add(totalAmount); - return { - amount, - swapWeth, - }; - }; - - const _getWETHInTick = async (tradingTick) => { - const tickState = await roosterOETHpWETHpool.getTick(tradingTick); - return tickState.reserveA; - }; - - const _getOETHInTick = async (tradingTick) => { - const tickState = await roosterOETHpWETHpool.getTick(tradingTick); - return tickState.reserveB; - }; - - const _estimateAmountsWithinTheAMOTick = async (wethRatio) => { - const tickState = await roosterOETHpWETHpool.getTick(-1); - - const wethAmount = tickState.reserveA; - const oethAmount = tickState.reserveB; - - const total = wethAmount.add(oethAmount); - // 1e18 denominated - const currentWethRatio = wethAmount.mul(oethUnits("1")).div(total); - - let diff, swapWeth; - if (wethRatio.gt(currentWethRatio)) { - diff = wethRatio.sub(currentWethRatio); - swapWeth = true; - } else { - diff = currentWethRatio.sub(wethRatio); - swapWeth = false; - } - - return { - amount: diff.mul(total).div(oethUnits("1")), - swapWeth, - }; - }; - - const rebalance = async ( - amountToSwap, - swapWETH, - minTokenReceived, - liquidityToRemove = "1" - ) => { - return await roosterAmoStrategy - .connect(strategist) - .rebalance( - amountToSwap, - swapWETH, - minTokenReceived, - oethUnits(liquidityToRemove) - ); - }; - - const mint = async ({ userOverride, amount } = {}) => { - const user = userOverride || rafael; - amount = amount || oethUnits("5"); - - const balance = weth.balanceOf(user.address); - if (balance < amount) { - await setERC20TokenBalance(user.address, weth, amount + balance, hre); - } - await weth.connect(user).approve(oethpVault.address, amount); - const tx = await oethpVault - .connect(user) - .mint(weth.address, amount, amount); - return tx; - }; - - const mintAndDepositToStrategy = async ( - { userOverride, amount, returnTransaction } = {}, - expectPoolRebalanced = true - ) => { - const user = userOverride || rafael; - amount = amount || oethUnits("5"); - - const balance = weth.balanceOf(user.address); - if (balance < amount) { - await setERC20TokenBalance(user.address, weth, amount + balance, hre); - } - await weth.connect(user).approve(oethpVault.address, amount); - await oethpVault.connect(user).mint(weth.address, amount, amount); - - const gov = await oethpVault.governor(); - const tx = await oethpVault - .connect(await impersonateAndFund(gov)) - .depositToStrategy(roosterAmoStrategy.address, [weth.address], [amount]); - - if (returnTransaction) { - return tx; - } - - if (expectPoolRebalanced) { - await expect(tx).to.emit(roosterAmoStrategy, "PoolRebalanced"); - } - }; - - /** When tests finish: - * - there should be no substantial amount of WETH / OETHp left on the strategy contract - */ - const verifyEndConditions = async () => { - await expect(await weth.balanceOf(roosterAmoStrategy.address)).to.lte( - oethUnits("0.00001") - ); - await expect(await oethp.balanceOf(roosterAmoStrategy.address)).to.lte( - oethUnits("0.000001") - ); - }; -}); diff --git a/contracts/test/vault/compound.js b/contracts/test/vault/compound.js deleted file mode 100644 index cdbb3cb342..0000000000 --- a/contracts/test/vault/compound.js +++ /dev/null @@ -1,468 +0,0 @@ -const { expect } = require("chai"); -const { utils } = require("ethers"); - -const { createFixtureLoader, compoundVaultFixture } = require("../_fixture"); - -const { - advanceTime, - ousdUnits, - usdsUnits, - usdcUnits, - setOracleTokenPriceUsd, - isFork, - expectApproxSupply, -} = require("../helpers"); -const { - increase, -} = require("@nomicfoundation/hardhat-network-helpers/dist/src/helpers/time"); - -describe.skip("Vault with Compound strategy", function () { - if (isFork) { - this.timeout(0); - } - let fixture; - const loadFixture = createFixtureLoader(compoundVaultFixture); - beforeEach(async () => { - fixture = await loadFixture(); - }); - - it("Anyone can call safeApproveAllTokens", async () => { - const { matt, compoundStrategy } = fixture; - await compoundStrategy.connect(matt).safeApproveAllTokens(); - }); - - it("Governor can call removePToken", async () => { - const { governor, compoundStrategy } = fixture; - - const tx = await compoundStrategy.connect(governor).removePToken(0); - - await expect(tx).to.emit(compoundStrategy, "PTokenRemoved"); - }); - - it("Governor can call setPTokenAddress", async () => { - const { usdc, ousd, matt, compoundStrategy } = fixture; - - await expect( - compoundStrategy - .connect(matt) - .setPTokenAddress(ousd.address, usdc.address) - ).to.be.revertedWith("Caller is not the Governor"); - }); - - it("Only Vault can call collectRewardToken", async () => { - const { matt, compoundStrategy } = fixture; - - await expect( - compoundStrategy.connect(matt).collectRewardTokens() - ).to.be.revertedWith("Caller is not the Harvester"); - }); - - it("Should allocate unallocated assets", async () => { - const { anna, governor, usdc, vault, compoundStrategy } = fixture; - - await vault.connect(governor).setDefaultStrategy(compoundStrategy.address); - await usdc.connect(anna).transfer(vault.address, usdcUnits("100")); - await expect(vault.connect(governor).allocate()) - .to.emit(vault, "AssetAllocated") - .withArgs(usdc.address, compoundStrategy.address, usdcUnits("300")); - - // Note compoundVaultFixture sets up with 200 USDC already in the Strategy - // 200 + 100 = 300 - await expect( - await compoundStrategy.checkBalance(usdc.address) - ).to.approxEqual(usdcUnits("300")); - }); - - it("Should correctly handle a deposit of USDC (6 decimals)", async function () { - const { anna, ousd, usdc, vault } = fixture; - - await expect(anna).has.a.balanceOf("0", ousd); - await usdc.connect(anna).approve(vault.address, usdcUnits("50")); - await vault - .connect(anna) - .mint(usdc.address, usdcUnits("50"), ousdUnits("50")); - await expect(anna).has.a.balanceOf("50", ousd); - }); - - it("Should allow withdrawals", async () => { - const { strategist, ousd, usdc, vault } = fixture; - - await expect(strategist).has.a.balanceOf("1000.00", usdc); - await usdc.connect(strategist).approve(vault.address, usdcUnits("50.0")); - await vault.connect(strategist).mint(usdc.address, usdcUnits("50.0"), 0); - await expect(strategist).has.a.balanceOf("50.00", ousd); - - await ousd.connect(strategist).approve(vault.address, ousdUnits("40.0")); - await vault.connect(strategist).requestWithdrawal(ousdUnits("40.0")); - await increase(60 * 10); // Advance 10 minutes - await vault.connect(strategist).claimWithdrawal(0); // Assumes request ID is 0 - - await expect(strategist).has.an.balanceOf("10", ousd); - await expect(strategist).has.an.balanceOf("990.0", usdc); - }); - - it("Should calculate the balance correctly with USDC in strategy", async () => { - const { usdc, vault, josh, compoundStrategy, governor } = fixture; - - expect(await vault.totalValue()).to.approxEqual( - utils.parseUnits("200", 18) - ); - - // Josh deposits USDC, 6 decimals - await usdc.connect(josh).approve(vault.address, usdcUnits("22.0")); - await vault.connect(josh).mint(usdc.address, usdcUnits("22.0"), 0); - - await vault.connect(governor).setDefaultStrategy(compoundStrategy.address); - await vault.connect(governor).allocate(); - - // Josh had 1000 USDC but used 100 USDC to mint OUSD in the fixture - await expect(josh).has.an.approxBalanceOf("878.0", usdc, "Josh has less"); - - // Verify the deposit went to Compound (as well as existing Vault assets) - expect(await compoundStrategy.checkBalance(usdc.address)).to.approxEqual( - usdcUnits("222") - ); - - expect(await vault.totalValue()).to.approxEqual( - utils.parseUnits("222", 18) - ); - }); - - it("Should calculate the balance correct with USDC in Vault and USDC in Compound strategy", async () => { - const { usdc, vault, matt, anna, governor, compoundStrategy } = fixture; - - expect(await vault.totalValue()).to.approxEqual( - utils.parseUnits("200", 18) - ); - - await vault.connect(governor).setDefaultStrategy(compoundStrategy.address); - await vault.connect(governor).allocate(); - // Existing 200 also ends up in strategy due to allocate call - expect(await compoundStrategy.checkBalance(usdc.address)).to.approxEqual( - usdcUnits("200") - ); - // Matt deposits USDC, 6 decimals - await usdc.connect(matt).approve(vault.address, usdcUnits("8.0")); - await vault.connect(matt).mint(usdc.address, usdcUnits("8.0"), 0); - await vault.connect(governor).allocate(); - expect(await compoundStrategy.checkBalance(usdc.address)).to.approxEqual( - usdcUnits("208.0") - ); - // Anna deposits USDC that will stay in the Vault, 6 decimals - await usdc.connect(anna).approve(vault.address, usdcUnits("10.0")); - await vault.connect(anna).mint(usdc.address, usdcUnits("10.0"), 0); - expect(await usdc.balanceOf(vault.address)).to.approxEqual( - usdcUnits("10.0") - ); - - expect(await vault.totalValue()).to.approxEqual( - utils.parseUnits("218", 18) - ); - }); - - it("Should correctly rebase with changes in Compound exchange rates", async () => { - // Mocks can't handle increasing time - if (!isFork) return; - - const { vault, matt, usds, governor } = fixture; - - await expect(await vault.totalValue()).to.equal( - utils.parseUnits("200", 18) - ); - await usds.connect(matt).approve(vault.address, usdsUnits("100")); - await vault.connect(matt).mint(usds.address, usdsUnits("100"), 0); - - await vault.connect(governor).allocate(); - - await expect(await vault.totalValue()).to.approxEqual( - utils.parseUnits("300", 18) - ); - - // Advance one year - await advanceTime(365 * 24 * 24 * 60); - - // Rebase OUSD - await vault.rebase(); - - // Expect a yield > 2% - await expect(await vault.totalValue()).gt(utils.parseUnits("306", 18)); - }); - - it("Should correctly withdrawAll all assets in Compound strategy", async () => { - const { usdc, vault, matt, compoundStrategy, governor } = fixture; - - expect(await vault.totalValue()).to.approxEqual( - utils.parseUnits("200", 18) - ); - - // Matt deposits USDC, 6 decimals - await usdc.connect(matt).approve(vault.address, usdcUnits("8.0")); - await vault.connect(matt).mint(usdc.address, usdcUnits("8.0"), 0); - - await vault.connect(governor).setDefaultStrategy(compoundStrategy.address); - await vault.connect(governor).allocate(); - - expect(await compoundStrategy.checkBalance(usdc.address)).to.approxEqual( - usdcUnits("208.0") - ); - expect(await vault.totalValue()).to.approxEqual( - utils.parseUnits("208", 18) - ); - await compoundStrategy.connect(governor).withdrawAll(); - - // There should be no USDS or USDC left in compound strategy - expect(await compoundStrategy.checkBalance(usdc.address)).to.equal(0); - - // Vault value should remain the same because the liquidattion sent the - // assets back to the vault - expect(await vault.totalValue()).to.approxEqual( - utils.parseUnits("208", 18) - ); - }); - - it("Should withdrawAll assets in Strategy and return them to Vault on removal", async () => { - const { usdc, vault, matt, compoundStrategy, governor } = fixture; - - expect(await vault.totalValue()).to.approxEqual( - utils.parseUnits("200", 18) - ); - - // Matt deposits USDC, 6 decimals - await usdc.connect(matt).approve(vault.address, usdcUnits("8.0")); - await vault.connect(matt).mint(usdc.address, usdcUnits("8.0"), 0); - - await vault.connect(governor).setDefaultStrategy(compoundStrategy.address); - await vault.connect(governor).allocate(); - - expect(await compoundStrategy.checkBalance(usdc.address)).to.approxEqual( - usdcUnits("208.0") - ); - - expect(await vault.totalValue()).to.approxEqual( - utils.parseUnits("208", 18) - ); - - expect(await vault.getStrategyCount()).to.equal(1); - - await vault - .connect(governor) - .setDefaultStrategy("0x0000000000000000000000000000000000000000"); - await vault.connect(governor).removeStrategy(compoundStrategy.address); - - expect(await vault.getStrategyCount()).to.equal(0); - // Vault value should remain the same because the liquidattion sent the - // assets back to the vault - expect(await vault.totalValue()).to.approxEqual( - utils.parseUnits("208", 18) - ); - - // Should be able to add Strategy back. Proves the struct in the mapping - // was updated i.e. isSupported set to false - await vault.connect(governor).approveStrategy(compoundStrategy.address); - }); - - it("Should not alter balances after an asset price change", async () => { - let { ousd, vault, matt, usdc } = fixture; - - await usdc.connect(matt).approve(vault.address, usdcUnits("200")); - await vault.connect(matt).mint(usdc.address, usdcUnits("200"), 0); - - // 200 OUSD was already minted in the fixture, 100 each for Matt and Josh - await expectApproxSupply(ousd, ousdUnits("400.0")); - // 100 + 200 = 300 - await expect(matt).has.an.approxBalanceOf("300", ousd, "Initial"); - - await setOracleTokenPriceUsd("USDC", "1.30"); - await vault.rebase(); - - await expectApproxSupply(ousd, ousdUnits("400.0")); - await expect(matt).has.an.approxBalanceOf( - "300.00", - ousd, - "After some assets double" - ); - - await setOracleTokenPriceUsd("USDC", "1.00"); - await vault.rebase(); - - await expectApproxSupply(ousd, ousdUnits("400.0")); - await expect(matt).has.an.approxBalanceOf( - "300.00", - ousd, - "After assets go back" - ); - }); - - it("Should never allocate anything when Vault buffer is 1e18 (100%)", async () => { - const { usds, vault, governor, compoundStrategy } = fixture; - - await expect(await vault.getStrategyCount()).to.equal(1); - - // Set a Vault buffer and allocate - await vault.connect(governor).setVaultBuffer(utils.parseUnits("1", 18)); - await vault.allocate(); - - // Verify that nothing went to compound - await expect(await compoundStrategy.checkBalance(usds.address)).to.equal(0); - }); - - it("Should allocate correctly with USDC when Vault buffer is 1e17 (10%)", async () => { - const { usdc, vault, governor, compoundStrategy } = await loadFixture( - compoundVaultFixture - ); - - expect(await vault.getStrategyCount()).to.equal(1); - - // Set a Vault buffer and allocate - await vault.connect(governor).setVaultBuffer(utils.parseUnits("1", 17)); - await vault.connect(governor).setDefaultStrategy(compoundStrategy.address); - await vault.allocate(); - - // Verify 80% went to Compound - await expect( - await compoundStrategy.checkBalance(usdc.address) - ).to.approxEqual(usdcUnits("180")); - // Remaining 20 should be in Vault - await expect(await vault.totalValue()).to.approxEqual(ousdUnits("200")); - }); - - it("Should allow transfer of arbitrary token by Governor", async () => { - const { vault, compoundStrategy, ousd, usdc, matt, governor } = fixture; - - // Matt deposits USDC, 6 decimals - await usdc.connect(matt).approve(vault.address, usdcUnits("8.0")); - await vault.connect(matt).mint(usdc.address, usdcUnits("8.0"), 0); - // Matt sends his OUSD directly to Strategy - await ousd - .connect(matt) - .transfer(compoundStrategy.address, ousdUnits("8.0")); - // Matt asks Governor for help - await compoundStrategy - .connect(governor) - .transferToken(ousd.address, ousdUnits("8.0")); - await expect(governor).has.a.balanceOf("8.0", ousd); - }); - - it("Should not allow transfer of arbitrary token by non-Governor", async () => { - const { compoundStrategy, ousd, matt } = fixture; - - // Naughty Matt - await expect( - compoundStrategy - .connect(matt) - .transferToken(ousd.address, ousdUnits("8.0")) - ).to.be.revertedWith("Caller is not the Governor"); - }); - - it("Should have correct balances on consecutive mint and redeem", async () => { - const { ousd, vault, usdc, anna, matt, josh, governor } = fixture; - - const testCases = [ - { user: anna, start: 0 }, - { user: matt, start: 100 }, - { user: josh, start: 100 }, - ]; - const amounts = [5.09, 10.32, 20.99, 100.01]; - - for (const { user, start } of testCases) { - for (const amount of amounts) { - const mintAmount = usdcUnits(amount.toString()); - await usdc.connect(user).approve(vault.address, mintAmount); - await vault.connect(user).mint(usdc.address, mintAmount, 0); - await expect(user).has.an.approxBalanceOf( - (start + amount).toString(), - ousd - ); - await vault.connect(governor).setStrategistAddr(user.address); - await vault - .connect(user) - .requestWithdrawal(ousdUnits(amount.toString())); - await expect(user).has.an.approxBalanceOf(start.toString(), ousd); - } - } - }); - - const mintDoesAllocate = async (amount) => { - const { anna, vault, usdc, governor, compoundStrategy } = fixture; - - await vault.connect(governor).setDefaultStrategy(compoundStrategy.address); - await vault.connect(governor).setVaultBuffer(0); - await vault.allocate(); - await usdc.connect(anna).mint(usdcUnits(amount)); - await usdc.connect(anna).approve(vault.address, usdcUnits(amount)); - await vault.connect(anna).mint(usdc.address, usdcUnits(amount), 0); - return (await usdc.balanceOf(vault.address)).isZero(); - }; - - const setThreshold = async (amount) => { - const { vault, governor } = fixture; - await vault.connect(governor).setAutoAllocateThreshold(ousdUnits(amount)); - }; - - it("Triggers auto allocation at the threshold", async () => { - await setThreshold("25000"); - expect(await mintDoesAllocate("25000")).to.be.true; - }); - - it("Alloc with both threshold and buffer", async () => { - const { anna, vault, usdc, usds, governor, compoundStrategy } = fixture; - - await vault.connect(governor).setDefaultStrategy(compoundStrategy.address); - await vault.allocate(); - await vault.connect(governor).setVaultBuffer(utils.parseUnits("1", 17)); - await vault.connect(governor).setAutoAllocateThreshold(ousdUnits("3")); - - const amount = "4"; - await usdc.connect(anna).mint(usdcUnits(amount)); - await usdc.connect(anna).approve(vault.address, usdcUnits(amount)); - await vault.connect(anna).mint(usdc.address, usdcUnits(amount), 0); - // No allocate triggered due to threshold so call manually - await vault.allocate(); - - // 5 should be below the 10% vault buffer (4/204 * 100 = 1.96%) - // All funds should remain in vault - expect(await usdc.balanceOf(vault.address)).to.equal(usdcUnits(amount)); - // USDS was allocated before the vault buffer was set - expect(await usds.balanceOf(vault.address)).to.equal(usdsUnits("0")); - - // Use an amount above the vault buffer size that will trigger an allocate - const allocAmount = "5000"; - await usdc.connect(anna).mint(usdcUnits(allocAmount)); - await usdc.connect(anna).approve(vault.address, usdcUnits(allocAmount)); - await vault.connect(anna).mint(usdc.address, usdcUnits(allocAmount), 0); - - // We should take 10% off for the buffer - // 10% * 5204 - await expect(await usdc.balanceOf(vault.address)).to.equal( - usdcUnits("520.4") - ); - - const minAmount = "0.000001"; - await usdc.connect(anna).mint(usdcUnits(minAmount)); - await usdc.connect(anna).approve(vault.address, usdcUnits(minAmount)); - await vault.connect(anna).mint(usdc.address, usdcUnits(minAmount), 0); - - //alloc should not crash here - await expect(vault.allocate()).not.to.be.reverted; - }); - - it("Triggers auto allocation above the threshold", async () => { - await setThreshold("25000"); - expect(await mintDoesAllocate("25001")).to.be.true; - }); - - it("Does not trigger auto allocation below the threshold", async () => { - await setThreshold("25000"); - expect(await mintDoesAllocate("24999")).to.be.false; - }); - - it("Governor can change the threshold", async () => { - await setThreshold("25000"); - }); - - it("Non-governor cannot change the threshold", async () => { - const { vault, anna } = fixture; - await expect(vault.connect(anna).setAutoAllocateThreshold(10000)).to.be - .reverted; - }); -}); diff --git a/contracts/test/vault/harvester.js b/contracts/test/vault/harvester.js deleted file mode 100644 index aecda3bb4d..0000000000 --- a/contracts/test/vault/harvester.js +++ /dev/null @@ -1,28 +0,0 @@ -const { createFixtureLoader, harvesterFixture } = require("./../_fixture"); -const { shouldBehaveLikeHarvester } = require("../behaviour/harvester"); - -const loadFixture = createFixtureLoader(harvesterFixture); - -describe("Harvester", function () { - let fixture; - - beforeEach(async () => { - fixture = await loadFixture(); - }); - - shouldBehaveLikeHarvester(() => ({ - ...fixture, - harvester: fixture.harvester, - strategies: [ - { - strategy: fixture.compoundStrategy, - rewardTokens: [fixture.comp], - }, - { - strategy: fixture.aaveStrategy, - rewardTokens: [fixture.aaveToken], - }, - ], - rewardProceedsAddress: fixture.vault.address, - })); -}); diff --git a/contracts/test/vault/harvester.mainnet.fork-test.js b/contracts/test/vault/harvester.mainnet.fork-test.js deleted file mode 100644 index 1b6d0887c5..0000000000 --- a/contracts/test/vault/harvester.mainnet.fork-test.js +++ /dev/null @@ -1,307 +0,0 @@ -const { expect } = require("chai"); -const { utils, BigNumber } = require("ethers"); - -const { createFixtureLoader, harvesterFixture } = require("./../_fixture"); -const { isCI, oethUnits } = require("./../helpers"); -const addresses = require("../../utils/addresses"); -const { setERC20TokenBalance } = require("../_fund"); -const { parseUnits } = require("ethers").utils; - -const loadFixture = createFixtureLoader(harvesterFixture); - -describe("ForkTest: Harvester", function () { - this.timeout(0); - - // Retry up to 3 times on CI - this.retries(isCI ? 3 : 0); - - let fixture; - beforeEach(async () => { - fixture = await loadFixture(); - }); - - // Skipping this since we switched to simple harvester - describe.skip("with Curve", () => { - it("Should swap CRV for WETH", async () => { - const { - oethHarvester, - strategist, - convexEthMetaStrategy, - oethDripper, - crv, - weth, - } = fixture; - const wethBefore = await weth.balanceOf(oethDripper.address); - - // Send some rewards to the strategy - await setERC20TokenBalance(convexEthMetaStrategy.address, crv, "2000"); - - // prettier-ignore - const tx = await oethHarvester - .connect(strategist)["harvestAndSwap(address)"](convexEthMetaStrategy.address); - - await expect(tx).to.emit(convexEthMetaStrategy, "RewardTokenCollected"); - await expect(tx).to.emit(oethHarvester, "RewardTokenSwapped"); - await expect(tx).to.emit(oethHarvester, "RewardProceedsTransferred"); - - // Should've transferred swapped WETH to Dripper - expect(await weth.balanceOf(oethDripper.address)).to.be.gt( - wethBefore.add(oethUnits("0.1")) - ); - }); - }); - - // Commenting this out since none of the strategies have anything - // that we want to harvest on Uniswap V2 or V3 right now. - - // describe("with Uniswap V3", () => { - // it("Should swap CRV for USDT", async () => { - // const { harvester, timelock, crv } = fixture; - // const crvBefore = await crv.balanceOf(harvester.address); - - // await harvester.connect(timelock).swapRewardToken(crv.address); - - // expect(await crv.balanceOf(harvester.address)).to.equal( - // crvBefore.sub(oethUnits("4000")) - // ); - // }); - // }); - - describe.skip("with Balancer", () => { - it("Should swap BAL and AURA for WETH", async () => { - const { - oethHarvester, - strategist, - bal, - aura, - weth, - oethDripper, - balancerREthStrategy, - } = fixture; - - const wethBefore = await weth.balanceOf(oethDripper.address); - - // Send some rewards to the strategy - await setERC20TokenBalance(balancerREthStrategy.address, bal, "1000"); - await setERC20TokenBalance(balancerREthStrategy.address, aura, "1000"); - - // prettier-ignore - const tx = await oethHarvester - .connect(strategist)["harvestAndSwap(address)"](balancerREthStrategy.address); - - await expect(tx).to.emit(balancerREthStrategy, "RewardTokenCollected"); - await expect(tx).to.emit(oethHarvester, "RewardTokenSwapped"); - await expect(tx).to.emit(oethHarvester, "RewardProceedsTransferred"); - - // Should've transferred everything to Harvester - expect(await bal.balanceOf(balancerREthStrategy.address)).to.equal("0"); - expect(await aura.balanceOf(balancerREthStrategy.address)).to.equal("0"); - - // Should've transferred swapped WETH to Dripper - expect(await weth.balanceOf(oethDripper.address)).to.be.gt( - wethBefore.add(oethUnits("0.1")) - ); - }); - }); - - describe("OUSD Rewards Config", () => { - it("Should have correct reward token config for CRV", async () => { - const { harvester, crv } = fixture; - - const config = await harvester.rewardTokenConfigs(crv.address); - - expect(config.allowedSlippageBps).to.equal(300); - expect(config.harvestRewardBps).to.equal(200); - expect(config.swapPlatform).to.equal( - 1 // Uniswap V3 - ); - expect(config.swapPlatformAddr).to.equal( - "0xE592427A0AEce92De3Edee1F18E0157C05861564" - ); - expect(config.doSwapRewardToken).to.be.true; - expect(config.liquidationLimit).to.equal(parseUnits("4000", 18)); - expect(await harvester.uniswapV3Path(crv.address)).to.eq( - utils.solidityPack( - ["address", "uint24", "address", "uint24", "address"], - [ - addresses.mainnet.CRV, - 3000, - addresses.mainnet.WETH, - 500, - addresses.mainnet.USDT, - ] - ) - ); - }); - - it("Should have correct reward token config for CVX", async () => { - const { harvester, cvx } = fixture; - - const config = await harvester.rewardTokenConfigs(cvx.address); - - expect(config.allowedSlippageBps).to.equal(300); - expect(config.harvestRewardBps).to.equal(100); - expect(config.swapPlatform).to.equal( - 1 // Uniswap V3 - ); - expect(config.swapPlatformAddr).to.equal( - "0xE592427A0AEce92De3Edee1F18E0157C05861564" - ); - expect(config.doSwapRewardToken).to.be.true; - expect(config.liquidationLimit).to.equal(utils.parseEther("2500")); - expect(await harvester.uniswapV3Path(cvx.address)).to.eq( - utils.solidityPack( - ["address", "uint24", "address", "uint24", "address"], - [ - addresses.mainnet.CVX, - 10000, - addresses.mainnet.WETH, - 500, - addresses.mainnet.USDT, - ] - ) - ); - }); - - it("Should have correct reward token config for COMP", async () => { - const { harvester, comp } = fixture; - - const config = await harvester.rewardTokenConfigs(comp.address); - - expect(config.allowedSlippageBps).to.equal(300); - expect(config.harvestRewardBps).to.equal(100); - expect(config.swapPlatform).to.equal( - 1 // Uniswap V3 - ); - expect(config.swapPlatformAddr).to.equal( - "0xE592427A0AEce92De3Edee1F18E0157C05861564" - ); - expect(config.doSwapRewardToken).to.be.true; - expect(config.liquidationLimit).to.equal(0); - expect((await harvester.uniswapV3Path(comp.address)).toLowerCase()).to.eq( - utils.solidityPack( - ["address", "uint24", "address", "uint24", "address"], - [ - addresses.mainnet.COMP, - 3000, - addresses.mainnet.WETH, - 500, - addresses.mainnet.USDT, - ] - ) - ); - }); - - it("Should have correct reward token config for AAVE", async () => { - const { harvester, aave } = fixture; - - const config = await harvester.rewardTokenConfigs(aave.address); - - expect(config.allowedSlippageBps).to.equal(300); - expect(config.harvestRewardBps).to.equal(100); - expect(config.swapPlatform).to.equal( - 1 // Uniswap V3 - ); - expect(config.swapPlatformAddr).to.equal( - "0xE592427A0AEce92De3Edee1F18E0157C05861564" - ); - expect(config.doSwapRewardToken).to.be.true; - expect(config.liquidationLimit).to.equal(0); - - expect((await harvester.uniswapV3Path(aave.address)).toLowerCase()).to.eq( - utils.solidityPack( - ["address", "uint24", "address", "uint24", "address"], - [ - addresses.mainnet.Aave, - 10000, - addresses.mainnet.WETH, - 500, - addresses.mainnet.USDT, - ] - ) - ); - }); - }); - - describe("OETH Rewards Config", () => { - it("Should have correct reward token config for CRV", async () => { - const { oethHarvester, crv } = fixture; - - const config = await oethHarvester.rewardTokenConfigs(crv.address); - - expect(config.allowedSlippageBps).to.equal(300); - expect(config.harvestRewardBps).to.equal(200); - expect(config.swapPlatform).to.equal( - 3 // Curve - ); - expect(config.swapPlatformAddr).to.equal( - "0x4eBdF703948ddCEA3B11f675B4D1Fba9d2414A14" - ); - expect(config.doSwapRewardToken).to.be.true; - expect(config.liquidationLimit).to.equal(parseUnits("4000", 18)); - const indices = await oethHarvester.curvePoolIndices(crv.address); - expect(indices[0]).to.eq(BigNumber.from("2")); - expect(indices[1]).to.eq(BigNumber.from("1")); - }); - - it("Should have correct reward token config for CVX", async () => { - const { oethHarvester, cvx } = fixture; - - const config = await oethHarvester.rewardTokenConfigs(cvx.address); - - expect(config.allowedSlippageBps).to.equal(300); - expect(config.harvestRewardBps).to.equal(200); - expect(config.swapPlatform).to.equal( - 3 // Curve - ); - expect(config.swapPlatformAddr).to.equal( - "0xB576491F1E6e5E62f1d8F26062Ee822B40B0E0d4" - ); - expect(config.doSwapRewardToken).to.be.true; - expect(config.liquidationLimit).to.equal(parseUnits("2500", 18)); - const indices = await oethHarvester.curvePoolIndices(cvx.address); - expect(indices[0]).to.eq(BigNumber.from("1")); - expect(indices[1]).to.eq(BigNumber.from("0")); - }); - - it("Should have correct reward token config for BAL", async () => { - const { oethHarvester, bal } = fixture; - - const config = await oethHarvester.rewardTokenConfigs(bal.address); - - expect(config.allowedSlippageBps).to.equal(300); - expect(config.harvestRewardBps).to.equal(200); - expect(config.swapPlatform).to.equal( - 2 // Balancer - ); - expect(config.swapPlatformAddr).to.equal( - "0xBA12222222228d8Ba445958a75a0704d566BF2C8" - ); - expect(config.doSwapRewardToken).to.be.true; - expect(config.liquidationLimit).to.equal(parseUnits("1000", 18)); - expect(await oethHarvester.balancerPoolId(bal.address)).to.equal( - "0x5c6ee304399dbdb9c8ef030ab642b10820db8f56000200000000000000000014" - ); - }); - - it("Should have correct reward token config for AURA", async () => { - const { oethHarvester, aura } = fixture; - - const config = await oethHarvester.rewardTokenConfigs(aura.address); - - expect(config.allowedSlippageBps).to.equal(300); - expect(config.harvestRewardBps).to.equal(200); - expect(config.swapPlatform).to.equal( - 2 // Balancer - ); - expect(config.swapPlatformAddr).to.equal( - "0xBA12222222228d8Ba445958a75a0704d566BF2C8" - ); - expect(config.doSwapRewardToken).to.be.true; - expect(config.liquidationLimit).to.equal(parseUnits("4000", 18)); - expect(await oethHarvester.balancerPoolId(aura.address)).to.equal( - "0xcfca23ca9ca720b6e98e3eb9b6aa0ffc4a5c08b9000200000000000000000274" - ); - }); - }); -}); diff --git a/contracts/test/vault/index.js b/contracts/test/vault/index.js index e0b0ea44a4..86afdf8bfa 100644 --- a/contracts/test/vault/index.js +++ b/contracts/test/vault/index.js @@ -11,9 +11,6 @@ describe("Vault", function () { let fixture; beforeEach(async () => { fixture = await loadDefaultFixture(); - await fixture.compoundStrategy - .connect(fixture.governor) - .setPTokenAddress(fixture.usdc.address, fixture.cusdc.address); }); it("Should support an asset", async () => { @@ -24,19 +21,19 @@ describe("Vault", function () { }); it("Should revert when adding a strategy that is already approved", async function () { - const { vault, governor, compoundStrategy } = fixture; + const { vault, governor, mockStrategy } = fixture; - await vault.connect(governor).approveStrategy(compoundStrategy.address); + await vault.connect(governor).approveStrategy(mockStrategy.address); await expect( - vault.connect(governor).approveStrategy(compoundStrategy.address) + vault.connect(governor).approveStrategy(mockStrategy.address) ).to.be.revertedWith("Strategy already approved"); }); it("Should revert when attempting to approve a strategy and not Governor", async function () { - const { vault, josh, compoundStrategy } = fixture; + const { vault, josh, mockStrategy } = fixture; await expect( - vault.connect(josh).approveStrategy(compoundStrategy.address) + vault.connect(josh).approveStrategy(mockStrategy.address) ).to.be.revertedWith("Caller is not the Governor"); }); @@ -180,11 +177,11 @@ describe("Vault", function () { }); it("Should allow the Governor to call withdraw and then deposit", async () => { - const { vault, governor, usdc, josh, compoundStrategy } = fixture; + const { vault, governor, usdc, josh, mockStrategy } = fixture; - await vault.connect(governor).approveStrategy(compoundStrategy.address); + await vault.connect(governor).approveStrategy(mockStrategy.address); // Send all USDC to Compound - await vault.connect(governor).setDefaultStrategy(compoundStrategy.address); + await vault.connect(governor).setDefaultStrategy(mockStrategy.address); await usdc.connect(josh).approve(vault.address, usdcUnits("200")); await vault.connect(josh).mint(usdc.address, usdcUnits("200"), 0); await vault.connect(governor).allocate(); @@ -192,7 +189,7 @@ describe("Vault", function () { await vault .connect(governor) .withdrawFromStrategy( - compoundStrategy.address, + mockStrategy.address, [usdc.address], [usdcUnits("200")] ); @@ -200,19 +197,18 @@ describe("Vault", function () { await vault .connect(governor) .depositToStrategy( - compoundStrategy.address, + mockStrategy.address, [usdc.address], [usdcUnits("200")] ); }); it("Should allow the Strategist to call withdrawFromStrategy and then depositToStrategy", async () => { - const { vault, governor, usdc, josh, strategist, compoundStrategy } = - fixture; + const { vault, governor, usdc, josh, strategist, mockStrategy } = fixture; - await vault.connect(governor).approveStrategy(compoundStrategy.address); + await vault.connect(governor).approveStrategy(mockStrategy.address); // Send all USDC to Compound - await vault.connect(governor).setDefaultStrategy(compoundStrategy.address); + await vault.connect(governor).setDefaultStrategy(mockStrategy.address); await usdc.connect(josh).approve(vault.address, usdcUnits("200")); await vault.connect(josh).mint(usdc.address, usdcUnits("200"), 0); await vault.connect(governor).allocate(); @@ -220,7 +216,7 @@ describe("Vault", function () { await vault .connect(strategist) .withdrawFromStrategy( - compoundStrategy.address, + mockStrategy.address, [usdc.address], [usdcUnits("200")] ); @@ -228,7 +224,7 @@ describe("Vault", function () { await vault .connect(strategist) .depositToStrategy( - compoundStrategy.address, + mockStrategy.address, [usdc.address], [usdcUnits("200")] ); @@ -255,13 +251,12 @@ describe("Vault", function () { }); it("Should withdrawFromStrategy the correct amount for multiple assests and redeploy them using depositToStrategy", async () => { - const { vault, governor, usdc, josh, strategist, compoundStrategy } = - fixture; + const { vault, governor, usdc, josh, strategist, mockStrategy } = fixture; - await vault.connect(governor).approveStrategy(compoundStrategy.address); + await vault.connect(governor).approveStrategy(mockStrategy.address); // Send all USDC to Compound - await vault.connect(governor).setDefaultStrategy(compoundStrategy.address); + await vault.connect(governor).setDefaultStrategy(mockStrategy.address); await usdc.connect(josh).approve(vault.address, usdcUnits("90")); await vault.connect(josh).mint(usdc.address, usdcUnits("90"), 0); @@ -270,7 +265,7 @@ describe("Vault", function () { await vault .connect(strategist) .withdrawFromStrategy( - compoundStrategy.address, + mockStrategy.address, [usdc.address], [usdcUnits("90")] ); @@ -284,7 +279,7 @@ describe("Vault", function () { await vault .connect(strategist) .depositToStrategy( - compoundStrategy.address, + mockStrategy.address, [usdc.address], [usdcUnits("90")] ); @@ -324,23 +319,24 @@ describe("Vault", function () { }); it("Should only allow Governor and Strategist to call withdrawAllFromStrategy", async () => { - const { vault, governor, strategist, compoundStrategy, matt, josh, usdc } = + const { vault, governor, strategist, mockStrategy, matt, josh, usdc } = fixture; - await vault.connect(governor).approveStrategy(compoundStrategy.address); + await vault.connect(governor).approveStrategy(mockStrategy.address); + await mockStrategy + .connect(governor) + .setWithdrawAll(usdc.address, vault.address); // Get the vault's initial USDC balance. const vaultUsdcBalance = await usdc.balanceOf(vault.address); // Mint and allocate USDC to Compound. - await vault.connect(governor).setDefaultStrategy(compoundStrategy.address); + await vault.connect(governor).setDefaultStrategy(mockStrategy.address); await usdc.connect(josh).approve(vault.address, usdcUnits("200")); await vault.connect(josh).mint(usdc.address, usdcUnits("200"), 0); await vault.connect(governor).allocate(); // Call to withdrawAll by the governor should go thru. - await vault - .connect(governor) - .withdrawAllFromStrategy(compoundStrategy.address); + await vault.connect(governor).withdrawAllFromStrategy(mockStrategy.address); // All the USDC should have been moved back to the vault. const expectedVaultUsdsBalance = vaultUsdcBalance.add(usdcUnits("200")); @@ -351,11 +347,11 @@ describe("Vault", function () { // Call to withdrawAll by the strategist should go thru. await vault .connect(strategist) - .withdrawAllFromStrategy(compoundStrategy.address); + .withdrawAllFromStrategy(mockStrategy.address); // Call to withdrawAll from random dude matt should get rejected. await expect( - vault.connect(matt).withdrawAllFromStrategy(compoundStrategy.address) + vault.connect(matt).withdrawAllFromStrategy(mockStrategy.address) ).to.be.revertedWith("Caller is not the Strategist or Governor"); }); }); diff --git a/contracts/test/vault/oeth-vault.mainnet.fork-test.js b/contracts/test/vault/oeth-vault.mainnet.fork-test.js index 4e144a48b3..b62f977477 100644 --- a/contracts/test/vault/oeth-vault.mainnet.fork-test.js +++ b/contracts/test/vault/oeth-vault.mainnet.fork-test.js @@ -25,23 +25,9 @@ describe("ForkTest: OETH Vault", function () { describe("post deployment", () => { it("Should have the correct governor address set", async () => { - const { - oethVault, - oethDripper, - convexEthMetaStrategy, - oeth, - woeth, - oethHarvester, - } = fixture; - - const oethContracts = [ - oethVault, - oethDripper, - convexEthMetaStrategy, - oeth, - woeth, - oethHarvester, - ]; + const { oethVault, oeth, woeth, oethHarvester } = fixture; + + const oethContracts = [oethVault, oeth, woeth, oethHarvester]; for (let i = 0; i < oethContracts.length; i++) { expect(await oethContracts[i].governor()).to.equal( @@ -115,26 +101,6 @@ describe("ForkTest: OETH Vault", function () { .withArgs(josh.address, amount); }); - it("should mint when specifying any other assets", async () => { - const { oethVault, frxETH, stETH, reth, weth, josh } = fixture; - - const amount = parseUnits("1", 18); - const minOeth = parseUnits("0.8", 18); - - for (const asset of [frxETH, stETH, reth]) { - const wethBefore = await weth.balanceOf(josh.address); - const assetBefore = await asset.balanceOf(josh.address); - - // Mints with WETH even though other assets are specified - await oethVault.connect(josh).mint(asset.address, amount, minOeth); - - expect(await weth.balanceOf(josh.address)).to.eq( - wethBefore.sub(amount) - ); - expect(await asset.balanceOf(josh.address)).to.eq(assetBefore); - } - }); - it("should request a withdraw by OETH whale", async () => { const { oeth, oethVault } = fixture; diff --git a/contracts/test/vault/rebase.js b/contracts/test/vault/rebase.js index c2aff4e085..d21d87d5bb 100644 --- a/contracts/test/vault/rebase.js +++ b/contracts/test/vault/rebase.js @@ -1,12 +1,7 @@ const { expect } = require("chai"); const { loadDefaultFixture } = require("../_fixture"); -const { - ousdUnits, - usdcUnits, - setOracleTokenPriceUsd, - expectApproxSupply, -} = require("../helpers"); +const { ousdUnits, usdcUnits, expectApproxSupply } = require("../helpers"); describe("Vault rebase", () => { let fixture; @@ -68,59 +63,6 @@ describe("Vault rebase", () => { }); describe("Vault rebasing", async () => { - it("Should not alter balances after an asset price change", async () => { - const { ousd, vault, matt } = fixture; - - await expect(matt).has.a.balanceOf("100.00", ousd); - await vault.rebase(); - await expect(matt).has.a.balanceOf("100.00", ousd); - await setOracleTokenPriceUsd("USDS", "1.30"); - - await vault.rebase(); - await expect(matt).has.a.approxBalanceOf("100.00", ousd); - await setOracleTokenPriceUsd("USDS", "1.00"); - await vault.rebase(); - await expect(matt).has.a.balanceOf("100.00", ousd); - }); - - it("Should not alter balances after an asset price change, single", async () => { - const { ousd, vault, matt } = fixture; - - await expect(matt).has.a.balanceOf("100.00", ousd); - await vault.rebase(); - await expect(matt).has.a.balanceOf("100.00", ousd); - await setOracleTokenPriceUsd("USDS", "1.30"); - await vault.rebase(); - await expect(matt).has.a.approxBalanceOf("100.00", ousd); - await setOracleTokenPriceUsd("USDS", "1.00"); - await vault.rebase(); - await expect(matt).has.a.balanceOf("100.00", ousd); - }); - - it("Should not alter balances after an asset price change with multiple assets", async () => { - const { ousd, vault, matt, usdc } = fixture; - - await usdc.connect(matt).approve(vault.address, usdcUnits("200")); - await vault.connect(matt).mint(usdc.address, usdcUnits("200"), 0); - expect(await ousd.totalSupply()).to.eq(ousdUnits("400.0")); - await expect(matt).has.a.balanceOf("300.00", ousd); - await vault.rebase(); - await expect(matt).has.a.balanceOf("300.00", ousd); - - await setOracleTokenPriceUsd("USDS", "1.30"); - await vault.rebase(); - expect(await ousd.totalSupply()).to.eq(ousdUnits("400.0")); - await expect(matt).has.an.approxBalanceOf("300.00", ousd); - - await setOracleTokenPriceUsd("USDS", "1.00"); - await vault.rebase(); - expect(await ousd.totalSupply()).to.eq( - ousdUnits("400.0"), - "After assets go back" - ); - await expect(matt).has.a.balanceOf("300.00", ousd); - }); - it("Should alter balances after supported asset deposited and rebase called for rebasing accounts", async () => { const { ousd, vault, matt, usdc, josh } = fixture; @@ -181,8 +123,6 @@ describe("Vault rebase", () => { const { anna, ousd, usdc, vault } = fixture; await expect(anna).has.a.balanceOf("0", ousd); - // The price should be limited by the code to $1 - await setOracleTokenPriceUsd("USDC", "1.20"); await usdc.connect(anna).approve(vault.address, usdcUnits("50")); await vault.connect(anna).mint(usdc.address, usdcUnits("50"), 0); await expect(anna).has.a.balanceOf("50", ousd); diff --git a/contracts/test/vault/vault.mainnet.fork-test.js b/contracts/test/vault/vault.mainnet.fork-test.js index afeaccf5a8..81a6b77ca1 100644 --- a/contracts/test/vault/vault.mainnet.fork-test.js +++ b/contracts/test/vault/vault.mainnet.fork-test.js @@ -124,6 +124,9 @@ describe("ForkTest: Vault", function () { it("should withdraw from and deposit to strategy", async () => { const { vault, josh, usdc, morphoOUSDv2Strategy } = fixture; + // Mint a lot more in case there are outstanding withdrawals + await vault.connect(josh).mint(usdc.address, usdcUnits("500000"), 0); + // The next mint should all be deposited into the strategy await vault.connect(josh).mint(usdc.address, usdcUnits("90"), 0); const strategistSigner = await impersonateAndFund( await vault.strategistAddr() @@ -163,21 +166,20 @@ describe("ForkTest: Vault", function () { [usdc.address], [morphoOUSDv2Strategy], async () => { - await vault - .connect(strategistSigner) - .withdrawFromStrategy( - morphoOUSDv2Strategy.address, - [usdc.address], - [usdcUnits("90")] - ); + await vault.connect(strategistSigner).withdrawFromStrategy( + morphoOUSDv2Strategy.address, + [usdc.address], + // Can not always get the full 90 back out due to Morpho utilization + [usdcUnits("89")] + ); } ); } ); - expect(usdcBalanceDiff).to.equal(usdcUnits("90")); + expect(usdcBalanceDiff).to.equal(usdcUnits("89")); - expect(usdcStratDiff).to.lte(usdcUnits("-89.91")); + expect(usdcStratDiff).to.lte(usdcUnits("-88.91")); }); it("Should have vault buffer disabled", async () => { @@ -213,6 +215,7 @@ describe("ForkTest: Vault", function () { // Update this every time a new strategy is added. Below are mainnet addresses "0x26a02ec47ACC2A3442b757F45E0A82B8e993Ce11", // Curve AMO OUSD/USDC "0x3643cafA6eF3dd7Fcc2ADaD1cabf708075AFFf6e", // Morpho OUSD v2 Strategy + "0xB1d624fc40824683e2bFBEfd19eB208DbBE00866", // Cross-Chain Strategy ]; for (const s of strategies) { diff --git a/contracts/utils/1Inch.js b/contracts/utils/1Inch.js deleted file mode 100644 index a0cc910804..0000000000 --- a/contracts/utils/1Inch.js +++ /dev/null @@ -1,148 +0,0 @@ -const axios = require("axios"); -const { defaultAbiCoder, formatUnits } = require("ethers/lib/utils"); - -const addresses = require("./addresses"); -const log = require("./logger")("utils:1inch"); - -const ONEINCH_API_ENDPOINT = "https://api.1inch.dev/swap/v5.2/1/swap"; -const ONEINCH_API_KEY = process.env.ONEINCH_API_KEY; -const SWAP_SELECTOR = "0x12aa3caf"; // swap(address,(address,address,address,address,uint256,uint256,uint256),bytes,bytes) -const UNISWAP_SELECTOR = "0xf78dc253"; // unoswapTo(address,address,uint256,uint256,uint256[]) -const UNISWAPV3_SELECTOR = "0xbc80f1a8"; // uniswapV3SwapTo(address,uint256,uint256,uint256[]) - -/** - * Re-encodes the 1Inch swap data to be used by the vault's swapper. - * The first 4 bytes are the function selector to call on 1Inch's router. - * If calling the swap function, the next 20 bytes is the executer's address and data. - * If calling the unoswap or uniswapV3SwapTo functions, an array of Uniswap pools are encoded. - * @param {string} apiEncodedData tx.data from 1inch's /v5.0/1/swap API - * @returns {string} RLP encoded data for the Vault's `swapCollateral` function - */ -const recodeSwapData = async (apiEncodedData) => { - try { - const c1InchRouter = await ethers.getContractAt( - "IOneInchRouter", - addresses.mainnet.oneInchRouterV5 - ); - - // decode the 1Inch tx.data that is RLP encoded - const swapTx = c1InchRouter.interface.parseTransaction({ - data: apiEncodedData, - }); - - // log(`parsed tx ${JSON.stringify(swapTx)}}`); - - let encodedData; - if (swapTx.sighash === SWAP_SELECTOR) { - // If swap(IAggregationExecutor executor, SwapDescription calldata desc, bytes calldata permit, bytes calldata data) - encodedData = defaultAbiCoder.encode( - ["bytes4", "address", "bytes"], - [swapTx.sighash, swapTx.args[0], swapTx.args[3]] - ); - } else if (swapTx.sighash === UNISWAP_SELECTOR) { - // If unoswapTo(address,address,uint256,uint256,uint256[]) - encodedData = defaultAbiCoder.encode( - ["bytes4", "uint256[]"], - [swapTx.sighash, swapTx.args[4]] - ); - } else if (swapTx.sighash === UNISWAPV3_SELECTOR) { - // If uniswapV3SwapTo(address,uint256,uint256,uint256[]) - encodedData = defaultAbiCoder.encode( - ["bytes4", "uint256[]"], - [swapTx.sighash, swapTx.args[3]] - ); - } else { - throw Error(`Unknown 1Inch tx signature ${swapTx.sighash}`); - } - - // log(`encoded collateral swap data ${encodedData}`); - - return encodedData; - } catch (err) { - throw Error(`Failed to recode 1Inch swap data: ${err.message}`, { - cause: err, - }); - } -}; - -/** - * Gets the tx.data in the response of 1inch's V5 swap API - * @param vault The Origin vault contract address. eg OUSD or OETH Vaults - * @param fromAsset The address of the asset to swap from. - * @param toAsset The address of the asset to swap to. - * @param fromAmount The unit amount of fromAsset to swap. eg 1.1 WETH = 1.1e18 - * @param slippage as a percentage. eg 0.5 is 0.5% - * @param protocols The 1Inch liquidity sources as a comma separated list. eg UNISWAP_V1,UNISWAP_V2,SUSHI,CURVE,ONE_INCH_LIMIT_ORDER - * See https://api.1inch.io/v5.0/1/liquidity-sources - */ -const getIInchSwapData = async ({ - vault, - fromAsset, - toAsset, - fromAmount, - slippage, - protocols, -}) => { - const swapper = await ethers.getContract("Swapper1InchV5"); - - const params = { - src: fromAsset.address, - dst: toAsset.address, - amount: fromAmount.toString(), - fromAddress: swapper.address, - receiver: vault.address, - slippage: slippage ?? 0.5, - disableEstimate: true, - allowPartialFill: false, - includeProtocols: true, - // add protocols property if it exists - ...(protocols && { protocols }), - }; - log("swap API params: ", params); - - let retries = 3; - - while (retries > 0) { - try { - const response = await axios.get(ONEINCH_API_ENDPOINT, { - params, - headers: { - Authorization: `Bearer ${ONEINCH_API_KEY}`, - }, - }); - - if (!response.data.tx || !response.data.tx.data) { - console.error(response.data); - throw Error("response is missing tx.data"); - } - - log("swap API toAmount: ", formatUnits(response.data.toAmount)); - log("swap API swap paths: ", JSON.stringify(response.data.protocols)); - // log("swap API data.protocols: ", response.data.protocols[0]); - - return response.data.tx.data; - } catch (err) { - if (err.response) { - console.error("Response data : ", err.response.data); - console.error("Response status: ", err.response.status); - } - if (err.response?.status == 429) { - retries = retries - 1; - // Wait for 2s before next try - await new Promise((r) => setTimeout(r, 2000)); - continue; - } - throw Error(`Call to 1Inch swap API failed: ${err.message}`); - } - } - - throw Error(`Call to 1Inch swap API failed: Rate-limited`); -}; - -module.exports = { - recodeSwapData, - getIInchSwapData, - SWAP_SELECTOR, - UNISWAP_SELECTOR, - UNISWAPV3_SELECTOR, -}; diff --git a/contracts/utils/addresses.js b/contracts/utils/addresses.js index 924006d4f1..605aa15473 100644 --- a/contracts/utils/addresses.js +++ b/contracts/utils/addresses.js @@ -64,9 +64,6 @@ addresses.mainnet.cUSDT = "0xf650c3d88d12db855b8bf7d11be6c55a4e07dcc9"; // Curve addresses.mainnet.CRV = "0xD533a949740bb3306d119CC777fa900bA034cd52"; addresses.mainnet.CRVMinter = "0xd061D61a4d941c39E5453435B6345Dc261C2fcE0"; -addresses.mainnet.ThreePool = "0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7"; -addresses.mainnet.ThreePoolToken = "0x6c3F90f043a72FA612cbac8115EE7e52BDe6E490"; -addresses.mainnet.ThreePoolGauge = "0xbFcF63294aD7105dEa65aA58F8AE5BE2D9d0952A"; // CVX addresses.mainnet.CVX = "0x4e3fbd56cd56c3e72c1403e103b45db9da5b9d2b"; addresses.mainnet.CVXBooster = "0xF403C135812408BFbE8713b5A23a04b3D48AAE31"; @@ -181,15 +178,18 @@ addresses.mainnet.CurveOUSDUSDTPool = addresses.mainnet.CurveOUSDUSDTGauge = "0x74231E4d96498A30FCEaf9aACCAbBD79339Ecd7f"; -// Curve OETH/ETH pool +// Old Curve OETH/ETH pool used by the Convex AMO. No longer used. addresses.mainnet.ConvexOETHAMOStrategy = "0x1827F9eA98E0bf96550b2FC20F7233277FcD7E63"; -addresses.mainnet.CurveOETHMetaPool = - "0x94B17476A93b3262d87B9a326965D1E91f9c13E7"; -addresses.mainnet.CurveOETHGauge = "0xd03BE91b1932715709e18021734fcB91BB431715"; +addresses.mainnet.ConvexOETHGauge = + "0xd03BE91b1932715709e18021734fcB91BB431715"; addresses.mainnet.CVXETHRewardsPool = "0x24b65DC1cf053A8D96872c323d29e86ec43eB33A"; +// New Curve OETH/WETH pool used by the Curve AMO +addresses.mainnet.CurveOETHAMOStrategy = + "0xba0e352AB5c13861C26e4E773e7a833C3A223FE6"; + addresses.mainnet.CurveOETHETHplusGauge = "0xCAe10a7553AccA53ad58c4EC63e3aB6Ad6546F71"; @@ -256,8 +256,6 @@ addresses.mainnet.OETHHarvesterProxy = // TODO add after deployment addresses.mainnet.OETHHarvesterSimpleProxy = "0x6D416E576eECBB9F897856a7c86007905274ed04"; -addresses.mainnet.BalancerRETHStrategy = - "0x49109629aC1deB03F2e9b2fe2aC4a623E0e7dfDC"; // OETH Tokens addresses.mainnet.sfrxETH = "0xac3E018457B222d93114458476f3E3416Abbe38F"; addresses.mainnet.frxETH = "0x5E8422345238F34275888049021821E8E08CAa1f"; @@ -270,39 +268,6 @@ addresses.mainnet.FraxETHMinter = "0xbAFA44EFE7901E04E39Dad13167D089C559c1138"; addresses.mainnet.oneInchRouterV5 = "0x1111111254EEB25477B68fb85Ed929f73A960582"; -// Balancer -addresses.mainnet.BAL = "0xba100000625a3754423978a60c9317c58a424e3D"; -addresses.mainnet.balancerVault = "0xBA12222222228d8Ba445958a75a0704d566BF2C8"; -// wstETH/WETH -addresses.mainnet.wstETH_WETH_BPT = - "0x32296969Ef14EB0c6d29669C550D4a0449130230"; -addresses.mainnet.wstETH_WETH_AuraRewards = - "0x59D66C58E83A26d6a0E35114323f65c3945c89c1"; -// rETH/WETH -addresses.mainnet.rETH_WETH_BPT = "0x1E19CF2D73a72Ef1332C882F20534B6519Be0276"; -addresses.mainnet.rETH_WETH_AuraRewards = - "0xDd1fE5AD401D4777cE89959b7fa587e569Bf125D"; -// wstETH/sfrxETH/rETH -addresses.mainnet.wstETH_sfrxETH_rETH_BPT = - "0x42ed016f826165c2e5976fe5bc3df540c5ad0af7"; -addresses.mainnet.wstETH_sfrxETH_rETH_AuraRewards = - "0xd26948E7a0223700e3C3cdEA21cA2471abCb8d47"; - -// Aura -addresses.mainnet.AURA = "0xC0c293ce456fF0ED870ADd98a0828Dd4d2903DBF"; -addresses.mainnet.AuraWeightedOraclePool = - "0xc29562b045D80fD77c69Bec09541F5c16fe20d9d"; - -// Frax Oracle for frxETH/ETH -addresses.mainnet.FrxEthFraxOracle = - "0xC58F3385FBc1C8AD2c0C9a061D7c13b141D7A5Df"; -// FrxEthEthDualOracle gets the oracle prices from the Curve and Uniswap pools -addresses.mainnet.FrxEthEthDualOracle = - "0xb12c19C838499E3447AFd9e59274B1BE56b1546A"; -// FrxEthWethDualOracle -addresses.mainnet.FrxEthWethDualOracle = - "0x350a9841956D8B0212EAdF5E14a449CA85FAE1C0"; - // Curve Pools addresses.mainnet.CurveTriPool = "0x4ebdf703948ddcea3b11f675b4d1fba9d2414a14"; addresses.mainnet.CurveCVXPool = "0xB576491F1E6e5E62f1d8F26062Ee822B40B0E0d4"; diff --git a/contracts/utils/balancerStrategyDeployment.js b/contracts/utils/balancerStrategyDeployment.js deleted file mode 100644 index 14b856b141..0000000000 --- a/contracts/utils/balancerStrategyDeployment.js +++ /dev/null @@ -1,125 +0,0 @@ -const { deploymentWithGovernanceProposal } = require("./deploy"); -const addresses = require("../utils/addresses"); - -module.exports = ({ - deploymentOpts, - - proxyContractName, - - platformAddress, // Address of the Balancer pool - poolId, // Pool ID of the Balancer pool - - auraRewardsContractAddress, - - rewardTokenAddresses, - assets, -}) => { - return deploymentWithGovernanceProposal( - deploymentOpts, - async ({ deployWithConfirmation, ethers, getTxOpts, withConfirmation }) => { - const { deployerAddr } = await getNamedAccounts(); - const sDeployer = await ethers.provider.getSigner(deployerAddr); - - // Current contracts - const cOETHVaultProxy = await ethers.getContract("OETHVaultProxy"); - const cOETHVaultAdmin = await ethers.getContractAt( - "OETHVaultAdmin", - cOETHVaultProxy.address - ); - - // Deployer Actions - // ---------------- - - // 1. Deploy new proxy - // New strategy will be living at a clean address - const dOETHBalancerMetaPoolStrategyProxy = await deployWithConfirmation( - proxyContractName - ); - const cOETHBalancerMetaPoolStrategyProxy = await ethers.getContractAt( - proxyContractName, - dOETHBalancerMetaPoolStrategyProxy.address - ); - - // 2. Deploy new implementation - const dOETHBalancerMetaPoolStrategyImpl = await deployWithConfirmation( - "BalancerMetaPoolStrategy", - [ - [platformAddress, cOETHVaultProxy.address], - [ - addresses.mainnet.rETH, - addresses.mainnet.stETH, - addresses.mainnet.wstETH, - addresses.mainnet.frxETH, - addresses.mainnet.sfrxETH, - addresses.mainnet.balancerVault, // Address of the Balancer vault - poolId, // Pool ID of the Balancer pool - ], - auraRewardsContractAddress, - ] - ); - const cOETHBalancerMetaPoolStrategy = await ethers.getContractAt( - "BalancerMetaPoolStrategy", - dOETHBalancerMetaPoolStrategyProxy.address - ); - - const cOETHHarvesterProxy = await ethers.getContract( - "OETHHarvesterProxy" - ); - const cOETHHarvester = await ethers.getContractAt( - "OETHHarvester", - cOETHHarvesterProxy.address - ); - - // 3. Encode the init data - const initFunction = "initialize(address[],address[],address[])"; - const initData = - cOETHBalancerMetaPoolStrategy.interface.encodeFunctionData( - initFunction, - [rewardTokenAddresses, assets, [platformAddress, platformAddress]] - ); - - // 4. Init the proxy to point at the implementation - // prettier-ignore - await withConfirmation( - cOETHBalancerMetaPoolStrategyProxy - .connect(sDeployer)["initialize(address,address,bytes)"]( - dOETHBalancerMetaPoolStrategyImpl.address, - addresses.mainnet.Timelock, - initData, - await getTxOpts() - ) - ); - - console.log( - "Balancer strategy address:", - dOETHBalancerMetaPoolStrategyProxy.address - ); - - // Governance Actions - // ---------------- - return { - name: "Deploy new Balancer MetaPool strategy", - actions: [ - // 1. Add new strategy to the vault - { - contract: cOETHVaultAdmin, - signature: "approveStrategy(address)", - args: [cOETHBalancerMetaPoolStrategy.address], - }, - // 2. Set supported strategy on Harvester - { - contract: cOETHHarvester, - signature: "setSupportedStrategy(address,bool)", - args: [cOETHBalancerMetaPoolStrategy.address, true], - }, - // 3. Set harvester address - { - contract: cOETHBalancerMetaPoolStrategy, - signature: "setHarvesterAddress(address)", - args: [cOETHHarvesterProxy.address], - }, - ], - }; - } - ); -}; diff --git a/contracts/utils/constants.js b/contracts/utils/constants.js index 4ef3d5e9e2..bfeb31529b 100644 --- a/contracts/utils/constants.js +++ b/contracts/utils/constants.js @@ -8,25 +8,8 @@ const ZERO_BYTES32 = ethers.utils.hexZeroPad("0x", 32); const ONE = ethers.utils.parseEther("1"); -const threeCRVPid = 9; -const metapoolLPCRVPid = 56; const oethPoolLpPID = 174; -// stETH/WETH -const aura_stETH_WETH_PID = 115; -const balancer_stETH_WETH_PID = - "0x32296969ef14eb0c6d29669c550d4a0449130230000200000000000000000080"; - -// wstETH/sfrxETH/rETH -const aura_wstETH_sfrxETH_rETH_PID = 50; -const balancer_wstETH_sfrxETH_rETH_PID = - "0x42ed016f826165c2e5976fe5bc3df540c5ad0af700000000000000000000058b"; - -// rETH/WETH -const aura_rETH_WETH_PID = 109; -const balancer_rETH_WETH_PID = - "0x1e19cf2d73a72ef1332c882f20534b6519be0276000200000000000000000112"; - // chain selectors for CCIP const ccip_arbChainSelector = "4949039107694359620"; @@ -41,18 +24,10 @@ const gIndexFirstPendingDepositPubKey = 1584842932224n; const gIndexFirstPendingDepositSlot = 1584842932228n; module.exports = { - threeCRVPid, - metapoolLPCRVPid, oethPoolLpPID, MAX_UINT256, MAX_UINT64, ZERO_BYTES32, - aura_stETH_WETH_PID, - balancer_stETH_WETH_PID, - aura_wstETH_sfrxETH_rETH_PID, - balancer_wstETH_sfrxETH_rETH_PID, - aura_rETH_WETH_PID, - balancer_rETH_WETH_PID, ccip_arbChainSelector, p2pApiEncodedKey, ONE, @@ -61,127 +36,3 @@ module.exports = { gIndexFirstPendingDepositPubKey, gIndexFirstPendingDepositSlot, }; - -// These are all the metapool ids. For easier future reference -// 0 0x845838DF265Dcd2c412A1Dc9e959c7d08537f8a2 -// 1 0x9fC689CCaDa600B6DF723D9E47D84d76664a1F23 -// 2 0xdF5e0e81Dff6FAF3A7e52BA697820c5e32D806A8 -// 3 0x3B3Ac5386837Dc563660FB6a0937DFAa5924333B -// 4 0xC25a3A3b969415c80451098fa907EC722572917F -// 5 0xD905e2eaeBe188fc92179b6350807D8bd91Db0D8 -// 6 0x49849C98ae39Fff122806C06791Fa73784FB3675 -// 7 0x075b1bb99792c9E1041bA13afEf80C91a1e70fB3 -// 8 0xb19059ebb43466C323583928285a49f558E572Fd -// 9 0x6c3F90f043a72FA612cbac8115EE7e52BDe6E490 -// 10 0xD2967f45c4f384DEEa880F807Be904762a3DeA07 -// 11 0x5B5CFE992AdAC0C9D48E05854B2d91C73a003858 -// 12 0x97E2768e8E73511cA874545DC5Ff8067eB19B787 -// 13 0x4f3E8F405CF5aFC05D68142F3783bDfE13811522 -// 14 0x1AEf73d49Dedc4b1778d0706583995958Dc862e6 -// 15 0xC2Ee6b0334C261ED60C72f6054450b61B8f18E35 -// 16 0x64eda51d3Ad40D56b9dFc5554E06F94e1Dd786Fd -// 17 0x3a664Ab939FD8482048609f652f9a0B0677337B9 -// 18 0xDE5331AC4B3630f94853Ff322B66407e0D6331E8 -// 19 0x410e3E86ef427e30B9235497143881f717d93c2A -// 20 0x2fE94ea3d5d4a175184081439753DE15AeF9d614 -// 21 0x94e131324b6054c0D789b190b2dAC504e4361b53 -// 22 0x194eBd173F6cDacE046C53eACcE9B953F28411d1 -// 23 0xA3D87FffcE63B53E0d54fAa1cc983B7eB0b74A9c -// 24 0xFd2a8fA60Abd58Efe3EeE34dd494cD491dC14900 -// 25 0x06325440D014e39736583c165C2963BA99fAf14E -// 26 0x02d341CcB60fAaf662bC0554d13778015d1b285C -// 27 0xaA17A236F2bAdc98DDc0Cf999AbB47D47Fc0A6Cf -// 28 0x7Eb40E450b9655f4B3cC4259BCC731c63ff55ae6 -// 29 0x5282a4eF67D9C33135340fB3289cc1711c13638C -// 30 0xcee60cFa923170e4f8204AE08B4fA6A3F5656F3a -// 31 0xEcd5e75AFb02eFa118AF914515D6521aaBd189F1 -// 32 0xd632f22692FaC7611d2AA1C0D552930D43CAEd3B -// 33 0xEd279fDD11cA84bEef15AF5D39BB4d4bEE23F0cA -// 34 0x4807862AA8b2bF68830e4C8dc86D0e9A998e085a -// 35 0x53a901d48795C58f485cBB38df08FA96a24669D5 -// 36 0x43b4FdFD4Ff969587185cDB6f0BD875c5Fc83f8c -// 37 0xcA3d75aC011BF5aD07a98d02f18225F9bD9A6BDF -// 38 0xc4AD29ba4B3c580e6D59105FFf484999997675Ff -// 39 0xFD5dB7463a3aB53fD211b4af195c5BCCC1A03890 -// 40 0x5a6A4D54456819380173272A5E8E9B9904BdF41B -// 41 0x9D0464996170c6B9e75eED71c68B99dDEDf279e8 -// 42 0x8818a9bb44Fbf33502bE7c15c500d0C783B73067 -// 43 0xD6Ac1CB9019137a896343Da59dDE6d097F710538 -// 44 0x3F1B0278A9ee595635B61817630cC19DE792f506 -// 45 0x19b080FE1ffA0553469D20Ca36219F17Fcf03859 -// 46 0x9c2C8910F113181783c249d8F6Aa41b51Cde0f0c -// 47 0x8461A004b50d321CB22B7d034969cE6803911899 -// 48 0xB15fFb543211b558D40160811e5DcBcd7d5aaac9 -// 49 0xC4C319E2D4d66CcA4464C0c2B32c9Bd23ebe784e -// 50 0x3Fb78e61784C9c637D560eDE23Ad57CA1294c14a -// 51 0x5B3b5DF2BF2B6543f78e053bD91C4Bdd820929f1 -// 52 0x55A8a39bc9694714E2874c1ce77aa1E599461E18 -// 53 0xFbdCA68601f835b27790D98bbb8eC7f05FDEaA9B -// 54 0x3D229E1B4faab62F621eF2F6A610961f7BD7b23B -// 55 0x3b6831c0077a1e44ED0a21841C3bC4dC11bCE833 -// 56 0x87650D7bbfC3A9F10587d7778206671719d9910D -// 57 0xc270b3B858c335B6BA5D5b10e2Da8a09976005ad -// 58 0xBaaa1F5DbA42C3389bDbc2c9D2dE134F5cD0Dc89 -// 59 0xCEAF7747579696A2F0bb206a14210e3c9e6fB269 -// 60 0xb9446c4Ef5EBE66268dA6700D26f96273DE3d571 -// 61 0xEd4064f376cB8d68F770FB1Ff088a3d0F3FF5c4d -// 62 0xAA5A67c256e27A5d80712c51971408db3370927D -// 63 0x6BA5b4e438FA0aAf7C1bD179285aF65d13bD3D90 -// 64 0x3A283D9c08E8b55966afb64C515f5143cf907611 -// 65 0x8484673cA7BfF40F82B041916881aeA15ee84834 -// 66 0x8282BD15dcA2EA2bDf24163E8f2781B30C43A2ef -// 67 0xCb08717451aaE9EF950a2524E33B6DCaBA60147B -// 68 0x29059568bB40344487d62f7450E78b8E6C74e0e5 -// 69 0x90244F43D548a4f8dFecfAD91a193465B1fad6F7 -// 70 0xB37D6c07482Bc11cd28a1f11f1a6ad7b66Dec933 -// 71 0x06cb22615BA53E60D67Bf6C341a0fD5E718E1655 -// 72 0xF3A43307DcAFa93275993862Aae628fCB50dC768 -// 73 0x447Ddd4960d9fdBF6af9a790560d0AF76795CB08 -// 74 0x137469B55D1f15651BA46A89D0588e97dD0B6562 -// 75 0xE160364FD8407FFc8b163e278300c6C5D18Ff61d -// 76 0xbcb91E689114B9Cc865AD7871845C95241Df4105 -// 77 0xC9467E453620f16b57a34a770C6bceBECe002587 -// 78 0x2302aaBe69e6E7A1b0Aa23aAC68fcCB8A4D2B460 -// 79 0x1054Ff2ffA34c055a13DCD9E0b4c0cA5b3aecEB9 -// 80 0x3a70DfA7d2262988064A2D051dd47521E43c9BdD -// 81 0x401322B9FDdba8c0a8D40fbCECE1D1752C12316B -// 82 0x4704aB1fb693ce163F7c9D3A31b3FF4eaF797714 -// 83 0x6359B6d3e327c497453d4376561eE276c6933323 -// 84 0x54c8Ecf46A81496eEB0608BD3353388b5D7a2a33 -// 85 0x08ceA8E5B4551722dEB97113C139Dd83C26c5398 -// 86 0x8682Fbf0CbF312C891532BA9F1A91e44f81ad7DF -// 87 0x22CF19EB64226e0E1A79c69b345b31466fD273A7 -// 88 0x127091edE112aEd7Bae281747771b3150Bb047bB -// 89 0x80CAcCdBD3f07BbdB558DB4a9e146D099933D677 -// 90 0x4647B6D835f3B393C7A955df51EEfcf0db961606 -// 91 0x8EE017541375F6Bcd802ba119bdDC94dad6911A1 -// 92 0x3660BD168494d61ffDac21E403d0F6356cF90fD7 -// 93 0xf7b55C3732aD8b2c2dA7c24f30A69f55c54FB717 -// 94 0x48fF31bBbD8Ab553Ebe7cBD84e1eA3dBa8f54957 -// 95 0xdf55670e27bE5cDE7228dD0A6849181891c9ebA1 -// 96 0xe6b5CC1B4b47305c58392CE3D359B10282FC36Ea -// 97 0xbE4f3AD6C9458b901C81b734CB22D9eaE9Ad8b50 -// 98 0x8c524635d52bd7b1Bd55E062303177a7d916C046 -// 99 0x7ea4aD8C803653498bF6AC1D2dEbc04DCe8Fd2aD -// 100 0x3175Df0976dFA876431C2E9eE6Bc45b65d3473CC -// 101 0xe3c190c57b5959Ae62EfE3B6797058B76bA2f5eF -// 102 0x497CE58F34605B9944E6b15EcafE6b001206fd25 -// 103 0x04b727C7e246CA70d496ecF52E6b6280f3c8077D -// 104 0x4e43151b78b5fbb16298C1161fcbF7531d5F8D93 -// 105 0x8fdb0bB9365a46B145Db80D0B1C5C5e979C84190 -// 106 0xB30dA2376F63De30b42dC055C93fa474F31330A5 -// 107 0x4606326b4Db89373F5377C316d3b0F6e55Bc6A20 -// 108 0x33baeDa08b8afACc4d3d07cf31d49FC1F1f3E893 -// 109 0xdaDfD00A2bBEb1abc4936b1644a3033e1B653228 -// 110 0x70fc957eb90E37Af82ACDbd12675699797745F68 -// 111 0xfa65aa60a9D45623c57D383fb4cf8Fb8b854cC4D -// 112 0xe7A3b38c39F97E977723bd1239C3470702568e7B -// 113 0xBa3436Fd341F2C8A928452Db3C5A3670d1d5Cc73 -// 114 0xC47EBd6c0f68fD5963005D28D0ba533750E5C11B -// 115 0xE57180685E3348589E9521aa53Af0BCD497E884d -// 116 0x22e859Ee894c2068920858A60b51DC03ac5581c1 -// 117 0x7F17A6C77C3938D235b014818092eb6305BdA110 -// 118 0x527331F3F550f6f85ACFEcAB9Cc0889180C6f1d5 -// 119 0xF57ccaD8122B898A147Cc8601B1ECA88B1662c7E -// 120 0xf985005a3793DbA4cCe241B3C19ddcd3Fe069ff4 -// 121 0x66E335622ad7a6C9c72c98dbfCCE684996a20Ef9 diff --git a/contracts/utils/deploy.js b/contracts/utils/deploy.js index 8461aa5165..000a9807f4 100644 --- a/contracts/utils/deploy.js +++ b/contracts/utils/deploy.js @@ -12,7 +12,6 @@ const { isHolesky, isFork, isMainnetOrFork, - getOracleAddresses, getAssetAddresses, isSmokeTest, isForkTest, @@ -493,7 +492,7 @@ const executeGovernanceProposalOnFork = async ({ await getStorageAt(governorSix.address, slotKey) ).toNumber(); const currentBlock = await hre.ethers.provider.getBlockNumber(); - let blocksToMine = deadline - currentBlock; + let blocksToMine = deadline - currentBlock + 7000; // Add 1d buffer if (blocksToMine > 0) { if (reduceQueueTime) { @@ -1027,10 +1026,8 @@ function deploymentWithGovernanceProposal(opts, fn) { executionRetries = 0, } = opts; const runDeployment = async (hre) => { - const oracleAddresses = await getOracleAddresses(hre.deployments); const assetAddresses = await getAssetAddresses(hre.deployments); const tools = { - oracleAddresses, assetAddresses, deployWithConfirmation, ethers, @@ -1208,10 +1205,8 @@ function deploymentWithGovernanceProposal(opts, fn) { function deploymentWithGuardianGovernor(opts, fn) { const { deployName, dependencies, forceDeploy, onlyOnFork, forceSkip } = opts; const runDeployment = async (hre) => { - const oracleAddresses = await getOracleAddresses(hre.deployments); const assetAddresses = await getAssetAddresses(hre.deployments); const tools = { - oracleAddresses, assetAddresses, deployWithConfirmation, ethers, diff --git a/contracts/utils/oracle.js b/contracts/utils/oracle.js deleted file mode 100644 index f2bec945c4..0000000000 --- a/contracts/utils/oracle.js +++ /dev/null @@ -1,53 +0,0 @@ -const { formatUnits } = require("ethers/lib/utils"); -const { ethers } = hre; -const addresses = require("./addresses"); -const { replaceContractAt } = require("./hardhat"); - -const feedConfig = { - [addresses.mainnet.rETH]: { - feed: "0x536218f9E9Eb48863970252233c8F271f554C2d0", - decimals: 18, - }, - [addresses.mainnet.frxETH]: { - feed: "0xC58F3385FBc1C8AD2c0C9a061D7c13b141D7A5Df", - decimals: 18, - }, - [addresses.mainnet.stETH]: { - feed: "0x86392dC19c0b719886221c78AB11eb8Cf5c52812", - decimals: 18, - }, -}; - -/** - * Sets the price of any chainlink oracle feed used by - * the OracleRouter. - * @param {BigNumber} price The price with 18 decimals - */ -const setChainlinkOraclePrice = async (asset, price) => { - const { feed, decimals } = feedConfig[asset]; - const { deploy } = deployments; - if (!feed || !decimals) { - throw new Error(`Can not mock oracle for asset: ${asset}`); - } - - const contractName = `MockChainlinkOracleFeed_${asset}`; - console.log( - `About to set Oracle price for asset: ${asset} to ${formatUnits(price)}` - ); - - // deploy mocked feed address - await deploy(contractName, { - from: addresses.mainnet.Timelock, // doesn't matter which address deploys it - contract: "MockChainlinkOracleFeed", - args: [price, decimals], - }); - - await replaceContractAt(feed, await ethers.getContract(contractName)); - - const contract = await ethers.getContractAt("MockChainlinkOracleFeed", feed); - contract.setPrice(price); -}; - -module.exports = { - setChainlinkOraclePrice, -}; From 61b357a3406df9f3e0ed0088c3bd6554456378f1 Mon Sep 17 00:00:00 2001 From: Nick Addison Date: Thu, 19 Feb 2026 22:21:57 +1100 Subject: [PATCH 24/36] Run log - Allocate 100k USDC to the Cross-chain strategy (#2805) * Run log Allocate 100k USDC to the Cross-chain strategy * Updated USDC balance message * Updated withdraw amount --- brownie/runlogs/2026_02_strategist.py | 35 +++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/brownie/runlogs/2026_02_strategist.py b/brownie/runlogs/2026_02_strategist.py index fa6073b293..4a91b785bf 100644 --- a/brownie/runlogs/2026_02_strategist.py +++ b/brownie/runlogs/2026_02_strategist.py @@ -78,3 +78,38 @@ def main(): {'from': STRATEGIST} ) ) + +# ------------------------------------------- +# Feb 19 2026 - Allocate 100k USDC to the Cross-chain strategy +# ------------------------------------------- +from world import * +def main(): + with TemporaryForkForReallocations() as txs: + txs.append(vault_core.rebase({'from': MULTICHAIN_STRATEGIST})) + txs.append(vault_value_checker.takeSnapshot({'from': MULTICHAIN_STRATEGIST})) + + txs.append(vault_admin.withdrawFromStrategy( + MORPHO_OUSD_V2_STRAT, + [usdc], + [189_000 * 10**6], + {'from': MULTICHAIN_STRATEGIST} + )) + + txs.append(vault_admin.depositToStrategy( + CROSSCHAIN_MORPHO_V2_BASE_MASTER_STRATEGY, + [usdc], + [100_000 * 10**6], + {'from': MULTICHAIN_STRATEGIST} + )) + vault_change = vault_core.totalValue() - vault_value_checker.snapshots(MULTICHAIN_STRATEGIST)[0] + supply_change = ousd.totalSupply() - vault_value_checker.snapshots(MULTICHAIN_STRATEGIST)[1] + profit = vault_change - supply_change + txs.append(vault_value_checker.checkDelta(profit, (1 * 10**18), vault_change, (1 * 10**18), {'from': MULTICHAIN_STRATEGIST})) + print("-----") + print("Profit", "{:.6f}".format(profit / 10**18), profit) + print("USDC supply change", "{:.6f}".format(supply_change / 10**18), supply_change) + print("Vault Change", "{:.6f}".format(vault_change / 10**18), vault_change) + print("-----") + + vault_usdc = usdc.balanceOf(VAULT_PROXY_ADDRESS) + print("USDC left in Vault", "{:.6f}".format(vault_usdc / 10**6), vault_usdc) From 1535963db30de5d29db54cd7eb4d2d61172f9f0c Mon Sep 17 00:00:00 2001 From: Nick Addison Date: Fri, 20 Feb 2026 07:16:36 +1100 Subject: [PATCH 25/36] Document updates and snapVault for OUSD (#2806) * Updated Vault hierarchy * Updated OUSD contract diagram * Added symbol to snapVault Hardhat task for OUSD Added symbol to queueLiquidity hardhat task for OUSD * regenerated using latest sol2uml just to be safe --- contracts/docs/VaultHierarchy.svg | 212 +++++++++++---------- contracts/docs/VaultSquashed.svg | 34 ++-- contracts/docs/VaultStorage.svg | 6 +- contracts/docs/generate.sh | 10 +- contracts/docs/plantuml/ousdContracts.png | Bin 26210 -> 30172 bytes contracts/docs/plantuml/ousdContracts.puml | 7 +- contracts/tasks/tasks.js | 17 +- contracts/tasks/vault.js | 60 ++++-- 8 files changed, 197 insertions(+), 149 deletions(-) diff --git a/contracts/docs/VaultHierarchy.svg b/contracts/docs/VaultHierarchy.svg index d7d69bf0d9..ac6eae87ce 100644 --- a/contracts/docs/VaultHierarchy.svg +++ b/contracts/docs/VaultHierarchy.svg @@ -4,159 +4,165 @@ - + UmlClassDiagram - + 44 - -<<Abstract>> -Governable -../contracts/governance/Governable.sol + +<<Abstract>> +Governable +../contracts/governance/Governable.sol - + -307 - -OUSD -../contracts/token/OUSD.sol +304 + +OUSD +../contracts/token/OUSD.sol - + -307->44 - - +304->44 + + - + -324 - -<<Abstract>> -Initializable -../contracts/utils/Initializable.sol +326 + +<<Abstract>> +Initializable +../contracts/utils/Initializable.sol - + -330 +332 OETHBaseVault ../contracts/vault/OETHBaseVault.sol - + -335 - -<<Abstract>> -VaultAdmin -../contracts/vault/VaultAdmin.sol +337 + +<<Abstract>> +VaultAdmin +../contracts/vault/VaultAdmin.sol - + -330->335 - - +332->337 + + - + -332 +334 OETHVault ../contracts/vault/OETHVault.sol - + -332->335 - - +334->337 + + - + -333 - -OSonicVault -../contracts/vault/OSonicVault.sol +335 + +OSVault +../contracts/vault/OSVault.sol - + -333->335 - - +335->337 + + - + -334 - -OUSDVault -../contracts/vault/OUSDVault.sol +336 + +OUSDVault +../contracts/vault/OUSDVault.sol - + -334->335 - - +336->337 + + - + -336 - -<<Abstract>> -VaultCore -../contracts/vault/VaultCore.sol +338 + +<<Abstract>> +VaultCore +../contracts/vault/VaultCore.sol - + -335->336 - - +337->338 + + - + -337 - -<<Abstract>> -VaultInitializer -../contracts/vault/VaultInitializer.sol +339 + +<<Abstract>> +VaultInitializer +../contracts/vault/VaultInitializer.sol - + -336->337 - - +338->339 + + - + + +339->304 + + + + -338 - -<<Abstract>> -VaultStorage -../contracts/vault/VaultStorage.sol +340 + +<<Abstract>> +VaultStorage +../contracts/vault/VaultStorage.sol - + -337->338 - - +339->340 + + - - -338->44 - - - - + -338->307 - - +340->44 + + - - -338->324 - - + + +340->304 + + + + + +340->326 + + diff --git a/contracts/docs/VaultSquashed.svg b/contracts/docs/VaultSquashed.svg index 06204eb0e2..c77b2de5db 100644 --- a/contracts/docs/VaultSquashed.svg +++ b/contracts/docs/VaultSquashed.svg @@ -9,9 +9,9 @@ UmlClassDiagram - + -334 +338 OUSDVault ../contracts/vault/OUSDVault.sol @@ -52,7 +52,7 @@   vaultBuffer: uint256 <<VaultStorage>>   autoAllocateThreshold: uint256 <<VaultStorage>>   rebaseThreshold: uint256 <<VaultStorage>> -   oUSD: OUSD <<VaultStorage>> +   oToken: OUSD <<VaultStorage>>   strategistAddr: address <<VaultStorage>>   maxSupplyDiff: uint256 <<VaultStorage>>   trusteeAddress: address <<VaultStorage>> @@ -81,19 +81,19 @@    _rebase(): uint256 <<whenNotRebasePaused>> <<VaultCore>>    _nextYield(supply: uint256, vaultValue: uint256): (yield: uint256, targetRate: uint256) <<VaultCore>>    _totalValue(): (value: uint256) <<VaultCore>> -    _totalValueInVault(): (value: uint256) <<VaultCore>> -    _checkBalance(_asset: address): (balance: uint256) <<VaultCore>> -    _addWithdrawalQueueLiquidity(): (addedClaimable: uint256) <<VaultCore>> -    _assetAvailable(): (assetAvailable: uint256) <<VaultCore>> -    _min(a: uint256, b: uint256): uint256 <<VaultCore>> -    _max(a: uint256, b: uint256): uint256 <<VaultCore>> -    _depositToStrategy(_strategyToAddress: address, _assets: address[], _amounts: uint256[]) <<VaultAdmin>> -    _withdrawFromStrategy(_recipient: address, _strategyFromAddress: address, _assets: address[], _amounts: uint256[]) <<VaultAdmin>> -    _withdrawAllFromStrategy(_strategyAddr: address) <<VaultAdmin>> -    _withdrawAllFromStrategies() <<VaultAdmin>> -External: -    transferGovernance(_newGovernor: address) <<onlyGovernor>> <<Governable>> -    claimGovernance() <<Governable>> +    _checkBalance(_asset: address): (balance: uint256) <<VaultCore>> +    _addWithdrawalQueueLiquidity(): (addedClaimable: uint256) <<VaultCore>> +    _assetAvailable(): (assetAvailable: uint256) <<VaultCore>> +    _min(a: uint256, b: uint256): uint256 <<VaultCore>> +    _max(a: uint256, b: uint256): uint256 <<VaultCore>> +    _depositToStrategy(_strategyToAddress: address, _assets: address[], _amounts: uint256[]) <<VaultAdmin>> +    _withdrawFromStrategy(_recipient: address, _strategyFromAddress: address, _assets: address[], _amounts: uint256[]) <<VaultAdmin>> +    _withdrawAllFromStrategy(_strategyAddr: address) <<VaultAdmin>> +    _withdrawAllFromStrategies() <<VaultAdmin>> +External: +    transferGovernance(_newGovernor: address) <<onlyGovernor>> <<Governable>> +    claimGovernance() <<Governable>> +    oUSD(): OUSD <<VaultStorage>>    initialize(_oToken: address) <<onlyGovernor, initializer>> <<VaultInitializer>>    mint(address, _amount: uint256, uint256) <<whenNotCapitalPaused, nonReentrant>> <<VaultCore>>    mint(_amount: uint256) <<whenNotCapitalPaused, nonReentrant>> <<VaultCore>> @@ -118,7 +118,7 @@    setStrategistAddr(_address: address) <<onlyGovernor>> <<VaultAdmin>>    setDefaultStrategy(_strategy: address) <<onlyGovernorOrStrategist>> <<VaultAdmin>>    setWithdrawalClaimDelay(_delay: uint256) <<onlyGovernor>> <<VaultAdmin>> -    setRebaseRateMax(yearlyApr: uint256) <<onlyGovernorOrStrategist>> <<VaultAdmin>> +    setRebaseRateMax(apr: uint256) <<onlyGovernorOrStrategist>> <<VaultAdmin>>    setDripDuration(_dripDuration: uint256) <<onlyGovernorOrStrategist>> <<VaultAdmin>>    approveStrategy(_addr: address) <<onlyGovernor>> <<VaultAdmin>>    removeStrategy(_addr: address) <<onlyGovernor>> <<VaultAdmin>> diff --git a/contracts/docs/VaultStorage.svg b/contracts/docs/VaultStorage.svg index 876c965fa2..24de2ade82 100644 --- a/contracts/docs/VaultStorage.svg +++ b/contracts/docs/VaultStorage.svg @@ -117,9 +117,9 @@ uint256: VaultStorage.rebaseThreshold (32) -unallocated (12) - -OUSD: VaultStorage.oUSD (20) +unallocated (12) + +OUSD: VaultStorage.oToken (20) unallocated (12) diff --git a/contracts/docs/generate.sh b/contracts/docs/generate.sh index 2420f4610b..924a67b2db 100644 --- a/contracts/docs/generate.sh +++ b/contracts/docs/generate.sh @@ -111,10 +111,10 @@ sol2uml storage .. -c WOSonic -o WOSonicStorage.svg --hideExpand ______gap # contracts/vault -sol2uml .. -v -hv -hf -he -hs -hl -hi -i prettier-plugin-solidity -b OUSDVault,OETHVault,OETHBaseVault,OSonicVault -o VaultHierarchy.svg +sol2uml .. -v -hv -hf -he -hs -hl -hi -i prettier-plugin-solidity -b OUSDVault,OETHVault,OETHBaseVault,OSVault -o VaultHierarchy.svg -sol2uml .. -s -d 0 -i prettier-plugin-solidity -b OUSDVault -o VaultSquashed.svg -sol2uml storage .. -i prettier-plugin-solidity -c OUSDVault -o VaultStorage.svg --hideExpand __gap,______gap,_deprecated_swapTokens +sol2uml .. -s -d 0 -i prettier-plugin-solidity -b OUSDVault -o VaultSquashed.svg -v +sol2uml storage .. -i prettier-plugin-solidity -c OUSDVault -o VaultStorage.svg --hideExpand __gap,______gap,_deprecated_swapTokens -v # sol2uml .. -s -d 0 -i prettier-plugin-solidity -b OETHVault -o OETHVaultSquashed.svg # sol2uml storage .. -i prettier-plugin-solidity -c OETHVault -o OETHVaultStorage.svg --hideExpand __gap,______gap,_deprecated_swapTokens @@ -122,8 +122,8 @@ sol2uml storage .. -i prettier-plugin-solidity -c OUSDVault -o VaultStorage.svg # sol2uml .. -s -d 0 -i prettier-plugin-solidity -b OETHBaseVault -o OETHBaseVaultSquashed.svg # sol2uml storage .. -i prettier-plugin-solidity -c OETHBaseVault -o OETHBaseVaultStorage.svg --hideExpand __gap,______gap -# sol2uml .. -s -d 0 -i prettier-plugin-solidity -b OSonicVault -o OSonicVaultSquashed.svg -# sol2uml storage .. -i prettier-plugin-solidity -c OSonicVault -o OSonicVaultStorage.svg --hideExpand __gap,______gap,_deprecated_swapTokens +# sol2uml .. -s -d 0 -i prettier-plugin-solidity -b OSVault -o OSVaultSquashed.svg +# sol2uml storage .. -i prettier-plugin-solidity -c OSVault -o OSVaultStorage.svg --hideExpand __gap,______gap,_deprecated_swapTokens # contracts/poolBooster sol2uml .. -v -hv -hf -he -hs -hl `-i prettier-plugin-solidity` -b PoolBoosterFactorySwapxSingle -o PoolBoosterFactorySwapxSingleHierarchy.svg diff --git a/contracts/docs/plantuml/ousdContracts.png b/contracts/docs/plantuml/ousdContracts.png index f4cf7a2584d4f752cede709657256e06e0279c94..bf8bf7852834d6c07d377536b61671ef3a4b7f7b 100644 GIT binary patch literal 30172 zcmZ_0bzD_lw>FG`go2=yG}4U%(xHTOm$Y<;|=xk*;@ziKqBf01!T1M=~E1fXE)F;Vd>cL?#QpcHfuNbLG1%@ zGrzsXRX(HcoNXxb5qpIpohW3>^F|D7JsUF^~zGL}*FI6sEwCR4DBe|LTIkZ|?4E(tiTVuX4+AQ0?%`jSXCoEQ7EP?p8 z<&b4@`HW>wkC>cvawOw2I1icV#IW$_zSoMC>1j!>_JBhwwP_W6zy+s1czND?ovm!X zTzB1t%jUfr` zSE-u=+?y}GK4Kz%j98|GqL88dAX`HJ0sMU91pbKv4}o1l0eS!D#{a$YzaIVf<(_QE zvzehapU9T_I7p?@thEl zx4xad{rUmtqI-qo?w=S2wS$?8&Hb9i*vQhxbB*i8zMh^-Xgh|@LcP(2hOXz;daF0G zfq{Wq(dWqIWa+s3Wq(qwtuvehwYa{xwZw$t?tGH#lX-12c%O&Xq}0ZE|-x^Sisft&AHl zuv_nw6S!I%7JRTgqHn%CpNpcBI;dTJv7nAPl)*Pv+H}3saM;Aqmw#~!^!n}J2$^6PqldVe1FdC_NY5t@ct6wc9BSix~?x|V>Gvt|e zGN4&|mX|GA%d7@nXz!124x8ir6hNTbkh+Kao4FdBW|QGmWK#FvPW!XUojviHV&Q#= zhM#?WeCAy?Qy>sXNB>X?@3?}5n6_kHQfw@T$Mxx_P=d4VasMS{c`z}zoe#$r^&)5$ zJs9V1w#s7ib-9cY|IMsLX@Xj7482N*uwjcj*#F_~&`Xa0OgV|esz9|^5{bM_e*d)M zTzG3!tyCxF*mC;!8>$HH{0)q^)YQgHE#J>}r%DwvKiS$sY0})uEAGywbn#ep+^4ji zM{$5L2yk(Y-vRPBXI(WON|vz6RV~)IyFP1h*p50^+goUGzP&s;y>|ETa6W`EVay10 zt`KHL2@6IXn?bd@0i)s((=J-UjnC$-642HFF6t7Y@E4LszdK{<_-L zdq3w-;@WeL@y4&07Xt+a1s(lvJx-TV-~+tG#lZpv1qB_xXc!5x2a1c7H(*Ju zhCTcj^9JvP8$jcC=6(LvbO{5^qQSU;#reCE^GnxrULo}EMeFukrs*G{_bJhlgT1dv&;ZK(bLaJNqdU_ro}udX9eS=To{~OiCI=S|)@x zS%3$ir=%3wkk6GR4#YhPN~Ik9ad`<^paQl#ja;+mUHuih zRb(*Wq&_Ewlt(3)7D`!(mTKT^Z*SL2flr2bk4Ki<{ReHn0Nd+T@)&%r)#}Y9vaj%FbVyb?R=|v!@*8*>1mbM}K`pNLa2k8;1_Rcw%#qz3BDODD3~D z+)XlN_Y;m!5w_Fb^fC$r@cGo;+b=0;g{zjeM8Kg$+)*!N;@^He@I0R}DQCU`JVjNn zH3Y0)Wv;?g2|Ch03AzV7cJ$&?%^GW_?LwSR;1hmf)fQ6+5BG-;6QBYjuj_3Zc<*XX z#eMIXRdvhT2w=tI=h1QkX6QD6Ax8}-R|ozn|N3EW2S?X+n`+-9(ZrO{re4=$V*o#) z_==XK_$ce#lMHIPPry z&LHKdr~7lxCmVyoxJ*$LWCu+*)@KdAR^Z5bhi!6(M8Ks^6{tvAW1R>6sHv=E?`319 z)7RIh^fyg<@zNOxT7dsch=_RWdTOff<#E)BXMX?v`}c6ZV-$S;V*^DXKm*Rr;%HL| zhpvIBH`x^kNWDV!hfOzmvASjYPt)s_a%Eq=k`uhE1V3r>b7$o66xY%svWhO2;3x|j z<@Z5AelfzlK=`uIY&_?9zR~TXKrJf`i-bh>0=ktMt}!q|PFZwkp5X}iDY_ibyX&9J zW7$$-p#)TO%*P@@x)sKul;k*m!1xmfbhuu%;o;%n1Hf@nJdf$n{AUDpm)99*_C;w4Z&#<@mBcigEgqwoXE;<2wz_;{4-5~SNto>Vz z<+ql%?yUVN!{&ac4E2t?PFo{yctm0BRmljdi{FW4#Z@Vio6kVp5gZvUv5etb_Ip;8Z?7joy1>Iaggw6d}_l zO%aFrrxunFmS-DuhYcpMZ5}in9*m?Tq|0h6!`s=7Y7)NF_aU-E`Pf2;2xOg4Y=F!Y zHVW(qiUj-ld*lCJ`M;0;-z)!X)W5?Wgw!YmuY6+B2>!D7p-m78@#BGS4MD}9AbYPj zuX*8s??16AA!U7l8-``P-#9au_IsT5*Vvb5gp~7W!E=ENQ0m;+Q(M6OwUxu3x% z_QWQeKv59SkucD@z2n3t@J>zCw34ios49YWoB!Pje&N-$z`qYp6JF(-5>gV}1XMI9 zktbE*cyu3kWi;xq>y4@VN`?o^5%iUjvm}}gnw!W( z2}_r#oed=&NVX}naIh5|sXJcz-W?-e*Fddp*}oB*b-^8F+0^G8@U%B4 z)6T#&p36R|xxLp@7GCpgR$nO=rB;lOofK9!z(a!GO9#AS;tiPW7M&y@UzQ2O=LvBdCZ{ zJZcn-Blr2M_(TDXlPB#CJ9a;Y-N3KF42eQ_GkD|*ez8x}1CEx<`A0j};V<=V^ImcD z>dY&o2=?cTRhjUY#juDG&Iw|oCmqw&4Us4GYR82KW?Qc0S7h&wi6>Jtw#e)1|j<|@p5*nCb!`g2S4P84MGxfKYXg60iilr@TIhFveehj|`uX~EZ(-I8hA z@G|R9K7MJi6CuIcaPy3SiRawN!p83?4GtzI=myf8JG=t}cgF8D2z&P!f0BJ*y1;xa z>z2@;{G%G|R1e>7X+_gHl~b>XDXJ&ae>XuKhLS!g<6?GP-<62Pbj6D^4+&-{;5yGO ztfHcH>2w*gQZ%yz<`2xSulO`WQgR1EdCw78s=!x{>m4RT0d+|oQFr>S%t}M{9EsFp zj~lf`bDJCe*TN?VI784X;t7y)UZ^BCJb#TfgbS&qanUP&hU4P*O{+CHDy44F`ic1; z9KwfQR6b35HFXcIiM;j$6sPX-#D>xA4Otj!)*dn4y3Fx9st_4A;AjvfM0 zh!pb;d>*?(j%c_O`bi?yHAlLKXyTe&KIg%e>&~t^v+z~n=;#x!W}$3QoN}Ba9^7An z6peqQdzWEN;lY~H@GbU!uT9yyn@Ep>g-NVzvd6kmb=+_hSPt}g;8OE(VG={bb2y-) zhKFeVG5hAE6<7z%T1s}VWMzrd((MZAH>!AKVc1c3-R7eHc(J}w*(TQb680-?(R1_q z@&-6qZM`&Tl!63eSL=!~^&Zl0v441V#&>l>=-ZlpYs7r7LLx<;0*6Tv_|v1CKG0ug z<0X}U$x(Iizn={uK~XT%oguxx92?HiV@rMW%?0LwTavyCK`jp&e^N;PwU0rvHye4= zH*~5@ba`#DT~9?zL~R2xtI?Q9P(Z?P5Tfw_QCcZxtSzJT5}$9Jh)aVbAp0sa5Dj1Y?(a}rzR3K6| z9qYnN?>8<#GzTL;?Suzz!SCp2_nn#x4+Y&up6^d(lC>2pT<`r%VEiXGow(ITCp(mi z>wUp58atbYrNxq}m-A<4Pq9y04EoN0Xs?9r^wq^2V(t#nAMLw+Rb`cjH!l_}!vWdr zfsO9zW|0EB1g?;}IV=hXTqj%-A{vg1@)j(igP$2UQztS=#`GdMxx?i^7oigbt(xkɣEe6%W*QIqkB? z^C73M(BE{43O6SJdd6q+P2-LdC#WS!YDs2Sm;7GE)BEM{rqqaiRyj0us2_2^uk(o? zKe_o7-YR*Whw!u1=tEj7`8Vk~lfMG{eq@i{B+v9CLXn=>FymbFw=mrGPf8p9LV8a} z*(Ly4k^L%J5H(Oi1+LnQDAn!nkHcyR6Sj5kP8w=Pj6sH1=^Au5)2Q53nRn2kUOPy1 z@SNIFoSk?@zM4uPx1&(^jy*(;LFPepd(*+11q-G@Zker-BnG;=M$=O0xk0nFUEHOJ z%y#BfUC=vSm#7});#dzbRa1M_Sl;&e5w)5jb!aLoxDF07a3_V9+Ht6yWUNiyJd(HI z%7V4ST$Mawy}u?!RJWS3op;Wt9$^?)i{+L8?1H7igygSEGH+_!LOC2UQtw0Odcp;1 zs==+1EL3c*lsx3+OD}(dR9lru^Ayxml z{rI7NpZYli#8M@C3X^X8T+Wy$XL;vEG}XuJ$&kO&lf@!WH96mV_;K?y&n|yX{L(?0 z2d6(~wbUY5DF&(bpo8vRUkzphi1(fXBb!2j&1*Zj8cLnw9sLwopU7s`P;*B)yJ!E2 z^Tzvd>%d-y>9pcKv=NsCd1o|QG(;juL>)2Z>*-5LTfC`8MIIBYU_O*2j zzY|!q8bsZ6rqX`esY{HM5EAN$l_tri3oGO26N~YSmEmy~Oie(<&D)0O^zpON!Hlc> zdxsgr9_}kcUI;eKA)MVn^iqbphT;<6*fp5=z4WAp+4&H*z8tN6$@)lm*$F6#ESEaK zD<)r;KQKxC^(gB7$^~t!gzE=8t0DLc z&NnjZU;^I)KsJ^|1A>=Qnsvv0qx6%tLs9;*;4d2GSvm&yS#kHx%}0Mu4vpmd%J!!n z^UP%7p&Kti?NNPze7L2=`ari&wBhs9HZ}9>YFSwzX=s2M5yC@N(LlT0QEF)1 z_Ph@S-I|wD1ywH$=h9E$?g}(S>msV=pRMI)IO5&2GpM9zK+Ux+6Yy*VU|SmaOs8MX z(}vnHuWQ4mo-hxW42RL(>nmOtmEH`kynr=vcYl-T%7wkp;eqX;z{wv*Ae`_)|}yt?GjKF!oqEs|N8%q~8S zS?xnrHBTUieX!t27sgcAT$99{-0;{NAby?s!R;1vH8q}jMeGwg=I!i(szHYSZ}9oG z{g3OX9SP`}|4fKhdYdrJi8Za_2&g!VnroBR$E%5D7Hw_)3iJib3w+jS-pbc?sTUvQ zbus!@cJdR$(!Y6KK*I4mp#@0OB|h#x!@iq8!Da^g1Z|x)9t-c;a4eYo;~h0Qxq?@8 zIOFR4n!Aie0$N%dgWk|gPd|^QT{pr%@+AUAGy4EKwzHY=s4eGTQP459x_Uw#t@U2> z$Z&1AaG+=3dl8!Ml63ZA_Gc%<)P%*rx%yt9kdC<23qm|%js!YZ3k10;%3%4S_@AEi zIYs5ZoNY{HCemDP2sEQ~9IQVL$s^GT?0;TiJJT=9FpCeJbS9e8U`fjP?WK?ma7e{c z!OS19g|ADYjIRoO-NKjm@tO^TOhqB23~m6ZjMb39_Y+g@wp_kkP@gNB}kc*TS&Oe%q^q_M#&28U~x=YPL|x*|g$ z7gGS`Nn74^SrsYC4`(*#yz$$2Vwt)R7gVDJIVnL!GdsrlTFyD zzE;gyRK9ZA;2fd3pcIYMq+ApD!Iz^TjwgtYZXzbx^~P z)pLye`4zim-@XjJIC_j|Fb6p!BotXR-QWjM6CAwF!_PLpTg_8owclDuDe7S}ov*7dj zyLk8fFQAIpHw%QFQfiGGSMrf1I-2Yjg|(f(V^LxHkPWS|nwkHp`iGTy*Z^_jQ=!A> zaid=l-_Y6YiZ5|y9&3HhQ2htiCJ&Y&NhHaU0;~qnrw~bG8*CDHavQ4M0=^%*U6kP7 zcNDotEfK%ofISu&l}HV$68AV#q!T1nidL-p=moHEqUeTy=!hpJg$BQHiT7s}U{7nj zvvLjf2larqS(;S&!)Dxk?;N6Wvg>gw{WuBnUnIBmUJKHu@(-_asF4iC4cANG-+rP} zzjiGDJMZjJ&V5QP!Z+INU_+;=Cm2=ID#ZiDnE)Nt;f+A1YF^g_m(zm8U3kgjO=>A1|gB**v5{J$w3?k?^Qh zeu%6Gov={h3O6HPlEyD(b;%RPV!zTOwV6ny&L5Ad{J0ta)BKYq4Hc+h33812XpF8m3mvLvX2<4G*^gCRMki1j8A9V2l~k`m?GLd7e; zz2jDKnjOEse7EdUQg6Sv6e_gKoLs&s-dS3M4p!KWw=L{{210-U`8exk4d(*lG%vr; z!@{)=*@-w|g$(g_@=qnBOsCz|+z#8@oT?T`OaFR?DG66gFLKP$XEC zF?0v3C&BT$JU#ql({}QCvJ`$JlEXNO=SNLd?$v2s(&^DEIk)FUH;~K;mhe@1i?zlu zbi#gx-x|8UMvMJUH3r1?jk+dlHz#Educa9sNx%(%&44@ZKq+RqsgoyaWHXPI~ z1&f79vR}qbkps;rtOhvVr}ggF~Xf@UX zS_mxtAl@6^)mYj2E1M;BKulykTQFI3oX8!NXgP-9k!M`;pu6mqcfy3_7;U@#3OBd4 zv$=2L4VKl(?#k8$-=F44&S9UqJ5s8T6V^ky7q4X{%uKz`56>6ysd8)_^P_e|vGUiXko1o-Dnh$o&65Zy0<9pd@ zx)Bt{!ok`-b?6XF>2E+zE;tK$6!8n$wwS^lDlPeWg+zZW!J~=8tP2@9rS6!$#x|^D zB4;>sK+mujpTY`!Kp;TT0+jfV*7I2NBLblp?OV;v5g2T^o&0p_D=eD;99y;zryC?U z=!CI04B@di0Adq>Eb+X66?wom5Ck`Ucwn17t+zk9V!R(8?ZTx47xisp0d!82U;)qA7&JPU?KZM*ezr4|uWzm~QCg~EJ54mI&R#~FMLyo5wc<`+4s_yyI zXi-tj5a}hz$P-Ro3Ap0NFC7ej%V-bY{xYP`D^OkyYeIbaW(BXpFIVrqx2~BM#U;K*blHfnfU~kRPt*O@bHIcd(;^0l)XX0iTmim=-aalygLfhai zEF*Qc`{BJg4@<4|+BB3kjn%??TmGcMN6kX)h+2D-uPi1`yhJgEGr$~3cW&1MZHd)_ zK!jM0)%Qe*Qz)n9@@hiXfi?EAxAnb~7ik{8$TQJ_#ZbpW!__s;W+o`%0)wGllev6An(YsgaSdKdB1yn{P1%4 z#5(xAk2pumZ(69h0v?& zo7lLO{`Pv`*3#kImu`K*s=Z-}J={!nE!e|d#AW!#-Xe9GR!8#1-D}?P8dN)5;!RIb z|IpAm0O2=xCNTk>1*EnlQ9Ntx9#aY#4a1;q)`*n6e5^c)oy*`i7$>$~ILb{abr|y3 z8Vx4Ny}pC3HkWQVqJ7JRY*Xq(^NA+4r@GTQl=kv&{^_H@;MO3D;9`J=&}mTvbE6$Y z62Z*#qsg2FTT!XHd$>f_}k)h#F`3+#F0?v;vPBi@GZ9ZxX9AsYN?9A`UBg8{2!h&4>M$& za#}(4P#_hdf##aO2EUwxhlg+B0J>Yxf;qRdewypeka8VJjHl;1L7x#%S}+Je9CD}6 zEL?ZgbZ)$lDY+tMW|D4AN>bhFw14QpDHG8loguVD)8*K{)BD= zWdFje0I6jzUD&^`K+f)9zV7dip(x?=P*ndEEYcOtJXjLWC1BU!7jS-?NOGBYME124 zH>7!MdP?@eu)#XR3e#r7tR)BMWws*D*K1ssR8~`c0ah!IskZG6PP5U%f>7Z<2Uk7L z100i@n0Jao!Q7DK_Oya0P$vLwE#HamsI*35#f3~{U-wdz`7*sfn@}*~ccmuO%<4{K z__q7aF~P+G%Z-(iR^DTOrtJj;uRXK; zHhn9X&@(5$ST27-6=^hB%Mw%($XTw?RAZq6;mi2@f6;=y7E(8hc(pg@CN%fi-@{{ z$GB>b=#IBSKSWClI9{kXY=%B4Q!A+zQl-v!Nd$d=Qfti^Z=tVY#8vb3l*#hu# z9rzU%R%T}A5s~D_moHxqRWJj!74UFtOiau=y<>?K11sy8?*<$Uk^>&jYdbqSYPF|T zi1gUgIGF?TV0cK#uRq0_x(!a~!|6WQz!Mu7*6jEw&Kp9QcB)*_Ifp#xE(hu9I|hyYXw{Cg99eK^3~ zN4S+SadC0+@ezf?!^nKB0RZ6v|J5s2=L4;Q7XS(b@C(|j0bYx@6(;QQuOi~lU%Utl z3W9&M60%iV3JL>&)br&L9(m5d$jHdcOE~=GF&O>a7)k+%UVfWP8*LqMFqPzVbciqx zKGoCU1P1#w4g`7t;P>5x1HW3#=`KhU^VY z92~geLeJv;VT=4xUS1Atr0`9+M~woCh3#C}0%1A^I+o3V6kLE9$A=8V=LY2g9+8l( z?QaFI1t4|^3kjhtgGOPGA7leGn#-FTLOVVBa!CYu_^9aUqgX4Hzq%~~4BcaUjB0hp zs)J+T$!`YfT|Z#7--&>2^hVQ!o(41PHtlV0A~!rH;e*9Eq@*^K-8LN@DlSu{y3QLnX}r$NI{Ib*%q|fie++0lVx90EuMIe-Mn`8h3Jc550w^rV zI|wllU?N$qt@TFJjrHaRM{eh=?5}hN3;UrxS%z6OS@fS0ZK-4w42-yb)t5Vfr2-VW z6c;VjnK*#077dEFuVG_lh0?>E!!P$8n00rBdjI$Vuro&Um1x>N`M)4eo_Q@PEp0iL zg}RVyNVoy_xTA0#fGEXbHQRG&Wr*`~hs5jlt4WhyMk!d+%ggIG4AVj{FPNU5#%0nF zH@>{S=3!%#P35C=qn6l;el%ChZGb-Y@#DuRe1LK|v?pM^HIi-u@@@@Q2_Q8C$F zz?*IBc)fpCd!YkvK>>#1PyO+Ij(2-|i}I=iBe$`4Z_kp2g$1>OS|-t9d(8I>pMFO` zR#sMWG7(q=xyq;?w|KcQML!boMDg(fVvo{iw}J2#Y@Z)2>{dE+xf~nH$qfU}=jQtQ zm0v!2lhZzdj@Cyi8Xx(j5B{9x9%h6kbHE0#_?5u0(xz7AI0`j&K%2By!Yi>InoFt% z;VkHk8$-MgNiW0=9o}t7+toaflai92t!8)3C9s*8dWRS&zXQxvT|QLZqO-19i&J?f zV!QG}^?vK@8M2)uxe1U&16<3=W$x3s=}!O?z|hDjJ?J}Nd!Ir5x9!=jXkeVSZx)M9 z9=m^v{m}_OeIm3%!)HZ2dk)){t#grw9X)1k6~1}$3ZQ=xIm}|7z*F64_ShhMNhYn@ zh=KP)iR`O94qFkx4!?d1h*21*?d=t6BDXlC`5t1Yy;OWYJy#r1cup4$!D34*OAH4v zxg@0>fZd|vpMHIH^%N2D%YrOmAZXtKtVVQ>Y;;;I`ECJwrl@#}H-sr3>Q}rGEd<}Vh9+0Q48xp(*5m$sJyNj7+ zevdQVWBR37;@^}uir}qGO$aYlKu}qp98ygvRFu}e*!mU=G zpA;_Qp(bz%-n^$0B7d!n&v}w8V+dMm}1@b8y7!HgtQ zOTBOn58lIkLz;YO@BX<9G+o%1d^})X3!s3j=}vn2@31&bd^gW({?JwK(*xD)BoTB& zqqF_jf9I9c0Tnr{%I!SmDj~f)M1p7)fnGbb2|$4+O^fEJP}-OEXf$7r@AIRE0@ok@ zq_Ldtc<`+^9@|N8HgUUrcY-Nz?B*PmCf@$mRwqda9@I zguLhtl%Eq^G8UMzcO>{-*TS(#qEM0y+BV@J3$QnAtozXb*Dky3bo4*w_nSK9=u?1( zOMF=tp*5NFjV~>Ktes0P&|FvJsr#3Qk6bx?&E(%0kCv$F z^9}_du2?FJo=wy^(~jnk~R1c}#BV zEbc@DByaleN4n!YanX~G(4I_W4axIqx&I^aq;a36nR&N-?Jm6cX85=mcxnGEdV~Eh zSL3=hIU7`a`5)_B8vORJc>zs91leh1Sjq?zwsrfj8VP^QtqunK`&bZW*aK}81dGN7 z`%;9*S9bovcbr*d#VIA)FX>+i=ly0%B0T&{zQ93_VP&^6u(QtZ;)BMQH-5ppL5R-S zxUH{{bidI_jzRnRONHhf9oSQ{M{noOHa0Kjq$LoM-mS>-~W#o{eaXed|8| zF8SH(FrCdI;S&rtksR}?y;Y_oV(LGSyar^(h)=_a5G_I*qkngI@i)DG!jNP$x!u)n zAA2wnuL*(;wubZaPku4G=2$C$T+-T(<)9A3?;eRNq`0}P$r(Sy`I~yp;8Y+Xuh9mjb zR|ocQ3z=+Q#uWVK{3c{5K8WX9@hCs$Z#9={2|R`wR-W2Kcd6aioW~mpwha%NlmWAY zs%nStlB32(kh0?pHub}PFG(xVc9b{$kez}_61MyJXdZbcyBr`?wm!-PXj!-n9pbp;Ew>W zE=x}-)^v|Txe9f_!~jsa?rdL-T9|4+HHnn`3nrJ=N<)dpEB$F>*=^oEhTUex^&jKz6B@;< z*tFT!!LXgGIs~P9cO{QBq+!+SaQAL+?a722dWaC-$%i+6L18ZvfshX%yG!tYG#wn+ zXFiw9MNNQRP5?z`e6}%sUTr=ZRDOJVnq?_0EX<MtM{XT>K*7Y`bSoiMrRSI;`f)@(lc6C}&qNM*9c^4FpRNtGE<= z3y}at8+?*o{r={oFzBX9J!fdZ{JqQ5QHkJMws8VTPSJ{R8B4-3BFrxUNRDI`$kHQD z!QtZWiX2%+dEP!ewYM>nzAM#B3RJZLFf#Y<{Z=1zvU2)Aq>kp5Y;6#>h)u@8sGyJF zQ7$bBkbhd14hZ6_&GrVf+)kwv(W+mwJW2;}bwS9mb!zr9t?2x!2z%(+jY zIjr2XfQ(osvS5%I@I)ZX%E6NQT)9hpZMmzVi_VGuqREvOq62=OGXb>4Am_||5{Hgq zv9+I}|MI+%ivgeZW1%cSf_?<6fcMRonYKMhEYxz-KYK>xSCb`}DOZ$mL92R8^WV$m znt0)zB?SOTHgSJP4Uiq_uL{ZxArgQBed+l6xj@xfW!nYrQ$^dKBXW4UM}JrpnVqjJG`b=DOpiFNR7~;WC`GW>og_D zGm3t;LHP|}jw(m7rzGLS#%x=%N8vFOxeBq z>P?7BCl0)7#NBB~^MIT>;+G{|Yhar6E_&D_7{<_W^A`-`Fkm8`T-Y4lop+i3FT|WV zM-iH6O=`1)y*kSF{Sh0`!nIMZ!z#%W3ZEZ}4x%;IqEtI@6Hs`CIfkMf^+AMnyNJ^E zZSK)_&zGe7#3#bN%JM4tUHsp%aNk;r7uZL?RB}-r|BH}e;U(z95Hcq6QTQ%^fTu3w zra>wQ5b(zB{)U?KKzdMGxl0(zJhsyPL20tGv}fGCryXfwq?L~m{VSpH3I!@OejVSAyjZjve4PP2 zO>9|eO3SxvZVNy8cp9T`>BWBh4afJk%(_gF)E(cU+ccVo`^9p|Fo1ts94e;HW}Xq) zlja}%T$7r=$YWkG3Nlv)SqaKJvQ8+=fzHNIOlQlT@}*j~J4txcfI2wO7h7*G>A{|wB zI=ZBspv((DO17|_Qh=FJy*)8W4UT*%5wirN=VeJoh&YFtEl?3`99G`O5~EVRYkhY_ zT)mxtcRKNc<0dapI(*d~a7cD4V3(TJF2?+9K{*N~jL6YXiIqLrnc6A*+a3Z1FGgM; zzs0yv`?x-rz>Dgl7iq`$4v53lx<9r3>5Eqj-ggW*#|^J18k7HALXFb{45D1PL55FBkab)@WM)9H{y+kLYljSLnV+}yXgw2F8( zqc?fpCoEF>KMYDo!HvsC?rz>>2#&Hj4;iEkEMl8Vj-}eieXX|SYU-yc$9(sh*@%Mc zOh~#uDx~9fwN{>c$PAoNEY3O97W$Tnf@n0!a^B}W{tG|>l!JEEbtYctqQPqwV=&TN?7kgN17SKO=VuvBQL4pPBtr>l z+n*KmxkW%L5MWbC)iJt;EZHc)q4CfdO`p9)_%>}|RiD+_gP_YcMgqD$q%yt*O5|8x z8Ybh<0YklQGO)Mt_Wr&NM4gF}&b)48j?W0XgnK<@vt+<4XvhnEFPv1eSGi|I8 zM&r+1U>rb#T1dJn%;=9X|BE{q;LCTZ?q6Ga^2bJC*x=?$C5i#n?M#@sa$6gAp|BLA zvFSVP8^rF$wBxQNd01<-CJN2>Ty+QS<8EA)ZK`$(vW?wZ=WR;ghc{6?uO?R0XW*N91s+;jMe{`zT4;!{?msWpKCkCCjC)Y6UNJ;rx$?$8BGV(A=o-}A2p zDJ^PTQ_e%)H9nkIq9Ms$nSww3pywVHMY3^~9R7Aa?IDFQe5rE#{rOlfBW`^VTZl(s z_lySb)GS(0RQ%Fw=O78KLXyR02m|<-ekPHJ@a5k&!rRu88pRV#fl^6PeB4Db`=louEEUb~ZzG$4w;w>q^m6XT>4{a=;3DtMf1z zp>N|EC|Xt$m9m)U=$6u)WiDk5_Lblhm*ACRWgX|CPdabb)30(~XWQM99**4skfyVg zpxE6(wv6%W1<%3i#K-2MS*tk=rJKy(8IO=H*%m*WUa zq0?Mf`@|jXUo@~71pV~>Y@9|Wb2X=9I$>-#Kv#+I#O3*ZvGVnk#p{x%N`YcqX71Th z+2XY2ew+=>Q|H)Uy6?(0ZqJSH-#QAym{+ixiUgdm@)n;q>vuM6Rx3O$UG5Mez)Es` z2`UaOA(iu>@pKq}Z*caoDwvb|>GW6F3dKkO5;M%2RaZb2@3N;}f@k7~HBD`@IiRt& z9|gB-r;l=EpE%946xQ!AwIjkfUhoh}A{!3i8$USz&T|3@vNg2JzBH^cTQa2Ik>vF3 z_o}D*q!lu0PV{oIR@WfV74E~ORN*k>K^X2MZPd9JVTLt}{92N#=m43#ui{9n8G%Rw zaSi_Hlj}E%v5`PZM>73osIkGb-`<|;GF{mQ7!}e-946};_&Lms76b)UNmO=Wl4h*EhpGBl%01mWw_?(*5488=_EW^QYi(Qm`}>6{>Kng(`}XuXt~+pcIPV51 zgD0UILT}g;#mWa}dMr#QONOc69602R!LT!5$3{gvw^DD|`^tq4 z_cr;YN6dl0Jph!!L*MjZO>#Wk9H`0Lv@kvqqX`EiYA)!;Z&p^; zjLb~UO4E-qj`H#&`*St@)wR`@Gm25(zgEyLgkjxz*g07Sa_0SvK3GQ*y*2-+8h|i_ zP_%=8jR7a1qiN(~t<-@EoA-?fl~%57O7eFqpJ`w%U7>{IOu*ldu!LFr95f|N9DI;Fe0b9>JDe&_o=_da+1 zv)8j@t+~dSW2`mD`;K>afyF$3n+R?t$~qha;j~WPDR>XQ1!XwABq#^)hHL97VEBNHe*S`CO%jo3_aJ=i-_p~NIAKyd( zLsdd)Q$&2=b91-7v8?&X2#R^K*c8e6YJbwtH+3t^%iVsYEcPpsVq$k}UpKf{_ED0| z20Vb!_4xtQVK}H7)HwyS)p9q5gQAKxdevJ|G0AA28ER&Lo}%4F+{$=k5)wAB#OXd( zOc|u4WDDwQvN)0w0W1Q;LH#Fx^DK;XP{Rpea($0|!Fw$BfZ_8>LZWuYwyUF~b{%C7 zh$HwSqa-hwPy-w$(x?)2LHqh5SrBXQYKBBaoSvQz^!B!9{rRmonEDSWg3@0O3nt#C>f0 zBaXcdDTtehmlGhh_fbf|HRMCz*LEVH&A*UPQRRYeXU4yv@D5sWZxhE^%UPG$gan;c z?_&f~m~5b~ak{i1T`8 zTG<1@81#R;L{|p-Rr_xO^S|d~n?nQVG#v(}?TVlT6bGC6dq{y@Nmf4SsHn}u|NVB-o`>t-!V5QLQ9Jh(TW`#6Z5_r2RH$lfSi1` z?6pYNGN@3R2H1QnsX)!#w&|FI~bd>9!S0nrE7wKC2x#8k1LKR+TP3xiuC&YTW< znk9gD-2!I4a-9KJ3bDTbgES+I`&S(|q~hB*u2m3NR2Ji-MOr}EW{=F5uPZh&ajMSQ zy5=enVD@eRXe4;}FcOSN3K>Qq4b)4pFfm)tRKZD-RRE59aCq1TGYA#yu+;)MFV2Gp z6e8X*zu6mbx@C7TZ+3^W&{ugxCDd(fY=El7S5G2fOtUh8T)z(rQmZFTn@KkY(h3Z5 zyneoX`CHD{7tU7pS;GIVR7U}vNZMVtgQDV?m$`fK=>6%MNnw_KL)=xR(|!W9R$l0 zYf?Z#Y;gh+nh%YMj`ml&09O8wg+J0lai9+ zjx-O~-9n2Jh3v>c)mlz4lekkwip;+u^FfDwr*%PlXU-ADvqO z4#$8(+2}KXRE_&DP-r}bdvMwU&su<+0hG0oRpF>0$p{ux4%C;G*nH_c^BRQ9v(8|4uOu- z0xlw`o%iNN&y{voRtSiRC%_W|NI?-~{M^jU_ujD9PE~<=>-FX7DWDV$2C9yLl;jGY z0DxeQRh#q9Ou|iJvz(Gr*C7~3R&ZkZk&m!>-cm9}0uzo`g$1r@Kgc>alE#k5+ zd45Xd9~yWdRi^bkQk-;kx1R2)F|KSVGbro^2T`FAgsB2{DNE5+hBf{k%?w@5BT$`b zzPt-pE}eM{VS+X%mG}1ke^7sMScX0_;=6h}fxiYMW+g!f!Tyz=@vR0f3A;3x6nnP3 zyHcNLr_Z+OsIGZ3cQ!9Z<;N)1Xpj4pg}V9Osm%N(Y|LRs*qHO z-6=~)=KodNpkiO~uP)iAdwA^E9qGZIP9xSd?r9hXY=&2=sMxr95M)SaJ83V$UZ6iZ zEY=J|k7Z|*T9f=yIIIISO;h7u8S>bz%8g_;5I+}9K9h-I<)nb>2glS~4|i1Az@^>O zXbXbX&mWXiO**ODVPQYAuyd@g+#*bOP;RK2h@ro;gNxIT4#)cVADjGmLM{7!iwXdy z8rC1*^YNSS(Q%MXJ^jsnP7qyiv*iYVepP~3wsYd~5)?dNF}z;vCMQw=5IAV1oxT)a z+77-iG4f3R=2tX;Sp#jEw3uP(-`L$K zCz-7Z*ggE;S_dBGqyLTD*GknNBIS&eLV57O?C|Boe=%ROg`zVF+L7W_-7WIGSIUaB z&jrs1H}s4TFjX4;0Tkuhlf3)LMnGkjoJ(V7gWBA7-jm5b&l@38kcsLSP_w7v2LLvO z?7_EK>|Ew+bpIII{(oS5YHyj!N`=^SyY{bqZtMU3;dR9@qqdGi zj9Ai3L+S10$<3*?gE0(@RQArFYPr#5mws9dvsZNvi#!(XGYZB%-sNxNnb-J96%G}B z`CQxn8E&l0U}?ERJt(0e+f1dODApd>Or`PY8Fw?6_5YSiC~~d;wK?^-0oEAM2HrtOO3J;?^b9+5e=9#*C{katJYTyRE-P&~6z4#8 zSQ$hpCO92c1H}Xknf^2Z>>8bRg>0creSQE#z4#U#g2wBjZ|iJ3|5wa9e)hLugC&%e zH!!5gp){ye%xti;%$XmSmCka$;@YcydetIKo7=@)KO5xOD`gTpOHM=(Fa4|8a#Y5i zWnHz%`fmrMCgHy@S?Ko+-)838syB$2udZ{E^Eo+VtTy@dE{Ta<%5qAXCr?*F?c2vK zcNSIu$w2Vx2?cWVCEArRKt9@0O@D#ETCt?a=nTkB~U#lIWmAmwsZbhV*TnxG&Nf~h%I zF1u*-`5a{{!y2~j|6f;8ta}iDfc$Km^^*33;f2mqn@$ufu3hX$D1XOW|8r{SGt$cg z6gE&9_&ez)iHh~9l%J{vG=LtazjL8771kM;Zhxj@+uIX0X1a09G0ysUbPHPwzNOt8k0=_B~u z*Xafb&uM~`7P`%CQ+CMJKGy=lg?nTdTCXGfoR$Aaap8Au0oUok3bxhTaY*ZLG&=qH zgfAv*`-wA~;{VZGAg;Efy*aniSnocY?ZRsx`mHZF{5ku;bql?^Q*Tiac5p{1D9`1q z+pE!|qvzfSIp+gvc=Q?RO{`AkcNk}RE(OUHuWc_F7a_&2Q z*0A1-d?-dMqh~vLo#&XHj{=eCN-9$KIM1HI64UB>QFoPCnnhA*|DMOMM)S61>2;h& z8-CLYzk2eyzTH5KH&&e2FW~n1-S+8JrL!UBZTVFm4H~INo(7Z2F0p1UB}?yk%kN*# z8PyE1CRf;l7KAHJU6)_WDx3&@=2z0dA)qaMWutDT zz8^ITG2~Ut;E!KxD>xtr&7ZmAKnCD-9o6J4it|+#Z??n1BNuQV+``l zmt``10?oL8U(3|jCm$ZE8OX#wK&zV;EKIt4I{mlfI{f+K^DmZXhcgs(Y^pVi=mn0| zW^z(5Vv|HchqP7|n*;atG6763mo8R&l51xk;WZbIf%3@sGU6wxf-VEsX6{`Ff-o28 z)10HXJL`fJLIFzg*K%X<@@=`dO^1hCk$(t1wZthm;nh9fd5)D*zj-zFTdl?*0>8>xQcDSi0K_O%%4l=IT+ zvxGe>`ChY|Agu;8)drocPnLGPyeUjYZhCA3p;q%|wW^?<_Y`L^Sn#$)WOH5? zot1G>07gFMnD@A|1#Bn@Ks9L;QEkR|?nF$nioN+3&Uc8O8rP+}iVdA_7(Zxv4?jSvE!>@1=lGj<8}&slCB{_;}Hvc^ts z?g+$;29po07kG&`x{3^#rf#2ddlck;`4%NL*o*Up^}(cjm+6v{4*iMY!i|8t-Xo$Z zx2`JA3~WH{3se`e^G>-e$lo66P7>MMxrvDO-C|Vz4?U^KPE94f^Rmi@;*QiP8R#e; z5l<%^sr&Zg8DxhcRa7Q3sruQ655K-9Sh6KU)?ju;P!0=jd>4mkQHHyN9vKztMHH^1 zs$3K2)O-XQdRFaO`u3b&D)PGRv{E)p{fj7+2TyLhfL4G)kwTfy&O?si=r@VYh%_3>rtLB0QSvLar_SRzWM7&ylkbA| zCiHhKd-$*OG7d(_&bzn>;qC|TNSyVH1@eL@zPk#l;i!&h^vYs`Jf`*V_=p3AAAMFZ z7gOv)IZtipaURy2QTn)D?ULs<`eH=S%=WD{C5VQnv}3k`q!L9`p=$rHWNI}7gZxZG zlby#}5cy9yaeVTlOtZmO2?^k~{7$w+2`+$rG^;$2Y=5dDH)h7d)48mc<0I`%C48;t zUbzY_EHnAEx?meRx@fxZ*L1`~q+2K~t)8K6vmK`_^L4-e*Goym`8Qt62PmGseHLWb zdEfMY`Bi5w_U9A|SDI=TbsTbz?otIXuzYF?wh)qH4wq;py++kJ)4Mpdkm$O#$NIq6 z)fSvqlVIZNCtR5#hOKd~Q#rv&?U9eip>06+{y@evscW{NLylbGC3aEvX|hXe)6iJ* ziv#i|23wq@D)h3C32BWTOLuJQUvOkZzgco6@c3!~hMJdxeBpC{_Dw>X_|NRZn!q zqCPMusu1_Z@b*aihqO#4{nqQFncYEicg?axHE#7(xiuB zq3$6TfCQDj6~Cp&t_-I4aIJ<0@;aog{kYR=l%ZA2Xr9ttq5dY^|%= zQ|)G2cYEjH0K1-vq4z|A(r0&X!xX3Z?tB4@X`=#y zh%DWJfzFLi_N&q`K5GhE=hd&ZS}%Fm$&M_QzON=mXmyO$pl6SvS1K$gfwg;&&(6^9 zdD~W2dGo(cQ0HnpEtOxJreqyO7{Eronx$LJ&sM3j=LG%cQMZ8E6sm;vb)9SYN?~2N zN7r#MPY5x1XN%a5%h4GY4HY=*hbn_wIlr9{JY^%6t-2fFkXY+j*6~in=>K!@&?rU+ zHz8{SDjtcrNOX#C&Nu>TNMo3(443a+YK>iRv)HFbskuRVw@dR_pn))&U(h z=}cGTG5+ZcWAx(uJ9)uFB$vAbdN`4 zWJd{UZ~J*E39L8BLNT3aA@DepAk%fCRxWk6WZO}cKkKS*T-5D>v^s~4&#i^U*ulY9 zuSRsKP@|cqEd~yscx@>3lcd?;!~7Tf-+h=f+c$4BG2L9l+{`hv-60sv3=lqiR8qT| zmC5gty7&8M8Ox2v4U>S|2WCgB*VD%@~?9e zxuco`!4ySP@(2kZ3D-L_sC68!>gp!Cv9?Z*=0*2fWt?*9uYQXoa!P-x!1ZfAORQ;^ zXTJYKS1dR(HQbabl2+$m$4w`hvq#T`K<$qZ^>9(E7b;Hu${8J@;8Dzn3V3jKHVq3#2=$3O2<{Zf zR2YB~_|N{o_xwqhwwJL}{hDs?OEKR5)_e?VY*E)SzEO$0rLX|k>ae?#^ z_LsWwgL52t&meMgjiMgm>wIj96?1p8H53?Vkyn1>%crix-RSS&kS`HmmorP&9P z`vyTFo0XFRLv3>M`#`~{e%FGwZZ#!@58XNn)Zhp@nQ|q`?Mk{imIgbvO#G*4;oe&o ze#Yj-1dS>8H8t}k#8nM+Czx2*Zd|(f+!;9C749}}W)VVJ4PhD0ND#A7zL=Nu{X-Sp ziz+U9)mOfYgArTC$7X}pxkL|Z4cf^n3HWWeiDGm}p&jcED+H8fNfi?k$_<^#+$X4A zujndnoA1ODV1kD?gJZISQ*k3hP43(IOc@|c}Bq^ka zZ=n-qu&lH_A5m)Bq{jbMMRR=<7`Fzik-koRzJ%`ImJjOfO_-i>0DbGz+@=}UKYD9l zG_95h*f0C;Ytp9iXW4wF=4@+?@sA})`;@8hrtyn=c*lFR3)7zXiAb&byne8%$2n8< zU;Fqs-KgYj|6hKU`Vn$f#qYxi$@A)%t{~N&7JTu}pXoJqnuV*asqTYnWxzRlXW`|q z&`(2}vBq@MEXU40`n^n9rEl8Utxg6A*_f?t?dm_>XivFaowf#Sqyi|AxD4gPC!Q~{ z*+&Egzpf_eY(L;~An}pKCTX2M?N<%6uDI4BUouDQ7`N;`++&wiU8KfwtaW=U%qRJ* zfq{^YH9<+I<8fyr9S}Tq1==@jF|W2)O&hvCo@sTgj=KdI@)(E_npr)v{jnM)q3$lN zrj^tiBITIMUCUt5H`}hpAQQo+_Ng;v5f4FL>VvZyuLaFWj_}s_R*L}y;4XmfU>T@) zAZY8|6i=h(u+do1f3Z;aPxp1S)|31A(pMf4vEs0zb-#$dL-4`qGe5d&hb+yzNK4}& zkn?2^J!d`xIZe55N;mU%)(Pwdvb<`JHa+)q&u>j{A`&@U<+K6-V!f8> zKqk_xqGeP8G0WBz4uR|u`@We7Mg}>p7Za535c%y79r-_dAnAdIkDX5laUIzLo)?we zu+Nz_0!}Qj$345oe!Xq`l;_yJ)T=p{n{rEHCx%5H3;o0zaU*dP-^o%>@_tgWdBbEF zJ(g$=K~=dw>XZCDr}Fv>Uq+wj;yTQ=g3Q!fL5d&vmd*{FzP=d5fo$BK4sIW0ibKkG zzp1((|K@f>a_{WNgGHK@Rc`GEvi#Ur4y3PIlTLjLU3F>fcGG*6!?HMmV>~qigXgM; z3m6Zi4b>u84BLVu(;5jk^$>4z@+&Qe89LK6)RM+16{aKex%TlAg?CdIwkCNTIp#n! zin*I2oU)!r(9!(o<D@u*iq|OZeMqK=G$sQB?y*I6m^kyyFab4SOiEW~a}JSZ66`dOSZ`vx!GAeiSlh`}%{0l8 zpiT2?y7gy3^$a81q3k>&iI`86!oN~M}qx_xUKi8Jy*OB{Q z(?1%i5hN4d@Z7k%i#w9~%cx+TJ->e4((yhE2gF=f!Tf^ekqHHrn@d7D?IWah^To6T z&0GI*$?%`E$8QPoQzE}A^Y^O`l8S3SF(nAqHY3ufZ{u#*y3RdznA}iFlnrm#pY>d~ zT@~x`^V$mTiIXdhhYDTyGH9B!M+ZY~UhikuJeF?#sKF<|bn&tL4*uxzXZ9@9H-ft^ zmV)@(B3rGqG&TlR-=A$eOdhYNwQ4+Fv#?WGwRI*kZ(Z&zf8f^kh(YEj#qs&M4}FgZ zTXDNJ@#4dD*%td=EPhfFZ0yQ)VZZw~6r%0FrlS$=LUSg%wzU)MSm~E9==sj6Pke~f zgJYrqNkCHOYlNmWkV{L7RzKAmr#<*2#94sG1=%-_e}o-e{5JLg-PQcM20ZWtE?}YFRVrOO8T|0u)yPu zG9SjCfSr!sWy}00Ra+Ue z4rd49k7pfY*nRqkj@QMgJ+o-ZWFW_$D^GujfdKVZBKW91@a)y#-q1^x{HQaKCQkX76wWB@hTO7T?TcIkAxJINrOsNPO^_~YjRtG=KzqC~A+ttICe z_|MO-qeqvO?hE(H#zY3m7<_hz=~7y{#m(?^D230B`2$wuW?tM7%f{@s*7g&L_fxVK zuS@Q$8%>sZb2cUTSUAe%!lv}UN8Kr6eO9Z?5~NI#uh((c-*cnVkhxxhjbiP z_9FX%Tx?}~Rz30LDg~r2n}L>V64zksb+c~?rZ_`!OtsfD~ z49OqE>PV&rrn`|um`>xTOa#Jo9)0w@kAt~zBBwgP)Cv&r?MPP)8jaI(ZwCAAykVou z)5%HxjHNRxWfPI7&Y?t-bj|SL#5<4p>+PGZ%p$WP`vi^xbTt+pQw^3G0$IqRR%P*a zUKIRedDV6}#37!Ds)5Ti3jQ{mk0jh3@^pAw+2&os^7o(haa9r#vYKFyq4;jlmkSJo z3v#yJ@^UA=VRv);9*#mAn|75crQ5hZk9$BEsVn{&&TE%h{B3AjNnk{gJs_n;jQj5JZ{waDmkpD;=5mzSQ|O7RZ7zk1t+dxc?pRN)A861xWMUCmz6W2 zuh=AB$xC0yRm{BPz3(#;ZQV7K6OA#CE?SIgz4h3=i0sW(p*aU#JF8X{yiOiWE)5Kw zjv+nWYbLdK_f0e!^nF%%DwfqaSCYQH=y|q^$0e(^e-U4S+Yr#!)y;`R0&nkKJvBp4 zKO+B>f4!V)nk5&&o_~c;>u!2SjD2bRcV)i;i(8_s`EHA>e0Xeb_2wOhe5+Mav1|u| zjgtFEosk-qks4$TIinHWXw|t7dgV(e(KI)g*4>$SL+bR}nG8CLCHx~M<*bUTLrzdb zIx%OEs7k$L>N<7lTXI?m)xAS-g@%2h8%_SOs34NxhuFpGI!3M4VqCk`d>(YQ~`E!4g!WMB&-O@;S;~^rmt~6d=z{+6e=o$WNFM8#Uu`17hp+&Y2~T zq;b|3-r)Q49vX#dlv&dV=5`upR%0Un<-^$`Fm+iYfi-0*D`dqX6fGq!W(TH~5Lnp7 zK0(0i*vg9noFp7ZZi%pB78frL@ynK^=(1^%s7k35YiW;_Ar6x9l&MNIR*scC_xI~j zciWS0ixd)ByeO~ls*oZ)7r$O2=6kO&(JfNh6){7B%L0psgNla-^=8>?oNs@&Q~KU% zG`w*<$7;`Spuo*?BA9D<3AY!c#PnJy@8V4xY1R7%DW1KoaQbc^-F6Xue8?f1^(P{t z?qP(r<4{oJgv7SL_RzjJHX(PU$=-a1m?p|`1&Aw)`|HX=vZ}vdM9IDTUCC)GEo|g) zaNMXxLL*s}h+g+9Y*@qQEa1h4#ADvNszn+5h)&ttmzg3?t#k=r6wUp}{Uy12m5w7p z{P_!~+|)r+t|W7`D_s(T<_~mLk!Ik{y{Y*mtO}6WyYjO|ATInX#xkV5ia@PZ#yT#HD@j#mY^34 z9=VJQJmUrI<*?ErQoSTXIS5dm0$VIcHXk8k8z#N~XWIz>@Mk;fY(;Ft2?su+&lI$n VbaR!#->f3ZNGQH46?^0Ve*n5ssHFe^ literal 26210 zcmb@uWn9%?(=Uv&6_iH0L%O?5Qo2D}QjqROHcE#GNJ*Epbc0B@lyrA@*IC>Db)D-z z=RW6io)^yx<5z3Vnwd3gX1+5!L|IW9Y*;+ zA9`m=ZD$iZdk80ql_^6X}F z3cKA7uT;h#3}(pROr~n*tD+^Vt)Db`tR$|6%4%dkBM;4oWMIowTM%()Ae1c1j>z~2 z;UD99w6oe_P`HXHd+F-xytI;wc|0c_o~m5^VI5uLft5g`xYNXagWP;+^hh!97t8TY zT?hGZ(du2YP(fh^f{3rGw}SWjDA!pYk-d-9toLpc_9d2oW*pfkWx^X$4X}q7wsTBA zJtO!{u==F)HLKFgXK@>3nj~SWNiBuueZ&=yaOGPr(6_m0XWySCuQz|5%KD*~zU#M= z7n{`~R~kQ({^n+EQq`5L*doq7o&8Vctx;7Y8fR%zh;BC~L$#WSd+ST~R!jP#ld4$y zkMd*7e(xwy7~HD3RLAW~XNYIU4S08DKB}V?_k1__>2&#OZ**C{eE4*>IUyXKeHhpH z;N7vS>cidZCjDWN=FADALG}zio7*R`v`*B#v#vKy_vtrfLh;D!wE;8ar-6g%8>hmp z&g;dk?E=|%W_7eTpTk~BA+q(wnKr!I!XZ8um3v+YWiTiCS~mT@1%9c>^;dYj{PR=_ zt<|?XTCeR{)DxbW#RLT;3*?eXDCqtALqpc*_>7sBW&Oj6W=v#U6UhKp#qpH~jET zeWqr#rooP;j;F5gLg;LDOLtDcNu!CcDh1Q;6*@#r@}<8PoESO;B90Du)Cz8(qa*+8 zQcRTf-(w6Ko(!=VG`jCiSK;I1Pfbnz>0;6TVYEA0mYA5xtXA!p ztytQ4EbHlc-4TX6p1`x<9b;UgS4ZLJdosY2%x$An@4PjbBCz0lV}pNny;GX^kYmyn z>2tXhG*N2k=26Vja_hLz?9&&|Vpwk4^QTyMYeeMsFaXV``Btb(+GT4bC)r6NttX;q zT|hug^hf;m#eyGAXdIoI)Vr~2n;EwSpNZw7A3s?6!{4p3``k!xs~2iA&4myPxMnB# z-JhgY3OOvdFL#6$@(>Xa6f}L)%#n*v<#Spe5_zz8j{{`wm35IcY~?09TVTv09m}>{ zZx*>GU*(dAYnJHccYcmwc_UioVj&+(uMz4RO>XUu_HeWCK;m;c zY(9__$E5oe*=D&)rV%0rYA5&JSh`aedd;HA3X379jX}#a0KX9CW5qTIDOtyeMwR~oz)G!>>I54R_lqq)O0lrvCa zLs{A9&jrwTBRsFx;?4M`YV6Xuo7M6Zv8FP_5p15(Xv+jdyJr-lsrF=~sjGKJ5NhQq z65C9T64Uvq(ovniE|-vySjBEt=Veg;E;6Oce9POmKpxv!Wu@%+*B8{JpZhJQ zJm33-F5$-J#WL%Aj(t~YSF_>nmbMx!{lzDcNHtTa#d1AWtjlwaCc7bgvs*#CEBb>c zv^TGRt$sdls=`9;Jz$^fYa-W^bP^pb`UbyOW*U@`})(rR!S3AXz1qrW1zw%ne*+1bBNKNS{lDgD7S1ZE%^?W z%g&#iG_3s8$*T7gX};H6VJywxRdb%-%~sna_o;m&z3orra1>x-ia^b($uz=1d&!c{kFjR;7-EY}4$wU$=oQ_vJVbhbxaLc;fk_WikrwJl(k zT$}yRb|(5XkAba-@HMD&l&l_7u9~|R>qjs0@!uFqOB43tA5ZBa`kKooS)$*dX}cpo z=S`-4GsZskg-Y(cH@Zl)8@KR=|L*!MD=kYp%GD$EBp{}W+28$aTSFfvIP=n1UTru% ziYfdW)Z?`dCfCvo4|~AaO>E7%EyVGifqgPYpbXncGW1u2SMz*iqN&KlceT#SeC$Y zC7UFa#6AROy|3UqKTZOBS$}OZk6m@ZqFg+4&%N7Ye5&sRWKSdx_1)$q8>Au#sQi4- zr!2}F@4!(p-3DW4we_3nDx=oN7I;2j^OvXazkDfiAM`uaIEm}Ml$UPhyCc7c3qLDC zY@>pN7mbxnXjWcfyYQ9np9KyYW2!9%zgY$@n)Q9{aZ{W*C6&B~&$2lSWobJ7axR?m zqEhO|e!kK-Uh){1{i`v8467MQJ-u&#H7-U;IC{pe=?rI&BF1pwDKIO$8tbDgoV5=i2LzTTUuiT{+Lnj?$N zpcy;!aUDv=%f+R^9xq>UdFkFGJDsb4+Ct9B=?qLAFd}%s7BHF^dLMmC@&$&P#QRW; zoy2T~TBFC%_sXoTE6_OiyVM2d`Tm@o$ku#t0kFI=8~f8$a!nLMiq6iQb8X^iLZ%7n zT&d^JhxLKvzE;tf;q9l--Zry9AU6RcBO|#=X}&WyHC48AN%lpG2GB;m)A6S$1TUpA zME^f~ivMTF5jdRxM-OwA8G@D?&uTg^3wc1@?%hL&c;G_&l=SHo??V0ZLJ6G`Y$Al12ZLFW6Zwlm*p0=ZnGTDE|z(q67ljkLInO;z~|$-@2YCf z1hm}EROfo=DkmNhmzUyCrKwwJas@4uBF_xS@StR5llf~)Xv2c9#%vi4?&mPxwA5{~ zh4ZZMValNeOXeFNzO>EyEVA~-VP?ucwNaIEo?(5@nW)IN$U}1W1trPU%gBePFh?5l zfNd_wL|uZOJdZsW7~DQFxgIf>u=AnBEkx0cma#-xqjH;?#9jW1PdJt5sW2_Q1}>$A zLmxbyqi0D|rGuRQFs=M4@qpJv6Z853jtWvcl3{4@{qm&?ATnK4@^JD~rWtM-qrw<6 z&d{wKa+FyDNyFEjgur5nG?nE;rA%!Hhl4iR?AtmDeL=54o-YdR#g&|C&p}kz~%RWS-Zk69hUrw+QUzh32ckae&Sko0AZ4WUANVL#WIqGEA zZkgLsYhG9ld78d3jO0$e5gq&9rO>(O$uQ6hBhY^moy~~REsII$XS(1uhy$a|?UwYr zO2ZuFB#tnR*BtJrEMjZ9MjSG)oqj`9xMbw@+qQnU+IX!`7Yc=;ZvKuBQHoXK?X7ki z^LFkML&y4wDC{lyvT1;@P>J4za6fIJ&7VOXgG$cnV!B>_t$Pti_9AJ2qdKZqi>b0A z;gFZ-{jH5c!Qo_Xe%Oh=|a~gsp-8{ckf@GY_&o(r>DL2 zN2Y@X?UOn2WK2LsGACahvttq-w>ZAQw{!T01CMKzwRRe%-~^ZDG9lwZZt3393I3-9 zFljw%^|Y`&tuSZxV$VWh{}_h7;rh&i$5K3NN|l5!h6X>LwkKuFDW>jiG{&JIy5qrvK=>Z!Ihln%DSxi4p8?#1kC_(-h9aa_e8u9Fv=9 z)lc+X2DrUG*d6luS;5iGT;h#1(`PMkfYlKi67RddMbd6u4)vK*AOL}==#QY3oMIUZ z$P1}>5+mpB+svLcp&*9h7mhx4BRmd;Ww%pTio(YkYrR7Q7TBb%rdQmSh3?RcH$0Yq znMC6Hm0LI!mwALZ*D2-?ZTMVe-`-__HDjKEy0t;MU!R8kWFJjA{_i%Et5WMH=Vt%; zEO#%=={zjg^N01k%I-~|&1CM9wXE7|Ij1e-{3TBEPb#^vlr&!cQyFLMDd`P(N-(1U~&K`YarjoDA;~BJ-^M zp#aH*b|HDx=V!L$=2V2F)k~a8L$Cial(eE4x64r!S3(Y}R;fzaYMbpshL?$E5Xg>r z?}lJ}-l!-EX7k2lvE5JXZe&~_NEZNoHGi&ig1JA zRJEm!onCK4U+F&9qQl0b&Er_M%$-FkP905?=T9TWe#p|U!z~XP-g7|+O$$qJtcE$$ z3y%$JGh@x~6z9IhM9O&~Z3-U=#LWV^zjJDqwvBFlw@N(aZb;U?L0e1ss^8A!(lJX+ z;mH-HMY$7_4JAZ8e+GGfw@uaDX~^noQUYH;pCkWc%wr-e%@I2E6G1X(+jy1no>V^c zTk3lj8)fPY5X(t3ph~SDoSPqbin5~b`u{#LIJc*^80ZlVd}irh^d3S(v__df$x9=L zXgkKQMO=|hMAPgLA!4TFq9pG=`(fYe#h*V;M0Hn1 zCZ*?$iP5Gf@wi?S@KFE7MwU>qx;M6J`l>e1dG85l2+%|*|FrZ(zexL?#><(slB33n z1W`k@9FkW3#CgVmi#S-@D(vI@8YCA&j4WC_eWXv^CP+pzNM|}s`?ni+@{UpV|7F()cmBPqU8(hzRF>L%nJ4M83d6M#BOjED@M_MPWmJ&yH@MbJXTHfn zS}rNwqGP)CbS|OT&@~ET(-8Pp;bHWQlfoL>xR&ExCccMXF#A7g(Nd(o1)CH>0bL`Y zeT?fHD)DT!14+hkY}Ne+V7`u?jbk=lm%J^bHQ z*7&qHS!JYjeC*=m_@Bqas-$k?{6A9)jlJ(waCB2ztA5Px^*U+~W8BI~sit?6Jm$>8 zr8#B*hfgLLdBdF8aW7|djb3{C#~D$)e1S+3dBS#rRT?k)ZrBPm$4m9U94rp5)8050 zE9pPQS;?dNzY?6qElHs0mWgnTM4t3MV&X4*p z#vPO<)D{7vAWRKD4G03)Vj=;B1Je7Di$Q~;n&_XzlERh!()vgt@tR& zi2hkl?_uqetCJ&K{7?1GkuHgXJFhh=Q0uJ6!wF}Pb&G1tYvlQuwHzl*h6HjuH3|n5 z`nMi#kXrR0l5DlYZ4M*hiB^_AEQ*S<&Yp^j+Rwk*mP}p!dLHn4qhD#3FXpxxzTC=d zp@cE@td#qyFhZ##ZGIyO!ft&W(IGt-2y(-x&!T1YOH>&-4>8DVC zqmOW}>?a!B&8I_lXMLUiIQ2EZF^P70H=!%iR!fGG# z9X~J;;pp~=sTC2er|r>t(Keokra{n?cjq|gk5xV+VSl!LeQ+pK<%sRBI+V(S*A;{5 z4tqG#Mtr>HjJOs}L_`KBpdhh)yP=H2gd zQQY1abKjKWN#y#?TP2?Ni}K z{d_d(0C%HPpeODyT*z-8`YqR*^fpq+@q3Ac28kroV&ZZv*4~}~e;RJ@6X%mKlDoJd zdd=^>6<&b@$^_nM-hF#$+Y+)I5Eh}Iqv>0-(Hu?+Lr#jy^~HEXT4Pr26`=(a7-g@o zlfCUR@~FEilBpaw+|Tvce3%-ZvpdE;gE8FyqtVvFii@}eBWto zY62EZ)?2}372AWGK(_&R9kcVEiO|&IyIVsiYM<;T}l}XF(0bf~RbL z5sc1cc!*MnFCj5z(%GBBu{DO5Uzoc1W{}q(#f(Y83n2dCl2|=><%-^Tv7=WqyY+)x z$X)z;!>RcrPAiox{lI9x;o2epJiQox>G}Fr(H3y^ziVk(v&l(_+4C7tL2O!K>Q#i7 zk<Rk`t`P#tH{_?*R@?rPhzzE6m`6-z}`iz0Ovy!-}YaA#9?L?7g~+ zoK2un8002=xy>_Tk!oxGV0{>C+F8@g-idY1bjxY{?<%=br~OZH9`z01P(gW``dDtO zy&NUai3c=aej-<^#SqPz@2siS3RUMw=z3~@YnYm{)neCI((Bs05jyj3ult9fzD#mb zhh)e)eAm%d_Lm%Y7YZ)3UzvL9y7i+-s4hkDg$jBU+AdC3Ul*4cGB@ly(hL(Fd!G|e zI|uhUV_!L&hrEkIfcjv!ny)mSVC9Fo+17C&Yex#FdrJ^qYO-n3)%=zoUQgV3c+c>J zpYbmKvA#@vQpfMel(a*`4Sj@`m51wTZ1`PB1tdyupstPZi7W*1TmzDfVyxsF^>9%x zrlWDSVU1(SYdq^gcDEXrX(@0wUPdG|drJ0GO-Gb+(i&ZWJbd%@WYQUt)+i$digdUD zT6UBIyC~DwECeXV2O~}iJY?fif|tK+M<*`7Q8Gz|&sYWn)qtrq(Qia&4y7-r+`7bAmN3ENZe!+f*j-@x8ThTl>$=JSMk$f&DgmWaS)|F6fw-)rHZMaz&~ z)J*$9=+0Z%yHBAftD@5>7OiK%#KX>yd8kn>3D}MwjM60R4Wykdqb5?4-|u}8*) z;~{FXjKubrG+mQR(HtI@zrN&5x!M0>Gl39#;@Hm3l_B>x>U*PYt0Nh|TXp`|^rR9$ zDv*q}a;!IKdLMn%hvsL$9M<%7J@_3JWjPzTB(UUQPDGsht5Tk?WGpJFt&uW6brC^f z@wM|EDLO~m8jeP}cK)A(a3k@dUo}#W4aKwj-0j0y_P0FeJP(H)_Cm-IO1M_xU?U6M zpSr@%_9wqDX4%euJAV4|QRmG7&8YUZ4R3TYuVf+q?gz^%9pj-VYcx|887Uqn3q002 z1@FDfhIPV~DyPsf8dQ#G{dNfmCp4_+<%W8(u`hg4CfZ=?wHtuQeTp>OnDu=;Sl2p_ zs(g;Vp`tQY-ZyH6gs0vJETyXNc?h+ zA!y1@ns^>CvXDbOI9>!Vz)`V4tW}E7qq-mzH}ib6diJ<Fz_;7uFsSR8$?#ro`$=~L&nB|-3Ltsu$$3<)W#WnTdYG4MHYYasIny3a>)1RO&cqs?dnOBys{pT~p? z@6isGIk0mTW*mGBueY0gvfG+go9$}vVkykxCt@`^Ly|pk6dyj>II$O2x#4k8GLTM(oI@mh^P9xwaMZ*h>oE_tJt))qX%lU$8;W2$%S6o?biD-_j>)+KrL z3c~S@g59peFE}6;rl$Q#Tu71xAYv!_<;B-H-+0QQWT{3hpu8zO#-=o#jIsMfc;)U{ zD;*sj1_lN$vz};l;A8mKz?HXjBgWr~J{`CUvoOa|JV6<%K?#_FObR_`Ip>(DARCE*dZENyu(>(Q=s}og+^pAb4 z_~x0Y`(CGhT7}27Q_kBG7UEI6N~!k!S|p@Hfg~FBJl>NpWX|F$zmvU0TyYuD| z^x|N_P<;=Ljg1X#icLi7b0G(bL{)hTN_DsX>L^~{KTZTz4aalM7w@GKB|N=({thasjz0l}C)u&=F2U|Nd1@8h&uO-RECOhUHbZycEBX zygT86qX`I`KUA7;eorAF8?`w!k+rAtkCDKE6_Ph^zgSN<;dAPlkDaqT%e@)LS!Rr3fHbi91djT0XF z>oc8obF{f&3c|vksURv*S6&RFN5PI9N7U{tHLIlML!1byJEPWey`1jCONXyN%r^Pv zjpL_;O7DW$$uspN*-qZ1)|+z+lzjK;ydi>Wl^PJ^<3EFl`-d9sh~gxkwxk|ON2Z-M z-mr`K>hIoueDJg_Nm^1UI4`f*a1YbRxsX4dy-Q?2YzqV5<3K}Nz#e_`=8b`)h<5Xf zV|K`m?GJ6Vu|d_0!&n^m+fN{`??Z0oTaxI@?FJI3@9Y59bpxtBXiNU4T5$`iZRccf zo4l{sd+8ytv+YxHAKxpnPal8>0ut`8#Ox<288F6vyZ>w|wqQJI*?0^k@0P(fL<8CxH20cC@!==jJX8VMThIt>WjJg9wxy7!b?*+begpPwA7B+6Rd+fI#$z z7A*(Is8;RbD1axwqD6?ng9KgeX9?wZv$bsV0n(CZ$O{AyAfGF0zeM-)u7ZON~f|N@RK%C)QNk#yc7Tf}=9!`p9p28A+#|UN?7B@tG5A86r0?9yL zk{l6$8vx*dUA|8da6@Hz1W=Lhh**S#0BORDzB(GqCd_!;yb3f!Byjc z;WOAIB&c1jSISmaR?g0q3zID%9SgyYpjJqV{qhBR@fq#Pa1YpTklfUK_3E+bDxmH$ zG)xLq3vEURfK(~~P4ydGQEXZP$n>c7^TjrRqi-$_E^fO1SyW?mOLTPf@bIuZHmr8( zQ^EU*5`fSPQd2+8$|DEMe4g9_s{*-10vMC}B*m_jlvHO&hkwi8w~Ik>5tB|O;$PKR zApk5$i23g#GgVYoF>~p}6OtZ1YL(0a)gUM&f_Mjy5If~Q zd|0IT4Or0j>(}Ee6j1A9c6Ge=X#NT&V*BWP0A2&7^uOB0Wii;DYv9p&1;8joX7&hD z5n)P7N;uNnU!RbG<}R|p)~Jke7~k)A2tX|$n0LDiVB8fAnI%IE*z)p#XvPb*R)qdL z=tiVn@47er2?_V#^yew1MTUo;PUH!Aox}8HYXMo96ITE8y-=$(@YAQOwU_$k`=HdM z#&#~W)SCS7Dk(HC_UDFD1UCH6S&nd5hV-8Qm18iM@9ia}bmZmeixd$*VNOo0^S{Ij zr?UB0@b)IhspQm0{{#I(Mb1|5K-k9Kw<^lqObT?Y{bZb@E4ol1R!i| zyw>Vn=>s}U_;4d}3|b`yO;+YuYGsYySLHkle)96j!kM}s#VkRln=>>j%FTd1pq40> z(ysX=441{~fc2*q2@I*Dp%VKFN-!dbcx;IzJ+JI_*!?viX+mDreh>HQFo2{v4-Agk zmzkBdhGp=}!QI_`G*_XijwN#U^!jW^4$^mp+Z9RNmiG}h*f=P;mOKJ2)5_9Q;`i2^IC+d?FH*Ymo^E zb;|&4T>v||(VH=aXY=;zWIC;0%(A`-=qqlZVwx~90f8(TWv9oU0;o&NwVkWStaaHQ zvs`Evx@*JZe5dT_cnnJGjxcPjKn2P4^N+6XhUVtxKy?j(0^$0b_a}(uH~=GI;1#se z8BwTFs3{{O1IXJleA2oM?8~LSM;uYO%tMVcVlF?##^f1xcMyi4+3Q!QU*d!bNRmjE zsO*2uQq6mKy})J60@XtzwF^R&q~Ev}BIsfs7guuH&T8Ap$0G_mEJJLRso-h-z42Pt z@=CC19!}?!%@qPC=5>$6l%9S5{MjEK;meU6;oZ#zf;2EJ|4+2HbZoZHsb`mJSJtT(6byl#yod-G zU@UW`r=Cy8kAg+Gfa%2?{QU}w<=4H~y?W6eOQSGaHjf1H%JBt7W-jip-@SVW9JX6) zK+xN2!CUeq>EP*zj=mNFiX9$Yp}?cgMt$S0M`g4c$f@l%SjTyS47LJ zjb(&R#J|s&hqq8abzm2SYR>9CDy1w(z%S4Pq&M{<^xP-5yLf}w@#IaH!QLB?{yg6wa`!sbQz^w4 zH|%grr*M~iY?-6=gpZVl*|wx+PMhq(5CXl!Xq7t;3mtF&iYB>6V0TDB4kt$xcg50{|PEzUH22iPBSEQR6~r{b|;C+_&pC%DOb<^%SHeR7U%WyCKwGbu;M0xd^%gF)1yZD1zuVH-5i&H^D;|>VF=Fr;;x8kofZa9SNlWC#B1SL=@{E0c7Z> z=%HF^`M7w7hxG7!2(BdAWKDTc+P|N%YH)zO%+k*o%v$82xu=jD?eoLY*F0c8B0R)q zE>%m4?B7N3TJ$Mi)~0+>->5+bxf3<-?k7`-QcK3S%hhKX6^eq@(*0BGG9K4DU7{vr zlUY-3q|67V_5rsZeJ;i%lBY-41W7zs_#J8Sv#gH$8{0an`&XXb5 z`-%DgL8Y+^aN|q+gFh*F<^YbS>mc9a9ZC(FRw?IBw zw_q;GPnp0f>!STCuOv+BewRkY@|qcr{lI{$2!$bjoy=>CU1)5 zM?!*MA9|BYS!!RyLz|pNP(TE6VC+0tMBsWZ^~f}Q>4JZpD*Pi0B@)JjgB&W4vQionD&3itM%;S?pWI$GV8uw z^>P$-IJ!ReTJxWmS(Au;=)Eo{{;D|)Yx%z@q7m2y zAVIeDk2HDV&;erI$U7Nb=ph`fnDOGmdnv2%zDC8~zbZ3u5lE zT5gf5CDrlZRBwBBdCkT9HMnwzG0j(8>#2BV8p+!(lb+M|Nqx6J@Mle!qb#RB#p5P7 zh)a~NJ1DASQ0Ff`g*LHzxgY&q+PNX*qA^~eHfQ)HyoS;1HWPiygo3o}s|J)WR1hAk zy~{*Us5BFcc7E;gCLw+I(?3e9)Hj*YH>YsoU1(m&dRyB^j@M z-y{^vj7{Gn{;}h39t>*D+T{F<#;j|==-v9f)iF}jo>o=>C>eY!t%)D~sK)C-eFjje zbbB79PpR#?B3Pdo6p6#oDPJSX`nG-rZh>hJ__}(aB(BbBL(kWfpPG#=IuZfM0f>zo zjV^7Ya!Bz{ms@j8UJl7aL=9ohl0*7*)3_Bwj9%5-9X?x&cM~&Mt)MVuBJ|sSSk4`& zfqVP*t(ce?aG}bs&OpvcvTLGP_u_PG^gaOOVDw%uoSP?3N>fZodTz--%;tTcOUx9|#^qHg}r7 zRJ+*XasM>;F>BUXH@C=S?nmvOb(1S;uk;98rC3*=@DV6DK^5@|)JIWnrB-mC^AZ9hrvzLU>r@edqE7;XdQdnI zoMkM7^Mr~B!t^_Y?^?>)oOp4Ev4WnKytFUmh!%F8s5*MW>ruWWx zVZ6wF0T%UrZ}vM%US3{qZf*vKLe}ywIgt~;QC|}xwtUy?)fV5oZ7JVC6yc7osZ`H(V=&@D1L@)aSpk6AvW1+f{kmwDQRo0*rB zlT)eM-rPKQ^`Nk{S*QIWW1{hk$tpF6(3+~_$cbk%Nv5jHdB^<)3W&@Y@LK)My8shX z<=2af7)!fJ@cj11%wEPecJcmwAQwY zM0ND=flEYAHhD5wXq}4f9>xI6l0hI|2vB%OhqYf4tbq}@ra!oW!G~-0aIwk~GH@%w z$u;lc@0t;_7iBI%X28>AU7bKw^o{CzTE_QAJ26vA==|MyWr_^1dH7-v zgGcGUFuaR{8L*6jw*;*avRCwC7Jr-sH&yk&+x%DHh97hHKfp~O*}s6BGXUI(zRZtA z0Kkn(+CRVzwHCW2&o^#Sd+RTctp3Z`8Uc7&(;2b<#%i4Yh1E=Bez>L@j>FnY0-%VKm50I6m5}>n%}qW2ML|HS1zooQJTGH7_ZsrXrgiMfC-AMG3_3^rhNDKmP_`ky zp5AKVZd+mqtzeKn=^%|jy`<}UrunFm6Ed>gD;aK7p%IZ16q76oDm@;SRFE}3bW_L0 z(QiD7LK)&BBHs^rw5ZOLIcq!9M+J^tui9L?Vw0*29C9NJ>*|8C6MM&sZvT~p0=0=l zJ(7a-ZqJYtXjk9{K_vP`JDfe=w)ZVN%y&bD9PqN#BQIC0h@zul&_rl*xsoW_;?-Te z@7!-f6VUWmizgf5lE9-*hzbn8MS+XY8cdmR1Rmic+}^R&Atofb*vgvs+bo}>hLqxp z7gHe16XVSK#$?piz-Q}dtyg$(srKQeit#WX5`aUx=+O|WfL0O zDg_2v#o89nA1yWruSNK(fH(yig8P%Qfd^82aCPfZdO-rwE&68~_A`o^jFbaTcg{Az zh!@VNwxn>16E>5fH6hcm_@)-I`n-Fei2roOM34IY39C(a4XN86+{SI4;yKB`iM0;* zctF6*zZf!)ahluNUAxL2g?5>5Bt^h@bNDgqb|KT4tGP}8%`V+1YuA)w6rdsZ;YRT! zQz_zO-sc!g>}JV9Tb@HF8(nj0=-wly8YXQ^tL7&CAwS@|9G-*X1EMzTN+goxl=s}P zX2s!uYMIeeep+K|&FBo6fki!Ph}C@G=W}7G)kY7cmza0|#<)t8g2?`#=8Y5#>jyK> zpq*mHMjqoM<4mWj99^OSuXe?R+)Dq?bW$YO>)G) zrcooKmPPv|A(_O>Zf4Z8;vhH@A zj;WKdleaiS$w;kV+b(UcQzvX^JQDAX?e#$)Z=}HfcaXcEt9J=ISEQhz0Gay<;NBwP zGQ+`;4~j|t1eCrS(*DlXjTy>@6jnQnNSo%a3XQqO>(rr%zEeKP!Awml=*cwo#k>dE z+pu$UV-u4G*F9OD(OQQU6hdxO3k!HD(H~0WlR>Y>y!Y>to4;gOq@faT)sH323>*ap}JTAzS0<9u^iylEUXsXmqm8Wz4dgQ%#W z5GO%7dVIXr2OtOFNvf!*V2zgO3)t1RfwscYfmzVX2A~N%4)cDH$p-G>s*oD;Bm}4! zqHxF};AfFxODe6@Y8)GYO#tV~+nfOYP-Qjlx#*A3xFie+k*(CPe5c~-$~|WwB^4M> zgOqab*WT5Y<$?#`DwLEK%+?L)1cf$F{fA(w4{~yHSvJ>lV{-EHr-3)K^)3u^$itd< zG#T$nT3Md~_{iG;lsJGqbz1uri0^_uCFgNzT-N*X>+!D{{2~FW!OV=hfCM0>2NeLt zYHKIxWVN{N5vY`Q-z+2Bt%EExV9=@3V?z}1jtB;RV|i|AXt=^bozUFC@8VQ`m*;<< zw2XtkSbGkD{D?_XmTs@S3L^YZ*E#Ut#6V75Tv<3SI+~ou__8vRhz~l90q$QVfKEUh z42%rL^D})jbMwHUAg>;Da~S5OtJCCJr4@0@6buv!v};D%J%@;YwxeZWVBqA8y}#~S z(Q9;%CfNXC!(S-l>O*HFaq{QSi%Lqdv9VbA_#rF+jr&})(;ZDYLYX5Xr0(kK3J?>v z5L7U_otBP{4)D7lgeJB|^8oN2bpYP}zqB_2+Wjg0h@ZU81zpZJ{x?W~zFRQ>wxOV* z`Rx?fJl==~O>l%bIIe5G*xo*X?V+HZ#{sB_|M^&jZWL=PrB4mLJ6#$nR=cwv+a?jEgltq1}7R((^1ed+-)_7_}a zRRdfE=+TzslE&YMwduv9IqV!9UKjfje(HdfI<=jNQc@0c zOm*{9MgGagP*+FC5^U^$)BWF;@!8D^*Z{l*9Z}ehG@zV^2gG}f>e@BIP)Z0cQR^uI z2M5Pry4m9@oD%QvZt}9T7YDHPEG;dCyw1m%9G}|>19JGXbptT_DjKxFCNNZ0{j!g; zQUzwtmJztR?2TgpSRUx~?RxJJ&urK%Um9yrw1gD&!HD)ZHWpSk_f2Pe`_B&lnJUk- z?aj@1bv}F3T%Jp(xTR0Pg1Egx5DUUO0Q_3q^t4VA{DMLl4%3%OA*XfZCVNKV$6!%A zKshBof)H~>11l1SShENU2@#h*Mhr|BN%On+p!l_Lf6k+S3vj}a!5_gVOrHK5gDFlV zlejNlh_iTupJs5E1Z%jfbKX*=c-8lnDFhcR512|)He(6pNL4kpBj4wlK+N4de9oJ{ zftW{c1ij9!L6a%1`JbXUpuz%tjgO5@#U_RYk`*rCR{#ijZ1rkv^`TD;6k7o>f%UusUEVBp%Q7|818NRX$Z;)&1AM!u zuvJh}P`tzfUG*S{Zm6u}Yh439nj&TP;q!;(k~e!fAc#n-119ccG`5y+naHvYa;=KT_Z34~82j%NptcrJtz zEQiPHMg=8=FH0ZocKGD2qDsE{l}@gav&Na2BhkR?VM>YY2K#kFxhWO;H>J3=dev{Y zFIx38Cd=Ma)ub@o?%<$85e81?XH^|Hu9kzCDG_j9r-wg+Ah1F?Y}1k9TD!G?mDj6X zT69}33CYwHSINvq{CCGq<+~Bh?1wLDO`@RM3-9VVh+*`DVuvK-k}^j{%Z`}JHlV>t zEHXzy$dU5CO7b2foWwie-E{JQ-nAJ+e?T9gl-TQBURxlJK1&WGX>&K3XD&PapdCy9 zRB5?B`<8AfbY}4B;A))lkpY`s-incn$pFK0D&A2yHLQPzcM(0@8|EwJzU>?}UVdJ- zr9W&FEaM$v6Ehi6hq~z*zv&#LYEG|?U0ff6K%(Xw8MP7Nj}|IB-qHO=PyFTo8;ZK2 zfkv0YyhRth2Ib0Kimy2#Nv+F?ej!U`-?skW_!IB{hCj8$xG2%T{IAW5KS=*4>;8Mk zVm#3vTOm8IpPJ8MzFMQhqAr!e-PDP}aB~!9-5HUzt?iXY4!Syd&rL`(Ecd z=RY&oHSe>1p6C7C&wbw?3H&8P`G(WDTx;UEa6|2xDmB2~G?rboip$yQxY5MsuCN#R z-Y_w1cVx#j^YjrsLPd0w0mv3cDUDyFUAY5X4Puo5?_%7`|LI-S@^81~xdtizKO8Le z_D0TkWH3^C1n=U3lPc)nQ^}r`JuRpWvfI%rq&_?uR&c9SVKA(?84z9CX`Ddek>?k^ z5bkmGG(=fN{uyba@6MK|j_(RC2iN%as^k-rCq|S>!T=@o{OP(Sh{u-THaa275K76S z4)Y9XOd5M505OMV^*lhZoySbfl@4Pr^t*oqlXUX&dQv2O21>wo120d=2aisgB1tIR zPpL!I&bI*|js-L|z*nmw!P|4jp6pu&YNF&&V7CirI}A2M@|#DTh#*xg-IS z>5I@Xy4uwrxyZE{9u5oC-?32Bjah9*?c|#H?;g26PM6P@i%YJWg`H&>T`e0O1HFOa7b~;AQEJ;$=*7rYKC3AVRo$)Xli)6s< z6yyFl%99=E*TF4L>AzMj2j}NeI!|wf*DH;rHJ^F5kP1S^Z#JzZx9= z&+5qCQU|BdRy~{EGrObu2Wx32;9xvEMCx9u{N?(ze77rs=-(HB^b@%4BM8I+^Tzof z1VQ$*n|E~lMvFy7PWWC;&}pFN4%Fmg^v1Sgj-K=t+0a{_bVp}Af=d>hwoKO=Debj4 zl}61xZV5$&I{9-BNz;rKv-Xs;$wJKvw%vZcDa`ufjsC5W^yN_;V4|Lv)cUDgvnq^; zm>aSzW2R0YWY-tlJFy#euC&dbk0B5Yc zFlV~bT|fo=k<%o<;61C+`^P&5dL4bfD}Nu4>)xd7amuU2F5N26`!rji!te>8pB~BSveBL5(nB5uMpL9N`3I`>$Mz{aj~Jpr zL=`qYwp@y@msi_%VUIK}yrJA=VYNbGad<@Ry&#@uADlcZP~Ovei{KYjsDA#t^=;O0 z*Y%V;+LO|@>EK;8y^dvHn2^DRVqfn2MZNW1K&NcE(yKOvc<1*T&2nwKml9Erw1 z+EoDt56s~e#We>aHEi^&`->Nwa8sX9`f~9GD8a|HeY{g?1(Y(vtI9b%te_q(=e&^MW}%5t`7{sk`8r*HGruY zdQPPW#q6mn%|3R0g{?<=%AWStO36Cs7Rl@=>jLRevPB(jtWbVMCRqQu8iOIytf6+7mDiMox;^d*ULa*Tp&}(J~TR%a80Z-Z0O8HesUci|m%G`Pys@0LQIxlv8!0 zJD6rbbnQ;&{$r&aBOWbjGseq_Dw-im6#zRWglm{IjLeH5ZYWU4UMa-LurLGk7F|CQ z_l|_=72Po4usFzQ7m-kWW0=11kIvvwn0B7V+wZoJB-o*Jty^t;z$V+CBKlygt)AC` zRl_Z5^BRSKx3o=K79}kXd6mh)dVEn4br8H_PVyb~No46suJ;VF!1a_)Pqc~BAAo2KNr6I zs5Wx)X*mjFxxhZ&)fUaC&pe(#X*h+K`FerFhpU6Xqz-c2yO&^1E&K<9td#5}DNL%@ z$lNtnqAl&z!-|BO5a+6#&N(w(KD^TqE+h}eY7i!OL^Obewb;@^2(#^2@dh2?j?$^QN5i%&Ej-AmP-#}mym1Y3XkBebDZCzEUF@XAON zt;g%E(!{k;NafnTXQS?Gc{ww5t%bY8IE(vGaDr=^3p&TQIb@Zn$#llAXu}Cw_w5(5 zcVipXzrTQETXU-caYg|)FuQG7Jqd8__4fx|D4F~B8~l-h^Pvp2^Mnbdymm72Jcq$YPMcIe|OA%DO$E)HpaysH@Q`!;mbM?RiEj()vz_- zpALKHNE9x`XJHTrn1lmb`>?x45(BJ>te*=}-pwHg0UpG8bhUpiBL>KAeT33?Uv(H8 z%G-eHT#^GG@uWAowVM9jed-ZQLc@l8M-){i8HKbBT!MvuV7X6KvOPOY9VlO zPT+)-^>$Cr6v|y7zJN4Q-sTeXc!|xNQb6%>HNfO9&ULFxC%g0qYw*ivuv-<#8Ds&1sB(9DtXX*C) zm$6Ep1ad>}DW|dUNven@nK^wmD!?CC-d(VA1VY7@X6!ZE1D7PWl&v!M!#vV^eKDo2IiS};J zRqV2n14g!?lOyD}7+3sKK{Q0x<4yw2E9pBhY=7ZP|s9B8iOQ@r;4X(s!jf^D*L z+7!53cA2%Lr1Gb@^!BvEUsWb2{YnHMN%OL9W%3kx#G(_nN{#Qz@fYb*(I7s^J%u!u zj(e-U`W*FvBDvg5&Nb(w(5G=zW;=;Hbw+0!l(C3!Q`_#g)E{4|kZPE!TD*Q!8?xxW zs&Dozb%aQ@U1u`{_2jr!7oeVivsl8}wQpo3_#z$P4hX3#9c)}T&?^rVt|eIp&Rr6k zDDpm9kV4@B;9;BOrvwY*X(VlxuhIPBAFgu?2v5l{!&|ONSzpnT*X$eSs-21(2uDZ4l~vC(eKJRzPj4C5 zXKG&+23Z(R{A8QPZy*2cR)8+fX*aq&TMBGGP?PvFzIZx)6KXuAUXh_ z%!8Fx>DX)F-rukf`R@99_rLK_X++2?8&bX9G_U0^g-?*5p;9+!dQjT{?kF4CRC0e* zBUJ7+=PZD?)sKR;_WvPV#lcESGVS38Pfu4-kzhAg< z^RBL_hrGz%uSKf1wd$=n$8faS=TaG=yLX``>ih!qKyj36mJREl7kRaV{lqNJC-gJV zLgNEVrq^`CZUM%{*wm+1WPRoD(+^fspM85@v=AS^eP_SF#Hyiz8Wxc4Npc1WYFMjm zK6$?As{ZvH^}ge>tCQ!C5682mB1KZ|&iXgEMv>mpAv(b?48k#zb^ZA&J8A;5x~;OC z8%&Y#T%`=CZN%&5`?%I^%F^IxFP_DM`(^U7A^u$GZd9p?T*%_8d?sr@A4Nt>XV3c*fdxIF zBVjWFiM(VhT@L&}{tIKS{gx!d%wHh3B`Q1wAvyzU^LahrTA;gIr?W9l-B?~$jm+zQ z!t&myt=np@U&gl(KFxt{i99!?;#@P)fFmHRPK4^L?a$I`-Sev{i>|Plo2%iZE|d4X zQ{%0?$fx}QMH}d}`|PUW>HSrzRVl#UH~6M7-en2)9d&I!sCu1Y@s{YO)~#6rJ_9mk zK=t!Q{oGj|Kya3mQ8sr7fw9BX;8=z;w);{k0J+Y$3hC08POlWZUu1&mluPpQeDt(adi}zmC9f{YFn$?~emT2@- z`Sbu&&#N8Zz;&x!ne!?$LAB_9IqMmAz?r+nbMX5ZT`Mc2&QuM)g}rEQl?G5hh(COt zY30`MlfF$A5+`I9i9{qzsujAPF{vREQAzh7O@4)y4_lN6#aC4>xaDZ4v>DYI=d)BJ z{uX4~jlT;OWne!Xb_aWQ;=cAW;z>a~tpFS?5210Zvlon+*$ZS4EUqwlFK|-RFYwDt z(tIA~1SzA}9i9ECmLU|gY?ZHfGV2-ALBHzHd&uj@y$pvBq6SjP zo$RN|_6*K-nWbC#m7la4+0Ama`?Zn_y(o2z+bM;=rt}|5AEaz_K7uEn4_omUW2vQh zjmt|gT6fJ9C;Q7y(!91Gs-G!BXqB&@V(yX%)ANid2A<3KCE;Z6as4Q5Cs4?UJ7I>$Q&Z> zbTR#g`|Gv`-Q3-#$&-njFE&wlKR?E`ay4P>1puc z=d&eG>-NdMbi`)BiN4G@aG}DxVA!Psf;26WB)2B1jrRI;jz++&_X`fRN*zZ-f^5IvXfQopTa}_tGX!x3ZD095+uLk`J zl{6pEuk<5rHOw_G;q~XhbY4?BCiXPA|3)Xf%8D#a+t`4JJ@5;+^|#XL$h`s*CjJn* z1RvS5doMg4%~FRpxhyT^`bfc85<*k}w@-jXCswB&cxs6%#{K}2{JQ25LzoNX9!|q#k;G8Z;jlX~|VuEK%t&*ULz6Hn++CgM1 z!CUKhJo$Wb&~((Y^m_uJquc=Btb<7QM#CXnqQUrQ1LJM0J!Ff0WthpSfbXcFb(LKu zC=K2NbQjCB=*T-Ro5Qm#O$`Q(Zqs{61h$;DFY-BS^hbt0Ybt*y-5tg?J3thfIsLej zOrBlfvA(j;yM^AKXr_u|^GJ9PCjc8iej#={X>S5i(z+=Q{0b@)R?qs`{R%VTg-xE* z`)183n}s6n@-PZ8)R!Uj5Z#8Z?-X%3b)DnkkzK2Zp3K>+h|#pVYnRGCX(pynBdQeE zYWiHS$GT$|7_;IdxUBcW$*^|UW|~ZiVPpK{^x#3R*Nfo45f#qzyMl$)X-Chm0J)z{ zM%42>IjS(YI30iy-!4=$BgKVHqMmN1yhFTc8s_cqb>{5pFp5BF&hy{tIUVee+y9vK zcf#hHk3LC>`CTEOUB#Hrd5f1V8n0I9By%mt78Vz_%SVTvdYLP1&Kr!C(BOZe>)Yf9 z9T+OhoELh>*DbHki9LI6|4HW=yp_UhL40rmdCb&j1~vORottAruH}ECw- zI2nx(D@NGyzB?uAO37)x1_tbutg(FIbCq}W4=s$l4Y!1G*kWSuyjQ0xvhUaiW7WH- zo~syr(nP<1rwVg@SOrw53-}n(x5LZk|H(Yxm^o=Tx;2>6IoB5zN4Ej!OQKDIA{y!$ z+9goJIQsb2EVY7)ue@3){P`?%U4}`8JL;NQ{DBwWstFjeI6iiWbghgZ9=z$du=-c1 zYcn0DUV&{Fh@zBu6uKU#zc9@YNj4G*-tFsC&U!>vy+_LNN}oUhpaZcmrO+55XKJ<9Mi)EvKigZB<6E zYP<>qADt$SlfQ2}DoCR9mIv2N&@`Va3kdA=ejB9M7lTy1rgtG86=47WUA-J7Y2LQ( zY9Zq{7)IWz;Fy$T>)M>!y=d|`kyOQZJ)`Sg@L^hI`3%P*OG@u5_+?l`NIGHthbV)Bl33l z^QvPz>hR!0X?;_}zqip~rA70iom=HjGrHa0 z?%<7ja%Zm3A*_$0c_6JfHV!IdRv3!S&}z6)>&0zz9gIM$dY7q`>#WTMsg!RGR(UOW z02eFv1&xk&)^DgbUd~lI*sfOa(nSOb0U!-fTGe?6nK7cjy<3wD zMv;RoZCmR1lU(}E98c0^Y1792j2#)9INQ6Xb2uc}hJX`_t~rMg!_mJ=|6>`t3CrN- zXje&biz7u??bL3elKn7eLbV&d3LiV+=m|f(;a=Y`DM{FiwLEpuz1tviQ#7J2 zWn5COk?{37Vp8W*<#)Ds!(=X7Z@dxF<%QH}NAHafyd0DembrPM@mVOaq~dkd9h;(yBbWKi_n}_Eq}f z>tE#ayE;Pw+``|DN3@#X6?q*|{~-ldVwR_+1zq1A;6dBP)-c*-$H>e-5p;(Vkm)w# zru^8Cx3@*KKf;5a00B_9K!f+Kd^yG6{T95d!^H8MF?izpmH`1XVO5`I=DLos0&gUR z)HN^e(P0(Mrvz5G63h=u3CiKzv1p{?Ptu!al>i3zLA^8%9y!*1d1}( zTL@-4B2G&Fi4(O86XsyLB})GkTr-UyD$Fv|E>I5>gJyhB-N?rNNf5iWgo>OF?6|Jg zFzA!>(C^gkH}ojU+J+V^TufoN!Z0WZ=^Ae5FguhoRLx>npt@!XWI=kM<*~^*PlIPW zaj`iFq)tk=&Zsv6|En9yo1a-lEsT|XVS?7d?&K3$aSzQh0B}z!?SAR!KbfMZc?|rCAAuhvh3$an|?r*iHD253|NPPK>O_M>q;Q1k1 zYLuKPXpS+=e>GwE#lTIWwr&EP-!RcVN-osA1iM(Z}l)hOIhSsT-xQR6|0{` zw0Jagd!d=tv+Q11&}iEdc+5G@O_#(53ZlVZ1j)<)mGm15cD*hg>!M3qKSOGc{8e8V zGhs8YLy`Hl@L`Aj#t=I~Leofxo`FaJ5IPNi1M<@U*CZ|~U|pU#cz83D6qYHjRR)u+ z<g zQvIFuYgii7Uwx(Yo~6dfadw&ImLE50v_8LwXGmJ&xlUOfqwLha|A{n@#Dx$h0mnYH zFi;EYRyiU+DWlbNv2-m-7e5^>NI&w%N>ft4II_4Xi_r1|>l^4 zUn>+QDMX*QZ1jmm8La5G_0;NZ%0njJt7B2ioqnxLq2@J>7V9znw_dx(!R5`i{Xs_$ zW4#v{2Du@cSxD5;pTtl|mG?g2eg}nIXlA6c{9prch$vaa=Vu?UqS99G^B6XGSFDeg zpdSm8T2S9>_{~tfY8YnlI~GuW$;j7C&zXD-zap(=TtLZ6qHlq^__pRUFj8eewIu#; zq_x(NguhrC;jhx{JWB6bsDru?Bua%LKEL~lzm}F)-y@P z{=C|9{$3N4RLl=j>(Mlp*o2k3AQN(%b`Ea5sZK6ZoIN>(~ zX4Y89I<=jcoY$y6R=m2aE2iX{(|6(VMi*K1u^oFV2V3!eE0!b5+U9324c_u>3&ZqY zMhauFT%B_kt(U;WmgJA&P9i8rD$BlzDY7?KbEz%UB6ESk$h8sAvMt_$ljtKS%~^p> zpPH0G+!cl~74OF*e9uP_AFb;~&UdEDK*pi(AG*-z+n3&s7AnSghz#QEWM#o8zXuq? zSeliPgdGaRDznt*PO&AHH-r&34|@+#S#C|$4%VC29Z4J)=vOZ+4NM?($`M;jag!O) z;6=R51V$9q_=o#KGwcN%6sX-g`kzb^0F*%lc8MS(J!hE+j+y`dVu><> #DeepSkyB Curve pool: OUSD/USDC rewards: CRV } +object "Morpho Base\nCross-chain Strategy" as baseCrossStrat <><> #DeepSkyBlue { + asset: USDC + Morpho vault: OUSD V2 + rewards: MORPHO +} checker ..> ousd checker ..> vault @@ -41,6 +46,6 @@ ousd <.> vault ' Strategies vault <..> musd vault <..> curveAmoStrat - +vault <..> baseCrossStrat @enduml \ No newline at end of file diff --git a/contracts/tasks/tasks.js b/contracts/tasks/tasks.js index e24c96526a..f8d8e14306 100644 --- a/contracts/tasks/tasks.js +++ b/contracts/tasks/tasks.js @@ -292,8 +292,15 @@ task("withdrawWETH").setAction(async (_, __, runSuper) => { task( "queueLiquidity", - "Call addWithdrawalQueueLiquidity() on the Vault to add WETH to the withdrawal queue" -).setAction(addWithdrawalQueueLiquidity); + "Call addWithdrawalQueueLiquidity() on the Vault to add assets to the withdrawal queue" +) + .addOptionalParam( + "symbol", + "Symbol of the OToken. eg OETH, OUSD or OS", + undefined, + types.string + ) + .setAction(addWithdrawalQueueLiquidity); task("queueLiquidity").setAction(async (_, __, runSuper) => { return runSuper(); }); @@ -1604,6 +1611,12 @@ task("snapAero").setAction(async (_, __, runSuper) => { }); subtask("snapVault", "Takes a snapshot of a OETH Vault") + .addOptionalParam( + "symbol", + "Symbol of the OToken. eg OETH, OUSD or OS", + undefined, + types.string + ) .addOptionalParam( "block", "Block number. (default: latest)", diff --git a/contracts/tasks/vault.js b/contracts/tasks/vault.js index 540cb0bd09..1dcfe4168d 100644 --- a/contracts/tasks/vault.js +++ b/contracts/tasks/vault.js @@ -37,15 +37,23 @@ async function getContracts(hre, symbol, assetSymbol) { // Resolve the wrapped OToken. eg wOETH, wOSonic let wOToken; if (networkName !== "hoodi") { + const wrappedProxyPrefix = symbol === "OUSD" ? "WrappedOUSD" : `W${symbol}`; const wOTokenProxy = await ethers.getContract( - `W${symbol}${networkPrefix}Proxy` + `${wrappedProxyPrefix}${networkPrefix}Proxy` + ); + const wrappedContractName = + symbol === "OUSD" ? "WrappedOusd" : `W${symbol}`; + wOToken = await ethers.getContractAt( + `${wrappedContractName}`, + wOTokenProxy.address ); - wOToken = await ethers.getContractAt(`W${symbol}`, wOTokenProxy.address); } // Resolve the Asset. eg WETH or wS // This won't work for OUSD if the assetSymbol has not been set as it has three assets - assetSymbol = assetSymbol || (networkName === "sonic" ? "wS" : "WETH"); + assetSymbol = + assetSymbol || + (networkName === "sonic" ? "wS" : symbol === "OUSD" ? "USDC" : "WETH"); const asset = await resolveAsset(assetSymbol); log( `Resolved ${networkName} ${symbol} Vault asset to ${assetSymbol} with address ${asset.address}` @@ -59,10 +67,10 @@ async function getContracts(hre, symbol, assetSymbol) { }; } -async function snapVault({ block }, hre) { +async function snapVault({ symbol, block }, hre) { const blockTag = getBlock(block); - const { vault, oToken, wOToken, asset } = await getContracts(hre); + const { vault, oToken, wOToken, asset } = await getContracts(hre, symbol); const assetBalance = await asset.balanceOf(vault.address, { blockTag, @@ -97,37 +105,53 @@ async function snapVault({ block }, hre) { blockTag, }); + const assetDecimals = await asset.decimals(); + console.log( - `Vault assets : ${formatUnits(assetBalance)}, ${assetBalance} wei` + `Vault assets : ${formatUnits( + assetBalance, + assetDecimals + )}, ${assetBalance} wei` ); console.log( - `Queued : ${formatUnits(queue.queued)}, ${queue.queued} wei` + `Queued : ${formatUnits(queue.queued, assetDecimals)}, ${ + queue.queued + } wei` ); console.log( - `Claimable : ${formatUnits(queue.claimable)}, ${ + `Claimable : ${formatUnits(queue.claimable, assetDecimals)}, ${ queue.claimable } wei` ); console.log( - `Claimed : ${formatUnits(queue.claimed)}, ${queue.claimed} wei` + `Claimed : ${formatUnits(queue.claimed, assetDecimals)}, ${ + queue.claimed + } wei` ); console.log( - `Shortfall : ${formatUnits(shortfall)}, ${shortfall} wei` + `Shortfall : ${formatUnits( + shortfall, + assetDecimals + )}, ${shortfall} wei` ); console.log( - `Unclaimed : ${formatUnits(unclaimed)}, ${unclaimed} wei` + `Unclaimed : ${formatUnits( + unclaimed, + assetDecimals + )}, ${unclaimed} wei` ); console.log( `Available : ${formatUnits( - available + available, + assetDecimals )}, ${available} wei, ${formatUnits(availablePercentage, 2)}%` ); console.log( - `Target Buffer : ${formatUnits(vaultBuffer)}, ${formatUnits( - vaultBufferPercentage, - 16 - )}%` + `Target Buffer : ${formatUnits( + vaultBuffer, + assetDecimals + )}, ${formatUnits(vaultBufferPercentage, assetDecimals - 2)}%` ); console.log( @@ -161,10 +185,10 @@ async function snapVault({ block }, hre) { console.log(`last request id : ${queue.nextWithdrawalIndex - 1}`); } -async function addWithdrawalQueueLiquidity(_, hre) { +async function addWithdrawalQueueLiquidity({ symbol }, hre) { const signer = await getSigner(); - const { vault } = await getContracts(hre); + const { vault } = await getContracts(hre, symbol); log( `About to call addWithdrawalQueueLiquidity() on the vault with address ${vault.address}` From 6b14cd62f86b5a344279862f95542f2db14f0a47 Mon Sep 17 00:00:00 2001 From: Nick Addison Date: Mon, 23 Feb 2026 16:37:10 +1100 Subject: [PATCH 26/36] Run log - 23 Feb withdraw from Morpho v2 OUSD Strategy (#2809) * Fix OUSD/USDC prices in amoStrat Hardhat task * Run log - 23 Feb withdraw from Morpho v2 OUSD Strategy * Update script --- brownie/runlogs/2026_02_strategist.py | 69 ++++++++++++++++++ contracts/tasks/curve.js | 100 +++++++++++--------------- 2 files changed, 109 insertions(+), 60 deletions(-) diff --git a/brownie/runlogs/2026_02_strategist.py b/brownie/runlogs/2026_02_strategist.py index 4a91b785bf..388780ca38 100644 --- a/brownie/runlogs/2026_02_strategist.py +++ b/brownie/runlogs/2026_02_strategist.py @@ -113,3 +113,72 @@ def main(): vault_usdc = usdc.balanceOf(VAULT_PROXY_ADDRESS) print("USDC left in Vault", "{:.6f}".format(vault_usdc / 10**6), vault_usdc) + + +# ------------------------------------------- +# Feb 23 2026 - Withdraw from Morpho v2 OUSD Strategy +# ------------------------------------------- +from world import * +def main(): + with TemporaryForkForReallocations() as txs: + txs.append(vault_core.rebase({'from': MULTICHAIN_STRATEGIST})) + txs.append(vault_value_checker.takeSnapshot({'from': MULTICHAIN_STRATEGIST})) + + # AMO pool before + usdcPoolBalance = usdc.balanceOf(OUSD_CURVE_POOL) + ousdPoolBalance = ousd.balanceOf(OUSD_CURVE_POOL) + totalPool = usdcPoolBalance * 10**12 + ousdPoolBalance + # Sell OUSD + assets_received = ousd_curve_pool.get_dy(0, 1, 10**18) + # Buy OUSD + oTokens_received = ousd_curve_pool.get_dy(1, 0, 10**6) + + print("Curve OUSD/USDC Pool before") + print("Pool USDC ", "{:.6f}".format(usdcPoolBalance / 10**6), usdcPoolBalance * 10**12 * 100 / totalPool) + print("Pool OUSD ", "{:.6f}".format(ousdPoolBalance / 10**18), ousdPoolBalance * 100 / totalPool) + print("Pool Total ", "{:.6f}".format(totalPool / 10**18)) + print("OUSD buy price ", "{:.6f}".format(10**18 / oTokens_received)) + print("OUSD sell price", "{:.6f}".format(assets_received / 10**6 )) + + # Remove and burn OUSD from the Curve pool + curve_lp = 700000 * 10**18 + txs.append( + ousd_curve_amo_strat.removeAndBurnOTokens( + curve_lp, + {'from': STRATEGIST} + ) + ) + + txs.append(vault_admin.withdrawFromStrategy( + MORPHO_OUSD_V2_STRAT, + [usdc], + [28204 * 10**6], + {'from': MULTICHAIN_STRATEGIST} + )) + + # AMO pool after + usdcPoolBalance = usdc.balanceOf(OUSD_CURVE_POOL) + ousdPoolBalance = ousd.balanceOf(OUSD_CURVE_POOL) + totalPool = usdcPoolBalance * 10**12 + ousdPoolBalance + # Sell OUSD + assets_received = ousd_curve_pool.get_dy(0, 1, 10**18) + # Buy OUSD + oTokens_received = ousd_curve_pool.get_dy(1, 0, 10**6) + + print("-----") + print("Curve OUSD/USDC Pool after") + print("Pool USDC ", "{:.6f}".format(usdcPoolBalance / 10**6), usdcPoolBalance * 10**12 * 100 / totalPool) + print("Pool OUSD ", "{:.6f}".format(ousdPoolBalance / 10**18), ousdPoolBalance * 100 / totalPool) + print("Pool Total ", "{:.6f}".format(totalPool / 10**18)) + print("OUSD buy price ", "{:.6f}".format(10**18 / oTokens_received)) + print("OUSD sell price", "{:.6f}".format(assets_received / 10**6 )) + + vault_change = vault_core.totalValue() - vault_value_checker.snapshots(MULTICHAIN_STRATEGIST)[0] + supply_change = ousd.totalSupply() - vault_value_checker.snapshots(MULTICHAIN_STRATEGIST)[1] + profit = vault_change - supply_change + txs.append(vault_value_checker.checkDelta(profit, (1 * 10**18), vault_change, (1 * 10**18), {'from': MULTICHAIN_STRATEGIST})) + print("-----") + print("Profit", "{:.6f}".format(profit / 10**18), profit) + print("OUSD supply change", "{:.6f}".format(supply_change / 10**18), supply_change) + print("Vault Change", "{:.6f}".format(vault_change / 10**18), vault_change) + print("-----") diff --git a/contracts/tasks/curve.js b/contracts/tasks/curve.js index 669a42aac9..d4196170f3 100644 --- a/contracts/tasks/curve.js +++ b/contracts/tasks/curve.js @@ -31,7 +31,7 @@ async function curvePoolTask(taskArguments) { } /** - * Dumps the current state of a Curve Metapool pool used for AMO + * Dumps the current state of a Curve pool used for AMO */ async function curvePool({ poolOTokenSymbol, @@ -86,8 +86,7 @@ async function curvePool({ (await pool.get_balances({ blockTag: fromBlockTag, })); - // let poolBalances = await pool.get_balances({ blockTag }); - let poolBalances = await pool.get_balances(); + let poolBalances = await pool.get_balances({ blockTag }); if (oTokenSymbol === "OUSD") { // scale up the USDC balance to 18 decimals poolBalancesBefore = poolBalancesBefore @@ -95,75 +94,56 @@ async function curvePool({ : []; poolBalances = [poolBalances[0], poolBalances[1].mul(parseUnits("1", 12))]; } - const assetBalanceBefore = - diffBlocks && - (oTokenSymbol === "OETH" ? poolBalancesBefore[0] : poolBalancesBefore[1]); - const oTokenBalanceBefore = - diffBlocks && - (oTokenSymbol === "OETH" ? poolBalancesBefore[1] : poolBalancesBefore[0]); - const assetBalance = - oTokenSymbol === "OETH" ? poolBalances[0] : poolBalances[1]; - const oTokenBalance = - oTokenSymbol === "OETH" ? poolBalances[1] : poolBalances[0]; + const assetBalanceBefore = diffBlocks && poolBalancesBefore[1]; + const oTokenBalanceBefore = diffBlocks && poolBalancesBefore[0]; + const assetBalance = poolBalances[1]; + const oTokenBalance = poolBalances[0]; - const price1Before = + const assetScaleup = oTokenSymbol === "OUSD" ? parseUnits("1", 12) : 1; + const sellPriceBefore = diffBlocks && - (oTokenSymbol === "OETH" - ? // swap 1 OETH for ETH (OETH/ETH) - await pool["get_dy(int128,int128,uint256)"](1, 0, parseUnits("1"), { - blockTag: fromBlockTag, - }) - : // swap 1 OUSD for USDC (OUSD/USDC) scaled to 18 decimals - ( - await pool["get_dy(int128,int128,uint256)"](0, 1, parseUnits("1"), { - blockTag, - }) - ).mul(parseUnits("1", 12))); - const price1 = - oTokenSymbol === "OETH" - ? // swap 1 OETH for ETH (OETH/ETH) - await pool["get_dy(int128,int128,uint256)"](1, 0, parseUnits("1"), { - blockTag, - }) - : // swap 1 OUSD for USDC (OUSD/USDC) scaled to 18 decimals - ( - await pool["get_dy(int128,int128,uint256)"](0, 1, parseUnits("1"), { - blockTag, - }) - ).mul(parseUnits("1", 12)); + // swap 1 OUSD or OETH for USDC (OUSD/USDC) or WETH + ( + await pool["get_dy(int128,int128,uint256)"](0, 1, parseUnits("1"), { + blockTag: fromBlockTag, + }) + ).mul(assetScaleup); + const sellPrice = + // swap 1 OUSD or OETH for USDC (OUSD/USDC) or WETH + ( + await pool["get_dy(int128,int128,uint256)"](0, 1, parseUnits("1"), { + blockTag, + }) + ).mul(assetScaleup); output( displayProperty( `${oTokenSymbol} sell price`, `${oTokenSymbol}/${assetSymbol}`, - price1, - price1Before, + sellPrice, + sellPriceBefore, 6 ) ); // swap 1 ETH for OETH (ETH/OETH) - const price2Before = + const oneAsset = parseUnits("1", oTokenSymbol === "OUSD" ? 6 : 18); + const buyPriceBefore = diffBlocks && - (oTokenSymbol === "OETH" - ? await pool["get_dy(int128,int128,uint256)"](0, 1, parseUnits("1"), { - blockTag: fromBlockTag, - }) - : // swap 1 USDC for OUSD (USDC/OUSD) - await pool["get_dy(int128,int128,uint256)"](1, 0, parseUnits("1", 6), { - blockTag: fromBlockTag, - })); - const price2 = - oTokenSymbol === "OETH" - ? await pool["get_dy(int128,int128,uint256)"](0, 1, parseUnits("1"), { - blockTag, - }) - : // swap 1 USDC for OUSD (USDC/OUSD) - await pool["get_dy(int128,int128,uint256)"](1, 0, parseUnits("1", 6), { - blockTag, - }); - const buyPriceBefore = diffBlocks && parseUnits("1", 36).div(price2Before); - const buyPrice = parseUnits("1", 36).div(price2); + // invert to get OUSD/USDC or OETH/WETH price + parseUnits("1", 36).div( + // swap 1 USDC (OUSD/USDC) or WETH for OUSD or OETH + await pool["get_dy(int128,int128,uint256)"](1, 0, oneAsset, { + blockTag: fromBlockTag, + }) + ); + // invert to get OUSD/USDC or OETH/WETH price + const buyPrice = parseUnits("1", 36).div( + // swap 1 USDC (OUSD/USDC) or WETH for OUSD or OETH + await pool["get_dy(int128,int128,uint256)"](1, 0, oneAsset, { + blockTag, + }) + ); output( displayProperty( @@ -372,7 +352,7 @@ async function curveSwapTask(taskArguments) { async function curveContracts(oTokenSymbol) { // Get symbols of tokens in the pool - const assetSymbol = oTokenSymbol === "OETH" ? "ETH " : "USDC"; + const assetSymbol = oTokenSymbol === "OETH" ? "WETH" : "USDC"; // Get the contract addresses const poolAddr = From 1dedc0d5b22e633a2268c72a9f1895bf869acc0c Mon Sep 17 00:00:00 2001 From: Shah <10547529+shahthepro@users.noreply.github.com> Date: Tue, 24 Feb 2026 12:33:11 +0400 Subject: [PATCH 27/36] Add Auto-Withdrawal Safe Module (#2807) * Add Auto-Withdrawal Safe Module * Get rid of rate limit logic * Add tests * Address CR comments --- .../automation/AutoWithdrawalModule.sol | 192 ++++++++++++++ .../mocks/MockAutoWithdrawalVault.sol | 45 ++++ .../contracts/mocks/MockSafeContract.sol | 14 ++ contracts/contracts/mocks/MockStrategy.sol | 7 + contracts/deploy/deployActions.js | 16 ++ contracts/deploy/mainnet/000_mock.js | 2 + contracts/deploy/mainnet/001_core.js | 2 + .../178_ousd_auto_withdrawal_module.js | 41 +++ contracts/test/_fixture.js | 28 +++ .../test/safe-modules/ousd-auto-withdrawal.js | 235 ++++++++++++++++++ 10 files changed, 582 insertions(+) create mode 100644 contracts/contracts/automation/AutoWithdrawalModule.sol create mode 100644 contracts/contracts/mocks/MockAutoWithdrawalVault.sol create mode 100644 contracts/contracts/mocks/MockSafeContract.sol create mode 100644 contracts/deploy/mainnet/178_ousd_auto_withdrawal_module.js create mode 100644 contracts/test/safe-modules/ousd-auto-withdrawal.js diff --git a/contracts/contracts/automation/AutoWithdrawalModule.sol b/contracts/contracts/automation/AutoWithdrawalModule.sol new file mode 100644 index 0000000000..2135615dd6 --- /dev/null +++ b/contracts/contracts/automation/AutoWithdrawalModule.sol @@ -0,0 +1,192 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import { AbstractSafeModule } from "./AbstractSafeModule.sol"; + +import { IVault } from "../interfaces/IVault.sol"; +import { VaultStorage } from "../vault/VaultStorage.sol"; +import { IStrategy } from "../interfaces/IStrategy.sol"; + +/** + * @title Auto Withdrawal Module + * @notice A Gnosis Safe module that automates funding the OUSD (or OETH) vault's + * withdrawal queue by pulling liquidity from a configured strategy. + * + * @dev The Safe (Guardian multisig) must: + * 1. Deploy this module + * 2. Call `safe.enableModule(address(this))` to authorize it + * + * An off-chain operator (e.g. Defender Relayer) calls `fundWithdrawals()` + * periodically. The module: + * - First tries to satisfy the queue from idle vault funds + * - If there's still a shortfall, withdraws the exact shortfall amount + * from the configured strategy (up to what the strategy holds) + * + * The Safe retains full override control via `setStrategy`. + */ +contract AutoWithdrawalModule is AbstractSafeModule { + // ───────────────────────────────────────────────────────── Immutables ── + + /// @notice The vault whose withdrawal queue is being funded. + IVault public immutable vault; + + /// @notice The vault's base asset (e.g. USDC for OUSD, WETH for OETH). + /// Stored as an address to match IStrategy.checkBalance() signature. + address public immutable asset; + + // ────────────────────────────────────────────────────── Mutable config ── + + /// @notice The strategy from which liquidity is pulled to fill the queue. + address public strategy; + + // ─────────────────────────────────────────────────────────── Events ── + + /// @notice Emitted when liquidity is successfully moved from strategy to vault. + event LiquidityWithdrawn( + address indexed strategy, + uint256 amount, + uint256 remainingShortfall + ); + + /// @notice Emitted when the strategy does not hold enough funds to cover the shortfall. + /// No withdrawal is attempted; an operator alert should fire on this event. + event InsufficientStrategyLiquidity( + address indexed strategy, + uint256 shortfall, + uint256 available + ); + + /// @notice Emitted when the Safe exec call to withdrawFromStrategy fails. + event WithdrawalFailed(address indexed strategy, uint256 attemptedAmount); + + /// @notice Emitted when the strategy address is updated. + event StrategyUpdated(address oldStrategy, address newStrategy); + + // ─────────────────────────────────────────────────────── Constructor ── + + /** + * @param _safeContract Address of the Gnosis Safe (Guardian multisig). + * @param _operator Address of the off-chain operator (e.g. Defender relayer). + * @param _vault Address of the OUSD/OETH vault. + * @param _strategy Initial strategy to pull liquidity from. + */ + constructor( + address _safeContract, + address _operator, + address _vault, + address _strategy + ) AbstractSafeModule(_safeContract) { + require(_vault != address(0), "Invalid vault"); + require(_strategy != address(0), "Invalid strategy"); + + vault = IVault(_vault); + asset = IVault(_vault).asset(); + + _setStrategy(_strategy); + + _grantRole(OPERATOR_ROLE, _operator); + } + + // ──────────────────────────────────────────────────── Core automation ── + + /** + * @notice Fund the vault's withdrawal queue from the configured strategy. + * Called periodically by an off-chain operator (Defender Actions). + * + * Steps: + * 1. Ask the vault to absorb any idle asset it already holds. + * 2. Compute the remaining shortfall. + * 3. Pull up to that amount from the strategy via the Safe. + * + * This function never reverts on "soft" failures (strategy underfunded, + * Safe exec failure). It emits a descriptive event instead so off-chain + * monitoring can alert the team without breaking the Defender action. + */ + function fundWithdrawals() external onlyOperator { + // Step 1: Let the vault absorb any asset it already holds idle. + // This is a permissionless call; no Safe exec needed. + vault.addWithdrawalQueueLiquidity(); + + // Step 2: Read the current shortfall. + uint256 shortfall = pendingShortfall(); + + if (shortfall == 0) { + // Queue is fully funded — nothing to do. + return; + } + + // Step 3: Read available balance from the strategy. + uint256 strategyBalance = IStrategy(strategy).checkBalance(asset); + + // Withdraw the lesser of the shortfall and what the strategy holds. + uint256 toWithdraw = shortfall < strategyBalance + ? shortfall + : strategyBalance; + + if (toWithdraw == 0) { + emit InsufficientStrategyLiquidity( + strategy, + shortfall, + strategyBalance + ); + return; + } + + // Step 4: Execute withdrawal via the Safe (which holds the Strategist role). + address[] memory assets = new address[](1); + assets[0] = asset; + uint256[] memory amounts = new uint256[](1); + amounts[0] = toWithdraw; + + bool success = safeContract.execTransactionFromModule( + address(vault), + 0, + abi.encodeWithSelector( + IVault.withdrawFromStrategy.selector, + strategy, + assets, + amounts + ), + 0 // Call (not delegatecall) + ); + + if (!success) { + emit WithdrawalFailed(strategy, toWithdraw); + return; + } + + emit LiquidityWithdrawn(strategy, toWithdraw, shortfall - toWithdraw); + } + + // ─────────────────────────────────────────────────────── Guardian controls ── + + /** + * @notice Change the strategy from which liquidity is pulled. + * @param _strategy New strategy address. Must not be zero. + */ + function setStrategy(address _strategy) external onlySafe { + _setStrategy(_strategy); + } + + function _setStrategy(address _strategy) internal { + require(_strategy != address(0), "Invalid strategy"); + emit StrategyUpdated(strategy, _strategy); + strategy = _strategy; + } + + // ──────────────────────────────────────────────────────── View helpers ── + + /** + * @notice The current unmet shortfall in the vault's withdrawal queue. + * @dev This is a raw read of `queued - claimable`. It does NOT account for + * idle vault asset that `addWithdrawalQueueLiquidity()` would absorb. + * For a fully up-to-date figure, call `vault.addWithdrawalQueueLiquidity()` + * first (which is what `fundWithdrawals()` does). + * @return shortfall Queue shortfall in asset units (vault asset decimals). + */ + function pendingShortfall() public view returns (uint256 shortfall) { + VaultStorage.WithdrawalQueueMetadata memory meta = vault + .withdrawalQueueMetadata(); + shortfall = meta.queued - meta.claimable; + } +} diff --git a/contracts/contracts/mocks/MockAutoWithdrawalVault.sol b/contracts/contracts/mocks/MockAutoWithdrawalVault.sol new file mode 100644 index 0000000000..61d71caeb5 --- /dev/null +++ b/contracts/contracts/mocks/MockAutoWithdrawalVault.sol @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import { VaultStorage } from "../vault/VaultStorage.sol"; + +contract MockAutoWithdrawalVault { + address public asset; + + VaultStorage.WithdrawalQueueMetadata public withdrawalQueueMetadata; + + bool private _revertNextWithdraw; + + event MockedWithdrawal(address strategy, address asset, uint256 amount); + + constructor(address _asset) { + asset = _asset; + } + + function setWithdrawalQueueMetadata(uint256 queued, uint256 claimable) + external + { + withdrawalQueueMetadata.queued = uint128(queued); + withdrawalQueueMetadata.claimable = uint128(claimable); + } + + function revertNextWithdraw() external { + _revertNextWithdraw = true; + } + + function addWithdrawalQueueLiquidity() external { + // Do nothing + } + + function withdrawFromStrategy( + address strategy, + address[] memory assets, + uint256[] memory amounts + ) external { + if (_revertNextWithdraw) { + _revertNextWithdraw = false; + revert("Mocked withdrawal revert"); + } + emit MockedWithdrawal(strategy, assets[0], amounts[0]); + } +} diff --git a/contracts/contracts/mocks/MockSafeContract.sol b/contracts/contracts/mocks/MockSafeContract.sol new file mode 100644 index 0000000000..4693fe4118 --- /dev/null +++ b/contracts/contracts/mocks/MockSafeContract.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +contract MockSafeContract { + function execTransactionFromModule( + address target, + uint256 value, + bytes memory data, + uint8 operation + ) external returns (bool) { + (bool success, ) = target.call{ value: value }(data); + return success; + } +} diff --git a/contracts/contracts/mocks/MockStrategy.sol b/contracts/contracts/mocks/MockStrategy.sol index df3eb5b636..8d4e9be88b 100644 --- a/contracts/contracts/mocks/MockStrategy.sol +++ b/contracts/contracts/mocks/MockStrategy.sol @@ -11,6 +11,8 @@ contract MockStrategy { bool public shouldSupportAsset; + uint256 private _nextBalance; + constructor() { shouldSupportAsset = true; } @@ -34,11 +36,16 @@ contract MockStrategy { ); } + function setNextBalance(uint256 balance) external { + _nextBalance = balance; + } + function checkBalance(address asset) external view returns (uint256 balance) { + if (_nextBalance > 0) return _nextBalance; balance = IERC20(asset).balanceOf(address(this)); } diff --git a/contracts/deploy/deployActions.js b/contracts/deploy/deployActions.js index 79afc82e29..7de245e46c 100644 --- a/contracts/deploy/deployActions.js +++ b/contracts/deploy/deployActions.js @@ -1173,6 +1173,21 @@ const deployCrossChainUnitTestStrategy = async (usdcAddress) => { // ); }; +const deploySafeModulesForUnitTests = async () => { + const cSafeContract = await ethers.getContract("MockSafeContract"); + const usdc = await ethers.getContract("MockUSDC"); + await deployWithConfirmation("MockAutoWithdrawalVault", [usdc.address]); + const mockAutoWithdrawalVault = await ethers.getContract( + "MockAutoWithdrawalVault" + ); + await deployWithConfirmation("AutoWithdrawalModule", [ + cSafeContract.address, + cSafeContract.address, + mockAutoWithdrawalVault.address, + addresses.dead, + ]); +}; + module.exports = { deployOracles, deployCore, @@ -1197,6 +1212,7 @@ module.exports = { deployCrossChainMasterStrategyImpl, deployCrossChainRemoteStrategyImpl, deployCrossChainUnitTestStrategy, + deploySafeModulesForUnitTests, getCreate2ProxyAddress, }; diff --git a/contracts/deploy/mainnet/000_mock.js b/contracts/deploy/mainnet/000_mock.js index 829f20f16e..29760f1dc4 100644 --- a/contracts/deploy/mainnet/000_mock.js +++ b/contracts/deploy/mainnet/000_mock.js @@ -119,6 +119,8 @@ const deployMocks = async ({ getNamedAccounts, deployments }) => { // .connect(sDeployer) // .setCCTPTokenMessenger(tokenMessenger.address); + await deploy("MockSafeContract", { from: deployerAddr }); + console.log("000_mock deploy done."); return true; diff --git a/contracts/deploy/mainnet/001_core.js b/contracts/deploy/mainnet/001_core.js index d6c811f63d..40ea071dd6 100644 --- a/contracts/deploy/mainnet/001_core.js +++ b/contracts/deploy/mainnet/001_core.js @@ -13,6 +13,7 @@ const { deployWOusd, deployWOeth, deployCrossChainUnitTestStrategy, + deploySafeModulesForUnitTests, } = require("../deployActions"); const main = async () => { @@ -31,6 +32,7 @@ const main = async () => { await deployWOusd(); await deployWOeth(); await deployCrossChainUnitTestStrategy(usdc.address); + await deploySafeModulesForUnitTests(); console.log("001_core deploy done."); return true; }; diff --git a/contracts/deploy/mainnet/178_ousd_auto_withdrawal_module.js b/contracts/deploy/mainnet/178_ousd_auto_withdrawal_module.js new file mode 100644 index 0000000000..6ddb483c17 --- /dev/null +++ b/contracts/deploy/mainnet/178_ousd_auto_withdrawal_module.js @@ -0,0 +1,41 @@ +const addresses = require("../../utils/addresses"); +const { + deploymentWithGovernanceProposal, + deployWithConfirmation, +} = require("../../utils/deploy"); + +module.exports = deploymentWithGovernanceProposal( + { + deployName: "178_ousd_auto_withdrawal_module", + forceDeploy: false, + reduceQueueTime: true, + deployerIsProposer: false, + proposalId: "", + }, + async () => { + const safeAddress = addresses.multichainStrategist; + + const cStrategyProxy = await ethers.getContract( + "OUSDMorphoV2StrategyProxy" + ); + const cVaultProxy = await ethers.getContract("VaultProxy"); + + await deployWithConfirmation("AutoWithdrawalModule", [ + safeAddress, + // Defender relayer + addresses.mainnet.validatorRegistrator, + cVaultProxy.address, + cStrategyProxy.address, + ]); + const cAutoWithdrawalModule = await ethers.getContract( + "AutoWithdrawalModule" + ); + console.log( + `AutoWithdrawalModule deployed to ${cAutoWithdrawalModule.address}` + ); + + return { + actions: [], + }; + } +); diff --git a/contracts/test/_fixture.js b/contracts/test/_fixture.js index accaf235e8..a0d3709611 100644 --- a/contracts/test/_fixture.js +++ b/contracts/test/_fixture.js @@ -931,6 +931,33 @@ async function claimRewardsModuleFixture() { }; } +async function autoWithdrawalModuleFixture() { + const fixture = await defaultFixture(); + + const autoWithdrawalModule = await ethers.getContract("AutoWithdrawalModule"); + const mockVault = await ethers.getContract("MockAutoWithdrawalVault"); + const mockSafe = await ethers.getContract("MockSafeContract"); + const mockStrategy = await ethers.getContract("MockStrategy"); + + // MockSafeContract is both safe and operator in the unit-test deployment. + const safeSigner = await impersonateAndFund(mockSafe.address); + + // A stranger with no roles + const stranger = await impersonateAndFund( + "0x0000000000000000000000000000000000000002" + ); + + return { + ...fixture, + autoWithdrawalModule, + mockVault, + mockSafe, + mockStrategy, + safeSigner, + stranger, + }; +} + /** * Configure a Vault with default USDC strategy to Yearn's Morpho OUSD v2 Vault. */ @@ -1613,6 +1640,7 @@ module.exports = { bridgeHelperModuleFixture, beaconChainFixture, claimRewardsModuleFixture, + autoWithdrawalModuleFixture, crossChainFixtureUnit, crossChainFixture, }; diff --git a/contracts/test/safe-modules/ousd-auto-withdrawal.js b/contracts/test/safe-modules/ousd-auto-withdrawal.js new file mode 100644 index 0000000000..aeb3f6b63b --- /dev/null +++ b/contracts/test/safe-modules/ousd-auto-withdrawal.js @@ -0,0 +1,235 @@ +const { expect } = require("chai"); +const { + createFixtureLoader, + autoWithdrawalModuleFixture, +} = require("../_fixture"); +const addresses = require("../../utils/addresses"); +const { ousdUnits } = require("../helpers"); + +const fixture = createFixtureLoader(autoWithdrawalModuleFixture); + +describe("Unit Test: OUSD Auto-Withdrawal Safe Module", function () { + let f; + + beforeEach(async () => { + f = await fixture(); + }); + + describe("Deployment / Immutables", () => { + it("Should set vault to MockVault", async () => { + const { autoWithdrawalModule, mockVault } = f; + expect(await autoWithdrawalModule.vault()).to.eq(mockVault.address); + }); + + it("Should set asset to MockVault's asset", async () => { + const { autoWithdrawalModule, mockVault } = f; + expect(await autoWithdrawalModule.asset()).to.eq(await mockVault.asset()); + }); + + it("Should set strategy to addresses.dead", async () => { + const { autoWithdrawalModule } = f; + expect(await autoWithdrawalModule.strategy()).to.eq(addresses.dead); + }); + + it("Should set safeContract to MockSafeContract", async () => { + const { autoWithdrawalModule, mockSafe } = f; + expect(await autoWithdrawalModule.safeContract()).to.eq(mockSafe.address); + }); + }); + + describe("pendingShortfall()", () => { + it("Should return queued minus claimable", async () => { + const { autoWithdrawalModule, mockVault } = f; + await mockVault.setWithdrawalQueueMetadata( + ousdUnits("1000"), + ousdUnits("400") + ); + expect(await autoWithdrawalModule.pendingShortfall()).to.eq( + ousdUnits("600") + ); + }); + + it("Should return 0 when queue is fully funded", async () => { + const { autoWithdrawalModule, mockVault } = f; + await mockVault.setWithdrawalQueueMetadata( + ousdUnits("1000"), + ousdUnits("1000") + ); + expect(await autoWithdrawalModule.pendingShortfall()).to.eq(0); + }); + }); + + describe("fundWithdrawals() - access control", () => { + it("Should revert if called by a non-operator", async () => { + const { autoWithdrawalModule, stranger } = f; + await expect( + autoWithdrawalModule.connect(stranger).fundWithdrawals() + ).to.be.revertedWith("Caller is not an operator"); + }); + }); + + describe("fundWithdrawals() - queue already satisfied", () => { + it("Should do nothing when shortfall is 0", async () => { + const { autoWithdrawalModule, mockVault, safeSigner } = f; + await mockVault.setWithdrawalQueueMetadata( + ousdUnits("1000"), + ousdUnits("1000") + ); + const tx = await autoWithdrawalModule + .connect(safeSigner) + .fundWithdrawals(); + await expect(tx).to.not.emit(autoWithdrawalModule, "LiquidityWithdrawn"); + await expect(tx).to.not.emit( + autoWithdrawalModule, + "InsufficientStrategyLiquidity" + ); + await expect(tx).to.not.emit(autoWithdrawalModule, "WithdrawalFailed"); + await expect(tx).to.not.emit(mockVault, "MockedWithdrawal"); + }); + }); + + describe("fundWithdrawals() - strategy has zero balance", () => { + it("Should emit InsufficientStrategyLiquidity when strategy balance is 0", async () => { + const { autoWithdrawalModule, mockVault, mockStrategy, safeSigner } = f; + + // Switch to a real strategy contract (addresses.dead has no code) + await autoWithdrawalModule + .connect(safeSigner) + .setStrategy(mockStrategy.address); + + // Leave setNextBalance at 0 (default) so checkBalance returns 0 + await mockVault.setWithdrawalQueueMetadata( + ousdUnits("1000"), + ousdUnits("400") + ); + + await expect(autoWithdrawalModule.connect(safeSigner).fundWithdrawals()) + .to.emit(autoWithdrawalModule, "InsufficientStrategyLiquidity") + .withArgs(mockStrategy.address, ousdUnits("600"), 0); + }); + }); + + describe("fundWithdrawals() - shortfall fully covered", () => { + it("Should withdraw exact shortfall when strategy has enough", async () => { + const { autoWithdrawalModule, mockVault, mockStrategy, safeSigner } = f; + + await autoWithdrawalModule + .connect(safeSigner) + .setStrategy(mockStrategy.address); + + await mockStrategy.setNextBalance(ousdUnits("1000")); + await mockVault.setWithdrawalQueueMetadata( + ousdUnits("1000"), + ousdUnits("400") + ); + + const tx = await autoWithdrawalModule + .connect(safeSigner) + .fundWithdrawals(); + + await expect(tx) + .to.emit(autoWithdrawalModule, "LiquidityWithdrawn") + .withArgs(mockStrategy.address, ousdUnits("600"), 0); + + await expect(tx) + .to.emit(mockVault, "MockedWithdrawal") + .withArgs( + mockStrategy.address, + await autoWithdrawalModule.asset(), + ousdUnits("600") + ); + }); + }); + + describe("fundWithdrawals() - shortfall partially covered", () => { + it("Should withdraw only what strategy has when balance < shortfall", async () => { + const { autoWithdrawalModule, mockVault, mockStrategy, safeSigner } = f; + + await autoWithdrawalModule + .connect(safeSigner) + .setStrategy(mockStrategy.address); + + await mockStrategy.setNextBalance(ousdUnits("200")); + await mockVault.setWithdrawalQueueMetadata( + ousdUnits("1000"), + ousdUnits("400") + ); + + const tx = await autoWithdrawalModule + .connect(safeSigner) + .fundWithdrawals(); + + await expect(tx) + .to.emit(autoWithdrawalModule, "LiquidityWithdrawn") + .withArgs(mockStrategy.address, ousdUnits("200"), ousdUnits("400")); + + await expect(tx) + .to.emit(mockVault, "MockedWithdrawal") + .withArgs( + mockStrategy.address, + await autoWithdrawalModule.asset(), + ousdUnits("200") + ); + }); + }); + + describe("fundWithdrawals() - Safe exec fails", () => { + it("Should emit WithdrawalFailed when vault reverts", async () => { + const { autoWithdrawalModule, mockVault, mockStrategy, safeSigner } = f; + + await autoWithdrawalModule + .connect(safeSigner) + .setStrategy(mockStrategy.address); + + await mockStrategy.setNextBalance(ousdUnits("1000")); + await mockVault.setWithdrawalQueueMetadata( + ousdUnits("1000"), + ousdUnits("400") + ); + + // Arm the vault to revert on the next withdrawal + await mockVault.revertNextWithdraw(); + + const tx = await autoWithdrawalModule + .connect(safeSigner) + .fundWithdrawals(); + + await expect(tx) + .to.emit(autoWithdrawalModule, "WithdrawalFailed") + .withArgs(mockStrategy.address, ousdUnits("600")); + + await expect(tx).to.not.emit(autoWithdrawalModule, "LiquidityWithdrawn"); + }); + }); + + describe("setStrategy()", () => { + it("Should revert if called by a non-safe address", async () => { + const { autoWithdrawalModule, mockStrategy, stranger } = f; + await expect( + autoWithdrawalModule.connect(stranger).setStrategy(mockStrategy.address) + ).to.be.revertedWith("Caller is not the safe contract"); + }); + + it("Should update strategy and emit StrategyUpdated", async () => { + const { autoWithdrawalModule, mockStrategy, safeSigner } = f; + const oldStrategy = await autoWithdrawalModule.strategy(); + + await expect( + autoWithdrawalModule + .connect(safeSigner) + .setStrategy(mockStrategy.address) + ) + .to.emit(autoWithdrawalModule, "StrategyUpdated") + .withArgs(oldStrategy, mockStrategy.address); + + expect(await autoWithdrawalModule.strategy()).to.eq(mockStrategy.address); + }); + + it("Should revert on zero address", async () => { + const { autoWithdrawalModule, safeSigner } = f; + await expect( + autoWithdrawalModule.connect(safeSigner).setStrategy(addresses.zero) + ).to.be.revertedWith("Invalid strategy"); + }); + }); +}); From 816286632dbe1fb357b9d618519e0b1b88140322 Mon Sep 17 00:00:00 2001 From: Shah <10547529+shahthepro@users.noreply.github.com> Date: Tue, 24 Feb 2026 13:00:35 +0400 Subject: [PATCH 28/36] Deploy OUSD Auto-Withdrawal Safe Module (#2812) --- .../deployments/mainnet/.migrations.json | 5 +- .../mainnet/AutoWithdrawalModule.json | 825 ++++++++++++++++++ .../cac7f19370ca72250f12b469194d7f09.json | 114 +++ contracts/package.json | 6 +- .../mainnet/AutoWithdrawalModule.json | 116 +++ 5 files changed, 1061 insertions(+), 5 deletions(-) create mode 100644 contracts/deployments/mainnet/AutoWithdrawalModule.json create mode 100644 contracts/deployments/mainnet/solcInputs/cac7f19370ca72250f12b469194d7f09.json create mode 100644 contracts/storageLayout/mainnet/AutoWithdrawalModule.json diff --git a/contracts/deployments/mainnet/.migrations.json b/contracts/deployments/mainnet/.migrations.json index 8ff70c6c46..0f34cd0769 100644 --- a/contracts/deployments/mainnet/.migrations.json +++ b/contracts/deployments/mainnet/.migrations.json @@ -64,5 +64,6 @@ "168_crosschain_strategy_proxies": 1770738208, "169_crosschain_strategy": 1770738961, "173_improve_curve_pb_module": 1770818413, - "177_change_crosschain_strategy_operator": 1771489463 -} + "177_change_crosschain_strategy_operator": 1771489463, + "178_ousd_auto_withdrawal_module": 1771922160 +} \ No newline at end of file diff --git a/contracts/deployments/mainnet/AutoWithdrawalModule.json b/contracts/deployments/mainnet/AutoWithdrawalModule.json new file mode 100644 index 0000000000..13a96c6eff --- /dev/null +++ b/contracts/deployments/mainnet/AutoWithdrawalModule.json @@ -0,0 +1,825 @@ +{ + "address": "0x90d588fc0eC3DB9c4b417dB4537fE08e063D2ae5", + "abi": [ + { + "inputs": [ + { + "internalType": "address", + "name": "_safeContract", + "type": "address" + }, + { + "internalType": "address", + "name": "_operator", + "type": "address" + }, + { + "internalType": "address", + "name": "_vault", + "type": "address" + }, + { + "internalType": "address", + "name": "_strategy", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "strategy", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "shortfall", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "available", + "type": "uint256" + } + ], + "name": "InsufficientStrategyLiquidity", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "strategy", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "remainingShortfall", + "type": "uint256" + } + ], + "name": "LiquidityWithdrawn", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "previousAdminRole", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "newAdminRole", + "type": "bytes32" + } + ], + "name": "RoleAdminChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "RoleGranted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "RoleRevoked", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "oldStrategy", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newStrategy", + "type": "address" + } + ], + "name": "StrategyUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "strategy", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "attemptedAmount", + "type": "uint256" + } + ], + "name": "WithdrawalFailed", + "type": "event" + }, + { + "inputs": [], + "name": "DEFAULT_ADMIN_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "OPERATOR_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "asset", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "fundWithdrawals", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + } + ], + "name": "getRoleAdmin", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + } + ], + "name": "getRoleMember", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + } + ], + "name": "getRoleMemberCount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "grantRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "hasRole", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pendingShortfall", + "outputs": [ + { + "internalType": "uint256", + "name": "shortfall", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "renounceRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "revokeRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "safeContract", + "outputs": [ + { + "internalType": "contract ISafe", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_strategy", + "type": "address" + } + ], + "name": "setStrategy", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "strategy", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transferTokens", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "vault", + "outputs": [ + { + "internalType": "contract IVault", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" + } + ], + "transactionHash": "0x16af3ac1ef108b0d3ba3263a3ec9c860a9ac62bd6de6fdc299cf37fae6c65d4b", + "receipt": { + "to": null, + "from": "0x58890A9cB27586E83Cb51d2d26bbE18a1a647245", + "contractAddress": "0x90d588fc0eC3DB9c4b417dB4537fE08e063D2ae5", + "transactionIndex": 44, + "gasUsed": "1620646", + "logsBloom": "0x00000004000001000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000800000080000000000000000000000000000000000000000000020002000000000000000000000000000000000000020000000000000000000800000000000000000000000000000000000000000000000000000000000000000000840800000000000000000000000000000000000000000000000000000000000000000000008000001000100000000000008000000000000000000000000000000000001000000500000000000020000000000000000400000000800001000000000000000000020000000000000000", + "blockHash": "0xb08efd0f163204dbae6b252b7a0ff665a0f70766e3059fcc34a267d0458f874b", + "transactionHash": "0x16af3ac1ef108b0d3ba3263a3ec9c860a9ac62bd6de6fdc299cf37fae6c65d4b", + "logs": [ + { + "transactionIndex": 44, + "blockNumber": 24525598, + "transactionHash": "0x16af3ac1ef108b0d3ba3263a3ec9c860a9ac62bd6de6fdc299cf37fae6c65d4b", + "address": "0x90d588fc0eC3DB9c4b417dB4537fE08e063D2ae5", + "topics": [ + "0x2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000004ff1b9d9ba8558f5eafcec096318ea0d8b541971", + "0x00000000000000000000000058890a9cb27586e83cb51d2d26bbe18a1a647245" + ], + "data": "0x", + "logIndex": 242, + "blockHash": "0xb08efd0f163204dbae6b252b7a0ff665a0f70766e3059fcc34a267d0458f874b" + }, + { + "transactionIndex": 44, + "blockNumber": 24525598, + "transactionHash": "0x16af3ac1ef108b0d3ba3263a3ec9c860a9ac62bd6de6fdc299cf37fae6c65d4b", + "address": "0x90d588fc0eC3DB9c4b417dB4537fE08e063D2ae5", + "topics": [ + "0x2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d", + "0x97667070c54ef182b0f5858b034beac1b6f3089aa2d3188bb1e8929f4fa9b929", + "0x0000000000000000000000004ff1b9d9ba8558f5eafcec096318ea0d8b541971", + "0x00000000000000000000000058890a9cb27586e83cb51d2d26bbe18a1a647245" + ], + "data": "0x", + "logIndex": 243, + "blockHash": "0xb08efd0f163204dbae6b252b7a0ff665a0f70766e3059fcc34a267d0458f874b" + }, + { + "transactionIndex": 44, + "blockNumber": 24525598, + "transactionHash": "0x16af3ac1ef108b0d3ba3263a3ec9c860a9ac62bd6de6fdc299cf37fae6c65d4b", + "address": "0x90d588fc0eC3DB9c4b417dB4537fE08e063D2ae5", + "topics": [ + "0xe4cec16b1a7e6b7979e923da619a8b1e5fd0f0fb6e5c1cf647f350430ee61ca9" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000003643cafa6ef3dd7fcc2adad1cabf708075afff6e", + "logIndex": 244, + "blockHash": "0xb08efd0f163204dbae6b252b7a0ff665a0f70766e3059fcc34a267d0458f874b" + }, + { + "transactionIndex": 44, + "blockNumber": 24525598, + "transactionHash": "0x16af3ac1ef108b0d3ba3263a3ec9c860a9ac62bd6de6fdc299cf37fae6c65d4b", + "address": "0x90d588fc0eC3DB9c4b417dB4537fE08e063D2ae5", + "topics": [ + "0x2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d", + "0x97667070c54ef182b0f5858b034beac1b6f3089aa2d3188bb1e8929f4fa9b929", + "0x0000000000000000000000004b91827516f79d6f6a1f292ed99671663b09169a", + "0x00000000000000000000000058890a9cb27586e83cb51d2d26bbe18a1a647245" + ], + "data": "0x", + "logIndex": 245, + "blockHash": "0xb08efd0f163204dbae6b252b7a0ff665a0f70766e3059fcc34a267d0458f874b" + } + ], + "blockNumber": 24525598, + "cumulativeGasUsed": "10548055", + "status": 1, + "byzantium": true + }, + "args": [ + "0x4FF1b9D9ba8558F5EAfCec096318eA0d8b541971", + "0x4b91827516f79d6F6a1F292eD99671663b09169a", + "0xE75D77B1865Ae93c7eaa3040B038D7aA7BC02F70", + "0x3643cafA6eF3dd7Fcc2ADaD1cabf708075AFFf6e" + ], + "numDeployments": 1, + "solcInputHash": "cac7f19370ca72250f12b469194d7f09", + "metadata": "{\"compiler\":{\"version\":\"0.8.28+commit.7893614a\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_safeContract\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_operator\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_vault\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_strategy\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"strategy\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"shortfall\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"available\",\"type\":\"uint256\"}],\"name\":\"InsufficientStrategyLiquidity\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"strategy\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"remainingShortfall\",\"type\":\"uint256\"}],\"name\":\"LiquidityWithdrawn\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"previousAdminRole\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"newAdminRole\",\"type\":\"bytes32\"}],\"name\":\"RoleAdminChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"}],\"name\":\"RoleGranted\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"}],\"name\":\"RoleRevoked\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"oldStrategy\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"newStrategy\",\"type\":\"address\"}],\"name\":\"StrategyUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"strategy\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"attemptedAmount\",\"type\":\"uint256\"}],\"name\":\"WithdrawalFailed\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"DEFAULT_ADMIN_ROLE\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"OPERATOR_ROLE\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"asset\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"fundWithdrawals\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"}],\"name\":\"getRoleAdmin\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"index\",\"type\":\"uint256\"}],\"name\":\"getRoleMember\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"}],\"name\":\"getRoleMemberCount\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"grantRole\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"hasRole\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"pendingShortfall\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"shortfall\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"renounceRole\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"revokeRole\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"safeContract\",\"outputs\":[{\"internalType\":\"contract ISafe\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_strategy\",\"type\":\"address\"}],\"name\":\"setStrategy\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"strategy\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes4\",\"name\":\"interfaceId\",\"type\":\"bytes4\"}],\"name\":\"supportsInterface\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"transferTokens\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"vault\",\"outputs\":[{\"internalType\":\"contract IVault\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"stateMutability\":\"payable\",\"type\":\"receive\"}],\"devdoc\":{\"details\":\"The Safe (Guardian multisig) must: 1. Deploy this module 2. Call `safe.enableModule(address(this))` to authorize it An off-chain operator (e.g. Defender Relayer) calls `fundWithdrawals()` periodically. The module: - First tries to satisfy the queue from idle vault funds - If there's still a shortfall, withdraws the exact shortfall amount from the configured strategy (up to what the strategy holds) The Safe retains full override control via `setStrategy`.\",\"events\":{\"RoleAdminChanged(bytes32,bytes32,bytes32)\":{\"details\":\"Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole` `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite {RoleAdminChanged} not being emitted signaling this. _Available since v3.1._\"},\"RoleGranted(bytes32,address,address)\":{\"details\":\"Emitted when `account` is granted `role`. `sender` is the account that originated the contract call, an admin role bearer except when using {AccessControl-_setupRole}.\"},\"RoleRevoked(bytes32,address,address)\":{\"details\":\"Emitted when `account` is revoked `role`. `sender` is the account that originated the contract call: - if using `revokeRole`, it is the admin role bearer - if using `renounceRole`, it is the role bearer (i.e. `account`)\"}},\"kind\":\"dev\",\"methods\":{\"constructor\":{\"params\":{\"_operator\":\"Address of the off-chain operator (e.g. Defender relayer).\",\"_safeContract\":\"Address of the Gnosis Safe (Guardian multisig).\",\"_strategy\":\"Initial strategy to pull liquidity from.\",\"_vault\":\"Address of the OUSD/OETH vault.\"}},\"getRoleAdmin(bytes32)\":{\"details\":\"Returns the admin role that controls `role`. See {grantRole} and {revokeRole}. To change a role's admin, use {_setRoleAdmin}.\"},\"getRoleMember(bytes32,uint256)\":{\"details\":\"Returns one of the accounts that have `role`. `index` must be a value between 0 and {getRoleMemberCount}, non-inclusive. Role bearers are not sorted in any particular way, and their ordering may change at any point. WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure you perform all queries on the same block. See the following https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post] for more information.\"},\"getRoleMemberCount(bytes32)\":{\"details\":\"Returns the number of accounts that have `role`. Can be used together with {getRoleMember} to enumerate all bearers of a role.\"},\"grantRole(bytes32,address)\":{\"details\":\"Grants `role` to `account`. If `account` had not been already granted `role`, emits a {RoleGranted} event. Requirements: - the caller must have ``role``'s admin role.\"},\"hasRole(bytes32,address)\":{\"details\":\"Returns `true` if `account` has been granted `role`.\"},\"pendingShortfall()\":{\"details\":\"This is a raw read of `queued - claimable`. It does NOT account for idle vault asset that `addWithdrawalQueueLiquidity()` would absorb. For a fully up-to-date figure, call `vault.addWithdrawalQueueLiquidity()` first (which is what `fundWithdrawals()` does).\",\"returns\":{\"shortfall\":\"Queue shortfall in asset units (vault asset decimals).\"}},\"renounceRole(bytes32,address)\":{\"details\":\"Revokes `role` from the calling account. Roles are often managed via {grantRole} and {revokeRole}: this function's purpose is to provide a mechanism for accounts to lose their privileges if they are compromised (such as when a trusted device is misplaced). If the calling account had been revoked `role`, emits a {RoleRevoked} event. Requirements: - the caller must be `account`.\"},\"revokeRole(bytes32,address)\":{\"details\":\"Revokes `role` from `account`. If `account` had been granted `role`, emits a {RoleRevoked} event. Requirements: - the caller must have ``role``'s admin role.\"},\"setStrategy(address)\":{\"params\":{\"_strategy\":\"New strategy address. Must not be zero.\"}},\"supportsInterface(bytes4)\":{\"details\":\"See {IERC165-supportsInterface}.\"},\"transferTokens(address,uint256)\":{\"details\":\"Helps recovering any tokens accidentally sent to this module.\",\"params\":{\"amount\":\"Amount to transfer. 0 to transfer all balance.\",\"token\":\"Token to transfer. 0x0 to transfer Native token.\"}}},\"title\":\"Auto Withdrawal Module\",\"version\":1},\"userdoc\":{\"events\":{\"InsufficientStrategyLiquidity(address,uint256,uint256)\":{\"notice\":\"Emitted when the strategy does not hold enough funds to cover the shortfall. No withdrawal is attempted; an operator alert should fire on this event.\"},\"LiquidityWithdrawn(address,uint256,uint256)\":{\"notice\":\"Emitted when liquidity is successfully moved from strategy to vault.\"},\"StrategyUpdated(address,address)\":{\"notice\":\"Emitted when the strategy address is updated.\"},\"WithdrawalFailed(address,uint256)\":{\"notice\":\"Emitted when the Safe exec call to withdrawFromStrategy fails.\"}},\"kind\":\"user\",\"methods\":{\"asset()\":{\"notice\":\"The vault's base asset (e.g. USDC for OUSD, WETH for OETH). Stored as an address to match IStrategy.checkBalance() signature.\"},\"fundWithdrawals()\":{\"notice\":\"Fund the vault's withdrawal queue from the configured strategy. Called periodically by an off-chain operator (Defender Actions). Steps: 1. Ask the vault to absorb any idle asset it already holds. 2. Compute the remaining shortfall. 3. Pull up to that amount from the strategy via the Safe. This function never reverts on \\\"soft\\\" failures (strategy underfunded, Safe exec failure). It emits a descriptive event instead so off-chain monitoring can alert the team without breaking the Defender action.\"},\"pendingShortfall()\":{\"notice\":\"The current unmet shortfall in the vault's withdrawal queue.\"},\"setStrategy(address)\":{\"notice\":\"Change the strategy from which liquidity is pulled.\"},\"strategy()\":{\"notice\":\"The strategy from which liquidity is pulled to fill the queue.\"},\"vault()\":{\"notice\":\"The vault whose withdrawal queue is being funded.\"}},\"notice\":\"A Gnosis Safe module that automates funding the OUSD (or OETH) vault's withdrawal queue by pulling liquidity from a configured strategy.\",\"version\":1}},\"settings\":{\"compilationTarget\":{\"contracts/automation/AutoWithdrawalModule.sol\":\"AutoWithdrawalModule\"},\"evmVersion\":\"paris\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\",\"useLiteralContent\":true},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[]},\"sources\":{\"@openzeppelin/contracts/access/AccessControl.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (access/AccessControl.sol)\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"./IAccessControl.sol\\\";\\nimport \\\"../utils/Context.sol\\\";\\nimport \\\"../utils/Strings.sol\\\";\\nimport \\\"../utils/introspection/ERC165.sol\\\";\\n\\n/**\\n * @dev Contract module that allows children to implement role-based access\\n * control mechanisms. This is a lightweight version that doesn't allow enumerating role\\n * members except through off-chain means by accessing the contract event logs. Some\\n * applications may benefit from on-chain enumerability, for those cases see\\n * {AccessControlEnumerable}.\\n *\\n * Roles are referred to by their `bytes32` identifier. These should be exposed\\n * in the external API and be unique. The best way to achieve this is by\\n * using `public constant` hash digests:\\n *\\n * ```\\n * bytes32 public constant MY_ROLE = keccak256(\\\"MY_ROLE\\\");\\n * ```\\n *\\n * Roles can be used to represent a set of permissions. To restrict access to a\\n * function call, use {hasRole}:\\n *\\n * ```\\n * function foo() public {\\n * require(hasRole(MY_ROLE, msg.sender));\\n * ...\\n * }\\n * ```\\n *\\n * Roles can be granted and revoked dynamically via the {grantRole} and\\n * {revokeRole} functions. Each role has an associated admin role, and only\\n * accounts that have a role's admin role can call {grantRole} and {revokeRole}.\\n *\\n * By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means\\n * that only accounts with this role will be able to grant or revoke other\\n * roles. More complex role relationships can be created by using\\n * {_setRoleAdmin}.\\n *\\n * WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to\\n * grant and revoke this role. Extra precautions should be taken to secure\\n * accounts that have been granted it.\\n */\\nabstract contract AccessControl is Context, IAccessControl, ERC165 {\\n struct RoleData {\\n mapping(address => bool) members;\\n bytes32 adminRole;\\n }\\n\\n mapping(bytes32 => RoleData) private _roles;\\n\\n bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;\\n\\n /**\\n * @dev Modifier that checks that an account has a specific role. Reverts\\n * with a standardized message including the required role.\\n *\\n * The format of the revert reason is given by the following regular expression:\\n *\\n * /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/\\n *\\n * _Available since v4.1._\\n */\\n modifier onlyRole(bytes32 role) {\\n _checkRole(role, _msgSender());\\n _;\\n }\\n\\n /**\\n * @dev See {IERC165-supportsInterface}.\\n */\\n function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {\\n return interfaceId == type(IAccessControl).interfaceId || super.supportsInterface(interfaceId);\\n }\\n\\n /**\\n * @dev Returns `true` if `account` has been granted `role`.\\n */\\n function hasRole(bytes32 role, address account) public view override returns (bool) {\\n return _roles[role].members[account];\\n }\\n\\n /**\\n * @dev Revert with a standard message if `account` is missing `role`.\\n *\\n * The format of the revert reason is given by the following regular expression:\\n *\\n * /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/\\n */\\n function _checkRole(bytes32 role, address account) internal view {\\n if (!hasRole(role, account)) {\\n revert(\\n string(\\n abi.encodePacked(\\n \\\"AccessControl: account \\\",\\n Strings.toHexString(uint160(account), 20),\\n \\\" is missing role \\\",\\n Strings.toHexString(uint256(role), 32)\\n )\\n )\\n );\\n }\\n }\\n\\n /**\\n * @dev Returns the admin role that controls `role`. See {grantRole} and\\n * {revokeRole}.\\n *\\n * To change a role's admin, use {_setRoleAdmin}.\\n */\\n function getRoleAdmin(bytes32 role) public view override returns (bytes32) {\\n return _roles[role].adminRole;\\n }\\n\\n /**\\n * @dev Grants `role` to `account`.\\n *\\n * If `account` had not been already granted `role`, emits a {RoleGranted}\\n * event.\\n *\\n * Requirements:\\n *\\n * - the caller must have ``role``'s admin role.\\n */\\n function grantRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {\\n _grantRole(role, account);\\n }\\n\\n /**\\n * @dev Revokes `role` from `account`.\\n *\\n * If `account` had been granted `role`, emits a {RoleRevoked} event.\\n *\\n * Requirements:\\n *\\n * - the caller must have ``role``'s admin role.\\n */\\n function revokeRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {\\n _revokeRole(role, account);\\n }\\n\\n /**\\n * @dev Revokes `role` from the calling account.\\n *\\n * Roles are often managed via {grantRole} and {revokeRole}: this function's\\n * purpose is to provide a mechanism for accounts to lose their privileges\\n * if they are compromised (such as when a trusted device is misplaced).\\n *\\n * If the calling account had been revoked `role`, emits a {RoleRevoked}\\n * event.\\n *\\n * Requirements:\\n *\\n * - the caller must be `account`.\\n */\\n function renounceRole(bytes32 role, address account) public virtual override {\\n require(account == _msgSender(), \\\"AccessControl: can only renounce roles for self\\\");\\n\\n _revokeRole(role, account);\\n }\\n\\n /**\\n * @dev Grants `role` to `account`.\\n *\\n * If `account` had not been already granted `role`, emits a {RoleGranted}\\n * event. Note that unlike {grantRole}, this function doesn't perform any\\n * checks on the calling account.\\n *\\n * [WARNING]\\n * ====\\n * This function should only be called from the constructor when setting\\n * up the initial roles for the system.\\n *\\n * Using this function in any other way is effectively circumventing the admin\\n * system imposed by {AccessControl}.\\n * ====\\n *\\n * NOTE: This function is deprecated in favor of {_grantRole}.\\n */\\n function _setupRole(bytes32 role, address account) internal virtual {\\n _grantRole(role, account);\\n }\\n\\n /**\\n * @dev Sets `adminRole` as ``role``'s admin role.\\n *\\n * Emits a {RoleAdminChanged} event.\\n */\\n function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {\\n bytes32 previousAdminRole = getRoleAdmin(role);\\n _roles[role].adminRole = adminRole;\\n emit RoleAdminChanged(role, previousAdminRole, adminRole);\\n }\\n\\n /**\\n * @dev Grants `role` to `account`.\\n *\\n * Internal function without access restriction.\\n */\\n function _grantRole(bytes32 role, address account) internal virtual {\\n if (!hasRole(role, account)) {\\n _roles[role].members[account] = true;\\n emit RoleGranted(role, account, _msgSender());\\n }\\n }\\n\\n /**\\n * @dev Revokes `role` from `account`.\\n *\\n * Internal function without access restriction.\\n */\\n function _revokeRole(bytes32 role, address account) internal virtual {\\n if (hasRole(role, account)) {\\n _roles[role].members[account] = false;\\n emit RoleRevoked(role, account, _msgSender());\\n }\\n }\\n}\\n\",\"keccak256\":\"0xb9a137b317dc4806805f2259686186c0c053c32d80fe9c15ecdbf2eb1cf52849\",\"license\":\"MIT\"},\"@openzeppelin/contracts/access/AccessControlEnumerable.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (access/AccessControlEnumerable.sol)\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"./IAccessControlEnumerable.sol\\\";\\nimport \\\"./AccessControl.sol\\\";\\nimport \\\"../utils/structs/EnumerableSet.sol\\\";\\n\\n/**\\n * @dev Extension of {AccessControl} that allows enumerating the members of each role.\\n */\\nabstract contract AccessControlEnumerable is IAccessControlEnumerable, AccessControl {\\n using EnumerableSet for EnumerableSet.AddressSet;\\n\\n mapping(bytes32 => EnumerableSet.AddressSet) private _roleMembers;\\n\\n /**\\n * @dev See {IERC165-supportsInterface}.\\n */\\n function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {\\n return interfaceId == type(IAccessControlEnumerable).interfaceId || super.supportsInterface(interfaceId);\\n }\\n\\n /**\\n * @dev Returns one of the accounts that have `role`. `index` must be a\\n * value between 0 and {getRoleMemberCount}, non-inclusive.\\n *\\n * Role bearers are not sorted in any particular way, and their ordering may\\n * change at any point.\\n *\\n * WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure\\n * you perform all queries on the same block. See the following\\n * https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post]\\n * for more information.\\n */\\n function getRoleMember(bytes32 role, uint256 index) public view override returns (address) {\\n return _roleMembers[role].at(index);\\n }\\n\\n /**\\n * @dev Returns the number of accounts that have `role`. Can be used\\n * together with {getRoleMember} to enumerate all bearers of a role.\\n */\\n function getRoleMemberCount(bytes32 role) public view override returns (uint256) {\\n return _roleMembers[role].length();\\n }\\n\\n /**\\n * @dev Overload {_grantRole} to track enumerable memberships\\n */\\n function _grantRole(bytes32 role, address account) internal virtual override {\\n super._grantRole(role, account);\\n _roleMembers[role].add(account);\\n }\\n\\n /**\\n * @dev Overload {_revokeRole} to track enumerable memberships\\n */\\n function _revokeRole(bytes32 role, address account) internal virtual override {\\n super._revokeRole(role, account);\\n _roleMembers[role].remove(account);\\n }\\n}\\n\",\"keccak256\":\"0x1304796e9cdc64294735b4222849a240363b2aff374bb58b7c728f8dc0f4aa75\",\"license\":\"MIT\"},\"@openzeppelin/contracts/access/IAccessControl.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (access/IAccessControl.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev External interface of AccessControl declared to support ERC165 detection.\\n */\\ninterface IAccessControl {\\n /**\\n * @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`\\n *\\n * `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite\\n * {RoleAdminChanged} not being emitted signaling this.\\n *\\n * _Available since v3.1._\\n */\\n event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);\\n\\n /**\\n * @dev Emitted when `account` is granted `role`.\\n *\\n * `sender` is the account that originated the contract call, an admin role\\n * bearer except when using {AccessControl-_setupRole}.\\n */\\n event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);\\n\\n /**\\n * @dev Emitted when `account` is revoked `role`.\\n *\\n * `sender` is the account that originated the contract call:\\n * - if using `revokeRole`, it is the admin role bearer\\n * - if using `renounceRole`, it is the role bearer (i.e. `account`)\\n */\\n event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);\\n\\n /**\\n * @dev Returns `true` if `account` has been granted `role`.\\n */\\n function hasRole(bytes32 role, address account) external view returns (bool);\\n\\n /**\\n * @dev Returns the admin role that controls `role`. See {grantRole} and\\n * {revokeRole}.\\n *\\n * To change a role's admin, use {AccessControl-_setRoleAdmin}.\\n */\\n function getRoleAdmin(bytes32 role) external view returns (bytes32);\\n\\n /**\\n * @dev Grants `role` to `account`.\\n *\\n * If `account` had not been already granted `role`, emits a {RoleGranted}\\n * event.\\n *\\n * Requirements:\\n *\\n * - the caller must have ``role``'s admin role.\\n */\\n function grantRole(bytes32 role, address account) external;\\n\\n /**\\n * @dev Revokes `role` from `account`.\\n *\\n * If `account` had been granted `role`, emits a {RoleRevoked} event.\\n *\\n * Requirements:\\n *\\n * - the caller must have ``role``'s admin role.\\n */\\n function revokeRole(bytes32 role, address account) external;\\n\\n /**\\n * @dev Revokes `role` from the calling account.\\n *\\n * Roles are often managed via {grantRole} and {revokeRole}: this function's\\n * purpose is to provide a mechanism for accounts to lose their privileges\\n * if they are compromised (such as when a trusted device is misplaced).\\n *\\n * If the calling account had been granted `role`, emits a {RoleRevoked}\\n * event.\\n *\\n * Requirements:\\n *\\n * - the caller must be `account`.\\n */\\n function renounceRole(bytes32 role, address account) external;\\n}\\n\",\"keccak256\":\"0x59ce320a585d7e1f163cd70390a0ef2ff9cec832e2aa544293a00692465a7a57\",\"license\":\"MIT\"},\"@openzeppelin/contracts/access/IAccessControlEnumerable.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (access/IAccessControlEnumerable.sol)\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"./IAccessControl.sol\\\";\\n\\n/**\\n * @dev External interface of AccessControlEnumerable declared to support ERC165 detection.\\n */\\ninterface IAccessControlEnumerable is IAccessControl {\\n /**\\n * @dev Returns one of the accounts that have `role`. `index` must be a\\n * value between 0 and {getRoleMemberCount}, non-inclusive.\\n *\\n * Role bearers are not sorted in any particular way, and their ordering may\\n * change at any point.\\n *\\n * WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure\\n * you perform all queries on the same block. See the following\\n * https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post]\\n * for more information.\\n */\\n function getRoleMember(bytes32 role, uint256 index) external view returns (address);\\n\\n /**\\n * @dev Returns the number of accounts that have `role`. Can be used\\n * together with {getRoleMember} to enumerate all bearers of a role.\\n */\\n function getRoleMemberCount(bytes32 role) external view returns (uint256);\\n}\\n\",\"keccak256\":\"0xba4459ab871dfa300f5212c6c30178b63898c03533a1ede28436f11546626676\",\"license\":\"MIT\"},\"@openzeppelin/contracts/token/ERC20/IERC20.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (token/ERC20/IERC20.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev Interface of the ERC20 standard as defined in the EIP.\\n */\\ninterface IERC20 {\\n /**\\n * @dev Returns the amount of tokens in existence.\\n */\\n function totalSupply() external view returns (uint256);\\n\\n /**\\n * @dev Returns the amount of tokens owned by `account`.\\n */\\n function balanceOf(address account) external view returns (uint256);\\n\\n /**\\n * @dev Moves `amount` tokens from the caller's account to `recipient`.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * Emits a {Transfer} event.\\n */\\n function transfer(address recipient, uint256 amount) external returns (bool);\\n\\n /**\\n * @dev Returns the remaining number of tokens that `spender` will be\\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\\n * zero by default.\\n *\\n * This value changes when {approve} or {transferFrom} are called.\\n */\\n function allowance(address owner, address spender) external view returns (uint256);\\n\\n /**\\n * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\\n * that someone may use both the old and the new allowance by unfortunate\\n * transaction ordering. One possible solution to mitigate this race\\n * condition is to first reduce the spender's allowance to 0 and set the\\n * desired value afterwards:\\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\\n *\\n * Emits an {Approval} event.\\n */\\n function approve(address spender, uint256 amount) external returns (bool);\\n\\n /**\\n * @dev Moves `amount` tokens from `sender` to `recipient` using the\\n * allowance mechanism. `amount` is then deducted from the caller's\\n * allowance.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * Emits a {Transfer} event.\\n */\\n function transferFrom(\\n address sender,\\n address recipient,\\n uint256 amount\\n ) external returns (bool);\\n\\n /**\\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\\n * another (`to`).\\n *\\n * Note that `value` may be zero.\\n */\\n event Transfer(address indexed from, address indexed to, uint256 value);\\n\\n /**\\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\\n * a call to {approve}. `value` is the new allowance.\\n */\\n event Approval(address indexed owner, address indexed spender, uint256 value);\\n}\\n\",\"keccak256\":\"0x61437cb513a887a1bbad006e7b1c8b414478427d33de47c5600af3c748f108da\",\"license\":\"MIT\"},\"@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"../IERC20.sol\\\";\\n\\n/**\\n * @dev Interface for the optional metadata functions from the ERC20 standard.\\n *\\n * _Available since v4.1._\\n */\\ninterface IERC20Metadata is IERC20 {\\n /**\\n * @dev Returns the name of the token.\\n */\\n function name() external view returns (string memory);\\n\\n /**\\n * @dev Returns the symbol of the token.\\n */\\n function symbol() external view returns (string memory);\\n\\n /**\\n * @dev Returns the decimals places of the token.\\n */\\n function decimals() external view returns (uint8);\\n}\\n\",\"keccak256\":\"0x8de418a5503946cabe331f35fe242d3201a73f67f77aaeb7110acb1f30423aca\",\"license\":\"MIT\"},\"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (token/ERC20/utils/SafeERC20.sol)\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"../IERC20.sol\\\";\\nimport \\\"../../../utils/Address.sol\\\";\\n\\n/**\\n * @title SafeERC20\\n * @dev Wrappers around ERC20 operations that throw on failure (when the token\\n * contract returns false). Tokens that return no value (and instead revert or\\n * throw on failure) are also supported, non-reverting calls are assumed to be\\n * successful.\\n * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,\\n * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.\\n */\\nlibrary SafeERC20 {\\n using Address for address;\\n\\n function safeTransfer(\\n IERC20 token,\\n address to,\\n uint256 value\\n ) internal {\\n _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));\\n }\\n\\n function safeTransferFrom(\\n IERC20 token,\\n address from,\\n address to,\\n uint256 value\\n ) internal {\\n _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));\\n }\\n\\n /**\\n * @dev Deprecated. This function has issues similar to the ones found in\\n * {IERC20-approve}, and its usage is discouraged.\\n *\\n * Whenever possible, use {safeIncreaseAllowance} and\\n * {safeDecreaseAllowance} instead.\\n */\\n function safeApprove(\\n IERC20 token,\\n address spender,\\n uint256 value\\n ) internal {\\n // safeApprove should only be called when setting an initial allowance,\\n // or when resetting it to zero. To increase and decrease it, use\\n // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'\\n require(\\n (value == 0) || (token.allowance(address(this), spender) == 0),\\n \\\"SafeERC20: approve from non-zero to non-zero allowance\\\"\\n );\\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));\\n }\\n\\n function safeIncreaseAllowance(\\n IERC20 token,\\n address spender,\\n uint256 value\\n ) internal {\\n uint256 newAllowance = token.allowance(address(this), spender) + value;\\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));\\n }\\n\\n function safeDecreaseAllowance(\\n IERC20 token,\\n address spender,\\n uint256 value\\n ) internal {\\n unchecked {\\n uint256 oldAllowance = token.allowance(address(this), spender);\\n require(oldAllowance >= value, \\\"SafeERC20: decreased allowance below zero\\\");\\n uint256 newAllowance = oldAllowance - value;\\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));\\n }\\n }\\n\\n /**\\n * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement\\n * on the return value: the return value is optional (but if data is returned, it must not be false).\\n * @param token The token targeted by the call.\\n * @param data The call data (encoded using abi.encode or one of its variants).\\n */\\n function _callOptionalReturn(IERC20 token, bytes memory data) private {\\n // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since\\n // we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that\\n // the target address contains contract code and also asserts for success in the low-level call.\\n\\n bytes memory returndata = address(token).functionCall(data, \\\"SafeERC20: low-level call failed\\\");\\n if (returndata.length > 0) {\\n // Return data is optional\\n require(abi.decode(returndata, (bool)), \\\"SafeERC20: ERC20 operation did not succeed\\\");\\n }\\n }\\n}\\n\",\"keccak256\":\"0xc3d946432c0ddbb1f846a0d3985be71299df331b91d06732152117f62f0be2b5\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/Address.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (utils/Address.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev Collection of functions related to the address type\\n */\\nlibrary Address {\\n /**\\n * @dev Returns true if `account` is a contract.\\n *\\n * [IMPORTANT]\\n * ====\\n * It is unsafe to assume that an address for which this function returns\\n * false is an externally-owned account (EOA) and not a contract.\\n *\\n * Among others, `isContract` will return false for the following\\n * types of addresses:\\n *\\n * - an externally-owned account\\n * - a contract in construction\\n * - an address where a contract will be created\\n * - an address where a contract lived, but was destroyed\\n * ====\\n */\\n function isContract(address account) internal view returns (bool) {\\n // This method relies on extcodesize, which returns 0 for contracts in\\n // construction, since the code is only stored at the end of the\\n // constructor execution.\\n\\n uint256 size;\\n assembly {\\n size := extcodesize(account)\\n }\\n return size > 0;\\n }\\n\\n /**\\n * @dev Replacement for Solidity's `transfer`: sends `amount` wei to\\n * `recipient`, forwarding all available gas and reverting on errors.\\n *\\n * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost\\n * of certain opcodes, possibly making contracts go over the 2300 gas limit\\n * imposed by `transfer`, making them unable to receive funds via\\n * `transfer`. {sendValue} removes this limitation.\\n *\\n * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].\\n *\\n * IMPORTANT: because control is transferred to `recipient`, care must be\\n * taken to not create reentrancy vulnerabilities. Consider using\\n * {ReentrancyGuard} or the\\n * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].\\n */\\n function sendValue(address payable recipient, uint256 amount) internal {\\n require(address(this).balance >= amount, \\\"Address: insufficient balance\\\");\\n\\n (bool success, ) = recipient.call{value: amount}(\\\"\\\");\\n require(success, \\\"Address: unable to send value, recipient may have reverted\\\");\\n }\\n\\n /**\\n * @dev Performs a Solidity function call using a low level `call`. A\\n * plain `call` is an unsafe replacement for a function call: use this\\n * function instead.\\n *\\n * If `target` reverts with a revert reason, it is bubbled up by this\\n * function (like regular Solidity function calls).\\n *\\n * Returns the raw returned data. To convert to the expected return value,\\n * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].\\n *\\n * Requirements:\\n *\\n * - `target` must be a contract.\\n * - calling `target` with `data` must not revert.\\n *\\n * _Available since v3.1._\\n */\\n function functionCall(address target, bytes memory data) internal returns (bytes memory) {\\n return functionCall(target, data, \\\"Address: low-level call failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with\\n * `errorMessage` as a fallback revert reason when `target` reverts.\\n *\\n * _Available since v3.1._\\n */\\n function functionCall(\\n address target,\\n bytes memory data,\\n string memory errorMessage\\n ) internal returns (bytes memory) {\\n return functionCallWithValue(target, data, 0, errorMessage);\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\\n * but also transferring `value` wei to `target`.\\n *\\n * Requirements:\\n *\\n * - the calling contract must have an ETH balance of at least `value`.\\n * - the called Solidity function must be `payable`.\\n *\\n * _Available since v3.1._\\n */\\n function functionCallWithValue(\\n address target,\\n bytes memory data,\\n uint256 value\\n ) internal returns (bytes memory) {\\n return functionCallWithValue(target, data, value, \\\"Address: low-level call with value failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but\\n * with `errorMessage` as a fallback revert reason when `target` reverts.\\n *\\n * _Available since v3.1._\\n */\\n function functionCallWithValue(\\n address target,\\n bytes memory data,\\n uint256 value,\\n string memory errorMessage\\n ) internal returns (bytes memory) {\\n require(address(this).balance >= value, \\\"Address: insufficient balance for call\\\");\\n require(isContract(target), \\\"Address: call to non-contract\\\");\\n\\n (bool success, bytes memory returndata) = target.call{value: value}(data);\\n return verifyCallResult(success, returndata, errorMessage);\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\\n * but performing a static call.\\n *\\n * _Available since v3.3._\\n */\\n function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {\\n return functionStaticCall(target, data, \\\"Address: low-level static call failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\\n * but performing a static call.\\n *\\n * _Available since v3.3._\\n */\\n function functionStaticCall(\\n address target,\\n bytes memory data,\\n string memory errorMessage\\n ) internal view returns (bytes memory) {\\n require(isContract(target), \\\"Address: static call to non-contract\\\");\\n\\n (bool success, bytes memory returndata) = target.staticcall(data);\\n return verifyCallResult(success, returndata, errorMessage);\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\\n * but performing a delegate call.\\n *\\n * _Available since v3.4._\\n */\\n function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {\\n return functionDelegateCall(target, data, \\\"Address: low-level delegate call failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\\n * but performing a delegate call.\\n *\\n * _Available since v3.4._\\n */\\n function functionDelegateCall(\\n address target,\\n bytes memory data,\\n string memory errorMessage\\n ) internal returns (bytes memory) {\\n require(isContract(target), \\\"Address: delegate call to non-contract\\\");\\n\\n (bool success, bytes memory returndata) = target.delegatecall(data);\\n return verifyCallResult(success, returndata, errorMessage);\\n }\\n\\n /**\\n * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the\\n * revert reason using the provided one.\\n *\\n * _Available since v4.3._\\n */\\n function verifyCallResult(\\n bool success,\\n bytes memory returndata,\\n string memory errorMessage\\n ) internal pure returns (bytes memory) {\\n if (success) {\\n return returndata;\\n } else {\\n // Look for revert reason and bubble it up if present\\n if (returndata.length > 0) {\\n // The easiest way to bubble the revert reason is using memory via assembly\\n\\n assembly {\\n let returndata_size := mload(returndata)\\n revert(add(32, returndata), returndata_size)\\n }\\n } else {\\n revert(errorMessage);\\n }\\n }\\n }\\n}\\n\",\"keccak256\":\"0x51b758a8815ecc9596c66c37d56b1d33883a444631a3f916b9fe65cb863ef7c4\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/Context.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev Provides information about the current execution context, including the\\n * sender of the transaction and its data. While these are generally available\\n * via msg.sender and msg.data, they should not be accessed in such a direct\\n * manner, since when dealing with meta-transactions the account sending and\\n * paying for execution may not be the actual sender (as far as an application\\n * is concerned).\\n *\\n * This contract is only required for intermediate, library-like contracts.\\n */\\nabstract contract Context {\\n function _msgSender() internal view virtual returns (address) {\\n return msg.sender;\\n }\\n\\n function _msgData() internal view virtual returns (bytes calldata) {\\n return msg.data;\\n }\\n}\\n\",\"keccak256\":\"0xe2e337e6dde9ef6b680e07338c493ebea1b5fd09b43424112868e9cc1706bca7\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/Strings.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (utils/Strings.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev String operations.\\n */\\nlibrary Strings {\\n bytes16 private constant _HEX_SYMBOLS = \\\"0123456789abcdef\\\";\\n\\n /**\\n * @dev Converts a `uint256` to its ASCII `string` decimal representation.\\n */\\n function toString(uint256 value) internal pure returns (string memory) {\\n // Inspired by OraclizeAPI's implementation - MIT licence\\n // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol\\n\\n if (value == 0) {\\n return \\\"0\\\";\\n }\\n uint256 temp = value;\\n uint256 digits;\\n while (temp != 0) {\\n digits++;\\n temp /= 10;\\n }\\n bytes memory buffer = new bytes(digits);\\n while (value != 0) {\\n digits -= 1;\\n buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));\\n value /= 10;\\n }\\n return string(buffer);\\n }\\n\\n /**\\n * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.\\n */\\n function toHexString(uint256 value) internal pure returns (string memory) {\\n if (value == 0) {\\n return \\\"0x00\\\";\\n }\\n uint256 temp = value;\\n uint256 length = 0;\\n while (temp != 0) {\\n length++;\\n temp >>= 8;\\n }\\n return toHexString(value, length);\\n }\\n\\n /**\\n * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.\\n */\\n function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {\\n bytes memory buffer = new bytes(2 * length + 2);\\n buffer[0] = \\\"0\\\";\\n buffer[1] = \\\"x\\\";\\n for (uint256 i = 2 * length + 1; i > 1; --i) {\\n buffer[i] = _HEX_SYMBOLS[value & 0xf];\\n value >>= 4;\\n }\\n require(value == 0, \\\"Strings: hex length insufficient\\\");\\n return string(buffer);\\n }\\n}\\n\",\"keccak256\":\"0x32c202bd28995dd20c4347b7c6467a6d3241c74c8ad3edcbb610cd9205916c45\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/introspection/ERC165.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol)\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"./IERC165.sol\\\";\\n\\n/**\\n * @dev Implementation of the {IERC165} interface.\\n *\\n * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check\\n * for the additional interface id that will be supported. For example:\\n *\\n * ```solidity\\n * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {\\n * return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);\\n * }\\n * ```\\n *\\n * Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation.\\n */\\nabstract contract ERC165 is IERC165 {\\n /**\\n * @dev See {IERC165-supportsInterface}.\\n */\\n function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {\\n return interfaceId == type(IERC165).interfaceId;\\n }\\n}\\n\",\"keccak256\":\"0xd10975de010d89fd1c78dc5e8a9a7e7f496198085c151648f20cba166b32582b\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/introspection/IERC165.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev Interface of the ERC165 standard, as defined in the\\n * https://eips.ethereum.org/EIPS/eip-165[EIP].\\n *\\n * Implementers can declare support of contract interfaces, which can then be\\n * queried by others ({ERC165Checker}).\\n *\\n * For an implementation, see {ERC165}.\\n */\\ninterface IERC165 {\\n /**\\n * @dev Returns true if this contract implements the interface defined by\\n * `interfaceId`. See the corresponding\\n * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]\\n * to learn more about how these ids are created.\\n *\\n * This function call must use less than 30 000 gas.\\n */\\n function supportsInterface(bytes4 interfaceId) external view returns (bool);\\n}\\n\",\"keccak256\":\"0x447a5f3ddc18419d41ff92b3773fb86471b1db25773e07f877f548918a185bf1\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/math/SafeCast.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (utils/math/SafeCast.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev Wrappers over Solidity's uintXX/intXX casting operators with added overflow\\n * checks.\\n *\\n * Downcasting from uint256/int256 in Solidity does not revert on overflow. This can\\n * easily result in undesired exploitation or bugs, since developers usually\\n * assume that overflows raise errors. `SafeCast` restores this intuition by\\n * reverting the transaction when such an operation overflows.\\n *\\n * Using this library instead of the unchecked operations eliminates an entire\\n * class of bugs, so it's recommended to use it always.\\n *\\n * Can be combined with {SafeMath} and {SignedSafeMath} to extend it to smaller types, by performing\\n * all math on `uint256` and `int256` and then downcasting.\\n */\\nlibrary SafeCast {\\n /**\\n * @dev Returns the downcasted uint224 from uint256, reverting on\\n * overflow (when the input is greater than largest uint224).\\n *\\n * Counterpart to Solidity's `uint224` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 224 bits\\n */\\n function toUint224(uint256 value) internal pure returns (uint224) {\\n require(value <= type(uint224).max, \\\"SafeCast: value doesn't fit in 224 bits\\\");\\n return uint224(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted uint128 from uint256, reverting on\\n * overflow (when the input is greater than largest uint128).\\n *\\n * Counterpart to Solidity's `uint128` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 128 bits\\n */\\n function toUint128(uint256 value) internal pure returns (uint128) {\\n require(value <= type(uint128).max, \\\"SafeCast: value doesn't fit in 128 bits\\\");\\n return uint128(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted uint96 from uint256, reverting on\\n * overflow (when the input is greater than largest uint96).\\n *\\n * Counterpart to Solidity's `uint96` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 96 bits\\n */\\n function toUint96(uint256 value) internal pure returns (uint96) {\\n require(value <= type(uint96).max, \\\"SafeCast: value doesn't fit in 96 bits\\\");\\n return uint96(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted uint64 from uint256, reverting on\\n * overflow (when the input is greater than largest uint64).\\n *\\n * Counterpart to Solidity's `uint64` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 64 bits\\n */\\n function toUint64(uint256 value) internal pure returns (uint64) {\\n require(value <= type(uint64).max, \\\"SafeCast: value doesn't fit in 64 bits\\\");\\n return uint64(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted uint32 from uint256, reverting on\\n * overflow (when the input is greater than largest uint32).\\n *\\n * Counterpart to Solidity's `uint32` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 32 bits\\n */\\n function toUint32(uint256 value) internal pure returns (uint32) {\\n require(value <= type(uint32).max, \\\"SafeCast: value doesn't fit in 32 bits\\\");\\n return uint32(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted uint16 from uint256, reverting on\\n * overflow (when the input is greater than largest uint16).\\n *\\n * Counterpart to Solidity's `uint16` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 16 bits\\n */\\n function toUint16(uint256 value) internal pure returns (uint16) {\\n require(value <= type(uint16).max, \\\"SafeCast: value doesn't fit in 16 bits\\\");\\n return uint16(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted uint8 from uint256, reverting on\\n * overflow (when the input is greater than largest uint8).\\n *\\n * Counterpart to Solidity's `uint8` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 8 bits.\\n */\\n function toUint8(uint256 value) internal pure returns (uint8) {\\n require(value <= type(uint8).max, \\\"SafeCast: value doesn't fit in 8 bits\\\");\\n return uint8(value);\\n }\\n\\n /**\\n * @dev Converts a signed int256 into an unsigned uint256.\\n *\\n * Requirements:\\n *\\n * - input must be greater than or equal to 0.\\n */\\n function toUint256(int256 value) internal pure returns (uint256) {\\n require(value >= 0, \\\"SafeCast: value must be positive\\\");\\n return uint256(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted int128 from int256, reverting on\\n * overflow (when the input is less than smallest int128 or\\n * greater than largest int128).\\n *\\n * Counterpart to Solidity's `int128` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 128 bits\\n *\\n * _Available since v3.1._\\n */\\n function toInt128(int256 value) internal pure returns (int128) {\\n require(value >= type(int128).min && value <= type(int128).max, \\\"SafeCast: value doesn't fit in 128 bits\\\");\\n return int128(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted int64 from int256, reverting on\\n * overflow (when the input is less than smallest int64 or\\n * greater than largest int64).\\n *\\n * Counterpart to Solidity's `int64` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 64 bits\\n *\\n * _Available since v3.1._\\n */\\n function toInt64(int256 value) internal pure returns (int64) {\\n require(value >= type(int64).min && value <= type(int64).max, \\\"SafeCast: value doesn't fit in 64 bits\\\");\\n return int64(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted int32 from int256, reverting on\\n * overflow (when the input is less than smallest int32 or\\n * greater than largest int32).\\n *\\n * Counterpart to Solidity's `int32` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 32 bits\\n *\\n * _Available since v3.1._\\n */\\n function toInt32(int256 value) internal pure returns (int32) {\\n require(value >= type(int32).min && value <= type(int32).max, \\\"SafeCast: value doesn't fit in 32 bits\\\");\\n return int32(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted int16 from int256, reverting on\\n * overflow (when the input is less than smallest int16 or\\n * greater than largest int16).\\n *\\n * Counterpart to Solidity's `int16` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 16 bits\\n *\\n * _Available since v3.1._\\n */\\n function toInt16(int256 value) internal pure returns (int16) {\\n require(value >= type(int16).min && value <= type(int16).max, \\\"SafeCast: value doesn't fit in 16 bits\\\");\\n return int16(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted int8 from int256, reverting on\\n * overflow (when the input is less than smallest int8 or\\n * greater than largest int8).\\n *\\n * Counterpart to Solidity's `int8` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 8 bits.\\n *\\n * _Available since v3.1._\\n */\\n function toInt8(int256 value) internal pure returns (int8) {\\n require(value >= type(int8).min && value <= type(int8).max, \\\"SafeCast: value doesn't fit in 8 bits\\\");\\n return int8(value);\\n }\\n\\n /**\\n * @dev Converts an unsigned uint256 into a signed int256.\\n *\\n * Requirements:\\n *\\n * - input must be less than or equal to maxInt256.\\n */\\n function toInt256(uint256 value) internal pure returns (int256) {\\n // Note: Unsafe cast below is okay because `type(int256).max` is guaranteed to be positive\\n require(value <= uint256(type(int256).max), \\\"SafeCast: value doesn't fit in an int256\\\");\\n return int256(value);\\n }\\n}\\n\",\"keccak256\":\"0x5c6caab697d302ad7eb59c234a4d2dbc965c1bae87709bd2850060b7695b28c7\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/structs/EnumerableSet.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (utils/structs/EnumerableSet.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev Library for managing\\n * https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive\\n * types.\\n *\\n * Sets have the following properties:\\n *\\n * - Elements are added, removed, and checked for existence in constant time\\n * (O(1)).\\n * - Elements are enumerated in O(n). No guarantees are made on the ordering.\\n *\\n * ```\\n * contract Example {\\n * // Add the library methods\\n * using EnumerableSet for EnumerableSet.AddressSet;\\n *\\n * // Declare a set state variable\\n * EnumerableSet.AddressSet private mySet;\\n * }\\n * ```\\n *\\n * As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)\\n * and `uint256` (`UintSet`) are supported.\\n */\\nlibrary EnumerableSet {\\n // To implement this library for multiple types with as little code\\n // repetition as possible, we write it in terms of a generic Set type with\\n // bytes32 values.\\n // The Set implementation uses private functions, and user-facing\\n // implementations (such as AddressSet) are just wrappers around the\\n // underlying Set.\\n // This means that we can only create new EnumerableSets for types that fit\\n // in bytes32.\\n\\n struct Set {\\n // Storage of set values\\n bytes32[] _values;\\n // Position of the value in the `values` array, plus 1 because index 0\\n // means a value is not in the set.\\n mapping(bytes32 => uint256) _indexes;\\n }\\n\\n /**\\n * @dev Add a value to a set. O(1).\\n *\\n * Returns true if the value was added to the set, that is if it was not\\n * already present.\\n */\\n function _add(Set storage set, bytes32 value) private returns (bool) {\\n if (!_contains(set, value)) {\\n set._values.push(value);\\n // The value is stored at length-1, but we add 1 to all indexes\\n // and use 0 as a sentinel value\\n set._indexes[value] = set._values.length;\\n return true;\\n } else {\\n return false;\\n }\\n }\\n\\n /**\\n * @dev Removes a value from a set. O(1).\\n *\\n * Returns true if the value was removed from the set, that is if it was\\n * present.\\n */\\n function _remove(Set storage set, bytes32 value) private returns (bool) {\\n // We read and store the value's index to prevent multiple reads from the same storage slot\\n uint256 valueIndex = set._indexes[value];\\n\\n if (valueIndex != 0) {\\n // Equivalent to contains(set, value)\\n // To delete an element from the _values array in O(1), we swap the element to delete with the last one in\\n // the array, and then remove the last element (sometimes called as 'swap and pop').\\n // This modifies the order of the array, as noted in {at}.\\n\\n uint256 toDeleteIndex = valueIndex - 1;\\n uint256 lastIndex = set._values.length - 1;\\n\\n if (lastIndex != toDeleteIndex) {\\n bytes32 lastvalue = set._values[lastIndex];\\n\\n // Move the last value to the index where the value to delete is\\n set._values[toDeleteIndex] = lastvalue;\\n // Update the index for the moved value\\n set._indexes[lastvalue] = valueIndex; // Replace lastvalue's index to valueIndex\\n }\\n\\n // Delete the slot where the moved value was stored\\n set._values.pop();\\n\\n // Delete the index for the deleted slot\\n delete set._indexes[value];\\n\\n return true;\\n } else {\\n return false;\\n }\\n }\\n\\n /**\\n * @dev Returns true if the value is in the set. O(1).\\n */\\n function _contains(Set storage set, bytes32 value) private view returns (bool) {\\n return set._indexes[value] != 0;\\n }\\n\\n /**\\n * @dev Returns the number of values on the set. O(1).\\n */\\n function _length(Set storage set) private view returns (uint256) {\\n return set._values.length;\\n }\\n\\n /**\\n * @dev Returns the value stored at position `index` in the set. O(1).\\n *\\n * Note that there are no guarantees on the ordering of values inside the\\n * array, and it may change when more values are added or removed.\\n *\\n * Requirements:\\n *\\n * - `index` must be strictly less than {length}.\\n */\\n function _at(Set storage set, uint256 index) private view returns (bytes32) {\\n return set._values[index];\\n }\\n\\n /**\\n * @dev Return the entire set in an array\\n *\\n * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed\\n * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that\\n * this function has an unbounded cost, and using it as part of a state-changing function may render the function\\n * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.\\n */\\n function _values(Set storage set) private view returns (bytes32[] memory) {\\n return set._values;\\n }\\n\\n // Bytes32Set\\n\\n struct Bytes32Set {\\n Set _inner;\\n }\\n\\n /**\\n * @dev Add a value to a set. O(1).\\n *\\n * Returns true if the value was added to the set, that is if it was not\\n * already present.\\n */\\n function add(Bytes32Set storage set, bytes32 value) internal returns (bool) {\\n return _add(set._inner, value);\\n }\\n\\n /**\\n * @dev Removes a value from a set. O(1).\\n *\\n * Returns true if the value was removed from the set, that is if it was\\n * present.\\n */\\n function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) {\\n return _remove(set._inner, value);\\n }\\n\\n /**\\n * @dev Returns true if the value is in the set. O(1).\\n */\\n function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {\\n return _contains(set._inner, value);\\n }\\n\\n /**\\n * @dev Returns the number of values in the set. O(1).\\n */\\n function length(Bytes32Set storage set) internal view returns (uint256) {\\n return _length(set._inner);\\n }\\n\\n /**\\n * @dev Returns the value stored at position `index` in the set. O(1).\\n *\\n * Note that there are no guarantees on the ordering of values inside the\\n * array, and it may change when more values are added or removed.\\n *\\n * Requirements:\\n *\\n * - `index` must be strictly less than {length}.\\n */\\n function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {\\n return _at(set._inner, index);\\n }\\n\\n /**\\n * @dev Return the entire set in an array\\n *\\n * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed\\n * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that\\n * this function has an unbounded cost, and using it as part of a state-changing function may render the function\\n * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.\\n */\\n function values(Bytes32Set storage set) internal view returns (bytes32[] memory) {\\n return _values(set._inner);\\n }\\n\\n // AddressSet\\n\\n struct AddressSet {\\n Set _inner;\\n }\\n\\n /**\\n * @dev Add a value to a set. O(1).\\n *\\n * Returns true if the value was added to the set, that is if it was not\\n * already present.\\n */\\n function add(AddressSet storage set, address value) internal returns (bool) {\\n return _add(set._inner, bytes32(uint256(uint160(value))));\\n }\\n\\n /**\\n * @dev Removes a value from a set. O(1).\\n *\\n * Returns true if the value was removed from the set, that is if it was\\n * present.\\n */\\n function remove(AddressSet storage set, address value) internal returns (bool) {\\n return _remove(set._inner, bytes32(uint256(uint160(value))));\\n }\\n\\n /**\\n * @dev Returns true if the value is in the set. O(1).\\n */\\n function contains(AddressSet storage set, address value) internal view returns (bool) {\\n return _contains(set._inner, bytes32(uint256(uint160(value))));\\n }\\n\\n /**\\n * @dev Returns the number of values in the set. O(1).\\n */\\n function length(AddressSet storage set) internal view returns (uint256) {\\n return _length(set._inner);\\n }\\n\\n /**\\n * @dev Returns the value stored at position `index` in the set. O(1).\\n *\\n * Note that there are no guarantees on the ordering of values inside the\\n * array, and it may change when more values are added or removed.\\n *\\n * Requirements:\\n *\\n * - `index` must be strictly less than {length}.\\n */\\n function at(AddressSet storage set, uint256 index) internal view returns (address) {\\n return address(uint160(uint256(_at(set._inner, index))));\\n }\\n\\n /**\\n * @dev Return the entire set in an array\\n *\\n * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed\\n * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that\\n * this function has an unbounded cost, and using it as part of a state-changing function may render the function\\n * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.\\n */\\n function values(AddressSet storage set) internal view returns (address[] memory) {\\n bytes32[] memory store = _values(set._inner);\\n address[] memory result;\\n\\n assembly {\\n result := store\\n }\\n\\n return result;\\n }\\n\\n // UintSet\\n\\n struct UintSet {\\n Set _inner;\\n }\\n\\n /**\\n * @dev Add a value to a set. O(1).\\n *\\n * Returns true if the value was added to the set, that is if it was not\\n * already present.\\n */\\n function add(UintSet storage set, uint256 value) internal returns (bool) {\\n return _add(set._inner, bytes32(value));\\n }\\n\\n /**\\n * @dev Removes a value from a set. O(1).\\n *\\n * Returns true if the value was removed from the set, that is if it was\\n * present.\\n */\\n function remove(UintSet storage set, uint256 value) internal returns (bool) {\\n return _remove(set._inner, bytes32(value));\\n }\\n\\n /**\\n * @dev Returns true if the value is in the set. O(1).\\n */\\n function contains(UintSet storage set, uint256 value) internal view returns (bool) {\\n return _contains(set._inner, bytes32(value));\\n }\\n\\n /**\\n * @dev Returns the number of values on the set. O(1).\\n */\\n function length(UintSet storage set) internal view returns (uint256) {\\n return _length(set._inner);\\n }\\n\\n /**\\n * @dev Returns the value stored at position `index` in the set. O(1).\\n *\\n * Note that there are no guarantees on the ordering of values inside the\\n * array, and it may change when more values are added or removed.\\n *\\n * Requirements:\\n *\\n * - `index` must be strictly less than {length}.\\n */\\n function at(UintSet storage set, uint256 index) internal view returns (uint256) {\\n return uint256(_at(set._inner, index));\\n }\\n\\n /**\\n * @dev Return the entire set in an array\\n *\\n * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed\\n * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that\\n * this function has an unbounded cost, and using it as part of a state-changing function may render the function\\n * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.\\n */\\n function values(UintSet storage set) internal view returns (uint256[] memory) {\\n bytes32[] memory store = _values(set._inner);\\n uint256[] memory result;\\n\\n assembly {\\n result := store\\n }\\n\\n return result;\\n }\\n}\\n\",\"keccak256\":\"0x9772845c886f87a3aab315f8d6b68aa599027c20f441b131cd4afaf65b588900\",\"license\":\"MIT\"},\"contracts/automation/AbstractSafeModule.sol\":{\"content\":\"// SPDX-License-Identifier: BUSL-1.1\\npragma solidity ^0.8.0;\\n\\nimport { AccessControlEnumerable } from \\\"@openzeppelin/contracts/access/AccessControlEnumerable.sol\\\";\\nimport { IERC20 } from \\\"@openzeppelin/contracts/token/ERC20/IERC20.sol\\\";\\nimport { ISafe } from \\\"../interfaces/ISafe.sol\\\";\\n\\nabstract contract AbstractSafeModule is AccessControlEnumerable {\\n ISafe public immutable safeContract;\\n\\n bytes32 public constant OPERATOR_ROLE = keccak256(\\\"OPERATOR_ROLE\\\");\\n\\n modifier onlySafe() {\\n require(\\n msg.sender == address(safeContract),\\n \\\"Caller is not the safe contract\\\"\\n );\\n _;\\n }\\n\\n modifier onlyOperator() {\\n require(\\n hasRole(OPERATOR_ROLE, msg.sender),\\n \\\"Caller is not an operator\\\"\\n );\\n _;\\n }\\n\\n constructor(address _safeContract) {\\n safeContract = ISafe(_safeContract);\\n _grantRole(DEFAULT_ADMIN_ROLE, address(safeContract));\\n _grantRole(OPERATOR_ROLE, address(safeContract));\\n }\\n\\n /**\\n * @dev Helps recovering any tokens accidentally sent to this module.\\n * @param token Token to transfer. 0x0 to transfer Native token.\\n * @param amount Amount to transfer. 0 to transfer all balance.\\n */\\n function transferTokens(address token, uint256 amount) external onlySafe {\\n if (address(token) == address(0)) {\\n // Move ETH\\n amount = amount > 0 ? amount : address(this).balance;\\n payable(address(safeContract)).transfer(amount);\\n return;\\n }\\n\\n // Move all balance if amount set to 0\\n amount = amount > 0 ? amount : IERC20(token).balanceOf(address(this));\\n\\n // Transfer to Safe contract\\n // slither-disable-next-line unchecked-transfer unused-return\\n IERC20(token).transfer(address(safeContract), amount);\\n }\\n\\n receive() external payable {\\n // Accept ETH to pay for bridge fees\\n }\\n}\\n\",\"keccak256\":\"0x02f5cebee3ef21afb1e5dafe15a9160017dde1b7361653e6285f884124d15af5\",\"license\":\"BUSL-1.1\"},\"contracts/automation/AutoWithdrawalModule.sol\":{\"content\":\"// SPDX-License-Identifier: BUSL-1.1\\npragma solidity ^0.8.0;\\n\\nimport { AbstractSafeModule } from \\\"./AbstractSafeModule.sol\\\";\\n\\nimport { IVault } from \\\"../interfaces/IVault.sol\\\";\\nimport { VaultStorage } from \\\"../vault/VaultStorage.sol\\\";\\nimport { IStrategy } from \\\"../interfaces/IStrategy.sol\\\";\\n\\n/**\\n * @title Auto Withdrawal Module\\n * @notice A Gnosis Safe module that automates funding the OUSD (or OETH) vault's\\n * withdrawal queue by pulling liquidity from a configured strategy.\\n *\\n * @dev The Safe (Guardian multisig) must:\\n * 1. Deploy this module\\n * 2. Call `safe.enableModule(address(this))` to authorize it\\n *\\n * An off-chain operator (e.g. Defender Relayer) calls `fundWithdrawals()`\\n * periodically. The module:\\n * - First tries to satisfy the queue from idle vault funds\\n * - If there's still a shortfall, withdraws the exact shortfall amount\\n * from the configured strategy (up to what the strategy holds)\\n *\\n * The Safe retains full override control via `setStrategy`.\\n */\\ncontract AutoWithdrawalModule is AbstractSafeModule {\\n // \\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500 Immutables \\u2500\\u2500\\n\\n /// @notice The vault whose withdrawal queue is being funded.\\n IVault public immutable vault;\\n\\n /// @notice The vault's base asset (e.g. USDC for OUSD, WETH for OETH).\\n /// Stored as an address to match IStrategy.checkBalance() signature.\\n address public immutable asset;\\n\\n // \\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500 Mutable config \\u2500\\u2500\\n\\n /// @notice The strategy from which liquidity is pulled to fill the queue.\\n address public strategy;\\n\\n // \\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500 Events \\u2500\\u2500\\n\\n /// @notice Emitted when liquidity is successfully moved from strategy to vault.\\n event LiquidityWithdrawn(\\n address indexed strategy,\\n uint256 amount,\\n uint256 remainingShortfall\\n );\\n\\n /// @notice Emitted when the strategy does not hold enough funds to cover the shortfall.\\n /// No withdrawal is attempted; an operator alert should fire on this event.\\n event InsufficientStrategyLiquidity(\\n address indexed strategy,\\n uint256 shortfall,\\n uint256 available\\n );\\n\\n /// @notice Emitted when the Safe exec call to withdrawFromStrategy fails.\\n event WithdrawalFailed(address indexed strategy, uint256 attemptedAmount);\\n\\n /// @notice Emitted when the strategy address is updated.\\n event StrategyUpdated(address oldStrategy, address newStrategy);\\n\\n // \\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500 Constructor \\u2500\\u2500\\n\\n /**\\n * @param _safeContract Address of the Gnosis Safe (Guardian multisig).\\n * @param _operator Address of the off-chain operator (e.g. Defender relayer).\\n * @param _vault Address of the OUSD/OETH vault.\\n * @param _strategy Initial strategy to pull liquidity from.\\n */\\n constructor(\\n address _safeContract,\\n address _operator,\\n address _vault,\\n address _strategy\\n ) AbstractSafeModule(_safeContract) {\\n require(_vault != address(0), \\\"Invalid vault\\\");\\n require(_strategy != address(0), \\\"Invalid strategy\\\");\\n\\n vault = IVault(_vault);\\n asset = IVault(_vault).asset();\\n\\n _setStrategy(_strategy);\\n\\n _grantRole(OPERATOR_ROLE, _operator);\\n }\\n\\n // \\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500 Core automation \\u2500\\u2500\\n\\n /**\\n * @notice Fund the vault's withdrawal queue from the configured strategy.\\n * Called periodically by an off-chain operator (Defender Actions).\\n *\\n * Steps:\\n * 1. Ask the vault to absorb any idle asset it already holds.\\n * 2. Compute the remaining shortfall.\\n * 3. Pull up to that amount from the strategy via the Safe.\\n *\\n * This function never reverts on \\\"soft\\\" failures (strategy underfunded,\\n * Safe exec failure). It emits a descriptive event instead so off-chain\\n * monitoring can alert the team without breaking the Defender action.\\n */\\n function fundWithdrawals() external onlyOperator {\\n // Step 1: Let the vault absorb any asset it already holds idle.\\n // This is a permissionless call; no Safe exec needed.\\n vault.addWithdrawalQueueLiquidity();\\n\\n // Step 2: Read the current shortfall.\\n uint256 shortfall = pendingShortfall();\\n\\n if (shortfall == 0) {\\n // Queue is fully funded \\u2014 nothing to do.\\n return;\\n }\\n\\n // Step 3: Read available balance from the strategy.\\n uint256 strategyBalance = IStrategy(strategy).checkBalance(asset);\\n\\n // Withdraw the lesser of the shortfall and what the strategy holds.\\n uint256 toWithdraw = shortfall < strategyBalance\\n ? shortfall\\n : strategyBalance;\\n\\n if (toWithdraw == 0) {\\n emit InsufficientStrategyLiquidity(\\n strategy,\\n shortfall,\\n strategyBalance\\n );\\n return;\\n }\\n\\n // Step 4: Execute withdrawal via the Safe (which holds the Strategist role).\\n address[] memory assets = new address[](1);\\n assets[0] = asset;\\n uint256[] memory amounts = new uint256[](1);\\n amounts[0] = toWithdraw;\\n\\n bool success = safeContract.execTransactionFromModule(\\n address(vault),\\n 0,\\n abi.encodeWithSelector(\\n IVault.withdrawFromStrategy.selector,\\n strategy,\\n assets,\\n amounts\\n ),\\n 0 // Call (not delegatecall)\\n );\\n\\n if (!success) {\\n emit WithdrawalFailed(strategy, toWithdraw);\\n return;\\n }\\n\\n emit LiquidityWithdrawn(strategy, toWithdraw, shortfall - toWithdraw);\\n }\\n\\n // \\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500 Guardian controls \\u2500\\u2500\\n\\n /**\\n * @notice Change the strategy from which liquidity is pulled.\\n * @param _strategy New strategy address. Must not be zero.\\n */\\n function setStrategy(address _strategy) external onlySafe {\\n _setStrategy(_strategy);\\n }\\n\\n function _setStrategy(address _strategy) internal {\\n require(_strategy != address(0), \\\"Invalid strategy\\\");\\n emit StrategyUpdated(strategy, _strategy);\\n strategy = _strategy;\\n }\\n\\n // \\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500 View helpers \\u2500\\u2500\\n\\n /**\\n * @notice The current unmet shortfall in the vault's withdrawal queue.\\n * @dev This is a raw read of `queued - claimable`. It does NOT account for\\n * idle vault asset that `addWithdrawalQueueLiquidity()` would absorb.\\n * For a fully up-to-date figure, call `vault.addWithdrawalQueueLiquidity()`\\n * first (which is what `fundWithdrawals()` does).\\n * @return shortfall Queue shortfall in asset units (vault asset decimals).\\n */\\n function pendingShortfall() public view returns (uint256 shortfall) {\\n VaultStorage.WithdrawalQueueMetadata memory meta = vault\\n .withdrawalQueueMetadata();\\n shortfall = meta.queued - meta.claimable;\\n }\\n}\\n\",\"keccak256\":\"0x9d27896145547badd4932cfcd5685f7e13b852a5cbb4b76220a514764291685a\",\"license\":\"BUSL-1.1\"},\"contracts/governance/Governable.sol\":{\"content\":\"// SPDX-License-Identifier: BUSL-1.1\\npragma solidity ^0.8.0;\\n\\n/**\\n * @title Base for contracts that are managed by the Origin Protocol's Governor.\\n * @dev Copy of the openzeppelin Ownable.sol contract with nomenclature change\\n * from owner to governor and renounce methods removed. Does not use\\n * Context.sol like Ownable.sol does for simplification.\\n * @author Origin Protocol Inc\\n */\\nabstract contract Governable {\\n // Storage position of the owner and pendingOwner of the contract\\n // keccak256(\\\"OUSD.governor\\\");\\n bytes32 private constant governorPosition =\\n 0x7bea13895fa79d2831e0a9e28edede30099005a50d652d8957cf8a607ee6ca4a;\\n\\n // keccak256(\\\"OUSD.pending.governor\\\");\\n bytes32 private constant pendingGovernorPosition =\\n 0x44c4d30b2eaad5130ad70c3ba6972730566f3e6359ab83e800d905c61b1c51db;\\n\\n // keccak256(\\\"OUSD.reentry.status\\\");\\n bytes32 private constant reentryStatusPosition =\\n 0x53bf423e48ed90e97d02ab0ebab13b2a235a6bfbe9c321847d5c175333ac4535;\\n\\n // See OpenZeppelin ReentrancyGuard implementation\\n uint256 constant _NOT_ENTERED = 1;\\n uint256 constant _ENTERED = 2;\\n\\n event PendingGovernorshipTransfer(\\n address indexed previousGovernor,\\n address indexed newGovernor\\n );\\n\\n event GovernorshipTransferred(\\n address indexed previousGovernor,\\n address indexed newGovernor\\n );\\n\\n /**\\n * @notice Returns the address of the current Governor.\\n */\\n function governor() public view returns (address) {\\n return _governor();\\n }\\n\\n /**\\n * @dev Returns the address of the current Governor.\\n */\\n function _governor() internal view returns (address governorOut) {\\n bytes32 position = governorPosition;\\n // solhint-disable-next-line no-inline-assembly\\n assembly {\\n governorOut := sload(position)\\n }\\n }\\n\\n /**\\n * @dev Returns the address of the pending Governor.\\n */\\n function _pendingGovernor()\\n internal\\n view\\n returns (address pendingGovernor)\\n {\\n bytes32 position = pendingGovernorPosition;\\n // solhint-disable-next-line no-inline-assembly\\n assembly {\\n pendingGovernor := sload(position)\\n }\\n }\\n\\n /**\\n * @dev Throws if called by any account other than the Governor.\\n */\\n modifier onlyGovernor() {\\n require(isGovernor(), \\\"Caller is not the Governor\\\");\\n _;\\n }\\n\\n /**\\n * @notice Returns true if the caller is the current Governor.\\n */\\n function isGovernor() public view returns (bool) {\\n return msg.sender == _governor();\\n }\\n\\n function _setGovernor(address newGovernor) internal {\\n emit GovernorshipTransferred(_governor(), newGovernor);\\n\\n bytes32 position = governorPosition;\\n // solhint-disable-next-line no-inline-assembly\\n assembly {\\n sstore(position, newGovernor)\\n }\\n }\\n\\n /**\\n * @dev Prevents a contract from calling itself, directly or indirectly.\\n * Calling a `nonReentrant` function from another `nonReentrant`\\n * function is not supported. It is possible to prevent this from happening\\n * by making the `nonReentrant` function external, and make it call a\\n * `private` function that does the actual work.\\n */\\n modifier nonReentrant() {\\n bytes32 position = reentryStatusPosition;\\n uint256 _reentry_status;\\n // solhint-disable-next-line no-inline-assembly\\n assembly {\\n _reentry_status := sload(position)\\n }\\n\\n // On the first call to nonReentrant, _notEntered will be true\\n require(_reentry_status != _ENTERED, \\\"Reentrant call\\\");\\n\\n // Any calls to nonReentrant after this point will fail\\n // solhint-disable-next-line no-inline-assembly\\n assembly {\\n sstore(position, _ENTERED)\\n }\\n\\n _;\\n\\n // By storing the original value once again, a refund is triggered (see\\n // https://eips.ethereum.org/EIPS/eip-2200)\\n // solhint-disable-next-line no-inline-assembly\\n assembly {\\n sstore(position, _NOT_ENTERED)\\n }\\n }\\n\\n function _setPendingGovernor(address newGovernor) internal {\\n bytes32 position = pendingGovernorPosition;\\n // solhint-disable-next-line no-inline-assembly\\n assembly {\\n sstore(position, newGovernor)\\n }\\n }\\n\\n /**\\n * @notice Transfers Governance of the contract to a new account (`newGovernor`).\\n * Can only be called by the current Governor. Must be claimed for this to complete\\n * @param _newGovernor Address of the new Governor\\n */\\n function transferGovernance(address _newGovernor) external onlyGovernor {\\n _setPendingGovernor(_newGovernor);\\n emit PendingGovernorshipTransfer(_governor(), _newGovernor);\\n }\\n\\n /**\\n * @notice Claim Governance of the contract to a new account (`newGovernor`).\\n * Can only be called by the new Governor.\\n */\\n function claimGovernance() external {\\n require(\\n msg.sender == _pendingGovernor(),\\n \\\"Only the pending Governor can complete the claim\\\"\\n );\\n _changeGovernor(msg.sender);\\n }\\n\\n /**\\n * @dev Change Governance of the contract to a new account (`newGovernor`).\\n * @param _newGovernor Address of the new Governor\\n */\\n function _changeGovernor(address _newGovernor) internal {\\n require(_newGovernor != address(0), \\\"New Governor is address(0)\\\");\\n _setGovernor(_newGovernor);\\n }\\n}\\n\",\"keccak256\":\"0xf32f873c8bfbacf2e5f01d0cf37bc7f54fbd5aa656e95c8a599114229946f107\",\"license\":\"BUSL-1.1\"},\"contracts/interfaces/IBasicToken.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.0;\\n\\ninterface IBasicToken {\\n function symbol() external view returns (string memory);\\n\\n function decimals() external view returns (uint8);\\n}\\n\",\"keccak256\":\"0xa562062698aa12572123b36dfd2072f1a39e44fed2031cc19c2c9fd522f96ec2\",\"license\":\"MIT\"},\"contracts/interfaces/ISafe.sol\":{\"content\":\"// SPDX-License-Identifier: BUSL-1.1\\npragma solidity ^0.8.0;\\n\\ninterface ISafe {\\n function execTransactionFromModule(\\n address,\\n uint256,\\n bytes memory,\\n uint8\\n ) external returns (bool);\\n}\\n\",\"keccak256\":\"0x6d5fb3512c4fab418222023fb1b482891906eae8d2bda9d1eb2ef3d3c7653dee\",\"license\":\"BUSL-1.1\"},\"contracts/interfaces/IStrategy.sol\":{\"content\":\"// SPDX-License-Identifier: BUSL-1.1\\npragma solidity ^0.8.0;\\n\\n/**\\n * @title Platform interface to integrate with lending platform like Compound, AAVE etc.\\n */\\ninterface IStrategy {\\n /**\\n * @dev Deposit the given asset to platform\\n * @param _asset asset address\\n * @param _amount Amount to deposit\\n */\\n function deposit(address _asset, uint256 _amount) external;\\n\\n /**\\n * @dev Deposit the entire balance of all supported assets in the Strategy\\n * to the platform\\n */\\n function depositAll() external;\\n\\n /**\\n * @dev Withdraw given asset from Lending platform\\n */\\n function withdraw(\\n address _recipient,\\n address _asset,\\n uint256 _amount\\n ) external;\\n\\n /**\\n * @dev Liquidate all assets in strategy and return them to Vault.\\n */\\n function withdrawAll() external;\\n\\n /**\\n * @dev Returns the current balance of the given asset.\\n */\\n function checkBalance(address _asset)\\n external\\n view\\n returns (uint256 balance);\\n\\n /**\\n * @dev Returns bool indicating whether strategy supports asset.\\n */\\n function supportsAsset(address _asset) external view returns (bool);\\n\\n /**\\n * @dev Collect reward tokens from the Strategy.\\n */\\n function collectRewardTokens() external;\\n\\n /**\\n * @dev The address array of the reward tokens for the Strategy.\\n */\\n function getRewardTokenAddresses() external view returns (address[] memory);\\n\\n function harvesterAddress() external view returns (address);\\n\\n function transferToken(address token, uint256 amount) external;\\n\\n function setRewardTokenAddresses(address[] calldata _rewardTokenAddresses)\\n external;\\n}\\n\",\"keccak256\":\"0x79ca47defb3b5a56bba13f14c440838152fd1c1aa640476154516a16da4da8ba\",\"license\":\"BUSL-1.1\"},\"contracts/interfaces/IVault.sol\":{\"content\":\"// SPDX-License-Identifier: BUSL-1.1\\npragma solidity ^0.8.0;\\n\\nimport { VaultStorage } from \\\"../vault/VaultStorage.sol\\\";\\n\\ninterface IVault {\\n // slither-disable-start constable-states\\n\\n event AssetAllocated(address _asset, address _strategy, uint256 _amount);\\n event StrategyApproved(address _addr);\\n event StrategyRemoved(address _addr);\\n event Mint(address _addr, uint256 _value);\\n event Redeem(address _addr, uint256 _value);\\n event CapitalPaused();\\n event CapitalUnpaused();\\n event DefaultStrategyUpdated(address _strategy);\\n event RebasePaused();\\n event RebaseUnpaused();\\n event VaultBufferUpdated(uint256 _vaultBuffer);\\n event AllocateThresholdUpdated(uint256 _threshold);\\n event RebaseThresholdUpdated(uint256 _threshold);\\n event StrategistUpdated(address _address);\\n event MaxSupplyDiffChanged(uint256 maxSupplyDiff);\\n event YieldDistribution(address _to, uint256 _yield, uint256 _fee);\\n event TrusteeFeeBpsChanged(uint256 _basis);\\n event TrusteeAddressChanged(address _address);\\n event StrategyAddedToMintWhitelist(address indexed strategy);\\n event StrategyRemovedFromMintWhitelist(address indexed strategy);\\n event RebasePerSecondMaxChanged(uint256 rebaseRatePerSecond);\\n event DripDurationChanged(uint256 dripDuration);\\n event WithdrawalRequested(\\n address indexed _withdrawer,\\n uint256 indexed _requestId,\\n uint256 _amount,\\n uint256 _queued\\n );\\n event WithdrawalClaimed(\\n address indexed _withdrawer,\\n uint256 indexed _requestId,\\n uint256 _amount\\n );\\n event WithdrawalClaimable(uint256 _claimable, uint256 _newClaimable);\\n event WithdrawalClaimDelayUpdated(uint256 _newDelay);\\n\\n // Governable.sol\\n function transferGovernance(address _newGovernor) external;\\n\\n function claimGovernance() external;\\n\\n function governor() external view returns (address);\\n\\n // VaultAdmin.sol\\n function setVaultBuffer(uint256 _vaultBuffer) external;\\n\\n function vaultBuffer() external view returns (uint256);\\n\\n function setAutoAllocateThreshold(uint256 _threshold) external;\\n\\n function autoAllocateThreshold() external view returns (uint256);\\n\\n function setRebaseThreshold(uint256 _threshold) external;\\n\\n function rebaseThreshold() external view returns (uint256);\\n\\n function setStrategistAddr(address _address) external;\\n\\n function strategistAddr() external view returns (address);\\n\\n function setMaxSupplyDiff(uint256 _maxSupplyDiff) external;\\n\\n function maxSupplyDiff() external view returns (uint256);\\n\\n function setTrusteeAddress(address _address) external;\\n\\n function trusteeAddress() external view returns (address);\\n\\n function setTrusteeFeeBps(uint256 _basis) external;\\n\\n function trusteeFeeBps() external view returns (uint256);\\n\\n function approveStrategy(address _addr) external;\\n\\n function removeStrategy(address _addr) external;\\n\\n function setDefaultStrategy(address _strategy) external;\\n\\n function defaultStrategy() external view returns (address);\\n\\n function pauseRebase() external;\\n\\n function unpauseRebase() external;\\n\\n function rebasePaused() external view returns (bool);\\n\\n function pauseCapital() external;\\n\\n function unpauseCapital() external;\\n\\n function capitalPaused() external view returns (bool);\\n\\n function transferToken(address _asset, uint256 _amount) external;\\n\\n function withdrawAllFromStrategy(address _strategyAddr) external;\\n\\n function withdrawAllFromStrategies() external;\\n\\n function withdrawFromStrategy(\\n address _strategyFromAddress,\\n address[] calldata _assets,\\n uint256[] calldata _amounts\\n ) external;\\n\\n function depositToStrategy(\\n address _strategyToAddress,\\n address[] calldata _assets,\\n uint256[] calldata _amounts\\n ) external;\\n\\n // VaultCore.sol\\n function mint(\\n address _asset,\\n uint256 _amount,\\n uint256 _minimumOusdAmount\\n ) external;\\n\\n function mintForStrategy(uint256 _amount) external;\\n\\n function redeem(uint256 _amount, uint256 _minimumUnitAmount) external;\\n\\n function burnForStrategy(uint256 _amount) external;\\n\\n function allocate() external;\\n\\n function rebase() external;\\n\\n function totalValue() external view returns (uint256 value);\\n\\n function checkBalance(address _asset) external view returns (uint256);\\n\\n /// @notice Deprecated: use calculateRedeemOutput\\n function calculateRedeemOutputs(uint256 _amount)\\n external\\n view\\n returns (uint256[] memory);\\n\\n function calculateRedeemOutput(uint256 _amount)\\n external\\n view\\n returns (uint256);\\n\\n function getAssetCount() external view returns (uint256);\\n\\n function getAllAssets() external view returns (address[] memory);\\n\\n function getStrategyCount() external view returns (uint256);\\n\\n function getAllStrategies() external view returns (address[] memory);\\n\\n /// @notice Deprecated.\\n function isSupportedAsset(address _asset) external view returns (bool);\\n\\n function dripper() external view returns (address);\\n\\n function asset() external view returns (address);\\n\\n function initialize(address) external;\\n\\n function addWithdrawalQueueLiquidity() external;\\n\\n function requestWithdrawal(uint256 _amount)\\n external\\n returns (uint256 requestId, uint256 queued);\\n\\n function claimWithdrawal(uint256 requestId)\\n external\\n returns (uint256 amount);\\n\\n function claimWithdrawals(uint256[] memory requestIds)\\n external\\n returns (uint256[] memory amounts, uint256 totalAmount);\\n\\n function withdrawalQueueMetadata()\\n external\\n view\\n returns (VaultStorage.WithdrawalQueueMetadata memory);\\n\\n function withdrawalRequests(uint256 requestId)\\n external\\n view\\n returns (VaultStorage.WithdrawalRequest memory);\\n\\n function addStrategyToMintWhitelist(address strategyAddr) external;\\n\\n function removeStrategyFromMintWhitelist(address strategyAddr) external;\\n\\n function isMintWhitelistedStrategy(address strategyAddr)\\n external\\n view\\n returns (bool);\\n\\n function withdrawalClaimDelay() external view returns (uint256);\\n\\n function setWithdrawalClaimDelay(uint256 newDelay) external;\\n\\n function lastRebase() external view returns (uint64);\\n\\n function dripDuration() external view returns (uint64);\\n\\n function setDripDuration(uint256 _dripDuration) external;\\n\\n function rebasePerSecondMax() external view returns (uint64);\\n\\n function setRebaseRateMax(uint256 yearlyApr) external;\\n\\n function rebasePerSecondTarget() external view returns (uint64);\\n\\n function previewYield() external view returns (uint256 yield);\\n\\n function weth() external view returns (address);\\n\\n // slither-disable-end constable-states\\n}\\n\",\"keccak256\":\"0x61e2ad6f41abac69275ba86e51d9d3cd95dfd6fc6b81960cf75b1e06adb6251d\",\"license\":\"BUSL-1.1\"},\"contracts/token/OUSD.sol\":{\"content\":\"// SPDX-License-Identifier: BUSL-1.1\\npragma solidity ^0.8.0;\\n\\n/**\\n * @title OUSD Token Contract\\n * @dev ERC20 compatible contract for OUSD\\n * @dev Implements an elastic supply\\n * @author Origin Protocol Inc\\n */\\nimport { IVault } from \\\"../interfaces/IVault.sol\\\";\\nimport { Governable } from \\\"../governance/Governable.sol\\\";\\nimport { SafeCast } from \\\"@openzeppelin/contracts/utils/math/SafeCast.sol\\\";\\n\\ncontract OUSD is Governable {\\n using SafeCast for int256;\\n using SafeCast for uint256;\\n\\n /// @dev Event triggered when the supply changes\\n /// @param totalSupply Updated token total supply\\n /// @param rebasingCredits Updated token rebasing credits\\n /// @param rebasingCreditsPerToken Updated token rebasing credits per token\\n event TotalSupplyUpdatedHighres(\\n uint256 totalSupply,\\n uint256 rebasingCredits,\\n uint256 rebasingCreditsPerToken\\n );\\n /// @dev Event triggered when an account opts in for rebasing\\n /// @param account Address of the account\\n event AccountRebasingEnabled(address account);\\n /// @dev Event triggered when an account opts out of rebasing\\n /// @param account Address of the account\\n event AccountRebasingDisabled(address account);\\n /// @dev Emitted when `value` tokens are moved from one account `from` to\\n /// another `to`.\\n /// @param from Address of the account tokens are moved from\\n /// @param to Address of the account tokens are moved to\\n /// @param value Amount of tokens transferred\\n event Transfer(address indexed from, address indexed to, uint256 value);\\n /// @dev Emitted when the allowance of a `spender` for an `owner` is set by\\n /// a call to {approve}. `value` is the new allowance.\\n /// @param owner Address of the owner approving allowance\\n /// @param spender Address of the spender allowance is granted to\\n /// @param value Amount of tokens spender can transfer\\n event Approval(\\n address indexed owner,\\n address indexed spender,\\n uint256 value\\n );\\n /// @dev Yield resulting from {changeSupply} that a `source` account would\\n /// receive is directed to `target` account.\\n /// @param source Address of the source forwarding the yield\\n /// @param target Address of the target receiving the yield\\n event YieldDelegated(address source, address target);\\n /// @dev Yield delegation from `source` account to the `target` account is\\n /// suspended.\\n /// @param source Address of the source suspending yield forwarding\\n /// @param target Address of the target no longer receiving yield from `source`\\n /// account\\n event YieldUndelegated(address source, address target);\\n\\n enum RebaseOptions {\\n NotSet,\\n StdNonRebasing,\\n StdRebasing,\\n YieldDelegationSource,\\n YieldDelegationTarget\\n }\\n\\n uint256[154] private _gap; // Slots to align with deployed contract\\n uint256 private constant MAX_SUPPLY = type(uint128).max;\\n /// @dev The amount of tokens in existence\\n uint256 public totalSupply;\\n mapping(address => mapping(address => uint256)) private allowances;\\n /// @dev The vault with privileges to execute {mint}, {burn}\\n /// and {changeSupply}\\n address public vaultAddress;\\n mapping(address => uint256) internal creditBalances;\\n // the 2 storage variables below need trailing underscores to not name collide with public functions\\n uint256 private rebasingCredits_; // Sum of all rebasing credits (creditBalances for rebasing accounts)\\n uint256 private rebasingCreditsPerToken_;\\n /// @dev The amount of tokens that are not rebasing - receiving yield\\n uint256 public nonRebasingSupply;\\n mapping(address => uint256) internal alternativeCreditsPerToken;\\n /// @dev A map of all addresses and their respective RebaseOptions\\n mapping(address => RebaseOptions) public rebaseState;\\n mapping(address => uint256) private __deprecated_isUpgraded;\\n /// @dev A map of addresses that have yields forwarded to. This is an\\n /// inverse mapping of {yieldFrom}\\n /// Key Account forwarding yield\\n /// Value Account receiving yield\\n mapping(address => address) public yieldTo;\\n /// @dev A map of addresses that are receiving the yield. This is an\\n /// inverse mapping of {yieldTo}\\n /// Key Account receiving yield\\n /// Value Account forwarding yield\\n mapping(address => address) public yieldFrom;\\n\\n uint256 private constant RESOLUTION_INCREASE = 1e9;\\n uint256[34] private __gap; // including below gap totals up to 200\\n\\n /// @dev Verifies that the caller is the Governor or Strategist.\\n modifier onlyGovernorOrStrategist() {\\n require(\\n isGovernor() || msg.sender == IVault(vaultAddress).strategistAddr(),\\n \\\"Caller is not the Strategist or Governor\\\"\\n );\\n _;\\n }\\n\\n /// @dev Initializes the contract and sets necessary variables.\\n /// @param _vaultAddress Address of the vault contract\\n /// @param _initialCreditsPerToken The starting rebasing credits per token.\\n function initialize(address _vaultAddress, uint256 _initialCreditsPerToken)\\n external\\n onlyGovernor\\n {\\n require(_vaultAddress != address(0), \\\"Zero vault address\\\");\\n require(vaultAddress == address(0), \\\"Already initialized\\\");\\n\\n rebasingCreditsPerToken_ = _initialCreditsPerToken;\\n vaultAddress = _vaultAddress;\\n }\\n\\n /// @dev Returns the symbol of the token, a shorter version\\n /// of the name.\\n function symbol() external pure virtual returns (string memory) {\\n return \\\"OUSD\\\";\\n }\\n\\n /// @dev Returns the name of the token.\\n function name() external pure virtual returns (string memory) {\\n return \\\"Origin Dollar\\\";\\n }\\n\\n /// @dev Returns the number of decimals used to get its user representation.\\n function decimals() external pure virtual returns (uint8) {\\n return 18;\\n }\\n\\n /**\\n * @dev Verifies that the caller is the Vault contract\\n */\\n modifier onlyVault() {\\n require(vaultAddress == msg.sender, \\\"Caller is not the Vault\\\");\\n _;\\n }\\n\\n /**\\n * @return High resolution rebasingCreditsPerToken\\n */\\n function rebasingCreditsPerTokenHighres() external view returns (uint256) {\\n return rebasingCreditsPerToken_;\\n }\\n\\n /**\\n * @return Low resolution rebasingCreditsPerToken\\n */\\n function rebasingCreditsPerToken() external view returns (uint256) {\\n return rebasingCreditsPerToken_ / RESOLUTION_INCREASE;\\n }\\n\\n /**\\n * @return High resolution total number of rebasing credits\\n */\\n function rebasingCreditsHighres() external view returns (uint256) {\\n return rebasingCredits_;\\n }\\n\\n /**\\n * @return Low resolution total number of rebasing credits\\n */\\n function rebasingCredits() external view returns (uint256) {\\n return rebasingCredits_ / RESOLUTION_INCREASE;\\n }\\n\\n /**\\n * @notice Gets the balance of the specified address.\\n * @param _account Address to query the balance of.\\n * @return A uint256 representing the amount of base units owned by the\\n * specified address.\\n */\\n function balanceOf(address _account) public view returns (uint256) {\\n RebaseOptions state = rebaseState[_account];\\n if (state == RebaseOptions.YieldDelegationSource) {\\n // Saves a slot read when transferring to or from a yield delegating source\\n // since we know creditBalances equals the balance.\\n return creditBalances[_account];\\n }\\n uint256 baseBalance = (creditBalances[_account] * 1e18) /\\n _creditsPerToken(_account);\\n if (state == RebaseOptions.YieldDelegationTarget) {\\n // creditBalances of yieldFrom accounts equals token balances\\n return baseBalance - creditBalances[yieldFrom[_account]];\\n }\\n return baseBalance;\\n }\\n\\n /**\\n * @notice Gets the credits balance of the specified address.\\n * @dev Backwards compatible with old low res credits per token.\\n * @param _account The address to query the balance of.\\n * @return (uint256, uint256) Credit balance and credits per token of the\\n * address\\n */\\n function creditsBalanceOf(address _account)\\n external\\n view\\n returns (uint256, uint256)\\n {\\n uint256 cpt = _creditsPerToken(_account);\\n if (cpt == 1e27) {\\n // For a period before the resolution upgrade, we created all new\\n // contract accounts at high resolution. Since they are not changing\\n // as a result of this upgrade, we will return their true values\\n return (creditBalances[_account], cpt);\\n } else {\\n return (\\n creditBalances[_account] / RESOLUTION_INCREASE,\\n cpt / RESOLUTION_INCREASE\\n );\\n }\\n }\\n\\n /**\\n * @notice Gets the credits balance of the specified address.\\n * @param _account The address to query the balance of.\\n * @return (uint256, uint256, bool) Credit balance, credits per token of the\\n * address, and isUpgraded\\n */\\n function creditsBalanceOfHighres(address _account)\\n external\\n view\\n returns (\\n uint256,\\n uint256,\\n bool\\n )\\n {\\n return (\\n creditBalances[_account],\\n _creditsPerToken(_account),\\n true // all accounts have their resolution \\\"upgraded\\\"\\n );\\n }\\n\\n // Backwards compatible view\\n function nonRebasingCreditsPerToken(address _account)\\n external\\n view\\n returns (uint256)\\n {\\n return alternativeCreditsPerToken[_account];\\n }\\n\\n /**\\n * @notice Transfer tokens to a specified address.\\n * @param _to the address to transfer to.\\n * @param _value the amount to be transferred.\\n * @return true on success.\\n */\\n function transfer(address _to, uint256 _value) external returns (bool) {\\n require(_to != address(0), \\\"Transfer to zero address\\\");\\n\\n _executeTransfer(msg.sender, _to, _value);\\n\\n emit Transfer(msg.sender, _to, _value);\\n return true;\\n }\\n\\n /**\\n * @notice Transfer tokens from one address to another.\\n * @param _from The address you want to send tokens from.\\n * @param _to The address you want to transfer to.\\n * @param _value The amount of tokens to be transferred.\\n * @return true on success.\\n */\\n function transferFrom(\\n address _from,\\n address _to,\\n uint256 _value\\n ) external returns (bool) {\\n require(_to != address(0), \\\"Transfer to zero address\\\");\\n uint256 userAllowance = allowances[_from][msg.sender];\\n require(_value <= userAllowance, \\\"Allowance exceeded\\\");\\n\\n unchecked {\\n allowances[_from][msg.sender] = userAllowance - _value;\\n }\\n\\n _executeTransfer(_from, _to, _value);\\n\\n emit Transfer(_from, _to, _value);\\n return true;\\n }\\n\\n function _executeTransfer(\\n address _from,\\n address _to,\\n uint256 _value\\n ) internal {\\n (\\n int256 fromRebasingCreditsDiff,\\n int256 fromNonRebasingSupplyDiff\\n ) = _adjustAccount(_from, -_value.toInt256());\\n (\\n int256 toRebasingCreditsDiff,\\n int256 toNonRebasingSupplyDiff\\n ) = _adjustAccount(_to, _value.toInt256());\\n\\n _adjustGlobals(\\n fromRebasingCreditsDiff + toRebasingCreditsDiff,\\n fromNonRebasingSupplyDiff + toNonRebasingSupplyDiff\\n );\\n }\\n\\n function _adjustAccount(address _account, int256 _balanceChange)\\n internal\\n returns (int256 rebasingCreditsDiff, int256 nonRebasingSupplyDiff)\\n {\\n RebaseOptions state = rebaseState[_account];\\n int256 currentBalance = balanceOf(_account).toInt256();\\n if (currentBalance + _balanceChange < 0) {\\n revert(\\\"Transfer amount exceeds balance\\\");\\n }\\n uint256 newBalance = (currentBalance + _balanceChange).toUint256();\\n\\n if (state == RebaseOptions.YieldDelegationSource) {\\n address target = yieldTo[_account];\\n uint256 targetOldBalance = balanceOf(target);\\n uint256 targetNewCredits = _balanceToRebasingCredits(\\n targetOldBalance + newBalance\\n );\\n rebasingCreditsDiff =\\n targetNewCredits.toInt256() -\\n creditBalances[target].toInt256();\\n\\n creditBalances[_account] = newBalance;\\n creditBalances[target] = targetNewCredits;\\n } else if (state == RebaseOptions.YieldDelegationTarget) {\\n uint256 newCredits = _balanceToRebasingCredits(\\n newBalance + creditBalances[yieldFrom[_account]]\\n );\\n rebasingCreditsDiff =\\n newCredits.toInt256() -\\n creditBalances[_account].toInt256();\\n creditBalances[_account] = newCredits;\\n } else {\\n _autoMigrate(_account);\\n uint256 alternativeCreditsPerTokenMem = alternativeCreditsPerToken[\\n _account\\n ];\\n if (alternativeCreditsPerTokenMem > 0) {\\n nonRebasingSupplyDiff = _balanceChange;\\n if (alternativeCreditsPerTokenMem != 1e18) {\\n alternativeCreditsPerToken[_account] = 1e18;\\n }\\n creditBalances[_account] = newBalance;\\n } else {\\n uint256 newCredits = _balanceToRebasingCredits(newBalance);\\n rebasingCreditsDiff =\\n newCredits.toInt256() -\\n creditBalances[_account].toInt256();\\n creditBalances[_account] = newCredits;\\n }\\n }\\n }\\n\\n function _adjustGlobals(\\n int256 _rebasingCreditsDiff,\\n int256 _nonRebasingSupplyDiff\\n ) internal {\\n if (_rebasingCreditsDiff != 0) {\\n rebasingCredits_ = (rebasingCredits_.toInt256() +\\n _rebasingCreditsDiff).toUint256();\\n }\\n if (_nonRebasingSupplyDiff != 0) {\\n nonRebasingSupply = (nonRebasingSupply.toInt256() +\\n _nonRebasingSupplyDiff).toUint256();\\n }\\n }\\n\\n /**\\n * @notice Function to check the amount of tokens that _owner has allowed\\n * to `_spender`.\\n * @param _owner The address which owns the funds.\\n * @param _spender The address which will spend the funds.\\n * @return The number of tokens still available for the _spender.\\n */\\n function allowance(address _owner, address _spender)\\n external\\n view\\n returns (uint256)\\n {\\n return allowances[_owner][_spender];\\n }\\n\\n /**\\n * @notice Approve the passed address to spend the specified amount of\\n * tokens on behalf of msg.sender.\\n * @param _spender The address which will spend the funds.\\n * @param _value The amount of tokens to be spent.\\n * @return true on success.\\n */\\n function approve(address _spender, uint256 _value) external returns (bool) {\\n allowances[msg.sender][_spender] = _value;\\n emit Approval(msg.sender, _spender, _value);\\n return true;\\n }\\n\\n /**\\n * @notice Creates `_amount` tokens and assigns them to `_account`,\\n * increasing the total supply.\\n */\\n function mint(address _account, uint256 _amount) external onlyVault {\\n require(_account != address(0), \\\"Mint to the zero address\\\");\\n\\n // Account\\n (\\n int256 toRebasingCreditsDiff,\\n int256 toNonRebasingSupplyDiff\\n ) = _adjustAccount(_account, _amount.toInt256());\\n // Globals\\n _adjustGlobals(toRebasingCreditsDiff, toNonRebasingSupplyDiff);\\n totalSupply = totalSupply + _amount;\\n\\n require(totalSupply < MAX_SUPPLY, \\\"Max supply\\\");\\n emit Transfer(address(0), _account, _amount);\\n }\\n\\n /**\\n * @notice Destroys `_amount` tokens from `_account`,\\n * reducing the total supply.\\n */\\n function burn(address _account, uint256 _amount) external onlyVault {\\n require(_account != address(0), \\\"Burn from the zero address\\\");\\n if (_amount == 0) {\\n return;\\n }\\n\\n // Account\\n (\\n int256 toRebasingCreditsDiff,\\n int256 toNonRebasingSupplyDiff\\n ) = _adjustAccount(_account, -_amount.toInt256());\\n // Globals\\n _adjustGlobals(toRebasingCreditsDiff, toNonRebasingSupplyDiff);\\n totalSupply = totalSupply - _amount;\\n\\n emit Transfer(_account, address(0), _amount);\\n }\\n\\n /**\\n * @dev Get the credits per token for an account. Returns a fixed amount\\n * if the account is non-rebasing.\\n * @param _account Address of the account.\\n */\\n function _creditsPerToken(address _account)\\n internal\\n view\\n returns (uint256)\\n {\\n uint256 alternativeCreditsPerTokenMem = alternativeCreditsPerToken[\\n _account\\n ];\\n if (alternativeCreditsPerTokenMem != 0) {\\n return alternativeCreditsPerTokenMem;\\n } else {\\n return rebasingCreditsPerToken_;\\n }\\n }\\n\\n /**\\n * @dev Auto migrate contracts to be non rebasing,\\n * unless they have opted into yield.\\n * @param _account Address of the account.\\n */\\n function _autoMigrate(address _account) internal {\\n uint256 codeLen = _account.code.length;\\n bool isEOA = (codeLen == 0) ||\\n (codeLen == 23 && bytes3(_account.code) == 0xef0100);\\n // In previous code versions, contracts would not have had their\\n // rebaseState[_account] set to RebaseOptions.NonRebasing when migrated\\n // therefore we check the actual accounting used on the account as well.\\n if (\\n (!isEOA) &&\\n rebaseState[_account] == RebaseOptions.NotSet &&\\n alternativeCreditsPerToken[_account] == 0\\n ) {\\n _rebaseOptOut(_account);\\n }\\n }\\n\\n /**\\n * @dev Calculates credits from contract's global rebasingCreditsPerToken_, and\\n * also balance that corresponds to those credits. The latter is important\\n * when adjusting the contract's global nonRebasingSupply to circumvent any\\n * possible rounding errors.\\n *\\n * @param _balance Balance of the account.\\n */\\n function _balanceToRebasingCredits(uint256 _balance)\\n internal\\n view\\n returns (uint256 rebasingCredits)\\n {\\n // Rounds up, because we need to ensure that accounts always have\\n // at least the balance that they should have.\\n // Note this should always be used on an absolute account value,\\n // not on a possibly negative diff, because then the rounding would be wrong.\\n return ((_balance) * rebasingCreditsPerToken_ + 1e18 - 1) / 1e18;\\n }\\n\\n /**\\n * @notice The calling account will start receiving yield after a successful call.\\n * @param _account Address of the account.\\n */\\n function governanceRebaseOptIn(address _account) external onlyGovernor {\\n require(_account != address(0), \\\"Zero address not allowed\\\");\\n _rebaseOptIn(_account);\\n }\\n\\n /**\\n * @notice The calling account will start receiving yield after a successful call.\\n */\\n function rebaseOptIn() external {\\n _rebaseOptIn(msg.sender);\\n }\\n\\n function _rebaseOptIn(address _account) internal {\\n uint256 balance = balanceOf(_account);\\n\\n // prettier-ignore\\n require(\\n alternativeCreditsPerToken[_account] > 0 ||\\n // Accounts may explicitly `rebaseOptIn` regardless of\\n // accounting if they have a 0 balance.\\n creditBalances[_account] == 0\\n ,\\n \\\"Account must be non-rebasing\\\"\\n );\\n RebaseOptions state = rebaseState[_account];\\n // prettier-ignore\\n require(\\n state == RebaseOptions.StdNonRebasing ||\\n state == RebaseOptions.NotSet,\\n \\\"Only standard non-rebasing accounts can opt in\\\"\\n );\\n\\n uint256 newCredits = _balanceToRebasingCredits(balance);\\n\\n // Account\\n rebaseState[_account] = RebaseOptions.StdRebasing;\\n alternativeCreditsPerToken[_account] = 0;\\n creditBalances[_account] = newCredits;\\n // Globals\\n _adjustGlobals(newCredits.toInt256(), -balance.toInt256());\\n\\n emit AccountRebasingEnabled(_account);\\n }\\n\\n /**\\n * @notice The calling account will no longer receive yield\\n */\\n function rebaseOptOut() external {\\n _rebaseOptOut(msg.sender);\\n }\\n\\n function _rebaseOptOut(address _account) internal {\\n require(\\n alternativeCreditsPerToken[_account] == 0,\\n \\\"Account must be rebasing\\\"\\n );\\n RebaseOptions state = rebaseState[_account];\\n require(\\n state == RebaseOptions.StdRebasing || state == RebaseOptions.NotSet,\\n \\\"Only standard rebasing accounts can opt out\\\"\\n );\\n\\n uint256 oldCredits = creditBalances[_account];\\n uint256 balance = balanceOf(_account);\\n\\n // Account\\n rebaseState[_account] = RebaseOptions.StdNonRebasing;\\n alternativeCreditsPerToken[_account] = 1e18;\\n creditBalances[_account] = balance;\\n // Globals\\n _adjustGlobals(-oldCredits.toInt256(), balance.toInt256());\\n\\n emit AccountRebasingDisabled(_account);\\n }\\n\\n /**\\n * @notice Distribute yield to users. This changes the exchange rate\\n * between \\\"credits\\\" and OUSD tokens to change rebasing user's balances.\\n * @param _newTotalSupply New total supply of OUSD.\\n */\\n function changeSupply(uint256 _newTotalSupply) external onlyVault {\\n require(totalSupply > 0, \\\"Cannot increase 0 supply\\\");\\n\\n if (totalSupply == _newTotalSupply) {\\n emit TotalSupplyUpdatedHighres(\\n totalSupply,\\n rebasingCredits_,\\n rebasingCreditsPerToken_\\n );\\n return;\\n }\\n\\n totalSupply = _newTotalSupply > MAX_SUPPLY\\n ? MAX_SUPPLY\\n : _newTotalSupply;\\n\\n uint256 rebasingSupply = totalSupply - nonRebasingSupply;\\n // round up in the favour of the protocol\\n rebasingCreditsPerToken_ =\\n (rebasingCredits_ * 1e18 + rebasingSupply - 1) /\\n rebasingSupply;\\n\\n require(rebasingCreditsPerToken_ > 0, \\\"Invalid change in supply\\\");\\n\\n emit TotalSupplyUpdatedHighres(\\n totalSupply,\\n rebasingCredits_,\\n rebasingCreditsPerToken_\\n );\\n }\\n\\n /*\\n * @notice Send the yield from one account to another account.\\n * Each account keeps its own balances.\\n */\\n function delegateYield(address _from, address _to)\\n external\\n onlyGovernorOrStrategist\\n {\\n require(_from != address(0), \\\"Zero from address not allowed\\\");\\n require(_to != address(0), \\\"Zero to address not allowed\\\");\\n\\n require(_from != _to, \\\"Cannot delegate to self\\\");\\n require(\\n yieldFrom[_to] == address(0) &&\\n yieldTo[_to] == address(0) &&\\n yieldFrom[_from] == address(0) &&\\n yieldTo[_from] == address(0),\\n \\\"Blocked by existing yield delegation\\\"\\n );\\n RebaseOptions stateFrom = rebaseState[_from];\\n RebaseOptions stateTo = rebaseState[_to];\\n\\n require(\\n stateFrom == RebaseOptions.NotSet ||\\n stateFrom == RebaseOptions.StdNonRebasing ||\\n stateFrom == RebaseOptions.StdRebasing,\\n \\\"Invalid rebaseState from\\\"\\n );\\n\\n require(\\n stateTo == RebaseOptions.NotSet ||\\n stateTo == RebaseOptions.StdNonRebasing ||\\n stateTo == RebaseOptions.StdRebasing,\\n \\\"Invalid rebaseState to\\\"\\n );\\n\\n if (alternativeCreditsPerToken[_from] == 0) {\\n _rebaseOptOut(_from);\\n }\\n if (alternativeCreditsPerToken[_to] > 0) {\\n _rebaseOptIn(_to);\\n }\\n\\n uint256 fromBalance = balanceOf(_from);\\n uint256 toBalance = balanceOf(_to);\\n uint256 oldToCredits = creditBalances[_to];\\n uint256 newToCredits = _balanceToRebasingCredits(\\n fromBalance + toBalance\\n );\\n\\n // Set up the bidirectional links\\n yieldTo[_from] = _to;\\n yieldFrom[_to] = _from;\\n\\n // Local\\n rebaseState[_from] = RebaseOptions.YieldDelegationSource;\\n alternativeCreditsPerToken[_from] = 1e18;\\n creditBalances[_from] = fromBalance;\\n rebaseState[_to] = RebaseOptions.YieldDelegationTarget;\\n creditBalances[_to] = newToCredits;\\n\\n // Global\\n int256 creditsChange = newToCredits.toInt256() -\\n oldToCredits.toInt256();\\n _adjustGlobals(creditsChange, -(fromBalance).toInt256());\\n emit YieldDelegated(_from, _to);\\n }\\n\\n /*\\n * @notice Stop sending the yield from one account to another account.\\n */\\n function undelegateYield(address _from) external onlyGovernorOrStrategist {\\n // Require a delegation, which will also ensure a valid delegation\\n require(yieldTo[_from] != address(0), \\\"Zero address not allowed\\\");\\n\\n address to = yieldTo[_from];\\n uint256 fromBalance = balanceOf(_from);\\n uint256 toBalance = balanceOf(to);\\n uint256 oldToCredits = creditBalances[to];\\n uint256 newToCredits = _balanceToRebasingCredits(toBalance);\\n\\n // Remove the bidirectional links\\n yieldFrom[to] = address(0);\\n yieldTo[_from] = address(0);\\n\\n // Local\\n rebaseState[_from] = RebaseOptions.StdNonRebasing;\\n // alternativeCreditsPerToken[from] already 1e18 from `delegateYield()`\\n creditBalances[_from] = fromBalance;\\n rebaseState[to] = RebaseOptions.StdRebasing;\\n // alternativeCreditsPerToken[to] already 0 from `delegateYield()`\\n creditBalances[to] = newToCredits;\\n\\n // Global\\n int256 creditsChange = newToCredits.toInt256() -\\n oldToCredits.toInt256();\\n _adjustGlobals(creditsChange, fromBalance.toInt256());\\n emit YieldUndelegated(_from, to);\\n }\\n}\\n\",\"keccak256\":\"0x73439bef6569f5adf6f5ce2cb54a5f0d3109d4819457532236e172a7091980a9\",\"license\":\"BUSL-1.1\"},\"contracts/utils/Helpers.sol\":{\"content\":\"// SPDX-License-Identifier: BUSL-1.1\\npragma solidity ^0.8.0;\\n\\nimport { IBasicToken } from \\\"../interfaces/IBasicToken.sol\\\";\\n\\nlibrary Helpers {\\n /**\\n * @notice Fetch the `symbol()` from an ERC20 token\\n * @dev Grabs the `symbol()` from a contract\\n * @param _token Address of the ERC20 token\\n * @return string Symbol of the ERC20 token\\n */\\n function getSymbol(address _token) internal view returns (string memory) {\\n string memory symbol = IBasicToken(_token).symbol();\\n return symbol;\\n }\\n\\n /**\\n * @notice Fetch the `decimals()` from an ERC20 token\\n * @dev Grabs the `decimals()` from a contract and fails if\\n * the decimal value does not live within a certain range\\n * @param _token Address of the ERC20 token\\n * @return uint256 Decimals of the ERC20 token\\n */\\n function getDecimals(address _token) internal view returns (uint256) {\\n uint256 decimals = IBasicToken(_token).decimals();\\n require(\\n decimals >= 4 && decimals <= 18,\\n \\\"Token must have sufficient decimal places\\\"\\n );\\n\\n return decimals;\\n }\\n}\\n\",\"keccak256\":\"0x4366f8d90b34c1eef8bbaaf369b1e5cd59f04027bb3c111f208eaee65bbc0346\",\"license\":\"BUSL-1.1\"},\"contracts/utils/Initializable.sol\":{\"content\":\"// SPDX-License-Identifier: BUSL-1.1\\npragma solidity ^0.8.0;\\n\\n/**\\n * @title Base contract any contracts that need to initialize state after deployment.\\n * @author Origin Protocol Inc\\n */\\nabstract contract Initializable {\\n /**\\n * @dev Indicates that the contract has been initialized.\\n */\\n bool private initialized;\\n\\n /**\\n * @dev Indicates that the contract is in the process of being initialized.\\n */\\n bool private initializing;\\n\\n /**\\n * @dev Modifier to protect an initializer function from being invoked twice.\\n */\\n modifier initializer() {\\n require(\\n initializing || !initialized,\\n \\\"Initializable: contract is already initialized\\\"\\n );\\n\\n bool isTopLevelCall = !initializing;\\n if (isTopLevelCall) {\\n initializing = true;\\n initialized = true;\\n }\\n\\n _;\\n\\n if (isTopLevelCall) {\\n initializing = false;\\n }\\n }\\n\\n uint256[50] private ______gap;\\n}\\n\",\"keccak256\":\"0x50d39ebf38a3d3111f2b77a6c75ece1d4ae731552fec4697ab16fcf6c0d4d5e8\",\"license\":\"BUSL-1.1\"},\"contracts/vault/VaultStorage.sol\":{\"content\":\"// SPDX-License-Identifier: BUSL-1.1\\npragma solidity ^0.8.0;\\n\\n/**\\n * @title OToken VaultStorage contract\\n * @notice The VaultStorage contract defines the storage for the Vault contracts\\n * @author Origin Protocol Inc\\n */\\n\\nimport { IERC20 } from \\\"@openzeppelin/contracts/token/ERC20/IERC20.sol\\\";\\nimport { SafeERC20 } from \\\"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\\\";\\nimport { Address } from \\\"@openzeppelin/contracts/utils/Address.sol\\\";\\n\\nimport { IStrategy } from \\\"../interfaces/IStrategy.sol\\\";\\nimport { IERC20Metadata } from \\\"@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol\\\";\\nimport { Governable } from \\\"../governance/Governable.sol\\\";\\nimport { OUSD } from \\\"../token/OUSD.sol\\\";\\nimport { Initializable } from \\\"../utils/Initializable.sol\\\";\\nimport \\\"../utils/Helpers.sol\\\";\\n\\nabstract contract VaultStorage is Initializable, Governable {\\n using SafeERC20 for IERC20;\\n\\n event AssetAllocated(address _asset, address _strategy, uint256 _amount);\\n event StrategyApproved(address _addr);\\n event StrategyRemoved(address _addr);\\n event Mint(address _addr, uint256 _value);\\n event Redeem(address _addr, uint256 _value);\\n event CapitalPaused();\\n event CapitalUnpaused();\\n event DefaultStrategyUpdated(address _strategy);\\n event RebasePaused();\\n event RebaseUnpaused();\\n event VaultBufferUpdated(uint256 _vaultBuffer);\\n event AllocateThresholdUpdated(uint256 _threshold);\\n event RebaseThresholdUpdated(uint256 _threshold);\\n event StrategistUpdated(address _address);\\n event MaxSupplyDiffChanged(uint256 maxSupplyDiff);\\n event YieldDistribution(address _to, uint256 _yield, uint256 _fee);\\n event TrusteeFeeBpsChanged(uint256 _basis);\\n event TrusteeAddressChanged(address _address);\\n event StrategyAddedToMintWhitelist(address indexed strategy);\\n event StrategyRemovedFromMintWhitelist(address indexed strategy);\\n event RebasePerSecondMaxChanged(uint256 rebaseRatePerSecond);\\n event DripDurationChanged(uint256 dripDuration);\\n event WithdrawalRequested(\\n address indexed _withdrawer,\\n uint256 indexed _requestId,\\n uint256 _amount,\\n uint256 _queued\\n );\\n event WithdrawalClaimed(\\n address indexed _withdrawer,\\n uint256 indexed _requestId,\\n uint256 _amount\\n );\\n event WithdrawalClaimable(uint256 _claimable, uint256 _newClaimable);\\n event WithdrawalClaimDelayUpdated(uint256 _newDelay);\\n\\n // Since we are proxy, all state should be uninitalized.\\n // Since this storage contract does not have logic directly on it\\n // we should not be checking for to see if these variables can be constant.\\n // slither-disable-start uninitialized-state\\n // slither-disable-start constable-states\\n\\n /// @dev mapping of supported vault assets to their configuration\\n uint256 private _deprecated_assets;\\n /// @dev list of all assets supported by the vault.\\n address[] private _deprecated_allAssets;\\n\\n // Strategies approved for use by the Vault\\n struct Strategy {\\n bool isSupported;\\n uint256 _deprecated; // Deprecated storage slot\\n }\\n /// @dev mapping of strategy contracts to their configuration\\n mapping(address => Strategy) public strategies;\\n /// @dev list of all vault strategies\\n address[] internal allStrategies;\\n\\n /// @notice Address of the Oracle price provider contract\\n address private _deprecated_priceProvider;\\n /// @notice pause rebasing if true\\n bool public rebasePaused;\\n /// @notice pause operations that change the OToken supply.\\n /// eg mint, redeem, allocate, mint/burn for strategy\\n bool public capitalPaused;\\n /// @notice Redemption fee in basis points. eg 50 = 0.5%\\n uint256 private _deprecated_redeemFeeBps;\\n /// @notice Percentage of assets to keep in Vault to handle (most) withdrawals. 100% = 1e18.\\n uint256 public vaultBuffer;\\n /// @notice OToken mints over this amount automatically allocate funds. 18 decimals.\\n uint256 public autoAllocateThreshold;\\n /// @notice OToken mints over this amount automatically rebase. 18 decimals.\\n uint256 public rebaseThreshold;\\n\\n /// @dev Address of the OToken token. eg OUSD or OETH.\\n OUSD public oToken;\\n\\n /// @dev Address of the contract responsible for post rebase syncs with AMMs\\n address private _deprecated_rebaseHooksAddr = address(0);\\n\\n /// @dev Deprecated: Address of Uniswap\\n address private _deprecated_uniswapAddr = address(0);\\n\\n /// @notice Address of the Strategist\\n address public strategistAddr = address(0);\\n\\n /// @notice Mapping of asset address to the Strategy that they should automatically\\n // be allocated to\\n uint256 private _deprecated_assetDefaultStrategies;\\n\\n /// @notice Max difference between total supply and total value of assets. 18 decimals.\\n uint256 public maxSupplyDiff;\\n\\n /// @notice Trustee contract that can collect a percentage of yield\\n address public trusteeAddress;\\n\\n /// @notice Amount of yield collected in basis points. eg 2000 = 20%\\n uint256 public trusteeFeeBps;\\n\\n /// @dev Deprecated: Tokens that should be swapped for stablecoins\\n address[] private _deprecated_swapTokens;\\n\\n /// @notice Metapool strategy that is allowed to mint/burn OTokens without changing collateral\\n\\n address private _deprecated_ousdMetaStrategy;\\n\\n /// @notice How much OTokens are currently minted by the strategy\\n int256 private _deprecated_netOusdMintedForStrategy;\\n\\n /// @notice How much net total OTokens are allowed to be minted by all strategies\\n uint256 private _deprecated_netOusdMintForStrategyThreshold;\\n\\n uint256 private _deprecated_swapConfig;\\n\\n // List of strategies that can mint oTokens directly\\n // Used in OETHBaseVaultCore\\n mapping(address => bool) public isMintWhitelistedStrategy;\\n\\n /// @notice Address of the Dripper contract that streams harvested rewards to the Vault\\n /// @dev The vault is proxied so needs to be set with setDripper against the proxy contract.\\n address private _deprecated_dripper;\\n\\n /// Withdrawal Queue Storage /////\\n\\n struct WithdrawalQueueMetadata {\\n // cumulative total of all withdrawal requests included the ones that have already been claimed\\n uint128 queued;\\n // cumulative total of all the requests that can be claimed including the ones that have already been claimed\\n uint128 claimable;\\n // total of all the requests that have been claimed\\n uint128 claimed;\\n // index of the next withdrawal request starting at 0\\n uint128 nextWithdrawalIndex;\\n }\\n\\n /// @notice Global metadata for the withdrawal queue including:\\n /// queued - cumulative total of all withdrawal requests included the ones that have already been claimed\\n /// claimable - cumulative total of all the requests that can be claimed including the ones already claimed\\n /// claimed - total of all the requests that have been claimed\\n /// nextWithdrawalIndex - index of the next withdrawal request starting at 0\\n WithdrawalQueueMetadata public withdrawalQueueMetadata;\\n\\n struct WithdrawalRequest {\\n address withdrawer;\\n bool claimed;\\n uint40 timestamp; // timestamp of the withdrawal request\\n // Amount of oTokens to redeem. eg OETH\\n uint128 amount;\\n // cumulative total of all withdrawal requests including this one.\\n // this request can be claimed when this queued amount is less than or equal to the queue's claimable amount.\\n uint128 queued;\\n }\\n\\n /// @notice Mapping of withdrawal request indices to the user withdrawal request data\\n mapping(uint256 => WithdrawalRequest) public withdrawalRequests;\\n\\n /// @notice Sets a minimum delay that is required to elapse between\\n /// requesting async withdrawals and claiming the request.\\n /// When set to 0 async withdrawals are disabled.\\n uint256 public withdrawalClaimDelay;\\n\\n /// @notice Time in seconds that the vault last rebased yield.\\n uint64 public lastRebase;\\n\\n /// @notice Automatic rebase yield calculations. In seconds. Set to 0 or 1 to disable.\\n uint64 public dripDuration;\\n\\n /// @notice max rebase percentage per second\\n /// Can be used to set maximum yield of the protocol,\\n /// spreading out yield over time\\n uint64 public rebasePerSecondMax;\\n\\n /// @notice target rebase rate limit, based on past rates and funds available.\\n uint64 public rebasePerSecondTarget;\\n\\n uint256 internal constant MAX_REBASE = 0.02 ether;\\n uint256 internal constant MAX_REBASE_PER_SECOND =\\n uint256(0.05 ether) / 1 days;\\n\\n /// @notice Default strategy for asset\\n address public defaultStrategy;\\n\\n // For future use\\n uint256[42] private __gap;\\n\\n /// @notice Index of WETH asset in allAssets array\\n /// Legacy OETHVaultCore code, relocated here for vault consistency.\\n uint256 private _deprecated_wethAssetIndex;\\n\\n /// @dev Address of the asset (eg. WETH or USDC)\\n address public immutable asset;\\n uint8 internal immutable assetDecimals;\\n\\n // slither-disable-end constable-states\\n // slither-disable-end uninitialized-state\\n\\n constructor(address _asset) {\\n uint8 _decimals = IERC20Metadata(_asset).decimals();\\n require(_decimals <= 18, \\\"invalid asset decimals\\\");\\n asset = _asset;\\n assetDecimals = _decimals;\\n }\\n\\n /// @notice Deprecated: use `oToken()` instead.\\n function oUSD() external view returns (OUSD) {\\n return oToken;\\n }\\n}\\n\",\"keccak256\":\"0xcca0e0ebbbbda50b23fba7aea96a1e34f6a9d58c8f2e86e84b0911d4a9e5d418\",\"license\":\"BUSL-1.1\"}},\"version\":1}", + "bytecode": "0x60e060405234801561001057600080fd5b50604051611b96380380611b9683398101604081905261002f916103a6565b6001600160a01b0384166080819052849061004c906000906101a5565b61006c600080516020611b768339815191526080516101a560201b60201c565b506001600160a01b0382166100b85760405162461bcd60e51b815260206004820152600d60248201526c125b9d985b1a59081d985d5b1d609a1b60448201526064015b60405180910390fd5b6001600160a01b0381166101015760405162461bcd60e51b815260206004820152601060248201526f496e76616c696420737472617465677960801b60448201526064016100af565b6001600160a01b03821660a0819052604080516338d52e0f60e01b815290516338d52e0f916004808201926020929091908290030181865afa15801561014b573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061016f91906103fa565b6001600160a01b031660c052610184816101cc565b61019c600080516020611b76833981519152846101a5565b50505050610415565b6101af828261027e565b60008281526001602052604090206101c7908261031d565b505050565b6001600160a01b0381166102155760405162461bcd60e51b815260206004820152601060248201526f496e76616c696420737472617465677960801b60448201526064016100af565b600254604080516001600160a01b03928316815291831660208301527fe4cec16b1a7e6b7979e923da619a8b1e5fd0f0fb6e5c1cf647f350430ee61ca9910160405180910390a1600280546001600160a01b0319166001600160a01b0392909216919091179055565b6000828152602081815260408083206001600160a01b038516845290915290205460ff16610319576000828152602081815260408083206001600160a01b03851684529091529020805460ff191660011790556102d83390565b6001600160a01b0316816001600160a01b0316837f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a45b5050565b6000610332836001600160a01b03841661033b565b90505b92915050565b600081815260018301602052604081205461038257508154600181810184556000848152602080822090930184905584548482528286019093526040902091909155610335565b506000610335565b80516001600160a01b03811681146103a157600080fd5b919050565b600080600080608085870312156103bc57600080fd5b6103c58561038a565b93506103d36020860161038a565b92506103e16040860161038a565b91506103ef6060860161038a565b905092959194509250565b60006020828403121561040c57600080fd5b6103328261038a565b60805160a05160c0516116ec61048a600039600081816102000152818161064c015261074f0152600081816103b3015281816105aa0152818161081a015261098801526000818161037f01528181610436015281816107f001528181610a7d01528181610b180152610bef01526116ec6000f3fe60806040526004361061010d5760003560e01c806391d1485411610095578063ca15c87311610064578063ca15c873146102f9578063d547741f14610319578063f5b541a614610339578063f9081ba21461036d578063fbfa77cf146103a157600080fd5b806391d1485414610284578063a217fddf146102a4578063a8c62e76146102b9578063bec3fa17146102d957600080fd5b806336568abe116100dc57806336568abe146101ce57806338d52e0f146101ee57806380bef06d1461023a57806384e637b51461024f5780639010d07c1461026457600080fd5b806301ffc9a714610119578063248a9ca31461014e5780632f2ff15d1461018c57806333a100ca146101ae57600080fd5b3661011457005b600080fd5b34801561012557600080fd5b5061013961013436600461126c565b6103d5565b60405190151581526020015b60405180910390f35b34801561015a57600080fd5b5061017e610169366004611296565b60009081526020819052604090206001015490565b604051908152602001610145565b34801561019857600080fd5b506101ac6101a73660046112cb565b610400565b005b3480156101ba57600080fd5b506101ac6101c93660046112f7565b61042b565b3480156101da57600080fd5b506101ac6101e93660046112cb565b6104b4565b3480156101fa57600080fd5b506102227f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b039091168152602001610145565b34801561024657600080fd5b506101ac610532565b34801561025b57600080fd5b5061017e610983565b34801561027057600080fd5b5061022261027f366004611312565b610a2a565b34801561029057600080fd5b5061013961029f3660046112cb565b610a49565b3480156102b057600080fd5b5061017e600081565b3480156102c557600080fd5b50600254610222906001600160a01b031681565b3480156102e557600080fd5b506101ac6102f4366004611334565b610a72565b34801561030557600080fd5b5061017e610314366004611296565b610c6f565b34801561032557600080fd5b506101ac6103343660046112cb565b610c86565b34801561034557600080fd5b5061017e7f97667070c54ef182b0f5858b034beac1b6f3089aa2d3188bb1e8929f4fa9b92981565b34801561037957600080fd5b506102227f000000000000000000000000000000000000000000000000000000000000000081565b3480156103ad57600080fd5b506102227f000000000000000000000000000000000000000000000000000000000000000081565b60006001600160e01b03198216635a05180f60e01b14806103fa57506103fa82610cac565b92915050565b60008281526020819052604090206001015461041c8133610ce1565b6104268383610d45565b505050565b336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146104a85760405162461bcd60e51b815260206004820152601f60248201527f43616c6c6572206973206e6f7420746865207361666520636f6e74726163740060448201526064015b60405180910390fd5b6104b181610d67565b50565b6001600160a01b03811633146105245760405162461bcd60e51b815260206004820152602f60248201527f416363657373436f6e74726f6c3a2063616e206f6e6c792072656e6f756e636560448201526e103937b632b9903337b91039b2b63360891b606482015260840161049f565b61052e8282610e19565b5050565b61055c7f97667070c54ef182b0f5858b034beac1b6f3089aa2d3188bb1e8929f4fa9b92933610a49565b6105a85760405162461bcd60e51b815260206004820152601960248201527f43616c6c6572206973206e6f7420616e206f70657261746f7200000000000000604482015260640161049f565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663b9b17f9f6040518163ffffffff1660e01b8152600401600060405180830381600087803b15801561060357600080fd5b505af1158015610617573d6000803e3d6000fd5b505050506000610625610983565b9050806000036106325750565b600254604051632fa8a91360e11b81526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000811660048301526000921690635f51522690602401602060405180830381865afa15801561069d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106c1919061135e565b905060008183106106d257816106d4565b825b90508060000361072b5760025460408051858152602081018590526001600160a01b03909216917f0ef7fed0b5b0ce7d6585da4aaebff577aad9e44fe4e8c333551e03a31e301636910160405180910390a2505050565b604080516001808252818301909252600091602080830190803683370190505090507f0000000000000000000000000000000000000000000000000000000000000000816000815181106107815761078161138d565b6001600160a01b03929092166020928302919091019091015260408051600180825281830190925260009181602001602082028036833701905050905082816000815181106107d2576107d261138d565b60209081029190910101526002546040516000916001600160a01b037f000000000000000000000000000000000000000000000000000000000000000081169263468721a7927f000000000000000000000000000000000000000000000000000000000000000092869263ae69f3cb60e01b926108579216908a908a906024016113a3565b60408051601f198184030181529181526020820180516001600160e01b03166001600160e01b03199485161790525160e086901b90921682526108a193929160009060040161148e565b6020604051808303816000875af11580156108c0573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108e491906114c9565b905080610936576002546040518581526001600160a01b03909116907f92873d130824b495f22ad10f7f14028200557770e5986714318e78c54f3aa83c906020015b60405180910390a2505050505050565b6002546001600160a01b03167fb195a67e698c5700e4f48f7b7748dda3a206ee2767ef024b61a26d7b17b2d63a8561096e818a611501565b60408051928352602083019190915201610926565b6000807f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663362bd1a36040518163ffffffff1660e01b8152600401608060405180830381865afa1580156109e4573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610a08919061152b565b60208101518151919250610a1b916115b8565b6001600160801b031691505090565b6000828152600160205260408120610a429083610e3b565b9392505050565b6000918252602082815260408084206001600160a01b0393909316845291905290205460ff1690565b336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614610aea5760405162461bcd60e51b815260206004820152601f60248201527f43616c6c6572206973206e6f7420746865207361666520636f6e747261637400604482015260640161049f565b6001600160a01b038216610b615760008111610b065747610b08565b805b6040519091506001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169082156108fc029083906000818181858888f19350505050158015610426573d6000803e3d6000fd5b60008111610bd6576040516370a0823160e01b81523060048201526001600160a01b038316906370a0823190602401602060405180830381865afa158015610bad573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610bd1919061135e565b610bd8565b805b60405163a9059cbb60e01b81526001600160a01b037f000000000000000000000000000000000000000000000000000000000000000081166004830152602482018390529192509083169063a9059cbb906044016020604051808303816000875af1158015610c4b573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061042691906114c9565b60008181526001602052604081206103fa90610e47565b600082815260208190526040902060010154610ca28133610ce1565b6104268383610e19565b60006001600160e01b03198216637965db0b60e01b14806103fa57506301ffc9a760e01b6001600160e01b03198316146103fa565b610ceb8282610a49565b61052e57610d03816001600160a01b03166014610e51565b610d0e836020610e51565b604051602001610d1f9291906115d7565b60408051601f198184030181529082905262461bcd60e51b825261049f9160040161164c565b610d4f8282610fed565b60008281526001602052604090206104269082611071565b6001600160a01b038116610db05760405162461bcd60e51b815260206004820152601060248201526f496e76616c696420737472617465677960801b604482015260640161049f565b600254604080516001600160a01b03928316815291831660208301527fe4cec16b1a7e6b7979e923da619a8b1e5fd0f0fb6e5c1cf647f350430ee61ca9910160405180910390a1600280546001600160a01b0319166001600160a01b0392909216919091179055565b610e238282611086565b600082815260016020526040902061042690826110eb565b6000610a428383611100565b60006103fa825490565b60606000610e6083600261165f565b610e6b906002611676565b67ffffffffffffffff811115610e8357610e83611377565b6040519080825280601f01601f191660200182016040528015610ead576020820181803683370190505b509050600360fc1b81600081518110610ec857610ec861138d565b60200101906001600160f81b031916908160001a905350600f60fb1b81600181518110610ef757610ef761138d565b60200101906001600160f81b031916908160001a9053506000610f1b84600261165f565b610f26906001611676565b90505b6001811115610f9e576f181899199a1a9b1b9c1cb0b131b232b360811b85600f1660108110610f5a57610f5a61138d565b1a60f81b828281518110610f7057610f7061138d565b60200101906001600160f81b031916908160001a90535060049490941c93610f9781611689565b9050610f29565b508315610a425760405162461bcd60e51b815260206004820181905260248201527f537472696e67733a20686578206c656e67746820696e73756666696369656e74604482015260640161049f565b610ff78282610a49565b61052e576000828152602081815260408083206001600160a01b03851684529091529020805460ff1916600117905561102d3390565b6001600160a01b0316816001600160a01b0316837f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a45050565b6000610a42836001600160a01b03841661112a565b6110908282610a49565b1561052e576000828152602081815260408083206001600160a01b0385168085529252808320805460ff1916905551339285917ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b9190a45050565b6000610a42836001600160a01b038416611179565b60008260000182815481106111175761111761138d565b9060005260206000200154905092915050565b6000818152600183016020526040812054611171575081546001818101845560008481526020808220909301849055845484825282860190935260409020919091556103fa565b5060006103fa565b6000818152600183016020526040812054801561126257600061119d600183611501565b85549091506000906111b190600190611501565b90508181146112165760008660000182815481106111d1576111d161138d565b90600052602060002001549050808760000184815481106111f4576111f461138d565b6000918252602080832090910192909255918252600188019052604090208390555b8554869080611227576112276116a0565b6001900381819060005260206000200160009055905585600101600086815260200190815260200160002060009055600193505050506103fa565b60009150506103fa565b60006020828403121561127e57600080fd5b81356001600160e01b031981168114610a4257600080fd5b6000602082840312156112a857600080fd5b5035919050565b80356001600160a01b03811681146112c657600080fd5b919050565b600080604083850312156112de57600080fd5b823591506112ee602084016112af565b90509250929050565b60006020828403121561130957600080fd5b610a42826112af565b6000806040838503121561132557600080fd5b50508035926020909101359150565b6000806040838503121561134757600080fd5b611350836112af565b946020939093013593505050565b60006020828403121561137057600080fd5b5051919050565b634e487b7160e01b600052604160045260246000fd5b634e487b7160e01b600052603260045260246000fd5b6001600160a01b0384168152606060208083018290528451918301829052600091908501906080840190835b818110156113f65783516001600160a01b03168352602093840193909201916001016113cf565b505083810360408501528451808252602091820192509085019060005b81811015611431578251845260209384019390920191600101611413565b5091979650505050505050565b60005b83811015611459578181015183820152602001611441565b50506000910152565b6000815180845261147a81602086016020860161143e565b601f01601f19169290920160200192915050565b60018060a01b03851681528360208201526080604082015260006114b56080830185611462565b905060ff8316606083015295945050505050565b6000602082840312156114db57600080fd5b81518015158114610a4257600080fd5b634e487b7160e01b600052601160045260246000fd5b818103818111156103fa576103fa6114eb565b80516001600160801b03811681146112c657600080fd5b6000608082840312801561153e57600080fd5b506040516080810167ffffffffffffffff8111828210171561157057634e487b7160e01b600052604160045260246000fd5b60405261157c83611514565b815261158a60208401611514565b602082015261159b60408401611514565b60408201526115ac60608401611514565b60608201529392505050565b6001600160801b0382811682821603908111156103fa576103fa6114eb565b7f416363657373436f6e74726f6c3a206163636f756e742000000000000000000081526000835161160f81601785016020880161143e565b7001034b99036b4b9b9b4b733903937b6329607d1b601791840191820152835161164081602884016020880161143e565b01602801949350505050565b602081526000610a426020830184611462565b80820281158282048414176103fa576103fa6114eb565b808201808211156103fa576103fa6114eb565b600081611698576116986114eb565b506000190190565b634e487b7160e01b600052603160045260246000fdfea264697066735822122042ac34741ed30d660a9e249e2976676ed4e668fdab490e71a4b0abda608d5b1d64736f6c634300081c003397667070c54ef182b0f5858b034beac1b6f3089aa2d3188bb1e8929f4fa9b929", + "deployedBytecode": "0x60806040526004361061010d5760003560e01c806391d1485411610095578063ca15c87311610064578063ca15c873146102f9578063d547741f14610319578063f5b541a614610339578063f9081ba21461036d578063fbfa77cf146103a157600080fd5b806391d1485414610284578063a217fddf146102a4578063a8c62e76146102b9578063bec3fa17146102d957600080fd5b806336568abe116100dc57806336568abe146101ce57806338d52e0f146101ee57806380bef06d1461023a57806384e637b51461024f5780639010d07c1461026457600080fd5b806301ffc9a714610119578063248a9ca31461014e5780632f2ff15d1461018c57806333a100ca146101ae57600080fd5b3661011457005b600080fd5b34801561012557600080fd5b5061013961013436600461126c565b6103d5565b60405190151581526020015b60405180910390f35b34801561015a57600080fd5b5061017e610169366004611296565b60009081526020819052604090206001015490565b604051908152602001610145565b34801561019857600080fd5b506101ac6101a73660046112cb565b610400565b005b3480156101ba57600080fd5b506101ac6101c93660046112f7565b61042b565b3480156101da57600080fd5b506101ac6101e93660046112cb565b6104b4565b3480156101fa57600080fd5b506102227f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b039091168152602001610145565b34801561024657600080fd5b506101ac610532565b34801561025b57600080fd5b5061017e610983565b34801561027057600080fd5b5061022261027f366004611312565b610a2a565b34801561029057600080fd5b5061013961029f3660046112cb565b610a49565b3480156102b057600080fd5b5061017e600081565b3480156102c557600080fd5b50600254610222906001600160a01b031681565b3480156102e557600080fd5b506101ac6102f4366004611334565b610a72565b34801561030557600080fd5b5061017e610314366004611296565b610c6f565b34801561032557600080fd5b506101ac6103343660046112cb565b610c86565b34801561034557600080fd5b5061017e7f97667070c54ef182b0f5858b034beac1b6f3089aa2d3188bb1e8929f4fa9b92981565b34801561037957600080fd5b506102227f000000000000000000000000000000000000000000000000000000000000000081565b3480156103ad57600080fd5b506102227f000000000000000000000000000000000000000000000000000000000000000081565b60006001600160e01b03198216635a05180f60e01b14806103fa57506103fa82610cac565b92915050565b60008281526020819052604090206001015461041c8133610ce1565b6104268383610d45565b505050565b336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146104a85760405162461bcd60e51b815260206004820152601f60248201527f43616c6c6572206973206e6f7420746865207361666520636f6e74726163740060448201526064015b60405180910390fd5b6104b181610d67565b50565b6001600160a01b03811633146105245760405162461bcd60e51b815260206004820152602f60248201527f416363657373436f6e74726f6c3a2063616e206f6e6c792072656e6f756e636560448201526e103937b632b9903337b91039b2b63360891b606482015260840161049f565b61052e8282610e19565b5050565b61055c7f97667070c54ef182b0f5858b034beac1b6f3089aa2d3188bb1e8929f4fa9b92933610a49565b6105a85760405162461bcd60e51b815260206004820152601960248201527f43616c6c6572206973206e6f7420616e206f70657261746f7200000000000000604482015260640161049f565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663b9b17f9f6040518163ffffffff1660e01b8152600401600060405180830381600087803b15801561060357600080fd5b505af1158015610617573d6000803e3d6000fd5b505050506000610625610983565b9050806000036106325750565b600254604051632fa8a91360e11b81526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000811660048301526000921690635f51522690602401602060405180830381865afa15801561069d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106c1919061135e565b905060008183106106d257816106d4565b825b90508060000361072b5760025460408051858152602081018590526001600160a01b03909216917f0ef7fed0b5b0ce7d6585da4aaebff577aad9e44fe4e8c333551e03a31e301636910160405180910390a2505050565b604080516001808252818301909252600091602080830190803683370190505090507f0000000000000000000000000000000000000000000000000000000000000000816000815181106107815761078161138d565b6001600160a01b03929092166020928302919091019091015260408051600180825281830190925260009181602001602082028036833701905050905082816000815181106107d2576107d261138d565b60209081029190910101526002546040516000916001600160a01b037f000000000000000000000000000000000000000000000000000000000000000081169263468721a7927f000000000000000000000000000000000000000000000000000000000000000092869263ae69f3cb60e01b926108579216908a908a906024016113a3565b60408051601f198184030181529181526020820180516001600160e01b03166001600160e01b03199485161790525160e086901b90921682526108a193929160009060040161148e565b6020604051808303816000875af11580156108c0573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108e491906114c9565b905080610936576002546040518581526001600160a01b03909116907f92873d130824b495f22ad10f7f14028200557770e5986714318e78c54f3aa83c906020015b60405180910390a2505050505050565b6002546001600160a01b03167fb195a67e698c5700e4f48f7b7748dda3a206ee2767ef024b61a26d7b17b2d63a8561096e818a611501565b60408051928352602083019190915201610926565b6000807f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663362bd1a36040518163ffffffff1660e01b8152600401608060405180830381865afa1580156109e4573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610a08919061152b565b60208101518151919250610a1b916115b8565b6001600160801b031691505090565b6000828152600160205260408120610a429083610e3b565b9392505050565b6000918252602082815260408084206001600160a01b0393909316845291905290205460ff1690565b336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614610aea5760405162461bcd60e51b815260206004820152601f60248201527f43616c6c6572206973206e6f7420746865207361666520636f6e747261637400604482015260640161049f565b6001600160a01b038216610b615760008111610b065747610b08565b805b6040519091506001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169082156108fc029083906000818181858888f19350505050158015610426573d6000803e3d6000fd5b60008111610bd6576040516370a0823160e01b81523060048201526001600160a01b038316906370a0823190602401602060405180830381865afa158015610bad573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610bd1919061135e565b610bd8565b805b60405163a9059cbb60e01b81526001600160a01b037f000000000000000000000000000000000000000000000000000000000000000081166004830152602482018390529192509083169063a9059cbb906044016020604051808303816000875af1158015610c4b573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061042691906114c9565b60008181526001602052604081206103fa90610e47565b600082815260208190526040902060010154610ca28133610ce1565b6104268383610e19565b60006001600160e01b03198216637965db0b60e01b14806103fa57506301ffc9a760e01b6001600160e01b03198316146103fa565b610ceb8282610a49565b61052e57610d03816001600160a01b03166014610e51565b610d0e836020610e51565b604051602001610d1f9291906115d7565b60408051601f198184030181529082905262461bcd60e51b825261049f9160040161164c565b610d4f8282610fed565b60008281526001602052604090206104269082611071565b6001600160a01b038116610db05760405162461bcd60e51b815260206004820152601060248201526f496e76616c696420737472617465677960801b604482015260640161049f565b600254604080516001600160a01b03928316815291831660208301527fe4cec16b1a7e6b7979e923da619a8b1e5fd0f0fb6e5c1cf647f350430ee61ca9910160405180910390a1600280546001600160a01b0319166001600160a01b0392909216919091179055565b610e238282611086565b600082815260016020526040902061042690826110eb565b6000610a428383611100565b60006103fa825490565b60606000610e6083600261165f565b610e6b906002611676565b67ffffffffffffffff811115610e8357610e83611377565b6040519080825280601f01601f191660200182016040528015610ead576020820181803683370190505b509050600360fc1b81600081518110610ec857610ec861138d565b60200101906001600160f81b031916908160001a905350600f60fb1b81600181518110610ef757610ef761138d565b60200101906001600160f81b031916908160001a9053506000610f1b84600261165f565b610f26906001611676565b90505b6001811115610f9e576f181899199a1a9b1b9c1cb0b131b232b360811b85600f1660108110610f5a57610f5a61138d565b1a60f81b828281518110610f7057610f7061138d565b60200101906001600160f81b031916908160001a90535060049490941c93610f9781611689565b9050610f29565b508315610a425760405162461bcd60e51b815260206004820181905260248201527f537472696e67733a20686578206c656e67746820696e73756666696369656e74604482015260640161049f565b610ff78282610a49565b61052e576000828152602081815260408083206001600160a01b03851684529091529020805460ff1916600117905561102d3390565b6001600160a01b0316816001600160a01b0316837f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a45050565b6000610a42836001600160a01b03841661112a565b6110908282610a49565b1561052e576000828152602081815260408083206001600160a01b0385168085529252808320805460ff1916905551339285917ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b9190a45050565b6000610a42836001600160a01b038416611179565b60008260000182815481106111175761111761138d565b9060005260206000200154905092915050565b6000818152600183016020526040812054611171575081546001818101845560008481526020808220909301849055845484825282860190935260409020919091556103fa565b5060006103fa565b6000818152600183016020526040812054801561126257600061119d600183611501565b85549091506000906111b190600190611501565b90508181146112165760008660000182815481106111d1576111d161138d565b90600052602060002001549050808760000184815481106111f4576111f461138d565b6000918252602080832090910192909255918252600188019052604090208390555b8554869080611227576112276116a0565b6001900381819060005260206000200160009055905585600101600086815260200190815260200160002060009055600193505050506103fa565b60009150506103fa565b60006020828403121561127e57600080fd5b81356001600160e01b031981168114610a4257600080fd5b6000602082840312156112a857600080fd5b5035919050565b80356001600160a01b03811681146112c657600080fd5b919050565b600080604083850312156112de57600080fd5b823591506112ee602084016112af565b90509250929050565b60006020828403121561130957600080fd5b610a42826112af565b6000806040838503121561132557600080fd5b50508035926020909101359150565b6000806040838503121561134757600080fd5b611350836112af565b946020939093013593505050565b60006020828403121561137057600080fd5b5051919050565b634e487b7160e01b600052604160045260246000fd5b634e487b7160e01b600052603260045260246000fd5b6001600160a01b0384168152606060208083018290528451918301829052600091908501906080840190835b818110156113f65783516001600160a01b03168352602093840193909201916001016113cf565b505083810360408501528451808252602091820192509085019060005b81811015611431578251845260209384019390920191600101611413565b5091979650505050505050565b60005b83811015611459578181015183820152602001611441565b50506000910152565b6000815180845261147a81602086016020860161143e565b601f01601f19169290920160200192915050565b60018060a01b03851681528360208201526080604082015260006114b56080830185611462565b905060ff8316606083015295945050505050565b6000602082840312156114db57600080fd5b81518015158114610a4257600080fd5b634e487b7160e01b600052601160045260246000fd5b818103818111156103fa576103fa6114eb565b80516001600160801b03811681146112c657600080fd5b6000608082840312801561153e57600080fd5b506040516080810167ffffffffffffffff8111828210171561157057634e487b7160e01b600052604160045260246000fd5b60405261157c83611514565b815261158a60208401611514565b602082015261159b60408401611514565b60408201526115ac60608401611514565b60608201529392505050565b6001600160801b0382811682821603908111156103fa576103fa6114eb565b7f416363657373436f6e74726f6c3a206163636f756e742000000000000000000081526000835161160f81601785016020880161143e565b7001034b99036b4b9b9b4b733903937b6329607d1b601791840191820152835161164081602884016020880161143e565b01602801949350505050565b602081526000610a426020830184611462565b80820281158282048414176103fa576103fa6114eb565b808201808211156103fa576103fa6114eb565b600081611698576116986114eb565b506000190190565b634e487b7160e01b600052603160045260246000fdfea264697066735822122042ac34741ed30d660a9e249e2976676ed4e668fdab490e71a4b0abda608d5b1d64736f6c634300081c0033", + "libraries": {}, + "devdoc": { + "details": "The Safe (Guardian multisig) must: 1. Deploy this module 2. Call `safe.enableModule(address(this))` to authorize it An off-chain operator (e.g. Defender Relayer) calls `fundWithdrawals()` periodically. The module: - First tries to satisfy the queue from idle vault funds - If there's still a shortfall, withdraws the exact shortfall amount from the configured strategy (up to what the strategy holds) The Safe retains full override control via `setStrategy`.", + "events": { + "RoleAdminChanged(bytes32,bytes32,bytes32)": { + "details": "Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole` `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite {RoleAdminChanged} not being emitted signaling this. _Available since v3.1._" + }, + "RoleGranted(bytes32,address,address)": { + "details": "Emitted when `account` is granted `role`. `sender` is the account that originated the contract call, an admin role bearer except when using {AccessControl-_setupRole}." + }, + "RoleRevoked(bytes32,address,address)": { + "details": "Emitted when `account` is revoked `role`. `sender` is the account that originated the contract call: - if using `revokeRole`, it is the admin role bearer - if using `renounceRole`, it is the role bearer (i.e. `account`)" + } + }, + "kind": "dev", + "methods": { + "constructor": { + "params": { + "_operator": "Address of the off-chain operator (e.g. Defender relayer).", + "_safeContract": "Address of the Gnosis Safe (Guardian multisig).", + "_strategy": "Initial strategy to pull liquidity from.", + "_vault": "Address of the OUSD/OETH vault." + } + }, + "getRoleAdmin(bytes32)": { + "details": "Returns the admin role that controls `role`. See {grantRole} and {revokeRole}. To change a role's admin, use {_setRoleAdmin}." + }, + "getRoleMember(bytes32,uint256)": { + "details": "Returns one of the accounts that have `role`. `index` must be a value between 0 and {getRoleMemberCount}, non-inclusive. Role bearers are not sorted in any particular way, and their ordering may change at any point. WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure you perform all queries on the same block. See the following https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post] for more information." + }, + "getRoleMemberCount(bytes32)": { + "details": "Returns the number of accounts that have `role`. Can be used together with {getRoleMember} to enumerate all bearers of a role." + }, + "grantRole(bytes32,address)": { + "details": "Grants `role` to `account`. If `account` had not been already granted `role`, emits a {RoleGranted} event. Requirements: - the caller must have ``role``'s admin role." + }, + "hasRole(bytes32,address)": { + "details": "Returns `true` if `account` has been granted `role`." + }, + "pendingShortfall()": { + "details": "This is a raw read of `queued - claimable`. It does NOT account for idle vault asset that `addWithdrawalQueueLiquidity()` would absorb. For a fully up-to-date figure, call `vault.addWithdrawalQueueLiquidity()` first (which is what `fundWithdrawals()` does).", + "returns": { + "shortfall": "Queue shortfall in asset units (vault asset decimals)." + } + }, + "renounceRole(bytes32,address)": { + "details": "Revokes `role` from the calling account. Roles are often managed via {grantRole} and {revokeRole}: this function's purpose is to provide a mechanism for accounts to lose their privileges if they are compromised (such as when a trusted device is misplaced). If the calling account had been revoked `role`, emits a {RoleRevoked} event. Requirements: - the caller must be `account`." + }, + "revokeRole(bytes32,address)": { + "details": "Revokes `role` from `account`. If `account` had been granted `role`, emits a {RoleRevoked} event. Requirements: - the caller must have ``role``'s admin role." + }, + "setStrategy(address)": { + "params": { + "_strategy": "New strategy address. Must not be zero." + } + }, + "supportsInterface(bytes4)": { + "details": "See {IERC165-supportsInterface}." + }, + "transferTokens(address,uint256)": { + "details": "Helps recovering any tokens accidentally sent to this module.", + "params": { + "amount": "Amount to transfer. 0 to transfer all balance.", + "token": "Token to transfer. 0x0 to transfer Native token." + } + } + }, + "title": "Auto Withdrawal Module", + "version": 1 + }, + "userdoc": { + "events": { + "InsufficientStrategyLiquidity(address,uint256,uint256)": { + "notice": "Emitted when the strategy does not hold enough funds to cover the shortfall. No withdrawal is attempted; an operator alert should fire on this event." + }, + "LiquidityWithdrawn(address,uint256,uint256)": { + "notice": "Emitted when liquidity is successfully moved from strategy to vault." + }, + "StrategyUpdated(address,address)": { + "notice": "Emitted when the strategy address is updated." + }, + "WithdrawalFailed(address,uint256)": { + "notice": "Emitted when the Safe exec call to withdrawFromStrategy fails." + } + }, + "kind": "user", + "methods": { + "asset()": { + "notice": "The vault's base asset (e.g. USDC for OUSD, WETH for OETH). Stored as an address to match IStrategy.checkBalance() signature." + }, + "fundWithdrawals()": { + "notice": "Fund the vault's withdrawal queue from the configured strategy. Called periodically by an off-chain operator (Defender Actions). Steps: 1. Ask the vault to absorb any idle asset it already holds. 2. Compute the remaining shortfall. 3. Pull up to that amount from the strategy via the Safe. This function never reverts on \"soft\" failures (strategy underfunded, Safe exec failure). It emits a descriptive event instead so off-chain monitoring can alert the team without breaking the Defender action." + }, + "pendingShortfall()": { + "notice": "The current unmet shortfall in the vault's withdrawal queue." + }, + "setStrategy(address)": { + "notice": "Change the strategy from which liquidity is pulled." + }, + "strategy()": { + "notice": "The strategy from which liquidity is pulled to fill the queue." + }, + "vault()": { + "notice": "The vault whose withdrawal queue is being funded." + } + }, + "notice": "A Gnosis Safe module that automates funding the OUSD (or OETH) vault's withdrawal queue by pulling liquidity from a configured strategy.", + "version": 1 + }, + "storageLayout": { + "storage": [ + { + "astId": 24, + "contract": "contracts/automation/AutoWithdrawalModule.sol:AutoWithdrawalModule", + "label": "_roles", + "offset": 0, + "slot": "0", + "type": "t_mapping(t_bytes32,t_struct(RoleData)19_storage)" + }, + { + "astId": 327, + "contract": "contracts/automation/AutoWithdrawalModule.sol:AutoWithdrawalModule", + "label": "_roleMembers", + "offset": 0, + "slot": "1", + "type": "t_mapping(t_bytes32,t_struct(AddressSet)2121_storage)" + }, + { + "astId": 2585, + "contract": "contracts/automation/AutoWithdrawalModule.sol:AutoWithdrawalModule", + "label": "strategy", + "offset": 0, + "slot": "2", + "type": "t_address" + } + ], + "types": { + "t_address": { + "encoding": "inplace", + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_bytes32)dyn_storage": { + "base": "t_bytes32", + "encoding": "dynamic_array", + "label": "bytes32[]", + "numberOfBytes": "32" + }, + "t_bool": { + "encoding": "inplace", + "label": "bool", + "numberOfBytes": "1" + }, + "t_bytes32": { + "encoding": "inplace", + "label": "bytes32", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_bool)": { + "encoding": "mapping", + "key": "t_address", + "label": "mapping(address => bool)", + "numberOfBytes": "32", + "value": "t_bool" + }, + "t_mapping(t_bytes32,t_struct(AddressSet)2121_storage)": { + "encoding": "mapping", + "key": "t_bytes32", + "label": "mapping(bytes32 => struct EnumerableSet.AddressSet)", + "numberOfBytes": "32", + "value": "t_struct(AddressSet)2121_storage" + }, + "t_mapping(t_bytes32,t_struct(RoleData)19_storage)": { + "encoding": "mapping", + "key": "t_bytes32", + "label": "mapping(bytes32 => struct AccessControl.RoleData)", + "numberOfBytes": "32", + "value": "t_struct(RoleData)19_storage" + }, + "t_mapping(t_bytes32,t_uint256)": { + "encoding": "mapping", + "key": "t_bytes32", + "label": "mapping(bytes32 => uint256)", + "numberOfBytes": "32", + "value": "t_uint256" + }, + "t_struct(AddressSet)2121_storage": { + "encoding": "inplace", + "label": "struct EnumerableSet.AddressSet", + "members": [ + { + "astId": 2120, + "contract": "contracts/automation/AutoWithdrawalModule.sol:AutoWithdrawalModule", + "label": "_inner", + "offset": 0, + "slot": "0", + "type": "t_struct(Set)1820_storage" + } + ], + "numberOfBytes": "64" + }, + "t_struct(RoleData)19_storage": { + "encoding": "inplace", + "label": "struct AccessControl.RoleData", + "members": [ + { + "astId": 16, + "contract": "contracts/automation/AutoWithdrawalModule.sol:AutoWithdrawalModule", + "label": "members", + "offset": 0, + "slot": "0", + "type": "t_mapping(t_address,t_bool)" + }, + { + "astId": 18, + "contract": "contracts/automation/AutoWithdrawalModule.sol:AutoWithdrawalModule", + "label": "adminRole", + "offset": 0, + "slot": "1", + "type": "t_bytes32" + } + ], + "numberOfBytes": "64" + }, + "t_struct(Set)1820_storage": { + "encoding": "inplace", + "label": "struct EnumerableSet.Set", + "members": [ + { + "astId": 1815, + "contract": "contracts/automation/AutoWithdrawalModule.sol:AutoWithdrawalModule", + "label": "_values", + "offset": 0, + "slot": "0", + "type": "t_array(t_bytes32)dyn_storage" + }, + { + "astId": 1819, + "contract": "contracts/automation/AutoWithdrawalModule.sol:AutoWithdrawalModule", + "label": "_indexes", + "offset": 0, + "slot": "1", + "type": "t_mapping(t_bytes32,t_uint256)" + } + ], + "numberOfBytes": "64" + }, + "t_uint256": { + "encoding": "inplace", + "label": "uint256", + "numberOfBytes": "32" + } + } + } +} \ No newline at end of file diff --git a/contracts/deployments/mainnet/solcInputs/cac7f19370ca72250f12b469194d7f09.json b/contracts/deployments/mainnet/solcInputs/cac7f19370ca72250f12b469194d7f09.json new file mode 100644 index 0000000000..2d95f183d1 --- /dev/null +++ b/contracts/deployments/mainnet/solcInputs/cac7f19370ca72250f12b469194d7f09.json @@ -0,0 +1,114 @@ +{ + "language": "Solidity", + "sources": { + "@openzeppelin/contracts/access/AccessControl.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (access/AccessControl.sol)\n\npragma solidity ^0.8.0;\n\nimport \"./IAccessControl.sol\";\nimport \"../utils/Context.sol\";\nimport \"../utils/Strings.sol\";\nimport \"../utils/introspection/ERC165.sol\";\n\n/**\n * @dev Contract module that allows children to implement role-based access\n * control mechanisms. This is a lightweight version that doesn't allow enumerating role\n * members except through off-chain means by accessing the contract event logs. Some\n * applications may benefit from on-chain enumerability, for those cases see\n * {AccessControlEnumerable}.\n *\n * Roles are referred to by their `bytes32` identifier. These should be exposed\n * in the external API and be unique. The best way to achieve this is by\n * using `public constant` hash digests:\n *\n * ```\n * bytes32 public constant MY_ROLE = keccak256(\"MY_ROLE\");\n * ```\n *\n * Roles can be used to represent a set of permissions. To restrict access to a\n * function call, use {hasRole}:\n *\n * ```\n * function foo() public {\n * require(hasRole(MY_ROLE, msg.sender));\n * ...\n * }\n * ```\n *\n * Roles can be granted and revoked dynamically via the {grantRole} and\n * {revokeRole} functions. Each role has an associated admin role, and only\n * accounts that have a role's admin role can call {grantRole} and {revokeRole}.\n *\n * By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means\n * that only accounts with this role will be able to grant or revoke other\n * roles. More complex role relationships can be created by using\n * {_setRoleAdmin}.\n *\n * WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to\n * grant and revoke this role. Extra precautions should be taken to secure\n * accounts that have been granted it.\n */\nabstract contract AccessControl is Context, IAccessControl, ERC165 {\n struct RoleData {\n mapping(address => bool) members;\n bytes32 adminRole;\n }\n\n mapping(bytes32 => RoleData) private _roles;\n\n bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;\n\n /**\n * @dev Modifier that checks that an account has a specific role. Reverts\n * with a standardized message including the required role.\n *\n * The format of the revert reason is given by the following regular expression:\n *\n * /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/\n *\n * _Available since v4.1._\n */\n modifier onlyRole(bytes32 role) {\n _checkRole(role, _msgSender());\n _;\n }\n\n /**\n * @dev See {IERC165-supportsInterface}.\n */\n function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {\n return interfaceId == type(IAccessControl).interfaceId || super.supportsInterface(interfaceId);\n }\n\n /**\n * @dev Returns `true` if `account` has been granted `role`.\n */\n function hasRole(bytes32 role, address account) public view override returns (bool) {\n return _roles[role].members[account];\n }\n\n /**\n * @dev Revert with a standard message if `account` is missing `role`.\n *\n * The format of the revert reason is given by the following regular expression:\n *\n * /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/\n */\n function _checkRole(bytes32 role, address account) internal view {\n if (!hasRole(role, account)) {\n revert(\n string(\n abi.encodePacked(\n \"AccessControl: account \",\n Strings.toHexString(uint160(account), 20),\n \" is missing role \",\n Strings.toHexString(uint256(role), 32)\n )\n )\n );\n }\n }\n\n /**\n * @dev Returns the admin role that controls `role`. See {grantRole} and\n * {revokeRole}.\n *\n * To change a role's admin, use {_setRoleAdmin}.\n */\n function getRoleAdmin(bytes32 role) public view override returns (bytes32) {\n return _roles[role].adminRole;\n }\n\n /**\n * @dev Grants `role` to `account`.\n *\n * If `account` had not been already granted `role`, emits a {RoleGranted}\n * event.\n *\n * Requirements:\n *\n * - the caller must have ``role``'s admin role.\n */\n function grantRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {\n _grantRole(role, account);\n }\n\n /**\n * @dev Revokes `role` from `account`.\n *\n * If `account` had been granted `role`, emits a {RoleRevoked} event.\n *\n * Requirements:\n *\n * - the caller must have ``role``'s admin role.\n */\n function revokeRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {\n _revokeRole(role, account);\n }\n\n /**\n * @dev Revokes `role` from the calling account.\n *\n * Roles are often managed via {grantRole} and {revokeRole}: this function's\n * purpose is to provide a mechanism for accounts to lose their privileges\n * if they are compromised (such as when a trusted device is misplaced).\n *\n * If the calling account had been revoked `role`, emits a {RoleRevoked}\n * event.\n *\n * Requirements:\n *\n * - the caller must be `account`.\n */\n function renounceRole(bytes32 role, address account) public virtual override {\n require(account == _msgSender(), \"AccessControl: can only renounce roles for self\");\n\n _revokeRole(role, account);\n }\n\n /**\n * @dev Grants `role` to `account`.\n *\n * If `account` had not been already granted `role`, emits a {RoleGranted}\n * event. Note that unlike {grantRole}, this function doesn't perform any\n * checks on the calling account.\n *\n * [WARNING]\n * ====\n * This function should only be called from the constructor when setting\n * up the initial roles for the system.\n *\n * Using this function in any other way is effectively circumventing the admin\n * system imposed by {AccessControl}.\n * ====\n *\n * NOTE: This function is deprecated in favor of {_grantRole}.\n */\n function _setupRole(bytes32 role, address account) internal virtual {\n _grantRole(role, account);\n }\n\n /**\n * @dev Sets `adminRole` as ``role``'s admin role.\n *\n * Emits a {RoleAdminChanged} event.\n */\n function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {\n bytes32 previousAdminRole = getRoleAdmin(role);\n _roles[role].adminRole = adminRole;\n emit RoleAdminChanged(role, previousAdminRole, adminRole);\n }\n\n /**\n * @dev Grants `role` to `account`.\n *\n * Internal function without access restriction.\n */\n function _grantRole(bytes32 role, address account) internal virtual {\n if (!hasRole(role, account)) {\n _roles[role].members[account] = true;\n emit RoleGranted(role, account, _msgSender());\n }\n }\n\n /**\n * @dev Revokes `role` from `account`.\n *\n * Internal function without access restriction.\n */\n function _revokeRole(bytes32 role, address account) internal virtual {\n if (hasRole(role, account)) {\n _roles[role].members[account] = false;\n emit RoleRevoked(role, account, _msgSender());\n }\n }\n}\n" + }, + "@openzeppelin/contracts/access/AccessControlEnumerable.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (access/AccessControlEnumerable.sol)\n\npragma solidity ^0.8.0;\n\nimport \"./IAccessControlEnumerable.sol\";\nimport \"./AccessControl.sol\";\nimport \"../utils/structs/EnumerableSet.sol\";\n\n/**\n * @dev Extension of {AccessControl} that allows enumerating the members of each role.\n */\nabstract contract AccessControlEnumerable is IAccessControlEnumerable, AccessControl {\n using EnumerableSet for EnumerableSet.AddressSet;\n\n mapping(bytes32 => EnumerableSet.AddressSet) private _roleMembers;\n\n /**\n * @dev See {IERC165-supportsInterface}.\n */\n function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {\n return interfaceId == type(IAccessControlEnumerable).interfaceId || super.supportsInterface(interfaceId);\n }\n\n /**\n * @dev Returns one of the accounts that have `role`. `index` must be a\n * value between 0 and {getRoleMemberCount}, non-inclusive.\n *\n * Role bearers are not sorted in any particular way, and their ordering may\n * change at any point.\n *\n * WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure\n * you perform all queries on the same block. See the following\n * https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post]\n * for more information.\n */\n function getRoleMember(bytes32 role, uint256 index) public view override returns (address) {\n return _roleMembers[role].at(index);\n }\n\n /**\n * @dev Returns the number of accounts that have `role`. Can be used\n * together with {getRoleMember} to enumerate all bearers of a role.\n */\n function getRoleMemberCount(bytes32 role) public view override returns (uint256) {\n return _roleMembers[role].length();\n }\n\n /**\n * @dev Overload {_grantRole} to track enumerable memberships\n */\n function _grantRole(bytes32 role, address account) internal virtual override {\n super._grantRole(role, account);\n _roleMembers[role].add(account);\n }\n\n /**\n * @dev Overload {_revokeRole} to track enumerable memberships\n */\n function _revokeRole(bytes32 role, address account) internal virtual override {\n super._revokeRole(role, account);\n _roleMembers[role].remove(account);\n }\n}\n" + }, + "@openzeppelin/contracts/access/IAccessControl.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (access/IAccessControl.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev External interface of AccessControl declared to support ERC165 detection.\n */\ninterface IAccessControl {\n /**\n * @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`\n *\n * `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite\n * {RoleAdminChanged} not being emitted signaling this.\n *\n * _Available since v3.1._\n */\n event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);\n\n /**\n * @dev Emitted when `account` is granted `role`.\n *\n * `sender` is the account that originated the contract call, an admin role\n * bearer except when using {AccessControl-_setupRole}.\n */\n event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);\n\n /**\n * @dev Emitted when `account` is revoked `role`.\n *\n * `sender` is the account that originated the contract call:\n * - if using `revokeRole`, it is the admin role bearer\n * - if using `renounceRole`, it is the role bearer (i.e. `account`)\n */\n event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);\n\n /**\n * @dev Returns `true` if `account` has been granted `role`.\n */\n function hasRole(bytes32 role, address account) external view returns (bool);\n\n /**\n * @dev Returns the admin role that controls `role`. See {grantRole} and\n * {revokeRole}.\n *\n * To change a role's admin, use {AccessControl-_setRoleAdmin}.\n */\n function getRoleAdmin(bytes32 role) external view returns (bytes32);\n\n /**\n * @dev Grants `role` to `account`.\n *\n * If `account` had not been already granted `role`, emits a {RoleGranted}\n * event.\n *\n * Requirements:\n *\n * - the caller must have ``role``'s admin role.\n */\n function grantRole(bytes32 role, address account) external;\n\n /**\n * @dev Revokes `role` from `account`.\n *\n * If `account` had been granted `role`, emits a {RoleRevoked} event.\n *\n * Requirements:\n *\n * - the caller must have ``role``'s admin role.\n */\n function revokeRole(bytes32 role, address account) external;\n\n /**\n * @dev Revokes `role` from the calling account.\n *\n * Roles are often managed via {grantRole} and {revokeRole}: this function's\n * purpose is to provide a mechanism for accounts to lose their privileges\n * if they are compromised (such as when a trusted device is misplaced).\n *\n * If the calling account had been granted `role`, emits a {RoleRevoked}\n * event.\n *\n * Requirements:\n *\n * - the caller must be `account`.\n */\n function renounceRole(bytes32 role, address account) external;\n}\n" + }, + "@openzeppelin/contracts/access/IAccessControlEnumerable.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (access/IAccessControlEnumerable.sol)\n\npragma solidity ^0.8.0;\n\nimport \"./IAccessControl.sol\";\n\n/**\n * @dev External interface of AccessControlEnumerable declared to support ERC165 detection.\n */\ninterface IAccessControlEnumerable is IAccessControl {\n /**\n * @dev Returns one of the accounts that have `role`. `index` must be a\n * value between 0 and {getRoleMemberCount}, non-inclusive.\n *\n * Role bearers are not sorted in any particular way, and their ordering may\n * change at any point.\n *\n * WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure\n * you perform all queries on the same block. See the following\n * https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post]\n * for more information.\n */\n function getRoleMember(bytes32 role, uint256 index) external view returns (address);\n\n /**\n * @dev Returns the number of accounts that have `role`. Can be used\n * together with {getRoleMember} to enumerate all bearers of a role.\n */\n function getRoleMemberCount(bytes32 role) external view returns (uint256);\n}\n" + }, + "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../IERC20.sol\";\n\n/**\n * @dev Interface for the optional metadata functions from the ERC20 standard.\n *\n * _Available since v4.1._\n */\ninterface IERC20Metadata is IERC20 {\n /**\n * @dev Returns the name of the token.\n */\n function name() external view returns (string memory);\n\n /**\n * @dev Returns the symbol of the token.\n */\n function symbol() external view returns (string memory);\n\n /**\n * @dev Returns the decimals places of the token.\n */\n function decimals() external view returns (uint8);\n}\n" + }, + "@openzeppelin/contracts/token/ERC20/IERC20.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (token/ERC20/IERC20.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Interface of the ERC20 standard as defined in the EIP.\n */\ninterface IERC20 {\n /**\n * @dev Returns the amount of tokens in existence.\n */\n function totalSupply() external view returns (uint256);\n\n /**\n * @dev Returns the amount of tokens owned by `account`.\n */\n function balanceOf(address account) external view returns (uint256);\n\n /**\n * @dev Moves `amount` tokens from the caller's account to `recipient`.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transfer(address recipient, uint256 amount) external returns (bool);\n\n /**\n * @dev Returns the remaining number of tokens that `spender` will be\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\n * zero by default.\n *\n * This value changes when {approve} or {transferFrom} are called.\n */\n function allowance(address owner, address spender) external view returns (uint256);\n\n /**\n * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\n * that someone may use both the old and the new allowance by unfortunate\n * transaction ordering. One possible solution to mitigate this race\n * condition is to first reduce the spender's allowance to 0 and set the\n * desired value afterwards:\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\n *\n * Emits an {Approval} event.\n */\n function approve(address spender, uint256 amount) external returns (bool);\n\n /**\n * @dev Moves `amount` tokens from `sender` to `recipient` using the\n * allowance mechanism. `amount` is then deducted from the caller's\n * allowance.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transferFrom(\n address sender,\n address recipient,\n uint256 amount\n ) external returns (bool);\n\n /**\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\n * another (`to`).\n *\n * Note that `value` may be zero.\n */\n event Transfer(address indexed from, address indexed to, uint256 value);\n\n /**\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\n * a call to {approve}. `value` is the new allowance.\n */\n event Approval(address indexed owner, address indexed spender, uint256 value);\n}\n" + }, + "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (token/ERC20/utils/SafeERC20.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../IERC20.sol\";\nimport \"../../../utils/Address.sol\";\n\n/**\n * @title SafeERC20\n * @dev Wrappers around ERC20 operations that throw on failure (when the token\n * contract returns false). Tokens that return no value (and instead revert or\n * throw on failure) are also supported, non-reverting calls are assumed to be\n * successful.\n * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,\n * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.\n */\nlibrary SafeERC20 {\n using Address for address;\n\n function safeTransfer(\n IERC20 token,\n address to,\n uint256 value\n ) internal {\n _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));\n }\n\n function safeTransferFrom(\n IERC20 token,\n address from,\n address to,\n uint256 value\n ) internal {\n _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));\n }\n\n /**\n * @dev Deprecated. This function has issues similar to the ones found in\n * {IERC20-approve}, and its usage is discouraged.\n *\n * Whenever possible, use {safeIncreaseAllowance} and\n * {safeDecreaseAllowance} instead.\n */\n function safeApprove(\n IERC20 token,\n address spender,\n uint256 value\n ) internal {\n // safeApprove should only be called when setting an initial allowance,\n // or when resetting it to zero. To increase and decrease it, use\n // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'\n require(\n (value == 0) || (token.allowance(address(this), spender) == 0),\n \"SafeERC20: approve from non-zero to non-zero allowance\"\n );\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));\n }\n\n function safeIncreaseAllowance(\n IERC20 token,\n address spender,\n uint256 value\n ) internal {\n uint256 newAllowance = token.allowance(address(this), spender) + value;\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));\n }\n\n function safeDecreaseAllowance(\n IERC20 token,\n address spender,\n uint256 value\n ) internal {\n unchecked {\n uint256 oldAllowance = token.allowance(address(this), spender);\n require(oldAllowance >= value, \"SafeERC20: decreased allowance below zero\");\n uint256 newAllowance = oldAllowance - value;\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));\n }\n }\n\n /**\n * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement\n * on the return value: the return value is optional (but if data is returned, it must not be false).\n * @param token The token targeted by the call.\n * @param data The call data (encoded using abi.encode or one of its variants).\n */\n function _callOptionalReturn(IERC20 token, bytes memory data) private {\n // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since\n // we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that\n // the target address contains contract code and also asserts for success in the low-level call.\n\n bytes memory returndata = address(token).functionCall(data, \"SafeERC20: low-level call failed\");\n if (returndata.length > 0) {\n // Return data is optional\n require(abi.decode(returndata, (bool)), \"SafeERC20: ERC20 operation did not succeed\");\n }\n }\n}\n" + }, + "@openzeppelin/contracts/utils/Address.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (utils/Address.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Collection of functions related to the address type\n */\nlibrary Address {\n /**\n * @dev Returns true if `account` is a contract.\n *\n * [IMPORTANT]\n * ====\n * It is unsafe to assume that an address for which this function returns\n * false is an externally-owned account (EOA) and not a contract.\n *\n * Among others, `isContract` will return false for the following\n * types of addresses:\n *\n * - an externally-owned account\n * - a contract in construction\n * - an address where a contract will be created\n * - an address where a contract lived, but was destroyed\n * ====\n */\n function isContract(address account) internal view returns (bool) {\n // This method relies on extcodesize, which returns 0 for contracts in\n // construction, since the code is only stored at the end of the\n // constructor execution.\n\n uint256 size;\n assembly {\n size := extcodesize(account)\n }\n return size > 0;\n }\n\n /**\n * @dev Replacement for Solidity's `transfer`: sends `amount` wei to\n * `recipient`, forwarding all available gas and reverting on errors.\n *\n * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost\n * of certain opcodes, possibly making contracts go over the 2300 gas limit\n * imposed by `transfer`, making them unable to receive funds via\n * `transfer`. {sendValue} removes this limitation.\n *\n * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].\n *\n * IMPORTANT: because control is transferred to `recipient`, care must be\n * taken to not create reentrancy vulnerabilities. Consider using\n * {ReentrancyGuard} or the\n * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].\n */\n function sendValue(address payable recipient, uint256 amount) internal {\n require(address(this).balance >= amount, \"Address: insufficient balance\");\n\n (bool success, ) = recipient.call{value: amount}(\"\");\n require(success, \"Address: unable to send value, recipient may have reverted\");\n }\n\n /**\n * @dev Performs a Solidity function call using a low level `call`. A\n * plain `call` is an unsafe replacement for a function call: use this\n * function instead.\n *\n * If `target` reverts with a revert reason, it is bubbled up by this\n * function (like regular Solidity function calls).\n *\n * Returns the raw returned data. To convert to the expected return value,\n * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].\n *\n * Requirements:\n *\n * - `target` must be a contract.\n * - calling `target` with `data` must not revert.\n *\n * _Available since v3.1._\n */\n function functionCall(address target, bytes memory data) internal returns (bytes memory) {\n return functionCall(target, data, \"Address: low-level call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with\n * `errorMessage` as a fallback revert reason when `target` reverts.\n *\n * _Available since v3.1._\n */\n function functionCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal returns (bytes memory) {\n return functionCallWithValue(target, data, 0, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but also transferring `value` wei to `target`.\n *\n * Requirements:\n *\n * - the calling contract must have an ETH balance of at least `value`.\n * - the called Solidity function must be `payable`.\n *\n * _Available since v3.1._\n */\n function functionCallWithValue(\n address target,\n bytes memory data,\n uint256 value\n ) internal returns (bytes memory) {\n return functionCallWithValue(target, data, value, \"Address: low-level call with value failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but\n * with `errorMessage` as a fallback revert reason when `target` reverts.\n *\n * _Available since v3.1._\n */\n function functionCallWithValue(\n address target,\n bytes memory data,\n uint256 value,\n string memory errorMessage\n ) internal returns (bytes memory) {\n require(address(this).balance >= value, \"Address: insufficient balance for call\");\n require(isContract(target), \"Address: call to non-contract\");\n\n (bool success, bytes memory returndata) = target.call{value: value}(data);\n return verifyCallResult(success, returndata, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but performing a static call.\n *\n * _Available since v3.3._\n */\n function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {\n return functionStaticCall(target, data, \"Address: low-level static call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\n * but performing a static call.\n *\n * _Available since v3.3._\n */\n function functionStaticCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal view returns (bytes memory) {\n require(isContract(target), \"Address: static call to non-contract\");\n\n (bool success, bytes memory returndata) = target.staticcall(data);\n return verifyCallResult(success, returndata, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but performing a delegate call.\n *\n * _Available since v3.4._\n */\n function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {\n return functionDelegateCall(target, data, \"Address: low-level delegate call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\n * but performing a delegate call.\n *\n * _Available since v3.4._\n */\n function functionDelegateCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal returns (bytes memory) {\n require(isContract(target), \"Address: delegate call to non-contract\");\n\n (bool success, bytes memory returndata) = target.delegatecall(data);\n return verifyCallResult(success, returndata, errorMessage);\n }\n\n /**\n * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the\n * revert reason using the provided one.\n *\n * _Available since v4.3._\n */\n function verifyCallResult(\n bool success,\n bytes memory returndata,\n string memory errorMessage\n ) internal pure returns (bytes memory) {\n if (success) {\n return returndata;\n } else {\n // Look for revert reason and bubble it up if present\n if (returndata.length > 0) {\n // The easiest way to bubble the revert reason is using memory via assembly\n\n assembly {\n let returndata_size := mload(returndata)\n revert(add(32, returndata), returndata_size)\n }\n } else {\n revert(errorMessage);\n }\n }\n }\n}\n" + }, + "@openzeppelin/contracts/utils/Context.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Provides information about the current execution context, including the\n * sender of the transaction and its data. While these are generally available\n * via msg.sender and msg.data, they should not be accessed in such a direct\n * manner, since when dealing with meta-transactions the account sending and\n * paying for execution may not be the actual sender (as far as an application\n * is concerned).\n *\n * This contract is only required for intermediate, library-like contracts.\n */\nabstract contract Context {\n function _msgSender() internal view virtual returns (address) {\n return msg.sender;\n }\n\n function _msgData() internal view virtual returns (bytes calldata) {\n return msg.data;\n }\n}\n" + }, + "@openzeppelin/contracts/utils/introspection/ERC165.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol)\n\npragma solidity ^0.8.0;\n\nimport \"./IERC165.sol\";\n\n/**\n * @dev Implementation of the {IERC165} interface.\n *\n * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check\n * for the additional interface id that will be supported. For example:\n *\n * ```solidity\n * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {\n * return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);\n * }\n * ```\n *\n * Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation.\n */\nabstract contract ERC165 is IERC165 {\n /**\n * @dev See {IERC165-supportsInterface}.\n */\n function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {\n return interfaceId == type(IERC165).interfaceId;\n }\n}\n" + }, + "@openzeppelin/contracts/utils/introspection/IERC165.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Interface of the ERC165 standard, as defined in the\n * https://eips.ethereum.org/EIPS/eip-165[EIP].\n *\n * Implementers can declare support of contract interfaces, which can then be\n * queried by others ({ERC165Checker}).\n *\n * For an implementation, see {ERC165}.\n */\ninterface IERC165 {\n /**\n * @dev Returns true if this contract implements the interface defined by\n * `interfaceId`. See the corresponding\n * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]\n * to learn more about how these ids are created.\n *\n * This function call must use less than 30 000 gas.\n */\n function supportsInterface(bytes4 interfaceId) external view returns (bool);\n}\n" + }, + "@openzeppelin/contracts/utils/math/SafeCast.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (utils/math/SafeCast.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Wrappers over Solidity's uintXX/intXX casting operators with added overflow\n * checks.\n *\n * Downcasting from uint256/int256 in Solidity does not revert on overflow. This can\n * easily result in undesired exploitation or bugs, since developers usually\n * assume that overflows raise errors. `SafeCast` restores this intuition by\n * reverting the transaction when such an operation overflows.\n *\n * Using this library instead of the unchecked operations eliminates an entire\n * class of bugs, so it's recommended to use it always.\n *\n * Can be combined with {SafeMath} and {SignedSafeMath} to extend it to smaller types, by performing\n * all math on `uint256` and `int256` and then downcasting.\n */\nlibrary SafeCast {\n /**\n * @dev Returns the downcasted uint224 from uint256, reverting on\n * overflow (when the input is greater than largest uint224).\n *\n * Counterpart to Solidity's `uint224` operator.\n *\n * Requirements:\n *\n * - input must fit into 224 bits\n */\n function toUint224(uint256 value) internal pure returns (uint224) {\n require(value <= type(uint224).max, \"SafeCast: value doesn't fit in 224 bits\");\n return uint224(value);\n }\n\n /**\n * @dev Returns the downcasted uint128 from uint256, reverting on\n * overflow (when the input is greater than largest uint128).\n *\n * Counterpart to Solidity's `uint128` operator.\n *\n * Requirements:\n *\n * - input must fit into 128 bits\n */\n function toUint128(uint256 value) internal pure returns (uint128) {\n require(value <= type(uint128).max, \"SafeCast: value doesn't fit in 128 bits\");\n return uint128(value);\n }\n\n /**\n * @dev Returns the downcasted uint96 from uint256, reverting on\n * overflow (when the input is greater than largest uint96).\n *\n * Counterpart to Solidity's `uint96` operator.\n *\n * Requirements:\n *\n * - input must fit into 96 bits\n */\n function toUint96(uint256 value) internal pure returns (uint96) {\n require(value <= type(uint96).max, \"SafeCast: value doesn't fit in 96 bits\");\n return uint96(value);\n }\n\n /**\n * @dev Returns the downcasted uint64 from uint256, reverting on\n * overflow (when the input is greater than largest uint64).\n *\n * Counterpart to Solidity's `uint64` operator.\n *\n * Requirements:\n *\n * - input must fit into 64 bits\n */\n function toUint64(uint256 value) internal pure returns (uint64) {\n require(value <= type(uint64).max, \"SafeCast: value doesn't fit in 64 bits\");\n return uint64(value);\n }\n\n /**\n * @dev Returns the downcasted uint32 from uint256, reverting on\n * overflow (when the input is greater than largest uint32).\n *\n * Counterpart to Solidity's `uint32` operator.\n *\n * Requirements:\n *\n * - input must fit into 32 bits\n */\n function toUint32(uint256 value) internal pure returns (uint32) {\n require(value <= type(uint32).max, \"SafeCast: value doesn't fit in 32 bits\");\n return uint32(value);\n }\n\n /**\n * @dev Returns the downcasted uint16 from uint256, reverting on\n * overflow (when the input is greater than largest uint16).\n *\n * Counterpart to Solidity's `uint16` operator.\n *\n * Requirements:\n *\n * - input must fit into 16 bits\n */\n function toUint16(uint256 value) internal pure returns (uint16) {\n require(value <= type(uint16).max, \"SafeCast: value doesn't fit in 16 bits\");\n return uint16(value);\n }\n\n /**\n * @dev Returns the downcasted uint8 from uint256, reverting on\n * overflow (when the input is greater than largest uint8).\n *\n * Counterpart to Solidity's `uint8` operator.\n *\n * Requirements:\n *\n * - input must fit into 8 bits.\n */\n function toUint8(uint256 value) internal pure returns (uint8) {\n require(value <= type(uint8).max, \"SafeCast: value doesn't fit in 8 bits\");\n return uint8(value);\n }\n\n /**\n * @dev Converts a signed int256 into an unsigned uint256.\n *\n * Requirements:\n *\n * - input must be greater than or equal to 0.\n */\n function toUint256(int256 value) internal pure returns (uint256) {\n require(value >= 0, \"SafeCast: value must be positive\");\n return uint256(value);\n }\n\n /**\n * @dev Returns the downcasted int128 from int256, reverting on\n * overflow (when the input is less than smallest int128 or\n * greater than largest int128).\n *\n * Counterpart to Solidity's `int128` operator.\n *\n * Requirements:\n *\n * - input must fit into 128 bits\n *\n * _Available since v3.1._\n */\n function toInt128(int256 value) internal pure returns (int128) {\n require(value >= type(int128).min && value <= type(int128).max, \"SafeCast: value doesn't fit in 128 bits\");\n return int128(value);\n }\n\n /**\n * @dev Returns the downcasted int64 from int256, reverting on\n * overflow (when the input is less than smallest int64 or\n * greater than largest int64).\n *\n * Counterpart to Solidity's `int64` operator.\n *\n * Requirements:\n *\n * - input must fit into 64 bits\n *\n * _Available since v3.1._\n */\n function toInt64(int256 value) internal pure returns (int64) {\n require(value >= type(int64).min && value <= type(int64).max, \"SafeCast: value doesn't fit in 64 bits\");\n return int64(value);\n }\n\n /**\n * @dev Returns the downcasted int32 from int256, reverting on\n * overflow (when the input is less than smallest int32 or\n * greater than largest int32).\n *\n * Counterpart to Solidity's `int32` operator.\n *\n * Requirements:\n *\n * - input must fit into 32 bits\n *\n * _Available since v3.1._\n */\n function toInt32(int256 value) internal pure returns (int32) {\n require(value >= type(int32).min && value <= type(int32).max, \"SafeCast: value doesn't fit in 32 bits\");\n return int32(value);\n }\n\n /**\n * @dev Returns the downcasted int16 from int256, reverting on\n * overflow (when the input is less than smallest int16 or\n * greater than largest int16).\n *\n * Counterpart to Solidity's `int16` operator.\n *\n * Requirements:\n *\n * - input must fit into 16 bits\n *\n * _Available since v3.1._\n */\n function toInt16(int256 value) internal pure returns (int16) {\n require(value >= type(int16).min && value <= type(int16).max, \"SafeCast: value doesn't fit in 16 bits\");\n return int16(value);\n }\n\n /**\n * @dev Returns the downcasted int8 from int256, reverting on\n * overflow (when the input is less than smallest int8 or\n * greater than largest int8).\n *\n * Counterpart to Solidity's `int8` operator.\n *\n * Requirements:\n *\n * - input must fit into 8 bits.\n *\n * _Available since v3.1._\n */\n function toInt8(int256 value) internal pure returns (int8) {\n require(value >= type(int8).min && value <= type(int8).max, \"SafeCast: value doesn't fit in 8 bits\");\n return int8(value);\n }\n\n /**\n * @dev Converts an unsigned uint256 into a signed int256.\n *\n * Requirements:\n *\n * - input must be less than or equal to maxInt256.\n */\n function toInt256(uint256 value) internal pure returns (int256) {\n // Note: Unsafe cast below is okay because `type(int256).max` is guaranteed to be positive\n require(value <= uint256(type(int256).max), \"SafeCast: value doesn't fit in an int256\");\n return int256(value);\n }\n}\n" + }, + "@openzeppelin/contracts/utils/Strings.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (utils/Strings.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev String operations.\n */\nlibrary Strings {\n bytes16 private constant _HEX_SYMBOLS = \"0123456789abcdef\";\n\n /**\n * @dev Converts a `uint256` to its ASCII `string` decimal representation.\n */\n function toString(uint256 value) internal pure returns (string memory) {\n // Inspired by OraclizeAPI's implementation - MIT licence\n // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol\n\n if (value == 0) {\n return \"0\";\n }\n uint256 temp = value;\n uint256 digits;\n while (temp != 0) {\n digits++;\n temp /= 10;\n }\n bytes memory buffer = new bytes(digits);\n while (value != 0) {\n digits -= 1;\n buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));\n value /= 10;\n }\n return string(buffer);\n }\n\n /**\n * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.\n */\n function toHexString(uint256 value) internal pure returns (string memory) {\n if (value == 0) {\n return \"0x00\";\n }\n uint256 temp = value;\n uint256 length = 0;\n while (temp != 0) {\n length++;\n temp >>= 8;\n }\n return toHexString(value, length);\n }\n\n /**\n * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.\n */\n function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {\n bytes memory buffer = new bytes(2 * length + 2);\n buffer[0] = \"0\";\n buffer[1] = \"x\";\n for (uint256 i = 2 * length + 1; i > 1; --i) {\n buffer[i] = _HEX_SYMBOLS[value & 0xf];\n value >>= 4;\n }\n require(value == 0, \"Strings: hex length insufficient\");\n return string(buffer);\n }\n}\n" + }, + "@openzeppelin/contracts/utils/structs/EnumerableSet.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (utils/structs/EnumerableSet.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Library for managing\n * https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive\n * types.\n *\n * Sets have the following properties:\n *\n * - Elements are added, removed, and checked for existence in constant time\n * (O(1)).\n * - Elements are enumerated in O(n). No guarantees are made on the ordering.\n *\n * ```\n * contract Example {\n * // Add the library methods\n * using EnumerableSet for EnumerableSet.AddressSet;\n *\n * // Declare a set state variable\n * EnumerableSet.AddressSet private mySet;\n * }\n * ```\n *\n * As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)\n * and `uint256` (`UintSet`) are supported.\n */\nlibrary EnumerableSet {\n // To implement this library for multiple types with as little code\n // repetition as possible, we write it in terms of a generic Set type with\n // bytes32 values.\n // The Set implementation uses private functions, and user-facing\n // implementations (such as AddressSet) are just wrappers around the\n // underlying Set.\n // This means that we can only create new EnumerableSets for types that fit\n // in bytes32.\n\n struct Set {\n // Storage of set values\n bytes32[] _values;\n // Position of the value in the `values` array, plus 1 because index 0\n // means a value is not in the set.\n mapping(bytes32 => uint256) _indexes;\n }\n\n /**\n * @dev Add a value to a set. O(1).\n *\n * Returns true if the value was added to the set, that is if it was not\n * already present.\n */\n function _add(Set storage set, bytes32 value) private returns (bool) {\n if (!_contains(set, value)) {\n set._values.push(value);\n // The value is stored at length-1, but we add 1 to all indexes\n // and use 0 as a sentinel value\n set._indexes[value] = set._values.length;\n return true;\n } else {\n return false;\n }\n }\n\n /**\n * @dev Removes a value from a set. O(1).\n *\n * Returns true if the value was removed from the set, that is if it was\n * present.\n */\n function _remove(Set storage set, bytes32 value) private returns (bool) {\n // We read and store the value's index to prevent multiple reads from the same storage slot\n uint256 valueIndex = set._indexes[value];\n\n if (valueIndex != 0) {\n // Equivalent to contains(set, value)\n // To delete an element from the _values array in O(1), we swap the element to delete with the last one in\n // the array, and then remove the last element (sometimes called as 'swap and pop').\n // This modifies the order of the array, as noted in {at}.\n\n uint256 toDeleteIndex = valueIndex - 1;\n uint256 lastIndex = set._values.length - 1;\n\n if (lastIndex != toDeleteIndex) {\n bytes32 lastvalue = set._values[lastIndex];\n\n // Move the last value to the index where the value to delete is\n set._values[toDeleteIndex] = lastvalue;\n // Update the index for the moved value\n set._indexes[lastvalue] = valueIndex; // Replace lastvalue's index to valueIndex\n }\n\n // Delete the slot where the moved value was stored\n set._values.pop();\n\n // Delete the index for the deleted slot\n delete set._indexes[value];\n\n return true;\n } else {\n return false;\n }\n }\n\n /**\n * @dev Returns true if the value is in the set. O(1).\n */\n function _contains(Set storage set, bytes32 value) private view returns (bool) {\n return set._indexes[value] != 0;\n }\n\n /**\n * @dev Returns the number of values on the set. O(1).\n */\n function _length(Set storage set) private view returns (uint256) {\n return set._values.length;\n }\n\n /**\n * @dev Returns the value stored at position `index` in the set. O(1).\n *\n * Note that there are no guarantees on the ordering of values inside the\n * array, and it may change when more values are added or removed.\n *\n * Requirements:\n *\n * - `index` must be strictly less than {length}.\n */\n function _at(Set storage set, uint256 index) private view returns (bytes32) {\n return set._values[index];\n }\n\n /**\n * @dev Return the entire set in an array\n *\n * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed\n * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that\n * this function has an unbounded cost, and using it as part of a state-changing function may render the function\n * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.\n */\n function _values(Set storage set) private view returns (bytes32[] memory) {\n return set._values;\n }\n\n // Bytes32Set\n\n struct Bytes32Set {\n Set _inner;\n }\n\n /**\n * @dev Add a value to a set. O(1).\n *\n * Returns true if the value was added to the set, that is if it was not\n * already present.\n */\n function add(Bytes32Set storage set, bytes32 value) internal returns (bool) {\n return _add(set._inner, value);\n }\n\n /**\n * @dev Removes a value from a set. O(1).\n *\n * Returns true if the value was removed from the set, that is if it was\n * present.\n */\n function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) {\n return _remove(set._inner, value);\n }\n\n /**\n * @dev Returns true if the value is in the set. O(1).\n */\n function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {\n return _contains(set._inner, value);\n }\n\n /**\n * @dev Returns the number of values in the set. O(1).\n */\n function length(Bytes32Set storage set) internal view returns (uint256) {\n return _length(set._inner);\n }\n\n /**\n * @dev Returns the value stored at position `index` in the set. O(1).\n *\n * Note that there are no guarantees on the ordering of values inside the\n * array, and it may change when more values are added or removed.\n *\n * Requirements:\n *\n * - `index` must be strictly less than {length}.\n */\n function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {\n return _at(set._inner, index);\n }\n\n /**\n * @dev Return the entire set in an array\n *\n * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed\n * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that\n * this function has an unbounded cost, and using it as part of a state-changing function may render the function\n * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.\n */\n function values(Bytes32Set storage set) internal view returns (bytes32[] memory) {\n return _values(set._inner);\n }\n\n // AddressSet\n\n struct AddressSet {\n Set _inner;\n }\n\n /**\n * @dev Add a value to a set. O(1).\n *\n * Returns true if the value was added to the set, that is if it was not\n * already present.\n */\n function add(AddressSet storage set, address value) internal returns (bool) {\n return _add(set._inner, bytes32(uint256(uint160(value))));\n }\n\n /**\n * @dev Removes a value from a set. O(1).\n *\n * Returns true if the value was removed from the set, that is if it was\n * present.\n */\n function remove(AddressSet storage set, address value) internal returns (bool) {\n return _remove(set._inner, bytes32(uint256(uint160(value))));\n }\n\n /**\n * @dev Returns true if the value is in the set. O(1).\n */\n function contains(AddressSet storage set, address value) internal view returns (bool) {\n return _contains(set._inner, bytes32(uint256(uint160(value))));\n }\n\n /**\n * @dev Returns the number of values in the set. O(1).\n */\n function length(AddressSet storage set) internal view returns (uint256) {\n return _length(set._inner);\n }\n\n /**\n * @dev Returns the value stored at position `index` in the set. O(1).\n *\n * Note that there are no guarantees on the ordering of values inside the\n * array, and it may change when more values are added or removed.\n *\n * Requirements:\n *\n * - `index` must be strictly less than {length}.\n */\n function at(AddressSet storage set, uint256 index) internal view returns (address) {\n return address(uint160(uint256(_at(set._inner, index))));\n }\n\n /**\n * @dev Return the entire set in an array\n *\n * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed\n * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that\n * this function has an unbounded cost, and using it as part of a state-changing function may render the function\n * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.\n */\n function values(AddressSet storage set) internal view returns (address[] memory) {\n bytes32[] memory store = _values(set._inner);\n address[] memory result;\n\n assembly {\n result := store\n }\n\n return result;\n }\n\n // UintSet\n\n struct UintSet {\n Set _inner;\n }\n\n /**\n * @dev Add a value to a set. O(1).\n *\n * Returns true if the value was added to the set, that is if it was not\n * already present.\n */\n function add(UintSet storage set, uint256 value) internal returns (bool) {\n return _add(set._inner, bytes32(value));\n }\n\n /**\n * @dev Removes a value from a set. O(1).\n *\n * Returns true if the value was removed from the set, that is if it was\n * present.\n */\n function remove(UintSet storage set, uint256 value) internal returns (bool) {\n return _remove(set._inner, bytes32(value));\n }\n\n /**\n * @dev Returns true if the value is in the set. O(1).\n */\n function contains(UintSet storage set, uint256 value) internal view returns (bool) {\n return _contains(set._inner, bytes32(value));\n }\n\n /**\n * @dev Returns the number of values on the set. O(1).\n */\n function length(UintSet storage set) internal view returns (uint256) {\n return _length(set._inner);\n }\n\n /**\n * @dev Returns the value stored at position `index` in the set. O(1).\n *\n * Note that there are no guarantees on the ordering of values inside the\n * array, and it may change when more values are added or removed.\n *\n * Requirements:\n *\n * - `index` must be strictly less than {length}.\n */\n function at(UintSet storage set, uint256 index) internal view returns (uint256) {\n return uint256(_at(set._inner, index));\n }\n\n /**\n * @dev Return the entire set in an array\n *\n * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed\n * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that\n * this function has an unbounded cost, and using it as part of a state-changing function may render the function\n * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.\n */\n function values(UintSet storage set) internal view returns (uint256[] memory) {\n bytes32[] memory store = _values(set._inner);\n uint256[] memory result;\n\n assembly {\n result := store\n }\n\n return result;\n }\n}\n" + }, + "contracts/automation/AbstractSafeModule.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { AccessControlEnumerable } from \"@openzeppelin/contracts/access/AccessControlEnumerable.sol\";\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport { ISafe } from \"../interfaces/ISafe.sol\";\n\nabstract contract AbstractSafeModule is AccessControlEnumerable {\n ISafe public immutable safeContract;\n\n bytes32 public constant OPERATOR_ROLE = keccak256(\"OPERATOR_ROLE\");\n\n modifier onlySafe() {\n require(\n msg.sender == address(safeContract),\n \"Caller is not the safe contract\"\n );\n _;\n }\n\n modifier onlyOperator() {\n require(\n hasRole(OPERATOR_ROLE, msg.sender),\n \"Caller is not an operator\"\n );\n _;\n }\n\n constructor(address _safeContract) {\n safeContract = ISafe(_safeContract);\n _grantRole(DEFAULT_ADMIN_ROLE, address(safeContract));\n _grantRole(OPERATOR_ROLE, address(safeContract));\n }\n\n /**\n * @dev Helps recovering any tokens accidentally sent to this module.\n * @param token Token to transfer. 0x0 to transfer Native token.\n * @param amount Amount to transfer. 0 to transfer all balance.\n */\n function transferTokens(address token, uint256 amount) external onlySafe {\n if (address(token) == address(0)) {\n // Move ETH\n amount = amount > 0 ? amount : address(this).balance;\n payable(address(safeContract)).transfer(amount);\n return;\n }\n\n // Move all balance if amount set to 0\n amount = amount > 0 ? amount : IERC20(token).balanceOf(address(this));\n\n // Transfer to Safe contract\n // slither-disable-next-line unchecked-transfer unused-return\n IERC20(token).transfer(address(safeContract), amount);\n }\n\n receive() external payable {\n // Accept ETH to pay for bridge fees\n }\n}\n" + }, + "contracts/automation/AutoWithdrawalModule.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { AbstractSafeModule } from \"./AbstractSafeModule.sol\";\n\nimport { IVault } from \"../interfaces/IVault.sol\";\nimport { VaultStorage } from \"../vault/VaultStorage.sol\";\nimport { IStrategy } from \"../interfaces/IStrategy.sol\";\n\n/**\n * @title Auto Withdrawal Module\n * @notice A Gnosis Safe module that automates funding the OUSD (or OETH) vault's\n * withdrawal queue by pulling liquidity from a configured strategy.\n *\n * @dev The Safe (Guardian multisig) must:\n * 1. Deploy this module\n * 2. Call `safe.enableModule(address(this))` to authorize it\n *\n * An off-chain operator (e.g. Defender Relayer) calls `fundWithdrawals()`\n * periodically. The module:\n * - First tries to satisfy the queue from idle vault funds\n * - If there's still a shortfall, withdraws the exact shortfall amount\n * from the configured strategy (up to what the strategy holds)\n *\n * The Safe retains full override control via `setStrategy`.\n */\ncontract AutoWithdrawalModule is AbstractSafeModule {\n // ───────────────────────────────────────────────────────── Immutables ──\n\n /// @notice The vault whose withdrawal queue is being funded.\n IVault public immutable vault;\n\n /// @notice The vault's base asset (e.g. USDC for OUSD, WETH for OETH).\n /// Stored as an address to match IStrategy.checkBalance() signature.\n address public immutable asset;\n\n // ────────────────────────────────────────────────────── Mutable config ──\n\n /// @notice The strategy from which liquidity is pulled to fill the queue.\n address public strategy;\n\n // ─────────────────────────────────────────────────────────── Events ──\n\n /// @notice Emitted when liquidity is successfully moved from strategy to vault.\n event LiquidityWithdrawn(\n address indexed strategy,\n uint256 amount,\n uint256 remainingShortfall\n );\n\n /// @notice Emitted when the strategy does not hold enough funds to cover the shortfall.\n /// No withdrawal is attempted; an operator alert should fire on this event.\n event InsufficientStrategyLiquidity(\n address indexed strategy,\n uint256 shortfall,\n uint256 available\n );\n\n /// @notice Emitted when the Safe exec call to withdrawFromStrategy fails.\n event WithdrawalFailed(address indexed strategy, uint256 attemptedAmount);\n\n /// @notice Emitted when the strategy address is updated.\n event StrategyUpdated(address oldStrategy, address newStrategy);\n\n // ─────────────────────────────────────────────────────── Constructor ──\n\n /**\n * @param _safeContract Address of the Gnosis Safe (Guardian multisig).\n * @param _operator Address of the off-chain operator (e.g. Defender relayer).\n * @param _vault Address of the OUSD/OETH vault.\n * @param _strategy Initial strategy to pull liquidity from.\n */\n constructor(\n address _safeContract,\n address _operator,\n address _vault,\n address _strategy\n ) AbstractSafeModule(_safeContract) {\n require(_vault != address(0), \"Invalid vault\");\n require(_strategy != address(0), \"Invalid strategy\");\n\n vault = IVault(_vault);\n asset = IVault(_vault).asset();\n\n _setStrategy(_strategy);\n\n _grantRole(OPERATOR_ROLE, _operator);\n }\n\n // ──────────────────────────────────────────────────── Core automation ──\n\n /**\n * @notice Fund the vault's withdrawal queue from the configured strategy.\n * Called periodically by an off-chain operator (Defender Actions).\n *\n * Steps:\n * 1. Ask the vault to absorb any idle asset it already holds.\n * 2. Compute the remaining shortfall.\n * 3. Pull up to that amount from the strategy via the Safe.\n *\n * This function never reverts on \"soft\" failures (strategy underfunded,\n * Safe exec failure). It emits a descriptive event instead so off-chain\n * monitoring can alert the team without breaking the Defender action.\n */\n function fundWithdrawals() external onlyOperator {\n // Step 1: Let the vault absorb any asset it already holds idle.\n // This is a permissionless call; no Safe exec needed.\n vault.addWithdrawalQueueLiquidity();\n\n // Step 2: Read the current shortfall.\n uint256 shortfall = pendingShortfall();\n\n if (shortfall == 0) {\n // Queue is fully funded — nothing to do.\n return;\n }\n\n // Step 3: Read available balance from the strategy.\n uint256 strategyBalance = IStrategy(strategy).checkBalance(asset);\n\n // Withdraw the lesser of the shortfall and what the strategy holds.\n uint256 toWithdraw = shortfall < strategyBalance\n ? shortfall\n : strategyBalance;\n\n if (toWithdraw == 0) {\n emit InsufficientStrategyLiquidity(\n strategy,\n shortfall,\n strategyBalance\n );\n return;\n }\n\n // Step 4: Execute withdrawal via the Safe (which holds the Strategist role).\n address[] memory assets = new address[](1);\n assets[0] = asset;\n uint256[] memory amounts = new uint256[](1);\n amounts[0] = toWithdraw;\n\n bool success = safeContract.execTransactionFromModule(\n address(vault),\n 0,\n abi.encodeWithSelector(\n IVault.withdrawFromStrategy.selector,\n strategy,\n assets,\n amounts\n ),\n 0 // Call (not delegatecall)\n );\n\n if (!success) {\n emit WithdrawalFailed(strategy, toWithdraw);\n return;\n }\n\n emit LiquidityWithdrawn(strategy, toWithdraw, shortfall - toWithdraw);\n }\n\n // ─────────────────────────────────────────────────────── Guardian controls ──\n\n /**\n * @notice Change the strategy from which liquidity is pulled.\n * @param _strategy New strategy address. Must not be zero.\n */\n function setStrategy(address _strategy) external onlySafe {\n _setStrategy(_strategy);\n }\n\n function _setStrategy(address _strategy) internal {\n require(_strategy != address(0), \"Invalid strategy\");\n emit StrategyUpdated(strategy, _strategy);\n strategy = _strategy;\n }\n\n // ──────────────────────────────────────────────────────── View helpers ──\n\n /**\n * @notice The current unmet shortfall in the vault's withdrawal queue.\n * @dev This is a raw read of `queued - claimable`. It does NOT account for\n * idle vault asset that `addWithdrawalQueueLiquidity()` would absorb.\n * For a fully up-to-date figure, call `vault.addWithdrawalQueueLiquidity()`\n * first (which is what `fundWithdrawals()` does).\n * @return shortfall Queue shortfall in asset units (vault asset decimals).\n */\n function pendingShortfall() public view returns (uint256 shortfall) {\n VaultStorage.WithdrawalQueueMetadata memory meta = vault\n .withdrawalQueueMetadata();\n shortfall = meta.queued - meta.claimable;\n }\n}\n" + }, + "contracts/governance/Governable.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title Base for contracts that are managed by the Origin Protocol's Governor.\n * @dev Copy of the openzeppelin Ownable.sol contract with nomenclature change\n * from owner to governor and renounce methods removed. Does not use\n * Context.sol like Ownable.sol does for simplification.\n * @author Origin Protocol Inc\n */\nabstract contract Governable {\n // Storage position of the owner and pendingOwner of the contract\n // keccak256(\"OUSD.governor\");\n bytes32 private constant governorPosition =\n 0x7bea13895fa79d2831e0a9e28edede30099005a50d652d8957cf8a607ee6ca4a;\n\n // keccak256(\"OUSD.pending.governor\");\n bytes32 private constant pendingGovernorPosition =\n 0x44c4d30b2eaad5130ad70c3ba6972730566f3e6359ab83e800d905c61b1c51db;\n\n // keccak256(\"OUSD.reentry.status\");\n bytes32 private constant reentryStatusPosition =\n 0x53bf423e48ed90e97d02ab0ebab13b2a235a6bfbe9c321847d5c175333ac4535;\n\n // See OpenZeppelin ReentrancyGuard implementation\n uint256 constant _NOT_ENTERED = 1;\n uint256 constant _ENTERED = 2;\n\n event PendingGovernorshipTransfer(\n address indexed previousGovernor,\n address indexed newGovernor\n );\n\n event GovernorshipTransferred(\n address indexed previousGovernor,\n address indexed newGovernor\n );\n\n /**\n * @notice Returns the address of the current Governor.\n */\n function governor() public view returns (address) {\n return _governor();\n }\n\n /**\n * @dev Returns the address of the current Governor.\n */\n function _governor() internal view returns (address governorOut) {\n bytes32 position = governorPosition;\n // solhint-disable-next-line no-inline-assembly\n assembly {\n governorOut := sload(position)\n }\n }\n\n /**\n * @dev Returns the address of the pending Governor.\n */\n function _pendingGovernor()\n internal\n view\n returns (address pendingGovernor)\n {\n bytes32 position = pendingGovernorPosition;\n // solhint-disable-next-line no-inline-assembly\n assembly {\n pendingGovernor := sload(position)\n }\n }\n\n /**\n * @dev Throws if called by any account other than the Governor.\n */\n modifier onlyGovernor() {\n require(isGovernor(), \"Caller is not the Governor\");\n _;\n }\n\n /**\n * @notice Returns true if the caller is the current Governor.\n */\n function isGovernor() public view returns (bool) {\n return msg.sender == _governor();\n }\n\n function _setGovernor(address newGovernor) internal {\n emit GovernorshipTransferred(_governor(), newGovernor);\n\n bytes32 position = governorPosition;\n // solhint-disable-next-line no-inline-assembly\n assembly {\n sstore(position, newGovernor)\n }\n }\n\n /**\n * @dev Prevents a contract from calling itself, directly or indirectly.\n * Calling a `nonReentrant` function from another `nonReentrant`\n * function is not supported. It is possible to prevent this from happening\n * by making the `nonReentrant` function external, and make it call a\n * `private` function that does the actual work.\n */\n modifier nonReentrant() {\n bytes32 position = reentryStatusPosition;\n uint256 _reentry_status;\n // solhint-disable-next-line no-inline-assembly\n assembly {\n _reentry_status := sload(position)\n }\n\n // On the first call to nonReentrant, _notEntered will be true\n require(_reentry_status != _ENTERED, \"Reentrant call\");\n\n // Any calls to nonReentrant after this point will fail\n // solhint-disable-next-line no-inline-assembly\n assembly {\n sstore(position, _ENTERED)\n }\n\n _;\n\n // By storing the original value once again, a refund is triggered (see\n // https://eips.ethereum.org/EIPS/eip-2200)\n // solhint-disable-next-line no-inline-assembly\n assembly {\n sstore(position, _NOT_ENTERED)\n }\n }\n\n function _setPendingGovernor(address newGovernor) internal {\n bytes32 position = pendingGovernorPosition;\n // solhint-disable-next-line no-inline-assembly\n assembly {\n sstore(position, newGovernor)\n }\n }\n\n /**\n * @notice Transfers Governance of the contract to a new account (`newGovernor`).\n * Can only be called by the current Governor. Must be claimed for this to complete\n * @param _newGovernor Address of the new Governor\n */\n function transferGovernance(address _newGovernor) external onlyGovernor {\n _setPendingGovernor(_newGovernor);\n emit PendingGovernorshipTransfer(_governor(), _newGovernor);\n }\n\n /**\n * @notice Claim Governance of the contract to a new account (`newGovernor`).\n * Can only be called by the new Governor.\n */\n function claimGovernance() external {\n require(\n msg.sender == _pendingGovernor(),\n \"Only the pending Governor can complete the claim\"\n );\n _changeGovernor(msg.sender);\n }\n\n /**\n * @dev Change Governance of the contract to a new account (`newGovernor`).\n * @param _newGovernor Address of the new Governor\n */\n function _changeGovernor(address _newGovernor) internal {\n require(_newGovernor != address(0), \"New Governor is address(0)\");\n _setGovernor(_newGovernor);\n }\n}\n" + }, + "contracts/interfaces/IBasicToken.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ninterface IBasicToken {\n function symbol() external view returns (string memory);\n\n function decimals() external view returns (uint8);\n}\n" + }, + "contracts/interfaces/ISafe.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\ninterface ISafe {\n function execTransactionFromModule(\n address,\n uint256,\n bytes memory,\n uint8\n ) external returns (bool);\n}\n" + }, + "contracts/interfaces/IStrategy.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title Platform interface to integrate with lending platform like Compound, AAVE etc.\n */\ninterface IStrategy {\n /**\n * @dev Deposit the given asset to platform\n * @param _asset asset address\n * @param _amount Amount to deposit\n */\n function deposit(address _asset, uint256 _amount) external;\n\n /**\n * @dev Deposit the entire balance of all supported assets in the Strategy\n * to the platform\n */\n function depositAll() external;\n\n /**\n * @dev Withdraw given asset from Lending platform\n */\n function withdraw(\n address _recipient,\n address _asset,\n uint256 _amount\n ) external;\n\n /**\n * @dev Liquidate all assets in strategy and return them to Vault.\n */\n function withdrawAll() external;\n\n /**\n * @dev Returns the current balance of the given asset.\n */\n function checkBalance(address _asset)\n external\n view\n returns (uint256 balance);\n\n /**\n * @dev Returns bool indicating whether strategy supports asset.\n */\n function supportsAsset(address _asset) external view returns (bool);\n\n /**\n * @dev Collect reward tokens from the Strategy.\n */\n function collectRewardTokens() external;\n\n /**\n * @dev The address array of the reward tokens for the Strategy.\n */\n function getRewardTokenAddresses() external view returns (address[] memory);\n\n function harvesterAddress() external view returns (address);\n\n function transferToken(address token, uint256 amount) external;\n\n function setRewardTokenAddresses(address[] calldata _rewardTokenAddresses)\n external;\n}\n" + }, + "contracts/interfaces/IVault.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { VaultStorage } from \"../vault/VaultStorage.sol\";\n\ninterface IVault {\n // slither-disable-start constable-states\n\n event AssetAllocated(address _asset, address _strategy, uint256 _amount);\n event StrategyApproved(address _addr);\n event StrategyRemoved(address _addr);\n event Mint(address _addr, uint256 _value);\n event Redeem(address _addr, uint256 _value);\n event CapitalPaused();\n event CapitalUnpaused();\n event DefaultStrategyUpdated(address _strategy);\n event RebasePaused();\n event RebaseUnpaused();\n event VaultBufferUpdated(uint256 _vaultBuffer);\n event AllocateThresholdUpdated(uint256 _threshold);\n event RebaseThresholdUpdated(uint256 _threshold);\n event StrategistUpdated(address _address);\n event MaxSupplyDiffChanged(uint256 maxSupplyDiff);\n event YieldDistribution(address _to, uint256 _yield, uint256 _fee);\n event TrusteeFeeBpsChanged(uint256 _basis);\n event TrusteeAddressChanged(address _address);\n event StrategyAddedToMintWhitelist(address indexed strategy);\n event StrategyRemovedFromMintWhitelist(address indexed strategy);\n event RebasePerSecondMaxChanged(uint256 rebaseRatePerSecond);\n event DripDurationChanged(uint256 dripDuration);\n event WithdrawalRequested(\n address indexed _withdrawer,\n uint256 indexed _requestId,\n uint256 _amount,\n uint256 _queued\n );\n event WithdrawalClaimed(\n address indexed _withdrawer,\n uint256 indexed _requestId,\n uint256 _amount\n );\n event WithdrawalClaimable(uint256 _claimable, uint256 _newClaimable);\n event WithdrawalClaimDelayUpdated(uint256 _newDelay);\n\n // Governable.sol\n function transferGovernance(address _newGovernor) external;\n\n function claimGovernance() external;\n\n function governor() external view returns (address);\n\n // VaultAdmin.sol\n function setVaultBuffer(uint256 _vaultBuffer) external;\n\n function vaultBuffer() external view returns (uint256);\n\n function setAutoAllocateThreshold(uint256 _threshold) external;\n\n function autoAllocateThreshold() external view returns (uint256);\n\n function setRebaseThreshold(uint256 _threshold) external;\n\n function rebaseThreshold() external view returns (uint256);\n\n function setStrategistAddr(address _address) external;\n\n function strategistAddr() external view returns (address);\n\n function setMaxSupplyDiff(uint256 _maxSupplyDiff) external;\n\n function maxSupplyDiff() external view returns (uint256);\n\n function setTrusteeAddress(address _address) external;\n\n function trusteeAddress() external view returns (address);\n\n function setTrusteeFeeBps(uint256 _basis) external;\n\n function trusteeFeeBps() external view returns (uint256);\n\n function approveStrategy(address _addr) external;\n\n function removeStrategy(address _addr) external;\n\n function setDefaultStrategy(address _strategy) external;\n\n function defaultStrategy() external view returns (address);\n\n function pauseRebase() external;\n\n function unpauseRebase() external;\n\n function rebasePaused() external view returns (bool);\n\n function pauseCapital() external;\n\n function unpauseCapital() external;\n\n function capitalPaused() external view returns (bool);\n\n function transferToken(address _asset, uint256 _amount) external;\n\n function withdrawAllFromStrategy(address _strategyAddr) external;\n\n function withdrawAllFromStrategies() external;\n\n function withdrawFromStrategy(\n address _strategyFromAddress,\n address[] calldata _assets,\n uint256[] calldata _amounts\n ) external;\n\n function depositToStrategy(\n address _strategyToAddress,\n address[] calldata _assets,\n uint256[] calldata _amounts\n ) external;\n\n // VaultCore.sol\n function mint(\n address _asset,\n uint256 _amount,\n uint256 _minimumOusdAmount\n ) external;\n\n function mintForStrategy(uint256 _amount) external;\n\n function redeem(uint256 _amount, uint256 _minimumUnitAmount) external;\n\n function burnForStrategy(uint256 _amount) external;\n\n function allocate() external;\n\n function rebase() external;\n\n function totalValue() external view returns (uint256 value);\n\n function checkBalance(address _asset) external view returns (uint256);\n\n /// @notice Deprecated: use calculateRedeemOutput\n function calculateRedeemOutputs(uint256 _amount)\n external\n view\n returns (uint256[] memory);\n\n function calculateRedeemOutput(uint256 _amount)\n external\n view\n returns (uint256);\n\n function getAssetCount() external view returns (uint256);\n\n function getAllAssets() external view returns (address[] memory);\n\n function getStrategyCount() external view returns (uint256);\n\n function getAllStrategies() external view returns (address[] memory);\n\n /// @notice Deprecated.\n function isSupportedAsset(address _asset) external view returns (bool);\n\n function dripper() external view returns (address);\n\n function asset() external view returns (address);\n\n function initialize(address) external;\n\n function addWithdrawalQueueLiquidity() external;\n\n function requestWithdrawal(uint256 _amount)\n external\n returns (uint256 requestId, uint256 queued);\n\n function claimWithdrawal(uint256 requestId)\n external\n returns (uint256 amount);\n\n function claimWithdrawals(uint256[] memory requestIds)\n external\n returns (uint256[] memory amounts, uint256 totalAmount);\n\n function withdrawalQueueMetadata()\n external\n view\n returns (VaultStorage.WithdrawalQueueMetadata memory);\n\n function withdrawalRequests(uint256 requestId)\n external\n view\n returns (VaultStorage.WithdrawalRequest memory);\n\n function addStrategyToMintWhitelist(address strategyAddr) external;\n\n function removeStrategyFromMintWhitelist(address strategyAddr) external;\n\n function isMintWhitelistedStrategy(address strategyAddr)\n external\n view\n returns (bool);\n\n function withdrawalClaimDelay() external view returns (uint256);\n\n function setWithdrawalClaimDelay(uint256 newDelay) external;\n\n function lastRebase() external view returns (uint64);\n\n function dripDuration() external view returns (uint64);\n\n function setDripDuration(uint256 _dripDuration) external;\n\n function rebasePerSecondMax() external view returns (uint64);\n\n function setRebaseRateMax(uint256 yearlyApr) external;\n\n function rebasePerSecondTarget() external view returns (uint64);\n\n function previewYield() external view returns (uint256 yield);\n\n function weth() external view returns (address);\n\n // slither-disable-end constable-states\n}\n" + }, + "contracts/mocks/MockAutoWithdrawalVault.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { VaultStorage } from \"../vault/VaultStorage.sol\";\n\ncontract MockAutoWithdrawalVault {\n address public asset;\n\n VaultStorage.WithdrawalQueueMetadata public withdrawalQueueMetadata;\n\n bool private _revertNextWithdraw;\n\n event MockedWithdrawal(address strategy, address asset, uint256 amount);\n\n constructor(address _asset) {\n asset = _asset;\n }\n\n function setWithdrawalQueueMetadata(uint256 queued, uint256 claimable)\n external\n {\n withdrawalQueueMetadata.queued = uint128(queued);\n withdrawalQueueMetadata.claimable = uint128(claimable);\n }\n\n function revertNextWithdraw() external {\n _revertNextWithdraw = true;\n }\n\n function addWithdrawalQueueLiquidity() external {\n // Do nothing\n }\n\n function withdrawFromStrategy(\n address strategy,\n address[] memory assets,\n uint256[] memory amounts\n ) external {\n if (_revertNextWithdraw) {\n _revertNextWithdraw = false;\n revert(\"Mocked withdrawal revert\");\n }\n emit MockedWithdrawal(strategy, assets[0], amounts[0]);\n }\n}\n" + }, + "contracts/mocks/MockSafeContract.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\ncontract MockSafeContract {\n function execTransactionFromModule(\n address target,\n uint256 value,\n bytes memory data,\n uint8 operation\n ) external returns (bool) {\n (bool success, ) = target.call{ value: value }(data);\n return success;\n }\n}\n" + }, + "contracts/token/OUSD.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title OUSD Token Contract\n * @dev ERC20 compatible contract for OUSD\n * @dev Implements an elastic supply\n * @author Origin Protocol Inc\n */\nimport { IVault } from \"../interfaces/IVault.sol\";\nimport { Governable } from \"../governance/Governable.sol\";\nimport { SafeCast } from \"@openzeppelin/contracts/utils/math/SafeCast.sol\";\n\ncontract OUSD is Governable {\n using SafeCast for int256;\n using SafeCast for uint256;\n\n /// @dev Event triggered when the supply changes\n /// @param totalSupply Updated token total supply\n /// @param rebasingCredits Updated token rebasing credits\n /// @param rebasingCreditsPerToken Updated token rebasing credits per token\n event TotalSupplyUpdatedHighres(\n uint256 totalSupply,\n uint256 rebasingCredits,\n uint256 rebasingCreditsPerToken\n );\n /// @dev Event triggered when an account opts in for rebasing\n /// @param account Address of the account\n event AccountRebasingEnabled(address account);\n /// @dev Event triggered when an account opts out of rebasing\n /// @param account Address of the account\n event AccountRebasingDisabled(address account);\n /// @dev Emitted when `value` tokens are moved from one account `from` to\n /// another `to`.\n /// @param from Address of the account tokens are moved from\n /// @param to Address of the account tokens are moved to\n /// @param value Amount of tokens transferred\n event Transfer(address indexed from, address indexed to, uint256 value);\n /// @dev Emitted when the allowance of a `spender` for an `owner` is set by\n /// a call to {approve}. `value` is the new allowance.\n /// @param owner Address of the owner approving allowance\n /// @param spender Address of the spender allowance is granted to\n /// @param value Amount of tokens spender can transfer\n event Approval(\n address indexed owner,\n address indexed spender,\n uint256 value\n );\n /// @dev Yield resulting from {changeSupply} that a `source` account would\n /// receive is directed to `target` account.\n /// @param source Address of the source forwarding the yield\n /// @param target Address of the target receiving the yield\n event YieldDelegated(address source, address target);\n /// @dev Yield delegation from `source` account to the `target` account is\n /// suspended.\n /// @param source Address of the source suspending yield forwarding\n /// @param target Address of the target no longer receiving yield from `source`\n /// account\n event YieldUndelegated(address source, address target);\n\n enum RebaseOptions {\n NotSet,\n StdNonRebasing,\n StdRebasing,\n YieldDelegationSource,\n YieldDelegationTarget\n }\n\n uint256[154] private _gap; // Slots to align with deployed contract\n uint256 private constant MAX_SUPPLY = type(uint128).max;\n /// @dev The amount of tokens in existence\n uint256 public totalSupply;\n mapping(address => mapping(address => uint256)) private allowances;\n /// @dev The vault with privileges to execute {mint}, {burn}\n /// and {changeSupply}\n address public vaultAddress;\n mapping(address => uint256) internal creditBalances;\n // the 2 storage variables below need trailing underscores to not name collide with public functions\n uint256 private rebasingCredits_; // Sum of all rebasing credits (creditBalances for rebasing accounts)\n uint256 private rebasingCreditsPerToken_;\n /// @dev The amount of tokens that are not rebasing - receiving yield\n uint256 public nonRebasingSupply;\n mapping(address => uint256) internal alternativeCreditsPerToken;\n /// @dev A map of all addresses and their respective RebaseOptions\n mapping(address => RebaseOptions) public rebaseState;\n mapping(address => uint256) private __deprecated_isUpgraded;\n /// @dev A map of addresses that have yields forwarded to. This is an\n /// inverse mapping of {yieldFrom}\n /// Key Account forwarding yield\n /// Value Account receiving yield\n mapping(address => address) public yieldTo;\n /// @dev A map of addresses that are receiving the yield. This is an\n /// inverse mapping of {yieldTo}\n /// Key Account receiving yield\n /// Value Account forwarding yield\n mapping(address => address) public yieldFrom;\n\n uint256 private constant RESOLUTION_INCREASE = 1e9;\n uint256[34] private __gap; // including below gap totals up to 200\n\n /// @dev Verifies that the caller is the Governor or Strategist.\n modifier onlyGovernorOrStrategist() {\n require(\n isGovernor() || msg.sender == IVault(vaultAddress).strategistAddr(),\n \"Caller is not the Strategist or Governor\"\n );\n _;\n }\n\n /// @dev Initializes the contract and sets necessary variables.\n /// @param _vaultAddress Address of the vault contract\n /// @param _initialCreditsPerToken The starting rebasing credits per token.\n function initialize(address _vaultAddress, uint256 _initialCreditsPerToken)\n external\n onlyGovernor\n {\n require(_vaultAddress != address(0), \"Zero vault address\");\n require(vaultAddress == address(0), \"Already initialized\");\n\n rebasingCreditsPerToken_ = _initialCreditsPerToken;\n vaultAddress = _vaultAddress;\n }\n\n /// @dev Returns the symbol of the token, a shorter version\n /// of the name.\n function symbol() external pure virtual returns (string memory) {\n return \"OUSD\";\n }\n\n /// @dev Returns the name of the token.\n function name() external pure virtual returns (string memory) {\n return \"Origin Dollar\";\n }\n\n /// @dev Returns the number of decimals used to get its user representation.\n function decimals() external pure virtual returns (uint8) {\n return 18;\n }\n\n /**\n * @dev Verifies that the caller is the Vault contract\n */\n modifier onlyVault() {\n require(vaultAddress == msg.sender, \"Caller is not the Vault\");\n _;\n }\n\n /**\n * @return High resolution rebasingCreditsPerToken\n */\n function rebasingCreditsPerTokenHighres() external view returns (uint256) {\n return rebasingCreditsPerToken_;\n }\n\n /**\n * @return Low resolution rebasingCreditsPerToken\n */\n function rebasingCreditsPerToken() external view returns (uint256) {\n return rebasingCreditsPerToken_ / RESOLUTION_INCREASE;\n }\n\n /**\n * @return High resolution total number of rebasing credits\n */\n function rebasingCreditsHighres() external view returns (uint256) {\n return rebasingCredits_;\n }\n\n /**\n * @return Low resolution total number of rebasing credits\n */\n function rebasingCredits() external view returns (uint256) {\n return rebasingCredits_ / RESOLUTION_INCREASE;\n }\n\n /**\n * @notice Gets the balance of the specified address.\n * @param _account Address to query the balance of.\n * @return A uint256 representing the amount of base units owned by the\n * specified address.\n */\n function balanceOf(address _account) public view returns (uint256) {\n RebaseOptions state = rebaseState[_account];\n if (state == RebaseOptions.YieldDelegationSource) {\n // Saves a slot read when transferring to or from a yield delegating source\n // since we know creditBalances equals the balance.\n return creditBalances[_account];\n }\n uint256 baseBalance = (creditBalances[_account] * 1e18) /\n _creditsPerToken(_account);\n if (state == RebaseOptions.YieldDelegationTarget) {\n // creditBalances of yieldFrom accounts equals token balances\n return baseBalance - creditBalances[yieldFrom[_account]];\n }\n return baseBalance;\n }\n\n /**\n * @notice Gets the credits balance of the specified address.\n * @dev Backwards compatible with old low res credits per token.\n * @param _account The address to query the balance of.\n * @return (uint256, uint256) Credit balance and credits per token of the\n * address\n */\n function creditsBalanceOf(address _account)\n external\n view\n returns (uint256, uint256)\n {\n uint256 cpt = _creditsPerToken(_account);\n if (cpt == 1e27) {\n // For a period before the resolution upgrade, we created all new\n // contract accounts at high resolution. Since they are not changing\n // as a result of this upgrade, we will return their true values\n return (creditBalances[_account], cpt);\n } else {\n return (\n creditBalances[_account] / RESOLUTION_INCREASE,\n cpt / RESOLUTION_INCREASE\n );\n }\n }\n\n /**\n * @notice Gets the credits balance of the specified address.\n * @param _account The address to query the balance of.\n * @return (uint256, uint256, bool) Credit balance, credits per token of the\n * address, and isUpgraded\n */\n function creditsBalanceOfHighres(address _account)\n external\n view\n returns (\n uint256,\n uint256,\n bool\n )\n {\n return (\n creditBalances[_account],\n _creditsPerToken(_account),\n true // all accounts have their resolution \"upgraded\"\n );\n }\n\n // Backwards compatible view\n function nonRebasingCreditsPerToken(address _account)\n external\n view\n returns (uint256)\n {\n return alternativeCreditsPerToken[_account];\n }\n\n /**\n * @notice Transfer tokens to a specified address.\n * @param _to the address to transfer to.\n * @param _value the amount to be transferred.\n * @return true on success.\n */\n function transfer(address _to, uint256 _value) external returns (bool) {\n require(_to != address(0), \"Transfer to zero address\");\n\n _executeTransfer(msg.sender, _to, _value);\n\n emit Transfer(msg.sender, _to, _value);\n return true;\n }\n\n /**\n * @notice Transfer tokens from one address to another.\n * @param _from The address you want to send tokens from.\n * @param _to The address you want to transfer to.\n * @param _value The amount of tokens to be transferred.\n * @return true on success.\n */\n function transferFrom(\n address _from,\n address _to,\n uint256 _value\n ) external returns (bool) {\n require(_to != address(0), \"Transfer to zero address\");\n uint256 userAllowance = allowances[_from][msg.sender];\n require(_value <= userAllowance, \"Allowance exceeded\");\n\n unchecked {\n allowances[_from][msg.sender] = userAllowance - _value;\n }\n\n _executeTransfer(_from, _to, _value);\n\n emit Transfer(_from, _to, _value);\n return true;\n }\n\n function _executeTransfer(\n address _from,\n address _to,\n uint256 _value\n ) internal {\n (\n int256 fromRebasingCreditsDiff,\n int256 fromNonRebasingSupplyDiff\n ) = _adjustAccount(_from, -_value.toInt256());\n (\n int256 toRebasingCreditsDiff,\n int256 toNonRebasingSupplyDiff\n ) = _adjustAccount(_to, _value.toInt256());\n\n _adjustGlobals(\n fromRebasingCreditsDiff + toRebasingCreditsDiff,\n fromNonRebasingSupplyDiff + toNonRebasingSupplyDiff\n );\n }\n\n function _adjustAccount(address _account, int256 _balanceChange)\n internal\n returns (int256 rebasingCreditsDiff, int256 nonRebasingSupplyDiff)\n {\n RebaseOptions state = rebaseState[_account];\n int256 currentBalance = balanceOf(_account).toInt256();\n if (currentBalance + _balanceChange < 0) {\n revert(\"Transfer amount exceeds balance\");\n }\n uint256 newBalance = (currentBalance + _balanceChange).toUint256();\n\n if (state == RebaseOptions.YieldDelegationSource) {\n address target = yieldTo[_account];\n uint256 targetOldBalance = balanceOf(target);\n uint256 targetNewCredits = _balanceToRebasingCredits(\n targetOldBalance + newBalance\n );\n rebasingCreditsDiff =\n targetNewCredits.toInt256() -\n creditBalances[target].toInt256();\n\n creditBalances[_account] = newBalance;\n creditBalances[target] = targetNewCredits;\n } else if (state == RebaseOptions.YieldDelegationTarget) {\n uint256 newCredits = _balanceToRebasingCredits(\n newBalance + creditBalances[yieldFrom[_account]]\n );\n rebasingCreditsDiff =\n newCredits.toInt256() -\n creditBalances[_account].toInt256();\n creditBalances[_account] = newCredits;\n } else {\n _autoMigrate(_account);\n uint256 alternativeCreditsPerTokenMem = alternativeCreditsPerToken[\n _account\n ];\n if (alternativeCreditsPerTokenMem > 0) {\n nonRebasingSupplyDiff = _balanceChange;\n if (alternativeCreditsPerTokenMem != 1e18) {\n alternativeCreditsPerToken[_account] = 1e18;\n }\n creditBalances[_account] = newBalance;\n } else {\n uint256 newCredits = _balanceToRebasingCredits(newBalance);\n rebasingCreditsDiff =\n newCredits.toInt256() -\n creditBalances[_account].toInt256();\n creditBalances[_account] = newCredits;\n }\n }\n }\n\n function _adjustGlobals(\n int256 _rebasingCreditsDiff,\n int256 _nonRebasingSupplyDiff\n ) internal {\n if (_rebasingCreditsDiff != 0) {\n rebasingCredits_ = (rebasingCredits_.toInt256() +\n _rebasingCreditsDiff).toUint256();\n }\n if (_nonRebasingSupplyDiff != 0) {\n nonRebasingSupply = (nonRebasingSupply.toInt256() +\n _nonRebasingSupplyDiff).toUint256();\n }\n }\n\n /**\n * @notice Function to check the amount of tokens that _owner has allowed\n * to `_spender`.\n * @param _owner The address which owns the funds.\n * @param _spender The address which will spend the funds.\n * @return The number of tokens still available for the _spender.\n */\n function allowance(address _owner, address _spender)\n external\n view\n returns (uint256)\n {\n return allowances[_owner][_spender];\n }\n\n /**\n * @notice Approve the passed address to spend the specified amount of\n * tokens on behalf of msg.sender.\n * @param _spender The address which will spend the funds.\n * @param _value The amount of tokens to be spent.\n * @return true on success.\n */\n function approve(address _spender, uint256 _value) external returns (bool) {\n allowances[msg.sender][_spender] = _value;\n emit Approval(msg.sender, _spender, _value);\n return true;\n }\n\n /**\n * @notice Creates `_amount` tokens and assigns them to `_account`,\n * increasing the total supply.\n */\n function mint(address _account, uint256 _amount) external onlyVault {\n require(_account != address(0), \"Mint to the zero address\");\n\n // Account\n (\n int256 toRebasingCreditsDiff,\n int256 toNonRebasingSupplyDiff\n ) = _adjustAccount(_account, _amount.toInt256());\n // Globals\n _adjustGlobals(toRebasingCreditsDiff, toNonRebasingSupplyDiff);\n totalSupply = totalSupply + _amount;\n\n require(totalSupply < MAX_SUPPLY, \"Max supply\");\n emit Transfer(address(0), _account, _amount);\n }\n\n /**\n * @notice Destroys `_amount` tokens from `_account`,\n * reducing the total supply.\n */\n function burn(address _account, uint256 _amount) external onlyVault {\n require(_account != address(0), \"Burn from the zero address\");\n if (_amount == 0) {\n return;\n }\n\n // Account\n (\n int256 toRebasingCreditsDiff,\n int256 toNonRebasingSupplyDiff\n ) = _adjustAccount(_account, -_amount.toInt256());\n // Globals\n _adjustGlobals(toRebasingCreditsDiff, toNonRebasingSupplyDiff);\n totalSupply = totalSupply - _amount;\n\n emit Transfer(_account, address(0), _amount);\n }\n\n /**\n * @dev Get the credits per token for an account. Returns a fixed amount\n * if the account is non-rebasing.\n * @param _account Address of the account.\n */\n function _creditsPerToken(address _account)\n internal\n view\n returns (uint256)\n {\n uint256 alternativeCreditsPerTokenMem = alternativeCreditsPerToken[\n _account\n ];\n if (alternativeCreditsPerTokenMem != 0) {\n return alternativeCreditsPerTokenMem;\n } else {\n return rebasingCreditsPerToken_;\n }\n }\n\n /**\n * @dev Auto migrate contracts to be non rebasing,\n * unless they have opted into yield.\n * @param _account Address of the account.\n */\n function _autoMigrate(address _account) internal {\n uint256 codeLen = _account.code.length;\n bool isEOA = (codeLen == 0) ||\n (codeLen == 23 && bytes3(_account.code) == 0xef0100);\n // In previous code versions, contracts would not have had their\n // rebaseState[_account] set to RebaseOptions.NonRebasing when migrated\n // therefore we check the actual accounting used on the account as well.\n if (\n (!isEOA) &&\n rebaseState[_account] == RebaseOptions.NotSet &&\n alternativeCreditsPerToken[_account] == 0\n ) {\n _rebaseOptOut(_account);\n }\n }\n\n /**\n * @dev Calculates credits from contract's global rebasingCreditsPerToken_, and\n * also balance that corresponds to those credits. The latter is important\n * when adjusting the contract's global nonRebasingSupply to circumvent any\n * possible rounding errors.\n *\n * @param _balance Balance of the account.\n */\n function _balanceToRebasingCredits(uint256 _balance)\n internal\n view\n returns (uint256 rebasingCredits)\n {\n // Rounds up, because we need to ensure that accounts always have\n // at least the balance that they should have.\n // Note this should always be used on an absolute account value,\n // not on a possibly negative diff, because then the rounding would be wrong.\n return ((_balance) * rebasingCreditsPerToken_ + 1e18 - 1) / 1e18;\n }\n\n /**\n * @notice The calling account will start receiving yield after a successful call.\n * @param _account Address of the account.\n */\n function governanceRebaseOptIn(address _account) external onlyGovernor {\n require(_account != address(0), \"Zero address not allowed\");\n _rebaseOptIn(_account);\n }\n\n /**\n * @notice The calling account will start receiving yield after a successful call.\n */\n function rebaseOptIn() external {\n _rebaseOptIn(msg.sender);\n }\n\n function _rebaseOptIn(address _account) internal {\n uint256 balance = balanceOf(_account);\n\n // prettier-ignore\n require(\n alternativeCreditsPerToken[_account] > 0 ||\n // Accounts may explicitly `rebaseOptIn` regardless of\n // accounting if they have a 0 balance.\n creditBalances[_account] == 0\n ,\n \"Account must be non-rebasing\"\n );\n RebaseOptions state = rebaseState[_account];\n // prettier-ignore\n require(\n state == RebaseOptions.StdNonRebasing ||\n state == RebaseOptions.NotSet,\n \"Only standard non-rebasing accounts can opt in\"\n );\n\n uint256 newCredits = _balanceToRebasingCredits(balance);\n\n // Account\n rebaseState[_account] = RebaseOptions.StdRebasing;\n alternativeCreditsPerToken[_account] = 0;\n creditBalances[_account] = newCredits;\n // Globals\n _adjustGlobals(newCredits.toInt256(), -balance.toInt256());\n\n emit AccountRebasingEnabled(_account);\n }\n\n /**\n * @notice The calling account will no longer receive yield\n */\n function rebaseOptOut() external {\n _rebaseOptOut(msg.sender);\n }\n\n function _rebaseOptOut(address _account) internal {\n require(\n alternativeCreditsPerToken[_account] == 0,\n \"Account must be rebasing\"\n );\n RebaseOptions state = rebaseState[_account];\n require(\n state == RebaseOptions.StdRebasing || state == RebaseOptions.NotSet,\n \"Only standard rebasing accounts can opt out\"\n );\n\n uint256 oldCredits = creditBalances[_account];\n uint256 balance = balanceOf(_account);\n\n // Account\n rebaseState[_account] = RebaseOptions.StdNonRebasing;\n alternativeCreditsPerToken[_account] = 1e18;\n creditBalances[_account] = balance;\n // Globals\n _adjustGlobals(-oldCredits.toInt256(), balance.toInt256());\n\n emit AccountRebasingDisabled(_account);\n }\n\n /**\n * @notice Distribute yield to users. This changes the exchange rate\n * between \"credits\" and OUSD tokens to change rebasing user's balances.\n * @param _newTotalSupply New total supply of OUSD.\n */\n function changeSupply(uint256 _newTotalSupply) external onlyVault {\n require(totalSupply > 0, \"Cannot increase 0 supply\");\n\n if (totalSupply == _newTotalSupply) {\n emit TotalSupplyUpdatedHighres(\n totalSupply,\n rebasingCredits_,\n rebasingCreditsPerToken_\n );\n return;\n }\n\n totalSupply = _newTotalSupply > MAX_SUPPLY\n ? MAX_SUPPLY\n : _newTotalSupply;\n\n uint256 rebasingSupply = totalSupply - nonRebasingSupply;\n // round up in the favour of the protocol\n rebasingCreditsPerToken_ =\n (rebasingCredits_ * 1e18 + rebasingSupply - 1) /\n rebasingSupply;\n\n require(rebasingCreditsPerToken_ > 0, \"Invalid change in supply\");\n\n emit TotalSupplyUpdatedHighres(\n totalSupply,\n rebasingCredits_,\n rebasingCreditsPerToken_\n );\n }\n\n /*\n * @notice Send the yield from one account to another account.\n * Each account keeps its own balances.\n */\n function delegateYield(address _from, address _to)\n external\n onlyGovernorOrStrategist\n {\n require(_from != address(0), \"Zero from address not allowed\");\n require(_to != address(0), \"Zero to address not allowed\");\n\n require(_from != _to, \"Cannot delegate to self\");\n require(\n yieldFrom[_to] == address(0) &&\n yieldTo[_to] == address(0) &&\n yieldFrom[_from] == address(0) &&\n yieldTo[_from] == address(0),\n \"Blocked by existing yield delegation\"\n );\n RebaseOptions stateFrom = rebaseState[_from];\n RebaseOptions stateTo = rebaseState[_to];\n\n require(\n stateFrom == RebaseOptions.NotSet ||\n stateFrom == RebaseOptions.StdNonRebasing ||\n stateFrom == RebaseOptions.StdRebasing,\n \"Invalid rebaseState from\"\n );\n\n require(\n stateTo == RebaseOptions.NotSet ||\n stateTo == RebaseOptions.StdNonRebasing ||\n stateTo == RebaseOptions.StdRebasing,\n \"Invalid rebaseState to\"\n );\n\n if (alternativeCreditsPerToken[_from] == 0) {\n _rebaseOptOut(_from);\n }\n if (alternativeCreditsPerToken[_to] > 0) {\n _rebaseOptIn(_to);\n }\n\n uint256 fromBalance = balanceOf(_from);\n uint256 toBalance = balanceOf(_to);\n uint256 oldToCredits = creditBalances[_to];\n uint256 newToCredits = _balanceToRebasingCredits(\n fromBalance + toBalance\n );\n\n // Set up the bidirectional links\n yieldTo[_from] = _to;\n yieldFrom[_to] = _from;\n\n // Local\n rebaseState[_from] = RebaseOptions.YieldDelegationSource;\n alternativeCreditsPerToken[_from] = 1e18;\n creditBalances[_from] = fromBalance;\n rebaseState[_to] = RebaseOptions.YieldDelegationTarget;\n creditBalances[_to] = newToCredits;\n\n // Global\n int256 creditsChange = newToCredits.toInt256() -\n oldToCredits.toInt256();\n _adjustGlobals(creditsChange, -(fromBalance).toInt256());\n emit YieldDelegated(_from, _to);\n }\n\n /*\n * @notice Stop sending the yield from one account to another account.\n */\n function undelegateYield(address _from) external onlyGovernorOrStrategist {\n // Require a delegation, which will also ensure a valid delegation\n require(yieldTo[_from] != address(0), \"Zero address not allowed\");\n\n address to = yieldTo[_from];\n uint256 fromBalance = balanceOf(_from);\n uint256 toBalance = balanceOf(to);\n uint256 oldToCredits = creditBalances[to];\n uint256 newToCredits = _balanceToRebasingCredits(toBalance);\n\n // Remove the bidirectional links\n yieldFrom[to] = address(0);\n yieldTo[_from] = address(0);\n\n // Local\n rebaseState[_from] = RebaseOptions.StdNonRebasing;\n // alternativeCreditsPerToken[from] already 1e18 from `delegateYield()`\n creditBalances[_from] = fromBalance;\n rebaseState[to] = RebaseOptions.StdRebasing;\n // alternativeCreditsPerToken[to] already 0 from `delegateYield()`\n creditBalances[to] = newToCredits;\n\n // Global\n int256 creditsChange = newToCredits.toInt256() -\n oldToCredits.toInt256();\n _adjustGlobals(creditsChange, fromBalance.toInt256());\n emit YieldUndelegated(_from, to);\n }\n}\n" + }, + "contracts/utils/Helpers.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport { IBasicToken } from \"../interfaces/IBasicToken.sol\";\n\nlibrary Helpers {\n /**\n * @notice Fetch the `symbol()` from an ERC20 token\n * @dev Grabs the `symbol()` from a contract\n * @param _token Address of the ERC20 token\n * @return string Symbol of the ERC20 token\n */\n function getSymbol(address _token) internal view returns (string memory) {\n string memory symbol = IBasicToken(_token).symbol();\n return symbol;\n }\n\n /**\n * @notice Fetch the `decimals()` from an ERC20 token\n * @dev Grabs the `decimals()` from a contract and fails if\n * the decimal value does not live within a certain range\n * @param _token Address of the ERC20 token\n * @return uint256 Decimals of the ERC20 token\n */\n function getDecimals(address _token) internal view returns (uint256) {\n uint256 decimals = IBasicToken(_token).decimals();\n require(\n decimals >= 4 && decimals <= 18,\n \"Token must have sufficient decimal places\"\n );\n\n return decimals;\n }\n}\n" + }, + "contracts/utils/Initializable.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title Base contract any contracts that need to initialize state after deployment.\n * @author Origin Protocol Inc\n */\nabstract contract Initializable {\n /**\n * @dev Indicates that the contract has been initialized.\n */\n bool private initialized;\n\n /**\n * @dev Indicates that the contract is in the process of being initialized.\n */\n bool private initializing;\n\n /**\n * @dev Modifier to protect an initializer function from being invoked twice.\n */\n modifier initializer() {\n require(\n initializing || !initialized,\n \"Initializable: contract is already initialized\"\n );\n\n bool isTopLevelCall = !initializing;\n if (isTopLevelCall) {\n initializing = true;\n initialized = true;\n }\n\n _;\n\n if (isTopLevelCall) {\n initializing = false;\n }\n }\n\n uint256[50] private ______gap;\n}\n" + }, + "contracts/vault/VaultStorage.sol": { + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @title OToken VaultStorage contract\n * @notice The VaultStorage contract defines the storage for the Vault contracts\n * @author Origin Protocol Inc\n */\n\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport { Address } from \"@openzeppelin/contracts/utils/Address.sol\";\n\nimport { IStrategy } from \"../interfaces/IStrategy.sol\";\nimport { IERC20Metadata } from \"@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol\";\nimport { Governable } from \"../governance/Governable.sol\";\nimport { OUSD } from \"../token/OUSD.sol\";\nimport { Initializable } from \"../utils/Initializable.sol\";\nimport \"../utils/Helpers.sol\";\n\nabstract contract VaultStorage is Initializable, Governable {\n using SafeERC20 for IERC20;\n\n event AssetAllocated(address _asset, address _strategy, uint256 _amount);\n event StrategyApproved(address _addr);\n event StrategyRemoved(address _addr);\n event Mint(address _addr, uint256 _value);\n event Redeem(address _addr, uint256 _value);\n event CapitalPaused();\n event CapitalUnpaused();\n event DefaultStrategyUpdated(address _strategy);\n event RebasePaused();\n event RebaseUnpaused();\n event VaultBufferUpdated(uint256 _vaultBuffer);\n event AllocateThresholdUpdated(uint256 _threshold);\n event RebaseThresholdUpdated(uint256 _threshold);\n event StrategistUpdated(address _address);\n event MaxSupplyDiffChanged(uint256 maxSupplyDiff);\n event YieldDistribution(address _to, uint256 _yield, uint256 _fee);\n event TrusteeFeeBpsChanged(uint256 _basis);\n event TrusteeAddressChanged(address _address);\n event StrategyAddedToMintWhitelist(address indexed strategy);\n event StrategyRemovedFromMintWhitelist(address indexed strategy);\n event RebasePerSecondMaxChanged(uint256 rebaseRatePerSecond);\n event DripDurationChanged(uint256 dripDuration);\n event WithdrawalRequested(\n address indexed _withdrawer,\n uint256 indexed _requestId,\n uint256 _amount,\n uint256 _queued\n );\n event WithdrawalClaimed(\n address indexed _withdrawer,\n uint256 indexed _requestId,\n uint256 _amount\n );\n event WithdrawalClaimable(uint256 _claimable, uint256 _newClaimable);\n event WithdrawalClaimDelayUpdated(uint256 _newDelay);\n\n // Since we are proxy, all state should be uninitalized.\n // Since this storage contract does not have logic directly on it\n // we should not be checking for to see if these variables can be constant.\n // slither-disable-start uninitialized-state\n // slither-disable-start constable-states\n\n /// @dev mapping of supported vault assets to their configuration\n uint256 private _deprecated_assets;\n /// @dev list of all assets supported by the vault.\n address[] private _deprecated_allAssets;\n\n // Strategies approved for use by the Vault\n struct Strategy {\n bool isSupported;\n uint256 _deprecated; // Deprecated storage slot\n }\n /// @dev mapping of strategy contracts to their configuration\n mapping(address => Strategy) public strategies;\n /// @dev list of all vault strategies\n address[] internal allStrategies;\n\n /// @notice Address of the Oracle price provider contract\n address private _deprecated_priceProvider;\n /// @notice pause rebasing if true\n bool public rebasePaused;\n /// @notice pause operations that change the OToken supply.\n /// eg mint, redeem, allocate, mint/burn for strategy\n bool public capitalPaused;\n /// @notice Redemption fee in basis points. eg 50 = 0.5%\n uint256 private _deprecated_redeemFeeBps;\n /// @notice Percentage of assets to keep in Vault to handle (most) withdrawals. 100% = 1e18.\n uint256 public vaultBuffer;\n /// @notice OToken mints over this amount automatically allocate funds. 18 decimals.\n uint256 public autoAllocateThreshold;\n /// @notice OToken mints over this amount automatically rebase. 18 decimals.\n uint256 public rebaseThreshold;\n\n /// @dev Address of the OToken token. eg OUSD or OETH.\n OUSD public oToken;\n\n /// @dev Address of the contract responsible for post rebase syncs with AMMs\n address private _deprecated_rebaseHooksAddr = address(0);\n\n /// @dev Deprecated: Address of Uniswap\n address private _deprecated_uniswapAddr = address(0);\n\n /// @notice Address of the Strategist\n address public strategistAddr = address(0);\n\n /// @notice Mapping of asset address to the Strategy that they should automatically\n // be allocated to\n uint256 private _deprecated_assetDefaultStrategies;\n\n /// @notice Max difference between total supply and total value of assets. 18 decimals.\n uint256 public maxSupplyDiff;\n\n /// @notice Trustee contract that can collect a percentage of yield\n address public trusteeAddress;\n\n /// @notice Amount of yield collected in basis points. eg 2000 = 20%\n uint256 public trusteeFeeBps;\n\n /// @dev Deprecated: Tokens that should be swapped for stablecoins\n address[] private _deprecated_swapTokens;\n\n /// @notice Metapool strategy that is allowed to mint/burn OTokens without changing collateral\n\n address private _deprecated_ousdMetaStrategy;\n\n /// @notice How much OTokens are currently minted by the strategy\n int256 private _deprecated_netOusdMintedForStrategy;\n\n /// @notice How much net total OTokens are allowed to be minted by all strategies\n uint256 private _deprecated_netOusdMintForStrategyThreshold;\n\n uint256 private _deprecated_swapConfig;\n\n // List of strategies that can mint oTokens directly\n // Used in OETHBaseVaultCore\n mapping(address => bool) public isMintWhitelistedStrategy;\n\n /// @notice Address of the Dripper contract that streams harvested rewards to the Vault\n /// @dev The vault is proxied so needs to be set with setDripper against the proxy contract.\n address private _deprecated_dripper;\n\n /// Withdrawal Queue Storage /////\n\n struct WithdrawalQueueMetadata {\n // cumulative total of all withdrawal requests included the ones that have already been claimed\n uint128 queued;\n // cumulative total of all the requests that can be claimed including the ones that have already been claimed\n uint128 claimable;\n // total of all the requests that have been claimed\n uint128 claimed;\n // index of the next withdrawal request starting at 0\n uint128 nextWithdrawalIndex;\n }\n\n /// @notice Global metadata for the withdrawal queue including:\n /// queued - cumulative total of all withdrawal requests included the ones that have already been claimed\n /// claimable - cumulative total of all the requests that can be claimed including the ones already claimed\n /// claimed - total of all the requests that have been claimed\n /// nextWithdrawalIndex - index of the next withdrawal request starting at 0\n WithdrawalQueueMetadata public withdrawalQueueMetadata;\n\n struct WithdrawalRequest {\n address withdrawer;\n bool claimed;\n uint40 timestamp; // timestamp of the withdrawal request\n // Amount of oTokens to redeem. eg OETH\n uint128 amount;\n // cumulative total of all withdrawal requests including this one.\n // this request can be claimed when this queued amount is less than or equal to the queue's claimable amount.\n uint128 queued;\n }\n\n /// @notice Mapping of withdrawal request indices to the user withdrawal request data\n mapping(uint256 => WithdrawalRequest) public withdrawalRequests;\n\n /// @notice Sets a minimum delay that is required to elapse between\n /// requesting async withdrawals and claiming the request.\n /// When set to 0 async withdrawals are disabled.\n uint256 public withdrawalClaimDelay;\n\n /// @notice Time in seconds that the vault last rebased yield.\n uint64 public lastRebase;\n\n /// @notice Automatic rebase yield calculations. In seconds. Set to 0 or 1 to disable.\n uint64 public dripDuration;\n\n /// @notice max rebase percentage per second\n /// Can be used to set maximum yield of the protocol,\n /// spreading out yield over time\n uint64 public rebasePerSecondMax;\n\n /// @notice target rebase rate limit, based on past rates and funds available.\n uint64 public rebasePerSecondTarget;\n\n uint256 internal constant MAX_REBASE = 0.02 ether;\n uint256 internal constant MAX_REBASE_PER_SECOND =\n uint256(0.05 ether) / 1 days;\n\n /// @notice Default strategy for asset\n address public defaultStrategy;\n\n // For future use\n uint256[42] private __gap;\n\n /// @notice Index of WETH asset in allAssets array\n /// Legacy OETHVaultCore code, relocated here for vault consistency.\n uint256 private _deprecated_wethAssetIndex;\n\n /// @dev Address of the asset (eg. WETH or USDC)\n address public immutable asset;\n uint8 internal immutable assetDecimals;\n\n // slither-disable-end constable-states\n // slither-disable-end uninitialized-state\n\n constructor(address _asset) {\n uint8 _decimals = IERC20Metadata(_asset).decimals();\n require(_decimals <= 18, \"invalid asset decimals\");\n asset = _asset;\n assetDecimals = _decimals;\n }\n\n /// @notice Deprecated: use `oToken()` instead.\n function oUSD() external view returns (OUSD) {\n return oToken;\n }\n}\n" + } + }, + "settings": { + "optimizer": { + "enabled": true, + "runs": 200 + }, + "evmVersion": "paris", + "outputSelection": { + "*": { + "*": [ + "abi", + "evm.bytecode", + "evm.deployedBytecode", + "evm.methodIdentifiers", + "metadata", + "devdoc", + "userdoc", + "storageLayout", + "evm.gasEstimates" + ], + "": [ + "ast" + ] + } + }, + "metadata": { + "useLiteralContent": true + } + } +} \ No newline at end of file diff --git a/contracts/package.json b/contracts/package.json index ffcbf28364..e632585c1b 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -5,11 +5,11 @@ "main": "index.js", "scripts": { "deploy": "rm -rf deployments/hardhat && npx hardhat deploy", - "deploy:mainnet": "npx hardhat deploy --network mainnet --verbose", + "deploy:mainnet": "VERIFY_CONTRACTS=true npx hardhat deploy --network mainnet --verbose", "deploy:arbitrum": "FORK_NETWORK_NAME=arbitrumOne npx hardhat deploy --network arbitrumOne --tags arbitrumOne --verbose", "deploy:holesky": "NETWORK_NAME=holesky npx hardhat deploy --network holesky --verbose", - "deploy:base": "NETWORK_NAME=base npx hardhat deploy --network base --verbose", - "deploy:sonic": "NETWORK_NAME=sonic npx hardhat deploy --network sonic --verbose", + "deploy:base": "VERIFY_CONTRACTS=true NETWORK_NAME=base npx hardhat deploy --network base --verbose", + "deploy:sonic": "VERIFY_CONTRACTS=true NETWORK_NAME=sonic npx hardhat deploy --network sonic --verbose", "deploy:plume": "NETWORK_NAME=plume npx hardhat deploy --network plume --verbose", "deploy:hoodi": "NETWORK_NAME=hoodi npx hardhat deploy --network hoodi --verbose", "abi:generate": "(rm -rf deployments/hardhat && mkdir -p dist/abi && npx hardhat deploy --export '../dist/network.json')", diff --git a/contracts/storageLayout/mainnet/AutoWithdrawalModule.json b/contracts/storageLayout/mainnet/AutoWithdrawalModule.json new file mode 100644 index 0000000000..a35e9cc9a3 --- /dev/null +++ b/contracts/storageLayout/mainnet/AutoWithdrawalModule.json @@ -0,0 +1,116 @@ +{ + "solcVersion": "0.8.28", + "storage": [ + { + "label": "_roles", + "offset": 0, + "slot": "0", + "type": "t_mapping(t_bytes32,t_struct(RoleData)19_storage)", + "contract": "AccessControl", + "src": "@openzeppelin/contracts/access/AccessControl.sol:55" + }, + { + "label": "_roleMembers", + "offset": 0, + "slot": "1", + "type": "t_mapping(t_bytes32,t_struct(AddressSet)2121_storage)", + "contract": "AccessControlEnumerable", + "src": "@openzeppelin/contracts/access/AccessControlEnumerable.sol:16" + }, + { + "label": "strategy", + "offset": 0, + "slot": "2", + "type": "t_address", + "contract": "AutoWithdrawalModule", + "src": "contracts/automation/AutoWithdrawalModule.sol:46" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_bytes32)dyn_storage": { + "label": "bytes32[]", + "numberOfBytes": "32" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_bytes32": { + "label": "bytes32", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_bool)": { + "label": "mapping(address => bool)", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes32,t_struct(AddressSet)2121_storage)": { + "label": "mapping(bytes32 => struct EnumerableSet.AddressSet)", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes32,t_struct(RoleData)19_storage)": { + "label": "mapping(bytes32 => struct AccessControl.RoleData)", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes32,t_uint256)": { + "label": "mapping(bytes32 => uint256)", + "numberOfBytes": "32" + }, + "t_struct(AddressSet)2121_storage": { + "label": "struct EnumerableSet.AddressSet", + "members": [ + { + "label": "_inner", + "type": "t_struct(Set)1820_storage", + "offset": 0, + "slot": "0" + } + ], + "numberOfBytes": "64" + }, + "t_struct(RoleData)19_storage": { + "label": "struct AccessControl.RoleData", + "members": [ + { + "label": "members", + "type": "t_mapping(t_address,t_bool)", + "offset": 0, + "slot": "0" + }, + { + "label": "adminRole", + "type": "t_bytes32", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_struct(Set)1820_storage": { + "label": "struct EnumerableSet.Set", + "members": [ + { + "label": "_values", + "type": "t_array(t_bytes32)dyn_storage", + "offset": 0, + "slot": "0" + }, + { + "label": "_indexes", + "type": "t_mapping(t_bytes32,t_uint256)", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + } + }, + "namespaces": {} +} \ No newline at end of file From a9ba0f5f486aba5c9030b3ea6ba0ac5e80104150 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment?= <55331875+clement-ux@users.noreply.github.com> Date: Tue, 24 Feb 2026 23:15:01 +0100 Subject: [PATCH 29/36] Async withdrawal + mint(uint256) cleanup (#2813) * Replace deprecated mint(address,uint256,uint256) with mint(uint256) in IVault Update the IVault interface to use the new single-param mint signature and fix all contract callers: MockNonRebasing, MockRebornMinter, AbstractOTokenZapper, OSonicZapper, and PlumeBridgeHelperModule. * Rewrite EthereumBridgeHelperModule with async withdrawal and remove Plume Remove all Plume/LayerZero bridging code and replace synchronous vault redeem with async withdrawal pattern (requestWithdrawal + claimWithdrawal). Add unwrapAndRequestWithdrawal, claimAndBridgeToBase, and claimWithdrawal. * Update BaseBridgeHelperModule to use async withdrawal Replace synchronous vault redeem with async withdrawal pattern. Remove depositWOETHAndBridgeWETH (no longer single-TX). Add depositWOETHAndRequestWithdrawal, claimWithdrawal, and claimAndBridgeWETH. * Update tests for new mint(uint256) signature and async withdrawal Replace all vault.mint(asset, amount, minAmount) calls with vault.mint(amount) across test files. Update bridge helper tests to match new async withdrawal API. Remove unused variable declarations. * Add deployment scripts for EthereumBridgeHelperModule and BaseBridgeHelperModule Deploy scripts for the updated bridge helper modules on mainnet and Base. Both enable the module on the Safe when running on a fork. * Fix Plume fork tests to use legacy mint/redeem signatures The deployed Plume vault won't be upgraded, so hardcode the old mint(address,uint256,uint256) selector in PlumeBridgeHelperModule and use a legacy ABI contract in fork tests for mint and redeem calls. * Fix review issues: approve check, skipped tests, stale mock signatures - Add missing require(success) on wOETH approve in BaseBridgeHelperModule._depositWOETH - Un-skip Base bridge helper tests and rewrite for async withdrawal flow - Remove unused _asset param from MockNonRebasing.mintOusd - Point MockRebornMinter.redeem() at requestWithdrawal instead of reverting * Fix prettier formatting in bridge-helper base fork test * Add requestWithdrawal boolean to BaseBridgeHelperModule.depositWOETH --- .../automation/BaseBridgeHelperModule.sol | 103 +++++++--- .../automation/EthereumBridgeHelperModule.sol | 189 ++++++------------ .../automation/PlumeBridgeHelperModule.sol | 6 +- contracts/contracts/interfaces/IVault.sol | 23 +-- contracts/contracts/mocks/MockNonRebasing.sol | 8 +- .../contracts/mocks/MockRebornMinter.sol | 8 +- .../contracts/zapper/AbstractOTokenZapper.sol | 2 +- contracts/contracts/zapper/OSonicZapper.sol | 2 +- .../deploy/base/044_bridge_helper_module.js | 42 ++++ .../mainnet/178_bridge_helper_module.js | 46 +++++ contracts/test/_fixture-plume.js | 11 + contracts/test/_fixture-sonic.js | 4 +- contracts/test/_fixture.js | 8 +- .../merkl-pool-booster.sonic.fork-test.js | 6 +- ...metropolis-pool-booster.sonic.fork-test.js | 6 +- .../poolBooster.sonic.fork-test.js | 6 +- .../shadow-pool-booster.sonic.fork-test.js | 6 +- .../bridge-helper.base.fork-test.js | 65 +++--- .../bridge-helper.mainnet.fork-test.js | 112 +---------- .../bridge-helper.plume.fork-test.js | 6 +- .../base/aerodrome-amo.base.fork-test.js | 24 +-- .../base/curve-amo.base.fork-test.js | 10 +- .../crosschain/cross-chain-strategy.js | 2 +- .../curve-amo-oeth.mainnet.fork-test.js | 12 +- .../curve-amo-ousd.mainnet.fork-test.js | 10 +- .../sonic/swapx-amo.sonic.fork-test.js | 6 +- .../test/token/oeth.mainnet.fork-test.js | 4 +- contracts/test/token/ousd.js | 24 +-- contracts/test/token/woeth.js | 2 +- .../test/token/woeth.mainnet.fork-test.js | 6 +- contracts/test/vault/deposit.js | 5 +- contracts/test/vault/index.js | 20 +- contracts/test/vault/oeth-vault.js | 170 ++++++---------- .../vault/oeth-vault.mainnet.fork-test.js | 17 +- .../test/vault/oethb-vault.base.fork-test.js | 6 +- .../test/vault/oethp-vault.plume.fork-test.js | 18 +- contracts/test/vault/os-vault.sonic.js | 11 +- contracts/test/vault/rebase.js | 2 +- contracts/test/vault/redeem.js | 50 +++-- .../test/vault/vault.mainnet.fork-test.js | 8 +- contracts/test/vault/vault.sonic.fork-test.js | 20 +- 41 files changed, 465 insertions(+), 621 deletions(-) create mode 100644 contracts/deploy/base/044_bridge_helper_module.js create mode 100644 contracts/deploy/mainnet/178_bridge_helper_module.js diff --git a/contracts/contracts/automation/BaseBridgeHelperModule.sol b/contracts/contracts/automation/BaseBridgeHelperModule.sol index d2a505f67b..424134ae83 100644 --- a/contracts/contracts/automation/BaseBridgeHelperModule.sol +++ b/contracts/contracts/automation/BaseBridgeHelperModule.sol @@ -73,41 +73,56 @@ contract BaseBridgeHelperModule is /** * @dev Deposits wOETH into the bridgedWOETH strategy. * @param woethAmount Amount of wOETH to deposit. - * @param redeemWithVault Whether to redeem the wOETH for WETH using the Vault. - * @return Amount of WETH received. + * @param requestWithdrawal Whether to request an async withdrawal of the + * resulting OETHb from the Vault. + * @return requestId The withdrawal request ID (0 if not requested). + * @return oethbAmount Amount of OETHb received or queued for withdrawal. */ - function depositWOETH(uint256 woethAmount, bool redeemWithVault) + function depositWOETH(uint256 woethAmount, bool requestWithdrawal) external onlyOperator - returns (uint256) + returns (uint256 requestId, uint256 oethbAmount) { - return _depositWOETH(woethAmount, redeemWithVault); + oethbAmount = _depositWOETH(woethAmount); + if (requestWithdrawal) { + requestId = _requestWithdrawal(oethbAmount); + } } /** - * @dev Deposits wOETH into the bridgedWOETH strategy and bridges it to Ethereum. - * @param woethAmount Amount of wOETH to deposit. - * @return Amount of WETH received. + * @dev Claims a previously requested withdrawal and bridges WETH to Ethereum. + * @param requestId The withdrawal request ID to claim. */ - function depositWOETHAndBridgeWETH(uint256 woethAmount) + function claimAndBridgeWETH(uint256 requestId) external + payable onlyOperator - returns (uint256) { - uint256 wethAmount = _depositWOETH(woethAmount, true); + uint256 wethAmount = _claimWithdrawal(requestId); bridgeWETHToEthereum(wethAmount); - return wethAmount; + } + + /** + * @dev Claims a previously requested withdrawal. + * @param requestId The withdrawal request ID to claim. + * @return wethAmount Amount of WETH received. + */ + function claimWithdrawal(uint256 requestId) + external + onlyOperator + returns (uint256 wethAmount) + { + return _claimWithdrawal(requestId); } /** * @dev Deposits wOETH into the bridgedWOETH strategy. * @param woethAmount Amount of wOETH to deposit. - * @param redeemWithVault Whether to redeem the wOETH for WETH using the Vault. - * @return Amount of WETH received. + * @return oethbAmount Amount of OETHb received. */ - function _depositWOETH(uint256 woethAmount, bool redeemWithVault) + function _depositWOETH(uint256 woethAmount) internal - returns (uint256) + returns (uint256 oethbAmount) { // Update oracle price bridgedWOETHStrategy.updateWOETHOraclePrice(); @@ -115,7 +130,7 @@ contract BaseBridgeHelperModule is // Rebase to account for any yields from price update vault.rebase(); - uint256 oethbAmount = oethb.balanceOf(address(safeContract)); + oethbAmount = oethb.balanceOf(address(safeContract)); // Approve bridgedWOETH strategy to move wOETH bool success = safeContract.execTransactionFromModule( @@ -128,6 +143,7 @@ contract BaseBridgeHelperModule is ), 0 // Call ); + require(success, "Failed to approve wOETH"); // Deposit to bridgedWOETH strategy success = safeContract.execTransactionFromModule( @@ -146,25 +162,53 @@ contract BaseBridgeHelperModule is // Rebase to account for any yields from price update // and backing asset change from deposit vault.rebase(); + } - if (!redeemWithVault) { - return oethbAmount; - } + /** + * @dev Requests an async withdrawal from the Vault. + * @param oethbAmount Amount of OETHb to withdraw. + * @return requestId The withdrawal request ID. + */ + function _requestWithdrawal(uint256 oethbAmount) + internal + returns (uint256 requestId) + { + // Read the next withdrawal index before requesting + // (safe because requestWithdrawal is nonReentrant) + requestId = vault.withdrawalQueueMetadata().nextWithdrawalIndex; - // Redeem for WETH using Vault - success = safeContract.execTransactionFromModule( + bool success = safeContract.execTransactionFromModule( address(vault), 0, // Value abi.encodeWithSelector( - vault.redeem.selector, - oethbAmount, + vault.requestWithdrawal.selector, oethbAmount ), 0 // Call ); - require(success, "Failed to redeem OETHb"); + require(success, "Failed to request withdrawal"); + } + + /** + * @dev Claims a previously requested withdrawal from the Vault. + * @param requestId The withdrawal request ID to claim. + * @return wethAmount Amount of WETH received. + */ + function _claimWithdrawal(uint256 requestId) + internal + returns (uint256 wethAmount) + { + wethAmount = weth.balanceOf(address(safeContract)); + + bool success = safeContract.execTransactionFromModule( + address(vault), + 0, // Value + abi.encodeWithSelector(vault.claimWithdrawal.selector, requestId), + 0 // Call + ); + require(success, "Failed to claim withdrawal"); - return oethbAmount; + wethAmount = weth.balanceOf(address(safeContract)) - wethAmount; } /** @@ -213,12 +257,7 @@ contract BaseBridgeHelperModule is success = safeContract.execTransactionFromModule( address(vault), 0, // Value - abi.encodeWithSelector( - vault.mint.selector, - address(weth), - wethAmount, - wethAmount - ), + abi.encodeWithSelector(vault.mint.selector, wethAmount), 0 // Call ); require(success, "Failed to mint OETHb"); diff --git a/contracts/contracts/automation/EthereumBridgeHelperModule.sol b/contracts/contracts/automation/EthereumBridgeHelperModule.sol index 547eff1c63..515b1dfc1a 100644 --- a/contracts/contracts/automation/EthereumBridgeHelperModule.sol +++ b/contracts/contracts/automation/EthereumBridgeHelperModule.sol @@ -1,13 +1,11 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.0; -import { AbstractLZBridgeHelperModule, AbstractSafeModule } from "./AbstractLZBridgeHelperModule.sol"; -import { AbstractCCIPBridgeHelperModule, IRouterClient } from "./AbstractCCIPBridgeHelperModule.sol"; +// solhint-disable-next-line max-line-length +import { AbstractCCIPBridgeHelperModule, AbstractSafeModule, IRouterClient } from "./AbstractCCIPBridgeHelperModule.sol"; import { AccessControlEnumerable } from "@openzeppelin/contracts/access/AccessControlEnumerable.sol"; -import { IOFT } from "@layerzerolabs/oft-evm/contracts/interfaces/IOFT.sol"; - import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { IERC4626 } from "../../lib/openzeppelin/interfaces/IERC4626.sol"; import { IWETH9 } from "../interfaces/IWETH9.sol"; @@ -15,7 +13,6 @@ import { IVault } from "../interfaces/IVault.sol"; contract EthereumBridgeHelperModule is AccessControlEnumerable, - AbstractLZBridgeHelperModule, AbstractCCIPBridgeHelperModule { IVault public constant vault = @@ -27,12 +24,6 @@ contract EthereumBridgeHelperModule is IERC4626 public constant woeth = IERC4626(0xDcEe70654261AF21C44c093C300eD3Bb97b78192); - uint32 public constant LZ_PLUME_ENDPOINT_ID = 30370; - IOFT public constant LZ_WOETH_OMNICHAIN_ADAPTER = - IOFT(0x7d1bEa5807e6af125826d56ff477745BB89972b8); - IOFT public constant LZ_ETH_OMNICHAIN_ADAPTER = - IOFT(0x77b2043768d28E9C9aB44E1aBfC95944bcE57931); - IRouterClient public constant CCIP_ROUTER = IRouterClient(0x80226fc0Ee2b096224EeAc085Bb9a8cba1146f7D); @@ -40,26 +31,6 @@ contract EthereumBridgeHelperModule is constructor(address _safeContract) AbstractSafeModule(_safeContract) {} - /** - * @dev Bridges wOETH to Plume. - * @param woethAmount Amount of wOETH to bridge. - * @param slippageBps Slippage in 10^4 basis points. - */ - function bridgeWOETHToPlume(uint256 woethAmount, uint256 slippageBps) - public - payable - onlyOperator - { - _bridgeTokenWithLz( - LZ_PLUME_ENDPOINT_ID, - woeth, - LZ_WOETH_OMNICHAIN_ADAPTER, - woethAmount, - slippageBps, - false - ); - } - /** * @dev Bridges wOETH to Base using CCIP. * @param woethAmount Amount of wOETH to bridge. @@ -77,34 +48,6 @@ contract EthereumBridgeHelperModule is ); } - /** - * @dev Bridges wETH to Plume. - * @param wethAmount Amount of wETH to bridge. - * @param slippageBps Slippage in 10^4 basis points. - */ - function bridgeWETHToPlume(uint256 wethAmount, uint256 slippageBps) - public - payable - onlyOperator - { - // Unwrap into ETH - safeContract.execTransactionFromModule( - address(weth), - 0, // Value - abi.encodeWithSelector(weth.withdraw.selector, wethAmount), - 0 // Call - ); - - _bridgeTokenWithLz( - LZ_PLUME_ENDPOINT_ID, - IERC20(address(weth)), - LZ_ETH_OMNICHAIN_ADAPTER, - wethAmount, - slippageBps, - true - ); - } - /** * @dev Bridges wETH to Base using CCIP. * @param wethAmount Amount of wETH to bridge. @@ -169,12 +112,7 @@ contract EthereumBridgeHelperModule is success = safeContract.execTransactionFromModule( address(vault), 0, // Value - abi.encodeWithSelector( - vault.mint.selector, - address(weth), - wethAmount, - wethAmount - ), + abi.encodeWithSelector(vault.mint.selector, wethAmount), 0 // Call ); require(success, "Failed to mint OETH"); @@ -211,25 +149,6 @@ contract EthereumBridgeHelperModule is return woeth.balanceOf(address(safeContract)) - woethAmount; } - /** - * @dev Mints OETH and wraps it into wOETH, then bridges it to Plume. - * @param wethAmount Amount of WETH to mint. - * @param slippageBps Bridge slippage in 10^4 basis points. - * @param useNativeToken Whether to use native token to mint. - */ - function mintWrapAndBridgeToPlume( - uint256 wethAmount, - uint256 slippageBps, - bool useNativeToken - ) external payable onlyOperator { - if (useNativeToken) { - wrapETH(wethAmount); - } - - uint256 woethAmount = _mintAndWrap(wethAmount); - bridgeWOETHToPlume(woethAmount, slippageBps); - } - /** * @dev Mints OETH and wraps it into wOETH, then bridges it to Base using CCIP. * @param wethAmount Amount of WETH to mint. @@ -249,25 +168,60 @@ contract EthereumBridgeHelperModule is } /** - * @dev Unwraps wOETH and redeems it to get WETH. + * @dev Unwraps wOETH and requests an async withdrawal from the Vault. * @param woethAmount Amount of wOETH to unwrap. - * @return Amount of WETH received. + * @return requestId The withdrawal request ID. + * @return oethAmount Amount of OETH queued for withdrawal. */ - function unwrapAndRedeem(uint256 woethAmount) + function unwrapAndRequestWithdrawal(uint256 woethAmount) external onlyOperator - returns (uint256) + returns (uint256 requestId, uint256 oethAmount) + { + return _unwrapAndRequestWithdrawal(woethAmount); + } + + /** + * @dev Claims a previously requested withdrawal and bridges WETH to Base. + * @param requestId The withdrawal request ID to claim. + */ + function claimAndBridgeToBase(uint256 requestId) + external + payable + onlyOperator + { + uint256 wethAmount = _claimWithdrawal(requestId); + bridgeWETHToBase(wethAmount); + } + + /** + * @dev Claims a previously requested withdrawal. + * @param requestId The withdrawal request ID to claim. + * @return wethAmount Amount of WETH received. + */ + function claimWithdrawal(uint256 requestId) + external + onlyOperator + returns (uint256 wethAmount) { - return _unwrapAndRedeem(woethAmount); + return _claimWithdrawal(requestId); } /** - * @dev Unwraps wOETH and redeems it to get WETH. + * @dev Unwraps wOETH and requests an async withdrawal from the Vault. * @param woethAmount Amount of wOETH to unwrap. - * @return Amount of WETH received. + * @return requestId The withdrawal request ID. + * @return oethAmount Amount of OETH queued for withdrawal. */ - function _unwrapAndRedeem(uint256 woethAmount) internal returns (uint256) { - uint256 oethAmount = oeth.balanceOf(address(safeContract)); + function _unwrapAndRequestWithdrawal(uint256 woethAmount) + internal + returns (uint256 requestId, uint256 oethAmount) + { + // Read the next withdrawal index before requesting + // (safe because requestWithdrawal is nonReentrant) + requestId = vault.withdrawalQueueMetadata().nextWithdrawalIndex; + + oethAmount = oeth.balanceOf(address(safeContract)); // Unwrap wOETH bool success = safeContract.execTransactionFromModule( @@ -285,53 +239,38 @@ contract EthereumBridgeHelperModule is oethAmount = oeth.balanceOf(address(safeContract)) - oethAmount; - // Redeem OETH using Vault to get WETH + // Request async withdrawal from Vault success = safeContract.execTransactionFromModule( address(vault), 0, // Value abi.encodeWithSelector( - vault.redeem.selector, - oethAmount, + vault.requestWithdrawal.selector, oethAmount ), 0 // Call ); - require(success, "Failed to redeem OETH"); - - return oethAmount; + require(success, "Failed to request withdrawal"); } /** - * @dev Unwraps wOETH and redeems it to get WETH, then bridges it to Plume. - * @param woethAmount Amount of wOETH to unwrap. - * @param slippageBps Bridge slippage in 10^4 basis points. + * @dev Claims a previously requested withdrawal from the Vault. + * @param requestId The withdrawal request ID to claim. + * @return wethAmount Amount of WETH received. */ - function unwrapRedeemAndBridgeToPlume( - uint256 woethAmount, - uint256 slippageBps - ) external payable onlyOperator { - uint256 wethAmount = _unwrapAndRedeem(woethAmount); - // Unwrap into ETH - safeContract.execTransactionFromModule( - address(weth), + function _claimWithdrawal(uint256 requestId) + internal + returns (uint256 wethAmount) + { + wethAmount = weth.balanceOf(address(safeContract)); + + bool success = safeContract.execTransactionFromModule( + address(vault), 0, // Value - abi.encodeWithSelector(weth.withdraw.selector, wethAmount), + abi.encodeWithSelector(vault.claimWithdrawal.selector, requestId), 0 // Call ); + require(success, "Failed to claim withdrawal"); - bridgeWETHToPlume(wethAmount, slippageBps); - } - - /** - * @dev Unwraps wOETH and redeems it to get WETH, then bridges it to Base using CCIP. - * @param woethAmount Amount of wOETH to unwrap. - */ - function unwrapRedeemAndBridgeToBase(uint256 woethAmount) - external - payable - onlyOperator - { - uint256 wethAmount = _unwrapAndRedeem(woethAmount); - bridgeWETHToBase(wethAmount); + wethAmount = weth.balanceOf(address(safeContract)) - wethAmount; } } diff --git a/contracts/contracts/automation/PlumeBridgeHelperModule.sol b/contracts/contracts/automation/PlumeBridgeHelperModule.sol index 2c2da15ba5..8257bede30 100644 --- a/contracts/contracts/automation/PlumeBridgeHelperModule.sol +++ b/contracts/contracts/automation/PlumeBridgeHelperModule.sol @@ -163,11 +163,12 @@ contract PlumeBridgeHelperModule is } // Redeem for WETH using Vault + // redeem(uint256,uint256) was removed from VaultCore; use hardcoded selector success = safeContract.execTransactionFromModule( address(vault), 0, // Value abi.encodeWithSelector( - vault.redeem.selector, + bytes4(keccak256("redeem(uint256,uint256)")), oethpAmount, oethpAmount ), @@ -228,11 +229,12 @@ contract PlumeBridgeHelperModule is require(success, "Failed to approve WETH"); // Mint OETHp with WETH + // mint(address,uint256,uint256) was removed from IVault; use hardcoded selector success = safeContract.execTransactionFromModule( address(vault), 0, // Value abi.encodeWithSelector( - vault.mint.selector, + bytes4(keccak256("mint(address,uint256,uint256)")), address(weth), wethAmount, wethAmount diff --git a/contracts/contracts/interfaces/IVault.sol b/contracts/contracts/interfaces/IVault.sol index c517cf82b0..55fae1009f 100644 --- a/contracts/contracts/interfaces/IVault.sol +++ b/contracts/contracts/interfaces/IVault.sol @@ -117,16 +117,10 @@ interface IVault { ) external; // VaultCore.sol - function mint( - address _asset, - uint256 _amount, - uint256 _minimumOusdAmount - ) external; + function mint(uint256 _amount) external; function mintForStrategy(uint256 _amount) external; - function redeem(uint256 _amount, uint256 _minimumUnitAmount) external; - function burnForStrategy(uint256 _amount) external; function allocate() external; @@ -137,17 +131,6 @@ interface IVault { function checkBalance(address _asset) external view returns (uint256); - /// @notice Deprecated: use calculateRedeemOutput - function calculateRedeemOutputs(uint256 _amount) - external - view - returns (uint256[] memory); - - function calculateRedeemOutput(uint256 _amount) - external - view - returns (uint256); - function getAssetCount() external view returns (uint256); function getAllAssets() external view returns (address[] memory); @@ -159,8 +142,6 @@ interface IVault { /// @notice Deprecated. function isSupportedAsset(address _asset) external view returns (bool); - function dripper() external view returns (address); - function asset() external view returns (address); function initialize(address) external; @@ -216,7 +197,5 @@ interface IVault { function previewYield() external view returns (uint256 yield); - function weth() external view returns (address); - // slither-disable-end constable-states } diff --git a/contracts/contracts/mocks/MockNonRebasing.sol b/contracts/contracts/mocks/MockNonRebasing.sol index b3ad870347..807f6bfd1e 100644 --- a/contracts/contracts/mocks/MockNonRebasing.sol +++ b/contracts/contracts/mocks/MockNonRebasing.sol @@ -38,12 +38,8 @@ contract MockNonRebasing { oUSD.approve(_spender, _addedValue); } - function mintOusd( - address _vaultContract, - address _asset, - uint256 _amount - ) public { - IVault(_vaultContract).mint(_asset, _amount, 0); + function mintOusd(address _vaultContract, uint256 _amount) public { + IVault(_vaultContract).mint(_amount); } function redeemOusd(address _vaultContract, uint256 _amount) public { diff --git a/contracts/contracts/mocks/MockRebornMinter.sol b/contracts/contracts/mocks/MockRebornMinter.sol index aa7394b7d1..d5dad1c86f 100644 --- a/contracts/contracts/mocks/MockRebornMinter.sol +++ b/contracts/contracts/mocks/MockRebornMinter.sol @@ -98,15 +98,15 @@ contract Reborner { address asset = sanctum.asset(); address vault = sanctum.vault(); IERC20(asset).approve(vault, 1e6); - IVault(vault).mint(asset, 1e6, 0); + IVault(vault).mint(1e6); log("We are now minting.."); } function redeem() public { - log("We are attempting to redeem.."); + log("We are attempting to request withdrawal.."); address vault = sanctum.vault(); - IVault(vault).redeem(1e18, 1e18); - log("We are now redeeming.."); + IVault(vault).requestWithdrawal(1e18); + log("We are now requesting withdrawal.."); } function transfer() public { diff --git a/contracts/contracts/zapper/AbstractOTokenZapper.sol b/contracts/contracts/zapper/AbstractOTokenZapper.sol index 06445d1442..e095fa1b69 100644 --- a/contracts/contracts/zapper/AbstractOTokenZapper.sol +++ b/contracts/contracts/zapper/AbstractOTokenZapper.sol @@ -123,7 +123,7 @@ abstract contract AbstractOTokenZapper { returns (uint256) { uint256 toMint = weth.balanceOf(address(this)); - vault.mint(address(weth), toMint, minOToken); + vault.mint(toMint); uint256 mintedAmount = oToken.balanceOf(address(this)); require(mintedAmount >= minOToken, "Zapper: not enough minted"); diff --git a/contracts/contracts/zapper/OSonicZapper.sol b/contracts/contracts/zapper/OSonicZapper.sol index cd08f45bb8..8465e751da 100644 --- a/contracts/contracts/zapper/OSonicZapper.sol +++ b/contracts/contracts/zapper/OSonicZapper.sol @@ -125,7 +125,7 @@ contract OSonicZapper { returns (uint256) { uint256 toMint = wS.balanceOf(address(this)); - vault.mint(address(wS), toMint, minOS); + vault.mint(toMint); uint256 mintedAmount = OS.balanceOf(address(this)); require(mintedAmount >= minOS, "Zapper: not enough minted"); diff --git a/contracts/deploy/base/044_bridge_helper_module.js b/contracts/deploy/base/044_bridge_helper_module.js new file mode 100644 index 0000000000..00e05d91d4 --- /dev/null +++ b/contracts/deploy/base/044_bridge_helper_module.js @@ -0,0 +1,42 @@ +const { deployOnBase } = require("../../utils/deploy-l2"); +const addresses = require("../../utils/addresses"); +const { isFork } = require("../../utils/hardhat-helpers"); +const { impersonateAndFund } = require("../../utils/signers"); + +module.exports = deployOnBase( + { + deployName: "044_bridge_helper_module", + }, + async ({ deployWithConfirmation, withConfirmation }) => { + const safeAddress = addresses.multichainStrategist; + + await deployWithConfirmation("BaseBridgeHelperModule", [safeAddress]); + const cBridgeHelperModule = await ethers.getContract( + "BaseBridgeHelperModule" + ); + + console.log( + `BaseBridgeHelperModule (for ${safeAddress}) deployed to`, + cBridgeHelperModule.address + ); + + if (isFork) { + const safeSigner = await impersonateAndFund(safeAddress); + + const cSafe = await ethers.getContractAt( + ["function enableModule(address module) external"], + safeAddress + ); + + await withConfirmation( + cSafe.connect(safeSigner).enableModule(cBridgeHelperModule.address) + ); + + console.log("Enabled module on fork"); + } + + return { + actions: [], + }; + } +); diff --git a/contracts/deploy/mainnet/178_bridge_helper_module.js b/contracts/deploy/mainnet/178_bridge_helper_module.js new file mode 100644 index 0000000000..928250d1c2 --- /dev/null +++ b/contracts/deploy/mainnet/178_bridge_helper_module.js @@ -0,0 +1,46 @@ +const addresses = require("../../utils/addresses"); +const { deploymentWithGovernanceProposal } = require("../../utils/deploy"); +const { isFork } = require("../../utils/hardhat-helpers"); +const { impersonateAndFund } = require("../../utils/signers"); + +module.exports = deploymentWithGovernanceProposal( + { + deployName: "178_bridge_helper_module", + forceDeploy: false, + reduceQueueTime: true, + deployerIsProposer: false, + proposalId: "", + }, + async ({ deployWithConfirmation, withConfirmation }) => { + const safeAddress = addresses.multichainStrategist; + + await deployWithConfirmation("EthereumBridgeHelperModule", [safeAddress]); + const cBridgeHelperModule = await ethers.getContract( + "EthereumBridgeHelperModule" + ); + + console.log( + `EthereumBridgeHelperModule (for ${safeAddress}) deployed to`, + cBridgeHelperModule.address + ); + + if (isFork) { + const safeSigner = await impersonateAndFund(safeAddress); + + const cSafe = await ethers.getContractAt( + ["function enableModule(address module) external"], + safeAddress + ); + + await withConfirmation( + cSafe.connect(safeSigner).enableModule(cBridgeHelperModule.address) + ); + + console.log("Enabled module on fork"); + } + + return { + actions: [], + }; + } +); diff --git a/contracts/test/_fixture-plume.js b/contracts/test/_fixture-plume.js index ece30ef133..cde954fbd9 100644 --- a/contracts/test/_fixture-plume.js +++ b/contracts/test/_fixture-plume.js @@ -80,6 +80,16 @@ const defaultFixture = async () => { oethpVaultProxy.address ); + // Plume vault uses deprecated mint/redeem signatures (won't be upgraded) + const oethpVaultLegacy = new ethers.Contract( + oethpVaultProxy.address, + [ + "function mint(address _asset, uint256 _amount, uint256 _minimumOusdAmount) external", + "function redeem(uint256 _amount, uint256 _minimumUnitAmount) external", + ], + ethers.provider + ); + // Bridged wOETH const woethProxy = await ethers.getContract("BridgedWOETHProxy"); const woeth = await ethers.getContractAt("BridgedWOETH", woethProxy.address); @@ -178,6 +188,7 @@ const defaultFixture = async () => { oethp, wOETHp, oethpVault, + oethpVaultLegacy, roosterAmoStrategy, roosterOETHpWETHpool, // Bridged wOETH diff --git a/contracts/test/_fixture-sonic.js b/contracts/test/_fixture-sonic.js index 0d320dc748..41ff1b2364 100644 --- a/contracts/test/_fixture-sonic.js +++ b/contracts/test/_fixture-sonic.js @@ -282,7 +282,7 @@ async function swapXAMOFixture( // Mint OS with wS // This will sit in the vault, not the strategy - await oSonicVault.connect(nick).mint(wS.address, mintAmount, 0); + await oSonicVault.connect(nick).mint(mintAmount); } // Add ETH to the Metapool @@ -324,7 +324,7 @@ async function swapXAMOFixture( const osAmount = parseUnits(config.poolAddOSAmount.toString(), 18); // Mint OS with wS - await oSonicVault.connect(rafael).mint(wS.address, osAmount, 0); + await oSonicVault.connect(rafael).mint(osAmount); // transfer OS to the pool await oSonic.connect(rafael).transfer(swapXPool.address, osAmount); diff --git a/contracts/test/_fixture.js b/contracts/test/_fixture.js index a0d3709611..28aba6a6e8 100644 --- a/contracts/test/_fixture.js +++ b/contracts/test/_fixture.js @@ -248,7 +248,7 @@ const createAccountTypes = async ({ vault, ousd, ousdUnlocked, deploy }) => { await fundAccounts(); const usdc = await ethers.getContract("MockUSDC"); await usdc.connect(matt).approve(vault.address, usdcUnits("1000")); - await vault.connect(matt).mint(usdc.address, usdcUnits("1000"), 0); + await vault.connect(matt).mint(usdcUnits("1000")); } const createAccount = async () => { @@ -722,9 +722,7 @@ const defaultFixture = deployments.createFixture(async () => { await usdc .connect(user) .approve(vaultAndTokenContracts.vault.address, usdcUnits("100")); - await vaultAndTokenContracts.vault - .connect(user) - .mint(usdc.address, usdcUnits("100"), 0); + await vaultAndTokenContracts.vault.connect(user).mint(usdcUnits("100")); // Fund WETH contract await hardhatSetBalance(user.address, "50000"); @@ -1005,7 +1003,7 @@ async function morphoOUSDv2Fixture( // Mint OUSD with USDC // This will sit in the vault, not the strategy - await vault.connect(josh).mint(usdc.address, usdcMintAmount, 0); + await vault.connect(josh).mint(usdcMintAmount); // Add USDC to the strategy if (config?.depositToStrategy) { diff --git a/contracts/test/poolBooster/merkl-pool-booster.sonic.fork-test.js b/contracts/test/poolBooster/merkl-pool-booster.sonic.fork-test.js index 9ba5ada3ce..4fbe18923e 100644 --- a/contracts/test/poolBooster/merkl-pool-booster.sonic.fork-test.js +++ b/contracts/test/poolBooster/merkl-pool-booster.sonic.fork-test.js @@ -15,7 +15,6 @@ describe("ForkTest: Merkl Pool Booster", function () { let fixture, poolBoosterMerklFactory, nick, - wS, oSonicVault, oSonic, strategist, @@ -23,7 +22,6 @@ describe("ForkTest: Merkl Pool Booster", function () { beforeEach(async () => { fixture = await sonicFixture(); nick = fixture.nick; - wS = fixture.wS; oSonicVault = fixture.oSonicVault; oSonic = fixture.oSonic; poolBoosterMerklFactory = fixture.poolBoosterMerklFactory; @@ -33,9 +31,7 @@ describe("ForkTest: Merkl Pool Booster", function () { await ensureTokenIsApproved(oSonic); // mint some OS to Nick - await oSonicVault - .connect(nick) - .mint(wS.address, oethUnits("10000"), oethUnits("0")); + await oSonicVault.connect(nick).mint(oethUnits("10000")); }); it("Should have correct deployment params", async () => { diff --git a/contracts/test/poolBooster/metropolis-pool-booster.sonic.fork-test.js b/contracts/test/poolBooster/metropolis-pool-booster.sonic.fork-test.js index a8635e9417..b8b2a38957 100644 --- a/contracts/test/poolBooster/metropolis-pool-booster.sonic.fork-test.js +++ b/contracts/test/poolBooster/metropolis-pool-booster.sonic.fork-test.js @@ -11,23 +11,19 @@ describe("ForkTest: Metropolis Pool Booster", function () { let fixture, poolBoosterFactoryMetropolis, nick, - wS, oSonicVault, oSonic, strategist; beforeEach(async () => { fixture = await sonicFixture(); nick = fixture.nick; - wS = fixture.wS; oSonicVault = fixture.oSonicVault; oSonic = fixture.oSonic; poolBoosterFactoryMetropolis = fixture.poolBoosterFactoryMetropolis; strategist = await impersonateAndFund(addresses.multichainStrategist); // mint some OS to Nick - await oSonicVault - .connect(nick) - .mint(wS.address, oethUnits("1000000"), oethUnits("0")); + await oSonicVault.connect(nick).mint(oethUnits("1000000")); }); it("Should deploy a Pool Booster for a Metropolis pool", async () => { diff --git a/contracts/test/poolBooster/poolBooster.sonic.fork-test.js b/contracts/test/poolBooster/poolBooster.sonic.fork-test.js index c10119e858..0b1c1a3b4d 100644 --- a/contracts/test/poolBooster/poolBooster.sonic.fork-test.js +++ b/contracts/test/poolBooster/poolBooster.sonic.fork-test.js @@ -17,11 +17,9 @@ describe("ForkTest: Pool Booster", function () { let fixture, strategist; beforeEach(async () => { fixture = await sonicFixture(); - const { wS, oSonicVault, nick } = fixture; + const { oSonicVault, nick } = fixture; // mint some OS - await oSonicVault - .connect(nick) - .mint(wS.address, oethUnits("1000"), oethUnits("0")); + await oSonicVault.connect(nick).mint(oethUnits("1000")); strategist = await impersonateAndFund(addresses.multichainStrategist); }); diff --git a/contracts/test/poolBooster/shadow-pool-booster.sonic.fork-test.js b/contracts/test/poolBooster/shadow-pool-booster.sonic.fork-test.js index fbce7d8961..e810d77da9 100644 --- a/contracts/test/poolBooster/shadow-pool-booster.sonic.fork-test.js +++ b/contracts/test/poolBooster/shadow-pool-booster.sonic.fork-test.js @@ -16,11 +16,9 @@ describe("ForkTest: Shadow Pool Booster (for S/WETH pool)", function () { let fixture; beforeEach(async () => { fixture = await sonicFixture(); - const { wS, oSonicVault, nick } = fixture; + const { oSonicVault, nick } = fixture; // mint some OS - await oSonicVault - .connect(nick) - .mint(wS.address, oethUnits("1000"), oethUnits("0")); + await oSonicVault.connect(nick).mint(oethUnits("1000")); }); it("Should create a pool booster for Shadow and bribe", async () => { diff --git a/contracts/test/safe-modules/bridge-helper.base.fork-test.js b/contracts/test/safe-modules/bridge-helper.base.fork-test.js index dd13824c32..9770915aef 100644 --- a/contracts/test/safe-modules/bridge-helper.base.fork-test.js +++ b/contracts/test/safe-modules/bridge-helper.base.fork-test.js @@ -1,6 +1,6 @@ const { createFixtureLoader } = require("../_fixture"); const { bridgeHelperModuleFixture } = require("../_fixture-base"); -const { oethUnits } = require("../helpers"); +const { oethUnits, advanceTime } = require("../helpers"); const { expect } = require("chai"); const baseFixture = createFixtureLoader(bridgeHelperModuleFixture); @@ -41,24 +41,31 @@ describe("ForkTest: Bridge Helper Safe Module (Base)", function () { .bridgeWETHToEthereum(oethUnits("1")); }); - it.skip("Should deposit wOETH for OETHb and redeem it for WETH", async () => { + it("Should deposit wOETH for OETHb and async withdraw for WETH", async () => { const { nick, _mintWETH, oethbVault, woeth, weth, - oethb, minter, + governor, safeSigner, woethStrategy, bridgeHelperModule, } = fixture; // Make sure Vault has some WETH - _mintWETH(nick, "10000"); + await _mintWETH(nick, "10000"); await weth.connect(nick).approve(oethbVault.address, oethUnits("10000")); - await oethbVault.connect(nick).mint(weth.address, oethUnits("10000"), "0"); + await oethbVault.connect(nick).mint(oethUnits("10000")); + + // Ensure withdrawal claim delay is set + let delayPeriod = await oethbVault.withdrawalClaimDelay(); + if (delayPeriod == 0) { + await oethbVault.connect(governor).setWithdrawalClaimDelay(10 * 60); + delayPeriod = 10 * 60; + } // Update oracle price await woethStrategy.updateWOETHOraclePrice(); @@ -70,7 +77,6 @@ describe("ForkTest: Bridge Helper Safe Module (Base)", function () { // Mint 1 wOETH await woeth.connect(minter).mint(safeSigner.address, woethAmount); - const supplyBefore = await oethb.totalSupply(); const wethBalanceBefore = await weth.balanceOf(safeSigner.address); const woethBalanceBefore = await woeth.balanceOf(safeSigner.address); @@ -81,37 +87,44 @@ describe("ForkTest: Bridge Helper Safe Module (Base)", function () { weth.address ); - // Deposit 1 wOETH for OETHb and redeem it for WETH + // Get request ID before the call + const { nextWithdrawalIndex } = await oethbVault.withdrawalQueueMetadata(); + + // Deposit 1 wOETH and request async withdrawal await bridgeHelperModule .connect(safeSigner) - .depositWOETH(oethUnits("1"), true); + .depositWOETH(woethAmount, true); - const supplyAfter = await oethb.totalSupply(); - const wethBalanceAfter = await weth.balanceOf(safeSigner.address); - const woethBalanceAfter = await woeth.balanceOf(safeSigner.address); - const woethStrategyBalanceAfter = await woeth.balanceOf( - woethStrategy.address + // wOETH should be transferred to strategy + expect(await woeth.balanceOf(safeSigner.address)).to.eq( + woethBalanceBefore.sub(woethAmount) ); - const woethStrategyValueAfter = await woethStrategy.checkBalance( - weth.address + expect(await woeth.balanceOf(woethStrategy.address)).to.eq( + woethStrategyBalanceBefore.add(woethAmount) ); + expect( + await woethStrategy.checkBalance(weth.address) + ).to.approxEqualTolerance(woethStrategyValueBefore.add(expectedWETH)); - expect(supplyAfter).to.approxEqualTolerance( - supplyBefore.add(oethUnits("1")) - ); + // WETH shouldn't have changed yet (withdrawal is pending) + expect(await weth.balanceOf(safeSigner.address)).to.eq(wethBalanceBefore); + + // Advance time past the claim delay + await advanceTime(delayPeriod + 1); + + // Claim the withdrawal + await bridgeHelperModule + .connect(safeSigner) + .claimWithdrawal(nextWithdrawalIndex); + + // WETH should have increased by the expected amount + const wethBalanceAfter = await weth.balanceOf(safeSigner.address); expect(wethBalanceAfter).to.approxEqualTolerance( wethBalanceBefore.add(expectedWETH) ); - expect(woethBalanceAfter).to.eq(woethBalanceBefore.sub(woethAmount)); - expect(woethStrategyBalanceAfter).to.eq( - woethStrategyBalanceBefore.add(woethAmount) - ); - expect(woethStrategyValueAfter).to.approxEqualTolerance( - woethStrategyValueBefore.add(expectedWETH) - ); }); - it.skip("Should mint OETHb with WETH and redeem it for wOETH", async () => { + it("Should mint OETHb with WETH and redeem it for wOETH", async () => { const { _mintWETH, oethbVault, diff --git a/contracts/test/safe-modules/bridge-helper.mainnet.fork-test.js b/contracts/test/safe-modules/bridge-helper.mainnet.fork-test.js index e66bc9a134..7f47e4a7aa 100644 --- a/contracts/test/safe-modules/bridge-helper.mainnet.fork-test.js +++ b/contracts/test/safe-modules/bridge-helper.mainnet.fork-test.js @@ -4,8 +4,6 @@ const { } = require("../_fixture"); const { oethUnits } = require("../helpers"); const { expect } = require("chai"); -const addresses = require("../../utils/addresses"); -//const { impersonateAndFund } = require("../../utils/signers"); const mainnetFixture = createFixtureLoader(bridgeHelperModuleFixture); @@ -21,7 +19,7 @@ describe("ForkTest: Bridge Helper Safe Module (Ethereum)", function () { const _mintOETH = async (amount, user) => { await weth.connect(user).approve(oethVault.address, amount); - await oethVault.connect(user).mint(weth.address, amount, amount); + await oethVault.connect(user).mint(amount); }; const _mintWOETH = async (amount, user, receiver) => { @@ -32,78 +30,6 @@ describe("ForkTest: Bridge Helper Safe Module (Ethereum)", function () { await woeth.connect(user).mint(amount, receiver); }; - it("Should bridge wOETH to Plume", async () => { - const { woeth, josh, safeSigner, bridgeHelperModule } = fixture; - - // Mint 1 wOETH - await _mintWOETH(oethUnits("1"), josh, safeSigner.address); - - const balanceBefore = await woeth.balanceOf(safeSigner.address); - - // Bridge 1 wOETH to Ethereum - const tx = await bridgeHelperModule.connect(safeSigner).bridgeWOETHToPlume( - oethUnits("1"), - 50 // 0.5% slippage - ); - - // Check balance - const balanceAfter = await woeth.balanceOf(safeSigner.address); - expect(balanceAfter).to.eq(balanceBefore.sub(oethUnits("1"))); - - // Check events - const { events } = await tx.wait(); - - const lzBridgeEvent = events.find( - (e) => - e.address.toLowerCase() === - addresses.mainnet.WOETHOmnichainAdapter.toLowerCase() - ); - - expect(lzBridgeEvent.topics[2]).to.eq( - "0x0000000000000000000000004ff1b9d9ba8558f5eafcec096318ea0d8b541971" - ); - const [endpointId, amountSentLD, minAmountLD] = - ethers.utils.defaultAbiCoder.decode( - ["uint256", "uint256", "uint256"], - lzBridgeEvent.data - ); - expect(endpointId).to.eq("30370"); - expect(amountSentLD).to.eq(oethUnits("1")); - expect(minAmountLD).to.eq(oethUnits("1")); - }); - - it("Should bridge WETH to Plume", async () => { - const { weth, josh, safeSigner, bridgeHelperModule } = fixture; - - await weth.connect(josh).transfer(safeSigner.address, oethUnits("1.1")); - - // Bridge 1 WETH to Plume - const tx = await bridgeHelperModule.connect(safeSigner).bridgeWETHToPlume( - oethUnits("1"), - 100 // 1% slippage - ); - - const { events } = await tx.wait(); - - const lzBridgeEvent = events.find( - (e) => - e.address.toLowerCase() === - addresses.mainnet.ETHOmnichainAdapter.toLowerCase() - ); - - expect(lzBridgeEvent.topics[2]).to.eq( - "0x0000000000000000000000004ff1b9d9ba8558f5eafcec096318ea0d8b541971" - ); - const [endpointId, amountSentLD, minAmountLD] = - ethers.utils.defaultAbiCoder.decode( - ["uint256", "uint256", "uint256"], - lzBridgeEvent.data - ); - expect(endpointId).to.eq("30370"); - expect(amountSentLD).to.eq(oethUnits("1")); - expect(minAmountLD).to.gt(oethUnits("0.99")); - }); - it("Should bridge wOETH to Base", async () => { const { woeth, josh, safeSigner, bridgeHelperModule } = fixture; @@ -177,40 +103,4 @@ describe("ForkTest: Bridge Helper Safe Module (Ethereum)", function () { woethSupplyBefore.add(woethAmount) ); }); - - // Redeem has been deprecated for now, so skipping this test - /* - it("Should unwrap WOETH and redeem it to WETH", async () => { - const { woeth, weth, josh, safeSigner, bridgeHelperModule, oethVault } = - fixture; - - await oethVault.connect(josh).rebase(); - - // Do a huge yield deposit to fund the Vault - await impersonateAndFund(josh.address, "10000"); - await weth.connect(josh).deposit({ value: oethUnits("9500") }); - await weth.connect(josh).approve(oethVault.address, oethUnits("9500")); - await oethVault.connect(josh).mint(weth.address, oethUnits("9000"), "0"); - - const woethAmount = oethUnits("1"); - - // Make sure Safe has some wOETH - await _mintWOETH(woethAmount, josh, safeSigner.address); - - const wethExpected = await woeth.previewRedeem(woethAmount); - - const wethBalanceBefore = await weth.balanceOf(safeSigner.address); - const woethBalanceBefore = await woeth.balanceOf(safeSigner.address); - - await bridgeHelperModule.connect(safeSigner).unwrapAndRedeem(woethAmount); - - const wethBalanceAfter = await weth.balanceOf(safeSigner.address); - const woethBalanceAfter = await woeth.balanceOf(safeSigner.address); - - expect(wethBalanceAfter).to.approxEqualTolerance( - wethBalanceBefore.add(wethExpected) - ); - expect(woethBalanceAfter).to.eq(woethBalanceBefore.sub(woethAmount)); - }); - */ }); diff --git a/contracts/test/safe-modules/bridge-helper.plume.fork-test.js b/contracts/test/safe-modules/bridge-helper.plume.fork-test.js index 41f5c35fd0..b75d386189 100644 --- a/contracts/test/safe-modules/bridge-helper.plume.fork-test.js +++ b/contracts/test/safe-modules/bridge-helper.plume.fork-test.js @@ -106,7 +106,7 @@ describe("ForkTest: Bridge Helper Safe Module (Plume)", function () { // Make sure Vault has some WETH _mintWETH(governor, oethUnits("1")); await weth.connect(governor).approve(oethpVault.address, oethUnits("1")); - await oethpVault.connect(governor).mint(weth.address, oethUnits("1"), "0"); + await oethpVault.connect(governor).mint(oethUnits("1")); // Update oracle price await woethStrategy.updateWOETHOraclePrice(); @@ -181,9 +181,7 @@ describe("ForkTest: Bridge Helper Safe Module (Plume)", function () { await oethpVault.rebase(); await woethStrategy.updateWOETHOraclePrice(); - await oethpVault - .connect(governor) - .mint(weth.address, oethUnits("1.1"), "0"); + await oethpVault.connect(governor).mint(oethUnits("1.1")); const woethAmount = oethUnits("1"); const expectedWETH = await woethStrategy.getBridgedWOETHValue(woethAmount); diff --git a/contracts/test/strategies/base/aerodrome-amo.base.fork-test.js b/contracts/test/strategies/base/aerodrome-amo.base.fork-test.js index a78a97bcde..23c072c46c 100644 --- a/contracts/test/strategies/base/aerodrome-amo.base.fork-test.js +++ b/contracts/test/strategies/base/aerodrome-amo.base.fork-test.js @@ -75,9 +75,7 @@ describe("Base Fork Test: Aerodrome AMO Strategy empty pool setup (Base)", async .timestamp; await weth.connect(rafael).approve(oethbVault.address, oethUnits("200")); - await oethbVault - .connect(rafael) - .mint(weth.address, oethUnits("200"), oethUnits("199.999")); + await oethbVault.connect(rafael).mint(oethUnits("200")); // we need to supply liquidity in 2 separate transactions so liquidity position is populated // outside the active tick. @@ -115,7 +113,7 @@ describe("Base Fork Test: Aerodrome AMO Strategy empty pool setup (Base)", async // Haven't found a way to test for this in the strategy contract yet it.skip("Revert when there is no token id yet and no liquidity to perform the swap.", async () => { const amount = oethUnits("5"); - await oethbVault.connect(rafael).mint(weth.address, amount, amount); + await oethbVault.connect(rafael).mint(amount); await oethbVault .connect(governor) @@ -221,7 +219,7 @@ describe("Base Fork Test: Aerodrome AMO Strategy empty pool setup (Base)", async const balanceOETHb = await oethb.balanceOf(rafael.address); if (!swapWeth && balanceOETHb.lt(amount)) { await weth.connect(rafael).approve(oethbVault.address, amount); - await oethbVault.connect(rafael).mint(weth.address, amount, amount); + await oethbVault.connect(rafael).mint(amount); } const sqrtRatioX96Tick1000 = BigNumber.from( @@ -315,9 +313,7 @@ describe("ForkTest: Aerodrome AMO Strategy (Base)", async function () { .timestamp; await weth.connect(rafael).approve(oethbVault.address, oethUnits("200")); - await oethbVault - .connect(rafael) - .mint(weth.address, oethUnits("200"), oethUnits("199.999")); + await oethbVault.connect(rafael).mint(oethUnits("200")); // we need to supply liquidity in 2 separate transactions so liquidity position is populated // outside the active tick. @@ -832,7 +828,7 @@ describe("ForkTest: Aerodrome AMO Strategy (Base)", async function () { const amount = oethUnits("5"); weth.connect(rafael).approve(oethbVault.address, amount); - await oethbVault.connect(rafael).mint(weth.address, amount, amount); + await oethbVault.connect(rafael).mint(amount); const gov = await aerodromeAmoStrategy.governor(); await oethbVault @@ -1337,7 +1333,7 @@ describe("ForkTest: Aerodrome AMO Strategy (Base)", async function () { const balanceOETHb = await oethb.balanceOf(rafael.address); if (!swapWeth && balanceOETHb.lt(amount)) { await weth.connect(rafael).approve(oethbVault.address, amount); - await oethbVault.connect(rafael).mint(weth.address, amount, amount); + await oethbVault.connect(rafael).mint(amount); // Deal WETH and mint OETHb } @@ -1411,9 +1407,7 @@ describe("ForkTest: Aerodrome AMO Strategy (Base)", async function () { await setERC20TokenBalance(user.address, weth, amount + balance, hre); } await weth.connect(user).approve(oethbVault.address, amount); - const tx = await oethbVault - .connect(user) - .mint(weth.address, amount, amount); + const tx = await oethbVault.connect(user).mint(amount); return tx; }; @@ -1430,7 +1424,7 @@ describe("ForkTest: Aerodrome AMO Strategy (Base)", async function () { const _amount = oethUnits("5000"); await setERC20TokenBalance(nick.address, weth, _amount, hre); await weth.connect(nick).approve(oethbVault.address, _amount); - await oethbVault.connect(nick).mint(weth.address, _amount, _amount); + await oethbVault.connect(nick).mint(_amount); } const balance = await weth.balanceOf(user.address); @@ -1438,7 +1432,7 @@ describe("ForkTest: Aerodrome AMO Strategy (Base)", async function () { await setERC20TokenBalance(user.address, weth, amount + balance, hre); } await weth.connect(user).approve(oethbVault.address, amount); - await oethbVault.connect(user).mint(weth.address, amount, amount); + await oethbVault.connect(user).mint(amount); const gov = await oethbVault.governor(); const tx = await oethbVault diff --git a/contracts/test/strategies/base/curve-amo.base.fork-test.js b/contracts/test/strategies/base/curve-amo.base.fork-test.js index 799d4cd040..edd065f38c 100644 --- a/contracts/test/strategies/base/curve-amo.base.fork-test.js +++ b/contracts/test/strategies/base/curve-amo.base.fork-test.js @@ -777,7 +777,7 @@ describe("Base Fork Test: Curve AMO strategy", function () { await setERC20TokenBalance(user.address, weth, amount.add(balance), hre); } await weth.connect(user).approve(oethbVault.address, amount); - await oethbVault.connect(user).mint(weth.address, amount, amount); + await oethbVault.connect(user).mint(amount); const gov = await oethbVault.governor(); log(`Depositing ${formatUnits(amount)} WETH to AMO strategy`); @@ -811,9 +811,7 @@ describe("Base Fork Test: Curve AMO strategy", function () { await weth .connect(nick) .approve(oethbVault.address, amount.mul(101).div(10)); - await oethbVault - .connect(nick) - .mint(weth.address, amount.mul(101).div(10), amount); + await oethbVault.connect(nick).mint(amount.mul(101).div(10)); await oethb.connect(nick).approve(curvePool.address, amount); // prettier-ignore await curvePool @@ -881,9 +879,7 @@ describe("Base Fork Test: Curve AMO strategy", function () { ); } await weth.connect(nick).approve(oethbVault.address, oethbAmount); - await oethbVault - .connect(nick) - .mint(weth.address, oethbAmount, oethbAmount); + await oethbVault.connect(nick).mint(oethbAmount); await oethb.connect(nick).approve(curvePool.address, oethbAmount); log( `Adding ${formatUnits( diff --git a/contracts/test/strategies/crosschain/cross-chain-strategy.js b/contracts/test/strategies/crosschain/cross-chain-strategy.js index 6815d238b2..bef6fdd50e 100644 --- a/contracts/test/strategies/crosschain/cross-chain-strategy.js +++ b/contracts/test/strategies/crosschain/cross-chain-strategy.js @@ -39,7 +39,7 @@ describe("ForkTest: CrossChainRemoteStrategy", function () { const mint = async (amount) => { await usdc.connect(josh).approve(vault.address, await units(amount, usdc)); - await vault.connect(josh).mint(usdc.address, await units(amount, usdc), 0); + await vault.connect(josh).mint(await units(amount, usdc)); }; const depositToMasterStrategy = async (amount) => { diff --git a/contracts/test/strategies/curve-amo-oeth.mainnet.fork-test.js b/contracts/test/strategies/curve-amo-oeth.mainnet.fork-test.js index bbcf6340b1..09b428b5de 100644 --- a/contracts/test/strategies/curve-amo-oeth.mainnet.fork-test.js +++ b/contracts/test/strategies/curve-amo-oeth.mainnet.fork-test.js @@ -90,7 +90,7 @@ describe("Curve AMO OETH strategy", function () { await setERC20TokenBalance(nick.address, weth, ousdUnits("5000000"), hre); await weth.connect(nick).approve(oethVault.address, ousdUnits("0")); await weth.connect(nick).approve(oethVault.address, ousdUnits("5000000")); - await oethVault.connect(nick).mint(weth.address, ousdUnits("2000000"), 0); + await oethVault.connect(nick).mint(ousdUnits("2000000")); await oeth .connect(nick) .approve(curvePool.address, ousdUnits("1000000000")); @@ -989,7 +989,7 @@ describe("Curve AMO OETH strategy", function () { await weth.connect(user).approve(oethVault.address, 0); await weth.connect(user).approve(oethVault.address, amount); - await oethVault.connect(user).mint(weth.address, amount, amount); + await oethVault.connect(user).mint(amount); const gov = await oethVault.governor(); const tx = await oethVault @@ -1023,9 +1023,7 @@ describe("Curve AMO OETH strategy", function () { await weth .connect(nick) .approve(oethVault.address, amount.mul(101).div(10)); - await oethVault - .connect(nick) - .mint(weth.address, amount.mul(101).div(10), amount); + await oethVault.connect(nick).mint(amount.mul(101).div(10)); await oeth.connect(nick).approve(curvePool.address, amount); // prettier-ignore await curvePool @@ -1088,9 +1086,7 @@ describe("Curve AMO OETH strategy", function () { } await weth.connect(nick).approve(oethVault.address, 0); await weth.connect(nick).approve(oethVault.address, ousdAmount.mul(2)); - await oethVault - .connect(nick) - .mint(weth.address, ousdAmount.mul(101).div(100), 0); + await oethVault.connect(nick).mint(ousdAmount.mul(101).div(100)); await oeth.connect(nick).approve(curvePool.address, ousdAmount); // prettier-ignore await curvePool diff --git a/contracts/test/strategies/curve-amo-ousd.mainnet.fork-test.js b/contracts/test/strategies/curve-amo-ousd.mainnet.fork-test.js index 0b4accf29f..4e326ab235 100644 --- a/contracts/test/strategies/curve-amo-ousd.mainnet.fork-test.js +++ b/contracts/test/strategies/curve-amo-ousd.mainnet.fork-test.js @@ -90,7 +90,7 @@ describe("Fork Test: Curve AMO OUSD strategy", function () { await setERC20TokenBalance(nick.address, usdc, "5000000", hre); await usdc.connect(nick).approve(ousdVault.address, usdcUnits("0")); await usdc.connect(nick).approve(ousdVault.address, usdcUnits("5000000")); - await ousdVault.connect(nick).mint(usdc.address, usdcUnits("2000000"), 0); + await ousdVault.connect(nick).mint(usdcUnits("2000000")); await ousd.connect(nick).approve(curvePool.address, ousdUnits("2000000")); await usdc.connect(nick).approve(curvePool.address, usdcUnits("2000000")); // prettier-ignore @@ -993,7 +993,7 @@ describe("Fork Test: Curve AMO OUSD strategy", function () { await usdc.connect(user).approve(ousdVault.address, 0); await usdc.connect(user).approve(ousdVault.address, amount); - await ousdVault.connect(user).mint(usdc.address, amount, amount); + await ousdVault.connect(user).mint(amount); const gov = await ousdVault.governor(); const tx = await ousdVault @@ -1027,9 +1027,7 @@ describe("Fork Test: Curve AMO OUSD strategy", function () { await usdc .connect(nick) .approve(ousdVault.address, amount.mul(101).div(10)); - await ousdVault - .connect(nick) - .mint(usdc.address, amount.mul(101).div(10), amount); + await ousdVault.connect(nick).mint(amount.mul(101).div(10)); await ousd.connect(nick).approve(curvePool.address, amount); // prettier-ignore await curvePool @@ -1094,7 +1092,7 @@ describe("Fork Test: Curve AMO OUSD strategy", function () { await usdc.connect(nick).approve(ousdVault.address, ousdAmount.mul(2)); await ousdVault .connect(nick) - .mint(usdc.address, ousdAmount.mul(101).div(100).div(1e12), 0); + .mint(ousdAmount.mul(101).div(100).div(1e12)); await ousd.connect(nick).approve(curvePool.address, ousdAmount); // prettier-ignore await curvePool diff --git a/contracts/test/strategies/sonic/swapx-amo.sonic.fork-test.js b/contracts/test/strategies/sonic/swapx-amo.sonic.fork-test.js index 0945b59fe2..5f4acaed0e 100644 --- a/contracts/test/strategies/sonic/swapx-amo.sonic.fork-test.js +++ b/contracts/test/strategies/sonic/swapx-amo.sonic.fork-test.js @@ -482,7 +482,7 @@ describe("Sonic ForkTest: SwapX AMO Strategy", function () { const osAmountIn = parseUnits("10000000"); // Mint OS using wS - await oSonicVault.connect(clement).mint(wS.address, osAmountIn, 0); + await oSonicVault.connect(clement).mint(osAmountIn); attackerBalanceBefore.os = await oSonic.balanceOf(clement.address); attackerBalanceBefore.ws = await wS.balanceOf(clement.address); @@ -830,7 +830,7 @@ describe("Sonic ForkTest: SwapX AMO Strategy", function () { // transfer wS to the pool await wS.connect(clement).transfer(swapXPool.address, bigAmount); // Mint OS with wS - await oSonicVault.connect(clement).mint(wS.address, bigAmount.mul(5), 0); + await oSonicVault.connect(clement).mint(bigAmount.mul(5)); // transfer OS to the pool await oSonic.connect(clement).transfer(swapXPool.address, bigAmount); // mint pool LP tokens @@ -1247,7 +1247,7 @@ describe("Sonic ForkTest: SwapX AMO Strategy", function () { wS, } = fixture; - await oSonicVault.connect(clement).mint(wS.address, wsDepositAmount, 0); + await oSonicVault.connect(clement).mint(wsDepositAmount); const dataBefore = await snapData(); await logSnapData(dataBefore, "\nBefore depositing wS to strategy"); diff --git a/contracts/test/token/oeth.mainnet.fork-test.js b/contracts/test/token/oeth.mainnet.fork-test.js index c83e066995..2c47da62e1 100644 --- a/contracts/test/token/oeth.mainnet.fork-test.js +++ b/contracts/test/token/oeth.mainnet.fork-test.js @@ -64,9 +64,7 @@ describe("ForkTest: OETH", function () { // Mint some OETH to the eip7702 user await weth.connect(eip770User).deposit({ value: ethUnits("13") }); await weth.connect(eip770User).approve(oethVault.address, ethUnits("3")); - await oethVault - .connect(eip770User) - .mint(weth.address, ethUnits("3"), ethUnits("0")); + await oethVault.connect(eip770User).mint(ethUnits("3")); // EIP7702 keep 1 OETH and transfer 1 OETH to a smart contract (USDC in this case) // and transfer 1 OETH to a wallet (josh in this case) diff --git a/contracts/test/token/ousd.js b/contracts/test/token/ousd.js index 8ac1708298..a92719c961 100644 --- a/contracts/test/token/ousd.js +++ b/contracts/test/token/ousd.js @@ -731,11 +731,7 @@ describe("Token", function () { vault.address, usdcUnits("100") ); - const tx = await mockNonRebasing.mintOusd( - vault.address, - usdc.address, - usdcUnits("50") - ); + const tx = await mockNonRebasing.mintOusd(vault.address, usdcUnits("50")); await expect(tx) .to.emit(ousd, "AccountRebasingDisabled") .withArgs(mockNonRebasing.address); @@ -771,11 +767,7 @@ describe("Token", function () { vault.address, usdcUnits("100") ); - await mockNonRebasing.mintOusd( - vault.address, - usdc.address, - usdcUnits("50") - ); + await mockNonRebasing.mintOusd(vault.address, usdcUnits("50")); expect(await ousd.totalSupply()).to.equal( totalSupplyBefore.add(ousdUnits("50")) ); @@ -791,11 +783,7 @@ describe("Token", function () { (await ousd.creditsBalanceOf(await josh.getAddress()))[1] ).to.not.equal(contractCreditsBalanceOf[1]); // Mint again - await mockNonRebasing.mintOusd( - vault.address, - usdc.address, - usdcUnits("50") - ); + await mockNonRebasing.mintOusd(vault.address, usdcUnits("50")); expect(await ousd.totalSupply()).to.equal( // Note 200 additional from simulated yield totalSupplyBefore.add(ousdUnits("100")).add(ousdUnits("200")) @@ -829,11 +817,7 @@ describe("Token", function () { vault.address, usdcUnits("100") ); - await mockNonRebasing.mintOusd( - vault.address, - usdc.address, - usdcUnits("50") - ); + await mockNonRebasing.mintOusd(vault.address, usdcUnits("50")); await expect(await ousd.totalSupply()).to.equal( totalSupplyBefore.add(ousdUnits("50")) ); diff --git a/contracts/test/token/woeth.js b/contracts/test/token/woeth.js index b4447d8726..5bc3730842 100644 --- a/contracts/test/token/woeth.js +++ b/contracts/test/token/woeth.js @@ -27,7 +27,7 @@ describe("WOETH", function () { // mint some OETH for (const user of [matt, josh]) { - await oethVault.connect(user).mint(weth.address, oethUnits("100"), 0); + await oethVault.connect(user).mint(oethUnits("100")); } // Josh wraps 50 OETH to WOETH diff --git a/contracts/test/token/woeth.mainnet.fork-test.js b/contracts/test/token/woeth.mainnet.fork-test.js index 6fe28f8248..b256f74fb3 100644 --- a/contracts/test/token/woeth.mainnet.fork-test.js +++ b/contracts/test/token/woeth.mainnet.fork-test.js @@ -8,12 +8,10 @@ const { oethUnits } = require("../helpers"); const oethWhaleFixture = async () => { const fixture = await simpleOETHFixture(); - const { weth, oeth, oethVault, woeth, domen } = fixture; + const { oeth, oethVault, woeth, domen } = fixture; // Domen is a OETH whale - await oethVault - .connect(domen) - .mint(weth.address, oethUnits("20000"), oethUnits("19999")); + await oethVault.connect(domen).mint(oethUnits("20000")); await oeth.connect(domen).approve(woeth.address, oethUnits("20000")); diff --git a/contracts/test/vault/deposit.js b/contracts/test/vault/deposit.js index e76c29f63b..2225336819 100644 --- a/contracts/test/vault/deposit.js +++ b/contracts/test/vault/deposit.js @@ -44,8 +44,7 @@ describe("Vault deposit pausing", function () { await vault.connect(governor).pauseCapital(); expect(await vault.connect(anna).capitalPaused()).to.be.true; await usdc.connect(anna).approve(vault.address, usdcUnits("50.0")); - await expect(vault.connect(anna).mint(usdc.address, usdcUnits("50.0"), 0)) - .to.be.reverted; + await expect(vault.connect(anna).mint(usdcUnits("50.0"))).to.be.reverted; }); it("Unpausing deposits allows mint", async () => { @@ -55,7 +54,7 @@ describe("Vault deposit pausing", function () { await vault.connect(governor).unpauseCapital(); expect(await vault.connect(anna).capitalPaused()).to.be.false; await usdc.connect(anna).approve(vault.address, usdcUnits("50.0")); - await vault.connect(anna).mint(usdc.address, usdcUnits("50.0"), 0); + await vault.connect(anna).mint(usdcUnits("50.0")); }); it("Deposit pause status can be read", async () => { diff --git a/contracts/test/vault/index.js b/contracts/test/vault/index.js index 86afdf8bfa..1b00de3067 100644 --- a/contracts/test/vault/index.js +++ b/contracts/test/vault/index.js @@ -43,7 +43,7 @@ describe("Vault", function () { // Matt deposits USDC, 6 decimals await usdc.connect(matt).approve(vault.address, usdcUnits("2.0")); - await vault.connect(matt).mint(usdc.address, usdcUnits("2.0"), 0); + await vault.connect(matt).mint(usdcUnits("2.0")); await expect(matt).has.a.balanceOf("102.00", ousd); }); @@ -52,7 +52,7 @@ describe("Vault", function () { await expect(anna).has.a.balanceOf("0.00", ousd); await usdc.connect(anna).approve(vault.address, usdcUnits("50.0")); - await vault.connect(anna).mint(usdc.address, usdcUnits("50.0"), 0); + await vault.connect(anna).mint(usdcUnits("50.0")); await expect(anna).has.a.balanceOf("50.00", ousd); }); @@ -61,7 +61,7 @@ describe("Vault", function () { // Matt deposits USDC, 6 decimals await usdc.connect(matt).approve(vault.address, usdcUnits("2.0")); - await vault.connect(matt).mint(usdc.address, usdcUnits("2.0"), 0); + await vault.connect(matt).mint(usdcUnits("2.0")); // Fixture loads 200 USDS, so result should be 202 expect(await vault.totalValue()).to.equal(utils.parseUnits("202", 18)); }); @@ -71,7 +71,7 @@ describe("Vault", function () { // Matt deposits USDC, 6 decimals await usdc.connect(matt).approve(vault.address, usdcUnits("8.0")); - await vault.connect(matt).mint(usdc.address, usdcUnits("8.0"), 0); + await vault.connect(matt).mint(usdcUnits("8.0")); // Matt sends his OUSD directly to Vault await ousd.connect(matt).transfer(vault.address, ousdUnits("8.0")); // Matt asks Governor for help @@ -121,7 +121,7 @@ describe("Vault", function () { await expect(matt).has.a.balanceOf("100.00", ousd); await usdc.connect(anna).mint(usdcUnits("5000.0")); await usdc.connect(anna).approve(vault.address, usdcUnits("5000.0")); - await vault.connect(anna).mint(usdc.address, usdcUnits("5000.0"), 0); + await vault.connect(anna).mint(usdcUnits("5000.0")); await expect(anna).has.a.balanceOf("5000.00", ousd); await expect(matt).has.a.balanceOf("100.00", ousd); }); @@ -131,7 +131,7 @@ describe("Vault", function () { // Matt deposits USDC, 6 decimals await usdc.connect(matt).approve(vault.address, usdcUnits("8.0")); - await vault.connect(matt).mint(usdc.address, usdcUnits("8.0"), 0); + await vault.connect(matt).mint(usdcUnits("8.0")); // Matt sends his OUSD directly to Vault await ousd.connect(matt).transfer(vault.address, ousdUnits("8.0")); // Matt asks Governor for help @@ -183,7 +183,7 @@ describe("Vault", function () { // Send all USDC to Compound await vault.connect(governor).setDefaultStrategy(mockStrategy.address); await usdc.connect(josh).approve(vault.address, usdcUnits("200")); - await vault.connect(josh).mint(usdc.address, usdcUnits("200"), 0); + await vault.connect(josh).mint(usdcUnits("200")); await vault.connect(governor).allocate(); await vault @@ -210,7 +210,7 @@ describe("Vault", function () { // Send all USDC to Compound await vault.connect(governor).setDefaultStrategy(mockStrategy.address); await usdc.connect(josh).approve(vault.address, usdcUnits("200")); - await vault.connect(josh).mint(usdc.address, usdcUnits("200"), 0); + await vault.connect(josh).mint(usdcUnits("200")); await vault.connect(governor).allocate(); await vault @@ -259,7 +259,7 @@ describe("Vault", function () { await vault.connect(governor).setDefaultStrategy(mockStrategy.address); await usdc.connect(josh).approve(vault.address, usdcUnits("90")); - await vault.connect(josh).mint(usdc.address, usdcUnits("90"), 0); + await vault.connect(josh).mint(usdcUnits("90")); await vault.connect(governor).allocate(); await vault @@ -332,7 +332,7 @@ describe("Vault", function () { // Mint and allocate USDC to Compound. await vault.connect(governor).setDefaultStrategy(mockStrategy.address); await usdc.connect(josh).approve(vault.address, usdcUnits("200")); - await vault.connect(josh).mint(usdc.address, usdcUnits("200"), 0); + await vault.connect(josh).mint(usdcUnits("200")); await vault.connect(governor).allocate(); // Call to withdrawAll by the governor should go thru. diff --git a/contracts/test/vault/oeth-vault.js b/contracts/test/vault/oeth-vault.js index f27fa02b90..5f7df04757 100644 --- a/contracts/test/vault/oeth-vault.js +++ b/contracts/test/vault/oeth-vault.js @@ -84,13 +84,9 @@ describe("OETH Vault", function () { const dataBefore = await snapData(fixtureWithUser); const amount = parseUnits("1", 18); - const minOeth = parseUnits("0.8", 18); - await weth.connect(josh).approve(oethVault.address, amount); - const tx = await oethVault - .connect(josh) - .mint(weth.address, amount, minOeth); + const tx = await oethVault.connect(josh).mint(amount); await expect(tx) .to.emit(oethVault, "Mint") @@ -115,21 +111,19 @@ describe("OETH Vault", function () { }); it("Fail to mint if amount is zero", async () => { - const { oethVault, weth, josh } = fixture; + const { oethVault, josh } = fixture; - const tx = oethVault.connect(josh).mint(weth.address, "0", "0"); + const tx = oethVault.connect(josh).mint("0"); await expect(tx).to.be.revertedWith("Amount must be greater than 0"); }); it("Fail to mint if capital is paused", async () => { - const { oethVault, weth, governor } = fixture; + const { oethVault, governor } = fixture; await oethVault.connect(governor).pauseCapital(); expect(await oethVault.capitalPaused()).to.equal(true); - const tx = oethVault - .connect(governor) - .mint(weth.address, oethUnits("10"), "0"); + const tx = oethVault.connect(governor).mint(oethUnits("10")); await expect(tx).to.be.revertedWith("Capital paused"); }); @@ -148,7 +142,7 @@ describe("OETH Vault", function () { // Mint some WETH await weth.connect(domen).approve(oethVault.address, oethUnits("10000")); const mintAmount = oethUnits("100"); - await oethVault.connect(domen).mint(weth.address, mintAmount, "0"); + await oethVault.connect(domen).mint(mintAmount); expect(await weth.balanceOf(mockStrategy.address)).to.eq( mintAmount, @@ -177,7 +171,7 @@ describe("OETH Vault", function () { describe("async withdrawal", () => { it("Should update total supply correctly", async () => { const { oethVault, oeth, weth, daniel } = fixture; - await oethVault.connect(daniel).mint(weth.address, oethUnits("10"), "0"); + await oethVault.connect(daniel).mint(oethUnits("10")); const userBalanceBefore = await weth.balanceOf(daniel.address); const vaultBalanceBefore = await weth.balanceOf(oethVault.address); @@ -208,10 +202,10 @@ describe("OETH Vault", function () { // Mint some WETH await weth.connect(domen).approve(oethVault.address, oethUnits("10000")); - await oethVault.connect(domen).mint(weth.address, oethUnits("100"), "0"); + await oethVault.connect(domen).mint(oethUnits("100")); // Mint a small amount that won't get allocated to the strategy - await oethVault.connect(domen).mint(weth.address, oethUnits("1.23"), "0"); + await oethVault.connect(domen).mint(oethUnits("1.23")); // Withdraw something more than what the Vault holds await oethVault.connect(domen).requestWithdrawal(oethUnits("12.55")); @@ -230,7 +224,7 @@ describe("OETH Vault", function () { it("Should allow every user to withdraw", async () => { const { oethVault, weth, daniel } = fixture; - await oethVault.connect(daniel).mint(weth.address, oethUnits("10"), "0"); + await oethVault.connect(daniel).mint(oethUnits("10")); await oethVault.connect(daniel).requestWithdrawal(oethUnits("10")); await advanceTime(10 * 60); // 10 minutes @@ -311,9 +305,7 @@ describe("OETH Vault", function () { .approve(oethVault.address, oethUnits("10")); // Then mint for strategy - await oethVault - .connect(strategySigner) - .mint(weth.address, oethUnits("10"), "0"); + await oethVault.connect(strategySigner).mint(oethUnits("10")); await expect(await oeth.balanceOf(mockStrategy.address)).to.equal( oethUnits("10") @@ -373,7 +365,7 @@ describe("OETH Vault", function () { describe("Allocate", () => { const delayPeriod = 10 * 60; // 10mins it("Shouldn't allocate as minted amount is lower than autoAllocateThreshold", async () => { - const { oethVault, weth, daniel } = fixture; + const { oethVault, daniel } = fixture; // Set auto allocate threshold to 100 WETH await oethVault @@ -381,14 +373,12 @@ describe("OETH Vault", function () { .setAutoAllocateThreshold(oethUnits("100")); // Mint for 10 WETH - const tx = oethVault - .connect(daniel) - .mint(weth.address, oethUnits("10"), "0"); + const tx = oethVault.connect(daniel).mint(oethUnits("10")); await expect(tx).to.not.emit(oethVault, "AssetAllocated"); }); it("Shouldn't allocate as no WETH available", async () => { - const { oethVault, weth, daniel } = fixture; + const { oethVault, daniel } = fixture; // Deploy default strategy const mockStrategy = await deployWithConfirmation("MockStrategy"); @@ -400,19 +390,17 @@ describe("OETH Vault", function () { .setDefaultStrategy(mockStrategy.address); // Mint will allocate all to default strategy bc no buffer, no threshold - await oethVault.connect(daniel).mint(weth.address, oethUnits("10"), "0"); + await oethVault.connect(daniel).mint(oethUnits("10")); await oethVault.connect(daniel).requestWithdrawal(oethUnits("5")); // Deposit less than queued amount (5 WETH) => _wethAvailable() return 0 - const tx = oethVault - .connect(daniel) - .mint(weth.address, oethUnits("3", "0")); + const tx = oethVault.connect(daniel).mint(oethUnits("3")); expect(tx).to.not.emit(oethVault, "AssetAllocated"); }); it("Shouldn't allocate as WETH available is lower than buffer", async () => { - const { oethVault, weth, daniel } = fixture; + const { oethVault, daniel } = fixture; - await oethVault.connect(daniel).mint(weth.address, oethUnits("100"), "0"); + await oethVault.connect(daniel).mint(oethUnits("100")); // Set vault buffer to 5% await oethVault @@ -422,15 +410,13 @@ describe("OETH Vault", function () { // OETH total supply = 100(first deposit) + 5(second deposit) = 105 // Buffer = 105 * 5% = 5.25 WETH // Second deposit should remain in the vault as below vault buffer - const tx = oethVault.connect(daniel).mint(oethUnits("5"), "0"); + const tx = oethVault.connect(daniel).mint(oethUnits("5")); expect(tx).to.not.emit(oethVault, "AssetAllocated"); }); it("Shouldn't allocate as default strategy is address null", async () => { - const { oethVault, weth, daniel } = fixture; + const { oethVault, daniel } = fixture; - const tx = oethVault - .connect(daniel) - .mint(weth.address, oethUnits("100"), "0"); + const tx = oethVault.connect(daniel).mint(oethUnits("100")); expect(tx).to.not.emit(oethVault, "AssetAllocated"); }); @@ -449,9 +435,7 @@ describe("OETH Vault", function () { }); it("buffer is 0%, 0 WETH in queue", async () => { const { oethVault, daniel, weth } = fixture; - const tx = oethVault - .connect(daniel) - .mint(weth.address, oethUnits("10"), "0"); + const tx = oethVault.connect(daniel).mint(oethUnits("10")); await expect(tx) .to.emit(oethVault, "AssetAllocated") .withArgs(weth.address, mockStrategy.address, oethUnits("10")); @@ -466,9 +450,7 @@ describe("OETH Vault", function () { .connect(await impersonateAndFund(await oethVault.governor())) .setVaultBuffer(oethUnits("0.05")); - const tx = oethVault - .connect(daniel) - .mint(weth.address, oethUnits("10"), "0"); + const tx = oethVault.connect(daniel).mint(oethUnits("10")); await expect(tx) .to.emit(oethVault, "AssetAllocated") .withArgs(weth.address, mockStrategy.address, oethUnits("9.5")); @@ -478,13 +460,9 @@ describe("OETH Vault", function () { }); it("buffer is 0%, 10 WETH in queue", async () => { const { oethVault, daniel, weth } = fixture; - await oethVault - .connect(daniel) - .mint(weth.address, oethUnits("10"), "0"); + await oethVault.connect(daniel).mint(oethUnits("10")); await oethVault.connect(daniel).requestWithdrawal(oethUnits("10")); - const tx = oethVault - .connect(daniel) - .mint(weth.address, oethUnits("20"), "0"); + const tx = oethVault.connect(daniel).mint(oethUnits("20")); // 10 WETH in the queue, 10 WETH in strat. New deposit of 20, only 10 WETH available to allocate to strategy. await expect(tx) @@ -496,9 +474,7 @@ describe("OETH Vault", function () { }); it("buffer is 0%, 20 WETH in queue, 10 WETH claimed", async () => { const { oethVault, daniel, weth } = fixture; - await oethVault - .connect(daniel) - .mint(weth.address, oethUnits("30"), "0"); + await oethVault.connect(daniel).mint(oethUnits("30")); await oethVault.connect(daniel).requestWithdrawal(oethUnits("10")); await advanceTime(delayPeriod); // Simulate strategist pulling back WETH to the vault. @@ -512,9 +488,7 @@ describe("OETH Vault", function () { // So far, 20 WETH queued, 10 WETH claimed, 0 WETH available, 20 WETH in strat // Deposit 35 WETH, 10 WETH should remain in the vault for withdraw, so strat should have 45WETH. - const tx = oethVault - .connect(daniel) - .mint(weth.address, oethUnits("35"), "0"); + const tx = oethVault.connect(daniel).mint(oethUnits("35")); await expect(tx) .to.emit(oethVault, "AssetAllocated") .withArgs(weth.address, mockStrategy.address, oethUnits("25")); @@ -525,9 +499,7 @@ describe("OETH Vault", function () { }); it("buffer is 5%, 20 WETH in queue, 10 WETH claimed", async () => { const { oethVault, daniel, weth } = fixture; - await oethVault - .connect(daniel) - .mint(weth.address, oethUnits("40"), "0"); + await oethVault.connect(daniel).mint(oethUnits("40")); await oethVault.connect(daniel).requestWithdrawal(oethUnits("10")); await advanceTime(delayPeriod); // Simulate strategist pulling back WETH to the vault. @@ -547,9 +519,7 @@ describe("OETH Vault", function () { // Deposit 40 WETH, 10 WETH should remain in the vault for withdraw + 3 (i.e. 20+40 *5%) // So strat should have 57WETH. - const tx = oethVault - .connect(daniel) - .mint(weth.address, oethUnits("40"), "0"); + const tx = oethVault.connect(daniel).mint(oethUnits("40")); await expect(tx) .to.emit(oethVault, "AssetAllocated") .withArgs(weth.address, mockStrategy.address, oethUnits("27")); @@ -565,13 +535,11 @@ describe("OETH Vault", function () { const delayPeriod = 10 * 60; // 10 minutes describe("with all 60 WETH in the vault", () => { beforeEach(async () => { - const { oethVault, weth, daniel, josh, matt } = fixture; + const { oethVault, daniel, josh, matt } = fixture; // Mint some OETH to three users - await oethVault - .connect(daniel) - .mint(weth.address, oethUnits("10"), "0"); - await oethVault.connect(josh).mint(weth.address, oethUnits("20"), "0"); - await oethVault.connect(matt).mint(weth.address, oethUnits("30"), "0"); + await oethVault.connect(daniel).mint(oethUnits("10")); + await oethVault.connect(josh).mint(oethUnits("20")); + await oethVault.connect(matt).mint(oethUnits("30")); await oethVault .connect(await impersonateAndFund(await oethVault.governor())) .setMaxSupplyDiff(oethUnits("0.03")); @@ -1221,7 +1189,7 @@ describe("OETH Vault", function () { await oethVault.connect(matt).claimWithdrawal(2); }); it("Fail to claim a new request after mint with NOT enough liquidity", async () => { - const { oethVault, daniel, matt, weth } = fixture; + const { oethVault, daniel, matt } = fixture; // Matt requests all 30 OETH to be withdrawn which is not enough liquidity const requestAmount = oethUnits("30"); @@ -1230,7 +1198,7 @@ describe("OETH Vault", function () { // WETH in the vault = 60 - 15 = 45 WETH // unallocated WETH in the Vault = 45 - 23 = 22 WETH // Add another 6 WETH so the unallocated WETH is 22 + 6 = 28 WETH - await oethVault.connect(daniel).mint(weth.address, oethUnits("6"), 0); + await oethVault.connect(daniel).mint(oethUnits("6")); await advanceTime(delayPeriod); // Advance in time to ensure time delay between request and claim. @@ -1238,7 +1206,7 @@ describe("OETH Vault", function () { await expect(tx).to.be.revertedWith("Queue pending liquidity"); }); it("Should claim a new request after mint adds enough liquidity", async () => { - const { oethVault, daniel, matt, weth } = fixture; + const { oethVault, daniel, matt } = fixture; // Set the claimable amount to the queued amount await oethVault.addWithdrawalQueueLiquidity(); @@ -1254,7 +1222,7 @@ describe("OETH Vault", function () { // unallocated WETH in the Vault = 45 - 23 = 22 WETH // Add another 8 WETH so the unallocated WETH is 22 + 8 = 30 WETH const mintAmount = oethUnits("8"); - await oethVault.connect(daniel).mint(weth.address, mintAmount, 0); + await oethVault.connect(daniel).mint(mintAmount); await assertChangedData( dataBeforeMint, @@ -1310,12 +1278,10 @@ describe("OETH Vault", function () { const { governor, oethVault, weth, daniel, domen, josh, matt } = fixture; // Mint 105 OETH to four users - await oethVault - .connect(daniel) - .mint(weth.address, oethUnits("15"), "0"); - await oethVault.connect(josh).mint(weth.address, oethUnits("20"), "0"); - await oethVault.connect(matt).mint(weth.address, oethUnits("30"), "0"); - await oethVault.connect(domen).mint(weth.address, oethUnits("40"), "0"); + await oethVault.connect(daniel).mint(oethUnits("15")); + await oethVault.connect(josh).mint(oethUnits("20")); + await oethVault.connect(matt).mint(oethUnits("30")); + await oethVault.connect(domen).mint(oethUnits("40")); await oethVault .connect(await impersonateAndFund(await oethVault.governor())) .setMaxSupplyDiff(oethUnits("0.03")); @@ -1434,10 +1400,8 @@ describe("OETH Vault", function () { }); describe("when mint covers exactly outstanding requests (32 - 15 = 17 OETH)", () => { beforeEach(async () => { - const { oethVault, daniel, weth } = fixture; - await oethVault - .connect(daniel) - .mint(weth.address, oethUnits("17"), "0"); + const { oethVault, daniel } = fixture; + await oethVault.connect(daniel).mint(oethUnits("17")); // Advance in time to ensure time delay between request and claim. await advanceTime(delayPeriod); @@ -1499,10 +1463,8 @@ describe("OETH Vault", function () { }); describe("when mint covers exactly outstanding requests and vault buffer (17 + 1 WETH)", () => { beforeEach(async () => { - const { oethVault, daniel, weth } = fixture; - await oethVault - .connect(daniel) - .mint(weth.address, oethUnits("18"), "0"); + const { oethVault, daniel } = fixture; + await oethVault.connect(daniel).mint(oethUnits("18")); }); it("Should deposit 1 WETH to a strategy which is the vault buffer", async () => { const { oethVault, weth, governor } = fixture; @@ -1542,10 +1504,8 @@ describe("OETH Vault", function () { }); describe("when mint more than covers outstanding requests and vault buffer (17 + 1 + 3 = 21 OETH)", () => { beforeEach(async () => { - const { oethVault, daniel, weth } = fixture; - await oethVault - .connect(daniel) - .mint(weth.address, oethUnits("21"), "0"); + const { oethVault, daniel } = fixture; + await oethVault.connect(daniel).mint(oethUnits("21")); }); it("Should deposit 4 WETH to a strategy", async () => { const { oethVault, weth, governor } = fixture; @@ -1606,14 +1566,12 @@ describe("OETH Vault", function () { }); describe("with 40 WETH in the queue, 10 WETH in the vault, 30 WETH already claimed", () => { beforeEach(async () => { - const { oethVault, weth, daniel, josh, matt } = fixture; + const { oethVault, daniel, josh, matt } = fixture; // Mint 60 OETH to three users - await oethVault - .connect(daniel) - .mint(weth.address, oethUnits("10"), "0"); - await oethVault.connect(josh).mint(weth.address, oethUnits("20"), "0"); - await oethVault.connect(matt).mint(weth.address, oethUnits("10"), "0"); + await oethVault.connect(daniel).mint(oethUnits("10")); + await oethVault.connect(josh).mint(oethUnits("20")); + await oethVault.connect(matt).mint(oethUnits("10")); // Request and claim 10 WETH from Vault await oethVault.connect(daniel).requestWithdrawal(oethUnits("10")); @@ -1690,13 +1648,11 @@ describe("OETH Vault", function () { }); describe("with 40 WETH in the queue, 100 WETH in the vault, 0 WETH in the strategy", () => { beforeEach(async () => { - const { oethVault, weth, daniel, josh, matt } = fixture; + const { oethVault, daniel, josh, matt } = fixture; // Mint 100 OETH to three users - await oethVault - .connect(daniel) - .mint(weth.address, oethUnits("10"), "0"); - await oethVault.connect(josh).mint(weth.address, oethUnits("20"), "0"); - await oethVault.connect(matt).mint(weth.address, oethUnits("70"), "0"); + await oethVault.connect(daniel).mint(oethUnits("10")); + await oethVault.connect(josh).mint(oethUnits("20")); + await oethVault.connect(matt).mint(oethUnits("70")); // Request 40 WETH from Vault await oethVault.connect(matt).requestWithdrawal(oethUnits("40")); @@ -1808,11 +1764,9 @@ describe("OETH Vault", function () { .setDefaultStrategy(mockStrategy.address); // Mint 60 OETH to three users - await oethVault - .connect(daniel) - .mint(weth.address, oethUnits("10"), "0"); - await oethVault.connect(josh).mint(weth.address, oethUnits("20"), "0"); - await oethVault.connect(matt).mint(weth.address, oethUnits("30"), "0"); + await oethVault.connect(daniel).mint(oethUnits("10")); + await oethVault.connect(josh).mint(oethUnits("20")); + await oethVault.connect(matt).mint(oethUnits("30")); // Request and claim 10 + 20 + 10 = 40 WETH from Vault await oethVault.connect(daniel).requestWithdrawal(oethUnits("10")); @@ -1968,11 +1922,9 @@ describe("OETH Vault", function () { .setDefaultStrategy(mockStrategy.address); // Mint 100 OETH to three users - await oethVault - .connect(daniel) - .mint(weth.address, oethUnits("20"), "0"); - await oethVault.connect(josh).mint(weth.address, oethUnits("30"), "0"); - await oethVault.connect(matt).mint(weth.address, oethUnits("50"), "0"); + await oethVault.connect(daniel).mint(oethUnits("20")); + await oethVault.connect(josh).mint(oethUnits("30")); + await oethVault.connect(matt).mint(oethUnits("50")); // Request and claim 20 + 30 + 49 = 99 WETH from Vault await oethVault.connect(daniel).requestWithdrawal(oethUnits("20")); diff --git a/contracts/test/vault/oeth-vault.mainnet.fork-test.js b/contracts/test/vault/oeth-vault.mainnet.fork-test.js index b62f977477..6048c7bfc0 100644 --- a/contracts/test/vault/oeth-vault.mainnet.fork-test.js +++ b/contracts/test/vault/oeth-vault.mainnet.fork-test.js @@ -48,13 +48,10 @@ describe("ForkTest: OETH Vault", function () { const { oethVault, weth, josh } = fixture; const amount = parseUnits("1", 18); - const minOeth = parseUnits("0.8", 18); await weth.connect(josh).approve(oethVault.address, amount); - const tx = await oethVault - .connect(josh) - .mint(weth.address, amount, minOeth); + const tx = await oethVault.connect(josh).mint(amount); await logTxDetails(tx, "mint"); @@ -67,13 +64,10 @@ describe("ForkTest: OETH Vault", function () { const { oethVault, weth, josh } = fixture; const amount = parseUnits("11", 18); - const minOeth = parseUnits("8", 18); await weth.connect(josh).approve(oethVault.address, amount); - const tx = await oethVault - .connect(josh) - .mint(weth.address, amount, minOeth); + const tx = await oethVault.connect(josh).mint(amount); await logTxDetails(tx, "mint"); @@ -86,13 +80,10 @@ describe("ForkTest: OETH Vault", function () { const { oethVault, weth, josh } = fixture; const amount = parseUnits("11", 18); - const minOeth = parseUnits("8", 18); await weth.connect(josh).approve(oethVault.address, amount); - const tx = await oethVault - .connect(josh) - .mint(weth.address, amount, minOeth); + const tx = await oethVault.connect(josh).mint(amount); await logTxDetails(tx, "mint"); @@ -219,7 +210,7 @@ describe("ForkTest: OETH Vault", function () { if (queue > claimed) { const diff = queue.sub(claimed).mul(110).div(100); await weth.connect(depositor).approve(oethVault.address, diff); - return await oethVault.connect(depositor).mint(weth.address, diff, 0); + return await oethVault.connect(depositor).mint(diff); } return null; } diff --git a/contracts/test/vault/oethb-vault.base.fork-test.js b/contracts/test/vault/oethb-vault.base.fork-test.js index 8232b5c937..ccca6bec32 100644 --- a/contracts/test/vault/oethb-vault.base.fork-test.js +++ b/contracts/test/vault/oethb-vault.base.fork-test.js @@ -18,7 +18,7 @@ describe("ForkTest: OETHb Vault", function () { const { weth, oethbVault } = fixture; await weth.connect(signer).deposit({ value: oethUnits("1") }); await weth.connect(signer).approve(oethbVault.address, oethUnits("1")); - await oethbVault.connect(signer).mint(weth.address, oethUnits("1"), "0"); + await oethbVault.connect(signer).mint(oethUnits("1")); } describe("Mint & Permissioned redeems", function () { @@ -61,9 +61,7 @@ describe("ForkTest: OETHb Vault", function () { await weth .connect(rafael) .approve(oethbVault.address, oethUnits("10000")); - await oethbVault - .connect(rafael) - .mint(weth.address, oethUnits("10000"), 0); + await oethbVault.connect(rafael).mint(oethUnits("10000")); const delayPeriod = await oethbVault.withdrawalClaimDelay(); diff --git a/contracts/test/vault/oethp-vault.plume.fork-test.js b/contracts/test/vault/oethp-vault.plume.fork-test.js index ddacf04732..42090df6ae 100644 --- a/contracts/test/vault/oethp-vault.plume.fork-test.js +++ b/contracts/test/vault/oethp-vault.plume.fork-test.js @@ -15,10 +15,10 @@ describe("ForkTest: OETHp Vault", function () { }); async function _mint(signer, amount = oethUnits("1")) { - const { weth, oethpVault, _mintWETH } = fixture; + const { weth, oethpVault, oethpVaultLegacy, _mintWETH } = fixture; await _mintWETH(signer, amount); await weth.connect(signer).approve(oethpVault.address, amount); - await oethpVault.connect(signer).mint(weth.address, amount, "0"); + await oethpVaultLegacy.connect(signer).mint(weth.address, amount, amount); } describe("Mint & Permissioned redeems", function () { @@ -28,9 +28,11 @@ describe("ForkTest: OETHp Vault", function () { }); it("Should not allow anyone else to mint", async () => { - const { nick, weth, oethpVault } = fixture; + const { nick, weth, oethpVaultLegacy } = fixture; await expect( - oethpVault.connect(nick).mint(weth.address, oethUnits("1"), "0") + oethpVaultLegacy + .connect(nick) + .mint(weth.address, oethUnits("1"), oethUnits("1")) ).to.be.revertedWith("Caller is not the Strategist or Governor"); }); @@ -79,7 +81,9 @@ describe("ForkTest: OETHp Vault", function () { const userBalanceBefore = await oethp.balanceOf(strategist.address); const totalSupplyBefore = await oethp.totalSupply(); - await oethpVault.connect(strategist).redeem(oethUnits("1"), "0"); + await fixture.oethpVaultLegacy + .connect(strategist) + .redeem(oethUnits("1"), "0"); const vaultBalanceAfter = await weth.balanceOf(oethpVault.address); const userBalanceAfter = await oethp.balanceOf(strategist.address); @@ -111,7 +115,9 @@ describe("ForkTest: OETHp Vault", function () { const userBalanceBefore = await oethp.balanceOf(governor.address); const totalSupplyBefore = await oethp.totalSupply(); - await oethpVault.connect(governor).redeem(oethUnits("1"), "0"); + await fixture.oethpVaultLegacy + .connect(governor) + .redeem(oethUnits("1"), "0"); const vaultBalanceAfter = await weth.balanceOf(oethpVault.address); const userBalanceAfter = await oethp.balanceOf(governor.address); diff --git a/contracts/test/vault/os-vault.sonic.js b/contracts/test/vault/os-vault.sonic.js index 3214c3c7b8..e67c14b2ed 100644 --- a/contracts/test/vault/os-vault.sonic.js +++ b/contracts/test/vault/os-vault.sonic.js @@ -135,13 +135,10 @@ describe("Origin S Vault", function () { const dataBefore = await snapData(fixtureWithUser); const mintAmount = parseUnits("1", 18); - const minOS = parseUnits("0.8", 18); await wS.connect(nick).approve(oSonicVault.address, mintAmount); - const tx = await oSonicVault - .connect(nick) - .mint(wS.address, mintAmount, minOS); + const tx = await oSonicVault.connect(nick).mint(mintAmount); await expect(tx) .to.emit(oSonicVault, "Mint") @@ -171,7 +168,7 @@ describe("Origin S Vault", function () { // Mint some OSonic const mintAmount = parseUnits("100", 18); await wS.connect(nick).approve(oSonicVault.address, mintAmount); - await oSonicVault.connect(nick).mint(wS.address, mintAmount, 0); + await oSonicVault.connect(nick).mint(mintAmount); const fixtureWithUser = { ...fixture, user: nick }; const dataBefore = await snapData(fixtureWithUser); @@ -210,7 +207,7 @@ describe("Origin S Vault", function () { // Mint some OSonic const mintAmount = parseUnits("100", 18); await wS.connect(nick).approve(oSonicVault.address, mintAmount); - await oSonicVault.connect(nick).mint(wS.address, mintAmount, 0); + await oSonicVault.connect(nick).mint(mintAmount); const withdrawAmount = parseUnits("90", 18); await oSonicVault.connect(nick).requestWithdrawal(withdrawAmount); @@ -250,7 +247,7 @@ describe("Origin S Vault", function () { // Mint some OSonic const mintAmount = parseUnits("100", 18); await wS.connect(nick).approve(oSonicVault.address, mintAmount); - await oSonicVault.connect(nick).mint(wS.address, mintAmount, 0); + await oSonicVault.connect(nick).mint(mintAmount); const withdrawAmount1 = parseUnits("10", 18); const withdrawAmount2 = parseUnits("20", 18); const withdrawAmount = withdrawAmount1.add(withdrawAmount2); diff --git a/contracts/test/vault/rebase.js b/contracts/test/vault/rebase.js index d21d87d5bb..2f28f31900 100644 --- a/contracts/test/vault/rebase.js +++ b/contracts/test/vault/rebase.js @@ -124,7 +124,7 @@ describe("Vault rebase", () => { await expect(anna).has.a.balanceOf("0", ousd); await usdc.connect(anna).approve(vault.address, usdcUnits("50")); - await vault.connect(anna).mint(usdc.address, usdcUnits("50"), 0); + await vault.connect(anna).mint(usdcUnits("50")); await expect(anna).has.a.balanceOf("50", ousd); }); }); diff --git a/contracts/test/vault/redeem.js b/contracts/test/vault/redeem.js index 60cbb2647e..3700764a08 100644 --- a/contracts/test/vault/redeem.js +++ b/contracts/test/vault/redeem.js @@ -105,9 +105,9 @@ describe("OUSD Vault Withdrawals", function () { await usdc.connect(matt).approve(vault.address, usdcUnits("30")); // Mint some OUSD to three users - await vault.connect(daniel).mint(usdc.address, usdcUnits("10"), "0"); - await vault.connect(josh).mint(usdc.address, usdcUnits("20"), "0"); - await vault.connect(matt).mint(usdc.address, usdcUnits("30"), "0"); + await vault.connect(daniel).mint(usdcUnits("10")); + await vault.connect(josh).mint(usdcUnits("20")); + await vault.connect(matt).mint(usdcUnits("30")); // Set max supply diff to 3% to allow withdrawals await vault @@ -784,7 +784,7 @@ describe("OUSD Vault Withdrawals", function () { await usdc .connect(daniel) .approve(vault.address, ousdUnits("6").div(1e12)); - await vault.connect(daniel).mint(usdc.address, usdcUnits("6"), 0); + await vault.connect(daniel).mint(usdcUnits("6")); await advanceTime(delayPeriod); // Advance in time to ensure time delay between request and claim. @@ -811,9 +811,7 @@ describe("OUSD Vault Withdrawals", function () { await usdc .connect(daniel) .approve(vault.address, mintAmount.div(1e12)); - await vault - .connect(daniel) - .mint(usdc.address, mintAmount.div(1e12), 0); + await vault.connect(daniel).mint(mintAmount.div(1e12)); await assertChangedData( dataBeforeMint, @@ -880,10 +878,10 @@ describe("OUSD Vault Withdrawals", function () { await usdc.connect(domen).approve(vault.address, usdcUnits("40")); // Mint 105 OUSD to four users - await vault.connect(daniel).mint(usdc.address, usdcUnits("15"), "0"); - await vault.connect(josh).mint(usdc.address, usdcUnits("20"), "0"); - await vault.connect(matt).mint(usdc.address, usdcUnits("30"), "0"); - await vault.connect(domen).mint(usdc.address, usdcUnits("40"), "0"); + await vault.connect(daniel).mint(usdcUnits("15")); + await vault.connect(josh).mint(usdcUnits("20")); + await vault.connect(matt).mint(usdcUnits("30")); + await vault.connect(domen).mint(usdcUnits("40")); await vault .connect(await impersonateAndFund(await vault.governor())) .setMaxSupplyDiff(ousdUnits("0.03")); @@ -1006,7 +1004,7 @@ describe("OUSD Vault Withdrawals", function () { const { vault, daniel, usdc } = fixture; await usdc.mintTo(daniel.address, usdcUnits("17")); await usdc.connect(daniel).approve(vault.address, usdcUnits("17")); - await vault.connect(daniel).mint(usdc.address, usdcUnits("17"), "0"); + await vault.connect(daniel).mint(usdcUnits("17")); // Advance in time to ensure time delay between request and claim. await advanceTime(delayPeriod); @@ -1071,7 +1069,7 @@ describe("OUSD Vault Withdrawals", function () { const { vault, daniel, usdc } = fixture; await usdc.mintTo(daniel.address, usdcUnits("18")); await usdc.connect(daniel).approve(vault.address, usdcUnits("18")); - await vault.connect(daniel).mint(usdc.address, usdcUnits("18"), "0"); + await vault.connect(daniel).mint(usdcUnits("18")); }); it("Should deposit 1 USDC to a strategy which is the vault buffer", async () => { const { vault, usdc, governor } = fixture; @@ -1114,7 +1112,7 @@ describe("OUSD Vault Withdrawals", function () { const { vault, daniel, usdc } = fixture; await usdc.mintTo(daniel.address, usdcUnits("21")); await usdc.connect(daniel).approve(vault.address, usdcUnits("21")); - await vault.connect(daniel).mint(usdc.address, usdcUnits("21"), "0"); + await vault.connect(daniel).mint(usdcUnits("21")); }); it("Should deposit 4 USDC to a strategy", async () => { const { vault, usdc, governor } = fixture; @@ -1188,9 +1186,9 @@ describe("OUSD Vault Withdrawals", function () { await usdc.connect(matt).approve(vault.address, usdcUnits("10")); // Mint 60 OUSD to three users - await vault.connect(daniel).mint(usdc.address, usdcUnits("10"), "0"); - await vault.connect(josh).mint(usdc.address, usdcUnits("20"), "0"); - await vault.connect(matt).mint(usdc.address, usdcUnits("10"), "0"); + await vault.connect(daniel).mint(usdcUnits("10")); + await vault.connect(josh).mint(usdcUnits("20")); + await vault.connect(matt).mint(usdcUnits("10")); // Request and claim 10 USDC from Vault await vault.connect(daniel).requestWithdrawal(ousdUnits("10")); @@ -1279,9 +1277,9 @@ describe("OUSD Vault Withdrawals", function () { await usdc.connect(matt).approve(vault.address, usdcUnits("70")); // Mint 100 OUSD to three users - await vault.connect(daniel).mint(usdc.address, usdcUnits("10"), "0"); - await vault.connect(josh).mint(usdc.address, usdcUnits("20"), "0"); - await vault.connect(matt).mint(usdc.address, usdcUnits("70"), "0"); + await vault.connect(daniel).mint(usdcUnits("10")); + await vault.connect(josh).mint(usdcUnits("20")); + await vault.connect(matt).mint(usdcUnits("70")); // Request 40 USDC from Vault await vault.connect(matt).requestWithdrawal(ousdUnits("40")); @@ -1401,9 +1399,9 @@ describe("OUSD Vault Withdrawals", function () { await usdc.connect(matt).approve(vault.address, usdcUnits("30")); // Mint 60 OUSD to three users - await vault.connect(daniel).mint(usdc.address, usdcUnits("10"), "0"); - await vault.connect(josh).mint(usdc.address, usdcUnits("20"), "0"); - await vault.connect(matt).mint(usdc.address, usdcUnits("30"), "0"); + await vault.connect(daniel).mint(usdcUnits("10")); + await vault.connect(josh).mint(usdcUnits("20")); + await vault.connect(matt).mint(usdcUnits("30")); await vault.allocate(); // Request and claim 10 + 20 + 10 = 40 USDC from Vault @@ -1568,9 +1566,9 @@ describe("OUSD Vault Withdrawals", function () { await usdc.connect(matt).approve(vault.address, usdcUnits("50")); // Mint 100 OUSD to three users - await vault.connect(daniel).mint(usdc.address, usdcUnits("20"), "0"); - await vault.connect(josh).mint(usdc.address, usdcUnits("30"), "0"); - await vault.connect(matt).mint(usdc.address, usdcUnits("50"), "0"); + await vault.connect(daniel).mint(usdcUnits("20")); + await vault.connect(josh).mint(usdcUnits("30")); + await vault.connect(matt).mint(usdcUnits("50")); await vault.allocate(); diff --git a/contracts/test/vault/vault.mainnet.fork-test.js b/contracts/test/vault/vault.mainnet.fork-test.js index 81a6b77ca1..8e73abbff6 100644 --- a/contracts/test/vault/vault.mainnet.fork-test.js +++ b/contracts/test/vault/vault.mainnet.fork-test.js @@ -108,11 +108,11 @@ describe("ForkTest: Vault", function () { }); it("Should allow to mint w/ USDC", async () => { - const { ousd, vault, josh, usdc } = fixture; + const { ousd, vault, josh } = fixture; const balancePreMint = await ousd .connect(josh) .balanceOf(josh.getAddress()); - await vault.connect(josh).mint(usdc.address, usdcUnits("500"), 0); + await vault.connect(josh).mint(usdcUnits("500")); const balancePostMint = await ousd .connect(josh) @@ -125,9 +125,9 @@ describe("ForkTest: Vault", function () { it("should withdraw from and deposit to strategy", async () => { const { vault, josh, usdc, morphoOUSDv2Strategy } = fixture; // Mint a lot more in case there are outstanding withdrawals - await vault.connect(josh).mint(usdc.address, usdcUnits("500000"), 0); + await vault.connect(josh).mint(usdcUnits("500000")); // The next mint should all be deposited into the strategy - await vault.connect(josh).mint(usdc.address, usdcUnits("90"), 0); + await vault.connect(josh).mint(usdcUnits("90")); const strategistSigner = await impersonateAndFund( await vault.strategistAddr() ); diff --git a/contracts/test/vault/vault.sonic.fork-test.js b/contracts/test/vault/vault.sonic.fork-test.js index 526f1e1273..ed8d2c2ed1 100644 --- a/contracts/test/vault/vault.sonic.fork-test.js +++ b/contracts/test/vault/vault.sonic.fork-test.js @@ -100,11 +100,11 @@ describe("ForkTest: Sonic Vault", function () { }); it("Should allow to mint w/ Wrapped S (wS)", async () => { - const { oSonic, oSonicVault, nick, wS } = fixture; + const { oSonic, oSonicVault, nick } = fixture; const balancePreMint = await oSonic .connect(nick) .balanceOf(nick.getAddress()); - await oSonicVault.connect(nick).mint(wS.address, parseUnits("1000"), 0); + await oSonicVault.connect(nick).mint(parseUnits("1000")); const balancePostMint = await oSonic .connect(nick) @@ -115,15 +115,13 @@ describe("ForkTest: Sonic Vault", function () { }); it.skip("should automatically deposit to staking strategy", async () => { - const { oSonicVault, nick, wS, sonicStakingStrategy } = fixture; + const { oSonicVault, nick, sonicStakingStrategy } = fixture; // Clear any wS out of the Vault first await oSonicVault.allocate(); const mintAmount = parseUnits("5000000"); - const tx = await oSonicVault - .connect(nick) - .mint(wS.address, mintAmount, 0); + const tx = await oSonicVault.connect(nick).mint(mintAmount); // Check mint event await expect(tx) @@ -138,7 +136,7 @@ describe("ForkTest: Sonic Vault", function () { const { oSonicVault, nick, wS, sonicStakingStrategy } = fixture; const depositAmount = parseUnits("2000"); - await oSonicVault.connect(nick).mint(wS.address, depositAmount, 0); + await oSonicVault.connect(nick).mint(depositAmount); const strategistSigner = await impersonateAndFund( await oSonicVault.strategistAddr() ); @@ -163,10 +161,10 @@ describe("ForkTest: Sonic Vault", function () { }); it("should call withdraw all from staking strategy even if all delegated", async () => { - const { oSonicVault, nick, wS, sonicStakingStrategy } = fixture; + const { oSonicVault, nick, sonicStakingStrategy } = fixture; const depositAmount = parseUnits("2000"); - await oSonicVault.connect(nick).mint(wS.address, depositAmount, 0); + await oSonicVault.connect(nick).mint(depositAmount); const strategistSigner = await impersonateAndFund( await oSonicVault.strategistAddr() ); @@ -181,7 +179,7 @@ describe("ForkTest: Sonic Vault", function () { it("should call withdraw all from staking strategy with wrapped S in it", async () => { const { oSonicVault, nick, wS, sonicStakingStrategy } = fixture; const depositAmount = parseUnits("2000"); - await oSonicVault.connect(nick).mint(wS.address, depositAmount, 0); + await oSonicVault.connect(nick).mint(depositAmount); const strategistSigner = await impersonateAndFund( await oSonicVault.strategistAddr() ); @@ -204,7 +202,7 @@ describe("ForkTest: Sonic Vault", function () { it("should call withdraw all from staking strategy with native S in it", async () => { const { oSonicVault, nick, wS, sonicStakingStrategy } = fixture; const depositAmount = parseUnits("2000"); - await oSonicVault.connect(nick).mint(wS.address, depositAmount, 0); + await oSonicVault.connect(nick).mint(depositAmount); const strategistSigner = await impersonateAndFund( await oSonicVault.strategistAddr() ); From 5b39e8a9ce85769c0fdcde2bae7043d7c25694ec Mon Sep 17 00:00:00 2001 From: Domen Grabec Date: Sat, 28 Feb 2026 21:10:14 +0100 Subject: [PATCH 30/36] rename --- ...n_strategy_proxies.js => 179_crosschain_strategy_proxies.js} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename contracts/deploy/mainnet/{168_crosschain_strategy_proxies.js => 179_crosschain_strategy_proxies.js} (93%) diff --git a/contracts/deploy/mainnet/168_crosschain_strategy_proxies.js b/contracts/deploy/mainnet/179_crosschain_strategy_proxies.js similarity index 93% rename from contracts/deploy/mainnet/168_crosschain_strategy_proxies.js rename to contracts/deploy/mainnet/179_crosschain_strategy_proxies.js index a33b711cf0..f70d8ecf07 100644 --- a/contracts/deploy/mainnet/168_crosschain_strategy_proxies.js +++ b/contracts/deploy/mainnet/179_crosschain_strategy_proxies.js @@ -3,7 +3,7 @@ const { deployProxyWithCreateX } = require("../deployActions"); module.exports = deploymentWithGovernanceProposal( { - deployName: "168_crosschain_strategy_proxies", + deployName: "179_crosschain_strategy_proxies", forceDeploy: false, reduceQueueTime: true, deployerIsProposer: false, From 350a10296f1a7e648368af9b61d7381eaf15c45d Mon Sep 17 00:00:00 2001 From: Domen Grabec Date: Mon, 2 Mar 2026 18:02:01 +0100 Subject: [PATCH 31/36] add agents.md to ignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 127e456554..83ad641d0e 100644 --- a/.gitignore +++ b/.gitignore @@ -90,3 +90,6 @@ unit-coverage # Certora # .certora_internal + +# Possible Agent.md file +AGENTS.md \ No newline at end of file From 09be7d062451139b57ce46f7c26f5c7d0054ace8 Mon Sep 17 00:00:00 2001 From: Domen Grabec Date: Wed, 4 Mar 2026 10:26:08 +0100 Subject: [PATCH 32/36] Update contracts/test/strategies/ousd-v2-morpho.mainnet.fork-test.js MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Clément <55331875+clement-ux@users.noreply.github.com> --- contracts/test/strategies/ousd-v2-morpho.mainnet.fork-test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/test/strategies/ousd-v2-morpho.mainnet.fork-test.js b/contracts/test/strategies/ousd-v2-morpho.mainnet.fork-test.js index 27b91bc6fd..f9ffb0b0a1 100644 --- a/contracts/test/strategies/ousd-v2-morpho.mainnet.fork-test.js +++ b/contracts/test/strategies/ousd-v2-morpho.mainnet.fork-test.js @@ -245,7 +245,7 @@ describe("ForkTest: Yearn's Morpho OUSD v2 Strategy", function () { log("Before withdraw all from strategy"); - // Now try to withdraw all the WETH from the strategy + // Now try to withdraw all the USDC from the strategy const tx = await morphoOUSDv2Strategy.connect(vaultSigner).withdrawAll(); log("After withdraw all from strategy"); From 0d8fe2e3058f4665a2ef49661b46e2e8a264412b Mon Sep 17 00:00:00 2001 From: Domen Grabec Date: Wed, 4 Mar 2026 10:28:13 +0100 Subject: [PATCH 33/36] natspec fix --- contracts/contracts/strategies/MorphoV2Strategy.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/contracts/strategies/MorphoV2Strategy.sol b/contracts/contracts/strategies/MorphoV2Strategy.sol index e9017400a8..9bac02adcc 100644 --- a/contracts/contracts/strategies/MorphoV2Strategy.sol +++ b/contracts/contracts/strategies/MorphoV2Strategy.sol @@ -36,12 +36,12 @@ contract MorphoV2Strategy is Generalized4626Strategy { /** * @notice Remove all the liquidity that is available in the Morpho V2 vault. - Which might not be all of the liquidity owned by the strategy. + * Which might not be all of the liquidity owned by the strategy. * @dev Remove all the liquidity that is available in the Morpho V2 vault * The particular behaviour of the Morpho V2 vault is that it can hold * multiple Morpho V1 vaults as adapters but only one liquidity adapter. * The immediate available funds on the Morpho V2 vault are therfore any - * any liquid assets residing on the Vault V2 contract and the maxWithdraw + * liquid assets residing on the Vault V2 contract and the maxWithdraw * amount that the Morpho V1 contract can supply. */ function withdrawAll() From 464a8cec1ccb6cc04cce63ff3ef4bf4f1645fdf7 Mon Sep 17 00:00:00 2001 From: Domen Grabec Date: Wed, 4 Mar 2026 10:41:29 +0100 Subject: [PATCH 34/36] include the USDC on the contract as well --- contracts/utils/morpho.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/contracts/utils/morpho.js b/contracts/utils/morpho.js index 6e8d76408b..2abb288a65 100644 --- a/contracts/utils/morpho.js +++ b/contracts/utils/morpho.js @@ -5,7 +5,7 @@ const { getBlock } = require("../tasks/block"); const morphoV1VaultAbi = require("../abi/morphoV1Vault.json"); const { resolveContract } = require("./resolvers"); - +const erc20Abi = require("../abi/erc20.json"); const log = require("../utils/logger")("utils:morpho"); async function canWithdrawAllFromMorphoOUSD() { @@ -24,7 +24,7 @@ async function morphoWithdrawShortfall() { "Generalized4626Strategy" ); - const maxWithdrawal = await morphoOUSDv1Vault.maxWithdraw( + let maxWithdrawal = await morphoOUSDv1Vault.maxWithdraw( addresses.mainnet.MorphoOUSDv2Adapter ); @@ -33,6 +33,13 @@ async function morphoWithdrawShortfall() { addresses.mainnet.USDC ); + const usdc = await ethers.getContractAt( + erc20Abi, + addresses.mainnet.USDC + ); + const vaultUSDCBalance = await usdc.balanceOf(addresses.mainnet.MorphoOUSDv2Vault); + + maxWithdrawal = maxWithdrawal.add(vaultUSDCBalance); log( `Morpho OUSD v2 Strategy USDC balance: ${formatUnits( strategyUSDCBalance, From f2a2b916aaca61a7d483e2f96885fa5aa4603ad7 Mon Sep 17 00:00:00 2001 From: Domen Grabec Date: Wed, 4 Mar 2026 10:42:32 +0100 Subject: [PATCH 35/36] Sparrow dom/morpho v2 withdraw all library (#2818) * create a library for calculating withdrawAll * add unit test support to mimic Morpho Vault liquidity adapters * add fork tests for calling withdrawAll and don't fail when calling withdrawAll twice * remove console log * add test for miscofigured adapter --- .../contracts/mocks/MockMorphoV1Vault.sol | 14 +++++ .../MockMorphoV1VaultLiquidityAdapter.sol | 20 ++++++++ .../contracts/strategies/MorphoV2Strategy.sol | 43 ++++------------ .../strategies/MorphoV2VaultUtils.sol | 38 ++++++++++++++ .../crosschain/CrossChainRemoteStrategy.sol | 20 ++++++-- .../base/045_crosschain_upgrade_remote.js | 51 +++++++++++++++++++ contracts/deploy/deployActions.js | 2 +- contracts/deploy/mainnet/000_mock.js | 7 +++ .../179_crosschain_strategy_proxies.js | 25 --------- ...=> 179_upgrade_ousd_morpho_v2_strategy.js} | 5 +- contracts/test/_fixture.js | 12 ++++- .../crosschain/cross-chain-strategy.js | 30 +++++++++++ ...osschain-remote-strategy.base.fork-test.js | 45 ++++++++++++++++ 13 files changed, 245 insertions(+), 67 deletions(-) create mode 100644 contracts/contracts/mocks/MockMorphoV1Vault.sol create mode 100644 contracts/contracts/mocks/MockMorphoV1VaultLiquidityAdapter.sol create mode 100644 contracts/contracts/strategies/MorphoV2VaultUtils.sol create mode 100644 contracts/deploy/base/045_crosschain_upgrade_remote.js delete mode 100644 contracts/deploy/mainnet/179_crosschain_strategy_proxies.js rename contracts/deploy/mainnet/{168_upgrade_ousd_morpho_v2_strategy.js => 179_upgrade_ousd_morpho_v2_strategy.js} (87%) diff --git a/contracts/contracts/mocks/MockMorphoV1Vault.sol b/contracts/contracts/mocks/MockMorphoV1Vault.sol new file mode 100644 index 0000000000..23614a052f --- /dev/null +++ b/contracts/contracts/mocks/MockMorphoV1Vault.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import { MockERC4626Vault } from "./MockERC4626Vault.sol"; + +contract MockMorphoV1Vault is MockERC4626Vault { + address public liquidityAdapter; + + constructor(address _asset) MockERC4626Vault(_asset) {} + + function setLiquidityAdapter(address _liquidityAdapter) external { + liquidityAdapter = _liquidityAdapter; + } +} diff --git a/contracts/contracts/mocks/MockMorphoV1VaultLiquidityAdapter.sol b/contracts/contracts/mocks/MockMorphoV1VaultLiquidityAdapter.sol new file mode 100644 index 0000000000..9d87b9d423 --- /dev/null +++ b/contracts/contracts/mocks/MockMorphoV1VaultLiquidityAdapter.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import { IMorphoV2Adapter } from "../interfaces/morpho/IMorphoV2Adapter.sol"; + +contract MockMorphoV1VaultLiquidityAdapter is IMorphoV2Adapter { + address public mockMorphoVault; + + function setMockMorphoVault(address _mockMorphoVault) external { + mockMorphoVault = _mockMorphoVault; + } + + function morphoVaultV1() external view override returns (address) { + return mockMorphoVault; + } + + function parentVault() external view override returns (address) { + return mockMorphoVault; + } +} diff --git a/contracts/contracts/strategies/MorphoV2Strategy.sol b/contracts/contracts/strategies/MorphoV2Strategy.sol index 9bac02adcc..a963398e53 100644 --- a/contracts/contracts/strategies/MorphoV2Strategy.sol +++ b/contracts/contracts/strategies/MorphoV2Strategy.sol @@ -7,32 +7,20 @@ pragma solidity ^0.8.0; * @author Origin Protocol Inc */ import { Generalized4626Strategy } from "./Generalized4626Strategy.sol"; +import { MorphoV2VaultUtils } from "./MorphoV2VaultUtils.sol"; import { IVaultV2 } from "../interfaces/morpho/IVaultV2.sol"; -import { IERC4626 } from "../../lib/openzeppelin/interfaces/IERC4626.sol"; -import { IMorphoV2Adapter } from "../interfaces/morpho/IMorphoV2Adapter.sol"; import { Math } from "@openzeppelin/contracts/utils/math/Math.sol"; contract MorphoV2Strategy is Generalized4626Strategy { - IMorphoV2Adapter public immutable expectedAdapter; - /** * @param _baseConfig Base strategy config with Morpho V2 Vault and * vaultAddress (OToken Vault contract), eg VaultProxy or OETHVaultProxy * @param _assetToken Address of the ERC-4626 asset token. e.g. USDC - * @param _expectedAdapter The Morpho V1 marketplace adapter address that is expected to offer - * additional liquidity for the Morpho V2 vault. The nature of the Morpho V2 vaults is that the - * adapter can be switched for another (type of) adapter. Which could point to Morpho V2 vault, - * or future V2 Market not yet introduced. The `withdrawAll` function of this strategy only supports - * Morpho V1 vault as the underlying liquidity source. For that reason the expected adapter must be - * confirmed to be a Morpho V1 adapter. */ - constructor(BaseStrategyConfig memory _baseConfig, address _assetToken, address _expectedAdapter) + constructor(BaseStrategyConfig memory _baseConfig, address _assetToken) Generalized4626Strategy(_baseConfig, _assetToken) - { - require(_expectedAdapter != address(0), "Expected adapter must be set"); - expectedAdapter = IMorphoV2Adapter(_expectedAdapter); - } + {} /** * @notice Remove all the liquidity that is available in the Morpho V2 vault. @@ -52,14 +40,13 @@ contract MorphoV2Strategy is Generalized4626Strategy { nonReentrant { uint256 availableMorphoVault = _maxWithdraw(); - - uint256 strategyAssetBalance = checkBalance(address(assetToken)); uint256 balanceToWithdraw = Math.min( availableMorphoVault, - strategyAssetBalance + checkBalance(address(assetToken)) ); if (balanceToWithdraw > 0) { + // slither-disable-next-line unused-return IVaultV2(platformAddress).withdraw( balanceToWithdraw, vaultAddress, @@ -83,21 +70,9 @@ contract MorphoV2Strategy is Generalized4626Strategy { view returns (uint256 availableAssetLiquidity) { - availableAssetLiquidity = assetToken.balanceOf(platformAddress); - - address liquidityAdapter = IVaultV2(platformAddress).liquidityAdapter(); - // This strategy can only safely calculate and withdraw additional liquidity when - // the liquidity adapter is set to the expected Morpho V1 Vault adapter. If that configuration - // changes the additional available liquidity can not be calculated anymore. - if ( - liquidityAdapter == address(expectedAdapter) - ) { - // adapter representing one Morpho V1 vault - address underlyingVault = IMorphoV2Adapter(liquidityAdapter) - .morphoVaultV1(); - availableAssetLiquidity += IERC4626(underlyingVault).maxWithdraw( - liquidityAdapter - ); - } + availableAssetLiquidity = MorphoV2VaultUtils.maxWithdrawableAssets( + platformAddress, + address(assetToken) + ); } } diff --git a/contracts/contracts/strategies/MorphoV2VaultUtils.sol b/contracts/contracts/strategies/MorphoV2VaultUtils.sol new file mode 100644 index 0000000000..02fcad5bd7 --- /dev/null +++ b/contracts/contracts/strategies/MorphoV2VaultUtils.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import { IERC20 } from "../utils/InitializableAbstractStrategy.sol"; +import { IERC4626 } from "../../lib/openzeppelin/interfaces/IERC4626.sol"; +import { IVaultV2 } from "../interfaces/morpho/IVaultV2.sol"; +import { IMorphoV2Adapter } from "../interfaces/morpho/IMorphoV2Adapter.sol"; + +library MorphoV2VaultUtils { + error IncompatibleAdapter(address adapter); + + /** + * @notice Return maximum amount that can be safely withdrawn from a Morpho V2 vault. + * @dev Available liquidity is: + * 1) asset balance parked on Morpho V2 vault contract + * 2) additional liquidity from the active adapter if it resolves to a Morpho V1 vault + * and, when provided, matches the expected adapter + */ + function maxWithdrawableAssets( + address platformAddress, + address assetToken + ) internal view returns (uint256 availableAssetLiquidity) { + availableAssetLiquidity = IERC20(assetToken).balanceOf(platformAddress); + + address liquidityAdapter = IVaultV2(platformAddress).liquidityAdapter(); + // this is a sufficient check to ensure the adapter is Morpho V1 + try IMorphoV2Adapter(liquidityAdapter).morphoVaultV1() returns ( + address underlyingVault + ) { + availableAssetLiquidity += IERC4626(underlyingVault).maxWithdraw( + liquidityAdapter + ); + } catch { + revert IncompatibleAdapter(liquidityAdapter); + } + } + +} diff --git a/contracts/contracts/strategies/crosschain/CrossChainRemoteStrategy.sol b/contracts/contracts/strategies/crosschain/CrossChainRemoteStrategy.sol index 6902c66ade..aa1c10e224 100644 --- a/contracts/contracts/strategies/crosschain/CrossChainRemoteStrategy.sol +++ b/contracts/contracts/strategies/crosschain/CrossChainRemoteStrategy.sol @@ -11,13 +11,16 @@ pragma solidity ^0.8.0; */ import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import { Math } from "@openzeppelin/contracts/utils/math/Math.sol"; import { IERC20 } from "../../utils/InitializableAbstractStrategy.sol"; import { IERC4626 } from "../../../lib/openzeppelin/interfaces/IERC4626.sol"; +import { IVaultV2 } from "../../interfaces/morpho/IVaultV2.sol"; import { Generalized4626Strategy } from "../Generalized4626Strategy.sol"; import { AbstractCCTPIntegrator } from "./AbstractCCTPIntegrator.sol"; import { CrossChainStrategyHelper } from "./CrossChainStrategyHelper.sol"; import { InitializableAbstractStrategy } from "../../utils/InitializableAbstractStrategy.sol"; import { Strategizable } from "../../governance/Strategizable.sol"; +import { MorphoV2VaultUtils } from "../MorphoV2VaultUtils.sol"; contract CrossChainRemoteStrategy is AbstractCCTPIntegrator, @@ -140,11 +143,22 @@ contract CrossChainRemoteStrategy is nonReentrant { IERC4626 platform = IERC4626(platformAddress); - _withdraw( - address(this), - usdcToken, + uint256 availableMorphoVault = MorphoV2VaultUtils.maxWithdrawableAssets( + platformAddress, + usdcToken + ); + uint256 amountToWithdraw = Math.min( + availableMorphoVault, platform.previewRedeem(platform.balanceOf(address(this))) ); + + if (amountToWithdraw > 0) { + _withdraw( + address(this), + usdcToken, + amountToWithdraw + ); + } } /// @inheritdoc AbstractCCTPIntegrator diff --git a/contracts/deploy/base/045_crosschain_upgrade_remote.js b/contracts/deploy/base/045_crosschain_upgrade_remote.js new file mode 100644 index 0000000000..c0fcf4179d --- /dev/null +++ b/contracts/deploy/base/045_crosschain_upgrade_remote.js @@ -0,0 +1,51 @@ +const { deployOnBase } = require("../../utils/deploy-l2"); +const addresses = require("../../utils/addresses"); +const { + deployCrossChainRemoteStrategyImpl, + getCreate2ProxyAddress, +} = require("../deployActions"); +const { cctpDomainIds } = require("../../utils/cctp"); + +module.exports = deployOnBase( + { + deployName: "045_crosschain_upgrade_remote", + }, + async () => { + const crossChainStrategyProxyAddress = await getCreate2ProxyAddress( + "CrossChainStrategyProxy" + ); + console.log( + `CrossChainStrategyProxy address: ${crossChainStrategyProxyAddress}` + ); + + const implAddress = await deployCrossChainRemoteStrategyImpl( + addresses.base.MorphoOusdV2Vault, // 4626 Vault + crossChainStrategyProxyAddress, + cctpDomainIds.Ethereum, + crossChainStrategyProxyAddress, + addresses.base.USDC, + addresses.mainnet.USDC, + "CrossChainRemoteStrategy", + addresses.CCTPTokenMessengerV2, + addresses.CCTPMessageTransmitterV2, + addresses.base.timelock, + false + ); + console.log(`CrossChainRemoteStrategyImpl address: ${implAddress}`); + + const cCrossChainStrategyProxy = await ethers.getContractAt( + "CrossChainStrategyProxy", + crossChainStrategyProxyAddress + ); + + return { + actions: [ + { + contract: cCrossChainStrategyProxy, + signature: "upgradeTo(address)", + args: [implAddress], + }, + ], + }; + } +); diff --git a/contracts/deploy/deployActions.js b/contracts/deploy/deployActions.js index 7de245e46c..60b96e7665 100644 --- a/contracts/deploy/deployActions.js +++ b/contracts/deploy/deployActions.js @@ -1131,7 +1131,7 @@ const deployCrossChainUnitTestStrategy = async (usdcAddress) => { "CCTPMessageTransmitterMock" ); const tokenMessenger = await ethers.getContract("CCTPTokenMessengerMock"); - const c4626Vault = await ethers.getContract("MockERC4626Vault"); + const c4626Vault = await ethers.getContract("MockMorphoV1Vault"); await deployCrossChainMasterStrategyImpl( dMasterProxy.address, diff --git a/contracts/deploy/mainnet/000_mock.js b/contracts/deploy/mainnet/000_mock.js index 29760f1dc4..924c0a9cf6 100644 --- a/contracts/deploy/mainnet/000_mock.js +++ b/contracts/deploy/mainnet/000_mock.js @@ -114,6 +114,13 @@ const deployMocks = async ({ getNamedAccounts, deployments }) => { from: deployerAddr, args: [usdc.address], }); + await deploy("MockMorphoV1Vault", { + from: deployerAddr, + args: [usdc.address], + }); + await deploy("MockMorphoV1VaultLiquidityAdapter", { + from: deployerAddr, + }); // const tokenMessenger = await ethers.getContract("CCTPTokenMessengerMock"); // await messageTransmitter // .connect(sDeployer) diff --git a/contracts/deploy/mainnet/179_crosschain_strategy_proxies.js b/contracts/deploy/mainnet/179_crosschain_strategy_proxies.js deleted file mode 100644 index f70d8ecf07..0000000000 --- a/contracts/deploy/mainnet/179_crosschain_strategy_proxies.js +++ /dev/null @@ -1,25 +0,0 @@ -const { deploymentWithGovernanceProposal } = require("../../utils/deploy"); -const { deployProxyWithCreateX } = require("../deployActions"); - -module.exports = deploymentWithGovernanceProposal( - { - deployName: "179_crosschain_strategy_proxies", - forceDeploy: false, - reduceQueueTime: true, - deployerIsProposer: false, - proposalId: "", - }, - async () => { - // the salt needs to match the salt on the base chain deploying the other part of the strategy - const salt = "Morpho V2 Crosschain Strategy 1"; - const proxyAddress = await deployProxyWithCreateX( - salt, - "CrossChainStrategyProxy" - ); - console.log(`CrossChainStrategyProxy address: ${proxyAddress}`); - - return { - actions: [], - }; - } -); diff --git a/contracts/deploy/mainnet/168_upgrade_ousd_morpho_v2_strategy.js b/contracts/deploy/mainnet/179_upgrade_ousd_morpho_v2_strategy.js similarity index 87% rename from contracts/deploy/mainnet/168_upgrade_ousd_morpho_v2_strategy.js rename to contracts/deploy/mainnet/179_upgrade_ousd_morpho_v2_strategy.js index 124c1a21e1..e35d24a6a8 100644 --- a/contracts/deploy/mainnet/168_upgrade_ousd_morpho_v2_strategy.js +++ b/contracts/deploy/mainnet/179_upgrade_ousd_morpho_v2_strategy.js @@ -3,7 +3,7 @@ const { deploymentWithGovernanceProposal } = require("../../utils/deploy"); module.exports = deploymentWithGovernanceProposal( { - deployName: "168_upgrade_ousd_morpho_v2_strategy", + deployName: "179_upgrade_ousd_morpho_v2_strategy", forceDeploy: false, reduceQueueTime: true, deployerIsProposer: false, @@ -19,8 +19,7 @@ module.exports = deploymentWithGovernanceProposal( "MorphoV2Strategy", [ [addresses.mainnet.MorphoOUSDv2Vault, cVaultProxy.address], - addresses.mainnet.USDC, - addresses.mainnet.MorphoOUSDv2Adapter, + addresses.mainnet.USDC ] ); diff --git a/contracts/test/_fixture.js b/contracts/test/_fixture.js index 28aba6a6e8..c3c89d211d 100644 --- a/contracts/test/_fixture.js +++ b/contracts/test/_fixture.js @@ -1304,7 +1304,16 @@ async function crossChainFixtureUnit() { .connect(governor) .setOperator(messageTransmitter.address); - const morphoVault = await ethers.getContract("MockERC4626Vault"); + const morphoVault = await ethers.getContract("MockMorphoV1Vault"); + const morphoVaultLiquidityAdapter = await ethers.getContract( + "MockMorphoV1VaultLiquidityAdapter" + ); + await morphoVault + .connect(governor) + .setLiquidityAdapter(morphoVaultLiquidityAdapter.address); + await morphoVaultLiquidityAdapter + .connect(governor) + .setMockMorphoVault(morphoVault.address); // Impersonate the OUSD Vault fixture.vaultSigner = await impersonateAndFund(vault.address); @@ -1324,6 +1333,7 @@ async function crossChainFixtureUnit() { messageTransmitter: messageTransmitter, tokenMessenger: tokenMessenger, morphoVault: morphoVault, + morphoVaultLiquidityAdapter: morphoVaultLiquidityAdapter, }; } diff --git a/contracts/test/strategies/crosschain/cross-chain-strategy.js b/contracts/test/strategies/crosschain/cross-chain-strategy.js index bef6fdd50e..4702d408bf 100644 --- a/contracts/test/strategies/crosschain/cross-chain-strategy.js +++ b/contracts/test/strategies/crosschain/cross-chain-strategy.js @@ -89,6 +89,33 @@ describe("ForkTest: CrossChainRemoteStrategy", function () { await crossChainRemoteStrategy.connect(governor).sendBalanceUpdate(); }; + it("Should wire morpho vault and liquidity adapter in fixture", async function () { + const { morphoVault, morphoVaultLiquidityAdapter } = fixture; + await expect(await morphoVault.liquidityAdapter()).to.eq( + morphoVaultLiquidityAdapter.address + ); + await expect(await morphoVaultLiquidityAdapter.morphoVaultV1()).to.eq( + morphoVault.address + ); + await expect(await morphoVaultLiquidityAdapter.parentVault()).to.eq( + morphoVault.address + ); + }); + + it("Should revert withdrawAll when morpho vault liquidity adapter is incompatible", async function () { + const { morphoVault } = fixture; + + // Misconfigure adapter to an invalid value that does not implement IMorphoV2Adapter. + await morphoVault + .connect(governor) + .setLiquidityAdapter(morphoVault.address); + + await expect(crossChainRemoteStrategy.connect(governor).withdrawAll()) + .to.be.revertedWithCustomError( + "IncompatibleAdapter(address)" + ); + }); + // Checks the diff in the total expected value in the vault // (plus accompanying strategy value) const assertVaultTotalValue = async (amountExpected) => { @@ -340,6 +367,9 @@ describe("ForkTest: CrossChainRemoteStrategy", function () { await expect( await crossChainRemoteStrategy.checkBalance(usdc.address) ).to.eq(await units("0", usdc)); + + // calling withdrawAll a second time should not fail + await directWithdrawAllFromRemoteStrategy(); }); it("Should fail when a withdrawal too large is requested on the remote strategy", async function () { diff --git a/contracts/test/strategies/crosschain/crosschain-remote-strategy.base.fork-test.js b/contracts/test/strategies/crosschain/crosschain-remote-strategy.base.fork-test.js index d6c754a06e..b2ed8d3a15 100644 --- a/contracts/test/strategies/crosschain/crosschain-remote-strategy.base.fork-test.js +++ b/contracts/test/strategies/crosschain/crosschain-remote-strategy.base.fork-test.js @@ -249,6 +249,51 @@ describe("ForkTest: CrossChainRemoteStrategy", function () { expect(balanceAfter).to.approxEqual(expectedBalance); }); + it("Should handle single withdrawAll", async function () { + const { crossChainRemoteStrategy, strategist, rafael, usdc } = fixture; + const fundedAmount = usdcUnits("1234.56"); + + const usdcBalanceBefore = await usdc.balanceOf( + crossChainRemoteStrategy.address + ); + + await usdc + .connect(rafael) + .transfer(crossChainRemoteStrategy.address, fundedAmount); + + await expect(crossChainRemoteStrategy.connect(strategist).withdrawAll()).to + .not.be.reverted; + + const usdcBalanceAfter = await usdc.balanceOf( + crossChainRemoteStrategy.address + ); + expect(usdcBalanceAfter).to.gte(usdcBalanceBefore.add(fundedAmount)); + }); + + it("Should allow calling withdrawAll twice", async function () { + const { crossChainRemoteStrategy, strategist, rafael, usdc } = fixture; + const fundedAmount = usdcUnits("1234.56"); + + await usdc + .connect(rafael) + .transfer(crossChainRemoteStrategy.address, fundedAmount); + + await expect(crossChainRemoteStrategy.connect(strategist).withdrawAll()).to + .not.be.reverted; + + const usdcBalanceAfterFirst = await usdc.balanceOf( + crossChainRemoteStrategy.address + ); + + await expect(crossChainRemoteStrategy.connect(strategist).withdrawAll()).to + .not.be.reverted; + + const usdcBalanceAfterSecond = await usdc.balanceOf( + crossChainRemoteStrategy.address + ); + expect(usdcBalanceAfterSecond).to.eq(usdcBalanceAfterFirst); + }); + it("Should revert if the burn token is not peer USDC", async function () { const { crossChainRemoteStrategy, relayer } = fixture; From 9e1f685211184df78446ea607614164c849f1cc6 Mon Sep 17 00:00:00 2001 From: Domen Grabec Date: Wed, 4 Mar 2026 16:51:18 +0100 Subject: [PATCH 36/36] set harvester to multichain strategist --- .../mainnet/179_upgrade_ousd_morpho_v2_strategy.js | 10 ++++++++++ .../strategies/ousd-v2-morpho.mainnet.fork-test.js | 3 +++ 2 files changed, 13 insertions(+) diff --git a/contracts/deploy/mainnet/179_upgrade_ousd_morpho_v2_strategy.js b/contracts/deploy/mainnet/179_upgrade_ousd_morpho_v2_strategy.js index e35d24a6a8..810103568c 100644 --- a/contracts/deploy/mainnet/179_upgrade_ousd_morpho_v2_strategy.js +++ b/contracts/deploy/mainnet/179_upgrade_ousd_morpho_v2_strategy.js @@ -23,6 +23,11 @@ module.exports = deploymentWithGovernanceProposal( ] ); + const cMorphoV2Strategy = await ethers.getContractAt( + "MorphoV2Strategy", + cOUSDMorphoV2StrategyProxy.address + ); + return { name: "Upgrade OUSD Morpho V2 strategy implementation", actions: [ @@ -31,6 +36,11 @@ module.exports = deploymentWithGovernanceProposal( signature: "upgradeTo(address)", args: [dMorphoV2StrategyImpl.address], }, + { + contract: cMorphoV2Strategy, + signature: "setHarvesterAddress(address)", + args: [addresses.multichainStrategist], + }, ], }; } diff --git a/contracts/test/strategies/ousd-v2-morpho.mainnet.fork-test.js b/contracts/test/strategies/ousd-v2-morpho.mainnet.fork-test.js index f9ffb0b0a1..5ec44e5691 100644 --- a/contracts/test/strategies/ousd-v2-morpho.mainnet.fork-test.js +++ b/contracts/test/strategies/ousd-v2-morpho.mainnet.fork-test.js @@ -46,6 +46,9 @@ describe("ForkTest: Yearn's Morpho OUSD v2 Strategy", function () { expect(await morphoOUSDv2Strategy.governor()).to.equal( addresses.mainnet.Timelock ); + expect(await morphoOUSDv2Strategy.harvesterAddress()).to.equal( + addresses.multichainStrategist + ); }); it("Should be able to check balance", async () => { const { usdc, josh, morphoOUSDv2Strategy } = fixture;