diff --git a/.env.example b/.env.example index d5ff52b7..b8eeccd7 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,13 @@ -PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 +AGGREGATOR_PUBLIC_KEY=0x68d30f47F19c07bCCEf4Ac7FAE2Dc12FCa3e0dC9 +AGGREGATOR_RPC_LISTEN_SERVER=localhost:8080 +AGGREGATOR_PRIVATE_KEY=59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d + +DEPLOYER_PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 + HOLESKY_PRIVATE_KEY= HOLESKY_RPC_URL= + + +HELLOWORLD_OPERATORS_ECDSA_MNEMONIC="test test test test test test test test test test test junk" + + diff --git a/contracts/.env.example b/contracts/.env.example index ada4ad43..eaaa9ab2 100644 --- a/contracts/.env.example +++ b/contracts/.env.example @@ -1,4 +1,6 @@ -PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 +DEPLOYER_PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 +AGGREGATOR_PRIVATE_KEY=0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d + HOLESKY_PRIVATE_KEY= HOLESKY_RPC_URL= ETHERSCAN_API_KEY= diff --git a/contracts/script/DeployEigenLayerCore.s.sol b/contracts/script/DeployEigenLayerCore.s.sol index 6e40702a..3302f75b 100644 --- a/contracts/script/DeployEigenLayerCore.s.sol +++ b/contracts/script/DeployEigenLayerCore.s.sol @@ -16,7 +16,7 @@ contract DeployEigenlayerCore is Script { CoreDeploymentLib.DeploymentConfigData internal configData; function setUp() public virtual { - deployer = vm.rememberKey(vm.envUint("PRIVATE_KEY")); + deployer = vm.rememberKey(vm.envUint("DEPLOYER_PRIVATE_KEY")); vm.label(deployer, "Deployer"); } diff --git a/contracts/script/HelloWorldDeployer.s.sol b/contracts/script/HelloWorldDeployer.s.sol index 394adcf8..bb78d5b4 100644 --- a/contracts/script/HelloWorldDeployer.s.sol +++ b/contracts/script/HelloWorldDeployer.s.sol @@ -19,12 +19,13 @@ contract HelloWorldDeployer is Script { address private deployer; address proxyAdmin; + address aggregator; CoreDeploymentLib.DeploymentData coreDeployment; HelloWorldDeploymentLib.DeploymentData helloWorldDeployment; Quorum internal quorum; function setUp() public virtual { - deployer = vm.rememberKey(vm.envUint("PRIVATE_KEY")); + deployer = vm.rememberKey(vm.envUint("DEPLOYER_PRIVATE_KEY")); vm.label(deployer, "Deployer"); coreDeployment = CoreDeploymentLib.readDeploymentJson("deployments/core/", block.chainid); @@ -38,8 +39,12 @@ contract HelloWorldDeployer is Script { vm.startBroadcast(deployer); proxyAdmin = UpgradeableProxyLib.deployProxyAdmin(); + aggregator = vm.addr(vm.envUint("AGGREGATOR_PRIVATE_KEY")); + + console2.log("aggregator", aggregator); + helloWorldDeployment = - HelloWorldDeploymentLib.deployContracts(proxyAdmin, coreDeployment, quorum); + HelloWorldDeploymentLib.deployContracts(proxyAdmin, aggregator, coreDeployment, quorum); vm.stopBroadcast(); @@ -56,6 +61,7 @@ contract HelloWorldDeployer is Script { "HelloWorldServiceManager address cannot be zero" ); require(proxyAdmin != address(0), "ProxyAdmin address cannot be zero"); + require(aggregator != address(0), "Aggregator address cannot be zero"); require( coreDeployment.delegationManager != address(0), "DelegationManager address cannot be zero" diff --git a/contracts/script/utils/CoreDeploymentLib.sol b/contracts/script/utils/CoreDeploymentLib.sol index 31ad403d..7c946a5e 100644 --- a/contracts/script/utils/CoreDeploymentLib.sol +++ b/contracts/script/utils/CoreDeploymentLib.sol @@ -378,9 +378,7 @@ library CoreDeploymentLib { } /// TODO: Need to be able to read json from eigenlayer-contracts repo for holesky/mainnet and output the json here - function writeDeploymentJson( - DeploymentData memory data - ) internal { + function writeDeploymentJson(DeploymentData memory data) internal { writeDeploymentJson("deployments/core/", block.chainid, data); } diff --git a/contracts/script/utils/HelloWorldDeploymentLib.sol b/contracts/script/utils/HelloWorldDeploymentLib.sol index 98988fa9..ac8d74d2 100644 --- a/contracts/script/utils/HelloWorldDeploymentLib.sol +++ b/contracts/script/utils/HelloWorldDeploymentLib.sol @@ -30,6 +30,7 @@ library HelloWorldDeploymentLib { function deployContracts( address proxyAdmin, + address aggregator, CoreDeploymentLib.DeploymentData memory core, Quorum memory quorum ) internal returns (DeploymentData memory) { @@ -43,7 +44,7 @@ library HelloWorldDeploymentLib { address(new ECDSAStakeRegistry(IDelegationManager(core.delegationManager))); address helloWorldServiceManagerImpl = address( new HelloWorldServiceManager( - core.avsDirectory, result.stakeRegistry, core.delegationManager + core.avsDirectory, result.stakeRegistry, core.delegationManager, aggregator ) ); // Upgrade contracts @@ -53,12 +54,17 @@ library HelloWorldDeploymentLib { UpgradeableProxyLib.upgradeAndCall(result.stakeRegistry, stakeRegistryImpl, upgradeCall); UpgradeableProxyLib.upgrade(result.helloWorldServiceManager, helloWorldServiceManagerImpl); + // Initialize HelloWorldServiceManager with aggregator + bytes memory helloWorldInitCall = + abi.encodeCall(HelloWorldServiceManager.initialize, (aggregator)); + UpgradeableProxyLib.upgradeAndCall( + result.helloWorldServiceManager, helloWorldServiceManagerImpl, helloWorldInitCall + ); + return result; } - function readDeploymentJson( - uint256 chainId - ) internal returns (DeploymentData memory) { + function readDeploymentJson(uint256 chainId) internal returns (DeploymentData memory) { return readDeploymentJson("deployments/", chainId); } @@ -81,9 +87,7 @@ library HelloWorldDeploymentLib { } /// write to default output path - function writeDeploymentJson( - DeploymentData memory data - ) internal { + function writeDeploymentJson(DeploymentData memory data) internal { writeDeploymentJson("deployments/hello-world/", block.chainid, data); } @@ -95,7 +99,11 @@ library HelloWorldDeploymentLib { address proxyAdmin = address(UpgradeableProxyLib.getProxyAdmin(data.helloWorldServiceManager)); - string memory deploymentData = _generateDeploymentJson(data, proxyAdmin); + address aggregator = HelloWorldServiceManager(data.helloWorldServiceManager).aggregator(); + + console2.log("aggregator2", aggregator); + + string memory deploymentData = _generateDeploymentJson(data, proxyAdmin, aggregator); string memory fileName = string.concat(outputPath, vm.toString(chainId), ".json"); if (!vm.exists(outputPath)) { @@ -108,7 +116,8 @@ library HelloWorldDeploymentLib { function _generateDeploymentJson( DeploymentData memory data, - address proxyAdmin + address proxyAdmin, + address aggregator ) private view returns (string memory) { return string.concat( '{"lastUpdate":{"timestamp":"', @@ -116,18 +125,21 @@ library HelloWorldDeploymentLib { '","block_number":"', vm.toString(block.number), '"},"addresses":', - _generateContractsJson(data, proxyAdmin), + _generateContractsJson(data, proxyAdmin, aggregator), "}" ); } function _generateContractsJson( DeploymentData memory data, - address proxyAdmin + address proxyAdmin, + address aggregator ) private view returns (string memory) { return string.concat( '{"proxyAdmin":"', proxyAdmin.toHexString(), + '","aggregator":"', + aggregator.toHexString(), '","helloWorldServiceManager":"', data.helloWorldServiceManager.toHexString(), '","helloWorldServiceManagerImpl":"', diff --git a/contracts/script/utils/UpgradeableProxyLib.sol b/contracts/script/utils/UpgradeableProxyLib.sol index 56bdae5d..a0ee2a1f 100644 --- a/contracts/script/utils/UpgradeableProxyLib.sol +++ b/contracts/script/utils/UpgradeableProxyLib.sol @@ -19,9 +19,7 @@ library UpgradeableProxyLib { return address(new ProxyAdmin()); } - function setUpEmptyProxy( - address admin - ) internal returns (address) { + function setUpEmptyProxy(address admin) internal returns (address) { address emptyContract = address(new EmptyContract()); return address(new TransparentUpgradeableProxy(emptyContract, admin, "")); } @@ -36,16 +34,12 @@ library UpgradeableProxyLib { admin.upgradeAndCall(TransparentUpgradeableProxy(payable(proxy)), impl, initData); } - function getImplementation( - address proxy - ) internal view returns (address) { + function getImplementation(address proxy) internal view returns (address) { bytes32 value = vm.load(proxy, IMPLEMENTATION_SLOT); return address(uint160(uint256(value))); } - function getProxyAdmin( - address proxy - ) internal view returns (ProxyAdmin) { + function getProxyAdmin(address proxy) internal view returns (ProxyAdmin) { bytes32 value = vm.load(proxy, ADMIN_SLOT); return ProxyAdmin(address(uint160(uint256(value)))); } diff --git a/contracts/src/HelloWorldServiceManager.sol b/contracts/src/HelloWorldServiceManager.sol index 145e0a5e..ebecd780 100644 --- a/contracts/src/HelloWorldServiceManager.sol +++ b/contracts/src/HelloWorldServiceManager.sol @@ -7,7 +7,8 @@ import {ECDSAStakeRegistry} from "@eigenlayer-middleware/src/unaudited/ECDSAStak import {IServiceManager} from "@eigenlayer-middleware/src/interfaces/IServiceManager.sol"; import {ECDSAUpgradeable} from "@openzeppelin-upgrades/contracts/utils/cryptography/ECDSAUpgradeable.sol"; -import {IERC1271Upgradeable} from "@openzeppelin-upgrades/contracts/interfaces/IERC1271Upgradeable.sol"; +import {IERC1271Upgradeable} from + "@openzeppelin-upgrades/contracts/interfaces/IERC1271Upgradeable.sol"; import {IHelloWorldServiceManager} from "./IHelloWorldServiceManager.sol"; import "@openzeppelin/contracts/utils/Strings.sol"; @@ -20,6 +21,8 @@ contract HelloWorldServiceManager is ECDSAServiceManagerBase, IHelloWorldService uint32 public latestTaskNum; + address public aggregator; + // mapping of task indices to all tasks hashes // when a task is created, task hash is stored here, // and responses need to pass the actual task, @@ -27,20 +30,20 @@ contract HelloWorldServiceManager is ECDSAServiceManagerBase, IHelloWorldService mapping(uint32 => bytes32) public allTaskHashes; // mapping of task indices to hash of abi.encode(taskResponse, taskResponseMetadata) - mapping(address => mapping(uint32 => bytes)) public allTaskResponses; + mapping(uint32 => bytes) public allTaskResponses; - modifier onlyOperator() { - require( - ECDSAStakeRegistry(stakeRegistry).operatorRegistered(msg.sender), - "Operator must be the caller" - ); + modifier onlyAggregator() { + if (msg.sender != aggregator) { + revert("Aggregator must be the caller"); + } _; } constructor( address _avsDirectory, address _stakeRegistry, - address _delegationManager + address _delegationManager, + address _aggregator ) ECDSAServiceManagerBase( _avsDirectory, @@ -50,11 +53,13 @@ contract HelloWorldServiceManager is ECDSAServiceManagerBase, IHelloWorldService ) {} + function initialize(address _aggregator) public initializer { + _updateAggregator(_aggregator); + } + /* FUNCTIONS */ // NOTE: this function creates new task, assigns it a taskId - function createNewTask( - string memory name - ) external returns (Task memory) { + function createNewTask(string memory name) external returns (Task memory) { // create a new task struct Task memory newTask; newTask.name = name; @@ -71,30 +76,59 @@ contract HelloWorldServiceManager is ECDSAServiceManagerBase, IHelloWorldService function respondToTask( Task calldata task, uint32 referenceTaskIndex, - bytes memory signature - ) external { + bytes memory aggregateSignatureData + ) external onlyAggregator { // check that the task is valid, hasn't been responsed yet, and is being responded in time require( keccak256(abi.encode(task)) == allTaskHashes[referenceTaskIndex], "supplied task does not match the one recorded in the contract" ); - require( - allTaskResponses[msg.sender][referenceTaskIndex].length == 0, - "Operator has already responded to the task" - ); // The message that was signed bytes32 messageHash = keccak256(abi.encodePacked("Hello, ", task.name)); - bytes32 ethSignedMessageHash = messageHash.toEthSignedMessageHash(); + + (address[] memory operators, bytes[] memory signatures, uint32 referenceBlock) = + abi.decode(aggregateSignatureData, (address[], bytes[], uint32)); + + // check if all signers are registered operators + // @note is this necessary? _checkSignatures requires that the total signer weight meets our threshold + // only registered operators can contribute to the weight. + for (uint256 i = 0; i < operators.length; i++) { + require(_isOperator(operators[i]), "Invalid signer: not registered as operator"); + } + + // check if signatures are valid bytes4 magicValue = IERC1271Upgradeable.isValidSignature.selector; - if (!(magicValue == ECDSAStakeRegistry(stakeRegistry).isValidSignature(ethSignedMessageHash,signature))){ - revert(); + bytes32 ethSignedMessageHash = messageHash.toEthSignedMessageHash(); + if ( + !( + magicValue + == ECDSAStakeRegistry(stakeRegistry).isValidSignature( + ethSignedMessageHash, aggregateSignatureData + ) + ) + ) { + revert("Invalid signature"); } - // updating the storage with task responses - allTaskResponses[msg.sender][referenceTaskIndex] = signature; + // updating the storage with task response + allTaskResponses[referenceTaskIndex] = aggregateSignatureData; // emitting event emit TaskResponded(referenceTaskIndex, task, msg.sender); } + + function updateAggregator(address _newAggregator) external onlyOwner { + _updateAggregator(_newAggregator); + } + + function _isOperator(address _operator) internal view returns (bool) { + return ECDSAStakeRegistry(stakeRegistry).operatorRegistered(_operator); + } + + function _updateAggregator(address _newAggregator) internal { + address oldAggregator = aggregator; + aggregator = _newAggregator; + emit AggregatorUpdated(oldAggregator, _newAggregator); + } } diff --git a/contracts/src/IHelloWorldServiceManager.sol b/contracts/src/IHelloWorldServiceManager.sol index e674fce6..bd361b1e 100644 --- a/contracts/src/IHelloWorldServiceManager.sol +++ b/contracts/src/IHelloWorldServiceManager.sol @@ -6,6 +6,8 @@ interface IHelloWorldServiceManager { event TaskResponded(uint32 indexed taskIndex, Task task, address operator); + event AggregatorUpdated(address oldAggregator, address newAggregator); + struct Task { string name; uint32 taskCreatedBlock; @@ -13,18 +15,13 @@ interface IHelloWorldServiceManager { function latestTaskNum() external view returns (uint32); - function allTaskHashes( - uint32 taskIndex - ) external view returns (bytes32); + function aggregator() external view returns (address); + + function allTaskHashes(uint32 taskIndex) external view returns (bytes32); - function allTaskResponses( - address operator, - uint32 taskIndex - ) external view returns (bytes memory); + function allTaskResponses(uint32 taskIndex) external view returns (bytes memory); - function createNewTask( - string memory name - ) external returns (Task memory); + function createNewTask(string memory name) external returns (Task memory); function respondToTask( Task calldata task, diff --git a/contracts/test/ERC20Mock.sol b/contracts/test/ERC20Mock.sol index 07ec5e48..f7b8b29e 100644 --- a/contracts/test/ERC20Mock.sol +++ b/contracts/test/ERC20Mock.sol @@ -5,7 +5,7 @@ import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; contract ERC20Mock is ERC20 { constructor() ERC20("", "") {} - + function mint(address account, uint256 amount) public { _mint(account, amount); } diff --git a/contracts/test/HelloWorldServiceManager.t.sol b/contracts/test/HelloWorldServiceManager.t.sol index f7ad8b55..b2dc145a 100644 --- a/contracts/test/HelloWorldServiceManager.t.sol +++ b/contracts/test/HelloWorldServiceManager.t.sol @@ -48,6 +48,8 @@ contract HelloWorldTaskManagerSetup is Test { ERC20Mock public mockToken; + address public aggregator; + mapping(address => IStrategy) public tokenToStrategy; function setUp() public virtual { @@ -61,17 +63,19 @@ contract HelloWorldTaskManagerSetup is Test { mockToken = new ERC20Mock(); + aggregator = address(1337); + IStrategy strategy = addStrategy(address(mockToken)); quorum.strategies.push(StrategyParams({strategy: strategy, multiplier: 10_000})); helloWorldDeployment = - HelloWorldDeploymentLib.deployContracts(proxyAdmin, coreDeployment, quorum); + HelloWorldDeploymentLib.deployContracts(proxyAdmin, aggregator, coreDeployment, quorum); labelContracts(coreDeployment, helloWorldDeployment); + + console.log("Aggregator address:", aggregator); } - function addStrategy( - address token - ) public returns (IStrategy) { + function addStrategy(address token) public returns (IStrategy) { if (tokenToStrategy[token] != IStrategy(address(0))) { return tokenToStrategy[token]; } @@ -136,9 +140,7 @@ contract HelloWorldTaskManagerSetup is Test { return shares; } - function registerAsOperator( - Operator memory operator - ) internal { + function registerAsOperator(Operator memory operator) internal { IDelegationManager delegationManager = IDelegationManager(coreDeployment.delegationManager); IDelegationManager.OperatorDetails memory operatorDetails = IDelegationManager @@ -152,9 +154,7 @@ contract HelloWorldTaskManagerSetup is Test { delegationManager.registerAsOperator(operatorDetails, ""); } - function registerOperatorToAVS( - Operator memory operator - ) internal { + function registerOperatorToAVS(Operator memory operator) internal { ECDSAStakeRegistry stakeRegistry = ECDSAStakeRegistry(helloWorldDeployment.stakeRegistry); AVSDirectory avsDirectory = AVSDirectory(coreDeployment.avsDirectory); @@ -175,9 +175,7 @@ contract HelloWorldTaskManagerSetup is Test { stakeRegistry.registerOperatorWithSignature(operatorSignature, operator.signingKey.addr); } - function deregisterOperatorFromAVS( - Operator memory operator - ) internal { + function deregisterOperatorFromAVS(Operator memory operator) internal { ECDSAStakeRegistry stakeRegistry = ECDSAStakeRegistry(helloWorldDeployment.stakeRegistry); vm.prank(operator.key.addr); @@ -196,9 +194,7 @@ contract HelloWorldTaskManagerSetup is Test { return newOperator; } - function updateOperatorWeights( - Operator[] memory _operators - ) internal { + function updateOperatorWeights(Operator[] memory _operators) internal { ECDSAStakeRegistry stakeRegistry = ECDSAStakeRegistry(helloWorldDeployment.stakeRegistry); address[] memory operatorAddresses = new address[](_operators.length); @@ -209,17 +205,17 @@ contract HelloWorldTaskManagerSetup is Test { stakeRegistry.updateOperators(operatorAddresses); } - function getSortedOperatorSignatures( + function getSortedOperatorAddressesAndSignatures( Operator[] memory _operators, bytes32 digest - ) internal pure returns (bytes[] memory) { + ) internal pure returns (address[] memory, bytes[] memory) { uint256 length = _operators.length; bytes[] memory signatures = new bytes[](length); address[] memory addresses = new address[](length); for (uint256 i = 0; i < length; i++) { addresses[i] = _operators[i].key.addr; - signatures[i] = signWithOperatorKey(_operators[i], digest); + signatures[i] = signWithSigningKey(_operators[i], digest); } for (uint256 i = 0; i < length - 1; i++) { @@ -238,7 +234,7 @@ contract HelloWorldTaskManagerSetup is Test { } } - return signatures; + return (addresses, signatures); } function createTask(TrafficGenerator memory generator, string memory taskName) internal { @@ -260,9 +256,9 @@ contract HelloWorldTaskManagerSetup is Test { bytes memory signature = signWithSigningKey(operator, messageHash); address[] memory operators = new address[](1); - operators[0]=operator.key.addr; + operators[0] = operator.key.addr; bytes[] memory signatures = new bytes[](1); - signatures[0]= signature; + signatures[0] = signature; bytes memory signedTask = abi.encode(operators, signatures, uint32(block.number)); @@ -397,6 +393,8 @@ contract RespondToTask is HelloWorldTaskManagerSetup { sm = IHelloWorldServiceManager(helloWorldDeployment.helloWorldServiceManager); stakeRegistry = ECDSAStakeRegistry(helloWorldDeployment.stakeRegistry); + aggregator = sm.aggregator(); + addStrategy(address(mockToken)); while (operators.length < OPERATOR_COUNT) { @@ -414,8 +412,84 @@ contract RespondToTask is HelloWorldTaskManagerSetup { } } - function testRespondToTask() public { - string memory taskName = "TestTask"; + function testRespondToTask_singleOperator() public { + string memory taskName = "TestTask single operator"; + IHelloWorldServiceManager.Task memory newTask = sm.createNewTask(taskName); + uint32 taskIndex = sm.latestTaskNum() - 1; + + bytes32 messageHash = keccak256(abi.encodePacked("Hello, ", taskName)); + bytes32 ethSignedMessageHash = messageHash.toEthSignedMessageHash(); + bytes memory signature = signWithSigningKey(operators[0], ethSignedMessageHash); // TODO: Use signing key after changes to service manager + + address[] memory operatorsMem = new address[](1); + operatorsMem[0] = operators[0].key.addr; + bytes[] memory signatures = new bytes[](1); + signatures[0] = signature; + + bytes memory signedTask = abi.encode(operatorsMem, signatures, uint32(block.number)); + + vm.roll(block.number + 1); + + vm.startPrank(aggregator); + sm.respondToTask(newTask, taskIndex, signedTask); + vm.stopPrank(); + } + + function testRespondToTask_multipleOperators() public { + string memory taskName = "TestTask multiple operators"; + IHelloWorldServiceManager.Task memory newTask = sm.createNewTask(taskName); + uint32 taskIndex = sm.latestTaskNum() - 1; + + bytes32 messageHash = keccak256(abi.encodePacked("Hello, ", taskName)); + bytes32 ethSignedMessageHash = messageHash.toEthSignedMessageHash(); + + (address[] memory operatorAddresses, bytes[] memory signatures) = + getSortedOperatorAddressesAndSignatures(operators, ethSignedMessageHash); + + bytes memory signedTask = abi.encode(operatorAddresses, signatures, uint32(block.number)); + + vm.roll(block.number + 1); + + vm.startPrank(aggregator); + sm.respondToTask(newTask, taskIndex, signedTask); + vm.stopPrank(); + } + + function testRespondToTask_failsWithUnregisteredOperator() public { + string memory taskName = "TestTask with unregistered operator"; + IHelloWorldServiceManager.Task memory newTask = sm.createNewTask(taskName); + uint32 taskIndex = sm.latestTaskNum() - 1; + + bytes32 messageHash = keccak256(abi.encodePacked("Hello, ", taskName)); + bytes32 ethSignedMessageHash = messageHash.toEthSignedMessageHash(); + + // Create an unregistered operator + Operator memory unregisteredOperator = createAndAddOperator(); + + Operator[] memory allOperators = new Operator[](operators.length + 1); + for (uint256 i = 0; i < operators.length; i++) { + allOperators[i] = operators[i]; + } + allOperators[operators.length] = unregisteredOperator; + + (address[] memory operatorAddresses, bytes[] memory signatures) = + getSortedOperatorAddressesAndSignatures(allOperators, ethSignedMessageHash); + + bytes memory signedTask = abi.encode(operatorAddresses, signatures, uint32(block.number)); + + vm.roll(block.number + 1); + + vm.startPrank(aggregator); + + // We expect this call to revert due to the unregistered operator being included in the aggregate signature + vm.expectRevert("Invalid signer: not registered as operator"); + sm.respondToTask(newTask, taskIndex, signedTask); + + vm.stopPrank(); + } + + function testRespondToTask_failsIfNotAggregator() public { + string memory taskName = "TestTask single operator"; IHelloWorldServiceManager.Task memory newTask = sm.createNewTask(taskName); uint32 taskIndex = sm.latestTaskNum() - 1; @@ -424,13 +498,18 @@ contract RespondToTask is HelloWorldTaskManagerSetup { bytes memory signature = signWithSigningKey(operators[0], ethSignedMessageHash); // TODO: Use signing key after changes to service manager address[] memory operatorsMem = new address[](1); - operatorsMem[0]=operators[0].key.addr; + operatorsMem[0] = operators[0].key.addr; bytes[] memory signatures = new bytes[](1); - signatures[0]= signature; + signatures[0] = signature; bytes memory signedTask = abi.encode(operatorsMem, signatures, uint32(block.number)); - vm.roll(block.number+1); + vm.roll(block.number + 1); + + address nonAggregator = address(0x1234); + vm.startPrank(nonAggregator); + vm.expectRevert("Only aggregator can call this function"); sm.respondToTask(newTask, taskIndex, signedTask); + vm.stopPrank(); } }