diff --git a/contracts/lib/eigenlayer-middleware b/contracts/lib/eigenlayer-middleware index a2b1847374..91400d9776 160000 --- a/contracts/lib/eigenlayer-middleware +++ b/contracts/lib/eigenlayer-middleware @@ -1 +1 @@ -Subproject commit a2b1847374350a723a34207578c098821302fa8b +Subproject commit 91400d9776d4f7276a2807a53559f49f7edf5378 diff --git a/contracts/lib/forge-std b/contracts/lib/forge-std index e8a047e3f4..e4aef94c17 160000 --- a/contracts/lib/forge-std +++ b/contracts/lib/forge-std @@ -1 +1 @@ -Subproject commit e8a047e3f40f13fa37af6fe14e6e06283d9a060e +Subproject commit e4aef94c1768803a16fe19f7ce8b65defd027cfd diff --git a/contracts/script/DeployOpenEigenLayer.s.sol b/contracts/script/DeployOpenEigenLayer.s.sol index c634ceaf54..b9fea526de 100644 --- a/contracts/script/DeployOpenEigenLayer.s.sol +++ b/contracts/script/DeployOpenEigenLayer.s.sol @@ -7,18 +7,17 @@ import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.so import "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol"; import "eigenlayer-core/contracts/interfaces/IETHPOSDeposit.sol"; -import "eigenlayer-core/contracts/interfaces/IBeaconChainOracle.sol"; import "eigenlayer-core/contracts/core/StrategyManager.sol"; import "eigenlayer-core/contracts/core/Slasher.sol"; import "eigenlayer-core/contracts/core/DelegationManager.sol"; import "eigenlayer-core/contracts/core/AVSDirectory.sol"; +import "eigenlayer-core/contracts/core/RewardsCoordinator.sol"; import "eigenlayer-core/contracts/strategies/StrategyBaseTVLLimits.sol"; import "eigenlayer-core/contracts/pods/EigenPod.sol"; import "eigenlayer-core/contracts/pods/EigenPodManager.sol"; -import "eigenlayer-core/contracts/pods/DelayedWithdrawalRouter.sol"; import "eigenlayer-core/contracts/permissions/PauserRegistry.sol"; @@ -34,7 +33,15 @@ import "forge-std/Test.sol"; // # To deploy and verify our contract // forge script script/M1_Deploy.s.sol:Deployer_M1 --rpc-url $RPC_URL --private-key $PRIVATE_KEY --broadcast -vvvv contract DeployOpenEigenLayer is Script, Test { - Vm cheats = Vm(HEVM_ADDRESS); + Vm cheats = Vm(VM_ADDRESS); + + uint32 CALCULATION_INTERVAL_SECONDS = 7 days; + uint32 MAX_REWARDS_DURATION = 70 days; + uint32 MAX_RETROACTIVE_LENGTH = 84 days; + uint32 MAX_FUTURE_LENGTH = 28 days; + uint32 GENESIS_REWARDS_TIMESTAMP = 1712188800; + uint32 activationDelay = 7 days; + uint16 globalCommissionBips = 1000; // struct used to encode token info in config file struct StrategyConfig { @@ -55,10 +62,10 @@ contract DeployOpenEigenLayer is Script, Test { StrategyManager public strategyManagerImplementation; EigenPodManager public eigenPodManager; EigenPodManager public eigenPodManagerImplementation; - DelayedWithdrawalRouter public delayedWithdrawalRouter; - DelayedWithdrawalRouter public delayedWithdrawalRouterImplementation; AVSDirectory public avsDirectory; AVSDirectory public avsDirectoryImplementation; + RewardsCoordinator public rewardsCoordinator; + RewardsCoordinator public rewardsCoordinatorImplementation; UpgradeableBeacon public eigenPodBeacon; EigenPod public eigenPodImplementation; StrategyBase public baseStrategyImplementation; @@ -112,17 +119,14 @@ contract DeployOpenEigenLayer is Script, Test { eigenPodManager = EigenPodManager( address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), "")) ); - delayedWithdrawalRouter = DelayedWithdrawalRouter( + rewardsCoordinator = RewardsCoordinator( address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), "")) ); // ETH POS deposit is 0 address eigenPodImplementation = new EigenPod( ethPOSDeposit, - delayedWithdrawalRouter, eigenPodManager, - // uint64(MAX_VALIDATOR_BALANCE_GWEI), - uint64(32 gwei), 1000 // temp genesis time ); @@ -131,6 +135,15 @@ contract DeployOpenEigenLayer is Script, Test { // Second, deploy the *implementation* contracts, using the *proxy contracts* as inputs delegationImplementation = new DelegationManager(strategyManager, slasher, eigenPodManager); avsDirectoryImplementation = new AVSDirectory(delegation); + rewardsCoordinatorImplementation = new RewardsCoordinator( + delegation, + strategyManager, + CALCULATION_INTERVAL_SECONDS, + MAX_REWARDS_DURATION, + MAX_RETROACTIVE_LENGTH, + MAX_FUTURE_LENGTH, + GENESIS_REWARDS_TIMESTAMP + ); strategyManagerImplementation = new StrategyManager(delegation, eigenPodManager, slasher); slasherImplementation = new Slasher(strategyManager, delegation); eigenPodManagerImplementation = new EigenPodManager( @@ -140,7 +153,6 @@ contract DeployOpenEigenLayer is Script, Test { slasher, delegation ); - delayedWithdrawalRouterImplementation = new DelayedWithdrawalRouter(eigenPodManager); // Third, upgrade the proxy contracts to use the correct implementation contracts and initialize them. IStrategy[] memory _strategies; @@ -167,6 +179,19 @@ contract DeployOpenEigenLayer is Script, Test { 0 ) ); + eigenLayerProxyAdmin.upgradeAndCall( + TransparentUpgradeableProxy(payable(address(rewardsCoordinator))), + address(rewardsCoordinatorImplementation), + abi.encodeWithSelector( + RewardsCoordinator.initialize.selector, + executorMultisig, + eigenLayerPauserReg, + 0, + executorMultisig, + activationDelay, + globalCommissionBips + ) + ); eigenLayerProxyAdmin.upgradeAndCall( TransparentUpgradeableProxy(payable(address(slasher))), address(slasherImplementation), @@ -177,23 +202,11 @@ contract DeployOpenEigenLayer is Script, Test { address(eigenPodManagerImplementation), abi.encodeWithSelector( EigenPodManager.initialize.selector, - IBeaconChainOracle(address(0)), executorMultisig, eigenLayerPauserReg, 0 ) ); - eigenLayerProxyAdmin.upgradeAndCall( - TransparentUpgradeableProxy(payable(address(delayedWithdrawalRouter))), - address(delayedWithdrawalRouterImplementation), - abi.encodeWithSelector( - DelayedWithdrawalRouter.initialize.selector, - executorMultisig, - eigenLayerPauserReg, - 0, - 0 - ) - ); // deploy StrategyBaseTVLLimits contract implementation baseStrategyImplementation = new StrategyBaseTVLLimits(strategyManager); diff --git a/contracts/script/EigenDADeployer.s.sol b/contracts/script/EigenDADeployer.s.sol index fba50be2e0..9e10f64124 100644 --- a/contracts/script/EigenDADeployer.s.sol +++ b/contracts/script/EigenDADeployer.s.sol @@ -14,14 +14,25 @@ import {StakeRegistry, IStrategy} from "eigenlayer-middleware/StakeRegistry.sol" import {IStakeRegistry, IDelegationManager} from "eigenlayer-middleware/interfaces/IStakeRegistry.sol"; import {IServiceManager} from "eigenlayer-middleware/interfaces/IServiceManager.sol"; import {IBLSApkRegistry} from "eigenlayer-middleware/interfaces/IBLSApkRegistry.sol"; - -import {EigenDAServiceManager, IAVSDirectory} from "../src/core/EigenDAServiceManager.sol"; +import {EigenDAServiceManager, IAVSDirectory, IRewardsCoordinator} from "../src/core/EigenDAServiceManager.sol"; import {EigenDAHasher} from "../src/libraries/EigenDAHasher.sol"; - +import {EigenDAThresholdRegistry} from "../src/core/EigenDAThresholdRegistry.sol"; +import {EigenDABlobVerifier} from "../src/core/EigenDABlobVerifier.sol"; +import {IEigenDAThresholdRegistry} from "../src/interfaces/IEigenDAThresholdRegistry.sol"; +import {IEigenDABatchMetadataStorage} from "../src/interfaces/IEigenDABatchMetadataStorage.sol"; +import {IEigenDASignatureVerifier} from "../src/interfaces/IEigenDASignatureVerifier.sol"; +import {IEigenDARelayRegistry} from "../src/interfaces/IEigenDARelayRegistry.sol"; +import {IPaymentVault} from "../src/interfaces/IPaymentVault.sol"; +import {PaymentVault} from "../src/payments/PaymentVault.sol"; +import {EigenDADisperserRegistry} from "../src/core/EigenDADisperserRegistry.sol"; +import {IEigenDADisperserRegistry} from "../src/interfaces/IEigenDADisperserRegistry.sol"; +import {EigenDARelayRegistry} from "../src/core/EigenDARelayRegistry.sol"; +import {ISocketRegistry, SocketRegistry} from "eigenlayer-middleware/SocketRegistry.sol"; import {DeployOpenEigenLayer, ProxyAdmin, ERC20PresetFixedSupply, TransparentUpgradeableProxy, IPauserRegistry} from "./DeployOpenEigenLayer.s.sol"; import "forge-std/Test.sol"; import "forge-std/Script.sol"; import "forge-std/StdJson.sol"; +import "../src/interfaces/IEigenDAStructs.sol"; // # To load the variables in the .env file // source .env @@ -34,16 +45,34 @@ contract EigenDADeployer is DeployOpenEigenLayer { BLSApkRegistry public apkRegistry; EigenDAServiceManager public eigenDAServiceManager; + EigenDAThresholdRegistry public eigenDAThresholdRegistry; + EigenDABlobVerifier public eigenDABlobVerifier; RegistryCoordinator public registryCoordinator; IIndexRegistry public indexRegistry; IStakeRegistry public stakeRegistry; + ISocketRegistry public socketRegistry; OperatorStateRetriever public operatorStateRetriever; + IPaymentVault public paymentVault; + EigenDARelayRegistry public eigenDARelayRegistry; + IEigenDADisperserRegistry public eigenDADisperserRegistry; BLSApkRegistry public apkRegistryImplementation; EigenDAServiceManager public eigenDAServiceManagerImplementation; IRegistryCoordinator public registryCoordinatorImplementation; IIndexRegistry public indexRegistryImplementation; IStakeRegistry public stakeRegistryImplementation; + EigenDAThresholdRegistry public eigenDAThresholdRegistryImplementation; + EigenDARelayRegistry public eigenDARelayRegistryImplementation; + ISocketRegistry public socketRegistryImplementation; + IPaymentVault public paymentVaultImplementation; + IEigenDADisperserRegistry public eigenDADisperserRegistryImplementation; + + uint64 _minNumSymbols = 4096; + uint64 _pricePerSymbol = 0.4470 gwei; + uint64 _priceUpdateCooldown = 1; + uint64 _globalSymbolsPerPeriod = 131072; + uint64 _reservationPeriodInterval = 300; + uint64 _globalRatePeriodInterval = 30; struct AddressConfig { address eigenLayerCommunityMultisig; @@ -89,9 +118,7 @@ contract EigenDADeployer is DeployOpenEigenLayer { } emptyContract = new EmptyContract(); - - // hard-coded inputs - + /** * First, deploy upgradeable proxy contracts that **will point** to the implementations. Since the implementation contracts are * not yet deployed, we give these proxies an empty contract as the initial implementation, to act as if they have no code. @@ -99,6 +126,13 @@ contract EigenDADeployer is DeployOpenEigenLayer { eigenDAServiceManager = EigenDAServiceManager( address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenDAProxyAdmin), "")) ); + eigenDAThresholdRegistry = EigenDAThresholdRegistry( + address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenDAProxyAdmin), "")) + ); + eigenDARelayRegistry = EigenDARelayRegistry( + address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenDAProxyAdmin), "")) + ); + registryCoordinator = RegistryCoordinator( address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenDAProxyAdmin), "")) ); @@ -111,6 +145,47 @@ contract EigenDADeployer is DeployOpenEigenLayer { apkRegistry = BLSApkRegistry( address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenDAProxyAdmin), "")) ); + socketRegistry = ISocketRegistry( + address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenDAProxyAdmin), "")) + ); + + { + paymentVault = IPaymentVault( + address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenDAProxyAdmin), "")) + ); + + eigenDADisperserRegistry = IEigenDADisperserRegistry( + address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenDAProxyAdmin), "")) + ); + + paymentVaultImplementation = new PaymentVault(); + + eigenDAProxyAdmin.upgradeAndCall( + TransparentUpgradeableProxy(payable(address(paymentVault))), + address(paymentVaultImplementation), + abi.encodeWithSelector( + PaymentVault.initialize.selector, + addressConfig.eigenDACommunityMultisig, + _minNumSymbols, + _pricePerSymbol, + _priceUpdateCooldown, + _globalSymbolsPerPeriod, + _reservationPeriodInterval, + _globalRatePeriodInterval + ) + ); + } + + eigenDADisperserRegistryImplementation = new EigenDADisperserRegistry(); + + eigenDAProxyAdmin.upgradeAndCall( + TransparentUpgradeableProxy(payable(address(eigenDADisperserRegistry))), + address(eigenDADisperserRegistryImplementation), + abi.encodeWithSelector( + EigenDADisperserRegistry.initialize.selector, + addressConfig.eigenDACommunityMultisig + ) + ); indexRegistryImplementation = new IndexRegistry( registryCoordinator @@ -140,11 +215,19 @@ contract EigenDADeployer is DeployOpenEigenLayer { address(apkRegistryImplementation) ); + socketRegistryImplementation = new SocketRegistry(registryCoordinator); + + eigenDAProxyAdmin.upgrade( + TransparentUpgradeableProxy(payable(address(socketRegistry))), + address(socketRegistryImplementation) + ); + registryCoordinatorImplementation = new RegistryCoordinator( IServiceManager(address(eigenDAServiceManager)), stakeRegistry, apkRegistry, - indexRegistry + indexRegistry, + socketRegistry ); { @@ -186,9 +269,14 @@ contract EigenDADeployer is DeployOpenEigenLayer { } eigenDAServiceManagerImplementation = new EigenDAServiceManager( - IAVSDirectory(address(avsDirectory)), + avsDirectory, + rewardsCoordinator, registryCoordinator, - stakeRegistry + stakeRegistry, + eigenDAThresholdRegistry, + eigenDARelayRegistry, + paymentVault, + eigenDADisperserRegistry ); address[] memory confirmers = new address[](1); @@ -203,10 +291,47 @@ contract EigenDADeployer is DeployOpenEigenLayer { eigenDAPauserReg, 0, addressConfig.eigenDACommunityMultisig, - confirmers + confirmers, + addressConfig.eigenDACommunityMultisig + ) + ); + + eigenDAThresholdRegistryImplementation = new EigenDAThresholdRegistry(); + + VersionedBlobParams[] memory versionedBlobParams = new VersionedBlobParams[](0); + SecurityThresholds memory defaultSecurityThresholds = SecurityThresholds(33, 55); + + eigenDAProxyAdmin.upgradeAndCall( + TransparentUpgradeableProxy(payable(address(eigenDAThresholdRegistry))), + address(eigenDAThresholdRegistryImplementation), + abi.encodeWithSelector( + EigenDAThresholdRegistry.initialize.selector, + addressConfig.eigenDACommunityMultisig, + hex"212121", + hex"373737", + hex"0001", + versionedBlobParams, + defaultSecurityThresholds ) ); operatorStateRetriever = new OperatorStateRetriever(); + + eigenDABlobVerifier = new EigenDABlobVerifier( + IEigenDAThresholdRegistry(address(eigenDAThresholdRegistry)), + IEigenDABatchMetadataStorage(address(eigenDAServiceManager)), + IEigenDASignatureVerifier(address(eigenDAServiceManager)), + IEigenDARelayRegistry(address(eigenDARelayRegistry)), + OperatorStateRetriever(address(operatorStateRetriever)), + IRegistryCoordinator(address(registryCoordinator)) + ); + + eigenDARelayRegistryImplementation = new EigenDARelayRegistry(); + + eigenDAProxyAdmin.upgradeAndCall( + TransparentUpgradeableProxy(payable(address(eigenDARelayRegistry))), + address(eigenDARelayRegistryImplementation), + abi.encodeWithSelector(EigenDARelayRegistry.initialize.selector, addressConfig.eigenDACommunityMultisig) + ); } -} +} \ No newline at end of file diff --git a/contracts/script/EjectionManagerDeployer.s.sol b/contracts/script/EjectionManagerDeployer.s.sol new file mode 100644 index 0000000000..b75375a485 --- /dev/null +++ b/contracts/script/EjectionManagerDeployer.s.sol @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity =0.8.12; + +import {EmptyContract} from "eigenlayer-core/test/mocks/EmptyContract.sol"; +import {EjectionManager} from "eigenlayer-middleware/EjectionManager.sol"; +import {IEjectionManager} from "eigenlayer-middleware/interfaces/IEjectionManager.sol"; +import {RegistryCoordinator} from "eigenlayer-middleware/RegistryCoordinator.sol"; +import {IRegistryCoordinator} from "eigenlayer-middleware/interfaces/IRegistryCoordinator.sol"; +import {StakeRegistry} from "eigenlayer-middleware/StakeRegistry.sol"; +import {IStakeRegistry} from "eigenlayer-middleware/interfaces/IStakeRegistry.sol"; +import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; + +import "forge-std/Test.sol"; +import "forge-std/Script.sol"; +import "forge-std/StdJson.sol"; + +contract Deployer_EjectionManager is Script, Test { + + string public existingDeploymentInfoPath = string(bytes("./script/deploy/mainnet/output/mainnet_deployment_data.json")); + string public deployConfigPath = string(bytes("./script/deploy/mainnet/config/ejector.config.json")); + + address ejectorOwner; + address ejector; + address deployer; + + EjectionManager public ejectionManager; + EjectionManager public ejectionManagerImplementation; + + RegistryCoordinator public registryCoordinator; + StakeRegistry public stakeRegistry; + ProxyAdmin public eigenDAProxyAdmin; + EmptyContract public emptyContract; + + function run() external { + string memory existingDeploymentData = vm.readFile(existingDeploymentInfoPath); + + eigenDAProxyAdmin = ProxyAdmin( + stdJson.readAddress(existingDeploymentData, ".addresses.eigenDAProxyAdmin") + ); + registryCoordinator = RegistryCoordinator( + stdJson.readAddress(existingDeploymentData, ".addresses.registryCoordinator") + ); + stakeRegistry = StakeRegistry( + stdJson.readAddress(existingDeploymentData, ".addresses.stakeRegistry") + ); + + string memory config_data = vm.readFile(deployConfigPath); + + uint256 currentChainId = block.chainid; + uint256 configChainId = stdJson.readUint(config_data, ".chainInfo.chainId"); + emit log_named_uint("You are deploying on ChainID", currentChainId); + require(configChainId == currentChainId, "You are on the wrong chain for this config"); + + ejectorOwner = stdJson.readAddress(config_data, ".permissions.owner"); + ejector = stdJson.readAddress(config_data, ".permissions.ejector"); + deployer = stdJson.readAddress(config_data, ".permissions.deployer"); + + emptyContract = EmptyContract(stdJson.readAddress(config_data, ".permissions.emptyContract")); + + vm.startBroadcast(); + + ejectionManager = EjectionManager( + address(new TransparentUpgradeableProxy(address(emptyContract), address(deployer), "")) + ); + + ejectionManagerImplementation = new EjectionManager( + registryCoordinator, + stakeRegistry + ); + + IEjectionManager.QuorumEjectionParams[] memory quorumEjectionParams = _parseQuorumEjectionParams(config_data); + address[] memory ejectors = new address[](1); + ejectors[0] = ejector; + + TransparentUpgradeableProxy(payable(address(ejectionManager))).upgradeToAndCall( + address(ejectionManagerImplementation), + abi.encodeWithSelector( + EjectionManager.initialize.selector, + ejectorOwner, + ejectors, + quorumEjectionParams + ) + ); + + TransparentUpgradeableProxy(payable(address(ejectionManager))).changeAdmin(address(eigenDAProxyAdmin)); + + vm.stopBroadcast(); + + console.log("EjectionManager deployed at: ", address(ejectionManager)); + console.log("EjectionManagerImplementation deployed at: ", address(ejectionManagerImplementation)); + + _sanityCheck( + ejectionManager, + ejectionManagerImplementation, + config_data + ); + } + + function _sanityCheck( + EjectionManager _ejectionManager, + EjectionManager _ejectionManagerImplementation, + string memory config_data + ) internal { + require(address(_ejectionManager.registryCoordinator()) == address(registryCoordinator), "ejectionManager.registryCoordinator() != registryCoordinator"); + require(address(_ejectionManager.stakeRegistry()) == address(stakeRegistry), "ejectionManager.stakeRegistry() != stakeRegistry"); + require(address(_ejectionManagerImplementation.registryCoordinator()) == address(registryCoordinator), "ejectionManagerImplementation.registryCoordinator() != registryCoordinator"); + require(address(_ejectionManagerImplementation.stakeRegistry()) == address(stakeRegistry), "ejectionManagerImplementation.stakeRegistry() != stakeRegistry"); + + require(eigenDAProxyAdmin.getProxyImplementation( + TransparentUpgradeableProxy(payable(address(_ejectionManager)))) == address(_ejectionManagerImplementation), + "ejectionManager: implementation set incorrectly" + ); + + require(_ejectionManager.owner() == ejectorOwner, "ejectionManager.owner() != ejectorOwner"); + require(_ejectionManager.isEjector(ejector) == true, "ejector != ejector"); + + IEjectionManager.QuorumEjectionParams[] memory quorumEjectionParams = _parseQuorumEjectionParams(config_data); + for (uint8 i = 0; i < quorumEjectionParams.length; ++i) { + (uint32 rateLimitWindow, uint16 ejectableStakePercent) = _ejectionManager.quorumEjectionParams(i); + IEjectionManager.QuorumEjectionParams memory params = IEjectionManager.QuorumEjectionParams( + rateLimitWindow, + ejectableStakePercent + ); + require( + keccak256(abi.encode(params)) == keccak256(abi.encode(quorumEjectionParams[i])), + "ejectionManager.quorumEjectionParams != quorumEjectionParams" + ); + } + } + + function _parseQuorumEjectionParams(string memory config_data) internal returns (IEjectionManager.QuorumEjectionParams[] memory quorumEjectionParams) { + bytes memory quorumEjectionParamsRaw = stdJson.parseRaw(config_data, ".quorumEjectionParams"); + quorumEjectionParams = abi.decode(quorumEjectionParamsRaw, (IEjectionManager.QuorumEjectionParams[])); + } +} diff --git a/contracts/script/GenerateUnitTestHashes.s.sol b/contracts/script/GenerateUnitTestHashes.s.sol index 0418b48fc0..6ae47829ca 100644 --- a/contracts/script/GenerateUnitTestHashes.s.sol +++ b/contracts/script/GenerateUnitTestHashes.s.sol @@ -5,22 +5,22 @@ import "../src/interfaces/IEigenDAServiceManager.sol"; import "forge-std/Script.sol"; import "forge-std/console.sol"; - +import "../src/interfaces/IEigenDAStructs.sol"; // # To generate the hashes needed for core/serialization_test.go: // forge script script/GenerateUnitTestHashes.s.sol -v contract GenerateHashes is Script { - string deployConfigPath = "script/eigenda_deploy_config.json"; + string deployConfigPath = "script/input/eigenda_deploy_config.json"; // deploy all the EigenDA contracts. Relies on many EL contracts having already been deployed. function run() external { - IEigenDAServiceManager.QuorumBlobParam[] memory quorumBlobParam = new IEigenDAServiceManager.QuorumBlobParam[](1); + QuorumBlobParam[] memory quorumBlobParam = new QuorumBlobParam[](1); - quorumBlobParam[0] = IEigenDAServiceManager.QuorumBlobParam({ + quorumBlobParam[0] = QuorumBlobParam({ quorumNumber: 0, adversaryThresholdPercentage: 80, confirmationThresholdPercentage: 100, @@ -37,14 +37,14 @@ contract GenerateHashes is Script { }); - quorumBlobParam[0] = IEigenDAServiceManager.QuorumBlobParam({ + quorumBlobParam[0] = QuorumBlobParam({ quorumNumber: 1, adversaryThresholdPercentage: 80, confirmationThresholdPercentage: 100, chunkLength: 10 }); - IEigenDAServiceManager.BlobHeader memory header = IEigenDAServiceManager.BlobHeader({ + BlobHeader memory header = BlobHeader({ commitment: commitment, dataLength: 10, quorumBlobParams: quorumBlobParam @@ -59,4 +59,4 @@ contract GenerateHashes is Script { } -} +} \ No newline at end of file diff --git a/contracts/script/MockRollupDeployer.s.sol b/contracts/script/MockRollupDeployer.s.sol index c6950fd28c..63c21482e9 100644 --- a/contracts/script/MockRollupDeployer.s.sol +++ b/contracts/script/MockRollupDeployer.s.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.9; import "forge-std/Script.sol"; -import "../src/rollup/MockRollup.sol"; +import "../test/rollupV1/MockRollup.sol"; import {IEigenDAServiceManager} from "../src/interfaces/IEigenDAServiceManager.sol"; contract MockRollupDeployer is Script { diff --git a/contracts/script/SetUpEigenDA.s.sol b/contracts/script/SetUpEigenDA.s.sol index 32ba817008..8fef32148a 100644 --- a/contracts/script/SetUpEigenDA.s.sol +++ b/contracts/script/SetUpEigenDA.s.sol @@ -25,7 +25,7 @@ import "forge-std/StdJson.sol"; // forge script script/Deployer.s.sol:SetupEigenDA --rpc-url $RPC_URL --private-key $PRIVATE_KEY --broadcast -vvvv contract SetupEigenDA is EigenDADeployer, EigenLayerUtils { - string deployConfigPath = "script/eigenda_deploy_config.json"; + string deployConfigPath = "script/input/eigenda_deploy_config.json"; // deploy all the EigenDA contracts. Relies on many EL contracts having already been deployed. function run() external { diff --git a/contracts/script/deploy/holesky/EigenDASM_RewardsUpgrade.s.sol b/contracts/script/deploy/holesky/EigenDASM_RewardsUpgrade.s.sol new file mode 100644 index 0000000000..166f1c7b6b --- /dev/null +++ b/contracts/script/deploy/holesky/EigenDASM_RewardsUpgrade.s.sol @@ -0,0 +1,198 @@ +// SPDX-License-Identifier: BUSL-1.1 +/* +pragma solidity ^0.8.12; + +import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import {ERC20PresetFixedSupply} from "@openzeppelin/contracts/token/ERC20/presets/ERC20PresetFixedSupply.sol"; +import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; +import { + ExistingDeploymentParser, + RewardsCoordinator, + IRewardsCoordinator, + IPauserRegistry, + IStrategy, + IERC20 +} from "eigenlayer-scripts/utils/ExistingDeploymentParser.sol"; +import {IRegistryCoordinator} from "eigenlayer-middleware/interfaces/IRegistryCoordinator.sol"; +import {IStakeRegistry} from "eigenlayer-middleware/interfaces/IStakeRegistry.sol"; + +import {EigenDAServiceManager} from "../../../src/core/EigenDAServiceManager.sol"; + +/** + * @title ServiceManagerBaseUpgrade for Preprod contracts. + * Assumes EOA deploying has permissions to call the proxyAdmin to upgrade. + * + * + * Local Fork: Deploy/Upgrade RewardsCoordinator + * anvil --fork-url $RPC_HOLESKY + * forge script script/deploy/holesky/EigenDASM_RewardsUpgrade.s.sol:ServiceManagerBaseUpgrade --rpc-url http://127.0.0.1:8545 --private-key $PRIVATE_KEY --broadcast -vvvv --sig "run(string memory deployArg)" upgrade + * forge script script/deploy/holesky/EigenDASM_RewardsUpgrade.s.sol:ServiceManagerBaseUpgrade --rpc-url http://127.0.0.1:8545 --private-key $PRIVATE_KEY --broadcast -vvvv --sig "run(string memory deployArg)" deploy + * forge script script/deploy/holesky/EigenDASM_RewardsUpgrade.s.sol:ServiceManagerBaseUpgrade --rpc-url http://127.0.0.1:8545 --private-key $PRIVATE_KEY --broadcast -vvvv --sig "run(string memory deployArg)" createAVSRewardsSubmission + * + * Upgrade Holesky testnet: Deploy/Upgrade RewardsCoordinator + * forge script script/deploy/holesky/EigenDASM_RewardsUpgrade.s.sol:ServiceManagerBaseUpgrade --rpc-url $RPC_HOLESKY --private-key $PRIVATE_KEY --broadcast --verify -vvvv --sig "run(string memory deployArg)" upgrade + * forge script script/deploy/holesky/EigenDASM_RewardsUpgrade.s.sol:ServiceManagerBaseUpgrade --rpc-url $RPC_HOLESKY --private-key $PRIVATE_KEY --broadcast --verify -vvvv --sig "run(string memory deployArg)" deploy + * forge script script/deploy/holesky/EigenDASM_RewardsUpgrade.s.sol:ServiceManagerBaseUpgrade --rpc-url $RPC_HOLESKY --private-key $PRIVATE_KEY --broadcast --verify -vvvv --sig "run(string memory deployArg)" createAVSRewardsSubmission + *//* +contract ServiceManagerBaseUpgrade is ExistingDeploymentParser { + // Hardcode these values to your needs + address public serviceManager = 0x54A03db2784E3D0aCC08344D05385d0b62d4F432; + address public serviceManagerImplementation = 0xFe779fB43280A92cd85466312E2AE8A4F1A48007; + ProxyAdmin public avsProxyAdmin = ProxyAdmin(0x9Fd7E279f5bD692Dc04792151E14Ad814FC60eC1); + address deployerAddress = 0xDA29BB71669f46F2a779b4b62f03644A84eE3479; + address registryCoordinator = 0x2c61EA360D6500b58E7f481541A36B443Bc858c6; + address stakeRegistry = 0x53668EBf2e28180e38B122c641BC51Ca81088871; + + function run(string memory deployArg) external { + // 1. Setup and parse existing EigenLayer Holesky preprod contracts + _parseInitialDeploymentParams( + "script/deploy/holesky/config/eigenlayer_preprod.config.json" + ); + _parseDeployedContracts( + "script/deploy/holesky/config/eigenlayer_preprod_addresses.config.json" + ); + + // 2. broadcast deployment + vm.startBroadcast(); + + emit log_named_address("Deployer Address", msg.sender); + + if (keccak256(abi.encode(deployArg)) == keccak256(abi.encode("upgrade"))) { + _upgradeServiceManager(); + } else if (keccak256(abi.encode(deployArg)) == keccak256(abi.encode("deploy"))) { + _deployServiceManager(); + } else if (keccak256(abi.encode(deployArg)) == keccak256(abi.encode("createAVSRewardsSubmission"))) { + _createAVSRewardsSubmission(); + } + + vm.stopBroadcast(); + + // 3. Sanity Checks + _verifyUpgrade(); + + // Verify Eigenlayer contracts parsed from config + _verifyContractPointers(); + _verifyImplementations(); + _verifyContractsInitialized({isInitialDeployment: false}); + _verifyInitializationParams(); + } + + /// @dev Should override this to change to your specific upgrade needs + function _upgradeServiceManager() internal virtual { + // 1. Deploy new ServiceManager implementation contract + serviceManagerImplementation = address( + new EigenDAServiceManager( + avsDirectory, + rewardsCoordinator, + IRegistryCoordinator(registryCoordinator), + IStakeRegistry(stakeRegistry) + ) + ); + + // 2. Upgrade the ServiceManager proxy to the new implementation + avsProxyAdmin.upgrade( + TransparentUpgradeableProxy(payable(address(serviceManager))), + address(serviceManagerImplementation) + ); + } + + function _deployServiceManager() internal virtual { + IPauserRegistry pauserRegistry = IPauserRegistry(0x9Ab2FEAf0465f0eD51Fc2b663eF228B418c9Dad1); + address emptyContract = 0xc08b788d587F927b49665b90ab35D5224965f3d9; + uint256 initialPausedStatus = 0; + address initialOwner = deployerAddress; + address[] memory batchConfirmers; + + // 1. Deploy new ServiceManager implementation contract + serviceManagerImplementation = address( + new EigenDAServiceManager( + avsDirectory, + rewardsCoordinator, + IRegistryCoordinator(registryCoordinator), + IStakeRegistry(stakeRegistry) + ) + ); + + // 2. Deploy new TUPS and initialize + serviceManager = address( + new TransparentUpgradeableProxy(emptyContract, address(avsProxyAdmin), "") + ); + + avsProxyAdmin.upgradeAndCall( + TransparentUpgradeableProxy(payable(address(serviceManager))), + address(serviceManagerImplementation), + abi.encodeWithSelector( + EigenDAServiceManager.initialize.selector, + eigenLayerPauserReg, + initialPausedStatus, + initialOwner, + batchConfirmers + ) + ); + } + + /// @notice Example createAVSRewardsSubmission call with the ServiceManager + function _createAVSRewardsSubmission() internal { + uint256 mockTokenInitialSupply = 1e30; + address stETHStrategy = 0x5C8b55722f421556a2AAfb7A3EA63d4c3e514312; + address rETHStrategy = 0x87f6C7d24b109919eB38295e3F8298425e6331D9; + + IRewardsCoordinator.StrategyAndMultiplier[] memory strategyAndMultipliers = new IRewardsCoordinator.StrategyAndMultiplier[](2); + // Strategy addresses must be in ascending order + strategyAndMultipliers[0] = IRewardsCoordinator.StrategyAndMultiplier({ + strategy: IStrategy(stETHStrategy), + multiplier: 1e18 + }); + strategyAndMultipliers[1] = IRewardsCoordinator.StrategyAndMultiplier({ + strategy: IStrategy(rETHStrategy), + multiplier: 1e18 + }); + + IERC20 token = new ERC20PresetFixedSupply( + "dog wif hat", + "MOCK1", + mockTokenInitialSupply, + msg.sender + ); + // must be in multiples of weeks i.e startTimestamp % 604800 == 0 + uint32 startTimestamp = 1714608000; + // must be in multiples of weeks i.e duration % 604800 == 0 + uint32 duration = 1 weeks; + // amount <= 1e38 - 1 + uint256 amount = 100e18; + + // 2. Create RewardsSubmission input param + IRewardsCoordinator.RewardsSubmission[] + memory rewardsSubmissions = new IRewardsCoordinator.RewardsSubmission[](1); + rewardsSubmissions[0] = IRewardsCoordinator.RewardsSubmission({ + strategiesAndMultipliers: strategyAndMultipliers, + token: token, + amount: amount, + startTimestamp: startTimestamp, + duration: duration + }); + + token.approve(serviceManager, amount); + EigenDAServiceManager(serviceManager).createAVSRewardsSubmission(rewardsSubmissions); + } + + /// @dev check implementation address set properly + function _verifyUpgrade() internal virtual { + // Preprod RewardsCoordinator + require( + address(rewardsCoordinator) == 0xb22Ef643e1E067c994019A4C19e403253C05c2B0, + "ServiceManagerBaseUpgrade: RewardsCoordinator address is incorrect" + ); + require( + avsProxyAdmin.getProxyImplementation( + TransparentUpgradeableProxy(payable(serviceManager)) + ) == serviceManagerImplementation, + "ServiceManagerBaseUpgrade: ServiceMananger implementation initially set incorrectly" + ); + require( + msg.sender == deployerAddress, + "ServiceManagerBaseUpgrade: deployer address is incorrect" + ); + } +} +*/ \ No newline at end of file diff --git a/contracts/script/deploy/holesky/config/eigenlayer_preprod.config.json b/contracts/script/deploy/holesky/config/eigenlayer_preprod.config.json new file mode 100644 index 0000000000..fd5f3e3bce --- /dev/null +++ b/contracts/script/deploy/holesky/config/eigenlayer_preprod.config.json @@ -0,0 +1,58 @@ +{ + "chainInfo": { + "chainId": 17000 + }, + "multisig_addresses": { + "pauserMultisig": "0x53410249ec7d3a3F9F1ba3912D50D6A3Df6d10A7", + "communityMultisig": "0xCb8d2f9e55Bc7B1FA9d089f9aC80C583D2BDD5F7", + "operationsMultisig": "0xfaEF7338b7490b9E272d80A1a39f4657cAf2b97d", + "executorMultisig": "0x28Ade60640fdBDb2609D8d8734D1b5cBeFc0C348", + "timelock": "0xcF19CE0561052a7A7Ff21156730285997B350A7D" + }, + "strategies": { + "numStrategies": 0, + "MAX_PER_DEPOSIT": 115792089237316195423570985008687907853269984665640564039457584007913129639935, + "MAX_TOTAL_DEPOSITS": 115792089237316195423570985008687907853269984665640564039457584007913129639935, + "strategiesToDeploy": [] + }, + "strategyManager": { + "init_strategy_whitelister": "0x28Ade60640fdBDb2609D8d8734D1b5cBeFc0C348", + "init_paused_status": 0 + }, + "delegationManager": { + "init_paused_status": 0, + "init_minWithdrawalDelayBlocks": 10 + }, + "rewardsCoordinator": { + "init_paused_status": 0, + "CALCULATION_INTERVAL_SECONDS": 604800, + "MAX_REWARDS_DURATION": 6048000, + "MAX_RETROACTIVE_LENGTH": 7776000, + "MAX_FUTURE_LENGTH": 2592000, + "GENESIS_REWARDS_TIMESTAMP": 1710979200, + "rewards_updater_address": "0x18a0f92Ad9645385E8A8f3db7d0f6CF7aBBb0aD4", + "activation_delay": 120, + "calculation_interval_seconds": 604800, + "global_operator_commission_bips": 1000 + }, + "avsDirectory": { + "init_paused_status": 0 + }, + "slasher": { + "init_paused_status": 0 + }, + "eigenPod": { + "MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR": 32000000000, + "GENESIS_TIME": 1695902400 + }, + "eigenPodManager": { + "init_paused_status": 0, + "deneb_fork_timestamp": "1707305664" + }, + "delayedWithdrawalRouter": { + "init_paused_status": 0, + "init_withdrawalDelayBlocks": 10 + }, + "ethPOSDepositAddress": "0x4242424242424242424242424242424242424242", + "beaconOracleAddress": "0x4C116BB629bff7A8373c2378bBd919f8349B8f25" +} \ No newline at end of file diff --git a/contracts/script/deploy/holesky/config/eigenlayer_preprod_addresses.config.json b/contracts/script/deploy/holesky/config/eigenlayer_preprod_addresses.config.json new file mode 100644 index 0000000000..371f58fe62 --- /dev/null +++ b/contracts/script/deploy/holesky/config/eigenlayer_preprod_addresses.config.json @@ -0,0 +1,48 @@ +{ + "addresses": { + "avsDirectory": "0x141d6995556135D4997b2ff72EB443Be300353bC", + "avsDirectoryImplementation": "0x357978adC03375BD6a3605DE055fABb84695d79A", + "baseStrategyImplementation": "0x62450517EfA1CE60d79801daf8f95973865e8D40", + "beaconOracle": "0x4C116BB629bff7A8373c2378bBd919f8349B8f25", + "delayedWithdrawalRouter": "0xC4BC46a87A67a531eCF7f74338E1FA79533334Fa", + "delayedWithdrawalRouterImplementation": "0x0011FA2c512063C495f77296Af8d195F33A8Dd38", + "delegationManager": "0x75dfE5B44C2E530568001400D3f704bC8AE350CC", + "delegationManagerImplementation": "0x56E88cb4f0136fC27D95499dE4BE2acf47946Fa1", + "eigenLayerPauserReg": "0x9Ab2FEAf0465f0eD51Fc2b663eF228B418c9Dad1", + "eigenLayerProxyAdmin": "0x1BEF05C7303d44e0E2FCD2A19d993eDEd4c51b5B", + "eigenPodBeacon": "0x92Cc4a800A1513E85C481dDDf3A06C6921211eaC", + "eigenPodImplementation": "0x2D6c7f9862BD80Cf0d9d93FC6b513D69E7Db7869", + "eigenPodManager": "0xB8d8952f572e67B11e43bC21250967772fa883Ff", + "eigenPodManagerImplementation": "0xc5B857A92245f64e9D90cCc5b096Db82eB77eB5c", + "emptyContract": "0x9690d52B1Ce155DB2ec5eCbF5a262ccCc7B3A6D2", + "rewardsCoordinator": "0xb22Ef643e1E067c994019A4C19e403253C05c2B0", + "rewardsCoordinatorImplementation": "0x76d4D84c90a2AFf213F7D859d2a288685A1a2Ede", + "slasher": "0x12699471dF8dca329C76D72823B1b79d55709384", + "slasherImplementation": "0x9460fCe11E1e0365419fa860599903B4E5097cf0", + "numStrategiesDeployed": 0, + "strategies": {}, + "strategyAddresses": [], + "strategyManager": "0xF9fbF2e35D8803273E214c99BF15174139f4E67a", + "strategyManagerImplementation": "0x1a26B23a004C512350d7Dd89056655A80b850199", + "token": { + "EIGEN": "0xD58f6844f79eB1fbd9f7091d05f7cb30d3363926", + "EIGENImpl": "0x95a7431400F362F3647a69535C5666cA0133CAA0", + "bEIGEN": "0xA72942289a043874249E60469F68f08B8c6ECCe8", + "bEIGENImpl": "0xd5FdabDac3d8ACeAB7BFfDDFA18877A4c5D5Aa82", + "eigenStrategy": "0xdcCF401fD121d8C542E96BC1d0078884422aFAD2", + "eigenStrategyImpl": "0x59D13E7Fb0bC0e57c1fc6594ff701592A6e4dD2B", + "tokenProxyAdmin": "0x1BEF05C7303d44e0E2FCD2A19d993eDEd4c51b5B" + } + }, + "chainInfo": { + "chainId": 17000, + "deploymentBlock": 1477016 + }, + "parameters": { + "communityMultisig": "0xDA29BB71669f46F2a779b4b62f03644A84eE3479", + "executorMultisig": "0xDA29BB71669f46F2a779b4b62f03644A84eE3479", + "operationsMultisig": "0xDA29BB71669f46F2a779b4b62f03644A84eE3479", + "pauserMultisig": "0xDA29BB71669f46F2a779b4b62f03644A84eE3479", + "timelock": "0xcF19CE0561052a7A7Ff21156730285997B350A7D" + } +} \ No newline at end of file diff --git a/contracts/script/deploy/holesky/config/ejector.config.json b/contracts/script/deploy/holesky/config/ejector.config.json new file mode 100644 index 0000000000..220c446d5a --- /dev/null +++ b/contracts/script/deploy/holesky/config/ejector.config.json @@ -0,0 +1,25 @@ +{ + "chainInfo": { + "chainId": 17000 + }, + + "permissions" : { + "owner": "0x28Ade60640fdBDb2609D8d8734D1b5cBeFc0C348", + "ejector": "0xe93765d8462034C43B417e6081BC1e1572c7F55f", + "fallbackEjector": "0xDA29BB71669f46F2a779b4b62f03644A84eE3479", + "deployer": "0xDA29BB71669f46F2a779b4b62f03644A84eE3479", + "emptyContract": "0x9690d52B1Ce155DB2ec5eCbF5a262ccCc7B3A6D2" + }, + + "quorumEjectionParams": [ + { + "0_rateLimitWindow": 604800, + "1_ejectableStakePercent": 3000 + }, + { + "0_rateLimitWindow": 604800, + "1_ejectableStakePercent": 3000 + } + ] + + } \ No newline at end of file diff --git a/contracts/script/deploy/mainnet/Mainnet_Deploy.s.sol b/contracts/script/deploy/mainnet/Mainnet_Deploy.s.sol index 3126e53f36..cdd86e9a52 100644 --- a/contracts/script/deploy/mainnet/Mainnet_Deploy.s.sol +++ b/contracts/script/deploy/mainnet/Mainnet_Deploy.s.sol @@ -1,4 +1,5 @@ // SPDX-License-Identifier: BUSL-1.1 +/* pragma solidity =0.8.12; import {PauserRegistry} from "eigenlayer-core/contracts/permissions/PauserRegistry.sol"; @@ -88,7 +89,7 @@ contract Deployer_Mainnet is ExistingDeploymentParser { /** * First, deploy upgradeable proxy contracts that **will point** to the implementations. Since the implementation contracts are * not yet deployed, we give these proxies an empty contract as the initial implementation, to act as if they have no code. - */ + *//* eigenDAServiceManager = EigenDAServiceManager( address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenDAProxyAdmin), "")) ); @@ -271,7 +272,7 @@ contract Deployer_Mainnet is ExistingDeploymentParser { /** * First, deploy upgradeable proxy contracts that **will point** to the implementations. Since the implementation contracts are * not yet deployed, we give these proxies an empty contract as the initial implementation, to act as if they have no code. - */ + *//* eigenDAServiceManager = EigenDAServiceManager( address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenDAProxyAdmin), "")) ); @@ -556,3 +557,4 @@ contract Deployer_Mainnet is ExistingDeploymentParser { ejector = stdJson.readAddress(config_data, ".permissions.ejector"); } } +*/ \ No newline at end of file diff --git a/contracts/script/deploy/mainnet/config/ejector.config.json b/contracts/script/deploy/mainnet/config/ejector.config.json new file mode 100644 index 0000000000..e55fb07360 --- /dev/null +++ b/contracts/script/deploy/mainnet/config/ejector.config.json @@ -0,0 +1,24 @@ +{ + "chainInfo": { + "chainId": 1 + }, + + "permissions" : { + "owner": "0xBE1685C81aA44FF9FB319dD389addd9374383e90", + "ejector": "0xD2Ee81Cf07B12140C793FcE5B26313CDd9d78eA8", + "deployer": "0x45B866E099a790cbddA655Ca20Cb11168B2cD088", + "emptyContract": "0x1f96861fEFa1065a5A96F20Deb6D8DC3ff48F7f9" + }, + + "quorumEjectionParams": [ + { + "0_rateLimitWindow": 604800, + "1_ejectableStakePercent": 3333 + }, + { + "0_rateLimitWindow": 604800, + "1_ejectableStakePercent": 3333 + } + ] + + } \ No newline at end of file diff --git a/contracts/script/deploy/mainnet/mainnet.config.json b/contracts/script/deploy/mainnet/config/mainnet.config.json similarity index 100% rename from contracts/script/deploy/mainnet/mainnet.config.json rename to contracts/script/deploy/mainnet/config/mainnet.config.json diff --git a/contracts/script/deploy/mainnet/mainnet_addresses.json b/contracts/script/deploy/mainnet/config/mainnet_addresses.json similarity index 100% rename from contracts/script/deploy/mainnet/mainnet_addresses.json rename to contracts/script/deploy/mainnet/config/mainnet_addresses.json diff --git a/contracts/script/deploy/mainnet/mainnet_deployment_data.json b/contracts/script/deploy/mainnet/output/mainnet_deployment_data.json similarity index 100% rename from contracts/script/deploy/mainnet/mainnet_deployment_data.json rename to contracts/script/deploy/mainnet/output/mainnet_deployment_data.json diff --git a/contracts/script/input/.gitkeep b/contracts/script/input/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/contracts/src/Imports.sol b/contracts/src/Imports.sol index 17ea848f2a..dd9470075a 100644 --- a/contracts/src/Imports.sol +++ b/contracts/src/Imports.sol @@ -3,3 +3,4 @@ import "eigenlayer-middleware/OperatorStateRetriever.sol"; import "eigenlayer-middleware/BLSApkRegistry.sol"; import "eigenlayer-middleware/RegistryCoordinator.sol"; +import "eigenlayer-middleware/EjectionManager.sol"; diff --git a/contracts/src/core/EigenDABlobVerifier.sol b/contracts/src/core/EigenDABlobVerifier.sol new file mode 100644 index 0000000000..1d3f623611 --- /dev/null +++ b/contracts/src/core/EigenDABlobVerifier.sol @@ -0,0 +1,209 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.9; + +import {IEigenDABlobVerifier} from "../interfaces/IEigenDABlobVerifier.sol"; +import {IEigenDAThresholdRegistry} from "../interfaces/IEigenDAThresholdRegistry.sol"; +import {IEigenDABatchMetadataStorage} from "../interfaces/IEigenDABatchMetadataStorage.sol"; +import {IEigenDASignatureVerifier} from "../interfaces/IEigenDASignatureVerifier.sol"; +import {EigenDABlobVerificationUtils} from "../libraries/EigenDABlobVerificationUtils.sol"; +import {OperatorStateRetriever} from "lib/eigenlayer-middleware/src/OperatorStateRetriever.sol"; +import {IRegistryCoordinator} from "lib/eigenlayer-middleware/src/RegistryCoordinator.sol"; +import {IEigenDARelayRegistry} from "../interfaces/IEigenDARelayRegistry.sol"; +import "../interfaces/IEigenDAStructs.sol"; + +contract EigenDABlobVerifier is IEigenDABlobVerifier { + + IEigenDAThresholdRegistry public immutable eigenDAThresholdRegistry; + IEigenDABatchMetadataStorage public immutable eigenDABatchMetadataStorage; + IEigenDASignatureVerifier public immutable eigenDASignatureVerifier; + IEigenDARelayRegistry public immutable eigenDARelayRegistry; + + OperatorStateRetriever public immutable operatorStateRetriever; + IRegistryCoordinator public immutable registryCoordinator; + + constructor( + IEigenDAThresholdRegistry _eigenDAThresholdRegistry, + IEigenDABatchMetadataStorage _eigenDABatchMetadataStorage, + IEigenDASignatureVerifier _eigenDASignatureVerifier, + IEigenDARelayRegistry _eigenDARelayRegistry, + OperatorStateRetriever _operatorStateRetriever, + IRegistryCoordinator _registryCoordinator + ) { + eigenDAThresholdRegistry = _eigenDAThresholdRegistry; + eigenDABatchMetadataStorage = _eigenDABatchMetadataStorage; + eigenDASignatureVerifier = _eigenDASignatureVerifier; + eigenDARelayRegistry = _eigenDARelayRegistry; + operatorStateRetriever = _operatorStateRetriever; + registryCoordinator = _registryCoordinator; + } + + ///////////////////////// V1 /////////////////////////////// + + /** + * @notice Verifies a the blob is valid for the required quorums + * @param blobHeader The blob header to verify + * @param blobVerificationProof The blob verification proof to verify the blob against + */ + function verifyBlobV1( + BlobHeader calldata blobHeader, + BlobVerificationProof calldata blobVerificationProof + ) external view { + EigenDABlobVerificationUtils._verifyBlobV1ForQuorums( + eigenDAThresholdRegistry, + eigenDABatchMetadataStorage, + blobHeader, + blobVerificationProof, + quorumNumbersRequired() + ); + } + + /** + * @notice Verifies a batch of blobs for the required quorums + * @param blobHeaders The blob headers to verify + * @param blobVerificationProofs The blob verification proofs to verify the blobs against + */ + function verifyBlobsV1( + BlobHeader[] calldata blobHeaders, + BlobVerificationProof[] calldata blobVerificationProofs + ) external view { + EigenDABlobVerificationUtils._verifyBlobsV1ForQuorums( + eigenDAThresholdRegistry, + eigenDABatchMetadataStorage, + blobHeaders, + blobVerificationProofs, + quorumNumbersRequired() + ); + } + + ///////////////////////// V2 /////////////////////////////// + + /** + * @notice Verifies a blob for the base required quorums and the default security thresholds + * @param batchHeader The batch header of the blob + * @param blobVerificationProof The blob verification proof for the blob + * @param nonSignerStakesAndSignature The nonSignerStakesAndSignature for the blob + */ + function verifyBlobV2( + BatchHeaderV2 calldata batchHeader, + BlobVerificationProofV2 calldata blobVerificationProof, + NonSignerStakesAndSignature calldata nonSignerStakesAndSignature + ) external view { + EigenDABlobVerificationUtils._verifyBlobV2ForQuorums( + eigenDAThresholdRegistry, + eigenDASignatureVerifier, + eigenDARelayRegistry, + batchHeader, + blobVerificationProof, + nonSignerStakesAndSignature, + getDefaultSecurityThresholdsV2(), + blobVerificationProof.blobCertificate.blobHeader.quorumNumbers + ); + } + + /** + * @notice Verifies a blob for the base required quorums and the default security thresholds + * @param signedBatch The signed batch to verify the blob against + * @param blobVerificationProof The blob verification proof for the blob + */ + function verifyBlobV2FromSignedBatch( + SignedBatch calldata signedBatch, + BlobVerificationProofV2 calldata blobVerificationProof + ) external view { + EigenDABlobVerificationUtils._verifyBlobV2ForQuorumsFromSignedBatch( + eigenDAThresholdRegistry, + eigenDASignatureVerifier, + eigenDARelayRegistry, + operatorStateRetriever, + registryCoordinator, + signedBatch, + blobVerificationProof, + getDefaultSecurityThresholdsV2(), + blobVerificationProof.blobCertificate.blobHeader.quorumNumbers + ); + } + + ///////////////////////// HELPER FUNCTIONS /////////////////////////////// + + /** + * @notice Returns the nonSignerStakesAndSignature for a given blob and signed batch + * @param signedBatch The signed batch to get the nonSignerStakesAndSignature for + */ + function getNonSignerStakesAndSignature( + SignedBatch calldata signedBatch + ) external view returns (NonSignerStakesAndSignature memory) { + return EigenDABlobVerificationUtils._getNonSignerStakesAndSignature( + operatorStateRetriever, + registryCoordinator, + signedBatch + ); + } + + /** + * @notice Verifies the security parameters for a blob + * @param blobParams The blob params to verify + * @param securityThresholds The security thresholds to verify against + */ + function verifyBlobSecurityParams( + VersionedBlobParams memory blobParams, + SecurityThresholds memory securityThresholds + ) external view { + EigenDABlobVerificationUtils._verifyBlobSecurityParams(blobParams, securityThresholds); + } + + /** + * @notice Verifies the security parameters for a blob + * @param version The version of the blob to verify + * @param securityThresholds The security thresholds to verify against + */ + function verifyBlobSecurityParams( + uint16 version, + SecurityThresholds memory securityThresholds + ) external view { + EigenDABlobVerificationUtils._verifyBlobSecurityParams(getBlobParams(version), securityThresholds); + } + + /// @notice Returns an array of bytes where each byte represents the adversary threshold percentage of the quorum at that index + function quorumAdversaryThresholdPercentages() external view returns (bytes memory) { + return eigenDAThresholdRegistry.quorumAdversaryThresholdPercentages(); + } + + /// @notice Returns an array of bytes where each byte represents the confirmation threshold percentage of the quorum at that index + function quorumConfirmationThresholdPercentages() external view returns (bytes memory) { + return eigenDAThresholdRegistry.quorumConfirmationThresholdPercentages(); + } + + /// @notice Returns an array of bytes where each byte represents the number of a required quorum + function quorumNumbersRequired() public view returns (bytes memory) { + return eigenDAThresholdRegistry.quorumNumbersRequired(); + } + + function getQuorumAdversaryThresholdPercentage( + uint8 quorumNumber + ) external view returns (uint8){ + return eigenDAThresholdRegistry.getQuorumAdversaryThresholdPercentage(quorumNumber); + } + + /// @notice Gets the confirmation threshold percentage for a quorum + function getQuorumConfirmationThresholdPercentage( + uint8 quorumNumber + ) external view returns (uint8){ + return eigenDAThresholdRegistry.getQuorumConfirmationThresholdPercentage(quorumNumber); + } + + /// @notice Checks if a quorum is required + function getIsQuorumRequired( + uint8 quorumNumber + ) external view returns (bool){ + return eigenDAThresholdRegistry.getIsQuorumRequired(quorumNumber); + } + + /// @notice Returns the blob params for a given blob version + function getBlobParams(uint16 version) public view returns (VersionedBlobParams memory) { + return eigenDAThresholdRegistry.getBlobParams(version); + } + + /// @notice Gets the default security thresholds for V2 + function getDefaultSecurityThresholdsV2() public view returns (SecurityThresholds memory) { + return eigenDAThresholdRegistry.getDefaultSecurityThresholdsV2(); + } +} diff --git a/contracts/src/core/EigenDADisperserRegistry.sol b/contracts/src/core/EigenDADisperserRegistry.sol new file mode 100644 index 0000000000..d21db3d6a2 --- /dev/null +++ b/contracts/src/core/EigenDADisperserRegistry.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.9; + +import {OwnableUpgradeable} from "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol"; +import {EigenDADisperserRegistryStorage} from "./EigenDADisperserRegistryStorage.sol"; +import {IEigenDADisperserRegistry} from "../interfaces/IEigenDADisperserRegistry.sol"; +import "../interfaces/IEigenDAStructs.sol"; + +/** + * @title Registry for EigenDA disperser info + * @author Layr Labs, Inc. + */ +contract EigenDADisperserRegistry is OwnableUpgradeable, EigenDADisperserRegistryStorage, IEigenDADisperserRegistry { + + constructor() { + _disableInitializers(); + } + + function initialize( + address _initialOwner + ) external initializer { + _transferOwnership(_initialOwner); + } + + function setDisperserInfo(uint32 _disperserKey, DisperserInfo memory _disperserInfo) external onlyOwner { + disperserKeyToInfo[_disperserKey] = _disperserInfo; + emit DisperserAdded(_disperserKey, _disperserInfo.disperserAddress); + } + + function disperserKeyToAddress(uint32 _key) external view returns (address) { + return disperserKeyToInfo[_key].disperserAddress; + } +} diff --git a/contracts/src/core/EigenDADisperserRegistryStorage.sol b/contracts/src/core/EigenDADisperserRegistryStorage.sol new file mode 100644 index 0000000000..203581adac --- /dev/null +++ b/contracts/src/core/EigenDADisperserRegistryStorage.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.9; + +import "../interfaces/IEigenDAStructs.sol"; + +/** + * @title Storage variables for the `EigenDADisperserRegistry` contract. + * @author Layr Labs, Inc. + * @notice This storage contract is separate from the logic to simplify the upgrade process. + */ +abstract contract EigenDADisperserRegistryStorage { + + mapping(uint32 => DisperserInfo) public disperserKeyToInfo; + + // storage gap for upgradeability + // slither-disable-next-line shadowing-state + uint256[49] private __GAP; +} \ No newline at end of file diff --git a/contracts/src/core/EigenDARelayRegistry.sol b/contracts/src/core/EigenDARelayRegistry.sol new file mode 100644 index 0000000000..bb1e0bdfb5 --- /dev/null +++ b/contracts/src/core/EigenDARelayRegistry.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.9; + +import {OwnableUpgradeable} from "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol"; +import {EigenDARelayRegistryStorage} from "./EigenDARelayRegistryStorage.sol"; +import {IEigenDARelayRegistry} from "../interfaces/IEigenDARelayRegistry.sol"; +import "../interfaces/IEigenDAStructs.sol"; + +/** + * @title Registry for EigenDA relay keys + * @author Layr Labs, Inc. + */ +contract EigenDARelayRegistry is OwnableUpgradeable, EigenDARelayRegistryStorage, IEigenDARelayRegistry { + + constructor() { + _disableInitializers(); + } + + function initialize( + address _initialOwner + ) external initializer { + _transferOwnership(_initialOwner); + } + + function addRelayInfo(RelayInfo memory relayInfo) external onlyOwner returns (uint32) { + relayKeyToInfo[nextRelayKey] = relayInfo; + emit RelayAdded(relayInfo.relayAddress, nextRelayKey, relayInfo.relayURL); + return nextRelayKey++; + } + + function relayKeyToAddress(uint32 key) external view returns (address) { + return relayKeyToInfo[key].relayAddress; + } + + function relayKeyToUrl(uint32 key) external view returns (string memory) { + return relayKeyToInfo[key].relayURL; + } +} diff --git a/contracts/src/core/EigenDARelayRegistryStorage.sol b/contracts/src/core/EigenDARelayRegistryStorage.sol new file mode 100644 index 0000000000..d0f37f22aa --- /dev/null +++ b/contracts/src/core/EigenDARelayRegistryStorage.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.9; + +import "../interfaces/IEigenDAStructs.sol"; + +/** + * @title Storage variables for the `EigenDARelayRegistry` contract. + * @author Layr Labs, Inc. + * @notice This storage contract is separate from the logic to simplify the upgrade process. + */ +abstract contract EigenDARelayRegistryStorage { + + mapping(uint32 => RelayInfo) public relayKeyToInfo; + + uint32 public nextRelayKey; + + // storage gap for upgradeability + // slither-disable-next-line shadowing-state + uint256[48] private __GAP; +} \ No newline at end of file diff --git a/contracts/src/core/EigenDAServiceManager.sol b/contracts/src/core/EigenDAServiceManager.sol index 80f5795d1c..1d76358bf1 100644 --- a/contracts/src/core/EigenDAServiceManager.sol +++ b/contracts/src/core/EigenDAServiceManager.sol @@ -1,16 +1,20 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: MIT pragma solidity ^0.8.9; import {Pausable} from "eigenlayer-core/contracts/permissions/Pausable.sol"; import {IPauserRegistry} from "eigenlayer-core/contracts/interfaces/IPauserRegistry.sol"; -import {ServiceManagerBase, IAVSDirectory} from "eigenlayer-middleware/ServiceManagerBase.sol"; +import {ServiceManagerBase, IAVSDirectory, IRewardsCoordinator, IServiceManager} from "eigenlayer-middleware/ServiceManagerBase.sol"; import {BLSSignatureChecker} from "eigenlayer-middleware/BLSSignatureChecker.sol"; import {IRegistryCoordinator} from "eigenlayer-middleware/interfaces/IRegistryCoordinator.sol"; import {IStakeRegistry} from "eigenlayer-middleware/interfaces/IStakeRegistry.sol"; - +import {IEigenDAThresholdRegistry} from "../interfaces/IEigenDAThresholdRegistry.sol"; +import {IEigenDARelayRegistry} from "../interfaces/IEigenDARelayRegistry.sol"; +import {IPaymentVault} from "../interfaces/IPaymentVault.sol"; +import {IEigenDADisperserRegistry} from "../interfaces/IEigenDADisperserRegistry.sol"; import {EigenDAServiceManagerStorage} from "./EigenDAServiceManagerStorage.sol"; import {EigenDAHasher} from "../libraries/EigenDAHasher.sol"; +import "../interfaces/IEigenDAStructs.sol"; /** * @title Primary entrypoint for procuring services from EigenDA. @@ -28,17 +32,23 @@ contract EigenDAServiceManager is EigenDAServiceManagerStorage, ServiceManagerBa /// @notice when applied to a function, ensures that the function is only callable by the `batchConfirmer`. modifier onlyBatchConfirmer() { - require(isBatchConfirmer[msg.sender], "onlyBatchConfirmer: not from batch confirmer"); + require(isBatchConfirmer[msg.sender]); _; } constructor( IAVSDirectory __avsDirectory, + IRewardsCoordinator __rewardsCoordinator, IRegistryCoordinator __registryCoordinator, - IStakeRegistry __stakeRegistry + IStakeRegistry __stakeRegistry, + IEigenDAThresholdRegistry __eigenDAThresholdRegistry, + IEigenDARelayRegistry __eigenDARelayRegistry, + IPaymentVault __paymentVault, + IEigenDADisperserRegistry __eigenDADisperserRegistry ) BLSSignatureChecker(__registryCoordinator) - ServiceManagerBase(__avsDirectory, __registryCoordinator, __stakeRegistry) + ServiceManagerBase(__avsDirectory, __rewardsCoordinator, __registryCoordinator, __stakeRegistry) + EigenDAServiceManagerStorage(__eigenDAThresholdRegistry, __eigenDARelayRegistry, __paymentVault, __eigenDADisperserRegistry) { _disableInitializers(); } @@ -47,13 +57,15 @@ contract EigenDAServiceManager is EigenDAServiceManagerStorage, ServiceManagerBa IPauserRegistry _pauserRegistry, uint256 _initialPausedStatus, address _initialOwner, - address[] memory _batchConfirmers + address[] memory _batchConfirmers, + address _rewardsInitiator ) public initializer { _initializePauser(_pauserRegistry, _initialPausedStatus); _transferOwnership(_initialOwner); + _setRewardsInitiator(_rewardsInitiator); for (uint i = 0; i < _batchConfirmers.length; ++i) { _setBatchConfirmer(_batchConfirmers[i]); } @@ -70,21 +82,21 @@ contract EigenDAServiceManager is EigenDAServiceManagerStorage, ServiceManagerBa NonSignerStakesAndSignature memory nonSignerStakesAndSignature ) external onlyWhenNotPaused(PAUSED_CONFIRM_BATCH) onlyBatchConfirmer() { // make sure the information needed to derive the non-signers and batch is in calldata to avoid emitting events - require(tx.origin == msg.sender, "EigenDAServiceManager.confirmBatch: header and nonsigner data must be in calldata"); + require(tx.origin == msg.sender, "header and nonsigner data must be in calldata"); // make sure the stakes against which the Batch is being confirmed are not stale require( - batchHeader.referenceBlockNumber < block.number, "EigenDAServiceManager.confirmBatch: specified referenceBlockNumber is in future" + batchHeader.referenceBlockNumber < block.number, "specified referenceBlockNumber is in future" ); require( (batchHeader.referenceBlockNumber + BLOCK_STALE_MEASURE) >= uint32(block.number), - "EigenDAServiceManager.confirmBatch: specified referenceBlockNumber is too far in past" + "specified referenceBlockNumber is too far in past" ); //make sure that the quorumNumbers and signedStakeForQuorums are of the same length require( batchHeader.quorumNumbers.length == batchHeader.signedStakeForQuorums.length, - "EigenDAServiceManager.confirmBatch: quorumNumbers and signedStakeForQuorums must be of the same length" + "quorumNumbers and signedStakeForQuorums must be same length" ); // calculate reducedBatchHeaderHash which nodes signed @@ -107,8 +119,8 @@ contract EigenDAServiceManager is EigenDAServiceManagerStorage, ServiceManagerBa // signed stake > total stake require( quorumStakeTotals.signedStakeForQuorum[i] * THRESHOLD_DENOMINATOR >= - quorumStakeTotals.totalStakeForQuorum[i] * uint8(batchHeader.signedStakeForQuorums[i]), - "EigenDAServiceManager.confirmBatch: signatories do not own at least threshold percentage of a quorum" + quorumStakeTotals.totalStakeForQuorum[i] * uint8(batchHeader.signedStakeForQuorums[i]), + "signatories do not own threshold percentage of a quorum" ); } @@ -144,4 +156,48 @@ contract EigenDAServiceManager is EigenDAServiceManagerStorage, ServiceManagerBa return referenceBlockNumber + STORE_DURATION_BLOCKS + BLOCK_STALE_MEASURE; } + /// @notice Returns the bytes array of quorumAdversaryThresholdPercentages + function quorumAdversaryThresholdPercentages() external view returns (bytes memory) { + return eigenDAThresholdRegistry.quorumAdversaryThresholdPercentages(); + } + + /// @notice Returns the bytes array of quorumAdversaryThresholdPercentages + function quorumConfirmationThresholdPercentages() external view returns (bytes memory) { + return eigenDAThresholdRegistry.quorumConfirmationThresholdPercentages(); + } + + /// @notice Returns the bytes array of quorumsNumbersRequired + function quorumNumbersRequired() external view returns (bytes memory) { + return eigenDAThresholdRegistry.quorumNumbersRequired(); + } + + function getQuorumAdversaryThresholdPercentage( + uint8 quorumNumber + ) external view returns (uint8){ + return eigenDAThresholdRegistry.getQuorumAdversaryThresholdPercentage(quorumNumber); + } + + /// @notice Gets the confirmation threshold percentage for a quorum + function getQuorumConfirmationThresholdPercentage( + uint8 quorumNumber + ) external view returns (uint8){ + return eigenDAThresholdRegistry.getQuorumConfirmationThresholdPercentage(quorumNumber); + } + + /// @notice Checks if a quorum is required + function getIsQuorumRequired( + uint8 quorumNumber + ) external view returns (bool){ + return eigenDAThresholdRegistry.getIsQuorumRequired(quorumNumber); + } + + /// @notice Gets the default security thresholds for V2 + function getDefaultSecurityThresholdsV2() external view returns (SecurityThresholds memory) { + return eigenDAThresholdRegistry.getDefaultSecurityThresholdsV2(); + } + + /// @notice Returns the blob params for a given blob version + function getBlobParams(uint16 version) external view returns (VersionedBlobParams memory) { + return eigenDAThresholdRegistry.getBlobParams(version); + } } \ No newline at end of file diff --git a/contracts/src/core/EigenDAServiceManagerStorage.sol b/contracts/src/core/EigenDAServiceManagerStorage.sol index 9c97cecebb..b482c7b201 100644 --- a/contracts/src/core/EigenDAServiceManagerStorage.sol +++ b/contracts/src/core/EigenDAServiceManagerStorage.sol @@ -1,7 +1,11 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: MIT pragma solidity ^0.8.9; import {IEigenDAServiceManager} from "../interfaces/IEigenDAServiceManager.sol"; +import {IEigenDAThresholdRegistry} from "../interfaces/IEigenDAThresholdRegistry.sol"; +import {IEigenDARelayRegistry} from "../interfaces/IEigenDARelayRegistry.sol"; +import {IPaymentVault} from "../interfaces/IPaymentVault.sol"; +import {IEigenDADisperserRegistry} from "../interfaces/IEigenDADisperserRegistry.sol"; /** * @title Storage variables for the `EigenDAServiceManager` contract. @@ -12,7 +16,6 @@ abstract contract EigenDAServiceManagerStorage is IEigenDAServiceManager { // CONSTANTS uint256 public constant THRESHOLD_DENOMINATOR = 100; - //TODO: mechanism to change any of these values? /// @notice Unit of measure (in blocks) for which data will be stored for after confirmation. uint32 public constant STORE_DURATION_BLOCKS = 2 weeks / 12 seconds; @@ -26,7 +29,7 @@ abstract contract EigenDAServiceManagerStorage is IEigenDAServiceManager { * * Note that this parameter needs to accommodate the delays which are introduced by the disperser, which are of two types: * - FinalizationBlockDelay: when initializing a batch, the disperser will use a ReferenceBlockNumber which is this many - * blocks behind the current block number. This is to ensure that the the operator state associated with the reference block + * blocks behind the current block number. This is to ensure that the operator state associated with the reference block * will be stable. * - BatchInterval: the batch itself will only be confirmed after the batch interval has passed. * @@ -36,26 +39,22 @@ abstract contract EigenDAServiceManagerStorage is IEigenDAServiceManager { */ uint32 public constant BLOCK_STALE_MEASURE = 300; - /** - * @notice The quorum adversary threshold percentages stored as an ordered bytes array - * this is the percentage of the total stake that must be adversarial to consider a blob invalid. - * The first byte is the threshold for quorum 0, the second byte is the threshold for quorum 1, etc. - */ - bytes public constant quorumAdversaryThresholdPercentages = hex"21"; - - /** - * @notice The quorum confirmation threshold percentages stored as an ordered bytes array - * this is the percentage of the total stake needed to confirm a blob. - * The first byte is the threshold for quorum 0, the second byte is the threshold for quorum 1, etc. - */ - bytes public constant quorumConfirmationThresholdPercentages = hex"37"; - - /** - * @notice The quorum numbers required for confirmation stored as an ordered bytes array - * these quorum numbers have respective canonical thresholds in the - * quorumConfirmationThresholdPercentages and quorumAdversaryThresholdPercentages above. - */ - bytes public constant quorumNumbersRequired = hex"00"; + IEigenDAThresholdRegistry public immutable eigenDAThresholdRegistry; + IEigenDARelayRegistry public immutable eigenDARelayRegistry; + IPaymentVault public immutable paymentVault; + IEigenDADisperserRegistry public immutable eigenDADisperserRegistry; + + constructor( + IEigenDAThresholdRegistry _eigenDAThresholdRegistry, + IEigenDARelayRegistry _eigenDARelayRegistry, + IPaymentVault _paymentVault, + IEigenDADisperserRegistry _eigenDADisperserRegistry + ) { + eigenDAThresholdRegistry = _eigenDAThresholdRegistry; + eigenDARelayRegistry = _eigenDARelayRegistry; + paymentVault = _paymentVault; + eigenDADisperserRegistry = _eigenDADisperserRegistry; + } /// @notice The current batchId uint32 public batchId; @@ -69,4 +68,4 @@ abstract contract EigenDAServiceManagerStorage is IEigenDAServiceManager { // storage gap for upgradeability // slither-disable-next-line shadowing-state uint256[47] private __GAP; -} +} \ No newline at end of file diff --git a/contracts/src/core/EigenDAThresholdRegistry.sol b/contracts/src/core/EigenDAThresholdRegistry.sol new file mode 100644 index 0000000000..871d995287 --- /dev/null +++ b/contracts/src/core/EigenDAThresholdRegistry.sol @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.9; + +import {EigenDAThresholdRegistryStorage} from "./EigenDAThresholdRegistryStorage.sol"; +import {IEigenDAThresholdRegistry} from "../interfaces/IEigenDAThresholdRegistry.sol"; +import {OwnableUpgradeable} from "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol"; +import {BitmapUtils} from "eigenlayer-middleware/libraries/BitmapUtils.sol"; +import "../interfaces/IEigenDAStructs.sol"; + +/** + * @title The `EigenDAThresholdRegistry` contract. + * @author Layr Labs, Inc. + */ +contract EigenDAThresholdRegistry is EigenDAThresholdRegistryStorage, OwnableUpgradeable { + + constructor() { + _disableInitializers(); + } + + function initialize( + address _initialOwner, + bytes memory _quorumAdversaryThresholdPercentages, + bytes memory _quorumConfirmationThresholdPercentages, + bytes memory _quorumNumbersRequired, + VersionedBlobParams[] memory _versionedBlobParams, + SecurityThresholds memory _defaultSecurityThresholdsV2 + ) external initializer { + _transferOwnership(_initialOwner); + + quorumAdversaryThresholdPercentages = _quorumAdversaryThresholdPercentages; + quorumConfirmationThresholdPercentages = _quorumConfirmationThresholdPercentages; + quorumNumbersRequired = _quorumNumbersRequired; + defaultSecurityThresholdsV2 = _defaultSecurityThresholdsV2; + + for (uint256 i = 0; i < _versionedBlobParams.length; ++i) { + _addVersionedBlobParams(_versionedBlobParams[i]); + } + } + + function updateQuorumAdversaryThresholdPercentages(bytes memory _quorumAdversaryThresholdPercentages) external onlyOwner { + emit QuorumAdversaryThresholdPercentagesUpdated(quorumAdversaryThresholdPercentages, _quorumAdversaryThresholdPercentages); + quorumAdversaryThresholdPercentages = _quorumAdversaryThresholdPercentages; + } + + function updateQuorumConfirmationThresholdPercentages(bytes memory _quorumConfirmationThresholdPercentages) external onlyOwner { + emit QuorumConfirmationThresholdPercentagesUpdated(quorumConfirmationThresholdPercentages, _quorumConfirmationThresholdPercentages); + quorumConfirmationThresholdPercentages = _quorumConfirmationThresholdPercentages; + } + + function updateQuorumNumbersRequired(bytes memory _quorumNumbersRequired) external onlyOwner { + emit QuorumNumbersRequiredUpdated(quorumNumbersRequired, _quorumNumbersRequired); + quorumNumbersRequired = _quorumNumbersRequired; + } + + function updateDefaultSecurityThresholdsV2(SecurityThresholds memory _defaultSecurityThresholdsV2) external onlyOwner { + emit DefaultSecurityThresholdsV2Updated(defaultSecurityThresholdsV2, _defaultSecurityThresholdsV2); + defaultSecurityThresholdsV2 = _defaultSecurityThresholdsV2; + } + + function addVersionedBlobParams(VersionedBlobParams memory _versionedBlobParams) external onlyOwner returns (uint16) { + return _addVersionedBlobParams(_versionedBlobParams); + } + + function _addVersionedBlobParams(VersionedBlobParams memory _versionedBlobParams) internal returns (uint16) { + versionedBlobParams[nextBlobVersion] = _versionedBlobParams; + emit VersionedBlobParamsAdded(nextBlobVersion, _versionedBlobParams); + return nextBlobVersion++; + } + + ///////////////////////// V1 /////////////////////////////// + + /// @notice Gets the adversary threshold percentage for a quorum + function getQuorumAdversaryThresholdPercentage( + uint8 quorumNumber + ) public view virtual returns (uint8 adversaryThresholdPercentage) { + if(quorumAdversaryThresholdPercentages.length > quorumNumber){ + adversaryThresholdPercentage = uint8(quorumAdversaryThresholdPercentages[quorumNumber]); + } + } + + /// @notice Gets the confirmation threshold percentage for a quorum + function getQuorumConfirmationThresholdPercentage( + uint8 quorumNumber + ) public view virtual returns (uint8 confirmationThresholdPercentage) { + if(quorumConfirmationThresholdPercentages.length > quorumNumber){ + confirmationThresholdPercentage = uint8(quorumConfirmationThresholdPercentages[quorumNumber]); + } + } + + /// @notice Checks if a quorum is required + function getIsQuorumRequired( + uint8 quorumNumber + ) public view virtual returns (bool) { + uint256 quorumBitmap = BitmapUtils.setBit(0, quorumNumber); + return (quorumBitmap & BitmapUtils.orderedBytesArrayToBitmap(quorumNumbersRequired) == quorumBitmap); + } + + ///////////////////////// V2 /////////////////////////////// + + /// @notice Gets the default security thresholds for V2 + function getDefaultSecurityThresholdsV2() external view returns (SecurityThresholds memory) { + return defaultSecurityThresholdsV2; + } + + /// @notice Returns the blob params for a given blob version + function getBlobParams(uint16 version) external view returns (VersionedBlobParams memory) { + return versionedBlobParams[version]; + } +} \ No newline at end of file diff --git a/contracts/src/core/EigenDAThresholdRegistryStorage.sol b/contracts/src/core/EigenDAThresholdRegistryStorage.sol new file mode 100644 index 0000000000..377fb8a037 --- /dev/null +++ b/contracts/src/core/EigenDAThresholdRegistryStorage.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.9; + +import {IEigenDAThresholdRegistry} from "../interfaces/IEigenDAThresholdRegistry.sol"; +import "../interfaces/IEigenDAStructs.sol"; + +/** + * @title Storage variables for the `EigenDAThresholdRegistry` contract. + * @author Layr Labs, Inc. + * @notice This storage contract is separate from the logic to simplify the upgrade process. + */ +abstract contract EigenDAThresholdRegistryStorage is IEigenDAThresholdRegistry { + + bytes public quorumAdversaryThresholdPercentages; + + bytes public quorumConfirmationThresholdPercentages; + + bytes public quorumNumbersRequired; + + uint16 public nextBlobVersion; + + mapping(uint16 => VersionedBlobParams) public versionedBlobParams; + + SecurityThresholds public defaultSecurityThresholdsV2; + + uint256[44] private __GAP; +} \ No newline at end of file diff --git a/contracts/src/interfaces/IEigenDABatchMetadataStorage.sol b/contracts/src/interfaces/IEigenDABatchMetadataStorage.sol new file mode 100644 index 0000000000..1904c505c7 --- /dev/null +++ b/contracts/src/interfaces/IEigenDABatchMetadataStorage.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.9; + +interface IEigenDABatchMetadataStorage { + function batchIdToBatchMetadataHash(uint32 batchId) external view returns (bytes32); +} \ No newline at end of file diff --git a/contracts/src/interfaces/IEigenDABlobVerifier.sol b/contracts/src/interfaces/IEigenDABlobVerifier.sol new file mode 100644 index 0000000000..31faeb4239 --- /dev/null +++ b/contracts/src/interfaces/IEigenDABlobVerifier.sol @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.9; + +import {IEigenDAThresholdRegistry} from "./IEigenDAThresholdRegistry.sol"; +import "./IEigenDAStructs.sol"; + +interface IEigenDABlobVerifier is IEigenDAThresholdRegistry { + + /** + * @notice Verifies a the blob is valid for the required quorums + * @param blobHeader The blob header to verify + * @param blobVerificationProof The blob verification proof to verify the blob against + */ + function verifyBlobV1( + BlobHeader calldata blobHeader, + BlobVerificationProof calldata blobVerificationProof + ) external view; + + + /** + * @notice Verifies a batch of blobs for the required quorums + * @param blobHeaders The blob headers to verify + * @param blobVerificationProofs The blob verification proofs to verify the blobs against + */ + function verifyBlobsV1( + BlobHeader[] calldata blobHeaders, + BlobVerificationProof[] calldata blobVerificationProofs + ) external view; + + /** + * @notice Verifies a blob for the required quorums and the default security thresholds + * @param batchHeader The batch header of the blob + * @param blobVerificationProof The blob verification proof for the blob + * @param nonSignerStakesAndSignature The nonSignerStakesAndSignature to verify the blob against + */ + function verifyBlobV2( + BatchHeaderV2 calldata batchHeader, + BlobVerificationProofV2 calldata blobVerificationProof, + NonSignerStakesAndSignature calldata nonSignerStakesAndSignature + ) external view; + + /** + * @notice Verifies a blob for the base required quorums and the default security thresholds + * @param signedBatch The signed batch to verify the blob against + * @param blobVerificationProof The blob verification proof for the blob + */ + function verifyBlobV2FromSignedBatch( + SignedBatch calldata signedBatch, + BlobVerificationProofV2 calldata blobVerificationProof + ) external view; + + /** + * @notice Returns the nonSignerStakesAndSignature for a given blob and signed batch + * @param signedBatch The signed batch to get the nonSignerStakesAndSignature for + */ + function getNonSignerStakesAndSignature( + SignedBatch calldata signedBatch + ) external view returns (NonSignerStakesAndSignature memory); + + /** + * @notice Verifies the security parameters for a blob + * @param blobParams The blob params to verify + * @param securityThresholds The security thresholds to verify against + */ + function verifyBlobSecurityParams( + VersionedBlobParams memory blobParams, + SecurityThresholds memory securityThresholds + ) external view; + + /** + * @notice Verifies the security parameters for a blob + * @param version The version of the blob to verify + * @param securityThresholds The security thresholds to verify against + */ + function verifyBlobSecurityParams( + uint16 version, + SecurityThresholds memory securityThresholds + ) external view; +} \ No newline at end of file diff --git a/contracts/src/interfaces/IEigenDADisperserRegistry.sol b/contracts/src/interfaces/IEigenDADisperserRegistry.sol new file mode 100644 index 0000000000..ac22dc04df --- /dev/null +++ b/contracts/src/interfaces/IEigenDADisperserRegistry.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.9; + +import "./IEigenDAStructs.sol"; + +interface IEigenDADisperserRegistry { + + event DisperserAdded(uint32 indexed key, address indexed disperser); + + function setDisperserInfo(uint32 _disperserKey, DisperserInfo memory _disperserInfo) external; + + function disperserKeyToAddress(uint32 key) external view returns (address); +} \ No newline at end of file diff --git a/contracts/src/interfaces/IEigenDARelayRegistry.sol b/contracts/src/interfaces/IEigenDARelayRegistry.sol new file mode 100644 index 0000000000..f99b9323ed --- /dev/null +++ b/contracts/src/interfaces/IEigenDARelayRegistry.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.9; + +import "./IEigenDAStructs.sol"; + +interface IEigenDARelayRegistry { + + event RelayAdded(address indexed relay, uint32 indexed key, string relayURL); + + function addRelayInfo(RelayInfo memory relayInfo) external returns (uint32); + + function relayKeyToAddress(uint32 key) external view returns (address); + + function relayKeyToUrl(uint32 key) external view returns (string memory); +} diff --git a/contracts/src/interfaces/IEigenDAServiceManager.sol b/contracts/src/interfaces/IEigenDAServiceManager.sol index 21221ea9be..5e02f7511e 100644 --- a/contracts/src/interfaces/IEigenDAServiceManager.sol +++ b/contracts/src/interfaces/IEigenDAServiceManager.sol @@ -1,11 +1,13 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: MIT pragma solidity ^0.8.9; import {IServiceManager} from "eigenlayer-middleware/interfaces/IServiceManager.sol"; import {BLSSignatureChecker} from "eigenlayer-middleware/BLSSignatureChecker.sol"; import {BN254} from "eigenlayer-middleware/libraries/BN254.sol"; +import {IEigenDAThresholdRegistry} from "./IEigenDAThresholdRegistry.sol"; +import "./IEigenDAStructs.sol"; -interface IEigenDAServiceManager is IServiceManager { +interface IEigenDAServiceManager is IServiceManager, IEigenDAThresholdRegistry { // EVENTS /** @@ -22,46 +24,6 @@ interface IEigenDAServiceManager is IServiceManager { */ event BatchConfirmerStatusChanged(address batchConfirmer, bool status); - // STRUCTS - - struct QuorumBlobParam { - uint8 quorumNumber; - uint8 adversaryThresholdPercentage; - uint8 confirmationThresholdPercentage; - uint32 chunkLength; // the length of the chunks in the quorum - } - - struct BlobHeader { - BN254.G1Point commitment; // the kzg commitment to the blob - uint32 dataLength; // the length of the blob in coefficients of the polynomial - QuorumBlobParam[] quorumBlobParams; // the quorumBlobParams for each quorum - } - - struct ReducedBatchHeader { - bytes32 blobHeadersRoot; - uint32 referenceBlockNumber; - } - - struct BatchHeader { - bytes32 blobHeadersRoot; - bytes quorumNumbers; // each byte is a different quorum number - bytes signedStakeForQuorums; // every bytes is an amount less than 100 specifying the percentage of stake - // that has signed in the corresponding quorum in `quorumNumbers` - uint32 referenceBlockNumber; - } - - // Relevant metadata for a given datastore - struct BatchMetadata { - BatchHeader batchHeader; // the header of the data store - bytes32 signatoryRecordHash; // the hash of the signatory record - uint32 confirmationBlockNumber; // the block number at which the batch was confirmed - } - - // FUNCTIONS - - /// @notice mapping between the batchId to the hash of the metadata of the corresponding Batch - function batchIdToBatchMetadataHash(uint32 batchId) external view returns(bytes32); - /** * @notice This function is used for * - submitting data availabilty certificates, @@ -73,8 +35,8 @@ interface IEigenDAServiceManager is IServiceManager { BLSSignatureChecker.NonSignerStakesAndSignature memory nonSignerStakesAndSignature ) external; - /// @notice This function is used for changing the batch confirmer - function setBatchConfirmer(address _batchConfirmer) external; + /// @notice mapping between the batchId to the hash of the metadata of the corresponding Batch + function batchIdToBatchMetadataHash(uint32 batchId) external view returns(bytes32); /// @notice Returns the current batchId function taskNumber() external view returns (uint32); @@ -84,13 +46,4 @@ interface IEigenDAServiceManager is IServiceManager { /// @notice The maximum amount of blocks in the past that the service will consider stake amounts to still be 'valid'. function BLOCK_STALE_MEASURE() external view returns (uint32); - - /// @notice Returns the bytes array of quorumAdversaryThresholdPercentages - function quorumAdversaryThresholdPercentages() external view returns (bytes memory); - - /// @notice Returns the bytes array of quorumAdversaryThresholdPercentages - function quorumConfirmationThresholdPercentages() external view returns (bytes memory); - - /// @notice Returns the bytes array of quorumsNumbersRequired - function quorumNumbersRequired() external view returns (bytes memory); -} +} \ No newline at end of file diff --git a/contracts/src/interfaces/IEigenDASignatureVerifier.sol b/contracts/src/interfaces/IEigenDASignatureVerifier.sol new file mode 100644 index 0000000000..6490e36248 --- /dev/null +++ b/contracts/src/interfaces/IEigenDASignatureVerifier.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.9; + +import "./IEigenDAStructs.sol"; + +interface IEigenDASignatureVerifier { + function checkSignatures( + bytes32 msgHash, + bytes calldata quorumNumbers, + uint32 referenceBlockNumber, + NonSignerStakesAndSignature memory params + ) external view returns (QuorumStakeTotals memory, bytes32); +} \ No newline at end of file diff --git a/contracts/src/interfaces/IEigenDAStructs.sol b/contracts/src/interfaces/IEigenDAStructs.sol new file mode 100644 index 0000000000..e1f8dc784d --- /dev/null +++ b/contracts/src/interfaces/IEigenDAStructs.sol @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.9; + +import {BN254} from "eigenlayer-middleware/libraries/BN254.sol"; + +///////////////////////// V1 /////////////////////////////// + +struct QuorumBlobParam { + uint8 quorumNumber; + uint8 adversaryThresholdPercentage; + uint8 confirmationThresholdPercentage; + uint32 chunkLength; +} + +struct BlobHeader { + BN254.G1Point commitment; + uint32 dataLength; + QuorumBlobParam[] quorumBlobParams; +} + +struct ReducedBatchHeader { + bytes32 blobHeadersRoot; + uint32 referenceBlockNumber; +} + +struct BatchHeader { + bytes32 blobHeadersRoot; + bytes quorumNumbers; + bytes signedStakeForQuorums; + uint32 referenceBlockNumber; +} + +struct BatchMetadata { + BatchHeader batchHeader; + bytes32 signatoryRecordHash; + uint32 confirmationBlockNumber; +} + +struct BlobVerificationProof { + uint32 batchId; + uint32 blobIndex; + BatchMetadata batchMetadata; + bytes inclusionProof; + bytes quorumIndices; +} + +///////////////////////// V2 /////////////////////////////// + +struct VersionedBlobParams { + uint32 maxNumOperators; + uint32 numChunks; + uint8 codingRate; +} + +struct SecurityThresholds { + uint8 confirmationThreshold; + uint8 adversaryThreshold; +} + +struct RelayInfo { + address relayAddress; + string relayURL; +} + +struct DisperserInfo { + address disperserAddress; +} + +struct BlobVerificationProofV2 { + BlobCertificate blobCertificate; + uint32 blobIndex; + bytes inclusionProof; +} + +struct BlobCertificate { + BlobHeaderV2 blobHeader; + uint32[] relayKeys; +} + +struct BlobHeaderV2 { + uint16 version; + bytes quorumNumbers; + BlobCommitment commitment; + bytes32 paymentHeaderHash; +} + +struct BlobCommitment { + BN254.G1Point commitment; + BN254.G2Point lengthCommitment; + BN254.G2Point lengthProof; + uint32 dataLength; +} + +struct SignedBatch { + BatchHeaderV2 batchHeader; + Attestation attestation; +} + +struct BatchHeaderV2 { + bytes32 batchRoot; + uint32 referenceBlockNumber; +} + +struct Attestation { + BN254.G1Point[] nonSignerPubkeys; + BN254.G1Point[] quorumApks; + BN254.G1Point sigma; + BN254.G2Point apkG2; + uint32[] quorumNumbers; +} + +///////////////////////// SIGNATURE VERIFIER /////////////////////////////// + +struct NonSignerStakesAndSignature { + uint32[] nonSignerQuorumBitmapIndices; + BN254.G1Point[] nonSignerPubkeys; + BN254.G1Point[] quorumApks; + BN254.G2Point apkG2; + BN254.G1Point sigma; + uint32[] quorumApkIndices; + uint32[] totalStakeIndices; + uint32[][] nonSignerStakeIndices; +} + +struct QuorumStakeTotals { + uint96[] signedStakeForQuorum; + uint96[] totalStakeForQuorum; +} + +struct CheckSignaturesIndices { + uint32[] nonSignerQuorumBitmapIndices; + uint32[] quorumApkIndices; + uint32[] totalStakeIndices; + uint32[][] nonSignerStakeIndices; +} \ No newline at end of file diff --git a/contracts/src/interfaces/IEigenDAThresholdRegistry.sol b/contracts/src/interfaces/IEigenDAThresholdRegistry.sol new file mode 100644 index 0000000000..e022e9ca1e --- /dev/null +++ b/contracts/src/interfaces/IEigenDAThresholdRegistry.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.9; + +import "../interfaces/IEigenDAStructs.sol"; + +interface IEigenDAThresholdRegistry { + + event VersionedBlobParamsAdded(uint16 indexed version, VersionedBlobParams versionedBlobParams); + + event QuorumAdversaryThresholdPercentagesUpdated(bytes previousQuorumAdversaryThresholdPercentages, bytes newQuorumAdversaryThresholdPercentages); + + event QuorumConfirmationThresholdPercentagesUpdated(bytes previousQuorumConfirmationThresholdPercentages, bytes newQuorumConfirmationThresholdPercentages); + + event QuorumNumbersRequiredUpdated(bytes previousQuorumNumbersRequired, bytes newQuorumNumbersRequired); + + event DefaultSecurityThresholdsV2Updated(SecurityThresholds previousDefaultSecurityThresholdsV2, SecurityThresholds newDefaultSecurityThresholdsV2); + + ///////////////////////// V1 /////////////////////////////// + + /// @notice Returns an array of bytes where each byte represents the adversary threshold percentage of the quorum at that index + function quorumAdversaryThresholdPercentages() external view returns (bytes memory); + + /// @notice Returns an array of bytes where each byte represents the confirmation threshold percentage of the quorum at that index + function quorumConfirmationThresholdPercentages() external view returns (bytes memory); + + /// @notice Returns an array of bytes where each byte represents the number of a required quorum + function quorumNumbersRequired() external view returns (bytes memory); + + /// @notice Gets the adversary threshold percentage for a quorum + function getQuorumAdversaryThresholdPercentage( + uint8 quorumNumber + ) external view returns (uint8); + + /// @notice Gets the confirmation threshold percentage for a quorum + function getQuorumConfirmationThresholdPercentage( + uint8 quorumNumber + ) external view returns (uint8); + + /// @notice Checks if a quorum is required + function getIsQuorumRequired( + uint8 quorumNumber + ) external view returns (bool); + + ///////////////////////// V2 /////////////////////////////// + + /// @notice Gets the default security thresholds for V2 + function getDefaultSecurityThresholdsV2() external view returns (SecurityThresholds memory); + + /// @notice Returns the blob params for a given blob version + function getBlobParams(uint16 version) external view returns (VersionedBlobParams memory); +} \ No newline at end of file diff --git a/contracts/src/interfaces/IPaymentVault.sol b/contracts/src/interfaces/IPaymentVault.sol new file mode 100644 index 0000000000..0fcfc32a93 --- /dev/null +++ b/contracts/src/interfaces/IPaymentVault.sol @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.9; + +interface IPaymentVault { + + struct Reservation { + uint64 symbolsPerSecond; // Number of symbols reserved per second + uint64 startTimestamp; // timestamp of epoch where reservation begins + uint64 endTimestamp; // timestamp of epoch where reservation ends + bytes quorumNumbers; // quorum numbers in an ordered bytes array + bytes quorumSplits; // quorum splits in a bytes array that correspond to the quorum numbers + } + + struct OnDemandPayment { + uint80 totalDeposit; + } + + /// @notice Emitted when a reservation is created or updated + event ReservationUpdated(address indexed account, Reservation reservation); + /// @notice Emitted when an on-demand payment is created or updated + event OnDemandPaymentUpdated(address indexed account, uint80 onDemandPayment, uint80 totalDeposit); + /// @notice Emitted when globalSymbolsPerPeriod is updated + event GlobalSymbolsPerPeriodUpdated(uint64 previousValue, uint64 newValue); + /// @notice Emitted when reservationPeriodInterval is updated + event ReservationPeriodIntervalUpdated(uint64 previousValue, uint64 newValue); + /// @notice Emitted when globalRatePeriodInterval is updated + event GlobalRatePeriodIntervalUpdated(uint64 previousValue, uint64 newValue); + /// @notice Emitted when priceParams are updated + event PriceParamsUpdated( + uint64 previousMinNumSymbols, + uint64 newMinNumSymbols, + uint64 previousPricePerSymbol, + uint64 newPricePerSymbol, + uint64 previousPriceUpdateCooldown, + uint64 newPriceUpdateCooldown + ); + + /** + * @notice This function is called by EigenDA governance to store reservations + * @param _account is the address to submit the reservation for + * @param _reservation is the Reservation struct containing details of the reservation + */ + function setReservation( + address _account, + Reservation memory _reservation + ) external; + + /** + * @notice This function is called to deposit funds for on demand payment + * @param _account is the address to deposit the funds for + */ + function depositOnDemand(address _account) external payable; + + /// @notice Fetches the current reservation for an account + function getReservation(address _account) external view returns (Reservation memory); + + /// @notice Fetches the current reservations for a set of accounts + function getReservations(address[] memory _accounts) external view returns (Reservation[] memory _reservations); + + /// @notice Fetches the current total on demand balance of an account + function getOnDemandTotalDeposit(address _account) external view returns (uint80); + + /// @notice Fetches the current total on demand balances for a set of accounts + function getOnDemandTotalDeposits(address[] memory _accounts) external view returns (uint80[] memory _payments); +} diff --git a/contracts/src/libraries/EigenDABlobVerificationUtils.sol b/contracts/src/libraries/EigenDABlobVerificationUtils.sol new file mode 100644 index 0000000000..d13bf88ccd --- /dev/null +++ b/contracts/src/libraries/EigenDABlobVerificationUtils.sol @@ -0,0 +1,410 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.9; + +import {Merkle} from "eigenlayer-core/contracts/libraries/Merkle.sol"; +import {BN254} from "eigenlayer-middleware/libraries/BN254.sol"; +import {EigenDAHasher} from "./EigenDAHasher.sol"; +import {BitmapUtils} from "eigenlayer-middleware/libraries/BitmapUtils.sol"; +import {IEigenDABatchMetadataStorage} from "../interfaces/IEigenDABatchMetadataStorage.sol"; +import {IEigenDAThresholdRegistry} from "../interfaces/IEigenDAThresholdRegistry.sol"; +import {IEigenDASignatureVerifier} from "../interfaces/IEigenDASignatureVerifier.sol"; +import {OperatorStateRetriever} from "lib/eigenlayer-middleware/src/OperatorStateRetriever.sol"; +import {IRegistryCoordinator} from "lib/eigenlayer-middleware/src/RegistryCoordinator.sol"; +import {IEigenDARelayRegistry} from "../interfaces/IEigenDARelayRegistry.sol"; +import "../interfaces/IEigenDAStructs.sol"; + +/** + * @title Library of functions to be used by smart contracts wanting to verify submissions of blobs on EigenDA. + * @author Layr Labs, Inc. + */ +library EigenDABlobVerificationUtils { + using BN254 for BN254.G1Point; + + uint256 public constant THRESHOLD_DENOMINATOR = 100; + + function _verifyBlobV1ForQuorums( + IEigenDAThresholdRegistry eigenDAThresholdRegistry, + IEigenDABatchMetadataStorage batchMetadataStorage, + BlobHeader calldata blobHeader, + BlobVerificationProof calldata blobVerificationProof, + bytes memory requiredQuorumNumbers + ) internal view { + require( + EigenDAHasher.hashBatchMetadata(blobVerificationProof.batchMetadata) == + IEigenDABatchMetadataStorage(batchMetadataStorage).batchIdToBatchMetadataHash(blobVerificationProof.batchId), + "EigenDABlobVerificationUtils._verifyBlobForQuorums: batchMetadata does not match stored metadata" + ); + + require( + Merkle.verifyInclusionKeccak( + blobVerificationProof.inclusionProof, + blobVerificationProof.batchMetadata.batchHeader.blobHeadersRoot, + keccak256(abi.encodePacked(EigenDAHasher.hashBlobHeader(blobHeader))), + blobVerificationProof.blobIndex + ), + "EigenDABlobVerificationUtils._verifyBlobForQuorums: inclusion proof is invalid" + ); + + uint256 confirmedQuorumsBitmap; + + for (uint i = 0; i < blobHeader.quorumBlobParams.length; i++) { + + require( + uint8(blobVerificationProof.batchMetadata.batchHeader.quorumNumbers[uint8(blobVerificationProof.quorumIndices[i])]) == + blobHeader.quorumBlobParams[i].quorumNumber, + "EigenDABlobVerificationUtils._verifyBlobForQuorums: quorumNumber does not match" + ); + + require( + blobHeader.quorumBlobParams[i].confirmationThresholdPercentage > + blobHeader.quorumBlobParams[i].adversaryThresholdPercentage, + "EigenDABlobVerificationUtils._verifyBlobForQuorums: threshold percentages are not valid" + ); + + require( + blobHeader.quorumBlobParams[i].confirmationThresholdPercentage >= + eigenDAThresholdRegistry.getQuorumConfirmationThresholdPercentage(blobHeader.quorumBlobParams[i].quorumNumber), + "EigenDABlobVerificationUtils._verifyBlobForQuorums: confirmationThresholdPercentage is not met" + ); + + require( + uint8(blobVerificationProof.batchMetadata.batchHeader.signedStakeForQuorums[uint8(blobVerificationProof.quorumIndices[i])]) >= + blobHeader.quorumBlobParams[i].confirmationThresholdPercentage, + "EigenDABlobVerificationUtils._verifyBlobForQuorums: confirmationThresholdPercentage is not met" + ); + + confirmedQuorumsBitmap = BitmapUtils.setBit(confirmedQuorumsBitmap, blobHeader.quorumBlobParams[i].quorumNumber); + } + + require( + BitmapUtils.isSubsetOf( + BitmapUtils.orderedBytesArrayToBitmap(requiredQuorumNumbers), + confirmedQuorumsBitmap + ), + "EigenDABlobVerificationUtils._verifyBlobForQuorums: required quorums are not a subset of the confirmed quorums" + ); + } + + function _verifyBlobsV1ForQuorums( + IEigenDAThresholdRegistry eigenDAThresholdRegistry, + IEigenDABatchMetadataStorage batchMetadataStorage, + BlobHeader[] calldata blobHeaders, + BlobVerificationProof[] calldata blobVerificationProofs, + bytes memory requiredQuorumNumbers + ) internal view { + require( + blobHeaders.length == blobVerificationProofs.length, + "EigenDABlobVerificationUtils._verifyBlobsForQuorums: blobHeaders and blobVerificationProofs length mismatch" + ); + + bytes memory confirmationThresholdPercentages = eigenDAThresholdRegistry.quorumConfirmationThresholdPercentages(); + + for (uint i = 0; i < blobHeaders.length; ++i) { + require( + EigenDAHasher.hashBatchMetadata(blobVerificationProofs[i].batchMetadata) == + IEigenDABatchMetadataStorage(batchMetadataStorage).batchIdToBatchMetadataHash(blobVerificationProofs[i].batchId), + "EigenDABlobVerificationUtils._verifyBlobForQuorums: batchMetadata does not match stored metadata" + ); + + require( + Merkle.verifyInclusionKeccak( + blobVerificationProofs[i].inclusionProof, + blobVerificationProofs[i].batchMetadata.batchHeader.blobHeadersRoot, + keccak256(abi.encodePacked(EigenDAHasher.hashBlobHeader(blobHeaders[i]))), + blobVerificationProofs[i].blobIndex + ), + "EigenDABlobVerificationUtils._verifyBlobForQuorums: inclusion proof is invalid" + ); + + uint256 confirmedQuorumsBitmap; + + for (uint j = 0; j < blobHeaders[i].quorumBlobParams.length; j++) { + + require( + uint8(blobVerificationProofs[i].batchMetadata.batchHeader.quorumNumbers[uint8(blobVerificationProofs[i].quorumIndices[j])]) == + blobHeaders[i].quorumBlobParams[j].quorumNumber, + "EigenDABlobVerificationUtils._verifyBlobForQuorums: quorumNumber does not match" + ); + + require( + blobHeaders[i].quorumBlobParams[j].confirmationThresholdPercentage > + blobHeaders[i].quorumBlobParams[j].adversaryThresholdPercentage, + "EigenDABlobVerificationUtils._verifyBlobForQuorums: threshold percentages are not valid" + ); + + require( + blobHeaders[i].quorumBlobParams[j].confirmationThresholdPercentage >= + uint8(confirmationThresholdPercentages[blobHeaders[i].quorumBlobParams[j].quorumNumber]), + "EigenDABlobVerificationUtils._verifyBlobForQuorums: confirmationThresholdPercentage is not met" + ); + + require( + uint8(blobVerificationProofs[i].batchMetadata.batchHeader.signedStakeForQuorums[uint8(blobVerificationProofs[i].quorumIndices[j])]) >= + blobHeaders[i].quorumBlobParams[j].confirmationThresholdPercentage, + "EigenDABlobVerificationUtils._verifyBlobForQuorums: confirmationThresholdPercentage is not met" + ); + + confirmedQuorumsBitmap = BitmapUtils.setBit(confirmedQuorumsBitmap, blobHeaders[i].quorumBlobParams[j].quorumNumber); + } + + require( + BitmapUtils.isSubsetOf( + BitmapUtils.orderedBytesArrayToBitmap(requiredQuorumNumbers), + confirmedQuorumsBitmap + ), + "EigenDABlobVerificationUtils._verifyBlobForQuorums: required quorums are not a subset of the confirmed quorums" + ); + + } + } + + function _verifyBlobV2ForQuorums( + IEigenDAThresholdRegistry eigenDAThresholdRegistry, + IEigenDASignatureVerifier signatureVerifier, + IEigenDARelayRegistry eigenDARelayRegistry, + BatchHeaderV2 memory batchHeader, + BlobVerificationProofV2 memory blobVerificationProof, + NonSignerStakesAndSignature memory nonSignerStakesAndSignature, + SecurityThresholds memory securityThresholds, + bytes memory requiredQuorumNumbers + ) internal view { + require( + Merkle.verifyInclusionKeccak( + blobVerificationProof.inclusionProof, + batchHeader.batchRoot, + keccak256(abi.encodePacked(EigenDAHasher.hashBlobCertificate(blobVerificationProof.blobCertificate))), + blobVerificationProof.blobIndex + ), + "EigenDABlobVerificationUtils._verifyBlobV2ForQuorums: inclusion proof is invalid" + ); + + ( + QuorumStakeTotals memory quorumStakeTotals, + bytes32 signatoryRecordHash + ) = signatureVerifier.checkSignatures( + EigenDAHasher.hashBatchHeaderV2(batchHeader), + blobVerificationProof.blobCertificate.blobHeader.quorumNumbers, + batchHeader.referenceBlockNumber, + nonSignerStakesAndSignature + ); + + _verifyRelayKeysSet( + eigenDARelayRegistry, + blobVerificationProof.blobCertificate.relayKeys + ); + + _verifyBlobSecurityParams( + eigenDAThresholdRegistry.getBlobParams(blobVerificationProof.blobCertificate.blobHeader.version), + securityThresholds + ); + + uint256 confirmedQuorumsBitmap; + + for (uint i = 0; i < blobVerificationProof.blobCertificate.blobHeader.quorumNumbers.length; i++) { + require( + quorumStakeTotals.signedStakeForQuorum[i] * THRESHOLD_DENOMINATOR >= + quorumStakeTotals.totalStakeForQuorum[i] * securityThresholds.confirmationThreshold, + "EigenDABlobVerificationUtils._verifyBlobV2ForQuorums: signatories do not own at least threshold percentage of a quorum" + ); + + confirmedQuorumsBitmap = BitmapUtils.setBit( + confirmedQuorumsBitmap, + uint8(blobVerificationProof.blobCertificate.blobHeader.quorumNumbers[i]) + ); + } + + require( + BitmapUtils.isSubsetOf( + BitmapUtils.orderedBytesArrayToBitmap(requiredQuorumNumbers), + confirmedQuorumsBitmap + ), + "EigenDABlobVerificationUtils._verifyBlobV2ForQuorums: required quorums are not a subset of the confirmed quorums" + ); + } + + function _verifyBlobV2ForQuorumsForThresholds( + IEigenDAThresholdRegistry eigenDAThresholdRegistry, + IEigenDASignatureVerifier signatureVerifier, + IEigenDARelayRegistry eigenDARelayRegistry, + BatchHeaderV2 memory batchHeader, + BlobVerificationProofV2 memory blobVerificationProof, + NonSignerStakesAndSignature memory nonSignerStakesAndSignature, + SecurityThresholds[] memory securityThresholds, + bytes memory requiredQuorumNumbers + ) internal view { + require( + securityThresholds.length == blobVerificationProof.blobCertificate.blobHeader.quorumNumbers.length, + "EigenDABlobVerificationUtils._verifyBlobV2ForQuorums: securityThresholds length does not match quorumNumbers" + ); + + require( + Merkle.verifyInclusionKeccak( + blobVerificationProof.inclusionProof, + batchHeader.batchRoot, + keccak256(abi.encodePacked(EigenDAHasher.hashBlobCertificate(blobVerificationProof.blobCertificate))), + blobVerificationProof.blobIndex + ), + "EigenDABlobVerificationUtils._verifyBlobV2ForQuorums: inclusion proof is invalid" + ); + + ( + QuorumStakeTotals memory quorumStakeTotals, + bytes32 signatoryRecordHash + ) = signatureVerifier.checkSignatures( + EigenDAHasher.hashBatchHeaderV2(batchHeader), + blobVerificationProof.blobCertificate.blobHeader.quorumNumbers, + batchHeader.referenceBlockNumber, + nonSignerStakesAndSignature + ); + + _verifyRelayKeysSet( + eigenDARelayRegistry, + blobVerificationProof.blobCertificate.relayKeys + ); + + uint256 confirmedQuorumsBitmap; + VersionedBlobParams memory blobParams = eigenDAThresholdRegistry.getBlobParams(blobVerificationProof.blobCertificate.blobHeader.version); + + for (uint i = 0; i < blobVerificationProof.blobCertificate.blobHeader.quorumNumbers.length; i++) { + _verifyBlobSecurityParams( + blobParams, + securityThresholds[i] + ); + + require( + quorumStakeTotals.signedStakeForQuorum[i] * THRESHOLD_DENOMINATOR >= + quorumStakeTotals.totalStakeForQuorum[i] * securityThresholds[i].confirmationThreshold, + "EigenDABlobVerificationUtils._verifyBlobV2ForQuorums: signatories do not own at least threshold percentage of a quorum" + ); + + confirmedQuorumsBitmap = BitmapUtils.setBit( + confirmedQuorumsBitmap, + uint8(blobVerificationProof.blobCertificate.blobHeader.quorumNumbers[i]) + ); + } + + require( + BitmapUtils.isSubsetOf( + BitmapUtils.orderedBytesArrayToBitmap(requiredQuorumNumbers), + confirmedQuorumsBitmap + ), + "EigenDABlobVerificationUtils._verifyBlobV2ForQuorums: required quorums are not a subset of the confirmed quorums" + ); + } + + function _verifyBlobV2ForQuorumsFromSignedBatch( + IEigenDAThresholdRegistry eigenDAThresholdRegistry, + IEigenDASignatureVerifier signatureVerifier, + IEigenDARelayRegistry eigenDARelayRegistry, + OperatorStateRetriever operatorStateRetriever, + IRegistryCoordinator registryCoordinator, + SignedBatch memory signedBatch, + BlobVerificationProofV2 memory blobVerificationProof, + SecurityThresholds memory securityThresholds, + bytes memory requiredQuorumNumbers + ) internal view { + NonSignerStakesAndSignature memory nonSignerStakesAndSignature = _getNonSignerStakesAndSignature( + operatorStateRetriever, + registryCoordinator, + signedBatch + ); + + _verifyBlobV2ForQuorums( + eigenDAThresholdRegistry, + signatureVerifier, + eigenDARelayRegistry, + signedBatch.batchHeader, + blobVerificationProof, + nonSignerStakesAndSignature, + securityThresholds, + requiredQuorumNumbers + ); + } + + function _verifyBlobV2ForQuorumsForThresholdsFromSignedBatch( + IEigenDAThresholdRegistry eigenDAThresholdRegistry, + IEigenDASignatureVerifier signatureVerifier, + IEigenDARelayRegistry eigenDARelayRegistry, + OperatorStateRetriever operatorStateRetriever, + IRegistryCoordinator registryCoordinator, + SignedBatch memory signedBatch, + BlobVerificationProofV2 memory blobVerificationProof, + SecurityThresholds[] memory securityThresholds, + bytes memory requiredQuorumNumbers + ) internal view { + NonSignerStakesAndSignature memory nonSignerStakesAndSignature = _getNonSignerStakesAndSignature( + operatorStateRetriever, + registryCoordinator, + signedBatch + ); + + _verifyBlobV2ForQuorumsForThresholds( + eigenDAThresholdRegistry, + signatureVerifier, + eigenDARelayRegistry, + signedBatch.batchHeader, + blobVerificationProof, + nonSignerStakesAndSignature, + securityThresholds, + requiredQuorumNumbers + ); + } + + function _getNonSignerStakesAndSignature( + OperatorStateRetriever operatorStateRetriever, + IRegistryCoordinator registryCoordinator, + SignedBatch memory signedBatch + ) internal view returns (NonSignerStakesAndSignature memory nonSignerStakesAndSignature) { + bytes32[] memory nonSignerOperatorIds = new bytes32[](signedBatch.attestation.nonSignerPubkeys.length); + for (uint i = 0; i < signedBatch.attestation.nonSignerPubkeys.length; ++i) { + nonSignerOperatorIds[i] = BN254.hashG1Point(signedBatch.attestation.nonSignerPubkeys[i]); + } + + bytes memory quorumNumbers; + for (uint i = 0; i < signedBatch.attestation.quorumNumbers.length; ++i) { + quorumNumbers = abi.encodePacked(quorumNumbers, uint8(signedBatch.attestation.quorumNumbers[i])); + } + + OperatorStateRetriever.CheckSignaturesIndices memory checkSignaturesIndices = operatorStateRetriever.getCheckSignaturesIndices( + registryCoordinator, + signedBatch.batchHeader.referenceBlockNumber, + quorumNumbers, + nonSignerOperatorIds + ); + + nonSignerStakesAndSignature.nonSignerQuorumBitmapIndices = checkSignaturesIndices.nonSignerQuorumBitmapIndices; + nonSignerStakesAndSignature.nonSignerPubkeys = signedBatch.attestation.nonSignerPubkeys; + nonSignerStakesAndSignature.quorumApks = signedBatch.attestation.quorumApks; + nonSignerStakesAndSignature.apkG2 = signedBatch.attestation.apkG2; + nonSignerStakesAndSignature.sigma = signedBatch.attestation.sigma; + nonSignerStakesAndSignature.quorumApkIndices = checkSignaturesIndices.quorumApkIndices; + nonSignerStakesAndSignature.totalStakeIndices = checkSignaturesIndices.totalStakeIndices; + nonSignerStakesAndSignature.nonSignerStakeIndices = checkSignaturesIndices.nonSignerStakeIndices; + } + + function _verifyBlobSecurityParams( + VersionedBlobParams memory blobParams, + SecurityThresholds memory securityThresholds + ) internal pure { + require( + securityThresholds.confirmationThreshold > securityThresholds.adversaryThreshold, + "EigenDABlobVerificationUtils._verifyBlobSecurityParams: confirmationThreshold must be greater than adversaryThreshold" + ); + uint256 gamma = securityThresholds.confirmationThreshold - securityThresholds.adversaryThreshold; + uint256 n = (10000 - ((1_000_000 / gamma) / uint256(blobParams.codingRate))) * uint256(blobParams.numChunks); + require(n >= blobParams.maxNumOperators * 10000, "EigenDABlobVerificationUtils._verifyBlobSecurityParams: security assumptions are not met"); + } + + function _verifyRelayKeysSet( + IEigenDARelayRegistry eigenDARelayRegistry, + uint32[] memory relayKeys + ) internal view { + for (uint i = 0; i < relayKeys.length; ++i) { + require( + eigenDARelayRegistry.relayKeyToAddress(relayKeys[i]) != address(0), + "EigenDABlobVerificationUtils._verifyRelayKeysSet: relay key is not set" + ); + } + } +} diff --git a/contracts/src/libraries/EigenDAHasher.sol b/contracts/src/libraries/EigenDAHasher.sol index 4f8d72c116..0f081012ae 100644 --- a/contracts/src/libraries/EigenDAHasher.sol +++ b/contracts/src/libraries/EigenDAHasher.sol @@ -1,8 +1,9 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: MIT pragma solidity ^0.8.9; import {IEigenDAServiceManager} from "../interfaces/IEigenDAServiceManager.sol"; +import "../interfaces/IEigenDAStructs.sol"; /** * @title Library of functions for hashing various EigenDA structs. @@ -39,12 +40,11 @@ library EigenDAHasher { } /** - * @notice given the a batchHeader in the provided metdata, calculates the hash of the batchMetadata + * @notice given the batchHeader in the provided metdata, calculates the hash of the batchMetadata * @param batchMetadata the metadata of the batch - * @return the hash of the batchMetadata */ function hashBatchMetadata( - IEigenDAServiceManager.BatchMetadata memory batchMetadata + BatchMetadata memory batchMetadata ) internal pure returns(bytes32) { return hashBatchHashedMetadata( keccak256(abi.encode(batchMetadata.batchHeader)), @@ -57,7 +57,7 @@ library EigenDAHasher { * @notice hashes the given batch header * @param batchHeader the batch header to hash */ - function hashBatchHeaderMemory(IEigenDAServiceManager.BatchHeader memory batchHeader) internal pure returns(bytes32) { + function hashBatchHeaderMemory(BatchHeader memory batchHeader) internal pure returns(bytes32) { return keccak256(abi.encode(batchHeader)); } @@ -65,7 +65,7 @@ library EigenDAHasher { * @notice hashes the given batch header * @param batchHeader the batch header to hash */ - function hashBatchHeader(IEigenDAServiceManager.BatchHeader calldata batchHeader) internal pure returns(bytes32) { + function hashBatchHeader(BatchHeader calldata batchHeader) internal pure returns(bytes32) { return keccak256(abi.encode(batchHeader)); } @@ -73,7 +73,7 @@ library EigenDAHasher { * @notice hashes the given reduced batch header * @param reducedBatchHeader the reduced batch header to hash */ - function hashReducedBatchHeader(IEigenDAServiceManager.ReducedBatchHeader memory reducedBatchHeader) internal pure returns(bytes32) { + function hashReducedBatchHeader(ReducedBatchHeader memory reducedBatchHeader) internal pure returns(bytes32) { return keccak256(abi.encode(reducedBatchHeader)); } @@ -81,7 +81,7 @@ library EigenDAHasher { * @notice hashes the given blob header * @param blobHeader the blob header to hash */ - function hashBlobHeader(IEigenDAServiceManager.BlobHeader memory blobHeader) internal pure returns(bytes32) { + function hashBlobHeader(BlobHeader memory blobHeader) internal pure returns(bytes32) { return keccak256(abi.encode(blobHeader)); } @@ -89,8 +89,8 @@ library EigenDAHasher { * @notice converts a batch header to a reduced batch header * @param batchHeader the batch header to convert */ - function convertBatchHeaderToReducedBatchHeader(IEigenDAServiceManager.BatchHeader memory batchHeader) internal pure returns(IEigenDAServiceManager.ReducedBatchHeader memory) { - return IEigenDAServiceManager.ReducedBatchHeader({ + function convertBatchHeaderToReducedBatchHeader(BatchHeader memory batchHeader) internal pure returns(ReducedBatchHeader memory) { + return ReducedBatchHeader({ blobHeadersRoot: batchHeader.blobHeadersRoot, referenceBlockNumber: batchHeader.referenceBlockNumber }); @@ -100,7 +100,47 @@ library EigenDAHasher { * @notice converts the given batch header to a reduced batch header and then hashes it * @param batchHeader the batch header to hash */ - function hashBatchHeaderToReducedBatchHeader(IEigenDAServiceManager.BatchHeader memory batchHeader) internal pure returns(bytes32) { + function hashBatchHeaderToReducedBatchHeader(BatchHeader memory batchHeader) internal pure returns(bytes32) { return keccak256(abi.encode(convertBatchHeaderToReducedBatchHeader(batchHeader))); } + + /** + * @notice hashes the given V2 batch header + * @param batchHeader the V2 batch header to hash + */ + function hashBatchHeaderV2(BatchHeaderV2 memory batchHeader) internal pure returns(bytes32) { + return keccak256(abi.encode(batchHeader)); + } + + /** + * @notice hashes the given V2 blob header + * @param blobHeader the V2 blob header to hash + */ + function hashBlobHeaderV2(BlobHeaderV2 memory blobHeader) internal pure returns(bytes32) { + return keccak256( + abi.encode( + keccak256( + abi.encode( + blobHeader.version, + blobHeader.quorumNumbers, + blobHeader.commitment + ) + ), + blobHeader.paymentHeaderHash + ) + ); + } + + /** + * @notice hashes the given V2 blob certificate + * @param blobCertificate the V2 blob certificate to hash + */ + function hashBlobCertificate(BlobCertificate memory blobCertificate) internal pure returns(bytes32) { + return keccak256( + abi.encode( + hashBlobHeaderV2(blobCertificate.blobHeader), + blobCertificate.relayKeys + ) + ); + } } diff --git a/contracts/src/payments/PaymentVault.sol b/contracts/src/payments/PaymentVault.sol new file mode 100644 index 0000000000..9dae3cd17c --- /dev/null +++ b/contracts/src/payments/PaymentVault.sol @@ -0,0 +1,151 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.9; + +import {OwnableUpgradeable} from "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol"; +import {PaymentVaultStorage} from "./PaymentVaultStorage.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +/** + * @title Entrypoint for making reservations and on demand payments for EigenDA. + * @author Layr Labs, Inc. +**/ +contract PaymentVault is OwnableUpgradeable, PaymentVaultStorage { + + constructor() { + _disableInitializers(); + } + + receive() external payable { + _deposit(msg.sender, msg.value); + } + + fallback() external payable { + _deposit(msg.sender, msg.value); + } + + function initialize( + address _initialOwner, + uint64 _minNumSymbols, + uint64 _pricePerSymbol, + uint64 _priceUpdateCooldown, + uint64 _globalSymbolsPerPeriod, + uint64 _reservationPeriodInterval, + uint64 _globalRatePeriodInterval + ) public initializer { + _transferOwnership(_initialOwner); + + minNumSymbols = _minNumSymbols; + pricePerSymbol = _pricePerSymbol; + priceUpdateCooldown = _priceUpdateCooldown; + lastPriceUpdateTime = uint64(block.timestamp); + + globalSymbolsPerPeriod = _globalSymbolsPerPeriod; + reservationPeriodInterval = _reservationPeriodInterval; + globalRatePeriodInterval = _globalRatePeriodInterval; + } + + /** + * @notice This function is called by EigenDA governance to store reservations + * @param _account is the address to submit the reservation for + * @param _reservation is the Reservation struct containing details of the reservation + */ + function setReservation( + address _account, + Reservation memory _reservation + ) external onlyOwner { + _checkQuorumSplit(_reservation.quorumNumbers, _reservation.quorumSplits); + require(_reservation.endTimestamp > _reservation.startTimestamp, "end timestamp must be greater than start timestamp"); + reservations[_account] = _reservation; + emit ReservationUpdated(_account, _reservation); + } + + /** + * @notice This function is called to deposit funds for on demand payment + * @param _account is the address to deposit the funds for + */ + function depositOnDemand(address _account) external payable { + _deposit(_account, msg.value); + } + + function setPriceParams( + uint64 _minNumSymbols, + uint64 _pricePerSymbol, + uint64 _priceUpdateCooldown + ) external onlyOwner { + require(block.timestamp >= lastPriceUpdateTime + priceUpdateCooldown, "price update cooldown not surpassed"); + + emit PriceParamsUpdated( + minNumSymbols, _minNumSymbols, + pricePerSymbol, _pricePerSymbol, + priceUpdateCooldown, _priceUpdateCooldown + ); + + pricePerSymbol = _pricePerSymbol; + minNumSymbols = _minNumSymbols; + priceUpdateCooldown = _priceUpdateCooldown; + lastPriceUpdateTime = uint64(block.timestamp); + } + + function setGlobalSymbolsPerPeriod(uint64 _globalSymbolsPerPeriod) external onlyOwner { + emit GlobalSymbolsPerPeriodUpdated(globalSymbolsPerPeriod, _globalSymbolsPerPeriod); + globalSymbolsPerPeriod = _globalSymbolsPerPeriod; + } + + function setReservationPeriodInterval(uint64 _reservationPeriodInterval) external onlyOwner { + emit ReservationPeriodIntervalUpdated(reservationPeriodInterval, _reservationPeriodInterval); + reservationPeriodInterval = _reservationPeriodInterval; + } + + function setGlobalRatePeriodInterval(uint64 _globalRatePeriodInterval) external onlyOwner { + emit GlobalRatePeriodIntervalUpdated(globalRatePeriodInterval, _globalRatePeriodInterval); + globalRatePeriodInterval = _globalRatePeriodInterval; + } + + function withdraw(uint256 _amount) external onlyOwner { + (bool success,) = payable(owner()).call{value: _amount}(""); + require(success); + } + + function withdrawERC20(address _token, uint256 _amount) external onlyOwner { + IERC20(_token).transfer(owner(), _amount); + } + + function _checkQuorumSplit(bytes memory _quorumNumbers, bytes memory _quorumSplits) internal pure { + require(_quorumNumbers.length == _quorumSplits.length, "arrays must have the same length"); + uint8 total; + for(uint256 i; i < _quorumSplits.length; ++i) total += uint8(_quorumSplits[i]); + require(total == 100, "sum of quorumSplits must be 100"); + } + + function _deposit(address _account, uint256 _amount) internal { + require(_amount <= type(uint80).max, "amount must be less than or equal to 80 bits"); + onDemandPayments[_account].totalDeposit += uint80(_amount); + emit OnDemandPaymentUpdated(_account, uint80(_amount), onDemandPayments[_account].totalDeposit); + } + + /// @notice Fetches the current reservation for an account + function getReservation(address _account) external view returns (Reservation memory) { + return reservations[_account]; + } + + /// @notice Fetches the current reservations for a set of accounts + function getReservations(address[] memory _accounts) external view returns (Reservation[] memory _reservations) { + _reservations = new Reservation[](_accounts.length); + for(uint256 i; i < _accounts.length; ++i){ + _reservations[i] = reservations[_accounts[i]]; + } + } + + /// @notice Fetches the current total on demand balance of an account + function getOnDemandTotalDeposit(address _account) external view returns (uint80) { + return onDemandPayments[_account].totalDeposit; + } + + /// @notice Fetches the current total on demand balances for a set of accounts + function getOnDemandTotalDeposits(address[] memory _accounts) external view returns (uint80[] memory _payments) { + _payments = new uint80[](_accounts.length); + for(uint256 i; i < _accounts.length; ++i){ + _payments[i] = onDemandPayments[_accounts[i]].totalDeposit; + } + } +} \ No newline at end of file diff --git a/contracts/src/payments/PaymentVaultStorage.sol b/contracts/src/payments/PaymentVaultStorage.sol new file mode 100644 index 0000000000..c8aae718df --- /dev/null +++ b/contracts/src/payments/PaymentVaultStorage.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.9; + +import {IPaymentVault} from "../interfaces/IPaymentVault.sol"; + +abstract contract PaymentVaultStorage is IPaymentVault { + + /// @notice minimum chargeable size for on-demand payments + uint64 public minNumSymbols; + /// @notice price per symbol in wei + uint64 public pricePerSymbol; + /// @notice cooldown period before the price can be updated again + uint64 public priceUpdateCooldown; + /// @notice timestamp of the last price update + uint64 public lastPriceUpdateTime; + + /// @notice maximum number of symbols to disperse per second network-wide for on-demand payments (applied to only ETH and EIGEN) + uint64 public globalSymbolsPerPeriod; + /// @notice reservation period interval + uint64 public reservationPeriodInterval; + /// @notice global rate period interval + uint64 public globalRatePeriodInterval; + + /// @notice mapping from user address to current reservation + mapping(address => Reservation) public reservations; + /// @notice mapping from user address to current on-demand payment + mapping(address => OnDemandPayment) public onDemandPayments; + + uint256[46] private __GAP; +} \ No newline at end of file diff --git a/contracts/test/MockEigenDADeployer.sol b/contracts/test/MockEigenDADeployer.sol new file mode 100644 index 0000000000..403a24483a --- /dev/null +++ b/contracts/test/MockEigenDADeployer.sol @@ -0,0 +1,279 @@ +// SPDX-License-Identifier: MIT +pragma solidity =0.8.12; + +import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "../lib/eigenlayer-middleware/test/utils/BLSMockAVSDeployer.sol"; +import {EigenDAHasher} from "../src/libraries/EigenDAHasher.sol"; +import {EigenDAServiceManager, IRewardsCoordinator} from "../src/core/EigenDAServiceManager.sol"; +import {EigenDABlobVerificationUtils} from "../src/libraries/EigenDABlobVerificationUtils.sol"; +import {EigenDAHasher} from "../src/libraries/EigenDAHasher.sol"; +import {EigenDAServiceManager} from "../src/core/EigenDAServiceManager.sol"; +import {IEigenDAServiceManager} from "../src/interfaces/IEigenDAServiceManager.sol"; +import {EigenDABlobVerifier} from "../src/core/EigenDABlobVerifier.sol"; +import {EigenDAThresholdRegistry, IEigenDAThresholdRegistry} from "../src/core/EigenDAThresholdRegistry.sol"; +import {IEigenDABatchMetadataStorage} from "../src/interfaces/IEigenDABatchMetadataStorage.sol"; +import {IEigenDASignatureVerifier} from "../src/interfaces/IEigenDASignatureVerifier.sol"; +import {IRegistryCoordinator} from "../lib/eigenlayer-middleware/src/interfaces/IRegistryCoordinator.sol"; +import {IEigenDARelayRegistry} from "../src/interfaces/IEigenDARelayRegistry.sol"; +import {EigenDARelayRegistry} from "../src/core/EigenDARelayRegistry.sol"; +import {IPaymentVault} from "../src/interfaces/IPaymentVault.sol"; +import {PaymentVault} from "../src/payments/PaymentVault.sol"; +import {IEigenDADisperserRegistry} from "../src/interfaces/IEigenDADisperserRegistry.sol"; +import {EigenDADisperserRegistry} from "../src/core/EigenDADisperserRegistry.sol"; +import "../src/interfaces/IEigenDAStructs.sol"; +import "forge-std/StdStorage.sol"; + +contract MockEigenDADeployer is BLSMockAVSDeployer { + using stdStorage for StdStorage; + using BN254 for BN254.G1Point; + using EigenDAHasher for BatchHeader; + using EigenDAHasher for ReducedBatchHeader; + using EigenDAHasher for BlobHeader; + using EigenDAHasher for BatchMetadata; + + address confirmer = address(uint160(uint256(keccak256(abi.encodePacked("confirmer"))))); + address notConfirmer = address(uint160(uint256(keccak256(abi.encodePacked("notConfirmer"))))); + address rewardsInitiator = address(uint160(uint256(keccak256(abi.encodePacked("rewardsInitiator"))))); + + EigenDAServiceManager eigenDAServiceManager; + EigenDAServiceManager eigenDAServiceManagerImplementation; + EigenDARelayRegistry eigenDARelayRegistry; + EigenDARelayRegistry eigenDARelayRegistryImplementation; + EigenDAThresholdRegistry eigenDAThresholdRegistry; + EigenDAThresholdRegistry eigenDAThresholdRegistryImplementation; + EigenDADisperserRegistry eigenDADisperserRegistry; + EigenDADisperserRegistry eigenDADisperserRegistryImplementation; + PaymentVault paymentVault; + PaymentVault paymentVaultImplementation; + EigenDABlobVerifier eigenDABlobVerifier; + + ERC20 mockToken; + + bytes quorumAdversaryThresholdPercentages = hex"212121"; + bytes quorumConfirmationThresholdPercentages = hex"373737"; + bytes quorumNumbersRequired = hex"0001"; + SecurityThresholds defaultSecurityThresholds = SecurityThresholds(55, 33); + + uint32 defaultReferenceBlockNumber = 100; + uint32 defaultConfirmationBlockNumber = 1000; + uint32 defaultBatchId = 0; + + uint64 minNumSymbols = 1; + uint64 pricePerSymbol = 3; + uint64 priceUpdateCooldown = 6 days; + uint64 globalSymbolsPerPeriod = 2; + uint64 reservationPeriodInterval = 4; + uint64 globalRatePeriodInterval = 5; + + mapping(uint8 => bool) public quorumNumbersUsed; + + function _deployDA() public { + _setUpBLSMockAVSDeployer(); + + eigenDAServiceManager = EigenDAServiceManager( + address( + new TransparentUpgradeableProxy(address(emptyContract), address(proxyAdmin), "") + ) + ); + + eigenDAThresholdRegistry = EigenDAThresholdRegistry( + address( + new TransparentUpgradeableProxy(address(emptyContract), address(proxyAdmin), "") + ) + ); + + eigenDARelayRegistry = EigenDARelayRegistry( + address( + new TransparentUpgradeableProxy(address(emptyContract), address(proxyAdmin), "") + ) + ); + + paymentVault = PaymentVault( + payable( + address( + new TransparentUpgradeableProxy(address(emptyContract), address(proxyAdmin), "") + ) + ) + ); + + eigenDADisperserRegistry = EigenDADisperserRegistry( + address( + new TransparentUpgradeableProxy(address(emptyContract), address(proxyAdmin), "") + ) + ); + + eigenDAServiceManagerImplementation = new EigenDAServiceManager( + avsDirectory, + rewardsCoordinator, + registryCoordinator, + stakeRegistry, + eigenDAThresholdRegistry, + eigenDARelayRegistry, + paymentVault, + eigenDADisperserRegistry + ); + + address[] memory confirmers = new address[](1); + confirmers[0] = confirmer; + + cheats.prank(proxyAdminOwner); + proxyAdmin.upgradeAndCall( + TransparentUpgradeableProxy(payable(address(eigenDAServiceManager))), + address(eigenDAServiceManagerImplementation), + abi.encodeWithSelector( + EigenDAServiceManager.initialize.selector, + pauserRegistry, + 0, + registryCoordinatorOwner, + confirmers, + registryCoordinatorOwner + ) + ); + + eigenDAThresholdRegistryImplementation = new EigenDAThresholdRegistry(); + + VersionedBlobParams[] memory versionedBlobParams = new VersionedBlobParams[](1); + versionedBlobParams[0] = VersionedBlobParams({ + maxNumOperators: 3537, + numChunks: 8192, + codingRate: 8 + }); + + cheats.prank(proxyAdminOwner); + proxyAdmin.upgradeAndCall( + TransparentUpgradeableProxy(payable(address(eigenDAThresholdRegistry))), + address(eigenDAThresholdRegistryImplementation), + abi.encodeWithSelector( + EigenDAThresholdRegistry.initialize.selector, + registryCoordinatorOwner, + quorumAdversaryThresholdPercentages, + quorumConfirmationThresholdPercentages, + quorumNumbersRequired, + versionedBlobParams, + defaultSecurityThresholds + ) + ); + + eigenDARelayRegistryImplementation = new EigenDARelayRegistry(); + + cheats.prank(proxyAdminOwner); + proxyAdmin.upgradeAndCall( + TransparentUpgradeableProxy(payable(address(eigenDARelayRegistry))), + address(eigenDARelayRegistryImplementation), + abi.encodeWithSelector(EigenDARelayRegistry.initialize.selector, registryCoordinatorOwner) + ); + + eigenDADisperserRegistryImplementation = new EigenDADisperserRegistry(); + + cheats.prank(proxyAdminOwner); + proxyAdmin.upgradeAndCall( + TransparentUpgradeableProxy(payable(address(eigenDADisperserRegistry))), + address(eigenDADisperserRegistryImplementation), + abi.encodeWithSelector(EigenDADisperserRegistry.initialize.selector, registryCoordinatorOwner) + ); + + paymentVaultImplementation = PaymentVault(payable(address(new PaymentVault()))); + + paymentVault = PaymentVault( + payable( + address( + new TransparentUpgradeableProxy( + address(paymentVaultImplementation), + address(proxyAdmin), + abi.encodeWithSelector( + PaymentVault.initialize.selector, + registryCoordinatorOwner, + minNumSymbols, + pricePerSymbol, + priceUpdateCooldown, + globalSymbolsPerPeriod, + reservationPeriodInterval, + globalRatePeriodInterval + ) + ) + ) + ) + ); + + mockToken = new ERC20("Mock Token", "MOCK"); + + eigenDABlobVerifier = new EigenDABlobVerifier( + IEigenDAThresholdRegistry(address(eigenDAThresholdRegistry)), + IEigenDABatchMetadataStorage(address(eigenDAServiceManager)), + IEigenDASignatureVerifier(address(eigenDAServiceManager)), + IEigenDARelayRegistry(address(eigenDARelayRegistry)), + OperatorStateRetriever(address(operatorStateRetriever)), + IRegistryCoordinator(address(registryCoordinator)) + ); + } + + function _getHeaderandNonSigners(uint256 _nonSigners, uint256 _pseudoRandomNumber, uint8 _threshold) internal returns (BatchHeader memory, BLSSignatureChecker.NonSignerStakesAndSignature memory) { + // register a bunch of operators + uint256 quorumBitmap = 1; + bytes memory quorumNumbers = BitmapUtils.bitmapToBytesArray(quorumBitmap); + + // 0 nonSigners + (uint32 referenceBlockNumber, BLSSignatureChecker.NonSignerStakesAndSignature memory nonSignerStakesAndSignature) = + _registerSignatoriesAndGetNonSignerStakeAndSignatureRandom(_pseudoRandomNumber, _nonSigners, quorumBitmap); + + // get a random batch header + BatchHeader memory batchHeader = _getRandomBatchHeader(_pseudoRandomNumber, quorumNumbers, referenceBlockNumber, _threshold); + + // set batch specific signature + bytes32 reducedBatchHeaderHash = batchHeader.hashBatchHeaderToReducedBatchHeader(); + nonSignerStakesAndSignature.sigma = BN254.hashToG1(reducedBatchHeaderHash).scalar_mul(aggSignerPrivKey); + + return (batchHeader, nonSignerStakesAndSignature); + } + + function _getRandomBatchHeader(uint256 pseudoRandomNumber, bytes memory quorumNumbers, uint32 referenceBlockNumber, uint8 threshold) internal pure returns(BatchHeader memory) { + BatchHeader memory batchHeader; + batchHeader.blobHeadersRoot = keccak256(abi.encodePacked("blobHeadersRoot", pseudoRandomNumber)); + batchHeader.quorumNumbers = quorumNumbers; + batchHeader.signedStakeForQuorums = new bytes(quorumNumbers.length); + for (uint256 i = 0; i < quorumNumbers.length; i++) { + batchHeader.signedStakeForQuorums[i] = bytes1(threshold); + } + batchHeader.referenceBlockNumber = referenceBlockNumber; + return batchHeader; + } + + function _generateRandomBlobHeader(uint256 pseudoRandomNumber, uint256 numQuorumsBlobParams) internal returns (BlobHeader memory) { + if(pseudoRandomNumber == 0) { + pseudoRandomNumber = 1; + } + + BlobHeader memory blobHeader; + blobHeader.commitment.X = uint256(keccak256(abi.encodePacked(pseudoRandomNumber, "blobHeader.commitment.X"))) % BN254.FP_MODULUS; + blobHeader.commitment.Y = uint256(keccak256(abi.encodePacked(pseudoRandomNumber, "blobHeader.commitment.Y"))) % BN254.FP_MODULUS; + + blobHeader.dataLength = uint32(uint256(keccak256(abi.encodePacked(pseudoRandomNumber, "blobHeader.dataLength")))); + + blobHeader.quorumBlobParams = new QuorumBlobParam[](numQuorumsBlobParams); + blobHeader.dataLength = uint32(uint256(keccak256(abi.encodePacked(pseudoRandomNumber, "blobHeader.dataLength")))); + for (uint i = 0; i < numQuorumsBlobParams; i++) { + if(i < 2){ + blobHeader.quorumBlobParams[i].quorumNumber = uint8(i); + } else { + blobHeader.quorumBlobParams[i].quorumNumber = uint8(uint256(keccak256(abi.encodePacked(pseudoRandomNumber, "blobHeader.quorumBlobParams[i].quorumNumber", i)))) % 192; + + // make sure it isn't already used + while(quorumNumbersUsed[blobHeader.quorumBlobParams[i].quorumNumber]) { + blobHeader.quorumBlobParams[i].quorumNumber = uint8(uint256(blobHeader.quorumBlobParams[i].quorumNumber) + 1) % 192; + } + quorumNumbersUsed[blobHeader.quorumBlobParams[i].quorumNumber] = true; + } + + blobHeader.quorumBlobParams[i].adversaryThresholdPercentage = eigenDABlobVerifier.getQuorumAdversaryThresholdPercentage(blobHeader.quorumBlobParams[i].quorumNumber); + blobHeader.quorumBlobParams[i].chunkLength = uint32(uint256(keccak256(abi.encodePacked(pseudoRandomNumber, "blobHeader.quorumBlobParams[i].chunkLength", i)))); + blobHeader.quorumBlobParams[i].confirmationThresholdPercentage = eigenDABlobVerifier.getQuorumConfirmationThresholdPercentage(blobHeader.quorumBlobParams[i].quorumNumber); + } + // mark all quorum numbers as unused + for (uint i = 0; i < numQuorumsBlobParams; i++) { + quorumNumbersUsed[blobHeader.quorumBlobParams[i].quorumNumber] = false; + } + + return blobHeader; + } +} diff --git a/contracts/test/harnesses/EigenDABlobUtilsHarness.sol b/contracts/test/harnesses/EigenDABlobUtilsHarness.sol deleted file mode 100644 index 8a4c7a4582..0000000000 --- a/contracts/test/harnesses/EigenDABlobUtilsHarness.sol +++ /dev/null @@ -1,17 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED - -pragma solidity ^0.8.9; - -import "../../src/libraries/EigenDARollupUtils.sol"; -import "forge-std/Test.sol"; - -contract EigenDABlobUtilsHarness is Test { - - function verifyBlob( - IEigenDAServiceManager.BlobHeader calldata blobHeader, - IEigenDAServiceManager eigenDAServiceManager, - EigenDARollupUtils.BlobVerificationProof calldata blobVerificationProof - ) external view { - EigenDARollupUtils.verifyBlob(blobHeader, eigenDAServiceManager, blobVerificationProof); - } -} diff --git a/contracts/src/libraries/EigenDARollupUtils.sol b/contracts/test/rollupV1/EigenDARollupUtils.sol similarity index 54% rename from contracts/src/libraries/EigenDARollupUtils.sol rename to contracts/test/rollupV1/EigenDARollupUtils.sol index b44bb1ff43..b2abecd008 100644 --- a/contracts/src/libraries/EigenDARollupUtils.sol +++ b/contracts/test/rollupV1/EigenDARollupUtils.sol @@ -4,9 +4,10 @@ pragma solidity ^0.8.9; import {Merkle} from "eigenlayer-core/contracts/libraries/Merkle.sol"; import {BN254} from "eigenlayer-middleware/libraries/BN254.sol"; -import {EigenDAHasher} from "./EigenDAHasher.sol"; -import {IEigenDAServiceManager} from "../interfaces/IEigenDAServiceManager.sol"; +import {EigenDAHasher} from "../../src/libraries/EigenDAHasher.sol"; +import {IEigenDAServiceManager} from "../../src/interfaces/IEigenDAServiceManager.sol"; import {BitmapUtils} from "eigenlayer-middleware/libraries/BitmapUtils.sol"; +import "../../src/interfaces/IEigenDAStructs.sol"; /** * @title Library of functions to be used by smart contracts wanting to prove blobs on EigenDA and open KZG commitments. @@ -14,15 +15,6 @@ import {BitmapUtils} from "eigenlayer-middleware/libraries/BitmapUtils.sol"; */ library EigenDARollupUtils { using BN254 for BN254.G1Point; - - // STRUCTS - struct BlobVerificationProof { - uint32 batchId; - uint8 blobIndex; - IEigenDAServiceManager.BatchMetadata batchMetadata; - bytes inclusionProof; - bytes quorumIndices; - } /** * @notice Verifies the inclusion of a blob within a batch confirmed in `eigenDAServiceManager` and its trust assumptions @@ -31,10 +23,10 @@ library EigenDARollupUtils { * @param blobVerificationProof the relevant data needed to prove inclusion of the blob and that the trust assumptions were as expected */ function verifyBlob( - IEigenDAServiceManager.BlobHeader calldata blobHeader, + BlobHeader memory blobHeader, IEigenDAServiceManager eigenDAServiceManager, - BlobVerificationProof calldata blobVerificationProof - ) external view { + BlobVerificationProof memory blobVerificationProof + ) internal view { require( EigenDAHasher.hashBatchMetadata(blobVerificationProof.batchMetadata) == eigenDAServiceManager.batchIdToBatchMetadataHash(blobVerificationProof.batchId), @@ -97,6 +89,84 @@ library EigenDARollupUtils { ); } + /** + * @notice Verifies the inclusion of a blob within a batch confirmed in `eigenDAServiceManager` and its trust assumptions + * @param blobHeaders the headers of the blobs containing relevant attributes of the blobs + * @param eigenDAServiceManager the contract in which the batch was confirmed + * @param blobVerificationProofs the relevant data needed to prove inclusion of the blobs and that the trust assumptions were as expected + */ + function verifyBlobs( + BlobHeader[] memory blobHeaders, + IEigenDAServiceManager eigenDAServiceManager, + BlobVerificationProof[] memory blobVerificationProofs + ) internal view { + require(blobHeaders.length == blobVerificationProofs.length, "EigenDARollupUtils.verifyBlobs: blobHeaders and blobVerificationProofs must have the same length"); + + bytes memory quorumAdversaryThresholdPercentages = eigenDAServiceManager.quorumAdversaryThresholdPercentages(); + uint256 quorumNumbersRequiredBitmap = BitmapUtils.orderedBytesArrayToBitmap(eigenDAServiceManager.quorumNumbersRequired()); + + for (uint i = 0; i < blobHeaders.length; i++) { + require( + EigenDAHasher.hashBatchMetadata(blobVerificationProofs[i].batchMetadata) + == eigenDAServiceManager.batchIdToBatchMetadataHash(blobVerificationProofs[i].batchId), + "EigenDARollupUtils.verifyBlob: batchMetadata does not match stored metadata" + ); + + require( + Merkle.verifyInclusionKeccak( + blobVerificationProofs[i].inclusionProof, + blobVerificationProofs[i].batchMetadata.batchHeader.blobHeadersRoot, + keccak256(abi.encodePacked(EigenDAHasher.hashBlobHeader(blobHeaders[i]))), + blobVerificationProofs[i].blobIndex + ), + "EigenDARollupUtils.verifyBlob: inclusion proof is invalid" + ); + + // bitmap of quorum numbers in all quorumBlobParams + uint256 confirmedQuorumsBitmap; + + // require that the security param in each blob is met + for (uint j = 0; j < blobHeaders[i].quorumBlobParams.length; j++) { + // make sure that the quorumIndex matches the given quorumNumber + require(uint8(blobVerificationProofs[i].batchMetadata.batchHeader.quorumNumbers[uint8(blobVerificationProofs[i].quorumIndices[i])]) == blobHeaders[i].quorumBlobParams[i].quorumNumber, + "EigenDARollupUtils.verifyBlob: quorumNumber does not match" + ); + + // make sure that the adversaryThresholdPercentage is less than the given confirmationThresholdPercentage + require(blobHeaders[i].quorumBlobParams[i].adversaryThresholdPercentage + < blobHeaders[i].quorumBlobParams[i].confirmationThresholdPercentage, + "EigenDARollupUtils.verifyBlob: adversaryThresholdPercentage is not valid" + ); + + // make sure that the adversaryThresholdPercentage is at least the given quorumAdversaryThresholdPercentage + uint8 _adversaryThresholdPercentage = uint8(quorumAdversaryThresholdPercentages[blobHeaders[i].quorumBlobParams[j].quorumNumber]); + if(_adversaryThresholdPercentage > 0){ + require(blobHeaders[i].quorumBlobParams[j].adversaryThresholdPercentage >= _adversaryThresholdPercentage, + "EigenDARollupUtils.verifyBlob: adversaryThresholdPercentage is not met" + ); + } + + // make sure that the stake signed for is greater than the given confirmationThresholdPercentage + require(uint8(blobVerificationProofs[i].batchMetadata.batchHeader.signedStakeForQuorums[uint8(blobVerificationProofs[i].quorumIndices[j])]) + >= blobHeaders[i].quorumBlobParams[j].confirmationThresholdPercentage, + "EigenDARollupUtils.verifyBlob: confirmationThresholdPercentage is not met" + ); + + // mark confirmed quorum in the bitmap + confirmedQuorumsBitmap = BitmapUtils.setBit(confirmedQuorumsBitmap, blobHeaders[i].quorumBlobParams[j].quorumNumber); + } + + // check that required quorums are a subset of the confirmed quorums + require( + BitmapUtils.isSubsetOf( + quorumNumbersRequiredBitmap, + confirmedQuorumsBitmap + ), + "EigenDARollupUtils.verifyBlob: required quorums are not a subset of the confirmed quorums" + ); + } + } + /** * @notice gets the adversary threshold percentage for a given quorum * @param eigenDAServiceManager the contract in which the batch was confirmed @@ -106,7 +176,7 @@ library EigenDARollupUtils { function getQuorumAdversaryThreshold( IEigenDAServiceManager eigenDAServiceManager, uint256 quorumNumber - ) public view returns(uint8 adversaryThresholdPercentage) { + ) internal view returns(uint8 adversaryThresholdPercentage) { if(eigenDAServiceManager.quorumAdversaryThresholdPercentages().length > quorumNumber){ adversaryThresholdPercentage = uint8(eigenDAServiceManager.quorumAdversaryThresholdPercentages()[quorumNumber]); } @@ -137,4 +207,4 @@ library EigenDARollupUtils { BN254.negGeneratorG2() ); } -} +} \ No newline at end of file diff --git a/contracts/src/rollup/MockRollup.sol b/contracts/test/rollupV1/MockRollup.sol similarity index 82% rename from contracts/src/rollup/MockRollup.sol rename to contracts/test/rollupV1/MockRollup.sol index e0ac664155..76c2d3e0b6 100644 --- a/contracts/src/rollup/MockRollup.sol +++ b/contracts/test/rollupV1/MockRollup.sol @@ -1,10 +1,11 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: MIT pragma solidity ^0.8.9; -import {EigenDARollupUtils} from "../libraries/EigenDARollupUtils.sol"; -import {EigenDAServiceManager} from "../core/EigenDAServiceManager.sol"; -import {IEigenDAServiceManager} from "../interfaces/IEigenDAServiceManager.sol"; +import {EigenDARollupUtils} from "./EigenDARollupUtils.sol"; +import {EigenDAServiceManager} from "../../src/core/EigenDAServiceManager.sol"; +import {IEigenDAServiceManager} from "../../src/interfaces/IEigenDAServiceManager.sol"; import {BN254} from "eigenlayer-middleware/libraries/BN254.sol"; +import "../../src/interfaces/IEigenDAStructs.sol"; struct Commitment { address confirmer; // confirmer who posted the commitment @@ -31,11 +32,11 @@ contract MockRollup { * @param blobVerificationProof the blob verification proof */ function postCommitment( - IEigenDAServiceManager.BlobHeader memory blobHeader, - EigenDARollupUtils.BlobVerificationProof memory blobVerificationProof + BlobHeader memory blobHeader, + BlobVerificationProof memory blobVerificationProof ) external { // require commitment has not already been posted - require(commitments[block.timestamp].confirmer == address(0), "MockRollup.postCommitment: Commitment already posted"); + // require(commitments[block.timestamp].confirmer == address(0), "MockRollup.postCommitment: Commitment already posted"); // verify that the blob was included in the batch EigenDARollupUtils.verifyBlob(blobHeader, eigenDAServiceManager, blobVerificationProof); diff --git a/contracts/test/unit/MockRollup.t.sol b/contracts/test/rollupV1/MockRollup.t.sol similarity index 59% rename from contracts/test/unit/MockRollup.t.sol rename to contracts/test/rollupV1/MockRollup.t.sol index 4cdc82e81a..2cf29b1d4e 100644 --- a/contracts/test/unit/MockRollup.t.sol +++ b/contracts/test/rollupV1/MockRollup.t.sol @@ -1,37 +1,17 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: MIT pragma solidity ^0.8.9; -import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import {MockRollup} from "./MockRollup.sol"; +import "../MockEigenDADeployer.sol"; -import {BLSMockAVSDeployer} from "../../lib/eigenlayer-middleware/test/utils/BLSMockAVSDeployer.sol"; -import {MockRollup} from "../../src/rollup/MockRollup.sol"; -import {EigenDAHasher} from "../../src/libraries/EigenDAHasher.sol"; -import {EigenDAServiceManager} from "../../src/core/EigenDAServiceManager.sol"; -import {IEigenDAServiceManager} from "../../src/interfaces/IEigenDAServiceManager.sol"; -import {EigenDARollupUtils} from "../../src/libraries/EigenDARollupUtils.sol"; -import {BN254} from "eigenlayer-middleware/libraries/BN254.sol"; - -import "forge-std/StdStorage.sol"; - -contract MockRollupTest is BLSMockAVSDeployer { - using stdStorage for StdStorage; +contract MockRollupTest is MockEigenDADeployer { using BN254 for BN254.G1Point; - using EigenDAHasher for IEigenDAServiceManager.BatchHeader; - using EigenDAHasher for IEigenDAServiceManager.ReducedBatchHeader; - using EigenDAHasher for IEigenDAServiceManager.BlobHeader; - using EigenDAHasher for IEigenDAServiceManager.BatchMetadata; - - EigenDAServiceManager eigenDAServiceManager; - EigenDAServiceManager eigenDAServiceManagerImplementation; - - uint8 defaultCodingRatioPercentage = 10; - uint32 defaultReferenceBlockNumber = 100; - uint32 defaultConfirmationBlockNumber = 1000; - uint32 defaultBatchId = 0; - uint256 defaultStakeRequired = 1 ether; - - mapping(uint8 => bool) public quorumNumbersUsed; + using EigenDAHasher for BatchHeader; + using EigenDAHasher for ReducedBatchHeader; + using EigenDAHasher for BlobHeader; + using EigenDAHasher for BatchMetadata; + using stdStorage for StdStorage; address alice = address(0x101); address bob = address(0x202); @@ -50,47 +30,20 @@ contract MockRollupTest is BLSMockAVSDeployer { BN254.G2Point illegalProof; function setUp() public { - _setUpBLSMockAVSDeployer(); - - eigenDAServiceManagerImplementation = new EigenDAServiceManager( - avsDirectory, - registryCoordinator, - stakeRegistry - ); - - address[] memory confirmers = new address[](1); - confirmers[0] = registryCoordinatorOwner; - - // Third, upgrade the proxy contracts to use the correct implementation contracts and initialize them. - eigenDAServiceManager = EigenDAServiceManager( - address( - new TransparentUpgradeableProxy( - address(eigenDAServiceManagerImplementation), - address(proxyAdmin), - abi.encodeWithSelector( - EigenDAServiceManager.initialize.selector, - pauserRegistry, - 0, - registryCoordinatorOwner, - confirmers - ) - ) - ) - ); - - mockRollup = new MockRollup(eigenDAServiceManager, s1); + _deployDA(); + + mockRollup = new MockRollup(IEigenDAServiceManager(address(eigenDAServiceManager)), s1); //hardcode g2 proof illegalProof.X[1] = 11151623676041303181597631684634074376466382703418354161831688442589830350329; illegalProof.X[0] = 21587740443732524623985464356760343072434825248946003815467233999912459579351; illegalProof.Y[1] = 4222041728992406478862708226745479381252734858741080790666424175645694456140; illegalProof.Y[0] = 17511259870083276759899704237100059449000397154439723516103658719937845846446; - } function testChallenge(uint256 pseudoRandomNumber) public { //get commitment with illegal value - (IEigenDAServiceManager.BlobHeader memory blobHeader, EigenDARollupUtils.BlobVerificationProof memory blobVerificationProof) = _getCommitment(pseudoRandomNumber); + (BlobHeader memory blobHeader, BlobVerificationProof memory blobVerificationProof) = _getCommitment(pseudoRandomNumber); mockRollup.postCommitment(blobHeader, blobVerificationProof); @@ -105,26 +58,26 @@ contract MockRollupTest is BLSMockAVSDeployer { illegalCommitment = s0.scalar_mul(1).plus(s1.scalar_mul(1)).plus(s2.scalar_mul(1)).plus(s3.scalar_mul(1)).plus(s4.scalar_mul(1)); } - function _getCommitment(uint256 pseudoRandomNumber) internal returns (IEigenDAServiceManager.BlobHeader memory, EigenDARollupUtils.BlobVerificationProof memory){ + function _getCommitment(uint256 pseudoRandomNumber) internal returns (BlobHeader memory, BlobVerificationProof memory){ uint256 numQuorumBlobParams = 2; - IEigenDAServiceManager.BlobHeader[] memory blobHeader = new IEigenDAServiceManager.BlobHeader[](2); + BlobHeader[] memory blobHeader = new BlobHeader[](2); blobHeader[0] = _generateBlobHeader(pseudoRandomNumber, numQuorumBlobParams); uint256 anotherPseudoRandomNumber = uint256(keccak256(abi.encodePacked(pseudoRandomNumber))); blobHeader[1] = _generateBlobHeader(anotherPseudoRandomNumber, numQuorumBlobParams); - IEigenDAServiceManager.BatchHeader memory batchHeader; + BatchHeader memory batchHeader; bytes memory firstBlobHash = abi.encodePacked(blobHeader[0].hashBlobHeader()); bytes memory secondBlobHash = abi.encodePacked(blobHeader[1].hashBlobHeader()); batchHeader.blobHeadersRoot = keccak256(abi.encodePacked(keccak256(firstBlobHash), keccak256(secondBlobHash))); // add dummy quorum numbers and quorum threshold percentages making sure confirmationThresholdPercentage = adversaryThresholdPercentage + defaultCodingRatioPercentage for (uint i = 0; i < blobHeader[1].quorumBlobParams.length; i++) { batchHeader.quorumNumbers = abi.encodePacked(batchHeader.quorumNumbers, blobHeader[1].quorumBlobParams[i].quorumNumber); - batchHeader.signedStakeForQuorums = abi.encodePacked(batchHeader.signedStakeForQuorums, blobHeader[1].quorumBlobParams[i].adversaryThresholdPercentage + defaultCodingRatioPercentage); + batchHeader.signedStakeForQuorums = abi.encodePacked(batchHeader.signedStakeForQuorums, blobHeader[1].quorumBlobParams[i].confirmationThresholdPercentage); } batchHeader.referenceBlockNumber = uint32(block.number); // add dummy batch metadata - IEigenDAServiceManager.BatchMetadata memory batchMetadata; + BatchMetadata memory batchMetadata; batchMetadata.batchHeader = batchHeader; batchMetadata.signatoryRecordHash = keccak256(abi.encodePacked("signatoryRecordHash")); batchMetadata.confirmationBlockNumber = defaultConfirmationBlockNumber; @@ -135,7 +88,7 @@ contract MockRollupTest is BLSMockAVSDeployer { .with_key(defaultBatchId) .checked_write(batchMetadata.hashBatchMetadata()); - EigenDARollupUtils.BlobVerificationProof memory blobVerificationProof; + BlobVerificationProof memory blobVerificationProof; blobVerificationProof.batchId = defaultBatchId; blobVerificationProof.batchMetadata = batchMetadata; blobVerificationProof.inclusionProof = abi.encodePacked(keccak256(firstBlobHash)); @@ -148,17 +101,17 @@ contract MockRollupTest is BLSMockAVSDeployer { return (blobHeader[1], blobVerificationProof); } - function _generateBlobHeader(uint256 pseudoRandomNumber, uint256 numQuorumsBlobParams) internal returns (IEigenDAServiceManager.BlobHeader memory) { + function _generateBlobHeader(uint256 pseudoRandomNumber, uint256 numQuorumsBlobParams) internal returns (BlobHeader memory) { if(pseudoRandomNumber == 0) { pseudoRandomNumber = 1; } - IEigenDAServiceManager.BlobHeader memory blobHeader; + BlobHeader memory blobHeader; blobHeader.commitment = _getIllegalCommitment(); blobHeader.dataLength = uint32(uint256(keccak256(abi.encodePacked(pseudoRandomNumber, "blobHeader.dataLength")))); - blobHeader.quorumBlobParams = new IEigenDAServiceManager.QuorumBlobParam[](numQuorumsBlobParams); + blobHeader.quorumBlobParams = new QuorumBlobParam[](numQuorumsBlobParams); for (uint i = 0; i < numQuorumsBlobParams; i++) { if(i < 2){ blobHeader.quorumBlobParams[i].quorumNumber = uint8(i); @@ -171,9 +124,9 @@ contract MockRollupTest is BLSMockAVSDeployer { } quorumNumbersUsed[blobHeader.quorumBlobParams[i].quorumNumber] = true; } - blobHeader.quorumBlobParams[i].adversaryThresholdPercentage = EigenDARollupUtils.getQuorumAdversaryThreshold(eigenDAServiceManager, blobHeader.quorumBlobParams[i].quorumNumber); + blobHeader.quorumBlobParams[i].adversaryThresholdPercentage = eigenDABlobVerifier.getQuorumAdversaryThresholdPercentage(blobHeader.quorumBlobParams[i].quorumNumber); blobHeader.quorumBlobParams[i].chunkLength = uint32(uint256(keccak256(abi.encodePacked(pseudoRandomNumber, "blobHeader.quorumBlobParams[i].chunkLength", i)))); - blobHeader.quorumBlobParams[i].confirmationThresholdPercentage = blobHeader.quorumBlobParams[i].adversaryThresholdPercentage + 1; + blobHeader.quorumBlobParams[i].confirmationThresholdPercentage = eigenDABlobVerifier.getQuorumConfirmationThresholdPercentage(blobHeader.quorumBlobParams[i].quorumNumber); } // mark all quorum numbers as unused for (uint i = 0; i < numQuorumsBlobParams; i++) { @@ -182,10 +135,4 @@ contract MockRollupTest is BLSMockAVSDeployer { return blobHeader; } - - function testGetQuorumAdversaryThreshold () public { - require(EigenDARollupUtils.getQuorumAdversaryThreshold(eigenDAServiceManager, 0) == 33, "getQuorumAdversaryThreshold failed"); - //require(EigenDARollupUtils.getQuorumAdversaryThreshold(eigenDAServiceManager, 1) == 33, "getQuorumAdversaryThreshold failed"); - } - } \ No newline at end of file diff --git a/contracts/test/go/rollup.go b/contracts/test/rollupV1/rollup_proof.go similarity index 100% rename from contracts/test/go/rollup.go rename to contracts/test/rollupV1/rollup_proof.go diff --git a/contracts/test/unit/EigenDABlobUtils.t.sol b/contracts/test/unit/EigenDABlobUtils.t.sol deleted file mode 100644 index 2ff183e7b3..0000000000 --- a/contracts/test/unit/EigenDABlobUtils.t.sol +++ /dev/null @@ -1,428 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity =0.8.12; - -import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; - -import "../../lib/eigenlayer-middleware/test/utils/BLSMockAVSDeployer.sol"; -import {EigenDAHasher} from "../../src/libraries/EigenDAHasher.sol"; -import {EigenDAServiceManager} from "../../src/core/EigenDAServiceManager.sol"; -import {EigenDARollupUtils} from "../../src/libraries/EigenDARollupUtils.sol"; -import {EigenDAHasher} from "../../src/libraries/EigenDAHasher.sol"; -import {EigenDABlobUtilsHarness} from "../harnesses/EigenDABlobUtilsHarness.sol"; -import {EigenDAServiceManager} from "../../src/core/EigenDAServiceManager.sol"; -import {IEigenDAServiceManager} from "../../src/interfaces/IEigenDAServiceManager.sol"; - - -import "forge-std/StdStorage.sol"; - -contract EigenDABlobUtilsUnit is BLSMockAVSDeployer { - using stdStorage for StdStorage; - - using BN254 for BN254.G1Point; - using EigenDAHasher for IEigenDAServiceManager.BatchHeader; - using EigenDAHasher for IEigenDAServiceManager.ReducedBatchHeader; - using EigenDAHasher for IEigenDAServiceManager.BlobHeader; - using EigenDAHasher for IEigenDAServiceManager.BatchMetadata; - - address confirmer = address(uint160(uint256(keccak256(abi.encodePacked("confirmer"))))); - address notConfirmer = address(uint160(uint256(keccak256(abi.encodePacked("notConfirmer"))))); - - EigenDABlobUtilsHarness eigenDABlobUtilsHarness; - - EigenDAServiceManager eigenDAServiceManager; - EigenDAServiceManager eigenDAServiceManagerImplementation; - - uint8 defaultCodingRatioPercentage = 10; - uint32 defaultReferenceBlockNumber = 100; - uint32 defaultConfirmationBlockNumber = 1000; - uint32 defaultBatchId = 0; - - mapping(uint8 => bool) public quorumNumbersUsed; - - function setUp() virtual public { - _setUpBLSMockAVSDeployer(); - - eigenDAServiceManagerImplementation = new EigenDAServiceManager( - avsDirectory, - registryCoordinator, - stakeRegistry - ); - - address[] memory confirmers = new address[](1); - confirmers[0] = confirmer; - - // Third, upgrade the proxy contracts to use the correct implementation contracts and initialize them. - eigenDAServiceManager = EigenDAServiceManager( - address( - new TransparentUpgradeableProxy( - address(eigenDAServiceManagerImplementation), - address(proxyAdmin), - abi.encodeWithSelector( - EigenDAServiceManager.initialize.selector, - pauserRegistry, - 0, - registryCoordinatorOwner, - confirmers - ) - ) - ) - ); - - eigenDABlobUtilsHarness = new EigenDABlobUtilsHarness(); - } - - function testVerifyBlob_TwoQuorums(uint256 pseudoRandomNumber) public { - uint256 numQuorumBlobParams = 2; - IEigenDAServiceManager.BlobHeader[] memory blobHeader = new IEigenDAServiceManager.BlobHeader[](2); - blobHeader[0] = _generateRandomBlobHeader(pseudoRandomNumber, numQuorumBlobParams); - uint256 anotherPseudoRandomNumber = uint256(keccak256(abi.encodePacked(pseudoRandomNumber))); - blobHeader[1] = _generateRandomBlobHeader(anotherPseudoRandomNumber, numQuorumBlobParams); - - IEigenDAServiceManager.BatchHeader memory batchHeader; - bytes memory firstBlobHash = abi.encodePacked(blobHeader[0].hashBlobHeader()); - bytes memory secondBlobHash = abi.encodePacked(blobHeader[1].hashBlobHeader()); - batchHeader.blobHeadersRoot = keccak256(abi.encodePacked(keccak256(firstBlobHash), keccak256(secondBlobHash))); - // add dummy quorum numbers and quorum threshold percentages making sure confirmationThresholdPercentage = adversaryThresholdPercentage + defaultCodingRatioPercentage - for (uint i = 0; i < blobHeader[1].quorumBlobParams.length; i++) { - batchHeader.quorumNumbers = abi.encodePacked(batchHeader.quorumNumbers, blobHeader[1].quorumBlobParams[i].quorumNumber); - batchHeader.signedStakeForQuorums = abi.encodePacked(batchHeader.signedStakeForQuorums, blobHeader[1].quorumBlobParams[i].adversaryThresholdPercentage + defaultCodingRatioPercentage); - } - batchHeader.referenceBlockNumber = uint32(block.number); - - // add dummy batch metadata - IEigenDAServiceManager.BatchMetadata memory batchMetadata; - batchMetadata.batchHeader = batchHeader; - batchMetadata.signatoryRecordHash = keccak256(abi.encodePacked("signatoryRecordHash")); - batchMetadata.confirmationBlockNumber = defaultConfirmationBlockNumber; - - stdstore - .target(address(eigenDAServiceManager)) - .sig("batchIdToBatchMetadataHash(uint32)") - .with_key(defaultBatchId) - .checked_write(batchMetadata.hashBatchMetadata()); - - EigenDARollupUtils.BlobVerificationProof memory blobVerificationProof; - blobVerificationProof.batchId = defaultBatchId; - blobVerificationProof.batchMetadata = batchMetadata; - blobVerificationProof.inclusionProof = abi.encodePacked(keccak256(firstBlobHash)); - blobVerificationProof.blobIndex = 1; - blobVerificationProof.quorumIndices = new bytes(batchHeader.quorumNumbers.length); - for (uint i = 0; i < batchHeader.quorumNumbers.length; i++) { - blobVerificationProof.quorumIndices[i] = bytes1(uint8(i)); - } - - uint256 gasBefore = gasleft(); - eigenDABlobUtilsHarness.verifyBlob(blobHeader[1], eigenDAServiceManager, blobVerificationProof); - uint256 gasAfter = gasleft(); - emit log_named_uint("gas used", gasBefore - gasAfter); - } - - function testVerifyBlob_InvalidMetadataHash(uint256 pseudoRandomNumber) public { - uint256 numQuorumBlobParams = pseudoRandomNumber % 192; - IEigenDAServiceManager.BlobHeader[] memory blobHeader = new IEigenDAServiceManager.BlobHeader[](2); - blobHeader[0] = _generateRandomBlobHeader(pseudoRandomNumber, numQuorumBlobParams); - uint256 anotherPseudoRandomNumber = uint256(keccak256(abi.encodePacked(pseudoRandomNumber))); - blobHeader[1] = _generateRandomBlobHeader(anotherPseudoRandomNumber, numQuorumBlobParams); - - EigenDARollupUtils.BlobVerificationProof memory blobVerificationProof; - blobVerificationProof.batchId = defaultBatchId; - - cheats.expectRevert("EigenDARollupUtils.verifyBlob: batchMetadata does not match stored metadata"); - eigenDABlobUtilsHarness.verifyBlob(blobHeader[1], eigenDAServiceManager, blobVerificationProof); - } - - function testVerifyBlob_InvalidMerkleProof(uint256 pseudoRandomNumber) public { - uint256 numQuorumBlobParams = pseudoRandomNumber % 192; - IEigenDAServiceManager.BlobHeader[] memory blobHeader = new IEigenDAServiceManager.BlobHeader[](2); - blobHeader[0] = _generateRandomBlobHeader(pseudoRandomNumber, numQuorumBlobParams); - uint256 anotherPseudoRandomNumber = uint256(keccak256(abi.encodePacked(pseudoRandomNumber))); - blobHeader[1] = _generateRandomBlobHeader(anotherPseudoRandomNumber, numQuorumBlobParams); - - // add dummy batch metadata - IEigenDAServiceManager.BatchMetadata memory batchMetadata; - - stdstore - .target(address(eigenDAServiceManager)) - .sig("batchIdToBatchMetadataHash(uint32)") - .with_key(defaultBatchId) - .checked_write(batchMetadata.hashBatchMetadata()); - - EigenDARollupUtils.BlobVerificationProof memory blobVerificationProof; - blobVerificationProof.batchId = defaultBatchId; - blobVerificationProof.batchMetadata = batchMetadata; - blobVerificationProof.inclusionProof = abi.encodePacked(bytes32(0)); - blobVerificationProof.blobIndex = 1; - - cheats.expectRevert("EigenDARollupUtils.verifyBlob: inclusion proof is invalid"); - eigenDABlobUtilsHarness.verifyBlob(blobHeader[1], eigenDAServiceManager, blobVerificationProof); - } - - function testVerifyBlob_RandomNumberOfQuorums(uint256 pseudoRandomNumber) public { - uint256 numQuorumBlobParams = 2 + (pseudoRandomNumber % 192); - IEigenDAServiceManager.BlobHeader[] memory blobHeader = new IEigenDAServiceManager.BlobHeader[](2); - blobHeader[0] = _generateRandomBlobHeader(pseudoRandomNumber, numQuorumBlobParams); - uint256 anotherPseudoRandomNumber = uint256(keccak256(abi.encodePacked(pseudoRandomNumber))); - blobHeader[1] = _generateRandomBlobHeader(anotherPseudoRandomNumber, numQuorumBlobParams); - - IEigenDAServiceManager.BatchHeader memory batchHeader; - bytes memory firstBlobHash = abi.encodePacked(blobHeader[0].hashBlobHeader()); - bytes memory secondBlobHash = abi.encodePacked(blobHeader[1].hashBlobHeader()); - batchHeader.blobHeadersRoot = keccak256(abi.encodePacked(keccak256(firstBlobHash), keccak256(secondBlobHash))); - // add dummy quorum numbers and quorum threshold percentages making sure confirmationThresholdPercentage = adversaryThresholdPercentage + defaultCodingRatioPercentage - for (uint i = 0; i < blobHeader[1].quorumBlobParams.length; i++) { - batchHeader.quorumNumbers = abi.encodePacked(batchHeader.quorumNumbers, blobHeader[1].quorumBlobParams[i].quorumNumber); - batchHeader.signedStakeForQuorums = abi.encodePacked(batchHeader.signedStakeForQuorums, blobHeader[1].quorumBlobParams[i].adversaryThresholdPercentage + defaultCodingRatioPercentage); - } - batchHeader.referenceBlockNumber = uint32(block.number); - - // add dummy batch metadata - IEigenDAServiceManager.BatchMetadata memory batchMetadata; - batchMetadata.batchHeader = batchHeader; - batchMetadata.signatoryRecordHash = keccak256(abi.encodePacked("signatoryRecordHash")); - batchMetadata.confirmationBlockNumber = defaultConfirmationBlockNumber; - - stdstore - .target(address(eigenDAServiceManager)) - .sig("batchIdToBatchMetadataHash(uint32)") - .with_key(defaultBatchId) - .checked_write(batchMetadata.hashBatchMetadata()); - - EigenDARollupUtils.BlobVerificationProof memory blobVerificationProof; - blobVerificationProof.batchId = defaultBatchId; - blobVerificationProof.batchMetadata = batchMetadata; - blobVerificationProof.inclusionProof = abi.encodePacked(keccak256(firstBlobHash)); - blobVerificationProof.blobIndex = 1; - blobVerificationProof.quorumIndices = new bytes(batchHeader.quorumNumbers.length); - for (uint i = 0; i < batchHeader.quorumNumbers.length; i++) { - blobVerificationProof.quorumIndices[i] = bytes1(uint8(i)); - } - - uint256 gasBefore = gasleft(); - eigenDABlobUtilsHarness.verifyBlob(blobHeader[1], eigenDAServiceManager, blobVerificationProof); - uint256 gasAfter = gasleft(); - emit log_named_uint("gas used", gasBefore - gasAfter); - } - - function xtestVerifyBlob_RequiredQuorumsNotMet(uint256 pseudoRandomNumber) public { - uint256 numQuorumBlobParams = 1; - IEigenDAServiceManager.BlobHeader[] memory blobHeader = new IEigenDAServiceManager.BlobHeader[](2); - blobHeader[0] = _generateRandomBlobHeader(pseudoRandomNumber, numQuorumBlobParams); - uint256 anotherPseudoRandomNumber = uint256(keccak256(abi.encodePacked(pseudoRandomNumber))); - blobHeader[1] = _generateRandomBlobHeader(anotherPseudoRandomNumber, numQuorumBlobParams); - - IEigenDAServiceManager.BatchHeader memory batchHeader; - bytes memory firstBlobHash = abi.encodePacked(blobHeader[0].hashBlobHeader()); - bytes memory secondBlobHash = abi.encodePacked(blobHeader[1].hashBlobHeader()); - batchHeader.blobHeadersRoot = keccak256(abi.encodePacked(keccak256(firstBlobHash), keccak256(secondBlobHash))); - // add dummy quorum numbers and quorum threshold percentages making sure confirmationThresholdPercentage = adversaryThresholdPercentage + defaultCodingRatioPercentage - for (uint i = 0; i < blobHeader[1].quorumBlobParams.length; i++) { - batchHeader.quorumNumbers = abi.encodePacked(batchHeader.quorumNumbers, blobHeader[1].quorumBlobParams[i].quorumNumber); - batchHeader.signedStakeForQuorums = abi.encodePacked(batchHeader.signedStakeForQuorums, blobHeader[1].quorumBlobParams[i].adversaryThresholdPercentage + defaultCodingRatioPercentage); - } - batchHeader.referenceBlockNumber = uint32(block.number); - - // add dummy batch metadata - IEigenDAServiceManager.BatchMetadata memory batchMetadata; - batchMetadata.batchHeader = batchHeader; - batchMetadata.signatoryRecordHash = keccak256(abi.encodePacked("signatoryRecordHash")); - batchMetadata.confirmationBlockNumber = defaultConfirmationBlockNumber; - - stdstore - .target(address(eigenDAServiceManager)) - .sig("batchIdToBatchMetadataHash(uint32)") - .with_key(defaultBatchId) - .checked_write(batchMetadata.hashBatchMetadata()); - - EigenDARollupUtils.BlobVerificationProof memory blobVerificationProof; - blobVerificationProof.batchId = defaultBatchId; - blobVerificationProof.batchMetadata = batchMetadata; - blobVerificationProof.inclusionProof = abi.encodePacked(keccak256(firstBlobHash)); - blobVerificationProof.blobIndex = 1; - blobVerificationProof.quorumIndices = new bytes(batchHeader.quorumNumbers.length); - for (uint i = 0; i < batchHeader.quorumNumbers.length; i++) { - blobVerificationProof.quorumIndices[i] = bytes1(uint8(i)); - } - - cheats.expectRevert("EigenDARollupUtils.verifyBlob: required quorums are not a subset of the confirmed quorums"); - eigenDABlobUtilsHarness.verifyBlob(blobHeader[1], eigenDAServiceManager, blobVerificationProof); - } - - function xtestVerifyBlob_AdversayThresholdNotMet(uint256 pseudoRandomNumber) public { - uint256 numQuorumBlobParams = 2; - IEigenDAServiceManager.BlobHeader[] memory blobHeader = new IEigenDAServiceManager.BlobHeader[](2); - blobHeader[0] = _generateRandomBlobHeader(pseudoRandomNumber, numQuorumBlobParams); - uint256 anotherPseudoRandomNumber = uint256(keccak256(abi.encodePacked(pseudoRandomNumber))); - blobHeader[1] = _generateRandomBlobHeader(anotherPseudoRandomNumber, numQuorumBlobParams); - - for (uint i = 0; i < numQuorumBlobParams; i++) { - blobHeader[0].quorumBlobParams[i].adversaryThresholdPercentage = EigenDARollupUtils.getQuorumAdversaryThreshold(eigenDAServiceManager, blobHeader[0].quorumBlobParams[i].quorumNumber) - 1; - blobHeader[1].quorumBlobParams[i].adversaryThresholdPercentage = EigenDARollupUtils.getQuorumAdversaryThreshold(eigenDAServiceManager, blobHeader[1].quorumBlobParams[i].quorumNumber) - 1; - } - - IEigenDAServiceManager.BatchHeader memory batchHeader; - bytes memory firstBlobHash = abi.encodePacked(blobHeader[0].hashBlobHeader()); - bytes memory secondBlobHash = abi.encodePacked(blobHeader[1].hashBlobHeader()); - batchHeader.blobHeadersRoot = keccak256(abi.encodePacked(keccak256(firstBlobHash), keccak256(secondBlobHash))); - // add dummy quorum numbers and quorum threshold percentages making sure confirmationThresholdPercentage = adversaryThresholdPercentage + defaultCodingRatioPercentage - for (uint i = 0; i < blobHeader[1].quorumBlobParams.length; i++) { - batchHeader.quorumNumbers = abi.encodePacked(batchHeader.quorumNumbers, blobHeader[1].quorumBlobParams[i].quorumNumber); - batchHeader.signedStakeForQuorums = abi.encodePacked(batchHeader.signedStakeForQuorums, blobHeader[1].quorumBlobParams[i].adversaryThresholdPercentage + defaultCodingRatioPercentage); - } - batchHeader.referenceBlockNumber = uint32(block.number); - - // add dummy batch metadata - IEigenDAServiceManager.BatchMetadata memory batchMetadata; - batchMetadata.batchHeader = batchHeader; - batchMetadata.signatoryRecordHash = keccak256(abi.encodePacked("signatoryRecordHash")); - batchMetadata.confirmationBlockNumber = defaultConfirmationBlockNumber; - - stdstore - .target(address(eigenDAServiceManager)) - .sig("batchIdToBatchMetadataHash(uint32)") - .with_key(defaultBatchId) - .checked_write(batchMetadata.hashBatchMetadata()); - - EigenDARollupUtils.BlobVerificationProof memory blobVerificationProof; - blobVerificationProof.batchId = defaultBatchId; - blobVerificationProof.batchMetadata = batchMetadata; - blobVerificationProof.inclusionProof = abi.encodePacked(keccak256(firstBlobHash)); - blobVerificationProof.blobIndex = 1; - blobVerificationProof.quorumIndices = new bytes(batchHeader.quorumNumbers.length); - for (uint i = 0; i < batchHeader.quorumNumbers.length; i++) { - blobVerificationProof.quorumIndices[i] = bytes1(uint8(i)); - } - - cheats.expectRevert("EigenDARollupUtils.verifyBlob: adversaryThresholdPercentage is not met"); - eigenDABlobUtilsHarness.verifyBlob(blobHeader[1], eigenDAServiceManager, blobVerificationProof); - } - - function testVerifyBlob_QuorumNumberMismatch(uint256 pseudoRandomNumber) public { - uint256 numQuorumBlobParams = 2; - IEigenDAServiceManager.BlobHeader[] memory blobHeader = new IEigenDAServiceManager.BlobHeader[](2); - blobHeader[0] = _generateRandomBlobHeader(pseudoRandomNumber, numQuorumBlobParams); - uint256 anotherPseudoRandomNumber = uint256(keccak256(abi.encodePacked(pseudoRandomNumber))); - blobHeader[1] = _generateRandomBlobHeader(anotherPseudoRandomNumber, numQuorumBlobParams); - - IEigenDAServiceManager.BatchHeader memory batchHeader; - bytes memory firstBlobHash = abi.encodePacked(blobHeader[0].hashBlobHeader()); - bytes memory secondBlobHash = abi.encodePacked(blobHeader[1].hashBlobHeader()); - batchHeader.blobHeadersRoot = keccak256(abi.encodePacked(keccak256(firstBlobHash), keccak256(secondBlobHash))); - // add dummy quorum numbers and quorum threshold percentages making sure confirmationThresholdPercentage = adversaryThresholdPercentage + defaultCodingRatioPercentage - for (uint i = 0; i < blobHeader[1].quorumBlobParams.length; i++) { - batchHeader.quorumNumbers = abi.encodePacked(batchHeader.quorumNumbers, blobHeader[1].quorumBlobParams[i].quorumNumber); - batchHeader.signedStakeForQuorums = abi.encodePacked(batchHeader.signedStakeForQuorums, blobHeader[1].quorumBlobParams[i].adversaryThresholdPercentage + defaultCodingRatioPercentage); - } - batchHeader.referenceBlockNumber = uint32(block.number); - - // add dummy batch metadata - IEigenDAServiceManager.BatchMetadata memory batchMetadata; - batchMetadata.batchHeader = batchHeader; - batchMetadata.signatoryRecordHash = keccak256(abi.encodePacked("signatoryRecordHash")); - batchMetadata.confirmationBlockNumber = defaultConfirmationBlockNumber; - - stdstore - .target(address(eigenDAServiceManager)) - .sig("batchIdToBatchMetadataHash(uint32)") - .with_key(defaultBatchId) - .checked_write(batchMetadata.hashBatchMetadata()); - - EigenDARollupUtils.BlobVerificationProof memory blobVerificationProof; - blobVerificationProof.batchId = defaultBatchId; - blobVerificationProof.batchMetadata = batchMetadata; - blobVerificationProof.inclusionProof = abi.encodePacked(keccak256(firstBlobHash)); - blobVerificationProof.blobIndex = 1; - blobVerificationProof.quorumIndices = new bytes(batchHeader.quorumNumbers.length); - for (uint i = 0; i < batchHeader.quorumNumbers.length; i++) { - // implant the incorrect quorumNumbers here - blobVerificationProof.quorumIndices[i] = bytes1(uint8(batchHeader.quorumNumbers.length - 1 - i)); - } - - cheats.expectRevert("EigenDARollupUtils.verifyBlob: quorumNumber does not match"); - eigenDABlobUtilsHarness.verifyBlob(blobHeader[1], eigenDAServiceManager, blobVerificationProof); - } - - function testVerifyBlob_QuorumThresholdNotMet(uint256 pseudoRandomNumber) public { - uint256 numQuorumBlobParams = 2; - IEigenDAServiceManager.BlobHeader[] memory blobHeader = new IEigenDAServiceManager.BlobHeader[](2); - blobHeader[0] = _generateRandomBlobHeader(pseudoRandomNumber, numQuorumBlobParams); - uint256 anotherPseudoRandomNumber = uint256(keccak256(abi.encodePacked(pseudoRandomNumber))); - blobHeader[1] = _generateRandomBlobHeader(anotherPseudoRandomNumber, numQuorumBlobParams); - - IEigenDAServiceManager.BatchHeader memory batchHeader; - bytes memory firstBlobHash = abi.encodePacked(blobHeader[0].hashBlobHeader()); - bytes memory secondBlobHash = abi.encodePacked(blobHeader[1].hashBlobHeader()); - batchHeader.blobHeadersRoot = keccak256(abi.encodePacked(keccak256(firstBlobHash), keccak256(secondBlobHash))); - // add dummy quorum numbers and quorum threshold percentages making sure confirmationThresholdPercentage = 100 - for (uint i = 0; i < blobHeader[1].quorumBlobParams.length; i++) { - batchHeader.quorumNumbers = abi.encodePacked(batchHeader.quorumNumbers, blobHeader[1].quorumBlobParams[i].quorumNumber); - batchHeader.signedStakeForQuorums = abi.encodePacked(batchHeader.signedStakeForQuorums, blobHeader[1].quorumBlobParams[i].confirmationThresholdPercentage - 1); - } - batchHeader.referenceBlockNumber = uint32(block.number); - - // add dummy batch metadata - IEigenDAServiceManager.BatchMetadata memory batchMetadata; - batchMetadata.batchHeader = batchHeader; - batchMetadata.signatoryRecordHash = keccak256(abi.encodePacked("signatoryRecordHash")); - batchMetadata.confirmationBlockNumber = defaultConfirmationBlockNumber; - - stdstore - .target(address(eigenDAServiceManager)) - .sig("batchIdToBatchMetadataHash(uint32)") - .with_key(defaultBatchId) - .checked_write(batchMetadata.hashBatchMetadata()); - - EigenDARollupUtils.BlobVerificationProof memory blobVerificationProof; - blobVerificationProof.batchId = defaultBatchId; - blobVerificationProof.batchMetadata = batchMetadata; - blobVerificationProof.inclusionProof = abi.encodePacked(keccak256(firstBlobHash)); - blobVerificationProof.blobIndex = 1; - blobVerificationProof.quorumIndices = new bytes(batchHeader.quorumNumbers.length); - for (uint i = 0; i < batchHeader.quorumNumbers.length; i++) { - // implant the incorrect quorumNumbers here - blobVerificationProof.quorumIndices[i] = bytes1(uint8(i)); - } - - cheats.expectRevert("EigenDARollupUtils.verifyBlob: confirmationThresholdPercentage is not met"); - eigenDABlobUtilsHarness.verifyBlob(blobHeader[1], eigenDAServiceManager, blobVerificationProof); - } - - // generates a random blob header with the given coding ratio percentage as the ratio of original data to encoded data - function _generateRandomBlobHeader(uint256 pseudoRandomNumber, uint256 numQuorumsBlobParams) internal returns (IEigenDAServiceManager.BlobHeader memory) { - if(pseudoRandomNumber == 0) { - pseudoRandomNumber = 1; - } - - IEigenDAServiceManager.BlobHeader memory blobHeader; - blobHeader.commitment.X = uint256(keccak256(abi.encodePacked(pseudoRandomNumber, "blobHeader.commitment.X"))) % BN254.FP_MODULUS; - blobHeader.commitment.Y = uint256(keccak256(abi.encodePacked(pseudoRandomNumber, "blobHeader.commitment.Y"))) % BN254.FP_MODULUS; - - blobHeader.dataLength = uint32(uint256(keccak256(abi.encodePacked(pseudoRandomNumber, "blobHeader.dataLength")))); - - blobHeader.quorumBlobParams = new IEigenDAServiceManager.QuorumBlobParam[](numQuorumsBlobParams); - blobHeader.dataLength = uint32(uint256(keccak256(abi.encodePacked(pseudoRandomNumber, "blobHeader.dataLength")))); - for (uint i = 0; i < numQuorumsBlobParams; i++) { - if(i < 2){ - blobHeader.quorumBlobParams[i].quorumNumber = uint8(i); - } else { - blobHeader.quorumBlobParams[i].quorumNumber = uint8(uint256(keccak256(abi.encodePacked(pseudoRandomNumber, "blobHeader.quorumBlobParams[i].quorumNumber", i)))) % 192; - - // make sure it isn't already used - while(quorumNumbersUsed[blobHeader.quorumBlobParams[i].quorumNumber]) { - blobHeader.quorumBlobParams[i].quorumNumber = uint8(uint256(blobHeader.quorumBlobParams[i].quorumNumber) + 1) % 192; - } - quorumNumbersUsed[blobHeader.quorumBlobParams[i].quorumNumber] = true; - } - - blobHeader.quorumBlobParams[i].adversaryThresholdPercentage = EigenDARollupUtils.getQuorumAdversaryThreshold(eigenDAServiceManager, blobHeader.quorumBlobParams[i].quorumNumber); - blobHeader.quorumBlobParams[i].chunkLength = uint32(uint256(keccak256(abi.encodePacked(pseudoRandomNumber, "blobHeader.quorumBlobParams[i].chunkLength", i)))); - blobHeader.quorumBlobParams[i].confirmationThresholdPercentage = blobHeader.quorumBlobParams[i].adversaryThresholdPercentage + 1; - } - // mark all quorum numbers as unused - for (uint i = 0; i < numQuorumsBlobParams; i++) { - quorumNumbersUsed[blobHeader.quorumBlobParams[i].quorumNumber] = false; - } - - return blobHeader; - } - -} \ No newline at end of file diff --git a/contracts/test/unit/EigenDABlobUtilsV1Unit.t.sol b/contracts/test/unit/EigenDABlobUtilsV1Unit.t.sol new file mode 100644 index 0000000000..8f740b5ee8 --- /dev/null +++ b/contracts/test/unit/EigenDABlobUtilsV1Unit.t.sol @@ -0,0 +1,292 @@ +// SPDX-License-Identifier: MIT +pragma solidity =0.8.12; + +import "../MockEigenDADeployer.sol"; + +contract EigenDABlobUtilsV1Unit is MockEigenDADeployer { + using stdStorage for StdStorage; + using BN254 for BN254.G1Point; + using EigenDAHasher for BatchHeader; + using EigenDAHasher for ReducedBatchHeader; + using EigenDAHasher for BlobHeader; + using EigenDAHasher for BatchMetadata; + + function setUp() virtual public { + _deployDA(); + } + + function testVerifyBlob_TwoQuorums(uint256 pseudoRandomNumber) public { + uint256 numQuorumBlobParams = 2; + BlobHeader[] memory blobHeader = new BlobHeader[](2); + blobHeader[0] = _generateRandomBlobHeader(pseudoRandomNumber, numQuorumBlobParams); + uint256 anotherPseudoRandomNumber = uint256(keccak256(abi.encodePacked(pseudoRandomNumber))); + blobHeader[1] = _generateRandomBlobHeader(anotherPseudoRandomNumber, numQuorumBlobParams); + + BatchHeader memory batchHeader; + bytes memory firstBlobHash = abi.encodePacked(blobHeader[0].hashBlobHeader()); + bytes memory secondBlobHash = abi.encodePacked(blobHeader[1].hashBlobHeader()); + batchHeader.blobHeadersRoot = keccak256(abi.encodePacked(keccak256(firstBlobHash), keccak256(secondBlobHash))); + for (uint i = 0; i < blobHeader[1].quorumBlobParams.length; i++) { + batchHeader.quorumNumbers = abi.encodePacked(batchHeader.quorumNumbers, blobHeader[1].quorumBlobParams[i].quorumNumber); + batchHeader.signedStakeForQuorums = abi.encodePacked(batchHeader.signedStakeForQuorums, blobHeader[1].quorumBlobParams[i].confirmationThresholdPercentage); + } + batchHeader.referenceBlockNumber = uint32(block.number); + + // add dummy batch metadata + BatchMetadata memory batchMetadata; + batchMetadata.batchHeader = batchHeader; + batchMetadata.signatoryRecordHash = keccak256(abi.encodePacked("signatoryRecordHash")); + batchMetadata.confirmationBlockNumber = defaultConfirmationBlockNumber; + + stdstore + .target(address(eigenDAServiceManager)) + .sig("batchIdToBatchMetadataHash(uint32)") + .with_key(defaultBatchId) + .checked_write(batchMetadata.hashBatchMetadata()); + + BlobVerificationProof memory blobVerificationProof; + blobVerificationProof.batchId = defaultBatchId; + blobVerificationProof.batchMetadata = batchMetadata; + blobVerificationProof.inclusionProof = abi.encodePacked(keccak256(firstBlobHash)); + blobVerificationProof.blobIndex = 1; + blobVerificationProof.quorumIndices = new bytes(batchHeader.quorumNumbers.length); + for (uint i = 0; i < batchHeader.quorumNumbers.length; i++) { + blobVerificationProof.quorumIndices[i] = bytes1(uint8(i)); + } + + uint256 gasBefore = gasleft(); + eigenDABlobVerifier.verifyBlobV1(blobHeader[1], blobVerificationProof); + uint256 gasAfter = gasleft(); + emit log_named_uint("gas used", gasBefore - gasAfter); + } + + function testVerifyBlobs_TwoBlobs(uint256 pseudoRandomNumber) public { + uint256 numQuorumBlobParams = 2; + BlobHeader[] memory blobHeader = new BlobHeader[](2); + blobHeader[0] = _generateRandomBlobHeader(pseudoRandomNumber, numQuorumBlobParams); + uint256 anotherPseudoRandomNumber = uint256(keccak256(abi.encodePacked(pseudoRandomNumber))); + blobHeader[1] = _generateRandomBlobHeader(anotherPseudoRandomNumber, numQuorumBlobParams); + BatchHeader memory batchHeader; + bytes memory firstBlobHash = abi.encodePacked(blobHeader[0].hashBlobHeader()); + bytes memory secondBlobHash = abi.encodePacked(blobHeader[1].hashBlobHeader()); + batchHeader.blobHeadersRoot = keccak256(abi.encodePacked(keccak256(firstBlobHash), keccak256(secondBlobHash))); + // add dummy quorum numbers and quorum threshold percentages making sure confirmationThresholdPercentage = adversaryThresholdPercentage + defaultCodingRatioPercentage + for (uint i = 0; i < blobHeader[1].quorumBlobParams.length; i++) { + batchHeader.quorumNumbers = abi.encodePacked(batchHeader.quorumNumbers, blobHeader[1].quorumBlobParams[i].quorumNumber); + batchHeader.signedStakeForQuorums = abi.encodePacked(batchHeader.signedStakeForQuorums, blobHeader[1].quorumBlobParams[i].confirmationThresholdPercentage); } + batchHeader.referenceBlockNumber = uint32(block.number); + // add dummy batch metadata + BatchMetadata memory batchMetadata; + batchMetadata.batchHeader = batchHeader; + batchMetadata.signatoryRecordHash = keccak256(abi.encodePacked("signatoryRecordHash")); + batchMetadata.confirmationBlockNumber = defaultConfirmationBlockNumber; + stdstore + .target(address(eigenDAServiceManager)) + .sig("batchIdToBatchMetadataHash(uint32)") + .with_key(defaultBatchId) + .checked_write(batchMetadata.hashBatchMetadata()); + BlobVerificationProof[] memory blobVerificationProofs = new BlobVerificationProof[](2); + blobVerificationProofs[0].batchId = defaultBatchId; + blobVerificationProofs[1].batchId = defaultBatchId; + blobVerificationProofs[0].batchMetadata = batchMetadata; + blobVerificationProofs[1].batchMetadata = batchMetadata; + blobVerificationProofs[0].inclusionProof = abi.encodePacked(keccak256(secondBlobHash)); + blobVerificationProofs[1].inclusionProof = abi.encodePacked(keccak256(firstBlobHash)); + blobVerificationProofs[0].blobIndex = 0; + blobVerificationProofs[1].blobIndex = 1; + blobVerificationProofs[0].quorumIndices = new bytes(batchHeader.quorumNumbers.length); + blobVerificationProofs[1].quorumIndices = new bytes(batchHeader.quorumNumbers.length); + for (uint i = 0; i < batchHeader.quorumNumbers.length; i++) { + blobVerificationProofs[0].quorumIndices[i] = bytes1(uint8(i)); + blobVerificationProofs[1].quorumIndices[i] = bytes1(uint8(i)); + } + uint256 gasBefore = gasleft(); + eigenDABlobVerifier.verifyBlobsV1(blobHeader, blobVerificationProofs); + uint256 gasAfter = gasleft(); + emit log_named_uint("gas used", gasBefore - gasAfter); + } + + function testVerifyBlob_InvalidMetadataHash(uint256 pseudoRandomNumber) public { + uint256 numQuorumBlobParams = pseudoRandomNumber % 192; + BlobHeader[] memory blobHeader = new BlobHeader[](2); + blobHeader[0] = _generateRandomBlobHeader(pseudoRandomNumber, numQuorumBlobParams); + uint256 anotherPseudoRandomNumber = uint256(keccak256(abi.encodePacked(pseudoRandomNumber))); + blobHeader[1] = _generateRandomBlobHeader(anotherPseudoRandomNumber, numQuorumBlobParams); + + BlobVerificationProof memory blobVerificationProof; + blobVerificationProof.batchId = defaultBatchId; + + cheats.expectRevert("EigenDABlobVerificationUtils._verifyBlobForQuorums: batchMetadata does not match stored metadata"); + eigenDABlobVerifier.verifyBlobV1(blobHeader[1], blobVerificationProof); + } + + function testVerifyBlob_InvalidMerkleProof(uint256 pseudoRandomNumber) public { + uint256 numQuorumBlobParams = pseudoRandomNumber % 192; + BlobHeader[] memory blobHeader = new BlobHeader[](2); + blobHeader[0] = _generateRandomBlobHeader(pseudoRandomNumber, numQuorumBlobParams); + uint256 anotherPseudoRandomNumber = uint256(keccak256(abi.encodePacked(pseudoRandomNumber))); + blobHeader[1] = _generateRandomBlobHeader(anotherPseudoRandomNumber, numQuorumBlobParams); + + // add dummy batch metadata + BatchMetadata memory batchMetadata; + + stdstore + .target(address(eigenDAServiceManager)) + .sig("batchIdToBatchMetadataHash(uint32)") + .with_key(defaultBatchId) + .checked_write(batchMetadata.hashBatchMetadata()); + + BlobVerificationProof memory blobVerificationProof; + blobVerificationProof.batchId = defaultBatchId; + blobVerificationProof.batchMetadata = batchMetadata; + blobVerificationProof.inclusionProof = abi.encodePacked(bytes32(0)); + blobVerificationProof.blobIndex = 1; + + cheats.expectRevert("EigenDABlobVerificationUtils._verifyBlobForQuorums: inclusion proof is invalid"); + eigenDABlobVerifier.verifyBlobV1(blobHeader[1], blobVerificationProof); + } + + function testVerifyBlob_RequiredQuorumsNotMet(uint256 pseudoRandomNumber) public { + uint256 numQuorumBlobParams = 1; + BlobHeader[] memory blobHeader = new BlobHeader[](2); + blobHeader[0] = _generateRandomBlobHeader(pseudoRandomNumber, numQuorumBlobParams); + uint256 anotherPseudoRandomNumber = uint256(keccak256(abi.encodePacked(pseudoRandomNumber))); + blobHeader[1] = _generateRandomBlobHeader(anotherPseudoRandomNumber, numQuorumBlobParams); + + BatchHeader memory batchHeader; + bytes memory firstBlobHash = abi.encodePacked(blobHeader[0].hashBlobHeader()); + bytes memory secondBlobHash = abi.encodePacked(blobHeader[1].hashBlobHeader()); + batchHeader.blobHeadersRoot = keccak256(abi.encodePacked(keccak256(firstBlobHash), keccak256(secondBlobHash))); + for (uint i = 0; i < blobHeader[1].quorumBlobParams.length; i++) { + batchHeader.quorumNumbers = abi.encodePacked(batchHeader.quorumNumbers, blobHeader[1].quorumBlobParams[i].quorumNumber); + batchHeader.signedStakeForQuorums = abi.encodePacked(batchHeader.signedStakeForQuorums, blobHeader[1].quorumBlobParams[i].confirmationThresholdPercentage); + } + batchHeader.referenceBlockNumber = uint32(block.number); + + // add dummy batch metadata + BatchMetadata memory batchMetadata; + batchMetadata.batchHeader = batchHeader; + batchMetadata.signatoryRecordHash = keccak256(abi.encodePacked("signatoryRecordHash")); + batchMetadata.confirmationBlockNumber = defaultConfirmationBlockNumber; + + stdstore + .target(address(eigenDAServiceManager)) + .sig("batchIdToBatchMetadataHash(uint32)") + .with_key(defaultBatchId) + .checked_write(batchMetadata.hashBatchMetadata()); + + BlobVerificationProof memory blobVerificationProof; + blobVerificationProof.batchId = defaultBatchId; + blobVerificationProof.batchMetadata = batchMetadata; + blobVerificationProof.inclusionProof = abi.encodePacked(keccak256(firstBlobHash)); + blobVerificationProof.blobIndex = 1; + blobVerificationProof.quorumIndices = new bytes(batchHeader.quorumNumbers.length); + for (uint i = 0; i < batchHeader.quorumNumbers.length; i++) { + blobVerificationProof.quorumIndices[i] = bytes1(uint8(i)); + } + + cheats.expectRevert("EigenDABlobVerificationUtils._verifyBlobForQuorums: required quorums are not a subset of the confirmed quorums"); + eigenDABlobVerifier.verifyBlobV1(blobHeader[1], blobVerificationProof); + } + + function testVerifyBlob_QuorumNumberMismatch(uint256 pseudoRandomNumber) public { + uint256 numQuorumBlobParams = 2; + BlobHeader[] memory blobHeader = new BlobHeader[](2); + blobHeader[0] = _generateRandomBlobHeader(pseudoRandomNumber, numQuorumBlobParams); + uint256 anotherPseudoRandomNumber = uint256(keccak256(abi.encodePacked(pseudoRandomNumber))); + blobHeader[1] = _generateRandomBlobHeader(anotherPseudoRandomNumber, numQuorumBlobParams); + + BatchHeader memory batchHeader; + bytes memory firstBlobHash = abi.encodePacked(blobHeader[0].hashBlobHeader()); + bytes memory secondBlobHash = abi.encodePacked(blobHeader[1].hashBlobHeader()); + batchHeader.blobHeadersRoot = keccak256(abi.encodePacked(keccak256(firstBlobHash), keccak256(secondBlobHash))); + for (uint i = 0; i < blobHeader[1].quorumBlobParams.length; i++) { + batchHeader.quorumNumbers = abi.encodePacked(batchHeader.quorumNumbers, blobHeader[1].quorumBlobParams[i].quorumNumber); + batchHeader.signedStakeForQuorums = abi.encodePacked(batchHeader.signedStakeForQuorums, blobHeader[1].quorumBlobParams[i].confirmationThresholdPercentage); + } + batchHeader.referenceBlockNumber = uint32(block.number); + + // add dummy batch metadata + BatchMetadata memory batchMetadata; + batchMetadata.batchHeader = batchHeader; + batchMetadata.signatoryRecordHash = keccak256(abi.encodePacked("signatoryRecordHash")); + batchMetadata.confirmationBlockNumber = defaultConfirmationBlockNumber; + + stdstore + .target(address(eigenDAServiceManager)) + .sig("batchIdToBatchMetadataHash(uint32)") + .with_key(defaultBatchId) + .checked_write(batchMetadata.hashBatchMetadata()); + + BlobVerificationProof memory blobVerificationProof; + blobVerificationProof.batchId = defaultBatchId; + blobVerificationProof.batchMetadata = batchMetadata; + blobVerificationProof.inclusionProof = abi.encodePacked(keccak256(firstBlobHash)); + blobVerificationProof.blobIndex = 1; + blobVerificationProof.quorumIndices = new bytes(batchHeader.quorumNumbers.length); + for (uint i = 0; i < batchHeader.quorumNumbers.length; i++) { + // implant the incorrect quorumNumbers here + blobVerificationProof.quorumIndices[i] = bytes1(uint8(batchHeader.quorumNumbers.length - 1 - i)); + } + + cheats.expectRevert("EigenDABlobVerificationUtils._verifyBlobForQuorums: quorumNumber does not match"); + eigenDABlobVerifier.verifyBlobV1(blobHeader[1], blobVerificationProof); + } + + function testVerifyBlob_QuorumThresholdNotMet(uint256 pseudoRandomNumber) public { + uint256 numQuorumBlobParams = 2; + BlobHeader[] memory blobHeader = new BlobHeader[](2); + blobHeader[0] = _generateRandomBlobHeader(pseudoRandomNumber, numQuorumBlobParams); + uint256 anotherPseudoRandomNumber = uint256(keccak256(abi.encodePacked(pseudoRandomNumber))); + blobHeader[1] = _generateRandomBlobHeader(anotherPseudoRandomNumber, numQuorumBlobParams); + + BatchHeader memory batchHeader; + bytes memory firstBlobHash = abi.encodePacked(blobHeader[0].hashBlobHeader()); + bytes memory secondBlobHash = abi.encodePacked(blobHeader[1].hashBlobHeader()); + batchHeader.blobHeadersRoot = keccak256(abi.encodePacked(keccak256(firstBlobHash), keccak256(secondBlobHash))); + // add dummy quorum numbers and quorum threshold percentages making sure confirmationThresholdPercentage = 100 + for (uint i = 0; i < blobHeader[1].quorumBlobParams.length; i++) { + batchHeader.quorumNumbers = abi.encodePacked(batchHeader.quorumNumbers, blobHeader[1].quorumBlobParams[i].quorumNumber); + batchHeader.signedStakeForQuorums = abi.encodePacked(batchHeader.signedStakeForQuorums, blobHeader[1].quorumBlobParams[i].confirmationThresholdPercentage - 1); + } + batchHeader.referenceBlockNumber = uint32(block.number); + + // add dummy batch metadata + BatchMetadata memory batchMetadata; + batchMetadata.batchHeader = batchHeader; + batchMetadata.signatoryRecordHash = keccak256(abi.encodePacked("signatoryRecordHash")); + batchMetadata.confirmationBlockNumber = defaultConfirmationBlockNumber; + + stdstore + .target(address(eigenDAServiceManager)) + .sig("batchIdToBatchMetadataHash(uint32)") + .with_key(defaultBatchId) + .checked_write(batchMetadata.hashBatchMetadata()); + + BlobVerificationProof memory blobVerificationProof; + blobVerificationProof.batchId = defaultBatchId; + blobVerificationProof.batchMetadata = batchMetadata; + blobVerificationProof.inclusionProof = abi.encodePacked(keccak256(firstBlobHash)); + blobVerificationProof.blobIndex = 1; + blobVerificationProof.quorumIndices = new bytes(batchHeader.quorumNumbers.length); + for (uint i = 0; i < batchHeader.quorumNumbers.length; i++) { + // implant the incorrect quorumNumbers here + blobVerificationProof.quorumIndices[i] = bytes1(uint8(i)); + } + + cheats.expectRevert("EigenDABlobVerificationUtils._verifyBlobForQuorums: confirmationThresholdPercentage is not met"); + eigenDABlobVerifier.verifyBlobV1(blobHeader[1], blobVerificationProof); + } + + function testThresholds() public { + require(eigenDABlobVerifier.getQuorumAdversaryThresholdPercentage(0) == 33, "getQuorumAdversaryThresholdPercentage failed"); + require(eigenDABlobVerifier.getQuorumAdversaryThresholdPercentage(1) == 33, "getQuorumAdversaryThresholdPercentage failed"); + require(eigenDABlobVerifier.getQuorumAdversaryThresholdPercentage(2) == 33, "getQuorumAdversaryThresholdPercentage failed"); + require(eigenDABlobVerifier.getQuorumConfirmationThresholdPercentage(0) == 55, "getQuorumConfirmationThresholdPercentage failed"); + require(eigenDABlobVerifier.getQuorumConfirmationThresholdPercentage(1) == 55, "getQuorumConfirmationThresholdPercentage failed"); + require(eigenDABlobVerifier.getQuorumConfirmationThresholdPercentage(2) == 55, "getQuorumConfirmationThresholdPercentage failed"); + require(eigenDABlobVerifier.getIsQuorumRequired(0) == true, "getIsQuorumRequired failed"); + require(eigenDABlobVerifier.getIsQuorumRequired(1) == true, "getIsQuorumRequired failed"); + require(eigenDABlobVerifier.getIsQuorumRequired(2) == false, "getIsQuorumRequired failed"); + } +} \ No newline at end of file diff --git a/contracts/test/unit/EigenDABlobVerifierV2Unit.t.sol b/contracts/test/unit/EigenDABlobVerifierV2Unit.t.sol new file mode 100644 index 0000000000..510ddbe2e5 --- /dev/null +++ b/contracts/test/unit/EigenDABlobVerifierV2Unit.t.sol @@ -0,0 +1,198 @@ +// SPDX-License-Identifier: MIT +pragma solidity =0.8.12; + +import "../MockEigenDADeployer.sol"; + +contract EigenDABlobVerifierV2Unit is MockEigenDADeployer { + using stdStorage for StdStorage; + using BN254 for BN254.G1Point; + + address relay0 = address(uint160(uint256(keccak256(abi.encodePacked("relay0"))))); + address relay1 = address(uint160(uint256(keccak256(abi.encodePacked("relay1"))))); + + function setUp() virtual public { + _deployDA(); + } + + function test_verifyBlobV2(uint256 pseudoRandomNumber) public { + ( + SignedBatch memory signedBatch, + BlobVerificationProofV2 memory blobVerificationProof, + BLSSignatureChecker.NonSignerStakesAndSignature memory nssas + ) = _getSignedBatchAndBlobVerificationProof(pseudoRandomNumber, 0); + + NonSignerStakesAndSignature memory nonSignerStakesAndSignature; + nonSignerStakesAndSignature.nonSignerQuorumBitmapIndices = nssas.nonSignerQuorumBitmapIndices; + nonSignerStakesAndSignature.nonSignerPubkeys = nssas.nonSignerPubkeys; + nonSignerStakesAndSignature.quorumApks = nssas.quorumApks; + nonSignerStakesAndSignature.apkG2 = nssas.apkG2; + nonSignerStakesAndSignature.sigma = nssas.sigma; + nonSignerStakesAndSignature.quorumApkIndices = nssas.quorumApkIndices; + nonSignerStakesAndSignature.totalStakeIndices = nssas.totalStakeIndices; + nonSignerStakesAndSignature.nonSignerStakeIndices = nssas.nonSignerStakeIndices; + + _registerRelayKeys(); + + eigenDABlobVerifier.verifyBlobV2FromSignedBatch(signedBatch, blobVerificationProof); + + eigenDABlobVerifier.verifyBlobV2(signedBatch.batchHeader, blobVerificationProof, nonSignerStakesAndSignature); + + NonSignerStakesAndSignature memory _nonSignerStakesAndSignature = eigenDABlobVerifier.getNonSignerStakesAndSignature(signedBatch); + eigenDABlobVerifier.verifyBlobV2(signedBatch.batchHeader, blobVerificationProof, _nonSignerStakesAndSignature); + } + + function test_verifyBlobV2_revert_RelayKeysNotSet(uint256 pseudoRandomNumber) public { + ( + SignedBatch memory signedBatch, + BlobVerificationProofV2 memory blobVerificationProof, + BLSSignatureChecker.NonSignerStakesAndSignature memory nssas + ) = _getSignedBatchAndBlobVerificationProof(pseudoRandomNumber, 0); + + vm.expectRevert("EigenDABlobVerificationUtils._verifyRelayKeysSet: relay key is not set"); + eigenDABlobVerifier.verifyBlobV2FromSignedBatch(signedBatch, blobVerificationProof); + } + + function test_verifyBlobV2_revert_InclusionProofInvalid(uint256 pseudoRandomNumber) public { + ( + SignedBatch memory signedBatch, + BlobVerificationProofV2 memory blobVerificationProof, + BLSSignatureChecker.NonSignerStakesAndSignature memory nssas + ) = _getSignedBatchAndBlobVerificationProof(pseudoRandomNumber, 0); + + blobVerificationProof.inclusionProof = abi.encodePacked(keccak256(abi.encode(pseudoRandomNumber, "inclusion proof"))); + + vm.expectRevert("EigenDABlobVerificationUtils._verifyBlobV2ForQuorums: inclusion proof is invalid"); + eigenDABlobVerifier.verifyBlobV2FromSignedBatch(signedBatch, blobVerificationProof); + } + + function test_verifyBlobV2_revert_BadVersion(uint256 pseudoRandomNumber) public { + ( + SignedBatch memory signedBatch, + BlobVerificationProofV2 memory blobVerificationProof, + BLSSignatureChecker.NonSignerStakesAndSignature memory nssas + ) = _getSignedBatchAndBlobVerificationProof(pseudoRandomNumber, 1); + + _registerRelayKeys(); + + vm.expectRevert(); + eigenDABlobVerifier.verifyBlobV2FromSignedBatch(signedBatch, blobVerificationProof); + } + + function test_verifyBlobV2_revert_BadSecurityParams(uint256 pseudoRandomNumber) public { + ( + SignedBatch memory signedBatch, + BlobVerificationProofV2 memory blobVerificationProof, + BLSSignatureChecker.NonSignerStakesAndSignature memory nssas + ) = _getSignedBatchAndBlobVerificationProof(pseudoRandomNumber, 0); + + vm.prank(registryCoordinatorOwner); + eigenDAThresholdRegistry.updateDefaultSecurityThresholdsV2(SecurityThresholds({ + confirmationThreshold: 33, + adversaryThreshold: 55 + })); + + _registerRelayKeys(); + + vm.expectRevert("EigenDABlobVerificationUtils._verifyBlobSecurityParams: confirmationThreshold must be greater than adversaryThreshold"); + eigenDABlobVerifier.verifyBlobV2FromSignedBatch(signedBatch, blobVerificationProof); + } + + function test_verifyBlobSecurityParams() public { + VersionedBlobParams memory blobParams = eigenDAThresholdRegistry.getBlobParams(0); + SecurityThresholds memory securityThresholds = eigenDAThresholdRegistry.getDefaultSecurityThresholdsV2(); + eigenDABlobVerifier.verifyBlobSecurityParams(blobParams, securityThresholds); + eigenDABlobVerifier.verifyBlobSecurityParams(0, securityThresholds); + } + + function _getSignedBatchAndBlobVerificationProof(uint256 pseudoRandomNumber, uint8 version) internal returns (SignedBatch memory, BlobVerificationProofV2 memory, BLSSignatureChecker.NonSignerStakesAndSignature memory) { + BlobHeaderV2 memory blobHeader1 = _getRandomBlobHeaderV2(pseudoRandomNumber, version); + BlobHeaderV2 memory blobHeader2 = _getRandomBlobHeaderV2(pseudoRandomNumber, version); + + uint32[] memory relayKeys = new uint32[](2); + relayKeys[0] = 0; + relayKeys[1] = 1; + + BlobCertificate memory blobCertificate1 = BlobCertificate({ + blobHeader: blobHeader1, + relayKeys: relayKeys + }); + + BlobCertificate memory blobCertificate2 = BlobCertificate({ + blobHeader: blobHeader2, + relayKeys: relayKeys + }); + + bytes32 batchRoot = keccak256(abi.encode( + keccak256(abi.encode(EigenDAHasher.hashBlobCertificate(blobCertificate1))), + keccak256(abi.encode(EigenDAHasher.hashBlobCertificate(blobCertificate2))) + )); + + BlobVerificationProofV2 memory blobVerificationProof = BlobVerificationProofV2({ + blobCertificate: blobCertificate1, + blobIndex: 0, + inclusionProof: abi.encodePacked(keccak256(abi.encode(EigenDAHasher.hashBlobCertificate(blobCertificate2)))) + }); + + (uint32 referenceBlockNumber, BLSSignatureChecker.NonSignerStakesAndSignature memory nonSignerStakesAndSignature) = + _registerSignatoriesAndGetNonSignerStakeAndSignatureRandom(pseudoRandomNumber, 0, 1); + + BatchHeaderV2 memory batchHeader = BatchHeaderV2({ + batchRoot: batchRoot, + referenceBlockNumber: referenceBlockNumber + }); + + nonSignerStakesAndSignature.sigma = BN254.hashToG1(keccak256(abi.encode(batchHeader))).scalar_mul(aggSignerPrivKey); + + uint32[] memory quorumNumbers = new uint32[](1); + quorumNumbers[0] = 0; + + Attestation memory attestation = Attestation({ + nonSignerPubkeys: nonSignerStakesAndSignature.nonSignerPubkeys, + quorumApks: nonSignerStakesAndSignature.quorumApks, + sigma: nonSignerStakesAndSignature.sigma, + apkG2: nonSignerStakesAndSignature.apkG2, + quorumNumbers: quorumNumbers + }); + + SignedBatch memory signedBatch = SignedBatch({ + batchHeader: batchHeader, + attestation: attestation + }); + + return (signedBatch, blobVerificationProof, nonSignerStakesAndSignature); + } + + function _getRandomBlobHeaderV2(uint256 psuedoRandomNumber, uint8 version) internal view returns (BlobHeaderV2 memory) { + uint256[2] memory lengthCommitmentX = [uint256(keccak256(abi.encode(psuedoRandomNumber, "blobHeader.commitment.lengthCommitment.X"))), uint256(keccak256(abi.encode(psuedoRandomNumber, "blobHeader.commitment.lengthCommitment.X")))]; + uint256[2] memory lengthCommitmentY = [uint256(keccak256(abi.encode(psuedoRandomNumber, "blobHeader.commitment.lengthCommitment.Y"))), uint256(keccak256(abi.encode(psuedoRandomNumber, "blobHeader.commitment.lengthCommitment.Y")))]; + uint256[2] memory lengthProofX = [uint256(keccak256(abi.encode(psuedoRandomNumber, "blobHeader.commitment.lengthProof.X"))), uint256(keccak256(abi.encode(psuedoRandomNumber, "blobHeader.commitment.lengthProof.X")))]; + uint256[2] memory lengthProofY = [uint256(keccak256(abi.encode(psuedoRandomNumber, "blobHeader.commitment.lengthProof.Y"))), uint256(keccak256(abi.encode(psuedoRandomNumber, "blobHeader.commitment.lengthProof.Y")))]; + + BlobHeaderV2 memory blobHeader = BlobHeaderV2({ + version: version, + quorumNumbers: hex"00", + commitment: BlobCommitment({ + commitment: BN254.G1Point(uint256(keccak256(abi.encode(psuedoRandomNumber, "blobHeader.commitment.X"))), uint256(keccak256(abi.encode(psuedoRandomNumber, "blobHeader.commitment.Y")))), + lengthCommitment: BN254.G2Point(lengthCommitmentX, lengthCommitmentY), + lengthProof: BN254.G2Point(lengthProofX, lengthProofY), + dataLength: uint32(uint256(keccak256(abi.encode(psuedoRandomNumber, "blobHeader.dataLength")))) + }), + paymentHeaderHash: keccak256(abi.encode(psuedoRandomNumber, "blobHeader.paymentHeaderHash")) + }); + + return blobHeader; + } + + function _registerRelayKeys() internal { + vm.startPrank(registryCoordinatorOwner); + eigenDARelayRegistry.addRelayInfo(RelayInfo({ + relayAddress: relay0, + relayURL: "https://relay0.com" + })); + eigenDARelayRegistry.addRelayInfo(RelayInfo({ + relayAddress: relay1, + relayURL: "https://relay1.com" + })); + vm.stopPrank(); + } +} diff --git a/contracts/test/unit/EigenDADisperserRegistryUnit.t.sol b/contracts/test/unit/EigenDADisperserRegistryUnit.t.sol new file mode 100644 index 0000000000..00e2759044 --- /dev/null +++ b/contracts/test/unit/EigenDADisperserRegistryUnit.t.sol @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: MIT +pragma solidity =0.8.12; + +import "../MockEigenDADeployer.sol"; + +contract EigenDADisperserRegistryUnit is MockEigenDADeployer { + + event DisperserAdded(uint32 indexed key, address indexed disperser); + + function setUp() virtual public { + _deployDA(); + } + + function test_initalize() public { + assertEq(eigenDADisperserRegistry.owner(), registryCoordinatorOwner); + vm.expectRevert("Initializable: contract is already initialized"); + eigenDADisperserRegistry.initialize(address(this)); + } + + function test_setDisperserInfo() public { + uint32 disperserKey = 1; + address disperserAddress = address(uint160(uint256(keccak256(abi.encodePacked("disperser"))))); + DisperserInfo memory disperserInfo = DisperserInfo({ + disperserAddress: disperserAddress + }); + + vm.expectEmit(address(eigenDADisperserRegistry)); + emit DisperserAdded(disperserKey, disperserAddress); + vm.prank(registryCoordinatorOwner); + eigenDADisperserRegistry.setDisperserInfo(disperserKey, disperserInfo); + + assertEq(eigenDADisperserRegistry.disperserKeyToAddress(disperserKey), disperserAddress); + } + + function test_setDisperserInfo_revert_notOwner() public { + uint32 disperserKey = 1; + address disperserAddress = address(uint160(uint256(keccak256(abi.encodePacked("disperser"))))); + DisperserInfo memory disperserInfo = DisperserInfo({ + disperserAddress: disperserAddress + }); + + vm.expectRevert("Ownable: caller is not the owner"); + eigenDADisperserRegistry.setDisperserInfo(disperserKey, disperserInfo); + } +} diff --git a/contracts/test/unit/EigenDARelayRegistryUnit.t.sol b/contracts/test/unit/EigenDARelayRegistryUnit.t.sol new file mode 100644 index 0000000000..3ea0b2ee78 --- /dev/null +++ b/contracts/test/unit/EigenDARelayRegistryUnit.t.sol @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: MIT +pragma solidity =0.8.12; + +import "../MockEigenDADeployer.sol"; + +contract EigenDARelayRegistryUnit is MockEigenDADeployer { + + event RelayAdded(address indexed relay, uint32 indexed key, string relayURL); + + function setUp() virtual public { + _deployDA(); + } + + function test_initalize() public { + assertEq(eigenDARelayRegistry.owner(), registryCoordinatorOwner); + vm.expectRevert("Initializable: contract is already initialized"); + eigenDARelayRegistry.initialize(address(this)); + } + + function test_addRelayInfo() public { + RelayInfo memory relayInfo = RelayInfo({ + relayAddress: address(uint160(uint256(keccak256(abi.encodePacked("relay"))))), + relayURL: "https://relay.com" + }); + + vm.expectEmit(address(eigenDARelayRegistry)); + emit RelayAdded(relayInfo.relayAddress, eigenDARelayRegistry.nextRelayKey(), relayInfo.relayURL); + vm.prank(registryCoordinatorOwner); + eigenDARelayRegistry.addRelayInfo(relayInfo); + + assertEq(eigenDARelayRegistry.relayKeyToAddress(eigenDARelayRegistry.nextRelayKey() - 1), relayInfo.relayAddress); + assertEq(eigenDARelayRegistry.relayKeyToUrl(eigenDARelayRegistry.nextRelayKey() - 1), relayInfo.relayURL); + } + + function test_addRelayInfo_revert_notOwner() public { + RelayInfo memory relayInfo = RelayInfo({ + relayAddress: address(uint160(uint256(keccak256(abi.encodePacked("relay"))))), + relayURL: "https://relay.com" + }); + + vm.expectRevert("Ownable: caller is not the owner"); + eigenDARelayRegistry.addRelayInfo(relayInfo); + } +} diff --git a/contracts/test/unit/EigenDAServiceManagerUnit.t.sol b/contracts/test/unit/EigenDAServiceManagerUnit.t.sol index 49ef10d0fb..443e30f21e 100644 --- a/contracts/test/unit/EigenDAServiceManagerUnit.t.sol +++ b/contracts/test/unit/EigenDAServiceManagerUnit.t.sol @@ -1,65 +1,21 @@ -// SPDX-License-Identifier: BUSL-1.1 +// SPDX-License-Identifier: MIT pragma solidity =0.8.12; -import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import "../MockEigenDADeployer.sol"; -import "../../lib/eigenlayer-middleware/test/utils/BLSMockAVSDeployer.sol"; -import {EigenDAServiceManager} from "../../src/core/EigenDAServiceManager.sol"; -import {EigenDAHasher} from "../../src/libraries/EigenDAHasher.sol"; -import {EigenDAServiceManager} from "../../src/core/EigenDAServiceManager.sol"; -import {IEigenDAServiceManager} from "../../src/interfaces/IEigenDAServiceManager.sol"; - - -contract EigenDAServiceManagerUnit is BLSMockAVSDeployer { +contract EigenDAServiceManagerUnit is MockEigenDADeployer { using BN254 for BN254.G1Point; - using EigenDAHasher for IEigenDAServiceManager.BatchHeader; - using EigenDAHasher for IEigenDAServiceManager.ReducedBatchHeader; - - address confirmer = address(uint160(uint256(keccak256(abi.encodePacked("confirmer"))))); - address notConfirmer = address(uint160(uint256(keccak256(abi.encodePacked("notConfirmer"))))); - address newFeeSetter = address(uint160(uint256(keccak256(abi.encodePacked("newFeeSetter"))))); - - EigenDAServiceManager eigenDAServiceManager; - EigenDAServiceManager eigenDAServiceManagerImplementation; - - uint256 feePerBytePerTime = 0; + using EigenDAHasher for BatchHeader; + using EigenDAHasher for ReducedBatchHeader; event BatchConfirmed(bytes32 indexed batchHeaderHash, uint32 batchId); - event FeePerBytePerTimeSet(uint256 previousValue, uint256 newValue); - event FeeSetterChanged(address previousAddress, address newAddress); function setUp() virtual public { - _setUpBLSMockAVSDeployer(); - - eigenDAServiceManagerImplementation = new EigenDAServiceManager( - avsDirectory, - registryCoordinator, - stakeRegistry - ); - - address[] memory confirmers = new address[](1); - confirmers[0] = confirmer; - - // Third, upgrade the proxy contracts to use the correct implementation contracts and initialize them. - eigenDAServiceManager = EigenDAServiceManager( - address( - new TransparentUpgradeableProxy( - address(eigenDAServiceManagerImplementation), - address(proxyAdmin), - abi.encodeWithSelector( - EigenDAServiceManager.initialize.selector, - pauserRegistry, - 0, - registryCoordinatorOwner, - confirmers - ) - ) - ) - ); + _deployDA(); } function testConfirmBatch_AllSigning_Valid(uint256 pseudoRandomNumber) public { - (IEigenDAServiceManager.BatchHeader memory batchHeader, BLSSignatureChecker.NonSignerStakesAndSignature memory nonSignerStakesAndSignature) + (BatchHeader memory batchHeader, BLSSignatureChecker.NonSignerStakesAndSignature memory nonSignerStakesAndSignature) = _getHeaderandNonSigners(0, pseudoRandomNumber, 100); uint32 batchIdToConfirm = eigenDAServiceManager.batchId(); @@ -80,10 +36,10 @@ contract EigenDAServiceManagerUnit is BLSMockAVSDeployer { } function testConfirmBatch_Revert_NotEOA(uint256 pseudoRandomNumber) public { - (IEigenDAServiceManager.BatchHeader memory batchHeader, BLSSignatureChecker.NonSignerStakesAndSignature memory nonSignerStakesAndSignature) + (BatchHeader memory batchHeader, BLSSignatureChecker.NonSignerStakesAndSignature memory nonSignerStakesAndSignature) = _getHeaderandNonSigners(0, pseudoRandomNumber, 100); - cheats.expectRevert(bytes("EigenDAServiceManager.confirmBatch: header and nonsigner data must be in calldata")); + cheats.expectRevert(bytes("header and nonsigner data must be in calldata")); cheats.prank(confirmer, notConfirmer); eigenDAServiceManager.confirmBatch( batchHeader, @@ -92,10 +48,10 @@ contract EigenDAServiceManagerUnit is BLSMockAVSDeployer { } function testConfirmBatch_Revert_NotConfirmer(uint256 pseudoRandomNumber) public { - (IEigenDAServiceManager.BatchHeader memory batchHeader, BLSSignatureChecker.NonSignerStakesAndSignature memory nonSignerStakesAndSignature) + (BatchHeader memory batchHeader, BLSSignatureChecker.NonSignerStakesAndSignature memory nonSignerStakesAndSignature) = _getHeaderandNonSigners(0, pseudoRandomNumber, 100); - cheats.expectRevert(bytes("onlyBatchConfirmer: not from batch confirmer")); + cheats.expectRevert(); cheats.prank(notConfirmer, notConfirmer); eigenDAServiceManager.confirmBatch( batchHeader, @@ -104,20 +60,19 @@ contract EigenDAServiceManagerUnit is BLSMockAVSDeployer { } function testConfirmBatch_Revert_FutureBlocknumber(uint256 pseudoRandomNumber) public { - uint256 quorumBitmap = 1; bytes memory quorumNumbers = BitmapUtils.bitmapToBytesArray(quorumBitmap); (, BLSSignatureChecker.NonSignerStakesAndSignature memory nonSignerStakesAndSignature) = _registerSignatoriesAndGetNonSignerStakeAndSignatureRandom(pseudoRandomNumber, 0, quorumBitmap); - IEigenDAServiceManager.BatchHeader memory batchHeader = + BatchHeader memory batchHeader = _getRandomBatchHeader(pseudoRandomNumber, quorumNumbers, uint32(block.number + 1), 100); bytes32 batchHeaderHash = batchHeader.hashBatchHeaderMemory(); nonSignerStakesAndSignature.sigma = BN254.hashToG1(batchHeaderHash).scalar_mul(aggSignerPrivKey); - cheats.expectRevert(bytes("EigenDAServiceManager.confirmBatch: specified referenceBlockNumber is in future")); + cheats.expectRevert(bytes("specified referenceBlockNumber is in future")); cheats.prank(confirmer, confirmer); eigenDAServiceManager.confirmBatch( batchHeader, @@ -126,11 +81,11 @@ contract EigenDAServiceManagerUnit is BLSMockAVSDeployer { } function testConfirmBatch_Revert_PastBlocknumber(uint256 pseudoRandomNumber) public { - (IEigenDAServiceManager.BatchHeader memory batchHeader, BLSSignatureChecker.NonSignerStakesAndSignature memory nonSignerStakesAndSignature) + (BatchHeader memory batchHeader, BLSSignatureChecker.NonSignerStakesAndSignature memory nonSignerStakesAndSignature) = _getHeaderandNonSigners(0, pseudoRandomNumber, 100); cheats.roll(block.number + eigenDAServiceManager.BLOCK_STALE_MEASURE()); - cheats.expectRevert(bytes("EigenDAServiceManager.confirmBatch: specified referenceBlockNumber is too far in past")); + cheats.expectRevert(bytes("specified referenceBlockNumber is too far in past")); cheats.prank(confirmer, confirmer); eigenDAServiceManager.confirmBatch( batchHeader, @@ -139,10 +94,10 @@ contract EigenDAServiceManagerUnit is BLSMockAVSDeployer { } function testConfirmBatch_Revert_Threshold(uint256 pseudoRandomNumber) public { - (IEigenDAServiceManager.BatchHeader memory batchHeader, BLSSignatureChecker.NonSignerStakesAndSignature memory nonSignerStakesAndSignature) + (BatchHeader memory batchHeader, BLSSignatureChecker.NonSignerStakesAndSignature memory nonSignerStakesAndSignature) = _getHeaderandNonSigners(1, pseudoRandomNumber, 100); - cheats.expectRevert(bytes("EigenDAServiceManager.confirmBatch: signatories do not own at least threshold percentage of a quorum")); + cheats.expectRevert(bytes("signatories do not own threshold percentage of a quorum")); cheats.prank(confirmer, confirmer); eigenDAServiceManager.confirmBatch( batchHeader, @@ -151,7 +106,7 @@ contract EigenDAServiceManagerUnit is BLSMockAVSDeployer { } function testConfirmBatch_NonSigner_Valid(uint256 pseudoRandomNumber) public { - (IEigenDAServiceManager.BatchHeader memory batchHeader, BLSSignatureChecker.NonSignerStakesAndSignature memory nonSignerStakesAndSignature) + (BatchHeader memory batchHeader, BLSSignatureChecker.NonSignerStakesAndSignature memory nonSignerStakesAndSignature) = _getHeaderandNonSigners(1, pseudoRandomNumber, 75); uint32 batchIdToConfirm = eigenDAServiceManager.batchId(); @@ -172,46 +127,15 @@ contract EigenDAServiceManagerUnit is BLSMockAVSDeployer { } function testConfirmBatch_Revert_LengthMismatch(uint256 pseudoRandomNumber) public { - (IEigenDAServiceManager.BatchHeader memory batchHeader, BLSSignatureChecker.NonSignerStakesAndSignature memory nonSignerStakesAndSignature) + (BatchHeader memory batchHeader, BLSSignatureChecker.NonSignerStakesAndSignature memory nonSignerStakesAndSignature) = _getHeaderandNonSigners(0, pseudoRandomNumber, 100); batchHeader.signedStakeForQuorums = new bytes(0); - cheats.expectRevert(bytes("EigenDAServiceManager.confirmBatch: quorumNumbers and signedStakeForQuorums must be of the same length")); + cheats.expectRevert(bytes("quorumNumbers and signedStakeForQuorums must be same length")); cheats.prank(confirmer, confirmer); eigenDAServiceManager.confirmBatch( batchHeader, nonSignerStakesAndSignature ); } - - function _getHeaderandNonSigners(uint256 _nonSigners, uint256 _pseudoRandomNumber, uint8 _threshold) internal returns (IEigenDAServiceManager.BatchHeader memory, BLSSignatureChecker.NonSignerStakesAndSignature memory) { - // register a bunch of operators - uint256 quorumBitmap = 1; - bytes memory quorumNumbers = BitmapUtils.bitmapToBytesArray(quorumBitmap); - - // 0 nonSigners - (uint32 referenceBlockNumber, BLSSignatureChecker.NonSignerStakesAndSignature memory nonSignerStakesAndSignature) = - _registerSignatoriesAndGetNonSignerStakeAndSignatureRandom(_pseudoRandomNumber, _nonSigners, quorumBitmap); - - // get a random batch header - IEigenDAServiceManager.BatchHeader memory batchHeader = _getRandomBatchHeader(_pseudoRandomNumber, quorumNumbers, referenceBlockNumber, _threshold); - - // set batch specific signature - bytes32 reducedBatchHeaderHash = batchHeader.hashBatchHeaderToReducedBatchHeader(); - nonSignerStakesAndSignature.sigma = BN254.hashToG1(reducedBatchHeaderHash).scalar_mul(aggSignerPrivKey); - - return (batchHeader, nonSignerStakesAndSignature); - } - - function _getRandomBatchHeader(uint256 pseudoRandomNumber, bytes memory quorumNumbers, uint32 referenceBlockNumber, uint8 threshold) internal pure returns(IEigenDAServiceManager.BatchHeader memory) { - IEigenDAServiceManager.BatchHeader memory batchHeader; - batchHeader.blobHeadersRoot = keccak256(abi.encodePacked("blobHeadersRoot", pseudoRandomNumber)); - batchHeader.quorumNumbers = quorumNumbers; - batchHeader.signedStakeForQuorums = new bytes(quorumNumbers.length); - for (uint256 i = 0; i < quorumNumbers.length; i++) { - batchHeader.signedStakeForQuorums[i] = bytes1(threshold); - } - batchHeader.referenceBlockNumber = referenceBlockNumber; - return batchHeader; - } } \ No newline at end of file diff --git a/contracts/test/unit/EigenDAThresholdRegistryUnit.t.sol b/contracts/test/unit/EigenDAThresholdRegistryUnit.t.sol new file mode 100644 index 0000000000..c372f7531e --- /dev/null +++ b/contracts/test/unit/EigenDAThresholdRegistryUnit.t.sol @@ -0,0 +1,171 @@ +// SPDX-License-Identifier: MIT +pragma solidity =0.8.12; + +import "../MockEigenDADeployer.sol"; + +contract EigenDAThresholdRegistryUnit is MockEigenDADeployer { + + event VersionedBlobParamsAdded(uint16 indexed version, VersionedBlobParams versionedBlobParams); + event QuorumAdversaryThresholdPercentagesUpdated(bytes previousQuorumAdversaryThresholdPercentages, bytes newQuorumAdversaryThresholdPercentages); + event QuorumConfirmationThresholdPercentagesUpdated(bytes previousQuorumConfirmationThresholdPercentages, bytes newQuorumConfirmationThresholdPercentages); + event QuorumNumbersRequiredUpdated(bytes previousQuorumNumbersRequired, bytes newQuorumNumbersRequired); + event DefaultSecurityThresholdsV2Updated(SecurityThresholds previousDefaultSecurityThresholdsV2, SecurityThresholds newDefaultSecurityThresholdsV2); + + function setUp() virtual public { + _deployDA(); + } + + function test_initalize() public { + VersionedBlobParams memory _versionedBlobParams = VersionedBlobParams({ + maxNumOperators: 3537, + numChunks: 8192, + codingRate: 8 + }); + + assertEq(eigenDAThresholdRegistry.owner(), registryCoordinatorOwner); + assertEq(keccak256(abi.encode(eigenDAThresholdRegistry.quorumAdversaryThresholdPercentages())), keccak256(abi.encode(quorumAdversaryThresholdPercentages))); + assertEq(keccak256(abi.encode(eigenDAThresholdRegistry.quorumConfirmationThresholdPercentages())), keccak256(abi.encode(quorumConfirmationThresholdPercentages))); + assertEq(keccak256(abi.encode(eigenDAThresholdRegistry.quorumNumbersRequired())), keccak256(abi.encode(quorumNumbersRequired))); + (uint8 confirmationThreshold, uint8 adversaryThreshold) = eigenDAThresholdRegistry.defaultSecurityThresholdsV2(); + assertEq(adversaryThreshold, defaultSecurityThresholds.adversaryThreshold); + assertEq(confirmationThreshold, defaultSecurityThresholds.confirmationThreshold); + (uint32 maxNumOperators, uint32 numChunks, uint8 codingRate) = eigenDAThresholdRegistry.versionedBlobParams(0); + assertEq(maxNumOperators, _versionedBlobParams.maxNumOperators); + assertEq(numChunks, _versionedBlobParams.numChunks); + assertEq(codingRate, _versionedBlobParams.codingRate); + + VersionedBlobParams[] memory versionedBlobParams = new VersionedBlobParams[](1); + versionedBlobParams[0] = _versionedBlobParams; + vm.expectRevert("Initializable: contract is already initialized"); + eigenDAThresholdRegistry.initialize( + registryCoordinatorOwner, + quorumAdversaryThresholdPercentages, + quorumConfirmationThresholdPercentages, + quorumNumbersRequired, + versionedBlobParams, + defaultSecurityThresholds + ); + } + + function test_updateQuorumAdversaryThresholdPercentages() public { + bytes memory _quorumAdversaryThresholdPercentages = hex"AABBCC"; + vm.expectEmit(address(eigenDAThresholdRegistry)); + emit QuorumAdversaryThresholdPercentagesUpdated(quorumAdversaryThresholdPercentages, _quorumAdversaryThresholdPercentages); + vm.prank(registryCoordinatorOwner); + eigenDAThresholdRegistry.updateQuorumAdversaryThresholdPercentages(_quorumAdversaryThresholdPercentages); + assertEq(keccak256(abi.encode(eigenDAThresholdRegistry.quorumAdversaryThresholdPercentages())), keccak256(abi.encode(_quorumAdversaryThresholdPercentages))); + } + + function test_updateQuorumConfirmationThresholdPercentages() public { + bytes memory _quorumConfirmationThresholdPercentages = hex"AABBCC"; + vm.expectEmit(address(eigenDAThresholdRegistry)); + emit QuorumConfirmationThresholdPercentagesUpdated(quorumConfirmationThresholdPercentages, _quorumConfirmationThresholdPercentages); + vm.prank(registryCoordinatorOwner); + eigenDAThresholdRegistry.updateQuorumConfirmationThresholdPercentages(_quorumConfirmationThresholdPercentages); + assertEq(keccak256(abi.encode(eigenDAThresholdRegistry.quorumConfirmationThresholdPercentages())), keccak256(abi.encode(_quorumConfirmationThresholdPercentages))); + } + + function test_updateQuorumNumbersRequired() public { + bytes memory _quorumNumbersRequired = hex"AABBCC"; + vm.expectEmit(address(eigenDAThresholdRegistry)); + emit QuorumNumbersRequiredUpdated(quorumNumbersRequired, _quorumNumbersRequired); + vm.prank(registryCoordinatorOwner); + eigenDAThresholdRegistry.updateQuorumNumbersRequired(_quorumNumbersRequired); + assertEq(keccak256(abi.encode(eigenDAThresholdRegistry.quorumNumbersRequired())), keccak256(abi.encode(_quorumNumbersRequired))); + } + + function test_updateDefaultSecurityThresholdsV2() public { + SecurityThresholds memory _defaultSecurityThresholds = SecurityThresholds({ + adversaryThreshold: 10, + confirmationThreshold: 20 + }); + vm.expectEmit(address(eigenDAThresholdRegistry)); + emit DefaultSecurityThresholdsV2Updated(defaultSecurityThresholds, _defaultSecurityThresholds); + vm.prank(registryCoordinatorOwner); + eigenDAThresholdRegistry.updateDefaultSecurityThresholdsV2(_defaultSecurityThresholds); + (uint8 confirmationThreshold, uint8 adversaryThreshold) = eigenDAThresholdRegistry.defaultSecurityThresholdsV2(); + assertEq(adversaryThreshold, _defaultSecurityThresholds.adversaryThreshold); + assertEq(confirmationThreshold, _defaultSecurityThresholds.confirmationThreshold); + } + + function test_addVersionedBlobParams() public { + VersionedBlobParams memory _versionedBlobParams = VersionedBlobParams({ + maxNumOperators: 999, + numChunks: 999, + codingRate: 9 + }); + vm.expectEmit(address(eigenDAThresholdRegistry)); + emit VersionedBlobParamsAdded(1, _versionedBlobParams); + vm.prank(registryCoordinatorOwner); + uint16 version = eigenDAThresholdRegistry.addVersionedBlobParams(_versionedBlobParams); + assertEq(version, 1); + (uint32 maxNumOperators, uint32 numChunks, uint8 codingRate) = eigenDAThresholdRegistry.versionedBlobParams(version); + assertEq(maxNumOperators, _versionedBlobParams.maxNumOperators); + assertEq(numChunks, _versionedBlobParams.numChunks); + assertEq(codingRate, _versionedBlobParams.codingRate); + } + + function test_revert_onlyOwner() public { + vm.expectRevert("Ownable: caller is not the owner"); + eigenDAThresholdRegistry.updateQuorumAdversaryThresholdPercentages(hex"AABBCC"); + vm.expectRevert("Ownable: caller is not the owner"); + eigenDAThresholdRegistry.updateQuorumConfirmationThresholdPercentages(hex"AABBCC"); + vm.expectRevert("Ownable: caller is not the owner"); + eigenDAThresholdRegistry.updateQuorumNumbersRequired(hex"AABBCC"); + vm.expectRevert("Ownable: caller is not the owner"); + eigenDAThresholdRegistry.updateDefaultSecurityThresholdsV2(SecurityThresholds({ + adversaryThreshold: 10, + confirmationThreshold: 20 + })); + vm.expectRevert("Ownable: caller is not the owner"); + eigenDAThresholdRegistry.addVersionedBlobParams(VersionedBlobParams({ + maxNumOperators: 999, + numChunks: 999, + codingRate: 9 + })); + } + + function test_getQuorumAdversaryThresholdPercentage() public { + uint8 quorumNumber = 1; + uint8 adversaryThresholdPercentage = eigenDAThresholdRegistry.getQuorumAdversaryThresholdPercentage(quorumNumber); + assertEq(adversaryThresholdPercentage, uint8(quorumAdversaryThresholdPercentages[quorumNumber])); + } + + function test_getQuorumConfirmationThresholdPercentage() public { + uint8 quorumNumber = 1; + uint8 confirmationThresholdPercentage = eigenDAThresholdRegistry.getQuorumConfirmationThresholdPercentage(quorumNumber); + assertEq(confirmationThresholdPercentage, uint8(quorumConfirmationThresholdPercentages[quorumNumber])); + } + + function test_getIsQuorumRequired() public { + uint8 quorumNumber = 0; + bool isQuorumRequired = eigenDAThresholdRegistry.getIsQuorumRequired(quorumNumber); + assertEq(isQuorumRequired, true); + quorumNumber = 1; + isQuorumRequired = eigenDAThresholdRegistry.getIsQuorumRequired(quorumNumber); + assertEq(isQuorumRequired, true); + quorumNumber = 2; + isQuorumRequired = eigenDAThresholdRegistry.getIsQuorumRequired(quorumNumber); + assertEq(isQuorumRequired, false); + } + + function test_getDefaultSecurityThresholdsV2() public { + SecurityThresholds memory defaultSecurityThresholds = eigenDAThresholdRegistry.getDefaultSecurityThresholdsV2(); + assertEq(defaultSecurityThresholds.adversaryThreshold, defaultSecurityThresholds.adversaryThreshold); + assertEq(defaultSecurityThresholds.confirmationThreshold, defaultSecurityThresholds.confirmationThreshold); + } + + function test_getBlobParams() public { + VersionedBlobParams memory _versionedBlobParams = VersionedBlobParams({ + maxNumOperators: 999, + numChunks: 999, + codingRate: 9 + }); + vm.prank(registryCoordinatorOwner); + uint16 version = eigenDAThresholdRegistry.addVersionedBlobParams(_versionedBlobParams); + VersionedBlobParams memory blobParams = eigenDAThresholdRegistry.getBlobParams(version); + assertEq(blobParams.maxNumOperators, _versionedBlobParams.maxNumOperators); + assertEq(blobParams.numChunks, _versionedBlobParams.numChunks); + assertEq(blobParams.codingRate, _versionedBlobParams.codingRate); + } +} \ No newline at end of file diff --git a/contracts/test/unit/PaymentVaultUnit.t.sol b/contracts/test/unit/PaymentVaultUnit.t.sol new file mode 100644 index 0000000000..0019898c1b --- /dev/null +++ b/contracts/test/unit/PaymentVaultUnit.t.sol @@ -0,0 +1,307 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity =0.8.12; + +import "../MockEigenDADeployer.sol"; + +contract PaymentVaultUnit is MockEigenDADeployer { + using stdStorage for StdStorage; + + event ReservationUpdated(address indexed account, IPaymentVault.Reservation reservation); + event OnDemandPaymentUpdated(address indexed account, uint80 onDemandPayment, uint80 totalDeposit); + event GlobalSymbolsPerPeriodUpdated(uint64 previousValue, uint64 newValue); + event ReservationPeriodIntervalUpdated(uint64 previousValue, uint64 newValue); + event GlobalRatePeriodIntervalUpdated(uint64 previousValue, uint64 newValue); + event PriceParamsUpdated( + uint64 previousMinNumSymbols, + uint64 newMinNumSymbols, + uint64 previousPricePerSymbol, + uint64 newPricePerSymbol, + uint64 previousPriceUpdateCooldown, + uint64 newPriceUpdateCooldown + ); + + address user = address(uint160(uint256(keccak256(abi.encodePacked("user"))))); + address user2 = address(uint160(uint256(keccak256(abi.encodePacked("user2"))))); + + bytes quorumNumbers = hex"0001"; + bytes quorumSplits = hex"3232"; + + function setUp() virtual public { + _deployDA(); + } + + function test_initialize() public { + assertEq(paymentVault.owner(), registryCoordinatorOwner); + assertEq(paymentVault.minNumSymbols(), minNumSymbols); + assertEq(paymentVault.globalSymbolsPerPeriod(), globalSymbolsPerPeriod); + assertEq(paymentVault.pricePerSymbol(), pricePerSymbol); + assertEq(paymentVault.reservationPeriodInterval(), reservationPeriodInterval); + assertEq(paymentVault.priceUpdateCooldown(), priceUpdateCooldown); + assertEq(paymentVault.globalRatePeriodInterval(), globalRatePeriodInterval); + + vm.expectRevert("Initializable: contract is already initialized"); + paymentVault.initialize(address(0), 0, 0, 0, 0, 0, 0); + } + + function test_setReservation(uint56 _seed) public { + uint64 _symbolsPerSecond = uint64(_seed); + uint64 _startTimestamp = uint64(_seed) + 1; + uint64 _endTimestamp = uint64(_seed) + 2; + + address _account = address(uint160(_seed)); + + IPaymentVault.Reservation memory reservation = IPaymentVault.Reservation({ + symbolsPerSecond: _symbolsPerSecond, + startTimestamp: _startTimestamp, + endTimestamp: _endTimestamp, + quorumNumbers: quorumNumbers, + quorumSplits: quorumSplits + }); + + vm.expectEmit(address(paymentVault)); + emit ReservationUpdated(_account, reservation); + vm.prank(registryCoordinatorOwner); + paymentVault.setReservation(_account, reservation); + + assertEq(keccak256(abi.encode(paymentVault.getReservation(_account))), keccak256(abi.encode(reservation))); + } + + function test_setReservation_revertInvalidQuorumSplits() public { + IPaymentVault.Reservation memory reservation = IPaymentVault.Reservation({ + symbolsPerSecond: 100, + startTimestamp: 101, + endTimestamp: 102, + quorumNumbers: hex"0001", + quorumSplits: hex"3233" + }); + + vm.expectRevert("sum of quorumSplits must be 100"); + vm.prank(registryCoordinatorOwner); + paymentVault.setReservation(user, reservation); + + reservation = IPaymentVault.Reservation({ + symbolsPerSecond: 100, + startTimestamp: 101, + endTimestamp: 102, + quorumNumbers: hex"0001", + quorumSplits: hex"3231" + }); + + vm.expectRevert("sum of quorumSplits must be 100"); + vm.prank(registryCoordinatorOwner); + paymentVault.setReservation(user, reservation); + + reservation = IPaymentVault.Reservation({ + symbolsPerSecond: 100, + startTimestamp: 101, + endTimestamp: 102, + quorumNumbers: hex"0001", + quorumSplits: hex"323334" + }); + + vm.expectRevert("arrays must have the same length"); + vm.prank(registryCoordinatorOwner); + paymentVault.setReservation(user, reservation); + } + + function test_setReservation_revertInvalidTimestamps() public { + IPaymentVault.Reservation memory reservation = IPaymentVault.Reservation({ + symbolsPerSecond: 100, + startTimestamp: 101, + endTimestamp: 100, + quorumNumbers: quorumNumbers, + quorumSplits: quorumSplits + }); + + vm.expectRevert("end timestamp must be greater than start timestamp"); + vm.prank(registryCoordinatorOwner); + paymentVault.setReservation(user, reservation); + } + + function test_depositOnDemand() public { + vm.deal(user, 200 ether); + + vm.expectEmit(address(paymentVault)); + emit OnDemandPaymentUpdated(user, 100 ether, 100 ether); + vm.prank(user); + paymentVault.depositOnDemand{value: 100 ether}(user); + assertEq(paymentVault.getOnDemandTotalDeposit(user), 100 ether); + + vm.expectEmit(address(paymentVault)); + emit OnDemandPaymentUpdated(user, 100 ether, 200 ether); + vm.prank(user); + paymentVault.depositOnDemand{value: 100 ether}(user); + assertEq(paymentVault.getOnDemandTotalDeposit(user), 200 ether); + } + + function test_depositOnDemand_forOtherUser() public { + vm.deal(user, 100 ether); + address otherUser = address(uint160(420)); + + vm.expectEmit(address(paymentVault)); + emit OnDemandPaymentUpdated(user2, 100 ether, 100 ether); + vm.prank(user); + paymentVault.depositOnDemand{value: 100 ether}(user2); + assertEq(paymentVault.getOnDemandTotalDeposit(user2), 100 ether); + assertEq(paymentVault.getOnDemandTotalDeposit(user), 0); + } + + function test_depositOnDemand_fallback() public { + vm.deal(user, 100 ether); + + vm.expectEmit(address(paymentVault)); + emit OnDemandPaymentUpdated(user, 100 ether, 100 ether); + vm.prank(user); + payable(paymentVault).call{value: 100 ether}(hex"69"); + assertEq(paymentVault.getOnDemandTotalDeposit(user), 100 ether); + } + + function test_depositOnDemand_recieve() public { + vm.deal(user, 100 ether); + + vm.expectEmit(address(paymentVault)); + emit OnDemandPaymentUpdated(user, 100 ether, 100 ether); + vm.prank(user); + payable(paymentVault).call{value: 100 ether}(""); + assertEq(paymentVault.getOnDemandTotalDeposit(user), 100 ether); + } + + function test_depositOnDemand_revertUint80Overflow() public { + vm.deal(user, uint256(type(uint80).max) + 1); + vm.expectRevert("amount must be less than or equal to 80 bits"); + vm.prank(user); + paymentVault.depositOnDemand{value: uint256(type(uint80).max) + 1}(user); + } + + function test_setPriceParams() public { + vm.warp(block.timestamp + priceUpdateCooldown); + + vm.expectEmit(address(paymentVault)); + emit PriceParamsUpdated(minNumSymbols, minNumSymbols + 1, pricePerSymbol, pricePerSymbol + 1, priceUpdateCooldown, priceUpdateCooldown + 1); + vm.prank(registryCoordinatorOwner); + paymentVault.setPriceParams(minNumSymbols + 1, pricePerSymbol + 1, priceUpdateCooldown + 1); + + assertEq(paymentVault.minNumSymbols(), minNumSymbols + 1); + assertEq(paymentVault.pricePerSymbol(), pricePerSymbol + 1); + assertEq(paymentVault.priceUpdateCooldown(), priceUpdateCooldown + 1); + assertEq(paymentVault.lastPriceUpdateTime(), block.timestamp); + } + + function test_setPriceParams_revertCooldownNotSurpassed() public { + vm.warp(block.timestamp + priceUpdateCooldown - 1); + + vm.expectRevert("price update cooldown not surpassed"); + vm.prank(registryCoordinatorOwner); + paymentVault.setPriceParams(minNumSymbols + 1, pricePerSymbol + 1, priceUpdateCooldown + 1); + } + + function test_setGlobalRatePeriodInterval() public { + vm.expectEmit(address(paymentVault)); + emit GlobalRatePeriodIntervalUpdated(globalRatePeriodInterval, globalRatePeriodInterval + 1); + vm.prank(registryCoordinatorOwner); + paymentVault.setGlobalRatePeriodInterval(globalRatePeriodInterval + 1); + assertEq(paymentVault.globalRatePeriodInterval(), globalRatePeriodInterval + 1); + } + + function test_setGlobalSymbolsPerPeriod() public { + vm.expectEmit(address(paymentVault)); + emit GlobalSymbolsPerPeriodUpdated(globalSymbolsPerPeriod, globalSymbolsPerPeriod + 1); + vm.prank(registryCoordinatorOwner); + paymentVault.setGlobalSymbolsPerPeriod(globalSymbolsPerPeriod + 1); + assertEq(paymentVault.globalSymbolsPerPeriod(), globalSymbolsPerPeriod + 1); + } + + function test_setReservationPeriodInterval() public { + vm.expectEmit(address(paymentVault)); + emit ReservationPeriodIntervalUpdated(reservationPeriodInterval, reservationPeriodInterval + 1); + vm.prank(registryCoordinatorOwner); + paymentVault.setReservationPeriodInterval(reservationPeriodInterval + 1); + assertEq(paymentVault.reservationPeriodInterval(), reservationPeriodInterval + 1); + } + + function test_withdraw() public { + test_depositOnDemand(); + vm.prank(registryCoordinatorOwner); + paymentVault.withdraw(100 ether); + assertEq(address(paymentVault).balance, 100 ether); + } + + function test_withdrawERC20() public { + deal(address(mockToken), address(paymentVault), 100 ether); + vm.prank(registryCoordinatorOwner); + paymentVault.withdrawERC20(address(mockToken), 100 ether); + assertEq(mockToken.balanceOf(address(registryCoordinatorOwner)), 100 ether); + } + + function test_ownedFunctions() public { + IPaymentVault.Reservation memory reservation = IPaymentVault.Reservation({ + symbolsPerSecond: 100, + startTimestamp: 101, + endTimestamp: 102, + quorumNumbers: quorumNumbers, + quorumSplits: quorumSplits + }); + + vm.expectRevert("Ownable: caller is not the owner"); + paymentVault.setReservation(user, reservation); + vm.expectRevert("Ownable: caller is not the owner"); + paymentVault.withdraw(100 ether); + vm.expectRevert("Ownable: caller is not the owner"); + paymentVault.withdrawERC20(address(mockToken), 100 ether); + vm.expectRevert("Ownable: caller is not the owner"); + paymentVault.setPriceParams(minNumSymbols + 1, pricePerSymbol + 1, priceUpdateCooldown + 1); + vm.expectRevert("Ownable: caller is not the owner"); + paymentVault.setGlobalRatePeriodInterval(globalRatePeriodInterval + 1); + vm.expectRevert("Ownable: caller is not the owner"); + paymentVault.setGlobalSymbolsPerPeriod(globalSymbolsPerPeriod + 1); + vm.expectRevert("Ownable: caller is not the owner"); + paymentVault.setReservationPeriodInterval(reservationPeriodInterval + 1); + } + + function test_getReservations() public { + IPaymentVault.Reservation memory reservation = IPaymentVault.Reservation({ + symbolsPerSecond: 100, + startTimestamp: 101, + endTimestamp: 102, + quorumNumbers: quorumNumbers, + quorumSplits: quorumSplits + }); + + IPaymentVault.Reservation memory reservation2 = IPaymentVault.Reservation({ + symbolsPerSecond: 200, + startTimestamp: 201, + endTimestamp: 202, + quorumNumbers: hex"0203", + quorumSplits: hex"0163" + }); + + vm.startPrank(registryCoordinatorOwner); + paymentVault.setReservation(user, reservation); + paymentVault.setReservation(user2, reservation2); + vm.stopPrank(); + + address[] memory accounts = new address[](2); + accounts[0] = user; + accounts[1] = user2; + IPaymentVault.Reservation[] memory reservations = paymentVault.getReservations(accounts); + assertEq(keccak256(abi.encode(reservations[0])), keccak256(abi.encode(reservation))); + assertEq(keccak256(abi.encode(reservations[1])), keccak256(abi.encode(reservation2))); + } + + function test_getOnDemandAmounts() public { + vm.deal(user, 300 ether); + + vm.startPrank(user); + paymentVault.depositOnDemand{value: 100 ether}(user); + paymentVault.depositOnDemand{value: 200 ether}(user2); + vm.stopPrank(); + + address[] memory accounts = new address[](2); + accounts[0] = user; + accounts[1] = user2; + + uint80[] memory payments = paymentVault.getOnDemandTotalDeposits(accounts); + assertEq(payments[0], 100 ether); + assertEq(payments[1], 200 ether); + } +} \ No newline at end of file