From 2aa7dec273e500f495d140139e10d595063c92c8 Mon Sep 17 00:00:00 2001 From: Daniel Wang Date: Tue, 12 Mar 2024 14:11:01 +0800 Subject: [PATCH 1/9] Create FairMintRewardToken.sol --- .../contracts/L2/FairMintRewardToken.sol | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 packages/protocol/contracts/L2/FairMintRewardToken.sol diff --git a/packages/protocol/contracts/L2/FairMintRewardToken.sol b/packages/protocol/contracts/L2/FairMintRewardToken.sol new file mode 100644 index 00000000000..a7d05cb089f --- /dev/null +++ b/packages/protocol/contracts/L2/FairMintRewardToken.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +contract FairMintRewardToken is ERC20 { + uint256 public constant START_BLOCK = 10_000; + uint256 public constant END_BLOCK = 1_000_000; + uint256 public constant START_MINT_AMOUNT = 24 ether; + uint256 public maxDifficulty = type(uint256).max; + uint256 private lastBlockMinted; + + error UNABLE_TO_MINT(); + error UNSUPPORTED(); + + constructor() ERC20("FairMintRewardToken", "CTK") { + _mint(msg.sender, 0); // Initial supply set to 0 + } + + function mint() public { + if ( + block.number < START_BLOCK || block.number > END_BLOCK + || block.prevrandao >= maxDifficulty || block.number <= lastBlockMinted + ) { + revert UNABLE_TO_MINT(); + } + + maxDifficulty = block.prevrandao; + lastBlockMinted = block.number; + + _mint(block.coinbase, nextMintAmount()); + } + + function nextMintAmount() public view returns (uint256) { + if (block.number < START_BLOCK || block.number >= END_BLOCK) return 0; + return START_MINT_AMOUNT * (END_BLOCK - block.number) / (END_BLOCK - START_BLOCK); + } + + function transfer(address, uint256) public virtual override returns (bool) { + revert UNSUPPORTED(); + } +} From 056b3d7dce9bf3e9ddbcc0230801032d4920c6bc Mon Sep 17 00:00:00 2001 From: Daniel Wang Date: Tue, 12 Mar 2024 14:12:16 +0800 Subject: [PATCH 2/9] Update FairMintRewardToken.sol --- packages/protocol/contracts/L2/FairMintRewardToken.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/protocol/contracts/L2/FairMintRewardToken.sol b/packages/protocol/contracts/L2/FairMintRewardToken.sol index a7d05cb089f..c34402b44b6 100644 --- a/packages/protocol/contracts/L2/FairMintRewardToken.sol +++ b/packages/protocol/contracts/L2/FairMintRewardToken.sol @@ -36,7 +36,7 @@ contract FairMintRewardToken is ERC20 { return START_MINT_AMOUNT * (END_BLOCK - block.number) / (END_BLOCK - START_BLOCK); } - function transfer(address, uint256) public virtual override returns (bool) { + function transfer(address, uint256) public override returns (bool) { revert UNSUPPORTED(); } } From 5e8cd92e0f12b8087c41fdfbbd4ef30723b89823 Mon Sep 17 00:00:00 2001 From: Daniel Wang Date: Tue, 12 Mar 2024 14:26:35 +0800 Subject: [PATCH 3/9] more --- ...ewardToken.sol => TaikoProposerPassToken.sol} | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) rename packages/protocol/contracts/L2/{FairMintRewardToken.sol => TaikoProposerPassToken.sol} (72%) diff --git a/packages/protocol/contracts/L2/FairMintRewardToken.sol b/packages/protocol/contracts/L2/TaikoProposerPassToken.sol similarity index 72% rename from packages/protocol/contracts/L2/FairMintRewardToken.sol rename to packages/protocol/contracts/L2/TaikoProposerPassToken.sol index c34402b44b6..5595b519d84 100644 --- a/packages/protocol/contracts/L2/FairMintRewardToken.sol +++ b/packages/protocol/contracts/L2/TaikoProposerPassToken.sol @@ -3,30 +3,30 @@ pragma solidity 0.8.24; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -contract FairMintRewardToken is ERC20 { +contract TaikoProposerPassToken is ERC20 { uint256 public constant START_BLOCK = 10_000; - uint256 public constant END_BLOCK = 1_000_000; + uint256 public constant END_BLOCK = 50_000_000; uint256 public constant START_MINT_AMOUNT = 24 ether; uint256 public maxDifficulty = type(uint256).max; - uint256 private lastBlockMinted; + uint256 private __lastBlockMinted; error UNABLE_TO_MINT(); error UNSUPPORTED(); - constructor() ERC20("FairMintRewardToken", "CTK") { + constructor() ERC20("Taiko Proposer Pass Token", "TKOPP") { _mint(msg.sender, 0); // Initial supply set to 0 } function mint() public { if ( block.number < START_BLOCK || block.number > END_BLOCK - || block.prevrandao >= maxDifficulty || block.number <= lastBlockMinted + || block.prevrandao >= maxDifficulty || block.number <= __lastBlockMinted ) { revert UNABLE_TO_MINT(); } maxDifficulty = block.prevrandao; - lastBlockMinted = block.number; + __lastBlockMinted = block.number; _mint(block.coinbase, nextMintAmount()); } @@ -35,8 +35,4 @@ contract FairMintRewardToken is ERC20 { if (block.number < START_BLOCK || block.number >= END_BLOCK) return 0; return START_MINT_AMOUNT * (END_BLOCK - block.number) / (END_BLOCK - START_BLOCK); } - - function transfer(address, uint256) public override returns (bool) { - revert UNSUPPORTED(); - } } From ebe49b44c7ff4ba6dd6c52ce223a571c64aec3a3 Mon Sep 17 00:00:00 2001 From: Daniel Wang Date: Tue, 12 Mar 2024 14:31:28 +0800 Subject: [PATCH 4/9] Update TaikoProposerPassToken.sol --- .../contracts/L2/TaikoProposerPassToken.sol | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/packages/protocol/contracts/L2/TaikoProposerPassToken.sol b/packages/protocol/contracts/L2/TaikoProposerPassToken.sol index 5595b519d84..fd4078a810c 100644 --- a/packages/protocol/contracts/L2/TaikoProposerPassToken.sol +++ b/packages/protocol/contracts/L2/TaikoProposerPassToken.sol @@ -4,10 +4,8 @@ pragma solidity 0.8.24; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; contract TaikoProposerPassToken is ERC20 { - uint256 public constant START_BLOCK = 10_000; uint256 public constant END_BLOCK = 50_000_000; - uint256 public constant START_MINT_AMOUNT = 24 ether; - uint256 public maxDifficulty = type(uint256).max; + uint256 public minDifficulty = type(uint256).max; uint256 private __lastBlockMinted; error UNABLE_TO_MINT(); @@ -19,20 +17,20 @@ contract TaikoProposerPassToken is ERC20 { function mint() public { if ( - block.number < START_BLOCK || block.number > END_BLOCK - || block.prevrandao >= maxDifficulty || block.number <= __lastBlockMinted + block.number > END_BLOCK || block.prevrandao >= minDifficulty + || block.number <= __lastBlockMinted ) { revert UNABLE_TO_MINT(); } - maxDifficulty = block.prevrandao; + minDifficulty = block.prevrandao; __lastBlockMinted = block.number; _mint(block.coinbase, nextMintAmount()); } function nextMintAmount() public view returns (uint256) { - if (block.number < START_BLOCK || block.number >= END_BLOCK) return 0; - return START_MINT_AMOUNT * (END_BLOCK - block.number) / (END_BLOCK - START_BLOCK); + if (block.number >= END_BLOCK) return 0; + return (32 ether) * (END_BLOCK - block.number) / END_BLOCK; } } From e62066a0a8a6b7e818dda2403dc58c000a94258f Mon Sep 17 00:00:00 2001 From: Daniel Wang Date: Tue, 12 Mar 2024 14:32:49 +0800 Subject: [PATCH 5/9] more --- .../protocol/contracts/{L2 => team}/TaikoProposerPassToken.sol | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename packages/protocol/contracts/{L2 => team}/TaikoProposerPassToken.sol (100%) diff --git a/packages/protocol/contracts/L2/TaikoProposerPassToken.sol b/packages/protocol/contracts/team/TaikoProposerPassToken.sol similarity index 100% rename from packages/protocol/contracts/L2/TaikoProposerPassToken.sol rename to packages/protocol/contracts/team/TaikoProposerPassToken.sol From ac76038bc8cbcbba93a9ed87867597c0d9a09d2b Mon Sep 17 00:00:00 2001 From: Daniel Wang Date: Tue, 12 Mar 2024 19:34:34 +0800 Subject: [PATCH 6/9] more --- .../contracts/team/TaikoPassToken.sol | 29 +++++++++++++++ .../contracts/team/TaikoProposerPassToken.sol | 36 ------------------- 2 files changed, 29 insertions(+), 36 deletions(-) create mode 100644 packages/protocol/contracts/team/TaikoPassToken.sol delete mode 100644 packages/protocol/contracts/team/TaikoProposerPassToken.sol diff --git a/packages/protocol/contracts/team/TaikoPassToken.sol b/packages/protocol/contracts/team/TaikoPassToken.sol new file mode 100644 index 00000000000..400afa04c38 --- /dev/null +++ b/packages/protocol/contracts/team/TaikoPassToken.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +contract TaikoPassToken is ERC20 { + uint256 public constant LAST_MINT_BLOCK = 50_000_000; + uint256 public lastMintBlock; + + error UNABLE_TO_MINT(); + error UNSUPPORTED(); + + constructor() ERC20("Taiko Proposer Fairmint Pass", "TPFP") { } + + function mint() public { + uint256 amount = nextMintAmount(); + if (amount == 0 || block.number <= lastMintBlock) { + revert UNABLE_TO_MINT(); + } + + lastMintBlock = block.number; + _mint(block.coinbase, amount); + } + + function nextMintAmount() public view returns (uint256) { + if (block.number >= LAST_MINT_BLOCK) return 0; + return (4 ether) * (LAST_MINT_BLOCK - block.number) / LAST_MINT_BLOCK; + } +} diff --git a/packages/protocol/contracts/team/TaikoProposerPassToken.sol b/packages/protocol/contracts/team/TaikoProposerPassToken.sol deleted file mode 100644 index fd4078a810c..00000000000 --- a/packages/protocol/contracts/team/TaikoProposerPassToken.sol +++ /dev/null @@ -1,36 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.24; - -import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; - -contract TaikoProposerPassToken is ERC20 { - uint256 public constant END_BLOCK = 50_000_000; - uint256 public minDifficulty = type(uint256).max; - uint256 private __lastBlockMinted; - - error UNABLE_TO_MINT(); - error UNSUPPORTED(); - - constructor() ERC20("Taiko Proposer Pass Token", "TKOPP") { - _mint(msg.sender, 0); // Initial supply set to 0 - } - - function mint() public { - if ( - block.number > END_BLOCK || block.prevrandao >= minDifficulty - || block.number <= __lastBlockMinted - ) { - revert UNABLE_TO_MINT(); - } - - minDifficulty = block.prevrandao; - __lastBlockMinted = block.number; - - _mint(block.coinbase, nextMintAmount()); - } - - function nextMintAmount() public view returns (uint256) { - if (block.number >= END_BLOCK) return 0; - return (32 ether) * (END_BLOCK - block.number) / END_BLOCK; - } -} From c8a36758350e10a4ad67e6adc21e147fb673863d Mon Sep 17 00:00:00 2001 From: Daniel Wang Date: Tue, 12 Mar 2024 19:53:31 +0800 Subject: [PATCH 7/9] more --- .../contracts/team/ProposerFairmintPass.sol | 45 +++++++++++++++++++ .../contracts/team/TaikoPassToken.sol | 29 ------------ 2 files changed, 45 insertions(+), 29 deletions(-) create mode 100644 packages/protocol/contracts/team/ProposerFairmintPass.sol delete mode 100644 packages/protocol/contracts/team/TaikoPassToken.sol diff --git a/packages/protocol/contracts/team/ProposerFairmintPass.sol b/packages/protocol/contracts/team/ProposerFairmintPass.sol new file mode 100644 index 00000000000..e311c5a9b52 --- /dev/null +++ b/packages/protocol/contracts/team/ProposerFairmintPass.sol @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +contract ProposerFairmintPass is ERC20 { + uint256 public constant LAST_MINT_BLOCK = 50_000_000; + uint64 public lastMintBlock; + uint192 public minDifficulty; + + error UNABLE_TO_MINT(); + error UNSUPPORTED(); + error INVALID_SALT(); + + constructor() ERC20("Taiko Proposer Fairmint Pass", "TPFP") { + minDifficulty = type(uint192).max; + } + + function mint(uint256 _salt) public { + uint256 value = uint256(keccak256(abi.encode("MINT", _salt))); + if (value >= minDifficulty) revert INVALID_SALT(); + + uint256 amount = nextMintAmount(); + if (amount == 0 || block.number <= lastMintBlock) { + revert UNABLE_TO_MINT(); + } + + lastMintBlock = uint64(block.number); + _mint(block.coinbase, amount); + } + + function _calcDifficulty(uint256 _oldDifficulty, uint256 _actualMiningTime, uint256 _expectedMiningTime) + private + pure + returns (uint256 newDifficulty) + { + // To avoid floating point operations, we use multiplication before division + return (oldDifficulty * actualMiningTime) / expectedMiningTime; + } + + function nextMintAmount() public view returns (uint256) { + if (block.number >= LAST_MINT_BLOCK) return 0; + return (4 ether) * (LAST_MINT_BLOCK - block.number) / LAST_MINT_BLOCK; + } +} diff --git a/packages/protocol/contracts/team/TaikoPassToken.sol b/packages/protocol/contracts/team/TaikoPassToken.sol deleted file mode 100644 index 400afa04c38..00000000000 --- a/packages/protocol/contracts/team/TaikoPassToken.sol +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.24; - -import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; - -contract TaikoPassToken is ERC20 { - uint256 public constant LAST_MINT_BLOCK = 50_000_000; - uint256 public lastMintBlock; - - error UNABLE_TO_MINT(); - error UNSUPPORTED(); - - constructor() ERC20("Taiko Proposer Fairmint Pass", "TPFP") { } - - function mint() public { - uint256 amount = nextMintAmount(); - if (amount == 0 || block.number <= lastMintBlock) { - revert UNABLE_TO_MINT(); - } - - lastMintBlock = block.number; - _mint(block.coinbase, amount); - } - - function nextMintAmount() public view returns (uint256) { - if (block.number >= LAST_MINT_BLOCK) return 0; - return (4 ether) * (LAST_MINT_BLOCK - block.number) / LAST_MINT_BLOCK; - } -} From 21bcfbc0b7355e920a56e4348b7b51c64f746a87 Mon Sep 17 00:00:00 2001 From: Daniel Wang Date: Wed, 13 Mar 2024 16:11:46 +0800 Subject: [PATCH 8/9] Taiko L2 offers parentTimestamp --- packages/protocol/contracts/L2/TaikoL2.sol | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/protocol/contracts/L2/TaikoL2.sol b/packages/protocol/contracts/L2/TaikoL2.sol index 99f5f7e3627..4dd7c2085b5 100644 --- a/packages/protocol/contracts/L2/TaikoL2.sol +++ b/packages/protocol/contracts/L2/TaikoL2.sol @@ -49,6 +49,12 @@ contract TaikoL2 is CrossChainOwned { /// @notice The last synced L1 block height. uint64 public lastSyncedBlock; + /// @notice The parent block's timestamp. + uint64 public parentTimestamp; + + /// @notice The current block's timestamp. + uint64 private __currentBlockTimestamp; + uint256[47] private __gap; /// @notice Emitted when the latest L1 block details are anchored to L2. @@ -95,6 +101,7 @@ contract TaikoL2 is CrossChainOwned { gasExcess = _gasExcess; (publicInputHash,) = _calcPublicInputHash(block.number); + __currentBlockTimestamp = uint64(block.timestamp); } /// @notice Anchors the latest L1 block details to L2 for cross-layer @@ -154,6 +161,9 @@ contract TaikoL2 is CrossChainOwned { l2Hashes[parentId] = blockhash(parentId); publicInputHash = publicInputHashNew; + parentTimestamp = __currentBlockTimestamp; + __currentBlockTimestamp = uint64(block.timestamp); + emit Anchored(blockhash(parentId), gasExcess); } From d48fed0cf1516488ee4b112730d276d3f8c04d1e Mon Sep 17 00:00:00 2001 From: Daniel Wang Date: Wed, 13 Mar 2024 16:30:17 +0800 Subject: [PATCH 9/9] Update ProposerFairmintPass.sol --- .../contracts/team/ProposerFairmintPass.sol | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/packages/protocol/contracts/team/ProposerFairmintPass.sol b/packages/protocol/contracts/team/ProposerFairmintPass.sol index e311c5a9b52..a0ed73c4b2d 100644 --- a/packages/protocol/contracts/team/ProposerFairmintPass.sol +++ b/packages/protocol/contracts/team/ProposerFairmintPass.sol @@ -3,41 +3,41 @@ pragma solidity 0.8.24; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +interface ITaikoL2 { + function parentTimestamp() external view returns (uint64); +} + +/// @title ProposerFairmintPass +/// @dev A token that can be fair-minted by block proposers (block.coinbase) if this block is +/// proposed no early than 12 seconds after its parent block. +/// This token then can be used for rewarding block proposers for Taiko L2's liveness. +/// @custom:security-contact security@taiko.xyz +/// TODO (dnaiel): we probably want to mint NFTs rather than ERC20 tokens. contract ProposerFairmintPass is ERC20 { + address public constant TAIKO_L2 = 0x1670080000000000000000000000000000010001; uint256 public constant LAST_MINT_BLOCK = 50_000_000; - uint64 public lastMintBlock; - uint192 public minDifficulty; + uint256 public constant MIN_BLOCK_TIME = 12 seconds; - error UNABLE_TO_MINT(); - error UNSUPPORTED(); - error INVALID_SALT(); + uint64 public lastMintBlock; - constructor() ERC20("Taiko Proposer Fairmint Pass", "TPFP") { - minDifficulty = type(uint192).max; - } + error MINT_DISALLOWED(); - function mint(uint256 _salt) public { - uint256 value = uint256(keccak256(abi.encode("MINT", _salt))); - if (value >= minDifficulty) revert INVALID_SALT(); + constructor() ERC20("Taiko Proposer Fairmint Pass", "TPFP") { } + function mint() public { uint256 amount = nextMintAmount(); - if (amount == 0 || block.number <= lastMintBlock) { - revert UNABLE_TO_MINT(); + + if ( + amount == 0 || block.number <= lastMintBlock + || block.timestamp < ITaikoL2(TAIKO_L2).parentTimestamp() + MIN_BLOCK_TIME + ) { + revert MINT_DISALLOWED(); } lastMintBlock = uint64(block.number); _mint(block.coinbase, amount); } - function _calcDifficulty(uint256 _oldDifficulty, uint256 _actualMiningTime, uint256 _expectedMiningTime) - private - pure - returns (uint256 newDifficulty) - { - // To avoid floating point operations, we use multiplication before division - return (oldDifficulty * actualMiningTime) / expectedMiningTime; - } - function nextMintAmount() public view returns (uint256) { if (block.number >= LAST_MINT_BLOCK) return 0; return (4 ether) * (LAST_MINT_BLOCK - block.number) / LAST_MINT_BLOCK;