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

Slashing upgrade #87

Draft
wants to merge 39 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
2990aac
remove unused function from interface
bxmmm1 Dec 12, 2024
c7f7833
refactor / remove everything
bxmmm1 Dec 12, 2024
5e06bf7
eeeeveerrryyythiing wip
bxmmm1 Dec 12, 2024
78d6631
more
bxmmm1 Dec 12, 2024
5fb8bc3
filename update
bxmmm1 Dec 12, 2024
9ea8d22
remove more code
bxmmm1 Dec 12, 2024
a018ae9
remove unused var
bxmmm1 Dec 12, 2024
f2bb9f5
update mocks / test
bxmmm1 Dec 13, 2024
2af9783
remove unused code
bxmmm1 Dec 13, 2024
57552d5
codespell
bxmmm1 Dec 13, 2024
55956e0
more weight loss
bxmmm1 Dec 13, 2024
c71d469
lowercase import
bxmmm1 Dec 13, 2024
f4d8503
exclude EL lib from the coverage
bxmmm1 Dec 13, 2024
8078dc6
Merge branch 'master' of github.com:PufferFinance/puffer-contracts in…
bxmmm1 Dec 16, 2024
64cf816
more weight loss
bxmmm1 Dec 16, 2024
29a6d34
remove unused code
bxmmm1 Dec 17, 2024
5a52fc3
remove unused code
bxmmm1 Dec 17, 2024
d6a6c5d
remove more useless code
bxmmm1 Dec 17, 2024
401ce32
move everything to vault v5
bxmmm1 Dec 17, 2024
5f0b6f1
remove everything checkpoint
bxmmm1 Dec 17, 2024
772a129
cleanup
bxmmm1 Dec 17, 2024
1e738b2
refactor vault v5, minor gas optimisation
bxmmm1 Dec 17, 2024
4a9f219
remove unused scripts, remove dead code
bxmmm1 Dec 17, 2024
7545629
update solhint, remove unused import
bxmmm1 Dec 17, 2024
0e0c8d0
delete unused mocks
bxmmm1 Dec 17, 2024
4d10109
increase test coverage
bxmmm1 Dec 18, 2024
2ed174d
remove IPufferModule
bxmmm1 Dec 18, 2024
f252525
add VaultV5 doc
bxmmm1 Dec 18, 2024
aa26cb4
remove more useless stuff
bxmmm1 Dec 18, 2024
c3654ee
add queue & claim fork test
bxmmm1 Dec 23, 2024
a32646e
Slashing calldata access control
bxmmm1 Dec 23, 2024
c357872
forge fmt
bxmmm1 Dec 23, 2024
6af4bb4
increase test coverage
bxmmm1 Jan 3, 2025
dd67609
Merge branch 'slashing/14' of github.com:PufferFinance/puffer-contrac…
bxmmm1 Jan 3, 2025
cc3bd09
fix failing test
bxmmm1 Jan 3, 2025
f0d66b9
move variable
bxmmm1 Jan 6, 2025
1b147b9
add vault v5 fork tests to make sure that the interface stayed the same
bxmmm1 Jan 6, 2025
b340a38
increase coverage
bxmmm1 Jan 6, 2025
dfd038c
forge fmt
bxmmm1 Jan 6, 2025
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
2 changes: 1 addition & 1 deletion .github/workflows/mainnet-contracts.yml
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ jobs:

