Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add call to burner #62

Merged
merged 3 commits into from
Oct 15, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions src/contracts/slasher/BaseSlasher.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {StaticDelegateCallable} from "../common/StaticDelegateCallable.sol";

import {IBaseDelegator} from "../../interfaces/delegator/IBaseDelegator.sol";
import {IBaseSlasher} from "../../interfaces/slasher/IBaseSlasher.sol";
import {IBurner} from "../../interfaces/slasher/IBurner.sol";
import {INetworkMiddlewareService} from "../../interfaces/service/INetworkMiddlewareService.sol";
import {IRegistry} from "../../interfaces/common/IRegistry.sol";
import {IVault} from "../../interfaces/vault/IVault.sol";
Expand All @@ -21,6 +22,16 @@ abstract contract BaseSlasher is Entity, StaticDelegateCallable, ReentrancyGuard
using Checkpoints for Checkpoints.Trace256;
using Subnetwork for bytes32;

/**
* @inheritdoc IBaseSlasher
*/
uint256 public constant BURNER_GAS_LIMIT = 150_000;

/**
* @inheritdoc IBaseSlasher
*/
uint256 public constant BURNER_RESERVE = 20_000;

/**
* @inheritdoc IBaseSlasher
*/
Expand Down Expand Up @@ -170,6 +181,21 @@ abstract contract BaseSlasher is Entity, StaticDelegateCallable, ReentrancyGuard
_cumulativeSlash[subnetwork][operator].push(Time.timestamp(), cumulativeSlash(subnetwork, operator) + amount);
}

function _burnerOnSlash(bytes32 subnetwork, address operator, uint256 amount, uint48 captureTimestamp) internal {
address burner = IVault(vault).burner();
if (burner != address(0)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mb only if hook enabled?

Copy link
Collaborator Author

@1kresh 1kresh Oct 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New base init param for slasher?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, If vault have normal slasher we don't need to call hook

bytes memory calldata_ = abi.encodeCall(IBurner.onSlash, (subnetwork, operator, amount, captureTimestamp));

if (gasleft() < BURNER_RESERVE + BURNER_GAS_LIMIT * 64 / 63) {
revert InsufficientBurnerGas();
}

assembly ("memory-safe") {
pop(call(BURNER_GAS_LIMIT, burner, 0, add(calldata_, 0x20), mload(calldata_), 0, 0))
}
}
}

function _initialize(
bytes calldata data
) internal override {
Expand Down
2 changes: 2 additions & 0 deletions src/contracts/slasher/Slasher.sol
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ contract Slasher is BaseSlasher, ISlasher {

IVault(vault_).onSlash(slashedAmount, captureTimestamp);

_burnerOnSlash(subnetwork, operator, slashedAmount, captureTimestamp);

emit Slash(subnetwork, operator, slashedAmount, captureTimestamp);
}
}
2 changes: 2 additions & 0 deletions src/contracts/slasher/VetoSlasher.sol
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,8 @@ contract VetoSlasher is BaseSlasher, IVetoSlasher {
IVault(vault_).onSlash(slashedAmount, request.captureTimestamp);
}

_burnerOnSlash(request.subnetwork, request.operator, slashedAmount, request.captureTimestamp);

emit ExecuteSlash(slashIndex, slashedAmount);
}

Expand Down
13 changes: 13 additions & 0 deletions src/interfaces/slasher/IBaseSlasher.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pragma solidity ^0.8.0;
import {IEntity} from "../common/IEntity.sol";

interface IBaseSlasher is IEntity {
error InsufficientBurnerGas();
error NotNetworkMiddleware();
error NotVault();
error OutdatedCaptureTimestamp();
Expand All @@ -22,6 +23,18 @@ interface IBaseSlasher is IEntity {
bytes cumulativeSlashFromHint;
}

/**
* @notice Get a gas limit for the burner.
* @return value of the burner gas limit
*/
function BURNER_GAS_LIMIT() external view returns (uint256);

/**
* @notice Get a reserve gas between the gas limit check and the burner's execution.
* @return value of the reserve gas
*/
function BURNER_RESERVE() external view returns (uint256);

/**
* @notice Get the vault factory's address.
* @return address of the vault factory
Expand Down
13 changes: 13 additions & 0 deletions src/interfaces/slasher/IBurner.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IBurner {
/**
* @notice Called when a slash happens.
* @param subnetwork full identifier of the subnetwork (address of the network concatenated with the uint96 identifier)
* @param operator address of the operator
* @param amount virtual amount of the collateral slashed
* @param captureTimestamp time point when the stake was captured
*/
function onSlash(bytes32 subnetwork, address operator, uint256 amount, uint48 captureTimestamp) external;
}
32 changes: 32 additions & 0 deletions test/mocks/SimpleBurner.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;

