Skip to content

Commit

Permalink
Merge pull request #177 from manifoldfinance/controlc/migration
Browse files Browse the repository at this point in the history
Add in initial staking functionality
  • Loading branch information
ControlCplusControlV authored Aug 28, 2023
2 parents 2e70b5a + f54712b commit 8104a52
Show file tree
Hide file tree
Showing 5 changed files with 140 additions and 2 deletions.
39 changes: 38 additions & 1 deletion src/MevEth.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ pragma solidity 0.8.19;
import { SafeTransferLib } from "solmate/utils/SafeTransferLib.sol";
import { FixedPointMathLib } from "solmate/utils/FixedPointMathLib.sol";
import { IERC4626 } from "./interfaces/IERC4626.sol";
import { ERC20 } from "solmate/tokens/ERC20.sol";
import { WETH } from "solmate/tokens/WETH.sol";
import { MevEthErrors } from "./interfaces/Errors.sol";
import { IStakingModule } from "./interfaces/IStakingModule.sol";
Expand Down Expand Up @@ -69,6 +70,11 @@ contract MevEth is OFTWithFee, IERC4626, ITinyMevEth {
uint256 internal lastRewards;
/// @notice Struct used to accounting the ETH staked within MevEth.
Fraction public fraction;
/// @notice The percent out of 1000 crETH2 can be redeemed for as mevEth
/// @notice Taken from https://twitter.com/dcfgod/status/1682295466774634496 , should likely be updated before prod
uint256 public constant CREAM_TO_MEV_ETH_PERCENT = 1130;
/// @notice The canonical address of the crETH2 address
ERC20 public constant creamToken = ERC20(0x49D72e3973900A195A155a46441F0C08179FdB64);
/// @notice Sandwich protection mapping of last user deposits by block number
mapping(address => uint256) lastDeposit;

Expand Down Expand Up @@ -696,7 +702,6 @@ contract MevEth is OFTWithFee, IERC4626, ITinyMevEth {
}

/// @dev Returns the smallest of two numbers.

function min(uint256 a, uint256 b) internal pure returns (uint256) {
return a < b ? a : b;
}
Expand All @@ -709,6 +714,38 @@ contract MevEth is OFTWithFee, IERC4626, ITinyMevEth {
}
}

/*//////////////////////////////////////////////////////////////
Special CreamEth2 redeem (from initial migration)
//////////////////////////////////////////////////////////////*/

/// @notice Redeem Cream staked eth tokens for mevETH at a fixed ratio
/// @param creamAmount The amount of Cream tokens to redeem
function redeemCream(uint256 creamAmount) external {
if (_isZero(creamAmount)) revert MevEthErrors.ZeroValue();

// Calculate the equivalent mevETH to be redeemed based on the ratio
uint256 mevEthAmount = creamAmount * uint256(CREAM_TO_MEV_ETH_PERCENT) / 1000;

// Transfer Cream tokens from the sender to the burn address
// safeTransferFrom not needed as we know the exact implementation
creamToken.transferFrom(msg.sender, address(0), creamAmount);

// Convert the shares to assets and update the fraction elastic and base
uint256 assets = convertToAssets(mevEthAmount);

fraction.elastic += uint128(assets);
fraction.base += uint128(mevEthAmount);

// Mint the equivalent mevETH
_mint(msg.sender, mevEthAmount);

// Emit event
emit CreamRedeemed(msg.sender, creamAmount, mevEthAmount);
}

// Event emitted when Cream tokens are redeemed for mevETH
event CreamRedeemed(address indexed redeemer, uint256 creamAmount, uint256 mevEthAmount);

