Skip to content

feat(l2): unlock based on gasProven #3769

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

Draft
wants to merge 15 commits into
base: main
Choose a base branch
from
Draft
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
5 changes: 5 additions & 0 deletions crates/l2/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ clean-contract-deps: ## 🧹 Cleans the dependencies for the L1 contracts.

restart-contract-deps: clean-contract-deps ## 🔄 Restarts the dependencies for the L1 contracts.

# TODO: add a new target for based deployments
deploy-l1: ## 📜 Deploys the L1 contracts
COMPILE_CONTRACTS=true \
cargo run --release --bin ethrex_l2_l1_deployer --manifest-path contracts/Cargo.toml -- \
Expand All @@ -113,6 +114,8 @@ deploy-l1: ## 📜 Deploys the L1 contracts
--deposit-rich \
--private-keys-file-path ../../fixtures/keys/private_keys_l1.txt \
--genesis-l1-path ../../fixtures/genesis/l1-dev.json \
--deploy-based-contracts \
--sequencer-registry-owner 0xacb3bb54d7c5295c158184044bdeedd9aa426607 \
--genesis-l2-path ../../fixtures/genesis/l2.json

## Same as deploy-l1 but does not do deposits for rich accounts since that doesn't make sense for deployments to devnets/testnets i.e Sepolia
Expand Down Expand Up @@ -152,6 +155,8 @@ init-l2-no-metrics: ## 🚀 Initializes an L2 Lambda ethrex Client
--committer.l1-private-key 0x385c546456b6a603a1cfcaa9ec9494ba4832da08dd6bcf4de9a71e4a01b74924 \
--proof-coordinator.l1-private-key 0x39725efee3fb28614de3bacaffe4cc4bd8c436257e2c8bb887c4b5c4be45e76d \
--proof-coordinator.addr ${PROOF_COORDINATOR_ADDRESS} \
--based \
--state-updater.sequencer-registry 0x8d7a798b208adfcaed7716424dbe768642736070 \
--proof-coordinator.tdx-private-key 0x39725efee3fb28614de3bacaffe4cc4bd8c436257e2c8bb887c4b5c4be45e76d

init-metrics: ## 🚀 Initializes Grafana and Prometheus with containers
Expand Down
45 changes: 45 additions & 0 deletions crates/l2/contracts/bin/deployer/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ pub struct ContractAddresses {
pub tdx_verifier_address: Address,
pub sequencer_registry_address: Address,
pub aligned_aggregator_address: Address,
pub reward_vault_address: Address,
}

