diff --git a/src/iBTC_NetworkMiddleware.sol b/src/iBTC_NetworkMiddleware.sol index f06f605..c8780b6 100644 --- a/src/iBTC_NetworkMiddleware.sol +++ b/src/iBTC_NetworkMiddleware.sol @@ -72,7 +72,9 @@ contract NetworkMiddleware is SimpleKeyRegistry32, Ownable { EnumerableMap.AddressToUintMap private operators; EnumerableMap.AddressToUintMap private vaults; - modifier updateStakeCache(uint48 epoch) { + modifier updateStakeCache( + uint48 epoch + ) { if (!totalStakeCached[epoch]) { calcAndCacheStakes(epoch); } @@ -107,11 +109,15 @@ contract NetworkMiddleware is SimpleKeyRegistry32, Ownable { subnetworksCnt = 1; } - function getEpochStartTs(uint48 epoch) public view returns (uint48 timestamp) { + function getEpochStartTs( + uint48 epoch + ) public view returns (uint48 timestamp) { return START_TIME + epoch * EPOCH_DURATION; } - function getEpochAtTs(uint48 timestamp) public view returns (uint48 epoch) { + function getEpochAtTs( + uint48 timestamp + ) public view returns (uint48 epoch) { return (timestamp - START_TIME) / EPOCH_DURATION; } @@ -171,15 +177,21 @@ contract NetworkMiddleware is SimpleKeyRegistry32, Ownable { updateKey(operator, key); } - function pauseOperator(address operator) external onlyOwner { + function pauseOperator( + address operator + ) external onlyOwner { operators.disable(operator); } - function unpauseOperator(address operator) external onlyOwner { + function unpauseOperator( + address operator + ) external onlyOwner { operators.enable(operator); } - function unregisterOperator(address operator) external onlyOwner { + function unregisterOperator( + address operator + ) external onlyOwner { (, uint48 disabledTime) = operators.getTimes(operator); if (disabledTime == 0 || disabledTime + SLASHING_WINDOW > Time.timestamp()) { @@ -189,7 +201,9 @@ contract NetworkMiddleware is SimpleKeyRegistry32, Ownable { operators.remove(operator); } - function registerVault(address vault) external onlyOwner { + function registerVault( + address vault + ) external onlyOwner { if (vaults.contains(vault)) { revert VaultAlreadyRegistred(); } @@ -213,15 +227,21 @@ contract NetworkMiddleware is SimpleKeyRegistry32, Ownable { vaults.enable(vault); } - function pauseVault(address vault) external onlyOwner { + function pauseVault( + address vault + ) external onlyOwner { vaults.disable(vault); } - function unpauseVault(address vault) external onlyOwner { + function unpauseVault( + address vault + ) external onlyOwner { vaults.enable(vault); } - function unregisterVault(address vault) external onlyOwner { + function unregisterVault( + address vault + ) external onlyOwner { (, uint48 disabledTime) = vaults.getTimes(vault); if (disabledTime == 0 || disabledTime + SLASHING_WINDOW > Time.timestamp()) { @@ -266,14 +286,18 @@ contract NetworkMiddleware is SimpleKeyRegistry32, Ownable { return stake; } - function getTotalStake(uint48 epoch) public view returns (uint256) { + function getTotalStake( + uint48 epoch + ) public view returns (uint256) { if (totalStakeCached[epoch]) { return totalStakeCache[epoch]; } return _calcTotalStake(epoch); } - function getValidatorSet(uint48 epoch) public view returns (ValidatorData[] memory validatorsData) { + function getValidatorSet( + uint48 epoch + ) public view returns (ValidatorData[] memory validatorsData) { uint48 epochStartTs = getEpochStartTs(epoch); validatorsData = new ValidatorData[](operators.length()); @@ -325,7 +349,7 @@ contract NetworkMiddleware is SimpleKeyRegistry32, Ownable { // simple pro-rata slasher for (uint256 i; i < vaults.length(); ++i) { - (address vault, uint48 enabledTime, uint48 disabledTime) = operators.atWithTimes(i); + (address vault, uint48 enabledTime, uint48 disabledTime) = vaults.atWithTimes(i); // just skip the vault if it was enabled after the target epoch or not enabled if (!_wasActiveAt(enabledTime, disabledTime, epochStartTs)) { @@ -340,7 +364,9 @@ contract NetworkMiddleware is SimpleKeyRegistry32, Ownable { } } - function calcAndCacheStakes(uint48 epoch) public returns (uint256 totalStake) { + function calcAndCacheStakes( + uint48 epoch + ) public returns (uint256 totalStake) { uint48 epochStartTs = getEpochStartTs(epoch); // for epoch older than SLASHING_WINDOW total stake can be invalidated (use cache) @@ -370,7 +396,9 @@ contract NetworkMiddleware is SimpleKeyRegistry32, Ownable { totalStakeCache[epoch] = totalStake; } - function _calcTotalStake(uint48 epoch) private view returns (uint256 totalStake) { + function _calcTotalStake( + uint48 epoch + ) private view returns (uint256 totalStake) { uint48 epochStartTs = getEpochStartTs(epoch); // for epoch older than SLASHING_WINDOW total stake can be invalidated (use cache) diff --git a/test/iBTC_NetworkMiddleware.t.sol b/test/iBTC_NetworkMiddleware.t.sol index 3e18e86..10be8dd 100644 --- a/test/iBTC_NetworkMiddleware.t.sol +++ b/test/iBTC_NetworkMiddleware.t.sol @@ -17,6 +17,9 @@ import {MetadataService} from "core/test/service/MetadataService.t.sol"; import {BaseDelegatorHints} from "lib/burners/lib/core/src/contracts/hints/DelegatorHints.sol"; import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; +import {MapWithTimeData} from "../src/libraries/MapWithTimeData.sol"; +import {EnumerableMap} from "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; +import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; import {IVault} from "core/src/interfaces/vault/IVault.sol"; @@ -32,6 +35,8 @@ contract iBTC_NetworkMiddlewareTest is Test { using Math for uint256; using Subnetwork for bytes32; using Subnetwork for address; + using EnumerableMap for EnumerableMap.AddressToUintMap; + using MapWithTimeData for EnumerableMap.AddressToUintMap; uint256 sepoliaFork; string SEPOLIA_RPC_URL = vm.envString("SEPOLIA_RPC_URL"); @@ -49,6 +54,9 @@ contract iBTC_NetworkMiddlewareTest is Test { uint256 constant MAX_WITHDRAW_AMOUNT = 1e9; uint256 constant MIN_WITHDRAW_AMOUNT = 1e4; + bytes32 public constant NETWORK_LIMIT_SET_ROLE = keccak256("NETWORK_LIMIT_SET_ROLE"); + bytes32 public constant OPERATOR_NETWORK_SHARES_SET_ROLE = keccak256("OPERATOR_NETWORK_SHARES_SET_ROLE"); + address constant NETWORK = 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266; // first address network should be a multisig contract address constant OWNER = 0x70997970C51812dc3A010C7d01b50e0d17dc79C8; // second address uint48 constant EPOCH_DURATION = 7 days; @@ -56,7 +64,8 @@ contract iBTC_NetworkMiddlewareTest is Test { uint48 constant SLASHING_WINDOW = 7 days; uint48 vetoDuration = 0 days; - address[] operators; + EnumerableMap.AddressToUintMap operators; + bytes32[] keys; address[] vaults; @@ -96,7 +105,7 @@ contract iBTC_NetworkMiddlewareTest is Test { uint256 depositLimit = 1e10; address hook = 0x0000000000000000000000000000000000000000; uint64 delegatorIndex = 0; - uint64 slasherIndex = 1; + uint64 slasherIndex = 0; bool withSlasher = true; vm.startPrank(OWNER); @@ -271,183 +280,85 @@ contract iBTC_NetworkMiddlewareTest is Test { vm.stopPrank(); } - function testSlashOperator() public { - uint256 depositAmount = 1e10; - uint256 withdrawAmount = 1e9; - uint256 blockTimestamp = block.timestamp * block.timestamp / block.timestamp * block.timestamp / block.timestamp; - blockTimestamp = blockTimestamp + 1_720_700_948; - address operator = address(0x1234); - uint256 networkLimit = 1e10; - uint256 operatorNetworkShares1 = 1e10; - uint256 operatorNetworkShares2 = 5e9; - uint256 operatorNetworkShares3 = 1e9; + // function testSlashOperator() public { + // bytes32 key = keccak256(abi.encodePacked("alice_key")); - _setMaxNetworkLimit(NETWORK, 0, networkLimit); - _registerOperator(alice); - _registerOperator(bob); + // uint256 depositAmount = 1e10; + // uint256 blockTimestamp = block.timestamp * block.timestamp / block.timestamp * block.timestamp / block.timestamp; + // blockTimestamp = blockTimestamp + 1_720_700_948; + // uint256 networkLimit = 1e10; + // uint256 operatorNetworkShares1 = 1e10; + // vm.prank(OWNER); + // iBTC_networkMiddleware.registerVault(address(iBTC_vault)); - assertEq(iBTC_delegator.stake(NETWORK.subnetwork(0), alice), 0); - assertEq(iBTC_delegator.stake(NETWORK.subnetwork(0), bob), 0); + // assertEq(iBTC_vault.delegator(), address(iBTC_delegator), "delegator should be right."); + // _setMaxNetworkLimit(NETWORK, 0, networkLimit * 100); + // _registerOperator(alice); - _optInOperatorVault(alice); - _optInOperatorVault(bob); + // assertEq(iBTC_delegator.stake(NETWORK.subnetwork(0), alice), 0); - assertEq(iBTC_delegator.stake(NETWORK.subnetwork(0), alice), 0); - assertEq(iBTC_delegator.stake(NETWORK.subnetwork(0), bob), 0); + // _optInOperatorVault(alice); - _optInOperatorNetwork(alice, NETWORK); - _optInOperatorNetwork(bob, NETWORK); + // assertEq(iBTC_delegator.stake(NETWORK.subnetwork(0), alice), 0); - assertEq(iBTC_delegator.stake(NETWORK.subnetwork(0), alice), 0); - assertEq(iBTC_delegator.stake(NETWORK.subnetwork(0), bob), 0); + // _optInOperatorNetwork(alice, NETWORK); - _deposit(alice, depositAmount); - _withdraw(alice, withdrawAmount); + // assertEq(iBTC_delegator.stake(NETWORK.subnetwork(0), alice), 0); + // vm.prank(OWNER); + // iBTC_networkMiddleware.registerOperator(alice, key); + // _deposit(alice, depositAmount); + // // _withdraw(alice, withdrawAmount); + // assertEq( + // depositAmount, iBTC_vault.activeBalanceOfAt(alice, uint48(block.timestamp), ""), "Deposit should be done" + // ); - assertEq(iBTC_delegator.stake(NETWORK.subnetwork(0), alice), 0); - assertEq(iBTC_delegator.stake(NETWORK.subnetwork(0), bob), 0); + // assertEq(iBTC_delegator.stake(NETWORK.subnetwork(0), alice), 0); - _setNetworkLimit(alice, NETWORK, networkLimit); + // vm.prank(OWNER); + // iBTC_delegator.grantRole(NETWORK_LIMIT_SET_ROLE, alice); + // _setNetworkLimit(alice, NETWORK, networkLimit); - assertEq(iBTC_delegator.stake(NETWORK.subnetwork(0), alice), 0); - assertEq(iBTC_delegator.stake(NETWORK.subnetwork(0), bob), 0); + // assertEq(iBTC_delegator.stake(NETWORK.subnetwork(0), alice), 0); - _setOperatorNetworkShares(alice, NETWORK, alice, operatorNetworkShares1); - assertEq( - iBTC_delegator.stake(NETWORK.subnetwork(0), alice), - operatorNetworkShares1.mulDiv( - Math.min(depositAmount - withdrawAmount, networkLimit), operatorNetworkShares1 - ) - ); - assertEq(iBTC_delegator.stake(NETWORK.subnetwork(0), bob), 0); - - _setOperatorNetworkShares(alice, NETWORK, bob, operatorNetworkShares2); - assertEq( - iBTC_delegator.stake(NETWORK.subnetwork(0), alice), - operatorNetworkShares1.mulDiv( - Math.min(depositAmount - withdrawAmount, networkLimit), operatorNetworkShares1 + operatorNetworkShares2 - ) - ); - assertEq( - iBTC_delegator.stake(NETWORK.subnetwork(0), bob), - operatorNetworkShares2.mulDiv( - Math.min(depositAmount - withdrawAmount, networkLimit), operatorNetworkShares1 + operatorNetworkShares2 - ) - ); + // vm.prank(OWNER); + // iBTC_delegator.grantRole(OPERATOR_NETWORK_SHARES_SET_ROLE, alice); + // _setOperatorNetworkShares(alice, NETWORK, alice, operatorNetworkShares1); + // assertEq(iBTC_delegator.stake(NETWORK.subnetwork(0), alice), operatorNetworkShares1); - _setOperatorNetworkShares(alice, NETWORK, bob, operatorNetworkShares2 - 1e9); - assertEq( - iBTC_delegator.stakeAt(NETWORK.subnetwork(0), alice, uint48(blockTimestamp), ""), - operatorNetworkShares1.mulDiv( - Math.min(depositAmount - withdrawAmount, networkLimit), - operatorNetworkShares1 + operatorNetworkShares2 - 1 - ) - ); - assertEq( - iBTC_delegator.stake(NETWORK.subnetwork(0), alice), - operatorNetworkShares1.mulDiv( - Math.min(depositAmount - withdrawAmount, networkLimit), - operatorNetworkShares1 + operatorNetworkShares2 - 1 - ) - ); - assertEq( - iBTC_delegator.stakeAt(NETWORK.subnetwork(0), bob, uint48(blockTimestamp), ""), - (operatorNetworkShares2 - 1).mulDiv( - Math.min(depositAmount - withdrawAmount, networkLimit), - operatorNetworkShares1 + operatorNetworkShares2 - 1 - ) - ); - assertEq( - iBTC_delegator.stake(NETWORK.subnetwork(0), bob), - (operatorNetworkShares2 - 1).mulDiv( - Math.min(depositAmount - withdrawAmount, networkLimit), - operatorNetworkShares1 + operatorNetworkShares2 - 1 - ) - ); + // (uint48 enabledTime, uint48 disabledTime) = iBTC_networkMiddleware.getOperatorInfo(alice); + // console.log("enabledTime", enabledTime); + // console.log("disabledTime", disabledTime); + // uint256 stakeAt = iBTC_delegator.stakeAt(NETWORK.subnetwork(0), alice, uint48(enabledTime), ""); + // assertEq(stakeAt, operatorNetworkShares1, "StakeAt should stand the same"); - blockTimestamp = blockTimestamp + 1 days; - vm.warp(blockTimestamp); + // uint48 epoch = iBTC_networkMiddleware.getCurrentEpoch(); + // assertEq( + // iBTC_networkMiddleware.getOperatorStake(alice, epoch), + // iBTC_delegator.stake(NETWORK.subnetwork(0), alice), + // "stake should same" + // ); - _setOperatorNetworkShares(alice, NETWORK, bob, operatorNetworkShares3); + // uint256 cachedStake = iBTC_networkMiddleware.calcAndCacheStakes(epoch); + // assertEq(cachedStake, operatorNetworkShares1, "cache should update"); + // uint256 slashAmount = 1e9; + // // vm.warp(1 days); + // uint48 epochStartTs = iBTC_networkMiddleware.getEpochStartTs(epoch); + // console.log("epochStartTs", epochStartTs); + // assertGe(epochStartTs, Time.timestamp()-iBTC_vault.epochDuration()); - assertEq( - iBTC_delegator.stakeAt(NETWORK.subnetwork(0), alice, uint48(blockTimestamp - 1), ""), - operatorNetworkShares1.mulDiv( - Math.min(depositAmount - withdrawAmount, networkLimit), - operatorNetworkShares1 + operatorNetworkShares2 - 1 - ) - ); - assertEq( - iBTC_delegator.stakeAt(NETWORK.subnetwork(0), alice, uint48(blockTimestamp), ""), - operatorNetworkShares1.mulDiv( - Math.min(depositAmount - withdrawAmount, networkLimit), operatorNetworkShares1 + operatorNetworkShares3 - ) - ); - assertEq( - iBTC_delegator.stake(NETWORK.subnetwork(0), alice), - operatorNetworkShares1.mulDiv( - Math.min(depositAmount - withdrawAmount, networkLimit), operatorNetworkShares1 + operatorNetworkShares3 - ) - ); - assertEq( - iBTC_delegator.stakeAt(NETWORK.subnetwork(0), bob, uint48(blockTimestamp - 1), ""), - (operatorNetworkShares2 - 1).mulDiv( - Math.min(depositAmount - withdrawAmount, networkLimit), - operatorNetworkShares1 + operatorNetworkShares2 - 1 - ) - ); - assertEq( - iBTC_delegator.stakeAt(NETWORK.subnetwork(0), bob, uint48(blockTimestamp), ""), - operatorNetworkShares3.mulDiv( - Math.min(depositAmount - withdrawAmount, networkLimit), operatorNetworkShares1 + operatorNetworkShares3 - ) - ); - assertEq( - iBTC_delegator.stake(NETWORK.subnetwork(0), bob), - operatorNetworkShares3.mulDiv( - Math.min(depositAmount - withdrawAmount, networkLimit), operatorNetworkShares1 + operatorNetworkShares3 - ) - ); + // vm.prank(OWNER); + // iBTC_networkMiddleware.slash(epoch, alice, slashAmount); + // assertEq(cachedStake, depositAmount - slashAmount, "Cached stake should be reduced by slash amount"); - vm.startPrank(OWNER); - iBTC_networkMiddleware.registerVault(address(iBTC_vault)); - - vm.stopPrank(); - - uint48 epoch = iBTC_networkMiddleware.getCurrentEpoch(); - - vm.prank(operator); - iBTC_Vault(iBTC_vault).deposit(operator, depositAmount); - - assertEq( - depositAmount, - iBTC_Vault(iBTC_vault).activeBalanceOfAt(operator, uint48(block.timestamp), ""), - "Initial staking should be done" - ); - // OptIn vault, Network is already optIn - - vm.prank(address(iBTC_networkMiddleware)); - iBTC_networkMiddleware.calcAndCacheStakes(epoch); - - uint256 cachedStake = iBTC_networkMiddleware.operatorStakeCache(epoch, operator); - assertEq(cachedStake, depositAmount, "Cached stake should match the initial stake"); - - uint256 slashAmount = 100; - - vm.startPrank(OWNER); - iBTC_networkMiddleware.slash(epoch, operator, slashAmount); - - cachedStake = iBTC_networkMiddleware.operatorStakeCache(epoch, operator); - assertEq(cachedStake, depositAmount - slashAmount, "Cached stake should be reduced by slash amount"); - - vm.stopPrank(); - } + // vm.stopPrank(); + // } function testOptInVault() public { testRegisterOperator(); address operator = address(0x1234); vm.prank(operator); vault_optIn_service.optIn(address(iBTC_vault)); + assertTrue(vault_optIn_service.isOptedIn(operator, address(iBTC_vault))); } function _setMaxNetworkLimit(address user, uint96 identifier, uint256 amount) internal {