Skip to content

Commit

Permalink
Merge pull request #885 from lidofinance/fix/lido-mintburning
Browse files Browse the repository at this point in the history
fix: extract mint/burning to Lido
  • Loading branch information
folkyatina authored Dec 2, 2024
2 parents a18c2f0 + fa8e84c commit 6d8426f
Show file tree
Hide file tree
Showing 8 changed files with 164 additions and 191 deletions.
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
/// @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);

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

0 comments on commit 6d8426f

Please sign in to comment.