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

feat: staker upgrade to include protocol fees #268

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
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
15 changes: 15 additions & 0 deletions nix/shell.nix
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,21 @@
-vvvvv
'';
}
{
category = "deployments";
name = "deployStaker";
help = "Deploy the Staker contract";
command = ''
forge script $PRJ_ROOT/script/DeployStaker.s.sol:DeployStakerScript \
--chain-id 1 \
--rpc-url $RPC_MAINNET \
--broadcast \
--private-key $PRIVATE_KEY \
--verify \
--etherscan-api-key $ETHERSCAN_API_KEY \
-vvvvv
'';
}
{
category = "deployments";
name = "deploy-goerli";
Expand Down
2 changes: 1 addition & 1 deletion script/Deploy.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ contract DeployScript is Script {
// MevEthShareVault initialShareVault = new MevEthShareVault(authority, address(mevEth), authority);
address initialShareVault = safe;
// deploy staking module
IStakingModule initialStakingModule = new WagyuStaker(authority, beaconDepositContract, address(mevEth));
IStakingModule initialStakingModule = new WagyuStaker(authority, beaconDepositContract, address(mevEth), authority);
// initialise mevETH
mevEth.init(address(initialShareVault), address(initialStakingModule));

Expand Down
56 changes: 56 additions & 0 deletions script/DeployStaker.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.19;

import "forge-std/Script.sol";
import { MevEth } from "src/MevEth.sol";
import { IAuth } from "src/interfaces/IAuth.sol";
import { WagyuStaker } from "src/WagyuStaker.sol";
import { AuthManager } from "src/libraries/AuthManager.sol";
import { IStakingModule } from "src/interfaces/IStakingModule.sol";

contract DeployStakerScript is Script {
error UnknownChain();

function run() public {
address authority = tx.origin; // make deployer initial authority for setup
address operator = 0xA0766B65A4f7B1da79a1AF79aC695456eFa28644; // manifoldfinance.eth
address multisig = 0x617c8dE5BdE54ffbb8d92716CC947858cA38f582;
uint256 chainId;
address beaconDepositContract;
address weth;
address mevEth = 0x24Ae2dA0f361AA4BE46b48EB19C91e02c5e4f27E;
assembly {
chainId := chainid()
}
if (chainId == 1) {
// Eth mainnet
beaconDepositContract = 0x00000000219ab540356cBB839Cbe05303d7705Fa;
weth = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
} else if (chainId == 5) {
// Goerli
beaconDepositContract = 0xff50ed3d0ec03aC01D4C79aAd74928BFF48a7b2b;
weth = 0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6;
} else {
revert UnknownChain();
}

vm.startBroadcast();

// deploy staking module
WagyuStaker wagyu = new WagyuStaker(authority, beaconDepositContract, mevEth, multisig);

// setup roles
// multisig is admin
wagyu.addAdmin(multisig);
if (authority != operator) {
// manifoldfinance.eth is operator
wagyu.addOperator(operator);
// remove deployer as operator
wagyu.deleteOperator(authority);
}
// remove deployer as admin
wagyu.deleteAdmin(authority);

vm.stopBroadcast();
}
}
2 changes: 1 addition & 1 deletion slither.config.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"detectors_to_exclude": "assembly,timestamp,solc-version,missing-zero-check,immutable-states,arbitrary-send-eth,too-many-digits,divide-before-multiply,conformance-to-solidity-naming-conventions,low-level-calls,reentrancy-events,cache-array-length,unused-return,cyclomatic-complexity,calls-loop, reentrancy-unlimited-gas,reentrancy-eth,reentrancy-benign,costly-loop, events-maths, incorrect-equality",
"detectors_to_exclude": "assembly,timestamp,solc-version,missing-zero-check,immutable-states,arbitrary-send-eth,too-many-digits,divide-before-multiply,conformance-to-solidity-naming-conventions,low-level-calls,reentrancy-events,cache-array-length,unused-return,cyclomatic-complexity,calls-loop, reentrancy-unlimited-gas,reentrancy-eth,reentrancy-benign,costly-loop, events-maths, incorrect-equality, missing-inheritance",
"exclude_informational": false,
"exclude_low": false,
"exclude_medium": false,
Expand Down
31 changes: 29 additions & 2 deletions src/WagyuStaker.sol
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,14 @@ contract WagyuStaker is Auth, IStakingModule {
uint128 totalValidatorExitsPaid;
}

/// @notice Record of total deposits, withdraws, rewards paid and validators exited
/// @notice Record of total deposits, withdraws, rewards and fees paid and validators exited
Record public record;
/// @notice The number of validators on the consensus layer registered under this contract
uint256 public validators;
/// @notice The address of the MevEth contract
address public mevEth;
/// @notice The address that protocol fees are sent to.
address public protocolFeeTo;
/// @notice Validator deposit size.
uint256 public constant override VALIDATOR_DEPOSIT_SIZE = 32 ether;
/// @notice The Canonical Address of the BeaconChainDepositContract
Expand All @@ -53,14 +55,20 @@ contract WagyuStaker is Auth, IStakingModule {
event ValidatorWithdraw(address sender, uint256 amount);
/// @notice Event emitted when the mevEth address is updated.
event MevEthUpdated(address indexed meveth);
/// @notice Event emitted when the protocolFeeTo address is updated.
event ProtocolFeeToUpdated(address indexed newProtocolFeeTo);
/// @notice Event emitted when the protocol fees are sent to the protocolFeeTo address.
event FeesSent(uint256 indexed feesSent);

/// @notice Construction sets authority, MevEth, and deposit contract addresses
/// @param _authority The address of the controlling admin authority
/// @param _depositContract The address of the beacon deposit contract
/// @param _mevEth The address of the mevETH contract
constructor(address _authority, address _depositContract, address _mevEth) Auth(_authority) {
/// @param _protocolFeeTo The address that protocol fees are sent to.
constructor(address _authority, address _depositContract, address _mevEth, address _protocolFeeTo) Auth(_authority) {
mevEth = _mevEth;
BEACON_CHAIN_DEPOSIT_CONTRACT = IBeaconDepositContract(_depositContract);
protocolFeeTo = _protocolFeeTo;
}

/// @notice Function to deposit funds into the BEACON_CHAIN_DEPOSIT_CONTRACT, and register a validator
Expand Down Expand Up @@ -111,6 +119,25 @@ contract WagyuStaker is Auth, IStakingModule {
emit RewardsPaid(rewards);
}

/// @notice Function to collect the fees owed to the prorotocol.
function sendFees(uint256 fees) external onlyOperator {
unchecked {
record.totalWithdrawn += uint128(fees);
}

SafeTransferLib.safeTransferETH(protocolFeeTo, fees);

emit FeesSent(fees);
}

function setProtocolFeeTo(address newProtocolFeeTo) external onlyAdmin {
if (newProtocolFeeTo == address(0)) {
revert MevEthErrors.ZeroAddress();
}
protocolFeeTo = newProtocolFeeTo;
emit ProtocolFeeToUpdated(newProtocolFeeTo);
}

function registerExit() external {
// Only the MevEth contract can call this function
if (msg.sender != mevEth) {
Expand Down
24 changes: 24 additions & 0 deletions test/DeployStaker.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/// SPDX: License-Identifier: MIT
pragma solidity ^0.8.19;

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

// Deploy script
import "script/DeployStaker.s.sol";

contract DeployStakerTest is Test {
string RPC_ETH_MAINNET = vm.envString("RPC_MAINNET");
uint256 FORK_ID;
DeployStakerScript deploy;

function setUp() public virtual {
FORK_ID = vm.createSelectFork(RPC_ETH_MAINNET);
deploy = new DeployStakerScript();
}

function testDeployStaker() public virtual {
vm.selectFork(FORK_ID);
deploy.run();
}
}
2 changes: 1 addition & 1 deletion test/MevEthTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ contract MevEthTest is Test {
// assign share vault as proxy to multisig
address initialShareVault = address(safe);

address initialStakingModule = address(IStakingModule(address(new WagyuStaker(SamBacha, address(depositContract), address(mevEth)))));
address initialStakingModule = address(IStakingModule(address(new WagyuStaker(SamBacha, address(depositContract), address(mevEth), SamBacha))));

AuthManager authManager = new AuthManager(SamBacha, address(mevEth), address(initialShareVault), address(initialStakingModule));

Expand Down
4 changes: 2 additions & 2 deletions test/unit/Admin.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -597,7 +597,7 @@ contract MevAdminTest is MevEthTest {

// Create new share vault and staking module
address initialShareVault = address(new MevEthShareVault(SamBacha, address(mevEth), SamBacha));
address initialStakingModule = address(IStakingModule(address(new WagyuStaker(SamBacha, address(depositContract), address(mevEth)))));
address initialStakingModule = address(IStakingModule(address(new WagyuStaker(SamBacha, address(depositContract), address(mevEth), SamBacha))));
assert(!mevEth.initialized());

// Initialize the MevEth contract
Expand All @@ -623,7 +623,7 @@ contract MevAdminTest is MevEthTest {

// Create new share vault and staking module
address initialShareVault = address(new MevEthShareVault(SamBacha, address(mevEth), SamBacha));
address initialStakingModule = address(IStakingModule(address(new WagyuStaker(SamBacha, address(depositContract), address(mevEth)))));
address initialStakingModule = address(IStakingModule(address(new WagyuStaker(SamBacha, address(depositContract), address(mevEth), SamBacha))));

// Expect an unauthorized revert
vm.expectRevert(Auth.Unauthorized.selector);
Expand Down
2 changes: 1 addition & 1 deletion test/unit/CreamRedeem.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ contract CreamRedeemTest is MevEthTest {
address constant CRETH2_HOLDER = 0x36cc7B13029B5DEe4034745FB4F24034f3F2ffc6;

function setUp() public override {
MAINNET_FORK_ID = vm.createSelectFork(RPC_ETH_MAINNET);
MAINNET_FORK_ID = vm.createSelectFork(RPC_ETH_MAINNET, 18_282_730);

vm.selectFork(MAINNET_FORK_ID);
// deploy mevEth (mainnet)
Expand Down
2 changes: 1 addition & 1 deletion test/unit/Validator.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ contract ValidatorTest is MevEthTest {
function testUpdateToWagyuStakingModule() public {
// Update the staking module to the WagyuStaker and create a new validator
address depositContract = address(new DepositContract());
IStakingModule wagyuStakingModule = IStakingModule(address(new WagyuStaker(SamBacha, depositContract, address(mevEth))));
IStakingModule wagyuStakingModule = IStakingModule(address(new WagyuStaker(SamBacha, depositContract, address(mevEth), SamBacha)));
_updateStakingModule(wagyuStakingModule);

uint256 depositSize = mevEth.stakingModule().VALIDATOR_DEPOSIT_SIZE();
Expand Down
32 changes: 32 additions & 0 deletions test/unit/WagyuStaker.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,38 @@ contract WagyuStakerTest is MevEthTest {
assertEq(address(wagyuStaker).balance - totalDeposited, amount);
}

function testSendFees(uint128 fees) public {
vm.assume(fees > 0);
vm.deal(address(this), fees);
payable(wagyuStaker).transfer(fees);

vm.prank(SamBacha);
vm.expectEmit(true, false, false, false, address(wagyuStaker));
emit FeesSent(fees);
wagyuStaker.sendFees(fees);

assertEq(address(wagyuStaker).balance, 0);
assertEq(wagyuStaker.protocolFeeTo().balance, fees);
}

function testNegativeSendFees(uint128 fees) public {
vm.assume(fees > 0);
vm.assume(fees < 100_000_000_000_000_000_000_000_000);
vm.deal(address(this), fees);
payable(wagyuStaker).transfer(fees);

vm.expectRevert(Auth.Unauthorized.selector);
wagyuStaker.sendFees(fees);

address newProtocolFeeTo = address(0);
vm.prank(SamBacha);
vm.expectRevert();
wagyuStaker.setProtocolFeeTo(newProtocolFeeTo);

assertEq(address(wagyuStaker).balance, fees);
assertEq(wagyuStaker.protocolFeeTo().balance, 0);
}

function testSetNewMevEth(address newMevEth) public {
vm.assume(newMevEth != address(0));

Expand Down