Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(protocol): restrict prover staking changes to once per hour #14093

Merged
merged 1 commit into from
Jul 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 17 additions & 10 deletions packages/protocol/contracts/L1/ProverPool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,11 @@ contract ProverPool is EssentialContract, IProverPool {
// provide a capacity of at least 3600/32=112.
uint32 public constant MAX_CAPACITY_LOWER_BOUND = 128;
uint64 public constant EXIT_PERIOD = 1 weeks;
uint32 public constant SLASH_POINTS = 500; // basis points
uint32 public constant SLASH_POINTS = 25; // basis points or 0.25%
uint64 public constant MIN_STAKE_PER_CAPACITY = 10_000;
uint64 public constant MIN_SLASH_AMOUNT = 1e8; // 1 token
uint256 public constant MAX_NUM_PROVERS = 32;
uint256 public constant MIN_CHANGE_DELAY = 1 hours;

// Reserve more slots than necessary
Prover[1024] public provers; // provers[0] is never used
Expand All @@ -60,6 +61,7 @@ contract ProverPool is EssentialContract, IProverPool {
uint16 currentCapacity
);

error CHANGE_TOO_FREQUENT();
error INVALID_PARAMS();
error NO_MATURE_EXIT();
error PROVER_NOT_GOOD_ENOUGH();
Expand Down Expand Up @@ -180,20 +182,18 @@ contract ProverPool is EssentialContract, IProverPool {
// Withdraw first
_withdraw(msg.sender);
// Force this prover to fully exit
_exit(msg.sender);
_exit(msg.sender, true);
// Then stake again
if (amount == 0) {
if (rewardPerGas != 0 || maxCapacity != 0) {
revert INVALID_PARAMS();
}
} else {
if (amount != 0) {
_stake(msg.sender, amount, rewardPerGas, maxCapacity);
} else if (rewardPerGas != 0 || maxCapacity != 0) {
revert INVALID_PARAMS();
}
}

function exit() external nonReentrant {
_withdraw(msg.sender);
_exit(msg.sender);
_exit(msg.sender, true);
}

// Withdraws staked tokens back from matured an exit
Expand Down Expand Up @@ -289,7 +289,7 @@ contract ProverPool is EssentialContract, IProverPool {
address replaced = idToProver[proverId];
if (replaced != address(0)) {
_withdraw(replaced);
_exit(replaced);
_exit(replaced, false);
}
idToProver[proverId] = addr;
staker.proverId = proverId;
Expand All @@ -306,12 +306,19 @@ contract ProverPool is EssentialContract, IProverPool {
}

// Perform a full exit for the given address
function _exit(address addr) private {
function _exit(address addr, bool checkExitTimestamp) private {
Staker storage staker = stakers[addr];
if (staker.proverId == 0) return;

Prover memory prover = provers[staker.proverId];
if (prover.stakedAmount > 0) {
if (
checkExitTimestamp
&& block.timestamp <= staker.exitRequestedAt + MIN_CHANGE_DELAY
) {
revert CHANGE_TOO_FREQUENT();
}

staker.exitAmount += prover.stakedAmount;
staker.exitRequestedAt = uint64(block.timestamp);
staker.proverId = 0;
Expand Down
2 changes: 1 addition & 1 deletion packages/protocol/contracts/L1/TaikoConfig.sol
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ library TaikoConfig {
ethDepositGas: 21_000,
ethDepositMaxFee: 1 ether / 10,
// Group 5: tokenomics
rewardPerGasRange: 1000, // 10%
rewardPerGasRange: 2000, // 20%
rewardOpenMultipler: 200, // percentage
rewardOpenMaxCount: 2000
});
Expand Down
4 changes: 2 additions & 2 deletions packages/protocol/contracts/L1/libs/LibProposing.sol
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,8 @@ library LibProposing {
} else {
blk.assignedProver = assignedProver;

// Cap the reward to a range of [95%, 105%] * blk.feePerGas, if
// rewardPerGasRange is set to 5% (500 bp)
// Cap the reward to a range of [80%, 120%] * blk.feePerGas, if
// rewardPerGasRange is set to 20% (2000 bp)
uint32 diff = blk.feePerGas * config.rewardPerGasRange / 10_000;
blk.rewardPerGas = uint32(
uint256(rewardPerGas).min(state.feePerGas + diff).max(
Expand Down
4 changes: 4 additions & 0 deletions packages/protocol/test/ProverPool.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ contract TestProverPool is Test {
}

// The same 32 provers restake
vm.warp(block.timestamp + 24 hours);
baseCapacity = 200;
for (uint16 i; i < provers.length; ++i) {
address addr = randomAddress(i);
Expand All @@ -80,6 +81,7 @@ contract TestProverPool is Test {
}

(provers, stakers) = printProvers();
vm.warp(block.timestamp + 24 hours);
for (uint16 i; i < provers.length; ++i) {
assertEq(
provers[i].stakedAmount, uint64(baseCapacity + i) * 10_000 * 1e8
Expand All @@ -90,6 +92,7 @@ contract TestProverPool is Test {

// Different 32 provers stake
baseCapacity = 500;
vm.warp(block.timestamp + 24 hours);
for (uint16 i; i < provers.length; ++i) {
address addr = randomAddress(i + 12_345);
uint16 capacity = baseCapacity + i;
Expand All @@ -99,6 +102,7 @@ contract TestProverPool is Test {
}

(provers, stakers) = printProvers();
vm.warp(block.timestamp + 24 hours);
for (uint16 i; i < provers.length; ++i) {
assertEq(
provers[i].stakedAmount, uint64(baseCapacity + i) * 10_000 * 1e8
Expand Down
2 changes: 2 additions & 0 deletions packages/protocol/test/ProverPool_Protocol_Interactions.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -451,9 +451,11 @@ contract TaikoL1ProverPool is TaikoL1TestBase {
if (blockId == (conf.blockMaxProposals * 9 / 2)) {
// Kai is the top staker at this point
vm.prank(Kai, Kai);
vm.warp(block.timestamp + 1 days);
realProverPool.stake(0, 0, 0);

// Now Khloe will be the new top staker
vm.warp(block.timestamp + 1 days);
vm.prank(Khloe, Khloe);
realProverPool.stake(uint64(6) * 1e8, 10, 128); // 6 * 1e8 is
// the biggest, Kai
Expand Down
3 changes: 1 addition & 2 deletions packages/protocol/test/TaikoL1TestBase.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ abstract contract TaikoL1TestBase is Test {
proof: new bytes(100)
});

bytes32 instance = getInstance(conf, L1, evidence);
bytes32 instance = getInstance(conf, evidence);

evidence.proof = bytes.concat(
bytes16(0),
Expand Down Expand Up @@ -282,7 +282,6 @@ abstract contract TaikoL1TestBase is Test {

function getInstance(
TaikoData.Config memory config,
AddressResolver resolver,
TaikoData.BlockEvidence memory evidence
)
internal
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,12 @@ uint64 MIN_SLASH_AMOUNT
uint256 MAX_NUM_PROVERS
```

### MIN_CHANGE_DELAY

```solidity
uint256 MIN_CHANGE_DELAY
```

### provers

```solidity
Expand Down Expand Up @@ -104,6 +110,12 @@ event Slashed(address addr, uint64 amount)
event Staked(address addr, uint64 amount, uint16 rewardPerGas, uint16 currentCapacity)
```

### CHANGE_TOO_FREQUENT

```solidity
error CHANGE_TOO_FREQUENT()
```

### INVALID_PARAMS

```solidity
Expand Down