FlexStrategyDeployer

Description:

Multi-signature wallet contract requiring multiple confirmations for transaction execution.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "lib/yieldnest-flex-strategy/script/FlexStrategyDeployer.sol": {
      "content": "pragma solidity ^0.8.28;

import { TransparentUpgradeableProxy, FlexStrategy, AccountingToken, IProvider, IActors } from "script/BaseScript.sol";
import { FixedRateProvider } from "src/FixedRateProvider.sol";
import { TimelockController } from "@openzeppelin/contracts/governance/TimelockController.sol";
import { AccountingModule } from "src/AccountingModule.sol";
import { BaseRoles } from "script/roles/BaseRoles.sol";
import { FlexStrategyRules } from "script/rules/FlexStrategyRules.sol";
import { SafeRules, IVault } from "@yieldnest-vault-script/rules/SafeRules.sol";
import { RewardsSweeper } from "src/utils/RewardsSweeper.sol";

contract FlexStrategyDeployer {
    error InvalidDeploymentParams(string);
    error DeploymentDone();

    struct DeploymentParams {
        string name;
        string symbol;
        string accountTokenName;
        string accountTokenSymbol;
        uint8 decimals;
        address[] allocators;
        address baseAsset;
        uint256 targetApy;
        uint256 lowerBound;
        address safe;
        address accountingProcessor;
        uint256 minRewardableAssets;
        bool alwaysComputeTotalAssets;
        bool paused;
        IActors actors;
        uint256 minDelay;
        Implementations implementations;
    }

    address public deployer;
    string public name;
    string public symbol_;
    string public accountTokenName;
    string public accountTokenSymbol;
    uint8 public decimals;
    address[] public allocators;
    address public baseAsset;
    uint256 public targetApy;
    uint256 public lowerBound;
    address public safe;
    address public accountingProcessor;
    uint256 public minRewardableAssets;
    bool public alwaysComputeTotalAssets;
    bool public paused;
    AccountingToken public accountingToken;
    AccountingModule public accountingModule;
    FlexStrategy public strategy;
    IProvider public rateProvider;
    TimelockController public timelock;
    RewardsSweeper public rewardsSweeper;
    IActors public actors;
    uint256 public minDelay;

    bool public useRewardsSweeper;

    Implementations public implementations;

    bool public deploymentDone;

    constructor(DeploymentParams memory params) {
        // the contract is the deployer
        deployer = address(this);
        actors = params.actors;
        minDelay = params.minDelay;

        // Set deployment parameters
        name = params.name;
        symbol_ = params.symbol;
        accountTokenName = params.accountTokenName;
        accountTokenSymbol = params.accountTokenSymbol;
        decimals = params.decimals;
        allocators = params.allocators;
        baseAsset = params.baseAsset;
        targetApy = params.targetApy;
        lowerBound = params.lowerBound;
        safe = params.safe;
        accountingProcessor = params.accountingProcessor;
        minRewardableAssets = params.minRewardableAssets;
        alwaysComputeTotalAssets = params.alwaysComputeTotalAssets;
        paused = params.paused;
        implementations = params.implementations;
    }

    struct Implementations {
        FlexStrategy flexStrategyImplementation;
        AccountingToken accountingTokenImplementation;
        AccountingModule accountingModuleImplementation;
        TimelockController timelockController;
        RewardsSweeper rewardsSweeperImplementation;
    }

    function deploy() public virtual {
        if (deploymentDone) {
            revert DeploymentDone();
        }
        deploymentDone = true;

        address admin = deployer;

        timelock = implementations.timelockController;

        if (address(implementations.rewardsSweeperImplementation) != address(0)) {
            useRewardsSweeper = true;
        }

        FlexStrategy strategyImplementation = implementations.flexStrategyImplementation;
        AccountingToken accountingTokenImplementation = implementations.accountingTokenImplementation;

        accountingToken = AccountingToken(
            payable(
                address(
                    new TransparentUpgradeableProxy(
                        address(accountingTokenImplementation),
                        address(timelock),
                        abi.encodeWithSelector(
                            AccountingToken.initialize.selector, admin, accountTokenName, accountTokenSymbol
                        )
                    )
                )
            )
        );

        deployRateProvider();

        strategy = FlexStrategy(
            payable(
                address(
                    new TransparentUpgradeableProxy(
                        address(strategyImplementation),
                        address(timelock),
                        abi.encodeWithSelector(
                            FlexStrategy.initialize.selector,
                            admin,
                            name,
                            symbol_,
                            decimals,
                            baseAsset,
                            address(accountingToken),
                            paused,
                            address(rateProvider),
                            alwaysComputeTotalAssets
                        )
                    )
                )
            )
        );

        AccountingModule accountingModuleImplementation =
            AccountingModule(address(implementations.accountingModuleImplementation));
        accountingModule = AccountingModule(
            payable(
                address(
                    new TransparentUpgradeableProxy(
                        address(accountingModuleImplementation),
                        address(timelock),
                        abi.encodeWithSelector(
                            AccountingModule.initialize.selector,
                            address(strategy),
                            admin,
                            safe,
                            address(accountingToken),
                            targetApy,
                            lowerBound,
                            minRewardableAssets,
                            1 hours
                        )
                    )
                )
            )
        );

        RewardsSweeper rewardsSweeperImplementation = implementations.rewardsSweeperImplementation;

        if (useRewardsSweeper) {
            rewardsSweeper = RewardsSweeper(
                payable(
                    address(
                        new TransparentUpgradeableProxy(
                            address(rewardsSweeperImplementation),
                            address(timelock),
                            abi.encodeWithSelector(RewardsSweeper.initialize.selector, admin, address(accountingModule))
                        )
                    )
                )
            );
        }

        configureStrategy();
    }

    function configureStrategy() internal virtual {
        BaseRoles.configureDefaultRolesStrategy(strategy, accountingModule, accountingToken, address(timelock), actors);
        BaseRoles.configureTemporaryRolesStrategy(strategy, accountingModule, accountingToken, deployer);

        // set has allocator
        strategy.setHasAllocator(true);
        // grant allocator roles
        for (uint256 i = 0; i < allocators.length; i++) {
            strategy.grantRole(strategy.ALLOCATOR_ROLE(), allocators[i]);
        }
        strategy.grantRole(strategy.ALLOCATOR_ROLE(), IActors(address(actors)).BOOTSTRAPPER());

        // set accounting module for token
        accountingToken.setAccountingModule(address(accountingModule));

        // set accounting module for strategy
        strategy.setAccountingModule(address(accountingModule));

        // set accounting processor role
        accountingModule.grantRole(accountingModule.REWARDS_PROCESSOR_ROLE(), accountingProcessor);
        accountingModule.grantRole(accountingModule.LOSS_PROCESSOR_ROLE(), safe);

        {
            // Safe Rules

            // Create an array to hold the rules
            SafeRules.RuleParams[] memory rules = new SafeRules.RuleParams[](2);

            // Set deposit rule for accounting module
            rules[0] = FlexStrategyRules.getDepositRule(address(accountingModule));

            // Set withdrawal rule for accounting module
            rules[1] = FlexStrategyRules.getWithdrawRule(address(accountingModule), address(strategy));

            // Set processor rules using SafeRules
            SafeRules.setProcessorRules(IVault(address(strategy)), rules, true);
        }

        if (useRewardsSweeper) {
            // RewardsSweeper
            rewardsSweeper.grantRole(rewardsSweeper.DEFAULT_ADMIN_ROLE(), actors.ADMIN());
            rewardsSweeper.grantRole(rewardsSweeper.REWARDS_SWEEPER_ROLE(), actors.PROCESSOR());

            accountingModule.grantRole(accountingModule.REWARDS_PROCESSOR_ROLE(), address(rewardsSweeper));

            rewardsSweeper.renounceRole(strategy.DEFAULT_ADMIN_ROLE(), deployer);
        }

        strategy.unpause();

        BaseRoles.renounceTemporaryRolesStrategy(strategy, accountingModule, accountingToken, deployer);
    }

    function deployRateProvider() internal {
        rateProvider = IProvider(address(new FixedRateProvider(address(accountingToken))));
    }
}
"
    },
    "lib/yieldnest-flex-strategy/script/BaseScript.sol": {
      "content": "// SPDX-License-Identifier: BSD-3-Clause
pragma solidity ^0.8.28;

import { Script, stdJson } from "lib/forge-std/src/Script.sol";
import { IProvider } from "@yieldnest-vault/interface/IProvider.sol";
import { TimelockController, TransparentUpgradeableProxy } from "@yieldnest-vault/Common.sol";
import { Strings } from "openzeppelin-contracts/contracts/utils/Strings.sol";
import { ProxyUtils } from "@yieldnest-vault-script/ProxyUtils.sol";
import { MainnetActors, IActors } from "@yieldnest-vault-script/Actors.sol";
import { IContracts, L1Contracts } from "@yieldnest-vault-script/Contracts.sol";
import { FlexStrategy } from "src/FlexStrategy.sol";
import { AccountingModule } from "src/AccountingModule.sol";
import { AccountingToken } from "src/AccountingToken.sol";
import { RewardsSweeper } from "src/utils/RewardsSweeper.sol";
import { console } from "forge-std/console.sol";

abstract contract BaseScript is Script {
    using stdJson for string;

    enum Env {
        TEST,
        PROD
    }

    struct DeploymentParameters {
        string name;
        string symbol_;
        string accountTokenName;
        string accountTokenSymbol;
        uint8 decimals;
        bool paused;
        uint256 targetApy;
        uint256 lowerBound;
        uint256 minRewardableAssets;
        address accountingProcessor;
        address baseAsset;
        address[] allocators;
        address safe;
        bool alwaysComputeTotalAssets;
        bool useRewardsSweeper;
    }

    function setDeploymentParameters(DeploymentParameters memory params) public virtual {
        name = params.name;
        symbol_ = params.symbol_;
        accountTokenName = params.accountTokenName;
        accountTokenSymbol = params.accountTokenSymbol;
        decimals = params.decimals;
        paused = params.paused;
        targetApy = params.targetApy;
        lowerBound = params.lowerBound;
        minRewardableAssets = params.minRewardableAssets;
        accountingProcessor = params.accountingProcessor;
        baseAsset = params.baseAsset;
        allocators = params.allocators;
        safe = params.safe;
        alwaysComputeTotalAssets = params.alwaysComputeTotalAssets;
        useRewardsSweeper = params.useRewardsSweeper;
    }

    Env public deploymentEnv = Env.PROD;

    string public name;
    string public symbol_;
    string public accountTokenName;
    string public accountTokenSymbol;
    uint8 public decimals;
    bool public paused;
    uint256 public targetApy;
    uint256 public lowerBound;
    uint256 public minRewardableAssets;
    address public accountingProcessor;
    address public baseAsset;
    address[] public allocators;
    bool public alwaysComputeTotalAssets;

    uint256 public minDelay;
    IActors public actors;
    IContracts public contracts;

    address public deployer;
    TimelockController public timelock;
    IProvider public rateProvider;
    address public safe;

    FlexStrategy public strategy;
    FlexStrategy public strategyImplementation;
    address public strategyProxyAdmin;

    AccountingModule public accountingModule;
    AccountingModule public accountingModuleImplementation;
    address public accountingModuleProxyAdmin;

    AccountingToken public accountingToken;
    AccountingToken public accountingTokenImplementation;
    address public accountingTokenProxyAdmin;

    bool public useRewardsSweeper;
    RewardsSweeper public rewardsSweeper;
    RewardsSweeper public rewardsSweeperImplementation;
    address public rewardsSweeperProxyAdmin;

    error UnsupportedChain();
    error InvalidSetup(string);

    // needs to be overridden by child script
    function symbol() public view virtual returns (string memory);

    function setEnv(Env env) public {
        deploymentEnv = env;
    }

    function _setup() public virtual {
        if (block.chainid == 1) {
            minDelay = 1 days;
            MainnetActors _actors = new MainnetActors();
            actors = IActors(_actors);
            contracts = IContracts(new L1Contracts());
        }
    }

    function _verifySetup() public view virtual {
        if (block.chainid != 1) {
            revert UnsupportedChain();
        }
        if (address(actors) == address(0)) {
            revert InvalidSetup("actors not set");
        }
        if (address(contracts) == address(0)) {
            revert InvalidSetup("contracts not set");
        }
        if (address(timelock) == address(0)) {
            revert InvalidSetup("timelock not set");
        }
    }

    function _deployTimelockController() internal virtual {
        address[] memory proposers = new address[](1);
        proposers[0] = actors.PROPOSER_1();

        address[] memory executors = new address[](1);
        executors[0] = actors.EXECUTOR_1();

        address admin = actors.ADMIN();

        timelock = new TimelockController(minDelay, proposers, executors, admin);
    }

    function _loadDeployment(Env env) internal virtual {
        if (!vm.isFile(_deploymentFilePath(env))) {
            console.log("No deployment file found");
            return;
        }
        string memory jsonInput = vm.readFile(_deploymentFilePath(env));
        symbol_ = vm.parseJsonString(jsonInput, ".symbol");
        deployer = address(vm.parseJsonAddress(jsonInput, ".deployer"));
        timelock = TimelockController(payable(address(vm.parseJsonAddress(jsonInput, ".timelock"))));
        rateProvider = IProvider(payable(address(vm.parseJsonAddress(jsonInput, ".rateProvider"))));
        safe = vm.parseJsonAddress(jsonInput, ".safe");

        strategy =
            FlexStrategy(payable(address(vm.parseJsonAddress(jsonInput, string.concat(".", symbol(), "-proxy")))));
        strategyImplementation = FlexStrategy(
            payable(address(vm.parseJsonAddress(jsonInput, string.concat(".", symbol(), "-implementation"))))
        );
        strategyProxyAdmin = address(vm.parseJsonAddress(jsonInput, string.concat(".", symbol(), "-proxyAdmin")));

        accountingModule = AccountingModule(
            payable(address(vm.parseJsonAddress(jsonInput, string.concat(".", symbol(), "-accountingModule-proxy"))))
        );
        accountingModuleImplementation = AccountingModule(
            payable(
                address(
                    vm.parseJsonAddress(jsonInput, string.concat(".", symbol(), "-accountingModule-implementation"))
                )
            )
        );
        accountingModuleProxyAdmin =
            address(vm.parseJsonAddress(jsonInput, string.concat(".", symbol(), "-accountingModule-proxyAdmin")));

        accountingToken = AccountingToken(
            payable(address(vm.parseJsonAddress(jsonInput, string.concat(".", symbol(), "-accountingToken-proxy"))))
        );
        accountingTokenImplementation = AccountingToken(
            payable(
                address(vm.parseJsonAddress(jsonInput, string.concat(".", symbol(), "-accountingToken-implementation")))
            )
        );
        accountingTokenProxyAdmin =
            address(vm.parseJsonAddress(jsonInput, string.concat(".", symbol(), "-accountingToken-proxyAdmin")));

        useRewardsSweeper = vm.parseJsonBool(jsonInput, string.concat(".useRewardsSweeper"));

        if (useRewardsSweeper) {
            rewardsSweeper = RewardsSweeper(
                payable(vm.parseJsonAddress(jsonInput, string.concat(".", symbol(), "-rewardsSweeper-proxy")))
            );
            rewardsSweeperImplementation = RewardsSweeper(
                payable(vm.parseJsonAddress(jsonInput, string.concat(".", symbol(), "-rewardsSweeper-implementation")))
            );
            rewardsSweeperProxyAdmin =
                address(vm.parseJsonAddress(jsonInput, string.concat(".", symbol(), "-rewardsSweeper-proxyAdmin")));
        }
    }

    function _deploymentFilePath(Env env) internal view virtual returns (string memory) {
        if (env == Env.PROD) {
            return string.concat(
                vm.projectRoot(), "/deployments/", symbol(), "-", Strings.toString(block.chainid), ".json"
            );
        }

        return string.concat(
            vm.projectRoot(), "/deployments/", "test-", symbol(), "-", Strings.toString(block.chainid), ".json"
        );
    }

    function _saveDeployment(Env env) internal virtual {
        vm.serializeString(symbol(), "symbol", symbol());
        vm.serializeAddress(symbol(), "deployer", deployer);
        vm.serializeAddress(symbol(), "admin", actors.ADMIN());
        vm.serializeAddress(symbol(), "timelock", address(timelock));
        vm.serializeAddress(symbol(), "rateProvider", address(rateProvider));
        vm.serializeAddress(symbol(), "safe", address(safe));
        vm.serializeAddress(symbol(), "baseAsset", address(baseAsset));
        vm.serializeAddress(symbol(), string.concat(symbol(), "-proxy"), address(strategy));
        vm.serializeAddress(
            symbol(), string.concat(symbol(), "-proxyAdmin"), ProxyUtils.getProxyAdmin(address(strategy))
        );
        vm.serializeAddress(symbol(), string.concat(symbol(), "-implementation"), address(strategyImplementation));

        vm.serializeAddress(symbol(), string.concat(symbol(), "-accountingModule-proxy"), address(accountingModule));
        vm.serializeAddress(
            symbol(),
            string.concat(symbol(), "-accountingModule-proxyAdmin"),
            ProxyUtils.getProxyAdmin(address(accountingModule))
        );
        vm.serializeAddress(
            symbol(),
            string.concat(symbol(), "-accountingModule-implementation"),
            address(accountingModuleImplementation)
        );

        vm.serializeBool(symbol(), "useRewardsSweeper", useRewardsSweeper);

        if (useRewardsSweeper) {
            vm.serializeAddress(symbol(), string.concat(symbol(), "-rewardsSweeper-proxy"), address(rewardsSweeper));
            vm.serializeAddress(
                symbol(),
                string.concat(symbol(), "-rewardsSweeper-proxyAdmin"),
                ProxyUtils.getProxyAdmin(address(rewardsSweeper))
            );
            vm.serializeAddress(
                symbol(),
                string.concat(symbol(), "-rewardsSweeper-implementation"),
                address(rewardsSweeperImplementation)
            );
        }

        vm.serializeAddress(symbol(), string.concat(symbol(), "-accountingToken-proxy"), address(accountingToken));
        vm.serializeAddress(
            symbol(),
            string.concat(symbol(), "-accountingToken-proxyAdmin"),
            ProxyUtils.getProxyAdmin(address(accountingToken))
        );
        string memory jsonOutput = vm.serializeAddress(
            symbol(), string.concat(symbol(), "-accountingToken-implementation"), address(accountingTokenImplementation)
        );

        vm.writeJson(jsonOutput, _deploymentFilePath(env));
    }
}
"
    },
    "lib/yieldnest-flex-strategy/src/FixedRateProvider.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