import {IBurner} from "../../src/interfaces/slasher/IBurner.sol";

import {SafeERC20, IERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

contract SimpleBurner is IBurner {
using SafeERC20 for IERC20;

address public immutable COLLATERAL;

constructor(
address collateral
) {
COLLATERAL = collateral;
}

uint256 public counter1;
uint256 public counter2;
uint256 public counter3;

function onSlash(bytes32 subnetwork, address operator, uint256, uint48) external {
++counter1;
++counter2;
++counter3;
}

function distribute() external {
IERC20(COLLATERAL).safeTransfer(msg.sender, IERC20(COLLATERAL).balanceOf(address(this)));
}
}
203 changes: 203 additions & 0 deletions test/slasher/Slasher.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ import {OptInServiceHints} from "../../src/contracts/hints/OptInServiceHints.sol
import {VaultHints} from "../../src/contracts/hints/VaultHints.sol";
import {Subnetwork} from "../../src/contracts/libraries/Subnetwork.sol";

import {SimpleBurner} from "../mocks/SimpleBurner.sol";

contract SlasherTest is Test {
using Subnetwork for bytes32;
using Subnetwork for address;
Expand Down Expand Up @@ -1047,6 +1049,207 @@ contract SlasherTest is Test {
_slash(alice, network, alice, slashAmount1, uint48(blockTimestamp - captureAgo), "");
}

function test_SlashWithBurner(
// uint48 epochDuration,
uint256 depositAmount,
// uint256 networkLimit,
uint256 operatorNetworkLimit1,
uint256 slashAmount1,
uint256 slashAmount2
) public {
// epochDuration = uint48(bound(epochDuration, 1, 10 days));
depositAmount = bound(depositAmount, 1, 100 * 10 ** 18);
// networkLimit = bound(networkLimit, 1, type(uint256).max);
operatorNetworkLimit1 = bound(operatorNetworkLimit1, 1, type(uint256).max / 2);
slashAmount1 = bound(slashAmount1, 1, type(uint256).max);
slashAmount2 = bound(slashAmount2, 1, type(uint256).max);
vm.assume(slashAmount1 < Math.min(depositAmount, Math.min(type(uint256).max, operatorNetworkLimit1)));

uint256 blockTimestamp = block.timestamp * block.timestamp / block.timestamp * block.timestamp / block.timestamp;
blockTimestamp = blockTimestamp + 1_720_700_948;
vm.warp(blockTimestamp);

address burner = address(new SimpleBurner(address(collateral)));
address[] memory networkLimitSetRoleHolders = new address[](1);
networkLimitSetRoleHolders[0] = alice;
address[] memory operatorNetworkLimitSetRoleHolders = new address[](1);
operatorNetworkLimitSetRoleHolders[0] = alice;
(address vault_, address delegator_, address slasher_) = vaultConfigurator.create(
IVaultConfigurator.InitParams({
version: vaultFactory.lastVersion(),
owner: alice,
vaultParams: abi.encode(
IVault.InitParams({
collateral: address(collateral),
burner: burner,
epochDuration: 7 days,
depositWhitelist: false,
isDepositLimit: false,
depositLimit: 0,
defaultAdminRoleHolder: alice,
depositWhitelistSetRoleHolder: alice,
depositorWhitelistRoleHolder: alice,
isDepositLimitSetRoleHolder: alice,
depositLimitSetRoleHolder: alice
})
),
delegatorIndex: 1,
delegatorParams: abi.encode(
IFullRestakeDelegator.InitParams({
baseParams: IBaseDelegator.BaseParams({
defaultAdminRoleHolder: alice,
hook: address(0),
hookSetRoleHolder: address(0)
}),
networkLimitSetRoleHolders: networkLimitSetRoleHolders,
operatorNetworkLimitSetRoleHolders: operatorNetworkLimitSetRoleHolders
})
),
withSlasher: true,
slasherIndex: 0,
slasherParams: ""
})
);

vault = Vault(vault_);
delegator = FullRestakeDelegator(delegator_);
slasher = Slasher(slasher_);

address network = alice;
_registerNetwork(network, alice);
_setMaxNetworkLimit(network, 0, type(uint256).max);

_registerOperator(alice);

_optInOperatorVault(alice);

_optInOperatorNetwork(alice, address(network));

_deposit(alice, depositAmount);

_setNetworkLimit(alice, network, type(uint256).max);

_setOperatorNetworkLimit(alice, network, alice, operatorNetworkLimit1);

assertEq(delegator.networkLimit(network.subnetwork(0)), type(uint256).max);
assertEq(delegator.operatorNetworkLimit(network.subnetwork(0), alice), operatorNetworkLimit1);

blockTimestamp = blockTimestamp + 1;
vm.warp(blockTimestamp);

_slash(alice, network, alice, slashAmount1, uint48(blockTimestamp - 1), "");

assertEq(SimpleBurner(burner).counter1(), 1);
}

function test_SlashWithBurnerGas(
// uint48 epochDuration,
uint256 depositAmount,
// uint256 networkLimit,
uint256 operatorNetworkLimit1,
uint256 slashAmount1,
uint256 totalGas
) public {
// epochDuration = uint48(bound(epochDuration, 1, 10 days));
depositAmount = bound(depositAmount, 1, 100 * 10 ** 18);
// networkLimit = bound(networkLimit, 1, type(uint256).max);
operatorNetworkLimit1 = bound(operatorNetworkLimit1, 1, type(uint256).max / 2);
slashAmount1 = bound(slashAmount1, 1, type(uint256).max);
totalGas = bound(totalGas, 1, 20_000_000);
vm.assume(slashAmount1 < Math.min(depositAmount, Math.min(type(uint256).max, operatorNetworkLimit1)));

uint256 blockTimestamp = block.timestamp * block.timestamp / block.timestamp * block.timestamp / block.timestamp;
blockTimestamp = blockTimestamp + 1_720_700_948;
vm.warp(blockTimestamp);

address burner = address(new SimpleBurner(address(collateral)));
address[] memory networkLimitSetRoleHolders = new address[](1);
networkLimitSetRoleHolders[0] = alice;
address[] memory operatorNetworkLimitSetRoleHolders = new address[](1);
operatorNetworkLimitSetRoleHolders[0] = alice;
(address vault_, address delegator_, address slasher_) = vaultConfigurator.create(
IVaultConfigurator.InitParams({
version: vaultFactory.lastVersion(),
owner: alice,
vaultParams: abi.encode(
IVault.InitParams({
collateral: address(collateral),
burner: burner,
epochDuration: 7 days,
depositWhitelist: false,
isDepositLimit: false,
depositLimit: 0,
defaultAdminRoleHolder: alice,
depositWhitelistSetRoleHolder: alice,
depositorWhitelistRoleHolder: alice,
isDepositLimitSetRoleHolder: alice,
depositLimitSetRoleHolder: alice
})
),
delegatorIndex: 1,
delegatorParams: abi.encode(
IFullRestakeDelegator.InitParams({
baseParams: IBaseDelegator.BaseParams({
defaultAdminRoleHolder: alice,
hook: address(0),
hookSetRoleHolder: address(0)
}),
networkLimitSetRoleHolders: networkLimitSetRoleHolders,
operatorNetworkLimitSetRoleHolders: operatorNetworkLimitSetRoleHolders
})
),
withSlasher: true,
slasherIndex: 0,
slasherParams: ""
})
);

vault = Vault(vault_);
delegator = FullRestakeDelegator(delegator_);
slasher = Slasher(slasher_);

address network = alice;
_registerNetwork(network, alice);
_setMaxNetworkLimit(network, 0, type(uint256).max);

_registerOperator(alice);

_optInOperatorVault(alice);

_optInOperatorNetwork(alice, address(network));

_deposit(alice, depositAmount);

_setNetworkLimit(alice, network, type(uint256).max);

_setOperatorNetworkLimit(alice, network, alice, operatorNetworkLimit1);

assertEq(delegator.networkLimit(network.subnetwork(0)), type(uint256).max);
assertEq(delegator.operatorNetworkLimit(network.subnetwork(0), alice), operatorNetworkLimit1);

blockTimestamp = blockTimestamp + 1;
vm.warp(blockTimestamp);

_slash(alice, network, alice, slashAmount1, uint48(blockTimestamp - 1), "");

vm.startPrank(alice);
uint256 HOOK_GAS_LIMIT = delegator.HOOK_GAS_LIMIT();
uint256 BURNER_GAS_LIMIT = slasher.BURNER_GAS_LIMIT();
vm.expectRevert(IBaseSlasher.InsufficientBurnerGas.selector);
slasher.slash{gas: HOOK_GAS_LIMIT}(network.subnetwork(0), alice, slashAmount1, uint48(blockTimestamp - 1), "");
vm.stopPrank();

vm.startPrank(alice);
(bool success,) = address(slasher).call{gas: totalGas}(
abi.encodeCall(ISlasher.slash, (network.subnetwork(0), alice, slashAmount1, uint48(blockTimestamp - 1), ""))
);
vm.stopPrank();

if (success) {
assertEq(SimpleBurner(burner).counter1(), 2);
}
}

// struct GasStruct {
// uint256 gasSpent1;
// uint256 gasSpent2;
Expand Down
Loading