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

Migrate to quicknet #301

Draft
wants to merge 10 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ resolver = "2"
# nois = { git = "https://github.com/noislabs/nois", branch = "add-published-time" }
nois = "0.7.0"
cw2 = "1.1.1"
hex-literal = "0.4.1"

[profile.release.package.nois-protocol]
codegen-units = 1
Expand Down
3 changes: 3 additions & 0 deletions contracts/nois-drand/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,6 @@ sha2 = "0.10.6"
# Drand dependencies
hex = { version = "0.4" }
drand-verify = "0.6.2"

[dev-dependencies]
hex-literal = { workspace = true }
148 changes: 113 additions & 35 deletions contracts/nois-drand/src/contract.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
use std::str::FromStr;

use cosmwasm_std::{
ensure_eq, to_json_binary, Addr, Attribute, BankMsg, Coin, CosmosMsg, Deps, DepsMut, Empty,
Env, HexBinary, MessageInfo, Order, QueryResponse, Response, StdError, StdResult, Uint128,
WasmMsg,
};
use cw2::set_contract_version;
use cw_storage_plus::Bound;
use drand_common::DRAND_MAINNET2_PUBKEY;
use drand_verify::{derive_randomness, G2PubkeyFastnet, Pubkey};
use drand_common::DrandNetwork;
use drand_verify::{derive_randomness, G2PubkeyFastnet, G2PubkeyRfc, Pubkey};

