From 959392e98049bb83d9c58afce3047b187b286600 Mon Sep 17 00:00:00 2001 From: Anonfedora Date: Fri, 27 Mar 2026 00:56:55 +0100 Subject: [PATCH] feat: reputation contract --- Cargo.lock | 101 ++++++++++++++++++++++++++++++++ contracts/reputation/src/lib.rs | 59 +++++++++++++++++-- 2 files changed, 156 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b183c96f..835b93ab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -221,11 +221,14 @@ dependencies = [ "anyhow", "axum", "axum-test", + "base64 0.22.1", "chrono", "dotenvy", + "ed25519-dalek", "reqwest", "serde", "serde_json", + "sha2", "sqlx", "thiserror 1.0.69", "tokio", @@ -234,6 +237,7 @@ dependencies = [ "tracing", "tracing-subscriber", "uuid", + "wiremock", ] [[package]] @@ -591,6 +595,24 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "deadpool" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0be2b1d1d6ec8d846f05e137292d0b89133caf95ef33695424c09568bdd39b1b" +dependencies = [ + "deadpool-runtime", + "lazy_static", + "num_cpus", + "tokio", +] + +[[package]] +name = "deadpool-runtime" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "092966b41edc516079bdf31ec78a2e0588d1d0c08f78b91d8307215928642b2b" + [[package]] name = "der" version = "0.7.10" @@ -871,6 +893,21 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "futures" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + [[package]] name = "futures-channel" version = "0.3.32" @@ -915,6 +952,17 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" +[[package]] +name = "futures-macro" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "futures-sink" version = "0.3.32" @@ -933,8 +981,10 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ + "futures-channel", "futures-core", "futures-io", + "futures-macro", "futures-sink", "futures-task", "memchr", @@ -1082,6 +1132,12 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + [[package]] name = "hex" version = "0.4.3" @@ -1755,6 +1811,16 @@ dependencies = [ "libm", ] +[[package]] +name = "num_cpus" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" +dependencies = [ + "hermit-abi", + "libc", +] + [[package]] name = "object" version = "0.37.3" @@ -2066,6 +2132,18 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + [[package]] name = "regex-automata" version = "0.4.14" @@ -3938,6 +4016,29 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "wiremock" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08db1edfb05d9b3c1542e521aea074442088292f00b5f28e435c714a98f85031" +dependencies = [ + "assert-json-diff", + "base64 0.22.1", + "deadpool", + "futures", + "http 1.4.0", + "http-body-util", + "hyper", + "hyper-util", + "log", + "once_cell", + "regex", + "serde", + "serde_json", + "tokio", + "url", +] + [[package]] name = "wit-bindgen" version = "0.51.0" diff --git a/contracts/reputation/src/lib.rs b/contracts/reputation/src/lib.rs index b94e05ba..ece48778 100644 --- a/contracts/reputation/src/lib.rs +++ b/contracts/reputation/src/lib.rs @@ -24,14 +24,65 @@ pub struct ReputationContract; #[contractimpl] impl ReputationContract { - pub fn initialize(_env: Env, _admin: Address) { todo!() } + pub fn initialize(env: Env, admin: Address) { + if env.storage().instance().has(&DataKey::Admin) { + panic!("already initialized"); + } + env.storage().instance().set(&DataKey::Admin, &admin); + } /// Update reputation after a completed job. `delta` in basis points. /// Score is clamped to [0, 10000]. - pub fn update_score(_env: Env, _address: Address, _role: Role, _delta: i32) { todo!() } + pub fn update_score(env: Env, address: Address, role: Role, delta: i32) { + let admin: Address = env + .storage() + .instance() + .get(&DataKey::Admin) + .expect("not initialized"); + admin.require_auth(); + + let mut reputation = Self::get_score(env.clone(), address, role.clone()); + reputation.score = Self::clamp_score(reputation.score.saturating_add(delta)); + reputation.total_jobs = reputation.total_jobs.saturating_add(1); + + env.storage() + .persistent() + .set(&DataKey::Score(reputation.address.clone(), role), &reputation); + } /// Slash address for fraud / abandonment — reduces score by 20%. - pub fn slash(_env: Env, _address: Address, _role: Role, _reason: Symbol) { todo!() } + pub fn slash(env: Env, address: Address, role: Role, reason: Symbol) { + let _ = reason; + let admin: Address = env + .storage() + .instance() + .get(&DataKey::Admin) + .expect("not initialized"); + admin.require_auth(); + + let mut reputation = Self::get_score(env.clone(), address, role.clone()); + reputation.score = Self::clamp_score(reputation.score.saturating_sub(2000)); + + env.storage() + .persistent() + .set(&DataKey::Score(reputation.address.clone(), role), &reputation); + } - pub fn get_score(_env: Env, _address: Address, _role: Role) -> ReputationScore { todo!() } + pub fn get_score(env: Env, address: Address, role: Role) -> ReputationScore { + env.storage() + .persistent() + .get(&DataKey::Score(address.clone(), role.clone())) + .unwrap_or(ReputationScore { + address, + role, + score: 5000, + total_jobs: 0, + }) + } +} + +impl ReputationContract { + fn clamp_score(value: i32) -> i32 { + value.clamp(0, 10_000) + } }