- name: "Generate the coverage report"
working-directory: mainnet-contracts
run: 'forge coverage --no-match-coverage "(script|test|mock|node_modules|integrations|echidna|L1RewardManagerUnsafe)" --no-match-contract "PufferModuleManagerHoleskyTestnetFFI" --report lcov -vvv'
run: 'forge coverage --no-match-coverage "(script|test|mock|node_modules|interface|integrations|echidna|L1RewardManagerUnsafe)" --no-match-contract "PufferModuleManagerHoleskyTestnetFFI" --report lcov -vvv'
env:
ETH_RPC_URL: ${{ secrets.ETH_RPC_URL }}
HOLESKY_RPC_URL: ${{ secrets.HOLESKY_RPC_URL }}
Expand Down
6 changes: 3 additions & 3 deletions mainnet-contracts/.solhint.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
"compiler-version": ["error", ">=0.8.0 <0.9.0"],
"contract-name-camelcase": "off",
"const-name-snakecase": "off",
"custom-errors": "error",
"func-name-mixedcase": "off",
"func-visibility": ["error", { "ignoreConstructors": true }],
"max-line-length": ["error", 123],
Expand All @@ -21,6 +20,7 @@
"func-param-name-mixedcase": "error",
"modifier-name-mixedcase": "error",
"code-complexity": "error",
"explicit-types": "error"
"explicit-types": "error",
"gas-custom-errors": "off"
}
}
}
2 changes: 1 addition & 1 deletion mainnet-contracts/docs/PufferVaultV2.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ The PufferVault maintains the addresses of important contracts related to EigenL

* `uint256 lidoLockedETH`: The amount of ETH the Puffer Protocol has locked inside of Lido
* `uint256 eigenLayerPendingWithdrawalSharesAmount`: The amount of stETH shares the Puffer vault has pending for withdrawal from EigenLayer
* `bool isLidoWithdrawal`: Deprecated from PufferVault version 1
* `bool deprecated_isLidoWithdrawal`: Deprecated from PufferVault version 1
* `EnumerableSet.UintSet lidoWithdrawals`: Deprecated from PufferVault version 1
* `EnumerableSet.Bytes32Set eigenLayerWithdrawals`: Tracks withdrawalRoots from EigenLayer withdrawals
* `EnumerableMap.UintToUintMap lidoWithdrawalAmounts`: Tracks the amounts of corresponding to each Lido withdrawal
Expand Down
177 changes: 177 additions & 0 deletions mainnet-contracts/docs/PufferVaultV5.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
# PufferVault V5

The PufferVaultV5 contract is the latest upgrade to the Puffer Vault, responsible for custodying funds for the Puffer protocol. It inherits from PufferVaultStorage and implements various interfaces including IPufferVaultV5, IERC721Receiver, AccessManagedUpgradeable, ERC20PermitUpgradeable, ERC4626Upgradeable, and UUPSUpgradeable.

The PufferVaultV5 contract is a modified ERC4626 vault with custom logic to handle deposits of stETH and ETH in addition to the standard WETH. It extends the ERC4626Upgradeable contract and overrides key functions like `deposit`, `mint`, `withdraw`, and `redeem` to support these additional asset types. The vault tracks the total assets across ETH, stETH, and WETH balances, and handles conversions between these asset types as needed. It also integrates with Lido for stETH withdrawals. Overall, PufferVaultV5 provides a custodial vault tailored for the Puffer protocol's unique requirements while adhering to the ERC4626 standard for compatibility.

## Core Functionality

The vault manages deposits and withdrawals of ETH, stETH, and WETH while minting pufETH tokens to represent user shares. It handles:

- WETH/ETH/stETH deposits and conversions to pufETH
- Withdrawals of WETH in exchange for pufETH
- Reward distribution and management
- Integration with Lido for stETH withdrawals

## Key Components

### Important State Variables
- `_ST_ETH`: Lido's stETH contract address
- `_LIDO_WITHDRAWAL_QUEUE`: Lido's withdrawal queue contract
- `_WETH`: Wrapped ETH contract
- `PUFFER_ORACLE`: Oracle for proof-of-reserves
- `RESTAKING_REWARDS_DEPOSITOR`: Contract for depositing rewards

## Core Functions

### Deposit Functions

#### `depositETH`
Allows users to deposit native ETH and receive pufETH tokens in return.

#### `depositStETH`
Enables deposits of stETH shares in exchange for pufETH tokens.

