diff --git a/bun.lockb b/bun.lockb index bfecddef..d1abb300 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/foundry.toml b/foundry.toml index c163bb98..aa00631d 100644 --- a/foundry.toml +++ b/foundry.toml @@ -10,9 +10,9 @@ fs_permissions = [{ access = "read-write", path = "./" }] ffi = true solc = '0.8.23' evm_version = 'paris' -via_ir = false +via_ir = true optimizer = true -optimizer_runs = 10000 +optimizer_runs = 1000 remappings = [ '@openzeppelin/=lib/kresko-foundry-helpers/lib/openzeppelin-contracts/', '@oz-upgradeable/=lib/kresko-foundry-helpers/lib/openzeppelin-contracts-upgradeable/contracts/', diff --git a/justfile b/justfile index eda87172..d2cedb63 100644 --- a/justfile +++ b/justfile @@ -15,6 +15,21 @@ hasBun := `bun --help | grep -q 'Usage: bun' && echo true || echo false` hasFoundry := `forge --version | grep -q 'forge' && echo true || echo false` hasPM2 := `bunx pm2 | grep -q 'usage: pm2' && echo true || echo false` +@deploy script sig: + forge script $1 --sig "$2()" --ffi --broadcast \ + --chain arbitrum \ + --fork-url "$RPC_ARBITRUM_INFURA" \ + --verify + +@resume script sig: + forge script $1 --sig "$2()" --ffi --broadcast \ + --chain arbitrum \ + --skip-simulation \ + --rpc-url "$RPC_ARBITRUM_INFURA" \ + --verify \ + --resume + + deploy-local: forge script src/contracts/scripts/deploy/Deploy.s.sol:Deploy \ --sig $(cast calldata "deploy(string,string,uint32,bool,bool)" "localhost" "MNEMONIC_DEVNET" 0 true false) \ diff --git a/out/foundry/deploy/42161/minter-caps-update-latest.json b/out/foundry/deploy/42161/minter-caps-update-latest.json new file mode 100644 index 00000000..7cf6c1d6 --- /dev/null +++ b/out/foundry/deploy/42161/minter-caps-update-latest.json @@ -0,0 +1 @@ +{"MinterMintFacet":{"address":"0x919999B03ca6B1E5F2CA7dC4031A0edD96FEFcb6","newSelectors":1,"oldSelectors":1},"MinterStateFacet":{"address":"0x97515a953227C6771229A4F15B92B064574789f1","newSelectors":10,"oldSelectors":9},"Payload9":{"address":"0xf1160543C721bcc8b0c57469979edf0eAdC18620","ctor":"0x"},"ViewDataFacet":{"address":"0x29451Fa9Eb20Cf1CE23C7C1b5B754EFBB8564862","newSelectors":9,"oldSelectors":9},"diamondCut-MinterLogicUpdate":{"calldata":"0x1f931c1c0000000000000000000000000000000000000000000000000000000000000060000000000000000000000000f1160543c721bcc8b0c57469979edf0eadc186200000000000000000000000000000000000000000000000000000000000000920000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003a0000000000000000000000000000000000000000000000000000000000000056000000000000000000000000000000000000000000000000000000000000007000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000001f2358af300000000000000000000000000000000000000000000000000000000000000000000000000000000919999b03ca6b1e5f2ca7dc4031a0edd96fefcb6000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000001f2358af3000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000009eca7f1f300000000000000000000000000000000000000000000000000000000e7499689000000000000000000000000000000000000000000000000000000009842ad9700000000000000000000000000000000000000000000000000000000e87b1dfa000000000000000000000000000000000000000000000000000000004b94b15800000000000000000000000000000000000000000000000000000000557ff65c00000000000000000000000000000000000000000000000000000000e9364e3e00000000000000000000000000000000000000000000000000000000c015681300000000000000000000000000000000000000000000000000000000c7dc1d640000000000000000000000000000000000000000000000000000000000000000000000000000000097515a953227c6771229a4f15b92b064574789f100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000aeca7f1f300000000000000000000000000000000000000000000000000000000e7499689000000000000000000000000000000000000000000000000000000009842ad9700000000000000000000000000000000000000000000000000000000e87b1dfa000000000000000000000000000000000000000000000000000000004b94b15800000000000000000000000000000000000000000000000000000000557ff65c00000000000000000000000000000000000000000000000000000000e9364e3e00000000000000000000000000000000000000000000000000000000c0156813000000000000000000000000000000000000000000000000000000005e88142800000000000000000000000000000000000000000000000000000000c7dc1d64000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000009cff9fda500000000000000000000000000000000000000000000000000000000ad1e72d700000000000000000000000000000000000000000000000000000000caf0340d00000000000000000000000000000000000000000000000000000000d5854f8d00000000000000000000000000000000000000000000000000000000a244df97000000000000000000000000000000000000000000000000000000000baf4ae20000000000000000000000000000000000000000000000000000000015a4352600000000000000000000000000000000000000000000000000000000ecc0dbc300000000000000000000000000000000000000000000000000000000e4073f6b0000000000000000000000000000000000000000000000000000000000000000000000000000000029451fa9eb20cf1ce23c7c1b5b754efbb8564862000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000009cff9fda500000000000000000000000000000000000000000000000000000000ad1e72d700000000000000000000000000000000000000000000000000000000caf0340d00000000000000000000000000000000000000000000000000000000d5854f8d00000000000000000000000000000000000000000000000000000000a244df97000000000000000000000000000000000000000000000000000000000baf4ae20000000000000000000000000000000000000000000000000000000015a4352600000000000000000000000000000000000000000000000000000000ecc0dbc300000000000000000000000000000000000000000000000000000000e4073f6b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000048129fc1c00000000000000000000000000000000000000000000000000000000","to":"0x0000000000177abD99485DCaea3eFaa91db3fe72"}} \ No newline at end of file diff --git a/src/contracts/core/common/funcs/Assets.sol b/src/contracts/core/common/funcs/Assets.sol index 96e524fc..d8a41c03 100644 --- a/src/contracts/core/common/funcs/Assets.sol +++ b/src/contracts/core/common/funcs/Assets.sol @@ -10,6 +10,9 @@ import {safePrice, SDIPrice} from "common/funcs/Prices.sol"; import {cs} from "common/State.sol"; import {PercentageMath} from "libs/PercentageMath.sol"; import {ms} from "minter/MState.sol"; +import {scdp} from "scdp/SState.sol"; +import {IERC20} from "kresko-lib/token/IERC20.sol"; +import {IKISS} from "kiss/interfaces/IKISS.sol"; library Assets { using WadRay for uint256; @@ -263,4 +266,45 @@ library Assets { } return _maybeRebasedAmount; } + + /** + * @notice Validate that the minter debt limit is not exceeded. + * @param _asset Asset struct of the asset being minted. + * @param _krAsset Address of the kresko asset being minted. + * @param _mintAmount Amount of the kresko asset being minted. + * @dev Reverts if the minter debt limit is exceeded. + */ + function validateMinterDebtLimit(Asset storage _asset, address _krAsset, uint256 _mintAmount) internal view { + uint256 supply = getMinterSupply(_asset, _krAsset); + uint256 newSupply = supply + _mintAmount; + if (newSupply > _asset.maxDebtMinter) { + revert Errors.EXCEEDS_ASSET_MINTING_LIMIT(Errors.id(_krAsset), newSupply, _asset.maxDebtMinter); + } + } + + /** + * @notice Get the minter supply for a given kresko asset. + * @param _asset Asset struct of the asset being minted. + * @param _krAsset Address of the kresko asset being minted. + * @return minterSupply Minter supply for the kresko asset. + */ + function getMinterSupply(Asset storage _asset, address _krAsset) internal view returns (uint256) { + if (_asset.anchor == _krAsset) { + return _getMinterSupplyKiss(_krAsset); + } + return _getMinterSupplyKrAsset(_krAsset, _asset.anchor); + } + + function _getMinterSupplyKrAsset(address _assetAddr, address _anchor) private view returns (uint256) { + IKreskoAssetAnchor anchor = IKreskoAssetAnchor(_anchor); + uint256 supply = anchor.totalSupply() - anchor.balanceOf(_assetAddr) - scdp().assetData[_assetAddr].debt; + if (supply == 0) return 0; + return anchor.convertToAssets(supply); + } + + function _getMinterSupplyKiss(address _assetAddr) private view returns (uint256) { + return + IERC20(_assetAddr).totalSupply() - + (IERC20(IKISS(_assetAddr).vKISS()).balanceOf(_assetAddr) + scdp().assetData[_assetAddr].debt); + } } diff --git a/src/contracts/core/minter/facets/MinterMintFacet.sol b/src/contracts/core/minter/facets/MinterMintFacet.sol index 85b8e6d8..b8eb7866 100644 --- a/src/contracts/core/minter/facets/MinterMintFacet.sol +++ b/src/contracts/core/minter/facets/MinterMintFacet.sol @@ -46,15 +46,13 @@ contract MinterMintFacet is IMinterMintFacet, Modifiers { if (!asset.isMarketOpen()) revert Errors.MARKET_CLOSED(Errors.id(_args.krAsset), asset.ticker.toString()); - uint256 newSupply = IKreskoAsset(_args.krAsset).totalSupply() + _args.amount; - if (newSupply > asset.maxDebtMinter) { - revert Errors.EXCEEDS_ASSET_MINTING_LIMIT(Errors.id(_args.krAsset), newSupply, asset.maxDebtMinter); - } + asset.validateMinterDebtLimit(_args.krAsset, _args.amount); // If there is a fee for opening a position, handle it if (asset.openFee > 0) { handleMinterFee(asset, _args.account, _args.amount, Enums.MinterFee.Open); } + uint256 existingDebt = s.accountDebtAmount(_args.account, _args.krAsset, asset); // The synthetic asset debt position must be greater than the minimum debt position value diff --git a/src/contracts/core/minter/facets/MinterStateFacet.sol b/src/contracts/core/minter/facets/MinterStateFacet.sol index f6fe5558..c3974b38 100644 --- a/src/contracts/core/minter/facets/MinterStateFacet.sol +++ b/src/contracts/core/minter/facets/MinterStateFacet.sol @@ -70,4 +70,8 @@ contract MinterStateFacet is IMinterStateFacet { ) external view returns (uint256 value, uint256 adjustedValue, uint256 price) { return debtAmountToValues(cs().assets[_krAsset], _amount); } + + function getMinterSupply(address _krAsset) external view returns (uint256) { + return cs().assets[_krAsset].getMinterSupply(_krAsset); + } } diff --git a/src/contracts/core/minter/interfaces/IMinterStateFacet.sol b/src/contracts/core/minter/interfaces/IMinterStateFacet.sol index 8d6e55e4..9b6d5c1e 100644 --- a/src/contracts/core/minter/interfaces/IMinterStateFacet.sol +++ b/src/contracts/core/minter/interfaces/IMinterStateFacet.sol @@ -25,6 +25,9 @@ interface IMinterStateFacet { /// @notice get all meaningful protocol parameters function getParametersMinter() external view returns (MinterParams memory); + /// @notice Gets the supply originating from the Minter for @param _asset. + function getMinterSupply(address _asset) external view returns (uint256); + /** * @notice Gets the USD value for a single collateral asset and amount. * @param _collateralAsset The address of the collateral asset. diff --git a/src/contracts/core/periphery/DataV1.sol b/src/contracts/core/periphery/DataV1.sol index b412f035..e9df9770 100644 --- a/src/contracts/core/periphery/DataV1.sol +++ b/src/contracts/core/periphery/DataV1.sol @@ -165,7 +165,6 @@ contract DataV1 is IDataV1 { function getAccount(PythView calldata _prices, address _account) external view returns (DAccount memory result) { result.protocol = DIAMOND.viewAccountData(_prices, _account); - result.protocol.minter.debts = kissFix(_account, _prices); result.vault.addr = VAULT; result.vault.name = IERC20(VAULT).name(); result.vault.amount = IERC20(VAULT).balanceOf(_account); @@ -179,50 +178,6 @@ contract DataV1 is IDataV1 { result.chainId = block.chainid; } - function kissFix(address _account, PythView calldata _prices) internal view returns (View.Position[] memory result) { - IKresko kr = IKresko(address(DIAMOND)); - - View.AssetView[] memory assets = DIAMOND.viewProtocolData(_prices).assets; - address[] memory mintedAssets = kr.getAccountMintedAssets(_account); - - View.Position[] memory found = new View.Position[](assets.length); - uint256 count; - for (uint256 i; i < assets.length; i++) { - View.AssetView memory asset = assets[i]; - if (asset.config.isMinterMintable) { - found[i] = _getMinterPos(_account, asset, kr, mintedAssets); - ++count; - } - } - - result = new View.Position[](count); - for (uint256 j; j < found.length; j++) { - if (found[j].addr != address(0)) result[--count] = found[j]; - } - } - - function _getMinterPos( - address _account, - View.AssetView memory _asset, - IKresko _kr, - address[] memory _mintedAssets - ) internal view returns (View.Position memory) { - uint256 debtAmount = _kr.getAccountDebtAmount(_account, _asset.addr); - uint256 val = debtAmount.wadMul(_asset.price); - return - View.Position({ - addr: _asset.addr, - symbol: _asset.symbol, - amount: debtAmount, - amountAdj: 0, - val: val, - valAdj: val.percentMul(_asset.config.kFactor), - index: _mintedAssets.findIndex(_asset.addr), - price: _asset.price, - config: _asset.config - }); - } - function getBalances( PythView calldata _prices, address _account, diff --git a/src/contracts/core/periphery/ViewData.sol b/src/contracts/core/periphery/ViewData.sol index 15d1e1b9..f2a0e9c8 100644 --- a/src/contracts/core/periphery/ViewData.sol +++ b/src/contracts/core/periphery/ViewData.sol @@ -235,6 +235,7 @@ library ViewFuncs { synthwrap: synthwrap, name: token.name(), tSupply: token.totalSupply(), + mSupply: asset.isMinterMintable ? asset.getMinterSupply(addr) : 0, price: uint256(price.answer), isMarketOpen: asset.isMarketOpen(), priceRaw: price, diff --git a/src/contracts/core/periphery/ViewTypes.sol b/src/contracts/core/periphery/ViewTypes.sol index dcb3df3c..bb4fac40 100644 --- a/src/contracts/core/periphery/ViewTypes.sol +++ b/src/contracts/core/periphery/ViewTypes.sol @@ -107,6 +107,7 @@ library View { address addr; bool isMarketOpen; uint256 tSupply; + uint256 mSupply; uint256 price; Asset config; } diff --git a/src/contracts/scripts/MinterCap.s.sol b/src/contracts/scripts/MinterCap.s.sol new file mode 100644 index 00000000..768a577c --- /dev/null +++ b/src/contracts/scripts/MinterCap.s.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {ArbScript} from "scripts/utils/ArbScript.s.sol"; +import {Help, Log} from "kresko-lib/utils/Libs.s.sol"; +import {ProtocolUpgrader} from "scripts/utils/ProtocolUpgrader.s.sol"; +import {deployPayload} from "scripts/payloads/Payloads.sol"; +import {Task0009} from "scripts/tasks/Task0009.s.sol"; +import {DataV1} from "periphery/DataV1.sol"; + +// solhint-disable no-empty-blocks, reason-string, state-visibility + +contract MinterCapLogicUpdate is ProtocolUpgrader, ArbScript { + using Log for *; + using Help for *; + + address sender; + + function setUp() public virtual { + vm.createSelectFork("arbitrum"); + initUpgrader(kreskoAddr, factoryAddr, CreateMode.Create2); + } + + function payload0009() public output("minter-caps-update") { + broadcastWith(safe); + createFacetCut("MinterMintFacet"); + createFacetCut("MinterStateFacet"); + createFacetCut("ViewDataFacet"); + initializer.initContract = deployPayload(type(Task0009).creationCode, "", 9); + initializer.initData = abi.encodeWithSelector(Task0009.initialize.selector); + executeCuts("MinterLogicUpdate", false); + } + + function afterRun() public { + useMnemonic("MNEMONIC"); + broadcastWith(getAddr(0)); + dataV1 = new DataV1(kreskoAddr, vaultAddr, kissAddr, address(quoter), kreskianAddr, questAddr); + } +} diff --git a/src/contracts/scripts/tasks/Task0009.s.sol b/src/contracts/scripts/tasks/Task0009.s.sol new file mode 100644 index 00000000..25ea0ee5 --- /dev/null +++ b/src/contracts/scripts/tasks/Task0009.s.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; +import {ArbDeployAddr} from "kresko-lib/info/ArbDeployAddr.sol"; +import {Arrays} from "libs/Arrays.sol"; +import {cs} from "common/State.sol"; +import {ms} from "minter/MState.sol"; + +contract Task0009 is ArbDeployAddr { + using Arrays for address[]; + + function initialize() public { + require(cs().assets[kissAddr].maxDebtMinter != 0, "zero"); + require(cs().assets[kissAddr].maxDebtMinter == 140_000 ether, "already-initialized"); + + cs().assets[kissAddr].maxDebtMinter = 60_000 ether; + ms().krAssets.pushUnique(kissAddr); + } +} diff --git a/src/contracts/scripts/utils/ProtocolUpgrader.s.sol b/src/contracts/scripts/utils/ProtocolUpgrader.s.sol new file mode 100644 index 00000000..02f2534b --- /dev/null +++ b/src/contracts/scripts/utils/ProtocolUpgrader.s.sol @@ -0,0 +1,139 @@ +// solhint-disable state-visibility +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {LibVm, Scripted} from "kresko-lib/utils/Scripted.s.sol"; +import {FacetScript, vmFFI} from "kresko-lib/utils/ffi/FacetScript.s.sol"; +import {Help, Log} from "kresko-lib/utils/Libs.s.sol"; +import {Deployed} from "scripts/deploy/libs/Deployed.s.sol"; +import {IDiamondCutFacet, IExtendedDiamondCutFacet} from "diamond/interfaces/IDiamondCutFacet.sol"; +import {FacetCut, Initializer} from "../../core/diamond/DSTypes.sol"; +import {LibDeploy} from "scripts/deploy/libs/LibDeploy.s.sol"; +import {__revert} from "kresko-lib/utils/Base.s.sol"; +import {IDiamondLoupeFacet} from "diamond/interfaces/IDiamondLoupeFacet.sol"; +import {FacetCutAction} from "diamond/DSTypes.sol"; +import {create1, getFacetsAndSelectors} from "scripts/deploy/DeployFuncs.s.sol"; +import {ArbDeployAddr} from "kresko-lib/info/ArbDeployAddr.sol"; + +contract ProtocolUpgrader is Scripted, FacetScript("./utils/getFunctionSelectors.sh") { + using Log for *; + using Help for *; + using Deployed for *; + using LibDeploy for bytes; + enum CreateMode { + Create1, + Create2, + Create3 + } + + IExtendedDiamondCutFacet diamond; + FacetCut[] cuts; + Initializer initializer; + + CreateMode createMode = CreateMode.Create1; + + modifier output(string memory id) { + LibDeploy.initOutputJSON(id); + _; + LibDeploy.writeOutputJSON(); + } + + function initUpgrader(address _kresko, address _factoryAddr, CreateMode _createMode) internal { + diamond = IExtendedDiamondCutFacet(_kresko); + createMode = _createMode; + Deployed.factory(_factoryAddr); + } + + function executeCuts(string memory id, bool _dry) internal { + LibDeploy.JSONKey(("diamondCut-").and(id)); + bytes memory data = abi.encodeWithSelector( + IDiamondCutFacet.diamondCut.selector, + cuts, + initializer.initContract, + initializer.initData + ); + LibDeploy.setJsonAddr("to", address(diamond)); + LibDeploy.setJsonBytes("calldata", data); + LibDeploy.saveJSONKey(); + + if (!_dry) { + (bool success, bytes memory retdata) = address(diamond).call(data); + if (!success) { + __revert(retdata); + } + } + } + + function fullUpgrade() internal { + createFullCut(); + executeCuts("full", false); + } + + function upgradeOrAdd(string memory artifactName) internal { + createFacetCut(artifactName); + executeCuts(artifactName, false); + } + + function createFacetCut(string memory artifact) internal { + (string[] memory files, bytes[] memory facets, bytes4[][] memory selectors) = getFacetsAndSelectors(artifact); + + require(facets.length == 1, "Only one facet should be returned"); + for (uint256 i; i < facets.length; i++) { + handleFacet(files[i], facets[i], selectors[i]); + } + } + + function createFacetCut(string memory name, bytes memory facet, bytes4[] memory selectors) internal returns (address) { + return handleFacet(name, facet, selectors); + } + + function createFullCut() private { + (string[] memory files, bytes[] memory facets, bytes4[][] memory selectors) = getFacetsAndSelectors(); + + for (uint256 i; i < facets.length; i++) { + handleFacet(files[i], facets[i], selectors[i]); + } + } + + function handleFacet( + string memory fileName, + bytes memory facet, + bytes4[] memory selectors + ) private returns (address facetAddr) { + address oldFacet = IDiamondLoupeFacet(address(diamond)).facetAddress(selectors[0]); + oldFacet = IDiamondLoupeFacet(address(diamond)).facetAddress(selectors[selectors.length - 1]); + bytes4[] memory oldSelectors; + if (oldFacet != address(0) && !fileName.equals("")) { + bytes memory code = vm.getDeployedCode(fileName.and(".sol:").and(fileName)); + // skip if code is the same + if (keccak256(abi.encodePacked(code)) == keccak256(abi.encodePacked(oldFacet.code))) { + LibDeploy.JSONKey(fileName.and("-skip")); + LibDeploy.setJsonAddr("address", oldFacet); + LibDeploy.setJsonBool("skipped", true); + LibDeploy.saveJSONKey(); + return oldFacet; + } + + oldSelectors = IDiamondLoupeFacet(address(diamond)).facetFunctionSelectors(oldFacet); + cuts.push(FacetCut({facetAddress: address(0), action: FacetCutAction.Remove, functionSelectors: oldSelectors})); + } + LibDeploy.JSONKey(fileName); + LibDeploy.setJsonNumber("oldSelectors", oldSelectors.length); + facetAddr = _create(fileName, facet); + LibDeploy.setJsonAddr("address", facetAddr); + + cuts.push(FacetCut({facetAddress: facetAddr, action: FacetCutAction.Add, functionSelectors: selectors})); + LibDeploy.setJsonNumber("newSelectors", selectors.length); + LibDeploy.saveJSONKey(); + } + + function _create(string memory _fileName, bytes memory _code) internal returns (address addr) { + if (createMode == CreateMode.Create1) { + addr = create1(_code); + } else if (createMode == CreateMode.Create2) { + addr = _code.d2("", bytes32(bytes(_fileName))).implementation; + } else { + addr = _code.d3("", keccak256(abi.encodePacked(_code))).implementation; + } + } +} diff --git a/src/contracts/test/forked/MulticallUpdate.t.sol b/src/contracts/test/forked/MulticallUpdate.t.sol index 7be8ce40..64c6e2b7 100644 --- a/src/contracts/test/forked/MulticallUpdate.t.sol +++ b/src/contracts/test/forked/MulticallUpdate.t.sol @@ -114,7 +114,8 @@ contract MulticallUpdate is Tested, ArbScript { }) }); exec(0, ops, new bytes[](0)); - krETH.balanceOf(sender).eq(994005248750000000, "krETH-bal"); + krETH.balanceOf(sender).gt(0.95 ether, "krETH-bal"); + krETH.balanceOf(sender).lt(1 ether, "krETH-bal"); } function testSynthwraps() public pranked(sender) {