diff --git a/contracts/accountants/GenericAccountant.vy b/contracts/accountants/GenericAccountant.vy index 00942b1..70a2a73 100644 --- a/contracts/accountants/GenericAccountant.vy +++ b/contracts/accountants/GenericAccountant.vy @@ -11,12 +11,12 @@ It is designed to be able to be added to any number of vaults with any underlying tokens. There is a default fee config that will be used for - any strategy that reports through a vault thats has been added to this - accountant. But also gives the ability for the fee_manager to choose + any strategy that reports through a vault that has been added to this + accountant. But also gives the ability for the feeManager to choose custom values for any value for any given strategy they want to. Funds received from the vaults can either be distributed to a specified - fee_recipient or redeemed for the underlying asset and held within this + feeRecipient or redeemed for the underlying asset and held within this contract until distributed. """ from vyper.interfaces import ERC20 @@ -31,34 +31,41 @@ struct StrategyParams: interface IVault: def asset() -> address: view def strategies(strategy: address) -> StrategyParams: view - def withdraw(amount: uint256, receiver: address, owner: address) -> uint256: nonpayable + def withdraw(amount: uint256, receiver: address, owner: address, maxLoss: uint256) -> uint256: nonpayable ### EVENTS ### event VaultChanged: - vault: address + vault: indexed(address) change: ChangeType event UpdateDefaultFeeConfig: - default_fee_config: Fee + defaultFeeConfig: Fee event SetFutureFeeManager: - future_fee_manager: address + futureFeeManager: indexed(address) event NewFeeManager: - fee_manager: address + feeManager: indexed(address) event UpdateFeeRecipient: - old_fee_recipient: address - new_fee_recipient: address + oldFeeRecipient: indexed(address) + newFeeRecipient: indexed(address) event UpdateCustomFeeConfig: - vault: address - strategy: address - custom_config: Fee + vault: indexed(address) + strategy: indexed(address) + customConfig: Fee + +event RemovedCustomFeeConfig: + vault: indexed(address) + strategy: indexed(address) + +event UpdateMaxLoss: + maxLoss: uint256 event DistributeRewards: - token: address + token: indexed(address) rewards: uint256 ### ENUMS ### @@ -74,16 +81,14 @@ enum ChangeType: # i.e. 10_000 == 100%. struct Fee: # Annual management fee to charge on strategy debt. - management_fee: uint16 + managementFee: uint16 # Performance fee to charge on reported gain. - performance_fee: uint16 + performanceFee: uint16 # Ratio of reported loss to attempt to refund. - refund_ratio: uint16 + refundRatio: uint16 # Max percent of the reported gain that the accountant can take. - # A max_fee of 0 will mean non is enforced. - max_fee: uint16 - # Bool set for custom fee configs - custom: bool + # A maxFee of 0 will mean none is enforced. + maxFee: uint16 ### CONSTANTS ### @@ -99,22 +104,29 @@ MAX_BPS: constant(uint256) = 10_000 # 365.2425 * 86400 = 31556952.0 SECS_PER_YEAR: constant(uint256) = 31_556_952 # 365.2425 days +PERFORMANCE_FEE_THRESHOLD: constant(uint16) = 5_000 + +MANAGEMENT_FEE_THRESHOLD: constant(uint16) = 200 ### STORAGE ### # Address in charge of the accountant. -fee_manager: public(address) +feeManager: public(address) # Address to become the fee manager. -future_fee_manager: public(address) +futureFeeManager: public(address) # Address to distribute the accumulated fees to. -fee_recipient: public(address) +feeRecipient: public(address) +# Max loss variable to use on withdraws. +maxLoss: public(uint256) # Mapping of vaults that this serves as an accountant for. vaults: public(HashMap[address, bool]) # Default config to use unless a custom one is set. -default_config: public(Fee) +defaultConfig: public(Fee) # Mapping vault => strategy => custom Fee config -fees: public(HashMap[address, HashMap[address, Fee]]) +customConfig: public(HashMap[address, HashMap[address, Fee]]) +# Mapping vault => strategy => flag to use a custom config. +custom: public(HashMap[address, HashMap[address, bool]]) @external def __init__( @@ -123,35 +135,35 @@ def __init__( default_management: uint16, default_performance: uint16, default_refund: uint16, - default_max: uint16 + default_maxFee: uint16 ): """ @notice Initialize the accountant and default fee config. @param fee_manager Address to be in charge of this accountant. + @param fee_recipient Address to receive fees. @param default_management Default annual management fee to charge. @param default_performance Default performance fee to charge. @param default_refund Default refund ratio to give back on losses. - @param default_max Default max fee to allow as a percent of gain. + @param default_maxFee Default max fee to allow as a percent of gain. """ assert fee_manager != empty(address), "ZERO ADDRESS" assert fee_recipient != empty(address), "ZERO ADDRESS" - assert default_management <= self._management_fee_threshold(), "exceeds management fee threshold" - assert default_performance <= self._performance_fee_threshold(), "exceeds performance fee threshold" + assert default_management <= MANAGEMENT_FEE_THRESHOLD, "exceeds management fee threshold" + assert default_performance <= PERFORMANCE_FEE_THRESHOLD, "exceeds performance fee threshold" # Set initial addresses - self.fee_manager = fee_manager - self.fee_recipient = fee_recipient + self.feeManager = fee_manager + self.feeRecipient = fee_recipient # Set the default fee config - self.default_config = Fee({ - management_fee: default_management, - performance_fee: default_performance, - refund_ratio: default_refund, - max_fee: default_max, - custom: False + self.defaultConfig = Fee({ + managementFee: default_management, + performanceFee: default_performance, + refundRatio: default_refund, + maxFee: default_maxFee }) - log UpdateDefaultFeeConfig(self.default_config) + log UpdateDefaultFeeConfig(self.defaultConfig) @external @@ -160,7 +172,7 @@ def report(strategy: address, gain: uint256, loss: uint256) -> (uint256, uint256 @notice To be called by a vault during the process_report in which the accountant will charge fees based on the gain or loss the strategy is reporting. @dev Can only be called by a vault that has been added to this accountant. - Will default to the default_config for all amounts unless a custom config + Will default to the defaultConfig for all amounts unless a custom config has been set for a specific strategy. @param strategy The strategy that is reporting. @param gain The profit the strategy is reporting if any. @@ -169,81 +181,78 @@ def report(strategy: address, gain: uint256, loss: uint256) -> (uint256, uint256 # Make sure this is a valid vault. assert self.vaults[msg.sender], "!authorized" - # Load the custom config to check the `custom` flag. - # This should just be one slot. - fee: Fee = self.fees[msg.sender][strategy] + # Declare the config to use + fee: Fee = empty(Fee) - # If not use the default. - if not fee.custom: - fee = self.default_config + # Check if it there is a custom config to use. + if self.custom[msg.sender][strategy]: + fee = self.customConfig[msg.sender][strategy] + else: + # Otherwise use the default. + fee = self.defaultConfig total_fees: uint256 = 0 total_refunds: uint256 = 0 # Charge management fees no matter gain or loss. - if fee.management_fee > 0: + if fee.managementFee > 0: # Retrieve the strategies params from the vault. strategy_params: StrategyParams = IVault(msg.sender).strategies(strategy) # Time since last harvest. duration: uint256 = block.timestamp - strategy_params.last_report - # management_fee is an annual amount, so charge based on the time passed. + # managementFee is an annual amount, so charge based on the time passed. total_fees = ( strategy_params.current_debt * duration - * convert(fee.management_fee, uint256) + * convert(fee.managementFee, uint256) / MAX_BPS / SECS_PER_YEAR ) # Only charge performance fees if there is a gain. if gain > 0: - total_fees += (gain * convert(fee.performance_fee, uint256)) / MAX_BPS + total_fees += (gain * convert(fee.performanceFee, uint256)) / MAX_BPS else: # Means we should have a loss. - if fee.refund_ratio > 0: + if fee.refundRatio > 0: # Cache the underlying asset the vault uses. asset: address = IVault(msg.sender).asset() # Give back either all we have or based on refund ratio. - total_refunds = min(loss * convert(fee.refund_ratio, uint256) / MAX_BPS, ERC20(asset).balanceOf(self)) + total_refunds = min(loss * convert(fee.refundRatio, uint256) / MAX_BPS, ERC20(asset).balanceOf(self)) if total_refunds > 0: # Approve the vault to pull the underlying asset. self.erc20_safe_approve(asset, msg.sender, total_refunds) # 0 Max fee means it is not enforced. - if fee.max_fee > 0: - # Ensure fee does not exceed more than the max_fee %. - total_fees = min(gain * convert(fee.max_fee, uint256) / MAX_BPS, total_fees) + if fee.maxFee > 0: + # Ensure fee does not exceed the maxFee %. + total_fees = min(gain * convert(fee.maxFee, uint256) / MAX_BPS, total_fees) return (total_fees, total_refunds) @internal def erc20_safe_approve(token: address, spender: address, amount: uint256): - # Used only to send tokens that are not the type managed by this Vault. - # HACK: Used to handle non-compliant tokens like USDT - response: Bytes[32] = raw_call( - token, - concat( - method_id("approve(address,uint256)"), - convert(spender, bytes32), - convert(amount, bytes32), - ), - max_outsize=32, - ) - if len(response) > 0: - assert convert(response, bool), "approval failed!" + # Used only to approve tokens that are not the type managed by this vault. + # Used to handle non-compliant tokens like USDT + assert ERC20(token).approve(spender, amount, default_return_value=True), "approval failed" + +@internal +def _erc20_safe_transfer(token: address, receiver: address, amount: uint256): + # Used only to send tokens that are not the type managed by this vault. + assert ERC20(token).transfer(receiver, amount, default_return_value=True), "transfer failed" @external -def add_vault(vault: address): +def addVault(vault: address): """ @notice Add a new vault for this accountant to charge fees for. @dev This is not used to set any of the fees for the specific vault or strategy. Each fee will be set separately. @param vault The address of a vault to allow to use this accountant. """ - assert msg.sender == self.fee_manager, "not fee manager" + assert msg.sender == self.feeManager, "!fee manager" assert not self.vaults[vault], "already added" self.vaults[vault] = True @@ -252,12 +261,12 @@ def add_vault(vault: address): @external -def remove_vault(vault: address): +def removeVault(vault: address): """ @notice Removes a vault for this accountant to charge fee for. @param vault The address of a vault to allow to use this accountant. """ - assert msg.sender == self.fee_manager, "not fee manager" + assert msg.sender == self.feeManager, "!fee manager" assert self.vaults[vault], "not added" self.vaults[vault] = False @@ -266,42 +275,41 @@ def remove_vault(vault: address): @external -def update_default_config( +def updateDefaultConfig( default_management: uint16, default_performance: uint16, default_refund: uint16, - default_max: uint16 + default_maxFee: uint16 ): """ @notice Update the default config used for all strategies. @param default_management Default annual management fee to charge. @param default_performance Default performance fee to charge. @param default_refund Default refund ratio to give back on losses. - @param default_max Default max fee to allow as a percent of gain. + @param default_maxFee Default max fee to allow as a percent of gain. """ - assert msg.sender == self.fee_manager, "not fee manager" - assert default_management <= self._management_fee_threshold(), "exceeds management fee threshold" - assert default_performance <= self._performance_fee_threshold(), "exceeds performance fee threshold" - - self.default_config = Fee({ - management_fee: default_management, - performance_fee: default_performance, - refund_ratio: default_refund, - max_fee: default_max, - custom: False + assert msg.sender == self.feeManager, "!fee manager" + assert default_management <= MANAGEMENT_FEE_THRESHOLD, "exceeds management fee threshold" + assert default_performance <= PERFORMANCE_FEE_THRESHOLD, "exceeds performance fee threshold" + + self.defaultConfig = Fee({ + managementFee: default_management, + performanceFee: default_performance, + refundRatio: default_refund, + maxFee: default_maxFee }) - log UpdateDefaultFeeConfig(self.default_config) + log UpdateDefaultFeeConfig(self.defaultConfig) @external -def set_custom_config( +def setCustomConfig( vault: address, strategy: address, custom_management: uint16, custom_performance: uint16, custom_refund: uint16, - custom_max: uint16 + custom_maxFee: uint16 ): """ @notice Used to set a custom fee amounts for a specific strategy. @@ -312,49 +320,59 @@ def set_custom_config( @param custom_management Custom annual management fee to charge. @param custom_performance Custom performance fee to charge. @param custom_refund Custom refund ratio to give back on losses. - @param custom_max Custom max fee to allow as a percent of gain. + @param custom_maxFee Custom max fee to allow as a percent of gain. """ - assert msg.sender == self.fee_manager, "not fee manager" + assert msg.sender == self.feeManager, "!fee manager" assert self.vaults[vault], "vault not added" - assert custom_management <= self._management_fee_threshold(), "exceeds management fee threshold" - assert custom_performance <= self._performance_fee_threshold(), "exceeds performance fee threshold" + assert custom_management <= MANAGEMENT_FEE_THRESHOLD, "exceeds management fee threshold" + assert custom_performance <= PERFORMANCE_FEE_THRESHOLD, "exceeds performance fee threshold" # Set this strategies custom config. - self.fees[vault][strategy] = Fee({ - management_fee: custom_management, - performance_fee: custom_performance, - refund_ratio: custom_refund, - max_fee: custom_max, - custom: True + self.customConfig[vault][strategy] = Fee({ + managementFee: custom_management, + performanceFee: custom_performance, + refundRatio: custom_refund, + maxFee: custom_maxFee }) - log UpdateCustomFeeConfig(vault, strategy, self.fees[vault][strategy]) + # Make sure flag is declared as True. + self.custom[vault][strategy] = True + + log UpdateCustomFeeConfig(vault, strategy, self.customConfig[vault][strategy]) @external -def remove_custom_config(vault: address, strategy: address): +def removeCustomConfig(vault: address, strategy: address): """ @notice Removes a previously set custom config for a strategy. @param strategy The strategy to remove custom setting for. """ - assert msg.sender == self.fee_manager, "not fee manager" - assert self.fees[vault][strategy].custom, "No custom fees set" + assert msg.sender == self.feeManager, "!fee manager" + assert self.custom[vault][strategy], "No custom fees set" # Set all the strategies custom fees to 0. - self.fees[vault][strategy] = Fee({ - management_fee: 0, - performance_fee: 0, - refund_ratio: 0, - max_fee: 0, - custom: False - }) + self.customConfig[vault][strategy] = empty(Fee) + # Turn off the flag. + self.custom[vault][strategy] = False # Emit relevant event. - log UpdateCustomFeeConfig(vault, strategy, self.fees[vault][strategy]) + log RemovedCustomFeeConfig(vault, strategy) + +@external +def setMaxLoss(maxLoss: uint256): + """ + @notice Set the max loss parameter to be used on withdraws. + @param maxLoss Amount in basis points. + """ + assert msg.sender == self.feeManager, "!fee manager" + assert maxLoss <= MAX_BPS, "higher than 100%" + + self.maxLoss = maxLoss + log UpdateMaxLoss(maxLoss) @external -def withdraw_underlying(vault: address, amount: uint256): +def withdrawUnderlying(vault: address, amount: uint256): """ @notice Can be used by the fee manager to simply withdraw the underlying asset from a vault it charges fees for. @@ -364,8 +382,8 @@ def withdraw_underlying(vault: address, amount: uint256): @param vault The vault to redeem from. @param amount The amount in the underlying to withdraw. """ - assert msg.sender == self.fee_manager, "not fee manager" - IVault(vault).withdraw(amount, self, self) + assert msg.sender == self.feeManager, "!fee manager" + IVault(vault).withdraw(amount, self, self, self.maxLoss) @external @@ -377,108 +395,71 @@ def distribute(token: address) -> uint256: @param token The token to distribute. @return The amount of token distributed. """ - assert msg.sender == self.fee_manager, "not fee manager" + assert msg.sender == self.feeManager, "!fee manager" rewards: uint256 = ERC20(token).balanceOf(self) - self._erc20_safe_transfer(token, self.fee_recipient, rewards) + self._erc20_safe_transfer(token, self.feeRecipient, rewards) log DistributeRewards(token, rewards) return rewards -@internal -def _erc20_safe_transfer(token: address, receiver: address, amount: uint256): - # Used only to send tokens that are not the type managed by this Vault. - # HACK: Used to handle non-compliant tokens like USDT - response: Bytes[32] = raw_call( - token, - concat( - method_id("transfer(address,uint256)"), - convert(receiver, bytes32), - convert(amount, bytes32), - ), - max_outsize=32, - ) - if len(response) > 0: - assert convert(response, bool), "Transfer failed!" - - @external -def set_future_fee_manager(future_fee_manager: address): +def setFutureFeeManager(futureFeeManager: address): """ - @notice Step 1 of 2 to set a new fee_manager. - @dev The address is set to future_fee_manager and will need to - call accept_fee_manager in order to update the actual fee_manager. - @param future_fee_manager Address to set to future_fee_manager. + @notice Step 1 of 2 to set a new feeManager. + @dev The address is set to futureFeeManager and will need to + call accept_feeManager in order to update the actual feeManager. + @param futureFeeManager Address to set to futureFeeManager. """ - assert msg.sender == self.fee_manager, "not fee manager" - assert future_fee_manager != empty(address), "ZERO ADDRESS" - self.future_fee_manager = future_fee_manager + assert msg.sender == self.feeManager, "!fee manager" + assert futureFeeManager != empty(address), "ZERO ADDRESS" + self.futureFeeManager = futureFeeManager - log SetFutureFeeManager(future_fee_manager) + log SetFutureFeeManager(futureFeeManager) @external -def accept_fee_manager(): +def acceptFeeManager(): """ - @notice to be called by the future_fee_manager to accept the role change. + @notice to be called by the futureFeeManager to accept the role change. """ - assert msg.sender == self.future_fee_manager, "not future fee manager" - self.fee_manager = self.future_fee_manager - self.future_fee_manager = empty(address) + assert msg.sender == self.futureFeeManager, "not future fee manager" + self.feeManager = self.futureFeeManager + self.futureFeeManager = empty(address) log NewFeeManager(msg.sender) @external -def set_fee_recipient(new_fee_recipient: address): +def setFeeRecipient(newFeeRecipient: address): """ @notice Set a new address to receive distributed rewards. - @param new_fee_recipient Address to receive distributed fees. + @param newFeeRecipient Address to receive distributed fees. """ - assert msg.sender == self.fee_manager, "not fee manager" - assert new_fee_recipient != empty(address), "ZERO ADDRESS" - old_fee_recipient: address = self.fee_recipient - self.fee_recipient = new_fee_recipient + assert msg.sender == self.feeManager, "!fee manager" + assert newFeeRecipient != empty(address), "ZERO ADDRESS" + oldFeeRecipient: address = self.feeRecipient + self.feeRecipient = newFeeRecipient - log UpdateFeeRecipient(old_fee_recipient, new_fee_recipient) + log UpdateFeeRecipient(oldFeeRecipient, newFeeRecipient) @view @external -def performance_fee_threshold() -> uint16: +def performanceFeeThreshold() -> uint16: """ @notice External function to get the max a performance fee can be. @return Max performance fee the accountant can charge. """ - return self._performance_fee_threshold() - - -@view -@internal -def _performance_fee_threshold() -> uint16: - """ - @notice Internal function to get the max a performance fee can be. - @return Max performance fee the accountant can charge. - """ - return 5_000 + return PERFORMANCE_FEE_THRESHOLD @view @external -def management_fee_threshold() -> uint16: +def managementFeeThreshold() -> uint16: """ @notice External function to get the max a management fee can be. @return Max management fee the accountant can charge. """ - return self._management_fee_threshold() - - -@view -@internal -def _management_fee_threshold() -> uint16: - """ - @notice Internal function to get the max a management fee can be. - @return Max management fee the accountant can charge. - """ - return 200 \ No newline at end of file + return MANAGEMENT_FEE_THRESHOLD diff --git a/contracts/accountants/HelathCheckAccountant.sol b/contracts/accountants/HelathCheckAccountant.sol new file mode 100644 index 0000000..098659c --- /dev/null +++ b/contracts/accountants/HelathCheckAccountant.sol @@ -0,0 +1,499 @@ +// SPDX-License-Identifier: GNU AGPLv3 +pragma solidity 0.8.18; + +import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; + +import "@yearn-vaults/interfaces/IVault.sol"; + +contract HealthCheckAccountant { + using SafeERC20 for ERC20; + + /// @notice An event emitted when a vault is added or removed. + event VaultChanged(address indexed vault, ChangeType change); + + /// @notice An event emitted when the default fee configuration is updated. + event UpdateDefaultFeeConfig(Fee defaultFeeConfig); + + /// @notice An event emitted when the future fee manager is set. + event SetFutureFeeManager(address futureFeeManager); + + /// @notice An event emitted when a new fee manager is accepted. + event NewFeeManager(address feeManager); + + /// @notice An event emitted when the fee recipient is updated. + event UpdateFeeRecipient(address oldFeeRecipient, address newFeeRecipient); + + /// @notice An event emitted when a custom fee configuration is updated. + event UpdateCustomFeeConfig( + address vault, + address strategy, + Fee custom_config + ); + + /// @notice An event emitted when a custom fee configuration is removed. + event RemovedCustomFeeConfig( + address indexed vault, + address indexed strategy + ); + + /// @notice An event emitted when the `maxLoss` parameter is updated. + event UpdateMaxLoss(uint256 maxLoss); + + /// @notice An event emitted when rewards are distributed. + event DistributeRewards(address token, uint256 rewards); + + /// @notice Enum defining change types (added or removed). + enum ChangeType { + NULL, + ADDED, + REMOVED + } + + /// @notice Struct representing fee details. + struct Fee { + uint16 managementFee; // Annual management fee to charge. + uint16 performanceFee; // Performance fee to charge. + uint16 refundRatio; // Refund ratio to give back on losses. + uint16 maxFee; // Max fee allowed as a percent of gain. + uint16 maxGain; // Max percent gain a strategy can report. + uint16 maxLoss; // Max percent loss a strategy can report. + } + + modifier onlyFeeManager() { + _checkFeeManager(); + _; + } + + function _checkFeeManager() internal view virtual { + require(feeManager == msg.sender, "!fee manager"); + } + + /// @notice Constant defining the maximum basis points. + uint256 internal constant MAX_BPS = 10_000; + + /// @notice Constant defining the number of seconds in a year. + uint256 internal constant SECS_PER_YEAR = 31_556_952; + + /// @notice Constant defining the performance fee threshold. + uint16 internal constant PERFORMANCE_FEE_THRESHOLD = 5_000; + + /// @notice Constant defining the management fee threshold. + uint16 internal constant MANAGEMENT_FEE_THRESHOLD = 200; + + /// @notice The address of the fee manager. + address public feeManager; + + /// @notice The address of the future fee manager. + address public futureFeeManager; + + /// @notice The address of the fee recipient. + address public feeRecipient; + + uint256 public maxLoss; + + /// @notice Mapping to track added vaults. + mapping(address => bool) public vaults; + + /// @notice The default fee configuration. + Fee public defaultConfig; + + /// @notice Mapping vault => strategy => custom Fee config if any. + mapping(address => mapping(address => Fee)) public customConfig; + + /// @notice Mapping vault => strategy => flag to use a custom config. + mapping(address => mapping(address => uint256)) internal _custom; + + constructor( + address _feeManager, + address _feeRecipient, + uint16 defaultManagement, + uint16 defaultPerformance, + uint16 defaultRefund, + uint16 defaultMaxFee, + uint16 defaultMaxGain, + uint16 defaultMaxLoss + ) { + require(_feeManager != address(0), "ZERO ADDRESS"); + require(_feeRecipient != address(0), "ZERO ADDRESS"); + require( + defaultManagement <= MANAGEMENT_FEE_THRESHOLD, + "exceeds management fee threshold" + ); + require( + defaultPerformance <= PERFORMANCE_FEE_THRESHOLD, + "exceeds performance fee threshold" + ); + require(defaultMaxFee <= MAX_BPS, "too high"); + require(defaultMaxGain <= MAX_BPS, "too high"); + require(defaultMaxLoss <= MAX_BPS, "too high"); + + feeManager = _feeManager; + feeRecipient = _feeRecipient; + + defaultConfig = Fee({ + managementFee: defaultManagement, + performanceFee: defaultPerformance, + refundRatio: defaultRefund, + maxFee: defaultMaxFee, + maxGain: defaultMaxGain, + maxLoss: defaultMaxLoss + }); + + emit UpdateDefaultFeeConfig(defaultConfig); + } + + /** + * @notice Called by a vault when a `strategy` is reporting. + * @dev The msg.sender must have been added to the `vaults` mapping. + * @param strategy Address of the strategy reporting. + * @param gain Amount of the gain if any. + * @param loss Amount of the loss if any. + * @return totalFees if any to charge. + * @return totalRefunds if any for the vault to pull. + */ + function report( + address strategy, + uint256 gain, + uint256 loss + ) external returns (uint256 totalFees, uint256 totalRefunds) { + // Make sure this is a valid vault. + require(vaults[msg.sender], "!authorized"); + + // Declare the config to use + Fee memory fee; + + // Check if there is a custom config to use. + if (_custom[msg.sender][strategy] != 0) { + fee = customConfig[msg.sender][strategy]; + } else { + // Otherwise use the default. + fee = defaultConfig; + } + + // Retrieve the strategy's params from the vault. + IVault.StrategyParams memory strategyParams = IVault(msg.sender) + .strategies(strategy); + + // Charge management fees no matter gain or loss. + if (fee.managementFee > 0) { + // Time since the last harvest. + uint256 duration = block.timestamp - strategyParams.last_report; + // managementFee is an annual amount, so charge based on the time passed. + totalFees = ((strategyParams.current_debt * + duration * + (fee.managementFee)) / + MAX_BPS / + SECS_PER_YEAR); + } + + // Only charge performance fees if there is a gain. + if (gain > 0) { + require( + gain <= (strategyParams.current_debt * (fee.maxGain)) / MAX_BPS, + "too much gain" + ); + totalFees += (gain * (fee.performanceFee)) / MAX_BPS; + } else { + if (fee.maxLoss < MAX_BPS) { + require( + loss <= + (strategyParams.current_debt * (fee.maxLoss)) / MAX_BPS, + "too much loss" + ); + } + + // Means we should have a loss. + if (fee.refundRatio > 0) { + // Cache the underlying asset the vault uses. + address asset = IVault(msg.sender).asset(); + // Give back either all we have or based on the refund ratio. + totalRefunds = Math.min( + (loss * (fee.refundRatio)) / MAX_BPS, + ERC20(asset).balanceOf(address(this)) + ); + + if (totalRefunds > 0) { + // Approve the vault to pull the underlying asset. + ERC20(asset).safeApprove(msg.sender, totalRefunds); + } + } + } + + // 0 Max fee means it is not enforced. + if (fee.maxFee > 0) { + // Ensure fee does not exceed the maxFee %. + totalFees = Math.min((gain * (fee.maxFee)) / MAX_BPS, totalFees); + } + + return (totalFees, totalRefunds); + } + + /** + * @notice Function to add a new vault for this accountant to charge fees for. + * @dev This is not used to set any of the fees for the specific vault or strategy. Each fee will be set separately. + * @param vault The address of a vault to allow to use this accountant. + */ + function addVault(address vault) external onlyFeeManager { + // Ensure the vault has not already been added. + require(!vaults[vault], "already added"); + + vaults[vault] = true; + + emit VaultChanged(vault, ChangeType.ADDED); + } + + /** + * @notice Function to remove a vault from this accountant's fee charging list. + * @param vault The address of the vault to be removed from this accountant. + */ + function removeVault(address vault) external onlyFeeManager { + // Ensure the vault has been previously added. + require(vaults[vault], "not added"); + + vaults[vault] = false; + + emit VaultChanged(vault, ChangeType.REMOVED); + } + + /** + * @notice Function to update the default fee configuration used for all strategies. + * @param defaultManagement Default annual management fee to charge. + * @param defaultPerformance Default performance fee to charge. + * @param defaultRefund Default refund ratio to give back on losses. + * @param defaultMaxFee Default max fee to allow as a percent of gain. + * @param defaultMaxGain Default max percent gain a strategy can report. + * @param defaultMaxLoss Default max percent loss a strategy can report. + */ + function updateDefaultConfig( + uint16 defaultManagement, + uint16 defaultPerformance, + uint16 defaultRefund, + uint16 defaultMaxFee, + uint16 defaultMaxGain, + uint16 defaultMaxLoss + ) external onlyFeeManager { + // Check for threshold and limit conditions. + require( + defaultManagement <= MANAGEMENT_FEE_THRESHOLD, + "exceeds management fee threshold" + ); + require( + defaultPerformance <= PERFORMANCE_FEE_THRESHOLD, + "exceeds performance fee threshold" + ); + require(defaultMaxFee <= MAX_BPS, "too high"); + require(defaultMaxGain <= MAX_BPS, "too high"); + require(defaultMaxLoss <= MAX_BPS, "too high"); + + // Update the default fee configuration. + defaultConfig = Fee({ + managementFee: defaultManagement, + performanceFee: defaultPerformance, + refundRatio: defaultRefund, + maxFee: defaultMaxFee, + maxGain: defaultMaxGain, + maxLoss: defaultMaxLoss + }); + + emit UpdateDefaultFeeConfig(defaultConfig); + } + + /** + * @notice Function to set a custom fee configuration for a specific strategy in a specific vault. + * @param vault The vault the strategy is hooked up to. + * @param strategy The strategy to customize. + * @param customManagement Custom annual management fee to charge. + * @param customPerformance Custom performance fee to charge. + * @param customRefund Custom refund ratio to give back on losses. + * @param customMaxFee Custom max fee to allow as a percent of gain. + * @param customMaxGain Custom max percent gain a strategy can report. + * @param customMaxLoss Custom max percent loss a strategy can report. + */ + function setCustomConfig( + address vault, + address strategy, + uint16 customManagement, + uint16 customPerformance, + uint16 customRefund, + uint16 customMaxFee, + uint16 customMaxGain, + uint16 customMaxLoss + ) external onlyFeeManager { + // Ensure the vault has been added. + require(vaults[vault], "vault not added"); + // Check for threshold and limit conditions. + require( + customManagement <= MANAGEMENT_FEE_THRESHOLD, + "exceeds management fee threshold" + ); + require( + customPerformance <= PERFORMANCE_FEE_THRESHOLD, + "exceeds performance fee threshold" + ); + require(customMaxFee <= MAX_BPS, "too high"); + require(customMaxGain <= MAX_BPS, "too high"); + require(customMaxLoss <= MAX_BPS, "too high"); + + // Set the strategy's custom config. + customConfig[vault][strategy] = Fee({ + managementFee: customManagement, + performanceFee: customPerformance, + refundRatio: customRefund, + maxFee: customMaxFee, + maxGain: customMaxGain, + maxLoss: customMaxLoss + }); + + // Set the custom flag. + _custom[vault][strategy] = 1; + + emit UpdateCustomFeeConfig( + vault, + strategy, + customConfig[vault][strategy] + ); + } + + /** + * @notice Function to remove a previously set custom fee configuration for a strategy. + * @param vault The vault to remove custom setting for. + * @param strategy The strategy to remove custom setting for. + */ + function removeCustomConfig( + address vault, + address strategy + ) external onlyFeeManager { + // Ensure custom fees are set for the specified vault and strategy. + require(_custom[vault][strategy] != 0, "No custom fees set"); + + // Set all the strategy's custom fees to 0. + delete customConfig[vault][strategy]; + + // Clear the custom flag. + _custom[vault][strategy] = 0; + + // Emit relevant event. + emit RemovedCustomFeeConfig(vault, strategy); + } + + /** + * @notice Public getter to check for custom setting. + * @dev We use uint256 for the flag since its cheaper so this + * will convert it to a bool for easy view functions. + * + * @param vault Address of the vault. + * @param strategy Address of the strategy + * @return If a custom fee config is set. + */ + function custom( + address vault, + address strategy + ) external view returns (bool) { + return _custom[vault][strategy] != 0; + } + + /** + * @notice Function to withdraw the underlying asset from a vault. + * @param vault The vault to withdraw from. + * @param amount The amount in the underlying to withdraw. + */ + function withdrawUnderlying( + address vault, + uint256 amount + ) external onlyFeeManager { + IVault(vault).withdraw(amount, address(this), address(this), maxLoss); + } + + /** + * @notice Sets the `maxLoss` parameter to be used on withdraws. + * @param _maxLoss The amount in basis points to set as the maximum loss. + */ + function setMaxLoss(uint256 _maxLoss) external onlyFeeManager { + // Ensure that the provided `maxLoss` does not exceed 100% (in basis points). + require(_maxLoss <= MAX_BPS, "higher than 100%"); + + maxLoss = _maxLoss; + + // Emit an event to signal the update of the `maxLoss` parameter. + emit UpdateMaxLoss(_maxLoss); + } + + /** + * @notice Function to distribute all accumulated fees to the designated recipient. + * @param token The token to distribute. + */ + function distribute(address token) external { + distribute(token, ERC20(token).balanceOf(address(this))); + } + + /** + * @notice Function to distribute accumulated fees to the designated recipient. + * @param token The token to distribute. + * @param amount amount of token to distribute. + */ + function distribute(address token, uint256 amount) public onlyFeeManager { + ERC20(token).safeTransfer(feeRecipient, amount); + + emit DistributeRewards(token, amount); + } + + /** + * @notice Function to set a future fee manager address. + * @param _futureFeeManager The address to set as the future fee manager. + */ + function setFutureFeeManager( + address _futureFeeManager + ) external onlyFeeManager { + // Ensure the futureFeeManager is not a zero address. + require(_futureFeeManager != address(0), "ZERO ADDRESS"); + futureFeeManager = _futureFeeManager; + + emit SetFutureFeeManager(_futureFeeManager); + } + + /** + * @notice Function to accept the role change and become the new fee manager. + * @dev This function allows the future fee manager to accept the role change and become the new fee manager. + */ + function acceptFeeManager() external { + // Make sure the sender is the future fee manager. + require(msg.sender == futureFeeManager, "not future fee manager"); + feeManager = futureFeeManager; + futureFeeManager = address(0); + + emit NewFeeManager(msg.sender); + } + + /** + * @notice Function to set a new address to receive distributed rewards. + * @param newFeeRecipient Address to receive distributed fees. + */ + function setFeeRecipient(address newFeeRecipient) external onlyFeeManager { + // Ensure the newFeeRecipient is not a zero address. + require(newFeeRecipient != address(0), "ZERO ADDRESS"); + address oldRecipient = feeRecipient; + feeRecipient = newFeeRecipient; + + emit UpdateFeeRecipient(oldRecipient, newFeeRecipient); + } + + /** + * @notice View function to get the max a performance fee can be. + * @dev This function provides the maximum performance fee that the accountant can charge. + * @return The maximum performance fee. + */ + function performanceFeeThreshold() external view returns (uint16) { + return PERFORMANCE_FEE_THRESHOLD; + } + + /** + * @notice View function to get the max a management fee can be. + * @dev This function provides the maximum management fee that the accountant can charge. + * @return The maximum management fee. + */ + function managementFeeThreshold() external view returns (uint16) { + return MANAGEMENT_FEE_THRESHOLD; + } +} diff --git a/package-lock.json b/package-lock.json index d7f360a..909c94c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,8 +8,8 @@ "devDependencies": { "@commitlint/cli": "^17.0.0", "@commitlint/config-conventional": "^17.0.0", - "@openzeppelin/contracts": "^4.8.2", - "hardhat": "^2.13.0", + "@openzeppelin/contracts": "4.8.2", + "hardhat": "^2.18.2", "prettier": "^2.5.1", "prettier-plugin-solidity": "^1.0.0-beta.19", "pretty-quick": "^3.1.3", @@ -134,6 +134,32 @@ "node": ">=4" } }, + "node_modules/@chainsafe/as-sha256": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@chainsafe/as-sha256/-/as-sha256-0.3.1.tgz", + "integrity": "sha512-hldFFYuf49ed7DAakWVXSJODuq3pzJEguD8tQ7h+sGkM18vja+OFoJI9krnGmgzyuZC2ETX0NOIcCTy31v2Mtg==", + "dev": true + }, + "node_modules/@chainsafe/persistent-merkle-tree": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@chainsafe/persistent-merkle-tree/-/persistent-merkle-tree-0.4.2.tgz", + "integrity": "sha512-lLO3ihKPngXLTus/L7WHKaw9PnNJWizlOF1H9NNzHP6Xvh82vzg9F2bzkXhYIFshMZ2gTCEz8tq6STe7r5NDfQ==", + "dev": true, + "dependencies": { + "@chainsafe/as-sha256": "^0.3.1" + } + }, + "node_modules/@chainsafe/ssz": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/@chainsafe/ssz/-/ssz-0.9.4.tgz", + "integrity": "sha512-77Qtg2N1ayqs4Bg/wvnWfg5Bta7iy7IRh8XqXh7oNMeP2HBbBwx8m6yTpA8p0EHItWPEBkgZd5S5/LSlp3GXuQ==", + "dev": true, + "dependencies": { + "@chainsafe/as-sha256": "^0.3.1", + "@chainsafe/persistent-merkle-tree": "^0.4.2", + "case": "^1.6.3" + } + }, "node_modules/@commitlint/cli": { "version": "17.6.3", "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-17.6.3.tgz", @@ -636,6 +662,26 @@ "@ethersproject/bytes": "^5.7.0" } }, + "node_modules/@ethersproject/basex": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/basex/-/basex-5.7.0.tgz", + "integrity": "sha512-ywlh43GwZLv2Voc2gQVTKBoVQ1mti3d8HK5aMxsfu/nRDnMmNqaSJ3r3n85HBByT8OpoY96SXM1FogC533T4zw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/properties": "^5.7.0" + } + }, "node_modules/@ethersproject/bignumber": { "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.7.0.tgz", @@ -701,6 +747,34 @@ "@ethersproject/bignumber": "^5.7.0" } }, + "node_modules/@ethersproject/contracts": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/contracts/-/contracts-5.7.0.tgz", + "integrity": "sha512-5GJbzEU3X+d33CdfPhcyS+z8MzsTrBGk/sc+G+59+tPa9yFkl6HQ9D6L0QMgNTA9q8dT0XKxxkyp883XsQvbbg==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/abi": "^5.7.0", + "@ethersproject/abstract-provider": "^5.7.0", + "@ethersproject/abstract-signer": "^5.7.0", + "@ethersproject/address": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/transactions": "^5.7.0" + } + }, "node_modules/@ethersproject/hash": { "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/hash/-/hash-5.7.0.tgz", @@ -728,6 +802,67 @@ "@ethersproject/strings": "^5.7.0" } }, + "node_modules/@ethersproject/hdnode": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/hdnode/-/hdnode-5.7.0.tgz", + "integrity": "sha512-OmyYo9EENBPPf4ERhR7oj6uAtUAhYGqOnIS+jE5pTXvdKBS99ikzq1E7Iv0ZQZ5V36Lqx1qZLeak0Ra16qpeOg==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/abstract-signer": "^5.7.0", + "@ethersproject/basex": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/pbkdf2": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/sha2": "^5.7.0", + "@ethersproject/signing-key": "^5.7.0", + "@ethersproject/strings": "^5.7.0", + "@ethersproject/transactions": "^5.7.0", + "@ethersproject/wordlists": "^5.7.0" + } + }, + "node_modules/@ethersproject/json-wallets": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/json-wallets/-/json-wallets-5.7.0.tgz", + "integrity": "sha512-8oee5Xgu6+RKgJTkvEMl2wDgSPSAQ9MB/3JYjFV9jlKvcYHUXZC+cQp0njgmxdHkYWn8s6/IqIZYm0YWCjO/0g==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/abstract-signer": "^5.7.0", + "@ethersproject/address": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/hdnode": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/pbkdf2": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/random": "^5.7.0", + "@ethersproject/strings": "^5.7.0", + "@ethersproject/transactions": "^5.7.0", + "aes-js": "3.0.0", + "scrypt-js": "3.0.1" + } + }, "node_modules/@ethersproject/keccak256": { "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/keccak256/-/keccak256-5.7.0.tgz", @@ -783,6 +918,26 @@ "@ethersproject/logger": "^5.7.0" } }, + "node_modules/@ethersproject/pbkdf2": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/pbkdf2/-/pbkdf2-5.7.0.tgz", + "integrity": "sha512-oR/dBRZR6GTyaofd86DehG72hY6NpAjhabkhxgr3X2FpJtJuodEl2auADWBZfhDHgVCbu3/H/Ocq2uC6dpNjjw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/sha2": "^5.7.0" + } + }, "node_modules/@ethersproject/properties": { "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.7.0.tgz", @@ -802,6 +957,85 @@ "@ethersproject/logger": "^5.7.0" } }, + "node_modules/@ethersproject/providers": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/@ethersproject/providers/-/providers-5.7.2.tgz", + "integrity": "sha512-g34EWZ1WWAVgr4aptGlVBF8mhl3VWjv+8hoAnzStu8Ah22VHBsuGzP17eb6xDVRzw895G4W7vvx60lFFur/1Rg==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/abstract-provider": "^5.7.0", + "@ethersproject/abstract-signer": "^5.7.0", + "@ethersproject/address": "^5.7.0", + "@ethersproject/base64": "^5.7.0", + "@ethersproject/basex": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/hash": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/networks": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/random": "^5.7.0", + "@ethersproject/rlp": "^5.7.0", + "@ethersproject/sha2": "^5.7.0", + "@ethersproject/strings": "^5.7.0", + "@ethersproject/transactions": "^5.7.0", + "@ethersproject/web": "^5.7.0", + "bech32": "1.1.4", + "ws": "7.4.6" + } + }, + "node_modules/@ethersproject/providers/node_modules/ws": { + "version": "7.4.6", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", + "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==", + "dev": true, + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/@ethersproject/random": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/random/-/random-5.7.0.tgz", + "integrity": "sha512-19WjScqRA8IIeWclFme75VMXSBvi4e6InrUNuaR4s5pTF2qNhcGdCUwdxUVGtDDqC00sDLCO93jPQoDUH4HVmQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0" + } + }, "node_modules/@ethersproject/rlp": { "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/rlp/-/rlp-5.7.0.tgz", @@ -822,6 +1056,27 @@ "@ethersproject/logger": "^5.7.0" } }, + "node_modules/@ethersproject/sha2": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/sha2/-/sha2-5.7.0.tgz", + "integrity": "sha512-gKlH42riwb3KYp0reLsFTokByAKoJdgFCwI+CCiX/k+Jm2mbNs6oOaCjYQSlI1+XBVejwH2KrmCbMAT/GnRDQw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "hash.js": "1.1.7" + } + }, "node_modules/@ethersproject/signing-key": { "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/signing-key/-/signing-key-5.7.0.tgz", @@ -852,6 +1107,30 @@ "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", "dev": true }, + "node_modules/@ethersproject/solidity": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/solidity/-/solidity-5.7.0.tgz", + "integrity": "sha512-HmabMd2Dt/raavyaGukF4XxizWKhKQ24DoLtdNbBmNKUOPqwjsKQSdV9GQtj9CBEea9DlzETlVER1gYeXXBGaA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/sha2": "^5.7.0", + "@ethersproject/strings": "^5.7.0" + } + }, "node_modules/@ethersproject/strings": { "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.7.0.tgz", @@ -900,6 +1179,60 @@ "@ethersproject/signing-key": "^5.7.0" } }, + "node_modules/@ethersproject/units": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/units/-/units-5.7.0.tgz", + "integrity": "sha512-pD3xLMy3SJu9kG5xDGI7+xhTEmGXlEqXU4OfNapmfnxLVY4EMSSRp7j1k7eezutBPH7RBN/7QPnwR7hzNlEFeg==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/logger": "^5.7.0" + } + }, + "node_modules/@ethersproject/wallet": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/wallet/-/wallet-5.7.0.tgz", + "integrity": "sha512-MhmXlJXEJFBFVKrDLB4ZdDzxcBxQ3rLyCkhNqVu3CDYvR97E+8r01UgrI+TI99Le+aYm/in/0vp86guJuM7FCA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/abstract-provider": "^5.7.0", + "@ethersproject/abstract-signer": "^5.7.0", + "@ethersproject/address": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/hash": "^5.7.0", + "@ethersproject/hdnode": "^5.7.0", + "@ethersproject/json-wallets": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/random": "^5.7.0", + "@ethersproject/signing-key": "^5.7.0", + "@ethersproject/transactions": "^5.7.0", + "@ethersproject/wordlists": "^5.7.0" + } + }, "node_modules/@ethersproject/web": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/@ethersproject/web/-/web-5.7.1.tgz", @@ -923,6 +1256,29 @@ "@ethersproject/strings": "^5.7.0" } }, + "node_modules/@ethersproject/wordlists": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/wordlists/-/wordlists-5.7.0.tgz", + "integrity": "sha512-S2TFNJNfHWVHNE6cNDjbVlZ6MgE17MIxMbMg2zv3wn+3XSJGosL1m9ZVv3GXCf/2ymSsQ+hRI5IzoMJTG6aoVA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/hash": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/strings": "^5.7.0" + } + }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", @@ -995,34 +1351,36 @@ "license": "MIT" }, "node_modules/@nomicfoundation/ethereumjs-block": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-block/-/ethereumjs-block-4.2.2.tgz", - "integrity": "sha512-atjpt4gc6ZGZUPHBAQaUJsm1l/VCo7FmyQ780tMGO8QStjLdhz09dXynmhwVTy5YbRr0FOh/uX3QaEM0yIB2Zg==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-block/-/ethereumjs-block-5.0.2.tgz", + "integrity": "sha512-hSe6CuHI4SsSiWWjHDIzWhSiAVpzMUcDRpWYzN0T9l8/Rz7xNn3elwVOJ/tAyS0LqL6vitUD78Uk7lQDXZun7Q==", "dev": true, "dependencies": { - "@nomicfoundation/ethereumjs-common": "3.1.2", - "@nomicfoundation/ethereumjs-rlp": "4.0.3", - "@nomicfoundation/ethereumjs-trie": "5.0.5", - "@nomicfoundation/ethereumjs-tx": "4.1.2", - "@nomicfoundation/ethereumjs-util": "8.0.6", - "ethereum-cryptography": "0.1.3" + "@nomicfoundation/ethereumjs-common": "4.0.2", + "@nomicfoundation/ethereumjs-rlp": "5.0.2", + "@nomicfoundation/ethereumjs-trie": "6.0.2", + "@nomicfoundation/ethereumjs-tx": "5.0.2", + "@nomicfoundation/ethereumjs-util": "9.0.2", + "ethereum-cryptography": "0.1.3", + "ethers": "^5.7.1" }, "engines": { "node": ">=14" } }, "node_modules/@nomicfoundation/ethereumjs-blockchain": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-blockchain/-/ethereumjs-blockchain-6.2.2.tgz", - "integrity": "sha512-6AIB2MoTEPZJLl6IRKcbd8mUmaBAQ/NMe3O7OsAOIiDjMNPPH5KaUQiLfbVlegT4wKIg/GOsFH7XlH2KDVoJNg==", - "dev": true, - "dependencies": { - "@nomicfoundation/ethereumjs-block": "4.2.2", - "@nomicfoundation/ethereumjs-common": "3.1.2", - "@nomicfoundation/ethereumjs-ethash": "2.0.5", - "@nomicfoundation/ethereumjs-rlp": "4.0.3", - "@nomicfoundation/ethereumjs-trie": "5.0.5", - "@nomicfoundation/ethereumjs-util": "8.0.6", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-blockchain/-/ethereumjs-blockchain-7.0.2.tgz", + "integrity": "sha512-8UUsSXJs+MFfIIAKdh3cG16iNmWzWC/91P40sazNvrqhhdR/RtGDlFk2iFTGbBAZPs2+klZVzhRX8m2wvuvz3w==", + "dev": true, + "dependencies": { + "@nomicfoundation/ethereumjs-block": "5.0.2", + "@nomicfoundation/ethereumjs-common": "4.0.2", + "@nomicfoundation/ethereumjs-ethash": "3.0.2", + "@nomicfoundation/ethereumjs-rlp": "5.0.2", + "@nomicfoundation/ethereumjs-trie": "6.0.2", + "@nomicfoundation/ethereumjs-tx": "5.0.2", + "@nomicfoundation/ethereumjs-util": "9.0.2", "abstract-level": "^1.0.3", "debug": "^4.3.3", "ethereum-cryptography": "0.1.3", @@ -1050,24 +1408,24 @@ "dev": true }, "node_modules/@nomicfoundation/ethereumjs-common": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-common/-/ethereumjs-common-3.1.2.tgz", - "integrity": "sha512-JAEBpIua62dyObHM9KI2b4wHZcRQYYge9gxiygTWa3lNCr2zo+K0TbypDpgiNij5MCGNWP1eboNfNfx1a3vkvA==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-common/-/ethereumjs-common-4.0.2.tgz", + "integrity": "sha512-I2WGP3HMGsOoycSdOTSqIaES0ughQTueOsddJ36aYVpI3SN8YSusgRFLwzDJwRFVIYDKx/iJz0sQ5kBHVgdDwg==", "dev": true, "dependencies": { - "@nomicfoundation/ethereumjs-util": "8.0.6", + "@nomicfoundation/ethereumjs-util": "9.0.2", "crc-32": "^1.2.0" } }, "node_modules/@nomicfoundation/ethereumjs-ethash": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-ethash/-/ethereumjs-ethash-2.0.5.tgz", - "integrity": "sha512-xlLdcICGgAYyYmnI3r1t0R5fKGBJNDQSOQxXNjVO99JmxJIdXR5MgPo5CSJO1RpyzKOgzi3uIFn8agv564dZEQ==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-ethash/-/ethereumjs-ethash-3.0.2.tgz", + "integrity": "sha512-8PfoOQCcIcO9Pylq0Buijuq/O73tmMVURK0OqdjhwqcGHYC2PwhbajDh7GZ55ekB0Px197ajK3PQhpKoiI/UPg==", "dev": true, "dependencies": { - "@nomicfoundation/ethereumjs-block": "4.2.2", - "@nomicfoundation/ethereumjs-rlp": "4.0.3", - "@nomicfoundation/ethereumjs-util": "8.0.6", + "@nomicfoundation/ethereumjs-block": "5.0.2", + "@nomicfoundation/ethereumjs-rlp": "5.0.2", + "@nomicfoundation/ethereumjs-util": "9.0.2", "abstract-level": "^1.0.3", "bigint-crypto-utils": "^3.0.23", "ethereum-cryptography": "0.1.3" @@ -1077,15 +1435,15 @@ } }, "node_modules/@nomicfoundation/ethereumjs-evm": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-evm/-/ethereumjs-evm-1.3.2.tgz", - "integrity": "sha512-I00d4MwXuobyoqdPe/12dxUQxTYzX8OckSaWsMcWAfQhgVDvBx6ffPyP/w1aL0NW7MjyerySPcSVfDJAMHjilw==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-evm/-/ethereumjs-evm-2.0.2.tgz", + "integrity": "sha512-rBLcUaUfANJxyOx9HIdMX6uXGin6lANCulIm/pjMgRqfiCRMZie3WKYxTSd8ZE/d+qT+zTedBF4+VHTdTSePmQ==", "dev": true, "dependencies": { - "@nomicfoundation/ethereumjs-common": "3.1.2", - "@nomicfoundation/ethereumjs-util": "8.0.6", - "@types/async-eventemitter": "^0.2.1", - "async-eventemitter": "^0.2.4", + "@ethersproject/providers": "^5.7.1", + "@nomicfoundation/ethereumjs-common": "4.0.2", + "@nomicfoundation/ethereumjs-tx": "5.0.2", + "@nomicfoundation/ethereumjs-util": "9.0.2", "debug": "^4.3.3", "ethereum-cryptography": "0.1.3", "mcl-wasm": "^0.7.1", @@ -1096,9 +1454,9 @@ } }, "node_modules/@nomicfoundation/ethereumjs-rlp": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-rlp/-/ethereumjs-rlp-4.0.3.tgz", - "integrity": "sha512-DZMzB/lqPK78T6MluyXqtlRmOMcsZbTTbbEyAjo0ncaff2mqu/k8a79PBcyvpgAhWD/R59Fjq/x3ro5Lof0AtA==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-rlp/-/ethereumjs-rlp-5.0.2.tgz", + "integrity": "sha512-QwmemBc+MMsHJ1P1QvPl8R8p2aPvvVcKBbvHnQOKBpBztEo0omN0eaob6FeZS/e3y9NSe+mfu3nNFBHszqkjTA==", "dev": true, "bin": { "rlp": "bin/rlp" @@ -1108,28 +1466,28 @@ } }, "node_modules/@nomicfoundation/ethereumjs-statemanager": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-statemanager/-/ethereumjs-statemanager-1.0.5.tgz", - "integrity": "sha512-CAhzpzTR5toh/qOJIZUUOnWekUXuRqkkzaGAQrVcF457VhtCmr+ddZjjK50KNZ524c1XP8cISguEVNqJ6ij1sA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-statemanager/-/ethereumjs-statemanager-2.0.2.tgz", + "integrity": "sha512-dlKy5dIXLuDubx8Z74sipciZnJTRSV/uHG48RSijhgm1V7eXYFC567xgKtsKiVZB1ViTP9iFL4B6Je0xD6X2OA==", "dev": true, "dependencies": { - "@nomicfoundation/ethereumjs-common": "3.1.2", - "@nomicfoundation/ethereumjs-rlp": "4.0.3", - "@nomicfoundation/ethereumjs-trie": "5.0.5", - "@nomicfoundation/ethereumjs-util": "8.0.6", + "@nomicfoundation/ethereumjs-common": "4.0.2", + "@nomicfoundation/ethereumjs-rlp": "5.0.2", "debug": "^4.3.3", "ethereum-cryptography": "0.1.3", - "functional-red-black-tree": "^1.0.1" + "ethers": "^5.7.1", + "js-sdsl": "^4.1.4" } }, "node_modules/@nomicfoundation/ethereumjs-trie": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-trie/-/ethereumjs-trie-5.0.5.tgz", - "integrity": "sha512-+8sNZrXkzvA1NH5F4kz5RSYl1I6iaRz7mAZRsyxOm0IVY4UaP43Ofvfp/TwOalFunurQrYB5pRO40+8FBcxFMA==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-trie/-/ethereumjs-trie-6.0.2.tgz", + "integrity": "sha512-yw8vg9hBeLYk4YNg5MrSJ5H55TLOv2FSWUTROtDtTMMmDGROsAu+0tBjiNGTnKRi400M6cEzoFfa89Fc5k8NTQ==", "dev": true, "dependencies": { - "@nomicfoundation/ethereumjs-rlp": "4.0.3", - "@nomicfoundation/ethereumjs-util": "8.0.6", + "@nomicfoundation/ethereumjs-rlp": "5.0.2", + "@nomicfoundation/ethereumjs-util": "9.0.2", + "@types/readable-stream": "^2.3.13", "ethereum-cryptography": "0.1.3", "readable-stream": "^3.6.0" }, @@ -1138,14 +1496,16 @@ } }, "node_modules/@nomicfoundation/ethereumjs-tx": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-tx/-/ethereumjs-tx-4.1.2.tgz", - "integrity": "sha512-emJBJZpmTdUa09cqxQqHaysbBI9Od353ZazeH7WgPb35miMgNY6mb7/3vBA98N5lUW/rgkiItjX0KZfIzihSoQ==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-tx/-/ethereumjs-tx-5.0.2.tgz", + "integrity": "sha512-T+l4/MmTp7VhJeNloMkM+lPU3YMUaXdcXgTGCf8+ZFvV9NYZTRLFekRwlG6/JMmVfIfbrW+dRRJ9A6H5Q/Z64g==", "dev": true, "dependencies": { - "@nomicfoundation/ethereumjs-common": "3.1.2", - "@nomicfoundation/ethereumjs-rlp": "4.0.3", - "@nomicfoundation/ethereumjs-util": "8.0.6", + "@chainsafe/ssz": "^0.9.2", + "@ethersproject/providers": "^5.7.2", + "@nomicfoundation/ethereumjs-common": "4.0.2", + "@nomicfoundation/ethereumjs-rlp": "5.0.2", + "@nomicfoundation/ethereumjs-util": "9.0.2", "ethereum-cryptography": "0.1.3" }, "engines": { @@ -1153,38 +1513,55 @@ } }, "node_modules/@nomicfoundation/ethereumjs-util": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-util/-/ethereumjs-util-8.0.6.tgz", - "integrity": "sha512-jOQfF44laa7xRfbfLXojdlcpkvxeHrE2Xu7tSeITsWFgoII163MzjOwFEzSNozHYieFysyoEMhCdP+NY5ikstw==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-util/-/ethereumjs-util-9.0.2.tgz", + "integrity": "sha512-4Wu9D3LykbSBWZo8nJCnzVIYGvGCuyiYLIJa9XXNVt1q1jUzHdB+sJvx95VGCpPkCT+IbLecW6yfzy3E1bQrwQ==", "dev": true, "dependencies": { - "@nomicfoundation/ethereumjs-rlp": "4.0.3", + "@chainsafe/ssz": "^0.10.0", + "@nomicfoundation/ethereumjs-rlp": "5.0.2", "ethereum-cryptography": "0.1.3" }, "engines": { "node": ">=14" } }, + "node_modules/@nomicfoundation/ethereumjs-util/node_modules/@chainsafe/persistent-merkle-tree": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@chainsafe/persistent-merkle-tree/-/persistent-merkle-tree-0.5.0.tgz", + "integrity": "sha512-l0V1b5clxA3iwQLXP40zYjyZYospQLZXzBVIhhr9kDg/1qHZfzzHw0jj4VPBijfYCArZDlPkRi1wZaV2POKeuw==", + "dev": true, + "dependencies": { + "@chainsafe/as-sha256": "^0.3.1" + } + }, + "node_modules/@nomicfoundation/ethereumjs-util/node_modules/@chainsafe/ssz": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@chainsafe/ssz/-/ssz-0.10.2.tgz", + "integrity": "sha512-/NL3Lh8K+0q7A3LsiFq09YXS9fPE+ead2rr7vM2QK8PLzrNsw3uqrif9bpRX5UxgeRjM+vYi+boCM3+GM4ovXg==", + "dev": true, + "dependencies": { + "@chainsafe/as-sha256": "^0.3.1", + "@chainsafe/persistent-merkle-tree": "^0.5.0" + } + }, "node_modules/@nomicfoundation/ethereumjs-vm": { - "version": "6.4.2", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-vm/-/ethereumjs-vm-6.4.2.tgz", - "integrity": "sha512-PRTyxZMP6kx+OdAzBhuH1LD2Yw+hrSpaytftvaK//thDy2OI07S0nrTdbrdk7b8ZVPAc9H9oTwFBl3/wJ3w15g==", - "dev": true, - "dependencies": { - "@nomicfoundation/ethereumjs-block": "4.2.2", - "@nomicfoundation/ethereumjs-blockchain": "6.2.2", - "@nomicfoundation/ethereumjs-common": "3.1.2", - "@nomicfoundation/ethereumjs-evm": "1.3.2", - "@nomicfoundation/ethereumjs-rlp": "4.0.3", - "@nomicfoundation/ethereumjs-statemanager": "1.0.5", - "@nomicfoundation/ethereumjs-trie": "5.0.5", - "@nomicfoundation/ethereumjs-tx": "4.1.2", - "@nomicfoundation/ethereumjs-util": "8.0.6", - "@types/async-eventemitter": "^0.2.1", - "async-eventemitter": "^0.2.4", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-vm/-/ethereumjs-vm-7.0.2.tgz", + "integrity": "sha512-Bj3KZT64j54Tcwr7Qm/0jkeZXJMfdcAtRBedou+Hx0dPOSIgqaIr0vvLwP65TpHbak2DmAq+KJbW2KNtIoFwvA==", + "dev": true, + "dependencies": { + "@nomicfoundation/ethereumjs-block": "5.0.2", + "@nomicfoundation/ethereumjs-blockchain": "7.0.2", + "@nomicfoundation/ethereumjs-common": "4.0.2", + "@nomicfoundation/ethereumjs-evm": "2.0.2", + "@nomicfoundation/ethereumjs-rlp": "5.0.2", + "@nomicfoundation/ethereumjs-statemanager": "2.0.2", + "@nomicfoundation/ethereumjs-trie": "6.0.2", + "@nomicfoundation/ethereumjs-tx": "5.0.2", + "@nomicfoundation/ethereumjs-util": "9.0.2", "debug": "^4.3.3", "ethereum-cryptography": "0.1.3", - "functional-red-black-tree": "^1.0.1", "mcl-wasm": "^0.7.1", "rustbn.js": "~0.2.0" }, @@ -1249,11 +1626,10 @@ } }, "node_modules/@openzeppelin/contracts": { - "version": "4.8.3", - "resolved": "https://registry.npmjs.org/@openzeppelin/contracts/-/contracts-4.8.3.tgz", - "integrity": "sha512-bQHV8R9Me8IaJoJ2vPG4rXcL7seB7YVuskr4f+f5RyOStSZetwzkWtoqDMl5erkBJy0lDRUnIR2WIkPiC0GJlg==", - "dev": true, - "license": "MIT" + "version": "4.8.2", + "resolved": "https://registry.npmjs.org/@openzeppelin/contracts/-/contracts-4.8.2.tgz", + "integrity": "sha512-kEUOgPQszC0fSYWpbh2kT94ltOJwj1qfT2DWo+zVttmGmf97JZ99LspePNaeeaLhCImaHVeBbjaQFZQn7+Zc5g==", + "dev": true }, "node_modules/@scure/base": { "version": "1.1.1", @@ -1450,12 +1826,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/async-eventemitter": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@types/async-eventemitter/-/async-eventemitter-0.2.1.tgz", - "integrity": "sha512-M2P4Ng26QbAeITiH7w1d7OxtldgfAe0wobpyJzVK/XOb0cUGKU2R4pfAhqcJBXAe2ife5ZOhSv4wk7p+ffURtg==", - "dev": true - }, "node_modules/@types/bn.js": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.1.tgz", @@ -1511,6 +1881,22 @@ "@types/node": "*" } }, + "node_modules/@types/readable-stream": { + "version": "2.3.15", + "resolved": "https://registry.npmjs.org/@types/readable-stream/-/readable-stream-2.3.15.tgz", + "integrity": "sha512-oM5JSKQCcICF1wvGgmecmHldZ48OZamtMxcGGVICOJA8o8cahXC1zEVAif8iwoc5j8etxFaRFnf095+CDsuoFQ==", + "dev": true, + "dependencies": { + "@types/node": "*", + "safe-buffer": "~5.1.1" + } + }, + "node_modules/@types/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, "node_modules/@types/secp256k1": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/@types/secp256k1/-/secp256k1-4.0.3.tgz", @@ -1521,19 +1907,6 @@ "@types/node": "*" } }, - "node_modules/abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "dev": true, - "license": "MIT", - "dependencies": { - "event-target-shim": "^5.0.0" - }, - "engines": { - "node": ">=6.5" - } - }, "node_modules/abstract-level": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/abstract-level/-/abstract-level-1.0.3.tgz", @@ -1585,6 +1958,12 @@ "node": ">=0.3.0" } }, + "node_modules/aes-js": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-3.0.0.tgz", + "integrity": "sha512-H7wUZRn8WpTq9jocdxQ2c8x2sKo9ZVmzfRE13GiNJXfp7NcKYEdvl3vspKjXox6RIG2VtaRe4JFvxG4rqp2Zuw==", + "dev": true + }, "node_modules/agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", @@ -1780,24 +2159,6 @@ "node": ">=8" } }, - "node_modules/async": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", - "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", - "dev": true, - "dependencies": { - "lodash": "^4.17.14" - } - }, - "node_modules/async-eventemitter": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/async-eventemitter/-/async-eventemitter-0.2.4.tgz", - "integrity": "sha512-pd20BwL7Yt1zwDFy+8MX8F1+WCT8aQeKj0kQnTrH9WaeRETlRamVhD0JtRPmrV4GfOJ2F9CvdQkZeZhnh2TuHw==", - "dev": true, - "dependencies": { - "async": "^2.4.0" - } - }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -1835,10 +2196,16 @@ } ] }, + "node_modules/bech32": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz", + "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==", + "dev": true + }, "node_modules/bigint-crypto-utils": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/bigint-crypto-utils/-/bigint-crypto-utils-3.2.2.tgz", - "integrity": "sha512-U1RbE3aX9ayCUVcIPHuPDPKcK3SFOXf93J1UK/iHlJuQB7bhagPIX06/CLpLEsDThJ7KA4Dhrnzynl+d2weTiw==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/bigint-crypto-utils/-/bigint-crypto-utils-3.3.0.tgz", + "integrity": "sha512-jOTSb+drvEDxEq6OuUybOAv/xxoh3cuYRUIPyu8sSHQNKM303UQ2R1DAo45o1AkcIXw6fzbaFI1+xGGdaXs2lg==", "dev": true, "engines": { "node": ">=14.0.0" @@ -2014,20 +2381,6 @@ "node": ">= 0.8" } }, - "node_modules/call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -2092,6 +2445,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/case": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/case/-/case-1.6.3.tgz", + "integrity": "sha512-mzDSXIPaFwVDvZAHqZ9VlbyF4yyXRuX6IvB06WvPYkqJVO24kX1PPhv9bfpKNFZyxYFmmgo03HUiD8iklmJYRQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/catering": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/catering/-/catering-2.1.1.tgz", @@ -2681,6 +3043,54 @@ "@types/node": "*" } }, + "node_modules/ethers": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-5.7.2.tgz", + "integrity": "sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/abi": "5.7.0", + "@ethersproject/abstract-provider": "5.7.0", + "@ethersproject/abstract-signer": "5.7.0", + "@ethersproject/address": "5.7.0", + "@ethersproject/base64": "5.7.0", + "@ethersproject/basex": "5.7.0", + "@ethersproject/bignumber": "5.7.0", + "@ethersproject/bytes": "5.7.0", + "@ethersproject/constants": "5.7.0", + "@ethersproject/contracts": "5.7.0", + "@ethersproject/hash": "5.7.0", + "@ethersproject/hdnode": "5.7.0", + "@ethersproject/json-wallets": "5.7.0", + "@ethersproject/keccak256": "5.7.0", + "@ethersproject/logger": "5.7.0", + "@ethersproject/networks": "5.7.1", + "@ethersproject/pbkdf2": "5.7.0", + "@ethersproject/properties": "5.7.0", + "@ethersproject/providers": "5.7.2", + "@ethersproject/random": "5.7.0", + "@ethersproject/rlp": "5.7.0", + "@ethersproject/sha2": "5.7.0", + "@ethersproject/signing-key": "5.7.0", + "@ethersproject/solidity": "5.7.0", + "@ethersproject/strings": "5.7.0", + "@ethersproject/transactions": "5.7.0", + "@ethersproject/units": "5.7.0", + "@ethersproject/wallet": "5.7.0", + "@ethersproject/web": "5.7.1", + "@ethersproject/wordlists": "5.7.0" + } + }, "node_modules/ethjs-util": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/ethjs-util/-/ethjs-util-0.1.6.tgz", @@ -2696,16 +3106,6 @@ "npm": ">=3" } }, - "node_modules/event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/evp_bytestokey": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", @@ -2882,21 +3282,6 @@ "node": "6.* || 8.* || >= 10.*" } }, - "node_modules/get-intrinsic": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", - "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/get-stream": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", @@ -2995,28 +3380,27 @@ } }, "node_modules/hardhat": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/hardhat/-/hardhat-2.13.0.tgz", - "integrity": "sha512-ZlzBOLML1QGlm6JWyVAG8lVTEAoOaVm1in/RU2zoGAnYEoD1Rp4T+ZMvrLNhHaaeS9hfjJ1gJUBfiDr4cx+htQ==", + "version": "2.18.2", + "resolved": "https://registry.npmjs.org/hardhat/-/hardhat-2.18.2.tgz", + "integrity": "sha512-lUVmJg7DsKcUCDpqv57CJl6vHqo/1PeHSfM3+WIa8UtRKmXyVTj1qQK01TDiuetkZBVg9Dn52qU+ZwaJQynaKA==", "dev": true, "dependencies": { "@ethersproject/abi": "^5.1.2", "@metamask/eth-sig-util": "^4.0.0", - "@nomicfoundation/ethereumjs-block": "^4.0.0", - "@nomicfoundation/ethereumjs-blockchain": "^6.0.0", - "@nomicfoundation/ethereumjs-common": "^3.0.0", - "@nomicfoundation/ethereumjs-evm": "^1.0.0", - "@nomicfoundation/ethereumjs-rlp": "^4.0.0", - "@nomicfoundation/ethereumjs-statemanager": "^1.0.0", - "@nomicfoundation/ethereumjs-trie": "^5.0.0", - "@nomicfoundation/ethereumjs-tx": "^4.0.0", - "@nomicfoundation/ethereumjs-util": "^8.0.0", - "@nomicfoundation/ethereumjs-vm": "^6.0.0", + "@nomicfoundation/ethereumjs-block": "5.0.2", + "@nomicfoundation/ethereumjs-blockchain": "7.0.2", + "@nomicfoundation/ethereumjs-common": "4.0.2", + "@nomicfoundation/ethereumjs-evm": "2.0.2", + "@nomicfoundation/ethereumjs-rlp": "5.0.2", + "@nomicfoundation/ethereumjs-statemanager": "2.0.2", + "@nomicfoundation/ethereumjs-trie": "6.0.2", + "@nomicfoundation/ethereumjs-tx": "5.0.2", + "@nomicfoundation/ethereumjs-util": "9.0.2", + "@nomicfoundation/ethereumjs-vm": "7.0.2", "@nomicfoundation/solidity-analyzer": "^0.1.0", "@sentry/node": "^5.18.1", "@types/bn.js": "^5.1.0", "@types/lru-cache": "^5.1.0", - "abort-controller": "^3.0.0", "adm-zip": "^0.4.16", "aggregate-error": "^3.0.0", "ansi-escapes": "^4.3.0", @@ -3039,7 +3423,6 @@ "mnemonist": "^0.38.0", "mocha": "^10.0.0", "p-map": "^4.0.0", - "qs": "^6.7.0", "raw-body": "^2.4.1", "resolve": "1.17.0", "semver": "^6.3.0", @@ -3054,9 +3437,6 @@ "bin": { "hardhat": "internal/cli/bootstrap.js" }, - "engines": { - "node": ">=14.0.0" - }, "peerDependencies": { "ts-node": "*", "typescript": "*" @@ -3332,19 +3712,6 @@ "node": ">=8" } }, - "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/hash-base": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", @@ -3752,6 +4119,16 @@ "dev": true, "license": "ISC" }, + "node_modules/js-sdsl": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.4.2.tgz", + "integrity": "sha512-dwXFwByc/ajSV6m5bcKAPwe4yDDF6D614pxmIi5odytzxRlwqF6nwoiCek80Ixc7Cvma5awClxrzFtxCQvcM8w==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, "node_modules/js-sha3": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", @@ -4595,16 +4972,6 @@ "node": ">=8" } }, - "node_modules/object-inspect": { - "version": "1.12.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", - "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/obliterator": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/obliterator/-/obliterator-2.0.4.tgz", @@ -4999,26 +5366,10 @@ "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.6.0", - "teleport": ">=0.2.0" - } - }, - "node_modules/qs": { - "version": "6.11.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.1.tgz", - "integrity": "sha512-0wsrzgTz/kAVIeuxSjnpGC56rzYtr6JT/2BwEvMaPhFIoYa1aGO8LbzuU1R0uUYQkLpWBTOj0l/CLAJB64J6nQ==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.0.4" - }, + "license": "MIT", "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=0.6.0", + "teleport": ">=0.2.0" } }, "node_modules/queue-microtask": { @@ -5483,21 +5834,6 @@ "node": ">=8" } }, - "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", @@ -6433,6 +6769,32 @@ } } }, + "@chainsafe/as-sha256": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@chainsafe/as-sha256/-/as-sha256-0.3.1.tgz", + "integrity": "sha512-hldFFYuf49ed7DAakWVXSJODuq3pzJEguD8tQ7h+sGkM18vja+OFoJI9krnGmgzyuZC2ETX0NOIcCTy31v2Mtg==", + "dev": true + }, + "@chainsafe/persistent-merkle-tree": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@chainsafe/persistent-merkle-tree/-/persistent-merkle-tree-0.4.2.tgz", + "integrity": "sha512-lLO3ihKPngXLTus/L7WHKaw9PnNJWizlOF1H9NNzHP6Xvh82vzg9F2bzkXhYIFshMZ2gTCEz8tq6STe7r5NDfQ==", + "dev": true, + "requires": { + "@chainsafe/as-sha256": "^0.3.1" + } + }, + "@chainsafe/ssz": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/@chainsafe/ssz/-/ssz-0.9.4.tgz", + "integrity": "sha512-77Qtg2N1ayqs4Bg/wvnWfg5Bta7iy7IRh8XqXh7oNMeP2HBbBwx8m6yTpA8p0EHItWPEBkgZd5S5/LSlp3GXuQ==", + "dev": true, + "requires": { + "@chainsafe/as-sha256": "^0.3.1", + "@chainsafe/persistent-merkle-tree": "^0.4.2", + "case": "^1.6.3" + } + }, "@commitlint/cli": { "version": "17.6.3", "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-17.6.3.tgz", @@ -6774,6 +7136,16 @@ "@ethersproject/bytes": "^5.7.0" } }, + "@ethersproject/basex": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/basex/-/basex-5.7.0.tgz", + "integrity": "sha512-ywlh43GwZLv2Voc2gQVTKBoVQ1mti3d8HK5aMxsfu/nRDnMmNqaSJ3r3n85HBByT8OpoY96SXM1FogC533T4zw==", + "dev": true, + "requires": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/properties": "^5.7.0" + } + }, "@ethersproject/bignumber": { "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.7.0.tgz", @@ -6811,6 +7183,24 @@ "@ethersproject/bignumber": "^5.7.0" } }, + "@ethersproject/contracts": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/contracts/-/contracts-5.7.0.tgz", + "integrity": "sha512-5GJbzEU3X+d33CdfPhcyS+z8MzsTrBGk/sc+G+59+tPa9yFkl6HQ9D6L0QMgNTA9q8dT0XKxxkyp883XsQvbbg==", + "dev": true, + "requires": { + "@ethersproject/abi": "^5.7.0", + "@ethersproject/abstract-provider": "^5.7.0", + "@ethersproject/abstract-signer": "^5.7.0", + "@ethersproject/address": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/transactions": "^5.7.0" + } + }, "@ethersproject/hash": { "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/hash/-/hash-5.7.0.tgz", @@ -6828,6 +7218,47 @@ "@ethersproject/strings": "^5.7.0" } }, + "@ethersproject/hdnode": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/hdnode/-/hdnode-5.7.0.tgz", + "integrity": "sha512-OmyYo9EENBPPf4ERhR7oj6uAtUAhYGqOnIS+jE5pTXvdKBS99ikzq1E7Iv0ZQZ5V36Lqx1qZLeak0Ra16qpeOg==", + "dev": true, + "requires": { + "@ethersproject/abstract-signer": "^5.7.0", + "@ethersproject/basex": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/pbkdf2": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/sha2": "^5.7.0", + "@ethersproject/signing-key": "^5.7.0", + "@ethersproject/strings": "^5.7.0", + "@ethersproject/transactions": "^5.7.0", + "@ethersproject/wordlists": "^5.7.0" + } + }, + "@ethersproject/json-wallets": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/json-wallets/-/json-wallets-5.7.0.tgz", + "integrity": "sha512-8oee5Xgu6+RKgJTkvEMl2wDgSPSAQ9MB/3JYjFV9jlKvcYHUXZC+cQp0njgmxdHkYWn8s6/IqIZYm0YWCjO/0g==", + "dev": true, + "requires": { + "@ethersproject/abstract-signer": "^5.7.0", + "@ethersproject/address": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/hdnode": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/pbkdf2": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/random": "^5.7.0", + "@ethersproject/strings": "^5.7.0", + "@ethersproject/transactions": "^5.7.0", + "aes-js": "3.0.0", + "scrypt-js": "3.0.1" + } + }, "@ethersproject/keccak256": { "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/keccak256/-/keccak256-5.7.0.tgz", @@ -6853,6 +7284,16 @@ "@ethersproject/logger": "^5.7.0" } }, + "@ethersproject/pbkdf2": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/pbkdf2/-/pbkdf2-5.7.0.tgz", + "integrity": "sha512-oR/dBRZR6GTyaofd86DehG72hY6NpAjhabkhxgr3X2FpJtJuodEl2auADWBZfhDHgVCbu3/H/Ocq2uC6dpNjjw==", + "dev": true, + "requires": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/sha2": "^5.7.0" + } + }, "@ethersproject/properties": { "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.7.0.tgz", @@ -6862,6 +7303,53 @@ "@ethersproject/logger": "^5.7.0" } }, + "@ethersproject/providers": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/@ethersproject/providers/-/providers-5.7.2.tgz", + "integrity": "sha512-g34EWZ1WWAVgr4aptGlVBF8mhl3VWjv+8hoAnzStu8Ah22VHBsuGzP17eb6xDVRzw895G4W7vvx60lFFur/1Rg==", + "dev": true, + "requires": { + "@ethersproject/abstract-provider": "^5.7.0", + "@ethersproject/abstract-signer": "^5.7.0", + "@ethersproject/address": "^5.7.0", + "@ethersproject/base64": "^5.7.0", + "@ethersproject/basex": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/hash": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/networks": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/random": "^5.7.0", + "@ethersproject/rlp": "^5.7.0", + "@ethersproject/sha2": "^5.7.0", + "@ethersproject/strings": "^5.7.0", + "@ethersproject/transactions": "^5.7.0", + "@ethersproject/web": "^5.7.0", + "bech32": "1.1.4", + "ws": "7.4.6" + }, + "dependencies": { + "ws": { + "version": "7.4.6", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", + "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==", + "dev": true, + "requires": {} + } + } + }, + "@ethersproject/random": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/random/-/random-5.7.0.tgz", + "integrity": "sha512-19WjScqRA8IIeWclFme75VMXSBvi4e6InrUNuaR4s5pTF2qNhcGdCUwdxUVGtDDqC00sDLCO93jPQoDUH4HVmQ==", + "dev": true, + "requires": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0" + } + }, "@ethersproject/rlp": { "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/rlp/-/rlp-5.7.0.tgz", @@ -6872,6 +7360,17 @@ "@ethersproject/logger": "^5.7.0" } }, + "@ethersproject/sha2": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/sha2/-/sha2-5.7.0.tgz", + "integrity": "sha512-gKlH42riwb3KYp0reLsFTokByAKoJdgFCwI+CCiX/k+Jm2mbNs6oOaCjYQSlI1+XBVejwH2KrmCbMAT/GnRDQw==", + "dev": true, + "requires": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "hash.js": "1.1.7" + } + }, "@ethersproject/signing-key": { "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/signing-key/-/signing-key-5.7.0.tgz", @@ -6894,6 +7393,20 @@ } } }, + "@ethersproject/solidity": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/solidity/-/solidity-5.7.0.tgz", + "integrity": "sha512-HmabMd2Dt/raavyaGukF4XxizWKhKQ24DoLtdNbBmNKUOPqwjsKQSdV9GQtj9CBEea9DlzETlVER1gYeXXBGaA==", + "dev": true, + "requires": { + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/sha2": "^5.7.0", + "@ethersproject/strings": "^5.7.0" + } + }, "@ethersproject/strings": { "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.7.0.tgz", @@ -6922,6 +7435,40 @@ "@ethersproject/signing-key": "^5.7.0" } }, + "@ethersproject/units": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/units/-/units-5.7.0.tgz", + "integrity": "sha512-pD3xLMy3SJu9kG5xDGI7+xhTEmGXlEqXU4OfNapmfnxLVY4EMSSRp7j1k7eezutBPH7RBN/7QPnwR7hzNlEFeg==", + "dev": true, + "requires": { + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/logger": "^5.7.0" + } + }, + "@ethersproject/wallet": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/wallet/-/wallet-5.7.0.tgz", + "integrity": "sha512-MhmXlJXEJFBFVKrDLB4ZdDzxcBxQ3rLyCkhNqVu3CDYvR97E+8r01UgrI+TI99Le+aYm/in/0vp86guJuM7FCA==", + "dev": true, + "requires": { + "@ethersproject/abstract-provider": "^5.7.0", + "@ethersproject/abstract-signer": "^5.7.0", + "@ethersproject/address": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/hash": "^5.7.0", + "@ethersproject/hdnode": "^5.7.0", + "@ethersproject/json-wallets": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/random": "^5.7.0", + "@ethersproject/signing-key": "^5.7.0", + "@ethersproject/transactions": "^5.7.0", + "@ethersproject/wordlists": "^5.7.0" + } + }, "@ethersproject/web": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/@ethersproject/web/-/web-5.7.1.tgz", @@ -6935,6 +7482,19 @@ "@ethersproject/strings": "^5.7.0" } }, + "@ethersproject/wordlists": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/wordlists/-/wordlists-5.7.0.tgz", + "integrity": "sha512-S2TFNJNfHWVHNE6cNDjbVlZ6MgE17MIxMbMg2zv3wn+3XSJGosL1m9ZVv3GXCf/2ymSsQ+hRI5IzoMJTG6aoVA==", + "dev": true, + "requires": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/hash": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/strings": "^5.7.0" + } + }, "@jridgewell/resolve-uri": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", @@ -6983,31 +7543,33 @@ "dev": true }, "@nomicfoundation/ethereumjs-block": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-block/-/ethereumjs-block-4.2.2.tgz", - "integrity": "sha512-atjpt4gc6ZGZUPHBAQaUJsm1l/VCo7FmyQ780tMGO8QStjLdhz09dXynmhwVTy5YbRr0FOh/uX3QaEM0yIB2Zg==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-block/-/ethereumjs-block-5.0.2.tgz", + "integrity": "sha512-hSe6CuHI4SsSiWWjHDIzWhSiAVpzMUcDRpWYzN0T9l8/Rz7xNn3elwVOJ/tAyS0LqL6vitUD78Uk7lQDXZun7Q==", "dev": true, "requires": { - "@nomicfoundation/ethereumjs-common": "3.1.2", - "@nomicfoundation/ethereumjs-rlp": "4.0.3", - "@nomicfoundation/ethereumjs-trie": "5.0.5", - "@nomicfoundation/ethereumjs-tx": "4.1.2", - "@nomicfoundation/ethereumjs-util": "8.0.6", - "ethereum-cryptography": "0.1.3" + "@nomicfoundation/ethereumjs-common": "4.0.2", + "@nomicfoundation/ethereumjs-rlp": "5.0.2", + "@nomicfoundation/ethereumjs-trie": "6.0.2", + "@nomicfoundation/ethereumjs-tx": "5.0.2", + "@nomicfoundation/ethereumjs-util": "9.0.2", + "ethereum-cryptography": "0.1.3", + "ethers": "^5.7.1" } }, "@nomicfoundation/ethereumjs-blockchain": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-blockchain/-/ethereumjs-blockchain-6.2.2.tgz", - "integrity": "sha512-6AIB2MoTEPZJLl6IRKcbd8mUmaBAQ/NMe3O7OsAOIiDjMNPPH5KaUQiLfbVlegT4wKIg/GOsFH7XlH2KDVoJNg==", - "dev": true, - "requires": { - "@nomicfoundation/ethereumjs-block": "4.2.2", - "@nomicfoundation/ethereumjs-common": "3.1.2", - "@nomicfoundation/ethereumjs-ethash": "2.0.5", - "@nomicfoundation/ethereumjs-rlp": "4.0.3", - "@nomicfoundation/ethereumjs-trie": "5.0.5", - "@nomicfoundation/ethereumjs-util": "8.0.6", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-blockchain/-/ethereumjs-blockchain-7.0.2.tgz", + "integrity": "sha512-8UUsSXJs+MFfIIAKdh3cG16iNmWzWC/91P40sazNvrqhhdR/RtGDlFk2iFTGbBAZPs2+klZVzhRX8m2wvuvz3w==", + "dev": true, + "requires": { + "@nomicfoundation/ethereumjs-block": "5.0.2", + "@nomicfoundation/ethereumjs-common": "4.0.2", + "@nomicfoundation/ethereumjs-ethash": "3.0.2", + "@nomicfoundation/ethereumjs-rlp": "5.0.2", + "@nomicfoundation/ethereumjs-trie": "6.0.2", + "@nomicfoundation/ethereumjs-tx": "5.0.2", + "@nomicfoundation/ethereumjs-util": "9.0.2", "abstract-level": "^1.0.3", "debug": "^4.3.3", "ethereum-cryptography": "0.1.3", @@ -7034,39 +7596,39 @@ } }, "@nomicfoundation/ethereumjs-common": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-common/-/ethereumjs-common-3.1.2.tgz", - "integrity": "sha512-JAEBpIua62dyObHM9KI2b4wHZcRQYYge9gxiygTWa3lNCr2zo+K0TbypDpgiNij5MCGNWP1eboNfNfx1a3vkvA==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-common/-/ethereumjs-common-4.0.2.tgz", + "integrity": "sha512-I2WGP3HMGsOoycSdOTSqIaES0ughQTueOsddJ36aYVpI3SN8YSusgRFLwzDJwRFVIYDKx/iJz0sQ5kBHVgdDwg==", "dev": true, "requires": { - "@nomicfoundation/ethereumjs-util": "8.0.6", + "@nomicfoundation/ethereumjs-util": "9.0.2", "crc-32": "^1.2.0" } }, "@nomicfoundation/ethereumjs-ethash": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-ethash/-/ethereumjs-ethash-2.0.5.tgz", - "integrity": "sha512-xlLdcICGgAYyYmnI3r1t0R5fKGBJNDQSOQxXNjVO99JmxJIdXR5MgPo5CSJO1RpyzKOgzi3uIFn8agv564dZEQ==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-ethash/-/ethereumjs-ethash-3.0.2.tgz", + "integrity": "sha512-8PfoOQCcIcO9Pylq0Buijuq/O73tmMVURK0OqdjhwqcGHYC2PwhbajDh7GZ55ekB0Px197ajK3PQhpKoiI/UPg==", "dev": true, "requires": { - "@nomicfoundation/ethereumjs-block": "4.2.2", - "@nomicfoundation/ethereumjs-rlp": "4.0.3", - "@nomicfoundation/ethereumjs-util": "8.0.6", + "@nomicfoundation/ethereumjs-block": "5.0.2", + "@nomicfoundation/ethereumjs-rlp": "5.0.2", + "@nomicfoundation/ethereumjs-util": "9.0.2", "abstract-level": "^1.0.3", "bigint-crypto-utils": "^3.0.23", "ethereum-cryptography": "0.1.3" } }, "@nomicfoundation/ethereumjs-evm": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-evm/-/ethereumjs-evm-1.3.2.tgz", - "integrity": "sha512-I00d4MwXuobyoqdPe/12dxUQxTYzX8OckSaWsMcWAfQhgVDvBx6ffPyP/w1aL0NW7MjyerySPcSVfDJAMHjilw==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-evm/-/ethereumjs-evm-2.0.2.tgz", + "integrity": "sha512-rBLcUaUfANJxyOx9HIdMX6uXGin6lANCulIm/pjMgRqfiCRMZie3WKYxTSd8ZE/d+qT+zTedBF4+VHTdTSePmQ==", "dev": true, "requires": { - "@nomicfoundation/ethereumjs-common": "3.1.2", - "@nomicfoundation/ethereumjs-util": "8.0.6", - "@types/async-eventemitter": "^0.2.1", - "async-eventemitter": "^0.2.4", + "@ethersproject/providers": "^5.7.1", + "@nomicfoundation/ethereumjs-common": "4.0.2", + "@nomicfoundation/ethereumjs-tx": "5.0.2", + "@nomicfoundation/ethereumjs-util": "9.0.2", "debug": "^4.3.3", "ethereum-cryptography": "0.1.3", "mcl-wasm": "^0.7.1", @@ -7074,80 +7636,101 @@ } }, "@nomicfoundation/ethereumjs-rlp": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-rlp/-/ethereumjs-rlp-4.0.3.tgz", - "integrity": "sha512-DZMzB/lqPK78T6MluyXqtlRmOMcsZbTTbbEyAjo0ncaff2mqu/k8a79PBcyvpgAhWD/R59Fjq/x3ro5Lof0AtA==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-rlp/-/ethereumjs-rlp-5.0.2.tgz", + "integrity": "sha512-QwmemBc+MMsHJ1P1QvPl8R8p2aPvvVcKBbvHnQOKBpBztEo0omN0eaob6FeZS/e3y9NSe+mfu3nNFBHszqkjTA==", "dev": true }, "@nomicfoundation/ethereumjs-statemanager": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-statemanager/-/ethereumjs-statemanager-1.0.5.tgz", - "integrity": "sha512-CAhzpzTR5toh/qOJIZUUOnWekUXuRqkkzaGAQrVcF457VhtCmr+ddZjjK50KNZ524c1XP8cISguEVNqJ6ij1sA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-statemanager/-/ethereumjs-statemanager-2.0.2.tgz", + "integrity": "sha512-dlKy5dIXLuDubx8Z74sipciZnJTRSV/uHG48RSijhgm1V7eXYFC567xgKtsKiVZB1ViTP9iFL4B6Je0xD6X2OA==", "dev": true, "requires": { - "@nomicfoundation/ethereumjs-common": "3.1.2", - "@nomicfoundation/ethereumjs-rlp": "4.0.3", - "@nomicfoundation/ethereumjs-trie": "5.0.5", - "@nomicfoundation/ethereumjs-util": "8.0.6", + "@nomicfoundation/ethereumjs-common": "4.0.2", + "@nomicfoundation/ethereumjs-rlp": "5.0.2", "debug": "^4.3.3", "ethereum-cryptography": "0.1.3", - "functional-red-black-tree": "^1.0.1" + "ethers": "^5.7.1", + "js-sdsl": "^4.1.4" } }, "@nomicfoundation/ethereumjs-trie": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-trie/-/ethereumjs-trie-5.0.5.tgz", - "integrity": "sha512-+8sNZrXkzvA1NH5F4kz5RSYl1I6iaRz7mAZRsyxOm0IVY4UaP43Ofvfp/TwOalFunurQrYB5pRO40+8FBcxFMA==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-trie/-/ethereumjs-trie-6.0.2.tgz", + "integrity": "sha512-yw8vg9hBeLYk4YNg5MrSJ5H55TLOv2FSWUTROtDtTMMmDGROsAu+0tBjiNGTnKRi400M6cEzoFfa89Fc5k8NTQ==", "dev": true, "requires": { - "@nomicfoundation/ethereumjs-rlp": "4.0.3", - "@nomicfoundation/ethereumjs-util": "8.0.6", + "@nomicfoundation/ethereumjs-rlp": "5.0.2", + "@nomicfoundation/ethereumjs-util": "9.0.2", + "@types/readable-stream": "^2.3.13", "ethereum-cryptography": "0.1.3", "readable-stream": "^3.6.0" } }, "@nomicfoundation/ethereumjs-tx": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-tx/-/ethereumjs-tx-4.1.2.tgz", - "integrity": "sha512-emJBJZpmTdUa09cqxQqHaysbBI9Od353ZazeH7WgPb35miMgNY6mb7/3vBA98N5lUW/rgkiItjX0KZfIzihSoQ==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-tx/-/ethereumjs-tx-5.0.2.tgz", + "integrity": "sha512-T+l4/MmTp7VhJeNloMkM+lPU3YMUaXdcXgTGCf8+ZFvV9NYZTRLFekRwlG6/JMmVfIfbrW+dRRJ9A6H5Q/Z64g==", "dev": true, "requires": { - "@nomicfoundation/ethereumjs-common": "3.1.2", - "@nomicfoundation/ethereumjs-rlp": "4.0.3", - "@nomicfoundation/ethereumjs-util": "8.0.6", + "@chainsafe/ssz": "^0.9.2", + "@ethersproject/providers": "^5.7.2", + "@nomicfoundation/ethereumjs-common": "4.0.2", + "@nomicfoundation/ethereumjs-rlp": "5.0.2", + "@nomicfoundation/ethereumjs-util": "9.0.2", "ethereum-cryptography": "0.1.3" } }, "@nomicfoundation/ethereumjs-util": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-util/-/ethereumjs-util-8.0.6.tgz", - "integrity": "sha512-jOQfF44laa7xRfbfLXojdlcpkvxeHrE2Xu7tSeITsWFgoII163MzjOwFEzSNozHYieFysyoEMhCdP+NY5ikstw==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-util/-/ethereumjs-util-9.0.2.tgz", + "integrity": "sha512-4Wu9D3LykbSBWZo8nJCnzVIYGvGCuyiYLIJa9XXNVt1q1jUzHdB+sJvx95VGCpPkCT+IbLecW6yfzy3E1bQrwQ==", "dev": true, "requires": { - "@nomicfoundation/ethereumjs-rlp": "4.0.3", + "@chainsafe/ssz": "^0.10.0", + "@nomicfoundation/ethereumjs-rlp": "5.0.2", "ethereum-cryptography": "0.1.3" + }, + "dependencies": { + "@chainsafe/persistent-merkle-tree": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@chainsafe/persistent-merkle-tree/-/persistent-merkle-tree-0.5.0.tgz", + "integrity": "sha512-l0V1b5clxA3iwQLXP40zYjyZYospQLZXzBVIhhr9kDg/1qHZfzzHw0jj4VPBijfYCArZDlPkRi1wZaV2POKeuw==", + "dev": true, + "requires": { + "@chainsafe/as-sha256": "^0.3.1" + } + }, + "@chainsafe/ssz": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@chainsafe/ssz/-/ssz-0.10.2.tgz", + "integrity": "sha512-/NL3Lh8K+0q7A3LsiFq09YXS9fPE+ead2rr7vM2QK8PLzrNsw3uqrif9bpRX5UxgeRjM+vYi+boCM3+GM4ovXg==", + "dev": true, + "requires": { + "@chainsafe/as-sha256": "^0.3.1", + "@chainsafe/persistent-merkle-tree": "^0.5.0" + } + } } }, "@nomicfoundation/ethereumjs-vm": { - "version": "6.4.2", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-vm/-/ethereumjs-vm-6.4.2.tgz", - "integrity": "sha512-PRTyxZMP6kx+OdAzBhuH1LD2Yw+hrSpaytftvaK//thDy2OI07S0nrTdbrdk7b8ZVPAc9H9oTwFBl3/wJ3w15g==", - "dev": true, - "requires": { - "@nomicfoundation/ethereumjs-block": "4.2.2", - "@nomicfoundation/ethereumjs-blockchain": "6.2.2", - "@nomicfoundation/ethereumjs-common": "3.1.2", - "@nomicfoundation/ethereumjs-evm": "1.3.2", - "@nomicfoundation/ethereumjs-rlp": "4.0.3", - "@nomicfoundation/ethereumjs-statemanager": "1.0.5", - "@nomicfoundation/ethereumjs-trie": "5.0.5", - "@nomicfoundation/ethereumjs-tx": "4.1.2", - "@nomicfoundation/ethereumjs-util": "8.0.6", - "@types/async-eventemitter": "^0.2.1", - "async-eventemitter": "^0.2.4", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-vm/-/ethereumjs-vm-7.0.2.tgz", + "integrity": "sha512-Bj3KZT64j54Tcwr7Qm/0jkeZXJMfdcAtRBedou+Hx0dPOSIgqaIr0vvLwP65TpHbak2DmAq+KJbW2KNtIoFwvA==", + "dev": true, + "requires": { + "@nomicfoundation/ethereumjs-block": "5.0.2", + "@nomicfoundation/ethereumjs-blockchain": "7.0.2", + "@nomicfoundation/ethereumjs-common": "4.0.2", + "@nomicfoundation/ethereumjs-evm": "2.0.2", + "@nomicfoundation/ethereumjs-rlp": "5.0.2", + "@nomicfoundation/ethereumjs-statemanager": "2.0.2", + "@nomicfoundation/ethereumjs-trie": "6.0.2", + "@nomicfoundation/ethereumjs-tx": "5.0.2", + "@nomicfoundation/ethereumjs-util": "9.0.2", "debug": "^4.3.3", "ethereum-cryptography": "0.1.3", - "functional-red-black-tree": "^1.0.1", "mcl-wasm": "^0.7.1", "rustbn.js": "~0.2.0" } @@ -7185,9 +7768,9 @@ "optional": true }, "@openzeppelin/contracts": { - "version": "4.8.3", - "resolved": "https://registry.npmjs.org/@openzeppelin/contracts/-/contracts-4.8.3.tgz", - "integrity": "sha512-bQHV8R9Me8IaJoJ2vPG4rXcL7seB7YVuskr4f+f5RyOStSZetwzkWtoqDMl5erkBJy0lDRUnIR2WIkPiC0GJlg==", + "version": "4.8.2", + "resolved": "https://registry.npmjs.org/@openzeppelin/contracts/-/contracts-4.8.2.tgz", + "integrity": "sha512-kEUOgPQszC0fSYWpbh2kT94ltOJwj1qfT2DWo+zVttmGmf97JZ99LspePNaeeaLhCImaHVeBbjaQFZQn7+Zc5g==", "dev": true }, "@scure/base": { @@ -7331,12 +7914,6 @@ "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", "dev": true }, - "@types/async-eventemitter": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@types/async-eventemitter/-/async-eventemitter-0.2.1.tgz", - "integrity": "sha512-M2P4Ng26QbAeITiH7w1d7OxtldgfAe0wobpyJzVK/XOb0cUGKU2R4pfAhqcJBXAe2ife5ZOhSv4wk7p+ffURtg==", - "dev": true - }, "@types/bn.js": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.1.tgz", @@ -7385,6 +7962,24 @@ "@types/node": "*" } }, + "@types/readable-stream": { + "version": "2.3.15", + "resolved": "https://registry.npmjs.org/@types/readable-stream/-/readable-stream-2.3.15.tgz", + "integrity": "sha512-oM5JSKQCcICF1wvGgmecmHldZ48OZamtMxcGGVICOJA8o8cahXC1zEVAif8iwoc5j8etxFaRFnf095+CDsuoFQ==", + "dev": true, + "requires": { + "@types/node": "*", + "safe-buffer": "~5.1.1" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + } + } + }, "@types/secp256k1": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/@types/secp256k1/-/secp256k1-4.0.3.tgz", @@ -7394,15 +7989,6 @@ "@types/node": "*" } }, - "abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "dev": true, - "requires": { - "event-target-shim": "^5.0.0" - } - }, "abstract-level": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/abstract-level/-/abstract-level-1.0.3.tgz", @@ -7436,6 +8022,12 @@ "integrity": "sha512-TFi4HBKSGfIKsK5YCkKaaFG2m4PEDyViZmEwof3MTIgzimHLto6muaHVpbrljdIvIrFZzEq/p4nafOeLcYegrg==", "dev": true }, + "aes-js": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-3.0.0.tgz", + "integrity": "sha512-H7wUZRn8WpTq9jocdxQ2c8x2sKo9ZVmzfRE13GiNJXfp7NcKYEdvl3vspKjXox6RIG2VtaRe4JFvxG4rqp2Zuw==", + "dev": true + }, "agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", @@ -7567,24 +8159,6 @@ "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", "dev": true }, - "async": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", - "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", - "dev": true, - "requires": { - "lodash": "^4.17.14" - } - }, - "async-eventemitter": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/async-eventemitter/-/async-eventemitter-0.2.4.tgz", - "integrity": "sha512-pd20BwL7Yt1zwDFy+8MX8F1+WCT8aQeKj0kQnTrH9WaeRETlRamVhD0JtRPmrV4GfOJ2F9CvdQkZeZhnh2TuHw==", - "dev": true, - "requires": { - "async": "^2.4.0" - } - }, "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -7606,10 +8180,16 @@ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", "dev": true }, + "bech32": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz", + "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==", + "dev": true + }, "bigint-crypto-utils": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/bigint-crypto-utils/-/bigint-crypto-utils-3.2.2.tgz", - "integrity": "sha512-U1RbE3aX9ayCUVcIPHuPDPKcK3SFOXf93J1UK/iHlJuQB7bhagPIX06/CLpLEsDThJ7KA4Dhrnzynl+d2weTiw==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/bigint-crypto-utils/-/bigint-crypto-utils-3.3.0.tgz", + "integrity": "sha512-jOTSb+drvEDxEq6OuUybOAv/xxoh3cuYRUIPyu8sSHQNKM303UQ2R1DAo45o1AkcIXw6fzbaFI1+xGGdaXs2lg==", "dev": true }, "binary-extensions": { @@ -7743,16 +8323,6 @@ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", "dev": true }, - "call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - } - }, "callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -7790,6 +8360,12 @@ } } }, + "case": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/case/-/case-1.6.3.tgz", + "integrity": "sha512-mzDSXIPaFwVDvZAHqZ9VlbyF4yyXRuX6IvB06WvPYkqJVO24kX1PPhv9bfpKNFZyxYFmmgo03HUiD8iklmJYRQ==", + "dev": true + }, "catering": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/catering/-/catering-2.1.1.tgz", @@ -8214,6 +8790,44 @@ } } }, + "ethers": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-5.7.2.tgz", + "integrity": "sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg==", + "dev": true, + "requires": { + "@ethersproject/abi": "5.7.0", + "@ethersproject/abstract-provider": "5.7.0", + "@ethersproject/abstract-signer": "5.7.0", + "@ethersproject/address": "5.7.0", + "@ethersproject/base64": "5.7.0", + "@ethersproject/basex": "5.7.0", + "@ethersproject/bignumber": "5.7.0", + "@ethersproject/bytes": "5.7.0", + "@ethersproject/constants": "5.7.0", + "@ethersproject/contracts": "5.7.0", + "@ethersproject/hash": "5.7.0", + "@ethersproject/hdnode": "5.7.0", + "@ethersproject/json-wallets": "5.7.0", + "@ethersproject/keccak256": "5.7.0", + "@ethersproject/logger": "5.7.0", + "@ethersproject/networks": "5.7.1", + "@ethersproject/pbkdf2": "5.7.0", + "@ethersproject/properties": "5.7.0", + "@ethersproject/providers": "5.7.2", + "@ethersproject/random": "5.7.0", + "@ethersproject/rlp": "5.7.0", + "@ethersproject/sha2": "5.7.0", + "@ethersproject/signing-key": "5.7.0", + "@ethersproject/solidity": "5.7.0", + "@ethersproject/strings": "5.7.0", + "@ethersproject/transactions": "5.7.0", + "@ethersproject/units": "5.7.0", + "@ethersproject/wallet": "5.7.0", + "@ethersproject/web": "5.7.1", + "@ethersproject/wordlists": "5.7.0" + } + }, "ethjs-util": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/ethjs-util/-/ethjs-util-0.1.6.tgz", @@ -8224,12 +8838,6 @@ "strip-hex-prefix": "1.0.0" } }, - "event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", - "dev": true - }, "evp_bytestokey": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", @@ -8355,17 +8963,6 @@ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true }, - "get-intrinsic": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", - "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.3" - } - }, "get-stream": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", @@ -8430,28 +9027,27 @@ "dev": true }, "hardhat": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/hardhat/-/hardhat-2.13.0.tgz", - "integrity": "sha512-ZlzBOLML1QGlm6JWyVAG8lVTEAoOaVm1in/RU2zoGAnYEoD1Rp4T+ZMvrLNhHaaeS9hfjJ1gJUBfiDr4cx+htQ==", + "version": "2.18.2", + "resolved": "https://registry.npmjs.org/hardhat/-/hardhat-2.18.2.tgz", + "integrity": "sha512-lUVmJg7DsKcUCDpqv57CJl6vHqo/1PeHSfM3+WIa8UtRKmXyVTj1qQK01TDiuetkZBVg9Dn52qU+ZwaJQynaKA==", "dev": true, "requires": { "@ethersproject/abi": "^5.1.2", "@metamask/eth-sig-util": "^4.0.0", - "@nomicfoundation/ethereumjs-block": "^4.0.0", - "@nomicfoundation/ethereumjs-blockchain": "^6.0.0", - "@nomicfoundation/ethereumjs-common": "^3.0.0", - "@nomicfoundation/ethereumjs-evm": "^1.0.0", - "@nomicfoundation/ethereumjs-rlp": "^4.0.0", - "@nomicfoundation/ethereumjs-statemanager": "^1.0.0", - "@nomicfoundation/ethereumjs-trie": "^5.0.0", - "@nomicfoundation/ethereumjs-tx": "^4.0.0", - "@nomicfoundation/ethereumjs-util": "^8.0.0", - "@nomicfoundation/ethereumjs-vm": "^6.0.0", + "@nomicfoundation/ethereumjs-block": "5.0.2", + "@nomicfoundation/ethereumjs-blockchain": "7.0.2", + "@nomicfoundation/ethereumjs-common": "4.0.2", + "@nomicfoundation/ethereumjs-evm": "2.0.2", + "@nomicfoundation/ethereumjs-rlp": "5.0.2", + "@nomicfoundation/ethereumjs-statemanager": "2.0.2", + "@nomicfoundation/ethereumjs-trie": "6.0.2", + "@nomicfoundation/ethereumjs-tx": "5.0.2", + "@nomicfoundation/ethereumjs-util": "9.0.2", + "@nomicfoundation/ethereumjs-vm": "7.0.2", "@nomicfoundation/solidity-analyzer": "^0.1.0", "@sentry/node": "^5.18.1", "@types/bn.js": "^5.1.0", "@types/lru-cache": "^5.1.0", - "abort-controller": "^3.0.0", "adm-zip": "^0.4.16", "aggregate-error": "^3.0.0", "ansi-escapes": "^4.3.0", @@ -8474,7 +9070,6 @@ "mnemonist": "^0.38.0", "mocha": "^10.0.0", "p-map": "^4.0.0", - "qs": "^6.7.0", "raw-body": "^2.4.1", "resolve": "1.17.0", "semver": "^6.3.0", @@ -8680,12 +9275,6 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true - }, "hash-base": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", @@ -8954,6 +9543,12 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, + "js-sdsl": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.4.2.tgz", + "integrity": "sha512-dwXFwByc/ajSV6m5bcKAPwe4yDDF6D614pxmIi5odytzxRlwqF6nwoiCek80Ixc7Cvma5awClxrzFtxCQvcM8w==", + "dev": true + }, "js-sha3": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", @@ -9565,12 +10160,6 @@ "path-key": "^3.0.0" } }, - "object-inspect": { - "version": "1.12.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", - "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", - "dev": true - }, "obliterator": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/obliterator/-/obliterator-2.0.4.tgz", @@ -9827,15 +10416,6 @@ "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==", "dev": true }, - "qs": { - "version": "6.11.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.1.tgz", - "integrity": "sha512-0wsrzgTz/kAVIeuxSjnpGC56rzYtr6JT/2BwEvMaPhFIoYa1aGO8LbzuU1R0uUYQkLpWBTOj0l/CLAJB64J6nQ==", - "dev": true, - "requires": { - "side-channel": "^1.0.4" - } - }, "queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -10151,17 +10731,6 @@ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true }, - "side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "dev": true, - "requires": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - } - }, "signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", diff --git a/package.json b/package.json index 935be1f..b98609f 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "@commitlint/cli": "^17.0.0", "@commitlint/config-conventional": "^17.0.0", "@openzeppelin/contracts": "4.8.2", - "hardhat": "^2.13.0", + "hardhat": "^2.18.2", "prettier": "^2.5.1", "prettier-plugin-solidity": "^1.0.0-beta.19", "pretty-quick": "^3.1.3", diff --git a/tests/conftest.py b/tests/conftest.py index d518e05..fb76ae6 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -224,8 +224,8 @@ def provide_strategy_with_debt(account, strategy, vault, target_debt: int): @pytest.fixture(scope="session") -def deploy_accountant(project, daddy, fee_recipient): - def deploy_accountant( +def deploy_generic_accountant(project, daddy, fee_recipient): + def deploy_generic_accountant( manager=daddy, fee_recipient=fee_recipient, management_fee=100, @@ -245,14 +245,50 @@ def deploy_accountant( return accountant - yield deploy_accountant + yield deploy_generic_accountant @pytest.fixture(scope="session") -def accountant(deploy_accountant): - accountant = deploy_accountant() +def deploy_healthcheck_accountant(project, daddy, fee_recipient): + def deploy_healthcheck_accountant( + manager=daddy, + fee_recipient=fee_recipient, + management_fee=100, + performance_fee=1_000, + refund_ratio=0, + max_fee=0, + max_gain=10_000, + max_loss=0, + ): + accountant = daddy.deploy( + project.HealthCheckAccountant, + manager, + fee_recipient, + management_fee, + performance_fee, + refund_ratio, + max_fee, + max_gain, + max_loss, + ) + + return accountant + + yield deploy_healthcheck_accountant + + +@pytest.fixture(scope="session") +def generic_accountant(deploy_generic_accountant): + generic_accountant = deploy_generic_accountant() + + yield generic_accountant + + +@pytest.fixture(scope="session") +def healthcheck_accountant(deploy_healthcheck_accountant): + healthcheck_accountant = deploy_healthcheck_accountant() - yield accountant + yield healthcheck_accountant @pytest.fixture(scope="session") diff --git a/tests/test_generic_accountant.py b/tests/test_generic_accountant.py index d35060c..1437799 100644 --- a/tests/test_generic_accountant.py +++ b/tests/test_generic_accountant.py @@ -3,25 +3,25 @@ from utils.constants import ChangeType, ZERO_ADDRESS, MAX_BPS, MAX_INT -def test_setup(daddy, vault, strategy, deploy_accountant, fee_recipient): - accountant = deploy_accountant() - - assert accountant.fee_manager() == daddy - assert accountant.future_fee_manager() == ZERO_ADDRESS - assert accountant.fee_recipient() == fee_recipient - assert accountant.default_config().management_fee == 100 - assert accountant.default_config().performance_fee == 1_000 - assert accountant.default_config().refund_ratio == 0 - assert accountant.default_config().max_fee == 0 +def test_setup(daddy, vault, strategy, generic_accountant, fee_recipient): + accountant = generic_accountant + assert accountant.feeManager() == daddy + assert accountant.futureFeeManager() == ZERO_ADDRESS + assert accountant.feeRecipient() == fee_recipient + assert accountant.defaultConfig().managementFee == 100 + assert accountant.defaultConfig().performanceFee == 1_000 + assert accountant.defaultConfig().refundRatio == 0 + assert accountant.defaultConfig().maxFee == 0 assert accountant.vaults(vault.address) == False - assert accountant.fees(vault.address, strategy.address).custom == False - assert accountant.fees(vault.address, strategy.address).management_fee == 0 - assert accountant.fees(vault.address, strategy.address).performance_fee == 0 - assert accountant.fees(vault.address, strategy.address).refund_ratio == 0 - assert accountant.fees(vault.address, strategy.address).max_fee == 0 + assert accountant.custom(vault.address, strategy.address) == False + assert accountant.customConfig(vault.address, strategy.address).managementFee == 0 + assert accountant.customConfig(vault.address, strategy.address).performanceFee == 0 + assert accountant.customConfig(vault.address, strategy.address).refundRatio == 0 + assert accountant.customConfig(vault.address, strategy.address).maxFee == 0 -def test_add_vault(daddy, vault, strategy, accountant): +def test_add_vault(daddy, vault, strategy, generic_accountant): + accountant = generic_accountant assert accountant.vaults(vault.address) == False vault.add_strategy(strategy.address, sender=daddy) @@ -30,7 +30,7 @@ def test_add_vault(daddy, vault, strategy, accountant): accountant.report(strategy, 1_000, 0, sender=vault) # set vault in accountant - tx = accountant.add_vault(vault.address, sender=daddy) + tx = accountant.addVault(vault.address, sender=daddy) event = list(tx.decode_logs(accountant.VaultChanged)) @@ -46,12 +46,13 @@ def test_add_vault(daddy, vault, strategy, accountant): assert refunds == 0 -def test_remove_vault(daddy, vault, strategy, accountant): +def test_remove_vault(daddy, vault, strategy, generic_accountant): + accountant = generic_accountant assert accountant.vaults(vault.address) == False vault.add_strategy(strategy.address, sender=daddy) # set vault in accountant - tx = accountant.add_vault(vault.address, sender=daddy) + tx = accountant.addVault(vault.address, sender=daddy) event = list(tx.decode_logs(accountant.VaultChanged)) @@ -66,7 +67,7 @@ def test_remove_vault(daddy, vault, strategy, accountant): assert fees == 100 assert refunds == 0 - tx = accountant.remove_vault(vault.address, sender=daddy) + tx = accountant.removeVault(vault.address, sender=daddy) event = list(tx.decode_logs(accountant.VaultChanged)) @@ -80,47 +81,49 @@ def test_remove_vault(daddy, vault, strategy, accountant): accountant.report(strategy, 0, 0, sender=vault) -def test_set_default_config(daddy, vault, strategy, accountant): - assert accountant.default_config().management_fee == 100 - assert accountant.default_config().performance_fee == 1_000 - assert accountant.default_config().refund_ratio == 0 - assert accountant.default_config().max_fee == 0 +def test_set_default_config(daddy, vault, strategy, generic_accountant): + accountant = generic_accountant + assert accountant.defaultConfig().managementFee == 100 + assert accountant.defaultConfig().performanceFee == 1_000 + assert accountant.defaultConfig().refundRatio == 0 + assert accountant.defaultConfig().maxFee == 0 new_management = 20 new_performance = 2_000 new_refund = 13 new_max = 18 - tx = accountant.update_default_config( + tx = accountant.updateDefaultConfig( new_management, new_performance, new_refund, new_max, sender=daddy ) event = list(tx.decode_logs(accountant.UpdateDefaultFeeConfig)) assert len(event) == 1 - config = list(event[0].default_fee_config) + config = list(event[0].defaultFeeConfig) assert config[0] == new_management assert config[1] == new_performance assert config[2] == new_refund assert config[3] == new_max - assert accountant.default_config().management_fee == new_management - assert accountant.default_config().performance_fee == new_performance - assert accountant.default_config().refund_ratio == new_refund - assert accountant.default_config().max_fee == new_max + assert accountant.defaultConfig().managementFee == new_management + assert accountant.defaultConfig().performanceFee == new_performance + assert accountant.defaultConfig().refundRatio == new_refund + assert accountant.defaultConfig().maxFee == new_max -def test_set_custom_config(daddy, vault, strategy, accountant): - accountant.add_vault(vault.address, sender=daddy) +def test_set_custom_config(daddy, vault, strategy, generic_accountant): + accountant = generic_accountant + accountant.addVault(vault.address, sender=daddy) - assert accountant.fees(vault.address, strategy.address) == (0, 0, 0, 0, False) + assert accountant.customConfig(vault.address, strategy.address) == (0, 0, 0, 0) new_management = 20 new_performance = 2_000 new_refund = 13 new_max = 18 - tx = accountant.set_custom_config( + tx = accountant.setCustomConfig( vault.address, strategy.address, new_management, @@ -135,40 +138,34 @@ def test_set_custom_config(daddy, vault, strategy, accountant): assert len(event) == 1 assert event[0].vault == vault.address assert event[0].strategy == strategy.address - config = list(event[0].custom_config) - - assert config[0] == new_management - assert config[1] == new_performance - assert config[2] == new_refund - assert config[3] == new_max - assert config[4] == True assert ( - accountant.fees(vault.address, strategy.address) != accountant.default_config() + accountant.customConfig(vault.address, strategy.address) + != accountant.defaultConfig() ) - assert accountant.fees(vault.address, strategy.address) == ( + assert accountant.customConfig(vault.address, strategy.address) == ( new_management, new_performance, new_refund, new_max, - True, ) -def test_remove_custom_config(daddy, vault, strategy, accountant): - accountant.add_vault(vault.address, sender=daddy) +def test_remove_custom_config(daddy, vault, strategy, generic_accountant): + accountant = generic_accountant + accountant.addVault(vault.address, sender=daddy) - assert accountant.fees(vault.address, strategy.address) == (0, 0, 0, 0, False) + assert accountant.customConfig(vault.address, strategy.address) == (0, 0, 0, 0) with ape.reverts("No custom fees set"): - accountant.remove_custom_config(vault.address, strategy.address, sender=daddy) + accountant.removeCustomConfig(vault.address, strategy.address, sender=daddy) new_management = 20 new_performance = 2_000 new_refund = 13 new_max = 18 - accountant.set_custom_config( + accountant.setCustomConfig( vault.address, strategy.address, new_management, @@ -178,103 +175,99 @@ def test_remove_custom_config(daddy, vault, strategy, accountant): sender=daddy, ) - assert accountant.fees(vault.address, strategy.address).custom == True + assert accountant.custom(vault.address, strategy.address) == True assert ( - accountant.fees(vault.address, strategy.address) != accountant.default_config() + accountant.customConfig(vault.address, strategy.address) + != accountant.defaultConfig() ) - assert accountant.fees(vault.address, strategy.address) == ( + assert accountant.customConfig(vault.address, strategy.address) == ( new_management, new_performance, new_refund, new_max, - True, ) - tx = accountant.remove_custom_config(vault.address, strategy.address, sender=daddy) + tx = accountant.removeCustomConfig(vault.address, strategy.address, sender=daddy) - event = list(tx.decode_logs(accountant.UpdateCustomFeeConfig)) + event = list(tx.decode_logs(accountant.RemovedCustomFeeConfig)) assert event[0].strategy == strategy.address assert event[0].vault == vault.address assert len(event) == 1 - config = list(event[0].custom_config) - assert config[0] == 0 - assert config[1] == 0 - assert config[2] == 0 - assert config[3] == 0 - assert config[4] == False + assert accountant.customConfig(vault.address, strategy.address) == (0, 0, 0, 0) - assert accountant.fees(vault.address, strategy.address) == (0, 0, 0, 0, False) +def test_set_fee_manager(generic_accountant, daddy, user): + accountant = generic_accountant + assert accountant.feeManager() == daddy + assert accountant.futureFeeManager() == ZERO_ADDRESS -def test_set_fee_manager(accountant, daddy, user): - assert accountant.fee_manager() == daddy - assert accountant.future_fee_manager() == ZERO_ADDRESS - - with ape.reverts("not fee manager"): - accountant.set_future_fee_manager(user, sender=user) + with ape.reverts("!fee manager"): + accountant.setFutureFeeManager(user, sender=user) with ape.reverts("not future fee manager"): - accountant.accept_fee_manager(sender=user) + accountant.acceptFeeManager(sender=user) with ape.reverts("not future fee manager"): - accountant.accept_fee_manager(sender=daddy) + accountant.acceptFeeManager(sender=daddy) with ape.reverts("ZERO ADDRESS"): - accountant.set_future_fee_manager(ZERO_ADDRESS, sender=daddy) + accountant.setFutureFeeManager(ZERO_ADDRESS, sender=daddy) - tx = accountant.set_future_fee_manager(user, sender=daddy) + tx = accountant.setFutureFeeManager(user, sender=daddy) event = list(tx.decode_logs(accountant.SetFutureFeeManager)) assert len(event) == 1 - assert event[0].future_fee_manager == user.address + assert event[0].futureFeeManager == user.address - assert accountant.fee_manager() == daddy - assert accountant.future_fee_manager() == user + assert accountant.feeManager() == daddy + assert accountant.futureFeeManager() == user with ape.reverts("not future fee manager"): - accountant.accept_fee_manager(sender=daddy) + accountant.acceptFeeManager(sender=daddy) - tx = accountant.accept_fee_manager(sender=user) + tx = accountant.acceptFeeManager(sender=user) event = list(tx.decode_logs(accountant.NewFeeManager)) assert len(event) == 1 - assert event[0].fee_manager == user.address + assert event[0].feeManager == user.address - assert accountant.fee_manager() == user - assert accountant.future_fee_manager() == ZERO_ADDRESS + assert accountant.feeManager() == user + assert accountant.futureFeeManager() == ZERO_ADDRESS -def test_set_fee_recipient(accountant, daddy, user, fee_recipient): - assert accountant.fee_manager() == daddy - assert accountant.fee_recipient() == fee_recipient +def test_set_fee_recipient(generic_accountant, daddy, user, fee_recipient): + accountant = generic_accountant + assert accountant.feeManager() == daddy + assert accountant.feeRecipient() == fee_recipient - with ape.reverts("not fee manager"): - accountant.set_fee_recipient(user, sender=user) + with ape.reverts("!fee manager"): + accountant.setFeeRecipient(user, sender=user) - with ape.reverts("not fee manager"): - accountant.set_fee_recipient(user, sender=fee_recipient) + with ape.reverts("!fee manager"): + accountant.setFeeRecipient(user, sender=fee_recipient) with ape.reverts("ZERO ADDRESS"): - accountant.set_fee_recipient(ZERO_ADDRESS, sender=daddy) + accountant.setFeeRecipient(ZERO_ADDRESS, sender=daddy) - tx = accountant.set_fee_recipient(user, sender=daddy) + tx = accountant.setFeeRecipient(user, sender=daddy) event = list(tx.decode_logs(accountant.UpdateFeeRecipient)) assert len(event) == 1 - assert event[0].old_fee_recipient == fee_recipient.address - assert event[0].new_fee_recipient == user.address + assert event[0].oldFeeRecipient == fee_recipient.address + assert event[0].newFeeRecipient == user.address - assert accountant.fee_recipient() == user + assert accountant.feeRecipient() == user def test_distribute( - accountant, daddy, user, vault, fee_recipient, deposit_into_vault, amount + generic_accountant, daddy, user, vault, fee_recipient, deposit_into_vault, amount ): + accountant = generic_accountant deposit_into_vault(vault, amount) assert vault.balanceOf(user) == amount @@ -289,7 +282,7 @@ def test_distribute( assert vault.balanceOf(daddy.address) == 0 assert vault.balanceOf(fee_recipient.address) == 0 - with ape.reverts("not fee manager"): + with ape.reverts("!fee manager"): accountant.distribute(vault.address, sender=user) tx = accountant.distribute(vault.address, sender=daddy) @@ -307,8 +300,9 @@ def test_distribute( def test_withdraw_underlying( - accountant, daddy, user, vault, asset, deposit_into_vault, amount + generic_accountant, daddy, user, vault, asset, deposit_into_vault, amount ): + accountant = generic_accountant deposit_into_vault(vault, amount) assert vault.balanceOf(user) == amount @@ -321,10 +315,10 @@ def test_withdraw_underlying( assert vault.balanceOf(accountant.address) == amount assert asset.balanceOf(accountant.address) == 0 - with ape.reverts("not fee manager"): - accountant.withdraw_underlying(vault.address, amount, sender=user) + with ape.reverts("!fee manager"): + accountant.withdrawUnderlying(vault.address, amount, sender=user) - tx = accountant.withdraw_underlying(vault.address, amount, sender=daddy) + tx = accountant.withdrawUnderlying(vault.address, amount, sender=daddy) assert vault.balanceOf(user) == 0 assert vault.balanceOf(accountant.address) == 0 @@ -332,7 +326,7 @@ def test_withdraw_underlying( def test_report_profit( - accountant, + generic_accountant, daddy, vault, strategy, @@ -342,9 +336,10 @@ def test_report_profit( provide_strategy_with_debt, asset, ): - config = list(accountant.default_config()) + accountant = generic_accountant + config = list(accountant.defaultConfig()) - accountant.add_vault(vault.address, sender=daddy) + accountant.addVault(vault.address, sender=daddy) vault.add_strategy(strategy.address, sender=daddy) vault.update_max_debt_for_strategy(strategy.address, MAX_INT, sender=daddy) @@ -377,7 +372,7 @@ def test_report_profit( def test_report_no_profit( - accountant, + generic_accountant, daddy, vault, strategy, @@ -387,9 +382,10 @@ def test_report_no_profit( provide_strategy_with_debt, asset, ): - config = list(accountant.default_config()) + accountant = generic_accountant + config = list(accountant.defaultConfig()) - accountant.add_vault(vault.address, sender=daddy) + accountant.addVault(vault.address, sender=daddy) vault.add_strategy(strategy.address, sender=daddy) vault.update_max_debt_for_strategy(strategy.address, MAX_INT, sender=daddy) @@ -422,7 +418,7 @@ def test_report_no_profit( def test_report_max_fee( - accountant, + generic_accountant, daddy, vault, strategy, @@ -432,11 +428,12 @@ def test_report_max_fee( provide_strategy_with_debt, asset, ): + accountant = generic_accountant # SEt max fee of 10% of gain - accountant.update_default_config(100, 1_000, 0, 100, sender=daddy) - config = list(accountant.default_config()) + accountant.updateDefaultConfig(100, 1_000, 0, 100, sender=daddy) + config = list(accountant.defaultConfig()) - accountant.add_vault(vault.address, sender=daddy) + accountant.addVault(vault.address, sender=daddy) vault.add_strategy(strategy.address, sender=daddy) vault.update_max_debt_for_strategy(strategy.address, MAX_INT, sender=daddy) @@ -471,7 +468,7 @@ def test_report_max_fee( def test_report_refund( - accountant, + generic_accountant, daddy, vault, strategy, @@ -481,11 +478,12 @@ def test_report_refund( provide_strategy_with_debt, asset, ): + accountant = generic_accountant # SEt refund ratio to 100% - accountant.update_default_config(100, 1_000, 10_000, 0, sender=daddy) - config = list(accountant.default_config()) + accountant.updateDefaultConfig(100, 1_000, 10_000, 0, sender=daddy) + config = list(accountant.defaultConfig()) - accountant.add_vault(vault.address, sender=daddy) + accountant.addVault(vault.address, sender=daddy) vault.add_strategy(strategy.address, sender=daddy) vault.update_max_debt_for_strategy(strategy.address, MAX_INT, sender=daddy) @@ -525,7 +523,7 @@ def test_report_refund( def test_report_refund_not_enough_asset( - accountant, + generic_accountant, daddy, vault, strategy, @@ -535,11 +533,12 @@ def test_report_refund_not_enough_asset( provide_strategy_with_debt, asset, ): + accountant = generic_accountant # SEt refund ratio to 100% - accountant.update_default_config(100, 1_000, 10_000, 0, sender=daddy) - config = list(accountant.default_config()) + accountant.updateDefaultConfig(100, 1_000, 10_000, 0, sender=daddy) + config = list(accountant.defaultConfig()) - accountant.add_vault(vault.address, sender=daddy) + accountant.addVault(vault.address, sender=daddy) vault.add_strategy(strategy.address, sender=daddy) vault.update_max_debt_for_strategy(strategy.address, MAX_INT, sender=daddy) @@ -579,7 +578,7 @@ def test_report_refund_not_enough_asset( def test_report_profit__custom_config( - accountant, + generic_accountant, daddy, vault, strategy, @@ -589,11 +588,12 @@ def test_report_profit__custom_config( provide_strategy_with_debt, asset, ): - accountant.add_vault(vault.address, sender=daddy) - accountant.set_custom_config( + accountant = generic_accountant + accountant.addVault(vault.address, sender=daddy) + accountant.setCustomConfig( vault.address, strategy.address, 200, 2_000, 0, 0, sender=daddy ) - config = list(accountant.fees(vault.address, strategy.address)) + config = list(accountant.customConfig(vault.address, strategy.address)) vault.add_strategy(strategy.address, sender=daddy) vault.update_max_debt_for_strategy(strategy.address, MAX_INT, sender=daddy) @@ -615,7 +615,6 @@ def test_report_profit__custom_config( tx = accountant.report(strategy.address, gain, loss, sender=vault.address) fees, refunds = tx.return_value - # Managment fees expected_management_fees = amount * config[0] // MAX_BPS # Perf fees @@ -625,7 +624,7 @@ def test_report_profit__custom_config( def test_report_no_profit__custom_config( - accountant, + generic_accountant, daddy, vault, strategy, @@ -635,11 +634,12 @@ def test_report_no_profit__custom_config( provide_strategy_with_debt, asset, ): - accountant.add_vault(vault.address, sender=daddy) - accountant.set_custom_config( + accountant = generic_accountant + accountant.addVault(vault.address, sender=daddy) + accountant.setCustomConfig( vault.address, strategy.address, 200, 2_000, 0, 0, sender=daddy ) - config = list(accountant.fees(vault.address, strategy.address)) + config = list(accountant.customConfig(vault.address, strategy.address)) vault.add_strategy(strategy.address, sender=daddy) vault.update_max_debt_for_strategy(strategy.address, MAX_INT, sender=daddy) @@ -672,7 +672,7 @@ def test_report_no_profit__custom_config( def test_report_max_fee__custom_config( - accountant, + generic_accountant, daddy, vault, strategy, @@ -682,12 +682,13 @@ def test_report_max_fee__custom_config( provide_strategy_with_debt, asset, ): - accountant.add_vault(vault.address, sender=daddy) + accountant = generic_accountant + accountant.addVault(vault.address, sender=daddy) # SEt max fee of 10% of gain - accountant.set_custom_config( + accountant.setCustomConfig( vault.address, strategy.address, 200, 2_000, 0, 100, sender=daddy ) - config = list(accountant.fees(vault.address, strategy.address)) + config = list(accountant.customConfig(vault.address, strategy.address)) vault.add_strategy(strategy.address, sender=daddy) vault.update_max_debt_for_strategy(strategy.address, MAX_INT, sender=daddy) @@ -722,7 +723,7 @@ def test_report_max_fee__custom_config( def test_report_refund__custom_config( - accountant, + generic_accountant, daddy, vault, strategy, @@ -732,12 +733,13 @@ def test_report_refund__custom_config( provide_strategy_with_debt, asset, ): - accountant.add_vault(vault.address, sender=daddy) + accountant = generic_accountant + accountant.addVault(vault.address, sender=daddy) # SEt refund ratio to 100% - accountant.set_custom_config( + accountant.setCustomConfig( vault.address, strategy.address, 200, 2_000, 10_000, 0, sender=daddy ) - config = list(accountant.fees(vault.address, strategy.address)) + config = list(accountant.customConfig(vault.address, strategy.address)) vault.add_strategy(strategy.address, sender=daddy) vault.update_max_debt_for_strategy(strategy.address, MAX_INT, sender=daddy) @@ -777,7 +779,7 @@ def test_report_refund__custom_config( def test_report_refund_not_enough_asset__custom_config( - accountant, + generic_accountant, daddy, vault, strategy, @@ -787,12 +789,13 @@ def test_report_refund_not_enough_asset__custom_config( provide_strategy_with_debt, asset, ): - accountant.add_vault(vault.address, sender=daddy) + accountant = generic_accountant + accountant.addVault(vault.address, sender=daddy) # SEt refund ratio to 100% - accountant.set_custom_config( + accountant.setCustomConfig( vault.address, strategy.address, 200, 2_000, 10_000, 0, sender=daddy ) - config = list(accountant.fees(vault.address, strategy.address)) + config = list(accountant.customConfig(vault.address, strategy.address)) vault.add_strategy(strategy.address, sender=daddy) vault.update_max_debt_for_strategy(strategy.address, MAX_INT, sender=daddy) diff --git a/tests/test_healthcheck_accountant.py b/tests/test_healthcheck_accountant.py new file mode 100644 index 0000000..d6d951c --- /dev/null +++ b/tests/test_healthcheck_accountant.py @@ -0,0 +1,1057 @@ +import ape +from ape import chain +from utils.constants import ChangeType, ZERO_ADDRESS, MAX_BPS, MAX_INT + + +def test_setup(daddy, vault, strategy, healthcheck_accountant, fee_recipient): + accountant = healthcheck_accountant + assert accountant.feeManager() == daddy + assert accountant.futureFeeManager() == ZERO_ADDRESS + assert accountant.feeRecipient() == fee_recipient + assert accountant.defaultConfig().managementFee == 100 + assert accountant.defaultConfig().performanceFee == 1_000 + assert accountant.defaultConfig().refundRatio == 0 + assert accountant.defaultConfig().maxFee == 0 + assert accountant.defaultConfig().maxGain == 10_000 + assert accountant.defaultConfig().maxLoss == 0 + assert accountant.vaults(vault.address) == False + assert accountant.custom(vault.address, strategy.address) == False + assert accountant.customConfig(vault.address, strategy.address).managementFee == 0 + assert accountant.customConfig(vault.address, strategy.address).performanceFee == 0 + assert accountant.customConfig(vault.address, strategy.address).refundRatio == 0 + assert accountant.customConfig(vault.address, strategy.address).maxFee == 0 + assert accountant.customConfig(vault.address, strategy.address).maxGain == 0 + assert accountant.customConfig(vault.address, strategy.address).maxLoss == 0 + + +def test_add_vault( + daddy, + vault, + strategy, + healthcheck_accountant, + amount, + deposit_into_vault, + provide_strategy_with_debt, +): + accountant = healthcheck_accountant + assert accountant.vaults(vault.address) == False + + new_management = 0 + new_performance = 1_000 + new_refund = 0 + new_max_fee = 0 + new_max_gain = 10_000 + new_max_loss = 0 + + tx = accountant.updateDefaultConfig( + new_management, + new_performance, + new_refund, + new_max_fee, + new_max_gain, + new_max_loss, + sender=daddy, + ) + + vault.add_strategy(strategy.address, sender=daddy) + vault.update_max_debt_for_strategy(strategy.address, MAX_INT, sender=daddy) + + deposit_into_vault(vault, amount) + provide_strategy_with_debt(daddy, strategy, vault, amount) + + with ape.reverts("!authorized"): + accountant.report(strategy, 0, 0, sender=vault) + + # set vault in accountant + tx = accountant.addVault(vault.address, sender=daddy) + + event = list(tx.decode_logs(accountant.VaultChanged)) + + assert len(event) == 1 + assert event[0].vault == vault.address + assert event[0].change == ChangeType.ADDED + assert accountant.vaults(vault.address) == True + + # Should work now + tx = accountant.report(strategy, 1_000, 0, sender=vault) + fees, refunds = tx.return_value + assert fees == 100 + assert refunds == 0 + + +def test_remove_vault( + daddy, + vault, + strategy, + healthcheck_accountant, + amount, + deposit_into_vault, + provide_strategy_with_debt, +): + accountant = healthcheck_accountant + assert accountant.vaults(vault.address) == False + + new_management = 0 + new_performance = 1_000 + new_refund = 0 + new_max_fee = 0 + new_max_gain = 10_000 + new_max_loss = 0 + + tx = accountant.updateDefaultConfig( + new_management, + new_performance, + new_refund, + new_max_fee, + new_max_gain, + new_max_loss, + sender=daddy, + ) + + vault.add_strategy(strategy.address, sender=daddy) + vault.update_max_debt_for_strategy(strategy.address, MAX_INT, sender=daddy) + + deposit_into_vault(vault, amount) + provide_strategy_with_debt(daddy, strategy, vault, amount) + + assert accountant.vaults(vault.address) == False + + # set vault in accountant + tx = accountant.addVault(vault.address, sender=daddy) + + event = list(tx.decode_logs(accountant.VaultChanged)) + + assert len(event) == 1 + assert event[0].vault == vault.address + assert event[0].change == ChangeType.ADDED + assert accountant.vaults(vault.address) == True + + # Should work + tx = accountant.report(strategy, 1_000, 0, sender=vault) + fees, refunds = tx.return_value + assert fees == 100 + assert refunds == 0 + + tx = accountant.removeVault(vault.address, sender=daddy) + + event = list(tx.decode_logs(accountant.VaultChanged)) + + assert len(event) == 1 + assert event[0].vault == vault.address + assert event[0].change == ChangeType.REMOVED + assert accountant.vaults(vault.address) == False + + # Should now not be able to report. + with ape.reverts("!authorized"): + accountant.report(strategy, 0, 0, sender=vault) + + +def test_set_default_config(daddy, vault, strategy, healthcheck_accountant): + accountant = healthcheck_accountant + assert accountant.defaultConfig().managementFee == 100 + assert accountant.defaultConfig().performanceFee == 1_000 + assert accountant.defaultConfig().refundRatio == 0 + assert accountant.defaultConfig().maxFee == 0 + assert accountant.defaultConfig().maxGain == 10_000 + assert accountant.defaultConfig().maxLoss == 0 + + new_management = 20 + new_performance = 2_000 + new_refund = 13 + new_max_fee = 18 + new_max_gain = 19 + new_max_loss = 27 + + tx = accountant.updateDefaultConfig( + new_management, + new_performance, + new_refund, + new_max_fee, + new_max_gain, + new_max_loss, + sender=daddy, + ) + + event = list(tx.decode_logs(accountant.UpdateDefaultFeeConfig)) + + assert len(event) == 1 + config = list(event[0].defaultFeeConfig) + assert config[0] == new_management + assert config[1] == new_performance + assert config[2] == new_refund + assert config[3] == new_max_fee + assert config[4] == new_max_gain + assert config[5] == new_max_loss + + assert accountant.defaultConfig().managementFee == new_management + assert accountant.defaultConfig().performanceFee == new_performance + assert accountant.defaultConfig().refundRatio == new_refund + assert accountant.defaultConfig().maxFee == new_max_fee + assert accountant.defaultConfig().maxGain == new_max_gain + assert accountant.defaultConfig().maxLoss == new_max_loss + + +def test_set_custom_config(daddy, vault, strategy, healthcheck_accountant): + accountant = healthcheck_accountant + accountant.addVault(vault.address, sender=daddy) + + assert accountant.customConfig(vault.address, strategy.address) == ( + 0, + 0, + 0, + 0, + 0, + 0, + ) + + new_management = 20 + new_performance = 2_000 + new_refund = 13 + new_max_fee = 18 + new_max_gain = 19 + new_max_loss = 27 + + tx = accountant.setCustomConfig( + vault.address, + strategy.address, + new_management, + new_performance, + new_refund, + new_max_fee, + new_max_gain, + new_max_loss, + sender=daddy, + ) + + event = list(tx.decode_logs(accountant.UpdateCustomFeeConfig)) + + assert len(event) == 1 + assert event[0].vault == vault.address + assert event[0].strategy == strategy.address + config = list(event[0].custom_config) + + assert config[0] == new_management + assert config[1] == new_performance + assert config[2] == new_refund + assert config[3] == new_max_fee + assert config[4] == new_max_gain + assert config[5] == new_max_loss + + assert ( + accountant.customConfig(vault.address, strategy.address) + != accountant.defaultConfig() + ) + assert accountant.customConfig(vault.address, strategy.address) == ( + new_management, + new_performance, + new_refund, + new_max_fee, + new_max_gain, + new_max_loss, + ) + + +def test_remove_custom_config(daddy, vault, strategy, healthcheck_accountant): + accountant = healthcheck_accountant + accountant.addVault(vault.address, sender=daddy) + + assert accountant.customConfig(vault.address, strategy.address) == ( + 0, + 0, + 0, + 0, + 0, + 0, + ) + + with ape.reverts("No custom fees set"): + accountant.removeCustomConfig(vault.address, strategy.address, sender=daddy) + + new_management = 20 + new_performance = 2_000 + new_refund = 13 + new_max_fee = 18 + new_max_gain = 19 + new_max_loss = 27 + + accountant.setCustomConfig( + vault.address, + strategy.address, + new_management, + new_performance, + new_refund, + new_max_fee, + new_max_gain, + new_max_loss, + sender=daddy, + ) + + assert accountant.custom(vault.address, strategy.address) == True + assert ( + accountant.customConfig(vault.address, strategy.address) + != accountant.defaultConfig() + ) + assert accountant.customConfig(vault.address, strategy.address) == ( + new_management, + new_performance, + new_refund, + new_max_fee, + new_max_gain, + new_max_loss, + ) + + tx = accountant.removeCustomConfig(vault.address, strategy.address, sender=daddy) + + event = list(tx.decode_logs(accountant.RemovedCustomFeeConfig)) + + assert event[0].strategy == strategy.address + assert event[0].vault == vault.address + assert len(event) == 1 + + assert accountant.customConfig(vault.address, strategy.address) == ( + 0, + 0, + 0, + 0, + 0, + 0, + ) + + +def test_set_fee_manager(healthcheck_accountant, daddy, user): + accountant = healthcheck_accountant + assert accountant.feeManager() == daddy + assert accountant.futureFeeManager() == ZERO_ADDRESS + + with ape.reverts("!fee manager"): + accountant.setFutureFeeManager(user, sender=user) + + with ape.reverts("not future fee manager"): + accountant.acceptFeeManager(sender=user) + + with ape.reverts("not future fee manager"): + accountant.acceptFeeManager(sender=daddy) + + with ape.reverts("ZERO ADDRESS"): + accountant.setFutureFeeManager(ZERO_ADDRESS, sender=daddy) + + tx = accountant.setFutureFeeManager(user, sender=daddy) + + event = list(tx.decode_logs(accountant.SetFutureFeeManager)) + + assert len(event) == 1 + assert event[0].futureFeeManager == user.address + + assert accountant.feeManager() == daddy + assert accountant.futureFeeManager() == user + + with ape.reverts("not future fee manager"): + accountant.acceptFeeManager(sender=daddy) + + tx = accountant.acceptFeeManager(sender=user) + + event = list(tx.decode_logs(accountant.NewFeeManager)) + + assert len(event) == 1 + assert event[0].feeManager == user.address + + assert accountant.feeManager() == user + assert accountant.futureFeeManager() == ZERO_ADDRESS + + +def test_set_fee_recipient(healthcheck_accountant, daddy, user, fee_recipient): + accountant = healthcheck_accountant + assert accountant.feeManager() == daddy + assert accountant.feeRecipient() == fee_recipient + + with ape.reverts("!fee manager"): + accountant.setFeeRecipient(user, sender=user) + + with ape.reverts("!fee manager"): + accountant.setFeeRecipient(user, sender=fee_recipient) + + with ape.reverts("ZERO ADDRESS"): + accountant.setFeeRecipient(ZERO_ADDRESS, sender=daddy) + + tx = accountant.setFeeRecipient(user, sender=daddy) + + event = list(tx.decode_logs(accountant.UpdateFeeRecipient)) + + assert len(event) == 1 + assert event[0].oldFeeRecipient == fee_recipient.address + assert event[0].newFeeRecipient == user.address + + assert accountant.feeRecipient() == user + + +def test_distribute( + healthcheck_accountant, + daddy, + user, + vault, + fee_recipient, + deposit_into_vault, + amount, +): + accountant = healthcheck_accountant + deposit_into_vault(vault, amount) + + assert vault.balanceOf(user) == amount + assert vault.balanceOf(accountant.address) == 0 + assert vault.balanceOf(daddy.address) == 0 + assert vault.balanceOf(fee_recipient.address) == 0 + + vault.transfer(accountant.address, amount, sender=user) + + assert vault.balanceOf(user) == 0 + assert vault.balanceOf(accountant.address) == amount + assert vault.balanceOf(daddy.address) == 0 + assert vault.balanceOf(fee_recipient.address) == 0 + + with ape.reverts("!fee manager"): + accountant.distribute(vault.address, sender=user) + + tx = accountant.distribute(vault.address, sender=daddy) + + event = list(tx.decode_logs(accountant.DistributeRewards)) + + assert len(event) == 1 + assert event[0].token == vault.address + assert event[0].rewards == amount + + assert vault.balanceOf(user) == 0 + assert vault.balanceOf(accountant.address) == 0 + assert vault.balanceOf(daddy.address) == 0 + assert vault.balanceOf(fee_recipient.address) == amount + + +def test_withdraw_underlying( + healthcheck_accountant, daddy, user, vault, asset, deposit_into_vault, amount +): + accountant = healthcheck_accountant + deposit_into_vault(vault, amount) + + assert vault.balanceOf(user) == amount + assert vault.balanceOf(accountant.address) == 0 + assert asset.balanceOf(accountant.address) == 0 + + vault.transfer(accountant.address, amount, sender=user) + + assert vault.balanceOf(user) == 0 + assert vault.balanceOf(accountant.address) == amount + assert asset.balanceOf(accountant.address) == 0 + + with ape.reverts("!fee manager"): + accountant.withdrawUnderlying(vault.address, amount, sender=user) + + tx = accountant.withdrawUnderlying(vault.address, amount, sender=daddy) + + assert vault.balanceOf(user) == 0 + assert vault.balanceOf(accountant.address) == 0 + assert asset.balanceOf(accountant.address) == amount + + +def test_report_profit( + healthcheck_accountant, + daddy, + vault, + strategy, + amount, + user, + deposit_into_vault, + provide_strategy_with_debt, + asset, +): + accountant = healthcheck_accountant + config = list(accountant.defaultConfig()) + + accountant.addVault(vault.address, sender=daddy) + + vault.add_strategy(strategy.address, sender=daddy) + vault.update_max_debt_for_strategy(strategy.address, MAX_INT, sender=daddy) + + deposit_into_vault(vault, amount) + provide_strategy_with_debt(daddy, strategy, vault, amount) + + assert vault.strategies(strategy.address).current_debt == amount + + # Skip a year + chain.pending_timestamp = ( + vault.strategies(strategy.address).last_report + 31_556_952 - 1 + ) + chain.mine(timestamp=chain.pending_timestamp) + + gain = amount // 10 + loss = 0 + + tx = accountant.report(strategy.address, gain, loss, sender=vault.address) + + fees, refunds = tx.return_value + + # Management fees + expected_management_fees = amount * config[0] // MAX_BPS + # Perf fees + expected_performance_fees = gain * config[1] // MAX_BPS + + assert expected_management_fees + expected_performance_fees == fees + assert refunds == 0 + + +def test_report_no_profit( + healthcheck_accountant, + daddy, + vault, + strategy, + amount, + user, + deposit_into_vault, + provide_strategy_with_debt, + asset, +): + accountant = healthcheck_accountant + config = list(accountant.defaultConfig()) + + accountant.addVault(vault.address, sender=daddy) + + vault.add_strategy(strategy.address, sender=daddy) + vault.update_max_debt_for_strategy(strategy.address, MAX_INT, sender=daddy) + + deposit_into_vault(vault, amount) + provide_strategy_with_debt(daddy, strategy, vault, amount) + + assert vault.strategies(strategy.address).current_debt == amount + + # Skip a year + chain.pending_timestamp = ( + vault.strategies(strategy.address).last_report + 31_556_952 - 1 + ) + chain.mine(timestamp=chain.pending_timestamp) + + gain = 0 + loss = 0 + + tx = accountant.report(strategy.address, gain, loss, sender=vault.address) + + fees, refunds = tx.return_value + + # Management fees + expected_management_fees = amount * config[0] // MAX_BPS + # Perf fees + expected_performance_fees = gain * config[1] // MAX_BPS + + assert expected_management_fees + expected_performance_fees == fees + assert refunds == 0 + + +def test_report_max_fee( + healthcheck_accountant, + daddy, + vault, + strategy, + amount, + user, + deposit_into_vault, + provide_strategy_with_debt, + asset, +): + accountant = healthcheck_accountant + # SEt max fee of 10% of gain + accountant.updateDefaultConfig(100, 1_000, 0, 100, 10_000, 0, sender=daddy) + config = list(accountant.defaultConfig()) + + accountant.addVault(vault.address, sender=daddy) + + vault.add_strategy(strategy.address, sender=daddy) + vault.update_max_debt_for_strategy(strategy.address, MAX_INT, sender=daddy) + + deposit_into_vault(vault, amount) + provide_strategy_with_debt(daddy, strategy, vault, amount) + + assert vault.strategies(strategy.address).current_debt == amount + + # Skip a year + chain.pending_timestamp = ( + vault.strategies(strategy.address).last_report + 31_556_952 - 1 + ) + chain.mine(timestamp=chain.pending_timestamp) + + gain = amount // 10 + loss = 0 + + tx = accountant.report(strategy.address, gain, loss, sender=vault.address) + + fees, refunds = tx.return_value + + # Management fees + expected_management_fees = amount * config[0] // MAX_BPS + # Perf fees + expected_performance_fees = gain * config[1] // MAX_BPS + + # The real fees charged should be less than what would be expected + assert expected_management_fees + expected_performance_fees > fees + assert fees == gain * config[3] / MAX_BPS + assert refunds == 0 + + +def test_report_refund( + healthcheck_accountant, + daddy, + vault, + strategy, + amount, + user, + deposit_into_vault, + provide_strategy_with_debt, + asset, +): + accountant = healthcheck_accountant + # SEt refund ratio to 100% + accountant.updateDefaultConfig(100, 1_000, 10_000, 0, 10_000, 10_000, sender=daddy) + config = list(accountant.defaultConfig()) + + accountant.addVault(vault.address, sender=daddy) + + vault.add_strategy(strategy.address, sender=daddy) + vault.update_max_debt_for_strategy(strategy.address, MAX_INT, sender=daddy) + + deposit_into_vault(vault, amount) + provide_strategy_with_debt(daddy, strategy, vault, amount) + + assert vault.strategies(strategy.address).current_debt == amount + + gain = 0 + loss = amount // 10 + + # make sure accountant has the funds + asset.mint(accountant.address, loss, sender=daddy) + + # Skip a year + chain.pending_timestamp = ( + vault.strategies(strategy.address).last_report + 31_556_952 - 1 + ) + chain.mine(timestamp=chain.pending_timestamp) + + tx = accountant.report(strategy.address, gain, loss, sender=vault.address) + + fees, refunds = tx.return_value + + # Management fees + expected_management_fees = amount * config[0] // MAX_BPS + # Perf fees + expected_performance_fees = gain * config[1] // MAX_BPS + + expected_refunds = loss * config[2] / MAX_BPS + + # The real fees charged should be less than what would be expected + assert expected_management_fees + expected_performance_fees == fees + assert expected_refunds == refunds + assert asset.allowance(accountant.address, vault.address) == expected_refunds + + +def test_report_refund_not_enough_asset( + healthcheck_accountant, + daddy, + vault, + strategy, + amount, + user, + deposit_into_vault, + provide_strategy_with_debt, + asset, +): + accountant = healthcheck_accountant + # SEt refund ratio to 100% + accountant.updateDefaultConfig(100, 1_000, 10_000, 0, 10_000, 10_000, sender=daddy) + config = list(accountant.defaultConfig()) + + accountant.addVault(vault.address, sender=daddy) + + vault.add_strategy(strategy.address, sender=daddy) + vault.update_max_debt_for_strategy(strategy.address, MAX_INT, sender=daddy) + + deposit_into_vault(vault, amount) + provide_strategy_with_debt(daddy, strategy, vault, amount) + + assert vault.strategies(strategy.address).current_debt == amount + + gain = 0 + loss = amount // 10 + + # make sure accountant has the funds + asset.mint(accountant.address, loss // 2, sender=daddy) + + # Skip a year + chain.pending_timestamp = ( + vault.strategies(strategy.address).last_report + 31_556_952 - 1 + ) + chain.mine(timestamp=chain.pending_timestamp) + + tx = accountant.report(strategy.address, gain, loss, sender=vault.address) + + fees, refunds = tx.return_value + + # Management fees + expected_management_fees = amount * config[0] // MAX_BPS + # Perf fees + expected_performance_fees = gain * config[1] // MAX_BPS + + expected_refunds = loss // 2 + + # The real fees charged should be less than what would be expected + assert expected_management_fees + expected_performance_fees == fees + assert expected_refunds == refunds + assert asset.allowance(accountant.address, vault.address) == expected_refunds + + +def test_report_profit__custom_config( + healthcheck_accountant, + daddy, + vault, + strategy, + amount, + user, + deposit_into_vault, + provide_strategy_with_debt, + asset, +): + accountant = healthcheck_accountant + accountant.addVault(vault.address, sender=daddy) + accountant.setCustomConfig( + vault.address, strategy.address, 200, 2_000, 0, 0, 10_000, 0, sender=daddy + ) + config = list(accountant.customConfig(vault.address, strategy.address)) + + vault.add_strategy(strategy.address, sender=daddy) + vault.update_max_debt_for_strategy(strategy.address, MAX_INT, sender=daddy) + + deposit_into_vault(vault, amount) + provide_strategy_with_debt(daddy, strategy, vault, amount) + + assert vault.strategies(strategy.address).current_debt == amount + + # Skip a year + chain.pending_timestamp = ( + vault.strategies(strategy.address).last_report + 31_556_952 - 1 + ) + chain.mine(timestamp=chain.pending_timestamp) + + gain = amount // 10 + loss = 0 + + tx = accountant.report(strategy.address, gain, loss, sender=vault.address) + + fees, refunds = tx.return_value + + # Management fees + expected_management_fees = amount * config[0] // MAX_BPS + # Perf fees + expected_performance_fees = gain * config[1] // MAX_BPS + + assert expected_management_fees + expected_performance_fees == fees + + +def test_report_no_profit__custom_config( + healthcheck_accountant, + daddy, + vault, + strategy, + amount, + user, + deposit_into_vault, + provide_strategy_with_debt, + asset, +): + accountant = healthcheck_accountant + accountant.addVault(vault.address, sender=daddy) + accountant.setCustomConfig( + vault.address, strategy.address, 200, 2_000, 0, 0, 10_000, 0, sender=daddy + ) + config = list(accountant.customConfig(vault.address, strategy.address)) + + vault.add_strategy(strategy.address, sender=daddy) + vault.update_max_debt_for_strategy(strategy.address, MAX_INT, sender=daddy) + + deposit_into_vault(vault, amount) + provide_strategy_with_debt(daddy, strategy, vault, amount) + + assert vault.strategies(strategy.address).current_debt == amount + + # Skip a year + chain.pending_timestamp = ( + vault.strategies(strategy.address).last_report + 31_556_952 - 1 + ) + chain.mine(timestamp=chain.pending_timestamp) + + gain = 0 + loss = 0 + + tx = accountant.report(strategy.address, gain, loss, sender=vault.address) + + fees, refunds = tx.return_value + + # Managmeent fees + expected_management_fees = amount * config[0] // MAX_BPS + # Perf fees + expected_performance_fees = gain * config[1] // MAX_BPS + + assert expected_management_fees + expected_performance_fees == fees + assert refunds == 0 + + +def test_report_max_fee__custom_config( + healthcheck_accountant, + daddy, + vault, + strategy, + amount, + user, + deposit_into_vault, + provide_strategy_with_debt, + asset, +): + accountant = healthcheck_accountant + accountant.addVault(vault.address, sender=daddy) + # SEt max fee of 10% of gain + accountant.setCustomConfig( + vault.address, strategy.address, 200, 2_000, 0, 100, 10_000, 0, sender=daddy + ) + config = list(accountant.customConfig(vault.address, strategy.address)) + + vault.add_strategy(strategy.address, sender=daddy) + vault.update_max_debt_for_strategy(strategy.address, MAX_INT, sender=daddy) + + deposit_into_vault(vault, amount) + provide_strategy_with_debt(daddy, strategy, vault, amount) + + assert vault.strategies(strategy.address).current_debt == amount + + # Skip a year + chain.pending_timestamp = ( + vault.strategies(strategy.address).last_report + 31_556_952 - 1 + ) + chain.mine(timestamp=chain.pending_timestamp) + + gain = amount // 10 + loss = 0 + + tx = accountant.report(strategy.address, gain, loss, sender=vault.address) + + fees, refunds = tx.return_value + + # Management fees + expected_management_fees = amount * config[0] // MAX_BPS + # Perf fees + expected_performance_fees = gain * config[1] // MAX_BPS + + # The real fees charged should be less than what would be expected + assert expected_management_fees + expected_performance_fees > fees + assert fees == gain * config[3] / MAX_BPS + assert refunds == 0 + + +def test_report_profit__custom_zero_max_gain__reverts( + healthcheck_accountant, + daddy, + vault, + strategy, + amount, + user, + deposit_into_vault, + provide_strategy_with_debt, + asset, +): + accountant = healthcheck_accountant + accountant.addVault(vault.address, sender=daddy) + # SEt max gain to 0% + accountant.setCustomConfig( + vault.address, strategy.address, 200, 2_000, 0, 100, 0, 0, sender=daddy + ) + config = list(accountant.customConfig(vault.address, strategy.address)) + + vault.add_strategy(strategy.address, sender=daddy) + vault.update_max_debt_for_strategy(strategy.address, MAX_INT, sender=daddy) + + deposit_into_vault(vault, amount) + provide_strategy_with_debt(daddy, strategy, vault, amount) + + assert vault.strategies(strategy.address).current_debt == amount + + # Skip a year + chain.pending_timestamp = ( + vault.strategies(strategy.address).last_report + 31_556_952 - 1 + ) + chain.mine(timestamp=chain.pending_timestamp) + + gain = amount // 10 + loss = 0 + + with ape.reverts("too much gain"): + accountant.report(strategy.address, gain, loss, sender=vault.address) + + +def test_report_loss__custom_zero_max_loss__reverts( + healthcheck_accountant, + daddy, + vault, + strategy, + amount, + user, + deposit_into_vault, + provide_strategy_with_debt, + asset, +): + accountant = healthcheck_accountant + accountant.addVault(vault.address, sender=daddy) + # SEt max gain to 0% + accountant.setCustomConfig( + vault.address, strategy.address, 200, 2_000, 0, 100, 0, 0, sender=daddy + ) + config = list(accountant.customConfig(vault.address, strategy.address)) + + vault.add_strategy(strategy.address, sender=daddy) + vault.update_max_debt_for_strategy(strategy.address, MAX_INT, sender=daddy) + + deposit_into_vault(vault, amount) + provide_strategy_with_debt(daddy, strategy, vault, amount) + + assert vault.strategies(strategy.address).current_debt == amount + + # Skip a year + chain.pending_timestamp = ( + vault.strategies(strategy.address).last_report + 31_556_952 - 1 + ) + chain.mine(timestamp=chain.pending_timestamp) + + gain = 0 + loss = 1 + + with ape.reverts("too much loss"): + accountant.report(strategy.address, gain, loss, sender=vault.address) + + +def test_report_refund__custom_config( + healthcheck_accountant, + daddy, + vault, + strategy, + amount, + user, + deposit_into_vault, + provide_strategy_with_debt, + asset, +): + accountant = healthcheck_accountant + accountant.addVault(vault.address, sender=daddy) + # SEt refund ratio to 100% + accountant.setCustomConfig( + vault.address, + strategy.address, + 200, + 2_000, + 10_000, + 0, + 10_000, + 10_000, + sender=daddy, + ) + config = list(accountant.customConfig(vault.address, strategy.address)) + + vault.add_strategy(strategy.address, sender=daddy) + vault.update_max_debt_for_strategy(strategy.address, MAX_INT, sender=daddy) + + deposit_into_vault(vault, amount) + provide_strategy_with_debt(daddy, strategy, vault, amount) + + assert vault.strategies(strategy.address).current_debt == amount + + gain = 0 + loss = amount // 10 + + # make sure accountant has the funds + asset.mint(accountant.address, loss, sender=daddy) + + # Skip a year + chain.pending_timestamp = ( + vault.strategies(strategy.address).last_report + 31_556_952 - 1 + ) + chain.mine(timestamp=chain.pending_timestamp) + + tx = accountant.report(strategy.address, gain, loss, sender=vault.address) + + fees, refunds = tx.return_value + + # Management fees + expected_management_fees = amount * config[0] // MAX_BPS + # Perf fees + expected_performance_fees = gain * config[1] // MAX_BPS + + expected_refunds = loss * config[2] / MAX_BPS + + # The real fees charged should be less than what would be expected + assert expected_management_fees + expected_performance_fees == fees + assert expected_refunds == refunds + assert asset.allowance(accountant.address, vault.address) == expected_refunds + + +def test_report_refund_not_enough_asset__custom_config( + healthcheck_accountant, + daddy, + vault, + strategy, + amount, + user, + deposit_into_vault, + provide_strategy_with_debt, + asset, +): + accountant = healthcheck_accountant + accountant.addVault(vault.address, sender=daddy) + # SEt refund ratio to 100% + accountant.setCustomConfig( + vault.address, + strategy.address, + 200, + 2_000, + 10_000, + 0, + 10_000, + 10_000, + sender=daddy, + ) + config = list(accountant.customConfig(vault.address, strategy.address)) + + vault.add_strategy(strategy.address, sender=daddy) + vault.update_max_debt_for_strategy(strategy.address, MAX_INT, sender=daddy) + + deposit_into_vault(vault, amount) + provide_strategy_with_debt(daddy, strategy, vault, amount) + + assert vault.strategies(strategy.address).current_debt == amount + + gain = 0 + loss = amount // 10 + + # make sure accountant has the funds + asset.mint(accountant.address, loss // 2, sender=daddy) + + # Skip a year + chain.pending_timestamp = ( + vault.strategies(strategy.address).last_report + 31_556_952 - 1 + ) + chain.mine(timestamp=chain.pending_timestamp) + + tx = accountant.report(strategy.address, gain, loss, sender=vault.address) + + fees, refunds = tx.return_value + + # Management fees + expected_management_fees = amount * config[0] // MAX_BPS + # Perf fees + expected_performance_fees = gain * config[1] // MAX_BPS + + expected_refunds = loss // 2 + + # The real fees charged should be less than what would be expected + assert expected_management_fees + expected_performance_fees == fees + assert expected_refunds == refunds + assert asset.allowance(accountant.address, vault.address) == expected_refunds