diff --git a/Cargo.lock b/Cargo.lock index d2aced6..fb05715 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -62,6 +62,7 @@ dependencies = [ "alloy-eips", "alloy-genesis", "alloy-network", + "alloy-node-bindings", "alloy-provider", "alloy-pubsub", "alloy-rpc-client", @@ -244,6 +245,19 @@ dependencies = [ "serde", ] +[[package]] +name = "alloy-hardforks" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819a3620fe125e0fff365363315ee5e24c23169173b19747dfd6deba33db8990" +dependencies = [ + "alloy-chains", + "alloy-eip2124", + "alloy-primitives", + "auto_impl", + "dyn-clone", +] + [[package]] name = "alloy-json-abi" version = "1.2.0" @@ -309,6 +323,27 @@ dependencies = [ "serde", ] +[[package]] +name = "alloy-node-bindings" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f70cdf0ba711fb6f1a427fd026e149bd6d64c9da4c020c1c96c69245c4721ac1" +dependencies = [ + "alloy-genesis", + "alloy-hardforks", + "alloy-network", + "alloy-primitives", + "alloy-signer", + "alloy-signer-local", + "k256", + "rand 0.8.5", + "serde_json", + "tempfile", + "thiserror 2.0.12", + "tracing", + "url", +] + [[package]] name = "alloy-primitives" version = "1.2.0" @@ -349,9 +384,11 @@ dependencies = [ "alloy-json-rpc", "alloy-network", "alloy-network-primitives", + "alloy-node-bindings", "alloy-primitives", "alloy-pubsub", "alloy-rpc-client", + "alloy-rpc-types-anvil", "alloy-rpc-types-eth", "alloy-signer", "alloy-sol-types", @@ -453,6 +490,19 @@ name = "alloy-rpc-types" version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c000cab4ec26a4b3e29d144e999e1c539c2fa0abed871bf90311eb3466187ca8" +dependencies = [ + "alloy-primitives", + "alloy-rpc-types-anvil", + "alloy-rpc-types-eth", + "alloy-serde", + "serde", +] + +[[package]] +name = "alloy-rpc-types-anvil" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ad84b1927e3c5985afca4a3c3a3a5bdce433de30cf8b2f7e52b642758d235d9" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -1967,6 +2017,12 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + [[package]] name = "ecdsa" version = "0.16.9" @@ -4840,8 +4896,10 @@ version = "0.1.0" dependencies = [ "alloy", "async-trait", - "derive_more", + "claims", "log", + "thiserror 2.0.12", + "tokio", "url", ] diff --git a/Cargo.toml b/Cargo.toml index 4569a8b..e00fad7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,9 +14,10 @@ ark-serialize = "0.5" ark-groth16 = "0.5" ark-ff = "0.5" url = { version = "2.5.4", features = ["serde"] } -alloy = { version = "1.0", features = ["getrandom", "sol-types", "contract", "provider-ws"] } +alloy = { version = "1.0", features = ["getrandom", "sol-types", "contract", "provider-ws", "provider-anvil-node"] } async-trait = "0.1" derive_more = "2.0.1" +thiserror = "2.0" # dev criterion = { version = "0.6", features = ["async_tokio"] } diff --git a/prover/Cargo.toml b/prover/Cargo.toml index cad67d3..8f54679 100644 --- a/prover/Cargo.toml +++ b/prover/Cargo.toml @@ -14,7 +14,7 @@ tokio = { version = "1", features = ["macros", "rt-multi-thread"] } tracing-subscriber = { version = "0.3.19", features = ["env-filter"] } tracing = "0.1.41" alloy.workspace = true -thiserror = "2.0" +thiserror.workspace = true futures = "0.3" ark-bn254.workspace = true ark-serialize.workspace = true diff --git a/prover/benches/prover_bench.rs b/prover/benches/prover_bench.rs index 38d805b..25ee1c7 100644 --- a/prover/benches/prover_bench.rs +++ b/prover/benches/prover_bench.rs @@ -1,5 +1,5 @@ -use criterion::{BenchmarkId, Throughput}; use criterion::Criterion; +use criterion::{BenchmarkId, Throughput}; use criterion::{criterion_group, criterion_main}; // std @@ -175,19 +175,23 @@ fn proof_generation_bench(c: &mut Criterion) { let proof_count = 5; group.throughput(Throughput::Elements(proof_count as u64)); - group.bench_with_input(BenchmarkId::new("proof generation", proof_count), &proof_count, |b, &_s| { - b.to_async(&rt).iter(|| { - async { - let mut set = JoinSet::new(); - set.spawn(proof_collector(port, proof_count)); - set.spawn(proof_sender(port, addresses.clone(), proof_count).map(|_r| vec![])); - // Wait for proof_sender + proof_collector to complete - let res = set.join_all().await; - // Check we receive enough proof - assert_eq!(res[1].len(), proof_count); - } - }); - }); + group.bench_with_input( + BenchmarkId::new("proof generation", proof_count), + &proof_count, + |b, &_s| { + b.to_async(&rt).iter(|| { + async { + let mut set = JoinSet::new(); + set.spawn(proof_collector(port, proof_count)); + set.spawn(proof_sender(port, addresses.clone(), proof_count).map(|_r| vec![])); + // Wait for proof_sender + proof_collector to complete + let res = set.join_all().await; + // Check we receive enough proof + assert_eq!(res[1].len(), proof_count); + } + }); + }, + ); group.finish(); } diff --git a/prover/src/error.rs b/prover/src/error.rs index b4d5785..7fe4b56 100644 --- a/prover/src/error.rs +++ b/prover/src/error.rs @@ -1,6 +1,7 @@ use alloy::transports::{RpcError, TransportErrorKind}; use ark_serialize::SerializationError; use rln::error::ProofError; +use smart_contract::GetScTiersError; // internal use crate::epoch_service::WaitUntilError; use crate::user_db_error::{RegisterError, UserMerkleTreeIndexError}; @@ -20,7 +21,7 @@ pub enum AppError { #[error(transparent)] RegistryError(#[from] HandleTransferError), #[error(transparent)] - ContractError(#[from] alloy::contract::Error), + TierLimitsError(#[from] GetScTiersError), } #[derive(thiserror::Error, Debug)] diff --git a/prover/src/grpc_service.rs b/prover/src/grpc_service.rs index 9a6e382..275031d 100644 --- a/prover/src/grpc_service.rs +++ b/prover/src/grpc_service.rs @@ -188,7 +188,7 @@ where let id_co = U256::from_le_slice(BigUint::from(id_commitment).to_bytes_le().as_slice()); - if let Err(e) = self.karma_rln_sc.register(id_co).await { + if let Err(e) = self.karma_rln_sc.register_user(&user, id_co).await { // Fail to register user on smart contract // Remove the user in internal Db if !self.user_db.remove_user(&user, false) { diff --git a/prover/src/lib.rs b/prover/src/lib.rs index fb740e3..1310520 100644 --- a/prover/src/lib.rs +++ b/prover/src/lib.rs @@ -47,7 +47,7 @@ use crate::user_db_error::RegisterError; use crate::user_db_service::UserDbService; use crate::user_db_types::RateLimit; use rln_proof::RlnIdentifier; -use smart_contract::KarmaTiersSC::KarmaTiersSCInstance; +use smart_contract::KarmaTiers::KarmaTiersInstance; use smart_contract::TIER_LIMITS; const RLN_IDENTIFIER_NAME: &[u8] = b"test-rln-identifier"; @@ -63,9 +63,9 @@ pub async fn run_prover( let epoch_service = EpochService::try_from((Duration::from_secs(60 * 2), GENESIS)) .expect("Failed to create epoch service"); - let mut tier_limits = if app_args.ws_rpc_url.is_some() { + let tier_limits = if app_args.ws_rpc_url.is_some() { TierLimits::from( - KarmaTiersSCInstance::get_tiers( + KarmaTiersInstance::get_tiers( app_args.ws_rpc_url.clone().unwrap(), app_args.tsc_address.unwrap(), ) @@ -78,7 +78,6 @@ pub async fn run_prover( tl }; - tier_limits.filter_inactive(); tier_limits.validate()?; // User db service diff --git a/prover/src/proof_service.rs b/prover/src/proof_service.rs index cc4a161..989bbb6 100644 --- a/prover/src/proof_service.rs +++ b/prover/src/proof_service.rs @@ -8,8 +8,10 @@ use metrics::{counter, histogram}; use parking_lot::RwLock; use rln::hashers::hash_to_field; use rln::protocol::serialize_proof_values; -use tracing::{Instrument, // debug, - debug_span, info +use tracing::{ + Instrument, // debug, + debug_span, + info, }; // internal use crate::epoch_service::{Epoch, EpochSlice}; @@ -84,7 +86,6 @@ impl ProofService { // see https://ryhl.io/blog/async-what-is-blocking/ rayon::spawn(move || { - let proof_generation_start = std::time::Instant::now(); let message_id = { @@ -110,7 +111,8 @@ impl ProofService { }; let epoch = hash_to_field(epoch_bytes.as_slice()); - let merkle_proof = match user_db.get_merkle_proof(&proof_generation_data.tx_sender) { + let merkle_proof = match user_db.get_merkle_proof(&proof_generation_data.tx_sender) + { Ok(merkle_proof) => merkle_proof, Err(e) => { let _ = send.send(Err(ProofGenerationError::MerkleProofError(e))); @@ -138,28 +140,25 @@ impl ProofService { // Serialize proof let mut output_buffer = Cursor::new(Vec::with_capacity(PROOF_SIZE)); - if let Err(e) = proof - .serialize_compressed(&mut output_buffer) - { + if let Err(e) = proof.serialize_compressed(&mut output_buffer) { let _ = send.send(Err(ProofGenerationError::Serialization(e))); return; } - if let Err(e) = output_buffer - .write_all(&serialize_proof_values(&proof_values)) { + if let Err(e) = output_buffer.write_all(&serialize_proof_values(&proof_values)) { let _ = send.send(Err(ProofGenerationError::SerializationWrite(e))); return; } histogram!(PROOF_SERVICE_GEN_PROOF_TIME.name, "prover" => "proof service") - .record(proof_generation_start.elapsed().as_secs_f64()); + .record(proof_generation_start.elapsed().as_secs_f64()); // println!("[proof service {counter_id}] proof generation time: {:?} secs", proof_generation_start.elapsed().as_secs_f64()); let labels = [("prover", format!("proof service id: {counter_id}"))]; counter!(PROOF_SERVICE_PROOF_COMPUTED.name, &labels).increment(1); // Send the result back to Tokio. - let _ = send.send( - Ok::, ProofGenerationError>(output_buffer.into_inner()) - ); + let _ = send.send(Ok::, ProofGenerationError>( + output_buffer.into_inner(), + )); }); // Wait for the rayon task. @@ -205,7 +204,7 @@ mod tests { use claims::assert_matches; use futures::TryFutureExt; use tokio::sync::broadcast; - use tracing::{info, debug}; + use tracing::{debug, info}; // third-party: zerokit use rln::{ circuit::{Curve, zkey_from_folder}, diff --git a/prover/src/tier.rs b/prover/src/tier.rs index b04fbfe..a25622f 100644 --- a/prover/src/tier.rs +++ b/prover/src/tier.rs @@ -1,10 +1,10 @@ -use std::collections::{BTreeMap, HashSet}; +use std::collections::HashSet; use std::ops::ControlFlow; // third-party use alloy::primitives::U256; use derive_more::{Deref, DerefMut, From, Into}; // internal -use smart_contract::{Tier, TierIndex}; +use smart_contract::Tier; #[derive(Debug, Clone, Copy, PartialEq, PartialOrd, From, Into)] pub struct TierLimit(u32); @@ -19,11 +19,11 @@ impl From<&str> for TierName { } #[derive(Debug, Clone, Default, From, Into, Deref, DerefMut, PartialEq)] -pub struct TierLimits(BTreeMap); +pub struct TierLimits(Vec); -impl From<[(TierIndex, Tier); N]> for TierLimits { - fn from(value: [(TierIndex, Tier); N]) -> Self { - Self(BTreeMap::from(value)) +impl From<[Tier; N]> for TierLimits { + fn from(value: [Tier; N]) -> Self { + Self(Vec::from(value)) } } @@ -34,17 +34,10 @@ pub enum TierMatch { /// Karma is above the highest tier AboveHighest, /// Karma is in the range of a defined tier. - Matched(TierIndex, Tier), + Matched(Tier), } impl TierLimits { - /// Filter inactive Tier (rejected by function validate) - pub(crate) fn filter_inactive(&mut self) -> Self { - let map = std::mem::take(&mut self.0); - let map_filtered = map.into_iter().filter(|(_k, v)| v.active).collect(); - Self(map_filtered) - } - /// Validate tier limits (unique names, increasing min & max karma ...) pub(crate) fn validate(&self) -> Result<(), ValidateTierLimitsError> { #[derive(Debug, Default)] @@ -53,49 +46,38 @@ impl TierLimits { prev_min: Option<&'a U256>, prev_max: Option<&'a U256>, prev_tx_per_epoch: Option<&'a u32>, - prev_index: Option<&'a TierIndex>, } - let _context = - self.0 - .iter() - .try_fold(Context::default(), |mut state, (tier_index, tier)| { - if !tier.active { - return Err(ValidateTierLimitsError::InactiveTier); - } - if *tier_index != (*state.prev_index.unwrap_or(&TierIndex::default()) + 1) { - // Tier index must be increasing and consecutive - // cf function `_validateNoOverlap` in KarmaTiers.sol - return Err(ValidateTierLimitsError::InvalidTierIndex); - } + let _context = self + .0 + .iter() + .try_fold(Context::default(), |mut state, tier| { + if tier.min_karma <= *state.prev_min.unwrap_or(&U256::ZERO) { + return Err(ValidateTierLimitsError::InvalidMinKarmaAmount); + } - if tier.min_karma <= *state.prev_min.unwrap_or(&U256::ZERO) { - return Err(ValidateTierLimitsError::InvalidMinKarmaAmount); - } + if tier.min_karma <= *state.prev_max.unwrap_or(&U256::ZERO) { + return Err(ValidateTierLimitsError::InvalidMinKarmaAmount); + } - if tier.min_karma <= *state.prev_max.unwrap_or(&U256::ZERO) { - return Err(ValidateTierLimitsError::InvalidMinKarmaAmount); - } + if tier.min_karma >= tier.max_karma { + return Err(ValidateTierLimitsError::InvalidMaxKarmaAmount); + } - if tier.min_karma >= tier.max_karma { - return Err(ValidateTierLimitsError::InvalidMaxKarmaAmount); - } + if tier.tx_per_epoch <= *state.prev_tx_per_epoch.unwrap_or(&0) { + return Err(ValidateTierLimitsError::InvalidTierLimit); + } - if tier.tx_per_epoch <= *state.prev_tx_per_epoch.unwrap_or(&0) { - return Err(ValidateTierLimitsError::InvalidTierLimit); - } + if state.tier_names.contains(&tier.name) { + return Err(ValidateTierLimitsError::NonUniqueTierName); + } - if state.tier_names.contains(&tier.name) { - return Err(ValidateTierLimitsError::NonUniqueTierName); - } - - state.prev_min = Some(&tier.min_karma); - state.prev_max = Some(&tier.max_karma); - state.prev_tx_per_epoch = Some(&tier.tx_per_epoch); - state.tier_names.insert(tier.name.clone()); - state.prev_index = Some(tier_index); - Ok(state) - })?; + state.prev_min = Some(&tier.min_karma); + state.prev_max = Some(&tier.max_karma); + state.prev_tx_per_epoch = Some(&tier.tx_per_epoch); + state.tier_names.insert(tier.name.clone()); + Ok(state) + })?; Ok(()) } @@ -103,33 +85,27 @@ impl TierLimits { /// Given some karma amount, find the matching Tier. Assume all tiers are active. pub(crate) fn get_tier_by_karma(&self, karma_amount: &U256) -> TierMatch { struct Context<'a> { - current: Option<(&'a TierIndex, &'a Tier)>, + current: Option<&'a Tier>, } let ctx_initial = Context { current: None }; - let ctx = self - .0 - .iter() - .try_fold(ctx_initial, |mut state, (tier_index, tier)| { - // Assume all the tier are active but checks it at dev time - debug_assert!(tier.active, "Find a non active tier"); - - if karma_amount < &tier.min_karma { - // Early break - above lowest tier (< lowest_tier.min_karma) - ControlFlow::Break(state) - } else if karma_amount >= &tier.min_karma && karma_amount <= &tier.max_karma { - // Found a match - update ctx and break - state.current = Some((tier_index, tier)); - ControlFlow::Break(state) - } else { - ControlFlow::Continue(state) - } - }); + let ctx = self.0.iter().try_fold(ctx_initial, |mut state, tier| { + if karma_amount < &tier.min_karma { + // Early break - above lowest tier (< lowest_tier.min_karma) + ControlFlow::Break(state) + } else if karma_amount >= &tier.min_karma && karma_amount <= &tier.max_karma { + // Found a match - update ctx and break + state.current = Some(tier); + ControlFlow::Break(state) + } else { + ControlFlow::Continue(state) + } + }); if let Some(ctx) = ctx.break_value() { // ControlFlow::Break - if let Some((tier_index, tier)) = ctx.current { - TierMatch::Matched(*tier_index, tier.clone()) + if let Some(tier) = ctx.current { + TierMatch::Matched(tier.clone()) } else { TierMatch::UnderLowest } @@ -148,12 +124,8 @@ pub enum ValidateTierLimitsError { InvalidMaxKarmaAmount, #[error("Invalid Tier limit (must be increasing)")] InvalidTierLimit, - #[error("Invalid Tier index (must be increasing & consecutive)")] - InvalidTierIndex, #[error("Non unique Tier name")] NonUniqueTierName, - #[error("Non active Tier")] - InactiveTier, } #[cfg(test)] @@ -162,98 +134,21 @@ mod tests { use alloy::primitives::U256; use claims::assert_matches; - #[test] - fn test_filter_inactive() { - let mut tier_limits = TierLimits::from([ - ( - TierIndex::from(0), - Tier { - name: "Basic".to_string(), - min_karma: U256::from(10), - max_karma: U256::from(49), - tx_per_epoch: 6, - active: true, - }, - ), - ( - TierIndex::from(1), - Tier { - name: "Active".to_string(), - min_karma: U256::from(50), - max_karma: U256::from(99), - tx_per_epoch: 120, - active: true, - }, - ), - ( - TierIndex::from(2), - Tier { - name: "Power User".to_string(), - min_karma: U256::from(500), - max_karma: U256::from(999), - tx_per_epoch: 86400, - active: false, - }, - ), - ]); - - let filtered = tier_limits.filter_inactive(); - assert_eq!(filtered.len(), 2); - } - - #[test] - fn test_validate_fails_with_inactive_tier() { - let tier_limits = TierLimits::from([ - ( - TierIndex::from(1), - Tier { - name: "Basic".to_string(), - min_karma: U256::from(10), - max_karma: U256::from(49), - tx_per_epoch: 6, - active: true, - }, - ), - ( - TierIndex::from(2), - Tier { - name: "Active".to_string(), - min_karma: U256::from(50), - max_karma: U256::from(99), - tx_per_epoch: 120, - active: false, - }, - ), - ]); - assert_matches!( - tier_limits.validate(), - Err(ValidateTierLimitsError::InactiveTier) - ); - } - #[test] fn test_validate_failed_when_karma_overlapping_between_tier() { let tier_limits = TierLimits::from([ - ( - TierIndex::from(1), - Tier { - name: "Basic".to_string(), - min_karma: U256::from(10), - max_karma: U256::from(100), - tx_per_epoch: 6, - active: true, - }, - ), - ( - TierIndex::from(2), - Tier { - name: "Active".to_string(), - min_karma: U256::from(50), - max_karma: U256::from(150), - tx_per_epoch: 120, - active: true, - }, - ), + Tier { + name: "Basic".to_string(), + min_karma: U256::from(10), + max_karma: U256::from(100), + tx_per_epoch: 6, + }, + Tier { + name: "Active".to_string(), + min_karma: U256::from(50), + max_karma: U256::from(150), + tx_per_epoch: 120, + }, ]); assert_matches!( @@ -264,32 +159,24 @@ mod tests { #[test] fn test_validate_fails_when_min_karma_equal_or_greater_max_karma() { - let tier_limits = TierLimits::from([( - TierIndex::from(1), - Tier { - name: "Basic".to_string(), - min_karma: U256::from(100), - max_karma: U256::from(100), - tx_per_epoch: 6, - active: true, - }, - )]); + let tier_limits = TierLimits::from([Tier { + name: "Basic".to_string(), + min_karma: U256::from(100), + max_karma: U256::from(100), + tx_per_epoch: 6, + }]); assert_matches!( tier_limits.validate(), Err(ValidateTierLimitsError::InvalidMaxKarmaAmount) ); - let tier_limits = TierLimits::from([( - TierIndex::from(1), - Tier { - name: "Basic".to_string(), - min_karma: U256::from(500), - max_karma: U256::from(100), - tx_per_epoch: 6, - active: true, - }, - )]); + let tier_limits = TierLimits::from([Tier { + name: "Basic".to_string(), + min_karma: U256::from(500), + max_karma: U256::from(100), + tx_per_epoch: 6, + }]); assert_matches!( tier_limits.validate(), @@ -302,26 +189,18 @@ mod tests { // Case 1: Duplicate min_karma values { let tier_limits = TierLimits::from([ - ( - TierIndex::from(1), - Tier { - name: "Basic".to_string(), - min_karma: U256::from(10), - max_karma: U256::from(49), - tx_per_epoch: 6, - active: true, - }, - ), - ( - TierIndex::from(2), - Tier { - name: "Active".to_string(), - min_karma: U256::from(10), - max_karma: U256::from(99), - tx_per_epoch: 120, - active: true, - }, - ), + Tier { + name: "Basic".to_string(), + min_karma: U256::from(10), + max_karma: U256::from(49), + tx_per_epoch: 6, + }, + Tier { + name: "Active".to_string(), + min_karma: U256::from(10), + max_karma: U256::from(99), + tx_per_epoch: 120, + }, ]); assert_matches!( @@ -333,26 +212,18 @@ mod tests { // Case 2: Decreasing min_karma values { let tier_limits = TierLimits::from([ - ( - TierIndex::from(1), - Tier { - name: "Basic".to_string(), - min_karma: U256::from(50), - max_karma: U256::from(99), - tx_per_epoch: 6, - active: true, - }, - ), - ( - TierIndex::from(2), - Tier { - name: "Active".to_string(), - min_karma: U256::from(10), - max_karma: U256::from(49), - tx_per_epoch: 120, - active: true, - }, - ), + Tier { + name: "Basic".to_string(), + min_karma: U256::from(50), + max_karma: U256::from(99), + tx_per_epoch: 6, + }, + Tier { + name: "Active".to_string(), + min_karma: U256::from(10), + max_karma: U256::from(49), + tx_per_epoch: 120, + }, ]); assert_matches!( @@ -367,26 +238,18 @@ mod tests { // Case 1: Duplicate tx_per_epoch values { let tier_limits = TierLimits::from([ - ( - TierIndex::from(1), - Tier { - name: "Basic".to_string(), - min_karma: U256::from(10), - max_karma: U256::from(49), - tx_per_epoch: 120, - active: true, - }, - ), - ( - TierIndex::from(2), - Tier { - name: "Active".to_string(), - min_karma: U256::from(50), - max_karma: U256::from(99), - tx_per_epoch: 120, - active: true, - }, - ), + Tier { + name: "Basic".to_string(), + min_karma: U256::from(10), + max_karma: U256::from(49), + tx_per_epoch: 120, + }, + Tier { + name: "Active".to_string(), + min_karma: U256::from(50), + max_karma: U256::from(99), + tx_per_epoch: 120, + }, ]); assert_matches!( @@ -398,26 +261,18 @@ mod tests { // Case 2: Decreasing tx_per_epoch values { let tier_limits = TierLimits::from([ - ( - TierIndex::from(1), - Tier { - name: "Basic".to_string(), - min_karma: U256::from(10), - max_karma: U256::from(49), - tx_per_epoch: 120, - active: true, - }, - ), - ( - TierIndex::from(2), - Tier { - name: "Active".to_string(), - min_karma: U256::from(50), - max_karma: U256::from(99), - tx_per_epoch: 6, - active: true, - }, - ), + Tier { + name: "Basic".to_string(), + min_karma: U256::from(10), + max_karma: U256::from(49), + tx_per_epoch: 120, + }, + Tier { + name: "Active".to_string(), + min_karma: U256::from(50), + max_karma: U256::from(99), + tx_per_epoch: 6, + }, ]); assert_matches!( @@ -430,26 +285,18 @@ mod tests { #[test] fn test_validate_fails_with_duplicate_tier_names() { let tier_limits = TierLimits::from([ - ( - TierIndex::from(1), - Tier { - name: "Basic".to_string(), - min_karma: U256::from(10), - max_karma: U256::from(49), - tx_per_epoch: 6, - active: true, - }, - ), - ( - TierIndex::from(2), - Tier { - name: "Basic".to_string(), - min_karma: U256::from(50), - max_karma: U256::from(99), - tx_per_epoch: 120, - active: true, - }, - ), + Tier { + name: "Basic".to_string(), + min_karma: U256::from(10), + max_karma: U256::from(49), + tx_per_epoch: 6, + }, + Tier { + name: "Basic".to_string(), + min_karma: U256::from(50), + max_karma: U256::from(99), + tx_per_epoch: 120, + }, ]); assert_matches!( @@ -458,40 +305,6 @@ mod tests { ); } - #[test] - fn test_validate_fails_tier_index() { - // Non-consecutive tier index - { - let tier_limits = TierLimits::from([ - ( - TierIndex::from(1), - Tier { - name: "Basic".to_string(), - min_karma: U256::from(10), - max_karma: U256::from(49), - tx_per_epoch: 6, - active: true, - }, - ), - ( - TierIndex::from(3), - Tier { - name: "Basic".to_string(), - min_karma: U256::from(50), - max_karma: U256::from(99), - tx_per_epoch: 120, - active: true, - }, - ), - ]); - - assert_matches!( - tier_limits.validate(), - Err(ValidateTierLimitsError::InvalidTierIndex) - ); - } - } - #[test] fn test_validate_and_get_tier_by_karma_with_empty_tier_limits() { let tier_limits = TierLimits::default(); @@ -505,36 +318,24 @@ mod tests { #[test] fn test_get_tier_by_karma_bounds_and_ranges() { let tier_limits = TierLimits::from([ - ( - TierIndex::from(1), - Tier { - name: "Basic".to_string(), - min_karma: U256::from(10), - max_karma: U256::from(49), - tx_per_epoch: 6, - active: true, - }, - ), - ( - TierIndex::from(2), - Tier { - name: "Active".to_string(), - min_karma: U256::from(50), - max_karma: U256::from(99), - tx_per_epoch: 120, - active: true, - }, - ), - ( - TierIndex::from(3), - Tier { - name: "Regular".to_string(), - min_karma: U256::from(100), - max_karma: U256::from(499), - tx_per_epoch: 720, - active: true, - }, - ), + Tier { + name: "Basic".to_string(), + min_karma: U256::from(10), + max_karma: U256::from(49), + tx_per_epoch: 6, + }, + Tier { + name: "Active".to_string(), + min_karma: U256::from(50), + max_karma: U256::from(99), + tx_per_epoch: 120, + }, + Tier { + name: "Regular".to_string(), + min_karma: U256::from(100), + max_karma: U256::from(499), + tx_per_epoch: 720, + }, ]); // Case 1: Zero karma @@ -547,8 +348,7 @@ mod tests { // Case 3: Exact match on min_karma (start of first tier) let result = tier_limits.get_tier_by_karma(&U256::from(10)); - if let TierMatch::Matched(index, tier) = result { - assert_eq!(index, TierIndex::from(1)); + if let TierMatch::Matched(tier) = result { assert_eq!(tier.name, "Basic"); } else { panic!("Expected TierMatch::Matched, got {:?}", result); @@ -556,8 +356,7 @@ mod tests { // Case 4: Exact match on a tier boundary (start of second tier) let result = tier_limits.get_tier_by_karma(&U256::from(50)); - if let TierMatch::Matched(index, tier) = result { - assert_eq!(index, TierIndex::from(2)); + if let TierMatch::Matched(tier) = result { assert_eq!(tier.name, "Active"); } else { panic!("Expected TierMatch::Matched, got {:?}", result); @@ -565,8 +364,7 @@ mod tests { // Case 5: Karma within a tier range (between third tier) let result = tier_limits.get_tier_by_karma(&U256::from(250)); - if let TierMatch::Matched(index, tier) = result { - assert_eq!(index, TierIndex::from(3)); + if let TierMatch::Matched(tier) = result { assert_eq!(tier.name, "Regular"); } else { panic!("Expected TierMatch, got {:?}", result); @@ -574,8 +372,7 @@ mod tests { // Case 6: Exact match on max_karma (end of the third tier) let result = tier_limits.get_tier_by_karma(&U256::from(499)); - if let TierMatch::Matched(index, tier) = result { - assert_eq!(index, TierIndex::from(3)); + if let TierMatch::Matched(tier) = result { assert_eq!(tier.name, "Regular"); } else { panic!("Expected TierMatch, got {:?}", result); @@ -585,33 +382,4 @@ mod tests { let result = tier_limits.get_tier_by_karma(&U256::from(1000)); assert_eq!(result, TierMatch::AboveHighest); } - - #[test] - #[should_panic(expected = "Find a non active tier")] - fn test_get_tier_by_karma_ignores_inactive_tiers() { - let tier_limits = TierLimits::from([ - ( - TierIndex::from(0), - Tier { - name: "Basic".to_string(), - min_karma: U256::from(10), - max_karma: U256::from(49), - tx_per_epoch: 6, - active: false, - }, - ), - ( - TierIndex::from(1), - Tier { - name: "Active".to_string(), - min_karma: U256::from(50), - max_karma: U256::from(99), - tx_per_epoch: 120, - active: true, - }, - ), - ]); - - let _result = tier_limits.get_tier_by_karma(&U256::from(25)); - } } diff --git a/prover/src/tiers_listener.rs b/prover/src/tiers_listener.rs index 1b474b1..a0a9a8a 100644 --- a/prover/src/tiers_listener.rs +++ b/prover/src/tiers_listener.rs @@ -9,8 +9,10 @@ use futures::StreamExt; use tracing::error; // internal use crate::error::AppError; +use crate::tier::TierLimits; use crate::user_db::UserDb; -use smart_contract::{AlloyWsProvider, KarmaTiersSC, Tier, TierIndex}; +use smart_contract::KarmaTiers::KarmaTiersInstance; +use smart_contract::{AlloyWsProvider, KarmaTiers}; pub(crate) struct TiersListener { rpc_url: String, @@ -40,42 +42,43 @@ impl TiersListener { let filter = alloy::rpc::types::Filter::new() .address(self.sc_address) - .event(KarmaTiersSC::TierAdded::SIGNATURE) - .event(KarmaTiersSC::TierUpdated::SIGNATURE); + .event(KarmaTiers::TiersUpdated::SIGNATURE); // Subscribe to logs matching the filter. - let subscription = provider.subscribe_logs(&filter).await?; + let subscription = provider.clone().subscribe_logs(&filter).await?; let mut stream = subscription.into_stream(); // Loop through the incoming event logs while let Some(log) = stream.next().await { - if let Ok(tier_added) = KarmaTiersSC::TierAdded::decode_log_data(log.data()) { - let tier_id: TierIndex = tier_added.tierId.into(); - if let Err(e) = self.user_db.on_new_tier(tier_id, Tier::from(tier_added)) { + if let Ok(_tu) = KarmaTiers::TiersUpdated::decode_log_data(log.data()) { + let tier_limits = + match KarmaTiersInstance::get_tiers_from_provider(&provider, &self.sc_address) + .await + { + Ok(tier_limits) => tier_limits, + Err(e) => { + error!( + "Error while getting tiers limits from smart contract: {}", + e + ); + return Err(AppError::TierLimitsError(e)); + } + }; + + if let Err(e) = self + .user_db + .on_tier_limits_updated(TierLimits::from(tier_limits)) + { // If there is an error here, we assume this is an error by the user // updating the Tier limits (and thus we don't want to shut down the prover) - error!("Error while adding tier (index: {:?}): {}", tier_id, e); + error!("Error while updating tier limits: {}", e); } } else { - match KarmaTiersSC::TierUpdated::decode_log_data(log.data()) { - Ok(tier_updated) => { - let tier_id: TierIndex = tier_updated.tierId.into(); - if let Err(e) = self - .user_db - .on_tier_updated(tier_updated.tierId.into(), Tier::from(tier_updated)) - { - // If there is an error here, we assume this is an error by the user - // updating the Tier limits (and thus we don't want to shut down the prover) - error!("Error while updating tier (index: {:?}): {}", tier_id, e); - }; - } - Err(e) => { - eprintln!("Error decoding log data: {e:?}"); - // It's also useful to print the raw log data for debugging - eprintln!("Raw log topics: {:?}", log.topics()); - eprintln!("Raw log data: {:?}", log.data()); - } - } + // Should never happen as TiersUpdated is empty + eprintln!("Error decoding log data"); + // It's also useful to print the raw log data for debugging + eprintln!("Raw log topics: {:?}", log.topics()); + eprintln!("Raw log data: {:?}", log.data()); } } diff --git a/prover/src/user_db.rs b/prover/src/user_db.rs index 30a9fce..6f06c35 100644 --- a/prover/src/user_db.rs +++ b/prover/src/user_db.rs @@ -35,7 +35,7 @@ use crate::user_db_serialization::{ }; use crate::user_db_types::{EpochCounter, EpochSliceCounter, MerkleTreeIndex, RateLimit}; use rln_proof::{RlnUserIdentity, ZerokitMerkleTree}; -use smart_contract::{KarmaAmountExt, Tier, TierIndex}; +use smart_contract::KarmaAmountExt; const MERKLE_TREE_HEIGHT: usize = 20; pub const USER_CF: &str = "user"; @@ -534,41 +534,10 @@ impl UserDb { Ok(tier_limits) } - pub(crate) fn on_new_tier( + pub(crate) fn on_tier_limits_updated( &self, - tier_index: TierIndex, - tier: Tier, + tier_limits: TierLimits, ) -> Result<(), SetTierLimitsError> { - let mut tier_limits = self.get_tier_limits()?; - tier_limits.insert(tier_index, tier); - tier_limits.validate()?; - - // Serialize - let tier_limits_serializer = TierLimitsSerializer::default(); - let mut buffer = Vec::with_capacity(tier_limits_serializer.size_hint(tier_limits.len())); - // Unwrap safe - already validated - should always serialize - tier_limits_serializer - .serialize(&tier_limits, &mut buffer) - .unwrap(); - - // Write - let cf = self.get_tier_limits_cf(); - self.db - .put_cf(cf, TIER_LIMITS_NEXT_KEY.as_slice(), buffer) - .map_err(SetTierLimitsError::Db) - } - - pub(crate) fn on_tier_updated( - &self, - tier_index: TierIndex, - tier: Tier, - ) -> Result<(), SetTierLimitsError> { - let mut tier_limits = self.get_tier_limits()?; - if !tier_limits.contains_key(&tier_index) { - return Err(SetTierLimitsError::InvalidUpdateTierIndex); - } - - tier_limits.entry(tier_index).and_modify(|e| *e = tier); tier_limits.validate()?; // Serialize @@ -622,7 +591,7 @@ impl UserDb { tier_limit: None, }; - if let TierMatch::Matched(_tier_index, tier) = tier_match { + if let TierMatch::Matched(tier) = tier_match { t.tier_name = Some(tier.name.into()); t.tier_limit = Some(TierLimit::from(tier.tx_per_epoch)); } @@ -661,25 +630,6 @@ mod tests { const ADDR_1: Address = address!("0xd8da6bf26964af9d7eed9e03e53415d37aa96045"); const ADDR_2: Address = address!("0xb20a608c624Ca5003905aA834De7156C68b2E1d0"); - /* - struct MockKarmaSc2 {} - - #[async_trait] - impl KarmaAmountExt for MockKarmaSc2 { - type Error = DummyError; - - async fn karma_amount(&self, address: &Address) -> Result { - if address == &ADDR_1 { - Ok(U256::from(10)) - } else if address == &ADDR_2 { - Ok(U256::from(2000)) - } else { - Ok(U256::ZERO) - } - } - } - */ - #[test] fn test_user_register() { let temp_folder = tempfile::tempdir().unwrap(); diff --git a/prover/src/user_db_error.rs b/prover/src/user_db_error.rs index bcfdc62..879d679 100644 --- a/prover/src/user_db_error.rs +++ b/prover/src/user_db_error.rs @@ -59,8 +59,6 @@ pub enum UserMerkleTreeIndexError { pub enum SetTierLimitsError { #[error(transparent)] Validate(#[from] ValidateTierLimitsError), - #[error("Updating an invalid tier index")] - InvalidUpdateTierIndex, #[error(transparent)] Db(#[from] rocksdb::Error), } diff --git a/prover/src/user_db_serialization.rs b/prover/src/user_db_serialization.rs index e22a221..a3bdb6b 100644 --- a/prover/src/user_db_serialization.rs +++ b/prover/src/user_db_serialization.rs @@ -1,4 +1,3 @@ -use std::collections::BTreeMap; use std::num::TryFromIntError; use std::string::FromUtf8Error; // third-party @@ -17,7 +16,7 @@ use rln_proof::RlnUserIdentity; // internal use crate::tier::TierLimits; use crate::user_db_types::MerkleTreeIndex; -use smart_contract::{Tier, TierIndex}; +use smart_contract::Tier; pub(crate) struct RlnUserIdentitySerializer {} @@ -106,7 +105,6 @@ impl TierSerializer { buffer.extend(name_len.to_le_bytes()); buffer.extend(value.name.as_bytes()); buffer.extend(value.tx_per_epoch.to_le_bytes().as_slice()); - buffer.push(u8::from(value.active)); Ok(()) } @@ -155,8 +153,6 @@ impl TierDeserializer { let name = String::from_utf8(name.to_vec()) .map_err(|e| nom::Err::Error(TierDeserializeError::Utf8Error(e)))?; let (input, tx_per_epoch) = le_u32(input)?; - let (input, active) = take(1usize)(input)?; - let active = active[0] != 0; Ok(( input, @@ -165,7 +161,6 @@ impl TierDeserializer { max_karma, name, tx_per_epoch, - active, }, )) } @@ -185,9 +180,8 @@ impl TierLimitsSerializer { let len = value.len() as u32; buffer.extend(len.to_le_bytes()); let mut tier_buffer = Vec::with_capacity(self.tier_serializer.size_hint()); - value.iter().try_for_each(|(k, v)| { - buffer.push(k.into()); - self.tier_serializer.serialize(v, &mut tier_buffer)?; + value.iter().try_for_each(|t| { + self.tier_serializer.serialize(t, &mut tier_buffer)?; buffer.extend_from_slice(&tier_buffer); tier_buffer.clear(); Ok(()) @@ -209,16 +203,14 @@ impl TierLimitsDeserializer { &self, buffer: &'a [u8], ) -> IResult<&'a [u8], TierLimits, TierDeserializeError<&'a [u8]>> { - let (input, tiers): (&[u8], BTreeMap) = length_count( + let (input, tiers): (&[u8], Vec) = length_count( le_u32, context("Tier index & Tier deser", |input: &'a [u8]| { - let (input, tier_index) = take(1usize)(input)?; - let tier_index = TierIndex::from(tier_index[0]); let (input, tier) = self.tier_deserializer.deserialize(input)?; - Ok((input, (tier_index, tier))) + Ok((input, tier)) }), ) - .map(BTreeMap::from_iter) + .map(Vec::from_iter) .parse(buffer)?; Ok((input, TierLimits::from(tiers))) @@ -270,7 +262,6 @@ mod tests { max_karma: U256::from(u64::MAX), name: "All".to_string(), tx_per_epoch: 10_000_000, - active: false, }; let serializer = TierSerializer {}; @@ -290,20 +281,15 @@ mod tests { max_karma: U256::from(4), name: "Basic".to_string(), tx_per_epoch: 10_000, - active: false, }; let tier_2 = Tier { min_karma: U256::from(10), max_karma: U256::from(u64::MAX), name: "Premium".to_string(), tx_per_epoch: 1_000_000_000, - active: true, }; - let tier_limits = TierLimits::from(BTreeMap::from([ - (TierIndex::from(1), tier_1), - (TierIndex::from(2), tier_2), - ])); + let tier_limits = TierLimits::from([tier_1, tier_2]); let serializer = TierLimitsSerializer::default(); let mut buffer = Vec::with_capacity(serializer.size_hint(tier_limits.len())); diff --git a/prover/src/user_db_tests.rs b/prover/src/user_db_tests.rs index 5287687..77a7e14 100644 --- a/prover/src/user_db_tests.rs +++ b/prover/src/user_db_tests.rs @@ -4,10 +4,9 @@ mod user_db_tests { use std::path::PathBuf; use std::sync::Arc; // third-party - use alloy::primitives::{Address, address}; - use claims::assert_matches; - use parking_lot::RwLock; use crate::epoch_service::{Epoch, EpochSlice}; + use alloy::primitives::{Address, address}; + use parking_lot::RwLock; // internal use crate::user_db::UserDb; use crate::user_db_types::{EpochCounter, EpochSliceCounter, MerkleTreeIndex}; @@ -17,7 +16,6 @@ mod user_db_tests { #[tokio::test] async fn test_incr_tx_counter_2() { - // Same as test_incr_tx_counter but multi users AND multi incr let temp_folder = tempfile::tempdir().unwrap(); @@ -35,7 +33,7 @@ mod user_db_tests { Default::default(), Default::default(), ) - .unwrap(); + .unwrap(); // Register users user_db.register(ADDR_1).unwrap(); @@ -83,7 +81,6 @@ mod user_db_tests { user_db.get_tx_counter(&ADDR_2), Ok((EpochCounter::from(2), EpochSliceCounter::from(2))) ); - } #[tokio::test] diff --git a/prover/tests/grpc_e2e.rs b/prover/tests/grpc_e2e.rs index 7da23b1..372f4a7 100644 --- a/prover/tests/grpc_e2e.rs +++ b/prover/tests/grpc_e2e.rs @@ -111,7 +111,6 @@ async fn test_grpc_register_users() { } async fn proof_sender(port: u16, addresses: Vec
, proof_count: usize) { - let start = std::time::Instant::now(); let chain_id = GrpcU256 { @@ -148,7 +147,11 @@ async fn proof_sender(port: u16, addresses: Vec
, proof_count: usize) { count += 1; } - println!("[proof_sender] sent {} tx - elapsed: {} secs", count, start.elapsed().as_secs_f64()); + println!( + "[proof_sender] sent {} tx - elapsed: {} secs", + count, + start.elapsed().as_secs_f64() + ); /* let tx_hash = U256::from(42).to_le_bytes::<32>().to_vec(); @@ -166,7 +169,6 @@ async fn proof_sender(port: u16, addresses: Vec
, proof_count: usize) { } async fn proof_collector(port: u16, proof_count: usize) -> Vec { - let start = std::time::Instant::now(); let result = Arc::new(RwLock::new(vec![])); @@ -190,7 +192,10 @@ async fn proof_collector(port: u16, proof_count: usize) -> Vec { if count >= proof_count { break; } - println!("count {count} - elapsed: {} secs", start_per_message.elapsed().as_secs_f64()); + println!( + "count {count} - elapsed: {} secs", + start_per_message.elapsed().as_secs_f64() + ); start_per_message = std::time::Instant::now(); } }; @@ -198,7 +203,10 @@ async fn proof_collector(port: u16, proof_count: usize) -> Vec { let _res = tokio::time::timeout(Duration::from_secs(500), receiver).await; println!("_res: {:?}", _res); let res = std::mem::take(&mut *result.write()); - println!("[proof_collector] elapsed: {} secs", start.elapsed().as_secs_f64()); + println!( + "[proof_collector] elapsed: {} secs", + start.elapsed().as_secs_f64() + ); res } diff --git a/smart_contract/Cargo.toml b/smart_contract/Cargo.toml index 0cfd828..df112b9 100644 --- a/smart_contract/Cargo.toml +++ b/smart_contract/Cargo.toml @@ -7,5 +7,10 @@ edition = "2024" alloy.workspace = true url.workspace = true async-trait.workspace = true -derive_more.workspace = true -log = "0.4.27" \ No newline at end of file +thiserror.workspace = true +log = "0.4.27" + +[dev-dependencies] +tokio = { version = "1", features = ["macros", "rt-multi-thread"] } +claims = "0.8" + diff --git a/smart_contract/src/karma_sc.rs b/smart_contract/src/karma_sc.rs index 65ec077..96be086 100644 --- a/smart_contract/src/karma_sc.rs +++ b/smart_contract/src/karma_sc.rs @@ -1,4 +1,5 @@ // third-party +use alloy::providers::Provider; use alloy::{ primitives::{Address, U256}, providers::{ProviderBuilder, WsConnect}, @@ -9,6 +10,7 @@ use async_trait::async_trait; use url::Url; // internal use crate::AlloyWsProvider; +use crate::KarmaSC::KarmaSCInstance; #[async_trait] pub trait KarmaAmountExt { @@ -19,12 +21,42 @@ pub trait KarmaAmountExt { sol! { // https://github.com/vacp2p/staking-reward-streamer/blob/main/src/Karma.sol - #[sol(rpc)] + // + // docker run -v ./:/sources ethereum/solc:stable --bin --via-ir --optimize --optimize-runs 1 --overwrite @openzeppelin/contracts=/sources/lib/openzeppelin-contracts/contracts @openzeppelin/contracts-upgradeable=/sources/lib/openzeppelin-contracts-upgradeable/contracts /sources/src/Karma.sol + + #[sol(rpc, bytecode="60a0806040523460d657306080525f549060ff8260081c166084575060ff80821610604b575b6040516128ae90816100db8239608051818181610dcd01528181610f3401526114f70152f35b60ff90811916175f557f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498602060405160ff8152a15f6025565b62461bcd60e51b815260206004820152602760248201527f496e697469616c697a61626c653a20636f6e747261637420697320696e697469604482015266616c697a696e6760c81b6064820152608490fd5b5f80fdfe6080806040526004361015610012575f80fd5b5f905f3560e01c90816301ffc9a714611ac957508063064dc9d214611a9057806306fdde03146119d9578063095ea7b314610a8b57806318160ddd146119b35780631972bc0d146119915780631c96e9501461191457806323b872dd146118e2578063248a9ca3146118c45780632f2ff15d1461182b578063313ce5671461181057806336568abe1461177e5780633659cfe6146114d9578063393ebbf81461146a578063395093511461144457806340c10f19146113215780634ea63793146111e55780634f1ef28614610ebf5780634fb3fda714610ea05780635095af6414610e7857806352d1902d14610dba5780635c15360914610d9f57806361252fd114610cdf5780636366c24214610ca557806370a0823114610c795780638d41b1b714610c5a57806391d1485414610c1057806395d89b4114610b54578063a217fddf14610b38578063a3f4df7e14610b1c578063a457c2d714610a90578063a9059cbb14610a8b578063bb6d890f14610a0b578063bd58199a146109ec578063c4d66de8146105b9578063c96be4cb14610392578063d547741f14610358578063dd62ed3e14610329578063f5b541a6146102ee578063f76f8d78146102c2578063fc69603c146102a5578063fd9ac78714610277578063fdf1a0e8146102235763fe8644f114610202575f80fd5b34610220578060031936011261022057602061012e54604051908152f35b80fd5b503461022057604036600319011261022057604061023f611b1c565b91610248611b32565b9260018060a01b03168152610132602052209060018060a01b03165f52602052602060405f2054604051908152f35b503461022057602036600319011261022057602061029b610296611b1c565b6120df565b9050604051908152f35b503461022057806003193601126102205760206040516127108152f35b50346102205780600319360112610220576102ea6102de611c00565b60405191829182611b48565b0390f35b503461022057806003193601126102205760206040517f97667070c54ef182b0f5858b034beac1b6f3089aa2d3188bb1e8929f4fa9b9298152f35b503461022057604036600319011261022057602090610346611b1c565b5061034f611b32565b50604051908152f35b50346102205760403660031901126102205761038f600435610378611b32565b9061038a61038582611c5b565b611e26565b611e6a565b80f35b5034610220576020366003190112610220576103ac611b1c565b81805260fb60209081526040808420335f908152925290205460ff161580610589575b61057a576103dc81611d37565b1561056b5781906001600160a01b0316815b61012f548310156104e65761040283612518565b90546040516338cbb55760e11b81526004810185905260039290921b1c6001600160a01b031690602081602481855afa9081156104db5786916104a3575b509060019261047261046d61049b94848a5261013260205260408a20885f5260205260405f205490611d2a565b611fef565b91875261013260205260408720855f5260205260405f20610494838254611d09565b9055611d09565b9201916103ee565b9190506020823d82116104d3575b816104be60209383611b8b565b810103126104cf5790516001610440565b5f80fd5b3d91506104b1565b6040513d88823e3d90fd5b906105288460209383825260338552604061051361046d8285205487865261013389528386205490611d2a565b92858152610133875220610494838254611d09565b906105368261012e54611d09565b61012e557fbd92430413e6ad801dfeeeb5f58e6b0ea3d9365b67b1d7fe8896e4025b83b8f183604051848152a2604051908152f35b630913597360e21b8252600482fd5b630e85f68360e11b8252600482fd5b505f805160206127b9833981519152825260fb60209081526040808420335f908152925290205460ff16156103cf565b5034610220576020366003190112610220576105d3611b1c565b9080549160ff8360081c1615928380946109df575b80156109c8575b1561096c5760ff19811660011783558361095b575b506001600160a01b031691821561094c5761061d611bdd565b92610626611c00565b9361064060ff855460081c1661063b816121f7565b6121f7565b8051906001600160401b03821161093857819061065e603654611c23565b601f81116108dd575b50602090601f831160011461087957869261086e575b50508160011b915f199060031b1c1916176036555b83516001600160401b03811161085a576106ad603754611c23565b601f811161080a575b50602094601f82116001146107a757948495829394959261079c575b50508160011b915f199060031b1c1916176037555b6106fb60ff845460081c1661063b816121f7565b82805260fb60205260408320815f5260205260ff60405f20541615610761575b506113886101345561072a5780f35b61ff001981541681557f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498602060405160018152a180f35b82805260fb60205260408320815f5260205260405f20600160ff198254161790553390835f805160206126b98339815191528180a45f61071b565b015190505f806106d2565b601f198216956037865280862091865b8881106107f2575083600195969798106107da575b505050811b016037556106e7565b01515f1960f88460031b161c191690555f80806107cc565b919260206001819286850151815501940192016107b7565b603785525f805160206127f9833981519152601f830160051c81019160208410610850575b601f0160051c01905b81811061084557506106b6565b858155600101610838565b909150819061082f565b634e487b7160e01b84526041600452602484fd5b015190505f8061067d565b603687528187209250601f198416875b8181106108c557509084600195949392106108ad575b505050811b01603655610692565b01515f1960f88460031b161c191690555f808061089f565b92936020600181928786015181550195019301610889565b603687529091505f80516020612719833981519152601f840160051c8101916020851061092e575b90601f859493920160051c01905b8181106109205750610667565b878155849350600101610913565b9091508190610905565b634e487b7160e01b85526041600452602485fd5b6324f61cdb60e01b8252600482fd5b61ffff19166101011782555f610604565b60405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b6064820152608490fd5b50303b1580156105ef5750600160ff8216146105ef565b50600160ff8216106105e8565b5034610220578060031936011261022057602061013454604051908152f35b503461022057602036600319011261022057610a25611b1c565b610a2d611d5a565b6001600160a01b0316610a3f81612506565b15610a7c57602081610a717f51493821a1424bebf150d9f396be5e9a86af464e975f17d50832d7fb9305f04b936125b8565b50604051908152a180f35b630d1a32eb60e11b8252600482fd5b611b72565b503461022057604036600319011261022057610aaa611b1c565b60243580610ac957610abe92039033611eeb565b602060405160018152f35b60405162461bcd60e51b815260206004820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f77604482015264207a65726f60d81b6064820152608490fd5b50346102205780600319360112610220576102ea6102de611bdd565b5034610220578060031936011261022057602090604051908152f35b5034610220578060031936011261022057604051908060375490610b7782611c23565b8085529160018116908115610be95750600114610b9f575b6102ea846102de81860382611b8b565b603781525f805160206127f9833981519152939250905b808210610bcf575090915081016020016102de82610b8f565b919260018160209254838588010152019101909291610bb6565b60ff191660208087019190915292151560051b850190920192506102de9150839050610b8f565b5034610220576040366003190112610220576040610c2c611b32565b91600435815260fb602052209060018060a01b03165f52602052602060ff60405f2054166040519015158152f35b5034610220578060031936011261022057602061012d54604051908152f35b5034610220576020366003190112610220576020610c9d610c98611b1c565b611d37565b604051908152f35b5034610220576020366003190112610220576020906040906001600160a01b03610ccd611b1c565b16815261013183522054604051908152f35b503461022057806003193601126102205760405161012f8054808352908352602082019081907f232da9e50dad2971456a78fb5cd6ff6b75019984d6e918139ce990999420f97990855b818110610d895750505082610d3f910383611b8b565b604051928392602084019060208552518091526040840192915b818110610d67575050500390f35b82516001600160a01b0316845285945060209384019390920191600101610d59565b8254845260209093019260019283019201610d29565b50346102205780600319360112610220576020610c9d612032565b50346102205780600319360112610220577f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03163003610e125760206040515f805160206127998339815191528152f35b60405162461bcd60e51b815260206004820152603860248201527f555550535570677261646561626c653a206d757374206e6f742062652063616c6044820152771b1959081d1a1c9bdd59da0819195b1959d85d1958d85b1b60421b6064820152608490fd5b503461022057806003193601126102205760206040515f805160206127b98339815191528152f35b5034610220576020366003190112610220576020610c9d600435611fef565b50604036600319011261022057610ed4611b1c565b6024356001600160401b0381116111e157366023820112156111e15780600401359083610f0083611bc2565b91610f0e6040519384611b8b565b838352602083019336602482840101116111e15780602460209301863783010152610f867f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316610f6830821415611c6d565b5f80516020612799833981519152546001600160a01b031614611cbb565b83805260fb60209081526040808620335f908152925290205460ff16156111d2575f805160206126f98339815191525460ff1615610fc957505061038f90612257565b6040516352d1902d60e01b81526001600160a01b03841690602081600481855afa86918161119a575b506110405760405162461bcd60e51b815260206004820152602e60248201525f8051602061285983398151915260448201526d6f6e206973206e6f74205555505360901b6064820152608490fd5b5f80516020612799833981519152036111565761105c84612257565b604051905f805160206127d98339815191528680a281511580159061114e575b611088575b5050505080f35b833b1561111057506110ff928492839251915af43d15611109573d6110ac81611bc2565b906110ba6040519283611b8b565b81523d84602083013e5b604051916110d3606084611b8b565b602783525f805160206128398339815191526020840152660819985a5b195960ca1b6040840152612680565b505f808080611081565b60606110c4565b62461bcd60e51b815260206004820152602660248201525f805160206126d98339815191526044820152651b9d1c9858dd60d21b6064820152608490fd5b50600161107c565b60405162461bcd60e51b815260206004820152602960248201525f805160206128198339815191526044820152681a58589b195555525160ba1b6064820152608490fd5b9091506020813d6020116111ca575b816111b660209383611b8b565b810103126111c65751905f610ff2565b8680fd5b3d91506111a9565b630e85f68360e11b8452600484fd5b8280fd5b50346104cf5760603660031901126104cf576111ff611b1c565b335f9081525f805160206127598339815191526020526040902054602435919060ff1615806112fc575b6112ed576001600160a01b03169061124082612506565b156112de5761125e8161125960355461012d5490611d09565b611d09565b50815f5261013160205260405f20611277828254611d09565b90556112868161012d54611d09565b61012d55813b156104cf575f9160448392604051948593849263523dea4b60e11b84526004840152833560248401525af180156112d3576112c5575080f35b6112d191505f90611b8b565b005b6040513d5f823e3d90fd5b630d1a32eb60e11b5f5260045ffd5b630e85f68360e11b5f5260045ffd5b50335f9081525f80516020612779833981519152602052604090205460ff1615611229565b346104cf5760403660031901126104cf5761133a611b1c565b335f9081525f805160206127598339815191526020526040902054602435919060ff16158061141f575b6112ed576035549061137d8361125961012d5485611d09565b506001600160a01b03169182156113da576020816113bd7fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef935f95611d09565b6035558484526033825260408420818154019055604051908152a3005b60405162461bcd60e51b815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f2061646472657373006044820152606490fd5b50335f9081525f80516020612779833981519152602052604090205460ff1615611364565b346104cf5760403660031901126104cf57610abe611460611b1c565b6024359033611eeb565b346104cf5760203660031901126104cf57600435611486611d5a565b61271081116114ca5760407f2b60ba8fc3afb74a8e3e46b5f094e0578b708b8066fccfe8d038c7c2dfc15dae916101345490806101345582519182526020820152a1005b6308b26a8360e31b5f5260045ffd5b346104cf5760203660031901126104cf576114f2611b1c565b61152b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316610f6830821415611c6d565b335f9081525f80516020612759833981519152602052604090205460ff16156112ed5760209060405161155e8382611b8b565b5f815282810190601f1984013683375f805160206126f98339815191525460ff16156115905750506112d19150612257565b6040516352d1902d60e01b81526001600160a01b038416908581600481855afa5f918161174f575b506116065760405162461bcd60e51b815260048101879052602e60248201525f8051602061285983398151915260448201526d6f6e206973206e6f74205555505360901b6064820152608490fd5b5f805160206127998339815191520361170b5761162284612257565b604051905f805160206127d98339815191525f80a2815115801590611704575b61164857005b833b156116c657506112d193925f92839251915af43d156116be573d9061166e82611bc2565b9161167c6040519384611b8b565b82523d5f8484013e5b5f80516020612839833981519152604051936116a2606086611b8b565b60278552840152660819985a5b195960ca1b6040840152612680565b606090611685565b62461bcd60e51b815260048101859052602660248201525f805160206126d98339815191526044820152651b9d1c9858dd60d21b6064820152608490fd5b505f611642565b60405162461bcd60e51b815260048101869052602960248201525f805160206128198339815191526044820152681a58589b195555525160ba1b6064820152608490fd5b9091508681813d8311611777575b6117678183611b8b565b810103126104cf575190876115b8565b503d61175d565b346104cf5760403660031901126104cf57611797611b32565b336001600160a01b038216036117b3576112d190600435611e6a565b60405162461bcd60e51b815260206004820152602f60248201527f416363657373436f6e74726f6c3a2063616e206f6e6c792072656e6f756e636560448201526e103937b632b9903337b91039b2b63360891b6064820152608490fd5b346104cf575f3660031901126104cf57602060405160128152f35b346104cf5760403660031901126104cf57600435611847611b32565b9061185461038582611c5b565b805f5260fb60205260405f2060018060a01b0383165f5260205260ff60405f2054161561187d57005b5f81815260fb602090815260408083206001600160a01b0395909516808452949091528120805460ff19166001179055339291905f805160206126b98339815191529080a4005b346104cf5760203660031901126104cf576020610c9d600435611c5b565b346104cf5760603660031901126104cf576118fb611b1c565b50611904611b32565b5063d981438f60e01b5f5260045ffd5b346104cf5760203660031901126104cf5761192d611b1c565b611935611d5a565b6001600160a01b031661194781612506565b611982576020816119787f74f5fc8438e42d45c059bfc6e1ab488468e0110e15bd5607d559bb36622120da93612547565b50604051908152a1005b6307378add60e11b5f5260045ffd5b346104cf575f3660031901126104cf576020604051670de0b6b3a76400008152f35b346104cf575f3660031901126104cf576020610c9d6035546119d3612032565b90611d09565b346104cf575f3660031901126104cf576040515f6036546119f981611c23565b8084529060018116908115611a6c5750600114611a21575b6102ea836102de81850382611b8b565b60365f9081525f80516020612719833981519152939250905b808210611a52575090915081016020016102de611a11565b919260018160209254838588010152019101909291611a3a565b60ff191660208086019190915291151560051b840190910191506102de9050611a11565b346104cf5760203660031901126104cf576001600160a01b03611ab1611b1c565b165f52610133602052602060405f2054604051908152f35b346104cf5760203660031901126104cf576004359063ffffffff60e01b82168092036104cf57602091637965db0b60e01b8114908115611b0b575b5015158152f35b6301ffc9a760e01b14905083611b04565b600435906001600160a01b03821682036104cf57565b602435906001600160a01b03821682036104cf57565b602060409281835280519182918282860152018484015e5f828201840152601f01601f1916010190565b346104cf5760403660031901126104cf57611904611b1c565b601f909101601f19168101906001600160401b03821190821017611bae57604052565b634e487b7160e01b5f52604160045260245ffd5b6001600160401b038111611bae57601f01601f191660200190565b60405190611bec604083611b8b565b60058252644b61726d6160d81b6020830152565b60405190611c0f604083611b8b565b60058252644b41524d4160d81b6020830152565b90600182811c92168015611c51575b6020831014611c3d57565b634e487b7160e01b5f52602260045260245ffd5b91607f1691611c32565b5f5260fb602052600160405f20015490565b15611c7457565b60405162461bcd60e51b815260206004820152602c60248201525f8051602061273983398151915260448201526b19195b1959d85d1958d85b1b60a21b6064820152608490fd5b15611cc257565b60405162461bcd60e51b815260206004820152602c60248201525f8051602061273983398151915260448201526b6163746976652070726f787960a01b6064820152608490fd5b91908201809211611d1657565b634e487b7160e01b5f52601160045260245ffd5b91908203918211611d1657565b611d40906120df565b81811015611d5457611d5191611d2a565b90565b50505f90565b335f9081525f80516020612759833981519152602052604090205460ff1615611d7f57565b611e226020611e0a6011611d923361237e565b603784611d9e5f612464565b60405196879476020b1b1b2b9b9a1b7b73a3937b61d1030b1b1b7bab73a1604d1b828701528051918291018587015e8401907001034b99036b4b9b9b4b733903937b6329607d1b84830152805192839101604883015e01015f838201520301601f198101835282611b8b565b60405162461bcd60e51b815291829160048301611b48565b0390fd5b5f81815260fb6020908152604080832033845290915290205460ff1615611e4a5750565b6020611e0a6011611e2293603784611d9e611e643361237e565b93612464565b805f5260fb60205260405f2060018060a01b0383165f5260205260ff60405f205416611e94575050565b5f81815260fb602090815260408083206001600160a01b0395909516808452949091528120805460ff19169055339291907ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b9080a4565b6001600160a01b0316908115611f9e576001600160a01b0316918215611f4e5760207f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92591835f526034825260405f20855f5282528060405f2055604051908152a3565b60405162461bcd60e51b815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b6064820152608490fd5b60405162461bcd60e51b8152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b6064820152608490fd5b611ffc61013454826122e7565b90670de0b6b3a76400008210612010575090565b9050670de0b6b3a76400008110156120255790565b50670de0b6b3a764000090565b5f8061012f545b80821061205657505061012d54808211612051575090565b905090565b90916004602061206585612518565b905460405163b58ba16360e01b81529384929091839160031b1c6001600160a01b03165afa9081156112d3575f916120ad575b506120a590600192611d09565b920190612039565b90506020813d82116120d7575b816120c760209383611b8b565b810103126104cf57516001612098565b3d91506120ba565b905f80925f61012f545b8082106121305750506001600160a01b03165f8181526101336020526040902054909361212c929161211a91611d09565b935f52603360205260405f2054611d09565b9190565b90949261213c86612518565b90546040516338cbb55760e11b81526001600160a01b03868116600483015260039390931b9190911c909116949091602083602481895afa9283156112d3575f936121c0575b50906121936001936121b893611d09565b955f5261013260205260405f20838060a01b0386165f5260205260405f205490611d09565b9501906120e9565b9250906020833d82116121ef575b816121db60209383611b8b565b810103126104cf5791519190612193612182565b3d91506121ce565b156121fe57565b60405162461bcd60e51b815260206004820152602b60248201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960448201526a6e697469616c697a696e6760a81b6064820152608490fd5b803b1561228c575f8051602061279983398151915280546001600160a01b0319166001600160a01b0392909216919091179055565b60405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b6064820152608490fd5b9091905f905f19848209908481029283808410930392808403931461234c5782612710111561022057507fbc01a36e2eb1c432ca57a786c226809d495182a9930be0ded288ce703afb7e919394612710910990828211900360fc1b910360041c170290565b5050506127109192500490565b90815181101561236a570160200190565b634e487b7160e01b5f52603260045260245ffd5b612388602a611bc2565b906123966040519283611b8b565b602a82526123a4602a611bc2565b6020830190601f190136823782511561236a576030905381516001101561236a576078602183015360295b6001811161242357506123df5790565b606460405162461bcd60e51b815260206004820152602060248201527f537472696e67733a20686578206c656e67746820696e73756666696369656e746044820152fd5b90600f8116601081101561236a576f181899199a1a9b1b9c1cb0b131b232b360811b901a6124518385612359565b5360041c908015611d16575f19016123cf565b61246e6042611bc2565b9061247c6040519283611b8b565b6042825261248a6042611bc2565b6020830190601f190136823782511561236a576030905381516001101561236a576078602183015360415b600181116124c557506123df5790565b90600f8116601081101561236a576f181899199a1a9b1b9c1cb0b131b232b360811b901a6124f38385612359565b5360041c908015611d16575f19016124b5565b5f5261013060205260405f2054151590565b61012f5481101561236a5761012f5f5260205f2001905f90565b805482101561236a575f5260205f2001905f90565b805f5261013060205260405f2054155f146125b35761012f54600160401b811015611bae5761259a61258482600185940161012f5561012f612532565b819391549060031b91821b915f19901b19161790565b905561012f54905f5261013060205260405f2055600190565b505f90565b5f81815261013060205260409020548015611d54575f198101818111611d165761012f545f19810191908211611d1657808203612645575b50505061012f548015612631575f190161260c8161012f612532565b8154905f199060031b1b1916905561012f555f526101306020525f6040812055600190565b634e487b7160e01b5f52603160045260245ffd5b6126696126576125849361012f612532565b90549060031b1c92839261012f612532565b90555f5261013060205260405f20555f80806125f0565b9091901561268c575090565b81511561269c5750805190602001fd5b60405162461bcd60e51b8152908190611e229060048301611b4856fe2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d416464726573733a2064656c65676174652063616c6c20746f206e6f6e2d636f4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd91434a11f94e20a93c79f6ec743a1954ec4fc2c08429ae2122118bf234b2185c81b846756e6374696f6e206d7573742062652063616c6c6564207468726f75676820c88390e7e62175be0932452175b6a7222b6b094ab0ef984a5153c620345d897537c5d0508d5d763c49e75786bc8129e4989797a94b61e8d0b75859fd0ec84daa360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc12b42e8a160f6064dc959c6f251e3af0750ad213dbecf573b4710d67d6c28e39bc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b42a7b7dd785cd69714a189dffb3fd7d7174edc9ece837694ce50f7078f7c31ae45524331393637557067726164653a20756e737570706f727465642070726f78416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c45524331393637557067726164653a206e657720696d706c656d656e74617469a26469706673582212204e188463b12f07d44a94882a5f68464af247a634f8194e4273a326c29b696ae064736f6c634300081a0033")] contract KarmaSC { + + /// @notice Emitted when the address is invalid + error Karma__InvalidAddress(); + /// @notice Emitted because transfers are not allowed + error Karma__TransfersNotAllowed(); + /// @notice Emitted when distributor is already added + error Karma__DistributorAlreadyAdded(); + /// @notice Emitted when distributor is not found + error Karma__UnknownDistributor(); + /// @notice Emitted sender does not have the required role + error Karma__Unauthorized(); + /// @notice Emitted when slash percentage to set is invalid + error Karma__InvalidSlashPercentage(); + /// @notice Emitted when balance to slash is invalid + error Karma__CannotSlashZeroBalance(); + // From: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/IERC20.sol#L16 event Transfer(address indexed from, address indexed to, uint256 value); function balanceOf(address account) public view override returns (uint256); + + function initialize(address _owner) public initializer; + + function totalSupply() public view virtual override returns (uint256); + function name() public view virtual override returns (string memory); + + // function _totalSupply() internal view returns (uint256); + function mint(address account, uint256 amount) public virtual onlyAdminOrOperator; + function addRewardDistributor(address distributor) public virtual onlyRole(DEFAULT_ADMIN_ROLE); + function setReward(address rewardsDistributor,uint256 amount,uint256 duration); + } } @@ -32,14 +64,186 @@ impl KarmaSC::KarmaSCInstance { pub async fn try_new(rpc_url: Url, address: Address) -> Result> { let ws = WsConnect::new(rpc_url.as_str()); let provider = ProviderBuilder::new().connect_ws(ws).await?; - Ok(KarmaSC::new(address, provider)) + // Ok(KarmaSC::new(address, provider)) + Ok(KarmaSCInstance::from((address, provider))) + } +} + +impl From<(Address, T)> for KarmaSC::KarmaSCInstance { + fn from((address, provider): (Address, T)) -> Self { + KarmaSC::new(address, provider) } } #[async_trait] -impl KarmaAmountExt for KarmaSC::KarmaSCInstance { +impl KarmaAmountExt for KarmaSC::KarmaSCInstance { type Error = alloy::contract::Error; async fn karma_amount(&self, address: &Address) -> Result { self.balanceOf(*address).call().await } } + +#[cfg(test)] +pub(crate) mod tests { + use super::*; + // third-party + use alloy::primitives::U256; + use alloy::primitives::address; + use alloy::sol_types::SolCall; + use claims::assert_gt; + + sol! { + + // src: staking-reward-streamer/test/mocks/KarmaDistributorMock.sol + // Compile bytecode (in staking-reward-streamer folder): + // docker run -v ./:/sources ethereum/solc:0.8.26 --bin --via-ir --optimize --optimize-runs 1 --overwrite @openzeppelin/contracts=/sources/lib/openzeppelin-contracts/contracts @openzeppelin/contracts-upgradeable=/sources/lib/openzeppelin-contracts-upgradeable/contracts /sources/test/mocks/KarmaDistributorMock.sol + + #[sol(rpc, bytecode="608080604052346015576101c6908161001a8239f35b5f80fdfe6080806040526004361015610012575f80fd5b5f3560e01c90816329cdf1d21461016157816333dc41c7146101135750806371976aae146100745780638bb87710146100df578063a47bd496146100cc578063a98cdada146100af578063b58ba163146100af5763c141426314610074575f80fd5b346100ab5760203660031901126100ab576001600160a01b0361009561017a565b165f525f602052602060405f2054604051908152f35b5f80fd5b346100ab575f3660031901126100ab576020600154604051908152f35b346100ab5760403660031901126100ab57005b346100ab5760403660031901126100ab576001600160a01b0361010061017a565b165f525f60205260243560405f20555f80f35b346100ab5760203660031901126100ab5760649061012f61017a565b5062461bcd60e51b815260206004820152600f60248201526e139bdd081a5b5c1b195b595b9d1959608a1b6044820152fd5b346100ab5760203660031901126100ab57600435600155005b600435906001600160a01b03821682036100ab5756fea264697066735822122005ab03ec7e765b2b87f3d54b2b2b46e58a1f445003ba393e1d418380b422808764736f6c634300081a0033")] + contract KarmaDistributorMock is IRewardDistributor { + // solhint-disable-next-line + mapping(address => uint256) public userKarmaShare; + + uint256 public totalKarmaShares; + + function setUserKarmaShare(address user, uint256 karma) external; + + function setTotalKarmaShares(uint256 karma) external; + + function rewardsBalanceOf(address) external pure override returns (uint256); + + // solhint-disable-next-line + function setReward(uint256, uint256) external pure override; + + function rewardsBalanceOfAccount(address account) external view override returns (uint256); + + function totalRewardsSupply() external view override returns (uint256); + } + } + + sol! { + + // src: staking-reward-streamer/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol + // Compile bytecode (in staking-reward-streamer folder): + // docker run -v ./:/sources ethereum/solc:0.8.26 --bin --via-ir --optimize --optimize-runs 1 --overwrite @openzeppelin/contracts=/sources/lib/openzeppelin-contracts/contracts @openzeppelin/contracts-upgradeable=/sources/lib/openzeppelin-contracts-upgradeable/contracts /sources/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol + + #[sol(rpc, bytecode="60806040526103be80380380610014816101f2565b9283398101906040818303126101ee5780516001600160a01b038116918282036101ee576020810151906001600160401b0382116101ee57019183601f840112156101ee57825161006c6100678261022b565b6101f2565b938185526020850195602083830101116101ee57815f926020809301885e85010152813b15610193577f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc80546001600160a01b031916821790557fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b5f80a281511580159061018c575b610108575b60405160c390816102fb8239f35b5f8061017b9461011860606101f2565b94602786527f416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c6020870152660819985a5b195960ca1b60408701525190845af43d15610184573d9161016c6100678461022b565b9283523d5f602085013e610246565b505f80806100fa565b606091610246565b505f6100f5565b60405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b6064820152608490fd5b5f80fd5b6040519190601f01601f191682016001600160401b0381118382101761021757604052565b634e487b7160e01b5f52604160045260245ffd5b6001600160401b03811161021757601f01601f191660200190565b919290156102a8575081511561025a575090565b3b156102635790565b60405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606490fd5b8251909150156102bb5750805190602001fd5b604460209160405192839162461bcd60e51b83528160048401528051918291826024860152018484015e5f828201840152601f01601f19168101030190fdfe60806040523615603d575f80516020606e833981519152545f9081906001600160a01b0316368280378136915af43d5f803e156039573d5ff35b3d5ffd5b5f80516020606e833981519152545f9081906001600160a01b0316368280378136915af43d5f803e156039573d5ff3fe360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbca26469706673582212208909899f17d67fa6e7c49a2041f3e1cb1f0006bd1701c1d5b0ffb69059b4fd6164736f6c634300081a0033")] + contract ERC1967Proxy is Proxy, ERC1967Upgrade { + constructor(address _logic, bytes memory _data) payable; + function _implementation() internal view virtual override returns (address impl); + } + + } + + /* + #[tokio::test] + async fn test_balance_of() { + + let provider = ProviderBuilder::new() + .connect_anvil_with_wallet() + ; + + let contract_distributor = KarmaDistributorMock::deploy(&provider).await.unwrap(); + + // Deploy the KarmaTiers contract. + let contract = KarmaSC::deploy(&provider).await.unwrap(); + // getTierCount call + let addr = address!("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"); + let call_1 = contract.balanceOf(addr); + let result_1 = call_1.call().await.unwrap(); + println!("result_1: {:?}", result_1); + assert_eq!(result_1, U256::from(0)); + + // let call_2_0 = contract.initialize(addr); + // let tx_hash_2_0 = call_2_0.send().await.unwrap().watch().await.unwrap(); + // println!("tx_hash_2_0: {:?}", tx_hash_2_0); + + // let call_2 = contract.mint(addr, U256::from(100)); + // let tx_hash_2 = call_2.send().await.unwrap().watch().await.unwrap(); + // println!("tx_hash_2: {:?}", tx_hash_2); + + let call_2 = contract.totalSupply(); + let result_2 = call_2.call().await.unwrap(); + println!("result_2: {:?}", result_2); + // assert_eq!(result_2, U256::from(0)); + + let call_2_2 = contract.name(); + let result_2_2 = call_2_2.call().await.unwrap(); + println!("result_2_2: {:?}", result_2_2); + + let call_3_0 = contract.mint(addr, U256::from(1)); + let tx_hash_3 = call_3_0.send().await.unwrap().watch().await.unwrap(); + println!("tx_hash_3: {:?}", tx_hash_3); + + let call_3 = contract.balanceOf(addr); + let result_3 = call_3.call().await.unwrap(); + println!("result_3: {:?}", result_3); + } + */ + + #[tokio::test] + async fn test_karma_amount() { + let provider = ProviderBuilder::new().connect_anvil_with_wallet(); + + let contract_distributor_1 = KarmaDistributorMock::deploy(&provider).await.unwrap(); + let contract_distributor_2 = KarmaDistributorMock::deploy(&provider).await.unwrap(); + + // Deploy the KarmaTiers contract. + let contract_0 = KarmaSC::deploy(&provider).await.unwrap(); + + let addr_alice = address!("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"); + + let init_data = KarmaSC::initializeCall { _owner: addr_alice }.abi_encode(); + let contract_proxy = + ERC1967Proxy::deploy(&provider, *contract_0.address(), init_data.into()) + .await + .unwrap(); + + println!("contract_proxy: {:?}", contract_proxy.address()); + let contract = KarmaSC::new(*contract_proxy.address(), &provider); + println!("contract KarmaSC: {:?}", contract_proxy.address()); + + // + let call_0_1 = contract.addRewardDistributor(*contract_distributor_1.address()); + let _tx_hash_0_1 = call_0_1.send().await.unwrap().watch().await.unwrap(); + let call_0_2 = contract.addRewardDistributor(*contract_distributor_2.address()); + let _tx_hash_0_2 = call_0_2.send().await.unwrap().watch().await.unwrap(); + + let call_1_1 = contract.setReward( + *contract_distributor_1.address(), + U256::from(1000), + U256::from(1000), + ); + let _tx_hash_1_1 = call_1_1.send().await.unwrap().watch().await.unwrap(); + let call_1_2 = contract.setReward( + *contract_distributor_2.address(), + U256::from(1000), + U256::from(1000), + ); + let _tx_hash_1_2 = call_1_2.send().await.unwrap().watch().await.unwrap(); + + let call_2_1 = contract_distributor_1.setTotalKarmaShares(U256::from(1000)); + let _tx_hash_2_1 = call_2_1.send().await.unwrap().watch().await.unwrap(); + let call_2_2 = contract_distributor_2.setTotalKarmaShares(U256::from(1000)); + let _tx_hash_2_2 = call_2_2.send().await.unwrap().watch().await.unwrap(); + + let addr_bob = address!("0x70997970C51812dc3A010C7d01b50e0d17dc79C8"); + let call_3_1 = contract_distributor_1.setUserKarmaShare(addr_bob, U256::from(1000e18)); + let _tx_hash_3_1 = call_3_1.send().await.unwrap().watch().await.unwrap(); + let call_3_2 = contract_distributor_2.setUserKarmaShare(addr_bob, U256::from(1000e18)); + let _tx_hash_3_2 = call_3_2.send().await.unwrap().watch().await.unwrap(); + + // mint some Karma + let call_3_0 = contract.mint(addr_alice, U256::from(500_000)); + let tx_hash_3 = call_3_0.send().await.unwrap().watch().await.unwrap(); + println!("tx_hash_3: {:?}", tx_hash_3); + + let call_4 = contract.balanceOf(addr_bob); + let result_4 = call_4.call().await.unwrap(); + + let ksc = KarmaSCInstance::from((*contract.address(), provider.clone())); + let result_5 = ksc.karma_amount(&addr_bob).await.unwrap(); + + assert_gt!(result_4, U256::from(0)); + assert_eq!(result_4, result_5); + } +} diff --git a/smart_contract/src/karma_tiers.rs b/smart_contract/src/karma_tiers.rs index 14b8778..285d2ef 100644 --- a/smart_contract/src/karma_tiers.rs +++ b/smart_contract/src/karma_tiers.rs @@ -1,64 +1,239 @@ -use std::collections::BTreeMap; -use std::ops::Add; +use std::fmt::Formatter; // third-party +use alloy::providers::{MulticallError, Provider}; +use alloy::transports::{RpcError, TransportErrorKind}; use alloy::{ primitives::{Address, U256}, providers::{ProviderBuilder, WsConnect}, sol, }; -use derive_more::From; use url::Url; // internal use crate::AlloyWsProvider; +/* sol! { // https://github.com/vacp2p/staking-reward-streamer/pull/224 #[sol(rpc)] contract KarmaTiersSC { - /// @notice Emitted when a new tier is added - event TierAdded(uint8 indexed tierId, string name, uint256 minKarma, uint256 maxKarma, uint32 txPerEpoch); - /// @notice Emitted when a tier is updated - event TierUpdated(uint8 indexed tierId, string name, uint256 minKarma, uint256 maxKarma, uint32 txPerEpoch); + event TiersUpdated(); struct Tier { uint256 minKarma; uint256 maxKarma; string name; uint32 txPerEpoch; - bool active; } - mapping(uint8 id => Tier tier) public tiers; - uint8 public currentTierId; + // mapping(uint8 id => Tier tier) public tiers; + // uint8 public currentTierId; + Tier[] public tiers; + + function getTierCount() external view returns (uint256 count); } } +*/ -impl KarmaTiersSC::KarmaTiersSCInstance { +sol!( + // https://github.com/vacp2p/staking-reward-streamer/pull/224 + // Compile bytecode using: + // docker run -v ./:/sources ethereum/solc:stable --bin --via-ir --optimize --optimize-runs 1 --overwrite @openzeppelin/contracts=/sources/lib/openzeppelin-contracts/contracts /sources/src/KarmaTiers.sol + + #[sol(rpc, bytecode = "608080604052346100da57610013336100de565b5f54336001600160a01b039091160361009857331561004457610035336100de565b604051610a8990816101258239f35b60405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b6064820152608490fd5b62461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152606490fd5b5f80fd5b5f80546001600160a01b039283166001600160a01b03198216811783559216907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09080a356fe60806040526004361015610011575f80fd5b5f3560e01c8063039af9eb146107345780635e12faa91461071a57806367184e28146106fd578063715018a6146106b95780638da5cb5b14610692578063a04f7fc714610669578063c7a416711461058d578063f1180965146101375763f2fde38b1461007c575f80fd5b34610133576020366003190112610133576004356001600160a01b03811690819003610133576100aa6109dc565b80156100df575f80546001600160a01b03198116831782556001600160a01b0316905f516020610a345f395f51905f529080a3005b60405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b6064820152608490fd5b5f80fd5b34610133576020366003190112610133576004356001600160401b0381116101335736602382011215610133576004810135906001600160401b03821161013357602481013660248460051b84010111610133576101936109dc565b821561057e576101a38382610979565b3561055957506001545f6001558061049d575b505f9160a219368390030191835b60ff81169083821015610477576024611fe08260051b1684010135858112156101335760249084010191604083016101fc81856109aa565b6001600160401b0381116103d25760405191610222601f8301601f19166020018461082a565b818352368282011161013357815f9260209283860137830101528051156104685751602081116104515750602084013597843592838a111561043a57806103f9575b50506001548890600160401b8110156103d25780600161028792016001556107a7565b9390936103e657835560018301556102a36002830191856109aa565b906001600160401b0382116103d2576102bc83546107d7565b601f8111610397575b505f90601f831160011461032f57918060039492606096945f92610324575b50508160011b915f1990861b1c19161790555b019201359163ffffffff83168093036101335761031f9263ffffffff19825416179055610911565b6101c4565b013590508c806102e4565b601f19831691845f5260205f20925f5b81811061037f5750926001928592606098966003989610610368575b505050811b0190556102f7565b01355f1983881b60f8161c191690558c808061035b565b9193602060018192878701358155019501920161033f565b6103c290845f5260205f20601f850160051c810191602086106103c8575b601f0160051c0190610994565b8a6102c5565b90915081906103b5565b634e487b7160e01b5f52604160045260245ffd5b634e487b7160e01b5f525f60045260245ffd5b600182018092116104265783821461026457909150639026ca5960e01b5f5260045260245260445260645ffd5b634e487b7160e01b5f52601160045260245ffd5b89846308a0f77b60e31b5f5260045260245260445ffd5b6307b53eff60e51b5f52600452602060245260445ffd5b637e15dcc760e01b5f5260045ffd5b7f37740b69a1cce7c6b884ff59b1465c52017ffcb23b6e46249f50f3375b71eada5f80a1005b6001600160fe1b03811681036104265760015f5260021b7fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6908101905b8181106104e757506101b6565b805f600492555f60018201556002810161050181546107d7565b9081610516575b50505f6003820155016104da565b81601f5f931160011461052d5750555b8580610508565b8183526020832061054991601f0160051c810190600101610994565b8082528160208120915555610526565b8261056391610979565b35639026ca5960e01b5f525f6004525f60245260445260645ffd5b630b59608360e21b5f5260045ffd5b346101335760203660031901126101335760043560ff8116808203610133575f60606040516105bb8161080f565b8281528260208201528160408201520152600154111561065a576105de906107a7565b506040516105eb8161080f565b815481526001820154916020820192835263ffffffff61064e8160036106136002860161084d565b9460408701958652015416926060850193845260405195869560208752516020870152516040860152516080606086015260a08501906108ed565b91511660808301520390f35b63b4bcd5a960e01b5f5260045ffd5b34610133576020366003190112610133576020610687600435610922565b60ff60405191168152f35b34610133575f366003190112610133575f546040516001600160a01b039091168152602090f35b34610133575f366003190112610133576106d16109dc565b5f80546001600160a01b0319811682556001600160a01b03165f516020610a345f395f51905f528280a3005b34610133575f366003190112610133576020600154604051908152f35b34610133575f366003190112610133576020604051818152f35b34610133576020366003190112610133576004356001548110156101335761075b906107a7565b50805460018201549161079d63ffffffff600361077a6002850161084d565b9301541691604051948594855260208501526080604085015260808401906108ed565b9060608301520390f35b6001548110156107c35760015f5260205f209060021b01905f90565b634e487b7160e01b5f52603260045260245ffd5b90600182811c92168015610805575b60208310146107f157565b634e487b7160e01b5f52602260045260245ffd5b91607f16916107e6565b608081019081106001600160401b038211176103d257604052565b601f909101601f19168101906001600160401b038211908210176103d257604052565b9060405191825f825492610860846107d7565b80845293600181169081156108cb5750600114610887575b506108859250038361082a565b565b90505f9291925260205f20905f915b8183106108af575050906020610885928201015f610878565b6020919350806001915483858901015201910190918492610896565b90506020925061088594915060ff191682840152151560051b8201015f610878565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b60ff1660ff81146104265760010190565b6001545f5b60ff8116828110156109655761093c826107a7565b50548410610953575061094e90610911565b610927565b925050505f190160ff81116104265790565b50505f198101915081116104265760ff1690565b90156107c357803590607e1981360301821215610133570190565b81811061099f575050565b5f8155600101610994565b903590601e198136030182121561013357018035906001600160401b0382116101335760200191813603831361013357565b5f546001600160a01b031633036109ef57565b606460405162461bcd60e51b815260206004820152602060248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152fdfe8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0a26469706673582212208f41e7c9492705a355026a69ee93fdb152512cb904e1d9b5d822d67cefbf646964736f6c634300081e0033")] + + contract KarmaTiers is Ownable { + /// @notice Emitted when a tier list is updated + event TiersUpdated(); + /// @notice Emitted when a transaction amount is invalid + + error InvalidTxAmount(); + /// @notice Emitted when a tier name is empty + error EmptyTierName(); + /// @notice Emitted when a tier array is empty + error EmptyTiersArray(); + /// @notice Emitted when a tier is not found + error TierNotFound(); + /// @notice Emitted when a tier name exceeds maximum length + error TierNameTooLong(uint256 nameLength, uint256 maxLength); + /// @notice Emitted when tiers are not contiguous + error NonContiguousTiers(uint8 index, uint256 expectedMinKarma, uint256 actualMinKarma); + /// @notice Emitted when a tier's minKarma is greater than or equal to maxKarma + error InvalidTierRange(uint256 minKarma, uint256 maxKarma); + + struct Tier { + uint256 minKarma; + uint256 maxKarma; + string name; + uint32 txPerEpoch; + } + + modifier onlyValidTierId(uint8 tierId) { + if (tierId >= tiers.length) { + revert TierNotFound(); + } + _; + } + + uint256 public constant MAX_TIER_NAME_LENGTH = 32; + + Tier[] public tiers; + + constructor() { + transferOwnership(msg.sender); + } + + function updateTiers(Tier[] calldata newTiers) external onlyOwner { + if (newTiers.length == 0) { + revert EmptyTiersArray(); + } + // Ensure the first tier starts at minKarma = 0 + if (newTiers[0].minKarma != 0) { + revert NonContiguousTiers(0, 0, newTiers[0].minKarma); + } + + delete tiers; // Clear existing tiers + + uint256 lastMaxKarma = 0; + for (uint8 i = 0; i < newTiers.length; i++) { + Tier calldata input = newTiers[i]; + + _validateTierName(input.name); + if (input.maxKarma <= input.minKarma) { + revert InvalidTierRange(input.minKarma, input.maxKarma); + } + + if (i > 0) { + uint256 expectedMinKarma = lastMaxKarma + 1; + if (input.minKarma != expectedMinKarma) { + revert NonContiguousTiers(i, expectedMinKarma, input.minKarma); + } + } + lastMaxKarma = input.maxKarma; + tiers.push(input); + } + + emit TiersUpdated(); + } + + function _validateTierName(string calldata name) internal pure { + bytes memory nameBytes = bytes(name); + if (nameBytes.length == 0) revert EmptyTierName(); + if (nameBytes.length > MAX_TIER_NAME_LENGTH) { + revert TierNameTooLong(nameBytes.length, MAX_TIER_NAME_LENGTH); + } + } + + function getTierIdByKarmaBalance(uint256 karmaBalance) external view returns (uint8) { + for (uint8 i = 0; i < tiers.length; i++) { + if (karmaBalance < tiers[i].minKarma) { + return i - 1; // Return the previous tier if this one is not met + } + } + return uint8(tiers.length - 1); // If all tiers are met, return the highest tier + } + + function getTierCount() external view returns (uint256 count) { + return tiers.length; + } + + function getTierById(uint8 tierId) external view onlyValidTierId(tierId) returns (Tier memory tier) { + return tiers[tierId]; + } + } +); + +impl KarmaTiers::KarmaTiersInstance { /// Read smart contract `tiers` mapping pub async fn get_tiers( ws_rpc_url: Url, sc_address: Address, - ) -> Result, alloy::contract::Error> { + ) -> Result, GetScTiersError> { let ws = WsConnect::new(ws_rpc_url.as_str()); - let provider = ProviderBuilder::new().connect_ws(ws).await?; - let karma_tiers_sc = KarmaTiersSC::new(sc_address, provider.clone()); + let provider = ProviderBuilder::new() + .connect_ws(ws) + .await + .map_err(GetScTiersError::RpcTransportError)?; - let current_tier_id = karma_tiers_sc.currentTierId().call().await?; + Self::get_tiers_from_provider(&provider, &sc_address).await + } - let mut tiers = BTreeMap::new(); + pub async fn get_tiers_from_provider( + provider: &T, + sc_address: &Address, + ) -> Result, GetScTiersError> { + let karma_tiers_sc = KarmaTiers::new(*sc_address, provider); - // Note: By design, karmaTiers first id is 1 - for i in 1..=current_tier_id { - let tiers_at = karma_tiers_sc.tiers(i).call().await?; + let tier_count = karma_tiers_sc + .getTierCount() + .call() + .await + .map_err(GetScTiersError::Alloy)?; - tiers.insert(TierIndex::from(i), tiers_at.into()); + if tier_count > U256::from(u8::MAX) { + return Err(GetScTiersError::TierCount); } + // Note: unwrap safe - just tested + let tier_count = u8::try_from(tier_count).unwrap(); + println!("tier_count: {tier_count}"); + // Wait for issue: https://github.com/alloy-rs/alloy/issues/2744 to be fixed + /* + let get0 = CallItemBuilder::new(karma_tiers_sc.getTierById(0)); // Set the amount of eth that should be deposited into the contract. + let get1 = CallItemBuilder::new(karma_tiers_sc.getTierById(1)); // Set the amount of eth that should be deposited into the contract. + let multicall = provider + .multicall() + .add_call(get0) + .add_call(get1) + ; + + let (res0, res1) = multicall + // .aggregate() + .aggregate() + .await + .unwrap() + // .map_err(GetScTiersError::Multicall)?; + ; + + // res.into_iter() + // .map(|t| t.map(Tier::from)) + // .collect::, _>>() + // .map_err(|_e| GetScTiersError::MulticallInner) + // let res_ = res.unwrap(); + // Ok(vec![Tier::from(res_.0.unwrap()), Tier::from(res_.1.unwrap())]) + Ok(vec![]) + */ + + let mut tiers = Vec::with_capacity(usize::from(tier_count)); + for i in 0..tier_count { + let tier = karma_tiers_sc + .getTierById(i) + .call() + .await + .map_err(GetScTiersError::Alloy)?; + tiers.push(Tier::from(tier)); + } Ok(tiers) } } +#[derive(Debug, thiserror::Error)] +pub enum GetScTiersError { + // #[error("Rpc error 1: {0}")] + // RpcError(#[from] RpcError>), + #[error("Rpc transport error 2: {0}")] + RpcTransportError(#[from] RpcError), + #[error(transparent)] + Alloy(#[from] alloy::contract::Error), + #[error(transparent)] + Multicall(MulticallError), + #[error("Error retrieving tier from multicall SC")] + MulticallInner, + #[error("Tier count too high (exceeds u16)")] + TierCount, +} + +/* #[derive(Debug, Clone, Default, Copy, From, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct TierIndex(u8); @@ -75,6 +250,7 @@ impl Add for TierIndex { Self(self.0 + rhs) } } +*/ #[derive(Debug, Clone, PartialEq)] pub struct Tier { @@ -82,9 +258,21 @@ pub struct Tier { pub max_karma: U256, pub name: String, pub tx_per_epoch: u32, - pub active: bool, + // pub active: bool, } +impl From for Tier { + fn from(value: KarmaTiers::Tier) -> Self { + Self { + min_karma: value.minKarma, + max_karma: value.maxKarma, + name: value.name, + tx_per_epoch: value.txPerEpoch, + } + } +} + +/* impl From for Tier { fn from(tier_added: KarmaTiersSC::TierAdded) -> Self { Self { @@ -92,11 +280,13 @@ impl From for Tier { max_karma: tier_added.maxKarma, name: tier_added.name, tx_per_epoch: tier_added.txPerEpoch, - active: true, + // active: true, } } } +*/ +/* impl From for Tier { fn from(tier_updated: KarmaTiersSC::TierUpdated) -> Self { Self { @@ -104,19 +294,109 @@ impl From for Tier { max_karma: tier_updated.maxKarma, name: tier_updated.name, tx_per_epoch: tier_updated.txPerEpoch, - active: true, + // active: true, } } } +*/ -impl From for Tier { - fn from(tiers_return: KarmaTiersSC::tiersReturn) -> Self { +impl From for Tier { + fn from(tiers_return: KarmaTiers::tiersReturn) -> Self { Self { min_karma: tiers_return._0, max_karma: tiers_return._1, name: tiers_return._2, tx_per_epoch: tiers_return._3, - active: tiers_return._4, + // active: tiers_return._4, } } } + +impl std::fmt::Debug for KarmaTiers::Tier { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + "KarmaTiers::Tier min_karma: {}, max_karma: {}, name: {}, tx_per_epoch: {}", + self.minKarma, self.maxKarma, self.name, self.txPerEpoch + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::KarmaTiers::KarmaTiersInstance; + + impl PartialEq for Tier { + fn eq(&self, other: &KarmaTiers::Tier) -> bool { + self.min_karma == other.minKarma + && self.max_karma == other.maxKarma + && self.name == other.name + && self.tx_per_epoch == other.txPerEpoch + } + } + + impl PartialEq for KarmaTiers::Tier { + fn eq(&self, other: &Self) -> bool { + self.minKarma == other.minKarma + && self.maxKarma == other.maxKarma + && self.name == other.name + && self.txPerEpoch == other.txPerEpoch + } + } + + #[tokio::test] + async fn test_get_tiers() { + // Spin up a forked Anvil node. + // Ensure `anvil` is available in $PATH. + let provider = ProviderBuilder::new().connect_anvil_with_wallet(); + + // Deploy the KarmaTiers contract. + let contract = KarmaTiers::deploy(&provider).await.unwrap(); + + // getTierCount call + let call_1 = contract.getTierCount(); + let result_1 = call_1.call().await.unwrap(); + assert_eq!(result_1, U256::from(0)); + + // updateTiers call + + let tiers = [ + KarmaTiers::Tier { + minKarma: U256::from(0), + maxKarma: U256::from(99), + name: "Basic".to_string(), + txPerEpoch: 10, + }, + KarmaTiers::Tier { + minKarma: U256::from(100), + maxKarma: U256::from(499), + name: "Advanced".to_string(), + txPerEpoch: 50, + }, + ]; + + let call_2 = contract.updateTiers(tiers.to_vec()); + let _tx_hash = call_2.send().await.unwrap().watch().await.unwrap(); + // let result_2 = call_2.call().await.unwrap(); + + let call_3 = contract.getTierCount(); + let result_3 = call_3.call().await.unwrap(); + assert_eq!(result_3, U256::from(tiers.len())); + + let call_4 = contract.getTierById(0); + let result_4 = call_4.call().await.unwrap(); + println!("result 4: {:?}", result_4); + assert_eq!(result_4, tiers[0]); + + let call_5 = contract.getTierById(1); + let result_5 = call_5.call().await.unwrap(); + println!("result 5: {:?}", result_5); + assert_eq!(result_5, tiers[1]); + + let res = KarmaTiersInstance::get_tiers_from_provider(&provider, contract.address()) + .await + .unwrap(); + assert_eq!(res, tiers.to_vec()); + } +} diff --git a/smart_contract/src/lib.rs b/smart_contract/src/lib.rs index 72a9a39..6044aef 100644 --- a/smart_contract/src/lib.rs +++ b/smart_contract/src/lib.rs @@ -6,7 +6,7 @@ mod rln_sc; pub use common::AlloyWsProvider; pub use karma_sc::{KarmaAmountExt, KarmaSC}; -pub use karma_tiers::{KarmaTiersSC, Tier, TierIndex}; +pub use karma_tiers::{GetScTiersError, KarmaTiers, Tier}; pub use rln_sc::{KarmaRLNSC, RLNRegister}; pub use mock::{MockKarmaRLNSc, MockKarmaSc, TIER_LIMITS}; diff --git a/smart_contract/src/mock.rs b/smart_contract/src/mock.rs index 764968c..418ac79 100644 --- a/smart_contract/src/mock.rs +++ b/smart_contract/src/mock.rs @@ -1,9 +1,8 @@ -use crate::karma_tiers::{Tier, TierIndex}; +use crate::karma_tiers::Tier; use crate::{KarmaAmountExt, RLNRegister}; use alloy::primitives::{Address, U256}; use async_trait::async_trait; use log::debug; -use std::collections::BTreeMap; use std::sync::LazyLock; pub struct MockKarmaSc {} @@ -23,66 +22,50 @@ pub struct MockKarmaRLNSc {} impl RLNRegister for MockKarmaRLNSc { type Error = alloy::contract::Error; - async fn register(&self, identity_commitment: U256) -> Result<(), Self::Error> { + async fn register_user( + &self, + address: &Address, + identity_commitment: U256, + ) -> Result<(), Self::Error> { debug!( - "Register user with identity_commitment: {:?}", - identity_commitment + "Register user ({}) with identity_commitment: {:?}", + address, identity_commitment ); Ok(()) } } -pub static TIER_LIMITS: LazyLock> = LazyLock::new(|| { - BTreeMap::from([ - ( - TierIndex::from(0), - Tier { - min_karma: U256::from(10), - max_karma: U256::from(49), - name: "Basic".to_string(), - tx_per_epoch: 6, - active: true, - }, - ), - ( - TierIndex::from(1), - Tier { - min_karma: U256::from(50), - max_karma: U256::from(99), - name: "Active".to_string(), - tx_per_epoch: 120, - active: true, - }, - ), - ( - TierIndex::from(2), - Tier { - min_karma: U256::from(100), - max_karma: U256::from(499), - name: "Regular".to_string(), - tx_per_epoch: 720, - active: true, - }, - ), - ( - TierIndex::from(3), - Tier { - min_karma: U256::from(500), - max_karma: U256::from(999), - name: "Power User".to_string(), - tx_per_epoch: 86400, - active: true, - }, - ), - ( - TierIndex::from(4), - Tier { - min_karma: U256::from(1000), - max_karma: U256::from(4999), - name: "S-Tier".to_string(), - tx_per_epoch: 432000, - active: true, - }, - ), - ]) +pub static TIER_LIMITS: LazyLock> = LazyLock::new(|| { + vec![ + Tier { + min_karma: U256::from(10), + max_karma: U256::from(49), + name: "Basic".to_string(), + tx_per_epoch: 6, + }, + Tier { + min_karma: U256::from(50), + max_karma: U256::from(99), + name: "Active".to_string(), + tx_per_epoch: 120, + }, + Tier { + min_karma: U256::from(100), + max_karma: U256::from(499), + name: "Regular".to_string(), + tx_per_epoch: 720, + }, + Tier { + min_karma: U256::from(500), + max_karma: U256::from(999), + name: "Power User".to_string(), + tx_per_epoch: 86400, + }, + Tier { + min_karma: U256::from(1000), + max_karma: U256::from(4999), + name: "S-Tier".to_string(), + tx_per_epoch: 432000, + }, + ] }); diff --git a/smart_contract/src/rln_sc.rs b/smart_contract/src/rln_sc.rs index eb358d9..ee94119 100644 --- a/smart_contract/src/rln_sc.rs +++ b/smart_contract/src/rln_sc.rs @@ -1,5 +1,6 @@ // third-party use alloy::primitives::U256; +use alloy::providers::Provider; use alloy::{ primitives::Address, providers::{ProviderBuilder, WsConnect}, @@ -15,14 +16,30 @@ use crate::AlloyWsProvider; pub trait RLNRegister { type Error; - async fn register(&self, identity_commitment: U256) -> Result<(), Self::Error>; + async fn register_user( + &self, + address: &Address, + identity_commitment: U256, + ) -> Result<(), Self::Error>; } sol! { - // https://github.com/vacp2p/staking-reward-streamer/pull/220 - #[sol(rpc)] + + // src: staking-reward-streamer/src/rln/RLN.sol + // Compile bytecode (in staking-reward-streamer folder): + // docker run -v ./:/sources ethereum/solc:0.8.26 --bin --via-ir --optimize --optimize-runs 1 --overwrite @openzeppelin/contracts=/sources/lib/openzeppelin-contracts/contracts @openzeppelin/contracts-upgradeable=/sources/lib/openzeppelin-contracts-upgradeable/contracts /sources/src/rln/RLN.sol + + #[sol(rpc, bytecode="60a0806040523460d657306080525f549060ff8260081c166084575060ff80821610604b575b6040516117b090816100db82396080518181816107040152818161084a0152610c700152f35b60ff90811916175f557f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498602060405160ff8152a15f6025565b62461bcd60e51b815260206004820152602760248201527f496e697469616c697a61626c653a20636f6e747261637420697320696e697469604482015266616c697a696e6760c81b6064820152608490fd5b5f80fdfe6080806040526004361015610012575f80fd5b5f3560e01c90816301ffc9a71461104857508063248a9ca3146110225780632f2ff15d14610f8957806336568abe14610ef75780633659cfe614610c5257806345bc4d1014610ae55780634f1ef286146107d65780635095af64146107af57806352d1902d146106f2578063530b97a4146104085780635daf08ca146103ce57806374a8569b146103a657806374f533171461037f57806391d14854146103365780639cf502fe14610319578063a217fddf146102ff578063d0383d68146102e2578063d547741f146102aa5763dbbdf083146100ed575f80fd5b346102a65760403660031901126102a65760043561010961109b565b335f9081525f805160206116db833981519152602052604090205490919060ff16156101f25760fc549060fb548210156101e3575f81815260fd60205260409020546001600160a01b03166101d45760407f5a92c2530f207992057b9c3e544108ffce3beda4a63719f316967c49bf6159d29160019482519061018b826110c7565b868060a01b031681528560208201868152835f5260fd602052845f2092828060a01b03905116828060a01b0319845416178355519101558151908152836020820152a10160fc55005b63c9ac99a160e01b5f5260045ffd5b63542b6c8360e01b5f5260045ffd5b6102a2602061028a6011610205336113e6565b60378461021e5f8051602061163b8339815191526114e0565b60405196879476020b1b1b2b9b9a1b7b73a3937b61d1030b1b1b7bab73a1604d1b828701528051918291018587015e8401907001034b99036b4b9b9b4b733903937b6329607d1b84830152805192839101604883015e01015f838201520301601f1981018352826110f6565b60405162461bcd60e51b815291829160048301611307565b0390fd5b5f80fd5b346102a65760403660031901126102a6576102e06004356102c961109b565b906102db6102d682611134565b6111e2565b611226565b005b346102a6575f3660031901126102a657602060fb54604051908152f35b346102a6575f3660031901126102a65760206040515f8152f35b346102a6575f3660031901126102a657602060fc54604051908152f35b346102a65760403660031901126102a65761034f61109b565b6004355f5260c960205260405f209060018060a01b03165f52602052602060ff60405f2054166040519015158152f35b346102a6575f3660031901126102a65760206040515f8051602061163b8339815191528152f35b346102a6575f3660031901126102a65760fe546040516001600160a01b039091168152602090f35b346102a65760203660031901126102a6576004355f5260fd6020526040805f206001808060a01b0382541691015482519182526020820152f35b346102a65760a03660031901126102a6576104216110b1565b61042961109b565b6044356001600160a01b038116908190036102a6576084356001600160a01b03811692908390036102a6575f549360ff8560081c1615948580966106e5575b80156106ce575b156106725760ff1981166001175f5585610661575b5061049e60ff5f5460081c16610499816112a7565b6112a7565b6001600160a01b0381165f9081525f805160206116fb833981519152602052604090205460ff161561061a575b506001600160a01b0381165f9081525f8051602061165b833981519152602052604090205460ff16156105c4575b505f8181525f805160206116db833981519152602052604090205460ff1615610577575b5060016064351b60fb5560fe80546001600160a01b03191691909117905561054157005b61ff00195f54165f557f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498602060405160018152a1005b5f8181525f805160206116db83398151915260205260408120805460ff191660011790553391905f8051602061163b833981519152905f805160206115bb8339815191529080a48261051d565b6001600160a01b03165f8181525f8051602061165b83398151915260205260408120805460ff191660011790553391905f8051602061169b833981519152905f805160206115bb8339815191529080a4836104f9565b6001600160a01b03165f8181525f805160206116fb83398151915260205260408120805460ff191660011790553391905f805160206115bb8339815191528180a4846104cb565b61ffff1916610101175f5585610484565b60405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b6064820152608490fd5b50303b15801561046f5750600160ff82161461046f565b50600160ff821610610468565b346102a6575f3660031901126102a6577f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031630036107495760206040515f8051602061167b8339815191528152f35b60405162461bcd60e51b815260206004820152603860248201527f555550535570677261646561626c653a206d757374206e6f742062652063616c6044820152771b1959081d1a1c9bdd59da0819195b1959d85d1958d85b1b60421b6064820152608490fd5b346102a6575f3660031901126102a65760206040515f8051602061169b8339815191528152f35b60403660031901126102a6576107ea6110b1565b6024356001600160401b0381116102a657366023820112156102a657806004013561081481611119565b9061082260405192836110f6565b808252602082019236602483830101116102a657815f9260246020930186378301015261089c7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031661087e30821415611146565b5f8051602061167b833981519152546001600160a01b031614611194565b335f9081525f805160206116fb833981519152602052604090205460ff1615610ad6575f805160206115fb8339815191525460ff16156108e15750506102e090611331565b6040516352d1902d60e01b81526001600160a01b03841690602081600481855afa5f9181610aa2575b506109585760405162461bcd60e51b815260206004820152602e60248201525f8051602061175b83398151915260448201526d6f6e206973206e6f74205555505360901b6064820152608490fd5b5f8051602061167b83398151915203610a5e5761097484611331565b604051905f805160206116bb8339815191525f80a2815115801590610a56575b61099a57005b833b15610a1857506102e0925f92839251915af43d15610a11573d6109be81611119565b906109cc60405192836110f6565b81523d5f602083013e5b604051916109e56060846110f6565b602783525f8051602061173b8339815191526020840152660819985a5b195960ca1b6040840152611582565b60606109d6565b62461bcd60e51b815260206004820152602660248201525f805160206115db8339815191526044820152651b9d1c9858dd60d21b6064820152608490fd5b506001610994565b60405162461bcd60e51b815260206004820152602960248201525f8051602061171b8339815191526044820152681a58589b195555525160ba1b6064820152608490fd5b9091506020813d602011610ace575b81610abe602093836110f6565b810103126102a65751908661090a565b3d9150610ab1565b6335a1657b60e11b5f5260045ffd5b346102a65760203660031901126102a657335f9081525f8051602061165b83398151915260205260409020546004359060ff1615610c2657805f5260fd60205260405f20604051610b35816110c7565b81546001600160a01b0316808252600190920154602082019081529115610c175760fe54905160405163c96be4cb60e01b81526001600160a01b03918216600482015291602091839160249183915f91165af18015610c0c57610bd9575b7f707cd9719d0c14265b9e456f7add99095401f907e570e5cdd65a92920947c450604083855f5260fd6020525f60018382208281550155518151908152336020820152a1005b906020823d602011610c04575b81610bf3602093836110f6565b810103126102a65790506040610b93565b3d9150610be6565b6040513d5f823e3d90fd5b637b6d05f560e01b5f5260045ffd5b6102a2602061028a6011610c39336113e6565b60378461021e5f8051602061169b8339815191526114e0565b346102a65760203660031901126102a657610c6b6110b1565b610ca47f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031661087e30821415611146565b335f9081525f805160206116fb833981519152602052604090205460ff1615610ad657602090604051610cd783826110f6565b5f815282810190601f1984013683375f805160206115fb8339815191525460ff1615610d095750506102e09150611331565b6040516352d1902d60e01b81526001600160a01b038416908581600481855afa5f9181610ec8575b50610d7f5760405162461bcd60e51b815260048101879052602e60248201525f8051602061175b83398151915260448201526d6f6e206973206e6f74205555505360901b6064820152608490fd5b5f8051602061167b83398151915203610e8457610d9b84611331565b604051905f805160206116bb8339815191525f80a2815115801590610e7d575b610dc157005b833b15610e3f57506102e093925f92839251915af43d15610e37573d90610de782611119565b91610df560405193846110f6565b82523d5f8484013e5b5f8051602061173b83398151915260405193610e1b6060866110f6565b60278552840152660819985a5b195960ca1b6040840152611582565b606090610dfe565b62461bcd60e51b815260048101859052602660248201525f805160206115db8339815191526044820152651b9d1c9858dd60d21b6064820152608490fd5b505f610dbb565b60405162461bcd60e51b815260048101869052602960248201525f8051602061171b8339815191526044820152681a58589b195555525160ba1b6064820152608490fd5b9091508681813d8311610ef0575b610ee081836110f6565b810103126102a657519087610d31565b503d610ed6565b346102a65760403660031901126102a657610f1061109b565b336001600160a01b03821603610f2c576102e090600435611226565b60405162461bcd60e51b815260206004820152602f60248201527f416363657373436f6e74726f6c3a2063616e206f6e6c792072656e6f756e636560448201526e103937b632b9903337b91039b2b63360891b6064820152608490fd5b346102a65760403660031901126102a657600435610fa561109b565b90610fb26102d682611134565b805f5260c960205260405f2060018060a01b0383165f5260205260ff60405f20541615610fdb57005b5f81815260c9602090815260408083206001600160a01b0395909516808452949091528120805460ff19166001179055339291905f805160206115bb8339815191529080a4005b346102a65760203660031901126102a6576020611040600435611134565b604051908152f35b346102a65760203660031901126102a6576004359063ffffffff60e01b82168092036102a657602091637965db0b60e01b811490811561108a575b5015158152f35b6301ffc9a760e01b14905083611083565b602435906001600160a01b03821682036102a657565b600435906001600160a01b03821682036102a657565b604081019081106001600160401b038211176110e257604052565b634e487b7160e01b5f52604160045260245ffd5b601f909101601f19168101906001600160401b038211908210176110e257604052565b6001600160401b0381116110e257601f01601f191660200190565b5f5260c9602052600160405f20015490565b1561114d57565b60405162461bcd60e51b815260206004820152602c60248201525f8051602061161b83398151915260448201526b19195b1959d85d1958d85b1b60a21b6064820152608490fd5b1561119b57565b60405162461bcd60e51b815260206004820152602c60248201525f8051602061161b83398151915260448201526b6163746976652070726f787960a01b6064820152608490fd5b5f81815260c96020908152604080832033845290915290205460ff16156112065750565b602061028a60116102a29360378461021e611220336113e6565b936114e0565b805f5260c960205260405f2060018060a01b0383165f5260205260ff60405f205416611250575050565b5f81815260c9602090815260408083206001600160a01b0395909516808452949091528120805460ff19169055339291907ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b9080a4565b156112ae57565b60405162461bcd60e51b815260206004820152602b60248201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960448201526a6e697469616c697a696e6760a81b6064820152608490fd5b602060409281835280519182918282860152018484015e5f828201840152601f01601f1916010190565b803b15611366575f8051602061167b83398151915280546001600160a01b0319166001600160a01b0392909216919091179055565b60405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b6064820152608490fd5b9081518110156113d2570160200190565b634e487b7160e01b5f52603260045260245ffd5b6113f0602a611119565b906113fe60405192836110f6565b602a825261140c602a611119565b6020830190601f19013682378251156113d257603090538151600110156113d2576078602183015360295b6001811161148b57506114475790565b606460405162461bcd60e51b815260206004820152602060248201527f537472696e67733a20686578206c656e67746820696e73756666696369656e746044820152fd5b90600f811660108110156113d2576f181899199a1a9b1b9c1cb0b131b232b360811b901a6114b983856113c1565b5360041c9080156114cc575f1901611437565b634e487b7160e01b5f52601160045260245ffd5b6114ea6042611119565b906114f860405192836110f6565b604282526115066042611119565b6020830190601f19013682378251156113d257603090538151600110156113d2576078602183015360415b6001811161154157506114475790565b90600f811660108110156113d2576f181899199a1a9b1b9c1cb0b131b232b360811b901a61156f83856113c1565b5360041c9080156114cc575f1901611531565b9091901561158e575090565b81511561159e5750805190602001fd5b60405162461bcd60e51b81529081906102a2906004830161130756fe2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d416464726573733a2064656c65676174652063616c6c20746f206e6f6e2d636f4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd914346756e6374696f6e206d7573742062652063616c6c6564207468726f75676820d1f21ec03a6eb050fba156f5316dad461735df521fb446dd42c5a4728e9c70fe897855dc996bc4db78f148038107b75f1f55bc585ff7cb118de0535894d61eed360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc12b42e8a160f6064dc959c6f251e3af0750ad213dbecf573b4710d67d6c28e39bc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b2c45c034475732bf079b2b599013f165badefbf9cb6b1b2cd9e47295795a726f81fe90a866a48a634a12852c1be675b683a22307409932a7443b8029347be75645524331393637557067726164653a20756e737570706f727465642070726f78416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c45524331393637557067726164653a206e657720696d706c656d656e74617469a2646970667358221220aabe61fff478beb4c0bdff38e04d638b5a6fa6c0e7c452ab539de7acb4afc78f64736f6c634300081a0033")] contract KarmaRLNSC { - function register(uint256 identityCommitment) external onlyRole(REGISTER_ROLE); + function register(uint256 identityCommitment, address user) external onlyRole(REGISTER_ROLE); + + // test + function initialize(address _owner, address _slasher, address _register, uint256 depth, address _token); + struct User { + address userAddress; + uint256 index; + } + mapping(uint256 commitment => User user) public members; } } @@ -35,11 +52,99 @@ impl KarmaRLNSC::KarmaRLNSCInstance { } #[async_trait] -impl RLNRegister for KarmaRLNSC::KarmaRLNSCInstance { +impl RLNRegister for KarmaRLNSC::KarmaRLNSCInstance { type Error = alloy::contract::Error; - async fn register(&self, identity_commitment: U256) -> Result<(), Self::Error> { - self.register(identity_commitment).call().await?; + async fn register_user( + &self, + address: &Address, + identity_commitment: U256, + ) -> Result<(), Self::Error> { + self.register(identity_commitment, *address) + .send() + .await? + .watch() + .await?; Ok(()) } } + +#[cfg(test)] +mod tests { + use super::*; + // third-party + use crate::KarmaSC; + use alloy::primitives::address; + use alloy::sol_types::SolCall; + + #[tokio::test] + async fn test_register() { + let provider = ProviderBuilder::new().connect_anvil_with_wallet(); + + // Deploy Karma SC + + // let contract_distributor_1 = crate::karma_sc::tests::KarmaDistributorMock::deploy(&provider).await.unwrap(); + // let contract_distributor_2 = crate::karma_sc::tests::KarmaDistributorMock::deploy(&provider).await.unwrap(); + + let addr_alice = address!("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"); + let addr_bob = address!("0x70997970C51812dc3A010C7d01b50e0d17dc79C8"); + let addr_mickey = address!("0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC"); + + // Deploy the KarmaTiers contract. + let contract_0 = KarmaSC::deploy(&provider).await.unwrap(); + let init_data = KarmaSC::initializeCall { _owner: addr_alice }.abi_encode(); + let contract_proxy = crate::karma_sc::tests::ERC1967Proxy::deploy( + &provider, + *contract_0.address(), + init_data.into(), + ) + .await + .unwrap(); + println!("contract_proxy: {:?}", contract_proxy.address()); + let contract = KarmaSC::new(*contract_proxy.address(), &provider); + println!("contract KarmaSC: {:?}", contract_proxy.address()); + + // Deploy RLN SC + + let contract_rln_0 = KarmaRLNSC::deploy(&provider).await.unwrap(); + let init_data_1 = KarmaRLNSC::initializeCall { + _owner: addr_alice, + _slasher: addr_alice, + _register: addr_alice, + depth: U256::from(2), + _token: *contract.address(), + } + .abi_encode(); + let contract_proxy_rln = crate::karma_sc::tests::ERC1967Proxy::deploy( + &provider, + *contract_rln_0.address(), + init_data_1.into(), + ) + .await + .unwrap(); + println!("contract_proxy_rln: {:?}", contract_proxy_rln.address()); + let contract_rln = KarmaRLNSC::new(*contract_proxy_rln.address(), &provider); + + let id_commitment = U256::from(1); + let call_1 = contract_rln.register(id_commitment, addr_bob); + let tx_hash_1 = call_1.send().await.unwrap().watch().await.unwrap(); + println!("tx_hash_1: {:?}", tx_hash_1); + + let id_commitment_2 = U256::from(2); + // let call_2 = contract_rln.register(id_commitment_2, addr_mickey); + // let tx_hash_2 = call_2.send().await.unwrap().watch().await.unwrap(); + // println!("tx_hash_2: {:?}", tx_hash_2); + + let _ = contract_rln + .register_user(&addr_mickey, id_commitment_2) + .await + .unwrap(); + + let result = contract_rln.members(id_commitment).call().await.unwrap(); + assert_eq!(result._0, addr_bob); + assert_eq!(result._1, U256::from(0)); + let result = contract_rln.members(id_commitment_2).call().await.unwrap(); + assert_eq!(result._0, addr_mickey); + assert_eq!(result._1, U256::from(1)); + } +}