Skip to content

Commit

Permalink
feat(protocol): trigger simultaneous recurring TKO snapshots (#16715)
Browse files Browse the repository at this point in the history
  • Loading branch information
dantaik committed Apr 18, 2024
1 parent 1613975 commit bffc8dc
Show file tree
Hide file tree
Showing 12 changed files with 124 additions and 21 deletions.
5 changes: 2 additions & 3 deletions packages/protocol/contracts/L1/TaikoData.sol
Expand Up @@ -140,7 +140,7 @@ library TaikoData {
uint64 genesisHeight;
uint64 genesisTimestamp;
uint64 lastSyncedBlockId;
uint64 lastSynecdAt;
uint64 lastSynecdAt; // typo!
}

struct SlotB {
Expand All @@ -149,7 +149,7 @@ library TaikoData {
bool provingPaused;
uint8 __reservedB1;
uint16 __reservedB2;
uint32 __reservedB3;
uint32 lastSnapshotIdx;
uint64 lastUnpausedAt;
}

Expand All @@ -164,7 +164,6 @@ library TaikoData {
uint64 blockId_mod_blockRingBufferSize
=> mapping(uint32 transitionId => TransitionState ts)
) transitions;
// Ring buffer for Ether deposits
bytes32 __reserve1;
SlotA slotA; // slot 5
SlotB slotB; // slot 6
Expand Down
6 changes: 6 additions & 0 deletions packages/protocol/contracts/L1/TaikoEvents.sol
Expand Up @@ -45,6 +45,12 @@ abstract contract TaikoEvents {
/// @param slotB The SlotB data structure.
event StateVariablesUpdated(TaikoData.SlotB slotB);

/// @notice Emitted when the Taiko token snapshot is taken.
/// @param tkoAddress The Taiko token address.
/// @param snapshotIdx The snapshot index.
/// @param snapshotId The snapshot id.
event TaikoTokenSnapshot(address tkoAddress, uint256 snapshotIdx, uint256 snapshotId);

/// @dev Emitted when a block transition is proved or re-proved.
/// @param blockId The ID of the proven block.
/// @param tran The verified transition.
Expand Down
1 change: 0 additions & 1 deletion packages/protocol/contracts/L1/TaikoL1.sol
Expand Up @@ -59,7 +59,6 @@ contract TaikoL1 is EssentialContract, ITaikoL1, TaikoEvents, TaikoErrors {
// reset some previously used slots for future reuse
state.slotB.__reservedB1 = 0;
state.slotB.__reservedB2 = 0;
state.slotB.__reservedB3 = 0;
state.__reserve1 = 0;
}

Expand Down
6 changes: 5 additions & 1 deletion packages/protocol/contracts/L1/TaikoToken.sol
Expand Up @@ -53,7 +53,7 @@ contract TaikoToken is EssentialContract, ERC20SnapshotUpgradeable, ERC20VotesUp
}

/// @notice Creates a new token snapshot.
function snapshot() public onlyFromOwnerOrNamed(LibStrings.B_SNAPSHOOTER) returns (uint256) {
function snapshot() public onlyFromNamed(LibStrings.B_TAIKO) returns (uint256) {
return _snapshot();
}

Expand Down Expand Up @@ -84,6 +84,10 @@ contract TaikoToken is EssentialContract, ERC20SnapshotUpgradeable, ERC20VotesUp
return super.transferFrom(_from, _to, _amount);
}

function currentSnapshotId() public view returns (uint256) {
return _getCurrentSnapshotId();
}

function _beforeTokenTransfer(
address _from,
address _to,
Expand Down
17 changes: 16 additions & 1 deletion packages/protocol/contracts/L1/libs/LibProposing.sol
Expand Up @@ -4,7 +4,7 @@ pragma solidity 0.8.24;
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "../../common/IAddressResolver.sol";
import "../../common/LibStrings.sol";
import "../../common/LibSnapshot.sol";
import "../../libs/LibAddress.sol";
import "../../libs/LibNetwork.sol";
import "../hooks/IHook.sol";
Expand Down Expand Up @@ -185,6 +185,8 @@ library LibProposing {

{
IERC20 tko = IERC20(_resolver.resolve(LibStrings.B_TAIKO_TOKEN, false));
_takeTaikoTokenSnapshot(_state, address(tko), b);

uint256 tkoBalance = tko.balanceOf(address(this));

// Run all hooks.
Expand Down Expand Up @@ -230,6 +232,19 @@ library LibProposing {
});
}

function _takeTaikoTokenSnapshot(
TaikoData.State storage _state,
address _taikoToken,
TaikoData.SlotB memory _slotB
)
private
{
uint32 idx = LibSnapshot.autoSnapshot(_taikoToken, block.number, _slotB.lastSnapshotIdx);
if (idx != 0) {
_state.slotB.lastSnapshotIdx = idx;
}
}

function _isProposerPermitted(
TaikoData.SlotB memory _slotB,
IAddressResolver _resolver
Expand Down
18 changes: 17 additions & 1 deletion packages/protocol/contracts/L2/TaikoL2.sol
Expand Up @@ -5,7 +5,7 @@ import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

import "../common/EssentialContract.sol";
import "../common/LibStrings.sol";
import "../common/LibSnapshot.sol";
import "../libs/LibAddress.sol";
import "../signal/ISignalService.sol";
import "./Lib1559Math.sol";
Expand Down Expand Up @@ -47,15 +47,24 @@ contract TaikoL2 is EssentialContract {
uint64 private __currentBlockTimestamp;

/// @notice The L1's chain ID.
/// @dev Slot 4.
uint64 public l1ChainId;

uint32 public lastSnapshotIdx;

uint256[46] private __gap;

/// @notice Emitted when the latest L1 block details are anchored to L2.
/// @param parentHash The hash of the parent block.
/// @param gasExcess The gas excess value used to calculate the base fee.
event Anchored(bytes32 parentHash, uint64 gasExcess);

/// @notice Emitted when the Taiko token snapshot is taken.
/// @param tkoAddress The Taiko token address.
/// @param snapshotIdx The snapshot index.
/// @param snapshotId The snapshot id.
event TaikoTokenSnapshot(address tkoAddress, uint256 snapshotIdx, uint256 snapshotId);

error L2_BASEFEE_MISMATCH();
error L2_INVALID_L1_CHAIN_ID();
error L2_INVALID_L2_CHAIN_ID();
Expand Down Expand Up @@ -105,6 +114,7 @@ contract TaikoL2 is EssentialContract {

/// @notice Anchors the latest L1 block details to L2 for cross-layer
/// message verification.
/// @dev The gas limit for this transaction is set to 250K in geth and raiko.
/// @dev This function can be called freely as the golden touch private key is publicly known,
/// but the Taiko node guarantees the first transaction of each block is always this anchor
/// transaction, and any subsequent calls will revert with L2_PUBLIC_INPUT_HASH_MISMATCH.
Expand Down Expand Up @@ -168,6 +178,12 @@ contract TaikoL2 is EssentialContract {
__currentBlockTimestamp = uint64(block.timestamp);
gasExcess = _gasExcess;

address tko = resolve(LibStrings.B_TAIKO_TOKEN, true);
if (tko != address(0)) {
uint32 idx = LibSnapshot.autoSnapshot(tko, _l1BlockId, lastSnapshotIdx);
if (idx != 0) lastSnapshotIdx = idx;
}

emit Anchored(_parentHash, _gasExcess);
}

Expand Down
47 changes: 47 additions & 0 deletions packages/protocol/contracts/common/LibSnapshot.sol
@@ -0,0 +1,47 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

import "./IAddressResolver.sol";
import "./LibStrings.sol";

/// @title ISnapshot
/// @custom:security-contact security@taiko.xyz
interface ISnapshot {
function snapshot() external returns (uint256);
}

/// @title LibSnapshot
/// @custom:security-contact security@taiko.xyz
library LibSnapshot {
uint256 public constant SNAPSHOT_INTERVAL = 7200; // uint = 1 L1 block

/// @notice Emitted when the Taiko token snapshot is taken.
/// @param tkoAddress The Taiko token address.
/// @param snapshotIdx The snapshot index.
/// @param snapshotId The snapshot id.
event TaikoTokenSnapshot(address tkoAddress, uint256 snapshotIdx, uint256 snapshotId);

/// @dev Takes a snapshot every 200,000 L1 blocks which is roughly 27 days.
/// @param _taikoToken The Taiko token address.
/// @param _blockId The L1's block ID.
/// @param _lastSnapshotIdx The latest snapshot's index.
/// @return The new snapshot's index, 0 if no new snapshot is taken.
function autoSnapshot(
address _taikoToken,
uint256 _blockId,
uint64 _lastSnapshotIdx
)
internal
returns (uint32)
{
if (_blockId % SNAPSHOT_INTERVAL != 0) return 0;

// if snapshotIdx = type(uint32).max, we can handle L1 block id up to 4e14.
uint32 snapshotIdx = uint32(_blockId / SNAPSHOT_INTERVAL + 1);
if (snapshotIdx == _lastSnapshotIdx) return 0;

uint256 snapshotId = ISnapshot(_taikoToken).snapshot();
emit TaikoTokenSnapshot(_taikoToken, snapshotIdx, snapshotId);
return snapshotIdx;
}
}
3 changes: 0 additions & 3 deletions packages/protocol/contracts/common/LibStrings.sol
Expand Up @@ -7,9 +7,6 @@ library LibStrings {
/// @notice bytes32 representation of the string "chain_pauser".
bytes32 internal constant B_CHAIN_PAUSER = bytes32("chain_pauser");

/// @notice bytes32 representation of the string "snapshooter".
bytes32 internal constant B_SNAPSHOOTER = bytes32("snapshooter");

/// @notice bytes32 representation of the string "withdrawer".
bytes32 internal constant B_WITHDRAWER = bytes32("withdrawer");

Expand Down
30 changes: 25 additions & 5 deletions packages/protocol/contracts/tokenvault/BridgedERC20.sol
Expand Up @@ -33,10 +33,8 @@ contract BridgedERC20 is
error BTOKEN_CANNOT_RECEIVE();
error BTOKEN_UNAUTHORIZED();

modifier onlyOwnerOrSnapshooter() {
if (msg.sender != owner() && msg.sender != snapshooter) {
revert BTOKEN_UNAUTHORIZED();
}
modifier onlyAuthorizedForSnapshot() {
if (!isAuthorizedForSnapshot(msg.sender)) revert BTOKEN_UNAUTHORIZED();
_;
}

Expand Down Expand Up @@ -81,7 +79,7 @@ contract BridgedERC20 is
}

/// @notice Creates a new token snapshot.
function snapshot() external onlyOwnerOrSnapshooter returns (uint256) {
function snapshot() external onlyAuthorizedForSnapshot returns (uint256) {
return _snapshot();
}

Expand Down Expand Up @@ -118,6 +116,28 @@ contract BridgedERC20 is
return __srcDecimals;
}

/// @notice Gets the current snapshot ID.
/// @return The current snapshot ID.
function currentSnapshotId() public view returns (uint256) {
return _getCurrentSnapshotId();
}

/// @notice Checks if an address can take a snapshot.
/// @param addr The address.
/// @return true if the address can perform a snapshot, false otherwise.
function isAuthorizedForSnapshot(address addr) public view returns (bool) {
if (addr == address(0)) return false;

if (
addr == resolve(LibStrings.B_TAIKO, true)
&& address(this) == resolve(LibStrings.B_TAIKO_TOKEN, true)
) return true;

if (addr == snapshooter) return true;

return false;
}

/// @notice Gets the canonical token's address and chain ID.
/// @return The canonical token's address.
/// @return The canonical token's chain ID.
Expand Down
2 changes: 1 addition & 1 deletion packages/protocol/package.json
Expand Up @@ -30,7 +30,7 @@
"eslint-plugin-promise": "^6.1.1",
"ethers": "^5.7.2",
"solc": "0.7.3",
"solhint": "^4.5.2",
"solhint": "^4.5.4",
"ts-node": "^10.9.2",
"typescript": "^5.2.2"
},
Expand Down
2 changes: 1 addition & 1 deletion packages/protocol/test/L1/TaikoL1.t.sol
Expand Up @@ -187,7 +187,7 @@ contract TaikoL1Test is TaikoL1TestBase {
}

function test_snapshot() external {
vm.prank(tko.owner(), tko.owner());
vm.prank(address(L1));
tko.snapshot();

uint256 totalSupplyAtSnapshot = tko.totalSupplyAt(1);
Expand Down
8 changes: 4 additions & 4 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit bffc8dc

Please sign in to comment.