11// SPDX-License-Identifier: Apache-2.0
22pragma solidity 0.8.20 ;
33
4+ import { ERC20 } from "openzeppelin/token/ERC20/ERC20.sol " ;
5+ import { Authority } from "solmate/auth/Auth.sol " ;
46import { MultiRolesAuthority } from "solmate/auth/authorities/MultiRolesAuthority.sol " ;
57import { 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 {
0 commit comments