Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: Is Eligible Changes #20

Merged
merged 6 commits into from
Sep 4, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ test-utils = []

[dependencies]
base64 = { version = "0.22.1", optional = true }
cosmwasm-schema = { version = "1.5.5", optional = true }
cosmwasm-std = { version = "1.5.5", optional = true }
cosmwasm-schema = { version = "1.5.7", optional = true }
cosmwasm-std = { version = "1.5.7", optional = true }
cw-storage-plus = "1.2.0"
hex = "0.4.3"
lazy_static = "1.5.0"
Expand All @@ -24,3 +24,4 @@ vrf-rs = { version = "0.0.0" }
[dev-dependencies]
schemars = { version = "0.8", features = ["semver"] }
serde_json = { version = "1.0" }
k256 = { version = "0.13" }
120 changes: 120 additions & 0 deletions src/msgs/staking/query/is_executor_eligible.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
use core::str;

use super::*;

#[cfg_attr(feature = "cosmwasm", cw_serde)]
#[cfg_attr(not(feature = "cosmwasm"), derive(Serialize, Debug, PartialEq))]
#[cfg_attr(not(feature = "cosmwasm"), serde(rename_all = "snake_case"))]
pub struct Query {
pub data: Bytes,
}

impl Query {
fn generate_hash(dr_id: &str, chain_id: &str, contract_addr: &str) -> Hash {
hash([
"is_executor_eligible".as_bytes(),
dr_id.as_bytes(),
chain_id.as_bytes(),
contract_addr.as_bytes(),
])
}

#[cfg(not(feature = "cosmwasm"))]
fn decode(&self) -> Result<Vec<u8>> {
use base64::{prelude::BASE64_STANDARD, Engine};

let decoded = BASE64_STANDARD.decode(&self.data)?;
Ok(decoded)
}

#[cfg(feature = "cosmwasm")]
fn decode(&self) -> Result<Vec<u8>> {
Ok(self.data.to_vec())
}

fn dr_id_hex(&self) -> Result<String> {
let decoded = self.decode()?;
Ok(str::from_utf8(&decoded[67..131]).unwrap().to_owned())
}

pub fn parts(&self) -> Result<([u8; 33], Hash, Vec<u8>)> {
let decoded = self.decode()?;
let public_key = hex::decode(&decoded[..66])?.try_into().expect("Invalid public key");
let dr_id = hex::decode(&decoded[67..131])?.try_into().expect("Invalid dr_id");
let proof = hex::decode(&decoded[132..])?;

Ok((public_key, dr_id, proof))
}
}

impl VerifySelf for Query {
type Extra = ();

fn proof(&self) -> Result<Vec<u8>> {
let decoded = self.decode()?;
Ok(hex::decode(&decoded[132..])?)
}

fn msg_hash(&self, chain_id: &str, contract_addr: &str, _: Self::Extra) -> Result<Hash> {
Ok(Self::generate_hash(&self.dr_id_hex()?, chain_id, contract_addr))
}
}

pub struct QueryFactory {
pub(crate) dr_id: String,
pub(crate) public_key: String,
pub(crate) hash: Hash,
}

impl QueryFactory {
pub fn get_hash(&self) -> &[u8] {
&self.hash
}

#[cfg(not(feature = "cosmwasm"))]
pub(crate) fn encode_data(data: &str) -> Bytes {
use base64::{prelude::BASE64_STANDARD, Engine};

BASE64_STANDARD.encode(data)
}

#[cfg(feature = "cosmwasm")]
pub(crate) fn encode_data(data: &str) -> Bytes {
use cosmwasm_std::Binary;
Binary(data.as_bytes().to_vec())
}

pub fn create_message(self, proof: Vec<u8>) -> (crate::msgs::QueryMsg, Bytes) {
let data = format!("{}:{}:{}", self.public_key, self.dr_id, proof.to_hex());
let base64_data = Self::encode_data(&data);

(
Query {
data: base64_data.clone(),
}
.into(),
base64_data,
)
}
}

impl Query {
pub fn factory(public_key: String, dr_id: String, chain_id: &str, contract_addr: &str) -> QueryFactory {
let hash = Self::generate_hash(&dr_id, chain_id, contract_addr);
QueryFactory {
dr_id,
public_key,
hash,
}
}

pub fn verify(&self, public_key: &[u8], chain_id: &str, contract_addr: &str) -> Result<()> {
self.verify_inner(public_key, chain_id, contract_addr, ())
}
}

impl From<Query> for crate::msgs::QueryMsg {
fn from(value: Query) -> Self {
super::QueryMsg::IsExecutorEligible(value).into()
}
}
6 changes: 5 additions & 1 deletion src/msgs/staking/query.rs → src/msgs/staking/query/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use super::*;

pub mod is_executor_eligible;

