-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Stores encrypted votes * Stores decrypted votes later
- Loading branch information
Showing
13 changed files
with
536 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
[package] | ||
name = "voting_contract" | ||
description = "The private voting contract for NDC" | ||
version = "0.1.0" | ||
edition = "2021" | ||
|
||
[lib] | ||
crate-type = ["cdylib", "rlib"] | ||
|
||
[dependencies] | ||
near-sdk = { workspace = true, features = ["legacy"] } | ||
common-contracts.workspace = true | ||
|
||
[dev-dependencies] | ||
near-sdk = { workspace = true, features = ["unit-testing"] } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
# voting-contract | ||
|
||
The private voting contract for NDC governance | ||
|
||
## How to Build Locally? | ||
|
||
Install [`cargo-near`](https://github.com/near/cargo-near) and run: | ||
|
||
```bash | ||
cargo near build | ||
``` | ||
|
||
## How to Test Locally? | ||
|
||
```bash | ||
cargo test | ||
``` | ||
|
||
## How to Deploy? | ||
|
||
Deployment is automated with GitHub Actions CI/CD pipeline. | ||
To deploy manually, install [`cargo-near`](https://github.com/near/cargo-near) and run: | ||
|
||
```bash | ||
cargo near deploy <account-id> | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
[toolchain] | ||
channel = "stable" | ||
components = ["rustfmt"] | ||
targets = ["wasm32-unknown-unknown"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
pub const RELAYER_ONLY: &str = "Only relayer can call this method"; | ||
pub const VOTING_PHASE_OVER: &str = "Voting phase is over"; | ||
pub const VOTING_PHASE_IN_PROGRESS: &str = "Voting phase is in progress"; | ||
pub const DEPOSIT_NOT_ENOUGH: &str = "Deposit is not enough to cover the storage cost"; | ||
pub const INVALID_VOTE_DATA: &str = "Invalid vote data"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
// Will be moved off-chain | ||
|
||
use aes_siv::{aead::generic_array::GenericArray, siv::Aes128Siv, KeyInit}; | ||
use secp256k1::{ecdh::SharedSecret, PublicKey, SecretKey}; | ||
|
||
pub fn decrypt_message(bs58message: &str, secret: &SecretKey, pubkey: [u8; 64]) -> Option<Vec<u8>> { | ||
let pubkey = PublicKey::from_slice(&pubkey).ok()?; | ||
|
||
let common_secret = SharedSecret::new(&pubkey, secret); | ||
|
||
let bytes = near_sdk::bs58::decode(bs58message).into_vec().ok()?; | ||
|
||
let ad_data: &[&[u8]] = &[]; | ||
let mut cipher = Aes128Siv::new(&GenericArray::clone_from_slice( | ||
&common_secret.secret_bytes(), | ||
)); | ||
|
||
let decrypted_data = cipher.decrypt(ad_data, &bytes).ok()?; | ||
|
||
Some(decrypted_data) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,262 @@ | ||
use near_sdk::borsh::{BorshDeserialize, BorshSerialize}; | ||
use near_sdk::collections::{UnorderedMap, Vector}; | ||
use near_sdk::env::panic_str; | ||
use near_sdk::{env, near_bindgen, require, AccountId, PanicOnDefault, Timestamp}; | ||
|
||
pub mod consts; | ||
pub mod storage; | ||
pub mod types; | ||
pub mod views; | ||
|
||
#[cfg(test)] | ||
pub mod test_utils; | ||
|
||
use consts::*; | ||
use storage::StorageKey; | ||
use types::{EncryptedVoteStorage, EncryptedVoteView}; | ||
|
||
#[near_bindgen] | ||
#[derive(BorshDeserialize, BorshSerialize, PanicOnDefault)] | ||
#[borsh(crate = "near_sdk::borsh")] | ||
pub struct Contract { | ||
votes: Vector<EncryptedVoteStorage>, | ||
|
||
candidate_weights: UnorderedMap<AccountId, u64>, | ||
|
||
relayer: AccountId, | ||
end_time_in_ms: Timestamp, | ||
} | ||
|
||
#[near_bindgen] | ||
impl Contract { | ||
#[init] | ||
pub fn new(relayer: AccountId, end_time_in_ms: Timestamp) -> Self { | ||
Contract { | ||
votes: Vector::new(StorageKey::Votes), | ||
candidate_weights: UnorderedMap::new(StorageKey::CandidatesWeights), | ||
relayer, | ||
end_time_in_ms, | ||
} | ||
} | ||
|
||
#[payable] | ||
pub fn send_encrypted_votes(&mut self, votes: Vec<EncryptedVoteView>) { | ||
let storage_start = env::storage_usage(); | ||
require!( | ||
env::block_timestamp_ms() < self.end_time_in_ms, | ||
VOTING_PHASE_OVER | ||
); | ||
self.assert_relayer(); | ||
let votes: Option<Vec<_>> = votes.into_iter().map(Into::into).collect(); | ||
|
||
if let Some(votes) = votes { | ||
self.votes.extend(votes); | ||
} else { | ||
panic_str(INVALID_VOTE_DATA); | ||
} | ||
|
||
require!( | ||
common_contracts::finalize_storage_check(storage_start, 0), | ||
DEPOSIT_NOT_ENOUGH | ||
); | ||
} | ||
|
||
#[payable] | ||
pub fn sumbit_results(&mut self, results: Vec<(AccountId, u64)>) { | ||
let storage_start = env::storage_usage(); | ||
println!("{}, {}", env::block_timestamp_ms(), self.end_time_in_ms); | ||
|
||
require!( | ||
env::block_timestamp_ms() > self.end_time_in_ms, | ||
VOTING_PHASE_IN_PROGRESS | ||
); | ||
self.assert_relayer(); | ||
|
||
self.candidate_weights.extend(results); | ||
|
||
require!( | ||
common_contracts::finalize_storage_check(storage_start, 0), | ||
DEPOSIT_NOT_ENOUGH | ||
); | ||
} | ||
|
||
fn assert_relayer(&self) { | ||
require!( | ||
env::predecessor_account_id() == self.relayer, | ||
consts::RELAYER_ONLY | ||
); | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod relayer_tests { | ||
use near_sdk::{json_types::Base64VecU8, testing_env, NearToken}; | ||
|
||
use crate::{test_utils::*, types::EncryptedVoteView}; | ||
|
||
#[test] | ||
fn can_init_contract() { | ||
let (context, contract) = setup_ctr(); | ||
testing_env!(context.clone()); | ||
assert_eq!(contract.get_relayer(), relayer()); | ||
assert_eq!(contract.get_end_time(), end_time()); | ||
} | ||
|
||
#[test] | ||
fn can_send_encrypted_votes() { | ||
let (mut context, mut contract) = setup_ctr(); | ||
context.predecessor_account_id = relayer(); | ||
context.attached_deposit = NearToken::from_near(1); | ||
testing_env!(context.clone()); | ||
|
||
let votes: Vec<_> = vec![ | ||
EncryptedVoteView { | ||
vote: "vote1".to_string(), | ||
pubkey: Base64VecU8([1; 64].to_vec()), | ||
}, | ||
EncryptedVoteView { | ||
vote: "vote2".to_string(), | ||
pubkey: Base64VecU8([2; 64].to_vec()), | ||
}, | ||
]; | ||
|
||
contract.send_encrypted_votes(votes.clone()); | ||
|
||
assert_eq!(contract.get_votes(0, 10), votes); | ||
} | ||
|
||
#[test] | ||
fn can_submit_results() { | ||
let (mut context, mut contract) = setup_ctr(); | ||
context.predecessor_account_id = relayer(); | ||
context.attached_deposit = NearToken::from_near(1); | ||
context.block_timestamp = (end_time() + 1) * MSECOND; | ||
testing_env!(context.clone()); | ||
|
||
let results: Vec<_> = vec![(acc(1), 1), (acc(2), 2), (acc(3), 3)]; | ||
|
||
contract.sumbit_results(results.clone()); | ||
|
||
assert_eq!(contract.get_candidate_weights(0, 10), results); | ||
} | ||
|
||
#[test] | ||
#[should_panic(expected = "Only relayer can call this method")] | ||
fn anybody_cant_add_votes() { | ||
let (mut context, mut contract) = setup_ctr(); | ||
context.predecessor_account_id = acc(1); | ||
context.attached_deposit = NearToken::from_near(1); | ||
testing_env!(context.clone()); | ||
|
||
let votes: Vec<_> = vec![ | ||
EncryptedVoteView { | ||
vote: "vote1".to_string(), | ||
pubkey: Base64VecU8([1; 64].to_vec()), | ||
}, | ||
EncryptedVoteView { | ||
vote: "vote2".to_string(), | ||
pubkey: Base64VecU8([2; 64].to_vec()), | ||
}, | ||
]; | ||
|
||
contract.send_encrypted_votes(votes.clone()); | ||
} | ||
|
||
#[test] | ||
#[should_panic(expected = "Only relayer can call this method")] | ||
fn anybody_cant_submit_results() { | ||
let (mut context, mut contract) = setup_ctr(); | ||
context.predecessor_account_id = acc(1); | ||
context.attached_deposit = NearToken::from_near(1); | ||
context.block_timestamp = (end_time() + 1) * MSECOND; | ||
testing_env!(context.clone()); | ||
|
||
let results: Vec<_> = vec![(acc(1), 1), (acc(2), 2), (acc(3), 3)]; | ||
|
||
contract.sumbit_results(results.clone()); | ||
} | ||
|
||
#[test] | ||
#[should_panic(expected = "Voting phase is over")] | ||
fn cant_add_votes_after_voting_phase() { | ||
let (mut context, mut contract) = setup_ctr(); | ||
context.predecessor_account_id = relayer(); | ||
context.attached_deposit = NearToken::from_near(1); | ||
context.block_timestamp = (end_time() + 1) * MSECOND; | ||
testing_env!(context.clone()); | ||
|
||
let votes: Vec<_> = vec![ | ||
EncryptedVoteView { | ||
vote: "vote1".to_string(), | ||
pubkey: Base64VecU8([1; 64].to_vec()), | ||
}, | ||
EncryptedVoteView { | ||
vote: "vote2".to_string(), | ||
pubkey: Base64VecU8([2; 64].to_vec()), | ||
}, | ||
]; | ||
|
||
contract.send_encrypted_votes(votes.clone()); | ||
} | ||
|
||
#[test] | ||
#[should_panic(expected = "Voting phase is in progress")] | ||
fn cant_submit_results_before_voting_phase() { | ||
let (mut context, mut contract) = setup_ctr(); | ||
context.predecessor_account_id = relayer(); | ||
context.attached_deposit = NearToken::from_near(1); | ||
testing_env!(context.clone()); | ||
|
||
let results: Vec<_> = vec![(acc(1), 1), (acc(2), 2), (acc(3), 3)]; | ||
|
||
contract.sumbit_results(results.clone()); | ||
} | ||
|
||
#[test] | ||
#[should_panic(expected = "Deposit is not enough to cover the storage cost")] | ||
fn cant_add_votes_with_insufficient_deposit() { | ||
let (mut context, mut contract) = setup_ctr(); | ||
context.predecessor_account_id = relayer(); | ||
context.attached_deposit = NearToken::from_near(0); | ||
testing_env!(context.clone()); | ||
|
||
let votes: Vec<_> = vec![ | ||
EncryptedVoteView { | ||
vote: "vote1".to_string(), | ||
pubkey: Base64VecU8([1; 64].to_vec()), | ||
}, | ||
EncryptedVoteView { | ||
vote: "vote2".to_string(), | ||
pubkey: Base64VecU8([2; 64].to_vec()), | ||
}, | ||
]; | ||
|
||
contract.send_encrypted_votes(votes.clone()); | ||
} | ||
|
||
#[test] | ||
#[should_panic(expected = "Invalid vote data")] | ||
fn cant_add_invalid_votes() { | ||
let (mut context, mut contract) = setup_ctr(); | ||
context.predecessor_account_id = relayer(); | ||
context.attached_deposit = NearToken::from_near(1); | ||
testing_env!(context.clone()); | ||
|
||
let votes: Vec<_> = vec![ | ||
EncryptedVoteView { | ||
vote: "vote1".to_string(), | ||
pubkey: Base64VecU8([1; 64].to_vec()), | ||
}, | ||
EncryptedVoteView { | ||
vote: "vote2".to_string(), | ||
pubkey: Base64VecU8([2; 66].to_vec()), | ||
}, | ||
EncryptedVoteView { | ||
vote: "vote3".to_string(), | ||
pubkey: Base64VecU8([3; 64].to_vec()), | ||
}, | ||
]; | ||
|
||
contract.send_encrypted_votes(votes.clone()); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
use near_sdk::borsh::BorshSerialize; | ||
use near_sdk::BorshStorageKey; | ||
|
||
#[derive(BorshSerialize, BorshStorageKey)] | ||
#[borsh(crate = "near_sdk::borsh")] | ||
pub enum StorageKey { | ||
Votes, | ||
CandidatesWeights, | ||
} |
Oops, something went wrong.