#### `mint`
```solidity
function mint(uint256 shares, address receiver) public returns (uint256)
```
Mints `shares` pufETH tokens and transfers them to `receiver`. Standard ERC4626 mint function.

#### `deposit`
```solidity
function deposit(uint256 assets, address receiver) public returns (uint256)
```
Deposits `assets` (WETH) and mints the corresponding amount of pufETH tokens to `receiver`. Standard ERC4626 deposit function.

### Withdrawal Functions

#### `withdraw`
```solidity
function withdraw(uint256 assets, address receiver, address owner) public returns (uint256)
```
Withdraws WETH assets from the vault by burning pufETH shares. Standard ERC4626 withdraw function.

#### `redeem`
```solidity
function redeem(uint256 shares, address receiver, address owner) public returns (uint256)
```
Redeems pufETH shares for WETH assets. Standard ERC4626 redeem function.

### Reward Management

#### `mintRewards`
```solidity
function mintRewards(uint256 rewardsAmount) external returns (uint256 ethToPufETHRate, uint256 pufETHAmount)
```
Mints pufETH rewards for the L1RewardManager contract. The rewards are then bridged to Base. On Base the Node operators can claim the rewards.

#### `depositRewards`
```solidity
function depositRewards() external payable
```
Deposits rewards to the vault and updates total reward deposit amount.

### Lido Integration

#### `initiateETHWithdrawalsFromLido`
```solidity
function initiateETHWithdrawalsFromLido(uint256[] calldata amounts) external returns (uint256[] memory)
```
Initiates ETH withdrawals from Lido by queueing withdrawal requests.

#### `claimWithdrawalsFromLido`
```solidity
function claimWithdrawalsFromLido(uint256[] calldata requestIds) external
```
Claims completed ETH withdrawals from Lido.

### Asset Transfer

#### `transferETH`
```solidity
function transferETH(address to, uint256 ethAmount) external
```
Transfers ETH to PufferModules for validator funding.

### Fee Management

#### `setExitFeeBasisPoints`
```solidity
function setExitFeeBasisPoints(uint256 newExitFeeBasisPoints) external
```
Sets the exit fee basis points (max 2%). This exit fee is distributed to all pufETH holders.

### View Functions

#### `totalAssets`
```solidity
function totalAssets() public view returns (uint256)
```
Calculates total assets by summing:
- WETH balance
- ETH balance
- Oracle-reported locked ETH
- Total reward mint amount
- Minus pending distributions and deposits

#### `getPendingLidoETHAmount`
```solidity
function getPendingLidoETHAmount() public view returns (uint256)
```
Returns amount of ETH pending withdrawal from Lido.

#### `getTotalRewardMintAmount`
```solidity
function getTotalRewardMintAmount() public view returns (uint256)
```
Returns total minted rewards amount.

#### `getTotalRewardDepositAmount`
```solidity
function getTotalRewardDepositAmount() public view returns (uint256)
```
Returns total deposited rewards amount.

## Events

### `UpdatedTotalRewardsAmount`
```solidity
event UpdatedTotalRewardsAmount(uint256 previousTotalRewardsAmount, uint256 newTotalRewardsAmount, uint256 depositedETHAmount)
```
Emitted when rewards are deposited to the vault.

### `RequestedWithdrawals`
```solidity
event RequestedWithdrawals(uint256[] requestIds)
```
Emitted when withdrawals are requested from Lido.

### `ClaimedWithdrawals`
```solidity
event ClaimedWithdrawals(uint256[] requestIds)
```
Emitted when withdrawals are claimed from Lido.

### `TransferredETH`
```solidity
event TransferredETH(address to, uint256 amount)
```
Emitted when ETH is transferred to a PufferModule.

## Security Features

- Access control via AccessManagedUpgradeable
- Deposit tracking to prevent simultaneous deposits/withdrawals
- Maximum exit fee of 2%
- Upgradeable via UUPS pattern
- Secure ETH handling with fallback functions