#[cfg_attr(feature = "cosmwasm", cw_serde)]
#[cfg_attr(feature = "cosmwasm", derive(QueryResponses))]
#[cfg_attr(not(feature = "cosmwasm"), derive(Serialize, Debug, PartialEq))]
Expand All @@ -12,7 +14,9 @@ pub enum QueryMsg {
#[cfg_attr(feature = "cosmwasm", returns(StakerAndSeq))]
GetStakerAndSeq { public_key: String },
#[cfg_attr(feature = "cosmwasm", returns(bool))]
IsExecutorEligible { proof: String, dr_id: String },
IsExecutorCommitteeEligible { public_key: String },
#[cfg_attr(feature = "cosmwasm", returns(bool))]
IsExecutorEligible(is_executor_eligible::Query),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To follow the rest of queries, why not like this?

IsExecutorEligible{ data: is_executor_eligible::Query},

Copy link
Contributor Author

@gluax gluax Sep 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It could be done that way, but our standard practice for messages is that if something is complicated to move to its type, we've been using tuples for it.

If we did this, it would change the json as well from:

"is_executor_eligible": {
  "data": "data_str",
}

to

"is_executor_eligible": {
  "data": {
    "data": "data_str",
  }
}

#[cfg_attr(feature = "cosmwasm", returns(StakingConfig))]
GetStakingConfig {},
}
Expand Down
85 changes: 76 additions & 9 deletions src/msgs/staking/query_tests.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
use k256::{
ecdsa::{SigningKey, VerifyingKey},
elliptic_curve::rand_core::OsRng,
};
use serde_json::json;

use super::{query::QueryMsg as StakingQueryMsg, QueryMsg};
use crate::msgs::*;
use super::{
query::{
is_executor_eligible::{self, Query, QueryFactory},
QueryMsg as StakingQueryMsg,
},
QueryMsg,
};
use crate::{crypto::VRF, msgs::*};

#[test]
fn json_get_staker() {
Expand Down Expand Up @@ -55,16 +65,14 @@ fn json_get_staker_and_seq() {
}

#[test]
fn json_is_executor_eligible() {
fn json_is_executor_committee_eligible() {
let expected_json = json!({
"is_executor_eligible": {
"proof": "public_key",
"dr_id": "dr_id"
"is_executor_committee_eligible": {
"public_key": "public_key",
}
});
let msg: QueryMsg = StakingQueryMsg::IsExecutorEligible {
proof: "public_key".to_string(),
dr_id: "dr_id".to_string(),
let msg: QueryMsg = StakingQueryMsg::IsExecutorCommitteeEligible {
public_key: "public_key".to_string(),
}
.into();
#[cfg(not(feature = "cosmwasm"))]
Expand All @@ -73,6 +81,25 @@ fn json_is_executor_eligible() {
assert_json_deser(msg, expected_json);
}

#[test]
fn json_is_executor_eligible() {
#[cfg(not(feature = "cosmwasm"))]
let data = "data".to_string();
#[cfg(feature = "cosmwasm")]
let data: Bytes = "data".as_bytes().into();

let expected_json = json!({
"is_executor_eligible": {
"data": data,
}
});
let msg: QueryMsg = is_executor_eligible::Query { data }.into();
#[cfg(not(feature = "cosmwasm"))]
assert_json_ser(msg, expected_json);
#[cfg(feature = "cosmwasm")]
assert_json_deser(msg, expected_json);
}

#[test]
fn json_get_staking_config() {
let expected_json = json!({
Expand All @@ -84,3 +111,43 @@ fn json_get_staking_config() {
#[cfg(feature = "cosmwasm")]
assert_json_deser(msg, expected_json);
}

fn new_public_key() -> (SigningKey, [u8; 33]) {
let signing_key = SigningKey::random(&mut OsRng);
let verifying_key = VerifyingKey::from(&signing_key);
let public_key = verifying_key.to_encoded_point(true).as_bytes().try_into().unwrap();

(signing_key, public_key)
}

fn prove(signing_key: &[u8], hash: &[u8]) -> Vec<u8> {
VRF.prove(signing_key, hash).unwrap()
}

impl QueryFactory {
fn create_query(self, proof: Vec<u8>) -> Query {
let data = format!("{}:{}:{}", self.public_key, self.dr_id, proof.to_hex());

Query {
data: Self::encode_data(&data),
}
}
}

#[test]
fn is_executor_eligible_decode_correctly() {
let (sk, pk) = new_public_key();
let pk_hex = hex::encode(pk);

let dr_id = "dr_id".hash();
let dr_id_hex = dr_id.to_hex();

let factory = is_executor_eligible::Query::factory(pk_hex, dr_id_hex, "foo", "bar");
let proof = prove(sk.to_bytes().as_slice(), factory.get_hash());
let query = factory.create_query(proof.clone());

let (decoded_pk, decoded_dr_id_hash, decoded_proof) = query.parts().unwrap();
assert_eq!(pk, decoded_pk);
assert_eq!(dr_id, decoded_dr_id_hash);
assert_eq!(proof, decoded_proof);
}