import { IProvider } from "@yieldnest-vault/interface/IProvider.sol";
import { IERC20Metadata } from "@openzeppelin/contracts/interfaces/IERC20Metadata.sol";
import { IAccountingToken } from "./AccountingToken.sol";

/**
 * Fixed rate provider that assumes 1:1 rate of added asset.
 */
contract FixedRateProvider is IProvider {
    address public immutable ASSET;
    uint8 public immutable DECIMALS;
    address public immutable ACCOUNTING_TOKEN;

    constructor(address accountingToken) {
        ASSET = IAccountingToken(accountingToken).TRACKED_ASSET();
        DECIMALS = IERC20Metadata(accountingToken).decimals();
        ACCOUNTING_TOKEN = accountingToken;
    }

    error UnsupportedAsset(address asset);

    function getRate(address asset) external view returns (uint256 rate) {
        if (asset == ASSET) {
            return 10 ** DECIMALS;
        }

        if (asset == ACCOUNTING_TOKEN) {
            return 10 ** DECIMALS;
        }

        revert UnsupportedAsset(asset);
    }
}
"
    },
    "lib/yieldnest-flex-strategy/lib/yieldnest-vault/lib/openzeppelin-contracts/contracts/governance/TimelockController.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (governance/TimelockController.sol)

pragma solidity ^0.8.20;

