Skip to content

Commit

Permalink
refactor: add strategies back to SlashingParams (#933)
Browse files Browse the repository at this point in the history
* refactor: add `strategies` back to `SlashingParams`

* refactor: review changes

* refactor: review changes

* feat: `SingleItemArrayLib` -> `ArrayLib`

- adds sorting
- adds array type conversions

* fix: sort

* refactor: final review changes
  • Loading branch information
0xClandestine authored Dec 9, 2024
1 parent 40ba7e7 commit b866c6c
Show file tree
Hide file tree
Showing 16 changed files with 403 additions and 179 deletions.
4 changes: 2 additions & 2 deletions docs/release/slashing/AllocationManager.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,15 +106,15 @@ Completing a deallocation decreases the encumbered magnitude for the strategy, a
* @param operator the address to slash
* @param operatorSetId the ID of the operatorSet the operator is being slashed on behalf of
* @param strategies the set of strategies to slash
* @param wadToSlash the parts in 1e18 to slash, this will be proportional to the operator's
* @param wadsToSlash the parts in 1e18 to slash, this will be proportional to the operator's
* slashable stake allocation for the operatorSet
* @param description the description of the slashing provided by the AVS for legibility
*/
struct SlashingParams {
address operator;
uint32 operatorSetId;
IStrategy[] strategies;
uint256 wadToSlash;
uint256[] wadsToSlash;
string description;
}
Expand Down
2 changes: 1 addition & 1 deletion lib/zeus-templates
15 changes: 11 additions & 4 deletions script/tasks/slash_operatorSet.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,16 @@ import "forge-std/Test.sol";
// use forge:
// RUST_LOG=forge,foundry=trace forge script script/tasks/slash_operatorSet.s.sol --rpc-url $RPC_URL --private-key $PRIVATE_KEY --broadcast --sig "run(string memory configFile,address operator,uint32 operatorSetId,uint256 wadToSlash)" -- <DEPLOYMENT_OUTPUT_JSON> <OPERATOR_ADDRESS> <OPERATOR_SET_ID> <WADS_TO_SLASH>
// RUST_LOG=forge,foundry=trace forge script script/tasks/slash_operatorSet.s.sol --rpc-url $RPC_URL --private-key $PRIVATE_KEY --broadcast --sig "run(string memory configFile,address operator,uint32 operatorSetId,uint256 wadToSlash)" -- local/slashing_output.json 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 00000001 05000000
contract SlashOperatorSet is Script, Test {
contract SlashOperatorSet is Script, Test, IAllocationManagerTypes {
Vm cheats = Vm(VM_ADDRESS);

function run(string memory configFile, address operator, uint32 operatorSetId, uint256 wadToSlash) public {
function run(
string memory configFile,
address operator,
uint32 operatorSetId,
IStrategy[] memory strategies,
uint256[] memory wadsToSlash
) public {
// Load config
string memory deployConfigPath = string(bytes(string.concat("script/output/", configFile)));
string memory config_data = vm.readFile(deployConfigPath);
Expand All @@ -27,10 +33,11 @@ contract SlashOperatorSet is Script, Test {
AllocationManager am = AllocationManager(allocationManager);

// Define SlashingParams struct instance with correct array initialization
IAllocationManagerTypes.SlashingParams memory slashing = IAllocationManagerTypes.SlashingParams({
SlashingParams memory slashing = SlashingParams({
operator: operator,
operatorSetId: operatorSetId,
wadToSlash: wadToSlash,
strategies: strategies,
wadsToSlash: wadsToSlash,
description: "slashed"
});

Expand Down
46 changes: 25 additions & 21 deletions src/contracts/core/AllocationManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -59,28 +59,33 @@ contract AllocationManager is
function slashOperator(
address avs,
SlashingParams calldata params
) external onlyWhenNotPaused(PAUSED_OPERATOR_SLASHING) {
// Check that the msg.sender can call - we don't use a modifier to avoid `stack too deep` errors
require(_checkCanCall(avs), InvalidCaller());
require(0 < params.wadToSlash && params.wadToSlash <= WAD, InvalidWadToSlash());

) external onlyWhenNotPaused(PAUSED_OPERATOR_SLASHING) checkCanCall(avs) {
// Check that the operator set exists and the operator is registered to it
OperatorSet memory operatorSet = OperatorSet(avs, params.operatorSetId);
bool isRegistered = _isRegistered(params.operator, operatorSet);
require(_operatorSets[operatorSet.avs].contains(operatorSet.id), InvalidOperatorSet());
require(isRegistered, NotMemberOfSet());

uint256 length = _operatorSetStrategies[operatorSet.key()].length();
IStrategy[] memory strategiesSlashed = new IStrategy[](length);
uint256[] memory wadSlashed = new uint256[](length);
uint256[] memory wadSlashed = new uint256[](params.strategies.length);

// For each strategy in the operator set, slash any existing allocation
for (uint256 i = 0; i < length; i++) {
for (uint256 i = 0; i < params.strategies.length; i++) {
// Check that `strategies` is in ascending order.
require(
i == 0 || uint160(address(params.strategies[i])) > uint160(address(params.strategies[i - 1])),
StrategiesMustBeInAscendingOrder()
);
// Check that `wadToSlash` is within acceptable bounds.
require(0 < params.wadsToSlash[i] && params.wadsToSlash[i] <= WAD, InvalidWadToSlash());
// Check that the operator set contains the strategy.
require(
_operatorSetStrategies[operatorSet.key()].contains(address(params.strategies[i])),
StrategyNotInOperatorSet()
);

// 1. Get the operator's allocation info for the strategy and operator set
IStrategy strategy = IStrategy(_operatorSetStrategies[operatorSet.key()].at(i));
(StrategyInfo memory info, Allocation memory allocation) =
_getUpdatedAllocation(params.operator, operatorSet.key(), strategy);
strategiesSlashed[i] = strategy;
_getUpdatedAllocation(params.operator, operatorSet.key(), params.strategies[i]);

// 2. Skip if the operator does not have a slashable allocation
// NOTE: this "if" is equivalent to: `if (!_isAllocationSlashable)`, because the other
Expand All @@ -92,10 +97,9 @@ contract AllocationManager is
// 3. Calculate the amount of magnitude being slashed, and subtract from
// the operator's currently-allocated magnitude, as well as the strategy's
// max and encumbered magnitudes
uint64 slashedMagnitude = uint64(uint256(allocation.currentMagnitude).mulWadRoundUp(params.wadToSlash));
uint256 sharesWadSlashed = uint256(slashedMagnitude).divWad(info.maxMagnitude);
wadSlashed[i] = sharesWadSlashed;
uint64 slashedMagnitude = uint64(uint256(allocation.currentMagnitude).mulWadRoundUp(params.wadsToSlash[i]));
uint64 prevMaxMagnitude = info.maxMagnitude;
wadSlashed[i] = uint256(slashedMagnitude).divWad(info.maxMagnitude);

allocation.currentMagnitude -= slashedMagnitude;
info.maxMagnitude -= slashedMagnitude;
Expand All @@ -105,32 +109,32 @@ contract AllocationManager is
// This ensures that when the deallocation is completed, less magnitude is freed.
if (allocation.pendingDiff < 0) {
uint64 slashedPending =
uint64(uint256(uint128(-allocation.pendingDiff)).mulWadRoundUp(params.wadToSlash));
uint64(uint256(uint128(-allocation.pendingDiff)).mulWadRoundUp(params.wadsToSlash[i]));
allocation.pendingDiff += int128(uint128(slashedPending));

emit AllocationUpdated(
params.operator,
operatorSet,
strategy,
params.strategies[i],
_addInt128(allocation.currentMagnitude, allocation.pendingDiff),
allocation.effectBlock
);
}

// 5. Update state
_updateAllocationInfo(params.operator, operatorSet.key(), strategy, info, allocation);
_updateMaxMagnitude(params.operator, strategy, info.maxMagnitude);
_updateAllocationInfo(params.operator, operatorSet.key(), params.strategies[i], info, allocation);
_updateMaxMagnitude(params.operator, params.strategies[i], info.maxMagnitude);

// 6. Decrease and burn operators shares in the DelegationManager
delegation.burnOperatorShares({
operator: params.operator,
strategy: strategy,
strategy: params.strategies[i],
prevMaxMagnitude: prevMaxMagnitude,
newMaxMagnitude: info.maxMagnitude
});
}

emit OperatorSlashed(params.operator, operatorSet, strategiesSlashed, wadSlashed, params.description);
emit OperatorSlashed(params.operator, operatorSet, params.strategies, wadSlashed, params.description);
}

/// @inheritdoc IAllocationManager
Expand Down
8 changes: 6 additions & 2 deletions src/contracts/interfaces/IAllocationManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ interface IAllocationManagerErrors {
error InvalidOperatorSet();
/// @dev Thrown when a strategy is referenced that does not belong to an operator set.
error InvalidStrategy();
/// @dev Thrown when provided `strategies` are not in ascending order.
error StrategiesMustBeInAscendingOrder();
/// @dev Thrown when trying to add a strategy to an operator set that already contains it.
error StrategyAlreadyInOperatorSet();
/// @dev Thrown when trying to remove a strategy from an operator set it is not a part of.
Expand Down Expand Up @@ -113,14 +115,16 @@ interface IAllocationManagerTypes {
* @notice Struct containing parameters to slashing
* @param operator the address to slash
* @param operatorSetId the ID of the operatorSet the operator is being slashed on behalf of
* @param wadToSlash the parts in 1e18 to slash, this will be proportional to the operator's
* @param strategies the set of strategies to slash
* @param wadsToSlash the parts in 1e18 to slash, this will be proportional to the operator's
* slashable stake allocation for the operatorSet
* @param description the description of the slashing provided by the AVS for legibility
*/
struct SlashingParams {
address operator;
uint32 operatorSetId;
uint256 wadToSlash;
IStrategy[] strategies;
uint256[] wadsToSlash;
string description;
}

Expand Down
63 changes: 21 additions & 42 deletions src/test/DevnetLifecycle.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,15 @@ import "../../src/contracts/core/AVSDirectory.sol";
import "../../src/contracts/core/AllocationManager.sol";
import "../../src/contracts/strategies/StrategyBase.sol";

import "src/test/utils/ArrayLib.sol";

// Test
import "forge-std/Test.sol";

/// @notice Tests deployed contracts as part of the public devnet
/// Run with: forge test --mc Devnet_Lifecycle_Test --rpc-url $RPC_HOLESKY
contract Devnet_Lifecycle_Test is Test {
contract Devnet_Lifecycle_Test is Test, IAllocationManagerTypes {
using ArrayLib for *;

// Contracts
DelegationManager public delegationManager;
Expand Down Expand Up @@ -53,15 +56,11 @@ contract Devnet_Lifecycle_Test is Test {
}

function _getOperatorSetArray() internal view returns (uint32[] memory) {
uint32[] memory operatorSets = new uint32[](1);
operatorSets[0] = operatorSetId;
return operatorSets;
return operatorSetId.toArrayU32();
}

function _getOperatorSetsArray() internal view returns (OperatorSet[] memory) {
OperatorSet[] memory operatorSets = new OperatorSet[](1);
operatorSets[0] = OperatorSet({avs: avs, id: operatorSetId});
return operatorSets;
return OperatorSet({avs: avs, id: operatorSetId}).toArray();
}

function test() public {
Expand Down Expand Up @@ -134,51 +133,35 @@ contract Devnet_Lifecycle_Test is Test {

function _registerAVS() internal {
cheats.startPrank(avs);
IStrategy[] memory strategies = new IStrategy[](1);
strategies[0] = wethStrategy;

IAllocationManagerTypes.CreateSetParams memory createSetParams = IAllocationManagerTypes.CreateSetParams({
CreateSetParams memory createSetParams =CreateSetParams({
operatorSetId: operatorSetId,
strategies: strategies
strategies: wethStrategy.toArray()
});

IAllocationManagerTypes.CreateSetParams[] memory array = new IAllocationManagerTypes.CreateSetParams[](1);
array[0] = createSetParams;

allocationManager.createOperatorSets(avs, array);
allocationManager.createOperatorSets(avs, createSetParams.toArray());
cheats.stopPrank();
}

function _registerOperatorToAVS() public {
cheats.prank(operator);

uint32[] memory operatorSetIds = new uint32[](1);
operatorSetIds[0] = operatorSetId;

allocationManager.registerForOperatorSets(operator, IAllocationManagerTypes.RegisterParams(avs, operatorSetIds, ""));

allocationManager.registerForOperatorSets(operator,RegisterParams(avs, operatorSetId.toArrayU32(), ""));
assertEq(allocationManager.getMembers(OperatorSet(avs, operatorSetId))[0], operator);
}

function _setMagnitude() public {
IStrategy[] memory strategies = new IStrategy[](1);
strategies[0] = wethStrategy;

uint64[] memory magnitudes = new uint64[](1);
magnitudes[0] = magnitudeToSet;

IAllocationManagerTypes.AllocateParams[] memory allocations = new IAllocationManagerTypes.AllocateParams[](1);
allocations[0] = IAllocationManagerTypes.AllocateParams({
AllocateParams[] memory allocations = new AllocateParams[](1);
allocations[0] = AllocateParams({
operatorSet: operatorSet,
strategies: strategies,
newMagnitudes: magnitudes
strategies: wethStrategy.toArray(),
newMagnitudes: magnitudeToSet.toArrayU64()
});

cheats.prank(operator);
allocationManager.modifyAllocations(operator, allocations);

// Assert storage
IAllocationManagerTypes.Allocation memory info = allocationManager.getAllocation(operator, operatorSet, wethStrategy);
Allocation memory info = allocationManager.getAllocation(operator, operatorSet, wethStrategy);
assertEq(info.currentMagnitude, 0);
assertEq(info.pendingDiff, int128(uint128(magnitudeToSet)));
assertEq(info.effectBlock, block.number + 1);
Expand All @@ -193,12 +176,11 @@ contract Devnet_Lifecycle_Test is Test {

function _slashOperator() public {
// Get slashing params
IStrategy[] memory strategies = new IStrategy[](1);
strategies[0] = wethStrategy;
IAllocationManagerTypes.SlashingParams memory slashingParams = IAllocationManagerTypes.SlashingParams({
SlashingParams memory slashingParams = SlashingParams({
operator: operator,
operatorSetId: 1,
wadToSlash: 5e17,
strategies: wethStrategy.toArray(),
wadsToSlash: 5e17.toArrayU256(),
description: "test"
});

Expand All @@ -207,14 +189,13 @@ contract Devnet_Lifecycle_Test is Test {
allocationManager.slashOperator(avs, slashingParams);

// Assert storage
IAllocationManagerTypes.Allocation memory info = allocationManager.getAllocation(operator, operatorSet, wethStrategy);
Allocation memory info = allocationManager.getAllocation(operator, operatorSet, wethStrategy);
assertEq(info.currentMagnitude, magnitudeToSet - 5e17);
}

function _withdrawStaker() public {
// Generate queued withdrawal params
IStrategy[] memory strategies = new IStrategy[](1);
strategies[0] = wethStrategy;
IStrategy[] memory strategies = wethStrategy.toArray();
(uint256[] memory withdrawableShares, ) = delegationManager.getWithdrawableShares(staker, strategies);
IDelegationManagerTypes.QueuedWithdrawalParams[] memory queuedWithdrawals = new IDelegationManagerTypes.QueuedWithdrawalParams[](1);
queuedWithdrawals[0] = IDelegationManagerTypes.QueuedWithdrawalParams({
Expand All @@ -224,16 +205,14 @@ contract Devnet_Lifecycle_Test is Test {
});

// Generate withdrawal params
uint256[] memory scaledShares = new uint256[](1);
scaledShares[0] = 100e18;
IDelegationManagerTypes.Withdrawal memory withdrawal = IDelegationManagerTypes.Withdrawal({
staker: staker,
delegatedTo: operator,
withdrawer: staker,
nonce: delegationManager.cumulativeWithdrawalsQueued(staker),
startBlock: uint32(block.number),
strategies: strategies,
scaledShares: scaledShares
scaledShares: 100e18.toArrayU256()
});
// bytes32 withdrawalRoot = delegationManager.calculateWithdrawalRoot(withdrawal);
// Generate complete withdrawal params
Expand Down
26 changes: 26 additions & 0 deletions src/test/integration/IntegrationBase.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ abstract contract IntegrationBase is IntegrationDeployer {
using Strings for *;
using print for *;

using ArrayLib for IStrategy[];

uint numStakers = 0;
uint numOperators = 0;
uint numAVSs = 0;
Expand Down Expand Up @@ -890,6 +892,30 @@ abstract contract IntegrationBase is IntegrationDeployer {
function _randWadToSlash() internal returns (uint) {
return _randUint({ min: 0.01 ether, max: 1 ether });
}

function _randStrategiesAndWadsToSlash(
OperatorSet memory operatorSet
) internal returns (IStrategy[] memory strategies, uint[] memory wadsToSlash) {
// Get list of all strategies in an operator set.
strategies = allocationManager.getStrategiesInOperatorSet(operatorSet);

// Randomly select a subset of strategies to slash.
uint len = _randUint({ min: 1, max: strategies.length });

// Update length of strategies array.
assembly {
mstore(strategies, len)
}

wadsToSlash = new uint[](len);

// Randomly select a `wadToSlash` for each strategy.
for (uint i; i < len; ++i) {
wadsToSlash[i] = _randWadToSlash();
}

return (strategies.sort(), wadsToSlash);
}

function _randMagnitudes(uint64 sum, uint256 len) internal returns (uint64[] memory magnitudes) {
magnitudes = new uint64[](len);
Expand Down
2 changes: 2 additions & 0 deletions src/test/integration/IntegrationDeployer.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ uint8 constant PAUSED_NON_PROOF_WITHDRAWALS = 5;

abstract contract IntegrationDeployer is ExistingDeploymentParser, Logger {
using StdStyle for *;
using ArrayLib for *;
using ArrayLib for IStrategy[];

// Fork ids for specific fork tests
bool isUpgraded;
Expand Down
3 changes: 2 additions & 1 deletion src/test/integration/tests/Deposit_Delegate_Allocate.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ contract Integration_Deposit_Delegate_Allocate is IntegrationCheckUtils {
for (uint i; i < operatorSets.length; ++i) {
uint256 len = allocationManager.getStrategiesInOperatorSet(operatorSets[i]).length;
operator.modifyAllocations(operatorSets[i], _randMagnitudes({ sum: 1 ether / uint64(operatorSets.length), len: len }));
avs.slashOperator(operator, operatorSets[i].id, _randWadToSlash());
(IStrategy[] memory strategiesToSlash, uint[] memory wadsToSlash) = _randStrategiesAndWadsToSlash(operatorSets[i]);
avs.slashOperator(operator, operatorSets[i].id, strategiesToSlash, wadsToSlash);
}

// TODO: write checks for slashing...
Expand Down
Loading

0 comments on commit b866c6c

Please sign in to comment.