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

fix: extract mint/burning to Lido #885

Merged
merged 1 commit into from
Dec 2, 2024
Merged
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
55 changes: 35 additions & 20 deletions contracts/0.4.24/Lido.sol
Original file line number Diff line number Diff line change
Expand Up @@ -591,16 +591,41 @@ contract Lido is Versioned, StETHPermit, AragonApp {
stakingRouter.deposit.value(depositsValue)(depositsCount, _stakingModuleId, _depositCalldata);
}

/// @notice Mint stETH shares
/// @param _recipient recipient of the shares
/// @param _sharesAmount amount of shares to mint
/// @dev can be called only by accounting
function mintShares(address _recipient, uint256 _sharesAmount) public {
_auth(getLidoLocator().accounting());

_mintShares(_recipient, _sharesAmount);
// emit event after minting shares because we are always having the net new ether under the hood
// for vaults we have new locked ether and for fees we have a part of rewards
_emitTransferAfterMintingShares(_recipient, _sharesAmount);
}

/// @notice Burn stETH shares from the sender address
/// @param _sharesAmount amount of shares to burn
/// @dev can be called only by burner
function burnShares(uint256 _sharesAmount) public {
_auth(getLidoLocator().burner());

_burnShares(msg.sender, _sharesAmount);

// historically there is no events for this kind of burning
// TODO: should burn events be emitted here?
// maybe TransferShare for cover burn and all events for withdrawal burn
}