use crate::attributes::{
ATTR_BOT, ATTR_RANDOMNESS, ATTR_REWARD_PAYOUT, ATTR_REWARD_POINTS, ATTR_ROUND,
Expand All @@ -20,8 +22,8 @@ use crate::msg::{
};
use crate::state::{
Bot, Config, QueriedBeacon, QueriedBot, StoredSubmission, VerifiedBeacon, ALLOWLIST, BEACONS,
BOTS, CONFIG, INCENTIVIZED_BY_GATEWAY, INCENTIVIZED_BY_GATEWAY_MARKER, SUBMISSIONS,
SUBMISSIONS_COUNT,
BOTS, CONFIG, FASTNET_SUBMISSIONS_COUNT, INCENTIVIZED_BY_GATEWAY,
INCENTIVIZED_BY_GATEWAY_MARKER, QUICKNET_SUBMISSIONS_COUNT, SUBMISSIONS,
};

/// Constant defining how many submissions per round will be rewarded
Expand Down Expand Up @@ -80,9 +82,11 @@ pub fn execute(
msg: ExecuteMsg,
) -> Result<Response, ContractError> {
match msg {
ExecuteMsg::AddRound { round, signature } => {
execute_add_round(deps, env, info, round, signature)
}
ExecuteMsg::AddRound {
network,
round,
signature,
} => execute_add_round(deps, env, info, network, round, signature),
ExecuteMsg::RegisterBot { moniker } => execute_register_bot(deps, env, info, moniker),
ExecuteMsg::SetIncentivized { round } => execute_set_incentivized(deps, env, info, round),
ExecuteMsg::UpdateAllowlistBots { add, remove } => {
Expand Down Expand Up @@ -307,6 +311,7 @@ fn execute_add_round(
deps: DepsMut,
env: Env,
info: MessageInfo,
network: Option<String>,
round: u64,
signature: HexBinary,
) -> Result<Response, ContractError> {
Expand All @@ -322,27 +327,49 @@ fn execute_add_round(
return Err(ContractError::RoundTooLow { round, min_round });
}

let network = DrandNetwork::from_str(network.as_deref().unwrap_or("fastnet"))?;

// Initialise the incentive to 0
let mut reward_points = 0u64;

// Get the number of submission before this one.
let submissions_count = SUBMISSIONS_COUNT
.may_load(deps.storage, round)?
.unwrap_or_default();
let submissions_count = match network {
DrandNetwork::Fastnet => FASTNET_SUBMISSIONS_COUNT
.may_load(deps.storage, round)?
.unwrap_or_default(),
DrandNetwork::Quicknet => QUICKNET_SUBMISSIONS_COUNT
.may_load(deps.storage, round)?
.unwrap_or_default(),
};

let randomness: HexBinary = derive_randomness(signature.as_slice()).into();
// Check if we need to verify the submission or we just compare it to the registered randomness from the first submission of this round
let is_verifying_tx: bool;

if submissions_count < NUMBER_OF_SUBMISSION_VERIFICATION_PER_ROUND {
is_verifying_tx = true;
// Since we have a static pubkey, it is safe to use the unchecked method
let pk = G2PubkeyFastnet::from_fixed_unchecked(DRAND_MAINNET2_PUBKEY)
.map_err(|_| ContractError::InvalidPubkey {})?;
// Verify BLS
if !pk.verify(round, b"", &signature).unwrap_or(false) {
return Err(ContractError::InvalidSignature {});

match network {
DrandNetwork::Fastnet => {
// Since we have a static pubkey, it is safe to use the unchecked method
let pk = G2PubkeyFastnet::from_fixed_unchecked(DrandNetwork::Fastnet.pubkey())
.map_err(|_| ContractError::InvalidPubkey {})?;
// Verify BLS
if !pk.verify(round, b"", &signature).unwrap_or(false) {
return Err(ContractError::InvalidSignature {});
}
}
DrandNetwork::Quicknet => {
// Since we have a static pubkey, it is safe to use the unchecked method
let pk = G2PubkeyRfc::from_fixed_unchecked(DrandNetwork::Quicknet.pubkey())
.map_err(|_| ContractError::InvalidPubkey {})?;
// Verify BLS
if !pk.verify(round, b"", &signature).unwrap_or(false) {
return Err(ContractError::InvalidSignature {});
}
}
}

// Send verification reward
reward_points += INCENTIVE_POINTS_FOR_VERIFICATION;
} else {
Expand Down Expand Up @@ -394,7 +421,7 @@ fn execute_add_round(
},
)?;

SUBMISSIONS_COUNT.save(deps.storage, round, &new_count)?;
FASTNET_SUBMISSIONS_COUNT.save(deps.storage, round, &new_count)?;

let mut attributes = vec![
Attribute::new(ATTR_ROUND, round.to_string()),
Expand Down Expand Up @@ -557,7 +584,9 @@ mod tests {
mock_dependencies, mock_dependencies_with_balance, mock_env, mock_info,
};
use cosmwasm_std::{coins, from_json, Addr, Timestamp, Uint128};
use drand_common::testing::testing_signature;
use drand_common::testing::{testing_signature_fastnet, testing_signature_quicknet};
use drand_common::DrandNetwork::*;
use hex_literal::hex;

const TESTING_MANAGER: &str = "mngr";
const GATEWAY: &str = "thegateway";
Expand All @@ -567,18 +596,26 @@ mod tests {
const DEFAULT_HEIGHT: u64 = 12345;
const DEFAULT_TX_INDEX: Option<u32> = Some(3);

fn make_add_round_msg(round: u64) -> ExecuteMsg {
if let Some(signature) = testing_signature(round) {
ExecuteMsg::AddRound { round, signature }
} else {
panic!("Test round {round} not set");
fn make_add_round_msg(network: DrandNetwork, round: u64) -> ExecuteMsg {
let signature = match network {
Fastnet => testing_signature_fastnet(round),
Quicknet => testing_signature_quicknet(round),
};
let Some(signature) = signature else {
panic!("Test round {round} not set for {network}");
};

ExecuteMsg::AddRound {
network: Some(network.to_string()),
round,
signature,
}
}

/// Adds round 72750, 72775, 72800, 72825
fn add_test_rounds(mut deps: DepsMut, bot_addr: &str) {
for round in [72750, 72775, 72800, 72825] {
let msg = make_add_round_msg(round);
let msg = make_add_round_msg(Fastnet, round);
execute(deps.branch(), mock_env(), mock_info(bot_addr, &[]), msg).unwrap();
}
}
Expand Down Expand Up @@ -661,7 +698,7 @@ mod tests {

register_bot(deps.as_mut(), "anyone");

let msg = make_add_round_msg(72780);
let msg = make_add_round_msg(Fastnet, 72780);
let info = mock_info("anyone", &[]);
execute(deps.as_mut(), mock_env(), info, msg).unwrap();

Expand All @@ -674,6 +711,44 @@ mod tests {
);
}

#[test]
fn add_round_works_for_quicknet() {
let mut deps = mock_dependencies();

let info = mock_info("creator", &[]);
let msg = InstantiateMsg {
manager: TESTING_MANAGER.to_string(),
min_round: TESTING_MIN_ROUND,
incentive_point_price: Uint128::new(20_000),
incentive_denom: "unois".to_string(),
};
instantiate(deps.as_mut(), mock_env(), info, msg).unwrap();

register_bot(deps.as_mut(), "anyone");

// https://api3.drand.sh/52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971/public/7307065
let msg = ExecuteMsg::AddRound {
network: Some("quicknet".to_string()),
round: 7307065,
signature: HexBinary::from_hex("a8f51ba7ab91c937aa8c54f58da04172a4a9a8af2d7c95502e99c1dc29e574968b4b832446a18c251e07f7abc16a362f").unwrap(),
};
let expected_randomness =
hex!("41155d35838a37100a908a4a713c593573d7ab6e845189fb24f7e31d72fc05e3");
let info = mock_info("anyone", &[]);
execute(deps.as_mut(), mock_env(), info, msg).unwrap();

let BeaconResponse { beacon } = from_json(
query(
deps.as_ref(),
mock_env(),
QueryMsg::Beacon { round: 7307065 },
)
.unwrap(),
)
.unwrap();
assert_eq!(beacon.unwrap().randomness, &expected_randomness);
}

#[test]
fn add_round_not_divisible_by_15_succeeds() {
let mut deps = mock_dependencies_with_balance(&[Coin::new(9999999, "unois")]);
Expand All @@ -700,7 +775,7 @@ mod tests {
allowlist_bot(deps.as_mut(), BOT2);

// succeeds but not incentivized
let msg: ExecuteMsg = make_add_round_msg(ROUND);
let msg = make_add_round_msg(Fastnet, ROUND);
let response: Response =
execute(deps.as_mut(), mock_env(), mock_info(BOT1, &[]), msg).unwrap();
assert_eq!(response.messages.len(), 0);
Expand Down Expand Up @@ -732,7 +807,7 @@ mod tests {
execute(deps.as_mut(), mock_env(), mock_info(GATEWAY, &[]), msg).unwrap();

// succeeds and incentivized
let msg: ExecuteMsg = make_add_round_msg(ROUND);
let msg = make_add_round_msg(Fastnet, ROUND);
let response: Response =
execute(deps.as_mut(), mock_env(), mock_info(BOT2, &[]), msg).unwrap();
assert_eq!(response.messages.len(), 2);
Expand Down Expand Up @@ -769,7 +844,7 @@ mod tests {
from_json(query(deps.as_ref(), mock_env(), QueryMsg::Config {}).unwrap()).unwrap();
assert_eq!(min_round, TESTING_MIN_ROUND);

let msg = make_add_round_msg(10);
let msg = make_add_round_msg(Fastnet, 10);
let err = execute(deps.as_mut(), mock_env(), mock_info("anyone", &[]), msg).unwrap_err();
assert!(matches!(
err,
Expand Down Expand Up @@ -809,7 +884,7 @@ mod tests {
const EXPECTED_RANDOMNESS: &str =
"2f3a6976baf6847d75b5eae60c0e460bb55ab6034ee28aef2f0d10b0b5cc57c1";

let msg = make_add_round_msg(ROUND);
let msg = make_add_round_msg(Fastnet, ROUND);
let info = mock_info("unregistered_bot", &[]);
let response = execute(deps.as_mut(), mock_env(), info, msg).unwrap();
let attrs = response.attributes;
Expand Down Expand Up @@ -1087,7 +1162,7 @@ mod tests {
const EXPECTED_RANDOMNESS: &str =
"b84506d4342f4ec2506baa60a6b611ab006cf45e870d069ebb1b6a051c9e9acf";

let msg = make_add_round_msg(ROUND);
let msg = make_add_round_msg(Fastnet, ROUND);
let info = mock_info(MYBOT, &[]);
let response = execute(deps.as_mut(), mock_env(), info, msg).unwrap();
assert_eq!(response.messages.len(), 0);
Expand Down Expand Up @@ -1158,7 +1233,7 @@ mod tests {

// Same msg for all submissions
const ROUND: u64 = 72775;
let msg = make_add_round_msg(ROUND);
let msg = make_add_round_msg(Fastnet, ROUND);

// 1st
let info = mock_info(bot1, &[]);
Expand Down Expand Up @@ -1251,7 +1326,7 @@ mod tests {
};
instantiate(deps.as_mut(), mock_env(), info, msg).unwrap();

let msg = make_add_round_msg(72780);
let msg = make_add_round_msg(Fastnet, 72780);
let info = mock_info("unregistered_bot", &[]);
let response = execute(deps.as_mut(), mock_env(), info, msg).unwrap();
let randomness = first_attr(response.attributes, "randomness").unwrap();
Expand All @@ -1277,6 +1352,7 @@ mod tests {
register_bot(deps.as_mut(), "anyone");
let info = mock_info("anyone", &[]);
let msg = ExecuteMsg::AddRound {
network: Some("fastnet".to_string()),
round: 72780,
signature: hex::decode("3cc6f6cdf59e95526d5a5d82aaa84fa6f181e4")
.unwrap()
Expand All @@ -1303,8 +1379,9 @@ mod tests {
instantiate(deps.as_mut(), mock_env(), info, msg).unwrap();

let msg = ExecuteMsg::AddRound {
network: Some("fastnet".to_string()),
round: 72790, // wrong round
signature: testing_signature(72780).unwrap(),
signature: testing_signature_fastnet(72780).unwrap(),
};
let result = execute(deps.as_mut(), mock_env(), mock_info("anon", &[]), msg);
match result.unwrap_err() {
Expand All @@ -1314,6 +1391,7 @@ mod tests {

let msg = ExecuteMsg::AddRound {
// curl -sS https://drand.cloudflare.com/dbd506d6ef76e5f386f41c651dcb808c5bcbd75471cc4eafa3f4df7ad4e4c493/public/72780
network: Some("fastnet".to_string()),
round: 72780,
// wrong signature (first two bytes swapped)
signature: hex::decode("ac86005aaffa5e9de34b558c470a111c862e976922e8da34f9dce1a78507dbd53badd554862bc54bd8e44f44ddd8b100").unwrap().into(),
Expand All @@ -1340,7 +1418,7 @@ mod tests {
};
instantiate(deps.as_mut(), mock_env(), info, msg).unwrap();

let msg = make_add_round_msg(72780);
let msg = make_add_round_msg(Fastnet, 72780);

// Execute 1
register_bot(deps.as_mut(), "anyone");
Expand Down Expand Up @@ -1378,7 +1456,7 @@ mod tests {
};
instantiate(deps.as_mut(), mock_env(), info, msg).unwrap();

let msg = make_add_round_msg(72780);
let msg = make_add_round_msg(Fastnet, 72780);

const A: &str = "bot_alice";
const B: &str = "bot_bob";
Expand Down
7 changes: 6 additions & 1 deletion contracts/nois-drand/src/msg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,12 @@ pub struct InstantiateMsg {
#[cw_serde]
pub enum ExecuteMsg {
/// Add drand beacon
AddRound { round: u64, signature: HexBinary },
AddRound {
/// "fastnet" or "quicknet", defaults to "fastnet" if not set
network: Option<String>,
round: u64,
signature: HexBinary,
},
/// Registers a bot using on the sender address of the message.
/// A re-registation updates the information of the bot.
RegisterBot { moniker: String },
Expand Down
Loading