## Integration Points

- EigenLayer for restaking
- Lido for stETH operations
- Puffer Oracle for proof-of-reserves on Beacon Chain
- Revenue Depositor for reward distribution

The contract serves as the core vault for the Puffer protocol, managing user deposits and withdrawals.
11 changes: 4 additions & 7 deletions mainnet-contracts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,18 @@
"@connext/interfaces": "^2.0.5",
"@openzeppelin/contracts": "5.0.1",
"@openzeppelin/contracts-upgradeable": "5.0.1",
"eigenlayer-contracts": "https://github.com/Layr-Labs/eigenlayer-contracts.git#4478eb6",
"eigenlayer-middleware": "https://github.com/bxmmm1/eigenlayer-middleware.git#dbf6c1a",
"l2-contracts": "*",
"murky": "https://github.com/dmfxyz/murky.git",
"openzeppelin-foundry-upgrades": "https://github.com/bxmmm1/openzeppelin-foundry-upgrades.git#patch-1",
"rave": "https://github.com/PufferFinance/rave.git#57ce268",
"solidity-stringutils": "https://github.com/Arachnid/solidity-stringutils"
},
"devDependencies": {
"@crytic/properties": "https://github.com/crytic/properties#f1ff61b",
"@prb/test": "0.6.4",
"erc4626-tests": "https://github.com/a16z/erc4626-tests#8b1d7c2",
"forge-std": "github:foundry-rs/forge-std#v1.9.2",
"forge-std": "github:foundry-rs/forge-std#v1.9.4",
"solarray": "github:evmcheb/solarray#a547630",
"solhint": "^5.0.1"
"solhint": "^5.0.3"
},
"homepage": "https://puffer.fi",
"keywords": [
Expand All @@ -49,7 +46,7 @@
"lint": "yarn run lint:sol",
"test:unit": "forge test --mp \"./test/unit/**/*.sol\" -vvv",
"slither": "slither .",
"coverage": "forge coverage --force --no-match-coverage \"(script|test|mock|node_modules|integrations|echidna)\" --no-match-contract \"PufferModuleManagerHoleskyTestnetFFI\"",
"coverage-lcov": "forge coverage --force --no-match-coverage \"(script|test|mock|node_modules|integrations|echidna)\" --no-match-contract \"PufferModuleManagerHoleskyTestnetFFI\" --report lcov"
"coverage": "forge coverage --force --no-match-coverage \"(script|test|mock|interface|node_modules|echidna)\" --no-match-contract \"PufferModuleManagerHoleskyTestnetFFI\"",
"coverage-lcov": "forge coverage --force --no-match-coverage \"(script|test|mock|interface|node_modules|echidna)\" --no-match-contract \"PufferModuleManagerHoleskyTestnetFFI\" --report lcov"
}
}
5 changes: 0 additions & 5 deletions mainnet-contracts/remappings.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,9 @@ forge-std/=node_modules/forge-std/src/
@openzeppelin/contracts/=node_modules/@openzeppelin/contracts/
@connext/=node_modules/@connext/
@crytic=node_modules/@crytic/properties
eigenlayer/=node_modules/eigenlayer-contracts/src/contracts
eigenlayer-middleware/=node_modules/eigenlayer-middleware/src/
eigenlayer-test/=node_modules/eigenlayer-contracts/src/test
eigenlayer-contracts/=node_modules/eigenlayer-contracts/
rave/=node_modules/rave/src/
rave-test/=node_modules/rave/test/
murky/=node_modules/murky/src/
l2-contracts/=node_modules/l2-contracts/
mainnet-contracts/=node_modules/mainnet-contracts/
openzeppelin-foundry-upgrades/=node_modules/openzeppelin-foundry-upgrades/src/
solidity-stringutils/=node_modules/solidity-stringutils
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
ROLE_ID_OPERATIONS_MULTISIG
} from "../../script/Roles.sol";
import { PufferWithdrawalManager } from "../../src/PufferWithdrawalManager.sol";
import { PufferVaultV2 } from "../../src/PufferVaultV2.sol";
import { PufferVaultV5 } from "../../src/PufferVaultV5.sol";

