Skip to content

Commit

Permalink
feat: cancel AVS registration salt (#434)
Browse files Browse the repository at this point in the history
* feat: cancel salt

* fix: require that salt cannot be cancelled twice

---------

Co-authored-by: wadealexc <[email protected]>
  • Loading branch information
0x0aa0 and wadealexc authored Feb 13, 2024
1 parent 0575211 commit 17b5dd8
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 1 deletion.
16 changes: 15 additions & 1 deletion docs/core/AVSDirectory.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,18 @@ Allows the caller (an AVS) to deregister an `operator` with itself
* `operator` MUST already be registered with the AVS

*As of M2*:
* Operator registration/deregistration does not have any sort of consequences for the Operator or its shares. Eventually, this will tie into payments for services and slashing for misbehavior.
* Operator registration/deregistration does not have any sort of consequences for the Operator or its shares. Eventually, this will tie into payments for services and slashing for misbehavior.

#### `cancelSalt`

```solidity
function cancelSalt(bytes32 salt) external
```

Allows the caller (an Operator) to cancel a signature salt before it is used to register for an AVS.

*Effects*:
* Sets `operatorSaltIsSpent[msg.sender][salt]` to `true`

*Requirements*:
* Salt MUST NOT already be cancelled
9 changes: 9 additions & 0 deletions src/contracts/core/AVSDirectory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,15 @@ contract AVSDirectory is
emit AVSMetadataURIUpdated(msg.sender, metadataURI);
}

/**
* @notice Called by an operator to cancel a salt that has been used to register with an AVS.
* @param salt A unique and single use value associated with the approver signature.
*/
function cancelSalt(bytes32 salt) external {
require(!operatorSaltIsSpent[msg.sender][salt], "AVSDirectory.cancelSalt: cannot cancel spent salt");
operatorSaltIsSpent[msg.sender][salt] = true;
}

/*******************************************************************************
VIEW FUNCTIONS
*******************************************************************************/
Expand Down
81 changes: 81 additions & 0 deletions src/test/unit/AVSDirectoryUnit.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -271,4 +271,85 @@ contract AVSDirectoryUnitTests_operatorAVSRegisterationStatus is AVSDirectoryUni
avsDirectory.registerOperatorToAVS(operator, operatorSignature);
cheats.stopPrank();
}

/// @notice Checks that cancelSalt updates the operatorSaltIsSpent mapping correctly
function testFuzz_cancelSalt(bytes32 salt) public {
address operator = cheats.addr(delegationSignerPrivateKey);
assertFalse(delegationManager.isOperator(operator), "bad test setup");
_registerOperatorWithBaseDetails(operator);

assertFalse(avsDirectory.operatorSaltIsSpent(operator, salt), "bad test setup");
assertFalse(avsDirectory.operatorSaltIsSpent(defaultAVS, salt), "bad test setup");

cheats.prank(operator);
avsDirectory.cancelSalt(salt);

assertTrue(avsDirectory.operatorSaltIsSpent(operator, salt), "salt was not successfully cancelled");
assertFalse(avsDirectory.operatorSaltIsSpent(defaultAVS, salt), "salt should only be cancelled for the operator");

bytes32 newSalt;
unchecked { newSalt = bytes32(uint(salt) + 1); }

assertFalse(salt == newSalt, "bad test setup");

cheats.prank(operator);
avsDirectory.cancelSalt(newSalt);

assertTrue(avsDirectory.operatorSaltIsSpent(operator, salt), "original salt should still be cancelled");
assertTrue(avsDirectory.operatorSaltIsSpent(operator, newSalt), "new salt should be cancelled");
}

/// @notice Verifies that registration fails when the salt has been cancelled via cancelSalt
function testFuzz_revert_whenRegisteringWithCancelledSalt(bytes32 salt) public {
address operator = cheats.addr(delegationSignerPrivateKey);
assertFalse(delegationManager.isOperator(operator), "bad test setup");
_registerOperatorWithBaseDetails(operator);

uint256 expiry = type(uint256).max;
ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature =
_getOperatorSignature(delegationSignerPrivateKey, operator, defaultAVS, salt, expiry);

cheats.prank(operator);
avsDirectory.cancelSalt(salt);

cheats.expectRevert("AVSDirectory.registerOperatorToAVS: salt already spent");
cheats.prank(defaultAVS);
avsDirectory.registerOperatorToAVS(operator, operatorSignature);
}

/// @notice Verifies that an operator cannot cancel the same salt twice
function testFuzz_revert_whenSaltCancelledTwice(bytes32 salt) public {
address operator = cheats.addr(delegationSignerPrivateKey);
assertFalse(delegationManager.isOperator(operator), "bad test setup");
_registerOperatorWithBaseDetails(operator);

uint256 expiry = type(uint256).max;
ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature =
_getOperatorSignature(delegationSignerPrivateKey, operator, defaultAVS, salt, expiry);

cheats.startPrank(operator);
avsDirectory.cancelSalt(salt);

cheats.expectRevert("AVSDirectory.cancelSalt: cannot cancel spent salt");
avsDirectory.cancelSalt(salt);
cheats.stopPrank();
}

/// @notice Verifies that an operator cannot cancel the same salt twice
function testFuzz_revert_whenCancellingSaltUsedToRegister(bytes32 salt) public {
address operator = cheats.addr(delegationSignerPrivateKey);
assertFalse(delegationManager.isOperator(operator), "bad test setup");
_registerOperatorWithBaseDetails(operator);

uint256 expiry = type(uint256).max;
ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature =
_getOperatorSignature(delegationSignerPrivateKey, operator, defaultAVS, salt, expiry);

cheats.prank(defaultAVS);
avsDirectory.registerOperatorToAVS(operator, operatorSignature);

cheats.prank(operator);
cheats.expectRevert("AVSDirectory.cancelSalt: cannot cancel spent salt");
avsDirectory.cancelSalt(salt);
}
}

0 comments on commit 17b5dd8

Please sign in to comment.