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_,
Submitted on: 2025-09-18 12:00:39
Comments
Log in to comment.
No comments yet.