Skip to content
This repository was archived by the owner on Dec 10, 2025. It is now read-only.

Commit 4d38d64

Browse files
committed
Added snapshot of exchange rate + search + updated power calculation
1 parent b7a48e9 commit 4d38d64

File tree

2 files changed

+121
-15
lines changed

2 files changed

+121
-15
lines changed

contracts/interfaces/IStakedTokenV3.sol

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ interface IStakedTokenV3 is IStakedToken {
1212

1313
function slash(address destination, uint256 amount) external;
1414

15+
function donate(uint256 amount) external;
16+
1517
function getMaxSlashablePercentage() external view returns (uint256);
1618

1719
function setMaxSlashablePercentage(uint256 percentage) external;

contracts/stake/StakedTokenV3.sol

Lines changed: 119 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)