forbid multiple rebases in the same block
Signed-off-by: Uncle Fatso <uncle.fatso@ghostchain.io>
This commit is contained in:
parent
b586b07c24
commit
ebd23d9829
@ -31,6 +31,8 @@ contract GhostStaking is IStaking, GhostAccessControlled {
|
|||||||
address public gatekeeper;
|
address public gatekeeper;
|
||||||
address public warmup;
|
address public warmup;
|
||||||
|
|
||||||
|
uint256 private _lastRebaseBlock;
|
||||||
|
|
||||||
mapping(address => bool) public locks;
|
mapping(address => bool) public locks;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@ -55,6 +57,7 @@ contract GhostStaking is IStaking, GhostAccessControlled {
|
|||||||
|
|
||||||
GhostWarmup newWarmup = new GhostWarmup(_ghst);
|
GhostWarmup newWarmup = new GhostWarmup(_ghst);
|
||||||
warmup = address(newWarmup);
|
warmup = address(newWarmup);
|
||||||
|
_lastRebaseBlock = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
function stake(
|
function stake(
|
||||||
@ -165,7 +168,7 @@ contract GhostStaking is IStaking, GhostAccessControlled {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function rebase() public override returns (uint256 bounty) {
|
function rebase() public override returns (uint256 bounty) {
|
||||||
if (epoch.end <= block.timestamp) {
|
if (epoch.end <= block.timestamp && block.number > _lastRebaseBlock) {
|
||||||
ISTNK(stnk).rebase(epoch.distribute, epoch.number);
|
ISTNK(stnk).rebase(epoch.distribute, epoch.number);
|
||||||
|
|
||||||
unchecked {
|
unchecked {
|
||||||
@ -181,6 +184,7 @@ contract GhostStaking is IStaking, GhostAccessControlled {
|
|||||||
uint256 balance = IERC20(ftso).balanceOf(address(this));
|
uint256 balance = IERC20(ftso).balanceOf(address(this));
|
||||||
uint256 extra = ISTNK(stnk).circulatingSupply() + bounty;
|
uint256 extra = ISTNK(stnk).circulatingSupply() + bounty;
|
||||||
|
|
||||||
|
_lastRebaseBlock = block.number;
|
||||||
epoch.distribute = balance > extra ? balance - extra : 0;
|
epoch.distribute = balance > extra ? balance - extra : 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -335,6 +335,7 @@ contract GhostBondDepositoryTest is Test {
|
|||||||
depository.deposit(0, amount, type(uint256).max, BOB, BOB);
|
depository.deposit(0, amount, type(uint256).max, BOB, BOB);
|
||||||
|
|
||||||
skip(DEPOSIT_INTERVAL);
|
skip(DEPOSIT_INTERVAL);
|
||||||
|
vm.roll(block.number + 1);
|
||||||
staking.rebase();
|
staking.rebase();
|
||||||
|
|
||||||
vm.startPrank(ALICE);
|
vm.startPrank(ALICE);
|
||||||
@ -410,6 +411,7 @@ contract GhostBondDepositoryTest is Test {
|
|||||||
assertEq(ghst.balanceOf(address(depository)), 0);
|
assertEq(ghst.balanceOf(address(depository)), 0);
|
||||||
|
|
||||||
skip(DEPOSIT_INTERVAL);
|
skip(DEPOSIT_INTERVAL);
|
||||||
|
vm.roll(block.number + 1);
|
||||||
staking.rebase();
|
staking.rebase();
|
||||||
|
|
||||||
depository.redeemAll(ALICE, true);
|
depository.redeemAll(ALICE, true);
|
||||||
|
|||||||
@ -16,6 +16,27 @@ import {GhostBondingCalculator} from "../../src/StandardBondingCalculator.sol";
|
|||||||
import {ITreasury} from "../../src/interfaces/ITreasury.sol";
|
import {ITreasury} from "../../src/interfaces/ITreasury.sol";
|
||||||
import {SafeERC20} from "@openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol";
|
import {SafeERC20} from "@openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol";
|
||||||
|
|
||||||
|
contract RebaseBatcher {
|
||||||
|
address public immutable STAKING;
|
||||||
|
|
||||||
|
constructor(address _staking) {
|
||||||
|
STAKING = _staking;
|
||||||
|
}
|
||||||
|
|
||||||
|
function multipleRebases(uint256 times) external returns (uint256) {
|
||||||
|
return _rebase(times);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _rebase(uint256 times) internal returns (uint256) {
|
||||||
|
if (times == 0) return 0;
|
||||||
|
|
||||||
|
(bool success, ) = STAKING.call(abi.encodeWithSignature("rebase()"));
|
||||||
|
require(success, "Batch rebase failed");
|
||||||
|
|
||||||
|
return 1 + _rebase(times - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
contract StakingTest is Test {
|
contract StakingTest is Test {
|
||||||
using SafeERC20 for Stinky;
|
using SafeERC20 for Stinky;
|
||||||
|
|
||||||
@ -92,6 +113,7 @@ contract StakingTest is Test {
|
|||||||
gatekeeper = new Gatekeeper(address(staking), 0, 0, 0, 0, 0);
|
gatekeeper = new Gatekeeper(address(staking), 0, 0, 0, 0, 0);
|
||||||
calculator = new GhostBondingCalculator(address(ftso), 1, 1);
|
calculator = new GhostBondingCalculator(address(ftso), 1, 1);
|
||||||
vm.stopPrank();
|
vm.stopPrank();
|
||||||
|
vm.roll(block.number + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
function test_correctAfterConstruction() public view {
|
function test_correctAfterConstruction() public view {
|
||||||
@ -537,11 +559,13 @@ contract StakingTest is Test {
|
|||||||
|
|
||||||
(,, uint48 end,) = staking.epoch();
|
(,, uint48 end,) = staking.epoch();
|
||||||
skip(end);
|
skip(end);
|
||||||
|
vm.roll(block.number + 1);
|
||||||
|
|
||||||
vm.startPrank(ALICE);
|
vm.startPrank(ALICE);
|
||||||
ftso.approve(address(staking), type(uint256).max);
|
ftso.approve(address(staking), type(uint256).max);
|
||||||
staking.stake(AMOUNT, ALICE, true, true);
|
staking.stake(AMOUNT, ALICE, true, true);
|
||||||
vm.stopPrank();
|
vm.stopPrank();
|
||||||
|
vm.roll(block.number + 1);
|
||||||
|
|
||||||
uint256 postBounty = AMOUNT + bounty;
|
uint256 postBounty = AMOUNT + bounty;
|
||||||
|
|
||||||
@ -666,7 +690,6 @@ contract StakingTest is Test {
|
|||||||
vm.prank(BOB);
|
vm.prank(BOB);
|
||||||
staking.breakout(receiver, payout);
|
staking.breakout(receiver, payout);
|
||||||
|
|
||||||
|
|
||||||
uint256 requestedPayout = 1;
|
uint256 requestedPayout = 1;
|
||||||
uint256 expectedPayout = 0;
|
uint256 expectedPayout = 0;
|
||||||
while (expectedPayout < payout / 2) {
|
while (expectedPayout < payout / 2) {
|
||||||
@ -703,6 +726,30 @@ contract StakingTest is Test {
|
|||||||
assertEq(newDeposit, ftso.balanceOf(ALICE));
|
assertEq(newDeposit, ftso.balanceOf(ALICE));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function test_revertDuringReentrancyRebase() public {
|
||||||
|
RebaseBatcher rebaseBatcher = new RebaseBatcher(address(staking));
|
||||||
|
uint256 startBlock = block.number;
|
||||||
|
uint256 passedRebases = 69;
|
||||||
|
skip((1 + passedRebases) * EPOCH_LENGTH);
|
||||||
|
|
||||||
|
(, uint48 prevNumber,,) = staking.epoch();
|
||||||
|
rebaseBatcher.multipleRebases(passedRebases);
|
||||||
|
(, uint48 currNumber,,) = staking.epoch();
|
||||||
|
assertEq(currNumber, prevNumber + 1);
|
||||||
|
|
||||||
|
uint256 i;
|
||||||
|
for (; i < passedRebases;) {
|
||||||
|
vm.roll(block.number + 1);
|
||||||
|
(,uint48 number,,) = staking.epoch();
|
||||||
|
rebaseBatcher.multipleRebases(1);
|
||||||
|
(,currNumber,,) = staking.epoch();
|
||||||
|
assertEq(currNumber, number + 1);
|
||||||
|
unchecked { ++i; }
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEq(currNumber, startBlock + passedRebases);
|
||||||
|
}
|
||||||
|
|
||||||
function _mintAndApprove(address who, uint256 value) internal {
|
function _mintAndApprove(address who, uint256 value) internal {
|
||||||
vm.prank(VAULT);
|
vm.prank(VAULT);
|
||||||
ftso.mint(who, value);
|
ftso.mint(who, value);
|
||||||
|
|||||||
@ -210,6 +210,7 @@ contract StakingDistributorTest is Test {
|
|||||||
assertEq(distributor.rewardRate(), TEN_PERCENT);
|
assertEq(distributor.rewardRate(), TEN_PERCENT);
|
||||||
(,, uint256 end,) = staking.epoch();
|
(,, uint256 end,) = staking.epoch();
|
||||||
skip(end);
|
skip(end);
|
||||||
|
vm.roll(block.number + 1);
|
||||||
staking.rebase();
|
staking.rebase();
|
||||||
assertEq(distributor.rewardRate(), TEN_PERCENT + 69);
|
assertEq(distributor.rewardRate(), TEN_PERCENT + 69);
|
||||||
}
|
}
|
||||||
@ -234,6 +235,7 @@ contract StakingDistributorTest is Test {
|
|||||||
assertEq(distributor.rewardRate(), TEN_PERCENT);
|
assertEq(distributor.rewardRate(), TEN_PERCENT);
|
||||||
(,, uint256 end,) = staking.epoch();
|
(,, uint256 end,) = staking.epoch();
|
||||||
skip(end);
|
skip(end);
|
||||||
|
vm.roll(block.number + 1);
|
||||||
staking.rebase();
|
staking.rebase();
|
||||||
assertEq(distributor.rewardRate(), 1337);
|
assertEq(distributor.rewardRate(), 1337);
|
||||||
}
|
}
|
||||||
@ -246,6 +248,7 @@ contract StakingDistributorTest is Test {
|
|||||||
assertEq(distributor.rewardRate(), TEN_PERCENT);
|
assertEq(distributor.rewardRate(), TEN_PERCENT);
|
||||||
(,, uint256 end,) = staking.epoch();
|
(,, uint256 end,) = staking.epoch();
|
||||||
skip(end);
|
skip(end);
|
||||||
|
vm.roll(block.number + 1);
|
||||||
staking.rebase();
|
staking.rebase();
|
||||||
assertEq(distributor.rewardRate(), 1337);
|
assertEq(distributor.rewardRate(), 1337);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user