#[tokio::main]
Expand Down Expand Up @@ -103,6 +104,11 @@ fn compile_contracts(opts: &DeployerOptions) -> Result<(), DeployerError> {
)?;
if opts.deploy_based_contracts {
info!("Compiling based contracts");
compile_contract(
&opts.contracts_path,
"lib/openzeppelin-contracts-upgradeable/contracts/utils/ReentrancyGuardUpgradeable.sol",
false
)?;
compile_contract(
&opts.contracts_path,
"src/l1/based/SequencerRegistry.sol",
Expand All @@ -113,6 +119,7 @@ fn compile_contracts(opts: &DeployerOptions) -> Result<(), DeployerError> {
"src/l1/based/OnChainProposer.sol",
false,
)?;
compile_contract(&opts.contracts_path, "src/l1/based/RewardVault.sol", false)?;
} else {
info!("Compiling OnChainProposer contract");
compile_contract(&opts.contracts_path, "src/l1/OnChainProposer.sol", false)?;
Expand Down Expand Up @@ -165,6 +172,24 @@ async fn deploy_contracts(
on_chain_proposer_deployment.implementation_tx_hash,
);

info!("Deploying RewardVault");

let reward_vault_deployment = deploy_with_proxy(
deployer,
eth_client,
&opts.contracts_path.join("solc_out/RewardVault.bin"),
&salt,
)
.await?;

info!(
"RewardVault deployed:\n Proxy -> address={:#x}, tx_hash={:#x}\n Impl -> address={:#x}, tx_hash={:#x}",
reward_vault_deployment.proxy_address,
reward_vault_deployment.proxy_tx_hash,
reward_vault_deployment.implementation_address,
reward_vault_deployment.implementation_tx_hash,
);

info!("Deploying CommonBridge");

let bridge_deployment = deploy_with_proxy(
Expand Down Expand Up @@ -265,6 +290,7 @@ async fn deploy_contracts(
tdx_verifier_address,
sequencer_registry_address: sequencer_registry_deployment.proxy_address,
aligned_aggregator_address: opts.aligned_aggregator_address,
reward_vault_address: reward_vault_deployment.proxy_address,
})
}

Expand Down Expand Up @@ -388,6 +414,25 @@ async fn initialize_contracts(
.await?
};
info!(tx_hash = %format!("{initialize_tx_hash:#x}"), "SequencerRegistry initialized");

info!("Deploying RewardVault...");

let reward_vault_calldata = encode_calldata("initialize(address)", &[
Value::Address(contract_addresses.on_chain_proposer_address),
// Value::Address(Default::default()), /* TODO: set the reward token address */
])?;

info!("Initializing RewardVault");

let reward_vault_initialize_tx_hash = initialize_contract(
contract_addresses.reward_vault_address,
reward_vault_calldata,
&deployer,
eth_client,
)
.await?;

info!(tx_hash = %format!("{reward_vault_initialize_tx_hash:#x}"), "RewardVault initialized");
} else {
// Initialize only OnChainProposer without Based config
let calldata_values = vec![
Expand Down
50 changes: 50 additions & 0 deletions crates/l2/contracts/src/l1/based/OnChainProposer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ contract OnChainProposer is
/// @dev The key is the batch number.
mapping(uint256 => BatchCommitmentInfo) public batchCommitments;

/// @notice The addresses of the verified batches.
/// @dev The key is the batch number, the value is the address of the prover.
// TODO: consider replacing it with a Merkle tree or just capping the number of verified batches.
// TODO: Consider moving this mapping to a separate contract if needed.
mapping(uint256 => VerifiedBatchInfo) _verifiedBatches;

/// @notice The latest verified batch number.
/// @dev This variable holds the batch number of the most recently verified batch.
/// @dev All batches with a batch number less than or equal to `lastVerifiedBatch` are considered verified.
Expand Down Expand Up @@ -339,6 +345,8 @@ contract OnChainProposer is
);
}

uint256 gasProven = 0;

if (R0VERIFIER != DEV_MODE) {
// If the verification fails, it will revert.
_verifyPublicData(batchNumber, risc0Journal);
Expand All @@ -347,8 +355,12 @@ contract OnChainProposer is
RISC0_VERIFICATION_KEY,
sha256(risc0Journal)
);
gasProven = uint256(
bytes32(risc0Journal[224:256])
);
}


if (SP1VERIFIER != DEV_MODE) {
// If the verification fails, it will revert.
_verifyPublicData(batchNumber, sp1PublicValues[16:]);
Expand All @@ -357,6 +369,9 @@ contract OnChainProposer is
sp1PublicValues,
sp1ProofBytes
);
gasProven = uint256(
bytes32(sp1PublicValues[16+224:16+256])
);
}

if (TDXVERIFIER != DEV_MODE) {
Expand All @@ -367,6 +382,10 @@ contract OnChainProposer is

lastVerifiedBatch = batchNumber;

// Update verified batches with the new batch
address message_sender = msg.sender;
addVerifiedBatch(batchNumber, message_sender, gasProven);

// Remove previous batch commitment as it is no longer needed.
delete batchCommitments[batchNumber - 1];

Expand Down Expand Up @@ -444,6 +463,34 @@ contract OnChainProposer is
emit BatchVerified(lastVerifiedBatch);
}

// @inheritdoc IOnChainProposer
function verifiedBatches(uint256 batchNumber) public view returns (VerifiedBatchInfo memory) {
return _verifiedBatches[batchNumber];
}

