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

Add mock contracts for StEth, WstEth, WithdrawalQueue for testing the DG setup on Holesky #143

Open
wants to merge 4 commits into
base: develop-archive
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
12 changes: 12 additions & 0 deletions scripts/deploy/Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,18 @@ anvil --fork-url https://<mainnet or holesky>.infura.io/v3/<YOUR_API_KEY> --bloc
forge script scripts/deploy/DeployConfigurable.s.sol:DeployConfigurable --fork-url https://holesky.infura.io/v3/<YOUR_API_KEY> --broadcast --account Deployer1 --sender <DEPLOYER1_ADDRESS> --verify
```

5. [Testnet and mainnet deployment only] Run Etherscan verification for Escrow contract

The Escrow contract is deployed internally by DualGovernance contract, so it can't be verified automatically during the initial deployment and requires manual verification afterward. To run Etherscan verification:

a. Query the deployed DualGovernance contract instance for ESCROW_MASTER_COPY address.

b. Run Etherscan verification (for example on a Holesky testnet)

```
forge verify-contract --chain holesky --verifier-url https://api-holesky.etherscan.io/api --watch --constructor-args $(cast abi-encode "Escrow(address,address,address,address,uint256)" <ST_ETH_ADDRESS> <WST_ETH_ADDRESS> <WITHDRAWAL_QUEUE_ADDRESS> <DUAL_GOVERNANCE_ADDRESS> <MIN_WITHDRAWALS_BATCH_SIZE>) <ESCROW_MASTER_COPY> contracts/Escrow.sol:Escrow
```

### Running the verification script

1. Set up the required env variables in the .env file
Expand Down
58 changes: 58 additions & 0 deletions scripts/lido-mocks/DeployHoleskyLidoMocks.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;

/* solhint-disable no-console */

import {Script} from "forge-std/Script.sol";
import {console} from "forge-std/console.sol";
import {StETHMock} from "./StETHMock.sol";
import {WstETHMock} from "./WstETHMock.sol";
import {UnsafeWithdrawalQueueMock} from "./UnsafeWithdrawalQueueMock.sol";

struct DeployedContracts {
address stETH;
address wstETH;
address withdrawalQueue;
}

contract DeployHoleskyLidoMocks is Script {
error DoNotRunThisOnMainnet(uint256 currentChainId);

address private deployer;

function run() external {
if (1 == block.chainid) {
revert DoNotRunThisOnMainnet(block.chainid);
}

deployer = msg.sender;
vm.label(deployer, "DEPLOYER");

vm.startBroadcast();

DeployedContracts memory res = deployLidoMockContracts();

vm.stopBroadcast();

printAddresses(res);
}

function deployLidoMockContracts() internal returns (DeployedContracts memory res) {
StETHMock stETH = new StETHMock();
WstETHMock wstETH = new WstETHMock(stETH);
UnsafeWithdrawalQueueMock withdrawalQueue = new UnsafeWithdrawalQueueMock(address(stETH), payable(deployer));

stETH.mint(deployer, 100 gwei);

res.stETH = address(stETH);
res.wstETH = address(wstETH);
res.withdrawalQueue = address(withdrawalQueue);
}

function printAddresses(DeployedContracts memory res) internal pure {
console.log("Lido mocks deployed successfully");
console.log("StETH address", res.stETH);
console.log("WstETH address", res.wstETH);
console.log("WithdrawalQueue address", res.withdrawalQueue);
}
}
13 changes: 13 additions & 0 deletions scripts/lido-mocks/Readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Lido contracts mocks for Dual Governance testing on Holesky

### Deploying

With the local fork (Anvil):
```
forge script scripts/lido-mocks/DeployHoleskyLidoMocks.s.sol:DeployHoleskyLidoMocks --fork-url http://localhost:8545 --broadcast --account Deployer1 --sender <DEPLOYER1_ADDRESS>
```

On a testnet (with Etherscan verification):
```
forge script scripts/lido-mocks/DeployHoleskyLidoMocks.s.sol:DeployHoleskyLidoMocks --fork-url https://holesky.infura.io/v3/<YOUR_API_KEY> --broadcast --account Deployer1 --sender <DEPLOYER1_ADDRESS> --verify
```
193 changes: 193 additions & 0 deletions scripts/lido-mocks/StETHBase.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
// SPDX-FileCopyrightText: 2023 Lido <[email protected]>
// SPDX-License-Identifier: GPL-3.0

pragma solidity 0.8.26;

import {IStETH} from "contracts/interfaces/IStETH.sol";

/* solhint-disable custom-errors */

abstract contract StETHBase is IStETH {
address internal constant INITIAL_TOKEN_HOLDER = address(0xdead);
uint256 internal constant INFINITE_ALLOWANCE = ~uint256(0);

mapping(address => uint256) private shares;
mapping(address => mapping(address => uint256)) private allowances;

event TransferShares(address indexed from, address indexed to, uint256 sharesValue);

event SharesBurnt(
address indexed account, uint256 preRebaseTokenAmount, uint256 postRebaseTokenAmount, uint256 sharesAmount
);

uint256 internal _totalPooledEther;
uint256 internal _totalShares;

function name() external pure virtual returns (string memory);

function symbol() external pure virtual returns (string memory);

function decimals() external pure returns (uint8) {
return 18;
}

function totalSupply() external view returns (uint256) {
return _totalPooledEther;
}

function getTotalPooledEther() external view returns (uint256) {
return _totalPooledEther;
}

function balanceOf(address _account) external view returns (uint256) {
return getPooledEthByShares(_sharesOf(_account));
}

function transfer(address _recipient, uint256 _amount) external returns (bool) {
_transfer(msg.sender, _recipient, _amount);
return true;
}

function allowance(address _owner, address _spender) external view returns (uint256) {
return allowances[_owner][_spender];
}

function approve(address _spender, uint256 _amount) external returns (bool) {
_approve(msg.sender, _spender, _amount);
return true;
}

function transferFrom(address _sender, address _recipient, uint256 _amount) external returns (bool) {
_spendAllowance(_sender, msg.sender, _amount);
_transfer(_sender, _recipient, _amount);
return true;
}

function increaseAllowance(address _spender, uint256 _addedValue) external returns (bool) {
_approve(msg.sender, _spender, allowances[msg.sender][_spender] + _addedValue);
return true;
}

function decreaseAllowance(address _spender, uint256 _subtractedValue) external returns (bool) {
uint256 currentAllowance = allowances[msg.sender][_spender];
require(currentAllowance >= _subtractedValue, "ALLOWANCE_BELOW_ZERO");
_approve(msg.sender, _spender, currentAllowance - _subtractedValue);
return true;
}

function getTotalShares() external view returns (uint256) {
return _totalShares;
}

function sharesOf(address _account) external view returns (uint256) {
return _sharesOf(_account);
}

function getSharesByPooledEth(uint256 _ethAmount) public view returns (uint256) {
return (_ethAmount * _totalShares) / _totalPooledEther;
}

function getPooledEthByShares(uint256 _sharesAmount) public view returns (uint256) {
return (_sharesAmount * _totalPooledEther) / _totalShares;
}

function transferShares(address _recipient, uint256 _sharesAmount) external /* returns (uint256) */ {
_transferShares(msg.sender, _recipient, _sharesAmount);
uint256 tokensAmount = getPooledEthByShares(_sharesAmount);
_emitTransferEvents(msg.sender, _recipient, tokensAmount, _sharesAmount);
// return tokensAmount;
}

function transferSharesFrom(
address _sender,
address _recipient,
uint256 _sharesAmount
) external returns (uint256) {
uint256 tokensAmount = getPooledEthByShares(_sharesAmount);
_spendAllowance(_sender, msg.sender, tokensAmount);
_transferShares(_sender, _recipient, _sharesAmount);
_emitTransferEvents(_sender, _recipient, tokensAmount, _sharesAmount);
return tokensAmount;
}

function _transfer(address _sender, address _recipient, uint256 _amount) internal {
uint256 _sharesToTransfer = getSharesByPooledEth(_amount);
_transferShares(_sender, _recipient, _sharesToTransfer);
_emitTransferEvents(_sender, _recipient, _amount, _sharesToTransfer);
}

function _approve(address _owner, address _spender, uint256 _amount) internal {
require(_owner != address(0), "APPROVE_FROM_ZERO_ADDR");
require(_spender != address(0), "APPROVE_TO_ZERO_ADDR");

allowances[_owner][_spender] = _amount;
emit Approval(_owner, _spender, _amount);
}

function _spendAllowance(address _owner, address _spender, uint256 _amount) internal {
uint256 currentAllowance = allowances[_owner][_spender];
if (currentAllowance != INFINITE_ALLOWANCE) {
require(currentAllowance >= _amount, "ALLOWANCE_EXCEEDED");
_approve(_owner, _spender, currentAllowance - _amount);
}
}

function _sharesOf(address _account) internal view returns (uint256) {
return shares[_account];
}

function _transferShares(address _sender, address _recipient, uint256 _sharesAmount) internal {
require(_sender != address(0), "TRANSFER_FROM_ZERO_ADDR");
require(_recipient != address(0), "TRANSFER_TO_ZERO_ADDR");
require(_recipient != address(this), "TRANSFER_TO_STETH_CONTRACT");

// Pausable functionality not needed for this implementation // _whenNotStopped();

uint256 currentSenderShares = shares[_sender];
require(_sharesAmount <= currentSenderShares, "BALANCE_EXCEEDED");

shares[_sender] = currentSenderShares - _sharesAmount;
shares[_recipient] = shares[_recipient] + _sharesAmount;
}

function _mintShares(address _recipient, uint256 _sharesAmount) internal returns (uint256 newTotalShares) {
require(_recipient != address(0), "MINT_TO_ZERO_ADDR");

newTotalShares = _totalShares + _sharesAmount;
_totalShares = newTotalShares;

shares[_recipient] += _sharesAmount;
}

function _burnShares(address _account, uint256 _sharesAmount) internal returns (uint256 newTotalShares) {
require(_account != address(0), "BURN_FROM_ZERO_ADDR");

uint256 accountShares = shares[_account];
require(_sharesAmount <= accountShares, "BALANCE_EXCEEDED");

uint256 preRebaseTokenAmount = getPooledEthByShares(_sharesAmount);

newTotalShares = _totalShares - _sharesAmount;
_totalShares = newTotalShares;

shares[_account] = accountShares - _sharesAmount;

uint256 postRebaseTokenAmount = getPooledEthByShares(_sharesAmount);

emit SharesBurnt(_account, preRebaseTokenAmount, postRebaseTokenAmount, _sharesAmount);
}

function _emitTransferEvents(address _from, address _to, uint256 _tokenAmount, uint256 _sharesAmount) internal {
emit Transfer(_from, _to, _tokenAmount);
emit TransferShares(_from, _to, _sharesAmount);
}

function _emitTransferAfterMintingShares(address _to, uint256 _sharesAmount) internal {
_emitTransferEvents(address(0), _to, getPooledEthByShares(_sharesAmount), _sharesAmount);
}

function _mintInitialShares(uint256 _sharesAmount) internal {
_mintShares(INITIAL_TOKEN_HOLDER, _sharesAmount);
_emitTransferAfterMintingShares(INITIAL_TOKEN_HOLDER, _sharesAmount);
}
}
44 changes: 44 additions & 0 deletions scripts/lido-mocks/StETHMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;

import {PercentD16, HUNDRED_PERCENT_D16} from "contracts/types/PercentD16.sol";
import {StETHBase} from "./StETHBase.sol";

contract StETHMock is StETHBase {
constructor() {
_totalPooledEther = 100 wei;
_mintInitialShares(100 wei);
}

function name() external pure override returns (string memory) {
return "StETHMock";
}

function symbol() external pure override returns (string memory) {
return "MStETH";
}

function rebaseTotalPooledEther(PercentD16 rebaseFactor) public {
_totalPooledEther = rebaseFactor.toUint256() * _totalPooledEther / HUNDRED_PERCENT_D16;
}

function setTotalPooledEther(uint256 ethAmount) public {
_totalPooledEther = ethAmount;
}

function mint(address account, uint256 ethAmount) external {
uint256 sharesAmount = getSharesByPooledEth(ethAmount);

_mintShares(account, sharesAmount);
_totalPooledEther += ethAmount;

_emitTransferAfterMintingShares(account, sharesAmount);
}

function burn(address account, uint256 ethAmount) external {
uint256 sharesToBurn = this.getSharesByPooledEth(ethAmount);
_burnShares(account, sharesToBurn);
_totalPooledEther -= ethAmount;
_emitTransferEvents(account, address(0), ethAmount, sharesToBurn);
}
}
Loading