From 8fc395a278a472d6f86cd87fe8611c4be1b78133 Mon Sep 17 00:00:00 2001 From: ckoopmann Date: Thu, 7 Dec 2023 14:21:28 +0800 Subject: [PATCH 01/36] Initialize OptimisticAuctionRebalance module from Global version and fix compilation issues --- .../BaseAuctionRebalanceExtension.sol | 212 +++++++++++ .../BaseOptimisticRebalanceExtension.sol | 355 ++++++++++++++++++ 2 files changed, 567 insertions(+) create mode 100644 contracts/base-extensions/BaseAuctionRebalanceExtension.sol create mode 100644 contracts/base-extensions/BaseOptimisticRebalanceExtension.sol diff --git a/contracts/base-extensions/BaseAuctionRebalanceExtension.sol b/contracts/base-extensions/BaseAuctionRebalanceExtension.sol new file mode 100644 index 00000000..b054cd6f --- /dev/null +++ b/contracts/base-extensions/BaseAuctionRebalanceExtension.sol @@ -0,0 +1,212 @@ +/* + Copyright 2023 Index Coop + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + SPDX-License-Identifier: Apache License, Version 2.0 +*/ + +pragma solidity 0.6.10; +pragma experimental "ABIEncoderV2"; + +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol"; + +import { AddressArrayUtils } from "../lib/AddressArrayUtils.sol"; +import { BaseExtension } from "../lib/BaseExtension.sol"; +import { IAuctionRebalanceModuleV1 } from "../interfaces/IAuctionRebalanceModuleV1.sol"; +import { IManagerCore } from "../interfaces/IManagerCore.sol"; +import { ISetToken } from "../interfaces/ISetToken.sol"; +import { IBaseManager } from "../interfaces/IBaseManager.sol"; + + +/** + * @title BaseAuctionRebalanceExtension + * @author Index Coop + * + * @dev Extension contract for interacting with the AuctionRebalanceModuleV1. This contract acts as a pass-through and functions + * are only callable by operator. + */ +contract BaseAuctionRebalanceExtension is BaseExtension { + using AddressArrayUtils for address[]; + using SafeMath for uint256; + + /* ============ Events ============ */ + + event AuctionRebalanceExtensionInitialized( + address indexed setToken, + address indexed _baseManager + ); + + + /* ============ Structs ============ */ + + struct AuctionExecutionParams { + uint256 targetUnit; // Target quantity of the component in Set, in precise units (10 ** 18). + string priceAdapterName; // Identifier for the price adapter to be used. + bytes priceAdapterConfigData; // Encoded data for configuring the chosen price adapter. + } + + /* ============ State Variables ============ */ + + IAuctionRebalanceModuleV1 public immutable auctionModule; // AuctionRebalanceModuleV1 + ISetToken public immutable setToken; // SetToken instance associated with BaseManager + + + /* ============ Constructor ============ */ + /** + * @dev Instantiate with ManagerCore address and WrapModuleV2 address. + * + * @param _manager Address of BaseManager contract + * @param _auctionModule Address of AuctionRebalanceModuleV1 contract + */ + constructor(IBaseManager _manager, IAuctionRebalanceModuleV1 _auctionModule) public BaseExtension(_manager) { + auctionModule = _auctionModule; + setToken = _manager.setToken(); + } + + /* ============ External Functions ============ */ + + + /** + * @dev ONLY OPERATOR: Initializes AuctionRebalanceModuleV1 on the SetToken associated with the BaseManager. + */ + function initialize() external onlyOperator { + bytes memory callData = abi.encodeWithSelector(IAuctionRebalanceModuleV1.initialize.selector, setToken); + invokeManager(address(auctionModule), callData); + } + + /** + * @dev OPERATOR ONLY: Checks that the old components array matches the current components array and then invokes the + * AuctionRebalanceModuleV1 startRebalance function. + * + * Refer to AuctionRebalanceModuleV1 for function specific restrictions. + * + * @param _quoteAsset ERC20 token used as the quote asset in auctions. + * @param _oldComponents Addresses of existing components in the SetToken. + * @param _newComponents Addresses of new components to be added. + * @param _newComponentsAuctionParams AuctionExecutionParams for new components, indexed corresponding to _newComponents. + * @param _oldComponentsAuctionParams AuctionExecutionParams for existing components, indexed corresponding to + * the current component positions. Set to 0 for components being removed. + * @param _shouldLockSetToken Indicates if the rebalance should lock the SetToken. + * @param _rebalanceDuration Duration of the rebalance in seconds. + * @param _positionMultiplier Position multiplier at the time target units were calculated. + */ + function startRebalance( + IERC20 _quoteAsset, + address[] memory _oldComponents, + address[] memory _newComponents, + AuctionExecutionParams[] memory _newComponentsAuctionParams, + AuctionExecutionParams[] memory _oldComponentsAuctionParams, + bool _shouldLockSetToken, + uint256 _rebalanceDuration, + uint256 _positionMultiplier + ) + external + virtual + onlyOperator + { + address[] memory currentComponents = setToken.getComponents(); + + require(currentComponents.length == _oldComponents.length, "Mismatch: old and current components length"); + + for (uint256 i = 0; i < _oldComponents.length; i++) { + require(currentComponents[i] == _oldComponents[i], "Mismatch: old and current components"); + } + + bytes memory callData = abi.encodeWithSelector( + IAuctionRebalanceModuleV1.startRebalance.selector, + setToken, + _quoteAsset, + _newComponents, + _newComponentsAuctionParams, + _oldComponentsAuctionParams, + _shouldLockSetToken, + _rebalanceDuration, + _positionMultiplier + ); + + invokeManager(address(auctionModule), callData); + } + + /** + * @dev OPERATOR ONLY: Unlocks SetToken via AuctionRebalanceModuleV1. + * Refer to AuctionRebalanceModuleV1 for function specific restrictions. + * + */ + function unlock() external onlyOperator { + bytes memory callData = abi.encodeWithSelector( + IAuctionRebalanceModuleV1.unlock.selector, + setToken + ); + + invokeManager(address(auctionModule), callData); + } + + /** + * @dev OPERATOR ONLY: Sets the target raise percentage for all components on AuctionRebalanceModuleV1. + * Refer to AuctionRebalanceModuleV1 for function specific restrictions. + * + * @param _raiseTargetPercentage Amount to raise all component's unit targets by (in precise units) + */ + function setRaiseTargetPercentage(uint256 _raiseTargetPercentage) external onlyOperator { + bytes memory callData = abi.encodeWithSelector( + IAuctionRebalanceModuleV1.setRaiseTargetPercentage.selector, + setToken, + _raiseTargetPercentage + ); + + invokeManager(address(auctionModule), callData); + } + + /** + * @dev OPERATOR ONLY: Updates the bidding permission status for a list of addresses on AuctionRebalanceModuleV1. + * Refer to AuctionRebalanceModuleV1 for function specific restrictions. + * + * @param _bidders An array of addresses whose bidding permission status is to be toggled. + * @param _statuses An array of booleans indicating the new bidding permission status for each corresponding address in `_bidders`. + */ + function setBidderStatus( + ISetToken setToken, + address[] memory _bidders, + bool[] memory _statuses + ) + external + onlyOperator + { + bytes memory callData = abi.encodeWithSelector( + IAuctionRebalanceModuleV1.setBidderStatus.selector, + setToken, + _bidders, + _statuses + ); + + invokeManager(address(auctionModule), callData); + } + + /** + * @dev OPERATOR ONLY: Sets whether anyone can bid on the AuctionRebalanceModuleV1. + * Refer to AuctionRebalanceModuleV1 for function specific restrictions. + * + * @param _status A boolean indicating if anyone can bid. + */ + function setAnyoneBid(bool _status) external onlyOperator { + bytes memory callData = abi.encodeWithSelector( + IAuctionRebalanceModuleV1.setAnyoneBid.selector, + setToken, + _status + ); + + invokeManager(address(auctionModule), callData); + } +} diff --git a/contracts/base-extensions/BaseOptimisticRebalanceExtension.sol b/contracts/base-extensions/BaseOptimisticRebalanceExtension.sol new file mode 100644 index 00000000..d5b00779 --- /dev/null +++ b/contracts/base-extensions/BaseOptimisticRebalanceExtension.sol @@ -0,0 +1,355 @@ +/* + Copyright 2023 Index Coop + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + SPDX-License-Identifier: Apache License, Version 2.0 +*/ + +pragma solidity 0.6.10; +pragma experimental "ABIEncoderV2"; + +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; + +import { AddressArrayUtils } from "../lib/AddressArrayUtils.sol"; + +import { IAuctionRebalanceModuleV1 } from "../interfaces/IAuctionRebalanceModuleV1.sol"; +import { IBaseManager } from "../interfaces/IBaseManager.sol"; +import { ISetToken } from "../interfaces/ISetToken.sol"; +import { IDelegatedManager } from "../interfaces/IDelegatedManager.sol"; +import {BaseAuctionRebalanceExtension} from "./BaseAuctionRebalanceExtension.sol"; +import {AncillaryData } from "../lib/AncillaryData.sol"; +import {OptimisticOracleV3Interface} from "../interfaces/OptimisticOracleV3Interface.sol"; + +/** + * @title BaseOptimisticAuctionRebalanceExtension + * @author Index Coop + * + * @dev The contract extends `BaseAuctionRebalanceExtension` by adding an optimistic oracle mechanism for validating rules on the proposing and executing of rebalances. + * It allows setting product-specific parameters for optimistic rebalancing and includes callback functions for resolved or disputed assertions. + */ +contract BaseOptimisticAuctionRebalanceExtension is BaseAuctionRebalanceExtension { + using AddressArrayUtils for address[]; + using SafeERC20 for IERC20; + + /* ============ Events ============ */ + + event ProductSettingsUpdated( + IERC20 indexed setToken, + address indexed delegatedManager, + OptimisticRebalanceParams optimisticParams, + bytes32 indexed rulesHash + ); + event RebalanceProposed( + ISetToken indexed setToken, + IERC20 indexed quoteAsset, + address[] oldComponents, + address[] newComponents, + AuctionExecutionParams[] newComponentsAuctionParams, + AuctionExecutionParams[] oldComponentsAuctionParams, + bool shouldLockSetToken, + uint256 rebalanceDuration, + uint256 positionMultiplier + ); + + event AssertedClaim( + IERC20 indexed setToken, + address indexed _assertedBy, + bytes32 indexed rulesHash, + bytes32 _assertionId, + bytes _claimData + ); + + event ProposalDeleted( + bytes32 assertionID, + Proposal indexed proposal + ); + /* ============ Structs ============ */ + + struct AuctionExtensionParams { + IBaseManager baseManager; // Manager Contract of the set token for which to deploy this extension + IAuctionRebalanceModuleV1 auctionModule; // Contract that rebalances index sets via single-asset auctions + } + + struct OptimisticRebalanceParams{ + IERC20 collateral; // Collateral currency used to assert proposed transactions. + uint64 liveness; // The amount of time to dispute proposed transactions before they can be executed. + uint256 bondAmount; // Configured amount of collateral currency to make assertions for proposed transactions. + bytes32 identifier; // Identifier used to request price from the DVM. + OptimisticOracleV3Interface optimisticOracleV3; // Optimistic Oracle V3 contract used to assert proposed transactions. + } + + struct ProductSettings{ + OptimisticRebalanceParams optimisticParams; // OptimisticRebalanceParams struct containing optimistic rebalance parameters. + bytes32 rulesHash; // IPFS hash of the rules for the product. + } + + struct Proposal{ + bytes32 proposalHash; // Hash of the proposal. + ISetToken product; // Address of the SetToken to set rules and settings for. + } + + /* ============ State Variables ============ */ + + mapping (ISetToken=>ProductSettings) public productSettings; // Mapping of set token to ProductSettings + mapping(bytes32 => bytes32) public assertionIds; // Maps proposal hashes to assertionIds. + mapping(bytes32 => Proposal) public proposedProduct; // Maps assertionIds to a Proposal. + + // Keys for assertion claim data. + bytes public constant PROPOSAL_HASH_KEY = "proposalHash"; + bytes public constant RULES_KEY = "rulesIPFSHash"; + + + /* ============ Constructor ============ */ + /* + * @dev Initializes the BaseOptimisticAuctionRebalanceExtension with the passed parameters. + * + * @param _auctionParams AuctionExtensionParams struct containing the baseManager and auctionModule addresses. + */ + constructor(AuctionExtensionParams memory _auctionParams) public BaseAuctionRebalanceExtension(_auctionParams.baseManager, _auctionParams.auctionModule) { + + } + + /* ============ External Functions ============ */ + + /** + * @dev OPERATOR ONLY: sets product settings for a given set token + * @param _optimisticParams OptimisticRebalanceParams struct containing optimistic rebalance parameters. + * @param _rulesHash bytes32 containing the ipfs hash rules for the product. + */ + function setProductSettings( + OptimisticRebalanceParams memory _optimisticParams, + bytes32 _rulesHash + ) + external + onlyOperator + { + productSettings[setToken] = ProductSettings({ + optimisticParams: _optimisticParams, + rulesHash: _rulesHash + }); + emit ProductSettingsUpdated(setToken, setToken.manager(), _optimisticParams, _rulesHash); + } + + /** + * @param _quoteAsset ERC20 token used as the quote asset in auctions. + * @param _oldComponents Addresses of existing components in the SetToken. + * @param _newComponents Addresses of new components to be added. + * @param _newComponentsAuctionParams AuctionExecutionParams for new components, indexed corresponding to _newComponents. + * @param _oldComponentsAuctionParams AuctionExecutionParams for existing components, indexed corresponding to + * the current component positions. Set to 0 for components being removed. + * @param _shouldLockSetToken Indicates if the rebalance should lock the SetToken. + * @param _rebalanceDuration Duration of the rebalance in seconds. + * @param _positionMultiplier Position multiplier at the time target units were calculated. + */ + function proposeRebalance( + IERC20 _quoteAsset, + address[] memory _oldComponents, + address[] memory _newComponents, + AuctionExecutionParams[] memory _newComponentsAuctionParams, + AuctionExecutionParams[] memory _oldComponentsAuctionParams, + bool _shouldLockSetToken, + uint256 _rebalanceDuration, + uint256 _positionMultiplier + ) + external + { + bytes32 proposalHash = keccak256(abi.encode( + setToken, + _quoteAsset, + _oldComponents, + _newComponents, + _newComponentsAuctionParams, + _oldComponentsAuctionParams, + _shouldLockSetToken, + _rebalanceDuration, + _positionMultiplier + )); + ProductSettings memory settings = productSettings[setToken]; + + require(assertionIds[proposalHash] == bytes32(0), "Proposal already exists"); + require(settings.rulesHash != bytes32(""), "Rules not set"); + require(address(settings.optimisticParams.optimisticOracleV3) != address(0), "Oracle not set"); + + + bytes memory claim = _constructClaim(proposalHash, settings.rulesHash); + uint256 totalBond = _pullBond(settings.optimisticParams); + + bytes32 assertionId = settings.optimisticParams.optimisticOracleV3.assertTruth( + claim, + msg.sender, + address(this), + address(0), + settings.optimisticParams.liveness, + settings.optimisticParams.collateral, + totalBond, + settings.optimisticParams.identifier, + bytes32(0) + ); + + assertionIds[proposalHash] = assertionId; + proposedProduct[assertionId] = Proposal({ + proposalHash: proposalHash, + product: setToken + }); + + emit RebalanceProposed( setToken, _quoteAsset, _oldComponents, _newComponents, _newComponentsAuctionParams, _oldComponentsAuctionParams, _shouldLockSetToken, _rebalanceDuration, _positionMultiplier); + emit AssertedClaim(setToken, msg.sender, settings.rulesHash, assertionId, claim); + + } + + /** + * @dev OPERATOR ONLY: Checks that the old components array matches the current components array and then invokes the + * AuctionRebalanceModuleV1 startRebalance function. + * + * Refer to AuctionRebalanceModuleV1 for function specific restrictions. + * + * @param _quoteAsset ERC20 token used as the quote asset in auctions. + * @param _oldComponents Addresses of existing components in the SetToken. + * @param _newComponents Addresses of new components to be added. + * @param _newComponentsAuctionParams AuctionExecutionParams for new components, indexed corresponding to _newComponents. + * @param _oldComponentsAuctionParams AuctionExecutionParams for existing components, indexed corresponding to + * the current component positions. Set to 0 for components being removed. + * @param _shouldLockSetToken Indicates if the rebalance should lock the SetToken. + * @param _rebalanceDuration Duration of the rebalance in seconds. + * @param _positionMultiplier Position multiplier at the time target units were calculated. + */ + function startRebalance( + IERC20 _quoteAsset, + address[] memory _oldComponents, + address[] memory _newComponents, + AuctionExecutionParams[] memory _newComponentsAuctionParams, + AuctionExecutionParams[] memory _oldComponentsAuctionParams, + bool _shouldLockSetToken, + uint256 _rebalanceDuration, + uint256 _positionMultiplier + ) + external + override + { + bytes32 proposalHash = keccak256(abi.encode( + setToken, + _quoteAsset, + _oldComponents, + _newComponents, + _newComponentsAuctionParams, + _oldComponentsAuctionParams, + _shouldLockSetToken, + _rebalanceDuration, + _positionMultiplier + )); + + bytes32 assertionId = assertionIds[proposalHash]; + // Disputed assertions are expected to revert here. Assumption past this point is that there was a valid assertion. + require(assertionId != bytes32(0), "Proposal hash does not exist"); + + ProductSettings memory settings = productSettings[setToken]; + _deleteProposal(assertionId); + + // There is no need to check the assertion result as this point can be reached only for non-disputed assertions. + // It is expected that future versions of the Optimistic Oracle will always revert here, + // if the assertionId has not been settled and can not currently be settled. + settings.optimisticParams.optimisticOracleV3.settleAndGetAssertionResult(assertionId); + + address[] memory currentComponents = setToken.getComponents(); + + require(currentComponents.length == _oldComponents.length, "Mismatch: old and current components length"); + + for (uint256 i = 0; i < _oldComponents.length; i++) { + require(currentComponents[i] == _oldComponents[i], "Mismatch: old and current components"); + } + + bytes memory callData = abi.encodeWithSelector( + IAuctionRebalanceModuleV1.startRebalance.selector, + setToken, + _quoteAsset, + _newComponents, + _newComponentsAuctionParams, + _oldComponentsAuctionParams, + _shouldLockSetToken, + _rebalanceDuration, + _positionMultiplier + ); + + invokeManager(address(auctionModule), callData); + } + + // Constructs the claim that will be asserted at the Optimistic Oracle V3. + function _constructClaim(bytes32 proposalHash, bytes32 rulesHash) internal pure returns (bytes memory) { + return + abi.encodePacked( + AncillaryData.appendKeyValueBytes32("", PROPOSAL_HASH_KEY, proposalHash), + ",", + RULES_KEY, + ":\"", + rulesHash, + "\"" + ); + } + + /** + * @notice Callback function that is called by Optimistic Oracle V3 when an assertion is resolved. + * @dev This function does nothing and is only here to satisfy the callback recipient interface. + * @param assertionId The identifier of the assertion that was resolved. + * @param assertedTruthfully Whether the assertion was resolved as truthful or not. + */ + function assertionResolvedCallback(bytes32 assertionId, bool assertedTruthfully) external {} + + /** + * @notice Callback to automatically delete a proposal that was disputed. + * @param _assertionId the identifier of the disputed assertion. + */ + function assertionDisputedCallback(bytes32 _assertionId) external { + Proposal memory proposal = proposedProduct[_assertionId]; + ProductSettings memory settings = productSettings[proposal.product]; + + require(address(settings.optimisticParams.optimisticOracleV3) != address(0), "Invalid oracle address"); + + // If the sender is the Optimistic Oracle V3, delete the proposal and associated assertionId. + if (msg.sender == address(settings.optimisticParams.optimisticOracleV3)) { + // Delete the disputed proposal and associated assertionId. + _deleteProposal(_assertionId); + + } else { + // If the sender is not the expected Optimistic Oracle V3, check if the expected Oracle has the assertion and if not delete. + require(proposal.proposalHash != bytes32(0), "Invalid proposal hash"); + require(settings.optimisticParams.optimisticOracleV3.getAssertion(_assertionId).asserter == address(0), "Oracle has assertion"); + _deleteProposal(_assertionId); + } + emit ProposalDeleted(_assertionId, proposal); + } + + /// @notice Pulls the higher of the minimum bond or configured bond amount from the sender. + /// @dev Internal function to pull the user's bond before asserting a claim. + /// @param optimisticRebalanceParams optimistic rebalance parameters for the product. + /// @return Bond amount pulled from the sender. + function _pullBond(OptimisticRebalanceParams memory optimisticRebalanceParams) internal returns (uint256) { + uint256 minimumBond = optimisticRebalanceParams.optimisticOracleV3.getMinimumBond(address(optimisticRebalanceParams.collateral)); + uint256 totalBond = minimumBond > optimisticRebalanceParams.bondAmount ? minimumBond : optimisticRebalanceParams.bondAmount; + + optimisticRebalanceParams.collateral.safeTransferFrom(msg.sender, address(this), totalBond); + optimisticRebalanceParams.collateral.safeIncreaseAllowance(address(optimisticRebalanceParams.optimisticOracleV3), totalBond); + + return totalBond; + } + + /// @notice Delete an existing proposal and associated assertionId. + /// @dev Internal function that deletes a proposal and associated assertionId. + /// @param assertionId assertionId of the proposal to delete. + function _deleteProposal(bytes32 assertionId) internal { + Proposal memory proposal = proposedProduct[assertionId]; + delete assertionIds[proposal.proposalHash]; + delete proposedProduct[assertionId]; + } +} From 7ee621d3b68ea068634bd0bc3a9c9dd75f6cc0b1 Mon Sep 17 00:00:00 2001 From: ckoopmann Date: Thu, 7 Dec 2023 16:09:25 +0800 Subject: [PATCH 02/36] Add AssetAllowList to BaseOptimisticRebalanceExtension --- .../BaseOptimisticRebalanceExtension.sol | 39 ++++- contracts/lib/AssetAllowList.sol | 151 ++++++++++++++++++ 2 files changed, 188 insertions(+), 2 deletions(-) create mode 100644 contracts/lib/AssetAllowList.sol diff --git a/contracts/base-extensions/BaseOptimisticRebalanceExtension.sol b/contracts/base-extensions/BaseOptimisticRebalanceExtension.sol index d5b00779..359e50fe 100644 --- a/contracts/base-extensions/BaseOptimisticRebalanceExtension.sol +++ b/contracts/base-extensions/BaseOptimisticRebalanceExtension.sol @@ -30,6 +30,7 @@ import { ISetToken } from "../interfaces/ISetToken.sol"; import { IDelegatedManager } from "../interfaces/IDelegatedManager.sol"; import {BaseAuctionRebalanceExtension} from "./BaseAuctionRebalanceExtension.sol"; import {AncillaryData } from "../lib/AncillaryData.sol"; +import { AssetAllowList } from "../lib/AssetAllowList.sol"; import {OptimisticOracleV3Interface} from "../interfaces/OptimisticOracleV3Interface.sol"; /** @@ -39,7 +40,7 @@ import {OptimisticOracleV3Interface} from "../interfaces/OptimisticOracleV3Inter * @dev The contract extends `BaseAuctionRebalanceExtension` by adding an optimistic oracle mechanism for validating rules on the proposing and executing of rebalances. * It allows setting product-specific parameters for optimistic rebalancing and includes callback functions for resolved or disputed assertions. */ -contract BaseOptimisticAuctionRebalanceExtension is BaseAuctionRebalanceExtension { +contract BaseOptimisticAuctionRebalanceExtension is BaseAuctionRebalanceExtension, AssetAllowList { using AddressArrayUtils for address[]; using SafeERC20 for IERC20; @@ -75,11 +76,14 @@ contract BaseOptimisticAuctionRebalanceExtension is BaseAuctionRebalanceExtensi bytes32 assertionID, Proposal indexed proposal ); + /* ============ Structs ============ */ struct AuctionExtensionParams { IBaseManager baseManager; // Manager Contract of the set token for which to deploy this extension IAuctionRebalanceModuleV1 auctionModule; // Contract that rebalances index sets via single-asset auctions + bool useAssetAllowlist; // Bool indicating whether to use asset allow list + address[] allowedAssets; // Array of allowed assets } struct OptimisticRebalanceParams{ @@ -117,12 +121,42 @@ contract BaseOptimisticAuctionRebalanceExtension is BaseAuctionRebalanceExtensi * * @param _auctionParams AuctionExtensionParams struct containing the baseManager and auctionModule addresses. */ - constructor(AuctionExtensionParams memory _auctionParams) public BaseAuctionRebalanceExtension(_auctionParams.baseManager, _auctionParams.auctionModule) { + constructor(AuctionExtensionParams memory _auctionParams) public BaseAuctionRebalanceExtension(_auctionParams.baseManager, _auctionParams.auctionModule) AssetAllowList(_auctionParams.allowedAssets, _auctionParams.useAssetAllowlist) { } /* ============ External Functions ============ */ + /** + * ONLY OPERATOR: Add new asset(s) that can be traded to, wrapped to, or claimed + * + * @param _assets New asset(s) to add + */ + function addAllowedAssets(address[] memory _assets) external onlyOperator { + _addAllowedAssets(_assets); + } + + /** + * ONLY OPERATOR: Remove asset(s) so that it/they can't be traded to, wrapped to, or claimed + * + * @param _assets Asset(s) to remove + */ + function removeAllowedAssets(address[] memory _assets) external onlyOperator { + _removeAllowedAssets(_assets); + } + + /** + * ONLY OPERATOR: Toggle useAssetAllowlist on and off. When false asset allowlist is ignored + * when true it is enforced. + * + * @param _useAssetAllowlist Bool indicating whether to use asset allow list + */ + function updateUseAssetAllowlist(bool _useAssetAllowlist) external onlyOperator { + _updateUseAssetAllowlist(_useAssetAllowlist); + } + + + /** * @dev OPERATOR ONLY: sets product settings for a given set token * @param _optimisticParams OptimisticRebalanceParams struct containing optimistic rebalance parameters. @@ -352,4 +386,5 @@ contract BaseOptimisticAuctionRebalanceExtension is BaseAuctionRebalanceExtensi delete assertionIds[proposal.proposalHash]; delete proposedProduct[assertionId]; } + } diff --git a/contracts/lib/AssetAllowList.sol b/contracts/lib/AssetAllowList.sol new file mode 100644 index 00000000..4cf56c7e --- /dev/null +++ b/contracts/lib/AssetAllowList.sol @@ -0,0 +1,151 @@ +/* + Copyright 2020 Set Labs Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + SPDX-License-Identifier: Apache License, Version 2.0 +*/ + +pragma solidity 0.6.10; +import { AddressArrayUtils } from "./AddressArrayUtils.sol"; + +/** + * @title ModuleBase + * @author Set Protocol + * + * Abstract class that houses common Module-related state and functions. + * + * CHANGELOG: + * - 4/21/21: Delegated modifier logic to internal helpers to reduce contract size + * + */ +abstract contract AssetAllowList { + using AddressArrayUtils for address[]; + /* ============ Events ============ */ + + event AllowedAssetAdded( + address indexed _asset + ); + + event AllowedAssetRemoved( + address indexed _asset + ); + + event UseAssetAllowlistUpdated( + bool _status + ); + + /* ============ State Variables ============ */ + + // Boolean indicating wether to use asset allow list + bool public useAssetAllowlist; + + // Mapping keeping track of allowed assets + mapping(address => bool) public assetAllowlist; + + // List of allowed assets + address[] internal allowedAssets; + + /* ============ Modifiers ============ */ + + modifier onlyAllowedAssets(address[] memory _assets) { + require( + _areAllowedAssets(_assets), + "BaseOptimisticAuctionRebalanceExtension.onlyAllowedAssets: Invalid asset" + ); + _; + } + + /* ============ Constructor ============ */ + + /** + * Set state variables and map asset pairs to their oracles + * + * @param _allowedAssets Array of allowed assets + * @param _useAssetAllowlist Bool indicating whether to use asset allow list + */ + constructor(address[] memory _allowedAssets, bool _useAssetAllowlist) public { + _addAllowedAssets(_allowedAssets); + _updateUseAssetAllowlist(_useAssetAllowlist); + } + + /* ============ External Functions ============ */ + + function getAllowedAssets() external view returns(address[] memory) { + return allowedAssets; + } + + /* ============ Internal Functions ============ */ + + + /** + * Add new assets that can be traded to, wrapped to, or claimed + * + * @param _assets New asset to add + */ + function _addAllowedAssets(address[] memory _assets) internal { + for (uint256 i = 0; i < _assets.length; i++) { + address asset = _assets[i]; + + require(!assetAllowlist[asset], "Asset already added"); + + allowedAssets.push(asset); + + assetAllowlist[asset] = true; + + emit AllowedAssetAdded(asset); + } + } + + /** + * Remove asset(s) so that it/they can't be traded to, wrapped to, or claimed + * + * @param _assets Asset(s) to remove + */ + function _removeAllowedAssets(address[] memory _assets) internal { + for (uint256 i = 0; i < _assets.length; i++) { + address asset = _assets[i]; + + require(assetAllowlist[asset], "Asset not already added"); + + allowedAssets.removeStorage(asset); + + assetAllowlist[asset] = false; + + emit AllowedAssetRemoved(asset); + } + } + + /** + * Toggle useAssetAllowlist on and off. When false asset allowlist is ignored + * when true it is enforced. + * + * @param _useAssetAllowlist Bool indicating whether to use asset allow list + */ + function _updateUseAssetAllowlist(bool _useAssetAllowlist) internal { + useAssetAllowlist = _useAssetAllowlist; + + emit UseAssetAllowlistUpdated(_useAssetAllowlist); + } + + /// @notice Check that all assets in array are allowed to be treated + /// @dev ca be bypassed by setting the useAssetAllowlist to false (default) + function _areAllowedAssets(address[] memory _assets) internal view returns(bool) { + if (!useAssetAllowlist) { return true; } + for (uint256 i = 0; i < _assets.length; i++) { + if (!assetAllowlist[_assets[i]]) { return false; } + } + return true; + } +} + From 002f8d5288ee99849ac82af94a10fc18e31999e2 Mon Sep 17 00:00:00 2001 From: ckoopmann Date: Fri, 8 Dec 2023 10:50:37 +0800 Subject: [PATCH 03/36] Use extisting AuctionRebalanceExtension --- .../adapters/AuctionRebalanceExtension.sol | 1 + .../OptimisticAuctionRebalanceExtension.sol} | 6 +- .../BaseAuctionRebalanceExtension.sol | 212 ------------------ 3 files changed, 4 insertions(+), 215 deletions(-) rename contracts/{base-extensions/BaseOptimisticRebalanceExtension.sol => adapters/OptimisticAuctionRebalanceExtension.sol} (97%) delete mode 100644 contracts/base-extensions/BaseAuctionRebalanceExtension.sol diff --git a/contracts/adapters/AuctionRebalanceExtension.sol b/contracts/adapters/AuctionRebalanceExtension.sol index 120119a4..0c86ac8a 100644 --- a/contracts/adapters/AuctionRebalanceExtension.sol +++ b/contracts/adapters/AuctionRebalanceExtension.sol @@ -88,6 +88,7 @@ contract AuctionRebalanceExtension is BaseExtension { uint256 _positionMultiplier ) external + virtual onlyOperator { address[] memory currentComponents = setToken.getComponents(); diff --git a/contracts/base-extensions/BaseOptimisticRebalanceExtension.sol b/contracts/adapters/OptimisticAuctionRebalanceExtension.sol similarity index 97% rename from contracts/base-extensions/BaseOptimisticRebalanceExtension.sol rename to contracts/adapters/OptimisticAuctionRebalanceExtension.sol index 359e50fe..a71dc1f5 100644 --- a/contracts/base-extensions/BaseOptimisticRebalanceExtension.sol +++ b/contracts/adapters/OptimisticAuctionRebalanceExtension.sol @@ -28,7 +28,7 @@ import { IAuctionRebalanceModuleV1 } from "../interfaces/IAuctionRebalanceModule import { IBaseManager } from "../interfaces/IBaseManager.sol"; import { ISetToken } from "../interfaces/ISetToken.sol"; import { IDelegatedManager } from "../interfaces/IDelegatedManager.sol"; -import {BaseAuctionRebalanceExtension} from "./BaseAuctionRebalanceExtension.sol"; +import {AuctionRebalanceExtension} from "./AuctionRebalanceExtension.sol"; import {AncillaryData } from "../lib/AncillaryData.sol"; import { AssetAllowList } from "../lib/AssetAllowList.sol"; import {OptimisticOracleV3Interface} from "../interfaces/OptimisticOracleV3Interface.sol"; @@ -40,7 +40,7 @@ import {OptimisticOracleV3Interface} from "../interfaces/OptimisticOracleV3Inter * @dev The contract extends `BaseAuctionRebalanceExtension` by adding an optimistic oracle mechanism for validating rules on the proposing and executing of rebalances. * It allows setting product-specific parameters for optimistic rebalancing and includes callback functions for resolved or disputed assertions. */ -contract BaseOptimisticAuctionRebalanceExtension is BaseAuctionRebalanceExtension, AssetAllowList { +contract OptimisticAuctionRebalanceExtension is AuctionRebalanceExtension, AssetAllowList { using AddressArrayUtils for address[]; using SafeERC20 for IERC20; @@ -121,7 +121,7 @@ contract BaseOptimisticAuctionRebalanceExtension is BaseAuctionRebalanceExtensi * * @param _auctionParams AuctionExtensionParams struct containing the baseManager and auctionModule addresses. */ - constructor(AuctionExtensionParams memory _auctionParams) public BaseAuctionRebalanceExtension(_auctionParams.baseManager, _auctionParams.auctionModule) AssetAllowList(_auctionParams.allowedAssets, _auctionParams.useAssetAllowlist) { + constructor(AuctionExtensionParams memory _auctionParams) public AuctionRebalanceExtension(_auctionParams.baseManager, _auctionParams.auctionModule) AssetAllowList(_auctionParams.allowedAssets, _auctionParams.useAssetAllowlist) { } diff --git a/contracts/base-extensions/BaseAuctionRebalanceExtension.sol b/contracts/base-extensions/BaseAuctionRebalanceExtension.sol deleted file mode 100644 index b054cd6f..00000000 --- a/contracts/base-extensions/BaseAuctionRebalanceExtension.sol +++ /dev/null @@ -1,212 +0,0 @@ -/* - Copyright 2023 Index Coop - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - SPDX-License-Identifier: Apache License, Version 2.0 -*/ - -pragma solidity 0.6.10; -pragma experimental "ABIEncoderV2"; - -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol"; - -import { AddressArrayUtils } from "../lib/AddressArrayUtils.sol"; -import { BaseExtension } from "../lib/BaseExtension.sol"; -import { IAuctionRebalanceModuleV1 } from "../interfaces/IAuctionRebalanceModuleV1.sol"; -import { IManagerCore } from "../interfaces/IManagerCore.sol"; -import { ISetToken } from "../interfaces/ISetToken.sol"; -import { IBaseManager } from "../interfaces/IBaseManager.sol"; - - -/** - * @title BaseAuctionRebalanceExtension - * @author Index Coop - * - * @dev Extension contract for interacting with the AuctionRebalanceModuleV1. This contract acts as a pass-through and functions - * are only callable by operator. - */ -contract BaseAuctionRebalanceExtension is BaseExtension { - using AddressArrayUtils for address[]; - using SafeMath for uint256; - - /* ============ Events ============ */ - - event AuctionRebalanceExtensionInitialized( - address indexed setToken, - address indexed _baseManager - ); - - - /* ============ Structs ============ */ - - struct AuctionExecutionParams { - uint256 targetUnit; // Target quantity of the component in Set, in precise units (10 ** 18). - string priceAdapterName; // Identifier for the price adapter to be used. - bytes priceAdapterConfigData; // Encoded data for configuring the chosen price adapter. - } - - /* ============ State Variables ============ */ - - IAuctionRebalanceModuleV1 public immutable auctionModule; // AuctionRebalanceModuleV1 - ISetToken public immutable setToken; // SetToken instance associated with BaseManager - - - /* ============ Constructor ============ */ - /** - * @dev Instantiate with ManagerCore address and WrapModuleV2 address. - * - * @param _manager Address of BaseManager contract - * @param _auctionModule Address of AuctionRebalanceModuleV1 contract - */ - constructor(IBaseManager _manager, IAuctionRebalanceModuleV1 _auctionModule) public BaseExtension(_manager) { - auctionModule = _auctionModule; - setToken = _manager.setToken(); - } - - /* ============ External Functions ============ */ - - - /** - * @dev ONLY OPERATOR: Initializes AuctionRebalanceModuleV1 on the SetToken associated with the BaseManager. - */ - function initialize() external onlyOperator { - bytes memory callData = abi.encodeWithSelector(IAuctionRebalanceModuleV1.initialize.selector, setToken); - invokeManager(address(auctionModule), callData); - } - - /** - * @dev OPERATOR ONLY: Checks that the old components array matches the current components array and then invokes the - * AuctionRebalanceModuleV1 startRebalance function. - * - * Refer to AuctionRebalanceModuleV1 for function specific restrictions. - * - * @param _quoteAsset ERC20 token used as the quote asset in auctions. - * @param _oldComponents Addresses of existing components in the SetToken. - * @param _newComponents Addresses of new components to be added. - * @param _newComponentsAuctionParams AuctionExecutionParams for new components, indexed corresponding to _newComponents. - * @param _oldComponentsAuctionParams AuctionExecutionParams for existing components, indexed corresponding to - * the current component positions. Set to 0 for components being removed. - * @param _shouldLockSetToken Indicates if the rebalance should lock the SetToken. - * @param _rebalanceDuration Duration of the rebalance in seconds. - * @param _positionMultiplier Position multiplier at the time target units were calculated. - */ - function startRebalance( - IERC20 _quoteAsset, - address[] memory _oldComponents, - address[] memory _newComponents, - AuctionExecutionParams[] memory _newComponentsAuctionParams, - AuctionExecutionParams[] memory _oldComponentsAuctionParams, - bool _shouldLockSetToken, - uint256 _rebalanceDuration, - uint256 _positionMultiplier - ) - external - virtual - onlyOperator - { - address[] memory currentComponents = setToken.getComponents(); - - require(currentComponents.length == _oldComponents.length, "Mismatch: old and current components length"); - - for (uint256 i = 0; i < _oldComponents.length; i++) { - require(currentComponents[i] == _oldComponents[i], "Mismatch: old and current components"); - } - - bytes memory callData = abi.encodeWithSelector( - IAuctionRebalanceModuleV1.startRebalance.selector, - setToken, - _quoteAsset, - _newComponents, - _newComponentsAuctionParams, - _oldComponentsAuctionParams, - _shouldLockSetToken, - _rebalanceDuration, - _positionMultiplier - ); - - invokeManager(address(auctionModule), callData); - } - - /** - * @dev OPERATOR ONLY: Unlocks SetToken via AuctionRebalanceModuleV1. - * Refer to AuctionRebalanceModuleV1 for function specific restrictions. - * - */ - function unlock() external onlyOperator { - bytes memory callData = abi.encodeWithSelector( - IAuctionRebalanceModuleV1.unlock.selector, - setToken - ); - - invokeManager(address(auctionModule), callData); - } - - /** - * @dev OPERATOR ONLY: Sets the target raise percentage for all components on AuctionRebalanceModuleV1. - * Refer to AuctionRebalanceModuleV1 for function specific restrictions. - * - * @param _raiseTargetPercentage Amount to raise all component's unit targets by (in precise units) - */ - function setRaiseTargetPercentage(uint256 _raiseTargetPercentage) external onlyOperator { - bytes memory callData = abi.encodeWithSelector( - IAuctionRebalanceModuleV1.setRaiseTargetPercentage.selector, - setToken, - _raiseTargetPercentage - ); - - invokeManager(address(auctionModule), callData); - } - - /** - * @dev OPERATOR ONLY: Updates the bidding permission status for a list of addresses on AuctionRebalanceModuleV1. - * Refer to AuctionRebalanceModuleV1 for function specific restrictions. - * - * @param _bidders An array of addresses whose bidding permission status is to be toggled. - * @param _statuses An array of booleans indicating the new bidding permission status for each corresponding address in `_bidders`. - */ - function setBidderStatus( - ISetToken setToken, - address[] memory _bidders, - bool[] memory _statuses - ) - external - onlyOperator - { - bytes memory callData = abi.encodeWithSelector( - IAuctionRebalanceModuleV1.setBidderStatus.selector, - setToken, - _bidders, - _statuses - ); - - invokeManager(address(auctionModule), callData); - } - - /** - * @dev OPERATOR ONLY: Sets whether anyone can bid on the AuctionRebalanceModuleV1. - * Refer to AuctionRebalanceModuleV1 for function specific restrictions. - * - * @param _status A boolean indicating if anyone can bid. - */ - function setAnyoneBid(bool _status) external onlyOperator { - bytes memory callData = abi.encodeWithSelector( - IAuctionRebalanceModuleV1.setAnyoneBid.selector, - setToken, - _status - ); - - invokeManager(address(auctionModule), callData); - } -} From 5314e891403985541df62a5e73c65752eb8335d1 Mon Sep 17 00:00:00 2001 From: ckoopmann Date: Fri, 8 Dec 2023 10:52:14 +0800 Subject: [PATCH 04/36] Only allowed assets as new components --- contracts/adapters/OptimisticAuctionRebalanceExtension.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/contracts/adapters/OptimisticAuctionRebalanceExtension.sol b/contracts/adapters/OptimisticAuctionRebalanceExtension.sol index a71dc1f5..bcdd324a 100644 --- a/contracts/adapters/OptimisticAuctionRebalanceExtension.sol +++ b/contracts/adapters/OptimisticAuctionRebalanceExtension.sol @@ -198,6 +198,7 @@ contract OptimisticAuctionRebalanceExtension is AuctionRebalanceExtension, Asse uint256 _positionMultiplier ) external + onlyAllowedAssets(_newComponents) { bytes32 proposalHash = keccak256(abi.encode( setToken, From 03d7edfebce6045f3cb025fcbc0ff5f67c3cbf79 Mon Sep 17 00:00:00 2001 From: ckoopmann Date: Fri, 8 Dec 2023 11:50:39 +0800 Subject: [PATCH 05/36] Port tests from global version --- .../OptimisticAuctionRebalanceExtension.sol | 3 +- ...ptimisticAuctionRebalanceExtension.spec.ts | 757 ++++++++++++++++++ utils/contracts/index.ts | 3 +- utils/deploys/deployExtensions.ts | 19 +- 4 files changed, 777 insertions(+), 5 deletions(-) create mode 100644 test/adapters/optimisticAuctionRebalanceExtension.spec.ts diff --git a/contracts/adapters/OptimisticAuctionRebalanceExtension.sol b/contracts/adapters/OptimisticAuctionRebalanceExtension.sol index bcdd324a..3cb7adf7 100644 --- a/contracts/adapters/OptimisticAuctionRebalanceExtension.sol +++ b/contracts/adapters/OptimisticAuctionRebalanceExtension.sol @@ -27,7 +27,6 @@ import { AddressArrayUtils } from "../lib/AddressArrayUtils.sol"; import { IAuctionRebalanceModuleV1 } from "../interfaces/IAuctionRebalanceModuleV1.sol"; import { IBaseManager } from "../interfaces/IBaseManager.sol"; import { ISetToken } from "../interfaces/ISetToken.sol"; -import { IDelegatedManager } from "../interfaces/IDelegatedManager.sol"; import {AuctionRebalanceExtension} from "./AuctionRebalanceExtension.sol"; import {AncillaryData } from "../lib/AncillaryData.sol"; import { AssetAllowList } from "../lib/AssetAllowList.sol"; @@ -48,7 +47,7 @@ contract OptimisticAuctionRebalanceExtension is AuctionRebalanceExtension, Asse event ProductSettingsUpdated( IERC20 indexed setToken, - address indexed delegatedManager, + address indexed manager, OptimisticRebalanceParams optimisticParams, bytes32 indexed rulesHash ); diff --git a/test/adapters/optimisticAuctionRebalanceExtension.spec.ts b/test/adapters/optimisticAuctionRebalanceExtension.spec.ts new file mode 100644 index 00000000..ca46d08f --- /dev/null +++ b/test/adapters/optimisticAuctionRebalanceExtension.spec.ts @@ -0,0 +1,757 @@ +import "module-alias/register"; + +import { Address, Account } from "@utils/types"; +import { ADDRESS_ZERO, ZERO } from "@utils/constants"; +import { + BaseManager, + ConstantPriceAdapter, +OptimisticAuctionRebalanceExtension, + OptimisticOracleV3Mock, + StandardTokenMock, +} from "@utils/contracts/index"; +import { SetToken } from "@utils/contracts/setV2"; +import DeployHelper from "@utils/deploys"; +import { + addSnapshotBeforeRestoreAfterEach, + ether, + getAccounts, + getSetFixture, + getWaffleExpect, + bitcoin, + usdc, + getTransactionTimestamp, + getRandomAccount, +} from "@utils/index"; +import { SetFixture } from "@utils/fixtures"; +import { BigNumber, ContractTransaction, utils } from "ethers"; +import base58 from "bs58"; + +const expect = getWaffleExpect(); + +function bufferToHex(buffer: Uint8Array) { + let hexStr = ""; + + for (let i = 0; i < buffer.length; i++) { + const hex = (buffer[i] & 0xff).toString(16); + hexStr += hex.length === 1 ? "0" + hex : hex; + } + + return hexStr; +} + +// Base58 decoding function (make sure you have a proper Base58 decoding function) +function base58ToHexString(base58String: string) { + const bytes = base58.decode(base58String); // Decode base58 to a buffer + const hexString = bufferToHex(bytes.slice(2)); // Convert buffer to hex, excluding the first 2 bytes + return "0x" + hexString; +} + +describe.only("OptimisticAuctionRebalanceExtension", () => { + let owner: Account; + let methodologist: Account; + let operator: Account; + + let setV2Setup: SetFixture; + + let deployer: DeployHelper; + let setToken: SetToken; + let baseManager: BaseManager; + + let auctionRebalanceExtension: OptimisticAuctionRebalanceExtension; + + let priceAdapter: ConstantPriceAdapter; + + let optimisticOracleV3Mock: OptimisticOracleV3Mock; + + let optimisticOracleV3MockUpgraded: OptimisticOracleV3Mock; + + let collateralAsset: StandardTokenMock; + + let useAssetAllowlist: boolean; + let allowedAssets: Address[]; + + before(async () => { + [owner, methodologist, operator, factory] = await getAccounts(); + + deployer = new DeployHelper(owner.wallet); + + setV2Setup = getSetFixture(owner.address); + await setV2Setup.initialize(); + + priceAdapter = await deployer.setV2.deployConstantPriceAdapter(); + optimisticOracleV3Mock = await deployer.mocks.deployOptimisticOracleV3Mock(); + optimisticOracleV3MockUpgraded = await deployer.mocks.deployOptimisticOracleV3Mock(); + collateralAsset = await deployer.mocks.deployStandardTokenMock(owner.address, 18); + + await setV2Setup.integrationRegistry.addIntegration( + setV2Setup.auctionModule.address, + "ConstantPriceAdapter", + priceAdapter.address, + ); + + useAssetAllowlist = false; + allowedAssets = []; + + setToken = await setV2Setup.createSetToken( + [setV2Setup.dai.address, setV2Setup.wbtc.address, setV2Setup.weth.address], + [ether(100), bitcoin(0.01), ether(0.1)], + [setV2Setup.auctionModule.address, setV2Setup.issuanceModule.address], + ); + + await setV2Setup.issuanceModule.initialize(setToken.address, ADDRESS_ZERO); + + baseManager = await deployer.manager.deployBaseManager( + setToken.address, + operator.address, + methodologist.address, + ); + baseManager = baseManager.connect(operator.wallet); + await setToken.setManager(baseManager.address); + + auctionRebalanceExtension = await deployer.extensions.deployOptimisticAuctionRebalanceExtension( + baseManager.address, + setV2Setup.auctionModule.address, + useAssetAllowlist, + allowedAssets, + ); + auctionRebalanceExtension = auctionRebalanceExtension.connect(operator.wallet); + }); + + addSnapshotBeforeRestoreAfterEach(); + + describe("#constructor", () => { + let subjectBaseManager: Address; + let subjectAuctionRebalanceModule: Address; + let subjectUseAssetAllowlist: boolean; + let subjectAllowedAssets: Address[]; + + beforeEach(async () => { + subjectBaseManager = baseManager.address; + subjectAuctionRebalanceModule = setV2Setup.auctionModule.address; + subjectUseAssetAllowlist = false; + subjectAllowedAssets = []; + }); + + async function subject(): Promise { + let extension = await deployer.extensions.deployOptimisticAuctionRebalanceExtension( + subjectBaseManager, + subjectAuctionRebalanceModule, + subjectUseAssetAllowlist, + allowedAssets, + ); + extension = extension.connect(operator.wallet); + await baseManager.addAdapter(extension.address); + return extension; + } + + it("should set the correct manager core", async () => { + const auctionExtension = await subject(); + + const actualBaseManager = await auctionExtension.manager(); + expect(actualBaseManager).to.eq(subjectBaseManager); + }); + + it("should set the correct auction rebalance module address", async () => { + const auctionExtension = await subject(); + + const actualAuctionRebalanceModule = await auctionExtension.auctionModule(); + expect(actualAuctionRebalanceModule).to.eq(subjectAuctionRebalanceModule); + }); + it("should be able to initialize extension and module at the same time", async () => { + const auctionExtension = await subject(); + await expect( + auctionExtension + .initialize(), + ).to.not.be.reverted; + }); + + it("should revert if module is initialized and extension is not", async () => { + const extension = await deployer.extensions.deployOptimisticAuctionRebalanceExtension( + subjectBaseManager, + subjectAuctionRebalanceModule, + subjectUseAssetAllowlist, + subjectAllowedAssets, + ); + await expect( + extension.connect(operator.wallet).initialize(), + ).to.be.revertedWith("Must be adapter"); + }); + + it("should revert if module is initialized without being added", async () => { + const extension = await deployer.extensions.deployOptimisticAuctionRebalanceExtension( + subjectBaseManager, + subjectAuctionRebalanceModule, + subjectUseAssetAllowlist, + subjectAllowedAssets, + ); + await expect( + extension.connect(operator.wallet).initialize(), + ).to.be.revertedWith("Must be adapter"); + }); + + it("should revert if extension is initialized without being added", async () => { + const extension = await deployer.extensions.deployOptimisticAuctionRebalanceExtension( + subjectBaseManager, + subjectAuctionRebalanceModule, + subjectUseAssetAllowlist, + allowedAssets, + ); + await expect( + extension.connect(operator.wallet).initialize(), + ).to.be.revertedWith("Must be adapter"); + }); + }); + + context("when auction rebalance extension is added as adapter and needs to be initialized", () => { + let subjectCaller: Account; + + beforeEach(async () => { + subjectCaller = operator; + await baseManager.addAdapter(auctionRebalanceExtension.address); + }); + + describe("#initialize", () => { + async function subject() { + return await auctionRebalanceExtension + .connect(subjectCaller.wallet) + .initialize(); + } + + it("should initialize AuctionRebalanceModule", async () => { + await subject(); + const isInitialized = await setToken.isInitializedModule(setV2Setup.auctionModule.address); + expect(isInitialized).to.be.true; + }); + + describe("when the initializer is not the owner", () => { + beforeEach(async () => { + subjectCaller = await getRandomAccount(); + }); + + it("should revert", async () => { + await expect(subject()).to.be.revertedWith("Must be operator"); + }); + }); + }); + + context("when auction rebalance extension is deployed and initialized.", () => { + beforeEach(async () => { + await auctionRebalanceExtension + .connect(operator.wallet) + .initialize(); + }); + + context("when the product settings have been set", () => { + beforeEach(async () => { + await auctionRebalanceExtension.connect(operator.wallet).setProductSettings( + { + collateral: collateralAsset.address, + liveness: BigNumber.from(0), + bondAmount: BigNumber.from(0), + identifier: utils.formatBytes32String(""), + optimisticOracleV3: optimisticOracleV3Mock.address, + }, + utils.arrayify(base58ToHexString("Qmc5gCcjYypU7y28oCALwfSvxCBskLuPKWpK4qpterKC7z")), + ); + }); + + context("when a rebalance has been proposed", () => { + let subjectQuoteAsset: Address; + let subjectOldComponents: Address[]; + let subjectNewComponents: Address[]; + let subjectNewComponentsAuctionParams: any[]; + let subjectOldComponentsAuctionParams: any[]; + let subjectShouldLockSetToken: boolean; + let subjectRebalanceDuration: BigNumber; + let subjectPositionMultiplier: BigNumber; + let subjectCaller: Account; + beforeEach(async () => { + subjectQuoteAsset = setV2Setup.weth.address; + + subjectOldComponents = [ + setV2Setup.dai.address, + setV2Setup.wbtc.address, + setV2Setup.weth.address, + ]; + subjectNewComponents = [setV2Setup.usdc.address]; + + subjectNewComponentsAuctionParams = [ + { + targetUnit: usdc(100), + priceAdapterName: "ConstantPriceAdapter", + priceAdapterConfigData: await priceAdapter.getEncodedData(ether(0.005)), + }, + ]; + + subjectOldComponentsAuctionParams = [ + { + targetUnit: ether(50), + priceAdapterName: "ConstantPriceAdapter", + priceAdapterConfigData: await priceAdapter.getEncodedData(ether(0.005)), + }, + { + targetUnit: bitcoin(0.01), + priceAdapterName: "ConstantPriceAdapter", + priceAdapterConfigData: await priceAdapter.getEncodedData(ether(0.005)), + }, + { + targetUnit: ether(0.1), + priceAdapterName: "ConstantPriceAdapter", + priceAdapterConfigData: await priceAdapter.getEncodedData(ether(0.005)), + }, + ]; + + subjectShouldLockSetToken = true; + subjectRebalanceDuration = BigNumber.from(86400); + subjectPositionMultiplier = ether(0.999); + subjectCaller = operator; + }); + describe("#startRebalance", () => { + async function subject(): Promise { + await auctionRebalanceExtension + .connect(subjectCaller.wallet) + .proposeRebalance( + subjectQuoteAsset, + subjectOldComponents, + subjectNewComponents, + subjectNewComponentsAuctionParams, + subjectOldComponentsAuctionParams, + subjectShouldLockSetToken, + subjectRebalanceDuration, + subjectPositionMultiplier, + ); + return auctionRebalanceExtension + .connect(subjectCaller.wallet) + .startRebalance( + subjectQuoteAsset, + subjectOldComponents, + subjectNewComponents, + subjectNewComponentsAuctionParams, + subjectOldComponentsAuctionParams, + subjectShouldLockSetToken, + subjectRebalanceDuration, + subjectPositionMultiplier, + ); + } + + it("should set the auction execution params correctly", async () => { + await subject(); + expect(1).to.eq(1); + + const aggregateComponents = [...subjectOldComponents, ...subjectNewComponents]; + const aggregateAuctionParams = [ + ...subjectOldComponentsAuctionParams, + ...subjectNewComponentsAuctionParams, + ]; + + for (let i = 0; i < aggregateAuctionParams.length; i++) { + const executionInfo = await setV2Setup.auctionModule.executionInfo( + setToken.address, + aggregateComponents[i], + ); + expect(executionInfo.targetUnit).to.eq(aggregateAuctionParams[i].targetUnit); + expect(executionInfo.priceAdapterName).to.eq( + aggregateAuctionParams[i].priceAdapterName, + ); + expect(executionInfo.priceAdapterConfigData).to.eq( + aggregateAuctionParams[i].priceAdapterConfigData, + ); + } + }); + + it("should set the rebalance info correctly", async () => { + const txnTimestamp = await getTransactionTimestamp(subject()); + + const rebalanceInfo = await setV2Setup.auctionModule.rebalanceInfo(setToken.address); + + expect(rebalanceInfo.quoteAsset).to.eq(subjectQuoteAsset); + expect(rebalanceInfo.rebalanceStartTime).to.eq(txnTimestamp); + expect(rebalanceInfo.rebalanceDuration).to.eq(subjectRebalanceDuration); + expect(rebalanceInfo.positionMultiplier).to.eq(subjectPositionMultiplier); + expect(rebalanceInfo.raiseTargetPercentage).to.eq(ZERO); + + const rebalanceComponents = await setV2Setup.auctionModule.getRebalanceComponents( + setToken.address, + ); + const aggregateComponents = [...subjectOldComponents, ...subjectNewComponents]; + + for (let i = 0; i < rebalanceComponents.length; i++) { + expect(rebalanceComponents[i]).to.eq(aggregateComponents[i]); + } + }); + + describe("when there are no new components", () => { + beforeEach(async () => { + subjectNewComponents = []; + subjectNewComponentsAuctionParams = []; + }); + + it("should set the auction execution params correctly", async () => { + await subject(); + + for (let i = 0; i < subjectOldComponents.length; i++) { + const executionInfo = await setV2Setup.auctionModule.executionInfo( + setToken.address, + subjectOldComponents[i], + ); + expect(executionInfo.targetUnit).to.eq( + subjectOldComponentsAuctionParams[i].targetUnit, + ); + expect(executionInfo.priceAdapterName).to.eq( + subjectOldComponentsAuctionParams[i].priceAdapterName, + ); + expect(executionInfo.priceAdapterConfigData).to.eq( + subjectOldComponentsAuctionParams[i].priceAdapterConfigData, + ); + } + }); + + it("should set the rebalance info correctly", async () => { + const txnTimestamp = await getTransactionTimestamp(subject()); + + const rebalanceInfo = await setV2Setup.auctionModule.rebalanceInfo( + setToken.address, + ); + + expect(rebalanceInfo.quoteAsset).to.eq(subjectQuoteAsset); + expect(rebalanceInfo.rebalanceStartTime).to.eq(txnTimestamp); + expect(rebalanceInfo.rebalanceDuration).to.eq(subjectRebalanceDuration); + expect(rebalanceInfo.positionMultiplier).to.eq(subjectPositionMultiplier); + expect(rebalanceInfo.raiseTargetPercentage).to.eq(ZERO); + + const rebalanceComponents = await setV2Setup.auctionModule.getRebalanceComponents( + setToken.address, + ); + for (let i = 0; i < rebalanceComponents.length; i++) { + expect(rebalanceComponents[i]).to.eq(subjectOldComponents[i]); + } + }); + }); + + describe("when old components are passed in different order", () => { + beforeEach(async () => { + subjectOldComponents = [ + setV2Setup.dai.address, + setV2Setup.weth.address, + setV2Setup.wbtc.address, + ]; + }); + + it("should revert", async () => { + await expect(subject()).to.be.revertedWith("Mismatch: old and current components"); + }); + }); + + describe("when old components array is shorter than current components array", () => { + beforeEach(async () => { + subjectOldComponents = [setV2Setup.dai.address, setV2Setup.wbtc.address]; + subjectOldComponentsAuctionParams = [ + { + targetUnit: ether(50), + priceAdapterName: "ConstantPriceAdapter", + priceAdapterConfigData: await priceAdapter.getEncodedData(ether(0.005)), + }, + { + targetUnit: bitcoin(0.01), + priceAdapterName: "ConstantPriceAdapter", + priceAdapterConfigData: await priceAdapter.getEncodedData(ether(0.005)), + }, + ]; + }); + + it("should revert", async () => { + await expect(subject()).to.be.revertedWith( + "Mismatch: old and current components length", + ); + }); + }); + + describe("when old components array is longer than current components array", () => { + beforeEach(async () => { + const price = await priceAdapter.getEncodedData(ether(1)); + subjectOldComponents = [ + setV2Setup.dai.address, + setV2Setup.wbtc.address, + setV2Setup.weth.address, + setV2Setup.usdc.address, + ]; + subjectOldComponentsAuctionParams = [ + { + targetUnit: ether(50), + priceAdapterName: "ConstantPriceAdapter", + priceAdapterConfigData: price, + }, + { + targetUnit: bitcoin(0.01), + priceAdapterName: "ConstantPriceAdapter", + priceAdapterConfigData: price, + }, + { + targetUnit: ether(0.1), + priceAdapterName: "ConstantPriceAdapter", + priceAdapterConfigData: price, + }, + { + targetUnit: usdc(100), + priceAdapterName: "ConstantPriceAdapter", + priceAdapterConfigData: price, + }, + ]; + }); + + it("should revert", async () => { + await expect(subject()).to.be.revertedWith( + "Mismatch: old and current components length", + ); + }); + }); + + describe("when not all old components have an entry", () => { + beforeEach(async () => { + subjectOldComponents = [ + setV2Setup.dai.address, + setV2Setup.wbtc.address, + setV2Setup.usdc.address, + ]; + }); + + it("should revert", async () => { + await expect(subject()).to.be.revertedWith("Mismatch: old and current components"); + }); + }); + + describe("when the caller is not the operator", () => { + beforeEach(async () => { + subjectCaller = await getRandomAccount(); + }); + + it("should not revert", async () => { + await expect(subject()).not.to.be.reverted; + }); + }); + }); + describe("assertionDisputedCallback", () => { + it("should delete the proposal on a disputed callback", async () => { + await auctionRebalanceExtension + .connect(subjectCaller.wallet) + .proposeRebalance( + subjectQuoteAsset, + subjectOldComponents, + subjectNewComponents, + subjectNewComponentsAuctionParams, + subjectOldComponentsAuctionParams, + subjectShouldLockSetToken, + subjectRebalanceDuration, + subjectPositionMultiplier, + ); + const proposal = await auctionRebalanceExtension + .connect(subjectCaller.wallet) + .proposedProduct(utils.formatBytes32String("win")); + expect(proposal.product).to.eq(setToken.address); + + await expect( + optimisticOracleV3Mock + .connect(subjectCaller.wallet) + .mockAssertionDisputedCallback( + auctionRebalanceExtension.address, + utils.formatBytes32String("win"), + ), + ).to.not.be.reverted; + + const proposalAfter = await auctionRebalanceExtension + .connect(subjectCaller.wallet) + .proposedProduct(utils.formatBytes32String("win")); + expect(proposalAfter.product).to.eq(ADDRESS_ZERO); + }); + it("should delete the proposal on a disputed callback from currently set oracle", async () => { + await auctionRebalanceExtension + .connect(subjectCaller.wallet) + .proposeRebalance( + subjectQuoteAsset, + subjectOldComponents, + subjectNewComponents, + subjectNewComponentsAuctionParams, + subjectOldComponentsAuctionParams, + subjectShouldLockSetToken, + subjectRebalanceDuration, + subjectPositionMultiplier, + ); + await auctionRebalanceExtension.connect(operator.wallet).setProductSettings( + { + collateral: collateralAsset.address, + liveness: BigNumber.from(0), + bondAmount: BigNumber.from(0), + identifier: utils.formatBytes32String(""), + optimisticOracleV3: optimisticOracleV3MockUpgraded.address, + }, + utils.arrayify(base58ToHexString("Qmc5gCcjYypU7y28oCALwfSvxCBskLuPKWpK4qpterKC7z")), + ); + const proposal = await auctionRebalanceExtension + .connect(subjectCaller.wallet) + .proposedProduct(utils.formatBytes32String("win")); + expect(proposal.product).to.eq(setToken.address); + await expect( + optimisticOracleV3Mock + .connect(subjectCaller.wallet) + .mockAssertionDisputedCallback( + auctionRebalanceExtension.address, + utils.formatBytes32String("win"), + ), + ).to.not.be.reverted; + const proposalAfter = await auctionRebalanceExtension + .connect(subjectCaller.wallet) + .proposedProduct(utils.formatBytes32String("win")); + expect(proposalAfter.product).to.eq(ADDRESS_ZERO); + }); + }); + describe("assertionResolvedCallback", () => { + it("should not revert on a resolved callback", async () => { + await auctionRebalanceExtension + .connect(subjectCaller.wallet) + .proposeRebalance( + subjectQuoteAsset, + subjectOldComponents, + subjectNewComponents, + subjectNewComponentsAuctionParams, + subjectOldComponentsAuctionParams, + subjectShouldLockSetToken, + subjectRebalanceDuration, + subjectPositionMultiplier, + ); + await expect( + optimisticOracleV3Mock + .connect(subjectCaller.wallet) + .mockAssertionResolvedCallback( + auctionRebalanceExtension.address, + utils.formatBytes32String("win"), + true, + ), + ).to.not.be.reverted; + }); + }); + }); + describe("#setRaiseTargetPercentage", () => { + let subjectRaiseTargetPercentage: BigNumber; + let subjectCaller: Account; + + beforeEach(async () => { + subjectRaiseTargetPercentage = ether(0.001); + subjectCaller = operator; + }); + + async function subject(): Promise { + return await auctionRebalanceExtension + .connect(subjectCaller.wallet) + .setRaiseTargetPercentage( subjectRaiseTargetPercentage); + } + + it("should correctly set the raiseTargetPercentage", async () => { + await subject(); + + const actualRaiseTargetPercentage = ( + await setV2Setup.auctionModule.rebalanceInfo(setToken.address) + ).raiseTargetPercentage; + + expect(actualRaiseTargetPercentage).to.eq(subjectRaiseTargetPercentage); + }); + + describe("when the caller is not the operator", async () => { + beforeEach(async () => { + subjectCaller = await getRandomAccount(); + }); + + it("should revert", async () => { + await expect(subject()).to.be.revertedWith("Must be operator"); + }); + }); + }); + + describe("#setBidderStatus", () => { + let subjectBidders: Address[]; + let subjectStatuses: boolean[]; + let subjectCaller: Account; + + beforeEach(async () => { + subjectBidders = [methodologist.address]; + subjectStatuses = [true]; + subjectCaller = operator; + }); + + async function subject(): Promise { + return await auctionRebalanceExtension + .connect(subjectCaller.wallet) + .setBidderStatus( subjectBidders, subjectStatuses); + } + + it("should correctly set the bidder status", async () => { + await subject(); + + const isCaller = await setV2Setup.auctionModule.isAllowedBidder( + setToken.address, + subjectBidders[0], + ); + + expect(isCaller).to.be.true; + }); + + describe("when the caller is not the operator", () => { + beforeEach(async () => { + subjectCaller = await getRandomAccount(); + }); + + it("should revert", async () => { + await expect(subject()).to.be.revertedWith("Must be operator"); + }); + }); + }); + + describe("#setAnyoneBid", () => { + let subjectStatus: boolean; + let subjectCaller: Account; + + beforeEach(async () => { + subjectStatus = true; + subjectCaller = operator; + }); + + async function subject(): Promise { + return await auctionRebalanceExtension + .connect(subjectCaller.wallet) + .setAnyoneBid(subjectStatus); + } + + it("should correctly set anyone bid", async () => { + await subject(); + + const anyoneBid = await setV2Setup.auctionModule.permissionInfo(setToken.address); + + expect(anyoneBid).to.be.true; + }); + + describe("when the caller is not the operator", () => { + beforeEach(async () => { + subjectCaller = await getRandomAccount(); + }); + + it("should revert", async () => { + await expect(subject()).to.be.revertedWith("Must be operator"); + }); + }); + }); + + describe("#removeAdapter", () => { + async function subject() { + return await baseManager + .connect(operator.wallet) + .removeAdapter(auctionRebalanceExtension.address); + } + it("should remove the extension", async () => { + expect(await baseManager.isAdapter(auctionRebalanceExtension.address)).to.be.true; + await subject(); + expect(await baseManager.isAdapter(auctionRebalanceExtension.address)).to.be.false; + }); + }); + }); + }); + }); +}); diff --git a/utils/contracts/index.ts b/utils/contracts/index.ts index bbfb9235..1f4decbf 100644 --- a/utils/contracts/index.ts +++ b/utils/contracts/index.ts @@ -65,4 +65,5 @@ export { GlobalWrapExtension } from "../../typechain/GlobalWrapExtension"; export { GlobalClaimExtension } from "../../typechain/GlobalClaimExtension"; export { GlobalAuctionRebalanceExtension } from "../../typechain/GlobalAuctionRebalanceExtension"; export { GlobalOptimisticAuctionRebalanceExtension } from "../../typechain/GlobalOptimisticAuctionRebalanceExtension"; -export { OptimisticOracleV3Mock } from "../../typechain/OptimisticOracleV3Mock"; \ No newline at end of file +export { OptimisticAuctionRebalanceExtension } from "../../typechain/OptimisticAuctionRebalanceExtension"; +export { OptimisticOracleV3Mock } from "../../typechain/OptimisticOracleV3Mock"; diff --git a/utils/deploys/deployExtensions.ts b/utils/deploys/deployExtensions.ts index 04b694a1..3ed7a336 100644 --- a/utils/deploys/deployExtensions.ts +++ b/utils/deploys/deployExtensions.ts @@ -24,6 +24,7 @@ import { FeeSplitExtension, GIMExtension, GovernanceExtension, + OptimisticAuctionRebalanceExtension, StreamingFeeSplitExtension, WrapExtension, } from "../contracts/index"; @@ -52,6 +53,7 @@ import { FlexibleLeverageStrategyExtension__factory } from "../../typechain/fact import { GIMExtension__factory } from "../../typechain/factories/GIMExtension__factory"; import { GovernanceExtension__factory } from "../../typechain/factories/GovernanceExtension__factory"; import { FixedRebalanceExtension__factory } from "../../typechain/factories/FixedRebalanceExtension__factory"; +import { OptimisticAuctionRebalanceExtension__factory } from "../../typechain/factories/OptimisticAuctionRebalanceExtension__factory"; import { StakeWiseReinvestmentExtension__factory } from "../../typechain/factories/StakeWiseReinvestmentExtension__factory"; import { StreamingFeeSplitExtension__factory } from "../../typechain/factories/StreamingFeeSplitExtension__factory"; import { WrapExtension__factory } from "../../typechain/factories/WrapExtension__factory"; @@ -259,11 +261,10 @@ export default class DeployExtensions { basicIssuanceModuleAddress, aaveLeveragedModuleAddress, aaveAddressProviderAddress, - BalancerV2VaultAddress + BalancerV2VaultAddress, ); } - public async deployExchangeIssuanceLeveragedForCompound( wethAddress: Address, quickRouterAddress: Address, @@ -518,4 +519,18 @@ export default class DeployExtensions { minPositions, ); } + + public async deployOptimisticAuctionRebalanceExtension( + baseManager: Address, + auctionModule: Address, + useAssetAllowlist: boolean, + allowedAssets: Address[], + ): Promise { + return await new OptimisticAuctionRebalanceExtension__factory(this._deployerSigner).deploy({ + baseManager, + auctionModule, + useAssetAllowlist, + allowedAssets, + }); + } } From c2cba1430445d393550a3aedbad496f1569b6949 Mon Sep 17 00:00:00 2001 From: ckoopmann Date: Fri, 8 Dec 2023 12:01:55 +0800 Subject: [PATCH 06/36] smol fix --- test/adapters/optimisticAuctionRebalanceExtension.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/adapters/optimisticAuctionRebalanceExtension.spec.ts b/test/adapters/optimisticAuctionRebalanceExtension.spec.ts index ca46d08f..d86a7486 100644 --- a/test/adapters/optimisticAuctionRebalanceExtension.spec.ts +++ b/test/adapters/optimisticAuctionRebalanceExtension.spec.ts @@ -71,7 +71,7 @@ describe.only("OptimisticAuctionRebalanceExtension", () => { let allowedAssets: Address[]; before(async () => { - [owner, methodologist, operator, factory] = await getAccounts(); + [owner, methodologist, operator] = await getAccounts(); deployer = new DeployHelper(owner.wallet); From 4aa9028018a2ac533b60b55bf01c03185285616b Mon Sep 17 00:00:00 2001 From: ckoopmann Date: Tue, 12 Dec 2023 16:23:46 +0800 Subject: [PATCH 07/36] PR Feedback round 2 --- .../OptimisticAuctionRebalanceExtension.sol | 77 +++++++++---------- ...ptimisticAuctionRebalanceExtension.spec.ts | 2 +- 2 files changed, 38 insertions(+), 41 deletions(-) diff --git a/contracts/adapters/OptimisticAuctionRebalanceExtension.sol b/contracts/adapters/OptimisticAuctionRebalanceExtension.sol index 3cb7adf7..107331d7 100644 --- a/contracts/adapters/OptimisticAuctionRebalanceExtension.sol +++ b/contracts/adapters/OptimisticAuctionRebalanceExtension.sol @@ -78,34 +78,34 @@ contract OptimisticAuctionRebalanceExtension is AuctionRebalanceExtension, Asse /* ============ Structs ============ */ - struct AuctionExtensionParams { - IBaseManager baseManager; // Manager Contract of the set token for which to deploy this extension - IAuctionRebalanceModuleV1 auctionModule; // Contract that rebalances index sets via single-asset auctions - bool useAssetAllowlist; // Bool indicating whether to use asset allow list - address[] allowedAssets; // Array of allowed assets - } + struct AuctionExtensionParams { + IBaseManager baseManager; // Manager Contract of the set token for which to deploy this extension + IAuctionRebalanceModuleV1 auctionModule; // Contract that rebalances index sets via single-asset auctions + bool useAssetAllowlist; // Bool indicating whether to use asset allow list + address[] allowedAssets; // Array of allowed assets + } - struct OptimisticRebalanceParams{ - IERC20 collateral; // Collateral currency used to assert proposed transactions. - uint64 liveness; // The amount of time to dispute proposed transactions before they can be executed. - uint256 bondAmount; // Configured amount of collateral currency to make assertions for proposed transactions. - bytes32 identifier; // Identifier used to request price from the DVM. - OptimisticOracleV3Interface optimisticOracleV3; // Optimistic Oracle V3 contract used to assert proposed transactions. - } + struct OptimisticRebalanceParams{ + IERC20 collateral; // Collateral currency used to assert proposed transactions. + uint64 liveness; // The amount of time to dispute proposed transactions before they can be executed. + uint256 bondAmount; // Configured amount of collateral currency to make assertions for proposed transactions. + bytes32 identifier; // Identifier used to request price from the DVM. + OptimisticOracleV3Interface optimisticOracleV3; // Optimistic Oracle V3 contract used to assert proposed transactions. + } - struct ProductSettings{ - OptimisticRebalanceParams optimisticParams; // OptimisticRebalanceParams struct containing optimistic rebalance parameters. - bytes32 rulesHash; // IPFS hash of the rules for the product. - } + struct ProductSettings{ + OptimisticRebalanceParams optimisticParams; // OptimisticRebalanceParams struct containing optimistic rebalance parameters. + bytes32 rulesHash; // IPFS hash of the rules for the product. + } - struct Proposal{ - bytes32 proposalHash; // Hash of the proposal. - ISetToken product; // Address of the SetToken to set rules and settings for. - } + struct Proposal{ + bytes32 proposalHash; // Hash of the proposal. + ISetToken product; // Address of the SetToken to set rules and settings for. + } /* ============ State Variables ============ */ - mapping (ISetToken=>ProductSettings) public productSettings; // Mapping of set token to ProductSettings + ProductSettings public productSettings; // Mapping of set token to ProductSettings mapping(bytes32 => bytes32) public assertionIds; // Maps proposal hashes to assertionIds. mapping(bytes32 => Proposal) public proposedProduct; // Maps assertionIds to a Proposal. @@ -168,7 +168,7 @@ contract OptimisticAuctionRebalanceExtension is AuctionRebalanceExtension, Asse external onlyOperator { - productSettings[setToken] = ProductSettings({ + productSettings = ProductSettings({ optimisticParams: _optimisticParams, rulesHash: _rulesHash }); @@ -210,25 +210,23 @@ contract OptimisticAuctionRebalanceExtension is AuctionRebalanceExtension, Asse _rebalanceDuration, _positionMultiplier )); - ProductSettings memory settings = productSettings[setToken]; - require(assertionIds[proposalHash] == bytes32(0), "Proposal already exists"); - require(settings.rulesHash != bytes32(""), "Rules not set"); - require(address(settings.optimisticParams.optimisticOracleV3) != address(0), "Oracle not set"); + require(productSettings.rulesHash != bytes32(""), "Rules not set"); + require(address(productSettings.optimisticParams.optimisticOracleV3) != address(0), "Oracle not set"); - bytes memory claim = _constructClaim(proposalHash, settings.rulesHash); - uint256 totalBond = _pullBond(settings.optimisticParams); + bytes memory claim = _constructClaim(proposalHash, productSettings.rulesHash); + uint256 totalBond = _pullBond(productSettings.optimisticParams); - bytes32 assertionId = settings.optimisticParams.optimisticOracleV3.assertTruth( + bytes32 assertionId = productSettings.optimisticParams.optimisticOracleV3.assertTruth( claim, msg.sender, address(this), address(0), - settings.optimisticParams.liveness, - settings.optimisticParams.collateral, + productSettings.optimisticParams.liveness, + productSettings.optimisticParams.collateral, totalBond, - settings.optimisticParams.identifier, + productSettings.optimisticParams.identifier, bytes32(0) ); @@ -239,7 +237,7 @@ contract OptimisticAuctionRebalanceExtension is AuctionRebalanceExtension, Asse }); emit RebalanceProposed( setToken, _quoteAsset, _oldComponents, _newComponents, _newComponentsAuctionParams, _oldComponentsAuctionParams, _shouldLockSetToken, _rebalanceDuration, _positionMultiplier); - emit AssertedClaim(setToken, msg.sender, settings.rulesHash, assertionId, claim); + emit AssertedClaim(setToken, msg.sender, productSettings.rulesHash, assertionId, claim); } @@ -288,13 +286,12 @@ contract OptimisticAuctionRebalanceExtension is AuctionRebalanceExtension, Asse // Disputed assertions are expected to revert here. Assumption past this point is that there was a valid assertion. require(assertionId != bytes32(0), "Proposal hash does not exist"); - ProductSettings memory settings = productSettings[setToken]; _deleteProposal(assertionId); // There is no need to check the assertion result as this point can be reached only for non-disputed assertions. // It is expected that future versions of the Optimistic Oracle will always revert here, // if the assertionId has not been settled and can not currently be settled. - settings.optimisticParams.optimisticOracleV3.settleAndGetAssertionResult(assertionId); + productSettings.optimisticParams.optimisticOracleV3.settleAndGetAssertionResult(assertionId); address[] memory currentComponents = setToken.getComponents(); @@ -346,19 +343,19 @@ contract OptimisticAuctionRebalanceExtension is AuctionRebalanceExtension, Asse */ function assertionDisputedCallback(bytes32 _assertionId) external { Proposal memory proposal = proposedProduct[_assertionId]; - ProductSettings memory settings = productSettings[proposal.product]; + require(proposal.product == setToken, "Invalid proposal product"); - require(address(settings.optimisticParams.optimisticOracleV3) != address(0), "Invalid oracle address"); + require(address(productSettings.optimisticParams.optimisticOracleV3) != address(0), "Invalid oracle address"); // If the sender is the Optimistic Oracle V3, delete the proposal and associated assertionId. - if (msg.sender == address(settings.optimisticParams.optimisticOracleV3)) { + if (msg.sender == address(productSettings.optimisticParams.optimisticOracleV3)) { // Delete the disputed proposal and associated assertionId. _deleteProposal(_assertionId); } else { // If the sender is not the expected Optimistic Oracle V3, check if the expected Oracle has the assertion and if not delete. require(proposal.proposalHash != bytes32(0), "Invalid proposal hash"); - require(settings.optimisticParams.optimisticOracleV3.getAssertion(_assertionId).asserter == address(0), "Oracle has assertion"); + require(productSettings.optimisticParams.optimisticOracleV3.getAssertion(_assertionId).asserter == address(0), "Oracle has assertion"); _deleteProposal(_assertionId); } emit ProposalDeleted(_assertionId, proposal); diff --git a/test/adapters/optimisticAuctionRebalanceExtension.spec.ts b/test/adapters/optimisticAuctionRebalanceExtension.spec.ts index d86a7486..a2f3cb2a 100644 --- a/test/adapters/optimisticAuctionRebalanceExtension.spec.ts +++ b/test/adapters/optimisticAuctionRebalanceExtension.spec.ts @@ -46,7 +46,7 @@ function base58ToHexString(base58String: string) { return "0x" + hexString; } -describe.only("OptimisticAuctionRebalanceExtension", () => { +describe("OptimisticAuctionRebalanceExtension", () => { let owner: Account; let methodologist: Account; let operator: Account; From 7c040e945abd387e520e488f25f923b661257a13 Mon Sep 17 00:00:00 2001 From: ckoopmann Date: Mon, 18 Dec 2023 12:04:12 +0800 Subject: [PATCH 08/36] PR Feedback round 3 --- ...ptimisticAuctionRebalanceExtension.spec.ts | 25 ------------------- 1 file changed, 25 deletions(-) diff --git a/test/adapters/optimisticAuctionRebalanceExtension.spec.ts b/test/adapters/optimisticAuctionRebalanceExtension.spec.ts index a2f3cb2a..4c00b7ba 100644 --- a/test/adapters/optimisticAuctionRebalanceExtension.spec.ts +++ b/test/adapters/optimisticAuctionRebalanceExtension.spec.ts @@ -165,18 +165,6 @@ describe("OptimisticAuctionRebalanceExtension", () => { ).to.not.be.reverted; }); - it("should revert if module is initialized and extension is not", async () => { - const extension = await deployer.extensions.deployOptimisticAuctionRebalanceExtension( - subjectBaseManager, - subjectAuctionRebalanceModule, - subjectUseAssetAllowlist, - subjectAllowedAssets, - ); - await expect( - extension.connect(operator.wallet).initialize(), - ).to.be.revertedWith("Must be adapter"); - }); - it("should revert if module is initialized without being added", async () => { const extension = await deployer.extensions.deployOptimisticAuctionRebalanceExtension( subjectBaseManager, @@ -738,19 +726,6 @@ describe("OptimisticAuctionRebalanceExtension", () => { }); }); }); - - describe("#removeAdapter", () => { - async function subject() { - return await baseManager - .connect(operator.wallet) - .removeAdapter(auctionRebalanceExtension.address); - } - it("should remove the extension", async () => { - expect(await baseManager.isAdapter(auctionRebalanceExtension.address)).to.be.true; - await subject(); - expect(await baseManager.isAdapter(auctionRebalanceExtension.address)).to.be.false; - }); - }); }); }); }); From c3d39023a8bd38f5a3fdb63572d7540226b649b5 Mon Sep 17 00:00:00 2001 From: ckoopmann Date: Mon, 18 Dec 2023 12:10:00 +0800 Subject: [PATCH 09/36] Simplify constructor tests --- ...ptimisticAuctionRebalanceExtension.spec.ts | 919 +++++++++--------- 1 file changed, 447 insertions(+), 472 deletions(-) diff --git a/test/adapters/optimisticAuctionRebalanceExtension.spec.ts b/test/adapters/optimisticAuctionRebalanceExtension.spec.ts index 4c00b7ba..887ec684 100644 --- a/test/adapters/optimisticAuctionRebalanceExtension.spec.ts +++ b/test/adapters/optimisticAuctionRebalanceExtension.spec.ts @@ -5,7 +5,7 @@ import { ADDRESS_ZERO, ZERO } from "@utils/constants"; import { BaseManager, ConstantPriceAdapter, -OptimisticAuctionRebalanceExtension, + OptimisticAuctionRebalanceExtension, OptimisticOracleV3Mock, StandardTokenMock, } from "@utils/contracts/index"; @@ -122,26 +122,23 @@ describe("OptimisticAuctionRebalanceExtension", () => { describe("#constructor", () => { let subjectBaseManager: Address; let subjectAuctionRebalanceModule: Address; - let subjectUseAssetAllowlist: boolean; - let subjectAllowedAssets: Address[]; + let subjectUseAssetAllowlist: boolean; + let subjectAllowedAssets: Address[]; beforeEach(async () => { subjectBaseManager = baseManager.address; subjectAuctionRebalanceModule = setV2Setup.auctionModule.address; - subjectUseAssetAllowlist = false; - subjectAllowedAssets = []; + subjectUseAssetAllowlist = false; + subjectAllowedAssets = []; }); - async function subject(): Promise { - let extension = await deployer.extensions.deployOptimisticAuctionRebalanceExtension( + function subject(): Promise { + return deployer.extensions.deployOptimisticAuctionRebalanceExtension( subjectBaseManager, subjectAuctionRebalanceModule, - subjectUseAssetAllowlist, - allowedAssets, + subjectUseAssetAllowlist, + subjectAllowedAssets, ); - extension = extension.connect(operator.wallet); - await baseManager.addAdapter(extension.address); - return extension; } it("should set the correct manager core", async () => { @@ -157,239 +154,161 @@ describe("OptimisticAuctionRebalanceExtension", () => { const actualAuctionRebalanceModule = await auctionExtension.auctionModule(); expect(actualAuctionRebalanceModule).to.eq(subjectAuctionRebalanceModule); }); - it("should be able to initialize extension and module at the same time", async () => { - const auctionExtension = await subject(); - await expect( - auctionExtension - .initialize(), - ).to.not.be.reverted; - }); - - it("should revert if module is initialized without being added", async () => { - const extension = await deployer.extensions.deployOptimisticAuctionRebalanceExtension( - subjectBaseManager, - subjectAuctionRebalanceModule, - subjectUseAssetAllowlist, - subjectAllowedAssets, - ); - await expect( - extension.connect(operator.wallet).initialize(), - ).to.be.revertedWith("Must be adapter"); - }); - - it("should revert if extension is initialized without being added", async () => { - const extension = await deployer.extensions.deployOptimisticAuctionRebalanceExtension( - subjectBaseManager, - subjectAuctionRebalanceModule, - subjectUseAssetAllowlist, - allowedAssets, - ); - await expect( - extension.connect(operator.wallet).initialize(), - ).to.be.revertedWith("Must be adapter"); - }); }); - context("when auction rebalance extension is added as adapter and needs to be initialized", () => { - let subjectCaller: Account; - - beforeEach(async () => { - subjectCaller = operator; - await baseManager.addAdapter(auctionRebalanceExtension.address); - }); + context( + "when auction rebalance extension is added as adapter and needs to be initialized", + () => { + let subjectCaller: Account; - describe("#initialize", () => { - async function subject() { - return await auctionRebalanceExtension - .connect(subjectCaller.wallet) - .initialize(); - } - - it("should initialize AuctionRebalanceModule", async () => { - await subject(); - const isInitialized = await setToken.isInitializedModule(setV2Setup.auctionModule.address); - expect(isInitialized).to.be.true; + beforeEach(async () => { + subjectCaller = operator; + await baseManager.addAdapter(auctionRebalanceExtension.address); }); - describe("when the initializer is not the owner", () => { - beforeEach(async () => { - subjectCaller = await getRandomAccount(); - }); + describe("#initialize", () => { + async function subject() { + return await auctionRebalanceExtension.connect(subjectCaller.wallet).initialize(); + } - it("should revert", async () => { - await expect(subject()).to.be.revertedWith("Must be operator"); + it("should initialize AuctionRebalanceModule", async () => { + await subject(); + const isInitialized = await setToken.isInitializedModule( + setV2Setup.auctionModule.address, + ); + expect(isInitialized).to.be.true; }); - }); - }); - context("when auction rebalance extension is deployed and initialized.", () => { - beforeEach(async () => { - await auctionRebalanceExtension - .connect(operator.wallet) - .initialize(); + describe("when the initializer is not the owner", () => { + beforeEach(async () => { + subjectCaller = await getRandomAccount(); + }); + + it("should revert", async () => { + await expect(subject()).to.be.revertedWith("Must be operator"); + }); + }); }); - context("when the product settings have been set", () => { + context("when auction rebalance extension is deployed and initialized.", () => { beforeEach(async () => { - await auctionRebalanceExtension.connect(operator.wallet).setProductSettings( - { - collateral: collateralAsset.address, - liveness: BigNumber.from(0), - bondAmount: BigNumber.from(0), - identifier: utils.formatBytes32String(""), - optimisticOracleV3: optimisticOracleV3Mock.address, - }, - utils.arrayify(base58ToHexString("Qmc5gCcjYypU7y28oCALwfSvxCBskLuPKWpK4qpterKC7z")), - ); + await auctionRebalanceExtension.connect(operator.wallet).initialize(); }); - context("when a rebalance has been proposed", () => { - let subjectQuoteAsset: Address; - let subjectOldComponents: Address[]; - let subjectNewComponents: Address[]; - let subjectNewComponentsAuctionParams: any[]; - let subjectOldComponentsAuctionParams: any[]; - let subjectShouldLockSetToken: boolean; - let subjectRebalanceDuration: BigNumber; - let subjectPositionMultiplier: BigNumber; - let subjectCaller: Account; + context("when the product settings have been set", () => { beforeEach(async () => { - subjectQuoteAsset = setV2Setup.weth.address; - - subjectOldComponents = [ - setV2Setup.dai.address, - setV2Setup.wbtc.address, - setV2Setup.weth.address, - ]; - subjectNewComponents = [setV2Setup.usdc.address]; - - subjectNewComponentsAuctionParams = [ - { - targetUnit: usdc(100), - priceAdapterName: "ConstantPriceAdapter", - priceAdapterConfigData: await priceAdapter.getEncodedData(ether(0.005)), - }, - ]; - - subjectOldComponentsAuctionParams = [ - { - targetUnit: ether(50), - priceAdapterName: "ConstantPriceAdapter", - priceAdapterConfigData: await priceAdapter.getEncodedData(ether(0.005)), - }, + await auctionRebalanceExtension.connect(operator.wallet).setProductSettings( { - targetUnit: bitcoin(0.01), - priceAdapterName: "ConstantPriceAdapter", - priceAdapterConfigData: await priceAdapter.getEncodedData(ether(0.005)), + collateral: collateralAsset.address, + liveness: BigNumber.from(0), + bondAmount: BigNumber.from(0), + identifier: utils.formatBytes32String(""), + optimisticOracleV3: optimisticOracleV3Mock.address, }, - { - targetUnit: ether(0.1), - priceAdapterName: "ConstantPriceAdapter", - priceAdapterConfigData: await priceAdapter.getEncodedData(ether(0.005)), - }, - ]; - - subjectShouldLockSetToken = true; - subjectRebalanceDuration = BigNumber.from(86400); - subjectPositionMultiplier = ether(0.999); - subjectCaller = operator; + utils.arrayify(base58ToHexString("Qmc5gCcjYypU7y28oCALwfSvxCBskLuPKWpK4qpterKC7z")), + ); }); - describe("#startRebalance", () => { - async function subject(): Promise { - await auctionRebalanceExtension - .connect(subjectCaller.wallet) - .proposeRebalance( - subjectQuoteAsset, - subjectOldComponents, - subjectNewComponents, - subjectNewComponentsAuctionParams, - subjectOldComponentsAuctionParams, - subjectShouldLockSetToken, - subjectRebalanceDuration, - subjectPositionMultiplier, - ); - return auctionRebalanceExtension - .connect(subjectCaller.wallet) - .startRebalance( - subjectQuoteAsset, - subjectOldComponents, - subjectNewComponents, - subjectNewComponentsAuctionParams, - subjectOldComponentsAuctionParams, - subjectShouldLockSetToken, - subjectRebalanceDuration, - subjectPositionMultiplier, - ); - } - it("should set the auction execution params correctly", async () => { - await subject(); - expect(1).to.eq(1); + context("when a rebalance has been proposed", () => { + let subjectQuoteAsset: Address; + let subjectOldComponents: Address[]; + let subjectNewComponents: Address[]; + let subjectNewComponentsAuctionParams: any[]; + let subjectOldComponentsAuctionParams: any[]; + let subjectShouldLockSetToken: boolean; + let subjectRebalanceDuration: BigNumber; + let subjectPositionMultiplier: BigNumber; + let subjectCaller: Account; + beforeEach(async () => { + subjectQuoteAsset = setV2Setup.weth.address; - const aggregateComponents = [...subjectOldComponents, ...subjectNewComponents]; - const aggregateAuctionParams = [ - ...subjectOldComponentsAuctionParams, - ...subjectNewComponentsAuctionParams, + subjectOldComponents = [ + setV2Setup.dai.address, + setV2Setup.wbtc.address, + setV2Setup.weth.address, ]; + subjectNewComponents = [setV2Setup.usdc.address]; - for (let i = 0; i < aggregateAuctionParams.length; i++) { - const executionInfo = await setV2Setup.auctionModule.executionInfo( - setToken.address, - aggregateComponents[i], - ); - expect(executionInfo.targetUnit).to.eq(aggregateAuctionParams[i].targetUnit); - expect(executionInfo.priceAdapterName).to.eq( - aggregateAuctionParams[i].priceAdapterName, - ); - expect(executionInfo.priceAdapterConfigData).to.eq( - aggregateAuctionParams[i].priceAdapterConfigData, - ); - } - }); - - it("should set the rebalance info correctly", async () => { - const txnTimestamp = await getTransactionTimestamp(subject()); - - const rebalanceInfo = await setV2Setup.auctionModule.rebalanceInfo(setToken.address); - - expect(rebalanceInfo.quoteAsset).to.eq(subjectQuoteAsset); - expect(rebalanceInfo.rebalanceStartTime).to.eq(txnTimestamp); - expect(rebalanceInfo.rebalanceDuration).to.eq(subjectRebalanceDuration); - expect(rebalanceInfo.positionMultiplier).to.eq(subjectPositionMultiplier); - expect(rebalanceInfo.raiseTargetPercentage).to.eq(ZERO); + subjectNewComponentsAuctionParams = [ + { + targetUnit: usdc(100), + priceAdapterName: "ConstantPriceAdapter", + priceAdapterConfigData: await priceAdapter.getEncodedData(ether(0.005)), + }, + ]; - const rebalanceComponents = await setV2Setup.auctionModule.getRebalanceComponents( - setToken.address, - ); - const aggregateComponents = [...subjectOldComponents, ...subjectNewComponents]; + subjectOldComponentsAuctionParams = [ + { + targetUnit: ether(50), + priceAdapterName: "ConstantPriceAdapter", + priceAdapterConfigData: await priceAdapter.getEncodedData(ether(0.005)), + }, + { + targetUnit: bitcoin(0.01), + priceAdapterName: "ConstantPriceAdapter", + priceAdapterConfigData: await priceAdapter.getEncodedData(ether(0.005)), + }, + { + targetUnit: ether(0.1), + priceAdapterName: "ConstantPriceAdapter", + priceAdapterConfigData: await priceAdapter.getEncodedData(ether(0.005)), + }, + ]; - for (let i = 0; i < rebalanceComponents.length; i++) { - expect(rebalanceComponents[i]).to.eq(aggregateComponents[i]); - } + subjectShouldLockSetToken = true; + subjectRebalanceDuration = BigNumber.from(86400); + subjectPositionMultiplier = ether(0.999); + subjectCaller = operator; }); - - describe("when there are no new components", () => { - beforeEach(async () => { - subjectNewComponents = []; - subjectNewComponentsAuctionParams = []; - }); + describe("#startRebalance", () => { + async function subject(): Promise { + await auctionRebalanceExtension + .connect(subjectCaller.wallet) + .proposeRebalance( + subjectQuoteAsset, + subjectOldComponents, + subjectNewComponents, + subjectNewComponentsAuctionParams, + subjectOldComponentsAuctionParams, + subjectShouldLockSetToken, + subjectRebalanceDuration, + subjectPositionMultiplier, + ); + return auctionRebalanceExtension + .connect(subjectCaller.wallet) + .startRebalance( + subjectQuoteAsset, + subjectOldComponents, + subjectNewComponents, + subjectNewComponentsAuctionParams, + subjectOldComponentsAuctionParams, + subjectShouldLockSetToken, + subjectRebalanceDuration, + subjectPositionMultiplier, + ); + } it("should set the auction execution params correctly", async () => { await subject(); + expect(1).to.eq(1); - for (let i = 0; i < subjectOldComponents.length; i++) { + const aggregateComponents = [...subjectOldComponents, ...subjectNewComponents]; + const aggregateAuctionParams = [ + ...subjectOldComponentsAuctionParams, + ...subjectNewComponentsAuctionParams, + ]; + + for (let i = 0; i < aggregateAuctionParams.length; i++) { const executionInfo = await setV2Setup.auctionModule.executionInfo( setToken.address, - subjectOldComponents[i], - ); - expect(executionInfo.targetUnit).to.eq( - subjectOldComponentsAuctionParams[i].targetUnit, + aggregateComponents[i], ); + expect(executionInfo.targetUnit).to.eq(aggregateAuctionParams[i].targetUnit); expect(executionInfo.priceAdapterName).to.eq( - subjectOldComponentsAuctionParams[i].priceAdapterName, + aggregateAuctionParams[i].priceAdapterName, ); expect(executionInfo.priceAdapterConfigData).to.eq( - subjectOldComponentsAuctionParams[i].priceAdapterConfigData, + aggregateAuctionParams[i].priceAdapterConfigData, ); } }); @@ -410,323 +329,379 @@ describe("OptimisticAuctionRebalanceExtension", () => { const rebalanceComponents = await setV2Setup.auctionModule.getRebalanceComponents( setToken.address, ); + const aggregateComponents = [...subjectOldComponents, ...subjectNewComponents]; + for (let i = 0; i < rebalanceComponents.length; i++) { - expect(rebalanceComponents[i]).to.eq(subjectOldComponents[i]); + expect(rebalanceComponents[i]).to.eq(aggregateComponents[i]); } }); - }); - describe("when old components are passed in different order", () => { - beforeEach(async () => { - subjectOldComponents = [ - setV2Setup.dai.address, - setV2Setup.weth.address, - setV2Setup.wbtc.address, - ]; - }); - - it("should revert", async () => { - await expect(subject()).to.be.revertedWith("Mismatch: old and current components"); - }); - }); + describe("when there are no new components", () => { + beforeEach(async () => { + subjectNewComponents = []; + subjectNewComponentsAuctionParams = []; + }); + + it("should set the auction execution params correctly", async () => { + await subject(); + + for (let i = 0; i < subjectOldComponents.length; i++) { + const executionInfo = await setV2Setup.auctionModule.executionInfo( + setToken.address, + subjectOldComponents[i], + ); + expect(executionInfo.targetUnit).to.eq( + subjectOldComponentsAuctionParams[i].targetUnit, + ); + expect(executionInfo.priceAdapterName).to.eq( + subjectOldComponentsAuctionParams[i].priceAdapterName, + ); + expect(executionInfo.priceAdapterConfigData).to.eq( + subjectOldComponentsAuctionParams[i].priceAdapterConfigData, + ); + } + }); + + it("should set the rebalance info correctly", async () => { + const txnTimestamp = await getTransactionTimestamp(subject()); + + const rebalanceInfo = await setV2Setup.auctionModule.rebalanceInfo( + setToken.address, + ); - describe("when old components array is shorter than current components array", () => { - beforeEach(async () => { - subjectOldComponents = [setV2Setup.dai.address, setV2Setup.wbtc.address]; - subjectOldComponentsAuctionParams = [ - { - targetUnit: ether(50), - priceAdapterName: "ConstantPriceAdapter", - priceAdapterConfigData: await priceAdapter.getEncodedData(ether(0.005)), - }, - { - targetUnit: bitcoin(0.01), - priceAdapterName: "ConstantPriceAdapter", - priceAdapterConfigData: await priceAdapter.getEncodedData(ether(0.005)), - }, - ]; - }); + expect(rebalanceInfo.quoteAsset).to.eq(subjectQuoteAsset); + expect(rebalanceInfo.rebalanceStartTime).to.eq(txnTimestamp); + expect(rebalanceInfo.rebalanceDuration).to.eq(subjectRebalanceDuration); + expect(rebalanceInfo.positionMultiplier).to.eq(subjectPositionMultiplier); + expect(rebalanceInfo.raiseTargetPercentage).to.eq(ZERO); - it("should revert", async () => { - await expect(subject()).to.be.revertedWith( - "Mismatch: old and current components length", - ); + const rebalanceComponents = await setV2Setup.auctionModule.getRebalanceComponents( + setToken.address, + ); + for (let i = 0; i < rebalanceComponents.length; i++) { + expect(rebalanceComponents[i]).to.eq(subjectOldComponents[i]); + } + }); }); - }); - describe("when old components array is longer than current components array", () => { - beforeEach(async () => { - const price = await priceAdapter.getEncodedData(ether(1)); - subjectOldComponents = [ - setV2Setup.dai.address, - setV2Setup.wbtc.address, - setV2Setup.weth.address, - setV2Setup.usdc.address, - ]; - subjectOldComponentsAuctionParams = [ - { - targetUnit: ether(50), - priceAdapterName: "ConstantPriceAdapter", - priceAdapterConfigData: price, - }, - { - targetUnit: bitcoin(0.01), - priceAdapterName: "ConstantPriceAdapter", - priceAdapterConfigData: price, - }, - { - targetUnit: ether(0.1), - priceAdapterName: "ConstantPriceAdapter", - priceAdapterConfigData: price, - }, - { - targetUnit: usdc(100), - priceAdapterName: "ConstantPriceAdapter", - priceAdapterConfigData: price, - }, - ]; + describe("when old components are passed in different order", () => { + beforeEach(async () => { + subjectOldComponents = [ + setV2Setup.dai.address, + setV2Setup.weth.address, + setV2Setup.wbtc.address, + ]; + }); + + it("should revert", async () => { + await expect(subject()).to.be.revertedWith( + "Mismatch: old and current components", + ); + }); }); - it("should revert", async () => { - await expect(subject()).to.be.revertedWith( - "Mismatch: old and current components length", - ); + describe("when old components array is shorter than current components array", () => { + beforeEach(async () => { + subjectOldComponents = [setV2Setup.dai.address, setV2Setup.wbtc.address]; + subjectOldComponentsAuctionParams = [ + { + targetUnit: ether(50), + priceAdapterName: "ConstantPriceAdapter", + priceAdapterConfigData: await priceAdapter.getEncodedData(ether(0.005)), + }, + { + targetUnit: bitcoin(0.01), + priceAdapterName: "ConstantPriceAdapter", + priceAdapterConfigData: await priceAdapter.getEncodedData(ether(0.005)), + }, + ]; + }); + + it("should revert", async () => { + await expect(subject()).to.be.revertedWith( + "Mismatch: old and current components length", + ); + }); }); - }); - describe("when not all old components have an entry", () => { - beforeEach(async () => { - subjectOldComponents = [ - setV2Setup.dai.address, - setV2Setup.wbtc.address, - setV2Setup.usdc.address, - ]; + describe("when old components array is longer than current components array", () => { + beforeEach(async () => { + const price = await priceAdapter.getEncodedData(ether(1)); + subjectOldComponents = [ + setV2Setup.dai.address, + setV2Setup.wbtc.address, + setV2Setup.weth.address, + setV2Setup.usdc.address, + ]; + subjectOldComponentsAuctionParams = [ + { + targetUnit: ether(50), + priceAdapterName: "ConstantPriceAdapter", + priceAdapterConfigData: price, + }, + { + targetUnit: bitcoin(0.01), + priceAdapterName: "ConstantPriceAdapter", + priceAdapterConfigData: price, + }, + { + targetUnit: ether(0.1), + priceAdapterName: "ConstantPriceAdapter", + priceAdapterConfigData: price, + }, + { + targetUnit: usdc(100), + priceAdapterName: "ConstantPriceAdapter", + priceAdapterConfigData: price, + }, + ]; + }); + + it("should revert", async () => { + await expect(subject()).to.be.revertedWith( + "Mismatch: old and current components length", + ); + }); }); - it("should revert", async () => { - await expect(subject()).to.be.revertedWith("Mismatch: old and current components"); + describe("when not all old components have an entry", () => { + beforeEach(async () => { + subjectOldComponents = [ + setV2Setup.dai.address, + setV2Setup.wbtc.address, + setV2Setup.usdc.address, + ]; + }); + + it("should revert", async () => { + await expect(subject()).to.be.revertedWith( + "Mismatch: old and current components", + ); + }); }); - }); - describe("when the caller is not the operator", () => { - beforeEach(async () => { - subjectCaller = await getRandomAccount(); - }); + describe("when the caller is not the operator", () => { + beforeEach(async () => { + subjectCaller = await getRandomAccount(); + }); - it("should not revert", async () => { - await expect(subject()).not.to.be.reverted; + it("should not revert", async () => { + await expect(subject()).not.to.be.reverted; + }); }); }); - }); - describe("assertionDisputedCallback", () => { - it("should delete the proposal on a disputed callback", async () => { - await auctionRebalanceExtension - .connect(subjectCaller.wallet) - .proposeRebalance( - subjectQuoteAsset, - subjectOldComponents, - subjectNewComponents, - subjectNewComponentsAuctionParams, - subjectOldComponentsAuctionParams, - subjectShouldLockSetToken, - subjectRebalanceDuration, - subjectPositionMultiplier, - ); - const proposal = await auctionRebalanceExtension - .connect(subjectCaller.wallet) - .proposedProduct(utils.formatBytes32String("win")); - expect(proposal.product).to.eq(setToken.address); - - await expect( - optimisticOracleV3Mock + describe("assertionDisputedCallback", () => { + it("should delete the proposal on a disputed callback", async () => { + await auctionRebalanceExtension + .connect(subjectCaller.wallet) + .proposeRebalance( + subjectQuoteAsset, + subjectOldComponents, + subjectNewComponents, + subjectNewComponentsAuctionParams, + subjectOldComponentsAuctionParams, + subjectShouldLockSetToken, + subjectRebalanceDuration, + subjectPositionMultiplier, + ); + const proposal = await auctionRebalanceExtension + .connect(subjectCaller.wallet) + .proposedProduct(utils.formatBytes32String("win")); + expect(proposal.product).to.eq(setToken.address); + + await expect( + optimisticOracleV3Mock + .connect(subjectCaller.wallet) + .mockAssertionDisputedCallback( + auctionRebalanceExtension.address, + utils.formatBytes32String("win"), + ), + ).to.not.be.reverted; + + const proposalAfter = await auctionRebalanceExtension + .connect(subjectCaller.wallet) + .proposedProduct(utils.formatBytes32String("win")); + expect(proposalAfter.product).to.eq(ADDRESS_ZERO); + }); + it("should delete the proposal on a disputed callback from currently set oracle", async () => { + await auctionRebalanceExtension .connect(subjectCaller.wallet) - .mockAssertionDisputedCallback( - auctionRebalanceExtension.address, - utils.formatBytes32String("win"), + .proposeRebalance( + subjectQuoteAsset, + subjectOldComponents, + subjectNewComponents, + subjectNewComponentsAuctionParams, + subjectOldComponentsAuctionParams, + subjectShouldLockSetToken, + subjectRebalanceDuration, + subjectPositionMultiplier, + ); + await auctionRebalanceExtension.connect(operator.wallet).setProductSettings( + { + collateral: collateralAsset.address, + liveness: BigNumber.from(0), + bondAmount: BigNumber.from(0), + identifier: utils.formatBytes32String(""), + optimisticOracleV3: optimisticOracleV3MockUpgraded.address, + }, + utils.arrayify( + base58ToHexString("Qmc5gCcjYypU7y28oCALwfSvxCBskLuPKWpK4qpterKC7z"), ), - ).to.not.be.reverted; - - const proposalAfter = await auctionRebalanceExtension - .connect(subjectCaller.wallet) - .proposedProduct(utils.formatBytes32String("win")); - expect(proposalAfter.product).to.eq(ADDRESS_ZERO); - }); - it("should delete the proposal on a disputed callback from currently set oracle", async () => { - await auctionRebalanceExtension - .connect(subjectCaller.wallet) - .proposeRebalance( - subjectQuoteAsset, - subjectOldComponents, - subjectNewComponents, - subjectNewComponentsAuctionParams, - subjectOldComponentsAuctionParams, - subjectShouldLockSetToken, - subjectRebalanceDuration, - subjectPositionMultiplier, ); - await auctionRebalanceExtension.connect(operator.wallet).setProductSettings( - { - collateral: collateralAsset.address, - liveness: BigNumber.from(0), - bondAmount: BigNumber.from(0), - identifier: utils.formatBytes32String(""), - optimisticOracleV3: optimisticOracleV3MockUpgraded.address, - }, - utils.arrayify(base58ToHexString("Qmc5gCcjYypU7y28oCALwfSvxCBskLuPKWpK4qpterKC7z")), - ); - const proposal = await auctionRebalanceExtension - .connect(subjectCaller.wallet) - .proposedProduct(utils.formatBytes32String("win")); - expect(proposal.product).to.eq(setToken.address); - await expect( - optimisticOracleV3Mock + const proposal = await auctionRebalanceExtension .connect(subjectCaller.wallet) - .mockAssertionDisputedCallback( - auctionRebalanceExtension.address, - utils.formatBytes32String("win"), - ), - ).to.not.be.reverted; - const proposalAfter = await auctionRebalanceExtension - .connect(subjectCaller.wallet) - .proposedProduct(utils.formatBytes32String("win")); - expect(proposalAfter.product).to.eq(ADDRESS_ZERO); + .proposedProduct(utils.formatBytes32String("win")); + expect(proposal.product).to.eq(setToken.address); + await expect( + optimisticOracleV3Mock + .connect(subjectCaller.wallet) + .mockAssertionDisputedCallback( + auctionRebalanceExtension.address, + utils.formatBytes32String("win"), + ), + ).to.not.be.reverted; + const proposalAfter = await auctionRebalanceExtension + .connect(subjectCaller.wallet) + .proposedProduct(utils.formatBytes32String("win")); + expect(proposalAfter.product).to.eq(ADDRESS_ZERO); + }); }); - }); - describe("assertionResolvedCallback", () => { - it("should not revert on a resolved callback", async () => { - await auctionRebalanceExtension - .connect(subjectCaller.wallet) - .proposeRebalance( - subjectQuoteAsset, - subjectOldComponents, - subjectNewComponents, - subjectNewComponentsAuctionParams, - subjectOldComponentsAuctionParams, - subjectShouldLockSetToken, - subjectRebalanceDuration, - subjectPositionMultiplier, - ); - await expect( - optimisticOracleV3Mock + describe("assertionResolvedCallback", () => { + it("should not revert on a resolved callback", async () => { + await auctionRebalanceExtension .connect(subjectCaller.wallet) - .mockAssertionResolvedCallback( - auctionRebalanceExtension.address, - utils.formatBytes32String("win"), - true, - ), - ).to.not.be.reverted; + .proposeRebalance( + subjectQuoteAsset, + subjectOldComponents, + subjectNewComponents, + subjectNewComponentsAuctionParams, + subjectOldComponentsAuctionParams, + subjectShouldLockSetToken, + subjectRebalanceDuration, + subjectPositionMultiplier, + ); + await expect( + optimisticOracleV3Mock + .connect(subjectCaller.wallet) + .mockAssertionResolvedCallback( + auctionRebalanceExtension.address, + utils.formatBytes32String("win"), + true, + ), + ).to.not.be.reverted; + }); }); }); - }); - describe("#setRaiseTargetPercentage", () => { - let subjectRaiseTargetPercentage: BigNumber; - let subjectCaller: Account; - - beforeEach(async () => { - subjectRaiseTargetPercentage = ether(0.001); - subjectCaller = operator; - }); + describe("#setRaiseTargetPercentage", () => { + let subjectRaiseTargetPercentage: BigNumber; + let subjectCaller: Account; - async function subject(): Promise { - return await auctionRebalanceExtension - .connect(subjectCaller.wallet) - .setRaiseTargetPercentage( subjectRaiseTargetPercentage); - } + beforeEach(async () => { + subjectRaiseTargetPercentage = ether(0.001); + subjectCaller = operator; + }); - it("should correctly set the raiseTargetPercentage", async () => { - await subject(); + async function subject(): Promise { + return await auctionRebalanceExtension + .connect(subjectCaller.wallet) + .setRaiseTargetPercentage(subjectRaiseTargetPercentage); + } - const actualRaiseTargetPercentage = ( - await setV2Setup.auctionModule.rebalanceInfo(setToken.address) - ).raiseTargetPercentage; + it("should correctly set the raiseTargetPercentage", async () => { + await subject(); - expect(actualRaiseTargetPercentage).to.eq(subjectRaiseTargetPercentage); - }); + const actualRaiseTargetPercentage = ( + await setV2Setup.auctionModule.rebalanceInfo(setToken.address) + ).raiseTargetPercentage; - describe("when the caller is not the operator", async () => { - beforeEach(async () => { - subjectCaller = await getRandomAccount(); + expect(actualRaiseTargetPercentage).to.eq(subjectRaiseTargetPercentage); }); - it("should revert", async () => { - await expect(subject()).to.be.revertedWith("Must be operator"); + describe("when the caller is not the operator", async () => { + beforeEach(async () => { + subjectCaller = await getRandomAccount(); + }); + + it("should revert", async () => { + await expect(subject()).to.be.revertedWith("Must be operator"); + }); }); }); - }); - - describe("#setBidderStatus", () => { - let subjectBidders: Address[]; - let subjectStatuses: boolean[]; - let subjectCaller: Account; - beforeEach(async () => { - subjectBidders = [methodologist.address]; - subjectStatuses = [true]; - subjectCaller = operator; - }); + describe("#setBidderStatus", () => { + let subjectBidders: Address[]; + let subjectStatuses: boolean[]; + let subjectCaller: Account; - async function subject(): Promise { - return await auctionRebalanceExtension - .connect(subjectCaller.wallet) - .setBidderStatus( subjectBidders, subjectStatuses); - } + beforeEach(async () => { + subjectBidders = [methodologist.address]; + subjectStatuses = [true]; + subjectCaller = operator; + }); - it("should correctly set the bidder status", async () => { - await subject(); + async function subject(): Promise { + return await auctionRebalanceExtension + .connect(subjectCaller.wallet) + .setBidderStatus(subjectBidders, subjectStatuses); + } - const isCaller = await setV2Setup.auctionModule.isAllowedBidder( - setToken.address, - subjectBidders[0], - ); + it("should correctly set the bidder status", async () => { + await subject(); - expect(isCaller).to.be.true; - }); + const isCaller = await setV2Setup.auctionModule.isAllowedBidder( + setToken.address, + subjectBidders[0], + ); - describe("when the caller is not the operator", () => { - beforeEach(async () => { - subjectCaller = await getRandomAccount(); + expect(isCaller).to.be.true; }); - it("should revert", async () => { - await expect(subject()).to.be.revertedWith("Must be operator"); + describe("when the caller is not the operator", () => { + beforeEach(async () => { + subjectCaller = await getRandomAccount(); + }); + + it("should revert", async () => { + await expect(subject()).to.be.revertedWith("Must be operator"); + }); }); }); - }); - describe("#setAnyoneBid", () => { - let subjectStatus: boolean; - let subjectCaller: Account; + describe("#setAnyoneBid", () => { + let subjectStatus: boolean; + let subjectCaller: Account; - beforeEach(async () => { - subjectStatus = true; - subjectCaller = operator; - }); - - async function subject(): Promise { - return await auctionRebalanceExtension - .connect(subjectCaller.wallet) - .setAnyoneBid(subjectStatus); - } + beforeEach(async () => { + subjectStatus = true; + subjectCaller = operator; + }); - it("should correctly set anyone bid", async () => { - await subject(); + async function subject(): Promise { + return await auctionRebalanceExtension + .connect(subjectCaller.wallet) + .setAnyoneBid(subjectStatus); + } - const anyoneBid = await setV2Setup.auctionModule.permissionInfo(setToken.address); + it("should correctly set anyone bid", async () => { + await subject(); - expect(anyoneBid).to.be.true; - }); + const anyoneBid = await setV2Setup.auctionModule.permissionInfo(setToken.address); - describe("when the caller is not the operator", () => { - beforeEach(async () => { - subjectCaller = await getRandomAccount(); + expect(anyoneBid).to.be.true; }); - it("should revert", async () => { - await expect(subject()).to.be.revertedWith("Must be operator"); + describe("when the caller is not the operator", () => { + beforeEach(async () => { + subjectCaller = await getRandomAccount(); + }); + + it("should revert", async () => { + await expect(subject()).to.be.revertedWith("Must be operator"); + }); }); }); }); }); - }); - }); + }, + ); }); From a73d98b95bf0cc42bf98230814f3811eda27f43a Mon Sep 17 00:00:00 2001 From: ckoopmann Date: Fri, 15 Dec 2023 16:07:09 +0800 Subject: [PATCH 10/36] Copy unit tests and run integrated with existing dseth deployment --- test/integration/ethereum/addresses.ts | 7 +- ...ptimisticAuctionRebalanceExtenison.spec.ts | 610 ++++++++++++++++++ utils/config.ts | 2 +- 3 files changed, 617 insertions(+), 2 deletions(-) create mode 100644 test/integration/ethereum/optimisticAuctionRebalanceExtenison.spec.ts diff --git a/test/integration/ethereum/addresses.ts b/test/integration/ethereum/addresses.ts index aa61a80c..09247643 100644 --- a/test/integration/ethereum/addresses.ts +++ b/test/integration/ethereum/addresses.ts @@ -5,11 +5,12 @@ export const PRODUCTION_ADDRESSES = { stEthAm: "0x28424507fefb6f7f8E9D3860F56504E4e5f5f390", stEth: "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84", dai: "0x6B175474E89094C44Da98b954EedeAC495271d0F", + dsEth: "0x341c05c0E9b33C0E38d64de76516b2Ce970bB3BE", weth: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", icEth: "0x7C07F7aBe10CE8e33DC6C5aD68FE033085256A84", icReth: "0xe8888Cdbc0A5958C29e7D91DAE44897c7e64F9BC", rETH: "0xae78736Cd615f374D3085123A210448E74Fc6393", - aEthrETH : "0xCc9EE9483f662091a1de4795249E24aC0aC2630f", + aEthrETH: "0xCc9EE9483f662091a1de4795249E24aC0aC2630f", aSTETH: "0x1982b2F5814301d4e9a8b0201555376e62F82428", ETH2xFli: "0xAa6E8127831c9DE45ae56bB1b0d4D4Da6e5665BD", cEther: "0x4ddc2d193948926d02f9b1fe9e1daa0718270ed5", @@ -20,6 +21,7 @@ export const PRODUCTION_ADDRESSES = { wsETH2: "0x5dA21D9e63F1EA13D34e48B7223bcc97e3ecD687", rETH2: "0x20BC832ca081b91433ff6c17f85701B6e92486c5", sETH2: "0xFe2e637202056d30016725477c5da089Ab0A043A", + wbtc: "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599", }, whales: { stEth: "0xdc24316b9ae028f1497c275eb9192a3ea0f67022", @@ -63,10 +65,13 @@ export const PRODUCTION_ADDRESSES = { controller: "0xD2463675a099101E36D85278494268261a66603A", debtIssuanceModuleV2: "0xa0a98EB7Af028BE00d04e46e1316808A62a8fd59", notionalTradeModule: "0x600d9950c6ecAef98Cc42fa207E92397A6c43416", + integrationRegistry: "0xb9083dee5e8273E54B9DB4c31bA9d4aB7C6B28d3", + auctionModuleV1: "0x59D55D53a715b3B4581c52098BCb4075C2941DBa", tradeModule: "0xFaAB3F8f3678f68AA0d307B66e71b636F82C28BF", airdropModule: "0x09b9e7c7e2daf40fCb286fE6b863e517d5d5c40F", aaveV3LeverageStrategyExtension: "0x7d3f7EDD04916F3Cb2bC6740224c636B9AE43200", aaveV3LeverageModule: "0x71E932715F5987077ADC5A7aA245f38841E0DcBe", + constantPriceAdapter: "0x13c33656570092555Bf27Bdf53Ce24482B85D992", }, lending: { aave: { diff --git a/test/integration/ethereum/optimisticAuctionRebalanceExtenison.spec.ts b/test/integration/ethereum/optimisticAuctionRebalanceExtenison.spec.ts new file mode 100644 index 00000000..8e8cd3d2 --- /dev/null +++ b/test/integration/ethereum/optimisticAuctionRebalanceExtenison.spec.ts @@ -0,0 +1,610 @@ +import "module-alias/register"; + +import { Address, Account } from "@utils/types"; +import { ADDRESS_ZERO, ZERO } from "@utils/constants"; +import { + OptimisticAuctionRebalanceExtension, + OptimisticOracleV3Mock, +} from "@utils/contracts/index"; +import { + AuctionRebalanceModuleV1, + AuctionRebalanceModuleV1__factory, + ConstantPriceAdapter, + ConstantPriceAdapter__factory, + SetToken, + SetToken__factory, + BaseManagerV2, + BaseManagerV2__factory, + IntegrationRegistry, + IntegrationRegistry__factory, +} from "../../../typechain"; +import DeployHelper from "@utils/deploys"; +import { impersonateAccount } from "./utils"; +import { PRODUCTION_ADDRESSES } from "./addresses"; +import { + addSnapshotBeforeRestoreAfterEach, + ether, + getAccounts, + getWaffleExpect, + usdc, + getTransactionTimestamp, + getRandomAccount, +} from "@utils/index"; +import { BigNumber, ContractTransaction, utils, Signer } from "ethers"; +import base58 from "bs58"; + +const expect = getWaffleExpect(); + +function bufferToHex(buffer: Uint8Array) { + let hexStr = ""; + + for (let i = 0; i < buffer.length; i++) { + const hex = (buffer[i] & 0xff).toString(16); + hexStr += hex.length === 1 ? "0" + hex : hex; + } + + return hexStr; +} + +// Base58 decoding function (make sure you have a proper Base58 decoding function) +function base58ToHexString(base58String: string) { + const bytes = base58.decode(base58String); // Decode base58 to a buffer + const hexString = bufferToHex(bytes.slice(2)); // Convert buffer to hex, excluding the first 2 bytes + return "0x" + hexString; +} + +if (process.env.INTEGRATIONTEST) { + describe("OptimisticAuctionRebalanceExtension - Integration Test dsEth", () => { + const contractAddresses = PRODUCTION_ADDRESSES; + let owner: Account; + let methodologist: Account; + let operator: Signer; + + let deployer: DeployHelper; + let dsEth: SetToken; + let baseManager: BaseManagerV2; + + let auctionModule: AuctionRebalanceModuleV1; + let auctionRebalanceExtension: OptimisticAuctionRebalanceExtension; + let integrationRegistry: IntegrationRegistry; + + let priceAdapter: ConstantPriceAdapter; + + let optimisticOracleV3Mock: OptimisticOracleV3Mock; + + let optimisticOracleV3MockUpgraded: OptimisticOracleV3Mock; + + let collateralAssetAddress: string; + + let useAssetAllowlist: boolean; + let allowedAssets: Address[]; + + before(async () => { + [owner, methodologist] = await getAccounts(); + + deployer = new DeployHelper(owner.wallet); + + priceAdapter = ConstantPriceAdapter__factory.connect( + contractAddresses.setFork.constantPriceAdapter, + owner.wallet, + ); + optimisticOracleV3Mock = await deployer.mocks.deployOptimisticOracleV3Mock(); + optimisticOracleV3MockUpgraded = await deployer.mocks.deployOptimisticOracleV3Mock(); + collateralAssetAddress = contractAddresses.tokens.weth; + + integrationRegistry = IntegrationRegistry__factory.connect( + contractAddresses.setFork.integrationRegistry, + owner.wallet, + ); + const integrationRegistryOwner = await impersonateAccount(await integrationRegistry.owner()); + integrationRegistry = integrationRegistry.connect(integrationRegistryOwner); + + auctionModule = AuctionRebalanceModuleV1__factory.connect( + contractAddresses.setFork.auctionModuleV1, + owner.wallet, + ); + + useAssetAllowlist = false; + allowedAssets = []; + + dsEth = SetToken__factory.connect(contractAddresses.tokens.dsEth, owner.wallet); + + baseManager = BaseManagerV2__factory.connect(await dsEth.manager(), owner.wallet); + operator = await impersonateAccount(await baseManager.operator()); + baseManager = baseManager.connect(operator); + + auctionRebalanceExtension = await deployer.extensions.deployOptimisticAuctionRebalanceExtension( + baseManager.address, + auctionModule.address, + useAssetAllowlist, + allowedAssets, + ); + auctionRebalanceExtension = auctionRebalanceExtension.connect(operator); + }); + + addSnapshotBeforeRestoreAfterEach(); + + context("when auction rebalance extension is added as extension", () => { + + beforeEach(async () => { + await baseManager.addExtension(auctionRebalanceExtension.address); + }); + + context("when the product settings have been set", () => { + beforeEach(async () => { + await auctionRebalanceExtension.connect(operator).setProductSettings( + { + collateral: collateralAssetAddress, + liveness: BigNumber.from(0), + bondAmount: BigNumber.from(0), + identifier: utils.formatBytes32String(""), + optimisticOracleV3: optimisticOracleV3Mock.address, + }, + utils.arrayify(base58ToHexString("Qmc5gCcjYypU7y28oCALwfSvxCBskLuPKWpK4qpterKC7z")), + ); + }); + + context("when a rebalance has been proposed", () => { + let subjectQuoteAsset: Address; + let subjectOldComponents: Address[]; + let subjectNewComponents: Address[]; + let subjectNewComponentsAuctionParams: any[]; + let subjectOldComponentsAuctionParams: any[]; + let subjectShouldLockSetToken: boolean; + let subjectRebalanceDuration: BigNumber; + let subjectPositionMultiplier: BigNumber; + let subjectCaller: Signer; + beforeEach(async () => { + subjectQuoteAsset = contractAddresses.tokens.weth; + + subjectOldComponents = await dsEth.getComponents(); + subjectNewComponents = [contractAddresses.tokens.USDC]; + + subjectNewComponentsAuctionParams = [ + { + targetUnit: usdc(100), + priceAdapterName: "ConstantPriceAdapter", + priceAdapterConfigData: await priceAdapter.getEncodedData(ether(0.005)), + }, + ]; + + const sellAllAuctionParam = { + targetUnit: ether(0), + priceAdapterName: "ConstantPriceAdapter", + priceAdapterConfigData: await priceAdapter.getEncodedData(ether(0.005)), + }; + subjectOldComponentsAuctionParams = subjectOldComponents.map(() => sellAllAuctionParam); + + subjectShouldLockSetToken = true; + subjectRebalanceDuration = BigNumber.from(86400); + subjectPositionMultiplier = ether(0.999); + subjectCaller = operator; + }); + describe("#startRebalance", () => { + async function subject(): Promise { + await auctionRebalanceExtension + .connect(subjectCaller) + .proposeRebalance( + subjectQuoteAsset, + subjectOldComponents, + subjectNewComponents, + subjectNewComponentsAuctionParams, + subjectOldComponentsAuctionParams, + subjectShouldLockSetToken, + subjectRebalanceDuration, + subjectPositionMultiplier, + ); + return auctionRebalanceExtension + .connect(subjectCaller) + .startRebalance( + subjectQuoteAsset, + subjectOldComponents, + subjectNewComponents, + subjectNewComponentsAuctionParams, + subjectOldComponentsAuctionParams, + subjectShouldLockSetToken, + subjectRebalanceDuration, + subjectPositionMultiplier, + ); + } + + it("should set the auction execution params correctly", async () => { + await subject(); + expect(1).to.eq(1); + + const aggregateComponents = [...subjectOldComponents, ...subjectNewComponents]; + const aggregateAuctionParams = [ + ...subjectOldComponentsAuctionParams, + ...subjectNewComponentsAuctionParams, + ]; + + for (let i = 0; i < aggregateAuctionParams.length; i++) { + const executionInfo = await auctionModule.executionInfo( + dsEth.address, + aggregateComponents[i], + ); + expect(executionInfo.targetUnit).to.eq(aggregateAuctionParams[i].targetUnit); + expect(executionInfo.priceAdapterName).to.eq( + aggregateAuctionParams[i].priceAdapterName, + ); + expect(executionInfo.priceAdapterConfigData).to.eq( + aggregateAuctionParams[i].priceAdapterConfigData, + ); + } + }); + + it("should set the rebalance info correctly", async () => { + const txnTimestamp = await getTransactionTimestamp(subject()); + + const rebalanceInfo = await auctionModule.rebalanceInfo(dsEth.address); + + expect(utils.getAddress(rebalanceInfo.quoteAsset)).to.eq( + utils.getAddress(subjectQuoteAsset), + ); + expect(rebalanceInfo.rebalanceStartTime).to.eq(txnTimestamp); + expect(rebalanceInfo.rebalanceDuration).to.eq(subjectRebalanceDuration); + expect(rebalanceInfo.positionMultiplier).to.eq(subjectPositionMultiplier); + expect(rebalanceInfo.raiseTargetPercentage).to.eq(ZERO); + + const rebalanceComponents = await auctionModule.getRebalanceComponents(dsEth.address); + const aggregateComponents = [...subjectOldComponents, ...subjectNewComponents]; + + for (let i = 0; i < rebalanceComponents.length; i++) { + expect(utils.getAddress(rebalanceComponents[i])).to.eq( + utils.getAddress(aggregateComponents[i]), + ); + } + }); + + describe("when there are no new components", () => { + beforeEach(async () => { + subjectNewComponents = []; + subjectNewComponentsAuctionParams = []; + }); + + it("should set the auction execution params correctly", async () => { + await subject(); + + for (let i = 0; i < subjectOldComponents.length; i++) { + const executionInfo = await auctionModule.executionInfo( + dsEth.address, + subjectOldComponents[i], + ); + expect(executionInfo.targetUnit).to.eq( + subjectOldComponentsAuctionParams[i].targetUnit, + ); + expect(executionInfo.priceAdapterName).to.eq( + subjectOldComponentsAuctionParams[i].priceAdapterName, + ); + expect(executionInfo.priceAdapterConfigData).to.eq( + subjectOldComponentsAuctionParams[i].priceAdapterConfigData, + ); + } + }); + + it("should set the rebalance info correctly", async () => { + const txnTimestamp = await getTransactionTimestamp(subject()); + + const rebalanceInfo = await auctionModule.rebalanceInfo(dsEth.address); + + expect(utils.getAddress(rebalanceInfo.quoteAsset)).to.eq( + utils.getAddress(subjectQuoteAsset), + ); + expect(rebalanceInfo.rebalanceStartTime).to.eq(txnTimestamp); + expect(rebalanceInfo.rebalanceDuration).to.eq(subjectRebalanceDuration); + expect(rebalanceInfo.positionMultiplier).to.eq(subjectPositionMultiplier); + expect(rebalanceInfo.raiseTargetPercentage).to.eq(ZERO); + + const rebalanceComponents = await auctionModule.getRebalanceComponents( + dsEth.address, + ); + for (let i = 0; i < rebalanceComponents.length; i++) { + expect(rebalanceComponents[i]).to.eq(subjectOldComponents[i]); + } + }); + }); + + describe("when old components are passed in different order", () => { + beforeEach(async () => { + subjectOldComponents = [ + contractAddresses.tokens.dai, + contractAddresses.tokens.weth, + contractAddresses.tokens.wbtc, + ]; + }); + + it("should revert", async () => { + await expect(subject()).to.be.revertedWith("Mismatch: old and current components"); + }); + }); + + describe("when old components array is shorter than current components array", () => { + beforeEach(async () => { + const setComponents = await dsEth.getComponents(); + subjectOldComponents = setComponents.slice(0, setComponents.length - 1); + const priceAdapterConfigData = await priceAdapter.getEncodedData(ether(0.005)); + subjectOldComponentsAuctionParams = subjectOldComponents.map(() => { + return { + targetUnit: ether(50), + priceAdapterName: "ConstantPriceAdapter", + priceAdapterConfigData, + }; + }); + }); + + it("should revert", async () => { + await expect(subject()).to.be.revertedWith( + "Mismatch: old and current components length", + ); + }); + }); + + describe("when old components array is longer than current components array", () => { + beforeEach(async () => { + const price = await priceAdapter.getEncodedData(ether(1)); + const setComponents = await dsEth.getComponents(); + subjectOldComponents = [ + ...setComponents, + contractAddresses.tokens.dai, + ]; + subjectOldComponentsAuctionParams = subjectOldComponents.map(() => { + return { + targetUnit: ether(50), + priceAdapterName: "ConstantPriceAdapter", + priceAdapterConfigData: price, + }; + }); + }); + + it("should revert", async () => { + await expect(subject()).to.be.revertedWith( + "Mismatch: old and current components length", + ); + }); + }); + + describe("when not all old components have an entry", () => { + beforeEach(async () => { + subjectOldComponents = [ + contractAddresses.tokens.dai, + contractAddresses.tokens.wbtc, + contractAddresses.tokens.USDC, + ]; + }); + + it("should revert", async () => { + await expect(subject()).to.be.revertedWith("Mismatch: old and current components"); + }); + }); + + describe("when the caller is not the operator", () => { + beforeEach(async () => { + subjectCaller = (await getRandomAccount()).wallet; + }); + + it("should not revert", async () => { + await expect(subject()).not.to.be.reverted; + }); + }); + }); + describe("assertionDisputedCallback", () => { + it("should delete the proposal on a disputed callback", async () => { + await auctionRebalanceExtension + .connect(subjectCaller) + .proposeRebalance( + subjectQuoteAsset, + subjectOldComponents, + subjectNewComponents, + subjectNewComponentsAuctionParams, + subjectOldComponentsAuctionParams, + subjectShouldLockSetToken, + subjectRebalanceDuration, + subjectPositionMultiplier, + ); + const proposal = await auctionRebalanceExtension + .connect(subjectCaller) + .proposedProduct(utils.formatBytes32String("win")); + expect(proposal.product).to.eq(dsEth.address); + + await expect( + optimisticOracleV3Mock + .connect(subjectCaller) + .mockAssertionDisputedCallback( + auctionRebalanceExtension.address, + utils.formatBytes32String("win"), + ), + ).to.not.be.reverted; + + const proposalAfter = await auctionRebalanceExtension + .connect(subjectCaller) + .proposedProduct(utils.formatBytes32String("win")); + expect(proposalAfter.product).to.eq(ADDRESS_ZERO); + }); + it("should delete the proposal on a disputed callback from currently set oracle", async () => { + await auctionRebalanceExtension + .connect(subjectCaller) + .proposeRebalance( + subjectQuoteAsset, + subjectOldComponents, + subjectNewComponents, + subjectNewComponentsAuctionParams, + subjectOldComponentsAuctionParams, + subjectShouldLockSetToken, + subjectRebalanceDuration, + subjectPositionMultiplier, + ); + await auctionRebalanceExtension.connect(operator).setProductSettings( + { + collateral: collateralAssetAddress, + liveness: BigNumber.from(0), + bondAmount: BigNumber.from(0), + identifier: utils.formatBytes32String(""), + optimisticOracleV3: optimisticOracleV3MockUpgraded.address, + }, + utils.arrayify(base58ToHexString("Qmc5gCcjYypU7y28oCALwfSvxCBskLuPKWpK4qpterKC7z")), + ); + const proposal = await auctionRebalanceExtension + .connect(subjectCaller) + .proposedProduct(utils.formatBytes32String("win")); + expect(proposal.product).to.eq(dsEth.address); + await expect( + optimisticOracleV3Mock + .connect(subjectCaller) + .mockAssertionDisputedCallback( + auctionRebalanceExtension.address, + utils.formatBytes32String("win"), + ), + ).to.not.be.reverted; + const proposalAfter = await auctionRebalanceExtension + .connect(subjectCaller) + .proposedProduct(utils.formatBytes32String("win")); + expect(proposalAfter.product).to.eq(ADDRESS_ZERO); + }); + }); + describe("assertionResolvedCallback", () => { + it("should not revert on a resolved callback", async () => { + await auctionRebalanceExtension + .connect(subjectCaller) + .proposeRebalance( + subjectQuoteAsset, + subjectOldComponents, + subjectNewComponents, + subjectNewComponentsAuctionParams, + subjectOldComponentsAuctionParams, + subjectShouldLockSetToken, + subjectRebalanceDuration, + subjectPositionMultiplier, + ); + await expect( + optimisticOracleV3Mock + .connect(subjectCaller) + .mockAssertionResolvedCallback( + auctionRebalanceExtension.address, + utils.formatBytes32String("win"), + true, + ), + ).to.not.be.reverted; + }); + }); + }); + describe("#setRaiseTargetPercentage", () => { + let subjectRaiseTargetPercentage: BigNumber; + let subjectCaller: Signer; + + beforeEach(async () => { + subjectRaiseTargetPercentage = ether(0.001); + subjectCaller = operator; + }); + + async function subject(): Promise { + return await auctionRebalanceExtension + .connect(subjectCaller) + .setRaiseTargetPercentage(subjectRaiseTargetPercentage); + } + + it("should correctly set the raiseTargetPercentage", async () => { + await subject(); + + const actualRaiseTargetPercentage = (await auctionModule.rebalanceInfo(dsEth.address)) + .raiseTargetPercentage; + + expect(actualRaiseTargetPercentage).to.eq(subjectRaiseTargetPercentage); + }); + + describe("when the caller is not the operator", async () => { + beforeEach(async () => { + subjectCaller = (await getRandomAccount()).wallet; + }); + + it("should revert", async () => { + await expect(subject()).to.be.revertedWith("Must be operator"); + }); + }); + }); + + describe("#setBidderStatus", () => { + let subjectBidders: Address[]; + let subjectStatuses: boolean[]; + let subjectCaller: Signer; + + beforeEach(async () => { + subjectBidders = [methodologist.address]; + subjectStatuses = [true]; + subjectCaller = operator; + }); + + async function subject(): Promise { + return await auctionRebalanceExtension + .connect(subjectCaller) + .setBidderStatus(subjectBidders, subjectStatuses); + } + + it("should correctly set the bidder status", async () => { + await subject(); + + const isCaller = await auctionModule.isAllowedBidder(dsEth.address, subjectBidders[0]); + + expect(isCaller).to.be.true; + }); + + describe("when the caller is not the operator", () => { + beforeEach(async () => { + subjectCaller = (await getRandomAccount()).wallet; + }); + + it("should revert", async () => { + await expect(subject()).to.be.revertedWith("Must be operator"); + }); + }); + }); + + describe("#setAnyoneBid", () => { + let subjectStatus: boolean; + let subjectCaller: Signer; + + beforeEach(async () => { + subjectStatus = true; + subjectCaller = operator; + }); + + async function subject(): Promise { + return await auctionRebalanceExtension + .connect(subjectCaller) + .setAnyoneBid(subjectStatus); + } + + it("should correctly set anyone bid", async () => { + await subject(); + + const anyoneBid = await auctionModule.permissionInfo(dsEth.address); + + expect(anyoneBid).to.be.true; + }); + + describe("when the caller is not the operator", () => { + beforeEach(async () => { + subjectCaller = (await getRandomAccount()).wallet; + }); + + it("should revert", async () => { + await expect(subject()).to.be.revertedWith("Must be operator"); + }); + }); + }); + + describe("#removeExtension", () => { + async function subject() { + return await baseManager + .connect(operator) + .removeExtension(auctionRebalanceExtension.address); + } + it("should remove the extension", async () => { + expect(await baseManager.isExtension(auctionRebalanceExtension.address)).to.be.true; + await subject(); + expect(await baseManager.isExtension(auctionRebalanceExtension.address)).to.be.false; + }); + }); + }); + }); + }); +} diff --git a/utils/config.ts b/utils/config.ts index fbbc2021..505d18da 100644 --- a/utils/config.ts +++ b/utils/config.ts @@ -10,7 +10,7 @@ export const optimismForkingConfig = { export const mainnetForkingConfig = { url: "https://eth-mainnet.alchemyapi.io/v2/" + process.env.ALCHEMY_TOKEN, - blockNumber: process.env.LATESTBLOCK ? undefined : 17895372, + blockNumber: process.env.LATESTBLOCK ? undefined : 18789000, }; export const forkingConfig = From 05ec545aaf496e566bfd84ce7674553c9ab1feae Mon Sep 17 00:00:00 2001 From: ckoopmann Date: Fri, 15 Dec 2023 17:10:56 +0800 Subject: [PATCH 11/36] Test failing with unsupported identifier error --- .../OptimisticOracleV3Interface.sol | 11 ++- contracts/mocks/OptimisticOracleV3Mock.sol | 6 +- test/integration/ethereum/addresses.ts | 7 +- ...ptimisticAuctionRebalanceExtenison.spec.ts | 87 ++++++++++++------- 4 files changed, 76 insertions(+), 35 deletions(-) diff --git a/contracts/interfaces/OptimisticOracleV3Interface.sol b/contracts/interfaces/OptimisticOracleV3Interface.sol index 168cb8ea..d9be9a64 100644 --- a/contracts/interfaces/OptimisticOracleV3Interface.sol +++ b/contracts/interfaces/OptimisticOracleV3Interface.sol @@ -39,6 +39,15 @@ interface OptimisticOracleV3Interface { uint256 finalFee; // Final fee of the currency. } + /** + * @notice Disputes an assertion. Depending on how the assertion was configured, this may either escalate to the UMA + * DVM or the configured escalation manager for arbitration. + * @dev The caller must approve this contract to spend at least bond amount of currency for the associated assertion. + * @param assertionId unique identifier for the assertion to dispute. + * @param disputer receives bonds back at settlement. + */ + function disputeAssertion(bytes32 assertionId, address disputer) external; + /** * @notice Returns the default identifier used by the Optimistic Oracle V3. * @return The default identifier. @@ -167,4 +176,4 @@ interface OptimisticOracleV3Interface { ); event AdminPropertiesSet(IERC20 defaultCurrency, uint64 defaultLiveness, uint256 burnedBondPercentage); -} \ No newline at end of file +} diff --git a/contracts/mocks/OptimisticOracleV3Mock.sol b/contracts/mocks/OptimisticOracleV3Mock.sol index 3ddb4087..6e135a81 100644 --- a/contracts/mocks/OptimisticOracleV3Mock.sol +++ b/contracts/mocks/OptimisticOracleV3Mock.sol @@ -80,6 +80,10 @@ contract OptimisticOracleV3Mock is OptimisticOracleV3Interface { return (false); } + function disputeAssertion(bytes32 assertionId, address disputer) external override { + revert("Not implemented"); + } + // Mock implementation of getMinimumBond function getMinimumBond(address ) public view override returns (uint256) { return (uint256(0)); @@ -95,4 +99,4 @@ contract OptimisticOracleV3Mock is OptimisticOracleV3Interface { callbackInterface(target).assertionResolvedCallback(assertionId, truthfully); } -} \ No newline at end of file +} diff --git a/test/integration/ethereum/addresses.ts b/test/integration/ethereum/addresses.ts index 09247643..c605dca0 100644 --- a/test/integration/ethereum/addresses.ts +++ b/test/integration/ethereum/addresses.ts @@ -66,7 +66,7 @@ export const PRODUCTION_ADDRESSES = { debtIssuanceModuleV2: "0xa0a98EB7Af028BE00d04e46e1316808A62a8fd59", notionalTradeModule: "0x600d9950c6ecAef98Cc42fa207E92397A6c43416", integrationRegistry: "0xb9083dee5e8273E54B9DB4c31bA9d4aB7C6B28d3", - auctionModuleV1: "0x59D55D53a715b3B4581c52098BCb4075C2941DBa", + auctionModuleV1: "0x59D55D53a715b3B4581c52098BCb4075C2941DBa", tradeModule: "0xFaAB3F8f3678f68AA0d307B66e71b636F82C28BF", airdropModule: "0x09b9e7c7e2daf40fCb286fE6b863e517d5d5c40F", aaveV3LeverageStrategyExtension: "0x7d3f7EDD04916F3Cb2bC6740224c636B9AE43200", @@ -88,6 +88,11 @@ export const PRODUCTION_ADDRESSES = { nUpgreadableBeacon: "0xFAaF0C5B81E802C231A5249221cfe0B6ae639118", }, }, + oracles: { + uma: { + optimisticOracleV3: "0xfb55F43fB9F48F63f9269DB7Dde3BbBe1ebDC0dE", + }, + }, }; export const STAGING_ADDRESSES = structuredClone(PRODUCTION_ADDRESSES); diff --git a/test/integration/ethereum/optimisticAuctionRebalanceExtenison.spec.ts b/test/integration/ethereum/optimisticAuctionRebalanceExtenison.spec.ts index 8e8cd3d2..5bee30cf 100644 --- a/test/integration/ethereum/optimisticAuctionRebalanceExtenison.spec.ts +++ b/test/integration/ethereum/optimisticAuctionRebalanceExtenison.spec.ts @@ -4,7 +4,6 @@ import { Address, Account } from "@utils/types"; import { ADDRESS_ZERO, ZERO } from "@utils/constants"; import { OptimisticAuctionRebalanceExtension, - OptimisticOracleV3Mock, } from "@utils/contracts/index"; import { AuctionRebalanceModuleV1, @@ -17,6 +16,10 @@ import { BaseManagerV2__factory, IntegrationRegistry, IntegrationRegistry__factory, + IWETH, + IWETH__factory, + OptimisticOracleV3Interface, + OptimisticOracleV3Interface__factory, } from "../../../typechain"; import DeployHelper from "@utils/deploys"; import { impersonateAccount } from "./utils"; @@ -31,6 +34,7 @@ import { getRandomAccount, } from "@utils/index"; import { BigNumber, ContractTransaction, utils, Signer } from "ethers"; +import { ethers } from "hardhat"; import base58 from "bs58"; const expect = getWaffleExpect(); @@ -69,16 +73,16 @@ if (process.env.INTEGRATIONTEST) { let integrationRegistry: IntegrationRegistry; let priceAdapter: ConstantPriceAdapter; - - let optimisticOracleV3Mock: OptimisticOracleV3Mock; - - let optimisticOracleV3MockUpgraded: OptimisticOracleV3Mock; + let optimisticOracleV3: OptimisticOracleV3Interface; let collateralAssetAddress: string; let useAssetAllowlist: boolean; let allowedAssets: Address[]; + let weth: IWETH; + let minimumBond: BigNumber; + before(async () => { [owner, methodologist] = await getAccounts(); @@ -88,9 +92,14 @@ if (process.env.INTEGRATIONTEST) { contractAddresses.setFork.constantPriceAdapter, owner.wallet, ); - optimisticOracleV3Mock = await deployer.mocks.deployOptimisticOracleV3Mock(); - optimisticOracleV3MockUpgraded = await deployer.mocks.deployOptimisticOracleV3Mock(); - collateralAssetAddress = contractAddresses.tokens.weth; + weth = IWETH__factory.connect(contractAddresses.tokens.weth, owner.wallet); + collateralAssetAddress = weth.address; + + optimisticOracleV3 = OptimisticOracleV3Interface__factory.connect( + contractAddresses.oracles.uma.optimisticOracleV3, + owner.wallet, + ); + minimumBond = await optimisticOracleV3.getMinimumBond(collateralAssetAddress); integrationRegistry = IntegrationRegistry__factory.connect( contractAddresses.setFork.integrationRegistry, @@ -125,23 +134,26 @@ if (process.env.INTEGRATIONTEST) { addSnapshotBeforeRestoreAfterEach(); context("when auction rebalance extension is added as extension", () => { - beforeEach(async () => { await baseManager.addExtension(auctionRebalanceExtension.address); }); context("when the product settings have been set", () => { + let productSettings: any; beforeEach(async () => { - await auctionRebalanceExtension.connect(operator).setProductSettings( - { - collateral: collateralAssetAddress, - liveness: BigNumber.from(0), - bondAmount: BigNumber.from(0), - identifier: utils.formatBytes32String(""), - optimisticOracleV3: optimisticOracleV3Mock.address, - }, - utils.arrayify(base58ToHexString("Qmc5gCcjYypU7y28oCALwfSvxCBskLuPKWpK4qpterKC7z")), - ); + productSettings = { + collateral: collateralAssetAddress, + liveness: BigNumber.from(0), + bondAmount: BigNumber.from(0), + identifier: utils.formatBytes32String(""), + optimisticOracleV3: optimisticOracleV3.address, + }; + await auctionRebalanceExtension + .connect(operator) + .setProductSettings( + productSettings, + utils.arrayify(base58ToHexString("Qmc5gCcjYypU7y28oCALwfSvxCBskLuPKWpK4qpterKC7z")), + ); }); context("when a rebalance has been proposed", () => { @@ -155,6 +167,8 @@ if (process.env.INTEGRATIONTEST) { let subjectPositionMultiplier: BigNumber; let subjectCaller: Signer; beforeEach(async () => { + const effectiveBond = productSettings.bondAmount.gt(minimumBond) ? productSettings.bondAmount : minimumBond; + subjectQuoteAsset = contractAddresses.tokens.weth; subjectOldComponents = await dsEth.getComponents(); @@ -179,6 +193,16 @@ if (process.env.INTEGRATIONTEST) { subjectRebalanceDuration = BigNumber.from(86400); subjectPositionMultiplier = ether(0.999); subjectCaller = operator; + + const quantity = utils.parseEther("1000").add(effectiveBond).toHexString(); + console.log("quantity", quantity); + // set operator balance to effective bond + await ethers.provider.send("hardhat_setBalance", [ + await subjectCaller.getAddress(), + quantity, + ]); + await weth.connect(subjectCaller).deposit({ value: effectiveBond }); + await weth.connect(subjectCaller).approve(auctionRebalanceExtension.address, effectiveBond); }); describe("#startRebalance", () => { async function subject(): Promise { @@ -343,10 +367,7 @@ if (process.env.INTEGRATIONTEST) { beforeEach(async () => { const price = await priceAdapter.getEncodedData(ether(1)); const setComponents = await dsEth.getComponents(); - subjectOldComponents = [ - ...setComponents, - contractAddresses.tokens.dai, - ]; + subjectOldComponents = [...setComponents, contractAddresses.tokens.dai]; subjectOldComponentsAuctionParams = subjectOldComponents.map(() => { return { targetUnit: ether(50), @@ -389,7 +410,7 @@ if (process.env.INTEGRATIONTEST) { }); describe("assertionDisputedCallback", () => { it("should delete the proposal on a disputed callback", async () => { - await auctionRebalanceExtension + const tx = await auctionRebalanceExtension .connect(subjectCaller) .proposeRebalance( subjectQuoteAsset, @@ -401,18 +422,20 @@ if (process.env.INTEGRATIONTEST) { subjectRebalanceDuration, subjectPositionMultiplier, ); + const receipt = await tx.wait(); + // Extract AssertedClaim event + console.log("Events:", receipt.events); + const proposalId = 0; + const proposal = await auctionRebalanceExtension .connect(subjectCaller) .proposedProduct(utils.formatBytes32String("win")); expect(proposal.product).to.eq(dsEth.address); await expect( - optimisticOracleV3Mock + optimisticOracleV3 .connect(subjectCaller) - .mockAssertionDisputedCallback( - auctionRebalanceExtension.address, - utils.formatBytes32String("win"), - ), + .disputeAssertion(proposalId, owner.address), ).to.not.be.reverted; const proposalAfter = await auctionRebalanceExtension @@ -439,7 +462,7 @@ if (process.env.INTEGRATIONTEST) { liveness: BigNumber.from(0), bondAmount: BigNumber.from(0), identifier: utils.formatBytes32String(""), - optimisticOracleV3: optimisticOracleV3MockUpgraded.address, + optimisticOracleV3: optimisticOracleV3.address, }, utils.arrayify(base58ToHexString("Qmc5gCcjYypU7y28oCALwfSvxCBskLuPKWpK4qpterKC7z")), ); @@ -448,7 +471,7 @@ if (process.env.INTEGRATIONTEST) { .proposedProduct(utils.formatBytes32String("win")); expect(proposal.product).to.eq(dsEth.address); await expect( - optimisticOracleV3Mock + optimisticOracleV3 .connect(subjectCaller) .mockAssertionDisputedCallback( auctionRebalanceExtension.address, @@ -476,7 +499,7 @@ if (process.env.INTEGRATIONTEST) { subjectPositionMultiplier, ); await expect( - optimisticOracleV3Mock + optimisticOracleV3 .connect(subjectCaller) .mockAssertionResolvedCallback( auctionRebalanceExtension.address, From b0932696768a3593ba84e9bdea2e267ba2f22a41 Mon Sep 17 00:00:00 2001 From: ckoopmann Date: Mon, 18 Dec 2023 15:26:50 +0800 Subject: [PATCH 12/36] All tests passing against uma deployed oracle --- contracts/interfaces/IIdentifierWhitelist.sol | 15 ++ test/integration/ethereum/addresses.ts | 1 + ...ptimisticAuctionRebalanceExtenison.spec.ts | 142 ++++++++++++------ 3 files changed, 108 insertions(+), 50 deletions(-) create mode 100644 contracts/interfaces/IIdentifierWhitelist.sol diff --git a/contracts/interfaces/IIdentifierWhitelist.sol b/contracts/interfaces/IIdentifierWhitelist.sol new file mode 100644 index 00000000..4c7fee57 --- /dev/null +++ b/contracts/interfaces/IIdentifierWhitelist.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.4; + +interface IIdentifierWhitelist { + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + event SupportedIdentifierAdded(bytes32 indexed identifier); + event SupportedIdentifierRemoved(bytes32 indexed identifier); + + function addSupportedIdentifier(bytes32 identifier) external; + function isIdentifierSupported(bytes32 identifier) external view returns (bool); + function owner() external view returns (address); + function removeSupportedIdentifier(bytes32 identifier) external; + function renounceOwnership() external; + function transferOwnership(address newOwner) external; +} diff --git a/test/integration/ethereum/addresses.ts b/test/integration/ethereum/addresses.ts index c605dca0..4e8bcf0e 100644 --- a/test/integration/ethereum/addresses.ts +++ b/test/integration/ethereum/addresses.ts @@ -91,6 +91,7 @@ export const PRODUCTION_ADDRESSES = { oracles: { uma: { optimisticOracleV3: "0xfb55F43fB9F48F63f9269DB7Dde3BbBe1ebDC0dE", + identifierWhitelist: "0xcF649d9Da4D1362C4DAEa67573430Bd6f945e570", }, }, }; diff --git a/test/integration/ethereum/optimisticAuctionRebalanceExtenison.spec.ts b/test/integration/ethereum/optimisticAuctionRebalanceExtenison.spec.ts index 5bee30cf..ab4084bb 100644 --- a/test/integration/ethereum/optimisticAuctionRebalanceExtenison.spec.ts +++ b/test/integration/ethereum/optimisticAuctionRebalanceExtenison.spec.ts @@ -1,10 +1,9 @@ import "module-alias/register"; import { Address, Account } from "@utils/types"; +import { increaseTimeAsync } from "@utils/test"; import { ADDRESS_ZERO, ZERO } from "@utils/constants"; -import { - OptimisticAuctionRebalanceExtension, -} from "@utils/contracts/index"; +import { OptimisticAuctionRebalanceExtension } from "@utils/contracts/index"; import { AuctionRebalanceModuleV1, AuctionRebalanceModuleV1__factory, @@ -16,8 +15,11 @@ import { BaseManagerV2__factory, IntegrationRegistry, IntegrationRegistry__factory, + IIdentifierWhitelist, + IIdentifierWhitelist__factory, IWETH, IWETH__factory, + OptimisticOracleV3Mock, OptimisticOracleV3Interface, OptimisticOracleV3Interface__factory, } from "../../../typechain"; @@ -73,7 +75,11 @@ if (process.env.INTEGRATIONTEST) { let integrationRegistry: IntegrationRegistry; let priceAdapter: ConstantPriceAdapter; + + // UMA contracts let optimisticOracleV3: OptimisticOracleV3Interface; + let optimisticOracleV3Mock: OptimisticOracleV3Mock; + let identifierWhitelist: IIdentifierWhitelist; let collateralAssetAddress: string; @@ -99,6 +105,19 @@ if (process.env.INTEGRATIONTEST) { contractAddresses.oracles.uma.optimisticOracleV3, owner.wallet, ); + + optimisticOracleV3Mock = await deployer.mocks.deployOptimisticOracleV3Mock(); + + identifierWhitelist = IIdentifierWhitelist__factory.connect( + contractAddresses.oracles.uma.identifierWhitelist, + owner.wallet, + ); + const whitelistOwner = await impersonateAccount(await identifierWhitelist.owner()); + await ethers.provider.send("hardhat_setBalance", [ + await whitelistOwner.getAddress(), + ethers.utils.parseEther("10").toHexString(), + ]); + identifierWhitelist = identifierWhitelist.connect(whitelistOwner); minimumBond = await optimisticOracleV3.getMinimumBond(collateralAssetAddress); integrationRegistry = IntegrationRegistry__factory.connect( @@ -140,12 +159,17 @@ if (process.env.INTEGRATIONTEST) { context("when the product settings have been set", () => { let productSettings: any; + let identifier: string; + let liveness: BigNumber; beforeEach(async () => { + identifier = utils.formatBytes32String("TestIdentifier"); // TODO: Check how do we ensure that our identifier is supported on UMAs whitelist + await identifierWhitelist.addSupportedIdentifier(identifier); + liveness = BigNumber.from(60 * 60); // 7 days productSettings = { collateral: collateralAssetAddress, - liveness: BigNumber.from(0), + liveness, bondAmount: BigNumber.from(0), - identifier: utils.formatBytes32String(""), + identifier, optimisticOracleV3: optimisticOracleV3.address, }; await auctionRebalanceExtension @@ -166,8 +190,11 @@ if (process.env.INTEGRATIONTEST) { let subjectRebalanceDuration: BigNumber; let subjectPositionMultiplier: BigNumber; let subjectCaller: Signer; + let effectiveBond: BigNumber; beforeEach(async () => { - const effectiveBond = productSettings.bondAmount.gt(minimumBond) ? productSettings.bondAmount : minimumBond; + effectiveBond = productSettings.bondAmount.gt(minimumBond) + ? productSettings.bondAmount + : minimumBond; subjectQuoteAsset = contractAddresses.tokens.weth; @@ -194,15 +221,20 @@ if (process.env.INTEGRATIONTEST) { subjectPositionMultiplier = ether(0.999); subjectCaller = operator; - const quantity = utils.parseEther("1000").add(effectiveBond).toHexString(); + const quantity = utils + .parseEther("1000") + .add(effectiveBond) + .toHexString(); console.log("quantity", quantity); // set operator balance to effective bond await ethers.provider.send("hardhat_setBalance", [ - await subjectCaller.getAddress(), - quantity, + await subjectCaller.getAddress(), + quantity, ]); await weth.connect(subjectCaller).deposit({ value: effectiveBond }); - await weth.connect(subjectCaller).approve(auctionRebalanceExtension.address, effectiveBond); + await weth + .connect(subjectCaller) + .approve(auctionRebalanceExtension.address, effectiveBond); }); describe("#startRebalance", () => { async function subject(): Promise { @@ -218,6 +250,7 @@ if (process.env.INTEGRATIONTEST) { subjectRebalanceDuration, subjectPositionMultiplier, ); + await increaseTimeAsync(liveness.add(1)); return auctionRebalanceExtension .connect(subjectCaller) .startRebalance( @@ -397,18 +430,13 @@ if (process.env.INTEGRATIONTEST) { await expect(subject()).to.be.revertedWith("Mismatch: old and current components"); }); }); - - describe("when the caller is not the operator", () => { - beforeEach(async () => { - subjectCaller = (await getRandomAccount()).wallet; - }); - - it("should not revert", async () => { - await expect(subject()).not.to.be.reverted; - }); - }); }); describe("assertionDisputedCallback", () => { + beforeEach(async () => { + await weth.connect(subjectCaller).deposit({ value: effectiveBond }); + await weth.connect(subjectCaller).approve(optimisticOracleV3.address, effectiveBond); + }); + it("should delete the proposal on a disputed callback", async () => { const tx = await auctionRebalanceExtension .connect(subjectCaller) @@ -423,28 +451,29 @@ if (process.env.INTEGRATIONTEST) { subjectPositionMultiplier, ); const receipt = await tx.wait(); - // Extract AssertedClaim event - console.log("Events:", receipt.events); - const proposalId = 0; + + // @ts-ignore + const assertEvent = receipt.events[receipt.events.length - 1] as any; + const proposalId = assertEvent.args._assertionId; const proposal = await auctionRebalanceExtension .connect(subjectCaller) - .proposedProduct(utils.formatBytes32String("win")); + .proposedProduct(proposalId); + expect(proposal.product).to.eq(dsEth.address); - await expect( - optimisticOracleV3 - .connect(subjectCaller) - .disputeAssertion(proposalId, owner.address), - ).to.not.be.reverted; + await optimisticOracleV3 + .connect(subjectCaller) + .disputeAssertion(proposalId, owner.address); const proposalAfter = await auctionRebalanceExtension .connect(subjectCaller) .proposedProduct(utils.formatBytes32String("win")); + console.log("proposalAfter", proposalAfter); expect(proposalAfter.product).to.eq(ADDRESS_ZERO); }); it("should delete the proposal on a disputed callback from currently set oracle", async () => { - await auctionRebalanceExtension + const tx = await auctionRebalanceExtension .connect(subjectCaller) .proposeRebalance( subjectQuoteAsset, @@ -456,37 +485,51 @@ if (process.env.INTEGRATIONTEST) { subjectRebalanceDuration, subjectPositionMultiplier, ); + const receipt = await tx.wait(); await auctionRebalanceExtension.connect(operator).setProductSettings( { collateral: collateralAssetAddress, - liveness: BigNumber.from(0), + liveness, bondAmount: BigNumber.from(0), - identifier: utils.formatBytes32String(""), - optimisticOracleV3: optimisticOracleV3.address, + identifier, + optimisticOracleV3: optimisticOracleV3Mock.address, }, utils.arrayify(base58ToHexString("Qmc5gCcjYypU7y28oCALwfSvxCBskLuPKWpK4qpterKC7z")), ); + // @ts-ignore + const assertEvent = receipt.events[receipt.events.length - 1] as any; + const proposalId = assertEvent.args._assertionId; + const proposal = await auctionRebalanceExtension .connect(subjectCaller) - .proposedProduct(utils.formatBytes32String("win")); + .proposedProduct(proposalId); + expect(proposal.product).to.eq(dsEth.address); + await expect( - optimisticOracleV3 + optimisticOracleV3Mock .connect(subjectCaller) - .mockAssertionDisputedCallback( - auctionRebalanceExtension.address, - utils.formatBytes32String("win"), - ), + .mockAssertionDisputedCallback(auctionRebalanceExtension.address, proposalId), ).to.not.be.reverted; const proposalAfter = await auctionRebalanceExtension .connect(subjectCaller) - .proposedProduct(utils.formatBytes32String("win")); + .proposedProduct(proposalId); expect(proposalAfter.product).to.eq(ADDRESS_ZERO); }); }); describe("assertionResolvedCallback", () => { it("should not revert on a resolved callback", async () => { - await auctionRebalanceExtension + await auctionRebalanceExtension.connect(operator).setProductSettings( + { + collateral: collateralAssetAddress, + liveness, + bondAmount: BigNumber.from(0), + identifier, + optimisticOracleV3: optimisticOracleV3Mock.address, + }, + utils.arrayify(base58ToHexString("Qmc5gCcjYypU7y28oCALwfSvxCBskLuPKWpK4qpterKC7z")), + ); + const tx = await auctionRebalanceExtension .connect(subjectCaller) .proposeRebalance( subjectQuoteAsset, @@ -498,15 +541,14 @@ if (process.env.INTEGRATIONTEST) { subjectRebalanceDuration, subjectPositionMultiplier, ); - await expect( - optimisticOracleV3 - .connect(subjectCaller) - .mockAssertionResolvedCallback( - auctionRebalanceExtension.address, - utils.formatBytes32String("win"), - true, - ), - ).to.not.be.reverted; + const receipt = await tx.wait(); + // @ts-ignore + const assertEvent = receipt.events[receipt.events.length - 1] as any; + const proposalId = assertEvent.args._assertionId; + + await optimisticOracleV3Mock + .connect(subjectCaller) + .mockAssertionResolvedCallback(auctionRebalanceExtension.address, proposalId, true); }); }); }); From a8d67aa2dca7efa28d32136334961ee3c307ccdc Mon Sep 17 00:00:00 2001 From: ckoopmann Date: Mon, 18 Dec 2023 16:08:14 +0800 Subject: [PATCH 13/36] Set block number individually in test --- .../ethereum/optimisticAuctionRebalanceExtenison.spec.ts | 6 ++++-- utils/config.ts | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/test/integration/ethereum/optimisticAuctionRebalanceExtenison.spec.ts b/test/integration/ethereum/optimisticAuctionRebalanceExtenison.spec.ts index ab4084bb..5e324dcc 100644 --- a/test/integration/ethereum/optimisticAuctionRebalanceExtenison.spec.ts +++ b/test/integration/ethereum/optimisticAuctionRebalanceExtenison.spec.ts @@ -2,6 +2,7 @@ import "module-alias/register"; import { Address, Account } from "@utils/types"; import { increaseTimeAsync } from "@utils/test"; +import { setBlockNumber } from "@utils/test/testingUtils"; import { ADDRESS_ZERO, ZERO } from "@utils/constants"; import { OptimisticAuctionRebalanceExtension } from "@utils/contracts/index"; import { @@ -89,6 +90,9 @@ if (process.env.INTEGRATIONTEST) { let weth: IWETH; let minimumBond: BigNumber; + setBlockNumber(18789000); + + before(async () => { [owner, methodologist] = await getAccounts(); @@ -225,7 +229,6 @@ if (process.env.INTEGRATIONTEST) { .parseEther("1000") .add(effectiveBond) .toHexString(); - console.log("quantity", quantity); // set operator balance to effective bond await ethers.provider.send("hardhat_setBalance", [ await subjectCaller.getAddress(), @@ -469,7 +472,6 @@ if (process.env.INTEGRATIONTEST) { const proposalAfter = await auctionRebalanceExtension .connect(subjectCaller) .proposedProduct(utils.formatBytes32String("win")); - console.log("proposalAfter", proposalAfter); expect(proposalAfter.product).to.eq(ADDRESS_ZERO); }); it("should delete the proposal on a disputed callback from currently set oracle", async () => { diff --git a/utils/config.ts b/utils/config.ts index 505d18da..fbbc2021 100644 --- a/utils/config.ts +++ b/utils/config.ts @@ -10,7 +10,7 @@ export const optimismForkingConfig = { export const mainnetForkingConfig = { url: "https://eth-mainnet.alchemyapi.io/v2/" + process.env.ALCHEMY_TOKEN, - blockNumber: process.env.LATESTBLOCK ? undefined : 18789000, + blockNumber: process.env.LATESTBLOCK ? undefined : 17895372, }; export const forkingConfig = From bdf2861a977f6019dfd28c3344835a02998248bc Mon Sep 17 00:00:00 2001 From: ckoopmann Date: Wed, 20 Dec 2023 14:57:04 +1100 Subject: [PATCH 14/36] Add isOpen boolean gate --- .../OptimisticAuctionRebalanceExtension.sol | 19 + ...ptimisticAuctionRebalanceExtension.spec.ts | 419 ++++++------ ...ptimisticAuctionRebalanceExtenison.spec.ts | 613 +++++++++--------- 3 files changed, 556 insertions(+), 495 deletions(-) diff --git a/contracts/adapters/OptimisticAuctionRebalanceExtension.sol b/contracts/adapters/OptimisticAuctionRebalanceExtension.sol index 107331d7..2c20340f 100644 --- a/contracts/adapters/OptimisticAuctionRebalanceExtension.sol +++ b/contracts/adapters/OptimisticAuctionRebalanceExtension.sol @@ -108,6 +108,7 @@ contract OptimisticAuctionRebalanceExtension is AuctionRebalanceExtension, Asse ProductSettings public productSettings; // Mapping of set token to ProductSettings mapping(bytes32 => bytes32) public assertionIds; // Maps proposal hashes to assertionIds. mapping(bytes32 => Proposal) public proposedProduct; // Maps assertionIds to a Proposal. + bool public isOpen; // Bool indicating whether the extension is open for proposing rebalances. // Keys for assertion claim data. bytes public constant PROPOSAL_HASH_KEY = "proposalHash"; @@ -124,6 +125,13 @@ contract OptimisticAuctionRebalanceExtension is AuctionRebalanceExtension, Asse } + /* ============ Modifier ============ */ + + modifier onlyIfOpen() { + require(isOpen, "Must be open for rebalancing"); + _; + } + /* ============ External Functions ============ */ /** @@ -154,6 +162,16 @@ contract OptimisticAuctionRebalanceExtension is AuctionRebalanceExtension, Asse _updateUseAssetAllowlist(_useAssetAllowlist); } + /** + * ONLY OPERATOR: Toggle isOpen on and off. When false the extension is closed for proposing rebalances. + * when true it is open. + * + * @param _isOpen Bool indicating whether the extension is open for proposing rebalances. + */ + function updateIsOpen(bool _isOpen) external onlyOperator { + isOpen = _isOpen; + } + /** @@ -198,6 +216,7 @@ contract OptimisticAuctionRebalanceExtension is AuctionRebalanceExtension, Asse ) external onlyAllowedAssets(_newComponents) + onlyIfOpen() { bytes32 proposalHash = keccak256(abi.encode( setToken, diff --git a/test/adapters/optimisticAuctionRebalanceExtension.spec.ts b/test/adapters/optimisticAuctionRebalanceExtension.spec.ts index 887ec684..b332e28b 100644 --- a/test/adapters/optimisticAuctionRebalanceExtension.spec.ts +++ b/test/adapters/optimisticAuctionRebalanceExtension.spec.ts @@ -209,7 +209,7 @@ describe("OptimisticAuctionRebalanceExtension", () => { ); }); - context("when a rebalance has been proposed", () => { + context("When the rebalance settings are set correctly", () => { let subjectQuoteAsset: Address; let subjectOldComponents: Address[]; let subjectNewComponents: Address[]; @@ -260,9 +260,26 @@ describe("OptimisticAuctionRebalanceExtension", () => { subjectPositionMultiplier = ether(0.999); subjectCaller = operator; }); - describe("#startRebalance", () => { + + async function proposeRebalance(): Promise { + await auctionRebalanceExtension.updateIsOpen(true); + return auctionRebalanceExtension + .connect(subjectCaller.wallet) + .proposeRebalance( + subjectQuoteAsset, + subjectOldComponents, + subjectNewComponents, + subjectNewComponentsAuctionParams, + subjectOldComponentsAuctionParams, + subjectShouldLockSetToken, + subjectRebalanceDuration, + subjectPositionMultiplier, + ); + } + + describe("#proposeRebalance", () => { async function subject(): Promise { - await auctionRebalanceExtension + return auctionRebalanceExtension .connect(subjectCaller.wallet) .proposeRebalance( subjectQuoteAsset, @@ -274,6 +291,34 @@ describe("OptimisticAuctionRebalanceExtension", () => { subjectRebalanceDuration, subjectPositionMultiplier, ); + } + + context("when the extension is open for rebalance", () => { + beforeEach(async () => { + await auctionRebalanceExtension.updateIsOpen(true); + }); + it("should not revert", async () => { + await subject(); + }); + it("should update proposed products correctly", async () => { + await subject(); + const proposal = await auctionRebalanceExtension + .connect(subjectCaller.wallet) + .proposedProduct(utils.formatBytes32String("win")); + expect(proposal.product).to.eq(setToken.address); + }); + }); + context("when the extension is not open for rebalance", () => { + beforeEach(async () => { + await auctionRebalanceExtension.updateIsOpen(false); + }); + it("should revert", async () => { + expect(subject()).to.be.revertedWith("Must be open for rebalancing"); + }); + }); + }); + describe("#startRebalance", () => { + async function subject(): Promise { return auctionRebalanceExtension .connect(subjectCaller.wallet) .startRebalance( @@ -287,103 +332,6 @@ describe("OptimisticAuctionRebalanceExtension", () => { subjectPositionMultiplier, ); } - - it("should set the auction execution params correctly", async () => { - await subject(); - expect(1).to.eq(1); - - const aggregateComponents = [...subjectOldComponents, ...subjectNewComponents]; - const aggregateAuctionParams = [ - ...subjectOldComponentsAuctionParams, - ...subjectNewComponentsAuctionParams, - ]; - - for (let i = 0; i < aggregateAuctionParams.length; i++) { - const executionInfo = await setV2Setup.auctionModule.executionInfo( - setToken.address, - aggregateComponents[i], - ); - expect(executionInfo.targetUnit).to.eq(aggregateAuctionParams[i].targetUnit); - expect(executionInfo.priceAdapterName).to.eq( - aggregateAuctionParams[i].priceAdapterName, - ); - expect(executionInfo.priceAdapterConfigData).to.eq( - aggregateAuctionParams[i].priceAdapterConfigData, - ); - } - }); - - it("should set the rebalance info correctly", async () => { - const txnTimestamp = await getTransactionTimestamp(subject()); - - const rebalanceInfo = await setV2Setup.auctionModule.rebalanceInfo( - setToken.address, - ); - - expect(rebalanceInfo.quoteAsset).to.eq(subjectQuoteAsset); - expect(rebalanceInfo.rebalanceStartTime).to.eq(txnTimestamp); - expect(rebalanceInfo.rebalanceDuration).to.eq(subjectRebalanceDuration); - expect(rebalanceInfo.positionMultiplier).to.eq(subjectPositionMultiplier); - expect(rebalanceInfo.raiseTargetPercentage).to.eq(ZERO); - - const rebalanceComponents = await setV2Setup.auctionModule.getRebalanceComponents( - setToken.address, - ); - const aggregateComponents = [...subjectOldComponents, ...subjectNewComponents]; - - for (let i = 0; i < rebalanceComponents.length; i++) { - expect(rebalanceComponents[i]).to.eq(aggregateComponents[i]); - } - }); - - describe("when there are no new components", () => { - beforeEach(async () => { - subjectNewComponents = []; - subjectNewComponentsAuctionParams = []; - }); - - it("should set the auction execution params correctly", async () => { - await subject(); - - for (let i = 0; i < subjectOldComponents.length; i++) { - const executionInfo = await setV2Setup.auctionModule.executionInfo( - setToken.address, - subjectOldComponents[i], - ); - expect(executionInfo.targetUnit).to.eq( - subjectOldComponentsAuctionParams[i].targetUnit, - ); - expect(executionInfo.priceAdapterName).to.eq( - subjectOldComponentsAuctionParams[i].priceAdapterName, - ); - expect(executionInfo.priceAdapterConfigData).to.eq( - subjectOldComponentsAuctionParams[i].priceAdapterConfigData, - ); - } - }); - - it("should set the rebalance info correctly", async () => { - const txnTimestamp = await getTransactionTimestamp(subject()); - - const rebalanceInfo = await setV2Setup.auctionModule.rebalanceInfo( - setToken.address, - ); - - expect(rebalanceInfo.quoteAsset).to.eq(subjectQuoteAsset); - expect(rebalanceInfo.rebalanceStartTime).to.eq(txnTimestamp); - expect(rebalanceInfo.rebalanceDuration).to.eq(subjectRebalanceDuration); - expect(rebalanceInfo.positionMultiplier).to.eq(subjectPositionMultiplier); - expect(rebalanceInfo.raiseTargetPercentage).to.eq(ZERO); - - const rebalanceComponents = await setV2Setup.auctionModule.getRebalanceComponents( - setToken.address, - ); - for (let i = 0; i < rebalanceComponents.length; i++) { - expect(rebalanceComponents[i]).to.eq(subjectOldComponents[i]); - } - }); - }); - describe("when old components are passed in different order", () => { beforeEach(async () => { subjectOldComponents = [ @@ -391,6 +339,7 @@ describe("OptimisticAuctionRebalanceExtension", () => { setV2Setup.weth.address, setV2Setup.wbtc.address, ]; + await proposeRebalance(); }); it("should revert", async () => { @@ -415,6 +364,7 @@ describe("OptimisticAuctionRebalanceExtension", () => { priceAdapterConfigData: await priceAdapter.getEncodedData(ether(0.005)), }, ]; + await proposeRebalance(); }); it("should revert", async () => { @@ -455,6 +405,7 @@ describe("OptimisticAuctionRebalanceExtension", () => { priceAdapterConfigData: price, }, ]; + await proposeRebalance(); }); it("should revert", async () => { @@ -471,6 +422,7 @@ describe("OptimisticAuctionRebalanceExtension", () => { setV2Setup.wbtc.address, setV2Setup.usdc.address, ]; + await proposeRebalance(); }); it("should revert", async () => { @@ -479,116 +431,183 @@ describe("OptimisticAuctionRebalanceExtension", () => { ); }); }); - - describe("when the caller is not the operator", () => { + context("when the rebalance has been proposed", () => { beforeEach(async () => { - subjectCaller = await getRandomAccount(); + await proposeRebalance(); }); - it("should not revert", async () => { - await expect(subject()).not.to.be.reverted; + it("should set the auction execution params correctly", async () => { + await subject(); + expect(1).to.eq(1); + + const aggregateComponents = [...subjectOldComponents, ...subjectNewComponents]; + const aggregateAuctionParams = [ + ...subjectOldComponentsAuctionParams, + ...subjectNewComponentsAuctionParams, + ]; + + for (let i = 0; i < aggregateAuctionParams.length; i++) { + const executionInfo = await setV2Setup.auctionModule.executionInfo( + setToken.address, + aggregateComponents[i], + ); + expect(executionInfo.targetUnit).to.eq(aggregateAuctionParams[i].targetUnit); + expect(executionInfo.priceAdapterName).to.eq( + aggregateAuctionParams[i].priceAdapterName, + ); + expect(executionInfo.priceAdapterConfigData).to.eq( + aggregateAuctionParams[i].priceAdapterConfigData, + ); + } }); - }); - }); - describe("assertionDisputedCallback", () => { - it("should delete the proposal on a disputed callback", async () => { - await auctionRebalanceExtension - .connect(subjectCaller.wallet) - .proposeRebalance( - subjectQuoteAsset, - subjectOldComponents, - subjectNewComponents, - subjectNewComponentsAuctionParams, - subjectOldComponentsAuctionParams, - subjectShouldLockSetToken, - subjectRebalanceDuration, - subjectPositionMultiplier, + + it("should set the rebalance info correctly", async () => { + const txnTimestamp = await getTransactionTimestamp(subject()); + + const rebalanceInfo = await setV2Setup.auctionModule.rebalanceInfo( + setToken.address, ); - const proposal = await auctionRebalanceExtension - .connect(subjectCaller.wallet) - .proposedProduct(utils.formatBytes32String("win")); - expect(proposal.product).to.eq(setToken.address); - await expect( - optimisticOracleV3Mock - .connect(subjectCaller.wallet) - .mockAssertionDisputedCallback( - auctionRebalanceExtension.address, - utils.formatBytes32String("win"), - ), - ).to.not.be.reverted; + expect(rebalanceInfo.quoteAsset).to.eq(subjectQuoteAsset); + expect(rebalanceInfo.rebalanceStartTime).to.eq(txnTimestamp); + expect(rebalanceInfo.rebalanceDuration).to.eq(subjectRebalanceDuration); + expect(rebalanceInfo.positionMultiplier).to.eq(subjectPositionMultiplier); + expect(rebalanceInfo.raiseTargetPercentage).to.eq(ZERO); - const proposalAfter = await auctionRebalanceExtension - .connect(subjectCaller.wallet) - .proposedProduct(utils.formatBytes32String("win")); - expect(proposalAfter.product).to.eq(ADDRESS_ZERO); - }); - it("should delete the proposal on a disputed callback from currently set oracle", async () => { - await auctionRebalanceExtension - .connect(subjectCaller.wallet) - .proposeRebalance( - subjectQuoteAsset, - subjectOldComponents, - subjectNewComponents, - subjectNewComponentsAuctionParams, - subjectOldComponentsAuctionParams, - subjectShouldLockSetToken, - subjectRebalanceDuration, - subjectPositionMultiplier, + const rebalanceComponents = await setV2Setup.auctionModule.getRebalanceComponents( + setToken.address, ); - await auctionRebalanceExtension.connect(operator.wallet).setProductSettings( - { - collateral: collateralAsset.address, - liveness: BigNumber.from(0), - bondAmount: BigNumber.from(0), - identifier: utils.formatBytes32String(""), - optimisticOracleV3: optimisticOracleV3MockUpgraded.address, - }, - utils.arrayify( - base58ToHexString("Qmc5gCcjYypU7y28oCALwfSvxCBskLuPKWpK4qpterKC7z"), - ), - ); - const proposal = await auctionRebalanceExtension - .connect(subjectCaller.wallet) - .proposedProduct(utils.formatBytes32String("win")); - expect(proposal.product).to.eq(setToken.address); - await expect( - optimisticOracleV3Mock - .connect(subjectCaller.wallet) - .mockAssertionDisputedCallback( - auctionRebalanceExtension.address, - utils.formatBytes32String("win"), - ), - ).to.not.be.reverted; - const proposalAfter = await auctionRebalanceExtension - .connect(subjectCaller.wallet) - .proposedProduct(utils.formatBytes32String("win")); - expect(proposalAfter.product).to.eq(ADDRESS_ZERO); + const aggregateComponents = [...subjectOldComponents, ...subjectNewComponents]; + + for (let i = 0; i < rebalanceComponents.length; i++) { + expect(rebalanceComponents[i]).to.eq(aggregateComponents[i]); + } + }); + + describe("assertionDisputedCallback", () => { + it("should delete the proposal on a disputed callback", async () => { + const proposal = await auctionRebalanceExtension + .connect(subjectCaller.wallet) + .proposedProduct(utils.formatBytes32String("win")); + expect(proposal.product).to.eq(setToken.address); + + await expect( + optimisticOracleV3Mock + .connect(subjectCaller.wallet) + .mockAssertionDisputedCallback( + auctionRebalanceExtension.address, + utils.formatBytes32String("win"), + ), + ).to.not.be.reverted; + + const proposalAfter = await auctionRebalanceExtension + .connect(subjectCaller.wallet) + .proposedProduct(utils.formatBytes32String("win")); + expect(proposalAfter.product).to.eq(ADDRESS_ZERO); + }); + it("should delete the proposal on a disputed callback from currently set oracle", async () => { + await auctionRebalanceExtension.connect(operator.wallet).setProductSettings( + { + collateral: collateralAsset.address, + liveness: BigNumber.from(0), + bondAmount: BigNumber.from(0), + identifier: utils.formatBytes32String(""), + optimisticOracleV3: optimisticOracleV3MockUpgraded.address, + }, + utils.arrayify( + base58ToHexString("Qmc5gCcjYypU7y28oCALwfSvxCBskLuPKWpK4qpterKC7z"), + ), + ); + const proposal = await auctionRebalanceExtension + .connect(subjectCaller.wallet) + .proposedProduct(utils.formatBytes32String("win")); + expect(proposal.product).to.eq(setToken.address); + await expect( + optimisticOracleV3Mock + .connect(subjectCaller.wallet) + .mockAssertionDisputedCallback( + auctionRebalanceExtension.address, + utils.formatBytes32String("win"), + ), + ).to.not.be.reverted; + const proposalAfter = await auctionRebalanceExtension + .connect(subjectCaller.wallet) + .proposedProduct(utils.formatBytes32String("win")); + expect(proposalAfter.product).to.eq(ADDRESS_ZERO); + }); + }); + describe("assertionResolvedCallback", () => { + it("should not revert on a resolved callback", async () => { + await expect( + optimisticOracleV3Mock + .connect(subjectCaller.wallet) + .mockAssertionResolvedCallback( + auctionRebalanceExtension.address, + utils.formatBytes32String("win"), + true, + ), + ).to.not.be.reverted; + }); + }); + + describe("when the caller is not the operator", () => { + beforeEach(async () => { + subjectCaller = await getRandomAccount(); + }); + + it("should not revert", async () => { + await subject(); + }); + }); }); - }); - describe("assertionResolvedCallback", () => { - it("should not revert on a resolved callback", async () => { - await auctionRebalanceExtension - .connect(subjectCaller.wallet) - .proposeRebalance( - subjectQuoteAsset, - subjectOldComponents, - subjectNewComponents, - subjectNewComponentsAuctionParams, - subjectOldComponentsAuctionParams, - subjectShouldLockSetToken, - subjectRebalanceDuration, - subjectPositionMultiplier, + + describe("when there are no new components", () => { + beforeEach(async () => { + subjectNewComponents = []; + subjectNewComponentsAuctionParams = []; + await proposeRebalance(); + }); + + it("should set the auction execution params correctly", async () => { + await subject(); + + for (let i = 0; i < subjectOldComponents.length; i++) { + const executionInfo = await setV2Setup.auctionModule.executionInfo( + setToken.address, + subjectOldComponents[i], + ); + expect(executionInfo.targetUnit).to.eq( + subjectOldComponentsAuctionParams[i].targetUnit, + ); + expect(executionInfo.priceAdapterName).to.eq( + subjectOldComponentsAuctionParams[i].priceAdapterName, + ); + expect(executionInfo.priceAdapterConfigData).to.eq( + subjectOldComponentsAuctionParams[i].priceAdapterConfigData, + ); + } + }); + + it("should set the rebalance info correctly", async () => { + const txnTimestamp = await getTransactionTimestamp(subject()); + + const rebalanceInfo = await setV2Setup.auctionModule.rebalanceInfo( + setToken.address, ); - await expect( - optimisticOracleV3Mock - .connect(subjectCaller.wallet) - .mockAssertionResolvedCallback( - auctionRebalanceExtension.address, - utils.formatBytes32String("win"), - true, - ), - ).to.not.be.reverted; + + expect(rebalanceInfo.quoteAsset).to.eq(subjectQuoteAsset); + expect(rebalanceInfo.rebalanceStartTime).to.eq(txnTimestamp); + expect(rebalanceInfo.rebalanceDuration).to.eq(subjectRebalanceDuration); + expect(rebalanceInfo.positionMultiplier).to.eq(subjectPositionMultiplier); + expect(rebalanceInfo.raiseTargetPercentage).to.eq(ZERO); + + const rebalanceComponents = await setV2Setup.auctionModule.getRebalanceComponents( + setToken.address, + ); + for (let i = 0; i < rebalanceComponents.length; i++) { + expect(rebalanceComponents[i]).to.eq(subjectOldComponents[i]); + } + }); }); }); }); diff --git a/test/integration/ethereum/optimisticAuctionRebalanceExtenison.spec.ts b/test/integration/ethereum/optimisticAuctionRebalanceExtenison.spec.ts index 5e324dcc..ca561301 100644 --- a/test/integration/ethereum/optimisticAuctionRebalanceExtenison.spec.ts +++ b/test/integration/ethereum/optimisticAuctionRebalanceExtenison.spec.ts @@ -92,7 +92,6 @@ if (process.env.INTEGRATIONTEST) { setBlockNumber(18789000); - before(async () => { [owner, methodologist] = await getAccounts(); @@ -184,160 +183,118 @@ if (process.env.INTEGRATIONTEST) { ); }); - context("when a rebalance has been proposed", () => { - let subjectQuoteAsset: Address; - let subjectOldComponents: Address[]; - let subjectNewComponents: Address[]; - let subjectNewComponentsAuctionParams: any[]; - let subjectOldComponentsAuctionParams: any[]; - let subjectShouldLockSetToken: boolean; - let subjectRebalanceDuration: BigNumber; - let subjectPositionMultiplier: BigNumber; - let subjectCaller: Signer; - let effectiveBond: BigNumber; + context("when the extension is open to rebalances", () => { beforeEach(async () => { - effectiveBond = productSettings.bondAmount.gt(minimumBond) - ? productSettings.bondAmount - : minimumBond; - - subjectQuoteAsset = contractAddresses.tokens.weth; - - subjectOldComponents = await dsEth.getComponents(); - subjectNewComponents = [contractAddresses.tokens.USDC]; - - subjectNewComponentsAuctionParams = [ - { - targetUnit: usdc(100), - priceAdapterName: "ConstantPriceAdapter", - priceAdapterConfigData: await priceAdapter.getEncodedData(ether(0.005)), - }, - ]; - - const sellAllAuctionParam = { - targetUnit: ether(0), - priceAdapterName: "ConstantPriceAdapter", - priceAdapterConfigData: await priceAdapter.getEncodedData(ether(0.005)), - }; - subjectOldComponentsAuctionParams = subjectOldComponents.map(() => sellAllAuctionParam); - - subjectShouldLockSetToken = true; - subjectRebalanceDuration = BigNumber.from(86400); - subjectPositionMultiplier = ether(0.999); - subjectCaller = operator; - - const quantity = utils - .parseEther("1000") - .add(effectiveBond) - .toHexString(); - // set operator balance to effective bond - await ethers.provider.send("hardhat_setBalance", [ - await subjectCaller.getAddress(), - quantity, - ]); - await weth.connect(subjectCaller).deposit({ value: effectiveBond }); - await weth - .connect(subjectCaller) - .approve(auctionRebalanceExtension.address, effectiveBond); + await auctionRebalanceExtension.updateIsOpen(true); }); - describe("#startRebalance", () => { - async function subject(): Promise { - await auctionRebalanceExtension - .connect(subjectCaller) - .proposeRebalance( - subjectQuoteAsset, - subjectOldComponents, - subjectNewComponents, - subjectNewComponentsAuctionParams, - subjectOldComponentsAuctionParams, - subjectShouldLockSetToken, - subjectRebalanceDuration, - subjectPositionMultiplier, - ); - await increaseTimeAsync(liveness.add(1)); - return auctionRebalanceExtension - .connect(subjectCaller) - .startRebalance( - subjectQuoteAsset, - subjectOldComponents, - subjectNewComponents, - subjectNewComponentsAuctionParams, - subjectOldComponentsAuctionParams, - subjectShouldLockSetToken, - subjectRebalanceDuration, - subjectPositionMultiplier, - ); - } - it("should set the auction execution params correctly", async () => { - await subject(); - expect(1).to.eq(1); - - const aggregateComponents = [...subjectOldComponents, ...subjectNewComponents]; - const aggregateAuctionParams = [ - ...subjectOldComponentsAuctionParams, - ...subjectNewComponentsAuctionParams, - ]; + context("when a rebalance has been proposed", () => { + let subjectQuoteAsset: Address; + let subjectOldComponents: Address[]; + let subjectNewComponents: Address[]; + let subjectNewComponentsAuctionParams: any[]; + let subjectOldComponentsAuctionParams: any[]; + let subjectShouldLockSetToken: boolean; + let subjectRebalanceDuration: BigNumber; + let subjectPositionMultiplier: BigNumber; + let subjectCaller: Signer; + let effectiveBond: BigNumber; + beforeEach(async () => { + effectiveBond = productSettings.bondAmount.gt(minimumBond) + ? productSettings.bondAmount + : minimumBond; - for (let i = 0; i < aggregateAuctionParams.length; i++) { - const executionInfo = await auctionModule.executionInfo( - dsEth.address, - aggregateComponents[i], - ); - expect(executionInfo.targetUnit).to.eq(aggregateAuctionParams[i].targetUnit); - expect(executionInfo.priceAdapterName).to.eq( - aggregateAuctionParams[i].priceAdapterName, - ); - expect(executionInfo.priceAdapterConfigData).to.eq( - aggregateAuctionParams[i].priceAdapterConfigData, - ); - } - }); + subjectQuoteAsset = contractAddresses.tokens.weth; - it("should set the rebalance info correctly", async () => { - const txnTimestamp = await getTransactionTimestamp(subject()); + subjectOldComponents = await dsEth.getComponents(); + subjectNewComponents = [contractAddresses.tokens.USDC]; - const rebalanceInfo = await auctionModule.rebalanceInfo(dsEth.address); + subjectNewComponentsAuctionParams = [ + { + targetUnit: usdc(100), + priceAdapterName: "ConstantPriceAdapter", + priceAdapterConfigData: await priceAdapter.getEncodedData(ether(0.005)), + }, + ]; - expect(utils.getAddress(rebalanceInfo.quoteAsset)).to.eq( - utils.getAddress(subjectQuoteAsset), + const sellAllAuctionParam = { + targetUnit: ether(0), + priceAdapterName: "ConstantPriceAdapter", + priceAdapterConfigData: await priceAdapter.getEncodedData(ether(0.005)), + }; + subjectOldComponentsAuctionParams = subjectOldComponents.map( + () => sellAllAuctionParam, ); - expect(rebalanceInfo.rebalanceStartTime).to.eq(txnTimestamp); - expect(rebalanceInfo.rebalanceDuration).to.eq(subjectRebalanceDuration); - expect(rebalanceInfo.positionMultiplier).to.eq(subjectPositionMultiplier); - expect(rebalanceInfo.raiseTargetPercentage).to.eq(ZERO); - const rebalanceComponents = await auctionModule.getRebalanceComponents(dsEth.address); - const aggregateComponents = [...subjectOldComponents, ...subjectNewComponents]; - - for (let i = 0; i < rebalanceComponents.length; i++) { - expect(utils.getAddress(rebalanceComponents[i])).to.eq( - utils.getAddress(aggregateComponents[i]), - ); - } + subjectShouldLockSetToken = true; + subjectRebalanceDuration = BigNumber.from(86400); + subjectPositionMultiplier = ether(0.999); + subjectCaller = operator; + + const quantity = utils + .parseEther("1000") + .add(effectiveBond) + .toHexString(); + // set operator balance to effective bond + await ethers.provider.send("hardhat_setBalance", [ + await subjectCaller.getAddress(), + quantity, + ]); + await weth.connect(subjectCaller).deposit({ value: effectiveBond }); + await weth + .connect(subjectCaller) + .approve(auctionRebalanceExtension.address, effectiveBond); }); - - describe("when there are no new components", () => { - beforeEach(async () => { - subjectNewComponents = []; - subjectNewComponentsAuctionParams = []; - }); + describe("#startRebalance", () => { + async function subject(): Promise { + await auctionRebalanceExtension + .connect(subjectCaller) + .proposeRebalance( + subjectQuoteAsset, + subjectOldComponents, + subjectNewComponents, + subjectNewComponentsAuctionParams, + subjectOldComponentsAuctionParams, + subjectShouldLockSetToken, + subjectRebalanceDuration, + subjectPositionMultiplier, + ); + await increaseTimeAsync(liveness.add(1)); + return auctionRebalanceExtension + .connect(subjectCaller) + .startRebalance( + subjectQuoteAsset, + subjectOldComponents, + subjectNewComponents, + subjectNewComponentsAuctionParams, + subjectOldComponentsAuctionParams, + subjectShouldLockSetToken, + subjectRebalanceDuration, + subjectPositionMultiplier, + ); + } it("should set the auction execution params correctly", async () => { await subject(); + expect(1).to.eq(1); + + const aggregateComponents = [...subjectOldComponents, ...subjectNewComponents]; + const aggregateAuctionParams = [ + ...subjectOldComponentsAuctionParams, + ...subjectNewComponentsAuctionParams, + ]; - for (let i = 0; i < subjectOldComponents.length; i++) { + for (let i = 0; i < aggregateAuctionParams.length; i++) { const executionInfo = await auctionModule.executionInfo( dsEth.address, - subjectOldComponents[i], - ); - expect(executionInfo.targetUnit).to.eq( - subjectOldComponentsAuctionParams[i].targetUnit, + aggregateComponents[i], ); + expect(executionInfo.targetUnit).to.eq(aggregateAuctionParams[i].targetUnit); expect(executionInfo.priceAdapterName).to.eq( - subjectOldComponentsAuctionParams[i].priceAdapterName, + aggregateAuctionParams[i].priceAdapterName, ); expect(executionInfo.priceAdapterConfigData).to.eq( - subjectOldComponentsAuctionParams[i].priceAdapterConfigData, + aggregateAuctionParams[i].priceAdapterConfigData, ); } }); @@ -358,199 +315,265 @@ if (process.env.INTEGRATIONTEST) { const rebalanceComponents = await auctionModule.getRebalanceComponents( dsEth.address, ); + const aggregateComponents = [...subjectOldComponents, ...subjectNewComponents]; + for (let i = 0; i < rebalanceComponents.length; i++) { - expect(rebalanceComponents[i]).to.eq(subjectOldComponents[i]); + expect(utils.getAddress(rebalanceComponents[i])).to.eq( + utils.getAddress(aggregateComponents[i]), + ); } }); - }); - describe("when old components are passed in different order", () => { - beforeEach(async () => { - subjectOldComponents = [ - contractAddresses.tokens.dai, - contractAddresses.tokens.weth, - contractAddresses.tokens.wbtc, - ]; - }); + describe("when there are no new components", () => { + beforeEach(async () => { + subjectNewComponents = []; + subjectNewComponentsAuctionParams = []; + }); - it("should revert", async () => { - await expect(subject()).to.be.revertedWith("Mismatch: old and current components"); - }); - }); + it("should set the auction execution params correctly", async () => { + await subject(); + + for (let i = 0; i < subjectOldComponents.length; i++) { + const executionInfo = await auctionModule.executionInfo( + dsEth.address, + subjectOldComponents[i], + ); + expect(executionInfo.targetUnit).to.eq( + subjectOldComponentsAuctionParams[i].targetUnit, + ); + expect(executionInfo.priceAdapterName).to.eq( + subjectOldComponentsAuctionParams[i].priceAdapterName, + ); + expect(executionInfo.priceAdapterConfigData).to.eq( + subjectOldComponentsAuctionParams[i].priceAdapterConfigData, + ); + } + }); - describe("when old components array is shorter than current components array", () => { - beforeEach(async () => { - const setComponents = await dsEth.getComponents(); - subjectOldComponents = setComponents.slice(0, setComponents.length - 1); - const priceAdapterConfigData = await priceAdapter.getEncodedData(ether(0.005)); - subjectOldComponentsAuctionParams = subjectOldComponents.map(() => { - return { - targetUnit: ether(50), - priceAdapterName: "ConstantPriceAdapter", - priceAdapterConfigData, - }; + it("should set the rebalance info correctly", async () => { + const txnTimestamp = await getTransactionTimestamp(subject()); + + const rebalanceInfo = await auctionModule.rebalanceInfo(dsEth.address); + + expect(utils.getAddress(rebalanceInfo.quoteAsset)).to.eq( + utils.getAddress(subjectQuoteAsset), + ); + expect(rebalanceInfo.rebalanceStartTime).to.eq(txnTimestamp); + expect(rebalanceInfo.rebalanceDuration).to.eq(subjectRebalanceDuration); + expect(rebalanceInfo.positionMultiplier).to.eq(subjectPositionMultiplier); + expect(rebalanceInfo.raiseTargetPercentage).to.eq(ZERO); + + const rebalanceComponents = await auctionModule.getRebalanceComponents( + dsEth.address, + ); + for (let i = 0; i < rebalanceComponents.length; i++) { + expect(rebalanceComponents[i]).to.eq(subjectOldComponents[i]); + } }); }); - it("should revert", async () => { - await expect(subject()).to.be.revertedWith( - "Mismatch: old and current components length", - ); - }); - }); + describe("when old components are passed in different order", () => { + beforeEach(async () => { + subjectOldComponents = [ + contractAddresses.tokens.dai, + contractAddresses.tokens.weth, + contractAddresses.tokens.wbtc, + ]; + }); - describe("when old components array is longer than current components array", () => { - beforeEach(async () => { - const price = await priceAdapter.getEncodedData(ether(1)); - const setComponents = await dsEth.getComponents(); - subjectOldComponents = [...setComponents, contractAddresses.tokens.dai]; - subjectOldComponentsAuctionParams = subjectOldComponents.map(() => { - return { - targetUnit: ether(50), - priceAdapterName: "ConstantPriceAdapter", - priceAdapterConfigData: price, - }; + it("should revert", async () => { + await expect(subject()).to.be.revertedWith( + "Mismatch: old and current components", + ); }); }); - it("should revert", async () => { - await expect(subject()).to.be.revertedWith( - "Mismatch: old and current components length", - ); + describe("when old components array is shorter than current components array", () => { + beforeEach(async () => { + const setComponents = await dsEth.getComponents(); + subjectOldComponents = setComponents.slice(0, setComponents.length - 1); + const priceAdapterConfigData = await priceAdapter.getEncodedData(ether(0.005)); + subjectOldComponentsAuctionParams = subjectOldComponents.map(() => { + return { + targetUnit: ether(50), + priceAdapterName: "ConstantPriceAdapter", + priceAdapterConfigData, + }; + }); + }); + + it("should revert", async () => { + await expect(subject()).to.be.revertedWith( + "Mismatch: old and current components length", + ); + }); }); - }); - describe("when not all old components have an entry", () => { - beforeEach(async () => { - subjectOldComponents = [ - contractAddresses.tokens.dai, - contractAddresses.tokens.wbtc, - contractAddresses.tokens.USDC, - ]; + describe("when old components array is longer than current components array", () => { + beforeEach(async () => { + const price = await priceAdapter.getEncodedData(ether(1)); + const setComponents = await dsEth.getComponents(); + subjectOldComponents = [...setComponents, contractAddresses.tokens.dai]; + subjectOldComponentsAuctionParams = subjectOldComponents.map(() => { + return { + targetUnit: ether(50), + priceAdapterName: "ConstantPriceAdapter", + priceAdapterConfigData: price, + }; + }); + }); + + it("should revert", async () => { + await expect(subject()).to.be.revertedWith( + "Mismatch: old and current components length", + ); + }); }); - it("should revert", async () => { - await expect(subject()).to.be.revertedWith("Mismatch: old and current components"); + describe("when not all old components have an entry", () => { + beforeEach(async () => { + subjectOldComponents = [ + contractAddresses.tokens.dai, + contractAddresses.tokens.wbtc, + contractAddresses.tokens.USDC, + ]; + }); + + it("should revert", async () => { + await expect(subject()).to.be.revertedWith( + "Mismatch: old and current components", + ); + }); }); }); - }); - describe("assertionDisputedCallback", () => { - beforeEach(async () => { - await weth.connect(subjectCaller).deposit({ value: effectiveBond }); - await weth.connect(subjectCaller).approve(optimisticOracleV3.address, effectiveBond); - }); + describe("assertionDisputedCallback", () => { + beforeEach(async () => { + await weth.connect(subjectCaller).deposit({ value: effectiveBond }); + await weth + .connect(subjectCaller) + .approve(optimisticOracleV3.address, effectiveBond); + }); - it("should delete the proposal on a disputed callback", async () => { - const tx = await auctionRebalanceExtension - .connect(subjectCaller) - .proposeRebalance( - subjectQuoteAsset, - subjectOldComponents, - subjectNewComponents, - subjectNewComponentsAuctionParams, - subjectOldComponentsAuctionParams, - subjectShouldLockSetToken, - subjectRebalanceDuration, - subjectPositionMultiplier, - ); - const receipt = await tx.wait(); + it("should delete the proposal on a disputed callback", async () => { + const tx = await auctionRebalanceExtension + .connect(subjectCaller) + .proposeRebalance( + subjectQuoteAsset, + subjectOldComponents, + subjectNewComponents, + subjectNewComponentsAuctionParams, + subjectOldComponentsAuctionParams, + subjectShouldLockSetToken, + subjectRebalanceDuration, + subjectPositionMultiplier, + ); + const receipt = await tx.wait(); - // @ts-ignore - const assertEvent = receipt.events[receipt.events.length - 1] as any; - const proposalId = assertEvent.args._assertionId; + // @ts-ignore + const assertEvent = receipt.events[receipt.events.length - 1] as any; + const proposalId = assertEvent.args._assertionId; - const proposal = await auctionRebalanceExtension - .connect(subjectCaller) - .proposedProduct(proposalId); + const proposal = await auctionRebalanceExtension + .connect(subjectCaller) + .proposedProduct(proposalId); - expect(proposal.product).to.eq(dsEth.address); + expect(proposal.product).to.eq(dsEth.address); - await optimisticOracleV3 - .connect(subjectCaller) - .disputeAssertion(proposalId, owner.address); + await optimisticOracleV3 + .connect(subjectCaller) + .disputeAssertion(proposalId, owner.address); - const proposalAfter = await auctionRebalanceExtension - .connect(subjectCaller) - .proposedProduct(utils.formatBytes32String("win")); - expect(proposalAfter.product).to.eq(ADDRESS_ZERO); - }); - it("should delete the proposal on a disputed callback from currently set oracle", async () => { - const tx = await auctionRebalanceExtension - .connect(subjectCaller) - .proposeRebalance( - subjectQuoteAsset, - subjectOldComponents, - subjectNewComponents, - subjectNewComponentsAuctionParams, - subjectOldComponentsAuctionParams, - subjectShouldLockSetToken, - subjectRebalanceDuration, - subjectPositionMultiplier, + const proposalAfter = await auctionRebalanceExtension + .connect(subjectCaller) + .proposedProduct(utils.formatBytes32String("win")); + expect(proposalAfter.product).to.eq(ADDRESS_ZERO); + }); + it("should delete the proposal on a disputed callback from currently set oracle", async () => { + const tx = await auctionRebalanceExtension + .connect(subjectCaller) + .proposeRebalance( + subjectQuoteAsset, + subjectOldComponents, + subjectNewComponents, + subjectNewComponentsAuctionParams, + subjectOldComponentsAuctionParams, + subjectShouldLockSetToken, + subjectRebalanceDuration, + subjectPositionMultiplier, + ); + const receipt = await tx.wait(); + await auctionRebalanceExtension.connect(operator).setProductSettings( + { + collateral: collateralAssetAddress, + liveness, + bondAmount: BigNumber.from(0), + identifier, + optimisticOracleV3: optimisticOracleV3Mock.address, + }, + utils.arrayify( + base58ToHexString("Qmc5gCcjYypU7y28oCALwfSvxCBskLuPKWpK4qpterKC7z"), + ), ); - const receipt = await tx.wait(); - await auctionRebalanceExtension.connect(operator).setProductSettings( - { - collateral: collateralAssetAddress, - liveness, - bondAmount: BigNumber.from(0), - identifier, - optimisticOracleV3: optimisticOracleV3Mock.address, - }, - utils.arrayify(base58ToHexString("Qmc5gCcjYypU7y28oCALwfSvxCBskLuPKWpK4qpterKC7z")), - ); - // @ts-ignore - const assertEvent = receipt.events[receipt.events.length - 1] as any; - const proposalId = assertEvent.args._assertionId; + // @ts-ignore + const assertEvent = receipt.events[receipt.events.length - 1] as any; + const proposalId = assertEvent.args._assertionId; - const proposal = await auctionRebalanceExtension - .connect(subjectCaller) - .proposedProduct(proposalId); + const proposal = await auctionRebalanceExtension + .connect(subjectCaller) + .proposedProduct(proposalId); - expect(proposal.product).to.eq(dsEth.address); + expect(proposal.product).to.eq(dsEth.address); - await expect( - optimisticOracleV3Mock + await expect( + optimisticOracleV3Mock + .connect(subjectCaller) + .mockAssertionDisputedCallback(auctionRebalanceExtension.address, proposalId), + ).to.not.be.reverted; + const proposalAfter = await auctionRebalanceExtension .connect(subjectCaller) - .mockAssertionDisputedCallback(auctionRebalanceExtension.address, proposalId), - ).to.not.be.reverted; - const proposalAfter = await auctionRebalanceExtension - .connect(subjectCaller) - .proposedProduct(proposalId); - expect(proposalAfter.product).to.eq(ADDRESS_ZERO); + .proposedProduct(proposalId); + expect(proposalAfter.product).to.eq(ADDRESS_ZERO); + }); }); - }); - describe("assertionResolvedCallback", () => { - it("should not revert on a resolved callback", async () => { - await auctionRebalanceExtension.connect(operator).setProductSettings( - { - collateral: collateralAssetAddress, - liveness, - bondAmount: BigNumber.from(0), - identifier, - optimisticOracleV3: optimisticOracleV3Mock.address, - }, - utils.arrayify(base58ToHexString("Qmc5gCcjYypU7y28oCALwfSvxCBskLuPKWpK4qpterKC7z")), - ); - const tx = await auctionRebalanceExtension - .connect(subjectCaller) - .proposeRebalance( - subjectQuoteAsset, - subjectOldComponents, - subjectNewComponents, - subjectNewComponentsAuctionParams, - subjectOldComponentsAuctionParams, - subjectShouldLockSetToken, - subjectRebalanceDuration, - subjectPositionMultiplier, + describe("assertionResolvedCallback", () => { + it("should not revert on a resolved callback", async () => { + await auctionRebalanceExtension.connect(operator).setProductSettings( + { + collateral: collateralAssetAddress, + liveness, + bondAmount: BigNumber.from(0), + identifier, + optimisticOracleV3: optimisticOracleV3Mock.address, + }, + utils.arrayify( + base58ToHexString("Qmc5gCcjYypU7y28oCALwfSvxCBskLuPKWpK4qpterKC7z"), + ), ); - const receipt = await tx.wait(); - // @ts-ignore - const assertEvent = receipt.events[receipt.events.length - 1] as any; - const proposalId = assertEvent.args._assertionId; + const tx = await auctionRebalanceExtension + .connect(subjectCaller) + .proposeRebalance( + subjectQuoteAsset, + subjectOldComponents, + subjectNewComponents, + subjectNewComponentsAuctionParams, + subjectOldComponentsAuctionParams, + subjectShouldLockSetToken, + subjectRebalanceDuration, + subjectPositionMultiplier, + ); + const receipt = await tx.wait(); + // @ts-ignore + const assertEvent = receipt.events[receipt.events.length - 1] as any; + const proposalId = assertEvent.args._assertionId; - await optimisticOracleV3Mock - .connect(subjectCaller) - .mockAssertionResolvedCallback(auctionRebalanceExtension.address, proposalId, true); + await optimisticOracleV3Mock + .connect(subjectCaller) + .mockAssertionResolvedCallback( + auctionRebalanceExtension.address, + proposalId, + true, + ); + }); }); }); }); From 6beb27923a60fd810674fb4c57e2fbd11dc730b1 Mon Sep 17 00:00:00 2001 From: ckoopmann Date: Wed, 20 Dec 2023 15:06:11 +1100 Subject: [PATCH 15/36] Set isOpen to false after starting rebalance --- contracts/adapters/OptimisticAuctionRebalanceExtension.sol | 1 + test/adapters/optimisticAuctionRebalanceExtension.spec.ts | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/contracts/adapters/OptimisticAuctionRebalanceExtension.sol b/contracts/adapters/OptimisticAuctionRebalanceExtension.sol index 2c20340f..ed25641e 100644 --- a/contracts/adapters/OptimisticAuctionRebalanceExtension.sol +++ b/contracts/adapters/OptimisticAuctionRebalanceExtension.sol @@ -333,6 +333,7 @@ contract OptimisticAuctionRebalanceExtension is AuctionRebalanceExtension, Asse ); invokeManager(address(auctionModule), callData); + isOpen = false; } // Constructs the claim that will be asserted at the Optimistic Oracle V3. diff --git a/test/adapters/optimisticAuctionRebalanceExtension.spec.ts b/test/adapters/optimisticAuctionRebalanceExtension.spec.ts index b332e28b..ed6ecb46 100644 --- a/test/adapters/optimisticAuctionRebalanceExtension.spec.ts +++ b/test/adapters/optimisticAuctionRebalanceExtension.spec.ts @@ -435,6 +435,11 @@ describe("OptimisticAuctionRebalanceExtension", () => { beforeEach(async () => { await proposeRebalance(); }); + it("should set isOpen to false", async () => { + await subject(); + const isOpen = await auctionRebalanceExtension.isOpen(); + expect(isOpen).to.be.false; + }); it("should set the auction execution params correctly", async () => { await subject(); From 41a1e71967f27ead3fb8301cfae4afa32d9e2907 Mon Sep 17 00:00:00 2001 From: ckoopmann Date: Wed, 20 Dec 2023 15:09:52 +1100 Subject: [PATCH 16/36] Add onlyIfOpen modifier to startRebalance function --- contracts/adapters/OptimisticAuctionRebalanceExtension.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/contracts/adapters/OptimisticAuctionRebalanceExtension.sol b/contracts/adapters/OptimisticAuctionRebalanceExtension.sol index ed25641e..98b2b4a8 100644 --- a/contracts/adapters/OptimisticAuctionRebalanceExtension.sol +++ b/contracts/adapters/OptimisticAuctionRebalanceExtension.sol @@ -288,6 +288,7 @@ contract OptimisticAuctionRebalanceExtension is AuctionRebalanceExtension, Asse ) external override + onlyIfOpen() { bytes32 proposalHash = keccak256(abi.encode( setToken, From ebeeda9226ecc150c421f874dc9b86c6656054b1 Mon Sep 17 00:00:00 2001 From: ckoopmann Date: Wed, 20 Dec 2023 16:51:09 +1100 Subject: [PATCH 17/36] Refactor / extend integration tests --- ...ptimisticAuctionRebalanceExtenison.spec.ts | 484 +++++++----------- 1 file changed, 193 insertions(+), 291 deletions(-) diff --git a/test/integration/ethereum/optimisticAuctionRebalanceExtenison.spec.ts b/test/integration/ethereum/optimisticAuctionRebalanceExtenison.spec.ts index ca561301..6fe44157 100644 --- a/test/integration/ethereum/optimisticAuctionRebalanceExtenison.spec.ts +++ b/test/integration/ethereum/optimisticAuctionRebalanceExtenison.spec.ts @@ -247,19 +247,6 @@ if (process.env.INTEGRATIONTEST) { }); describe("#startRebalance", () => { async function subject(): Promise { - await auctionRebalanceExtension - .connect(subjectCaller) - .proposeRebalance( - subjectQuoteAsset, - subjectOldComponents, - subjectNewComponents, - subjectNewComponentsAuctionParams, - subjectOldComponentsAuctionParams, - subjectShouldLockSetToken, - subjectRebalanceDuration, - subjectPositionMultiplier, - ); - await increaseTimeAsync(liveness.add(1)); return auctionRebalanceExtension .connect(subjectCaller) .startRebalance( @@ -274,305 +261,220 @@ if (process.env.INTEGRATIONTEST) { ); } - it("should set the auction execution params correctly", async () => { - await subject(); - expect(1).to.eq(1); - - const aggregateComponents = [...subjectOldComponents, ...subjectNewComponents]; - const aggregateAuctionParams = [ - ...subjectOldComponentsAuctionParams, - ...subjectNewComponentsAuctionParams, - ]; - - for (let i = 0; i < aggregateAuctionParams.length; i++) { - const executionInfo = await auctionModule.executionInfo( - dsEth.address, - aggregateComponents[i], - ); - expect(executionInfo.targetUnit).to.eq(aggregateAuctionParams[i].targetUnit); - expect(executionInfo.priceAdapterName).to.eq( - aggregateAuctionParams[i].priceAdapterName, - ); - expect(executionInfo.priceAdapterConfigData).to.eq( - aggregateAuctionParams[i].priceAdapterConfigData, - ); - } - }); - - it("should set the rebalance info correctly", async () => { - const txnTimestamp = await getTransactionTimestamp(subject()); - - const rebalanceInfo = await auctionModule.rebalanceInfo(dsEth.address); - - expect(utils.getAddress(rebalanceInfo.quoteAsset)).to.eq( - utils.getAddress(subjectQuoteAsset), - ); - expect(rebalanceInfo.rebalanceStartTime).to.eq(txnTimestamp); - expect(rebalanceInfo.rebalanceDuration).to.eq(subjectRebalanceDuration); - expect(rebalanceInfo.positionMultiplier).to.eq(subjectPositionMultiplier); - expect(rebalanceInfo.raiseTargetPercentage).to.eq(ZERO); - - const rebalanceComponents = await auctionModule.getRebalanceComponents( - dsEth.address, - ); - const aggregateComponents = [...subjectOldComponents, ...subjectNewComponents]; - - for (let i = 0; i < rebalanceComponents.length; i++) { - expect(utils.getAddress(rebalanceComponents[i])).to.eq( - utils.getAddress(aggregateComponents[i]), - ); - } - }); - - describe("when there are no new components", () => { + context("when the rebalance has been proposed", () => { + let proposalId: string; beforeEach(async () => { - subjectNewComponents = []; - subjectNewComponentsAuctionParams = []; - }); - - it("should set the auction execution params correctly", async () => { - await subject(); - - for (let i = 0; i < subjectOldComponents.length; i++) { - const executionInfo = await auctionModule.executionInfo( - dsEth.address, - subjectOldComponents[i], - ); - expect(executionInfo.targetUnit).to.eq( - subjectOldComponentsAuctionParams[i].targetUnit, - ); - expect(executionInfo.priceAdapterName).to.eq( - subjectOldComponentsAuctionParams[i].priceAdapterName, - ); - expect(executionInfo.priceAdapterConfigData).to.eq( - subjectOldComponentsAuctionParams[i].priceAdapterConfigData, + const tx = await auctionRebalanceExtension + .connect(subjectCaller) + .proposeRebalance( + subjectQuoteAsset, + subjectOldComponents, + subjectNewComponents, + subjectNewComponentsAuctionParams, + subjectOldComponentsAuctionParams, + subjectShouldLockSetToken, + subjectRebalanceDuration, + subjectPositionMultiplier, ); - } - }); - - it("should set the rebalance info correctly", async () => { - const txnTimestamp = await getTransactionTimestamp(subject()); + const receipt = await tx.wait(); - const rebalanceInfo = await auctionModule.rebalanceInfo(dsEth.address); - - expect(utils.getAddress(rebalanceInfo.quoteAsset)).to.eq( - utils.getAddress(subjectQuoteAsset), - ); - expect(rebalanceInfo.rebalanceStartTime).to.eq(txnTimestamp); - expect(rebalanceInfo.rebalanceDuration).to.eq(subjectRebalanceDuration); - expect(rebalanceInfo.positionMultiplier).to.eq(subjectPositionMultiplier); - expect(rebalanceInfo.raiseTargetPercentage).to.eq(ZERO); - - const rebalanceComponents = await auctionModule.getRebalanceComponents( - dsEth.address, - ); - for (let i = 0; i < rebalanceComponents.length; i++) { - expect(rebalanceComponents[i]).to.eq(subjectOldComponents[i]); - } - }); - }); - - describe("when old components are passed in different order", () => { - beforeEach(async () => { - subjectOldComponents = [ - contractAddresses.tokens.dai, - contractAddresses.tokens.weth, - contractAddresses.tokens.wbtc, - ]; - }); - - it("should revert", async () => { - await expect(subject()).to.be.revertedWith( - "Mismatch: old and current components", - ); + // @ts-ignore + const assertEvent = receipt.events[receipt.events.length - 1] as any; + proposalId = assertEvent.args._assertionId; }); - }); - - describe("when old components array is shorter than current components array", () => { - beforeEach(async () => { - const setComponents = await dsEth.getComponents(); - subjectOldComponents = setComponents.slice(0, setComponents.length - 1); - const priceAdapterConfigData = await priceAdapter.getEncodedData(ether(0.005)); - subjectOldComponentsAuctionParams = subjectOldComponents.map(() => { - return { - targetUnit: ether(50), - priceAdapterName: "ConstantPriceAdapter", - priceAdapterConfigData, - }; + context("when the liveness period has passed", () => { + beforeEach(async () => { + await increaseTimeAsync(liveness.add(1)); }); - }); - it("should revert", async () => { - await expect(subject()).to.be.revertedWith( - "Mismatch: old and current components length", - ); - }); - }); + context("when the rebalance has been executed once already", () => { + beforeEach(async () => { + await auctionRebalanceExtension + .connect(subjectCaller) + .startRebalance( + subjectQuoteAsset, + subjectOldComponents, + subjectNewComponents, + subjectNewComponentsAuctionParams, + subjectOldComponentsAuctionParams, + subjectShouldLockSetToken, + subjectRebalanceDuration, + subjectPositionMultiplier, + ); + await auctionRebalanceExtension.updateIsOpen(true); + }); + it("should revert", async () => { + await expect(subject()).to.be.revertedWith("Proposal hash does not exist"); + }); + context("when identical rebalanced again but liveness has not passed", () => { + beforeEach(async () => { + // set operator balance to effective bond + await weth.connect(subjectCaller).deposit({ value: effectiveBond }); + await weth + .connect(subjectCaller) + .approve(auctionRebalanceExtension.address, effectiveBond); + await auctionRebalanceExtension + .connect(subjectCaller) + .proposeRebalance( + subjectQuoteAsset, + subjectOldComponents, + subjectNewComponents, + subjectNewComponentsAuctionParams, + subjectOldComponentsAuctionParams, + subjectShouldLockSetToken, + subjectRebalanceDuration, + subjectPositionMultiplier, + ); + }); + it("should revert", async () => { + await expect(subject()).to.be.revertedWith("Assertion not expired"); + }); + }); + }); - describe("when old components array is longer than current components array", () => { - beforeEach(async () => { - const price = await priceAdapter.getEncodedData(ether(1)); - const setComponents = await dsEth.getComponents(); - subjectOldComponents = [...setComponents, contractAddresses.tokens.dai]; - subjectOldComponentsAuctionParams = subjectOldComponents.map(() => { - return { - targetUnit: ether(50), - priceAdapterName: "ConstantPriceAdapter", - priceAdapterConfigData: price, - }; + it("should set the auction execution params correctly", async () => { + await subject(); + expect(1).to.eq(1); + + const aggregateComponents = [...subjectOldComponents, ...subjectNewComponents]; + const aggregateAuctionParams = [ + ...subjectOldComponentsAuctionParams, + ...subjectNewComponentsAuctionParams, + ]; + + for (let i = 0; i < aggregateAuctionParams.length; i++) { + const executionInfo = await auctionModule.executionInfo( + dsEth.address, + aggregateComponents[i], + ); + expect(executionInfo.targetUnit).to.eq(aggregateAuctionParams[i].targetUnit); + expect(executionInfo.priceAdapterName).to.eq( + aggregateAuctionParams[i].priceAdapterName, + ); + expect(executionInfo.priceAdapterConfigData).to.eq( + aggregateAuctionParams[i].priceAdapterConfigData, + ); + } }); - }); - it("should revert", async () => { - await expect(subject()).to.be.revertedWith( - "Mismatch: old and current components length", - ); - }); - }); + it("should set the rebalance info correctly", async () => { + const txnTimestamp = await getTransactionTimestamp(subject()); - describe("when not all old components have an entry", () => { - beforeEach(async () => { - subjectOldComponents = [ - contractAddresses.tokens.dai, - contractAddresses.tokens.wbtc, - contractAddresses.tokens.USDC, - ]; - }); + const rebalanceInfo = await auctionModule.rebalanceInfo(dsEth.address); - it("should revert", async () => { - await expect(subject()).to.be.revertedWith( - "Mismatch: old and current components", - ); - }); - }); - }); - describe("assertionDisputedCallback", () => { - beforeEach(async () => { - await weth.connect(subjectCaller).deposit({ value: effectiveBond }); - await weth - .connect(subjectCaller) - .approve(optimisticOracleV3.address, effectiveBond); - }); + expect(utils.getAddress(rebalanceInfo.quoteAsset)).to.eq( + utils.getAddress(subjectQuoteAsset), + ); + expect(rebalanceInfo.rebalanceStartTime).to.eq(txnTimestamp); + expect(rebalanceInfo.rebalanceDuration).to.eq(subjectRebalanceDuration); + expect(rebalanceInfo.positionMultiplier).to.eq(subjectPositionMultiplier); + expect(rebalanceInfo.raiseTargetPercentage).to.eq(ZERO); - it("should delete the proposal on a disputed callback", async () => { - const tx = await auctionRebalanceExtension - .connect(subjectCaller) - .proposeRebalance( - subjectQuoteAsset, - subjectOldComponents, - subjectNewComponents, - subjectNewComponentsAuctionParams, - subjectOldComponentsAuctionParams, - subjectShouldLockSetToken, - subjectRebalanceDuration, - subjectPositionMultiplier, - ); - const receipt = await tx.wait(); + const rebalanceComponents = await auctionModule.getRebalanceComponents( + dsEth.address, + ); + const aggregateComponents = [...subjectOldComponents, ...subjectNewComponents]; - // @ts-ignore - const assertEvent = receipt.events[receipt.events.length - 1] as any; - const proposalId = assertEvent.args._assertionId; + for (let i = 0; i < rebalanceComponents.length; i++) { + expect(utils.getAddress(rebalanceComponents[i])).to.eq( + utils.getAddress(aggregateComponents[i]), + ); + } + }); + }); - const proposal = await auctionRebalanceExtension - .connect(subjectCaller) - .proposedProduct(proposalId); + describe("assertionDisputedCallback", () => { + it("should delete the proposal on a disputed callback", async () => { + const proposal = await auctionRebalanceExtension + .connect(subjectCaller) + .proposedProduct(proposalId); - expect(proposal.product).to.eq(dsEth.address); + expect(proposal.product).to.eq(dsEth.address); - await optimisticOracleV3 - .connect(subjectCaller) - .disputeAssertion(proposalId, owner.address); + await weth.connect(subjectCaller).deposit({ value: effectiveBond }); + await weth.connect(subjectCaller).approve(optimisticOracleV3.address, effectiveBond); + await optimisticOracleV3 + .connect(subjectCaller) + .disputeAssertion(proposalId, owner.address); - const proposalAfter = await auctionRebalanceExtension - .connect(subjectCaller) - .proposedProduct(utils.formatBytes32String("win")); - expect(proposalAfter.product).to.eq(ADDRESS_ZERO); + const proposalAfter = await auctionRebalanceExtension + .connect(subjectCaller) + .proposedProduct(utils.formatBytes32String("win")); + expect(proposalAfter.product).to.eq(ADDRESS_ZERO); + }); + it("should delete the proposal on a disputed callback from currently set oracle", async () => { + await auctionRebalanceExtension.connect(operator).setProductSettings( + { + collateral: collateralAssetAddress, + liveness, + bondAmount: BigNumber.from(0), + identifier, + optimisticOracleV3: optimisticOracleV3Mock.address, + }, + utils.arrayify( + base58ToHexString("Qmc5gCcjYypU7y28oCALwfSvxCBskLuPKWpK4qpterKC7z"), + ), + ); + const proposal = await auctionRebalanceExtension + .connect(subjectCaller) + .proposedProduct(proposalId); + + expect(proposal.product).to.eq(dsEth.address); + + await expect( + optimisticOracleV3Mock + .connect(subjectCaller) + .mockAssertionDisputedCallback( + auctionRebalanceExtension.address, + proposalId, + ), + ).to.not.be.reverted; + const proposalAfter = await auctionRebalanceExtension + .connect(subjectCaller) + .proposedProduct(proposalId); + expect(proposalAfter.product).to.eq(ADDRESS_ZERO); + }); + }); }); - it("should delete the proposal on a disputed callback from currently set oracle", async () => { - const tx = await auctionRebalanceExtension - .connect(subjectCaller) - .proposeRebalance( - subjectQuoteAsset, - subjectOldComponents, - subjectNewComponents, - subjectNewComponentsAuctionParams, - subjectOldComponentsAuctionParams, - subjectShouldLockSetToken, - subjectRebalanceDuration, - subjectPositionMultiplier, + describe("assertionResolvedCallback", () => { + it("should not revert on a resolved callback", async () => { + await auctionRebalanceExtension.connect(operator).setProductSettings( + { + collateral: collateralAssetAddress, + liveness, + bondAmount: BigNumber.from(0), + identifier, + optimisticOracleV3: optimisticOracleV3Mock.address, + }, + utils.arrayify( + base58ToHexString("Qmc5gCcjYypU7y28oCALwfSvxCBskLuPKWpK4qpterKC7z"), + ), ); - const receipt = await tx.wait(); - await auctionRebalanceExtension.connect(operator).setProductSettings( - { - collateral: collateralAssetAddress, - liveness, - bondAmount: BigNumber.from(0), - identifier, - optimisticOracleV3: optimisticOracleV3Mock.address, - }, - utils.arrayify( - base58ToHexString("Qmc5gCcjYypU7y28oCALwfSvxCBskLuPKWpK4qpterKC7z"), - ), - ); - // @ts-ignore - const assertEvent = receipt.events[receipt.events.length - 1] as any; - const proposalId = assertEvent.args._assertionId; - - const proposal = await auctionRebalanceExtension - .connect(subjectCaller) - .proposedProduct(proposalId); - - expect(proposal.product).to.eq(dsEth.address); - - await expect( - optimisticOracleV3Mock + const tx = await auctionRebalanceExtension .connect(subjectCaller) - .mockAssertionDisputedCallback(auctionRebalanceExtension.address, proposalId), - ).to.not.be.reverted; - const proposalAfter = await auctionRebalanceExtension - .connect(subjectCaller) - .proposedProduct(proposalId); - expect(proposalAfter.product).to.eq(ADDRESS_ZERO); - }); - }); - describe("assertionResolvedCallback", () => { - it("should not revert on a resolved callback", async () => { - await auctionRebalanceExtension.connect(operator).setProductSettings( - { - collateral: collateralAssetAddress, - liveness, - bondAmount: BigNumber.from(0), - identifier, - optimisticOracleV3: optimisticOracleV3Mock.address, - }, - utils.arrayify( - base58ToHexString("Qmc5gCcjYypU7y28oCALwfSvxCBskLuPKWpK4qpterKC7z"), - ), - ); - const tx = await auctionRebalanceExtension - .connect(subjectCaller) - .proposeRebalance( - subjectQuoteAsset, - subjectOldComponents, - subjectNewComponents, - subjectNewComponentsAuctionParams, - subjectOldComponentsAuctionParams, - subjectShouldLockSetToken, - subjectRebalanceDuration, - subjectPositionMultiplier, - ); - const receipt = await tx.wait(); - // @ts-ignore - const assertEvent = receipt.events[receipt.events.length - 1] as any; - const proposalId = assertEvent.args._assertionId; + .proposeRebalance( + subjectQuoteAsset, + subjectOldComponents, + subjectNewComponents, + subjectNewComponentsAuctionParams, + subjectOldComponentsAuctionParams, + subjectShouldLockSetToken, + subjectRebalanceDuration, + subjectPositionMultiplier, + ); + const receipt = await tx.wait(); + // @ts-ignore + const assertEvent = receipt.events[receipt.events.length - 1] as any; + const proposalId = assertEvent.args._assertionId; - await optimisticOracleV3Mock - .connect(subjectCaller) - .mockAssertionResolvedCallback( - auctionRebalanceExtension.address, - proposalId, - true, - ); + await optimisticOracleV3Mock + .connect(subjectCaller) + .mockAssertionResolvedCallback( + auctionRebalanceExtension.address, + proposalId, + true, + ); + }); }); }); }); From 75b71075279f1b05aff50f955869ebd8352e0160 Mon Sep 17 00:00:00 2001 From: ckoopmann Date: Wed, 20 Dec 2023 17:00:24 +1100 Subject: [PATCH 18/36] Disable locking set token --- contracts/adapters/OptimisticAuctionRebalanceExtension.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contracts/adapters/OptimisticAuctionRebalanceExtension.sol b/contracts/adapters/OptimisticAuctionRebalanceExtension.sol index 98b2b4a8..5187a809 100644 --- a/contracts/adapters/OptimisticAuctionRebalanceExtension.sol +++ b/contracts/adapters/OptimisticAuctionRebalanceExtension.sol @@ -200,7 +200,7 @@ contract OptimisticAuctionRebalanceExtension is AuctionRebalanceExtension, Asse * @param _newComponentsAuctionParams AuctionExecutionParams for new components, indexed corresponding to _newComponents. * @param _oldComponentsAuctionParams AuctionExecutionParams for existing components, indexed corresponding to * the current component positions. Set to 0 for components being removed. - * @param _shouldLockSetToken Indicates if the rebalance should lock the SetToken. + * @param _shouldLockSetToken Indicates if the rebalance should lock the SetToken. Must be false in this version. * @param _rebalanceDuration Duration of the rebalance in seconds. * @param _positionMultiplier Position multiplier at the time target units were calculated. */ @@ -218,6 +218,7 @@ contract OptimisticAuctionRebalanceExtension is AuctionRebalanceExtension, Asse onlyAllowedAssets(_newComponents) onlyIfOpen() { + require(_shouldLockSetToken == false, "_shouldLockSetToken must be false"); bytes32 proposalHash = keccak256(abi.encode( setToken, _quoteAsset, From 9577f49fd6cdd88d24852c50b0d38d0825738a09 Mon Sep 17 00:00:00 2001 From: ckoopmann Date: Wed, 20 Dec 2023 17:08:28 +1100 Subject: [PATCH 19/36] Adjust tests --- .../adapters/AuctionRebalanceExtension.sol | 2 +- .../optimisticAuctionRebalanceExtension.spec.ts | 17 +++++++++++++---- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/contracts/adapters/AuctionRebalanceExtension.sol b/contracts/adapters/AuctionRebalanceExtension.sol index 0c86ac8a..7025bcea 100644 --- a/contracts/adapters/AuctionRebalanceExtension.sol +++ b/contracts/adapters/AuctionRebalanceExtension.sol @@ -73,7 +73,7 @@ contract AuctionRebalanceExtension is BaseExtension { * @param _newComponentsAuctionParams AuctionExecutionParams for new components, indexed corresponding to _newComponents. * @param _oldComponentsAuctionParams AuctionExecutionParams for existing components, indexed corresponding to * the current component positions. Set to 0 for components being removed. - * @param _shouldLockSetToken Indicates if the rebalance should lock the SetToken. + * @param _shouldLockSetToken Indicates if the rebalance should lock the SetToken. Has to be false in this version * @param _rebalanceDuration Duration of the rebalance in seconds. * @param _positionMultiplier Position multiplier at the time target units were calculated. */ diff --git a/test/adapters/optimisticAuctionRebalanceExtension.spec.ts b/test/adapters/optimisticAuctionRebalanceExtension.spec.ts index ed6ecb46..6fe99a9d 100644 --- a/test/adapters/optimisticAuctionRebalanceExtension.spec.ts +++ b/test/adapters/optimisticAuctionRebalanceExtension.spec.ts @@ -255,7 +255,7 @@ describe("OptimisticAuctionRebalanceExtension", () => { }, ]; - subjectShouldLockSetToken = true; + subjectShouldLockSetToken = false; subjectRebalanceDuration = BigNumber.from(86400); subjectPositionMultiplier = ether(0.999); subjectCaller = operator; @@ -307,6 +307,15 @@ describe("OptimisticAuctionRebalanceExtension", () => { .proposedProduct(utils.formatBytes32String("win")); expect(proposal.product).to.eq(setToken.address); }); + + context("when trying to lock set token", () => { + beforeEach(() => { + subjectShouldLockSetToken = true; + }); + it("should revert", async () => { + await expect(subject()).to.be.revertedWith("_shouldLockSetToken must be false"); + }); + }); }); context("when the extension is not open for rebalance", () => { beforeEach(async () => { @@ -436,9 +445,9 @@ describe("OptimisticAuctionRebalanceExtension", () => { await proposeRebalance(); }); it("should set isOpen to false", async () => { - await subject(); - const isOpen = await auctionRebalanceExtension.isOpen(); - expect(isOpen).to.be.false; + await subject(); + const isOpen = await auctionRebalanceExtension.isOpen(); + expect(isOpen).to.be.false; }); it("should set the auction execution params correctly", async () => { From 77b876c0664495853d4aa22604624e32e732babd Mon Sep 17 00:00:00 2001 From: ckoopmann Date: Wed, 20 Dec 2023 17:27:07 +1100 Subject: [PATCH 20/36] Add V1 suffix --- ...sol => OptimisticAuctionRebalanceExtensionV1.sol} | 3 ++- ...=> optimisticAuctionRebalanceExtensionV1.spec.ts} | 12 ++++++------ ...=> optimisticAuctionRebalanceExtenisonV1.spec.ts} | 8 ++++---- utils/contracts/index.ts | 2 +- utils/deploys/deployExtensions.ts | 10 +++++----- 5 files changed, 18 insertions(+), 17 deletions(-) rename contracts/adapters/{OptimisticAuctionRebalanceExtension.sol => OptimisticAuctionRebalanceExtensionV1.sol} (98%) rename test/adapters/{optimisticAuctionRebalanceExtension.spec.ts => optimisticAuctionRebalanceExtensionV1.spec.ts} (99%) rename test/integration/ethereum/{optimisticAuctionRebalanceExtenison.spec.ts => optimisticAuctionRebalanceExtenisonV1.spec.ts} (99%) diff --git a/contracts/adapters/OptimisticAuctionRebalanceExtension.sol b/contracts/adapters/OptimisticAuctionRebalanceExtensionV1.sol similarity index 98% rename from contracts/adapters/OptimisticAuctionRebalanceExtension.sol rename to contracts/adapters/OptimisticAuctionRebalanceExtensionV1.sol index 5187a809..1a90509c 100644 --- a/contracts/adapters/OptimisticAuctionRebalanceExtension.sol +++ b/contracts/adapters/OptimisticAuctionRebalanceExtensionV1.sol @@ -38,8 +38,9 @@ import {OptimisticOracleV3Interface} from "../interfaces/OptimisticOracleV3Inter * * @dev The contract extends `BaseAuctionRebalanceExtension` by adding an optimistic oracle mechanism for validating rules on the proposing and executing of rebalances. * It allows setting product-specific parameters for optimistic rebalancing and includes callback functions for resolved or disputed assertions. + * @dev Version 1 is characterised by: Optional Asset Whitelist, Disabled Set Token locking, control over rebalance timing via "isOpen" flag */ -contract OptimisticAuctionRebalanceExtension is AuctionRebalanceExtension, AssetAllowList { +contract OptimisticAuctionRebalanceExtensionV1 is AuctionRebalanceExtension, AssetAllowList { using AddressArrayUtils for address[]; using SafeERC20 for IERC20; diff --git a/test/adapters/optimisticAuctionRebalanceExtension.spec.ts b/test/adapters/optimisticAuctionRebalanceExtensionV1.spec.ts similarity index 99% rename from test/adapters/optimisticAuctionRebalanceExtension.spec.ts rename to test/adapters/optimisticAuctionRebalanceExtensionV1.spec.ts index 6fe99a9d..71c5e0f3 100644 --- a/test/adapters/optimisticAuctionRebalanceExtension.spec.ts +++ b/test/adapters/optimisticAuctionRebalanceExtensionV1.spec.ts @@ -5,7 +5,7 @@ import { ADDRESS_ZERO, ZERO } from "@utils/constants"; import { BaseManager, ConstantPriceAdapter, - OptimisticAuctionRebalanceExtension, + OptimisticAuctionRebalanceExtensionV1, OptimisticOracleV3Mock, StandardTokenMock, } from "@utils/contracts/index"; @@ -46,7 +46,7 @@ function base58ToHexString(base58String: string) { return "0x" + hexString; } -describe("OptimisticAuctionRebalanceExtension", () => { +describe("OptimisticAuctionRebalanceExtensionV1", () => { let owner: Account; let methodologist: Account; let operator: Account; @@ -57,7 +57,7 @@ describe("OptimisticAuctionRebalanceExtension", () => { let setToken: SetToken; let baseManager: BaseManager; - let auctionRebalanceExtension: OptimisticAuctionRebalanceExtension; + let auctionRebalanceExtension: OptimisticAuctionRebalanceExtensionV1; let priceAdapter: ConstantPriceAdapter; @@ -108,7 +108,7 @@ describe("OptimisticAuctionRebalanceExtension", () => { baseManager = baseManager.connect(operator.wallet); await setToken.setManager(baseManager.address); - auctionRebalanceExtension = await deployer.extensions.deployOptimisticAuctionRebalanceExtension( + auctionRebalanceExtension = await deployer.extensions.deployOptimisticAuctionRebalanceExtensionV1( baseManager.address, setV2Setup.auctionModule.address, useAssetAllowlist, @@ -132,8 +132,8 @@ describe("OptimisticAuctionRebalanceExtension", () => { subjectAllowedAssets = []; }); - function subject(): Promise { - return deployer.extensions.deployOptimisticAuctionRebalanceExtension( + function subject(): Promise { + return deployer.extensions.deployOptimisticAuctionRebalanceExtensionV1( subjectBaseManager, subjectAuctionRebalanceModule, subjectUseAssetAllowlist, diff --git a/test/integration/ethereum/optimisticAuctionRebalanceExtenison.spec.ts b/test/integration/ethereum/optimisticAuctionRebalanceExtenisonV1.spec.ts similarity index 99% rename from test/integration/ethereum/optimisticAuctionRebalanceExtenison.spec.ts rename to test/integration/ethereum/optimisticAuctionRebalanceExtenisonV1.spec.ts index 6fe44157..b374e33f 100644 --- a/test/integration/ethereum/optimisticAuctionRebalanceExtenison.spec.ts +++ b/test/integration/ethereum/optimisticAuctionRebalanceExtenisonV1.spec.ts @@ -4,7 +4,7 @@ import { Address, Account } from "@utils/types"; import { increaseTimeAsync } from "@utils/test"; import { setBlockNumber } from "@utils/test/testingUtils"; import { ADDRESS_ZERO, ZERO } from "@utils/constants"; -import { OptimisticAuctionRebalanceExtension } from "@utils/contracts/index"; +import { OptimisticAuctionRebalanceExtensionV1 } from "@utils/contracts/index"; import { AuctionRebalanceModuleV1, AuctionRebalanceModuleV1__factory, @@ -61,7 +61,7 @@ function base58ToHexString(base58String: string) { } if (process.env.INTEGRATIONTEST) { - describe("OptimisticAuctionRebalanceExtension - Integration Test dsEth", () => { + describe("OptimisticAuctionRebalanceExtensionV1 - Integration Test dsEth", () => { const contractAddresses = PRODUCTION_ADDRESSES; let owner: Account; let methodologist: Account; @@ -72,7 +72,7 @@ if (process.env.INTEGRATIONTEST) { let baseManager: BaseManagerV2; let auctionModule: AuctionRebalanceModuleV1; - let auctionRebalanceExtension: OptimisticAuctionRebalanceExtension; + let auctionRebalanceExtension: OptimisticAuctionRebalanceExtensionV1; let integrationRegistry: IntegrationRegistry; let priceAdapter: ConstantPriceAdapter; @@ -144,7 +144,7 @@ if (process.env.INTEGRATIONTEST) { operator = await impersonateAccount(await baseManager.operator()); baseManager = baseManager.connect(operator); - auctionRebalanceExtension = await deployer.extensions.deployOptimisticAuctionRebalanceExtension( + auctionRebalanceExtension = await deployer.extensions.deployOptimisticAuctionRebalanceExtensionV1( baseManager.address, auctionModule.address, useAssetAllowlist, diff --git a/utils/contracts/index.ts b/utils/contracts/index.ts index 1f4decbf..15a791cc 100644 --- a/utils/contracts/index.ts +++ b/utils/contracts/index.ts @@ -65,5 +65,5 @@ export { GlobalWrapExtension } from "../../typechain/GlobalWrapExtension"; export { GlobalClaimExtension } from "../../typechain/GlobalClaimExtension"; export { GlobalAuctionRebalanceExtension } from "../../typechain/GlobalAuctionRebalanceExtension"; export { GlobalOptimisticAuctionRebalanceExtension } from "../../typechain/GlobalOptimisticAuctionRebalanceExtension"; -export { OptimisticAuctionRebalanceExtension } from "../../typechain/OptimisticAuctionRebalanceExtension"; +export { OptimisticAuctionRebalanceExtensionV1 } from "../../typechain/OptimisticAuctionRebalanceExtensionV1"; export { OptimisticOracleV3Mock } from "../../typechain/OptimisticOracleV3Mock"; diff --git a/utils/deploys/deployExtensions.ts b/utils/deploys/deployExtensions.ts index 3ed7a336..c64d1c99 100644 --- a/utils/deploys/deployExtensions.ts +++ b/utils/deploys/deployExtensions.ts @@ -24,7 +24,7 @@ import { FeeSplitExtension, GIMExtension, GovernanceExtension, - OptimisticAuctionRebalanceExtension, + OptimisticAuctionRebalanceExtensionV1, StreamingFeeSplitExtension, WrapExtension, } from "../contracts/index"; @@ -53,7 +53,7 @@ import { FlexibleLeverageStrategyExtension__factory } from "../../typechain/fact import { GIMExtension__factory } from "../../typechain/factories/GIMExtension__factory"; import { GovernanceExtension__factory } from "../../typechain/factories/GovernanceExtension__factory"; import { FixedRebalanceExtension__factory } from "../../typechain/factories/FixedRebalanceExtension__factory"; -import { OptimisticAuctionRebalanceExtension__factory } from "../../typechain/factories/OptimisticAuctionRebalanceExtension__factory"; +import { OptimisticAuctionRebalanceExtensionV1__factory } from "../../typechain/factories/OptimisticAuctionRebalanceExtensionV1__factory"; import { StakeWiseReinvestmentExtension__factory } from "../../typechain/factories/StakeWiseReinvestmentExtension__factory"; import { StreamingFeeSplitExtension__factory } from "../../typechain/factories/StreamingFeeSplitExtension__factory"; import { WrapExtension__factory } from "../../typechain/factories/WrapExtension__factory"; @@ -520,13 +520,13 @@ export default class DeployExtensions { ); } - public async deployOptimisticAuctionRebalanceExtension( + public async deployOptimisticAuctionRebalanceExtensionV1( baseManager: Address, auctionModule: Address, useAssetAllowlist: boolean, allowedAssets: Address[], - ): Promise { - return await new OptimisticAuctionRebalanceExtension__factory(this._deployerSigner).deploy({ + ): Promise { + return await new OptimisticAuctionRebalanceExtensionV1__factory(this._deployerSigner).deploy({ baseManager, auctionModule, useAssetAllowlist, From ba40dae30c5f1b556f0cfbdcc5ce067c3cca3e25 Mon Sep 17 00:00:00 2001 From: ckoopmann Date: Wed, 20 Dec 2023 17:45:19 +1100 Subject: [PATCH 21/36] Remove shouldLockSetToken argument entirely from propose method --- .../OptimisticAuctionRebalanceExtensionV1.sol | 12 ++++-------- .../optimisticAuctionRebalanceExtensionV1.spec.ts | 11 ----------- .../optimisticAuctionRebalanceExtenisonV1.spec.ts | 5 +---- 3 files changed, 5 insertions(+), 23 deletions(-) diff --git a/contracts/adapters/OptimisticAuctionRebalanceExtensionV1.sol b/contracts/adapters/OptimisticAuctionRebalanceExtensionV1.sol index 1a90509c..d5221f15 100644 --- a/contracts/adapters/OptimisticAuctionRebalanceExtensionV1.sol +++ b/contracts/adapters/OptimisticAuctionRebalanceExtensionV1.sol @@ -38,7 +38,7 @@ import {OptimisticOracleV3Interface} from "../interfaces/OptimisticOracleV3Inter * * @dev The contract extends `BaseAuctionRebalanceExtension` by adding an optimistic oracle mechanism for validating rules on the proposing and executing of rebalances. * It allows setting product-specific parameters for optimistic rebalancing and includes callback functions for resolved or disputed assertions. - * @dev Version 1 is characterised by: Optional Asset Whitelist, Disabled Set Token locking, control over rebalance timing via "isOpen" flag + * @dev Version 1 is characterised by: Optional Asset Whitelist, No Set Token locking, control over rebalance timing via "isOpen" flag */ contract OptimisticAuctionRebalanceExtensionV1 is AuctionRebalanceExtension, AssetAllowList { using AddressArrayUtils for address[]; @@ -59,7 +59,6 @@ contract OptimisticAuctionRebalanceExtensionV1 is AuctionRebalanceExtension, As address[] newComponents, AuctionExecutionParams[] newComponentsAuctionParams, AuctionExecutionParams[] oldComponentsAuctionParams, - bool shouldLockSetToken, uint256 rebalanceDuration, uint256 positionMultiplier ); @@ -201,7 +200,6 @@ contract OptimisticAuctionRebalanceExtensionV1 is AuctionRebalanceExtension, As * @param _newComponentsAuctionParams AuctionExecutionParams for new components, indexed corresponding to _newComponents. * @param _oldComponentsAuctionParams AuctionExecutionParams for existing components, indexed corresponding to * the current component positions. Set to 0 for components being removed. - * @param _shouldLockSetToken Indicates if the rebalance should lock the SetToken. Must be false in this version. * @param _rebalanceDuration Duration of the rebalance in seconds. * @param _positionMultiplier Position multiplier at the time target units were calculated. */ @@ -211,7 +209,6 @@ contract OptimisticAuctionRebalanceExtensionV1 is AuctionRebalanceExtension, As address[] memory _newComponents, AuctionExecutionParams[] memory _newComponentsAuctionParams, AuctionExecutionParams[] memory _oldComponentsAuctionParams, - bool _shouldLockSetToken, uint256 _rebalanceDuration, uint256 _positionMultiplier ) @@ -219,7 +216,6 @@ contract OptimisticAuctionRebalanceExtensionV1 is AuctionRebalanceExtension, As onlyAllowedAssets(_newComponents) onlyIfOpen() { - require(_shouldLockSetToken == false, "_shouldLockSetToken must be false"); bytes32 proposalHash = keccak256(abi.encode( setToken, _quoteAsset, @@ -227,7 +223,7 @@ contract OptimisticAuctionRebalanceExtensionV1 is AuctionRebalanceExtension, As _newComponents, _newComponentsAuctionParams, _oldComponentsAuctionParams, - _shouldLockSetToken, + false, // We don't allow locking the set token in this version _rebalanceDuration, _positionMultiplier )); @@ -257,7 +253,7 @@ contract OptimisticAuctionRebalanceExtensionV1 is AuctionRebalanceExtension, As product: setToken }); - emit RebalanceProposed( setToken, _quoteAsset, _oldComponents, _newComponents, _newComponentsAuctionParams, _oldComponentsAuctionParams, _shouldLockSetToken, _rebalanceDuration, _positionMultiplier); + emit RebalanceProposed( setToken, _quoteAsset, _oldComponents, _newComponents, _newComponentsAuctionParams, _oldComponentsAuctionParams, _rebalanceDuration, _positionMultiplier); emit AssertedClaim(setToken, msg.sender, productSettings.rulesHash, assertionId, claim); } @@ -274,7 +270,7 @@ contract OptimisticAuctionRebalanceExtensionV1 is AuctionRebalanceExtension, As * @param _newComponentsAuctionParams AuctionExecutionParams for new components, indexed corresponding to _newComponents. * @param _oldComponentsAuctionParams AuctionExecutionParams for existing components, indexed corresponding to * the current component positions. Set to 0 for components being removed. - * @param _shouldLockSetToken Indicates if the rebalance should lock the SetToken. + * @param _shouldLockSetToken Indicates if the rebalance should lock the SetToken. Has to be false in this version * @param _rebalanceDuration Duration of the rebalance in seconds. * @param _positionMultiplier Position multiplier at the time target units were calculated. */ diff --git a/test/adapters/optimisticAuctionRebalanceExtensionV1.spec.ts b/test/adapters/optimisticAuctionRebalanceExtensionV1.spec.ts index 71c5e0f3..f02627e8 100644 --- a/test/adapters/optimisticAuctionRebalanceExtensionV1.spec.ts +++ b/test/adapters/optimisticAuctionRebalanceExtensionV1.spec.ts @@ -271,7 +271,6 @@ describe("OptimisticAuctionRebalanceExtensionV1", () => { subjectNewComponents, subjectNewComponentsAuctionParams, subjectOldComponentsAuctionParams, - subjectShouldLockSetToken, subjectRebalanceDuration, subjectPositionMultiplier, ); @@ -287,7 +286,6 @@ describe("OptimisticAuctionRebalanceExtensionV1", () => { subjectNewComponents, subjectNewComponentsAuctionParams, subjectOldComponentsAuctionParams, - subjectShouldLockSetToken, subjectRebalanceDuration, subjectPositionMultiplier, ); @@ -307,15 +305,6 @@ describe("OptimisticAuctionRebalanceExtensionV1", () => { .proposedProduct(utils.formatBytes32String("win")); expect(proposal.product).to.eq(setToken.address); }); - - context("when trying to lock set token", () => { - beforeEach(() => { - subjectShouldLockSetToken = true; - }); - it("should revert", async () => { - await expect(subject()).to.be.revertedWith("_shouldLockSetToken must be false"); - }); - }); }); context("when the extension is not open for rebalance", () => { beforeEach(async () => { diff --git a/test/integration/ethereum/optimisticAuctionRebalanceExtenisonV1.spec.ts b/test/integration/ethereum/optimisticAuctionRebalanceExtenisonV1.spec.ts index b374e33f..b2fc934d 100644 --- a/test/integration/ethereum/optimisticAuctionRebalanceExtenisonV1.spec.ts +++ b/test/integration/ethereum/optimisticAuctionRebalanceExtenisonV1.spec.ts @@ -226,7 +226,7 @@ if (process.env.INTEGRATIONTEST) { () => sellAllAuctionParam, ); - subjectShouldLockSetToken = true; + subjectShouldLockSetToken = false; subjectRebalanceDuration = BigNumber.from(86400); subjectPositionMultiplier = ether(0.999); subjectCaller = operator; @@ -272,7 +272,6 @@ if (process.env.INTEGRATIONTEST) { subjectNewComponents, subjectNewComponentsAuctionParams, subjectOldComponentsAuctionParams, - subjectShouldLockSetToken, subjectRebalanceDuration, subjectPositionMultiplier, ); @@ -321,7 +320,6 @@ if (process.env.INTEGRATIONTEST) { subjectNewComponents, subjectNewComponentsAuctionParams, subjectOldComponentsAuctionParams, - subjectShouldLockSetToken, subjectRebalanceDuration, subjectPositionMultiplier, ); @@ -458,7 +456,6 @@ if (process.env.INTEGRATIONTEST) { subjectNewComponents, subjectNewComponentsAuctionParams, subjectOldComponentsAuctionParams, - subjectShouldLockSetToken, subjectRebalanceDuration, subjectPositionMultiplier, ); From 84672e01b969b8253e273494326d6da078a5ec9d Mon Sep 17 00:00:00 2001 From: ckoopmann Date: Wed, 3 Jan 2024 12:45:04 +1100 Subject: [PATCH 22/36] Add events for updating the isOpen parameter --- .../OptimisticAuctionRebalanceExtensionV1.sol | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/contracts/adapters/OptimisticAuctionRebalanceExtensionV1.sol b/contracts/adapters/OptimisticAuctionRebalanceExtensionV1.sol index d5221f15..668acb53 100644 --- a/contracts/adapters/OptimisticAuctionRebalanceExtensionV1.sol +++ b/contracts/adapters/OptimisticAuctionRebalanceExtensionV1.sol @@ -74,7 +74,11 @@ contract OptimisticAuctionRebalanceExtensionV1 is AuctionRebalanceExtension, As event ProposalDeleted( bytes32 assertionID, Proposal indexed proposal - ); + ); + + event IsOpenUpdated( + bool indexed isOpen + ); /* ============ Structs ============ */ @@ -169,11 +173,9 @@ contract OptimisticAuctionRebalanceExtensionV1 is AuctionRebalanceExtension, As * @param _isOpen Bool indicating whether the extension is open for proposing rebalances. */ function updateIsOpen(bool _isOpen) external onlyOperator { - isOpen = _isOpen; + _updateIsOpen(_isOpen); } - - /** * @dev OPERATOR ONLY: sets product settings for a given set token * @param _optimisticParams OptimisticRebalanceParams struct containing optimistic rebalance parameters. @@ -332,7 +334,7 @@ contract OptimisticAuctionRebalanceExtensionV1 is AuctionRebalanceExtension, As ); invokeManager(address(auctionModule), callData); - isOpen = false; + _updateIsOpen(false); } // Constructs the claim that will be asserted at the Optimistic Oracle V3. @@ -403,4 +405,9 @@ contract OptimisticAuctionRebalanceExtensionV1 is AuctionRebalanceExtension, As delete proposedProduct[assertionId]; } + function _updateIsOpen(bool _isOpen) internal { + isOpen = _isOpen; + emit IsOpenUpdated(_isOpen); + } + } From 83356e63c36e635b903e5d248e374b77b956fb83 Mon Sep 17 00:00:00 2001 From: pblivin0x <84149824+pblivin0x@users.noreply.github.com> Date: Thu, 4 Jan 2024 02:45:43 -0500 Subject: [PATCH 23/36] chore(edits): UMA Extension Edits (#157) * Add events for updating the isOpen parameter * lintoor * remove .only() * lintoor * fix AssetAllowList nits * Run only new integration tests * Fix unittests * Add tests for require statements in proposeRebalance * Add tests for emitted events * Extend event emission test * Adjust integration test to use index token as proposer collateral (currently failing) * integration test test * cleanups * edit integration tests * Test bond transfer and claim construction * Fix unittest --------- Co-authored-by: ckoopmann --- .../OptimisticAuctionRebalanceExtensionV1.sol | 121 +++++----- contracts/lib/AssetAllowList.sol | 16 +- .../BoundedStepwiseLinearPriceAdapter.json | 206 ++++++++++++++++++ .../set/BoundedStepwiseLinearPriceAdapter.sol | 169 ++++++++++++++ ...imisticAuctionRebalanceExtensionV1.spec.ts | 200 ++++++++++++++--- ...ptimisticAuctionRebalanceExtension.spec.ts | 22 +- test/integration/ethereum/addresses.ts | 4 + ...imisticAuctionRebalanceExtenisonV1.spec.ts | 198 +++++++++++------ test/manager/baseManagerV2.spec.ts | 4 +- utils/common/conversionUtils.ts | 21 +- utils/common/index.ts | 4 +- utils/contracts/setV2.ts | 1 + 12 files changed, 785 insertions(+), 181 deletions(-) create mode 100644 external/abi/set/BoundedStepwiseLinearPriceAdapter.json create mode 100644 external/contracts/set/BoundedStepwiseLinearPriceAdapter.sol diff --git a/contracts/adapters/OptimisticAuctionRebalanceExtensionV1.sol b/contracts/adapters/OptimisticAuctionRebalanceExtensionV1.sol index d5221f15..447a69d9 100644 --- a/contracts/adapters/OptimisticAuctionRebalanceExtensionV1.sol +++ b/contracts/adapters/OptimisticAuctionRebalanceExtensionV1.sol @@ -20,23 +20,22 @@ pragma solidity 0.6.10; pragma experimental "ABIEncoderV2"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; +import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; import { AddressArrayUtils } from "../lib/AddressArrayUtils.sol"; - +import { AncillaryData } from "../lib/AncillaryData.sol"; +import { AssetAllowList } from "../lib/AssetAllowList.sol"; +import { AuctionRebalanceExtension } from "./AuctionRebalanceExtension.sol"; import { IAuctionRebalanceModuleV1 } from "../interfaces/IAuctionRebalanceModuleV1.sol"; import { IBaseManager } from "../interfaces/IBaseManager.sol"; import { ISetToken } from "../interfaces/ISetToken.sol"; -import {AuctionRebalanceExtension} from "./AuctionRebalanceExtension.sol"; -import {AncillaryData } from "../lib/AncillaryData.sol"; -import { AssetAllowList } from "../lib/AssetAllowList.sol"; -import {OptimisticOracleV3Interface} from "../interfaces/OptimisticOracleV3Interface.sol"; +import { OptimisticOracleV3Interface } from "../interfaces/OptimisticOracleV3Interface.sol"; /** - * @title BaseOptimisticAuctionRebalanceExtension + * @title OptimisticAuctionRebalanceExtension * @author Index Coop * - * @dev The contract extends `BaseAuctionRebalanceExtension` by adding an optimistic oracle mechanism for validating rules on the proposing and executing of rebalances. + * @dev The contract extends `AuctionRebalanceExtension` by adding an optimistic oracle mechanism for validating rules on the proposing and executing of rebalances. * It allows setting product-specific parameters for optimistic rebalancing and includes callback functions for resolved or disputed assertions. * @dev Version 1 is characterised by: Optional Asset Whitelist, No Set Token locking, control over rebalance timing via "isOpen" flag */ @@ -52,6 +51,7 @@ contract OptimisticAuctionRebalanceExtensionV1 is AuctionRebalanceExtension, As OptimisticRebalanceParams optimisticParams, bytes32 indexed rulesHash ); + event RebalanceProposed( ISetToken indexed setToken, IERC20 indexed quoteAsset, @@ -74,7 +74,11 @@ contract OptimisticAuctionRebalanceExtensionV1 is AuctionRebalanceExtension, As event ProposalDeleted( bytes32 assertionID, Proposal indexed proposal - ); + ); + + event IsOpenUpdated( + bool indexed isOpen + ); /* ============ Structs ============ */ @@ -114,16 +118,20 @@ contract OptimisticAuctionRebalanceExtensionV1 is AuctionRebalanceExtension, As bytes public constant PROPOSAL_HASH_KEY = "proposalHash"; bytes public constant RULES_KEY = "rulesIPFSHash"; - /* ============ Constructor ============ */ + /* - * @dev Initializes the BaseOptimisticAuctionRebalanceExtension with the passed parameters. + * @dev Initializes the OptimisticAuctionRebalanceExtension with the passed parameters. * * @param _auctionParams AuctionExtensionParams struct containing the baseManager and auctionModule addresses. */ - constructor(AuctionExtensionParams memory _auctionParams) public AuctionRebalanceExtension(_auctionParams.baseManager, _auctionParams.auctionModule) AssetAllowList(_auctionParams.allowedAssets, _auctionParams.useAssetAllowlist) { - - } + constructor( + AuctionExtensionParams memory _auctionParams + ) + public + AuctionRebalanceExtension(_auctionParams.baseManager, _auctionParams.auctionModule) + AssetAllowList(_auctionParams.allowedAssets, _auctionParams.useAssetAllowlist) + { } /* ============ Modifier ============ */ @@ -135,7 +143,7 @@ contract OptimisticAuctionRebalanceExtensionV1 is AuctionRebalanceExtension, As /* ============ External Functions ============ */ /** - * ONLY OPERATOR: Add new asset(s) that can be traded to, wrapped to, or claimed + * ONLY OPERATOR: Add new asset(s) that can be included as new components in rebalances * * @param _assets New asset(s) to add */ @@ -144,7 +152,7 @@ contract OptimisticAuctionRebalanceExtensionV1 is AuctionRebalanceExtension, As } /** - * ONLY OPERATOR: Remove asset(s) so that it/they can't be traded to, wrapped to, or claimed + * ONLY OPERATOR: Remove asset(s) so that it/they can't be included as new components in rebalances * * @param _assets Asset(s) to remove */ @@ -163,22 +171,20 @@ contract OptimisticAuctionRebalanceExtensionV1 is AuctionRebalanceExtension, As } /** - * ONLY OPERATOR: Toggle isOpen on and off. When false the extension is closed for proposing rebalances. - * when true it is open. - * - * @param _isOpen Bool indicating whether the extension is open for proposing rebalances. - */ + * ONLY OPERATOR: Toggle isOpen on and off. When false the extension is closed for proposing rebalances. + * when true it is open. + * + * @param _isOpen Bool indicating whether the extension is open for proposing rebalances. + */ function updateIsOpen(bool _isOpen) external onlyOperator { - isOpen = _isOpen; + _updateIsOpen(_isOpen); } - - /** - * @dev OPERATOR ONLY: sets product settings for a given set token - * @param _optimisticParams OptimisticRebalanceParams struct containing optimistic rebalance parameters. - * @param _rulesHash bytes32 containing the ipfs hash rules for the product. - */ + * @dev OPERATOR ONLY: sets product settings for a given set token + * @param _optimisticParams OptimisticRebalanceParams struct containing optimistic rebalance parameters. + * @param _rulesHash bytes32 containing the ipfs hash rules for the product. + */ function setProductSettings( OptimisticRebalanceParams memory _optimisticParams, bytes32 _rulesHash @@ -190,10 +196,13 @@ contract OptimisticAuctionRebalanceExtensionV1 is AuctionRebalanceExtension, As optimisticParams: _optimisticParams, rulesHash: _rulesHash }); + emit ProductSettingsUpdated(setToken, setToken.manager(), _optimisticParams, _rulesHash); } - /** + /** + * @dev IF OPEN ONLY: Proposes a rebalance for the SetToken using the Optimistic Oracle V3. + * * @param _quoteAsset ERC20 token used as the quote asset in auctions. * @param _oldComponents Addresses of existing components in the SetToken. * @param _newComponents Addresses of new components to be added. @@ -202,7 +211,7 @@ contract OptimisticAuctionRebalanceExtensionV1 is AuctionRebalanceExtension, As * the current component positions. Set to 0 for components being removed. * @param _rebalanceDuration Duration of the rebalance in seconds. * @param _positionMultiplier Position multiplier at the time target units were calculated. - */ + */ function proposeRebalance( IERC20 _quoteAsset, address[] memory _oldComponents, @@ -231,7 +240,6 @@ contract OptimisticAuctionRebalanceExtensionV1 is AuctionRebalanceExtension, As require(productSettings.rulesHash != bytes32(""), "Rules not set"); require(address(productSettings.optimisticParams.optimisticOracleV3) != address(0), "Oracle not set"); - bytes memory claim = _constructClaim(proposalHash, productSettings.rulesHash); uint256 totalBond = _pullBond(productSettings.optimisticParams); @@ -255,7 +263,6 @@ contract OptimisticAuctionRebalanceExtensionV1 is AuctionRebalanceExtension, As emit RebalanceProposed( setToken, _quoteAsset, _oldComponents, _newComponents, _newComponentsAuctionParams, _oldComponentsAuctionParams, _rebalanceDuration, _positionMultiplier); emit AssertedClaim(setToken, msg.sender, productSettings.rulesHash, assertionId, claim); - } /** @@ -332,20 +339,7 @@ contract OptimisticAuctionRebalanceExtensionV1 is AuctionRebalanceExtension, As ); invokeManager(address(auctionModule), callData); - isOpen = false; - } - - // Constructs the claim that will be asserted at the Optimistic Oracle V3. - function _constructClaim(bytes32 proposalHash, bytes32 rulesHash) internal pure returns (bytes memory) { - return - abi.encodePacked( - AncillaryData.appendKeyValueBytes32("", PROPOSAL_HASH_KEY, proposalHash), - ",", - RULES_KEY, - ":\"", - rulesHash, - "\"" - ); + _updateIsOpen(false); } /** @@ -380,6 +374,30 @@ contract OptimisticAuctionRebalanceExtensionV1 is AuctionRebalanceExtension, As emit ProposalDeleted(_assertionId, proposal); } + /* ============ Internal Functions ============ */ + + // Constructs the claim that will be asserted at the Optimistic Oracle V3. + function _constructClaim(bytes32 proposalHash, bytes32 rulesHash) internal pure returns (bytes memory) { + return + abi.encodePacked( + AncillaryData.appendKeyValueBytes32("", PROPOSAL_HASH_KEY, proposalHash), + ",", + RULES_KEY, + ":\"", + rulesHash, + "\"" + ); + } + + /// @notice Delete an existing proposal and associated assertionId. + /// @dev Internal function that deletes a proposal and associated assertionId. + /// @param assertionId assertionId of the proposal to delete. + function _deleteProposal(bytes32 assertionId) internal { + Proposal memory proposal = proposedProduct[assertionId]; + delete assertionIds[proposal.proposalHash]; + delete proposedProduct[assertionId]; + } + /// @notice Pulls the higher of the minimum bond or configured bond amount from the sender. /// @dev Internal function to pull the user's bond before asserting a claim. /// @param optimisticRebalanceParams optimistic rebalance parameters for the product. @@ -394,13 +412,8 @@ contract OptimisticAuctionRebalanceExtensionV1 is AuctionRebalanceExtension, As return totalBond; } - /// @notice Delete an existing proposal and associated assertionId. - /// @dev Internal function that deletes a proposal and associated assertionId. - /// @param assertionId assertionId of the proposal to delete. - function _deleteProposal(bytes32 assertionId) internal { - Proposal memory proposal = proposedProduct[assertionId]; - delete assertionIds[proposal.proposalHash]; - delete proposedProduct[assertionId]; + function _updateIsOpen(bool _isOpen) internal { + isOpen = _isOpen; + emit IsOpenUpdated(_isOpen); } - } diff --git a/contracts/lib/AssetAllowList.sol b/contracts/lib/AssetAllowList.sol index 4cf56c7e..845f4e69 100644 --- a/contracts/lib/AssetAllowList.sol +++ b/contracts/lib/AssetAllowList.sol @@ -1,5 +1,5 @@ /* - Copyright 2020 Set Labs Inc. + Copyright 2023 Index Coop Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -20,17 +20,14 @@ pragma solidity 0.6.10; import { AddressArrayUtils } from "./AddressArrayUtils.sol"; /** - * @title ModuleBase - * @author Set Protocol - * - * Abstract class that houses common Module-related state and functions. - * - * CHANGELOG: - * - 4/21/21: Delegated modifier logic to internal helpers to reduce contract size + * @title AssetAllowList + * @author Index Coop * + * Abstract contract that allows inheriting contracts to restrict the assets that can be traded to, wrapped to, or claimed */ abstract contract AssetAllowList { using AddressArrayUtils for address[]; + /* ============ Events ============ */ event AllowedAssetAdded( @@ -61,7 +58,7 @@ abstract contract AssetAllowList { modifier onlyAllowedAssets(address[] memory _assets) { require( _areAllowedAssets(_assets), - "BaseOptimisticAuctionRebalanceExtension.onlyAllowedAssets: Invalid asset" + "Invalid asset" ); _; } @@ -148,4 +145,3 @@ abstract contract AssetAllowList { return true; } } - diff --git a/external/abi/set/BoundedStepwiseLinearPriceAdapter.json b/external/abi/set/BoundedStepwiseLinearPriceAdapter.json new file mode 100644 index 00000000..25087d2b --- /dev/null +++ b/external/abi/set/BoundedStepwiseLinearPriceAdapter.json @@ -0,0 +1,206 @@ +{ + "_format": "hh-sol-artifact-1", + "contractName": "BoundedStepwiseLinearPriceAdapter", + "sourceName": "contracts/protocol/integration/auction-price/BoundedStepwiseLinearPriceAdapter.sol", + "abi": [ + { + "inputs": [ + { + "internalType": "uint256", + "name": "_initialPrice", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_slope", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_bucketSize", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_maxPrice", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_minPrice", + "type": "uint256" + } + ], + "name": "areParamsValid", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "pure", + "type": "function", + "gas": "0xa7d8c0" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "_data", + "type": "bytes" + } + ], + "name": "getDecodedData", + "outputs": [ + { + "internalType": "uint256", + "name": "initialPrice", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "slope", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "bucketSize", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "isDecreasing", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "maxPrice", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minPrice", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function", + "gas": "0xa7d8c0" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_initialPrice", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_slope", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_bucketSize", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "_isDecreasing", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "_maxPrice", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_minPrice", + "type": "uint256" + } + ], + "name": "getEncodedData", + "outputs": [ + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "stateMutability": "pure", + "type": "function", + "gas": "0xa7d8c0" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_timeElapsed", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "_priceAdapterConfigData", + "type": "bytes" + } + ], + "name": "getPrice", + "outputs": [ + { + "internalType": "uint256", + "name": "price", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function", + "gas": "0xa7d8c0" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "_priceAdapterConfigData", + "type": "bytes" + } + ], + "name": "isPriceAdapterConfigDataValid", + "outputs": [ + { + "internalType": "bool", + "name": "isValid", + "type": "bool" + } + ], + "stateMutability": "pure", + "type": "function", + "gas": "0xa7d8c0" + } + ], + "bytecode": "0x608060405234801561001057600080fd5b506106d0806100206000396000f3fe608060405234801561001057600080fd5b50600436106100575760003560e01c8063079f6bf41461005c578063104b9b641461008457806363dc2e6b146100a5578063bf160e2514610105578063d3516db614610118575b600080fd5b61006f61006a36600461040a565b61015a565b60405190151581526020015b60405180910390f35b610097610092366004610463565b61018f565b60405190815260200161007b565b6100f86100b33660046104ee565b604080516020810197909752868101959095526060860193909352901515608085015260a084015260c0808401919091528151808403909101815260e0909201905290565b60405161007b9190610541565b61006f61011336600461058f565b6102fa565b61012b61012636600461040a565b610336565b6040805196875260208701959095529385019290925215156060840152608083015260a082015260c00161007b565b60008060008060008061016c87610336565b955095505094509450945061018485858585856102fa565b979650505050505050565b60008060008060008060006101a388610336565b9550955095509550955095506101bc86868685856102fa565b6102265760405162461bcd60e51b815260206004820152603160248201527f426f756e64656453746570776973654c696e6561725072696365416461707465604482015270723a20496e76616c696420706172616d7360781b606482015260840160405180910390fd5b6000610232858c6105e0565b9050610240866000196105e0565b81111561026257836102525782610254565b815b9750505050505050506102f0565b600061026e8783610602565b905084156102b5578781111561028e5782985050505050505050506102f0565b6102a661029b828a61061f565b848110818618021890565b985050505050505050506102f0565b6102c18860001961061f565b8111156102d85783985050505050505050506102f0565b6102a66102e5828a610632565b858111818718021890565b9695505050505050565b6000808611801561030b5750600085115b80156103175750600084115b80156103235750828611155b80156102f0575050909310159392505050565b600080600080600080868060200190518101906103539190610645565b949c939b5091995097509550909350915050565b634e487b7160e01b600052604160045260246000fd5b600082601f83011261038e57600080fd5b813567ffffffffffffffff808211156103a9576103a9610367565b604051601f8301601f19908116603f011681019082821181831017156103d1576103d1610367565b816040528381528660208588010111156103ea57600080fd5b836020870160208301376000602085830101528094505050505092915050565b60006020828403121561041c57600080fd5b813567ffffffffffffffff81111561043357600080fd5b61043f8482850161037d565b949350505050565b80356001600160a01b038116811461045e57600080fd5b919050565b60008060008060008060c0878903121561047c57600080fd5b61048587610447565b955061049360208801610447565b945060408701359350606087013592506080870135915060a087013567ffffffffffffffff8111156104c457600080fd5b6104d089828a0161037d565b9150509295509295509295565b80151581146104eb57600080fd5b50565b60008060008060008060c0878903121561050757600080fd5b8635955060208701359450604087013593506060870135610527816104dd565b9598949750929560808101359460a0909101359350915050565b600060208083528351808285015260005b8181101561056e57858101830151858201604001528201610552565b506000604082860101526040601f19601f8301168501019250505092915050565b600080600080600060a086880312156105a757600080fd5b505083359560208501359550604085013594606081013594506080013592509050565b634e487b7160e01b600052601160045260246000fd5b6000826105fd57634e487b7160e01b600052601260045260246000fd5b500490565b8082028115828204841417610619576106196105ca565b92915050565b81810381811115610619576106196105ca565b80820180821115610619576106196105ca565b60008060008060008060c0878903121561065e57600080fd5b865195506020870151945060408701519350606087015161067e816104dd565b809350506080870151915060a08701519050929550929550929556fea2646970667358221220a50447f4d241ad3f0e8ac061ae0c04c474f1f7c3a7a5cab954e568dac556890e64736f6c63430008110033", + "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100575760003560e01c8063079f6bf41461005c578063104b9b641461008457806363dc2e6b146100a5578063bf160e2514610105578063d3516db614610118575b600080fd5b61006f61006a36600461040a565b61015a565b60405190151581526020015b60405180910390f35b610097610092366004610463565b61018f565b60405190815260200161007b565b6100f86100b33660046104ee565b604080516020810197909752868101959095526060860193909352901515608085015260a084015260c0808401919091528151808403909101815260e0909201905290565b60405161007b9190610541565b61006f61011336600461058f565b6102fa565b61012b61012636600461040a565b610336565b6040805196875260208701959095529385019290925215156060840152608083015260a082015260c00161007b565b60008060008060008061016c87610336565b955095505094509450945061018485858585856102fa565b979650505050505050565b60008060008060008060006101a388610336565b9550955095509550955095506101bc86868685856102fa565b6102265760405162461bcd60e51b815260206004820152603160248201527f426f756e64656453746570776973654c696e6561725072696365416461707465604482015270723a20496e76616c696420706172616d7360781b606482015260840160405180910390fd5b6000610232858c6105e0565b9050610240866000196105e0565b81111561026257836102525782610254565b815b9750505050505050506102f0565b600061026e8783610602565b905084156102b5578781111561028e5782985050505050505050506102f0565b6102a661029b828a61061f565b848110818618021890565b985050505050505050506102f0565b6102c18860001961061f565b8111156102d85783985050505050505050506102f0565b6102a66102e5828a610632565b858111818718021890565b9695505050505050565b6000808611801561030b5750600085115b80156103175750600084115b80156103235750828611155b80156102f0575050909310159392505050565b600080600080600080868060200190518101906103539190610645565b949c939b5091995097509550909350915050565b634e487b7160e01b600052604160045260246000fd5b600082601f83011261038e57600080fd5b813567ffffffffffffffff808211156103a9576103a9610367565b604051601f8301601f19908116603f011681019082821181831017156103d1576103d1610367565b816040528381528660208588010111156103ea57600080fd5b836020870160208301376000602085830101528094505050505092915050565b60006020828403121561041c57600080fd5b813567ffffffffffffffff81111561043357600080fd5b61043f8482850161037d565b949350505050565b80356001600160a01b038116811461045e57600080fd5b919050565b60008060008060008060c0878903121561047c57600080fd5b61048587610447565b955061049360208801610447565b945060408701359350606087013592506080870135915060a087013567ffffffffffffffff8111156104c457600080fd5b6104d089828a0161037d565b9150509295509295509295565b80151581146104eb57600080fd5b50565b60008060008060008060c0878903121561050757600080fd5b8635955060208701359450604087013593506060870135610527816104dd565b9598949750929560808101359460a0909101359350915050565b600060208083528351808285015260005b8181101561056e57858101830151858201604001528201610552565b506000604082860101526040601f19601f8301168501019250505092915050565b600080600080600060a086880312156105a757600080fd5b505083359560208501359550604085013594606081013594506080013592509050565b634e487b7160e01b600052601160045260246000fd5b6000826105fd57634e487b7160e01b600052601260045260246000fd5b500490565b8082028115828204841417610619576106196105ca565b92915050565b81810381811115610619576106196105ca565b80820180821115610619576106196105ca565b60008060008060008060c0878903121561065e57600080fd5b865195506020870151945060408701519350606087015161067e816104dd565b809350506080870151915060a08701519050929550929550929556fea2646970667358221220a50447f4d241ad3f0e8ac061ae0c04c474f1f7c3a7a5cab954e568dac556890e64736f6c63430008110033", + "linkReferences": {}, + "deployedLinkReferences": {} +} diff --git a/external/contracts/set/BoundedStepwiseLinearPriceAdapter.sol b/external/contracts/set/BoundedStepwiseLinearPriceAdapter.sol new file mode 100644 index 00000000..23be78fb --- /dev/null +++ b/external/contracts/set/BoundedStepwiseLinearPriceAdapter.sol @@ -0,0 +1,169 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.17; + +import { FixedPointMathLib } from "solady/src/utils/FixedPointMathLib.sol"; + +/** + * @title BoundedStepwiseLinearPriceAdapter + * @author Index Coop + * @notice Price adapter contract for the AuctionRebalanceModuleV1. It returns a price that + * increases or decreases linearly in steps over time, within a bounded range. + * The rate of change is constant. + * Price formula: price = initialPrice +/- slope * timeBucket + */ +contract BoundedStepwiseLinearPriceAdapter { + + /** + * @dev Calculates and returns the linear price. + * + * @param _timeElapsed Time elapsed since the start of the auction. + * @param _priceAdapterConfigData Encoded bytes representing the linear function parameters. + * + * @return price The price calculated using the linear function. + */ + function getPrice( + address /* _setToken */, + address /* _component */, + uint256 /* _componentQuantity */, + uint256 _timeElapsed, + uint256 /* _duration */, + bytes memory _priceAdapterConfigData + ) + external + pure + returns (uint256 price) + { + ( + uint256 initialPrice, + uint256 slope, + uint256 bucketSize, + bool isDecreasing, + uint256 maxPrice, + uint256 minPrice + ) = getDecodedData(_priceAdapterConfigData); + + require( + areParamsValid(initialPrice, slope, bucketSize, maxPrice, minPrice), + "BoundedStepwiseLinearPriceAdapter: Invalid params" + ); + + uint256 bucket = _timeElapsed / bucketSize; + + // Protect against priceChange overflow + if (bucket > type(uint256).max / slope) { + return isDecreasing ? minPrice : maxPrice; + } + + uint256 priceChange = bucket * slope; + + if (isDecreasing) { + // Protect against price underflow + if (priceChange > initialPrice) { + return minPrice; + } + return FixedPointMathLib.max(initialPrice - priceChange, minPrice); + } else { + // Protect against price overflow + if (priceChange > type(uint256).max - initialPrice) { + return maxPrice; + } + return FixedPointMathLib.min(initialPrice + priceChange, maxPrice); + } + } + + /** + * @dev Returns true if the price adapter is valid for the given parameters. + * + * @param _priceAdapterConfigData Encoded data for configuring the price adapter. + * + * @return isValid Boolean indicating if the adapter config data is valid. + */ + function isPriceAdapterConfigDataValid( + bytes memory _priceAdapterConfigData + ) + external + pure + returns (bool isValid) + { + ( + uint256 initialPrice, + uint256 slope, + uint256 bucketSize, + , + uint256 maxPrice, + uint256 minPrice + ) = getDecodedData(_priceAdapterConfigData); + + return areParamsValid(initialPrice, slope, bucketSize, maxPrice, minPrice); + } + + /** + * @dev Returns true if the price adapter parameters are valid. + * + * @param _initialPrice Initial price of the auction + * @param _bucketSize Time elapsed between each bucket + * @param _maxPrice Maximum price of the auction + * @param _minPrice Minimum price of the auction + */ + function areParamsValid( + uint256 _initialPrice, + uint256 _slope, + uint256 _bucketSize, + uint256 _maxPrice, + uint256 _minPrice + ) + public + pure + returns (bool) + { + return _initialPrice > 0 + && _slope > 0 + && _bucketSize > 0 + && _initialPrice <= _maxPrice + && _initialPrice >= _minPrice; + } + + /** + * @dev Returns the encoded data for the price curve parameters + * + * @param _initialPrice Initial price of the auction + * @param _slope Slope of the linear price change + * @param _bucketSize Time elapsed between each bucket + * @param _isDecreasing Flag for whether the price is decreasing or increasing + * @param _maxPrice Maximum price of the auction + * @param _minPrice Minimum price of the auction + */ + function getEncodedData( + uint256 _initialPrice, + uint256 _slope, + uint256 _bucketSize, + bool _isDecreasing, + uint256 _maxPrice, + uint256 _minPrice + ) + external + pure + returns (bytes memory data) + { + return abi.encode(_initialPrice, _slope, _bucketSize, _isDecreasing, _maxPrice, _minPrice); + } + + /** + * @dev Decodes the parameters from the provided bytes. + * + * @param _data Bytes encoded auction parameters + * @return initialPrice Initial price of the auction + * @return slope Slope of the linear price change + * @return bucketSize Time elapsed between each bucket + * @return isDecreasing Flag for whether the price is decreasing or increasing + * @return maxPrice Maximum price of the auction + * @return minPrice Minimum price of the auction + */ + function getDecodedData(bytes memory _data) + public + pure + returns (uint256 initialPrice, uint256 slope, uint256 bucketSize, bool isDecreasing, uint256 maxPrice, uint256 minPrice) + { + return abi.decode(_data, (uint256, uint256, uint256, bool, uint256, uint256)); + } +} diff --git a/test/adapters/optimisticAuctionRebalanceExtensionV1.spec.ts b/test/adapters/optimisticAuctionRebalanceExtensionV1.spec.ts index f02627e8..08951ff9 100644 --- a/test/adapters/optimisticAuctionRebalanceExtensionV1.spec.ts +++ b/test/adapters/optimisticAuctionRebalanceExtensionV1.spec.ts @@ -1,6 +1,7 @@ import "module-alias/register"; import { Address, Account } from "@utils/types"; +import { base58ToHexString } from "@utils/common"; import { ADDRESS_ZERO, ZERO } from "@utils/constants"; import { BaseManager, @@ -23,29 +24,10 @@ import { getRandomAccount, } from "@utils/index"; import { SetFixture } from "@utils/fixtures"; -import { BigNumber, ContractTransaction, utils } from "ethers"; -import base58 from "bs58"; +import { BigNumber, ContractTransaction, utils, constants } from "ethers"; const expect = getWaffleExpect(); -function bufferToHex(buffer: Uint8Array) { - let hexStr = ""; - - for (let i = 0; i < buffer.length; i++) { - const hex = (buffer[i] & 0xff).toString(16); - hexStr += hex.length === 1 ? "0" + hex : hex; - } - - return hexStr; -} - -// Base58 decoding function (make sure you have a proper Base58 decoding function) -function base58ToHexString(base58String: string) { - const bytes = base58.decode(base58String); // Decode base58 to a buffer - const hexString = bufferToHex(bytes.slice(2)); // Convert buffer to hex, excluding the first 2 bytes - return "0x" + hexString; -} - describe("OptimisticAuctionRebalanceExtensionV1", () => { let owner: Account; let methodologist: Account; @@ -81,7 +63,7 @@ describe("OptimisticAuctionRebalanceExtensionV1", () => { priceAdapter = await deployer.setV2.deployConstantPriceAdapter(); optimisticOracleV3Mock = await deployer.mocks.deployOptimisticOracleV3Mock(); optimisticOracleV3MockUpgraded = await deployer.mocks.deployOptimisticOracleV3Mock(); - collateralAsset = await deployer.mocks.deployStandardTokenMock(owner.address, 18); + collateralAsset = await deployer.mocks.deployStandardTokenMock(operator.address, 18); await setV2Setup.integrationRegistry.addIntegration( setV2Setup.auctionModule.address, @@ -115,6 +97,9 @@ describe("OptimisticAuctionRebalanceExtensionV1", () => { allowedAssets, ); auctionRebalanceExtension = auctionRebalanceExtension.connect(operator.wallet); + await collateralAsset + .connect(operator.wallet) + .approve(auctionRebalanceExtension.address, ether(1000)); }); addSnapshotBeforeRestoreAfterEach(); @@ -141,7 +126,7 @@ describe("OptimisticAuctionRebalanceExtensionV1", () => { ); } - it("should set the correct manager core", async () => { + it("should set the correct base manager", async () => { const auctionExtension = await subject(); const actualBaseManager = await auctionExtension.manager(); @@ -179,7 +164,7 @@ describe("OptimisticAuctionRebalanceExtensionV1", () => { expect(isInitialized).to.be.true; }); - describe("when the initializer is not the owner", () => { + describe("when the initializer is not the operator", () => { beforeEach(async () => { subjectCaller = await getRandomAccount(); }); @@ -196,16 +181,22 @@ describe("OptimisticAuctionRebalanceExtensionV1", () => { }); context("when the product settings have been set", () => { + let rulesHash: Uint8Array; + let bondAmount: BigNumber; beforeEach(async () => { + rulesHash = utils.arrayify( + base58ToHexString("Qmc5gCcjYypU7y28oCALwfSvxCBskLuPKWpK4qpterKC7z"), + ); + bondAmount = ether(140); // 140 INDEX minimum bond await auctionRebalanceExtension.connect(operator.wallet).setProductSettings( { collateral: collateralAsset.address, - liveness: BigNumber.from(0), - bondAmount: BigNumber.from(0), + liveness: BigNumber.from(60 * 60), // 7 days + bondAmount, identifier: utils.formatBytes32String(""), optimisticOracleV3: optimisticOracleV3Mock.address, }, - utils.arrayify(base58ToHexString("Qmc5gCcjYypU7y28oCALwfSvxCBskLuPKWpK4qpterKC7z")), + rulesHash, ); }); @@ -291,13 +282,59 @@ describe("OptimisticAuctionRebalanceExtensionV1", () => { ); } + function constructClaim(): string { + const abi = utils.defaultAbiCoder; + const proposalHash = utils.keccak256( + abi.encode( + [ + "address", + "address", + "address[]", + "address[]", + "(uint256,string,bytes)[]", + "(uint256,string,bytes)[]", + "bool", + "uint256", + "uint256", + ], + [ + setToken.address, + subjectQuoteAsset, + subjectOldComponents, + subjectNewComponents, + subjectNewComponentsAuctionParams.map(component => [ + component.targetUnit, + component.priceAdapterName, + component.priceAdapterConfigData, + ]), + subjectOldComponentsAuctionParams.map(component => [ + component.targetUnit, + component.priceAdapterName, + component.priceAdapterConfigData, + ]), + false, // We don't allow locking the set token in this version + subjectRebalanceDuration, + subjectPositionMultiplier, + ], + ), + ); + const firstPart = utils.toUtf8Bytes( + "proposalHash:" + proposalHash.slice(2) + ',rulesIPFSHash:"', + ); + const lastPart = utils.toUtf8Bytes('"'); + + return utils.hexlify(utils.concat([firstPart, rulesHash, lastPart])); + } + context("when the extension is open for rebalance", () => { beforeEach(async () => { await auctionRebalanceExtension.updateIsOpen(true); }); + it("should not revert", async () => { await subject(); }); + it("should update proposed products correctly", async () => { await subject(); const proposal = await auctionRebalanceExtension @@ -305,16 +342,122 @@ describe("OptimisticAuctionRebalanceExtensionV1", () => { .proposedProduct(utils.formatBytes32String("win")); expect(proposal.product).to.eq(setToken.address); }); + + it("should pull bond", async () => { + const collateralBalanceBefore = await collateralAsset.balanceOf( + subjectCaller.address, + ); + await subject(); + const collateralBalanceAfter = await collateralAsset.balanceOf( + subjectCaller.address, + ); + expect(collateralBalanceAfter).to.eq(collateralBalanceBefore.sub(bondAmount)); + }); + + it("should emit RebalanceProposed event", async () => { + const receipt = (await subject().then(tx => tx.wait())) as any; + const proposeEvent = receipt.events.find( + (event: any) => event.event === "RebalanceProposed", + ); + expect(proposeEvent.args.setToken).to.eq(setToken.address); + expect(proposeEvent.args.quoteAsset).to.eq(subjectQuoteAsset); + expect(proposeEvent.args.oldComponents).to.deep.eq(subjectOldComponents); + expect(proposeEvent.args.newComponents).to.deep.eq(subjectNewComponents); + expect(proposeEvent.args.rebalanceDuration).to.eq(subjectRebalanceDuration); + expect(proposeEvent.args.positionMultiplier).to.eq(subjectPositionMultiplier); + + const newComponentsAuctionParams = proposeEvent.args.newComponentsAuctionParams.map( + (entry: any) => { + return { + priceAdapterConfigData: entry.priceAdapterConfigData, + priceAdapterName: entry.priceAdapterName, + targetUnit: entry.targetUnit, + }; + }, + ); + expect(newComponentsAuctionParams).to.deep.eq(subjectNewComponentsAuctionParams); + + const oldComponentsAuctionParams = proposeEvent.args.oldComponentsAuctionParams.map( + (entry: any) => { + return { + priceAdapterConfigData: entry.priceAdapterConfigData, + priceAdapterName: entry.priceAdapterName, + targetUnit: entry.targetUnit, + }; + }, + ); + expect(oldComponentsAuctionParams).to.deep.eq(subjectOldComponentsAuctionParams); + }); + + it("should emit AssertedClaim event", async () => { + const receipt = (await subject().then( tx => tx.wait())) as any; + const assertEvent = receipt.events.find( + (event: any) => event.event === "AssertedClaim", + ); + const emittedSetToken = assertEvent.args.setToken; + expect(emittedSetToken).to.eq(setToken.address); + const assertedBy = assertEvent.args._assertedBy; + expect(assertedBy).to.eq(operator.wallet.address); + const emittedRulesHash = assertEvent.args.rulesHash; + expect(emittedRulesHash).to.eq(utils.hexlify(rulesHash)); + const claim = assertEvent.args._claimData; + expect(claim).to.eq(constructClaim()); + }); + + context("when the same rebalance has been proposed already", () => { + beforeEach(async () => { + await subject(); + }); + + it("should revert", async () => { + await expect(subject()).to.be.revertedWith("Proposal already exists"); + }); + }); + + context("when the rule hash is empty", () => { + beforeEach(async () => { + const currentSettings = await auctionRebalanceExtension.productSettings(); + await auctionRebalanceExtension.setProductSettings( + currentSettings.optimisticParams, + constants.HashZero, + ); + }); + + it("should revert", async () => { + await expect(subject()).to.be.revertedWith("Rules not set"); + }); + }); + + context("when the oracle address is zero", () => { + beforeEach(async () => { + const [ + currentOptimisticParams, + ruleHash, + ] = await auctionRebalanceExtension.productSettings(); + const optimisticParams = { + ...currentOptimisticParams, + optimisticOracleV3: constants.AddressZero, + }; + await auctionRebalanceExtension.setProductSettings(optimisticParams, ruleHash); + }); + + it("should revert", async () => { + await expect(subject()).to.be.revertedWith("Oracle not set"); + }); + }); }); + context("when the extension is not open for rebalance", () => { beforeEach(async () => { await auctionRebalanceExtension.updateIsOpen(false); }); + it("should revert", async () => { expect(subject()).to.be.revertedWith("Must be open for rebalancing"); }); }); }); + describe("#startRebalance", () => { async function subject(): Promise { return auctionRebalanceExtension @@ -508,6 +651,7 @@ describe("OptimisticAuctionRebalanceExtensionV1", () => { .proposedProduct(utils.formatBytes32String("win")); expect(proposalAfter.product).to.eq(ADDRESS_ZERO); }); + it("should delete the proposal on a disputed callback from currently set oracle", async () => { await auctionRebalanceExtension.connect(operator.wallet).setProductSettings( { @@ -521,6 +665,7 @@ describe("OptimisticAuctionRebalanceExtensionV1", () => { base58ToHexString("Qmc5gCcjYypU7y28oCALwfSvxCBskLuPKWpK4qpterKC7z"), ), ); + const proposal = await auctionRebalanceExtension .connect(subjectCaller.wallet) .proposedProduct(utils.formatBytes32String("win")); @@ -533,12 +678,14 @@ describe("OptimisticAuctionRebalanceExtensionV1", () => { utils.formatBytes32String("win"), ), ).to.not.be.reverted; + const proposalAfter = await auctionRebalanceExtension .connect(subjectCaller.wallet) .proposedProduct(utils.formatBytes32String("win")); expect(proposalAfter.product).to.eq(ADDRESS_ZERO); }); }); + describe("assertionResolvedCallback", () => { it("should not revert on a resolved callback", async () => { await expect( @@ -614,6 +761,7 @@ describe("OptimisticAuctionRebalanceExtensionV1", () => { }); }); }); + describe("#setRaiseTargetPercentage", () => { let subjectRaiseTargetPercentage: BigNumber; let subjectCaller: Account; diff --git a/test/global-extensions/globalOptimisticAuctionRebalanceExtension.spec.ts b/test/global-extensions/globalOptimisticAuctionRebalanceExtension.spec.ts index d108c665..56dc8cdf 100644 --- a/test/global-extensions/globalOptimisticAuctionRebalanceExtension.spec.ts +++ b/test/global-extensions/globalOptimisticAuctionRebalanceExtension.spec.ts @@ -1,6 +1,7 @@ import "module-alias/register"; import { Address, Account } from "@utils/types"; +import { base58ToHexString } from "@utils/common"; import { ADDRESS_ZERO, ZERO } from "@utils/constants"; import { GlobalOptimisticAuctionRebalanceExtension, DelegatedManager, ManagerCore, @@ -21,29 +22,10 @@ import { } from "@utils/index"; import { SetFixture } from "@utils/fixtures"; import { BigNumber, ContractTransaction, utils } from "ethers"; -import base58 from "bs58"; const expect = getWaffleExpect(); -function bufferToHex(buffer: Uint8Array) { - let hexStr = ""; - - for (let i = 0; i < buffer.length; i++) { - const hex = (buffer[i] & 0xff).toString(16); - hexStr += (hex.length === 1) ? "0" + hex : hex; - } - - return hexStr; -} - -// Base58 decoding function (make sure you have a proper Base58 decoding function) -function base58ToHexString(base58String: string) { - const bytes = base58.decode(base58String); // Decode base58 to a buffer - const hexString = bufferToHex(bytes.slice(2)); // Convert buffer to hex, excluding the first 2 bytes - return "0x" + hexString; -} - -describe.only("GlobalOptimisticAuctionRebalanceExtension", () => { +describe("GlobalOptimisticAuctionRebalanceExtension", () => { let owner: Account; let methodologist: Account; let operator: Account; diff --git a/test/integration/ethereum/addresses.ts b/test/integration/ethereum/addresses.ts index 4e8bcf0e..a198db19 100644 --- a/test/integration/ethereum/addresses.ts +++ b/test/integration/ethereum/addresses.ts @@ -2,6 +2,7 @@ import structuredClone from "@ungap/structured-clone"; export const PRODUCTION_ADDRESSES = { tokens: { + index: "0x0954906da0Bf32d5479e25f46056d22f08464cab", stEthAm: "0x28424507fefb6f7f8E9D3860F56504E4e5f5f390", stEth: "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84", dai: "0x6B175474E89094C44Da98b954EedeAC495271d0F", @@ -22,6 +23,8 @@ export const PRODUCTION_ADDRESSES = { rETH2: "0x20BC832ca081b91433ff6c17f85701B6e92486c5", sETH2: "0xFe2e637202056d30016725477c5da089Ab0A043A", wbtc: "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599", + swETH: "0xf951E335afb289353dc249e82926178EaC7DEd78", + ETHx: "0xA35b1B31Ce002FBF2058D22F30f95D405200A15b", }, whales: { stEth: "0xdc24316b9ae028f1497c275eb9192a3ea0f67022", @@ -72,6 +75,7 @@ export const PRODUCTION_ADDRESSES = { aaveV3LeverageStrategyExtension: "0x7d3f7EDD04916F3Cb2bC6740224c636B9AE43200", aaveV3LeverageModule: "0x71E932715F5987077ADC5A7aA245f38841E0DcBe", constantPriceAdapter: "0x13c33656570092555Bf27Bdf53Ce24482B85D992", + linearPriceAdapter: "0x237F7BBe0b358415bE84AB6d279D4338C0d026bB", }, lending: { aave: { diff --git a/test/integration/ethereum/optimisticAuctionRebalanceExtenisonV1.spec.ts b/test/integration/ethereum/optimisticAuctionRebalanceExtenisonV1.spec.ts index b2fc934d..08b570af 100644 --- a/test/integration/ethereum/optimisticAuctionRebalanceExtenisonV1.spec.ts +++ b/test/integration/ethereum/optimisticAuctionRebalanceExtenisonV1.spec.ts @@ -3,13 +3,14 @@ import "module-alias/register"; import { Address, Account } from "@utils/types"; import { increaseTimeAsync } from "@utils/test"; import { setBlockNumber } from "@utils/test/testingUtils"; -import { ADDRESS_ZERO, ZERO } from "@utils/constants"; +import { base58ToHexString } from "@utils/common"; +import { ADDRESS_ZERO, ONE_HOUR_IN_SECONDS, ZERO } from "@utils/constants"; import { OptimisticAuctionRebalanceExtensionV1 } from "@utils/contracts/index"; import { AuctionRebalanceModuleV1, AuctionRebalanceModuleV1__factory, - ConstantPriceAdapter, - ConstantPriceAdapter__factory, + BoundedStepwiseLinearPriceAdapter, + BoundedStepwiseLinearPriceAdapter__factory, SetToken, SetToken__factory, BaseManagerV2, @@ -18,8 +19,8 @@ import { IntegrationRegistry__factory, IIdentifierWhitelist, IIdentifierWhitelist__factory, - IWETH, - IWETH__factory, + IERC20, + IERC20__factory, OptimisticOracleV3Mock, OptimisticOracleV3Interface, OptimisticOracleV3Interface__factory, @@ -32,37 +33,21 @@ import { ether, getAccounts, getWaffleExpect, - usdc, getTransactionTimestamp, getRandomAccount, } from "@utils/index"; import { BigNumber, ContractTransaction, utils, Signer } from "ethers"; import { ethers } from "hardhat"; -import base58 from "bs58"; const expect = getWaffleExpect(); -function bufferToHex(buffer: Uint8Array) { - let hexStr = ""; - - for (let i = 0; i < buffer.length; i++) { - const hex = (buffer[i] & 0xff).toString(16); - hexStr += hex.length === 1 ? "0" + hex : hex; - } - - return hexStr; -} - -// Base58 decoding function (make sure you have a proper Base58 decoding function) -function base58ToHexString(base58String: string) { - const bytes = base58.decode(base58String); // Decode base58 to a buffer - const hexString = bufferToHex(bytes.slice(2)); // Convert buffer to hex, excluding the first 2 bytes - return "0x" + hexString; -} - if (process.env.INTEGRATIONTEST) { - describe("OptimisticAuctionRebalanceExtensionV1 - Integration Test dsEth", () => { + describe.only("OptimisticAuctionRebalanceExtensionV1 - Integration Test dsEth", () => { const contractAddresses = PRODUCTION_ADDRESSES; + + const liveness = BigNumber.from(60 * 60 * 24 * 2); // 2 days + const minimumBond = ether(140); // 140 INDEX Minimum Bond + let owner: Account; let methodologist: Account; let operator: Signer; @@ -75,7 +60,7 @@ if (process.env.INTEGRATIONTEST) { let auctionRebalanceExtension: OptimisticAuctionRebalanceExtensionV1; let integrationRegistry: IntegrationRegistry; - let priceAdapter: ConstantPriceAdapter; + let priceAdapter: BoundedStepwiseLinearPriceAdapter; // UMA contracts let optimisticOracleV3: OptimisticOracleV3Interface; @@ -87,22 +72,21 @@ if (process.env.INTEGRATIONTEST) { let useAssetAllowlist: boolean; let allowedAssets: Address[]; - let weth: IWETH; - let minimumBond: BigNumber; + let indexToken: IERC20; - setBlockNumber(18789000); + setBlockNumber(18924016); before(async () => { [owner, methodologist] = await getAccounts(); deployer = new DeployHelper(owner.wallet); - priceAdapter = ConstantPriceAdapter__factory.connect( - contractAddresses.setFork.constantPriceAdapter, + priceAdapter = BoundedStepwiseLinearPriceAdapter__factory.connect( + contractAddresses.setFork.linearPriceAdapter, owner.wallet, ); - weth = IWETH__factory.connect(contractAddresses.tokens.weth, owner.wallet); - collateralAssetAddress = weth.address; + indexToken = IERC20__factory.connect(contractAddresses.tokens.index, owner.wallet); + collateralAssetAddress = indexToken.address; optimisticOracleV3 = OptimisticOracleV3Interface__factory.connect( contractAddresses.oracles.uma.optimisticOracleV3, @@ -121,7 +105,6 @@ if (process.env.INTEGRATIONTEST) { ethers.utils.parseEther("10").toHexString(), ]); identifierWhitelist = identifierWhitelist.connect(whitelistOwner); - minimumBond = await optimisticOracleV3.getMinimumBond(collateralAssetAddress); integrationRegistry = IntegrationRegistry__factory.connect( contractAddresses.setFork.integrationRegistry, @@ -135,8 +118,8 @@ if (process.env.INTEGRATIONTEST) { owner.wallet, ); - useAssetAllowlist = false; - allowedAssets = []; + useAssetAllowlist = true; + allowedAssets = [contractAddresses.tokens.swETH, contractAddresses.tokens.ETHx]; // New dsETH components dsEth = SetToken__factory.connect(contractAddresses.tokens.dsEth, owner.wallet); @@ -153,6 +136,12 @@ if (process.env.INTEGRATIONTEST) { auctionRebalanceExtension = auctionRebalanceExtension.connect(operator); }); + async function getIndexTokens(receiver: string, amount: BigNumber): Promise { + const INDEX_TOKEN_WHALE = "0x9467cfADC9DE245010dF95Ec6a585A506A8ad5FC"; + const indexWhaleSinger = await impersonateAccount(INDEX_TOKEN_WHALE); + await indexToken.connect(indexWhaleSinger).transfer(receiver, amount); + } + addSnapshotBeforeRestoreAfterEach(); context("when auction rebalance extension is added as extension", () => { @@ -163,18 +152,19 @@ if (process.env.INTEGRATIONTEST) { context("when the product settings have been set", () => { let productSettings: any; let identifier: string; - let liveness: BigNumber; + beforeEach(async () => { identifier = utils.formatBytes32String("TestIdentifier"); // TODO: Check how do we ensure that our identifier is supported on UMAs whitelist await identifierWhitelist.addSupportedIdentifier(identifier); - liveness = BigNumber.from(60 * 60); // 7 days + productSettings = { collateral: collateralAssetAddress, liveness, - bondAmount: BigNumber.from(0), + bondAmount: minimumBond, identifier, optimisticOracleV3: optimisticOracleV3.address, }; + await auctionRebalanceExtension .connect(operator) .setProductSettings( @@ -199,6 +189,7 @@ if (process.env.INTEGRATIONTEST) { let subjectPositionMultiplier: BigNumber; let subjectCaller: Signer; let effectiveBond: BigNumber; + beforeEach(async () => { effectiveBond = productSettings.bondAmount.gt(minimumBond) ? productSettings.bondAmount @@ -207,44 +198,108 @@ if (process.env.INTEGRATIONTEST) { subjectQuoteAsset = contractAddresses.tokens.weth; subjectOldComponents = await dsEth.getComponents(); - subjectNewComponents = [contractAddresses.tokens.USDC]; + subjectNewComponents = [contractAddresses.tokens.swETH, contractAddresses.tokens.ETHx]; subjectNewComponentsAuctionParams = [ - { - targetUnit: usdc(100), - priceAdapterName: "ConstantPriceAdapter", - priceAdapterConfigData: await priceAdapter.getEncodedData(ether(0.005)), + { // swETH: https://etherscan.io/address/0xf951E335afb289353dc249e82926178EaC7DEd78#readProxyContract#F6 + targetUnit: ether(0.166), // To do: Check target units + priceAdapterName: "BoundedStepwiseLinearPriceAdapter", + priceAdapterConfigData: await priceAdapter.getEncodedData( + ether(1.043), + ether(0.001), + ONE_HOUR_IN_SECONDS, + true, + ether(1.05), + ether(1.043), + ), + }, + { // ETHx: https://etherscan.io/address/0xcf5ea1b38380f6af39068375516daf40ed70d299#readProxyContract#F5 + targetUnit: ether(0.166), // To do: Check target units + priceAdapterName: "BoundedStepwiseLinearPriceAdapter", + priceAdapterConfigData: await priceAdapter.getEncodedData( + ether(1.014), + ether(0.001), + ONE_HOUR_IN_SECONDS, + true, + ether(1.02), + ether(1.014), + ), }, ]; - const sellAllAuctionParam = { - targetUnit: ether(0), - priceAdapterName: "ConstantPriceAdapter", - priceAdapterConfigData: await priceAdapter.getEncodedData(ether(0.005)), - }; - subjectOldComponentsAuctionParams = subjectOldComponents.map( - () => sellAllAuctionParam, - ); + subjectOldComponentsAuctionParams = [ + { // wstETH: https://etherscan.io/address/0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0#readContract#F10 + targetUnit: ether(0.166), // To do: Check target units + priceAdapterName: "BoundedStepwiseLinearPriceAdapter", + priceAdapterConfigData: await priceAdapter.getEncodedData( + ether(1.155), + ether(0.001), + ONE_HOUR_IN_SECONDS, + true, + ether(1.155), + ether(1.149), + ), + }, + { // rETH: https://etherscan.io/address/0xae78736Cd615f374D3085123A210448E74Fc6393#readContract#F6 + targetUnit: ether(0.166), // To do: Check target units + priceAdapterName: "BoundedStepwiseLinearPriceAdapter", + priceAdapterConfigData: await priceAdapter.getEncodedData( + ether(1.097), + ether(0.001), + ONE_HOUR_IN_SECONDS, + true, + ether(1.097), + ether(1.091), + ), + }, + { // sfrxETH: https://etherscan.io/address/0xac3E018457B222d93114458476f3E3416Abbe38F#readContract#F20 + targetUnit: ether(0.166), // To do: Check target units + priceAdapterName: "BoundedStepwiseLinearPriceAdapter", + priceAdapterConfigData: await priceAdapter.getEncodedData( + ether(1.073), + ether(0.001), + ONE_HOUR_IN_SECONDS, + true, + ether(1.073), + ether(1.067), + ), + }, + { // osETH: Add conversion rate source + targetUnit: ether(0.166), // To do: Check target units + priceAdapterName: "BoundedStepwiseLinearPriceAdapter", + priceAdapterConfigData: await priceAdapter.getEncodedData( + ether(1.005), + ether(0.001), + ONE_HOUR_IN_SECONDS, + true, + ether(1.005), + ether(1.004), + ), + }, + ]; subjectShouldLockSetToken = false; - subjectRebalanceDuration = BigNumber.from(86400); - subjectPositionMultiplier = ether(0.999); + subjectRebalanceDuration = BigNumber.from(60 * 60 * 24 * 3); + subjectPositionMultiplier = await dsEth.positionMultiplier(); subjectCaller = operator; const quantity = utils .parseEther("1000") .add(effectiveBond) .toHexString(); - // set operator balance to effective bond + + // set operator balance to effective bond await ethers.provider.send("hardhat_setBalance", [ await subjectCaller.getAddress(), quantity, ]); - await weth.connect(subjectCaller).deposit({ value: effectiveBond }); - await weth - .connect(subjectCaller) - .approve(auctionRebalanceExtension.address, effectiveBond); - }); + + await getIndexTokens(await subjectCaller.getAddress(), effectiveBond); + await indexToken + .connect(subjectCaller) + .approve(auctionRebalanceExtension.address, effectiveBond); + }); + describe("#startRebalance", () => { async function subject(): Promise { return auctionRebalanceExtension @@ -263,6 +318,7 @@ if (process.env.INTEGRATIONTEST) { context("when the rebalance has been proposed", () => { let proposalId: string; + beforeEach(async () => { const tx = await auctionRebalanceExtension .connect(subjectCaller) @@ -281,6 +337,7 @@ if (process.env.INTEGRATIONTEST) { const assertEvent = receipt.events[receipt.events.length - 1] as any; proposalId = assertEvent.args._assertionId; }); + context("when the liveness period has passed", () => { beforeEach(async () => { await increaseTimeAsync(liveness.add(1)); @@ -302,16 +359,19 @@ if (process.env.INTEGRATIONTEST) { ); await auctionRebalanceExtension.updateIsOpen(true); }); + it("should revert", async () => { await expect(subject()).to.be.revertedWith("Proposal hash does not exist"); }); + context("when identical rebalanced again but liveness has not passed", () => { beforeEach(async () => { // set operator balance to effective bond - await weth.connect(subjectCaller).deposit({ value: effectiveBond }); - await weth + await getIndexTokens(await subjectCaller.getAddress(), effectiveBond); + await indexToken .connect(subjectCaller) .approve(auctionRebalanceExtension.address, effectiveBond); + await auctionRebalanceExtension .connect(subjectCaller) .proposeRebalance( @@ -324,6 +384,7 @@ if (process.env.INTEGRATIONTEST) { subjectPositionMultiplier, ); }); + it("should revert", async () => { await expect(subject()).to.be.revertedWith("Assertion not expired"); }); @@ -389,8 +450,8 @@ if (process.env.INTEGRATIONTEST) { expect(proposal.product).to.eq(dsEth.address); - await weth.connect(subjectCaller).deposit({ value: effectiveBond }); - await weth.connect(subjectCaller).approve(optimisticOracleV3.address, effectiveBond); + await getIndexTokens(await subjectCaller.getAddress(), effectiveBond); + await indexToken.connect(subjectCaller).approve(optimisticOracleV3.address, effectiveBond); await optimisticOracleV3 .connect(subjectCaller) .disputeAssertion(proposalId, owner.address); @@ -400,12 +461,13 @@ if (process.env.INTEGRATIONTEST) { .proposedProduct(utils.formatBytes32String("win")); expect(proposalAfter.product).to.eq(ADDRESS_ZERO); }); + it("should delete the proposal on a disputed callback from currently set oracle", async () => { await auctionRebalanceExtension.connect(operator).setProductSettings( { collateral: collateralAssetAddress, liveness, - bondAmount: BigNumber.from(0), + bondAmount: minimumBond, identifier, optimisticOracleV3: optimisticOracleV3Mock.address, }, @@ -413,6 +475,7 @@ if (process.env.INTEGRATIONTEST) { base58ToHexString("Qmc5gCcjYypU7y28oCALwfSvxCBskLuPKWpK4qpterKC7z"), ), ); + const proposal = await auctionRebalanceExtension .connect(subjectCaller) .proposedProduct(proposalId); @@ -440,7 +503,7 @@ if (process.env.INTEGRATIONTEST) { { collateral: collateralAssetAddress, liveness, - bondAmount: BigNumber.from(0), + bondAmount: minimumBond, identifier, optimisticOracleV3: optimisticOracleV3Mock.address, }, @@ -587,6 +650,7 @@ if (process.env.INTEGRATIONTEST) { .connect(operator) .removeExtension(auctionRebalanceExtension.address); } + it("should remove the extension", async () => { expect(await baseManager.isExtension(auctionRebalanceExtension.address)).to.be.true; await subject(); diff --git a/test/manager/baseManagerV2.spec.ts b/test/manager/baseManagerV2.spec.ts index e2b00552..977e7316 100644 --- a/test/manager/baseManagerV2.spec.ts +++ b/test/manager/baseManagerV2.spec.ts @@ -371,8 +371,8 @@ describe("BaseManagerV2", () => { }); describe("when the extension is authorized for a protected module", () => { - beforeEach(() => { - baseManager.connect(operator.wallet).protectModule(subjectModule, [subjectExtension]); + beforeEach(async () => { + await baseManager.connect(operator.wallet).protectModule(subjectModule, [subjectExtension]); }); it("should revert", async() => { diff --git a/utils/common/conversionUtils.ts b/utils/common/conversionUtils.ts index 1dcd2430..056dc297 100644 --- a/utils/common/conversionUtils.ts +++ b/utils/common/conversionUtils.ts @@ -1,3 +1,22 @@ import { BigNumber } from "ethers/lib/ethers"; +import base58 from "bs58"; -export const bigNumberToData = (number: BigNumber) => number.toHexString().replace("0x", "").padStart(64, "0"); \ No newline at end of file +export const bigNumberToData = (number: BigNumber) => number.toHexString().replace("0x", "").padStart(64, "0"); + +export const bufferToHex = (buffer: Uint8Array) => { + let hexStr = ""; + + for (let i = 0; i < buffer.length; i++) { + const hex = (buffer[i] & 0xff).toString(16); + hexStr += hex.length === 1 ? "0" + hex : hex; + } + + return hexStr; +}; + +// Base58 decoding function (make sure you have a proper Base58 decoding function) +export const base58ToHexString = (base58String: string) => { + const bytes = base58.decode(base58String); // Decode base58 to a buffer + const hexString = bufferToHex(bytes.slice(2)); // Convert buffer to hex, excluding the first 2 bytes + return "0x" + hexString; +}; diff --git a/utils/common/index.ts b/utils/common/index.ts index eeac5056..d50dd8dd 100644 --- a/utils/common/index.ts +++ b/utils/common/index.ts @@ -21,5 +21,7 @@ export { convertLibraryNameToLinkId } from "./libraryUtils"; export { - bigNumberToData + bigNumberToData, + bufferToHex, + base58ToHexString, } from "./conversionUtils"; diff --git a/utils/contracts/setV2.ts b/utils/contracts/setV2.ts index 295237ad..91c39c8d 100644 --- a/utils/contracts/setV2.ts +++ b/utils/contracts/setV2.ts @@ -4,6 +4,7 @@ export { AaveV2 } from "../../typechain/AaveV2"; export { AirdropModule } from "../../typechain/AirdropModule"; export { AuctionRebalanceModuleV1 } from "../../typechain/AuctionRebalanceModuleV1"; export { BasicIssuanceModule } from "../../typechain/BasicIssuanceModule"; +export { BoundedStepwiseLinearPriceAdapter } from "../../typechain/BoundedStepwiseLinearPriceAdapter"; export { Compound } from "../../typechain/Compound"; export { Controller } from "../../typechain/Controller"; export { ContractCallerMock } from "../../typechain/ContractCallerMock"; From ced1ad5a55b5b2eb00b8642028d9ab9ce3fd25c0 Mon Sep 17 00:00:00 2001 From: ckoopmann Date: Thu, 4 Jan 2024 16:27:05 +0800 Subject: [PATCH 24/36] Add comment with link to optimistic governor reference implementation of _constructClaim --- contracts/adapters/OptimisticAuctionRebalanceExtensionV1.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/contracts/adapters/OptimisticAuctionRebalanceExtensionV1.sol b/contracts/adapters/OptimisticAuctionRebalanceExtensionV1.sol index 447a69d9..6ddd28a4 100644 --- a/contracts/adapters/OptimisticAuctionRebalanceExtensionV1.sol +++ b/contracts/adapters/OptimisticAuctionRebalanceExtensionV1.sol @@ -377,6 +377,7 @@ contract OptimisticAuctionRebalanceExtensionV1 is AuctionRebalanceExtension, As /* ============ Internal Functions ============ */ // Constructs the claim that will be asserted at the Optimistic Oracle V3. + // @dev Inspired by the equivalent function in the OptimisticGovernor: https://github.com/UMAprotocol/protocol/blob/96cf5be32a3f57ac761f004890dd3466c63e1fa5/packages/core/contracts/optimistic-governor/implementation/OptimisticGovernor.sol#L437 function _constructClaim(bytes32 proposalHash, bytes32 rulesHash) internal pure returns (bytes memory) { return abi.encodePacked( From 57e8ced8c49c187e7f7a908a597bcd0ea58f9baf Mon Sep 17 00:00:00 2001 From: ckoopmann Date: Thu, 4 Jan 2024 17:00:21 +0800 Subject: [PATCH 25/36] Additional tests arround asset allow list --- ...imisticAuctionRebalanceExtensionV1.spec.ts | 147 +++++++++++++++++- 1 file changed, 146 insertions(+), 1 deletion(-) diff --git a/test/adapters/optimisticAuctionRebalanceExtensionV1.spec.ts b/test/adapters/optimisticAuctionRebalanceExtensionV1.spec.ts index 08951ff9..7697f9b6 100644 --- a/test/adapters/optimisticAuctionRebalanceExtensionV1.spec.ts +++ b/test/adapters/optimisticAuctionRebalanceExtensionV1.spec.ts @@ -141,6 +141,130 @@ describe("OptimisticAuctionRebalanceExtensionV1", () => { }); }); + describe("#updateUseAssetAllowlist", () => { + let subjectCaller: Account; + let subjectNewValue: boolean; + function subject() { + return auctionRebalanceExtension + .connect(subjectCaller.wallet) + .updateUseAssetAllowlist(subjectNewValue); + } + beforeEach(async () => { + subjectCaller = operator; + }); + [true, false].forEach((useAssetAllowlist: boolean) => { + describe(`when setting value to ${useAssetAllowlist}`, () => { + beforeEach(async () => { + subjectNewValue = useAssetAllowlist; + await auctionRebalanceExtension + .connect(operator.wallet) + .updateUseAssetAllowlist(!subjectNewValue); + }); + + it("should update the useAssetAllowlist correctly", async () => { + await subject(); + const actualUseAssetAllowlist = await auctionRebalanceExtension.useAssetAllowlist(); + expect(actualUseAssetAllowlist).to.eq(subjectNewValue); + }); + }); + }); + context("when the caller is not the operator", async () => { + beforeEach(async () => { + subjectCaller = methodologist; + subjectNewValue = true; + }); + + it("should revert", async () => { + await expect(subject()).to.be.revertedWith("Must be operator"); + }); + }); + }); + + describe("#removeAllowedAssets", () => { + let subjectCaller: Account; + let subjectRemovedAssets: Address[]; + async function subject() { + return await auctionRebalanceExtension + .connect(subjectCaller.wallet) + .removeAllowedAssets(subjectRemovedAssets); + } + beforeEach(async () => { + subjectRemovedAssets = [collateralAsset.address]; + subjectCaller = operator; + await auctionRebalanceExtension + .connect(operator.wallet) + .addAllowedAssets(subjectRemovedAssets); + }); + + it("should add the new assets to the allowed assets", async () => { + await subject(); + for (let i = 0; i < subjectRemovedAssets.length; i++) { + const isAllowed = await auctionRebalanceExtension.assetAllowlist(subjectRemovedAssets[i]); + expect(isAllowed).to.be.false; + } + }); + + it("should emit AllowedAssetAdded event", async () => { + const promise = subject(); + for (let i = 0; i < subjectRemovedAssets.length; i++) { + await expect(promise) + .to.emit(auctionRebalanceExtension, "AllowedAssetRemoved") + .withArgs(subjectRemovedAssets[i]); + } + }); + + context("If the caller is not the operator", async () => { + beforeEach(async () => { + subjectCaller = await getRandomAccount(); + }); + + it("should revert", async () => { + await expect(subject()).to.be.revertedWith("Must be operator"); + }); + }); + }); + + describe("#addAllowedAssets", () => { + let subjectCaller: Account; + let subjectNewAssets: Address[]; + async function subject() { + return await auctionRebalanceExtension + .connect(subjectCaller.wallet) + .addAllowedAssets(subjectNewAssets); + } + beforeEach(async () => { + subjectCaller = operator; + subjectNewAssets = [collateralAsset.address]; + }); + + it("should add the new assets to the allowed assets", async () => { + await subject(); + for (let i = 0; i < subjectNewAssets.length; i++) { + const isAllowed = await auctionRebalanceExtension.assetAllowlist(subjectNewAssets[i]); + expect(isAllowed).to.be.true; + } + }); + + it("should emit AllowedAssetAdded event", async () => { + const promise = subject(); + for (let i = 0; i < subjectNewAssets.length; i++) { + await expect(promise) + .to.emit(auctionRebalanceExtension, "AllowedAssetAdded") + .withArgs(subjectNewAssets[i]); + } + }); + + context("If the caller is not the operator", async () => { + beforeEach(async () => { + subjectCaller = await getRandomAccount(); + }); + + it("should revert", async () => { + await expect(subject()).to.be.revertedWith("Must be operator"); + }); + }); + }); + context( "when auction rebalance extension is added as adapter and needs to be initialized", () => { @@ -390,7 +514,7 @@ describe("OptimisticAuctionRebalanceExtensionV1", () => { }); it("should emit AssertedClaim event", async () => { - const receipt = (await subject().then( tx => tx.wait())) as any; + const receipt = (await subject().then(tx => tx.wait())) as any; const assertEvent = receipt.events.find( (event: any) => event.event === "AssertedClaim", ); @@ -414,6 +538,27 @@ describe("OptimisticAuctionRebalanceExtensionV1", () => { }); }); + context("when asset allow list is activated", () => { + beforeEach(async () => { + await auctionRebalanceExtension.updateUseAssetAllowlist(true); + }); + + context("when new assets are not on the allow list", () => { + it("should revert", async () => { + await expect(subject()).to.be.revertedWith("Invalid asset"); + }); + }); + + context("when new assets are on the allow list", () => { + beforeEach(async () => { + await auctionRebalanceExtension.addAllowedAssets(subjectNewComponents); + }); + + it("should not revert", async () => { + await subject(); + }); + }); + }); context("when the rule hash is empty", () => { beforeEach(async () => { const currentSettings = await auctionRebalanceExtension.productSettings(); From c4401d97b38054765b8490570f46e3598a6bd335 Mon Sep 17 00:00:00 2001 From: ckoopmann Date: Thu, 4 Jan 2024 17:05:34 +0800 Subject: [PATCH 26/36] Additional tests to increase coverage --- .../optimisticAuctionRebalanceExtensionV1.spec.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/adapters/optimisticAuctionRebalanceExtensionV1.spec.ts b/test/adapters/optimisticAuctionRebalanceExtensionV1.spec.ts index 7697f9b6..904dc9a0 100644 --- a/test/adapters/optimisticAuctionRebalanceExtensionV1.spec.ts +++ b/test/adapters/optimisticAuctionRebalanceExtensionV1.spec.ts @@ -635,6 +635,19 @@ describe("OptimisticAuctionRebalanceExtensionV1", () => { }); }); + describe("when any parameter is different from the proposedRebalance", () => { + beforeEach(async () => { + await proposeRebalance(); + subjectPositionMultiplier = subjectPositionMultiplier.add(1); + }); + + it("should revert", async () => { + await expect(subject()).to.be.revertedWith( + "Proposal hash does not exist", + ); + }); + }); + describe("when old components array is shorter than current components array", () => { beforeEach(async () => { subjectOldComponents = [setV2Setup.dai.address, setV2Setup.wbtc.address]; From ac920884768477275ecabb196632a4f17c68f558 Mon Sep 17 00:00:00 2001 From: ckoopmann Date: Thu, 4 Jan 2024 17:11:10 +0800 Subject: [PATCH 27/36] Declare setToken and auctionModule as immutable --- contracts/adapters/AuctionRebalanceExtension.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/adapters/AuctionRebalanceExtension.sol b/contracts/adapters/AuctionRebalanceExtension.sol index 7025bcea..f1a8295f 100644 --- a/contracts/adapters/AuctionRebalanceExtension.sol +++ b/contracts/adapters/AuctionRebalanceExtension.sol @@ -49,8 +49,8 @@ contract AuctionRebalanceExtension is BaseExtension { /* ============ State Variables ============ */ - ISetToken public setToken; - IAuctionRebalanceModuleV1 public auctionModule; // AuctionRebalanceModuleV1 + ISetToken public immutable setToken; + IAuctionRebalanceModuleV1 public immutable auctionModule; // AuctionRebalanceModuleV1 /* ============ Constructor ============ */ From cc8a357929471b1d7393837d1f24128dd0586a4b Mon Sep 17 00:00:00 2001 From: ckoopmann Date: Thu, 4 Jan 2024 17:21:37 +0800 Subject: [PATCH 28/36] Remove Proposal struct because we are only managing one product per extension --- .../OptimisticAuctionRebalanceExtensionV1.sol | 27 +++++-------- ...imisticAuctionRebalanceExtensionV1.spec.ts | 40 +++++++++++-------- 2 files changed, 32 insertions(+), 35 deletions(-) diff --git a/contracts/adapters/OptimisticAuctionRebalanceExtensionV1.sol b/contracts/adapters/OptimisticAuctionRebalanceExtensionV1.sol index 6ddd28a4..427e715b 100644 --- a/contracts/adapters/OptimisticAuctionRebalanceExtensionV1.sol +++ b/contracts/adapters/OptimisticAuctionRebalanceExtensionV1.sol @@ -73,7 +73,7 @@ contract OptimisticAuctionRebalanceExtensionV1 is AuctionRebalanceExtension, As event ProposalDeleted( bytes32 assertionID, - Proposal indexed proposal + bytes32 indexed proposalHash ); event IsOpenUpdated( @@ -102,16 +102,11 @@ contract OptimisticAuctionRebalanceExtensionV1 is AuctionRebalanceExtension, As bytes32 rulesHash; // IPFS hash of the rules for the product. } - struct Proposal{ - bytes32 proposalHash; // Hash of the proposal. - ISetToken product; // Address of the SetToken to set rules and settings for. - } - /* ============ State Variables ============ */ ProductSettings public productSettings; // Mapping of set token to ProductSettings mapping(bytes32 => bytes32) public assertionIds; // Maps proposal hashes to assertionIds. - mapping(bytes32 => Proposal) public proposedProduct; // Maps assertionIds to a Proposal. + mapping(bytes32 => bytes32) public assertionIdToProposalHash; // Maps assertionIds to a proposal hash. bool public isOpen; // Bool indicating whether the extension is open for proposing rebalances. // Keys for assertion claim data. @@ -256,10 +251,7 @@ contract OptimisticAuctionRebalanceExtensionV1 is AuctionRebalanceExtension, As ); assertionIds[proposalHash] = assertionId; - proposedProduct[assertionId] = Proposal({ - proposalHash: proposalHash, - product: setToken - }); + assertionIdToProposalHash[assertionId] = proposalHash; emit RebalanceProposed( setToken, _quoteAsset, _oldComponents, _newComponents, _newComponentsAuctionParams, _oldComponentsAuctionParams, _rebalanceDuration, _positionMultiplier); emit AssertedClaim(setToken, msg.sender, productSettings.rulesHash, assertionId, claim); @@ -355,8 +347,7 @@ contract OptimisticAuctionRebalanceExtensionV1 is AuctionRebalanceExtension, As * @param _assertionId the identifier of the disputed assertion. */ function assertionDisputedCallback(bytes32 _assertionId) external { - Proposal memory proposal = proposedProduct[_assertionId]; - require(proposal.product == setToken, "Invalid proposal product"); + bytes32 proposalHash = assertionIdToProposalHash[_assertionId]; require(address(productSettings.optimisticParams.optimisticOracleV3) != address(0), "Invalid oracle address"); @@ -367,11 +358,11 @@ contract OptimisticAuctionRebalanceExtensionV1 is AuctionRebalanceExtension, As } else { // If the sender is not the expected Optimistic Oracle V3, check if the expected Oracle has the assertion and if not delete. - require(proposal.proposalHash != bytes32(0), "Invalid proposal hash"); + require(proposalHash != bytes32(0), "Invalid proposal hash"); require(productSettings.optimisticParams.optimisticOracleV3.getAssertion(_assertionId).asserter == address(0), "Oracle has assertion"); _deleteProposal(_assertionId); } - emit ProposalDeleted(_assertionId, proposal); + emit ProposalDeleted(_assertionId, proposalHash); } /* ============ Internal Functions ============ */ @@ -394,9 +385,9 @@ contract OptimisticAuctionRebalanceExtensionV1 is AuctionRebalanceExtension, As /// @dev Internal function that deletes a proposal and associated assertionId. /// @param assertionId assertionId of the proposal to delete. function _deleteProposal(bytes32 assertionId) internal { - Proposal memory proposal = proposedProduct[assertionId]; - delete assertionIds[proposal.proposalHash]; - delete proposedProduct[assertionId]; + bytes32 proposalHash = assertionIdToProposalHash[assertionId]; + delete assertionIds[proposalHash]; + delete assertionIdToProposalHash[assertionId]; } /// @notice Pulls the higher of the minimum bond or configured bond amount from the sender. diff --git a/test/adapters/optimisticAuctionRebalanceExtensionV1.spec.ts b/test/adapters/optimisticAuctionRebalanceExtensionV1.spec.ts index 904dc9a0..8e5df2e5 100644 --- a/test/adapters/optimisticAuctionRebalanceExtensionV1.spec.ts +++ b/test/adapters/optimisticAuctionRebalanceExtensionV1.spec.ts @@ -459,12 +459,18 @@ describe("OptimisticAuctionRebalanceExtensionV1", () => { await subject(); }); - it("should update proposed products correctly", async () => { + it("should update proposal hash correctly", async () => { + const proposalHashBefore = await auctionRebalanceExtension + .connect(subjectCaller.wallet) + .assertionIdToProposalHash(utils.formatBytes32String("win")); + expect(proposalHashBefore).to.eq(constants.HashZero); + await subject(); - const proposal = await auctionRebalanceExtension + + const proposalHashAfter = await auctionRebalanceExtension .connect(subjectCaller.wallet) - .proposedProduct(utils.formatBytes32String("win")); - expect(proposal.product).to.eq(setToken.address); + .assertionIdToProposalHash(utils.formatBytes32String("win")); + expect(proposalHashAfter).to.not.eq(constants.HashZero); }); it("should pull bond", async () => { @@ -788,12 +794,12 @@ describe("OptimisticAuctionRebalanceExtensionV1", () => { } }); - describe("assertionDisputedCallback", () => { + describe("#assertionDisputedCallback", () => { it("should delete the proposal on a disputed callback", async () => { - const proposal = await auctionRebalanceExtension + const proposalHash = await auctionRebalanceExtension .connect(subjectCaller.wallet) - .proposedProduct(utils.formatBytes32String("win")); - expect(proposal.product).to.eq(setToken.address); + .assertionIdToProposalHash(utils.formatBytes32String("win")); + expect(proposalHash).to.not.eq(constants.HashZero); await expect( optimisticOracleV3Mock @@ -804,10 +810,10 @@ describe("OptimisticAuctionRebalanceExtensionV1", () => { ), ).to.not.be.reverted; - const proposalAfter = await auctionRebalanceExtension + const proposalHashAfter = await auctionRebalanceExtension .connect(subjectCaller.wallet) - .proposedProduct(utils.formatBytes32String("win")); - expect(proposalAfter.product).to.eq(ADDRESS_ZERO); + .assertionIdToProposalHash(utils.formatBytes32String("win")); + expect(proposalHashAfter).to.eq(constants.HashZero); }); it("should delete the proposal on a disputed callback from currently set oracle", async () => { @@ -824,10 +830,10 @@ describe("OptimisticAuctionRebalanceExtensionV1", () => { ), ); - const proposal = await auctionRebalanceExtension + const proposalHash = await auctionRebalanceExtension .connect(subjectCaller.wallet) - .proposedProduct(utils.formatBytes32String("win")); - expect(proposal.product).to.eq(setToken.address); + .assertionIdToProposalHash(utils.formatBytes32String("win")); + expect(proposalHash).to.not.eq(constants.HashZero); await expect( optimisticOracleV3Mock .connect(subjectCaller.wallet) @@ -837,10 +843,10 @@ describe("OptimisticAuctionRebalanceExtensionV1", () => { ), ).to.not.be.reverted; - const proposalAfter = await auctionRebalanceExtension + const proposalHashAfter = await auctionRebalanceExtension .connect(subjectCaller.wallet) - .proposedProduct(utils.formatBytes32String("win")); - expect(proposalAfter.product).to.eq(ADDRESS_ZERO); + .assertionIdToProposalHash(utils.formatBytes32String("win")); + expect(proposalHashAfter).to.eq(constants.HashZero); }); }); From a62e71cb283c6264b5042e82edc585ab9de4b66d Mon Sep 17 00:00:00 2001 From: ckoopmann Date: Thu, 4 Jan 2024 20:06:02 +0800 Subject: [PATCH 29/36] Additional tests to trigger require statements in dispute callback --- contracts/mocks/OptimisticOracleV3Mock.sol | 7 +- ...imisticAuctionRebalanceExtensionV1.spec.ts | 97 +++++++++++++++---- 2 files changed, 84 insertions(+), 20 deletions(-) diff --git a/contracts/mocks/OptimisticOracleV3Mock.sol b/contracts/mocks/OptimisticOracleV3Mock.sol index 6e135a81..33d53b7d 100644 --- a/contracts/mocks/OptimisticOracleV3Mock.sol +++ b/contracts/mocks/OptimisticOracleV3Mock.sol @@ -21,11 +21,16 @@ interface callbackInterface { * tradeoffs, enabling the notion of "sovereign security". */ contract OptimisticOracleV3Mock is OptimisticOracleV3Interface { + address public asserter; // Mock implementation of defaultIdentifier function defaultIdentifier() public view override returns (bytes32) { return (bytes32("helloWorld")); } + function setAsserter(address _asserter) public { + asserter = _asserter; + } + // Mock implementation of getAssertion function getAssertion(bytes32 ) public view override returns (Assertion memory) { return (Assertion({ @@ -36,7 +41,7 @@ contract OptimisticOracleV3Mock is OptimisticOracleV3Interface { assertingCaller: address(0), escalationManager: address(0) }), - asserter: address(0), + asserter: asserter, assertionTime: uint64(0), settled: false, currency: IERC20(address(0)), diff --git a/test/adapters/optimisticAuctionRebalanceExtensionV1.spec.ts b/test/adapters/optimisticAuctionRebalanceExtensionV1.spec.ts index 8e5df2e5..d2495cad 100644 --- a/test/adapters/optimisticAuctionRebalanceExtensionV1.spec.ts +++ b/test/adapters/optimisticAuctionRebalanceExtensionV1.spec.ts @@ -648,9 +648,7 @@ describe("OptimisticAuctionRebalanceExtensionV1", () => { }); it("should revert", async () => { - await expect(subject()).to.be.revertedWith( - "Proposal hash does not exist", - ); + await expect(subject()).to.be.revertedWith("Proposal hash does not exist"); }); }); @@ -795,20 +793,88 @@ describe("OptimisticAuctionRebalanceExtensionV1", () => { }); describe("#assertionDisputedCallback", () => { + let subjectAssertionId: string; + function subject(): Promise { + return optimisticOracleV3Mock + .connect(subjectCaller.wallet) + .mockAssertionDisputedCallback( + auctionRebalanceExtension.address, + subjectAssertionId, + ); + } + beforeEach(() => { + subjectAssertionId = utils.formatBytes32String("win"); + }); + + context("when the caller is not the oracle", () => { + function subject(): Promise { + return auctionRebalanceExtension + .connect(subjectCaller.wallet) + .assertionDisputedCallback(subjectAssertionId); + } + context("when the assertionId is wrong", () => { + beforeEach(async () => { + subjectAssertionId = utils.formatBytes32String("wrongid"); + }); + + it("should revert", async () => { + await expect(subject()).to.be.revertedWith("Invalid proposal hash"); + }); + }); + context("when the oracle does not have the assertion", () => { + it("should delete the proposal", async () => { + const proposalHash = await auctionRebalanceExtension + .connect(subjectCaller.wallet) + .assertionIdToProposalHash(utils.formatBytes32String("win")); + expect(proposalHash).to.not.eq(constants.HashZero); + + await subject(); + + const proposalHashAfter = await auctionRebalanceExtension + .connect(subjectCaller.wallet) + .assertionIdToProposalHash(utils.formatBytes32String("win")); + expect(proposalHashAfter).to.eq(constants.HashZero); + }); + }); + + context("when the oracle has the assertion", () => { + beforeEach(async () => { + await optimisticOracleV3Mock.setAsserter(subjectCaller.wallet.address); + }); + it("should revert", async () => { + await expect(subject()).to.be.revertedWith("Oracle has assertion"); + }); + }); + }); + + context("when the oracle address is zero", () => { + beforeEach(async () => { + const [ + currentOptimisticParams, + ruleHash, + ] = await auctionRebalanceExtension.productSettings(); + const optimisticParams = { + ...currentOptimisticParams, + optimisticOracleV3: constants.AddressZero, + }; + await auctionRebalanceExtension.setProductSettings( + optimisticParams, + ruleHash, + ); + }); + + it("should revert", async () => { + await expect(subject()).to.be.revertedWith("Invalid oracle address"); + }); + }); + it("should delete the proposal on a disputed callback", async () => { const proposalHash = await auctionRebalanceExtension .connect(subjectCaller.wallet) .assertionIdToProposalHash(utils.formatBytes32String("win")); expect(proposalHash).to.not.eq(constants.HashZero); - await expect( - optimisticOracleV3Mock - .connect(subjectCaller.wallet) - .mockAssertionDisputedCallback( - auctionRebalanceExtension.address, - utils.formatBytes32String("win"), - ), - ).to.not.be.reverted; + await subject(); const proposalHashAfter = await auctionRebalanceExtension .connect(subjectCaller.wallet) @@ -834,14 +900,7 @@ describe("OptimisticAuctionRebalanceExtensionV1", () => { .connect(subjectCaller.wallet) .assertionIdToProposalHash(utils.formatBytes32String("win")); expect(proposalHash).to.not.eq(constants.HashZero); - await expect( - optimisticOracleV3Mock - .connect(subjectCaller.wallet) - .mockAssertionDisputedCallback( - auctionRebalanceExtension.address, - utils.formatBytes32String("win"), - ), - ).to.not.be.reverted; + await subject(); const proposalHashAfter = await auctionRebalanceExtension .connect(subjectCaller.wallet) From bc4f074e9178edfe6be8a5552ee31cd18dd10c33 Mon Sep 17 00:00:00 2001 From: ckoopmann Date: Thu, 4 Jan 2024 20:12:57 +0800 Subject: [PATCH 30/36] Fix integration tests --- ...imisticAuctionRebalanceExtenisonV1.spec.ts | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/test/integration/ethereum/optimisticAuctionRebalanceExtenisonV1.spec.ts b/test/integration/ethereum/optimisticAuctionRebalanceExtenisonV1.spec.ts index 08b570af..6eab94b2 100644 --- a/test/integration/ethereum/optimisticAuctionRebalanceExtenisonV1.spec.ts +++ b/test/integration/ethereum/optimisticAuctionRebalanceExtenisonV1.spec.ts @@ -4,7 +4,7 @@ import { Address, Account } from "@utils/types"; import { increaseTimeAsync } from "@utils/test"; import { setBlockNumber } from "@utils/test/testingUtils"; import { base58ToHexString } from "@utils/common"; -import { ADDRESS_ZERO, ONE_HOUR_IN_SECONDS, ZERO } from "@utils/constants"; +import { ONE_HOUR_IN_SECONDS, ZERO } from "@utils/constants"; import { OptimisticAuctionRebalanceExtensionV1 } from "@utils/contracts/index"; import { AuctionRebalanceModuleV1, @@ -444,11 +444,11 @@ if (process.env.INTEGRATIONTEST) { describe("assertionDisputedCallback", () => { it("should delete the proposal on a disputed callback", async () => { - const proposal = await auctionRebalanceExtension + const proposalHash = await auctionRebalanceExtension .connect(subjectCaller) - .proposedProduct(proposalId); + .assertionIdToProposalHash(proposalId); - expect(proposal.product).to.eq(dsEth.address); + expect(proposalHash).to.not.eq(ethers.constants.HashZero); await getIndexTokens(await subjectCaller.getAddress(), effectiveBond); await indexToken.connect(subjectCaller).approve(optimisticOracleV3.address, effectiveBond); @@ -456,10 +456,11 @@ if (process.env.INTEGRATIONTEST) { .connect(subjectCaller) .disputeAssertion(proposalId, owner.address); - const proposalAfter = await auctionRebalanceExtension + const proposalHashAfter = await auctionRebalanceExtension .connect(subjectCaller) - .proposedProduct(utils.formatBytes32String("win")); - expect(proposalAfter.product).to.eq(ADDRESS_ZERO); + .assertionIdToProposalHash(proposalId); + + expect(proposalHashAfter).to.eq(ethers.constants.HashZero); }); it("should delete the proposal on a disputed callback from currently set oracle", async () => { @@ -476,11 +477,10 @@ if (process.env.INTEGRATIONTEST) { ), ); - const proposal = await auctionRebalanceExtension + const proposalHash = await auctionRebalanceExtension .connect(subjectCaller) - .proposedProduct(proposalId); - - expect(proposal.product).to.eq(dsEth.address); + .assertionIdToProposalHash(proposalId); + expect(proposalHash).to.not.eq(ethers.constants.HashZero); await expect( optimisticOracleV3Mock @@ -490,10 +490,10 @@ if (process.env.INTEGRATIONTEST) { proposalId, ), ).to.not.be.reverted; - const proposalAfter = await auctionRebalanceExtension + const proposalHashAfter = await auctionRebalanceExtension .connect(subjectCaller) - .proposedProduct(proposalId); - expect(proposalAfter.product).to.eq(ADDRESS_ZERO); + .assertionIdToProposalHash(proposalId); + expect(proposalHashAfter).to.eq(ethers.constants.HashZero); }); }); }); From 3562beff3f0456e6d7f2498ce029b6da92ea94ce Mon Sep 17 00:00:00 2001 From: pblivin0x <84149824+pblivin0x@users.noreply.github.com> Date: Thu, 4 Jan 2024 09:57:10 -0500 Subject: [PATCH 31/36] Update contracts/adapters/AuctionRebalanceExtension.sol --- contracts/adapters/AuctionRebalanceExtension.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/adapters/AuctionRebalanceExtension.sol b/contracts/adapters/AuctionRebalanceExtension.sol index f1a8295f..f511d568 100644 --- a/contracts/adapters/AuctionRebalanceExtension.sol +++ b/contracts/adapters/AuctionRebalanceExtension.sol @@ -73,7 +73,7 @@ contract AuctionRebalanceExtension is BaseExtension { * @param _newComponentsAuctionParams AuctionExecutionParams for new components, indexed corresponding to _newComponents. * @param _oldComponentsAuctionParams AuctionExecutionParams for existing components, indexed corresponding to * the current component positions. Set to 0 for components being removed. - * @param _shouldLockSetToken Indicates if the rebalance should lock the SetToken. Has to be false in this version + * @param _shouldLockSetToken Indicates if the rebalance should lock the SetToken. * @param _rebalanceDuration Duration of the rebalance in seconds. * @param _positionMultiplier Position multiplier at the time target units were calculated. */ From 587ee0b72536d2190df53cf216703bf09fc38fe3 Mon Sep 17 00:00:00 2001 From: Pranav Bhardwaj Date: Thu, 4 Jan 2024 11:10:10 -0500 Subject: [PATCH 32/36] integration test cleanups --- ...ptimisticAuctionRebalanceExtenisonV1.spec.ts | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/test/integration/ethereum/optimisticAuctionRebalanceExtenisonV1.spec.ts b/test/integration/ethereum/optimisticAuctionRebalanceExtenisonV1.spec.ts index 6eab94b2..b0698ce3 100644 --- a/test/integration/ethereum/optimisticAuctionRebalanceExtenisonV1.spec.ts +++ b/test/integration/ethereum/optimisticAuctionRebalanceExtenisonV1.spec.ts @@ -154,8 +154,7 @@ if (process.env.INTEGRATIONTEST) { let identifier: string; beforeEach(async () => { - identifier = utils.formatBytes32String("TestIdentifier"); // TODO: Check how do we ensure that our identifier is supported on UMAs whitelist - await identifierWhitelist.addSupportedIdentifier(identifier); + identifier = "0x4153534552545f54525554480000000000000000000000000000000000000000"; // ASSERT_TRUTH identifier productSettings = { collateral: collateralAssetAddress, @@ -202,7 +201,7 @@ if (process.env.INTEGRATIONTEST) { subjectNewComponents = [contractAddresses.tokens.swETH, contractAddresses.tokens.ETHx]; subjectNewComponentsAuctionParams = [ { // swETH: https://etherscan.io/address/0xf951E335afb289353dc249e82926178EaC7DEd78#readProxyContract#F6 - targetUnit: ether(0.166), // To do: Check target units + targetUnit: "155716754710815260", priceAdapterName: "BoundedStepwiseLinearPriceAdapter", priceAdapterConfigData: await priceAdapter.getEncodedData( ether(1.043), @@ -214,7 +213,7 @@ if (process.env.INTEGRATIONTEST) { ), }, { // ETHx: https://etherscan.io/address/0xcf5ea1b38380f6af39068375516daf40ed70d299#readProxyContract#F5 - targetUnit: ether(0.166), // To do: Check target units + targetUnit: "162815732702576500", priceAdapterName: "BoundedStepwiseLinearPriceAdapter", priceAdapterConfigData: await priceAdapter.getEncodedData( ether(1.014), @@ -229,7 +228,7 @@ if (process.env.INTEGRATIONTEST) { subjectOldComponentsAuctionParams = [ { // wstETH: https://etherscan.io/address/0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0#readContract#F10 - targetUnit: ether(0.166), // To do: Check target units + targetUnit: "148503139447300450", priceAdapterName: "BoundedStepwiseLinearPriceAdapter", priceAdapterConfigData: await priceAdapter.getEncodedData( ether(1.155), @@ -241,7 +240,7 @@ if (process.env.INTEGRATIONTEST) { ), }, { // rETH: https://etherscan.io/address/0xae78736Cd615f374D3085123A210448E74Fc6393#readContract#F6 - targetUnit: ether(0.166), // To do: Check target units + targetUnit: "233170302540761920", priceAdapterName: "BoundedStepwiseLinearPriceAdapter", priceAdapterConfigData: await priceAdapter.getEncodedData( ether(1.097), @@ -253,7 +252,7 @@ if (process.env.INTEGRATIONTEST) { ), }, { // sfrxETH: https://etherscan.io/address/0xac3E018457B222d93114458476f3E3416Abbe38F#readContract#F20 - targetUnit: ether(0.166), // To do: Check target units + targetUnit: "123631627061020350", priceAdapterName: "BoundedStepwiseLinearPriceAdapter", priceAdapterConfigData: await priceAdapter.getEncodedData( ether(1.073), @@ -264,8 +263,8 @@ if (process.env.INTEGRATIONTEST) { ether(1.067), ), }, - { // osETH: Add conversion rate source - targetUnit: ether(0.166), // To do: Check target units + { // osETH: https://etherscan.io/address/0x8023518b2192fb5384dadc596765b3dd1cdfe471#readContract#F3 + targetUnit: "153017509830141340", priceAdapterName: "BoundedStepwiseLinearPriceAdapter", priceAdapterConfigData: await priceAdapter.getEncodedData( ether(1.005), From 3b481b931456d56c9fcc0b987aec5e9278ee1300 Mon Sep 17 00:00:00 2001 From: Pranav Bhardwaj Date: Thu, 4 Jan 2024 13:01:48 -0500 Subject: [PATCH 33/36] fix buy auction params --- .../optimisticAuctionRebalanceExtenisonV1.spec.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/integration/ethereum/optimisticAuctionRebalanceExtenisonV1.spec.ts b/test/integration/ethereum/optimisticAuctionRebalanceExtenisonV1.spec.ts index b0698ce3..a9dc48a1 100644 --- a/test/integration/ethereum/optimisticAuctionRebalanceExtenisonV1.spec.ts +++ b/test/integration/ethereum/optimisticAuctionRebalanceExtenisonV1.spec.ts @@ -205,9 +205,9 @@ if (process.env.INTEGRATIONTEST) { priceAdapterName: "BoundedStepwiseLinearPriceAdapter", priceAdapterConfigData: await priceAdapter.getEncodedData( ether(1.043), - ether(0.001), + ether(0.0005), ONE_HOUR_IN_SECONDS, - true, + false, ether(1.05), ether(1.043), ), @@ -217,9 +217,9 @@ if (process.env.INTEGRATIONTEST) { priceAdapterName: "BoundedStepwiseLinearPriceAdapter", priceAdapterConfigData: await priceAdapter.getEncodedData( ether(1.014), - ether(0.001), + ether(0.0005), ONE_HOUR_IN_SECONDS, - true, + false, ether(1.02), ether(1.014), ), From f1fcb692955f6068ba79818db27ef9bcd624ccc0 Mon Sep 17 00:00:00 2001 From: pblivin0x <84149824+pblivin0x@users.noreply.github.com> Date: Thu, 4 Jan 2024 13:53:43 -0500 Subject: [PATCH 34/36] Update contracts/adapters/OptimisticAuctionRebalanceExtensionV1.sol --- contracts/adapters/OptimisticAuctionRebalanceExtensionV1.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/adapters/OptimisticAuctionRebalanceExtensionV1.sol b/contracts/adapters/OptimisticAuctionRebalanceExtensionV1.sol index 427e715b..829b343e 100644 --- a/contracts/adapters/OptimisticAuctionRebalanceExtensionV1.sol +++ b/contracts/adapters/OptimisticAuctionRebalanceExtensionV1.sol @@ -104,7 +104,7 @@ contract OptimisticAuctionRebalanceExtensionV1 is AuctionRebalanceExtension, As /* ============ State Variables ============ */ - ProductSettings public productSettings; // Mapping of set token to ProductSettings + ProductSettings public productSettings; mapping(bytes32 => bytes32) public assertionIds; // Maps proposal hashes to assertionIds. mapping(bytes32 => bytes32) public assertionIdToProposalHash; // Maps assertionIds to a proposal hash. bool public isOpen; // Bool indicating whether the extension is open for proposing rebalances. From eca86cdb72e239b8a019df0834e7417f49e0a129 Mon Sep 17 00:00:00 2001 From: Pranav Bhardwaj Date: Sat, 6 Jan 2024 14:09:54 -0500 Subject: [PATCH 35/36] cleanups --- contracts/adapters/OptimisticAuctionRebalanceExtensionV1.sol | 2 +- .../ethereum/optimisticAuctionRebalanceExtenisonV1.spec.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/adapters/OptimisticAuctionRebalanceExtensionV1.sol b/contracts/adapters/OptimisticAuctionRebalanceExtensionV1.sol index 427e715b..4b94f371 100644 --- a/contracts/adapters/OptimisticAuctionRebalanceExtensionV1.sol +++ b/contracts/adapters/OptimisticAuctionRebalanceExtensionV1.sol @@ -126,7 +126,7 @@ contract OptimisticAuctionRebalanceExtensionV1 is AuctionRebalanceExtension, As public AuctionRebalanceExtension(_auctionParams.baseManager, _auctionParams.auctionModule) AssetAllowList(_auctionParams.allowedAssets, _auctionParams.useAssetAllowlist) - { } + {} /* ============ Modifier ============ */ diff --git a/test/integration/ethereum/optimisticAuctionRebalanceExtenisonV1.spec.ts b/test/integration/ethereum/optimisticAuctionRebalanceExtenisonV1.spec.ts index a9dc48a1..156ae3b4 100644 --- a/test/integration/ethereum/optimisticAuctionRebalanceExtenisonV1.spec.ts +++ b/test/integration/ethereum/optimisticAuctionRebalanceExtenisonV1.spec.ts @@ -42,7 +42,7 @@ import { ethers } from "hardhat"; const expect = getWaffleExpect(); if (process.env.INTEGRATIONTEST) { - describe.only("OptimisticAuctionRebalanceExtensionV1 - Integration Test dsEth", () => { + describe("OptimisticAuctionRebalanceExtensionV1 - Integration Test dsEth", () => { const contractAddresses = PRODUCTION_ADDRESSES; const liveness = BigNumber.from(60 * 60 * 24 * 2); // 2 days From 8ce942f04c45f682b58163404ba2e407c486ad6b Mon Sep 17 00:00:00 2001 From: ckoopmann Date: Mon, 8 Jan 2024 12:24:07 +0800 Subject: [PATCH 36/36] Dummy commit to retrigger CI