From 8449804c867109718743e55e03d6ae2f116632c7 Mon Sep 17 00:00:00 2001 From: tiaoxizhan Date: Fri, 13 Sep 2024 18:08:57 +0800 Subject: [PATCH 1/9] Remove unnecessary symbols in comment Signed-off-by: tiaoxizhan --- contracts/rust/adapter/src/jellyfish.rs | 2 +- marketplace-solver/src/state.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/rust/adapter/src/jellyfish.rs b/contracts/rust/adapter/src/jellyfish.rs index 85ef13fa63..b0031c8aaf 100644 --- a/contracts/rust/adapter/src/jellyfish.rs +++ b/contracts/rust/adapter/src/jellyfish.rs @@ -176,7 +176,7 @@ impl From> for ParsedVerifyingKey { // ```rust // let srs = ark_srs::kzg10::aztec20::setup(2u64.pow(6) as usize + 2).expect("Aztec SRS fail to load"); // println!("{}", hex::encode(jf_utils::to_bytes!(&srs.beta_h).unwrap())); - // ```` + // ``` assert_eq!( g2_lsb.encode_hex::(), String::from("b0838893ec1f237e8b07323b0744599f4e97b598b3b589bcc2bc37b8d5c41801") diff --git a/marketplace-solver/src/state.rs b/marketplace-solver/src/state.rs index dd0ea77af3..0e25cd693d 100644 --- a/marketplace-solver/src/state.rs +++ b/marketplace-solver/src/state.rs @@ -203,7 +203,7 @@ impl UpdateSolverState for GlobalState { registration.body.active = active; } - // The given signature key should also be from the database `signature_keys`.` + // The given signature key should also be from the database `signature_keys`. if !registration.body.signature_keys.contains(&signature_key) { return Err(SolverError::SignatureKeysMismatch( signature_key.to_string(), From 6ce0169b57ca2dcf12b58acb964eec041bf26efb Mon Sep 17 00:00:00 2001 From: imabdulbasit Date: Fri, 6 Dec 2024 00:15:26 +0500 Subject: [PATCH 2/9] update query-service --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a0f2c68633..a7096ac47b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4232,7 +4232,7 @@ dependencies = [ [[package]] name = "hotshot-query-service" version = "0.1.75" -source = "git+https://github.com/EspressoSystems/hotshot-query-service?branch=hotshot%2F0.5.82#5e2c984d19da3826f4cc8d80c5cf1a84dcd377f7" +source = "git+https://github.com/EspressoSystems/hotshot-query-service?branch=hotshot%2F0.5.82#31ab748ab89f64b65ded54793825520f297d0dba" dependencies = [ "anyhow", "ark-serialize", @@ -7570,7 +7570,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" dependencies = [ "anyhow", - "itertools 0.12.1", + "itertools 0.11.0", "proc-macro2", "quote", "syn 2.0.87", From b24c6c5ca2e0bd3ab7745deaac25590e223f4964 Mon Sep 17 00:00:00 2001 From: imabdulbasit Date: Fri, 6 Dec 2024 01:15:36 +0500 Subject: [PATCH 3/9] db max connections = 25 for dev node tests --- sequencer/src/bin/espresso-dev-node.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sequencer/src/bin/espresso-dev-node.rs b/sequencer/src/bin/espresso-dev-node.rs index a6e19d41c9..b7cd9b7fe8 100644 --- a/sequencer/src/bin/espresso-dev-node.rs +++ b/sequencer/src/bin/espresso-dev-node.rs @@ -603,6 +603,7 @@ mod tests { "ESPRESSO_SEQUENCER_STORAGE_PATH", tmp_dir.path().as_os_str(), ) + .env("ESPRESSO_SEQUENCER_DATABASE_MAX_CONNECTIONS", "25") .spawn() .unwrap(); @@ -909,6 +910,7 @@ mod tests { "ESPRESSO_SEQUENCER_STORAGE_PATH", tmp_dir.path().as_os_str(), ) + .env("ESPRESSO_SEQUENCER_DATABASE_MAX_CONNECTIONS", "25") .spawn() .unwrap(); From 7fe8751fd0754516bcc081a49962daf576b15605 Mon Sep 17 00:00:00 2001 From: imabdulbasit Date: Fri, 6 Dec 2024 03:05:19 +0500 Subject: [PATCH 4/9] update query-service --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a7096ac47b..bce77eaf02 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4232,7 +4232,7 @@ dependencies = [ [[package]] name = "hotshot-query-service" version = "0.1.75" -source = "git+https://github.com/EspressoSystems/hotshot-query-service?branch=hotshot%2F0.5.82#31ab748ab89f64b65ded54793825520f297d0dba" +source = "git+https://github.com/EspressoSystems/hotshot-query-service?branch=hotshot%2F0.5.82#ab55db87ab86b78e40ef420537192d213c52008d" dependencies = [ "anyhow", "ark-serialize", @@ -7570,7 +7570,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" dependencies = [ "anyhow", - "itertools 0.11.0", + "itertools 0.12.1", "proc-macro2", "quote", "syn 2.0.87", From f3244b9f43994eb72f1834e5130a219e414aef14 Mon Sep 17 00:00:00 2001 From: Artemii Gerasimovich Date: Fri, 6 Dec 2024 02:37:50 +0100 Subject: [PATCH 5/9] Fix no-storage decides --- sequencer/src/persistence/no_storage.rs | 37 +++++++++++-------------- 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/sequencer/src/persistence/no_storage.rs b/sequencer/src/persistence/no_storage.rs index 0b1e4cd44f..f7b34aeb1a 100644 --- a/sequencer/src/persistence/no_storage.rs +++ b/sequencer/src/persistence/no_storage.rs @@ -57,28 +57,23 @@ impl SequencerPersistence for NoStorage { leaves: impl IntoIterator, QuorumCertificate2)> + Send, consumer: &impl EventConsumer, ) -> anyhow::Result<()> { - let (mut leaf_chain, mut qcs): (Vec<_>, Vec<_>) = leaves + let leaves = leaves .into_iter() - .map(|(info, qc)| (info.clone(), qc)) - .unzip(); - - // Put in reverse chronological order, as expected from Decide events. - leaf_chain.reverse(); - qcs.reverse(); - - // Generate decide event for the consumer. - let final_qc = qcs.pop().unwrap(); - - consumer - .handle_event(&Event { - view_number, - event: EventType::Decide { - leaf_chain: Arc::new(leaf_chain), - qc: Arc::new(final_qc), - block_size: None, - }, - }) - .await + .map(|(info_ref, qc)| (info_ref.clone(), qc)) + .collect::>(); + for (leaf_info, qc) in leaves { + consumer + .handle_event(&Event { + view_number, + event: EventType::Decide { + leaf_chain: Arc::new(vec![leaf_info.clone()]), + qc: Arc::new(qc), + block_size: None, + }, + }) + .await?; + } + Ok(()) } async fn load_latest_acted_view(&self) -> anyhow::Result> { From f0bfe82584282b9999d77c3c2a2839d1d8c9e78b Mon Sep 17 00:00:00 2001 From: Artemii Gerasimovich Date: Fri, 6 Dec 2024 03:26:43 +0100 Subject: [PATCH 6/9] Branches -> tags --- Cargo.lock | 45 +++++++++------------------------------------ Cargo.toml | 8 ++++---- 2 files changed, 13 insertions(+), 40 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bce77eaf02..7276550596 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1267,7 +1267,7 @@ dependencies = [ "hotshot", "hotshot-builder-api", "hotshot-builder-core", - "hotshot-events-service 0.1.56 (git+https://github.com/EspressoSystems/hotshot-events-service.git?tag=0.1.56)", + "hotshot-events-service", "hotshot-state-prover", "hotshot-types", "jf-signature 0.2.0", @@ -4051,7 +4051,7 @@ dependencies = [ [[package]] name = "hotshot-builder-core" version = "0.1.57" -source = "git+https://github.com/EspressoSystems/marketplace-builder-core?branch=hotshot%2F0.5.82#be5ab81e83127b665af026ec18dfa48b9aba3bb1" +source = "git+https://github.com/EspressoSystems/marketplace-builder-core?tag=0.1.57#cf9d2cbf154809a31bcf2dccc1218ee9c6875f1e" dependencies = [ "anyhow", "async-broadcast", @@ -4125,33 +4125,6 @@ dependencies = [ "vbs", ] -[[package]] -name = "hotshot-events-service" -version = "0.1.56" -source = "git+https://github.com/EspressoSystems/hotshot-events-service.git?branch=hotshot%2F0.5.82#f7b6da149c2aa3c15996ece0a07d30039f540680" -dependencies = [ - "async-broadcast", - "async-lock 2.8.0", - "async-trait", - "clap", - "derivative", - "derive_more 0.99.18", - "either", - "futures", - "hotshot-types", - "libp2p-identity", - "rand 0.8.5", - "serde", - "snafu 0.8.5", - "tagged-base64", - "tide-disco", - "tokio", - "toml 0.8.19", - "tracing", - "tracing-test", - "vbs", -] - [[package]] name = "hotshot-example-types" version = "0.5.79" @@ -4232,7 +4205,7 @@ dependencies = [ [[package]] name = "hotshot-query-service" version = "0.1.75" -source = "git+https://github.com/EspressoSystems/hotshot-query-service?branch=hotshot%2F0.5.82#ab55db87ab86b78e40ef420537192d213c52008d" +source = "git+https://github.com/EspressoSystems/hotshot-query-service?tag=v0.1.75#dffefa160f441a663723a67bc54efedb11a88b02" dependencies = [ "anyhow", "ark-serialize", @@ -6204,7 +6177,7 @@ dependencies = [ "futures", "hotshot", "hotshot-builder-api", - "hotshot-events-service 0.1.56 (git+https://github.com/EspressoSystems/hotshot-events-service.git?tag=0.1.56)", + "hotshot-events-service", "hotshot-orchestrator", "hotshot-query-service", "hotshot-stake-table", @@ -6230,7 +6203,7 @@ dependencies = [ [[package]] name = "marketplace-builder-core" version = "0.1.57" -source = "git+https://github.com/EspressoSystems/marketplace-builder-core?branch=hotshot%2F0.5.82#be5ab81e83127b665af026ec18dfa48b9aba3bb1" +source = "git+https://github.com/EspressoSystems/marketplace-builder-core?tag=0.1.57#cf9d2cbf154809a31bcf2dccc1218ee9c6875f1e" dependencies = [ "anyhow", "async-broadcast", @@ -6256,7 +6229,7 @@ dependencies = [ [[package]] name = "marketplace-builder-shared" version = "0.1.57" -source = "git+https://github.com/EspressoSystems/marketplace-builder-core?branch=hotshot%2F0.5.82#be5ab81e83127b665af026ec18dfa48b9aba3bb1" +source = "git+https://github.com/EspressoSystems/marketplace-builder-core?tag=0.1.57#cf9d2cbf154809a31bcf2dccc1218ee9c6875f1e" dependencies = [ "anyhow", "async-broadcast", @@ -6271,7 +6244,7 @@ dependencies = [ "hex", "hotshot", "hotshot-builder-api", - "hotshot-events-service 0.1.56 (git+https://github.com/EspressoSystems/hotshot-events-service.git?branch=hotshot%2F0.5.82)", + "hotshot-events-service", "hotshot-example-types", "hotshot-task-impls", "hotshot-testing", @@ -6302,7 +6275,7 @@ dependencies = [ "espresso-types", "futures", "hotshot", - "hotshot-events-service 0.1.56 (git+https://github.com/EspressoSystems/hotshot-events-service.git?tag=0.1.56)", + "hotshot-events-service", "hotshot-query-service", "hotshot-types", "marketplace-solver", @@ -8645,7 +8618,7 @@ dependencies = [ "futures", "hotshot", "hotshot-contract-adapter", - "hotshot-events-service 0.1.56 (git+https://github.com/EspressoSystems/hotshot-events-service.git?tag=0.1.56)", + "hotshot-events-service", "hotshot-example-types", "hotshot-orchestrator", "hotshot-query-service", diff --git a/Cargo.toml b/Cargo.toml index 2e6ef2c62c..011701805d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -67,12 +67,12 @@ tokio = { version = "1", default-features = false, features = [ hotshot = { git = "https://github.com/EspressoSystems/hotshot", tag = "0.5.82" } # Hotshot imports hotshot-builder-api = { git = "https://github.com/EspressoSystems/hotshot", tag = "0.5.82" } -hotshot-builder-core = { git = "https://github.com/EspressoSystems/marketplace-builder-core", branch = "hotshot/0.5.82" } -marketplace-builder-core = { git = "https://github.com/EspressoSystems/marketplace-builder-core", branch = "hotshot/0.5.82" } -marketplace-builder-shared = { git = "https://github.com/EspressoSystems/marketplace-builder-core", branch = "hotshot/0.5.82" } +hotshot-builder-core = { git = "https://github.com/EspressoSystems/marketplace-builder-core", tag = "0.1.57" } +marketplace-builder-core = { git = "https://github.com/EspressoSystems/marketplace-builder-core", tag = "0.1.57" } +marketplace-builder-shared = { git = "https://github.com/EspressoSystems/marketplace-builder-core", tag = "0.1.57" } hotshot-events-service = { git = "https://github.com/EspressoSystems/hotshot-events-service.git", tag = "0.1.56" } hotshot-orchestrator = { git = "https://github.com/EspressoSystems/hotshot", tag = "0.5.82" } -hotshot-query-service = { git = "https://github.com/EspressoSystems/hotshot-query-service", branch = "hotshot/0.5.82" } +hotshot-query-service = { git = "https://github.com/EspressoSystems/hotshot-query-service", tag = "v0.1.75" } hotshot-stake-table = { git = "https://github.com/EspressoSystems/hotshot", tag = "0.5.82" } hotshot-state-prover = { version = "0.1.0", path = "hotshot-state-prover" } hotshot-task = { git = "https://github.com/EspressoSystems/hotshot", tag = "0.5.82" } From bc405da15960d94ff1e527dfa4d816e73a2702bb Mon Sep 17 00:00:00 2001 From: Alysia Tech Date: Fri, 6 Dec 2024 10:17:07 -0500 Subject: [PATCH 7/9] update comment to match implementation (#2282) * align comment with implementation * added more clarity * comment format * clearer comment * update comment to make it clear that there may be outdated elements in the state history --- contracts/src/LightClient.sol | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/contracts/src/LightClient.sol b/contracts/src/LightClient.sol index fcb5ad4985..c38a422672 100644 --- a/contracts/src/LightClient.sol +++ b/contracts/src/LightClient.sol @@ -306,13 +306,16 @@ contract LightClient is Initializable, OwnableUpgradeable, UUPSUpgradeable { } /// @notice Updates the `stateHistoryCommitments` array when a new finalized state is added - /// and ensures that the array does not retain states older than the + /// and prunes the most outdated element starting from the first element if they fall outside + /// the /// `stateHistoryRetentionPeriod`. /// @dev the block timestamp is used to determine if the stateHistoryCommitments array /// should be pruned, based on the stateHistoryRetentionPeriod (seconds). - /// @dev a FIFO approach is used to delete elements from the start of the array, - /// ensuring that only the most recent states that only recent states are kept within - /// the retention window. + /// @dev A FIFO approach is used to remove the most outdated element from the start of the + /// array. + /// However, only one outdated element is removed per invocation of this function, even if + /// multiple elements exceed the retention period. As a result, some outdated elements may + /// remain in the array temporarily until subsequent invocations of this function. /// @dev the `delete` method does not reduce the array length but resets the value at the /// specified index to zero. the stateHistoryFirstIndex variable acts as an offset to indicate /// the starting point for reading the array, since the length of the array is not reduced @@ -426,8 +429,8 @@ contract LightClient is Initializable, OwnableUpgradeable, UUPSUpgradeable { revert InvalidHotShotBlockForCommitmentCheck(); } for (uint256 i = stateHistoryFirstIndex; i < commitmentsHeight; i++) { - // The first commitment greater than the provided height is the root of the tree - // that leaf at that HotShot height + // Finds and returns the first HotShot commitment whose height is greater than + // or equal to the specified HotShot height. if (stateHistoryCommitments[i].hotShotBlockHeight >= hotShotBlockHeight) { return ( stateHistoryCommitments[i].hotShotBlockCommRoot, From 713558d8f8cb140442b39c8184d4da0dc2d1c7ab Mon Sep 17 00:00:00 2001 From: Alysia Tech Date: Fri, 6 Dec 2024 13:51:18 -0500 Subject: [PATCH 8/9] 2368 stake table registration with fixed stake (#2365) * add stake table tests * remove stake types * verify token allowance, balance and reprioritize verification order on registration * set the fixed stake amount, added related tests, updated data types * add more verification checks to the withdraw function * updated errror types * added TODO statements in comments to be explicit about outdated functions that need to be updated to the new spec --- contracts/src/StakeTable.sol | 152 +++++++--- .../src/interfaces/AbstractStakeTable.sol | 29 +- contracts/test/StakeTable.t.sol | 262 ++++++++++++++++++ 3 files changed, 385 insertions(+), 58 deletions(-) create mode 100644 contracts/test/StakeTable.t.sol diff --git a/contracts/src/StakeTable.sol b/contracts/src/StakeTable.sol index 6a809e0c7a..1bff92ea27 100644 --- a/contracts/src/StakeTable.sol +++ b/contracts/src/StakeTable.sol @@ -21,6 +21,9 @@ contract StakeTable is AbstractStakeTable { /// account. error NodeAlreadyRegistered(); + /// Error raised when a user tries to withdraw funds from a node that is not registered. + error NodeNotRegistered(); + /// Error raised when a user tries to make a deposit or request an exit but does not control the /// node public key. error Unauthenticated(); @@ -37,14 +40,24 @@ contract StakeTable is AbstractStakeTable { // Error raised when a user tries to withdraw funds before the exit escrow period is over. error PrematureWithdrawal(); + // Error raised when this contract does not have the sufficient allowance on the stake ERC20 + // token + error InsufficientAllowance(uint256, uint256); + + // Error raised when the staker does not have the sufficient balance on the stake ERC20 token + error InsufficientBalance(uint256); + + // Error raised when the staker does not have the sufficient stake balance to withdraw + error InsufficientStakeBalance(uint256); + + // Error raised when the staker does not register with the correct stakeAmount + error InsufficientStakeAmount(uint256); + /// Mapping from a hash of a BLS key to a node struct defined in the abstract contract. mapping(bytes32 keyHash => Node node) public nodes; - /// Total native stake locked for the latest stake table (HEAD). - uint256 public totalNativeStake; - - /// Total restaked stake locked for the latest stake table (HEAD). - uint256 public totalRestakedStake; + /// Total stake locked; + uint256 public totalStake; /// Address of the native token contract. address public tokenAddress; @@ -97,18 +110,11 @@ contract StakeTable is AbstractStakeTable { return 0; } - /// @notice Total stakes of the registered keys in the latest stake table (Head). - /// @dev Given that the current implementation does not support restaking, the second value of - /// the output is set to 0. - /// @return The total stake for native token and restaked token respectively. - function totalStake() external view override returns (uint256, uint256) { - return (totalNativeStake, totalRestakedStake); - } - /// @notice Look up the balance of `blsVK` /// @param blsVK BLS public key controlled by the user. /// @return Current balance owned by the user. - function lookupStake(BN254.G2Point memory blsVK) external view override returns (uint64) { + /// TODO modify this according to the current spec + function lookupStake(BN254.G2Point memory blsVK) external view override returns (uint256) { Node memory node = this.lookupNode(blsVK); return node.balance; } @@ -117,11 +123,13 @@ contract StakeTable is AbstractStakeTable { /// @dev The lookup is achieved by hashing first the four field elements of blsVK using /// keccak256. /// @return Node indexed by blsVK + /// TODO modify this according to the current spec function lookupNode(BN254.G2Point memory blsVK) external view override returns (Node memory) { return nodes[_hashBlsKey(blsVK)]; } /// @notice Get the next available epoch and queue size in that epoch + /// TODO modify this according to the current spec function nextRegistrationEpoch() external view override returns (uint64, uint64) { uint64 epoch; uint64 queueSize; @@ -143,17 +151,20 @@ contract StakeTable is AbstractStakeTable { // @param epoch next available registration epoch // @param queueSize current size of the registration queue (after insertion of new element in // the queue) + /// TODO modify this according to the current spec function appendRegistrationQueue(uint64 epoch, uint64 queueSize) private { firstAvailableRegistrationEpoch = epoch; _numPendingRegistrations = queueSize + 1; } /// @notice Get the number of pending registration requests in the waiting queue + /// TODO modify this according to the current spec function numPendingRegistrations() external view override returns (uint64) { return _numPendingRegistrations; } /// @notice Get the next available epoch for exit and queue size in that epoch + /// TODO modify this according to the current spec function nextExitEpoch() external view override returns (uint64, uint64) { uint64 epoch; uint64 queueSize; @@ -174,12 +185,14 @@ contract StakeTable is AbstractStakeTable { // @notice Update the exit queue // @param epoch next available exit epoch // @param queueSize current size of the exit queue (after insertion of new element in the queue) + /// TODO modify this according to the current spec function appendExitQueue(uint64 epoch, uint64 queueSize) private { firstAvailableExitEpoch = epoch; _numPendingExits = queueSize + 1; } /// @notice Get the number of pending exit requests in the waiting queue + /// TODO modify this according to the current spec function numPendingExits() external view override returns (uint64) { return _numPendingExits; } @@ -198,6 +211,7 @@ contract StakeTable is AbstractStakeTable { /// withdraw. /// @param node node which is assigned an exit escrow period. /// @return Number of epochs post exit after which funds can be withdrawn. + /// TODO modify this according to the current spec function exitEscrowPeriod(Node memory node) public pure returns (uint64) { if (node.balance > 100) { return 10; @@ -211,29 +225,56 @@ contract StakeTable is AbstractStakeTable { /// @param blsVK The BLS verification key /// @param schnorrVK The Schnorr verification key (as the auxiliary info) /// @param amount The amount to register - /// @param stakeType The type of staking (native or restaking) /// @param blsSig The BLS signature that authenticates the ethereum account this function is /// called from /// @param validUntilEpoch The maximum epoch the sender is willing to wait to be included /// (cannot be smaller than the current epoch) /// - /// @dev No validity check on `schnorrVK`, as it's assumed to be sender's responsibility, - /// the contract only treat it as auxiliary info submitted by `blsVK`. - /// @dev `blsSig` field is necessary to prevent "rogue public-key attack". + /// @dev The function will revert if the sender does not have the correct stake amount. + /// @dev The function will revert if the sender does not have the correct allowance. + /// @dev The function will revert if the sender does not have the correct balance. + /// @dev The function will revert if the sender does not have the correct BLS signature. + /// `blsSig` field is necessary to prevent "rogue public-key attack". /// The signature is over the caller address of the function to ensure that each message is /// unique. + /// @dev No validity check on `schnorrVK`, as it's assumed to be sender's responsibility, + /// the contract only treat it as auxiliary info submitted by `blsVK`. + /// @dev The function will revert if the sender does not have the correct registration epoch. function register( BN254.G2Point memory blsVK, EdOnBN254.EdOnBN254Point memory schnorrVK, - uint64 amount, - StakeType stakeType, + uint256 amount, BN254.G1Point memory blsSig, uint64 validUntilEpoch ) external override { - if (stakeType != StakeType.Native) { - revert RestakingNotImplemented(); + uint256 fixedStakeAmount = minStakeAmount(); + + // Verify that the sender amount is the minStakeAmount + if (amount < fixedStakeAmount) { + revert InsufficientStakeAmount(amount); } + bytes32 key = _hashBlsKey(blsVK); + Node memory node = nodes[key]; + + // Verify that the node is not already registered. + if (node.account != address(0x0)) { + revert NodeAlreadyRegistered(); + } + + // Verify that this contract has permissions to access the validator's stake token. + uint256 allowance = ERC20(tokenAddress).allowance(msg.sender, address(this)); + if (allowance < fixedStakeAmount) { + revert InsufficientAllowance(allowance, fixedStakeAmount); + } + + // Verify that the validator has the balance for this stake token. + uint256 balance = ERC20(tokenAddress).balanceOf(msg.sender); + if (balance < fixedStakeAmount) { + revert InsufficientBalance(balance); + } + + // Verify that the validator can sign for that blsVK bytes memory message = abi.encode(msg.sender); BLSSig.verifyBlsSig(message, blsSig, blsVK); @@ -247,42 +288,36 @@ contract StakeTable is AbstractStakeTable { } appendRegistrationQueue(registerEpoch, queueSize); - bytes32 key = _hashBlsKey(blsVK); - Node memory node = nodes[key]; + // Transfer the stake amount of ERC20 tokens from the sender to this contract. + SafeTransferLib.safeTransferFrom( + ERC20(tokenAddress), msg.sender, address(this), fixedStakeAmount + ); - // The node must not already be registered. - if (node.account != address(0x0)) { - revert NodeAlreadyRegistered(); - } + // Update the total staked amount + totalStake += fixedStakeAmount; // Create an entry for the node. node.account = msg.sender; - node.balance = amount; - node.stakeType = stakeType; + node.balance = fixedStakeAmount; node.schnorrVK = schnorrVK; node.registerEpoch = registerEpoch; nodes[key] = node; - // Lock the deposited tokens in this contract. - if (stakeType == StakeType.Native) { - totalNativeStake += amount; - SafeTransferLib.safeTransferFrom(ERC20(tokenAddress), msg.sender, address(this), amount); - } // Other case will be implemented when we support restaking - - emit Registered(key, registerEpoch, stakeType, amount); + emit Registered(key, registerEpoch, fixedStakeAmount); } /// @notice Deposit more stakes to registered keys /// @dev TODO this implementation will be revisited later. See /// https://github.com/EspressoSystems/espresso-sequencer/issues/806 + /// @dev TODO modify this according to the current spec /// @param blsVK The BLS verification key /// @param amount The amount to deposit /// @return (newBalance, effectiveEpoch) the new balance effective at a future epoch - function deposit(BN254.G2Point memory blsVK, uint64 amount) + function deposit(BN254.G2Point memory blsVK, uint256 amount) external override - returns (uint64, uint64) + returns (uint256, uint64) { bytes32 key = _hashBlsKey(blsVK); Node memory node = nodes[key]; @@ -315,6 +350,7 @@ contract StakeTable is AbstractStakeTable { /// @notice Request to exit from the stake table, not immediately withdrawable! /// + /// @dev TODO modify this according to the current spec /// @param blsVK The BLS verification key to exit function requestExit(BN254.G2Point memory blsVK) external override { bytes32 key = _hashBlsKey(blsVK); @@ -349,19 +385,53 @@ contract StakeTable is AbstractStakeTable { /// withdraw past their `exitEpoch`. /// /// @param blsVK The BLS verification key to withdraw + /// @param blsSig The BLS signature that authenticates the ethereum account this function is + /// called from the caller /// @return The total amount withdrawn, equal to `Node.balance` associated with `blsVK` - function withdrawFunds(BN254.G2Point memory blsVK) external override returns (uint64) { + /// TODO: This function should be tested + /// TODO modify this according to the current spec + + function withdrawFunds(BN254.G2Point memory blsVK, BN254.G1Point memory blsSig) + external + override + returns (uint256) + { bytes32 key = _hashBlsKey(blsVK); Node memory node = nodes[key]; + // Verify that the node is already registered. + if (node.account == address(0)) { + revert NodeNotRegistered(); + } + + // Verify that the balance is greater than zero + uint256 balance = node.balance; + if (balance == 0) { + revert InsufficientStakeBalance(0); + } + + // Verify that the validator can sign for that blsVK + bytes memory message = abi.encode(msg.sender); + BLSSig.verifyBlsSig(message, blsSig, blsVK); + + // Verify that the exit escrow period is over. if (currentEpoch() < node.exitEpoch + exitEscrowPeriod(node)) { revert PrematureWithdrawal(); } - uint64 balance = node.balance; + + // Delete the node from the stake table. delete nodes[key]; + // Transfer the balance to the node's account. SafeTransferLib.safeTransfer(ERC20(tokenAddress), node.account, balance); return balance; } + + /// @notice Minimum stake amount + /// @return Minimum stake amount + /// TODO: This value should be a variable modifiable by admin + function minStakeAmount() public pure returns (uint256) { + return 10 ether; + } } diff --git a/contracts/src/interfaces/AbstractStakeTable.sol b/contracts/src/interfaces/AbstractStakeTable.sol index d5a37ba099..62ff0fc5e3 100644 --- a/contracts/src/interfaces/AbstractStakeTable.sol +++ b/contracts/src/interfaces/AbstractStakeTable.sol @@ -26,11 +26,8 @@ abstract contract AbstractStakeTable { /// @notice Signals a registration of a BLS public key. /// @param blsVKhash hash of the BLS public key that is registered. /// @param registerEpoch epoch when the registration becomes effective. - /// @param stakeType native or restake token. /// @param amountDeposited amount deposited when registering the new node. - event Registered( - bytes32 blsVKhash, uint64 registerEpoch, StakeType stakeType, uint256 amountDeposited - ); + event Registered(bytes32 blsVKhash, uint64 registerEpoch, uint256 amountDeposited); /// @notice Signals an exit request has been granted. /// @param blsVKhash hash of the BLS public key owned by the user who requested to exit. @@ -55,15 +52,13 @@ abstract contract AbstractStakeTable { /// @notice Represents a HotShot validator node /// In the dual-staking model, a HotShot validator could have multiple `Node` entries. /// @param account The Ethereum account of the validator. - /// @param stakeType The type of token staked. /// @param balance The amount of token staked. /// @param registerEpoch The starting epoch for the validator. /// @param exitEpoch The ending epoch for the validator. /// @param schnorrVK The Schnorr verification key associated. struct Node { address account; - StakeType stakeType; - uint64 balance; + uint256 balance; uint64 registerEpoch; uint64 exitEpoch; EdOnBN254.EdOnBN254Point schnorrVK; @@ -71,11 +66,8 @@ abstract contract AbstractStakeTable { // === Table State & Stats === - /// @notice Total stakes of the registered keys in the latest stake table (Head). - /// @return The total stake for native token and restaked token respectively. - function totalStake() external view virtual returns (uint256, uint256); /// @notice Look up the balance of `blsVK` - function lookupStake(BN254.G2Point memory blsVK) external view virtual returns (uint64); + function lookupStake(BN254.G2Point memory blsVK) external view virtual returns (uint256); /// @notice Look up the full `Node` state associated with `blsVK` function lookupNode(BN254.G2Point memory blsVK) external view virtual returns (Node memory); @@ -97,7 +89,6 @@ abstract contract AbstractStakeTable { /// @param blsVK The BLS verification key /// @param schnorrVK The Schnorr verification key (as the auxiliary info) /// @param amount The amount to register - /// @param stakeType The type of staking (native or restaking) /// @param blsSig The BLS signature that authenticates the ethereum account this function is /// called from /// @param validUntilEpoch The maximum epoch the sender is willing to wait to be included @@ -110,8 +101,7 @@ abstract contract AbstractStakeTable { function register( BN254.G2Point memory blsVK, EdOnBN254.EdOnBN254Point memory schnorrVK, - uint64 amount, - StakeType stakeType, + uint256 amount, BN254.G1Point memory blsSig, uint64 validUntilEpoch ) external virtual; @@ -121,10 +111,10 @@ abstract contract AbstractStakeTable { /// @param blsVK The BLS verification key /// @param amount The amount to deposit /// @return (newBalance, effectiveEpoch) the new balance effective at a future epoch - function deposit(BN254.G2Point memory blsVK, uint64 amount) + function deposit(BN254.G2Point memory blsVK, uint256 amount) external virtual - returns (uint64, uint64); + returns (uint256, uint64); /// @notice Request to exit from the stake table, not immediately withdrawable! /// @@ -135,6 +125,11 @@ abstract contract AbstractStakeTable { /// withdraw past their `exitEpoch`. /// /// @param blsVK The BLS verification key to withdraw + /// @param blsSig The BLS signature that authenticates the ethereum account this function is + /// called from the caller /// @return The total amount withdrawn, equal to `Node.balance` associated with `blsVK` - function withdrawFunds(BN254.G2Point memory blsVK) external virtual returns (uint64); + function withdrawFunds(BN254.G2Point memory blsVK, BN254.G1Point memory blsSig) + external + virtual + returns (uint256); } diff --git a/contracts/test/StakeTable.t.sol b/contracts/test/StakeTable.t.sol new file mode 100644 index 0000000000..53154850f6 --- /dev/null +++ b/contracts/test/StakeTable.t.sol @@ -0,0 +1,262 @@ +// SPDX-License-Identifier: Unlicensed + +/* solhint-disable contract-name-camelcase, func-name-mixedcase, one-contract-per-file */ + +pragma solidity ^0.8.0; + +// Libraries +import "forge-std/Test.sol"; +// import {console} from "forge-std/console.sol"; + +using stdStorage for StdStorage; + +import { ERC20 } from "solmate/utils/SafeTransferLib.sol"; +import { BN254 } from "bn254/BN254.sol"; +import { BLSSig } from "../src/libraries/BLSSig.sol"; +import { EdOnBN254 } from "../src/libraries/EdOnBn254.sol"; +import { AbstractStakeTable } from "../src/interfaces/AbstractStakeTable.sol"; +import { LightClient } from "../src/LightClient.sol"; +import { LightClientMock } from "../test/mocks/LightClientMock.sol"; + +// Token contract +import { ExampleToken } from "../src/ExampleToken.sol"; + +// Target contract +import { StakeTable as S } from "../src/StakeTable.sol"; + +contract StakeTable_register_Test is Test { + event Registered(bytes32, uint64, uint256); + + S public stakeTable; + ExampleToken public token; + LightClientMock public lcMock; + uint256 public constant INITIAL_BALANCE = 10 ether; + address public exampleTokenCreator; + + function genClientWallet(address sender) + private + returns (BN254.G2Point memory, EdOnBN254.EdOnBN254Point memory, BN254.G1Point memory) + { + // Generate a BLS signature and other values using rust code + string[] memory cmds = new string[](4); + cmds[0] = "diff-test"; + cmds[1] = "gen-client-wallet"; + cmds[2] = vm.toString(sender); + cmds[3] = "123"; + + bytes memory result = vm.ffi(cmds); + ( + BN254.G1Point memory blsSig, + BN254.G2Point memory blsVK, + uint256 schnorrVKx, + uint256 schnorrVKy, + ) = abi.decode(result, (BN254.G1Point, BN254.G2Point, uint256, uint256, address)); + + return ( + blsVK, // blsVK + EdOnBN254.EdOnBN254Point(schnorrVKx, schnorrVKy), // schnorrVK + blsSig // sig + ); + } + + function setUp() public { + exampleTokenCreator = makeAddr("tokenCreator"); + vm.prank(exampleTokenCreator); + token = new ExampleToken(INITIAL_BALANCE); + + string[] memory cmds = new string[](3); + cmds[0] = "diff-test"; + cmds[1] = "mock-genesis"; + cmds[2] = "5"; + + bytes memory result = vm.ffi(cmds); + ( + LightClientMock.LightClientState memory state, + LightClientMock.StakeTableState memory stakeState + ) = abi.decode(result, (LightClient.LightClientState, LightClient.StakeTableState)); + LightClientMock.LightClientState memory genesis = state; + LightClientMock.StakeTableState memory genesisStakeTableState = stakeState; + + lcMock = new LightClientMock(genesis, genesisStakeTableState, 864000); + address lightClientAddress = address(lcMock); + stakeTable = new S(address(token), lightClientAddress, 10); + } + + function testFuzz_RevertWhen_InvalidBLSSig(uint256 scalar) external { + uint64 depositAmount = 10 ether; + uint64 validUntilEpoch = 5; + + (BN254.G2Point memory blsVK, EdOnBN254.EdOnBN254Point memory schnorrVK,) = + genClientWallet(exampleTokenCreator); + + // Prepare for the token transfer + vm.startPrank(exampleTokenCreator); + token.approve(address(stakeTable), depositAmount); + + // Ensure the scalar is valid + // Note: Apparently BN254.scalarMul is not well defined when the scalar is 0 + scalar = bound(scalar, 1, BN254.R_MOD - 1); + BN254.validateScalarField(BN254.ScalarField.wrap(scalar)); + BN254.G1Point memory badSig = BN254.scalarMul(BN254.P1(), BN254.ScalarField.wrap(scalar)); + BN254.validateG1Point(badSig); + + // Failed signature verification + vm.expectRevert(BLSSig.BLSSigVerificationFailed.selector); + stakeTable.register(blsVK, schnorrVK, depositAmount, badSig, validUntilEpoch); + vm.stopPrank(); + } + + // commenting out epoch related tests for now + // function testFuzz_RevertWhen_InvalidNextRegistrationEpoch(uint64 rand) external { + // LCMock.setCurrentEpoch(3); + // uint64 currentEpoch = stakeTable.currentEpoch(); + + // uint64 depositAmount = 10 ether; + // vm.prank(exampleTokenCreator); + // token.approve(address(stakeTable), depositAmount); + + // ( + // BN254.G2Point memory blsVK, + // EdOnBN254.EdOnBN254Point memory schnorrVK, + // BN254.G1Point memory sig + // ) = genClientWallet(exampleTokenCreator); + + // // Invalid next registration epoch + // uint64 validUntilEpoch = uint64(bound(rand, 0, currentEpoch - 1)); + // vm.prank(exampleTokenCreator); + // vm.expectRevert( + // abi.encodeWithSelector( + // S.InvalidNextRegistrationEpoch.selector, currentEpoch + 1, validUntilEpoch + // ) + // ); + // stakeTable.register( + // blsVK, + // schnorrVK, + // depositAmount, + // sig, + // validUntilEpoch + // ); + + // // Valid next registration epoch + // validUntilEpoch = uint64(bound(rand, currentEpoch + 1, type(uint64).max)); + // vm.prank(exampleTokenCreator); + // stakeTable.register( + // blsVK, + // schnorrVK, + // depositAmount, + // sig, + // validUntilEpoch + // ); + // } + + function test_RevertWhen_NodeAlreadyRegistered() external { + uint64 depositAmount = 10 ether; + uint64 validUntilEpoch = 5; + + ( + BN254.G2Point memory blsVK, + EdOnBN254.EdOnBN254Point memory schnorrVK, + BN254.G1Point memory sig + ) = genClientWallet(exampleTokenCreator); + + // Prepare for the token transfer + vm.prank(exampleTokenCreator); + token.approve(address(stakeTable), depositAmount); + + // Successful call to register + vm.prank(exampleTokenCreator); + stakeTable.register(blsVK, schnorrVK, depositAmount, sig, validUntilEpoch); + + // The node is already registered + vm.prank(exampleTokenCreator); + vm.expectRevert(S.NodeAlreadyRegistered.selector); + stakeTable.register(blsVK, schnorrVK, depositAmount, sig, validUntilEpoch); + } + + function test_RevertWhen_NoTokenAllowanceOrBalance() external { + uint64 depositAmount = 10 ether; + uint64 validUntilEpoch = 10; + + ( + BN254.G2Point memory blsVK, + EdOnBN254.EdOnBN254Point memory schnorrVK, + BN254.G1Point memory sig + ) = genClientWallet(exampleTokenCreator); + + assertEq(ERC20(token).balanceOf(exampleTokenCreator), INITIAL_BALANCE); + vm.prank(exampleTokenCreator); + // The call to register is expected to fail because the depositAmount has not been approved + // and thus the stake table contract cannot lock the stake. + vm.expectRevert(abi.encodeWithSelector(S.InsufficientAllowance.selector, 0, depositAmount)); + stakeTable.register(blsVK, schnorrVK, depositAmount, sig, validUntilEpoch); + + // A user with 0 balance cannot register either + address newUser = makeAddr("New user with zero balance"); + (blsVK, schnorrVK, sig) = genClientWallet(newUser); + + vm.startPrank(newUser); + // Prepare for the token transfer by giving the StakeTable contract the required allowance + token.approve(address(stakeTable), depositAmount); + vm.expectRevert(abi.encodeWithSelector(S.InsufficientBalance.selector, 0)); + stakeTable.register(blsVK, schnorrVK, depositAmount, sig, validUntilEpoch); + vm.stopPrank(); + } + + function test_RevertWhen_WrongStakeAmount() external { + uint64 depositAmount = 5 ether; + uint64 validUntilEpoch = 10; + + ( + BN254.G2Point memory blsVK, + EdOnBN254.EdOnBN254Point memory schnorrVK, + BN254.G1Point memory sig + ) = genClientWallet(exampleTokenCreator); + + assertEq(ERC20(token).balanceOf(exampleTokenCreator), INITIAL_BALANCE); + vm.prank(exampleTokenCreator); + // The call to register is expected to fail because the depositAmount has not been approved + // and thus the stake table contract cannot lock the stake. + vm.expectRevert(abi.encodeWithSelector(S.InsufficientStakeAmount.selector, depositAmount)); + stakeTable.register(blsVK, schnorrVK, depositAmount, sig, validUntilEpoch); + } + + /// @dev Tests a correct registration + function test_Registration_succeeds() external { + ( + BN254.G2Point memory blsVK, + EdOnBN254.EdOnBN254Point memory schnorrVK, + BN254.G1Point memory sig + ) = genClientWallet(exampleTokenCreator); + + uint64 depositAmount = 10 ether; + uint64 validUntilEpoch = 5; + + // Prepare for the token transfer + vm.prank(exampleTokenCreator); + token.approve(address(stakeTable), depositAmount); + + // Balances before registration + assertEq(token.balanceOf(exampleTokenCreator), INITIAL_BALANCE); + + uint256 totalStakeAmount; + totalStakeAmount = stakeTable.totalStake(); + assertEq(totalStakeAmount, 0); + + AbstractStakeTable.Node memory node; + node.account = exampleTokenCreator; + node.balance = depositAmount; + node.schnorrVK = schnorrVK; + node.registerEpoch = 1; + + // Check event is emitted after calling successfully `register` + vm.expectEmit(false, false, false, true, address(stakeTable)); + emit Registered(stakeTable._hashBlsKey(blsVK), node.registerEpoch, node.balance); + vm.prank(exampleTokenCreator); + stakeTable.register(blsVK, schnorrVK, depositAmount, sig, validUntilEpoch); + + // Balance after registration + assertEq(token.balanceOf(exampleTokenCreator), INITIAL_BALANCE - depositAmount); + totalStakeAmount = stakeTable.totalStake(); + assertEq(totalStakeAmount, depositAmount); + } +} From 006ce0cfdc086a26366b7f987679369ba5a179c5 Mon Sep 17 00:00:00 2001 From: rob-maron <132852777+rob-maron@users.noreply.github.com> Date: Fri, 6 Dec 2024 20:18:17 -0500 Subject: [PATCH 9/9] [Easy] `futures` -> `tokio` select (#2366) futures -> tokio select --- sequencer/src/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sequencer/src/lib.rs b/sequencer/src/lib.rs index bba6356ad3..98fc958930 100644 --- a/sequencer/src/lib.rs +++ b/sequencer/src/lib.rs @@ -18,11 +18,11 @@ use espresso_types::{ traits::EventConsumer, BackoffParams, L1ClientOptions, NodeState, PubKey, SeqTypes, SolverAuctionResultsProvider, ValidatedState, }; -use futures::FutureExt; use genesis::L1Finalized; use hotshot::traits::election::static_committee::StaticCommittee; use hotshot_types::traits::election::Membership; use std::sync::Arc; +use tokio::select; // Should move `STAKE_TABLE_CAPACITY` in the sequencer repo when we have variate stake table support use libp2p::Multiaddr; use network::libp2p::split_off_peer_id; @@ -457,11 +457,11 @@ pub async fn init_node( })?; tracing::warn!("Waiting for at least one connection to be initialized"); - futures::select! { - _ = cdn_network.wait_for_ready().fuse() => { + select! { + _ = cdn_network.wait_for_ready() => { tracing::warn!("CDN connection initialized"); }, - _ = p2p_network.wait_for_ready().fuse() => { + _ = p2p_network.wait_for_ready() => { tracing::warn!("P2P connection initialized"); }, };