@@ -44,6 +44,9 @@ contract StakedTokenV3 is StakedTokenV2, IStakedTokenV3, RoleManager {
4444
4545 address internal _claimHelper;
4646
47+ mapping (uint256 => Snapshot) internal _exchangeRateSnapshots;
48+ uint256 internal _countExchangeRateSnapshots;
49+
4750 modifier onlyAdmin {
4851 require (msg .sender == getAdmin (MAIN_ADMIN_ROLE), 'CALLER_NOT_MAIN_ADMIN ' );
4952 _;
@@ -69,6 +72,8 @@ contract StakedTokenV3 is StakedTokenV2, IStakedTokenV3, RoleManager {
6972 event CooldownPauseChanged (bool pause );
7073 event MaxSlashablePercentageChanged (uint256 newPercentage );
7174 event Slashed (address indexed destination , uint256 amount );
75+ event Donated (address indexed sender , uint256 amount );
76+ event EchangeRateSnapshotted (uint128 blockNumber , uint128 exchangeRate );
7277 event CooldownPauseAdminChanged (address indexed newAdmin );
7378 event SlashingAdminChanged (address indexed newAdmin );
7479 event ClaimHelperChanged (address indexed newClaimHelper );
@@ -301,21 +306,6 @@ contract StakedTokenV3 is StakedTokenV2, IStakedTokenV3, RoleManager {
301306 _redeem (from, to, redeemAmount);
302307 }
303308
304- /**
305- * @dev Calculates the exchange rate between the amount of STAKED_TOKEN and the the StakeToken total supply.
306- * Slashing will reduce the exchange rate. Supplying STAKED_TOKEN to the stake contract
307- * can replenish the slashed STAKED_TOKEN and bring the exchange rate back to 1
308- **/
309- function exchangeRate () public view override returns (uint256 ) {
310- uint256 currentSupply = totalSupply ();
311-
312- if (currentSupply == 0 ) {
313- return 1e18 ; //initial exchange rate is 1:1
314- }
315-
316- return STAKED_TOKEN.balanceOf (address (this )).mul (1e18 ).div (currentSupply);
317- }
318-
319309 /**
320310 * @dev Executes a slashing of the underlying of a certain amount, transferring the seized funds
321311 * to destination. Decreasing the amount of underlying will automatically adjust the exchange rate
@@ -330,10 +320,18 @@ contract StakedTokenV3 is StakedTokenV2, IStakedTokenV3, RoleManager {
330320 require (amount <= maxSlashable, 'INVALID_SLASHING_AMOUNT ' );
331321
332322 STAKED_TOKEN.safeTransfer (destination, amount);
323+ _snapshotExchangeRate ();
333324
334325 emit Slashed (destination, amount);
335326 }
336327
328+ function donate (uint256 amount ) external override {
329+ STAKED_TOKEN.safeTransferFrom (msg .sender , address (this ), amount);
330+ _snapshotExchangeRate ();
331+
332+ emit Donated (msg .sender , amount);
333+ }
334+
337335 /**
338336 * @dev Set the address of the contract with priviledge, the ClaimHelper contract
339337 * It speicifically enables to claim from several contracts at once
@@ -364,6 +362,21 @@ contract StakedTokenV3 is StakedTokenV2, IStakedTokenV3, RoleManager {
364362 emit MaxSlashablePercentageChanged (percentage);
365363 }
366364
365+ /**
366+ * @dev Calculates the exchange rate between the amount of STAKED_TOKEN and the the StakeToken total supply.
367+ * Slashing will reduce the exchange rate. Supplying STAKED_TOKEN to the stake contract
368+ * can replenish the slashed STAKED_TOKEN and bring the exchange rate back to 1
369+ **/
370+ function exchangeRate () public view override returns (uint256 ) {
371+ uint256 currentSupply = totalSupply ();
372+
373+ if (currentSupply == 0 ) {
374+ return 1e18 ; //initial exchange rate is 1:1
375+ }
376+
377+ return STAKED_TOKEN.balanceOf (address (this )).mul (1e18 ).div (currentSupply);
378+ }
379+
367380 /**
368381 * @dev returns the current address of the claimHelper Contract, contract with priviledge
369382 * It speicifically enables to claim from several contracts at once
@@ -394,6 +407,43 @@ contract StakedTokenV3 is StakedTokenV2, IStakedTokenV3, RoleManager {
394407 return REVISION ();
395408 }
396409
410+ function getPowerAtBlock (
411+ address user ,
412+ uint256 blockNumber ,
413+ DelegationType delegationType
414+ ) external view override returns (uint256 ) {
415+ (
416+ mapping (address => mapping (uint256 => Snapshot)) storage snapshots ,
417+ mapping (address => uint256 ) storage snapshotsCounts ,
418+
419+ ) = _getDelegationDataByType (delegationType);
420+
421+ return (
422+ _searchByBlockNumber (snapshots, snapshotsCounts, user, blockNumber)
423+ .mul (_searchExchangeRateByBlockNumber (blockNumber))
424+ .div (1e18 )
425+ );
426+ }
427+
428+ function getPowerCurrent (address user , DelegationType delegationType )
429+ external
430+ view
431+ override
432+ returns (uint256 )
433+ {
434+ (
435+ mapping (address => mapping (uint256 => Snapshot)) storage snapshots ,
436+ mapping (address => uint256 ) storage snapshotsCounts ,
437+
438+ ) = _getDelegationDataByType (delegationType);
439+
440+ return (
441+ _searchByBlockNumber (snapshots, snapshotsCounts, user, block .number ).mul (exchangeRate ()).div (
442+ 1e18
443+ )
444+ );
445+ }
446+
397447 function _claimRewards (
398448 address from ,
399449 address to ,
@@ -479,4 +529,58 @@ contract StakedTokenV3 is StakedTokenV2, IStakedTokenV3, RoleManager {
479529
480530 emit Redeem (from, to, amountToRedeem, underlyingToRedeem);
481531 }
532+
533+ function _snapshotExchangeRate () internal {
534+ uint128 currentBlock = uint128 (block .number );
535+ uint128 newExchangeRate = uint128 (exchangeRate ());
536+
537+ // Doing multiple operations in the same block
538+ if (
539+ _countExchangeRateSnapshots != 0 &&
540+ _exchangeRateSnapshots[_countExchangeRateSnapshots - 1 ].blockNumber == currentBlock
541+ ) {
542+ _exchangeRateSnapshots[_countExchangeRateSnapshots - 1 ].value = newExchangeRate;
543+ } else {
544+ _exchangeRateSnapshots[_countExchangeRateSnapshots] = Snapshot (currentBlock, newExchangeRate);
545+ _countExchangeRateSnapshots = _countExchangeRateSnapshots + 1 ;
546+ }
547+ emit EchangeRateSnapshotted (currentBlock, newExchangeRate);
548+ }
549+
550+ /**
551+ * @dev searches a exchange Rate by block number. Uses binary search.
552+ * @param blockNumber the block number being searched
553+ **/
554+ function _searchExchangeRateByBlockNumber (uint256 blockNumber ) internal view returns (uint256 ) {
555+ require (blockNumber <= block .number , 'INVALID_BLOCK_NUMBER ' );
556+
557+ if (_countExchangeRateSnapshots == 0 ) {
558+ return exchangeRate ();
559+ }
560+
561+ // First check most recent balance
562+ if (_exchangeRateSnapshots[_countExchangeRateSnapshots - 1 ].blockNumber <= blockNumber) {
563+ return _exchangeRateSnapshots[_countExchangeRateSnapshots - 1 ].value;
564+ }
565+
566+ // Next check implicit zero balance
567+ if (_exchangeRateSnapshots[0 ].blockNumber > blockNumber) {
568+ return 1e18 ; //initial exchange rate is 1:1
569+ }
570+
571+ uint256 lower = 0 ;
572+ uint256 upper = _countExchangeRateSnapshots - 1 ;
573+ while (upper > lower) {
574+ uint256 center = upper - (upper - lower) / 2 ; // ceil, avoiding overflow
575+ Snapshot memory snapshot = _exchangeRateSnapshots[center];
576+ if (snapshot.blockNumber == blockNumber) {
577+ return snapshot.value;
578+ } else if (snapshot.blockNumber < blockNumber) {
579+ lower = center;
580+ } else {
581+ upper = center - 1 ;
582+ }
583+ }
584+ return _exchangeRateSnapshots[lower].value;
585+ }
482586}
0 commit comments