contract Generate2StepWithdrawalsCalldata is Script {
function run(
Expand Down Expand Up @@ -65,7 +65,7 @@ contract Generate2StepWithdrawalsCalldata is Script {
calldatas[7] = abi.encodeWithSelector(AccessManager.labelRole.selector, ROLE_ID_PUFETH_BURNER, "pufETH Burner");

bytes4[] memory vaultWithdrawerSelectors = new bytes4[](1);
vaultWithdrawerSelectors[0] = PufferVaultV2.transferETH.selector;
vaultWithdrawerSelectors[0] = PufferVaultV5.transferETH.selector;

calldatas[8] = abi.encodeWithSelector(
AccessManager.setTargetFunctionRole.selector,
Expand All @@ -75,7 +75,7 @@ contract Generate2StepWithdrawalsCalldata is Script {
);

bytes4[] memory burnerSelectors = new bytes4[](1);
burnerSelectors[0] = PufferVaultV2.burn.selector;
burnerSelectors[0] = PufferVaultV5.burn.selector;

calldatas[9] = abi.encodeWithSelector(
AccessManager.setTargetFunctionRole.selector, pufferVaultProxy, burnerSelectors, ROLE_ID_PUFETH_BURNER
Expand All @@ -92,7 +92,7 @@ contract Generate2StepWithdrawalsCalldata is Script {

// in AccessManager contract, one selector can be assigned to only one role for a target contract
// see `AccessManager._setTargetFunctionRole` function
// creation of this new `ROLE_ID_VAULT_WITHDRAWER` and assigning the `PufferVaultV2.transferETH` selector to it
// creation of this new `ROLE_ID_VAULT_WITHDRAWER` and assigning the `PufferVaultV5.transferETH` selector to it
// would revoke that ability from the original `ROLE_ID_PUFFER_PROTOCOL`
// that's why we need to grant the `ROLE_ID_VAULT_WITHDRAWER` and `ROLE_ID_PUFETH_BURNER` to the pufferProtocolProxy
calldatas[12] =
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.0 <0.9.0;

import { Script } from "forge-std/Script.sol";
import { AccessManager } from "@openzeppelin/contracts/access/manager/AccessManager.sol";
import { Multicall } from "@openzeppelin/contracts/utils/Multicall.sol";
import { ROLE_ID_DAO, ROLE_ID_OPERATIONS_PAYMASTER } from "../../script/Roles.sol";
import { PufferModuleManager } from "../../src/PufferModuleManager.sol";

// forge script script/AccessManagerMigrations/07_GenerateSlashingELCalldata.s.sol:GenerateSlashingELCalldata -vvvv --sig "run(address)(bytes memory)" PUFFER_MODULE_MANAGER_PROXY_ADDRESS
contract GenerateSlashingELCalldata is Script {
function run(address pufferModuleManagerProxy) public pure returns (bytes memory) {
bytes[] memory calldatas = new bytes[](2);

bytes4[] memory daoSelectors = new bytes4[](9);
daoSelectors[0] = PufferModuleManager.callSetClaimerFor.selector;
daoSelectors[1] = PufferModuleManager.callSetProofSubmitter.selector;
daoSelectors[2] = PufferModuleManager.createNewRestakingOperator.selector;
daoSelectors[3] = PufferModuleManager.callDelegateTo.selector;
daoSelectors[4] = PufferModuleManager.callUndelegate.selector;
daoSelectors[5] = PufferModuleManager.callRegisterOperatorToAVS.selector;
daoSelectors[6] = PufferModuleManager.customExternalCall.selector;
daoSelectors[7] = PufferModuleManager.callDeregisterOperatorFromAVS.selector;
daoSelectors[8] = PufferModuleManager.updateAVSRegistrationSignatureProof.selector;

calldatas[0] =
abi.encodeCall(AccessManager.setTargetFunctionRole, (pufferModuleManagerProxy, daoSelectors, ROLE_ID_DAO));

bytes4[] memory paymasterSelectors = new bytes4[](3);
paymasterSelectors[0] = PufferModuleManager.callCompleteQueuedWithdrawals.selector;
paymasterSelectors[1] = PufferModuleManager.transferRewardsToTheVault.selector;
paymasterSelectors[2] = PufferModuleManager.callQueueWithdrawals.selector;

calldatas[1] = abi.encodeCall(
AccessManager.setTargetFunctionRole,
(pufferModuleManagerProxy, paymasterSelectors, ROLE_ID_OPERATIONS_PAYMASTER)
);

bytes memory encodedMulticall = abi.encodeCall(Multicall.multicall, (calldatas));

return encodedMulticall;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,9 @@ contract GenerateAccessManagerCalldata2 is Script {
function run(address moduleManager) public pure returns (bytes memory) {
bytes[] memory calldatas = new bytes[](1);

bytes4[] memory daoSelectors = new bytes4[](3);
bytes4[] memory daoSelectors = new bytes4[](2);
daoSelectors[0] = PufferModuleManager.callSetClaimerFor.selector;
daoSelectors[1] = PufferModuleManager.callStartCheckpoint.selector;
daoSelectors[2] = PufferModuleManager.callSetProofSubmitter.selector;
daoSelectors[1] = PufferModuleManager.callSetProofSubmitter.selector;

calldatas[0] = abi.encodeWithSelector(
AccessManager.setTargetFunctionRole.selector, moduleManager, daoSelectors, ROLE_ID_DAO
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { AccessManager } from "@openzeppelin/contracts/access/manager/AccessMana
import { Multicall } from "@openzeppelin/contracts/utils/Multicall.sol";
import { console } from "forge-std/console.sol";
import { PufferModuleManager } from "../../src/PufferModuleManager.sol";
import { PufferVaultV3 } from "../../src/PufferVaultV3.sol";
import { PufferVaultV5 } from "../../src/PufferVaultV5.sol";
import { L1RewardManager } from "../../src/L1RewardManager.sol";
import { L2RewardManager } from "l2-contracts/src/L2RewardManager.sol";
import {
Expand Down Expand Up @@ -65,8 +65,8 @@ contract GenerateAccessManagerCalldata3 is Script {
);

bytes4[] memory vaultSelectors = new bytes4[](2);
vaultSelectors[0] = PufferVaultV3.mintRewards.selector;
vaultSelectors[1] = PufferVaultV3.revertMintRewards.selector;
vaultSelectors[0] = PufferVaultV5.mintRewards.selector;
vaultSelectors[1] = PufferVaultV5.revertMintRewards.selector;
calldatas[4] = abi.encodeWithSelector(
AccessManager.setTargetFunctionRole.selector, pufferVaultProxy, vaultSelectors, ROLE_ID_L1_REWARD_MANAGER
);
Expand All @@ -75,7 +75,7 @@ contract GenerateAccessManagerCalldata3 is Script {
abi.encodeWithSelector(AccessManager.grantRole.selector, ROLE_ID_L1_REWARD_MANAGER, l1RewardManagerProxy, 0);

bytes4[] memory pufferModuleManagerSelectors = new bytes4[](1);
pufferModuleManagerSelectors[0] = PufferVaultV3.depositRewards.selector;
pufferModuleManagerSelectors[0] = PufferVaultV5.depositRewards.selector;

calldatas[6] = abi.encodeWithSelector(
AccessManager.setTargetFunctionRole.selector,
Expand Down
Loading