import {AccessControl} from "../access/AccessControl.sol";
import {ERC721Holder} from "../token/ERC721/utils/ERC721Holder.sol";
import {ERC1155Holder} from "../token/ERC1155/utils/ERC1155Holder.sol";
import {Address} from "../utils/Address.sol";

/**
 * @dev Contract module which acts as a timelocked controller. When set as the
 * owner of an `Ownable` smart contract, it enforces a timelock on all
 * `onlyOwner` maintenance operations. This gives time for users of the
 * controlled contract to exit before a potentially dangerous maintenance
 * operation is applied.
 *
 * By default, this contract is self administered, meaning administration tasks
 * have to go through the timelock process. The proposer (resp executor) role
 * is in charge of proposing (resp executing) operations. A common use case is
 * to position this {TimelockController} as the owner of a smart contract, with
 * a multisig or a DAO as the sole proposer.
 */
contract TimelockController is AccessControl, ERC721Holder, ERC1155Holder {
    bytes32 public constant PROPOSER_ROLE = keccak256("PROPOSER_ROLE");
    bytes32 public constant EXECUTOR_ROLE = keccak256("EXECUTOR_ROLE");
    bytes32 public constant CANCELLER_ROLE = keccak256("CANCELLER_ROLE");
    uint256 internal constant _DONE_TIMESTAMP = uint256(1);

    mapping(bytes32 id => uint256) private _timestamps;
    uint256 private _minDelay;

    enum OperationState {
        Unset,
        Waiting,
        Ready,
        Done
    }

    /**
     * @dev Mismatch between the parameters length for an operation call.
     */
    error TimelockInvalidOperationLength(uint256 targets, uint256 payloads, uint256 values);

    /**
     * @dev The schedule operation doesn't meet the minimum delay.
     */
    error TimelockInsufficientDelay(uint256 delay, uint256 minDelay);

    /**
     * @dev The current state of an operation is not as required.
     * The `expectedStates` is a bitmap with the bits enabled for each OperationState enum position
     * counting from right to left.
     *
     * See {_encodeStateBitmap}.
     */
    error TimelockUnexpectedOperationState(bytes32 operationId, bytes32 expectedStates);

    /**
     * @dev The predecessor to an operation not yet done.
     */
    error TimelockUnexecutedPredecessor(bytes32 predecessorId);

    /**
     * @dev The caller account is not authorized.
     */
    error TimelockUnauthorizedCaller(address caller);

    /**
     * @dev Emitted when a call is scheduled as part of operation `id`.
     */
    event CallScheduled(
        bytes32 indexed id,
        uint256 indexed index,
        address target,
        uint256 value,
        bytes data,
        bytes32 predecessor,
        uint256 delay
    );

    /**
     * @dev Emitted when a call is performed as part of operation `id`.
     */
    event CallExecuted(bytes32 indexed id, uint256 indexed index, address target, uint256 value, bytes data);

    /**
     * @dev Emitted when new proposal is scheduled with non-zero salt.
     */
    event CallSalt(bytes32 indexed id, bytes32 salt);

    /**
     * @dev Emitted when operation `id` is cancelled.
     */
    event Cancelled(bytes32 indexed id);

    /**
     * @dev Emitted when the minimum delay for future operations is modified.
     */
    event MinDelayChange(uint256 oldDuration, uint256 newDuration);

    /**
     * @dev Initializes the contract with the following parameters:
     *
     * - `minDelay`: initial minimum delay in seconds for operations
     * - `proposers`: accounts to be granted proposer and canceller roles
     * - `executors`: accounts to be granted executor role
     * - `admin`: optional account to be granted admin role; disable with zero address
     *
     * IMPORTANT: The optional admin can aid with initial configuration of roles after deployment
     * without being subject to delay, but this role should be subsequently renounced in favor of
     * administration through timelocked proposals. Previous versions of this contract would assign
     * this admin to the deployer automatically and should be renounced as well.
     */
    constructor(uint256 minDelay, address[] memory proposers, address[] memory executors, address admin) {
        // self administration
        _grantRole(DEFAULT_ADMIN_ROLE, address(this));

        // optional admin
        if (admin != address(0)) {
            _grantRole(DEFAULT_ADMIN_ROLE, admin);
        }

        // register proposers and cancellers
        for (uint256 i = 0; i < proposers.length; ++i) {
            _grantRole(PROPOSER_ROLE, proposers[i]);
            _grantRole(CANCELLER_ROLE, proposers[i]);
        }

        // register executors
        for (uint256 i = 0; i < executors.length; ++i) {
            _grantRole(EXECUTOR_ROLE, executors[i]);
        }

        _minDelay = minDelay;
        emit MinDelayChange(0, minDelay);
    }

    /**
     * @dev Modifier to make a function callable only by a certain role. In
     * addition to checking the sender's role, `address(0)` 's role is also
     * considered. Granting a role to `address(0)` is equivalent to enabling
     * this role for everyone.
     */
    modifier onlyRoleOrOpenRole(bytes32 role) {
        if (!hasRole(role, address(0))) {
            _checkRole(role, _msgSender());
        }
        _;
    }

    /**
     * @dev Contract might receive/hold ETH as part of the maintenance process.
     */
    receive() external payable {}

    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(
        bytes4 interfaceId
    ) public view virtual override(AccessControl, ERC1155Holder) returns (bool) {
        return super.supportsInterface(interfaceId);
    }

    /**
     * @dev Returns whether an id corresponds to a registered operation. This
     * includes both Waiting, Ready, and Done operations.
     */
    function isOperation(bytes32 id) public view returns (bool) {
        return getOperationState(id) != OperationState.Unset;
    }

    /**
     * @dev Returns whether an operation is pending or not. Note that a "pending" operation may also be "ready".
     */
    function isOperationPending(bytes32 id) public view returns (bool) {
        OperationState state = getOperationState(id);
        return state == OperationState.Waiting || state == OperationState.Ready;
    }

    /**
     * @dev Returns whether an operation is ready for execution. Note that a "ready" operation is also "pending".
     */
    function isOperationReady(bytes32 id) public view returns (bool) {
        return getOperationState(id) == OperationState.Ready;
    }

    /**
     * @dev Returns whether an operation is done or not.
     */
    function isOperationDone(bytes32 id) public view returns (bool) {
        return getOperationState(id) == OperationState.Done;
    }

    /**
     * @dev Returns the timestamp at which an operation becomes ready (0 for
     * unset operations, 1 for done operations).
     */
    function getTimestamp(bytes32 id) public view virtual returns (uint256) {
        return _timestamps[id];
    }

    /**
     * @dev Returns operation state.
     */
    function getOperationState(bytes32 id) public view virtual returns (OperationState) {
        uint256 timestamp = getTimestamp(id);
        if (timestamp == 0) {
            return OperationState.Unset;
        } else if (timestamp == _DONE_TIMESTAMP) {
            return OperationState.Done;
        } else if (timestamp > block.timestamp) {
            return OperationState.Waiting;
        } else {
            return OperationState.Ready;
        }
    }

    /**
     * @dev Returns the minimum delay in seconds for an operation to become valid.
     *
     * This value can be changed by executing an operation that calls `updateDelay`.
     */
    function getMinDelay() public view virtual returns (uint256) {
        return _minDelay;
    }

    /**
     * @dev Returns the identifier of an operation containing a single
     * transaction.
     */
    function hashOperation(
        address target,
        uint256 value,
        bytes calldata data,
        bytes32 predecessor,
        bytes32 salt
    ) public pure virtual returns (bytes32) {
        return keccak256(abi.encode(target, value, data, predecessor, salt));
    }

    /**
     * @dev Returns the identifier of an operation containing a batch of
     * transactions.
     */
    function hashOperationBatch(
        address[] calldata targets,
        uint256[] calldata values,
        bytes[] calldata payloads,
        bytes32 predecessor,
        bytes32 salt
    ) public pure virtual returns (bytes32) {
        return keccak256(abi.encode(targets, values, payloads, predecessor, salt));
    }

    /**
     * @dev Schedule an operation containing a single transaction.
     *
     * Emits {CallSalt} if salt is nonzero, and {CallScheduled}.
     *
     * Requirements:
     *
     * - the caller must have the 'proposer' role.
     */
    function schedule(
        address target,
        uint256 value,
        bytes calldata data,
        bytes32 predecessor,
        bytes32 salt,
        uint256 delay
    ) public virtual onlyRole(PROPOSER_ROLE) {
        bytes32 id = hashOperation(target, value, data, predecessor, salt);
        _schedule(id, delay);
        emit CallScheduled(id, 0, target, value, data, predecessor, delay);
        if (salt != bytes32(0)) {
            emit CallSalt(id, salt);
        }
    }

    /**
     * @dev Schedule an operation containing a batch of transactions.
     *
     * Emits {CallSalt} if salt is nonzero, and one {CallScheduled} event per transaction in the batch.
     *
     * Requirements:
     *
     * - the caller must have the 'proposer' role.
     */
    function scheduleBatch(
        address[] calldata targets,
        uint256[] calldata values,
        bytes[] calldata payloads,
        bytes32 predecessor,
        bytes32 salt,
        uint256 delay
    ) public virtual onlyRole(PROPOSER_ROLE) {
        if (targets.length != values.length || targets.length != payloads.length) {
            revert TimelockInvalidOperationLength(targets.length, payloads.length, values.length);
        }

        bytes32 id = hashOperationBatch(targets, values, payloads, predecessor, salt);
        _schedule(id, delay);
        for (uint256 i = 0; i < targets.length; ++i) {
            emit CallScheduled(id, i, targets[i], values[i], payloads[i], predecessor, delay);
        }
        if (salt != bytes32(0)) {
            emit CallSalt(id, salt);
        }
    }

    /**
     * @dev Schedule an operation that is to become valid after a given delay.
     */
    function _schedule(bytes32 id, uint256 delay) private {
        if (isOperation(id)) {
            revert TimelockUnexpectedOperationState(id, _encodeStateBitmap(OperationState.Unset));
        }
        uint256 minDelay = getMinDelay();
        if (delay < minDelay) {
            revert TimelockInsufficientDelay(delay, minDelay);
        }
        _timestamps[id] = block.timestamp + delay;
    }

    /**
     * @dev Cancel an operation.
     *
     * Requirements:
     *
     * - the caller must have the 'canceller' role.
     */
    function cancel(bytes32 id) public virtual onlyRole(CANCELLER_ROLE) {
        if (!isOperationPending(id)) {
            revert TimelockUnexpectedOperationState(
                id,
                _encodeStateBitmap(OperationState.Waiting) | _encodeStateBitmap(OperationState.Ready)
            );
        }
        delete _timestamps[id];

        emit Cancelled(id);
    }

    /**
     * @dev Execute an (ready) operation containing a single transaction.
     *
     * Emits a {CallExecuted} event.
     *
     * Requirements:
     *
     * - the caller must have the 'executor' role.
     */
    // This function can reenter, but it doesn't pose a risk because _afterCall checks that the proposal is pending,
    // thus any modifications to the operation during reentrancy should be caught.
    // slither-disable-next-line reentrancy-eth
    function execute(
        address target,
        uint256 value,
        bytes calldata payload,
        bytes32 predecessor,
        bytes32 salt
    ) public payable virtual onlyRoleOrOpenRole(EXECUTOR_ROLE) {
        bytes32 id = hashOperation(target, value, payload, predecessor, salt);

        _beforeCall(id, predecessor);
        _execute(target, value, payload);
        emit CallExecuted(id, 0, target, value, payload);
        _afterCall(id);
    }

    /**
     * @dev Execute an (ready) operation containing a batch of transactions.
     *
     * Emits one {CallExecuted} event per transaction in the batch.
     *
     * Requirements:
     *
     * - the caller must have the 'executor' role.
     */
    // This function can reenter, but it doesn't pose a risk because _afterCall checks that the proposal is pending,
    // thus any modifications to the operation during reentrancy should be caught.
    // slither-disable-next-line reentrancy-eth
    function executeBatch(
        address[] calldata targets,
        uint256[] calldata values,
        bytes[] calldata payloads,
        bytes32 predecessor,
        bytes32 salt
    ) public payable virtual onlyRoleOrOpenRole(EXECUTOR_ROLE) {
        if (targets.length != values.length || targets.length != payloads.length) {
            revert TimelockInvalidOperationLength(targets.length, payloads.length, values.length);
        }

        bytes32 id = hashOperationBatch(targets, values, payloads, predecessor, salt);

        _beforeCall(id, predecessor);
        for (uint256 i = 0; i < targets.length; ++i) {
            address target = targets[i];
            uint256 value = values[i];
            bytes calldata payload = payloads[i];
            _execute(target, value, payload);
            emit CallExecuted(id, i, target, value, payload);
        }
        _afterCall(id);
    }

    /**
     * @dev Execute an operation's call.
     */
    function _execute(address target, uint256 value, bytes calldata data) internal virtual {
        (bool success, bytes memory returndata) = target.call{value: value}(data);
        Address.verifyCallResult(success, returndata);
    }

    /**
     * @dev Checks before execution of an operation's calls.
     */
    function _beforeCall(bytes32 id, bytes32 predecessor) private view {
        if (!isOperationReady(id)) {
            revert TimelockUnexpectedOperationState(id, _encodeStateBitmap(OperationState.Ready));
        }
        if (predecessor != bytes32(0) && !isOperationDone(predecessor)) {
            revert TimelockUnexecutedPredecessor(predecessor);
        }
    }

    /**
     * @dev Checks after execution of an operation's calls.
     */
    function _afterCall(bytes32 id) private {
        if (!isOperationReady(id)) {
            revert TimelockUnexpectedOperationState(id, _encodeStateBitmap(OperationState.Ready));
        }
        _timestamps[id] = _DONE_TIMESTAMP;
    }

    /**
     * @dev Changes the minimum timelock duration for future operations.
     *
     * Emits a {MinDelayChange} event.
     *
     * Requirements:
     *
     * - the caller must be the timelock itself. This can only be achieved by scheduling and later executing
     * an operation where the timelock is the target and the data is the ABI-encoded call to this function.
     */
    function updateDelay(uint256 newDelay) external virtual {
        address sender = _msgSender();
        if (sender != address(this)) {
            revert TimelockUnauthorizedCaller(sender);
        }
        emit MinDelayChange(_minDelay, newDelay);
        _minDelay = newDelay;
    }

    /**
     * @dev Encodes a `OperationState` into a `bytes32` representation where each bit enabled corresponds to
     * the underlying position in the `OperationState` enum. For example:
     *
     * 0x000...1000
     *   ^^^^^^----- ...
     *         ^---- Done
     *          ^--- Ready
     *           ^-- Waiting
     *            ^- Unset
     */
    function _encodeStateBitmap(OperationState operationState) internal pure returns (bytes32) {
        return bytes32(1 << uint8(operationState));
    }
}
"
    },
    "lib/yieldnest-flex-strategy/src/AccountingModule.sol": {
      "content": "// SPDX-License-Identifier: BSD-3-Clause
pragma solidity ^0.8.28;

import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { IAccountingToken } from "./AccountingToken.sol";
import { IVault } from "@yieldnest-vault/interface/IVault.sol";
import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import { AccessControlUpgradeable } from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import { IERC4626 } from "@openzeppelin/contracts/interfaces/IERC4626.sol";

interface IAccountingModule {
    struct StrategySnapshot {
        uint256 timestamp;
        uint256 pricePerShare;
        uint256 totalSupply;
        uint256 totalAssets;
    }

    event LowerBoundUpdated(uint256 newValue, uint256 oldValue);
    event TargetApyUpdated(uint256 newValue, uint256 oldValue);
    event CooldownSecondsUpdated(uint16 newValue, uint16 oldValue);
    event SafeUpdated(address newValue, address oldValue);

    error ZeroAddress();
    error TooEarly();
    error NotStrategy();
    error AccountingLimitsExceeded(uint256 aprSinceLastSnapshot, uint256 targetApr);
    error LossLimitsExceeded(uint256 amount, uint256 lowerBoundAmount);
    error InvariantViolation();
    error TvlTooLow();
    error CurrentTimestampBeforePreviousTimestamp();
    error SnapshotIndexOutOfBounds(uint256 index);

    function deposit(uint256 amount) external;
    function withdraw(uint256 amount, address recipient) external;
    function processRewards(uint256 amount) external;
    function processRewards(uint256 amount, uint256 snapshotIndex) external;
    function processLosses(uint256 amount) external;
    function setCooldownSeconds(uint16 cooldownSeconds) external;

    function baseAsset() external view returns (address);
    function strategy() external view returns (address);
    function DIVISOR() external view returns (uint256);
    function YEAR() external view returns (uint256);
    function accountingToken() external view returns (IAccountingToken);
    function safe() external view returns (address);
    function nextUpdateWindow() external view returns (uint64);
    function targetApy() external view returns (uint256);
    function lowerBound() external view returns (uint256);
    function cooldownSeconds() external view returns (uint16);
    function SAFE_MANAGER_ROLE() external view returns (bytes32);
    function REWARDS_PROCESSOR_ROLE() external view returns (bytes32);
    function LOSS_PROCESSOR_ROLE() external view returns (bytes32);

    function calculateApr(
        uint256 previousPricePerShare,
        uint256 previousTimestamp,
        uint256 currentPricePerShare,
        uint256 currentTimestamp
    )
        external
        view
        returns (uint256 apr);

    function snapshotsLength() external view returns (uint256);
    function snapshots(uint256 index) external view returns (StrategySnapshot memory);
    function lastSnapshot() external view returns (StrategySnapshot memory);
}

/**
 * @notice Storage struct for AccountingModule
 */
struct AccountingModuleStorage {
    IAccountingToken accountingToken;
    address safe;
    address baseAsset;
    address strategy;
    uint64 nextUpdateWindow;
    uint16 cooldownSeconds;
    uint256 targetApy; // in bips;
    uint256 lowerBound; // in bips; % of tvl
    uint256 minRewardableAssets;
    IAccountingModule.StrategySnapshot[] _snapshots;
}

/**
 * Module to configure strategy params,
 *  and mint/burn IOU tokens to represent value accrual/loss.
 */
contract AccountingModule is IAccountingModule, Initializable, AccessControlUpgradeable {
    using SafeERC20 for IERC20;

    /// @notice Role for safe manager permissions
    bytes32 public constant SAFE_MANAGER_ROLE = keccak256("SAFE_MANAGER_ROLE");

    /// @notice Role for processing rewards/losses
    bytes32 public constant REWARDS_PROCESSOR_ROLE = keccak256("REWARDS_PROCESSOR_ROLE");
    bytes32 public constant LOSS_PROCESSOR_ROLE = keccak256("LOSS_PROCESSOR_ROLE");

    uint256 public constant YEAR = 365.25 days;
    uint256 public constant DIVISOR = 1e18;
    uint256 public constant MAX_LOWER_BOUND = DIVISOR / 2;

    /// @notice Storage slot for AccountingModule data
    bytes32 private constant ACCOUNTING_MODULE_STORAGE_SLOT = keccak256("yieldnest.storage.accountingModule");

    /// @custom:oz-upgrades-unsafe-allow constructor
    constructor() {
        _disableInitializers();
    }

    /**
     * @notice Get the storage struct
     */
    function _getAccountingModuleStorage() internal pure returns (AccountingModuleStorage storage s) {
        bytes32 slot = ACCOUNTING_MODULE_STORAGE_SLOT;
        assembly {
            s.slot := slot
        }
    }

    /**
     * /**
     * @notice Initializes the vault.
     * @param strategy_ The strategy address.
     * @param admin The address of the admin.
     * @param safe_ The safe associated with the module.
     * @param accountingToken_ The accountingToken associated with the module.
     * @param targetApy_ The target APY of the strategy.
     * @param lowerBound_ The lower bound of losses of the strategy(as % of TVL).
     * @param minRewardableAssets_ The minimum rewardable assets.
     * @param cooldownSeconds_ The cooldown period in seconds.
     */
    function initialize(
        address strategy_,
        address admin,
        address safe_,
        IAccountingToken accountingToken_,
        uint256 targetApy_,
        uint256 lowerBound_,
        uint256 minRewardableAssets_,
        uint16 cooldownSeconds_
    )
        external
        virtual
        initializer
    {
        __AccessControl_init();

        if (admin == address(0)) revert ZeroAddress();

        _grantRole(DEFAULT_ADMIN_ROLE, admin);

        AccountingModuleStorage storage s = _getAccountingModuleStorage();

        if (address(accountingToken_) == address(0)) revert ZeroAddress();
        s.accountingToken = accountingToken_;

        s.minRewardableAssets = minRewardableAssets_;

        if (strategy_ == address(0)) revert ZeroAddress();
        s.strategy = strategy_;
        s.baseAsset = IERC4626(strategy_).asset();

        _setSafeAddress(safe_);
        _setTargetApy(targetApy_);
        _setLowerBound(lowerBound_);
        _setCooldownSeconds(cooldownSeconds_);

        createStrategySnapshot();
    }

    modifier checkAndResetCooldown() {
        AccountingModuleStorage storage s = _getAccountingModuleStorage();
        if (block.timestamp < s.nextUpdateWindow) revert TooEarly();
        s.nextUpdateWindow = (uint64(block.timestamp) + s.cooldownSeconds);
        _;
    }

    modifier onlyStrategy() {
        AccountingModuleStorage storage s = _getAccountingModuleStorage();
        if (msg.sender != s.strategy) revert NotStrategy();
        _;
    }

    /// DEPOSIT/WITHDRAW ///

    /**
     * @notice Proxies deposit of base assets from caller to associated SAFE,
     * and mints an equiv amount of accounting tokens
     * @param amount amount to deposit
     */
    function deposit(uint256 amount) external onlyStrategy {
        AccountingModuleStorage storage s = _getAccountingModuleStorage();
        IERC20(s.baseAsset).safeTransferFrom(s.strategy, s.safe, amount);
        s.accountingToken.mintTo(s.strategy, amount);
    }

    /**
     * @notice Proxies withdraw of base assets from associated SAFE to caller,
     * and burns an equiv amount of accounting tokens
     * @param amount amount to deposit
     * @param recipient address to receive the base assets
     */
    function withdraw(uint256 amount, address recipient) external onlyStrategy {
        AccountingModuleStorage storage s = _getAccountingModuleStorage();
        s.accountingToken.burnFrom(s.strategy, amount);
        IERC20(s.baseAsset).safeTransferFrom(s.safe, recipient, amount);
    }

    /// REWARDS ///

    /**
     * @notice Process rewards by minting accounting tokens
     * @param amount profits to mint
     */
    function processRewards(uint256 amount) external onlyRole(REWARDS_PROCESSOR_ROLE) checkAndResetCooldown {
        AccountingModuleStorage storage s = _getAccountingModuleStorage();
        _processRewards(amount, s._snapshots.length - 1);
    }

    /**
     * @notice Process rewards by minting accounting tokens with specific snapshot index
     * @param amount profits to mint
     * @param snapshotIndex index of the snapshot to compare against
     */
    function processRewards(
        uint256 amount,
        uint256 snapshotIndex
    )
        external
        onlyRole(REWARDS_PROCESSOR_ROLE)
        checkAndResetCooldown
    {
        _processRewards(amount, snapshotIndex);
    }

    /**
     * @notice Internal function to process rewards with snapshot validation
     * @param amount profits to mint
     * @param snapshotIndex index of the snapshot to compare against
     *
     * @dev This function validates rewards by comparing current PPS against a historical snapshot.
     * Using a past snapshot (rather than the most recent) helps prevent APR manipulation
     * by smoothing out reward distribution over time.
     *
     *
     * Example with daily processRewards calls:
     *
     * Day 0: PPS = 100  [snapshot 0]
     * Day 1: PPS = 101  [snapshot 1]
     * Day 2: PPS = 102  [snapshot 2]
     * Day 3: PPS = 107  [snapshot 3] ← Big jump due to delayed rewards
     *
     * If we only compared Day 2→3 (102→107):
     *   Daily return: 4.9% → ~720% APR (exceeds cap)
     *
     * Instead, compare Day 0→3 (100→107):
     *   Daily return: ~2.3% → ~240% APR (within sustainable range)
     *
     * This approach provides flexibility by allowing irregular reward distributions
     * while still enforcing APR limits. By comparing against historical snapshots,
     * the system can accommodate delayed or lump-sum rewards without triggering
     * false positives, while maintaining protection against actual APR manipulation.
     */
    function _processRewards(uint256 amount, uint256 snapshotIndex) internal {
        AccountingModuleStorage storage s = _getAccountingModuleStorage();
        // check if snapshot index is valid
        if (snapshotIndex >= s._snapshots.length) revert SnapshotIndexOutOfBounds(snapshotIndex);

        uint256 totalSupply = s.accountingToken.totalSupply();
        if (totalSupply < s.minRewardableAssets) revert TvlTooLow();

        IVault strategyVault = IVault(s.strategy);

        s.accountingToken.mintTo(s.strategy, amount);
        strategyVault.processAccounting();

        // check if apr is within acceptable bounds

        StrategySnapshot memory previousSnapshot = s._snapshots[snapshotIndex];

        uint256 currentPricePerShare = createStrategySnapshot().pricePerShare;

        // Check if APR is within acceptable bounds
        uint256 aprSinceLastSnapshot = calculateApr(
            previousSnapshot.pricePerShare, previousSnapshot.timestamp, currentPricePerShare, block.timestamp
        );

        if (aprSinceLastSnapshot > s.targetApy) revert AccountingLimitsExceeded(aprSinceLastSnapshot, s.targetApy);
    }

    function createStrategySnapshot() internal returns (StrategySnapshot memory) {
        AccountingModuleStorage storage s = _getAccountingModuleStorage();
        IVault strategyVault = IVault(s.strategy);

        // Take snapshot of current state
        uint256 currentPricePerShare = strategyVault.convertToAssets(10 ** strategyVault.decimals());

        StrategySnapshot memory snapshot = StrategySnapshot({
            timestamp: block.timestamp,
            pricePerShare: currentPricePerShare,
            totalSupply: strategyVault.totalSupply(),
            totalAssets: strategyVault.totalAssets()
        });

        s._snapshots.push(snapshot);

        return snapshot;
    }

    /**
     * @notice Calculate APR based on price per share changes over time
     * @param previousPricePerShare The price per share at the start of the period
     * @param previousTimestamp The timestamp at the start of the period
     * @param currentPricePerShare The price per share at the end of the period
     * @param currentTimestamp The timestamp at the end of the period
     * @return apr The calculated APR in basis points
     */
    function calculateApr(
        uint256 previousPricePerShare,
        uint256 previousTimestamp,
        uint256 currentPricePerShare,
        uint256 currentTimestamp
    )
        public
        pure
        returns (uint256 apr)
    {
        /*
        ppsStart - Price per share at the start of the period
        ppsEnd - Price per share at the end of the period
        t - Time period in years*
        Formula: (ppsEnd - ppsStart) / (ppsStart * t)
        */

        // Ensure timestamps are ordered (current should be after previous)
        if (currentTimestamp <= previousTimestamp) revert CurrentTimestampBeforePreviousTimestamp();

        // Prevent division by zero
        if (previousPricePerShare == 0) revert InvariantViolation();

        return (currentPricePerShare - previousPricePerShare) * YEAR * DIVISOR / previousPricePerShare
            / (currentTimestamp - previousTimestamp);
    }

    /// LOSS ///

    /**
     * @notice Process losses by burning accounting tokens
     * @param amount losses to burn
     */
    function processLosses(uint256 amount) external onlyRole(LOSS_PROCESSOR_ROLE) checkAndResetCooldown {
        AccountingModuleStorage storage s = _getAccountingModuleStorage();
        uint256 totalSupply = s.accountingToken.totalSupply();
        if (totalSupply < 10 ** s.accountingToken.decimals()) revert TvlTooLow();

        // check bound on losses
        if (amount > totalSupply * s.lowerBound / DIVISOR) {
            revert LossLimitsExceeded(amount, totalSupply * s.lowerBound / DIVISOR);
        }

        s.accountingToken.burnFrom(s.strategy, amount);
        IVault(s.strategy).processAccounting();

        createStrategySnapshot();
    }

    /// ADMIN ///

    /**
     * @notice Set target APY to determine upper bound. e.g. 1000 = 10% APY
     * @param targetApyInBips in bips
     * @dev hard max of 100% targetApy
     */
    function setTargetApy(uint256 targetApyInBips) external onlyRole(SAFE_MANAGER_ROLE) {
        _setTargetApy(targetApyInBips);
    }

    /**
     * @notice Set lower bound as a function of tvl for losses. e.g. 1000 = 10% of tvl
     * @param _lowerBound in bips, as a function of % of tvl
     * @dev hard max of 50% of tvl
     */
    function setLowerBound(uint256 _lowerBound) external onlyRole(SAFE_MANAGER_ROLE) {
        _setLowerBound(_lowerBound);
    }

    /**
     * @notice Set cooldown in seconds between every processing of rewards/losses
     * @param cooldownSeconds_ new cooldown seconds
     */
    function setCooldownSeconds(uint16 cooldownSeconds_) external onlyRole(SAFE_MANAGER_ROLE) {
        _setCooldownSeconds(cooldownSeconds_);
    }

    /**
     * @notice Set a new safe address
     * @param newSafe new safe address
     */
    function setSafeAddress(address newSafe) external virtual onlyRole(SAFE_MANAGER_ROLE) {
        _setSafeAddress(newSafe);
    }

    /// ADMIN INTERNAL SETTERS ///

    function _setTargetApy(uint256 targetApyInBips) internal {
        AccountingModuleStorage storage s = _getAccountingModuleStorage();
        if (targetApyInBips > 10 * DIVISOR) revert InvariantViolation();

        emit TargetApyUpdated(targetApyInBips, s.targetApy);
        s.targetApy = targetApyInBips;
    }

    function _setLowerBound(uint256 _lowerBound) internal {
        AccountingModuleStorage storage s = _getAccountingModuleStorage();
        if (_lowerBound > MAX_LOWER_BOUND) revert InvariantViolation();

        emit LowerBoundUpdated(_lowerBound, s.lowerBound);
        s.lowerBound = _lowerBound;
    }

    function _setCooldownSeconds(uint16 cooldownSeconds_) internal {
        AccountingModuleStorage storage s = _getAccountingModuleStorage();
        emit CooldownSecondsUpdated(cooldownSeconds_, s.cooldownSeconds);
        s.cooldownSeconds = cooldownSeconds_;
    }

    function _setSafeAddress(address newSafe) internal {
        AccountingModuleStorage storage s = _getAccountingModuleStorage();
        if (newSafe == address(0)) revert ZeroAddress();
        emit SafeUpdated(newSafe, s.safe);
        s.safe = newSafe;
    }

    /// VIEWS ///

    function baseAsset() external view returns (address) {
        return _getAccountingModuleStorage().baseAsset;
    }

    function strategy() external view returns (address) {
        return _getAccountingModuleStorage().strategy;
    }

    function accountingToken() external view returns (IAccountingToken) {
        return _getAccountingModuleStorage().accountingToken;
    }

    function cooldownSeconds() external view returns (uint16) {
        return _getAccountingModuleStorage().cooldownSeconds;
    }

    function lowerBound() external view returns (uint256) {
        return _getAccountingModuleStorage().lowerBound;
    }

    function nextUpdateWindow() external view returns (uint64) {
        return _getAccountingModuleStorage().nextUpdateWindow;
    }

    function safe() external view returns (address) {
        return _getAccountingModuleStorage().safe;
    }

    function targetApy() external view returns (uint256) {
        return _getAccountingModuleStorage().targetApy;
    }

    function snapshotsLength() external view returns (uint256) {
        return _getAccountingModuleStorage()._snapshots.length;
    }

    function snapshots(uint256 index) external view returns (StrategySnapshot memory) {
        return _getAccountingModuleStorage()._snapshots[index];
    }

    function lastSnapshot() external view returns (StrategySnapshot memory) {
        AccountingModuleStorage storage s = _getAccountingModuleStorage();
        return s._snapshots[s._snapshots.length - 1];
    }
}
"
    },
    "lib/yieldnest-flex-strategy/script/roles/BaseRoles.sol": {
      "content": "// SPDX-License-Identifier: BSD Clause-3
pragma solidity ^0.8.28;

import { IActors } from "@yieldnest-vault-script/Actors.sol";
import { FlexStrategy } from "src/FlexStrategy.sol";
import { AccountingModule } from "src/AccountingModule.sol";
import { AccountingToken } from "src/AccountingToken.sol";

library BaseRoles {
    function configureDefaultRoles(
        FlexStrategy strategy,
        AccountingModule accountingModule,
        AccountingToken accountingToken,
        address timelock,
        IActors actors
    )
        internal
    {
        // set admin roles
        strategy.grantRole(strategy.DEFAULT_ADMIN_ROLE(), actors.ADMIN());
        strategy.grantRole(strategy.PROCESSOR_ROLE(), actors.PROCESSOR());
        strategy.grantRole(strategy.PAUSER_ROLE(), actors.PAUSER());
        strategy.grantRole(strategy.UNPAUSER_ROLE(), actors.UNPAUSER());

        // set timelock roles
        strategy.grantRole(strategy.PROVIDER_MANAGER_ROLE(), timelock);
        strategy.grantRole(strategy.ASSET_MANAGER_ROLE(), timelock);
        strategy.grantRole(strategy.BUFFER_MANAGER_ROLE(), timelock);
        strategy.grantRole(strategy.PROCESSOR_MANAGER_ROLE(), timelock);
        strategy.grantRole(strategy.ALLOCATOR_MANAGER_ROLE(), timelock);
        accountingModule.grantRole(accountingModule.SAFE_MANAGER_ROLE(), timelock);
        accountingModule.grantRole(accountingModule.DEFAULT_ADMIN_ROLE(), actors.ADMIN());
        accountingToken.grantRole(accountingToken.DEFAULT_ADMIN_ROLE(), actors.ADMIN());
    }

    function configureDefaultRolesStrategy(
        FlexStrategy strategy,
        AccountingModule accountingModule,
        AccountingToken accountingToken,
        address timelock,
        IActors actors
    )
        internal
    {
        configureDefaultRoles(strategy, accountingModule, accountingToken, timelock, actors);
    }

    function configureTemporaryRoles(
        FlexStrategy strategy,
        AccountingModule accountingModule,
        AccountingToken accountingToken,
        address deployer
    )
        internal
    {
        strategy.grantRole(strategy.DEFAULT_ADMIN_ROLE(), deployer);
        strategy.grantRole(strategy.PROCESSOR_MANAGER_ROLE(), deployer);
        strategy.grantRole(strategy.BUFFER_MANAGER_ROLE(), deployer);
        strategy.grantRole(strategy.PROVIDER_MANAGER_ROLE(), deployer);
        strategy.grantRole(strategy.ASSET_MANAGER_ROLE(), deployer);
        strategy.grantRole(strategy.UNPAUSER_ROLE(), deployer);
        strategy.grantRole(strategy.ALLOCATOR_MANAGER_ROLE(), deployer);
        accountingToken.grantRole(accountingToken.DEFAULT_ADMIN_ROLE(), deployer);
        accountingModule.grantRole(accountingModule.DEFAULT_ADMIN_ROLE(), deployer);
    }

    function configureTemporaryRolesStrategy(
        FlexStrategy strategy,
        AccountingModule accountingModule,
        AccountingToken accountingToken,
        address deployer
    )
        internal
    {
        configureTemporaryRoles(strategy, accountingModule, accountingToken, deployer);
    }

    function renounceTemporaryRoles(
        FlexStrategy strategy,
        AccountingModule accountingModule,
        AccountingToken accountingToken,
        address deployer
    )
        internal
    {
        strategy.renounceRole(strategy.DEFAULT_ADMIN_ROLE(), deployer);
        strategy.renounceRole(strategy.PROCESSOR_MANAGER_ROLE(), deployer);
        strategy.renounceRole(strategy.BUFFER_MANAGER_ROLE(), deployer);
        strategy.renounceRole(strategy.PROVIDER_MANAGER_ROLE(), deployer);
        strategy.renounceRole(strategy.ASSET_MANAGER_ROLE(), deployer);
        strategy.renounceRole(strategy.UNPAUSER_ROLE(), deployer);
        strategy.renounceRole(strategy.ALLOCATOR_MANAGER_ROLE(), deployer);
        accountingToken.renounceRole(accountingToken.DEFAULT_ADMIN_ROLE(), deployer);
        accountingModule.renounceRole(accountingModule.DEFAULT_ADMIN_ROLE(), deployer);
    }

    function renounceTemporaryRolesStrategy(
        FlexStrategy strategy,
        AccountingModule accountingModule,
        AccountingToken accountingToken,
        address deployer
    )
        internal
    {
        renounceTemporaryRoles(strategy, accountingModule, accountingToken, deployer);
    }
}
"
    },
    "lib/yieldnest-flex-strategy/script/rules/FlexStrategyRules.sol": {
      "content": "// SPDX-License-Identifier: BSD-3-Clause
pragma solidity ^0.8.24;

import { IValidator } from "@yieldnest-vault/interface/IVault.sol";
import { SafeRules, IVault } from "@yieldnest-vault-script/rules/SafeRules.sol";

library FlexStrategyRules {
    function getDepositRule(address contractAddress) internal pure returns (SafeRules.RuleParams memory) {
        bytes4 funcSig = bytes4(keccak256("deposit(uint256)"));

        IVault.ParamRule[] memory paramRules = new IVault.ParamRule[](1);

        paramRules[0] =
            IVault.ParamRule({ paramType: IVault.ParamType.UINT256, isArray: false, allowList: new address[](0) });

        IVault.FunctionRule memory rule =
            IVault.FunctionRule({ isActive: true, paramRules: paramRules, validator: IValidator(address(0)) });

        return SafeRules.RuleParams({ contractAddress: contractAddress, funcSig: funcSig, rule: rule });
    }

    function getWithdrawRule(
        address contractAddress,
        address receiver
    )
        internal
        pure
        returns (SafeRules.RuleParams memory)
    {
        bytes4 funcSig = bytes4(keccak256("withdraw(uint256,address)"));

        IVault.ParamRule[] memory paramRules = new IVault.ParamRule[](2);

        paramRules[0] =
            IVault.ParamRule({ paramType: IVault.ParamType.UINT256, isArray: false, allowList: new address[](0) });

        address[] memory allowList = new address[](1);
        allowList[0] = address(receiver);

        paramRules[1] = IVault.ParamRule({ paramType: IVault.ParamType.ADDRESS, isArray: false, allowList: allowList });

        IVault.FunctionRule memory rule =
            IVault.FunctionRule({ isActive: true, paramRules: paramRules, validator: IValidator(address(0)) });

        return SafeRules.RuleParams({ contractAddress: contractAddress, funcSig: funcSig, rule: rule });
    }
}
"
    },
    "lib/yieldnest-flex-strategy/lib/yieldnest-vault/script/rules/SafeRules.sol": {
      "content": "// SPDX-License-Identifier: BSD-3-Clause
pragma solidity ^0.8.24;

import {IVault} from "src/interface/IVault.sol";

library SafeRules {
    error RuleAlreadyExists();

    struct RuleParams {
        address contractAddress;
        bytes4 funcSig;
        IVault.FunctionRule rule;
    }

    function setProcessorRule(IVault vault_, RuleParams memory params) internal {
        setProcessorRule(vault_, params, false);
    }

    function setProcessorRule(IVault vault_, RuleParams memory params, bool force) internal {
        if (force) {
            vault_.setProcessorRule(params.contractAddress, params.funcSig, params.rule);
            return;
        }
        IVault.FunctionRule memory existingRule = vault_.getProcessorRule(params.contractAddress, params.funcSig);
        if (
            existingRule.isActive || address(existingRule.validator) != address(0) || existingRule.paramRules.length > 0
        ) {
            revert RuleAlreadyExists();
        }
        vault_.setProcessorRule(params.contractAddress, params.funcSig, params.rule);
    }

    function setProcessorRules(IVault vault_, RuleParams[] memory params) internal {
        setProcessorRules(vault_,

Tags:
ERC20, ERC165, Multisig, Mintable, Pausable, Swap, Staking, Yield, Voting, Timelock, Upgradeable, Multi-Signature, Factory|addr:0x728086662f6d83eed41606f0ec0dc0f45217eba6|verified:true|block:23387131|tx:0xa48de0f2bdce71735be0cb592b1af2eb6422ed3f43252c881a848b3253c44806|first_check:1758189637

Submitted on: 2025-09-18 12:00:39

Comments

Log in to comment.

No comments yet.