Skip to content

Commit 0db25cd

Browse files
Re-wrote the MockLido contract to store its balances as shares (#1074)
* Updated MockLido * update version --------- Co-authored-by: John McClure (pickleback) <[email protected]>
1 parent 101627a commit 0db25cd

File tree

3 files changed

+206
-46
lines changed

3 files changed

+206
-46
lines changed

contracts/src/libraries/Constants.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ pragma solidity 0.8.20;
55
address constant ETH = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
66

77
/// @dev The version of the contracts.
8-
string constant VERSION = "v1.0.13";
8+
string constant VERSION = "v1.0.14";
99

1010
/// @dev The number of targets that must be deployed for a full deployment.
1111
uint256 constant NUM_TARGETS = 4;

contracts/test/MockLido.sol

Lines changed: 200 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
// SPDX-License-Identifier: Apache-2.0
22
pragma solidity 0.8.20;
33

4+
import { ERC20 } from "openzeppelin/token/ERC20/ERC20.sol";
5+
import { Authority } from "solmate/auth/Auth.sol";
46
import { MultiRolesAuthority } from "solmate/auth/authorities/MultiRolesAuthority.sol";
57
import { FixedPointMath } from "../src/libraries/FixedPointMath.sol";
6-
import { ERC20Mintable } from "./ERC20Mintable.sol";
78

89
/// @author DELV
910
/// @title MockLido
@@ -14,37 +15,172 @@ import { ERC20Mintable } from "./ERC20Mintable.sol";
1415
/// @custom:disclaimer The language used in this code is for coding convenience
1516
/// only, and is not intended to, and does not, have any
1617
/// particular legal or regulatory significance.
17-
contract MockLido is MultiRolesAuthority, ERC20Mintable {
18+
contract MockLido is MultiRolesAuthority, ERC20 {
1819
using FixedPointMath for uint256;
1920

21+
// Admin State
22+
bool public immutable isCompetitionMode;
23+
uint256 public maxMintAmount;
24+
mapping(address => bool) public isUnrestricted;
25+
2026
// Interest State
2127
uint256 internal _rate;
2228
uint256 internal _lastUpdated;
2329

2430
// Lido State
25-
uint256 totalPooledEther;
26-
uint256 totalShares;
31+
uint256 internal totalPooledEther;
32+
uint256 internal totalShares;
33+
34+
// The shares that each account owns.
35+
mapping(address => uint256) public sharesOf;
36+
37+
// Emitted when shares are transferred.
38+
event TransferShares(
39+
address indexed from,
40+
address indexed to,
41+
uint256 sharesValue
42+
);
2743

2844
constructor(
2945
uint256 _initialRate,
3046
address _admin,
3147
bool _isCompetitionMode,
3248
uint256 _maxMintAmount
3349
)
34-
ERC20Mintable(
35-
"Liquid staked Ether 2.0",
36-
"stETH",
37-
18,
38-
_admin,
39-
_isCompetitionMode,
40-
_maxMintAmount
41-
)
50+
ERC20("Liquid staked Ether 2.0", "stETH")
51+
MultiRolesAuthority(_admin, Authority(address(address(this))))
4252
{
53+
// Store the initial rate and the last updated time.
4354
_rate = _initialRate;
4455
_lastUpdated = block.timestamp;
56+
57+
// Update the admin settings.
58+
isCompetitionMode = _isCompetitionMode;
59+
maxMintAmount = _maxMintAmount;
60+
}
61+
62+
/// Admin ///
63+
64+
modifier requiresAuthDuringCompetition() {
65+
if (isCompetitionMode) {
66+
require(
67+
isAuthorized(msg.sender, msg.sig),
68+
"MockLido: not authorized"
69+
);
70+
}
71+
_;
72+
}
73+
74+
function mint(uint256 _amount) external requiresAuthDuringCompetition {
75+
_mintShares(msg.sender, _amount);
76+
}
77+
78+
function mint(
79+
address _recipient,
80+
uint256 _amount
81+
) external requiresAuthDuringCompetition {
82+
_mintShares(_recipient, _amount);
83+
}
84+
85+
function _mintShares(address _recipient, uint256 _amount) internal {
86+
// If the sender is restricted, ensure that the mint amount is less than
87+
// the maximum.
88+
if (!isUnrestricted[msg.sender]) {
89+
require(_amount <= maxMintAmount, "MockLido: Invalid mint amount");
90+
}
91+
92+
// Credit shares to the recipient.
93+
uint256 sharesAmount;
94+
if (getTotalShares() == 0) {
95+
sharesAmount = _amount;
96+
} else {
97+
sharesAmount = getSharesByPooledEth(_amount);
98+
}
99+
sharesOf[_recipient] += sharesAmount;
100+
101+
// Update the Lido state.
102+
totalPooledEther += _amount;
103+
totalShares += sharesAmount;
104+
}
105+
106+
function burn(uint256 amount) external requiresAuthDuringCompetition {
107+
_burnShares(msg.sender, amount);
108+
}
109+
110+
function burn(
111+
address _target,
112+
uint256 _amount
113+
) external requiresAuthDuringCompetition {
114+
_burnShares(_target, _amount);
115+
}
116+
117+
function _burnShares(address _target, uint256 _amount) internal {
118+
// Debit shares from the recipient.
119+
uint256 sharesAmount = getSharesByPooledEth(_amount);
120+
sharesOf[_target] -= sharesAmount;
121+
122+
// Update the Lido state.
123+
totalPooledEther -= _amount;
124+
totalShares -= sharesAmount;
125+
}
126+
127+
function setMaxMintAmount(
128+
uint256 _maxMintAmount
129+
) external requiresAuthDuringCompetition {
130+
maxMintAmount = _maxMintAmount;
131+
}
132+
133+
function setUnrestrictedMintStatus(
134+
address _target,
135+
bool _status
136+
) external requiresAuthDuringCompetition {
137+
isUnrestricted[_target] = _status;
138+
}
139+
140+
/// ERC20 Functions ///
141+
142+
function balanceOf(address _owner) public view override returns (uint256) {
143+
return getPooledEthByShares(sharesOf[_owner]);
45144
}
46145

47-
/// Overrides ///
146+
function transfer(
147+
address _recipient,
148+
uint256 _amount
149+
) public override returns (bool) {
150+
// Accrue interest.
151+
_accrue();
152+
153+
// Transfer the tokens.
154+
uint256 sharesAmount = getSharesByPooledEth(_amount);
155+
_transferShares(_recipient, sharesAmount);
156+
157+
// Emit the transfer events.
158+
emit Transfer(msg.sender, _recipient, _amount);
159+
emit TransferShares(msg.sender, _recipient, sharesAmount);
160+
161+
return true;
162+
}
163+
164+
function transferFrom(
165+
address _sender,
166+
address _recipient,
167+
uint256 _amount
168+
) public override returns (bool) {
169+
// Accrue interest.
170+
_accrue();
171+
172+
// Transfer the tokens.
173+
uint256 sharesAmount = getSharesByPooledEth(_amount);
174+
_transferSharesFrom(_sender, _recipient, sharesAmount);
175+
176+
// Emit the transfer events.
177+
emit Transfer(msg.sender, _recipient, _amount);
178+
emit TransferShares(msg.sender, _recipient, sharesAmount);
179+
180+
return true;
181+
}
182+
183+
/// stETH Functions ///
48184

49185
function submit(address) external payable returns (uint256) {
50186
// Accrue interest.
@@ -54,7 +190,7 @@ contract MockLido is MultiRolesAuthority, ERC20Mintable {
54190
if (getTotalShares() == 0) {
55191
totalShares = msg.value;
56192
totalPooledEther = msg.value;
57-
_mint(msg.sender, msg.value);
193+
sharesOf[msg.sender] += msg.value;
58194
return msg.value;
59195
}
60196

@@ -68,29 +204,40 @@ contract MockLido is MultiRolesAuthority, ERC20Mintable {
68204
totalPooledEther += msg.value;
69205
totalShares += shares;
70206

71-
// Mint the stETH tokens to the user.
72-
_mint(msg.sender, msg.value);
207+
// Mint shares to the user.
208+
sharesOf[msg.sender] += shares;
73209

74210
return shares;
75211
}
76212

77213
function transferShares(
78214
address _recipient,
79215
uint256 _sharesAmount
80-
) external returns (uint256) {
216+
) public returns (uint256) {
81217
// Accrue interest.
82218
_accrue();
83219

84-
// Calculate the amount of tokens that should be transferred.
85-
uint256 tokenAmount = _sharesAmount.mulDivDown(
86-
getTotalPooledEther(),
87-
getTotalShares()
88-
);
220+
// Transfer the shares.
221+
uint256 tokenAmount = _transferShares(_recipient, _sharesAmount);
89222

90-
// Transfer the tokens to the user.
91-
transfer(_recipient, tokenAmount);
223+
// Emit the transfer events.
224+
emit Transfer(msg.sender, _recipient, tokenAmount);
225+
emit TransferShares(msg.sender, _recipient, _sharesAmount);
92226

93-
return tokenAmount;
227+
return getPooledEthByShares(_sharesAmount);
228+
}
229+
230+
function _transferShares(
231+
address _recipient,
232+
uint256 _sharesAmount
233+
) internal returns (uint256) {
234+
// Debit shares from the sender.
235+
sharesOf[msg.sender] -= _sharesAmount;
236+
237+
// Credit shares to the recipient.
238+
sharesOf[_recipient] += _sharesAmount;
239+
240+
return getPooledEthByShares(_sharesAmount);
94241
}
95242

96243
function transferSharesFrom(
@@ -101,27 +248,47 @@ contract MockLido is MultiRolesAuthority, ERC20Mintable {
101248
// Accrue interest.
102249
_accrue();
103250

104-
// Calculate the amount of tokens that should be transferred.
105-
uint256 tokenAmount = _sharesAmount.mulDivDown(
106-
getTotalPooledEther(),
107-
getTotalShares()
251+
// Transfer the shares.
252+
uint256 tokenAmount = _transferSharesFrom(
253+
_sender,
254+
_recipient,
255+
_sharesAmount
108256
);
109257

110-
// Transfer the tokens to the user.
111-
transferFrom(_sender, _recipient, tokenAmount);
258+
// Emit the transfer events.
259+
emit Transfer(_sender, _recipient, tokenAmount);
260+
emit TransferShares(_sender, _recipient, _sharesAmount);
261+
262+
return tokenAmount;
263+
}
264+
265+
function _transferSharesFrom(
266+
address _sender,
267+
address _recipient,
268+
uint256 _sharesAmount
269+
) internal returns (uint256) {
270+
// Reduce the allowance.
271+
uint256 tokenAmount = getPooledEthByShares(_sharesAmount);
272+
_spendAllowance(_sender, msg.sender, tokenAmount);
273+
274+
// Debit shares from the sender.
275+
sharesOf[_sender] -= _sharesAmount;
276+
277+
// Credit shares to the recipient.
278+
sharesOf[_recipient] += _sharesAmount;
112279

113280
return tokenAmount;
114281
}
115282

116283
function getSharesByPooledEth(
117284
uint256 _ethAmount
118-
) external view returns (uint256) {
285+
) public view returns (uint256) {
119286
return _ethAmount.mulDivDown(getTotalShares(), getTotalPooledEther());
120287
}
121288

122289
function getPooledEthByShares(
123290
uint256 _sharesAmount
124-
) external view returns (uint256) {
291+
) public view returns (uint256) {
125292
return
126293
_sharesAmount.mulDivDown(getTotalPooledEther(), getTotalShares());
127294
}
@@ -138,11 +305,6 @@ contract MockLido is MultiRolesAuthority, ERC20Mintable {
138305
return totalShares;
139306
}
140307

141-
function sharesOf(address _account) external view returns (uint256) {
142-
uint256 tokenBalance = balanceOf[_account];
143-
return tokenBalance.mulDivDown(getTotalShares(), getTotalPooledEther());
144-
}
145-
146308
/// Mock ///
147309

148310
function setRate(uint256 _rate_) external requiresAuthDuringCompetition {

test/instances/steth/Sweep.t.sol

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -208,14 +208,12 @@ contract LeakyLido is MockLido {
208208
address to,
209209
uint256 amount
210210
) external returns (bool) {
211-
balanceOf[from] -= amount;
212-
213-
// Cannot overflow because the sum of all user
214-
// balances can't exceed the max uint256 value.
215-
unchecked {
216-
balanceOf[to] += amount;
217-
}
211+
// Transfer the shares.
212+
uint256 sharesAmount = getSharesByPooledEth(amount);
213+
sharesOf[from] -= sharesAmount;
214+
sharesOf[to] += sharesAmount;
218215

216+
// Emit an event.
219217
emit Transfer(from, to, amount);
220218

221219
return true;

0 commit comments

Comments
 (0)