merge develop
This commit is contained in:
@@ -33,13 +33,13 @@ jobs:
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --all
|
||||
args: --workspace
|
||||
|
||||
- name: Run all tests
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --all --all-features
|
||||
args: --workspace --all-features
|
||||
|
||||
- name: Check formatting
|
||||
uses: actions-rs/cargo@v1
|
||||
@@ -63,13 +63,13 @@ jobs:
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --all --features=coconut
|
||||
args: --workspace --features=coconut
|
||||
|
||||
- name: Run all tests with coconut enabled
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --all --features=coconut
|
||||
args: --workspace --features=coconut
|
||||
|
||||
- name: Run clippy with coconut enabled
|
||||
uses: actions-rs/cargo@v1
|
||||
|
||||
@@ -45,7 +45,7 @@ jobs:
|
||||
RUSTFLAGS: '-C link-arg=-s'
|
||||
with:
|
||||
command: build
|
||||
args: --manifest-path contracts/Cargo.toml --all --target wasm32-unknown-unknown
|
||||
args: --manifest-path contracts/Cargo.toml --workspace --target wasm32-unknown-unknown
|
||||
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
@@ -61,4 +61,4 @@ jobs:
|
||||
if: ${{ matrix.rust != 'nightly' }}
|
||||
with:
|
||||
command: clippy
|
||||
args: --manifest-path contracts/Cargo.toml --all -- -D warnings
|
||||
args: --manifest-path contracts/Cargo.toml --workspace -- -D warnings
|
||||
|
||||
@@ -16,8 +16,9 @@ jobs:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '14'
|
||||
- run: npm install
|
||||
node-version: '16'
|
||||
- name: Setup yarn
|
||||
run: npm install -g yarn
|
||||
- name: Run ESLint
|
||||
# GitHub should automatically annotate the PR
|
||||
run: npm run lint
|
||||
run: yarn && yarn lint
|
||||
@@ -19,14 +19,17 @@ jobs:
|
||||
- uses: rlespinasse/github-slug-action@v3.x
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '14'
|
||||
- run: npm install
|
||||
node-version: '16'
|
||||
- name: Setup yarn
|
||||
run: npm install -g yarn
|
||||
continue-on-error: true
|
||||
- name: Build shared packages
|
||||
run: cd .. && yarn && yarn build
|
||||
- name: Set environment from the example
|
||||
run: cp .env.prod .env
|
||||
- run: npm run test
|
||||
continue-on-error: true
|
||||
- run: npm run build
|
||||
# - run: yarn test
|
||||
# continue-on-error: true
|
||||
- run: yarn && yarn build
|
||||
continue-on-error: true
|
||||
- name: Deploy branch to CI www
|
||||
continue-on-error: true
|
||||
|
||||
@@ -42,13 +42,13 @@ jobs:
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --all
|
||||
args: --workspace
|
||||
|
||||
- name: Run all tests
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --all
|
||||
args: --workspace
|
||||
|
||||
- name: Check formatting
|
||||
uses: actions-rs/cargo@v1
|
||||
@@ -73,7 +73,7 @@ jobs:
|
||||
if: ${{ matrix.rust != 'nightly' }}
|
||||
with:
|
||||
command: clippy
|
||||
args: -- -D warnings
|
||||
args: --workspace --all-targets -- -D warnings
|
||||
|
||||
|
||||
# COCONUT stuff
|
||||
@@ -81,20 +81,20 @@ jobs:
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --all --features=coconut
|
||||
args: --workspace --features=coconut
|
||||
|
||||
- name: Run all tests with coconut enabled
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --all --features=coconut
|
||||
args: --workspace --features=coconut
|
||||
|
||||
- name: Run clippy with coconut enabled
|
||||
uses: actions-rs/cargo@v1
|
||||
if: ${{ matrix.rust != 'nightly' }}
|
||||
with:
|
||||
command: clippy
|
||||
args: --features=coconut -- -D warnings
|
||||
args: --workspace --all-targets --features=coconut -- -D warnings
|
||||
notification:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
name: Generate TS types
|
||||
|
||||
on:
|
||||
push:
|
||||
paths-ignore:
|
||||
- "explorer/**"
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- "explorer/**"
|
||||
|
||||
jobs:
|
||||
nym-wallet-types:
|
||||
runs-on: [ self-hosted, custom-linux-exoscale ]
|
||||
# Enable sccache
|
||||
env:
|
||||
RUSTC_WRAPPER: /home/ubuntu/.cargo/bin/sccache
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
steps:
|
||||
- name: Prepare
|
||||
run: sudo apt-get update && sudo apt-get install -y libpango1.0-dev libatk1.0-dev libgdk-pixbuf2.0-dev libsoup2.4-dev librust-gdk-dev libwebkit2gtk-4.0-dev
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
- name: Generate TS
|
||||
run: cd nym-wallet/src-tauri && cargo test
|
||||
- uses: EndBug/add-and-commit@v7.2.1 # https://github.com/marketplace/actions/add-commit
|
||||
with:
|
||||
add: '["nym-wallet"]'
|
||||
message: "[ci skip] Generate TS types"
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
Generated
+14
-2
@@ -823,6 +823,7 @@ dependencies = [
|
||||
name = "credentials"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bls12_381",
|
||||
"coconut-interface",
|
||||
"crypto",
|
||||
"network-defaults",
|
||||
@@ -1077,6 +1078,17 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cw-storage-plus"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c087ff98fb0475db4c2b5298a5fd12b2848d2854b39d1115d930ee6da24d1eed"
|
||||
dependencies = [
|
||||
"cosmwasm-std",
|
||||
"schemars",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.13.1"
|
||||
@@ -6046,7 +6058,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"config",
|
||||
"cosmwasm-std",
|
||||
"cw-storage-plus",
|
||||
"cw-storage-plus 0.12.1",
|
||||
"mixnet-contract-common",
|
||||
"schemars",
|
||||
"serde",
|
||||
@@ -6060,7 +6072,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"config",
|
||||
"cosmwasm-std",
|
||||
"cw-storage-plus",
|
||||
"cw-storage-plus 0.11.1",
|
||||
"mixnet-contract-common",
|
||||
"schemars",
|
||||
"serde",
|
||||
|
||||
@@ -16,16 +16,16 @@ clippy-happy-wallet:
|
||||
cargo clippy --manifest-path nym-wallet/Cargo.toml
|
||||
|
||||
clippy-all-main:
|
||||
cargo clippy --all-features -- -D warnings
|
||||
cargo clippy --workspace --all-features -- -D warnings
|
||||
|
||||
clippy-all-contracts:
|
||||
cargo clippy --manifest-path contracts/Cargo.toml --all-features --target wasm32-unknown-unknown -- -D warnings
|
||||
cargo clippy --workspace --manifest-path contracts/Cargo.toml --all-features --target wasm32-unknown-unknown -- -D warnings
|
||||
|
||||
clippy-all-wallet:
|
||||
cargo clippy --manifest-path nym-wallet/Cargo.toml --all-features -- -D warnings
|
||||
cargo clippy --workspace --manifest-path nym-wallet/Cargo.toml --all-features -- -D warnings
|
||||
|
||||
test-main:
|
||||
cargo test --all-features --all
|
||||
cargo test --all-features --workspace
|
||||
|
||||
test-contracts:
|
||||
cargo test --manifest-path contracts/Cargo.toml --all-features
|
||||
@@ -34,13 +34,13 @@ test-wallet:
|
||||
cargo test --manifest-path nym-wallet/Cargo.toml --all-features
|
||||
|
||||
build-main:
|
||||
cargo build --all
|
||||
cargo build --workspace
|
||||
|
||||
build-contracts:
|
||||
cargo build --manifest-path contracts/Cargo.toml --all
|
||||
cargo build --manifest-path contracts/Cargo.toml --workspace
|
||||
|
||||
build-wallet:
|
||||
cargo build --manifest-path nym-wallet/Cargo.toml --all
|
||||
cargo build --manifest-path nym-wallet/Cargo.toml --workspace
|
||||
|
||||
fmt-main:
|
||||
cargo fmt --all
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 545 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 48 KiB |
@@ -1,13 +1,10 @@
|
||||
<svg width="300" height="300" viewBox="0 0 296 296" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M148 296C229.738 296 296 229.738 296 148C296 66.2619 229.738 0 148 0C66.2619 0 0 66.2619 0 148C0 229.738 66.2619 296 148 296Z" fill="url(#paint0_linear_113_1244)"/>
|
||||
<path d="M148 285.875C224.147 285.875 285.875 224.146 285.875 148C285.875 71.8536 224.147 10.1248 148 10.1248C71.8538 10.1248 10.125 71.8536 10.125 148C10.125 224.146 71.8538 285.875 148 285.875Z" fill="#121725"/>
|
||||
<path d="M88.8829 120.143H88.7169V120.281V168.637L68.3289 120.226L68.3012 120.143H68.1905H56.6272H43.653H43.5146V120.281V175.719V175.857H43.653H56.6272H56.7655V175.719V127.28L77.2365 175.774L77.2642 175.857H77.3748H88.8829H101.829H101.968V175.719V120.281V120.143H101.829H88.8829Z" fill="white"/>
|
||||
<path d="M252.347 120.143H227.616H227.477L227.45 120.253L214.78 168.858L202.082 120.253L202.054 120.143H201.944H177.157H176.991V120.281V175.719V175.857H177.157H190.104H190.242V175.719V127.667L202.774 175.747L202.801 175.857H202.94H226.564H226.675L226.703 175.747L239.234 127.667V175.719V175.857H239.373H252.347H252.485V175.719V120.281V120.143H252.347Z" fill="white"/>
|
||||
<path d="M155.663 120.143H155.58L155.552 120.198L139.812 147.557L123.988 120.198L123.96 120.143H123.877H108.911H108.635L108.773 120.364L133.145 162.579V175.719V175.857H133.283H146.257H146.396V175.719V162.579L170.767 120.364L170.905 120.143H170.629H155.663Z" fill="white"/>
|
||||
<svg width="64" height="64" viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M40 78.5C61.263 78.5 78.5 61.263 78.5 40C78.5 18.737 61.263 1.5 40 1.5C18.737 1.5 1.5 18.737 1.5 40C1.5 61.263 18.737 78.5 40 78.5Z" fill="#070B15" stroke="url(#paint0_linear_0_1)" stroke-width="3"/>
|
||||
<path d="M31.4894 27.56L41.8623 56H48.5106H56V24H48.5106V52.4L38.1777 24H31.4894H24V56H31.4894V27.56Z" fill="white"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_113_1244" x1="0" y1="148" x2="296" y2="148" gradientUnits="userSpaceOnUse">
|
||||
<linearGradient id="paint0_linear_0_1" x1="0.839161" y1="80" x2="80" y2="80" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.09375" stop-color="#FB6E4E"/>
|
||||
<stop offset="1" stop-color="#FC1D60"/>
|
||||
<stop offset="1" stop-color="#F51473"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 672 B |
@@ -92,7 +92,7 @@ impl Config {
|
||||
|
||||
#[cfg(feature = "nymd-client")]
|
||||
pub struct Client<C> {
|
||||
network: network_defaults::all::Network,
|
||||
pub network: network_defaults::all::Network,
|
||||
mixnet_contract_address: Option<cosmrs::AccountId>,
|
||||
vesting_contract_address: Option<cosmrs::AccountId>,
|
||||
erc20_bridge_contract_address: Option<cosmrs::AccountId>,
|
||||
@@ -283,6 +283,13 @@ impl<C> Client<C> {
|
||||
Ok(self.nymd.get_current_interval().await?)
|
||||
}
|
||||
|
||||
pub async fn get_epochs_in_interval(&self) -> Result<u64, ValidatorClientError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
{
|
||||
Ok(self.nymd.get_epochs_in_interval().await?)
|
||||
}
|
||||
|
||||
pub async fn get_circulating_supply(&self) -> Result<u128, ValidatorClientError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
|
||||
@@ -21,7 +21,7 @@ where
|
||||
}
|
||||
|
||||
// maybe the wallet could be made into a generic, but for now, let's just have this one implementation
|
||||
pub fn connect_with_signer<U>(
|
||||
pub fn connect_with_signer<U: Clone>(
|
||||
endpoint: U,
|
||||
signer: DirectSecp256k1HdWallet,
|
||||
gas_price: GasPrice,
|
||||
|
||||
@@ -641,7 +641,7 @@ pub struct Client {
|
||||
}
|
||||
|
||||
impl Client {
|
||||
pub fn connect_with_signer<U>(
|
||||
pub fn connect_with_signer<U: Clone>(
|
||||
endpoint: U,
|
||||
signer: DirectSecp256k1HdWallet,
|
||||
gas_price: GasPrice,
|
||||
|
||||
@@ -47,9 +47,12 @@ pub enum Operation {
|
||||
CreatePeriodicVestingAccount,
|
||||
|
||||
AdvanceCurrentInterval,
|
||||
AdvanceCurrentEpoch,
|
||||
WriteRewardedSet,
|
||||
ClearRewardedSet,
|
||||
UpdateMixnetAddress,
|
||||
CheckpointMixnodes,
|
||||
ReconcileDelegations,
|
||||
}
|
||||
|
||||
pub(crate) fn calculate_fee(gas_price: &GasPrice, gas_limit: Gas) -> Coin {
|
||||
@@ -91,6 +94,9 @@ impl fmt::Display for Operation {
|
||||
Operation::WriteRewardedSet => f.write_str("WriteRewardedSet"),
|
||||
Operation::ClearRewardedSet => f.write_str("ClearRewardedSet"),
|
||||
Operation::UpdateMixnetAddress => f.write_str("UpdateMixnetAddress"),
|
||||
Operation::CheckpointMixnodes => f.write_str("CheckpointMixnodes"),
|
||||
Operation::ReconcileDelegations => f.write_str("ReconcileDelegations"),
|
||||
Operation::AdvanceCurrentEpoch => f.write_str("AdvanceCurrentEpoch"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -132,6 +138,9 @@ impl Operation {
|
||||
Operation::WriteRewardedSet => 175_000u64.into(),
|
||||
Operation::ClearRewardedSet => 175_000u64.into(),
|
||||
Operation::UpdateMixnetAddress => 80_000u64.into(),
|
||||
Operation::CheckpointMixnodes => 175_000u64.into(),
|
||||
Operation::ReconcileDelegations => 500_000u64.into(),
|
||||
Operation::AdvanceCurrentEpoch => 175_000u64.into(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,8 @@ use crate::nymd::cosmwasm_client::types::{
|
||||
use crate::nymd::error::NymdError;
|
||||
use crate::nymd::wallet::DirectSecp256k1HdWallet;
|
||||
use cosmrs::rpc::endpoint::broadcast;
|
||||
use cosmrs::rpc::{Error as TendermintRpcError, HttpClientUrl};
|
||||
use cosmrs::rpc::Error as TendermintRpcError;
|
||||
use cosmrs::rpc::HttpClientUrl;
|
||||
use cosmwasm_std::{Coin, Uint128};
|
||||
pub use fee::gas_price::GasPrice;
|
||||
use fee::helpers::Operation;
|
||||
@@ -82,7 +83,7 @@ impl NymdClient<QueryNymdClient> {
|
||||
|
||||
impl NymdClient<SigningNymdClient> {
|
||||
// maybe the wallet could be made into a generic, but for now, let's just have this one implementation
|
||||
pub fn connect_with_signer<U>(
|
||||
pub fn connect_with_signer<U: Clone>(
|
||||
network: config::defaults::all::Network,
|
||||
endpoint: U,
|
||||
mixnet_contract_address: Option<AccountId>,
|
||||
@@ -113,7 +114,7 @@ impl NymdClient<SigningNymdClient> {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn connect_with_mnemonic<U>(
|
||||
pub fn connect_with_mnemonic<U: Clone>(
|
||||
network: config::defaults::all::Network,
|
||||
endpoint: U,
|
||||
mixnet_contract_address: Option<AccountId>,
|
||||
@@ -388,6 +389,16 @@ impl<C> NymdClient<C> {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_epochs_in_interval(&self) -> Result<u64, NymdError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
{
|
||||
let request = QueryMsg::GetEpochsInInterval {};
|
||||
self.client
|
||||
.query_contract_smart(self.mixnet_contract_address()?, &request)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_circulating_supply(&self) -> Result<Uint128, NymdError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
@@ -496,7 +507,7 @@ impl<C> NymdClient<C> {
|
||||
pub async fn get_mix_delegations_paged(
|
||||
&self,
|
||||
mix_identity: IdentityKey,
|
||||
start_after: Option<String>,
|
||||
start_after: Option<(String, u64)>,
|
||||
page_limit: Option<u32>,
|
||||
) -> Result<PagedMixDelegationsResponse, NymdError>
|
||||
where
|
||||
@@ -515,7 +526,7 @@ impl<C> NymdClient<C> {
|
||||
/// Gets list of all mixnode delegations on particular page.
|
||||
pub async fn get_all_network_delegations_paged(
|
||||
&self,
|
||||
start_after: Option<(IdentityKey, String)>,
|
||||
start_after: Option<(IdentityKey, Vec<u8>, u64)>,
|
||||
page_limit: Option<u32>,
|
||||
) -> Result<PagedAllDelegationsResponse, NymdError>
|
||||
where
|
||||
@@ -1175,6 +1186,25 @@ impl<C> NymdClient<C> {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn advance_current_epoch(&self) -> Result<ExecuteResult, NymdError>
|
||||
where
|
||||
C: SigningCosmWasmClient + Sync,
|
||||
{
|
||||
let fee = self.operation_fee(Operation::AdvanceCurrentEpoch);
|
||||
|
||||
let req = ExecuteMsg::AdvanceCurrentEpoch {};
|
||||
self.client
|
||||
.execute(
|
||||
self.address(),
|
||||
self.mixnet_contract_address()?,
|
||||
&req,
|
||||
fee,
|
||||
"Advance current epoch",
|
||||
Vec::new(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn advance_current_interval(&self) -> Result<ExecuteResult, NymdError>
|
||||
where
|
||||
C: SigningCosmWasmClient + Sync,
|
||||
@@ -1194,6 +1224,44 @@ impl<C> NymdClient<C> {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn reconcile_delegations(&self) -> Result<ExecuteResult, NymdError>
|
||||
where
|
||||
C: SigningCosmWasmClient + Sync,
|
||||
{
|
||||
let fee = self.operation_fee(Operation::ReconcileDelegations);
|
||||
|
||||
let req = ExecuteMsg::ReconcileDelegations {};
|
||||
self.client
|
||||
.execute(
|
||||
self.address(),
|
||||
self.mixnet_contract_address()?,
|
||||
&req,
|
||||
fee,
|
||||
"Reconciling delegation events",
|
||||
Vec::new(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn checkpoint_mixnodes(&self) -> Result<ExecuteResult, NymdError>
|
||||
where
|
||||
C: SigningCosmWasmClient + Sync,
|
||||
{
|
||||
let fee = self.operation_fee(Operation::CheckpointMixnodes);
|
||||
|
||||
let req = ExecuteMsg::CheckpointMixnodes {};
|
||||
self.client
|
||||
.execute(
|
||||
self.address(),
|
||||
self.mixnet_contract_address()?,
|
||||
&req,
|
||||
fee,
|
||||
"Snapshotting mixnodes",
|
||||
Vec::new(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn write_rewarded_set(
|
||||
&self,
|
||||
rewarded_set: Vec<IdentityKey>,
|
||||
|
||||
@@ -83,8 +83,6 @@ impl VerifyCredentialBody {
|
||||
pub struct BlindSignRequestBody {
|
||||
#[getset(get = "pub")]
|
||||
blind_sign_request: BlindSignRequest,
|
||||
#[getset(get = "pub")]
|
||||
public_key: nymcoconut::PublicKey,
|
||||
public_attributes: Vec<String>,
|
||||
#[getset(get = "pub")]
|
||||
total_params: u32,
|
||||
@@ -93,13 +91,11 @@ pub struct BlindSignRequestBody {
|
||||
impl BlindSignRequestBody {
|
||||
pub fn new(
|
||||
blind_sign_request: &BlindSignRequest,
|
||||
public_key: &nymcoconut::PublicKey,
|
||||
public_attributes: &[Attribute],
|
||||
total_params: u32,
|
||||
) -> BlindSignRequestBody {
|
||||
BlindSignRequestBody {
|
||||
blind_sign_request: blind_sign_request.clone(),
|
||||
public_key: public_key.clone(),
|
||||
public_attributes: public_attributes
|
||||
.iter()
|
||||
.map(|attr| attr.to_bs58())
|
||||
|
||||
@@ -37,8 +37,12 @@ impl Delegation {
|
||||
}
|
||||
|
||||
// TODO: change that to use .joined_key() and return Vec<u8>
|
||||
pub fn storage_key(&self) -> (IdentityKey, Addr) {
|
||||
(self.node_identity(), self.owner())
|
||||
pub fn storage_key(&self) -> (IdentityKey, Vec<u8>, u64) {
|
||||
(
|
||||
self.node_identity(),
|
||||
self.owner().as_bytes().to_vec(),
|
||||
self.block_height(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn increment_amount(&mut self, amount: Uint128, at_height: Option<u64>) {
|
||||
@@ -78,14 +82,14 @@ impl Display for Delegation {
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
pub struct PagedMixDelegationsResponse {
|
||||
pub delegations: Vec<Delegation>,
|
||||
pub start_next_after: Option<String>,
|
||||
pub start_next_after: Option<(String, u64)>,
|
||||
}
|
||||
|
||||
impl PagedMixDelegationsResponse {
|
||||
pub fn new(delegations: Vec<Delegation>, start_next_after: Option<Addr>) -> Self {
|
||||
pub fn new(delegations: Vec<Delegation>, start_next_after: Option<(Addr, u64)>) -> Self {
|
||||
PagedMixDelegationsResponse {
|
||||
delegations,
|
||||
start_next_after: start_next_after.map(|s| s.to_string()),
|
||||
start_next_after: start_next_after.map(|(s, h)| (s.to_string(), h)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -108,17 +112,17 @@ impl PagedDelegatorDelegationsResponse {
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
pub struct PagedAllDelegationsResponse {
|
||||
pub delegations: Vec<Delegation>,
|
||||
pub start_next_after: Option<(IdentityKey, String)>,
|
||||
pub start_next_after: Option<(IdentityKey, Vec<u8>, u64)>,
|
||||
}
|
||||
|
||||
impl PagedAllDelegationsResponse {
|
||||
pub fn new(
|
||||
delegations: Vec<Delegation>,
|
||||
start_next_after: Option<(IdentityKey, Addr)>,
|
||||
start_next_after: Option<(IdentityKey, Vec<u8>, u64)>,
|
||||
) -> Self {
|
||||
PagedAllDelegationsResponse {
|
||||
delegations,
|
||||
start_next_after: start_next_after.map(|(id, addr)| (id, addr.to_string())),
|
||||
start_next_after: start_next_after.map(|(id, addr, height)| (id, addr, height)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,4 +6,11 @@ pub enum MixnetContractError {
|
||||
OverflowError(#[from] cosmwasm_std::OverflowError),
|
||||
#[error("reward_blockstamp field not set, set_reward_blockstamp must be called before attempting to issue rewards")]
|
||||
BlockstampNotSet,
|
||||
#[error("{source}")]
|
||||
TryFromIntError {
|
||||
#[from]
|
||||
source: std::num::TryFromIntError,
|
||||
},
|
||||
#[error("Error casting from U128")]
|
||||
CastError,
|
||||
}
|
||||
|
||||
@@ -2,14 +2,16 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::mixnode::NodeRewardResult;
|
||||
use crate::{ContractStateParams, Delegation, IdentityKeyRef, Interval, Layer};
|
||||
use crate::{ContractStateParams, IdentityKeyRef, Interval, Layer};
|
||||
use cosmwasm_std::{Addr, Coin, Event, Uint128};
|
||||
|
||||
pub use contracts_common::events::*;
|
||||
|
||||
// FIXME: This should becoma an Enum
|
||||
// event types
|
||||
pub const DELEGATION_EVENT_TYPE: &str = "delegation";
|
||||
pub const PENDING_DELEGATION_EVENT_TYPE: &str = "pending_delegation";
|
||||
pub const UNDELEGATION_EVENT_TYPE: &str = "undelegation";
|
||||
pub const PENDING_UNDELEGATION_EVENT_TYPE: &str = "pending_undelegation";
|
||||
pub const GATEWAY_BONDING_EVENT_TYPE: &str = "gateway_bonding";
|
||||
pub const GATEWAY_UNBONDING_EVENT_TYPE: &str = "gateway_unbonding";
|
||||
pub const MIXNODE_BONDING_EVENT_TYPE: &str = "mixnode_bonding";
|
||||
@@ -19,6 +21,10 @@ pub const OPERATOR_REWARDING_EVENT_TYPE: &str = "mix_rewarding";
|
||||
pub const MIX_DELEGATORS_REWARDING_EVENT_TYPE: &str = "mix_delegators_rewarding";
|
||||
pub const CHANGE_REWARDED_SET_EVENT_TYPE: &str = "change_rewarded_set";
|
||||
pub const ADVANCE_INTERVAL_EVENT_TYPE: &str = "advance_interval";
|
||||
pub const ADVANCE_EPOCH_EVENT_TYPE: &str = "advance_epoch";
|
||||
pub const COMPOUND_DELEGATOR_REWARD_EVENT_TYPE: &str = "compound_delegator_reward";
|
||||
pub const COMPOUND_OPERATOR_REWARD_EVENT_TYPE: &str = "compound_operator_reward";
|
||||
pub const SNAPSHOT_MIXNODES_EVENT: &str = "snapshot_mixnodes";
|
||||
|
||||
// attributes that are used in multiple places
|
||||
pub const OWNER_KEY: &str = "owner";
|
||||
@@ -70,6 +76,14 @@ pub const NODES_IN_REWARDED_SET_KEY: &str = "nodes_in_rewarded_set";
|
||||
pub const CURRENT_INTERVAL_ID_KEY: &str = "current_interval";
|
||||
|
||||
pub const NEW_CURRENT_INTERVAL_KEY: &str = "new_current_interval";
|
||||
pub const NEW_CURRENT_EPOCH_KEY: &str = "new_current_epoch";
|
||||
pub const BLOCK_HEIGHT_KEY: &str = "block_height";
|
||||
pub const CHECKPOINT_MIXNODES_EVENT: &str = "checkpoint_mixnodes";
|
||||
|
||||
pub fn new_checkpoint_mixnodes_event(block_height: u64) -> Event {
|
||||
Event::new(CHECKPOINT_MIXNODES_EVENT)
|
||||
.add_attribute(BLOCK_HEIGHT_KEY, format!("{}", block_height))
|
||||
}
|
||||
|
||||
pub fn new_delegation_event(
|
||||
delegator: &Addr,
|
||||
@@ -89,11 +103,55 @@ pub fn new_delegation_event(
|
||||
.add_attribute(DELEGATION_TARGET_KEY, mix_identity)
|
||||
}
|
||||
|
||||
pub fn new_pending_delegation_event(
|
||||
delegator: &Addr,
|
||||
proxy: &Option<Addr>,
|
||||
amount: &Coin,
|
||||
mix_identity: IdentityKeyRef<'_>,
|
||||
) -> Event {
|
||||
let mut event =
|
||||
Event::new(PENDING_DELEGATION_EVENT_TYPE).add_attribute(DELEGATOR_KEY, delegator);
|
||||
|
||||
if let Some(proxy) = proxy {
|
||||
event = event.add_attribute(PROXY_KEY, proxy)
|
||||
}
|
||||
|
||||
// coin implements Display trait and we use that implementation here
|
||||
event
|
||||
.add_attribute(AMOUNT_KEY, amount.to_string())
|
||||
.add_attribute(DELEGATION_TARGET_KEY, mix_identity)
|
||||
}
|
||||
|
||||
pub fn new_compound_operator_reward_event(owner: &Addr, amount: Uint128) -> Event {
|
||||
let event = Event::new(COMPOUND_OPERATOR_REWARD_EVENT_TYPE).add_attribute(OWNER_KEY, owner);
|
||||
event.add_attribute(AMOUNT_KEY, amount.to_string())
|
||||
}
|
||||
|
||||
pub fn new_compound_delegator_reward_event(
|
||||
delegator: &Addr,
|
||||
proxy: &Option<Addr>,
|
||||
amount: Uint128,
|
||||
mix_identity: IdentityKeyRef<'_>,
|
||||
) -> Event {
|
||||
let mut event =
|
||||
Event::new(COMPOUND_DELEGATOR_REWARD_EVENT_TYPE).add_attribute(DELEGATOR_KEY, delegator);
|
||||
|
||||
if let Some(proxy) = proxy {
|
||||
event = event.add_attribute(PROXY_KEY, proxy)
|
||||
}
|
||||
|
||||
// coin implements Display trait and we use that implementation here
|
||||
event
|
||||
.add_attribute(AMOUNT_KEY, amount.to_string())
|
||||
.add_attribute(DELEGATION_TARGET_KEY, mix_identity)
|
||||
.add_attribute(DELEGATOR_KEY, delegator)
|
||||
}
|
||||
|
||||
pub fn new_undelegation_event(
|
||||
delegator: &Addr,
|
||||
proxy: &Option<Addr>,
|
||||
old_delegation: &Delegation,
|
||||
mix_identity: IdentityKeyRef<'_>,
|
||||
amount: Uint128,
|
||||
) -> Event {
|
||||
let mut event = Event::new(UNDELEGATION_EVENT_TYPE).add_attribute(DELEGATOR_KEY, delegator);
|
||||
|
||||
@@ -103,14 +161,26 @@ pub fn new_undelegation_event(
|
||||
|
||||
// coin implements Display trait and we use that implementation here
|
||||
event
|
||||
.add_attribute(AMOUNT_KEY, old_delegation.amount.to_string())
|
||||
.add_attribute(
|
||||
DELEGATION_HEIGHT_KEY,
|
||||
old_delegation.block_height.to_string(),
|
||||
)
|
||||
.add_attribute(AMOUNT_KEY, amount.to_string())
|
||||
.add_attribute(DELEGATION_TARGET_KEY, mix_identity)
|
||||
}
|
||||
|
||||
pub fn new_pending_undelegation_event(
|
||||
delegator: &Addr,
|
||||
proxy: &Option<Addr>,
|
||||
mix_identity: IdentityKeyRef<'_>,
|
||||
) -> Event {
|
||||
let mut event =
|
||||
Event::new(PENDING_UNDELEGATION_EVENT_TYPE).add_attribute(DELEGATOR_KEY, delegator);
|
||||
|
||||
if let Some(proxy) = proxy {
|
||||
event = event.add_attribute(PROXY_KEY, proxy)
|
||||
}
|
||||
|
||||
// coin implements Display trait and we use that implementation here
|
||||
event.add_attribute(DELEGATION_TARGET_KEY, mix_identity)
|
||||
}
|
||||
|
||||
pub fn new_gateway_bonding_event(
|
||||
owner: &Addr,
|
||||
proxy: &Option<Addr>,
|
||||
@@ -280,9 +350,6 @@ pub fn new_mix_operator_rewarding_event(
|
||||
node_reward_result: NodeRewardResult,
|
||||
node_pledge: Uint128,
|
||||
node_delegation: Uint128,
|
||||
operator_reward: Uint128,
|
||||
delegation_rewards_distributed: Uint128,
|
||||
further_delegations: bool,
|
||||
) -> Event {
|
||||
Event::new(OPERATOR_REWARDING_EVENT_TYPE)
|
||||
.add_attribute(INTERVAL_ID_KEY, interval_id.to_string())
|
||||
@@ -295,15 +362,6 @@ pub fn new_mix_operator_rewarding_event(
|
||||
)
|
||||
.add_attribute(LAMBDA_KEY, node_reward_result.lambda().to_string())
|
||||
.add_attribute(SIGMA_KEY, node_reward_result.sigma().to_string())
|
||||
.add_attribute(OPERATOR_REWARD_KEY, operator_reward)
|
||||
.add_attribute(
|
||||
DISTRIBUTED_DELEGATION_REWARDS_KEY,
|
||||
delegation_rewards_distributed,
|
||||
)
|
||||
.add_attribute(
|
||||
FURTHER_DELEGATIONS_TO_REWARD_KEY,
|
||||
further_delegations.to_string(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_mix_delegators_rewarding_event(
|
||||
@@ -343,3 +401,7 @@ pub fn new_advance_interval_event(interval: Interval) -> Event {
|
||||
Event::new(ADVANCE_INTERVAL_EVENT_TYPE)
|
||||
.add_attribute(NEW_CURRENT_INTERVAL_KEY, interval.to_string())
|
||||
}
|
||||
|
||||
pub fn new_advance_epoch_event(interval: Interval) -> Event {
|
||||
Event::new(ADVANCE_EPOCH_EVENT_TYPE).add_attribute(NEW_CURRENT_EPOCH_KEY, interval.to_string())
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ impl Interval {
|
||||
|
||||
/// Returns the next interval.
|
||||
#[must_use]
|
||||
pub fn next_interval(&self) -> Self {
|
||||
pub fn next(&self) -> Self {
|
||||
Interval {
|
||||
id: self.id + 1,
|
||||
start: self.end(),
|
||||
@@ -81,7 +81,7 @@ impl Interval {
|
||||
}
|
||||
|
||||
/// Returns the last interval.
|
||||
pub fn previous_interval(&self) -> Option<Self> {
|
||||
pub fn previous(&self) -> Option<Self> {
|
||||
if self.id > 0 {
|
||||
Some(Interval {
|
||||
id: self.id - 1,
|
||||
@@ -125,14 +125,14 @@ impl Interval {
|
||||
if candidate.contains(now) {
|
||||
return Some(candidate);
|
||||
}
|
||||
candidate = candidate.next_interval();
|
||||
candidate = candidate.next();
|
||||
}
|
||||
} else {
|
||||
loop {
|
||||
if candidate.contains(now) {
|
||||
return Some(candidate);
|
||||
}
|
||||
candidate = candidate.previous_interval()?;
|
||||
candidate = candidate.previous()?;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -151,14 +151,14 @@ impl Interval {
|
||||
if candidate.contains_timestamp(now_unix) {
|
||||
return Some(candidate);
|
||||
}
|
||||
candidate = candidate.next_interval();
|
||||
candidate = candidate.next();
|
||||
}
|
||||
} else {
|
||||
loop {
|
||||
if candidate.contains_timestamp(now_unix) {
|
||||
return Some(candidate);
|
||||
}
|
||||
candidate = candidate.previous_interval()?;
|
||||
candidate = candidate.previous()?;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -239,7 +239,7 @@ mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn previous_interval() {
|
||||
fn previous() {
|
||||
let interval = Interval {
|
||||
id: 1,
|
||||
start: time::macros::datetime!(2021-08-23 12:00 UTC),
|
||||
@@ -250,18 +250,18 @@ mod tests {
|
||||
start: time::macros::datetime!(2021-08-22 12:00 UTC),
|
||||
length: Duration::from_secs(24 * 60 * 60),
|
||||
};
|
||||
assert_eq!(expected, interval.previous_interval().unwrap());
|
||||
assert_eq!(expected, interval.previous().unwrap());
|
||||
|
||||
let genesis_interval = Interval {
|
||||
id: 0,
|
||||
start: time::macros::datetime!(2021-08-23 12:00 UTC),
|
||||
length: Duration::from_secs(24 * 60 * 60),
|
||||
};
|
||||
assert!(genesis_interval.previous_interval().is_none());
|
||||
assert!(genesis_interval.previous().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn next_interval() {
|
||||
fn next() {
|
||||
let interval = Interval {
|
||||
id: 0,
|
||||
start: time::macros::datetime!(2021-08-23 12:00 UTC),
|
||||
@@ -273,7 +273,7 @@ mod tests {
|
||||
length: Duration::from_secs(24 * 60 * 60),
|
||||
};
|
||||
|
||||
assert_eq!(expected, interval.next_interval())
|
||||
assert_eq!(expected, interval.next())
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -291,8 +291,8 @@ mod tests {
|
||||
let in_the_midle = interval.start + Duration::from_secs(interval.length.as_secs() / 2);
|
||||
assert!(interval.contains(in_the_midle));
|
||||
|
||||
assert!(!interval.contains(interval.next_interval().end()));
|
||||
assert!(!interval.contains(interval.previous_interval().unwrap().start()));
|
||||
assert!(!interval.contains(interval.next().end()));
|
||||
assert!(!interval.contains(interval.previous().unwrap().start()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -305,10 +305,7 @@ mod tests {
|
||||
|
||||
// interval just before
|
||||
let fake_now = first_interval.start - Duration::from_secs(123);
|
||||
assert_eq!(
|
||||
first_interval.previous_interval(),
|
||||
first_interval.current(fake_now)
|
||||
);
|
||||
assert_eq!(first_interval.previous(), first_interval.current(fake_now));
|
||||
|
||||
// this interval (start boundary)
|
||||
assert_eq!(
|
||||
@@ -329,7 +326,7 @@ mod tests {
|
||||
// next interval
|
||||
let fake_now = first_interval.end() + Duration::from_secs(123);
|
||||
assert_eq!(
|
||||
first_interval.next_interval(),
|
||||
first_interval.next(),
|
||||
first_interval.current(fake_now).unwrap()
|
||||
);
|
||||
|
||||
@@ -340,11 +337,11 @@ mod tests {
|
||||
- first_interval.length;
|
||||
assert_eq!(
|
||||
first_interval
|
||||
.previous_interval()
|
||||
.previous()
|
||||
.unwrap()
|
||||
.previous_interval()
|
||||
.previous()
|
||||
.unwrap()
|
||||
.previous_interval()
|
||||
.previous()
|
||||
.unwrap(),
|
||||
first_interval.current(fake_now).unwrap()
|
||||
);
|
||||
@@ -355,10 +352,7 @@ mod tests {
|
||||
+ first_interval.length
|
||||
+ first_interval.length;
|
||||
assert_eq!(
|
||||
first_interval
|
||||
.next_interval()
|
||||
.next_interval()
|
||||
.next_interval(),
|
||||
first_interval.next().next().next(),
|
||||
first_interval.current(fake_now).unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ mod gateway;
|
||||
mod interval;
|
||||
pub mod mixnode;
|
||||
mod msg;
|
||||
pub mod reward_params;
|
||||
mod types;
|
||||
|
||||
pub const MIXNODE_DELEGATORS_PAGE_LIMIT: usize = 250;
|
||||
@@ -24,3 +25,9 @@ pub use mixnode::{
|
||||
};
|
||||
pub use msg::*;
|
||||
pub use types::*;
|
||||
|
||||
pub type U128 = fixed::types::U75F53;
|
||||
|
||||
fixed::const_fixed_from_int! {
|
||||
const ONE: U128 = 1;
|
||||
}
|
||||
|
||||
@@ -1,23 +1,19 @@
|
||||
// due to code generated by JsonSchema
|
||||
#![allow(clippy::field_reassign_with_default)]
|
||||
|
||||
use crate::{IdentityKey, SphinxKey};
|
||||
use crate::error::MixnetContractError;
|
||||
use crate::reward_params::RewardParams;
|
||||
use crate::{Delegation, IdentityKey, SphinxKey};
|
||||
use crate::{ONE, U128};
|
||||
use az::CheckedCast;
|
||||
use cosmwasm_std::{coin, Addr, Coin, Uint128};
|
||||
use log::error;
|
||||
use network_defaults::DEFAULT_OPERATOR_INTERVAL_COST;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_repr::{Deserialize_repr, Serialize_repr};
|
||||
use std::cmp::Ordering;
|
||||
use std::fmt::Display;
|
||||
|
||||
type U128 = fixed::types::U75F53; // u128 with 18 significant digits
|
||||
|
||||
fixed::const_fixed_from_int! {
|
||||
const ONE: U128 = 1;
|
||||
}
|
||||
|
||||
#[cfg_attr(test, derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
test,
|
||||
@@ -26,7 +22,7 @@ fixed::const_fixed_from_int! {
|
||||
export_to = "../../../nym-wallet/src/types/rust/rewardedsetnodestatus.ts"
|
||||
)
|
||||
)]
|
||||
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, PartialOrd, Serialize, JsonSchema)]
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Serialize, JsonSchema, PartialEq)]
|
||||
pub enum RewardedSetNodeStatus {
|
||||
Active,
|
||||
Standby,
|
||||
@@ -38,6 +34,52 @@ impl RewardedSetNodeStatus {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)]
|
||||
pub enum DelegationEvent {
|
||||
Delegate(Delegation),
|
||||
Undelegate(PendingUndelegate),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)]
|
||||
pub struct PendingUndelegate {
|
||||
mix_identity: IdentityKey,
|
||||
delegate: Addr,
|
||||
proxy: Option<Addr>,
|
||||
block_height: u64,
|
||||
}
|
||||
|
||||
impl PendingUndelegate {
|
||||
pub fn new(
|
||||
mix_identity: IdentityKey,
|
||||
delegate: Addr,
|
||||
proxy: Option<Addr>,
|
||||
block_height: u64,
|
||||
) -> Self {
|
||||
Self {
|
||||
mix_identity,
|
||||
delegate,
|
||||
proxy,
|
||||
block_height,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mix_identity(&self) -> IdentityKey {
|
||||
self.mix_identity.clone()
|
||||
}
|
||||
|
||||
pub fn delegate(&self) -> Addr {
|
||||
self.delegate.clone()
|
||||
}
|
||||
|
||||
pub fn proxy(&self) -> Option<Addr> {
|
||||
self.proxy.clone()
|
||||
}
|
||||
|
||||
pub fn block_height(&self) -> u64 {
|
||||
self.block_height
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(test, derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
test,
|
||||
@@ -87,112 +129,6 @@ impl From<Layer> for String {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, JsonSchema, PartialEq, Serialize, Deserialize, Copy)]
|
||||
pub struct NodeRewardParams {
|
||||
period_reward_pool: Uint128,
|
||||
rewarded_set_size: Uint128,
|
||||
active_set_size: Uint128,
|
||||
reward_blockstamp: u64,
|
||||
circulating_supply: Uint128,
|
||||
uptime: Uint128,
|
||||
sybil_resistance_percent: u8,
|
||||
in_active_set: bool,
|
||||
active_set_work_factor: u8,
|
||||
}
|
||||
|
||||
impl NodeRewardParams {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
period_reward_pool: u128,
|
||||
rewarded_set_size: u128,
|
||||
active_set_size: u128,
|
||||
reward_blockstamp: u64,
|
||||
circulating_supply: u128,
|
||||
uptime: u128,
|
||||
sybil_resistance_percent: u8,
|
||||
in_active_set: bool,
|
||||
active_set_work_factor: u8,
|
||||
) -> NodeRewardParams {
|
||||
NodeRewardParams {
|
||||
period_reward_pool: Uint128::new(period_reward_pool),
|
||||
rewarded_set_size: Uint128::new(rewarded_set_size),
|
||||
active_set_size: Uint128::new(active_set_size),
|
||||
reward_blockstamp,
|
||||
circulating_supply: Uint128::new(circulating_supply),
|
||||
uptime: Uint128::new(uptime),
|
||||
sybil_resistance_percent,
|
||||
in_active_set,
|
||||
active_set_work_factor,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn omega(&self) -> U128 {
|
||||
// As per keybase://chat/nymtech#tokeneconomics/1179
|
||||
let denom = self.active_set_work_factor() * U128::from_num(self.rewarded_set_size())
|
||||
- (self.active_set_work_factor() - ONE) * U128::from_num(self.idle_nodes().u128());
|
||||
|
||||
if self.in_active_set() {
|
||||
// work_active = factor / (factor * self.network.k[month] - (factor - 1) * idle_nodes)
|
||||
self.active_set_work_factor() / denom * self.rewarded_set_size()
|
||||
} else {
|
||||
// work_idle = 1 / (factor * self.network.k[month] - (factor - 1) * idle_nodes)
|
||||
ONE / denom * self.rewarded_set_size()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn idle_nodes(&self) -> Uint128 {
|
||||
self.rewarded_set_size - self.active_set_size
|
||||
}
|
||||
|
||||
pub fn active_set_work_factor(&self) -> U128 {
|
||||
U128::from_num(self.active_set_work_factor)
|
||||
}
|
||||
|
||||
pub fn in_active_set(&self) -> bool {
|
||||
self.in_active_set
|
||||
}
|
||||
|
||||
pub fn performance(&self) -> U128 {
|
||||
U128::from_num(self.uptime.u128()) / U128::from_num(100)
|
||||
}
|
||||
|
||||
pub fn operator_cost(&self) -> U128 {
|
||||
U128::from_num(self.uptime.u128() / 100u128 * DEFAULT_OPERATOR_INTERVAL_COST as u128)
|
||||
}
|
||||
|
||||
pub fn set_reward_blockstamp(&mut self, blockstamp: u64) {
|
||||
self.reward_blockstamp = blockstamp;
|
||||
}
|
||||
|
||||
pub fn period_reward_pool(&self) -> u128 {
|
||||
self.period_reward_pool.u128()
|
||||
}
|
||||
|
||||
pub fn rewarded_set_size(&self) -> u128 {
|
||||
self.rewarded_set_size.u128()
|
||||
}
|
||||
|
||||
pub fn circulating_supply(&self) -> u128 {
|
||||
self.circulating_supply.u128()
|
||||
}
|
||||
|
||||
pub fn reward_blockstamp(&self) -> u64 {
|
||||
self.reward_blockstamp
|
||||
}
|
||||
|
||||
pub fn uptime(&self) -> u128 {
|
||||
self.uptime.u128()
|
||||
}
|
||||
|
||||
pub fn one_over_k(&self) -> U128 {
|
||||
ONE / U128::from_num(self.rewarded_set_size.u128())
|
||||
}
|
||||
|
||||
pub fn alpha(&self) -> U128 {
|
||||
U128::from_num(self.sybil_resistance_percent) / U128::from_num(100)
|
||||
}
|
||||
}
|
||||
|
||||
// cosmwasm's limited serde doesn't work with U128 directly
|
||||
#[allow(non_snake_case)]
|
||||
pub mod fixed_U128_as_string {
|
||||
@@ -226,7 +162,7 @@ pub mod fixed_U128_as_string {
|
||||
// everything required to reward delegator of given mixnode
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct DelegatorRewardParams {
|
||||
node_reward_params: NodeRewardParams,
|
||||
reward_params: RewardParams,
|
||||
|
||||
// to be completely honest I don't understand all consequences of using `#[schemars(with = "String")]`
|
||||
// for U128 here, but it seems that CosmWasm is using the same attribute for their Uint128
|
||||
@@ -242,19 +178,24 @@ pub struct DelegatorRewardParams {
|
||||
}
|
||||
|
||||
impl DelegatorRewardParams {
|
||||
pub fn new(mixnode_bond: &MixNodeBond, node_reward_params: NodeRewardParams) -> Self {
|
||||
pub fn new(
|
||||
sigma: U128,
|
||||
profit_margin: U128,
|
||||
node_profit: U128,
|
||||
reward_params: RewardParams,
|
||||
) -> Self {
|
||||
DelegatorRewardParams {
|
||||
sigma: mixnode_bond.sigma(&node_reward_params),
|
||||
profit_margin: mixnode_bond.profit_margin(),
|
||||
node_profit: mixnode_bond.node_profit(&node_reward_params),
|
||||
node_reward_params,
|
||||
sigma,
|
||||
profit_margin,
|
||||
node_profit,
|
||||
reward_params,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn determine_delegation_reward(&self, delegation_amount: Uint128) -> u128 {
|
||||
// change all values into their fixed representations
|
||||
let delegation_amount = U128::from_num(delegation_amount.u128());
|
||||
let circulating_supply = U128::from_num(self.node_reward_params.circulating_supply());
|
||||
let circulating_supply = U128::from_num(self.reward_params.circulating_supply());
|
||||
|
||||
let scaled_delegation_amount = delegation_amount / circulating_supply;
|
||||
let delegator_reward =
|
||||
@@ -272,8 +213,56 @@ impl DelegatorRewardParams {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn node_reward_params(&self) -> &NodeRewardParams {
|
||||
&self.node_reward_params
|
||||
pub fn node_reward_params(&self) -> RewardParams {
|
||||
self.reward_params
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, JsonSchema, PartialEq, Serialize, Deserialize, Copy)]
|
||||
pub struct StoredNodeRewardResult {
|
||||
reward: Uint128,
|
||||
lambda: Uint128,
|
||||
sigma: Uint128,
|
||||
}
|
||||
|
||||
impl StoredNodeRewardResult {
|
||||
pub fn reward(&self) -> Uint128 {
|
||||
self.reward
|
||||
}
|
||||
|
||||
pub fn lambda(&self) -> Uint128 {
|
||||
self.lambda
|
||||
}
|
||||
|
||||
pub fn sigma(&self) -> Uint128 {
|
||||
self.sigma
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<NodeRewardResult> for StoredNodeRewardResult {
|
||||
type Error = MixnetContractError;
|
||||
|
||||
fn try_from(node_reward_result: NodeRewardResult) -> Result<Self, Self::Error> {
|
||||
Ok(StoredNodeRewardResult {
|
||||
reward: Uint128::new(
|
||||
node_reward_result
|
||||
.reward()
|
||||
.checked_cast()
|
||||
.ok_or(MixnetContractError::CastError)?,
|
||||
),
|
||||
lambda: Uint128::new(
|
||||
node_reward_result
|
||||
.lambda()
|
||||
.checked_cast()
|
||||
.ok_or(MixnetContractError::CastError)?,
|
||||
),
|
||||
sigma: Uint128::new(
|
||||
node_reward_result
|
||||
.sigma()
|
||||
.checked_cast()
|
||||
.ok_or(MixnetContractError::CastError)?,
|
||||
),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -307,6 +296,7 @@ pub struct MixNodeBond {
|
||||
pub block_height: u64,
|
||||
pub mix_node: MixNode,
|
||||
pub proxy: Option<Addr>,
|
||||
pub accumulated_rewards: Uint128,
|
||||
}
|
||||
|
||||
impl MixNodeBond {
|
||||
@@ -326,6 +316,7 @@ impl MixNodeBond {
|
||||
block_height,
|
||||
mix_node,
|
||||
proxy,
|
||||
accumulated_rewards: Uint128::zero(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -349,11 +340,16 @@ impl MixNodeBond {
|
||||
&self.mix_node
|
||||
}
|
||||
|
||||
// Takes into account accumulated rewards as well as current pledge and delegation amounts
|
||||
pub fn total_bond(&self) -> Option<u128> {
|
||||
if self.pledge_amount.denom != self.total_delegation.denom {
|
||||
None
|
||||
} else {
|
||||
Some(self.pledge_amount.amount.u128() + self.total_delegation.amount.u128())
|
||||
Some(
|
||||
self.pledge_amount.amount.u128()
|
||||
+ self.total_delegation.amount.u128()
|
||||
+ self.accumulated_rewards.u128(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -366,6 +362,10 @@ impl MixNodeBond {
|
||||
* U128::from_num(rewarded_set_size)
|
||||
}
|
||||
|
||||
// TODO: There is an effect here when adding accumulted rewards to the total bond, ie accumulated rewards will not
|
||||
// affect lambda, but will affect sigma, in turn over time, if left unclaimed operator rewards will not compound, but
|
||||
// behave similarly to delegations.
|
||||
// The question is should this be taken into account when calculating operator rewards?
|
||||
pub fn pledge_to_circulating_supply(&self, circulating_supply: u128) -> U128 {
|
||||
U128::from_num(self.pledge_amount().amount.u128()) / U128::from_num(circulating_supply)
|
||||
}
|
||||
@@ -375,26 +375,46 @@ impl MixNodeBond {
|
||||
/ U128::from_num(circulating_supply)
|
||||
}
|
||||
|
||||
pub fn lambda(&self, params: &NodeRewardParams) -> U128 {
|
||||
pub fn lambda(&self, params: &RewardParams) -> U128 {
|
||||
// Ratio of a bond to the token circulating supply
|
||||
let pledge_to_circulating_supply_ratio =
|
||||
self.pledge_to_circulating_supply(params.circulating_supply());
|
||||
pledge_to_circulating_supply_ratio.min(params.one_over_k())
|
||||
}
|
||||
|
||||
pub fn sigma(&self, params: &NodeRewardParams) -> U128 {
|
||||
pub fn sigma(&self, params: &RewardParams) -> U128 {
|
||||
// Ratio of a delegation to the the token circulating supply
|
||||
let total_bond_to_circulating_supply_ratio =
|
||||
self.total_bond_to_circulating_supply(params.circulating_supply());
|
||||
total_bond_to_circulating_supply_ratio.min(params.one_over_k())
|
||||
}
|
||||
|
||||
pub fn reward(&self, params: &NodeRewardParams) -> NodeRewardResult {
|
||||
pub fn estimate_reward(
|
||||
&self,
|
||||
params: &RewardParams,
|
||||
) -> Result<(u64, u64, u64), MixnetContractError> {
|
||||
let total_node_reward = self.reward(params);
|
||||
let operator_reward = self.operator_reward(params);
|
||||
// TODO: This overestimates the reward by a lot, it should take a Uint128 and return estiamte for that
|
||||
let delegators_reward = self.reward_delegation(self.total_delegation().amount, params);
|
||||
|
||||
Ok((
|
||||
total_node_reward
|
||||
.reward()
|
||||
.checked_to_num::<u128>()
|
||||
.unwrap_or_default()
|
||||
.try_into()?,
|
||||
operator_reward.try_into()?,
|
||||
delegators_reward.try_into()?,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn reward(&self, params: &RewardParams) -> NodeRewardResult {
|
||||
let lambda = self.lambda(params);
|
||||
let sigma = self.sigma(params);
|
||||
|
||||
let reward = params.performance()
|
||||
* params.period_reward_pool()
|
||||
* params.epoch_reward_pool()
|
||||
* (sigma * params.omega()
|
||||
+ params.alpha() * lambda * sigma * params.rewarded_set_size())
|
||||
/ (ONE + params.alpha());
|
||||
@@ -406,22 +426,22 @@ impl MixNodeBond {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn node_profit(&self, params: &NodeRewardParams) -> U128 {
|
||||
if self.reward(params).reward() < params.operator_cost() {
|
||||
U128::from_num(0)
|
||||
pub fn node_profit(&self, params: &RewardParams) -> U128 {
|
||||
if self.reward(params).reward() < params.node.operator_cost() {
|
||||
U128::from_num(0u128)
|
||||
} else {
|
||||
self.reward(params).reward() - params.operator_cost()
|
||||
self.reward(params).reward() - params.node.operator_cost()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn operator_reward(&self, params: &NodeRewardParams) -> u128 {
|
||||
pub fn operator_reward(&self, params: &RewardParams) -> u128 {
|
||||
let reward = self.reward(params);
|
||||
let profit = if reward.reward < params.operator_cost() {
|
||||
U128::from_num(0)
|
||||
let profit = if reward.reward < params.node.operator_cost() {
|
||||
U128::from_num(0u128)
|
||||
} else {
|
||||
reward.reward - params.operator_cost()
|
||||
reward.reward - params.node.operator_cost()
|
||||
};
|
||||
let operator_base_reward = reward.reward.min(params.operator_cost());
|
||||
let operator_base_reward = reward.reward.min(params.node.operator_cost());
|
||||
let operator_reward = (self.profit_margin()
|
||||
+ (ONE - self.profit_margin()) * reward.lambda / reward.sigma)
|
||||
* profit;
|
||||
@@ -440,7 +460,7 @@ impl MixNodeBond {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sigma_ratio(&self, params: &NodeRewardParams) -> U128 {
|
||||
pub fn sigma_ratio(&self, params: &RewardParams) -> U128 {
|
||||
if self.total_bond_to_circulating_supply(params.circulating_supply()) < params.one_over_k()
|
||||
{
|
||||
self.total_bond_to_circulating_supply(params.circulating_supply())
|
||||
@@ -449,8 +469,13 @@ impl MixNodeBond {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reward_delegation(&self, delegation_amount: Uint128, params: &NodeRewardParams) -> u128 {
|
||||
let reward_params = DelegatorRewardParams::new(self, *params);
|
||||
pub fn reward_delegation(&self, delegation_amount: Uint128, params: &RewardParams) -> u128 {
|
||||
let reward_params = DelegatorRewardParams::new(
|
||||
self.sigma(params),
|
||||
self.profit_margin(),
|
||||
self.node_profit(params),
|
||||
params.to_owned(),
|
||||
);
|
||||
reward_params.determine_delegation_reward(delegation_amount)
|
||||
}
|
||||
}
|
||||
@@ -589,6 +614,7 @@ mod tests {
|
||||
block_height: 100,
|
||||
mix_node: mixnode_fixture(),
|
||||
proxy: None,
|
||||
accumulated_rewards: Uint128::zero(),
|
||||
};
|
||||
|
||||
let mix2 = MixNodeBond {
|
||||
@@ -599,6 +625,7 @@ mod tests {
|
||||
block_height: 120,
|
||||
mix_node: mixnode_fixture(),
|
||||
proxy: None,
|
||||
accumulated_rewards: Uint128::zero(),
|
||||
};
|
||||
|
||||
let mix3 = MixNodeBond {
|
||||
@@ -609,6 +636,7 @@ mod tests {
|
||||
block_height: 120,
|
||||
mix_node: mixnode_fixture(),
|
||||
proxy: None,
|
||||
accumulated_rewards: Uint128::zero(),
|
||||
};
|
||||
|
||||
let mix4 = MixNodeBond {
|
||||
@@ -619,6 +647,7 @@ mod tests {
|
||||
block_height: 120,
|
||||
mix_node: mixnode_fixture(),
|
||||
proxy: None,
|
||||
accumulated_rewards: Uint128::zero(),
|
||||
};
|
||||
|
||||
let mix5 = MixNodeBond {
|
||||
@@ -629,6 +658,7 @@ mod tests {
|
||||
block_height: 120,
|
||||
mix_node: mixnode_fixture(),
|
||||
proxy: None,
|
||||
accumulated_rewards: Uint128::zero(),
|
||||
};
|
||||
|
||||
// summary:
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::mixnode::NodeRewardParams;
|
||||
use crate::reward_params::NodeRewardParams;
|
||||
use crate::ContractStateParams;
|
||||
use crate::{Gateway, IdentityKey, MixNode};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
type BlockHeight = u64;
|
||||
type DelegateAddress = Vec<u8>;
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
pub struct InstantiateMsg {
|
||||
pub rewarding_validator_address: String,
|
||||
@@ -15,6 +18,19 @@ pub struct InstantiateMsg {
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ExecuteMsg {
|
||||
ReconcileDelegations {},
|
||||
CheckpointMixnodes {},
|
||||
CompoundOperatorRewardOnBehalf {
|
||||
owner: String,
|
||||
},
|
||||
CompoundDelegatorRewardOnBehalf {
|
||||
owner: String,
|
||||
mix_identity: IdentityKey,
|
||||
},
|
||||
CompoundOperatorReward {},
|
||||
CompoundDelegatorReward {
|
||||
mix_identity: IdentityKey,
|
||||
},
|
||||
BondMixnode {
|
||||
mix_node: MixNode,
|
||||
owner_signature: String,
|
||||
@@ -50,11 +66,11 @@ pub enum ExecuteMsg {
|
||||
// id of the current rewarding interval
|
||||
interval_id: u32,
|
||||
},
|
||||
RewardNextMixDelegators {
|
||||
mix_identity: IdentityKey,
|
||||
// id of the current rewarding interval
|
||||
interval_id: u32,
|
||||
},
|
||||
// RewardNextMixDelegators {
|
||||
// mix_identity: IdentityKey,
|
||||
// // id of the current rewarding interval
|
||||
// interval_id: u32,
|
||||
// },
|
||||
DelegateToMixnodeOnBehalf {
|
||||
mix_identity: IdentityKey,
|
||||
delegate: String,
|
||||
@@ -84,6 +100,7 @@ pub enum ExecuteMsg {
|
||||
expected_active_set_size: u32,
|
||||
},
|
||||
AdvanceCurrentInterval {},
|
||||
AdvanceCurrentEpoch {},
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
@@ -108,7 +125,7 @@ pub enum QueryMsg {
|
||||
// gets all [paged] delegations in the entire network
|
||||
// TODO: do we even want that?
|
||||
GetAllNetworkDelegations {
|
||||
start_after: Option<(IdentityKey, String)>,
|
||||
start_after: Option<(IdentityKey, DelegateAddress, BlockHeight)>,
|
||||
limit: Option<u32>,
|
||||
},
|
||||
// gets all [paged] delegations associated with particular mixnode
|
||||
@@ -116,7 +133,7 @@ pub enum QueryMsg {
|
||||
mix_identity: IdentityKey,
|
||||
// since `start_after` is user-provided input, we can't use `Addr` as we
|
||||
// can't guarantee it's validated.
|
||||
start_after: Option<String>,
|
||||
start_after: Option<(String, u64)>,
|
||||
limit: Option<u32>,
|
||||
},
|
||||
// gets all [paged] delegations associated with particular delegator
|
||||
@@ -154,6 +171,7 @@ pub enum QueryMsg {
|
||||
GetCurrentRewardedSetHeight {},
|
||||
GetCurrentInterval {},
|
||||
GetRewardedSetRefreshBlocks {},
|
||||
GetEpochsInInterval {},
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
|
||||
@@ -0,0 +1,260 @@
|
||||
use crate::{error::MixnetContractError, mixnode::StoredNodeRewardResult, ONE, U128};
|
||||
use az::CheckedCast;
|
||||
use cosmwasm_std::Uint128;
|
||||
use network_defaults::DEFAULT_OPERATOR_INTERVAL_COST;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, JsonSchema, PartialEq, Serialize, Deserialize, Copy)]
|
||||
pub struct NodeEpochRewards {
|
||||
params: NodeRewardParams,
|
||||
result: StoredNodeRewardResult,
|
||||
epoch_id: u32,
|
||||
}
|
||||
|
||||
impl NodeEpochRewards {
|
||||
pub fn new(params: NodeRewardParams, result: StoredNodeRewardResult, epoch_id: u32) -> Self {
|
||||
Self {
|
||||
params,
|
||||
result,
|
||||
epoch_id,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn epoch_id(&self) -> u32 {
|
||||
self.epoch_id
|
||||
}
|
||||
|
||||
pub fn sigma(&self) -> Uint128 {
|
||||
self.result.sigma()
|
||||
}
|
||||
|
||||
pub fn lambda(&self) -> Uint128 {
|
||||
self.result.lambda()
|
||||
}
|
||||
|
||||
pub fn params(&self) -> NodeRewardParams {
|
||||
self.params
|
||||
}
|
||||
|
||||
pub fn reward(&self) -> Uint128 {
|
||||
self.result.reward()
|
||||
}
|
||||
|
||||
pub fn operator_cost(&self) -> U128 {
|
||||
U128::from_num(self.params.uptime.u128() / 100u128 * DEFAULT_OPERATOR_INTERVAL_COST as u128)
|
||||
}
|
||||
|
||||
pub fn node_profit(&self) -> U128 {
|
||||
let reward = U128::from_num(self.reward().u128());
|
||||
if reward < self.operator_cost() {
|
||||
U128::from_num(0u128)
|
||||
} else {
|
||||
reward - self.operator_cost()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn operator_reward(&self, profit_margin: U128) -> Result<Uint128, MixnetContractError> {
|
||||
let reward = self.node_profit();
|
||||
let operator_base_reward = reward.min(self.operator_cost());
|
||||
let operator_reward = (profit_margin
|
||||
+ (ONE - profit_margin) * U128::from_num(self.lambda().u128())
|
||||
/ U128::from_num(self.sigma().u128()))
|
||||
* reward;
|
||||
|
||||
let reward = (operator_reward + operator_base_reward).max(U128::from_num(0u128));
|
||||
|
||||
if let Some(int_reward) = reward.checked_cast() {
|
||||
Ok(Uint128::new(int_reward))
|
||||
} else {
|
||||
Err(MixnetContractError::CastError)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn delegation_reward(
|
||||
&self,
|
||||
delegation_amount: Uint128,
|
||||
profit_margin: U128,
|
||||
epoch_reward_params: EpochRewardParams,
|
||||
) -> Result<Uint128, MixnetContractError> {
|
||||
// change all values into their fixed representations
|
||||
let delegation_amount = U128::from_num(delegation_amount.u128());
|
||||
let circulating_supply = U128::from_num(epoch_reward_params.circulating_supply());
|
||||
|
||||
let scaled_delegation_amount = delegation_amount / circulating_supply;
|
||||
let delegator_reward = (ONE - profit_margin) * scaled_delegation_amount
|
||||
/ U128::from_num(self.sigma().u128())
|
||||
* self.node_profit();
|
||||
|
||||
let reward = delegator_reward.max(U128::ZERO);
|
||||
if let Some(int_reward) = reward.checked_cast() {
|
||||
Ok(Uint128::new(int_reward))
|
||||
} else {
|
||||
Err(MixnetContractError::CastError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, JsonSchema, PartialEq, Serialize, Deserialize, Copy)]
|
||||
pub struct EpochRewardParams {
|
||||
epoch_reward_pool: Uint128,
|
||||
rewarded_set_size: Uint128,
|
||||
active_set_size: Uint128,
|
||||
circulating_supply: Uint128,
|
||||
sybil_resistance_percent: u8,
|
||||
active_set_work_factor: u8,
|
||||
}
|
||||
|
||||
impl EpochRewardParams {
|
||||
pub fn new(
|
||||
epoch_reward_pool: u128,
|
||||
rewarded_set_size: u128,
|
||||
active_set_size: u128,
|
||||
circulating_supply: u128,
|
||||
sybil_resistance_percent: u8,
|
||||
active_set_work_factor: u8,
|
||||
) -> EpochRewardParams {
|
||||
EpochRewardParams {
|
||||
epoch_reward_pool: Uint128::new(epoch_reward_pool),
|
||||
rewarded_set_size: Uint128::new(rewarded_set_size),
|
||||
active_set_size: Uint128::new(active_set_size),
|
||||
circulating_supply: Uint128::new(circulating_supply),
|
||||
sybil_resistance_percent,
|
||||
active_set_work_factor,
|
||||
}
|
||||
}
|
||||
|
||||
// technically it's identical to what would have been derived with a Default implementation,
|
||||
// however, I prefer to be explicit about it, as a `Default::default` value makes no sense
|
||||
// apart from the `ValidatorCacheInner` context, where this value is not going to be touched anyway
|
||||
// (it's guarded behind an `initialised` flag)
|
||||
pub fn new_empty() -> Self {
|
||||
EpochRewardParams {
|
||||
epoch_reward_pool: Uint128::new(0),
|
||||
circulating_supply: Uint128::new(0),
|
||||
sybil_resistance_percent: 0,
|
||||
rewarded_set_size: Uint128::new(0),
|
||||
active_set_size: Uint128::new(0),
|
||||
active_set_work_factor: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rewarded_set_size(&self) -> u128 {
|
||||
self.rewarded_set_size.u128()
|
||||
}
|
||||
|
||||
pub fn active_set_size(&self) -> u128 {
|
||||
self.active_set_size.u128()
|
||||
}
|
||||
|
||||
pub fn circulating_supply(&self) -> u128 {
|
||||
self.circulating_supply.u128()
|
||||
}
|
||||
|
||||
pub fn epoch_reward_pool(&self) -> u128 {
|
||||
self.epoch_reward_pool.u128()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, JsonSchema, PartialEq, Serialize, Deserialize, Copy)]
|
||||
pub struct NodeRewardParams {
|
||||
reward_blockstamp: u64,
|
||||
uptime: Uint128,
|
||||
in_active_set: bool,
|
||||
}
|
||||
|
||||
impl NodeRewardParams {
|
||||
pub fn new(reward_blockstamp: u64, uptime: u128, in_active_set: bool) -> NodeRewardParams {
|
||||
NodeRewardParams {
|
||||
reward_blockstamp,
|
||||
uptime: Uint128::new(uptime),
|
||||
in_active_set,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn operator_cost(&self) -> U128 {
|
||||
U128::from_num(self.uptime.u128() / 100u128 * DEFAULT_OPERATOR_INTERVAL_COST as u128)
|
||||
}
|
||||
|
||||
pub fn uptime(&self) -> u128 {
|
||||
self.uptime.u128()
|
||||
}
|
||||
|
||||
pub fn set_reward_blockstamp(&mut self, blockstamp: u64) {
|
||||
self.reward_blockstamp = blockstamp;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, JsonSchema, PartialEq, Serialize, Deserialize, Copy)]
|
||||
pub struct RewardParams {
|
||||
pub epoch: EpochRewardParams,
|
||||
pub node: NodeRewardParams,
|
||||
}
|
||||
|
||||
impl RewardParams {
|
||||
pub fn new(epoch: EpochRewardParams, node: NodeRewardParams) -> RewardParams {
|
||||
RewardParams { epoch, node }
|
||||
}
|
||||
|
||||
pub fn omega(&self) -> U128 {
|
||||
// As per keybase://chat/nymtech#tokeneconomics/1179
|
||||
let denom = self.active_set_work_factor() * U128::from_num(self.rewarded_set_size())
|
||||
- (self.active_set_work_factor() - ONE) * U128::from_num(self.idle_nodes().u128());
|
||||
|
||||
if self.in_active_set() {
|
||||
// work_active = factor / (factor * self.network.k[month] - (factor - 1) * idle_nodes)
|
||||
self.active_set_work_factor() / denom * self.rewarded_set_size()
|
||||
} else {
|
||||
// work_idle = 1 / (factor * self.network.k[month] - (factor - 1) * idle_nodes)
|
||||
ONE / denom * self.rewarded_set_size()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn idle_nodes(&self) -> Uint128 {
|
||||
self.epoch.rewarded_set_size - self.epoch.active_set_size
|
||||
}
|
||||
|
||||
pub fn active_set_work_factor(&self) -> U128 {
|
||||
U128::from_num(self.epoch.active_set_work_factor)
|
||||
}
|
||||
|
||||
pub fn in_active_set(&self) -> bool {
|
||||
self.node.in_active_set
|
||||
}
|
||||
|
||||
pub fn performance(&self) -> U128 {
|
||||
U128::from_num(self.node.uptime.u128()) / U128::from_num(100)
|
||||
}
|
||||
|
||||
pub fn set_reward_blockstamp(&mut self, blockstamp: u64) {
|
||||
self.node.reward_blockstamp = blockstamp;
|
||||
}
|
||||
|
||||
pub fn epoch_reward_pool(&self) -> u128 {
|
||||
self.epoch.epoch_reward_pool.u128()
|
||||
}
|
||||
|
||||
pub fn rewarded_set_size(&self) -> u128 {
|
||||
self.epoch.rewarded_set_size.u128()
|
||||
}
|
||||
|
||||
pub fn circulating_supply(&self) -> u128 {
|
||||
self.epoch.circulating_supply.u128()
|
||||
}
|
||||
|
||||
pub fn reward_blockstamp(&self) -> u64 {
|
||||
self.node.reward_blockstamp
|
||||
}
|
||||
|
||||
pub fn uptime(&self) -> u128 {
|
||||
self.node.uptime.u128()
|
||||
}
|
||||
|
||||
pub fn one_over_k(&self) -> U128 {
|
||||
ONE / U128::from_num(self.epoch.rewarded_set_size.u128())
|
||||
}
|
||||
|
||||
pub fn alpha(&self) -> U128 {
|
||||
U128::from_num(self.epoch.sybil_resistance_percent) / U128::from_num(100)
|
||||
}
|
||||
}
|
||||
@@ -73,8 +73,7 @@ impl Display for ContractStateParams {
|
||||
|
||||
#[derive(Default, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct RewardingResult {
|
||||
pub operator_reward: Uint128,
|
||||
pub total_delegator_reward: Uint128,
|
||||
pub node_reward: Uint128,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
@@ -124,7 +123,7 @@ pub type IdentityKey = String;
|
||||
pub type IdentityKeyRef<'a> = &'a str;
|
||||
pub type SphinxKey = String;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, JsonSchema)]
|
||||
pub struct PagedRewardedSetResponse {
|
||||
pub identities: Vec<(IdentityKey, RewardedSetNodeStatus)>,
|
||||
pub start_next_after: Option<IdentityKey>,
|
||||
|
||||
@@ -6,6 +6,7 @@ edition = "2021"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
bls12_381 = { version = "0.5", default-features = false, features = ["pairings", "alloc", "experimental"] }
|
||||
thiserror = "1.0"
|
||||
url = "2.2"
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use bls12_381::Scalar;
|
||||
use coconut_interface::{
|
||||
aggregate_signature_shares, aggregate_verification_keys, prepare_blind_sign,
|
||||
prove_bandwidth_credential, Attribute, BlindSignRequest, BlindSignRequestBody, Credential,
|
||||
ElGamalKeyPair, Parameters, Signature, SignatureShare, VerificationKey,
|
||||
Parameters, Signature, SignatureShare, VerificationKey,
|
||||
};
|
||||
use url::Url;
|
||||
|
||||
@@ -64,14 +65,13 @@ async fn obtain_partial_credential(
|
||||
params: &Parameters,
|
||||
public_attributes: &[Attribute],
|
||||
private_attributes: &[Attribute],
|
||||
pedersen_commitments_openings: &[Scalar],
|
||||
blind_sign_request: &BlindSignRequest,
|
||||
client: &validator_client::ApiClient,
|
||||
validator_vk: &VerificationKey,
|
||||
blind_sign_request: &BlindSignRequest,
|
||||
elgamal_keypair: &ElGamalKeyPair,
|
||||
) -> Result<Signature, Error> {
|
||||
let blind_sign_request_body = BlindSignRequestBody::new(
|
||||
blind_sign_request,
|
||||
elgamal_keypair.public_key(),
|
||||
public_attributes,
|
||||
(public_attributes.len() + private_attributes.len()) as u32,
|
||||
);
|
||||
@@ -83,11 +83,11 @@ async fn obtain_partial_credential(
|
||||
|
||||
let unblinded_signature = blinded_signature.unblind(
|
||||
params,
|
||||
elgamal_keypair.private_key(),
|
||||
validator_vk,
|
||||
private_attributes,
|
||||
public_attributes,
|
||||
&blind_sign_request.get_commitment_hash(),
|
||||
&*pedersen_commitments_openings,
|
||||
)?;
|
||||
|
||||
Ok(unblinded_signature)
|
||||
@@ -110,22 +110,17 @@ pub async fn obtain_aggregate_signature(
|
||||
let validator_partial_vk = client.get_coconut_verification_key().await?;
|
||||
validators_partial_vks.push(validator_partial_vk.key.clone());
|
||||
|
||||
let elgamal_keypair = coconut_interface::elgamal_keygen(params);
|
||||
let blind_sign_request = prepare_blind_sign(
|
||||
params,
|
||||
&elgamal_keypair,
|
||||
private_attributes,
|
||||
public_attributes,
|
||||
)?;
|
||||
let (pedersen_commitments_openings, blind_sign_request) =
|
||||
prepare_blind_sign(params, private_attributes, public_attributes)?;
|
||||
|
||||
let first = obtain_partial_credential(
|
||||
params,
|
||||
public_attributes,
|
||||
private_attributes,
|
||||
&pedersen_commitments_openings,
|
||||
&blind_sign_request,
|
||||
&client,
|
||||
&validator_partial_vk.key,
|
||||
&blind_sign_request,
|
||||
&elgamal_keypair,
|
||||
)
|
||||
.await?;
|
||||
shares.push(SignatureShare::new(first, 1));
|
||||
@@ -138,10 +133,10 @@ pub async fn obtain_aggregate_signature(
|
||||
params,
|
||||
public_attributes,
|
||||
private_attributes,
|
||||
&pedersen_commitments_openings,
|
||||
&blind_sign_request,
|
||||
&client,
|
||||
&validator_partial_vk.key,
|
||||
&blind_sign_request,
|
||||
&elgamal_keypair,
|
||||
)
|
||||
.await?;
|
||||
let share = SignatureShare::new(signature, (id + 1) as u64);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{collections::HashMap, fmt, ops::Deref, str::FromStr};
|
||||
use std::{collections::HashMap, fmt, str::FromStr};
|
||||
|
||||
use crate::{
|
||||
DefaultNetworkDetails, ValidatorDetails, MAINNET_DEFAULTS, QA_DEFAULTS, SANDBOX_DEFAULTS,
|
||||
@@ -98,7 +98,7 @@ pub struct NetworkDetails {
|
||||
}
|
||||
|
||||
impl From<&DefaultNetworkDetails<'_>> for NetworkDetails {
|
||||
fn from(details: &DefaultNetworkDetails) -> Self {
|
||||
fn from(details: &DefaultNetworkDetails<'_>) -> Self {
|
||||
NetworkDetails {
|
||||
bech32_prefix: details.bech32_prefix.into(),
|
||||
denom: details.denom.into(),
|
||||
@@ -118,20 +118,12 @@ pub struct SupportedNetworks {
|
||||
|
||||
impl SupportedNetworks {
|
||||
pub fn new(support: Vec<Network>) -> Self {
|
||||
let mut networks = HashMap::new();
|
||||
|
||||
for network in support {
|
||||
match network {
|
||||
Network::MAINNET => {
|
||||
networks.insert(Network::MAINNET, MAINNET_DEFAULTS.deref().into())
|
||||
SupportedNetworks {
|
||||
networks: support
|
||||
.into_iter()
|
||||
.map(|n| (n, n.details().into()))
|
||||
.collect(),
|
||||
}
|
||||
Network::SANDBOX => {
|
||||
networks.insert(Network::SANDBOX, SANDBOX_DEFAULTS.deref().into())
|
||||
}
|
||||
Network::QA => networks.insert(Network::QA, QA_DEFAULTS.deref().into()),
|
||||
};
|
||||
}
|
||||
SupportedNetworks { networks }
|
||||
}
|
||||
|
||||
pub fn bech32_prefix(&self, network: Network) -> Option<&str> {
|
||||
|
||||
@@ -179,7 +179,8 @@ pub const VALIDATOR_API_VERSION: &str = "v1";
|
||||
// REWARDING
|
||||
|
||||
/// We'll be assuming a few more things, profit margin and cost function. Since we don't have relialable package measurement, we'll be using uptime. We'll also set the value of 1 Nym to 1 $, to be able to translate interval costs to Nyms. We'll also assume a cost of 40$ per interval(month), converting that to Nym at our 1$ rate translates to 40_000_000 uNyms
|
||||
pub const DEFAULT_OPERATOR_INTERVAL_COST: u64 = 40_000_000; // 40$/(30 days) at 1 Nym == 1$
|
||||
// pub const DEFAULT_OPERATOR_INTERVAL_COST: u64 = 40_000_000; // 40$/(30 days) at 1 Nym == 1$
|
||||
pub const DEFAULT_OPERATOR_INTERVAL_COST: u64 = 55_556; // 40$/1hr at 1 Nym == 1$
|
||||
|
||||
// TODO: is there a way to get this from the chain
|
||||
pub const TOTAL_SUPPLY: u128 = 1_000_000_000_000_000;
|
||||
|
||||
@@ -32,9 +32,9 @@ doc-comment = "0.3"
|
||||
[dev-dependencies.bincode]
|
||||
version = "1"
|
||||
|
||||
#[[bench]]
|
||||
#name = "benchmarks"
|
||||
#harness = false
|
||||
[[bench]]
|
||||
name = "benchmarks"
|
||||
harness = false
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
@@ -0,0 +1,332 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use bls12_381::{multi_miller_loop, G1Affine, G1Projective, G2Affine, G2Prepared, Scalar};
|
||||
use criterion::{criterion_group, criterion_main, Criterion};
|
||||
use ff::Field;
|
||||
use group::{Curve, Group};
|
||||
use nymcoconut::{
|
||||
aggregate_signature_shares, aggregate_verification_keys, blind_sign, elgamal_keygen,
|
||||
prepare_blind_sign, prove_bandwidth_credential, setup, ttp_keygen, verify_credential,
|
||||
Attribute, BlindedSignature, Parameters, Signature, SignatureShare, VerificationKey,
|
||||
};
|
||||
use rand::seq::SliceRandom;
|
||||
use std::ops::Neg;
|
||||
use std::time::Duration;
|
||||
|
||||
fn double_pairing(g11: &G1Affine, g21: &G2Affine, g12: &G1Affine, g22: &G2Affine) {
|
||||
let gt1 = bls12_381::pairing(&g11, &g21);
|
||||
let gt2 = bls12_381::pairing(&g12, &g22);
|
||||
assert_eq!(gt1, gt2)
|
||||
}
|
||||
|
||||
fn multi_miller_pairing_affine(g11: &G1Affine, g21: &G2Affine, g12: &G1Affine, g22: &G2Affine) {
|
||||
let miller_loop_result = multi_miller_loop(&[
|
||||
(g11, &G2Prepared::from(*g21)),
|
||||
(&g12.neg(), &G2Prepared::from(*g22)),
|
||||
]);
|
||||
assert!(bool::from(
|
||||
miller_loop_result.final_exponentiation().is_identity()
|
||||
))
|
||||
}
|
||||
|
||||
fn multi_miller_pairing_with_prepared(
|
||||
g11: &G1Affine,
|
||||
g21: &G2Prepared,
|
||||
g12: &G1Affine,
|
||||
g22: &G2Prepared,
|
||||
) {
|
||||
let miller_loop_result = multi_miller_loop(&[(g11, &g21), (&g12.neg(), &g22)]);
|
||||
assert!(bool::from(
|
||||
miller_loop_result.final_exponentiation().is_identity()
|
||||
))
|
||||
}
|
||||
|
||||
// the case of being able to prepare G2 generator
|
||||
fn multi_miller_pairing_with_semi_prepared(
|
||||
g11: &G1Affine,
|
||||
g21: &G2Affine,
|
||||
g12: &G1Affine,
|
||||
g22: &G2Prepared,
|
||||
) {
|
||||
let miller_loop_result =
|
||||
multi_miller_loop(&[(g11, &G2Prepared::from(*g21)), (&g12.neg(), &g22)]);
|
||||
assert!(bool::from(
|
||||
miller_loop_result.final_exponentiation().is_identity()
|
||||
))
|
||||
}
|
||||
|
||||
fn unblind_and_aggregate(
|
||||
params: &Parameters,
|
||||
blinded_signatures: &[BlindedSignature],
|
||||
partial_verification_keys: &[VerificationKey],
|
||||
private_attributes: &[Attribute],
|
||||
public_attributes: &[Attribute],
|
||||
commitment_hash: &G1Projective,
|
||||
pedersen_commitments_openings: &[Scalar],
|
||||
verification_key: &VerificationKey,
|
||||
) -> Signature {
|
||||
// Unblind all partial signatures
|
||||
let unblinded_signatures: Vec<Signature> = blinded_signatures
|
||||
.iter()
|
||||
.zip(partial_verification_keys.iter())
|
||||
.map(|(signature, partial_verification_key)| {
|
||||
signature
|
||||
.unblind(
|
||||
¶ms,
|
||||
&partial_verification_key,
|
||||
&private_attributes,
|
||||
&public_attributes,
|
||||
&commitment_hash,
|
||||
&pedersen_commitments_openings,
|
||||
)
|
||||
.unwrap()
|
||||
})
|
||||
.collect();
|
||||
|
||||
let unblinded_signature_shares: Vec<SignatureShare> = unblinded_signatures
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(idx, signature)| SignatureShare::new(*signature, (idx + 1) as u64))
|
||||
.collect();
|
||||
|
||||
let mut attributes = vec![];
|
||||
attributes.extend_from_slice(private_attributes);
|
||||
attributes.extend_from_slice(public_attributes);
|
||||
aggregate_signature_shares(
|
||||
¶ms,
|
||||
&verification_key,
|
||||
&attributes,
|
||||
&unblinded_signature_shares,
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
struct BenchCase {
|
||||
num_authorities: u64,
|
||||
threshold_p: f32,
|
||||
num_public_attrs: u32,
|
||||
num_private_attrs: u32,
|
||||
}
|
||||
|
||||
impl BenchCase {
|
||||
fn threshold(&self) -> u64 {
|
||||
(self.num_authorities as f32 * self.threshold_p).round() as u64
|
||||
}
|
||||
|
||||
fn num_attrs(&self) -> u32 {
|
||||
self.num_public_attrs + self.num_private_attrs
|
||||
}
|
||||
}
|
||||
|
||||
fn bench_pairings(c: &mut Criterion) {
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
let g1 = G1Affine::generator();
|
||||
let g2 = G2Affine::generator();
|
||||
let r = Scalar::random(&mut rng);
|
||||
let s = Scalar::random(&mut rng);
|
||||
|
||||
let g11 = (g1 * r).to_affine();
|
||||
let g21 = (g2 * s).to_affine();
|
||||
let g21_prep = G2Prepared::from(g21);
|
||||
|
||||
let g12 = (g1 * s).to_affine();
|
||||
let g22 = (g2 * r).to_affine();
|
||||
let g22_prep = G2Prepared::from(g22);
|
||||
|
||||
c.bench_function("double pairing", |b| {
|
||||
b.iter(|| double_pairing(&g11, &g21, &g12, &g22))
|
||||
});
|
||||
|
||||
c.bench_function("multi miller in affine", |b| {
|
||||
b.iter(|| multi_miller_pairing_affine(&g11, &g21, &g12, &g22))
|
||||
});
|
||||
|
||||
c.bench_function("multi miller with prepared g2", |b| {
|
||||
b.iter(|| multi_miller_pairing_with_prepared(&g11, &g21_prep, &g12, &g22_prep))
|
||||
});
|
||||
|
||||
c.bench_function("multi miller with semi-prepared g2", |b| {
|
||||
b.iter(|| multi_miller_pairing_with_semi_prepared(&g11, &g21, &g12, &g22_prep))
|
||||
});
|
||||
}
|
||||
|
||||
fn bench_coconut(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("benchmark-coconut");
|
||||
group.measurement_time(Duration::from_secs(100));
|
||||
let case = BenchCase {
|
||||
num_authorities: 100,
|
||||
threshold_p: 0.7,
|
||||
num_public_attrs: 2,
|
||||
num_private_attrs: 2,
|
||||
};
|
||||
|
||||
let params = setup((case.num_public_attrs + case.num_private_attrs)).unwrap();
|
||||
|
||||
let public_attributes = params.n_random_scalars(case.num_public_attrs as usize);
|
||||
let serial_number = params.random_scalar();
|
||||
let binding_number = params.random_scalar();
|
||||
let private_attributes = vec![serial_number, binding_number];
|
||||
|
||||
let elgamal_keypair = elgamal_keygen(¶ms);
|
||||
|
||||
// The prepare blind sign is performed by the user
|
||||
let (pedersen_commitments_openings, blind_sign_request) =
|
||||
prepare_blind_sign(¶ms, &private_attributes, &public_attributes).unwrap();
|
||||
|
||||
// CLIENT BENCHMARK: Data needed to ask for a credential
|
||||
// Let's benchmark the operations the client has to perform
|
||||
// to ask for a credential
|
||||
group.bench_function(
|
||||
&format!(
|
||||
"[Client] prepare_blind_sign_{}_authorities_{}_attributes_{}_threshold",
|
||||
case.num_authorities,
|
||||
case.num_attrs(),
|
||||
case.threshold_p,
|
||||
),
|
||||
|b| {
|
||||
b.iter(|| prepare_blind_sign(¶ms, &private_attributes, &public_attributes).unwrap())
|
||||
},
|
||||
);
|
||||
|
||||
// keys for the validators
|
||||
let coconut_keypairs = ttp_keygen(¶ms, case.threshold(), case.num_authorities).unwrap();
|
||||
|
||||
// VALIDATOR BENCHMARK: Issue partial credential
|
||||
// we pick only one key pair, as we want to validate how much does it
|
||||
// take for a single validator to issue a partial credential
|
||||
let mut rng = rand::thread_rng();
|
||||
let keypair = coconut_keypairs.choose(&mut rng).unwrap();
|
||||
|
||||
group.bench_function(
|
||||
&format!(
|
||||
"[Validator] compute_single_blind_sign_for_credential_with_{}_attributes",
|
||||
case.num_attrs(),
|
||||
),
|
||||
|b| {
|
||||
b.iter(|| {
|
||||
blind_sign(
|
||||
¶ms,
|
||||
&keypair.secret_key(),
|
||||
&blind_sign_request,
|
||||
&public_attributes,
|
||||
)
|
||||
.unwrap()
|
||||
})
|
||||
},
|
||||
);
|
||||
|
||||
// computing all partial credentials
|
||||
// NOTE: in reality, each validator computes only single signature
|
||||
let mut blinded_signatures = Vec::new();
|
||||
for keypair in coconut_keypairs.iter() {
|
||||
let blinded_signature = blind_sign(
|
||||
¶ms,
|
||||
&keypair.secret_key(),
|
||||
&blind_sign_request,
|
||||
&public_attributes,
|
||||
)
|
||||
.unwrap();
|
||||
blinded_signatures.push(blinded_signature)
|
||||
}
|
||||
|
||||
let verification_keys: Vec<VerificationKey> = coconut_keypairs
|
||||
.iter()
|
||||
.map(|keypair| keypair.verification_key())
|
||||
.collect();
|
||||
|
||||
// Lets bench worse case, ie aggregating all
|
||||
let indices: Vec<u64> = (1..=case.num_authorities).collect();
|
||||
// aggregate verification keys
|
||||
let aggr_verification_key =
|
||||
aggregate_verification_keys(&verification_keys, Some(&indices)).unwrap();
|
||||
|
||||
// CLIENT OPERATION: Unblind partial singatures and aggregate into single signature
|
||||
let aggregated_signature = unblind_and_aggregate(
|
||||
¶ms,
|
||||
&blinded_signatures,
|
||||
&verification_keys,
|
||||
&private_attributes,
|
||||
&public_attributes,
|
||||
&blind_sign_request.get_commitment_hash(),
|
||||
&pedersen_commitments_openings,
|
||||
&aggr_verification_key,
|
||||
);
|
||||
|
||||
// CLIENT BENCHMARK: aggregate all partial credentials
|
||||
group.bench_function(
|
||||
&format!(
|
||||
"[Client] unblind_and_aggregate_partial_credentials_{}_authorities_{}_attributes_{}_threshold",
|
||||
case.num_authorities,
|
||||
case.num_attrs(),
|
||||
case.threshold_p,
|
||||
),
|
||||
|b| {
|
||||
b.iter(|| {
|
||||
unblind_and_aggregate(
|
||||
¶ms,
|
||||
&blinded_signatures,
|
||||
&verification_keys,
|
||||
&private_attributes,
|
||||
&public_attributes,
|
||||
&blind_sign_request.get_commitment_hash(),
|
||||
&pedersen_commitments_openings,
|
||||
&aggr_verification_key)
|
||||
})
|
||||
},
|
||||
);
|
||||
|
||||
// CLIENT OPERATION: Randomize credentials and generate any cryptographic material to verify them
|
||||
let theta = prove_bandwidth_credential(
|
||||
¶ms,
|
||||
&aggr_verification_key,
|
||||
&aggregated_signature,
|
||||
serial_number,
|
||||
binding_number,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// CLIENT BENCHMARK
|
||||
group.bench_function(
|
||||
&format!(
|
||||
"[Client] randomize_and_prove_credential_{}_authorities_{}_attributes_{}_threshold",
|
||||
case.num_authorities,
|
||||
case.num_attrs(),
|
||||
case.threshold_p,
|
||||
),
|
||||
|b| {
|
||||
b.iter(|| {
|
||||
prove_bandwidth_credential(
|
||||
¶ms,
|
||||
&aggr_verification_key,
|
||||
&aggregated_signature,
|
||||
serial_number,
|
||||
binding_number,
|
||||
)
|
||||
.unwrap()
|
||||
})
|
||||
},
|
||||
);
|
||||
|
||||
// VERIFIER OPERATION
|
||||
// Verify credentials
|
||||
verify_credential(¶ms, &aggr_verification_key, &theta, &public_attributes);
|
||||
|
||||
// VERIFICATION BENCHMARK
|
||||
group.bench_function(
|
||||
&format!(
|
||||
"[Verifier] verify_credentials_{}_authorities_{}_attributes_{}_threshold",
|
||||
case.num_authorities,
|
||||
case.num_attrs(),
|
||||
case.threshold_p,
|
||||
),
|
||||
|b| {
|
||||
b.iter(|| {
|
||||
verify_credential(¶ms, &aggr_verification_key, &theta, &public_attributes)
|
||||
})
|
||||
},
|
||||
);
|
||||
}
|
||||
criterion_group!(benches, bench_coconut);
|
||||
criterion_main!(benches);
|
||||
@@ -13,6 +13,7 @@ use crate::error::{CoconutError, Result};
|
||||
use crate::scheme::setup::Parameters;
|
||||
use crate::traits::{Base58, Bytable};
|
||||
use crate::utils::{try_deserialize_g1_projective, try_deserialize_scalar};
|
||||
use crate::Attribute;
|
||||
|
||||
/// Type alias for the ephemeral key generated during ElGamal encryption
|
||||
pub type EphemeralKey = Scalar;
|
||||
@@ -222,6 +223,18 @@ pub fn elgamal_keygen(params: &Parameters) -> ElGamalKeyPair {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compute_attribute_encryption(
|
||||
params: &Parameters,
|
||||
private_attributes: &[Attribute],
|
||||
pub_key: &PublicKey,
|
||||
commitment_hash: G1Projective,
|
||||
) -> (Vec<Ciphertext>, Vec<EphemeralKey>) {
|
||||
private_attributes
|
||||
.iter()
|
||||
.map(|m| pub_key.encrypt(params, &commitment_hash, m))
|
||||
.unzip()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
+101
-157
@@ -13,12 +13,11 @@ use group::GroupEncoding;
|
||||
use itertools::izip;
|
||||
use sha2::Sha256;
|
||||
|
||||
use crate::elgamal::Ciphertext;
|
||||
use crate::error::{CoconutError, Result};
|
||||
use crate::scheme::setup::Parameters;
|
||||
use crate::scheme::VerificationKey;
|
||||
use crate::utils::{hash_g1, try_deserialize_scalar, try_deserialize_scalar_vec};
|
||||
use crate::{elgamal, Attribute, ElGamalKeyPair};
|
||||
use crate::Attribute;
|
||||
|
||||
// as per the reference python implementation
|
||||
type ChallengeDigest = Sha256;
|
||||
@@ -28,8 +27,7 @@ type ChallengeDigest = Sha256;
|
||||
pub struct ProofCmCs {
|
||||
challenge: Scalar,
|
||||
response_opening: Scalar,
|
||||
response_private_elgamal_key: Scalar,
|
||||
response_keys: Vec<Scalar>,
|
||||
response_openings: Vec<Scalar>,
|
||||
response_attributes: Vec<Scalar>,
|
||||
}
|
||||
|
||||
@@ -88,12 +86,11 @@ impl ProofCmCs {
|
||||
/// using the Fiat-Shamir heuristic.
|
||||
pub(crate) fn construct(
|
||||
params: &Parameters,
|
||||
elgamal_keypair: &ElGamalKeyPair,
|
||||
ephemeral_keys: &[elgamal::EphemeralKey],
|
||||
commitment: &G1Projective,
|
||||
commitment_opening: &Scalar,
|
||||
commitments: &[G1Projective],
|
||||
pedersen_commitments_openings: &[Scalar],
|
||||
private_attributes: &[Attribute],
|
||||
priv_attributes_ciphertexts: &[Ciphertext],
|
||||
) -> Self {
|
||||
// note: this is only called from `prepare_blind_sign` that already checks
|
||||
// whether private attributes are non-empty and whether we don't have too many
|
||||
@@ -101,10 +98,9 @@ impl ProofCmCs {
|
||||
// we also know, due to the single call place, that ephemeral_keys.len() == private_attributes.len()
|
||||
|
||||
// witness creation
|
||||
|
||||
let witness_commitment_opening = params.random_scalar();
|
||||
let witness_private_elgamal_key = params.random_scalar();
|
||||
let witness_keys = params.n_random_scalars(ephemeral_keys.len());
|
||||
let witness_pedersen_commitments_openings =
|
||||
params.n_random_scalars(pedersen_commitments_openings.len());
|
||||
let witness_attributes = params.n_random_scalars(private_attributes.len());
|
||||
|
||||
// recompute h
|
||||
@@ -118,22 +114,6 @@ impl ProofCmCs {
|
||||
let g1 = params.gen1();
|
||||
|
||||
// compute commitments
|
||||
let commitment_private_key_elgamal = g1 * witness_private_elgamal_key;
|
||||
|
||||
// Aw[i] = (wk[i] * g1)
|
||||
let commitment_keys1_bytes = witness_keys
|
||||
.iter()
|
||||
.map(|wk_i| g1 * wk_i)
|
||||
.map(|witness| witness.to_bytes())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Bw[i] = (wm[i] * h) + (wk[i] * gamma)
|
||||
let commitment_keys2_bytes = witness_keys
|
||||
.iter()
|
||||
.zip(witness_attributes.iter())
|
||||
.map(|(wk_i, wm_i)| elgamal_keypair.public_key() * wk_i + h * wm_i)
|
||||
.map(|witness| witness.to_bytes())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// zkp commitment for the attributes commitment cm
|
||||
// Ccm = (wr * g1) + (wm[0] * hs[0]) + ... + (wm[i] * hs[i])
|
||||
@@ -144,9 +124,21 @@ impl ProofCmCs {
|
||||
.map(|(wm_i, hs_i)| hs_i * wm_i)
|
||||
.sum::<G1Projective>();
|
||||
|
||||
let ciphertexts_bytes = priv_attributes_ciphertexts
|
||||
// zkp commitments for the individual attributes
|
||||
let commitments_attributes = witness_pedersen_commitments_openings
|
||||
.iter()
|
||||
.map(|c| c.to_bytes())
|
||||
.zip(witness_attributes.iter())
|
||||
.map(|(o_j, m_j)| g1 * o_j + h * m_j)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let commitments_bytes = commitments
|
||||
.iter()
|
||||
.map(|cm| cm.to_bytes())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let commitments_attributes_bytes = commitments_attributes
|
||||
.iter()
|
||||
.map(|cm| cm.to_bytes())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// compute challenge
|
||||
@@ -154,28 +146,20 @@ impl ProofCmCs {
|
||||
std::iter::once(params.gen1().to_bytes().as_ref())
|
||||
.chain(hs_bytes.iter().map(|hs| hs.as_ref()))
|
||||
.chain(std::iter::once(h.to_bytes().as_ref()))
|
||||
.chain(std::iter::once(
|
||||
elgamal_keypair.public_key().to_bytes().as_ref(),
|
||||
))
|
||||
.chain(std::iter::once(commitment.to_bytes().as_ref()))
|
||||
.chain(commitments_bytes.iter().map(|cm| cm.as_ref()))
|
||||
.chain(std::iter::once(commitment_attributes.to_bytes().as_ref()))
|
||||
.chain(std::iter::once(
|
||||
commitment_private_key_elgamal.to_bytes().as_ref(),
|
||||
))
|
||||
.chain(commitment_keys1_bytes.iter().map(|aw| aw.as_ref()))
|
||||
.chain(commitment_keys2_bytes.iter().map(|bw| bw.as_ref()))
|
||||
.chain(ciphertexts_bytes.iter().map(|c| c.as_ref())),
|
||||
.chain(commitments_attributes_bytes.iter().map(|cm| cm.as_ref())),
|
||||
);
|
||||
|
||||
// Responses
|
||||
let response_opening =
|
||||
produce_response(&witness_commitment_opening, &challenge, commitment_opening);
|
||||
let response_private_elgamal_key = produce_response(
|
||||
&witness_private_elgamal_key,
|
||||
let response_openings = produce_responses(
|
||||
&witness_pedersen_commitments_openings,
|
||||
&challenge,
|
||||
&elgamal_keypair.private_key().0,
|
||||
&pedersen_commitments_openings.iter().collect::<Vec<_>>(),
|
||||
);
|
||||
let response_keys = produce_responses(&witness_keys, &challenge, ephemeral_keys);
|
||||
let response_attributes = produce_responses(
|
||||
&witness_attributes,
|
||||
&challenge,
|
||||
@@ -185,8 +169,7 @@ impl ProofCmCs {
|
||||
ProofCmCs {
|
||||
challenge,
|
||||
response_opening,
|
||||
response_private_elgamal_key,
|
||||
response_keys,
|
||||
response_openings,
|
||||
response_attributes,
|
||||
}
|
||||
}
|
||||
@@ -194,11 +177,11 @@ impl ProofCmCs {
|
||||
pub(crate) fn verify(
|
||||
&self,
|
||||
params: &Parameters,
|
||||
pub_key: &elgamal::PublicKey,
|
||||
commitment: &G1Projective,
|
||||
attributes_ciphertexts: &[elgamal::Ciphertext],
|
||||
commitments: &[G1Projective],
|
||||
public_attributes: &[Attribute],
|
||||
) -> bool {
|
||||
if self.response_keys.len() != attributes_ciphertexts.len() {
|
||||
if self.response_attributes.len() != commitments.len() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -213,32 +196,14 @@ impl ProofCmCs {
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// recompute witnesses commitments
|
||||
let commitment_private_key_elgamal =
|
||||
pub_key * &self.challenge + g1 * self.response_private_elgamal_key;
|
||||
|
||||
// Aw[i] = (c * c1[i]) + (rk[i] * g1)
|
||||
let commitment_keys1_bytes = attributes_ciphertexts
|
||||
.iter()
|
||||
.map(|ciphertext| ciphertext.c1())
|
||||
.zip(self.response_keys.iter())
|
||||
.map(|(c1, res_k)| c1 * self.challenge + g1 * res_k)
|
||||
.map(|witness| witness.to_bytes())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Bw[i] = (c * c2[i]) + (rk[i] * gamma) + (rm[i] * h)
|
||||
let commitment_keys2_bytes = izip!(
|
||||
attributes_ciphertexts
|
||||
.iter()
|
||||
.map(|ciphertext| ciphertext.c2()),
|
||||
self.response_keys.iter(),
|
||||
self.response_attributes.iter()
|
||||
)
|
||||
.map(|(c2, res_key, res_attr)| c2 * self.challenge + pub_key * res_key + h * res_attr)
|
||||
.map(|witness| witness.to_bytes())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Cw = (cm * c) + (rr * g1) + (rm[0] * hs[0]) + ... + (rm[n] * hs[n])
|
||||
let commitment_attributes = commitment * self.challenge
|
||||
let commitment_attributes = (commitment
|
||||
- public_attributes
|
||||
.iter()
|
||||
.zip(params.gen_hs().iter().skip(self.response_attributes.len()))
|
||||
.map(|(pub_attr, hs)| hs * pub_attr)
|
||||
.sum::<G1Projective>())
|
||||
* self.challenge
|
||||
+ g1 * self.response_opening
|
||||
+ self
|
||||
.response_attributes
|
||||
@@ -247,9 +212,22 @@ impl ProofCmCs {
|
||||
.map(|(res_attr, hs)| hs * res_attr)
|
||||
.sum::<G1Projective>();
|
||||
|
||||
let ciphertexts_bytes = attributes_ciphertexts
|
||||
let commitments_attributes = izip!(
|
||||
commitments.iter(),
|
||||
self.response_openings.iter(),
|
||||
self.response_attributes.iter()
|
||||
)
|
||||
.map(|(cm_j, r_o_j, r_m_j)| cm_j * self.challenge + g1 * r_o_j + h * r_m_j)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let commitments_bytes = commitments
|
||||
.iter()
|
||||
.map(|c| c.to_bytes())
|
||||
.map(|cm| cm.to_bytes())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let commitments_attributes_bytes = commitments_attributes
|
||||
.iter()
|
||||
.map(|cm| cm.to_bytes())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// re-compute the challenge
|
||||
@@ -257,38 +235,32 @@ impl ProofCmCs {
|
||||
std::iter::once(params.gen1().to_bytes().as_ref())
|
||||
.chain(hs_bytes.iter().map(|hs| hs.as_ref()))
|
||||
.chain(std::iter::once(h.to_bytes().as_ref()))
|
||||
.chain(std::iter::once(pub_key.to_bytes().as_ref()))
|
||||
.chain(std::iter::once(commitment.to_bytes().as_ref()))
|
||||
.chain(commitments_bytes.iter().map(|cm| cm.as_ref()))
|
||||
.chain(std::iter::once(commitment_attributes.to_bytes().as_ref()))
|
||||
.chain(std::iter::once(
|
||||
commitment_private_key_elgamal.to_bytes().as_ref(),
|
||||
))
|
||||
.chain(commitment_keys1_bytes.iter().map(|aw| aw.as_ref()))
|
||||
.chain(commitment_keys2_bytes.iter().map(|bw| bw.as_ref()))
|
||||
.chain(ciphertexts_bytes.iter().map(|c| c.as_ref())),
|
||||
.chain(commitments_attributes_bytes.iter().map(|cm| cm.as_ref())),
|
||||
);
|
||||
|
||||
challenge == self.challenge
|
||||
}
|
||||
|
||||
// challenge || response opening || response private elgamal key || keys len || response keys || attributes len || response attributes
|
||||
// challenge || response opening || openings len || response openings || attributes len ||
|
||||
// response attributes
|
||||
pub(crate) fn to_bytes(&self) -> Vec<u8> {
|
||||
let keys_len = self.response_keys.len() as u64;
|
||||
let openings_len = self.response_openings.len() as u64;
|
||||
let attributes_len = self.response_attributes.len() as u64;
|
||||
|
||||
let mut bytes = Vec::with_capacity(16 + (keys_len + attributes_len + 3) as usize * 32);
|
||||
let mut bytes = Vec::with_capacity(16 + (2 + openings_len + attributes_len) as usize * 32);
|
||||
|
||||
bytes.extend_from_slice(&self.challenge.to_bytes());
|
||||
bytes.extend_from_slice(&self.response_opening.to_bytes());
|
||||
bytes.extend_from_slice(&self.response_private_elgamal_key.to_bytes());
|
||||
bytes.extend_from_slice(&keys_len.to_le_bytes());
|
||||
|
||||
for rk in &self.response_keys {
|
||||
bytes.extend_from_slice(&rk.to_bytes());
|
||||
bytes.extend_from_slice(&openings_len.to_le_bytes());
|
||||
for ro in &self.response_openings {
|
||||
bytes.extend_from_slice(&ro.to_bytes());
|
||||
}
|
||||
|
||||
bytes.extend_from_slice(&attributes_len.to_le_bytes());
|
||||
|
||||
for rm in &self.response_attributes {
|
||||
bytes.extend_from_slice(&rm.to_bytes());
|
||||
}
|
||||
@@ -298,11 +270,11 @@ impl ProofCmCs {
|
||||
|
||||
pub(crate) fn from_bytes(bytes: &[u8]) -> Result<Self> {
|
||||
// at the very minimum there must be a single attribute being proven
|
||||
if bytes.len() < 32 * 5 + 16 || (bytes.len() - 16) % 32 != 0 {
|
||||
return Err(
|
||||
CoconutError::Deserialization(
|
||||
"tried to deserialize proof of ciphertexts and commitment with bytes of invalid length".to_string())
|
||||
);
|
||||
if bytes.len() < 32 * 4 + 16 || (bytes.len() - 16) % 32 != 0 {
|
||||
return Err(CoconutError::Deserialization(
|
||||
"tried to deserialize proof of commitments with bytes of invalid length"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let mut idx = 0;
|
||||
@@ -310,54 +282,46 @@ impl ProofCmCs {
|
||||
idx += 32;
|
||||
let response_opening_bytes = bytes[idx..idx + 32].try_into().unwrap();
|
||||
idx += 32;
|
||||
let response_private_elgamal_key_bytes = bytes[idx..idx + 32].try_into().unwrap();
|
||||
idx += 32;
|
||||
|
||||
let challenge = try_deserialize_scalar(
|
||||
&challenge_bytes,
|
||||
CoconutError::Deserialization("Failed to deserialize challenge".to_string()),
|
||||
)?;
|
||||
|
||||
let response_opening = try_deserialize_scalar(
|
||||
&response_opening_bytes,
|
||||
CoconutError::Deserialization(
|
||||
"Failed to deserialize the response to the random".to_string(),
|
||||
),
|
||||
)?;
|
||||
let response_private_elgamal_key = try_deserialize_scalar(
|
||||
&response_private_elgamal_key_bytes,
|
||||
CoconutError::Deserialization(
|
||||
"Failed to deserialize the response to the private ElGamal key".to_string(),
|
||||
),
|
||||
)?;
|
||||
|
||||
let rk_len = u64::from_le_bytes(bytes[idx..idx + 8].try_into().unwrap());
|
||||
let ro_len = u64::from_le_bytes(bytes[idx..idx + 8].try_into().unwrap());
|
||||
idx += 8;
|
||||
if bytes[idx..].len() < rk_len as usize * 32 + 8 {
|
||||
if bytes[idx..].len() < ro_len as usize * 32 + 8 {
|
||||
return Err(
|
||||
CoconutError::Deserialization(
|
||||
"tried to deserialize proof of ciphertexts and commitment with insufficient number of bytes provided".to_string()),
|
||||
);
|
||||
}
|
||||
|
||||
let rk_end = idx + rk_len as usize * 32;
|
||||
let response_keys = try_deserialize_scalar_vec(
|
||||
rk_len,
|
||||
&bytes[idx..rk_end],
|
||||
CoconutError::Deserialization("Failed to deserialize keys response".to_string()),
|
||||
let ro_end = idx + ro_len as usize * 32;
|
||||
let response_openings = try_deserialize_scalar_vec(
|
||||
ro_len,
|
||||
&bytes[idx..ro_end],
|
||||
CoconutError::Deserialization("Failed to deserialize openings response".to_string()),
|
||||
)?;
|
||||
|
||||
let rm_len = u64::from_le_bytes(bytes[rk_end..rk_end + 8].try_into().unwrap());
|
||||
let rm_len = u64::from_le_bytes(bytes[ro_end..ro_end + 8].try_into().unwrap());
|
||||
let response_attributes = try_deserialize_scalar_vec(
|
||||
rm_len,
|
||||
&bytes[rk_end + 8..],
|
||||
&bytes[ro_end + 8..],
|
||||
CoconutError::Deserialization("Failed to deserialize attributes response".to_string()),
|
||||
)?;
|
||||
|
||||
Ok(ProofCmCs {
|
||||
challenge,
|
||||
response_opening,
|
||||
response_private_elgamal_key,
|
||||
response_keys,
|
||||
response_openings,
|
||||
response_attributes,
|
||||
})
|
||||
}
|
||||
@@ -392,7 +356,7 @@ impl ProofKappaZeta {
|
||||
let witness_attributes = vec![witness_serial_number, witness_binding_number];
|
||||
|
||||
let beta_bytes = verification_key
|
||||
.beta
|
||||
.beta_g2
|
||||
.iter()
|
||||
.map(|beta_i| beta_i.to_bytes())
|
||||
.collect::<Vec<_>>();
|
||||
@@ -403,7 +367,7 @@ impl ProofKappaZeta {
|
||||
+ verification_key.alpha
|
||||
+ witness_attributes
|
||||
.iter()
|
||||
.zip(verification_key.beta.iter())
|
||||
.zip(verification_key.beta_g2.iter())
|
||||
.map(|(wm_i, beta_i)| beta_i * wm_i)
|
||||
.sum::<G2Projective>();
|
||||
|
||||
@@ -447,7 +411,7 @@ impl ProofKappaZeta {
|
||||
zeta: &G2Projective,
|
||||
) -> bool {
|
||||
let beta_bytes = verification_key
|
||||
.beta
|
||||
.beta_g2
|
||||
.iter()
|
||||
.map(|beta_i| beta_i.to_bytes())
|
||||
.collect::<Vec<_>>();
|
||||
@@ -460,7 +424,7 @@ impl ProofKappaZeta {
|
||||
+ verification_key.alpha * (Scalar::one() - self.challenge)
|
||||
+ response_attributes
|
||||
.iter()
|
||||
.zip(verification_key.beta.iter())
|
||||
.zip(verification_key.beta_g2.iter())
|
||||
.map(|(priv_attr, beta_i)| beta_i * priv_attr)
|
||||
.sum::<G2Projective>();
|
||||
|
||||
@@ -550,7 +514,6 @@ mod tests {
|
||||
use group::Group;
|
||||
use rand::thread_rng;
|
||||
|
||||
use crate::scheme::issuance::{compute_attribute_encryption, compute_commitment_hash};
|
||||
use crate::scheme::keygen::keygen;
|
||||
use crate::scheme::setup::setup;
|
||||
use crate::scheme::verification::{compute_kappa, compute_zeta};
|
||||
@@ -560,51 +523,32 @@ mod tests {
|
||||
#[test]
|
||||
fn proof_cm_cs_bytes_roundtrip() {
|
||||
let mut rng = thread_rng();
|
||||
|
||||
let mut params = setup(1).unwrap();
|
||||
|
||||
let elgamal_keypair = elgamal::elgamal_keygen(¶ms);
|
||||
let private_attributes = params.n_random_scalars(1);
|
||||
|
||||
// we don't care about 'correctness' of the proof. only whether we can correctly recover it from bytes
|
||||
let cm = G1Projective::random(&mut rng);
|
||||
let r = params.random_scalar();
|
||||
|
||||
let commitment_hash = compute_commitment_hash(cm);
|
||||
let (attributes_ciphertexts, _): (Vec<_>, Vec<_>) = compute_attribute_encryption(
|
||||
¶ms,
|
||||
private_attributes.as_ref(),
|
||||
elgamal_keypair.public_key(),
|
||||
commitment_hash,
|
||||
);
|
||||
let ephemeral_keys = params.n_random_scalars(1);
|
||||
let cms: [G1Projective; 1] = [G1Projective::random(&mut rng)];
|
||||
let rs = params.n_random_scalars(1);
|
||||
let private_attributes = params.n_random_scalars(1);
|
||||
|
||||
// 0 public 1 private
|
||||
let pi_s = ProofCmCs::construct(
|
||||
&mut params,
|
||||
&elgamal_keypair,
|
||||
&ephemeral_keys,
|
||||
&cm,
|
||||
&r,
|
||||
&private_attributes,
|
||||
&*attributes_ciphertexts,
|
||||
);
|
||||
let pi_s = ProofCmCs::construct(&mut params, &cm, &r, &cms, &rs, &private_attributes);
|
||||
|
||||
let bytes = pi_s.to_bytes();
|
||||
assert_eq!(ProofCmCs::from_bytes(&bytes).unwrap(), pi_s);
|
||||
|
||||
// 2 private
|
||||
let mut params = setup(2).unwrap();
|
||||
let cm = G1Projective::random(&mut rng);
|
||||
let r = params.random_scalar();
|
||||
let cms: [G1Projective; 2] = [
|
||||
G1Projective::random(&mut rng),
|
||||
G1Projective::random(&mut rng),
|
||||
];
|
||||
let rs = params.n_random_scalars(2);
|
||||
let private_attributes = params.n_random_scalars(2);
|
||||
let ephemeral_keys = params.n_random_scalars(2);
|
||||
|
||||
let pi_s = ProofCmCs::construct(
|
||||
&mut params,
|
||||
&elgamal_keypair,
|
||||
&ephemeral_keys,
|
||||
&cm,
|
||||
&r,
|
||||
&private_attributes,
|
||||
&*attributes_ciphertexts,
|
||||
);
|
||||
// 0 public 2 privates
|
||||
let pi_s = ProofCmCs::construct(&mut params, &cm, &r, &cms, &rs, &private_attributes);
|
||||
|
||||
let bytes = pi_s.to_bytes();
|
||||
assert_eq!(ProofCmCs::from_bytes(&bytes).unwrap(), pi_s);
|
||||
@@ -612,9 +556,9 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn proof_kappa_zeta_bytes_roundtrip() {
|
||||
let mut params = setup(4).unwrap();
|
||||
let params = setup(4).unwrap();
|
||||
|
||||
let keypair = keygen(&mut params);
|
||||
let keypair = keygen(¶ms);
|
||||
|
||||
// we don't care about 'correctness' of the proof. only whether we can correctly recover it from bytes
|
||||
let serial_number = params.random_scalar();
|
||||
@@ -627,7 +571,7 @@ mod tests {
|
||||
|
||||
// 0 public 2 private
|
||||
let pi_v = ProofKappaZeta::construct(
|
||||
&mut params,
|
||||
¶ms,
|
||||
&keypair.verification_key(),
|
||||
&serial_number,
|
||||
&binding_number,
|
||||
@@ -642,11 +586,11 @@ mod tests {
|
||||
assert_eq!(proof_from_bytes, pi_v);
|
||||
|
||||
// 2 public 2 private
|
||||
let mut params = setup(4).unwrap();
|
||||
let keypair = keygen(&mut params);
|
||||
let params = setup(4).unwrap();
|
||||
let keypair = keygen(¶ms);
|
||||
|
||||
let pi_v = ProofKappaZeta::construct(
|
||||
&mut params,
|
||||
¶ms,
|
||||
&keypair.verification_key(),
|
||||
&serial_number,
|
||||
&binding_number,
|
||||
|
||||
@@ -64,7 +64,8 @@ impl Aggregatable for PartialSignature {
|
||||
|
||||
/// Ensures all provided verification keys were generated to verify the same number of attributes.
|
||||
fn check_same_key_size(keys: &[VerificationKey]) -> bool {
|
||||
keys.iter().map(|vk| vk.beta.len()).all_equal()
|
||||
keys.iter().map(|vk| vk.beta_g1.len()).all_equal()
|
||||
&& keys.iter().map(|vk| vk.beta_g2.len()).all_equal()
|
||||
}
|
||||
|
||||
pub fn aggregate_verification_keys(
|
||||
@@ -98,7 +99,7 @@ pub fn aggregate_signatures(
|
||||
|
||||
let tmp = attributes
|
||||
.iter()
|
||||
.zip(verification_key.beta.iter())
|
||||
.zip(verification_key.beta_g2.iter())
|
||||
.map(|(attr, beta_i)| beta_i * attr)
|
||||
.sum::<G2Projective>();
|
||||
|
||||
@@ -149,8 +150,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn key_aggregation_works_for_any_subset_of_keys() {
|
||||
let mut params = Parameters::new(2).unwrap();
|
||||
let keypairs = ttp_keygen(&mut params, 3, 5).unwrap();
|
||||
let params = Parameters::new(2).unwrap();
|
||||
let keypairs = ttp_keygen(¶ms, 3, 5).unwrap();
|
||||
|
||||
let vks = keypairs
|
||||
.into_iter()
|
||||
@@ -213,7 +214,7 @@ mod tests {
|
||||
let mut params = Parameters::new(2).unwrap();
|
||||
let attributes = params.n_random_scalars(2);
|
||||
|
||||
let keypairs = ttp_keygen(&mut params, 3, 5).unwrap();
|
||||
let keypairs = ttp_keygen(¶ms, 3, 5).unwrap();
|
||||
|
||||
let (sks, vks): (Vec<_>, Vec<_>) = keypairs
|
||||
.into_iter()
|
||||
@@ -310,9 +311,9 @@ mod tests {
|
||||
#[test]
|
||||
fn signature_aggregation_doesnt_work_for_empty_set_of_signatures() {
|
||||
let signatures: Vec<Signature> = vec![];
|
||||
let mut params = Parameters::new(2).unwrap();
|
||||
let params = Parameters::new(2).unwrap();
|
||||
let attributes = params.n_random_scalars(2);
|
||||
let keypairs = ttp_keygen(&mut params, 3, 5).unwrap();
|
||||
let keypairs = ttp_keygen(¶ms, 3, 5).unwrap();
|
||||
|
||||
let (_, vks): (Vec<_>, Vec<_>) = keypairs
|
||||
.into_iter()
|
||||
@@ -328,9 +329,9 @@ mod tests {
|
||||
#[test]
|
||||
fn signature_aggregation_doesnt_work_if_indices_have_invalid_length() {
|
||||
let signatures = vec![random_signature()];
|
||||
let mut params = Parameters::new(2).unwrap();
|
||||
let params = Parameters::new(2).unwrap();
|
||||
let attributes = params.n_random_scalars(2);
|
||||
let keypairs = ttp_keygen(&mut params, 3, 5).unwrap();
|
||||
let keypairs = ttp_keygen(¶ms, 3, 5).unwrap();
|
||||
let (_, vks): (Vec<_>, Vec<_>) = keypairs
|
||||
.into_iter()
|
||||
.map(|keypair| (keypair.secret_key(), keypair.verification_key()))
|
||||
@@ -354,9 +355,9 @@ mod tests {
|
||||
#[test]
|
||||
fn signature_aggregation_doesnt_work_for_non_unique_indices() {
|
||||
let signatures = vec![random_signature(), random_signature()];
|
||||
let mut params = Parameters::new(2).unwrap();
|
||||
let params = Parameters::new(2).unwrap();
|
||||
let attributes = params.n_random_scalars(2);
|
||||
let keypairs = ttp_keygen(&mut params, 3, 5).unwrap();
|
||||
let keypairs = ttp_keygen(¶ms, 3, 5).unwrap();
|
||||
let (_, vks): (Vec<_>, Vec<_>) = keypairs
|
||||
.into_iter()
|
||||
.map(|keypair| (keypair.secret_key(), keypair.verification_key()))
|
||||
|
||||
@@ -7,16 +7,15 @@ use std::convert::TryInto;
|
||||
use bls12_381::{G1Affine, G1Projective, Scalar};
|
||||
use group::{Curve, GroupEncoding};
|
||||
|
||||
use crate::elgamal::{Ciphertext, EphemeralKey};
|
||||
use crate::error::{CoconutError, Result};
|
||||
use crate::proofs::ProofCmCs;
|
||||
use crate::scheme::setup::Parameters;
|
||||
use crate::scheme::BlindedSignature;
|
||||
use crate::scheme::SecretKey;
|
||||
use crate::Attribute;
|
||||
/// Creates a Coconut Signature under a given secret key on a set of public attributes only.
|
||||
#[cfg(test)]
|
||||
use crate::Signature;
|
||||
use crate::{elgamal, Attribute, ElGamalKeyPair};
|
||||
// TODO: possibly completely remove those two functions.
|
||||
// They only exist to have a simpler and smaller code snippets to test
|
||||
// basic functionalities.
|
||||
@@ -33,7 +32,7 @@ pub struct BlindSignRequest {
|
||||
// h
|
||||
commitment_hash: G1Projective,
|
||||
// c
|
||||
private_attributes_ciphertexts: Vec<elgamal::Ciphertext>,
|
||||
private_attributes_commitments: Vec<G1Projective>,
|
||||
// pi_s
|
||||
pi_s: ProofCmCs,
|
||||
}
|
||||
@@ -42,9 +41,9 @@ impl TryFrom<&[u8]> for BlindSignRequest {
|
||||
type Error = CoconutError;
|
||||
|
||||
fn try_from(bytes: &[u8]) -> Result<BlindSignRequest> {
|
||||
if bytes.len() < 48 + 48 + 8 + 96 {
|
||||
if bytes.len() < 48 + 48 + 8 + 48 {
|
||||
return Err(CoconutError::DeserializationMinLength {
|
||||
min: 48 + 48 + 8 + 96,
|
||||
min: 48 + 48 + 8 + 48,
|
||||
actual: bytes.len(),
|
||||
});
|
||||
}
|
||||
@@ -73,26 +72,35 @@ impl TryFrom<&[u8]> for BlindSignRequest {
|
||||
|
||||
let c_len = u64::from_le_bytes(bytes[j..j + 8].try_into().unwrap());
|
||||
j += 8;
|
||||
if bytes[j..].len() < c_len as usize * 96 {
|
||||
if bytes[j..].len() < c_len as usize * 48 {
|
||||
return Err(CoconutError::DeserializationMinLength {
|
||||
min: c_len as usize * 96,
|
||||
min: c_len as usize * 48,
|
||||
actual: bytes[56..].len(),
|
||||
});
|
||||
}
|
||||
|
||||
let mut private_attributes_ciphertexts = Vec::with_capacity(c_len as usize);
|
||||
let mut private_attributes_commitments = Vec::with_capacity(c_len as usize);
|
||||
for i in 0..c_len as usize {
|
||||
let start = j + i * 96;
|
||||
let end = start + 96;
|
||||
private_attributes_ciphertexts.push(Ciphertext::try_from(&bytes[start..end])?)
|
||||
let start = j + i * 48;
|
||||
let end = start + 48;
|
||||
|
||||
let private_attributes_commitment_bytes = bytes[start..end].try_into().unwrap();
|
||||
let private_attributes_commitment = try_deserialize_g1_projective(
|
||||
&private_attributes_commitment_bytes,
|
||||
CoconutError::Deserialization(
|
||||
"Failed to deserialize compressed commitment".to_string(),
|
||||
),
|
||||
)?;
|
||||
|
||||
private_attributes_commitments.push(private_attributes_commitment)
|
||||
}
|
||||
|
||||
let pi_s = ProofCmCs::from_bytes(&bytes[j + c_len as usize * 96..])?;
|
||||
let pi_s = ProofCmCs::from_bytes(&bytes[j + c_len as usize * 48..])?;
|
||||
|
||||
Ok(BlindSignRequest {
|
||||
commitment,
|
||||
commitment_hash,
|
||||
private_attributes_ciphertexts,
|
||||
private_attributes_commitments,
|
||||
pi_s,
|
||||
})
|
||||
}
|
||||
@@ -102,16 +110,16 @@ impl Bytable for BlindSignRequest {
|
||||
fn to_byte_vec(&self) -> Vec<u8> {
|
||||
let cm_bytes = self.commitment.to_affine().to_compressed();
|
||||
let cm_hash_bytes = self.commitment_hash.to_affine().to_compressed();
|
||||
let c_len = self.private_attributes_ciphertexts.len() as u64;
|
||||
let c_len = self.private_attributes_commitments.len() as u64;
|
||||
let proof_bytes = self.pi_s.to_bytes();
|
||||
|
||||
let mut bytes = Vec::with_capacity(48 + 48 + 8 + c_len as usize * 96 + proof_bytes.len());
|
||||
let mut bytes = Vec::with_capacity(48 + 48 + 8 + c_len as usize * 48 + proof_bytes.len());
|
||||
|
||||
bytes.extend_from_slice(&cm_bytes);
|
||||
bytes.extend_from_slice(&cm_hash_bytes);
|
||||
bytes.extend_from_slice(&c_len.to_le_bytes());
|
||||
for c in &self.private_attributes_ciphertexts {
|
||||
bytes.extend_from_slice(&c.to_bytes());
|
||||
for c in &self.private_attributes_commitments {
|
||||
bytes.extend_from_slice(&c.to_affine().to_compressed());
|
||||
}
|
||||
|
||||
bytes.extend_from_slice(&proof_bytes);
|
||||
@@ -127,12 +135,12 @@ impl Bytable for BlindSignRequest {
|
||||
impl Base58 for BlindSignRequest {}
|
||||
|
||||
impl BlindSignRequest {
|
||||
fn verify_proof(&self, params: &Parameters, pub_key: &elgamal::PublicKey) -> bool {
|
||||
fn verify_proof(&self, params: &Parameters, public_attributes: &[Attribute]) -> bool {
|
||||
self.pi_s.verify(
|
||||
params,
|
||||
pub_key,
|
||||
&self.commitment,
|
||||
&self.private_attributes_ciphertexts,
|
||||
&self.private_attributes_commitments,
|
||||
public_attributes,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -140,6 +148,10 @@ impl BlindSignRequest {
|
||||
self.commitment_hash
|
||||
}
|
||||
|
||||
pub fn get_private_attributes_pedersen_commitments(&self) -> Vec<G1Projective> {
|
||||
self.private_attributes_commitments.clone()
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
self.to_byte_vec()
|
||||
}
|
||||
@@ -149,17 +161,19 @@ impl BlindSignRequest {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compute_private_attributes_commitment(
|
||||
pub fn compute_attributes_commitment(
|
||||
params: &Parameters,
|
||||
private_attributes: &[Attribute],
|
||||
public_attributes: &[Attribute],
|
||||
hs: &[G1Affine],
|
||||
) -> (Scalar, G1Projective) {
|
||||
let commitment_opening = params.random_scalar();
|
||||
|
||||
// Produces h0 ^ m0 * h1^m1 * .... * hn^mn
|
||||
// where m0, m1, ...., mn are private attributes
|
||||
// where m0, m1, ...., mn are attributes
|
||||
let attr_cm = private_attributes
|
||||
.iter()
|
||||
.chain(public_attributes.iter())
|
||||
.zip(hs)
|
||||
.map(|(&m, h)| h * m)
|
||||
.sum::<G1Projective>();
|
||||
@@ -169,29 +183,34 @@ pub fn compute_private_attributes_commitment(
|
||||
(commitment_opening, commitment)
|
||||
}
|
||||
|
||||
pub fn compute_commitment_hash(commitment: G1Projective) -> G1Projective {
|
||||
hash_g1(commitment.to_bytes())
|
||||
}
|
||||
|
||||
pub fn compute_attribute_encryption(
|
||||
pub fn compute_pedersen_commitments_for_private_attributes(
|
||||
params: &Parameters,
|
||||
private_attributes: &[Attribute],
|
||||
pub_key: &elgamal::PublicKey,
|
||||
commitment_hash: G1Projective,
|
||||
) -> (Vec<Ciphertext>, Vec<EphemeralKey>) {
|
||||
private_attributes
|
||||
h: &G1Projective,
|
||||
) -> (Vec<Scalar>, Vec<G1Projective>) {
|
||||
// Generate openings for Pedersen commitment for each private attribute
|
||||
let commitments_openings = params.n_random_scalars(private_attributes.len());
|
||||
|
||||
// Compute Pedersen commitment for each private attribute
|
||||
let pedersen_commitments = commitments_openings
|
||||
.iter()
|
||||
.map(|m| pub_key.encrypt(params, &commitment_hash, m))
|
||||
.unzip()
|
||||
.zip(private_attributes.iter())
|
||||
.map(|(o_j, m_j)| params.gen1() * o_j + h * m_j)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
(commitments_openings, pedersen_commitments)
|
||||
}
|
||||
|
||||
pub fn compute_commitment_hash(commitment: G1Projective) -> G1Projective {
|
||||
hash_g1(commitment.to_bytes())
|
||||
}
|
||||
|
||||
/// Builds cryptographic material required for blind sign.
|
||||
pub fn prepare_blind_sign(
|
||||
params: &Parameters,
|
||||
elgamal_keypair: &ElGamalKeyPair,
|
||||
private_attributes: &[Attribute],
|
||||
public_attributes: &[Attribute],
|
||||
) -> Result<BlindSignRequest> {
|
||||
) -> Result<(Vec<Scalar>, BlindSignRequest)> {
|
||||
if private_attributes.is_empty() {
|
||||
return Err(CoconutError::Issuance(
|
||||
"Tried to prepare blind sign request for an empty set of private attributes"
|
||||
@@ -208,45 +227,45 @@ pub fn prepare_blind_sign(
|
||||
}
|
||||
|
||||
let (commitment_opening, commitment) =
|
||||
compute_private_attributes_commitment(params, private_attributes, hs);
|
||||
compute_attributes_commitment(params, private_attributes, public_attributes, hs);
|
||||
|
||||
// Compute the challenge as the commitment hash
|
||||
let commitment_hash = compute_commitment_hash(commitment);
|
||||
// build ElGamal encryption
|
||||
let (private_attributes_ciphertexts, ephemeral_keys): (Vec<_>, Vec<_>) =
|
||||
compute_attribute_encryption(
|
||||
|
||||
let (pedersen_commitments_openings, pedersen_commitments) =
|
||||
compute_pedersen_commitments_for_private_attributes(
|
||||
params,
|
||||
private_attributes,
|
||||
elgamal_keypair.public_key(),
|
||||
commitment_hash,
|
||||
&commitment_hash,
|
||||
);
|
||||
|
||||
let pi_s = ProofCmCs::construct(
|
||||
params,
|
||||
elgamal_keypair,
|
||||
&ephemeral_keys,
|
||||
&commitment,
|
||||
&commitment_opening,
|
||||
&pedersen_commitments,
|
||||
&pedersen_commitments_openings,
|
||||
private_attributes,
|
||||
&*private_attributes_ciphertexts,
|
||||
);
|
||||
|
||||
Ok(BlindSignRequest {
|
||||
Ok((
|
||||
pedersen_commitments_openings,
|
||||
BlindSignRequest {
|
||||
commitment,
|
||||
commitment_hash,
|
||||
private_attributes_ciphertexts,
|
||||
private_attributes_commitments: pedersen_commitments,
|
||||
pi_s,
|
||||
})
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
pub fn blind_sign(
|
||||
params: &Parameters,
|
||||
signing_secret_key: &SecretKey,
|
||||
prover_pub_key: &elgamal::PublicKey,
|
||||
blind_sign_request: &BlindSignRequest,
|
||||
public_attributes: &[Attribute],
|
||||
) -> Result<BlindedSignature> {
|
||||
let num_private = blind_sign_request.private_attributes_ciphertexts.len();
|
||||
let num_private = blind_sign_request.private_attributes_commitments.len();
|
||||
let hs = params.gen_hs();
|
||||
|
||||
if num_private + public_attributes.len() > hs.len() {
|
||||
@@ -265,7 +284,7 @@ pub fn blind_sign(
|
||||
}
|
||||
|
||||
// Verify the ZK proof
|
||||
if !blind_sign_request.verify_proof(params, prover_pub_key) {
|
||||
if !blind_sign_request.verify_proof(params, public_attributes) {
|
||||
return Err(CoconutError::Issuance(
|
||||
"Failed to verify the proof of knowledge".to_string(),
|
||||
));
|
||||
@@ -280,27 +299,17 @@ pub fn blind_sign(
|
||||
.map(|(attr, yi)| attr * yi)
|
||||
.sum::<Scalar>();
|
||||
|
||||
// c1[0] ^ y[0] * ... * c1[m] ^ y[m]
|
||||
let sig_1 = blind_sign_request
|
||||
.private_attributes_ciphertexts
|
||||
// h ^ x + c[0] ^ y[0] + ... c[m] ^ y[m] + h ^ (pub_m[0] * y[m + 1] + ... + pub_m[n] * y[m + n])
|
||||
let sig = blind_sign_request
|
||||
.private_attributes_commitments
|
||||
.iter()
|
||||
.map(|ciphertext| ciphertext.c1())
|
||||
.zip(signing_secret_key.ys.iter())
|
||||
.map(|(c1, yi)| c1 * yi)
|
||||
.sum();
|
||||
|
||||
// h ^ x + c2[0] ^ y[0] + ... c2[m] ^ y[m] + h ^ (pub_m[0] * y[m + 1] + ... + pub_m[n] * y[m + n])
|
||||
let sig_2 = blind_sign_request
|
||||
.private_attributes_ciphertexts
|
||||
.iter()
|
||||
.map(|ciphertext| ciphertext.c2())
|
||||
.zip(signing_secret_key.ys.iter())
|
||||
.map(|(c2, yi)| c2 * yi)
|
||||
.map(|(c, yi)| c * yi)
|
||||
.chain(std::iter::once(h * signing_secret_key.x))
|
||||
.chain(std::iter::once(signed_public))
|
||||
.sum();
|
||||
|
||||
Ok(BlindedSignature(h, elgamal::Ciphertext(sig_1, sig_2)))
|
||||
Ok(BlindedSignature(h, sig))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -342,36 +351,27 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn blind_sign_request_bytes_roundtrip() {
|
||||
let mut params = Parameters::new(1).unwrap();
|
||||
let public_attributes = params.n_random_scalars(0);
|
||||
// 0 public and 1 private attribute
|
||||
let params = Parameters::new(1).unwrap();
|
||||
let private_attributes = params.n_random_scalars(1);
|
||||
let elgamal_keypair = elgamal::elgamal_keygen(¶ms);
|
||||
let public_attributes = params.n_random_scalars(0);
|
||||
|
||||
let lambda = prepare_blind_sign(
|
||||
&mut params,
|
||||
&elgamal_keypair,
|
||||
&private_attributes,
|
||||
&public_attributes,
|
||||
)
|
||||
.unwrap();
|
||||
let (_commitments_openings, lambda) =
|
||||
prepare_blind_sign(¶ms, &private_attributes, &public_attributes).unwrap();
|
||||
|
||||
let bytes = lambda.to_bytes();
|
||||
println!("{:?}", bytes.len());
|
||||
assert_eq!(
|
||||
BlindSignRequest::try_from(bytes.as_slice()).unwrap(),
|
||||
lambda
|
||||
);
|
||||
|
||||
let mut params = Parameters::new(4).unwrap();
|
||||
let public_attributes = params.n_random_scalars(2);
|
||||
// 2 public and 2 private attributes
|
||||
let params = Parameters::new(4).unwrap();
|
||||
let private_attributes = params.n_random_scalars(2);
|
||||
let lambda = prepare_blind_sign(
|
||||
&mut params,
|
||||
&elgamal_keypair,
|
||||
&private_attributes,
|
||||
&public_attributes,
|
||||
)
|
||||
.unwrap();
|
||||
let public_attributes = params.n_random_scalars(2);
|
||||
|
||||
let (_commitments_openings, lambda) =
|
||||
prepare_blind_sign(¶ms, &private_attributes, &public_attributes).unwrap();
|
||||
|
||||
let bytes = lambda.to_bytes();
|
||||
assert_eq!(
|
||||
|
||||
@@ -7,7 +7,7 @@ use core::ops::{Add, Mul};
|
||||
use std::convert::TryFrom;
|
||||
use std::convert::TryInto;
|
||||
|
||||
use bls12_381::{G2Projective, Scalar};
|
||||
use bls12_381::{G1Projective, G2Projective, Scalar};
|
||||
use group::Curve;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
|
||||
@@ -17,7 +17,8 @@ use crate::scheme::setup::Parameters;
|
||||
use crate::scheme::SignerIndex;
|
||||
use crate::traits::Bytable;
|
||||
use crate::utils::{
|
||||
try_deserialize_g2_projective, try_deserialize_scalar, try_deserialize_scalar_vec, Polynomial,
|
||||
try_deserialize_g1_projective, try_deserialize_g2_projective, try_deserialize_scalar,
|
||||
try_deserialize_scalar_vec, Polynomial,
|
||||
};
|
||||
use crate::Base58;
|
||||
|
||||
@@ -32,6 +33,7 @@ impl TryFrom<&[u8]> for SecretKey {
|
||||
type Error = CoconutError;
|
||||
|
||||
fn try_from(bytes: &[u8]) -> Result<SecretKey> {
|
||||
// There should be x and at least one y
|
||||
if bytes.len() < 32 * 2 + 8 || (bytes.len() - 8) % 32 != 0 {
|
||||
return Err(CoconutError::DeserializationInvalidLength {
|
||||
actual: bytes.len(),
|
||||
@@ -71,16 +73,18 @@ impl TryFrom<&[u8]> for SecretKey {
|
||||
impl SecretKey {
|
||||
/// Derive verification key using this secret key.
|
||||
pub fn verification_key(&self, params: &Parameters) -> VerificationKey {
|
||||
let g1 = params.gen1();
|
||||
let g2 = params.gen2();
|
||||
VerificationKey {
|
||||
alpha: g2 * self.x,
|
||||
beta: self.ys.iter().map(|y| g2 * y).collect(),
|
||||
beta_g1: self.ys.iter().map(|y| g1 * y).collect(),
|
||||
beta_g2: self.ys.iter().map(|y| g2 * y).collect(),
|
||||
}
|
||||
}
|
||||
|
||||
// x || ys.len() || ys
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
let ys_len = self.ys.len() as u64;
|
||||
let ys_len = self.ys.len();
|
||||
let mut bytes = Vec::with_capacity(8 + (ys_len + 1) as usize * 32);
|
||||
|
||||
bytes.extend_from_slice(&self.x.to_bytes());
|
||||
@@ -114,33 +118,36 @@ impl Base58 for SecretKey {}
|
||||
pub struct VerificationKey {
|
||||
// TODO add gen2 as per the paper or imply it from the fact library is using bls381?
|
||||
pub(crate) alpha: G2Projective,
|
||||
pub(crate) beta: Vec<G2Projective>,
|
||||
pub(crate) beta_g1: Vec<G1Projective>,
|
||||
pub(crate) beta_g2: Vec<G2Projective>,
|
||||
}
|
||||
|
||||
impl TryFrom<&[u8]> for VerificationKey {
|
||||
type Error = CoconutError;
|
||||
|
||||
fn try_from(bytes: &[u8]) -> Result<VerificationKey> {
|
||||
if bytes.len() < 96 * 2 + 8 || (bytes.len() - 8) % 96 != 0 {
|
||||
// There should be at least alpha, one betaG1 and one betaG2 and their length
|
||||
if bytes.len() < 96 * 2 + 48 + 8 || (bytes.len() - 8 - 96) % (96 + 48) != 0 {
|
||||
return Err(CoconutError::DeserializationInvalidLength {
|
||||
actual: bytes.len(),
|
||||
modulus_target: bytes.len() - 8,
|
||||
target: 96 * 2 + 8,
|
||||
modulus: 96,
|
||||
object: "secret key".to_string(),
|
||||
modulus_target: bytes.len() - 8 - 96,
|
||||
target: 96 * 2 + 48 + 8,
|
||||
modulus: 96 + 48,
|
||||
object: "verification key".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
// this conversion will not fail as we are taking the same length of data
|
||||
let alpha_bytes: [u8; 96] = bytes[..96].try_into().unwrap();
|
||||
let beta_len = u64::from_le_bytes(bytes[96..104].try_into().unwrap());
|
||||
let actual_beta_len = (bytes.len() - 104) / 96;
|
||||
let betas_len = u64::from_le_bytes(bytes[96..104].try_into().unwrap());
|
||||
|
||||
if beta_len as usize != actual_beta_len {
|
||||
let actual_betas_len = (bytes.len() - 104) / (96 + 48);
|
||||
|
||||
if betas_len as usize != actual_betas_len {
|
||||
return Err(
|
||||
CoconutError::Deserialization(
|
||||
format!("Tried to deserialize verification key with inconsistent beta len (expected {}, got {})",
|
||||
beta_len, actual_beta_len
|
||||
format!("Tried to deserialize verification key with inconsistent betas len (expected {}, got {})",
|
||||
betas_len, actual_betas_len
|
||||
)));
|
||||
}
|
||||
|
||||
@@ -151,10 +158,27 @@ impl TryFrom<&[u8]> for VerificationKey {
|
||||
),
|
||||
)?;
|
||||
|
||||
let mut beta = Vec::with_capacity(actual_beta_len);
|
||||
for i in 0..actual_beta_len {
|
||||
let start = 104 + i * 96;
|
||||
let end = start + 96;
|
||||
let mut beta_g1 = Vec::with_capacity(betas_len as usize);
|
||||
let mut beta_g1_end: u64 = 0;
|
||||
for i in 0..betas_len {
|
||||
let start = (104 + i * 48) as usize;
|
||||
let end = (start + 48) as usize;
|
||||
let beta_i_bytes = bytes[start..end].try_into().unwrap();
|
||||
let beta_i = try_deserialize_g1_projective(
|
||||
&beta_i_bytes,
|
||||
CoconutError::Deserialization(
|
||||
"Failed to deserialize verification key G1 point (beta)".to_string(),
|
||||
),
|
||||
)?;
|
||||
|
||||
beta_g1_end = end as u64;
|
||||
beta_g1.push(beta_i)
|
||||
}
|
||||
|
||||
let mut beta_g2 = Vec::with_capacity(betas_len as usize);
|
||||
for i in 0..betas_len {
|
||||
let start = (beta_g1_end + i * 96) as usize;
|
||||
let end = (start + 96) as usize;
|
||||
let beta_i_bytes = bytes[start..end].try_into().unwrap();
|
||||
let beta_i = try_deserialize_g2_projective(
|
||||
&beta_i_bytes,
|
||||
@@ -163,10 +187,14 @@ impl TryFrom<&[u8]> for VerificationKey {
|
||||
),
|
||||
)?;
|
||||
|
||||
beta.push(beta_i)
|
||||
beta_g2.push(beta_i)
|
||||
}
|
||||
|
||||
Ok(VerificationKey { alpha, beta })
|
||||
Ok(VerificationKey {
|
||||
alpha,
|
||||
beta_g1,
|
||||
beta_g2,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,18 +207,42 @@ impl<'b> Add<&'b VerificationKey> for VerificationKey {
|
||||
// for different number of attributes, just panic as it's a
|
||||
// nonsense operation.
|
||||
assert_eq!(
|
||||
self.beta.len(),
|
||||
rhs.beta.len(),
|
||||
"trying to add verification keys generated for different number of attributes"
|
||||
self.beta_g1.len(),
|
||||
rhs.beta_g1.len(),
|
||||
"trying to add verification keys generated for different number of attributes [G1]"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
self.beta_g2.len(),
|
||||
rhs.beta_g2.len(),
|
||||
"trying to add verification keys generated for different number of attributes [G2]"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
self.beta_g1.len(),
|
||||
self.beta_g2.len(),
|
||||
"this key is incorrect - the number of elements G1 and G2 does not match"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
rhs.beta_g1.len(),
|
||||
rhs.beta_g2.len(),
|
||||
"they key you want to add is incorrect - the number of elements G1 and G2 does not match"
|
||||
);
|
||||
|
||||
VerificationKey {
|
||||
alpha: self.alpha + rhs.alpha,
|
||||
beta: self
|
||||
.beta
|
||||
beta_g1: self
|
||||
.beta_g1
|
||||
.iter()
|
||||
.zip(rhs.beta.iter())
|
||||
.map(|(self_beta, rhs_beta)| self_beta + rhs_beta)
|
||||
.zip(rhs.beta_g1.iter())
|
||||
.map(|(self_beta_g1, rhs_beta_g1)| self_beta_g1 + rhs_beta_g1)
|
||||
.collect(),
|
||||
beta_g2: self
|
||||
.beta_g2
|
||||
.iter()
|
||||
.zip(rhs.beta_g2.iter())
|
||||
.map(|(self_beta_g2, rhs_beta_g2)| self_beta_g2 + rhs_beta_g2)
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
@@ -203,7 +255,8 @@ impl<'a> Mul<Scalar> for &'a VerificationKey {
|
||||
fn mul(self, rhs: Scalar) -> Self::Output {
|
||||
VerificationKey {
|
||||
alpha: self.alpha * rhs,
|
||||
beta: self.beta.iter().map(|b_i| b_i * rhs).collect(),
|
||||
beta_g1: self.beta_g1.iter().map(|b_i| b_i * rhs).collect(),
|
||||
beta_g2: self.beta_g2.iter().map(|b_i| b_i * rhs).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -219,7 +272,7 @@ where
|
||||
{
|
||||
let mut peekable = iter.peekable();
|
||||
let head_attributes = match peekable.peek() {
|
||||
Some(head) => head.borrow().beta.len(),
|
||||
Some(head) => head.borrow().beta_g2.len(),
|
||||
None => {
|
||||
// TODO: this is a really weird edge case. You're trying to sum an EMPTY iterator
|
||||
// of VerificationKey. So should it panic here or just return some nonsense value?
|
||||
@@ -239,7 +292,8 @@ impl VerificationKey {
|
||||
pub(crate) fn identity(beta_size: usize) -> Self {
|
||||
VerificationKey {
|
||||
alpha: G2Projective::identity(),
|
||||
beta: vec![G2Projective::identity(); beta_size],
|
||||
beta_g1: vec![G1Projective::identity(); beta_size],
|
||||
beta_g2: vec![G2Projective::identity(); beta_size],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -251,19 +305,31 @@ impl VerificationKey {
|
||||
&self.alpha
|
||||
}
|
||||
|
||||
pub fn beta(&self) -> &Vec<G2Projective> {
|
||||
&self.beta
|
||||
pub fn beta_g1(&self) -> &Vec<G1Projective> {
|
||||
&self.beta_g1
|
||||
}
|
||||
|
||||
pub fn beta_g2(&self) -> &Vec<G2Projective> {
|
||||
&self.beta_g2
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
let beta_len = self.beta.len() as u64;
|
||||
let mut bytes = Vec::with_capacity(8 + (beta_len + 1) as usize * 96);
|
||||
let beta_g1_len = self.beta_g1.len();
|
||||
let beta_g2_len = self.beta_g2.len();
|
||||
let mut bytes = Vec::with_capacity(96 + 8 + beta_g1_len * 48 + beta_g2_len * 96);
|
||||
|
||||
bytes.extend_from_slice(&self.alpha.to_affine().to_compressed());
|
||||
bytes.extend_from_slice(&beta_len.to_le_bytes());
|
||||
for beta in self.beta.iter() {
|
||||
bytes.extend_from_slice(&beta.to_affine().to_compressed())
|
||||
|
||||
bytes.extend_from_slice(&beta_g1_len.to_le_bytes());
|
||||
|
||||
for beta_g1 in self.beta_g1.iter() {
|
||||
bytes.extend_from_slice(&beta_g1.to_affine().to_compressed())
|
||||
}
|
||||
|
||||
for beta_g2 in self.beta_g2.iter() {
|
||||
bytes.extend_from_slice(&beta_g2.to_affine().to_compressed())
|
||||
}
|
||||
|
||||
bytes
|
||||
}
|
||||
|
||||
@@ -488,11 +554,11 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn keypair_bytes_roundtrip() {
|
||||
let mut params1 = setup(1).unwrap();
|
||||
let mut params5 = setup(5).unwrap();
|
||||
let params1 = setup(1).unwrap();
|
||||
let params5 = setup(5).unwrap();
|
||||
|
||||
let keypair1 = keygen(&mut params1);
|
||||
let keypair5 = keygen(&mut params5);
|
||||
let keypair1 = keygen(¶ms1);
|
||||
let keypair5 = keygen(¶ms5);
|
||||
|
||||
let bytes1 = keypair1.to_bytes();
|
||||
let bytes5 = keypair5.to_bytes();
|
||||
@@ -503,11 +569,11 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn secret_key_bytes_roundtrip() {
|
||||
let mut params1 = setup(1).unwrap();
|
||||
let mut params5 = setup(5).unwrap();
|
||||
let params1 = setup(1).unwrap();
|
||||
let params5 = setup(5).unwrap();
|
||||
|
||||
let keypair1 = keygen(&mut params1);
|
||||
let keypair5 = keygen(&mut params5);
|
||||
let keypair1 = keygen(¶ms1);
|
||||
let keypair5 = keygen(¶ms5);
|
||||
|
||||
let bytes1 = keypair1.secret_key.to_bytes();
|
||||
let bytes5 = keypair5.secret_key.to_bytes();
|
||||
@@ -518,11 +584,11 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn verification_key_bytes_roundtrip() {
|
||||
let mut params1 = setup(1).unwrap();
|
||||
let mut params5 = setup(5).unwrap();
|
||||
let params1 = setup(1).unwrap();
|
||||
let params5 = setup(5).unwrap();
|
||||
|
||||
let keypair1 = &keygen(&mut params1);
|
||||
let keypair5 = &keygen(&mut params5);
|
||||
let keypair1 = &keygen(¶ms1);
|
||||
let keypair5 = &keygen(¶ms5);
|
||||
|
||||
let bytes1: Vec<u8> = keypair1.verification_key.to_bytes();
|
||||
let bytes5: Vec<u8> = keypair5.verification_key.to_bytes();
|
||||
|
||||
@@ -11,13 +11,12 @@ use group::Curve;
|
||||
|
||||
pub use keygen::{SecretKey, VerificationKey};
|
||||
|
||||
use crate::elgamal::Ciphertext;
|
||||
use crate::error::{CoconutError, Result};
|
||||
use crate::scheme::setup::Parameters;
|
||||
use crate::scheme::verification::check_bilinear_pairing;
|
||||
use crate::traits::{Base58, Bytable};
|
||||
use crate::utils::try_deserialize_g1_projective;
|
||||
use crate::{elgamal, Attribute};
|
||||
use crate::Attribute;
|
||||
|
||||
pub mod aggregation;
|
||||
pub mod issuance;
|
||||
@@ -105,7 +104,7 @@ impl Base58 for Signature {}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
pub struct BlindedSignature(G1Projective, elgamal::Ciphertext);
|
||||
pub struct BlindedSignature(G1Projective, G1Projective);
|
||||
|
||||
impl Bytable for BlindedSignature {
|
||||
fn to_byte_vec(&self) -> Vec<u8> {
|
||||
@@ -123,22 +122,26 @@ impl TryFrom<&[u8]> for BlindedSignature {
|
||||
type Error = CoconutError;
|
||||
|
||||
fn try_from(bytes: &[u8]) -> Result<BlindedSignature> {
|
||||
if bytes.len() != 144 {
|
||||
if bytes.len() != 96 {
|
||||
return Err(CoconutError::Deserialization(format!(
|
||||
"BlindedSignature must be exactly 144 bytes, got {}",
|
||||
"BlindedSignature must be exactly 96 bytes, got {}",
|
||||
bytes.len()
|
||||
)));
|
||||
}
|
||||
|
||||
let h_bytes: &[u8; 48] = &bytes[..48].try_into().expect("Slice size != 48");
|
||||
let sig_bytes: &[u8; 48] = &bytes[48..].try_into().expect("Slice size != 48");
|
||||
|
||||
let h = try_deserialize_g1_projective(
|
||||
h_bytes,
|
||||
CoconutError::Deserialization("Failed to deserialize compressed h".to_string()),
|
||||
)?;
|
||||
let c_tilde = Ciphertext::try_from(&bytes[48..])?;
|
||||
let sig = try_deserialize_g1_projective(
|
||||
sig_bytes,
|
||||
CoconutError::Deserialization("Failed to deserialize compressed sig".to_string()),
|
||||
)?;
|
||||
|
||||
Ok(BlindedSignature(h, c_tilde))
|
||||
Ok(BlindedSignature(h, sig))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,16 +149,15 @@ impl BlindedSignature {
|
||||
pub fn unblind(
|
||||
&self,
|
||||
params: &Parameters,
|
||||
private_key: &elgamal::PrivateKey,
|
||||
partial_verification_key: &VerificationKey,
|
||||
private_attributes: &[Attribute],
|
||||
public_attributes: &[Attribute],
|
||||
commitment_hash: &G1Projective,
|
||||
pedersen_commitments_openings: &[Scalar],
|
||||
) -> Result<Signature> {
|
||||
// parse the signature
|
||||
let h = &self.0;
|
||||
let c = &self.1;
|
||||
let sig2 = private_key.decrypt(c);
|
||||
|
||||
// Verify the commitment hash
|
||||
if !(commitment_hash == h) {
|
||||
@@ -164,20 +166,29 @@ impl BlindedSignature {
|
||||
));
|
||||
}
|
||||
|
||||
let blinding_removers = partial_verification_key
|
||||
.beta_g1
|
||||
.iter()
|
||||
.zip(pedersen_commitments_openings.iter())
|
||||
.map(|(beta, opening)| beta * opening)
|
||||
.sum::<G1Projective>();
|
||||
|
||||
let unblinded_c = c - blinding_removers;
|
||||
|
||||
let alpha = partial_verification_key.alpha;
|
||||
|
||||
let tmp = private_attributes
|
||||
let signed_attributes = private_attributes
|
||||
.iter()
|
||||
.chain(public_attributes.iter())
|
||||
.zip(partial_verification_key.beta.iter())
|
||||
.zip(partial_verification_key.beta_g2.iter())
|
||||
.map(|(attr, beta_i)| beta_i * attr)
|
||||
.sum::<G2Projective>();
|
||||
|
||||
// Verify the signature share
|
||||
if !check_bilinear_pairing(
|
||||
&h.to_affine(),
|
||||
&G2Prepared::from((alpha + tmp).to_affine()),
|
||||
&sig2.to_affine(),
|
||||
&G2Prepared::from((alpha + signed_attributes).to_affine()),
|
||||
&unblinded_c.to_affine(),
|
||||
params.prepared_miller_g2(),
|
||||
) {
|
||||
return Err(CoconutError::Unblind(
|
||||
@@ -185,13 +196,13 @@ impl BlindedSignature {
|
||||
));
|
||||
}
|
||||
|
||||
Ok(Signature(self.0, sig2))
|
||||
Ok(Signature(*h, unblinded_c))
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self) -> [u8; 144] {
|
||||
let mut bytes = [0u8; 144];
|
||||
pub fn to_bytes(&self) -> [u8; 96] {
|
||||
let mut bytes = [0u8; 96];
|
||||
bytes[..48].copy_from_slice(&self.0.to_affine().to_compressed());
|
||||
bytes[48..].copy_from_slice(&self.1.to_bytes());
|
||||
bytes[48..].copy_from_slice(&self.1.to_affine().to_compressed());
|
||||
bytes
|
||||
}
|
||||
|
||||
@@ -239,124 +250,97 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn unblind_returns_error_if_integrity_check_on_commitment_hash_fails() {
|
||||
let mut params = Parameters::new(2).unwrap();
|
||||
let params = Parameters::new(2).unwrap();
|
||||
let private_attributes = params.n_random_scalars(2 as usize);
|
||||
let elgamal_keypair = elgamal::elgamal_keygen(&mut params);
|
||||
|
||||
let lambda =
|
||||
prepare_blind_sign(&mut params, &elgamal_keypair, &private_attributes, &[]).unwrap();
|
||||
let (_commitments_openings, lambda) =
|
||||
prepare_blind_sign(¶ms, &private_attributes, &[]).unwrap();
|
||||
|
||||
let keypair1 = keygen(&mut params);
|
||||
let keypair1 = keygen(¶ms);
|
||||
|
||||
let sig1 = blind_sign(
|
||||
&mut params,
|
||||
&keypair1.secret_key(),
|
||||
elgamal_keypair.public_key(),
|
||||
&lambda,
|
||||
&[],
|
||||
)
|
||||
.unwrap();
|
||||
let sig1 = blind_sign(¶ms, &keypair1.secret_key(), &lambda, &[]).unwrap();
|
||||
|
||||
let wrong_commitment_opening = params.random_scalar();
|
||||
let wrong_commitment = params.gen1() * wrong_commitment_opening;
|
||||
let fake_commitment_hash = hash_g1(wrong_commitment.to_bytes());
|
||||
let wrong_commitments_openings = params.n_random_scalars(private_attributes.len());
|
||||
|
||||
assert!(sig1
|
||||
.unblind(
|
||||
¶ms,
|
||||
elgamal_keypair.private_key(),
|
||||
&keypair1.verification_key(),
|
||||
&private_attributes,
|
||||
&[],
|
||||
&fake_commitment_hash,
|
||||
&wrong_commitments_openings,
|
||||
)
|
||||
.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unblind_returns_error_if_signature_verification_fails() {
|
||||
let mut params = Parameters::new(2).unwrap();
|
||||
let params = Parameters::new(2).unwrap();
|
||||
let private_attributes = vec![hash_to_scalar("Attribute1"), hash_to_scalar("Attribute2")];
|
||||
let private_attributes2 = vec![hash_to_scalar("Attribute3"), hash_to_scalar("Attribute4")];
|
||||
let elgamal_keypair = elgamal::elgamal_keygen(&mut params);
|
||||
|
||||
let lambda =
|
||||
prepare_blind_sign(&mut params, &elgamal_keypair, &private_attributes, &[]).unwrap();
|
||||
let (commitments_openings, lambda) =
|
||||
prepare_blind_sign(¶ms, &private_attributes, &[]).unwrap();
|
||||
|
||||
let keypair1 = keygen(&mut params);
|
||||
let keypair1 = keygen(¶ms);
|
||||
|
||||
let sig1 = blind_sign(
|
||||
&mut params,
|
||||
&keypair1.secret_key(),
|
||||
elgamal_keypair.public_key(),
|
||||
&lambda,
|
||||
&[],
|
||||
)
|
||||
.unwrap();
|
||||
let sig1 = blind_sign(¶ms, &keypair1.secret_key(), &lambda, &[]).unwrap();
|
||||
|
||||
assert!(sig1
|
||||
.unblind(
|
||||
¶ms,
|
||||
elgamal_keypair.private_key(),
|
||||
&keypair1.verification_key(),
|
||||
&private_attributes2,
|
||||
&[],
|
||||
&lambda.get_commitment_hash(),
|
||||
&commitments_openings,
|
||||
)
|
||||
.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verification_on_two_private_attributes() {
|
||||
let mut params = Parameters::new(2).unwrap();
|
||||
let params = Parameters::new(2).unwrap();
|
||||
let serial_number = params.random_scalar();
|
||||
let binding_number = params.random_scalar();
|
||||
let private_attributes = vec![serial_number, binding_number];
|
||||
let elgamal_keypair = elgamal::elgamal_keygen(&mut params);
|
||||
|
||||
let keypair1 = keygen(&mut params);
|
||||
let keypair2 = keygen(&mut params);
|
||||
let keypair1 = keygen(¶ms);
|
||||
let keypair2 = keygen(¶ms);
|
||||
|
||||
let lambda =
|
||||
prepare_blind_sign(&mut params, &elgamal_keypair, &private_attributes, &[]).unwrap();
|
||||
let (commitments_openings, lambda) =
|
||||
prepare_blind_sign(¶ms, &private_attributes, &[]).unwrap();
|
||||
|
||||
let sig1 = blind_sign(
|
||||
&mut params,
|
||||
&keypair1.secret_key(),
|
||||
elgamal_keypair.public_key(),
|
||||
&lambda,
|
||||
&[],
|
||||
)
|
||||
let sig1 = blind_sign(¶ms, &keypair1.secret_key(), &lambda, &[])
|
||||
.unwrap()
|
||||
.unblind(
|
||||
¶ms,
|
||||
elgamal_keypair.private_key(),
|
||||
&keypair1.verification_key(),
|
||||
&private_attributes,
|
||||
&[],
|
||||
&lambda.get_commitment_hash(),
|
||||
&commitments_openings,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let sig2 = blind_sign(
|
||||
&mut params,
|
||||
&keypair2.secret_key(),
|
||||
elgamal_keypair.public_key(),
|
||||
&lambda,
|
||||
&[],
|
||||
)
|
||||
let sig2 = blind_sign(¶ms, &keypair2.secret_key(), &lambda, &[])
|
||||
.unwrap()
|
||||
.unblind(
|
||||
¶ms,
|
||||
elgamal_keypair.private_key(),
|
||||
&keypair2.verification_key(),
|
||||
&private_attributes,
|
||||
&[],
|
||||
&lambda.get_commitment_hash(),
|
||||
&commitments_openings,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let theta1 = prove_bandwidth_credential(
|
||||
&mut params,
|
||||
¶ms,
|
||||
&keypair1.verification_key(),
|
||||
&sig1,
|
||||
serial_number,
|
||||
@@ -365,7 +349,7 @@ mod tests {
|
||||
.unwrap();
|
||||
|
||||
let theta2 = prove_bandwidth_credential(
|
||||
&mut params,
|
||||
¶ms,
|
||||
&keypair2.verification_key(),
|
||||
&sig2,
|
||||
serial_number,
|
||||
@@ -400,8 +384,8 @@ mod tests {
|
||||
let mut params = Parameters::new(2).unwrap();
|
||||
let attributes = params.n_random_scalars(2);
|
||||
|
||||
let keypair1 = keygen(&mut params);
|
||||
let keypair2 = keygen(&mut params);
|
||||
let keypair1 = keygen(¶ms);
|
||||
let keypair2 = keygen(¶ms);
|
||||
let sig1 = sign(&mut params, &keypair1.secret_key(), &attributes).unwrap();
|
||||
let sig2 = sign(&mut params, &keypair2.secret_key(), &attributes).unwrap();
|
||||
|
||||
@@ -429,62 +413,44 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn verification_on_two_public_and_two_private_attributes() {
|
||||
let mut params = Parameters::new(4).unwrap();
|
||||
let params = Parameters::new(4).unwrap();
|
||||
let public_attributes = params.n_random_scalars(2);
|
||||
let serial_number = params.random_scalar();
|
||||
let binding_number = params.random_scalar();
|
||||
let private_attributes = vec![serial_number, binding_number];
|
||||
let elgamal_keypair = elgamal::elgamal_keygen(&mut params);
|
||||
|
||||
let keypair1 = keygen(&mut params);
|
||||
let keypair2 = keygen(&mut params);
|
||||
let keypair1 = keygen(¶ms);
|
||||
let keypair2 = keygen(¶ms);
|
||||
|
||||
let lambda = prepare_blind_sign(
|
||||
&mut params,
|
||||
&elgamal_keypair,
|
||||
&private_attributes,
|
||||
&public_attributes,
|
||||
)
|
||||
.unwrap();
|
||||
let (commitments_openings, lambda) =
|
||||
prepare_blind_sign(¶ms, &private_attributes, &public_attributes).unwrap();
|
||||
|
||||
let sig1 = blind_sign(
|
||||
&mut params,
|
||||
&keypair1.secret_key(),
|
||||
elgamal_keypair.public_key(),
|
||||
&lambda,
|
||||
&public_attributes,
|
||||
)
|
||||
let sig1 = blind_sign(¶ms, &keypair1.secret_key(), &lambda, &public_attributes)
|
||||
.unwrap()
|
||||
.unblind(
|
||||
¶ms,
|
||||
elgamal_keypair.private_key(),
|
||||
&keypair1.verification_key(),
|
||||
&private_attributes,
|
||||
&public_attributes,
|
||||
&lambda.get_commitment_hash(),
|
||||
&commitments_openings,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let sig2 = blind_sign(
|
||||
&mut params,
|
||||
&keypair2.secret_key(),
|
||||
elgamal_keypair.public_key(),
|
||||
&lambda,
|
||||
&public_attributes,
|
||||
)
|
||||
let sig2 = blind_sign(¶ms, &keypair2.secret_key(), &lambda, &public_attributes)
|
||||
.unwrap()
|
||||
.unblind(
|
||||
¶ms,
|
||||
elgamal_keypair.private_key(),
|
||||
&keypair2.verification_key(),
|
||||
&private_attributes,
|
||||
&public_attributes,
|
||||
&lambda.get_commitment_hash(),
|
||||
&commitments_openings,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let theta1 = prove_bandwidth_credential(
|
||||
&mut params,
|
||||
¶ms,
|
||||
&keypair1.verification_key(),
|
||||
&sig1,
|
||||
serial_number,
|
||||
@@ -493,7 +459,7 @@ mod tests {
|
||||
.unwrap();
|
||||
|
||||
let theta2 = prove_bandwidth_credential(
|
||||
&mut params,
|
||||
¶ms,
|
||||
&keypair2.verification_key(),
|
||||
&sig2,
|
||||
serial_number,
|
||||
@@ -525,41 +491,29 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn verification_on_two_public_and_two_private_attributes_from_two_signers() {
|
||||
let mut params = Parameters::new(4).unwrap();
|
||||
let params = Parameters::new(4).unwrap();
|
||||
let public_attributes = params.n_random_scalars(2);
|
||||
let serial_number = params.random_scalar();
|
||||
let binding_number = params.random_scalar();
|
||||
let private_attributes = vec![serial_number, binding_number];
|
||||
let elgamal_keypair = elgamal::elgamal_keygen(¶ms);
|
||||
|
||||
let keypairs = ttp_keygen(&mut params, 2, 3).unwrap();
|
||||
let keypairs = ttp_keygen(¶ms, 2, 3).unwrap();
|
||||
|
||||
let lambda = prepare_blind_sign(
|
||||
&mut params,
|
||||
&elgamal_keypair,
|
||||
&private_attributes,
|
||||
&public_attributes,
|
||||
)
|
||||
.unwrap();
|
||||
let (commitments_openings, lambda) =
|
||||
prepare_blind_sign(¶ms, &private_attributes, &public_attributes).unwrap();
|
||||
|
||||
let sigs = keypairs
|
||||
.iter()
|
||||
.map(|keypair| {
|
||||
blind_sign(
|
||||
&mut params,
|
||||
&keypair.secret_key(),
|
||||
elgamal_keypair.public_key(),
|
||||
&lambda,
|
||||
&public_attributes,
|
||||
)
|
||||
blind_sign(¶ms, &keypair.secret_key(), &lambda, &public_attributes)
|
||||
.unwrap()
|
||||
.unblind(
|
||||
¶ms,
|
||||
elgamal_keypair.private_key(),
|
||||
&keypair.verification_key(),
|
||||
&private_attributes,
|
||||
&public_attributes,
|
||||
&lambda.get_commitment_hash(),
|
||||
&commitments_openings,
|
||||
)
|
||||
.unwrap()
|
||||
})
|
||||
@@ -579,13 +533,8 @@ mod tests {
|
||||
aggregate_signatures(¶ms, &aggr_vk, &attributes, &sigs[..2], Some(&[1, 2]))
|
||||
.unwrap();
|
||||
|
||||
let theta = prove_bandwidth_credential(
|
||||
&mut params,
|
||||
&aggr_vk,
|
||||
&aggr_sig,
|
||||
serial_number,
|
||||
binding_number,
|
||||
)
|
||||
let theta =
|
||||
prove_bandwidth_credential(¶ms, &aggr_vk, &aggr_sig, serial_number, binding_number)
|
||||
.unwrap();
|
||||
|
||||
assert!(verify_credential(
|
||||
@@ -601,13 +550,8 @@ mod tests {
|
||||
aggregate_signatures(¶ms, &aggr_vk, &attributes, &sigs[1..], Some(&[2, 3]))
|
||||
.unwrap();
|
||||
|
||||
let theta = prove_bandwidth_credential(
|
||||
&mut params,
|
||||
&aggr_vk,
|
||||
&aggr_sig,
|
||||
serial_number,
|
||||
binding_number,
|
||||
)
|
||||
let theta =
|
||||
prove_bandwidth_credential(¶ms, &aggr_vk, &aggr_sig, serial_number, binding_number)
|
||||
.unwrap();
|
||||
|
||||
assert!(verify_credential(
|
||||
@@ -641,18 +585,13 @@ mod tests {
|
||||
let params = Parameters::default();
|
||||
let r = params.random_scalar();
|
||||
let s = params.random_scalar();
|
||||
let t = params.random_scalar();
|
||||
let blinded_sig = BlindedSignature(
|
||||
params.gen1() * t,
|
||||
Ciphertext(params.gen1() * r, params.gen1() * s),
|
||||
);
|
||||
let blinded_sig = BlindedSignature(params.gen1() * r, params.gen1() * s);
|
||||
let bytes = blinded_sig.to_bytes();
|
||||
|
||||
// also make sure it is equivalent to the internal g1 compressed bytes concatenated
|
||||
let expected_bytes = [
|
||||
blinded_sig.0.to_affine().to_compressed(),
|
||||
blinded_sig.1 .0.to_affine().to_compressed(),
|
||||
blinded_sig.1 .1.to_affine().to_compressed(),
|
||||
blinded_sig.1.to_affine().to_compressed(),
|
||||
]
|
||||
.concat();
|
||||
assert_eq!(expected_bytes, bytes);
|
||||
|
||||
@@ -124,7 +124,7 @@ pub fn compute_kappa(
|
||||
+ verification_key.alpha
|
||||
+ private_attributes
|
||||
.iter()
|
||||
.zip(verification_key.beta.iter())
|
||||
.zip(verification_key.beta_g2.iter())
|
||||
.map(|(priv_attr, beta_i)| beta_i * priv_attr)
|
||||
.sum::<G2Projective>()
|
||||
}
|
||||
@@ -140,11 +140,11 @@ pub fn prove_bandwidth_credential(
|
||||
serial_number: Attribute,
|
||||
binding_number: Attribute,
|
||||
) -> Result<Theta> {
|
||||
if verification_key.beta.len() < 2 {
|
||||
if verification_key.beta_g2.len() < 2 {
|
||||
return Err(
|
||||
CoconutError::Verification(
|
||||
format!("Tried to prove a credential for higher than supported by the provided verification key number of attributes (max: {}, requested: 2)",
|
||||
verification_key.beta.len()
|
||||
verification_key.beta_g2.len()
|
||||
)));
|
||||
}
|
||||
|
||||
@@ -205,7 +205,9 @@ pub fn verify_credential(
|
||||
theta: &Theta,
|
||||
public_attributes: &[Attribute],
|
||||
) -> bool {
|
||||
if public_attributes.len() + theta.pi_v.private_attributes_len() > verification_key.beta.len() {
|
||||
if public_attributes.len() + theta.pi_v.private_attributes_len()
|
||||
> verification_key.beta_g2.len()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -220,7 +222,7 @@ pub fn verify_credential(
|
||||
.iter()
|
||||
.zip(
|
||||
verification_key
|
||||
.beta
|
||||
.beta_g2
|
||||
.iter()
|
||||
.skip(theta.pi_v.private_attributes_len()),
|
||||
)
|
||||
@@ -249,7 +251,7 @@ pub fn verify(
|
||||
let kappa = (verification_key.alpha
|
||||
+ public_attributes
|
||||
.iter()
|
||||
.zip(verification_key.beta.iter())
|
||||
.zip(verification_key.beta_g2.iter())
|
||||
.map(|(m_i, b_i)| b_i * m_i)
|
||||
.sum::<G2Projective>())
|
||||
.to_affine();
|
||||
@@ -271,9 +273,9 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn theta_bytes_roundtrip() {
|
||||
let mut params = setup(2).unwrap();
|
||||
let params = setup(2).unwrap();
|
||||
|
||||
let keypair = keygen(&mut params);
|
||||
let keypair = keygen(¶ms);
|
||||
let r = params.random_scalar();
|
||||
let s = params.random_scalar();
|
||||
|
||||
@@ -282,7 +284,7 @@ mod tests {
|
||||
let binding_number = params.random_scalar();
|
||||
|
||||
let theta = prove_bandwidth_credential(
|
||||
&mut params,
|
||||
¶ms,
|
||||
&keypair.verification_key(),
|
||||
&signature,
|
||||
serial_number,
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
use crate::{
|
||||
aggregate_signature_shares, aggregate_verification_keys, blind_sign, elgamal_keygen,
|
||||
prepare_blind_sign, prove_bandwidth_credential, setup, ttp_keygen, verify_credential,
|
||||
CoconutError, Signature, SignatureShare, VerificationKey,
|
||||
aggregate_signature_shares, aggregate_verification_keys, blind_sign, prepare_blind_sign,
|
||||
prove_bandwidth_credential, setup, ttp_keygen, verify_credential, CoconutError, Signature,
|
||||
SignatureShare, VerificationKey,
|
||||
};
|
||||
|
||||
use itertools::izip;
|
||||
|
||||
#[test]
|
||||
fn main() -> Result<(), CoconutError> {
|
||||
let params = setup(5)?;
|
||||
@@ -13,15 +15,9 @@ fn main() -> Result<(), CoconutError> {
|
||||
let binding_number = params.random_scalar();
|
||||
let private_attributes = vec![serial_number, binding_number];
|
||||
|
||||
let elgamal_keypair = elgamal_keygen(¶ms);
|
||||
|
||||
// generate commitment and encryption
|
||||
let blind_sign_request = prepare_blind_sign(
|
||||
¶ms,
|
||||
&elgamal_keypair,
|
||||
&private_attributes,
|
||||
&public_attributes,
|
||||
)?;
|
||||
// generate commitment
|
||||
let (commitments_openings, blind_sign_request) =
|
||||
prepare_blind_sign(¶ms, &private_attributes, &public_attributes)?;
|
||||
|
||||
// generate_keys
|
||||
let coconut_keypairs = ttp_keygen(¶ms, 2, 3)?;
|
||||
@@ -41,7 +37,6 @@ fn main() -> Result<(), CoconutError> {
|
||||
let blinded_signature = blind_sign(
|
||||
¶ms,
|
||||
&keypair.secret_key(),
|
||||
&elgamal_keypair.public_key(),
|
||||
&blind_sign_request,
|
||||
&public_attributes,
|
||||
)?;
|
||||
@@ -49,26 +44,22 @@ fn main() -> Result<(), CoconutError> {
|
||||
}
|
||||
|
||||
// Unblind
|
||||
|
||||
let unblinded_signatures: Vec<Signature> = blinded_signatures
|
||||
.into_iter()
|
||||
.zip(verification_keys.iter())
|
||||
.map(|(signature, verification_key)| {
|
||||
signature
|
||||
.unblind(
|
||||
let unblinded_signatures: Vec<Signature> =
|
||||
izip!(blinded_signatures.iter(), verification_keys.iter())
|
||||
.map(|(s, vk)| {
|
||||
s.unblind(
|
||||
¶ms,
|
||||
&elgamal_keypair.private_key(),
|
||||
&verification_key,
|
||||
&vk,
|
||||
&private_attributes,
|
||||
&public_attributes,
|
||||
&blind_sign_request.get_commitment_hash(),
|
||||
&commitments_openings,
|
||||
)
|
||||
.unwrap()
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Aggregate signatures
|
||||
|
||||
let signature_shares: Vec<SignatureShare> = unblinded_signatures
|
||||
.iter()
|
||||
.enumerate()
|
||||
@@ -84,7 +75,6 @@ fn main() -> Result<(), CoconutError> {
|
||||
aggregate_signature_shares(¶ms, &verification_key, &attributes, &signature_shares)?;
|
||||
|
||||
// Generate cryptographic material to verify them
|
||||
|
||||
let theta = prove_bandwidth_credential(
|
||||
¶ms,
|
||||
&verification_key,
|
||||
@@ -94,7 +84,6 @@ fn main() -> Result<(), CoconutError> {
|
||||
)?;
|
||||
|
||||
// Verify credentials
|
||||
|
||||
assert!(verify_credential(
|
||||
¶ms,
|
||||
&verification_key,
|
||||
|
||||
@@ -923,7 +923,7 @@ mod fragment_header {
|
||||
|
||||
let header_bytes = fragmented_header.to_bytes();
|
||||
let header_bytes = &header_bytes[..header_bytes.len() - 1];
|
||||
assert!(FragmentHeader::try_from_bytes(&header_bytes).is_err())
|
||||
assert!(FragmentHeader::try_from_bytes(header_bytes).is_err())
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -446,7 +446,7 @@ mod reconstruction_buffer {
|
||||
.collect();
|
||||
|
||||
for raw_fragment in raw_fragments.iter().take(u8::max_value() as usize - 1) {
|
||||
buf.insert_fragment(Fragment::try_from_bytes(&raw_fragment).unwrap());
|
||||
buf.insert_fragment(Fragment::try_from_bytes(raw_fragment).unwrap());
|
||||
}
|
||||
|
||||
assert!(!buf.is_complete);
|
||||
@@ -1268,7 +1268,7 @@ mod message_reconstructor {
|
||||
.collect();
|
||||
|
||||
for raw_fragment in raw_fragments.iter().take(u8::max_value() as usize) {
|
||||
set_buf1.insert_fragment(Fragment::try_from_bytes(&raw_fragment).unwrap());
|
||||
set_buf1.insert_fragment(Fragment::try_from_bytes(raw_fragment).unwrap());
|
||||
}
|
||||
|
||||
set_buf2.insert_fragment(Fragment::try_from_bytes(&raw_fragments[255]).unwrap());
|
||||
|
||||
@@ -162,7 +162,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn is_cover_works_for_identical_input() {
|
||||
assert!(is_cover(&LOOP_COVER_MESSAGE_PAYLOAD))
|
||||
assert!(is_cover(LOOP_COVER_MESSAGE_PAYLOAD))
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -123,7 +123,7 @@ mod tests {
|
||||
let read_data = available_reader.next().await.unwrap().unwrap();
|
||||
|
||||
assert_eq!(read_data, data);
|
||||
assert!(available_reader.next().await.is_none())
|
||||
assert!(available_reader.next().await.is_none());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -135,7 +135,7 @@ mod tests {
|
||||
let read_data = available_reader.next().await.unwrap().unwrap();
|
||||
|
||||
assert_eq!(read_data, data);
|
||||
assert!(available_reader.next().await.is_none())
|
||||
assert!(available_reader.next().await.is_none());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -157,7 +157,7 @@ mod tests {
|
||||
|
||||
// before dropping the mock, we need to empty it
|
||||
let mut buf = vec![0u8; second_data_chunk.len()];
|
||||
reader_mock.read(&mut buf).await.unwrap();
|
||||
assert_eq!(reader_mock.read(&mut buf).await.unwrap(), 100);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -173,7 +173,7 @@ mod tests {
|
||||
let read_data = available_reader.next().await.unwrap().unwrap();
|
||||
|
||||
assert_eq!(read_data, data);
|
||||
assert!(available_reader.next().await.is_none())
|
||||
assert!(available_reader.next().await.is_none());
|
||||
}
|
||||
|
||||
// perhaps the issue of tokio io builder will be resolved in tokio 0.3?
|
||||
|
||||
Generated
+21
-9
@@ -218,9 +218,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cosmwasm-crypto"
|
||||
version = "1.0.0-beta4"
|
||||
version = "1.0.0-beta5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f903ebbabc0d4880dbc76148efb8be8fc29fa4bf294c440c3d70da1c8bcafff7"
|
||||
checksum = "8904127a5b9e325ef5d6b2b3f997dcd74943cd35097139b1a4d15b1b6bccae66"
|
||||
dependencies = [
|
||||
"digest 0.9.0",
|
||||
"ed25519-zebra",
|
||||
@@ -231,9 +231,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cosmwasm-derive"
|
||||
version = "1.0.0-beta4"
|
||||
version = "1.0.0-beta5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "832bebef577ecb394603de8e2bf0de429b74aa364e17dec18e15ce37e71b0cae"
|
||||
checksum = "a14364ac4d9d085867929d0cf3e94b1d2100121ce02c33c72961406830002613"
|
||||
dependencies = [
|
||||
"syn",
|
||||
]
|
||||
@@ -250,9 +250,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cosmwasm-std"
|
||||
version = "1.0.0-beta4"
|
||||
version = "1.0.0-beta5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6238c45840cc9de5a39f0f619e3a4f7c38c5d2c6ac9e3e4d72751ee045e6d7da"
|
||||
checksum = "e2ece12e5bbde434b93937d7b2107e6291f11d69ffa72398c50e8bab41d451d3"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"cosmwasm-crypto",
|
||||
@@ -368,6 +368,17 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cw-storage-plus"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c087ff98fb0475db4c2b5298a5fd12b2848d2854b39d1115d930ee6da24d1eed"
|
||||
dependencies = [
|
||||
"cosmwasm-std",
|
||||
"schemars",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "der"
|
||||
version = "0.4.5"
|
||||
@@ -804,13 +815,14 @@ checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
|
||||
name = "mixnet-contract"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"az",
|
||||
"bs58",
|
||||
"config",
|
||||
"cosmwasm-schema",
|
||||
"cosmwasm-std",
|
||||
"cosmwasm-storage",
|
||||
"crypto",
|
||||
"cw-storage-plus",
|
||||
"cw-storage-plus 0.12.1",
|
||||
"fixed",
|
||||
"mixnet-contract-common",
|
||||
"rand",
|
||||
@@ -1512,7 +1524,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"config",
|
||||
"cosmwasm-std",
|
||||
"cw-storage-plus",
|
||||
"cw-storage-plus 0.12.1",
|
||||
"mixnet-contract-common",
|
||||
"schemars",
|
||||
"serde",
|
||||
@@ -1526,7 +1538,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"config",
|
||||
"cosmwasm-std",
|
||||
"cw-storage-plus",
|
||||
"cw-storage-plus 0.11.1",
|
||||
"mixnet-contract-common",
|
||||
"schemars",
|
||||
"serde",
|
||||
|
||||
@@ -22,8 +22,9 @@ config = { path = "../../common/config"}
|
||||
|
||||
cosmwasm-std = "1.0.0-beta3"
|
||||
cosmwasm-storage = "1.0.0-beta3"
|
||||
cw-storage-plus = "0.11.1"
|
||||
cw-storage-plus = "0.12.1"
|
||||
|
||||
az = "1.2.0"
|
||||
bs58 = "0.4.0"
|
||||
schemars = "0.8"
|
||||
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
|
||||
|
||||
@@ -16,5 +16,6 @@ pub const ACTIVE_SET_WORK_FACTOR: u8 = 10;
|
||||
// and we can't change this easily to `Duration`, because then the entire rewarded set storage
|
||||
// would be messed up... (as we look up stuff "by blocks")
|
||||
pub const REWARDED_SET_REFRESH_BLOCKS: u64 = 720; // with blocktime being approximately 5s, it should be roughly 1h
|
||||
pub const EPOCHS_IN_INTERVAL: u64 = 720; // Hours in a month
|
||||
|
||||
pub const REWARDING_INTERVAL_LENGTH: Duration = Duration::from_secs(60 * 60 * 720); // 720h, i.e. 30 days
|
||||
|
||||
@@ -86,7 +86,7 @@ pub fn instantiate(
|
||||
mixnet_params_storage::CONTRACT_STATE.save(deps.storage, &state)?;
|
||||
mixnet_params_storage::LAYERS.save(deps.storage, &Default::default())?;
|
||||
rewards_storage::REWARD_POOL.save(deps.storage, &Uint128::new(INITIAL_REWARD_POOL))?;
|
||||
interval_storage::CURRENT_INTERVAL.save(deps.storage, &rewarding_interval)?;
|
||||
interval_storage::save_interval(deps.storage, &rewarding_interval)?;
|
||||
interval_storage::CURRENT_REWARDED_SET_HEIGHT.save(deps.storage, &env.block.height)?;
|
||||
|
||||
Ok(Response::default())
|
||||
@@ -112,7 +112,7 @@ pub fn execute(
|
||||
owner_signature,
|
||||
),
|
||||
ExecuteMsg::UnbondMixnode {} => {
|
||||
crate::mixnodes::transactions::try_remove_mixnode(deps, info)
|
||||
crate::mixnodes::transactions::try_remove_mixnode(env, deps, info)
|
||||
}
|
||||
ExecuteMsg::UpdateMixnodeConfig {
|
||||
profit_margin_percent,
|
||||
@@ -168,19 +168,20 @@ pub fn execute(
|
||||
ExecuteMsg::UndelegateFromMixnode { mix_identity } => {
|
||||
crate::delegations::transactions::try_remove_delegation_from_mixnode(
|
||||
deps,
|
||||
env,
|
||||
info,
|
||||
mix_identity,
|
||||
)
|
||||
}
|
||||
ExecuteMsg::RewardNextMixDelegators {
|
||||
mix_identity,
|
||||
interval_id,
|
||||
} => crate::rewards::transactions::try_reward_next_mixnode_delegators(
|
||||
deps,
|
||||
info,
|
||||
mix_identity,
|
||||
interval_id,
|
||||
),
|
||||
// ExecuteMsg::RewardNextMixDelegators {
|
||||
// mix_identity,
|
||||
// interval_id,
|
||||
// } => crate::rewards::transactions::try_reward_next_mixnode_delegators(
|
||||
// deps,
|
||||
// info,
|
||||
// mix_identity,
|
||||
// interval_id,
|
||||
// ),
|
||||
ExecuteMsg::DelegateToMixnodeOnBehalf {
|
||||
mix_identity,
|
||||
delegate,
|
||||
@@ -196,6 +197,7 @@ pub fn execute(
|
||||
delegate,
|
||||
} => crate::delegations::transactions::try_remove_delegation_from_mixnode_on_behalf(
|
||||
deps,
|
||||
env,
|
||||
info,
|
||||
mix_identity,
|
||||
delegate,
|
||||
@@ -213,7 +215,7 @@ pub fn execute(
|
||||
owner_signature,
|
||||
),
|
||||
ExecuteMsg::UnbondMixnodeOnBehalf { owner } => {
|
||||
crate::mixnodes::transactions::try_remove_mixnode_on_behalf(deps, info, owner)
|
||||
crate::mixnodes::transactions::try_remove_mixnode_on_behalf(env, deps, info, owner)
|
||||
}
|
||||
ExecuteMsg::BondGatewayOnBehalf {
|
||||
gateway,
|
||||
@@ -243,6 +245,45 @@ pub fn execute(
|
||||
ExecuteMsg::AdvanceCurrentInterval {} => {
|
||||
crate::interval::transactions::try_advance_interval(env, deps.storage)
|
||||
}
|
||||
ExecuteMsg::AdvanceCurrentEpoch {} => {
|
||||
crate::interval::transactions::try_advance_epoch(env, deps.storage)
|
||||
}
|
||||
ExecuteMsg::CompoundDelegatorReward { mix_identity } => {
|
||||
crate::rewards::transactions::try_compound_delegator_reward(
|
||||
deps,
|
||||
env,
|
||||
info,
|
||||
mix_identity,
|
||||
)
|
||||
}
|
||||
ExecuteMsg::CompoundOperatorReward {} => {
|
||||
crate::rewards::transactions::try_compound_operator_reward(deps, env, info)
|
||||
}
|
||||
ExecuteMsg::CompoundDelegatorRewardOnBehalf {
|
||||
owner,
|
||||
mix_identity,
|
||||
} => crate::rewards::transactions::try_compound_delegator_reward_on_behalf(
|
||||
deps,
|
||||
env,
|
||||
info,
|
||||
owner,
|
||||
mix_identity,
|
||||
),
|
||||
ExecuteMsg::CompoundOperatorRewardOnBehalf { owner } => {
|
||||
crate::rewards::transactions::try_compound_operator_reward_on_behalf(
|
||||
deps, env, info, owner,
|
||||
)
|
||||
}
|
||||
ExecuteMsg::ReconcileDelegations {} => {
|
||||
crate::delegations::transactions::try_reconcile_all_delegation_events(deps, info)
|
||||
}
|
||||
ExecuteMsg::CheckpointMixnodes {} => {
|
||||
crate::mixnodes::transactions::try_checkpoint_mixnodes(
|
||||
deps.storage,
|
||||
env.block.height,
|
||||
info,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -288,7 +329,12 @@ pub fn query(deps: Deps<'_>, env: Env, msg: QueryMsg) -> Result<QueryResponse, C
|
||||
QueryMsg::GetDelegationDetails {
|
||||
mix_identity,
|
||||
delegator,
|
||||
} => to_binary(&query_mixnode_delegation(deps, mix_identity, delegator)?),
|
||||
} => to_binary(&query_mixnode_delegation(
|
||||
deps.storage,
|
||||
deps.api,
|
||||
mix_identity,
|
||||
delegator,
|
||||
)?),
|
||||
QueryMsg::GetRewardPool {} => to_binary(&query_reward_pool(deps)?),
|
||||
QueryMsg::GetCirculatingSupply {} => to_binary(&query_circulating_supply(deps)?),
|
||||
QueryMsg::GetIntervalRewardPercent {} => to_binary(&INTERVAL_REWARD_PERCENT),
|
||||
@@ -321,6 +367,7 @@ pub fn query(deps: Deps<'_>, env: Env, msg: QueryMsg) -> Result<QueryResponse, C
|
||||
QueryMsg::GetRewardedSetRefreshBlocks {} => {
|
||||
to_binary(&query_rewarded_set_refresh_minimum_blocks())
|
||||
}
|
||||
QueryMsg::GetEpochsInInterval {} => to_binary(&crate::constants::EPOCHS_IN_INTERVAL),
|
||||
};
|
||||
|
||||
Ok(query_res?)
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
|
||||
use super::storage;
|
||||
use crate::error::ContractError;
|
||||
use cosmwasm_std::Deps;
|
||||
use cosmwasm_std::Order;
|
||||
use cosmwasm_std::StdResult;
|
||||
use cosmwasm_std::{Api, Deps, Storage};
|
||||
use cw_storage_plus::{Bound, PrimaryKey};
|
||||
use mixnet_contract_common::{
|
||||
Delegation, IdentityKey, PagedAllDelegationsResponse, PagedDelegatorDelegationsResponse,
|
||||
@@ -14,16 +14,14 @@ use mixnet_contract_common::{
|
||||
|
||||
pub(crate) fn query_all_network_delegations_paged(
|
||||
deps: Deps<'_>,
|
||||
start_after: Option<(IdentityKey, String)>,
|
||||
start_after: Option<(IdentityKey, Vec<u8>, u64)>,
|
||||
limit: Option<u32>,
|
||||
) -> StdResult<PagedAllDelegationsResponse> {
|
||||
let limit = limit
|
||||
.unwrap_or(storage::DELEGATION_PAGE_DEFAULT_LIMIT)
|
||||
.min(storage::DELEGATION_PAGE_MAX_LIMIT) as usize;
|
||||
|
||||
let start = start_after
|
||||
.map(|start| start.joined_key())
|
||||
.map(Bound::exclusive);
|
||||
let start = start_after.map(Bound::exclusive);
|
||||
|
||||
let delegations = storage::delegations()
|
||||
.range(deps.storage, start, None, Order::Ascending)
|
||||
@@ -52,8 +50,9 @@ pub(crate) fn query_delegator_delegations_paged(
|
||||
let limit = limit
|
||||
.unwrap_or(storage::DELEGATION_PAGE_DEFAULT_LIMIT)
|
||||
.min(storage::DELEGATION_PAGE_MAX_LIMIT) as usize;
|
||||
let start = start_after
|
||||
.map(|mix_identity| Bound::Exclusive((mix_identity, validated_owner.clone()).joined_key()));
|
||||
let start = start_after.map(|mix_identity| {
|
||||
Bound::ExclusiveRaw((mix_identity, validated_owner.clone()).joined_key())
|
||||
});
|
||||
|
||||
let delegations = storage::delegations()
|
||||
.idx
|
||||
@@ -76,35 +75,47 @@ pub(crate) fn query_delegator_delegations_paged(
|
||||
|
||||
// queries for delegation value of given address for particular node
|
||||
pub(crate) fn query_mixnode_delegation(
|
||||
deps: Deps<'_>,
|
||||
storage: &dyn Storage,
|
||||
api: &dyn Api,
|
||||
mix_identity: IdentityKey,
|
||||
delegator: String,
|
||||
) -> Result<Delegation, ContractError> {
|
||||
let validated_delegator = deps.api.addr_validate(&delegator)?;
|
||||
let storage_key = (mix_identity.clone(), validated_delegator.clone()).joined_key();
|
||||
) -> Result<Vec<Delegation>, ContractError> {
|
||||
let validated_delegator = api.addr_validate(&delegator)?;
|
||||
let storage_key = (
|
||||
mix_identity.clone(),
|
||||
validated_delegator.as_bytes().to_vec(),
|
||||
);
|
||||
|
||||
storage::delegations()
|
||||
.may_load(deps.storage, storage_key)?
|
||||
.ok_or(ContractError::NoMixnodeDelegationFound {
|
||||
let delegations = storage::delegations()
|
||||
.prefix(storage_key)
|
||||
.range(storage, None, None, Order::Ascending)
|
||||
.filter_map(|d| d.ok())
|
||||
.map(|r| r.1)
|
||||
.collect::<Vec<Delegation>>();
|
||||
|
||||
if delegations.is_empty() {
|
||||
Err(ContractError::NoMixnodeDelegationFound {
|
||||
identity: mix_identity,
|
||||
address: validated_delegator,
|
||||
address: delegator,
|
||||
})
|
||||
} else {
|
||||
Ok(delegations)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn query_mixnode_delegations_paged(
|
||||
deps: Deps<'_>,
|
||||
mix_identity: IdentityKey,
|
||||
start_after: Option<String>,
|
||||
start_after: Option<(String, u64)>,
|
||||
limit: Option<u32>,
|
||||
) -> StdResult<PagedMixDelegationsResponse> {
|
||||
let limit = limit
|
||||
.unwrap_or(storage::DELEGATION_PAGE_DEFAULT_LIMIT)
|
||||
.min(storage::DELEGATION_PAGE_MAX_LIMIT) as usize;
|
||||
|
||||
let start = start_after
|
||||
.map(|addr| deps.api.addr_validate(&addr))
|
||||
.transpose()?
|
||||
.map(|addr| Bound::Exclusive((mix_identity.clone(), addr).joined_key()));
|
||||
let start = start_after.map(|(addr, height)| {
|
||||
Bound::ExclusiveRaw((mix_identity.clone(), addr.as_bytes(), height).joined_key())
|
||||
});
|
||||
|
||||
let delegations = storage::delegations()
|
||||
.idx
|
||||
@@ -115,7 +126,9 @@ pub(crate) fn query_mixnode_delegations_paged(
|
||||
.map(|record| record.map(|r| r.1))
|
||||
.collect::<StdResult<Vec<_>>>()?;
|
||||
|
||||
let start_next_after = delegations.last().map(|delegation| delegation.owner());
|
||||
let start_next_after = delegations
|
||||
.last()
|
||||
.map(|delegation| (delegation.owner(), delegation.block_height()));
|
||||
|
||||
Ok(PagedMixDelegationsResponse::new(
|
||||
delegations,
|
||||
@@ -245,25 +258,26 @@ pub(crate) mod tests {
|
||||
Option::from(per_page),
|
||||
)
|
||||
.unwrap();
|
||||
// println!("{:?}", page1);
|
||||
let start_after = page1.start_next_after.unwrap();
|
||||
assert_eq!(2, page1.delegations.len());
|
||||
assert_eq!("200".to_string(), page1.start_next_after.unwrap());
|
||||
assert_eq!(("200".to_string(), 12345), start_after);
|
||||
|
||||
// retrieving the next page should start after the last key on this page
|
||||
let start_after = "200".to_string();
|
||||
|
||||
let page2 = query_mixnode_delegations_paged(
|
||||
deps.as_ref(),
|
||||
node_identity.clone(),
|
||||
Option::from(start_after),
|
||||
Option::from(start_after.clone()),
|
||||
Option::from(per_page),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// println!("{:?}", page2);
|
||||
assert_eq!(1, page2.delegations.len());
|
||||
|
||||
// save another one
|
||||
test_helpers::save_dummy_delegation(&mut deps.storage, &node_identity, "400");
|
||||
|
||||
let start_after = "200".to_string();
|
||||
let page2 = query_mixnode_delegations_paged(
|
||||
deps.as_ref(),
|
||||
node_identity,
|
||||
@@ -271,6 +285,7 @@ pub(crate) mod tests {
|
||||
Option::from(per_page),
|
||||
)
|
||||
.unwrap();
|
||||
// println!("{:?}", page2);
|
||||
|
||||
// now we have 2 pages, with 2 results on the second page
|
||||
assert_eq!(2, page2.delegations.len());
|
||||
@@ -408,16 +423,17 @@ pub(crate) mod tests {
|
||||
);
|
||||
|
||||
storage::delegations()
|
||||
.save(
|
||||
deps.as_mut().storage,
|
||||
delegation.storage_key().joined_key(),
|
||||
&delegation,
|
||||
)
|
||||
.save(deps.as_mut().storage, delegation.storage_key(), &delegation)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
Ok(delegation),
|
||||
query_mixnode_delegation(deps.as_ref(), node_identity, delegation_owner.to_string())
|
||||
Ok(vec![delegation]),
|
||||
query_mixnode_delegation(
|
||||
&deps.storage,
|
||||
&deps.api,
|
||||
node_identity,
|
||||
delegation_owner.to_string()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -433,10 +449,11 @@ pub(crate) mod tests {
|
||||
assert_eq!(
|
||||
Err(ContractError::NoMixnodeDelegationFound {
|
||||
identity: node_identity1.clone(),
|
||||
address: delegation_owner1.clone(),
|
||||
address: delegation_owner1.to_string(),
|
||||
}),
|
||||
query_mixnode_delegation(
|
||||
deps.as_ref(),
|
||||
&deps.storage,
|
||||
&deps.api,
|
||||
node_identity1.clone(),
|
||||
delegation_owner1.to_string()
|
||||
)
|
||||
@@ -452,20 +469,17 @@ pub(crate) mod tests {
|
||||
);
|
||||
|
||||
storage::delegations()
|
||||
.save(
|
||||
deps.as_mut().storage,
|
||||
delegation.storage_key().joined_key(),
|
||||
&delegation,
|
||||
)
|
||||
.save(deps.as_mut().storage, delegation.storage_key(), &delegation)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
Err(ContractError::NoMixnodeDelegationFound {
|
||||
identity: node_identity1.clone(),
|
||||
address: delegation_owner1.clone(),
|
||||
address: delegation_owner1.to_string(),
|
||||
}),
|
||||
query_mixnode_delegation(
|
||||
deps.as_ref(),
|
||||
&deps.storage,
|
||||
&deps.api,
|
||||
node_identity1.clone(),
|
||||
delegation_owner1.to_string()
|
||||
)
|
||||
@@ -481,19 +495,20 @@ pub(crate) mod tests {
|
||||
);
|
||||
|
||||
storage::delegations()
|
||||
.save(
|
||||
deps.as_mut().storage,
|
||||
delegation.storage_key().joined_key(),
|
||||
&delegation,
|
||||
)
|
||||
.save(deps.as_mut().storage, delegation.storage_key(), &delegation)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
Err(ContractError::NoMixnodeDelegationFound {
|
||||
identity: node_identity1.clone(),
|
||||
address: Addr::unchecked(delegation_owner1.clone())
|
||||
address: delegation_owner1.to_string()
|
||||
}),
|
||||
query_mixnode_delegation(deps.as_ref(), node_identity1, delegation_owner1.to_string())
|
||||
query_mixnode_delegation(
|
||||
&deps.storage,
|
||||
&deps.api,
|
||||
node_identity1,
|
||||
delegation_owner1.to_string()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,20 +1,27 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use cw_storage_plus::{Index, IndexList, IndexedMap, MultiIndex};
|
||||
use mixnet_contract_common::{Addr, Delegation, IdentityKey};
|
||||
use cw_storage_plus::{Index, IndexList, IndexedMap, Map, MultiIndex};
|
||||
use mixnet_contract_common::{mixnode::DelegationEvent, Addr, Delegation, IdentityKey};
|
||||
|
||||
// storage prefixes
|
||||
const DELEGATION_PK_NAMESPACE: &str = "dl";
|
||||
const DELEGATION_OWNER_IDX_NAMESPACE: &str = "dlo";
|
||||
const DELEGATION_MIXNODE_IDX_NAMESPACE: &str = "dlm";
|
||||
|
||||
pub const PENDING_DELEGATION_EVENTS: Map<
|
||||
(BlockHeight, IdentityKey, OwnerAddress),
|
||||
DelegationEvent,
|
||||
> = Map::new("pend");
|
||||
|
||||
// paged retrieval limits for all queries and transactions
|
||||
pub(crate) const DELEGATION_PAGE_MAX_LIMIT: u32 = 500;
|
||||
pub(crate) const DELEGATION_PAGE_DEFAULT_LIMIT: u32 = 250;
|
||||
|
||||
// It's a composite key on node's identity and delegator address
|
||||
type PrimaryKey = Vec<u8>;
|
||||
type BlockHeight = u64;
|
||||
type OwnerAddress = Vec<u8>;
|
||||
// It's a composite key on node's identity, delegator address, and block height
|
||||
type PrimaryKey = (IdentityKey, OwnerAddress, BlockHeight);
|
||||
|
||||
pub(crate) struct DelegationIndex<'a> {
|
||||
pub(crate) owner: MultiIndex<'a, Addr, Delegation>,
|
||||
@@ -73,7 +80,6 @@ mod tests {
|
||||
use config::defaults::DENOM;
|
||||
use cosmwasm_std::testing::mock_env;
|
||||
use cosmwasm_std::{coin, Order};
|
||||
use cw_storage_plus::PrimaryKey;
|
||||
use mixnet_contract_common::Delegation;
|
||||
|
||||
#[test]
|
||||
@@ -94,7 +100,7 @@ mod tests {
|
||||
storage::delegations()
|
||||
.save(
|
||||
&mut deps.storage,
|
||||
(node_identity, delegation_owner.clone()).joined_key(),
|
||||
(node_identity, delegation_owner.as_bytes().to_vec(), 0),
|
||||
&dummy_data,
|
||||
)
|
||||
.unwrap();
|
||||
@@ -124,7 +130,8 @@ mod tests {
|
||||
assert!(test_helpers::read_delegation(
|
||||
deps.as_ref().storage,
|
||||
&node_identity1,
|
||||
&delegation_owner1
|
||||
delegation_owner1.as_bytes(),
|
||||
mock_env().block.height
|
||||
)
|
||||
.is_none());
|
||||
|
||||
@@ -139,7 +146,11 @@ mod tests {
|
||||
storage::delegations()
|
||||
.save(
|
||||
&mut deps.storage,
|
||||
(node_identity1.clone(), delegation_owner1.clone()).joined_key(),
|
||||
(
|
||||
node_identity1.clone(),
|
||||
delegation_owner1.as_bytes().to_vec(),
|
||||
0,
|
||||
),
|
||||
&dummy_data,
|
||||
)
|
||||
.unwrap();
|
||||
@@ -163,7 +174,11 @@ mod tests {
|
||||
storage::delegations()
|
||||
.save(
|
||||
&mut deps.storage,
|
||||
(node_identity1.clone(), delegation_owner2).joined_key(),
|
||||
(
|
||||
node_identity1.clone(),
|
||||
delegation_owner2.as_bytes().to_vec(),
|
||||
0,
|
||||
),
|
||||
&dummy_data,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -1,17 +1,69 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
use super::storage;
|
||||
use super::storage::{self, PENDING_DELEGATION_EVENTS};
|
||||
use crate::error::ContractError;
|
||||
use crate::mixnet_contract_settings::storage as mixnet_params_storage;
|
||||
use crate::mixnodes::storage as mixnodes_storage;
|
||||
use crate::support::helpers::generate_storage_key;
|
||||
use config::defaults::DENOM;
|
||||
use cosmwasm_std::{coins, wasm_execute, Addr, BankMsg, Coin, DepsMut, Env, MessageInfo, Response};
|
||||
use cw_storage_plus::PrimaryKey;
|
||||
use mixnet_contract_common::events::{new_delegation_event, new_undelegation_event};
|
||||
use cosmwasm_std::{
|
||||
coins, wasm_execute, Addr, Api, BankMsg, Coin, DepsMut, Env, Event, MessageInfo, Order,
|
||||
Response, Storage, Uint128, WasmMsg,
|
||||
};
|
||||
use mixnet_contract_common::events::{
|
||||
new_pending_delegation_event, new_pending_undelegation_event, new_undelegation_event,
|
||||
};
|
||||
use mixnet_contract_common::mixnode::{DelegationEvent, PendingUndelegate};
|
||||
use mixnet_contract_common::{Delegation, IdentityKey};
|
||||
use vesting_contract_common::messages::ExecuteMsg as VestingContractExecuteMsg;
|
||||
use vesting_contract_common::one_ucoin;
|
||||
|
||||
pub fn try_reconcile_all_delegation_events(
|
||||
deps: DepsMut<'_>,
|
||||
info: MessageInfo,
|
||||
) -> Result<Response, ContractError> {
|
||||
let state = mixnet_params_storage::CONTRACT_STATE.load(deps.storage)?;
|
||||
// check if this is executed by the permitted validator, if not reject the transaction
|
||||
if info.sender != state.rewarding_validator_address {
|
||||
return Err(ContractError::Unauthorized);
|
||||
}
|
||||
|
||||
_try_reconcile_all_delegation_events(deps.storage, deps.api)
|
||||
}
|
||||
|
||||
// TODO: Error handling?
|
||||
pub(crate) fn _try_reconcile_all_delegation_events(
|
||||
storage: &mut dyn Storage,
|
||||
api: &dyn Api,
|
||||
) -> Result<Response, ContractError> {
|
||||
let pending_delegation_events = PENDING_DELEGATION_EVENTS
|
||||
.range(storage, None, None, Order::Ascending)
|
||||
.filter_map(|r| r.ok())
|
||||
.collect::<Vec<((u64, String, Vec<u8>), DelegationEvent)>>();
|
||||
|
||||
let mut response = Response::new();
|
||||
|
||||
for (key, delegation_event) in pending_delegation_events {
|
||||
match delegation_event {
|
||||
DelegationEvent::Delegate(delegation) => {
|
||||
let event = try_reconcile_delegation(storage, delegation)?;
|
||||
response = response.add_event(event);
|
||||
}
|
||||
DelegationEvent::Undelegate(pending_undelegate) => {
|
||||
let undelegate_response =
|
||||
try_reconcile_undelegation(storage, api, &pending_undelegate)?;
|
||||
response = response.add_event(undelegate_response.event);
|
||||
response = response.add_message(undelegate_response.bank_msg);
|
||||
if let Some(msg) = undelegate_response.wasm_msg {
|
||||
response = response.add_message(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
PENDING_DELEGATION_EVENTS.remove(storage, key);
|
||||
}
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
fn validate_delegation_stake(mut delegation: Vec<Coin>) -> Result<Coin, ContractError> {
|
||||
// check if anything was put as delegation
|
||||
if delegation.is_empty() {
|
||||
@@ -44,7 +96,15 @@ pub(crate) fn try_delegate_to_mixnode(
|
||||
// check if the delegation contains any funds of the appropriate denomination
|
||||
let amount = validate_delegation_stake(info.funds)?;
|
||||
|
||||
_try_delegate_to_mixnode(deps, env, mix_identity, info.sender.as_str(), amount, None)
|
||||
_try_delegate_to_mixnode(
|
||||
deps.storage,
|
||||
deps.api,
|
||||
env.block.height,
|
||||
&mix_identity,
|
||||
info.sender.as_str(),
|
||||
amount,
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn try_delegate_to_mixnode_on_behalf(
|
||||
@@ -58,173 +118,277 @@ pub(crate) fn try_delegate_to_mixnode_on_behalf(
|
||||
let amount = validate_delegation_stake(info.funds)?;
|
||||
|
||||
_try_delegate_to_mixnode(
|
||||
deps,
|
||||
env,
|
||||
mix_identity,
|
||||
deps.storage,
|
||||
deps.api,
|
||||
env.block.height,
|
||||
&mix_identity,
|
||||
&delegate,
|
||||
amount,
|
||||
Some(info.sender),
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn _try_delegate_to_mixnode(
|
||||
deps: DepsMut<'_>,
|
||||
env: Env,
|
||||
mix_identity: IdentityKey,
|
||||
delegate: &str,
|
||||
amount: Coin,
|
||||
proxy: Option<Addr>,
|
||||
) -> Result<Response, ContractError> {
|
||||
let delegate = deps.api.addr_validate(delegate)?;
|
||||
|
||||
// check if the target node actually exists
|
||||
if mixnodes_storage::mixnodes()
|
||||
.may_load(deps.storage, &mix_identity)?
|
||||
.is_none()
|
||||
{
|
||||
return Err(ContractError::MixNodeBondNotFound {
|
||||
identity: mix_identity,
|
||||
});
|
||||
}
|
||||
|
||||
let maybe_proxy_storage = generate_storage_key(&delegate, proxy.as_ref());
|
||||
let storage_key = (mix_identity.clone(), maybe_proxy_storage).joined_key();
|
||||
|
||||
pub(crate) fn try_reconcile_delegation(
|
||||
storage: &mut dyn Storage,
|
||||
delegation: Delegation,
|
||||
) -> Result<Event, ContractError> {
|
||||
// update total_delegation of this node
|
||||
mixnodes_storage::TOTAL_DELEGATION.update::<_, ContractError>(
|
||||
deps.storage,
|
||||
&mix_identity,
|
||||
storage,
|
||||
&delegation.node_identity,
|
||||
|total_delegation| {
|
||||
// since we know that the target node exists and because the total_delegation bucket
|
||||
// entry is created whenever the node itself is added, the unwrap here is fine
|
||||
// as the entry MUST exist
|
||||
Ok(total_delegation.unwrap() + amount.amount)
|
||||
Ok(total_delegation.unwrap() + delegation.amount.amount)
|
||||
},
|
||||
)?;
|
||||
|
||||
// update [or create new] delegation of this delegator
|
||||
// update [or create new] pending delegation of this delegator
|
||||
storage::delegations().update::<_, ContractError>(
|
||||
deps.storage,
|
||||
storage_key,
|
||||
storage,
|
||||
delegation.storage_key(),
|
||||
|existing_delegation| {
|
||||
Ok(match existing_delegation {
|
||||
Some(mut existing_delegation) => {
|
||||
existing_delegation.increment_amount(amount.amount, Some(env.block.height));
|
||||
existing_delegation
|
||||
.increment_amount(delegation.amount.amount, Some(delegation.block_height));
|
||||
existing_delegation
|
||||
}
|
||||
None => Delegation::new(
|
||||
delegate.to_owned(),
|
||||
mix_identity.clone(),
|
||||
amount.clone(),
|
||||
env.block.height,
|
||||
proxy.clone(),
|
||||
),
|
||||
None => delegation.clone(),
|
||||
})
|
||||
},
|
||||
)?;
|
||||
|
||||
Ok(Response::new().add_event(new_delegation_event(
|
||||
Ok(new_pending_delegation_event(
|
||||
&delegation.owner,
|
||||
&delegation.proxy,
|
||||
&delegation.amount,
|
||||
&delegation.node_identity,
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) fn _try_delegate_to_mixnode(
|
||||
storage: &mut dyn Storage,
|
||||
api: &dyn Api,
|
||||
block_height: u64,
|
||||
mix_identity: &str,
|
||||
delegate: &str,
|
||||
amount: Coin,
|
||||
proxy: Option<Addr>,
|
||||
) -> Result<Response, ContractError> {
|
||||
let delegate = api.addr_validate(delegate)?;
|
||||
|
||||
// check if the target node actually exists
|
||||
if mixnodes_storage::mixnodes()
|
||||
.may_load(storage, mix_identity)?
|
||||
.is_none()
|
||||
{
|
||||
return Err(ContractError::MixNodeBondNotFound {
|
||||
identity: mix_identity.to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
let maybe_proxy_storage = generate_storage_key(&delegate, proxy.as_ref());
|
||||
let storage_key = (block_height, mix_identity.to_string(), maybe_proxy_storage);
|
||||
|
||||
storage::PENDING_DELEGATION_EVENTS.save(
|
||||
storage,
|
||||
storage_key,
|
||||
&DelegationEvent::Delegate(Delegation::new(
|
||||
delegate.to_owned(),
|
||||
mix_identity.to_string(),
|
||||
amount.clone(),
|
||||
block_height,
|
||||
proxy.clone(),
|
||||
)),
|
||||
)?;
|
||||
|
||||
Ok(Response::new().add_event(new_pending_delegation_event(
|
||||
&delegate,
|
||||
&proxy,
|
||||
&amount,
|
||||
&mix_identity,
|
||||
mix_identity,
|
||||
)))
|
||||
}
|
||||
|
||||
pub(crate) fn try_remove_delegation_from_mixnode(
|
||||
deps: DepsMut<'_>,
|
||||
env: Env,
|
||||
info: MessageInfo,
|
||||
mix_identity: IdentityKey,
|
||||
) -> Result<Response, ContractError> {
|
||||
_try_remove_delegation_from_mixnode(deps, mix_identity, info.sender.as_str(), None)
|
||||
_try_remove_delegation_from_mixnode(deps, env, mix_identity, info.sender.as_str(), None)
|
||||
}
|
||||
|
||||
pub(crate) fn try_remove_delegation_from_mixnode_on_behalf(
|
||||
deps: DepsMut<'_>,
|
||||
env: Env,
|
||||
info: MessageInfo,
|
||||
mix_identity: IdentityKey,
|
||||
delegate: String,
|
||||
) -> Result<Response, ContractError> {
|
||||
_try_remove_delegation_from_mixnode(deps, mix_identity, &delegate, Some(info.sender))
|
||||
_try_remove_delegation_from_mixnode(deps, env, mix_identity, &delegate, Some(info.sender))
|
||||
}
|
||||
|
||||
pub struct ReconcileUndelegateResponse {
|
||||
bank_msg: BankMsg,
|
||||
wasm_msg: Option<WasmMsg>,
|
||||
event: Event,
|
||||
}
|
||||
|
||||
pub(crate) fn try_reconcile_undelegation(
|
||||
storage: &mut dyn Storage,
|
||||
api: &dyn Api,
|
||||
pending_undelegate: &PendingUndelegate,
|
||||
) -> Result<ReconcileUndelegateResponse, ContractError> {
|
||||
let delegation_map = storage::delegations();
|
||||
let maybe_proxy_storage = generate_storage_key(
|
||||
&pending_undelegate.delegate(),
|
||||
pending_undelegate.proxy().as_ref(),
|
||||
);
|
||||
let storage_key = (
|
||||
pending_undelegate.mix_identity(),
|
||||
maybe_proxy_storage.clone(),
|
||||
);
|
||||
|
||||
let any_delegations = delegation_map
|
||||
.prefix(storage_key.clone())
|
||||
.keys(storage, None, None, cosmwasm_std::Order::Ascending)
|
||||
.filter_map(|v| v.ok())
|
||||
.next()
|
||||
.is_some();
|
||||
|
||||
if !any_delegations {
|
||||
return Err(ContractError::NoMixnodeDelegationFound {
|
||||
identity: pending_undelegate.mix_identity(),
|
||||
address: pending_undelegate.delegate().to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
let reward = crate::rewards::transactions::_try_compound_delegator_reward(
|
||||
pending_undelegate.block_height(),
|
||||
api,
|
||||
storage,
|
||||
pending_undelegate.delegate().as_str(),
|
||||
&pending_undelegate.mix_identity(),
|
||||
None,
|
||||
)?;
|
||||
|
||||
// Might want to introduce paging here
|
||||
let delegation_heights = delegation_map
|
||||
.prefix(storage_key)
|
||||
.keys(storage, None, None, cosmwasm_std::Order::Ascending)
|
||||
.filter_map(|v| v.ok())
|
||||
.collect::<Vec<u64>>();
|
||||
|
||||
if delegation_heights.is_empty() {
|
||||
return Err(ContractError::NoMixnodeDelegationFound {
|
||||
identity: pending_undelegate.mix_identity(),
|
||||
address: pending_undelegate.delegate().to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
let mut total_delegation = Uint128::zero();
|
||||
|
||||
if crate::mixnodes::storage::mixnodes()
|
||||
.may_load(storage, &pending_undelegate.mix_identity())?
|
||||
.is_none()
|
||||
{
|
||||
// Since the mixnode is no longer bonded the reward did not compound and we need to manually add it to the total
|
||||
total_delegation = reward;
|
||||
}
|
||||
|
||||
for h in delegation_heights {
|
||||
let storage_key = (
|
||||
pending_undelegate.mix_identity(),
|
||||
maybe_proxy_storage.clone(),
|
||||
h,
|
||||
);
|
||||
let delegation = delegation_map.load(storage, storage_key.clone())?;
|
||||
total_delegation += delegation.amount.amount;
|
||||
delegation_map.replace(storage, storage_key, None, Some(&delegation))?;
|
||||
}
|
||||
|
||||
let bank_msg = BankMsg::Send {
|
||||
to_address: pending_undelegate
|
||||
.proxy()
|
||||
.as_ref()
|
||||
.unwrap_or(&pending_undelegate.delegate())
|
||||
.to_string(),
|
||||
amount: coins(total_delegation.u128(), DENOM),
|
||||
};
|
||||
|
||||
mixnodes_storage::TOTAL_DELEGATION.update::<_, ContractError>(
|
||||
storage,
|
||||
&pending_undelegate.mix_identity(),
|
||||
|total_node_delegation| {
|
||||
// the first unwrap is fine because the delegation information MUST exist, otherwise we would
|
||||
// have never gotten here in the first place
|
||||
// the second unwrap is also fine because we should NEVER underflow here,
|
||||
// if we do, it means we have some serious error in our logic
|
||||
Ok(total_node_delegation
|
||||
.unwrap()
|
||||
.checked_sub(total_delegation)
|
||||
.unwrap())
|
||||
},
|
||||
)?;
|
||||
|
||||
let mut wasm_msg = None;
|
||||
|
||||
if let Some(proxy) = &pending_undelegate.proxy() {
|
||||
let msg = Some(VestingContractExecuteMsg::TrackUndelegation {
|
||||
owner: pending_undelegate.delegate().as_str().to_string(),
|
||||
mix_identity: pending_undelegate.mix_identity(),
|
||||
amount: Coin::new(total_delegation.u128(), DENOM),
|
||||
});
|
||||
|
||||
wasm_msg = Some(wasm_execute(proxy, &msg, vec![one_ucoin()])?);
|
||||
}
|
||||
|
||||
let event = new_undelegation_event(
|
||||
&pending_undelegate.delegate(),
|
||||
&pending_undelegate.proxy(),
|
||||
&pending_undelegate.mix_identity(),
|
||||
total_delegation,
|
||||
);
|
||||
|
||||
Ok(ReconcileUndelegateResponse {
|
||||
bank_msg,
|
||||
wasm_msg,
|
||||
event,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn _try_remove_delegation_from_mixnode(
|
||||
deps: DepsMut<'_>,
|
||||
env: Env,
|
||||
mix_identity: IdentityKey,
|
||||
delegate: &str,
|
||||
proxy: Option<Addr>,
|
||||
) -> Result<Response, ContractError> {
|
||||
let delegate = deps.api.addr_validate(delegate)?;
|
||||
let delegation_map = storage::delegations();
|
||||
let maybe_proxy_storage = generate_storage_key(&delegate, proxy.as_ref());
|
||||
let storage_key = (mix_identity.clone(), maybe_proxy_storage).joined_key();
|
||||
|
||||
match delegation_map.may_load(deps.storage, storage_key.clone())? {
|
||||
None => Err(ContractError::NoMixnodeDelegationFound {
|
||||
identity: mix_identity,
|
||||
address: delegate,
|
||||
}),
|
||||
Some(old_delegation) => {
|
||||
// remove all delegation associated with this delegator
|
||||
if proxy != old_delegation.proxy {
|
||||
return Err(ContractError::ProxyMismatch {
|
||||
existing: old_delegation
|
||||
.proxy
|
||||
.map_or_else(|| "None".to_string(), |a| a.to_string()),
|
||||
incoming: proxy.map_or_else(|| "None".to_string(), |a| a.to_string()),
|
||||
});
|
||||
}
|
||||
// remove old delegation data from the store
|
||||
// note for reviewers: I'm using `replace` as `remove` is just `may_load` followed by `replace`
|
||||
// and we've already performed `may_load` and have access to pre-existing data
|
||||
delegation_map.replace(deps.storage, storage_key, None, Some(&old_delegation))?;
|
||||
|
||||
// send delegated funds back to the delegation owner
|
||||
let return_tokens = BankMsg::Send {
|
||||
to_address: proxy.as_ref().unwrap_or(&delegate).to_string(),
|
||||
amount: coins(
|
||||
old_delegation.amount.amount.u128(),
|
||||
old_delegation.amount.denom.clone(),
|
||||
),
|
||||
};
|
||||
|
||||
// update total_delegation of this node
|
||||
mixnodes_storage::TOTAL_DELEGATION.update::<_, ContractError>(
|
||||
PENDING_DELEGATION_EVENTS.save(
|
||||
deps.storage,
|
||||
&mix_identity,
|
||||
|total_delegation| {
|
||||
// the first unwrap is fine because the delegation information MUST exist, otherwise we would
|
||||
// have never gotten here in the first place
|
||||
// the second unwrap is also fine because we should NEVER underflow here,
|
||||
// if we do, it means we have some serious error in our logic
|
||||
Ok(total_delegation
|
||||
.unwrap()
|
||||
.checked_sub(old_delegation.amount.amount)
|
||||
.unwrap())
|
||||
},
|
||||
(
|
||||
env.block.height,
|
||||
mix_identity.to_string(),
|
||||
delegate.as_bytes().to_vec(),
|
||||
),
|
||||
&DelegationEvent::Undelegate(PendingUndelegate::new(
|
||||
mix_identity.to_string(),
|
||||
delegate.clone(),
|
||||
proxy.clone(),
|
||||
env.block.height,
|
||||
)),
|
||||
)?;
|
||||
|
||||
let mut response = Response::new().add_message(return_tokens);
|
||||
|
||||
if let Some(proxy) = &proxy {
|
||||
let msg = Some(VestingContractExecuteMsg::TrackUndelegation {
|
||||
owner: delegate.as_str().to_string(),
|
||||
mix_identity: mix_identity.clone(),
|
||||
amount: old_delegation.amount.clone(),
|
||||
});
|
||||
|
||||
let track_undelegation_msg = wasm_execute(proxy, &msg, vec![one_ucoin()])?;
|
||||
|
||||
response = response.add_message(track_undelegation_msg);
|
||||
}
|
||||
Ok(response.add_event(new_undelegation_event(
|
||||
Ok(Response::new().add_event(new_pending_undelegation_event(
|
||||
&delegate,
|
||||
&proxy,
|
||||
&old_delegation,
|
||||
&mix_identity,
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -332,6 +496,8 @@ mod tests {
|
||||
)
|
||||
.is_ok());
|
||||
|
||||
_try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap();
|
||||
|
||||
let expected = Delegation::new(
|
||||
delegation_owner.clone(),
|
||||
identity.clone(),
|
||||
@@ -342,7 +508,13 @@ mod tests {
|
||||
|
||||
assert_eq!(
|
||||
expected,
|
||||
test_helpers::read_delegation(&deps.storage, &identity, delegation_owner).unwrap()
|
||||
test_helpers::read_delegation(
|
||||
&deps.storage,
|
||||
&identity,
|
||||
delegation_owner.as_bytes(),
|
||||
mock_env().block.height
|
||||
)
|
||||
.unwrap()
|
||||
);
|
||||
|
||||
// node's "total_delegation" is increased
|
||||
@@ -364,7 +536,7 @@ mod tests {
|
||||
deps.as_mut(),
|
||||
);
|
||||
let delegation_owner = Addr::unchecked("sender");
|
||||
try_remove_mixnode(deps.as_mut(), mock_info(mixnode_owner, &[])).unwrap();
|
||||
try_remove_mixnode(mock_env(), deps.as_mut(), mock_info(mixnode_owner, &[])).unwrap();
|
||||
assert_eq!(
|
||||
Err(ContractError::MixNodeBondNotFound {
|
||||
identity: identity.clone()
|
||||
@@ -387,7 +559,7 @@ mod tests {
|
||||
tests::fixtures::good_mixnode_pledge(),
|
||||
deps.as_mut(),
|
||||
);
|
||||
try_remove_mixnode(deps.as_mut(), mock_info(mixnode_owner, &[])).unwrap();
|
||||
try_remove_mixnode(mock_env(), deps.as_mut(), mock_info(mixnode_owner, &[])).unwrap();
|
||||
let identity = test_helpers::add_mixnode(
|
||||
mixnode_owner,
|
||||
tests::fixtures::good_mixnode_pledge(),
|
||||
@@ -403,6 +575,8 @@ mod tests {
|
||||
)
|
||||
.is_ok());
|
||||
|
||||
_try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap();
|
||||
|
||||
let expected = Delegation::new(
|
||||
delegation_owner.clone(),
|
||||
identity.clone(),
|
||||
@@ -413,7 +587,13 @@ mod tests {
|
||||
|
||||
assert_eq!(
|
||||
expected,
|
||||
test_helpers::read_delegation(&deps.storage, &identity, delegation_owner).unwrap()
|
||||
test_helpers::read_delegation(
|
||||
&deps.storage,
|
||||
&identity,
|
||||
delegation_owner.as_bytes(),
|
||||
mock_env().block.height
|
||||
)
|
||||
.unwrap()
|
||||
);
|
||||
|
||||
// node's "total_delegation" is increased
|
||||
@@ -437,33 +617,47 @@ mod tests {
|
||||
let delegation_owner = Addr::unchecked("sender");
|
||||
let delegation1 = coin(100, DENOM);
|
||||
let delegation2 = coin(50, DENOM);
|
||||
|
||||
let mut env = mock_env();
|
||||
|
||||
try_delegate_to_mixnode(
|
||||
deps.as_mut(),
|
||||
mock_env(),
|
||||
env.clone(),
|
||||
mock_info(delegation_owner.as_str(), &[delegation1.clone()]),
|
||||
identity.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
env.block.height += 1;
|
||||
|
||||
try_delegate_to_mixnode(
|
||||
deps.as_mut(),
|
||||
mock_env(),
|
||||
env,
|
||||
mock_info(delegation_owner.as_str(), &[delegation2.clone()]),
|
||||
identity.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let expected = Delegation::new(
|
||||
delegation_owner.clone(),
|
||||
identity.clone(),
|
||||
coin(delegation1.amount.u128() + delegation2.amount.u128(), DENOM),
|
||||
mock_env().block.height,
|
||||
None,
|
||||
);
|
||||
_try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
expected,
|
||||
test_helpers::read_delegation(&deps.storage, &identity, delegation_owner).unwrap()
|
||||
);
|
||||
// let expected = Delegation::new(
|
||||
// delegation_owner.clone(),
|
||||
// identity.clone(),
|
||||
// coin(delegation1.amount.u128() + delegation2.amount.u128(), DENOM),
|
||||
// mock_env().block.height,
|
||||
// None,
|
||||
// );
|
||||
|
||||
// assert_eq!(
|
||||
// expected,
|
||||
// test_helpers::read_delegation(
|
||||
// &deps.storage,
|
||||
// &identity,
|
||||
// delegation_owner.as_bytes(),
|
||||
// mock_env().block.height
|
||||
// )
|
||||
// .unwrap()
|
||||
// );
|
||||
|
||||
// node's "total_delegation" is sum of both
|
||||
assert_eq!(
|
||||
@@ -493,14 +687,22 @@ mod tests {
|
||||
env2.block.height = updated_height;
|
||||
try_delegate_to_mixnode(
|
||||
deps.as_mut(),
|
||||
env1,
|
||||
env1.clone(),
|
||||
mock_info(delegation_owner.as_str(), &[delegation.clone()]),
|
||||
identity.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
_try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
initial_height,
|
||||
test_helpers::read_delegation(&deps.storage, &identity, &delegation_owner)
|
||||
test_helpers::read_delegation(
|
||||
&deps.storage,
|
||||
&identity,
|
||||
delegation_owner.as_bytes(),
|
||||
env1.block.height
|
||||
)
|
||||
.unwrap()
|
||||
.block_height
|
||||
);
|
||||
@@ -512,11 +714,21 @@ mod tests {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let updated =
|
||||
test_helpers::read_delegation(&deps.storage, &identity, &delegation_owner).unwrap();
|
||||
_try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap();
|
||||
|
||||
assert_eq!(delegation.amount + delegation.amount, updated.amount.amount);
|
||||
assert_eq!(updated_height, updated.block_height);
|
||||
let delegations = crate::delegations::queries::query_mixnode_delegation(
|
||||
&deps.storage,
|
||||
&deps.api,
|
||||
identity,
|
||||
delegation_owner.to_string(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let total_delegation = delegations
|
||||
.iter()
|
||||
.fold(Uint128::zero(), |acc, d| acc + d.amount.amount);
|
||||
|
||||
assert_eq!(delegation.amount + delegation.amount, total_delegation);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -540,35 +752,54 @@ mod tests {
|
||||
env2.block.height = second_height;
|
||||
try_delegate_to_mixnode(
|
||||
deps.as_mut(),
|
||||
env1,
|
||||
env1.clone(),
|
||||
mock_info(delegation_owner1.as_str(), &[delegation1]),
|
||||
identity.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
_try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
initial_height,
|
||||
test_helpers::read_delegation(&deps.storage, &identity, &delegation_owner1)
|
||||
test_helpers::read_delegation(
|
||||
&deps.storage,
|
||||
&identity,
|
||||
delegation_owner1.as_bytes(),
|
||||
env1.block.height
|
||||
)
|
||||
.unwrap()
|
||||
.block_height
|
||||
);
|
||||
try_delegate_to_mixnode(
|
||||
deps.as_mut(),
|
||||
env2,
|
||||
env2.clone(),
|
||||
mock_info(delegation_owner2.as_str(), &[delegation2]),
|
||||
identity.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
_try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
initial_height,
|
||||
test_helpers::read_delegation(&deps.storage, &identity, &delegation_owner1)
|
||||
test_helpers::read_delegation(
|
||||
&deps.storage,
|
||||
&identity,
|
||||
delegation_owner1.as_bytes(),
|
||||
env1.block.height
|
||||
)
|
||||
.unwrap()
|
||||
.block_height
|
||||
);
|
||||
assert_eq!(
|
||||
second_height,
|
||||
test_helpers::read_delegation(&deps.storage, identity, &delegation_owner2)
|
||||
test_helpers::read_delegation(
|
||||
&deps.storage,
|
||||
identity,
|
||||
delegation_owner2.as_bytes(),
|
||||
env2.block.height
|
||||
)
|
||||
.unwrap()
|
||||
.block_height
|
||||
);
|
||||
@@ -591,7 +822,7 @@ mod tests {
|
||||
identity.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
try_remove_mixnode(deps.as_mut(), mock_info(mixnode_owner, &[])).unwrap();
|
||||
try_remove_mixnode(mock_env(), deps.as_mut(), mock_info(mixnode_owner, &[])).unwrap();
|
||||
assert_eq!(
|
||||
Err(ContractError::MixNodeBondNotFound {
|
||||
identity: identity.clone()
|
||||
@@ -636,6 +867,8 @@ mod tests {
|
||||
)
|
||||
.is_ok());
|
||||
|
||||
_try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap();
|
||||
|
||||
let expected1 = Delegation::new(
|
||||
delegation_owner.clone(),
|
||||
identity1.clone(),
|
||||
@@ -654,11 +887,23 @@ mod tests {
|
||||
|
||||
assert_eq!(
|
||||
expected1,
|
||||
test_helpers::read_delegation(&deps.storage, identity1, &delegation_owner).unwrap()
|
||||
test_helpers::read_delegation(
|
||||
&deps.storage,
|
||||
identity1,
|
||||
delegation_owner.as_bytes(),
|
||||
mock_env().block.height
|
||||
)
|
||||
.unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
expected2,
|
||||
test_helpers::read_delegation(&deps.storage, identity2, &delegation_owner).unwrap()
|
||||
test_helpers::read_delegation(
|
||||
&deps.storage,
|
||||
identity2,
|
||||
delegation_owner.as_bytes(),
|
||||
mock_env().block.height
|
||||
)
|
||||
.unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -687,6 +932,8 @@ mod tests {
|
||||
identity.clone(),
|
||||
)
|
||||
.is_ok());
|
||||
_try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap();
|
||||
|
||||
// node's "total_delegation" is sum of both
|
||||
assert_eq!(
|
||||
delegation1.amount + delegation2.amount,
|
||||
@@ -714,7 +961,10 @@ mod tests {
|
||||
identity.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
try_remove_mixnode(deps.as_mut(), mock_info(mixnode_owner, &[])).unwrap();
|
||||
|
||||
_try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap();
|
||||
|
||||
try_remove_mixnode(mock_env(), deps.as_mut(), mock_info(mixnode_owner, &[])).unwrap();
|
||||
|
||||
let expected = Delegation::new(
|
||||
delegation_owner.clone(),
|
||||
@@ -726,7 +976,13 @@ mod tests {
|
||||
|
||||
assert_eq!(
|
||||
expected,
|
||||
test_helpers::read_delegation(&deps.storage, identity, delegation_owner).unwrap()
|
||||
test_helpers::read_delegation(
|
||||
&deps.storage,
|
||||
identity,
|
||||
delegation_owner.as_bytes(),
|
||||
mock_env().block.height
|
||||
)
|
||||
.unwrap()
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -746,9 +1002,12 @@ mod tests {
|
||||
use super::storage;
|
||||
use super::*;
|
||||
|
||||
// TODO: Probably delete due to reconciliation logic
|
||||
#[ignore]
|
||||
#[test]
|
||||
fn fails_if_delegation_never_existed() {
|
||||
let mut deps = test_helpers::init_contract();
|
||||
let env = mock_env();
|
||||
let mixnode_owner = "bob";
|
||||
let identity = test_helpers::add_mixnode(
|
||||
mixnode_owner,
|
||||
@@ -759,20 +1018,24 @@ mod tests {
|
||||
assert_eq!(
|
||||
Err(ContractError::NoMixnodeDelegationFound {
|
||||
identity: identity.clone(),
|
||||
address: delegation_owner.clone(),
|
||||
address: delegation_owner.to_string(),
|
||||
}),
|
||||
try_remove_delegation_from_mixnode(
|
||||
deps.as_mut(),
|
||||
env,
|
||||
mock_info(delegation_owner.as_str(), &[]),
|
||||
identity,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: Update to work with reconciliation
|
||||
#[ignore]
|
||||
#[test]
|
||||
fn succeeds_if_delegation_existed() {
|
||||
let mut deps = test_helpers::init_contract();
|
||||
let mixnode_owner = "bob";
|
||||
let env = mock_env();
|
||||
let identity = test_helpers::add_mixnode(
|
||||
mixnode_owner,
|
||||
tests::fixtures::good_mixnode_pledge(),
|
||||
@@ -786,8 +1049,12 @@ mod tests {
|
||||
identity.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
_try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap();
|
||||
|
||||
let delegation = query_mixnode_delegation(
|
||||
deps.as_ref(),
|
||||
&deps.storage,
|
||||
&deps.api,
|
||||
identity.clone(),
|
||||
delegation_owner.clone().into_string(),
|
||||
)
|
||||
@@ -801,14 +1068,15 @@ mod tests {
|
||||
.add_event(new_undelegation_event(
|
||||
&delegation_owner,
|
||||
&None,
|
||||
&delegation,
|
||||
&identity,
|
||||
Uint128::new(100),
|
||||
));
|
||||
|
||||
assert_eq!(
|
||||
Ok(expected_response),
|
||||
try_remove_delegation_from_mixnode(
|
||||
deps.as_mut(),
|
||||
env,
|
||||
mock_info(delegation_owner.as_str(), &[]),
|
||||
identity.clone(),
|
||||
)
|
||||
@@ -816,7 +1084,7 @@ mod tests {
|
||||
assert!(storage::delegations()
|
||||
.may_load(
|
||||
&deps.storage,
|
||||
(identity.clone(), delegation_owner).joined_key(),
|
||||
(identity.clone(), delegation_owner.as_bytes().to_vec(), 0),
|
||||
)
|
||||
.unwrap()
|
||||
.is_none());
|
||||
@@ -830,10 +1098,13 @@ mod tests {
|
||||
)
|
||||
}
|
||||
|
||||
// TODO: Update to work with reconciliation
|
||||
#[ignore]
|
||||
#[test]
|
||||
fn succeeds_if_delegation_existed_even_if_node_unbonded() {
|
||||
let mut deps = test_helpers::init_contract();
|
||||
let mixnode_owner = "bob";
|
||||
let env = mock_env();
|
||||
let identity = test_helpers::add_mixnode(
|
||||
mixnode_owner,
|
||||
tests::fixtures::good_mixnode_pledge(),
|
||||
@@ -847,12 +1118,17 @@ mod tests {
|
||||
identity.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
_try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap();
|
||||
|
||||
let delegation = query_mixnode_delegation(
|
||||
deps.as_ref(),
|
||||
&deps.storage,
|
||||
&deps.api,
|
||||
identity.clone(),
|
||||
delegation_owner.clone().into_string(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let expected_response = Response::new()
|
||||
.add_message(BankMsg::Send {
|
||||
to_address: delegation_owner.clone().into(),
|
||||
@@ -861,28 +1137,37 @@ mod tests {
|
||||
.add_event(new_undelegation_event(
|
||||
&delegation_owner,
|
||||
&None,
|
||||
&delegation,
|
||||
&identity,
|
||||
Uint128::new(100),
|
||||
));
|
||||
|
||||
try_remove_mixnode(deps.as_mut(), mock_info(mixnode_owner, &[])).unwrap();
|
||||
try_remove_mixnode(mock_env(), deps.as_mut(), mock_info(mixnode_owner, &[])).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
Ok(expected_response),
|
||||
try_remove_delegation_from_mixnode(
|
||||
deps.as_mut(),
|
||||
env,
|
||||
mock_info(delegation_owner.as_str(), &[]),
|
||||
identity.clone(),
|
||||
)
|
||||
);
|
||||
|
||||
assert!(
|
||||
test_helpers::read_delegation(&deps.storage, identity, delegation_owner).is_none()
|
||||
);
|
||||
_try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap();
|
||||
|
||||
assert!(test_helpers::read_delegation(
|
||||
&deps.storage,
|
||||
identity,
|
||||
delegation_owner.as_bytes(),
|
||||
mock_env().block.height
|
||||
)
|
||||
.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn total_delegation_is_preserved_if_only_some_undelegate() {
|
||||
let mut deps = test_helpers::init_contract();
|
||||
let env = mock_env();
|
||||
let mixnode_owner = "bob";
|
||||
let identity = test_helpers::add_mixnode(
|
||||
mixnode_owner,
|
||||
@@ -900,6 +1185,9 @@ mod tests {
|
||||
identity.clone(),
|
||||
)
|
||||
.is_ok());
|
||||
|
||||
_try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap();
|
||||
|
||||
assert!(try_delegate_to_mixnode(
|
||||
deps.as_mut(),
|
||||
mock_env(),
|
||||
@@ -907,13 +1195,19 @@ mod tests {
|
||||
identity.clone(),
|
||||
)
|
||||
.is_ok());
|
||||
|
||||
_try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap();
|
||||
|
||||
// sender1 undelegates
|
||||
try_remove_delegation_from_mixnode(
|
||||
deps.as_mut(),
|
||||
env,
|
||||
mock_info(delegation_owner1.as_str(), &[]),
|
||||
identity.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
_try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap();
|
||||
// but total delegation should still equal to what sender2 sent
|
||||
// node's "total_delegation" is sum of both
|
||||
assert_eq!(
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
use config::defaults::DENOM;
|
||||
use cosmwasm_std::{Addr, StdError};
|
||||
use mixnet_contract_common::IdentityKey;
|
||||
use mixnet_contract_common::{error::MixnetContractError, IdentityKey};
|
||||
use thiserror::Error;
|
||||
|
||||
/// Custom errors for contract failure conditions.
|
||||
@@ -69,7 +69,7 @@ pub enum ContractError {
|
||||
#[error("MIXNET ({}): Could not find any delegation information associated with mixnode {identity} for {address}", line!())]
|
||||
NoMixnodeDelegationFound {
|
||||
identity: IdentityKey,
|
||||
address: Addr,
|
||||
address: String,
|
||||
},
|
||||
|
||||
#[error("MIXNET ({}): We tried to remove more funds then are available in the Reward pool. Wanted to remove {to_remove}, but have only {reward_pool}", line!())]
|
||||
@@ -124,4 +124,21 @@ pub enum ContractError {
|
||||
interval_start: i64,
|
||||
interval_end: i64,
|
||||
},
|
||||
|
||||
#[error("MIXNET ({}): Can't change to the desired interval as it's not in progress yet. It starts at {epoch_start} and finishes at {epoch_end}, while the current block time is {current_block_time}", line!())]
|
||||
EpochNotInProgress {
|
||||
current_block_time: u64,
|
||||
epoch_start: i64,
|
||||
epoch_end: i64,
|
||||
},
|
||||
|
||||
#[error("Could not cast reward to a u128, this should be impossible, at {}", line!())]
|
||||
CastError,
|
||||
#[error("{source}")]
|
||||
MixnetCommonError {
|
||||
#[from]
|
||||
source: MixnetContractError,
|
||||
},
|
||||
#[error("No rewards to claim for mixnode {identity} for delegate {delegate}")]
|
||||
NoRewardsToClaim { identity: String, delegate: String },
|
||||
}
|
||||
|
||||
@@ -17,7 +17,8 @@ pub(crate) fn query_gateways_paged(
|
||||
let limit = limit
|
||||
.unwrap_or(BOND_PAGE_DEFAULT_LIMIT)
|
||||
.min(BOND_PAGE_MAX_LIMIT) as usize;
|
||||
let start = start_after.map(Bound::exclusive);
|
||||
|
||||
let start = start_after.as_deref().map(Bound::exclusive);
|
||||
|
||||
let nodes = storage::gateways()
|
||||
.range(deps.storage, start, None, Order::Ascending)
|
||||
|
||||
@@ -11,7 +11,7 @@ use mixnet_contract_common::{
|
||||
};
|
||||
|
||||
pub fn query_current_interval(storage: &dyn Storage) -> Result<Interval, ContractError> {
|
||||
Ok(storage::CURRENT_INTERVAL.load(storage)?)
|
||||
storage::current_interval(storage)
|
||||
}
|
||||
|
||||
pub(crate) fn query_rewarded_set_refresh_minimum_blocks() -> u64 {
|
||||
|
||||
@@ -3,7 +3,11 @@
|
||||
|
||||
use cosmwasm_std::{StdResult, Storage};
|
||||
use cw_storage_plus::{Item, Map};
|
||||
use mixnet_contract_common::{IdentityKey, Interval, RewardedSetNodeStatus};
|
||||
use mixnet_contract_common::{
|
||||
reward_params::EpochRewardParams, IdentityKey, Interval, RewardedSetNodeStatus,
|
||||
};
|
||||
|
||||
use crate::{error::ContractError, support::helpers::epoch_reward_params};
|
||||
|
||||
// type aliases for better reasoning for storage keys
|
||||
// (I found it helpful)
|
||||
@@ -14,7 +18,9 @@ type IntervalId = u32;
|
||||
pub(crate) const REWARDED_NODE_DEFAULT_PAGE_LIMIT: u32 = 1000;
|
||||
pub(crate) const REWARDED_NODE_MAX_PAGE_LIMIT: u32 = 1500;
|
||||
|
||||
pub(crate) const CURRENT_INTERVAL: Item<'_, Interval> = Item::new("cep");
|
||||
const CURRENT_INTERVAL: Item<'_, Interval> = Item::new("cei");
|
||||
const CURRENT_EPOCH: Item<'_, Interval> = Item::new("cep");
|
||||
const CURRENT_EPOCH_REWARD_PARAMS: Item<'_, EpochRewardParams> = Item::new("erp");
|
||||
pub(crate) const CURRENT_REWARDED_SET_HEIGHT: Item<'_, BlockHeight> = Item::new("crh");
|
||||
|
||||
// I've changed the `()` data to an `u8` as after serializing `()` is represented as "null",
|
||||
@@ -26,6 +32,45 @@ pub(crate) const REWARDED_SET_HEIGHTS_FOR_INTERVAL: Map<'_, (IntervalId, BlockHe
|
||||
pub(crate) const REWARDED_SET: Map<'_, (BlockHeight, IdentityKey), RewardedSetNodeStatus> =
|
||||
Map::new("rs");
|
||||
|
||||
pub(crate) const INTERVALS: Map<'_, IntervalId, Interval> = Map::new("ins");
|
||||
pub(crate) const EPOCHS: Map<'_, IntervalId, Interval> = Map::new("ephs");
|
||||
|
||||
pub fn save_interval(storage: &mut dyn Storage, interval: &Interval) -> Result<(), ContractError> {
|
||||
CURRENT_INTERVAL.save(storage, interval)?;
|
||||
INTERVALS.save(storage, interval.id(), interval)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn save_epoch(storage: &mut dyn Storage, interval: &Interval) -> Result<(), ContractError> {
|
||||
CURRENT_EPOCH.save(storage, interval)?;
|
||||
EPOCHS.save(storage, interval.id(), interval)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn current_epoch_reward_params(
|
||||
storage: &dyn Storage,
|
||||
) -> Result<EpochRewardParams, ContractError> {
|
||||
Ok(CURRENT_EPOCH_REWARD_PARAMS.load(storage)?)
|
||||
}
|
||||
|
||||
pub fn save_epoch_reward_params(
|
||||
epoch_id: u32,
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<(), ContractError> {
|
||||
let epoch_reward_params = epoch_reward_params(epoch_id, storage)?;
|
||||
CURRENT_EPOCH_REWARD_PARAMS.save(storage, &epoch_reward_params)?;
|
||||
crate::rewards::storage::EPOCH_REWARD_PARAMS.save(storage, epoch_id, &epoch_reward_params)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn current_interval(storage: &dyn Storage) -> Result<Interval, ContractError> {
|
||||
Ok(CURRENT_INTERVAL.load(storage)?)
|
||||
}
|
||||
|
||||
pub fn current_epoch(storage: &dyn Storage) -> Result<Interval, ContractError> {
|
||||
Ok(CURRENT_EPOCH.load(storage)?)
|
||||
}
|
||||
|
||||
pub(crate) fn save_rewarded_set(
|
||||
storage: &mut dyn Storage,
|
||||
height: BlockHeight,
|
||||
|
||||
@@ -3,12 +3,15 @@
|
||||
|
||||
use super::storage;
|
||||
use crate::error::ContractError;
|
||||
use crate::error::ContractError::IntervalNotInProgress;
|
||||
use crate::error::ContractError::{EpochNotInProgress, IntervalNotInProgress};
|
||||
use crate::mixnet_contract_settings::storage as mixnet_params_storage;
|
||||
use cosmwasm_std::{DepsMut, Env, MessageInfo, Response, Storage};
|
||||
use mixnet_contract_common::events::{new_advance_interval_event, new_change_rewarded_set_event};
|
||||
use mixnet_contract_common::IdentityKey;
|
||||
|
||||
// We've distributed the rewards to the rewarded set from the validator api before making this call (implicit order, should be solved in the future)
|
||||
// We now write the new rewarded set, snapshot the mixnodes and finally reconcile all delegations and undelegations. That way the rewards for the previous
|
||||
// epoch will be calculated correctly as the delegations and undelegations from the previous epoch will only take effect in the next (current) one.
|
||||
pub fn try_write_rewarded_set(
|
||||
deps: DepsMut<'_>,
|
||||
env: Env,
|
||||
@@ -50,7 +53,7 @@ pub fn try_write_rewarded_set(
|
||||
});
|
||||
}
|
||||
|
||||
let current_interval = storage::CURRENT_INTERVAL.load(deps.storage)?.id();
|
||||
let current_interval = storage::current_interval(deps.storage)?.id();
|
||||
let num_nodes = rewarded_set.len();
|
||||
|
||||
storage::save_rewarded_set(deps.storage, block_height, active_set_size, rewarded_set)?;
|
||||
@@ -76,8 +79,8 @@ pub fn try_advance_interval(
|
||||
// in theory, we could have just changed the state and relied on its reversal upon failed
|
||||
// execution, but better safe than sorry and do not modify the state at all unless we know
|
||||
// all checks have succeeded.
|
||||
let current_interval = storage::CURRENT_INTERVAL.load(storage)?;
|
||||
let next_interval = current_interval.next_interval();
|
||||
let current_interval = storage::current_interval(storage)?;
|
||||
let next_interval = current_interval.next();
|
||||
|
||||
if next_interval.start_unix_timestamp() > env.block.time.seconds() as i64 {
|
||||
// the reason for this check is as follows:
|
||||
@@ -95,11 +98,40 @@ pub fn try_advance_interval(
|
||||
});
|
||||
}
|
||||
|
||||
storage::CURRENT_INTERVAL.save(storage, &next_interval)?;
|
||||
storage::save_interval(storage, &next_interval)?;
|
||||
|
||||
Ok(Response::new().add_event(new_advance_interval_event(next_interval)))
|
||||
}
|
||||
|
||||
pub fn try_advance_epoch(env: Env, storage: &mut dyn Storage) -> Result<Response, ContractError> {
|
||||
// in theory, we could have just changed the state and relied on its reversal upon failed
|
||||
// execution, but better safe than sorry and do not modify the state at all unless we know
|
||||
// all checks have succeeded.
|
||||
let current_epoch = storage::current_epoch(storage)?;
|
||||
let next_epoch = current_epoch.next();
|
||||
|
||||
if next_epoch.start_unix_timestamp() > env.block.time.seconds() as i64 {
|
||||
// the reason for this check is as follows:
|
||||
// nobody, even trusted validators, should be able to continuously keep advancing epochs,
|
||||
// because otherwise it would be possible for them to continuously keep rewarding nodes.
|
||||
//
|
||||
// Therefore, even if "trusted" validator, responsible for rewarding, is malicious,
|
||||
// they can't send rewards more often than every `REWARDED_SET_REFRESH_BLOCKS`
|
||||
// and changing this value requires going through governance and having agreement of
|
||||
// the super-majority of the validators (by stake)
|
||||
return Err(EpochNotInProgress {
|
||||
current_block_time: env.block.time.seconds(),
|
||||
epoch_start: next_epoch.start_unix_timestamp(),
|
||||
epoch_end: next_epoch.end_unix_timestamp(),
|
||||
});
|
||||
}
|
||||
|
||||
storage::save_epoch(storage, &next_epoch)?;
|
||||
storage::save_epoch_reward_params(next_epoch.id(), storage)?;
|
||||
|
||||
Ok(Response::new().add_event(new_advance_interval_event(next_epoch)))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -251,10 +283,8 @@ mod tests {
|
||||
OffsetDateTime::from_unix_timestamp(1640995200).unwrap(),
|
||||
Duration::from_secs(60 * 60 * 720),
|
||||
);
|
||||
let next_interval = current_interval.next_interval();
|
||||
storage::CURRENT_INTERVAL
|
||||
.save(deps.as_mut().storage, ¤t_interval)
|
||||
.unwrap();
|
||||
let next_interval = current_interval.next();
|
||||
storage::save_interval(deps.as_mut().storage, ¤t_interval).unwrap();
|
||||
|
||||
// fails if the current interval hasn't finished yet i.e. the new interval hasn't begun
|
||||
env.block.time = Timestamp::from_seconds(1641081600);
|
||||
@@ -284,7 +314,7 @@ mod tests {
|
||||
// interval that has just finished
|
||||
env.block.time =
|
||||
Timestamp::from_seconds(next_interval.start_unix_timestamp() as u64 + 10000);
|
||||
let expected_new_interval = current_interval.next_interval();
|
||||
let expected_new_interval = current_interval.next();
|
||||
let expected_response =
|
||||
Response::new().add_event(new_advance_interval_event(expected_new_interval));
|
||||
assert_eq!(
|
||||
@@ -294,10 +324,8 @@ mod tests {
|
||||
|
||||
// interval way back in the past (i.e. 'somebody' failed to advance it for a long time)
|
||||
env.block.time = Timestamp::from_seconds(1672531200);
|
||||
storage::CURRENT_INTERVAL
|
||||
.save(deps.as_mut().storage, ¤t_interval)
|
||||
.unwrap();
|
||||
let expected_new_interval = current_interval.next_interval();
|
||||
storage::save_interval(deps.as_mut().storage, ¤t_interval).unwrap();
|
||||
let expected_new_interval = current_interval.next();
|
||||
let expected_response =
|
||||
Response::new().add_event(new_advance_interval_event(expected_new_interval));
|
||||
assert_eq!(
|
||||
|
||||
@@ -17,7 +17,7 @@ pub fn query_mixnodes_paged(
|
||||
.unwrap_or(storage::BOND_PAGE_DEFAULT_LIMIT)
|
||||
.min(storage::BOND_PAGE_MAX_LIMIT) as usize;
|
||||
|
||||
let start = start_after.map(Bound::exclusive);
|
||||
let start = start_after.as_deref().map(Bound::exclusive);
|
||||
|
||||
let nodes = storage::mixnodes()
|
||||
.range(deps.storage, start, None, Order::Ascending)
|
||||
@@ -203,6 +203,7 @@ pub(crate) mod tests {
|
||||
#[test]
|
||||
fn query_for_mixnode_owner_works() {
|
||||
let mut deps = test_helpers::init_contract();
|
||||
let env = mock_env();
|
||||
|
||||
// "fred" does not own a mixnode if there are no mixnodes
|
||||
let res = query_owns_mixnode(deps.as_ref(), "fred".to_string()).unwrap();
|
||||
@@ -225,7 +226,11 @@ pub(crate) mod tests {
|
||||
assert!(res.mixnode.is_some());
|
||||
|
||||
// but after unbonding it, he doesn't own one anymore
|
||||
crate::mixnodes::transactions::try_remove_mixnode(deps.as_mut(), mock_info("fred", &[]))
|
||||
crate::mixnodes::transactions::try_remove_mixnode(
|
||||
env,
|
||||
deps.as_mut(),
|
||||
mock_info("fred", &[]),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let res = query_owns_mixnode(deps.as_ref(), "fred".to_string()).unwrap();
|
||||
|
||||
@@ -3,14 +3,19 @@
|
||||
|
||||
use config::defaults::DENOM;
|
||||
use cosmwasm_std::{StdResult, Storage, Uint128};
|
||||
use cw_storage_plus::{Index, IndexList, IndexedMap, Map, UniqueIndex};
|
||||
use mixnet_contract_common::{Addr, Coin, IdentityKeyRef, Layer, MixNode, MixNodeBond};
|
||||
use cw_storage_plus::{Index, IndexList, IndexedSnapshotMap, Map, Strategy, UniqueIndex};
|
||||
use mixnet_contract_common::U128;
|
||||
use mixnet_contract_common::{
|
||||
reward_params::NodeEpochRewards, Addr, Coin, IdentityKeyRef, Layer, MixNode, MixNodeBond,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
// storage prefixes
|
||||
const TOTAL_DELEGATION_NAMESPACE: &str = "td";
|
||||
const MIXNODES_PK_NAMESPACE: &str = "mn";
|
||||
const MIXNODES_PK_CHECKPOINTS: &str = "mn__check";
|
||||
const MIXNODES_PK_CHANGELOG: &str = "mn__change";
|
||||
const MIXNODES_OWNER_IDX_NAMESPACE: &str = "mno";
|
||||
|
||||
// paged retrieval limits for all queries and transactions
|
||||
@@ -35,11 +40,17 @@ impl<'a> IndexList<StoredMixnodeBond> for MixnodeBondIndex<'a> {
|
||||
|
||||
// mixnodes() is the storage access function.
|
||||
pub(crate) fn mixnodes<'a>(
|
||||
) -> IndexedMap<'a, IdentityKeyRef<'a>, StoredMixnodeBond, MixnodeBondIndex<'a>> {
|
||||
) -> IndexedSnapshotMap<'a, IdentityKeyRef<'a>, StoredMixnodeBond, MixnodeBondIndex<'a>> {
|
||||
let indexes = MixnodeBondIndex {
|
||||
owner: UniqueIndex::new(|d| d.owner.clone(), MIXNODES_OWNER_IDX_NAMESPACE),
|
||||
};
|
||||
IndexedMap::new(MIXNODES_PK_NAMESPACE, indexes)
|
||||
IndexedSnapshotMap::new(
|
||||
MIXNODES_PK_NAMESPACE,
|
||||
MIXNODES_PK_CHECKPOINTS,
|
||||
MIXNODES_PK_CHANGELOG,
|
||||
Strategy::Never,
|
||||
indexes,
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
|
||||
@@ -50,9 +61,27 @@ pub(crate) struct StoredMixnodeBond {
|
||||
pub block_height: u64,
|
||||
pub mix_node: MixNode,
|
||||
pub proxy: Option<Addr>,
|
||||
pub accumulated_rewards: Uint128,
|
||||
pub epoch_rewards: Option<NodeEpochRewards>,
|
||||
}
|
||||
|
||||
impl From<MixNodeBond> for StoredMixnodeBond {
|
||||
fn from(mixnode_bond: MixNodeBond) -> StoredMixnodeBond {
|
||||
StoredMixnodeBond {
|
||||
pledge_amount: mixnode_bond.pledge_amount,
|
||||
owner: mixnode_bond.owner,
|
||||
layer: mixnode_bond.layer,
|
||||
block_height: mixnode_bond.block_height,
|
||||
mix_node: mixnode_bond.mix_node,
|
||||
proxy: mixnode_bond.proxy,
|
||||
accumulated_rewards: mixnode_bond.accumulated_rewards,
|
||||
epoch_rewards: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl StoredMixnodeBond {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn new(
|
||||
pledge_amount: Coin,
|
||||
owner: Addr,
|
||||
@@ -60,6 +89,8 @@ impl StoredMixnodeBond {
|
||||
block_height: u64,
|
||||
mix_node: MixNode,
|
||||
proxy: Option<Addr>,
|
||||
accumulated_rewards: Uint128,
|
||||
epoch_rewards: Option<NodeEpochRewards>,
|
||||
) -> Self {
|
||||
StoredMixnodeBond {
|
||||
pledge_amount,
|
||||
@@ -68,6 +99,8 @@ impl StoredMixnodeBond {
|
||||
block_height,
|
||||
mix_node,
|
||||
proxy,
|
||||
accumulated_rewards,
|
||||
epoch_rewards,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,6 +116,7 @@ impl StoredMixnodeBond {
|
||||
block_height: self.block_height,
|
||||
mix_node: self.mix_node,
|
||||
proxy: self.proxy,
|
||||
accumulated_rewards: self.accumulated_rewards,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,6 +127,10 @@ impl StoredMixnodeBond {
|
||||
pub(crate) fn pledge_amount(&self) -> Coin {
|
||||
self.pledge_amount.clone()
|
||||
}
|
||||
|
||||
pub fn profit_margin(&self) -> U128 {
|
||||
U128::from_num(self.mix_node.profit_margin_percent) / U128::from_num(100)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for StoredMixnodeBond {
|
||||
@@ -125,6 +163,7 @@ pub(crate) fn read_full_mixnode_bond(
|
||||
block_height: stored_bond.block_height,
|
||||
mix_node: stored_bond.mix_node,
|
||||
proxy: stored_bond.proxy,
|
||||
accumulated_rewards: stored_bond.accumulated_rewards,
|
||||
}))
|
||||
}
|
||||
}
|
||||
@@ -145,8 +184,8 @@ mod tests {
|
||||
let mut storage = MockStorage::new();
|
||||
let bond1 = tests::fixtures::stored_mixnode_bond_fixture("owner1");
|
||||
let bond2 = tests::fixtures::stored_mixnode_bond_fixture("owner2");
|
||||
mixnodes().save(&mut storage, "bond1", &bond1).unwrap();
|
||||
mixnodes().save(&mut storage, "bond2", &bond2).unwrap();
|
||||
mixnodes().save(&mut storage, "bond1", &bond1, 1).unwrap();
|
||||
mixnodes().save(&mut storage, "bond2", &bond2, 1).unwrap();
|
||||
|
||||
let res1 = mixnodes().load(&storage, "bond1").unwrap();
|
||||
let res2 = mixnodes().load(&storage, "bond2").unwrap();
|
||||
@@ -177,10 +216,12 @@ mod tests {
|
||||
..tests::fixtures::mix_node_fixture()
|
||||
},
|
||||
proxy: None,
|
||||
accumulated_rewards: Uint128::zero(),
|
||||
epoch_rewards: None,
|
||||
};
|
||||
|
||||
storage::mixnodes()
|
||||
.save(&mut mock_storage, &node_identity, &mixnode_bond)
|
||||
.save(&mut mock_storage, &node_identity, &mixnode_bond, 1)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
|
||||
@@ -9,13 +9,31 @@ use crate::mixnodes::storage::StoredMixnodeBond;
|
||||
use crate::support::helpers::{ensure_no_existing_bond, validate_node_identity_signature};
|
||||
use config::defaults::DENOM;
|
||||
use cosmwasm_std::{
|
||||
wasm_execute, Addr, BankMsg, Coin, DepsMut, Env, MessageInfo, Response, Uint128,
|
||||
wasm_execute, Addr, BankMsg, Coin, DepsMut, Env, MessageInfo, Response, Storage, Uint128,
|
||||
};
|
||||
use mixnet_contract_common::events::{
|
||||
new_checkpoint_mixnodes_event, new_mixnode_bonding_event, new_mixnode_unbonding_event,
|
||||
};
|
||||
use mixnet_contract_common::events::{new_mixnode_bonding_event, new_mixnode_unbonding_event};
|
||||
use mixnet_contract_common::MixNode;
|
||||
use vesting_contract_common::messages::ExecuteMsg as VestingContractExecuteMsg;
|
||||
use vesting_contract_common::one_ucoin;
|
||||
|
||||
pub fn try_checkpoint_mixnodes(
|
||||
storage: &mut dyn Storage,
|
||||
block_height: u64,
|
||||
info: MessageInfo,
|
||||
) -> Result<Response, ContractError> {
|
||||
let state = mixnet_params_storage::CONTRACT_STATE.load(storage)?;
|
||||
// check if this is executed by the permitted validator, if not reject the transaction
|
||||
if info.sender != state.rewarding_validator_address {
|
||||
return Err(ContractError::Unauthorized);
|
||||
}
|
||||
|
||||
crate::mixnodes::storage::mixnodes().add_checkpoint(storage, block_height)?;
|
||||
|
||||
Ok(Response::new().add_event(new_checkpoint_mixnodes_event(block_height)))
|
||||
}
|
||||
|
||||
pub fn try_add_mixnode(
|
||||
deps: DepsMut<'_>,
|
||||
env: Env,
|
||||
@@ -117,12 +135,14 @@ fn _try_add_mixnode(
|
||||
env.block.height,
|
||||
mix_node,
|
||||
proxy.clone(),
|
||||
Uint128::zero(),
|
||||
None,
|
||||
);
|
||||
|
||||
// technically we don't have to set the total_delegation bucket, but it makes things easier
|
||||
// in different places that we can guarantee that if node exists, so does the data behind the total delegation
|
||||
let identity = stored_bond.identity();
|
||||
storage::mixnodes().save(deps.storage, identity, &stored_bond)?;
|
||||
storage::mixnodes().save(deps.storage, identity, &stored_bond, env.block.height)?;
|
||||
|
||||
// if this is a fresh mixnode - write 0 total delegation, otherwise, don't touch it since the node has just rebonded
|
||||
if storage::TOTAL_DELEGATION
|
||||
@@ -144,25 +164,38 @@ fn _try_add_mixnode(
|
||||
}
|
||||
|
||||
pub fn try_remove_mixnode_on_behalf(
|
||||
env: Env,
|
||||
deps: DepsMut<'_>,
|
||||
info: MessageInfo,
|
||||
owner: String,
|
||||
) -> Result<Response, ContractError> {
|
||||
let proxy = info.sender;
|
||||
_try_remove_mixnode(deps, &owner, Some(proxy))
|
||||
_try_remove_mixnode(env, deps, &owner, Some(proxy))
|
||||
}
|
||||
|
||||
pub fn try_remove_mixnode(deps: DepsMut<'_>, info: MessageInfo) -> Result<Response, ContractError> {
|
||||
_try_remove_mixnode(deps, info.sender.as_ref(), None)
|
||||
pub fn try_remove_mixnode(
|
||||
env: Env,
|
||||
deps: DepsMut<'_>,
|
||||
info: MessageInfo,
|
||||
) -> Result<Response, ContractError> {
|
||||
_try_remove_mixnode(env, deps, info.sender.as_ref(), None)
|
||||
}
|
||||
|
||||
pub(crate) fn _try_remove_mixnode(
|
||||
env: Env,
|
||||
deps: DepsMut<'_>,
|
||||
owner: &str,
|
||||
proxy: Option<Addr>,
|
||||
) -> Result<Response, ContractError> {
|
||||
let owner = deps.api.addr_validate(owner)?;
|
||||
|
||||
crate::rewards::transactions::_try_compound_operator_reward(
|
||||
deps.storage,
|
||||
env.block.height,
|
||||
&owner,
|
||||
None,
|
||||
)?;
|
||||
|
||||
// try to find the node of the sender
|
||||
let mixnode_bond = match storage::mixnodes()
|
||||
.idx
|
||||
@@ -188,7 +221,7 @@ pub(crate) fn _try_remove_mixnode(
|
||||
};
|
||||
|
||||
// remove the bond
|
||||
storage::mixnodes().remove(deps.storage, mixnode_bond.identity())?;
|
||||
storage::mixnodes().remove(deps.storage, mixnode_bond.identity(), env.block.height)?;
|
||||
|
||||
// decrement layer count
|
||||
mixnet_params_storage::decrement_layer_count(deps.storage, mixnode_bond.layer)?;
|
||||
@@ -267,7 +300,11 @@ pub(crate) fn _try_update_mixnode_config(
|
||||
));
|
||||
}
|
||||
|
||||
storage::mixnodes().update(deps.storage, mixnode_bond.identity(), |mixnode_bond_opt| {
|
||||
storage::mixnodes().update(
|
||||
deps.storage,
|
||||
mixnode_bond.identity(),
|
||||
env.block.height,
|
||||
|mixnode_bond_opt| {
|
||||
mixnode_bond_opt
|
||||
.map(|mut mixnode_bond| {
|
||||
mixnode_bond.mix_node.profit_margin_percent = profit_margin_percent;
|
||||
@@ -275,7 +312,8 @@ pub(crate) fn _try_update_mixnode_config(
|
||||
mixnode_bond
|
||||
})
|
||||
.ok_or(ContractError::NoBondFound)
|
||||
})?;
|
||||
},
|
||||
)?;
|
||||
|
||||
let mut response = Response::new();
|
||||
|
||||
@@ -607,7 +645,7 @@ pub mod tests {
|
||||
.add_event(new_mixnode_unbonding_event(
|
||||
&Addr::unchecked("fred"),
|
||||
&None,
|
||||
&tests::fixtures::good_gateway_pledge()[0],
|
||||
&tests::fixtures::good_mixnode_pledge()[0],
|
||||
&fred_identity,
|
||||
));
|
||||
|
||||
@@ -615,6 +653,7 @@ pub mod tests {
|
||||
|
||||
// only 1 node now exists, owned by bob:
|
||||
let mix_node_bonds = tests::queries::get_mix_nodes(&mut deps);
|
||||
|
||||
assert_eq!(1, mix_node_bonds.len());
|
||||
assert_eq!(&Addr::unchecked("bob"), mix_node_bonds[0].owner());
|
||||
}
|
||||
@@ -642,7 +681,9 @@ pub mod tests {
|
||||
let info = mock_info("mix-owner", &[]);
|
||||
let msg = ExecuteMsg::UnbondMixnode {};
|
||||
|
||||
assert!(execute(deps.as_mut(), mock_env(), info, msg).is_ok());
|
||||
let response = execute(deps.as_mut(), mock_env(), info, msg);
|
||||
|
||||
assert!(response.is_ok());
|
||||
|
||||
assert!(storage::mixnodes()
|
||||
.idx
|
||||
|
||||
@@ -3,81 +3,20 @@
|
||||
|
||||
use super::storage;
|
||||
use crate::error::ContractError;
|
||||
use crate::mixnodes::storage as mixnodes_storage;
|
||||
use cosmwasm_std::{Addr, Storage, Uint128};
|
||||
use mixnet_contract_common::mixnode::DelegatorRewardParams;
|
||||
use mixnet_contract_common::{
|
||||
IdentityKey, IdentityKeyRef, PendingDelegatorRewarding, RewardingResult, RewardingStatus,
|
||||
};
|
||||
|
||||
pub(crate) fn update_post_rewarding_storage(
|
||||
storage: &mut dyn Storage,
|
||||
mix_identity: IdentityKeyRef<'_>,
|
||||
operator_reward: Uint128,
|
||||
delegators_reward: Uint128,
|
||||
) -> Result<(), ContractError> {
|
||||
if operator_reward == Uint128::zero() && delegators_reward == Uint128::zero() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// update pledge
|
||||
if operator_reward > Uint128::zero() {
|
||||
mixnodes_storage::mixnodes().update(storage, mix_identity, |current_bond| {
|
||||
match current_bond {
|
||||
None => Err(ContractError::MixNodeBondNotFound {
|
||||
identity: mix_identity.to_string(),
|
||||
}),
|
||||
Some(mut mixnode_bond) => {
|
||||
mixnode_bond.pledge_amount.amount += operator_reward;
|
||||
Ok(mixnode_bond)
|
||||
}
|
||||
}
|
||||
})?;
|
||||
}
|
||||
|
||||
// update total_delegation
|
||||
if delegators_reward > Uint128::zero() {
|
||||
mixnodes_storage::TOTAL_DELEGATION.update(storage, mix_identity, |current_total| {
|
||||
match current_total {
|
||||
None => Err(ContractError::MixNodeBondNotFound {
|
||||
identity: mix_identity.to_string(),
|
||||
}),
|
||||
Some(current_total) => Ok(current_total + delegators_reward),
|
||||
}
|
||||
})?;
|
||||
}
|
||||
|
||||
// update reward pool
|
||||
storage::decr_reward_pool(storage, operator_reward + delegators_reward)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
use cosmwasm_std::Storage;
|
||||
use mixnet_contract_common::{IdentityKey, RewardingResult, RewardingStatus};
|
||||
|
||||
pub(crate) fn update_rewarding_status(
|
||||
storage: &mut dyn Storage,
|
||||
interval_id: u32,
|
||||
mix_identity: IdentityKey,
|
||||
rewarding_results: RewardingResult,
|
||||
next_start: Option<Addr>,
|
||||
delegators_rewarding_params: DelegatorRewardParams,
|
||||
rewarding_result: RewardingResult,
|
||||
) -> Result<(), ContractError> {
|
||||
if let Some(next_start) = next_start {
|
||||
storage::REWARDING_STATUS.save(
|
||||
storage,
|
||||
(interval_id, mix_identity),
|
||||
&RewardingStatus::PendingNextDelegatorPage(PendingDelegatorRewarding {
|
||||
running_results: rewarding_results,
|
||||
next_start,
|
||||
rewarding_params: delegators_rewarding_params,
|
||||
}),
|
||||
&RewardingStatus::Complete(rewarding_result),
|
||||
)?;
|
||||
} else {
|
||||
storage::REWARDING_STATUS.save(
|
||||
storage,
|
||||
(interval_id, mix_identity),
|
||||
&RewardingStatus::Complete(rewarding_results),
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -35,11 +35,10 @@ pub(crate) mod tests {
|
||||
#[cfg(test)]
|
||||
mod querying_for_rewarding_status {
|
||||
use super::*;
|
||||
use crate::constants;
|
||||
use crate::delegations::transactions::try_delegate_to_mixnode;
|
||||
use crate::rewards::transactions::{
|
||||
try_reward_mixnode, try_reward_next_mixnode_delegators,
|
||||
};
|
||||
use crate::interval::storage::{save_epoch, save_epoch_reward_params};
|
||||
use crate::rewards::transactions::try_reward_mixnode;
|
||||
use crate::{constants, support::tests::fixtures::epoch_fixture};
|
||||
use config::defaults::DENOM;
|
||||
use cosmwasm_std::{coin, Addr};
|
||||
use mixnet_contract_common::{
|
||||
@@ -75,7 +74,7 @@ pub(crate) mod tests {
|
||||
env,
|
||||
info,
|
||||
node_identity.clone(),
|
||||
tests::fixtures::node_rewarding_params_fixture(100),
|
||||
tests::fixtures::node_reward_params_fixture(100),
|
||||
0,
|
||||
)
|
||||
.unwrap();
|
||||
@@ -87,6 +86,7 @@ pub(crate) mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
fn returns_complete_status_for_fully_rewarded_node() {
|
||||
// with single page
|
||||
let mut deps = test_helpers::init_contract();
|
||||
@@ -106,12 +106,17 @@ pub(crate) mod tests {
|
||||
env.block.height += constants::MINIMUM_BLOCK_AGE_FOR_REWARDING;
|
||||
|
||||
let info = mock_info(rewarding_validator_address.as_ref(), &[]);
|
||||
|
||||
let epoch = epoch_fixture();
|
||||
save_epoch(&mut deps.storage, &epoch).unwrap();
|
||||
save_epoch_reward_params(epoch.id(), &mut deps.storage).unwrap();
|
||||
|
||||
try_reward_mixnode(
|
||||
deps.as_mut(),
|
||||
env.clone(),
|
||||
info,
|
||||
node_identity.clone(),
|
||||
tests::fixtures::node_rewarding_params_fixture(100),
|
||||
tests::fixtures::node_reward_params_fixture(100),
|
||||
0,
|
||||
)
|
||||
.unwrap();
|
||||
@@ -121,14 +126,7 @@ pub(crate) mod tests {
|
||||
|
||||
match res.status.unwrap() {
|
||||
RewardingStatus::Complete(result) => {
|
||||
assert_ne!(
|
||||
RewardingResult::default().operator_reward,
|
||||
result.operator_reward
|
||||
);
|
||||
assert_eq!(
|
||||
RewardingResult::default().total_delegator_reward,
|
||||
result.total_delegator_reward
|
||||
);
|
||||
assert_ne!(RewardingResult::default().node_reward, result.node_reward);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
@@ -161,93 +159,21 @@ pub(crate) mod tests {
|
||||
env,
|
||||
info.clone(),
|
||||
node_identity.clone(),
|
||||
tests::fixtures::node_rewarding_params_fixture(100),
|
||||
tests::fixtures::node_reward_params_fixture(100),
|
||||
1,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// rewards all pending
|
||||
try_reward_next_mixnode_delegators(deps.as_mut(), info, node_identity.to_string(), 1)
|
||||
.unwrap();
|
||||
// try_reward_next_mixnode_delegators(deps.as_mut(), info, node_identity.to_string(), 1)
|
||||
// .unwrap();
|
||||
|
||||
let res = query_rewarding_status(deps.as_ref(), node_identity, 1).unwrap();
|
||||
assert!(matches!(res.status, Some(RewardingStatus::Complete(..))));
|
||||
|
||||
match res.status.unwrap() {
|
||||
RewardingStatus::Complete(result) => {
|
||||
assert_ne!(
|
||||
RewardingResult::default().operator_reward,
|
||||
result.operator_reward
|
||||
);
|
||||
assert_ne!(
|
||||
RewardingResult::default().total_delegator_reward,
|
||||
result.total_delegator_reward
|
||||
);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn returns_pending_next_delegator_page_status_when_there_are_more_delegators_to_reward() {
|
||||
let mut deps = test_helpers::init_contract();
|
||||
let mut env = mock_env();
|
||||
let current_state = mixnet_params_storage::CONTRACT_STATE
|
||||
.load(deps.as_mut().storage)
|
||||
.unwrap();
|
||||
let rewarding_validator_address = current_state.rewarding_validator_address;
|
||||
|
||||
let node_owner: Addr = Addr::unchecked("bob");
|
||||
let node_identity = test_helpers::add_mixnode(
|
||||
node_owner.as_str(),
|
||||
tests::fixtures::good_mixnode_pledge(),
|
||||
deps.as_mut(),
|
||||
);
|
||||
|
||||
for i in 0..MIXNODE_DELEGATORS_PAGE_LIMIT + 123 {
|
||||
try_delegate_to_mixnode(
|
||||
deps.as_mut(),
|
||||
env.clone(),
|
||||
mock_info(&*format!("delegator{:04}", i), &[coin(200_000000, DENOM)]),
|
||||
node_identity.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
env.block.height += constants::MINIMUM_BLOCK_AGE_FOR_REWARDING;
|
||||
|
||||
let info = mock_info(rewarding_validator_address.as_ref(), &[]);
|
||||
|
||||
try_reward_mixnode(
|
||||
deps.as_mut(),
|
||||
env,
|
||||
info,
|
||||
node_identity.clone(),
|
||||
tests::fixtures::node_rewarding_params_fixture(100),
|
||||
0,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let res = query_rewarding_status(deps.as_ref(), node_identity, 0).unwrap();
|
||||
assert!(matches!(
|
||||
res.status,
|
||||
Some(RewardingStatus::PendingNextDelegatorPage(..))
|
||||
));
|
||||
|
||||
match res.status.unwrap() {
|
||||
RewardingStatus::PendingNextDelegatorPage(result) => {
|
||||
assert_ne!(
|
||||
RewardingResult::default().operator_reward,
|
||||
result.running_results.operator_reward
|
||||
);
|
||||
assert_ne!(
|
||||
RewardingResult::default().total_delegator_reward,
|
||||
result.running_results.total_delegator_reward
|
||||
);
|
||||
assert_eq!(
|
||||
&*format!("delegator{:04}", MIXNODE_DELEGATORS_PAGE_LIMIT),
|
||||
result.next_start
|
||||
);
|
||||
assert_ne!(RewardingResult::default().node_reward, result.node_reward);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
@@ -5,12 +5,31 @@ use crate::error::ContractError;
|
||||
use config::defaults::TOTAL_SUPPLY;
|
||||
use cosmwasm_std::{StdResult, Storage, Uint128};
|
||||
use cw_storage_plus::{Item, Map};
|
||||
use mixnet_contract_common::{IdentityKey, RewardingStatus};
|
||||
use mixnet_contract_common::{reward_params::EpochRewardParams, IdentityKey, RewardingStatus};
|
||||
|
||||
type BlockHeight = u64;
|
||||
type Address = String;
|
||||
|
||||
pub(crate) const REWARD_POOL: Item<'_, Uint128> = Item::new("pool");
|
||||
// TODO: Do we need a migration for this?
|
||||
pub(crate) const REWARDING_STATUS: Map<'_, (u32, IdentityKey), RewardingStatus> = Map::new("rm");
|
||||
|
||||
pub(crate) const DELEGATOR_REWARD_CLAIMED_HEIGHT: Map<'_, (Address, IdentityKey), BlockHeight> =
|
||||
Map::new("drc");
|
||||
pub(crate) const OPERATOR_REWARD_CLAIMED_HEIGHT: Map<'_, (Address, IdentityKey), BlockHeight> =
|
||||
Map::new("orc");
|
||||
|
||||
type EpochId = u32;
|
||||
|
||||
pub(crate) const EPOCH_REWARD_PARAMS: Map<'_, EpochId, EpochRewardParams> = Map::new("epr");
|
||||
|
||||
pub fn epoch_reward_params_for_id(
|
||||
storage: &dyn Storage,
|
||||
id: EpochId,
|
||||
) -> StdResult<EpochRewardParams> {
|
||||
EPOCH_REWARD_PARAMS.load(storage, id)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn incr_reward_pool(
|
||||
amount: Uint128,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,11 +1,37 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::error::ContractError;
|
||||
use crate::gateways::storage as gateways_storage;
|
||||
use crate::mixnodes::storage as mixnodes_storage;
|
||||
use crate::{constants, gateways::storage as gateways_storage};
|
||||
|
||||
use crate::error::ContractError;
|
||||
use cosmwasm_std::{Addr, Deps, Storage};
|
||||
use mixnet_contract_common::IdentityKeyRef;
|
||||
use mixnet_contract_common::{reward_params::EpochRewardParams, IdentityKeyRef};
|
||||
|
||||
pub(crate) fn epoch_reward_params(
|
||||
epoch_id: u32,
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<EpochRewardParams, ContractError> {
|
||||
let state = crate::mixnet_contract_settings::storage::CONTRACT_STATE
|
||||
.load(storage)
|
||||
.map(|settings| settings.params)?;
|
||||
let reward_pool = crate::rewards::storage::REWARD_POOL.load(storage)?;
|
||||
let interval_reward_percent = crate::constants::INTERVAL_REWARD_PERCENT;
|
||||
let epochs_in_interval = crate::constants::EPOCHS_IN_INTERVAL;
|
||||
|
||||
let epoch_reward_params = EpochRewardParams::new(
|
||||
(reward_pool.u128() / 100 / epochs_in_interval as u128) * interval_reward_percent as u128,
|
||||
state.mixnode_rewarded_set_size as u128,
|
||||
state.mixnode_active_set_size as u128,
|
||||
crate::rewards::storage::circulating_supply(storage)?.u128(),
|
||||
constants::SYBIL_RESISTANCE_PERCENT,
|
||||
constants::ACTIVE_SET_WORK_FACTOR,
|
||||
);
|
||||
|
||||
crate::rewards::storage::EPOCH_REWARD_PARAMS.save(storage, epoch_id, &epoch_reward_params)?;
|
||||
|
||||
Ok(epoch_reward_params)
|
||||
}
|
||||
|
||||
pub fn generate_storage_key(address: &Addr, proxy: Option<&Addr>) -> Vec<u8> {
|
||||
if let Some(proxy) = &proxy {
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
use crate::constants::{INTERVAL_REWARD_PERCENT, SYBIL_RESISTANCE_PERCENT};
|
||||
use crate::contract::{INITIAL_MIXNODE_PLEDGE, INITIAL_REWARD_POOL};
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::contract::INITIAL_MIXNODE_PLEDGE;
|
||||
use crate::mixnodes::storage as mixnodes_storage;
|
||||
use crate::{mixnodes::storage::StoredMixnodeBond, support::tests};
|
||||
use config::defaults::{DENOM, TOTAL_SUPPLY};
|
||||
use cosmwasm_std::{coin, Addr, Coin};
|
||||
use mixnet_contract_common::mixnode::NodeRewardParams;
|
||||
use mixnet_contract_common::{Gateway, GatewayBond, Layer, MixNode};
|
||||
use config::defaults::DENOM;
|
||||
use cosmwasm_std::{coin, Addr, Coin, Uint128};
|
||||
use mixnet_contract_common::reward_params::NodeRewardParams;
|
||||
use mixnet_contract_common::{Gateway, GatewayBond, Interval, Layer, MixNode};
|
||||
use time::OffsetDateTime;
|
||||
|
||||
pub fn mix_node_fixture() -> MixNode {
|
||||
MixNode {
|
||||
@@ -57,6 +59,8 @@ pub(crate) fn stored_mixnode_bond_fixture(owner: &str) -> mixnodes_storage::Stor
|
||||
..super::fixtures::mix_node_fixture()
|
||||
},
|
||||
None,
|
||||
Uint128::zero(),
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -74,17 +78,10 @@ pub fn good_gateway_pledge() -> Vec<Coin> {
|
||||
}]
|
||||
}
|
||||
|
||||
// when exact values are irrelevant and what matters is the action of rewarding
|
||||
pub fn node_rewarding_params_fixture(uptime: u128) -> NodeRewardParams {
|
||||
NodeRewardParams::new(
|
||||
(INITIAL_REWARD_POOL / 100) * INTERVAL_REWARD_PERCENT as u128,
|
||||
50 as u128,
|
||||
25 as u128,
|
||||
0,
|
||||
TOTAL_SUPPLY - INITIAL_REWARD_POOL,
|
||||
uptime,
|
||||
SYBIL_RESISTANCE_PERCENT,
|
||||
true,
|
||||
10,
|
||||
)
|
||||
pub fn node_reward_params_fixture(uptime: u128) -> NodeRewardParams {
|
||||
NodeRewardParams::new(0, uptime, true)
|
||||
}
|
||||
|
||||
pub fn epoch_fixture() -> Interval {
|
||||
Interval::new(1, OffsetDateTime::now_utc(), Duration::from_secs(3600))
|
||||
}
|
||||
|
||||
@@ -29,7 +29,6 @@ pub mod test_helpers {
|
||||
use cosmwasm_std::{coin, Env, Timestamp};
|
||||
use cosmwasm_std::{Addr, StdResult, Storage};
|
||||
use cosmwasm_std::{Empty, MemoryStorage};
|
||||
use cw_storage_plus::PrimaryKey;
|
||||
use mixnet_contract_common::{Delegation, Gateway, IdentityKeyRef, InstantiateMsg, MixNode};
|
||||
use rand::thread_rng;
|
||||
|
||||
@@ -114,27 +113,27 @@ pub mod test_helpers {
|
||||
};
|
||||
|
||||
delegations_storage::delegations()
|
||||
.save(storage, delegation.storage_key().joined_key(), &delegation)
|
||||
.save(storage, delegation.storage_key(), &delegation)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub(crate) fn read_delegation(
|
||||
storage: &dyn Storage,
|
||||
mix: impl Into<String>,
|
||||
owner: impl Into<String>,
|
||||
owner: impl Into<Vec<u8>>,
|
||||
block_height: u64,
|
||||
) -> Option<Delegation> {
|
||||
delegations_storage::delegations()
|
||||
.may_load(storage, (mix.into(), owner.into()).joined_key())
|
||||
.may_load(storage, (mix.into(), owner.into(), block_height))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub(crate) fn update_env_and_progress_interval(env: &mut Env, storage: &mut dyn Storage) {
|
||||
// make sure current block time is within the expected next interval
|
||||
env.block.time = Timestamp::from_seconds(
|
||||
(interval_storage::CURRENT_INTERVAL
|
||||
.load(storage)
|
||||
(interval_storage::current_interval(storage)
|
||||
.unwrap()
|
||||
.next_interval()
|
||||
.next()
|
||||
.start_unix_timestamp()
|
||||
+ 123) as u64,
|
||||
);
|
||||
|
||||
@@ -19,7 +19,7 @@ vesting-contract-common = { path = "../../common/cosmwasm-smart-contracts/vestin
|
||||
config = { path = "../../common/config" }
|
||||
|
||||
cosmwasm-std = { version = "1.0.0-beta4"}
|
||||
cw-storage-plus = { version = "0.11.1", features = ["iterator"] }
|
||||
cw-storage-plus = { version = "0.12.1", features = ["iterator"] }
|
||||
|
||||
schemars = "0.8"
|
||||
serde = { version = "1.0", default-features = false, features = ["derive"] }
|
||||
|
||||
@@ -7,7 +7,7 @@ use crate::vesting::{populate_vesting_periods, Account};
|
||||
use config::defaults::DENOM;
|
||||
use cosmwasm_std::{
|
||||
coin, entry_point, to_binary, BankMsg, Coin, Deps, DepsMut, Env, MessageInfo, QueryResponse,
|
||||
Response, Timestamp, Uint128,
|
||||
Response, Timestamp,
|
||||
};
|
||||
use mixnet_contract_common::{Gateway, IdentityKey, MixNode};
|
||||
use vesting_contract_common::events::{
|
||||
@@ -146,11 +146,7 @@ pub fn try_withdraw_vested_coins(
|
||||
}
|
||||
let spendable_coins = account.spendable_coins(None, &env, deps.storage)?;
|
||||
if amount.amount <= spendable_coins.amount {
|
||||
let new_balance = account
|
||||
.load_balance(deps.storage)?
|
||||
.u128()
|
||||
.saturating_sub(amount.amount.u128());
|
||||
account.save_balance(Uint128::new(new_balance), deps.storage)?;
|
||||
let new_balance = account.withdraw(&amount, deps.storage)?;
|
||||
|
||||
let send_tokens = BankMsg::Send {
|
||||
to_address: account.owner_address().as_str().to_string(),
|
||||
|
||||
@@ -11,6 +11,7 @@ pub const KEY: Item<'_, u32> = Item::new("key");
|
||||
const ACCOUNTS: Map<'_, String, Account> = Map::new("acc");
|
||||
// Holds data related to individual accounts
|
||||
const BALANCES: Map<'_, u32, Uint128> = Map::new("blc");
|
||||
const WITHDRAWNS: Map<'_, u32, Uint128> = Map::new("wthd");
|
||||
const BOND_PLEDGES: Map<'_, u32, PledgeData> = Map::new("bnd");
|
||||
const GATEWAY_PLEDGES: Map<'_, u32, PledgeData> = Map::new("gtw");
|
||||
pub const DELEGATIONS: Map<'_, (u32, IdentityKey, BlockHeight), Uint128> = Map::new("dlg");
|
||||
@@ -39,6 +40,13 @@ pub fn delete_account(address: &Addr, storage: &mut dyn Storage) -> Result<(), C
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn load_withdrawn(key: u32, storage: &dyn Storage) -> Result<Uint128, ContractError> {
|
||||
Ok(WITHDRAWNS
|
||||
.may_load(storage, key)
|
||||
.unwrap_or(None)
|
||||
.unwrap_or_else(Uint128::zero))
|
||||
}
|
||||
|
||||
pub fn load_balance(key: u32, storage: &dyn Storage) -> Result<Uint128, ContractError> {
|
||||
Ok(BALANCES
|
||||
.may_load(storage, key)
|
||||
@@ -55,6 +63,15 @@ pub fn save_balance(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn save_withdrawn(
|
||||
key: u32,
|
||||
value: Uint128,
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<(), ContractError> {
|
||||
WITHDRAWNS.save(storage, key, &value)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn load_bond_pledge(
|
||||
key: u32,
|
||||
storage: &dyn Storage,
|
||||
|
||||
@@ -18,7 +18,29 @@ pub mod helpers {
|
||||
return deps;
|
||||
}
|
||||
|
||||
pub fn vesting_account_fixture(storage: &mut dyn Storage, env: &Env) -> Account {
|
||||
pub fn vesting_account_mid_fixture(storage: &mut dyn Storage, env: &Env) -> Account {
|
||||
let start_time_ts = env.block.time.clone();
|
||||
let start_time = env.block.time.seconds() - 7200;
|
||||
let periods = populate_vesting_periods(
|
||||
start_time,
|
||||
VestingSpecification::new(None, Some(3600), None),
|
||||
);
|
||||
|
||||
Account::new(
|
||||
Addr::unchecked("owner"),
|
||||
Some(Addr::unchecked("staking")),
|
||||
Coin {
|
||||
amount: Uint128::new(1_000_000_000_000),
|
||||
denom: DENOM.to_string(),
|
||||
},
|
||||
start_time_ts,
|
||||
periods,
|
||||
storage,
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn vesting_account_new_fixture(storage: &mut dyn Storage, env: &Env) -> Account {
|
||||
let start_time = env.block.time;
|
||||
let periods =
|
||||
populate_vesting_periods(start_time.seconds(), VestingSpecification::default());
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
use crate::errors::ContractError;
|
||||
use cosmwasm_std::{Coin, Env, Response, Storage};
|
||||
use mixnet_contract_common::{Gateway, MixNode};
|
||||
|
||||
pub trait MixnodeBondingAccount {
|
||||
fn try_bond_mixnode(
|
||||
&self,
|
||||
mix_node: MixNode,
|
||||
owner_signature: String,
|
||||
pledge: Coin,
|
||||
env: &Env,
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<Response, ContractError>;
|
||||
|
||||
fn try_unbond_mixnode(&self, storage: &dyn Storage) -> Result<Response, ContractError>;
|
||||
|
||||
fn try_track_unbond_mixnode(
|
||||
&self,
|
||||
amount: Coin,
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<(), ContractError>;
|
||||
|
||||
fn try_update_mixnode_config(
|
||||
&self,
|
||||
profit_margin_percent: u8,
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<Response, ContractError>;
|
||||
}
|
||||
|
||||
pub trait GatewayBondingAccount {
|
||||
fn try_bond_gateway(
|
||||
&self,
|
||||
gateway: Gateway,
|
||||
owner_signature: String,
|
||||
pledge: Coin,
|
||||
env: &Env,
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<Response, ContractError>;
|
||||
|
||||
fn try_unbond_gateway(&self, storage: &dyn Storage) -> Result<Response, ContractError>;
|
||||
|
||||
fn try_track_unbond_gateway(
|
||||
&self,
|
||||
amount: Coin,
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<(), ContractError>;
|
||||
}
|
||||
@@ -1 +1,47 @@
|
||||
use crate::errors::ContractError;
|
||||
use cosmwasm_std::{Coin, Env, Response, Storage};
|
||||
use mixnet_contract_common::{Gateway, MixNode};
|
||||
|
||||
pub trait MixnodeBondingAccount {
|
||||
fn try_bond_mixnode(
|
||||
&self,
|
||||
mix_node: MixNode,
|
||||
owner_signature: String,
|
||||
pledge: Coin,
|
||||
env: &Env,
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<Response, ContractError>;
|
||||
|
||||
fn try_unbond_mixnode(&self, storage: &dyn Storage) -> Result<Response, ContractError>;
|
||||
|
||||
fn try_track_unbond_mixnode(
|
||||
&self,
|
||||
amount: Coin,
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<(), ContractError>;
|
||||
|
||||
fn try_update_mixnode_config(
|
||||
&self,
|
||||
profit_margin_percent: u8,
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<Response, ContractError>;
|
||||
}
|
||||
|
||||
pub trait GatewayBondingAccount {
|
||||
fn try_bond_gateway(
|
||||
&self,
|
||||
gateway: Gateway,
|
||||
owner_signature: String,
|
||||
pledge: Coin,
|
||||
env: &Env,
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<Response, ContractError>;
|
||||
|
||||
fn try_unbond_gateway(&self, storage: &dyn Storage) -> Result<Response, ContractError>;
|
||||
|
||||
fn try_track_unbond_gateway(
|
||||
&self,
|
||||
amount: Coin,
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<(), ContractError>;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
pub mod bonding;
|
||||
pub mod bonding_account;
|
||||
pub mod delegating_account;
|
||||
pub mod vesting_account;
|
||||
|
||||
pub use self::bonding::{GatewayBondingAccount, MixnodeBondingAccount};
|
||||
pub use self::bonding_account::{GatewayBondingAccount, MixnodeBondingAccount};
|
||||
pub use self::delegating_account::DelegatingAccount;
|
||||
pub use self::vesting_account::VestingAccount;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use super::VestingPeriod;
|
||||
use crate::errors::ContractError;
|
||||
use crate::storage::{
|
||||
load_balance, load_bond_pledge, load_gateway_pledge, remove_bond_pledge, remove_delegation,
|
||||
remove_gateway_pledge, save_account, save_balance, save_bond_pledge, save_gateway_pledge,
|
||||
DELEGATIONS, KEY,
|
||||
load_balance, load_bond_pledge, load_gateway_pledge, load_withdrawn, remove_bond_pledge,
|
||||
remove_delegation, remove_gateway_pledge, save_account, save_balance, save_bond_pledge,
|
||||
save_gateway_pledge, save_withdrawn, DELEGATIONS, KEY,
|
||||
};
|
||||
use cosmwasm_std::{Addr, Coin, Order, Storage, Timestamp, Uint128};
|
||||
use cw_storage_plus::Bound;
|
||||
@@ -121,6 +121,33 @@ impl Account {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn withdraw(
|
||||
&self,
|
||||
amount: &Coin,
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<u128, ContractError> {
|
||||
let new_balance = self
|
||||
.load_balance(storage)?
|
||||
.u128()
|
||||
.saturating_sub(amount.amount.u128());
|
||||
self.save_balance(Uint128::new(new_balance), storage)?;
|
||||
let withdrawn = self.load_withdrawn(storage)?;
|
||||
self.save_withdrawn(withdrawn + amount.amount, storage)?;
|
||||
Ok(new_balance)
|
||||
}
|
||||
|
||||
pub fn load_withdrawn(&self, storage: &dyn Storage) -> Result<Uint128, ContractError> {
|
||||
load_withdrawn(self.storage_key, storage)
|
||||
}
|
||||
|
||||
pub fn save_withdrawn(
|
||||
&self,
|
||||
withdrawn: Uint128,
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<(), ContractError> {
|
||||
save_withdrawn(self.storage_key, withdrawn, storage)
|
||||
}
|
||||
|
||||
pub fn load_balance(&self, storage: &dyn Storage) -> Result<Uint128, ContractError> {
|
||||
load_balance(self.storage_key(), storage)
|
||||
}
|
||||
@@ -204,7 +231,7 @@ impl Account {
|
||||
|
||||
prev_len = block_heights.len();
|
||||
|
||||
start_after = block_heights.last().map(|last| Bound::exclusive_int(*last));
|
||||
start_after = block_heights.last().map(|last| Bound::exclusive(*last));
|
||||
if start_after.is_none() {
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -114,7 +114,11 @@ impl VestingAccount for Account {
|
||||
) -> Result<Coin, ContractError> {
|
||||
let block_time = block_time.unwrap_or(env.block.time);
|
||||
let period = self.get_current_vesting_period(block_time);
|
||||
let max_vested = self.get_vested_coins(Some(block_time), env)?;
|
||||
let withdrawn = self.load_withdrawn(storage)?;
|
||||
let max_available = self
|
||||
.get_vested_coins(Some(block_time), env)?
|
||||
.amount
|
||||
.saturating_sub(withdrawn);
|
||||
let start_time = match period {
|
||||
Period::Before => 0,
|
||||
Period::After => u64::MAX,
|
||||
@@ -130,7 +134,7 @@ impl VestingAccount for Account {
|
||||
acc + amount
|
||||
});
|
||||
|
||||
let amount = Uint128::new(coin.u128().min(max_vested.amount.u128()));
|
||||
let amount = Uint128::new(coin.u128().min(max_available.u128()));
|
||||
|
||||
Ok(Coin {
|
||||
amount,
|
||||
|
||||
@@ -38,7 +38,9 @@ pub fn populate_vesting_periods(
|
||||
mod tests {
|
||||
use crate::contract::execute;
|
||||
use crate::storage::load_account;
|
||||
use crate::support::tests::helpers::{init_contract, vesting_account_fixture};
|
||||
use crate::support::tests::helpers::{
|
||||
init_contract, vesting_account_mid_fixture, vesting_account_new_fixture,
|
||||
};
|
||||
use crate::traits::DelegatingAccount;
|
||||
use crate::traits::VestingAccount;
|
||||
use crate::traits::{GatewayBondingAccount, MixnodeBondingAccount};
|
||||
@@ -81,7 +83,7 @@ mod tests {
|
||||
let _response = execute(deps.as_mut(), env.clone(), info, msg.clone());
|
||||
assert!(response.is_err());
|
||||
|
||||
let account_again = vesting_account_fixture(&mut deps.storage, &env);
|
||||
let account_again = vesting_account_new_fixture(&mut deps.storage, &env);
|
||||
assert_eq!(created_account.storage_key(), 1);
|
||||
assert_ne!(created_account.storage_key(), account_again.storage_key());
|
||||
}
|
||||
@@ -91,7 +93,7 @@ mod tests {
|
||||
let mut deps = init_contract();
|
||||
let mut env = mock_env();
|
||||
let info = mock_info("owner", &[]);
|
||||
let account = vesting_account_fixture(&mut deps.storage, &env);
|
||||
let account = vesting_account_new_fixture(&mut deps.storage, &env);
|
||||
let msg = ExecuteMsg::TransferOwnership {
|
||||
to_address: "new_owner".to_string(),
|
||||
};
|
||||
@@ -172,7 +174,7 @@ mod tests {
|
||||
let num_vesting_periods = 8;
|
||||
let vesting_period = 3 * 30 * 86400;
|
||||
|
||||
let account = vesting_account_fixture(&mut deps.storage, &env);
|
||||
let account = vesting_account_new_fixture(&mut deps.storage, &env);
|
||||
|
||||
assert_eq!(account.periods().len(), num_vesting_periods as usize);
|
||||
|
||||
@@ -236,12 +238,149 @@ mod tests {
|
||||
assert_eq!(vesting_coins.amount, Uint128::zero());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_withdraw_case() {
|
||||
let mut deps = init_contract();
|
||||
let env = mock_env();
|
||||
let account = vesting_account_mid_fixture(&mut deps.storage, &env);
|
||||
|
||||
let vested_coins = account.get_vested_coins(None, &env).unwrap();
|
||||
let vesting_coins = account.get_vesting_coins(None, &env).unwrap();
|
||||
let locked_coins = account.locked_coins(None, &env, &mut deps.storage).unwrap();
|
||||
assert_eq!(vested_coins.amount, Uint128::new(250_000_000_000));
|
||||
assert_eq!(vesting_coins.amount, Uint128::new(750_000_000_000));
|
||||
assert_eq!(locked_coins.amount, Uint128::new(750_000_000_000));
|
||||
let spendable = account
|
||||
.spendable_coins(None, &env, &mut deps.storage)
|
||||
.unwrap();
|
||||
assert_eq!(spendable.amount, Uint128::new(250_000_000_000));
|
||||
let withdrawn = account.load_withdrawn(&deps.storage).unwrap();
|
||||
assert_eq!(withdrawn, Uint128::zero());
|
||||
|
||||
let mix_identity = "alice".to_string();
|
||||
|
||||
let delegation = Coin {
|
||||
amount: Uint128::new(500_000_000_000),
|
||||
denom: DENOM.to_string(),
|
||||
};
|
||||
|
||||
let ok = account.try_delegate_to_mixnode(
|
||||
mix_identity.clone(),
|
||||
delegation.clone(),
|
||||
&env,
|
||||
&mut deps.storage,
|
||||
);
|
||||
assert!(ok.is_ok());
|
||||
|
||||
let vested_coins = account.get_vested_coins(None, &env).unwrap();
|
||||
let vesting_coins = account.get_vesting_coins(None, &env).unwrap();
|
||||
assert_eq!(vested_coins.amount, Uint128::new(250_000_000_000));
|
||||
assert_eq!(vesting_coins.amount, Uint128::new(750_000_000_000));
|
||||
|
||||
let delegated_free = account
|
||||
.get_delegated_free(None, &env, &mut deps.storage)
|
||||
.unwrap();
|
||||
let delegated_vesting = account
|
||||
.get_delegated_vesting(None, &env, &mut deps.storage)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(delegated_free.amount, Uint128::new(250_000_000_000));
|
||||
assert_eq!(delegated_vesting.amount, Uint128::new(250_000_000_000));
|
||||
|
||||
let locked_coins = account.locked_coins(None, &env, &mut deps.storage).unwrap();
|
||||
// vesting - delegated_vesting - pledged_vesting
|
||||
assert_eq!(locked_coins.amount, Uint128::new(500_000_000_000));
|
||||
let spendable = account
|
||||
.spendable_coins(None, &env, &mut deps.storage)
|
||||
.unwrap();
|
||||
assert_eq!(spendable.amount, Uint128::zero());
|
||||
|
||||
let ok = account.try_undelegate_from_mixnode(mix_identity.clone(), &mut deps.storage);
|
||||
assert!(ok.is_ok());
|
||||
|
||||
account
|
||||
.track_undelegation(mix_identity.clone(), delegation.clone(), &mut deps.storage)
|
||||
.unwrap();
|
||||
|
||||
let delegated_free = account
|
||||
.get_delegated_free(None, &env, &mut deps.storage)
|
||||
.unwrap();
|
||||
let delegated_vesting = account
|
||||
.get_delegated_vesting(None, &env, &mut deps.storage)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(delegated_free.amount, Uint128::zero());
|
||||
assert_eq!(delegated_vesting.amount, Uint128::zero());
|
||||
|
||||
assert_eq!(
|
||||
account.load_balance(&deps.storage).unwrap(),
|
||||
Uint128::new(1000_000_000_000)
|
||||
);
|
||||
|
||||
account
|
||||
.withdraw(
|
||||
&account.spendable_coins(None, &env, &deps.storage).unwrap(),
|
||||
&mut deps.storage,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
account.load_balance(&deps.storage).unwrap(),
|
||||
Uint128::new(750_000_000_000)
|
||||
);
|
||||
|
||||
let withdrawn = account.load_withdrawn(&deps.storage).unwrap();
|
||||
assert_eq!(withdrawn, Uint128::new(250_000_000_000));
|
||||
|
||||
let vested_coins = account.get_vested_coins(None, &env).unwrap();
|
||||
let vesting_coins = account.get_vesting_coins(None, &env).unwrap();
|
||||
let locked_coins = account.locked_coins(None, &env, &mut deps.storage).unwrap();
|
||||
assert_eq!(vested_coins.amount, Uint128::new(250_000_000_000));
|
||||
assert_eq!(vesting_coins.amount, Uint128::new(750_000_000_000));
|
||||
assert_eq!(locked_coins.amount, Uint128::new(750_000_000_000));
|
||||
let spendable = account
|
||||
.spendable_coins(None, &env, &mut deps.storage)
|
||||
.unwrap();
|
||||
assert_eq!(spendable.amount, Uint128::zero());
|
||||
|
||||
let ok = account.try_delegate_to_mixnode(
|
||||
mix_identity.clone(),
|
||||
delegation.clone(),
|
||||
&env,
|
||||
&mut deps.storage,
|
||||
);
|
||||
assert!(ok.is_ok());
|
||||
|
||||
let vested_coins = account.get_vested_coins(None, &env).unwrap();
|
||||
let vesting_coins = account.get_vesting_coins(None, &env).unwrap();
|
||||
let locked_coins = account.locked_coins(None, &env, &mut deps.storage).unwrap();
|
||||
assert_eq!(vested_coins.amount, Uint128::new(250_000_000_000));
|
||||
assert_eq!(vesting_coins.amount, Uint128::new(750_000_000_000));
|
||||
let spendable = account
|
||||
.spendable_coins(None, &env, &mut deps.storage)
|
||||
.unwrap();
|
||||
assert_eq!(spendable.amount, Uint128::zero());
|
||||
|
||||
let delegated_free = account
|
||||
.get_delegated_free(None, &env, &mut deps.storage)
|
||||
.unwrap();
|
||||
let delegated_vesting = account
|
||||
.get_delegated_vesting(None, &env, &mut deps.storage)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(delegated_free.amount, Uint128::zero());
|
||||
assert_eq!(delegated_vesting.amount, Uint128::new(500_000_000_000));
|
||||
|
||||
// vesting - delegated_vesting - pledged_vesting
|
||||
assert_eq!(locked_coins.amount, Uint128::new(250_000_000_000));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_delegations() {
|
||||
let mut deps = init_contract();
|
||||
let env = mock_env();
|
||||
|
||||
let account = vesting_account_fixture(&mut deps.storage, &env);
|
||||
let account = vesting_account_new_fixture(&mut deps.storage, &env);
|
||||
|
||||
// Try delegating too much
|
||||
let err = account.try_delegate_to_mixnode(
|
||||
@@ -352,7 +491,7 @@ mod tests {
|
||||
let mut deps = init_contract();
|
||||
let env = mock_env();
|
||||
|
||||
let account = vesting_account_fixture(&mut deps.storage, &env);
|
||||
let account = vesting_account_new_fixture(&mut deps.storage, &env);
|
||||
|
||||
let mix_node = MixNode {
|
||||
host: "mix.node.org".to_string(),
|
||||
@@ -472,7 +611,7 @@ mod tests {
|
||||
let mut deps = init_contract();
|
||||
let env = mock_env();
|
||||
|
||||
let account = vesting_account_fixture(&mut deps.storage, &env);
|
||||
let account = vesting_account_new_fixture(&mut deps.storage, &env);
|
||||
|
||||
let gateway = Gateway {
|
||||
host: "1.1.1.1".to_string(),
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es6": true,
|
||||
"node": true,
|
||||
"jest": true
|
||||
},
|
||||
"parserOptions": {
|
||||
"ecmaFeatures": {
|
||||
"jsx": true
|
||||
},
|
||||
"ecmaVersion": 2019,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"globals": {
|
||||
"Atomics": "readonly",
|
||||
"SharedArrayBuffer": "readonly"
|
||||
},
|
||||
"plugins": ["react", "react-hooks", "jsx-a11y", "prettier", "jest"],
|
||||
"extends": ["plugin:react/recommended", "airbnb", "prettier", "plugin:jest/recommended", "plugin:jest/style"],
|
||||
"rules": {
|
||||
"jest/prefer-strict-equal": "error",
|
||||
"jest/prefer-to-have-length": "warn",
|
||||
"prettier/prettier": "error",
|
||||
"import/prefer-default-export": "off",
|
||||
"react/prop-types": "off",
|
||||
"react/jsx-filename-extension": "off",
|
||||
"react/jsx-props-no-spreading": "off",
|
||||
"import/no-extraneous-dependencies": [
|
||||
"error",
|
||||
{
|
||||
"devDependencies": [
|
||||
"**/*.test.[jt]s",
|
||||
"**/*.spec.[jt]s",
|
||||
"**/*.test.[jt]sx",
|
||||
"**/*.spec.[jt]sx"
|
||||
]
|
||||
}
|
||||
],
|
||||
"import/extensions": [
|
||||
"error",
|
||||
"ignorePackages",
|
||||
{
|
||||
"ts": "never",
|
||||
"tsx": "never",
|
||||
"js": "never",
|
||||
"jsx": "never"
|
||||
}
|
||||
]
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"files": "**/*.+(ts|tsx)",
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"project": "./tsconfig.json"
|
||||
},
|
||||
"plugins": ["@typescript-eslint/eslint-plugin"],
|
||||
"extends": [
|
||||
"plugin:@typescript-eslint/eslint-recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"prettier"
|
||||
],
|
||||
"rules": {
|
||||
"@typescript-eslint/explicit-function-return-type": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-var-requires": "off",
|
||||
"no-use-before-define": [0],
|
||||
"@typescript-eslint/no-use-before-define": [1],
|
||||
"import/no-unresolved": 0,
|
||||
"import/no-extraneous-dependencies": [
|
||||
"error",
|
||||
{
|
||||
"devDependencies": [
|
||||
"**/*.test.ts",
|
||||
"**/*.spec.ts",
|
||||
"**/*.test.tsx",
|
||||
"**/*.spec.tsx"
|
||||
]
|
||||
}
|
||||
],
|
||||
"quotes": "off",
|
||||
"@typescript-eslint/quotes": [
|
||||
2,
|
||||
"single",
|
||||
{
|
||||
"avoidEscape": true
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/no-unused-vars": [2, { "argsIgnorePattern": "^_" }]
|
||||
}
|
||||
}
|
||||
],
|
||||
"settings": {
|
||||
"import/resolver": {
|
||||
"root-import": {
|
||||
"rootPathPrefix": "@",
|
||||
"rootPathSuffix": "src",
|
||||
"extensions": [".js", ".ts", ".tsx", ".jsx", ".mdx"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"extends": [
|
||||
"@nymproject/eslint-config-react-typescript"
|
||||
]
|
||||
}
|
||||
+1
-1
@@ -1 +1 @@
|
||||
14
|
||||
16
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"trailingComma": "all",
|
||||
"singleQuote": true,
|
||||
"printWidth": 80,
|
||||
"printWidth": 120,
|
||||
"tabWidth": 2
|
||||
}
|
||||
|
||||
+17
-5
@@ -8,12 +8,22 @@ You will need:
|
||||
|
||||
- NodeJS (use `nvm install` to automatically install the correct version)
|
||||
- `npm`
|
||||
- `yarn`
|
||||
|
||||
> **Note**: This project ipart of a mono repo, so you will need to build the shared packages before starting. And any time they change, you'll need to rebuild them.
|
||||
|
||||
From the [root of the repository](../README.md) run the following to build shared packages:
|
||||
|
||||
```
|
||||
yarn
|
||||
yarn build
|
||||
```
|
||||
|
||||
From the `explorer` directory of the `nym` monorepo, run:
|
||||
|
||||
```
|
||||
npm install
|
||||
npm run start
|
||||
cd explorer
|
||||
yarn start
|
||||
```
|
||||
|
||||
You can then open a browser to http://localhost:3000 and start development.
|
||||
@@ -24,11 +34,13 @@ Documentation for developers [can be found here](./docs).
|
||||
|
||||
## Deployment
|
||||
|
||||
Build the UI with:
|
||||
Build the UI with (starting in the repository root):
|
||||
|
||||
```
|
||||
npm install
|
||||
npm run build
|
||||
yarn
|
||||
yarn build
|
||||
cd explorer
|
||||
yarn build
|
||||
```
|
||||
|
||||
The output will be in the `dist` directory. Serve this with `nginx` or `httpd`.
|
||||
|
||||
Generated
-30225
File diff suppressed because it is too large
Load Diff
+35
-24
@@ -12,20 +12,27 @@
|
||||
"@mui/styles": "^5.0.1",
|
||||
"@mui/system": "^5.0.1",
|
||||
"@mui/x-data-grid": "^5.0.0-beta.5",
|
||||
"@nymproject/mui-theme": "^1.0.0",
|
||||
"@nymproject/nym-validator-client": "^0.18.0",
|
||||
"@nymproject/react": "^1.0.0",
|
||||
"d3-scale": "^4.0.0",
|
||||
"date-fns": "^2.24.0",
|
||||
"i18n-iso-countries": "^6.8.0",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-error-boundary": "^3.1.4",
|
||||
"react-google-charts": "^3.0.15",
|
||||
"react-identicons": "^1.2.5",
|
||||
"react-router": "^5.2.1",
|
||||
"react-router-dom": "^5.3.0",
|
||||
"react-simple-maps": "^2.3.0",
|
||||
"react-tooltip": "^4.2.21"
|
||||
"react-tooltip": "^4.2.21",
|
||||
"use-clipboard-copy": "^0.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.0-rc.3",
|
||||
"@nymproject/eslint-config-react-typescript": "^1.0.0",
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.4",
|
||||
"@svgr/webpack": "^6.1.1",
|
||||
"@testing-library/jest-dom": "^5.14.1",
|
||||
"@testing-library/react": "^12.0.0",
|
||||
"@types/d3-fetch": "^3.0.1",
|
||||
@@ -36,32 +43,36 @@
|
||||
"@types/node": "^16.7.13",
|
||||
"@types/react": "^17.0.34",
|
||||
"@types/react-dom": "^17.0.9",
|
||||
"@types/react-router": "^5.1.18",
|
||||
"@types/react-router-dom": "^5.1.8",
|
||||
"@types/react-simple-maps": "^1.0.6",
|
||||
"@types/react-tooltip": "^4.2.4",
|
||||
"@types/topojson-client": "^3.1.0",
|
||||
"@typescript-eslint/eslint-plugin": "4.31.0",
|
||||
"@typescript-eslint/parser": "4.31.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.13.0",
|
||||
"@typescript-eslint/parser": "^5.13.0",
|
||||
"babel-loader": "^8.2.2",
|
||||
"babel-plugin-root-import": "^6.6.0",
|
||||
"clean-webpack-plugin": "^4.0.0",
|
||||
"css-loader": "^6.2.0",
|
||||
"css-minimizer-webpack-plugin": "^3.0.2",
|
||||
"dotenv-webpack": "^7.0.3",
|
||||
"eslint": "7.32.0",
|
||||
"eslint-config-airbnb": "18.2.1",
|
||||
"eslint-config-prettier": "8.3.0",
|
||||
"eslint-import-resolver-root-import": "1.0.4",
|
||||
"eslint-plugin-import": "2.24.2",
|
||||
"eslint-plugin-jest": "^24.4.0",
|
||||
"eslint-plugin-jsx-a11y": "6.4.1",
|
||||
"eslint-plugin-prettier": "4.0.0",
|
||||
"eslint-plugin-react": "7.25.1",
|
||||
"eslint-plugin-react-hooks": "4.2.0",
|
||||
"fork-ts-checker-webpack-plugin": "^6.3.3",
|
||||
"eslint": "^8.10.0",
|
||||
"eslint-config-airbnb": "^19.0.4",
|
||||
"eslint-config-airbnb-typescript": "^16.1.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-import-resolver-root-import": "^1.0.4",
|
||||
"eslint-plugin-import": "^2.25.4",
|
||||
"eslint-plugin-jest": "^26.1.1",
|
||||
"eslint-plugin-jsx-a11y": "^6.5.1",
|
||||
"eslint-plugin-prettier": "^4.0.0",
|
||||
"eslint-plugin-react": "^7.29.2",
|
||||
"eslint-plugin-react-hooks": "^4.3.0",
|
||||
"eslint-plugin-storybook": "^0.5.7",
|
||||
"favicons-webpack-plugin": "^5.0.2",
|
||||
"file-loader": "^6.2.0",
|
||||
"fork-ts-checker-webpack-plugin": "^7.2.1",
|
||||
"html-webpack-plugin": "^5.3.2",
|
||||
"imports-loader": "^3.0.0",
|
||||
"jest": "^27.1.0",
|
||||
"loader-utils": "^2.0.0",
|
||||
"mini-css-extract-plugin": "^2.2.2",
|
||||
"prettier": "2.3.2",
|
||||
"react-refresh": "^0.10.0",
|
||||
@@ -69,14 +80,14 @@
|
||||
"style-loader": "^3.2.1",
|
||||
"ts-jest": "^27.0.5",
|
||||
"ts-loader": "^9.2.5",
|
||||
"tsconfig-paths-webpack-plugin": "^3.5.1",
|
||||
"typescript": "^4.4.2",
|
||||
"webpack": "^5.52.0",
|
||||
"tsconfig-paths-webpack-plugin": "^3.5.2",
|
||||
"typescript": "^4.6.2",
|
||||
"url-loader": "^4.1.1",
|
||||
"webpack": "^5.64.3",
|
||||
"webpack-cli": "^4.8.0",
|
||||
"webpack-dev-server": "^4.1.0",
|
||||
"webpack-favicons": "^1.0.7",
|
||||
"webpack-merge": "^5.8.0",
|
||||
"yaml-loader": "^0.6.0"
|
||||
"webpack-dev-server": "^4.5.0",
|
||||
"webpack-favicons": "^1.3.8",
|
||||
"webpack-merge": "^5.8.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "webpack serve --progress --port 3000",
|
||||
|
||||
@@ -15,7 +15,6 @@ export const COUNTRY_DATA_API = `${API_BASE_URL}/countries`;
|
||||
export const UPTIME_STORY_API = `${VALIDATOR_API_BASE_URL}/api/v1/status/mixnode`; // add ID then '/history' to this.
|
||||
|
||||
// errors
|
||||
export const MIXNODE_API_ERROR =
|
||||
"We're having trouble finding that record, please try again or Contact Us.";
|
||||
export const MIXNODE_API_ERROR = "We're having trouble finding that record, please try again or Contact Us.";
|
||||
|
||||
export const NYM_WEBSITE = 'https://nymtech.net';
|
||||
|
||||
@@ -63,9 +63,7 @@ export class Api {
|
||||
return json;
|
||||
};
|
||||
|
||||
static fetchMixnodesActiveSetByStatus = async (
|
||||
status: MixnodeStatus,
|
||||
): Promise<MixNodeResponse> => {
|
||||
static fetchMixnodesActiveSetByStatus = async (status: MixnodeStatus): Promise<MixNodeResponse> => {
|
||||
const cachedMixnodes = getFromCache(`mixnodes-${status}`);
|
||||
if (cachedMixnodes) {
|
||||
return cachedMixnodes;
|
||||
@@ -76,9 +74,7 @@ export class Api {
|
||||
return json;
|
||||
};
|
||||
|
||||
static fetchMixnodeByID = async (
|
||||
id: string,
|
||||
): Promise<MixNodeResponseItem | undefined> => {
|
||||
static fetchMixnodeByID = async (id: string): Promise<MixNodeResponseItem | undefined> => {
|
||||
const response = await fetch(`${MIXNODE_API}/${id}`);
|
||||
|
||||
// when the mixnode is not found, returned undefined
|
||||
@@ -117,24 +113,17 @@ export class Api {
|
||||
return result;
|
||||
};
|
||||
|
||||
static fetchDelegationsById = async (
|
||||
id: string,
|
||||
): Promise<DelegationsResponse> =>
|
||||
static fetchDelegationsById = async (id: string): Promise<DelegationsResponse> =>
|
||||
(await fetch(`${MIXNODE_API}/${id}/delegations`)).json();
|
||||
|
||||
static fetchStatsById = async (id: string): Promise<StatsResponse> =>
|
||||
(await fetch(`${MIXNODE_API}/${id}/stats`)).json();
|
||||
|
||||
static fetchMixnodeDescriptionById = async (
|
||||
id: string,
|
||||
): Promise<MixNodeDescriptionResponse> =>
|
||||
static fetchMixnodeDescriptionById = async (id: string): Promise<MixNodeDescriptionResponse> =>
|
||||
(await fetch(`${MIXNODE_API}/${id}/description`)).json();
|
||||
|
||||
static fetchStatusById = async (id: string): Promise<StatusResponse> =>
|
||||
(await fetch(`${MIXNODE_PING}/${id}`)).json();
|
||||
static fetchStatusById = async (id: string): Promise<StatusResponse> => (await fetch(`${MIXNODE_PING}/${id}`)).json();
|
||||
|
||||
static fetchUptimeStoryById = async (
|
||||
id: string,
|
||||
): Promise<UptimeStoryResponse> =>
|
||||
static fetchUptimeStoryById = async (id: string): Promise<UptimeStoryResponse> =>
|
||||
(await fetch(`${UPTIME_STORY_API}/${id}/history`)).json();
|
||||
}
|
||||
|
||||
@@ -20,14 +20,7 @@ export const ContentCard: React.FC<ContentCardProps> = ({
|
||||
onClick,
|
||||
}) => (
|
||||
<Card onClick={onClick} sx={{ height: '100%' }}>
|
||||
{title && (
|
||||
<CardHeader
|
||||
title={title || ''}
|
||||
avatar={Icon}
|
||||
action={Action}
|
||||
subheader={subtitle}
|
||||
/>
|
||||
)}
|
||||
{title && <CardHeader title={title || ''} avatar={Icon} action={Action} subheader={subtitle} />}
|
||||
{children && <CardContent>{children}</CardContent>}
|
||||
{errorMsg && (
|
||||
<Typography variant="body2" sx={{ color: 'danger', padding: 2 }}>
|
||||
|
||||
@@ -2,9 +2,7 @@ import * as React from 'react';
|
||||
import { Box, Typography } from '@mui/material';
|
||||
import { ExpandLess, ExpandMore } from '@mui/icons-material';
|
||||
|
||||
export const CustomColumnHeading: React.FC<{ headingTitle: string }> = ({
|
||||
headingTitle,
|
||||
}) => {
|
||||
export const CustomColumnHeading: React.FC<{ headingTitle: string }> = ({ headingTitle }) => {
|
||||
const [filter, toggleFilter] = React.useState<boolean>(false);
|
||||
|
||||
const handleClick = () => {
|
||||
|
||||
@@ -1,13 +1,7 @@
|
||||
import * as React from 'react';
|
||||
import {
|
||||
Paper,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableContainer,
|
||||
TableHead,
|
||||
TableRow,
|
||||
} from '@mui/material';
|
||||
import { Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow } from '@mui/material';
|
||||
import { CopyToClipboard } from '@nymproject/react';
|
||||
import { Box } from '@mui/system';
|
||||
import { cellStyles } from './Universal-DataGrid';
|
||||
import { currencyToString } from '../utils/currency';
|
||||
import { MixnodeRowType } from './MixNodes';
|
||||
@@ -27,6 +21,18 @@ export interface UniversalTableProps {
|
||||
}
|
||||
|
||||
function formatCellValues(val: string | number, field: string) {
|
||||
if (field === 'identity_key' && typeof val === 'string') {
|
||||
return (
|
||||
<Box display="flex" justifyContent="flex-end">
|
||||
<CopyToClipboard
|
||||
sx={{ mr: 1, mt: 0.5, fontSize: '18px' }}
|
||||
value={val}
|
||||
tooltip={`Copy identity key ${val} to clipboard`}
|
||||
/>
|
||||
<span>{val}</span>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
if (field === 'bond') {
|
||||
return currencyToString(val.toString());
|
||||
}
|
||||
@@ -51,10 +57,7 @@ export const DetailTable: React.FC<{
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{rows.map((eachRow) => (
|
||||
<TableRow
|
||||
key={eachRow.id}
|
||||
sx={{ '&:last-child td, &:last-child th': { border: 0 } }}
|
||||
>
|
||||
<TableRow key={eachRow.id} sx={{ '&:last-child td, &:last-child th': { border: 0 } }}>
|
||||
{columnsData?.map((_, index) => (
|
||||
<TableCell
|
||||
key={_.title}
|
||||
@@ -69,10 +72,7 @@ export const DetailTable: React.FC<{
|
||||
}}
|
||||
data-testid={`${_.title.replace(/ /g, '-')}-value`}
|
||||
>
|
||||
{formatCellValues(
|
||||
eachRow[columnsData[index].field],
|
||||
columnsData[index].field,
|
||||
)}
|
||||
{formatCellValues(eachRow[columnsData[index].field], columnsData[index].field)}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
|
||||
@@ -39,7 +39,7 @@ export const Footer: React.FC = () => {
|
||||
sx={{
|
||||
fontSize: 12,
|
||||
textAlign: isMobile ? 'center' : 'end',
|
||||
color: theme.palette.nym.text.footer,
|
||||
color: theme.palette.nym.muted.onDarkBg,
|
||||
}}
|
||||
>
|
||||
© {new Date().getFullYear()} Nym Technologies SA, all rights reserved
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import * as React from 'react';
|
||||
import { GatewayResponse } from '../typeDefs/explorer-api';
|
||||
|
||||
export type GatewayRowType = {
|
||||
@@ -10,9 +9,7 @@ export type GatewayRowType = {
|
||||
location: string;
|
||||
};
|
||||
|
||||
export function gatewayToGridRow(
|
||||
arrayOfGateways: GatewayResponse,
|
||||
): GatewayRowType[] {
|
||||
export function gatewayToGridRow(arrayOfGateways: GatewayResponse): GatewayRowType[] {
|
||||
return !arrayOfGateways
|
||||
? []
|
||||
: arrayOfGateways.map((gw) => ({
|
||||
@@ -1,15 +1,14 @@
|
||||
import * as React from 'react';
|
||||
import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline';
|
||||
import PauseCircleOutlineIcon from '@mui/icons-material/PauseCircleOutline';
|
||||
import CircleOutlinedIcon from '@mui/icons-material/CircleOutlined';
|
||||
import { MixnodeStatus } from '../typeDefs/explorer-api';
|
||||
|
||||
export const Icons = {
|
||||
mixnodes: {
|
||||
status: {
|
||||
active: CheckCircleOutlineIcon,
|
||||
standby: PauseCircleOutlineIcon,
|
||||
inactive: CircleOutlinedIcon,
|
||||
Mixnodes: {
|
||||
Status: {
|
||||
Active: CheckCircleOutlineIcon,
|
||||
Standby: PauseCircleOutlineIcon,
|
||||
Inactive: CircleOutlinedIcon,
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -18,12 +17,12 @@ export const getMixNodeIcon = (value: any) => {
|
||||
if (value && typeof value === 'string') {
|
||||
switch (value) {
|
||||
case MixnodeStatus.active:
|
||||
return Icons.mixnodes.status.active;
|
||||
return Icons.Mixnodes.Status.Active;
|
||||
case MixnodeStatus.standby:
|
||||
return Icons.mixnodes.status.standby;
|
||||
return Icons.Mixnodes.Status.Standby;
|
||||
default:
|
||||
return Icons.mixnodes.status.inactive;
|
||||
return Icons.Mixnodes.Status.Inactive;
|
||||
}
|
||||
}
|
||||
return Icons.mixnodes.status.inactive;
|
||||
return Icons.Mixnodes.Status.Inactive;
|
||||
};
|
||||
@@ -14,8 +14,7 @@ import { useMixnodeContext } from '../../context/mixnode';
|
||||
|
||||
export const BondBreakdownTable: React.FC = () => {
|
||||
const { mixNode, delegations } = useMixnodeContext();
|
||||
const [showDelegations, toggleShowDelegations] =
|
||||
React.useState<boolean>(false);
|
||||
const [showDelegations, toggleShowDelegations] = React.useState<boolean>(false);
|
||||
|
||||
const [bonds, setBonds] = React.useState({
|
||||
delegations: '0',
|
||||
@@ -43,9 +42,7 @@ export const BondBreakdownTable: React.FC = () => {
|
||||
// bonds total (del + pledges)
|
||||
const pledgesSum = Number(mixNode.data.pledge_amount.amount);
|
||||
const delegationsSum = Number(mixNode.data.total_delegation.amount);
|
||||
const bondsTotal = currencyToString(
|
||||
(delegationsSum + pledgesSum).toString(),
|
||||
);
|
||||
const bondsTotal = currencyToString((delegationsSum + pledgesSum).toString());
|
||||
|
||||
setBonds({
|
||||
delegations: decimalisedDelegations,
|
||||
@@ -79,13 +76,10 @@ export const BondBreakdownTable: React.FC = () => {
|
||||
return <Alert severity="error">Mixnode not found</Alert>;
|
||||
}
|
||||
if (delegations?.error) {
|
||||
return (
|
||||
<Alert severity="error">Unable to get delegations for mixnode</Alert>
|
||||
);
|
||||
return <Alert severity="error">Unable to get delegations for mixnode</Alert>;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<TableContainer component={Paper}>
|
||||
<Table sx={{ minWidth: 650 }} aria-label="bond breakdown totals">
|
||||
<TableBody>
|
||||
@@ -118,9 +112,7 @@ export const BondBreakdownTable: React.FC = () => {
|
||||
}}
|
||||
>
|
||||
Delegation total {'\u00A0'}
|
||||
{delegations?.data && delegations?.data?.length > 0 && (
|
||||
<ExpandMore />
|
||||
)}
|
||||
{delegations?.data && delegations?.data?.length > 0 && <ExpandMore />}
|
||||
</Box>
|
||||
</TableCell>
|
||||
<TableCell align="left" data-testid="delegation-total-amount">
|
||||
@@ -140,16 +132,10 @@ export const BondBreakdownTable: React.FC = () => {
|
||||
<Table stickyHeader>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell
|
||||
sx={{ fontWeight: 600, background: '#242C3D' }}
|
||||
align="left"
|
||||
>
|
||||
<TableCell sx={{ fontWeight: 600, background: '#242C3D' }} align="left">
|
||||
Delegators
|
||||
</TableCell>
|
||||
<TableCell
|
||||
sx={{ fontWeight: 600, background: '#242C3D' }}
|
||||
align="left"
|
||||
>
|
||||
<TableCell sx={{ fontWeight: 600, background: '#242C3D' }} align="left">
|
||||
Stake
|
||||
</TableCell>
|
||||
<TableCell
|
||||
@@ -166,29 +152,19 @@ export const BondBreakdownTable: React.FC = () => {
|
||||
</TableHead>
|
||||
|
||||
<TableBody>
|
||||
{delegations?.data?.map(
|
||||
({ owner, amount: { amount, denom } }) => (
|
||||
{delegations?.data?.map(({ owner, amount: { amount, denom } }) => (
|
||||
<TableRow key={owner}>
|
||||
<TableCell
|
||||
sx={matches ? { width: 190 } : null}
|
||||
align="left"
|
||||
>
|
||||
<TableCell sx={matches ? { width: 190 } : null} align="left">
|
||||
{owner}
|
||||
</TableCell>
|
||||
<TableCell align="left">
|
||||
{currencyToString(amount.toString(), denom)}
|
||||
</TableCell>
|
||||
<TableCell align="left">
|
||||
{calcBondPercentage(amount)}%
|
||||
</TableCell>
|
||||
<TableCell align="left">{currencyToString(amount.toString(), denom)}</TableCell>
|
||||
<TableCell align="left">{calcBondPercentage(amount)}%</TableCell>
|
||||
</TableRow>
|
||||
),
|
||||
)}
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Box>
|
||||
)}
|
||||
</TableContainer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -11,17 +11,11 @@ interface MixNodeDetailProps {
|
||||
mixnodeDescription: MixNodeDescriptionResponse;
|
||||
}
|
||||
|
||||
export const MixNodeDetailSection: React.FC<MixNodeDetailProps> = ({
|
||||
mixNodeRow,
|
||||
mixnodeDescription,
|
||||
}) => {
|
||||
export const MixNodeDetailSection: React.FC<MixNodeDetailProps> = ({ mixNodeRow, mixnodeDescription }) => {
|
||||
const theme = useTheme();
|
||||
const palette = [theme.palette.text.primary];
|
||||
const matches = useMediaQuery(theme.breakpoints.down('sm'));
|
||||
const statusText = React.useMemo(
|
||||
() => getMixNodeStatusText(mixNodeRow.status),
|
||||
[mixNodeRow.status],
|
||||
);
|
||||
const statusText = React.useMemo(() => getMixNodeStatusText(mixNodeRow.status), [mixNodeRow.status]);
|
||||
return (
|
||||
<Grid container>
|
||||
<Grid item xs={12} sm={6}>
|
||||
@@ -40,17 +34,11 @@ export const MixNodeDetailSection: React.FC<MixNodeDetailProps> = ({
|
||||
placeItems: 'center',
|
||||
}}
|
||||
>
|
||||
<Identicon
|
||||
size={43}
|
||||
string={mixNodeRow.identity_key}
|
||||
palette={palette}
|
||||
/>
|
||||
<Identicon size={43} string={mixNodeRow.identity_key} palette={palette} />
|
||||
</Box>
|
||||
<Box ml={2}>
|
||||
<Typography fontSize={21}>{mixnodeDescription.name}</Typography>
|
||||
<Typography>
|
||||
{(mixnodeDescription.description || '').slice(0, 1000)}
|
||||
</Typography>
|
||||
<Typography>{(mixnodeDescription.description || '').slice(0, 1000)}</Typography>
|
||||
<Button
|
||||
component="a"
|
||||
variant="text"
|
||||
@@ -76,14 +64,7 @@ export const MixNodeDetailSection: React.FC<MixNodeDetailProps> = ({
|
||||
</Box>
|
||||
</Box>
|
||||
</Grid>
|
||||
<Grid
|
||||
item
|
||||
xs={12}
|
||||
sm={6}
|
||||
display="flex"
|
||||
justifyContent="end"
|
||||
mt={matches ? 5 : undefined}
|
||||
>
|
||||
<Grid item xs={12} sm={6} display="flex" justifyContent="end" mt={matches ? 5 : undefined}>
|
||||
<Box display="flex" flexDirection="column">
|
||||
<Typography fontWeight="600" alignSelf="self-end">
|
||||
Node status:
|
||||
@@ -91,12 +72,7 @@ export const MixNodeDetailSection: React.FC<MixNodeDetailProps> = ({
|
||||
<Box mt={2} alignSelf="self-end">
|
||||
<MixNodeStatus status={mixNodeRow.status} />
|
||||
</Box>
|
||||
<Typography
|
||||
mt={1}
|
||||
alignSelf="self-end"
|
||||
color={theme.palette.text.secondary}
|
||||
fontSize="smaller"
|
||||
>
|
||||
<Typography mt={1} alignSelf="self-end" color={theme.palette.text.secondary} fontSize="smaller">
|
||||
This node is {statusText} in this epoch
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { Typography } from '@mui/material';
|
||||
import * as React from 'react';
|
||||
import { Theme, useTheme } from '@mui/material/styles';
|
||||
import { MixnodeRowType } from '.';
|
||||
import { getMixNodeIcon } from '../Icons';
|
||||
import { MixnodeStatus } from '../../typeDefs/explorer-api';
|
||||
|
||||
@@ -9,21 +8,6 @@ interface MixNodeStatusProps {
|
||||
status: MixnodeStatus;
|
||||
}
|
||||
|
||||
export const MixNodeStatus: React.FC<MixNodeStatusProps> = ({ status }) => {
|
||||
const theme = useTheme();
|
||||
const Icon = React.useMemo(() => getMixNodeIcon(status), [status]);
|
||||
const color = React.useMemo(() => getMixNodeStatusColor(theme, status), [status, theme]);
|
||||
|
||||
return (
|
||||
<Typography color={color} display="flex" alignItems="center">
|
||||
<Icon />
|
||||
<Typography ml={1} component="span" color="inherit">
|
||||
{`${status[0].toUpperCase()}${status.slice(1)}`}
|
||||
</Typography>
|
||||
</Typography>
|
||||
);
|
||||
};
|
||||
|
||||
export const getMixNodeStatusColor = (theme: Theme, status: MixnodeStatus) => {
|
||||
let color;
|
||||
switch (status) {
|
||||
@@ -51,3 +35,18 @@ export const getMixNodeStatusText = (status: MixnodeStatus) => {
|
||||
return 'inactive';
|
||||
}
|
||||
};
|
||||
|
||||
export const MixNodeStatus: React.FC<MixNodeStatusProps> = ({ status }) => {
|
||||
const theme = useTheme();
|
||||
const Icon = React.useMemo(() => getMixNodeIcon(status), [status]);
|
||||
const color = React.useMemo(() => getMixNodeStatusColor(theme, status), [status, theme]);
|
||||
|
||||
return (
|
||||
<Typography color={color} display="flex" alignItems="center">
|
||||
<Icon />
|
||||
<Typography ml={1} component="span" color="inherit">
|
||||
{`${status[0].toUpperCase()}${status.slice(1)}`}
|
||||
</Typography>
|
||||
</Typography>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -5,10 +5,7 @@ import { SelectInputProps } from '@mui/material/Select/SelectInput';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import { SxProps } from '@mui/system';
|
||||
import { MixNodeStatus } from './Status';
|
||||
import {
|
||||
MixnodeStatus,
|
||||
MixnodeStatusWithAll,
|
||||
} from '../../typeDefs/explorer-api';
|
||||
import { MixnodeStatus, MixnodeStatusWithAll } from '../../typeDefs/explorer-api';
|
||||
|
||||
// TODO: replace with i18n
|
||||
const ALL_NODES = 'All nodes';
|
||||
@@ -19,18 +16,11 @@ interface MixNodeStatusDropdownProps {
|
||||
onSelectionChanged?: (status?: MixnodeStatusWithAll) => void;
|
||||
}
|
||||
|
||||
export const MixNodeStatusDropdown: React.FC<MixNodeStatusDropdownProps> = ({
|
||||
status,
|
||||
onSelectionChanged,
|
||||
sx,
|
||||
}) => {
|
||||
export const MixNodeStatusDropdown: React.FC<MixNodeStatusDropdownProps> = ({ status, onSelectionChanged, sx }) => {
|
||||
const theme = useTheme();
|
||||
const matches = useMediaQuery(theme.breakpoints.down('sm'));
|
||||
const [statusValue, setStatusValue] = React.useState<MixnodeStatusWithAll>(
|
||||
status || MixnodeStatusWithAll.all,
|
||||
);
|
||||
const onChange: SelectInputProps<MixnodeStatusWithAll>['onChange'] =
|
||||
React.useCallback(
|
||||
const [statusValue, setStatusValue] = React.useState<MixnodeStatusWithAll>(status || MixnodeStatusWithAll.all);
|
||||
const onChange: SelectInputProps<MixnodeStatusWithAll>['onChange'] = React.useCallback(
|
||||
({ target: { value } }) => {
|
||||
setStatusValue(value);
|
||||
if (onSelectionChanged) {
|
||||
@@ -61,28 +51,16 @@ export const MixNodeStatusDropdown: React.FC<MixNodeStatusDropdownProps> = ({
|
||||
...sx,
|
||||
}}
|
||||
>
|
||||
<MenuItem
|
||||
value={MixnodeStatus.active}
|
||||
data-testid="mixnodeStatusSelectOption_active"
|
||||
>
|
||||
<MenuItem value={MixnodeStatus.active} data-testid="mixnodeStatusSelectOption_active">
|
||||
<MixNodeStatus status={MixnodeStatus.active} />
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
value={MixnodeStatus.standby}
|
||||
data-testid="mixnodeStatusSelectOption_standby"
|
||||
>
|
||||
<MenuItem value={MixnodeStatus.standby} data-testid="mixnodeStatusSelectOption_standby">
|
||||
<MixNodeStatus status={MixnodeStatus.standby} />
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
value={MixnodeStatus.inactive}
|
||||
data-testid="mixnodeStatusSelectOption_inactive"
|
||||
>
|
||||
<MenuItem value={MixnodeStatus.inactive} data-testid="mixnodeStatusSelectOption_inactive">
|
||||
<MixNodeStatus status={MixnodeStatus.inactive} />
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
value={MixnodeStatusWithAll.all}
|
||||
data-testid="mixnodeStatusSelectOption_allNodes"
|
||||
>
|
||||
<MenuItem value={MixnodeStatusWithAll.all} data-testid="mixnodeStatusSelectOption_allNodes">
|
||||
{ALL_NODES}
|
||||
</MenuItem>
|
||||
</Select>
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
/* eslint-disable camelcase */
|
||||
import {
|
||||
MixNodeResponse,
|
||||
MixNodeResponseItem,
|
||||
MixnodeStatus,
|
||||
} from '../../typeDefs/explorer-api';
|
||||
import { MixNodeResponse, MixNodeResponseItem, MixnodeStatus } from '../../typeDefs/explorer-api';
|
||||
|
||||
export type MixnodeRowType = {
|
||||
id: string;
|
||||
@@ -17,15 +13,11 @@ export type MixnodeRowType = {
|
||||
layer: string;
|
||||
};
|
||||
|
||||
export function mixnodeToGridRow(
|
||||
arrayOfMixnodes?: MixNodeResponse,
|
||||
): MixnodeRowType[] {
|
||||
export function mixnodeToGridRow(arrayOfMixnodes?: MixNodeResponse): MixnodeRowType[] {
|
||||
return (arrayOfMixnodes || []).map(mixNodeResponseItemToMixnodeRowType);
|
||||
}
|
||||
|
||||
export function mixNodeResponseItemToMixnodeRowType(
|
||||
item: MixNodeResponseItem,
|
||||
): MixnodeRowType {
|
||||
export function mixNodeResponseItemToMixnodeRowType(item: MixNodeResponseItem): MixnodeRowType {
|
||||
const pledge = Number(item.pledge_amount.amount) || 0;
|
||||
const delegations = Number(item.total_delegation.amount) || 0;
|
||||
const totalBond = pledge + delegations;
|
||||
|
||||
@@ -16,9 +16,9 @@ import {
|
||||
Typography,
|
||||
} from '@mui/material';
|
||||
import { Menu } from '@mui/icons-material';
|
||||
import { NymLogoSVG } from 'src/icons/NymLogoSVG';
|
||||
import { useMainContext } from 'src/context/main';
|
||||
import { MobileDrawerClose } from 'src/icons/MobileDrawerClose';
|
||||
import { NymLogo } from '@nymproject/react';
|
||||
import { useMainContext } from '../context/main';
|
||||
import { MobileDrawerClose } from '../icons/MobileDrawerClose';
|
||||
import { Footer } from './Footer';
|
||||
import { NYM_WEBSITE } from '../api/constants';
|
||||
import { ExpandableButton } from './Nav';
|
||||
@@ -28,9 +28,7 @@ type MobileNavProps = {
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
export const MobileNav: React.FC<{ children: React.ReactNode }> = ({
|
||||
children,
|
||||
}: MobileNavProps) => {
|
||||
export const MobileNav: React.FC<{ children: React.ReactNode }> = ({ children }: MobileNavProps) => {
|
||||
const theme = useTheme();
|
||||
const { navState, updateNavState } = useMainContext();
|
||||
const [drawerOpen, setDrawerOpen] = React.useState(false);
|
||||
@@ -73,7 +71,7 @@ export const MobileNav: React.FC<{ children: React.ReactNode }> = ({
|
||||
}}
|
||||
>
|
||||
<IconButton component="a" href={NYM_WEBSITE} target="_blank">
|
||||
<NymLogoSVG />
|
||||
<NymLogo height="40px" width="40px" />
|
||||
</IconButton>
|
||||
<Typography
|
||||
variant="h6"
|
||||
@@ -85,12 +83,7 @@ export const MobileNav: React.FC<{ children: React.ReactNode }> = ({
|
||||
ml: 2,
|
||||
}}
|
||||
>
|
||||
<MuiLink
|
||||
component={Link}
|
||||
to="/overview"
|
||||
underline="none"
|
||||
color="inherit"
|
||||
>
|
||||
<MuiLink component={Link} to="/overview" underline="none" color="inherit">
|
||||
Network Explorer
|
||||
</MuiLink>
|
||||
</Typography>
|
||||
|
||||
@@ -14,10 +14,10 @@ import IconButton from '@mui/material/IconButton';
|
||||
import ListItemButton from '@mui/material/ListItemButton';
|
||||
import ListItemIcon from '@mui/material/ListItemIcon';
|
||||
import ListItemText from '@mui/material/ListItemText';
|
||||
import { NymLogoSVG } from 'src/icons/NymLogoSVG';
|
||||
import { BIG_DIPPER, NYM_WEBSITE } from 'src/api/constants';
|
||||
import { useMainContext } from 'src/context/main';
|
||||
import { MobileDrawerClose } from 'src/icons/MobileDrawerClose';
|
||||
import { NymLogo } from '@nymproject/react';
|
||||
import { BIG_DIPPER, NYM_WEBSITE } from '../api/constants';
|
||||
import { useMainContext } from '../context/main';
|
||||
import { MobileDrawerClose } from '../icons/MobileDrawerClose';
|
||||
import { OverviewSVG } from '../icons/OverviewSVG';
|
||||
import { NetworkComponentsSVG } from '../icons/NetworksSVG';
|
||||
import { NodemapSVG } from '../icons/NodemapSVG';
|
||||
@@ -70,17 +70,17 @@ const Drawer = styled(MuiDrawer, {
|
||||
}),
|
||||
}));
|
||||
|
||||
type navOptionType = {
|
||||
type NavOptionType = {
|
||||
id: number;
|
||||
isActive?: boolean;
|
||||
url: string;
|
||||
title: string;
|
||||
Icon?: React.ReactNode;
|
||||
nested?: navOptionType[];
|
||||
nested?: NavOptionType[];
|
||||
isExpandedChild?: boolean;
|
||||
};
|
||||
|
||||
export const originalNavOptions: navOptionType[] = [
|
||||
export const originalNavOptions: NavOptionType[] = [
|
||||
{
|
||||
id: 0,
|
||||
isActive: false,
|
||||
@@ -127,7 +127,7 @@ type ExpandableButtonType = {
|
||||
url: string;
|
||||
isActive?: boolean;
|
||||
Icon?: React.ReactNode;
|
||||
nested?: navOptionType[];
|
||||
nested?: NavOptionType[];
|
||||
isChild?: boolean;
|
||||
openDrawer: () => void;
|
||||
closeDrawer?: () => void;
|
||||
@@ -226,9 +226,7 @@ export const ExpandableButton: React.FC<ExpandableButtonType> = ({
|
||||
sx={{
|
||||
pt: 2,
|
||||
pb: 2,
|
||||
background: isChild
|
||||
? palette.nym.networkExplorer.nav.selected.nested
|
||||
: 'none',
|
||||
background: isChild ? palette.nym.networkExplorer.nav.selected.nested : 'none',
|
||||
}}
|
||||
>
|
||||
<ListItemIcon>{Icon}</ListItemIcon>
|
||||
@@ -334,7 +332,7 @@ export const Nav: React.FC = ({ children }) => {
|
||||
}}
|
||||
>
|
||||
<IconButton component="a" href={NYM_WEBSITE} target="_blank">
|
||||
<NymLogoSVG />
|
||||
<NymLogo height="40px" width="40px" />
|
||||
</IconButton>
|
||||
<Typography
|
||||
variant="h6"
|
||||
@@ -345,12 +343,7 @@ export const Nav: React.FC = ({ children }) => {
|
||||
fontWeight: 600,
|
||||
}}
|
||||
>
|
||||
<MuiLink
|
||||
component={Link}
|
||||
to="/overview"
|
||||
underline="none"
|
||||
color="inherit"
|
||||
>
|
||||
<MuiLink component={Link} to="/overview" underline="none" color="inherit">
|
||||
Network Explorer
|
||||
</MuiLink>
|
||||
</Typography>
|
||||
@@ -408,11 +401,7 @@ export const Nav: React.FC = ({ children }) => {
|
||||
</IconButton>
|
||||
</DrawerHeader>
|
||||
|
||||
<List
|
||||
sx={{ pt: 0, pb: 0 }}
|
||||
onMouseEnter={tempDrawerOpen}
|
||||
onMouseLeave={tempDrawerClose}
|
||||
>
|
||||
<List sx={{ pt: 0, pb: 0 }} onMouseEnter={tempDrawerOpen} onMouseLeave={tempDrawerClose}>
|
||||
{navState.map((props) => (
|
||||
<ExpandableButton
|
||||
key={props.url}
|
||||
|
||||
@@ -19,38 +19,18 @@ export const Socials: React.FC<{ isFooter?: boolean }> = ({ isFooter }) => {
|
||||
: theme.palette.nym.networkExplorer.topNav.socialIcons;
|
||||
return (
|
||||
<Box>
|
||||
<IconButton
|
||||
component="a"
|
||||
href={TELEGRAM_LINK}
|
||||
target="_blank"
|
||||
data-testid="telegram"
|
||||
>
|
||||
<IconButton component="a" href={TELEGRAM_LINK} target="_blank" data-testid="telegram">
|
||||
<TelegramIcon color={color} size={24} />
|
||||
</IconButton>
|
||||
{false && (
|
||||
<IconButton
|
||||
component="a"
|
||||
href={DISCORD_LINK}
|
||||
target="_blank"
|
||||
data-testid="discord"
|
||||
>
|
||||
<IconButton component="a" href={DISCORD_LINK} target="_blank" data-testid="discord">
|
||||
<DiscordIcon color={color} size={24} />
|
||||
</IconButton>
|
||||
)}
|
||||
<IconButton
|
||||
component="a"
|
||||
href={TWITTER_LINK}
|
||||
target="_blank"
|
||||
data-testid="twitter"
|
||||
>
|
||||
<IconButton component="a" href={TWITTER_LINK} target="_blank" data-testid="twitter">
|
||||
<TwitterIcon color={color} size={24} />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
component="a"
|
||||
href={GITHUB_LINK}
|
||||
target="_blank"
|
||||
data-testid="github"
|
||||
>
|
||||
<IconButton component="a" href={GITHUB_LINK} target="_blank" data-testid="github">
|
||||
<GitHubIcon color={color} size={24} />
|
||||
</IconButton>
|
||||
</Box>
|
||||
|
||||
@@ -11,14 +11,7 @@ interface StatsCardProps {
|
||||
onClick?: () => void;
|
||||
color?: string;
|
||||
}
|
||||
export const StatsCard: React.FC<StatsCardProps> = ({
|
||||
icon,
|
||||
title,
|
||||
count,
|
||||
onClick,
|
||||
errorMsg,
|
||||
color: colorProp,
|
||||
}) => {
|
||||
export const StatsCard: React.FC<StatsCardProps> = ({ icon, title, count, onClick, errorMsg, color: colorProp }) => {
|
||||
const theme = useTheme();
|
||||
const color = colorProp || theme.palette.text.primary;
|
||||
return (
|
||||
@@ -38,21 +31,10 @@ export const StatsCard: React.FC<StatsCardProps> = ({
|
||||
<Box display="flex" alignItems="center" color={color}>
|
||||
<Box display="flex">
|
||||
{icon}
|
||||
<Typography
|
||||
ml={3}
|
||||
mr={0.75}
|
||||
fontSize="inherit"
|
||||
fontWeight="inherit"
|
||||
data-testid={`${title}-amount`}
|
||||
>
|
||||
<Typography ml={3} mr={0.75} fontSize="inherit" fontWeight="inherit" data-testid={`${title}-amount`}>
|
||||
{count === undefined || count === null ? '' : count}
|
||||
</Typography>
|
||||
<Typography
|
||||
mr={1}
|
||||
fontSize="inherit"
|
||||
fontWeight="inherit"
|
||||
data-testid={title}
|
||||
>
|
||||
<Typography mr={1} fontSize="inherit" fontWeight="inherit" data-testid={title}>
|
||||
{title}
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user