/// @dev Only Weth withdraw is defined for the behaviour. Deposits should be directed to deposit / mint. Rewards via grantRewards and validator withdraws
/// via grantValidatorWithdraw.
receive() external payable {
Expand Down
17 changes: 17 additions & 0 deletions src/WagyuStaker.sol
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,23 @@ contract WagyuStaker is Auth, IStakingModule {
emit MevEthUpdated(newMevEth);
}

/// @notice Batch register Validators for migration
/// @dev only Admin
/// @param batchData list of each validators' data struct
function batchMigrate(IStakingModule.ValidatorData[] calldata batchData) external onlyAdmin {
uint256 length = batchData.length;
// Update the contract balance and validator count
unchecked {
record.totalDeposited += uint128(length * VALIDATOR_DEPOSIT_SIZE);
validators += length;
}
for (uint256 i = 0; i < length; ++i) {
IStakingModule.ValidatorData memory data = batchData[i];
// Emit an event inidicating a new validator has been registered, allowing for offchain listeners to track the validator registry
emit NewValidator(data.operator, data.pubkey, data.withdrawal_credentials, data.signature, data.deposit_data_root);
}
}

/// @notice Function to receive Ether
receive() external payable { }
}
3 changes: 2 additions & 1 deletion src/interfaces/IStakingModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ interface IStakingModule {

function validators() external view returns (uint256);


function mevEth() external view returns (address);

function VALIDATOR_DEPOSIT_SIZE() external view returns (uint256);
Expand All @@ -33,4 +32,6 @@ interface IStakingModule {
function recoverToken(address token, address recipient, uint256 amount) external;
function record() external returns (uint128, uint128, uint128, uint128);
function registerExit() external;

function batchMigrate(IStakingModule.ValidatorData[] calldata batchData) external;
}
16 changes: 16 additions & 0 deletions test/mocks/MockERC20.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

import { ERC20 } from "solmate/tokens/ERC20.sol";

contract MockERC20 is ERC20 {
constructor(string memory name, string memory symbol, uint8 decimals) ERC20(name, symbol, decimals) { }

function mint(address to, uint256 amount) external {
_mint(to, amount);
}

function burn(address from, uint256 amount) external {
_burn(from, amount);
}
}
67 changes: 67 additions & 0 deletions test/unit/Migration.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/// SPDX: License-Identifier: GPL-3.0-only
pragma solidity 0.8.19;

import "../MevEthTest.sol";
import { MevEthShareVault } from "src/MevEthShareVault.sol";
import { Empty } from "test/mocks/Empty.sol";
import { MockERC20 } from "../mocks/MockERC20.sol";
import "src/libraries/Auth.sol";
import "../mocks/DepositContract.sol";
import { IStakingModule } from "../../src/interfaces/IStakingModule.sol";
import "../../src/interfaces/IMevEthShareVault.sol";
import "../../lib/safe-tools/src/SafeTestTools.sol";

contract MevEthMigrationTest is MevEthTest {
address internal creamTokenAddress = 0x49D72e3973900A195A155a46441F0C08179FdB64;

function testRedeemCreamETHIsolated() external {
MockERC20 tempCreth2 = new MockERC20("Cream Ether 2", "creth2", 18);
vm.etch(creamTokenAddress, address(tempCreth2).code);

MockERC20 creth2 = MockERC20(creamTokenAddress);

vm.startPrank(User01);
creth2.mint(User01, 1000 ether);
creth2.approve(address(mevEth), 1000 ether);

uint256 oldCreth2Balance = creth2.balanceOf(User01);
uint256 oldMevEthBalance = mevEth.balanceOf(User01);

mevEth.redeemCream(1000 ether);

uint256 newCreth2Balance = creth2.balanceOf(User01);
uint256 newMevEthBalance = mevEth.balanceOf(User01);

assert(newCreth2Balance == 0 && newCreth2Balance < oldCreth2Balance);
assert(newMevEthBalance > 0 && newMevEthBalance > oldMevEthBalance);

assertEq(newMevEthBalance, (1000 ether * mevEth.CREAM_TO_MEV_ETH_PERCENT()) / 1000);
}

function testMigrateValidatorsThroughWagyu() external {
IStakingModule wagyuStaker = mevEth.stakingModule();

IStakingModule.ValidatorData[] memory migratedValidators = new IStakingModule.ValidatorData[](5);

migratedValidators[0] = mockValidatorData(Operator01, 32 ether);
migratedValidators[1] = mockValidatorData(Operator01, 32 ether);
migratedValidators[2] = mockValidatorData(Operator01, 32 ether);
migratedValidators[3] = mockValidatorData(Operator01, 32 ether);
migratedValidators[4] = mockValidatorData(Operator01, 32 ether);

uint256 oldTotalValidators = wagyuStaker.validators();
(uint256 oldTotalDeposited,,,) = wagyuStaker.record();

vm.prank(SamBacha);
wagyuStaker.batchMigrate(migratedValidators);

uint256 newTotalValidators = wagyuStaker.validators();
(uint256 newTotalDeposited,,,) = wagyuStaker.record();

assert(newTotalDeposited > oldTotalDeposited);
assert(newTotalValidators > oldTotalValidators);

assertEq(newTotalValidators, oldTotalValidators + 5);
assertEq(newTotalDeposited, oldTotalDeposited + (32 ether * 5));
}
}

0 comments on commit 8104a52

Please sign in to comment.