// @inheritdoc IOnChainProposer
function addVerifiedBatch(uint256 batchNumber, address prover, uint256 gasProven) internal {
_verifiedBatches[batchNumber] = VerifiedBatchInfo({
prover: prover,
gasProven: gasProven,
verificationTimestamp: block.timestamp
});
}

// @inheritdoc IOnChainProposer
function getTotalGasProven() public view returns (uint256) {
uint256 totalGasProven = 0;
uint256 oneDayAgo = block.timestamp - 1 days;

// only take into account batches proven in the last day
for (uint256 i = 0; i <= lastVerifiedBatch; i++) {
if (_verifiedBatches[i].verificationTimestamp >= oneDayAgo) {
totalGasProven += _verifiedBatches[i].gasProven;
}
}
return totalGasProven;
}

function _verifyPublicData(
uint256 batchNumber,
bytes calldata publicData
Expand Down Expand Up @@ -489,6 +536,9 @@ contract OnChainProposer is
uint256 nonPrivilegedTransactions = uint256(
bytes32(publicData[192:224])
);

// TODO: consider checking the gasProven here.

require(
!ICommonBridge(BRIDGE).hasExpiredPrivilegedTransactions() ||
nonPrivilegedTransactions == 0,
Expand Down
55 changes: 55 additions & 0 deletions crates/l2/contracts/src/l1/based/RewardVault.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// SPDX-License-Identifier: MIT
pragma solidity =0.8.29;

import "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import "./interfaces/IOnChainProposer.sol";

contract RewardVault is Initializable, ReentrancyGuardUpgradeable {
IOnChainProposer public onChainProposer;
// It must implement IERC20 and IERC20Metadata
address public rewardToken;

// TODO: we should replace this value with a mechanism that changes over time.
uint256 public tokensUnlockedPerDay;

/// @notice Keep record of which batches have been claimed.
mapping(uint256 => bool) public claimedBatches;

function initialize(address _onChainProposer /* address _rewardToken */) public initializer {
onChainProposer = IOnChainProposer(_onChainProposer);
// rewardToken = IERC20(_rewardToken);
// TODO: change this value to a mechanism that changes over time.
tokensUnlockedPerDay = 1_000_000;
}

/// @notice Claims rewards for a list of batches.
/// @param _batchNumbers The list of batch numbers to claim rewards for.
function claimRewards(uint256[] calldata _batchNumbers) external nonReentrant {
address sender = msg.sender;
uint256 numberOfBatches = _batchNumbers.length;
uint256 gasProvenByClaimer = 0;

for (uint256 i = 0; i < numberOfBatches; i++) {
uint256 batchNumber = _batchNumbers[i];
VerifiedBatchInfo memory verifiedBatchInfo = onChainProposer.verifiedBatches(batchNumber);
require(verifiedBatchInfo.prover == sender, "Sender must be the prover address");
require(!claimedBatches[batchNumber], "Batch already claimed");
gasProvenByClaimer += verifiedBatchInfo.gasProven;
}

// calculate the rewards for the prover and transfer them
uint256 totalGasProven = onChainProposer.getTotalGasProven();
uint256 dailyRewardPool = tokensUnlockedPerDay * 10 ** IERC20Metadata(rewardToken).decimals();
uint256 totalRewards = dailyRewardPool * gasProvenByClaimer / totalGasProven;

// mark the batches as claimed, so they cannot be claimed again
for (uint256 i = 0; i < numberOfBatches; i++) {
claimedBatches[_batchNumbers[i]] = true;
}

// TODO: transfer the tokens
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
// SPDX-License-Identifier: MIT
pragma solidity =0.8.29;

/// @title Holds verified batch information.
struct VerifiedBatchInfo {
/// @notice The address of the prover.
address prover;
/// @notice The amount of gas proven for this batch.
uint256 gasProven;
/// @notice The timestamp of the verification.
uint256 verificationTimestamp;
}

/// @title Interface for the OnChainProposer contract.
/// @author LambdaClass
/// @notice A OnChainProposer contract ensures the advancement of the L2. It is used
Expand Down Expand Up @@ -88,4 +98,14 @@ interface IOnChainProposer {
bytes[] calldata alignedPublicInputsList,
bytes32[][] calldata alignedMerkleProofsList
) external;


/// @notice Get verified batch information for a specific batch number.
/// @param batchNumber The batch number to query.
/// @return The VerifiedBatchInfo struct containing prover address and gas proven.
function verifiedBatches(uint256 batchNumber) external view returns (VerifiedBatchInfo memory);

/// @notice Get the total gas proven across all verified batches.
/// @return The total amount of gas proven.
function getTotalGasProven() external view returns (uint256);
}
10 changes: 10 additions & 0 deletions crates/l2/prover/zkvm/interface/src/execution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ pub fn stateless_validation_l1(
final_state_hash,
last_block_hash,
non_privileged_count,
total_gas_used,
..
} = execute_stateless(blocks, db, elasticity_multiplier)?;
Ok(ProgramOutput {
Expand All @@ -142,6 +143,7 @@ pub fn stateless_validation_l1(
last_block_hash,
chain_id: chain_id.into(),
non_privileged_count,
total_gas_used,
})
}

Expand All @@ -164,6 +166,7 @@ pub fn stateless_validation_l2(
last_block_header,
last_block_hash,
non_privileged_count,
total_gas_used,
} = execute_stateless(blocks, db, elasticity_multiplier)?;

let (l1messages, privileged_transactions) =
Expand Down Expand Up @@ -203,6 +206,7 @@ pub fn stateless_validation_l2(
last_block_hash,
chain_id: chain_id.into(),
non_privileged_count,
total_gas_used,
})
}

Expand All @@ -214,6 +218,7 @@ struct StatelessResult {
last_block_header: BlockHeader,
last_block_hash: H256,
non_privileged_count: U256,
total_gas_used: U256,
}

fn execute_stateless(
Expand Down Expand Up @@ -263,6 +268,8 @@ fn execute_stateless(
let mut acc_account_updates: HashMap<Address, AccountUpdate> = HashMap::new();
let mut acc_receipts = Vec::new();
let mut non_privileged_count = 0;
// Total gas used for the batch
let mut total_gas_used = 0;
for block in blocks {
// Validate the block
validate_block(
Expand Down Expand Up @@ -303,8 +310,10 @@ fn execute_stateless(
non_privileged_count += block.body.transactions.len()
- get_block_privileged_transactions(&block.body.transactions).len();


validate_gas_used(&receipts, &block.header)
.map_err(StatelessExecutionError::GasValidationError)?;
total_gas_used += block.header.gas_used;
validate_receipts_root(&block.header, &receipts)
.map_err(StatelessExecutionError::ReceiptsRootValidationError)?;
// validate_requests_hash doesn't do anything for l2 blocks as this verifies l1 requests (messages, privileged transactions and consolidations)
Expand Down Expand Up @@ -336,6 +345,7 @@ fn execute_stateless(
last_block_header: last_block.header.clone(),
last_block_hash,
non_privileged_count: non_privileged_count.into(),
total_gas_used: total_gas_used.into(),
})
}

Expand Down
3 changes: 3 additions & 0 deletions crates/l2/prover/zkvm/interface/src/io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ pub struct ProgramOutput {
pub chain_id: U256,
/// amount of non-privileged transactions
pub non_privileged_count: U256,
/// total gas used in the batch
pub total_gas_used: U256,
}

impl ProgramOutput {
Expand All @@ -110,6 +112,7 @@ impl ProgramOutput {
self.last_block_hash.to_fixed_bytes(),
self.chain_id.to_big_endian(),
self.non_privileged_count.to_big_endian(),
self.total_gas_used.to_big_endian(),
]
.concat()
}
Expand Down