/// @notice Mint shares backed by external vaults
///
/// @param _receiver Address to receive the minted shares
/// @param _amountOfShares Amount of shares to mint
///
/// @dev authentication goes through isMinter in StETH
/// @return stethAmount The amount of stETH minted
loga4 marked this conversation as resolved.
Show resolved Hide resolved
/// @dev can be called only by accounting (authentication in mintShares method)
function mintExternalShares(address _receiver, uint256 _amountOfShares) external {
if (_receiver == address(0)) revert("MINT_RECEIVER_ZERO_ADDRESS");
if (_amountOfShares == 0) revert("MINT_ZERO_AMOUNT_OF_SHARES");

require(_receiver != address(0), "MINT_RECEIVER_ZERO_ADDRESS");
require(_amountOfShares != 0, "MINT_ZERO_AMOUNT_OF_SHARES");
_whenNotStakingPaused();

uint256 stethAmount = super.getPooledEthByShares(_amountOfShares);
Expand All @@ -620,11 +645,9 @@ contract Lido is Versioned, StETHPermit, AragonApp {
/// @notice Burns external shares from a specified account
///
/// @param _amountOfShares Amount of shares to burn
///
/// @dev authentication goes through _isBurner() method
function burnExternalShares(uint256 _amountOfShares) external {
if (_amountOfShares == 0) revert("BURN_ZERO_AMOUNT_OF_SHARES");

require(_amountOfShares != 0, "BURN_ZERO_AMOUNT_OF_SHARES");
_auth(getLidoLocator().accounting());
_whenNotStakingPaused();

uint256 stethAmount = super.getPooledEthByShares(_amountOfShares);
Expand All @@ -634,7 +657,9 @@ contract Lido is Versioned, StETHPermit, AragonApp {

EXTERNAL_BALANCE_POSITION.setStorageUint256(extBalance - stethAmount);

burnShares(msg.sender, _amountOfShares);
_burnShares(msg.sender, _amountOfShares);

_emitTransferEvents(msg.sender, address(0), stethAmount, _amountOfShares);
Copy link
Contributor

Choose a reason for hiding this comment

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

We dont have events for burnShares, but we have them here - why?


emit ExternalSharesBurned(msg.sender, _amountOfShares, stethAmount);
}
Expand Down Expand Up @@ -916,16 +941,6 @@ contract Lido is Versioned, StETHPermit, AragonApp {
return _getPooledEther().add(EXTERNAL_BALANCE_POSITION.getStorageUint256());
}

/// @dev override isMinter from StETH to allow accounting to mint
function _isMinter(address _sender) internal view returns (bool) {
return _sender == getLidoLocator().accounting();
}

/// @dev override isBurner from StETH to allow accounting to burn
function _isBurner(address _sender) internal view returns (bool) {
return _sender == getLidoLocator().burner() || _sender == getLidoLocator().accounting();
}

function _pauseStaking() internal {
STAKING_STATE_POSITION.setStorageStakeLimitStruct(
STAKING_STATE_POSITION.getStorageStakeLimitStruct().setStakeLimitPauseState(true)
Expand Down
23 changes: 0 additions & 23 deletions contracts/0.4.24/StETH.sol
Original file line number Diff line number Diff line change
Expand Up @@ -360,29 +360,6 @@ contract StETH is IERC20, Pausable {
return tokensAmount;
}

function mintShares(address _recipient, uint256 _sharesAmount) public {
require(_isMinter(msg.sender), "AUTH_FAILED");

_mintShares(_recipient, _sharesAmount);
_emitTransferAfterMintingShares(_recipient, _sharesAmount);
}

function burnShares(address _account, uint256 _sharesAmount) public {
require(_isBurner(msg.sender), "AUTH_FAILED");

_burnShares(_account, _sharesAmount);

// TODO: do something with Transfer event
}

function _isMinter(address) internal view returns (bool) {
return false;
}

function _isBurner(address) internal view returns (bool) {
return false;
}

/**
* @return the total amount (in wei) of Ether controlled by the protocol.
* @dev This is used for calculating tokens from shares and vice versa.
Expand Down
45 changes: 25 additions & 20 deletions contracts/0.8.9/Burner.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ import {IBurner} from "../common/interfaces/IBurner.sol";
import {ILidoLocator} from "../common/interfaces/ILidoLocator.sol";

/**
* @title Interface defining ERC20-compatible StETH token
* @title Interface defining Lido contract
*/
interface IStETH is IERC20 {
interface ILido is IERC20 {
/**
* @notice Get stETH amount by the provided shares amount
* @param _sharesAmount shares amount
Expand Down Expand Up @@ -44,7 +44,11 @@ interface IStETH is IERC20 {
address _sender, address _recipient, uint256 _sharesAmount
) external returns (uint256);

function burnShares(address _account, uint256 _amount) external;
/**
* @notice Burn shares from the account
* @param _amount amount of shares to burn
*/
function burnShares(uint256 _amount) external;
}

/**
Expand Down Expand Up @@ -73,7 +77,7 @@ contract Burner is IBurner, AccessControlEnumerable {
uint256 private totalNonCoverSharesBurnt;

ILidoLocator public immutable LOCATOR;
IStETH public immutable STETH;
ILido public immutable LIDO;

/**
* Emitted when a new stETH burning request is added by the `requestedBy` address.
Expand Down Expand Up @@ -148,7 +152,7 @@ contract Burner is IBurner, AccessControlEnumerable {
_setupRole(REQUEST_BURN_SHARES_ROLE, _stETH);

LOCATOR = ILidoLocator(_locator);
STETH = IStETH(_stETH);
LIDO = ILido(_stETH);

totalCoverSharesBurnt = _totalCoverSharesBurnt;
totalNonCoverSharesBurnt = _totalNonCoverSharesBurnt;
Expand All @@ -166,8 +170,8 @@ contract Burner is IBurner, AccessControlEnumerable {
*
*/
function requestBurnMyStETHForCover(uint256 _stETHAmountToBurn) external onlyRole(REQUEST_BURN_MY_STETH_ROLE) {
STETH.transferFrom(msg.sender, address(this), _stETHAmountToBurn);
uint256 sharesAmount = STETH.getSharesByPooledEth(_stETHAmountToBurn);
LIDO.transferFrom(msg.sender, address(this), _stETHAmountToBurn);
uint256 sharesAmount = LIDO.getSharesByPooledEth(_stETHAmountToBurn);
_requestBurn(sharesAmount, _stETHAmountToBurn, true /* _isCover */);
}

Expand All @@ -183,7 +187,7 @@ contract Burner is IBurner, AccessControlEnumerable {
*
*/
function requestBurnSharesForCover(address _from, uint256 _sharesAmountToBurn) external onlyRole(REQUEST_BURN_SHARES_ROLE) {
uint256 stETHAmount = STETH.transferSharesFrom(_from, address(this), _sharesAmountToBurn);
uint256 stETHAmount = LIDO.transferSharesFrom(_from, address(this), _sharesAmountToBurn);
_requestBurn(_sharesAmountToBurn, stETHAmount, true /* _isCover */);
}

Expand All @@ -199,8 +203,8 @@ contract Burner is IBurner, AccessControlEnumerable {
*
*/
function requestBurnMyStETH(uint256 _stETHAmountToBurn) external onlyRole(REQUEST_BURN_MY_STETH_ROLE) {
STETH.transferFrom(msg.sender, address(this), _stETHAmountToBurn);
uint256 sharesAmount = STETH.getSharesByPooledEth(_stETHAmountToBurn);
LIDO.transferFrom(msg.sender, address(this), _stETHAmountToBurn);
uint256 sharesAmount = LIDO.getSharesByPooledEth(_stETHAmountToBurn);
_requestBurn(sharesAmount, _stETHAmountToBurn, false /* _isCover */);
}

Expand All @@ -216,7 +220,7 @@ contract Burner is IBurner, AccessControlEnumerable {
*
*/
function requestBurnShares(address _from, uint256 _sharesAmountToBurn) external onlyRole(REQUEST_BURN_SHARES_ROLE) {
uint256 stETHAmount = STETH.transferSharesFrom(_from, address(this), _sharesAmountToBurn);
uint256 stETHAmount = LIDO.transferSharesFrom(_from, address(this), _sharesAmountToBurn);
_requestBurn(_sharesAmountToBurn, stETHAmount, false /* _isCover */);
}

Expand All @@ -229,11 +233,11 @@ contract Burner is IBurner, AccessControlEnumerable {
uint256 excessStETH = getExcessStETH();

if (excessStETH > 0) {
uint256 excessSharesAmount = STETH.getSharesByPooledEth(excessStETH);
uint256 excessSharesAmount = LIDO.getSharesByPooledEth(excessStETH);

emit ExcessStETHRecovered(msg.sender, excessStETH, excessSharesAmount);

STETH.transfer(LOCATOR.treasury(), excessStETH);
LIDO.transfer(LOCATOR.treasury(), excessStETH);
}
}

Expand All @@ -253,7 +257,7 @@ contract Burner is IBurner, AccessControlEnumerable {
*/
function recoverERC20(address _token, uint256 _amount) external {
if (_amount == 0) revert ZeroRecoveryAmount();
if (_token == address(STETH)) revert StETHRecoveryWrongFunc();
if (_token == address(LIDO)) revert StETHRecoveryWrongFunc();

emit ERC20Recovered(msg.sender, _token, _amount);

Expand All @@ -268,7 +272,7 @@ contract Burner is IBurner, AccessControlEnumerable {
* @param _tokenId minted token id
*/
function recoverERC721(address _token, uint256 _tokenId) external {
if (_token == address(STETH)) revert StETHRecoveryWrongFunc();
if (_token == address(LIDO)) revert StETHRecoveryWrongFunc();

emit ERC721Recovered(msg.sender, _token, _tokenId);

Expand Down Expand Up @@ -307,7 +311,7 @@ contract Burner is IBurner, AccessControlEnumerable {
uint256 sharesToBurnNowForCover = Math.min(_sharesToBurn, memCoverSharesBurnRequested);

totalCoverSharesBurnt += sharesToBurnNowForCover;
uint256 stETHToBurnNowForCover = STETH.getPooledEthByShares(sharesToBurnNowForCover);
uint256 stETHToBurnNowForCover = LIDO.getPooledEthByShares(sharesToBurnNowForCover);
emit StETHBurnt(true /* isCover */, stETHToBurnNowForCover, sharesToBurnNowForCover);

coverSharesBurnRequested -= sharesToBurnNowForCover;
Expand All @@ -320,14 +324,15 @@ contract Burner is IBurner, AccessControlEnumerable {
);

totalNonCoverSharesBurnt += sharesToBurnNowForNonCover;
uint256 stETHToBurnNowForNonCover = STETH.getPooledEthByShares(sharesToBurnNowForNonCover);
uint256 stETHToBurnNowForNonCover = LIDO.getPooledEthByShares(sharesToBurnNowForNonCover);
emit StETHBurnt(false /* isCover */, stETHToBurnNowForNonCover, sharesToBurnNowForNonCover);

nonCoverSharesBurnRequested -= sharesToBurnNowForNonCover;
sharesToBurnNow += sharesToBurnNowForNonCover;
}

STETH.burnShares(address(this), _sharesToBurn);

LIDO.burnShares(_sharesToBurn);
assert(sharesToBurnNow == _sharesToBurn);
}

Expand Down Expand Up @@ -359,12 +364,12 @@ contract Burner is IBurner, AccessControlEnumerable {
* Returns the stETH amount belonging to the burner contract address but not marked for burning.
*/
function getExcessStETH() public view returns (uint256) {
return STETH.getPooledEthByShares(_getExcessStETHShares());
return LIDO.getPooledEthByShares(_getExcessStETHShares());
}

function _getExcessStETHShares() internal view returns (uint256) {
uint256 sharesBurnRequested = (coverSharesBurnRequested + nonCoverSharesBurnRequested);
uint256 totalShares = STETH.sharesOf(address(this));
uint256 totalShares = LIDO.sharesOf(address(this));

// sanity check, don't revert
if (totalShares <= sharesBurnRequested) {
Expand Down
36 changes: 6 additions & 30 deletions test/0.4.24/contracts/StETH__Harness.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,6 @@ pragma solidity 0.4.24;
import {StETH} from "contracts/0.4.24/StETH.sol";

contract StETH__Harness is StETH {
address private mock__minter;
address private mock__burner;
bool private mock__shouldUseSuperGuards;

uint256 private totalPooledEther;

constructor(address _holder) public payable {
Expand All @@ -29,35 +25,15 @@ contract StETH__Harness is StETH {
totalPooledEther = _totalPooledEther;
}

function mock__setMinter(address _minter) public {
mock__minter = _minter;
}

function mock__setBurner(address _burner) public {
mock__burner = _burner;
}

function mock__useSuperGuards(bool _shouldUseSuperGuards) public {
mock__shouldUseSuperGuards = _shouldUseSuperGuards;
}

function _isMinter(address _address) internal view returns (bool) {
if (mock__shouldUseSuperGuards) {
return super._isMinter(_address);
}

return _address == mock__minter;
function harness__mintInitialShares(uint256 _sharesAmount) public {
_mintInitialShares(_sharesAmount);
}

function _isBurner(address _address) internal view returns (bool) {
if (mock__shouldUseSuperGuards) {
return super._isBurner(_address);
}

return _address == mock__burner;
function harness__mintShares(address _recipient, uint256 _sharesAmount) public {
_mintShares(_recipient, _sharesAmount);
}

function harness__mintInitialShares(uint256 _sharesAmount) public {
_mintInitialShares(_sharesAmount);
function burnShares(uint256 _amount) external {
_burnShares(msg.sender, _amount);
}
}
Loading
Loading