Skip to content

Commit

Permalink
feat(protocol): move prover assignment verification to hook (#15208)
Browse files Browse the repository at this point in the history
Co-authored-by: Daniel Wang <dong77@gmail.com>
Co-authored-by: jeff <113397187+cyberhorsey@users.noreply.github.com>
Co-authored-by: David <david@taiko.xyz>
Co-authored-by: Roger <50648015+RogerLamTd@users.noreply.github.com>
Co-authored-by: adaki2004 <keszeydani@gmail.com>
Co-authored-by: D <51912515+adaki2004@users.noreply.github.com>
Co-authored-by: Brecht Devos <Brechtp.Devos@gmail.com>
Co-authored-by: Jeffery Walsh <cyberhorsey@gmail.com>
  • Loading branch information
9 people committed Nov 18, 2023
1 parent 80dd6c4 commit d61af90
Show file tree
Hide file tree
Showing 40 changed files with 331 additions and 363 deletions.
Binary file added packages/Unknown.pdf
Binary file not shown.
15 changes: 5 additions & 10 deletions packages/protocol/contracts/L1/TaikoData.sol
Original file line number Diff line number Diff line change
Expand Up @@ -69,25 +69,20 @@ library TaikoData {
bytes data;
}

struct ProverAssignment {
address prover;
address feeToken;
TierFee[] tierFees;
uint64 expiry;
uint64 maxBlockId;
uint64 maxProposedIn;
bytes32 metaHash;
bytes signature;
struct HookCall {
address hook;
bytes data;
}

struct BlockParams {
ProverAssignment assignment;
address assignedProver;
bytes32 extraData;
bytes32 blobHash;
uint24 txListByteOffset;
uint24 txListByteSize;
bool cacheBlobForReuse;
bytes32 parentMetaHash;
HookCall[] hookCalls;
}

/// @dev Struct containing data only required for proving a block
Expand Down
9 changes: 4 additions & 5 deletions packages/protocol/contracts/L1/TaikoErrors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,6 @@ abstract contract TaikoErrors {
// `L1/libs/*.sol`.
error L1_ALREADY_CONTESTED();
error L1_ALREADY_PROVED();
error L1_ASSIGNMENT_EXPIRED();
error L1_ASSIGNMENT_INVALID_SIG();
error L1_ASSIGNMENT_INVALID_PARAMS();
error L1_ASSIGNMENT_INSUFFICIENT_FEE();
error L1_ASSIGNED_PROVER_NOT_ALLOWED();
error L1_BLOB_FOR_DA_DISABLED();
error L1_BLOB_NOT_FOUND();
Expand All @@ -33,13 +29,16 @@ abstract contract TaikoErrors {
error L1_INVALID_PARAM();
error L1_INVALID_PAUSE_STATUS();
error L1_INVALID_PROOF();
error L1_INVALID_PROVER();
error L1_INVALID_TIER();
error L1_INVALID_TRANSITION();
error L1_LIVENESS_BOND_NOT_RECEIVED();
error L1_NOT_ASSIGNED_PROVER();
error L1_PROPOSER_NOT_EOA();
error L1_PROVING_PAUSED();
error L1_TIER_NOT_FOUND();
error L1_RECEIVE_DISABLED();
error L1_TOO_MANY_BLOCKS();
error L1_TOO_MANY_TIERS();
error L1_TRANSITION_ID_ZERO();
error L1_TRANSITION_NOT_FOUND();
error L1_TXLIST_OFFSET_SIZE();
Expand Down
2 changes: 0 additions & 2 deletions packages/protocol/contracts/L1/TaikoEvents.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,13 @@ abstract contract TaikoEvents {
/// @param blockId The ID of the proposed block.
/// @param assignedProver The block's assigned prover.
/// @param livenessBond The bond in Taiko token from the assigned prover.
/// @param proverFee The fee paid to the assigned prover.
/// @param meta The block metadata containing information about the proposed
/// block.
/// @param depositsProcessed Ether deposits processed.
event BlockProposed(
uint256 indexed blockId,
address indexed assignedProver,
uint96 livenessBond,
uint256 proverFee,
TaikoData.BlockMetadata meta,
TaikoData.EthDeposit[] depositsProcessed
);
Expand Down
34 changes: 10 additions & 24 deletions packages/protocol/contracts/L1/TaikoL1.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,13 @@

pragma solidity ^0.8.20;

import "../common/AddressResolver.sol";
import "../common/EssentialContract.sol";
import "../common/ICrossChainSync.sol";
import "../common/Proxied.sol";
import "./libs/LibDepositing.sol";
import "./libs/LibProposing.sol";
import "./libs/LibProving.sol";
import "./libs/LibTaikoToken.sol";
import "./libs/LibUtils.sol";
import "./libs/LibVerifying.sol";
import "./TaikoData.sol";
import "./TaikoErrors.sol";
import "./TaikoEvents.sol";
import "./tiers/ITierProvider.sol";

/// @title TaikoL1
/// @dev Labeled in AddressResolver as "taiko"
Expand All @@ -29,15 +22,14 @@ import "./tiers/ITierProvider.sol";
/// deployed on L1, it can also be deployed on L2s to create L3s ("inception
/// layers"). The contract also handles the deposit and withdrawal of Taiko
/// tokens and Ether.
/// This contract doesn't hold any Ether. Ether deposited to L2 are held by the Bridge contract.
contract TaikoL1 is EssentialContract, ICrossChainSync, ITierProvider, TaikoEvents, TaikoErrors {
TaikoData.State public state;
uint256[100] private __gap;

error L1_TOO_MANY_TIERS();

/// @dev Fallback function to receive Ether and deposit to Layer 2.
/// @dev Fallback function to receive Ether from Hooks
receive() external payable {
depositEtherToL2(address(0));
if (!_inNonReentrant()) revert L1_RECEIVE_DISABLED();
}

/// @notice Initializes the rollup.
Expand Down Expand Up @@ -67,8 +59,10 @@ contract TaikoL1 is EssentialContract, ICrossChainSync, ITierProvider, TaikoEven
)
{
TaikoData.Config memory config = getConfig();

(meta, depositsProcessed) =
LibProposing.proposeBlock(state, config, AddressResolver(this), params, txList);

if (!state.slotB.provingPaused && config.maxBlocksToVerifyPerProposal > 0) {
LibVerifying.verifyBlocks(
state, config, AddressResolver(this), config.maxBlocksToVerifyPerProposal
Expand All @@ -82,6 +76,8 @@ contract TaikoL1 is EssentialContract, ICrossChainSync, ITierProvider, TaikoEven
/// @param input An abi-encoded (BlockMetadata, Transition, TierProof)
/// tuple.
function proveBlock(uint64 blockId, bytes calldata input) external nonReentrant whenNotPaused {
if (state.slotB.provingPaused) revert L1_PROVING_PAUSED();

(
TaikoData.BlockMetadata memory meta,
TaikoData.Transition memory tran,
Expand All @@ -91,8 +87,10 @@ contract TaikoL1 is EssentialContract, ICrossChainSync, ITierProvider, TaikoEven
if (blockId != meta.id) revert L1_INVALID_BLOCK_ID();

TaikoData.Config memory config = getConfig();

uint8 maxBlocksToVerify =
LibProving.proveBlock(state, config, AddressResolver(this), meta, tran, proof);

if (maxBlocksToVerify > 0) {
LibVerifying.verifyBlocks(state, config, AddressResolver(this), maxBlocksToVerify);
}
Expand All @@ -113,22 +111,10 @@ contract TaikoL1 is EssentialContract, ICrossChainSync, ITierProvider, TaikoEven
LibProving.pauseProving(state, pause);
}

/// @notice Deposit Taiko token to this contract
/// @param amount Amount of Taiko token to deposit.
function depositTaikoToken(uint256 amount) external whenNotPaused {
LibTaikoToken.depositTaikoToken(state, AddressResolver(this), amount);
}

/// @notice Withdraw Taiko token from this contract
/// @param amount Amount of Taiko token to withdraw.
function withdrawTaikoToken(uint256 amount) external whenNotPaused {
LibTaikoToken.withdrawTaikoToken(state, AddressResolver(this), amount);
}

/// @notice Deposits Ether to Layer 2.
/// @param recipient Address of the recipient for the deposited Ether on
/// Layer 2.
function depositEtherToL2(address recipient) public payable whenNotPaused {
function depositEtherToL2(address recipient) external payable whenNotPaused {
LibDepositing.depositEtherToL2(state, getConfig(), AddressResolver(this), recipient);
}

Expand Down
3 changes: 1 addition & 2 deletions packages/protocol/contracts/L1/TaikoToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,12 @@

pragma solidity ^0.8.20;

import "lib/openzeppelin-contracts-upgradeable/contracts/token/ERC20/ERC20Upgradeable.sol";
import
"lib/openzeppelin-contracts-upgradeable/contracts/token/ERC20/extensions/ERC20SnapshotUpgradeable.sol";
import "lib/openzeppelin-contracts-upgradeable/contracts/token/ERC20/ERC20Upgradeable.sol";
import
"lib/openzeppelin-contracts-upgradeable/contracts/token/ERC20/extensions/ERC20VotesUpgradeable.sol";
import "../common/EssentialContract.sol";
import "../common/Proxied.sol";

/// @title TaikoToken
/// @dev Labeled in AddressResolver as "taiko_token"
Expand Down
2 changes: 1 addition & 1 deletion packages/protocol/contracts/L1/gov/TaikoGovernor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ contract TaikoGovernor is
IVotes _token,
TimelockController _timelock
)
Governor("MyGovernor")
Governor("TaikoGovernor")
GovernorVotes(_token)
GovernorVotesQuorumFraction(4)
GovernorTimelockControl(_timelock)
Expand Down
171 changes: 171 additions & 0 deletions packages/protocol/contracts/L1/hooks/AssignmentHook.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
// SPDX-License-Identifier: MIT
// _____ _ _ _ _
// |_ _|_ _(_) |_____ | | __ _| |__ ___
// | |/ _` | | / / _ \ | |__/ _` | '_ (_-<
// |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/

pragma solidity ^0.8.20;

import "lib/openzeppelin-contracts-upgradeable/contracts/token/ERC20/ERC20Upgradeable.sol";
import "../../common/EssentialContract.sol";
import "../../libs/LibAddress.sol";
import "../TaikoData.sol";
import "../TaikoToken.sol";
import "./IHook.sol";

/// @title AssignmentHook
/// A hook that handles prover assignment varification and fee processing.
contract AssignmentHook is EssentialContract, IHook {
using LibAddress for address;

struct ProverAssignment {
address feeToken;
uint64 expiry;
uint64 maxBlockId;
uint64 maxProposedIn;
bytes32 metaHash;
TaikoData.TierFee[] tierFees;
bytes signature;
}

struct Input {
ProverAssignment assignment;
uint256 tip;
}

// Max gas paying the prover. This should be large enough to prevent the
// worst cases, usually block proposer shall be aware the risks and only
// choose provers that cannot consume too much gas when receiving Ether.
uint256 public constant MAX_GAS_PAYING_PROVER = 200_000;

event BlockAssigned(
address indexed assignedProver, TaikoData.BlockMetadata meta, ProverAssignment assignment
);

error HOOK_ASSIGNMENT_EXPIRED();
error HOOK_ASSIGNMENT_INVALID_SIG();
error HOOK_ASSIGNMENT_INSUFFICIENT_FEE();
error HOOK_TIER_NOT_FOUND();

function init(address _addressManager) external initializer {
EssentialContract._init(_addressManager);
}

function onBlockProposed(
TaikoData.Block memory blk,
TaikoData.BlockMetadata memory meta,
bytes memory data
)
external
payable
nonReentrant
onlyFromNamed("taiko")
{
Input memory input = abi.decode(data, (Input));
ProverAssignment memory assignment = input.assignment;

// Check assignment validity
if (
block.timestamp > assignment.expiry
|| assignment.metaHash != 0 && blk.metaHash != assignment.metaHash
|| assignment.maxBlockId != 0 && meta.id > assignment.maxBlockId
|| assignment.maxProposedIn != 0 && block.number > assignment.maxProposedIn
) {
revert HOOK_ASSIGNMENT_EXPIRED();
}

// Hash the assignment with the blobHash, this hash will be signed by
// the prover, therefore, we add a string as a prefix.
bytes32 hash = hashAssignment(assignment, msg.sender, meta.blobHash);

if (!blk.assignedProver.isValidSignature(hash, assignment.signature)) {
revert HOOK_ASSIGNMENT_INVALID_SIG();
}

// Send the liveness bond to the Taiko contract
TaikoToken tko = TaikoToken(resolve("taiko_token", false));
tko.transferFrom(blk.assignedProver, msg.sender, blk.livenessBond);

// Find the prover fee using the minimal tier
uint256 proverFee = _getProverFee(assignment.tierFees, meta.minTier);

// The proposer irrevocably pays a fee to the assigned prover, either in
// Ether or ERC20 tokens.
uint256 refund;
if (assignment.feeToken == address(0)) {
if (msg.value < proverFee + input.tip) {
revert HOOK_ASSIGNMENT_INSUFFICIENT_FEE();
}

unchecked {
refund = msg.value - proverFee - input.tip;
}

// Paying Ether
blk.assignedProver.sendEther(proverFee, MAX_GAS_PAYING_PROVER);
} else {
if (msg.value < input.tip) {
revert HOOK_ASSIGNMENT_INSUFFICIENT_FEE();
}
unchecked {
refund = msg.value - input.tip;
}
// Paying ERC20 tokens
ERC20Upgradeable(assignment.feeToken).transferFrom(
msg.sender, blk.assignedProver, proverFee
);
}

// block.coinbase can be address(0) in tests
if (input.tip != 0 && block.coinbase != address(0)) {
address(block.coinbase).sendEther(input.tip);
}

if (refund != 0) {
msg.sender.sendEther(refund);
}

emit BlockAssigned(blk.assignedProver, meta, assignment);
}

function hashAssignment(
ProverAssignment memory assignment,
address taikoAddress,
bytes32 blobHash
)
public
pure
returns (bytes32)
{
return keccak256(
abi.encode(
"PROVER_ASSIGNMENT",
taikoAddress,
blobHash,
assignment.feeToken,
assignment.expiry,
assignment.maxBlockId,
assignment.maxProposedIn,
assignment.tierFees
)
);
}

function _getProverFee(
TaikoData.TierFee[] memory tierFees,
uint16 tierId
)
private
pure
returns (uint256)
{
for (uint256 i; i < tierFees.length; ++i) {
if (tierFees[i].tier == tierId) return tierFees[i].fee;
}
revert HOOK_TIER_NOT_FOUND();
}
}

/// @title ProxiedAssignmentHook
/// @notice Proxied version of the parent contract.
contract ProxiedAssignmentHook is Proxied, AssignmentHook { }
20 changes: 20 additions & 0 deletions packages/protocol/contracts/L1/hooks/IHook.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// SPDX-License-Identifier: MIT
// _____ _ _ _ _
// |_ _|_ _(_) |_____ | | __ _| |__ ___
// | |/ _` | | / / _ \ | |__/ _` | '_ (_-<
// |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/

pragma solidity ^0.8.20;

import "../TaikoData.sol";

/// @title IHook Interface
interface IHook {
function onBlockProposed(
TaikoData.Block memory blk,
TaikoData.BlockMetadata memory meta,
bytes memory data
)
external
payable;
}
2 changes: 1 addition & 1 deletion packages/protocol/contracts/L1/libs/LibDepositing.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ pragma solidity ^0.8.20;
import "../../common/AddressResolver.sol";
import "../../libs/LibAddress.sol";
import "../../libs/LibMath.sol";

import "../TaikoData.sol";
import "../TaikoToken.sol";

/// @title LibDepositing
/// @notice A library for handling Ether deposits in the Taiko protocol.
Expand Down
Loading

0 comments on commit d61af90

Please sign in to comment.