diff --git a/packages/protocol/contracts/L1/ProverPool.sol b/packages/protocol/contracts/L1/ProverPool.sol index 6c3e2b0868..4493b06346 100644 --- a/packages/protocol/contracts/L1/ProverPool.sol +++ b/packages/protocol/contracts/L1/ProverPool.sol @@ -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 @@ -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(); @@ -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 @@ -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; @@ -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; diff --git a/packages/protocol/contracts/L1/TaikoConfig.sol b/packages/protocol/contracts/L1/TaikoConfig.sol index 1d0b8faf1d..3aad38e9ba 100644 --- a/packages/protocol/contracts/L1/TaikoConfig.sol +++ b/packages/protocol/contracts/L1/TaikoConfig.sol @@ -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 }); diff --git a/packages/protocol/contracts/L1/libs/LibProposing.sol b/packages/protocol/contracts/L1/libs/LibProposing.sol index b5fa2fd0e7..7096f88549 100644 --- a/packages/protocol/contracts/L1/libs/LibProposing.sol +++ b/packages/protocol/contracts/L1/libs/LibProposing.sol @@ -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( diff --git a/packages/protocol/test/ProverPool.t.sol b/packages/protocol/test/ProverPool.t.sol index ebde90ca01..b9ce07ac55 100644 --- a/packages/protocol/test/ProverPool.t.sol +++ b/packages/protocol/test/ProverPool.t.sol @@ -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); @@ -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 @@ -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; @@ -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 diff --git a/packages/protocol/test/ProverPool_Protocol_Interactions.t.sol b/packages/protocol/test/ProverPool_Protocol_Interactions.t.sol index fe635e95f6..86a10cdba5 100644 --- a/packages/protocol/test/ProverPool_Protocol_Interactions.t.sol +++ b/packages/protocol/test/ProverPool_Protocol_Interactions.t.sol @@ -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 diff --git a/packages/protocol/test/TaikoL1TestBase.t.sol b/packages/protocol/test/TaikoL1TestBase.t.sol index 83406fc9b5..da314f2f06 100644 --- a/packages/protocol/test/TaikoL1TestBase.t.sol +++ b/packages/protocol/test/TaikoL1TestBase.t.sol @@ -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), @@ -282,7 +282,6 @@ abstract contract TaikoL1TestBase is Test { function getInstance( TaikoData.Config memory config, - AddressResolver resolver, TaikoData.BlockEvidence memory evidence ) internal diff --git a/packages/website/pages/docs/reference/contract-documentation/L1/ProverPool.md b/packages/website/pages/docs/reference/contract-documentation/L1/ProverPool.md index a8878279dd..294c1ce756 100644 --- a/packages/website/pages/docs/reference/contract-documentation/L1/ProverPool.md +++ b/packages/website/pages/docs/reference/contract-documentation/L1/ProverPool.md @@ -62,6 +62,12 @@ uint64 MIN_SLASH_AMOUNT uint256 MAX_NUM_PROVERS ``` +### MIN_CHANGE_DELAY + +```solidity +uint256 MIN_CHANGE_DELAY +``` + ### provers ```solidity @@ -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